1238106Sdes/*
2238106Sdes * iterator/iter_scrub.c - scrubbing, normalization, sanitization of DNS msgs.
3238106Sdes *
4238106Sdes * Copyright (c) 2007, NLnet Labs. All rights reserved.
5238106Sdes *
6238106Sdes * This software is open source.
7238106Sdes *
8238106Sdes * Redistribution and use in source and binary forms, with or without
9238106Sdes * modification, are permitted provided that the following conditions
10238106Sdes * are met:
11238106Sdes *
12238106Sdes * Redistributions of source code must retain the above copyright notice,
13238106Sdes * this list of conditions and the following disclaimer.
14238106Sdes *
15238106Sdes * Redistributions in binary form must reproduce the above copyright notice,
16238106Sdes * this list of conditions and the following disclaimer in the documentation
17238106Sdes * and/or other materials provided with the distribution.
18238106Sdes *
19238106Sdes * Neither the name of the NLNET LABS nor the names of its contributors may
20238106Sdes * be used to endorse or promote products derived from this software without
21238106Sdes * specific prior written permission.
22238106Sdes *
23238106Sdes * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24269257Sdes * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25269257Sdes * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26269257Sdes * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27269257Sdes * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28269257Sdes * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29269257Sdes * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30269257Sdes * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31269257Sdes * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32269257Sdes * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33269257Sdes * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34238106Sdes */
35238106Sdes
36238106Sdes/**
37238106Sdes * \file
38238106Sdes *
39238106Sdes * This file has routine(s) for cleaning up incoming DNS messages from
40238106Sdes * possible useless or malicious junk in it.
41238106Sdes */
42238106Sdes#include "config.h"
43238106Sdes#include "iterator/iter_scrub.h"
44238106Sdes#include "iterator/iterator.h"
45238106Sdes#include "iterator/iter_priv.h"
46238106Sdes#include "services/cache/rrset.h"
47238106Sdes#include "util/log.h"
48238106Sdes#include "util/net_help.h"
49238106Sdes#include "util/regional.h"
50238106Sdes#include "util/config_file.h"
51238106Sdes#include "util/module.h"
52238106Sdes#include "util/data/msgparse.h"
53238106Sdes#include "util/data/dname.h"
54238106Sdes#include "util/data/msgreply.h"
55238106Sdes#include "util/alloc.h"
56291767Sdes#include "sldns/sbuffer.h"
57238106Sdes
58238106Sdes/** RRset flag used during scrubbing. The RRset is OK. */
59238106Sdes#define RRSET_SCRUB_OK	0x80
60238106Sdes
61238106Sdes/** remove rrset, update loop variables */
62238106Sdesstatic void
63269257Sdesremove_rrset(const char* str, sldns_buffer* pkt, struct msg_parse* msg,
64238106Sdes	struct rrset_parse* prev, struct rrset_parse** rrset)
65238106Sdes{
66269257Sdes	if(verbosity >= VERB_QUERY && str
67238106Sdes		&& (*rrset)->dname_len <= LDNS_MAX_DOMAINLEN) {
68238106Sdes		uint8_t buf[LDNS_MAX_DOMAINLEN+1];
69238106Sdes		dname_pkt_copy(pkt, buf, (*rrset)->dname);
70238106Sdes		log_nametypeclass(VERB_QUERY, str, buf,
71238106Sdes			(*rrset)->type, ntohs((*rrset)->rrset_class));
72238106Sdes	}
73238106Sdes	if(prev)
74238106Sdes		prev->rrset_all_next = (*rrset)->rrset_all_next;
75238106Sdes	else	msg->rrset_first = (*rrset)->rrset_all_next;
76238106Sdes	if(msg->rrset_last == *rrset)
77238106Sdes		msg->rrset_last = prev;
78238106Sdes	msg->rrset_count --;
79238106Sdes	switch((*rrset)->section) {
80238106Sdes		case LDNS_SECTION_ANSWER: msg->an_rrsets--; break;
81238106Sdes		case LDNS_SECTION_AUTHORITY: msg->ns_rrsets--; break;
82238106Sdes		case LDNS_SECTION_ADDITIONAL: msg->ar_rrsets--; break;
83238106Sdes		default: log_assert(0);
84238106Sdes	}
85238106Sdes	msgparse_bucket_remove(msg, *rrset);
86238106Sdes	*rrset = (*rrset)->rrset_all_next;
87238106Sdes}
88238106Sdes
89238106Sdes/** return true if rr type has additional names in it */
90238106Sdesstatic int
91238106Sdeshas_additional(uint16_t t)
92238106Sdes{
93238106Sdes	switch(t) {
94238106Sdes		case LDNS_RR_TYPE_MB:
95238106Sdes		case LDNS_RR_TYPE_MD:
96238106Sdes		case LDNS_RR_TYPE_MF:
97238106Sdes		case LDNS_RR_TYPE_NS:
98238106Sdes		case LDNS_RR_TYPE_MX:
99238106Sdes		case LDNS_RR_TYPE_KX:
100238106Sdes		case LDNS_RR_TYPE_SRV:
101238106Sdes			return 1;
102238106Sdes		case LDNS_RR_TYPE_NAPTR:
103238106Sdes			/* TODO: NAPTR not supported, glue stripped off */
104238106Sdes			return 0;
105238106Sdes	}
106238106Sdes	return 0;
107238106Sdes}
108238106Sdes
109238106Sdes/** get additional name from rrset RR, return false if no name present */
110238106Sdesstatic int
111238106Sdesget_additional_name(struct rrset_parse* rrset, struct rr_parse* rr,
112269257Sdes	uint8_t** nm, size_t* nmlen, sldns_buffer* pkt)
113238106Sdes{
114238106Sdes	size_t offset = 0;
115238106Sdes	size_t len, oldpos;
116238106Sdes	switch(rrset->type) {
117238106Sdes		case LDNS_RR_TYPE_MB:
118238106Sdes		case LDNS_RR_TYPE_MD:
119238106Sdes		case LDNS_RR_TYPE_MF:
120238106Sdes		case LDNS_RR_TYPE_NS:
121238106Sdes			offset = 0;
122238106Sdes			break;
123238106Sdes		case LDNS_RR_TYPE_MX:
124238106Sdes		case LDNS_RR_TYPE_KX:
125238106Sdes			offset = 2;
126238106Sdes			break;
127238106Sdes		case LDNS_RR_TYPE_SRV:
128238106Sdes			offset = 6;
129238106Sdes			break;
130238106Sdes		case LDNS_RR_TYPE_NAPTR:
131238106Sdes			/* TODO: NAPTR not supported, glue stripped off */
132238106Sdes			return 0;
133238106Sdes		default:
134238106Sdes			return 0;
135238106Sdes	}
136269257Sdes	len = sldns_read_uint16(rr->ttl_data+sizeof(uint32_t));
137238106Sdes	if(len < offset+1)
138238106Sdes		return 0; /* rdata field too small */
139238106Sdes	*nm = rr->ttl_data+sizeof(uint32_t)+sizeof(uint16_t)+offset;
140269257Sdes	oldpos = sldns_buffer_position(pkt);
141269257Sdes	sldns_buffer_set_position(pkt, (size_t)(*nm - sldns_buffer_begin(pkt)));
142238106Sdes	*nmlen = pkt_dname_len(pkt);
143269257Sdes	sldns_buffer_set_position(pkt, oldpos);
144238106Sdes	if(*nmlen == 0)
145238106Sdes		return 0;
146238106Sdes	return 1;
147238106Sdes}
148238106Sdes
149238106Sdes/** Place mark on rrsets in additional section they are OK */
150238106Sdesstatic void
151269257Sdesmark_additional_rrset(sldns_buffer* pkt, struct msg_parse* msg,
152238106Sdes	struct rrset_parse* rrset)
153238106Sdes{
154238106Sdes	/* Mark A and AAAA for NS as appropriate additional section info. */
155238106Sdes	uint8_t* nm = NULL;
156238106Sdes	size_t nmlen = 0;
157238106Sdes	struct rr_parse* rr;
158238106Sdes
159238106Sdes	if(!has_additional(rrset->type))
160238106Sdes		return;
161238106Sdes	for(rr = rrset->rr_first; rr; rr = rr->next) {
162238106Sdes		if(get_additional_name(rrset, rr, &nm, &nmlen, pkt)) {
163238106Sdes			/* mark A */
164238106Sdes			hashvalue_t h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_A,
165238106Sdes				rrset->rrset_class, 0);
166238106Sdes			struct rrset_parse* r = msgparse_hashtable_lookup(
167238106Sdes				msg, pkt, h, 0, nm, nmlen,
168238106Sdes				LDNS_RR_TYPE_A, rrset->rrset_class);
169238106Sdes			if(r && r->section == LDNS_SECTION_ADDITIONAL) {
170238106Sdes				r->flags |= RRSET_SCRUB_OK;
171238106Sdes			}
172238106Sdes
173238106Sdes			/* mark AAAA */
174238106Sdes			h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_AAAA,
175238106Sdes				rrset->rrset_class, 0);
176238106Sdes			r = msgparse_hashtable_lookup(msg, pkt, h, 0, nm,
177238106Sdes				nmlen, LDNS_RR_TYPE_AAAA, rrset->rrset_class);
178238106Sdes			if(r && r->section == LDNS_SECTION_ADDITIONAL) {
179238106Sdes				r->flags |= RRSET_SCRUB_OK;
180238106Sdes			}
181238106Sdes		}
182238106Sdes	}
183238106Sdes}
184238106Sdes
185238106Sdes/** Get target name of a CNAME */
186238106Sdesstatic int
187238106Sdesparse_get_cname_target(struct rrset_parse* rrset, uint8_t** sname,
188238106Sdes	size_t* snamelen)
189238106Sdes{
190238106Sdes	if(rrset->rr_count != 1) {
191238106Sdes		struct rr_parse* sig;
192238106Sdes		verbose(VERB_ALGO, "Found CNAME rrset with "
193238106Sdes			"size > 1: %u", (unsigned)rrset->rr_count);
194238106Sdes		/* use the first CNAME! */
195238106Sdes		rrset->rr_count = 1;
196238106Sdes		rrset->size = rrset->rr_first->size;
197238106Sdes		for(sig=rrset->rrsig_first; sig; sig=sig->next)
198238106Sdes			rrset->size += sig->size;
199238106Sdes		rrset->rr_last = rrset->rr_first;
200238106Sdes		rrset->rr_first->next = NULL;
201238106Sdes	}
202238106Sdes	if(rrset->rr_first->size < sizeof(uint16_t)+1)
203238106Sdes		return 0; /* CNAME rdata too small */
204238106Sdes	*sname = rrset->rr_first->ttl_data + sizeof(uint32_t)
205238106Sdes		+ sizeof(uint16_t); /* skip ttl, rdatalen */
206238106Sdes	*snamelen = rrset->rr_first->size - sizeof(uint16_t);
207238106Sdes	return 1;
208238106Sdes}
209238106Sdes
210238106Sdes/** Synthesize CNAME from DNAME, false if too long */
211238106Sdesstatic int
212238106Sdessynth_cname(uint8_t* qname, size_t qnamelen, struct rrset_parse* dname_rrset,
213269257Sdes	uint8_t* alias, size_t* aliaslen, sldns_buffer* pkt)
214238106Sdes{
215238106Sdes	/* we already know that sname is a strict subdomain of DNAME owner */
216238106Sdes	uint8_t* dtarg = NULL;
217238106Sdes	size_t dtarglen;
218238106Sdes	if(!parse_get_cname_target(dname_rrset, &dtarg, &dtarglen))
219238106Sdes		return 0;
220238106Sdes	log_assert(qnamelen > dname_rrset->dname_len);
221238106Sdes	/* DNAME from com. to net. with qname example.com. -> example.net. */
222238106Sdes	/* so: \3com\0 to \3net\0 and qname \7example\3com\0 */
223238106Sdes	*aliaslen = qnamelen + dtarglen - dname_rrset->dname_len;
224238106Sdes	if(*aliaslen > LDNS_MAX_DOMAINLEN)
225238106Sdes		return 0; /* should have been RCODE YXDOMAIN */
226238106Sdes	/* decompress dnames into buffer, we know it fits */
227238106Sdes	dname_pkt_copy(pkt, alias, qname);
228238106Sdes	dname_pkt_copy(pkt, alias+(qnamelen-dname_rrset->dname_len), dtarg);
229238106Sdes	return 1;
230238106Sdes}
231238106Sdes
232238106Sdes/** synthesize a CNAME rrset */
233238106Sdesstatic struct rrset_parse*
234238106Sdessynth_cname_rrset(uint8_t** sname, size_t* snamelen, uint8_t* alias,
235238106Sdes	size_t aliaslen, struct regional* region, struct msg_parse* msg,
236238106Sdes	struct rrset_parse* rrset, struct rrset_parse* prev,
237269257Sdes	struct rrset_parse* nx, sldns_buffer* pkt)
238238106Sdes{
239238106Sdes	struct rrset_parse* cn = (struct rrset_parse*)regional_alloc(region,
240238106Sdes		sizeof(struct rrset_parse));
241238106Sdes	if(!cn)
242238106Sdes		return NULL;
243238106Sdes	memset(cn, 0, sizeof(*cn));
244238106Sdes	cn->rr_first = (struct rr_parse*)regional_alloc(region,
245238106Sdes		sizeof(struct rr_parse));
246238106Sdes	if(!cn->rr_first)
247238106Sdes		return NULL;
248238106Sdes	cn->rr_last = cn->rr_first;
249238106Sdes	/* CNAME from sname to alias */
250238106Sdes	cn->dname = (uint8_t*)regional_alloc(region, *snamelen);
251238106Sdes	if(!cn->dname)
252238106Sdes		return NULL;
253238106Sdes	dname_pkt_copy(pkt, cn->dname, *sname);
254238106Sdes	cn->dname_len = *snamelen;
255238106Sdes	cn->type = LDNS_RR_TYPE_CNAME;
256238106Sdes	cn->section = rrset->section;
257238106Sdes	cn->rrset_class = rrset->rrset_class;
258238106Sdes	cn->rr_count = 1;
259238106Sdes	cn->size = sizeof(uint16_t) + aliaslen;
260238106Sdes	cn->hash=pkt_hash_rrset(pkt, cn->dname, cn->type, cn->rrset_class, 0);
261238106Sdes	/* allocate TTL + rdatalen + uncompressed dname */
262238106Sdes	memset(cn->rr_first, 0, sizeof(struct rr_parse));
263238106Sdes	cn->rr_first->outside_packet = 1;
264238106Sdes	cn->rr_first->ttl_data = (uint8_t*)regional_alloc(region,
265238106Sdes		sizeof(uint32_t)+sizeof(uint16_t)+aliaslen);
266238106Sdes	if(!cn->rr_first->ttl_data)
267238106Sdes		return NULL;
268269257Sdes	sldns_write_uint32(cn->rr_first->ttl_data, 0); /* TTL = 0 */
269269257Sdes	sldns_write_uint16(cn->rr_first->ttl_data+4, aliaslen);
270238106Sdes	memmove(cn->rr_first->ttl_data+6, alias, aliaslen);
271238106Sdes	cn->rr_first->size = sizeof(uint16_t)+aliaslen;
272238106Sdes
273238106Sdes	/* link it in */
274238106Sdes	cn->rrset_all_next = nx;
275238106Sdes	if(prev)
276238106Sdes		prev->rrset_all_next = cn;
277238106Sdes	else	msg->rrset_first = cn;
278238106Sdes	if(nx == NULL)
279238106Sdes		msg->rrset_last = cn;
280238106Sdes	msg->rrset_count ++;
281238106Sdes	msg->an_rrsets++;
282238106Sdes	/* it is not inserted in the msg hashtable. */
283238106Sdes
284238106Sdes	*sname = cn->rr_first->ttl_data + sizeof(uint32_t)+sizeof(uint16_t);
285238106Sdes	*snamelen = aliaslen;
286238106Sdes	return cn;
287238106Sdes}
288238106Sdes
289238106Sdes/** check if DNAME applies to a name */
290238106Sdesstatic int
291269257Sdespkt_strict_sub(sldns_buffer* pkt, uint8_t* sname, uint8_t* dr)
292238106Sdes{
293238106Sdes	uint8_t buf1[LDNS_MAX_DOMAINLEN+1];
294238106Sdes	uint8_t buf2[LDNS_MAX_DOMAINLEN+1];
295238106Sdes	/* decompress names */
296238106Sdes	dname_pkt_copy(pkt, buf1, sname);
297238106Sdes	dname_pkt_copy(pkt, buf2, dr);
298238106Sdes	return dname_strict_subdomain_c(buf1, buf2);
299238106Sdes}
300238106Sdes
301238106Sdes/** check subdomain with decompression */
302238106Sdesstatic int
303269257Sdespkt_sub(sldns_buffer* pkt, uint8_t* comprname, uint8_t* zone)
304238106Sdes{
305238106Sdes	uint8_t buf[LDNS_MAX_DOMAINLEN+1];
306238106Sdes	dname_pkt_copy(pkt, buf, comprname);
307238106Sdes	return dname_subdomain_c(buf, zone);
308238106Sdes}
309238106Sdes
310238106Sdes/** check subdomain with decompression, compressed is parent */
311238106Sdesstatic int
312269257Sdessub_of_pkt(sldns_buffer* pkt, uint8_t* zone, uint8_t* comprname)
313238106Sdes{
314238106Sdes	uint8_t buf[LDNS_MAX_DOMAINLEN+1];
315238106Sdes	dname_pkt_copy(pkt, buf, comprname);
316238106Sdes	return dname_subdomain_c(zone, buf);
317238106Sdes}
318238106Sdes
319238106Sdes/**
320238106Sdes * This routine normalizes a response. This includes removing "irrelevant"
321238106Sdes * records from the answer and additional sections and (re)synthesizing
322238106Sdes * CNAMEs from DNAMEs, if present.
323238106Sdes *
324238106Sdes * @param pkt: packet.
325238106Sdes * @param msg: msg to normalize.
326238106Sdes * @param qinfo: original query.
327238106Sdes * @param region: where to allocate synthesized CNAMEs.
328238106Sdes * @return 0 on error.
329238106Sdes */
330238106Sdesstatic int
331269257Sdesscrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
332238106Sdes	struct query_info* qinfo, struct regional* region)
333238106Sdes{
334238106Sdes	uint8_t* sname = qinfo->qname;
335238106Sdes	size_t snamelen = qinfo->qname_len;
336238106Sdes	struct rrset_parse* rrset, *prev, *nsset=NULL;
337238106Sdes
338238106Sdes	if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR &&
339238106Sdes		FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN)
340238106Sdes		return 1;
341238106Sdes
342238106Sdes	/* For the ANSWER section, remove all "irrelevant" records and add
343238106Sdes	 * synthesized CNAMEs from DNAMEs
344238106Sdes	 * This will strip out-of-order CNAMEs as well. */
345238106Sdes
346238106Sdes	/* walk through the parse packet rrset list, keep track of previous
347238106Sdes	 * for insert and delete ease, and examine every RRset */
348238106Sdes	prev = NULL;
349238106Sdes	rrset = msg->rrset_first;
350238106Sdes	while(rrset && rrset->section == LDNS_SECTION_ANSWER) {
351238106Sdes		if(rrset->type == LDNS_RR_TYPE_DNAME &&
352238106Sdes			pkt_strict_sub(pkt, sname, rrset->dname)) {
353238106Sdes			/* check if next rrset is correct CNAME. else,
354238106Sdes			 * synthesize a CNAME */
355238106Sdes			struct rrset_parse* nx = rrset->rrset_all_next;
356238106Sdes			uint8_t alias[LDNS_MAX_DOMAINLEN+1];
357238106Sdes			size_t aliaslen = 0;
358238106Sdes			if(rrset->rr_count != 1) {
359238106Sdes				verbose(VERB_ALGO, "Found DNAME rrset with "
360238106Sdes					"size > 1: %u",
361238106Sdes					(unsigned)rrset->rr_count);
362238106Sdes				return 0;
363238106Sdes			}
364238106Sdes			if(!synth_cname(sname, snamelen, rrset, alias,
365238106Sdes				&aliaslen, pkt)) {
366238106Sdes				verbose(VERB_ALGO, "synthesized CNAME "
367238106Sdes					"too long");
368238106Sdes				return 0;
369238106Sdes			}
370238106Sdes			if(nx && nx->type == LDNS_RR_TYPE_CNAME &&
371238106Sdes			   dname_pkt_compare(pkt, sname, nx->dname) == 0) {
372238106Sdes				/* check next cname */
373238106Sdes				uint8_t* t = NULL;
374238106Sdes				size_t tlen = 0;
375291767Sdes				if(!parse_get_cname_target(nx, &t, &tlen))
376238106Sdes					return 0;
377238106Sdes				if(dname_pkt_compare(pkt, alias, t) == 0) {
378238106Sdes					/* it's OK and better capitalized */
379238106Sdes					prev = rrset;
380238106Sdes					rrset = nx;
381238106Sdes					continue;
382238106Sdes				}
383238106Sdes				/* synth ourselves */
384238106Sdes			}
385238106Sdes			/* synth a CNAME rrset */
386238106Sdes			prev = synth_cname_rrset(&sname, &snamelen, alias,
387238106Sdes				aliaslen, region, msg, rrset, rrset, nx, pkt);
388238106Sdes			if(!prev) {
389238106Sdes				log_err("out of memory synthesizing CNAME");
390238106Sdes				return 0;
391238106Sdes			}
392238106Sdes			/* FIXME: resolve the conflict between synthesized
393238106Sdes			 * CNAME ttls and the cache. */
394238106Sdes			rrset = nx;
395238106Sdes			continue;
396238106Sdes
397238106Sdes		}
398238106Sdes
399238106Sdes		/* The only records in the ANSWER section not allowed to */
400238106Sdes		if(dname_pkt_compare(pkt, sname, rrset->dname) != 0) {
401238106Sdes			remove_rrset("normalize: removing irrelevant RRset:",
402238106Sdes				pkt, msg, prev, &rrset);
403238106Sdes			continue;
404238106Sdes		}
405238106Sdes
406238106Sdes		/* Follow the CNAME chain. */
407238106Sdes		if(rrset->type == LDNS_RR_TYPE_CNAME) {
408294190Sdes			struct rrset_parse* nx = rrset->rrset_all_next;
409238106Sdes			uint8_t* oldsname = sname;
410294190Sdes			/* see if the next one is a DNAME, if so, swap them */
411294190Sdes			if(nx && nx->section == LDNS_SECTION_ANSWER &&
412294190Sdes				nx->type == LDNS_RR_TYPE_DNAME &&
413294190Sdes				nx->rr_count == 1 &&
414294190Sdes				pkt_strict_sub(pkt, sname, nx->dname)) {
415294190Sdes				/* there is a DNAME after this CNAME, it
416294190Sdes				 * is in the ANSWER section, and the DNAME
417294190Sdes				 * applies to the name we cover */
418294190Sdes				/* check if the alias of the DNAME equals
419294190Sdes				 * this CNAME */
420294190Sdes				uint8_t alias[LDNS_MAX_DOMAINLEN+1];
421294190Sdes				size_t aliaslen = 0;
422294190Sdes				uint8_t* t = NULL;
423294190Sdes				size_t tlen = 0;
424294190Sdes				if(synth_cname(sname, snamelen, nx, alias,
425294190Sdes					&aliaslen, pkt) &&
426294190Sdes					parse_get_cname_target(rrset, &t, &tlen) &&
427294190Sdes			   		dname_pkt_compare(pkt, alias, t) == 0) {
428294190Sdes					/* the synthesized CNAME equals the
429294190Sdes					 * current CNAME.  This CNAME is the
430294190Sdes					 * one that the DNAME creates, and this
431294190Sdes					 * CNAME is better capitalised */
432294190Sdes					verbose(VERB_ALGO, "normalize: re-order of DNAME and its CNAME");
433294190Sdes					if(prev) prev->rrset_all_next = nx;
434294190Sdes					else msg->rrset_first = nx;
435294190Sdes					if(nx->rrset_all_next == NULL)
436294190Sdes						msg->rrset_last = rrset;
437294190Sdes					rrset->rrset_all_next =
438294190Sdes						nx->rrset_all_next;
439294190Sdes					nx->rrset_all_next = rrset;
440294190Sdes					prev = nx;
441294190Sdes				}
442294190Sdes			}
443294190Sdes
444294190Sdes			/* move to next name in CNAME chain */
445238106Sdes			if(!parse_get_cname_target(rrset, &sname, &snamelen))
446238106Sdes				return 0;
447238106Sdes			prev = rrset;
448238106Sdes			rrset = rrset->rrset_all_next;
449238106Sdes			/* in CNAME ANY response, can have data after CNAME */
450238106Sdes			if(qinfo->qtype == LDNS_RR_TYPE_ANY) {
451238106Sdes				while(rrset && rrset->section ==
452238106Sdes					LDNS_SECTION_ANSWER &&
453238106Sdes					dname_pkt_compare(pkt, oldsname,
454238106Sdes					rrset->dname) == 0) {
455238106Sdes					prev = rrset;
456238106Sdes					rrset = rrset->rrset_all_next;
457238106Sdes				}
458238106Sdes			}
459238106Sdes			continue;
460238106Sdes		}
461238106Sdes
462238106Sdes		/* Otherwise, make sure that the RRset matches the qtype. */
463238106Sdes		if(qinfo->qtype != LDNS_RR_TYPE_ANY &&
464238106Sdes			qinfo->qtype != rrset->type) {
465238106Sdes			remove_rrset("normalize: removing irrelevant RRset:",
466238106Sdes				pkt, msg, prev, &rrset);
467238106Sdes			continue;
468238106Sdes		}
469238106Sdes
470238106Sdes		/* Mark the additional names from relevant rrset as OK. */
471238106Sdes		/* only for RRsets that match the query name, other ones
472238106Sdes		 * will be removed by sanitize, so no additional for them */
473238106Sdes		if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0)
474238106Sdes			mark_additional_rrset(pkt, msg, rrset);
475238106Sdes
476238106Sdes		prev = rrset;
477238106Sdes		rrset = rrset->rrset_all_next;
478238106Sdes	}
479238106Sdes
480238106Sdes	/* Mark additional names from AUTHORITY */
481238106Sdes	while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) {
482238106Sdes		if(rrset->type==LDNS_RR_TYPE_DNAME ||
483238106Sdes			rrset->type==LDNS_RR_TYPE_CNAME ||
484238106Sdes			rrset->type==LDNS_RR_TYPE_A ||
485238106Sdes			rrset->type==LDNS_RR_TYPE_AAAA) {
486238106Sdes			remove_rrset("normalize: removing irrelevant "
487238106Sdes				"RRset:", pkt, msg, prev, &rrset);
488238106Sdes			continue;
489238106Sdes		}
490238106Sdes		/* only one NS set allowed in authority section */
491238106Sdes		if(rrset->type==LDNS_RR_TYPE_NS) {
492238106Sdes			/* NS set must be pertinent to the query */
493238106Sdes			if(!sub_of_pkt(pkt, qinfo->qname, rrset->dname)) {
494238106Sdes				remove_rrset("normalize: removing irrelevant "
495238106Sdes					"RRset:", pkt, msg, prev, &rrset);
496238106Sdes				continue;
497238106Sdes			}
498238106Sdes			if(nsset == NULL) {
499238106Sdes				nsset = rrset;
500238106Sdes			} else {
501238106Sdes				remove_rrset("normalize: removing irrelevant "
502238106Sdes					"RRset:", pkt, msg, prev, &rrset);
503238106Sdes				continue;
504238106Sdes			}
505238106Sdes		}
506238106Sdes		mark_additional_rrset(pkt, msg, rrset);
507238106Sdes		prev = rrset;
508238106Sdes		rrset = rrset->rrset_all_next;
509238106Sdes	}
510238106Sdes
511238106Sdes	/* For each record in the additional section, remove it if it is an
512238106Sdes	 * address record and not in the collection of additional names
513238106Sdes	 * found in ANSWER and AUTHORITY. */
514238106Sdes	/* These records have not been marked OK previously */
515238106Sdes	while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) {
516238106Sdes		/* FIXME: what about other types? */
517238106Sdes		if(rrset->type==LDNS_RR_TYPE_A ||
518238106Sdes			rrset->type==LDNS_RR_TYPE_AAAA)
519238106Sdes		{
520238106Sdes			if((rrset->flags & RRSET_SCRUB_OK)) {
521238106Sdes				/* remove flag to clean up flags variable */
522238106Sdes				rrset->flags &= ~RRSET_SCRUB_OK;
523238106Sdes			} else {
524238106Sdes				remove_rrset("normalize: removing irrelevant "
525238106Sdes					"RRset:", pkt, msg, prev, &rrset);
526238106Sdes				continue;
527238106Sdes			}
528238106Sdes		}
529238106Sdes		if(rrset->type==LDNS_RR_TYPE_DNAME ||
530238106Sdes			rrset->type==LDNS_RR_TYPE_CNAME ||
531238106Sdes			rrset->type==LDNS_RR_TYPE_NS) {
532238106Sdes			remove_rrset("normalize: removing irrelevant "
533238106Sdes				"RRset:", pkt, msg, prev, &rrset);
534238106Sdes			continue;
535238106Sdes		}
536238106Sdes		prev = rrset;
537238106Sdes		rrset = rrset->rrset_all_next;
538238106Sdes	}
539238106Sdes
540238106Sdes	return 1;
541238106Sdes}
542238106Sdes
543238106Sdes/**
544238106Sdes * Store potential poison in the cache (only if hardening disabled).
545238106Sdes * The rrset is stored in the cache but removed from the message.
546238106Sdes * So that it will be used for infrastructure purposes, but not be
547238106Sdes * returned to the client.
548238106Sdes * @param pkt: packet
549238106Sdes * @param msg: message parsed
550238106Sdes * @param env: environment with cache
551238106Sdes * @param rrset: to store.
552238106Sdes */
553238106Sdesstatic void
554269257Sdesstore_rrset(sldns_buffer* pkt, struct msg_parse* msg, struct module_env* env,
555238106Sdes	struct rrset_parse* rrset)
556238106Sdes{
557238106Sdes	struct ub_packed_rrset_key* k;
558238106Sdes	struct packed_rrset_data* d;
559238106Sdes	struct rrset_ref ref;
560269257Sdes	time_t now = *env->now;
561238106Sdes
562238106Sdes	k = alloc_special_obtain(env->alloc);
563238106Sdes	if(!k)
564238106Sdes		return;
565238106Sdes	k->entry.data = NULL;
566238106Sdes	if(!parse_copy_decompress_rrset(pkt, msg, rrset, NULL, k)) {
567238106Sdes		alloc_special_release(env->alloc, k);
568238106Sdes		return;
569238106Sdes	}
570238106Sdes	d = (struct packed_rrset_data*)k->entry.data;
571238106Sdes	packed_rrset_ttl_add(d, now);
572238106Sdes	ref.key = k;
573238106Sdes	ref.id = k->id;
574238106Sdes	/*ignore ret: it was in the cache, ref updated */
575238106Sdes	(void)rrset_cache_update(env->rrset_cache, &ref, env->alloc, now);
576238106Sdes}
577238106Sdes
578238106Sdes/** Check if there are SOA records in the authority section (negative) */
579238106Sdesstatic int
580238106Sdessoa_in_auth(struct msg_parse* msg)
581238106Sdes{
582238106Sdes	struct rrset_parse* rrset;
583238106Sdes	for(rrset = msg->rrset_first; rrset; rrset = rrset->rrset_all_next)
584238106Sdes		if(rrset->type == LDNS_RR_TYPE_SOA &&
585238106Sdes			rrset->section == LDNS_SECTION_AUTHORITY)
586238106Sdes			return 1;
587238106Sdes	return 0;
588238106Sdes}
589238106Sdes
590238106Sdes/**
591238106Sdes * Check if right hand name in NSEC is within zone
592238106Sdes * @param rrset: the NSEC rrset
593238106Sdes * @param zonename: the zone name.
594238106Sdes * @return true if BAD.
595238106Sdes */
596238106Sdesstatic int sanitize_nsec_is_overreach(struct rrset_parse* rrset,
597238106Sdes	uint8_t* zonename)
598238106Sdes{
599238106Sdes	struct rr_parse* rr;
600238106Sdes	uint8_t* rhs;
601238106Sdes	size_t len;
602238106Sdes	log_assert(rrset->type == LDNS_RR_TYPE_NSEC);
603238106Sdes	for(rr = rrset->rr_first; rr; rr = rr->next) {
604238106Sdes		rhs = rr->ttl_data+4+2;
605269257Sdes		len = sldns_read_uint16(rr->ttl_data+4);
606238106Sdes		if(!dname_valid(rhs, len)) {
607238106Sdes			/* malformed domain name in rdata */
608238106Sdes			return 1;
609238106Sdes		}
610238106Sdes		if(!dname_subdomain_c(rhs, zonename)) {
611238106Sdes			/* overreaching */
612238106Sdes			return 1;
613238106Sdes		}
614238106Sdes	}
615238106Sdes	/* all NSEC RRs OK */
616238106Sdes	return 0;
617238106Sdes}
618238106Sdes
619238106Sdes/**
620238106Sdes * Given a response event, remove suspect RRsets from the response.
621238106Sdes * "Suspect" rrsets are potentially poison. Note that this routine expects
622238106Sdes * the response to be in a "normalized" state -- that is, all "irrelevant"
623238106Sdes * RRsets have already been removed, CNAMEs are in order, etc.
624238106Sdes *
625238106Sdes * @param pkt: packet.
626238106Sdes * @param msg: msg to normalize.
627238106Sdes * @param qinfo: the question originally asked.
628238106Sdes * @param zonename: name of server zone.
629238106Sdes * @param env: module environment with config and cache.
630238106Sdes * @param ie: iterator environment with private address data.
631238106Sdes * @return 0 on error.
632238106Sdes */
633238106Sdesstatic int
634269257Sdesscrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg,
635238106Sdes	struct query_info* qinfo, uint8_t* zonename, struct module_env* env,
636238106Sdes	struct iter_env* ie)
637238106Sdes{
638238106Sdes	int del_addi = 0; /* if additional-holding rrsets are deleted, we
639238106Sdes		do not trust the normalized additional-A-AAAA any more */
640238106Sdes	struct rrset_parse* rrset, *prev;
641238106Sdes	prev = NULL;
642238106Sdes	rrset = msg->rrset_first;
643238106Sdes
644238106Sdes	/* the first DNAME is allowed to stay. It needs checking before
645238106Sdes	 * it can be used from the cache. After normalization, an initial
646238106Sdes	 * DNAME will have a correctly synthesized CNAME after it. */
647238106Sdes	if(rrset && rrset->type == LDNS_RR_TYPE_DNAME &&
648238106Sdes		rrset->section == LDNS_SECTION_ANSWER &&
649238106Sdes		pkt_strict_sub(pkt, qinfo->qname, rrset->dname) &&
650238106Sdes		pkt_sub(pkt, rrset->dname, zonename)) {
651238106Sdes		prev = rrset; /* DNAME allowed to stay in answer section */
652238106Sdes		rrset = rrset->rrset_all_next;
653238106Sdes	}
654238106Sdes
655238106Sdes	/* remove all records from the answer section that are
656238106Sdes	 * not the same domain name as the query domain name.
657238106Sdes	 * The answer section should contain rrsets with the same name
658238106Sdes	 * as the question. For DNAMEs a CNAME has been synthesized.
659238106Sdes	 * Wildcards have the query name in answer section.
660238106Sdes	 * ANY queries get query name in answer section.
661238106Sdes	 * Remainders of CNAME chains are cut off and resolved by iterator. */
662238106Sdes	while(rrset && rrset->section == LDNS_SECTION_ANSWER) {
663238106Sdes		if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) != 0) {
664238106Sdes			if(has_additional(rrset->type)) del_addi = 1;
665238106Sdes			remove_rrset("sanitize: removing extraneous answer "
666238106Sdes				"RRset:", pkt, msg, prev, &rrset);
667238106Sdes			continue;
668238106Sdes		}
669238106Sdes		prev = rrset;
670238106Sdes		rrset = rrset->rrset_all_next;
671238106Sdes	}
672238106Sdes
673238106Sdes	/* At this point, we brutally remove ALL rrsets that aren't
674238106Sdes	 * children of the originating zone. The idea here is that,
675238106Sdes	 * as far as we know, the server that we contacted is ONLY
676238106Sdes	 * authoritative for the originating zone. It, of course, MAY
677294190Sdes	 * be authoritative for any other zones, and of course, MAY
678238106Sdes	 * NOT be authoritative for some subdomains of the originating
679238106Sdes	 * zone. */
680238106Sdes	prev = NULL;
681238106Sdes	rrset = msg->rrset_first;
682238106Sdes	while(rrset) {
683238106Sdes
684238106Sdes		/* remove private addresses */
685238106Sdes		if( (rrset->type == LDNS_RR_TYPE_A ||
686269257Sdes			rrset->type == LDNS_RR_TYPE_AAAA)) {
687238106Sdes
688238106Sdes			/* do not set servfail since this leads to too
689238106Sdes			 * many drops of other people using rfc1918 space */
690269257Sdes			/* also do not remove entire rrset, unless all records
691269257Sdes			 * in it are bad */
692269257Sdes			if(priv_rrset_bad(ie->priv, pkt, rrset)) {
693269257Sdes				remove_rrset(NULL, pkt, msg, prev, &rrset);
694269257Sdes				continue;
695269257Sdes			}
696238106Sdes		}
697238106Sdes
698238106Sdes		/* skip DNAME records -- they will always be followed by a
699238106Sdes		 * synthesized CNAME, which will be relevant.
700238106Sdes		 * FIXME: should this do something differently with DNAME
701238106Sdes		 * rrsets NOT in Section.ANSWER? */
702238106Sdes		/* But since DNAME records are also subdomains of the zone,
703238106Sdes		 * same check can be used */
704238106Sdes
705238106Sdes		if(!pkt_sub(pkt, rrset->dname, zonename)) {
706238106Sdes			if(msg->an_rrsets == 0 &&
707238106Sdes				rrset->type == LDNS_RR_TYPE_NS &&
708238106Sdes				rrset->section == LDNS_SECTION_AUTHORITY &&
709238106Sdes				FLAGS_GET_RCODE(msg->flags) ==
710238106Sdes				LDNS_RCODE_NOERROR && !soa_in_auth(msg) &&
711238106Sdes				sub_of_pkt(pkt, zonename, rrset->dname)) {
712238106Sdes				/* noerror, nodata and this NS rrset is above
713238106Sdes				 * the zone. This is LAME!
714238106Sdes				 * Leave in the NS for lame classification. */
715238106Sdes				/* remove everything from the additional
716238106Sdes				 * (we dont want its glue that was approved
717238106Sdes				 * during the normalize action) */
718238106Sdes				del_addi = 1;
719285206Sdes			} else if(!env->cfg->harden_glue && (
720285206Sdes				rrset->type == LDNS_RR_TYPE_A ||
721285206Sdes				rrset->type == LDNS_RR_TYPE_AAAA)) {
722238106Sdes				/* store in cache! Since it is relevant
723238106Sdes				 * (from normalize) it will be picked up
724238106Sdes				 * from the cache to be used later */
725238106Sdes				store_rrset(pkt, msg, env, rrset);
726238106Sdes				remove_rrset("sanitize: storing potential "
727238106Sdes				"poison RRset:", pkt, msg, prev, &rrset);
728238106Sdes				continue;
729238106Sdes			} else {
730238106Sdes				if(has_additional(rrset->type)) del_addi = 1;
731238106Sdes				remove_rrset("sanitize: removing potential "
732238106Sdes				"poison RRset:", pkt, msg, prev, &rrset);
733238106Sdes				continue;
734238106Sdes			}
735238106Sdes		}
736238106Sdes		if(del_addi && rrset->section == LDNS_SECTION_ADDITIONAL) {
737238106Sdes			remove_rrset("sanitize: removing potential "
738238106Sdes			"poison reference RRset:", pkt, msg, prev, &rrset);
739238106Sdes			continue;
740238106Sdes		}
741238106Sdes		/* check if right hand side of NSEC is within zone */
742238106Sdes		if(rrset->type == LDNS_RR_TYPE_NSEC &&
743238106Sdes			sanitize_nsec_is_overreach(rrset, zonename)) {
744238106Sdes			remove_rrset("sanitize: removing overreaching NSEC "
745238106Sdes				"RRset:", pkt, msg, prev, &rrset);
746238106Sdes			continue;
747238106Sdes		}
748238106Sdes		prev = rrset;
749238106Sdes		rrset = rrset->rrset_all_next;
750238106Sdes	}
751238106Sdes	return 1;
752238106Sdes}
753238106Sdes
754238106Sdesint
755269257Sdesscrub_message(sldns_buffer* pkt, struct msg_parse* msg,
756238106Sdes	struct query_info* qinfo, uint8_t* zonename, struct regional* region,
757238106Sdes	struct module_env* env, struct iter_env* ie)
758238106Sdes{
759238106Sdes	/* basic sanity checks */
760238106Sdes	log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS,
761238106Sdes		qinfo->qclass);
762238106Sdes	if(msg->qdcount > 1)
763238106Sdes		return 0;
764238106Sdes	if( !(msg->flags&BIT_QR) )
765238106Sdes		return 0;
766238106Sdes	msg->flags &= ~(BIT_AD|BIT_Z); /* force off bit AD and Z */
767238106Sdes
768238106Sdes	/* make sure that a query is echoed back when NOERROR or NXDOMAIN */
769238106Sdes	/* this is not required for basic operation but is a forgery
770238106Sdes	 * resistance (security) feature */
771238106Sdes	if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR ||
772238106Sdes		FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN) &&
773238106Sdes		msg->qdcount == 0)
774238106Sdes		return 0;
775238106Sdes
776238106Sdes	/* if a query is echoed back, make sure it is correct. Otherwise,
777238106Sdes	 * this may be not a reply to our query. */
778238106Sdes	if(msg->qdcount == 1) {
779238106Sdes		if(dname_pkt_compare(pkt, msg->qname, qinfo->qname) != 0)
780238106Sdes			return 0;
781238106Sdes		if(msg->qtype != qinfo->qtype || msg->qclass != qinfo->qclass)
782238106Sdes			return 0;
783238106Sdes	}
784238106Sdes
785238106Sdes	/* normalize the response, this cleans up the additional.  */
786238106Sdes	if(!scrub_normalize(pkt, msg, qinfo, region))
787238106Sdes		return 0;
788238106Sdes	/* delete all out-of-zone information */
789238106Sdes	if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie))
790238106Sdes		return 0;
791238106Sdes	return 1;
792238106Sdes}
793