query.c 164 KB
Newer Older
Bob Halley's avatar
add  
Bob Halley committed
1
/*
Automatic Updater's avatar
Automatic Updater committed
2
 * Copyright (C) 2004-2010  Internet Systems Consortium, Inc. ("ISC")
Mark Andrews's avatar
Mark Andrews committed
3
 * Copyright (C) 1999-2003  Internet Software Consortium.
4
 *
Automatic Updater's avatar
Automatic Updater committed
5
 * Permission to use, copy, modify, and/or distribute this software for any
Bob Halley's avatar
add  
Bob Halley committed
6 7
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
8
 *
Mark Andrews's avatar
Mark Andrews committed
9 10 11 12 13 14 15
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
Bob Halley's avatar
add  
Bob Halley committed
16 17
 */

18
/* $Id: query.c,v 1.349 2010/12/16 09:51:27 jinmei Exp $ */
19 20

/*! \file */
David Lawrence's avatar
David Lawrence committed
21

Bob Halley's avatar
add  
Bob Halley committed
22 23
#include <config.h>

Brian Wellington's avatar
Brian Wellington committed
24 25
#include <string.h>

26
#include <isc/hex.h>
Bob Halley's avatar
add  
Bob Halley committed
27
#include <isc/mem.h>
28
#include <isc/stats.h>
Michael Graff's avatar
Michael Graff committed
29
#include <isc/util.h>
Bob Halley's avatar
add  
Bob Halley committed
30

31
#include <dns/adb.h>
32
#include <dns/byaddr.h>
Bob Halley's avatar
Bob Halley committed
33
#include <dns/db.h>
34 35 36
#ifdef DLZ
#include <dns/dlz.h>
#endif
37
#include <dns/dns64.h>
38
#include <dns/dnssec.h>
Bob Halley's avatar
add  
Bob Halley committed
39
#include <dns/events.h>
Bob Halley's avatar
Bob Halley committed
40
#include <dns/message.h>
41
#include <dns/ncache.h>
42
#include <dns/nsec3.h>
43
#include <dns/order.h>
Bob Halley's avatar
Bob Halley committed
44
#include <dns/rdata.h>
45
#include <dns/rdataclass.h>
46
#include <dns/rdatalist.h>
Bob Halley's avatar
Bob Halley committed
47 48
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
49
#include <dns/rdatastruct.h>
50
#include <dns/rdatatype.h>
51
#include <dns/resolver.h>
52
#include <dns/result.h>
53
#include <dns/stats.h>
Brian Wellington's avatar
Brian Wellington committed
54
#include <dns/tkey.h>
55
#include <dns/view.h>
Bob Halley's avatar
Bob Halley committed
56 57
#include <dns/zone.h>
#include <dns/zt.h>
Bob Halley's avatar
add  
Bob Halley committed
58 59

#include <named/client.h>
60
#include <named/globals.h>
61 62
#include <named/log.h>
#include <named/server.h>
63
#include <named/sortlist.h>
64
#include <named/xfrout.h>
65

66 67 68 69 70 71 72 73 74 75 76
#if 0
/*
 * It has been recommended that DNS64 be changed to return excluded
 * AAAA addresses if DNS64 synthesis does not occur.  This minimises
 * the impact on the lookup results.  While most DNS AAAA lookups are
 * done to send IP packets to a host, not all of them are and filtering
 * excluded addresses has a negative impact on those uses.
 */
#define dns64_bis_return_excluded_addresses 1
#endif

77
/*% Partial answer? */
Bob Halley's avatar
Bob Halley committed
78 79
#define PARTIALANSWER(c)	(((c)->query.attributes & \
				  NS_QUERYATTR_PARTIALANSWER) != 0)
80
/*% Use Cache? */
Bob Halley's avatar
Bob Halley committed
81 82
#define USECACHE(c)		(((c)->query.attributes & \
				  NS_QUERYATTR_CACHEOK) != 0)
83
/*% Recursion OK? */
Bob Halley's avatar
Bob Halley committed
84 85
#define RECURSIONOK(c)		(((c)->query.attributes & \
				  NS_QUERYATTR_RECURSIONOK) != 0)
86
/*% Recursing? */
87 88
#define RECURSING(c)		(((c)->query.attributes & \
				  NS_QUERYATTR_RECURSING) != 0)
89
/*% Cache glue ok? */
Bob Halley's avatar
Bob Halley committed
90 91
#define CACHEGLUEOK(c)		(((c)->query.attributes & \
				  NS_QUERYATTR_CACHEGLUEOK) != 0)
92
/*% Want Recursion? */
93 94
#define WANTRECURSION(c)	(((c)->query.attributes & \
				  NS_QUERYATTR_WANTRECURSION) != 0)
