/* $NetBSD: query.c,v 1.21 2024/04/19 12:35:28 christos Exp $ */ /* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * 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 https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ /*! \file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 /* if 0 */ /*% * Maximum number of chained queries before we give up * to prevent CNAME loops. */ #define MAX_RESTARTS 16 #define QUERY_ERROR(qctx, r) \ do { \ (qctx)->result = r; \ (qctx)->want_restart = false; \ (qctx)->line = __LINE__; \ } while (0) /*% Partial answer? */ #define PARTIALANSWER(c) \ (((c)->query.attributes & NS_QUERYATTR_PARTIALANSWER) != 0) /*% Use Cache? */ #define USECACHE(c) (((c)->query.attributes & NS_QUERYATTR_CACHEOK) != 0) /*% Recursion OK? */ #define RECURSIONOK(c) (((c)->query.attributes & NS_QUERYATTR_RECURSIONOK) != 0) /*% Recursing? */ #define RECURSING(c) (((c)->query.attributes & NS_QUERYATTR_RECURSING) != 0) /*% Want Recursion? */ #define WANTRECURSION(c) \ (((c)->query.attributes & NS_QUERYATTR_WANTRECURSION) != 0) /*% Is TCP? */ #define TCP(c) (((c)->attributes & NS_CLIENTATTR_TCP) != 0) /*% Want DNSSEC? */ #define WANTDNSSEC(c) (((c)->attributes & NS_CLIENTATTR_WANTDNSSEC) != 0) /*% Want WANTAD? */ #define WANTAD(c) (((c)->attributes & NS_CLIENTATTR_WANTAD) != 0) /*% Client presented a valid COOKIE. */ #define HAVECOOKIE(c) (((c)->attributes & NS_CLIENTATTR_HAVECOOKIE) != 0) /*% Client presented a COOKIE. */ #define WANTCOOKIE(c) (((c)->attributes & NS_CLIENTATTR_WANTCOOKIE) != 0) /*% Client presented a CLIENT-SUBNET option. */ #define HAVEECS(c) (((c)->attributes & NS_CLIENTATTR_HAVEECS) != 0) /*% No authority? */ #define NOAUTHORITY(c) (((c)->query.attributes & NS_QUERYATTR_NOAUTHORITY) != 0) /*% No additional? */ #define NOADDITIONAL(c) \ (((c)->query.attributes & NS_QUERYATTR_NOADDITIONAL) != 0) /*% Secure? */ #define SECURE(c) (((c)->query.attributes & NS_QUERYATTR_SECURE) != 0) /*% DNS64 A lookup? */ #define DNS64(c) (((c)->query.attributes & NS_QUERYATTR_DNS64) != 0) #define DNS64EXCLUDE(c) \ (((c)->query.attributes & NS_QUERYATTR_DNS64EXCLUDE) != 0) #define REDIRECT(c) (((c)->query.attributes & NS_QUERYATTR_REDIRECT) != 0) /*% Was the client already sent a response? */ #define QUERY_ANSWERED(q) (((q)->attributes & NS_QUERYATTR_ANSWERED) != 0) /*% Have we already processed an answer via stale-answer-client-timeout? */ #define QUERY_STALEPENDING(q) \ (((q)->attributes & NS_QUERYATTR_STALEPENDING) != 0) /*% Does the query allow stale data in the response? */ #define QUERY_STALEOK(q) (((q)->attributes & NS_QUERYATTR_STALEOK) != 0) /*% Does the query wants to check for stale RRset due to a timeout? */ #define QUERY_STALETIMEOUT(q) (((q)->dboptions & DNS_DBFIND_STALETIMEOUT) != 0) /*% Does the rdataset 'r' have an attached 'No QNAME Proof'? */ #define NOQNAME(r) (((r)->attributes & DNS_RDATASETATTR_NOQNAME) != 0) /*% Does the rdataset 'r' contain a stale answer? */ #define STALE(r) (((r)->attributes & DNS_RDATASETATTR_STALE) != 0) /*% Does the rdataset 'r' is stale and within stale-refresh-time? */ #define STALE_WINDOW(r) (((r)->attributes & DNS_RDATASETATTR_STALE_WINDOW) != 0) #ifdef WANT_QUERYTRACE static void client_trace(ns_client_t *client, int level, const char *message) { if (client != NULL && client->query.qname != NULL) { if (isc_log_wouldlog(ns_lctx, level)) { char qbuf[DNS_NAME_FORMATSIZE]; char tbuf[DNS_RDATATYPE_FORMATSIZE]; dns_name_format(client->query.qname, qbuf, sizeof(qbuf)); dns_rdatatype_format(client->query.qtype, tbuf, sizeof(tbuf)); isc_log_write(ns_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, level, "query client=%p thread=0x%" PRIxPTR "(%s/%s): %s", client, isc_thread_self(), qbuf, tbuf, message); } } else { isc_log_write(ns_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, level, "query client=%p thread=0x%" PRIxPTR "(): %s", client, isc_thread_self(), message); } } #define CTRACE(l, m) client_trace(client, l, m) #define CCTRACE(l, m) client_trace(qctx->client, l, m) #else /* ifdef WANT_QUERYTRACE */ #define CTRACE(l, m) ((void)m) #define CCTRACE(l, m) ((void)m) #endif /* WANT_QUERYTRACE */ #define DNS_GETDB_NOEXACT 0x01U #define DNS_GETDB_NOLOG 0x02U #define DNS_GETDB_PARTIAL 0x04U #define DNS_GETDB_IGNOREACL 0x08U #define DNS_GETDB_STALEFIRST 0X0CU #define PENDINGOK(x) (((x) & DNS_DBFIND_PENDINGOK) != 0) #define SFCACHE_CDFLAG 0x1 /* * SAVE and RESTORE have the same semantics as: * * foo_attach(b, &a); * foo_detach(&b); * * without the locking and magic testing. * * We use the names SAVE and RESTORE to show the operation being performed, * even though the two macros are identical. */ #define SAVE(a, b) \ do { \ INSIST(a == NULL); \ a = b; \ b = NULL; \ } while (0) #define RESTORE(a, b) SAVE(a, b) static bool validate(ns_client_t *client, dns_db_t *db, dns_name_t *name, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); 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, bool exact, dns_name_t *found); static void log_queryerror(ns_client_t *client, isc_result_t result, int line, int level); static void rpz_st_clear(ns_client_t *client); static bool rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); static void log_noexistnodata(void *val, int level, const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); static isc_result_t query_addanswer(query_ctx_t *qctx); static isc_result_t query_prepare_delegation_response(query_ctx_t *qctx); /* * Return the hooktable in use with 'qctx', or if there isn't one * set, return the default hooktable. */ static ns_hooktable_t * get_hooktab(query_ctx_t *qctx) { if (qctx == NULL || qctx->view == NULL || qctx->view->hooktable == NULL) { return (ns__hook_table); } return (qctx->view->hooktable); } /* * Call the specified hook function in every configured module that implements * that function. If any hook function returns NS_HOOK_RETURN, we * set 'result' and terminate processing by jumping to the 'cleanup' tag. * * (Note that a hook function may set the 'result' to ISC_R_SUCCESS but * still terminate processing within the calling function. That's why this * is a macro instead of a static function; it needs to be able to use * 'goto cleanup' regardless of the return value.) */ #define CALL_HOOK(_id, _qctx) \ do { \ isc_result_t _res = result; \ ns_hooktable_t *_tab = get_hooktab(_qctx); \ ns_hook_t *_hook; \ _hook = ISC_LIST_HEAD((*_tab)[_id]); \ while (_hook != NULL) { \ ns_hook_action_t _func = _hook->action; \ void *_data = _hook->action_data; \ INSIST(_func != NULL); \ switch (_func(_qctx, _data, &_res)) { \ case NS_HOOK_CONTINUE: \ _hook = ISC_LIST_NEXT(_hook, link); \ break; \ case NS_HOOK_RETURN: \ result = _res; \ goto cleanup; \ default: \ UNREACHABLE(); \ } \ } \ } while (false) /* * Call the specified hook function in every configured module that * implements that function. All modules are called; hook function return * codes are ignored. This is intended for use with initialization and * destruction calls which *must* run in every configured module. * * (This could be implemented as a static void function, but is left as a * macro for symmetry with CALL_HOOK above.) */ #define CALL_HOOK_NORETURN(_id, _qctx) \ do { \ isc_result_t _res; \ ns_hooktable_t *_tab = get_hooktab(_qctx); \ ns_hook_t *_hook; \ _hook = ISC_LIST_HEAD((*_tab)[_id]); \ while (_hook != NULL) { \ ns_hook_action_t _func = _hook->action; \ void *_data = _hook->action_data; \ INSIST(_func != NULL); \ _func(_qctx, _data, &_res); \ _hook = ISC_LIST_NEXT(_hook, link); \ } \ } while (false) /* * The functions defined below implement the query logic that previously lived * in the single very complex function query_find(). The query_ctx_t structure * defined in maintains state from function to function. The call * flow for the general query processing algorithm is described below: * * 1. Set up query context and other resources for a client * query (query_setup()) * * 2. Start the search (ns__query_start()) * * 3. Identify authoritative data sources which may have an answer; * search them (query_lookup()). If an answer is found, go to 7. * * 4. If recursion or cache access are allowed, search the cache * (query_lookup() again, using the cache database) to find a better * answer. If an answer is found, go to 7. * * 5. If recursion is allowed, begin recursion (ns_query_recurse()). * Go to 15 to clean up this phase of the query. When recursion * is complete, processing will resume at 6. * * 6. Resume from recursion; set up query context for resumed processing. * * 7. Determine what sort of answer we've found (query_gotanswer()) * and call other functions accordingly: * - not found (auth or cache), go to 8 * - delegation, go to 9 * - no such domain (auth), go to 10 * - empty answer (auth), go to 11 * - negative response (cache), go to 12 * - answer found, go to 13 * * 8. The answer was not found in the database (query_notfound(). * Set up a referral and go to 9. * * 9. Handle a delegation response (query_delegation()). If we need * to and are allowed to recurse (query_delegation_recurse()), go to 5, * otherwise go to 15 to clean up and return the delegation to the client. * * 10. No such domain (query_nxdomain()). Attempt redirection; if * unsuccessful, add authority section records (query_addsoa(), * query_addauth()), then go to 15 to return NXDOMAIN to client. * * 11. Empty answer (query_nodata()). Add authority section records * (query_addsoa(), query_addauth()) and signatures if authoritative * (query_sign_nodata()) then go to 15 and return * NOERROR/ANCOUNT=0 to client. * * 12. No such domain or empty answer returned from cache (query_ncache()). * Set response code appropriately, go to 11. * * 13. Prepare a response (query_prepresponse()) and then fill it * appropriately (query_respond(), or for type ANY, * query_respond_any()). * * 14. If a restart is needed due to CNAME/DNAME chaining, go to 2. * * 15. Clean up resources. If recursing, stop and wait for the event * handler to be called back (step 6). If an answer is ready, * return it to the client. * * (XXX: This description omits several special cases including * DNS64, RPZ, RRL, and the SERVFAIL cache. It also doesn't discuss * plugins.) */ static void query_trace(query_ctx_t *qctx); static void qctx_init(ns_client_t *client, dns_fetchevent_t **eventp, dns_rdatatype_t qtype, query_ctx_t *qctx); static isc_result_t query_setup(ns_client_t *client, dns_rdatatype_t qtype); static isc_result_t query_lookup(query_ctx_t *qctx); static void fetch_callback(isc_task_t *task, isc_event_t *event); static void recparam_update(ns_query_recparam_t *param, dns_rdatatype_t qtype, const dns_name_t *qname, const dns_name_t *qdomain); static isc_result_t query_resume(query_ctx_t *qctx); static isc_result_t query_checkrrl(query_ctx_t *qctx, isc_result_t result); static isc_result_t query_checkrpz(query_ctx_t *qctx, isc_result_t result); static isc_result_t query_rpzcname(query_ctx_t *qctx, dns_name_t *cname); static isc_result_t query_gotanswer(query_ctx_t *qctx, isc_result_t result); static void query_addnoqnameproof(query_ctx_t *qctx); static isc_result_t query_respond_any(query_ctx_t *qctx); static isc_result_t query_respond(query_ctx_t *qctx); static isc_result_t query_dns64(query_ctx_t *qctx); static void query_filter64(query_ctx_t *qctx); static isc_result_t query_notfound(query_ctx_t *qctx); static isc_result_t query_zone_delegation(query_ctx_t *qctx); static isc_result_t query_delegation(query_ctx_t *qctx); static isc_result_t query_delegation_recurse(query_ctx_t *qctx); static void query_addds(query_ctx_t *qctx); static isc_result_t query_nodata(query_ctx_t *qctx, isc_result_t result); static isc_result_t query_sign_nodata(query_ctx_t *qctx); static void query_addnxrrsetnsec(query_ctx_t *qctx); static isc_result_t query_nxdomain(query_ctx_t *qctx, isc_result_t result); static isc_result_t query_redirect(query_ctx_t *qctx, isc_result_t result); static isc_result_t query_ncache(query_ctx_t *qctx, isc_result_t result); static isc_result_t query_coveringnsec(query_ctx_t *qctx); static isc_result_t query_zerottl_refetch(query_ctx_t *qctx); static isc_result_t query_cname(query_ctx_t *qctx); static isc_result_t query_dname(query_ctx_t *qctx); static isc_result_t query_addcname(query_ctx_t *qctx, dns_trust_t trust, dns_ttl_t ttl); static isc_result_t query_prepresponse(query_ctx_t *qctx); static isc_result_t query_addsoa(query_ctx_t *qctx, unsigned int override_ttl, dns_section_t section); static isc_result_t query_addns(query_ctx_t *qctx); static void query_addbestns(query_ctx_t *qctx); static void query_addwildcardproof(query_ctx_t *qctx, bool ispositive, bool nodata); static void query_addauth(query_ctx_t *qctx); static void query_clear_stale(ns_client_t *client); /* * Increment query statistics counters. */ static void inc_stats(ns_client_t *client, isc_statscounter_t counter) { dns_zone_t *zone = client->query.authzone; dns_rdatatype_t qtype; dns_rdataset_t *rdataset; isc_stats_t *zonestats; dns_stats_t *querystats = NULL; ns_stats_increment(client->sctx->nsstats, counter); if (zone == NULL) { return; } /* Do regular response type stats */ zonestats = dns_zone_getrequeststats(zone); if (zonestats != NULL) { isc_stats_increment(zonestats, counter); } /* Do query type statistics * * We only increment per-type if we're using the authoritative * answer counter, preventing double-counting. */ if (counter == ns_statscounter_authans) { querystats = dns_zone_getrcvquerystats(zone); if (querystats != NULL) { rdataset = ISC_LIST_HEAD(client->query.qname->list); if (rdataset != NULL) { qtype = rdataset->type; dns_rdatatypestats_increment(querystats, qtype); } } } } static void query_send(ns_client_t *client) { isc_statscounter_t counter; if ((client->message->flags & DNS_MESSAGEFLAG_AA) == 0) { inc_stats(client, ns_statscounter_nonauthans); } else { inc_stats(client, ns_statscounter_authans); } if (client->message->rcode == dns_rcode_noerror) { dns_section_t answer = DNS_SECTION_ANSWER; if (ISC_LIST_EMPTY(client->message->sections[answer])) { if (client->query.isreferral) { counter = ns_statscounter_referral; } else { counter = ns_statscounter_nxrrset; } } else { counter = ns_statscounter_success; } } else if (client->message->rcode == dns_rcode_nxdomain) { counter = ns_statscounter_nxdomain; } else if (client->message->rcode == dns_rcode_badcookie) { counter = ns_statscounter_badcookie; } else { /* We end up here in case of YXDOMAIN, and maybe others */ counter = ns_statscounter_failure; } inc_stats(client, counter); ns_client_send(client); if (!client->nodetach) { isc_nmhandle_detach(&client->reqhandle); } } static void query_error(ns_client_t *client, isc_result_t result, int line) { int loglevel = ISC_LOG_DEBUG(3); switch (dns_result_torcode(result)) { case dns_rcode_servfail: loglevel = ISC_LOG_DEBUG(1); inc_stats(client, ns_statscounter_servfail); break; case dns_rcode_formerr: inc_stats(client, ns_statscounter_formerr); break; default: inc_stats(client, ns_statscounter_failure); break; } if ((client->sctx->options & NS_SERVER_LOGQUERIES) != 0) { loglevel = ISC_LOG_INFO; } log_queryerror(client, result, line, loglevel); ns_client_error(client, result); if (!client->nodetach) { isc_nmhandle_detach(&client->reqhandle); } } static void query_next(ns_client_t *client, isc_result_t result) { if (result == DNS_R_DUPLICATE) { inc_stats(client, ns_statscounter_duplicate); } else if (result == DNS_R_DROP) { inc_stats(client, ns_statscounter_dropped); } else { inc_stats(client, ns_statscounter_failure); } ns_client_drop(client, result); if (!client->nodetach) { isc_nmhandle_detach(&client->reqhandle); } } static void query_freefreeversions(ns_client_t *client, bool 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)); } } } void ns_query_cancel(ns_client_t *client) { REQUIRE(NS_CLIENT_VALID(client)); LOCK(&client->query.fetchlock); if (client->query.fetch != NULL) { dns_resolver_cancelfetch(client->query.fetch); client->query.fetch = NULL; } if (client->query.hookactx != NULL) { client->query.hookactx->cancel(client->query.hookactx); client->query.hookactx = NULL; } UNLOCK(&client->query.fetchlock); } static void query_reset(ns_client_t *client, bool everything) { isc_buffer_t *dbuf, *dbuf_next; ns_dbversion_t *dbversion, *dbversion_next; CTRACE(ISC_LOG_DEBUG(3), "query_reset"); /*% * Reset the query state of a client to its default state. */ /* * Cancel the fetch if it's running. */ ns_query_cancel(client); /* * 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, false); dns_db_detach(&dbversion->db); ISC_LIST_INITANDAPPEND(client->query.freeversions, dbversion, link); } ISC_LIST_INIT(client->query.activeversions); if (client->query.authdb != NULL) { dns_db_detach(&client->query.authdb); } if (client->query.authzone != NULL) { dns_zone_detach(&client->query.authzone); } if (client->query.dns64_aaaa != NULL) { ns_client_putrdataset(client, &client->query.dns64_aaaa); } if (client->query.dns64_sigaaaa != NULL) { ns_client_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(bool)); client->query.dns64_aaaaok = NULL; client->query.dns64_aaaaoklen = 0; } ns_client_putrdataset(client, &client->query.redirect.rdataset); ns_client_putrdataset(client, &client->query.redirect.sigrdataset); if (client->query.redirect.db != NULL) { if (client->query.redirect.node != NULL) { dns_db_detachnode(client->query.redirect.db, &client->query.redirect.node); } dns_db_detach(&client->query.redirect.db); } if (client->query.redirect.zone != NULL) { dns_zone_detach(&client->query.redirect.zone); } query_freefreeversions(client, everything); 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); isc_buffer_free(&dbuf); } } if (client->query.restarts > 0) { /* * client->query.qname was dynamically allocated. */ dns_message_puttempname(client->message, &client->query.qname); } client->query.qname = NULL; client->query.attributes = (NS_QUERYATTR_RECURSIONOK | NS_QUERYATTR_CACHEOK | NS_QUERYATTR_SECURE); client->query.restarts = 0; client->query.timerset = false; if (client->query.rpz_st != NULL) { rpz_st_clear(client); if (everything) { INSIST(client->query.rpz_st->rpsdb == NULL); isc_mem_put(client->mctx, client->query.rpz_st, sizeof(*client->query.rpz_st)); client->query.rpz_st = NULL; } } client->query.origqname = NULL; client->query.dboptions = 0; client->query.fetchoptions = 0; client->query.gluedb = NULL; client->query.authdbset = false; client->query.isreferral = false; client->query.dns64_options = 0; client->query.dns64_ttl = UINT32_MAX; recparam_update(&client->query.recparam, 0, NULL, NULL); client->query.root_key_sentinel_keyid = 0; client->query.root_key_sentinel_is_ta = false; client->query.root_key_sentinel_not_ta = false; } static void query_cleanup(ns_client_t *client) { query_reset(client, false); } void ns_query_free(ns_client_t *client) { REQUIRE(NS_CLIENT_VALID(client)); query_reset(client, true); } isc_result_t ns_query_init(ns_client_t *client) { isc_result_t result = ISC_R_SUCCESS; REQUIRE(NS_CLIENT_VALID(client)); ISC_LIST_INIT(client->query.namebufs); ISC_LIST_INIT(client->query.activeversions); ISC_LIST_INIT(client->query.freeversions); client->query.restarts = 0; client->query.timerset = false; client->query.rpz_st = NULL; client->query.qname = NULL; /* * This mutex is destroyed when the client is destroyed in * exit_check(). */ isc_mutex_init(&client->query.fetchlock); client->query.fetch = NULL; client->query.prefetch = NULL; client->query.authdb = NULL; client->query.authzone = NULL; client->query.authdbset = false; client->query.isreferral = false; client->query.dns64_aaaa = NULL; client->query.dns64_sigaaaa = NULL; client->query.dns64_aaaaok = NULL; client->query.dns64_aaaaoklen = 0; client->query.redirect.db = NULL; client->query.redirect.node = NULL; client->query.redirect.zone = NULL; client->query.redirect.qtype = dns_rdatatype_none; client->query.redirect.result = ISC_R_SUCCESS; client->query.redirect.rdataset = NULL; client->query.redirect.sigrdataset = NULL; client->query.redirect.authoritative = false; client->query.redirect.is_zone = false; client->query.redirect.fname = dns_fixedname_initname(&client->query.redirect.fixed); query_reset(client, false); ns_client_newdbversion(client, 3); ns_client_newnamebuf(client); return (result); } /*% * Check if 'client' is allowed to query the cache of its associated view. * Unless 'options' has DNS_GETDB_NOLOG set, log the result of cache ACL * evaluation using the appropriate level, along with 'name' and 'qtype'. * * The cache ACL is only evaluated once for each client and then the result is * cached: if NS_QUERYATTR_CACHEACLOKVALID is set in client->query.attributes, * cache ACL evaluation has already been performed. The evaluation result is * also stored in client->query.attributes: if NS_QUERYATTR_CACHEACLOK is set, * the client is allowed cache access. * * Returns: * *\li #ISC_R_SUCCESS 'client' is allowed to access cache *\li #DNS_R_REFUSED 'client' is not allowed to access cache */ static isc_result_t query_checkcacheaccess(ns_client_t *client, const dns_name_t *name, dns_rdatatype_t qtype, unsigned int options) { isc_result_t result; if ((client->query.attributes & NS_QUERYATTR_CACHEACLOKVALID) == 0) { enum refusal_reasons { ALLOW_QUERY_CACHE, ALLOW_QUERY_CACHE_ON }; static const char *acl_desc[] = { "allow-query-cache did not match", "allow-query-cache-on did not match", }; /* * The view's cache ACLs have not yet been evaluated. * Do it now. Both allow-query-cache and * allow-query-cache-on must be satisfied. */ bool log = ((options & DNS_GETDB_NOLOG) == 0); char msg[NS_CLIENT_ACLMSGSIZE("query (cache)")]; enum refusal_reasons refusal_reason = ALLOW_QUERY_CACHE; result = ns_client_checkaclsilent(client, NULL, client->view->cacheacl, true); if (result == ISC_R_SUCCESS) { refusal_reason = ALLOW_QUERY_CACHE_ON; result = ns_client_checkaclsilent( client, &client->destaddr, client->view->cacheonacl, true); } if (result == ISC_R_SUCCESS) { /* * We were allowed by the "allow-query-cache" ACL. */ client->query.attributes |= NS_QUERYATTR_CACHEACLOK; if (log && isc_log_wouldlog(ns_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 { /* * We were denied by the "allow-query-cache" ACL. * There is no need to clear NS_QUERYATTR_CACHEACLOK * since it is cleared by query_reset(), before query * processing starts. */ ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL); 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 (%s)", msg, acl_desc[refusal_reason]); } } /* * Evaluation has been finished; make sure we will just consult * NS_QUERYATTR_CACHEACLOK for this client from now on. */ client->query.attributes |= NS_QUERYATTR_CACHEACLOKVALID; } return ((client->query.attributes & NS_QUERYATTR_CACHEACLOK) != 0 ? ISC_R_SUCCESS : DNS_R_REFUSED); } static isc_result_t query_validatezonedb(ns_client_t *client, const dns_name_t *name, dns_rdatatype_t qtype, unsigned int options, dns_zone_t *zone, dns_db_t *db, dns_dbversion_t **versionp) { isc_result_t result; dns_acl_t *queryacl, *queryonacl; ns_dbversion_t *dbversion; REQUIRE(zone != NULL); REQUIRE(db != NULL); /* * Mirror zone data is treated as cache data. */ if (dns_zone_gettype(zone) == dns_zone_mirror) { return (query_checkcacheaccess(client, name, qtype, options)); } /* * This limits our searching to the zone where the first name * (the query target) was looked for. This prevents following * CNAMES or DNAMES into other zones and prevents returning * additional data from other zones. This does not apply if we're * answering a query where recursion is requested and allowed. */ if (client->query.rpz_st == NULL && !(WANTRECURSION(client) && RECURSIONOK(client)) && client->query.authdbset && db != client->query.authdb) { return (DNS_R_REFUSED); } /* * 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)) { return (DNS_R_REFUSED); } /* * If the zone has an ACL, we'll check it, otherwise * we use the view's "allow-query" ACL. Each ACL is only checked * once per query. * * Also, get the database version to use. */ /* * Get the current version of this database. */ dbversion = ns_client_findversion(client, db); if (dbversion == NULL) { CTRACE(ISC_LOG_ERROR, "unable to get db version"); return (DNS_R_SERVFAIL); } if ((options & DNS_GETDB_IGNOREACL) != 0) { goto approved; } if (dbversion->acl_checked) { if (!dbversion->queryok) { return (DNS_R_REFUSED); } goto approved; } queryacl = dns_zone_getqueryacl(zone); 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. */ dbversion->acl_checked = true; if ((client->query.attributes & NS_QUERYATTR_QUERYOK) == 0) { dbversion->queryok = false; return (DNS_R_REFUSED); } dbversion->queryok = true; goto approved; } } result = ns_client_checkaclsilent(client, NULL, queryacl, true); if ((options & DNS_GETDB_NOLOG) == 0) { char msg[NS_CLIENT_ACLMSGSIZE("query")]; if (result == ISC_R_SUCCESS) { if (isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(3))) { ns_client_aclmsg("query", 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 { pfilter_notify(result, client, "validatezonedb"); ns_client_aclmsg("query", name, qtype, client->view->rdclass, msg, sizeof(msg)); ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "%s denied", msg); ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL); } } 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; } /* If and only if we've gotten this far, check allow-query-on too */ if (result == ISC_R_SUCCESS) { queryonacl = dns_zone_getqueryonacl(zone); if (queryonacl == NULL) { queryonacl = client->view->queryonacl; } result = ns_client_checkaclsilent(client, &client->destaddr, queryonacl, true); if (result != ISC_R_SUCCESS) { ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL); } if ((options & DNS_GETDB_NOLOG) == 0 && result != ISC_R_SUCCESS) { ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "query-on denied"); } } dbversion->acl_checked = true; if (result != ISC_R_SUCCESS) { dbversion->queryok = false; return (DNS_R_REFUSED); } dbversion->queryok = true; approved: /* Transfer ownership, if necessary. */ if (versionp != NULL) { *versionp = dbversion->version; } return (ISC_R_SUCCESS); } static isc_result_t query_getzonedb(ns_client_t *client, const 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; bool partial = false; REQUIRE(zonep != NULL && *zonep == NULL); REQUIRE(dbp != NULL && *dbp == NULL); /*% * Find a zone database to answer the query. */ ztoptions = DNS_ZTFIND_MIRROR; if ((options & DNS_GETDB_NOEXACT) != 0) { ztoptions |= DNS_ZTFIND_NOEXACT; } result = dns_zt_find(client->view->zonetable, name, ztoptions, NULL, &zone); if (result == DNS_R_PARTIALMATCH) { partial = true; } if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { result = dns_zone_getdb(zone, &db); } if (result != ISC_R_SUCCESS) { goto fail; } result = query_validatezonedb(client, name, qtype, options, zone, db, versionp); if (result != ISC_R_SUCCESS) { goto fail; } /* Transfer ownership. */ *zonep = zone; *dbp = db; if (partial && (options & DNS_GETDB_PARTIAL) != 0) { return (DNS_R_PARTIALMATCH); } return (ISC_R_SUCCESS); fail: if (zone != NULL) { dns_zone_detach(&zone); } if (db != NULL) { dns_db_detach(&db); } return (result); } static void rpz_log_rewrite(ns_client_t *client, bool disabled, dns_rpz_policy_t policy, dns_rpz_type_t type, dns_zone_t *p_zone, dns_name_t *p_name, dns_name_t *cname, dns_rpz_num_t rpz_num) { char cname_buf[DNS_NAME_FORMATSIZE] = { 0 }; char p_name_buf[DNS_NAME_FORMATSIZE]; char qname_buf[DNS_NAME_FORMATSIZE]; char classbuf[DNS_RDATACLASS_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; const char *s1 = cname_buf, *s2 = cname_buf; dns_rdataset_t *rdataset; dns_rpz_st_t *st; isc_stats_t *zonestats; /* * Count enabled rewrites in the global counter. * Count both enabled and disabled rewrites for each zone. */ if (!disabled && policy != DNS_RPZ_POLICY_PASSTHRU) { ns_stats_increment(client->sctx->nsstats, ns_statscounter_rpz_rewrites); } if (p_zone != NULL) { zonestats = dns_zone_getrequeststats(p_zone); if (zonestats != NULL) { isc_stats_increment(zonestats, ns_statscounter_rpz_rewrites); } } if (!isc_log_wouldlog(ns_lctx, DNS_RPZ_INFO_LEVEL)) { return; } st = client->query.rpz_st; if ((st->popt.no_log & DNS_RPZ_ZBIT(rpz_num)) != 0) { return; } dns_name_format(client->query.qname, qname_buf, sizeof(qname_buf)); dns_name_format(p_name, p_name_buf, sizeof(p_name_buf)); if (cname != NULL) { s1 = " (CNAME to: "; dns_name_format(cname, cname_buf, sizeof(cname_buf)); s2 = ")"; } /* * Log Qclass and Qtype in addition to existing * fields. */ rdataset = ISC_LIST_HEAD(client->query.origqname->list); INSIST(rdataset != NULL); dns_rdataclass_format(rdataset->rdclass, classbuf, sizeof(classbuf)); dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); /* It's possible to have a separate log channel for rpz passthru. */ isc_logcategory_t *log_cat = (policy == DNS_RPZ_POLICY_PASSTHRU) ? DNS_LOGCATEGORY_RPZ_PASSTHRU : DNS_LOGCATEGORY_RPZ; ns_client_log(client, log_cat, NS_LOGMODULE_QUERY, DNS_RPZ_INFO_LEVEL, "%srpz %s %s rewrite %s/%s/%s via %s%s%s%s", disabled ? "disabled " : "", dns_rpz_type2str(type), dns_rpz_policy2str(policy), qname_buf, typebuf, classbuf, p_name_buf, s1, cname_buf, s2); } static void rpz_log_fail_helper(ns_client_t *client, int level, dns_name_t *p_name, dns_rpz_type_t rpz_type1, dns_rpz_type_t rpz_type2, const char *str, isc_result_t result) { char qnamebuf[DNS_NAME_FORMATSIZE]; char p_namebuf[DNS_NAME_FORMATSIZE]; const char *failed, *via, *slash, *str_blank; const char *rpztypestr1; const char *rpztypestr2; if (!isc_log_wouldlog(ns_lctx, level)) { return; } /* * bin/tests/system/rpz/tests.sh looks for "rpz.*failed" for problems. */ if (level <= DNS_RPZ_DEBUG_LEVEL1) { failed = " failed: "; } else { failed = ": "; } rpztypestr1 = dns_rpz_type2str(rpz_type1); if (rpz_type2 != DNS_RPZ_TYPE_BAD) { slash = "/"; rpztypestr2 = dns_rpz_type2str(rpz_type2); } else { slash = ""; rpztypestr2 = ""; } str_blank = (*str != ' ' && *str != '\0') ? " " : ""; dns_name_format(client->query.qname, qnamebuf, sizeof(qnamebuf)); if (p_name != NULL) { via = " via "; dns_name_format(p_name, p_namebuf, sizeof(p_namebuf)); } else { via = ""; p_namebuf[0] = '\0'; } ns_client_log(client, NS_LOGCATEGORY_QUERY_ERRORS, NS_LOGMODULE_QUERY, level, "rpz %s%s%s rewrite %s%s%s%s%s%s%s", rpztypestr1, slash, rpztypestr2, qnamebuf, via, p_namebuf, str_blank, str, failed, isc_result_totext(result)); } static void rpz_log_fail(ns_client_t *client, int level, dns_name_t *p_name, dns_rpz_type_t rpz_type, const char *str, isc_result_t result) { rpz_log_fail_helper(client, level, p_name, rpz_type, DNS_RPZ_TYPE_BAD, str, result); } /* * Get a policy rewrite zone database. */ static isc_result_t rpz_getdb(ns_client_t *client, dns_name_t *p_name, dns_rpz_type_t rpz_type, dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp) { char qnamebuf[DNS_NAME_FORMATSIZE]; char p_namebuf[DNS_NAME_FORMATSIZE]; dns_dbversion_t *rpz_version = NULL; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "rpz_getdb"); result = query_getzonedb(client, p_name, dns_rdatatype_any, DNS_GETDB_IGNOREACL, zonep, dbp, &rpz_version); if (result == ISC_R_SUCCESS) { dns_rpz_st_t *st = client->query.rpz_st; /* * It isn't meaningful to log this message when * logging is disabled for some policy zones. */ if (st->popt.no_log == 0 && isc_log_wouldlog(ns_lctx, DNS_RPZ_DEBUG_LEVEL2)) { dns_name_format(client->query.qname, qnamebuf, sizeof(qnamebuf)); dns_name_format(p_name, p_namebuf, sizeof(p_namebuf)); ns_client_log(client, DNS_LOGCATEGORY_RPZ, NS_LOGMODULE_QUERY, DNS_RPZ_DEBUG_LEVEL2, "try rpz %s rewrite %s via %s", dns_rpz_type2str(rpz_type), qnamebuf, p_namebuf); } *versionp = rpz_version; return (ISC_R_SUCCESS); } rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, "query_getzonedb()", result); return (result); } /*% * Find a cache database to answer the query. This may fail with DNS_R_REFUSED * if the client is not allowed to use the cache. */ static isc_result_t query_getcachedb(ns_client_t *client, const dns_name_t *name, dns_rdatatype_t qtype, dns_db_t **dbp, unsigned int options) { isc_result_t result; dns_db_t *db = NULL; REQUIRE(dbp != NULL && *dbp == NULL); if (!USECACHE(client)) { return (DNS_R_REFUSED); } dns_db_attach(client->view->cachedb, &db); result = query_checkcacheaccess(client, name, qtype, options); if (result != ISC_R_SUCCESS) { dns_db_detach(&db); } /* * If query_checkcacheaccess() succeeded, transfer ownership of 'db'. * Otherwise, 'db' will be NULL due to the dns_db_detach() call above. */ *dbp = db; return (result); } static isc_result_t query_getdb(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, bool *is_zonep) { isc_result_t result; isc_result_t tresult; unsigned int namelabels; unsigned int zonelabels; dns_zone_t *zone = NULL; REQUIRE(zonep != NULL && *zonep == NULL); /* Calculate how many labels are in name. */ namelabels = dns_name_countlabels(name); zonelabels = 0; /* Try to find name in bind's standard database. */ result = query_getzonedb(client, name, qtype, options, &zone, dbp, versionp); /* See how many labels are in the zone's name. */ if (result == ISC_R_SUCCESS && zone != NULL) { zonelabels = dns_name_countlabels(dns_zone_getorigin(zone)); } /* * If # zone labels < # name labels, try to find an even better match * Only try if DLZ drivers are loaded for this view */ if (zonelabels < namelabels && !ISC_LIST_EMPTY(client->view->dlz_searched)) { dns_clientinfomethods_t cm; dns_clientinfo_t ci; dns_db_t *tdbp; dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); dns_clientinfo_setecs(&ci, &client->ecs); tdbp = NULL; tresult = dns_view_searchdlz(client->view, name, zonelabels, &cm, &ci, &tdbp); /* If we successful, we found a better match. */ if (tresult == ISC_R_SUCCESS) { ns_dbversion_t *dbversion; /* * If the previous search returned a zone, detach it. */ if (zone != NULL) { dns_zone_detach(&zone); } /* * If the previous search returned a database, * detach it. */ if (*dbp != NULL) { dns_db_detach(dbp); } /* * If the previous search returned a version, clear it. */ *versionp = NULL; dbversion = ns_client_findversion(client, tdbp); if (dbversion == NULL) { tresult = ISC_R_NOMEMORY; } else { /* * Be sure to return our database. */ *dbp = tdbp; *versionp = dbversion->version; } /* * We return a null zone, No stats for DLZ zones. */ zone = NULL; result = tresult; } } /* If successful, Transfer ownership of zone. */ if (result == ISC_R_SUCCESS) { *zonep = zone; /* * If neither attempt above succeeded, return the cache instead */ *is_zonep = true; } else { if (result == ISC_R_NOTFOUND) { result = query_getcachedb(client, name, qtype, dbp, options); } *is_zonep = false; } return (result); } static bool query_isduplicate(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type, dns_name_t **mnamep) { dns_section_t section; dns_name_t *mname = NULL; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "query_isduplicate"); for (section = DNS_SECTION_ANSWER; section <= DNS_SECTION_ADDITIONAL; section++) { result = dns_message_findname(client->message, section, name, type, 0, &mname, NULL); if (result == ISC_R_SUCCESS) { /* * We've already got this RRset in the response. */ CTRACE(ISC_LOG_DEBUG(3), "query_isduplicate: true: " "done"); return (true); } else if (result == DNS_R_NXRRSET) { /* * The name exists, but the rdataset does not. */ if (section == DNS_SECTION_ADDITIONAL) { break; } } else { RUNTIME_CHECK(result == DNS_R_NXDOMAIN); } mname = NULL; } if (mnamep != NULL) { *mnamep = mname; } CTRACE(ISC_LOG_DEBUG(3), "query_isduplicate: false: done"); return (false); } /* * Look up data for given 'name' and 'type' in given 'version' of 'db' for * 'client'. Called from query_additionalauth(). * * If the lookup is successful: * * - store the node containing the result at 'nodep', * * - store the owner name of the returned node in 'fname', * * - if 'type' is not ANY, dns_db_findext() will put the exact rdataset being * looked for in 'rdataset' and its signatures (if any) in 'sigrdataset', * * - if 'type' is ANY, dns_db_findext() will leave 'rdataset' and * 'sigrdataset' disassociated and the returned node will be iterated in * query_additional_cb(). * * If the lookup is not successful: * * - 'nodep' will not be written to, * - 'fname' may still be modified as it is passed to dns_db_findext(), * - 'rdataset' and 'sigrdataset' will remain disassociated. */ static isc_result_t query_additionalauthfind(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, dns_rdatatype_t type, ns_client_t *client, dns_dbnode_t **nodep, dns_name_t *fname, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { dns_clientinfomethods_t cm; dns_dbnode_t *node = NULL; dns_clientinfo_t ci; isc_result_t result; dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * Since we are looking for authoritative data, we do not set * the GLUEOK flag. Glue will be looked for later, but not * necessarily in the same database. */ result = dns_db_findext(db, name, version, type, client->query.dboptions, client->now, &node, fname, &cm, &ci, rdataset, sigrdataset); if (result != ISC_R_SUCCESS) { if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } /* * Do not return signatures if the zone is not fully signed. */ if (sigrdataset != NULL && !dns_db_issecure(db) && dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } *nodep = node; return (ISC_R_SUCCESS); } /* * For query context 'qctx', try finding authoritative additional data for * given 'name' and 'type'. Called from query_additional_cb(). * * If successful: * * - store pointers to the database and node which contain the result in * 'dbp' and 'nodep', respectively, * * - store the owner name of the returned node in 'fname', * * - potentially bind 'rdataset' and 'sigrdataset', as explained in the * comment for query_additionalauthfind(). * * If unsuccessful: * * - 'dbp' and 'nodep' will not be written to, * - 'fname' may still be modified as it is passed to dns_db_findext(), * - 'rdataset' and 'sigrdataset' will remain disassociated. */ static isc_result_t query_additionalauth(query_ctx_t *qctx, const dns_name_t *name, dns_rdatatype_t type, dns_db_t **dbp, dns_dbnode_t **nodep, dns_name_t *fname, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { ns_client_t *client = qctx->client; ns_dbversion_t *dbversion = NULL; dns_dbversion_t *version = NULL; dns_dbnode_t *node = NULL; dns_zone_t *zone = NULL; dns_db_t *db = NULL; isc_result_t result; /* * First, look within the same zone database for authoritative * additional data. */ if (!client->query.authdbset || client->query.authdb == NULL) { return (ISC_R_NOTFOUND); } dbversion = ns_client_findversion(client, client->query.authdb); if (dbversion == NULL) { return (ISC_R_NOTFOUND); } dns_db_attach(client->query.authdb, &db); version = dbversion->version; CTRACE(ISC_LOG_DEBUG(3), "query_additionalauth: same zone"); result = query_additionalauthfind(db, version, name, type, client, &node, fname, rdataset, sigrdataset); if (result != ISC_R_SUCCESS && qctx->view->minimalresponses == dns_minimal_no && RECURSIONOK(client)) { /* * If we aren't doing response minimization and recursion is * allowed, we can try and see if any other zone matches. */ version = NULL; dns_db_detach(&db); result = query_getzonedb(client, name, type, DNS_GETDB_NOLOG, &zone, &db, &version); if (result != ISC_R_SUCCESS) { return (result); } dns_zone_detach(&zone); CTRACE(ISC_LOG_DEBUG(3), "query_additionalauth: other zone"); result = query_additionalauthfind(db, version, name, type, client, &node, fname, rdataset, sigrdataset); } if (result != ISC_R_SUCCESS) { dns_db_detach(&db); } else { *nodep = node; node = NULL; *dbp = db; db = NULL; } return (result); } static isc_result_t query_additional_cb(void *arg, const dns_name_t *name, dns_rdatatype_t qtype, dns_rdataset_t *found) { query_ctx_t *qctx = arg; ns_client_t *client = qctx->client; isc_result_t result, eresult = ISC_R_SUCCESS; dns_dbnode_t *node = NULL; dns_db_t *db = NULL; dns_name_t *fname = NULL, *mname = NULL; dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; dns_rdataset_t *trdataset = NULL; isc_buffer_t *dbuf = NULL; isc_buffer_t b; ns_dbversion_t *dbversion = NULL; dns_dbversion_t *version = NULL; bool added_something = false, need_addname = false; dns_rdatatype_t type; dns_clientinfomethods_t cm; dns_clientinfo_t ci; dns_rdatasetadditional_t additionaltype = dns_rdatasetadditional_fromauth; REQUIRE(NS_CLIENT_VALID(client)); REQUIRE(qtype != dns_rdatatype_any); if (!WANTDNSSEC(client) && dns_rdatatype_isdnssec(qtype)) { return (ISC_R_SUCCESS); } CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb"); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * We treat type A additional section processing as if it * were "any address type" additional section processing. * To avoid multiple lookups, we do an 'any' database * lookup and iterate over the node. */ if (qtype == dns_rdatatype_a) { type = dns_rdatatype_any; } else { type = qtype; } /* * Get some resources. */ dbuf = ns_client_getnamebuf(client); if (dbuf == NULL) { goto cleanup; } fname = ns_client_newname(client, dbuf, &b); rdataset = ns_client_newrdataset(client); if (fname == NULL || rdataset == NULL) { goto cleanup; } if (WANTDNSSEC(client)) { sigrdataset = ns_client_newrdataset(client); if (sigrdataset == NULL) { goto cleanup; } } /* * If we want only minimal responses and are here, then it must * be for glue. */ if (qctx->view->minimalresponses == dns_minimal_yes && client->query.qtype != dns_rdatatype_ns) { goto try_glue; } /* * First, look for authoritative additional data. */ result = query_additionalauth(qctx, name, type, &db, &node, fname, rdataset, sigrdataset); if (result == ISC_R_SUCCESS) { goto found; } /* * No authoritative data was found. The cache is our next best bet. */ if (!qctx->view->recursion) { goto try_glue; } additionaltype = dns_rdatasetadditional_fromcache; result = query_getcachedb(client, name, qtype, &db, DNS_GETDB_NOLOG); if (result != ISC_R_SUCCESS) { /* * Most likely the client isn't allowed to query the cache. */ goto try_glue; } /* * Attempt to validate glue. */ if (sigrdataset == NULL) { sigrdataset = ns_client_newrdataset(client); if (sigrdataset == NULL) { goto cleanup; } } version = NULL; result = dns_db_findext(db, name, version, type, client->query.dboptions | DNS_DBFIND_GLUEOK | DNS_DBFIND_ADDITIONALOK, client->now, &node, fname, &cm, &ci, rdataset, sigrdataset); dns_cache_updatestats(qctx->view->cache, result); if (!WANTDNSSEC(client)) { ns_client_putrdataset(client, &sigrdataset); } if (result == ISC_R_SUCCESS) { goto found; } if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } dns_db_detach(&db); try_glue: /* * No cached data was found. Glue is our last chance. * RFC1035 sayeth: * * NS records cause both the usual additional section * processing to locate a type A record, and, when used * in a referral, a special search of the zone in which * they reside for glue information. * * This is the "special search". Note that we must search * the zone where the NS record resides, not the zone it * points to, and that we only do the search in the delegation * case (identified by client->query.gluedb being set). */ if (client->query.gluedb == NULL) { goto cleanup; } /* * Don't poison caches using the bailiwick protection model. */ if (!dns_name_issubdomain(name, dns_db_origin(client->query.gluedb))) { goto cleanup; } dbversion = ns_client_findversion(client, client->query.gluedb); if (dbversion == NULL) { goto cleanup; } dns_db_attach(client->query.gluedb, &db); version = dbversion->version; additionaltype = dns_rdatasetadditional_fromglue; result = dns_db_findext(db, name, version, type, client->query.dboptions | DNS_DBFIND_GLUEOK, client->now, &node, fname, &cm, &ci, rdataset, sigrdataset); if (result != ISC_R_SUCCESS && result != DNS_R_ZONECUT && result != DNS_R_GLUE) { goto cleanup; } found: /* * We have found a potential additional data rdataset, or * at least a node to iterate over. */ ns_client_keepname(client, fname, dbuf); /* * Does the caller want the found rdataset? */ if (found != NULL && dns_rdataset_isassociated(rdataset)) { dns_rdataset_clone(rdataset, found); } /* * If we have an rdataset, add it to the additional data * section. */ mname = NULL; if (dns_rdataset_isassociated(rdataset) && !query_isduplicate(client, fname, type, &mname)) { if (mname != NULL) { INSIST(mname != fname); ns_client_releasename(client, &fname); fname = mname; } else { need_addname = true; } ISC_LIST_APPEND(fname->list, rdataset, link); trdataset = rdataset; rdataset = NULL; added_something = true; /* * Note: we only add SIGs if we've added the type they cover, * so we do not need to check if the SIG rdataset is already * in the response. */ if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { ISC_LIST_APPEND(fname->list, sigrdataset, link); sigrdataset = NULL; } } if (qtype == dns_rdatatype_a) { /* * We now go looking for A and AAAA records, along with * their signatures. * * XXXRTH This code could be more efficient. */ if (rdataset != NULL) { if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } } else { rdataset = ns_client_newrdataset(client); if (rdataset == NULL) { goto addname; } } if (sigrdataset != NULL) { if (dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } } else if (WANTDNSSEC(client)) { sigrdataset = ns_client_newrdataset(client); if (sigrdataset == NULL) { goto addname; } } if (query_isduplicate(client, fname, dns_rdatatype_a, NULL)) { goto aaaa_lookup; } result = dns_db_findrdataset(db, node, version, dns_rdatatype_a, 0, client->now, rdataset, sigrdataset); if (result == DNS_R_NCACHENXDOMAIN) { goto addname; } else if (result == DNS_R_NCACHENXRRSET) { dns_rdataset_disassociate(rdataset); if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } } else if (result == ISC_R_SUCCESS) { bool invalid = false; mname = NULL; if (additionaltype == dns_rdatasetadditional_fromcache && (DNS_TRUST_PENDING(rdataset->trust) || DNS_TRUST_GLUE(rdataset->trust))) { /* validate() may change rdataset->trust */ invalid = !validate(client, db, fname, rdataset, sigrdataset); } if (invalid && DNS_TRUST_PENDING(rdataset->trust)) { dns_rdataset_disassociate(rdataset); if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } } else if (!query_isduplicate(client, fname, dns_rdatatype_a, &mname)) { if (mname != fname) { if (mname != NULL) { ns_client_releasename(client, &fname); fname = mname; } else { need_addname = true; } } ISC_LIST_APPEND(fname->list, rdataset, link); added_something = true; if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { ISC_LIST_APPEND(fname->list, sigrdataset, link); sigrdataset = ns_client_newrdataset(client); } rdataset = ns_client_newrdataset(client); if (rdataset == NULL) { goto addname; } if (WANTDNSSEC(client) && sigrdataset == NULL) { goto addname; } } else { dns_rdataset_disassociate(rdataset); if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } } } aaaa_lookup: if (query_isduplicate(client, fname, dns_rdatatype_aaaa, NULL)) { goto addname; } result = dns_db_findrdataset(db, node, version, dns_rdatatype_aaaa, 0, client->now, rdataset, sigrdataset); if (result == DNS_R_NCACHENXDOMAIN) { goto addname; } else if (result == DNS_R_NCACHENXRRSET) { dns_rdataset_disassociate(rdataset); if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } } else if (result == ISC_R_SUCCESS) { bool invalid = false; mname = NULL; if (additionaltype == dns_rdatasetadditional_fromcache && (DNS_TRUST_PENDING(rdataset->trust) || DNS_TRUST_GLUE(rdataset->trust))) { /* validate() may change rdataset->trust */ invalid = !validate(client, db, fname, rdataset, sigrdataset); } if (invalid && DNS_TRUST_PENDING(rdataset->trust)) { dns_rdataset_disassociate(rdataset); if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } } else if (!query_isduplicate(client, fname, dns_rdatatype_aaaa, &mname)) { if (mname != fname) { if (mname != NULL) { ns_client_releasename(client, &fname); fname = mname; } else { need_addname = true; } } ISC_LIST_APPEND(fname->list, rdataset, link); added_something = true; if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { ISC_LIST_APPEND(fname->list, sigrdataset, link); sigrdataset = NULL; } rdataset = NULL; } } } addname: CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb: addname"); /* * If we haven't added anything, then we're done. */ if (!added_something) { goto cleanup; } /* * We may have added our rdatasets to an existing name, if so, then * need_addname will be false. Whether we used an existing name * or a new one, we must set fname to NULL to prevent cleanup. */ if (need_addname) { dns_message_addname(client->message, fname, DNS_SECTION_ADDITIONAL); } /* * In some cases, a record that has been added as additional * data may *also* trigger the addition of additional data. * This cannot go more than MAX_RESTARTS levels deep. */ if (trdataset != NULL && dns_rdatatype_followadditional(type)) { if (client->additionaldepth++ < MAX_RESTARTS) { eresult = dns_rdataset_additionaldata( trdataset, fname, query_additional_cb, qctx); } client->additionaldepth--; } /* * Don't release fname. */ fname = NULL; cleanup: CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb: cleanup"); ns_client_putrdataset(client, &rdataset); if (sigrdataset != NULL) { ns_client_putrdataset(client, &sigrdataset); } if (fname != NULL) { ns_client_releasename(client, &fname); } if (node != NULL) { dns_db_detachnode(db, &node); } if (db != NULL) { dns_db_detach(&db); } CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb: done"); return (eresult); } /* * Add 'rdataset' to 'name'. */ static void query_addtoname(dns_name_t *name, dns_rdataset_t *rdataset) { ISC_LIST_APPEND(name->list, rdataset, link); } /* * Set the ordering for 'rdataset'. */ static void query_setorder(query_ctx_t *qctx, dns_name_t *name, dns_rdataset_t *rdataset) { ns_client_t *client = qctx->client; dns_order_t *order = client->view->order; CTRACE(ISC_LOG_DEBUG(3), "query_setorder"); UNUSED(client); if (order != NULL) { rdataset->attributes |= dns_order_find( order, name, rdataset->type, rdataset->rdclass); } rdataset->attributes |= DNS_RDATASETATTR_LOADORDER; } /* * Handle glue and fetch any other needed additional data for 'rdataset'. */ static void query_additional(query_ctx_t *qctx, dns_name_t *name, dns_rdataset_t *rdataset) { ns_client_t *client = qctx->client; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "query_additional"); if (NOADDITIONAL(client)) { return; } /* * Try to process glue directly. */ if (qctx->view->use_glue_cache && (rdataset->type == dns_rdatatype_ns) && (client->query.gluedb != NULL) && dns_db_iszone(client->query.gluedb)) { ns_dbversion_t *dbversion; dbversion = ns_client_findversion(client, client->query.gluedb); if (dbversion == NULL) { goto regular; } result = dns_rdataset_addglue(rdataset, dbversion->version, client->message); if (result == ISC_R_SUCCESS) { return; } } regular: /* * Add other additional data if needed. * We don't care if dns_rdataset_additionaldata() fails. */ (void)dns_rdataset_additionaldata(rdataset, name, query_additional_cb, qctx); CTRACE(ISC_LOG_DEBUG(3), "query_additional: done"); } static void query_addrrset(query_ctx_t *qctx, dns_name_t **namep, dns_rdataset_t **rdatasetp, dns_rdataset_t **sigrdatasetp, isc_buffer_t *dbuf, dns_section_t section) { isc_result_t result; ns_client_t *client = qctx->client; dns_name_t *name = *namep, *mname = NULL; dns_rdataset_t *rdataset = *rdatasetp, *mrdataset = NULL; dns_rdataset_t *sigrdataset = NULL; CTRACE(ISC_LOG_DEBUG(3), "query_addrrset"); REQUIRE(name != NULL); if (sigrdatasetp != NULL) { sigrdataset = *sigrdatasetp; } /*% * To the current response for 'client', add the answer RRset * '*rdatasetp' and an optional signature set '*sigrdatasetp', with * owner name '*namep', to section 'section', unless they are * already there. Also add any pertinent additional data. * * If 'dbuf' is not NULL, then '*namep' is the name whose data is * stored in 'dbuf'. In this case, query_addrrset() guarantees that * when it returns the name will either have been kept or released. */ result = dns_message_findname(client->message, section, name, rdataset->type, rdataset->covers, &mname, &mrdataset); if (result == ISC_R_SUCCESS) { /* * We've already got an RRset of the given name and type. */ CTRACE(ISC_LOG_DEBUG(3), "query_addrrset: dns_message_findname " "succeeded: done"); if (dbuf != NULL) { ns_client_releasename(client, namep); } if ((rdataset->attributes & DNS_RDATASETATTR_REQUIRED) != 0) { mrdataset->attributes |= DNS_RDATASETATTR_REQUIRED; } if ((rdataset->attributes & DNS_RDATASETATTR_STALE_ADDED) != 0) { mrdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED; } return; } else if (result == DNS_R_NXDOMAIN) { /* * The name doesn't exist. */ if (dbuf != NULL) { ns_client_keepname(client, name, dbuf); } dns_message_addname(client->message, name, section); *namep = NULL; mname = name; } else { RUNTIME_CHECK(result == DNS_R_NXRRSET); if (dbuf != NULL) { ns_client_releasename(client, namep); } } if (rdataset->trust != dns_trust_secure && (section == DNS_SECTION_ANSWER || section == DNS_SECTION_AUTHORITY)) { client->query.attributes &= ~NS_QUERYATTR_SECURE; } /* * Update message name, set rdataset order, and do additional * section processing if needed. */ query_addtoname(mname, rdataset); query_setorder(qctx, mname, rdataset); query_additional(qctx, mname, rdataset); /* * Note: we only add SIGs if we've added the type they cover, so * we do not need to check if the SIG rdataset is already in the * response. */ *rdatasetp = NULL; if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { /* * We have a signature. Add it to the response. */ ISC_LIST_APPEND(mname->list, sigrdataset, link); *sigrdatasetp = NULL; } CTRACE(ISC_LOG_DEBUG(3), "query_addrrset: done"); } /* * Mark the RRsets as secure. Update the cache (db) to reflect the * change in trust level. */ static void mark_secure(ns_client_t *client, dns_db_t *db, dns_name_t *name, dns_rdata_rrsig_t *rrsig, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { isc_result_t result; dns_dbnode_t *node = NULL; dns_clientinfomethods_t cm; dns_clientinfo_t ci; isc_stdtime_t now; rdataset->trust = dns_trust_secure; sigrdataset->trust = dns_trust_secure; dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * Save the updated secure state. Ignore failures. */ result = dns_db_findnodeext(db, name, true, &cm, &ci, &node); if (result != ISC_R_SUCCESS) { return; } isc_stdtime_get(&now); dns_rdataset_trimttl(rdataset, sigrdataset, rrsig, now, client->view->acceptexpired); (void)dns_db_addrdataset(db, node, NULL, client->now, rdataset, 0, NULL); (void)dns_db_addrdataset(db, node, NULL, client->now, sigrdataset, 0, NULL); dns_db_detachnode(db, &node); } /* * Find the secure key that corresponds to rrsig. * Note: 'keyrdataset' maintains state between successive calls, * there may be multiple keys with the same keyid. * Return false if we have exhausted all the possible keys. */ static bool get_key(ns_client_t *client, dns_db_t *db, dns_rdata_rrsig_t *rrsig, dns_rdataset_t *keyrdataset, dst_key_t **keyp) { isc_result_t result; dns_dbnode_t *node = NULL; bool secure = false; dns_clientinfomethods_t cm; dns_clientinfo_t ci; dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); if (!dns_rdataset_isassociated(keyrdataset)) { result = dns_db_findnodeext(db, &rrsig->signer, false, &cm, &ci, &node); if (result != ISC_R_SUCCESS) { return (false); } result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_dnskey, 0, client->now, keyrdataset, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { return (false); } if (keyrdataset->trust != dns_trust_secure) { return (false); } result = dns_rdataset_first(keyrdataset); } else { result = dns_rdataset_next(keyrdataset); } for (; result == ISC_R_SUCCESS; result = dns_rdataset_next(keyrdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; isc_buffer_t b; dns_rdataset_current(keyrdataset, &rdata); isc_buffer_init(&b, rdata.data, rdata.length); isc_buffer_add(&b, rdata.length); result = dst_key_fromdns(&rrsig->signer, rdata.rdclass, &b, client->mctx, keyp); if (result != ISC_R_SUCCESS) { continue; } if (rrsig->algorithm == (dns_secalg_t)dst_key_alg(*keyp) && rrsig->keyid == (dns_keytag_t)dst_key_id(*keyp) && dst_key_iszonekey(*keyp)) { secure = true; break; } dst_key_free(keyp); } return (secure); } static bool verify(dst_key_t *key, dns_name_t *name, dns_rdataset_t *rdataset, dns_rdata_t *rdata, ns_client_t *client) { isc_result_t result; dns_fixedname_t fixed; bool ignore = false; dns_fixedname_init(&fixed); again: result = dns_dnssec_verify(name, rdataset, key, ignore, client->view->maxbits, client->mctx, rdata, NULL); if (result == DNS_R_SIGEXPIRED && client->view->acceptexpired) { ignore = true; goto again; } if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) { return (true); } return (false); } /* * Validate the rdataset if possible with available records. */ static bool validate(ns_client_t *client, dns_db_t *db, dns_name_t *name, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_rrsig_t rrsig; dst_key_t *key = NULL; dns_rdataset_t keyrdataset; if (sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)) { return (false); } for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(sigrdataset)) { dns_rdata_reset(&rdata); dns_rdataset_current(sigrdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (!dns_resolver_algorithm_supported(client->view->resolver, name, rrsig.algorithm)) { continue; } if (!dns_name_issubdomain(name, &rrsig.signer)) { continue; } dns_rdataset_init(&keyrdataset); do { if (!get_key(client, db, &rrsig, &keyrdataset, &key)) { break; } if (verify(key, name, rdataset, &rdata, client)) { dst_key_free(&key); dns_rdataset_disassociate(&keyrdataset); mark_secure(client, db, name, &rrsig, rdataset, sigrdataset); return (true); } dst_key_free(&key); } while (1); if (dns_rdataset_isassociated(&keyrdataset)) { dns_rdataset_disassociate(&keyrdataset); } } return (false); } static void fixrdataset(ns_client_t *client, dns_rdataset_t **rdataset) { if (*rdataset == NULL) { *rdataset = ns_client_newrdataset(client); } else if (dns_rdataset_isassociated(*rdataset)) { dns_rdataset_disassociate(*rdataset); } } static void fixfname(ns_client_t *client, dns_name_t **fname, isc_buffer_t **dbuf, isc_buffer_t *nbuf) { if (*fname == NULL) { *dbuf = ns_client_getnamebuf(client); if (*dbuf == NULL) { return; } *fname = ns_client_newname(client, *dbuf, nbuf); } } static void free_devent(ns_client_t *client, isc_event_t **eventp, dns_fetchevent_t **deventp) { dns_fetchevent_t *devent = *deventp; REQUIRE((void *)(*eventp) == (void *)(*deventp)); CTRACE(ISC_LOG_DEBUG(3), "free_devent"); if (devent->fetch != NULL) { dns_resolver_destroyfetch(&devent->fetch); } if (devent->node != NULL) { dns_db_detachnode(devent->db, &devent->node); } if (devent->db != NULL) { dns_db_detach(&devent->db); } if (devent->rdataset != NULL) { ns_client_putrdataset(client, &devent->rdataset); } if (devent->sigrdataset != NULL) { ns_client_putrdataset(client, &devent->sigrdataset); } /* * If the two pointers are the same then leave the setting of * (*deventp) to NULL to isc_event_free. */ if ((void *)eventp != (void *)deventp) { (*deventp) = NULL; } isc_event_free(eventp); } static void prefetch_done(isc_task_t *task, isc_event_t *event) { dns_fetchevent_t *devent = (dns_fetchevent_t *)event; ns_client_t *client; UNUSED(task); REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); client = devent->ev_arg; REQUIRE(NS_CLIENT_VALID(client)); REQUIRE(task == client->task); CTRACE(ISC_LOG_DEBUG(3), "prefetch_done"); LOCK(&client->query.fetchlock); if (client->query.prefetch != NULL) { INSIST(devent->fetch == client->query.prefetch); client->query.prefetch = NULL; } UNLOCK(&client->query.fetchlock); /* * We're done prefetching, detach from quota. */ if (client->recursionquota != NULL) { isc_quota_detach(&client->recursionquota); ns_stats_decrement(client->sctx->nsstats, ns_statscounter_recursclients); } free_devent(client, &event, &devent); isc_nmhandle_detach(&client->prefetchhandle); } static void query_prefetch(ns_client_t *client, dns_name_t *qname, dns_rdataset_t *rdataset) { isc_result_t result; isc_sockaddr_t *peeraddr; dns_rdataset_t *tmprdataset; unsigned int options; CTRACE(ISC_LOG_DEBUG(3), "query_prefetch"); if (client->query.prefetch != NULL || client->view->prefetch_trigger == 0U || rdataset->ttl > client->view->prefetch_trigger || (rdataset->attributes & DNS_RDATASETATTR_PREFETCH) == 0) { return; } if (client->recursionquota == NULL) { result = isc_quota_attach(&client->sctx->recursionquota, &client->recursionquota); switch (result) { case ISC_R_SUCCESS: ns_stats_increment(client->sctx->nsstats, ns_statscounter_recursclients); break; case ISC_R_SOFTQUOTA: isc_quota_detach(&client->recursionquota); FALLTHROUGH; default: return; } } tmprdataset = ns_client_newrdataset(client); if (tmprdataset == NULL) { return; } if (!TCP(client)) { peeraddr = &client->peeraddr; } else { peeraddr = NULL; } isc_nmhandle_attach(client->handle, &client->prefetchhandle); options = client->query.fetchoptions | DNS_FETCHOPT_PREFETCH; result = dns_resolver_createfetch( client->view->resolver, qname, rdataset->type, NULL, NULL, NULL, peeraddr, client->message->id, options, 0, NULL, client->task, prefetch_done, client, tmprdataset, NULL, &client->query.prefetch); if (result != ISC_R_SUCCESS) { ns_client_putrdataset(client, &tmprdataset); isc_nmhandle_detach(&client->prefetchhandle); } dns_rdataset_clearprefetch(rdataset); ns_stats_increment(client->sctx->nsstats, ns_statscounter_prefetch); } static void rpz_clean(dns_zone_t **zonep, dns_db_t **dbp, dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp) { if (nodep != NULL && *nodep != NULL) { REQUIRE(dbp != NULL && *dbp != NULL); dns_db_detachnode(*dbp, nodep); } if (dbp != NULL && *dbp != NULL) { dns_db_detach(dbp); } if (zonep != NULL && *zonep != NULL) { dns_zone_detach(zonep); } if (rdatasetp != NULL && *rdatasetp != NULL && dns_rdataset_isassociated(*rdatasetp)) { dns_rdataset_disassociate(*rdatasetp); } } static void rpz_match_clear(dns_rpz_st_t *st) { rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset); st->m.version = NULL; } static isc_result_t rpz_ready(ns_client_t *client, dns_rdataset_t **rdatasetp) { REQUIRE(rdatasetp != NULL); CTRACE(ISC_LOG_DEBUG(3), "rpz_ready"); if (*rdatasetp == NULL) { *rdatasetp = ns_client_newrdataset(client); if (*rdatasetp == NULL) { CTRACE(ISC_LOG_ERROR, "rpz_ready: " "ns_client_newrdataset failed"); return (DNS_R_SERVFAIL); } } else if (dns_rdataset_isassociated(*rdatasetp)) { dns_rdataset_disassociate(*rdatasetp); } return (ISC_R_SUCCESS); } static void rpz_st_clear(ns_client_t *client) { dns_rpz_st_t *st = client->query.rpz_st; CTRACE(ISC_LOG_DEBUG(3), "rpz_st_clear"); if (st->m.rdataset != NULL) { ns_client_putrdataset(client, &st->m.rdataset); } rpz_match_clear(st); rpz_clean(NULL, &st->r.db, NULL, NULL); if (st->r.ns_rdataset != NULL) { ns_client_putrdataset(client, &st->r.ns_rdataset); } if (st->r.r_rdataset != NULL) { ns_client_putrdataset(client, &st->r.r_rdataset); } rpz_clean(&st->q.zone, &st->q.db, &st->q.node, NULL); if (st->q.rdataset != NULL) { ns_client_putrdataset(client, &st->q.rdataset); } if (st->q.sigrdataset != NULL) { ns_client_putrdataset(client, &st->q.sigrdataset); } st->state = 0; st->m.type = DNS_RPZ_TYPE_BAD; st->m.policy = DNS_RPZ_POLICY_MISS; if (st->rpsdb != NULL) { dns_db_detach(&st->rpsdb); } } static dns_rpz_zbits_t rpz_get_zbits(ns_client_t *client, dns_rdatatype_t ip_type, dns_rpz_type_t rpz_type) { dns_rpz_st_t *st; dns_rpz_zbits_t zbits = 0; REQUIRE(client != NULL); REQUIRE(client->query.rpz_st != NULL); st = client->query.rpz_st; #ifdef USE_DNSRPS if (st->popt.dnsrps_enabled) { if (st->rpsdb == NULL || librpz->have_trig(dns_dnsrps_type2trig(rpz_type), ip_type == dns_rdatatype_aaaa, ((rpsdb_t *)st->rpsdb)->rsp)) { return (DNS_RPZ_ALL_ZBITS); } return (0); } #endif /* ifdef USE_DNSRPS */ switch (rpz_type) { case DNS_RPZ_TYPE_CLIENT_IP: zbits = st->have.client_ip; break; case DNS_RPZ_TYPE_QNAME: zbits = st->have.qname; break; case DNS_RPZ_TYPE_IP: if (ip_type == dns_rdatatype_a) { zbits = st->have.ipv4; } else if (ip_type == dns_rdatatype_aaaa) { zbits = st->have.ipv6; } else { zbits = st->have.ip; } break; case DNS_RPZ_TYPE_NSDNAME: zbits = st->have.nsdname; break; case DNS_RPZ_TYPE_NSIP: if (ip_type == dns_rdatatype_a) { zbits = st->have.nsipv4; } else if (ip_type == dns_rdatatype_aaaa) { zbits = st->have.nsipv6; } else { zbits = st->have.nsip; } break; default: UNREACHABLE(); } /* * Choose * the earliest configured policy zone (rpz->num) * QNAME over IP over NSDNAME over NSIP (rpz_type) * the smallest name, * the longest IP address prefix, * the lexically smallest address. */ if (st->m.policy != DNS_RPZ_POLICY_MISS) { if (st->m.type >= rpz_type) { zbits &= DNS_RPZ_ZMASK(st->m.rpz->num); } else { zbits &= DNS_RPZ_ZMASK(st->m.rpz->num) >> 1; } } /* * If the client wants recursion, allow only compatible policies. */ if (!RECURSIONOK(client)) { zbits &= st->popt.no_rd_ok; } return (zbits); } static void query_rpzfetch(ns_client_t *client, dns_name_t *qname, dns_rdatatype_t type) { isc_result_t result; isc_sockaddr_t *peeraddr; dns_rdataset_t *tmprdataset; unsigned int options; CTRACE(ISC_LOG_DEBUG(3), "query_rpzfetch"); if (client->query.prefetch != NULL) { return; } if (client->recursionquota == NULL) { result = isc_quota_attach(&client->sctx->recursionquota, &client->recursionquota); switch (result) { case ISC_R_SUCCESS: ns_stats_increment(client->sctx->nsstats, ns_statscounter_recursclients); break; case ISC_R_SOFTQUOTA: isc_quota_detach(&client->recursionquota); FALLTHROUGH; default: return; } } tmprdataset = ns_client_newrdataset(client); if (tmprdataset == NULL) { return; } if (!TCP(client)) { peeraddr = &client->peeraddr; } else { peeraddr = NULL; } options = client->query.fetchoptions; isc_nmhandle_attach(client->handle, &client->prefetchhandle); result = dns_resolver_createfetch( client->view->resolver, qname, type, NULL, NULL, NULL, peeraddr, client->message->id, options, 0, NULL, client->task, prefetch_done, client, tmprdataset, NULL, &client->query.prefetch); if (result != ISC_R_SUCCESS) { ns_client_putrdataset(client, &tmprdataset); isc_nmhandle_detach(&client->prefetchhandle); } } /* * Get an NS, A, or AAAA rrset related to the response for the client * to check the contents of that rrset for hits by eligible policy zones. */ static isc_result_t rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type, unsigned int options, dns_rpz_type_t rpz_type, dns_db_t **dbp, dns_dbversion_t *version, dns_rdataset_t **rdatasetp, bool resuming) { dns_rpz_st_t *st; bool is_zone; dns_dbnode_t *node; dns_fixedname_t fixed; dns_name_t *found; isc_result_t result; dns_clientinfomethods_t cm; dns_clientinfo_t ci; CTRACE(ISC_LOG_DEBUG(3), "rpz_rrset_find"); st = client->query.rpz_st; if ((st->state & DNS_RPZ_RECURSING) != 0) { INSIST(st->r.r_type == type); INSIST(dns_name_equal(name, st->r_name)); INSIST(*rdatasetp == NULL || !dns_rdataset_isassociated(*rdatasetp)); st->state &= ~DNS_RPZ_RECURSING; RESTORE(*dbp, st->r.db); if (*rdatasetp != NULL) { ns_client_putrdataset(client, rdatasetp); } RESTORE(*rdatasetp, st->r.r_rdataset); result = st->r.r_result; if (result == DNS_R_DELEGATION) { CTRACE(ISC_LOG_ERROR, "RPZ recursing"); rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, rpz_type, "rpz_rrset_find(1)", result); st->m.policy = DNS_RPZ_POLICY_ERROR; result = DNS_R_SERVFAIL; } return (result); } result = rpz_ready(client, rdatasetp); if (result != ISC_R_SUCCESS) { st->m.policy = DNS_RPZ_POLICY_ERROR; return (result); } if (*dbp != NULL) { is_zone = false; } else { dns_zone_t *zone; version = NULL; zone = NULL; result = query_getdb(client, name, type, 0, &zone, dbp, &version, &is_zone); if (result != ISC_R_SUCCESS) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, rpz_type, "rpz_rrset_find(2)", result); st->m.policy = DNS_RPZ_POLICY_ERROR; if (zone != NULL) { dns_zone_detach(&zone); } return (result); } if (zone != NULL) { dns_zone_detach(&zone); } } node = NULL; found = dns_fixedname_initname(&fixed); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); result = dns_db_findext(*dbp, name, version, type, options, client->now, &node, found, &cm, &ci, *rdatasetp, NULL); if (result == DNS_R_DELEGATION && is_zone && USECACHE(client)) { /* * Try the cache if we're authoritative for an * ancestor but not the domain itself. */ rpz_clean(NULL, dbp, &node, rdatasetp); version = NULL; dns_db_attach(client->view->cachedb, dbp); result = dns_db_findext(*dbp, name, version, type, 0, client->now, &node, found, &cm, &ci, *rdatasetp, NULL); } rpz_clean(NULL, dbp, &node, NULL); if (result == DNS_R_DELEGATION) { rpz_clean(NULL, NULL, NULL, rdatasetp); /* * Recurse for NS rrset or A or AAAA rrset for an NS. * Do not recurse for addresses for the query name. */ if (rpz_type == DNS_RPZ_TYPE_IP) { result = DNS_R_NXRRSET; } else if (!client->view->rpzs->p.nsip_wait_recurse || (!client->view->rpzs->p.nsdname_wait_recurse && rpz_type == DNS_RPZ_TYPE_NSDNAME)) { query_rpzfetch(client, name, type); result = DNS_R_NXRRSET; } else { dns_name_copy(name, st->r_name); result = ns_query_recurse(client, type, st->r_name, NULL, NULL, resuming); if (result == ISC_R_SUCCESS) { st->state |= DNS_RPZ_RECURSING; result = DNS_R_DELEGATION; } } } return (result); } /* * Compute a policy owner name, p_name, in a policy zone given the needed * policy type and the trigger name. */ static isc_result_t rpz_get_p_name(ns_client_t *client, dns_name_t *p_name, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, dns_name_t *trig_name) { dns_offsets_t prefix_offsets; dns_name_t prefix, *suffix; unsigned int first, labels; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "rpz_get_p_name"); /* * The policy owner name consists of a suffix depending on the type * and policy zone and a prefix that is the longest possible string * from the trigger name that keesp the resulting policy owner name * from being too long. */ switch (rpz_type) { case DNS_RPZ_TYPE_CLIENT_IP: suffix = &rpz->client_ip; break; case DNS_RPZ_TYPE_QNAME: suffix = &rpz->origin; break; case DNS_RPZ_TYPE_IP: suffix = &rpz->ip; break; case DNS_RPZ_TYPE_NSDNAME: suffix = &rpz->nsdname; break; case DNS_RPZ_TYPE_NSIP: suffix = &rpz->nsip; break; default: UNREACHABLE(); } /* * Start with relative version of the full trigger name, * and trim enough allow the addition of the suffix. */ dns_name_init(&prefix, prefix_offsets); labels = dns_name_countlabels(trig_name); first = 0; for (;;) { dns_name_getlabelsequence(trig_name, first, labels - first - 1, &prefix); result = dns_name_concatenate(&prefix, suffix, p_name, NULL); if (result == ISC_R_SUCCESS) { break; } INSIST(result == DNS_R_NAMETOOLONG); /* * Trim the trigger name until the combination is not too long. */ if (labels - first < 2) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, suffix, rpz_type, "concatenate()", result); return (ISC_R_FAILURE); } /* * Complain once about trimming the trigger name. */ if (first == 0) { rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, suffix, rpz_type, "concatenate()", result); } ++first; } return (ISC_R_SUCCESS); } /* * Look in policy zone rpz for a policy of rpz_type by p_name. * The self-name (usually the client qname or an NS name) is compared with * the target of a CNAME policy for the old style passthru encoding. * If found, the policy is recorded in *zonep, *dbp, *versionp, *nodep, * *rdatasetp, and *policyp. * The target DNS type, qtype, chooses the best rdataset for *rdatasetp. * The caller must decide if the found policy is most suitable, including * better than a previously found policy. * If it is best, the caller records it in client->query.rpz_st->m. */ static isc_result_t rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype, dns_name_t *p_name, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp, dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, dns_rpz_policy_t *policyp) { dns_fixedname_t foundf; dns_name_t *found; isc_result_t result; dns_clientinfomethods_t cm; dns_clientinfo_t ci; bool found_a = false; REQUIRE(nodep != NULL); CTRACE(ISC_LOG_DEBUG(3), "rpz_find_p"); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * Try to find either a CNAME or the type of record demanded by the * request from the policy zone. */ rpz_clean(zonep, dbp, nodep, rdatasetp); result = rpz_ready(client, rdatasetp); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_ERROR, "rpz_ready() failed"); return (DNS_R_SERVFAIL); } *versionp = NULL; result = rpz_getdb(client, p_name, rpz_type, zonep, dbp, versionp); if (result != ISC_R_SUCCESS) { return (DNS_R_NXDOMAIN); } found = dns_fixedname_initname(&foundf); result = dns_db_findext(*dbp, p_name, *versionp, dns_rdatatype_any, 0, client->now, nodep, found, &cm, &ci, *rdatasetp, NULL); /* * Choose the best rdataset if we found something. */ if (result == ISC_R_SUCCESS) { dns_rdatasetiter_t *rdsiter; rdsiter = NULL; result = dns_db_allrdatasets(*dbp, *nodep, *versionp, 0, 0, &rdsiter); if (result != ISC_R_SUCCESS) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, "allrdatasets()", result); CTRACE(ISC_LOG_ERROR, "rpz_find_p: allrdatasets failed"); return (DNS_R_SERVFAIL); } if (qtype == dns_rdatatype_aaaa && !ISC_LIST_EMPTY(client->view->dns64)) { for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(rdsiter)) { dns_rdatasetiter_current(rdsiter, *rdatasetp); if ((*rdatasetp)->type == dns_rdatatype_a) { found_a = true; } dns_rdataset_disassociate(*rdatasetp); } } for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(rdsiter)) { dns_rdatasetiter_current(rdsiter, *rdatasetp); if ((*rdatasetp)->type == dns_rdatatype_cname || (*rdatasetp)->type == qtype) { break; } dns_rdataset_disassociate(*rdatasetp); } dns_rdatasetiter_destroy(&rdsiter); if (result != ISC_R_SUCCESS) { if (result != ISC_R_NOMORE) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, "rdatasetiter", result); CTRACE(ISC_LOG_ERROR, "rpz_find_p: " "rdatasetiter failed"); return (DNS_R_SERVFAIL); } /* * Ask again to get the right DNS_R_DNAME/NXRRSET/... * result if there is neither a CNAME nor target type. */ if (dns_rdataset_isassociated(*rdatasetp)) { dns_rdataset_disassociate(*rdatasetp); } dns_db_detachnode(*dbp, nodep); if (qtype == dns_rdatatype_rrsig || qtype == dns_rdatatype_sig) { result = DNS_R_NXRRSET; } else { result = dns_db_findext(*dbp, p_name, *versionp, qtype, 0, client->now, nodep, found, &cm, &ci, *rdatasetp, NULL); } } } switch (result) { case ISC_R_SUCCESS: if ((*rdatasetp)->type != dns_rdatatype_cname) { *policyp = DNS_RPZ_POLICY_RECORD; } else { *policyp = dns_rpz_decode_cname(rpz, *rdatasetp, self_name); if ((*policyp == DNS_RPZ_POLICY_RECORD || *policyp == DNS_RPZ_POLICY_WILDCNAME) && qtype != dns_rdatatype_cname && qtype != dns_rdatatype_any) { return (DNS_R_CNAME); } } return (ISC_R_SUCCESS); case DNS_R_NXRRSET: if (found_a) { *policyp = DNS_RPZ_POLICY_DNS64; } else { *policyp = DNS_RPZ_POLICY_NODATA; } return (result); case DNS_R_DNAME: /* * DNAME policy RRs have very few if any uses that are not * better served with simple wildcards. Making them work would * require complications to get the number of labels matched * in the name or the found name to the main DNS_R_DNAME case * in query_dname(). The domain also does not appear in the * summary database at the right level, so this happens only * with a single policy zone when we have no summary database. * Treat it as a miss. */ case DNS_R_NXDOMAIN: case DNS_R_EMPTYNAME: return (DNS_R_NXDOMAIN); default: rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, "", result); CTRACE(ISC_LOG_ERROR, "rpz_find_p: unexpected result"); return (DNS_R_SERVFAIL); } } static void rpz_save_p(dns_rpz_st_t *st, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, dns_rpz_policy_t policy, dns_name_t *p_name, dns_rpz_prefix_t prefix, isc_result_t result, dns_zone_t **zonep, dns_db_t **dbp, dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, dns_dbversion_t *version) { dns_rdataset_t *trdataset = NULL; rpz_match_clear(st); st->m.rpz = rpz; st->m.type = rpz_type; st->m.policy = policy; dns_name_copy(p_name, st->p_name); st->m.prefix = prefix; st->m.result = result; SAVE(st->m.zone, *zonep); SAVE(st->m.db, *dbp); SAVE(st->m.node, *nodep); if (*rdatasetp != NULL && dns_rdataset_isassociated(*rdatasetp)) { /* * Save the replacement rdataset from the policy * and make the previous replacement rdataset scratch. */ SAVE(trdataset, st->m.rdataset); SAVE(st->m.rdataset, *rdatasetp); SAVE(*rdatasetp, trdataset); st->m.ttl = ISC_MIN(st->m.rdataset->ttl, rpz->max_policy_ttl); } else { st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT, rpz->max_policy_ttl); } SAVE(st->m.version, version); } #ifdef USE_DNSRPS /* * Check the results of a RPZ service interface lookup. * Stop after an error (<0) or not a hit on a disabled zone (0). * Continue after a hit on a disabled zone (>0). */ static int dnsrps_ck(librpz_emsg_t *emsg, ns_client_t *client, rpsdb_t *rpsdb, bool recursed) { isc_region_t region; librpz_domain_buf_t pname_buf; if (!librpz->rsp_result(emsg, &rpsdb->result, recursed, rpsdb->rsp)) { return (-1); } /* * Forget the state from before the IP address or domain check * if the lookup hit nothing. */ if (rpsdb->result.policy == LIBRPZ_POLICY_UNDEFINED || rpsdb->result.hit_id != rpsdb->hit_id || rpsdb->result.policy != LIBRPZ_POLICY_DISABLED) { if (!librpz->rsp_pop_discard(emsg, rpsdb->rsp)) { return (-1); } return (0); } /* * Log a hit on a disabled zone. * Forget the zone to not try it again, and restore the pre-hit state. */ if (!librpz->rsp_domain(emsg, &pname_buf, rpsdb->rsp)) { return (-1); } region.base = pname_buf.d; region.length = pname_buf.size; dns_name_fromregion(client->query.rpz_st->p_name, ®ion); rpz_log_rewrite(client, true, dns_dnsrps_2policy(rpsdb->result.zpolicy), dns_dnsrps_trig2type(rpsdb->result.trig), NULL, client->query.rpz_st->p_name, NULL, rpsdb->result.cznum); if (!librpz->rsp_forget_zone(emsg, rpsdb->result.cznum, rpsdb->rsp) || !librpz->rsp_pop(emsg, &rpsdb->result, rpsdb->rsp)) { return (-1); } return (1); } /* * Ready the shim database and rdataset for a DNSRPS hit. */ static bool dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st, dns_rdatatype_t qtype, dns_rdataset_t **p_rdatasetp, bool recursed) { rpsdb_t *rpsdb; librpz_domain_buf_t pname_buf; isc_region_t region; dns_zone_t *p_zone; dns_db_t *p_db; dns_dbnode_t *p_node; dns_rpz_policy_t policy; dns_fixedname_t foundf; dns_name_t *found; dns_rdatatype_t foundtype, searchtype; isc_result_t result; rpsdb = (rpsdb_t *)st->rpsdb; if (!librpz->rsp_result(emsg, &rpsdb->result, recursed, rpsdb->rsp)) { return (false); } if (rpsdb->result.policy == LIBRPZ_POLICY_UNDEFINED) { return (true); } /* * Give the fake or shim DNSRPS database its new origin. */ if (!librpz->rsp_soa(emsg, NULL, NULL, &rpsdb->origin_buf, &rpsdb->result, rpsdb->rsp)) { return (false); } region.base = rpsdb->origin_buf.d; region.length = rpsdb->origin_buf.size; dns_name_fromregion(&rpsdb->common.origin, ®ion); if (!librpz->rsp_domain(emsg, &pname_buf, rpsdb->rsp)) { return (false); } region.base = pname_buf.d; region.length = pname_buf.size; dns_name_fromregion(st->p_name, ®ion); p_zone = NULL; p_db = NULL; p_node = NULL; rpz_ready(client, p_rdatasetp); dns_db_attach(st->rpsdb, &p_db); policy = dns_dnsrps_2policy(rpsdb->result.policy); if (policy != DNS_RPZ_POLICY_RECORD) { result = ISC_R_SUCCESS; } else if (qtype == dns_rdatatype_rrsig) { /* * dns_find_db() refuses to look for and fail to * find dns_rdatatype_rrsig. */ result = DNS_R_NXRRSET; policy = DNS_RPZ_POLICY_NODATA; } else { /* * Get the next (and so first) RR from the policy node. * If it is a CNAME, then look for it regardless of the * query type. */ if (!librpz->rsp_rr(emsg, &foundtype, NULL, NULL, NULL, &rpsdb->result, rpsdb->qname->ndata, rpsdb->qname->length, rpsdb->rsp)) { return (false); } if (foundtype == dns_rdatatype_cname) { searchtype = dns_rdatatype_cname; } else { searchtype = qtype; } /* * Get the DNSPRS imitation rdataset. */ found = dns_fixedname_initname(&foundf); result = dns_db_find(p_db, st->p_name, NULL, searchtype, 0, 0, &p_node, found, *p_rdatasetp, NULL); if (result == ISC_R_SUCCESS) { if (searchtype == dns_rdatatype_cname && qtype != dns_rdatatype_cname) { result = DNS_R_CNAME; } } else if (result == DNS_R_NXRRSET) { policy = DNS_RPZ_POLICY_NODATA; } else { snprintf(emsg->c, sizeof(emsg->c), "dns_db_find(): %s", isc_result_totext(result)); return (false); } } rpz_save_p(st, client->view->rpzs->zones[rpsdb->result.cznum], dns_dnsrps_trig2type(rpsdb->result.trig), policy, st->p_name, 0, result, &p_zone, &p_db, &p_node, p_rdatasetp, NULL); rpz_clean(NULL, NULL, NULL, p_rdatasetp); return (true); } static isc_result_t dnsrps_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr, dns_rpz_type_t rpz_type, dns_rdataset_t **p_rdatasetp) { dns_rpz_st_t *st; rpsdb_t *rpsdb; librpz_trig_t trig = LIBRPZ_TRIG_CLIENT_IP; bool recursed = false; int res; librpz_emsg_t emsg; isc_result_t result; st = client->query.rpz_st; rpsdb = (rpsdb_t *)st->rpsdb; result = rpz_ready(client, p_rdatasetp); if (result != ISC_R_SUCCESS) { st->m.policy = DNS_RPZ_POLICY_ERROR; return (result); } switch (rpz_type) { case DNS_RPZ_TYPE_CLIENT_IP: trig = LIBRPZ_TRIG_CLIENT_IP; recursed = false; break; case DNS_RPZ_TYPE_IP: trig = LIBRPZ_TRIG_IP; recursed = true; break; case DNS_RPZ_TYPE_NSIP: trig = LIBRPZ_TRIG_NSIP; recursed = true; break; default: UNREACHABLE(); } do { if (!librpz->rsp_push(&emsg, rpsdb->rsp) || !librpz->ck_ip(&emsg, netaddr->family == AF_INET ? (const void *)&netaddr->type.in : (const void *)&netaddr->type.in6, netaddr->family, trig, ++rpsdb->hit_id, recursed, rpsdb->rsp) || (res = dnsrps_ck(&emsg, client, rpsdb, recursed)) < 0) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, NULL, rpz_type, emsg.c, DNS_R_SERVFAIL); st->m.policy = DNS_RPZ_POLICY_ERROR; return (DNS_R_SERVFAIL); } } while (res != 0); return (ISC_R_SUCCESS); } static isc_result_t dnsrps_rewrite_name(ns_client_t *client, dns_name_t *trig_name, bool recursed, dns_rpz_type_t rpz_type, dns_rdataset_t **p_rdatasetp) { dns_rpz_st_t *st; rpsdb_t *rpsdb; librpz_trig_t trig = LIBRPZ_TRIG_CLIENT_IP; isc_region_t r; int res; librpz_emsg_t emsg; isc_result_t result; st = client->query.rpz_st; rpsdb = (rpsdb_t *)st->rpsdb; result = rpz_ready(client, p_rdatasetp); if (result != ISC_R_SUCCESS) { st->m.policy = DNS_RPZ_POLICY_ERROR; return (result); } switch (rpz_type) { case DNS_RPZ_TYPE_QNAME: trig = LIBRPZ_TRIG_QNAME; break; case DNS_RPZ_TYPE_NSDNAME: trig = LIBRPZ_TRIG_NSDNAME; break; default: UNREACHABLE(); } dns_name_toregion(trig_name, &r); do { if (!librpz->rsp_push(&emsg, rpsdb->rsp) || !librpz->ck_domain(&emsg, r.base, r.length, trig, ++rpsdb->hit_id, recursed, rpsdb->rsp) || (res = dnsrps_ck(&emsg, client, rpsdb, recursed)) < 0) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, NULL, rpz_type, emsg.c, DNS_R_SERVFAIL); st->m.policy = DNS_RPZ_POLICY_ERROR; return (DNS_R_SERVFAIL); } } while (res != 0); return (ISC_R_SUCCESS); } #endif /* USE_DNSRPS */ /* * Check this address in every eligible policy zone. */ static isc_result_t rpz_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr, dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, dns_rpz_zbits_t zbits, dns_rdataset_t **p_rdatasetp) { dns_rpz_zones_t *rpzs; dns_rpz_st_t *st; dns_rpz_zone_t *rpz; dns_rpz_prefix_t prefix; dns_rpz_num_t rpz_num; dns_fixedname_t ip_namef, p_namef; dns_name_t *ip_name, *p_name; dns_zone_t *p_zone; dns_db_t *p_db; dns_dbversion_t *p_version; dns_dbnode_t *p_node; dns_rpz_policy_t policy; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip"); rpzs = client->view->rpzs; st = client->query.rpz_st; #ifdef USE_DNSRPS if (st->popt.dnsrps_enabled) { return (dnsrps_rewrite_ip(client, netaddr, rpz_type, p_rdatasetp)); } #endif /* ifdef USE_DNSRPS */ ip_name = dns_fixedname_initname(&ip_namef); p_zone = NULL; p_db = NULL; p_node = NULL; while (zbits != 0) { rpz_num = dns_rpz_find_ip(rpzs, rpz_type, zbits, netaddr, ip_name, &prefix); if (rpz_num == DNS_RPZ_INVALID_NUM) { break; } zbits &= (DNS_RPZ_ZMASK(rpz_num) >> 1); /* * Do not try applying policy zones that cannot replace a * previously found policy zone. * Stop looking if the next best choice cannot * replace what we already have. */ rpz = rpzs->zones[rpz_num]; if (st->m.policy != DNS_RPZ_POLICY_MISS) { if (st->m.rpz->num < rpz->num) { break; } if (st->m.rpz->num == rpz->num && (st->m.type < rpz_type || st->m.prefix > prefix)) { break; } } /* * Get the policy for a prefix at least as long * as the prefix of the entry we had before. */ p_name = dns_fixedname_initname(&p_namef); result = rpz_get_p_name(client, p_name, rpz, rpz_type, ip_name); if (result != ISC_R_SUCCESS) { continue; } result = rpz_find_p(client, ip_name, qtype, p_name, rpz, rpz_type, &p_zone, &p_db, &p_version, &p_node, p_rdatasetp, &policy); switch (result) { case DNS_R_NXDOMAIN: /* * Continue after a policy record that is missing * contrary to the summary data. The summary * data can out of date during races with and among * policy zone updates. */ CTRACE(ISC_LOG_ERROR, "rpz_rewrite_ip: mismatched " "summary data; " "continuing"); continue; case DNS_R_SERVFAIL: rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); st->m.policy = DNS_RPZ_POLICY_ERROR; return (DNS_R_SERVFAIL); default: /* * Forget this policy if it is not preferable * to the previously found policy. * If this policy is not good, then stop looking * because none of the later policy zones would work. * * With more than one applicable policy, prefer * the earliest configured policy, * client-IP over QNAME over IP over NSDNAME over NSIP, * the longest prefix * the lexically smallest address. * dns_rpz_find_ip() ensures st->m.rpz->num >= rpz->num. * We can compare new and current p_name because * both are of the same type and in the same zone. * The tests above eliminate other reasons to * reject this policy. If this policy can't work, * then neither can later zones. */ if (st->m.policy != DNS_RPZ_POLICY_MISS && rpz->num == st->m.rpz->num && (st->m.type == rpz_type && st->m.prefix == prefix && 0 > dns_name_rdatacompare(st->p_name, p_name))) { break; } /* * Stop checking after saving an enabled hit in this * policy zone. The radix tree in the policy zone * ensures that we found the longest match. */ if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip: " "rpz_save_p"); rpz_save_p(st, rpz, rpz_type, policy, p_name, prefix, result, &p_zone, &p_db, &p_node, p_rdatasetp, p_version); break; } /* * Log DNS_RPZ_POLICY_DISABLED zones * and try the next eligible policy zone. */ rpz_log_rewrite(client, true, policy, rpz_type, p_zone, p_name, NULL, rpz_num); } } rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); return (ISC_R_SUCCESS); } /* * Check the IP addresses in the A or AAAA rrsets for name against * all eligible rpz_type (IP or NSIP) response policy rewrite rules. */ static isc_result_t rpz_rewrite_ip_rrset(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, dns_rdatatype_t ip_type, dns_db_t **ip_dbp, dns_dbversion_t *ip_version, dns_rdataset_t **ip_rdatasetp, dns_rdataset_t **p_rdatasetp, bool resuming) { dns_rpz_zbits_t zbits; isc_netaddr_t netaddr; struct in_addr ina; struct in6_addr in6a; isc_result_t result; unsigned int options = client->query.dboptions | DNS_DBFIND_GLUEOK; bool done = false; CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip_rrset"); do { zbits = rpz_get_zbits(client, ip_type, rpz_type); if (zbits == 0) { return (ISC_R_SUCCESS); } /* * Get the A or AAAA rdataset. */ result = rpz_rrset_find(client, name, ip_type, options, rpz_type, ip_dbp, ip_version, ip_rdatasetp, resuming); switch (result) { case ISC_R_SUCCESS: case DNS_R_GLUE: case DNS_R_ZONECUT: break; case DNS_R_EMPTYNAME: case DNS_R_EMPTYWILD: case DNS_R_NXDOMAIN: case DNS_R_NCACHENXDOMAIN: case DNS_R_NXRRSET: case DNS_R_NCACHENXRRSET: case ISC_R_NOTFOUND: return (ISC_R_SUCCESS); case DNS_R_DELEGATION: case DNS_R_DUPLICATE: case DNS_R_DROP: return (result); case DNS_R_CNAME: case DNS_R_DNAME: rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, name, rpz_type, "NS address rewrite rrset", result); return (ISC_R_SUCCESS); default: if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR; rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, rpz_type, "NS address rewrite rrset", result); } CTRACE(ISC_LOG_ERROR, "rpz_rewrite_ip_rrset: unexpected " "result"); return (DNS_R_SERVFAIL); } /* * If we are processing glue setup for the next loop * otherwise we are done. */ if (result == DNS_R_GLUE) { options = client->query.dboptions; } else { options = client->query.dboptions | DNS_DBFIND_GLUEOK; done = true; } /* * Check all of the IP addresses in the rdataset. */ for (result = dns_rdataset_first(*ip_rdatasetp); result == ISC_R_SUCCESS; result = dns_rdataset_next(*ip_rdatasetp)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(*ip_rdatasetp, &rdata); switch (rdata.type) { case dns_rdatatype_a: INSIST(rdata.length == 4); memmove(&ina.s_addr, rdata.data, 4); isc_netaddr_fromin(&netaddr, &ina); break; case dns_rdatatype_aaaa: INSIST(rdata.length == 16); memmove(in6a.s6_addr, rdata.data, 16); isc_netaddr_fromin6(&netaddr, &in6a); break; default: continue; } result = rpz_rewrite_ip(client, &netaddr, qtype, rpz_type, zbits, p_rdatasetp); if (result != ISC_R_SUCCESS) { return (result); } } } while (!done && client->query.rpz_st->m.policy == DNS_RPZ_POLICY_MISS); return (ISC_R_SUCCESS); } /* * Look for IP addresses in A and AAAA rdatasets * that trigger all eligible IP or NSIP policy rules. */ static isc_result_t rpz_rewrite_ip_rrsets(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, dns_rdataset_t **ip_rdatasetp, bool resuming) { dns_rpz_st_t *st; dns_dbversion_t *ip_version; dns_db_t *ip_db; dns_rdataset_t *p_rdataset; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip_rrsets"); st = client->query.rpz_st; ip_version = NULL; ip_db = NULL; p_rdataset = NULL; if ((st->state & DNS_RPZ_DONE_IPv4) == 0 && (qtype == dns_rdatatype_a || qtype == dns_rdatatype_any || rpz_type == DNS_RPZ_TYPE_NSIP)) { /* * Rewrite based on an IPv4 address that will appear * in the ANSWER section or if we are checking IP addresses. */ result = rpz_rewrite_ip_rrset( client, name, qtype, rpz_type, dns_rdatatype_a, &ip_db, ip_version, ip_rdatasetp, &p_rdataset, resuming); if (result == ISC_R_SUCCESS) { st->state |= DNS_RPZ_DONE_IPv4; } } else { result = ISC_R_SUCCESS; } if (result == ISC_R_SUCCESS && (qtype == dns_rdatatype_aaaa || qtype == dns_rdatatype_any || rpz_type == DNS_RPZ_TYPE_NSIP)) { /* * Rewrite based on IPv6 addresses that will appear * in the ANSWER section or if we are checking IP addresses. */ result = rpz_rewrite_ip_rrset(client, name, qtype, rpz_type, dns_rdatatype_aaaa, &ip_db, ip_version, ip_rdatasetp, &p_rdataset, resuming); } if (ip_db != NULL) { dns_db_detach(&ip_db); } ns_client_putrdataset(client, &p_rdataset); return (result); } /* * Try to rewrite a request for a qtype rdataset based on the trigger name * trig_name and rpz_type (DNS_RPZ_TYPE_QNAME or DNS_RPZ_TYPE_NSDNAME). * Record the results including the replacement rdataset if any * in client->query.rpz_st. * *rdatasetp is a scratch rdataset. */ static isc_result_t rpz_rewrite_name(ns_client_t *client, dns_name_t *trig_name, dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, dns_rpz_zbits_t allowed_zbits, bool recursed, dns_rdataset_t **rdatasetp) { dns_rpz_zones_t *rpzs; dns_rpz_zone_t *rpz; dns_rpz_st_t *st; dns_fixedname_t p_namef; dns_name_t *p_name; dns_rpz_zbits_t zbits; dns_rpz_num_t rpz_num; dns_zone_t *p_zone; dns_db_t *p_db; dns_dbversion_t *p_version; dns_dbnode_t *p_node; dns_rpz_policy_t policy; isc_result_t result; #ifndef USE_DNSRPS UNUSED(recursed); #endif /* ifndef USE_DNSRPS */ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_name"); rpzs = client->view->rpzs; st = client->query.rpz_st; #ifdef USE_DNSRPS if (st->popt.dnsrps_enabled) { return (dnsrps_rewrite_name(client, trig_name, recursed, rpz_type, rdatasetp)); } #endif /* ifdef USE_DNSRPS */ zbits = rpz_get_zbits(client, qtype, rpz_type); zbits &= allowed_zbits; if (zbits == 0) { return (ISC_R_SUCCESS); } /* * Use the summary database to find the bit mask of policy zones * with policies for this trigger name. We do this even if there * is only one eligible policy zone so that wildcard triggers * are matched correctly, and not into their parent. */ zbits = dns_rpz_find_name(rpzs, rpz_type, zbits, trig_name); if (zbits == 0) { return (ISC_R_SUCCESS); } p_name = dns_fixedname_initname(&p_namef); p_zone = NULL; p_db = NULL; p_node = NULL; /* * Check the trigger name in every policy zone that the summary data * says has a hit for the trigger name. * Most of the time there are no eligible zones and the summary data * keeps us from getting this far. * We check the most eligible zone first and so usually check only * one policy zone. */ for (rpz_num = 0; zbits != 0; ++rpz_num, zbits >>= 1) { if ((zbits & 1) == 0) { continue; } /* * Do not check policy zones that cannot replace a previously * found policy. */ rpz = rpzs->zones[rpz_num]; if (st->m.policy != DNS_RPZ_POLICY_MISS) { if (st->m.rpz->num < rpz->num) { break; } if (st->m.rpz->num == rpz->num && st->m.type < rpz_type) { break; } } /* * Get the next policy zone's record for this trigger name. */ result = rpz_get_p_name(client, p_name, rpz, rpz_type, trig_name); if (result != ISC_R_SUCCESS) { continue; } result = rpz_find_p(client, trig_name, qtype, p_name, rpz, rpz_type, &p_zone, &p_db, &p_version, &p_node, rdatasetp, &policy); switch (result) { case DNS_R_NXDOMAIN: /* * Continue after a missing policy record * contrary to the summary data. The summary * data can out of date during races with and among * policy zone updates. */ CTRACE(ISC_LOG_ERROR, "rpz_rewrite_name: mismatched " "summary data; " "continuing"); continue; case DNS_R_SERVFAIL: rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); st->m.policy = DNS_RPZ_POLICY_ERROR; return (DNS_R_SERVFAIL); default: /* * With more than one applicable policy, prefer * the earliest configured policy, * client-IP over QNAME over IP over NSDNAME over NSIP, * and the smallest name. * We known st->m.rpz->num >= rpz->num and either * st->m.rpz->num > rpz->num or st->m.type >= rpz_type */ if (st->m.policy != DNS_RPZ_POLICY_MISS && rpz->num == st->m.rpz->num && (st->m.type < rpz_type || (st->m.type == rpz_type && 0 >= dns_name_compare(p_name, st->p_name)))) { continue; } if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_name: " "rpz_save_p"); rpz_save_p(st, rpz, rpz_type, policy, p_name, 0, result, &p_zone, &p_db, &p_node, rdatasetp, p_version); /* * After a hit, higher numbered policy zones * are irrelevant */ rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); return (ISC_R_SUCCESS); } /* * Log DNS_RPZ_POLICY_DISABLED zones * and try the next eligible policy zone. */ rpz_log_rewrite(client, true, policy, rpz_type, p_zone, p_name, NULL, rpz_num); break; } } rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); return (ISC_R_SUCCESS); } static void rpz_rewrite_ns_skip(ns_client_t *client, dns_name_t *nsname, isc_result_t result, int level, const char *str) { dns_rpz_st_t *st; CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ns_skip"); st = client->query.rpz_st; if (str != NULL) { rpz_log_fail_helper(client, level, nsname, DNS_RPZ_TYPE_NSIP, DNS_RPZ_TYPE_NSDNAME, str, result); } if (st->r.ns_rdataset != NULL && dns_rdataset_isassociated(st->r.ns_rdataset)) { dns_rdataset_disassociate(st->r.ns_rdataset); } st->r.label--; } /* * RPZ query result types */ typedef enum { qresult_type_done = 0, qresult_type_restart = 1, qresult_type_recurse = 2 } qresult_type_t; /* * Look for response policy zone QNAME, NSIP, and NSDNAME rewriting. */ static isc_result_t rpz_rewrite(ns_client_t *client, dns_rdatatype_t qtype, isc_result_t qresult, bool resuming, dns_rdataset_t *ordataset, dns_rdataset_t *osigset) { dns_rpz_zones_t *rpzs; dns_rpz_st_t *st; dns_rdataset_t *rdataset; dns_fixedname_t nsnamef; dns_name_t *nsname; qresult_type_t qresult_type; dns_rpz_zbits_t zbits; isc_result_t result = ISC_R_SUCCESS; dns_rpz_have_t have; dns_rpz_popt_t popt; int rpz_ver; unsigned int options; #ifdef USE_DNSRPS librpz_emsg_t emsg; #endif /* ifdef USE_DNSRPS */ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite"); rpzs = client->view->rpzs; st = client->query.rpz_st; if (rpzs == NULL) { return (ISC_R_NOTFOUND); } if (st != NULL && (st->state & DNS_RPZ_REWRITTEN) != 0) { return (DNS_R_DISALLOWED); } if (RECURSING(client)) { return (DNS_R_DISALLOWED); } RWLOCK(&rpzs->search_lock, isc_rwlocktype_read); if ((rpzs->p.num_zones == 0 && !rpzs->p.dnsrps_enabled) || (!RECURSIONOK(client) && rpzs->p.no_rd_ok == 0) || !rpz_ck_dnssec(client, qresult, ordataset, osigset)) { RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); return (DNS_R_DISALLOWED); } have = rpzs->have; popt = rpzs->p; rpz_ver = rpzs->rpz_ver; RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); #ifndef USE_DNSRPS INSIST(!popt.dnsrps_enabled); #endif /* ifndef USE_DNSRPS */ if (st == NULL) { st = isc_mem_get(client->mctx, sizeof(*st)); st->state = 0; st->rpsdb = NULL; } if (st->state == 0) { st->state |= DNS_RPZ_ACTIVE; memset(&st->m, 0, sizeof(st->m)); st->m.type = DNS_RPZ_TYPE_BAD; st->m.policy = DNS_RPZ_POLICY_MISS; st->m.ttl = ~0; memset(&st->r, 0, sizeof(st->r)); memset(&st->q, 0, sizeof(st->q)); st->p_name = dns_fixedname_initname(&st->_p_namef); st->r_name = dns_fixedname_initname(&st->_r_namef); st->fname = dns_fixedname_initname(&st->_fnamef); st->have = have; st->popt = popt; st->rpz_ver = rpz_ver; client->query.rpz_st = st; #ifdef USE_DNSRPS if (popt.dnsrps_enabled) { if (st->rpsdb != NULL) { dns_db_detach(&st->rpsdb); } result = dns_dnsrps_rewrite_init( &emsg, st, rpzs, client->query.qname, client->mctx, RECURSIONOK(client)); if (result != ISC_R_SUCCESS) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, NULL, DNS_RPZ_TYPE_QNAME, emsg.c, result); st->m.policy = DNS_RPZ_POLICY_ERROR; return (ISC_R_SUCCESS); } } #endif /* ifdef USE_DNSRPS */ } /* * There is nothing to rewrite if the main query failed. */ switch (qresult) { case ISC_R_SUCCESS: case DNS_R_GLUE: case DNS_R_ZONECUT: qresult_type = qresult_type_done; break; case DNS_R_EMPTYNAME: case DNS_R_NXRRSET: case DNS_R_NXDOMAIN: case DNS_R_EMPTYWILD: case DNS_R_NCACHENXDOMAIN: case DNS_R_NCACHENXRRSET: case DNS_R_COVERINGNSEC: case DNS_R_CNAME: case DNS_R_DNAME: qresult_type = qresult_type_restart; break; case DNS_R_DELEGATION: case ISC_R_NOTFOUND: /* * If recursion is on, do only tentative rewriting. * If recursion is off, this the normal and only time we * can rewrite. */ if (RECURSIONOK(client)) { qresult_type = qresult_type_recurse; } else { qresult_type = qresult_type_restart; } break; case ISC_R_FAILURE: case ISC_R_TIMEDOUT: case DNS_R_BROKENCHAIN: rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, NULL, DNS_RPZ_TYPE_QNAME, "stop on qresult in rpz_rewrite()", qresult); return (ISC_R_SUCCESS); default: rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, NULL, DNS_RPZ_TYPE_QNAME, "stop on unrecognized qresult in rpz_rewrite()", qresult); return (ISC_R_SUCCESS); } rdataset = NULL; if ((st->state & (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) != (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) { isc_netaddr_t netaddr; dns_rpz_zbits_t allowed; if (!st->popt.dnsrps_enabled && qresult_type == qresult_type_recurse) { /* * This request needs recursion that has not been done. * Get bits for the policy zones that do not need * to wait for the results of recursion. */ allowed = st->have.qname_skip_recurse; if (allowed == 0) { return (ISC_R_SUCCESS); } } else { allowed = DNS_RPZ_ALL_ZBITS; } /* * Check once for triggers for the client IP address. */ if ((st->state & DNS_RPZ_DONE_CLIENT_IP) == 0) { zbits = rpz_get_zbits(client, dns_rdatatype_none, DNS_RPZ_TYPE_CLIENT_IP); zbits &= allowed; if (zbits != 0) { isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); result = rpz_rewrite_ip(client, &netaddr, qtype, DNS_RPZ_TYPE_CLIENT_IP, zbits, &rdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } } } /* * Check triggers for the query name if this is the first time * for the current qname. * There is a first time for each name in a CNAME chain */ if ((st->state & DNS_RPZ_DONE_QNAME) == 0) { bool norec = (qresult_type != qresult_type_recurse); result = rpz_rewrite_name(client, client->query.qname, qtype, DNS_RPZ_TYPE_QNAME, allowed, norec, &rdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } /* * Check IPv4 addresses in A RRs next. * Reset to the start of the NS names. */ st->r.label = dns_name_countlabels(client->query.qname); st->state &= ~(DNS_RPZ_DONE_QNAME_IP | DNS_RPZ_DONE_IPv4); } /* * Quit if this was an attempt to find a qname or * client-IP trigger before recursion. * We will be back if no pre-recursion triggers hit. * For example, consider 2 policy zones, both with qname and * IP address triggers. If the qname misses the 1st zone, * then we cannot know whether a hit for the qname in the * 2nd zone matters until after recursing to get the A RRs and * testing them in the first zone. * Do not bother saving the work from this attempt, * because recursion is so slow. */ if (qresult_type == qresult_type_recurse) { goto cleanup; } /* * DNS_RPZ_DONE_QNAME but not DNS_RPZ_DONE_CLIENT_IP * is reset at the end of dealing with each CNAME. */ st->state |= (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME); } /* * Check known IP addresses for the query name if the database lookup * resulted in some addresses (qresult_type == qresult_type_done) * and if we have not already checked them. * Any recursion required for the query has already happened. * Do not check addresses that will not be in the ANSWER section. */ if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 && qresult_type == qresult_type_done && rpz_get_zbits(client, qtype, DNS_RPZ_TYPE_IP) != 0) { result = rpz_rewrite_ip_rrsets(client, client->query.qname, qtype, DNS_RPZ_TYPE_IP, &rdataset, resuming); if (result != ISC_R_SUCCESS) { goto cleanup; } /* * We are finished checking the IP addresses for the qname. * Start with IPv4 if we will check NS IP addresses. */ st->state |= DNS_RPZ_DONE_QNAME_IP; st->state &= ~DNS_RPZ_DONE_IPv4; } /* * Stop looking for rules if there are none of the other kinds * that could override what we already have. */ if (rpz_get_zbits(client, dns_rdatatype_any, DNS_RPZ_TYPE_NSDNAME) == 0 && rpz_get_zbits(client, dns_rdatatype_any, DNS_RPZ_TYPE_NSIP) == 0) { result = ISC_R_SUCCESS; goto cleanup; } dns_fixedname_init(&nsnamef); dns_name_clone(client->query.qname, dns_fixedname_name(&nsnamef)); options = client->query.dboptions | DNS_DBFIND_GLUEOK; while (st->r.label > st->popt.min_ns_labels) { bool was_glue = false; /* * Get NS rrset for each domain in the current qname. */ if (st->r.label == dns_name_countlabels(client->query.qname)) { nsname = client->query.qname; } else { nsname = dns_fixedname_name(&nsnamef); dns_name_split(client->query.qname, st->r.label, NULL, nsname); } if (st->r.ns_rdataset == NULL || !dns_rdataset_isassociated(st->r.ns_rdataset)) { dns_db_t *db = NULL; result = rpz_rrset_find(client, nsname, dns_rdatatype_ns, options, DNS_RPZ_TYPE_NSDNAME, &db, NULL, &st->r.ns_rdataset, resuming); if (db != NULL) { dns_db_detach(&db); } if (st->m.policy == DNS_RPZ_POLICY_ERROR) { goto cleanup; } switch (result) { case DNS_R_GLUE: was_glue = true; FALLTHROUGH; case ISC_R_SUCCESS: result = dns_rdataset_first(st->r.ns_rdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } st->state &= ~(DNS_RPZ_DONE_NSDNAME | DNS_RPZ_DONE_IPv4); break; case DNS_R_DELEGATION: case DNS_R_DUPLICATE: case DNS_R_DROP: goto cleanup; case DNS_R_EMPTYNAME: case DNS_R_NXRRSET: case DNS_R_EMPTYWILD: case DNS_R_NXDOMAIN: case DNS_R_NCACHENXDOMAIN: case DNS_R_NCACHENXRRSET: case ISC_R_NOTFOUND: case DNS_R_CNAME: case DNS_R_DNAME: rpz_rewrite_ns_skip(client, nsname, result, 0, NULL); continue; case ISC_R_TIMEDOUT: case DNS_R_BROKENCHAIN: case ISC_R_FAILURE: rpz_rewrite_ns_skip(client, nsname, result, DNS_RPZ_DEBUG_LEVEL3, " NS rpz_rrset_find()"); continue; default: rpz_rewrite_ns_skip(client, nsname, result, DNS_RPZ_INFO_LEVEL, " unrecognized NS" " rpz_rrset_find()"); continue; } } /* * Check all NS names. */ do { dns_rdata_ns_t ns; dns_rdata_t nsrdata = DNS_RDATA_INIT; dns_rdataset_current(st->r.ns_rdataset, &nsrdata); result = dns_rdata_tostruct(&nsrdata, &ns, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&nsrdata); /* * Do nothing about "NS ." */ if (dns_name_equal(&ns.name, dns_rootname)) { dns_rdata_freestruct(&ns); result = dns_rdataset_next(st->r.ns_rdataset); continue; } /* * Check this NS name if we did not handle it * during a previous recursion. */ if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0) { result = rpz_rewrite_name( client, &ns.name, qtype, DNS_RPZ_TYPE_NSDNAME, DNS_RPZ_ALL_ZBITS, true, &rdataset); if (result != ISC_R_SUCCESS) { dns_rdata_freestruct(&ns); goto cleanup; } st->state |= DNS_RPZ_DONE_NSDNAME; } /* * Check all IP addresses for this NS name. */ result = rpz_rewrite_ip_rrsets(client, &ns.name, qtype, DNS_RPZ_TYPE_NSIP, &rdataset, resuming); dns_rdata_freestruct(&ns); if (result != ISC_R_SUCCESS) { goto cleanup; } st->state &= ~(DNS_RPZ_DONE_NSDNAME | DNS_RPZ_DONE_IPv4); result = dns_rdataset_next(st->r.ns_rdataset); } while (result == ISC_R_SUCCESS); dns_rdataset_disassociate(st->r.ns_rdataset); /* * If we just checked a glue NS RRset retry without allowing * glue responses, otherwise setup for the next name. */ if (was_glue) { options = client->query.dboptions; } else { options = client->query.dboptions | DNS_DBFIND_GLUEOK; st->r.label--; } if (rpz_get_zbits(client, dns_rdatatype_any, DNS_RPZ_TYPE_NSDNAME) == 0 && rpz_get_zbits(client, dns_rdatatype_any, DNS_RPZ_TYPE_NSIP) == 0) { break; } } /* * Use the best hit, if any. */ result = ISC_R_SUCCESS; cleanup: #ifdef USE_DNSRPS if (st->popt.dnsrps_enabled && st->m.policy != DNS_RPZ_POLICY_ERROR && !dnsrps_set_p(&emsg, client, st, qtype, &rdataset, (qresult_type != qresult_type_recurse))) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, NULL, DNS_RPZ_TYPE_BAD, emsg.c, DNS_R_SERVFAIL); st->m.policy = DNS_RPZ_POLICY_ERROR; } #endif /* ifdef USE_DNSRPS */ if (st->m.policy != DNS_RPZ_POLICY_MISS && st->m.policy != DNS_RPZ_POLICY_ERROR && st->m.rpz->policy != DNS_RPZ_POLICY_GIVEN) { st->m.policy = st->m.rpz->policy; } if (st->m.policy == DNS_RPZ_POLICY_MISS || st->m.policy == DNS_RPZ_POLICY_PASSTHRU || st->m.policy == DNS_RPZ_POLICY_ERROR) { if (st->m.policy == DNS_RPZ_POLICY_PASSTHRU && result != DNS_R_DELEGATION) { rpz_log_rewrite(client, false, st->m.policy, st->m.type, st->m.zone, st->p_name, NULL, st->m.rpz->num); } rpz_match_clear(st); } if (st->m.policy == DNS_RPZ_POLICY_ERROR) { CTRACE(ISC_LOG_ERROR, "SERVFAIL due to RPZ policy"); st->m.type = DNS_RPZ_TYPE_BAD; result = DNS_R_SERVFAIL; } ns_client_putrdataset(client, &rdataset); if ((st->state & DNS_RPZ_RECURSING) == 0) { rpz_clean(NULL, &st->r.db, NULL, &st->r.ns_rdataset); } return (result); } /* * See if response policy zone rewriting is allowed by a lack of interest * by the client in DNSSEC or a lack of signatures. */ static bool rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { dns_fixedname_t fixed; dns_name_t *found; dns_rdataset_t trdataset; dns_rdatatype_t type; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "rpz_ck_dnssec"); if (client->view->rpzs->p.break_dnssec || !WANTDNSSEC(client)) { return (true); } /* * We do not know if there are signatures if we have not recursed * for them. */ if (qresult == DNS_R_DELEGATION || qresult == ISC_R_NOTFOUND) { return (false); } if (sigrdataset == NULL) { return (true); } if (dns_rdataset_isassociated(sigrdataset)) { return (false); } /* * We are happy to rewrite nothing. */ if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) { return (true); } /* * Do not rewrite if there is any sign of signatures. */ if (rdataset->type == dns_rdatatype_nsec || rdataset->type == dns_rdatatype_nsec3 || rdataset->type == dns_rdatatype_rrsig) { return (false); } /* * Look for a signature in a negative cache rdataset. */ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) == 0) { return (true); } found = dns_fixedname_initname(&fixed); dns_rdataset_init(&trdataset); for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_ncache_current(rdataset, found, &trdataset); type = trdataset.type; dns_rdataset_disassociate(&trdataset); if (type == dns_rdatatype_nsec || type == dns_rdatatype_nsec3 || type == dns_rdatatype_rrsig) { return (false); } } return (true); } /* * Extract a network address from the RDATA of an A or AAAA * record. * * Returns: * ISC_R_SUCCESS * ISC_R_NOTIMPLEMENTED The rdata is not a known address type. */ static isc_result_t rdata_tonetaddr(const dns_rdata_t *rdata, isc_netaddr_t *netaddr) { struct in_addr ina; struct in6_addr in6a; switch (rdata->type) { case dns_rdatatype_a: INSIST(rdata->length == 4); memmove(&ina.s_addr, rdata->data, 4); isc_netaddr_fromin(netaddr, &ina); return (ISC_R_SUCCESS); case dns_rdatatype_aaaa: INSIST(rdata->length == 16); memmove(in6a.s6_addr, rdata->data, 16); isc_netaddr_fromin6(netaddr, &in6a); return (ISC_R_SUCCESS); default: return (ISC_R_NOTIMPLEMENTED); } } static unsigned char inaddr10_offsets[] = { 0, 3, 11, 16 }; static unsigned char inaddr172_offsets[] = { 0, 3, 7, 15, 20 }; static unsigned char inaddr192_offsets[] = { 0, 4, 8, 16, 21 }; static unsigned char inaddr10[] = "\00210\007IN-ADDR\004ARPA"; static unsigned char inaddr16172[] = "\00216\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr17172[] = "\00217\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr18172[] = "\00218\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr19172[] = "\00219\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr20172[] = "\00220\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr21172[] = "\00221\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr22172[] = "\00222\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr23172[] = "\00223\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr24172[] = "\00224\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr25172[] = "\00225\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr26172[] = "\00226\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr27172[] = "\00227\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr28172[] = "\00228\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr29172[] = "\00229\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr30172[] = "\00230\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr31172[] = "\00231\003172\007IN-ADDR\004ARPA"; static unsigned char inaddr168192[] = "\003168\003192\007IN-ADDR\004ARPA"; static dns_name_t rfc1918names[] = { DNS_NAME_INITABSOLUTE(inaddr10, inaddr10_offsets), DNS_NAME_INITABSOLUTE(inaddr16172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr17172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr18172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr19172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr20172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr21172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr22172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr23172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr24172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr25172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr26172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr27172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr28172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr29172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr30172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr31172, inaddr172_offsets), DNS_NAME_INITABSOLUTE(inaddr168192, inaddr192_offsets) }; static unsigned char prisoner_data[] = "\010prisoner\004iana\003org"; static unsigned char hostmaster_data[] = "\012hostmaster\014root-" "servers\003org"; static unsigned char prisoner_offsets[] = { 0, 9, 14, 18 }; static unsigned char hostmaster_offsets[] = { 0, 11, 24, 28 }; static dns_name_t const prisoner = DNS_NAME_INITABSOLUTE(prisoner_data, prisoner_offsets); static dns_name_t const hostmaster = DNS_NAME_INITABSOLUTE(hostmaster_data, hostmaster_offsets); static void warn_rfc1918(ns_client_t *client, dns_name_t *fname, dns_rdataset_t *rdataset) { unsigned int i; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_soa_t soa; dns_rdataset_t found; isc_result_t result; for (i = 0; i < (sizeof(rfc1918names) / sizeof(*rfc1918names)); i++) { if (dns_name_issubdomain(fname, &rfc1918names[i])) { dns_rdataset_init(&found); result = dns_ncache_getrdataset( rdataset, &rfc1918names[i], dns_rdatatype_soa, &found); if (result != ISC_R_SUCCESS) { return; } result = dns_rdataset_first(&found); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdataset_current(&found, &rdata); result = dns_rdata_tostruct(&rdata, &soa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (dns_name_equal(&soa.origin, &prisoner) && dns_name_equal(&soa.contact, &hostmaster)) { char buf[DNS_NAME_FORMATSIZE]; dns_name_format(fname, buf, sizeof(buf)); ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "RFC 1918 response from " "Internet for %s", buf); } dns_rdataset_disassociate(&found); return; } } } 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, bool exact, dns_name_t *found) { unsigned char salt[256]; size_t salt_length; uint16_t iterations; isc_result_t result; unsigned int dboptions; dns_fixedname_t fixed; dns_hash_t hash; dns_name_t name; unsigned int skip = 0, labels; dns_rdata_nsec3_t nsec3; dns_rdata_t rdata = DNS_RDATA_INIT; bool optout; dns_clientinfomethods_t cm; dns_clientinfo_t ci; salt_length = sizeof(salt); result = dns_db_getnsec3parameters(db, version, &hash, NULL, &iterations, salt, &salt_length); if (result != ISC_R_SUCCESS) { return; } dns_name_init(&name, NULL); dns_name_clone(qname, &name); labels = dns_name_countlabels(&name); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * Map unknown algorithm to known value. */ if (hash == DNS_NSEC3_UNKNOWNALG) { hash = 1; } again: dns_fixedname_init(&fixed); result = dns_nsec3_hashname(&fixed, NULL, NULL, &name, dns_db_origin(db), hash, iterations, salt, salt_length); if (result != ISC_R_SUCCESS) { return; } dboptions = client->query.dboptions | DNS_DBFIND_FORCENSEC3; result = dns_db_findext(db, dns_fixedname_name(&fixed), version, dns_rdatatype_nsec3, dboptions, client->now, NULL, fname, &cm, &ci, rdataset, sigrdataset); if (result == DNS_R_NXDOMAIN) { if (!dns_rdataset_isassociated(rdataset)) { return; } result = dns_rdataset_first(rdataset); INSIST(result == ISC_R_SUCCESS); dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &nsec3, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); optout = ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0); if (found != NULL && optout && dns_name_issubdomain(&name, dns_db_origin(db))) { dns_rdataset_disassociate(rdataset); if (dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } skip++; dns_name_getlabelsequence(qname, skip, labels - skip, &name); ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, NS_LOGMODULE_QUERY, ISC_LOG_DEBUG(3), "looking for closest provable encloser"); goto again; } if (exact) { ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "expected a exact match NSEC3, got " "a covering record"); } } else if (result != ISC_R_SUCCESS) { return; } else if (!exact) { ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "expected covering NSEC3, got an exact match"); } if (found == qname) { if (skip != 0U) { dns_name_getlabelsequence(qname, skip, labels - skip, found); } } else if (found != NULL) { dns_name_copy(&name, found); } return; } static uint32_t dns64_ttl(dns_db_t *db, dns_dbversion_t *version) { dns_dbnode_t *node = NULL; dns_rdata_soa_t soa; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t rdataset; isc_result_t result; uint32_t ttl = UINT32_MAX; dns_rdataset_init(&rdataset); result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) { goto cleanup; } result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa, 0, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { goto cleanup; } result = dns_rdataset_first(&rdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &soa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); ttl = ISC_MIN(rdataset.ttl, soa.minimum); cleanup: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } return (ttl); } static bool dns64_aaaaok(ns_client_t *client, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { isc_netaddr_t netaddr; dns_aclenv_t *env = client->manager->aclenv; dns_dns64_t *dns64 = ISC_LIST_HEAD(client->view->dns64); unsigned int flags = 0; unsigned int i, count; bool *aaaaok; INSIST(client->query.dns64_aaaaok == NULL); INSIST(client->query.dns64_aaaaoklen == 0); INSIST(client->query.dns64_aaaa == NULL); INSIST(client->query.dns64_sigaaaa == NULL); if (dns64 == NULL) { return (true); } if (RECURSIONOK(client)) { flags |= DNS_DNS64_RECURSIVE; } if (WANTDNSSEC(client) && sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { flags |= DNS_DNS64_DNSSEC; } count = dns_rdataset_count(rdataset); aaaaok = isc_mem_get(client->mctx, sizeof(bool) * count); isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); if (dns_dns64_aaaaok(dns64, &netaddr, client->signer, env, flags, rdataset, aaaaok, count)) { for (i = 0; i < count; i++) { if (aaaaok != NULL && !aaaaok[i]) { SAVE(client->query.dns64_aaaaok, aaaaok); client->query.dns64_aaaaoklen = count; break; } } if (aaaaok != NULL) { isc_mem_put(client->mctx, aaaaok, sizeof(bool) * count); } return (true); } if (aaaaok != NULL) { isc_mem_put(client->mctx, aaaaok, sizeof(bool) * count); } return (false); } /* * Look for the name and type in the redirection zone. If found update * the arguments as appropriate. Return true if a update was * performed. * * Only perform the update if the client is in the allow query acl and * returning the update would not cause a DNSSEC validation failure. */ static isc_result_t redirect(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset, dns_dbnode_t **nodep, dns_db_t **dbp, dns_dbversion_t **versionp, dns_rdatatype_t qtype) { dns_db_t *db = NULL; dns_dbnode_t *node = NULL; dns_fixedname_t fixed; dns_name_t *found; dns_rdataset_t trdataset; isc_result_t result; dns_rdatatype_t type; dns_clientinfomethods_t cm; dns_clientinfo_t ci; ns_dbversion_t *dbversion; CTRACE(ISC_LOG_DEBUG(3), "redirect"); if (client->view->redirect == NULL) { return (ISC_R_NOTFOUND); } found = dns_fixedname_initname(&fixed); dns_rdataset_init(&trdataset); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); dns_clientinfo_setecs(&ci, &client->ecs); if (WANTDNSSEC(client) && dns_db_iszone(*dbp) && dns_db_issecure(*dbp)) { return (ISC_R_NOTFOUND); } if (WANTDNSSEC(client) && dns_rdataset_isassociated(rdataset)) { if (rdataset->trust == dns_trust_secure) { return (ISC_R_NOTFOUND); } if (rdataset->trust == dns_trust_ultimate && (rdataset->type == dns_rdatatype_nsec || rdataset->type == dns_rdatatype_nsec3)) { return (ISC_R_NOTFOUND); } if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_ncache_current(rdataset, found, &trdataset); type = trdataset.type; dns_rdataset_disassociate(&trdataset); if (type == dns_rdatatype_nsec || type == dns_rdatatype_nsec3 || type == dns_rdatatype_rrsig) { return (ISC_R_NOTFOUND); } } } } result = ns_client_checkaclsilent( client, NULL, dns_zone_getqueryacl(client->view->redirect), true); if (result != ISC_R_SUCCESS) { return (ISC_R_NOTFOUND); } result = dns_zone_getdb(client->view->redirect, &db); if (result != ISC_R_SUCCESS) { return (ISC_R_NOTFOUND); } dbversion = ns_client_findversion(client, db); if (dbversion == NULL) { dns_db_detach(&db); return (ISC_R_NOTFOUND); } /* * Lookup the requested data in the redirect zone. */ result = dns_db_findext(db, client->query.qname, dbversion->version, qtype, DNS_DBFIND_NOZONECUT, client->now, &node, found, &cm, &ci, &trdataset, NULL); if (result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) { if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } if (dns_rdataset_isassociated(&trdataset)) { dns_rdataset_disassociate(&trdataset); } goto nxrrset; } else if (result != ISC_R_SUCCESS) { if (dns_rdataset_isassociated(&trdataset)) { dns_rdataset_disassociate(&trdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } dns_db_detach(&db); return (ISC_R_NOTFOUND); } CTRACE(ISC_LOG_DEBUG(3), "redirect: found data: done"); dns_name_copy(found, name); if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } if (dns_rdataset_isassociated(&trdataset)) { dns_rdataset_clone(&trdataset, rdataset); dns_rdataset_disassociate(&trdataset); } nxrrset: if (*nodep != NULL) { dns_db_detachnode(*dbp, nodep); } dns_db_detach(dbp); dns_db_attachnode(db, node, nodep); dns_db_attach(db, dbp); dns_db_detachnode(db, &node); dns_db_detach(&db); *versionp = dbversion->version; client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); return (result); } static isc_result_t redirect2(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset, dns_dbnode_t **nodep, dns_db_t **dbp, dns_dbversion_t **versionp, dns_rdatatype_t qtype, bool *is_zonep) { dns_db_t *db = NULL; dns_dbnode_t *node = NULL; dns_fixedname_t fixed; dns_fixedname_t fixedredirect; dns_name_t *found, *redirectname; dns_rdataset_t trdataset; isc_result_t result; dns_rdatatype_t type; dns_clientinfomethods_t cm; dns_clientinfo_t ci; dns_dbversion_t *version = NULL; dns_zone_t *zone = NULL; bool is_zone; unsigned int labels; unsigned int options; CTRACE(ISC_LOG_DEBUG(3), "redirect2"); if (client->view->redirectzone == NULL) { return (ISC_R_NOTFOUND); } if (dns_name_issubdomain(name, client->view->redirectzone)) { return (ISC_R_NOTFOUND); } found = dns_fixedname_initname(&fixed); dns_rdataset_init(&trdataset); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); dns_clientinfo_setecs(&ci, &client->ecs); if (WANTDNSSEC(client) && dns_db_iszone(*dbp) && dns_db_issecure(*dbp)) { return (ISC_R_NOTFOUND); } if (WANTDNSSEC(client) && dns_rdataset_isassociated(rdataset)) { if (rdataset->trust == dns_trust_secure) { return (ISC_R_NOTFOUND); } if (rdataset->trust == dns_trust_ultimate && (rdataset->type == dns_rdatatype_nsec || rdataset->type == dns_rdatatype_nsec3)) { return (ISC_R_NOTFOUND); } if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_ncache_current(rdataset, found, &trdataset); type = trdataset.type; dns_rdataset_disassociate(&trdataset); if (type == dns_rdatatype_nsec || type == dns_rdatatype_nsec3 || type == dns_rdatatype_rrsig) { return (ISC_R_NOTFOUND); } } } } redirectname = dns_fixedname_initname(&fixedredirect); labels = dns_name_countlabels(client->query.qname); if (labels > 1U) { dns_name_t prefix; dns_name_init(&prefix, NULL); dns_name_getlabelsequence(client->query.qname, 0, labels - 1, &prefix); result = dns_name_concatenate(&prefix, client->view->redirectzone, redirectname, NULL); if (result != ISC_R_SUCCESS) { return (ISC_R_NOTFOUND); } } else { dns_name_copy(redirectname, client->view->redirectzone); } options = 0; result = query_getdb(client, redirectname, qtype, options, &zone, &db, &version, &is_zone); if (result != ISC_R_SUCCESS) { return (ISC_R_NOTFOUND); } if (zone != NULL) { dns_zone_detach(&zone); } /* * Lookup the requested data in the redirect zone. */ result = dns_db_findext(db, redirectname, version, qtype, 0, client->now, &node, found, &cm, &ci, &trdataset, NULL); if (result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) { if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } if (dns_rdataset_isassociated(&trdataset)) { dns_rdataset_disassociate(&trdataset); } goto nxrrset; } else if (result == ISC_R_NOTFOUND || result == DNS_R_DELEGATION) { /* * Cleanup. */ if (dns_rdataset_isassociated(&trdataset)) { dns_rdataset_disassociate(&trdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } dns_db_detach(&db); /* * Don't loop forever if the lookup failed last time. */ if (!REDIRECT(client)) { result = ns_query_recurse(client, qtype, redirectname, NULL, NULL, true); if (result == ISC_R_SUCCESS) { client->query.attributes |= NS_QUERYATTR_RECURSING; client->query.attributes |= NS_QUERYATTR_REDIRECT; return (DNS_R_CONTINUE); } } return (ISC_R_NOTFOUND); } else if (result != ISC_R_SUCCESS) { if (dns_rdataset_isassociated(&trdataset)) { dns_rdataset_disassociate(&trdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } dns_db_detach(&db); return (ISC_R_NOTFOUND); } CTRACE(ISC_LOG_DEBUG(3), "redirect2: found data: done"); /* * Adjust the found name to not include the redirectzone suffix. */ dns_name_split(found, dns_name_countlabels(client->view->redirectzone), found, NULL); /* * Make the name absolute. */ result = dns_name_concatenate(found, dns_rootname, found, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_name_copy(found, name); if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } if (dns_rdataset_isassociated(&trdataset)) { dns_rdataset_clone(&trdataset, rdataset); dns_rdataset_disassociate(&trdataset); } nxrrset: if (*nodep != NULL) { dns_db_detachnode(*dbp, nodep); } dns_db_detach(dbp); dns_db_attachnode(db, node, nodep); dns_db_attach(db, dbp); dns_db_detachnode(db, &node); dns_db_detach(&db); *is_zonep = is_zone; *versionp = version; client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); return (result); } /*% * Initialize query context 'qctx'. Run by query_setup() when * first handling a client query, and by query_resume() when * returning from recursion. * * Whenever this function is called, qctx_destroy() must be called * when leaving the scope or freeing the qctx. */ static void qctx_init(ns_client_t *client, dns_fetchevent_t **eventp, dns_rdatatype_t qtype, query_ctx_t *qctx) { REQUIRE(qctx != NULL); REQUIRE(client != NULL); memset(qctx, 0, sizeof(*qctx)); /* Set this first so CCTRACE will work */ qctx->client = client; dns_view_attach(client->view, &qctx->view); CCTRACE(ISC_LOG_DEBUG(3), "qctx_init"); if (eventp != NULL) { qctx->event = *eventp; *eventp = NULL; } else { qctx->event = NULL; } qctx->qtype = qctx->type = qtype; qctx->result = ISC_R_SUCCESS; qctx->findcoveringnsec = qctx->view->synthfromdnssec; /* * If it's an RRSIG or SIG query, we'll iterate the node. */ if (qctx->qtype == dns_rdatatype_rrsig || qctx->qtype == dns_rdatatype_sig) { qctx->type = dns_rdatatype_any; } CALL_HOOK_NORETURN(NS_QUERY_QCTX_INITIALIZED, qctx); } /* * Make 'dst' and exact copy of 'src', with exception of the * option field, which is reset to zero. * This function also attaches dst's view and db to the src's * view and cachedb. */ static void qctx_copy(const query_ctx_t *qctx, query_ctx_t *dst) { REQUIRE(qctx != NULL); REQUIRE(dst != NULL); memmove(dst, qctx, sizeof(*dst)); dst->view = NULL; dst->db = NULL; dst->options = 0; dns_view_attach(qctx->view, &dst->view); dns_db_attach(qctx->view->cachedb, &dst->db); CCTRACE(ISC_LOG_DEBUG(3), "qctx_copy"); } /*% * Clean up and disassociate the rdataset and node pointers in qctx. */ static void qctx_clean(query_ctx_t *qctx) { if (qctx->rdataset != NULL && dns_rdataset_isassociated(qctx->rdataset)) { dns_rdataset_disassociate(qctx->rdataset); } if (qctx->sigrdataset != NULL && dns_rdataset_isassociated(qctx->sigrdataset)) { dns_rdataset_disassociate(qctx->sigrdataset); } if (qctx->db != NULL && qctx->node != NULL) { dns_db_detachnode(qctx->db, &qctx->node); } } /*% * Free any allocated memory associated with qctx. */ static void qctx_freedata(query_ctx_t *qctx) { if (qctx->rdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->rdataset); } if (qctx->sigrdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->sigrdataset); } if (qctx->fname != NULL) { ns_client_releasename(qctx->client, &qctx->fname); } if (qctx->db != NULL) { INSIST(qctx->node == NULL); dns_db_detach(&qctx->db); } if (qctx->zone != NULL) { dns_zone_detach(&qctx->zone); } if (qctx->zdb != NULL) { ns_client_putrdataset(qctx->client, &qctx->zsigrdataset); ns_client_putrdataset(qctx->client, &qctx->zrdataset); ns_client_releasename(qctx->client, &qctx->zfname); dns_db_detachnode(qctx->zdb, &qctx->znode); dns_db_detach(&qctx->zdb); } if (qctx->event != NULL && !qctx->client->nodetach) { free_devent(qctx->client, ISC_EVENT_PTR(&qctx->event), &qctx->event); } } static void qctx_destroy(query_ctx_t *qctx) { CALL_HOOK_NORETURN(NS_QUERY_QCTX_DESTROYED, qctx); dns_view_detach(&qctx->view); } /* * Call SAVE but set 'a' to NULL first so as not to assert. */ #define INITANDSAVE(a, b) \ do { \ a = NULL; \ SAVE(a, b); \ } while (0) /* * "save" qctx data from 'src' to 'tgt'. * It essentially moves ownership of the data from src to tgt, so the former * becomes unusable except for final cleanup (such as by qctx_destroy). * Note: this function doesn't attach to the client's handle. It's the caller's * responsibility to do it if it's necessary. */ static void qctx_save(query_ctx_t *src, query_ctx_t *tgt) { /* First copy all fields in a straightforward way */ *tgt = *src; /* Then "move" pointers (except client and view) */ INITANDSAVE(tgt->dbuf, src->dbuf); INITANDSAVE(tgt->fname, src->fname); INITANDSAVE(tgt->tname, src->tname); INITANDSAVE(tgt->rdataset, src->rdataset); INITANDSAVE(tgt->sigrdataset, src->sigrdataset); INITANDSAVE(tgt->noqname, src->noqname); INITANDSAVE(tgt->event, src->event); INITANDSAVE(tgt->db, src->db); INITANDSAVE(tgt->version, src->version); INITANDSAVE(tgt->node, src->node); INITANDSAVE(tgt->zdb, src->zdb); INITANDSAVE(tgt->znode, src->znode); INITANDSAVE(tgt->zfname, src->zfname); INITANDSAVE(tgt->zversion, src->zversion); INITANDSAVE(tgt->zrdataset, src->zrdataset); INITANDSAVE(tgt->zsigrdataset, src->zsigrdataset); INITANDSAVE(tgt->rpz_st, src->rpz_st); INITANDSAVE(tgt->zone, src->zone); /* View has to stay in 'src' for qctx_destroy. */ tgt->view = NULL; dns_view_attach(src->view, &tgt->view); } /*% * Log detailed information about the query immediately after * the client request or a return from recursion. */ static void query_trace(query_ctx_t *qctx) { #ifdef WANT_QUERYTRACE char mbuf[2 * DNS_NAME_FORMATSIZE]; char qbuf[DNS_NAME_FORMATSIZE]; if (qctx->client->query.origqname != NULL) { dns_name_format(qctx->client->query.origqname, qbuf, sizeof(qbuf)); } else { snprintf(qbuf, sizeof(qbuf), ""); } snprintf(mbuf, sizeof(mbuf) - 1, "client attr:0x%x, query attr:0x%X, restarts:%u, " "origqname:%s, timer:%d, authdb:%d, referral:%d", qctx->client->attributes, qctx->client->query.attributes, qctx->client->query.restarts, qbuf, (int)qctx->client->query.timerset, (int)qctx->client->query.authdbset, (int)qctx->client->query.isreferral); CCTRACE(ISC_LOG_DEBUG(3), mbuf); #else /* ifdef WANT_QUERYTRACE */ UNUSED(qctx); #endif /* ifdef WANT_QUERYTRACE */ } /* * Set up query processing for the current query of 'client'. * Calls qctx_init() to initialize a query context, checks * the SERVFAIL cache, then hands off processing to ns__query_start(). * * This is called only from ns_query_start(), to begin a query * for the first time. Restarting an existing query (for * instance, to handle CNAME lookups), is done by calling * ns__query_start() again with the same query context. Resuming from * recursion is handled by query_resume(). */ static isc_result_t query_setup(ns_client_t *client, dns_rdatatype_t qtype) { isc_result_t result = ISC_R_UNSET; query_ctx_t qctx; qctx_init(client, NULL, qtype, &qctx); query_trace(&qctx); CALL_HOOK(NS_QUERY_SETUP, &qctx); /* * Check SERVFAIL cache */ result = ns__query_sfcache(&qctx); if (result != ISC_R_COMPLETE) { qctx_destroy(&qctx); return (result); } result = ns__query_start(&qctx); cleanup: qctx_destroy(&qctx); return (result); } static bool get_root_key_sentinel_id(query_ctx_t *qctx, const char *ndata) { unsigned int v = 0; int i; for (i = 0; i < 5; i++) { if (!isdigit((unsigned char)ndata[i])) { return (false); } v *= 10; v += ndata[i] - '0'; } if (v > 65535U) { return (false); } qctx->client->query.root_key_sentinel_keyid = v; return (true); } /*% * Find out if the query is for a root key sentinel and if so, record the type * of root key sentinel query and the key id that is being checked for. * * The code is assuming a zero padded decimal field of width 5. */ static void root_key_sentinel_detect(query_ctx_t *qctx) { const char *ndata = (const char *)qctx->client->query.qname->ndata; if (qctx->client->query.qname->length > 30 && ndata[0] == 29 && strncasecmp(ndata + 1, "root-key-sentinel-is-ta-", 24) == 0) { if (!get_root_key_sentinel_id(qctx, ndata + 25)) { return; } qctx->client->query.root_key_sentinel_is_ta = true; /* * Simplify processing by disabling aggressive * negative caching. */ qctx->findcoveringnsec = false; ns_client_log(qctx->client, NS_LOGCATEGORY_TAT, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "root-key-sentinel-is-ta query label found"); } else if (qctx->client->query.qname->length > 31 && ndata[0] == 30 && strncasecmp(ndata + 1, "root-key-sentinel-not-ta-", 25) == 0) { if (!get_root_key_sentinel_id(qctx, ndata + 26)) { return; } qctx->client->query.root_key_sentinel_not_ta = true; /* * Simplify processing by disabling aggressive * negative caching. */ qctx->findcoveringnsec = false; ns_client_log(qctx->client, NS_LOGCATEGORY_TAT, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "root-key-sentinel-not-ta query label found"); } } /*% * Starting point for a client query or a chaining query. * * Called first by query_setup(), and then again as often as needed to * follow a CNAME chain. Determines which authoritative database to * search, then hands off processing to query_lookup(). */ isc_result_t ns__query_start(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; CCTRACE(ISC_LOG_DEBUG(3), "ns__query_start"); qctx->want_restart = false; qctx->authoritative = false; qctx->version = NULL; qctx->zversion = NULL; qctx->need_wildcardproof = false; qctx->rpz = false; CALL_HOOK(NS_QUERY_START_BEGIN, qctx); /* * If we require a server cookie then send back BADCOOKIE * before we have done too much work. */ if (!TCP(qctx->client) && qctx->view->requireservercookie && WANTCOOKIE(qctx->client) && !HAVECOOKIE(qctx->client)) { qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA; qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD; qctx->client->message->rcode = dns_rcode_badcookie; return (ns_query_done(qctx)); } if (qctx->view->checknames && !dns_rdata_checkowner(qctx->client->query.qname, qctx->client->message->rdclass, qctx->qtype, false)) { char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; char classbuf[DNS_RDATACLASS_FORMATSIZE]; dns_name_format(qctx->client->query.qname, namebuf, sizeof(namebuf)); dns_rdatatype_format(qctx->qtype, typebuf, sizeof(typebuf)); dns_rdataclass_format(qctx->client->message->rdclass, classbuf, sizeof(classbuf)); ns_client_log(qctx->client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_QUERY, ISC_LOG_ERROR, "check-names failure %s/%s/%s", namebuf, typebuf, classbuf); QUERY_ERROR(qctx, DNS_R_REFUSED); return (ns_query_done(qctx)); } /* * Setup for root key sentinel processing. */ if (qctx->view->root_key_sentinel && qctx->client->query.restarts == 0 && (qctx->qtype == dns_rdatatype_a || qctx->qtype == dns_rdatatype_aaaa) && (qctx->client->message->flags & DNS_MESSAGEFLAG_CD) == 0) { root_key_sentinel_detect(qctx); } /* * First we must find the right database. */ qctx->options &= DNS_GETDB_NOLOG; /* Preserve DNS_GETDB_NOLOG. */ if (dns_rdatatype_atparent(qctx->qtype) && !dns_name_equal(qctx->client->query.qname, dns_rootname)) { /* * If authoritative data for this QTYPE is supposed to live in * the parent zone, do not look for an exact match for QNAME, * but rather for its containing zone (unless the QNAME is * root). */ qctx->options |= DNS_GETDB_NOEXACT; } result = query_getdb(qctx->client, qctx->client->query.qname, qctx->qtype, qctx->options, &qctx->zone, &qctx->db, &qctx->version, &qctx->is_zone); if ((result != ISC_R_SUCCESS || !qctx->is_zone) && qctx->qtype == dns_rdatatype_ds && !RECURSIONOK(qctx->client) && (qctx->options & DNS_GETDB_NOEXACT) != 0) { /* * This is a non-recursive QTYPE=DS query with QNAME whose * parent we are not authoritative for. Check whether we are * authoritative for QNAME, because if so, we need to send a * "no data" response as required by RFC 4035, section 3.1.4.1. */ dns_db_t *tdb = NULL; dns_zone_t *tzone = NULL; dns_dbversion_t *tversion = NULL; isc_result_t tresult; tresult = query_getzonedb( qctx->client, qctx->client->query.qname, qctx->qtype, DNS_GETDB_PARTIAL, &tzone, &tdb, &tversion); if (tresult == ISC_R_SUCCESS) { /* * We are authoritative for QNAME. Attach the relevant * zone to query context, set result to ISC_R_SUCCESS. */ qctx->options &= ~DNS_GETDB_NOEXACT; ns_client_putrdataset(qctx->client, &qctx->rdataset); if (qctx->db != NULL) { dns_db_detach(&qctx->db); } if (qctx->zone != NULL) { dns_zone_detach(&qctx->zone); } qctx->version = NULL; RESTORE(qctx->version, tversion); RESTORE(qctx->db, tdb); RESTORE(qctx->zone, tzone); qctx->is_zone = true; result = ISC_R_SUCCESS; } else { /* * We are not authoritative for QNAME. Clean up and * leave result as it was. */ if (tdb != NULL) { dns_db_detach(&tdb); } if (tzone != NULL) { dns_zone_detach(&tzone); } } } /* * If we did not find a database from which we can answer the query, * respond with either REFUSED or SERVFAIL, depending on what the * result of query_getdb() was. */ if (result != ISC_R_SUCCESS) { if (result == DNS_R_REFUSED) { if (WANTRECURSION(qctx->client)) { inc_stats(qctx->client, ns_statscounter_recurserej); } else { inc_stats(qctx->client, ns_statscounter_authrej); } if (!PARTIALANSWER(qctx->client)) { QUERY_ERROR(qctx, DNS_R_REFUSED); } } else { CCTRACE(ISC_LOG_ERROR, "ns__query_start: query_getdb " "failed"); QUERY_ERROR(qctx, result); } return (ns_query_done(qctx)); } /* * We found a database from which we can answer the query. Update * relevant query context flags if the answer is to be prepared using * authoritative data. */ qctx->is_staticstub_zone = false; if (qctx->is_zone) { qctx->authoritative = true; if (qctx->zone != NULL) { if (dns_zone_gettype(qctx->zone) == dns_zone_mirror) { qctx->authoritative = false; } if (dns_zone_gettype(qctx->zone) == dns_zone_staticstub) { qctx->is_staticstub_zone = true; } } } /* * Attach to the database which will be used to prepare the answer. * Update query statistics. */ if (qctx->event == NULL && qctx->client->query.restarts == 0) { if (qctx->is_zone) { if (qctx->zone != NULL) { /* * if is_zone = true, zone = NULL then this is * a DLZ zone. Don't attempt to attach zone. */ dns_zone_attach(qctx->zone, &qctx->client->query.authzone); } dns_db_attach(qctx->db, &qctx->client->query.authdb); } qctx->client->query.authdbset = true; /* Track TCP vs UDP stats per zone */ if (TCP(qctx->client)) { inc_stats(qctx->client, ns_statscounter_tcp); } else { inc_stats(qctx->client, ns_statscounter_udp); } } if (!qctx->is_zone && (qctx->view->staleanswerclienttimeout == 0) && dns_view_staleanswerenabled(qctx->view)) { /* * If stale answers are enabled and * stale-answer-client-timeout is zero, then we can promptly * answer with a stale RRset if one is available in cache. */ qctx->options |= DNS_GETDB_STALEFIRST; } result = query_lookup(qctx); /* * Clear "look-also-for-stale-data" flag. * If a fetch is created to resolve this query, then, * when it completes, this option is not expected to be set. */ qctx->options &= ~DNS_GETDB_STALEFIRST; cleanup: return (result); } /* * Allocate buffers in 'qctx' used to store query results. * * 'buffer' must be a pointer to an object whose lifetime * doesn't expire while 'qctx' is in use. */ static isc_result_t qctx_prepare_buffers(query_ctx_t *qctx, isc_buffer_t *buffer) { REQUIRE(qctx != NULL); REQUIRE(qctx->client != NULL); REQUIRE(buffer != NULL); qctx->dbuf = ns_client_getnamebuf(qctx->client); if (qctx->dbuf == NULL) { CCTRACE(ISC_LOG_ERROR, "qctx_prepare_buffers: ns_client_getnamebuf " "failed"); return (ISC_R_NOMEMORY); } qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, buffer); if (qctx->fname == NULL) { CCTRACE(ISC_LOG_ERROR, "qctx_prepare_buffers: ns_client_newname failed"); return (ISC_R_NOMEMORY); } qctx->rdataset = ns_client_newrdataset(qctx->client); if (qctx->rdataset == NULL) { CCTRACE(ISC_LOG_ERROR, "qctx_prepare_buffers: ns_client_newrdataset failed"); goto error; } if ((WANTDNSSEC(qctx->client) || qctx->findcoveringnsec) && (!qctx->is_zone || dns_db_issecure(qctx->db))) { qctx->sigrdataset = ns_client_newrdataset(qctx->client); if (qctx->sigrdataset == NULL) { CCTRACE(ISC_LOG_ERROR, "qctx_prepare_buffers: " "ns_client_newrdataset failed (2)"); goto error; } } return (ISC_R_SUCCESS); error: if (qctx->fname != NULL) { ns_client_releasename(qctx->client, &qctx->fname); } if (qctx->rdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->rdataset); } return (ISC_R_NOMEMORY); } /* * Setup a new query context for resolving a query. * * This function is only called if both these conditions are met: * 1. BIND is configured with stale-answer-client-timeout 0. * 2. A stale RRset is found in cache during initial query * database lookup. * * We continue with this function for refreshing/resolving an RRset * after answering a client with stale data. */ static void query_refresh_rrset(query_ctx_t *orig_qctx) { isc_buffer_t buffer; query_ctx_t qctx; REQUIRE(orig_qctx != NULL); REQUIRE(orig_qctx->client != NULL); qctx_copy(orig_qctx, &qctx); qctx.client->query.dboptions &= ~(DNS_DBFIND_STALETIMEOUT | DNS_DBFIND_STALEOK | DNS_DBFIND_STALEENABLED); qctx.client->nodetach = false; /* * We'll need some resources... */ if (qctx_prepare_buffers(&qctx, &buffer) != ISC_R_SUCCESS) { dns_db_detach(&qctx.db); qctx_destroy(&qctx); return; } /* * Pretend we didn't find anything in cache. */ (void)query_gotanswer(&qctx, ISC_R_NOTFOUND); if (qctx.fname != NULL) { ns_client_releasename(qctx.client, &qctx.fname); } if (qctx.rdataset != NULL) { ns_client_putrdataset(qctx.client, &qctx.rdataset); } qctx_destroy(&qctx); } /*% * Depending on the db lookup result, we can respond to the * client this stale answer. */ static bool stale_client_answer(isc_result_t result) { switch (result) { case ISC_R_SUCCESS: case DNS_R_EMPTYNAME: case DNS_R_NXRRSET: case DNS_R_NCACHENXRRSET: case DNS_R_CNAME: case DNS_R_DNAME: return (true); default: return (false); } UNREACHABLE(); } /*% * Perform a local database lookup, in either an authoritative or * cache database. If unable to answer, call ns_query_done(); otherwise * hand off processing to query_gotanswer(). */ static isc_result_t query_lookup(query_ctx_t *qctx) { isc_buffer_t buffer; isc_result_t result = ISC_R_UNSET; dns_clientinfomethods_t cm; dns_clientinfo_t ci; dns_name_t *rpzqname = NULL; char namebuf[DNS_NAME_FORMATSIZE]; unsigned int dboptions; dns_ttl_t stale_refresh = 0; bool dbfind_stale = false; bool stale_timeout = false; bool answer_found = false; bool stale_found = false; bool stale_refresh_window = false; uint16_t ede = 0; CCTRACE(ISC_LOG_DEBUG(3), "query_lookup"); CALL_HOOK(NS_QUERY_LOOKUP_BEGIN, qctx); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, qctx->client, NULL); if (HAVEECS(qctx->client)) { dns_clientinfo_setecs(&ci, &qctx->client->ecs); } /* * We'll need some resources... */ result = qctx_prepare_buffers(qctx, &buffer); if (result != ISC_R_SUCCESS) { QUERY_ERROR(qctx, result); return (ns_query_done(qctx)); } /* * Now look for an answer in the database. */ if (qctx->dns64 && qctx->rpz) { rpzqname = qctx->client->query.rpz_st->p_name; } else { rpzqname = qctx->client->query.qname; } if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) { /* * If DNS_GETDB_STALEFIRST is set, it means that a stale * RRset may be returned as part of this lookup. An attempt * to refresh the RRset will still take place if an * active RRset is not available. */ qctx->client->query.dboptions |= DNS_DBFIND_STALETIMEOUT; } dboptions = qctx->client->query.dboptions; if (!qctx->is_zone && qctx->findcoveringnsec && (qctx->type != dns_rdatatype_null || !dns_name_istat(rpzqname))) { dboptions |= DNS_DBFIND_COVERINGNSEC; } (void)dns_db_getservestalerefresh(qctx->client->view->cachedb, &stale_refresh); if (stale_refresh > 0 && dns_view_staleanswerenabled(qctx->client->view)) { dboptions |= DNS_DBFIND_STALEENABLED; } result = dns_db_findext(qctx->db, rpzqname, qctx->version, qctx->type, dboptions, qctx->client->now, &qctx->node, qctx->fname, &cm, &ci, qctx->rdataset, qctx->sigrdataset); /* * Fixup fname and sigrdataset. */ if (qctx->dns64 && qctx->rpz) { dns_name_copy(qctx->client->query.qname, qctx->fname); if (qctx->sigrdataset != NULL && dns_rdataset_isassociated(qctx->sigrdataset)) { dns_rdataset_disassociate(qctx->sigrdataset); } } if (!qctx->is_zone) { dns_cache_updatestats(qctx->view->cache, result); } /* * If DNS_DBFIND_STALEOK is set this means we are dealing with a * lookup following a failed lookup and it is okay to serve a stale * answer. This will (re)start the 'stale-refresh-time' window in * rbtdb, tracking the last time the RRset lookup failed. */ dbfind_stale = ((dboptions & DNS_DBFIND_STALEOK) != 0); /* * If DNS_DBFIND_STALEENABLED is set, this may be a normal lookup, but * we are allowed to immediately respond with a stale answer if the * request is within the 'stale-refresh-time' window. */ stale_refresh_window = (STALE_WINDOW(qctx->rdataset) && (dboptions & DNS_DBFIND_STALEENABLED) != 0); /* * If DNS_DBFIND_STALETIMEOUT is set, a stale answer is requested. * This can happen if 'stale-answer-client-timeout' is enabled. * * If 'stale-answer-client-timeout' is set to 0, and a stale * answer is found, send it to the client, and try to refresh the * RRset. * * If 'stale-answer-client-timeout' is non-zero, and a stale * answer is found, send it to the client. Don't try to refresh the * RRset because a fetch is already in progress. */ stale_timeout = ((dboptions & DNS_DBFIND_STALETIMEOUT) != 0); if (dns_rdataset_isassociated(qctx->rdataset) && dns_rdataset_count(qctx->rdataset) > 0 && !STALE(qctx->rdataset)) { /* Found non-stale usable rdataset. */ answer_found = true; } if (dbfind_stale || stale_refresh_window || stale_timeout) { dns_name_format(qctx->client->query.qname, namebuf, sizeof(namebuf)); inc_stats(qctx->client, ns_statscounter_trystale); if (dns_rdataset_isassociated(qctx->rdataset) && dns_rdataset_count(qctx->rdataset) > 0 && STALE(qctx->rdataset)) { stale_found = true; if (result == DNS_R_NCACHENXDOMAIN || result == DNS_R_NXDOMAIN) { ede = DNS_EDE_STALENXANSWER; } else { ede = DNS_EDE_STALEANSWER; } qctx->rdataset->ttl = qctx->view->staleanswerttl; inc_stats(qctx->client, ns_statscounter_usedstale); } else { stale_found = false; } } if (dbfind_stale) { isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "%s resolver failure, stale answer %s", namebuf, stale_found ? "used" : "unavailable"); if (stale_found) { ns_client_extendederror(qctx->client, ede, "resolver failure"); } else if (!answer_found) { /* * Resolver failure, no stale data, nothing more we * can do, return SERVFAIL. */ QUERY_ERROR(qctx, DNS_R_SERVFAIL); return (ns_query_done(qctx)); } } else if (stale_refresh_window) { /* * A recent lookup failed, so during this time window we are * allowed to return stale data immediately. */ isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "%s query within stale refresh time, stale " "answer %s", namebuf, stale_found ? "used" : "unavailable"); if (stale_found) { ns_client_extendederror( qctx->client, ede, "query within stale refresh time window"); } else if (!answer_found) { /* * During the stale refresh window explicitly do not try * to refresh the data, because a recent lookup failed. */ QUERY_ERROR(qctx, DNS_R_SERVFAIL); return (ns_query_done(qctx)); } } else if (stale_timeout) { if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) { if (!stale_found && !answer_found) { /* * We have nothing useful in cache to return * immediately. */ qctx_clean(qctx); qctx_freedata(qctx); dns_db_attach(qctx->client->view->cachedb, &qctx->db); qctx->client->query.dboptions &= ~DNS_DBFIND_STALETIMEOUT; qctx->options &= ~DNS_GETDB_STALEFIRST; if (qctx->client->query.fetch != NULL) { dns_resolver_destroyfetch( &qctx->client->query.fetch); } return (query_lookup(qctx)); } else if (stale_client_answer(result)) { /* * Immediately return the stale answer, start a * resolver fetch to refresh the data in cache. */ isc_log_write( ns_lctx, NS_LOGCATEGORY_SERVE_STALE, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "%s stale answer used, an attempt to " "refresh the RRset will still be made", namebuf); qctx->refresh_rrset = STALE(qctx->rdataset); /* * If we are refreshing the RRSet, we must not * detach from the client in query_send(). */ qctx->client->nodetach = qctx->refresh_rrset; if (stale_found) { ns_client_extendederror( qctx->client, ede, "stale data prioritized over " "lookup"); } } } else { /* * The 'stale-answer-client-timeout' triggered, return * the stale answer if available, otherwise wait until * the resolver finishes. */ isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "%s client timeout, stale answer %s", namebuf, stale_found ? "used" : "unavailable"); if (stale_found) { ns_client_extendederror(qctx->client, ede, "client timeout"); } else if (!answer_found) { return (result); } if (!stale_client_answer(result)) { return (result); } /* * There still might be real answer later. Mark the * query so we'll know we can skip answering. */ qctx->client->query.attributes |= NS_QUERYATTR_STALEPENDING; } } if (stale_timeout && (answer_found || stale_found)) { /* * Mark RRsets that we are adding to the client message on a * lookup during 'stale-answer-client-timeout', so we can * clean it up if needed when we resume from recursion. */ qctx->client->query.attributes |= NS_QUERYATTR_STALEOK; qctx->rdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED; } result = query_gotanswer(qctx, result); cleanup: return (result); } /* * Clear all rdatasets from the message that are in the given section and * that have the 'attr' attribute set. */ static void message_clearrdataset(dns_message_t *msg, unsigned int attr) { unsigned int i; dns_name_t *name, *next_name; dns_rdataset_t *rds, *next_rds; /* * Clean up name lists by calling the rdataset disassociate function. */ for (i = DNS_SECTION_ANSWER; i < DNS_SECTION_MAX; i++) { name = ISC_LIST_HEAD(msg->sections[i]); while (name != NULL) { next_name = ISC_LIST_NEXT(name, link); rds = ISC_LIST_HEAD(name->list); while (rds != NULL) { next_rds = ISC_LIST_NEXT(rds, link); if ((rds->attributes & attr) != attr) { rds = next_rds; continue; } ISC_LIST_UNLINK(name->list, rds, link); INSIST(dns_rdataset_isassociated(rds)); dns_rdataset_disassociate(rds); isc_mempool_put(msg->rdspool, rds); rds = next_rds; } if (ISC_LIST_EMPTY(name->list)) { ISC_LIST_UNLINK(msg->sections[i], name, link); if (dns_name_dynamic(name)) { dns_name_free(name, msg->mctx); } isc_mempool_put(msg->namepool, name); } name = next_name; } } } /* * Clear any rdatasets from the client's message that were added on a lookup * due to a client timeout. */ static void query_clear_stale(ns_client_t *client) { message_clearrdataset(client->message, DNS_RDATASETATTR_STALE_ADDED); } /* * Create a new query context with the sole intent of looking up for a stale * RRset in cache. If an entry is found, we mark the original query as * answered, in order to avoid answering the query twice, when the original * fetch finishes. */ static void query_lookup_stale(ns_client_t *client) { query_ctx_t qctx; qctx_init(client, NULL, client->query.qtype, &qctx); if (DNS64(client)) { qctx.qtype = qctx.type = dns_rdatatype_a; qctx.dns64 = true; } if (DNS64EXCLUDE(client)) { qctx.dns64_exclude = true; } dns_db_attach(client->view->cachedb, &qctx.db); client->query.attributes &= ~NS_QUERYATTR_RECURSIONOK; client->query.dboptions |= DNS_DBFIND_STALETIMEOUT; client->nodetach = true; (void)query_lookup(&qctx); if (qctx.node != NULL) { dns_db_detachnode(qctx.db, &qctx.node); } qctx_freedata(&qctx); qctx_destroy(&qctx); } /* * Event handler to resume processing a query after recursion, or when a * client timeout is triggered. If the query has timed out or been cancelled * or the system is shutting down, clean up and exit. If a client timeout is * triggered, see if we can respond with a stale answer from cache. Otherwise, * call query_resume() to continue the ongoing work. */ static void fetch_callback(isc_task_t *task, isc_event_t *event) { dns_fetchevent_t *devent = (dns_fetchevent_t *)event; dns_fetch_t *fetch = NULL; ns_client_t *client = NULL; bool fetch_canceled = false; bool fetch_answered = false; bool client_shuttingdown = false; isc_logcategory_t *logcategory = NS_LOGCATEGORY_QUERY_ERRORS; isc_result_t result; int errorloglevel; query_ctx_t qctx; UNUSED(task); REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE || event->ev_type == DNS_EVENT_TRYSTALE); client = devent->ev_arg; REQUIRE(NS_CLIENT_VALID(client)); REQUIRE(task == client->task); REQUIRE(RECURSING(client)); CTRACE(ISC_LOG_DEBUG(3), "fetch_callback"); if (event->ev_type == DNS_EVENT_TRYSTALE) { if (devent->result != ISC_R_CANCELED) { query_lookup_stale(client); } isc_event_free(ISC_EVENT_PTR(&event)); return; } /* * We are resuming from recursion. Reset any attributes, options * that a lookup due to stale-answer-client-timeout may have set. */ if (client->view->cachedb != NULL && client->view->recursion) { client->query.attributes |= NS_QUERYATTR_RECURSIONOK; } client->query.fetchoptions &= ~DNS_FETCHOPT_TRYSTALE_ONTIMEOUT; client->query.dboptions &= ~DNS_DBFIND_STALETIMEOUT; client->nodetach = false; LOCK(&client->query.fetchlock); INSIST(client->query.fetch == devent->fetch || client->query.fetch == NULL); if (QUERY_STALEPENDING(&client->query)) { /* * We've gotten an authoritative answer to a query that * was left pending after a stale timeout. We don't need * to do anything with it; free all the data and go home. */ client->query.fetch = NULL; fetch_answered = true; } else if (client->query.fetch != NULL) { /* * This is the fetch we've been waiting for. */ INSIST(devent->fetch == client->query.fetch); client->query.fetch = NULL; /* * Update client->now. */ isc_stdtime_get(&client->now); } else { /* * This is a fetch completion event for a canceled fetch. * Clean up and don't resume the find. */ fetch_canceled = true; } UNLOCK(&client->query.fetchlock); SAVE(fetch, devent->fetch); /* * We're done recursing, detach from quota and unlink from * the manager's recursing-clients list. */ if (client->recursionquota != NULL) { isc_quota_detach(&client->recursionquota); ns_stats_decrement(client->sctx->nsstats, ns_statscounter_recursclients); } LOCK(&client->manager->reclock); if (ISC_LINK_LINKED(client, rlink)) { ISC_LIST_UNLINK(client->manager->recursing, client, rlink); } UNLOCK(&client->manager->reclock); isc_nmhandle_detach(&client->fetchhandle); client->query.attributes &= ~NS_QUERYATTR_RECURSING; client->state = NS_CLIENTSTATE_WORKING; /* * Initialize a new qctx and use it to either resume from * recursion or clean up after cancelation. Transfer * ownership of devent to the new qctx in the process. */ qctx_init(client, &devent, 0, &qctx); client_shuttingdown = ns_client_shuttingdown(client); if (fetch_canceled || fetch_answered || client_shuttingdown) { /* * We've timed out or are shutting down. We can now * free the event and other resources held by qctx, but * don't call qctx_destroy() yet: it might destroy the * client, which we still need for a moment. */ qctx_freedata(&qctx); /* * Return an error to the client, or just drop. */ if (fetch_canceled) { CTRACE(ISC_LOG_ERROR, "fetch cancelled"); query_error(client, DNS_R_SERVFAIL, __LINE__); } else { query_next(client, ISC_R_CANCELED); } /* * Free any persistent plugin data that was allocated to * service the client, then detach the client object. */ qctx.detach_client = true; qctx_destroy(&qctx); } else { /* * Resume the find process. */ query_trace(&qctx); result = query_resume(&qctx); if (result != ISC_R_SUCCESS) { if (result == DNS_R_SERVFAIL) { errorloglevel = ISC_LOG_DEBUG(2); } else { errorloglevel = ISC_LOG_DEBUG(4); } if (isc_log_wouldlog(ns_lctx, errorloglevel)) { dns_resolver_logfetch(fetch, ns_lctx, logcategory, NS_LOGMODULE_QUERY, errorloglevel, false); } } qctx_destroy(&qctx); } dns_resolver_destroyfetch(&fetch); } /*% * Check whether the recursion parameters in 'param' match the current query's * recursion parameters provided in 'qtype', 'qname', and 'qdomain'. */ static bool recparam_match(const ns_query_recparam_t *param, dns_rdatatype_t qtype, const dns_name_t *qname, const dns_name_t *qdomain) { REQUIRE(param != NULL); return (param->qtype == qtype && param->qname != NULL && qname != NULL && param->qdomain != NULL && qdomain != NULL && dns_name_equal(param->qname, qname) && dns_name_equal(param->qdomain, qdomain)); } /*% * Update 'param' with current query's recursion parameters provided in * 'qtype', 'qname', and 'qdomain'. */ static void recparam_update(ns_query_recparam_t *param, dns_rdatatype_t qtype, const dns_name_t *qname, const dns_name_t *qdomain) { REQUIRE(param != NULL); param->qtype = qtype; if (qname == NULL) { param->qname = NULL; } else { param->qname = dns_fixedname_initname(¶m->fqname); dns_name_copy(qname, param->qname); } if (qdomain == NULL) { param->qdomain = NULL; } else { param->qdomain = dns_fixedname_initname(¶m->fqdomain); dns_name_copy(qdomain, param->qdomain); } } static atomic_uint_fast32_t last_soft, last_hard; /*% * Check recursion quota before making the current client "recursing". */ static isc_result_t check_recursionquota(ns_client_t *client) { isc_result_t result = ISC_R_SUCCESS; /* * We are about to recurse, which means that this client will * be unavailable for serving new requests for an indeterminate * amount of time. If this client is currently responsible * for handling incoming queries, set up a new client * object to handle them while we are waiting for a * response. There is no need to replace TCP clients * because those have already been replaced when the * connection was accepted (if allowed by the TCP quota). */ if (client->recursionquota == NULL) { result = isc_quota_attach(&client->sctx->recursionquota, &client->recursionquota); if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) { ns_stats_increment(client->sctx->nsstats, ns_statscounter_recursclients); } if (result == ISC_R_SOFTQUOTA) { isc_stdtime_t now; isc_stdtime_get(&now); if (now != atomic_load_relaxed(&last_soft)) { atomic_store_relaxed(&last_soft, now); ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "recursive-clients soft limit " "exceeded (%u/%u/%u), " "aborting oldest query", isc_quota_getused( client->recursionquota), isc_quota_getsoft( client->recursionquota), isc_quota_getmax( client->recursionquota)); } ns_client_killoldestquery(client); result = ISC_R_SUCCESS; } else if (result == ISC_R_QUOTA) { isc_stdtime_t now; isc_stdtime_get(&now); if (now != atomic_load_relaxed(&last_hard)) { ns_server_t *sctx = client->sctx; atomic_store_relaxed(&last_hard, now); ns_client_log( client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "no more recursive clients " "(%u/%u/%u): %s", isc_quota_getused( &sctx->recursionquota), isc_quota_getsoft( &sctx->recursionquota), isc_quota_getmax(&sctx->recursionquota), isc_result_totext(result)); } ns_client_killoldestquery(client); } if (result != ISC_R_SUCCESS) { return (result); } dns_message_clonebuffer(client->message); ns_client_recursing(client); } return (result); } isc_result_t ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, dns_name_t *qdomain, dns_rdataset_t *nameservers, bool resuming) { isc_result_t result; dns_rdataset_t *rdataset, *sigrdataset; isc_sockaddr_t *peeraddr = NULL; CTRACE(ISC_LOG_DEBUG(3), "ns_query_recurse"); /* * Check recursion parameters from the previous query to see if they * match. If not, update recursion parameters and proceed. */ if (recparam_match(&client->query.recparam, qtype, qname, qdomain)) { ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "recursion loop detected"); return (ISC_R_ALREADYRUNNING); } recparam_update(&client->query.recparam, qtype, qname, qdomain); if (!resuming) { inc_stats(client, ns_statscounter_recursion); } result = check_recursionquota(client); if (result != ISC_R_SUCCESS) { return (result); } /* * Invoke the resolver. */ REQUIRE(nameservers == NULL || nameservers->type == dns_rdatatype_ns); REQUIRE(client->query.fetch == NULL); rdataset = ns_client_newrdataset(client); if (rdataset == NULL) { return (ISC_R_NOMEMORY); } if (WANTDNSSEC(client)) { sigrdataset = ns_client_newrdataset(client); if (sigrdataset == NULL) { ns_client_putrdataset(client, &rdataset); return (ISC_R_NOMEMORY); } } else { sigrdataset = NULL; } if (!client->query.timerset) { ns_client_settimeout(client, 60); } if (!TCP(client)) { peeraddr = &client->peeraddr; } if (client->view->staleanswerclienttimeout > 0 && client->view->staleanswerclienttimeout != (uint32_t)-1 && dns_view_staleanswerenabled(client->view)) { client->query.fetchoptions |= DNS_FETCHOPT_TRYSTALE_ONTIMEOUT; } isc_nmhandle_attach(client->handle, &client->fetchhandle); result = dns_resolver_createfetch( client->view->resolver, qname, qtype, qdomain, nameservers, NULL, peeraddr, client->message->id, client->query.fetchoptions, 0, NULL, client->task, fetch_callback, client, rdataset, sigrdataset, &client->query.fetch); if (result != ISC_R_SUCCESS) { isc_nmhandle_detach(&client->fetchhandle); ns_client_putrdataset(client, &rdataset); if (sigrdataset != NULL) { ns_client_putrdataset(client, &sigrdataset); } } /* * We're now waiting for a fetch event. A client which is * shutting down will not be destroyed until all the events * have been received. */ return (result); } /*% * Restores the query context after resuming from recursion, and * continues the query processing if needed. */ static isc_result_t query_resume(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; dns_name_t *tname; isc_buffer_t b; #ifdef WANT_QUERYTRACE char mbuf[4 * DNS_NAME_FORMATSIZE]; char qbuf[DNS_NAME_FORMATSIZE]; char tbuf[DNS_RDATATYPE_FORMATSIZE]; #endif /* ifdef WANT_QUERYTRACE */ CCTRACE(ISC_LOG_DEBUG(3), "query_resume"); CALL_HOOK(NS_QUERY_RESUME_BEGIN, qctx); qctx->want_restart = false; qctx->rpz_st = qctx->client->query.rpz_st; if (qctx->rpz_st != NULL && (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0) { CCTRACE(ISC_LOG_DEBUG(3), "resume from RPZ recursion"); #ifdef WANT_QUERYTRACE { char pbuf[DNS_NAME_FORMATSIZE] = ""; char fbuf[DNS_NAME_FORMATSIZE] = ""; if (qctx->rpz_st->r_name != NULL) { dns_name_format(qctx->rpz_st->r_name, qbuf, sizeof(qbuf)); } else { snprintf(qbuf, sizeof(qbuf), ""); } if (qctx->rpz_st->p_name != NULL) { dns_name_format(qctx->rpz_st->p_name, pbuf, sizeof(pbuf)); } if (qctx->rpz_st->fname != NULL) { dns_name_format(qctx->rpz_st->fname, fbuf, sizeof(fbuf)); } snprintf(mbuf, sizeof(mbuf) - 1, "rpz rname:%s, pname:%s, qctx->fname:%s", qbuf, pbuf, fbuf); CCTRACE(ISC_LOG_DEBUG(3), mbuf); } #endif /* ifdef WANT_QUERYTRACE */ qctx->is_zone = qctx->rpz_st->q.is_zone; qctx->authoritative = qctx->rpz_st->q.authoritative; RESTORE(qctx->zone, qctx->rpz_st->q.zone); RESTORE(qctx->node, qctx->rpz_st->q.node); RESTORE(qctx->db, qctx->rpz_st->q.db); RESTORE(qctx->rdataset, qctx->rpz_st->q.rdataset); RESTORE(qctx->sigrdataset, qctx->rpz_st->q.sigrdataset); qctx->qtype = qctx->rpz_st->q.qtype; if (qctx->event->node != NULL) { dns_db_detachnode(qctx->event->db, &qctx->event->node); } SAVE(qctx->rpz_st->r.db, qctx->event->db); qctx->rpz_st->r.r_type = qctx->event->qtype; SAVE(qctx->rpz_st->r.r_rdataset, qctx->event->rdataset); ns_client_putrdataset(qctx->client, &qctx->event->sigrdataset); } else if (REDIRECT(qctx->client)) { /* * Restore saved state. */ CCTRACE(ISC_LOG_DEBUG(3), "resume from redirect recursion"); #ifdef WANT_QUERYTRACE dns_name_format(qctx->client->query.redirect.fname, qbuf, sizeof(qbuf)); dns_rdatatype_format(qctx->client->query.redirect.qtype, tbuf, sizeof(tbuf)); snprintf(mbuf, sizeof(mbuf) - 1, "redirect qctx->fname:%s, qtype:%s, auth:%d", qbuf, tbuf, qctx->client->query.redirect.authoritative); CCTRACE(ISC_LOG_DEBUG(3), mbuf); #endif /* ifdef WANT_QUERYTRACE */ qctx->qtype = qctx->client->query.redirect.qtype; INSIST(qctx->client->query.redirect.rdataset != NULL); RESTORE(qctx->rdataset, qctx->client->query.redirect.rdataset); RESTORE(qctx->sigrdataset, qctx->client->query.redirect.sigrdataset); RESTORE(qctx->db, qctx->client->query.redirect.db); RESTORE(qctx->node, qctx->client->query.redirect.node); RESTORE(qctx->zone, qctx->client->query.redirect.zone); qctx->authoritative = qctx->client->query.redirect.authoritative; /* * Free resources used while recursing. */ ns_client_putrdataset(qctx->client, &qctx->event->rdataset); ns_client_putrdataset(qctx->client, &qctx->event->sigrdataset); if (qctx->event->node != NULL) { dns_db_detachnode(qctx->event->db, &qctx->event->node); } if (qctx->event->db != NULL) { dns_db_detach(&qctx->event->db); } } else { CCTRACE(ISC_LOG_DEBUG(3), "resume from normal recursion"); qctx->authoritative = false; qctx->qtype = qctx->event->qtype; SAVE(qctx->db, qctx->event->db); SAVE(qctx->node, qctx->event->node); SAVE(qctx->rdataset, qctx->event->rdataset); SAVE(qctx->sigrdataset, qctx->event->sigrdataset); } INSIST(qctx->rdataset != NULL); if (qctx->qtype == dns_rdatatype_rrsig || qctx->qtype == dns_rdatatype_sig) { qctx->type = dns_rdatatype_any; } else { qctx->type = qctx->qtype; } CALL_HOOK(NS_QUERY_RESUME_RESTORED, qctx); if (DNS64(qctx->client)) { qctx->client->query.attributes &= ~NS_QUERYATTR_DNS64; qctx->dns64 = true; } if (DNS64EXCLUDE(qctx->client)) { qctx->client->query.attributes &= ~NS_QUERYATTR_DNS64EXCLUDE; qctx->dns64_exclude = true; } if (qctx->rpz_st != NULL && (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0) { /* * Has response policy changed out from under us? */ if (qctx->rpz_st->rpz_ver != qctx->view->rpzs->rpz_ver) { ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, DNS_RPZ_INFO_LEVEL, "query_resume: RPZ settings " "out of date " "(rpz_ver %d, expected %d)", qctx->view->rpzs->rpz_ver, qctx->rpz_st->rpz_ver); QUERY_ERROR(qctx, DNS_R_SERVFAIL); return (ns_query_done(qctx)); } } /* * We'll need some resources... */ qctx->dbuf = ns_client_getnamebuf(qctx->client); if (qctx->dbuf == NULL) { CCTRACE(ISC_LOG_ERROR, "query_resume: ns_client_getnamebuf " "failed (1)"); QUERY_ERROR(qctx, ISC_R_NOMEMORY); return (ns_query_done(qctx)); } qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, &b); if (qctx->fname == NULL) { CCTRACE(ISC_LOG_ERROR, "query_resume: ns_client_newname failed " "(1)"); QUERY_ERROR(qctx, ISC_R_NOMEMORY); return (ns_query_done(qctx)); } if (qctx->rpz_st != NULL && (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0) { tname = qctx->rpz_st->fname; } else if (REDIRECT(qctx->client)) { tname = qctx->client->query.redirect.fname; } else { tname = qctx->event->foundname; } dns_name_copy(tname, qctx->fname); if (qctx->rpz_st != NULL && (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0) { qctx->rpz_st->r.r_result = qctx->event->result; result = qctx->rpz_st->q.result; free_devent(qctx->client, ISC_EVENT_PTR(&qctx->event), &qctx->event); } else if (REDIRECT(qctx->client)) { result = qctx->client->query.redirect.result; } else { result = qctx->event->result; } qctx->resuming = true; return (query_gotanswer(qctx, result)); cleanup: return (result); } static void query_hookresume(isc_task_t *task, isc_event_t *event) { ns_hook_resevent_t *rev = (ns_hook_resevent_t *)event; ns_hookasync_t *hctx = NULL; ns_client_t *client = rev->ev_arg; query_ctx_t *qctx = rev->saved_qctx; bool canceled; CTRACE(ISC_LOG_DEBUG(3), "query_hookresume"); REQUIRE(NS_CLIENT_VALID(client)); REQUIRE(task == client->task); REQUIRE(event->ev_type == NS_EVENT_HOOKASYNCDONE); LOCK(&client->query.fetchlock); if (client->query.hookactx != NULL) { INSIST(rev->ctx == client->query.hookactx); client->query.hookactx = NULL; canceled = false; isc_stdtime_get(&client->now); } else { canceled = true; } UNLOCK(&client->query.fetchlock); SAVE(hctx, rev->ctx); if (client->recursionquota != NULL) { isc_quota_detach(&client->recursionquota); ns_stats_decrement(client->sctx->nsstats, ns_statscounter_recursclients); } LOCK(&client->manager->reclock); if (ISC_LINK_LINKED(client, rlink)) { ISC_LIST_UNLINK(client->manager->recursing, client, rlink); } UNLOCK(&client->manager->reclock); /* * This event is running under a client task, so it's safe to detach * the fetch handle. And it should be done before resuming query * processing below, since that may trigger another recursion or * asynchronous hook event. */ isc_nmhandle_detach(&client->fetchhandle); client->state = NS_CLIENTSTATE_WORKING; if (canceled) { /* * Note: unlike fetch_callback, this function doesn't bother * to check the 'shutdown' condition, as that doesn't seem to * happen in the latest implementation. */ query_error(client, DNS_R_SERVFAIL, __LINE__); /* * There's no other place to free/release any data maintained * in qctx. We need to do it here to prevent leak. */ qctx_clean(qctx); qctx_freedata(qctx); /* * As we're almost done with this client, make sure any internal * resource for hooks will be released (if necessary) via the * QCTX_DESTROYED hook. */ qctx->detach_client = true; } else { switch (rev->hookpoint) { case NS_QUERY_SETUP: (void)query_setup(client, qctx->qtype); break; case NS_QUERY_START_BEGIN: (void)ns__query_start(qctx); break; case NS_QUERY_LOOKUP_BEGIN: (void)query_lookup(qctx); break; case NS_QUERY_RESUME_BEGIN: case NS_QUERY_RESUME_RESTORED: (void)query_resume(qctx); break; case NS_QUERY_GOT_ANSWER_BEGIN: (void)query_gotanswer(qctx, rev->origresult); break; case NS_QUERY_RESPOND_ANY_BEGIN: (void)query_respond_any(qctx); break; case NS_QUERY_ADDANSWER_BEGIN: (void)query_addanswer(qctx); break; case NS_QUERY_NOTFOUND_BEGIN: (void)query_notfound(qctx); break; case NS_QUERY_PREP_DELEGATION_BEGIN: (void)query_prepare_delegation_response(qctx); break; case NS_QUERY_ZONE_DELEGATION_BEGIN: (void)query_zone_delegation(qctx); break; case NS_QUERY_DELEGATION_BEGIN: (void)query_delegation(qctx); break; case NS_QUERY_DELEGATION_RECURSE_BEGIN: (void)query_delegation_recurse(qctx); break; case NS_QUERY_NODATA_BEGIN: (void)query_nodata(qctx, rev->origresult); break; case NS_QUERY_NXDOMAIN_BEGIN: (void)query_nxdomain(qctx, rev->origresult); break; case NS_QUERY_NCACHE_BEGIN: (void)query_ncache(qctx, rev->origresult); break; case NS_QUERY_CNAME_BEGIN: (void)query_cname(qctx); break; case NS_QUERY_DNAME_BEGIN: (void)query_dname(qctx); break; case NS_QUERY_RESPOND_BEGIN: (void)query_respond(qctx); break; case NS_QUERY_PREP_RESPONSE_BEGIN: (void)query_prepresponse(qctx); break; case NS_QUERY_DONE_BEGIN: case NS_QUERY_DONE_SEND: (void)ns_query_done(qctx); break; /* Not all hookpoints can use recursion. Catch violations */ case NS_QUERY_RESPOND_ANY_FOUND: /* due to side effect */ case NS_QUERY_NOTFOUND_RECURSE: /* in recursion */ case NS_QUERY_ZEROTTL_RECURSE: /* in recursion */ default: /* catch-all just in case */ INSIST(false); } } hctx->destroy(&hctx); qctx_destroy(qctx); isc_mem_put(client->mctx, qctx, sizeof(*qctx)); isc_event_free(&event); } isc_result_t ns_query_hookasync(query_ctx_t *qctx, ns_query_starthookasync_t runasync, void *arg) { isc_result_t result; ns_client_t *client = qctx->client; query_ctx_t *saved_qctx = NULL; CTRACE(ISC_LOG_DEBUG(3), "ns_query_hookasync"); REQUIRE(NS_CLIENT_VALID(client)); REQUIRE(client->query.hookactx == NULL); REQUIRE(client->query.fetch == NULL); result = check_recursionquota(client); if (result != ISC_R_SUCCESS) { goto cleanup; } saved_qctx = isc_mem_get(client->mctx, sizeof(*saved_qctx)); qctx_save(qctx, saved_qctx); result = runasync(saved_qctx, client->mctx, arg, client->task, query_hookresume, client, &client->query.hookactx); if (result != ISC_R_SUCCESS) { goto cleanup; } /* * Typically the runasync() function will trigger recursion, but * there is no need to set NS_QUERYATTR_RECURSING. The calling hook * is expected to return NS_HOOK_RETURN, and the RECURSING * attribute won't be checked anywhere. * * Hook-based asynchronous processing cannot coincide with normal * recursion, so we can safely use fetchhandle here. Unlike in * ns_query_recurse(), we attach to the handle only if 'runasync' * succeeds. It should be safe since we're either in the client * task or pausing it. */ isc_nmhandle_attach(client->handle, &client->fetchhandle); return (ISC_R_SUCCESS); cleanup: /* * If we fail, send SERVFAIL now. It may be better to let the caller * decide what to do on failure of this function, but hooks don't have * access to query_error(). */ query_error(client, DNS_R_SERVFAIL, __LINE__); /* * Free all resource related to the query and set detach_client, * similar to the cancel case of query_hookresume; the callers will * simply return on failure of this function, so there's no other * place for this to prevent leak. */ if (saved_qctx != NULL) { qctx_clean(saved_qctx); qctx_freedata(saved_qctx); qctx_destroy(saved_qctx); isc_mem_put(client->mctx, saved_qctx, sizeof(*saved_qctx)); } qctx->detach_client = true; return (result); } /*% * If the query is recursive, check the SERVFAIL cache to see whether * identical queries have failed recently. If we find a match, and it was * from a query with CD=1, *or* if the current query has CD=0, then we just * return SERVFAIL again. This prevents a validation failure from eliciting a * SERVFAIL response to a CD=1 query. */ isc_result_t ns__query_sfcache(query_ctx_t *qctx) { bool failcache; uint32_t flags; /* * The SERVFAIL cache doesn't apply to authoritative queries. */ if (!RECURSIONOK(qctx->client)) { return (ISC_R_COMPLETE); } flags = 0; #ifdef ENABLE_AFL if (qctx->client->sctx->fuzztype == isc_fuzz_resolver) { failcache = false; } else { failcache = dns_badcache_find( qctx->view->failcache, qctx->client->query.qname, qctx->qtype, &flags, &qctx->client->tnow); } #else /* ifdef ENABLE_AFL */ failcache = dns_badcache_find(qctx->view->failcache, qctx->client->query.qname, qctx->qtype, &flags, &qctx->client->tnow); #endif /* ifdef ENABLE_AFL */ if (failcache && (((flags & NS_FAILCACHE_CD) != 0) || ((qctx->client->message->flags & DNS_MESSAGEFLAG_CD) == 0))) { if (isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1))) { char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; dns_name_format(qctx->client->query.qname, namebuf, sizeof(namebuf)); dns_rdatatype_format(qctx->qtype, typebuf, sizeof(typebuf)); ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_DEBUG(1), "servfail cache hit %s/%s (%s)", namebuf, typebuf, ((flags & NS_FAILCACHE_CD) != 0) ? "CD=1" : "CD=" "0"); } qctx->client->attributes |= NS_CLIENTATTR_NOSETFC; QUERY_ERROR(qctx, DNS_R_SERVFAIL); return (ns_query_done(qctx)); } return (ISC_R_COMPLETE); } /*% * Handle response rate limiting (RRL). */ static isc_result_t query_checkrrl(query_ctx_t *qctx, isc_result_t result) { /* * Rate limit these responses to this client. * Do not delay counting and handling obvious referrals, * since those won't come here again. * Delay handling delegations for which we are certain to recurse and * return here (DNS_R_DELEGATION, not a child of one of our * own zones, and recursion enabled) * Don't mess with responses rewritten by RPZ * Count each response at most once. */ /* * XXXMPA the rrl system tests fails sometimes and RRL_CHECKED * is set when we are called the second time preventing the * response being dropped. */ ns_client_log( qctx->client, DNS_LOGCATEGORY_RRL, NS_LOGMODULE_QUERY, ISC_LOG_DEBUG(99), "rrl=%p, HAVECOOKIE=%u, result=%s, " "fname=%p(%u), is_zone=%u, RECURSIONOK=%u, " "query.rpz_st=%p(%u), RRL_CHECKED=%u\n", qctx->client->view->rrl, HAVECOOKIE(qctx->client), isc_result_toid(result), qctx->fname, qctx->fname != NULL ? dns_name_isabsolute(qctx->fname) : 0, qctx->is_zone, RECURSIONOK(qctx->client), qctx->client->query.rpz_st, qctx->client->query.rpz_st != NULL ? ((qctx->client->query.rpz_st->state & DNS_RPZ_REWRITTEN) != 0) : 0, (qctx->client->query.attributes & NS_QUERYATTR_RRL_CHECKED) != 0); if (qctx->view->rrl != NULL && !HAVECOOKIE(qctx->client) && ((qctx->fname != NULL && dns_name_isabsolute(qctx->fname)) || (result == ISC_R_NOTFOUND && !RECURSIONOK(qctx->client))) && !(result == DNS_R_DELEGATION && !qctx->is_zone && RECURSIONOK(qctx->client)) && (qctx->client->query.rpz_st == NULL || (qctx->client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0) && (qctx->client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0) { dns_rdataset_t nc_rdataset; bool wouldlog; dns_fixedname_t fixed; const dns_name_t *constname; char log_buf[DNS_RRL_LOG_BUF_LEN]; isc_result_t nc_result, resp_result; dns_rrl_result_t rrl_result; qctx->client->query.attributes |= NS_QUERYATTR_RRL_CHECKED; wouldlog = isc_log_wouldlog(ns_lctx, DNS_RRL_LOG_DROP); constname = qctx->fname; if (result == DNS_R_NXDOMAIN) { /* * Use the database origin name to rate limit NXDOMAIN */ if (qctx->db != NULL) { constname = dns_db_origin(qctx->db); } resp_result = result; } else if (result == DNS_R_NCACHENXDOMAIN && qctx->rdataset != NULL && dns_rdataset_isassociated(qctx->rdataset) && (qctx->rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { /* * Try to use owner name in the negative cache SOA. */ dns_fixedname_init(&fixed); dns_rdataset_init(&nc_rdataset); for (nc_result = dns_rdataset_first(qctx->rdataset); nc_result == ISC_R_SUCCESS; nc_result = dns_rdataset_next(qctx->rdataset)) { dns_ncache_current(qctx->rdataset, dns_fixedname_name(&fixed), &nc_rdataset); if (nc_rdataset.type == dns_rdatatype_soa) { dns_rdataset_disassociate(&nc_rdataset); constname = dns_fixedname_name(&fixed); break; } dns_rdataset_disassociate(&nc_rdataset); } resp_result = DNS_R_NXDOMAIN; } else if (result == DNS_R_NXRRSET || result == DNS_R_EMPTYNAME) { resp_result = DNS_R_NXRRSET; } else if (result == DNS_R_DELEGATION) { resp_result = result; } else if (result == ISC_R_NOTFOUND) { /* * Handle referral to ".", including when recursion * is off or not requested and the hints have not * been loaded. */ constname = dns_rootname; resp_result = DNS_R_DELEGATION; } else { resp_result = ISC_R_SUCCESS; } rrl_result = dns_rrl( qctx->view, qctx->zone, &qctx->client->peeraddr, TCP(qctx->client), qctx->client->message->rdclass, qctx->qtype, constname, resp_result, qctx->client->now, wouldlog, log_buf, sizeof(log_buf)); if (rrl_result != DNS_RRL_RESULT_OK) { /* * Log dropped or slipped responses in the query * category so that requests are not silently lost. * Starts of rate-limited bursts are logged in * DNS_LOGCATEGORY_RRL. * * Dropped responses are counted with dropped queries * in QryDropped while slipped responses are counted * with other truncated responses in RespTruncated. */ if (wouldlog) { ns_client_log(qctx->client, DNS_LOGCATEGORY_RRL, NS_LOGMODULE_QUERY, DNS_RRL_LOG_DROP, "%s", log_buf); } if (!qctx->view->rrl->log_only) { if (rrl_result == DNS_RRL_RESULT_DROP) { /* * These will also be counted in * ns_statscounter_dropped */ inc_stats(qctx->client, ns_statscounter_ratedropped); QUERY_ERROR(qctx, DNS_R_DROP); } else { /* * These will also be counted in * ns_statscounter_truncatedresp */ inc_stats(qctx->client, ns_statscounter_rateslipped); if (WANTCOOKIE(qctx->client)) { qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA; qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD; qctx->client->message->rcode = dns_rcode_badcookie; } else { qctx->client->message->flags |= DNS_MESSAGEFLAG_TC; if (resp_result == DNS_R_NXDOMAIN) { qctx->client->message ->rcode = dns_rcode_nxdomain; } } } return (DNS_R_DROP); } } } return (ISC_R_SUCCESS); } /*% * Do any RPZ rewriting that may be needed for this query. */ static isc_result_t query_checkrpz(query_ctx_t *qctx, isc_result_t result) { isc_result_t rresult; CCTRACE(ISC_LOG_DEBUG(3), "query_checkrpz"); rresult = rpz_rewrite(qctx->client, qctx->qtype, result, qctx->resuming, qctx->rdataset, qctx->sigrdataset); qctx->rpz_st = qctx->client->query.rpz_st; switch (rresult) { case ISC_R_SUCCESS: break; case ISC_R_NOTFOUND: case DNS_R_DISALLOWED: return (result); case DNS_R_DELEGATION: /* * recursing for NS names or addresses, * so save the main query state */ INSIST(!RECURSING(qctx->client)); qctx->rpz_st->q.qtype = qctx->qtype; qctx->rpz_st->q.is_zone = qctx->is_zone; qctx->rpz_st->q.authoritative = qctx->authoritative; SAVE(qctx->rpz_st->q.zone, qctx->zone); SAVE(qctx->rpz_st->q.db, qctx->db); SAVE(qctx->rpz_st->q.node, qctx->node); SAVE(qctx->rpz_st->q.rdataset, qctx->rdataset); SAVE(qctx->rpz_st->q.sigrdataset, qctx->sigrdataset); dns_name_copy(qctx->fname, qctx->rpz_st->fname); qctx->rpz_st->q.result = result; qctx->client->query.attributes |= NS_QUERYATTR_RECURSING; return (ISC_R_COMPLETE); default: QUERY_ERROR(qctx, rresult); return (ISC_R_COMPLETE); } if (qctx->rpz_st->m.policy != DNS_RPZ_POLICY_MISS) { qctx->rpz_st->state |= DNS_RPZ_REWRITTEN; } if (qctx->rpz_st->m.policy != DNS_RPZ_POLICY_MISS && qctx->rpz_st->m.policy != DNS_RPZ_POLICY_PASSTHRU && (qctx->rpz_st->m.policy != DNS_RPZ_POLICY_TCP_ONLY || !TCP(qctx->client)) && qctx->rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { /* * We got a hit and are going to answer with our * fiction. Ensure that we answer with the name * we looked up even if we were stopped short * in recursion or for a deferral. */ dns_name_copy(qctx->client->query.qname, qctx->fname); rpz_clean(&qctx->zone, &qctx->db, &qctx->node, NULL); if (qctx->rpz_st->m.rdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->rdataset); RESTORE(qctx->rdataset, qctx->rpz_st->m.rdataset); } else { qctx_clean(qctx); } qctx->version = NULL; RESTORE(qctx->node, qctx->rpz_st->m.node); RESTORE(qctx->db, qctx->rpz_st->m.db); RESTORE(qctx->version, qctx->rpz_st->m.version); RESTORE(qctx->zone, qctx->rpz_st->m.zone); /* * Add SOA record to additional section */ if (qctx->rpz_st->m.rpz->addsoa) { bool override_ttl = dns_rdataset_isassociated(qctx->rdataset); rresult = query_addsoa(qctx, override_ttl, DNS_SECTION_ADDITIONAL); if (rresult != ISC_R_SUCCESS) { QUERY_ERROR(qctx, result); return (ISC_R_COMPLETE); } } switch (qctx->rpz_st->m.policy) { case DNS_RPZ_POLICY_TCP_ONLY: qctx->client->message->flags |= DNS_MESSAGEFLAG_TC; if (result == DNS_R_NXDOMAIN || result == DNS_R_NCACHENXDOMAIN) { qctx->client->message->rcode = dns_rcode_nxdomain; } rpz_log_rewrite(qctx->client, false, qctx->rpz_st->m.policy, qctx->rpz_st->m.type, qctx->zone, qctx->rpz_st->p_name, NULL, qctx->rpz_st->m.rpz->num); return (ISC_R_COMPLETE); case DNS_RPZ_POLICY_DROP: QUERY_ERROR(qctx, DNS_R_DROP); rpz_log_rewrite(qctx->client, false, qctx->rpz_st->m.policy, qctx->rpz_st->m.type, qctx->zone, qctx->rpz_st->p_name, NULL, qctx->rpz_st->m.rpz->num); return (ISC_R_COMPLETE); case DNS_RPZ_POLICY_NXDOMAIN: result = DNS_R_NXDOMAIN; qctx->nxrewrite = true; qctx->rpz = true; break; case DNS_RPZ_POLICY_NODATA: qctx->nxrewrite = true; FALLTHROUGH; case DNS_RPZ_POLICY_DNS64: result = DNS_R_NXRRSET; qctx->rpz = true; break; case DNS_RPZ_POLICY_RECORD: result = qctx->rpz_st->m.result; if (qctx->qtype == dns_rdatatype_any && result != DNS_R_CNAME) { /* * We will add all of the rdatasets of * the node by iterating later, * and set the TTL then. */ if (dns_rdataset_isassociated(qctx->rdataset)) { dns_rdataset_disassociate( qctx->rdataset); } } else { /* * We will add this rdataset. */ qctx->rdataset->ttl = ISC_MIN(qctx->rdataset->ttl, qctx->rpz_st->m.ttl); } qctx->rpz = true; break; case DNS_RPZ_POLICY_WILDCNAME: { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_cname_t cname; result = dns_rdataset_first(qctx->rdataset); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdataset_current(qctx->rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &cname, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); result = query_rpzcname(qctx, &cname.cname); if (result != ISC_R_SUCCESS) { return (ISC_R_COMPLETE); } qctx->fname = NULL; qctx->want_restart = true; return (ISC_R_COMPLETE); } case DNS_RPZ_POLICY_CNAME: /* * Add overriding CNAME from a named.conf * response-policy statement */ result = query_rpzcname(qctx, &qctx->rpz_st->m.rpz->cname); if (result != ISC_R_SUCCESS) { return (ISC_R_COMPLETE); } qctx->fname = NULL; qctx->want_restart = true; return (ISC_R_COMPLETE); default: UNREACHABLE(); } /* * Turn off DNSSEC because the results of a * response policy zone cannot verify. */ qctx->client->attributes &= ~(NS_CLIENTATTR_WANTDNSSEC | NS_CLIENTATTR_WANTAD); qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD; ns_client_putrdataset(qctx->client, &qctx->sigrdataset); qctx->rpz_st->q.is_zone = qctx->is_zone; qctx->is_zone = true; rpz_log_rewrite(qctx->client, false, qctx->rpz_st->m.policy, qctx->rpz_st->m.type, qctx->zone, qctx->rpz_st->p_name, NULL, qctx->rpz_st->m.rpz->num); } return (result); } /*% * Add a CNAME to a query response, including translating foo.evil.com and * *.evil.com CNAME *.example.com * to * foo.evil.com CNAME foo.evil.com.example.com */ static isc_result_t query_rpzcname(query_ctx_t *qctx, dns_name_t *cname) { ns_client_t *client; dns_fixedname_t prefix, suffix; unsigned int labels; isc_result_t result; REQUIRE(qctx != NULL && qctx->client != NULL); client = qctx->client; CTRACE(ISC_LOG_DEBUG(3), "query_rpzcname"); labels = dns_name_countlabels(cname); if (labels > 2 && dns_name_iswildcard(cname)) { dns_fixedname_init(&prefix); dns_name_split(client->query.qname, 1, dns_fixedname_name(&prefix), NULL); dns_fixedname_init(&suffix); dns_name_split(cname, labels - 1, NULL, dns_fixedname_name(&suffix)); result = dns_name_concatenate(dns_fixedname_name(&prefix), dns_fixedname_name(&suffix), qctx->fname, NULL); if (result == DNS_R_NAMETOOLONG) { client->message->rcode = dns_rcode_yxdomain; } else if (result != ISC_R_SUCCESS) { return (result); } } else { dns_name_copy(cname, qctx->fname); } ns_client_keepname(client, qctx->fname, qctx->dbuf); result = query_addcname(qctx, dns_trust_authanswer, qctx->rpz_st->m.ttl); if (result != ISC_R_SUCCESS) { return (result); } rpz_log_rewrite(client, false, qctx->rpz_st->m.policy, qctx->rpz_st->m.type, qctx->rpz_st->m.zone, qctx->rpz_st->p_name, qctx->fname, qctx->rpz_st->m.rpz->num); ns_client_qnamereplace(client, qctx->fname); /* * Turn off DNSSEC because the results of a * response policy zone cannot verify. */ client->attributes &= ~(NS_CLIENTATTR_WANTDNSSEC | NS_CLIENTATTR_WANTAD); return (ISC_R_SUCCESS); } /*% * Check the configured trust anchors for a root zone trust anchor * with a key id that matches qctx->client->query.root_key_sentinel_keyid. * * Return true when found, otherwise return false. */ static bool has_ta(query_ctx_t *qctx) { dns_keytable_t *keytable = NULL; dns_keynode_t *keynode = NULL; dns_rdataset_t dsset; dns_keytag_t sentinel = qctx->client->query.root_key_sentinel_keyid; isc_result_t result; result = dns_view_getsecroots(qctx->view, &keytable); if (result != ISC_R_SUCCESS) { return (false); } result = dns_keytable_find(keytable, dns_rootname, &keynode); if (result != ISC_R_SUCCESS) { if (keynode != NULL) { dns_keytable_detachkeynode(keytable, &keynode); } dns_keytable_detach(&keytable); return (false); } dns_rdataset_init(&dsset); if (dns_keynode_dsset(keynode, &dsset)) { for (result = dns_rdataset_first(&dsset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&dsset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_ds_t ds; dns_rdata_reset(&rdata); dns_rdataset_current(&dsset, &rdata); result = dns_rdata_tostruct(&rdata, &ds, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (ds.key_tag == sentinel) { dns_keytable_detachkeynode(keytable, &keynode); dns_keytable_detach(&keytable); dns_rdataset_disassociate(&dsset); return (true); } } dns_rdataset_disassociate(&dsset); } if (keynode != NULL) { dns_keytable_detachkeynode(keytable, &keynode); } dns_keytable_detach(&keytable); return (false); } /*% * Check if a root key sentinel SERVFAIL should be returned. */ static bool root_key_sentinel_return_servfail(query_ctx_t *qctx, isc_result_t result) { /* * Are we looking at a "root-key-sentinel" query? */ if (!qctx->client->query.root_key_sentinel_is_ta && !qctx->client->query.root_key_sentinel_not_ta) { return (false); } /* * We only care about the query if 'result' indicates we have a cached * answer. */ switch (result) { case ISC_R_SUCCESS: case DNS_R_CNAME: case DNS_R_DNAME: case DNS_R_NCACHENXDOMAIN: case DNS_R_NCACHENXRRSET: break; default: return (false); } /* * Do we meet the specified conditions to return SERVFAIL? */ if (!qctx->is_zone && qctx->rdataset->trust == dns_trust_secure && ((qctx->client->query.root_key_sentinel_is_ta && !has_ta(qctx)) || (qctx->client->query.root_key_sentinel_not_ta && has_ta(qctx)))) { return (true); } /* * As special processing may only be triggered by the original QNAME, * disable it after following a CNAME/DNAME. */ qctx->client->query.root_key_sentinel_is_ta = false; qctx->client->query.root_key_sentinel_not_ta = false; return (false); } /*% * If serving stale answers is allowed, set up 'qctx' to look for one and * return true; otherwise, return false. */ static bool query_usestale(query_ctx_t *qctx, isc_result_t result) { if ((qctx->client->query.dboptions & DNS_DBFIND_STALEOK) != 0) { /* * Query was already using stale, if that didn't work the * last time, it won't work this time either. */ return (false); } if (qctx->refresh_rrset) { /* * This is a refreshing query, we have already prioritized * stale data, so don't enable serve-stale again. */ return (false); } if (result == DNS_R_DUPLICATE || result == DNS_R_DROP || result == ISC_R_ALREADYRUNNING) { /* * Don't enable serve-stale if the result signals a duplicate * query or a query that is being dropped or can't proceed * because of a recursion loop. */ return (false); } qctx_clean(qctx); qctx_freedata(qctx); if (dns_view_staleanswerenabled(qctx->client->view)) { isc_result_t ret; ret = query_getdb(qctx->client, qctx->client->query.qname, qctx->client->query.qtype, qctx->options, &qctx->zone, &qctx->db, &qctx->version, &qctx->is_zone); if (ret != ISC_R_SUCCESS) { /* * Failed to get the database, unexpected, but let us * at least abandon serve-stale. */ return (false); } qctx->client->query.dboptions |= DNS_DBFIND_STALEOK; if (qctx->client->query.fetch != NULL) { dns_resolver_destroyfetch(&qctx->client->query.fetch); } /* * Start the stale-refresh-time window in case there was a * resolver query timeout. */ if (qctx->resuming && result == ISC_R_TIMEDOUT) { qctx->client->query.dboptions |= DNS_DBFIND_STALESTART; } return (true); } return (false); } /*% * Continue after doing a database lookup or returning from * recursion, and call out to the next function depending on the * result from the search. */ static isc_result_t query_gotanswer(query_ctx_t *qctx, isc_result_t result) { char errmsg[256]; CCTRACE(ISC_LOG_DEBUG(3), "query_gotanswer"); CALL_HOOK(NS_QUERY_GOT_ANSWER_BEGIN, qctx); if (query_checkrrl(qctx, result) != ISC_R_SUCCESS) { return (ns_query_done(qctx)); } if (!dns_name_equal(qctx->client->query.qname, dns_rootname)) { result = query_checkrpz(qctx, result); if (result == ISC_R_NOTFOUND) { /* * RPZ not configured for this view. */ goto root_key_sentinel; } if (RECURSING(qctx->client) && result == DNS_R_DISALLOWED) { /* * We are recursing, and thus RPZ processing is not * allowed at the moment. This could happen on a * "stale-answer-client-timeout" lookup. In this case, * bail out and wait for recursion to complete, as we * we can't perform the RPZ rewrite rules. */ return (result); } if (result == ISC_R_COMPLETE) { return (ns_query_done(qctx)); } } root_key_sentinel: /* * If required, handle special "root-key-sentinel-is-ta-" and * "root-key-sentinel-not-ta-" labels by returning SERVFAIL. */ if (root_key_sentinel_return_servfail(qctx, result)) { /* * Don't record this response in the SERVFAIL cache. */ qctx->client->attributes |= NS_CLIENTATTR_NOSETFC; QUERY_ERROR(qctx, DNS_R_SERVFAIL); return (ns_query_done(qctx)); } switch (result) { case ISC_R_SUCCESS: return (query_prepresponse(qctx)); case DNS_R_GLUE: case DNS_R_ZONECUT: INSIST(qctx->is_zone); qctx->authoritative = false; return (query_prepresponse(qctx)); case ISC_R_NOTFOUND: return (query_notfound(qctx)); case DNS_R_DELEGATION: return (query_delegation(qctx)); case DNS_R_EMPTYNAME: case DNS_R_NXRRSET: return (query_nodata(qctx, result)); case DNS_R_EMPTYWILD: case DNS_R_NXDOMAIN: return (query_nxdomain(qctx, result)); case DNS_R_COVERINGNSEC: return (query_coveringnsec(qctx)); case DNS_R_NCACHENXDOMAIN: result = query_redirect(qctx, result); if (result != ISC_R_COMPLETE) { return (result); } return (query_ncache(qctx, DNS_R_NCACHENXDOMAIN)); case DNS_R_NCACHENXRRSET: return (query_ncache(qctx, DNS_R_NCACHENXRRSET)); case DNS_R_CNAME: return (query_cname(qctx)); case DNS_R_DNAME: return (query_dname(qctx)); default: /* * Something has gone wrong. */ snprintf(errmsg, sizeof(errmsg) - 1, "query_gotanswer: unexpected error: %s", isc_result_totext(result)); CCTRACE(ISC_LOG_ERROR, errmsg); if (query_usestale(qctx, result)) { /* * If serve-stale is enabled, query_usestale() already * set up 'qctx' for looking up a stale response. */ return (query_lookup(qctx)); } /* * Regardless of the triggering result, we definitely * want to return SERVFAIL from here. */ qctx->client->rcode_override = dns_rcode_servfail; QUERY_ERROR(qctx, result); return (ns_query_done(qctx)); } cleanup: return (result); } static void query_addnoqnameproof(query_ctx_t *qctx) { ns_client_t *client = qctx->client; isc_buffer_t *dbuf, b; dns_name_t *fname = NULL; dns_rdataset_t *neg = NULL, *negsig = NULL; isc_result_t result = ISC_R_NOMEMORY; CTRACE(ISC_LOG_DEBUG(3), "query_addnoqnameproof"); if (qctx->noqname == NULL) { return; } dbuf = ns_client_getnamebuf(client); if (dbuf == NULL) { goto cleanup; } fname = ns_client_newname(client, dbuf, &b); neg = ns_client_newrdataset(client); negsig = ns_client_newrdataset(client); if (fname == NULL || neg == NULL || negsig == NULL) { goto cleanup; } result = dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); query_addrrset(qctx, &fname, &neg, &negsig, dbuf, DNS_SECTION_AUTHORITY); if ((qctx->noqname->attributes & DNS_RDATASETATTR_CLOSEST) == 0) { goto cleanup; } if (fname == NULL) { dbuf = ns_client_getnamebuf(client); if (dbuf == NULL) { goto cleanup; } fname = ns_client_newname(client, dbuf, &b); } if (neg == NULL) { neg = ns_client_newrdataset(client); } else if (dns_rdataset_isassociated(neg)) { dns_rdataset_disassociate(neg); } if (negsig == NULL) { negsig = ns_client_newrdataset(client); } else if (dns_rdataset_isassociated(negsig)) { dns_rdataset_disassociate(negsig); } if (fname == NULL || neg == NULL || negsig == NULL) { goto cleanup; } result = dns_rdataset_getclosest(qctx->noqname, fname, neg, negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); query_addrrset(qctx, &fname, &neg, &negsig, dbuf, DNS_SECTION_AUTHORITY); cleanup: if (neg != NULL) { ns_client_putrdataset(client, &neg); } if (negsig != NULL) { ns_client_putrdataset(client, &negsig); } if (fname != NULL) { ns_client_releasename(client, &fname); } } /*% * Build the response for a query for type ANY. */ static isc_result_t query_respond_any(query_ctx_t *qctx) { bool found = false, hidden = false; dns_rdatasetiter_t *rdsiter = NULL; isc_result_t result = ISC_R_UNSET; dns_rdatatype_t onetype = 0; /* type to use for minimal-any */ isc_buffer_t b; CCTRACE(ISC_LOG_DEBUG(3), "query_respond_any"); CALL_HOOK(NS_QUERY_RESPOND_ANY_BEGIN, qctx); result = dns_db_allrdatasets(qctx->db, qctx->node, qctx->version, 0, 0, &rdsiter); if (result != ISC_R_SUCCESS) { CCTRACE(ISC_LOG_ERROR, "query_respond_any: allrdatasets " "failed"); QUERY_ERROR(qctx, result); return (ns_query_done(qctx)); } /* * Calling query_addrrset() with a non-NULL dbuf is going * to either keep or release the name. We don't want it to * release fname, since we may have to call query_addrrset() * more than once. That means we have to call ns_client_keepname() * now, and pass a NULL dbuf to query_addrrset(). * * If we do a query_addrrset() below, we must set qctx->fname to * NULL before leaving this block, otherwise we might try to * cleanup qctx->fname even though we're using it! */ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf); qctx->tname = qctx->fname; result = dns_rdatasetiter_first(rdsiter); while (result == ISC_R_SUCCESS) { dns_rdatasetiter_current(rdsiter, qctx->rdataset); /* * We found an NS RRset; no need to add one later. */ if (qctx->qtype == dns_rdatatype_any && qctx->rdataset->type == dns_rdatatype_ns) { qctx->answer_has_ns = true; } /* * Note: if we're in this function, then qctx->type * is guaranteed to be ANY, but qctx->qtype (i.e. the * original type requested) might have been RRSIG or * SIG; we need to check for that. */ if (qctx->is_zone && qctx->qtype == dns_rdatatype_any && !dns_db_issecure(qctx->db) && dns_rdatatype_isdnssec(qctx->rdataset->type)) { /* * The zone may be transitioning from insecure * to secure. Hide DNSSEC records from ANY queries. */ dns_rdataset_disassociate(qctx->rdataset); hidden = true; } else if (qctx->view->minimal_any && !TCP(qctx->client) && !WANTDNSSEC(qctx->client) && qctx->qtype == dns_rdatatype_any && (qctx->rdataset->type == dns_rdatatype_sig || qctx->rdataset->type == dns_rdatatype_rrsig)) { CCTRACE(ISC_LOG_DEBUG(5), "query_respond_any: " "minimal-any skip signature"); dns_rdataset_disassociate(qctx->rdataset); } else if (qctx->view->minimal_any && !TCP(qctx->client) && onetype != 0 && qctx->rdataset->type != onetype && qctx->rdataset->covers != onetype) { CCTRACE(ISC_LOG_DEBUG(5), "query_respond_any: " "minimal-any skip rdataset"); dns_rdataset_disassociate(qctx->rdataset); } else if ((qctx->qtype == dns_rdatatype_any || qctx->rdataset->type == qctx->qtype) && qctx->rdataset->type != 0) { if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client)) { qctx->noqname = qctx->rdataset; } else { qctx->noqname = NULL; } qctx->rpz_st = qctx->client->query.rpz_st; if (qctx->rpz_st != NULL) { qctx->rdataset->ttl = ISC_MIN(qctx->rdataset->ttl, qctx->rpz_st->m.ttl); } if (!qctx->is_zone && RECURSIONOK(qctx->client)) { dns_name_t *name; name = (qctx->fname != NULL) ? qctx->fname : qctx->tname; query_prefetch(qctx->client, name, qctx->rdataset); } /* * Remember the first RRtype we find so we * can skip others with minimal-any. */ if (qctx->rdataset->type == dns_rdatatype_sig || qctx->rdataset->type == dns_rdatatype_rrsig) { onetype = qctx->rdataset->covers; } else { onetype = qctx->rdataset->type; } query_addrrset(qctx, (qctx->fname != NULL) ? &qctx->fname : &qctx->tname, &qctx->rdataset, NULL, NULL, DNS_SECTION_ANSWER); query_addnoqnameproof(qctx); found = true; INSIST(qctx->tname != NULL); /* * rdataset is non-NULL only in certain * pathological cases involving DNAMEs. */ if (qctx->rdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->rdataset); } qctx->rdataset = ns_client_newrdataset(qctx->client); if (qctx->rdataset == NULL) { break; } } else { /* * We're not interested in this rdataset. */ dns_rdataset_disassociate(qctx->rdataset); } result = dns_rdatasetiter_next(rdsiter); } dns_rdatasetiter_destroy(&rdsiter); if (result != ISC_R_NOMORE) { CCTRACE(ISC_LOG_ERROR, "query_respond_any: rdataset iterator " "failed"); QUERY_ERROR(qctx, DNS_R_SERVFAIL); return (ns_query_done(qctx)); } if (found) { /* * Call hook if any answers were found. * Do this before releasing qctx->fname, in case * the hook function needs it. */ CALL_HOOK(NS_QUERY_RESPOND_ANY_FOUND, qctx); } if (qctx->fname != NULL) { dns_message_puttempname(qctx->client->message, &qctx->fname); } if (found) { /* * At least one matching rdataset was found */ query_addauth(qctx); } else if (qctx->qtype == dns_rdatatype_rrsig || qctx->qtype == dns_rdatatype_sig) { /* * No matching rdatasets were found, but we got * here on a search for RRSIG/SIG, so that's okay. */ if (!qctx->is_zone) { qctx->authoritative = false; qctx->client->attributes &= ~NS_CLIENTATTR_RA; query_addauth(qctx); return (ns_query_done(qctx)); } if (qctx->qtype == dns_rdatatype_rrsig && dns_db_issecure(qctx->db)) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(qctx->client->query.qname, namebuf, sizeof(namebuf)); ns_client_log(qctx->client, DNS_LOGCATEGORY_DNSSEC, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "missing signature for %s", namebuf); } qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, &b); return (query_sign_nodata(qctx)); } else if (!hidden) { /* * No matching rdatasets were found and nothing was * deliberately hidden: something must have gone wrong. */ QUERY_ERROR(qctx, DNS_R_SERVFAIL); } return (ns_query_done(qctx)); cleanup: return (result); } /* * Set the expire time, if requested, when answering from a secondary, * mirror, or primary zone. */ static void query_getexpire(query_ctx_t *qctx) { dns_zone_t *raw = NULL, *mayberaw; CCTRACE(ISC_LOG_DEBUG(3), "query_getexpire"); if (qctx->zone == NULL || !qctx->is_zone || qctx->qtype != dns_rdatatype_soa || qctx->client->query.restarts != 0 || (qctx->client->attributes & NS_CLIENTATTR_WANTEXPIRE) == 0) { return; } dns_zone_getraw(qctx->zone, &raw); mayberaw = (raw != NULL) ? raw : qctx->zone; if (dns_zone_gettype(mayberaw) == dns_zone_secondary || dns_zone_gettype(mayberaw) == dns_zone_mirror) { isc_time_t expiretime; uint32_t secs; dns_zone_getexpiretime(qctx->zone, &expiretime); secs = isc_time_seconds(&expiretime); if (secs >= qctx->client->now && qctx->result == ISC_R_SUCCESS) { qctx->client->attributes |= NS_CLIENTATTR_HAVEEXPIRE; qctx->client->expire = secs - qctx->client->now; } } else if (dns_zone_gettype(mayberaw) == dns_zone_primary) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_soa_t soa; result = dns_rdataset_first(qctx->rdataset); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdataset_current(qctx->rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &soa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); qctx->client->expire = soa.expire; qctx->client->attributes |= NS_CLIENTATTR_HAVEEXPIRE; } if (raw != NULL) { dns_zone_detach(&raw); } } /*% * Fill the ANSWER section of a positive response. */ static isc_result_t query_addanswer(query_ctx_t *qctx) { dns_rdataset_t **sigrdatasetp = NULL; isc_result_t result = ISC_R_UNSET; CCTRACE(ISC_LOG_DEBUG(3), "query_addanswer"); CALL_HOOK(NS_QUERY_ADDANSWER_BEGIN, qctx); /* * On normal lookups, clear any rdatasets that were added on a * lookup due to stale-answer-client-timeout. Do not clear if we * are going to refresh the RRset, because the stale contents are * prioritized. */ if (QUERY_STALEOK(&qctx->client->query) && !QUERY_STALETIMEOUT(&qctx->client->query) && !qctx->refresh_rrset) { CCTRACE(ISC_LOG_DEBUG(3), "query_clear_stale"); query_clear_stale(qctx->client); /* * We can clear the attribute to prevent redundant clearing * in subsequent lookups. */ qctx->client->query.attributes &= ~NS_QUERYATTR_STALEOK; } if (qctx->dns64) { result = query_dns64(qctx); qctx->noqname = NULL; dns_rdataset_disassociate(qctx->rdataset); dns_message_puttemprdataset(qctx->client->message, &qctx->rdataset); if (result == ISC_R_NOMORE) { #ifndef dns64_bis_return_excluded_addresses if (qctx->dns64_exclude) { if (!qctx->is_zone) { return (ns_query_done(qctx)); } /* * Add a fake SOA record. */ (void)query_addsoa(qctx, 600, DNS_SECTION_AUTHORITY); return (ns_query_done(qctx)); } #endif /* ifndef dns64_bis_return_excluded_addresses */ if (qctx->is_zone) { return (query_nodata(qctx, DNS_R_NXDOMAIN)); } else { return (query_ncache(qctx, DNS_R_NXDOMAIN)); } } else if (result != ISC_R_SUCCESS) { qctx->result = result; return (ns_query_done(qctx)); } } else if (qctx->client->query.dns64_aaaaok != NULL) { query_filter64(qctx); ns_client_putrdataset(qctx->client, &qctx->rdataset); } else { if (!qctx->is_zone && RECURSIONOK(qctx->client) && !QUERY_STALETIMEOUT(&qctx->client->query)) { query_prefetch(qctx->client, qctx->fname, qctx->rdataset); } if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) { sigrdatasetp = &qctx->sigrdataset; } query_addrrset(qctx, &qctx->fname, &qctx->rdataset, sigrdatasetp, qctx->dbuf, DNS_SECTION_ANSWER); } return (ISC_R_COMPLETE); cleanup: return (result); } /*% * Build a response for a "normal" query, for a type other than ANY, * for which we have an answer (either positive or negative). */ static isc_result_t query_respond(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; CCTRACE(ISC_LOG_DEBUG(3), "query_respond"); /* * Check to see if the AAAA RRset has non-excluded addresses * in it. If not look for a A RRset. */ INSIST(qctx->client->query.dns64_aaaaok == NULL); if (qctx->qtype == dns_rdatatype_aaaa && !qctx->dns64_exclude && !ISC_LIST_EMPTY(qctx->view->dns64) && qctx->client->message->rdclass == dns_rdataclass_in && !dns64_aaaaok(qctx->client, qctx->rdataset, qctx->sigrdataset)) { /* * Look to see if there are A records for this name. */ qctx->client->query.dns64_ttl = qctx->rdataset->ttl; SAVE(qctx->client->query.dns64_aaaa, qctx->rdataset); SAVE(qctx->client->query.dns64_sigaaaa, qctx->sigrdataset); ns_client_releasename(qctx->client, &qctx->fname); dns_db_detachnode(qctx->db, &qctx->node); qctx->type = qctx->qtype = dns_rdatatype_a; qctx->dns64_exclude = qctx->dns64 = true; return (query_lookup(qctx)); } /* * XXX: This hook is meant to be at the top of this function, * but is postponed until after DNS64 in order to avoid an * assertion if the hook causes recursion. (When DNS64 also * becomes a plugin, it will be necessary to find some * other way to prevent that assertion, since the order in * which plugins are configured can't be enforced.) */ CALL_HOOK(NS_QUERY_RESPOND_BEGIN, qctx); if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client)) { qctx->noqname = qctx->rdataset; } else { qctx->noqname = NULL; } /* * Special case NS handling */ if (qctx->is_zone && qctx->qtype == dns_rdatatype_ns) { /* * We've already got an NS, no need to add one in * the authority section */ if (dns_name_equal(qctx->client->query.qname, dns_db_origin(qctx->db))) { qctx->answer_has_ns = true; } /* * Always add glue for root priming queries, regardless * of "minimal-responses" setting. */ if (dns_name_equal(qctx->client->query.qname, dns_rootname)) { qctx->client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; dns_db_attach(qctx->db, &qctx->client->query.gluedb); } } /* * Set expire time */ query_getexpire(qctx); result = query_addanswer(qctx); if (result != ISC_R_COMPLETE) { return (result); } query_addnoqnameproof(qctx); /* * 'qctx->rdataset' will only be non-NULL here if the ANSWER section of * the message to be sent to the client already contains an RRset with * the same owner name and the same type as 'qctx->rdataset'. This * should never happen, with one exception: when chasing DNAME records, * one of the DNAME records placed in the ANSWER section may turn out * to be the final answer to the client's query, but we have no way of * knowing that until now. In such a case, 'qctx->rdataset' will be * freed later, so we do not need to free it here. */ INSIST(qctx->rdataset == NULL || qctx->qtype == dns_rdatatype_dname); query_addauth(qctx); return (ns_query_done(qctx)); cleanup: return (result); } static isc_result_t query_dns64(query_ctx_t *qctx) { ns_client_t *client = qctx->client; dns_aclenv_t *env = client->manager->aclenv; dns_name_t *name, *mname; dns_rdata_t *dns64_rdata; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdatalist_t *dns64_rdatalist; dns_rdataset_t *dns64_rdataset; dns_rdataset_t *mrdataset; isc_buffer_t *buffer; isc_region_t r; isc_result_t result; dns_view_t *view = client->view; isc_netaddr_t netaddr; dns_dns64_t *dns64; unsigned int flags = 0; const dns_section_t section = DNS_SECTION_ANSWER; /*% * To the current response for 'qctx->client', add the answer RRset * '*rdatasetp' and an optional signature set '*sigrdatasetp', with * owner name '*namep', to the answer section, unless they are * already there. Also add any pertinent additional data. * * If 'qctx->dbuf' is not NULL, then 'qctx->fname' is the name * whose data is stored 'qctx->dbuf'. In this case, * query_addrrset() guarantees that when it returns the name * will either have been kept or released. */ CTRACE(ISC_LOG_DEBUG(3), "query_dns64"); qctx->qtype = qctx->type = dns_rdatatype_aaaa; name = qctx->fname; mname = NULL; mrdataset = NULL; buffer = NULL; dns64_rdata = NULL; dns64_rdataset = NULL; dns64_rdatalist = NULL; result = dns_message_findname( client->message, section, name, dns_rdatatype_aaaa, qctx->rdataset->covers, &mname, &mrdataset); if (result == ISC_R_SUCCESS) { /* * We've already got an RRset of the given name and type. * There's nothing else to do; */ CTRACE(ISC_LOG_DEBUG(3), "query_dns64: dns_message_findname " "succeeded: done"); if (qctx->dbuf != NULL) { ns_client_releasename(client, &qctx->fname); } return (ISC_R_SUCCESS); } else if (result == DNS_R_NXDOMAIN) { /* * The name doesn't exist. */ if (qctx->dbuf != NULL) { ns_client_keepname(client, name, qctx->dbuf); } dns_message_addname(client->message, name, section); qctx->fname = NULL; mname = name; } else { RUNTIME_CHECK(result == DNS_R_NXRRSET); if (qctx->dbuf != NULL) { ns_client_releasename(client, &qctx->fname); } } if (qctx->rdataset->trust != dns_trust_secure) { client->query.attributes &= ~NS_QUERYATTR_SECURE; } isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); isc_buffer_allocate(client->mctx, &buffer, view->dns64cnt * 16 * dns_rdataset_count(qctx->rdataset)); result = dns_message_gettemprdataset(client->message, &dns64_rdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } result = dns_message_gettemprdatalist(client->message, &dns64_rdatalist); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_rdatalist_init(dns64_rdatalist); dns64_rdatalist->rdclass = dns_rdataclass_in; dns64_rdatalist->type = dns_rdatatype_aaaa; if (client->query.dns64_ttl != UINT32_MAX) { dns64_rdatalist->ttl = ISC_MIN(qctx->rdataset->ttl, client->query.dns64_ttl); } else { dns64_rdatalist->ttl = ISC_MIN(qctx->rdataset->ttl, 600); } if (RECURSIONOK(client)) { flags |= DNS_DNS64_RECURSIVE; } /* * We use the signatures from the A lookup to set DNS_DNS64_DNSSEC * as this provides a easy way to see if the answer was signed. */ if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL && dns_rdataset_isassociated(qctx->sigrdataset)) { flags |= DNS_DNS64_DNSSEC; } for (result = dns_rdataset_first(qctx->rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(qctx->rdataset)) { for (dns64 = ISC_LIST_HEAD(client->view->dns64); dns64 != NULL; dns64 = dns_dns64_next(dns64)) { dns_rdataset_current(qctx->rdataset, &rdata); isc_buffer_availableregion(buffer, &r); INSIST(r.length >= 16); result = dns_dns64_aaaafroma(dns64, &netaddr, client->signer, env, flags, rdata.data, r.base); if (result != ISC_R_SUCCESS) { dns_rdata_reset(&rdata); continue; } isc_buffer_add(buffer, 16); isc_buffer_remainingregion(buffer, &r); isc_buffer_forward(buffer, 16); result = dns_message_gettemprdata(client->message, &dns64_rdata); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_rdata_init(dns64_rdata); dns_rdata_fromregion(dns64_rdata, dns_rdataclass_in, dns_rdatatype_aaaa, &r); ISC_LIST_APPEND(dns64_rdatalist->rdata, dns64_rdata, link); dns64_rdata = NULL; dns_rdata_reset(&rdata); } } if (result != ISC_R_NOMORE) { goto cleanup; } if (ISC_LIST_EMPTY(dns64_rdatalist->rdata)) { goto cleanup; } result = dns_rdatalist_tordataset(dns64_rdatalist, dns64_rdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_rdataset_setownercase(dns64_rdataset, mname); client->query.attributes |= NS_QUERYATTR_NOADDITIONAL; dns64_rdataset->trust = qctx->rdataset->trust; query_addtoname(mname, dns64_rdataset); query_setorder(qctx, mname, dns64_rdataset); dns64_rdataset = NULL; dns64_rdatalist = NULL; dns_message_takebuffer(client->message, &buffer); inc_stats(client, ns_statscounter_dns64); result = ISC_R_SUCCESS; cleanup: if (buffer != NULL) { isc_buffer_free(&buffer); } if (dns64_rdata != NULL) { dns_message_puttemprdata(client->message, &dns64_rdata); } if (dns64_rdataset != NULL) { dns_message_puttemprdataset(client->message, &dns64_rdataset); } if (dns64_rdatalist != NULL) { for (dns64_rdata = ISC_LIST_HEAD(dns64_rdatalist->rdata); dns64_rdata != NULL; dns64_rdata = ISC_LIST_HEAD(dns64_rdatalist->rdata)) { ISC_LIST_UNLINK(dns64_rdatalist->rdata, dns64_rdata, link); dns_message_puttemprdata(client->message, &dns64_rdata); } dns_message_puttemprdatalist(client->message, &dns64_rdatalist); } CTRACE(ISC_LOG_DEBUG(3), "query_dns64: done"); return (result); } static void query_filter64(query_ctx_t *qctx) { ns_client_t *client = qctx->client; dns_name_t *name, *mname; dns_rdata_t *myrdata; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdatalist_t *myrdatalist; dns_rdataset_t *myrdataset; isc_buffer_t *buffer; isc_region_t r; isc_result_t result; unsigned int i; const dns_section_t section = DNS_SECTION_ANSWER; CTRACE(ISC_LOG_DEBUG(3), "query_filter64"); INSIST(client->query.dns64_aaaaok != NULL); INSIST(client->query.dns64_aaaaoklen == dns_rdataset_count(qctx->rdataset)); name = qctx->fname; mname = NULL; buffer = NULL; myrdata = NULL; myrdataset = NULL; myrdatalist = NULL; result = dns_message_findname( client->message, section, name, dns_rdatatype_aaaa, qctx->rdataset->covers, &mname, &myrdataset); if (result == ISC_R_SUCCESS) { /* * We've already got an RRset of the given name and type. * There's nothing else to do; */ CTRACE(ISC_LOG_DEBUG(3), "query_filter64: dns_message_findname " "succeeded: done"); if (qctx->dbuf != NULL) { ns_client_releasename(client, &qctx->fname); } return; } else if (result == DNS_R_NXDOMAIN) { mname = name; qctx->fname = NULL; } else { RUNTIME_CHECK(result == DNS_R_NXRRSET); if (qctx->dbuf != NULL) { ns_client_releasename(client, &qctx->fname); } qctx->dbuf = NULL; } if (qctx->rdataset->trust != dns_trust_secure) { client->query.attributes &= ~NS_QUERYATTR_SECURE; } isc_buffer_allocate(client->mctx, &buffer, 16 * dns_rdataset_count(qctx->rdataset)); result = dns_message_gettemprdataset(client->message, &myrdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } result = dns_message_gettemprdatalist(client->message, &myrdatalist); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_rdatalist_init(myrdatalist); myrdatalist->rdclass = dns_rdataclass_in; myrdatalist->type = dns_rdatatype_aaaa; myrdatalist->ttl = qctx->rdataset->ttl; i = 0; for (result = dns_rdataset_first(qctx->rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(qctx->rdataset)) { if (!client->query.dns64_aaaaok[i++]) { continue; } dns_rdataset_current(qctx->rdataset, &rdata); INSIST(rdata.length == 16); isc_buffer_putmem(buffer, rdata.data, rdata.length); isc_buffer_remainingregion(buffer, &r); isc_buffer_forward(buffer, rdata.length); result = dns_message_gettemprdata(client->message, &myrdata); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_rdata_init(myrdata); dns_rdata_fromregion(myrdata, dns_rdataclass_in, dns_rdatatype_aaaa, &r); ISC_LIST_APPEND(myrdatalist->rdata, myrdata, link); myrdata = NULL; dns_rdata_reset(&rdata); } if (result != ISC_R_NOMORE) { goto cleanup; } result = dns_rdatalist_tordataset(myrdatalist, myrdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_rdataset_setownercase(myrdataset, name); client->query.attributes |= NS_QUERYATTR_NOADDITIONAL; if (mname == name) { if (qctx->dbuf != NULL) { ns_client_keepname(client, name, qctx->dbuf); } dns_message_addname(client->message, name, section); qctx->dbuf = NULL; } myrdataset->trust = qctx->rdataset->trust; query_addtoname(mname, myrdataset); query_setorder(qctx, mname, myrdataset); myrdataset = NULL; myrdatalist = NULL; dns_message_takebuffer(client->message, &buffer); cleanup: if (buffer != NULL) { isc_buffer_free(&buffer); } if (myrdata != NULL) { dns_message_puttemprdata(client->message, &myrdata); } if (myrdataset != NULL) { dns_message_puttemprdataset(client->message, &myrdataset); } if (myrdatalist != NULL) { for (myrdata = ISC_LIST_HEAD(myrdatalist->rdata); myrdata != NULL; myrdata = ISC_LIST_HEAD(myrdatalist->rdata)) { ISC_LIST_UNLINK(myrdatalist->rdata, myrdata, link); dns_message_puttemprdata(client->message, &myrdata); } dns_message_puttemprdatalist(client->message, &myrdatalist); } if (qctx->dbuf != NULL) { ns_client_releasename(client, &name); } CTRACE(ISC_LOG_DEBUG(3), "query_filter64: done"); } /*% * Handle the case of a name not being found in a database lookup. * Called from query_gotanswer(). Passes off processing to * query_delegation() for a root referral if appropriate. */ static isc_result_t query_notfound(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; CCTRACE(ISC_LOG_DEBUG(3), "query_notfound"); CALL_HOOK(NS_QUERY_NOTFOUND_BEGIN, qctx); INSIST(!qctx->is_zone); if (qctx->db != NULL) { dns_db_detach(&qctx->db); } /* * If the cache doesn't even have the root NS, * try to get that from the hints DB. */ if (qctx->view->hints != NULL) { dns_clientinfomethods_t cm; dns_clientinfo_t ci; dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, qctx->client, NULL); dns_db_attach(qctx->view->hints, &qctx->db); result = dns_db_findext(qctx->db, dns_rootname, NULL, dns_rdatatype_ns, 0, qctx->client->now, &qctx->node, qctx->fname, &cm, &ci, qctx->rdataset, qctx->sigrdataset); } else { /* We have no hints. */ result = ISC_R_FAILURE; } if (result != ISC_R_SUCCESS) { /* * Nonsensical root hints may require cleanup. */ qctx_clean(qctx); /* * We don't have any root server hints, but * we may have working forwarders, so try to * recurse anyway. */ if (RECURSIONOK(qctx->client)) { INSIST(!REDIRECT(qctx->client)); result = ns_query_recurse(qctx->client, qctx->qtype, qctx->client->query.qname, NULL, NULL, qctx->resuming); if (result == ISC_R_SUCCESS) { CALL_HOOK(NS_QUERY_NOTFOUND_RECURSE, qctx); qctx->client->query.attributes |= NS_QUERYATTR_RECURSING; if (qctx->dns64) { qctx->client->query.attributes |= NS_QUERYATTR_DNS64; } if (qctx->dns64_exclude) { qctx->client->query.attributes |= NS_QUERYATTR_DNS64EXCLUDE; } } else if (query_usestale(qctx, result)) { /* * If serve-stale is enabled, query_usestale() * already set up 'qctx' for looking up a * stale response. */ return (query_lookup(qctx)); } else { QUERY_ERROR(qctx, result); } return (ns_query_done(qctx)); } else { /* Unable to give root server referral. */ CCTRACE(ISC_LOG_ERROR, "unable to give root server " "referral"); QUERY_ERROR(qctx, result); return (ns_query_done(qctx)); } } return (query_delegation(qctx)); cleanup: return (result); } /*% * We have a delegation but recursion is not allowed, so return the delegation * to the client. */ static isc_result_t query_prepare_delegation_response(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; dns_rdataset_t **sigrdatasetp = NULL; bool detach = false; CALL_HOOK(NS_QUERY_PREP_DELEGATION_BEGIN, qctx); /* * qctx->fname could be released in query_addrrset(), so save a copy of * it here in case we need it. */ dns_fixedname_init(&qctx->dsname); dns_name_copy(qctx->fname, dns_fixedname_name(&qctx->dsname)); /* * This is the best answer. */ qctx->client->query.isreferral = true; if (!dns_db_iscache(qctx->db) && qctx->client->query.gluedb == NULL) { dns_db_attach(qctx->db, &qctx->client->query.gluedb); detach = true; } /* * We must ensure NOADDITIONAL is off, because the generation of * additional data is required in delegations. */ qctx->client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) { sigrdatasetp = &qctx->sigrdataset; } query_addrrset(qctx, &qctx->fname, &qctx->rdataset, sigrdatasetp, qctx->dbuf, DNS_SECTION_AUTHORITY); if (detach) { dns_db_detach(&qctx->client->query.gluedb); } /* * Add DS/NSEC(3) record(s) if needed. */ query_addds(qctx); return (ns_query_done(qctx)); cleanup: return (result); } /*% * Handle a delegation response from an authoritative lookup. This * may trigger additional lookups, e.g. from the cache database to * see if we have a better answer; if that is not allowed, return the * delegation to the client and call ns_query_done(). */ static isc_result_t query_zone_delegation(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; CALL_HOOK(NS_QUERY_ZONE_DELEGATION_BEGIN, qctx); /* * If the query type is DS, look to see if we are * authoritative for the child zone */ if (!RECURSIONOK(qctx->client) && (qctx->options & DNS_GETDB_NOEXACT) != 0 && qctx->qtype == dns_rdatatype_ds) { dns_db_t *tdb = NULL; dns_zone_t *tzone = NULL; dns_dbversion_t *tversion = NULL; result = query_getzonedb( qctx->client, qctx->client->query.qname, qctx->qtype, DNS_GETDB_PARTIAL, &tzone, &tdb, &tversion); if (result != ISC_R_SUCCESS) { if (tdb != NULL) { dns_db_detach(&tdb); } if (tzone != NULL) { dns_zone_detach(&tzone); } } else { qctx->options &= ~DNS_GETDB_NOEXACT; ns_client_putrdataset(qctx->client, &qctx->rdataset); if (qctx->sigrdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->sigrdataset); } if (qctx->fname != NULL) { ns_client_releasename(qctx->client, &qctx->fname); } if (qctx->node != NULL) { dns_db_detachnode(qctx->db, &qctx->node); } if (qctx->db != NULL) { dns_db_detach(&qctx->db); } if (qctx->zone != NULL) { dns_zone_detach(&qctx->zone); } qctx->version = NULL; RESTORE(qctx->version, tversion); RESTORE(qctx->db, tdb); RESTORE(qctx->zone, tzone); qctx->authoritative = true; return (query_lookup(qctx)); } } if (USECACHE(qctx->client) && (RECURSIONOK(qctx->client) || (qctx->zone != NULL && dns_zone_gettype(qctx->zone) == dns_zone_mirror))) { /* * We might have a better answer or delegation in the * cache. We'll remember the current values of fname, * rdataset, and sigrdataset. We'll then go looking for * QNAME in the cache. If we find something better, we'll * use it instead. If not, then query_lookup() calls * query_notfound() which calls query_delegation(), and * we'll restore these values there. */ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf); SAVE(qctx->zdb, qctx->db); SAVE(qctx->znode, qctx->node); SAVE(qctx->zfname, qctx->fname); SAVE(qctx->zversion, qctx->version); SAVE(qctx->zrdataset, qctx->rdataset); SAVE(qctx->zsigrdataset, qctx->sigrdataset); dns_db_attach(qctx->view->cachedb, &qctx->db); qctx->is_zone = false; return (query_lookup(qctx)); } return (query_prepare_delegation_response(qctx)); cleanup: return (result); } /*% * Handle delegation responses, including root referrals. * * If the delegation was returned from authoritative data, * call query_zone_delgation(). Otherwise, we can start * recursion if allowed; or else return the delegation to the * client and call ns_query_done(). */ static isc_result_t query_delegation(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; CCTRACE(ISC_LOG_DEBUG(3), "query_delegation"); CALL_HOOK(NS_QUERY_DELEGATION_BEGIN, qctx); qctx->authoritative = false; if (qctx->is_zone) { return (query_zone_delegation(qctx)); } if (qctx->zfname != NULL && (!dns_name_issubdomain(qctx->fname, qctx->zfname) || (qctx->is_staticstub_zone && dns_name_equal(qctx->fname, qctx->zfname)))) { /* * In the following cases use "authoritative" * data instead of the cache delegation: * 1. We've already got a delegation from * authoritative data, and it is better * than what we found in the cache. * (See the comment above.) * 2. The query name matches the origin name * of a static-stub zone. This needs to be * considered for the case where the NS of * the static-stub zone and the cached NS * are different. We still need to contact * the nameservers configured in the * static-stub zone. */ ns_client_releasename(qctx->client, &qctx->fname); /* * We've already done ns_client_keepname() on * qctx->zfname, so we must set dbuf to NULL to * prevent query_addrrset() from trying to * call ns_client_keepname() again. */ qctx->dbuf = NULL; ns_client_putrdataset(qctx->client, &qctx->rdataset); if (qctx->sigrdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->sigrdataset); } qctx->version = NULL; dns_db_detachnode(qctx->db, &qctx->node); dns_db_detach(&qctx->db); RESTORE(qctx->db, qctx->zdb); RESTORE(qctx->node, qctx->znode); RESTORE(qctx->fname, qctx->zfname); RESTORE(qctx->version, qctx->zversion); RESTORE(qctx->rdataset, qctx->zrdataset); RESTORE(qctx->sigrdataset, qctx->zsigrdataset); } result = query_delegation_recurse(qctx); if (result != ISC_R_COMPLETE) { return (result); } return (query_prepare_delegation_response(qctx)); cleanup: return (result); } /*% * Handle recursive queries that are triggered as part of the * delegation process. */ static isc_result_t query_delegation_recurse(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; dns_name_t *qname = qctx->client->query.qname; CCTRACE(ISC_LOG_DEBUG(3), "query_delegation_recurse"); if (!RECURSIONOK(qctx->client)) { return (ISC_R_COMPLETE); } CALL_HOOK(NS_QUERY_DELEGATION_RECURSE_BEGIN, qctx); /* * We have a delegation and recursion is allowed, * so we call ns_query_recurse() to follow it. * This phase of the query processing is done; * we'll resume via fetch_callback() and * query_resume() when the recursion is complete. */ INSIST(!REDIRECT(qctx->client)); if (dns_rdatatype_atparent(qctx->type)) { /* * Parent is authoritative for this RDATA type (i.e. DS). */ result = ns_query_recurse(qctx->client, qctx->qtype, qname, NULL, NULL, qctx->resuming); } else if (qctx->dns64) { /* * Look up an A record so we can synthesize DNS64. */ result = ns_query_recurse(qctx->client, dns_rdatatype_a, qname, NULL, NULL, qctx->resuming); } else { /* * Any other recursion. */ result = ns_query_recurse(qctx->client, qctx->qtype, qname, qctx->fname, qctx->rdataset, qctx->resuming); } if (result == ISC_R_SUCCESS) { qctx->client->query.attributes |= NS_QUERYATTR_RECURSING; if (qctx->dns64) { qctx->client->query.attributes |= NS_QUERYATTR_DNS64; } if (qctx->dns64_exclude) { qctx->client->query.attributes |= NS_QUERYATTR_DNS64EXCLUDE; } } else if (query_usestale(qctx, result)) { /* * If serve-stale is enabled, query_usestale() already set up * 'qctx' for looking up a stale response. */ return (query_lookup(qctx)); } else { QUERY_ERROR(qctx, result); } return (ns_query_done(qctx)); cleanup: return (result); } /*% * Add DS/NSEC(3) record(s) if needed. */ static void query_addds(query_ctx_t *qctx) { ns_client_t *client = qctx->client; dns_fixedname_t fixed; dns_name_t *fname = NULL; dns_name_t *rname = NULL; dns_name_t *name; dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; isc_buffer_t *dbuf, b; isc_result_t result; unsigned int count; CTRACE(ISC_LOG_DEBUG(3), "query_addds"); /* * DS not needed. */ if (!WANTDNSSEC(client)) { return; } /* * We'll need some resources... */ rdataset = ns_client_newrdataset(client); sigrdataset = ns_client_newrdataset(client); if (rdataset == NULL || sigrdataset == NULL) { goto cleanup; } /* * Look for the DS record, which may or may not be present. */ result = dns_db_findrdataset(qctx->db, qctx->node, qctx->version, dns_rdatatype_ds, 0, client->now, rdataset, sigrdataset); /* * If we didn't find it, look for an NSEC. */ if (result == ISC_R_NOTFOUND) { result = dns_db_findrdataset( qctx->db, qctx->node, qctx->version, dns_rdatatype_nsec, 0, client->now, rdataset, sigrdataset); } if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { goto addnsec3; } if (!dns_rdataset_isassociated(rdataset) || !dns_rdataset_isassociated(sigrdataset)) { goto addnsec3; } /* * We've already added the NS record, so if the name's not there, * we have other problems. */ result = dns_message_firstname(client->message, DNS_SECTION_AUTHORITY); if (result != ISC_R_SUCCESS) { goto cleanup; } /* * Find the delegation in the response message - it is not necessarily * the first name in the AUTHORITY section when wildcard processing is * involved. */ while (result == ISC_R_SUCCESS) { rname = NULL; dns_message_currentname(client->message, DNS_SECTION_AUTHORITY, &rname); result = dns_message_findtype(rname, dns_rdatatype_ns, 0, NULL); if (result == ISC_R_SUCCESS) { break; } result = dns_message_nextname(client->message, DNS_SECTION_AUTHORITY); } if (result != ISC_R_SUCCESS) { goto cleanup; } /* * Add the relevant RRset (DS or NSEC) to the delegation. */ query_addrrset(qctx, &rname, &rdataset, &sigrdataset, NULL, DNS_SECTION_AUTHORITY); goto cleanup; addnsec3: if (!dns_db_iszone(qctx->db)) { goto cleanup; } /* * Add the NSEC3 which proves the DS does not exist. */ dbuf = ns_client_getnamebuf(client); if (dbuf == NULL) { goto cleanup; } fname = ns_client_newname(client, dbuf, &b); dns_fixedname_init(&fixed); if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } if (dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } name = dns_fixedname_name(&qctx->dsname); query_findclosestnsec3(name, qctx->db, qctx->version, client, rdataset, sigrdataset, fname, true, dns_fixedname_name(&fixed)); if (!dns_rdataset_isassociated(rdataset)) { goto cleanup; } query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); /* * Did we find the closest provable encloser instead? * If so add the nearest to the closest provable encloser. */ if (!dns_name_equal(name, dns_fixedname_name(&fixed))) { count = dns_name_countlabels(dns_fixedname_name(&fixed)) + 1; dns_name_getlabelsequence(name, dns_name_countlabels(name) - count, count, dns_fixedname_name(&fixed)); fixfname(client, &fname, &dbuf, &b); fixrdataset(client, &rdataset); fixrdataset(client, &sigrdataset); if (fname == NULL || rdataset == NULL || sigrdataset == NULL) { goto cleanup; } query_findclosestnsec3(dns_fixedname_name(&fixed), qctx->db, qctx->version, client, rdataset, sigrdataset, fname, false, NULL); if (!dns_rdataset_isassociated(rdataset)) { goto cleanup; } query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); } cleanup: if (rdataset != NULL) { ns_client_putrdataset(client, &rdataset); } if (sigrdataset != NULL) { ns_client_putrdataset(client, &sigrdataset); } if (fname != NULL) { ns_client_releasename(client, &fname); } } /*% * Handle authoritative NOERROR/NODATA responses. */ static isc_result_t query_nodata(query_ctx_t *qctx, isc_result_t res) { isc_result_t result = res; CCTRACE(ISC_LOG_DEBUG(3), "query_nodata"); CALL_HOOK(NS_QUERY_NODATA_BEGIN, qctx); #ifdef dns64_bis_return_excluded_addresses if (qctx->dns64) #else /* ifdef dns64_bis_return_excluded_addresses */ if (qctx->dns64 && !qctx->dns64_exclude) #endif /* ifdef dns64_bis_return_excluded_addresses */ { isc_buffer_t b; /* * Restore the answers from the previous AAAA lookup. */ if (qctx->rdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->rdataset); } if (qctx->sigrdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->sigrdataset); } RESTORE(qctx->rdataset, qctx->client->query.dns64_aaaa); RESTORE(qctx->sigrdataset, qctx->client->query.dns64_sigaaaa); if (qctx->fname == NULL) { qctx->dbuf = ns_client_getnamebuf(qctx->client); if (qctx->dbuf == NULL) { CCTRACE(ISC_LOG_ERROR, "query_nodata: " "ns_client_getnamebuf " "failed (3)"); QUERY_ERROR(qctx, ISC_R_NOMEMORY); return (ns_query_done(qctx)); } qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, &b); if (qctx->fname == NULL) { CCTRACE(ISC_LOG_ERROR, "query_nodata: " "ns_client_newname " "failed (3)"); QUERY_ERROR(qctx, ISC_R_NOMEMORY); return (ns_query_done(qctx)); } } dns_name_copy(qctx->client->query.qname, qctx->fname); qctx->dns64 = false; #ifdef dns64_bis_return_excluded_addresses /* * Resume the diverted processing of the AAAA response? */ if (qctx->dns64_exclude) { return (query_prepresponse(qctx)); } #endif /* ifdef dns64_bis_return_excluded_addresses */ } else if ((result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) && !ISC_LIST_EMPTY(qctx->view->dns64) && !qctx->nxrewrite && qctx->client->message->rdclass == dns_rdataclass_in && qctx->qtype == dns_rdatatype_aaaa) { /* * Look to see if there are A records for this name. */ switch (result) { case DNS_R_NCACHENXRRSET: /* * This is from the negative cache; if the ttl is * zero, we need to work out whether we have just * decremented to zero or there was no negative * cache ttl in the answer. */ if (qctx->rdataset->ttl != 0) { qctx->client->query.dns64_ttl = qctx->rdataset->ttl; break; } if (dns_rdataset_first(qctx->rdataset) == ISC_R_SUCCESS) { qctx->client->query.dns64_ttl = 0; } break; case DNS_R_NXRRSET: qctx->client->query.dns64_ttl = dns64_ttl(qctx->db, qctx->version); break; default: UNREACHABLE(); } SAVE(qctx->client->query.dns64_aaaa, qctx->rdataset); SAVE(qctx->client->query.dns64_sigaaaa, qctx->sigrdataset); ns_client_releasename(qctx->client, &qctx->fname); dns_db_detachnode(qctx->db, &qctx->node); qctx->type = qctx->qtype = dns_rdatatype_a; qctx->dns64 = true; return (query_lookup(qctx)); } if (qctx->is_zone) { return (query_sign_nodata(qctx)); } else { /* * We don't call query_addrrset() because we don't need any * of its extra features (and things would probably break!). */ if (dns_rdataset_isassociated(qctx->rdataset)) { ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf); dns_message_addname(qctx->client->message, qctx->fname, DNS_SECTION_AUTHORITY); ISC_LIST_APPEND(qctx->fname->list, qctx->rdataset, link); qctx->fname = NULL; qctx->rdataset = NULL; } } return (ns_query_done(qctx)); cleanup: return (result); } /*% * Add RRSIGs for NOERROR/NODATA responses when answering authoritatively. */ isc_result_t query_sign_nodata(query_ctx_t *qctx) { isc_result_t result; CCTRACE(ISC_LOG_DEBUG(3), "query_sign_nodata"); /* * Look for a NSEC3 record if we don't have a NSEC record. */ if (qctx->redirected) { return (ns_query_done(qctx)); } if (!dns_rdataset_isassociated(qctx->rdataset) && WANTDNSSEC(qctx->client)) { if ((qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) == 0) { dns_name_t *found; dns_name_t *qname; dns_fixedname_t fixed; isc_buffer_t b; found = dns_fixedname_initname(&fixed); qname = qctx->client->query.qname; query_findclosestnsec3(qname, qctx->db, qctx->version, qctx->client, qctx->rdataset, qctx->sigrdataset, qctx->fname, true, found); /* * Did we find the closest provable encloser * instead? If so add the nearest to the * closest provable encloser. */ if (dns_rdataset_isassociated(qctx->rdataset) && !dns_name_equal(qname, found) && (((qctx->client->sctx->options & NS_SERVER_NONEAREST) == 0) || qctx->qtype == dns_rdatatype_ds)) { unsigned int count; unsigned int skip; /* * Add the closest provable encloser. */ query_addrrset(qctx, &qctx->fname, &qctx->rdataset, &qctx->sigrdataset, qctx->dbuf, DNS_SECTION_AUTHORITY); count = dns_name_countlabels(found) + 1; skip = dns_name_countlabels(qname) - count; dns_name_getlabelsequence(qname, skip, count, found); fixfname(qctx->client, &qctx->fname, &qctx->dbuf, &b); fixrdataset(qctx->client, &qctx->rdataset); fixrdataset(qctx->client, &qctx->sigrdataset); if (qctx->fname == NULL || qctx->rdataset == NULL || qctx->sigrdataset == NULL) { CCTRACE(ISC_LOG_ERROR, "query_sign_" "nodata: " "failure " "getting " "closest " "encloser"); QUERY_ERROR(qctx, ISC_R_NOMEMORY); return (ns_query_done(qctx)); } /* * 'nearest' doesn't exist so * 'exist' is set to false. */ query_findclosestnsec3( found, qctx->db, qctx->version, qctx->client, qctx->rdataset, qctx->sigrdataset, qctx->fname, false, NULL); } } else { ns_client_releasename(qctx->client, &qctx->fname); query_addwildcardproof(qctx, false, true); } } if (dns_rdataset_isassociated(qctx->rdataset)) { /* * If we've got a NSEC record, we need to save the * name now because we're going call query_addsoa() * below, and it needs to use the name buffer. */ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf); } else if (qctx->fname != NULL) { /* * We're not going to use fname, and need to release * our hold on the name buffer so query_addsoa() * may use it. */ ns_client_releasename(qctx->client, &qctx->fname); } /* * The RPZ SOA has already been added to the additional section * if this was an RPZ rewrite, but if it wasn't, add it now. */ if (!qctx->nxrewrite) { result = query_addsoa(qctx, UINT32_MAX, DNS_SECTION_AUTHORITY); if (result != ISC_R_SUCCESS) { QUERY_ERROR(qctx, result); return (ns_query_done(qctx)); } } /* * Add NSEC record if we found one. */ if (WANTDNSSEC(qctx->client) && dns_rdataset_isassociated(qctx->rdataset)) { query_addnxrrsetnsec(qctx); } return (ns_query_done(qctx)); } static void query_addnxrrsetnsec(query_ctx_t *qctx) { ns_client_t *client = qctx->client; dns_rdata_t sigrdata; dns_rdata_rrsig_t sig; unsigned int labels; isc_buffer_t *dbuf, b; dns_name_t *fname; isc_result_t result; INSIST(qctx->fname != NULL); if ((qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) == 0) { query_addrrset(qctx, &qctx->fname, &qctx->rdataset, &qctx->sigrdataset, NULL, DNS_SECTION_AUTHORITY); return; } if (qctx->sigrdataset == NULL || !dns_rdataset_isassociated(qctx->sigrdataset)) { return; } if (dns_rdataset_first(qctx->sigrdataset) != ISC_R_SUCCESS) { return; } dns_rdata_init(&sigrdata); dns_rdataset_current(qctx->sigrdataset, &sigrdata); result = dns_rdata_tostruct(&sigrdata, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); labels = dns_name_countlabels(qctx->fname); if ((unsigned int)sig.labels + 1 >= labels) { return; } query_addwildcardproof(qctx, true, false); /* * We'll need some resources... */ dbuf = ns_client_getnamebuf(client); if (dbuf == NULL) { return; } fname = ns_client_newname(client, dbuf, &b); if (fname == NULL) { return; } dns_name_split(qctx->fname, sig.labels + 1, NULL, fname); /* This will succeed, since we've stripped labels. */ RUNTIME_CHECK(dns_name_concatenate(dns_wildcardname, fname, fname, NULL) == ISC_R_SUCCESS); query_addrrset(qctx, &fname, &qctx->rdataset, &qctx->sigrdataset, dbuf, DNS_SECTION_AUTHORITY); } /*% * Handle NXDOMAIN and empty wildcard responses. */ static isc_result_t query_nxdomain(query_ctx_t *qctx, isc_result_t result) { dns_section_t section; uint32_t ttl; bool empty_wild = (result == DNS_R_EMPTYWILD); CCTRACE(ISC_LOG_DEBUG(3), "query_nxdomain"); CALL_HOOK(NS_QUERY_NXDOMAIN_BEGIN, qctx); INSIST(qctx->is_zone || REDIRECT(qctx->client)); if (!empty_wild) { result = query_redirect(qctx, result); if (result != ISC_R_COMPLETE) { return (result); } } if (dns_rdataset_isassociated(qctx->rdataset)) { /* * If we've got a NSEC record, we need to save the * name now because we're going call query_addsoa() * below, and it needs to use the name buffer. */ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf); } else if (qctx->fname != NULL) { /* * We're not going to use fname, and need to release * our hold on the name buffer so query_addsoa() * may use it. */ ns_client_releasename(qctx->client, &qctx->fname); } /* * Add SOA to the additional section if generated by a * RPZ rewrite. * * If the query was for a SOA record force the * ttl to zero so that it is possible for clients to find * the containing zone of an arbitrary name with a stub * resolver and not have it cached. */ section = qctx->nxrewrite ? DNS_SECTION_ADDITIONAL : DNS_SECTION_AUTHORITY; ttl = UINT32_MAX; if (!qctx->nxrewrite && qctx->qtype == dns_rdatatype_soa && qctx->zone != NULL && dns_zone_getzeronosoattl(qctx->zone)) { ttl = 0; } if (!qctx->nxrewrite || (qctx->rpz_st != NULL && qctx->rpz_st->m.rpz->addsoa)) { result = query_addsoa(qctx, ttl, section); if (result != ISC_R_SUCCESS) { QUERY_ERROR(qctx, result); return (ns_query_done(qctx)); } } if (WANTDNSSEC(qctx->client)) { /* * Add NSEC record if we found one. */ if (dns_rdataset_isassociated(qctx->rdataset)) { query_addrrset(qctx, &qctx->fname, &qctx->rdataset, &qctx->sigrdataset, NULL, DNS_SECTION_AUTHORITY); } query_addwildcardproof(qctx, false, false); } /* * Set message rcode. */ if (empty_wild) { qctx->client->message->rcode = dns_rcode_noerror; } else { qctx->client->message->rcode = dns_rcode_nxdomain; } return (ns_query_done(qctx)); cleanup: return (result); } /* * Handle both types of NXDOMAIN redirection, calling redirect() * (which implements type redirect zones) and redirect2() (which * implements recursive nxdomain-redirect lookups). * * Any result code other than ISC_R_COMPLETE means redirection was * successful and the result code should be returned up the call stack. * * ISC_R_COMPLETE means we reached the end of this function without * redirecting, so query processing should continue past it. */ static isc_result_t query_redirect(query_ctx_t *qctx, isc_result_t saved_result) { isc_result_t result; CCTRACE(ISC_LOG_DEBUG(3), "query_redirect"); result = redirect(qctx->client, qctx->fname, qctx->rdataset, &qctx->node, &qctx->db, &qctx->version, qctx->type); switch (result) { case ISC_R_SUCCESS: inc_stats(qctx->client, ns_statscounter_nxdomainredirect); return (query_prepresponse(qctx)); case DNS_R_NXRRSET: qctx->redirected = true; qctx->is_zone = true; return (query_nodata(qctx, DNS_R_NXRRSET)); case DNS_R_NCACHENXRRSET: qctx->redirected = true; qctx->is_zone = false; return (query_ncache(qctx, DNS_R_NCACHENXRRSET)); default: break; } result = redirect2(qctx->client, qctx->fname, qctx->rdataset, &qctx->node, &qctx->db, &qctx->version, qctx->type, &qctx->is_zone); switch (result) { case ISC_R_SUCCESS: inc_stats(qctx->client, ns_statscounter_nxdomainredirect); return (query_prepresponse(qctx)); case DNS_R_CONTINUE: inc_stats(qctx->client, ns_statscounter_nxdomainredirect_rlookup); SAVE(qctx->client->query.redirect.db, qctx->db); SAVE(qctx->client->query.redirect.node, qctx->node); SAVE(qctx->client->query.redirect.zone, qctx->zone); qctx->client->query.redirect.qtype = qctx->qtype; INSIST(qctx->rdataset != NULL); SAVE(qctx->client->query.redirect.rdataset, qctx->rdataset); SAVE(qctx->client->query.redirect.sigrdataset, qctx->sigrdataset); qctx->client->query.redirect.result = saved_result; dns_name_copy(qctx->fname, qctx->client->query.redirect.fname); qctx->client->query.redirect.authoritative = qctx->authoritative; qctx->client->query.redirect.is_zone = qctx->is_zone; return (ns_query_done(qctx)); case DNS_R_NXRRSET: qctx->redirected = true; qctx->is_zone = true; return (query_nodata(qctx, DNS_R_NXRRSET)); case DNS_R_NCACHENXRRSET: qctx->redirected = true; qctx->is_zone = false; return (query_ncache(qctx, DNS_R_NCACHENXRRSET)); default: break; } return (ISC_R_COMPLETE); } /*% * Logging function to be passed to dns_nsec_noexistnodata. */ static void log_noexistnodata(void *val, int level, const char *fmt, ...) { query_ctx_t *qctx = val; va_list ap; va_start(ap, fmt); ns_client_logv(qctx->client, NS_LOGCATEGORY_QUERIES, NS_LOGMODULE_QUERY, level, fmt, ap); va_end(ap); } static dns_ttl_t query_synthttl(dns_rdataset_t *soardataset, dns_rdataset_t *sigsoardataset, dns_rdataset_t *p1rdataset, dns_rdataset_t *sigp1rdataset, dns_rdataset_t *p2rdataset, dns_rdataset_t *sigp2rdataset) { dns_rdata_soa_t soa; dns_rdata_t rdata = DNS_RDATA_INIT; dns_ttl_t ttl; isc_result_t result; REQUIRE(soardataset != NULL); REQUIRE(sigsoardataset != NULL); REQUIRE(p1rdataset != NULL); REQUIRE(sigp1rdataset != NULL); result = dns_rdataset_first(soardataset); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdataset_current(soardataset, &rdata); result = dns_rdata_tostruct(&rdata, &soa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); ttl = ISC_MIN(soa.minimum, soardataset->ttl); ttl = ISC_MIN(ttl, sigsoardataset->ttl); ttl = ISC_MIN(ttl, p1rdataset->ttl); ttl = ISC_MIN(ttl, sigp1rdataset->ttl); if (p2rdataset != NULL) { ttl = ISC_MIN(ttl, p2rdataset->ttl); } if (sigp2rdataset != NULL) { ttl = ISC_MIN(ttl, sigp2rdataset->ttl); } return (ttl); } /* * Synthesize a NODATA response from the SOA and covering NSEC in cache. */ static isc_result_t query_synthnodata(query_ctx_t *qctx, const dns_name_t *signer, dns_rdataset_t **soardatasetp, dns_rdataset_t **sigsoardatasetp) { dns_name_t *name = NULL; dns_ttl_t ttl; isc_buffer_t *dbuf, b; isc_result_t result; /* * Determine the correct TTL to use for the SOA and RRSIG */ ttl = query_synthttl(*soardatasetp, *sigsoardatasetp, qctx->rdataset, qctx->sigrdataset, NULL, NULL); (*soardatasetp)->ttl = (*sigsoardatasetp)->ttl = ttl; /* * We want the SOA record to be first, so save the * NODATA proof's name now or else discard it. */ if (WANTDNSSEC(qctx->client)) { ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf); } else { ns_client_releasename(qctx->client, &qctx->fname); } dbuf = ns_client_getnamebuf(qctx->client); if (dbuf == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } name = ns_client_newname(qctx->client, dbuf, &b); if (name == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } dns_name_copy(signer, name); /* * Add SOA record. Omit the RRSIG if DNSSEC was not requested. */ if (!WANTDNSSEC(qctx->client)) { sigsoardatasetp = NULL; } query_addrrset(qctx, &name, soardatasetp, sigsoardatasetp, dbuf, DNS_SECTION_AUTHORITY); if (WANTDNSSEC(qctx->client)) { /* * Add NODATA proof. */ query_addrrset(qctx, &qctx->fname, &qctx->rdataset, &qctx->sigrdataset, NULL, DNS_SECTION_AUTHORITY); } result = ISC_R_SUCCESS; inc_stats(qctx->client, ns_statscounter_nodatasynth); cleanup: if (name != NULL) { ns_client_releasename(qctx->client, &name); } return (result); } /* * Synthesize a wildcard answer using the contents of 'rdataset'. * qctx contains the NODATA proof. */ static isc_result_t query_synthwildcard(query_ctx_t *qctx, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { dns_name_t *name = NULL; isc_buffer_t *dbuf, b; isc_result_t result; dns_rdataset_t *cloneset = NULL, *clonesigset = NULL; dns_rdataset_t **sigrdatasetp; CCTRACE(ISC_LOG_DEBUG(3), "query_synthwildcard"); /* * We want the answer to be first, so save the * NOQNAME proof's name now or else discard it. */ if (WANTDNSSEC(qctx->client)) { ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf); } else { ns_client_releasename(qctx->client, &qctx->fname); } dbuf = ns_client_getnamebuf(qctx->client); if (dbuf == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } name = ns_client_newname(qctx->client, dbuf, &b); if (name == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } dns_name_copy(qctx->client->query.qname, name); cloneset = ns_client_newrdataset(qctx->client); if (cloneset == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } dns_rdataset_clone(rdataset, cloneset); /* * Add answer RRset. Omit the RRSIG if DNSSEC was not requested. */ if (WANTDNSSEC(qctx->client)) { clonesigset = ns_client_newrdataset(qctx->client); if (clonesigset == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } dns_rdataset_clone(sigrdataset, clonesigset); sigrdatasetp = &clonesigset; } else { sigrdatasetp = NULL; } query_addrrset(qctx, &name, &cloneset, sigrdatasetp, dbuf, DNS_SECTION_ANSWER); if (WANTDNSSEC(qctx->client)) { /* * Add NOQNAME proof. */ query_addrrset(qctx, &qctx->fname, &qctx->rdataset, &qctx->sigrdataset, NULL, DNS_SECTION_AUTHORITY); } result = ISC_R_SUCCESS; inc_stats(qctx->client, ns_statscounter_wildcardsynth); cleanup: if (name != NULL) { ns_client_releasename(qctx->client, &name); } if (cloneset != NULL) { ns_client_putrdataset(qctx->client, &cloneset); } if (clonesigset != NULL) { ns_client_putrdataset(qctx->client, &clonesigset); } return (result); } /* * Add a synthesized CNAME record from the wildard RRset (rdataset) * and NODATA proof by calling query_synthwildcard then setup to * follow the CNAME. */ static isc_result_t query_synthcnamewildcard(query_ctx_t *qctx, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { isc_result_t result; dns_name_t *tname = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_cname_t cname; result = query_synthwildcard(qctx, rdataset, sigrdataset); if (result != ISC_R_SUCCESS) { return (result); } qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; /* * Reset qname to be the target name of the CNAME and restart * the query. */ result = dns_message_gettempname(qctx->client->message, &tname); if (result != ISC_R_SUCCESS) { return (result); } result = dns_rdataset_first(rdataset); if (result != ISC_R_SUCCESS) { dns_message_puttempname(qctx->client->message, &tname); return (result); } dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &cname, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); if (dns_name_equal(qctx->client->query.qname, &cname.cname)) { dns_message_puttempname(qctx->client->message, &tname); dns_rdata_freestruct(&cname); return (ISC_R_SUCCESS); } dns_name_copy(&cname.cname, tname); dns_rdata_freestruct(&cname); ns_client_qnamereplace(qctx->client, tname); qctx->want_restart = true; if (!WANTRECURSION(qctx->client)) { qctx->options |= DNS_GETDB_NOLOG; } return (result); } /* * Synthesize a NXDOMAIN or NODATA response from qctx (which contains the * NOQNAME proof), nowild + nowildrdataset + signowildrdataset (which * contains the NOWILDCARD proof or NODATA at wildcard) and * signer + soardatasetp + sigsoardatasetp which contain the * SOA record + RRSIG for the negative answer. */ static isc_result_t query_synthnxdomainnodata(query_ctx_t *qctx, bool nodata, dns_name_t *nowild, dns_rdataset_t *nowildrdataset, dns_rdataset_t *signowildrdataset, dns_name_t *signer, dns_rdataset_t **soardatasetp, dns_rdataset_t **sigsoardatasetp) { dns_name_t *name = NULL; dns_ttl_t ttl; isc_buffer_t *dbuf, b; isc_result_t result; dns_rdataset_t *cloneset = NULL, *clonesigset = NULL; CCTRACE(ISC_LOG_DEBUG(3), "query_synthnxdomain"); /* * Determine the correct TTL to use for the SOA and RRSIG */ ttl = query_synthttl(*soardatasetp, *sigsoardatasetp, qctx->rdataset, qctx->sigrdataset, nowildrdataset, signowildrdataset); (*soardatasetp)->ttl = (*sigsoardatasetp)->ttl = ttl; /* * We want the SOA record to be first, so save the * NOQNAME proof's name now or else discard it. */ if (WANTDNSSEC(qctx->client)) { ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf); } else { ns_client_releasename(qctx->client, &qctx->fname); } dbuf = ns_client_getnamebuf(qctx->client); if (dbuf == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } name = ns_client_newname(qctx->client, dbuf, &b); if (name == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } dns_name_copy(signer, name); /* * Add SOA record. Omit the RRSIG if DNSSEC was not requested. */ if (!WANTDNSSEC(qctx->client)) { sigsoardatasetp = NULL; } query_addrrset(qctx, &name, soardatasetp, sigsoardatasetp, dbuf, DNS_SECTION_AUTHORITY); if (WANTDNSSEC(qctx->client)) { /* * Add NOQNAME proof. */ query_addrrset(qctx, &qctx->fname, &qctx->rdataset, &qctx->sigrdataset, NULL, DNS_SECTION_AUTHORITY); dbuf = ns_client_getnamebuf(qctx->client); if (dbuf == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } name = ns_client_newname(qctx->client, dbuf, &b); if (name == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } dns_name_copy(nowild, name); cloneset = ns_client_newrdataset(qctx->client); clonesigset = ns_client_newrdataset(qctx->client); if (cloneset == NULL || clonesigset == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } dns_rdataset_clone(nowildrdataset, cloneset); dns_rdataset_clone(signowildrdataset, clonesigset); /* * Add NOWILDCARD proof. */ query_addrrset(qctx, &name, &cloneset, &clonesigset, dbuf, DNS_SECTION_AUTHORITY); } if (nodata) { inc_stats(qctx->client, ns_statscounter_nodatasynth); } else { qctx->client->message->rcode = dns_rcode_nxdomain; inc_stats(qctx->client, ns_statscounter_nxdomainsynth); } result = ISC_R_SUCCESS; cleanup: if (name != NULL) { ns_client_releasename(qctx->client, &name); } if (cloneset != NULL) { ns_client_putrdataset(qctx->client, &cloneset); } if (clonesigset != NULL) { ns_client_putrdataset(qctx->client, &clonesigset); } return (result); } /* * Check that all signer names in sigrdataset match the expected signer. */ static isc_result_t checksignames(dns_name_t *signer, dns_rdataset_t *sigrdataset) { isc_result_t result; for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(sigrdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_rrsig_t rrsig; dns_rdataset_current(sigrdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (dns_name_countlabels(signer) == 0) { dns_name_copy(&rrsig.signer, signer); } else if (!dns_name_equal(signer, &rrsig.signer)) { return (ISC_R_FAILURE); } } return (ISC_R_SUCCESS); } /*% * Handle covering NSEC responses. * * Verify the NSEC record is appropriate for the QNAME; if not, * redo the initial query without DNS_DBFIND_COVERINGNSEC. * * If the covering NSEC proves that the name exists but not the type, * synthesize a NODATA response. * * If the name doesn't exist, compute the wildcard record and check whether * the wildcard name exists or not. If we can't determine this, redo the * initial query without DNS_DBFIND_COVERINGNSEC. * * If the wildcard name does not exist, compute the SOA name and look that * up. If the SOA record does not exist, redo the initial query without * DNS_DBFIND_COVERINGNSEC. If the SOA record exists, synthesize an * NXDOMAIN response from the found records. * * If the wildcard name does exist, perform a lookup for the requested * type at the wildcard name. */ static isc_result_t query_coveringnsec(query_ctx_t *qctx) { dns_db_t *db = NULL; dns_clientinfo_t ci; dns_clientinfomethods_t cm; dns_dbnode_t *node = NULL; dns_fixedname_t fixed; dns_fixedname_t fnamespace; dns_fixedname_t fnowild; dns_fixedname_t fsigner; dns_fixedname_t fwild; dns_name_t *fname = NULL; dns_name_t *namespace = NULL; dns_name_t *nowild = NULL; dns_name_t *signer = NULL; dns_name_t *wild = NULL; dns_name_t qname; dns_rdataset_t *soardataset = NULL, *sigsoardataset = NULL; dns_rdataset_t rdataset, sigrdataset; bool done = false; bool exists = true, data = true; bool redirected = false; isc_result_t result = ISC_R_SUCCESS; unsigned int dboptions = qctx->client->query.dboptions; unsigned int labels; CCTRACE(ISC_LOG_DEBUG(3), "query_coveringnsec"); dns_name_init(&qname, NULL); dns_rdataset_init(&rdataset); dns_rdataset_init(&sigrdataset); namespace = dns_fixedname_initname(&fnamespace); /* * Check that the NSEC record is from the correct namespace. * For records that belong to the parent zone (i.e. DS), * remove a label to find the correct namespace. */ dns_name_clone(qctx->client->query.qname, &qname); labels = dns_name_countlabels(&qname); if (dns_rdatatype_atparent(qctx->qtype) && labels > 1) { dns_name_getlabelsequence(&qname, 1, labels - 1, &qname); } dns_view_sfd_find(qctx->view, &qname, namespace); if (!dns_name_issubdomain(qctx->fname, namespace)) { goto cleanup; } /* * If we have no signer name, stop immediately. */ if (!dns_rdataset_isassociated(qctx->sigrdataset)) { goto cleanup; } wild = dns_fixedname_initname(&fwild); fname = dns_fixedname_initname(&fixed); signer = dns_fixedname_initname(&fsigner); nowild = dns_fixedname_initname(&fnowild); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, qctx->client, NULL); /* * All signer names must be the same to accept. */ result = checksignames(signer, qctx->sigrdataset); if (result != ISC_R_SUCCESS) { result = ISC_R_SUCCESS; goto cleanup; } /* * If NSEC or RRSIG are missing from the type map * reject the NSEC RRset. */ if (!dns_nsec_requiredtypespresent(qctx->rdataset)) { goto cleanup; } /* * Check that we have the correct NOQNAME NSEC record. */ result = dns_nsec_noexistnodata(qctx->qtype, qctx->client->query.qname, qctx->fname, qctx->rdataset, &exists, &data, wild, log_noexistnodata, qctx); if (result != ISC_R_SUCCESS || (exists && data)) { goto cleanup; } if (exists) { if (qctx->type == dns_rdatatype_any) { /* XXX not yet */ goto cleanup; } if (!ISC_LIST_EMPTY(qctx->view->dns64) && (qctx->type == dns_rdatatype_a || qctx->type == dns_rdatatype_aaaa)) /* XXX not yet */ { goto cleanup; } if (!qctx->resuming && !STALE(qctx->rdataset) && qctx->rdataset->ttl == 0 && RECURSIONOK(qctx->client)) { goto cleanup; } soardataset = ns_client_newrdataset(qctx->client); sigsoardataset = ns_client_newrdataset(qctx->client); if (soardataset == NULL || sigsoardataset == NULL) { goto cleanup; } /* * Look for SOA record to construct NODATA response. */ dns_db_attach(qctx->db, &db); result = dns_db_findext(db, signer, qctx->version, dns_rdatatype_soa, dboptions, qctx->client->now, &node, fname, &cm, &ci, soardataset, sigsoardataset); if (result != ISC_R_SUCCESS) { goto cleanup; } (void)query_synthnodata(qctx, signer, &soardataset, &sigsoardataset); done = true; goto cleanup; } /* * Look up the no-wildcard proof. */ dns_db_attach(qctx->db, &db); result = dns_db_findext(db, wild, qctx->version, qctx->type, dboptions | DNS_DBFIND_COVERINGNSEC, qctx->client->now, &node, nowild, &cm, &ci, &rdataset, &sigrdataset); if (rdataset.trust != dns_trust_secure || sigrdataset.trust != dns_trust_secure) { goto cleanup; } /* * Zero TTL handling of wildcard record. * * We don't yet have code to handle synthesis and type ANY or dns64 * processing so we abort the synthesis here if there would be a * interaction. */ switch (result) { case ISC_R_SUCCESS: if (qctx->type == dns_rdatatype_any) { /* XXX not yet */ goto cleanup; } if (!ISC_LIST_EMPTY(qctx->view->dns64) && (qctx->type == dns_rdatatype_a || qctx->type == dns_rdatatype_aaaa)) /* XXX not yet */ { goto cleanup; } FALLTHROUGH; case DNS_R_CNAME: if (!qctx->resuming && !STALE(&rdataset) && rdataset.ttl == 0 && RECURSIONOK(qctx->client)) { goto cleanup; } default: break; } switch (result) { case DNS_R_COVERINGNSEC: /* * Check that the covering NSEC record is from the right * namespace. */ if (!dns_name_issubdomain(nowild, namespace)) { goto cleanup; } result = dns_nsec_noexistnodata(qctx->qtype, wild, nowild, &rdataset, &exists, &data, NULL, log_noexistnodata, qctx); if (result != ISC_R_SUCCESS || (exists && data)) { goto cleanup; } break; case ISC_R_SUCCESS: /* wild card match */ (void)query_synthwildcard(qctx, &rdataset, &sigrdataset); done = true; goto cleanup; case DNS_R_CNAME: /* wild card cname */ (void)query_synthcnamewildcard(qctx, &rdataset, &sigrdataset); done = true; goto cleanup; case DNS_R_NCACHENXRRSET: /* wild card nodata */ case DNS_R_NCACHENXDOMAIN: /* direct nxdomain */ default: goto cleanup; } /* * We now have the proof that we have an NXDOMAIN. Apply * NXDOMAIN redirection if configured. */ result = query_redirect(qctx, DNS_R_COVERINGNSEC); if (result != ISC_R_COMPLETE) { redirected = true; goto cleanup; } /* * Must be signed to accept. */ if (!dns_rdataset_isassociated(&sigrdataset)) { goto cleanup; } /* * Check signer signer names again. */ result = checksignames(signer, &sigrdataset); if (result != ISC_R_SUCCESS) { result = ISC_R_SUCCESS; goto cleanup; } if (node != NULL) { dns_db_detachnode(db, &node); } soardataset = ns_client_newrdataset(qctx->client); sigsoardataset = ns_client_newrdataset(qctx->client); if (soardataset == NULL || sigsoardataset == NULL) { goto cleanup; } /* * Look for SOA record to construct NXDOMAIN response. */ result = dns_db_findext(db, signer, qctx->version, dns_rdatatype_soa, dboptions, qctx->client->now, &node, fname, &cm, &ci, soardataset, sigsoardataset); if (result != ISC_R_SUCCESS) { goto cleanup; } (void)query_synthnxdomainnodata(qctx, exists, nowild, &rdataset, &sigrdataset, signer, &soardataset, &sigsoardataset); done = true; cleanup: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (dns_rdataset_isassociated(&sigrdataset)) { dns_rdataset_disassociate(&sigrdataset); } if (soardataset != NULL) { ns_client_putrdataset(qctx->client, &soardataset); } if (sigsoardataset != NULL) { ns_client_putrdataset(qctx->client, &sigsoardataset); } if (db != NULL) { if (node != NULL) { dns_db_detachnode(db, &node); } dns_db_detach(&db); } if (redirected) { return (result); } if (!done) { /* * No covering NSEC was found; proceed with recursion. */ qctx->findcoveringnsec = false; if (qctx->fname != NULL) { ns_client_releasename(qctx->client, &qctx->fname); } if (qctx->node != NULL) { dns_db_detachnode(qctx->db, &qctx->node); } ns_client_putrdataset(qctx->client, &qctx->rdataset); if (qctx->sigrdataset != NULL) { ns_client_putrdataset(qctx->client, &qctx->sigrdataset); } return (query_lookup(qctx)); } return (ns_query_done(qctx)); } /*% * Handle negative cache responses, DNS_R_NCACHENXRRSET or * DNS_R_NCACHENXDOMAIN. (Note: may also be called with result * set to DNS_R_NXDOMAIN when handling DNS64 lookups.) */ static isc_result_t query_ncache(query_ctx_t *qctx, isc_result_t result) { INSIST(!qctx->is_zone); INSIST(result == DNS_R_NCACHENXDOMAIN || result == DNS_R_NCACHENXRRSET || result == DNS_R_NXDOMAIN); CCTRACE(ISC_LOG_DEBUG(3), "query_ncache"); CALL_HOOK(NS_QUERY_NCACHE_BEGIN, qctx); qctx->authoritative = false; if (result == DNS_R_NCACHENXDOMAIN) { /* * Set message rcode. (This is not done when * result == DNS_R_NXDOMAIN because that means we're * being called after a DNS64 lookup and don't want * to update the rcode now.) */ qctx->client->message->rcode = dns_rcode_nxdomain; /* Look for RFC 1918 leakage from Internet. */ if (qctx->qtype == dns_rdatatype_ptr && qctx->client->message->rdclass == dns_rdataclass_in && dns_name_countlabels(qctx->fname) == 7) { warn_rfc1918(qctx->client, qctx->fname, qctx->rdataset); } } return (query_nodata(qctx, result)); cleanup: return (result); } /* * If we have a zero ttl from the cache, refetch. */ static isc_result_t query_zerottl_refetch(query_ctx_t *qctx) { isc_result_t result; CCTRACE(ISC_LOG_DEBUG(3), "query_zerottl_refetch"); if (qctx->is_zone || qctx->resuming || STALE(qctx->rdataset) || qctx->rdataset->ttl != 0 || !RECURSIONOK(qctx->client)) { return (ISC_R_COMPLETE); } qctx_clean(qctx); INSIST(!REDIRECT(qctx->client)); result = ns_query_recurse(qctx->client, qctx->qtype, qctx->client->query.qname, NULL, NULL, qctx->resuming); if (result == ISC_R_SUCCESS) { CALL_HOOK(NS_QUERY_ZEROTTL_RECURSE, qctx); qctx->client->query.attributes |= NS_QUERYATTR_RECURSING; if (qctx->dns64) { qctx->client->query.attributes |= NS_QUERYATTR_DNS64; } if (qctx->dns64_exclude) { qctx->client->query.attributes |= NS_QUERYATTR_DNS64EXCLUDE; } } else { /* * There was a zero ttl from the cache, don't fallback to * serve-stale lookup. */ QUERY_ERROR(qctx, result); } return (ns_query_done(qctx)); cleanup: return (result); } /* * Handle CNAME responses. */ static isc_result_t query_cname(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; dns_name_t *tname = NULL; dns_rdataset_t *trdataset = NULL; dns_rdataset_t **sigrdatasetp = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_cname_t cname; CCTRACE(ISC_LOG_DEBUG(3), "query_cname"); CALL_HOOK(NS_QUERY_CNAME_BEGIN, qctx); result = query_zerottl_refetch(qctx); if (result != ISC_R_COMPLETE) { return (result); } /* * Keep a copy of the rdataset. We have to do this because * query_addrrset may clear 'rdataset' (to prevent the * cleanup code from cleaning it up). */ trdataset = qctx->rdataset; /* * Add the CNAME to the answer section. */ if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) { sigrdatasetp = &qctx->sigrdataset; } if (WANTDNSSEC(qctx->client) && (qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) { dns_fixedname_init(&qctx->wildcardname); dns_name_copy(qctx->fname, dns_fixedname_name(&qctx->wildcardname)); qctx->need_wildcardproof = true; } if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client)) { qctx->noqname = qctx->rdataset; } else { qctx->noqname = NULL; } if (!qctx->is_zone && RECURSIONOK(qctx->client)) { query_prefetch(qctx->client, qctx->fname, qctx->rdataset); } query_addrrset(qctx, &qctx->fname, &qctx->rdataset, sigrdatasetp, qctx->dbuf, DNS_SECTION_ANSWER); query_addnoqnameproof(qctx); /* * We set the PARTIALANSWER attribute so that if anything goes * wrong later on, we'll return what we've got so far. */ qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; /* * Reset qname to be the target name of the CNAME and restart * the query. */ result = dns_message_gettempname(qctx->client->message, &tname); if (result != ISC_R_SUCCESS) { return (ns_query_done(qctx)); } result = dns_rdataset_first(trdataset); if (result != ISC_R_SUCCESS) { dns_message_puttempname(qctx->client->message, &tname); return (ns_query_done(qctx)); } dns_rdataset_current(trdataset, &rdata); result = dns_rdata_tostruct(&rdata, &cname, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); dns_name_copy(&cname.cname, tname); dns_rdata_freestruct(&cname); ns_client_qnamereplace(qctx->client, tname); qctx->want_restart = true; if (!WANTRECURSION(qctx->client)) { qctx->options |= DNS_GETDB_NOLOG; } query_addauth(qctx); return (ns_query_done(qctx)); cleanup: return (result); } /* * Handle DNAME responses. */ static isc_result_t query_dname(query_ctx_t *qctx) { dns_name_t *tname, *prefix; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_dname_t dname; dns_fixedname_t fixed; dns_rdataset_t *trdataset; dns_rdataset_t **sigrdatasetp = NULL; dns_namereln_t namereln; isc_buffer_t b; int order; isc_result_t result = ISC_R_UNSET; unsigned int nlabels; CCTRACE(ISC_LOG_DEBUG(3), "query_dname"); CALL_HOOK(NS_QUERY_DNAME_BEGIN, qctx); /* * Compare the current qname to the found name. We need * to know how many labels and bits are in common because * we're going to have to split qname later on. */ namereln = dns_name_fullcompare(qctx->client->query.qname, qctx->fname, &order, &nlabels); INSIST(namereln == dns_namereln_subdomain); /* * Keep a copy of the rdataset. We have to do this because * query_addrrset may clear 'rdataset' (to prevent the * cleanup code from cleaning it up). */ trdataset = qctx->rdataset; /* * Add the DNAME to the answer section. */ if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) { sigrdatasetp = &qctx->sigrdataset; } if (WANTDNSSEC(qctx->client) && (qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) { dns_fixedname_init(&qctx->wildcardname); dns_name_copy(qctx->fname, dns_fixedname_name(&qctx->wildcardname)); qctx->need_wildcardproof = true; } if (!qctx->is_zone && RECURSIONOK(qctx->client)) { query_prefetch(qctx->client, qctx->fname, qctx->rdataset); } query_addrrset(qctx, &qctx->fname, &qctx->rdataset, sigrdatasetp, qctx->dbuf, DNS_SECTION_ANSWER); /* * We set the PARTIALANSWER attribute so that if anything goes * wrong later on, we'll return what we've got so far. */ qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; /* * Get the target name of the DNAME. */ tname = NULL; result = dns_message_gettempname(qctx->client->message, &tname); if (result != ISC_R_SUCCESS) { return (ns_query_done(qctx)); } result = dns_rdataset_first(trdataset); if (result != ISC_R_SUCCESS) { dns_message_puttempname(qctx->client->message, &tname); return (ns_query_done(qctx)); } dns_rdataset_current(trdataset, &rdata); result = dns_rdata_tostruct(&rdata, &dname, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); dns_name_copy(&dname.dname, tname); dns_rdata_freestruct(&dname); /* * Construct the new qname consisting of * . */ prefix = dns_fixedname_initname(&fixed); dns_name_split(qctx->client->query.qname, nlabels, prefix, NULL); INSIST(qctx->fname == NULL); qctx->dbuf = ns_client_getnamebuf(qctx->client); if (qctx->dbuf == NULL) { dns_message_puttempname(qctx->client->message, &tname); return (ns_query_done(qctx)); } qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, &b); if (qctx->fname == NULL) { dns_message_puttempname(qctx->client->message, &tname); return (ns_query_done(qctx)); } result = dns_name_concatenate(prefix, tname, qctx->fname, NULL); dns_message_puttempname(qctx->client->message, &tname); /* * RFC2672, section 4.1, subsection 3c says * we should return YXDOMAIN if the constructed * name would be too long. */ if (result == DNS_R_NAMETOOLONG) { qctx->client->message->rcode = dns_rcode_yxdomain; } if (result != ISC_R_SUCCESS) { return (ns_query_done(qctx)); } ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf); /* * Synthesize a CNAME consisting of * CNAME * with * * Synthesize a CNAME so old old clients that don't understand * DNAME can chain. * * We do not try to synthesize a signature because we hope * that security aware servers will understand DNAME. Also, * even if we had an online key, making a signature * on-the-fly is costly, and not really legitimate anyway * since the synthesized CNAME is NOT in the zone. */ result = query_addcname(qctx, trdataset->trust, trdataset->ttl); if (result != ISC_R_SUCCESS) { return (ns_query_done(qctx)); } /* * If the original query was not for a CNAME or ANY then follow the * CNAME. */ if (qctx->qtype != dns_rdatatype_cname && qctx->qtype != dns_rdatatype_any) { /* * Switch to the new qname and restart. */ ns_client_qnamereplace(qctx->client, qctx->fname); qctx->fname = NULL; qctx->want_restart = true; if (!WANTRECURSION(qctx->client)) { qctx->options |= DNS_GETDB_NOLOG; } } query_addauth(qctx); return (ns_query_done(qctx)); cleanup: return (result); } /*% * Add CNAME to response. */ static isc_result_t query_addcname(query_ctx_t *qctx, dns_trust_t trust, dns_ttl_t ttl) { ns_client_t *client = qctx->client; dns_rdataset_t *rdataset = NULL; dns_rdatalist_t *rdatalist = NULL; dns_rdata_t *rdata = NULL; isc_region_t r; dns_name_t *aname = NULL; isc_result_t result; result = dns_message_gettempname(client->message, &aname); if (result != ISC_R_SUCCESS) { return (result); } dns_name_copy(client->query.qname, aname); result = dns_message_gettemprdatalist(client->message, &rdatalist); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &aname); return (result); } result = dns_message_gettemprdata(client->message, &rdata); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &aname); dns_message_puttemprdatalist(client->message, &rdatalist); return (result); } result = dns_message_gettemprdataset(client->message, &rdataset); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &aname); dns_message_puttemprdatalist(client->message, &rdatalist); dns_message_puttemprdata(client->message, &rdata); return (result); } rdatalist->type = dns_rdatatype_cname; rdatalist->rdclass = client->message->rdclass; rdatalist->ttl = ttl; dns_name_toregion(qctx->fname, &r); rdata->data = r.base; rdata->length = r.length; rdata->rdclass = client->message->rdclass; rdata->type = dns_rdatatype_cname; ISC_LIST_APPEND(rdatalist->rdata, rdata, link); RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) == ISC_R_SUCCESS); rdataset->trust = trust; dns_rdataset_setownercase(rdataset, aname); query_addrrset(qctx, &aname, &rdataset, NULL, NULL, DNS_SECTION_ANSWER); if (rdataset != NULL) { if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } dns_message_puttemprdataset(client->message, &rdataset); } if (aname != NULL) { dns_message_puttempname(client->message, &aname); } return (ISC_R_SUCCESS); } /*% * Prepare to respond: determine whether a wildcard proof is needed, * then hand off to query_respond() or (for type ANY queries) * query_respond_any(). */ static isc_result_t query_prepresponse(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; CCTRACE(ISC_LOG_DEBUG(3), "query_prepresponse"); CALL_HOOK(NS_QUERY_PREP_RESPONSE_BEGIN, qctx); if (WANTDNSSEC(qctx->client) && (qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) { dns_fixedname_init(&qctx->wildcardname); dns_name_copy(qctx->fname, dns_fixedname_name(&qctx->wildcardname)); qctx->need_wildcardproof = true; } if (qctx->type == dns_rdatatype_any) { return (query_respond_any(qctx)); } result = query_zerottl_refetch(qctx); if (result != ISC_R_COMPLETE) { return (result); } return (query_respond(qctx)); cleanup: return (result); } /*% * Add SOA to the authority section when sending negative responses * (or to the additional section if sending negative responses triggered * by RPZ rewriting.) */ static isc_result_t query_addsoa(query_ctx_t *qctx, unsigned int override_ttl, dns_section_t section) { ns_client_t *client = qctx->client; dns_name_t *name = NULL; dns_dbnode_t *node = NULL; isc_result_t result, eresult = ISC_R_SUCCESS; dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; dns_rdataset_t **sigrdatasetp = NULL; dns_clientinfomethods_t cm; dns_clientinfo_t ci; CTRACE(ISC_LOG_DEBUG(3), "query_addsoa"); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * Don't add the SOA record for test which set "-T nosoa". */ if (((client->sctx->options & NS_SERVER_NOSOA) != 0) && (!WANTDNSSEC(client) || !dns_rdataset_isassociated(qctx->rdataset))) { return (ISC_R_SUCCESS); } /* * Get resources and make 'name' be the database origin. */ result = dns_message_gettempname(client->message, &name); if (result != ISC_R_SUCCESS) { return (result); } /* * We'll be releasing 'name' before returning, so it's safe to * use clone instead of copying here. */ dns_name_clone(dns_db_origin(qctx->db), name); rdataset = ns_client_newrdataset(client); if (rdataset == NULL) { CTRACE(ISC_LOG_ERROR, "unable to allocate rdataset"); eresult = DNS_R_SERVFAIL; goto cleanup; } if (WANTDNSSEC(client) && dns_db_issecure(qctx->db)) { sigrdataset = ns_client_newrdataset(client); if (sigrdataset == NULL) { CTRACE(ISC_LOG_ERROR, "unable to allocate sigrdataset"); eresult = DNS_R_SERVFAIL; goto cleanup; } } /* * Find the SOA. */ result = dns_db_getoriginnode(qctx->db, &node); if (result == ISC_R_SUCCESS) { result = dns_db_findrdataset(qctx->db, node, qctx->version, dns_rdatatype_soa, 0, client->now, rdataset, sigrdataset); } else { dns_fixedname_t foundname; dns_name_t *fname; fname = dns_fixedname_initname(&foundname); result = dns_db_findext(qctx->db, name, qctx->version, dns_rdatatype_soa, client->query.dboptions, 0, &node, fname, &cm, &ci, rdataset, sigrdataset); } if (result != ISC_R_SUCCESS) { /* * This is bad. We tried to get the SOA RR at the zone top * and it didn't work! */ CTRACE(ISC_LOG_ERROR, "unable to find SOA RR at zone apex"); eresult = DNS_R_SERVFAIL; } else { /* * Extract the SOA MINIMUM. */ dns_rdata_soa_t soa; dns_rdata_t rdata = DNS_RDATA_INIT; result = dns_rdataset_first(rdataset); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &soa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (override_ttl != UINT32_MAX && override_ttl < rdataset->ttl) { rdataset->ttl = override_ttl; if (sigrdataset != NULL) { sigrdataset->ttl = override_ttl; } } /* * Add the SOA and its SIG to the response, with the * TTLs adjusted per RFC2308 section 3. */ if (rdataset->ttl > soa.minimum) { rdataset->ttl = soa.minimum; } if (sigrdataset != NULL && sigrdataset->ttl > soa.minimum) { sigrdataset->ttl = soa.minimum; } if (sigrdataset != NULL) { sigrdatasetp = &sigrdataset; } else { sigrdatasetp = NULL; } if (section == DNS_SECTION_ADDITIONAL) { rdataset->attributes |= DNS_RDATASETATTR_REQUIRED; } query_addrrset(qctx, &name, &rdataset, sigrdatasetp, NULL, section); } cleanup: ns_client_putrdataset(client, &rdataset); if (sigrdataset != NULL) { ns_client_putrdataset(client, &sigrdataset); } if (name != NULL) { ns_client_releasename(client, &name); } if (node != NULL) { dns_db_detachnode(qctx->db, &node); } return (eresult); } /*% * Add NS to authority section (used when the zone apex is already known). */ static isc_result_t query_addns(query_ctx_t *qctx) { ns_client_t *client = qctx->client; isc_result_t result, eresult; dns_name_t *name = NULL, *fname; dns_dbnode_t *node = NULL; dns_fixedname_t foundname; dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; dns_rdataset_t **sigrdatasetp = NULL; dns_clientinfomethods_t cm; dns_clientinfo_t ci; CTRACE(ISC_LOG_DEBUG(3), "query_addns"); /* * Initialization. */ eresult = ISC_R_SUCCESS; fname = dns_fixedname_initname(&foundname); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * Get resources and make 'name' be the database origin. */ result = dns_message_gettempname(client->message, &name); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_DEBUG(3), "query_addns: dns_message_gettempname " "failed: done"); return (result); } dns_name_clone(dns_db_origin(qctx->db), name); rdataset = ns_client_newrdataset(client); if (rdataset == NULL) { CTRACE(ISC_LOG_ERROR, "query_addns: ns_client_newrdataset " "failed"); eresult = DNS_R_SERVFAIL; goto cleanup; } if (WANTDNSSEC(client) && dns_db_issecure(qctx->db)) { sigrdataset = ns_client_newrdataset(client); if (sigrdataset == NULL) { CTRACE(ISC_LOG_ERROR, "query_addns: " "ns_client_newrdataset failed"); eresult = DNS_R_SERVFAIL; goto cleanup; } } /* * Find the NS rdataset. */ result = dns_db_getoriginnode(qctx->db, &node); if (result == ISC_R_SUCCESS) { result = dns_db_findrdataset(qctx->db, node, qctx->version, dns_rdatatype_ns, 0, client->now, rdataset, sigrdataset); } else { CTRACE(ISC_LOG_DEBUG(3), "query_addns: calling dns_db_find"); result = dns_db_findext(qctx->db, name, NULL, dns_rdatatype_ns, client->query.dboptions, 0, &node, fname, &cm, &ci, rdataset, sigrdataset); CTRACE(ISC_LOG_DEBUG(3), "query_addns: dns_db_find complete"); } if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_ERROR, "query_addns: " "dns_db_findrdataset or dns_db_find " "failed"); /* * This is bad. We tried to get the NS rdataset at the zone * top and it didn't work! */ eresult = DNS_R_SERVFAIL; } else { if (sigrdataset != NULL) { sigrdatasetp = &sigrdataset; } query_addrrset(qctx, &name, &rdataset, sigrdatasetp, NULL, DNS_SECTION_AUTHORITY); } cleanup: CTRACE(ISC_LOG_DEBUG(3), "query_addns: cleanup"); ns_client_putrdataset(client, &rdataset); if (sigrdataset != NULL) { ns_client_putrdataset(client, &sigrdataset); } if (name != NULL) { ns_client_releasename(client, &name); } if (node != NULL) { dns_db_detachnode(qctx->db, &node); } CTRACE(ISC_LOG_DEBUG(3), "query_addns: done"); return (eresult); } /*% * Find the zone cut and add the best NS rrset to the authority section. */ static void query_addbestns(query_ctx_t *qctx) { ns_client_t *client = qctx->client; dns_db_t *db = NULL, *zdb = NULL; dns_dbnode_t *node = NULL; dns_name_t *fname = NULL, *zfname = NULL; dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; dns_rdataset_t *zrdataset = NULL, *zsigrdataset = NULL; bool is_zone = false, use_zone = false; isc_buffer_t *dbuf = NULL; isc_result_t result; dns_dbversion_t *version = NULL; dns_zone_t *zone = NULL; isc_buffer_t b; dns_clientinfomethods_t cm; dns_clientinfo_t ci; CTRACE(ISC_LOG_DEBUG(3), "query_addbestns"); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * Find the right database. */ result = query_getdb(client, client->query.qname, dns_rdatatype_ns, 0, &zone, &db, &version, &is_zone); if (result != ISC_R_SUCCESS) { goto cleanup; } db_find: /* * We'll need some resources... */ dbuf = ns_client_getnamebuf(client); if (dbuf == NULL) { goto cleanup; } fname = ns_client_newname(client, dbuf, &b); rdataset = ns_client_newrdataset(client); if (fname == NULL || rdataset == NULL) { goto cleanup; } /* * Get the RRSIGs if the client requested them or if we may * need to validate answers from the cache. */ if (WANTDNSSEC(client) || !is_zone) { sigrdataset = ns_client_newrdataset(client); if (sigrdataset == NULL) { goto cleanup; } } /* * Now look for the zonecut. */ if (is_zone) { result = dns_db_findext( db, client->query.qname, version, dns_rdatatype_ns, client->query.dboptions, client->now, &node, fname, &cm, &ci, rdataset, sigrdataset); if (result != DNS_R_DELEGATION) { goto cleanup; } if (USECACHE(client)) { ns_client_keepname(client, fname, dbuf); dns_db_detachnode(db, &node); SAVE(zdb, db); SAVE(zfname, fname); SAVE(zrdataset, rdataset); SAVE(zsigrdataset, sigrdataset); version = NULL; dns_db_attach(client->view->cachedb, &db); is_zone = false; goto db_find; } } else { result = dns_db_findzonecut( db, client->query.qname, client->query.dboptions, client->now, &node, fname, NULL, rdataset, sigrdataset); if (result == ISC_R_SUCCESS) { if (zfname != NULL && !dns_name_issubdomain(fname, zfname)) { /* * We found a zonecut in the cache, but our * zone delegation is better. */ use_zone = true; } } else if (result == ISC_R_NOTFOUND && zfname != NULL) { /* * We didn't find anything in the cache, but we * have a zone delegation, so use it. */ use_zone = true; } else { goto cleanup; } } if (use_zone) { ns_client_releasename(client, &fname); /* * We've already done ns_client_keepname() on * zfname, so we must set dbuf to NULL to * prevent query_addrrset() from trying to * call ns_client_keepname() again. */ dbuf = NULL; ns_client_putrdataset(client, &rdataset); if (sigrdataset != NULL) { ns_client_putrdataset(client, &sigrdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } dns_db_detach(&db); RESTORE(db, zdb); RESTORE(fname, zfname); RESTORE(rdataset, zrdataset); RESTORE(sigrdataset, zsigrdataset); } /* * Attempt to validate RRsets that are pending or that are glue. */ if ((DNS_TRUST_PENDING(rdataset->trust) || (sigrdataset != NULL && DNS_TRUST_PENDING(sigrdataset->trust))) && !validate(client, db, fname, rdataset, sigrdataset) && !PENDINGOK(client->query.dboptions)) { goto cleanup; } if ((DNS_TRUST_GLUE(rdataset->trust) || (sigrdataset != NULL && DNS_TRUST_GLUE(sigrdataset->trust))) && !validate(client, db, fname, rdataset, sigrdataset) && SECURE(client) && WANTDNSSEC(client)) { goto cleanup; } /* * If the answer is secure only add NS records if they are secure * when the client may be looking for AD in the response. */ if (SECURE(client) && (WANTDNSSEC(client) || WANTAD(client)) && ((rdataset->trust != dns_trust_secure) || (sigrdataset != NULL && sigrdataset->trust != dns_trust_secure))) { goto cleanup; } /* * If the client doesn't want DNSSEC we can discard the sigrdataset * now. */ if (!WANTDNSSEC(client)) { ns_client_putrdataset(client, &sigrdataset); } query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); cleanup: if (rdataset != NULL) { ns_client_putrdataset(client, &rdataset); } if (sigrdataset != NULL) { ns_client_putrdataset(client, &sigrdataset); } if (fname != NULL) { ns_client_releasename(client, &fname); } if (node != NULL) { dns_db_detachnode(db, &node); } if (db != NULL) { dns_db_detach(&db); } if (zone != NULL) { dns_zone_detach(&zone); } if (zdb != NULL) { ns_client_putrdataset(client, &zrdataset); if (zsigrdataset != NULL) { ns_client_putrdataset(client, &zsigrdataset); } if (zfname != NULL) { ns_client_releasename(client, &zfname); } dns_db_detach(&zdb); } } static void query_addwildcardproof(query_ctx_t *qctx, bool ispositive, bool nodata) { ns_client_t *client = qctx->client; isc_buffer_t *dbuf, b; dns_name_t *name; dns_name_t *fname = NULL; dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; dns_fixedname_t wfixed; dns_name_t *wname; dns_dbnode_t *node = NULL; unsigned int options; unsigned int olabels, nlabels, labels; isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_nsec_t nsec; bool have_wname; int order; dns_fixedname_t cfixed; dns_name_t *cname; dns_clientinfomethods_t cm; dns_clientinfo_t ci; CTRACE(ISC_LOG_DEBUG(3), "query_addwildcardproof"); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * If a name has been specifically flagged as needing * a wildcard proof then it will have been copied to * qctx->wildcardname. Otherwise we just use the client * QNAME. */ if (qctx->need_wildcardproof) { name = dns_fixedname_name(&qctx->wildcardname); } else { name = client->query.qname; } /* * Get the NOQNAME proof then if !ispositive * get the NOWILDCARD proof. * * DNS_DBFIND_NOWILD finds the NSEC records that covers the * name ignoring any wildcard. From the owner and next names * of this record you can compute which wildcard (if it exists) * will match by finding the longest common suffix of the * owner name and next names with the qname and prefixing that * with the wildcard label. * * e.g. * Given: * example SOA * example NSEC b.example * b.example A * b.example NSEC a.d.example * a.d.example A * a.d.example NSEC g.f.example * g.f.example A * g.f.example NSEC z.i.example * z.i.example A * z.i.example NSEC example * * QNAME: * a.example -> example NSEC b.example * owner common example * next common example * wild *.example * d.b.example -> b.example NSEC a.d.example * owner common b.example * next common example * wild *.b.example * a.f.example -> a.d.example NSEC g.f.example * owner common example * next common f.example * wild *.f.example * j.example -> z.i.example NSEC example * owner common example * next common example * wild *.example */ options = client->query.dboptions | DNS_DBFIND_NOWILD; wname = dns_fixedname_initname(&wfixed); again: have_wname = false; /* * We'll need some resources... */ dbuf = ns_client_getnamebuf(client); if (dbuf == NULL) { goto cleanup; } fname = ns_client_newname(client, dbuf, &b); rdataset = ns_client_newrdataset(client); sigrdataset = ns_client_newrdataset(client); if (fname == NULL || rdataset == NULL || sigrdataset == NULL) { goto cleanup; } result = dns_db_findext(qctx->db, name, qctx->version, dns_rdatatype_nsec, options, 0, &node, fname, &cm, &ci, rdataset, sigrdataset); if (node != NULL) { dns_db_detachnode(qctx->db, &node); } if (!dns_rdataset_isassociated(rdataset)) { /* * No NSEC proof available, return NSEC3 proofs instead. */ cname = dns_fixedname_initname(&cfixed); /* * Find the closest encloser. */ dns_name_copy(name, cname); while (result == DNS_R_NXDOMAIN) { labels = dns_name_countlabels(cname) - 1; /* * Sanity check. */ if (labels == 0U) { goto cleanup; } dns_name_split(cname, labels, NULL, cname); result = dns_db_findext(qctx->db, cname, qctx->version, dns_rdatatype_nsec, options, 0, NULL, fname, &cm, &ci, NULL, NULL); } /* * Add closest (provable) encloser NSEC3. */ query_findclosestnsec3(cname, qctx->db, qctx->version, client, rdataset, sigrdataset, fname, true, cname); if (!dns_rdataset_isassociated(rdataset)) { goto cleanup; } if (!ispositive) { query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); } /* * Replace resources which were consumed by query_addrrset. */ if (fname == NULL) { dbuf = ns_client_getnamebuf(client); if (dbuf == NULL) { goto cleanup; } fname = ns_client_newname(client, dbuf, &b); } if (rdataset == NULL) { rdataset = ns_client_newrdataset(client); } else if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } if (sigrdataset == NULL) { sigrdataset = ns_client_newrdataset(client); } else if (dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } if (fname == NULL || rdataset == NULL || sigrdataset == NULL) { goto cleanup; } /* * Add no qname proof. */ labels = dns_name_countlabels(cname) + 1; if (dns_name_countlabels(name) == labels) { dns_name_copy(name, wname); } else { dns_name_split(name, labels, NULL, wname); } query_findclosestnsec3(wname, qctx->db, qctx->version, client, rdataset, sigrdataset, fname, false, NULL); if (!dns_rdataset_isassociated(rdataset)) { goto cleanup; } query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); if (ispositive) { goto cleanup; } /* * Replace resources which were consumed by query_addrrset. */ if (fname == NULL) { dbuf = ns_client_getnamebuf(client); if (dbuf == NULL) { goto cleanup; } fname = ns_client_newname(client, dbuf, &b); } if (rdataset == NULL) { rdataset = ns_client_newrdataset(client); } else if (dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } if (sigrdataset == NULL) { sigrdataset = ns_client_newrdataset(client); } else if (dns_rdataset_isassociated(sigrdataset)) { dns_rdataset_disassociate(sigrdataset); } if (fname == NULL || rdataset == NULL || sigrdataset == NULL) { goto cleanup; } /* * Add the no wildcard proof. */ result = dns_name_concatenate(dns_wildcardname, cname, wname, NULL); if (result != ISC_R_SUCCESS) { goto cleanup; } query_findclosestnsec3(wname, qctx->db, qctx->version, client, rdataset, sigrdataset, fname, nodata, NULL); if (!dns_rdataset_isassociated(rdataset)) { goto cleanup; } query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); goto cleanup; } else if (result == DNS_R_NXDOMAIN) { if (!ispositive) { result = dns_rdataset_first(rdataset); } if (result == ISC_R_SUCCESS) { dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &nsec, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); (void)dns_name_fullcompare(name, fname, &order, &olabels); (void)dns_name_fullcompare(name, &nsec.next, &order, &nlabels); /* * Check for a pathological condition created when * serving some malformed signed zones and bail out. */ if (dns_name_countlabels(name) == nlabels) { goto cleanup; } if (olabels > nlabels) { dns_name_split(name, olabels, NULL, wname); } else { dns_name_split(name, nlabels, NULL, wname); } result = dns_name_concatenate(dns_wildcardname, wname, wname, NULL); if (result == ISC_R_SUCCESS) { have_wname = true; } dns_rdata_freestruct(&nsec); } query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); } if (rdataset != NULL) { ns_client_putrdataset(client, &rdataset); } if (sigrdataset != NULL) { ns_client_putrdataset(client, &sigrdataset); } if (fname != NULL) { ns_client_releasename(client, &fname); } if (have_wname) { ispositive = true; /* prevent loop */ if (!dns_name_equal(name, wname)) { name = wname; goto again; } } cleanup: if (rdataset != NULL) { ns_client_putrdataset(client, &rdataset); } if (sigrdataset != NULL) { ns_client_putrdataset(client, &sigrdataset); } if (fname != NULL) { ns_client_releasename(client, &fname); } } /*% * Add NS records, and NSEC/NSEC3 wildcard proof records if needed, * to the authority section. */ static void query_addauth(query_ctx_t *qctx) { CCTRACE(ISC_LOG_DEBUG(3), "query_addauth"); /* * Add NS records to the authority section (if we haven't already * added them to the answer section). */ if (!qctx->want_restart && !NOAUTHORITY(qctx->client)) { if (qctx->is_zone) { if (!qctx->answer_has_ns) { (void)query_addns(qctx); } } else if (!qctx->answer_has_ns && qctx->qtype != dns_rdatatype_ns) { if (qctx->fname != NULL) { ns_client_releasename(qctx->client, &qctx->fname); } query_addbestns(qctx); } } /* * Add NSEC records to the authority section if they're needed for * DNSSEC wildcard proofs. */ if (qctx->need_wildcardproof && dns_db_issecure(qctx->db)) { query_addwildcardproof(qctx, true, false); } } /* * Find the sort order of 'rdata' in the topology-like * ACL forming the second element in a 2-element top-level * sortlist statement. */ static int query_sortlist_order_2element(const dns_rdata_t *rdata, const void *arg) { isc_netaddr_t netaddr; if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) { return (INT_MAX); } return (ns_sortlist_addrorder2(&netaddr, arg)); } /* * Find the sort order of 'rdata' in the matching element * of a 1-element top-level sortlist statement. */ static int query_sortlist_order_1element(const dns_rdata_t *rdata, const void *arg) { isc_netaddr_t netaddr; if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) { return (INT_MAX); } return (ns_sortlist_addrorder1(&netaddr, arg)); } /* * Find the sortlist statement that applies to 'client' and set up * the sortlist info in in client->message appropriately. */ static void query_setup_sortlist(query_ctx_t *qctx) { isc_netaddr_t netaddr; ns_client_t *client = qctx->client; dns_aclenv_t *env = client->manager->aclenv; dns_acl_t *acl = NULL; dns_aclelement_t *elt = NULL; void *order_arg = NULL; isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); switch (ns_sortlist_setup(client->view->sortlist, env, &netaddr, &order_arg)) { case NS_SORTLISTTYPE_1ELEMENT: elt = order_arg; dns_message_setsortorder(client->message, query_sortlist_order_1element, env, NULL, elt); break; case NS_SORTLISTTYPE_2ELEMENT: acl = order_arg; dns_message_setsortorder(client->message, query_sortlist_order_2element, env, acl, NULL); dns_acl_detach(&acl); break; case NS_SORTLISTTYPE_NONE: break; default: UNREACHABLE(); } } /* * When sending a referral, if the answer to the question is * in the glue, sort it to the start of the additional section. */ static void query_glueanswer(query_ctx_t *qctx) { const dns_namelist_t *secs = qctx->client->message->sections; const dns_section_t section = DNS_SECTION_ADDITIONAL; dns_name_t *name; dns_message_t *msg; dns_rdataset_t *rdataset = NULL; if (!ISC_LIST_EMPTY(secs[DNS_SECTION_ANSWER]) || qctx->client->message->rcode != dns_rcode_noerror || (qctx->qtype != dns_rdatatype_a && qctx->qtype != dns_rdatatype_aaaa)) { return; } msg = qctx->client->message; for (name = ISC_LIST_HEAD(msg->sections[section]); name != NULL; name = ISC_LIST_NEXT(name, link)) { if (dns_name_equal(name, qctx->client->query.qname)) { for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if (rdataset->type == qctx->qtype) { break; } } break; } } if (rdataset != NULL) { ISC_LIST_UNLINK(msg->sections[section], name, link); ISC_LIST_PREPEND(msg->sections[section], name, link); ISC_LIST_UNLINK(name->list, rdataset, link); ISC_LIST_PREPEND(name->list, rdataset, link); rdataset->attributes |= DNS_RDATASETATTR_REQUIRED; } } isc_result_t ns_query_done(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; const dns_namelist_t *secs = qctx->client->message->sections; bool nodetach; CCTRACE(ISC_LOG_DEBUG(3), "ns_query_done"); CALL_HOOK(NS_QUERY_DONE_BEGIN, qctx); /* * General cleanup. */ qctx->rpz_st = qctx->client->query.rpz_st; if (qctx->rpz_st != NULL && (qctx->rpz_st->state & DNS_RPZ_RECURSING) == 0) { rpz_match_clear(qctx->rpz_st); qctx->rpz_st->state &= ~DNS_RPZ_DONE_QNAME; } qctx_clean(qctx); qctx_freedata(qctx); if (qctx->client->query.gluedb != NULL) { dns_db_detach(&qctx->client->query.gluedb); } /* * Clear the AA bit if we're not authoritative. */ if (qctx->client->query.restarts == 0 && !qctx->authoritative) { qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA; } /* * Do we need to restart the query (e.g. for CNAME chaining)? */ if (qctx->want_restart && qctx->client->query.restarts < MAX_RESTARTS) { qctx->client->query.restarts++; return (ns__query_start(qctx)); } if (qctx->result != ISC_R_SUCCESS && (!PARTIALANSWER(qctx->client) || WANTRECURSION(qctx->client) || qctx->result == DNS_R_DROP)) { if (qctx->result == DNS_R_DUPLICATE || qctx->result == DNS_R_DROP) { /* * This was a duplicate query that we are * recursing on or the result of rate limiting. * Don't send a response now for a duplicate query, * because the original will still cause a response. */ query_next(qctx->client, qctx->result); } else { /* * If we don't have any answer to give the client, * or if the client requested recursion and thus wanted * the complete answer, send an error response. */ INSIST(qctx->line >= 0); query_error(qctx->client, qctx->result, qctx->line); } qctx->detach_client = true; return (qctx->result); } /* * If we're recursing then just return; the query will * resume when recursion ends. */ if (RECURSING(qctx->client) && (!QUERY_STALETIMEOUT(&qctx->client->query) || ((qctx->options & DNS_GETDB_STALEFIRST) != 0))) { return (qctx->result); } /* * We are done. Set up sortlist data for the message * rendering code, sort the answer to the front of the * additional section if necessary, make a final tweak * to the AA bit if the auth-nxdomain config option * says so, then render and send the response. */ query_setup_sortlist(qctx); query_glueanswer(qctx); if (qctx->client->message->rcode == dns_rcode_nxdomain && qctx->view->auth_nxdomain) { qctx->client->message->flags |= DNS_MESSAGEFLAG_AA; } /* * If the response is somehow unexpected for the client and this * is a result of recursion, return an error to the caller * to indicate it may need to be logged. */ if (qctx->resuming && (ISC_LIST_EMPTY(secs[DNS_SECTION_ANSWER]) || qctx->client->message->rcode != dns_rcode_noerror)) { qctx->result = ISC_R_FAILURE; } CALL_HOOK(NS_QUERY_DONE_SEND, qctx); /* * Client may have been detached after query_send(), so * we test and store the flag state here, for safety. */ nodetach = qctx->client->nodetach; query_send(qctx->client); if (qctx->refresh_rrset) { /* * If we reached this point then it means that we have found a * stale RRset entry in cache and BIND is configured to allow * queries to be answered with stale data if no active RRset * is available, i.e. "stale-anwer-client-timeout 0". But, we * still need to refresh the RRset. To prevent adding duplicate * RRsets, clear the RRsets from the message before doing the * refresh. */ message_clearrdataset(qctx->client->message, 0); query_refresh_rrset(qctx); } if (!nodetach) { qctx->detach_client = true; } return (qctx->result); cleanup: return (result); } static void log_tat(ns_client_t *client) { char namebuf[DNS_NAME_FORMATSIZE]; char clientbuf[ISC_NETADDR_FORMATSIZE]; char classbuf[DNS_RDATACLASS_FORMATSIZE]; isc_netaddr_t netaddr; char *tags = NULL; size_t taglen = 0; if (!isc_log_wouldlog(ns_lctx, ISC_LOG_INFO)) { return; } if ((client->query.qtype != dns_rdatatype_null || !dns_name_istat(client->query.qname)) && (client->keytag == NULL || client->query.qtype != dns_rdatatype_dnskey)) { return; } isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); dns_name_format(client->query.qname, namebuf, sizeof(namebuf)); isc_netaddr_format(&netaddr, clientbuf, sizeof(clientbuf)); dns_rdataclass_format(client->view->rdclass, classbuf, sizeof(classbuf)); if (client->query.qtype == dns_rdatatype_dnskey) { uint16_t keytags = client->keytag_len / 2; size_t len = taglen = sizeof("65000") * keytags + 1; char *cp = tags = isc_mem_get(client->mctx, taglen); int i = 0; INSIST(client->keytag != NULL); if (tags != NULL) { while (keytags-- > 0U) { int n; uint16_t keytag; keytag = (client->keytag[i * 2] << 8) | client->keytag[i * 2 + 1]; n = snprintf(cp, len, " %u", keytag); if (n > 0 && (size_t)n <= len) { cp += n; len -= n; i++; } else { break; } } } } isc_log_write(ns_lctx, NS_LOGCATEGORY_TAT, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "trust-anchor-telemetry '%s/%s' from %s%s", namebuf, classbuf, clientbuf, tags != NULL ? tags : ""); if (tags != NULL) { isc_mem_put(client->mctx, tags, taglen); } } static void log_query(ns_client_t *client, unsigned int flags, unsigned int extflags) { char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; char classbuf[DNS_RDATACLASS_FORMATSIZE]; char onbuf[ISC_NETADDR_FORMATSIZE]; char ecsbuf[DNS_ECS_FORMATSIZE + sizeof(" [ECS ]") - 1] = { 0 }; char ednsbuf[sizeof("E(65535)")] = { 0 }; dns_rdataset_t *rdataset; int level = ISC_LOG_INFO; if (!isc_log_wouldlog(ns_lctx, level)) { return; } rdataset = ISC_LIST_HEAD(client->query.qname->list); INSIST(rdataset != NULL); dns_name_format(client->query.qname, namebuf, sizeof(namebuf)); dns_rdataclass_format(rdataset->rdclass, classbuf, sizeof(classbuf)); dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); isc_netaddr_format(&client->destaddr, onbuf, sizeof(onbuf)); if (client->ednsversion >= 0) { snprintf(ednsbuf, sizeof(ednsbuf), "E(%hd)", client->ednsversion); } if (HAVEECS(client)) { strlcpy(ecsbuf, " [ECS ", sizeof(ecsbuf)); dns_ecs_format(&client->ecs, ecsbuf + 6, sizeof(ecsbuf) - 6); strlcat(ecsbuf, "]", sizeof(ecsbuf)); } ns_client_log(client, NS_LOGCATEGORY_QUERIES, NS_LOGMODULE_QUERY, level, "query: %s %s %s %s%s%s%s%s%s%s (%s)%s", namebuf, classbuf, typebuf, WANTRECURSION(client) ? "+" : "-", (client->signer != NULL) ? "S" : "", ednsbuf, TCP(client) ? "T" : "", ((extflags & DNS_MESSAGEEXTFLAG_DO) != 0) ? "D" : "", ((flags & DNS_MESSAGEFLAG_CD) != 0) ? "C" : "", HAVECOOKIE(client) ? "V" : WANTCOOKIE(client) ? "K" : "", onbuf, ecsbuf); } static void log_queryerror(ns_client_t *client, isc_result_t result, int line, int level) { char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; char classbuf[DNS_RDATACLASS_FORMATSIZE]; const char *namep, *typep, *classp, *sep1, *sep2; dns_rdataset_t *rdataset; if (!isc_log_wouldlog(ns_lctx, level)) { return; } namep = typep = classp = sep1 = sep2 = ""; /* * Query errors can happen for various reasons. In some cases we cannot * even assume the query contains a valid question section, so we should * expect exceptional cases. */ if (client->query.origqname != NULL) { dns_name_format(client->query.origqname, namebuf, sizeof(namebuf)); namep = namebuf; sep1 = " for "; rdataset = ISC_LIST_HEAD(client->query.origqname->list); if (rdataset != NULL) { dns_rdataclass_format(rdataset->rdclass, classbuf, sizeof(classbuf)); classp = classbuf; dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); typep = typebuf; sep2 = "/"; } } ns_client_log(client, NS_LOGCATEGORY_QUERY_ERRORS, NS_LOGMODULE_QUERY, level, "query failed (%s)%s%s%s%s%s%s at %s:%d", isc_result_totext(result), sep1, namep, sep2, classp, sep2, typep, __FILE__, line); } void ns_query_start(ns_client_t *client, isc_nmhandle_t *handle) { isc_result_t result; dns_message_t *message; dns_rdataset_t *rdataset; dns_rdatatype_t qtype; unsigned int saved_extflags; unsigned int saved_flags; REQUIRE(NS_CLIENT_VALID(client)); /* * Attach to the request handle */ isc_nmhandle_attach(handle, &client->reqhandle); message = client->message; saved_extflags = client->extflags; saved_flags = client->message->flags; CTRACE(ISC_LOG_DEBUG(3), "ns_query_start"); /* * Ensure that appropriate cleanups occur. */ client->cleanup = query_cleanup; if ((message->flags & DNS_MESSAGEFLAG_RD) != 0) { client->query.attributes |= NS_QUERYATTR_WANTRECURSION; } if ((client->extflags & DNS_MESSAGEEXTFLAG_DO) != 0) { client->attributes |= NS_CLIENTATTR_WANTDNSSEC; } switch (client->view->minimalresponses) { case dns_minimal_no: break; case dns_minimal_yes: client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); break; case dns_minimal_noauth: client->query.attributes |= NS_QUERYATTR_NOAUTHORITY; break; case dns_minimal_noauthrec: if ((message->flags & DNS_MESSAGEFLAG_RD) != 0) { client->query.attributes |= NS_QUERYATTR_NOAUTHORITY; } break; } if (client->view->cachedb == NULL || !client->view->recursion) { /* * We don't have a cache. Turn off cache support and * recursion. */ client->query.attributes &= ~(NS_QUERYATTR_RECURSIONOK | NS_QUERYATTR_CACHEOK); client->attributes |= NS_CLIENTATTR_NOSETFC; } else if ((client->attributes & NS_CLIENTATTR_RA) == 0 || (message->flags & DNS_MESSAGEFLAG_RD) == 0) { /* * If the client isn't allowed to recurse (due to * "recursion no", the allow-recursion ACL, or the * lack of a resolver in this view), or if it * doesn't want recursion, turn recursion off. */ client->query.attributes &= ~NS_QUERYATTR_RECURSIONOK; client->attributes |= NS_CLIENTATTR_NOSETFC; } /* * Check for multiple question queries, since edns1 is dead. */ if (message->counts[DNS_SECTION_QUESTION] > 1) { query_error(client, DNS_R_FORMERR, __LINE__); return; } /* * Get the question name. */ result = dns_message_firstname(message, DNS_SECTION_QUESTION); if (result != ISC_R_SUCCESS) { query_error(client, result, __LINE__); return; } dns_message_currentname(message, DNS_SECTION_QUESTION, &client->query.qname); client->query.origqname = client->query.qname; result = dns_message_nextname(message, DNS_SECTION_QUESTION); if (result != ISC_R_NOMORE) { if (result == ISC_R_SUCCESS) { /* * There's more than one QNAME in the question * section. */ query_error(client, DNS_R_FORMERR, __LINE__); } else { query_error(client, result, __LINE__); } return; } if ((client->sctx->options & NS_SERVER_LOGQUERIES) != 0) { log_query(client, saved_flags, saved_extflags); } /* * Check for meta-queries like IXFR and AXFR. */ rdataset = ISC_LIST_HEAD(client->query.qname->list); INSIST(rdataset != NULL); client->query.qtype = qtype = rdataset->type; dns_rdatatypestats_increment(client->sctx->rcvquerystats, qtype); log_tat(client); if (dns_rdatatype_ismeta(qtype)) { switch (qtype) { case dns_rdatatype_any: break; /* Let the query logic handle it. */ case dns_rdatatype_ixfr: case dns_rdatatype_axfr: if (isc_nm_is_http_handle(handle)) { /* * We cannot use DoH for zone transfers. * According to RFC 8484 a DoH request contains * exactly one DNS message (see Section 6: * Definition of the "application/dns-message" * Media Type). * * This makes DoH unsuitable for zone transfers * as often (and usually!) these need more than * one DNS message, especially for larger zones. * As zone transfers over DoH are not (yet) * standardised, nor discussed in RFC 8484, * the best thing we can do is to return "not * implemented". */ query_error(client, DNS_R_NOTIMP, __LINE__); return; } if (isc_nm_socket_type(handle) == isc_nm_tlsdnssocket) { /* * Currently this code is here for DoT, which * has more complex requirements for zone * transfers compared to other stream * protocols. See RFC 9103 for details. */ switch (isc_nm_xfr_checkperm(handle)) { case ISC_R_SUCCESS: break; case ISC_R_DOTALPNERROR: query_error(client, DNS_R_NOALPN, __LINE__); return; default: query_error(client, DNS_R_REFUSED, __LINE__); return; } } ns_xfr_start(client, rdataset->type); return; case dns_rdatatype_maila: case dns_rdatatype_mailb: query_error(client, DNS_R_NOTIMP, __LINE__); return; case dns_rdatatype_tkey: result = dns_tkey_processquery( client->message, client->sctx->tkeyctx, client->view->dynamickeys); if (result == ISC_R_SUCCESS) { query_send(client); } else { query_error(client, result, __LINE__); } return; default: /* TSIG, etc. */ query_error(client, DNS_R_FORMERR, __LINE__); return; } } /* * Turn on minimal response for (C)DNSKEY and (C)DS queries. */ if (qtype == dns_rdatatype_dnskey || qtype == dns_rdatatype_ds || qtype == dns_rdatatype_cdnskey || qtype == dns_rdatatype_cds) { client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); } else if (qtype == dns_rdatatype_ns) { /* * Always turn on additional records for NS queries. */ client->query.attributes &= ~(NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); } /* * Maybe turn on minimal responses for ANY queries. */ if (qtype == dns_rdatatype_any && client->view->minimal_any && !TCP(client)) { client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); } /* * Turn on minimal responses for EDNS/UDP bufsize 512 queries. */ if (client->ednsversion >= 0 && client->udpsize <= 512U && !TCP(client)) { client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); } /* * If the client has requested that DNSSEC checking be disabled, * allow lookups to return pending data and instruct the resolver * to return data before validation has completed. * * We don't need to set DNS_DBFIND_PENDINGOK when validation is * disabled as there will be no pending data. */ if ((message->flags & DNS_MESSAGEFLAG_CD) != 0 || qtype == dns_rdatatype_rrsig) { client->query.dboptions |= DNS_DBFIND_PENDINGOK; client->query.fetchoptions |= DNS_FETCHOPT_NOVALIDATE; } else if (!client->view->enablevalidation) { client->query.fetchoptions |= DNS_FETCHOPT_NOVALIDATE; } if (client->view->qminimization) { client->query.fetchoptions |= DNS_FETCHOPT_QMINIMIZE | DNS_FETCHOPT_QMIN_SKIP_IP6A; if (client->view->qmin_strict) { client->query.fetchoptions |= DNS_FETCHOPT_QMIN_STRICT; } } /* * Allow glue NS records to be added to the authority section * if the answer is secure. */ if ((message->flags & DNS_MESSAGEFLAG_CD) != 0) { client->query.attributes &= ~NS_QUERYATTR_SECURE; } /* * Set NS_CLIENTATTR_WANTAD if the client has set AD in the query. * This allows AD to be returned on queries without DO set. */ if ((message->flags & DNS_MESSAGEFLAG_AD) != 0) { client->attributes |= NS_CLIENTATTR_WANTAD; } /* * This is an ordinary query. */ result = dns_message_reply(message, true); if (result != ISC_R_SUCCESS) { query_next(client, result); return; } /* * Assume authoritative response until it is known to be * otherwise. * * If "-T noaa" has been set on the command line don't set * AA on authoritative answers. */ if ((client->sctx->options & NS_SERVER_NOAA) == 0) { message->flags |= DNS_MESSAGEFLAG_AA; } /* * Set AD. We must clear it if we add non-validated data to a * response. */ if (WANTDNSSEC(client) || WANTAD(client)) { message->flags |= DNS_MESSAGEFLAG_AD; } (void)query_setup(client, qtype); }