diff options
Diffstat (limited to 'jni/iodine/src/dns.c')
-rw-r--r-- | jni/iodine/src/dns.c | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/jni/iodine/src/dns.c b/jni/iodine/src/dns.c new file mode 100644 index 0000000..fb2bcaf --- /dev/null +++ b/jni/iodine/src/dns.c @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <time.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <ctype.h> + +#ifdef WINDOWS32 +#include "windows.h" +#else +#include <arpa/nameser.h> +#ifdef DARWIN +#define BIND_8_COMPAT +#include <arpa/nameser_compat.h> +#endif +#include <arpa/inet.h> +#include <err.h> +#endif + + +#include "dns.h" +#include "encoding.h" +#include "read.h" + +int dnsc_use_edns0 = 1; + +#define CHECKLEN(x) if (buflen - (p-buf) < (x)) return 0 + +int +dns_encode(char *buf, size_t buflen, struct query *q, qr_t qr, char *data, size_t datalen) +{ + HEADER *header; + short name; + char *p; + int len; + int ancnt; + + if (buflen < sizeof(HEADER)) + return 0; + + memset(buf, 0, buflen); + + header = (HEADER*)buf; + + header->id = htons(q->id); + header->qr = (qr == QR_ANSWER); + header->opcode = 0; + header->aa = (qr == QR_ANSWER); + header->tc = 0; + header->rd = (qr == QR_QUERY); + header->ra = 0; + + p = buf + sizeof(HEADER); + + switch (qr) { + case QR_ANSWER: + header->qdcount = htons(1); + + name = 0xc000 | ((p - buf) & 0x3fff); + + /* Question section */ + putname(&p, buflen - (p - buf), q->name); + + CHECKLEN(4); + putshort(&p, q->type); + putshort(&p, C_IN); + + /* Answer section */ + + if (q->type == T_CNAME || q->type == T_A) { + /* data is expected to be like "Hblabla.host.name.com\0" */ + + char *startp; + int namelen; + + CHECKLEN(10); + putshort(&p, name); + if (q->type == T_A) + /* answer CNAME to A question */ + putshort(&p, T_CNAME); + else + putshort(&p, q->type); + putshort(&p, C_IN); + putlong(&p, 0); /* TTL */ + + startp = p; + p += 2; /* skip 2 bytes length */ + putname(&p, buflen - (p - buf), data); + CHECKLEN(0); + namelen = p - startp; + namelen -= 2; + putshort(&startp, namelen); + ancnt = 1; + } else if (q->type == T_MX || q->type == T_SRV) { + /* Data is expected to be like + "Hblabla.host.name.com\0Hanother.com\0\0" + For SRV, see RFC2782. + */ + + char *mxdata = data; + char *startp; + int namelen; + + ancnt = 1; + while (1) { + CHECKLEN(10); + putshort(&p, name); + putshort(&p, q->type); + putshort(&p, C_IN); + putlong(&p, 0); /* TTL */ + + startp = p; + p += 2; /* skip 2 bytes length */ + CHECKLEN(2); + putshort(&p, 10 * ancnt); /* preference */ + + if (q->type == T_SRV) { + /* weight, port (5060 = SIP) */ + CHECKLEN(4); + putshort(&p, 10); + putshort(&p, 5060); + } + + putname(&p, buflen - (p - buf), mxdata); + CHECKLEN(0); + namelen = p - startp; + namelen -= 2; + putshort(&startp, namelen); + + mxdata = mxdata + strlen(mxdata) + 1; + if (*mxdata == '\0') + break; + + ancnt++; + } + } else if (q->type == T_TXT) { + /* TXT has binary or base-X data */ + char *startp; + int txtlen; + + CHECKLEN(10); + putshort(&p, name); + putshort(&p, q->type); + putshort(&p, C_IN); + putlong(&p, 0); /* TTL */ + + startp = p; + p += 2; /* skip 2 bytes length */ + puttxtbin(&p, buflen - (p - buf), data, datalen); + CHECKLEN(0); + txtlen = p - startp; + txtlen -= 2; + putshort(&startp, txtlen); + ancnt = 1; + } else { + /* NULL has raw binary data */ + + CHECKLEN(10); + putshort(&p, name); + putshort(&p, q->type); + putshort(&p, C_IN); + putlong(&p, 0); /* TTL */ + + datalen = MIN(datalen, buflen - (p - buf)); + CHECKLEN(2); + putshort(&p, datalen); + CHECKLEN(datalen); + putdata(&p, data, datalen); + CHECKLEN(0); + ancnt = 1; + } + header->ancount = htons(ancnt); + break; + case QR_QUERY: + /* Note that iodined also uses this for forward queries */ + + header->qdcount = htons(1); + + datalen = MIN(datalen, buflen - (p - buf)); + putname(&p, datalen, data); + + CHECKLEN(4); + putshort(&p, q->type); + putshort(&p, C_IN); + + /* EDNS0 to advertise maximum response length + (even CNAME/A/MX, 255+255+header would be >512) */ + if (dnsc_use_edns0) { + header->arcount = htons(1); + /*XXX START adjust indent 1 tab forward*/ + CHECKLEN(11); + putbyte(&p, 0x00); /* Root */ + putshort(&p, 0x0029); /* OPT */ + putshort(&p, 0x1000); /* Payload size: 4096 */ + putshort(&p, 0x0000); /* Higher bits/edns version */ + putshort(&p, 0x8000); /* Z */ + putshort(&p, 0x0000); /* Data length */ + /*XXX END adjust indent 1 tab forward*/ + } + + break; + } + + len = p - buf; + + return len; +} + +int +dns_encode_ns_response(char *buf, size_t buflen, struct query *q, char *topdomain) +/* Only used when iodined gets an NS type query */ +/* Mostly same as dns_encode_a_response() below */ +{ + HEADER *header; + int len; + short name; + short topname; + short nsname; + char *ipp; + int domain_len; + char *p; + + if (buflen < sizeof(HEADER)) + return 0; + + memset(buf, 0, buflen); + + header = (HEADER*)buf; + + header->id = htons(q->id); + header->qr = 1; + header->opcode = 0; + header->aa = 1; + header->tc = 0; + header->rd = 0; + header->ra = 0; + + p = buf + sizeof(HEADER); + + header->qdcount = htons(1); + header->ancount = htons(1); + header->arcount = htons(1); + + /* pointer to start of name */ + name = 0xc000 | ((p - buf) & 0x3fff); + + domain_len = strlen(q->name) - strlen(topdomain); + if (domain_len < 0 || domain_len == 1) + return -1; + if (strcasecmp(q->name + domain_len, topdomain)) + return -1; + if (domain_len >= 1 && q->name[domain_len - 1] != '.') + return -1; + + /* pointer to start of topdomain; instead of dots at the end + we have length-bytes in front, so total length is the same */ + topname = 0xc000 | ((p - buf + domain_len) & 0x3fff); + + /* Query section */ + putname(&p, buflen - (p - buf), q->name); /* Name */ + CHECKLEN(4); + putshort(&p, q->type); /* Type */ + putshort(&p, C_IN); /* Class */ + + /* Answer section */ + CHECKLEN(12); + putshort(&p, name); /* Name */ + putshort(&p, q->type); /* Type */ + putshort(&p, C_IN); /* Class */ + putlong(&p, 3600); /* TTL */ + putshort(&p, 5); /* Data length */ + + /* pointer to ns.topdomain */ + nsname = 0xc000 | ((p - buf) & 0x3fff); + CHECKLEN(5); + putbyte(&p, 2); + putbyte(&p, 'n'); + putbyte(&p, 's'); + putshort(&p, topname); /* Name Server */ + + /* Additional data (A-record of NS server) */ + CHECKLEN(12); + putshort(&p, nsname); /* Name Server */ + putshort(&p, T_A); /* Type */ + putshort(&p, C_IN); /* Class */ + putlong(&p, 3600); /* TTL */ + putshort(&p, 4); /* Data length */ + + /* ugly hack to output IP address */ + ipp = (char *) &q->destination; + CHECKLEN(4); + putbyte(&p, *(ipp++)); + putbyte(&p, *(ipp++)); + putbyte(&p, *(ipp++)); + putbyte(&p, *ipp); + + len = p - buf; + return len; +} + +int +dns_encode_a_response(char *buf, size_t buflen, struct query *q) +/* Only used when iodined gets an A type query for ns.topdomain or www.topdomain */ +/* Mostly same as dns_encode_ns_response() above */ +{ + HEADER *header; + int len; + short name; + char *ipp; + char *p; + + if (buflen < sizeof(HEADER)) + return 0; + + memset(buf, 0, buflen); + + header = (HEADER*)buf; + + header->id = htons(q->id); + header->qr = 1; + header->opcode = 0; + header->aa = 1; + header->tc = 0; + header->rd = 0; + header->ra = 0; + + p = buf + sizeof(HEADER); + + header->qdcount = htons(1); + header->ancount = htons(1); + + /* pointer to start of name */ + name = 0xc000 | ((p - buf) & 0x3fff); + + /* Query section */ + putname(&p, buflen - (p - buf), q->name); /* Name */ + CHECKLEN(4); + putshort(&p, q->type); /* Type */ + putshort(&p, C_IN); /* Class */ + + /* Answer section */ + CHECKLEN(12); + putshort(&p, name); /* Name */ + putshort(&p, q->type); /* Type */ + putshort(&p, C_IN); /* Class */ + putlong(&p, 3600); /* TTL */ + putshort(&p, 4); /* Data length */ + + /* ugly hack to output IP address */ + ipp = (char *) &q->destination; + CHECKLEN(4); + putbyte(&p, *(ipp++)); + putbyte(&p, *(ipp++)); + putbyte(&p, *(ipp++)); + putbyte(&p, *ipp); + + len = p - buf; + return len; +} + +#undef CHECKLEN + +unsigned short +dns_get_id(char *packet, size_t packetlen) +{ + HEADER *header; + header = (HEADER*)packet; + + if (packetlen < sizeof(HEADER)) + return 0; + + return ntohs(header->id); +} + +#define CHECKLEN(x) if (packetlen - (data-packet) < (x)) return 0 + +int +dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, size_t packetlen) +{ + char name[QUERY_NAME_SIZE]; + char rdata[4*1024]; + HEADER *header; + short qdcount; + short ancount; + uint32_t ttl; + short class; + short type; + char *data; + short rlen; + int id; + int rv; + + q->id2 = 0; + rv = 0; + header = (HEADER*)packet; + + /* Reject short packets */ + if (packetlen < sizeof(HEADER)) + return 0; + + if (header->qr != qr) { + warnx("header->qr does not match the requested qr"); + return -1; + } + + data = packet + sizeof(HEADER); + qdcount = ntohs(header->qdcount); + ancount = ntohs(header->ancount); + + id = ntohs(header->id); + id = id & 0xFFFF; /* Kill any sign extension */ + + rlen = 0; + + if (q != NULL) + q->rcode = header->rcode; + + switch (qr) { + case QR_ANSWER: + if(qdcount < 1) { + /* We need a question */ + return -1; + } + + if (q != NULL) + q->id = id; + + /* Read name even if no answer, to give better error message */ + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(4); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + + /* if CHECKLEN okay, then we're sure to have a proper name */ + if (q != NULL) { + /* We only need the first char to check it */ + q->name[0] = name[0]; + q->name[1] = '\0'; + } + + if (ancount < 1) { + /* DNS errors like NXDOMAIN have ancount=0 and + stop here. CNAME may also have A; MX/SRV may have + multiple results. */ + return -1; + } + + /* Here type is still the question type */ + if (type == T_NULL) { + /* Assume that first answer is what we wanted */ + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(10); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + readlong(packet, &data, &ttl); + readshort(packet, &data, &rlen); + + rv = MIN(rlen, sizeof(rdata)); + rv = readdata(packet, &data, rdata, rv); + if (rv >= 2 && buf) { + rv = MIN(rv, buflen); + memcpy(buf, rdata, rv); + } else { + rv = 0; + } + } + else if ((type == T_A || type == T_CNAME) && buf) { + /* Assume that first answer is what we wanted */ + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(10); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + readlong(packet, &data, &ttl); + readshort(packet, &data, &rlen); + + memset(name, 0, sizeof(name)); + readname(packet, packetlen, &data, name, sizeof(name) - 1); + name[sizeof(name)-1] = '\0'; + strncpy(buf, name, buflen); + buf[buflen - 1] = '\0'; + rv = strlen(buf); + } + else if ((type == T_MX || type == T_SRV) && buf) { + /* We support 250 records, 250*(255+header) ~= 64kB. + Only exact 10-multiples are accepted, and gaps in + numbering are not jumped over (->truncated). + Hopefully DNS servers won't mess around too much. + */ + char names[250][QUERY_NAME_SIZE]; + char *rdatastart; + short pref; + int i; + int offset; + + memset(names, 0, sizeof(names)); + + for (i=0; i < ancount; i++) { + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(12); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + readlong(packet, &data, &ttl); + readshort(packet, &data, &rlen); + rdatastart = data; + readshort(packet, &data, &pref); + + if (type == T_SRV) { + /* skip weight, port */ + data += 4; + CHECKLEN(0); + } + + if (pref % 10 == 0 && pref >= 10 && + pref < 2500) { + readname(packet, packetlen, &data, + names[pref / 10 - 1], + QUERY_NAME_SIZE - 1); + names[pref / 10 - 1][QUERY_NAME_SIZE-1] = '\0'; + } + + /* always trust rlen, not name encoding */ + data = rdatastart + rlen; + CHECKLEN(0); + } + + /* output is like Hname10.com\0Hname20.com\0\0 */ + offset = 0; + i = 0; + while (names[i][0] != '\0') { + int l = MIN(strlen(names[i]), buflen-offset-2); + if (l <= 0) + break; + memcpy(buf + offset, names[i], l); + offset += l; + *(buf + offset) = '\0'; + offset++; + i++; + } + *(buf + offset) = '\0'; + rv = offset; + } + else if (type == T_TXT && buf) { + /* Assume that first answer is what we wanted */ + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(10); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + readlong(packet, &data, &ttl); + readshort(packet, &data, &rlen); + + rv = readtxtbin(packet, &data, rlen, rdata, sizeof(rdata)); + if (rv >= 1) { + rv = MIN(rv, buflen); + memcpy(buf, rdata, rv); + } else { + rv = 0; + } + } + + /* Here type is the answer type (note A->CNAME) */ + if (q != NULL) + q->type = type; + break; + case QR_QUERY: + if (qdcount < 1) { + warnx("no question section in name query"); + return -1; + } + + memset(name, 0, sizeof(name)); + readname(packet, packetlen, &data, name, sizeof(name) - 1); + name[sizeof(name)-1] = '\0'; + CHECKLEN(4); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + + if (q == NULL) { + rv = 0; + break; + } + + strncpy(q->name, name, sizeof(q->name)); + q->name[sizeof(q->name) - 1] = '\0'; + q->type = type; + q->id = id; + + rv = strlen(q->name); + break; + } + + return rv; +} + |