/* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ /*! \file */ #include #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 /*% 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) /*% Cache glue ok? */ #define CACHEGLUEOK(c) (((c)->query.attributes & \ NS_QUERYATTR_CACHEGLUEOK) != 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) /*% 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) /*% No QNAME Proof? */ #define NOQNAME(r) (((r)->attributes & \ DNS_RDATASETATTR_NOQNAME) != 0) #ifdef WANT_QUERYTRACE static inline void client_trace(ns_client_t *client, int level, const char *message) { if (client != NULL && client->query.qname != NULL) { if (isc_log_wouldlog(ns_g_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_g_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, level, "query client=%p thread=0x%lx " "(%s/%s): %s", client, (unsigned long) isc_thread_self(), qbuf, tbuf, message); } } else { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, level, "query client=%p thread=0x%lx " "(): %s", client, (unsigned long) isc_thread_self(), message); } } #define CTRACE(l,m) client_trace(client, l, m) #else #define CTRACE(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 PENDINGOK(x) (((x) & DNS_DBFIND_PENDINGOK) != 0) #define SFCACHE_CDFLAG 0x1 /* * These have the same semantics as: * * foo_attach(b, a); * foo_detach(&a); * * without the locking and magic testing. * * We use SAVE and RESTORE as that shows the operation being performed. */ #define SAVE(a, b) do { INSIST(a == NULL); a = b; b = NULL; } while (0) #define RESTORE(a, b) SAVE(a, b) typedef struct client_additionalctx { ns_client_t *client; dns_rdataset_t *rdataset; } client_additionalctx_t; static isc_result_t query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype); 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 inline 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); /*% * Increment query statistics counters. */ static inline 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; isc_stats_increment(ns_g_server->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 == dns_nsstatscounter_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, dns_nsstatscounter_nonauthans); else inc_stats(client, dns_nsstatscounter_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 = dns_nsstatscounter_referral; else counter = dns_nsstatscounter_nxrrset; } else counter = dns_nsstatscounter_success; } else if (client->message->rcode == dns_rcode_nxdomain) counter = dns_nsstatscounter_nxdomain; else if (client->message->rcode == dns_rcode_badcookie) counter = dns_nsstatscounter_badcookie; else /* We end up here in case of YXDOMAIN, and maybe others */ counter = dns_nsstatscounter_failure; inc_stats(client, counter); ns_client_send(client); } static void query_error(ns_client_t *client, isc_result_t result, int line) { int loglevel = ISC_LOG_DEBUG(3); switch (result) { case DNS_R_SERVFAIL: loglevel = ISC_LOG_DEBUG(1); inc_stats(client, dns_nsstatscounter_servfail); break; case DNS_R_FORMERR: inc_stats(client, dns_nsstatscounter_formerr); break; default: inc_stats(client, dns_nsstatscounter_failure); break; } if (ns_g_server->log_queries) loglevel = ISC_LOG_INFO; log_queryerror(client, result, line, loglevel); ns_client_error(client, result); } static void query_next(ns_client_t *client, isc_result_t result) { if (result == DNS_R_DUPLICATE) inc_stats(client, dns_nsstatscounter_duplicate); else if (result == DNS_R_DROP) inc_stats(client, dns_nsstatscounter_dropped); else inc_stats(client, dns_nsstatscounter_failure); ns_client_next(client, result); } static inline 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; } UNLOCK(&client->query.fetchlock); } static inline void query_putrdataset(ns_client_t *client, dns_rdataset_t **rdatasetp) { dns_rdataset_t *rdataset = *rdatasetp; CTRACE(ISC_LOG_DEBUG(3), "query_putrdataset"); if (rdataset != NULL) { if (dns_rdataset_isassociated(rdataset)) dns_rdataset_disassociate(rdataset); dns_message_puttemprdataset(client->message, rdatasetp); } CTRACE(ISC_LOG_DEBUG(3), "query_putrdataset: done"); } static inline 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) query_putrdataset(client, &client->query.dns64_aaaa); if (client->query.dns64_sigaaaa != NULL) query_putrdataset(client, &client->query.dns64_sigaaaa); if (client->query.dns64_aaaaok != NULL) { isc_mem_put(client->mctx, client->query.dns64_aaaaok, client->query.dns64_aaaaoklen * sizeof(bool)); client->query.dns64_aaaaok = NULL; client->query.dns64_aaaaoklen = 0; } query_putrdataset(client, &client->query.redirect.rdataset); query_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) { 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; 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_next_callback(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); } static inline isc_result_t query_newnamebuf(ns_client_t *client) { isc_buffer_t *dbuf; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "query_newnamebuf"); /*% * Allocate a name buffer. */ dbuf = NULL; result = isc_buffer_allocate(client->mctx, &dbuf, 1024); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_DEBUG(3), "query_newnamebuf: isc_buffer_allocate failed: done"); return (result); } ISC_LIST_APPEND(client->query.namebufs, dbuf, link); CTRACE(ISC_LOG_DEBUG(3), "query_newnamebuf: done"); return (ISC_R_SUCCESS); } static inline isc_buffer_t * query_getnamebuf(ns_client_t *client) { isc_buffer_t *dbuf; isc_result_t result; isc_region_t r; CTRACE(ISC_LOG_DEBUG(3), "query_getnamebuf"); /*% * Return a name buffer with space for a maximal name, allocating * a new one if necessary. */ if (ISC_LIST_EMPTY(client->query.namebufs)) { result = query_newnamebuf(client); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_DEBUG(3), "query_getnamebuf: query_newnamebuf failed: done"); return (NULL); } } dbuf = ISC_LIST_TAIL(client->query.namebufs); INSIST(dbuf != NULL); isc_buffer_availableregion(dbuf, &r); if (r.length < DNS_NAME_MAXWIRE) { result = query_newnamebuf(client); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_DEBUG(3), "query_getnamebuf: query_newnamebuf failed: done"); return (NULL); } dbuf = ISC_LIST_TAIL(client->query.namebufs); isc_buffer_availableregion(dbuf, &r); INSIST(r.length >= 255); } CTRACE(ISC_LOG_DEBUG(3), "query_getnamebuf: done"); return (dbuf); } static inline void query_keepname(ns_client_t *client, dns_name_t *name, isc_buffer_t *dbuf) { isc_region_t r; CTRACE(ISC_LOG_DEBUG(3), "query_keepname"); /*% * 'name' is using space in 'dbuf', but 'dbuf' has not yet been * adjusted to take account of that. We do the adjustment. */ REQUIRE((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) != 0); dns_name_toregion(name, &r); isc_buffer_add(dbuf, r.length); dns_name_setbuffer(name, NULL); client->query.attributes &= ~NS_QUERYATTR_NAMEBUFUSED; } static inline void query_releasename(ns_client_t *client, dns_name_t **namep) { dns_name_t *name = *namep; /*% * 'name' is no longer needed. Return it to our pool of temporary * names. If it is using a name buffer, relinquish its exclusive * rights on the buffer. */ CTRACE(ISC_LOG_DEBUG(3), "query_releasename"); if (dns_name_hasbuffer(name)) { INSIST((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) != 0); client->query.attributes &= ~NS_QUERYATTR_NAMEBUFUSED; } dns_message_puttempname(client->message, namep); CTRACE(ISC_LOG_DEBUG(3), "query_releasename: done"); } static inline dns_name_t * query_newname(ns_client_t *client, isc_buffer_t *dbuf, isc_buffer_t *nbuf) { dns_name_t *name; isc_region_t r; isc_result_t result; REQUIRE((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) == 0); CTRACE(ISC_LOG_DEBUG(3), "query_newname"); name = NULL; result = dns_message_gettempname(client->message, &name); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_DEBUG(3), "query_newname: dns_message_gettempname failed: done"); return (NULL); } isc_buffer_availableregion(dbuf, &r); isc_buffer_init(nbuf, r.base, r.length); dns_name_init(name, NULL); dns_name_setbuffer(name, nbuf); client->query.attributes |= NS_QUERYATTR_NAMEBUFUSED; CTRACE(ISC_LOG_DEBUG(3), "query_newname: done"); return (name); } static inline dns_rdataset_t * query_newrdataset(ns_client_t *client) { dns_rdataset_t *rdataset; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "query_newrdataset"); rdataset = NULL; result = dns_message_gettemprdataset(client->message, &rdataset); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_DEBUG(3), "query_newrdataset: " "dns_message_gettemprdataset failed: done"); return (NULL); } CTRACE(ISC_LOG_DEBUG(3), "query_newrdataset: done"); return (rdataset); } static inline isc_result_t query_newdbversion(ns_client_t *client, unsigned int n) { unsigned int i; ns_dbversion_t *dbversion; for (i = 0; i < n; i++) { dbversion = isc_mem_get(client->mctx, sizeof(*dbversion)); if (dbversion != NULL) { dbversion->db = NULL; dbversion->version = NULL; ISC_LIST_INITANDAPPEND(client->query.freeversions, dbversion, link); } else { /* * We only return ISC_R_NOMEMORY if we couldn't * allocate anything. */ if (i == 0) return (ISC_R_NOMEMORY); else return (ISC_R_SUCCESS); } } return (ISC_R_SUCCESS); } static inline ns_dbversion_t * query_getdbversion(ns_client_t *client) { isc_result_t result; ns_dbversion_t *dbversion; if (ISC_LIST_EMPTY(client->query.freeversions)) { result = query_newdbversion(client, 1); if (result != ISC_R_SUCCESS) return (NULL); } dbversion = ISC_LIST_HEAD(client->query.freeversions); INSIST(dbversion != NULL); ISC_LIST_UNLINK(client->query.freeversions, dbversion, link); return (dbversion); } isc_result_t ns_query_init(ns_client_t *client) { isc_result_t result; 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(). */ result = isc_mutex_init(&client->query.fetchlock); if (result != ISC_R_SUCCESS) return (result); 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); result = query_newdbversion(client, 3); if (result != ISC_R_SUCCESS) { DESTROYLOCK(&client->query.fetchlock); return (result); } result = query_newnamebuf(client); if (result != ISC_R_SUCCESS) { query_freefreeversions(client, true); DESTROYLOCK(&client->query.fetchlock); } return (result); } static ns_dbversion_t * query_findversion(ns_client_t *client, dns_db_t *db) { ns_dbversion_t *dbversion; /*% * We may already have done a query related to this * database. If so, we must be sure to make subsequent * queries from the same version. */ for (dbversion = ISC_LIST_HEAD(client->query.activeversions); dbversion != NULL; dbversion = ISC_LIST_NEXT(dbversion, link)) { if (dbversion->db == db) break; } if (dbversion == NULL) { /* * This is a new zone for this query. Add it to * the active list. */ dbversion = query_getdbversion(client); if (dbversion == NULL) return (NULL); dns_db_attach(db, &dbversion->db); dns_db_currentversion(db, &dbversion->version); dbversion->acl_checked = false; dbversion->queryok = false; ISC_LIST_APPEND(client->query.activeversions, dbversion, link); } return (dbversion); } static inline isc_result_t query_validatezonedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, unsigned int options, dns_zone_t *zone, dns_db_t *db, dns_dbversion_t **versionp) { isc_result_t result; dns_acl_t *queryacl, *queryonacl; ns_dbversion_t *dbversion; REQUIRE(zone != NULL); REQUIRE(db != NULL); /* * 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. */ if (!client->view->additionalfromauth && 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 = query_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_g_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 { 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); } } 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 ((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 inline isc_result_t query_getzonedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, unsigned int options, dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp) { isc_result_t result; unsigned int ztoptions; dns_zone_t *zone = NULL; dns_db_t *db = NULL; bool partial = false; REQUIRE(zonep != NULL && *zonep == NULL); REQUIRE(dbp != NULL && *dbp == NULL); /*% * Find a zone database to answer the query. */ ztoptions = ((options & DNS_GETDB_NOEXACT) != 0) ? DNS_ZTFIND_NOEXACT : 0; result = dns_zt_find(client->view->zonetable, name, ztoptions, NULL, &zone); if (result == DNS_R_PARTIALMATCH) partial = 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) { isc_stats_t *zonestats; char qname_buf[DNS_NAME_FORMATSIZE]; char p_name_buf[DNS_NAME_FORMATSIZE]; char cname_buf[DNS_NAME_FORMATSIZE] = { 0 }; const char *s1 = cname_buf, *s2 = cname_buf; dns_rpz_st_t *st; /* * Count enabled rewrites in the global counter. * Count both enabled and disabled rewrites for each zone. */ if (!disabled && policy != DNS_RPZ_POLICY_PASSTHRU) { isc_stats_increment(ns_g_server->nsstats, dns_nsstatscounter_rpz_rewrites); } if (p_zone != NULL) { zonestats = dns_zone_getrequeststats(p_zone); if (zonestats != NULL) isc_stats_increment(zonestats, dns_nsstatscounter_rpz_rewrites); } if (!isc_log_wouldlog(ns_g_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 = ")"; } ns_client_log(client, DNS_LOGCATEGORY_RPZ, NS_LOGMODULE_QUERY, DNS_RPZ_INFO_LEVEL, "%srpz %s %s rewrite %s via %s%s%s%s", disabled ? "disabled " : "", dns_rpz_type2str(type), dns_rpz_policy2str(policy), qname_buf, 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; const char *slash; const char *via; const char *str_blank; const char *rpztypestr1; const char *rpztypestr2; if (!isc_log_wouldlog(ns_g_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_g_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); } static inline isc_result_t query_getcachedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, dns_db_t **dbp, unsigned int options) { isc_result_t result; bool check_acl; dns_db_t *db = NULL; REQUIRE(dbp != NULL && *dbp == NULL); /*% * 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. */ if (!USECACHE(client)) return (DNS_R_REFUSED); dns_db_attach(client->view->cachedb, &db); if ((client->query.attributes & NS_QUERYATTR_CACHEACLOKVALID) != 0) { /* * We've evaluated the view's cacheacl already. If * NS_QUERYATTR_CACHEACLOK is set, then the client is * allowed to make queries, otherwise the query should * be refused. */ check_acl = false; if ((client->query.attributes & NS_QUERYATTR_CACHEACLOK) == 0) goto refuse; } else { /* * We haven't evaluated the view's queryacl yet. */ check_acl = true; } if (check_acl) { bool log = !(options & DNS_GETDB_NOLOG); char msg[NS_CLIENT_ACLMSGSIZE("query (cache)")]; result = ns_client_checkaclsilent(client, NULL, client->view->cacheacl, true); if (result == ISC_R_SUCCESS) { /* * We were allowed by the "allow-query-cache" ACL. * Remember this so we don't have to check again. */ client->query.attributes |= NS_QUERYATTR_CACHEACLOK; if (log && isc_log_wouldlog(ns_g_lctx, ISC_LOG_DEBUG(3))) { ns_client_aclmsg("query (cache)", name, qtype, client->view->rdclass, msg, sizeof(msg)); ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_QUERY, ISC_LOG_DEBUG(3), "%s approved", msg); } } else if (log) { ns_client_aclmsg("query (cache)", name, qtype, client->view->rdclass, msg, sizeof(msg)); ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "%s denied", msg); } /* * We've now evaluated the view's query ACL, and * the NS_QUERYATTR_CACHEACLOKVALID attribute is now valid. */ client->query.attributes |= NS_QUERYATTR_CACHEACLOKVALID; if (result != ISC_R_SUCCESS) goto refuse; } /* Approved. */ /* Transfer ownership. */ *dbp = db; return (ISC_R_SUCCESS); refuse: result = DNS_R_REFUSED; if (db != NULL) dns_db_detach(&db); return (result); } static inline 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 (ISC_UNLIKELY(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); 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 = query_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 inline 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); } static isc_result_t query_addadditional(void *arg, dns_name_t *name, dns_rdatatype_t qtype) { ns_client_t *client = arg; isc_result_t result, eresult; dns_dbnode_t *node; dns_db_t *db; dns_name_t *fname, *mname; dns_rdataset_t *rdataset, *sigrdataset, *trdataset; isc_buffer_t *dbuf; isc_buffer_t b; dns_dbversion_t *version; bool added_something, need_addname; dns_zone_t *zone; dns_rdatatype_t type; dns_clientinfomethods_t cm; dns_clientinfo_t ci; dns_rdatasetadditional_t additionaltype; 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_addadditional"); /* * Initialization. */ eresult = ISC_R_SUCCESS; fname = NULL; rdataset = NULL; sigrdataset = NULL; trdataset = NULL; db = NULL; version = NULL; node = NULL; added_something = false; need_addname = false; zone = NULL; additionaltype = dns_rdatasetadditional_fromauth; 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 = query_getnamebuf(client); if (dbuf == NULL) goto cleanup; fname = query_newname(client, dbuf, &b); rdataset = query_newrdataset(client); if (fname == NULL || rdataset == NULL) goto cleanup; if (WANTDNSSEC(client)) { sigrdataset = query_newrdataset(client); if (sigrdataset == NULL) goto cleanup; } /* * Look for a zone database that might contain authoritative * additional data. */ result = query_getzonedb(client, name, qtype, DNS_GETDB_NOLOG, &zone, &db, &version); if (result != ISC_R_SUCCESS) goto try_cache; CTRACE(ISC_LOG_DEBUG(3), "query_addadditional: db_find"); /* * 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 (sigrdataset != NULL && !dns_db_issecure(db) && dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); 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); version = NULL; dns_db_detach(&db); /* * No authoritative data was found. The cache is our next best bet. */ try_cache: 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 = query_newrdataset(client); if (sigrdataset == NULL) goto cleanup; } 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(client->view->cache, result); if (!WANTDNSSEC(client)) query_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; dns_db_attach(client->query.gluedb, &db); 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. */ query_keepname(client, fname, dbuf); /* * 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); query_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) { #ifdef ALLOW_FILTER_AAAA bool have_a = false; #endif /* * 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 = query_newrdataset(client); if (rdataset == NULL) goto addname; } if (sigrdataset != NULL) { if (dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); } else if (WANTDNSSEC(client)) { sigrdataset = query_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; if (result == DNS_R_NCACHENXRRSET) { dns_rdataset_disassociate(rdataset); if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); } if (result == ISC_R_SUCCESS) { bool invalid = false; mname = NULL; #ifdef ALLOW_FILTER_AAAA have_a = true; #endif 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) { query_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 = query_newrdataset(client); } rdataset = query_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; if (result == DNS_R_NCACHENXRRSET) { dns_rdataset_disassociate(rdataset); if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); } if (result == ISC_R_SUCCESS) { bool invalid = false; mname = NULL; /* * There's an A; check whether we're filtering AAAA */ #ifdef ALLOW_FILTER_AAAA if (have_a && (client->filter_aaaa == dns_aaaa_break_dnssec || (client->filter_aaaa == dns_aaaa_filter && (!WANTDNSSEC(client) || sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset))))) goto addname; #endif 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) { query_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_addadditional: 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); fname = NULL; /* * In a few cases, we want to add additional data for additional * data. It's simpler to just deal with special cases here than * to try to create a general purpose mechanism and allow the * rdata implementations to do it themselves. * * This involves recursion, but the depth is limited. The * most complex case is adding a SRV rdataset, which involves * recursing to add address records, which in turn can cause * recursion to add KEYs. */ if (type == dns_rdatatype_srv && trdataset != NULL) { /* * If we're adding SRV records to the additional data * section, it's helpful if we add the SRV additional data * as well. */ eresult = dns_rdataset_additionaldata(trdataset, query_addadditional, client); } cleanup: CTRACE(ISC_LOG_DEBUG(3), "query_addadditional: cleanup"); query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); if (fname != NULL) query_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); CTRACE(ISC_LOG_DEBUG(3), "query_addadditional: done"); return (eresult); } static inline void query_discardcache(ns_client_t *client, dns_rdataset_t *rdataset_base, dns_rdatasetadditional_t additionaltype, dns_rdatatype_t type, dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp, dns_dbnode_t **nodep, dns_name_t *fname) { dns_rdataset_t *rdataset; while ((rdataset = ISC_LIST_HEAD(fname->list)) != NULL) { ISC_LIST_UNLINK(fname->list, rdataset, link); query_putrdataset(client, &rdataset); } if (*versionp != NULL) dns_db_closeversion(*dbp, versionp, false); if (*nodep != NULL) dns_db_detachnode(*dbp, nodep); if (*dbp != NULL) dns_db_detach(dbp); if (*zonep != NULL) dns_zone_detach(zonep); (void)dns_rdataset_putadditional(client->view->acache, rdataset_base, additionaltype, type); } static inline isc_result_t query_iscachevalid(dns_zone_t *zone, dns_db_t *db, dns_db_t *db0, dns_dbversion_t *version) { isc_result_t result = ISC_R_SUCCESS; dns_dbversion_t *version_current = NULL; dns_db_t *db_current = db0; if (db_current == NULL) { result = dns_zone_getdb(zone, &db_current); if (result != ISC_R_SUCCESS) return (result); } dns_db_currentversion(db_current, &version_current); if (db_current != db || version_current != version) { result = ISC_R_FAILURE; goto cleanup; } cleanup: dns_db_closeversion(db_current, &version_current, false); if (db0 == NULL && db_current != NULL) dns_db_detach(&db_current); return (result); } static isc_result_t query_addadditional2(void *arg, dns_name_t *name, dns_rdatatype_t qtype) { client_additionalctx_t *additionalctx = arg; dns_rdataset_t *rdataset_base; ns_client_t *client; isc_result_t result, eresult; dns_dbnode_t *node, *cnode; dns_db_t *db, *cdb; dns_name_t *fname, *mname0, cfname; dns_rdataset_t *rdataset, *sigrdataset; dns_rdataset_t *crdataset, *crdataset_next; isc_buffer_t *dbuf; isc_buffer_t b; dns_dbversion_t *version, *cversion; bool added_something, need_addname, needadditionalcache; bool need_sigrrset; dns_zone_t *zone; dns_rdatatype_t type; dns_rdatasetadditional_t additionaltype; dns_clientinfomethods_t cm; dns_clientinfo_t ci; bool invalid; /* * If we don't have an additional cache call query_addadditional. */ client = additionalctx->client; REQUIRE(NS_CLIENT_VALID(client)); if (qtype != dns_rdatatype_a || client->view->acache == NULL) { /* * This function is optimized for "address" types. For other * types, use a generic routine. * XXX: ideally, this function should be generic enough. */ return (query_addadditional(additionalctx->client, name, qtype)); } /* * Initialization. */ rdataset_base = additionalctx->rdataset; eresult = ISC_R_SUCCESS; fname = NULL; rdataset = NULL; sigrdataset = NULL; db = NULL; cdb = NULL; version = NULL; cversion = NULL; node = NULL; cnode = NULL; added_something = false; need_addname = false; zone = NULL; needadditionalcache = false; POST(needadditionalcache); additionaltype = dns_rdatasetadditional_fromauth; dns_name_init(&cfname, NULL); dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2"); /* * 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. * XXXJT: this approach can cause a suboptimal result when the cache * DB only has partial address types and the glue DB has remaining * ones. */ type = dns_rdatatype_any; /* * Get some resources. */ dbuf = query_getnamebuf(client); if (dbuf == NULL) goto cleanup; fname = query_newname(client, dbuf, &b); if (fname == NULL) goto cleanup; dns_name_setbuffer(&cfname, &b); /* share the buffer */ /* Check additional cache */ result = dns_rdataset_getadditional(rdataset_base, additionaltype, type, client->view->acache, &zone, &cdb, &cversion, &cnode, &cfname, client->message, client->now); if (result != ISC_R_SUCCESS) goto findauthdb; if (zone == NULL) { CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: auth zone not found"); goto try_cache; } /* Is the cached DB up-to-date? */ result = query_iscachevalid(zone, cdb, NULL, cversion); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: old auth additional cache"); query_discardcache(client, rdataset_base, additionaltype, type, &zone, &cdb, &cversion, &cnode, &cfname); goto findauthdb; } if (cnode == NULL) { /* * We have a negative cache. We don't have to check the zone * ACL, since the result (not using this zone) would be same * regardless of the result. */ CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: negative auth additional cache"); dns_db_closeversion(cdb, &cversion, false); dns_db_detach(&cdb); dns_zone_detach(&zone); goto try_cache; } result = query_validatezonedb(client, name, qtype, DNS_GETDB_NOLOG, zone, cdb, NULL); if (result != ISC_R_SUCCESS) { query_discardcache(client, rdataset_base, additionaltype, type, &zone, &cdb, &cversion, &cnode, &cfname); goto try_cache; } /* We've got an active cache. */ CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: auth additional cache"); dns_db_closeversion(cdb, &cversion, false); db = cdb; node = cnode; dns_name_clone(&cfname, fname); query_keepname(client, fname, dbuf); goto foundcache; /* * Look for a zone database that might contain authoritative * additional data. */ findauthdb: result = query_getzonedb(client, name, qtype, DNS_GETDB_NOLOG, &zone, &db, &version); if (result != ISC_R_SUCCESS) { /* Cache the negative result */ (void)dns_rdataset_setadditional(rdataset_base, additionaltype, type, client->view->acache, NULL, NULL, NULL, NULL, NULL); goto try_cache; } CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: db_find"); /* * 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. */ node = NULL; result = dns_db_findext(db, name, version, type, client->query.dboptions, client->now, &node, fname, &cm, &ci, NULL, NULL); if (result == ISC_R_SUCCESS) goto found; /* Cache the negative result */ (void)dns_rdataset_setadditional(rdataset_base, additionaltype, type, client->view->acache, zone, db, version, NULL, fname); if (node != NULL) dns_db_detachnode(db, &node); version = NULL; dns_db_detach(&db); /* * No authoritative data was found. The cache is our next best bet. */ try_cache: 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; result = dns_db_findext(db, name, version, type, client->query.dboptions | DNS_DBFIND_GLUEOK | DNS_DBFIND_ADDITIONALOK, client->now, &node, fname, &cm, &ci, NULL, NULL); if (result == ISC_R_SUCCESS) goto found; 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; /* Check additional cache */ additionaltype = dns_rdatasetadditional_fromglue; result = dns_rdataset_getadditional(rdataset_base, additionaltype, type, client->view->acache, NULL, &cdb, &cversion, &cnode, &cfname, client->message, client->now); if (result != ISC_R_SUCCESS) goto findglue; result = query_iscachevalid(zone, cdb, client->query.gluedb, cversion); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: old glue additional cache"); query_discardcache(client, rdataset_base, additionaltype, type, &zone, &cdb, &cversion, &cnode, &cfname); goto findglue; } if (cnode == NULL) { /* We have a negative cache. */ CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: negative glue additional cache"); dns_db_closeversion(cdb, &cversion, false); dns_db_detach(&cdb); goto cleanup; } /* Cache hit. */ CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: glue additional cache"); dns_db_closeversion(cdb, &cversion, false); db = cdb; node = cnode; dns_name_clone(&cfname, fname); query_keepname(client, fname, dbuf); goto foundcache; findglue: dns_db_attach(client->query.gluedb, &db); result = dns_db_findext(db, name, version, type, client->query.dboptions | DNS_DBFIND_GLUEOK, client->now, &node, fname, &cm, &ci, NULL, NULL); if (!(result == ISC_R_SUCCESS || result == DNS_R_ZONECUT || result == DNS_R_GLUE)) { /* cache the negative result */ (void)dns_rdataset_setadditional(rdataset_base, additionaltype, type, client->view->acache, NULL, db, version, NULL, fname); goto cleanup; } found: /* * We have found a DB node to iterate over from a DB. * We are going to look for address RRsets (i.e., A and AAAA) in the DB * node we've just found. We'll then store the complete information * in the additional data cache. */ dns_name_clone(fname, &cfname); query_keepname(client, fname, dbuf); needadditionalcache = true; rdataset = query_newrdataset(client); if (rdataset == NULL) goto cleanup; sigrdataset = query_newrdataset(client); if (sigrdataset == NULL) goto cleanup; if (additionaltype == dns_rdatasetadditional_fromcache && query_isduplicate(client, fname, dns_rdatatype_a, NULL)) goto aaaa_lookup; /* * Find A RRset with sig RRset. Even if we don't find a sig RRset * for a client using DNSSEC, we'll continue the process to make a * complete list to be cached. However, we need to cancel the * caching when something unexpected happens, in order to avoid * caching incomplete information. */ result = dns_db_findrdataset(db, node, version, dns_rdatatype_a, 0, client->now, rdataset, sigrdataset); /* * Try to promote pending/glue from the cache to secure. * If unable to do so, drop it from the response unless * it's glue, in which case it may still be needed. */ invalid = false; if (result == ISC_R_SUCCESS && additionaltype == dns_rdatasetadditional_fromcache && (DNS_TRUST_PENDING(rdataset->trust) || DNS_TRUST_GLUE(rdataset->trust))) { invalid = !validate(client, db, fname, rdataset, sigrdataset); } if (invalid && DNS_TRUST_PENDING(rdataset->trust)) { dns_rdataset_disassociate(rdataset); if (dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); result = ISC_R_NOTFOUND; } if (result == DNS_R_NCACHENXDOMAIN) goto setcache; if (result == DNS_R_NCACHENXRRSET) { dns_rdataset_disassociate(rdataset); if (dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); } if (result == ISC_R_SUCCESS) { /* Remember the result as a cache */ ISC_LIST_APPEND(cfname.list, rdataset, link); if (dns_rdataset_isassociated(sigrdataset)) { ISC_LIST_APPEND(cfname.list, sigrdataset, link); sigrdataset = query_newrdataset(client); } rdataset = query_newrdataset(client); if (sigrdataset == NULL || rdataset == NULL) { /* do not cache incomplete information */ goto foundcache; } } aaaa_lookup: if (additionaltype == dns_rdatasetadditional_fromcache && query_isduplicate(client, fname, dns_rdatatype_aaaa, NULL)) goto foundcache; /* Find AAAA RRset with sig RRset */ result = dns_db_findrdataset(db, node, version, dns_rdatatype_aaaa, 0, client->now, rdataset, sigrdataset); /* * Try to promote pending/glue from the cache to secure. * If unable to do so, drop it from the response unless * it's glue, in which case it may still be needed. */ invalid = false; if (result == ISC_R_SUCCESS && additionaltype == dns_rdatasetadditional_fromcache && (DNS_TRUST_PENDING(rdataset->trust) || DNS_TRUST_GLUE(rdataset->trust))) { invalid = !validate(client, db, fname, rdataset, sigrdataset); } if (invalid && DNS_TRUST_PENDING(rdataset->trust)) { dns_rdataset_disassociate(rdataset); if (dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); result = ISC_R_NOTFOUND; } if (result == ISC_R_SUCCESS) { ISC_LIST_APPEND(cfname.list, rdataset, link); rdataset = NULL; if (dns_rdataset_isassociated(sigrdataset)) { ISC_LIST_APPEND(cfname.list, sigrdataset, link); sigrdataset = NULL; } } setcache: /* * Set the new result in the cache if required. We do not support * caching additional data from a cache DB. */ if (needadditionalcache == true && (additionaltype == dns_rdatasetadditional_fromauth || additionaltype == dns_rdatasetadditional_fromglue)) { (void)dns_rdataset_setadditional(rdataset_base, additionaltype, type, client->view->acache, zone, db, version, node, &cfname); } foundcache: need_sigrrset = false; mname0 = NULL; for (crdataset = ISC_LIST_HEAD(cfname.list); crdataset != NULL; crdataset = crdataset_next) { dns_name_t *mname; crdataset_next = ISC_LIST_NEXT(crdataset, link); mname = NULL; if (crdataset->type == dns_rdatatype_a || crdataset->type == dns_rdatatype_aaaa) { if (!query_isduplicate(client, fname, crdataset->type, &mname)) { if (mname != fname) { if (mname != NULL) { /* * A different type of this name is * already stored in the additional * section. We'll reuse the name. * Note that this should happen at most * once. Otherwise, fname->link could * leak below. */ INSIST(mname0 == NULL); query_releasename(client, &fname); fname = mname; mname0 = mname; } else need_addname = true; } ISC_LIST_UNLINK(cfname.list, crdataset, link); ISC_LIST_APPEND(fname->list, crdataset, link); added_something = true; need_sigrrset = true; } else need_sigrrset = false; } else if (crdataset->type == dns_rdatatype_rrsig && need_sigrrset && WANTDNSSEC(client)) { ISC_LIST_UNLINK(cfname.list, crdataset, link); ISC_LIST_APPEND(fname->list, crdataset, link); added_something = true; /* just in case */ need_sigrrset = false; } } CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: 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); fname = NULL; cleanup: CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: cleanup"); if (rdataset != NULL) query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); while ((crdataset = ISC_LIST_HEAD(cfname.list)) != NULL) { ISC_LIST_UNLINK(cfname.list, crdataset, link); query_putrdataset(client, &crdataset); } if (fname != NULL) query_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); CTRACE(ISC_LOG_DEBUG(3), "query_addadditional2: done"); return (eresult); } static inline void query_addrdataset(ns_client_t *client, dns_name_t *fname, dns_rdataset_t *rdataset) { client_additionalctx_t additionalctx; /* * Add 'rdataset' and any pertinent additional data to * 'fname', a name in the response message for 'client'. */ CTRACE(ISC_LOG_DEBUG(3), "query_addrdataset"); ISC_LIST_APPEND(fname->list, rdataset, link); if (client->view->order != NULL) rdataset->attributes |= dns_order_find(client->view->order, fname, rdataset->type, rdataset->rdclass); rdataset->attributes |= DNS_RDATASETATTR_LOADORDER; if (NOADDITIONAL(client)) return; /* * Add additional data. * * We don't care if dns_rdataset_additionaldata() fails. */ additionalctx.client = client; additionalctx.rdataset = rdataset; (void)dns_rdataset_additionaldata(rdataset, query_addadditional2, &additionalctx); CTRACE(ISC_LOG_DEBUG(3), "query_addrdataset: done"); } static isc_result_t query_dns64(ns_client_t *client, dns_name_t **namep, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, isc_buffer_t *dbuf, dns_section_t section) { 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; /*% * 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. */ CTRACE(ISC_LOG_DEBUG(3), "query_dns64"); name = *namep; 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, 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 (dbuf != NULL) query_releasename(client, namep); return (ISC_R_SUCCESS); } else if (result == DNS_R_NXDOMAIN) { /* * The name doesn't exist. */ if (dbuf != NULL) query_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) query_releasename(client, namep); } if (rdataset->trust != dns_trust_secure && (section == DNS_SECTION_ANSWER || section == DNS_SECTION_AUTHORITY)) client->query.attributes &= ~NS_QUERYATTR_SECURE; isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); result = isc_buffer_allocate(client->mctx, &buffer, view->dns64cnt * 16 * dns_rdataset_count(rdataset)); if (result != ISC_R_SUCCESS) goto cleanup; 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(rdataset->ttl, client->query.dns64_ttl); else dns64_rdatalist->ttl = ISC_MIN(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 (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) flags |= DNS_DNS64_DNSSEC; for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { for (dns64 = ISC_LIST_HEAD(client->view->dns64); dns64 != NULL; dns64 = dns_dns64_next(dns64)) { dns_rdataset_current(rdataset, &rdata); isc_buffer_availableregion(buffer, &r); INSIST(r.length >= 16); result = dns_dns64_aaaafroma(dns64, &netaddr, client->signer, &ns_g_server->aclenv, 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 = rdataset->trust; query_addrdataset(client, mname, dns64_rdataset); dns64_rdataset = NULL; dns64_rdatalist = NULL; dns_message_takebuffer(client->message, &buffer); inc_stats(client, dns_nsstatscounter_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(ns_client_t *client, dns_name_t **namep, dns_rdataset_t *rdataset, isc_buffer_t *dbuf, dns_section_t section) { 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; CTRACE(ISC_LOG_DEBUG(3), "query_filter64"); INSIST(client->query.dns64_aaaaok != NULL); INSIST(client->query.dns64_aaaaoklen == dns_rdataset_count(rdataset)); name = *namep; mname = NULL; buffer = NULL; myrdata = NULL; myrdataset = NULL; myrdatalist = NULL; result = dns_message_findname(client->message, section, name, dns_rdatatype_aaaa, 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 (dbuf != NULL) query_releasename(client, namep); return; } else if (result == DNS_R_NXDOMAIN) { mname = name; *namep = NULL; } else { RUNTIME_CHECK(result == DNS_R_NXRRSET); if (dbuf != NULL) query_releasename(client, namep); dbuf = NULL; } if (rdataset->trust != dns_trust_secure && (section == DNS_SECTION_ANSWER || section == DNS_SECTION_AUTHORITY)) client->query.attributes &= ~NS_QUERYATTR_SECURE; result = isc_buffer_allocate(client->mctx, &buffer, 16 * dns_rdataset_count(rdataset)); if (result != ISC_R_SUCCESS) goto cleanup; 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 = rdataset->ttl; i = 0; for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { if (!client->query.dns64_aaaaok[i++]) continue; dns_rdataset_current(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 (dbuf != NULL) query_keepname(client, name, dbuf); dns_message_addname(client->message, name, section); dbuf = NULL; } myrdataset->trust = rdataset->trust; query_addrdataset(client, 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 (dbuf != NULL) query_releasename(client, &name); CTRACE(ISC_LOG_DEBUG(3), "query_filter64: done"); } static void query_addrrset(ns_client_t *client, dns_name_t **namep, dns_rdataset_t **rdatasetp, dns_rdataset_t **sigrdatasetp, isc_buffer_t *dbuf, dns_section_t section) { dns_name_t *name, *mname; dns_rdataset_t *rdataset, *mrdataset, *sigrdataset; isc_result_t result; /*% * 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. */ CTRACE(ISC_LOG_DEBUG(3), "query_addrrset"); name = *namep; rdataset = *rdatasetp; if (sigrdatasetp != NULL) sigrdataset = *sigrdatasetp; else sigrdataset = NULL; mname = NULL; mrdataset = NULL; 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) query_releasename(client, namep); if ((rdataset->attributes & DNS_RDATASETATTR_REQUIRED) != 0) mrdataset->attributes |= DNS_RDATASETATTR_REQUIRED; return; } else if (result == DNS_R_NXDOMAIN) { /* * The name doesn't exist. */ if (dbuf != NULL) query_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) query_releasename(client, namep); } if (rdataset->trust != dns_trust_secure && (section == DNS_SECTION_ANSWER || section == DNS_SECTION_AUTHORITY)) client->query.attributes &= ~NS_QUERYATTR_SECURE; /* * 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. */ query_addrdataset(client, mname, rdataset); *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"); } static inline isc_result_t query_addsoa(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version, unsigned int override_ttl, bool isassociated, dns_section_t section) { dns_name_t *name; dns_dbnode_t *node; isc_result_t result, eresult; 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"); /* * Initialization. */ eresult = ISC_R_SUCCESS; name = NULL; rdataset = NULL; node = NULL; 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 (ns_g_nosoa && (!WANTDNSSEC(client) || !isassociated)) 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); dns_name_init(name, NULL); dns_name_clone(dns_db_origin(db), name); rdataset = query_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(db)) { sigrdataset = query_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(db, &node); if (result == ISC_R_SUCCESS) { result = dns_db_findrdataset(db, node, 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(db, name, 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); if (result != ISC_R_SUCCESS) goto cleanup; 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(client, &name, &rdataset, sigrdatasetp, NULL, section); } cleanup: query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); if (name != NULL) query_releasename(client, &name); if (node != NULL) dns_db_detachnode(db, &node); return (eresult); } static inline isc_result_t query_addns(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version) { dns_name_t *name, *fname; dns_dbnode_t *node; isc_result_t result, eresult; 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; name = NULL; rdataset = NULL; node = NULL; 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_init(name, NULL); dns_name_clone(dns_db_origin(db), name); rdataset = query_newrdataset(client); if (rdataset == NULL) { CTRACE(ISC_LOG_ERROR, "query_addns: query_newrdataset failed"); eresult = DNS_R_SERVFAIL; goto cleanup; } if (WANTDNSSEC(client) && dns_db_issecure(db)) { sigrdataset = query_newrdataset(client); if (sigrdataset == NULL) { CTRACE(ISC_LOG_ERROR, "query_addns: query_newrdataset failed"); eresult = DNS_R_SERVFAIL; goto cleanup; } } /* * Find the NS rdataset. */ result = dns_db_getoriginnode(db, &node); if (result == ISC_R_SUCCESS) { result = dns_db_findrdataset(db, node, 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(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; else sigrdatasetp = NULL; query_addrrset(client, &name, &rdataset, sigrdatasetp, NULL, DNS_SECTION_AUTHORITY); } cleanup: CTRACE(ISC_LOG_DEBUG(3), "query_addns: cleanup"); query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); if (name != NULL) query_releasename(client, &name); if (node != NULL) dns_db_detachnode(db, &node); CTRACE(ISC_LOG_DEBUG(3), "query_addns: done"); return (eresult); } static isc_result_t query_add_cname(ns_client_t *client, dns_name_t *qname, dns_name_t *tname, dns_trust_t trust, dns_ttl_t ttl) { dns_rdataset_t *rdataset; dns_rdatalist_t *rdatalist; dns_rdata_t *rdata; isc_region_t r; dns_name_t *aname; isc_result_t result; /* * We assume the name data referred to by tname won't go away. */ aname = NULL; result = dns_message_gettempname(client->message, &aname); if (result != ISC_R_SUCCESS) return (result); result = dns_name_dup(qname, client->mctx, aname); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &aname); return (result); } rdatalist = NULL; result = dns_message_gettemprdatalist(client->message, &rdatalist); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &aname); return (result); } rdata = NULL; 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); } rdataset = NULL; 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(tname, &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(client, &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); } static bool get_root_key_sentinel_id(ns_client_t *client, const char *ndata) { unsigned int v = 0; int i; for (i = 0; i < 5; i++) { if (ndata[i] < '0' || ndata[i] > '9') { return (false); } v *= 10; v += ndata[i] - '0'; } if (v > 65535U) { return (false); } 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(ns_client_t *client) { const char *ndata = (const char *)client->query.qname->ndata; if (client->query.qname->length > 30 && ndata[0] == 29 && strncasecmp(ndata + 1, "root-key-sentinel-is-ta-", 24) == 0) { if (!get_root_key_sentinel_id(client, ndata + 25)) { return; } client->query.root_key_sentinel_is_ta = true; ns_client_log(client, NS_LOGCATEGORY_TAT, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "root-key-sentinel-is-ta query label found"); } else if (client->query.qname->length > 31 && ndata[0] == 30 && strncasecmp(ndata + 1, "root-key-sentinel-not-ta-", 25) == 0) { if (!get_root_key_sentinel_id(client, ndata + 26)) { return; } client->query.root_key_sentinel_not_ta = true; ns_client_log(client, NS_LOGCATEGORY_TAT, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "root-key-sentinel-not-ta query label found"); } } /* * 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_verify3(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); if (result != ISC_R_SUCCESS) return (false); 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 query_addbestns(ns_client_t *client) { dns_db_t *db, *zdb; dns_dbnode_t *node; dns_name_t *fname, *zfname; dns_rdataset_t *rdataset, *sigrdataset, *zrdataset, *zsigrdataset; bool is_zone, use_zone; isc_buffer_t *dbuf; isc_result_t result; dns_dbversion_t *version; dns_zone_t *zone; isc_buffer_t b; dns_clientinfomethods_t cm; dns_clientinfo_t ci; CTRACE(ISC_LOG_DEBUG(3), "query_addbestns"); fname = NULL; zfname = NULL; rdataset = NULL; zrdataset = NULL; sigrdataset = NULL; zsigrdataset = NULL; node = NULL; db = NULL; zdb = NULL; version = NULL; zone = NULL; is_zone = false; use_zone = false; 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 = query_getnamebuf(client); if (dbuf == NULL) goto cleanup; fname = query_newname(client, dbuf, &b); rdataset = query_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 = query_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)) { query_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, 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) { query_releasename(client, &fname); /* * We've already done query_keepname() on * zfname, so we must set dbuf to NULL to * prevent query_addrrset() from trying to * call query_keepname() again. */ dbuf = NULL; query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_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)) query_putrdataset(client, &sigrdataset); query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); cleanup: if (rdataset != NULL) query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); if (fname != NULL) query_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) { query_putrdataset(client, &zrdataset); if (zsigrdataset != NULL) query_putrdataset(client, &zsigrdataset); if (zfname != NULL) query_releasename(client, &zfname); dns_db_detach(&zdb); } } static void fixrdataset(ns_client_t *client, dns_rdataset_t **rdataset) { if (*rdataset == NULL) *rdataset = query_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 = query_getnamebuf(client); if (*dbuf == NULL) return; *fname = query_newname(client, *dbuf, nbuf); } } static void query_addds(ns_client_t *client, dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, dns_name_t *name) { dns_fixedname_t fixed; dns_name_t *fname = NULL; dns_name_t *rname; dns_rdataset_t *rdataset, *sigrdataset; isc_buffer_t *dbuf, b; isc_result_t result; unsigned int count; CTRACE(ISC_LOG_DEBUG(3), "query_addds"); rname = NULL; rdataset = NULL; sigrdataset = NULL; /* * We'll need some resources... */ rdataset = query_newrdataset(client); sigrdataset = query_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(db, node, 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(db, node, 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. Use this name rather than calling * query_addrrset(). */ result = dns_message_firstname(client->message, DNS_SECTION_AUTHORITY); if (result != ISC_R_SUCCESS) goto cleanup; 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) goto cleanup; ISC_LIST_APPEND(rname->list, rdataset, link); ISC_LIST_APPEND(rname->list, sigrdataset, link); rdataset = NULL; sigrdataset = NULL; return; addnsec3: if (!dns_db_iszone(db)) goto cleanup; /* * Add the NSEC3 which proves the DS does not exist. */ dbuf = query_getnamebuf(client); if (dbuf == NULL) goto cleanup; fname = query_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); query_findclosestnsec3(name, db, version, client, rdataset, sigrdataset, fname, true, dns_fixedname_name(&fixed)); if (!dns_rdataset_isassociated(rdataset)) goto cleanup; query_addrrset(client, &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), db, version, client, rdataset, sigrdataset, fname, false, NULL); if (!dns_rdataset_isassociated(rdataset)) goto cleanup; query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); } cleanup: if (rdataset != NULL) query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); if (fname != NULL) query_releasename(client, &fname); } static void query_addwildcardproof(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, bool ispositive, bool nodata) { isc_buffer_t *dbuf, b; dns_name_t *fname; dns_rdataset_t *rdataset, *sigrdataset; dns_fixedname_t wfixed; dns_name_t *wname; dns_dbnode_t *node; 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"); fname = NULL; rdataset = NULL; sigrdataset = NULL; node = NULL; dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); /* * 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 = query_getnamebuf(client); if (dbuf == NULL) goto cleanup; fname = query_newname(client, dbuf, &b); rdataset = query_newrdataset(client); sigrdataset = query_newrdataset(client); if (fname == NULL || rdataset == NULL || sigrdataset == NULL) goto cleanup; result = dns_db_findext(db, name, version, dns_rdatatype_nsec, options, 0, &node, fname, &cm, &ci, rdataset, sigrdataset); if (node != NULL) dns_db_detachnode(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, NULL); 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(db, cname, version, dns_rdatatype_nsec, options, 0, NULL, fname, &cm, &ci, NULL, NULL); } /* * Add closest (provable) encloser NSEC3. */ query_findclosestnsec3(cname, db, version, client, rdataset, sigrdataset, fname, true, cname); if (!dns_rdataset_isassociated(rdataset)) goto cleanup; if (!ispositive) query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); /* * Replace resources which were consumed by query_addrrset. */ if (fname == NULL) { dbuf = query_getnamebuf(client); if (dbuf == NULL) goto cleanup; fname = query_newname(client, dbuf, &b); } if (rdataset == NULL) rdataset = query_newrdataset(client); else if (dns_rdataset_isassociated(rdataset)) dns_rdataset_disassociate(rdataset); if (sigrdataset == NULL) sigrdataset = query_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, NULL); else dns_name_split(name, labels, NULL, wname); query_findclosestnsec3(wname, db, version, client, rdataset, sigrdataset, fname, false, NULL); if (!dns_rdataset_isassociated(rdataset)) goto cleanup; query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); if (ispositive) goto cleanup; /* * Replace resources which were consumed by query_addrrset. */ if (fname == NULL) { dbuf = query_getnamebuf(client); if (dbuf == NULL) goto cleanup; fname = query_newname(client, dbuf, &b); } if (rdataset == NULL) rdataset = query_newrdataset(client); else if (dns_rdataset_isassociated(rdataset)) dns_rdataset_disassociate(rdataset); if (sigrdataset == NULL) sigrdataset = query_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, db, version, client, rdataset, sigrdataset, fname, nodata, NULL); if (!dns_rdataset_isassociated(rdataset)) goto cleanup; query_addrrset(client, &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); } if (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(client, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); } if (rdataset != NULL) query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); if (fname != NULL) query_releasename(client, &fname); if (have_wname) { ispositive = true; /* prevent loop */ if (!dns_name_equal(name, wname)) { name = wname; goto again; } } cleanup: if (rdataset != NULL) query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); if (fname != NULL) query_releasename(client, &fname); } static void query_addnxrrsetnsec(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version, dns_name_t **namep, dns_rdataset_t **rdatasetp, dns_rdataset_t **sigrdatasetp) { dns_name_t *name; dns_rdataset_t *sigrdataset; 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; name = *namep; if ((name->attributes & DNS_NAMEATTR_WILDCARD) == 0) { query_addrrset(client, namep, rdatasetp, sigrdatasetp, NULL, DNS_SECTION_AUTHORITY); return; } if (sigrdatasetp == NULL) return; sigrdataset = *sigrdatasetp; if (sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)) return; result = dns_rdataset_first(sigrdataset); if (result != ISC_R_SUCCESS) return; dns_rdata_init(&sigrdata); dns_rdataset_current(sigrdataset, &sigrdata); result = dns_rdata_tostruct(&sigrdata, &sig, NULL); if (result != ISC_R_SUCCESS) return; labels = dns_name_countlabels(name); if ((unsigned int)sig.labels + 1 >= labels) return; /* XXX */ query_addwildcardproof(client, db, version, client->query.qname, true, false); /* * We'll need some resources... */ dbuf = query_getnamebuf(client); if (dbuf == NULL) return; fname = query_newname(client, dbuf, &b); if (fname == NULL) return; dns_name_split(name, 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(client, &fname, rdatasetp, sigrdatasetp, dbuf, DNS_SECTION_AUTHORITY); } 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)); 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) query_putrdataset(client, &devent->rdataset); if (devent->sigrdataset != NULL) query_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); } /*% * Check the configured trust anchors for a root zone trust anchor * with a key id that matches client->query.root_key_sentinel_keyid. * * Return true when found, otherwise return false. */ static bool has_ta(ns_client_t *client) { dns_keytable_t *keytable = NULL; dns_keynode_t *keynode = NULL; isc_result_t result; result = dns_view_getsecroots(client->view, &keytable); if (result != ISC_R_SUCCESS) { return (false); } result = dns_keytable_find(keytable, dns_rootname, &keynode); while (result == ISC_R_SUCCESS) { dns_keynode_t *nextnode = NULL; dns_keytag_t keyid = dst_key_id(dns_keynode_key(keynode)); if (keyid == client->query.root_key_sentinel_keyid) { dns_keytable_detachkeynode(keytable, &keynode); dns_keytable_detach(&keytable); return (true); } result = dns_keytable_nextkeynode(keytable, keynode, &nextnode); dns_keytable_detachkeynode(keytable, &keynode); keynode = nextnode; } dns_keytable_detach(&keytable); return (false); } /*% * Check if a root key sentinel SERVFAIL should be returned. */ static bool root_key_sentinel_return_servfail(ns_client_t *client, bool is_zone, dns_rdataset_t *rdataset, isc_result_t result) { /* * Are we looking at a "root-key-sentinel" query? */ if (!client->query.root_key_sentinel_is_ta && !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 (!is_zone && rdataset->trust == dns_trust_secure && ((client->query.root_key_sentinel_is_ta && !has_ta(client)) || (client->query.root_key_sentinel_not_ta && has_ta(client)))) { return (true); } /* * As special processing may only be triggered by the original QNAME, * disable it after following a CNAME/DNAME. */ client->query.root_key_sentinel_is_ta = false; client->query.root_key_sentinel_not_ta = false; return (false); } static void query_resume(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; bool fetch_canceled, client_shuttingdown; isc_result_t result; isc_logcategory_t *logcategory = NS_LOGCATEGORY_QUERY_ERRORS; int errorloglevel; /* * Resume a query after recursion. */ UNUSED(task); REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); client = devent->ev_arg; REQUIRE(NS_CLIENT_VALID(client)); REQUIRE(task == client->task); REQUIRE(RECURSING(client)); LOCK(&client->query.fetchlock); if (client->query.fetch != NULL) { /* * This is the fetch we've been waiting for. */ INSIST(devent->fetch == client->query.fetch); client->query.fetch = NULL; fetch_canceled = false; /* * 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); INSIST(client->query.fetch == NULL); client->query.attributes &= ~NS_QUERYATTR_RECURSING; SAVE(fetch, devent->fetch); /* * If this client is shutting down, or this transaction * has timed out, do not resume the find. */ client_shuttingdown = ns_client_shuttingdown(client); if (fetch_canceled || client_shuttingdown) { free_devent(client, &event, &devent); if (fetch_canceled) { CTRACE(ISC_LOG_ERROR, "fetch cancelled"); query_error(client, DNS_R_SERVFAIL, __LINE__); } else query_next(client, ISC_R_CANCELED); /* * This may destroy the client. */ ns_client_detach(&client); } else { result = query_find(client, devent, 0); 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_g_lctx, errorloglevel)) { dns_resolver_logfetch(fetch, ns_g_lctx, logcategory, NS_LOGMODULE_QUERY, errorloglevel, false); } } } dns_resolver_destroyfetch(&fetch); } 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); LOCK(&client->query.fetchlock); if (client->query.prefetch != NULL) { INSIST(devent->fetch == client->query.prefetch); client->query.prefetch = NULL; } UNLOCK(&client->query.fetchlock); free_devent(client, &event, &devent); ns_client_detach(&client); } 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; ns_client_t *dummy = NULL; unsigned int options; 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(&ns_g_server->recursionquota, &client->recursionquota); if (result == ISC_R_SUCCESS && !client->mortal && !TCP(client)) result = ns_client_replace(client); if (result != ISC_R_SUCCESS) return; isc_stats_increment(ns_g_server->nsstats, dns_nsstatscounter_recursclients); } tmprdataset = query_newrdataset(client); if (tmprdataset == NULL) return; if (!TCP(client)) peeraddr = &client->peeraddr; else peeraddr = NULL; ns_client_attach(client, &dummy); options = client->query.fetchoptions | DNS_FETCHOPT_PREFETCH; result = dns_resolver_createfetch3(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) { query_putrdataset(client, &tmprdataset); ns_client_detach(&dummy); } dns_rdataset_clearprefetch(rdataset); } static isc_result_t 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; if (!resuming) inc_stats(client, dns_nsstatscounter_recursion); /* * 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(&ns_g_server->recursionquota, &client->recursionquota); isc_stats_increment(ns_g_server->nsstats, dns_nsstatscounter_recursclients); if (result == ISC_R_SOFTQUOTA) { static isc_stdtime_t last = 0; isc_stdtime_t now; isc_stdtime_get(&now); if (now != last) { last = now; ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "recursive-clients soft limit " "exceeded (%d/%d/%d), " "aborting oldest query", client->recursionquota->used, client->recursionquota->soft, client->recursionquota->max); } ns_client_killoldestquery(client); result = ISC_R_SUCCESS; } else if (result == ISC_R_QUOTA) { static isc_stdtime_t last = 0; isc_stdtime_t now; isc_stdtime_get(&now); if (now != last) { last = now; ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "no more recursive clients " "(%d/%d/%d): %s", ns_g_server->recursionquota.used, ns_g_server->recursionquota.soft, ns_g_server->recursionquota.max, isc_result_totext(result)); } ns_client_killoldestquery(client); } if (result == ISC_R_SUCCESS && !client->mortal && !TCP(client)) { result = ns_client_replace(client); if (result != ISC_R_SUCCESS) { ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "ns_client_replace() failed: %s", isc_result_totext(result)); isc_quota_detach(&client->recursionquota); isc_stats_decrement(ns_g_server->nsstats, dns_nsstatscounter_recursclients); } } if (result != ISC_R_SUCCESS) return (result); ns_client_recursing(client); } /* * Invoke the resolver. */ REQUIRE(nameservers == NULL || nameservers->type == dns_rdatatype_ns); REQUIRE(client->query.fetch == NULL); rdataset = query_newrdataset(client); if (rdataset == NULL) return (ISC_R_NOMEMORY); if (WANTDNSSEC(client)) { sigrdataset = query_newrdataset(client); if (sigrdataset == NULL) { query_putrdataset(client, &rdataset); return (ISC_R_NOMEMORY); } } else sigrdataset = NULL; if (client->query.timerset == false) ns_client_settimeout(client, 60); if (!TCP(client)) peeraddr = &client->peeraddr; else peeraddr = NULL; result = dns_resolver_createfetch3(client->view->resolver, qname, qtype, qdomain, nameservers, NULL, peeraddr, client->message->id, client->query.fetchoptions, 0, NULL, client->task, query_resume, client, rdataset, sigrdataset, &client->query.fetch); if (result == ISC_R_SUCCESS) { /* * Record that we're waiting for an event. A client which * is shutting down will not be destroyed until all the * events have been received. */ } else { query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); } return (result); } static inline 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 inline 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 inline 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 = query_newrdataset(client); if (*rdatasetp == NULL) { CTRACE(ISC_LOG_ERROR, "rpz_ready: query_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) query_putrdataset(client, &st->m.rdataset); rpz_match_clear(st); rpz_clean(NULL, &st->r.db, NULL, NULL); if (st->r.ns_rdataset != NULL) query_putrdataset(client, &st->r.ns_rdataset); if (st->r.r_rdataset != NULL) query_putrdataset(client, &st->r.r_rdataset); rpz_clean(&st->q.zone, &st->q.db, &st->q.node, NULL); if (st->q.rdataset != NULL) query_putrdataset(client, &st->q.rdataset); if (st->q.sigrdataset != NULL) query_putrdataset(client, &st->q.sigrdataset); st->state = 0; st->m.type = DNS_RPZ_TYPE_BAD; st->m.policy = DNS_RPZ_POLICY_MISS; } 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; REQUIRE(client != NULL); REQUIRE(client->query.rpz_st != NULL); st = client->query.rpz_st; 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: INSIST(0); break; } /* * 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; ns_client_t *dummy = NULL; unsigned int options; if (client->query.prefetch != NULL) return; if (client->recursionquota == NULL) { result = isc_quota_attach(&ns_g_server->recursionquota, &client->recursionquota); if (result == ISC_R_SUCCESS && !client->mortal && !TCP(client)) result = ns_client_replace(client); if (result != ISC_R_SUCCESS) return; isc_stats_increment(ns_g_server->nsstats, dns_nsstatscounter_recursclients); } tmprdataset = query_newrdataset(client); if (tmprdataset == NULL) return; if (!TCP(client)) peeraddr = &client->peeraddr; else peeraddr = NULL; ns_client_attach(client, &dummy); options = client->query.fetchoptions; result = dns_resolver_createfetch3(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) { query_putrdataset(client, &tmprdataset); ns_client_detach(&dummy); } } /* * 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, 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) query_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, DNS_DBFIND_GLUEOK, 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) { query_rpzfetch(client, name, type); result = DNS_R_NXRRSET; } else { dns_name_copy(name, st->r_name, NULL); result = 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: INSIST(0); } /* * 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, " concatentate()", 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, " concatentate()", 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; 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, &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); } 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: *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_find(). 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, NULL); 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); } /* * 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"); ip_name = dns_fixedname_initname(&ip_namef); p_zone = NULL; p_db = NULL; p_node = NULL; rpzs = client->view->rpzs; st = client->query.rpz_st; 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; CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip_rrset"); 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, 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); } /* * 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); } 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); query_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, 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; CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_name"); zbits = rpz_get_zbits(client, qtype, rpz_type); zbits &= allowed_zbits; if (zbits == 0) return (ISC_R_SUCCESS); rpzs = client->view->rpzs; /* * 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; st = client->query.rpz_st; /* * 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 0 /* * This code would block a customer reported information * leak of rpz rules by rewriting requests in the * rpz-ip, rpz-nsip, rpz-nsdname,and rpz-passthru TLDs. * Without this code, a bad guy could request * 24.0.3.2.10.rpz-ip. to find the policy rule for * 10.2.3.0/14. It is an insignificant leak and this * code is not worth its cost, because the bad guy * could publish "evil.com A 10.2.3.4" and request * evil.com to get the same information. * Keep code with "#if 0" in case customer demand * is irresistible. * * We have the less frequent case of a triggered * policy. Check that we have not trigger on one * of the pretend RPZ TLDs. * This test would make it impossible to rewrite * names in TLDs that start with "rpz-" should * ICANN ever allow such TLDs. */ unsigned int labels; labels = dns_name_countlabels(trig_name); if (labels >= 2) { dns_label_t label; dns_name_getlabel(trig_name, labels-2, &label); if (label.length >= sizeof(DNS_RPZ_PREFIX)-1 && strncasecmp((const char *)label.base+1, DNS_RPZ_PREFIX, sizeof(DNS_RPZ_PREFIX)-1) == 0) continue; } #endif 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 { RPZ_QRESULT_TYPE_DONE, RPZ_QRESULT_TYPE_RESTART, RPZ_QRESULT_TYPE_RECURSE, } rpz_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; rpz_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; CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite"); rpzs = client->view->rpzs; st = client->query.rpz_st; if (rpzs == NULL || (st != NULL && (st->state & DNS_RPZ_REWRITTEN) != 0)) return (DNS_R_DISALLOWED); RWLOCK(&rpzs->search_lock, isc_rwlocktype_read); if (rpzs->p.num_zones == 0 || (!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); if (st == NULL) { st = isc_mem_get(client->mctx, sizeof(*st)); if (st == NULL) return (ISC_R_NOMEMORY); st->state = 0; } 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; } /* * 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 = RPZ_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_CNAME: case DNS_R_DNAME: qresult_type = RPZ_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 = RPZ_QRESULT_TYPE_RECURSE; else qresult_type = RPZ_QRESULT_TYPE_RESTART; break; case ISC_R_FAILURE: case ISC_R_TIMEDOUT: case DNS_R_BROKENCHAIN: rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, client->query.qname, DNS_RPZ_TYPE_QNAME, " stop on qresult in rpz_rewrite()", qresult); return (ISC_R_SUCCESS); default: rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, client->query.qname, 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 (qresult_type == RPZ_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) { result = rpz_rewrite_name(client, client->query.qname, qtype, DNS_RPZ_TYPE_QNAME, allowed, &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 recusion is so slow. */ if (qresult_type == RPZ_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 == * RPZ_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 == RPZ_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 addesses. */ 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)); while (st->r.label > st->popt.min_ns_labels) { /* * 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, 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 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); dns_rdata_reset(&nsrdata); if (result != ISC_R_SUCCESS) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, nsname, DNS_RPZ_TYPE_NSIP, " rdata_tostruct()", result); st->m.policy = DNS_RPZ_POLICY_ERROR; goto cleanup; } /* * 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, &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); 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: 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; } query_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); } /* * Add a CNAME to the 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 rpz_add_cname(ns_client_t *client, dns_rpz_st_t *st, dns_name_t *cname, dns_name_t *fname, isc_buffer_t *dbuf) { dns_fixedname_t prefix, suffix; unsigned int labels; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "rpz_add_cname"); 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), fname, NULL); if (result == DNS_R_NAMETOOLONG) client->message->rcode = dns_rcode_yxdomain; } else { result = dns_name_copy(cname, fname, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) return (result); query_keepname(client, fname, dbuf); result = query_add_cname(client, client->query.qname, fname, dns_trust_authanswer, st->m.ttl); if (result != ISC_R_SUCCESS) return (result); rpz_log_rewrite(client, false, st->m.policy, st->m.type, st->m.zone, st->p_name, fname, st->m.rpz->num); ns_client_qnamereplace(client, 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); } #define MAX_RESTARTS 16 #define QUERY_ERROR(r) \ do { \ eresult = r; \ want_restart = false; \ line = __LINE__; \ } while (0) #define RECURSE_ERROR(r) \ do { \ if ((r) == DNS_R_DUPLICATE || (r) == DNS_R_DROP) \ QUERY_ERROR(r); \ else \ QUERY_ERROR(DNS_R_SERVFAIL); \ } while (0) /* * 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); } } /* * 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 setup_query_sortlist(ns_client_t *client) { isc_netaddr_t netaddr; dns_rdatasetorderfunc_t order = NULL; const void *order_arg = NULL; isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); switch (ns_sortlist_setup(client->view->sortlist, &netaddr, &order_arg)) { case NS_SORTLISTTYPE_1ELEMENT: order = query_sortlist_order_1element; break; case NS_SORTLISTTYPE_2ELEMENT: order = query_sortlist_order_2element; break; case NS_SORTLISTTYPE_NONE: order = NULL; break; default: INSIST(0); break; } dns_message_setsortorder(client->message, order, order_arg); } static void query_addnoqnameproof(ns_client_t *client, dns_rdataset_t *rdataset) { isc_buffer_t *dbuf, b; dns_name_t *fname; dns_rdataset_t *neg, *negsig; isc_result_t result = ISC_R_NOMEMORY; CTRACE(ISC_LOG_DEBUG(3), "query_addnoqnameproof"); fname = NULL; neg = NULL; negsig = NULL; dbuf = query_getnamebuf(client); if (dbuf == NULL) goto cleanup; fname = query_newname(client, dbuf, &b); neg = query_newrdataset(client); negsig = query_newrdataset(client); if (fname == NULL || neg == NULL || negsig == NULL) goto cleanup; result = dns_rdataset_getnoqname(rdataset, fname, neg, negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); query_addrrset(client, &fname, &neg, &negsig, dbuf, DNS_SECTION_AUTHORITY); if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) == 0) goto cleanup; if (fname == NULL) { dbuf = query_getnamebuf(client); if (dbuf == NULL) goto cleanup; fname = query_newname(client, dbuf, &b); } if (neg == NULL) neg = query_newrdataset(client); else if (dns_rdataset_isassociated(neg)) dns_rdataset_disassociate(neg); if (negsig == NULL) negsig = query_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(rdataset, fname, neg, negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); query_addrrset(client, &fname, &neg, &negsig, dbuf, DNS_SECTION_AUTHORITY); cleanup: if (neg != NULL) query_putrdataset(client, &neg); if (negsig != NULL) query_putrdataset(client, &negsig); if (fname != NULL) query_releasename(client, &fname); } static inline void answer_in_glue(ns_client_t *client, dns_rdatatype_t qtype) { dns_name_t *name; dns_message_t *msg; dns_section_t section = DNS_SECTION_ADDITIONAL; dns_rdataset_t *rdataset = NULL; msg = client->message; for (name = ISC_LIST_HEAD(msg->sections[section]); name != NULL; name = ISC_LIST_NEXT(name, link)) if (dns_name_equal(name, client->query.qname)) { for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) if (rdataset->type == 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; } } 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 prisoner = DNS_NAME_INITABSOLUTE(prisoner_data, prisoner_offsets); static dns_name_t 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); dns_rdata_tostruct(&rdata, &nsec3, NULL); dns_rdata_reset(&rdata); optout = (nsec3.flags & DNS_NSEC3FLAG_OPTOUT); 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, NULL); return; } #ifdef ALLOW_FILTER_AAAA static bool is_v4_client(ns_client_t *client) { if (isc_sockaddr_pf(&client->peeraddr) == AF_INET) return (true); if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr)) return (true); return (false); } static bool is_v6_client(ns_client_t *client) { if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 && !IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr)) return (true); return (false); } #endif 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_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 (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, &ns_g_server->aclenv, 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); 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 = query_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, NULL); 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 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); 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); if (dns_name_countlabels(name) > 1U) { dns_name_t prefix; unsigned int labels = dns_name_countlabels(name) - 1; dns_name_init(&prefix, NULL); dns_name_getlabelsequence(name, 0, labels, &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, NULL); 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 = 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, NULL); 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); } /* * Do the bulk of query processing for the current query of 'client'. * If 'event' is non-NULL, we are returning from recursion and 'qtype' * is ignored. Otherwise, 'qtype' is the query type. */ static isc_result_t query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) { dns_db_t *db, *zdb; dns_dbnode_t *node; dns_rdatatype_t type = qtype; dns_name_t *fname, *zfname, *tname, *prefix; dns_rdataset_t *rdataset, *trdataset; dns_rdataset_t *sigrdataset, *zrdataset, *zsigrdataset; dns_rdataset_t **sigrdatasetp; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdatasetiter_t *rdsiter; bool want_restart, is_zone, need_wildcardproof; bool is_staticstub_zone; bool authoritative = false; bool answer_has_ns = false; unsigned int n, nlabels; dns_namereln_t namereln; int order; isc_buffer_t *dbuf; isc_buffer_t b; isc_result_t result, eresult, tresult; dns_fixedname_t fixed; dns_fixedname_t wildcardname; dns_dbversion_t *version, *zversion; dns_zone_t *zone; dns_rdata_cname_t cname; dns_rdata_dname_t dname; unsigned int options; bool empty_wild; dns_rdataset_t *noqname; dns_rpz_st_t *rpz_st; bool resuming; int line = -1; bool dns64_exclude, dns64, rpz; bool nxrewrite = false; bool redirected = false; dns_clientinfomethods_t cm; dns_clientinfo_t ci; char errmsg[256]; bool associated; dns_section_t section; dns_ttl_t ttl; bool failcache; uint32_t flags; #ifdef WANT_QUERYTRACE char mbuf[BUFSIZ]; char qbuf[DNS_NAME_FORMATSIZE]; char tbuf[DNS_RDATATYPE_FORMATSIZE]; #endif dns_name_t *rpzqname; CTRACE(ISC_LOG_DEBUG(3), "query_find"); /* * One-time initialization. * * It's especially important to initialize anything that the cleanup * code might cleanup. */ eresult = ISC_R_SUCCESS; fname = NULL; zfname = NULL; rdataset = NULL; zrdataset = NULL; sigrdataset = NULL; zsigrdataset = NULL; zversion = NULL; node = NULL; db = NULL; zdb = NULL; version = NULL; zone = NULL; need_wildcardproof = false; empty_wild = false; dns64_exclude = dns64 = rpz = false; options = 0; resuming = false; is_zone = false; is_staticstub_zone = false; dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); #ifdef WANT_QUERYTRACE if (client->query.origqname != NULL) dns_name_format(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:%d, " "origqname:%s, timer:%d, authdb:%d, referral:%d", client->attributes, client->query.attributes, client->query.restarts, qbuf, (int) client->query.timerset, (int) client->query.authdbset, (int) client->query.isreferral); CTRACE(ISC_LOG_DEBUG(3), mbuf); #endif if (event != NULL) { /* * We're returning from recursion. Restore the query context * and resume. */ want_restart = false; rpz_st = client->query.rpz_st; if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) != 0) { CTRACE(ISC_LOG_DEBUG(3), "resume from RPZ recursion"); #ifdef WANT_QUERYTRACE { char pbuf[DNS_NAME_FORMATSIZE] = ""; char fbuf[DNS_NAME_FORMATSIZE] = ""; if (rpz_st->r_name != NULL) dns_name_format(rpz_st->r_name, qbuf, sizeof(qbuf)); else snprintf(qbuf, sizeof(qbuf), ""); if (rpz_st->p_name != NULL) dns_name_format(rpz_st->p_name, pbuf, sizeof(pbuf)); if (rpz_st->fname != NULL) dns_name_format(rpz_st->fname, fbuf, sizeof(fbuf)); snprintf(mbuf, sizeof(mbuf) - 1, "rpz rname:%s, pname:%s, fname:%s", qbuf, pbuf, fbuf); CTRACE(ISC_LOG_DEBUG(3), mbuf); } #endif is_zone = rpz_st->q.is_zone; authoritative = rpz_st->q.authoritative; RESTORE(zone, rpz_st->q.zone); RESTORE(node, rpz_st->q.node); RESTORE(db, rpz_st->q.db); RESTORE(rdataset, rpz_st->q.rdataset); RESTORE(sigrdataset, rpz_st->q.sigrdataset); qtype = rpz_st->q.qtype; if (event->node != NULL) dns_db_detachnode(event->db, &event->node); SAVE(rpz_st->r.db, event->db); rpz_st->r.r_type = event->qtype; SAVE(rpz_st->r.r_rdataset, event->rdataset); query_putrdataset(client, &event->sigrdataset); } else if (REDIRECT(client)) { /* * Restore saved state. */ CTRACE(ISC_LOG_DEBUG(3), "resume from redirect recursion"); #ifdef WANT_QUERYTRACE dns_name_format(client->query.redirect.fname, qbuf, sizeof(qbuf)); dns_rdatatype_format(client->query.redirect.qtype, tbuf, sizeof(tbuf)); snprintf(mbuf, sizeof(mbuf) - 1, "redirect fname:%s, qtype:%s, auth:%d", qbuf, tbuf, client->query.redirect.authoritative); CTRACE(ISC_LOG_DEBUG(3), mbuf); #endif qtype = client->query.redirect.qtype; INSIST(client->query.redirect.rdataset != NULL); RESTORE(rdataset, client->query.redirect.rdataset); RESTORE(sigrdataset, client->query.redirect.sigrdataset); RESTORE(db, client->query.redirect.db); RESTORE(node, client->query.redirect.node); RESTORE(zone, client->query.redirect.zone); authoritative = client->query.redirect.authoritative; is_zone = client->query.redirect.is_zone; /* * Free resources used while recursing. */ query_putrdataset(client, &event->rdataset); query_putrdataset(client, &event->sigrdataset); if (event->node != NULL) dns_db_detachnode(event->db, &event->node); if (event->db != NULL) dns_db_detach(&event->db); } else { CTRACE(ISC_LOG_DEBUG(3), "resume from normal recursion"); authoritative = false; qtype = event->qtype; SAVE(db, event->db); SAVE(node, event->node); SAVE(rdataset, event->rdataset); SAVE(sigrdataset, event->sigrdataset); } INSIST(rdataset != NULL); if (qtype == dns_rdatatype_rrsig || qtype == dns_rdatatype_sig) type = dns_rdatatype_any; else type = qtype; if (DNS64(client)) { client->query.attributes &= ~NS_QUERYATTR_DNS64; dns64 = true; } if (DNS64EXCLUDE(client)) { client->query.attributes &= ~NS_QUERYATTR_DNS64EXCLUDE; dns64_exclude = true; } if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) != 0) { /* * Has response policy changed out from under us? */ if (rpz_st->rpz_ver != client->view->rpzs->rpz_ver) { ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, DNS_RPZ_INFO_LEVEL, "query_find: RPZ settings " "out of date " "(rpz_ver %d, expected %d)", client->view->rpzs->rpz_ver, rpz_st->rpz_ver); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } /* * We'll need some resources... */ dbuf = query_getnamebuf(client); if (dbuf == NULL) { CTRACE(ISC_LOG_ERROR, "query_find: query_getnamebuf failed (1)"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } fname = query_newname(client, dbuf, &b); if (fname == NULL) { CTRACE(ISC_LOG_ERROR, "query_find: query_newname failed (1)"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) != 0) { tname = rpz_st->fname; } else if (REDIRECT(client)) { tname = client->query.redirect.fname; } else { tname = dns_fixedname_name(&event->foundname); } result = dns_name_copy(tname, fname, NULL); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_ERROR, "query_find: dns_name_copy failed"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) != 0) { rpz_st->r.r_result = event->result; result = rpz_st->q.result; free_devent(client, ISC_EVENT_PTR(&event), &event); } else if (REDIRECT(client)) { result = client->query.redirect.result; is_zone = client->query.redirect.is_zone; } else { result = event->result; } resuming = true; goto resume; } /* * Not returning from recursion. * * First, check for a recent match in the view's SERVFAIL cache. * If we find one, and it was from a query with CD=1, *or* * if the current query has CD=0, then we can just return * SERVFAIL now. */ if (RECURSIONOK(client)) { flags = 0; #ifdef ENABLE_AFL if (ns_g_fuzz_type == ns_fuzz_resolver) { failcache = false; } else { failcache = dns_badcache_find(client->view->failcache, client->query.qname, qtype, &flags, &client->tnow); } #else failcache = dns_badcache_find(client->view->failcache, client->query.qname, qtype, &flags, &client->tnow); #endif if (failcache && (((flags & NS_FAILCACHE_CD) != 0) || ((client->message->flags & DNS_MESSAGEFLAG_CD) == 0))) { if (isc_log_wouldlog(ns_g_lctx, ISC_LOG_DEBUG(1))) { char namebuf[DNS_NAME_FORMATSIZE]; char typename[DNS_RDATATYPE_FORMATSIZE]; dns_name_format(client->query.qname, namebuf, sizeof(namebuf)); dns_rdatatype_format(qtype, typename, sizeof(typename)); ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_DEBUG(1), "servfail cache hit %s/%s (%s)", namebuf, typename, ((flags & NS_FAILCACHE_CD) != 0) ? "CD=1" : "CD=0"); } client->attributes |= NS_CLIENTATTR_NOSETFC; QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } /* * If it's a SIG query, we'll iterate the node. */ if (qtype == dns_rdatatype_rrsig || qtype == dns_rdatatype_sig) type = dns_rdatatype_any; else type = qtype; restart: CTRACE(ISC_LOG_DEBUG(3), "query_find: restart"); want_restart = false; authoritative = false; version = NULL; zversion = NULL; need_wildcardproof = false; rpz = false; if (client->view->checknames && !dns_rdata_checkowner(client->query.qname, client->message->rdclass, qtype, false)) { char namebuf[DNS_NAME_FORMATSIZE]; char typename[DNS_RDATATYPE_FORMATSIZE]; char classname[DNS_RDATACLASS_FORMATSIZE]; dns_name_format(client->query.qname, namebuf, sizeof(namebuf)); dns_rdatatype_format(qtype, typename, sizeof(typename)); dns_rdataclass_format(client->message->rdclass, classname, sizeof(classname)); ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_QUERY, ISC_LOG_ERROR, "check-names failure %s/%s/%s", namebuf, typename, classname); QUERY_ERROR(DNS_R_REFUSED); goto cleanup; } /* * Setup for root key sentinel processing. */ if (client->view->root_key_sentinel && client->query.restarts == 0 && (qtype == dns_rdatatype_a || qtype == dns_rdatatype_aaaa) && (client->message->flags & DNS_MESSAGEFLAG_CD) == 0) { root_key_sentinel_detect(client); } /* * First we must find the right database. */ options &= DNS_GETDB_NOLOG; /* Preserve DNS_GETDB_NOLOG. */ if (dns_rdatatype_atparent(qtype) && !dns_name_equal(client->query.qname, dns_rootname)) options |= DNS_GETDB_NOEXACT; result = query_getdb(client, client->query.qname, qtype, options, &zone, &db, &version, &is_zone); if (ISC_UNLIKELY((result != ISC_R_SUCCESS || !is_zone) && qtype == dns_rdatatype_ds && !RECURSIONOK(client) && (options & DNS_GETDB_NOEXACT) != 0)) { /* * If the query type is DS, look to see if we are * authoritative for the child zone. */ dns_db_t *tdb = NULL; dns_zone_t *tzone = NULL; dns_dbversion_t *tversion = NULL; tresult = query_getzonedb(client, client->query.qname, qtype, DNS_GETDB_PARTIAL, &tzone, &tdb, &tversion); if (tresult == ISC_R_SUCCESS) { options &= ~DNS_GETDB_NOEXACT; query_putrdataset(client, &rdataset); if (db != NULL) dns_db_detach(&db); if (zone != NULL) dns_zone_detach(&zone); version = NULL; RESTORE(version, tversion); RESTORE(db, tdb); RESTORE(zone, tzone); is_zone = true; result = ISC_R_SUCCESS; } else { if (tdb != NULL) dns_db_detach(&tdb); if (tzone != NULL) dns_zone_detach(&tzone); } } if (result != ISC_R_SUCCESS) { if (result == DNS_R_REFUSED) { if (WANTRECURSION(client)) { inc_stats(client, dns_nsstatscounter_recurserej); } else inc_stats(client, dns_nsstatscounter_authrej); if (!PARTIALANSWER(client)) QUERY_ERROR(DNS_R_REFUSED); } else { CTRACE(ISC_LOG_ERROR, "query_find: query_getdb failed"); QUERY_ERROR(DNS_R_SERVFAIL); } goto cleanup; } is_staticstub_zone = false; if (is_zone) { authoritative = true; if (zone != NULL && dns_zone_gettype(zone) == dns_zone_staticstub) is_staticstub_zone = true; } if (event == NULL && client->query.restarts == 0) { if (is_zone) { if (zone != NULL) { /* * if is_zone = true, zone = NULL then this is * a DLZ zone. Don't attempt to attach zone. */ dns_zone_attach(zone, &client->query.authzone); } dns_db_attach(db, &client->query.authdb); } client->query.authdbset = true; /* Track TCP vs UDP stats per zone */ if (TCP(client)) inc_stats(client, dns_nsstatscounter_tcp); else inc_stats(client, dns_nsstatscounter_udp); } db_find: CTRACE(ISC_LOG_DEBUG(3), "query_find: db_find"); /* * We'll need some resources... */ dbuf = query_getnamebuf(client); if (ISC_UNLIKELY(dbuf == NULL)) { CTRACE(ISC_LOG_ERROR, "query_find: query_getnamebuf failed (2)"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } fname = query_newname(client, dbuf, &b); rdataset = query_newrdataset(client); if (ISC_UNLIKELY(fname == NULL || rdataset == NULL)) { CTRACE(ISC_LOG_ERROR, "query_find: query_newname failed (2)"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } if (WANTDNSSEC(client) && (!is_zone || dns_db_issecure(db))) { sigrdataset = query_newrdataset(client); if (sigrdataset == NULL) { CTRACE(ISC_LOG_ERROR, "query_find: query_newrdataset failed (2)"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } /* * Now look for an answer in the database. If this is a dns64 * AAAA lookup on a rpz database adjust the qname. */ if (dns64 && rpz) rpzqname = client->query.rpz_st->p_name; else rpzqname = client->query.qname; result = dns_db_findext(db, rpzqname, version, type, client->query.dboptions, client->now, &node, fname, &cm, &ci, rdataset, sigrdataset); /* * Fixup fname and sigrdataset. */ if (dns64 && rpz) { isc_result_t rresult; rresult = dns_name_copy(client->query.qname, fname, NULL); RUNTIME_CHECK(rresult == ISC_R_SUCCESS); if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); } if (!is_zone) dns_cache_updatestats(client->view->cache, result); resume: CTRACE(ISC_LOG_DEBUG(3), "query_find: resume"); /* * 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. */ if (client->view->rrl != NULL && !HAVECOOKIE(client) && ((fname != NULL && dns_name_isabsolute(fname)) || (result == ISC_R_NOTFOUND && !RECURSIONOK(client))) && !(result == DNS_R_DELEGATION && !is_zone && RECURSIONOK(client)) && (client->query.rpz_st == NULL || (client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0)&& (client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0) { dns_rdataset_t nc_rdataset; bool wouldlog; char log_buf[DNS_RRL_LOG_BUF_LEN]; isc_result_t nc_result, resp_result; dns_rrl_result_t rrl_result; client->query.attributes |= NS_QUERYATTR_RRL_CHECKED; wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP); tname = fname; if (result == DNS_R_NXDOMAIN) { /* * Use the database origin name to rate limit NXDOMAIN */ if (db != NULL) tname = dns_db_origin(db); resp_result = result; } else if (result == DNS_R_NCACHENXDOMAIN && rdataset != NULL && dns_rdataset_isassociated(rdataset) && (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(rdataset); nc_result == ISC_R_SUCCESS; nc_result = dns_rdataset_next(rdataset)) { dns_ncache_current(rdataset, dns_fixedname_name(&fixed), &nc_rdataset); if (nc_rdataset.type == dns_rdatatype_soa) { dns_rdataset_disassociate(&nc_rdataset); tname = 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 or we have "additional-from-cache no". */ tname = dns_rootname; resp_result = DNS_R_DELEGATION; } else { resp_result = ISC_R_SUCCESS; } rrl_result = dns_rrl(client->view, &client->peeraddr, TCP(client), client->message->rdclass, qtype, tname, resp_result, 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(client, DNS_LOGCATEGORY_RRL, NS_LOGMODULE_QUERY, DNS_RRL_LOG_DROP, "%s", log_buf); } if (!client->view->rrl->log_only) { if (rrl_result == DNS_RRL_RESULT_DROP) { /* * These will also be counted in * dns_nsstatscounter_dropped */ inc_stats(client, dns_nsstatscounter_ratedropped); QUERY_ERROR(DNS_R_DROP); } else { /* * These will also be counted in * dns_nsstatscounter_truncatedresp */ inc_stats(client, dns_nsstatscounter_rateslipped); if (WANTCOOKIE(client)) { client->message->flags &= ~DNS_MESSAGEFLAG_AA; client->message->flags &= ~DNS_MESSAGEFLAG_AD; client->message->rcode = dns_rcode_badcookie; } else { client->message->flags |= DNS_MESSAGEFLAG_TC; if (resp_result == DNS_R_NXDOMAIN) client->message->rcode = dns_rcode_nxdomain; } } goto cleanup; } } } else if (!TCP(client) && client->view->requireservercookie && WANTCOOKIE(client) && !HAVECOOKIE(client)) { client->message->flags &= ~DNS_MESSAGEFLAG_AA; client->message->flags &= ~DNS_MESSAGEFLAG_AD; client->message->rcode = dns_rcode_badcookie; goto cleanup; } if (!RECURSING(client) && !dns_name_equal(client->query.qname, dns_rootname)) { isc_result_t rresult; rresult = rpz_rewrite(client, qtype, result, resuming, rdataset, sigrdataset); rpz_st = client->query.rpz_st; switch (rresult) { case ISC_R_SUCCESS: break; case DNS_R_DISALLOWED: goto norpz; case DNS_R_DELEGATION: /* * recursing for NS names or addresses, * so save the main query state */ rpz_st->q.qtype = qtype; rpz_st->q.is_zone = is_zone; rpz_st->q.authoritative = authoritative; SAVE(rpz_st->q.zone, zone); SAVE(rpz_st->q.db, db); SAVE(rpz_st->q.node, node); SAVE(rpz_st->q.rdataset, rdataset); SAVE(rpz_st->q.sigrdataset, sigrdataset); dns_name_copy(fname, rpz_st->fname, NULL); rpz_st->q.result = result; client->query.attributes |= NS_QUERYATTR_RECURSING; goto cleanup; default: RECURSE_ERROR(rresult); goto cleanup; } if (rpz_st->m.policy != DNS_RPZ_POLICY_MISS) rpz_st->state |= DNS_RPZ_REWRITTEN; if (rpz_st->m.policy != DNS_RPZ_POLICY_MISS && rpz_st->m.policy != DNS_RPZ_POLICY_PASSTHRU && (rpz_st->m.policy != DNS_RPZ_POLICY_TCP_ONLY || !TCP(client)) && 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. */ rresult = dns_name_copy(client->query.qname, fname, NULL); RUNTIME_CHECK(rresult == ISC_R_SUCCESS); rpz_clean(&zone, &db, &node, NULL); if (rpz_st->m.rdataset != NULL) { query_putrdataset(client, &rdataset); RESTORE(rdataset, rpz_st->m.rdataset); } else if (rdataset != NULL && dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } version = NULL; RESTORE(node, rpz_st->m.node); RESTORE(db, rpz_st->m.db); RESTORE(version, rpz_st->m.version); RESTORE(zone, rpz_st->m.zone); switch (rpz_st->m.policy) { case DNS_RPZ_POLICY_TCP_ONLY: client->message->flags |= DNS_MESSAGEFLAG_TC; if (result == DNS_R_NXDOMAIN || result == DNS_R_NCACHENXDOMAIN) client->message->rcode = dns_rcode_nxdomain; rpz_log_rewrite(client, false, rpz_st->m.policy, rpz_st->m.type, zone, rpz_st->p_name, NULL, rpz_st->m.rpz->num); goto cleanup; case DNS_RPZ_POLICY_DROP: QUERY_ERROR(DNS_R_DROP); rpz_log_rewrite(client, false, rpz_st->m.policy, rpz_st->m.type, zone, rpz_st->p_name, NULL, rpz_st->m.rpz->num); goto cleanup; case DNS_RPZ_POLICY_NXDOMAIN: result = DNS_R_NXDOMAIN; nxrewrite = true; rpz = true; break; case DNS_RPZ_POLICY_NODATA: result = DNS_R_NXRRSET; nxrewrite = true; rpz = true; break; case DNS_RPZ_POLICY_RECORD: result = rpz_st->m.result; if (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(rdataset)) dns_rdataset_disassociate(rdataset); } else { /* * We will add this rdataset. */ rdataset->ttl = ISC_MIN(rdataset->ttl, rpz_st->m.ttl); } rpz = true; break; case DNS_RPZ_POLICY_WILDCNAME: result = dns_rdataset_first(rdataset); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &cname, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); result = rpz_add_cname(client, rpz_st, &cname.cname, fname, dbuf); if (result != ISC_R_SUCCESS) goto cleanup; fname = NULL; want_restart = true; goto cleanup; case DNS_RPZ_POLICY_CNAME: /* * Add overridding CNAME from a named.conf * response-policy statement */ result = rpz_add_cname(client, rpz_st, &rpz_st->m.rpz->cname, fname, dbuf); if (result != ISC_R_SUCCESS) goto cleanup; fname = NULL; want_restart = true; goto cleanup; default: INSIST(0); } /* * Turn off DNSSEC because the results of a * response policy zone cannot verify. */ client->attributes &= ~(NS_CLIENTATTR_WANTDNSSEC | NS_CLIENTATTR_WANTAD); client->message->flags &= ~DNS_MESSAGEFLAG_AD; query_putrdataset(client, &sigrdataset); is_zone = true; rpz_log_rewrite(client, false, rpz_st->m.policy, rpz_st->m.type, zone, rpz_st->p_name, NULL, rpz_st->m.rpz->num); } } norpz: /* * 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(client, is_zone, rdataset, result)) { /* * Don't record this response in the SERVFAIL cache. */ client->attributes |= NS_CLIENTATTR_NOSETFC; QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } switch (result) { case ISC_R_SUCCESS: /* * This case is handled in the main line below. */ break; case DNS_R_GLUE: case DNS_R_ZONECUT: /* * These cases are handled in the main line below. */ INSIST(is_zone); authoritative = false; break; case ISC_R_NOTFOUND: /* * The cache doesn't even have the root NS. Get them from * the hints DB. */ INSIST(!is_zone); if (db != NULL) dns_db_detach(&db); if (client->view->hints == NULL) { /* We have no hints. */ result = ISC_R_FAILURE; } else { dns_db_attach(client->view->hints, &db); result = dns_db_findext(db, dns_rootname, NULL, dns_rdatatype_ns, 0, client->now, &node, fname, &cm, &ci, rdataset, sigrdataset); } if (result != ISC_R_SUCCESS) { /* * Nonsensical root hints may require cleanup. */ 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); /* * We don't have any root server hints, but * we may have working forwarders, so try to * recurse anyway. */ if (RECURSIONOK(client)) { INSIST(!REDIRECT(client)); result = query_recurse(client, qtype, client->query.qname, NULL, NULL, resuming); if (result == ISC_R_SUCCESS) { client->query.attributes |= NS_QUERYATTR_RECURSING; if (dns64) client->query.attributes |= NS_QUERYATTR_DNS64; if (dns64_exclude) client->query.attributes |= NS_QUERYATTR_DNS64EXCLUDE; } else RECURSE_ERROR(result); goto cleanup; } else { /* Unable to give root server referral. */ CTRACE(ISC_LOG_ERROR, "unable to give root server referral"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } /* * XXXRTH We should trigger root server priming here. */ /* FALLTHROUGH */ case DNS_R_DELEGATION: authoritative = false; if (is_zone) { /* * Look to see if we are authoritative for the * child zone if the query type is DS. */ if (!RECURSIONOK(client) && (options & DNS_GETDB_NOEXACT) != 0 && qtype == dns_rdatatype_ds) { dns_db_t *tdb = NULL; dns_zone_t *tzone = NULL; dns_dbversion_t *tversion = NULL; result = query_getzonedb(client, client->query.qname, qtype, DNS_GETDB_PARTIAL, &tzone, &tdb, &tversion); if (result == ISC_R_SUCCESS) { options &= ~DNS_GETDB_NOEXACT; query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); if (fname != NULL) query_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); version = NULL; RESTORE(version, tversion); RESTORE(db, tdb); RESTORE(zone, tzone); authoritative = true; goto db_find; } if (tdb != NULL) dns_db_detach(&tdb); if (tzone != NULL) dns_zone_detach(&tzone); } /* * We're authoritative for an ancestor of QNAME. */ if (!USECACHE(client) || !RECURSIONOK(client)) { bool detach = false; dns_fixedname_init(&fixed); dns_name_copy(fname, dns_fixedname_name(&fixed), NULL); /* * If we don't have a cache, this is the best * answer. * * If the client is making a nonrecursive * query we always give out the authoritative * delegation. This way even if we get * junk in our cache, we won't fail in our * role as the delegating authority if another * nameserver asks us about a delegated * subzone. * * We enable the retrieval of glue for this * database by setting client->query.gluedb. */ if (db != NULL && client->query.gluedb == NULL) { dns_db_attach(db, &client->query.gluedb); detach = true; } client->query.isreferral = true; /* * We must ensure NOADDITIONAL is off, * because the generation of * additional data is required in * delegations. */ client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; if (sigrdataset != NULL) sigrdatasetp = &sigrdataset; else sigrdatasetp = NULL; query_addrrset(client, &fname, &rdataset, sigrdatasetp, dbuf, DNS_SECTION_AUTHORITY); if (detach) { dns_db_detach(&client->query.gluedb); } if (WANTDNSSEC(client)) query_addds(client, db, node, version, dns_fixedname_name(&fixed)); } else { /* * 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. */ query_keepname(client, fname, dbuf); dns_db_detachnode(db, &node); SAVE(zdb, db); SAVE(zfname, fname); SAVE(zversion, version); SAVE(zrdataset, rdataset); SAVE(zsigrdataset, sigrdataset); dns_db_attach(client->view->cachedb, &db); is_zone = false; goto db_find; } } else { if (zfname != NULL && (!dns_name_issubdomain(fname, zfname) || (is_staticstub_zone && dns_name_equal(fname, 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. * 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. */ query_releasename(client, &fname); /* * We've already done query_keepname() on * zfname, so we must set dbuf to NULL to * prevent query_addrrset() from trying to * call query_keepname() again. */ dbuf = NULL; query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); version = NULL; RESTORE(fname, zfname); RESTORE(version, zversion); RESTORE(rdataset, zrdataset); RESTORE(sigrdataset, zsigrdataset); /* * We don't clean up zdb here because we * may still need it. It will get cleaned * up by the main cleanup code. */ } if (RECURSIONOK(client)) { /* * Recurse! */ INSIST(!REDIRECT(client)); if (dns_rdatatype_atparent(type)) result = query_recurse(client, qtype, client->query.qname, NULL, NULL, resuming); else if (dns64) result = query_recurse(client, dns_rdatatype_a, client->query.qname, NULL, NULL, resuming); else result = query_recurse(client, qtype, client->query.qname, fname, rdataset, resuming); if (result == ISC_R_SUCCESS) { client->query.attributes |= NS_QUERYATTR_RECURSING; if (dns64) client->query.attributes |= NS_QUERYATTR_DNS64; if (dns64_exclude) client->query.attributes |= NS_QUERYATTR_DNS64EXCLUDE; } else if (result == DNS_R_DUPLICATE || result == DNS_R_DROP) QUERY_ERROR(result); else RECURSE_ERROR(result); } else { bool detach = false; dns_fixedname_init(&fixed); dns_name_copy(fname, dns_fixedname_name(&fixed), NULL); /* * This is the best answer. */ client->query.attributes |= NS_QUERYATTR_CACHEGLUEOK; client->query.isreferral = true; if (zdb != NULL && client->query.gluedb == NULL) { dns_db_attach(zdb, &client->query.gluedb); detach = true; } /* * We must ensure NOADDITIONAL is off, * because the generation of * additional data is required in * delegations. */ client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; if (sigrdataset != NULL) sigrdatasetp = &sigrdataset; else sigrdatasetp = NULL; query_addrrset(client, &fname, &rdataset, sigrdatasetp, dbuf, DNS_SECTION_AUTHORITY); client->query.attributes &= ~NS_QUERYATTR_CACHEGLUEOK; if (detach) { dns_db_detach(&client->query.gluedb); } if (WANTDNSSEC(client)) query_addds(client, db, node, version, dns_fixedname_name(&fixed)); } } goto cleanup; case DNS_R_EMPTYNAME: case DNS_R_NXRRSET: iszone_nxrrset: INSIST(is_zone); #ifdef dns64_bis_return_excluded_addresses if (dns64) #else if (dns64 && !dns64_exclude) #endif { /* * Restore the answers from the previous AAAA lookup. */ if (rdataset != NULL) query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); RESTORE(rdataset, client->query.dns64_aaaa); RESTORE(sigrdataset, client->query.dns64_sigaaaa); if (fname == NULL) { dbuf = query_getnamebuf(client); if (dbuf == NULL) { CTRACE(ISC_LOG_ERROR, "query_find: " "query_getnamebuf failed (3)"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } fname = query_newname(client, dbuf, &b); if (fname == NULL) { CTRACE(ISC_LOG_ERROR, "query_find: " "query_newname failed (3)"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } dns_name_copy(client->query.qname, fname, NULL); dns64 = false; #ifdef dns64_bis_return_excluded_addresses /* * Resume the diverted processing of the AAAA response? */ if (dns64_excluded) break; #endif } else if (result == DNS_R_NXRRSET && !ISC_LIST_EMPTY(client->view->dns64) && client->message->rdclass == dns_rdataclass_in && qtype == dns_rdatatype_aaaa) { /* * Look to see if there are A records for this * name. */ SAVE(client->query.dns64_aaaa, rdataset); SAVE(client->query.dns64_sigaaaa, sigrdataset); client->query.dns64_ttl = dns64_ttl(db, version); query_releasename(client, &fname); dns_db_detachnode(db, &node); type = qtype = dns_rdatatype_a; dns64 = true; goto db_find; } /* * Look for a NSEC3 record if we don't have a NSEC record. */ nxrrset_rrsig: if (redirected) goto cleanup; if (!dns_rdataset_isassociated(rdataset) && WANTDNSSEC(client)) { if ((fname->attributes & DNS_NAMEATTR_WILDCARD) == 0) { dns_name_t *found; dns_name_t *qname; found = dns_fixedname_initname(&fixed); qname = client->query.qname; query_findclosestnsec3(qname, db, version, client, rdataset, sigrdataset, 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(rdataset) && !dns_name_equal(qname, found) && !(ns_g_nonearest && qtype != dns_rdatatype_ds)) { unsigned int count; unsigned int skip; /* * Add the closest provable encloser. */ query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, DNS_SECTION_AUTHORITY); count = dns_name_countlabels(found) + 1; skip = dns_name_countlabels(qname) - count; dns_name_getlabelsequence(qname, skip, count, found); fixfname(client, &fname, &dbuf, &b); fixrdataset(client, &rdataset); fixrdataset(client, &sigrdataset); if (fname == NULL || rdataset == NULL || sigrdataset == NULL) { CTRACE(ISC_LOG_ERROR, "query_find: " "failure getting " "closest encloser"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } /* * 'nearest' doesn't exist so * 'exist' is set to false. */ query_findclosestnsec3(found, db, version, client, rdataset, sigrdataset, fname, false, NULL); } } else { query_releasename(client, &fname); query_addwildcardproof(client, db, version, client->query.qname, false, true); } } if (dns_rdataset_isassociated(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. */ query_keepname(client, fname, dbuf); } else if (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. */ query_releasename(client, &fname); } /* * Add SOA to the additional section if generated by a RPZ * rewrite. */ associated = dns_rdataset_isassociated(rdataset); section = nxrewrite ? DNS_SECTION_ADDITIONAL : DNS_SECTION_AUTHORITY; result = query_addsoa(client, db, version, UINT32_MAX, associated, section); if (result != ISC_R_SUCCESS) { QUERY_ERROR(result); goto cleanup; } /* * Add NSEC record if we found one. */ if (WANTDNSSEC(client)) { if (dns_rdataset_isassociated(rdataset)) query_addnxrrsetnsec(client, db, version, &fname, &rdataset, &sigrdataset); } goto cleanup; case DNS_R_EMPTYWILD: empty_wild = true; /* FALLTHROUGH */ case DNS_R_NXDOMAIN: INSIST(is_zone || REDIRECT(client)); if (!empty_wild) { tresult = redirect(client, fname, rdataset, &node, &db, &version, type); if (tresult == ISC_R_SUCCESS) { inc_stats(client, dns_nsstatscounter_nxdomainredirect); break; } if (tresult == DNS_R_NXRRSET) { redirected = true; goto iszone_nxrrset; } if (tresult == DNS_R_NCACHENXRRSET) { redirected = true; is_zone = false; goto ncache_nxrrset; } tresult = redirect2(client, fname, rdataset, &node, &db, &version, type, &is_zone); if (tresult == DNS_R_CONTINUE) { inc_stats(client, dns_nsstatscounter_nxdomainredirect_rlookup); client->query.redirect.qtype = qtype; INSIST(rdataset != NULL); SAVE(client->query.redirect.rdataset, rdataset); SAVE(client->query.redirect.sigrdataset, sigrdataset); SAVE(client->query.redirect.db, db); SAVE(client->query.redirect.node, node); SAVE(client->query.redirect.zone, zone); client->query.redirect.result = DNS_R_NXDOMAIN; dns_name_copy(fname, client->query.redirect.fname, NULL); client->query.redirect.authoritative = authoritative; client->query.redirect.is_zone = is_zone; goto cleanup; } if (tresult == ISC_R_SUCCESS) { inc_stats(client, dns_nsstatscounter_nxdomainredirect); break; } if (tresult == DNS_R_NXRRSET) { redirected = true; goto iszone_nxrrset; } if (tresult == DNS_R_NCACHENXRRSET) { redirected = true; is_zone = false; goto ncache_nxrrset; } } if (dns_rdataset_isassociated(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. */ query_keepname(client, fname, dbuf); } else if (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. */ query_releasename(client, &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. */ associated = dns_rdataset_isassociated(rdataset); section = nxrewrite ? DNS_SECTION_ADDITIONAL : DNS_SECTION_AUTHORITY; ttl = UINT32_MAX; if (!nxrewrite && qtype == dns_rdatatype_soa && zone != NULL && dns_zone_getzeronosoattl(zone)) ttl = 0; result = query_addsoa(client, db, version, ttl, associated, section); if (result != ISC_R_SUCCESS) { QUERY_ERROR(result); goto cleanup; } if (WANTDNSSEC(client)) { /* * Add NSEC record if we found one. */ if (dns_rdataset_isassociated(rdataset)) query_addrrset(client, &fname, &rdataset, &sigrdataset, NULL, DNS_SECTION_AUTHORITY); query_addwildcardproof(client, db, version, client->query.qname, false, false); } /* * Set message rcode. */ if (empty_wild) client->message->rcode = dns_rcode_noerror; else client->message->rcode = dns_rcode_nxdomain; goto cleanup; case DNS_R_NCACHENXDOMAIN: tresult = redirect(client, fname, rdataset, &node, &db, &version, type); if (tresult == ISC_R_SUCCESS) { inc_stats(client, dns_nsstatscounter_nxdomainredirect); break; } if (tresult == DNS_R_NXRRSET) { redirected = true; is_zone = true; goto iszone_nxrrset; } if (tresult == DNS_R_NCACHENXRRSET) { redirected = true; result = tresult; goto ncache_nxrrset; } tresult = redirect2(client, fname, rdataset, &node, &db, &version, type, &is_zone); if (tresult == DNS_R_CONTINUE) { inc_stats(client, dns_nsstatscounter_nxdomainredirect_rlookup); SAVE(client->query.redirect.db, db); SAVE(client->query.redirect.node, node); SAVE(client->query.redirect.zone, zone); client->query.redirect.qtype = qtype; INSIST(rdataset != NULL); SAVE(client->query.redirect.rdataset, rdataset); SAVE(client->query.redirect.sigrdataset, sigrdataset); client->query.redirect.result = DNS_R_NCACHENXDOMAIN; dns_name_copy(fname, client->query.redirect.fname, NULL); client->query.redirect.authoritative = authoritative; client->query.redirect.is_zone = is_zone; goto cleanup; } if (tresult == ISC_R_SUCCESS) { inc_stats(client, dns_nsstatscounter_nxdomainredirect); break; } if (tresult == DNS_R_NXRRSET) { redirected = true; is_zone = true; goto iszone_nxrrset; } if (tresult == DNS_R_NCACHENXRRSET) { redirected = true; result = tresult; goto ncache_nxrrset; } /* FALLTHROUGH */ case DNS_R_NCACHENXRRSET: ncache_nxrrset: INSIST(!is_zone); authoritative = false; /* * Set message rcode, if required. */ if (result == DNS_R_NCACHENXDOMAIN) client->message->rcode = dns_rcode_nxdomain; /* * Look for RFC 1918 leakage from Internet. */ if (result == DNS_R_NCACHENXDOMAIN && qtype == dns_rdatatype_ptr && client->message->rdclass == dns_rdataclass_in && dns_name_countlabels(fname) == 7) warn_rfc1918(client, fname, rdataset); #ifdef dns64_bis_return_excluded_addresses if (dns64) #else if (dns64 && !dns64_exclude) #endif { /* * Restore the answers from the previous AAAA lookup. */ if (rdataset != NULL) query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); RESTORE(rdataset, client->query.dns64_aaaa); RESTORE(sigrdataset, client->query.dns64_sigaaaa); if (fname == NULL) { dbuf = query_getnamebuf(client); if (dbuf == NULL) { CTRACE(ISC_LOG_ERROR, "query_find: " "query_getnamebuf failed (4)"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } fname = query_newname(client, dbuf, &b); if (fname == NULL) { CTRACE(ISC_LOG_ERROR, "query_find: " "query_newname failed (4)"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } dns_name_copy(client->query.qname, fname, NULL); dns64 = false; #ifdef dns64_bis_return_excluded_addresses if (dns64_excluded) break; #endif } else if (result == DNS_R_NCACHENXRRSET && !ISC_LIST_EMPTY(client->view->dns64) && client->message->rdclass == dns_rdataclass_in && qtype == dns_rdatatype_aaaa) { /* * Look to see if there are A records for this * name. */ /* * If the ttl is zero we need to workout if we have just * decremented to zero or if there was no negative cache * ttl in the answer. */ if (rdataset->ttl != 0) client->query.dns64_ttl = rdataset->ttl; else if (dns_rdataset_first(rdataset) == ISC_R_SUCCESS) client->query.dns64_ttl = 0; SAVE(client->query.dns64_aaaa, rdataset); SAVE(client->query.dns64_sigaaaa, sigrdataset); query_releasename(client, &fname); dns_db_detachnode(db, &node); type = qtype = dns_rdatatype_a; dns64 = true; goto db_find; } /* * 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(rdataset)) { query_keepname(client, fname, dbuf); dns_message_addname(client->message, fname, DNS_SECTION_AUTHORITY); ISC_LIST_APPEND(fname->list, rdataset, link); fname = NULL; rdataset = NULL; } goto cleanup; case DNS_R_CNAME: /* * If we have a zero ttl from the cache refetch it. */ if (!is_zone && !resuming && rdataset->ttl == 0 && RECURSIONOK(client)) { 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); INSIST(!REDIRECT(client)); result = query_recurse(client, qtype, client->query.qname, NULL, NULL, resuming); if (result == ISC_R_SUCCESS) { client->query.attributes |= NS_QUERYATTR_RECURSING; if (dns64) client->query.attributes |= NS_QUERYATTR_DNS64; if (dns64_exclude) client->query.attributes |= NS_QUERYATTR_DNS64EXCLUDE; } else RECURSE_ERROR(result); goto cleanup; } /* * 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 = rdataset; /* * Add the CNAME to the answer section. */ if (sigrdataset != NULL) sigrdatasetp = &sigrdataset; else sigrdatasetp = NULL; if (WANTDNSSEC(client) && (fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) { dns_fixedname_init(&wildcardname); dns_name_copy(fname, dns_fixedname_name(&wildcardname), NULL); need_wildcardproof = true; } if (NOQNAME(rdataset) && WANTDNSSEC(client)) noqname = rdataset; else noqname = NULL; if (!is_zone && RECURSIONOK(client)) query_prefetch(client, fname, rdataset); query_addrrset(client, &fname, &rdataset, sigrdatasetp, dbuf, DNS_SECTION_ANSWER); if (noqname != NULL) query_addnoqnameproof(client, noqname); /* * We set the PARTIALANSWER attribute so that if anything goes * wrong later on, we'll return what we've got so far. */ client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; /* * Reset qname to be the target name of the CNAME and restart * the query. */ tname = NULL; result = dns_message_gettempname(client->message, &tname); if (result != ISC_R_SUCCESS) goto cleanup; result = dns_rdataset_first(trdataset); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &tname); goto cleanup; } dns_rdataset_current(trdataset, &rdata); result = dns_rdata_tostruct(&rdata, &cname, NULL); dns_rdata_reset(&rdata); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &tname); goto cleanup; } dns_name_init(tname, NULL); result = dns_name_dup(&cname.cname, client->mctx, tname); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &tname); dns_rdata_freestruct(&cname); goto cleanup; } dns_rdata_freestruct(&cname); ns_client_qnamereplace(client, tname); want_restart = true; if (!WANTRECURSION(client)) options |= DNS_GETDB_NOLOG; goto addauth; case DNS_R_DNAME: /* * 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(client->query.qname, 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 = rdataset; /* * Add the DNAME to the answer section. */ if (sigrdataset != NULL) sigrdatasetp = &sigrdataset; else sigrdatasetp = NULL; if (WANTDNSSEC(client) && (fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) { dns_fixedname_init(&wildcardname); dns_name_copy(fname, dns_fixedname_name(&wildcardname), NULL); need_wildcardproof = true; } if (!is_zone && RECURSIONOK(client)) query_prefetch(client, fname, rdataset); query_addrrset(client, &fname, &rdataset, sigrdatasetp, 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. */ client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; /* * Get the target name of the DNAME. */ tname = NULL; result = dns_message_gettempname(client->message, &tname); if (result != ISC_R_SUCCESS) goto cleanup; result = dns_rdataset_first(trdataset); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &tname); goto cleanup; } dns_rdataset_current(trdataset, &rdata); result = dns_rdata_tostruct(&rdata, &dname, NULL); dns_rdata_reset(&rdata); if (result != ISC_R_SUCCESS) { dns_message_puttempname(client->message, &tname); goto cleanup; } dns_name_clone(&dname.dname, tname); dns_rdata_freestruct(&dname); /* * Construct the new qname consisting of * . */ prefix = dns_fixedname_initname(&fixed); dns_name_split(client->query.qname, nlabels, prefix, NULL); INSIST(fname == NULL); dbuf = query_getnamebuf(client); if (dbuf == NULL) { dns_message_puttempname(client->message, &tname); goto cleanup; } fname = query_newname(client, dbuf, &b); if (fname == NULL) { dns_message_puttempname(client->message, &tname); goto cleanup; } result = dns_name_concatenate(prefix, tname, fname, NULL); dns_message_puttempname(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) client->message->rcode = dns_rcode_yxdomain; if (result != ISC_R_SUCCESS) goto cleanup; query_keepname(client, fname, 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_add_cname(client, client->query.qname, fname, trdataset->trust, trdataset->ttl); if (result != ISC_R_SUCCESS) goto cleanup; /* * Switch to the new qname and restart. */ ns_client_qnamereplace(client, fname); fname = NULL; want_restart = true; if (!WANTRECURSION(client)) options |= DNS_GETDB_NOLOG; goto addauth; default: /* * Something has gone wrong. */ snprintf(errmsg, sizeof(errmsg) - 1, "query_find: unexpected error after resuming: %s", isc_result_totext(result)); CTRACE(ISC_LOG_ERROR, errmsg); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } if (WANTDNSSEC(client) && (fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) { dns_fixedname_init(&wildcardname); dns_name_copy(fname, dns_fixedname_name(&wildcardname), NULL); need_wildcardproof = true; } #ifdef ALLOW_FILTER_AAAA /* * The filter-aaaa-on-v4 option should suppress AAAAs for IPv4 * clients if there is an A; filter-aaaa-on-v6 option does the same * for IPv6 clients. */ client->filter_aaaa = dns_aaaa_ok; if (client->view->v4_aaaa != dns_aaaa_ok || client->view->v6_aaaa != dns_aaaa_ok) { result = ns_client_checkaclsilent(client, NULL, client->view->aaaa_acl, true); if (result == ISC_R_SUCCESS && client->view->v4_aaaa != dns_aaaa_ok && is_v4_client(client)) client->filter_aaaa = client->view->v4_aaaa; else if (result == ISC_R_SUCCESS && client->view->v6_aaaa != dns_aaaa_ok && is_v6_client(client)) client->filter_aaaa = client->view->v6_aaaa; } #endif if (type == dns_rdatatype_any) { /* * For minimal-any, we only add records that * match this type or cover this type. */ dns_rdatatype_t onetype = 0; #ifdef ALLOW_FILTER_AAAA bool have_aaaa, have_a, have_sig; /* * If we are not authoritative, assume there is a A * even in if it is not in our cache. This assumption could * be wrong but it is a good bet. */ have_aaaa = false; have_a = !authoritative; have_sig = false; #endif /* * XXXRTH Need to handle zonecuts with special case * code. */ n = 0; rdsiter = NULL; result = dns_db_allrdatasets(db, node, version, 0, &rdsiter); if (result != ISC_R_SUCCESS) { CTRACE(ISC_LOG_ERROR, "query_find: type any; allrdatasets failed"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } /* * 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 query_keepname() * now, and pass a NULL dbuf to query_addrrset(). * * If we do a query_addrrset() below, we must set fname to * NULL before leaving this block, otherwise we might try to * cleanup fname even though we're using it! */ query_keepname(client, fname, dbuf); tname = fname; result = dns_rdatasetiter_first(rdsiter); while (result == ISC_R_SUCCESS) { dns_rdatasetiter_current(rdsiter, rdataset); #ifdef ALLOW_FILTER_AAAA /* * Notice the presence of A and AAAAs so * that AAAAs can be hidden from IPv4 clients. */ if (client->filter_aaaa != dns_aaaa_ok) { if (rdataset->type == dns_rdatatype_aaaa) have_aaaa = true; else if (rdataset->type == dns_rdatatype_a) have_a = true; } #endif /* * We found an NS RRset; no need to add one later. */ if (qtype == dns_rdatatype_any && rdataset->type == dns_rdatatype_ns) { answer_has_ns = true; } if (is_zone && qtype == dns_rdatatype_any && !dns_db_issecure(db) && dns_rdatatype_isdnssec(rdataset->type)) { /* * The zone is transitioning from insecure * to secure. Hide the dnssec records from * ANY queries. */ dns_rdataset_disassociate(rdataset); } else if (client->view->minimal_any && !TCP(client) && !WANTDNSSEC(client) && qtype == dns_rdatatype_any && (rdataset->type == dns_rdatatype_sig || rdataset->type == dns_rdatatype_rrsig)) { CTRACE(ISC_LOG_DEBUG(5), "query_find: " "minimal-any skip signature"); dns_rdataset_disassociate(rdataset); } else if (client->view->minimal_any && !TCP(client) && onetype != 0 && rdataset->type != onetype && rdataset->covers != onetype) { CTRACE(ISC_LOG_DEBUG(5), "query_find: " "minimal-any skip rdataset"); dns_rdataset_disassociate(rdataset); } else if ((qtype == dns_rdatatype_any || rdataset->type == qtype) && rdataset->type != 0) { #ifdef ALLOW_FILTER_AAAA if (dns_rdatatype_isdnssec(rdataset->type)) have_sig = true; #endif if (NOQNAME(rdataset) && WANTDNSSEC(client)) noqname = rdataset; else noqname = NULL; rpz_st = client->query.rpz_st; if (rpz_st != NULL) rdataset->ttl = ISC_MIN(rdataset->ttl, rpz_st->m.ttl); if (!is_zone && RECURSIONOK(client)) { dns_name_t *name; name = (fname != NULL) ? fname : tname; query_prefetch(client, name, rdataset); } /* * Remember the first RRtype we find so we * can skip others with minimal-any. */ if (rdataset->type == dns_rdatatype_sig || rdataset->type == dns_rdatatype_rrsig) onetype = rdataset->covers; else onetype = rdataset->type; query_addrrset(client, fname != NULL ? &fname : &tname, &rdataset, NULL, NULL, DNS_SECTION_ANSWER); if (noqname != NULL) query_addnoqnameproof(client, noqname); n++; INSIST(tname != NULL); /* * rdataset is non-NULL only in certain * pathological cases involving DNAMEs. */ if (rdataset != NULL) query_putrdataset(client, &rdataset); rdataset = query_newrdataset(client); if (rdataset == NULL) break; } else { /* * We're not interested in this rdataset. */ dns_rdataset_disassociate(rdataset); } result = dns_rdatasetiter_next(rdsiter); } #ifdef ALLOW_FILTER_AAAA /* * Filter AAAAs if there is an A and there is no signature * or we are supposed to break DNSSEC. */ if (client->filter_aaaa == dns_aaaa_break_dnssec) client->attributes |= NS_CLIENTATTR_FILTER_AAAA; else if (client->filter_aaaa != dns_aaaa_ok && have_aaaa && have_a && (!have_sig || !WANTDNSSEC(client))) client->attributes |= NS_CLIENTATTR_FILTER_AAAA; #endif if (fname != NULL) dns_message_puttempname(client->message, &fname); if (n == 0) { /* * No matching rdatasets found in cache. If we were * searching for RRSIG/SIG, that's probably okay; * otherwise this is an error condition. */ if ((qtype == dns_rdatatype_rrsig || qtype == dns_rdatatype_sig) && result == ISC_R_NOMORE) { if (!is_zone) { authoritative = false; dns_rdatasetiter_destroy(&rdsiter); client->attributes &= ~NS_CLIENTATTR_RA; goto addauth; } if (qtype == dns_rdatatype_rrsig && dns_db_issecure(db)) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(client->query.qname, namebuf, sizeof(namebuf)); ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, NS_LOGMODULE_QUERY, ISC_LOG_WARNING, "missing signature " "for %s", namebuf); } dns_rdatasetiter_destroy(&rdsiter); fname = query_newname(client, dbuf, &b); goto nxrrset_rrsig; } else { CTRACE(ISC_LOG_ERROR, "query_find: no matching rdatasets " "in cache"); result = DNS_R_SERVFAIL; } } dns_rdatasetiter_destroy(&rdsiter); if (result != ISC_R_NOMORE) { CTRACE(ISC_LOG_ERROR, "query_find: dns_rdatasetiter_destroy failed"); QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } else { /* * This is the "normal" case -- an ordinary question to which * we know the answer. */ /* * If we have a zero ttl from the cache refetch it. */ if (!is_zone && !resuming && rdataset->ttl == 0 && RECURSIONOK(client)) { 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); INSIST(!REDIRECT(client)); result = query_recurse(client, qtype, client->query.qname, NULL, NULL, resuming); if (result == ISC_R_SUCCESS) { client->query.attributes |= NS_QUERYATTR_RECURSING; if (dns64) client->query.attributes |= NS_QUERYATTR_DNS64; if (dns64_exclude) client->query.attributes |= NS_QUERYATTR_DNS64EXCLUDE; } else RECURSE_ERROR(result); goto cleanup; } /* * Check to see if the AAAA RRset has non-excluded addresses * in it. If not look for a A RRset. * * Note: the order of dns64_aaaaok() and filter_aaaa check is * important. Both result is fetches being called but the * dns64 case goes to db_find while the filter_aaaa case * adds the records now for later potential exclusion. */ INSIST(client->query.dns64_aaaaok == NULL); if (qtype == dns_rdatatype_aaaa && !dns64_exclude && !ISC_LIST_EMPTY(client->view->dns64) && client->message->rdclass == dns_rdataclass_in && !dns64_aaaaok(client, rdataset, sigrdataset)) { /* * Look to see if there are A records for this * name. */ client->query.dns64_ttl = rdataset->ttl; SAVE(client->query.dns64_aaaa, rdataset); SAVE(client->query.dns64_sigaaaa, sigrdataset); query_releasename(client, &fname); dns_db_detachnode(db, &node); type = qtype = dns_rdatatype_a; dns64_exclude = dns64 = true; goto db_find; } #ifdef ALLOW_FILTER_AAAA /* * Optionally hide AAAAs from IPv4 clients if there is an A. * We add the AAAAs now, but might refuse to render them later * after DNSSEC is figured out. * This could be more efficient, but the whole idea is * so fundamentally wrong, unavoidably inaccurate, and * unneeded that it is best to keep it as short as possible. */ if (client->filter_aaaa == dns_aaaa_break_dnssec || (client->filter_aaaa == dns_aaaa_filter && (!WANTDNSSEC(client) || sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)))) { if (qtype == dns_rdatatype_aaaa) { trdataset = query_newrdataset(client); result = dns_db_findrdataset(db, node, version, dns_rdatatype_a, 0, client->now, trdataset, NULL); if (dns_rdataset_isassociated(trdataset)) dns_rdataset_disassociate(trdataset); query_putrdataset(client, &trdataset); /* * We have an AAAA but the A is not in our cache. * Assume any result other than DNS_R_DELEGATION * or ISC_R_NOTFOUND means there is no A and * so AAAAs are ok. * Assume there is no A if we can't recurse * for this client, although that could be * the wrong answer. What else can we do? * Besides, that we have the AAAA and are using * this mechanism suggests that we care more * about As than AAAAs and would have cached * the A if it existed. */ if (result == ISC_R_SUCCESS) { client->attributes |= NS_CLIENTATTR_FILTER_AAAA; } else if (authoritative || !RECURSIONOK(client) || (result != DNS_R_DELEGATION && result != ISC_R_NOTFOUND)) { client->attributes &= ~NS_CLIENTATTR_FILTER_AAAA; } else { /* * This is an ugly kludge to recurse * for the A and discard the result. * * Continue to add the AAAA now. * We'll make a note to not render it * if the recursion for the A succeeds. */ INSIST(!REDIRECT(client)); result = query_recurse(client, dns_rdatatype_a, client->query.qname, NULL, NULL, resuming); if (result == ISC_R_SUCCESS) { client->attributes |= NS_CLIENTATTR_FILTER_AAAA_RC; client->query.attributes |= NS_QUERYATTR_RECURSING; } } } else if (qtype == dns_rdatatype_a && (client->attributes & NS_CLIENTATTR_FILTER_AAAA_RC) != 0) { client->attributes &= ~NS_CLIENTATTR_FILTER_AAAA_RC; client->attributes |= NS_CLIENTATTR_FILTER_AAAA; dns_rdataset_disassociate(rdataset); if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); goto cleanup; } } #endif if (sigrdataset != NULL) sigrdatasetp = &sigrdataset; else sigrdatasetp = NULL; if (NOQNAME(rdataset) && WANTDNSSEC(client)) noqname = rdataset; else noqname = NULL; /* * Special case NS handling */ if (is_zone && qtype == dns_rdatatype_ns) { /* * We've already got an NS, no need to add one in * the authority section */ if (dns_name_equal(client->query.qname, dns_db_origin(db))) { answer_has_ns = true; } /* * BIND 8 priming queries need the additional section. */ if (dns_name_equal(client->query.qname, dns_rootname)) { client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; } } /* * Return the time to expire for slave and master zones. */ if (zone != NULL && is_zone && qtype == dns_rdatatype_soa && (client->attributes & NS_CLIENTATTR_WANTEXPIRE) != 0 && client->query.restarts == 0) { dns_zone_t *raw = NULL, *mayberaw; dns_zone_getraw(zone, &raw); mayberaw = (raw != NULL) ? raw : zone; if (dns_zone_gettype(mayberaw) == dns_zone_slave) { isc_time_t expiretime; uint32_t secs; dns_zone_getexpiretime(zone, &expiretime); secs = isc_time_seconds(&expiretime); if (secs >= client->now && result == ISC_R_SUCCESS) { client->attributes |= NS_CLIENTATTR_HAVEEXPIRE; client->expire = secs - client->now; } } if (dns_zone_gettype(mayberaw) == dns_zone_master) { dns_rdata_soa_t soa; 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); client->expire = soa.expire; client->attributes |= NS_CLIENTATTR_HAVEEXPIRE; } if (raw != NULL) dns_zone_detach(&raw); } if (dns64) { qtype = type = dns_rdatatype_aaaa; result = query_dns64(client, &fname, rdataset, sigrdataset, dbuf, DNS_SECTION_ANSWER); noqname = NULL; dns_rdataset_disassociate(rdataset); dns_message_puttemprdataset(client->message, &rdataset); if (result == ISC_R_NOMORE) { #ifndef dns64_bis_return_excluded_addresses if (dns64_exclude) { if (!is_zone) goto cleanup; /* * Add a fake SOA record. */ (void)query_addsoa(client, db, version, 600, false, DNS_SECTION_AUTHORITY); goto cleanup; } #endif if (is_zone) goto iszone_nxrrset; else goto ncache_nxrrset; } else if (result != ISC_R_SUCCESS) { eresult = result; goto cleanup; } } else if (client->query.dns64_aaaaok != NULL) { query_filter64(client, &fname, rdataset, dbuf, DNS_SECTION_ANSWER); query_putrdataset(client, &rdataset); } else { if (!is_zone && RECURSIONOK(client)) query_prefetch(client, fname, rdataset); query_addrrset(client, &fname, &rdataset, sigrdatasetp, dbuf, DNS_SECTION_ANSWER); } if (noqname != NULL) query_addnoqnameproof(client, noqname); /* * We shouldn't ever fail to add 'rdataset' * because it's already in the answer. */ INSIST(rdataset == NULL); } addauth: CTRACE(ISC_LOG_DEBUG(3), "query_find: addauth"); /* * Add NS records to the authority section (if we haven't already * added them to the answer section). */ if (!want_restart && !NOAUTHORITY(client)) { if (is_zone) { if (!answer_has_ns) { (void)query_addns(client, db, version); } } else if (!answer_has_ns && qtype != dns_rdatatype_ns) { if (fname != NULL) { query_releasename(client, &fname); } query_addbestns(client); } } /* * Add NSEC records to the authority section if they're needed for * DNSSEC wildcard proofs. */ if (need_wildcardproof && dns_db_issecure(db)) query_addwildcardproof(client, db, version, dns_fixedname_name(&wildcardname), true, false); cleanup: CTRACE(ISC_LOG_DEBUG(3), "query_find: cleanup"); /* * General cleanup. */ rpz_st = client->query.rpz_st; if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) == 0) { rpz_match_clear(rpz_st); rpz_st->state &= ~DNS_RPZ_DONE_QNAME; } if (rdataset != NULL) query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); if (fname != NULL) query_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) { query_putrdataset(client, &zrdataset); if (zsigrdataset != NULL) query_putrdataset(client, &zsigrdataset); if (zfname != NULL) query_releasename(client, &zfname); dns_db_detach(&zdb); } if (event != NULL) { free_devent(client, ISC_EVENT_PTR(&event), &event); } /* * AA bit. */ if (client->query.restarts == 0 && !authoritative) { /* * We're not authoritative, so we must ensure the AA bit * isn't set. */ client->message->flags &= ~DNS_MESSAGEFLAG_AA; } /* * Restart the query? */ if (want_restart && client->query.restarts < MAX_RESTARTS) { client->query.restarts++; goto restart; } if (eresult != ISC_R_SUCCESS && (!PARTIALANSWER(client) || WANTRECURSION(client) || eresult == DNS_R_DROP)) { if (eresult == DNS_R_DUPLICATE || eresult == 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(client, eresult); } 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(line >= 0); query_error(client, eresult, line); } ns_client_detach(&client); } else if (!RECURSING(client)) { /* * We are done. Set up sortlist data for the message * rendering code, make a final tweak to the AA bit if the * auth-nxdomain config option says so, then render and * send the response. */ setup_query_sortlist(client); /* * If this is a referral and the answer to the question * is in the glue sort it to the start of the additional * section. */ if (ISC_LIST_EMPTY(client->message->sections[DNS_SECTION_ANSWER]) && client->message->rcode == dns_rcode_noerror && (qtype == dns_rdatatype_a || qtype == dns_rdatatype_aaaa)) answer_in_glue(client, qtype); if (client->message->rcode == dns_rcode_nxdomain && client->view->auth_nxdomain == true) 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 (resuming && (ISC_LIST_EMPTY(client->message->sections[DNS_SECTION_ANSWER]) || client->message->rcode != dns_rcode_noerror)) eresult = ISC_R_FAILURE; query_send(client); ns_client_detach(&client); } CTRACE(ISC_LOG_DEBUG(3), "query_find: done"); return (eresult); } static inline void log_tat(ns_client_t *client) { char namebuf[DNS_NAME_FORMATSIZE]; char clientbuf[ISC_NETADDR_FORMATSIZE]; char classname[DNS_RDATACLASS_FORMATSIZE]; isc_netaddr_t netaddr; char *tags = NULL; size_t taglen = 0; if (!isc_log_wouldlog(ns_g_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, classname, sizeof(classname)); 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_g_lctx, NS_LOGCATEGORY_TAT, NS_LOGMODULE_QUERY, ISC_LOG_INFO, "trust-anchor-telemetry '%s/%s' from %s%s", namebuf, classname, clientbuf, tags != NULL? tags : ""); if (tags != NULL) { isc_mem_put(client->mctx, tags, taglen); } } static inline void log_query(ns_client_t *client, unsigned int flags, unsigned int extflags) { char namebuf[DNS_NAME_FORMATSIZE]; char typename[DNS_RDATATYPE_FORMATSIZE]; char classname[DNS_RDATACLASS_FORMATSIZE]; char onbuf[ISC_NETADDR_FORMATSIZE]; char ednsbuf[sizeof("E(65535)")] = { 0 }; dns_rdataset_t *rdataset; int level = ISC_LOG_INFO; if (! isc_log_wouldlog(ns_g_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, classname, sizeof(classname)); dns_rdatatype_format(rdataset->type, typename, sizeof(typename)); isc_netaddr_format(&client->destaddr, onbuf, sizeof(onbuf)); if (client->ednsversion >= 0) snprintf(ednsbuf, sizeof(ednsbuf), "E(%hd)", client->ednsversion); ns_client_log(client, NS_LOGCATEGORY_QUERIES, NS_LOGMODULE_QUERY, level, "query: %s %s %s %s%s%s%s%s%s%s (%s)", namebuf, classname, typename, 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); } static inline void log_queryerror(ns_client_t *client, isc_result_t result, int line, int level) { char namebuf[DNS_NAME_FORMATSIZE]; char typename[DNS_RDATATYPE_FORMATSIZE]; char classname[DNS_RDATACLASS_FORMATSIZE]; const char *namep, *typep, *classp, *sep1, *sep2; dns_rdataset_t *rdataset; if (!isc_log_wouldlog(ns_g_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, classname, sizeof(classname)); classp = classname; dns_rdatatype_format(rdataset->type, typename, sizeof(typename)); typep = typename; 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_result_t result; dns_message_t *message = client->message; dns_rdataset_t *rdataset; ns_client_t *qclient; dns_rdatatype_t qtype; unsigned int saved_extflags = client->extflags; unsigned int saved_flags = client->message->flags; REQUIRE(NS_CLIENT_VALID(client)); CTRACE(ISC_LOG_DEBUG(3), "ns_query_start"); /* * Test only. */ if (ns_g_clienttest && !TCP(client)) { result = ns_client_replace(client); if (result == ISC_R_SHUTTINGDOWN) { ns_client_next(client, result); return; } else if (result != ISC_R_SUCCESS) { query_error(client, result, __LINE__); return; } } /* * Ensure that appropriate cleanups occur. */ client->next = query_next_callback; /* * Behave as if we don't support DNSSEC if not enabled. */ if (!client->view->enablednssec) { message->flags &= ~DNS_MESSAGEFLAG_CD; client->extflags &= ~DNS_MESSAGEEXTFLAG_DO; } 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->additionalfromcache)) { /* * 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 (ns_g_server->log_queries) 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(ns_g_server->rcvquerystats, qtype); log_tat(client); if (dns_rdatatype_ismeta(qtype)) { switch (qtype) { case dns_rdatatype_any: break; /* Let query_find handle it. */ case dns_rdatatype_ixfr: case dns_rdatatype_axfr: 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, ns_g_server->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); } /* * 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 || 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; /* * Allow glue NS records to be added to the authority section * if the answer is secure. */ if (message->flags & DNS_MESSAGEFLAG_CD) 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 (!ns_g_noaa) 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; qclient = NULL; ns_client_attach(client, &qclient); (void)query_find(qclient, NULL, qtype); }