// $Id: DNS.cc,v 1.23 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 2001, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include <ctype.h>

#include "NetVar.h"
#include "DNS.h"
#include "Sessions.h"
#include "Event.h"



DNS_Interpreter::DNS_Interpreter(Connection* arg_conn)
	{
	conn = arg_conn;
	}

int DNS_Interpreter::ParseMessage(const u_char* data, int len, int is_query)
	{
	int hdr_len = sizeof(DNS_RawMsgHdr);

	if ( len < hdr_len )
		{
		conn->Weird("truncated_DNS");
		return 0;
		}

	DNS_MsgInfo msg((DNS_RawMsgHdr*) data, is_query);

	const u_char* msg_start = data;	// needed for interpreting compression

	data += hdr_len;
	len -= hdr_len;

	if ( ! ParseQuestions(&msg, data, len, msg_start) )
		return 0;

	if ( ! ParseAnswers(&msg, msg.ancount, DNS_ANSWER,
				data, len, msg_start) )
		return 0;

	AddrVal server(conn->RespAddr());

	int skip_auth = 0;
	int skip_addl = 0;
	if ( msg.ancount > 0 )
		{ // We did an answer, so can potentially skip auth/addl.
		skip_auth = msg.nscount == 0 || dns_skip_auth->Lookup(&server);
		skip_addl = msg.arcount == 0 || dns_skip_addl->Lookup(&server);
		}

	if ( skip_auth && skip_addl )
		// No point doing further work parsing the message.
		return 1;

	msg.skip_event = skip_auth;
	if ( ! ParseAnswers(&msg, msg.nscount, DNS_AUTHORITY,
				data, len, msg_start) )
		return 0;

	if ( skip_addl )
		// No point doing further work parsing the message.
		return 1;

	msg.skip_event = skip_addl;
	if ( ! ParseAnswers(&msg, msg.arcount, DNS_ADDITIONAL,
				data, len, msg_start) )
		return 0;

	return 1;
	}

int DNS_Interpreter::ParseQuestions(DNS_MsgInfo* msg,
				const u_char*& data, int& len,
				const u_char* msg_start)
	{
	int n = msg->qdcount;

	while ( n > 0 && ParseQuestion(msg, data, len, msg_start) )
		--n;

	return n == 0;
	}

int DNS_Interpreter::ParseAnswers(DNS_MsgInfo* msg, int n, DNS_AnswerType atype,
				const u_char*& data, int& len,
				const u_char* msg_start)
	{
	msg->answer_type = atype;

	while ( n > 0 && ParseAnswer(msg, data, len, msg_start) )
		--n;

	return n == 0;
	}

int DNS_Interpreter::ParseQuestion(DNS_MsgInfo* msg,
				const u_char*& data, int& len,
				const u_char* msg_start)
	{
	u_char name[513];
	int name_len = sizeof(name) - 1;

	u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
	if ( ! name_end )
		return 0;

	if ( len < int(sizeof(short)) * 2 )
		{
		conn->Weird("truncated_DNS");
		return 0;
		}

	RR_Type qtype = RR_Type(ExtractShort(data, len));
	int qclass = ExtractShort(data, len);
	EventHandlerPtr dns_event = 0;

	if ( msg->QR == 0 )
		dns_event = dns_request;

	else if ( msg->QR == 1 &&
		  msg->ancount == 0 && msg->nscount == 0 && msg->arcount == 0 )
		// Service rejected in some fashion, and it won't be reported
		// via a returned RR because there aren't any.
		dns_event = dns_rejected;

	if ( dns_event && ! msg->skip_event )
		{
		BroString* question_name = new BroString(name, name_end - name, 1);

		val_list* vl = new val_list;
		vl->append(conn->BuildConnVal());
		vl->append(msg->BuildHdrVal());
		vl->append(new StringVal(question_name));
		vl->append(new Val(qtype, TYPE_COUNT));
		vl->append(new Val(qclass, TYPE_COUNT));

		conn->ConnectionEvent(dns_event, vl);
		}

	return 1;
	}

