iter_scrub.c revision 294190
1/*
2 * iterator/iter_scrub.c - scrubbing, normalization, sanitization of DNS msgs.
3 *
4 * Copyright (c) 2007, NLnet Labs. All rights reserved.
5 *
6 * This software is open source.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 *
15 * Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 *
19 * Neither the name of the NLNET LABS nor the names of its contributors may
20 * be used to endorse or promote products derived from this software without
21 * specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36/**
37 * \file
38 *
39 * This file has routine(s) for cleaning up incoming DNS messages from
40 * possible useless or malicious junk in it.
41 */
42#include "config.h"
43#include "iterator/iter_scrub.h"
44#include "iterator/iterator.h"
45#include "iterator/iter_priv.h"
46#include "services/cache/rrset.h"
47#include "util/log.h"
48#include "util/net_help.h"
49#include "util/regional.h"
50#include "util/config_file.h"
51#include "util/module.h"
52#include "util/data/msgparse.h"
53#include "util/data/dname.h"
54#include "util/data/msgreply.h"
55#include "util/alloc.h"
56#include "sldns/sbuffer.h"
57
58/** RRset flag used during scrubbing. The RRset is OK. */
59#define RRSET_SCRUB_OK	0x80
60
61/** remove rrset, update loop variables */
62static void
63remove_rrset(const char* str, sldns_buffer* pkt, struct msg_parse* msg,
64	struct rrset_parse* prev, struct rrset_parse** rrset)
65{
66	if(verbosity >= VERB_QUERY && str
67		&& (*rrset)->dname_len <= LDNS_MAX_DOMAINLEN) {
68		uint8_t buf[LDNS_MAX_DOMAINLEN+1];
69		dname_pkt_copy(pkt, buf, (*rrset)->dname);
70		log_nametypeclass(VERB_QUERY, str, buf,
71			(*rrset)->type, ntohs((*rrset)->rrset_class));
72	}
73	if(prev)
74		prev->rrset_all_next = (*rrset)->rrset_all_next;
75	else	msg->rrset_first = (*rrset)->rrset_all_next;
76	if(msg->rrset_last == *rrset)
77		msg->rrset_last = prev;
78	msg->rrset_count --;
79	switch((*rrset)->section) {
80		case LDNS_SECTION_ANSWER: msg->an_rrsets--; break;
81		case LDNS_SECTION_AUTHORITY: msg->ns_rrsets--; break;
82		case LDNS_SECTION_ADDITIONAL: msg->ar_rrsets--; break;
83		default: log_assert(0);
84	}
85	msgparse_bucket_remove(msg, *rrset);
86	*rrset = (*rrset)->rrset_all_next;
87}
88
89/** return true if rr type has additional names in it */
90static int
91has_additional(uint16_t t)
92{
93	switch(t) {
94		case LDNS_RR_TYPE_MB:
95		case LDNS_RR_TYPE_MD:
96		case LDNS_RR_TYPE_MF:
97		case LDNS_RR_TYPE_NS:
98		case LDNS_RR_TYPE_MX:
99		case LDNS_RR_TYPE_KX:
100		case LDNS_RR_TYPE_SRV:
101			return 1;
102		case LDNS_RR_TYPE_NAPTR:
103			/* TODO: NAPTR not supported, glue stripped off */
104			return 0;
105	}
106	return 0;
107}
108
109/** get additional name from rrset RR, return false if no name present */
110static int
111get_additional_name(struct rrset_parse* rrset, struct rr_parse* rr,
112	uint8_t** nm, size_t* nmlen, sldns_buffer* pkt)
113{
114	size_t offset = 0;
115	size_t len, oldpos;
116	switch(rrset->type) {
117		case LDNS_RR_TYPE_MB:
118		case LDNS_RR_TYPE_MD:
119		case LDNS_RR_TYPE_MF:
120		case LDNS_RR_TYPE_NS:
121			offset = 0;
122			break;
123		case LDNS_RR_TYPE_MX:
124		case LDNS_RR_TYPE_KX:
125			offset = 2;
126			break;
127		case LDNS_RR_TYPE_SRV:
128			offset = 6;
129			break;
130		case LDNS_RR_TYPE_NAPTR:
131			/* TODO: NAPTR not supported, glue stripped off */
132			return 0;
133		default:
134			return 0;
135	}
136	len = sldns_read_uint16(rr->ttl_data+sizeof(uint32_t));
137	if(len < offset+1)
138		return 0; /* rdata field too small */
139	*nm = rr->ttl_data+sizeof(uint32_t)+sizeof(uint16_t)+offset;
140	oldpos = sldns_buffer_position(pkt);
141	sldns_buffer_set_position(pkt, (size_t)(*nm - sldns_buffer_begin(pkt)));
142	*nmlen = pkt_dname_len(pkt);
143	sldns_buffer_set_position(pkt, oldpos);
144	if(*nmlen == 0)
145		return 0;
146	return 1;
147}
148
149/** Place mark on rrsets in additional section they are OK */
150static void
151mark_additional_rrset(sldns_buffer* pkt, struct msg_parse* msg,
152	struct rrset_parse* rrset)
153{
154	/* Mark A and AAAA for NS as appropriate additional section info. */
155	uint8_t* nm = NULL;
156	size_t nmlen = 0;
157	struct rr_parse* rr;
158
159	if(!has_additional(rrset->type))
160		return;
161	for(rr = rrset->rr_first; rr; rr = rr->next) {
162		if(get_additional_name(rrset, rr, &nm, &nmlen, pkt)) {
163			/* mark A */
164			hashvalue_t h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_A,
165				rrset->rrset_class, 0);
166			struct rrset_parse* r = msgparse_hashtable_lookup(
167				msg, pkt, h, 0, nm, nmlen,
168				LDNS_RR_TYPE_A, rrset->rrset_class);
169			if(r && r->section == LDNS_SECTION_ADDITIONAL) {
170				r->flags |= RRSET_SCRUB_OK;
171			}
172
173			/* mark AAAA */
174			h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_AAAA,
175				rrset->rrset_class, 0);
176			r = msgparse_hashtable_lookup(msg, pkt, h, 0, nm,
177				nmlen, LDNS_RR_TYPE_AAAA, rrset->rrset_class);
178			if(r && r->section == LDNS_SECTION_ADDITIONAL) {
179				r->flags |= RRSET_SCRUB_OK;
180			}
181		}
182	}
183}
184
185/** Get target name of a CNAME */
186static int
187parse_get_cname_target(struct rrset_parse* rrset, uint8_t** sname,
188	size_t* snamelen)
189{
190	if(rrset->rr_count != 1) {
191		struct rr_parse* sig;
192		verbose(VERB_ALGO, "Found CNAME rrset with "
193			"size > 1: %u", (unsigned)rrset->rr_count);
194		/* use the first CNAME! */
195		rrset->rr_count = 1;
196		rrset->size = rrset->rr_first->size;
197		for(sig=rrset->rrsig_first; sig; sig=sig->next)
198			rrset->size += sig->size;
199		rrset->rr_last = rrset->rr_first;
200		rrset->rr_first->next = NULL;
201	}
202	if(rrset->rr_first->size < sizeof(uint16_t)+1)
203		return 0; /* CNAME rdata too small */
204	*sname = rrset->rr_first->ttl_data + sizeof(uint32_t)
205		+ sizeof(uint16_t); /* skip ttl, rdatalen */
206	*snamelen = rrset->rr_first->size - sizeof(uint16_t);
207	return 1;
208}
209
210/** Synthesize CNAME from DNAME, false if too long */
211static int
212synth_cname(uint8_t* qname, size_t qnamelen, struct rrset_parse* dname_rrset,
213	uint8_t* alias, size_t* aliaslen, sldns_buffer* pkt)
214{
215	/* we already know that sname is a strict subdomain of DNAME owner */
216	uint8_t* dtarg = NULL;
217	size_t dtarglen;
218	if(!parse_get_cname_target(dname_rrset, &dtarg, &dtarglen))
219		return 0;
220	log_assert(qnamelen > dname_rrset->dname_len);
221	/* DNAME from com. to net. with qname example.com. -> example.net. */
222	/* so: \3com\0 to \3net\0 and qname \7example\3com\0 */
223	*aliaslen = qnamelen + dtarglen - dname_rrset->dname_len;
224	if(*aliaslen > LDNS_MAX_DOMAINLEN)
225		return 0; /* should have been RCODE YXDOMAIN */
226	/* decompress dnames into buffer, we know it fits */
227	dname_pkt_copy(pkt, alias, qname);
228	dname_pkt_copy(pkt, alias+(qnamelen-dname_rrset->dname_len), dtarg);
229	return 1;
230}
231
232/** synthesize a CNAME rrset */
233static struct rrset_parse*
234synth_cname_rrset(uint8_t** sname, size_t* snamelen, uint8_t* alias,
235	size_t aliaslen, struct regional* region, struct msg_parse* msg,
236	struct rrset_parse* rrset, struct rrset_parse* prev,
237	struct rrset_parse* nx, sldns_buffer* pkt)
238{
239	struct rrset_parse* cn = (struct rrset_parse*)regional_alloc(region,
240		sizeof(struct rrset_parse));
241	if(!cn)
242		return NULL;
243	memset(cn, 0, sizeof(*cn));
244	cn->rr_first = (struct rr_parse*)regional_alloc(region,
245		sizeof(struct rr_parse));
246	if(!cn->rr_first)
247		return NULL;
248	cn->rr_last = cn->rr_first;
249	/* CNAME from sname to alias */
250	cn->dname = (uint8_t*)regional_alloc(region, *snamelen);
251	if(!cn->dname)
252		return NULL;
253	dname_pkt_copy(pkt, cn->dname, *sname);
254	cn->dname_len = *snamelen;
255	cn->type = LDNS_RR_TYPE_CNAME;
256	cn->section = rrset->section;
257	cn->rrset_class = rrset->rrset_class;
258	cn->rr_count = 1;
259	cn->size = sizeof(uint16_t) + aliaslen;
260	cn->hash=pkt_hash_rrset(pkt, cn->dname, cn->type, cn->rrset_class, 0);
261	/* allocate TTL + rdatalen + uncompressed dname */
262	memset(cn->rr_first, 0, sizeof(struct rr_parse));
263	cn->rr_first->outside_packet = 1;
264	cn->rr_first->ttl_data = (uint8_t*)regional_alloc(region,
265		sizeof(uint32_t)+sizeof(uint16_t)+aliaslen);
266	if(!cn->rr_first->ttl_data)
267		return NULL;
268	sldns_write_uint32(cn->rr_first->ttl_data, 0); /* TTL = 0 */
269	sldns_write_uint16(cn->rr_first->ttl_data+4, aliaslen);
270	memmove(cn->rr_first->ttl_data+6, alias, aliaslen);
271	cn->rr_first->size = sizeof(uint16_t)+aliaslen;
272
273	/* link it in */
274	cn->rrset_all_next = nx;
275	if(prev)
276		prev->rrset_all_next = cn;
277	else	msg->rrset_first = cn;
278	if(nx == NULL)
279		msg->rrset_last = cn;
280	msg->rrset_count ++;
281	msg->an_rrsets++;
282	/* it is not inserted in the msg hashtable. */
283
284	*sname = cn->rr_first->ttl_data + sizeof(uint32_t)+sizeof(uint16_t);
285	*snamelen = aliaslen;
286	return cn;
287}
288
289/** check if DNAME applies to a name */
290static int
291pkt_strict_sub(sldns_buffer* pkt, uint8_t* sname, uint8_t* dr)
292{
293	uint8_t buf1[LDNS_MAX_DOMAINLEN+1];
294	uint8_t buf2[LDNS_MAX_DOMAINLEN+1];
295	/* decompress names */
296	dname_pkt_copy(pkt, buf1, sname);
297	dname_pkt_copy(pkt, buf2, dr);
298	return dname_strict_subdomain_c(buf1, buf2);
299}
300
301/** check subdomain with decompression */
302static int
303pkt_sub(sldns_buffer* pkt, uint8_t* comprname, uint8_t* zone)
304{
305	uint8_t buf[LDNS_MAX_DOMAINLEN+1];
306	dname_pkt_copy(pkt, buf, comprname);
307	return dname_subdomain_c(buf, zone);
308}
309
310/** check subdomain with decompression, compressed is parent */
311static int
312sub_of_pkt(sldns_buffer* pkt, uint8_t* zone, uint8_t* comprname)
313{
314	uint8_t buf[LDNS_MAX_DOMAINLEN+1];
315	dname_pkt_copy(pkt, buf, comprname);
316	return dname_subdomain_c(zone, buf);
317}
318
319/**
320 * This routine normalizes a response. This includes removing "irrelevant"
321 * records from the answer and additional sections and (re)synthesizing
322 * CNAMEs from DNAMEs, if present.
323 *
324 * @param pkt: packet.
325 * @param msg: msg to normalize.
326 * @param qinfo: original query.
327 * @param region: where to allocate synthesized CNAMEs.
328 * @return 0 on error.
329 */
330static int
331scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
332	struct query_info* qinfo, struct regional* region)
333{
334	uint8_t* sname = qinfo->qname;
335	size_t snamelen = qinfo->qname_len;
336	struct rrset_parse* rrset, *prev, *nsset=NULL;
337
338	if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR &&
339		FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN)
340		return 1;
341
342	/* For the ANSWER section, remove all "irrelevant" records and add
343	 * synthesized CNAMEs from DNAMEs
344	 * This will strip out-of-order CNAMEs as well. */
345
346	/* walk through the parse packet rrset list, keep track of previous
347	 * for insert and delete ease, and examine every RRset */
348	prev = NULL;
349	rrset = msg->rrset_first;
350	while(rrset && rrset->section == LDNS_SECTION_ANSWER) {
351		if(rrset->type == LDNS_RR_TYPE_DNAME &&
352			pkt_strict_sub(pkt, sname, rrset->dname)) {
353			/* check if next rrset is correct CNAME. else,
354			 * synthesize a CNAME */
355			struct rrset_parse* nx = rrset->rrset_all_next;
356			uint8_t alias[LDNS_MAX_DOMAINLEN+1];
357			size_t aliaslen = 0;
358			if(rrset->rr_count != 1) {
359				verbose(VERB_ALGO, "Found DNAME rrset with "
360					"size > 1: %u",
361					(unsigned)rrset->rr_count);
362				return 0;
363			}
364			if(!synth_cname(sname, snamelen, rrset, alias,
365				&aliaslen, pkt)) {
366				verbose(VERB_ALGO, "synthesized CNAME "
367					"too long");
368				return 0;
369			}
370			if(nx && nx->type == LDNS_RR_TYPE_CNAME &&
371			   dname_pkt_compare(pkt, sname, nx->dname) == 0) {
372				/* check next cname */
373				uint8_t* t = NULL;
374				size_t tlen = 0;
375				if(!parse_get_cname_target(nx, &t, &tlen))
376					return 0;
377				if(dname_pkt_compare(pkt, alias, t) == 0) {
378					/* it's OK and better capitalized */
379					prev = rrset;
380					rrset = nx;
381					continue;
382				}
383				/* synth ourselves */
384			}
385			/* synth a CNAME rrset */
386			prev = synth_cname_rrset(&sname, &snamelen, alias,
387				aliaslen, region, msg, rrset, rrset, nx, pkt);
388			if(!prev) {
389				log_err("out of memory synthesizing CNAME");
390				return 0;
391			}
392			/* FIXME: resolve the conflict between synthesized
393			 * CNAME ttls and the cache. */
394			rrset = nx;
395			continue;
396
397		}
398
399		/* The only records in the ANSWER section not allowed to */
400		if(dname_pkt_compare(pkt, sname, rrset->dname) != 0) {
401			remove_rrset("normalize: removing irrelevant RRset:",
402				pkt, msg, prev, &rrset);
403			continue;
404		}
405
406		/* Follow the CNAME chain. */
407		if(rrset->type == LDNS_RR_TYPE_CNAME) {
408			struct rrset_parse* nx = rrset->rrset_all_next;
409			uint8_t* oldsname = sname;
410			/* see if the next one is a DNAME, if so, swap them */
411			if(nx && nx->section == LDNS_SECTION_ANSWER &&
412				nx->type == LDNS_RR_TYPE_DNAME &&
413				nx->rr_count == 1 &&
414				pkt_strict_sub(pkt, sname, nx->dname)) {
415				/* there is a DNAME after this CNAME, it
416				 * is in the ANSWER section, and the DNAME
417				 * applies to the name we cover */
418				/* check if the alias of the DNAME equals
419				 * this CNAME */
420				uint8_t alias[LDNS_MAX_DOMAINLEN+1];
421				size_t aliaslen = 0;
422				uint8_t* t = NULL;
423				size_t tlen = 0;
424				if(synth_cname(sname, snamelen, nx, alias,
425					&aliaslen, pkt) &&
426					parse_get_cname_target(rrset, &t, &tlen) &&
427			   		dname_pkt_compare(pkt, alias, t) == 0) {
428					/* the synthesized CNAME equals the
429					 * current CNAME.  This CNAME is the
430					 * one that the DNAME creates, and this
431					 * CNAME is better capitalised */
432					verbose(VERB_ALGO, "normalize: re-order of DNAME and its CNAME");
433					if(prev) prev->rrset_all_next = nx;
434					else msg->rrset_first = nx;
435					if(nx->rrset_all_next == NULL)
436						msg->rrset_last = rrset;
437					rrset->rrset_all_next =
438						nx->rrset_all_next;
439					nx->rrset_all_next = rrset;
440					prev = nx;
441				}
442			}
443
444			/* move to next name in CNAME chain */
445			if(!parse_get_cname_target(rrset, &sname, &snamelen))
446				return 0;
447			prev = rrset;
448			rrset = rrset->rrset_all_next;
449			/* in CNAME ANY response, can have data after CNAME */
450			if(qinfo->qtype == LDNS_RR_TYPE_ANY) {
451				while(rrset && rrset->section ==
452					LDNS_SECTION_ANSWER &&
453					dname_pkt_compare(pkt, oldsname,
454					rrset->dname) == 0) {
455					prev = rrset;
456					rrset = rrset->rrset_all_next;
457				}
458			}
459			continue;
460		}
461
462		/* Otherwise, make sure that the RRset matches the qtype. */
463		if(qinfo->qtype != LDNS_RR_TYPE_ANY &&
464			qinfo->qtype != rrset->type) {
465			remove_rrset("normalize: removing irrelevant RRset:",
466				pkt, msg, prev, &rrset);
467			continue;
468		}
469
470		/* Mark the additional names from relevant rrset as OK. */
471		/* only for RRsets that match the query name, other ones
472		 * will be removed by sanitize, so no additional for them */
473		if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0)
474			mark_additional_rrset(pkt, msg, rrset);
475
476		prev = rrset;
477		rrset = rrset->rrset_all_next;
478	}
479
480	/* Mark additional names from AUTHORITY */
481	while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) {
482		if(rrset->type==LDNS_RR_TYPE_DNAME ||
483			rrset->type==LDNS_RR_TYPE_CNAME ||
484			rrset->type==LDNS_RR_TYPE_A ||
485			rrset->type==LDNS_RR_TYPE_AAAA) {
486			remove_rrset("normalize: removing irrelevant "
487				"RRset:", pkt, msg, prev, &rrset);
488			continue;
489		}
490		/* only one NS set allowed in authority section */
491		if(rrset->type==LDNS_RR_TYPE_NS) {
492			/* NS set must be pertinent to the query */
493			if(!sub_of_pkt(pkt, qinfo->qname, rrset->dname)) {
494				remove_rrset("normalize: removing irrelevant "
495					"RRset:", pkt, msg, prev, &rrset);
496				continue;
497			}
498			if(nsset == NULL) {
499				nsset = rrset;
500			} else {
501				remove_rrset("normalize: removing irrelevant "
502					"RRset:", pkt, msg, prev, &rrset);
503				continue;
504			}
505		}
506		mark_additional_rrset(pkt, msg, rrset);
507		prev = rrset;
508		rrset = rrset->rrset_all_next;
509	}
510
511	/* For each record in the additional section, remove it if it is an
512	 * address record and not in the collection of additional names
513	 * found in ANSWER and AUTHORITY. */
514	/* These records have not been marked OK previously */
515	while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) {
516		/* FIXME: what about other types? */
517		if(rrset->type==LDNS_RR_TYPE_A ||
518			rrset->type==LDNS_RR_TYPE_AAAA)
519		{
520			if((rrset->flags & RRSET_SCRUB_OK)) {
521				/* remove flag to clean up flags variable */
522				rrset->flags &= ~RRSET_SCRUB_OK;
523			} else {
524				remove_rrset("normalize: removing irrelevant "
525					"RRset:", pkt, msg, prev, &rrset);
526				continue;
527			}
528		}
529		if(rrset->type==LDNS_RR_TYPE_DNAME ||
530			rrset->type==LDNS_RR_TYPE_CNAME ||
531			rrset->type==LDNS_RR_TYPE_NS) {
532			remove_rrset("normalize: removing irrelevant "
533				"RRset:", pkt, msg, prev, &rrset);
534			continue;
535		}
536		prev = rrset;
537		rrset = rrset->rrset_all_next;
538	}
539
540	return 1;
541}
542
543/**
544 * Store potential poison in the cache (only if hardening disabled).
545 * The rrset is stored in the cache but removed from the message.
546 * So that it will be used for infrastructure purposes, but not be
547 * returned to the client.
548 * @param pkt: packet
549 * @param msg: message parsed
550 * @param env: environment with cache
551 * @param rrset: to store.
552 */
553static void
554store_rrset(sldns_buffer* pkt, struct msg_parse* msg, struct module_env* env,
555	struct rrset_parse* rrset)
556{
557	struct ub_packed_rrset_key* k;
558	struct packed_rrset_data* d;
559	struct rrset_ref ref;
560	time_t now = *env->now;
561
562	k = alloc_special_obtain(env->alloc);
563	if(!k)
564		return;
565	k->entry.data = NULL;
566	if(!parse_copy_decompress_rrset(pkt, msg, rrset, NULL, k)) {
567		alloc_special_release(env->alloc, k);
568		return;
569	}
570	d = (struct packed_rrset_data*)k->entry.data;
571	packed_rrset_ttl_add(d, now);
572	ref.key = k;
573	ref.id = k->id;
574	/*ignore ret: it was in the cache, ref updated */
575	(void)rrset_cache_update(env->rrset_cache, &ref, env->alloc, now);
576}
577
578/** Check if there are SOA records in the authority section (negative) */
579static int
580soa_in_auth(struct msg_parse* msg)
581{
582	struct rrset_parse* rrset;
583	for(rrset = msg->rrset_first; rrset; rrset = rrset->rrset_all_next)
584		if(rrset->type == LDNS_RR_TYPE_SOA &&
585			rrset->section == LDNS_SECTION_AUTHORITY)
586			return 1;
587	return 0;
588}
589
590/**
591 * Check if right hand name in NSEC is within zone
592 * @param rrset: the NSEC rrset
593 * @param zonename: the zone name.
594 * @return true if BAD.
595 */
596static int sanitize_nsec_is_overreach(struct rrset_parse* rrset,
597	uint8_t* zonename)
598{
599	struct rr_parse* rr;
600	uint8_t* rhs;
601	size_t len;
602	log_assert(rrset->type == LDNS_RR_TYPE_NSEC);
603	for(rr = rrset->rr_first; rr; rr = rr->next) {
604		rhs = rr->ttl_data+4+2;
605		len = sldns_read_uint16(rr->ttl_data+4);
606		if(!dname_valid(rhs, len)) {
607			/* malformed domain name in rdata */
608			return 1;
609		}
610		if(!dname_subdomain_c(rhs, zonename)) {
611			/* overreaching */
612			return 1;
613		}
614	}
615	/* all NSEC RRs OK */
616	return 0;
617}
618
619/**
620 * Given a response event, remove suspect RRsets from the response.
621 * "Suspect" rrsets are potentially poison. Note that this routine expects
622 * the response to be in a "normalized" state -- that is, all "irrelevant"
623 * RRsets have already been removed, CNAMEs are in order, etc.
624 *
625 * @param pkt: packet.
626 * @param msg: msg to normalize.
627 * @param qinfo: the question originally asked.
628 * @param zonename: name of server zone.
629 * @param env: module environment with config and cache.
630 * @param ie: iterator environment with private address data.
631 * @return 0 on error.
632 */
633static int
634scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg,
635	struct query_info* qinfo, uint8_t* zonename, struct module_env* env,
636	struct iter_env* ie)
637{
638	int del_addi = 0; /* if additional-holding rrsets are deleted, we
639		do not trust the normalized additional-A-AAAA any more */
640	struct rrset_parse* rrset, *prev;
641	prev = NULL;
642	rrset = msg->rrset_first;
643
644	/* the first DNAME is allowed to stay. It needs checking before
645	 * it can be used from the cache. After normalization, an initial
646	 * DNAME will have a correctly synthesized CNAME after it. */
647	if(rrset && rrset->type == LDNS_RR_TYPE_DNAME &&
648		rrset->section == LDNS_SECTION_ANSWER &&
649		pkt_strict_sub(pkt, qinfo->qname, rrset->dname) &&
650		pkt_sub(pkt, rrset->dname, zonename)) {
651		prev = rrset; /* DNAME allowed to stay in answer section */
652		rrset = rrset->rrset_all_next;
653	}
654
655	/* remove all records from the answer section that are
656	 * not the same domain name as the query domain name.
657	 * The answer section should contain rrsets with the same name
658	 * as the question. For DNAMEs a CNAME has been synthesized.
659	 * Wildcards have the query name in answer section.
660	 * ANY queries get query name in answer section.
661	 * Remainders of CNAME chains are cut off and resolved by iterator. */
662	while(rrset && rrset->section == LDNS_SECTION_ANSWER) {
663		if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) != 0) {
664			if(has_additional(rrset->type)) del_addi = 1;
665			remove_rrset("sanitize: removing extraneous answer "
666				"RRset:", pkt, msg, prev, &rrset);
667			continue;
668		}
669		prev = rrset;
670		rrset = rrset->rrset_all_next;
671	}
672
673	/* At this point, we brutally remove ALL rrsets that aren't
674	 * children of the originating zone. The idea here is that,
675	 * as far as we know, the server that we contacted is ONLY
676	 * authoritative for the originating zone. It, of course, MAY
677	 * be authoritative for any other zones, and of course, MAY
678	 * NOT be authoritative for some subdomains of the originating
679	 * zone. */
680	prev = NULL;
681	rrset = msg->rrset_first;
682	while(rrset) {
683
684		/* remove private addresses */
685		if( (rrset->type == LDNS_RR_TYPE_A ||
686			rrset->type == LDNS_RR_TYPE_AAAA)) {
687
688			/* do not set servfail since this leads to too
689			 * many drops of other people using rfc1918 space */
690			/* also do not remove entire rrset, unless all records
691			 * in it are bad */
692			if(priv_rrset_bad(ie->priv, pkt, rrset)) {
693				remove_rrset(NULL, pkt, msg, prev, &rrset);
694				continue;
695			}
696		}
697
698		/* skip DNAME records -- they will always be followed by a
699		 * synthesized CNAME, which will be relevant.
700		 * FIXME: should this do something differently with DNAME
701		 * rrsets NOT in Section.ANSWER? */
702		/* But since DNAME records are also subdomains of the zone,
703		 * same check can be used */
704
705		if(!pkt_sub(pkt, rrset->dname, zonename)) {
706			if(msg->an_rrsets == 0 &&
707				rrset->type == LDNS_RR_TYPE_NS &&
708				rrset->section == LDNS_SECTION_AUTHORITY &&
709				FLAGS_GET_RCODE(msg->flags) ==
710				LDNS_RCODE_NOERROR && !soa_in_auth(msg) &&
711				sub_of_pkt(pkt, zonename, rrset->dname)) {
712				/* noerror, nodata and this NS rrset is above
713				 * the zone. This is LAME!
714				 * Leave in the NS for lame classification. */
715				/* remove everything from the additional
716				 * (we dont want its glue that was approved
717				 * during the normalize action) */
718				del_addi = 1;
719			} else if(!env->cfg->harden_glue && (
720				rrset->type == LDNS_RR_TYPE_A ||
721				rrset->type == LDNS_RR_TYPE_AAAA)) {
722				/* store in cache! Since it is relevant
723				 * (from normalize) it will be picked up
724				 * from the cache to be used later */
725				store_rrset(pkt, msg, env, rrset);
726				remove_rrset("sanitize: storing potential "
727				"poison RRset:", pkt, msg, prev, &rrset);
728				continue;
729			} else {
730				if(has_additional(rrset->type)) del_addi = 1;
731				remove_rrset("sanitize: removing potential "
732				"poison RRset:", pkt, msg, prev, &rrset);
733				continue;
734			}
735		}
736		if(del_addi && rrset->section == LDNS_SECTION_ADDITIONAL) {
737			remove_rrset("sanitize: removing potential "
738			"poison reference RRset:", pkt, msg, prev, &rrset);
739			continue;
740		}
741		/* check if right hand side of NSEC is within zone */
742		if(rrset->type == LDNS_RR_TYPE_NSEC &&
743			sanitize_nsec_is_overreach(rrset, zonename)) {
744			remove_rrset("sanitize: removing overreaching NSEC "
745				"RRset:", pkt, msg, prev, &rrset);
746			continue;
747		}
748		prev = rrset;
749		rrset = rrset->rrset_all_next;
750	}
751	return 1;
752}
753
754int
755scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
756	struct query_info* qinfo, uint8_t* zonename, struct regional* region,
757	struct module_env* env, struct iter_env* ie)
758{
759	/* basic sanity checks */
760	log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS,
761		qinfo->qclass);
762	if(msg->qdcount > 1)
763		return 0;
764	if( !(msg->flags&BIT_QR) )
765		return 0;
766	msg->flags &= ~(BIT_AD|BIT_Z); /* force off bit AD and Z */
767
768	/* make sure that a query is echoed back when NOERROR or NXDOMAIN */
769	/* this is not required for basic operation but is a forgery
770	 * resistance (security) feature */
771	if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR ||
772		FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN) &&
773		msg->qdcount == 0)
774		return 0;
775
776	/* if a query is echoed back, make sure it is correct. Otherwise,
777	 * this may be not a reply to our query. */
778	if(msg->qdcount == 1) {
779		if(dname_pkt_compare(pkt, msg->qname, qinfo->qname) != 0)
780			return 0;
781		if(msg->qtype != qinfo->qtype || msg->qclass != qinfo->qclass)
782			return 0;
783	}
784
785	/* normalize the response, this cleans up the additional.  */
786	if(!scrub_normalize(pkt, msg, qinfo, region))
787		return 0;
788	/* delete all out-of-zone information */
789	if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie))
790		return 0;
791	return 1;
792}
793