filter-aaaa.c 24.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

/*! \file */

14
15
16
17
18
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>

#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
28
#include <isc/util.h>

29
#include <isccfg/aclconf.h>
30
#include <isccfg/cfg.h>
31
#include <isccfg/grammar.h>
32
33
34
35
36

#include <ns/client.h>
#include <ns/hooks.h>
#include <ns/log.h>
#include <ns/query.h>
37
#include <ns/types.h>
38

39
40
41
42
43
44
45
46
#include <dns/acl.h>
#include <dns/db.h>
#include <dns/enumtype.h>
#include <dns/log.h>
#include <dns/message.h>
#include <dns/rdataset.h>
#include <dns/result.h>
#include <dns/types.h>
47
#include <dns/view.h>
48
49
50
51
52
53
54

#define CHECK(op)						\
	do {							\
		result = (op);					\
		if (result != ISC_R_SUCCESS) {			\
			goto cleanup;				\
		}						\
55
56
	} while (0)

57
/*
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 * Possible values for the settings of filter-aaaa-on-v4 and
 * filter-aaaa-on-v6: "no" is NONE, "yes" is FILTER, "break-dnssec"
 * is BREAK_DNSSEC.
 */
typedef enum {
	NONE = 0,
	FILTER = 1,
	BREAK_DNSSEC = 2
} filter_aaaa_t;

/*
 * Persistent data for use by this module. This will be associated
 * with client object address in the hash table, and will remain
 * accessible until the client object is detached.
72
 */
73
74
75
76
typedef struct filter_data {
	filter_aaaa_t mode;
	uint32_t flags;
} filter_data_t;
77

78
typedef struct filter_instance {
79
	ns_plugin_t *module;
80
	isc_mem_t *mctx;
81

82
83
84
85
	/*
	 * Memory pool for use with persistent data.
	 */
	isc_mempool_t *datapool;
86
	isc_mutex_t plock;
87
88
89
90
91

	/*
	 * Hash table associating a client object with its persistent data.
	 */
	isc_ht_t *ht;
Mark Andrews's avatar
Mark Andrews committed
92
	isc_mutex_t hlock;
93
94
95
96
97
98
99
100

	/*
	 * Values configured when the module is loaded.
	 */
	filter_aaaa_t v4_aaaa;
	filter_aaaa_t v6_aaaa;
	dns_acl_t *aaaa_acl;
} filter_instance_t;
101

102
103
104
105
106
107
/*
 * Per-client flags set by this module
 */
#define FILTER_AAAA_RECURSING	0x0001	/* Recursing for A */
#define FILTER_AAAA_FILTERED	0x0002	/* AAAA was removed from answer */

108
109
110
111
112
/*
 * Client attribute tests.
 */
#define WANTDNSSEC(c)	(((c)->attributes & NS_CLIENTATTR_WANTDNSSEC) != 0)
#define RECURSIONOK(c)	(((c)->query.attributes & \
113
114
				  NS_QUERYATTR_RECURSIONOK) != 0)

115
/*
116
 * Forward declarations of functions referenced in install_hooks().
117
 */
118
static ns_hookresult_t
119
filter_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp);
120
static ns_hookresult_t
121
filter_respond_begin(void *arg, void *cbdata, isc_result_t *resp);
122
static ns_hookresult_t
123
filter_respond_any_found(void *arg, void *cbdata, isc_result_t *resp);
124
static ns_hookresult_t
125
filter_prep_response_begin(void *arg, void *cbdata, isc_result_t *resp);
126
static ns_hookresult_t
127
filter_query_done_send(void *arg, void *cbdata, isc_result_t *resp);
128
static ns_hookresult_t
129
filter_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp);
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

/*%
 * Register the functions to be called at each hook point in 'hooktable', using
 * memory context 'mctx' for allocating copies of stack-allocated structures
 * passed to ns_hook_add().  Make sure 'inst' will be passed as the 'cbdata'
 * argument to every callback.
 */
