1 /*
2  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7  *
8  * See the COPYRIGHT file distributed with this work for additional
9  * information regarding copyright ownership.
10  */
11 
12 /*! \file */
13 
14 #include <inttypes.h>
15 #include <stdbool.h>
16 #include <string.h>
17 
18 #include <isc/buffer.h>
19 #include <isc/hash.h>
20 #include <isc/ht.h>
21 #include <isc/lib.h>
22 #include <isc/log.h>
23 #include <isc/mem.h>
24 #include <isc/netaddr.h>
25 #include <isc/result.h>
26 #include <isc/types.h>
27 #include <isc/util.h>
28 
29 #include <dns/acl.h>
30 #include <dns/db.h>
31 #include <dns/enumtype.h>
32 #include <dns/log.h>
33 #include <dns/message.h>
34 #include <dns/rdataset.h>
35 #include <dns/result.h>
36 #include <dns/types.h>
37 #include <dns/view.h>
38 
39 #include <isccfg/aclconf.h>
40 #include <isccfg/cfg.h>
41 #include <isccfg/grammar.h>
42 
43 #include <ns/client.h>
44 #include <ns/hooks.h>
45 #include <ns/log.h>
46 #include <ns/query.h>
47 #include <ns/types.h>
48 
49 #define CHECK(op)                              \
50 	do {                                   \
51 		result = (op);                 \
52 		if (result != ISC_R_SUCCESS) { \
53 			goto cleanup;          \
54 		}                              \
55 	} while (0)
56 
57 /*
58  * Possible values for the settings of filter-aaaa-on-v4 and
59  * filter-aaaa-on-v6: "no" is NONE, "yes" is FILTER, "break-dnssec"
60  * is BREAK_DNSSEC.
61  */
62 typedef enum { NONE = 0, FILTER = 1, BREAK_DNSSEC = 2 } filter_aaaa_t;
63 
64 /*
65  * Persistent data for use by this module. This will be associated
66  * with client object address in the hash table, and will remain
67  * accessible until the client object is detached.
68  */
69 typedef struct filter_data {
70 	filter_aaaa_t mode;
71 	uint32_t flags;
72 } filter_data_t;
73 
74 typedef struct filter_instance {
75 	ns_plugin_t *module;
76 	isc_mem_t *mctx;
77 
78 	/*
79 	 * Memory pool for use with persistent data.
80 	 */
81 	isc_mempool_t *datapool;
82 	isc_mutex_t plock;
83 
84 	/*
85 	 * Hash table associating a client object with its persistent data.
86 	 */
87 	isc_ht_t *ht;
88 	isc_mutex_t hlock;
89 
90 	/*
91 	 * Values configured when the module is loaded.
92 	 */
93 	filter_aaaa_t v4_aaaa;
94 	filter_aaaa_t v6_aaaa;
95 	dns_acl_t *aaaa_acl;
96 } filter_instance_t;
97 
98 /*
99  * Per-client flags set by this module
100  */
101 #define FILTER_AAAA_RECURSING 0x0001 /* Recursing for A */
102 #define FILTER_AAAA_FILTERED  0x0002 /* AAAA was removed from answer */
103 
104 /*
105  * Client attribute tests.
106  */
107 #define WANTDNSSEC(c)  (((c)->attributes & NS_CLIENTATTR_WANTDNSSEC) != 0)
108 #define RECURSIONOK(c) (((c)->query.attributes & NS_QUERYATTR_RECURSIONOK) != 0)
109 
110 /*
111  * Forward declarations of functions referenced in install_hooks().
112  */
113 static ns_hookresult_t
114 filter_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp);
115 static ns_hookresult_t
116 filter_respond_begin(void *arg, void *cbdata, isc_result_t *resp);
117 static ns_hookresult_t
118 filter_respond_any_found(void *arg, void *cbdata, isc_result_t *resp);
119 static ns_hookresult_t
120 filter_prep_response_begin(void *arg, void *cbdata, isc_result_t *resp);
121 static ns_hookresult_t
122 filter_query_done_send(void *arg, void *cbdata, isc_result_t *resp);
123 static ns_hookresult_t
124 filter_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp);
125 
126 /*%
127  * Register the functions to be called at each hook point in 'hooktable', using
128  * memory context 'mctx' for allocating copies of stack-allocated structures
129  * passed to ns_hook_add().  Make sure 'inst' will be passed as the 'cbdata'
130  * argument to every callback.
131  */
132 static void
install_hooks(ns_hooktable_t * hooktable,isc_mem_t * mctx,filter_instance_t * inst)133 install_hooks(ns_hooktable_t *hooktable, isc_mem_t *mctx,
134 	      filter_instance_t *inst) {
135 	const ns_hook_t filter_init = {
136 		.action = filter_qctx_initialize,
137 		.action_data = inst,
138 	};
139 
140 	const ns_hook_t filter_respbegin = {
141 		.action = filter_respond_begin,
142 		.action_data = inst,
143 	};
144 
145 	const ns_hook_t filter_respanyfound = {
146 		.action = filter_respond_any_found,
147 		.action_data = inst,
148 	};
149 
150 	const ns_hook_t filter_prepresp = {
151 		.action = filter_prep_response_begin,
152 		.action_data = inst,
153 	};
154 
155 	const ns_hook_t filter_donesend = {
156 		.action = filter_query_done_send,
157 		.action_data = inst,
158 	};
159 
160 	const ns_hook_t filter_destroy = {
161 		.action = filter_qctx_destroy,
162 		.action_data = inst,
163 	};
164 
165 	ns_hook_add(hooktable, mctx, -NS_QUERY_QCTX_INITIALIZED, &filter_init);
166 	ns_hook_add(hooktable, mctx, NS_QUERY_RESPOND_BEGIN, &filter_respbegin);
167 	ns_hook_add(hooktable, mctx, NS_QUERY_RESPOND_ANY_FOUND,
168 		    &filter_respanyfound);
169 	ns_hook_add(hooktable, mctx, NS_QUERY_PREP_RESPONSE_BEGIN,
170 		    &filter_prepresp);
171 	ns_hook_add(hooktable, mctx, NS_QUERY_DONE_SEND, &filter_donesend);
172 	ns_hook_add(hooktable, mctx, NS_QUERY_QCTX_DESTROYED, &filter_destroy);
173 }
174 
175 /**
176 ** Support for parsing of parameters and configuration of the module.
177 **/
178 
179 /*
180  * Support for parsing of parameters.
181  */
182 static const char *filter_aaaa_enums[] = { "break-dnssec", NULL };
183 
184 static isc_result_t
parse_filter_aaaa(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)185 parse_filter_aaaa(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
186 	return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
187 }
188 
189 static void
doc_filter_aaaa(cfg_printer_t * pctx,const cfg_type_t * type)190 doc_filter_aaaa(cfg_printer_t *pctx, const cfg_type_t *type) {
191 	cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
192 }
193 
194 static cfg_type_t cfg_type_filter_aaaa = {
195 	"filter_aaaa",	 parse_filter_aaaa, cfg_print_ustring,
196 	doc_filter_aaaa, &cfg_rep_string,   filter_aaaa_enums,
197 };
198 
199 static cfg_clausedef_t param_clauses[] = {
200 	{ "filter-aaaa", &cfg_type_bracketed_aml, 0 },
201 	{ "filter-aaaa-on-v4", &cfg_type_filter_aaaa, 0 },
202 	{ "filter-aaaa-on-v6", &cfg_type_filter_aaaa, 0 },
203 };
204 
205 static cfg_clausedef_t *param_clausesets[] = { param_clauses, NULL };
206 
207 static cfg_type_t cfg_type_parameters = {
208 	"filter-aaaa-params", cfg_parse_mapbody, cfg_print_mapbody,
209 	cfg_doc_mapbody,      &cfg_rep_map,	 param_clausesets
210 };
211 
212 static isc_result_t
parse_filter_aaaa_on(const cfg_obj_t * param_obj,const char * param_name,filter_aaaa_t * dstp)213 parse_filter_aaaa_on(const cfg_obj_t *param_obj, const char *param_name,
214 		     filter_aaaa_t *dstp) {
215 	const cfg_obj_t *obj = NULL;
216 	isc_result_t result;
217 
218 	result = cfg_map_get(param_obj, param_name, &obj);
219 	if (result != ISC_R_SUCCESS) {
220 		return (ISC_R_SUCCESS);
221 	}
222 
223 	if (cfg_obj_isboolean(obj)) {
224 		if (cfg_obj_asboolean(obj)) {
225 			*dstp = FILTER;
226 		} else {
227 			*dstp = NONE;
228 		}
229 	} else if (strcasecmp(cfg_obj_asstring(obj), "break-dnssec") == 0) {
230 		*dstp = BREAK_DNSSEC;
231 	} else {
232 		result = ISC_R_UNEXPECTED;
233 	}
234 
235 	return (result);
236 }
237 
238 static isc_result_t
check_syntax(cfg_obj_t * fmap,const void * cfg,isc_mem_t * mctx,isc_log_t * lctx,void * actx)239 check_syntax(cfg_obj_t *fmap, const void *cfg, isc_mem_t *mctx, isc_log_t *lctx,
240 	     void *actx) {
241 	isc_result_t result = ISC_R_SUCCESS;
242 	const cfg_obj_t *aclobj = NULL;
243 	dns_acl_t *acl = NULL;
244 	filter_aaaa_t f4 = NONE, f6 = NONE;
245 
246 	cfg_map_get(fmap, "filter-aaaa", &aclobj);
247 	if (aclobj == NULL) {
248 		return (result);
249 	}
250 
251 	CHECK(cfg_acl_fromconfig(aclobj, (const cfg_obj_t *)cfg, lctx,
252 				 (cfg_aclconfctx_t *)actx, mctx, 0, &acl));
253 
254 	CHECK(parse_filter_aaaa_on(fmap, "filter-aaaa-on-v4", &f4));
255 	CHECK(parse_filter_aaaa_on(fmap, "filter-aaaa-on-v6", &f6));
256 
257 	if ((f4 != NONE || f6 != NONE) && dns_acl_isnone(acl)) {
258 		cfg_obj_log(aclobj, lctx, ISC_LOG_WARNING,
259 			    "\"filter-aaaa\" is 'none;' but "
260 			    "either filter-aaaa-on-v4 or filter-aaaa-on-v6 "
261 			    "is enabled");
262 		result = ISC_R_FAILURE;
263 	} else if (f4 == NONE && f6 == NONE && !dns_acl_isnone(acl)) {
264 		cfg_obj_log(aclobj, lctx, ISC_LOG_WARNING,
265 			    "\"filter-aaaa\" is set but "
266 			    "neither filter-aaaa-on-v4 or filter-aaaa-on-v6 "
267 			    "is enabled");
268 		result = ISC_R_FAILURE;
269 	}
270 
271 cleanup:
272 	if (acl != NULL) {
273 		dns_acl_detach(&acl);
274 	}
275 
276 	return (result);
277 }
278 
279 static isc_result_t
parse_parameters(filter_instance_t * inst,const char * parameters,const void * cfg,const char * cfg_file,unsigned long cfg_line,isc_mem_t * mctx,isc_log_t * lctx,void * actx)280 parse_parameters(filter_instance_t *inst, const char *parameters,
281 		 const void *cfg, const char *cfg_file, unsigned long cfg_line,
282 		 isc_mem_t *mctx, isc_log_t *lctx, void *actx) {
283 	isc_result_t result = ISC_R_SUCCESS;
284 	cfg_parser_t *parser = NULL;
285 	cfg_obj_t *param_obj = NULL;
286 	const cfg_obj_t *obj = NULL;
287 	isc_buffer_t b;
288 
289 	CHECK(cfg_parser_create(mctx, lctx, &parser));
290 
291 	isc_buffer_constinit(&b, parameters, strlen(parameters));
292 	isc_buffer_add(&b, strlen(parameters));
293 	CHECK(cfg_parse_buffer(parser, &b, cfg_file, cfg_line,
294 			       &cfg_type_parameters, 0, &param_obj));
295 
296 	CHECK(check_syntax(param_obj, cfg, mctx, lctx, actx));
297 
298 	CHECK(parse_filter_aaaa_on(param_obj, "filter-aaaa-on-v4",
299 				   &inst->v4_aaaa));
300 	CHECK(parse_filter_aaaa_on(param_obj, "filter-aaaa-on-v6",
301 				   &inst->v6_aaaa));
302 
303 	result = cfg_map_get(param_obj, "filter-aaaa", &obj);
304 	if (result == ISC_R_SUCCESS) {
305 		CHECK(cfg_acl_fromconfig(obj, (const cfg_obj_t *)cfg, lctx,
306 					 (cfg_aclconfctx_t *)actx, mctx, 0,
307 					 &inst->aaaa_acl));
308 	} else {
309 		CHECK(dns_acl_any(mctx, &inst->aaaa_acl));
310 	}
311 
312 cleanup:
313 	if (param_obj != NULL) {
314 		cfg_obj_destroy(parser, &param_obj);
315 	}
316 	if (parser != NULL) {
317 		cfg_parser_destroy(&parser);
318 	}
319 	return (result);
320 }
321 
322 /**
323 ** Mandatory plugin API functions:
324 **
325 ** - plugin_destroy
326 ** - plugin_register
327 ** - plugin_version
328 ** - plugin_check
329 **/
330 
331 /*
332  * Called by ns_plugin_register() to initialize the plugin and
333  * register hook functions into the view hook table.
334  */
335 isc_result_t
plugin_register(const char * parameters,const void * cfg,const char * cfg_file,unsigned long cfg_line,isc_mem_t * mctx,isc_log_t * lctx,void * actx,ns_hooktable_t * hooktable,void ** instp)336 plugin_register(const char *parameters, const void *cfg, const char *cfg_file,
337 		unsigned long cfg_line, isc_mem_t *mctx, isc_log_t *lctx,
338 		void *actx, ns_hooktable_t *hooktable, void **instp) {
339 	filter_instance_t *inst = NULL;
340 	isc_result_t result;
341 
342 	isc_log_write(lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
343 		      ISC_LOG_INFO,
344 		      "registering 'filter-aaaa' "
345 		      "module from %s:%lu, %s parameters",
346 		      cfg_file, cfg_line, parameters != NULL ? "with" : "no");
347 
348 	inst = isc_mem_get(mctx, sizeof(*inst));
349 	memset(inst, 0, sizeof(*inst));
350 	isc_mem_attach(mctx, &inst->mctx);
351 
352 	if (parameters != NULL) {
353 		CHECK(parse_parameters(inst, parameters, cfg, cfg_file,
354 				       cfg_line, mctx, lctx, actx));
355 	}
356 
357 	isc_mempool_create(mctx, sizeof(filter_data_t), &inst->datapool);
358 	CHECK(isc_ht_init(&inst->ht, mctx, 16));
359 	isc_mutex_init(&inst->hlock);
360 
361 	/*
362 	 * Fill the mempool with 1K filter_aaaa state objects at
363 	 * a time; ideally after a single allocation, the mempool will
364 	 * have enough to handle all the simultaneous queries the system
365 	 * requires and it won't be necessary to allocate more.
366 	 *
367 	 * We don't set any limit on the number of free state objects
368 	 * so that they'll always be returned to the pool and not
369 	 * freed until the pool is destroyed on shutdown.
370 	 */
371 	isc_mempool_setfillcount(inst->datapool, 1024);
372 	isc_mempool_setfreemax(inst->datapool, UINT_MAX);
373 	isc_mutex_init(&inst->plock);
374 	isc_mempool_associatelock(inst->datapool, &inst->plock);
375 
376 	/*
377 	 * Set hook points in the view's hooktable.
378 	 */
379 	install_hooks(hooktable, mctx, inst);
380 
381 	*instp = inst;
382 
383 cleanup:
384 	if (result != ISC_R_SUCCESS && inst != NULL) {
385 		plugin_destroy((void **)&inst);
386 	}
387 
388 	return (result);
389 }
390 
391 isc_result_t
plugin_check(const char * parameters,const void * cfg,const char * cfg_file,unsigned long cfg_line,isc_mem_t * mctx,isc_log_t * lctx,void * actx)392 plugin_check(const char *parameters, const void *cfg, const char *cfg_file,
393 	     unsigned long cfg_line, isc_mem_t *mctx, isc_log_t *lctx,
394 	     void *actx) {
395 	isc_result_t result = ISC_R_SUCCESS;
396 	cfg_parser_t *parser = NULL;
397 	cfg_obj_t *param_obj = NULL;
398 	isc_buffer_t b;
399 
400 	CHECK(cfg_parser_create(mctx, lctx, &parser));
401 
402 	isc_buffer_constinit(&b, parameters, strlen(parameters));
403 	isc_buffer_add(&b, strlen(parameters));
404 	CHECK(cfg_parse_buffer(parser, &b, cfg_file, cfg_line,
405 			       &cfg_type_parameters, 0, &param_obj));
406 
407 	CHECK(check_syntax(param_obj, cfg, mctx, lctx, actx));
408 
409 cleanup:
410 	if (param_obj != NULL) {
411 		cfg_obj_destroy(parser, &param_obj);
412 	}
413 	if (parser != NULL) {
414 		cfg_parser_destroy(&parser);
415 	}
416 	return (result);
417 }
418 
419 /*
420  * Called by ns_plugins_free(); frees memory allocated by
421  * the module when it was registered.
422  */
423 void
plugin_destroy(void ** instp)424 plugin_destroy(void **instp) {
425 	filter_instance_t *inst = (filter_instance_t *)*instp;
426 
427 	if (inst->ht != NULL) {
428 		isc_ht_destroy(&inst->ht);
429 		isc_mutex_destroy(&inst->hlock);
430 	}
431 	if (inst->datapool != NULL) {
432 		isc_mempool_destroy(&inst->datapool);
433 		isc_mutex_destroy(&inst->plock);
434 	}
435 	if (inst->aaaa_acl != NULL) {
436 		dns_acl_detach(&inst->aaaa_acl);
437 	}
438 
439 	isc_mem_putanddetach(&inst->mctx, inst, sizeof(*inst));
440 	*instp = NULL;
441 
442 	return;
443 }
444 
445 /*
446  * Returns plugin API version for compatibility checks.
447  */
448 int
plugin_version(void)449 plugin_version(void) {
450 	return (NS_PLUGIN_VERSION);
451 }
452 
453 /**
454 ** "filter-aaaa" feature implementation begins here.
455 **/
456 
457 /*%
458  * Structure describing the filtering to be applied by process_section().
459  */
460 typedef struct section_filter {
461 	query_ctx_t *qctx;
462 	filter_aaaa_t mode;
463 	dns_section_t section;
464 	const dns_name_t *name;
465 	dns_rdatatype_t type;
466 	bool only_if_a_exists;
467 } section_filter_t;
468 
469 /*
470  * Check whether this is an IPv4 client.
471  */
472 static bool
is_v4_client(ns_client_t * client)473 is_v4_client(ns_client_t *client) {
474 	if (isc_sockaddr_pf(&client->peeraddr) == AF_INET) {
475 		return (true);
476 	}
477 	if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 &&
478 	    IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr))
479 	{
480 		return (true);
481 	}
482 	return (false);
483 }
484 
485 /*
486  * Check whether this is an IPv6 client.
487  */
488 static bool
is_v6_client(ns_client_t * client)489 is_v6_client(ns_client_t *client) {
490 	if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 &&
491 	    !IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr))
492 	{
493 		return (true);
494 	}
495 	return (false);
496 }
497 
498 static filter_data_t *
client_state_get(const query_ctx_t * qctx,filter_instance_t * inst)499 client_state_get(const query_ctx_t *qctx, filter_instance_t *inst) {
500 	filter_data_t *client_state = NULL;
501 	isc_result_t result;
502 
503 	LOCK(&inst->hlock);
504 	result = isc_ht_find(inst->ht, (const unsigned char *)&qctx->client,
505 			     sizeof(qctx->client), (void **)&client_state);
506 	UNLOCK(&inst->hlock);
507 
508 	return (result == ISC_R_SUCCESS ? client_state : NULL);
509 }
510 
511 static void
client_state_create(const query_ctx_t * qctx,filter_instance_t * inst)512 client_state_create(const query_ctx_t *qctx, filter_instance_t *inst) {
513 	filter_data_t *client_state;
514 	isc_result_t result;
515 
516 	client_state = isc_mempool_get(inst->datapool);
517 	if (client_state == NULL) {
518 		return;
519 	}
520 
521 	client_state->mode = NONE;
522 	client_state->flags = 0;
523 
524 	LOCK(&inst->hlock);
525 	result = isc_ht_add(inst->ht, (const unsigned char *)&qctx->client,
526 			    sizeof(qctx->client), client_state);
527 	UNLOCK(&inst->hlock);
528 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
529 }
530 
531 static void
client_state_destroy(const query_ctx_t * qctx,filter_instance_t * inst)532 client_state_destroy(const query_ctx_t *qctx, filter_instance_t *inst) {
533 	filter_data_t *client_state = client_state_get(qctx, inst);
534 	isc_result_t result;
535 
536 	if (client_state == NULL) {
537 		return;
538 	}
539 
540 	LOCK(&inst->hlock);
541 	result = isc_ht_delete(inst->ht, (const unsigned char *)&qctx->client,
542 			       sizeof(qctx->client));
543 	UNLOCK(&inst->hlock);
544 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
545 
546 	isc_mempool_put(inst->datapool, client_state);
547 }
548 
549 /*%
550  * Mark 'rdataset' and 'sigrdataset' as rendered, gracefully handling NULL
551  * pointers and non-associated rdatasets.
552  */
553 static void
mark_as_rendered(dns_rdataset_t * rdataset,dns_rdataset_t * sigrdataset)554 mark_as_rendered(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
555 	if (rdataset != NULL && dns_rdataset_isassociated(rdataset)) {
556 		rdataset->attributes |= DNS_RDATASETATTR_RENDERED;
557 	}
558 	if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) {
559 		sigrdataset->attributes |= DNS_RDATASETATTR_RENDERED;
560 	}
561 }
562 
563 /*%
564  * Check whether an RRset of given 'type' is present at given 'name'.  If
565  * it is found and either it is not signed or the combination of query
566  * flags and configured processing 'mode' allows it, mark the RRset and its
567  * associated signatures as already rendered to prevent them from appearing
568  * in the response message stored in 'qctx'.  If 'only_if_a_exists' is
569  * true, an RRset of type A must also exist at 'name' in order for the
570  * above processing to happen.
571  */
572 static bool
process_name(query_ctx_t * qctx,filter_aaaa_t mode,const dns_name_t * name,dns_rdatatype_t type,bool only_if_a_exists)573 process_name(query_ctx_t *qctx, filter_aaaa_t mode, const dns_name_t *name,
574 	     dns_rdatatype_t type, bool only_if_a_exists) {
575 	dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
576 	isc_result_t result;
577 	bool modified = false;
578 
579 	if (only_if_a_exists) {
580 		CHECK(dns_message_findtype(name, dns_rdatatype_a, 0, NULL));
581 	}
582 
583 	(void)dns_message_findtype(name, type, 0, &rdataset);
584 	(void)dns_message_findtype(name, dns_rdatatype_rrsig, type,
585 				   &sigrdataset);
586 
587 	if (rdataset != NULL &&
588 	    (sigrdataset == NULL || !WANTDNSSEC(qctx->client) ||
589 	     mode == BREAK_DNSSEC))
590 	{
591 		/*
592 		 * An RRset of given 'type' was found at 'name' and at least
593 		 * one of the following is true:
594 		 *
595 		 *   - the RRset is not signed,
596 		 *   - the client did not set the DO bit in its request,
597 		 *   - configuration allows us to tamper with signed responses.
598 		 *
599 		 * This means it is okay to filter out this RRset and its
600 		 * signatures, if any, from the response.
601 		 */
602 		mark_as_rendered(rdataset, sigrdataset);
603 		modified = true;
604 	}
605 
606 cleanup:
607 	return (modified);
608 }
609 
610 /*%
611  * Apply the requested section filter, i.e. prevent (when possible, as
612  * determined by process_name()) RRsets of given 'type' from being rendered
613  * in the given 'section' of the response message stored in 'qctx'.  Clear
614  * the AD bit if the answer and/or authority section was modified.  If
615  * 'name' is NULL, all names in the given 'section' are processed;
616  * otherwise, only 'name' is.  'only_if_a_exists' is passed through to
617  * process_name().
618  */
619 static void
process_section(const section_filter_t * filter)620 process_section(const section_filter_t *filter) {
621 	query_ctx_t *qctx = filter->qctx;
622 	filter_aaaa_t mode = filter->mode;
623 	dns_section_t section = filter->section;
624 	const dns_name_t *name = filter->name;
625 	dns_rdatatype_t type = filter->type;
626 	bool only_if_a_exists = filter->only_if_a_exists;
627 
628 	dns_message_t *message = qctx->client->message;
629 	isc_result_t result;
630 
631 	for (result = dns_message_firstname(message, section);
632 	     result == ISC_R_SUCCESS;
633 	     result = dns_message_nextname(message, section))
634 	{
635 		dns_name_t *cur = NULL;
636 		dns_message_currentname(message, section, &cur);
637 		if (name != NULL && !dns_name_equal(name, cur)) {
638 			/*
639 			 * We only want to process 'name' and this is not it.
640 			 */
641 			continue;
642 		}
643 
644 		if (!process_name(qctx, mode, cur, type, only_if_a_exists)) {
645 			/*
646 			 * Response was not modified, do not touch the AD bit.
647 			 */
648 			continue;
649 		}
650 
651 		if (section == DNS_SECTION_ANSWER ||
652 		    section == DNS_SECTION_AUTHORITY) {
653 			message->flags &= ~DNS_MESSAGEFLAG_AD;
654 		}
655 	}
656 }
657 
658 /*
659  * Initialize filter state, fetching it from a memory pool and storing it
660  * in a hash table keyed according to the client object; this enables us to
661  * retrieve persistent data related to a client query for as long as the
662  * object persists.
663  */
664 static ns_hookresult_t
filter_qctx_initialize(void * arg,void * cbdata,isc_result_t * resp)665 filter_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp) {
666 	query_ctx_t *qctx = (query_ctx_t *)arg;
667 	filter_instance_t *inst = (filter_instance_t *)cbdata;
668 	filter_data_t *client_state;
669 
670 	*resp = ISC_R_UNSET;
671 
672 	client_state = client_state_get(qctx, inst);
673 	if (client_state == NULL) {
674 		client_state_create(qctx, inst);
675 	}
676 
677 	return (NS_HOOK_CONTINUE);
678 }
679 
680 /*
681  * Determine whether this client should have AAAA filtered or not, based on
682  * the client address family and the settings of filter-aaaa-on-v4 and
683  * filter-aaaa-on-v6.
684  */
685 static ns_hookresult_t
filter_prep_response_begin(void * arg,void * cbdata,isc_result_t * resp)686 filter_prep_response_begin(void *arg, void *cbdata, isc_result_t *resp) {
687 	query_ctx_t *qctx = (query_ctx_t *)arg;
688 	filter_instance_t *inst = (filter_instance_t *)cbdata;
689 	filter_data_t *client_state = client_state_get(qctx, inst);
690 	isc_result_t result;
691 
692 	*resp = ISC_R_UNSET;
693 
694 	if (client_state == NULL) {
695 		return (NS_HOOK_CONTINUE);
696 	}
697 
698 	if (inst->v4_aaaa != NONE || inst->v6_aaaa != NONE) {
699 		result = ns_client_checkaclsilent(qctx->client, NULL,
700 						  inst->aaaa_acl, true);
701 		if (result == ISC_R_SUCCESS && inst->v4_aaaa != NONE &&
702 		    is_v4_client(qctx->client))
703 		{
704 			client_state->mode = inst->v4_aaaa;
705 		} else if (result == ISC_R_SUCCESS && inst->v6_aaaa != NONE &&
706 			   is_v6_client(qctx->client))
707 		{
708 			client_state->mode = inst->v6_aaaa;
709 		}
710 	}
711 
712 	return (NS_HOOK_CONTINUE);
713 }
714 
715 /*
716  * Hide AAAA rrsets if there is a matching A. Trigger recursion if
717  * necessary to find out whether an A exists.
718  *
719  * (This version is for processing answers to explicit AAAA queries; ANY
720  * queries are handled in filter_respond_any_found().)
721  */
722 static ns_hookresult_t
filter_respond_begin(void * arg,void * cbdata,isc_result_t * resp)723 filter_respond_begin(void *arg, void *cbdata, isc_result_t *resp) {
724 	query_ctx_t *qctx = (query_ctx_t *)arg;
725 	filter_instance_t *inst = (filter_instance_t *)cbdata;
726 	filter_data_t *client_state = client_state_get(qctx, inst);
727 	isc_result_t result = ISC_R_UNSET;
728 
729 	*resp = ISC_R_UNSET;
730 
731 	if (client_state == NULL) {
732 		return (NS_HOOK_CONTINUE);
733 	}
734 
735 	if (client_state->mode != BREAK_DNSSEC &&
736 	    (client_state->mode != FILTER ||
737 	     (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL &&
738 	      dns_rdataset_isassociated(qctx->sigrdataset))))
739 	{
740 		return (NS_HOOK_CONTINUE);
741 	}
742 
743 	if (qctx->qtype == dns_rdatatype_aaaa) {
744 		dns_rdataset_t *trdataset;
745 		trdataset = ns_client_newrdataset(qctx->client);
746 		result = dns_db_findrdataset(
747 			qctx->db, qctx->node, qctx->version, dns_rdatatype_a, 0,
748 			qctx->client->now, trdataset, NULL);
749 		if (dns_rdataset_isassociated(trdataset)) {
750 			dns_rdataset_disassociate(trdataset);
751 		}
752 		ns_client_putrdataset(qctx->client, &trdataset);
753 
754 		/*
755 		 * We found an AAAA. If we also found an A, then the AAAA
756 		 * must not be rendered.
757 		 *
758 		 * If the A is not in our cache, then any result other than
759 		 * DNS_R_DELEGATION or ISC_R_NOTFOUND means there is no A,
760 		 * and so AAAAs are okay.
761 		 *
762 		 * We assume there is no A if we can't recurse for this
763 		 * client. That might be the wrong answer, but what else
764 		 * can we do?  Besides, the fact that we have the AAAA and
765 		 * are using this mechanism in the first place suggests
766 		 * that we care more about As than AAAAs, and would have
767 		 * cached an A if it existed.
768 		 */
769 		if (result == ISC_R_SUCCESS) {
770 			mark_as_rendered(qctx->rdataset, qctx->sigrdataset);
771 			qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD;
772 			client_state->flags |= FILTER_AAAA_FILTERED;
773 		} else if (!qctx->authoritative && RECURSIONOK(qctx->client) &&
774 			   (result == DNS_R_DELEGATION ||
775 			    result == ISC_R_NOTFOUND))
776 		{
777 			/*
778 			 * This is an ugly kludge to recurse
779 			 * for the A and discard the result.
780 			 *
781 			 * Continue to add the AAAA now.
782 			 * We'll make a note to not render it
783 			 * if the recursion for the A succeeds.
784 			 */
785 			result = ns_query_recurse(qctx->client, dns_rdatatype_a,
786 						  qctx->client->query.qname,
787 						  NULL, NULL, qctx->resuming);
788 			if (result == ISC_R_SUCCESS) {
789 				client_state->flags |= FILTER_AAAA_RECURSING;
790 				qctx->client->query.attributes |=
791 					NS_QUERYATTR_RECURSING;
792 			}
793 		}
794 	} else if (qctx->qtype == dns_rdatatype_a &&
795 		   (client_state->flags & FILTER_AAAA_RECURSING) != 0)
796 	{
797 		const section_filter_t filter_answer = {
798 			.qctx = qctx,
799 			.mode = client_state->mode,
800 			.section = DNS_SECTION_ANSWER,
801 			.name = qctx->fname,
802 			.type = dns_rdatatype_aaaa,
803 		};
804 		process_section(&filter_answer);
805 
806 		client_state->flags &= ~FILTER_AAAA_RECURSING;
807 
808 		result = ns_query_done(qctx);
809 
810 		*resp = result;
811 
812 		return (NS_HOOK_RETURN);
813 	}
814 
815 	*resp = result;
816 	return (NS_HOOK_CONTINUE);
817 }
818 
819 /*
820  * When answering an ANY query, remove AAAA if A is present.
821  */
822 static ns_hookresult_t
filter_respond_any_found(void * arg,void * cbdata,isc_result_t * resp)823 filter_respond_any_found(void *arg, void *cbdata, isc_result_t *resp) {
824 	query_ctx_t *qctx = (query_ctx_t *)arg;
825 	filter_instance_t *inst = (filter_instance_t *)cbdata;
826 	filter_data_t *client_state = client_state_get(qctx, inst);
827 
828 	*resp = ISC_R_UNSET;
829 
830 	if (client_state != NULL && client_state->mode != NONE) {
831 		/*
832 		 * If we are authoritative, require an A record to be
833 		 * present before filtering out AAAA records; otherwise,
834 		 * just assume an A record exists even if it was not in the
835 		 * cache (and therefore is not in the response message),
836 		 * thus proceeding with filtering out AAAA records.
837 		 */
838 		const section_filter_t filter_answer = {
839 			.qctx = qctx,
840 			.mode = client_state->mode,
841 			.section = DNS_SECTION_ANSWER,
842 			.name = qctx->tname,
843 			.type = dns_rdatatype_aaaa,
844 			.only_if_a_exists = qctx->authoritative,
845 		};
846 		process_section(&filter_answer);
847 	}
848 
849 	return (NS_HOOK_CONTINUE);
850 }
851 
852 /*
853  * Hide AAAA rrsets in the additional section if there is a matching A, and
854  * hide NS in the authority section if AAAA was filtered in the answer
855  * section.
856  */
857 static ns_hookresult_t
filter_query_done_send(void * arg,void * cbdata,isc_result_t * resp)858 filter_query_done_send(void *arg, void *cbdata, isc_result_t *resp) {
859 	query_ctx_t *qctx = (query_ctx_t *)arg;
860 	filter_instance_t *inst = (filter_instance_t *)cbdata;
861 	filter_data_t *client_state = client_state_get(qctx, inst);
862 
863 	*resp = ISC_R_UNSET;
864 
865 	if (client_state != NULL && client_state->mode != NONE) {
866 		const section_filter_t filter_additional = {
867 			.qctx = qctx,
868 			.mode = client_state->mode,
869 			.section = DNS_SECTION_ADDITIONAL,
870 			.type = dns_rdatatype_aaaa,
871 			.only_if_a_exists = true,
872 		};
873 		process_section(&filter_additional);
874 
875 		if ((client_state->flags & FILTER_AAAA_FILTERED) != 0) {
876 			const section_filter_t filter_authority = {
877 				.qctx = qctx,
878 				.mode = client_state->mode,
879 				.section = DNS_SECTION_AUTHORITY,
880 				.type = dns_rdatatype_ns,
881 			};
882 			process_section(&filter_authority);
883 		}
884 	}
885 
886 	return (NS_HOOK_CONTINUE);
887 }
888 
889 /*
890  * If the client is being detached, then we can delete our persistent data
891  * from hash table and return it to the memory pool.
892  */
893 static ns_hookresult_t
filter_qctx_destroy(void * arg,void * cbdata,isc_result_t * resp)894 filter_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp) {
895 	query_ctx_t *qctx = (query_ctx_t *)arg;
896 	filter_instance_t *inst = (filter_instance_t *)cbdata;
897 
898 	*resp = ISC_R_UNSET;
899 
900 	if (!qctx->detach_client) {
901 		return (NS_HOOK_CONTINUE);
902 	}
903 
904 	client_state_destroy(qctx, inst);
905 
906 	return (NS_HOOK_CONTINUE);
907 }
908