95
/*% Want DNSSEC? */
96 97
#define WANTDNSSEC(c)		(((c)->attributes & \
				  NS_CLIENTATTR_WANTDNSSEC) != 0)
98
/*% No authority? */
99 100
#define NOAUTHORITY(c)		(((c)->query.attributes & \
				  NS_QUERYATTR_NOAUTHORITY) != 0)
101
/*% No additional? */
102 103
#define NOADDITIONAL(c)		(((c)->query.attributes & \
				  NS_QUERYATTR_NOADDITIONAL) != 0)
104
/*% Secure? */
105 106
#define SECURE(c)		(((c)->query.attributes & \
				  NS_QUERYATTR_SECURE) != 0)
107 108 109 110 111 112
/*% DNS64 A lookup? */
#define DNS64(c)		(((c)->query.attributes & \
				  NS_QUERYATTR_DNS64) != 0)

#define DNS64EXCLUDE(c)		(((c)->query.attributes & \
				  NS_QUERYATTR_DNS64EXCLUDE) != 0)
Bob Halley's avatar
Bob Halley committed
113

114 115 116 117
/*% No QNAME Proof? */
#define NOQNAME(r)		(((r)->attributes & \
				  DNS_RDATASETATTR_NOQNAME) != 0)

118 119 120 121
#if 0
#define CTRACE(m)       isc_log_write(ns_g_lctx, \
				      NS_LOGCATEGORY_CLIENT, \
				      NS_LOGMODULE_QUERY, \
Brian Wellington's avatar
Brian Wellington committed
122 123
				      ISC_LOG_DEBUG(3), \
				      "client %p: %s", client, (m))
124 125 126
#define QTRACE(m)       isc_log_write(ns_g_lctx, \
				      NS_LOGCATEGORY_GENERAL, \
				      NS_LOGMODULE_QUERY, \
Brian Wellington's avatar
Brian Wellington committed
127 128
				      ISC_LOG_DEBUG(3), \
				      "query %p: %s", query, (m))
129 130 131 132 133
#else
#define CTRACE(m) ((void)m)
#define QTRACE(m) ((void)m)
#endif

134 135
#define DNS_GETDB_NOEXACT 0x01U
#define DNS_GETDB_NOLOG 0x02U
136
#define DNS_GETDB_PARTIAL 0x04U
137

138 139
#define PENDINGOK(x)	(((x) & DNS_DBFIND_PENDINGOK) != 0)

140 141 142 143 144
typedef struct client_additionalctx {
	ns_client_t *client;
	dns_rdataset_t *rdataset;
} client_additionalctx_t;

145
static isc_result_t
146
query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype);
147

148 149 150 151
static isc_boolean_t
validate(ns_client_t *client, dns_db_t *db, dns_name_t *name,
	 dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);

152 153 154 155 156 157 158
static void
query_findclosestnsec3(dns_name_t *qname, dns_db_t *db,
		       dns_dbversion_t *version, ns_client_t *client,
		       dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
		       dns_name_t *fname, isc_boolean_t exact,
		       dns_name_t *found);

159 160 161
static inline void
log_queryerror(ns_client_t *client, isc_result_t result, int line, int level);

162
/*%
163 164
 * Increment query statistics counters.
 */
165
static inline void
166
inc_stats(ns_client_t *client, isc_statscounter_t counter) {
167
	dns_zone_t *zone = client->query.authzone;
168

169
	isc_stats_increment(ns_g_server->nsstats, counter);
170

171
	if (zone != NULL) {
172
		isc_stats_t *zonestats = dns_zone_getrequeststats(zone);
173
		if (zonestats != NULL)
174
			isc_stats_increment(zonestats, counter);
175 176
	}
}
177

178 179
static void
query_send(ns_client_t *client) {
180
	isc_statscounter_t counter;
181 182 183 184
	if ((client->message->flags & DNS_MESSAGEFLAG_AA) == 0)
		inc_stats(client, dns_nsstatscounter_nonauthans);
	else
		inc_stats(client, dns_nsstatscounter_authans);
185 186 187
	if (client->message->rcode == dns_rcode_noerror) {
		if (ISC_LIST_EMPTY(client->message->sections[DNS_SECTION_ANSWER])) {
			if (client->query.isreferral) {
188
				counter = dns_nsstatscounter_referral;
189
			} else {
190
				counter = dns_nsstatscounter_nxrrset;
191 192
			}
		} else {
193
			counter = dns_nsstatscounter_success;
194 195
		}
	} else if (client->message->rcode == dns_rcode_nxdomain) {
196
		counter = dns_nsstatscounter_nxdomain;
197 198
	} else {
		/* We end up here in case of YXDOMAIN, and maybe others */
199
		counter = dns_nsstatscounter_failure;
200 201 202 203 204 205
	}
	inc_stats(client, counter);
	ns_client_send(client);
}