int DNS_Interpreter::ParseAnswer(DNS_MsgInfo* msg,
				const u_char*& data, int& len,
				const u_char* msg_start)
	{
	u_char name[513];
	int name_len = sizeof(name) - 1;

	u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
	if ( ! name_end )
		return 0;

	if ( len < int(sizeof(short)) * 2 )
		{
		conn->Weird("truncated_DNS");
		return 0;
		}

	Unref(msg->query_name);
	msg->query_name = new StringVal(new BroString(name, name_end - name, 1));
	msg->atype = RR_Type(ExtractShort(data, len));
	msg->aclass = ExtractShort(data, len);
	msg->ttl = ExtractLong(data, len);

	int rdlength = ExtractShort(data, len);
	if ( rdlength > len )
		{
		conn->Weird("truncated_DNS_RR");
		return 0;
		}

	int status;
	switch ( msg->atype ) {
		case TYPE_A:
			status = ParseRR_A(msg, data, len, rdlength);
			break;

		case TYPE_NS:
		case TYPE_CNAME:
		case TYPE_PTR:
			status = ParseRR_Name(msg, data, len, rdlength, msg_start);
			break;

		case TYPE_SOA:
			status = ParseRR_SOA(msg, data, len, rdlength, msg_start);
			break;

		case TYPE_WKS:
			status = ParseRR_WKS(msg, data, len, rdlength);
			break;

		case TYPE_HINFO:
			status = ParseRR_HINFO(msg, data, len, rdlength);
			break;

		case TYPE_MX:
			status = ParseRR_MX(msg, data, len, rdlength, msg_start);
			break;

		case TYPE_TXT:
			status = ParseRR_TXT(msg, data, len, rdlength);
			break;

		case TYPE_SRV:
			status = ParseRR_SRV(msg, data, len, rdlength, msg_start);
			break;

		case TYPE_EDNS0:
			status = ParseRR_EDNS0(msg, data, len, rdlength, msg_start);
			break;

		default:
			conn->Weird("unknown_DNS_RR_type");
			data += rdlength;
			len -= rdlength;
			status = 1;
			break;
	}

	return status;
	}

u_char* DNS_Interpreter::ExtractName(const u_char*& data, int& len,
					u_char* name, int name_len,
					const u_char* msg_start)
	{
	u_char* name_start = name;

	while ( ExtractLabel(data, len, name, name_len, msg_start) )
		;

	int n = name - name_start;

	if ( n >= 255 )
		conn->Weird("DNS_name_too_long");

	if ( n >= 2 && name[-1] == '.' )
		{
		// Remove trailing dot.
		--name;
		name[0] = 0;
		}

	// Convert labels to lower case for consistency.
	for ( u_char* np = name_start; np < name; ++np )
		if ( isupper(*np) )
			*np = tolower(*np);

	return name;
	}

int DNS_Interpreter::ExtractLabel(const u_char*& data, int& len,
				u_char*& name, int& name_len,
				const u_char* msg_start)
	{
	if ( len <= 0 )
		return 0;

	const u_char* orig_data = data;
	int label_len = data[0];

	++data;
	--len;

	if ( len <= 0 )
		return 0;

	if ( label_len == 0 )
		// Found terminating label.
		return 0;

	if ( (label_len & 0xc0) == 0xc0 )
		{
		unsigned short offset = (label_len & ~0xc0) << 8;

		offset |= *data;

		++data;
		--len;

		if ( offset >= orig_data - msg_start )
			{
			// (You'd think that actually the offset should be
			//  at least 6 bytes below our current position:
			//  2 bytes for a non-trivial label, plus 4 bytes for
			//  its class and type, which presumably are between
			//  our current location and the instance of the label.
			//  But actually this turns out not to be the case -
			//  sometimes compression points to compression.)

			conn->Weird("forward_DNS_compression_offset");
			return 0;
			}

		// Recursively resolve name.
		const u_char* recurse_data = msg_start + offset;
		int recurse_max_len = orig_data - recurse_data;

		u_char* name_end = ExtractName(recurse_data, recurse_max_len,
						name, name_len, msg_start);

		name_len -= name_end - name;
		name = name_end;

		return 0;
		}

	if ( label_len > len )
		{
		conn->Weird("DNS_truncated_label");
		data += len;	// consume the rest of the packet
		len = 0;
		return 0;
		}

	if ( label_len > 63 || label_len >= name_len )
		{
		conn->Weird("DNS_label_too_long");
		return 0;
		}

	memcpy(name, data, label_len);
	name[label_len] = '.';

	name += label_len + 1;
	name_len -= label_len + 1;

	data += label_len;
	len -= label_len;

	return 1;
	}

