/* dircproxy
* Copyright (C) 2002 Scott James Remnant <scott@netsplit.com>.
* 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 <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;
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;
}
syntax highlighted by Code2HTML, v. 0.9.1