/*
* dnscore.c -- part of dns.mod
* This file contains all core functions needed for the eggdrop dns module.
* Many of them are only minimaly modified from the original source.
*
* Modified/written by Fabian Knittel <fknittel@gmx.de>
*
* $Id: coredns.c,v 1.30 2006-03-28 02:35:51 wcc Exp $
*/
/*
* Portions Copyright (C) 1999 - 2006 Eggheads Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*/
/*
* Borrowed from mtr -- a network diagnostic tool
* Copyright (C) 1997,1998 Matt Kimball <mkimball@xmission.com>
* Released under the GPL, as above.
*
* Non-blocking DNS portion --
* Copyright (C) 1998 Simon Kirby <sim@neato.org>
* Released under the GPL, as above.
*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <errno.h>
/* Defines */
#define BASH_SIZE 8192 /* Size of hash tables */
#define HOSTNAMELEN 255 /* From RFC */
#define RES_RETRYDELAY 3
#define RES_MAXSENDS 4
#define RES_FAILEDDELAY 600 /* TTL for failed records (in seconds). */
#define RES_MAX_TTL 86400 /* Maximum TTL (in seconds). */
#define RES_ERR "DNS Resolver error: "
#define RES_MSG "DNS Resolver: "
#define RES_WRN "DNS Resolver warning: "
#define MAX_PACKETSIZE (PACKETSZ)
#define MAX_DOMAINLEN (MAXDNAME)
/* Macros */
#define nonull(s) (s) ? s : nullstring
#define BASH_MODULO(x) ((x) & 8191) /* Modulo for hash table size */
/* Non-blocking nameserver interface routines */
#ifdef DEBUG_DNS
# define RESPONSECODES_COUNT 6
static char *responsecodes[RESPONSECODES_COUNT + 1] = {
"no error",
"format error in query",
"server failure",
"queried domain name does not exist",
"requested query type not implemented",
"refused by name server",
"unknown error",
};
#endif /* DEBUG_DNS */
#ifdef DEBUG_DNS
# define RESOURCETYPES_COUNT 17
static const char *resourcetypes[RESOURCETYPES_COUNT + 1] = {
"unknown type",
"A: host address",
"NS: authoritative name server",
"MD: mail destination (OBSOLETE)",
"MF: mail forwarder (OBSOLETE)",
"CNAME: name alias",
"SOA: authority record",
"MB: mailbox domain name (EXPERIMENTAL)",
"MG: mail group member (EXPERIMENTAL)",
"MR: mail rename domain name (EXPERIMENTAL)",
"NULL: NULL RR (EXPERIMENTAL)",
"WKS: well known service description",
"PTR: domain name pointer",
"HINFO: host information",
"MINFO: mailbox or mail list information",
"MX: mail exchange",
"TXT: text string",
"unknown type",
};
#endif /* DEBUG_DNS */
#ifdef DEBUG_DNS
# define CLASSTYPES_COUNT 5
static const char *classtypes[CLASSTYPES_COUNT + 1] = {
"unknown class",
"IN: the Internet",
"CS: CSNET (OBSOLETE)",
"CH: CHAOS",
"HS: Hesoid [Dyer 87]",
"unknown class"
};
#endif /* DEBUG_DNS */
typedef struct {
u_16bit_t id; /* Packet id */
u_8bit_t databyte_a;
/* rd:1 recursion desired
* tc:1 truncated message
* aa:1 authoritive answer
* opcode:4 purpose of message
* qr:1 response flag
*/
u_8bit_t databyte_b;
/* rcode:4 response code
* unassigned:2 unassigned bits
* pr:1 primary server required (non standard)
* ra:1 recursion available
*/
u_16bit_t qdcount; /* Query record count */
u_16bit_t ancount; /* Answer record count */
u_16bit_t nscount; /* Authority reference record count */
u_16bit_t arcount; /* Resource reference record count */
} packetheader;
#ifndef HFIXEDSZ
#define HFIXEDSZ (sizeof(packetheader))
#endif
/*
* Byte order independent macros for packetheader
*/
#define getheader_rd(x) (x->databyte_a & 1)
#define getheader_tc(x) ((x->databyte_a >> 1) & 1)
#define getheader_aa(x) ((x->databyte_a >> 2) & 1)
#define getheader_opcode(x) ((x->databyte_a >> 3) & 15)
#define getheader_qr(x) (x->databyte_a >> 7)
#define getheader_rcode(x) (x->databyte_b & 15)
#define getheader_pr(x) ((x->databyte_b >> 6) & 1)
#define getheader_ra(x) (x->databyte_b >> 7)
#define sucknetword(x) ((x)+=2,((u_16bit_t) (((x)[-2] << 8) | ((x)[-1] << 0))))
#define sucknetshort(x) ((x)+=2,((short) (((x)[-2] << 8) | ((x)[-1] << 0))))
#define sucknetdword(x) ((x)+=4,((dword) (((x)[-4] << 24) | ((x)[-3] << 16) | \
((x)[-2] << 8) | ((x)[-1] << 0))))
#define sucknetlong(x) ((x)+=4,((long) (((x)[-4] << 24) | ((x)[-3] << 16) | \
((x)[-2] << 8) | ((x)[-1] << 0))))
static u_32bit_t resrecvbuf[(MAX_PACKETSIZE + 7) >> 2]; /* MUST BE DWORD ALIGNED */
static struct resolve *idbash[BASH_SIZE];
static struct resolve *ipbash[BASH_SIZE];
static struct resolve *hostbash[BASH_SIZE];
static struct resolve *expireresolves = NULL;
static IP localhost;
static long idseed = 0xdeadbeef;
static long aseed;
static int resfd;
static char tempstring[512];
static char namestring[1024 + 1];
static char stackstring[1024 + 1];
#ifdef DEBUG_DNS
static char sendstring[1024 + 1];
#endif /* DEBUG_DNS */
static const char nullstring[] = "";
/*
* Miscellaneous helper functions
*/
#ifdef DEBUG_DNS
/* Displays the time difference passed in signeddiff.
*/
static char *strtdiff(char *d, long signeddiff)
{
u_32bit_t diff;
u_32bit_t seconds, minutes, hours;
long day;
if ((diff = labs(signeddiff))) {
seconds = diff % 60;
diff /= 60;
minutes = diff % 60;
diff /= 60;
hours = diff % 24;
day = signeddiff / (60 * 60 * 24);
if (day)
sprintf(d, "%lid", day);
else
*d = '\0';
if (hours)
sprintf(d + strlen(d), "%uh", hours);
if (minutes)
sprintf(d + strlen(d), "%um", minutes);
if (seconds)
sprintf(d + strlen(d), "%us", seconds);
} else
sprintf(d, "0s");
return d;
}
#endif /* DEBUG_DNS */
/* Allocate memory to hold one resolve request structure.
*/
static struct resolve *allocresolve()
{
struct resolve *rp;
rp = nmalloc(sizeof *rp);
egg_bzero(rp, sizeof(struct resolve));
return rp;
}
/*
* Hash and linked-list related functions
*/
/* Return the hash bucket number for id.
*/
inline static u_32bit_t getidbash(u_16bit_t id)
{
return (u_32bit_t) BASH_MODULO(id);
}
/* Return the hash bucket number for ip.
*/
inline static u_32bit_t getipbash(IP ip)
{
return (u_32bit_t) BASH_MODULO(ip);
}
/* Return the hash bucket number for host.
*/
static u_32bit_t gethostbash(char *host)
{
u_32bit_t bashvalue = 0;
for (; *host; host++) {
bashvalue ^= *host;
bashvalue += (*host >> 1) + (bashvalue >> 1);
}
return BASH_MODULO(bashvalue);
}
/* Insert request structure addrp into the id hash table.
*/
static void linkresolveid(struct resolve *addrp)
{
struct resolve *rp;
u_32bit_t bashnum;
bashnum = getidbash(addrp->id);
rp = idbash[bashnum];
if (rp) {
while ((rp->nextid) && (addrp->id > rp->nextid->id))
rp = rp->nextid;
while ((rp->previousid) && (addrp->id < rp->previousid->id))
rp = rp->previousid;
if (rp->id < addrp->id) {
addrp->previousid = rp;
addrp->nextid = rp->nextid;
if (rp->nextid)
rp->nextid->previousid = addrp;
rp->nextid = addrp;
} else if (rp->id > addrp->id) {
addrp->previousid = rp->previousid;
addrp->nextid = rp;
if (rp->previousid)
rp->previousid->nextid = addrp;
rp->previousid = addrp;
} else /* Trying to add the same id! */
return;
} else
addrp->nextid = addrp->previousid = NULL;
idbash[bashnum] = addrp;
}
/* Remove request structure rp from the id hash table.
*/
static void unlinkresolveid(struct resolve *rp)
{
u_32bit_t bashnum;
bashnum = getidbash(rp->id);
if (idbash[bashnum] == rp) {
if (rp->previousid)
idbash[bashnum] = rp->previousid;
else
idbash[bashnum] = rp->nextid;
}
if (rp->nextid)
rp->nextid->previousid = rp->previousid;
if (rp->previousid)
rp->previousid->nextid = rp->nextid;
}
/* Insert request structure addrp into the host hash table.
*/
static void linkresolvehost(struct resolve *addrp)
{
struct resolve *rp;
u_32bit_t bashnum;
int ret;
bashnum = gethostbash(addrp->hostn);
rp = hostbash[bashnum];
if (rp) {
while ((rp->nexthost) &&
(egg_strcasecmp(addrp->hostn, rp->nexthost->hostn) < 0))
rp = rp->nexthost;
while ((rp->previoushost) &&
(egg_strcasecmp(addrp->hostn, rp->previoushost->hostn) > 0))
rp = rp->previoushost;
ret = egg_strcasecmp(addrp->hostn, rp->hostn);
if (ret < 0) {
addrp->previoushost = rp;
addrp->nexthost = rp->nexthost;
if (rp->nexthost)
rp->nexthost->previoushost = addrp;
rp->nexthost = addrp;
} else if (ret > 0) {
addrp->previoushost = rp->previoushost;
addrp->nexthost = rp;
if (rp->previoushost)
rp->previoushost->nexthost = addrp;
rp->previoushost = addrp;
} else /* Trying to add the same host! */
return;
} else
addrp->nexthost = addrp->previoushost = NULL;
hostbash[bashnum] = addrp;
}
/* Remove request structure rp from the host hash table.
*/
static void unlinkresolvehost(struct resolve *rp)
{
u_32bit_t bashnum;
bashnum = gethostbash(rp->hostn);
if (hostbash[bashnum] == rp) {
if (rp->previoushost)
hostbash[bashnum] = rp->previoushost;
else
hostbash[bashnum] = rp->nexthost;
}
if (rp->nexthost)
rp->nexthost->previoushost = rp->previoushost;
if (rp->previoushost)
rp->previoushost->nexthost = rp->nexthost;
nfree(rp->hostn);
}
/* Insert request structure addrp into the ip hash table.
*/
static void linkresolveip(struct resolve *addrp)
{
struct resolve *rp;
u_32bit_t bashnum;
bashnum = getipbash(addrp->ip);
rp = ipbash[bashnum];
if (rp) {
while ((rp->nextip) && (addrp->ip > rp->nextip->ip))
rp = rp->nextip;
while ((rp->previousip) && (addrp->ip < rp->previousip->ip))
rp = rp->previousip;
if (rp->ip < addrp->ip) {
addrp->previousip = rp;
addrp->nextip = rp->nextip;
if (rp->nextip)
rp->nextip->previousip = addrp;
rp->nextip = addrp;
} else if (rp->ip > addrp->ip) {
addrp->previousip = rp->previousip;
addrp->nextip = rp;
if (rp->previousip)
rp->previousip->nextip = addrp;
rp->previousip = addrp;
} else /* Trying to add the same ip! */
return;
} else
addrp->nextip = addrp->previousip = NULL;
ipbash[bashnum] = addrp;
}
/* Remove request structure rp from the ip hash table.
*/
static void unlinkresolveip(struct resolve *rp)
{
u_32bit_t bashnum;
bashnum = getipbash(rp->ip);
if (ipbash[bashnum] == rp) {
if (rp->previousip)
ipbash[bashnum] = rp->previousip;
else
ipbash[bashnum] = rp->nextip;
}
if (rp->nextip)
rp->nextip->previousip = rp->previousip;
if (rp->previousip)
rp->previousip->nextip = rp->nextip;
}
/* Add request structure rp to the expireresolves list. Entries are sorted
* by expire time.
*/
static void linkresolve(struct resolve *rp)
{
struct resolve *irp;
if (expireresolves) {
irp = expireresolves;
while ((irp->next) && (rp->expiretime >= irp->expiretime))
irp = irp->next;
if (rp->expiretime >= irp->expiretime) {
rp->next = NULL;
rp->previous = irp;
irp->next = rp;
} else {
rp->previous = irp->previous;
rp->next = irp;
if (irp->previous)
irp->previous->next = rp;
else
expireresolves = rp;
irp->previous = rp;
}
} else {
rp->next = NULL;
rp->previous = NULL;
expireresolves = rp;
}
}
/* Remove reqeust structure rp from the expireresolves list.
*/
static void untieresolve(struct resolve *rp)
{
if (rp->previous)
rp->previous->next = rp->next;
else
expireresolves = rp->next;
if (rp->next)
rp->next->previous = rp->previous;
}
/* Remove request structure rp from all lists and hash tables and
* then delete and free the structure
*/
static void unlinkresolve(struct resolve *rp)
{
untieresolve(rp); /* Not really needed. Left in to be on the
* safe side. */
unlinkresolveid(rp);
unlinkresolveip(rp);
if (rp->hostn)
unlinkresolvehost(rp);
nfree(rp);
}
/* Find request structure using the id.
*/
static struct resolve *findid(u_16bit_t id)
{
struct resolve *rp;
int bashnum;
bashnum = getidbash(id);
rp = idbash[bashnum];
if (rp) {
while ((rp->nextid) && (id >= rp->nextid->id))
rp = rp->nextid;
while ((rp->previousid) && (id <= rp->previousid->id))
rp = rp->previousid;
if (id == rp->id) {
idbash[bashnum] = rp;
return rp;
} else
return NULL;
}
return rp; /* NULL */
}
/* Find request structure using the host.
*/
static struct resolve *findhost(char *hostn)
{
struct resolve *rp;
int bashnum;
bashnum = gethostbash(hostn);
rp = hostbash[bashnum];
if (rp) {
while ((rp->nexthost) &&
(egg_strcasecmp(hostn, rp->nexthost->hostn) >= 0))
rp = rp->nexthost;
while ((rp->previoushost) &&
(egg_strcasecmp(hostn, rp->previoushost->hostn) <= 0))
rp = rp->previoushost;
if (egg_strcasecmp(hostn, rp->hostn))
return NULL;
else {
hostbash[bashnum] = rp;
return rp;
}
}
return rp; /* NULL */
}
/* Find request structure using the ip.
*/
static struct resolve *findip(IP ip)
{
struct resolve *rp;
u_32bit_t bashnum;
bashnum = getipbash(ip);
rp = ipbash[bashnum];
if (rp) {
while ((rp->nextip) && (ip >= rp->nextip->ip))
rp = rp->nextip;
while ((rp->previousip) && (ip <= rp->previousip->ip))
rp = rp->previousip;
if (ip == rp->ip) {
ipbash[bashnum] = rp;
return rp;
} else
return NULL;
}
return rp; /* NULL */
}
/*
* Network and resolver related functions
*/
/* Create packet for the request and send it to all available nameservers.
*/
static void dorequest(char *s, int type, u_16bit_t id)
{
packetheader *hp;
int r, i;
u_8bit_t buf[(MAX_PACKETSIZE / sizeof(char)) + 1];
r = res_mkquery(QUERY, s, C_IN, type, NULL, 0, NULL, buf, MAX_PACKETSIZE);
if (r == -1) {
ddebug0(RES_ERR "Query too large.");
return;
}
hp = (packetheader *) buf;
hp->id = id; /* htons() deliberately left out (redundant) */
for (i = 0; i < _res.nscount; i++)
(void) sendto(resfd, buf, r, 0,
(struct sockaddr *) &_res.nsaddr_list[i],
sizeof(struct sockaddr));
}
/* (Re-)send request with existing id.
*/
static void resendrequest(struct resolve *rp, int type)
{
rp->sends++;
/* Update expire time */
rp->expiretime = now + (RES_RETRYDELAY * rp->sends);
/* Add (back) to expire list */
linkresolve(rp);
if (type == T_A) {
dorequest(rp->hostn, type, rp->id);
ddebug1(RES_MSG "Sent domain lookup request for \"%s\".", rp->hostn);
} else if (type == T_PTR) {
sprintf(tempstring, "%u.%u.%u.%u.in-addr.arpa",
((u_8bit_t *) & rp->ip)[3],
((u_8bit_t *) & rp->ip)[2],
((u_8bit_t *) & rp->ip)[1], ((u_8bit_t *) & rp->ip)[0]);
dorequest(tempstring, type, rp->id);
ddebug1(RES_MSG "Sent domain lookup request for \"%s\".", iptostr(rp->ip));
}
}
/* Send request for the first time.
*/
static void sendrequest(struct resolve *rp, int type)
{
/* Create unique id */
do {
idseed = (((idseed + idseed) | (long) time(NULL))
+ idseed - 0x54bad4a) ^ aseed;
aseed ^= idseed;
rp->id = (u_16bit_t) idseed;
} while (findid(rp->id));
linkresolveid(rp); /* Add id to id hash table */
resendrequest(rp, type); /* Send request */
}
/* Gets called as soon as the request turns out to have failed. Calls
* the eggdrop hook.
*/
static void failrp(struct resolve *rp, int type)
{
if (rp->state == STATE_FINISHED)
return;
rp->expiretime = now + RES_FAILEDDELAY;
rp->state = STATE_FAILED;
/* Expire time was changed, reinsert entry to maintain order */
untieresolve(rp);
linkresolve(rp);
ddebug0(RES_MSG "Lookup failed.");
dns_event_failure(rp, type);
}
/* Gets called as soon as the request turns out to be successful. Calls
* the eggdrop hook.
*/
static void passrp(struct resolve *rp, long ttl, int type)
{
rp->state = STATE_FINISHED;
/* Do not cache entries for too long. */
if (ttl < RES_MAX_TTL)
rp->expiretime = now + (time_t) ttl;
else
rp->expiretime = now + RES_MAX_TTL;
/* Expire time was changed, reinsert entry to maintain order */
untieresolve(rp);
linkresolve(rp);
ddebug1(RES_MSG "Lookup successful: %s", rp->hostn);
dns_event_success(rp, type);
}
/* Parses the response packets received.
*/
static void parserespacket(u_8bit_t *s, int l)
{
struct resolve *rp;
packetheader *hp;
u_8bit_t *eob;
u_8bit_t *c;
long ttl;
int r, usefulanswer;
u_16bit_t rr, datatype, class, qdatatype, qclass;
u_8bit_t rdatalength;
if (l < sizeof(packetheader)) {
debug0(RES_ERR "Packet smaller than standard header size.");
return;
}
if (l == sizeof(packetheader)) {
debug0(RES_ERR "Packet has empty body.");
return;
}
hp = (packetheader *) s;
/* Convert data to host byte order
*
* hp->id does not need to be redundantly byte-order flipped, it
* is only echoed by nameserver
*/
rp = findid(hp->id);
if (!rp)
return;
if ((rp->state == STATE_FINISHED) || (rp->state == STATE_FAILED))
return;
hp->qdcount = ntohs(hp->qdcount);
hp->ancount = ntohs(hp->ancount);
hp->nscount = ntohs(hp->nscount);
hp->arcount = ntohs(hp->arcount);
if (getheader_tc(hp)) { /* Packet truncated */
ddebug0(RES_ERR "Nameserver packet truncated.");
return;
}
if (!getheader_qr(hp)) { /* Not a reply */
ddebug0(RES_ERR "Query packet received on nameserver communication "
"socket.");
return;
}
if (getheader_opcode(hp)) { /* Not opcode 0 (standard query) */
ddebug0(RES_ERR "Invalid opcode in response packet.");
return;
}
eob = s + l;
c = s + HFIXEDSZ;
switch (getheader_rcode(hp)) {
case NOERROR:
if (hp->ancount) {
ddebug4(RES_MSG "Received nameserver reply. (qd:%u an:%u ns:%u ar:%u)",
hp->qdcount, hp->ancount, hp->nscount, hp->arcount);
if (hp->qdcount != 1) {
ddebug0(RES_ERR "Reply does not contain one query.");
return;
}
if (c > eob) {
ddebug0(RES_ERR "Reply too short.");
return;
}
switch (rp->state) { /* Construct expected query reply */
case STATE_PTRREQ:
sprintf(stackstring,
"%u.%u.%u.%u.in-addr.arpa",
((u_8bit_t *) & rp->ip)[3],
((u_8bit_t *) & rp->ip)[2],
((u_8bit_t *) & rp->ip)[1], ((u_8bit_t *) & rp->ip)[0]);
break;
case STATE_AREQ:
strncpy(stackstring, rp->hostn, 1024);
}
*namestring = '\0';
r = dn_expand(s, s + l, c, namestring, MAXDNAME);
if (r == -1) {
ddebug0(RES_ERR "dn_expand() failed while expanding query domain.");
return;
}
namestring[strlen(stackstring)] = '\0';
if (egg_strcasecmp(stackstring, namestring)) {
ddebug2(RES_MSG "Unknown query packet dropped. (\"%s\" does not "
"match \"%s\")", stackstring, namestring);
return;
}
ddebug1(RES_MSG "Queried domain name: \"%s\"", namestring);
c += r;
if (c + 4 > eob) {
ddebug0(RES_ERR "Query resource record truncated.");
return;
}
qdatatype = sucknetword(c);
qclass = sucknetword(c);
if (qclass != C_IN) {
ddebug2(RES_ERR "Received unsupported query class: %u (%s)",
qclass, (qclass < CLASSTYPES_COUNT) ?
classtypes[qclass] : classtypes[CLASSTYPES_COUNT]);
}
switch (qdatatype) {
case T_PTR:
if (!IS_PTR(rp)) {
ddebug0(RES_WRN "Ignoring response with unexpected query type "
"\"PTR\".");
return;
}
break;
case T_A:
if (!IS_A(rp)) {
ddebug0(RES_WRN "Ignoring response with unexpected query type "
"\"PTR\".");
return;
}
break;
default:
ddebug2(RES_ERR "Received unimplemented query type: %u (%s)",
qdatatype, (qdatatype < RESOURCETYPES_COUNT) ?
resourcetypes[qdatatype] : resourcetypes[RESOURCETYPES_COUNT]);
}
for (rr = hp->ancount + hp->nscount + hp->arcount; rr; rr--) {
if (c > eob) {
ddebug0(RES_ERR "Packet does not contain all specified resouce "
"records.");
return;
}
*namestring = '\0';
r = dn_expand(s, s + l, c, namestring, MAXDNAME);
if (r == -1) {
ddebug0(RES_ERR "dn_expand() failed while expanding answer domain.");
return;
}
namestring[strlen(stackstring)] = '\0';
if (egg_strcasecmp(stackstring, namestring))
usefulanswer = 0;
else
usefulanswer = 1;
ddebug1(RES_MSG "answered domain query: \"%s\"", namestring);
c += r;
if (c + 10 > eob) {
ddebug0(RES_ERR "Resource record truncated.");
return;
}
datatype = sucknetword(c);
class = sucknetword(c);
ttl = sucknetlong(c);
rdatalength = sucknetword(c);
if (class != qclass) {
ddebug2(RES_MSG "query class: %u (%s)",
qclass, (qclass < CLASSTYPES_COUNT) ?
classtypes[qclass] : classtypes[CLASSTYPES_COUNT]);
ddebug2(RES_MSG "rr class: %u (%s)", class,
(class < CLASSTYPES_COUNT) ?
classtypes[class] : classtypes[CLASSTYPES_COUNT]);
ddebug0(RES_ERR "Answered class does not match queried class.");
return;
}
if (!rdatalength) {
ddebug0(RES_ERR "Zero size rdata.");
return;
}
if (c + rdatalength > eob) {
ddebug0(RES_ERR "Specified rdata length exceeds packet size.");
return;
}
if (datatype == qdatatype) {
ddebug1(RES_MSG "TTL: %s", strtdiff(sendstring, ttl));
ddebug1(RES_MSG "TYPE: %s", (datatype < RESOURCETYPES_COUNT) ?
resourcetypes[datatype] :
resourcetypes[RESOURCETYPES_COUNT]);
if (usefulanswer)
switch (datatype) {
case T_A:
if (rdatalength != 4) {
ddebug1(RES_ERR "Unsupported rdata format for \"A\" type. "
"(%u bytes)", rdatalength);
return;
}
my_memcpy(&rp->ip, (IP *) c, sizeof(IP));
linkresolveip(rp);
passrp(rp, ttl, T_A);
return;
case T_PTR:
*namestring = '\0';
r = dn_expand(s, s + l, c, namestring, MAXDNAME);
if (r == -1) {
ddebug0(RES_ERR "dn_expand() failed while expanding domain in "
"rdata.");
return;
}
ddebug1(RES_MSG "Answered domain: \"%s\"", namestring);
if (r > HOSTNAMELEN) {
ddebug0(RES_ERR "Domain name too long.");
failrp(rp, T_PTR);
return;
}
if (!rp->hostn) {
rp->hostn = nmalloc(strlen(namestring) + 1);
strcpy(rp->hostn, namestring);
linkresolvehost(rp);
passrp(rp, ttl, T_PTR);
return;
}
break;
default:
ddebug2(RES_ERR "Received unimplemented data type: %u (%s)",
datatype, (datatype < RESOURCETYPES_COUNT) ?
resourcetypes[datatype] :
resourcetypes[RESOURCETYPES_COUNT]);
}
} else if (datatype == T_CNAME) {
*namestring = '\0';
r = dn_expand(s, s + l, c, namestring, MAXDNAME);
if (r == -1) {
ddebug0(RES_ERR "dn_expand() failed while expanding domain in "
"rdata.");
return;
}
ddebug1(RES_MSG "answered domain is CNAME for: %s", namestring);
/* The next responses will be related to the domain
* pointed to by CNAME, so we need to update which
* respones we regard as important.
*/
strncpy(stackstring, namestring, 1024);
} else {
ddebug2(RES_MSG "Ignoring resource type %u. (%s)",
datatype, (datatype < RESOURCETYPES_COUNT) ?
resourcetypes[datatype] :
resourcetypes[RESOURCETYPES_COUNT]);
}
c += rdatalength;
}
} else
ddebug0(RES_ERR "No error returned but no answers given.");
break;
case NXDOMAIN:
ddebug0(RES_MSG "Host not found.");
switch (rp->state) {
case STATE_PTRREQ:
failrp(rp, T_PTR);
break;
case STATE_AREQ:
failrp(rp, T_A);
break;
default:
failrp(rp, 0);
break;
}
break;
default:
ddebug2(RES_MSG "Received error response %u. (%s)",
getheader_rcode(hp), (getheader_rcode(hp) < RESPONSECODES_COUNT) ?
responsecodes[getheader_rcode(hp)] :
responsecodes[RESPONSECODES_COUNT]);
}
}
/* Read data received on our dns socket. This function is called
* as soon as traffic is detected.
*/
static void dns_ack(void)
{
struct sockaddr_in from;
unsigned int fromlen = sizeof(struct sockaddr_in);
int r, i;
r = recvfrom(resfd, (u_8bit_t *) resrecvbuf, MAX_PACKETSIZE, 0,
(struct sockaddr *) &from, &fromlen);
if (r <= 0) {
ddebug1(RES_MSG "Socket error: %s", strerror(errno));
return;
}
/* Check to see if this server is actually one we sent to */
if (from.sin_addr.s_addr == localhost) {
for (i = 0; i < _res.nscount; i++)
/* 0.0.0.0 replies as 127.0.0.1 */
if ((_res.nsaddr_list[i].sin_addr.s_addr == from.sin_addr.s_addr) ||
(!_res.nsaddr_list[i].sin_addr.s_addr))
break;
} else {
for (i = 0; i < _res.nscount; i++)
if (_res.nsaddr_list[i].sin_addr.s_addr == from.sin_addr.s_addr)
break;
}
if (i == _res.nscount) {
ddebug1(RES_ERR "Received reply from unknown source: %s",
iptostr(from.sin_addr.s_addr));
} else
parserespacket((u_8bit_t *) resrecvbuf, r);
}
/* Remove or resend expired requests. Called once a second.
*/
static void dns_check_expires(void)
{
struct resolve *rp, *nextrp;
/* Walk through sorted list ... */
for (rp = expireresolves; (rp) && (now >= rp->expiretime); rp = nextrp) {
nextrp = rp->next;
untieresolve(rp);
switch (rp->state) {
case STATE_FINISHED: /* TTL has expired */
case STATE_FAILED: /* Fake TTL has expired */
ddebug4(RES_MSG
"Cache record for \"%s\" (%s) has expired. (state: %u) Marked for expire at: %ld.",
nonull(rp->hostn), iptostr(rp->ip), rp->state, rp->expiretime);
unlinkresolve(rp);
break;
case STATE_PTRREQ: /* T_PTR send timed out */
if (rp->sends <= RES_MAXSENDS) {
ddebug1(RES_MSG "Resend #%d for \"PTR\" query...", rp->sends - 1);
resendrequest(rp, T_PTR);
} else {
ddebug0(RES_MSG "\"PTR\" query timed out.");
failrp(rp, T_PTR);
}
break;
case STATE_AREQ: /* T_A send timed out */
if (rp->sends <= RES_MAXSENDS) {
ddebug1(RES_MSG "Resend #%d for \"A\" query...", rp->sends - 1);
resendrequest(rp, T_A);
} else {
ddebug0(RES_MSG "\"A\" query timed out.");
failrp(rp, T_A);
}
break;
default: /* Unknown state, let it expire */
ddebug1(RES_WRN "Unknown request state %d. Request expired.", rp->state);
failrp(rp, 0);
}
}
}
/* Start searching for a host-name, using it's ip-address.
*/
static void dns_lookup(IP ip)
{
struct resolve *rp;
ip = htonl(ip);
if ((rp = findip(ip))) {
if (rp->state == STATE_FINISHED || rp->state == STATE_FAILED) {
if (rp->state == STATE_FINISHED && rp->hostn) {
ddebug2(RES_MSG "Used cached record: %s == \"%s\".",
iptostr(ip), rp->hostn);
dns_event_success(rp, T_PTR);
} else {
ddebug1(RES_MSG "Used failed record: %s == ???", iptostr(ip));
dns_event_failure(rp, T_PTR);
}
}
return;
}
ddebug0(RES_MSG "Creating new record");
rp = allocresolve();
rp->state = STATE_PTRREQ;
rp->sends = 1;
rp->ip = ip;
linkresolveip(rp);
sendrequest(rp, T_PTR);
}
/* Start searching for an ip-address, using it's host-name.
*/
static void dns_forward(char *hostn)
{
struct resolve *rp;
struct in_addr inaddr;
/* Check if someone passed us an IP address as hostname
* and return it straight away.
*/
if (egg_inet_aton(hostn, &inaddr)) {
call_ipbyhost(hostn, ntohl(inaddr.s_addr), 1);
return;
}
if ((rp = findhost(hostn))) {
if (rp->state == STATE_FINISHED || rp->state == STATE_FAILED) {
if (rp->state == STATE_FINISHED && rp->ip) {
ddebug2(RES_MSG "Used cached record: %s == \"%s\".", hostn,
iptostr(rp->ip));
dns_event_success(rp, T_A);
} else {
ddebug1(RES_MSG "Used failed record: %s == ???", hostn);
dns_event_failure(rp, T_A);
}
}
return;
}
ddebug0(RES_MSG "Creating new record");
rp = allocresolve();
rp->state = STATE_AREQ;
rp->sends = 1;
rp->hostn = nmalloc(strlen(hostn) + 1);
strcpy(rp->hostn, hostn);
linkresolvehost(rp);
sendrequest(rp, T_A);
}
/* Initialise the network.
*/
static int init_dns_network(void)
{
int option;
struct in_addr inaddr;
resfd = socket(AF_INET, SOCK_DGRAM, 0);
if (resfd == -1) {
putlog(LOG_MISC, "*",
"Unable to allocate socket for nameserver communication: %s",
strerror(errno));
return 0;
}
(void) allocsock(resfd, SOCK_PASS);
option = 1;
if (setsockopt(resfd, SOL_SOCKET, SO_BROADCAST, (char *) &option,
sizeof(option))) {
putlog(LOG_MISC, "*",
"Unable to setsockopt() on nameserver communication socket: %s",
strerror(errno));
killsock(resfd);
return 0;
}
egg_inet_aton("127.0.0.1", &inaddr);
localhost = inaddr.s_addr;
return 1;
}
/* Initialise the core dns system, returns 1 if all goes well, 0 if not.
*/
static int init_dns_core(void)
{
int i;
/* Initialise the resolv library. */
res_init();
if (!_res.nscount) {
putlog(LOG_MISC, "*", "No nameservers defined.");
return 0;
}
_res.options |= RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
for (i = 0; i < _res.nscount; i++)
_res.nsaddr_list[i].sin_family = AF_INET;
if (!init_dns_network())
return 0;
/* Initialise the hash tables. */
aseed = time(NULL) ^ (time(NULL) << 3) ^ (u_32bit_t) getpid();
for (i = 0; i < BASH_SIZE; i++) {
idbash[i] = NULL;
ipbash[i] = NULL;
hostbash[i] = NULL;
}
expireresolves = NULL;
return 1;
}
syntax highlighted by Code2HTML, v. 0.9.1