/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <gnet/gnet.h>

#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 <port> \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;
}


syntax highlighted by Code2HTML, v. 0.9.1