iter_scrub.c revision 269257
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 "ldns/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(rrset, &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			uint8_t* oldsname = sname;
409			if(!parse_get_cname_target(rrset, &sname, &snamelen))
410				return 0;
411			prev = rrset;
412			rrset = rrset->rrset_all_next;
413			/* in CNAME ANY response, can have data after CNAME */
414			if(qinfo->qtype == LDNS_RR_TYPE_ANY) {
415				while(rrset && rrset->section ==
416					LDNS_SECTION_ANSWER &&
417					dname_pkt_compare(pkt, oldsname,
418					rrset->dname) == 0) {
419					prev = rrset;
420					rrset = rrset->rrset_all_next;
421				}
422			}
423			continue;
424		}
425
426		/* Otherwise, make sure that the RRset matches the qtype. */
427		if(qinfo->qtype != LDNS_RR_TYPE_ANY &&
428			qinfo->qtype != rrset->type) {
429			remove_rrset("normalize: removing irrelevant RRset:",
430				pkt, msg, prev, &rrset);
431			continue;
432		}
433
434		/* Mark the additional names from relevant rrset as OK. */
435		/* only for RRsets that match the query name, other ones
436		 * will be removed by sanitize, so no additional for them */
437		if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0)
438			mark_additional_rrset(pkt, msg, rrset);
439
440		prev = rrset;
441		rrset = rrset->rrset_all_next;
442	}
443
444	/* Mark additional names from AUTHORITY */
445	while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) {
446		if(rrset->type==LDNS_RR_TYPE_DNAME ||
447			rrset->type==LDNS_RR_TYPE_CNAME ||
448			rrset->type==LDNS_RR_TYPE_A ||
449			rrset->type==LDNS_RR_TYPE_AAAA) {
450			remove_rrset("normalize: removing irrelevant "
451				"RRset:", pkt, msg, prev, &rrset);
452			continue;
453		}
454		/* only one NS set allowed in authority section */
455		if(rrset->type==LDNS_RR_TYPE_NS) {
456			/* NS set must be pertinent to the query */
457			if(!sub_of_pkt(pkt, qinfo->qname, rrset->dname)) {
458				remove_rrset("normalize: removing irrelevant "
459					"RRset:", pkt, msg, prev, &rrset);
460				continue;
461			}
462			if(nsset == NULL) {
463				nsset = rrset;
464			} else {
465				remove_rrset("normalize: removing irrelevant "
466					"RRset:", pkt, msg, prev, &rrset);
467				continue;
468			}
469		}
470		mark_additional_rrset(pkt, msg, rrset);
471		prev = rrset;
472		rrset = rrset->rrset_all_next;
473	}
474
475	/* For each record in the additional section, remove it if it is an
476	 * address record and not in the collection of additional names
477	 * found in ANSWER and AUTHORITY. */
478	/* These records have not been marked OK previously */
479	while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) {
480		/* FIXME: what about other types? */
481		if(rrset->type==LDNS_RR_TYPE_A ||
482			rrset->type==LDNS_RR_TYPE_AAAA)
483		{
484			if((rrset->flags & RRSET_SCRUB_OK)) {
485				/* remove flag to clean up flags variable */
486				rrset->flags &= ~RRSET_SCRUB_OK;
487			} else {
488				remove_rrset("normalize: removing irrelevant "
489					"RRset:", pkt, msg, prev, &rrset);
490				continue;
491			}
492		}
493		if(rrset->type==LDNS_RR_TYPE_DNAME ||
494			rrset->type==LDNS_RR_TYPE_CNAME ||
495			rrset->type==LDNS_RR_TYPE_NS) {
496			remove_rrset("normalize: removing irrelevant "
497				"RRset:", pkt, msg, prev, &rrset);
498			continue;
499		}
500		prev = rrset;
501		rrset = rrset->rrset_all_next;
502	}
503
504	return 1;
505}
506
507/**
508 * Store potential poison in the cache (only if hardening disabled).
509 * The rrset is stored in the cache but removed from the message.
510 * So that it will be used for infrastructure purposes, but not be
511 * returned to the client.
512 * @param pkt: packet
513 * @param msg: message parsed
514 * @param env: environment with cache
515 * @param rrset: to store.
516 */
517static void
518store_rrset(sldns_buffer* pkt, struct msg_parse* msg, struct module_env* env,
519	struct rrset_parse* rrset)
520{
521	struct ub_packed_rrset_key* k;
522	struct packed_rrset_data* d;
523	struct rrset_ref ref;
524	time_t now = *env->now;
525
526	k = alloc_special_obtain(env->alloc);
527	if(!k)
528		return;
529	k->entry.data = NULL;
530	if(!parse_copy_decompress_rrset(pkt, msg, rrset, NULL, k)) {
531		alloc_special_release(env->alloc, k);
532		return;
533	}
534	d = (struct packed_rrset_data*)k->entry.data;
535	packed_rrset_ttl_add(d, now);
536	ref.key = k;
537	ref.id = k->id;
538	/*ignore ret: it was in the cache, ref updated */
539	(void)rrset_cache_update(env->rrset_cache, &ref, env->alloc, now);
540}
541
542/** Check if there are SOA records in the authority section (negative) */
543static int
544soa_in_auth(struct msg_parse* msg)
545{
546	struct rrset_parse* rrset;
547	for(rrset = msg->rrset_first; rrset; rrset = rrset->rrset_all_next)
548		if(rrset->type == LDNS_RR_TYPE_SOA &&
549			rrset->section == LDNS_SECTION_AUTHORITY)
550			return 1;
551	return 0;
552}
553
554/**
555 * Check if right hand name in NSEC is within zone
556 * @param rrset: the NSEC rrset
557 * @param zonename: the zone name.
558 * @return true if BAD.
559 */
560static int sanitize_nsec_is_overreach(struct rrset_parse* rrset,
561	uint8_t* zonename)
562{
563	struct rr_parse* rr;
564	uint8_t* rhs;
565	size_t len;
566	log_assert(rrset->type == LDNS_RR_TYPE_NSEC);
567	for(rr = rrset->rr_first; rr; rr = rr->next) {
568		rhs = rr->ttl_data+4+2;
569		len = sldns_read_uint16(rr->ttl_data+4);
570		if(!dname_valid(rhs, len)) {
571			/* malformed domain name in rdata */
572			return 1;
573		}
574		if(!dname_subdomain_c(rhs, zonename)) {
575			/* overreaching */
576			return 1;
577		}
578	}
579	/* all NSEC RRs OK */
580	return 0;
581}
582
583/**
584 * Given a response event, remove suspect RRsets from the response.
585 * "Suspect" rrsets are potentially poison. Note that this routine expects
586 * the response to be in a "normalized" state -- that is, all "irrelevant"
587 * RRsets have already been removed, CNAMEs are in order, etc.
588 *
589 * @param pkt: packet.
590 * @param msg: msg to normalize.
591 * @param qinfo: the question originally asked.
592 * @param zonename: name of server zone.
593 * @param env: module environment with config and cache.
594 * @param ie: iterator environment with private address data.
595 * @return 0 on error.
596 */
597static int
598scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg,
599	struct query_info* qinfo, uint8_t* zonename, struct module_env* env,
600	struct iter_env* ie)
601{
602	int del_addi = 0; /* if additional-holding rrsets are deleted, we
603		do not trust the normalized additional-A-AAAA any more */
604	struct rrset_parse* rrset, *prev;
605	prev = NULL;
606	rrset = msg->rrset_first;
607
608	/* the first DNAME is allowed to stay. It needs checking before
609	 * it can be used from the cache. After normalization, an initial
610	 * DNAME will have a correctly synthesized CNAME after it. */
611	if(rrset && rrset->type == LDNS_RR_TYPE_DNAME &&
612		rrset->section == LDNS_SECTION_ANSWER &&
613		pkt_strict_sub(pkt, qinfo->qname, rrset->dname) &&
614		pkt_sub(pkt, rrset->dname, zonename)) {
615		prev = rrset; /* DNAME allowed to stay in answer section */
616		rrset = rrset->rrset_all_next;
617	}
618
619	/* remove all records from the answer section that are
620	 * not the same domain name as the query domain name.
621	 * The answer section should contain rrsets with the same name
622	 * as the question. For DNAMEs a CNAME has been synthesized.
623	 * Wildcards have the query name in answer section.
624	 * ANY queries get query name in answer section.
625	 * Remainders of CNAME chains are cut off and resolved by iterator. */
626	while(rrset && rrset->section == LDNS_SECTION_ANSWER) {
627		if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) != 0) {
628			if(has_additional(rrset->type)) del_addi = 1;
629			remove_rrset("sanitize: removing extraneous answer "
630				"RRset:", pkt, msg, prev, &rrset);
631			continue;
632		}
633		prev = rrset;
634		rrset = rrset->rrset_all_next;
635	}
636
637	/* At this point, we brutally remove ALL rrsets that aren't
638	 * children of the originating zone. The idea here is that,
639	 * as far as we know, the server that we contacted is ONLY
640	 * authoritative for the originating zone. It, of course, MAY
641	 * be authoriative for any other zones, and of course, MAY
642	 * NOT be authoritative for some subdomains of the originating
643	 * zone. */
644	prev = NULL;
645	rrset = msg->rrset_first;
646	while(rrset) {
647
648		/* remove private addresses */
649		if( (rrset->type == LDNS_RR_TYPE_A ||
650			rrset->type == LDNS_RR_TYPE_AAAA)) {
651
652			/* do not set servfail since this leads to too
653			 * many drops of other people using rfc1918 space */
654			/* also do not remove entire rrset, unless all records
655			 * in it are bad */
656			if(priv_rrset_bad(ie->priv, pkt, rrset)) {
657				remove_rrset(NULL, pkt, msg, prev, &rrset);
658				continue;
659			}
660		}
661
662		/* skip DNAME records -- they will always be followed by a
663		 * synthesized CNAME, which will be relevant.
664		 * FIXME: should this do something differently with DNAME
665		 * rrsets NOT in Section.ANSWER? */
666		/* But since DNAME records are also subdomains of the zone,
667		 * same check can be used */
668
669		if(!pkt_sub(pkt, rrset->dname, zonename)) {
670			if(msg->an_rrsets == 0 &&
671				rrset->type == LDNS_RR_TYPE_NS &&
672				rrset->section == LDNS_SECTION_AUTHORITY &&
673				FLAGS_GET_RCODE(msg->flags) ==
674				LDNS_RCODE_NOERROR && !soa_in_auth(msg) &&
675				sub_of_pkt(pkt, zonename, rrset->dname)) {
676				/* noerror, nodata and this NS rrset is above
677				 * the zone. This is LAME!
678				 * Leave in the NS for lame classification. */
679				/* remove everything from the additional
680				 * (we dont want its glue that was approved
681				 * during the normalize action) */
682				del_addi = 1;
683			} else if(!env->cfg->harden_glue) {
684				/* store in cache! Since it is relevant
685				 * (from normalize) it will be picked up
686				 * from the cache to be used later */
687				store_rrset(pkt, msg, env, rrset);
688				remove_rrset("sanitize: storing potential "
689				"poison RRset:", pkt, msg, prev, &rrset);
690				continue;
691			} else {
692				if(has_additional(rrset->type)) del_addi = 1;
693				remove_rrset("sanitize: removing potential "
694				"poison RRset:", pkt, msg, prev, &rrset);
695				continue;
696			}
697		}
698		if(del_addi && rrset->section == LDNS_SECTION_ADDITIONAL) {
699			remove_rrset("sanitize: removing potential "
700			"poison reference RRset:", pkt, msg, prev, &rrset);
701			continue;
702		}
703		/* check if right hand side of NSEC is within zone */
704		if(rrset->type == LDNS_RR_TYPE_NSEC &&
705			sanitize_nsec_is_overreach(rrset, zonename)) {
706			remove_rrset("sanitize: removing overreaching NSEC "
707				"RRset:", pkt, msg, prev, &rrset);
708			continue;
709		}
710		prev = rrset;
711		rrset = rrset->rrset_all_next;
712	}
713	return 1;
714}
715
716int
717scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
718	struct query_info* qinfo, uint8_t* zonename, struct regional* region,
719	struct module_env* env, struct iter_env* ie)
720{
721	/* basic sanity checks */
722	log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS,
723		qinfo->qclass);
724	if(msg->qdcount > 1)
725		return 0;
726	if( !(msg->flags&BIT_QR) )
727		return 0;
728	msg->flags &= ~(BIT_AD|BIT_Z); /* force off bit AD and Z */
729
730	/* make sure that a query is echoed back when NOERROR or NXDOMAIN */
731	/* this is not required for basic operation but is a forgery
732	 * resistance (security) feature */
733	if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR ||
734		FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN) &&
735		msg->qdcount == 0)
736		return 0;
737
738	/* if a query is echoed back, make sure it is correct. Otherwise,
739	 * this may be not a reply to our query. */
740	if(msg->qdcount == 1) {
741		if(dname_pkt_compare(pkt, msg->qname, qinfo->qname) != 0)
742			return 0;
743		if(msg->qtype != qinfo->qtype || msg->qclass != qinfo->qclass)
744			return 0;
745	}
746
747	/* normalize the response, this cleans up the additional.  */
748	if(!scrub_normalize(pkt, msg, qinfo, region))
749		return 0;
750	/* delete all out-of-zone information */
751	if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie))
752		return 0;
753	return 1;
754}
755