static void
install_hooks(ns_hooktable_t *hooktable, isc_mem_t *mctx,
	      filter_instance_t *inst)
{
	const ns_hook_t filter_init = {
		.action = filter_qctx_initialize,
		.action_data = inst,
	};

	const ns_hook_t filter_respbegin = {
		.action = filter_respond_begin,
		.action_data = inst,
	};

	const ns_hook_t filter_respanyfound = {
		.action = filter_respond_any_found,
		.action_data = inst,
	};

	const ns_hook_t filter_prepresp = {
		.action = filter_prep_response_begin,
		.action_data = inst,
	};

	const ns_hook_t filter_donesend = {
		.action = filter_query_done_send,
		.action_data = inst,
	};

	const ns_hook_t filter_destroy = {
		.action = filter_qctx_destroy,
		.action_data = inst,
	};

	ns_hook_add(hooktable, mctx, -
		    NS_QUERY_QCTX_INITIALIZED, &filter_init);
	ns_hook_add(hooktable, mctx,
		    NS_QUERY_RESPOND_BEGIN, &filter_respbegin);
	ns_hook_add(hooktable, mctx,
		    NS_QUERY_RESPOND_ANY_FOUND, &filter_respanyfound);
	ns_hook_add(hooktable, mctx,
		    NS_QUERY_PREP_RESPONSE_BEGIN, &filter_prepresp);
	ns_hook_add(hooktable, mctx,
		    NS_QUERY_DONE_SEND, &filter_donesend);
	ns_hook_add(hooktable, mctx,
		    NS_QUERY_QCTX_DESTROYED, &filter_destroy);
}
184
185
186
187
188
189
190
191

/**
 ** Support for parsing of parameters and configuration of the module.
 **/

/*
 * Support for parsing of parameters.
 */
192
static const char *filter_aaaa_enums[] = { "break-dnssec", NULL };
193

194
195
196
197
static isc_result_t
parse_filter_aaaa(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
	return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
}
198

199
200
201
202
static void
doc_filter_aaaa(cfg_printer_t *pctx, const cfg_type_t *type) {
	cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
}
203

204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
static cfg_type_t cfg_type_filter_aaaa = {
	"filter_aaaa", parse_filter_aaaa, cfg_print_ustring,
	doc_filter_aaaa, &cfg_rep_string, filter_aaaa_enums,
};

static cfg_clausedef_t param_clauses[] = {
	{ "filter-aaaa", &cfg_type_bracketed_aml, 0 },
	{ "filter-aaaa-on-v4", &cfg_type_filter_aaaa, 0 },
	{ "filter-aaaa-on-v6", &cfg_type_filter_aaaa, 0 },
};

static cfg_clausedef_t *param_clausesets[] = {
	param_clauses,
	NULL
};

static cfg_type_t cfg_type_parameters = {
	"filter-aaaa-params", cfg_parse_mapbody, cfg_print_mapbody,
	cfg_doc_mapbody, &cfg_rep_map, param_clausesets
};

static isc_result_t
parse_filter_aaaa_on(const cfg_obj_t *param_obj, const char *param_name,
227
		     filter_aaaa_t *dstp)
228
229
230
231
232
233
234
235
236
237
238
{
	const cfg_obj_t *obj = NULL;
	isc_result_t result;

	result = cfg_map_get(param_obj, param_name, &obj);
	if (result != ISC_R_SUCCESS) {
		return (ISC_R_SUCCESS);
	}

	if (cfg_obj_isboolean(obj)) {
		if (cfg_obj_asboolean(obj)) {
239
			*dstp = FILTER;
240
		} else {
241
			*dstp = NONE;
242
243
		}
	} else if (strcasecmp(cfg_obj_asstring(obj), "break-dnssec") == 0) {
244
		*dstp = BREAK_DNSSEC;
245
246
247
248
249
250
251
	} else {
		result = ISC_R_UNEXPECTED;
	}

	return (result);
}

