/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \file * \brief * Outgoing AXFR and IXFR. */ /* * TODO: * - IXFR over UDP */ #define XFROUT_COMMON_LOGARGS \ ns_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 void inc_stats(ns_client_t *client, dns_zone_t *zone, isc_statscounter_t counter) { ns_stats_increment(client->sctx->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, ""); } } /**************************************************************************/ /* * 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, size_t *sizep, rrstream_t **sp) { isc_result_t result; ixfr_rrstream_t *s = NULL; INSIST(sp != NULL && *sp == NULL); s = isc_mem_get(mctx, sizeof(*s)); 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, sizep)); *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 != NULL) { 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)); 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)); 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)); 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; *data_stream = NULL; *soa_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 }; /**************************************************************************/ /*% * Structure holding outgoing transfer statistics */ struct xfr_stats { uint64_t nmsg; /*%< Number of messages sent */ uint64_t nrecs; /*%< Number of records sent */ uint64_t nbytes; /*%< Number of bytes sent */ isc_time_t start; /*%< Start time of the transfer */ isc_time_t end; /*%< End time of the transfer */ }; /*% * 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 question_added; /* QUESTION section sent? */ bool end_of_stream; /* EOS has been reached */ isc_buffer_t buf; /* Buffer for message owner * names and rdatas */ isc_buffer_t txbuf; /* Transmit message buffer */ size_t cbytes; /* Length of current message */ void *txmem; unsigned int txmemlen; 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; bool poll; const char *mnemonic; /* Style of transfer */ uint32_t end_serial; /* Serial number after XFR is done */ struct xfr_stats stats; /*%< Transfer statistics */ /* Timeouts */ uint64_t maxtime; /*%< Maximum XFR timeout (in ms) */ isc_nm_timer_t *maxtime_timer; uint64_t idletime; /*%< XFR idle timeout (in ms) */ } xfrout_ctx_t; static void 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_nmhandle_t *handle, isc_result_t result, void *arg); 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_timeout(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; bool useviewacl = 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: UNREACHABLE(); } 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(&client->sctx->xfroutquota, "a); 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_DEFAULT) { useviewacl = true; result = ISC_R_SUCCESS; } 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, slave, and mirror zones are OK for transfer. */ case dns_zone_primary: case dns_zone_secondary: case dns_zone_mirror: 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 or we are falling back to the view's transfer * ACL, decide whether to allow this transfer. */ if (!is_dlz || useviewacl) { dns_acl_t *acl; ns_client_aclmsg("zone transfer", question_name, reqtype, client->view->rdclass, msg, sizeof(msg)); if (useviewacl) { acl = client->view->transferacl; } else { acl = dns_zone_getxfracl(zone); } CHECK(ns_client_checkacl(client, NULL, msg, acl, 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, ¤t_soa_tuple)); current_serial = dns_soa_getserial(¤t_soa_tuple->rdata); if (reqtype == dns_rdatatype_ixfr) { size_t jsize; uint64_t dbsize; /* * Outgoing IXFR may have been disabled for this peer * or globally. */ if ((client->attributes & NS_CLIENTATTR_TCP) != 0) { bool provide_ixfr; 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; } /* * Outgoing IXFR may have been disabled for this peer * or globally. */ if ((client->attributes & NS_CLIENTATTR_TCP) != 0) { bool provide_ixfr; provide_ixfr = client->view->provideixfr; if (peer != NULL) { (void)dns_peer_getprovideixfr(peer, &provide_ixfr); } if (!provide_ixfr) { xfrout_log1(client, question_name, question_class, ISC_LOG_DEBUG(4), "IXFR delta response disabled due " "to 'provide-ixfr no;' being set"); mnemonic = "AXFR-style IXFR"; goto axfr_fallback; } } journalfile = is_dlz ? NULL : dns_zone_getjournal(zone); if (journalfile != NULL) { result = ixfr_rrstream_create( mctx, journalfile, begin_serial, current_serial, &jsize, &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_INFO, "IXFR version not in journal, " "falling back to AXFR"); mnemonic = "AXFR-style IXFR"; goto axfr_fallback; } CHECK(result); result = dns_db_getsize(db, ver, NULL, &dbsize); if (result == ISC_R_SUCCESS) { uint32_t ratio = dns_zone_getixfrratio(zone); if (ratio != 0 && ((100 * jsize) / dbsize) > ratio) { data_stream->methods->destroy(&data_stream); data_stream = NULL; xfrout_log1(client, question_name, question_class, ISC_LOG_INFO, "IXFR delta size (%zu bytes) " "exceeds the maximum ratio to " "database size " "(%" PRIu64 " bytes), " "falling back to AXFR", jsize, dbsize); mnemonic = "AXFR-style IXFR"; goto axfr_fallback; } else { xfrout_log1(client, question_name, question_class, ISC_LOG_DEBUG(4), "IXFR delta size (%zu bytes); " "database size " "(%" PRIu64 " bytes)", jsize, dbsize); } } 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) { 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 { 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->end_serial = current_serial; 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'; } xfr->poll = is_poll; if (is_poll) { xfr->mnemonic = "IXFR poll response"; 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_secondary || dns_zone_gettype(mayberaw) == dns_zone_mirror)) { 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); } } /* Start the timers */ if (xfr->maxtime > 0) { xfrout_log(xfr, ISC_LOG_DEBUG(1), "starting maxtime timer %" PRIu64 " ms", xfr->maxtime); isc_nm_timer_start(xfr->maxtime_timer, xfr->maxtime); } /* * 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(client, zone, ns_statscounter_xfrrej); } if (quota != NULL) { isc_quota_detach("a); } if (current_soa_tuple != NULL) { dns_difftuple_free(¤t_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); isc_nmhandle_detach(&client->reqhandle); } } static void 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 = NULL; unsigned int len = NS_CLIENT_TCP_BUFFER_SIZE; void *mem = NULL; REQUIRE(xfrp != NULL && *xfrp == NULL); xfr = isc_mem_get(mctx, sizeof(*xfr)); *xfr = (xfrout_ctx_t){ .client = client, .id = id, .qname = qname, .qtype = qtype, .qclass = qclass, .maxtime = maxtime * 1000, /* in milliseconds */ .idletime = idletime * 1000, /* In milliseconds */ .tsigkey = tsigkey, .lasttsig = lasttsig, .verified_tsig = verified_tsig, .many_answers = many_answers, }; isc_mem_attach(mctx, &xfr->mctx); 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); isc_time_now(&xfr->stats.start); isc_nm_timer_create(xfr->client->handle, xfrout_client_timeout, xfr, &xfr->maxtime_timer); /* * Allocate a temporary buffer for the uncompressed response * message data. The buffer size must be 65535 bytes * (NS_CLIENT_TCP_BUFFER_SIZE): small enough that compressed * data will fit in a single TCP message, and big enough to * hold a maximum-sized RR. * * 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 65535 byte limit. */ mem = isc_mem_get(mctx, len); isc_buffer_init(&xfr->buf, mem, len); /* * Allocate another temporary buffer for the compressed * response message. */ mem = isc_mem_get(mctx, len); isc_buffer_init(&xfr->txbuf, (char *)mem, len); xfr->txmem = mem; xfr->txmemlen = len; /* * 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; } /* * 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; 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->txbuf); is_tcp = ((xfr->client->attributes & NS_CLIENTATTR_TCP) != 0); 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. */ 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->question_added) { 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; } 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); xfr->question_added = true; } 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_lctx, XFROUT_RR_LOGLEVEL)) { log_rr(name, rdata, ttl); /* XXX */ } result = dns_message_gettempname(msg, &msgname); if (result != ISC_R_SUCCESS) { goto failure; } 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; xfr->stats.nrecs++; 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) >= xfr->client->sctx->transfer_tcp_message_size) && is_tcp) { break; } } if (is_tcp) { isc_region_t used; 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); xfrout_log(xfr, ISC_LOG_DEBUG(8), "sending TCP message of %d bytes", used.length); isc_nmhandle_attach(xfr->client->handle, &xfr->client->sendhandle); if (xfr->idletime > 0) { isc_nmhandle_setwritetimeout(xfr->client->sendhandle, xfr->idletime); } isc_nm_send(xfr->client->sendhandle, &used, xfrout_senddone, xfr); xfr->sends++; xfr->cbytes = used.length; } else { xfrout_log(xfr, ISC_LOG_DEBUG(8), "sending IXFR UDP response"); ns_client_send(xfr->client); xfr->stream->methods->pause(xfr->stream); isc_nmhandle_detach(&xfr->client->reqhandle); xfrout_ctx_destroy(&xfr); return; } /* Advance lasttsig to be the last TSIG generated */ CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig)); 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_detach(&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; } if (xfr->client->sendhandle != NULL) { isc_nmhandle_detach(&xfr->client->sendhandle); } xfrout_fail(xfr, result, "sending zone data"); } static void xfrout_ctx_destroy(xfrout_ctx_t **xfrp) { xfrout_ctx_t *xfr = *xfrp; *xfrp = NULL; INSIST(xfr->sends == 0); isc_nm_timer_stop(xfr->maxtime_timer); isc_nm_timer_detach(&xfr->maxtime_timer); 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); } isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr)); } static void xfrout_senddone(isc_nmhandle_t *handle, isc_result_t result, void *arg) { xfrout_ctx_t *xfr = (xfrout_ctx_t *)arg; REQUIRE((xfr->client->attributes & NS_CLIENTATTR_TCP) != 0); INSIST(handle == xfr->client->handle); xfr->sends--; INSIST(xfr->sends == 0); isc_nmhandle_detach(&xfr->client->sendhandle); /* * Update transfer statistics if sending succeeded, accounting for the * two-byte TCP length prefix included in the number of bytes sent. */ if (result == ISC_R_SUCCESS) { xfr->stats.nmsg++; xfr->stats.nbytes += xfr->cbytes; } if (xfr->shuttingdown) { xfrout_maybe_destroy(xfr); } else if (result != ISC_R_SUCCESS) { xfrout_fail(xfr, result, "send"); } else if (!xfr->end_of_stream) { sendstream(xfr); } else { /* End of zone transfer stream. */ uint64_t msecs, persec; inc_stats(xfr->client, xfr->zone, ns_statscounter_xfrdone); isc_time_now(&xfr->stats.end); msecs = isc_time_microdiff(&xfr->stats.end, &xfr->stats.start); msecs /= 1000; if (msecs == 0) { msecs = 1; } persec = (xfr->stats.nbytes * 1000) / msecs; xfrout_log(xfr, xfr->poll ? ISC_LOG_DEBUG(1) : ISC_LOG_INFO, "%s ended: " "%" PRIu64 " messages, %" PRIu64 " records, " "%" PRIu64 " bytes, " "%u.%03u secs (%u bytes/sec) (serial %u)", xfr->mnemonic, xfr->stats.nmsg, xfr->stats.nrecs, xfr->stats.nbytes, (unsigned int)(msecs / 1000), (unsigned int)(msecs % 1000), (unsigned int)persec, xfr->end_serial); /* * We're done, unreference the handle and destroy the xfr * context. */ isc_nmhandle_detach(&xfr->client->reqhandle); 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) { REQUIRE(xfr->shuttingdown); ns_client_drop(xfr->client, ISC_R_CANCELED); isc_nmhandle_detach(&xfr->client->reqhandle); xfrout_ctx_destroy(&xfr); } static void xfrout_client_timeout(void *arg, isc_result_t result) { xfrout_ctx_t *xfr = (xfrout_ctx_t *)arg; xfr->shuttingdown = true; xfrout_log(xfr, ISC_LOG_ERROR, "%s: %s", "aborted", isc_result_totext(result)); } /* * Log outgoing zone transfer messages in a format like * : transfer of : */ 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); }