int DNS_Interpreter::ExtractShort(const u_char*& data, int& len)
	{
	if ( len < int(sizeof(unsigned short)) )
		return 0;

	unsigned short val;

	val = data[0] << 8;

	++data;
	--len;

	val |= data[0];

	++data;
	--len;

	return val;
	}

int DNS_Interpreter::ExtractLong(const u_char*& data, int& len)
	{
	if ( len < int(sizeof(unsigned long)) )
		return 0;

	uint32 val;

	val = data[0] << 24;
	val |= data[1] << 16;
	val |= data[2] << 8;
	val |= data[3];

	data += sizeof(val);
	len -= sizeof(val);

	return val;
	}

int DNS_Interpreter::ParseRR_Name(DNS_MsgInfo* msg,
				const u_char*& data, int& len, int rdlength,
				const u_char* msg_start)
	{
	const u_char* data_start = data;

	u_char name[513];
	int name_len = sizeof(name) - 1;

	u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
	if ( ! name_end )
		return 0;

	if ( data - data_start != rdlength )
		conn->Weird("DNS_RR_length_mismatch");

	EventHandlerPtr reply_event;
	switch ( msg->atype ) {
		case TYPE_NS:
			reply_event = dns_NS_reply;
			break;

		case TYPE_CNAME:
			reply_event = dns_CNAME_reply;
			break;

		case TYPE_PTR:
			reply_event = dns_PTR_reply;
			break;

		default:
			conn->Internal("bad RR type in DNS_Interpreter::ParseRR_Name");
			reply_event = 0;
	}

	if ( reply_event && ! msg->skip_event )
		{
		val_list* vl = new val_list;

		vl->append(conn->BuildConnVal());
		vl->append(msg->BuildHdrVal());
		vl->append(msg->BuildAnswerVal());
		vl->append(new StringVal(new BroString(name, name_end - name, 1)));

		conn->ConnectionEvent(reply_event, vl);
		}

	return 1;
	}

int DNS_Interpreter::ParseRR_SOA(DNS_MsgInfo* msg,
				const u_char*& data, int& len, int rdlength,
				const u_char* msg_start)
	{
	const u_char* data_start = data;

	u_char mname[513];
	int mname_len = sizeof(mname) - 1;

	u_char* mname_end = ExtractName(data, len, mname, mname_len, msg_start);
	if ( ! mname_end )
		return 0;

	u_char rname[513];
	int rname_len = sizeof(rname) - 1;

	u_char* rname_end = ExtractName(data, len, rname, rname_len, msg_start);
	if ( ! rname_end )
		return 0;

	if ( len < 20 )
		return 0;

	uint32 serial = ExtractLong(data, len);
	uint32 refresh = ExtractLong(data, len);
	uint32 retry = ExtractLong(data, len);
	uint32 expire = ExtractLong(data, len);
	uint32 minimum = ExtractLong(data, len);

	if ( data - data_start != rdlength )
		conn->Weird("DNS_RR_length_mismatch");

	if ( dns_SOA_reply && ! msg->skip_event )
		{
		val_list* vl = new val_list;

		vl->append(conn->BuildConnVal());
		vl->append(msg->BuildHdrVal());
		vl->append(msg->BuildAnswerVal());

		RecordVal* r = new RecordVal(dns_soa);

		r->Assign(0, new StringVal(new BroString(mname, mname_end - mname, 1)));
		r->Assign(1, new StringVal(new BroString(rname, rname_end - rname, 1)));
		r->Assign(2, new Val(serial, TYPE_COUNT));
		r->Assign(3, new IntervalVal(double(refresh), Seconds));
		r->Assign(4, new IntervalVal(double(retry), Seconds));
		r->Assign(5, new IntervalVal(double(expire), Seconds));
		r->Assign(6, new IntervalVal(double(minimum), Seconds));

		vl->append(r);

		conn->ConnectionEvent(dns_SOA_reply, vl);
		}

	return 1;
	}