static void
206 207 208
query_error(ns_client_t *client, isc_result_t result, int line) {
	int loglevel = ISC_LOG_DEBUG(3);

209 210
	switch (result) {
	case DNS_R_SERVFAIL:
211
		loglevel = ISC_LOG_DEBUG(1);
212 213 214 215 216 217 218 219 220
		inc_stats(client, dns_nsstatscounter_servfail);
		break;
	case DNS_R_FORMERR:
		inc_stats(client, dns_nsstatscounter_formerr);
		break;
	default:
		inc_stats(client, dns_nsstatscounter_failure);
		break;
	}
221 222 223

	log_queryerror(client, result, line, loglevel);

224 225 226 227 228
	ns_client_error(client, result);
}

static void
query_next(ns_client_t *client, isc_result_t result) {
229
	if (result == DNS_R_DUPLICATE)
230
		inc_stats(client, dns_nsstatscounter_duplicate);
231
	else if (result == DNS_R_DROP)
232
		inc_stats(client, dns_nsstatscounter_dropped);
233
	else
234
		inc_stats(client, dns_nsstatscounter_failure);
235 236 237
	ns_client_next(client, result);
}

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
static inline void
query_freefreeversions(ns_client_t *client, isc_boolean_t everything) {
	ns_dbversion_t *dbversion, *dbversion_next;
	unsigned int i;

	for (dbversion = ISC_LIST_HEAD(client->query.freeversions), i = 0;
	     dbversion != NULL;
	     dbversion = dbversion_next, i++)
	{
		dbversion_next = ISC_LIST_NEXT(dbversion, link);
		/*
		 * If we're not freeing everything, we keep the first three
		 * dbversions structures around.
		 */
		if (i > 3 || everything) {
			ISC_LIST_UNLINK(client->query.freeversions, dbversion,
					link);
			isc_mem_put(client->mctx, dbversion,
				    sizeof(*dbversion));
		}
	}
}

261 262 263 264 265 266 267 268 269 270 271
void
ns_query_cancel(ns_client_t *client) {
	LOCK(&client->query.fetchlock);
	if (client->query.fetch != NULL) {
		dns_resolver_cancelfetch(client->query.fetch);

		client->query.fetch = NULL;
	}
	UNLOCK(&client->query.fetchlock);
}

272 273 274 275 276 277 278 279 280 281 282 283 284
static inline void
query_putrdataset(ns_client_t *client, dns_rdataset_t **rdatasetp) {
	dns_rdataset_t *rdataset = *rdatasetp;

	CTRACE("query_putrdataset");
	if (rdataset != NULL) {
		if (dns_rdataset_isassociated(rdataset))
			dns_rdataset_disassociate(rdataset);
		dns_message_puttemprdataset(client->message, rdatasetp);
	}
	CTRACE("query_putrdataset: done");
}

