Current Path : /usr/src/contrib/bind9/bin/named/ |
FreeBSD hs32.drive.ne.jp 9.1-RELEASE FreeBSD 9.1-RELEASE #1: Wed Jan 14 12:18:08 JST 2015 root@hs32.drive.ne.jp:/sys/amd64/compile/hs32 amd64 |
Current File : //usr/src/contrib/bind9/bin/named/query.c |
/* * Copyright (C) 2004-2012 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 1999-2003 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /* $Id: query.c,v 1.353.8.24 2012/02/07 01:14:39 marka Exp $ */ /*! \file */ #include <config.h> #include <string.h> #include <isc/hex.h> #include <isc/mem.h> #include <isc/stats.h> #include <isc/util.h> #include <dns/adb.h> #include <dns/byaddr.h> #include <dns/db.h> #include <dns/dlz.h> #include <dns/dns64.h> #include <dns/dnssec.h> #include <dns/events.h> #include <dns/message.h> #include <dns/ncache.h> #include <dns/nsec3.h> #include <dns/order.h> #include <dns/rdata.h> #include <dns/rdataclass.h> #include <dns/rdatalist.h> #include <dns/rdataset.h> #include <dns/rdatasetiter.h> #include <dns/rdatastruct.h> #include <dns/rdatatype.h> #include <dns/resolver.h> #include <dns/result.h> #include <dns/stats.h> #include <dns/tkey.h> #include <dns/view.h> #include <dns/zone.h> #include <dns/zt.h> #include <named/client.h> #include <named/globals.h> #include <named/log.h> #include <named/server.h> #include <named/sortlist.h> #include <named/xfrout.h> #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) /*% Want DNSSEC? */ #define WANTDNSSEC(c) (((c)->attributes & \ NS_CLIENTATTR_WANTDNSSEC) != 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) /*% No QNAME Proof? */ #define NOQNAME(r) (((r)->attributes & \ DNS_RDATASETATTR_NOQNAME) != 0) #if 0 #define CTRACE(m) isc_log_write(ns_g_lctx, \ NS_LOGCATEGORY_CLIENT, \ NS_LOGMODULE_QUERY, \ ISC_LOG_DEBUG(3), \ "client %p: %s", client, (m)) #define QTRACE(m) isc_log_write(ns_g_lctx, \ NS_LOGCATEGORY_GENERAL, \ NS_LOGMODULE_QUERY, \ ISC_LOG_DEBUG(3), \ "query %p: %s", query, (m)) #else #define CTRACE(m) ((void)m) #define QTRACE(m) ((void)m) #endif #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) 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 isc_boolean_t 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, isc_boolean_t 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); /*% * Increment query statistics counters. */ static inline void inc_stats(ns_client_t *client, isc_statscounter_t counter) { dns_zone_t *zone = client->query.authzone; isc_stats_increment(ns_g_server->nsstats, counter); if (zone != NULL) { isc_stats_t *zonestats = dns_zone_getrequeststats(zone); if (zonestats != NULL) isc_stats_increment(zonestats, counter); } } 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) { if (ISC_LIST_EMPTY(client->message->sections[DNS_SECTION_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 { /* 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; } 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, isc_boolean_t everything) { ns_dbversion_t *dbversion, *dbversion_next; unsigned int i; for (dbversion = ISC_LIST_HEAD(client->query.freeversions), i = 0; dbversion != NULL; dbversion = dbversion_next, i++) { dbversion_next = ISC_LIST_NEXT(dbversion, link); /* * If we're not freeing everything, we keep the first three * dbversions structures around. */ if (i > 3 || everything) { ISC_LIST_UNLINK(client->query.freeversions, dbversion, link); isc_mem_put(client->mctx, dbversion, sizeof(*dbversion)); } } } void ns_query_cancel(ns_client_t *client) { LOCK(&client->query.fetchlock); if (client->query.fetch != NULL) { dns_resolver_cancelfetch(client->query.fetch); client->query.fetch = NULL; } UNLOCK(&client->query.fetchlock); } static inline void query_putrdataset(ns_client_t *client, dns_rdataset_t **rdatasetp) { dns_rdataset_t *rdataset = *rdatasetp; CTRACE("query_putrdataset"); if (rdataset != NULL) { if (dns_rdataset_isassociated(rdataset)) dns_rdataset_disassociate(rdataset); dns_message_puttemprdataset(client->message, rdatasetp); } CTRACE("query_putrdataset: done"); } static inline void query_reset(ns_client_t *client, isc_boolean_t everything) { isc_buffer_t *dbuf, *dbuf_next; ns_dbversion_t *dbversion, *dbversion_next; /*% * 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, ISC_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(isc_boolean_t)); client->query.dns64_aaaaok = NULL; client->query.dns64_aaaaoklen = 0; } 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 = ISC_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 = ISC_FALSE; client->query.isreferral = ISC_FALSE; client->query.dns64_options = 0; client->query.dns64_ttl = ISC_UINT32_MAX; } static void query_next_callback(ns_client_t *client) { query_reset(client, ISC_FALSE); } void ns_query_free(ns_client_t *client) { query_reset(client, ISC_TRUE); } static inline isc_result_t query_newnamebuf(ns_client_t *client) { isc_buffer_t *dbuf; isc_result_t result; CTRACE("query_newnamebuf"); /*% * Allocate a name buffer. */ dbuf = NULL; result = isc_buffer_allocate(client->mctx, &dbuf, 1024); if (result != ISC_R_SUCCESS) { CTRACE("query_newnamebuf: isc_buffer_allocate failed: done"); return (result); } ISC_LIST_APPEND(client->query.namebufs, dbuf, link); CTRACE("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("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("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 < 255) { result = query_newnamebuf(client); if (result != ISC_R_SUCCESS) { CTRACE("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("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("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("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("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("query_newname"); name = NULL; result = dns_message_gettempname(client->message, &name); if (result != ISC_R_SUCCESS) { CTRACE("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("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("query_newrdataset"); rdataset = NULL; result = dns_message_gettemprdataset(client->message, &rdataset); if (result != ISC_R_SUCCESS) { CTRACE("query_newrdataset: " "dns_message_gettemprdataset failed: done"); return (NULL); } dns_rdataset_init(rdataset); CTRACE("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; 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 = ISC_FALSE; client->query.rpz_st = NULL; client->query.qname = NULL; result = isc_mutex_init(&client->query.fetchlock); if (result != ISC_R_SUCCESS) return (result); client->query.fetch = NULL; client->query.authdb = NULL; client->query.authzone = NULL; client->query.authdbset = ISC_FALSE; client->query.isreferral = ISC_FALSE; client->query.dns64_aaaa = NULL; client->query.dns64_sigaaaa = NULL; client->query.dns64_aaaaok = NULL; client->query.dns64_aaaaoklen = 0; query_reset(client, ISC_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, ISC_TRUE); return (result); } static inline 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 = ISC_FALSE; dbversion->queryok = ISC_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; 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) 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 = ISC_TRUE; if ((client->query.attributes & NS_QUERYATTR_QUERYOK) == 0) { dbversion->queryok = ISC_FALSE; return (DNS_R_REFUSED); } dbversion->queryok = ISC_TRUE; goto approved; } } result = ns_client_checkaclsilent(client, NULL, queryacl, ISC_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; } dbversion->acl_checked = ISC_TRUE; if (result != ISC_R_SUCCESS) { dbversion->queryok = ISC_FALSE; return (DNS_R_REFUSED); } dbversion->queryok = ISC_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; isc_boolean_t partial = ISC_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 = ISC_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, const char *disabled, dns_rpz_policy_t policy, dns_rpz_type_t type, dns_name_t *rpz_qname) { char qname_buf[DNS_NAME_FORMATSIZE]; char rpz_qname_buf[DNS_NAME_FORMATSIZE]; if (!isc_log_wouldlog(ns_g_lctx, DNS_RPZ_INFO_LEVEL)) return; dns_name_format(client->query.qname, qname_buf, sizeof(qname_buf)); dns_name_format(rpz_qname, rpz_qname_buf, sizeof(rpz_qname_buf)); ns_client_log(client, DNS_LOGCATEGORY_RPZ, NS_LOGMODULE_QUERY, DNS_RPZ_INFO_LEVEL, "%srpz %s %s rewrite %s via %s", disabled, dns_rpz_type2str(type), dns_rpz_policy2str(policy), qname_buf, rpz_qname_buf); } static void rpz_log_fail(ns_client_t *client, int level, dns_rpz_type_t rpz_type, dns_name_t *name, const char *str, isc_result_t result) { char namebuf1[DNS_NAME_FORMATSIZE]; char namebuf2[DNS_NAME_FORMATSIZE]; if (!isc_log_wouldlog(ns_g_lctx, level)) return; dns_name_format(client->query.qname, namebuf1, sizeof(namebuf1)); dns_name_format(name, namebuf2, sizeof(namebuf2)); ns_client_log(client, NS_LOGCATEGORY_QUERY_EERRORS, NS_LOGMODULE_QUERY, level, "rpz %s rewrite %s via %s %sfailed: %s", dns_rpz_type2str(rpz_type), namebuf1, namebuf2, str, isc_result_totext(result)); } /* * Get a policy rewrite zone database. */ static isc_result_t rpz_getdb(ns_client_t *client, dns_rpz_type_t rpz_type, dns_name_t *rpz_qname, dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp) { char namebuf1[DNS_NAME_FORMATSIZE]; char namebuf2[DNS_NAME_FORMATSIZE]; dns_dbversion_t *rpz_version = NULL; isc_result_t result; result = query_getzonedb(client, rpz_qname, dns_rdatatype_any, DNS_GETDB_IGNOREACL, zonep, dbp, &rpz_version); if (result == ISC_R_SUCCESS) { if (isc_log_wouldlog(ns_g_lctx, DNS_RPZ_DEBUG_LEVEL2)) { dns_name_format(client->query.qname, namebuf1, sizeof(namebuf1)); dns_name_format(rpz_qname, namebuf2, sizeof(namebuf2)); 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), namebuf1, namebuf2); } *versionp = rpz_version; return (ISC_R_SUCCESS); } rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, rpz_qname, "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; isc_boolean_t 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 = ISC_FALSE; if ((client->query.attributes & NS_QUERYATTR_CACHEACLOK) == 0) goto refuse; } else { /* * We haven't evaluated the view's queryacl yet. */ check_acl = ISC_TRUE; } if (check_acl) { isc_boolean_t log = ISC_TF((options & DNS_GETDB_NOLOG) == 0); char msg[NS_CLIENT_ACLMSGSIZE("query (cache)")]; result = ns_client_checkaclsilent(client, NULL, client->view->cacheacl, ISC_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, isc_boolean_t *is_zonep) { isc_result_t result; isc_result_t tresult; unsigned int namelabels; unsigned int zonelabels; dns_zone_t *zone = NULL; dns_db_t *tdbp; REQUIRE(zonep != NULL && *zonep == NULL); tdbp = 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 a DLZ driver is loaded for this view */ if (zonelabels < namelabels && client->view->dlzdatabase != NULL) { tresult = dns_dlzfindzone(client->view, name, zonelabels, &tdbp); /* If we successful, we found a better match. */ if (tresult == ISC_R_SUCCESS) { /* * 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; /* * Get our database version. */ dns_db_currentversion(tdbp, versionp); /* * Be sure to return our database. */ *dbp = tdbp; /* * 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 = ISC_TRUE; } else if (result == ISC_R_NOTFOUND) { result = query_getcachedb(client, name, qtype, dbp, options); *is_zonep = ISC_FALSE; } return (result); } static inline isc_boolean_t 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("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("query_isduplicate: true: done"); return (ISC_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("query_isduplicate: false: done"); return (ISC_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; isc_boolean_t added_something, need_addname; dns_zone_t *zone; dns_rdatatype_t type; REQUIRE(NS_CLIENT_VALID(client)); REQUIRE(qtype != dns_rdatatype_any); if (!WANTDNSSEC(client) && dns_rdatatype_isdnssec(qtype)) return (ISC_R_SUCCESS); CTRACE("query_addadditional"); /* * Initialization. */ eresult = ISC_R_SUCCESS; fname = NULL; rdataset = NULL; sigrdataset = NULL; trdataset = NULL; db = NULL; version = NULL; node = NULL; added_something = ISC_FALSE; need_addname = ISC_FALSE; zone = 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("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. */ node = NULL; result = dns_db_find(db, name, version, type, client->query.dboptions, client->now, &node, fname, 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: 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_find(db, name, version, type, client->query.dboptions | DNS_DBFIND_GLUEOK | DNS_DBFIND_ADDITIONALOK, client->now, &node, fname, rdataset, sigrdataset); if (result == DNS_R_GLUE && validate(client, db, fname, rdataset, sigrdataset)) result = ISC_R_SUCCESS; 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); result = dns_db_find(db, name, version, type, client->query.dboptions | DNS_DBFIND_GLUEOK, client->now, &node, fname, 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 = ISC_TRUE; ISC_LIST_APPEND(fname->list, rdataset, link); trdataset = rdataset; rdataset = NULL; added_something = ISC_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_ON_V4 isc_boolean_t have_a = ISC_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) { mname = NULL; #ifdef ALLOW_FILTER_AAAA_ON_V4 have_a = ISC_TRUE; #endif if (!query_isduplicate(client, fname, dns_rdatatype_a, &mname)) { if (mname != fname) { if (mname != NULL) { query_releasename(client, &fname); fname = mname; } else need_addname = ISC_TRUE; } ISC_LIST_APPEND(fname->list, rdataset, link); added_something = ISC_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) { mname = NULL; /* * There's an A; check whether we're filtering AAAA */ #ifdef ALLOW_FILTER_AAAA_ON_V4 if (have_a && (client->filter_aaaa == dns_v4_aaaa_break_dnssec || (client->filter_aaaa == dns_v4_aaaa_filter && (!WANTDNSSEC(client) || sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset))))) goto addname; #endif if (!query_isduplicate(client, fname, dns_rdatatype_aaaa, &mname)) { if (mname != fname) { if (mname != NULL) { query_releasename(client, &fname); fname = mname; } else need_addname = ISC_TRUE; } ISC_LIST_APPEND(fname->list, rdataset, link); added_something = ISC_TRUE; if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { ISC_LIST_APPEND(fname->list, sigrdataset, link); sigrdataset = NULL; } rdataset = NULL; } } } addname: CTRACE("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 ISC_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("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("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, ISC_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, ISC_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; isc_boolean_t added_something, need_addname, needadditionalcache; isc_boolean_t need_sigrrset; dns_zone_t *zone; dns_rdatatype_t type; dns_rdatasetadditional_t additionaltype; /* * 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 = ISC_FALSE; need_addname = ISC_FALSE; zone = NULL; needadditionalcache = ISC_FALSE; POST(needadditionalcache); additionaltype = dns_rdatasetadditional_fromauth; dns_name_init(&cfname, NULL); CTRACE("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("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("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("query_addadditional2: negative auth additional cache"); dns_db_closeversion(cdb, &cversion, ISC_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("query_addadditional2: auth additional cache"); dns_db_closeversion(cdb, &cversion, ISC_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("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_find(db, name, version, type, client->query.dboptions, client->now, &node, fname, 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_find(db, name, version, type, client->query.dboptions | DNS_DBFIND_GLUEOK | DNS_DBFIND_ADDITIONALOK, client->now, &node, fname, 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("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("query_addadditional2: negative glue additional cache"); dns_db_closeversion(cdb, &cversion, ISC_FALSE); dns_db_detach(&cdb); goto cleanup; } /* Cache hit. */ CTRACE("query_addadditional2: glue additional cache"); dns_db_closeversion(cdb, &cversion, ISC_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_find(db, name, version, type, client->query.dboptions | DNS_DBFIND_GLUEOK, client->now, &node, fname, 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 = ISC_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); /* * If we can't promote glue/pending from the cache to secure * then drop it. */ if (result == ISC_R_SUCCESS && additionaltype == dns_rdatasetadditional_fromcache && (DNS_TRUST_PENDING(rdataset->trust) || DNS_TRUST_GLUE(rdataset->trust)) && !validate(client, db, fname, rdataset, sigrdataset)) { 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); /* * If we can't promote glue/pending from the cache to secure * then drop it. */ if (result == ISC_R_SUCCESS && additionaltype == dns_rdatasetadditional_fromcache && (DNS_TRUST_PENDING(rdataset->trust) || DNS_TRUST_GLUE(rdataset->trust)) && !validate(client, db, fname, rdataset, sigrdataset)) { 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 == ISC_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 = ISC_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 = ISC_TRUE; } ISC_LIST_UNLINK(cfname.list, crdataset, link); ISC_LIST_APPEND(fname->list, crdataset, link); added_something = ISC_TRUE; need_sigrrset = ISC_TRUE; } else need_sigrrset = ISC_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 = ISC_TRUE; /* just in case */ need_sigrrset = ISC_FALSE; } } CTRACE("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 ISC_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("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("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("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("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("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("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_rdataset_init(dns64_rdataset); dns_rdatalist_init(dns64_rdatalist); dns64_rdatalist->rdclass = dns_rdataclass_in; dns64_rdatalist->type = dns_rdatatype_aaaa; if (client->query.dns64_ttl != ISC_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; 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); 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("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("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("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_rdataset_init(myrdataset); 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; 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("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("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. * There's nothing else to do; */ CTRACE("query_addrrset: dns_message_findname succeeded: done"); if (dbuf != NULL) query_releasename(client, namep); 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("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, isc_boolean_t isassociated) { 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; CTRACE("query_addsoa"); /* * Initialization. */ eresult = ISC_R_SUCCESS; name = NULL; rdataset = NULL; node = 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) { eresult = DNS_R_SERVFAIL; goto cleanup; } if (WANTDNSSEC(client) && dns_db_issecure(db)) { sigrdataset = query_newrdataset(client); if (sigrdataset == NULL) { 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; dns_fixedname_init(&foundname); fname = dns_fixedname_name(&foundname); result = dns_db_find(db, name, version, dns_rdatatype_soa, client->query.dboptions, 0, &node, fname, 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! */ 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 != ISC_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; query_addrrset(client, &name, &rdataset, sigrdatasetp, NULL, DNS_SECTION_AUTHORITY); } 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; CTRACE("query_addns"); /* * Initialization. */ eresult = ISC_R_SUCCESS; name = NULL; rdataset = NULL; node = NULL; dns_fixedname_init(&foundname); fname = dns_fixedname_name(&foundname); /* * Get resources and make 'name' be the database origin. */ result = dns_message_gettempname(client->message, &name); if (result != ISC_R_SUCCESS) { CTRACE("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("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("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("query_addns: calling dns_db_find"); result = dns_db_find(db, name, NULL, dns_rdatatype_ns, client->query.dboptions, 0, &node, fname, rdataset, sigrdataset); CTRACE("query_addns: dns_db_find complete"); } if (result != ISC_R_SUCCESS) { CTRACE("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("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("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); } dns_rdataset_init(rdataset); rdatalist->type = dns_rdatatype_cname; rdatalist->covers = 0; 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_INIT(rdatalist->rdata); ISC_LIST_APPEND(rdatalist->rdata, rdata, link); RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) == ISC_R_SUCCESS); rdataset->trust = trust; 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); } /* * 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, isc_uint32_t ttl, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { isc_result_t result; dns_dbnode_t *node = NULL; rdataset->trust = dns_trust_secure; sigrdataset->trust = dns_trust_secure; /* * Save the updated secure state. Ignore failures. */ result = dns_db_findnode(db, name, ISC_TRUE, &node); if (result != ISC_R_SUCCESS) return; /* * Bound the validated ttls then minimise. */ if (sigrdataset->ttl > ttl) sigrdataset->ttl = ttl; if (rdataset->ttl > ttl) rdataset->ttl = ttl; if (rdataset->ttl > sigrdataset->ttl) rdataset->ttl = sigrdataset->ttl; else sigrdataset->ttl = rdataset->ttl; (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 ISC_FALSE if we have exhausted all the possible keys. */ static isc_boolean_t 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; isc_boolean_t secure = ISC_FALSE; if (!dns_rdataset_isassociated(keyrdataset)) { result = dns_db_findnode(db, &rrsig->signer, ISC_FALSE, &node); if (result != ISC_R_SUCCESS) return (ISC_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 (ISC_FALSE); if (keyrdataset->trust != dns_trust_secure) return (ISC_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 = ISC_TRUE; break; } dst_key_free(keyp); } return (secure); } static isc_boolean_t verify(dst_key_t *key, dns_name_t *name, dns_rdataset_t *rdataset, dns_rdata_t *rdata, isc_mem_t *mctx, isc_boolean_t acceptexpired) { isc_result_t result; dns_fixedname_t fixed; isc_boolean_t ignore = ISC_FALSE; dns_fixedname_init(&fixed); again: result = dns_dnssec_verify2(name, rdataset, key, ignore, mctx, rdata, NULL); if (result == DNS_R_SIGEXPIRED && acceptexpired) { ignore = ISC_TRUE; goto again; } if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) return (ISC_TRUE); return (ISC_FALSE); } /* * Validate the rdataset if possible with available records. */ static isc_boolean_t validate(ns_client_t *client, dns_db_t *db, dns_name_t *name, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { 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 (ISC_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 (ISC_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->mctx, client->view->acceptexpired)) { dst_key_free(&key); dns_rdataset_disassociate(&keyrdataset); mark_secure(client, db, name, rrsig.originalttl, rdataset, sigrdataset); return (ISC_TRUE); } dst_key_free(&key); } while (1); if (dns_rdataset_isassociated(&keyrdataset)) dns_rdataset_disassociate(&keyrdataset); } return (ISC_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; isc_boolean_t is_zone, use_zone; isc_buffer_t *dbuf; isc_result_t result; dns_dbversion_t *version; dns_zone_t *zone; isc_buffer_t b; CTRACE("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 = ISC_FALSE; use_zone = ISC_FALSE; /* * 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_find(db, client->query.qname, version, dns_rdatatype_ns, client->query.dboptions, client->now, &node, fname, rdataset, sigrdataset); if (result != DNS_R_DELEGATION) goto cleanup; if (USECACHE(client)) { query_keepname(client, fname, dbuf); zdb = db; zfname = fname; fname = NULL; zrdataset = rdataset; rdataset = NULL; zsigrdataset = sigrdataset; sigrdataset = NULL; dns_db_detachnode(db, &node); version = NULL; db = NULL; dns_db_attach(client->view->cachedb, &db); is_zone = ISC_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 = ISC_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 = ISC_TRUE; } else goto cleanup; } if (use_zone) { query_releasename(client, &fname); fname = zfname; zfname = NULL; /* * 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); rdataset = zrdataset; zrdataset = NULL; sigrdataset = zsigrdataset; zsigrdataset = NULL; } /* * 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 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("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, ISC_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, ISC_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, isc_boolean_t ispositive, isc_boolean_t 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; isc_boolean_t have_wname; int order; dns_fixedname_t cfixed; dns_name_t *cname; CTRACE("query_addwildcardproof"); fname = NULL; rdataset = NULL; sigrdataset = NULL; node = 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; dns_fixedname_init(&wfixed); wname = dns_fixedname_name(&wfixed); again: have_wname = ISC_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_find(db, name, version, dns_rdatatype_nsec, options, 0, &node, fname, rdataset, sigrdataset); if (node != NULL) dns_db_detachnode(db, &node); if (!dns_rdataset_isassociated(rdataset)) { /* * No NSEC proof available, return NSEC3 proofs instead. */ dns_fixedname_init(&cfixed); cname = dns_fixedname_name(&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_find(db, cname, version, dns_rdatatype_nsec, options, 0, NULL, fname, NULL, NULL); } /* * Add closest (provable) encloser NSEC3. */ query_findclosestnsec3(cname, db, NULL, client, rdataset, sigrdataset, fname, ISC_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, NULL, client, rdataset, sigrdataset, fname, ISC_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, NULL, 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 = ISC_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 = ISC_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, ISC_TRUE, ISC_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 query_resume(isc_task_t *task, isc_event_t *event) { dns_fetchevent_t *devent = (dns_fetchevent_t *)event; dns_fetch_t *fetch; ns_client_t *client; isc_boolean_t fetch_canceled, client_shuttingdown; isc_result_t result; isc_logcategory_t *logcategory = NS_LOGCATEGORY_QUERY_EERRORS; 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 = ISC_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 = ISC_TRUE; } UNLOCK(&client->query.fetchlock); INSIST(client->query.fetch == NULL); client->query.attributes &= ~NS_QUERYATTR_RECURSING; fetch = devent->fetch; devent->fetch = NULL; /* * 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) { if (devent->node != NULL) dns_db_detachnode(devent->db, &devent->node); if (devent->db != NULL) dns_db_detach(&devent->db); query_putrdataset(client, &devent->rdataset); if (devent->sigrdataset != NULL) query_putrdataset(client, &devent->sigrdataset); isc_event_free(&event); if (fetch_canceled) 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, ISC_FALSE); } } } dns_resolver_destroyfetch(&fetch); } 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, isc_boolean_t 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); 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 && (client->attributes & NS_CLIENTATTR_TCP) == 0) { 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); } } 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 == ISC_FALSE) ns_client_settimeout(client, 60); if ((client->attributes & NS_CLIENTATTR_TCP) == 0) peeraddr = &client->peeraddr; else peeraddr = NULL; result = dns_resolver_createfetch2(client->view->resolver, qname, qtype, qdomain, nameservers, NULL, peeraddr, client->message->id, client->query.fetchoptions, 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 isc_result_t rpz_ready(ns_client_t *client, dns_zone_t **zonep, dns_db_t **dbp, dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp) { REQUIRE(rdatasetp != NULL); rpz_clean(zonep, dbp, nodep, rdatasetp); if (*rdatasetp == NULL) { *rdatasetp = query_newrdataset(client); if (*rdatasetp == NULL) return (DNS_R_SERVFAIL); } return (ISC_R_SUCCESS); } static void rpz_st_clear(ns_client_t *client) { dns_rpz_st_t *st = client->query.rpz_st; rpz_clean(&st->m.zone, &st->m.db, &st->m.node, NULL); st->m.version = NULL; if (st->m.rdataset != NULL) query_putrdataset(client, &st->m.rdataset); 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; } /* * Get NS, A, or AAAA rrset for response policy zone checks. */ static isc_result_t rpz_rrset_find(ns_client_t *client, dns_rpz_type_t rpz_type, dns_name_t *name, dns_rdatatype_t type, dns_db_t **dbp, dns_dbversion_t *version, dns_rdataset_t **rdatasetp, isc_boolean_t resuming) { dns_rpz_st_t *st; isc_boolean_t is_zone; dns_dbnode_t *node; dns_fixedname_t fixed; dns_name_t *found; isc_result_t result; 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; *dbp = st->r.db; st->r.db = NULL; if (*rdatasetp != NULL) query_putrdataset(client, rdatasetp); *rdatasetp = st->r.r_rdataset; st->r.r_rdataset = NULL; result = st->r.r_result; if (result == DNS_R_DELEGATION) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, name, "rpz_rrset_find(1) ", result); st->m.policy = DNS_RPZ_POLICY_ERROR; result = DNS_R_SERVFAIL; } return (result); } result = rpz_ready(client, NULL, NULL, NULL, rdatasetp); if (result != ISC_R_SUCCESS) { st->m.policy = DNS_RPZ_POLICY_ERROR; return (result); } if (*dbp != NULL) { is_zone = ISC_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, rpz_type, name, "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; dns_fixedname_init(&fixed); found = dns_fixedname_name(&fixed); result = dns_db_find(*dbp, name, version, type, DNS_DBFIND_GLUEOK, client->now, &node, found, *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_find(*dbp, name, version, dns_rdatatype_ns, 0, client->now, &node, found, *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 { 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); } /* * Check the IP address in an A or AAAA rdataset against * the IP or NSIP response policy rules of a view. */ static isc_result_t rpz_rewrite_ip(ns_client_t *client, dns_rdataset_t *rdataset, dns_rpz_type_t rpz_type) { dns_rpz_st_t *st; dns_dbversion_t *version; dns_zone_t *zone; dns_db_t *db; dns_rpz_zone_t *rpz; isc_result_t result; st = client->query.rpz_st; if (st->m.rdataset == NULL) { st->m.rdataset = query_newrdataset(client); if (st->m.rdataset == NULL) return (DNS_R_SERVFAIL); } zone = NULL; db = NULL; for (rpz = ISC_LIST_HEAD(client->view->rpz_zones); rpz != NULL; rpz = ISC_LIST_NEXT(rpz, link)) { /* * Do not check policy zones that cannot replace a policy * already known to match. */ 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) continue; } /* * Find the database for this policy zone to get its radix tree. */ version = NULL; result = rpz_getdb(client, rpz_type, &rpz->origin, &zone, &db, &version); if (result != ISC_R_SUCCESS) { rpz_clean(&zone, &db, NULL, NULL); continue; } /* * Look for a better (e.g. longer prefix) hit for an IP address * in this rdataset in this radix tree than than the previous * hit, if any. Note the domain name and quality of the * best hit. */ (void)dns_db_rpz_findips(rpz, rpz_type, zone, db, version, rdataset, st, client->query.rpz_st->qname); rpz_clean(&zone, &db, NULL, NULL); } return (ISC_R_SUCCESS); } /* * Look for an A or AAAA rdataset * and check for IP or NSIP rewrite policy rules. */ static isc_result_t rpz_rewrite_rrset(ns_client_t *client, dns_rpz_type_t rpz_type, dns_rdatatype_t type, dns_name_t *name, dns_db_t **dbp, dns_dbversion_t *version, dns_rdataset_t **rdatasetp, isc_boolean_t resuming) { isc_result_t result; result = rpz_rrset_find(client, rpz_type, name, type, dbp, version, rdatasetp, resuming); switch (result) { case ISC_R_SUCCESS: result = rpz_rewrite_ip(client, *rdatasetp, rpz_type); 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: result = ISC_R_SUCCESS; break; case DNS_R_DELEGATION: case DNS_R_DUPLICATE: case DNS_R_DROP: break; case DNS_R_CNAME: case DNS_R_DNAME: rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, rpz_type, name, "NS address rewrite rrset ", result); result = ISC_R_SUCCESS; break; 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, rpz_type, name, "NS address rewrite rrset ", result); } break; } return (result); } /* * Look for both A and AAAA rdatasets * and check for IP or NSIP rewrite policy rules. * Look only for addresses that will be in the ANSWER section * when checking for IP rules. */ static isc_result_t rpz_rewrite_rrsets(ns_client_t *client, dns_rpz_type_t rpz_type, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t **rdatasetp, isc_boolean_t resuming) { dns_rpz_st_t *st; dns_dbversion_t *version; dns_db_t *ipdb; isc_result_t result; st = client->query.rpz_st; version = NULL; ipdb = NULL; if ((st->state & DNS_RPZ_DONE_IPv4) == 0 && ((rpz_type == DNS_RPZ_TYPE_NSIP) ? (st->state & DNS_RPZ_HAVE_NSIPv4) : (st->state & DNS_RPZ_HAVE_IP)) != 0 && (type == dns_rdatatype_any || type == dns_rdatatype_a)) { result = rpz_rewrite_rrset(client, rpz_type, dns_rdatatype_a, name, &ipdb, version, rdatasetp, resuming); if (result == ISC_R_SUCCESS) st->state |= DNS_RPZ_DONE_IPv4; } else { result = ISC_R_SUCCESS; } if (result == ISC_R_SUCCESS && ((rpz_type == DNS_RPZ_TYPE_NSIP) ? (st->state & DNS_RPZ_HAVE_NSIPv6) : (st->state & DNS_RPZ_HAVE_IP)) != 0 && (type == dns_rdatatype_any || type == dns_rdatatype_aaaa)) { result = rpz_rewrite_rrset(client, rpz_type, dns_rdatatype_aaaa, name, &ipdb, version, rdatasetp, resuming); } if (ipdb != NULL) dns_db_detach(&ipdb); return (result); } /* * Get the rrset from a response policy zone. */ static isc_result_t rpz_find(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qnamef, dns_name_t *sname, 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_rpz_policy_t policy; dns_fixedname_t fixed; dns_name_t *found; isc_result_t result; result = rpz_ready(client, zonep, dbp, nodep, rdatasetp); if (result != ISC_R_SUCCESS) { *policyp = DNS_RPZ_POLICY_ERROR; return (result); } /* * Try to get either a CNAME or the type of record demanded by the * request from the policy zone. */ *versionp = NULL; result = rpz_getdb(client, rpz_type, qnamef, zonep, dbp, versionp); if (result != ISC_R_SUCCESS) { *policyp = DNS_RPZ_POLICY_MISS; return (DNS_R_NXDOMAIN); } dns_fixedname_init(&fixed); found = dns_fixedname_name(&fixed); result = dns_db_find(*dbp, qnamef, *versionp, dns_rdatatype_any, 0, client->now, nodep, found, *rdatasetp, NULL); 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) { dns_db_detachnode(*dbp, nodep); rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, qnamef, "allrdatasets()", result); *policyp = DNS_RPZ_POLICY_ERROR; 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, rpz_type, qnamef, "rdatasetiter", result); *policyp = DNS_RPZ_POLICY_ERROR; 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_find(*dbp, qnamef, *versionp, qtype, 0, client->now, nodep, found, *rdatasetp, NULL); } } switch (result) { case ISC_R_SUCCESS: if ((*rdatasetp)->type != dns_rdatatype_cname) { policy = DNS_RPZ_POLICY_RECORD; } else { policy = dns_rpz_decode_cname(*rdatasetp, sname); if ((policy == DNS_RPZ_POLICY_RECORD || policy == DNS_RPZ_POLICY_WILDCNAME) && qtype != dns_rdatatype_cname && qtype != dns_rdatatype_any) result = DNS_R_CNAME; } break; case DNS_R_DNAME: /* * DNAME policy RRs have very few if any uses that are not * better served with simple wildcards. Making the 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(). So fall through to treat them as NODATA. */ case DNS_R_NXRRSET: policy = DNS_RPZ_POLICY_NODATA; break; case DNS_R_NXDOMAIN: case DNS_R_EMPTYNAME: /* * If we don't get a qname hit, * see if it is worth looking for other types. */ dns_db_rpz_enabled(*dbp, client->query.rpz_st); dns_db_detach(dbp); dns_zone_detach(zonep); policy = DNS_RPZ_POLICY_MISS; break; default: dns_db_detach(dbp); dns_zone_detach(zonep); rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, qnamef, "", result); policy = DNS_RPZ_POLICY_ERROR; result = DNS_R_SERVFAIL; break; } *policyp = policy; return (result); } /* * Build and look for a QNAME or NSDNAME owner name in a response policy zone. */ static isc_result_t rpz_rewrite_name(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, dns_rpz_type_t rpz_type, dns_rdataset_t **rdatasetp) { dns_rpz_st_t *st; dns_rpz_zone_t *rpz; dns_fixedname_t prefixf, rpz_qnamef; dns_name_t *prefix, *suffix, *rpz_qname; dns_zone_t *zone; dns_db_t *db; dns_dbversion_t *version; dns_dbnode_t *node; dns_rpz_policy_t policy; unsigned int labels; isc_result_t result; st = client->query.rpz_st; zone = NULL; db = NULL; node = NULL; for (rpz = ISC_LIST_HEAD(client->view->rpz_zones); rpz != NULL; rpz = ISC_LIST_NEXT(rpz, link)) { /* * Do not check policy zones that cannot replace a policy * already known to match. */ 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) continue; } /* * Construct the policy's owner name. */ dns_fixedname_init(&prefixf); prefix = dns_fixedname_name(&prefixf); dns_name_split(qname, 1, prefix, NULL); if (rpz_type == DNS_RPZ_TYPE_NSDNAME) suffix = &rpz->nsdname; else suffix = &rpz->origin; dns_fixedname_init(&rpz_qnamef); rpz_qname = dns_fixedname_name(&rpz_qnamef); for (;;) { result = dns_name_concatenate(prefix, suffix, rpz_qname, NULL); if (result == ISC_R_SUCCESS) break; INSIST(result == DNS_R_NAMETOOLONG); labels = dns_name_countlabels(prefix); if (labels < 2) { rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, suffix, "concatentate() ", result); return (ISC_R_SUCCESS); } if (labels+1 == dns_name_countlabels(qname)) { rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, rpz_type, suffix, "concatentate() ", result); } dns_name_split(prefix, labels - 1, NULL, prefix); } /* * See if the policy record exists. */ result = rpz_find(client, qtype, rpz_qname, qname, rpz_type, &zone, &db, &version, &node, rdatasetp, &policy); switch (result) { case DNS_R_NXDOMAIN: case DNS_R_EMPTYNAME: break; case DNS_R_SERVFAIL: rpz_clean(&zone, &db, &node, rdatasetp); st->m.policy = DNS_RPZ_POLICY_ERROR; return (DNS_R_SERVFAIL); default: /* * We are dealing with names here. * With more than one applicable policy, prefer * the earliest configured policy, * QNAME over IP over NSDNAME over NSIP, * and the smallest name. * Because of the testing above, * 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(rpz_qname, st->qname)))) continue; /* * Merely log DNS_RPZ_POLICY_DISABLED hits. */ if (rpz->policy == DNS_RPZ_POLICY_DISABLED) { rpz_log_rewrite(client, "disabled ", policy, rpz_type, rpz_qname); continue; } rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset); st->m.rpz = rpz; st->m.type = rpz_type; st->m.prefix = 0; st->m.policy = policy; st->m.result = result; dns_name_copy(rpz_qname, st->qname, NULL); if (*rdatasetp != NULL && dns_rdataset_isassociated(*rdatasetp)) { dns_rdataset_t *trdataset; trdataset = st->m.rdataset; st->m.rdataset = *rdatasetp; *rdatasetp = trdataset; st->m.ttl = st->m.rdataset->ttl; } else { st->m.ttl = DNS_RPZ_TTL_DEFAULT; } st->m.node = node; node = NULL; st->m.db = db; db = NULL; st->m.version = version; st->m.zone = zone; zone = NULL; } } rpz_clean(&zone, &db, &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; st = client->query.rpz_st; if (str != NULL) rpz_log_fail(client, level, DNS_RPZ_TYPE_NSIP, nsname, 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--; } /* * 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, isc_boolean_t resuming) { dns_rpz_st_t *st; dns_rdataset_t *rdataset; dns_fixedname_t nsnamef; dns_name_t *nsname; isc_boolean_t ck_ip; isc_result_t result; st = client->query.rpz_st; if (st == NULL) { st = isc_mem_get(client->mctx, sizeof(*st)); if (st == NULL) return (ISC_R_NOMEMORY); st->state = 0; memset(&st->m, 0, sizeof(st->m)); st->m.type = DNS_RPZ_TYPE_BAD; st->m.policy = DNS_RPZ_POLICY_MISS; memset(&st->r, 0, sizeof(st->r)); memset(&st->q, 0, sizeof(st->q)); dns_fixedname_init(&st->_qnamef); dns_fixedname_init(&st->_r_namef); dns_fixedname_init(&st->_fnamef); st->qname = dns_fixedname_name(&st->_qnamef); st->r_name = dns_fixedname_name(&st->_r_namef); st->fname = dns_fixedname_name(&st->_fnamef); 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: ck_ip = ISC_TRUE; 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: ck_ip = ISC_FALSE; break; case DNS_R_DELEGATION: case ISC_R_NOTFOUND: return (ISC_R_SUCCESS); case ISC_R_FAILURE: case ISC_R_TIMEDOUT: case DNS_R_BROKENCHAIN: rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, DNS_RPZ_TYPE_QNAME, client->query.qname, "stop on qresult in rpz_rewrite()", qresult); return (ISC_R_SUCCESS); default: rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, DNS_RPZ_TYPE_QNAME, client->query.qname, "stop on unrecognized qresult in rpz_rewrite()", qresult); return (ISC_R_SUCCESS); } rdataset = NULL; if ((st->state & DNS_RPZ_DONE_QNAME) == 0) { /* * Check rules for the query name if this it the first time * for the current qname, i.e. we've not been recursing. * There is a first time for each name in a CNAME chain. */ result = rpz_rewrite_name(client, qtype, client->query.qname, DNS_RPZ_TYPE_QNAME, &rdataset); if (result != ISC_R_SUCCESS) goto cleanup; st->r.label = dns_name_countlabels(client->query.qname); st->state &= ~(DNS_RPZ_DONE_QNAME_IP | DNS_RPZ_DONE_IPv4); st->state |= DNS_RPZ_DONE_QNAME; } /* * Check known IP addresses for the query name. * 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 && (st->state & DNS_RPZ_HAVE_IP) != 0 && ck_ip) { result = rpz_rewrite_rrsets(client, DNS_RPZ_TYPE_IP, client->query.qname, qtype, &rdataset, resuming); if (result != ISC_R_SUCCESS) goto cleanup; st->state &= ~DNS_RPZ_DONE_IPv4; st->state |= DNS_RPZ_DONE_QNAME_IP; } /* * Stop looking for rules if there are none of the other kinds. */ if ((st->state & (DNS_RPZ_HAVE_NSIPv4 | DNS_RPZ_HAVE_NSIPv6 | DNS_RPZ_HAVE_NSDNAME)) == 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 > 1) { /* * 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, DNS_RPZ_TYPE_NSDNAME, nsname, dns_rdatatype_ns, &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: 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 db_find() "); continue; default: rpz_rewrite_ns_skip(client, nsname, result, DNS_RPZ_INFO_LEVEL, "unrecognized NS db_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, DNS_RPZ_TYPE_NSIP, nsname, "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 && (st->state & DNS_RPZ_HAVE_NSDNAME) != 0) { result = rpz_rewrite_name(client, qtype, &ns.name, DNS_RPZ_TYPE_NSDNAME, &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_rrsets(client, DNS_RPZ_TYPE_NSIP, &ns.name, dns_rdatatype_any, &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--; } /* * Use the best, if any, hit. */ 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) rpz_log_rewrite(client, "", st->m.policy, st->m.type, st->qname); rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset); } if (st->m.policy == DNS_RPZ_POLICY_ERROR) { 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); } /* * 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; 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, "", st->m.policy, st->m.type, st->qname); ns_client_qnamereplace(client, fname); /* * Turn off DNSSEC because the results of a * response policy zone cannot verify. */ client->attributes &= ~NS_CLIENTATTR_WANTDNSSEC; return (ISC_R_SUCCESS); } #define MAX_RESTARTS 16 #define QUERY_ERROR(r) \ do { \ eresult = r; \ want_restart = ISC_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); memcpy(&ina.s_addr, rdata->data, 4); isc_netaddr_fromin(netaddr, &ina); return (ISC_R_SUCCESS); case dns_rdatatype_aaaa: INSIST(rdata->length == 16); memcpy(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("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_REQUIREDGLUE; } } #define NS_NAME_INIT(A,B) \ { \ DNS_NAME_MAGIC, \ A, sizeof(A), sizeof(B), \ DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE, \ B, NULL, { (void *)-1, (void *)-1}, \ {NULL, NULL} \ } 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[] = { NS_NAME_INIT(inaddr10, inaddr10_offsets), NS_NAME_INIT(inaddr16172, inaddr172_offsets), NS_NAME_INIT(inaddr17172, inaddr172_offsets), NS_NAME_INIT(inaddr18172, inaddr172_offsets), NS_NAME_INIT(inaddr19172, inaddr172_offsets), NS_NAME_INIT(inaddr20172, inaddr172_offsets), NS_NAME_INIT(inaddr21172, inaddr172_offsets), NS_NAME_INIT(inaddr22172, inaddr172_offsets), NS_NAME_INIT(inaddr23172, inaddr172_offsets), NS_NAME_INIT(inaddr24172, inaddr172_offsets), NS_NAME_INIT(inaddr25172, inaddr172_offsets), NS_NAME_INIT(inaddr26172, inaddr172_offsets), NS_NAME_INIT(inaddr27172, inaddr172_offsets), NS_NAME_INIT(inaddr28172, inaddr172_offsets), NS_NAME_INIT(inaddr29172, inaddr172_offsets), NS_NAME_INIT(inaddr30172, inaddr172_offsets), NS_NAME_INIT(inaddr31172, inaddr172_offsets), NS_NAME_INIT(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 = NS_NAME_INIT(prisoner_data, prisoner_offsets); static dns_name_t hostmaster = NS_NAME_INIT(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, isc_boolean_t exact, dns_name_t *found) { unsigned char salt[256]; size_t salt_length; isc_uint16_t iterations; isc_result_t result; unsigned int dboptions; dns_fixedname_t fixed; dns_hash_t hash; dns_name_t name; int order; unsigned int count; dns_rdata_nsec3_t nsec3; dns_rdata_t rdata = DNS_RDATA_INIT; isc_boolean_t optout; 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); /* * 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_find(db, dns_fixedname_name(&fixed), version, dns_rdatatype_nsec3, dboptions, client->now, NULL, fname, 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 = ISC_TF((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0); if (found != NULL && optout && dns_name_fullcompare(&name, dns_db_origin(db), &order, &count) == dns_namereln_subdomain) { dns_rdataset_disassociate(rdataset); if (dns_rdataset_isassociated(sigrdataset)) dns_rdataset_disassociate(sigrdataset); count = dns_name_countlabels(&name) - 1; dns_name_getlabelsequence(&name, 1, count, &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 != NULL) dns_name_copy(&name, found, NULL); return; } #ifdef ALLOW_FILTER_AAAA_ON_V4 static isc_boolean_t is_v4_client(ns_client_t *client) { if (isc_sockaddr_pf(&client->peeraddr) == AF_INET) return (ISC_TRUE); if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr)) return (ISC_TRUE); return (ISC_FALSE); } #endif static isc_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; isc_uint32_t ttl = ISC_UINT32_MAX; result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) goto cleanup; dns_rdataset_init(&rdataset); 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 isc_boolean_t 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; isc_boolean_t *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 (ISC_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(isc_boolean_t) * 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]) { client->query.dns64_aaaaok = aaaaok; client->query.dns64_aaaaoklen = count; break; } } if (i == count && aaaaok != NULL) isc_mem_put(client->mctx, aaaaok, sizeof(isc_boolean_t) * count); return (ISC_TRUE); } if (aaaaok != NULL) isc_mem_put(client->mctx, aaaaok, sizeof(isc_boolean_t) * count); return (ISC_FALSE); } /* * 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; 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; isc_boolean_t want_restart, authoritative, is_zone, need_wildcardproof; isc_boolean_t is_staticstub_zone; unsigned int n, nlabels; dns_namereln_t namereln; int order; isc_buffer_t *dbuf; isc_buffer_t b; isc_result_t result, eresult; 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; isc_boolean_t empty_wild; dns_rdataset_t *noqname; dns_rpz_st_t *rpz_st; isc_boolean_t resuming; int line = -1; isc_boolean_t dns64_exclude, dns64; CTRACE("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 = ISC_FALSE; empty_wild = ISC_FALSE; dns64_exclude = dns64 = ISC_FALSE; options = 0; resuming = ISC_FALSE; is_zone = ISC_FALSE; is_staticstub_zone = ISC_FALSE; if (event != NULL) { /* * We're returning from recursion. Restore the query context * and resume. */ want_restart = ISC_FALSE; rpz_st = client->query.rpz_st; if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) != 0) { is_zone = rpz_st->q.is_zone; authoritative = rpz_st->q.authoritative; zone = rpz_st->q.zone; rpz_st->q.zone = NULL; node = rpz_st->q.node; rpz_st->q.node = NULL; db = rpz_st->q.db; rpz_st->q.db = NULL; rdataset = rpz_st->q.rdataset; rpz_st->q.rdataset = NULL; sigrdataset = rpz_st->q.sigrdataset; rpz_st->q.sigrdataset = NULL; qtype = rpz_st->q.qtype; rpz_st->r.db = event->db; if (event->node != NULL) dns_db_detachnode(event->db, &event->node); rpz_st->r.r_type = event->qtype; rpz_st->r.r_rdataset = event->rdataset; query_putrdataset(client, &event->sigrdataset); } else { authoritative = ISC_FALSE; qtype = event->qtype; db = event->db; node = event->node; rdataset = event->rdataset; sigrdataset = event->sigrdataset; } 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 = ISC_TRUE; } if (DNS64EXCLUDE(client)) { client->query.attributes &= ~NS_QUERYATTR_DNS64EXCLUDE; dns64_exclude = ISC_TRUE; } /* * We'll need some resources... */ dbuf = query_getnamebuf(client); if (dbuf == NULL) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } fname = query_newname(client, dbuf, &b); if (fname == NULL) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) != 0) { tname = rpz_st->fname; } else { tname = dns_fixedname_name(&event->foundname); } result = dns_name_copy(tname, fname, NULL); if (result != ISC_R_SUCCESS) { 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; isc_event_free(ISC_EVENT_PTR(&event)); } else { result = event->result; } resuming = ISC_TRUE; goto resume; } /* * Not returning from recursion. */ /* * 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("query_find: restart"); want_restart = ISC_FALSE; authoritative = ISC_FALSE; version = NULL; need_wildcardproof = ISC_FALSE; if (client->view->checknames && !dns_rdata_checkowner(client->query.qname, client->message->rdclass, qtype, ISC_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; } /* * 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 ((result != ISC_R_SUCCESS || !is_zone) && !RECURSIONOK(client) && (options & DNS_GETDB_NOEXACT) != 0 && qtype == dns_rdatatype_ds) { /* * Look to see if we are authoritative for the * child zone if the query type is DS. */ dns_db_t *tdb = NULL; dns_zone_t *tzone = NULL; dns_dbversion_t *tversion = NULL; isc_result_t tresult; 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 = tversion; db = tdb; zone = tzone; is_zone = ISC_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 QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } is_staticstub_zone = ISC_FALSE; if (is_zone) { authoritative = ISC_TRUE; if (zone != NULL && dns_zone_gettype(zone) == dns_zone_staticstub) is_staticstub_zone = ISC_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 = ISC_TRUE; } db_find: CTRACE("query_find: db_find"); /* * We'll need some resources... */ dbuf = query_getnamebuf(client); if (dbuf == NULL) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } fname = query_newname(client, dbuf, &b); rdataset = query_newrdataset(client); if (fname == NULL || rdataset == NULL) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } if (WANTDNSSEC(client) && (!is_zone || dns_db_issecure(db))) { sigrdataset = query_newrdataset(client); if (sigrdataset == NULL) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } /* * Now look for an answer in the database. */ result = dns_db_find(db, client->query.qname, version, type, client->query.dboptions, client->now, &node, fname, rdataset, sigrdataset); resume: CTRACE("query_find: resume"); if (!ISC_LIST_EMPTY(client->view->rpz_zones) && RECURSIONOK(client) && !RECURSING(client) && (!WANTDNSSEC(client) || sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)) && (client->query.rpz_st == NULL || (client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0) && !dns_name_equal(client->query.qname, dns_rootname)) { isc_result_t rresult; rresult = rpz_rewrite(client, qtype, result, resuming); rpz_st = client->query.rpz_st; switch (rresult) { case ISC_R_SUCCESS: break; 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; rpz_st->q.zone = zone; zone = NULL; rpz_st->q.db = db; db = NULL; rpz_st->q.node = node; node = NULL; rpz_st->q.rdataset = rdataset; rdataset = NULL; rpz_st->q.sigrdataset = sigrdataset; sigrdataset = NULL; 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_ERROR) { if (rpz_st->m.type == DNS_RPZ_TYPE_QNAME) { result = dns_name_copy(client->query.qname, fname, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); } rpz_clean(&zone, &db, &node, NULL); if (rpz_st->m.rdataset != NULL) { query_putrdataset(client, &rdataset); rdataset = rpz_st->m.rdataset; rpz_st->m.rdataset = NULL; } else if (rdataset != NULL && dns_rdataset_isassociated(rdataset)) { dns_rdataset_disassociate(rdataset); } node = rpz_st->m.node; rpz_st->m.node = NULL; db = rpz_st->m.db; rpz_st->m.db = NULL; version = rpz_st->m.version; rpz_st->m.version = NULL; zone = rpz_st->m.zone; rpz_st->m.zone = NULL; switch (rpz_st->m.policy) { case DNS_RPZ_POLICY_NXDOMAIN: result = DNS_R_NXDOMAIN; break; case DNS_RPZ_POLICY_NODATA: result = DNS_R_NXRRSET; break; case DNS_RPZ_POLICY_RECORD: result = rpz_st->m.result; if (type == dns_rdatatype_any && result != DNS_R_CNAME && dns_rdataset_isassociated(rdataset)) dns_rdataset_disassociate(rdataset); 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 = ISC_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 = ISC_TRUE; goto cleanup; default: INSIST(0); } /* * Turn off DNSSEC because the results of a * response policy zone cannot verify. */ client->attributes &= ~NS_CLIENTATTR_WANTDNSSEC; query_putrdataset(client, &sigrdataset); is_zone = ISC_TRUE; rpz_log_rewrite(client, "", rpz_st->m.policy, rpz_st->m.type, rpz_st->qname); } } 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 = ISC_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_find(db, dns_rootname, NULL, dns_rdatatype_ns, 0, client->now, &node, fname, 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)) { 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. */ QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } /* * XXXRTH We should trigger root server priming here. */ /* FALLTHROUGH */ case DNS_R_DELEGATION: authoritative = ISC_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 = tversion; db = tdb; zone = tzone; authoritative = ISC_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)) { dns_fixedname_t fixed; 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. */ client->query.gluedb = db; client->query.isreferral = ISC_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.gluedb = NULL; 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); zdb = db; zfname = fname; fname = NULL; zrdataset = rdataset; rdataset = NULL; zsigrdataset = sigrdataset; sigrdataset = NULL; dns_db_detachnode(db, &node); zversion = version; version = NULL; db = NULL; dns_db_attach(client->view->cachedb, &db); is_zone = ISC_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); fname = zfname; zfname = NULL; /* * 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); rdataset = zrdataset; zrdataset = NULL; sigrdataset = zsigrdataset; zsigrdataset = NULL; version = zversion; zversion = NULL; /* * 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! */ 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 { dns_fixedname_t fixed; 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.gluedb = zdb; client->query.isreferral = ISC_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.gluedb = NULL; client->query.attributes &= ~NS_QUERYATTR_CACHEGLUEOK; 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); rdataset = client->query.dns64_aaaa; sigrdataset = client->query.dns64_sigaaaa; client->query.dns64_aaaa = NULL; client->query.dns64_sigaaaa = NULL; if (fname == NULL) { dbuf = query_getnamebuf(client); if (dbuf == NULL) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } fname = query_newname(client, dbuf, &b); if (fname == NULL) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } dns_name_copy(client->query.qname, fname, NULL); dns64 = ISC_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. */ INSIST(client->query.dns64_aaaa == NULL); INSIST(client->query.dns64_sigaaaa == NULL); client->query.dns64_aaaa = rdataset; client->query.dns64_sigaaaa = sigrdataset; client->query.dns64_ttl = dns64_ttl(db, version); query_releasename(client, &fname); dns_db_detachnode(db, &node); rdataset = NULL; sigrdataset = NULL; type = qtype = dns_rdatatype_a; dns64 = ISC_TRUE; goto db_find; } /* * Look for a NSEC3 record if we don't have a NSEC record. */ nxrrset_rrsig: if (!dns_rdataset_isassociated(rdataset) && WANTDNSSEC(client)) { if ((fname->attributes & DNS_NAMEATTR_WILDCARD) == 0) { dns_name_t *found; dns_name_t *qname; dns_fixedname_init(&fixed); found = dns_fixedname_name(&fixed); qname = client->query.qname; query_findclosestnsec3(qname, db, version, client, rdataset, sigrdataset, fname, ISC_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)) { 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) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } /* * 'nearest' doesn't exist so * 'exist' is set to ISC_FALSE. */ query_findclosestnsec3(found, db, version, client, rdataset, sigrdataset, fname, ISC_FALSE, NULL); } } else { query_releasename(client, &fname); query_addwildcardproof(client, db, version, client->query.qname, ISC_FALSE, ISC_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. */ result = query_addsoa(client, db, version, ISC_UINT32_MAX, dns_rdataset_isassociated(rdataset)); 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 = ISC_TRUE; /* FALLTHROUGH */ case DNS_R_NXDOMAIN: INSIST(is_zone); 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. 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. */ if (qtype == dns_rdatatype_soa && zone != NULL && dns_zone_getzeronosoattl(zone)) result = query_addsoa(client, db, version, 0, dns_rdataset_isassociated(rdataset)); else result = query_addsoa(client, db, version, ISC_UINT32_MAX, dns_rdataset_isassociated(rdataset)); 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, ISC_FALSE, ISC_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: case DNS_R_NCACHENXRRSET: ncache_nxrrset: INSIST(!is_zone); authoritative = ISC_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); rdataset = client->query.dns64_aaaa; sigrdataset = client->query.dns64_sigaaaa; client->query.dns64_aaaa = NULL; client->query.dns64_sigaaaa = NULL; if (fname == NULL) { dbuf = query_getnamebuf(client); if (dbuf == NULL) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } fname = query_newname(client, dbuf, &b); if (fname == NULL) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } dns_name_copy(client->query.qname, fname, NULL); dns64 = ISC_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. */ INSIST(client->query.dns64_aaaa == NULL); INSIST(client->query.dns64_sigaaaa == NULL); client->query.dns64_aaaa = rdataset; client->query.dns64_sigaaaa = sigrdataset; /* * 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; query_releasename(client, &fname); dns_db_detachnode(db, &node); rdataset = NULL; sigrdataset = NULL; fname = NULL; type = qtype = dns_rdatatype_a; dns64 = ISC_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!). */ 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: /* * 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 = ISC_TRUE; } if (NOQNAME(rdataset) && WANTDNSSEC(client)) noqname = rdataset; else noqname = NULL; 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 = ISC_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 = ISC_TRUE; } 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 * <found name prefix>.<dname target> */ dns_fixedname_init(&fixed); prefix = dns_fixedname_name(&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 * <old qname> <dname ttl> CNAME <new qname> * with <dname trust value> * * 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 = ISC_TRUE; if (!WANTRECURSION(client)) options |= DNS_GETDB_NOLOG; goto addauth; default: /* * Something has gone wrong. */ 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 = ISC_TRUE; } #ifdef ALLOW_FILTER_AAAA_ON_V4 if (client->view->v4_aaaa != dns_v4_aaaa_ok && is_v4_client(client) && ns_client_checkaclsilent(client, NULL, client->view->v4_aaaa_acl, ISC_TRUE) == ISC_R_SUCCESS) client->filter_aaaa = client->view->v4_aaaa; else client->filter_aaaa = dns_v4_aaaa_ok; #endif if (type == dns_rdatatype_any) { #ifdef ALLOW_FILTER_AAAA_ON_V4 isc_boolean_t have_aaaa, have_a, have_sig; /* * The filter-aaaa-on-v4 option should * suppress AAAAs for IPv4 clients if there is an A. * 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 = ISC_FALSE; have_a = !authoritative; have_sig = ISC_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) { 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_ON_V4 /* * Notice the presence of A and AAAAs so * that AAAAs can be hidden from IPv4 clients. */ if (client->filter_aaaa != dns_v4_aaaa_ok) { if (rdataset->type == dns_rdatatype_aaaa) have_aaaa = ISC_TRUE; else if (rdataset->type == dns_rdatatype_a) have_a = ISC_TRUE; } #endif 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 ((qtype == dns_rdatatype_any || rdataset->type == qtype) && rdataset->type != 0) { #ifdef ALLOW_FILTER_AAAA_ON_V4 if (dns_rdatatype_isdnssec(rdataset->type)) have_sig = ISC_TRUE; #endif if (NOQNAME(rdataset) && WANTDNSSEC(client)) noqname = rdataset; else noqname = NULL; 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_ON_V4 /* * Filter AAAAs if there is an A and there is no signature * or we are supposed to break DNSSEC. */ if (client->filter_aaaa == dns_v4_aaaa_break_dnssec) client->attributes |= NS_CLIENTATTR_FILTER_AAAA; else if (client->filter_aaaa != dns_v4_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 = ISC_FALSE; dns_rdatasetiter_destroy(&rdsiter); client->attributes &= ~NS_CLIENTATTR_RA; goto addauth; } if (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 result = DNS_R_SERVFAIL; } dns_rdatasetiter_destroy(&rdsiter); if (result != ISC_R_NOMORE) { QUERY_ERROR(DNS_R_SERVFAIL); goto cleanup; } } else { /* * This is the "normal" case -- an ordinary question to which * we know the answer. */ #ifdef ALLOW_FILTER_AAAA_ON_V4 /* * 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_v4_aaaa_break_dnssec || (client->filter_aaaa == dns_v4_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. */ 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 /* * Check to see if the AAAA RRset has non-excluded addresses * in it. If not look for a A RRset. */ 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_aaaa = rdataset; client->query.dns64_sigaaaa = sigrdataset; client->query.dns64_ttl = rdataset->ttl; query_releasename(client, &fname); dns_db_detachnode(db, &node); rdataset = NULL; sigrdataset = NULL; type = qtype = dns_rdatatype_a; dns64_exclude = dns64 = ISC_TRUE; goto db_find; } if (sigrdataset != NULL) sigrdatasetp = &sigrdataset; else sigrdatasetp = NULL; if (NOQNAME(rdataset) && WANTDNSSEC(client)) noqname = rdataset; else noqname = NULL; /* * BIND 8 priming queries need the additional section. */ if (is_zone && qtype == dns_rdatatype_ns && dns_name_equal(client->query.qname, dns_rootname)) client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; if (dns64) { qtype = type = dns_rdatatype_aaaa; result = query_dns64(client, &fname, rdataset, sigrdataset, dbuf, DNS_SECTION_ANSWER); 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, ISC_FALSE); 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 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("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 (!((qtype == dns_rdatatype_ns || qtype == dns_rdatatype_any) && dns_name_equal(client->query.qname, dns_db_origin(db)))) (void)query_addns(client, db, version); } else if (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), ISC_TRUE, ISC_FALSE); cleanup: CTRACE("query_find: cleanup"); /* * General cleanup. */ rpz_st = client->query.rpz_st; if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) == 0) { rpz_clean(&rpz_st->m.zone, &rpz_st->m.db, &rpz_st->m.node, &rpz_st->m.rdataset); 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) isc_event_free(ISC_EVENT_PTR(&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))) { if (eresult == DNS_R_DUPLICATE || eresult == DNS_R_DROP) { /* * This was a duplicate query that we are * recursing on. Don't send a response now. * The original query 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 == ISC_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("query_find: done"); return (eresult); } 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]; 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)); ns_client_log(client, NS_LOGCATEGORY_QUERIES, NS_LOGMODULE_QUERY, level, "query: %s %s %s %s%s%s%s%s%s (%s)", namebuf, classname, typename, WANTRECURSION(client) ? "+" : "-", (client->signer != NULL) ? "S": "", (client->opt != NULL) ? "E" : "", ((client->attributes & NS_CLIENTATTR_TCP) != 0) ? "T" : "", ((extflags & DNS_MESSAGEEXTFLAG_DO) != 0) ? "D" : "", ((flags & DNS_MESSAGEFLAG_CD) != 0) ? "C" : "", 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_EERRORS, 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; isc_boolean_t want_ad; CTRACE("ns_query_start"); /* * Test only. */ if (ns_g_clienttest && (client->attributes & NS_CLIENTATTR_TCP) == 0) RUNTIME_CHECK(ns_client_replace(client) == ISC_R_SUCCESS); /* * 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 (client->opt != NULL) client->opt->ttl &= ~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; if (client->view->minimalresponses) client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); 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); } 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; } /* * 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 multiple question queries, since edns1 is dead. */ if (message->counts[DNS_SECTION_QUESTION] > 1) { query_error(client, DNS_R_FORMERR, __LINE__); return; } /* * Check for meta-queries like IXFR and AXFR. */ rdataset = ISC_LIST_HEAD(client->query.qname->list); INSIST(rdataset != NULL); qtype = rdataset->type; dns_rdatatypestats_increment(ns_g_server->rcvquerystats, qtype); 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 DNSKEY and DS queries. */ if (qtype == dns_rdatatype_dnskey || qtype == dns_rdatatype_ds) client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); /* * Turn on minimal responses for EDNS/UDP bufsize 512 queries. */ if (client->opt != NULL && client->udpsize <= 512U && (client->attributes & NS_CLIENTATTR_TCP) == 0) 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 'want_ad' 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) want_ad = ISC_TRUE; else want_ad = ISC_FALSE; /* * This is an ordinary query. */ result = dns_message_reply(message, ISC_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) || want_ad) message->flags |= DNS_MESSAGEFLAG_AD; qclient = NULL; ns_client_attach(client, &qclient); (void)query_find(qclient, NULL, qtype); }