summaryrefslogtreecommitdiffstats
path: root/bin/named/xfrout.c
diff options
context:
space:
mode:
Diffstat (limited to 'bin/named/xfrout.c')
-rw-r--r--bin/named/xfrout.c1714
1 files changed, 1714 insertions, 0 deletions
diff --git a/bin/named/xfrout.c b/bin/named/xfrout.c
new file mode 100644
index 0000000..2da7e70
--- /dev/null
+++ b/bin/named/xfrout.c
@@ -0,0 +1,1714 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* $Id$ */
+
+#include <config.h>
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/formatcheck.h>
+#include <isc/mem.h>
+#include <isc/timer.h>
+#include <isc/print.h>
+#include <isc/stats.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/dlz.h>
+#include <dns/fixedname.h>
+#include <dns/journal.h>
+#include <dns/message.h>
+#include <dns/peer.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/result.h>
+#include <dns/rriterator.h>
+#include <dns/soa.h>
+#include <dns/stats.h>
+#include <dns/timer.h>
+#include <dns/tsig.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zt.h>
+
+#include <named/client.h>
+#include <named/log.h>
+#include <named/server.h>
+#include <named/xfrout.h>
+
+/*! \file
+ * \brief
+ * Outgoing AXFR and IXFR.
+ */
+
+/*
+ * TODO:
+ * - IXFR over UDP
+ */
+
+#define XFROUT_COMMON_LOGARGS \
+ ns_g_lctx, DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT
+
+#define XFROUT_PROTOCOL_LOGARGS \
+ XFROUT_COMMON_LOGARGS, ISC_LOG_INFO
+
+#define XFROUT_DEBUG_LOGARGS(n) \
+ XFROUT_COMMON_LOGARGS, ISC_LOG_DEBUG(n)
+
+#define XFROUT_RR_LOGARGS \
+ XFROUT_COMMON_LOGARGS, XFROUT_RR_LOGLEVEL
+
+#define XFROUT_RR_LOGLEVEL ISC_LOG_DEBUG(8)
+
+/*%
+ * Fail unconditionally and log as a client error.
+ * The test against ISC_R_SUCCESS is there to keep the Solaris compiler
+ * from complaining about "end-of-loop code not reached".
+ */
+#define FAILC(code, msg) \
+ do { \
+ result = (code); \
+ ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, \
+ NS_LOGMODULE_XFER_OUT, ISC_LOG_INFO, \
+ "bad zone transfer request: %s (%s)", \
+ msg, isc_result_totext(code)); \
+ if (result != ISC_R_SUCCESS) goto failure; \
+ } while (0)
+
+#define FAILQ(code, msg, question, rdclass) \
+ do { \
+ char _buf1[DNS_NAME_FORMATSIZE]; \
+ char _buf2[DNS_RDATACLASS_FORMATSIZE]; \
+ result = (code); \
+ dns_name_format(question, _buf1, sizeof(_buf1)); \
+ dns_rdataclass_format(rdclass, _buf2, sizeof(_buf2)); \
+ ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, \
+ NS_LOGMODULE_XFER_OUT, ISC_LOG_INFO, \
+ "bad zone transfer request: '%s/%s': %s (%s)", \
+ _buf1, _buf2, msg, isc_result_totext(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)
+
+/**************************************************************************/
+
+static inline void
+inc_stats(dns_zone_t *zone, isc_statscounter_t counter) {
+ isc_stats_increment(ns_g_server->nsstats, counter);
+ if (zone != NULL) {
+ isc_stats_t *zonestats = dns_zone_getrequeststats(zone);
+ if (zonestats != NULL)
+ isc_stats_increment(zonestats, counter);
+ }
+}
+
+/**************************************************************************/
+
+/*% Log an RR (for debugging) */
+
+static void
+log_rr(dns_name_t *name, dns_rdata_t *rdata, uint32_t ttl) {
+ isc_result_t result;
+ isc_buffer_t buf;
+ char mem[2000];
+ dns_rdatalist_t rdl;
+ dns_rdataset_t rds;
+ dns_rdata_t rd = DNS_RDATA_INIT;
+
+ dns_rdatalist_init(&rdl);
+ rdl.type = rdata->type;
+ rdl.rdclass = rdata->rdclass;
+ rdl.ttl = ttl;
+ if (rdata->type == dns_rdatatype_sig ||
+ rdata->type == dns_rdatatype_rrsig)
+ rdl.covers = dns_rdata_covers(rdata);
+ else
+ rdl.covers = dns_rdatatype_none;
+ dns_rdataset_init(&rds);
+ dns_rdata_init(&rd);
+ dns_rdata_clone(rdata, &rd);
+ ISC_LIST_APPEND(rdl.rdata, &rd, link);
+ RUNTIME_CHECK(dns_rdatalist_tordataset(&rdl, &rds) == ISC_R_SUCCESS);
+
+ isc_buffer_init(&buf, mem, sizeof(mem));
+ result = dns_rdataset_totext(&rds, name,
+ false, false, &buf);
+
+ /*
+ * We could use xfrout_log(), but that would produce
+ * very long lines with a repetitive prefix.
+ */
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Get rid of final newline.
+ */
+ INSIST(buf.used >= 1 &&
+ ((char *) buf.base)[buf.used - 1] == '\n');
+ buf.used--;
+
+ isc_log_write(XFROUT_RR_LOGARGS, "%.*s",
+ (int)isc_buffer_usedlength(&buf),
+ (char *)isc_buffer_base(&buf));
+ } else {
+ isc_log_write(XFROUT_RR_LOGARGS, "<RR too large to print>");
+ }
+}
+
+/**************************************************************************/
+/*
+ * An 'rrstream_t' is a polymorphic iterator that returns
+ * a stream of resource records. There are multiple implementations,
+ * e.g. for generating AXFR and IXFR records streams.
+ */
+
+typedef struct rrstream_methods rrstream_methods_t;
+
+typedef struct rrstream {
+ isc_mem_t *mctx;
+ rrstream_methods_t *methods;
+} rrstream_t;
+
+struct rrstream_methods {
+ isc_result_t (*first)(rrstream_t *);
+ isc_result_t (*next)(rrstream_t *);
+ void (*current)(rrstream_t *,
+ dns_name_t **,
+ uint32_t *,
+ dns_rdata_t **);
+ void (*pause)(rrstream_t *);
+ void (*destroy)(rrstream_t **);
+};
+
+static void
+rrstream_noop_pause(rrstream_t *rs) {
+ UNUSED(rs);
+}
+
+/**************************************************************************/
+/*
+ * An 'ixfr_rrstream_t' is an 'rrstream_t' that returns
+ * an IXFR-like RR stream from a journal file.
+ *
+ * The SOA at the beginning of each sequence of additions
+ * or deletions are included in the stream, but the extra
+ * SOAs at the beginning and end of the entire transfer are
+ * not included.
+ */
+
+typedef struct ixfr_rrstream {
+ rrstream_t common;
+ dns_journal_t *journal;
+} ixfr_rrstream_t;
+
+/* Forward declarations. */
+static void
+ixfr_rrstream_destroy(rrstream_t **sp);
+
+static rrstream_methods_t ixfr_rrstream_methods;
+
+/*
+ * Returns: anything dns_journal_open() or dns_journal_iter_init()
+ * may return.
+ */
+
+static isc_result_t
+ixfr_rrstream_create(isc_mem_t *mctx,
+ const char *journal_filename,
+ uint32_t begin_serial,
+ uint32_t end_serial,
+ rrstream_t **sp)
+{
+ ixfr_rrstream_t *s;
+ isc_result_t result;
+
+ INSIST(sp != NULL && *sp == NULL);
+
+ s = isc_mem_get(mctx, sizeof(*s));
+ if (s == NULL)
+ return (ISC_R_NOMEMORY);
+ s->common.mctx = NULL;
+ isc_mem_attach(mctx, &s->common.mctx);
+ s->common.methods = &ixfr_rrstream_methods;
+ s->journal = NULL;
+
+ CHECK(dns_journal_open(mctx, journal_filename,
+ DNS_JOURNAL_READ, &s->journal));
+ CHECK(dns_journal_iter_init(s->journal, begin_serial, end_serial));
+
+ *sp = (rrstream_t *) s;
+ return (ISC_R_SUCCESS);
+
+ failure:
+ ixfr_rrstream_destroy((rrstream_t **) (void *)&s);
+ return (result);
+}
+
+static isc_result_t
+ixfr_rrstream_first(rrstream_t *rs) {
+ ixfr_rrstream_t *s = (ixfr_rrstream_t *) rs;
+ return (dns_journal_first_rr(s->journal));
+}
+
+static isc_result_t
+ixfr_rrstream_next(rrstream_t *rs) {
+ ixfr_rrstream_t *s = (ixfr_rrstream_t *) rs;
+ return (dns_journal_next_rr(s->journal));
+}
+
+static void
+ixfr_rrstream_current(rrstream_t *rs,
+ dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata)
+{
+ ixfr_rrstream_t *s = (ixfr_rrstream_t *) rs;
+ dns_journal_current_rr(s->journal, name, ttl, rdata);
+}
+
+static void
+ixfr_rrstream_destroy(rrstream_t **rsp) {
+ ixfr_rrstream_t *s = (ixfr_rrstream_t *) *rsp;
+ if (s->journal != 0)
+ dns_journal_destroy(&s->journal);
+ isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
+}
+
+static rrstream_methods_t ixfr_rrstream_methods = {
+ ixfr_rrstream_first,
+ ixfr_rrstream_next,
+ ixfr_rrstream_current,
+ rrstream_noop_pause,
+ ixfr_rrstream_destroy
+};
+
+/**************************************************************************/
+/*
+ * An 'axfr_rrstream_t' is an 'rrstream_t' that returns
+ * an AXFR-like RR stream from a database.
+ *
+ * The SOAs at the beginning and end of the transfer are
+ * not included in the stream.
+ */
+
+typedef struct axfr_rrstream {
+ rrstream_t common;
+ dns_rriterator_t it;
+ bool it_valid;
+} axfr_rrstream_t;
+
+/*
+ * Forward declarations.
+ */
+static void
+axfr_rrstream_destroy(rrstream_t **rsp);
+
+static rrstream_methods_t axfr_rrstream_methods;
+
+static isc_result_t
+axfr_rrstream_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *ver,
+ rrstream_t **sp)
+{
+ axfr_rrstream_t *s;
+ isc_result_t result;
+
+ INSIST(sp != NULL && *sp == NULL);
+
+ s = isc_mem_get(mctx, sizeof(*s));
+ if (s == NULL)
+ return (ISC_R_NOMEMORY);
+ s->common.mctx = NULL;
+ isc_mem_attach(mctx, &s->common.mctx);
+ s->common.methods = &axfr_rrstream_methods;
+ s->it_valid = false;
+
+ CHECK(dns_rriterator_init(&s->it, db, ver, 0));
+ s->it_valid = true;
+
+ *sp = (rrstream_t *) s;
+ return (ISC_R_SUCCESS);
+
+ failure:
+ axfr_rrstream_destroy((rrstream_t **) (void *)&s);
+ return (result);
+}
+
+static isc_result_t
+axfr_rrstream_first(rrstream_t *rs) {
+ axfr_rrstream_t *s = (axfr_rrstream_t *) rs;
+ isc_result_t result;
+ result = dns_rriterator_first(&s->it);
+ if (result != ISC_R_SUCCESS)
+ return (result);
+ /* Skip SOA records. */
+ for (;;) {
+ dns_name_t *name_dummy = NULL;
+ uint32_t ttl_dummy;
+ dns_rdata_t *rdata = NULL;
+ dns_rriterator_current(&s->it, &name_dummy,
+ &ttl_dummy, NULL, &rdata);
+ if (rdata->type != dns_rdatatype_soa)
+ break;
+ result = dns_rriterator_next(&s->it);
+ if (result != ISC_R_SUCCESS)
+ break;
+ }
+ return (result);
+}
+
+static isc_result_t
+axfr_rrstream_next(rrstream_t *rs) {
+ axfr_rrstream_t *s = (axfr_rrstream_t *) rs;
+ isc_result_t result;
+
+ /* Skip SOA records. */
+ for (;;) {
+ dns_name_t *name_dummy = NULL;
+ uint32_t ttl_dummy;
+ dns_rdata_t *rdata = NULL;
+ result = dns_rriterator_next(&s->it);
+ if (result != ISC_R_SUCCESS)
+ break;
+ dns_rriterator_current(&s->it, &name_dummy,
+ &ttl_dummy, NULL, &rdata);
+ if (rdata->type != dns_rdatatype_soa)
+ break;
+ }
+ return (result);
+}
+
+static void
+axfr_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata)
+{
+ axfr_rrstream_t *s = (axfr_rrstream_t *) rs;
+ dns_rriterator_current(&s->it, name, ttl, NULL, rdata);
+}
+
+static void
+axfr_rrstream_pause(rrstream_t *rs) {
+ axfr_rrstream_t *s = (axfr_rrstream_t *) rs;
+ dns_rriterator_pause(&s->it);
+}
+
+static void
+axfr_rrstream_destroy(rrstream_t **rsp) {
+ axfr_rrstream_t *s = (axfr_rrstream_t *) *rsp;
+ if (s->it_valid)
+ dns_rriterator_destroy(&s->it);
+ isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
+}
+
+static rrstream_methods_t axfr_rrstream_methods = {
+ axfr_rrstream_first,
+ axfr_rrstream_next,
+ axfr_rrstream_current,
+ axfr_rrstream_pause,
+ axfr_rrstream_destroy
+};
+
+/**************************************************************************/
+/*
+ * An 'soa_rrstream_t' is a degenerate 'rrstream_t' that returns
+ * a single SOA record.
+ */
+
+typedef struct soa_rrstream {
+ rrstream_t common;
+ dns_difftuple_t *soa_tuple;
+} soa_rrstream_t;
+
+/*
+ * Forward declarations.
+ */
+static void
+soa_rrstream_destroy(rrstream_t **rsp);
+
+static rrstream_methods_t soa_rrstream_methods;
+
+static isc_result_t
+soa_rrstream_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *ver,
+ rrstream_t **sp)
+{
+ soa_rrstream_t *s;
+ isc_result_t result;
+
+ INSIST(sp != NULL && *sp == NULL);
+
+ s = isc_mem_get(mctx, sizeof(*s));
+ if (s == NULL)
+ return (ISC_R_NOMEMORY);
+ s->common.mctx = NULL;
+ isc_mem_attach(mctx, &s->common.mctx);
+ s->common.methods = &soa_rrstream_methods;
+ s->soa_tuple = NULL;
+
+ CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_EXISTS,
+ &s->soa_tuple));
+
+ *sp = (rrstream_t *) s;
+ return (ISC_R_SUCCESS);
+
+ failure:
+ soa_rrstream_destroy((rrstream_t **) (void *)&s);
+ return (result);
+}
+
+static isc_result_t
+soa_rrstream_first(rrstream_t *rs) {
+ UNUSED(rs);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+soa_rrstream_next(rrstream_t *rs) {
+ UNUSED(rs);
+ return (ISC_R_NOMORE);
+}
+
+static void
+soa_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata)
+{
+ soa_rrstream_t *s = (soa_rrstream_t *) rs;
+ *name = &s->soa_tuple->name;
+ *ttl = s->soa_tuple->ttl;
+ *rdata = &s->soa_tuple->rdata;
+}
+
+static void
+soa_rrstream_destroy(rrstream_t **rsp) {
+ soa_rrstream_t *s = (soa_rrstream_t *) *rsp;
+ if (s->soa_tuple != NULL)
+ dns_difftuple_free(&s->soa_tuple);
+ isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
+}
+
+static rrstream_methods_t soa_rrstream_methods = {
+ soa_rrstream_first,
+ soa_rrstream_next,
+ soa_rrstream_current,
+ rrstream_noop_pause,
+ soa_rrstream_destroy
+};
+
+/**************************************************************************/
+/*
+ * A 'compound_rrstream_t' objects owns a soa_rrstream
+ * and another rrstream, the "data stream". It returns
+ * a concatenated stream consisting of the soa_rrstream, then
+ * the data stream, then the soa_rrstream again.
+ *
+ * The component streams are owned by the compound_rrstream_t
+ * and are destroyed with it.
+ */
+
+typedef struct compound_rrstream {
+ rrstream_t common;
+ rrstream_t *components[3];
+ int state;
+ isc_result_t result;
+} compound_rrstream_t;
+
+/*
+ * Forward declarations.
+ */
+static void
+compound_rrstream_destroy(rrstream_t **rsp);
+
+static isc_result_t
+compound_rrstream_next(rrstream_t *rs);
+
+static rrstream_methods_t compound_rrstream_methods;
+
+/*
+ * Requires:
+ * soa_stream != NULL && *soa_stream != NULL
+ * data_stream != NULL && *data_stream != NULL
+ * sp != NULL && *sp == NULL
+ *
+ * Ensures:
+ * *soa_stream == NULL
+ * *data_stream == NULL
+ * *sp points to a valid compound_rrstream_t
+ * The soa and data streams will be destroyed
+ * when the compound_rrstream_t is destroyed.
+ */
+static isc_result_t
+compound_rrstream_create(isc_mem_t *mctx, rrstream_t **soa_stream,
+ rrstream_t **data_stream, rrstream_t **sp)
+{
+ compound_rrstream_t *s;
+
+ INSIST(sp != NULL && *sp == NULL);
+
+ s = isc_mem_get(mctx, sizeof(*s));
+ if (s == NULL)
+ return (ISC_R_NOMEMORY);
+ s->common.mctx = NULL;
+ isc_mem_attach(mctx, &s->common.mctx);
+ s->common.methods = &compound_rrstream_methods;
+ s->components[0] = *soa_stream;
+ s->components[1] = *data_stream;
+ s->components[2] = *soa_stream;
+ s->state = -1;
+ s->result = ISC_R_FAILURE;
+
+ *soa_stream = NULL;
+ *data_stream = NULL;
+ *sp = (rrstream_t *) s;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+compound_rrstream_first(rrstream_t *rs) {
+ compound_rrstream_t *s = (compound_rrstream_t *) rs;
+ s->state = 0;
+ do {
+ rrstream_t *curstream = s->components[s->state];
+ s->result = curstream->methods->first(curstream);
+ } while (s->result == ISC_R_NOMORE && s->state < 2);
+ return (s->result);
+}
+
+static isc_result_t
+compound_rrstream_next(rrstream_t *rs) {
+ compound_rrstream_t *s = (compound_rrstream_t *) rs;
+ rrstream_t *curstream = s->components[s->state];
+ s->result = curstream->methods->next(curstream);
+ while (s->result == ISC_R_NOMORE) {
+ /*
+ * Make sure locks held by the current stream
+ * are released before we switch streams.
+ */
+ curstream->methods->pause(curstream);
+ if (s->state == 2)
+ return (ISC_R_NOMORE);
+ s->state++;
+ curstream = s->components[s->state];
+ s->result = curstream->methods->first(curstream);
+ }
+ return (s->result);
+}
+
+static void
+compound_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata)
+{
+ compound_rrstream_t *s = (compound_rrstream_t *) rs;
+ rrstream_t *curstream;
+ INSIST(0 <= s->state && s->state < 3);
+ INSIST(s->result == ISC_R_SUCCESS);
+ curstream = s->components[s->state];
+ curstream->methods->current(curstream, name, ttl, rdata);
+}
+
+static void
+compound_rrstream_pause(rrstream_t *rs)
+{
+ compound_rrstream_t *s = (compound_rrstream_t *) rs;
+ rrstream_t *curstream;
+ INSIST(0 <= s->state && s->state < 3);
+ curstream = s->components[s->state];
+ curstream->methods->pause(curstream);
+}
+
+static void
+compound_rrstream_destroy(rrstream_t **rsp) {
+ compound_rrstream_t *s = (compound_rrstream_t *) *rsp;
+ s->components[0]->methods->destroy(&s->components[0]);
+ s->components[1]->methods->destroy(&s->components[1]);
+ s->components[2] = NULL; /* Copy of components[0]. */
+ isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
+}
+
+static rrstream_methods_t compound_rrstream_methods = {
+ compound_rrstream_first,
+ compound_rrstream_next,
+ compound_rrstream_current,
+ compound_rrstream_pause,
+ compound_rrstream_destroy
+};
+
+/**************************************************************************/
+/*
+ * An 'xfrout_ctx_t' contains the state of an outgoing AXFR or IXFR
+ * in progress.
+ */
+
+typedef struct {
+ isc_mem_t *mctx;
+ ns_client_t *client;
+ unsigned int id; /* ID of request */
+ dns_name_t *qname; /* Question name of request */
+ dns_rdatatype_t qtype; /* dns_rdatatype_{a,i}xfr */
+ dns_rdataclass_t qclass;
+ dns_zone_t *zone; /* (necessary for stats) */
+ dns_db_t *db;
+ dns_dbversion_t *ver;
+ isc_quota_t *quota;
+ rrstream_t *stream; /* The XFR RR stream */
+ bool end_of_stream; /* EOS has been reached */
+ isc_buffer_t buf; /* Buffer for message owner
+ names and rdatas */
+ isc_buffer_t txlenbuf; /* Transmit length buffer */
+ isc_buffer_t txbuf; /* Transmit message buffer */
+ void *txmem;
+ unsigned int txmemlen;
+ unsigned int nmsg; /* Number of messages sent */
+ dns_tsigkey_t *tsigkey; /* Key used to create TSIG */
+ isc_buffer_t *lasttsig; /* the last TSIG */
+ bool verified_tsig; /* verified request MAC */
+ bool many_answers;
+ int sends; /* Send in progress */
+ bool shuttingdown;
+ const char *mnemonic; /* Style of transfer */
+} xfrout_ctx_t;
+
+static isc_result_t
+xfrout_ctx_create(isc_mem_t *mctx, ns_client_t *client,
+ unsigned int id, dns_name_t *qname, dns_rdatatype_t qtype,
+ dns_rdataclass_t qclass, dns_zone_t *zone,
+ dns_db_t *db, dns_dbversion_t *ver, isc_quota_t *quota,
+ rrstream_t *stream, dns_tsigkey_t *tsigkey,
+ isc_buffer_t *lasttsig,
+ bool verified_tsig,
+ unsigned int maxtime,
+ unsigned int idletime,
+ bool many_answers,
+ xfrout_ctx_t **xfrp);
+
+static void
+sendstream(xfrout_ctx_t *xfr);
+
+static void
+xfrout_senddone(isc_task_t *task, isc_event_t *event);
+
+static void
+xfrout_fail(xfrout_ctx_t *xfr, isc_result_t result, const char *msg);
+
+static void
+xfrout_maybe_destroy(xfrout_ctx_t *xfr);
+
+static void
+xfrout_ctx_destroy(xfrout_ctx_t **xfrp);
+
+static void
+xfrout_client_shutdown(void *arg, isc_result_t result);
+
+static void
+xfrout_log1(ns_client_t *client, dns_name_t *zonename,
+ dns_rdataclass_t rdclass, int level,
+ const char *fmt, ...) ISC_FORMAT_PRINTF(5, 6);
+
+static void
+xfrout_log(xfrout_ctx_t *xfr, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+/**************************************************************************/
+
+void
+ns_xfr_start(ns_client_t *client, dns_rdatatype_t reqtype) {
+ isc_result_t result;
+ dns_name_t *question_name;
+ dns_rdataset_t *question_rdataset;
+ dns_zone_t *zone = NULL, *raw = NULL, *mayberaw;
+ dns_db_t *db = NULL;
+ dns_dbversion_t *ver = NULL;
+ dns_rdataclass_t question_class;
+ rrstream_t *soa_stream = NULL;
+ rrstream_t *data_stream = NULL;
+ rrstream_t *stream = NULL;
+ dns_difftuple_t *current_soa_tuple = NULL;
+ dns_name_t *soa_name;
+ dns_rdataset_t *soa_rdataset;
+ dns_rdata_t soa_rdata = DNS_RDATA_INIT;
+ bool have_soa = false;
+ const char *mnemonic = NULL;
+ isc_mem_t *mctx = client->mctx;
+ dns_message_t *request = client->message;
+ xfrout_ctx_t *xfr = NULL;
+ isc_quota_t *quota = NULL;
+ dns_transfer_format_t format = client->view->transfer_format;
+ isc_netaddr_t na;
+ dns_peer_t *peer = NULL;
+ isc_buffer_t *tsigbuf = NULL;
+ char *journalfile;
+ char msg[NS_CLIENT_ACLMSGSIZE("zone transfer")];
+ char keyname[DNS_NAME_FORMATSIZE];
+ bool is_poll = false;
+ bool is_dlz = false;
+ bool is_ixfr = false;
+ uint32_t begin_serial = 0, current_serial;
+
+ switch (reqtype) {
+ case dns_rdatatype_axfr:
+ mnemonic = "AXFR";
+ break;
+ case dns_rdatatype_ixfr:
+ mnemonic = "IXFR";
+ break;
+ default:
+ INSIST(0);
+ break;
+ }
+
+ ns_client_log(client,
+ DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT,
+ ISC_LOG_DEBUG(6), "%s request", mnemonic);
+ /*
+ * Apply quota.
+ */
+ result = isc_quota_attach(&ns_g_server->xfroutquota, &quota);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(XFROUT_COMMON_LOGARGS, ISC_LOG_WARNING,
+ "%s request denied: %s", mnemonic,
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * Interpret the question section.
+ */
+ result = dns_message_firstname(request, DNS_SECTION_QUESTION);
+ INSIST(result == ISC_R_SUCCESS);
+
+ /*
+ * The question section must contain exactly one question, and
+ * it must be for AXFR/IXFR as appropriate.
+ */
+ question_name = NULL;
+ dns_message_currentname(request, DNS_SECTION_QUESTION, &question_name);
+ question_rdataset = ISC_LIST_HEAD(question_name->list);
+ question_class = question_rdataset->rdclass;
+ INSIST(question_rdataset->type == reqtype);
+ if (ISC_LIST_NEXT(question_rdataset, link) != NULL)
+ FAILC(DNS_R_FORMERR, "multiple questions");
+ result = dns_message_nextname(request, DNS_SECTION_QUESTION);
+ if (result != ISC_R_NOMORE)
+ FAILC(DNS_R_FORMERR, "multiple questions");
+
+ result = dns_zt_find(client->view->zonetable, question_name, 0, NULL,
+ &zone);
+
+ if (result != ISC_R_SUCCESS || dns_zone_gettype(zone) == dns_zone_dlz) {
+ /*
+ * The normal zone table does not have a match, or this is
+ * marked in the zone table as a DLZ zone. Check the DLZ
+ * databases for a match.
+ */
+ if (! ISC_LIST_EMPTY(client->view->dlz_searched)) {
+ result = dns_dlzallowzonexfr(client->view,
+ question_name,
+ &client->peeraddr,
+ &db);
+
+ if (result == ISC_R_NOPERM) {
+ char _buf1[DNS_NAME_FORMATSIZE];
+ char _buf2[DNS_RDATACLASS_FORMATSIZE];
+
+ result = DNS_R_REFUSED;
+ dns_name_format(question_name, _buf1,
+ sizeof(_buf1));
+ dns_rdataclass_format(question_class,
+ _buf2, sizeof(_buf2));
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_XFER_OUT,
+ ISC_LOG_ERROR,
+ "zone transfer '%s/%s' denied",
+ _buf1, _buf2);
+ goto failure;
+ }
+ if (result != ISC_R_SUCCESS)
+ FAILQ(DNS_R_NOTAUTH, "non-authoritative zone",
+ question_name, question_class);
+ is_dlz = true;
+ } else {
+ /*
+ * not DLZ and not in normal zone table, we are
+ * not authoritative
+ */
+ FAILQ(DNS_R_NOTAUTH, "non-authoritative zone",
+ question_name, question_class);
+ }
+ } else {
+ /* zone table has a match */
+ switch(dns_zone_gettype(zone)) {
+ /* Master and slave zones are OK for transfer. */
+ case dns_zone_master:
+ case dns_zone_slave:
+ case dns_zone_dlz:
+ break;
+ default:
+ FAILQ(DNS_R_NOTAUTH, "non-authoritative zone",
+ question_name, question_class);
+ }
+ CHECK(dns_zone_getdb(zone, &db));
+ dns_db_currentversion(db, &ver);
+ }
+
+ xfrout_log1(client, question_name, question_class, ISC_LOG_DEBUG(6),
+ "%s question section OK", mnemonic);
+
+ /*
+ * Check the authority section. Look for a SOA record with
+ * the same name and class as the question.
+ */
+ for (result = dns_message_firstname(request, DNS_SECTION_AUTHORITY);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(request, DNS_SECTION_AUTHORITY))
+ {
+ soa_name = NULL;
+ dns_message_currentname(request, DNS_SECTION_AUTHORITY,
+ &soa_name);
+
+ /*
+ * Ignore data whose owner name is not the zone apex.
+ */
+ if (! dns_name_equal(soa_name, question_name))
+ continue;
+
+ for (soa_rdataset = ISC_LIST_HEAD(soa_name->list);
+ soa_rdataset != NULL;
+ soa_rdataset = ISC_LIST_NEXT(soa_rdataset, link))
+ {
+ /*
+ * Ignore non-SOA data.
+ */
+ if (soa_rdataset->type != dns_rdatatype_soa)
+ continue;
+ if (soa_rdataset->rdclass != question_class)
+ continue;
+
+ CHECK(dns_rdataset_first(soa_rdataset));
+ dns_rdataset_current(soa_rdataset, &soa_rdata);
+ result = dns_rdataset_next(soa_rdataset);
+ if (result == ISC_R_SUCCESS)
+ FAILC(DNS_R_FORMERR,
+ "IXFR authority section "
+ "has multiple SOAs");
+ have_soa = true;
+ goto got_soa;
+ }
+ }
+ got_soa:
+ if (result != ISC_R_NOMORE)
+ CHECK(result);
+
+ xfrout_log1(client, question_name, question_class, ISC_LOG_DEBUG(6),
+ "%s authority section OK", mnemonic);
+
+ /*
+ * If not a DLZ zone, decide whether to allow this transfer.
+ */
+ if (!is_dlz) {
+ ns_client_aclmsg("zone transfer", question_name, reqtype,
+ client->view->rdclass, msg, sizeof(msg));
+ CHECK(ns_client_checkacl(client, NULL, msg,
+ dns_zone_getxfracl(zone),
+ true, ISC_LOG_ERROR));
+ }
+
+ /*
+ * AXFR over UDP is not possible.
+ */
+ if (reqtype == dns_rdatatype_axfr &&
+ (client->attributes & NS_CLIENTATTR_TCP) == 0)
+ FAILC(DNS_R_FORMERR, "attempted AXFR over UDP");
+
+ /*
+ * Look up the requesting server in the peer table.
+ */
+ isc_netaddr_fromsockaddr(&na, &client->peeraddr);
+ (void)dns_peerlist_peerbyaddr(client->view->peers, &na, &peer);
+
+ /*
+ * Decide on the transfer format (one-answer or many-answers).
+ */
+ if (peer != NULL)
+ (void)dns_peer_gettransferformat(peer, &format);
+
+ /*
+ * Get a dynamically allocated copy of the current SOA.
+ */
+ if (is_dlz)
+ dns_db_currentversion(db, &ver);
+
+ CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_EXISTS,
+ &current_soa_tuple));
+
+ current_serial = dns_soa_getserial(&current_soa_tuple->rdata);
+ if (reqtype == dns_rdatatype_ixfr) {
+ bool provide_ixfr;
+
+ /*
+ * Outgoing IXFR may have been disabled for this peer
+ * or globally.
+ */
+ provide_ixfr = client->view->provideixfr;
+ if (peer != NULL)
+ (void) dns_peer_getprovideixfr(peer, &provide_ixfr);
+ if (provide_ixfr == false)
+ goto axfr_fallback;
+
+ if (! have_soa)
+ FAILC(DNS_R_FORMERR,
+ "IXFR request missing SOA");
+
+ begin_serial = dns_soa_getserial(&soa_rdata);
+
+ /*
+ * RFC1995 says "If an IXFR query with the same or
+ * newer version number than that of the server
+ * is received, it is replied to with a single SOA
+ * record of the server's current version, just as
+ * in AXFR". The claim about AXFR is incorrect,
+ * but other than that, we do as the RFC says.
+ *
+ * Sending a single SOA record is also how we refuse
+ * IXFR over UDP (currently, we always do).
+ */
+ if (DNS_SERIAL_GE(begin_serial, current_serial) ||
+ (client->attributes & NS_CLIENTATTR_TCP) == 0)
+ {
+ CHECK(soa_rrstream_create(mctx, db, ver, &stream));
+ is_poll = true;
+ goto have_stream;
+ }
+ journalfile = is_dlz ? NULL : dns_zone_getjournal(zone);
+ if (journalfile != NULL)
+ result = ixfr_rrstream_create(mctx,
+ journalfile,
+ begin_serial,
+ current_serial,
+ &data_stream);
+ else
+ result = ISC_R_NOTFOUND;
+ if (result == ISC_R_NOTFOUND ||
+ result == ISC_R_RANGE) {
+ xfrout_log1(client, question_name, question_class,
+ ISC_LOG_DEBUG(4),
+ "IXFR version not in journal, "
+ "falling back to AXFR");
+ mnemonic = "AXFR-style IXFR";
+ goto axfr_fallback;
+ }
+ CHECK(result);
+ is_ixfr = true;
+ } else {
+ axfr_fallback:
+ CHECK(axfr_rrstream_create(mctx, db, ver, &data_stream));
+ }
+
+ /*
+ * Bracket the data stream with SOAs.
+ */
+ CHECK(soa_rrstream_create(mctx, db, ver, &soa_stream));
+ CHECK(compound_rrstream_create(mctx, &soa_stream, &data_stream,
+ &stream));
+ soa_stream = NULL;
+ data_stream = NULL;
+
+ have_stream:
+ CHECK(dns_message_getquerytsig(request, mctx, &tsigbuf));
+ /*
+ * Create the xfrout context object. This transfers the ownership
+ * of "stream", "db", "ver", and "quota" to the xfrout context object.
+ */
+
+
+
+ if (is_dlz)
+ CHECK(xfrout_ctx_create(mctx, client, request->id,
+ question_name, reqtype, question_class,
+ zone, db, ver, quota, stream,
+ dns_message_gettsigkey(request),
+ tsigbuf,
+ request->verified_sig,
+ 3600,
+ 3600,
+ (format == dns_many_answers) ?
+ true : false,
+ &xfr));
+ else
+ CHECK(xfrout_ctx_create(mctx, client, request->id,
+ question_name, reqtype, question_class,
+ zone, db, ver, quota, stream,
+ dns_message_gettsigkey(request),
+ tsigbuf,
+ request->verified_sig,
+ dns_zone_getmaxxfrout(zone),
+ dns_zone_getidleout(zone),
+ (format == dns_many_answers) ?
+ true : false,
+ &xfr));
+
+ xfr->mnemonic = mnemonic;
+ stream = NULL;
+ quota = NULL;
+
+ CHECK(xfr->stream->methods->first(xfr->stream));
+
+ if (xfr->tsigkey != NULL)
+ dns_name_format(&xfr->tsigkey->name, keyname, sizeof(keyname));
+ else
+ keyname[0] = '\0';
+ if (is_poll)
+ xfrout_log1(client, question_name, question_class,
+ ISC_LOG_DEBUG(1), "IXFR poll up to date%s%s",
+ (xfr->tsigkey != NULL) ? ": TSIG " : "", keyname);
+ else if (is_ixfr)
+ xfrout_log1(client, question_name, question_class,
+ ISC_LOG_INFO, "%s started%s%s (serial %u -> %u)",
+ mnemonic, (xfr->tsigkey != NULL) ? ": TSIG " : "",
+ keyname, begin_serial, current_serial);
+ else
+ xfrout_log1(client, question_name, question_class,
+ ISC_LOG_INFO, "%s started%s%s (serial %u)",
+ mnemonic, (xfr->tsigkey != NULL) ? ": TSIG " : "",
+ keyname, current_serial);
+
+
+ if (zone != NULL) {
+ dns_zone_getraw(zone, &raw);
+ mayberaw = (raw != NULL) ? raw : zone;
+ if ((client->attributes & NS_CLIENTATTR_WANTEXPIRE) != 0 &&
+ dns_zone_gettype(mayberaw) == dns_zone_slave) {
+ isc_time_t expiretime;
+ uint32_t secs;
+ dns_zone_getexpiretime(zone, &expiretime);
+ secs = isc_time_seconds(&expiretime);
+ if (secs >= client->now && result == ISC_R_SUCCESS) {
+ client->attributes |= NS_CLIENTATTR_HAVEEXPIRE;
+ client->expire = secs - client->now;
+ }
+ }
+ if (raw != NULL)
+ dns_zone_detach(&raw);
+ }
+
+ /*
+ * Hand the context over to sendstream(). Set xfr to NULL;
+ * sendstream() is responsible for either passing the
+ * context on to a later event handler or destroying it.
+ */
+ sendstream(xfr);
+ xfr = NULL;
+
+ result = ISC_R_SUCCESS;
+
+ failure:
+ if (result == DNS_R_REFUSED)
+ inc_stats(zone, dns_nsstatscounter_xfrrej);
+ if (quota != NULL)
+ isc_quota_detach(&quota);
+ if (current_soa_tuple != NULL)
+ dns_difftuple_free(&current_soa_tuple);
+ if (stream != NULL)
+ stream->methods->destroy(&stream);
+ if (soa_stream != NULL)
+ soa_stream->methods->destroy(&soa_stream);
+ if (data_stream != NULL)
+ data_stream->methods->destroy(&data_stream);
+ if (ver != NULL)
+ dns_db_closeversion(db, &ver, false);
+ if (db != NULL)
+ dns_db_detach(&db);
+ if (zone != NULL)
+ dns_zone_detach(&zone);
+ /* XXX kludge */
+ if (xfr != NULL) {
+ xfrout_fail(xfr, result, "setting up zone transfer");
+ } else if (result != ISC_R_SUCCESS) {
+ ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT,
+ NS_LOGMODULE_XFER_OUT,
+ ISC_LOG_DEBUG(3), "zone transfer setup failed");
+ ns_client_error(client, result);
+ }
+}
+
+static isc_result_t
+xfrout_ctx_create(isc_mem_t *mctx, ns_client_t *client, unsigned int id,
+ dns_name_t *qname, dns_rdatatype_t qtype,
+ dns_rdataclass_t qclass, dns_zone_t *zone,
+ dns_db_t *db, dns_dbversion_t *ver, isc_quota_t *quota,
+ rrstream_t *stream, dns_tsigkey_t *tsigkey,
+ isc_buffer_t *lasttsig, bool verified_tsig,
+ unsigned int maxtime, unsigned int idletime,
+ bool many_answers, xfrout_ctx_t **xfrp)
+{
+ xfrout_ctx_t *xfr;
+ isc_result_t result;
+ unsigned int len;
+ void *mem;
+
+ INSIST(xfrp != NULL && *xfrp == NULL);
+ xfr = isc_mem_get(mctx, sizeof(*xfr));
+ if (xfr == NULL)
+ return (ISC_R_NOMEMORY);
+ xfr->mctx = NULL;
+ isc_mem_attach(mctx, &xfr->mctx);
+ xfr->client = NULL;
+ ns_client_attach(client, &xfr->client);
+ xfr->id = id;
+ xfr->qname = qname;
+ xfr->qtype = qtype;
+ xfr->qclass = qclass;
+ xfr->zone = NULL;
+ xfr->db = NULL;
+ xfr->ver = NULL;
+ if (zone != NULL) /* zone will be NULL if it's DLZ */
+ dns_zone_attach(zone, &xfr->zone);
+ dns_db_attach(db, &xfr->db);
+ dns_db_attachversion(db, ver, &xfr->ver);
+ xfr->end_of_stream = false;
+ xfr->tsigkey = tsigkey;
+ xfr->lasttsig = lasttsig;
+ xfr->verified_tsig = verified_tsig;
+ xfr->nmsg = 0;
+ xfr->many_answers = many_answers;
+ xfr->sends = 0;
+ xfr->shuttingdown = false;
+ xfr->mnemonic = NULL;
+ xfr->buf.base = NULL;
+ xfr->buf.length = 0;
+ xfr->txmem = NULL;
+ xfr->txmemlen = 0;
+ xfr->stream = NULL;
+ xfr->quota = NULL;
+
+ /*
+ * Allocate a temporary buffer for the uncompressed response
+ * message data. The size should be no more than 65535 bytes
+ * so that the compressed data will fit in a TCP message,
+ * and no less than 65535 bytes so that an almost maximum-sized
+ * RR will fit. Note that although 65535-byte RRs are allowed
+ * in principle, they cannot be zone-transferred (at least not
+ * if uncompressible), because the message and RR headers would
+ * push the size of the TCP message over the 65536 byte limit.
+ */
+ len = 65535;
+ mem = isc_mem_get(mctx, len);
+ if (mem == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto failure;
+ }
+ isc_buffer_init(&xfr->buf, mem, len);
+
+ /*
+ * Allocate another temporary buffer for the compressed
+ * response message and its TCP length prefix.
+ */
+ len = 2 + 65535;
+ mem = isc_mem_get(mctx, len);
+ if (mem == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto failure;
+ }
+ isc_buffer_init(&xfr->txlenbuf, mem, 2);
+ isc_buffer_init(&xfr->txbuf, (char *) mem + 2, len - 2);
+ xfr->txmem = mem;
+ xfr->txmemlen = len;
+
+ CHECK(dns_timer_setidle(xfr->client->timer,
+ maxtime, idletime, false));
+
+ /*
+ * Register a shutdown callback with the client, so that we
+ * can stop the transfer immediately when the client task
+ * gets a shutdown event.
+ */
+ xfr->client->shutdown = xfrout_client_shutdown;
+ xfr->client->shutdown_arg = xfr;
+ /*
+ * These MUST be after the last "goto failure;" / CHECK to
+ * prevent a double free by the caller.
+ */
+ xfr->quota = quota;
+ xfr->stream = stream;
+
+ *xfrp = xfr;
+ return (ISC_R_SUCCESS);
+
+failure:
+ xfrout_ctx_destroy(&xfr);
+ return (result);
+}
+
+
+/*
+ * Arrange to send as much as we can of "stream" without blocking.
+ *
+ * Requires:
+ * The stream iterator is initialized and points at an RR,
+ * or possibly at the end of the stream (that is, the
+ * _first method of the iterator has been called).
+ */
+static void
+sendstream(xfrout_ctx_t *xfr) {
+ dns_message_t *tcpmsg = NULL;
+ dns_message_t *msg = NULL; /* Client message if UDP, tcpmsg if TCP */
+ isc_result_t result;
+ isc_region_t used;
+ isc_region_t region;
+ dns_rdataset_t *qrdataset;
+ dns_name_t *msgname = NULL;
+ dns_rdata_t *msgrdata = NULL;
+ dns_rdatalist_t *msgrdl = NULL;
+ dns_rdataset_t *msgrds = NULL;
+ dns_compress_t cctx;
+ bool cleanup_cctx = false;
+ bool is_tcp;
+
+ int n_rrs;
+
+ isc_buffer_clear(&xfr->buf);
+ isc_buffer_clear(&xfr->txlenbuf);
+ isc_buffer_clear(&xfr->txbuf);
+
+ is_tcp = (xfr->client->attributes & NS_CLIENTATTR_TCP);
+ if (!is_tcp) {
+ /*
+ * In the UDP case, we put the response data directly into
+ * the client message.
+ */
+ msg = xfr->client->message;
+ CHECK(dns_message_reply(msg, true));
+ } else {
+ /*
+ * TCP. Build a response dns_message_t, temporarily storing
+ * the raw, uncompressed owner names and RR data contiguously
+ * in xfr->buf. We know that if the uncompressed data fits
+ * in xfr->buf, the compressed data will surely fit in a TCP
+ * message.
+ */
+
+ CHECK(dns_message_create(xfr->mctx,
+ DNS_MESSAGE_INTENTRENDER, &tcpmsg));
+ msg = tcpmsg;
+
+ msg->id = xfr->id;
+ msg->rcode = dns_rcode_noerror;
+ msg->flags = DNS_MESSAGEFLAG_QR | DNS_MESSAGEFLAG_AA;
+ if ((xfr->client->attributes & NS_CLIENTATTR_RA) != 0)
+ msg->flags |= DNS_MESSAGEFLAG_RA;
+ CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
+ CHECK(dns_message_setquerytsig(msg, xfr->lasttsig));
+ if (xfr->lasttsig != NULL)
+ isc_buffer_free(&xfr->lasttsig);
+ msg->verified_sig = xfr->verified_tsig;
+
+ /*
+ * Add a EDNS option to the message?
+ */
+ if ((xfr->client->attributes & NS_CLIENTATTR_WANTOPT) != 0) {
+ dns_rdataset_t *opt = NULL;
+
+ CHECK(ns_client_addopt(xfr->client, msg, &opt));
+ CHECK(dns_message_setopt(msg, opt));
+ /*
+ * Add to first message only.
+ */
+ xfr->client->attributes &= ~NS_CLIENTATTR_WANTNSID;
+ xfr->client->attributes &= ~NS_CLIENTATTR_HAVEEXPIRE;
+ }
+
+ /*
+ * Account for reserved space.
+ */
+ if (xfr->tsigkey != NULL)
+ INSIST(msg->reserved != 0U);
+ isc_buffer_add(&xfr->buf, msg->reserved);
+
+ /*
+ * Include a question section in the first message only.
+ * BIND 8.2.1 will not recognize an IXFR if it does not
+ * have a question section.
+ */
+ if (xfr->nmsg == 0) {
+ dns_name_t *qname = NULL;
+ isc_region_t r;
+
+ /*
+ * Reserve space for the 12-byte message header
+ * and 4 bytes of question.
+ */
+ isc_buffer_add(&xfr->buf, 12 + 4);
+
+ qrdataset = NULL;
+ result = dns_message_gettemprdataset(msg, &qrdataset);
+ if (result != ISC_R_SUCCESS)
+ goto failure;
+ dns_rdataset_makequestion(qrdataset,
+ xfr->client->message->rdclass,
+ xfr->qtype);
+
+ result = dns_message_gettempname(msg, &qname);
+ if (result != ISC_R_SUCCESS)
+ goto failure;
+ dns_name_init(qname, NULL);
+ isc_buffer_availableregion(&xfr->buf, &r);
+ INSIST(r.length >= xfr->qname->length);
+ r.length = xfr->qname->length;
+ isc_buffer_putmem(&xfr->buf, xfr->qname->ndata,
+ xfr->qname->length);
+ dns_name_fromregion(qname, &r);
+ ISC_LIST_INIT(qname->list);
+ ISC_LIST_APPEND(qname->list, qrdataset, link);
+
+ dns_message_addname(msg, qname, DNS_SECTION_QUESTION);
+ } else {
+ /*
+ * Reserve space for the 12-byte message header
+ */
+ isc_buffer_add(&xfr->buf, 12);
+ msg->tcp_continuation = 1;
+ }
+ }
+
+ /*
+ * Try to fit in as many RRs as possible, unless "one-answer"
+ * format has been requested.
+ */
+ for (n_rrs = 0; ; n_rrs++) {
+ dns_name_t *name = NULL;
+ uint32_t ttl;
+ dns_rdata_t *rdata = NULL;
+
+ unsigned int size;
+ isc_region_t r;
+
+ msgname = NULL;
+ msgrdata = NULL;
+ msgrdl = NULL;
+ msgrds = NULL;
+
+ xfr->stream->methods->current(xfr->stream,
+ &name, &ttl, &rdata);
+ size = name->length + 10 + rdata->length;
+ isc_buffer_availableregion(&xfr->buf, &r);
+ if (size >= r.length) {
+ /*
+ * RR would not fit. If there are other RRs in the
+ * buffer, send them now and leave this RR to the
+ * next message. If this RR overflows the buffer
+ * all by itself, fail.
+ *
+ * In theory some RRs might fit in a TCP message
+ * when compressed even if they do not fit when
+ * uncompressed, but surely we don't want
+ * to send such monstrosities to an unsuspecting
+ * slave.
+ */
+ if (n_rrs == 0) {
+ xfrout_log(xfr, ISC_LOG_WARNING,
+ "RR too large for zone transfer "
+ "(%d bytes)", size);
+ /* XXX DNS_R_RRTOOLARGE? */
+ result = ISC_R_NOSPACE;
+ goto failure;
+ }
+ break;
+ }
+
+ if (isc_log_wouldlog(ns_g_lctx, XFROUT_RR_LOGLEVEL))
+ log_rr(name, rdata, ttl); /* XXX */
+
+ result = dns_message_gettempname(msg, &msgname);
+ if (result != ISC_R_SUCCESS)
+ goto failure;
+ dns_name_init(msgname, NULL);
+ isc_buffer_availableregion(&xfr->buf, &r);
+ INSIST(r.length >= name->length);
+ r.length = name->length;
+ isc_buffer_putmem(&xfr->buf, name->ndata, name->length);
+ dns_name_fromregion(msgname, &r);
+
+ /* Reserve space for RR header. */
+ isc_buffer_add(&xfr->buf, 10);
+
+ result = dns_message_gettemprdata(msg, &msgrdata);
+ if (result != ISC_R_SUCCESS)
+ goto failure;
+ isc_buffer_availableregion(&xfr->buf, &r);
+ r.length = rdata->length;
+ isc_buffer_putmem(&xfr->buf, rdata->data, rdata->length);
+ dns_rdata_init(msgrdata);
+ dns_rdata_fromregion(msgrdata,
+ rdata->rdclass, rdata->type, &r);
+
+ result = dns_message_gettemprdatalist(msg, &msgrdl);
+ if (result != ISC_R_SUCCESS)
+ goto failure;
+ msgrdl->type = rdata->type;
+ msgrdl->rdclass = rdata->rdclass;
+ msgrdl->ttl = ttl;
+ if (rdata->type == dns_rdatatype_sig ||
+ rdata->type == dns_rdatatype_rrsig)
+ msgrdl->covers = dns_rdata_covers(rdata);
+ else
+ msgrdl->covers = dns_rdatatype_none;
+ ISC_LIST_APPEND(msgrdl->rdata, msgrdata, link);
+
+ result = dns_message_gettemprdataset(msg, &msgrds);
+ if (result != ISC_R_SUCCESS)
+ goto failure;
+ result = dns_rdatalist_tordataset(msgrdl, msgrds);
+ INSIST(result == ISC_R_SUCCESS);
+
+ ISC_LIST_APPEND(msgname->list, msgrds, link);
+
+ dns_message_addname(msg, msgname, DNS_SECTION_ANSWER);
+ msgname = NULL;
+
+ result = xfr->stream->methods->next(xfr->stream);
+ if (result == ISC_R_NOMORE) {
+ xfr->end_of_stream = true;
+ break;
+ }
+ CHECK(result);
+
+ if (! xfr->many_answers)
+ break;
+ /*
+ * At this stage, at least 1 RR has been rendered into
+ * the message. Check if we want to clamp this message
+ * here (TCP only).
+ */
+ if ((isc_buffer_usedlength(&xfr->buf) >=
+ ns_g_server->transfer_tcp_message_size) && is_tcp)
+ break;
+ }
+
+ if (is_tcp) {
+ CHECK(dns_compress_init(&cctx, -1, xfr->mctx));
+ dns_compress_setsensitive(&cctx, true);
+ cleanup_cctx = true;
+ CHECK(dns_message_renderbegin(msg, &cctx, &xfr->txbuf));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_QUESTION, 0));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_ANSWER, 0));
+ CHECK(dns_message_renderend(msg));
+ dns_compress_invalidate(&cctx);
+ cleanup_cctx = false;
+
+ isc_buffer_usedregion(&xfr->txbuf, &used);
+ isc_buffer_putuint16(&xfr->txlenbuf,
+ (uint16_t)used.length);
+ region.base = xfr->txlenbuf.base;
+ region.length = 2 + used.length;
+ xfrout_log(xfr, ISC_LOG_DEBUG(8),
+ "sending TCP message of %d bytes",
+ used.length);
+ CHECK(isc_socket_send(xfr->client->tcpsocket, /* XXX */
+ &region, xfr->client->task,
+ xfrout_senddone,
+ xfr));
+ xfr->sends++;
+ } else {
+ xfrout_log(xfr, ISC_LOG_DEBUG(8), "sending IXFR UDP response");
+ ns_client_send(xfr->client);
+ xfr->stream->methods->pause(xfr->stream);
+ xfrout_ctx_destroy(&xfr);
+ return;
+ }
+
+ /* Advance lasttsig to be the last TSIG generated */
+ CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
+
+ xfr->nmsg++;
+
+ failure:
+ if (msgname != NULL) {
+ if (msgrds != NULL) {
+ if (dns_rdataset_isassociated(msgrds))
+ dns_rdataset_disassociate(msgrds);
+ dns_message_puttemprdataset(msg, &msgrds);
+ }
+ if (msgrdl != NULL) {
+ ISC_LIST_UNLINK(msgrdl->rdata, msgrdata, link);
+ dns_message_puttemprdatalist(msg, &msgrdl);
+ }
+ if (msgrdata != NULL)
+ dns_message_puttemprdata(msg, &msgrdata);
+ dns_message_puttempname(msg, &msgname);
+ }
+
+ if (tcpmsg != NULL)
+ dns_message_destroy(&tcpmsg);
+
+ if (cleanup_cctx)
+ dns_compress_invalidate(&cctx);
+ /*
+ * Make sure to release any locks held by database
+ * iterators before returning from the event handler.
+ */
+ xfr->stream->methods->pause(xfr->stream);
+
+ if (result == ISC_R_SUCCESS)
+ return;
+
+ xfrout_fail(xfr, result, "sending zone data");
+}
+
+static void
+xfrout_ctx_destroy(xfrout_ctx_t **xfrp) {
+ xfrout_ctx_t *xfr = *xfrp;
+ ns_client_t *client = NULL;
+
+ INSIST(xfr->sends == 0);
+
+ xfr->client->shutdown = NULL;
+ xfr->client->shutdown_arg = NULL;
+
+ if (xfr->stream != NULL)
+ xfr->stream->methods->destroy(&xfr->stream);
+ if (xfr->buf.base != NULL)
+ isc_mem_put(xfr->mctx, xfr->buf.base, xfr->buf.length);
+ if (xfr->txmem != NULL)
+ isc_mem_put(xfr->mctx, xfr->txmem, xfr->txmemlen);
+ if (xfr->lasttsig != NULL)
+ isc_buffer_free(&xfr->lasttsig);
+ if (xfr->quota != NULL)
+ isc_quota_detach(&xfr->quota);
+ if (xfr->ver != NULL)
+ dns_db_closeversion(xfr->db, &xfr->ver, false);
+ if (xfr->zone != NULL)
+ dns_zone_detach(&xfr->zone);
+ if (xfr->db != NULL)
+ dns_db_detach(&xfr->db);
+
+ /*
+ * We want to detch the client after we have released the memory
+ * context as ns_client_detach checks the memory reference count.
+ */
+ ns_client_attach(xfr->client, &client);
+ ns_client_detach(&xfr->client);
+ isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr));
+ ns_client_detach(&client);
+
+ *xfrp = NULL;
+}
+
+static void
+xfrout_senddone(isc_task_t *task, isc_event_t *event) {
+ isc_socketevent_t *sev = (isc_socketevent_t *)event;
+ xfrout_ctx_t *xfr = (xfrout_ctx_t *)event->ev_arg;
+ isc_result_t evresult = sev->result;
+
+ UNUSED(task);
+
+ INSIST(event->ev_type == ISC_SOCKEVENT_SENDDONE);
+
+ isc_event_free(&event);
+ xfr->sends--;
+ INSIST(xfr->sends == 0);
+
+ (void)isc_timer_touch(xfr->client->timer);
+ if (xfr->shuttingdown == true) {
+ xfrout_maybe_destroy(xfr);
+ } else if (evresult != ISC_R_SUCCESS) {
+ xfrout_fail(xfr, evresult, "send");
+ } else if (xfr->end_of_stream == false) {
+ sendstream(xfr);
+ } else {
+ /* End of zone transfer stream. */
+ inc_stats(xfr->zone, dns_nsstatscounter_xfrdone);
+ xfrout_log(xfr, ISC_LOG_INFO, "%s ended", xfr->mnemonic);
+ ns_client_next(xfr->client, ISC_R_SUCCESS);
+ xfrout_ctx_destroy(&xfr);
+ }
+}
+
+static void
+xfrout_fail(xfrout_ctx_t *xfr, isc_result_t result, const char *msg) {
+ xfr->shuttingdown = true;
+ xfrout_log(xfr, ISC_LOG_ERROR, "%s: %s",
+ msg, isc_result_totext(result));
+ xfrout_maybe_destroy(xfr);
+}
+
+static void
+xfrout_maybe_destroy(xfrout_ctx_t *xfr) {
+ INSIST(xfr->shuttingdown == true);
+ if (xfr->sends > 0) {
+ /*
+ * If we are currently sending, cancel it and wait for
+ * cancel event before destroying the context.
+ */
+ isc_socket_cancel(xfr->client->tcpsocket, xfr->client->task,
+ ISC_SOCKCANCEL_SEND);
+ } else {
+ ns_client_next(xfr->client, ISC_R_CANCELED);
+ xfrout_ctx_destroy(&xfr);
+ }
+}
+
+static void
+xfrout_client_shutdown(void *arg, isc_result_t result) {
+ xfrout_ctx_t *xfr = (xfrout_ctx_t *) arg;
+ xfrout_fail(xfr, result, "aborted");
+}
+
+/*
+ * Log outgoing zone transfer messages in a format like
+ * <client>: transfer of <zone>: <message>
+ */
+
+static void
+xfrout_logv(ns_client_t *client, dns_name_t *zonename,
+ dns_rdataclass_t rdclass, int level, const char *fmt, va_list ap)
+ ISC_FORMAT_PRINTF(5, 0);
+
+static void
+xfrout_logv(ns_client_t *client, dns_name_t *zonename,
+ dns_rdataclass_t rdclass, int level, const char *fmt, va_list ap)
+{
+ char msgbuf[2048];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+
+ dns_name_format(zonename, namebuf, sizeof(namebuf));
+ dns_rdataclass_format(rdclass, classbuf, sizeof(classbuf));
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT,
+ NS_LOGMODULE_XFER_OUT, level,
+ "transfer of '%s/%s': %s", namebuf, classbuf, msgbuf);
+}
+
+/*
+ * Logging function for use when a xfrout_ctx_t has not yet been created.
+ */
+static void
+xfrout_log1(ns_client_t *client, dns_name_t *zonename,
+ dns_rdataclass_t rdclass, int level, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ xfrout_logv(client, zonename, rdclass, level, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Logging function for use when there is a xfrout_ctx_t.
+ */
+static void
+xfrout_log(xfrout_ctx_t *xfr, int level, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ xfrout_logv(xfr->client, xfr->qname, xfr->qclass, level, fmt, ap);
+ va_end(ap);
+}