Bob Halley's avatar
Bob Halley committed
285
static inline void
286
query_reset(ns_client_t *client, isc_boolean_t everything) {
287
	isc_buffer_t *dbuf, *dbuf_next;
288 289
	ns_dbversion_t *dbversion, *dbversion_next;

290
	/*%
291 292
	 * Reset the query state of a client to its default state.
	 */
293

294
	/*
295
	 * Cancel the fetch if it's running.
296
	 */
297
	ns_query_cancel(client);
298

299 300 301 302 303 304 305 306 307 308
	/*
	 * Cleanup any active versions.
	 */
	for (dbversion = ISC_LIST_HEAD(client->query.activeversions);
	     dbversion != NULL;
	     dbversion = dbversion_next) {
		dbversion_next = ISC_LIST_NEXT(dbversion, link);
		dns_db_closeversion(dbversion->db, &dbversion->version,
				    ISC_FALSE);
		dns_db_detach(&dbversion->db);
309
		ISC_LIST_INITANDAPPEND(client->query.freeversions,
310
				      dbversion, link);
311 312 313
	}
	ISC_LIST_INIT(client->query.activeversions);

Michael Graff's avatar
 
Michael Graff committed
314 315
	if (client->query.authdb != NULL)
		dns_db_detach(&client->query.authdb);
316 317
	if (client->query.authzone != NULL)
		dns_zone_detach(&client->query.authzone);
Michael Graff's avatar
 
Michael Graff committed
318

319 320 321 322 323 324 325 326 327 328 329 330
	if (client->query.dns64_aaaa != NULL)
		query_putrdataset(client, &client->query.dns64_aaaa);
	if (client->query.dns64_sigaaaa != NULL)
		query_putrdataset(client, &client->query.dns64_sigaaaa);
	if (client->query.dns64_aaaaok != NULL) {
		isc_mem_put(client->mctx, client->query.dns64_aaaaok,
			    client->query.dns64_aaaaoklen *
			    sizeof(isc_boolean_t));
		client->query.dns64_aaaaok =  NULL;
		client->query.dns64_aaaaoklen =  0;
	}

331
	query_freefreeversions(client, everything);
Bob Halley's avatar
Bob Halley committed
332 333 334 335 336 337 338

	for (dbuf = ISC_LIST_HEAD(client->query.namebufs);
	     dbuf != NULL;
	     dbuf = dbuf_next) {
		dbuf_next = ISC_LIST_NEXT(dbuf, link);
		if (dbuf_next != NULL || everything) {
			ISC_LIST_UNLINK(client->query.namebufs, dbuf, link);
339
			isc_buffer_free(&dbuf);
Bob Halley's avatar
Bob Halley committed
340 341
		}
	}
Bob Halley's avatar
Bob Halley committed
342

343 344 345 346 347 348 349 350
	if (client->query.restarts > 0) {
		/*
		 * client->query.qname was dynamically allocated.
		 */
		dns_message_puttempname(client->message,
					&client->query.qname);
	}
	client->query.qname = NULL;
351
	client->query.attributes = (NS_QUERYATTR_RECURSIONOK |
352 353
				    NS_QUERYATTR_CACHEOK |
				    NS_QUERYATTR_SECURE);
354
	client->query.restarts = 0;
355
	client->query.timerset = ISC_FALSE;
356
	client->query.origqname = NULL;
357
	client->query.dboptions = 0;
358
	client->query.fetchoptions = 0;
359
	client->query.gluedb = NULL;
360 361
	client->query.authdbset = ISC_FALSE;
	client->query.isreferral = ISC_FALSE;
362 363
	client->query.dns64_options = 0;
	client->query.dns64_ttl = ISC_UINT32_MAX;
Bob Halley's avatar
Bob Halley committed
364 365 366
}

static void
367
query_next_callback(ns_client_t *client) {
368
	query_reset(client, ISC_FALSE);
Bob Halley's avatar
Bob Halley committed
369 370
}

371 372 373
void
ns_query_free(ns_client_t *client) {
	query_reset(client, ISC_TRUE);
Bob Halley's avatar
Bob Halley committed
374 375 376 377
}

static inline isc_result_t
query_newnamebuf(ns_client_t *client) {
378
	isc_buffer_t *dbuf;
Bob Halley's avatar
Bob Halley committed
379 380
	isc_result_t result;

381
	CTRACE("query_newnamebuf");
382
	/*%
383 384 385
	 * Allocate a name buffer.
	 */

Bob Halley's avatar
Bob Halley committed
386
	dbuf = NULL;
387
	result = isc_buffer_allocate(client->mctx, &dbuf, 1024);
388 389
	if (result != ISC_R_SUCCESS) {
		CTRACE("query_newnamebuf: isc_buffer_allocate failed: done");
Bob Halley's avatar
Bob Halley committed
390
		return (result);
391
	}
Bob Halley's avatar
Bob Halley committed
392 393
	ISC_LIST_APPEND(client->query.namebufs, dbuf, link);

394
	CTRACE("query_newnamebuf: done");
Bob Halley's avatar
Bob Halley committed
395 396 397
	return (ISC_R_SUCCESS);
}

398
static inline isc_buffer_t *
Bob Halley's avatar
Bob Halley committed
399
query_getnamebuf(ns_client_t *client) {
400
	isc_buffer_t *dbuf;
Bob Halley's avatar
Bob Halley committed
401 402 403
	isc_result_t result;
	isc_region_t r;

404
	CTRACE("query_getnamebuf");
405
	/*%
406 407 408 409
	 * Return a name buffer with space for a maximal name, allocating
	 * a new one if necessary.
	 */

Bob Halley's avatar
Bob Halley committed
410 411
	if (ISC_LIST_EMPTY(client->query.namebufs)) {
		result = query_newnamebuf(client);
412 413
		if (result != ISC_R_SUCCESS) {
		    CTRACE("query_getnamebuf: query_newnamebuf failed: done");
Bob Halley's avatar
Bob Halley committed
414
			return (NULL);
415
		}
Bob Halley's avatar
Bob Halley committed
416 417 418 419
	}

	dbuf = ISC_LIST_TAIL(client->query.namebufs);
	INSIST(dbuf != NULL);
420
	isc_buffer_availableregion(dbuf, &r);
Bob Halley's avatar
Bob Halley committed
421 422
	if (r.length < 255) {
		result = query_newnamebuf(client);
423 424
		if (result != ISC_R_SUCCESS) {
		    CTRACE("query_getnamebuf: query_newnamebuf failed: done");
Bob Halley's avatar
Bob Halley committed
425
			return (NULL);
426 427

		}
Bob Halley's avatar
Bob Halley committed
428
		dbuf = ISC_LIST_TAIL(client->query.namebufs);
429
		isc_buffer_availableregion(dbuf, &r);
Bob Halley's avatar
Bob Halley committed
430 431
		INSIST(r.length >= 255);
	}
432
	CTRACE("query_getnamebuf: done");
Bob Halley's avatar
Bob Halley committed
433 434 435
	return (dbuf);
}

