summaryrefslogtreecommitdiffstats
path: root/lib/dns/xfrin.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:59:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:59:48 +0000
commit3b9b6d0b8e7f798023c9d109c490449d528fde80 (patch)
tree2e1c188dd7b8d7475cd163de9ae02c428343669b /lib/dns/xfrin.c
parentInitial commit. (diff)
downloadbind9-0cd617f6bad00b68e380aeb0024ef4dc1985191c.tar.xz
bind9-0cd617f6bad00b68e380aeb0024ef4dc1985191c.zip
Adding upstream version 1:9.18.19.upstream/1%9.18.19upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/dns/xfrin.c')
-rw-r--r--lib/dns/xfrin.c2034
1 files changed, 2034 insertions, 0 deletions
diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c
new file mode 100644
index 0000000..393b557
--- /dev/null
+++ b/lib/dns/xfrin.c
@@ -0,0 +1,2034 @@
+/*
+ * 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 <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/catz.h>
+#include <dns/db.h>
+#include <dns/diff.h>
+#include <dns/events.h>
+#include <dns/journal.h>
+#include <dns/log.h>
+#include <dns/message.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/result.h>
+#include <dns/soa.h>
+#include <dns/transport.h>
+#include <dns/tsig.h>
+#include <dns/view.h>
+#include <dns/xfrin.h>
+#include <dns/zone.h>
+
+#include <dst/dst.h>
+
+/*
+ * Incoming AXFR and IXFR.
+ */
+
+/*%
+ * It would be non-sensical (or at least obtuse) to use FAIL() with an
+ * ISC_R_SUCCESS code, but the test is there to keep the Solaris compiler
+ * from complaining about "end-of-loop code not reached".
+ */
+#define FAIL(code) \
+ do { \
+ result = (code); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/*%
+ * The states of the *XFR state machine. We handle both IXFR and AXFR
+ * with a single integrated state machine because they cannot be distinguished
+ * immediately - an AXFR response to an IXFR request can only be detected
+ * when the first two (2) response RRs have already been received.
+ */
+typedef enum {
+ XFRST_SOAQUERY,
+ XFRST_GOTSOA,
+ XFRST_INITIALSOA,
+ XFRST_FIRSTDATA,
+ XFRST_IXFR_DELSOA,
+ XFRST_IXFR_DEL,
+ XFRST_IXFR_ADDSOA,
+ XFRST_IXFR_ADD,
+ XFRST_IXFR_END,
+ XFRST_AXFR,
+ XFRST_AXFR_END
+} xfrin_state_t;
+
+/*%
+ * Incoming zone transfer context.
+ */
+
+struct dns_xfrin_ctx {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ dns_zone_t *zone;
+
+ isc_refcount_t references;
+
+ isc_nm_t *netmgr;
+
+ isc_refcount_t connects; /*%< Connect in progress */
+ isc_refcount_t sends; /*%< Send in progress */
+ isc_refcount_t recvs; /*%< Receive in progress */
+
+ atomic_bool shuttingdown;
+
+ isc_result_t shutdown_result;
+
+ dns_name_t name; /*%< Name of zone to transfer */
+ dns_rdataclass_t rdclass;
+
+ dns_messageid_t id;
+
+ /*%
+ * Requested transfer type (dns_rdatatype_axfr or
+ * dns_rdatatype_ixfr). The actual transfer type
+ * may differ due to IXFR->AXFR fallback.
+ */
+ dns_rdatatype_t reqtype;
+
+ isc_sockaddr_t primaryaddr;
+ isc_sockaddr_t sourceaddr;
+
+ isc_nmhandle_t *handle;
+ isc_nmhandle_t *readhandle;
+ isc_nmhandle_t *sendhandle;
+
+ /*% Buffer for IXFR/AXFR request message */
+ isc_buffer_t qbuffer;
+ unsigned char qbuffer_data[512];
+
+ /*%
+ * Whether the zone originally had a database attached at the time this
+ * transfer context was created. Used by xfrin_destroy() when making
+ * logging decisions.
+ */
+ bool zone_had_db;
+
+ dns_db_t *db;
+ dns_dbversion_t *ver;
+ dns_diff_t diff; /*%< Pending database changes */
+ int difflen; /*%< Number of pending tuples */
+
+ xfrin_state_t state;
+ uint32_t end_serial;
+ bool is_ixfr;
+
+ unsigned int nmsg; /*%< Number of messages recvd */
+ unsigned int nrecs; /*%< Number of records recvd */
+ uint64_t nbytes; /*%< Number of bytes received */
+
+ unsigned int maxrecords; /*%< The maximum number of
+ * records set for the zone */
+
+ isc_time_t start; /*%< Start time of the transfer */
+ isc_time_t end; /*%< End time of the transfer */
+
+ dns_tsigkey_t *tsigkey; /*%< Key used to create TSIG */
+ isc_buffer_t *lasttsig; /*%< The last TSIG */
+ dst_context_t *tsigctx; /*%< TSIG verification context */
+ unsigned int sincetsig; /*%< recvd since the last TSIG */
+
+ dns_transport_t *transport;
+
+ dns_xfrindone_t done;
+
+ /*%
+ * AXFR- and IXFR-specific data. Only one is used at a time
+ * according to the is_ixfr flag, so this could be a union,
+ * but keeping them separate makes it a bit simpler to clean
+ * things up when destroying the context.
+ */
+ dns_rdatacallbacks_t axfr;
+
+ struct {
+ uint32_t request_serial;
+ uint32_t current_serial;
+ dns_journal_t *journal;
+ } ixfr;
+
+ dns_rdata_t firstsoa;
+ unsigned char *firstsoa_data;
+
+ isc_tlsctx_cache_t *tlsctx_cache;
+
+ isc_timer_t *max_time_timer;
+ isc_timer_t *max_idle_timer;
+};
+
+#define XFRIN_MAGIC ISC_MAGIC('X', 'f', 'r', 'I')
+#define VALID_XFRIN(x) ISC_MAGIC_VALID(x, XFRIN_MAGIC)
+
+/**************************************************************************/
+/*
+ * Forward declarations.
+ */
+
+static void
+xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr,
+ dns_name_t *zonename, dns_rdataclass_t rdclass,
+ dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
+ const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
+ dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
+ dns_xfrin_ctx_t **xfrp);
+
+static isc_result_t
+axfr_init(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp);
+static isc_result_t
+axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata);
+static isc_result_t
+axfr_apply(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+axfr_commit(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+axfr_finalize(dns_xfrin_ctx_t *xfr);
+
+static isc_result_t
+ixfr_init(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+ixfr_apply(dns_xfrin_ctx_t *xfr);
+static isc_result_t
+ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata);
+static isc_result_t
+ixfr_commit(dns_xfrin_ctx_t *xfr);
+
+static isc_result_t
+xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, uint32_t ttl,
+ dns_rdata_t *rdata);
+
+static isc_result_t
+xfrin_start(dns_xfrin_ctx_t *xfr);
+
+static void
+xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg);
+static isc_result_t
+xfrin_send_request(dns_xfrin_ctx_t *xfr);
+static void
+xfrin_send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg);
+static void
+xfrin_recv_done(isc_nmhandle_t *handle, isc_result_t result,
+ isc_region_t *region, void *cbarg);
+
+static void
+xfrin_destroy(dns_xfrin_ctx_t *xfr);
+
+static void
+xfrin_timedout(struct isc_task *, struct isc_event *);
+static void
+xfrin_idledout(struct isc_task *, struct isc_event *);
+static void
+xfrin_fail(dns_xfrin_ctx_t *xfr, isc_result_t result, const char *msg);
+static isc_result_t
+render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf);
+
+static void
+xfrin_logv(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
+ const char *fmt, va_list ap) ISC_FORMAT_PRINTF(4, 0);
+
+static void
+xfrin_log1(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
+ const char *fmt, ...) ISC_FORMAT_PRINTF(4, 5);
+
+static void
+xfrin_log(dns_xfrin_ctx_t *xfr, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+/**************************************************************************/
+/*
+ * AXFR handling
+ */
+
+static isc_result_t
+axfr_init(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+
+ xfr->is_ixfr = false;
+
+ if (xfr->db != NULL) {
+ dns_db_detach(&xfr->db);
+ }
+
+ CHECK(axfr_makedb(xfr, &xfr->db));
+ dns_rdatacallbacks_init(&xfr->axfr);
+ CHECK(dns_db_beginload(xfr->db, &xfr->axfr));
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp) {
+ isc_result_t result;
+
+ result = dns_db_create(xfr->mctx, /* XXX */
+ "rbt", /* XXX guess */
+ &xfr->name, dns_dbtype_zone, xfr->rdclass, 0,
+ NULL, /* XXX guess */
+ dbp);
+ if (result == ISC_R_SUCCESS) {
+ dns_zone_rpz_enable_db(xfr->zone, *dbp);
+ dns_zone_catz_enable_db(xfr->zone, *dbp);
+ }
+ return (result);
+}
+
+static isc_result_t
+axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata) {
+ isc_result_t result;
+
+ dns_difftuple_t *tuple = NULL;
+
+ if (rdata->rdclass != xfr->rdclass) {
+ return (DNS_R_BADCLASS);
+ }
+
+ CHECK(dns_zone_checknames(xfr->zone, name, rdata));
+ CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata,
+ &tuple));
+ dns_diff_append(&xfr->diff, &tuple);
+ if (++xfr->difflen > 100) {
+ CHECK(axfr_apply(xfr));
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/*
+ * Store a set of AXFR RRs in the database.
+ */
+static isc_result_t
+axfr_apply(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ uint64_t records;
+
+ CHECK(dns_diff_load(&xfr->diff, xfr->axfr.add, xfr->axfr.add_private));
+ xfr->difflen = 0;
+ dns_diff_clear(&xfr->diff);
+ if (xfr->maxrecords != 0U) {
+ result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL);
+ if (result == ISC_R_SUCCESS && records > xfr->maxrecords) {
+ result = DNS_R_TOOMANYRECORDS;
+ goto failure;
+ }
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+axfr_commit(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+
+ CHECK(axfr_apply(xfr));
+ CHECK(dns_db_endload(xfr->db, &xfr->axfr));
+ CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL));
+
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+axfr_finalize(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+
+ CHECK(dns_zone_replacedb(xfr->zone, xfr->db, true));
+
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/**************************************************************************/
+/*
+ * IXFR handling
+ */
+
+static isc_result_t
+ixfr_init(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ char *journalfile = NULL;
+
+ if (xfr->reqtype != dns_rdatatype_ixfr) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "got incremental response to AXFR request");
+ return (DNS_R_FORMERR);
+ }
+
+ xfr->is_ixfr = true;
+ INSIST(xfr->db != NULL);
+ xfr->difflen = 0;
+
+ journalfile = dns_zone_getjournal(xfr->zone);
+ if (journalfile != NULL) {
+ CHECK(dns_journal_open(xfr->mctx, journalfile,
+ DNS_JOURNAL_CREATE, &xfr->ixfr.journal));
+ }
+
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
+ dns_ttl_t ttl, dns_rdata_t *rdata) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+
+ if (rdata->rdclass != xfr->rdclass) {
+ return (DNS_R_BADCLASS);
+ }
+
+ if (op == DNS_DIFFOP_ADD) {
+ CHECK(dns_zone_checknames(xfr->zone, name, rdata));
+ }
+ CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata,
+ &tuple));
+ dns_diff_append(&xfr->diff, &tuple);
+ if (++xfr->difflen > 100) {
+ CHECK(ixfr_apply(xfr));
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/*
+ * Apply a set of IXFR changes to the database.
+ */
+static isc_result_t
+ixfr_apply(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ uint64_t records;
+
+ if (xfr->ver == NULL) {
+ CHECK(dns_db_newversion(xfr->db, &xfr->ver));
+ if (xfr->ixfr.journal != NULL) {
+ CHECK(dns_journal_begin_transaction(xfr->ixfr.journal));
+ }
+ }
+ CHECK(dns_diff_apply(&xfr->diff, xfr->db, xfr->ver));
+ if (xfr->maxrecords != 0U) {
+ result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL);
+ if (result == ISC_R_SUCCESS && records > xfr->maxrecords) {
+ result = DNS_R_TOOMANYRECORDS;
+ goto failure;
+ }
+ }
+ if (xfr->ixfr.journal != NULL) {
+ result = dns_journal_writediff(xfr->ixfr.journal, &xfr->diff);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+ dns_diff_clear(&xfr->diff);
+ xfr->difflen = 0;
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+static isc_result_t
+ixfr_commit(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+
+ CHECK(ixfr_apply(xfr));
+ if (xfr->ver != NULL) {
+ CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver));
+ /* XXX enter ready-to-commit state here */
+ if (xfr->ixfr.journal != NULL) {
+ CHECK(dns_journal_commit(xfr->ixfr.journal));
+ }
+ dns_db_closeversion(xfr->db, &xfr->ver, true);
+ dns_zone_markdirty(xfr->zone);
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+/**************************************************************************/
+/*
+ * Common AXFR/IXFR protocol code
+ */
+
+/*
+ * Handle a single incoming resource record according to the current
+ * state.
+ */
+static isc_result_t
+xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, uint32_t ttl,
+ dns_rdata_t *rdata) {
+ isc_result_t result;
+
+ xfr->nrecs++;
+
+ if (rdata->type == dns_rdatatype_none ||
+ dns_rdatatype_ismeta(rdata->type))
+ {
+ char buf[64];
+ dns_rdatatype_format(rdata->type, buf, sizeof(buf));
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "Unexpected %s record in zone transfer", buf);
+ FAIL(DNS_R_FORMERR);
+ }
+
+ /*
+ * Immediately reject the entire transfer if the RR that is currently
+ * being processed is an SOA record that is not placed at the zone
+ * apex.
+ */
+ if (rdata->type == dns_rdatatype_soa &&
+ !dns_name_equal(&xfr->name, name))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ xfrin_log(xfr, ISC_LOG_DEBUG(3), "SOA name mismatch: '%s'",
+ namebuf);
+ FAIL(DNS_R_NOTZONETOP);
+ }
+
+redo:
+ switch (xfr->state) {
+ case XFRST_SOAQUERY:
+ if (rdata->type != dns_rdatatype_soa) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "non-SOA response to SOA query");
+ FAIL(DNS_R_FORMERR);
+ }
+ xfr->end_serial = dns_soa_getserial(rdata);
+ if (!DNS_SERIAL_GT(xfr->end_serial, xfr->ixfr.request_serial) &&
+ !dns_zone_isforced(xfr->zone))
+ {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "requested serial %u, "
+ "primary has %u, not updating",
+ xfr->ixfr.request_serial, xfr->end_serial);
+ FAIL(DNS_R_UPTODATE);
+ }
+ xfr->state = XFRST_GOTSOA;
+ break;
+
+ case XFRST_GOTSOA:
+ /*
+ * Skip other records in the answer section.
+ */
+ break;
+
+ case XFRST_INITIALSOA:
+ if (rdata->type != dns_rdatatype_soa) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "first RR in zone transfer must be SOA");
+ FAIL(DNS_R_FORMERR);
+ }
+ /*
+ * Remember the serial number in the initial SOA.
+ * We need it to recognize the end of an IXFR.
+ */
+ xfr->end_serial = dns_soa_getserial(rdata);
+ if (xfr->reqtype == dns_rdatatype_ixfr &&
+ !DNS_SERIAL_GT(xfr->end_serial, xfr->ixfr.request_serial) &&
+ !dns_zone_isforced(xfr->zone))
+ {
+ /*
+ * This must be the single SOA record that is
+ * sent when the current version on the primary
+ * is not newer than the version in the request.
+ */
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "requested serial %u, "
+ "primary has %u, not updating",
+ xfr->ixfr.request_serial, xfr->end_serial);
+ FAIL(DNS_R_UPTODATE);
+ }
+ xfr->firstsoa = *rdata;
+ if (xfr->firstsoa_data != NULL) {
+ isc_mem_free(xfr->mctx, xfr->firstsoa_data);
+ }
+ xfr->firstsoa_data = isc_mem_allocate(xfr->mctx, rdata->length);
+ memcpy(xfr->firstsoa_data, rdata->data, rdata->length);
+ xfr->firstsoa.data = xfr->firstsoa_data;
+ xfr->state = XFRST_FIRSTDATA;
+ break;
+
+ case XFRST_FIRSTDATA:
+ /*
+ * If the transfer begins with one SOA record, it is an AXFR,
+ * if it begins with two SOAs, it is an IXFR.
+ */
+ if (xfr->reqtype == dns_rdatatype_ixfr &&
+ rdata->type == dns_rdatatype_soa &&
+ xfr->ixfr.request_serial == dns_soa_getserial(rdata))
+ {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "got incremental response");
+ CHECK(ixfr_init(xfr));
+ xfr->state = XFRST_IXFR_DELSOA;
+ } else {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "got nonincremental response");
+ CHECK(axfr_init(xfr));
+ xfr->state = XFRST_AXFR;
+ }
+ goto redo;
+
+ case XFRST_IXFR_DELSOA:
+ INSIST(rdata->type == dns_rdatatype_soa);
+ CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata));
+ xfr->state = XFRST_IXFR_DEL;
+ break;
+
+ case XFRST_IXFR_DEL:
+ if (rdata->type == dns_rdatatype_soa) {
+ uint32_t soa_serial = dns_soa_getserial(rdata);
+ xfr->state = XFRST_IXFR_ADDSOA;
+ xfr->ixfr.current_serial = soa_serial;
+ goto redo;
+ }
+ CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata));
+ break;
+
+ case XFRST_IXFR_ADDSOA:
+ INSIST(rdata->type == dns_rdatatype_soa);
+ CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
+ xfr->state = XFRST_IXFR_ADD;
+ break;
+
+ case XFRST_IXFR_ADD:
+ if (rdata->type == dns_rdatatype_soa) {
+ uint32_t soa_serial = dns_soa_getserial(rdata);
+ if (soa_serial == xfr->end_serial) {
+ CHECK(ixfr_commit(xfr));
+ xfr->state = XFRST_IXFR_END;
+ break;
+ } else if (soa_serial != xfr->ixfr.current_serial) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "IXFR out of sync: "
+ "expected serial %u, got %u",
+ xfr->ixfr.current_serial, soa_serial);
+ FAIL(DNS_R_FORMERR);
+ } else {
+ CHECK(ixfr_commit(xfr));
+ xfr->state = XFRST_IXFR_DELSOA;
+ goto redo;
+ }
+ }
+ if (rdata->type == dns_rdatatype_ns &&
+ dns_name_iswildcard(name))
+ {
+ FAIL(DNS_R_INVALIDNS);
+ }
+ CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
+ break;
+
+ case XFRST_AXFR:
+ /*
+ * Old BINDs sent cross class A records for non IN classes.
+ */
+ if (rdata->type == dns_rdatatype_a &&
+ rdata->rdclass != xfr->rdclass &&
+ xfr->rdclass != dns_rdataclass_in)
+ {
+ break;
+ }
+ CHECK(axfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
+ if (rdata->type == dns_rdatatype_soa) {
+ /*
+ * Use dns_rdata_compare instead of memcmp to
+ * allow for case differences.
+ */
+ if (dns_rdata_compare(rdata, &xfr->firstsoa) != 0) {
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "start and ending SOA records "
+ "mismatch");
+ FAIL(DNS_R_FORMERR);
+ }
+ CHECK(axfr_commit(xfr));
+ xfr->state = XFRST_AXFR_END;
+ break;
+ }
+ break;
+ case XFRST_AXFR_END:
+ case XFRST_IXFR_END:
+ FAIL(DNS_R_EXTRADATA);
+ FALLTHROUGH;
+ default:
+ UNREACHABLE();
+ }
+ result = ISC_R_SUCCESS;
+failure:
+ return (result);
+}
+
+isc_result_t
+dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
+ const isc_sockaddr_t *primaryaddr,
+ const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
+ dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
+ isc_mem_t *mctx, isc_nm_t *netmgr, dns_xfrindone_t done,
+ dns_xfrin_ctx_t **xfrp) {
+ dns_name_t *zonename = dns_zone_getorigin(zone);
+ dns_xfrin_ctx_t *xfr = NULL;
+ isc_result_t result;
+ dns_db_t *db = NULL;
+
+ REQUIRE(xfrp != NULL && *xfrp == NULL);
+ REQUIRE(done != NULL);
+ REQUIRE(isc_sockaddr_getport(primaryaddr) != 0);
+
+ (void)dns_zone_getdb(zone, &db);
+
+ if (xfrtype == dns_rdatatype_soa || xfrtype == dns_rdatatype_ixfr) {
+ REQUIRE(db != NULL);
+ }
+
+ xfrin_create(mctx, zone, db, netmgr, zonename, dns_zone_getclass(zone),
+ xfrtype, primaryaddr, sourceaddr, tsigkey, transport,
+ tlsctx_cache, &xfr);
+
+ if (db != NULL) {
+ xfr->zone_had_db = true;
+ }
+
+ xfr->done = done;
+
+ isc_refcount_init(&xfr->references, 1);
+
+ /*
+ * Set *xfrp now, before calling xfrin_start(). Asynchronous
+ * netmgr processing could cause the 'done' callback to run in
+ * another thread before we reached the end of the present
+ * function. In that case, if *xfrp hadn't already been
+ * attached, the 'done' function would be unable to detach it.
+ */
+ *xfrp = xfr;
+
+ result = xfrin_start(xfr);
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&xfr->shuttingdown, true);
+ xfr->shutdown_result = result;
+ dns_xfrin_detach(xfrp);
+ }
+
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ char zonetext[DNS_NAME_MAXTEXT + 32];
+ dns_zone_name(zone, zonetext, sizeof(zonetext));
+ xfrin_log1(ISC_LOG_ERROR, zonetext, primaryaddr,
+ "zone transfer setup failed");
+ }
+
+ return (result);
+}
+
+static void
+xfrin_cancelio(dns_xfrin_ctx_t *xfr);
+
+static void
+xfrin_timedout(struct isc_task *task, struct isc_event *event) {
+ UNUSED(task);
+
+ dns_xfrin_ctx_t *xfr = event->ev_arg;
+ REQUIRE(VALID_XFRIN(xfr));
+
+ xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum transfer time exceeded");
+ isc_event_free(&event);
+}
+
+static void
+xfrin_idledout(struct isc_task *task, struct isc_event *event) {
+ UNUSED(task);
+
+ dns_xfrin_ctx_t *xfr = event->ev_arg;
+ REQUIRE(VALID_XFRIN(xfr));
+
+ xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum idle time exceeded");
+ isc_event_free(&event);
+}
+
+void
+dns_xfrin_shutdown(dns_xfrin_ctx_t *xfr) {
+ REQUIRE(VALID_XFRIN(xfr));
+
+ xfrin_fail(xfr, ISC_R_CANCELED, "shut down");
+}
+
+void
+dns_xfrin_attach(dns_xfrin_ctx_t *source, dns_xfrin_ctx_t **target) {
+ REQUIRE(VALID_XFRIN(source));
+ REQUIRE(target != NULL && *target == NULL);
+ (void)isc_refcount_increment(&source->references);
+
+ *target = source;
+}
+
+void
+dns_xfrin_detach(dns_xfrin_ctx_t **xfrp) {
+ dns_xfrin_ctx_t *xfr = NULL;
+
+ REQUIRE(xfrp != NULL && VALID_XFRIN(*xfrp));
+
+ xfr = *xfrp;
+ *xfrp = NULL;
+
+ if (isc_refcount_decrement(&xfr->references) == 1) {
+ xfrin_destroy(xfr);
+ }
+}
+
+static void
+xfrin_cancelio(dns_xfrin_ctx_t *xfr) {
+ if (xfr->readhandle == NULL) {
+ return;
+ }
+
+ isc_nm_cancelread(xfr->readhandle);
+ /* The xfr->readhandle detach will happen in xfrin_recv_done callback */
+}
+
+static void
+xfrin_reset(dns_xfrin_ctx_t *xfr) {
+ REQUIRE(VALID_XFRIN(xfr));
+
+ xfrin_log(xfr, ISC_LOG_INFO, "resetting");
+
+ REQUIRE(xfr->readhandle == NULL);
+ REQUIRE(xfr->sendhandle == NULL);
+
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+
+ dns_diff_clear(&xfr->diff);
+ xfr->difflen = 0;
+
+ if (xfr->ixfr.journal != NULL) {
+ dns_journal_destroy(&xfr->ixfr.journal);
+ }
+
+ if (xfr->axfr.add_private != NULL) {
+ (void)dns_db_endload(xfr->db, &xfr->axfr);
+ }
+
+ if (xfr->ver != NULL) {
+ dns_db_closeversion(xfr->db, &xfr->ver, false);
+ }
+}
+
+static void
+xfrin_fail(dns_xfrin_ctx_t *xfr, isc_result_t result, const char *msg) {
+ /* Make sure only the first xfrin_fail() trumps */
+ if (atomic_compare_exchange_strong(&xfr->shuttingdown, &(bool){ false },
+ true))
+ {
+ (void)isc_timer_reset(xfr->max_time_timer,
+ isc_timertype_inactive, NULL, NULL, true);
+ (void)isc_timer_reset(xfr->max_idle_timer,
+ isc_timertype_inactive, NULL, NULL, true);
+
+ if (result != DNS_R_UPTODATE && result != DNS_R_TOOMANYRECORDS)
+ {
+ xfrin_log(xfr, ISC_LOG_ERROR, "%s: %s", msg,
+ isc_result_totext(result));
+ if (xfr->is_ixfr) {
+ /* Pass special result code to force AXFR retry
+ */
+ result = DNS_R_BADIXFR;
+ }
+ }
+ xfrin_cancelio(xfr);
+ /*
+ * Close the journal.
+ */
+ if (xfr->ixfr.journal != NULL) {
+ dns_journal_destroy(&xfr->ixfr.journal);
+ }
+ if (xfr->done != NULL) {
+ (xfr->done)(xfr->zone, result);
+ xfr->done = NULL;
+ }
+ xfr->shutdown_result = result;
+ }
+}
+
+static void
+xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr,
+ dns_name_t *zonename, dns_rdataclass_t rdclass,
+ dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
+ const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
+ dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
+ dns_xfrin_ctx_t **xfrp) {
+ dns_xfrin_ctx_t *xfr = NULL;
+ dns_zonemgr_t *zmgr = dns_zone_getmgr(zone);
+ isc_timermgr_t *timermgr = dns_zonemgr_gettimermgr(zmgr);
+ isc_task_t *ztask = NULL;
+
+ xfr = isc_mem_get(mctx, sizeof(*xfr));
+ *xfr = (dns_xfrin_ctx_t){ .netmgr = netmgr,
+ .shutdown_result = ISC_R_UNSET,
+ .rdclass = rdclass,
+ .reqtype = reqtype,
+ .id = (dns_messageid_t)isc_random16(),
+ .maxrecords = dns_zone_getmaxrecords(zone),
+ .primaryaddr = *primaryaddr,
+ .sourceaddr = *sourceaddr,
+ .firstsoa = DNS_RDATA_INIT,
+ .magic = XFRIN_MAGIC };
+
+ isc_mem_attach(mctx, &xfr->mctx);
+ dns_zone_iattach(zone, &xfr->zone);
+ dns_name_init(&xfr->name, NULL);
+
+ isc_refcount_init(&xfr->connects, 0);
+ isc_refcount_init(&xfr->sends, 0);
+ isc_refcount_init(&xfr->recvs, 0);
+
+ atomic_init(&xfr->shuttingdown, false);
+
+ if (db != NULL) {
+ dns_db_attach(db, &xfr->db);
+ }
+
+ dns_diff_init(xfr->mctx, &xfr->diff);
+
+ if (reqtype == dns_rdatatype_soa) {
+ xfr->state = XFRST_SOAQUERY;
+ } else {
+ xfr->state = XFRST_INITIALSOA;
+ }
+
+ isc_time_now(&xfr->start);
+
+ if (tsigkey != NULL) {
+ dns_tsigkey_attach(tsigkey, &xfr->tsigkey);
+ }
+
+ if (transport != NULL) {
+ dns_transport_attach(transport, &xfr->transport);
+ }
+
+ dns_name_dup(zonename, mctx, &xfr->name);
+
+ INSIST(isc_sockaddr_pf(primaryaddr) == isc_sockaddr_pf(sourceaddr));
+ isc_sockaddr_setport(&xfr->sourceaddr, 0);
+
+ /*
+ * Reserve 2 bytes for TCP length at the beginning of the buffer.
+ */
+ isc_buffer_init(&xfr->qbuffer, &xfr->qbuffer_data[2],
+ sizeof(xfr->qbuffer_data) - 2);
+
+ isc_tlsctx_cache_attach(tlsctx_cache, &xfr->tlsctx_cache);
+
+ dns_zone_gettask(zone, &ztask);
+ isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, ztask,
+ xfrin_timedout, xfr, &xfr->max_time_timer);
+ isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, ztask,
+ xfrin_idledout, xfr, &xfr->max_idle_timer);
+ isc_task_detach(&ztask); /* dns_zone_task() attaches to the task */
+
+ *xfrp = xfr;
+}
+
+static isc_result_t
+get_create_tlsctx(const dns_xfrin_ctx_t *xfr, isc_tlsctx_t **pctx,
+ isc_tlsctx_client_session_cache_t **psess_cache) {
+ isc_result_t result = ISC_R_FAILURE;
+ isc_tlsctx_t *tlsctx = NULL, *found = NULL;
+ isc_tls_cert_store_t *store = NULL, *found_store = NULL;
+ isc_tlsctx_client_session_cache_t *sess_cache = NULL,
+ *found_sess_cache = NULL;
+ uint32_t tls_versions;
+ const char *ciphers = NULL;
+ bool prefer_server_ciphers;
+ const uint16_t family = isc_sockaddr_pf(&xfr->primaryaddr) == PF_INET6
+ ? AF_INET6
+ : AF_INET;
+ const char *tlsname = NULL;
+
+ REQUIRE(psess_cache != NULL && *psess_cache == NULL);
+ REQUIRE(pctx != NULL && *pctx == NULL);
+
+ INSIST(xfr->transport != NULL);
+ tlsname = dns_transport_get_tlsname(xfr->transport);
+ INSIST(tlsname != NULL && *tlsname != '\0');
+
+ /*
+ * Let's try to re-use the already created context. This way
+ * we have a chance to resume the TLS session, bypassing the
+ * full TLS handshake procedure, making establishing
+ * subsequent TLS connections for XoT faster.
+ */
+ result = isc_tlsctx_cache_find(xfr->tlsctx_cache, tlsname,
+ isc_tlsctx_cache_tls, family, &found,
+ &found_store, &found_sess_cache);
+ if (result != ISC_R_SUCCESS) {
+ const char *hostname =
+ dns_transport_get_remote_hostname(xfr->transport);
+ const char *ca_file = dns_transport_get_cafile(xfr->transport);
+ const char *cert_file =
+ dns_transport_get_certfile(xfr->transport);
+ const char *key_file =
+ dns_transport_get_keyfile(xfr->transport);
+ char primary_addr_str[INET6_ADDRSTRLEN] = { 0 };
+ isc_netaddr_t primary_netaddr = { 0 };
+ bool hostname_ignore_subject;
+ /*
+ * So, no context exists. Let's create one using the
+ * parameters from the configuration file and try to
+ * store it for further reuse.
+ */
+ result = isc_tlsctx_createclient(&tlsctx);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ tls_versions = dns_transport_get_tls_versions(xfr->transport);
+ if (tls_versions != 0) {
+ isc_tlsctx_set_protocols(tlsctx, tls_versions);
+ }
+ ciphers = dns_transport_get_ciphers(xfr->transport);
+ if (ciphers != NULL) {
+ isc_tlsctx_set_cipherlist(tlsctx, ciphers);
+ }
+
+ if (dns_transport_get_prefer_server_ciphers(
+ xfr->transport, &prefer_server_ciphers))
+ {
+ isc_tlsctx_prefer_server_ciphers(tlsctx,
+ prefer_server_ciphers);
+ }
+
+ if (hostname != NULL || ca_file != NULL) {
+ /*
+ * The situation when 'found_store != NULL' while 'found
+ * == NULL' might appear as there is one to many
+ * relation between per transport TLS contexts and cert
+ * stores. That is, there could be one store shared
+ * between multiple contexts.
+ */
+ if (found_store == NULL) {
+ /*
+ * 'ca_file' can equal 'NULL' here, in
+ * that case the store with system-wide
+ * CA certificates will be created, just
+ * as planned.
+ */
+ result = isc_tls_cert_store_create(ca_file,
+ &store);
+
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ } else {
+ store = found_store;
+ }
+
+ INSIST(store != NULL);
+ if (hostname == NULL) {
+ /*
+ * If CA bundle file is specified, but
+ * hostname is not, then use the primary
+ * IP address for validation, just like
+ * dig does.
+ */
+ INSIST(ca_file != NULL);
+ isc_netaddr_fromsockaddr(&primary_netaddr,
+ &xfr->primaryaddr);
+ isc_netaddr_format(&primary_netaddr,
+ primary_addr_str,
+ sizeof(primary_addr_str));
+ hostname = primary_addr_str;
+ }
+ /*
+ * According to RFC 8310, Subject field MUST NOT
+ * be inspected when verifying hostname for DoT.
+ * Only SubjectAltName must be checked.
+ */
+ hostname_ignore_subject = true;
+ result = isc_tlsctx_enable_peer_verification(
+ tlsctx, false, store, hostname,
+ hostname_ignore_subject);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Let's load client certificate and enable
+ * Mutual TLS. We do that only in the case when
+ * Strict TLS is enabled, because Mutual TLS is
+ * an extension of it.
+ */
+ if (cert_file != NULL) {
+ INSIST(key_file != NULL);
+
+ result = isc_tlsctx_load_certificate(
+ tlsctx, key_file, cert_file);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+ }
+
+ isc_tlsctx_enable_dot_client_alpn(tlsctx);
+
+ isc_tlsctx_client_session_cache_create(
+ xfr->mctx, tlsctx,
+ ISC_TLSCTX_CLIENT_SESSION_CACHE_DEFAULT_SIZE,
+ &sess_cache);
+
+ found_store = NULL;
+ result = isc_tlsctx_cache_add(xfr->tlsctx_cache, tlsname,
+ isc_tlsctx_cache_tls, family,
+ tlsctx, store, sess_cache, &found,
+ &found_store, &found_sess_cache);
+ if (result == ISC_R_EXISTS) {
+ /*
+ * It seems the entry has just been created from within
+ * another thread while we were initialising
+ * ours. Although this is unlikely, it could happen
+ * after startup/re-initialisation. In such a case,
+ * discard the new context and associated data and use
+ * the already established one from now on.
+ *
+ * Such situation will not occur after the
+ * initial 'warm-up', so it is not critical
+ * performance-wise.
+ */
+ INSIST(found != NULL);
+ isc_tlsctx_free(&tlsctx);
+ isc_tls_cert_store_free(&store);
+ isc_tlsctx_client_session_cache_detach(&sess_cache);
+ /* Let's return the data from the cache. */
+ *psess_cache = found_sess_cache;
+ *pctx = found;
+ } else {
+ /*
+ * Adding the fresh values into the cache has been
+ * successful, let's return them
+ */
+ INSIST(result == ISC_R_SUCCESS);
+ *psess_cache = sess_cache;
+ *pctx = tlsctx;
+ }
+ } else {
+ /*
+ * The cache lookup has been successful, let's return the
+ * results.
+ */
+ INSIST(result == ISC_R_SUCCESS);
+ *psess_cache = found_sess_cache;
+ *pctx = found;
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ if (tlsctx != NULL) {
+ isc_tlsctx_free(&tlsctx);
+ }
+
+ /*
+ * The 'found_store' is being managed by the TLS context
+ * cache. Thus, we should keep it as it is, as it will get
+ * destroyed alongside the cache. As there is one store per
+ * multiple TLS contexts, we need to handle store deletion in a
+ * special way.
+ */
+ if (store != NULL && store != found_store) {
+ isc_tls_cert_store_free(&store);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+xfrin_start(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ dns_xfrin_ctx_t *connect_xfr = NULL;
+ dns_transport_type_t transport_type = DNS_TRANSPORT_TCP;
+ isc_tlsctx_t *tlsctx = NULL;
+ isc_tlsctx_client_session_cache_t *sess_cache = NULL;
+ isc_interval_t interval;
+ isc_time_t next;
+
+ (void)isc_refcount_increment0(&xfr->connects);
+ dns_xfrin_attach(xfr, &connect_xfr);
+
+ if (xfr->transport != NULL) {
+ transport_type = dns_transport_get_type(xfr->transport);
+ }
+
+ /* Set the maximum timer */
+ isc_interval_set(&interval, dns_zone_getmaxxfrin(xfr->zone), 0);
+ isc_time_nowplusinterval(&next, &interval);
+ result = isc_timer_reset(xfr->max_time_timer, isc_timertype_once, &next,
+ NULL, true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /* Set the idle timer */
+ isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0);
+ isc_time_nowplusinterval(&next, &interval);
+ result = isc_timer_reset(xfr->max_idle_timer, isc_timertype_once, &next,
+ NULL, true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ /*
+ * XXX: timeouts are hard-coded to 30 seconds; this needs to be
+ * configurable.
+ */
+ switch (transport_type) {
+ case DNS_TRANSPORT_TCP:
+ isc_nm_tcpdnsconnect(xfr->netmgr, &xfr->sourceaddr,
+ &xfr->primaryaddr, xfrin_connect_done,
+ connect_xfr, 30000, 0);
+ break;
+ case DNS_TRANSPORT_TLS: {
+ result = get_create_tlsctx(xfr, &tlsctx, &sess_cache);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ INSIST(tlsctx != NULL);
+ isc_nm_tlsdnsconnect(xfr->netmgr, &xfr->sourceaddr,
+ &xfr->primaryaddr, xfrin_connect_done,
+ connect_xfr, 30000, 0, tlsctx, sess_cache);
+ } break;
+ default:
+ UNREACHABLE();
+ }
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ isc_refcount_decrement0(&xfr->connects);
+ dns_xfrin_detach(&connect_xfr);
+ return (result);
+}
+
+/* XXX the resolver could use this, too */
+
+static isc_result_t
+render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf) {
+ dns_compress_t cctx;
+ bool cleanup_cctx = false;
+ isc_result_t result;
+
+ CHECK(dns_compress_init(&cctx, -1, mctx));
+ cleanup_cctx = true;
+ CHECK(dns_message_renderbegin(msg, &cctx, buf));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_QUESTION, 0));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_ANSWER, 0));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_AUTHORITY, 0));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_ADDITIONAL, 0));
+ CHECK(dns_message_renderend(msg));
+ result = ISC_R_SUCCESS;
+failure:
+ if (cleanup_cctx) {
+ dns_compress_invalidate(&cctx);
+ }
+ return (result);
+}
+
+/*
+ * A connection has been established.
+ */
+static void
+xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg;
+ char sourcetext[ISC_SOCKADDR_FORMATSIZE];
+ char signerbuf[DNS_NAME_FORMATSIZE];
+ const char *signer = "", *sep = "";
+ isc_sockaddr_t sockaddr;
+ dns_zonemgr_t *zmgr = NULL;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ isc_refcount_decrement0(&xfr->connects);
+
+ if (atomic_load(&xfr->shuttingdown)) {
+ result = ISC_R_SHUTTINGDOWN;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed to connect");
+ goto failure;
+ }
+
+ result = isc_nm_xfr_checkperm(handle);
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "connected but unable to transfer");
+ goto failure;
+ }
+
+ zmgr = dns_zone_getmgr(xfr->zone);
+ if (zmgr != NULL) {
+ dns_zonemgr_unreachabledel(zmgr, &xfr->primaryaddr,
+ &xfr->sourceaddr);
+ }
+
+ xfr->handle = handle;
+ sockaddr = isc_nmhandle_peeraddr(handle);
+ isc_sockaddr_format(&sockaddr, sourcetext, sizeof(sourcetext));
+
+ if (xfr->tsigkey != NULL && xfr->tsigkey->key != NULL) {
+ dns_name_format(dst_key_name(xfr->tsigkey->key), signerbuf,
+ sizeof(signerbuf));
+ sep = " TSIG ";
+ signer = signerbuf;
+ }
+
+ xfrin_log(xfr, ISC_LOG_INFO, "connected using %s%s%s", sourcetext, sep,
+ signer);
+
+ result = xfrin_send_request(xfr);
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "connected but unable to send");
+ }
+
+failure:
+ switch (result) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_NETDOWN:
+ case ISC_R_HOSTDOWN:
+ case ISC_R_NETUNREACH:
+ case ISC_R_HOSTUNREACH:
+ case ISC_R_CONNREFUSED:
+ case ISC_R_TIMEDOUT:
+ /*
+ * Add the server to unreachable primaries table if
+ * the server has a permanent networking error or
+ * the connection attempt as timed out.
+ */
+ zmgr = dns_zone_getmgr(xfr->zone);
+ if (zmgr != NULL) {
+ isc_time_t now;
+
+ TIME_NOW(&now);
+
+ dns_zonemgr_unreachableadd(zmgr, &xfr->primaryaddr,
+ &xfr->sourceaddr, &now);
+ }
+ break;
+ default:
+ /* Retry sooner than in 10 minutes */
+ break;
+ }
+
+ dns_xfrin_detach(&xfr);
+}
+
+/*
+ * Convert a tuple into a dns_name_t suitable for inserting
+ * into the given dns_message_t.
+ */
+static isc_result_t
+tuple2msgname(dns_difftuple_t *tuple, dns_message_t *msg, dns_name_t **target) {
+ isc_result_t result;
+ dns_rdata_t *rdata = NULL;
+ dns_rdatalist_t *rdl = NULL;
+ dns_rdataset_t *rds = NULL;
+ dns_name_t *name = NULL;
+
+ REQUIRE(target != NULL && *target == NULL);
+
+ CHECK(dns_message_gettemprdata(msg, &rdata));
+ dns_rdata_init(rdata);
+ dns_rdata_clone(&tuple->rdata, rdata);
+
+ CHECK(dns_message_gettemprdatalist(msg, &rdl));
+ dns_rdatalist_init(rdl);
+ rdl->type = tuple->rdata.type;
+ rdl->rdclass = tuple->rdata.rdclass;
+ rdl->ttl = tuple->ttl;
+ ISC_LIST_APPEND(rdl->rdata, rdata, link);
+
+ CHECK(dns_message_gettemprdataset(msg, &rds));
+ CHECK(dns_rdatalist_tordataset(rdl, rds));
+
+ CHECK(dns_message_gettempname(msg, &name));
+ dns_name_clone(&tuple->name, name);
+ ISC_LIST_APPEND(name->list, rds, link);
+
+ *target = name;
+ return (ISC_R_SUCCESS);
+
+failure:
+
+ if (rds != NULL) {
+ dns_rdataset_disassociate(rds);
+ dns_message_puttemprdataset(msg, &rds);
+ }
+ if (rdl != NULL) {
+ ISC_LIST_UNLINK(rdl->rdata, rdata, link);
+ dns_message_puttemprdatalist(msg, &rdl);
+ }
+ if (rdata != NULL) {
+ dns_message_puttemprdata(msg, &rdata);
+ }
+
+ return (result);
+}
+
+/*
+ * Build an *XFR request and send its length prefix.
+ */
+static isc_result_t
+xfrin_send_request(dns_xfrin_ctx_t *xfr) {
+ isc_result_t result;
+ isc_region_t region;
+ dns_rdataset_t *qrdataset = NULL;
+ dns_message_t *msg = NULL;
+ dns_difftuple_t *soatuple = NULL;
+ dns_name_t *qname = NULL;
+ dns_dbversion_t *ver = NULL;
+ dns_name_t *msgsoaname = NULL;
+ dns_xfrin_ctx_t *send_xfr = NULL;
+
+ /* Create the request message */
+ dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTRENDER, &msg);
+ CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
+
+ /* Create a name for the question section. */
+ CHECK(dns_message_gettempname(msg, &qname));
+ dns_name_clone(&xfr->name, qname);
+
+ /* Formulate the question and attach it to the question name. */
+ CHECK(dns_message_gettemprdataset(msg, &qrdataset));
+ dns_rdataset_makequestion(qrdataset, xfr->rdclass, xfr->reqtype);
+ ISC_LIST_APPEND(qname->list, qrdataset, link);
+ qrdataset = NULL;
+
+ dns_message_addname(msg, qname, DNS_SECTION_QUESTION);
+ qname = NULL;
+
+ if (xfr->reqtype == dns_rdatatype_ixfr) {
+ /* Get the SOA and add it to the authority section. */
+ /* XXX is using the current version the right thing? */
+ dns_db_currentversion(xfr->db, &ver);
+ CHECK(dns_db_createsoatuple(xfr->db, ver, xfr->mctx,
+ DNS_DIFFOP_EXISTS, &soatuple));
+ xfr->ixfr.request_serial = dns_soa_getserial(&soatuple->rdata);
+ xfr->ixfr.current_serial = xfr->ixfr.request_serial;
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "requesting IXFR for serial %u",
+ xfr->ixfr.request_serial);
+
+ CHECK(tuple2msgname(soatuple, msg, &msgsoaname));
+ dns_message_addname(msg, msgsoaname, DNS_SECTION_AUTHORITY);
+ } else if (xfr->reqtype == dns_rdatatype_soa) {
+ CHECK(dns_db_getsoaserial(xfr->db, NULL,
+ &xfr->ixfr.request_serial));
+ }
+
+ xfr->id++;
+ xfr->nmsg = 0;
+ xfr->nrecs = 0;
+ xfr->nbytes = 0;
+ isc_time_now(&xfr->start);
+ msg->id = xfr->id;
+ if (xfr->tsigctx != NULL) {
+ dst_context_destroy(&xfr->tsigctx);
+ }
+
+ CHECK(render(msg, xfr->mctx, &xfr->qbuffer));
+
+ /*
+ * Free the last tsig, if there is one.
+ */
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+
+ /*
+ * Save the query TSIG and don't let message_destroy free it.
+ */
+ CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
+
+ isc_buffer_usedregion(&xfr->qbuffer, &region);
+ INSIST(region.length <= 65535);
+
+ dns_xfrin_attach(xfr, &send_xfr);
+ isc_nmhandle_attach(send_xfr->handle, &xfr->sendhandle);
+ isc_refcount_increment0(&send_xfr->sends);
+ isc_nm_send(xfr->handle, &region, xfrin_send_done, send_xfr);
+
+failure:
+ if (qname != NULL) {
+ dns_message_puttempname(msg, &qname);
+ }
+ if (qrdataset != NULL) {
+ dns_message_puttemprdataset(msg, &qrdataset);
+ }
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ if (soatuple != NULL) {
+ dns_difftuple_free(&soatuple);
+ }
+ if (ver != NULL) {
+ dns_db_closeversion(xfr->db, &ver, false);
+ }
+
+ return (result);
+}
+
+static void
+xfrin_send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg;
+ dns_xfrin_ctx_t *recv_xfr = NULL;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ isc_refcount_decrement0(&xfr->sends);
+ if (atomic_load(&xfr->shuttingdown)) {
+ result = ISC_R_SHUTTINGDOWN;
+ }
+
+ CHECK(result);
+
+ xfrin_log(xfr, ISC_LOG_DEBUG(3), "sent request data");
+
+ dns_xfrin_attach(xfr, &recv_xfr);
+ isc_nmhandle_attach(handle, &recv_xfr->readhandle);
+ isc_refcount_increment0(&recv_xfr->recvs);
+ isc_nm_read(recv_xfr->handle, xfrin_recv_done, recv_xfr);
+
+failure:
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed sending request data");
+ }
+
+ isc_nmhandle_detach(&xfr->sendhandle);
+ dns_xfrin_detach(&xfr); /* send_xfr */
+}
+
+static void
+xfrin_recv_done(isc_nmhandle_t *handle, isc_result_t result,
+ isc_region_t *region, void *cbarg) {
+ dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg;
+ dns_message_t *msg = NULL;
+ dns_name_t *name = NULL;
+ const dns_name_t *tsigowner = NULL;
+ isc_buffer_t buffer;
+ isc_sockaddr_t peer;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ isc_refcount_decrement0(&xfr->recvs);
+
+ if (atomic_load(&xfr->shuttingdown)) {
+ result = ISC_R_SHUTTINGDOWN;
+ }
+
+ /* Stop the idle timer */
+ (void)isc_timer_reset(xfr->max_idle_timer, isc_timertype_inactive, NULL,
+ NULL, true);
+
+ CHECK(result);
+
+ xfrin_log(xfr, ISC_LOG_DEBUG(7), "received %u bytes", region->length);
+
+ dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
+
+ CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
+ CHECK(dns_message_setquerytsig(msg, xfr->lasttsig));
+
+ msg->tsigctx = xfr->tsigctx;
+ xfr->tsigctx = NULL;
+
+ dns_message_setclass(msg, xfr->rdclass);
+
+ if (xfr->nmsg > 0) {
+ msg->tcp_continuation = 1;
+ }
+
+ isc_buffer_init(&buffer, region->base, region->length);
+ isc_buffer_add(&buffer, region->length);
+ peer = isc_nmhandle_peeraddr(handle);
+
+ result = dns_message_parse(msg, &buffer,
+ DNS_MESSAGEPARSE_PRESERVEORDER);
+ if (result == ISC_R_SUCCESS) {
+ dns_message_logpacket(msg, "received message from", &peer,
+ DNS_LOGCATEGORY_XFER_IN,
+ DNS_LOGMODULE_XFER_IN, ISC_LOG_DEBUG(10),
+ xfr->mctx);
+ } else {
+ xfrin_log(xfr, ISC_LOG_DEBUG(10), "dns_message_parse: %s",
+ isc_result_totext(result));
+ }
+
+ if (result != ISC_R_SUCCESS || msg->rcode != dns_rcode_noerror ||
+ msg->opcode != dns_opcode_query || msg->rdclass != xfr->rdclass ||
+ msg->id != xfr->id)
+ {
+ if (result == ISC_R_SUCCESS && msg->rcode != dns_rcode_noerror)
+ {
+ result = dns_result_fromrcode(msg->rcode);
+ } else if (result == ISC_R_SUCCESS &&
+ msg->opcode != dns_opcode_query)
+ {
+ result = DNS_R_UNEXPECTEDOPCODE;
+ } else if (result == ISC_R_SUCCESS &&
+ msg->rdclass != xfr->rdclass)
+ {
+ result = DNS_R_BADCLASS;
+ } else if (result == ISC_R_SUCCESS || result == DNS_R_NOERROR) {
+ result = DNS_R_UNEXPECTEDID;
+ }
+
+ if (xfr->reqtype == dns_rdatatype_axfr ||
+ xfr->reqtype == dns_rdatatype_soa)
+ {
+ goto failure;
+ }
+
+ xfrin_log(xfr, ISC_LOG_DEBUG(3), "got %s, retrying with AXFR",
+ isc_result_totext(result));
+ try_axfr:
+ isc_nmhandle_detach(&xfr->readhandle);
+ dns_message_detach(&msg);
+ xfrin_reset(xfr);
+ xfr->reqtype = dns_rdatatype_soa;
+ xfr->state = XFRST_SOAQUERY;
+ result = xfrin_start(xfr);
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed setting up socket");
+ }
+ dns_xfrin_detach(&xfr); /* recv_xfr */
+ return;
+ }
+
+ /*
+ * The question section should exist for SOA and in the first
+ * message of a AXFR or IXFR response. The question section
+ * may exist in the 2nd and subsequent messages in a AXFR or
+ * IXFR response. If the question section exists it should
+ * match the question that was sent.
+ */
+ if (msg->counts[DNS_SECTION_QUESTION] > 1) {
+ xfrin_log(xfr, ISC_LOG_NOTICE, "too many questions (%u)",
+ msg->counts[DNS_SECTION_QUESTION]);
+ result = DNS_R_FORMERR;
+ goto failure;
+ }
+
+ if ((xfr->state == XFRST_SOAQUERY || xfr->state == XFRST_INITIALSOA) &&
+ msg->counts[DNS_SECTION_QUESTION] != 1)
+ {
+ xfrin_log(xfr, ISC_LOG_NOTICE, "missing question section");
+ result = DNS_R_FORMERR;
+ goto failure;
+ }
+
+ for (result = dns_message_firstname(msg, DNS_SECTION_QUESTION);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(msg, DNS_SECTION_QUESTION))
+ {
+ dns_rdataset_t *rds = NULL;
+
+ name = NULL;
+ dns_message_currentname(msg, DNS_SECTION_QUESTION, &name);
+ if (!dns_name_equal(name, &xfr->name)) {
+ result = DNS_R_FORMERR;
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "question name mismatch");
+ goto failure;
+ }
+ rds = ISC_LIST_HEAD(name->list);
+ INSIST(rds != NULL);
+ if (rds->type != xfr->reqtype) {
+ result = DNS_R_FORMERR;
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "question type mismatch");
+ goto failure;
+ }
+ if (rds->rdclass != xfr->rdclass) {
+ result = DNS_R_FORMERR;
+ xfrin_log(xfr, ISC_LOG_NOTICE,
+ "question class mismatch");
+ goto failure;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+ /*
+ * Does the server know about IXFR? If it doesn't we will get
+ * a message with a empty answer section or a potentially a CNAME /
+ * DNAME, the later is handled by xfr_rr() which will return FORMERR
+ * if the first RR in the answer section is not a SOA record.
+ */
+ if (xfr->reqtype == dns_rdatatype_ixfr &&
+ xfr->state == XFRST_INITIALSOA &&
+ msg->counts[DNS_SECTION_ANSWER] == 0)
+ {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3),
+ "empty answer section, retrying with AXFR");
+ goto try_axfr;
+ }
+
+ if (xfr->reqtype == dns_rdatatype_soa &&
+ (msg->flags & DNS_MESSAGEFLAG_AA) == 0)
+ {
+ FAIL(DNS_R_NOTAUTHORITATIVE);
+ }
+
+ result = dns_message_checksig(msg, dns_zone_getview(xfr->zone));
+ if (result != ISC_R_SUCCESS) {
+ xfrin_log(xfr, ISC_LOG_DEBUG(3), "TSIG check failed: %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ for (result = dns_message_firstname(msg, DNS_SECTION_ANSWER);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(msg, DNS_SECTION_ANSWER))
+ {
+ dns_rdataset_t *rds = NULL;
+
+ name = NULL;
+ dns_message_currentname(msg, DNS_SECTION_ANSWER, &name);
+ for (rds = ISC_LIST_HEAD(name->list); rds != NULL;
+ rds = ISC_LIST_NEXT(rds, link))
+ {
+ for (result = dns_rdataset_first(rds);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rds))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(rds, &rdata);
+ CHECK(xfr_rr(xfr, name, rds->ttl, &rdata));
+ }
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+ if (dns_message_gettsig(msg, &tsigowner) != NULL) {
+ /*
+ * Reset the counter.
+ */
+ xfr->sincetsig = 0;
+
+ /*
+ * Free the last tsig, if there is one.
+ */
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+
+ /*
+ * Update the last tsig pointer.
+ */
+ CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
+ } else if (dns_message_gettsigkey(msg) != NULL) {
+ xfr->sincetsig++;
+ if (xfr->sincetsig > 100 || xfr->nmsg == 0 ||
+ xfr->state == XFRST_AXFR_END ||
+ xfr->state == XFRST_IXFR_END)
+ {
+ result = DNS_R_EXPECTEDTSIG;
+ goto failure;
+ }
+ }
+
+ /*
+ * Update the number of messages received.
+ */
+ xfr->nmsg++;
+
+ /*
+ * Update the number of bytes received.
+ */
+ xfr->nbytes += buffer.used;
+
+ /*
+ * Take the context back.
+ */
+ INSIST(xfr->tsigctx == NULL);
+ xfr->tsigctx = msg->tsigctx;
+ msg->tsigctx = NULL;
+
+ switch (xfr->state) {
+ case XFRST_GOTSOA:
+ xfr->reqtype = dns_rdatatype_axfr;
+ xfr->state = XFRST_INITIALSOA;
+ CHECK(xfrin_send_request(xfr));
+ break;
+ case XFRST_AXFR_END:
+ CHECK(axfr_finalize(xfr));
+ FALLTHROUGH;
+ case XFRST_IXFR_END:
+ /*
+ * Close the journal.
+ */
+ if (xfr->ixfr.journal != NULL) {
+ dns_journal_destroy(&xfr->ixfr.journal);
+ }
+
+ /*
+ * Inform the caller we succeeded.
+ */
+ if (xfr->done != NULL) {
+ (xfr->done)(xfr->zone, ISC_R_SUCCESS);
+ xfr->done = NULL;
+ }
+
+ atomic_store(&xfr->shuttingdown, true);
+ (void)isc_timer_reset(xfr->max_time_timer,
+ isc_timertype_inactive, NULL, NULL, true);
+ xfr->shutdown_result = ISC_R_SUCCESS;
+ break;
+ default:
+ /*
+ * Read the next message.
+ */
+ /* The readhandle is still attached */
+ /* The recv_xfr is still attached */
+ dns_message_detach(&msg);
+ isc_refcount_increment0(&xfr->recvs);
+ isc_nm_read(xfr->handle, xfrin_recv_done, xfr);
+ isc_time_t next;
+ isc_interval_t interval;
+ isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0);
+ isc_time_nowplusinterval(&next, &interval);
+ result = isc_timer_reset(xfr->max_idle_timer,
+ isc_timertype_once, &next, NULL, true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ return;
+ }
+
+failure:
+ if (result != ISC_R_SUCCESS) {
+ xfrin_fail(xfr, result, "failed while receiving responses");
+ }
+
+ if (msg != NULL) {
+ dns_message_detach(&msg);
+ }
+ isc_nmhandle_detach(&xfr->readhandle);
+ dns_xfrin_detach(&xfr); /* recv_xfr */
+}
+
+static void
+xfrin_destroy(dns_xfrin_ctx_t *xfr) {
+ uint64_t msecs;
+ uint64_t persec;
+ const char *result_str;
+
+ REQUIRE(VALID_XFRIN(xfr));
+
+ /* Safe-guards */
+ REQUIRE(atomic_load(&xfr->shuttingdown));
+ isc_refcount_destroy(&xfr->references);
+ isc_refcount_destroy(&xfr->connects);
+ isc_refcount_destroy(&xfr->recvs);
+ isc_refcount_destroy(&xfr->sends);
+
+ INSIST(xfr->shutdown_result != ISC_R_UNSET);
+
+ /*
+ * If we're called through dns_xfrin_detach() and are not
+ * shutting down, we can't know what the transfer status is as
+ * we are only called when the last reference is lost.
+ */
+ result_str = isc_result_totext(xfr->shutdown_result);
+ xfrin_log(xfr, ISC_LOG_INFO, "Transfer status: %s", result_str);
+
+ /*
+ * Calculate the length of time the transfer took,
+ * and print a log message with the bytes and rate.
+ */
+ isc_time_now(&xfr->end);
+ msecs = isc_time_microdiff(&xfr->end, &xfr->start) / 1000;
+ if (msecs == 0) {
+ msecs = 1;
+ }
+ persec = (xfr->nbytes * 1000) / msecs;
+ xfrin_log(xfr, ISC_LOG_INFO,
+ "Transfer completed: %d messages, %d records, "
+ "%" PRIu64 " bytes, "
+ "%u.%03u secs (%u bytes/sec) (serial %u)",
+ xfr->nmsg, xfr->nrecs, xfr->nbytes,
+ (unsigned int)(msecs / 1000), (unsigned int)(msecs % 1000),
+ (unsigned int)persec, xfr->end_serial);
+
+ if (xfr->readhandle != NULL) {
+ isc_nmhandle_detach(&xfr->readhandle);
+ }
+ if (xfr->sendhandle != NULL) {
+ isc_nmhandle_detach(&xfr->sendhandle);
+ }
+
+ if (xfr->transport != NULL) {
+ dns_transport_detach(&xfr->transport);
+ }
+
+ if (xfr->tsigkey != NULL) {
+ dns_tsigkey_detach(&xfr->tsigkey);
+ }
+
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+
+ dns_diff_clear(&xfr->diff);
+
+ if (xfr->ixfr.journal != NULL) {
+ dns_journal_destroy(&xfr->ixfr.journal);
+ }
+
+ if (xfr->axfr.add_private != NULL) {
+ (void)dns_db_endload(xfr->db, &xfr->axfr);
+ }
+
+ if (xfr->tsigctx != NULL) {
+ dst_context_destroy(&xfr->tsigctx);
+ }
+
+ if ((xfr->name.attributes & DNS_NAMEATTR_DYNAMIC) != 0) {
+ dns_name_free(&xfr->name, xfr->mctx);
+ }
+
+ if (xfr->ver != NULL) {
+ dns_db_closeversion(xfr->db, &xfr->ver, false);
+ }
+
+ if (xfr->db != NULL) {
+ dns_db_detach(&xfr->db);
+ }
+
+ if (xfr->zone != NULL) {
+ if (!xfr->zone_had_db &&
+ xfr->shutdown_result == ISC_R_SUCCESS &&
+ dns_zone_gettype(xfr->zone) == dns_zone_mirror)
+ {
+ dns_zone_log(xfr->zone, ISC_LOG_INFO,
+ "mirror zone is now in use");
+ }
+ xfrin_log(xfr, ISC_LOG_DEBUG(99), "freeing transfer context");
+ /*
+ * xfr->zone must not be detached before xfrin_log() is called.
+ */
+ dns_zone_idetach(&xfr->zone);
+ }
+
+ if (xfr->firstsoa_data != NULL) {
+ isc_mem_free(xfr->mctx, xfr->firstsoa_data);
+ }
+
+ if (xfr->tlsctx_cache != NULL) {
+ isc_tlsctx_cache_detach(&xfr->tlsctx_cache);
+ }
+
+ isc_timer_destroy(&xfr->max_idle_timer);
+ isc_timer_destroy(&xfr->max_time_timer);
+
+ isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr));
+}
+
+/*
+ * Log incoming zone transfer messages in a format like
+ * transfer of <zone> from <address>: <message>
+ */
+static void
+xfrin_logv(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
+ const char *fmt, va_list ap) {
+ char primarytext[ISC_SOCKADDR_FORMATSIZE];
+ char msgtext[2048];
+
+ isc_sockaddr_format(primaryaddr, primarytext, sizeof(primarytext));
+ vsnprintf(msgtext, sizeof(msgtext), fmt, ap);
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_XFER_IN, DNS_LOGMODULE_XFER_IN,
+ level, "transfer of '%s' from %s: %s", zonetext,
+ primarytext, msgtext);
+}
+
+/*
+ * Logging function for use when a xfrin_ctx_t has not yet been created.
+ */
+
+static void
+xfrin_log1(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
+ const char *fmt, ...) {
+ va_list ap;
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ xfrin_logv(level, zonetext, primaryaddr, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Logging function for use when there is a xfrin_ctx_t.
+ */
+
+static void
+xfrin_log(dns_xfrin_ctx_t *xfr, int level, const char *fmt, ...) {
+ va_list ap;
+ char zonetext[DNS_NAME_MAXTEXT + 32];
+
+ if (!isc_log_wouldlog(dns_lctx, level)) {
+ return;
+ }
+
+ dns_zone_name(xfr->zone, zonetext, sizeof(zonetext));
+
+ va_start(ap, fmt);
+ xfrin_logv(level, zonetext, &xfr->primaryaddr, fmt, ap);
+ va_end(ap);
+}