/* vim: set shiftwidth=3 softtabstop=3 expandtab: */
/*
firedns.c - firedns library
Copyright (C) 2002 Ian Gulliver
This file has been gutted and mucked with for use in BOPM - see the
real library at http://ares.penguinhosting.net/~ian/ before you judge
firedns based on this..
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "setup.h"
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include "compat.h"
#include "inet.h"
#include "malloc.h"
#include "firedns.h"
#include "config.h"
#include "list.h"
#include "log.h"
#include "dnsbl.h"
RCSID("$Id: firedns.c,v 1.21 2004/01/12 18:12:48 dg Exp $");
#define FIREDNS_TRIES 3
#define min(a,b) (a < b ? a : b)
/* Global variables */
int fdns_errno = FDNS_ERR_NONE;
unsigned int fdns_fdinuse = 0;
/* Variables local to this file */
/* up to FDNS_MAX nameservers; populated by firedns_init() */
static struct in_addr servers4[FDNS_MAX];
/* actual count of nameservers; set by firedns_init() */
static int i4;
#ifdef IPV6
static int i6;
static struct in6_addr servers6[FDNS_MAX];
#endif
/*
* linked list of open DNS queries; populated by firedns_add_query(),
* decimated by firedns_getresult()
*/
static list_t *CONNECTIONS = NULL;
/*
* List of errors, in order of values used in FDNS_ERR_*, returned by
* firedns_strerror
*/
static char *errors[] = {
"Success",
"Format error",
"Server failure",
"Name error",
"Not implemented",
"Refused",
"Timeout",
"Network error",
"FD Limit reached",
"Unknown error"
};
/* Structures */
/* open DNS query */
struct s_connection
{
/*
* unique ID (random number), matches header ID; both set by
* firedns_add_query()
*/
unsigned char id[2];
unsigned short class;
unsigned short type;
/* file descriptor returned from sockets */
int fd;
void *info;
time_t start;
char lookup[256];
#ifdef IPV6
int v6;
#endif
};
struct s_rr_middle
{
unsigned short type;
unsigned short class;
/* XXX - firedns depends on this being 4 bytes */
uint32 ttl;
unsigned short rdlength;
};
/* DNS query header */
struct s_header
{
unsigned char id[2];
unsigned char flags1;
#define FLAGS1_MASK_QR 0x80
/* bitshift right 3 */
#define FLAGS1_MASK_OPCODE 0x78
#define FLAGS1_MASK_AA 0x04
#define FLAGS1_MASK_TC 0x02
#define FLAGS1_MASK_RD 0x01
unsigned char flags2;
#define FLAGS2_MASK_RA 0x80
#define FLAGS2_MASK_Z 0x70
#define FLAGS2_MASK_RCODE 0x0f
unsigned short qdcount;
unsigned short ancount;
unsigned short nscount;
unsigned short arcount;
/* DNS question, populated by firedns_build_query_payload() */
unsigned char payload[512];
};
/* Function prototypes */
static struct s_connection *firedns_add_query(void);
static int firedns_doquery(struct s_connection *s);
static int firedns_build_query_payload(const char * const name,
unsigned short rr, unsigned short class, unsigned char * payload);
static int firedns_send_requests(struct s_header *h, struct s_connection *s,
int l);
void firedns_init(void)
{
/*
* populates servers4 (or -6) struct with up to FDNS_MAX nameserver IP
* addresses from /etc/firedns.conf (or /etc/resolv.conf)
*/
FILE *f;
int i;
struct in_addr addr4;
char buf[1024];
char *file;
#ifdef IPV6
struct in6_addr addr6;
i6 = 0;
#endif
i4 = 0;
/* Initialize connections list */
CONNECTIONS = list_create();
srand((unsigned int) time(NULL));
memset(servers4,'\0',sizeof(struct in_addr) * FDNS_MAX);
#ifdef IPV6
memset(servers6,'\0',sizeof(struct in6_addr) * FDNS_MAX);
#endif
/* read etc/firedns.conf if we've got it, otherwise parse /etc/resolv.conf */
f = fopen(FDNS_CONFIG_PREF,"r");
if (f == NULL)
{
f = fopen(FDNS_CONFIG_FBCK,"r");
if (f == NULL)
{
log_printf("Unable to open %s", FDNS_CONFIG_FBCK);
return;
}
file = FDNS_CONFIG_FBCK;
while (fgets(buf,1024,f) != NULL)
{
if (strncmp(buf,"nameserver",10) == 0)
{
i = 10;
while (buf[i] == ' ' || buf[i] == '\t')
i++;
#ifdef IPV6
/* glibc /etc/resolv.conf seems to allow ipv6 server names */
if (i6 < FDNS_MAX)
{
if (inet_pton6(&buf[i], (char *)&addr6) != NULL)
{
memcpy(&servers6[i6++],&addr6,sizeof(struct in6_addr));
continue;
}
}
#endif
if (i4 < FDNS_MAX)
{
if (inet_aton(&buf[i], &addr4))
{
memcpy(&servers4[i4++],&addr4,sizeof(struct in_addr));
}
}
}
}
}
else
{
file = FDNS_CONFIG_PREF;
while (fgets(buf,1024,f) != NULL)
{
buf[strspn(buf, "0123456789.")] = '\0';
#ifdef IPV6
if (i6 < FDNS_MAX)
{
if (inet_pton(AF_INET6, buf, (char *)&addr6))
{
memcpy(&servers6[i6++], &addr6, sizeof(struct in6_addr));
continue;
}
}
#endif
if (i4 < FDNS_MAX)
{
if (inet_pton(AF_INET, buf, (char *)&addr4))
memcpy(&servers4[i4++],&addr4,sizeof(struct in_addr));
}
}
}
fclose(f);
if(i4 == 0
#ifdef IPV6 /* (yuck) */
&& i6
#endif
)
{
log_printf("FIREDNS -> No nameservers found in %s", file);
exit(EXIT_FAILURE);
}
}
struct in_addr *firedns_resolveip4(const char * const name)
{ /* immediate A query */
static struct in_addr addr;
if(inet_aton(name, &addr))
return &addr;
return (struct in_addr *) firedns_resolveip(FDNS_QRY_A, name);
}
struct in6_addr *firedns_resolveip6(const char * const name)
{ /* immediate AAAA query */
return (struct in6_addr *) firedns_resolveip(FDNS_QRY_AAAA, name);
}
char *firedns_resolveip(int type, const char * const name)
{ /* resolve a query of a given type */
int fd, t, i;
struct firedns_result *result;
struct timeval tv;
fd_set s;
for (t = 0; t < FIREDNS_TRIES; t++)
{
fd = firedns_getip(type, name, NULL);
if (fd == -1)
return NULL;
tv.tv_sec = 5;
tv.tv_usec = 0;
FD_ZERO(&s);
FD_SET(fd, &s);
i = select(fd + 1, &s, NULL, NULL, &tv);
result = firedns_getresult(fd);
if (fdns_errno == FDNS_ERR_NONE)
/* Return is from static memory in getresult, so there is no need to
copy it until the next call to firedns. */
return result->text;
else if(fdns_errno == FDNS_ERR_NXDOMAIN)
return NULL;
}
if(fdns_errno == FDNS_ERR_NONE)
fdns_errno = FDNS_ERR_TIMEOUT;
return NULL;
}
/*
* build, add and send specified query; retrieve result with
* firedns_getresult()
*/
int firedns_getip(int type, const char * const name, void *info)
{
struct s_connection *s;
node_t *node;
int fd;
s = firedns_add_query();
s->class = 1;
s->type = type;
strncpy(s->lookup, name, 256);
s->info = info;
if(fdns_fdinuse >= OptionsItem->dns_fdlimit)
{
fdns_errno = FDNS_ERR_FDLIMIT;
/* Don't add to queue if there is no info */
if(info == NULL)
{
MyFree(s);
}else{
node = node_create(s);
list_add(CONNECTIONS, node);
}
return -1;
}
fd = firedns_doquery(s);
if(fd == -1)
{
MyFree(s);
return -1;
}
node = node_create(s);
list_add(CONNECTIONS, node);
return fd;
}
static struct s_connection *firedns_add_query(void)
{ /* build DNS query, add to list */
struct s_connection *s;
/* create new connection object */
s = MyMalloc(sizeof *s);
/* verified by firedns_getresult() */
s->id[0] = rand() % 255;
s->id[1] = rand() % 255;
s->fd = -1;
return s;
}
static int firedns_doquery(struct s_connection *s)
{
int len;
struct s_header h;
len = firedns_build_query_payload(s->lookup, s->type, 1,
(unsigned char *)&h.payload);
if(len == -1)
{
fdns_errno = FDNS_ERR_FORMAT;
return -1;
}
return firedns_send_requests(&h, s, len);
}
/*
* populate payload with query: name= question, rr= record type
*/
static int firedns_build_query_payload(const char * const name,
unsigned short rr, unsigned short class, unsigned char * payload)
{
short payloadpos;
const char * tempchr, * tempchr2;
unsigned short l;
payloadpos = 0;
tempchr2 = name;
/* split name up into labels, create query */
while ((tempchr = strchr(tempchr2,'.')) != NULL)
{
l = tempchr - tempchr2;
if (payloadpos + l + 1 > 507)
return -1;
payload[payloadpos++] = l;
memcpy(&payload[payloadpos],tempchr2,l);
payloadpos += l;
tempchr2 = &tempchr[1];
}
l = strlen(tempchr2);
if (l)
{
if (payloadpos + l + 2 > 507)
return -1;
payload[payloadpos++] = l;
memcpy(&payload[payloadpos],tempchr2,l);
payloadpos += l;
payload[payloadpos++] = '\0';
}
if (payloadpos > 508)
return -1;
l = htons(rr);
memcpy(&payload[payloadpos],&l,2);
l = htons(class);
memcpy(&payload[payloadpos + 2],&l,2);
return payloadpos + 4;
}
/* send DNS query */
static int firedns_send_requests(struct s_header *h, struct s_connection *s,
int l)
{
int i, sent_ok = 0;
struct sockaddr_in addr4;
#ifdef IPV6
struct sockaddr_in6 addr6;
#endif
/* set header flags */
h->flags1 = 0 | FLAGS1_MASK_RD;
h->flags2 = 0;
h->qdcount = htons(1);
h->ancount = htons(0);
h->nscount = htons(0);
h->arcount = htons(0);
memcpy(h->id, s->id, 2);
/* try to create ipv6 or ipv4 socket */
#ifdef IPV6
s->v6 = 0;
if (i6 > 0)
{
s->fd = socket(PF_INET6, SOCK_DGRAM, 0);
if (s->fd != -1)
{
if (fcntl(s->fd, F_SETFL, O_NONBLOCK) != 0)
{
close(s->fd);
s->fd = -1;
}
}
if (s->fd != -1)
{
struct sockaddr_in6 addr6;
memset(&addr6,0,sizeof(addr6));
addr6.sin6_family = AF_INET6;
if (bind(s->fd,(struct sockaddr *)&addr6,sizeof(addr6)) == 0)
s->v6 = 1;
else
{
close(s->fd);
}
}
}
if (s->v6 == 0)
{
#endif
s->fd = socket(PF_INET, SOCK_DGRAM, 0);
if (s->fd != -1)
{
if (fcntl(s->fd, F_SETFL, O_NONBLOCK) != 0)
{
close(s->fd);
s->fd = -1;
}
}
if (s->fd != -1)
{
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(s->fd,(struct sockaddr *)&addr,sizeof(addr)) != 0)
{
close(s->fd);
s->fd = -1;
}
}
if (s->fd == -1)
{
fdns_errno = FDNS_ERR_NETWORK;
return -1;
}
#ifdef IPV6
}
#endif
#ifdef IPV6
/* if we've got ipv6 support, an ip v6 socket, and ipv6 servers, send to them */
if (i6 > 0 && s->v6 == 1)
{
for (i = 0; i < i6; i++)
{
memset(&addr6,0,sizeof(addr6));
memcpy(&addr6.sin6_addr,&servers6[i],sizeof(addr6.sin6_addr));
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(FDNS_PORT);
if(sendto(s->fd, h, l + 12, 0, (struct sockaddr *) &addr6, sizeof(addr6)) > 0)
sent_ok = 1;
}
}
#endif
for (i = 0; i < i4; i++)
{
#ifdef IPV6
/* send via ipv4-over-ipv6 if we've got an ipv6 socket */
if (s->v6 == 1)
{
memset(&addr6,0,sizeof(addr6));
memcpy(addr6.sin6_addr.s6_addr,"\0\0\0\0\0\0\0\0\0\0\xff\xff",12);
memcpy(&addr6.sin6_addr.s6_addr[12],&servers4[i].s_addr,4);
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(FDNS_PORT);
if(sendto(s->fd, h, l + 12, 0, (struct sockaddr *) &addr6, sizeof(addr6)) > 0)
sent_ok = 1;
continue;
}
#endif
/* otherwise send via standard ipv4 boringness */
memset(&addr4,0,sizeof(addr4));
memcpy(&addr4.sin_addr,&servers4[i],sizeof(addr4.sin_addr));
addr4.sin_family = AF_INET;
addr4.sin_port = htons(FDNS_PORT);
if(sendto(s->fd, h, l + 12, 0, (struct sockaddr *) &addr4, sizeof(addr4)) > 0)
sent_ok = 1;
}
if(!sent_ok)
{
close(s->fd);
s->fd = -1;
fdns_errno = FDNS_ERR_NETWORK;
return -1;
}
time(&s->start);
fdns_fdinuse++;
fdns_errno = FDNS_ERR_NONE;
return s->fd;
}
struct firedns_result *firedns_getresult(const int fd)
{ /* retrieve result of DNS query */
static struct firedns_result result;
struct s_header h;
struct s_connection *c;
node_t *node;
int l,i,q,curanswer;
struct s_rr_middle *rr, rrbacking;
char *src, *dst;
int bytes;
fdns_errno = FDNS_ERR_OTHER;
result.info = (void *) NULL;
memset(result.text, 0, sizeof(result.text));
/* Find query in list of dns lookups */
LIST_FOREACH(node, CONNECTIONS->head)
{
c = (struct s_connection *) node->data;
if(c->fd == fd)
break;
else
c = NULL;
}
/* query not found */
if(c == NULL)
return &result;
/* query found -- we remove in cleanup */
l = recv(c->fd,&h,sizeof(struct s_header),0);
result.info = (void *) c->info;
strncpy(result.lookup, c->lookup, 256);
if(l == -1)
{
fdns_errno = FDNS_ERR_NETWORK;
goto cleanup;
}
if (l < 12)
goto cleanup;
if (c->id[0] != h.id[0] || c->id[1] != h.id[1])
/* ID mismatch: we keep the connection, as this could be an answer to
a previous lookup.. */
return NULL;
if ((h.flags1 & FLAGS1_MASK_QR) == 0)
goto cleanup;
if ((h.flags1 & FLAGS1_MASK_OPCODE) != 0)
goto cleanup;
if ((h.flags2 & FLAGS2_MASK_RCODE) != 0)
{
fdns_errno = (h.flags2 & FLAGS2_MASK_RCODE);
goto cleanup;
}
h.ancount = ntohs(h.ancount);
if (h.ancount < 1)
/* no sense going on if we don't have any answers */
goto cleanup;
/* skip queries */
i = 0;
q = 0;
l -= 12;
h.qdcount = ntohs(h.qdcount);
while (q < h.qdcount && i < l)
{
if (h.payload[i] > 63)
{ /* pointer */
i += 6; /* skip pointer, class and type */
q++;
}
else
{ /* label */
if (h.payload[i] == 0)
{
q++;
i += 5; /* skip nil, class and type */
}
else
i += h.payload[i] + 1; /* skip length and label */
}
}
/* &h.payload[i] should now be the start of the first response */
curanswer = 0;
while (curanswer < h.ancount)
{
q = 0;
while (q == 0 && i < l)
{
if (h.payload[i] > 63)
{ /* pointer */
i += 2; /* skip pointer */
q = 1;
}
else
{ /* label */
if (h.payload[i] == 0)
{
i++;
q = 1;
}
else
i += h.payload[i] + 1; /* skip length and label */
}
}
if (l - i < 10)
goto cleanup;
rr = (struct s_rr_middle *)&h.payload[i];
src = (char *) rr;
dst = (char *) &rrbacking;
for (bytes = sizeof(rrbacking); bytes; bytes--)
*dst++ = *src++;
rr = &rrbacking;
i += 10;
rr->rdlength = ntohs(rr->rdlength);
if (ntohs(rr->type) != c->type)
{
curanswer++;
i += rr->rdlength;
continue;
}
if (ntohs(rr->class) != c->class)
{
curanswer++;
i += rr->rdlength;
continue;
}
break;
}
if (curanswer == h.ancount)
goto cleanup;
if (i + rr->rdlength > l)
goto cleanup;
if (rr->rdlength > 1023)
goto cleanup;
fdns_errno = FDNS_ERR_NONE;
memcpy(result.text,&h.payload[i],rr->rdlength);
result.text[rr->rdlength] = '\0';
/* Clean-up */
cleanup:
list_remove(CONNECTIONS, node);
node_free(node);
close(c->fd);
fdns_fdinuse--;
MyFree(c);
return &result;
}
void firedns_cycle(void)
{
node_t *node, *next;
struct s_connection *p;
struct firedns_result *res, new_result;
static struct pollfd *ufds = NULL;
int fd;
unsigned int size, i;
time_t timenow;
if(LIST_SIZE(CONNECTIONS) == 0)
return;
if(ufds == NULL)
ufds = MyMalloc((sizeof *ufds) * OptionsItem->dns_fdlimit);
time(&timenow);
size = 0;
LIST_FOREACH_SAFE(node, next, CONNECTIONS->head)
{
if(size >= OptionsItem->dns_fdlimit)
break;
p = (struct s_connection *) node->data;
if(p->fd < 0)
continue;
if(p->fd > 0 && (p->start + FDNS_TIMEOUT) < timenow)
{
/* Timed out - remove from list */
list_remove(CONNECTIONS, node);
node_free(node);
memset(new_result.text, 0, sizeof(new_result.text));
new_result.info = p->info;
strncpy(new_result.lookup, p->lookup, 256);
close(p->fd);
fdns_fdinuse--;
MyFree(p);
fdns_errno = FDNS_ERR_TIMEOUT;
if(new_result.info != NULL)
dnsbl_result(&new_result);
continue;
}
ufds[size].events = 0;
ufds[size].revents = 0;
ufds[size].fd = p->fd;
ufds[size].events = POLLIN;
size++;
}
switch(poll(ufds, size, 0))
{
case -1:
case 0:
return;
}
LIST_FOREACH_SAFE(node, next, CONNECTIONS->head)
{
p = (struct s_connection *) node->data;
if(p->fd > 0)
{
for(i = 0; i < size; i++)
{
if((ufds[i].revents & POLLIN) && ufds[i].fd == p->fd)
{
fd = p->fd;
res = firedns_getresult(fd);
if(res != NULL && res->info != NULL)
dnsbl_result(res);
break;
}
}
}
else if(fdns_fdinuse < OptionsItem->dns_fdlimit)
{
firedns_doquery(p);
}
}
}
char *firedns_strerror(int error)
{
if(error == FDNS_ERR_NETWORK)
return strerror(errno);
return errors[error];
}
syntax highlighted by Code2HTML, v. 0.9.1