xfrout.c revision 0c32f6b044792f3dc78b6861933bc51fdecba1c5
/*
* Copyright (C) 1999 Internet Software Consortium.
*
* Permission to use, copy, modify, and distribute this software for any
* 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
* CONSORTIUM 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.36 2000/01/12 18:01:11 gson Exp $ */
#include <config.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <isc/assertions.h>
#include <dns/dbiterator.h>
#include <dns/fixedname.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatastruct.h>
/*
* Outgoing AXFR and IXFR.
*/
/*
* TODO:
* - IXFR over UDP
*/
#define XFROUT_COMMON_LOGARGS \
#define XFROUT_PROTOCOL_LOGARGS \
#define XFROUT_DEBUG_LOGARGS(n) \
/*
* Fail unconditionally and log as a client error.
*/
do { \
"bad zone transfer request: %s (%s)", \
goto failure; \
} 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;
struct db_rr_iterator {
};
{
if (result != DNS_R_SUCCESS)
return (result);
}
&it->rdatasetit);
}
/* We are at the end of the entire database. */
}
&it->rdatasetit);
}
}
}
void
}
void
{
}
/**************************************************************************/
/* Log an RR (for debugging) */
static void
char mem[2000];
isc_region_t r;
/* Get rid of final newline. */
if (result == DNS_R_SUCCESS) {
isc_buffer_used(&buf, &r);
} else {
"<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 {
} rrstream_t;
struct rrstream_methods {
void (*current)(rrstream_t *,
dns_name_t **,
isc_uint32_t *,
dns_rdata_t **);
void (*destroy)(rrstream_t **);
};
/**************************************************************************/
/*
* 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. */
/*
* 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 (DNS_R_NOMEMORY);
*sp = (rrstream_t *) s;
return (DNS_R_SUCCESS);
ixfr_rrstream_destroy((rrstream_t **) &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 'ixfr_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 {
int state;
/* Forward declarations. */
static isc_result_t
rrstream_t **sp)
{
axfr_rrstream_t *s;
s = isc_mem_get(mctx, sizeof(*s));
if (s == NULL)
return (DNS_R_NOMEMORY);
*sp = (rrstream_t *) s;
return (DNS_R_SUCCESS);
axfr_rrstream_destroy((rrstream_t **) &s);
return (result);
}
static isc_result_t
{
/* Skip SOA records. */
for (;;) {
break;
if (result != DNS_R_SUCCESS)
break;
}
return (result);
}
static isc_result_t
{
/* Skip SOA records. */
for (;;) {
if (result != DNS_R_SUCCESS)
break;
break;
}
return (result);
}
static void
dns_rdata_t **rdata)
{
}
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 isc_result_t
rrstream_t **sp)
{
soa_rrstream_t *s;
s = isc_mem_get(mctx, sizeof(*s));
if (s == NULL)
return (DNS_R_NOMEMORY);
&s->soa_tuple));
*sp = (rrstream_t *) s;
return (DNS_R_SUCCESS);
soa_rrstream_destroy((rrstream_t **) &s);
return (result);
}
static isc_result_t
return (DNS_R_SUCCESS);
}
static isc_result_t
return (DNS_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. */
/*
* 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
rrstream_t **sp)
{
s = isc_mem_get(mctx, sizeof(*s));
if (s == NULL)
return (DNS_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 (DNS_R_SUCCESS);
}
static isc_result_t
s->state = 0;
do {
return (s->result);
}
static isc_result_t
while (s->result == DNS_R_NOMORE) {
if (s->state == 2)
return (DNS_R_NOMORE);
s->state++;
}
return (s->result);
}
static void
dns_rdata_t **rdata)
{
}
static void
}
{
};
/**************************************************************************/
/*
* 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 */
} xfrout_ctx_t;
static isc_result_t
unsigned int maxtime,
unsigned int idletime,
xfrout_ctx_t **xfrp);
static void xfrout_client_shutdown(void *arg);
/**************************************************************************/
void
{
switch (reqtype) {
case dns_rdatatype_axfr:
mnemonic = "AXFR";
break;
case dns_rdatatype_ixfr:
mnemonic = "IXFR";
break;
default:
INSIST(0);
break;
}
/*
* Apply quotas.
*/
if (result != DNS_R_SUCCESS) {
"zone transfer request denied: %s",
goto failure;
}
/*
* Interpret the question section.
*/
/*
* The question section must contain exactly one question, and
*/
if (result != DNS_R_NOMORE)
if (result != DNS_R_SUCCESS)
switch(dns_zone_gettype(zone)) {
case dns_zone_master:
case dns_zone_slave:
break; /* Master and slave zones are OK for transfer. */
default:
}
mnemonic);
/*
* Check the authority section. Look for a SOA record with
* the same name and class as the question.
*/
result == DNS_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 == DNS_R_SUCCESS)
"IXFR authority section "
"has multiple SOAs");
goto got_soa;
}
}
if (result != DNS_R_NOMORE)
mnemonic);
/* Decide whether to allow this transfer. */
"zone transfer",
ISC_TRUE));
/* AXFR over UDP is not possible. */
if (reqtype == dns_rdatatype_axfr &&
}
/* Get a dynamically allocated copy of the current SOA. */
if (reqtype == dns_rdatatype_ixfr) {
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;
}
&data_stream);
if (result == ISC_R_NOTFOUND ||
result == DNS_R_RANGE) {
"IXFR version not in journal, "
"falling back to AXFR");
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", and "ver" to the xfrout context object.
*/
&xfr));
/*
* 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 != DNS_R_SUCCESS) {
"zone transfer setup failed");
}
}
static isc_result_t
{
unsigned int len;
void *mem;
return (DNS_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;
}
&expires, &idleinterval,
/*
* Register a shutdown callback with the client, so that we
* can stop the transfer immediately when the client task
* gets a shutdown event.
*/
return (DNS_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;
/*
* 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;
}
dns_name_fromregion(msgname, &r);
/* Reserve space for RR header. */
if (result == DNS_R_NOMORE) {
break;
}
/* XXX per-server, too */
break;
}
"sending zone transfer TCP message of %d bytes",
xfr));
} else {
"sending IXFR UDP response");
/* XXX kludge */
return;
}
/* Advance lasttsig to be the last TSIG generated */
/* Clear this field so the TSIG is not destroyed */
/*
* If this is the first message, clear this too, since this is
* also pointed to by the request.
*/
/*
* XXXRTH need to cleanup qname and qrdataset...
*/
}
if (result == DNS_R_SUCCESS)
return;
}
static void
}
/* XXX */
}
static void
} else if (evresult != ISC_R_SUCCESS) {
} else {
/* End of zone transfer stream. */
"end of outgoing zone transfer");
}
}
static void
/* This will log "giving up: timeout". */
}
static void
{
"outgoing zone transfer: %s: %s",
}
static void
/*
* If we are currently sending, cancel it and wait for
* cancel event before destroying the context.
*/
} else {
}
}
static void
xfrout_client_shutdown(void *arg)
{
}