/* * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman * * 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 #include #include #include #include #include #include #ifdef WINDOWS32 #include "windows.h" #else #ifdef ANDROID #include "android_dns.h" #endif #include #ifdef DARWIN #define BIND_8_COMPAT #include #endif #include #include #include #endif #include "dns.h" #include "encoding.h" #include "read.h" int dnsc_use_edns0 = 1; #define CHECKLEN(x) if (buflen < (x) + (unsigned)(p-buf)) 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); 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 */ } 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 < (x) + (unsigned)(data-packet)) 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; }