/* dircproxy
* Copyright (C) 2000,2001,2002,2003 Scott James Remnant <scott@netsplit.com>.
* Copyright (C) 2004, 2005 Francois Harvey <fharvey at securiweb dot net>
*
* 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 <stdio.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <netdb.h>
#include <dircproxy.h>
#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
}
syntax highlighted by Code2HTML, v. 0.9.1