/* dircproxy * Copyright (C) 2000,2001,2002,2003 Scott James Remnant . * Copyright (C) 2004, 2005 Francois Harvey * * 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.15 2002/12/29 21:30:11 scott 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 #include "sprintf.h" #include "dns.h" /* Structure used to hold information about a dns child */ struct dnschild { pid_t pid; int pipe; const char *ip; dns_fun_t function; void *boundto; void *data; struct dnschild *next; }; /* Reply generated by a child */ struct dnsresult { int success; char ip[40]; char name[DNS_MAX_HOSTLEN]; }; /* forward declarations */ static struct dnsresult _dns_lookup(const char *, const char *); static int _dns_startrequest(void *, dns_fun_t, void *, const char *, const char *); /* Children */ static struct dnschild *dnschildren = 0; /* Function that looks up DNS request */ static struct dnsresult _dns_lookup(const char *name, const char *ip) { struct dnsresult res; pid_t pid; memset(&res, 0, sizeof(struct dnsresult)); pid = getpid(); if (name) { debug("%d: Looking up IP for '%s'", pid, name); if (dns_getip(name, res.ip)) { strncpy(res.name, name, sizeof(res.name)); res.name[sizeof(res.name) - 1] = '\0'; res.success = 1; } } else if (ip) { debug("%d: Lookup up name for '%s'", pid, ip); if (dns_getname(ip, res.name, sizeof(res.name))) { strncpy(res.ip, ip, sizeof(res.ip)); res.ip[sizeof(res.ip) - 1] = '\0'; res.success = 1; } } if (res.success) debug("%d: Got '%s' (%s)", pid, res.name, res.ip); else debug("%d: No result", pid); return res; } /* Function that starts a non-blocking DNS request. */ static int _dns_startrequest(void *boundto, dns_fun_t function, void *data, const char *ip, 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->ip = ip; 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, ip); 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; char *ip; 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; ip = 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); ip = result.ip; 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 lookup terminated abnormally", pid); } /* If DNS failed, but we were looking up an IP address, fill that */ if (!ip && child->ip) { strcpy(result.ip, child->ip); ip = result.ip; } /* If DNS failed but we have an IP, fill the name with the IP */ if (ip && (!name || !strlen(name))) { strncpy(result.name, ip, sizeof(result.name)); result.name[sizeof(result.name) - 1] = '\0'; debug("%d: Changed name to '%s'", pid, result.name); name = result.name; } /* Call the function */ child->function(child->boundto, child->data, ip, 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, dns_fun_t function) { return _dns_startrequest(boundto, function, data, 0, name); } /* Returns the hostname of an IP address */ int dns_hostfromaddr(void *boundto, void *data, const char *ip, dns_fun_t function) { return _dns_startrequest(boundto, function, data, ip, 0); } /* Fill a sockaddr_in from a hostname or hostname:port combo thing */ int dns_filladdr(void *boundto, const char *name, const char *defaultport, SOCKADDR *result, dns_fun_t function) { int ret = 0; char host[DNS_MAX_HOSTLEN]; char portbuf[32]; int port; memset(result, 0, sizeof(SOCKADDR)); host[0] = '\0'; /* 1. IPv6 [addr]:port */ if ((sscanf(name, "[%39[^]]]:%31s", host, portbuf) == 2) || /* 2. host/ipv4:port */ (sscanf(name, "%255[^:]:%31s", host, portbuf) == 2)) port = dns_portfromserv(portbuf); else { /* 3. just host name */ port = dns_portfromserv(defaultport); strncpy(host, name, sizeof(host)); host[sizeof(host) - 1] = '\0'; } ret = _dns_startrequest(boundto, function, (void*)port, 0, host); 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; } /* look up hostname, place string form of address into ip. * ip must be long enough to hold the result: IPv6:40, v4:16 * Returns 1 on success */ int dns_getip(const char *name, char *ip) { int isip; /* save lookup time if name is already in IP form? */ #ifdef HAVE_IPV6 struct addrinfo *head, hints; int ret = 0; union { struct in_addr v4addr; struct in6_addr v6addr; } addr_u; if (inet_pton(AF_INET, name, &addr_u.v4addr) <= 0) isip = inet_pton(AF_INET6, name, &addr_u.v6addr) > 0 ? 1 : 0; else isip = 1; #else struct in_addr inp; struct hostent *host; isip = inet_aton(name, &inp); #endif if (isip) { strcpy(ip, name); return 1; } #ifdef HAVE_IPV6 head = NULL; memset (&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; getaddrinfo (name, NULL, &hints, &head); if (head) { if (getnameinfo(head->ai_addr, head->ai_addrlen, ip, 40, NULL, 0, NI_NUMERICHOST) == 0) ret = 1; freeaddrinfo (head); } return ret; #else host = gethostbyname(name); if (host) { char *temp = inet_ntoa(*(struct in_addr *)host->h_addr); strcpy(ip, temp); return 1; } return 0; #endif } /* look up ip, place up to len bytes of name into name. * Returns 1 on success */ int dns_getname(const char *ip, char *name, int len) { #ifdef HAVE_IPV6 struct addrinfo *head = NULL, hints; int ret = 0; memset (&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_CANONNAME; getaddrinfo (ip, NULL, &hints, &head); if (head) { if (getnameinfo(head->ai_addr, head->ai_addrlen, name, len, NULL, 0, NI_NAMEREQD) == 0) ret = 1; freeaddrinfo (head); } return ret; #else struct hostent *host; struct in_addr addr; if (inet_aton (ip, &addr)) { host = gethostbyaddr((const char*)&addr, sizeof(addr), AF_INET); if (host) { strncpy(name, host->h_name, len); name[len - 1] = '\0'; return 1; } } return 0; #endif }