/* 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 #include /* 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); }