252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
static isc_result_t
check_syntax(cfg_obj_t *fmap, const void *cfg,
	     isc_mem_t *mctx, isc_log_t *lctx, void *actx)
{
	isc_result_t result = ISC_R_SUCCESS;
	const cfg_obj_t *aclobj = NULL;
	dns_acl_t *acl = NULL;
	filter_aaaa_t f4 = NONE, f6 = NONE;

	cfg_map_get(fmap, "filter-aaaa", &aclobj);
	if (aclobj == NULL) {
		return (result);
	}

	CHECK(cfg_acl_fromconfig(aclobj, (const cfg_obj_t *) cfg,
				 lctx, (cfg_aclconfctx_t *) actx,
				 mctx, 0, &acl));

	CHECK(parse_filter_aaaa_on(fmap, "filter-aaaa-on-v4", &f4));
	CHECK(parse_filter_aaaa_on(fmap, "filter-aaaa-on-v6", &f6));

	if ((f4 != NONE || f6 != NONE) && dns_acl_isnone(acl)) {
		cfg_obj_log(aclobj, lctx, ISC_LOG_WARNING,
			    "\"filter-aaaa\" is 'none;' but "
			    "either filter-aaaa-on-v4 or filter-aaaa-on-v6 "
			    "is enabled");
		result = ISC_R_FAILURE;
	} else if (f4 == NONE && f6 == NONE && !dns_acl_isnone(acl)) {
		cfg_obj_log(aclobj, lctx, ISC_LOG_WARNING,
			    "\"filter-aaaa\" is set but "
			    "neither filter-aaaa-on-v4 or filter-aaaa-on-v6 "
			    "is enabled");
		result = ISC_R_FAILURE;
	}

 cleanup:
	if (acl != NULL) {
		dns_acl_detach(&acl);
	}

	return (result);
}

295
static isc_result_t
296
parse_parameters(filter_instance_t *inst, const char *parameters,
297
298
		 const void *cfg, const char *cfg_file, unsigned long cfg_line,
		 isc_mem_t *mctx, isc_log_t *lctx, void *actx)
299
300
301
302
303
304
305
{
	isc_result_t result = ISC_R_SUCCESS;
	cfg_parser_t *parser = NULL;
	cfg_obj_t *param_obj = NULL;
	const cfg_obj_t *obj = NULL;
	isc_buffer_t b;

306
	CHECK(cfg_parser_create(mctx, lctx, &parser));
307
308
309

	isc_buffer_constinit(&b, parameters, strlen(parameters));
	isc_buffer_add(&b, strlen(parameters));
310
311
	CHECK(cfg_parse_buffer(parser, &b, cfg_file, cfg_line,
			       &cfg_type_parameters, 0, &param_obj));
312

313
	CHECK(check_syntax(param_obj, cfg, mctx, lctx, actx));
314

315
316
317
318
	CHECK(parse_filter_aaaa_on(param_obj, "filter-aaaa-on-v4",
				   &inst->v4_aaaa));
	CHECK(parse_filter_aaaa_on(param_obj, "filter-aaaa-on-v6",
				   &inst->v6_aaaa));
319
320
321
322

	result = cfg_map_get(param_obj, "filter-aaaa", &obj);
	if (result == ISC_R_SUCCESS) {
		CHECK(cfg_acl_fromconfig(obj, (const cfg_obj_t *) cfg,
323
324
					 lctx, (cfg_aclconfctx_t *) actx,
					 mctx, 0, &inst->aaaa_acl));
325
	} else {
326
		CHECK(dns_acl_any(mctx, &inst->aaaa_acl));
327
328
329
330
331
332
333
334
335
336
337
338
	}

 cleanup:
	if (param_obj != NULL) {
		cfg_obj_destroy(parser, &param_obj);
	}
	if (parser != NULL) {
		cfg_parser_destroy(&parser);
	}
	return (result);
}

339
/**
340
 ** Mandatory plugin API functions:
341
 **
342
343
344
345
 ** - plugin_destroy
 ** - plugin_register
 ** - plugin_version
 ** - plugin_check
346
347
 **/

348
/*
349
350
 * Called by ns_plugin_register() to initialize the plugin and
 * register hook functions into the view hook table.
351
 */
