/* Connection-Reflection Protocol - Tests if listening socket is reachable * Copyright (C) 2000 David Helder * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include "util/util.h" #include "crp.h" #define MAX_BUFFER 80 /* Don't read more than 80 bytes */ #define CONN_TIMEOUT 60000 /* 60 seconds to connect max */ static void server_func (GServer* server, GServerStatus status, GConn* conn, gpointer user_data); static gboolean conn_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data); static void lookup_func (GInetAddr* inetaddr, GInetAddrAsyncStatus status, gpointer data); static gboolean conn_out_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data); typedef struct _CRPState { gint port; GInetAddrNewAsyncID addr_id; GConn* conn_in; GConn* conn_out; gboolean bad_hostname; } CRPState; int main(int argc, char** argv) { int port = 0; GInetAddr* iface; gboolean force_port; GServer* server; GMainLoop* main_loop = NULL; if (argc > 2) { g_print ("usage: %s \n", argv[0]); exit(EXIT_FAILURE); } if (argc == 2) { port = atoi(argv[1]); force_port = TRUE; } else { port = 5885; force_port = FALSE; } iface = gnet_inetaddr_new_any (); gnet_inetaddr_set_port (iface, port); /* Create the server */ server = gnet_server_new (iface, force_port, server_func, NULL); if (!server) my_error ("Could not start server on port %d\n", port); g_print ("CRP server running on port %d\n", server->port); /* Start the main loop */ main_loop = g_main_new(FALSE); g_main_run (main_loop); exit (EXIT_SUCCESS); return 0; } static void server_func (GServer* server, GServerStatus status, GConn* conn, gpointer user_data) { CRPState* state; g_return_if_fail (status == GNET_SERVER_STATUS_CONNECT); g_print ("%s:%-5d\tCONNECT\n", conn->hostname, conn->port); state = g_new0 (CRPState, 1); state->conn_in = conn; conn->func = conn_func; conn->user_data = state; gnet_conn_readline (conn, NULL, MAX_BUFFER, 0); /* Must receive request, do check, and reply in CONN_TIMEOUT ms. */ gnet_conn_timeout (conn, CONN_TIMEOUT); } static gboolean conn_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data) { CRPState* state = (CRPState*) user_data; g_return_val_if_fail (state, FALSE); switch (status) { case GNET_CONN_STATUS_READ: { gchar* hostname; gchar* port_str; gint port; /* Expect "CHECK hostname:port" */ if (strncmp (buffer, "CHECK ", sizeof("CHECK ") - 1)) goto error; /* Get the address */ hostname = &buffer[sizeof("CHECK ") - 1]; /* Get port string */ port_str = strchr (hostname, ':'); if (!port_str) goto error; *port_str = '\0'; ++port_str; port = atoi(port_str); /* Port should be more than 1024 and less than 65535 */ if (port < 1024 || port > 65535) goto error; state->port = port; g_print ("%s:%-5d\tCHECK %s:%d\n", conn->hostname, conn->port, hostname, port); /* Lookup the name */ state->addr_id = gnet_inetaddr_new_async(hostname, port, lookup_func, state); break; error: g_print ("%s:%-5d\tERROR\n", conn->hostname, conn->port); /* fall through */ } case GNET_CONN_STATUS_CONNECT: case GNET_CONN_STATUS_WRITE: case GNET_CONN_STATUS_CLOSE: case GNET_CONN_STATUS_TIMEOUT: case GNET_CONN_STATUS_ERROR: { if (state->addr_id) gnet_inetaddr_new_async_cancel(state->addr_id); conn->user_data = NULL; if (state->conn_in) gnet_conn_delete (state->conn_in, FALSE); if (state->conn_out) gnet_conn_delete (state->conn_out, FALSE); g_free (state); } } return FALSE; } static void lookup_func (GInetAddr* inetaddr, GInetAddrAsyncStatus status, gpointer data) { CRPState* state = (CRPState*) data; GInetAddr* inetaddr_out; GConn* conn_out; g_return_if_fail (state); g_return_if_fail (state->conn_in); g_return_if_fail (state->conn_in->inetaddr); /* Reset addr_id */ state->addr_id = NULL; /* If the address is bad or the address is not the same one used to connect, it's an error. The second condition prevents a host of concern from using our server to port scan an arbitrary host. The host could port scan itself or its firewall, but who cares. */ if (status != GINETADDR_ASYNC_STATUS_OK || !gnet_inetaddr_noport_equal (inetaddr, state->conn_in->inetaddr)) { state->bad_hostname = TRUE; } /* Now check the port using the incoming address. Note this won't always work. */ /* Connect to the port. */ inetaddr_out = gnet_inetaddr_clone (state->conn_in->inetaddr); gnet_inetaddr_set_port (inetaddr_out, state->port); conn_out = gnet_conn_new_inetaddr (inetaddr_out, conn_out_func, state); g_return_if_fail (conn_out); state->conn_out = conn_out; gnet_conn_connect (conn_out, 0); gnet_inetaddr_delete (inetaddr_out); } static gboolean conn_out_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data) { CRPState* state = (CRPState*) user_data; gboolean bad_port = FALSE; gchar* reply; gchar* str; g_return_val_if_fail (state, FALSE); g_return_val_if_fail (state->conn_out == conn, FALSE); if (status != GNET_CONN_STATUS_CONNECT) bad_port = TRUE; /* Get reply */ if (bad_port) { if (state->bad_hostname) reply = "BAD_PAIR"; else reply = "BAD_PORT"; } else { if (state->bad_hostname) reply = "BAD_NAME"; else reply = "GOOD"; } /* Write reply */ str = g_strdup_printf ("%s %s:%d\n", reply, conn->hostname, conn->port); g_print ("%s:%-5d\t%s %s:%d\n", state->conn_in->hostname, state->conn_in->port, reply, conn->hostname, conn->port); gnet_conn_write (state->conn_in, str, strlen(str), 0); /* Delete socket out */ gnet_conn_delete (conn, FALSE); state->conn_out = NULL; return FALSE; }