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