filter-aaaa.c 24.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * 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 */

#include <config.h>

16 17 18 19 20
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>

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

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

#include <ns/client.h>
#include <ns/hooks.h>
#include <ns/log.h>
#include <ns/query.h>
39
#include <ns/types.h>
40

41 42 43 44 45 46 47 48
#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>
49
#include <dns/view.h>
50 51 52 53 54 55 56

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

59
/*
60 61 62 63 64 65 66 67 68 69 70 71 72 73
 * 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.
74
 */
75 76 77 78
typedef struct filter_data {
	filter_aaaa_t mode;
	uint32_t flags;
} filter_data_t;
79

80
typedef struct filter_instance {
81
	ns_plugin_t *module;
82
	isc_mem_t *mctx;
83

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
	/*
	 * Memory pool for use with persistent data.
	 */
	isc_mempool_t *datapool;

	/*
	 * Hash table associating a client object with its persistent data.
	 */
	isc_ht_t *ht;

	/*
	 * 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));
380

381
	/*
382 383 384 385 386 387 388 389
	 * 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.
390
	 */
391 392 393 394 395 396
	isc_mempool_setfillcount(inst->datapool, 1024);
	isc_mempool_setfreemax(inst->datapool, UINT_MAX);

	/*
	 * Set hook points in the view's hooktable.
	 */
397
	install_hooks(hooktable, mctx, inst);
398 399

	*instp = inst;
400 401

 cleanup:
402
	if (result != ISC_R_SUCCESS && inst != NULL) {
403
		plugin_destroy((void **) &inst);
404
	}
405

406
	return (result);
407 408
}

409
isc_result_t
410 411 412
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)
413 414 415 416 417 418 419 420 421 422
{
	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));
423 424
	CHECK(cfg_parse_buffer(parser, &b, cfg_file, cfg_line,
			       &cfg_type_parameters, 0, &param_obj));
425 426 427 428 429 430 431 432 433 434 435 436 437

	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);
}

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

446 447
	if (inst->ht != NULL) {
		isc_ht_destroy(&inst->ht);
448
	}
449 450
	if (inst->datapool != NULL) {
		isc_mempool_destroy(&inst->datapool);
451
	}
452 453
	if (inst->aaaa_acl != NULL) {
		dns_acl_detach(&inst->aaaa_acl);
454 455
	}

456 457 458
	isc_mem_putanddetach(&inst->mctx, inst, sizeof(*inst));
	*instp = NULL;

459 460 461
	return;
}

462
/*
Tinderbox User's avatar
Tinderbox User committed
463
 * Returns plugin API version for compatibility checks.
464
 */
465
int
466 467
plugin_version(void) {
	return (NS_PLUGIN_VERSION);
468 469
}

470
/**
471
 ** "filter-aaaa" feature implementation begins here.
472 473
 **/

474 475 476 477 478 479 480 481 482 483 484 485
/*%
 * 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;

486
/*
487
 * Check whether this is an IPv4 client.
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
 */
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);
}

/*
503
 * Check whether this is an IPv6 client.
504 505 506 507 508 509 510 511 512 513 514
 */
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);
}

515
static filter_data_t *
516
client_state_get(const query_ctx_t *qctx, filter_instance_t *inst) {
517 518 519
	filter_data_t *client_state = NULL;
	isc_result_t result;

520
	result = isc_ht_find(inst->ht, (const unsigned char *)&qctx->client,
521 522 523 524 525 526
			     sizeof(qctx->client), (void **)&client_state);

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

static void
527
client_state_create(const query_ctx_t *qctx, filter_instance_t *inst) {
528 529 530
	filter_data_t *client_state;
	isc_result_t result;

531
	client_state = isc_mempool_get(inst->datapool);
532 533 534 535 536 537 538
	if (client_state == NULL) {
		return;
	}

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

539
	result = isc_ht_add(inst->ht, (const unsigned char *)&qctx->client,
540 541 542 543 544
			    sizeof(qctx->client), client_state);
	RUNTIME_CHECK(result == ISC_R_SUCCESS);
}

static void
545 546
client_state_destroy(const query_ctx_t *qctx, filter_instance_t *inst) {
	filter_data_t *client_state = client_state_get(qctx, inst);
547 548 549 550 551 552
	isc_result_t result;

	if (client_state == NULL) {
		return;
	}

553
	result = isc_ht_delete(inst->ht, (const unsigned char *)&qctx->client,
554 555 556
			       sizeof(qctx->client));
	RUNTIME_CHECK(result == ISC_R_SUCCESS);

557
	isc_mempool_put(inst->datapool, client_state);
558
}
559

560 561 562 563 564 565 566 567 568 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
/*%
 * 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));
	}

595 596 597
	(void)dns_message_findtype(name, type, 0, &rdataset);
	(void)dns_message_findtype(name, dns_rdatatype_rrsig, type,
				   &sigrdataset);
598 599 600 601 602 603 604 605 606 607 608 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

	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;
		}
	}
}

671
/*
672
 * Initialize filter state, fetching it from a memory pool and storing it
673 674 675
 * 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.
676
 */
677
static ns_hookresult_t
678 679
filter_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
680
	filter_instance_t *inst = (filter_instance_t *) cbdata;
681 682
	filter_data_t *client_state;

683 684
	*resp = ISC_R_UNSET;

685
	client_state = client_state_get(qctx, inst);
686
	if (client_state == NULL) {
687
		client_state_create(qctx, inst);
688
	}
689

690
	return (NS_HOOK_CONTINUE);
691 692 693
}

