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