352
isc_result_t
353
354
355
356
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)
357
{
358
	filter_instance_t *inst = NULL;
359
360
	isc_result_t result;

361

362
	isc_log_write(lctx, NS_LOGCATEGORY_GENERAL,
363
		      NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
364
		      "registering 'filter-aaaa' "
365
366
		      "module from %s:%lu, %s parameters",
		      cfg_file, cfg_line, parameters != NULL ? "with" : "no");
367

368
	inst = isc_mem_get(mctx, sizeof(*inst));
369
	memset(inst, 0, sizeof(*inst));
370
	isc_mem_attach(mctx, &inst->mctx);
371

372
	if (parameters != NULL) {
373
374
		CHECK(parse_parameters(inst, parameters, cfg, cfg_file,
				       cfg_line, mctx, lctx, actx));
375
	}
376

377
	CHECK(isc_mempool_create(mctx, sizeof(filter_data_t),
378
				 &inst->datapool));
379
	CHECK(isc_ht_init(&inst->ht, mctx, 16));
Mark Andrews's avatar
Mark Andrews committed
380
	isc_mutex_init(&inst->hlock);
381

382
	/*
383
384
385
386
387
388
389
390
	 * Fill the mempool with 1K filter_aaaa state objects at
	 * a time; ideally after a single allocation, the mempool will
	 * have enough to handle all the simultaneous queries the system
	 * requires and it won't be necessary to allocate more.
	 *
	 * We don't set any limit on the number of free state objects
	 * so that they'll always be returned to the pool and not
	 * freed until the pool is destroyed on shutdown.
391
	 */
392
393
	isc_mempool_setfillcount(inst->datapool, 1024);
	isc_mempool_setfreemax(inst->datapool, UINT_MAX);
394
395
	isc_mutex_init(&inst->plock);
	isc_mempool_associatelock(inst->datapool, &inst->plock);
396
397
398
399

	/*
	 * Set hook points in the view's hooktable.
	 */
400
	install_hooks(hooktable, mctx, inst);
401
402

	*instp = inst;
403
404

 cleanup:
405
	if (result != ISC_R_SUCCESS && inst != NULL) {
406
		plugin_destroy((void **) &inst);
407
	}
408

409
	return (result);
410
411
}

412
isc_result_t
413
414
415
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)
416
417
418
419
420
421
422
423
424
425
{
	isc_result_t result = ISC_R_SUCCESS;
	cfg_parser_t *parser = NULL;
	cfg_obj_t *param_obj = NULL;
	isc_buffer_t b;

	CHECK(cfg_parser_create(mctx, lctx, &parser));

	isc_buffer_constinit(&b, parameters, strlen(parameters));
	isc_buffer_add(&b, strlen(parameters));
426
427
	CHECK(cfg_parse_buffer(parser, &b, cfg_file, cfg_line,
			       &cfg_type_parameters, 0, &param_obj));
428
429
430
431
432
433
434
435
436
437
438
439
440

	CHECK(check_syntax(param_obj, cfg, mctx, lctx, actx));

 cleanup:
	if (param_obj != NULL) {
		cfg_obj_destroy(parser, &param_obj);
	}
	if (parser != NULL) {
		cfg_parser_destroy(&parser);
	}
	return (result);
}

441
/*
442
 * Called by ns_plugins_free(); frees memory allocated by
443
444
 * the module when it was registered.
 */
445
void
446
plugin_destroy(void **instp) {
447
	filter_instance_t *inst = (filter_instance_t *) *instp;
448

449
450
	if (inst->ht != NULL) {
		isc_ht_destroy(&inst->ht);
Mark Andrews's avatar
Mark Andrews committed
451
		isc_mutex_destroy(&inst->hlock);
452
	}
453
454
	if (inst->datapool != NULL) {
		isc_mempool_destroy(&inst->datapool);
455
		isc_mutex_destroy(&inst->plock);
456
	}
457
458
	if (inst->aaaa_acl != NULL) {
		dns_acl_detach(&inst->aaaa_acl);
459
460
	}

461
462
463
	isc_mem_putanddetach(&inst->mctx, inst, sizeof(*inst));
	*instp = NULL;

464
465
466
	return;
}