int DNS_Interpreter::ParseRR_MX(DNS_MsgInfo* msg,
				const u_char*& data, int& len, int rdlength,
				const u_char* msg_start)
	{
	const u_char* data_start = data;

	int preference = ExtractShort(data, len);

	u_char name[513];
	int name_len = sizeof(name) - 1;

	u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
	if ( ! name_end )
		return 0;

	if ( data - data_start != rdlength )
		conn->Weird("DNS_RR_length_mismatch");

	if ( dns_MX_reply && ! msg->skip_event )
		{
		val_list* vl = new val_list;

		vl->append(conn->BuildConnVal());
		vl->append(msg->BuildHdrVal());
		vl->append(msg->BuildAnswerVal());
		vl->append(new StringVal(new BroString(name, name_end - name, 1)));
		vl->append(new Val(preference, TYPE_COUNT));

		conn->ConnectionEvent(dns_MX_reply, vl);
		}

	return 1;
	}

int DNS_Interpreter::ParseRR_SRV(DNS_MsgInfo* msg,
				const u_char*& data, int& len, int rdlength,
				const u_char* msg_start)
	{
	const u_char* data_start = data;

	unsigned int priority = ExtractShort(data, len);
	unsigned int weight = ExtractShort(data, len);
	unsigned int port = ExtractShort(data, len);

	u_char name[513];
	int name_len = sizeof(name) - 1;

	u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
	if ( ! name_end )
		return 0;

	if ( data - data_start != rdlength )
		conn->Weird("DNS_RR_length_mismatch");

	char buf[2048];
	sprintf(buf, "SRV %s priority=%d weight=%d port=%d", name, priority, weight, port);
	return 1;
	}

int DNS_Interpreter::ParseRR_EDNS0(DNS_MsgInfo* msg,
				const u_char*& data, int& len, int rdlength,
				const u_char* msg_start)
	{
	data += rdlength;
	len -= rdlength;

	if ( dns_EDNS0 && ! msg->skip_event )
		{
		val_list* vl = new val_list;

		vl->append(conn->BuildConnVal());
		vl->append(msg->BuildHdrVal());
		vl->append(msg->BuildAnswerVal());

		conn->ConnectionEvent(dns_EDNS0, vl);
		}

	return 1;
	}

int DNS_Interpreter::ParseRR_A(DNS_MsgInfo* msg,
				const u_char*& data, int& len, int rdlength)
	{
	if ( rdlength != 4 )
		{
		conn->Weird("bad_DNS_RR_length");
		return 0;
		}

	uint32 addr = ExtractLong(data, len);

	if ( dns_A_reply && ! msg->skip_event )
		{
		val_list* vl = new val_list;

		vl->append(conn->BuildConnVal());
		vl->append(msg->BuildHdrVal());
		vl->append(msg->BuildAnswerVal());
		vl->append(new AddrVal(htonl(addr)));

		conn->ConnectionEvent(dns_A_reply, vl);
		}

	return 1;
	}

int DNS_Interpreter::ParseRR_WKS(DNS_MsgInfo* msg,
				const u_char*& data, int& len, int rdlength)
	{
	data += rdlength;
	len -= rdlength;

	return 1;
	}