436
static inline void
437
query_keepname(ns_client_t *client, dns_name_t *name, isc_buffer_t *dbuf) {
438 439
	isc_region_t r;

440
	CTRACE("query_keepname");
441
	/*%
442 443 444 445 446 447 448
	 * 'name' is using space in 'dbuf', but 'dbuf' has not yet been
	 * adjusted to take account of that.  We do the adjustment.
	 */

	REQUIRE((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) != 0);

	dns_name_toregion(name, &r);
449
	isc_buffer_add(dbuf, r.length);
450 451 452 453 454 455 456 457
	dns_name_setbuffer(name, NULL);
	client->query.attributes &= ~NS_QUERYATTR_NAMEBUFUSED;
}

static inline void
query_releasename(ns_client_t *client, dns_name_t **namep) {
	dns_name_t *name = *namep;

458
	/*%
459 460 461 462 463
	 * 'name' is no longer needed.  Return it to our pool of temporary
	 * names.  If it is using a name buffer, relinquish its exclusive
	 * rights on the buffer.
	 */

464
	CTRACE("query_releasename");
465 466 467 468 469
	if (dns_name_hasbuffer(name)) {
		INSIST((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED)
		       != 0);
		client->query.attributes &= ~NS_QUERYATTR_NAMEBUFUSED;
	}
Bob Halley's avatar
Bob Halley committed
470
	dns_message_puttempname(client->message, namep);
471
	CTRACE("query_releasename: done");
472 473 474
}

static inline dns_name_t *
475
query_newname(ns_client_t *client, isc_buffer_t *dbuf,
476 477 478 479 480 481 482 483
	      isc_buffer_t *nbuf)
{
	dns_name_t *name;
	isc_region_t r;
	isc_result_t result;

	REQUIRE((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) == 0);

484
	CTRACE("query_newname");
Bob Halley's avatar
Bob Halley committed
485 486
	name = NULL;
	result = dns_message_gettempname(client->message, &name);
487 488
	if (result != ISC_R_SUCCESS) {
		CTRACE("query_newname: dns_message_gettempname failed: done");
Bob Halley's avatar
Bob Halley committed
489
		return (NULL);
490
	}
491 492
	isc_buffer_availableregion(dbuf, &r);
	isc_buffer_init(nbuf, r.base, r.length);
493 494 495 496
	dns_name_init(name, NULL);
	dns_name_setbuffer(name, nbuf);
	client->query.attributes |= NS_QUERYATTR_NAMEBUFUSED;

497
	CTRACE("query_newname: done");
498 499 500 501 502 503 504
	return (name);
}

static inline dns_rdataset_t *
query_newrdataset(ns_client_t *client) {
	dns_rdataset_t *rdataset;
	isc_result_t result;
505

506
	CTRACE("query_newrdataset");
Bob Halley's avatar
Bob Halley committed
507 508
	rdataset = NULL;
	result = dns_message_gettemprdataset(client->message, &rdataset);
509
	if (result != ISC_R_SUCCESS) {
David Lawrence's avatar
David Lawrence committed
510 511
	  CTRACE("query_newrdataset: "
		 "dns_message_gettemprdataset failed: done");
Bob Halley's avatar
Bob Halley committed
512
		return (NULL);
513
	}
514 515
	dns_rdataset_init(rdataset);

516
	CTRACE("query_newrdataset: done");
517 518 519
	return (rdataset);
}

520 521 522 523 524 525
static inline isc_result_t
query_newdbversion(ns_client_t *client, unsigned int n) {
	unsigned int i;
	ns_dbversion_t *dbversion;

	for (i = 0; i < n; i++) {
Andreas Gustafsson's avatar
Andreas Gustafsson committed
526
		dbversion = isc_mem_get(client->mctx, sizeof(*dbversion));
527 528 529
		if (dbversion != NULL) {
			dbversion->db = NULL;
			dbversion->version = NULL;
530
			ISC_LIST_INITANDAPPEND(client->query.freeversions,
531
					      dbversion, link);
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
		} else {
			/*
			 * We only return ISC_R_NOMEMORY if we couldn't
			 * allocate anything.
			 */
			if (i == 0)
				return (ISC_R_NOMEMORY);
			else
				return (ISC_R_SUCCESS);
		}
	}

	return (ISC_R_SUCCESS);
}