/*
694 695 696
 * 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.
697
 */
698
static ns_hookresult_t
699 700
filter_prep_response_begin(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
701 702
	filter_instance_t *inst = (filter_instance_t *) cbdata;
	filter_data_t *client_state = client_state_get(qctx, inst);
703 704
	isc_result_t result;

705 706
	*resp = ISC_R_UNSET;

707
	if (client_state == NULL) {
708
		return (NS_HOOK_CONTINUE);
709
	}
710

711
	if (inst->v4_aaaa != NONE || inst->v6_aaaa != NONE) {
712
		result = ns_client_checkaclsilent(qctx->client, NULL,
713
						  inst->aaaa_acl, true);
714
		if (result == ISC_R_SUCCESS &&
715
		    inst->v4_aaaa != NONE &&
716 717
		    is_v4_client(qctx->client))
		{
718
			client_state->mode = inst->v4_aaaa;
719
		} else if (result == ISC_R_SUCCESS &&
720
			   inst->v6_aaaa != NONE &&
721 722
			   is_v6_client(qctx->client))
		{
723
			client_state->mode = inst->v6_aaaa;
724 725 726
		}
	}

727
	return (NS_HOOK_CONTINUE);
728 729 730
}

/*
731 732 733
 * Hide AAAA rrsets if there is a matching A. Trigger recursion if
 * necessary to find out whether an A exists.
 *
734 735
 * (This version is for processing answers to explicit AAAA queries; ANY
 * queries are handled in filter_respond_any_found().)
736
 */
737
static ns_hookresult_t
738 739
filter_respond_begin(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
740 741
	filter_instance_t *inst = (filter_instance_t *) cbdata;
	filter_data_t *client_state = client_state_get(qctx, inst);
742 743
	isc_result_t result = ISC_R_UNSET;

744 745
	*resp = ISC_R_UNSET;

746
	if (client_state == NULL) {
747
		return (NS_HOOK_CONTINUE);
748
	}
749

750 751
	if (client_state->mode != BREAK_DNSSEC &&
	    (client_state->mode != FILTER ||
752 753 754
	     (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL &&
	      dns_rdataset_isassociated(qctx->sigrdataset))))
	{
755
		return (NS_HOOK_CONTINUE);
756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
	}

	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) {
787
			mark_as_rendered(qctx->rdataset, qctx->sigrdataset);
788
			qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD;
789
			client_state->flags |= FILTER_AAAA_FILTERED;
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
		} 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) {
808
				client_state->flags |= FILTER_AAAA_RECURSING;
809 810 811 812 813
				qctx->client->query.attributes |=
					NS_QUERYATTR_RECURSING;
			}
		}
	} else if (qctx->qtype == dns_rdatatype_a &&
814
		   (client_state->flags & FILTER_AAAA_RECURSING) != 0)
815
	{
816 817 818 819 820 821 822 823
		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);
824

825
		client_state->flags &= ~FILTER_AAAA_RECURSING;
826 827 828 829 830

		result = ns_query_done(qctx);

		*resp = result;

831
		return (NS_HOOK_RETURN);
832 833 834
	}

	*resp = result;
835
	return (NS_HOOK_CONTINUE);
836 837
}

838 839 840
/*
 * When answering an ANY query, remove AAAA if A is present.
 */
841
static ns_hookresult_t
842 843
filter_respond_any_found(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
844 845
	filter_instance_t *inst = (filter_instance_t *) cbdata;
	filter_data_t *client_state = client_state_get(qctx, inst);
846

847
	*resp = ISC_R_UNSET;
848

849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865
	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);
866 867
	}

868
	return (NS_HOOK_CONTINUE);
869 870 871
}

/*
872 873
 * 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
874 875
 * section.
 */
876
static ns_hookresult_t
877 878
filter_query_done_send(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
879 880
	filter_instance_t *inst = (filter_instance_t *) cbdata;
	filter_data_t *client_state = client_state_get(qctx, inst);
881

882
	*resp = ISC_R_UNSET;
883

884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
	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);
902 903 904
		}
	}

905
	return (NS_HOOK_CONTINUE);
906
}
907 908

/*
909 910
 * If the client is being detached, then we can delete our persistent data
 * from hash table and return it to the memory pool.
911
 */
912
static ns_hookresult_t
913 914
filter_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp) {
	query_ctx_t *qctx = (query_ctx_t *) arg;
915
	filter_instance_t *inst = (filter_instance_t *) cbdata;
916

917 918
	*resp = ISC_R_UNSET;

919
	if (!qctx->detach_client) {
920
		return (NS_HOOK_CONTINUE);
921 922
	}

923
	client_state_destroy(qctx, inst);
924

925
	return (NS_HOOK_CONTINUE);
926
}