int DNS_Interpreter::ParseRR_HINFO(DNS_MsgInfo* msg,
				const u_char*& data, int& len, int rdlength)
	{
	data += rdlength;
	len -= rdlength;

	return 1;
	}

int DNS_Interpreter::ParseRR_TXT(DNS_MsgInfo* msg,
				const u_char*& data, int& len, int rdlength)
	{
	data += rdlength;
	len -= rdlength;

	return 1;
	}


DNS_MsgInfo::DNS_MsgInfo(DNS_RawMsgHdr* hdr, int arg_is_query)
	{
	//### Need to fix alignment if hdr is misaligned (not on a short
	// boundary).
	unsigned short flags = ntohs(hdr->flags);

	QR = (flags & 0x8000) != 0;
	opcode = (flags & 0x7800) >> 11;
	AA = (flags & 0x0400) != 0;
	TC = (flags & 0x0200) != 0;
	RD = (flags & 0x0100) != 0;
	RA = (flags & 0x0080) != 0;
	Z = (flags & 0x0070) >> 4;
	rcode = (flags & 0x000f);

	qdcount = ntohs(hdr->qdcount);
	ancount = ntohs(hdr->ancount);
	nscount = ntohs(hdr->nscount);
	arcount = ntohs(hdr->arcount);

	id = ntohs(hdr->id);
	is_query = arg_is_query;

	query_name = 0;
	atype = TYPE_ALL;
	aclass = 0;
	ttl = 0;

	answer_type = DNS_QUESTION;
	skip_event = 0;
	}

DNS_MsgInfo::~DNS_MsgInfo()
	{
	Unref(query_name);
	}

Val* DNS_MsgInfo::BuildHdrVal()
	{
	RecordVal* r = new RecordVal(dns_msg);

	r->Assign(0, new Val(id, TYPE_COUNT));
	r->Assign(1, new Val(opcode, TYPE_COUNT));
	r->Assign(2, new Val(rcode, TYPE_COUNT));
	r->Assign(3, new Val(QR, TYPE_BOOL));
	r->Assign(4, new Val(AA, TYPE_BOOL));
	r->Assign(5, new Val(TC, TYPE_BOOL));
	r->Assign(6, new Val(RD, TYPE_BOOL));
	r->Assign(7, new Val(RA, TYPE_BOOL));
	r->Assign(8, new Val(Z, TYPE_COUNT));
	r->Assign(9, new Val(qdcount, TYPE_COUNT));
	r->Assign(10, new Val(ancount, TYPE_COUNT));
	r->Assign(11, new Val(nscount, TYPE_COUNT));
	r->Assign(12, new Val(arcount, TYPE_COUNT));

	return r;
	}

Val* DNS_MsgInfo::BuildAnswerVal()
	{
	RecordVal* r = new RecordVal(dns_answer);

	Ref(query_name);
	r->Assign(0, new Val(int(answer_type), TYPE_COUNT));
	r->Assign(1, query_name);
	r->Assign(2, new Val(atype, TYPE_COUNT));
	r->Assign(3, new Val(aclass, TYPE_COUNT));
	r->Assign(4, new IntervalVal(double(ttl), Seconds));

	return r;
	}


TCP_Endpoint_DNS::TCP_Endpoint_DNS(DNS_Interpreter* arg_interp, int arg_is_orig)
: TCP_EndpointContents(arg_interp->TCP_Conn(), arg_is_orig)
	{
	interp = arg_interp;
	is_orig = arg_is_orig;
	msg_buf = 0;
	buf_n = msg_size = 0;
	state = DNS_LEN_HI;
	}

TCP_Endpoint_DNS::~TCP_Endpoint_DNS()
	{
	delete msg_buf;
	}

void TCP_Endpoint_DNS::Flush()
	{
	if ( buf_n > 0 )
		{ // Deliver partial message.
		interp->ParseMessage(msg_buf, buf_n, is_orig);
		msg_size = 0;
		}
	}