467
/*
Tinderbox User's avatar
Tinderbox User committed
468
 * Returns plugin API version for compatibility checks.
469
 */
470
int
471
472
plugin_version(void) {
	return (NS_PLUGIN_VERSION);
473
474
}

475
/**
476
 ** "filter-aaaa" feature implementation begins here.
477
478
 **/

479
480
481
482
483
484
485
486
487
488
489
490
/*%
 * Structure describing the filtering to be applied by process_section().
 */
typedef struct section_filter {
	query_ctx_t *		qctx;
	filter_aaaa_t 		mode;
	dns_section_t		section;
	const dns_name_t *	name;
	dns_rdatatype_t		type;
	bool			only_if_a_exists;
} section_filter_t;

491
/*
492
 * Check whether this is an IPv4 client.
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
 */
static bool
is_v4_client(ns_client_t *client) {
	if (isc_sockaddr_pf(&client->peeraddr) == AF_INET) {
		return (true);
	}
	if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 &&
	    IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr))
	{
		return (true);
	}
	return (false);
}

/*
508
 * Check whether this is an IPv6 client.
509
510
511
512
513
514
515
516
517
518
519
 */
static bool
is_v6_client(ns_client_t *client) {
	if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 &&
	    !IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr))
	{
		return (true);
	}
	return (false);
}

520
static filter_data_t *
521
client_state_get(const query_ctx_t *qctx, filter_instance_t *inst) {
522
523
524
	filter_data_t *client_state = NULL;
	isc_result_t result;

Mark Andrews's avatar
Mark Andrews committed
525
	LOCK(&inst->hlock);
526
	result = isc_ht_find(inst->ht, (const unsigned char *)&qctx->client,
527
			     sizeof(qctx->client), (void **)&client_state);
Mark Andrews's avatar
Mark Andrews committed
528
	UNLOCK(&inst->hlock);
529
530
531
532
533

	return (result == ISC_R_SUCCESS ? client_state : NULL);
}

static void
534
client_state_create(const query_ctx_t *qctx, filter_instance_t *inst) {
535
536
537
	filter_data_t *client_state;
	isc_result_t result;

538
	client_state = isc_mempool_get(inst->datapool);
539
540
541
542
543
544
545
	if (client_state == NULL) {
		return;
	}

	client_state->mode = NONE;
	client_state->flags = 0;

Mark Andrews's avatar
Mark Andrews committed
546
	LOCK(&inst->hlock);
547
	result = isc_ht_add(inst->ht, (const unsigned char *)&qctx->client,
548
			    sizeof(qctx->client), client_state);
Mark Andrews's avatar
Mark Andrews committed
549
	UNLOCK(&inst->hlock);
550
551
552
553
	RUNTIME_CHECK(result == ISC_R_SUCCESS);
}

static void
554
555
client_state_destroy(const query_ctx_t *qctx, filter_instance_t *inst) {
	filter_data_t *client_state = client_state_get(qctx, inst);
556
557
558
559
560
561
	isc_result_t result;

	if (client_state == NULL) {
		return;
	}

Mark Andrews's avatar
Mark Andrews committed
562
	LOCK(&inst->hlock);
563
	result = isc_ht_delete(inst->ht, (const unsigned char *)&qctx->client,
564
			       sizeof(qctx->client));
Mark Andrews's avatar
Mark Andrews committed
565
	UNLOCK(&inst->hlock);
566
567
	RUNTIME_CHECK(result == ISC_R_SUCCESS);

568
	isc_mempool_put(inst->datapool, client_state);
569
}
570

571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
/*%
 * Mark 'rdataset' and 'sigrdataset' as rendered, gracefully handling NULL
 * pointers and non-associated rdatasets.
 */
static void
mark_as_rendered(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
	if (rdataset != NULL && dns_rdataset_isassociated(rdataset)) {
		rdataset->attributes |= DNS_RDATASETATTR_RENDERED;
	}
	if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) {
		sigrdataset->attributes |= DNS_RDATASETATTR_RENDERED;
	}
}

