/* 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