/* csinkinet.c
 * Description:
 * Author(s): Cory Stone, Jim Meier
 * Created: 05/25/2000
 * Last Modified: $Id: csinkinet.c,v 1.70 2001/02/23 19:14:52 john Exp $ */

#include "csinkinet.h"
#include <signal.h>
#include <sys/wait.h>

/* method definitions */
static void csink_inet_free (CSinkInet * sink);
static void csink_inet_open (CSinkInet * sink);

/* fd watcher callbacks for various states */
static void csink_inet_accept_action (CSink * this_sink);

/* DNS functions (should be moved somewhere else) */
static void csink_inet_remote_sync (CSinkInet * sink);
static void csink_inet_local_sync (CSinkInet * sink);
/*
static CSinkCallbackFunc csink_inet_get_on_dns_lookup_success (CSinkInet *sink);
*/
void csink_inet_on_connect (CSink * sink_);


/**csink_inet_create
 * Creates storage for an inet connection.
 * 
 * $CSinkInet The new sink.
 */
CSinkInet *
csink_inet_create (CSinkInet * old_sink)
{
  CSinkInet *sink;

  sink = g_new0 (CSinkInet, 1);
  csink_inet_init(sink);
  CSINK (sink)->csink_type = CSINK_INET_TYPE;

  if (old_sink) {
    /* Need to clone stuff out of old_sink. */
    csink_set_new_data_func (CSINK (sink), CSINK (old_sink)->on_new_data);
    csink_set_close_func    (CSINK (sink), CSINK (old_sink)->on_close);
    csink_set_connect_func  (CSINK (sink), CSINK (old_sink)->on_connect);
    csink_set_error_func    (CSINK (sink), CSINK (old_sink)->on_error);
    csink_set_user_data     (CSINK (sink), CSINK (old_sink)->user_data);
  }

  return sink;
}

/**csink_inet_init
 * Called on a newly created csink to init stuff.
 * 
 * @sink The sink being initialized.  Passed in and has elements changed.
 */
void
csink_inet_init (CSinkInet *sink)
{
  /* struct sockaddr_in addr; */
  socklen_t addrlen = sizeof(struct sockaddr_in);
  
  csink_socket_init (CSINK_SOCKET(sink), addrlen, AF_INET);
  
  sink->ip = INADDR_NONE;
  sink->port = -1;
  
  sink->localip = INADDR_ANY;
  sink->localport = -1;
  
  csink_inet_local_sync (sink);
  csink_inet_remote_sync (sink);
  
  /* Initialize function pointers. */
  CSINK(sink)->open   = (CSinkOpenFunc) csink_inet_open;
  CSINK(sink)->free   = (CSinkFreeFunc) csink_inet_free;
  CSINK(sink)->create = (CSinkCreateFunc) csink_inet_create;
  CSINK(sink)->close  = (CSinkCloseFunc) csink_inet_close;
  /* Write function: using socket's write (). */

  CSINK_SOCKET(sink)->listen = 
		(CSinkSocketListenFunc) csink_inet_listen;
  CSINK_SOCKET(sink)->accept_action = 
		(CSinkCallbackFunc) csink_inet_accept_action;
  CSINK_SOCKET(sink)->can_read_action = 
		(CSinkSocketCanReadFunc) csink_socket_can_read_action;
  CSINK_SOCKET(sink)->can_write_action = 
		(CSinkSocketCanReadFunc) csink_socket_can_write_action;
}


void csink_inet_release (CSinkInet *sink)
{
  /* Release the parent's indirect refs. */
  csink_socket_release (CSINK_SOCKET(sink));

  /* Release our indirect refs. */
  if (sink->hostname)
      g_free (sink->hostname);
  if (sink->localinterface)
      g_free (sink->localinterface);
}

static void
csink_inet_free (CSinkInet * sink)
{
    CDEBUG (("csinkinet", "freeing a csinkinet"));
    csink_inet_release (sink);	/* Free indirect refs. */
    g_free (sink);			/* Free the sink itself. */
}

void
csink_inet_open (CSinkInet * sink)
{
    CDEBUG (("csinkinet", "open, status = %i\n", sink->socket.status));

    if (sink->socket.status & SOCKET_INET_DNS_INPROGRESS) {
      CDEBUG (("csinkinet", "open, dealing with DNS lookup..\n"));
      if (!(sink->socket.status & SOCKET_CONNECT_INPROGRESS)) {
	sink->socket.status |= SOCKET_CONNECT_INPROGRESS;
	csink_inet_set_on_dns_lookup_success
	  (sink, (CSinkCallbackFunc)csink_inet_open);
	CDEBUG (("csinkinet",
		"open, queued connect after lookup completes..\n"));
      }
      return;
    }

    CSINK (sink)->flags = CSSF_TRYING;
    
    CDEBUG (("csinkinet", "open, calling socket open to finish up"));
    csink_socket_open (CSINK_SOCKET(sink));
    CDEBUG (("csinkinet", "open, returning"));
}

