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