void TCP_Endpoint_DNS::Deliver(double t, int seq, int len, u_char* data)
	{
	if ( state == DNS_LEN_HI )
		{
		msg_size = (*data) << 8;
		state = DNS_LEN_LO;

		++data;
		--len;

		if ( len == 0 )
			return;
		}

	if ( state == DNS_LEN_LO )
		{
		msg_size += *data;
		state = DNS_MESSAGE_BUFFER;

		buf_n = 0;

		if ( msg_buf )
			{
			if ( buf_len < msg_size )
				{
				buf_len = msg_size;
				msg_buf = (u_char*) safe_realloc((void*) msg_buf, buf_len);
				}
			}
		else
			{
			buf_len = msg_size;
			msg_buf = (u_char*) safe_malloc(buf_len);
			}

		++data;
		--len;

		if ( len == 0 )
			return;
		}

	if ( state != DNS_MESSAGE_BUFFER )
		conn->Internal("state inconsistency in TCP_Endpoint_DNS::Deliver");

	int n;
	for ( n = 0; buf_n < msg_size && n < len; ++n )
		msg_buf[buf_n++] = data[n];

	if ( buf_n < msg_size )
		// Haven't filled up the message buffer yet, no more to do.
		return;

	(void) interp->ParseMessage(msg_buf, msg_size, is_orig);
	buf_n = 0;

	state = DNS_LEN_HI;

	if ( n < len )
		// More data to munch on.
		Deliver(t, seq, len - n, data + n);
	}

TCP_DNS::TCP_DNS(NetSessions* s, HashKey* k, double t, const ConnID* id,
		const struct tcphdr* tp)
: TCP_Connection(s, k, t, id, tp)
	{
	interp = new DNS_Interpreter(this);
	}

void TCP_DNS::BuildEndpoints()
	{
	orig = new TCP_Endpoint_DNS(interp, 1);
	resp = new TCP_Endpoint_DNS(interp, 0);
	}

TCP_DNS::~TCP_DNS()
	{
	delete interp;
	}

void TCP_DNS::Done()
	{
	interp->Timeout();
	TCP_Connection::Done();
	}

void TCP_DNS::ConnectionClosed(TCP_Endpoint* endpoint,
				TCP_Endpoint* peer, int gen_event)
	{
	((TCP_Endpoint_DNS*) orig)->Flush();
	((TCP_Endpoint_DNS*) resp)->Flush();

	TCP_Connection::ConnectionClosed(endpoint, peer, gen_event);
	}

UDP_DNS::UDP_DNS(NetSessions* s, HashKey* k, double t, const ConnID* id,
		const struct udphdr* up)
: UDP_Connection(s, k, t, id, up)
	{
	interp = new DNS_Interpreter(this);
	did_session_done = 0;
	ADD_TIMER(&UDP_DNS::ExpireTimer, t + dns_session_timeout, 1);
	}

UDP_DNS::~UDP_DNS()
	{
	delete interp;
	}

void UDP_DNS::Done()
	{
	if ( ! did_session_done )
		Event(udp_session_done);

	UDP_Connection::Done();
	}

int UDP_DNS::Request(double /* t */, const u_char* data, int len)
	{
	(void) interp->ParseMessage(data, len, 1);
	return 1;
	}

int UDP_DNS::Reply(double /* t */, const u_char* data, int len)
	{
	(void) interp->ParseMessage(data, len, 0);

	return 1;
	}

void UDP_DNS::ExpireTimer(double t)
	{
	// The - 1.0 in the following is to allow 1 second for the
	// common case of a single request followed by a single reply,
	// so we don't needlessly set the timer twice in that case.
	if ( t - last_time >= dns_session_timeout - 1.0 )
		{
		Event(connection_timeout);
		sessions->Remove(this, 1);
		}
	else
		ADD_TIMER(&UDP_DNS::ExpireTimer, t + dns_session_timeout, 1);
	}

int UDP_DNS::IsReuse(double /* t */, const u_char* /* pkt */)
	{
	return did_session_done;
	}