/*%
 * Check whether an RRset of given 'type' is present at given 'name'.  If
 * it is found and either it is not signed or the combination of query
 * flags and configured processing 'mode' allows it, mark the RRset and its
 * associated signatures as already rendered to prevent them from appearing
 * in the response message stored in 'qctx'.  If 'only_if_a_exists' is
 * true, an RRset of type A must also exist at 'name' in order for the
 * above processing to happen.
 */
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)
{
	dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
	isc_result_t result;
	bool modified = false;

	if (only_if_a_exists) {
		CHECK(dns_message_findtype(name, dns_rdatatype_a, 0, NULL));
	}

606
607
608
	(void)dns_message_findtype(name, type, 0, &rdataset);
	(void)dns_message_findtype(name, dns_rdatatype_rrsig, type,
				   &sigrdataset);
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681

	if (rdataset != NULL &&
	    (sigrdataset == NULL || !WANTDNSSEC(qctx->client) ||
	     mode == BREAK_DNSSEC))
	{
		/*
		 * An RRset of given 'type' was found at 'name' and at least
		 * one of the following is true:
		 *
		 *   - the RRset is not signed,
		 *   - the client did not set the DO bit in its request,
		 *   - configuration allows us to tamper with signed responses.
		 *
		 * This means it is okay to filter out this RRset and its
		 * signatures, if any, from the response.
		 */
		mark_as_rendered(rdataset, sigrdataset);
		modified = true;
	}

 cleanup:
	return (modified);
}

/*%
 * Apply the requested section filter, i.e. prevent (when possible, as
 * determined by process_name()) RRsets of given 'type' from being rendered
 * in the given 'section' of the response message stored in 'qctx'.  Clear
 * the AD bit if the answer and/or authority section was modified.  If
 * 'name' is NULL, all names in the given 'section' are processed;
 * otherwise, only 'name' is.  'only_if_a_exists' is passed through to
 * process_name().
 */
static void
process_section(const section_filter_t *filter) {
	query_ctx_t *qctx = filter->qctx;
	filter_aaaa_t mode = filter->mode;
	dns_section_t section = filter->section;
	const dns_name_t *name = filter->name;
	dns_rdatatype_t type = filter->type;
	bool only_if_a_exists = filter->only_if_a_exists;

	dns_message_t *message = qctx->client->message;
	isc_result_t result;

	for (result = dns_message_firstname(message, section);
	     result == ISC_R_SUCCESS;
	     result = dns_message_nextname(message, section))
	{
		dns_name_t *cur = NULL;
		dns_message_currentname(message, section, &cur);
		if (name != NULL && !dns_name_equal(name, cur)) {
			/*
			 * We only want to process 'name' and this is not it.
			 */
			continue;
		}

		if (!process_name(qctx, mode, cur, type, only_if_a_exists)) {
			/*
			 * Response was not modified, do not touch the AD bit.
			 */
			continue;
		}

		if (section == DNS_SECTION_ANSWER ||
		    section == DNS_SECTION_AUTHORITY)
		{
			message->flags &= ~DNS_MESSAGEFLAG_AD;
		}
	}
}

682
/*
683
 * Initialize filter state, fetching it from a memory pool and storing it
684
685
686
 * in a hash table keyed according to the client object; this enables us to
 * retrieve persistent data related to a client query for as long as the
 * object persists.
687
 */
688
static ns_hookresult_t
689
690
filter_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
691
	filter_instance_t *inst = (filter_instance_t *) cbdata;
692
693
	filter_data_t *client_state;

694
695
	*resp = ISC_R_UNSET;

696
	client_state = client_state_get(qctx, inst);
697
	if (client_state == NULL) {
698
		client_state_create(qctx, inst);
699
	}
700

701
	return (NS_HOOK_CONTINUE);
702
703
704
}

/*
705
706
707
 * Determine whether this client should have AAAA filtered or not, based on
 * the client address family and the settings of filter-aaaa-on-v4 and
 * filter-aaaa-on-v6.
708
 */