void csink_inet_close (CSinkInet * sink)
{
    csink_socket_close (CSINK_SOCKET (sink));

    if (CSINK_INET_TYPE == CSINK (sink)->csink_type) {
        CSINK (sink)->flags = CSSF_CLOSED;
    }
}

void
csink_inet_set_on_dns_lookup_success (CSinkInet *sink, CSinkCallbackFunc cb)
{
  sink->on_dns_lookup_success = cb;
}

#if 0
static CSinkCallbackFunc
csink_inet_get_on_dns_lookup_success (CSinkInet *sink)
{
  return sink->on_dns_lookup_success;
}
#endif



static void
csink_host_cancel_lookup(CSinkInetDNSLookup *lookup)
{
  if (lookup->active) {
    /* Close the pipe, if still open. */
    pclose (lookup->fp);
    /* Remove the watch tag, if still set. */
    csink_remove_fd (lookup->fd_watch_tag, 
		"Lookup Closed.");
    lookup->active = FALSE;
  }
}

static void
csink_host_free_lookup(CSinkInetDNSLookup *lookup)
{
  CDEBUG(("csinkinet", "freeing a lookup struct(not really)"));

  lookup->active = FALSE;

  /* free the mem */
  /* g_free(lookup); */
}


void
csink_dns_result (CSink *sink_cbdata)
{
  /* Undo our cheating. */
  CSinkInetDNSLookup *lookup = (CSinkInetDNSLookup*)sink_cbdata;
  CSinkInet *sink = lookup->sink;
  char buf[2048];
  int count, res, fd;


  CDEBUG (("csinkinet", "csink_dns_result called\n"));

  if (!lookup->active) {
    CDEBUG(("csinkinet", "csink_dns_result called for an inactive lookup\n"));
    return;
  }

  fd = fileno(lookup->fp);
  count = read (fd, buf, sizeof (buf) - 1);
  if (-1 == count) {
    CDEBUG(("csinkinet", "unable to read hostname lookup"));
    CDEBUG(("csinkinet", "error: %s", strerror(errno) ));
    csink_on_error (CSINK (sink), "DNS_LOOKUP_FAILED");
    return;
  }

  buf[count] = '\0';		/* this is to make it "safe" */

  res = pclose(lookup->fp);
  csink_remove_fd (lookup->fd_watch_tag, "DNS Result received.");
  lookup->active = FALSE;

  if (WEXITSTATUS (res) == EXIT_FAILURE) {
    CDEBUG(("csinkinet", "hostname lookup failed"));
    CDEBUG(("csinkinet", "%s", buf));

    /* Signal error. */
    csink_on_error (CSINK (sink), "DNS_LOOKUP_FAILED");
    return;
  }

  CDEBUG(("csinkinet", "csink_dns_result ip returned was %s\n", buf));

  /* Set the IP address. */
  *lookup->in_addr_target = htonl(inet_addr(buf));

  if (lookup->on_lookup) {
    lookup->on_lookup(CSINK(sink));
  }

}


/* We are using popen so we have to kill all the special
 * chars in the command. */
void normalize_resolve_cmd (gchar * resolve_cmd)
{
    char * str;

    str = resolve_cmd;

    while (*str != '\0') {
        if ('\\' == *str	||
            '\'' == *str	||
            '\"' == *str	||
            '*'  == *str	||
            '&'  == *str	||
            '#'  == *str	||
            '('  == *str	||
            ')'  == *str	||
            '|'  == *str	||
            '~'  == *str	||
            '\t' == *str	||
            '$'  == *str	||
            '>'  == *str	||
            '<'  == *str	||
            '{'  == *str	||
            '}'  == *str) {

            *str = '_';	/* Assume that want _. :) */
        }

        str++;
    }
}

static int
csink_inet_is_dotquads (char *hostname) 
{
  gchar *index;
  for(index=hostname; *index != 0; index++) {
    switch(*index) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
    case '.':
      continue;
    default:
      return FALSE;
    }
  }
  return TRUE;
}

