xfrout.c revision 499b34cea04a46823d003d4c0520c8b03e8513cb
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews/*
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * Copyright (C) 1999-2001 Internet Software Consortium.
0c27b3fe77ac1d5094ba3521e8142d9e7973133fMark Andrews *
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * Permission to use, copy, modify, and distribute this software for any
0c27b3fe77ac1d5094ba3521e8142d9e7973133fMark Andrews * purpose with or without fee is hereby granted, provided that the above
0c27b3fe77ac1d5094ba3521e8142d9e7973133fMark Andrews * copyright notice and this permission notice appear in all copies.
0c27b3fe77ac1d5094ba3521e8142d9e7973133fMark Andrews *
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews */
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews/* $Id: xfrout.c,v 1.90 2001/01/09 21:40:09 bwelling Exp $ */
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews
cc51cd2d2076e33117c60c9effcb8caccde4983bWitold Krecicki#include <config.h>
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews#include <isc/mem.h>
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews#include <isc/timer.h>
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews#include <isc/print.h>
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews#include <isc/util.h>
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews#include <dns/db.h>
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews#include <dns/dbiterator.h>
c9297d37593b36de9978c122dbeaf1bb7ae8d19dMark Andrews#include <dns/fixedname.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/journal.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/message.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/peer.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/rdatalist.h>
cc51cd2d2076e33117c60c9effcb8caccde4983bWitold Krecicki#include <dns/rdataset.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/rdatasetiter.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/result.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/timer.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/view.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/zone.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <dns/zt.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <named/client.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <named/log.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <named/server.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#include <named/xfrout.h>
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews/*
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * Outgoing AXFR and IXFR.
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews */
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews/*
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * TODO:
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * - IXFR over UDP
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews */
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#define XFROUT_COMMON_LOGARGS \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews ns_g_lctx, DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#define XFROUT_PROTOCOL_LOGARGS \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews XFROUT_COMMON_LOGARGS, ISC_LOG_INFO
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#define XFROUT_DEBUG_LOGARGS(n) \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews XFROUT_COMMON_LOGARGS, ISC_LOG_DEBUG(n)
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#define XFROUT_RR_LOGARGS \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews XFROUT_COMMON_LOGARGS, XFROUT_RR_LOGLEVEL
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#define XFROUT_RR_LOGLEVEL ISC_LOG_DEBUG(8)
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews/*
cc51cd2d2076e33117c60c9effcb8caccde4983bWitold Krecicki * Fail unconditionally and log as a client error.
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * The test against ISC_R_SUCCESS is there to keep the Solaris compiler
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * from complaining about "end-of-loop code not reached".
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews */
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#define FAILC(code, msg) \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews do { \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews result = (code); \
4040ff974c6971121b6e7f380c3cab472d485490Mark Andrews ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews NS_LOGMODULE_XFER_OUT, ISC_LOG_INFO, \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews "bad zone transfer request: %s (%s)", \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews msg, isc_result_totext(code)); \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews if (result != ISC_R_SUCCESS) goto failure; \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews } while (0)
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews#define CHECK(op) \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews do { result = (op); \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews if (result != ISC_R_SUCCESS) goto failure; \
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews } while (0)
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews/**************************************************************************/
4040ff974c6971121b6e7f380c3cab472d485490Mark Andrews/*
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * A db_rr_iterator_t is an iterator that iterates over an entire database,
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews * returning one RR at a time, in some arbitrary order.
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews */
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewstypedef struct db_rr_iterator db_rr_iterator_t;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsstruct db_rr_iterator {
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews isc_result_t result;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews dns_db_t *db;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews dns_dbiterator_t *dbit;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews dns_dbversion_t *ver;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews isc_stdtime_t now;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews dns_dbnode_t *node;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews dns_fixedname_t fixedname;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews dns_rdatasetiter_t *rdatasetit;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews dns_rdataset_t rdataset;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews dns_rdata_t rdata;
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews};
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsstatic isc_result_t
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsdb_rr_iterator_init(db_rr_iterator_t *it, dns_db_t *db, dns_dbversion_t *ver,
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews isc_stdtime_t now);
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsstatic isc_result_t
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsdb_rr_iterator_first(db_rr_iterator_t *it);
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsstatic isc_result_t
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsdb_rr_iterator_next(db_rr_iterator_t *it);
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsstatic void
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsdb_rr_iterator_current(db_rr_iterator_t *it, dns_name_t **name,
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews isc_uint32_t *ttl, dns_rdata_t **rdata);
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrews
8462dfb880040cde3a60f047ec18808737fd7e85Mark Andrewsstatic void
db_rr_iterator_destroy(db_rr_iterator_t *it);
static isc_result_t
db_rr_iterator_init(db_rr_iterator_t *it, dns_db_t *db, dns_dbversion_t *ver,
isc_stdtime_t now)
{
isc_result_t result;
it->db = db;
it->dbit = NULL;
it->ver = ver;
it->now = now;
it->node = NULL;
result = dns_db_createiterator(it->db, ISC_FALSE, &it->dbit);
if (result != ISC_R_SUCCESS)
return (result);
it->rdatasetit = NULL;
dns_rdata_init(&it->rdata);
dns_rdataset_init(&it->rdataset);
dns_fixedname_init(&it->fixedname);
INSIST(! dns_rdataset_isassociated(&it->rdataset));
it->result = ISC_R_SUCCESS;
return (it->result);
}
static isc_result_t
db_rr_iterator_first(db_rr_iterator_t *it) {
it->result = dns_dbiterator_first(it->dbit);
if (it->result != ISC_R_SUCCESS)
return (it->result);
it->result = dns_dbiterator_current(it->dbit, &it->node,
dns_fixedname_name(&it->fixedname));
if (it->result != ISC_R_SUCCESS)
return (it->result);
it->result = dns_db_allrdatasets(it->db, it->node,
it->ver, it->now,
&it->rdatasetit);
if (it->result != ISC_R_SUCCESS)
return (it->result);
it->result = dns_rdatasetiter_first(it->rdatasetit);
if (it->result != ISC_R_SUCCESS)
return (it->result);
dns_rdatasetiter_current(it->rdatasetit, &it->rdataset);
it->result = dns_rdataset_first(&it->rdataset);
return (it->result);
}
static isc_result_t
db_rr_iterator_next(db_rr_iterator_t *it) {
if (it->result != ISC_R_SUCCESS)
return (it->result);
INSIST(it->dbit != NULL);
INSIST(it->node != NULL);
INSIST(it->rdatasetit != NULL);
it->result = dns_rdataset_next(&it->rdataset);
if (it->result == ISC_R_NOMORE) {
dns_rdataset_disassociate(&it->rdataset);
it->result = dns_rdatasetiter_next(it->rdatasetit);
/*
* The while loop body is executed more than once
* only when an empty dbnode needs to be skipped.
*/
while (it->result == ISC_R_NOMORE) {
dns_rdatasetiter_destroy(&it->rdatasetit);
dns_db_detachnode(it->db, &it->node);
it->result = dns_dbiterator_next(it->dbit);
if (it->result == ISC_R_NOMORE) {
/* We are at the end of the entire database. */
return (it->result);
}
if (it->result != ISC_R_SUCCESS)
return (it->result);
it->result = dns_dbiterator_current(it->dbit,
&it->node,
dns_fixedname_name(&it->fixedname));
if (it->result != ISC_R_SUCCESS)
return (it->result);
it->result = dns_db_allrdatasets(it->db, it->node,
it->ver, it->now,
&it->rdatasetit);
if (it->result != ISC_R_SUCCESS)
return (it->result);
it->result = dns_rdatasetiter_first(it->rdatasetit);
}
if (it->result != ISC_R_SUCCESS)
return (it->result);
dns_rdatasetiter_current(it->rdatasetit, &it->rdataset);
it->result = dns_rdataset_first(&it->rdataset);
if (it->result != ISC_R_SUCCESS)
return (it->result);
}
return (it->result);
}
static void
db_rr_iterator_pause(db_rr_iterator_t *it) {
dns_dbiterator_pause(it->dbit);
}
static void
db_rr_iterator_destroy(db_rr_iterator_t *it) {
if (dns_rdataset_isassociated(&it->rdataset))
dns_rdataset_disassociate(&it->rdataset);
if (it->rdatasetit != NULL)
dns_rdatasetiter_destroy(&it->rdatasetit);
if (it->node != NULL)
dns_db_detachnode(it->db, &it->node);
dns_dbiterator_destroy(&it->dbit);
}
static void
db_rr_iterator_current(db_rr_iterator_t *it, dns_name_t **name,
isc_uint32_t *ttl, dns_rdata_t **rdata)
{
REQUIRE(name != NULL && *name == NULL);
REQUIRE(it->result == ISC_R_SUCCESS);
*name = dns_fixedname_name(&it->fixedname);
*ttl = it->rdataset.ttl;
dns_rdata_reset(&it->rdata);
dns_rdataset_current(&it->rdataset, &it->rdata);
*rdata = &it->rdata;
}
/**************************************************************************/
/* Log an RR (for debugging) */
static void
log_rr(dns_name_t *name, dns_rdata_t *rdata, isc_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;
rdl.type = rdata->type;
rdl.rdclass = rdata->rdclass;
rdl.ttl = ttl;
ISC_LIST_INIT(rdl.rdata);
ISC_LINK_INIT(&rdl, link);
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,
ISC_FALSE, ISC_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 **,
isc_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,
isc_uint32_t begin_serial,
isc_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 = mctx;
s->common.methods = &ixfr_rrstream_methods;
s->journal = NULL;
CHECK(dns_journal_open(mctx, journal_filename,
ISC_FALSE, &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 **) &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, isc_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_put(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;
int state;
db_rr_iterator_t it;
isc_boolean_t 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 = mctx;
s->common.methods = &axfr_rrstream_methods;
s->it_valid = ISC_FALSE;
CHECK(db_rr_iterator_init(&s->it, db, ver, 0));
s->it_valid = ISC_TRUE;
*sp = (rrstream_t *) s;
return (ISC_R_SUCCESS);
failure:
axfr_rrstream_destroy((rrstream_t **) &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 = db_rr_iterator_first(&s->it);
if (result != ISC_R_SUCCESS)
return (result);
/* Skip SOA records. */
for (;;) {
dns_name_t *name_dummy = NULL;
isc_uint32_t ttl_dummy;
dns_rdata_t *rdata = NULL;
db_rr_iterator_current(&s->it, &name_dummy,
&ttl_dummy, &rdata);
if (rdata->type != dns_rdatatype_soa)
break;
result = db_rr_iterator_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;
isc_uint32_t ttl_dummy;
dns_rdata_t *rdata = NULL;
result = db_rr_iterator_next(&s->it);
if (result != ISC_R_SUCCESS)
break;
db_rr_iterator_current(&s->it, &name_dummy,
&ttl_dummy, &rdata);
if (rdata->type != dns_rdatatype_soa)
break;
}
return (result);
}
static void
axfr_rrstream_current(rrstream_t *rs, dns_name_t **name, isc_uint32_t *ttl,
dns_rdata_t **rdata)
{
axfr_rrstream_t *s = (axfr_rrstream_t *) rs;
db_rr_iterator_current(&s->it, name, ttl, rdata);
}
static void
axfr_rrstream_pause(rrstream_t *rs) {
axfr_rrstream_t *s = (axfr_rrstream_t *) rs;
db_rr_iterator_pause(&s->it);
}
static void
axfr_rrstream_destroy(rrstream_t **rsp) {
axfr_rrstream_t *s = (axfr_rrstream_t *) *rsp;
if (s->it_valid)
db_rr_iterator_destroy(&s->it);
isc_mem_put(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 = 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 **) &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, isc_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_put(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 = 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, isc_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_put(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_db_t *db;
dns_dbversion_t *ver;
isc_quota_t *quota;
rrstream_t *stream; /* The XFR RR stream */
isc_boolean_t 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 */
isc_boolean_t many_answers;
int sends; /* Send in progress */
isc_boolean_t shuttingdown;
} 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_db_t *db, dns_dbversion_t *ver, isc_quota_t *quota,
rrstream_t *stream, dns_tsigkey_t *tsigkey,
isc_buffer_t *lasttsig,
unsigned int maxtime,
unsigned int idletime,
isc_boolean_t 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, int level,
const char *fmt, ...);
static void
xfrout_log(xfrout_ctx_t *xfr, unsigned int level, const char *fmt, ...);
/**************************************************************************/
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;
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;
isc_boolean_t have_soa = ISC_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;
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)
FAILC(DNS_R_NOTAUTH, "non-authoritative zone");
switch(dns_zone_gettype(zone)) {
case dns_zone_master:
case dns_zone_slave:
break; /* Master and slave zones are OK for transfer. */
default:
FAILC(DNS_R_NOTAUTH, "non-authoritative zone");
}
CHECK(dns_zone_getdb(zone, &db));
dns_db_currentversion(db, &ver);
xfrout_log1(client, question_name, 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 = ISC_TRUE;
goto got_soa;
}
}
got_soa:
if (result != ISC_R_NOMORE)
CHECK(result);
xfrout_log1(client, question_name, ISC_LOG_DEBUG(6),
"%s authority section OK", mnemonic);
/*
* Decide whether to allow this transfer.
*/
CHECK(ns_client_checkacl(client, "zone transfer",
dns_zone_getxfracl(zone), ISC_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.
*/
CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_EXISTS,
&current_soa_tuple));
if (reqtype == dns_rdatatype_ixfr) {
isc_uint32_t begin_serial, current_serial;
isc_boolean_t 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 == ISC_FALSE)
goto axfr_fallback;
if (! have_soa)
FAILC(DNS_R_FORMERR,
"IXFR request missing SOA");
begin_serial = dns_soa_getserial(&soa_rdata);
current_serial = dns_soa_getserial(&current_soa_tuple->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));
goto have_stream;
}
result = ixfr_rrstream_create(mctx,
dns_zone_getjournal(zone),
begin_serial,
current_serial,
&data_stream);
if (result == ISC_R_NOTFOUND ||
result == ISC_R_RANGE) {
xfrout_log1(client, question_name, ISC_LOG_DEBUG(4),
"IXFR version not in journal, "
"falling back to AXFR");
goto axfr_fallback;
}
CHECK(result);
} else {
axfr_fallback:
CHECK(axfr_rrstream_create(mctx, db, ver,
&data_stream));
}
/*
* Bracket the 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.
*/
CHECK(xfrout_ctx_create(mctx, client, request->id, question_name,
reqtype, db, ver, quota, stream,
dns_message_gettsigkey(request),
tsigbuf,
dns_zone_getmaxxfrout(zone),
dns_zone_getidleout(zone),
(format == dns_many_answers) ?
ISC_TRUE : ISC_FALSE,
&xfr));
stream = NULL;
db = NULL;
ver = NULL;
quota = NULL;
CHECK(xfr->stream->methods->first(xfr->stream));
/*
* 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 (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, ISC_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_db_t *db, dns_dbversion_t *ver, isc_quota_t *quota,
rrstream_t *stream, dns_tsigkey_t *tsigkey,
isc_buffer_t *lasttsig, unsigned int maxtime,
unsigned int idletime, isc_boolean_t 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 = mctx;
xfr->client = NULL;
ns_client_attach(client, &xfr->client);
xfr->id = id;
xfr->qname = qname;
xfr->qtype = qtype;
xfr->db = db;
xfr->ver = ver;
xfr->quota = quota;
xfr->stream = stream;
xfr->end_of_stream = ISC_FALSE;
xfr->tsigkey = tsigkey;
xfr->lasttsig = lasttsig;
xfr->txmem = NULL;
xfr->txmemlen = 0;
xfr->nmsg = 0;
xfr->many_answers = many_answers,
xfr->sends = 0;
xfr->shuttingdown = ISC_FALSE;
/*
* 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, ISC_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;
*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 possiby 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;
int n_rrs;
isc_buffer_clear(&xfr->buf);
isc_buffer_clear(&xfr->txlenbuf);
isc_buffer_clear(&xfr->txbuf);
if ((xfr->client->attributes & NS_CLIENTATTR_TCP) == 0) {
/*
* In the UDP case, we put the response data directly into
* the client message.
*/
msg = xfr->client->message;
CHECK(dns_message_reply(msg, ISC_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;
dns_message_settsigkey(msg, xfr->tsigkey);
CHECK(dns_message_setquerytsig(msg, xfr->lasttsig));
if (xfr->lasttsig != NULL)
isc_buffer_free(&xfr->lasttsig);
/*
* 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_init(qrdataset);
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
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;
isc_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;
ISC_LINK_INIT(msgrdl, link);
ISC_LIST_INIT(msgrdl->rdata);
ISC_LIST_APPEND(msgrdl->rdata, msgrdata, link);
result = dns_message_gettemprdataset(msg, &msgrds);
if (result != ISC_R_SUCCESS)
goto failure;
dns_rdataset_init(msgrds);
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 = ISC_TRUE;
break;
}
CHECK(result);
if (! xfr->many_answers)
break;
}
if ((xfr->client->attributes & NS_CLIENTATTR_TCP) != 0) {
CHECK(dns_message_renderbegin(msg, &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));
isc_buffer_usedregion(&xfr->txbuf, &used);
isc_buffer_putuint16(&xfr->txlenbuf, 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);
/*
* 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;
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, ISC_FALSE);
if (xfr->db != NULL)
dns_db_detach(&xfr->db);
ns_client_detach(&xfr->client);
isc_mem_put(xfr->mctx, xfr, sizeof(*xfr));
*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 == ISC_TRUE) {
xfrout_maybe_destroy(xfr);
} else if (evresult != ISC_R_SUCCESS) {
xfrout_fail(xfr, evresult, "send");
} else if (xfr->end_of_stream == ISC_FALSE) {
sendstream(xfr);
} else {
/* End of zone transfer stream. */
xfrout_log(xfr, ISC_LOG_DEBUG(6),
"end of transfer");
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 = ISC_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 == ISC_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, int level,
const char *fmt, va_list ap)
{
char msgbuf[2048];
char namebuf[DNS_NAME_FORMATSIZE];
dns_name_format(zonename, namebuf, sizeof(namebuf));
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT,
NS_LOGMODULE_XFER_OUT, level,
"transfer of '%s': %s", namebuf, 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, int level,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
xfrout_logv(client, zonename, 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, unsigned int level, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
xfrout_logv(xfr->client, xfr->qname, level, fmt, ap);
va_end(ap);
}