xfrout.c revision 0c27b3fe77ac1d5094ba3521e8142d9e7973133f
/*
* Copyright (C) 1999-2016 Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* $Id$ */
#include <config.h>
#include <isc/formatcheck.h>
#include <dns/dbiterator.h>
#include <dns/fixedname.h>
#include <dns/rdataclass.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
#include <dns/rriterator.h>
/*! \file
* \brief
* Outgoing AXFR and IXFR.
*/
/*
* TODO:
* - IXFR over UDP
*/
#define XFROUT_COMMON_LOGARGS \
#define XFROUT_PROTOCOL_LOGARGS \
#define XFROUT_DEBUG_LOGARGS(n) \
#define XFROUT_RR_LOGARGS \
/*%
* 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".
*/
do { \
"bad zone transfer request: %s (%s)", \
} while (0)
do { \
char _buf1[DNS_NAME_FORMATSIZE]; \
char _buf2[DNS_RDATACLASS_FORMATSIZE]; \
"bad zone transfer request: '%s/%s': %s (%s)", \
} while (0)
} while (0)
/**************************************************************************/
static inline void
}
}
/**************************************************************************/
/*% Log an RR (for debugging) */
static void
char mem[2000];
else
dns_rdata_init(&rd);
/*
* 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.
*/
(int)isc_buffer_usedlength(&buf),
(char *)isc_buffer_base(&buf));
} else {
}
}
/**************************************************************************/
/*
* 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 {
} rrstream_t;
struct rrstream_methods {
void (*current)(rrstream_t *,
dns_name_t **,
isc_uint32_t *,
dns_rdata_t **);
void (*pause)(rrstream_t *);
void (*destroy)(rrstream_t **);
};
static void
}
/**************************************************************************/
/*
* 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 {
/* Forward declarations. */
static void
/*
* Returns: anything dns_journal_open() or dns_journal_iter_init()
* may return.
*/
static isc_result_t
const char *journal_filename,
rrstream_t **sp)
{
ixfr_rrstream_t *s;
s = isc_mem_get(mctx, sizeof(*s));
if (s == NULL)
return (ISC_R_NOMEMORY);
DNS_JOURNAL_READ, &s->journal));
*sp = (rrstream_t *) s;
return (ISC_R_SUCCESS);
ixfr_rrstream_destroy((rrstream_t **) (void *)&s);
return (result);
}
static isc_result_t
return (dns_journal_first_rr(s->journal));
}
static isc_result_t
return (dns_journal_next_rr(s->journal));
}
static void
dns_rdata_t **rdata)
{
}
static void
if (s->journal != 0)
dns_journal_destroy(&s->journal);
}
static rrstream_methods_t ixfr_rrstream_methods = {
};
/**************************************************************************/
/*
* 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 {
/*
* Forward declarations.
*/
static void
static isc_result_t
rrstream_t **sp)
{
axfr_rrstream_t *s;
s = isc_mem_get(mctx, sizeof(*s));
if (s == NULL)
return (ISC_R_NOMEMORY);
*sp = (rrstream_t *) s;
return (ISC_R_SUCCESS);
axfr_rrstream_destroy((rrstream_t **) (void *)&s);
return (result);
}
static isc_result_t
if (result != ISC_R_SUCCESS)
return (result);
/* Skip SOA records. */
for (;;) {
break;
if (result != ISC_R_SUCCESS)
break;
}
return (result);
}
static isc_result_t
/* Skip SOA records. */
for (;;) {
if (result != ISC_R_SUCCESS)
break;
break;
}
return (result);
}
static void
dns_rdata_t **rdata)
{
}
static void
dns_rriterator_pause(&s->it);
}
static void
if (s->it_valid)
dns_rriterator_destroy(&s->it);
}
static rrstream_methods_t axfr_rrstream_methods = {
};
/**************************************************************************/
/*
* An 'soa_rrstream_t' is a degenerate 'rrstream_t' that returns
* a single SOA record.
*/
typedef struct soa_rrstream {
/*
* Forward declarations.
*/
static void
static isc_result_t
rrstream_t **sp)
{
soa_rrstream_t *s;
s = isc_mem_get(mctx, sizeof(*s));
if (s == NULL)
return (ISC_R_NOMEMORY);
&s->soa_tuple));
*sp = (rrstream_t *) s;
return (ISC_R_SUCCESS);
soa_rrstream_destroy((rrstream_t **) (void *)&s);
return (result);
}
static isc_result_t
return (ISC_R_SUCCESS);
}
static isc_result_t
return (ISC_R_NOMORE);
}
static void
dns_rdata_t **rdata)
{
}
static void
dns_difftuple_free(&s->soa_tuple);
}
static rrstream_methods_t soa_rrstream_methods = {
};
/**************************************************************************/
/*
* 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 {
int state;
/*
* Forward declarations.
*/
static void
static isc_result_t
/*
* 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
{
s = isc_mem_get(mctx, sizeof(*s));
if (s == NULL)
return (ISC_R_NOMEMORY);
s->components[0] = *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
s->state = 0;
do {
return (s->result);
}
static isc_result_t
while (s->result == ISC_R_NOMORE) {
/*
* Make sure locks held by the current stream
* are released before we switch streams.
*/
if (s->state == 2)
return (ISC_R_NOMORE);
s->state++;
}
return (s->result);
}
static void
dns_rdata_t **rdata)
{
}
static void
{
}
static void
}
static rrstream_methods_t compound_rrstream_methods = {
};
/**************************************************************************/
/*
* An 'xfrout_ctx_t' contains the state of an outgoing AXFR or IXFR
* in progress.
*/
typedef struct {
unsigned int id; /* ID of request */
names and rdatas */
void *txmem;
unsigned int txmemlen;
unsigned int nmsg; /* Number of messages sent */
int sends; /* Send in progress */
const char *mnemonic; /* Style of transfer */
} xfrout_ctx_t;
static isc_result_t
unsigned int maxtime,
unsigned int idletime,
xfrout_ctx_t **xfrp);
static void
static void
static void
static void
static void
static void
static void
static void
/**************************************************************************/
void
char *journalfile;
char keyname[DNS_NAME_FORMATSIZE];
switch (reqtype) {
case dns_rdatatype_axfr:
mnemonic = "AXFR";
break;
case dns_rdatatype_ixfr:
mnemonic = "IXFR";
break;
default:
INSIST(0);
break;
}
/*
* Apply quota.
*/
if (result != ISC_R_SUCCESS) {
"%s request denied: %s", mnemonic,
goto failure;
}
/*
* Interpret the question section.
*/
/*
* The question section must contain exactly one question, and
*/
if (result != ISC_R_NOMORE)
&zone);
if (result != ISC_R_SUCCESS) {
/*
* Normal zone table does not have a match.
* Try the DLZ database
*/
// Temporary: only searching the first DLZ database
&db);
if (result == ISC_R_NOPERM) {
char _buf1[DNS_NAME_FORMATSIZE];
char _buf2[DNS_RDATACLASS_FORMATSIZE];
sizeof(_buf1));
"zone transfer '%s/%s' denied",
goto failure;
}
if (result != ISC_R_SUCCESS)
} else {
/*
* not DLZ and not in normal zone table, we are
* not authoritative
*/
}
} else {
/* zone table has a match */
switch(dns_zone_gettype(zone)) {
/* Master and slave zones are OK for transfer. */
case dns_zone_master:
case dns_zone_slave:
case dns_zone_dlz:
break;
default:
}
}
"%s question section OK", mnemonic);
/*
* Check the authority section. Look for a SOA record with
* the same name and class as the question.
*/
result == ISC_R_SUCCESS;
{
&soa_name);
/*
* Ignore data whose owner name is not the zone apex.
*/
continue;
soa_rdataset != NULL;
{
/*
* Ignore non-SOA data.
*/
continue;
continue;
if (result == ISC_R_SUCCESS)
"IXFR authority section "
"has multiple SOAs");
goto got_soa;
}
}
if (result != ISC_R_NOMORE)
"%s authority section OK", mnemonic);
/*
* If not a DLZ zone, decide whether to allow this transfer.
*/
if (!is_dlz) {
}
/*
* AXFR over UDP is not possible.
*/
if (reqtype == dns_rdatatype_axfr &&
/*
* Look up the requesting server in the peer table.
*/
/*
* Decide on the transfer format (one-answer or many-answers).
*/
/*
* Get a dynamically allocated copy of the current SOA.
*/
if (is_dlz)
if (reqtype == dns_rdatatype_ixfr) {
/*
* Outgoing IXFR may have been disabled for this peer
* or globally.
*/
if (provide_ixfr == ISC_FALSE)
goto axfr_fallback;
if (! have_soa)
"IXFR request missing SOA");
/*
* 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).
*/
{
goto have_stream;
}
if (journalfile != NULL)
&data_stream);
else
if (result == ISC_R_NOTFOUND ||
result == ISC_R_RANGE) {
ISC_LOG_DEBUG(4),
"IXFR version not in journal, "
"falling back to AXFR");
mnemonic = "AXFR-style IXFR";
goto axfr_fallback;
}
} else {
}
/*
* Bracket the data stream with SOAs.
*/
&stream));
soa_stream = NULL;
data_stream = NULL;
/*
* Create the xfrout context object. This transfers the ownership
* of "stream", "db", "ver", and "quota" to the xfrout context object.
*/
if (is_dlz)
3600,
3600,
(format == dns_many_answers) ?
&xfr));
else
(format == dns_many_answers) ?
&xfr));
else
keyname[0] = '\0';
if (is_poll)
else if (is_ixfr)
ISC_LOG_INFO, "%s started%s%s (serial %u -> %u)",
else
ISC_LOG_INFO, "%s started%s%s (serial %u)",
}
}
}
/*
* 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.
*/
if (result == DNS_R_REFUSED)
if (current_soa_tuple != NULL)
if (soa_stream != NULL)
if (data_stream != NULL)
dns_db_detach(&db);
/* XXX kludge */
} else if (result != ISC_R_SUCCESS) {
}
}
static isc_result_t
xfrout_ctx_t **xfrp)
{
unsigned int len;
void *mem;
return (ISC_R_NOMEMORY);
/*
* 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;
goto failure;
}
/*
* Allocate another temporary buffer for the compressed
* response message and its TCP length prefix.
*/
goto failure;
}
/*
* Register a shutdown callback with the client, so that we
* can stop the transfer immediately when the client task
* gets a shutdown event.
*/
/*
* These MUST be after the last "goto failure;" / CHECK to
* prevent a double free by the caller.
*/
return (ISC_R_SUCCESS);
return (result);
}
/*
* Arrange to send as much as we can of "stream" without blocking.
*
* Requires:
* The stream iterator is initialized and points at an RR,
* or possibly at the end of the stream (that is, the
* _first method of the iterator has been called).
*/
static void
int n_rrs;
if (!is_tcp) {
/*
* In the UDP case, we put the response data directly into
* the client message.
*/
} 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.
*/
/*
* Add a EDNS option to the message?
*/
/*
* Add to first message only.
*/
}
/*
* Account for reserved space.
*/
/*
* 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.
*/
isc_region_t r;
/*
* Reserve space for the 12-byte message header
* and 4 bytes of question.
*/
if (result != ISC_R_SUCCESS)
goto failure;
if (result != ISC_R_SUCCESS)
goto failure;
dns_name_fromregion(qname, &r);
} else {
/*
* Reserve space for the 12-byte message header
*/
}
}
/*
* Try to fit in as many RRs as possible, unless "one-answer"
* format has been requested.
*/
unsigned int size;
isc_region_t r;
/*
* 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) {
"RR too large for zone transfer "
"(%d bytes)", size);
/* XXX DNS_R_RRTOOLARGE? */
goto failure;
}
break;
}
if (result != ISC_R_SUCCESS)
goto failure;
dns_name_fromregion(msgname, &r);
/* Reserve space for RR header. */
if (result != ISC_R_SUCCESS)
goto failure;
if (result != ISC_R_SUCCESS)
goto failure;
else
if (result != ISC_R_SUCCESS)
goto failure;
if (result == ISC_R_NOMORE) {
break;
}
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).
*/
break;
}
if (is_tcp) {
"sending TCP message of %d bytes",
xfr));
} else {
return;
}
/* Advance lasttsig to be the last TSIG generated */
}
}
}
if (cleanup_cctx)
/*
* Make sure to release any locks held by database
* iterators before returning from the event handler.
*/
if (result == ISC_R_SUCCESS)
return;
}
static void
/*
* We want to detch the client after we have released the memory
* context as ns_client_detach checks the memory reference count.
*/
}
static void
} else if (evresult != ISC_R_SUCCESS) {
} else {
/* End of zone transfer stream. */
}
}
static void
}
static void
/*
* If we are currently sending, cancel it and wait for
* cancel event before destroying the context.
*/
} else {
}
}
static void
}
/*
* Log outgoing zone transfer messages in a format like
* <client>: transfer of <zone>: <message>
*/
static void
ISC_FORMAT_PRINTF(5, 0);
static void
{
char msgbuf[2048];
char namebuf[DNS_NAME_FORMATSIZE];
}
/*
* Logging function for use when a xfrout_ctx_t has not yet been created.
*/
static void
}
/*
* Logging function for use when there is a xfrout_ctx_t.
*/
static void
}