709
static ns_hookresult_t
710
711
filter_prep_response_begin(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
712
713
	filter_instance_t *inst = (filter_instance_t *) cbdata;
	filter_data_t *client_state = client_state_get(qctx, inst);
714
715
	isc_result_t result;

716
717
	*resp = ISC_R_UNSET;

718
	if (client_state == NULL) {
719
		return (NS_HOOK_CONTINUE);
720
	}
721

722
	if (inst->v4_aaaa != NONE || inst->v6_aaaa != NONE) {
723
		result = ns_client_checkaclsilent(qctx->client, NULL,
724
						  inst->aaaa_acl, true);
725
		if (result == ISC_R_SUCCESS &&
726
		    inst->v4_aaaa != NONE &&
727
728
		    is_v4_client(qctx->client))
		{
729
			client_state->mode = inst->v4_aaaa;
730
		} else if (result == ISC_R_SUCCESS &&
731
			   inst->v6_aaaa != NONE &&
732
733
			   is_v6_client(qctx->client))
		{
734
			client_state->mode = inst->v6_aaaa;
735
736
737
		}
	}

738
	return (NS_HOOK_CONTINUE);
739
740
741
}

/*
742
743
744
 * Hide AAAA rrsets if there is a matching A. Trigger recursion if
 * necessary to find out whether an A exists.
 *
745
746
 * (This version is for processing answers to explicit AAAA queries; ANY
 * queries are handled in filter_respond_any_found().)
747
 */
748
static ns_hookresult_t
749
750
filter_respond_begin(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
751
752
	filter_instance_t *inst = (filter_instance_t *) cbdata;
	filter_data_t *client_state = client_state_get(qctx, inst);
753
754
	isc_result_t result = ISC_R_UNSET;

755
756
	*resp = ISC_R_UNSET;

757
	if (client_state == NULL) {
758
		return (NS_HOOK_CONTINUE);
759
	}
760

761
762
	if (client_state->mode != BREAK_DNSSEC &&
	    (client_state->mode != FILTER ||
763
764
765
	     (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL &&
	      dns_rdataset_isassociated(qctx->sigrdataset))))
	{
766
		return (NS_HOOK_CONTINUE);
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
	}

	if (qctx->qtype == dns_rdatatype_aaaa) {
		dns_rdataset_t *trdataset;
		trdataset = ns_client_newrdataset(qctx->client);
		result = dns_db_findrdataset(qctx->db, qctx->node,
					     qctx->version,
					     dns_rdatatype_a, 0,
					     qctx->client->now,
					     trdataset, NULL);
		if (dns_rdataset_isassociated(trdataset)) {
			dns_rdataset_disassociate(trdataset);
		}
		ns_client_putrdataset(qctx->client, &trdataset);

		/*
		 * We found an AAAA. If we also found an A, then the AAAA
		 * must not be rendered.
		 *
		 * If the A is not in our cache, then any result other than
		 * DNS_R_DELEGATION or ISC_R_NOTFOUND means there is no A,
		 * and so AAAAs are okay.
		 *
		 * We assume there is no A if we can't recurse for this
		 * client. That might be the wrong answer, but what else
		 * can we do?  Besides, the fact that we have the AAAA and
		 * are using this mechanism in the first place suggests
		 * that we care more about As than AAAAs, and would have
		 * cached an A if it existed.
		 */
		if (result == ISC_R_SUCCESS) {
798
			mark_as_rendered(qctx->rdataset, qctx->sigrdataset);
799
			qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD;
800
			client_state->flags |= FILTER_AAAA_FILTERED;
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
		} else if (!qctx->authoritative &&
			   RECURSIONOK(qctx->client) &&
			   (result == DNS_R_DELEGATION ||
			    result == ISC_R_NOTFOUND))
		{
			/*
			 * This is an ugly kludge to recurse
			 * for the A and discard the result.
			 *
			 * Continue to add the AAAA now.
			 * We'll make a note to not render it
			 * if the recursion for the A succeeds.
			 */
			result = ns_query_recurse(qctx->client,
						  dns_rdatatype_a,
						  qctx->client->query.qname,
						  NULL, NULL, qctx->resuming);
			if (result == ISC_R_SUCCESS) {
819
				client_state->flags |= FILTER_AAAA_RECURSING;
820
821
822
823
824
				qctx->client->query.attributes |=
					NS_QUERYATTR_RECURSING;
			}
		}
	} else if (qctx->qtype == dns_rdatatype_a &&
825
		   (client_state->flags & FILTER_AAAA_RECURSING) != 0)
826
	{
827
828
829
830
831
832
833
834
		const section_filter_t filter_answer = {
			.qctx = qctx,
			.mode = client_state->mode,
			.section = DNS_SECTION_ANSWER,
			.name = qctx->fname,
			.type = dns_rdatatype_aaaa,
		};
		process_section(&filter_answer);
835

836
		client_state->flags &= ~FILTER_AAAA_RECURSING;
837
838
839
840
841

		result = ns_query_done(qctx);

		*resp = result;

842
		return (NS_HOOK_RETURN);
843
844
845
	}

	*resp = result;
846
	return (NS_HOOK_CONTINUE);
847
848
}