static CSinkInetDNSLookup *
csink_host_lookup_cb (CSinkInet * sink, gchar * hostname, uint32_t *inaddr,
		      CSinkCallbackFunc cb)
{
  FILE *fp;
  gint fd;
  gchar resolve_cmd[256 + PATH_MAX + 1];
  CSinkInetDNSLookup *lookup;

  if(!hostname) {
    CDEBUG(("csinkinet", "asked to lookup null hostname.\n"));
    /* signal error */
    return NULL;
  }

  /* If hostname is all digits and periods, then just atoi it. */
  if (csink_inet_is_dotquads (hostname)) {
    *inaddr = htonl (inet_addr(hostname));
    cb (CSINK(sink));
    return NULL;
  }

  snprintf (resolve_cmd, 
	sizeof (resolve_cmd)-1, RESOLVER_BIN" -i %s", hostname);
  
  /* Need to keep malicious code from being ran when we popen(3). */
  /* resolve_cmd is changed in place. */
  normalize_resolve_cmd (resolve_cmd);

  fp = popen(resolve_cmd, "r");

  if(!fp) {
    CDEBUG(("csinkinet", "error opening resolve command\n"));
    /* signal error */
    return NULL;
  }

  /* Fileno doesn't fail if fp is good... */
  fd = fileno(fp);


  lookup = g_new0(CSinkInetDNSLookup, 1);
  lookup->active = TRUE;
  lookup->sink = sink;
  lookup->fp = fp;
  lookup->in_addr_target = inaddr;
  lookup->on_lookup = cb;


  /* Slight cheating here. */
  lookup->fd_watch_tag = 
    csink_add_fd (fd, EIO_READ | EIO_ERROR, 
		  csink_dns_result, 
		  CSINK(lookup), "Conn to host lookup program");

  return lookup;
}


static void
csink_set_remote_addr_action (CSinkInet *sink)
{
  struct sockaddr_in addr;
  guint ip;


  memset (&addr, 0, sizeof (struct sockaddr_in));
  
  addr.sin_family = AF_INET;	/* internet naming format */

  ip = htonl (sink->ip);

  /* note that this is only good for ipv4, 32bit addresses */
  memcpy ((char *) &addr.sin_addr, &ip, sizeof(uint32_t));
  addr.sin_port = htons (sink->port);

  csink_socket_set_remote_address (CSINK_SOCKET(sink),
				(struct sockaddr *)&addr);

  sink->socket.status &= ~(SOCKET_INET_DNS_INPROGRESS_REMOTE);

  if (sink->remote_lookup) {
    csink_host_free_lookup(sink->remote_lookup);
    sink->remote_lookup = NULL;
  }
  if (sink->on_dns_lookup_success) {
    sink->on_dns_lookup_success(CSINK(sink));
  }
}

static void
csink_inet_remote_sync (CSinkInet * sink)
{
  sink->ip = INADDR_NONE;

  /* Cancel any previous lookup. */
  if (sink->remote_lookup) {
    csink_host_cancel_lookup (sink->remote_lookup);
    csink_host_free_lookup (sink->remote_lookup);
    sink->remote_lookup = NULL;
  }
  
  /* This garauntees either an error or to set the address; eventually. */
  sink->remote_lookup = 
    csink_host_lookup_cb (sink, sink->hostname,  &sink->ip, 
			  (CSinkCallbackFunc)csink_set_remote_addr_action);

  if (sink->remote_lookup) {
    sink->socket.status |= SOCKET_INET_DNS_INPROGRESS_REMOTE;
  }
}


static void
csink_set_local_addr_action (CSink *sink_)
{
  struct sockaddr_in addr;

  CSinkInet *sink = CSINK_INET(sink_);

  memset (&addr, 0, sizeof (struct sockaddr_in));
  addr.sin_family = AF_INET;	/* internet naming format */

  /* note that this is only good for ipv4, 32bit addresses */
  memcpy ((char *) &addr.sin_addr, &sink->localip, sizeof(uint32_t));
  addr.sin_port = htons (sink->localport);

  csink_socket_set_local_address (CSINK_SOCKET(sink), (struct sockaddr *)&addr);

  sink->socket.status &= ~(SOCKET_INET_DNS_INPROGRESS_LOCAL);

  if (sink->local_lookup) {
    csink_host_free_lookup(sink->local_lookup);
    sink->local_lookup = NULL;
  }

  if (sink->on_dns_lookup_success)
    sink->on_dns_lookup_success(CSINK(sink));
}

static void
csink_inet_local_sync (CSinkInet * sink)
{

  /* cancel any previous lookup */
  if (sink->local_lookup) {
    csink_host_cancel_lookup (sink->local_lookup);
    csink_host_free_lookup (sink->local_lookup);
    sink->local_lookup = NULL;
  }

  sink->socket.status |= SOCKET_INET_DNS_INPROGRESS_LOCAL;
  if (sink->localinterface) {
    sink->local_lookup = 
      csink_host_lookup_cb (sink, sink->localinterface, &sink->localip, 
			    csink_set_local_addr_action);
  } else {
    sink->localip = htonl (INADDR_ANY);
    csink_set_local_addr_action (CSINK(sink));
  }
}

