/*
 * 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;
}

