xfrout.c revision ec5347e2c775f027573ce5648b910361aa926c01
/*
* Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC")
* Copyright (C) 1999-2003 Internet Software Consortium.
*
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: xfrout.c,v 1.126 2007/06/18 23:47:19 tbox Exp $ */
#include <config.h>
#include <isc/formatcheck.h>
#include <dns/dbiterator.h>
#ifdef DLZ
#endif
#include <dns/fixedname.h>
#include <dns/rdataclass.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.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)
/**************************************************************************/
/*%
* A db_rr_iterator_t is an iterator that iterates over an entire database,
* returning one RR at a time, in some arbitrary order.
*/
typedef struct db_rr_iterator db_rr_iterator_t;
/*% db_rr_iterator structure */
struct db_rr_iterator {
};
static isc_result_t
static isc_result_t
static isc_result_t
static void
static void
static isc_result_t
{
if (result != ISC_R_SUCCESS)
return (result);
}
static isc_result_t
/*
* The top node may be empty when out of zone glue exists.
* Walk the tree to find the first node with data.
*/
&it->rdatasetit);
/*
* This node is empty. Try next node.
*/
continue;
}
}
}
static isc_result_t
/*
* The while loop body is executed more than once
* only when an empty dbnode needs to be skipped.
*/
/* We are at the end of the entire database. */
}
&it->rdatasetit);
}
}
}
static void
}
static void
}
static void
{
}
/**************************************************************************/
/*% Log an RR (for debugging) */
static void
char mem[2000];
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);
*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
db_rr_iterator_pause(&s->it);
}
static void
if (s->it_valid)
db_rr_iterator_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];
#ifdef DLZ
#endif
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)
#ifdef DLZ
{
/*
* Normal zone table does not have a match. Try the 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)
#endif
#ifdef DLZ
/*
* DLZ only support full zone transfer, not incremental
*/
if (reqtype != dns_rdatatype_axfr) {
mnemonic = "AXFR-style IXFR";
}
} else {
/*
* not DLZ and not in normal zone table, we are
* not authoritative
*/
}
} else {
/* zone table has a match */
#endif
switch(dns_zone_gettype(zone)) {
case dns_zone_master:
case dns_zone_slave:
break; /* Master and slave zones are OK for transfer. */
default:
}
#ifdef DLZ
}
#endif
"%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);
/*
* Decide whether to allow this transfer.
*/
#ifdef DLZ
/*
* if not a DLZ zone decide whether to allow this transfer.
*/
if (!is_dlz) {
#endif
#ifdef DLZ
}
#endif
/*
* 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.
*/
#ifdef DLZ
if (is_dlz)
#endif
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 {
&data_stream));
}
/*
* Bracket the 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.
*/
#ifdef DLZ
if (is_dlz)
3600,
3600,
(format == dns_many_answers) ?
&xfr));
else
#endif
(format == dns_many_answers) ?
&xfr));
} else
keyname[0] = '\0';
if (is_poll)
else
/*
* 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 (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 possiby at the end of the stream (that is, the
* _first method of the iterator has been called).
*/
static void
int n_rrs;
/*
* 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.
*/
/*
* 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
}
/*
* 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;
if (result != ISC_R_SUCCESS)
goto failure;
if (result == ISC_R_NOMORE) {
break;
}
if (! xfr->many_answers)
break;
}
"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
}
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
}