static inline ns_dbversion_t *
query_getdbversion(ns_client_t *client) {
	isc_result_t result;
	ns_dbversion_t *dbversion;

	if (ISC_LIST_EMPTY(client->query.freeversions)) {
		result = query_newdbversion(client, 1);
		if (result != ISC_R_SUCCESS)
			return (NULL);
	}
	dbversion = ISC_LIST_HEAD(client->query.freeversions);
	INSIST(dbversion != NULL);
	ISC_LIST_UNLINK(client->query.freeversions, dbversion, link);
560

561 562 563
	return (dbversion);
}

Bob Halley's avatar
Bob Halley committed
564 565
isc_result_t
ns_query_init(ns_client_t *client) {
566 567
	isc_result_t result;

Bob Halley's avatar
Bob Halley committed
568
	ISC_LIST_INIT(client->query.namebufs);
569 570
	ISC_LIST_INIT(client->query.activeversions);
	ISC_LIST_INIT(client->query.freeversions);
571
	client->query.restarts = 0;
572
	client->query.timerset = ISC_FALSE;
573
	client->query.qname = NULL;
574 575 576
	result = isc_mutex_init(&client->query.fetchlock);
	if (result != ISC_R_SUCCESS)
		return (result);
577
	client->query.fetch = NULL;
Michael Graff's avatar
 
Michael Graff committed
578
	client->query.authdb = NULL;
579 580
	client->query.authzone = NULL;
	client->query.authdbset = ISC_FALSE;
581
	client->query.isreferral = ISC_FALSE;
582 583 584 585
	client->query.dns64_aaaa = NULL;
	client->query.dns64_sigaaaa = NULL;
	client->query.dns64_aaaaok = NULL;
	client->query.dns64_aaaaoklen = 0;
586
	query_reset(client, ISC_FALSE);
587
	result = query_newdbversion(client, 3);
588 589
	if (result != ISC_R_SUCCESS) {
		DESTROYLOCK(&client->query.fetchlock);
590
		return (result);
591
	}
Mark Andrews's avatar
Mark Andrews committed
592
	result = query_newnamebuf(client);
593 594 595
	if (result != ISC_R_SUCCESS)
		query_freefreeversions(client, ISC_TRUE);

Mark Andrews's avatar
Mark Andrews committed
596
	return (result);
Bob Halley's avatar
Bob Halley committed
597 598
}

599 600 601 602
static inline ns_dbversion_t *
query_findversion(ns_client_t *client, dns_db_t *db,
		  isc_boolean_t *newzonep)
{
603 604
	ns_dbversion_t *dbversion;

605
	/*%
606 607 608 609 610 611 612 613 614
	 * We may already have done a query related to this
	 * database.  If so, we must be sure to make subsequent
	 * queries from the same version.
	 */
	for (dbversion = ISC_LIST_HEAD(client->query.activeversions);
	     dbversion != NULL;
	     dbversion = ISC_LIST_NEXT(dbversion, link)) {
		if (dbversion->db == db)
			break;
Michael Graff's avatar
 
Michael Graff committed
615 616
	}

617 618 619 620 621 622 623 624 625 626
	if (dbversion == NULL) {
		/*
		 * This is a new zone for this query.  Add it to
		 * the active list.
		 */
		dbversion = query_getdbversion(client);
		if (dbversion == NULL)
			return (NULL);
		dns_db_attach(db, &dbversion->db);
		dns_db_currentversion(db, &dbversion->version);
627
		dbversion->queryok = ISC_FALSE;
628 629
		ISC_LIST_APPEND(client->query.activeversions,
				dbversion, link);
630 631 632
		*newzonep = ISC_TRUE;
	} else
		*newzonep = ISC_FALSE;
633

634 635 636 637
	return (dbversion);
}

static inline isc_result_t
638 639 640 641
query_validatezonedb(ns_client_t *client, dns_name_t *name,
		     dns_rdatatype_t qtype, unsigned int options,
		     dns_zone_t *zone, dns_db_t *db,
		     dns_dbversion_t **versionp)
