#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <syslog.h>
#include <unistd.h>
#include <assert.h>
#include "nstxfun.h"
#include "nstxdns.h"
/* lbl2str: Decodes a domainname to a string
* <len1><NAME1><len2><NAME2><len3><NAME3>'\0' ->
* NAME1.NAME2.NAME3
* A lenght-byte may not be larger than 63 bytes (the two most significant
* bits are reserved/used for compression */
/* Reverses lbl2str */
static const char *
str2lbl(const char *data)
{
const char *ptr;
char *ptr2, *buf;
unsigned int buflen, chunklen;
ptr = data;
buf = NULL;
buflen = 0;
while ((ptr2 = strchr(ptr, '.'))) {
chunklen = ptr2 - ptr;
if ((chunklen > 63) || (chunklen <= 0)) {
DEBUG("Too long or zero-length label");
if (buf)
free(buf);
return NULL;
}
buf = realloc(buf, buflen + chunklen + 1);
buf[buflen] = chunklen;
memcpy(buf+buflen+1, ptr, chunklen);
buflen += chunklen + 1;
ptr = ptr2 + 1;
}
chunklen = strlen(ptr);
buf = realloc(buf, buflen + chunklen + 2);
buf[buflen] = chunklen;
memcpy(buf+buflen+1, ptr, chunklen);
buflen += chunklen+1;
buf[buflen] = '\0';
buflen++;
return buf;
}
/* decompress_label decompresses the label pointed to by 'lbl' inside the
* DNS-packet 'msg'. */
static char *
decompress_label(const char *msg, unsigned int msglen, const char *lbl)
{
const char *ptr = lbl;
char *buf;
unsigned int chunklen, offset, buflen, followed = 0;
buf = NULL;
buflen = 0;
while ((chunklen = *ptr)) {
if (chunklen > 63) {
if ((ptr-msg) >= ((signed int)msglen-1)) {
DEBUG("Bad pointer at end of msg");
if (buf)
free(buf);
return NULL;
}
if (followed > 20) {
DEBUG("Too many pointer-loops");
if (buf)
free(buf);
return NULL;
}
offset = (chunklen % 64)*256 + *(ptr+1);
if (offset >= msglen) {
DEBUG("offset behind message");
if (buf)
free(buf);
return NULL;
}
ptr = msg + offset;
followed++;
}
else {
buf = realloc(buf, buflen + chunklen + 2);
if ((ptr + chunklen + 1) >= (msg + msglen)) {
DEBUG("Invalid chunklen");
if (buf)
free(buf);
return NULL;
}
memcpy(buf+buflen, ptr, chunklen + 1);
buflen += chunklen + 1;
ptr += chunklen + 1;
}
}
if (buf) {
buf[buflen] = 0;
buflen++;
}
return buf;
}
static const unsigned char *
_cstringify(const unsigned char *data, int *dlen, unsigned int clen)
{
static unsigned char *buf;
const unsigned char *s = data;
unsigned char *d;
unsigned int llen, len;
len = *dlen;
*dlen = 0;
d = buf = realloc(buf, len+len/clen+2);
while (len > 0) {
llen = (len > clen) ? clen : len;
*(d++) = llen;
memcpy(d, s, llen);
d += llen;
(*dlen) += llen + 1;
s += llen;
len -= llen;
}
*d = '\0';
(*dlen)++;
return buf;
}
static const unsigned char *
data2lbl (const unsigned char *data)
{
int len;
len = strlen((char*)data);
return _cstringify(data, &len, 63);
}
const unsigned char *
data2txt (const unsigned char *data, int *len)
{
return _cstringify(data, len, 255);
}
const unsigned char *
txt2data (const unsigned char *data, int *dlen)
{
static unsigned char *buf;
const unsigned char *s = data;
unsigned char *d;
unsigned int len, llen;
len = *dlen;
d = buf = realloc(buf, len);
do
{
llen = *s++;
if (llen > len - (s - data))
return NULL;
memcpy(d, s, llen);
s += llen;
d += llen;
} while (llen);
*d = '\0';
*dlen = d - buf;
return buf;
}
static const unsigned char *
lbl2data (const unsigned char *data, size_t len)
{
static signed char *buf = NULL;
const unsigned char *s = data;
signed char *d;
signed int llen;
d = buf = realloc(buf, len);
assert(d);
do
{
llen = *s++;
if ((llen > 63) || (llen > (signed int)(len - (s - data))))
break;
memcpy(d, s, llen);
s += llen;
d += llen;
} while (llen);
*d = '\0';
return (const unsigned char*)buf;
}
/* New DNS-Code */
static struct rr *_new_listitem (struct rr **list)
{
struct rr *rrp, *tmp;
rrp = malloc(sizeof(struct rr));
memset(rrp, 0, sizeof(struct rr));
if (!*list)
*list = rrp;
else {
for (tmp = *list; tmp->next; tmp = tmp->next)
;
tmp->next = rrp;
}
return rrp;
}
static const unsigned char *
_skip_lbl (const unsigned char *ptr, int *len)
{
while (*ptr) {
if (*len < 2)
return NULL;
if ((*ptr & 0xc0)) {
ptr++;
(*len)--;
break;
}
*len -= *ptr;
if (*len < 1)
return NULL;
ptr += *ptr+1;
}
ptr++;
(*len)--;
return ptr;
}
static __inline__ int _get_listlen (const struct rr *list)
{
int nr = 0;
while (list) {
list = list->next;
nr++;
}
return nr;
}
static const char *suffix = NULL;
static int suffixlen = 0;
void dns_setsuffix (char *suf)
{
suffix = str2lbl(suf);
suffixlen = strlen(suf)+1;
}
struct dnspkt *dns_alloc (void)
{
void *ptr;
ptr = malloc(sizeof(struct dnspkt));
memset(ptr, 0, sizeof(struct dnspkt));
return ptr;
}
void dns_free (struct dnspkt *pkt)
{
struct rr *list, *next;
list = pkt->query;
while (list) {
if (list->data)
free(list->data);
next = list->next;
free(list);
list = next;
}
list = pkt->answer;
while (list) {
if (list->data)
free(list->data);
next = list->next;
free(list);
list = next;
}
free(pkt);
}
void dns_setid (struct dnspkt *pkt, unsigned short id)
{
pkt->id = id;
}
void dns_settype (struct dnspkt *pkt, int type)
{
pkt->type = type;
}
const char *
dns_data2fqdn (const char *data)
{
const char *ptr;
static char *fqdn;
ptr = (char*)data2lbl((unsigned char*)data);
fqdn = realloc(fqdn, strlen(ptr)+strlen(suffix)+1);
strcpy(fqdn, ptr);
strcat(fqdn, suffix);
return fqdn;
}
const char *
dns_fqdn2data (const char *fqdn)
{
static char *buf;
const char *off;
if (buf)
free(buf);
off = strstr(fqdn, suffix);
/* only parse if the fqdn was found, and there is more than the fqdn */
if (off && off != fqdn)
buf = strdup((char*)lbl2data((unsigned char*)fqdn, off - fqdn));
else
/* Our suffix not found... */
buf = NULL;
return buf;
}
int
dns_addquery (struct dnspkt *pkt, const char *data)
{
struct rr *rrp;
rrp = _new_listitem(&pkt->query);
rrp->data = strdup(data);
rrp->len = strlen(data)+1;
return _get_listlen(pkt->query) - 1;
}
int
dns_addanswer(struct dnspkt *pkt, const char *data, int len, int link)
{
struct rr *rrp;
const char *ptr;
char *buf;
ptr = (char*)data2txt((unsigned char*)data, &len);
buf = malloc(len);
memcpy(buf, ptr, len);
rrp = _new_listitem(&pkt->answer);
rrp->data = buf;
rrp->len = len;
rrp->link = link;
return _get_listlen(pkt->query) - 1;
}
int
dns_getpktsize(const struct dnspkt *pkt)
{
int size;
struct rr *list;
size = 12; /* DNS-header */
for (list = pkt->query; list; list = list->next)
size += list->len + 4;
for (list = pkt->answer; list; list = list->next)
size += list->len + 12;
return size;
}
unsigned char *dns_constructpacket (struct dnspkt *pkt, int *l)
{
static unsigned char *buf, *ptr;
int len, *offsets, qdcount, ancount, i;
struct rr *list;
qdcount = _get_listlen(pkt->query);
ancount = _get_listlen(pkt->answer);
len = dns_getpktsize(pkt);
ptr = buf = malloc(len);
memset(buf, 0, len);
if (len > 512)
syslog(LOG_WARNING, "WARNING: Constructed non-conform DNS-packet (size: %d)\n", len);
offsets = alloca(qdcount * 4);
/* Header */
buf[0] = pkt->id / 256;
buf[1] = pkt->id % 256;
if (pkt->type == DNS_RESPONSE) {
buf[2] = 0x84; /* Flags: Response, Authoritative Answer */
buf[3] = 0x80; /* Flag: Recursion available */
} else
buf[2] = 0x01; /* Flags: Recursion desired */
buf[5] = qdcount;
buf[7] = _get_listlen(pkt->answer);
ptr += 12;
/* Query section */
for (list = pkt->query, i=0; list; list = list->next, i++) {
offsets[i] = ptr-buf;
memcpy(ptr, list->data, list->len);
ptr += list->len;
ptr[1] = 16;
ptr[3] = 1;
ptr += 4;
}
/* Answer section */
for (list = pkt->answer; list; list = list->next) {
ptr[0] = 0xc0 | (offsets[list->link]/256);
ptr[1] = offsets[list->link]%256;
ptr[3] = 16;
ptr[5] = 1;
ptr[10] = list->len / 256;
ptr[11] = list->len % 256;
ptr += 12;
memcpy(ptr, list->data, list->len);
ptr += list->len;
}
*l = len;
dns_free (pkt);
return buf;
}
struct dnspkt *
dns_extractpkt(const unsigned char *buf, int len)
{
int qdcount, ancount, remain, *offsets, i, j, off;
const unsigned char *ptr;
struct dnspkt *pkt;
struct rr *rrp;
if (len < 17)
return NULL;
pkt = dns_alloc();
pkt->id = buf[0]*256+buf[1];
qdcount = buf[5];
ancount = buf[7];
offsets = malloc(qdcount * 4);
ptr = buf + 12;
remain = len - 12;
i = 0;
while (qdcount--)
{
offsets[i++] = ptr - buf;
rrp = _new_listitem(&pkt->query);
rrp->data = decompress_label((char*)buf, len, (char*)ptr);
if (!rrp->data)
{
syslog(LOG_ERR, "dns_extractpkt: decompress_label choked in qd\n");
free(offsets);
dns_free(pkt);
return NULL;
}
rrp->len = strlen(rrp->data)+1;
ptr = _skip_lbl(ptr, &remain);
if (!ptr)
{
syslog(LOG_ERR, "dns_extractpkt: _skip_lbl choked in qd\n");
free(offsets);
dns_free(pkt);
return NULL;
}
ptr += 4;
remain -= 4;
}
while (ancount--)
{
if (remain < 12)
{
syslog(LOG_ERR, "dns_extractpkt: too less bytes in an\n");
free(offsets);
dns_free(pkt);
return NULL;
}
rrp = _new_listitem(&pkt->answer);
rrp->link = -1;
if ((ptr[0] & 0xc0) == 0xc0)
{
off = (ptr[0] & ~(0xc0)) * 256 + ptr[1];
for (j = 0; j < i; j++)
if (offsets[j] == off)
break;
if (j < i)
rrp->link = j;
}
// ptr = _skip_lbl(ptr, &remain);
// rrp->len = ptr[8]*256+ptr[9];
rrp->len = ptr[10]*256+ptr[11];
ptr += 12;
remain -= 12;
if (remain < rrp->len)
{
syslog(LOG_ERR, "dns_extractpkt: record too long in an (%d->%d)\n",
remain, rrp->len);
dns_free(pkt);
return NULL;
}
rrp->data = malloc(rrp->len);
memcpy(rrp->data, ptr, rrp->len);
ptr += rrp->len;
remain -= rrp->len;
}
return pkt;
}
const char *
dns_getquerydata(struct dnspkt *pkt)
{
struct rr *q;
static char *ret = NULL;
if (!pkt->query)
return NULL;
if (ret)
{
free(ret);
ret = NULL;
}
q = pkt->query;
pkt->query = pkt->query->next;
ret = q->data;
free(q);
return ret;
}
char *dns_getanswerdata (struct dnspkt *pkt, int *len)
{
struct rr *q;
static char *ret = NULL;
if (!pkt->answer)
return NULL;
q = pkt->answer;
pkt->answer = pkt->answer->next;
if (ret)
free(ret);
ret = q->data;
*len = q->len;
free(q);
return ret;
}
int
dns_getfreespace(const struct dnspkt *pkt, int type)
{
int raw, ret = 0, maxq;
raw = DNS_MAXPKT - dns_getpktsize(pkt);
if (raw < 0)
return -1;
if (type == DNS_RESPONSE) {
ret = raw - 14;
if (ret > 253)
ret = 253;
} else if (type == DNS_QUERY)
{
// ret = ((raw-suffixlen)*189-759)/256;
ret = (189*(254-suffixlen))/256-6;
if (ret > (maxq = (183-(189*suffixlen)/256)))
ret = maxq;
}
return (ret > 0) ? ret : 0;
}
syntax highlighted by Code2HTML, v. 0.9.1