849
850
851
/*
 * When answering an ANY query, remove AAAA if A is present.
 */
852
static ns_hookresult_t
853
854
filter_respond_any_found(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
855
856
	filter_instance_t *inst = (filter_instance_t *) cbdata;
	filter_data_t *client_state = client_state_get(qctx, inst);
857

858
	*resp = ISC_R_UNSET;
859

860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
	if (client_state != NULL && client_state->mode != NONE) {
		/*
		 * If we are authoritative, require an A record to be
		 * present before filtering out AAAA records; otherwise,
		 * just assume an A record exists even if it was not in the
		 * cache (and therefore is not in the response message),
		 * thus proceeding with filtering out AAAA records.
		 */
		const section_filter_t filter_answer = {
			.qctx = qctx,
			.mode = client_state->mode,
			.section = DNS_SECTION_ANSWER,
			.name = qctx->tname,
			.type = dns_rdatatype_aaaa,
			.only_if_a_exists = qctx->authoritative,
		};
		process_section(&filter_answer);
877
878
	}

879
	return (NS_HOOK_CONTINUE);
880
881
882
}

/*
883
884
 * Hide AAAA rrsets in the additional section if there is a matching A, and
 * hide NS in the authority section if AAAA was filtered in the answer
885
886
 * section.
 */
887
static ns_hookresult_t
888
889
filter_query_done_send(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
890
891
	filter_instance_t *inst = (filter_instance_t *) cbdata;
	filter_data_t *client_state = client_state_get(qctx, inst);
892

893
	*resp = ISC_R_UNSET;
894

895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
	if (client_state != NULL && client_state->mode != NONE) {
		const section_filter_t filter_additional = {
			.qctx = qctx,
			.mode = client_state->mode,
			.section = DNS_SECTION_ADDITIONAL,
			.type = dns_rdatatype_aaaa,
			.only_if_a_exists = true,
		};
		process_section(&filter_additional);

		if ((client_state->flags & FILTER_AAAA_FILTERED) != 0) {
			const section_filter_t filter_authority = {
				.qctx = qctx,
				.mode = client_state->mode,
				.section = DNS_SECTION_AUTHORITY,
				.type = dns_rdatatype_ns,
			};
			process_section(&filter_authority);
913
914
915
		}
	}

916
	return (NS_HOOK_CONTINUE);
917
}
918
919

/*
920
921
 * If the client is being detached, then we can delete our persistent data
 * from hash table and return it to the memory pool.
922
 */
923
static ns_hookresult_t
924
925
filter_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
926
	filter_instance_t *inst = (filter_instance_t *) cbdata;
927

928
929
	*resp = ISC_R_UNSET;

930
	if (!qctx->detach_client) {
931
		return (NS_HOOK_CONTINUE);
932
933
	}

934
	client_state_destroy(qctx, inst);
935

936
	return (NS_HOOK_CONTINUE);
937
}