642 643 644 645 646 647
{
	isc_result_t result;
	isc_boolean_t check_acl, new_zone;
	dns_acl_t *queryacl;
	ns_dbversion_t *dbversion;

648 649
	REQUIRE(zone != NULL);
	REQUIRE(db != NULL);
650

Michael Graff's avatar
 
Michael Graff committed
651 652
	/*
	 * This limits our searching to the zone where the first name
653
	 * (the query target) was looked for.  This prevents following
654
	 * CNAMES or DNAMES into other zones and prevents returning
655
	 * additional data from other zones.
Michael Graff's avatar
 
Michael Graff committed
656
	 */
657
	if (!client->view->additionalfromauth &&
658
	    client->query.authdbset &&
659 660
	    db != client->query.authdb)
		goto refuse;
Michael Graff's avatar
 
Michael Graff committed
661

662 663 664 665 666 667 668 669 670 671
	/*
	 * Non recursive query to a static-stub zone is prohibited; its
	 * zone content is not public data, but a part of local configuration
	 * and should not be disclosed.
	 */
	if (dns_zone_gettype(zone) == dns_zone_staticstub &&
	    !RECURSIONOK(client)) {
		goto refuse;
	}

672
	/*
673
	 * If the zone has an ACL, we'll check it, otherwise
674 675 676
	 * we use the view's "allow-query" ACL.  Each ACL is only checked
	 * once per query.
	 *
677
	 * Also, get the database version to use.
678 679 680 681
	 */

	check_acl = ISC_TRUE;	/* Keep compiler happy. */
	queryacl = NULL;
682 683 684 685

	/*
	 * Get the current version of this database.
	 */
686 687 688 689 690
	dbversion = query_findversion(client, db, &new_zone);
	if (dbversion == NULL) {
		result = DNS_R_SERVFAIL;
		goto fail;
	}
691 692 693
	if (new_zone) {
		check_acl = ISC_TRUE;
	} else if (!dbversion->queryok) {
694
		goto refuse;
695 696 697
	} else {
		check_acl = ISC_FALSE;
	}
698

699
	queryacl = dns_zone_getqueryacl(zone);
700 701 702 703 704 705 706 707 708 709 710 711 712
	if (queryacl == NULL) {
		queryacl = client->view->queryacl;
		if ((client->query.attributes &
		     NS_QUERYATTR_QUERYOKVALID) != 0) {
			/*
			 * We've evaluated the view's queryacl already.  If
			 * NS_QUERYATTR_QUERYOK is set, then the client is
			 * allowed to make queries, otherwise the query should
			 * be refused.
			 */
			check_acl = ISC_FALSE;
			if ((client->query.attributes &
			     NS_QUERYATTR_QUERYOK) == 0)
713
				goto refuse;
714 715 716 717 718 719 720 721 722
		} else {
			/*
			 * We haven't evaluated the view's queryacl yet.
			 */
			check_acl = ISC_TRUE;
		}
	}

	if (check_acl) {
723
		isc_boolean_t log = ISC_TF((options & DNS_GETDB_NOLOG) == 0);
724

725 726
		result = ns_client_checkaclsilent(client, NULL, queryacl,
						  ISC_TRUE);
727
		if (log) {
728
			char msg[NS_CLIENT_ACLMSGSIZE("query")];
729 730 731 732
			if (result == ISC_R_SUCCESS) {
				if (isc_log_wouldlog(ns_g_lctx,
						     ISC_LOG_DEBUG(3)))
				{
733
					ns_client_aclmsg("query", name, qtype,
734 735 736 737 738 739 740 741
							 client->view->rdclass,
							 msg, sizeof(msg));
					ns_client_log(client,
						      DNS_LOGCATEGORY_SECURITY,
						      NS_LOGMODULE_QUERY,
						      ISC_LOG_DEBUG(3),
						      "%s approved", msg);
				}
742
			} else {
743
				ns_client_aclmsg("query", name, qtype,
744 745 746 747 748 749 750
						 client->view->rdclass,
						 msg, sizeof(msg));
				ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
					      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
					      "%s denied", msg);
			}
		}
751

752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
		if (queryacl == client->view->queryacl) {
			if (result == ISC_R_SUCCESS) {
				/*
				 * We were allowed by the default
				 * "allow-query" ACL.  Remember this so we
				 * don't have to check again.
				 */
				client->query.attributes |=
					NS_QUERYATTR_QUERYOK;
			}
			/*
			 * We've now evaluated the view's query ACL, and
			 * the NS_QUERYATTR_QUERYOK attribute is now valid.
			 */
			client->query.attributes |= NS_QUERYATTR_QUERYOKVALID;
Michael Graff's avatar
 
Michael Graff committed
767
		}
768 769 770 771 772 773

		if (result != ISC_R_SUCCESS)
			goto refuse;
	}

	/* Approved. */
774

775 776 777 778
	/*
	 * Remember the result of the ACL check so we
	 * don't have to check again.
	 */
779 780
	dbversion->queryok = ISC_TRUE;

781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
	/* Transfer ownership, if necessary. */
	if (versionp != NULL)
		*versionp = dbversion->version;

	return (ISC_R_SUCCESS);

 refuse:
	return (DNS_R_REFUSED);

 fail:
	return (result);
}