void
csink_inet_set_remote_host (CSinkInet * sink, const gchar * hostname)
{
    /* save the hostname */
    if (sink->hostname)
	g_free (sink->hostname);
    sink->hostname = g_strdup (hostname);

    CDEBUG (("csinkinet", "setting remote host to '%s'", hostname));

    /* magicly synch remote address structure */
    csink_inet_remote_sync (sink);
}

const gchar *
csink_inet_get_remote_host (CSinkInet * sink)
{
    return sink->hostname;
}

void
csink_inet_set_remote_port (CSinkInet * sink, const gint port)
{
    /* should have a sanity check on port here */
    sink->port = port;

    CDEBUG (("csinkinet", "setting remote port to %d", port));
    csink_inet_remote_sync (sink);
}

gint
csink_inet_get_remote_port (CSinkInet * sink)
{
    return sink->port;
}

void
csink_inet_set_local_port (CSinkInet * sink, const gint port)
{
    /* should have a sanity check on port here */
    sink->localport = port;

    csink_inet_local_sync (sink);
}

gint
csink_inet_get_local_port (CSinkInet * sink)
{
    return sink->localport;
}

void
csink_inet_set_local_interface (CSinkInet * sink, const gchar * hostname)
{
    /* save hostname */
    if (sink->localinterface)
	g_free (sink->localinterface);
    sink->localinterface = g_strdup (hostname);

    csink_inet_local_sync (sink);
}

const gchar *
csink_inet_get_local_interface (CSinkInet * sink)
{
    return sink->localinterface;
}

int
csink_inet_listen (CSinkInet *sink)
{
  /* ensure that listening is allowed right now */
  if (sink->socket.status & SOCKET_INET_DNS_INPROGRESS) { 
    CDEBUG (("csinkinet", 
	     "hostname lookup in progress, chaining on it's success.")); 
    csink_inet_set_on_dns_lookup_success (sink, 
					  (CSinkCallbackFunc)csink_inet_listen); 
    sink->socket.status |= SOCKET_CONNECT_INPROGRESS; 
    return 0; 
  } 

  return csink_socket_default_listen (CSINK_SOCKET(sink));
}


CSink *
csink_inet_do_accept (CSinkInet *sink)
{
    CSink *newsink;
    int connfd;
    struct sockaddr_in addr;
    socklen_t addrlen;

    /* Accept the new socket connection. */
    addrlen = sizeof(addr);
    connfd = accept (sink->socket.fd, (struct sockaddr *) &addr, &addrlen);

    /* check for sanity */
    if (connfd == -1) {
	csink_on_error (CSINK (sink), "NO_SOCKET");
	return NULL;
    }

    CDEBUG (("csinkinet", "connection from %s, port %d\n",
		inet_ntoa (*(struct in_addr*)&addr.sin_addr.s_addr), 
		ntohs (addr.sin_port)));    

    /* Ensure that the new socket is non-blocking; whether or not it inherits
     * this from it's associated listen() socket seems to be different from unix
     * to unix. */
    fcntl (connfd, F_SETFL, O_NONBLOCK);


    /* make a new sink but with the properties of the old. */
    newsink = csink_create (CSINK(sink));

    csink_inet_set_local_port  (CSINK_INET (newsink), sink->localport);
    csink_inet_set_remote_host (CSINK_INET (newsink), inet_ntoa (addr.sin_addr));
    csink_inet_set_remote_port (CSINK_INET (newsink), addr.sin_port);

    CSINK_SOCKET (newsink)->fd = connfd;

    CSINK_SOCKET (newsink)->status = SOCKET_CONNECTED;

    /* Save the ip address from the newly accepted connection. */
    CSINK_INET (newsink)->ip = (uint32_t) addr.sin_addr.s_addr;

    /* Mark the watch tags. */
    CSINK_SOCKET (newsink)->read_watch_tag = 
	csink_add_fd (connfd, 
		EIO_READ | EIO_ERROR, 
		CSINK_SOCKET (sink)->can_read_action, newsink,
		"read tag for inet an sink");

    /* success */
    return newsink;
}


/* A new accept()ed connection. */
static void
csink_inet_accept_action (CSink * sink_)
{
    CSinkInet *sink = CSINK_INET (sink_);
    CSink *newsink;


    CDEBUG (("csinkinet", "inet_accept on fd: %d\n", sink->socket.fd));

    newsink = csink_inet_do_accept (sink);
    if (NULL == newsink)
      return;

    CSINK (newsink)->flags = CSSF_CAN_WRITE;

    CDEBUG (("csinkinet",
		"csink_inet_accept_action calling csink_on_connect\n"));
    /* Do the on_connect callback. */
    csink_on_connect (newsink);
}



syntax highlighted by Code2HTML, v. 0.9.1