/* dircproxy * Copyright (C) 2002 Scott James Remnant . * All Rights Reserved. * * dns.c * - non-blocking DNS lookups using callbacks * - wrappers around /etc/services lookup functions * * The non-blocking stuff is a little complex, but it means that the main * loop can continue while waiting for DNS requests to complete. Completion * of a DNS request is notified by the child death signal, so it will * interrupt the main loop to continue where you left off. * -- * @(#) $Id: dns.c,v 1.14 2001/12/21 20:15:55 keybuk Exp $ * * This file is distributed according to the GNU General Public * License. For full details, read the top of 'main.c' or the * file called COPYING that was distributed with this code. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sprintf.h" #include "dns.h" /* Structure used to hold information about a dns child */ struct dnschild { pid_t pid; int pipe; struct in_addr addr; void (*function)(void *, void *, struct in_addr *, const char *); void *boundto; void *data; struct dnschild *next; }; /* Reply generated by a child */ struct dnsresult { int success; struct in_addr addr; char name[256]; }; /* forward declarations */ static struct dnsresult _dns_lookup(const char *, struct in_addr *); static int _dns_startrequest(void *, void (*)(void *, void *, struct in_addr *, const char *), void *, struct in_addr *, const char *); /* Children */ static struct dnschild *dnschildren = 0; /* Function that looks up DNS request */ static struct dnsresult _dns_lookup(const char *name, struct in_addr *addr) { struct dnsresult res; struct hostent *info; pid_t pid; pid = getpid(); memset(&res, 0, sizeof(struct dnsresult)); if (name) { debug("%d: Looking up IP for '%s'", pid, name); info = gethostbyname(name); } else if (addr) { debug("%d: Lookup up name for '%s'", pid, inet_ntoa(*addr)); info = gethostbyaddr((char *)addr, sizeof(struct in_addr), AF_INET); } else { info = 0; } if (info) { res.success = 1; res.addr.s_addr = *((unsigned long *) info->h_addr); if (info->h_name) { strncpy(res.name, info->h_name, 255); res.name[255] = 0; } debug("%d: Got '%s' (%s)", pid, res.name, inet_ntoa(res.addr)); } else { debug("%d: No result", pid); } return res; } /* Function that starts a non-blocking DNS request. */ static int _dns_startrequest(void *boundto, void (*function)(void *, void *, struct in_addr *, const char *), void *data, struct in_addr *addr, const char *name) { struct dnschild *child; int p[2], wp[2]; /* Pipe where the result will be placed on success */ if (pipe(p)) { syscall_fail("pipe", "p", 0); function(boundto, data, 0, 0); return -1; } /* Pipe to indicate the child can go */ if (pipe(wp)) { close(p[0]); close(p[1]); syscall_fail("pipe", "wp", 0); function(boundto, data, 0, 0); return -1; } /* Allocate and place the child on the list now, to avoid race conditions */ child = (struct dnschild *)malloc(sizeof(struct dnschild)); child->pipe = p[0]; child->function = function; child->boundto = boundto; child->addr.s_addr = (addr ? addr->s_addr : 0); child->data = data; child->next = dnschildren; dnschildren = child; /* Fork */ child->pid = fork(); if (child->pid == -1) { /* Error */ syscall_fail("fork", 0, 0); dnschildren = child->next; close(p[1]); close(p[0]); close(wp[1]); close(wp[0]); free(child); function(boundto, data, 0, 0); return -1; } else if (child->pid) { /* Parent */ debug("New DNS process started, pid %d", child->pid); close(p[1]); close(wp[0]); /* Send go signal */ write(wp[1], "go", 2); close(wp[1]); return 0; } else { struct dnsresult result; char gobuf[2]; close(p[0]); close(wp[1]); /* Use ALARM to do timeouts */ signal(SIGALRM, SIG_DFL); alarm(g.dns_timeout); /* Wait for a go signal */ read(wp[0], gobuf, 2); close(wp[0]); /* Do the lookup */ result = _dns_lookup(name, addr); if (result.success) { /* Succeded, write to our parent and die */ write(p[1], (void *)&result, sizeof(struct dnsresult)); exit(0); } else { /* Didn't succeed */ exit(1); } } } /* Called to end a DNS request. 0 = Not handled, >0 = Ok, <0 = Error */ int dns_endrequest(pid_t pid, int status) { struct dnschild *lastchild, *child; struct dnsresult result; struct in_addr *addr; char *name; size_t len; /* Check to see whether this was a DNS child */ child = dnschildren; lastchild = 0; while (child) { if (child->pid == pid) break; lastchild = child; child = child->next; } if (!child) return 0; /* Remove it from the list */ if (lastchild) { lastchild->next = child->next; } else { dnschildren = child->next; } debug("%d: Was a DNS child, getting result", pid); /* Parameters to call function with */ name = 0; addr = 0; /* Read from pipe if child returned normally */ if (WIFEXITED(status)) { if (!WEXITSTATUS(status)) { len = read(child->pipe, (void *)&result, sizeof(struct dnsresult)); if (len != sizeof(struct dnsresult)) { syscall_fail("read", 0, 0); } else if (result.success) { debug("%d: Got result", pid); addr = &(result.addr); name = result.name; } else { debug("%d: DNS lookup failed", pid); } } else { debug("%d: DNS lookup returned %d", pid, WEXITSTATUS(status)); } } else if (WIFSIGNALED(status)) { debug("%d: DNS lookup terminated with signal %d", pid, WTERMSIG(status)); } else { debug("%d: DNS lookyp terminated abnormally", pid); } /* If DNS failed, but we were looking up an IP address, fill that */ if (!addr && child->addr.s_addr) { result.addr.s_addr = child->addr.s_addr; addr = &(result.addr); } /* If DNS failed but we have an IP, fill the name in with inet_ntoa() */ if (addr && (!name || !strlen(name))) { char *ip; ip = inet_ntoa(*addr); strncpy(result.name, ip, 255); result.name[255] = 0; debug("%d: Changed name to '%s'", pid, result.name); name = result.name; } /* Call the function */ child->function(child->boundto, child->data, addr, name); /* Clean up */ close(child->pipe); free(child); return 1; } /* Kill off any children associated with an ircproxy */ int dns_delall(void *b) { struct dnschild *c, *l; int numdone; l = 0; c = dnschildren; numdone = 0; while (c) { if (c->boundto == b) { struct dnschild *n; n = c->next; debug("Killing DNS process %d", c->pid); kill(c->pid, SIGKILL); close(c->pipe); free(c); if (l) { c = l->next = n; } else { c = dnschildren = n; } } else { l = c; c = c->next; } } return numdone; } /* Kill off ALL dns children */ void dns_flush(void) { struct dnschild *c; c = dnschildren; while (c) { struct dnschild *n; n = c->next; debug("Killing DNS process %d", c->pid); kill(c->pid, SIGKILL); close(c->pipe); free(c); c = n; } dnschildren = 0; } /* Returns the IP address of a hostname */ int dns_addrfromhost(void *boundto, void *data, const char *name, void (*function)(void *, void *, struct in_addr *, const char *)) { return _dns_startrequest(boundto, function, data, 0, name); } /* Returns the hostname of an IP address */ int dns_hostfromaddr(void *boundto, void *data, struct in_addr addr, void (*function)(void *, void *, struct in_addr *, const char *)) { return _dns_startrequest(boundto, function, data, &addr, 0); } /* Fill a sockaddr_in from a hostname or hostname:port combo thing */ int dns_filladdr(void *boundto, const char *name, const char *defaultport, int allowcolon, struct sockaddr_in *result, void (*function)(void *, void *, struct in_addr *, const char *), void *data) { char *addr, *port; int ret = 0; memset(result, 0, sizeof(struct sockaddr_in)); result->sin_family = AF_INET; if (defaultport) result->sin_port = dns_portfromserv(defaultport); addr = x_strdup(name); if (allowcolon) { port = strchr(addr, ':'); if (port) { *(port++) = 0; if (strlen(port)) result->sin_port = dns_portfromserv(port); } } ret = _dns_startrequest(boundto, function, data, 0, addr); free(addr); return ret; } /* Returns a network port number for a port as a string */ int dns_portfromserv(const char *serv) { struct servent *entry; entry = getservbyname(serv, "tcp"); return (entry ? entry->s_port : htons(atoi(serv))); } /* Returns a service name for a network port number */ char *dns_servfromport(int port) { struct servent *entry; char *str; entry = getservbyport(port, "tcp"); if (entry) { str = x_strdup(entry->s_name); } else { str = x_sprintf("%d", port); } return str; }