static inline isc_result_t
query_getzonedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype,
		unsigned int options, dns_zone_t **zonep, dns_db_t **dbp,
		dns_dbversion_t **versionp)
{
	isc_result_t result;
	unsigned int ztoptions;
	dns_zone_t *zone = NULL;
	dns_db_t *db = NULL;
	isc_boolean_t partial = ISC_FALSE;

	REQUIRE(zonep != NULL && *zonep == NULL);
	REQUIRE(dbp != NULL && *dbp == NULL);

808
	/*%
809 810 811 812 813 814 815 816 817 818 819 820
	 * Find a zone database to answer the query.
	 */
	ztoptions = ((options & DNS_GETDB_NOEXACT) != 0) ?
		DNS_ZTFIND_NOEXACT : 0;

	result = dns_zt_find(client->view->zonetable, name, ztoptions, NULL,
			     &zone);
	if (result == DNS_R_PARTIALMATCH)
		partial = ISC_TRUE;
	if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH)
		result = dns_zone_getdb(zone, &db);

821
	if (result != ISC_R_SUCCESS)
822 823 824 825 826 827 828 829
		goto fail;

	result = query_validatezonedb(client, name, qtype, options, zone, db,
				      versionp);

	if (result != ISC_R_SUCCESS)
		goto fail;

830 831 832 833
	/* Transfer ownership. */
	*zonep = zone;
	*dbp = db;

834 835
	if (partial && (options & DNS_GETDB_PARTIAL) != 0)
		return (DNS_R_PARTIALMATCH);
836 837 838 839 840 841 842
	return (ISC_R_SUCCESS);

 fail:
	if (zone != NULL)
		dns_zone_detach(&zone);
	if (db != NULL)
		dns_db_detach(&db);
Michael Graff's avatar
 
Michael Graff committed
843

844
	return (result);
845 846
}

847
static inline isc_result_t
848 849
query_getcachedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype,
		 dns_db_t **dbp, unsigned int options)
850 851 852
{
	isc_result_t result;
	isc_boolean_t check_acl;
853 854 855
	dns_db_t *db = NULL;

	REQUIRE(dbp != NULL && *dbp == NULL);
856

857
	/*%
858
	 * Find a cache database to answer the query.
859
	 * This may fail with DNS_R_REFUSED if the client
860 861 862 863 864
	 * is not allowed to use the cache.
	 */

	if (!USECACHE(client))
		return (DNS_R_REFUSED);
865
	dns_db_attach(client->view->cachedb, &db);
866

867
	if ((client->query.attributes & NS_QUERYATTR_CACHEACLOKVALID) != 0) {
868
		/*
869 870
		 * We've evaluated the view's cacheacl already.  If
		 * NS_QUERYATTR_CACHEACLOK is set, then the client is
871 872 873 874
		 * allowed to make queries, otherwise the query should
		 * be refused.
		 */
		check_acl = ISC_FALSE;
875
		if ((client->query.attributes & NS_QUERYATTR_CACHEACLOK) == 0)
876
			goto refuse;
877 878 879 880 881 882
	} else {
		/*
		 * We haven't evaluated the view's queryacl yet.
		 */
		check_acl = ISC_TRUE;
	}
883

884 885
	if (check_acl) {
		isc_boolean_t log = ISC_TF((options & DNS_GETDB_NOLOG) == 0);
886
		char msg[NS_CLIENT_ACLMSGSIZE("query (cache)")];
887

888
		result = ns_client_checkaclsilent(client, NULL,
889
						  client->view->cacheacl,
890
						  ISC_TRUE);
891 892
		if (result == ISC_R_SUCCESS) {
			/*
893 894
			 * We were allowed by the "allow-query-cache" ACL.
			 * Remember this so we don't have to check again.
895 896
			 */
			client->query.attributes |=
897
				NS_QUERYATTR_CACHEACLOK;
898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916
			if (log && isc_log_wouldlog(ns_g_lctx,
						     ISC_LOG_DEBUG(3)))
			{
				ns_client_aclmsg("query (cache)", name, qtype,
						 client->view->rdclass,
						 msg, sizeof(msg));
				ns_client_log(client,
					      DNS_LOGCATEGORY_SECURITY,
					      NS_LOGMODULE_QUERY,
					      ISC_LOG_DEBUG(3),
					      "%s approved", msg);
			}
		} else if (log) {
			ns_client_aclmsg("query (cache)", name, qtype,
					 client->view->rdclass, msg,
					 sizeof(msg));
			ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
				      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
				      "%s denied", msg);
917 918 919
		}
		/*
		 * We've now evaluated the view's query ACL, and
920
		 * the NS_QUERYATTR_CACHEACLOKVALID attribute is now valid.
921
		 */
922
		client->query.attributes |= NS_QUERYATTR_CACHEACLOKVALID;
923

924 925