/* $NetBSD: xfrin.c,v 1.1.1.1 2004/05/17 23:44:55 christos Exp $ */ /* * Copyright (C) 2004 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 1999-2003 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 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: xfrin.c,v 1.124.2.4.2.7 2004/03/08 09:04:33 marka Exp */ #include #include #include #include #include /* Required for HP/UX (and others?) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Incoming AXFR and IXFR. */ /* * It would be non-sensical (or at least obtuse) to use FAIL() with an * ISC_R_SUCCESS code, but the test is there to keep the Solaris compiler * from complaining about "end-of-loop code not reached". */ #define FAIL(code) \ do { result = (code); \ if (result != ISC_R_SUCCESS) goto failure; \ } while (0) #define CHECK(op) \ do { result = (op); \ if (result != ISC_R_SUCCESS) goto failure; \ } while (0) /* * The states of the *XFR state machine. We handle both IXFR and AXFR * with a single integrated state machine because they cannot be distinguished * immediately - an AXFR response to an IXFR request can only be detected * when the first two (2) response RRs have already been received. */ typedef enum { XFRST_INITIALSOA, XFRST_FIRSTDATA, XFRST_IXFR_DELSOA, XFRST_IXFR_DEL, XFRST_IXFR_ADDSOA, XFRST_IXFR_ADD, XFRST_AXFR, XFRST_END } xfrin_state_t; /* * Incoming zone transfer context. */ struct dns_xfrin_ctx { unsigned int magic; isc_mem_t *mctx; dns_zone_t *zone; int refcount; isc_task_t *task; isc_timer_t *timer; isc_socketmgr_t *socketmgr; int connects; /* Connect in progress */ int sends; /* Send in progress */ int recvs; /* Receive in progress */ isc_boolean_t shuttingdown; dns_name_t name; /* Name of zone to transfer */ dns_rdataclass_t rdclass; isc_boolean_t checkid; dns_messageid_t id; /* * Requested transfer type (dns_rdatatype_axfr or * dns_rdatatype_ixfr). The actual transfer type * may differ due to IXFR->AXFR fallback. */ dns_rdatatype_t reqtype; isc_sockaddr_t masteraddr; isc_sockaddr_t sourceaddr; isc_socket_t *socket; /* Buffer for IXFR/AXFR request message */ isc_buffer_t qbuffer; unsigned char qbuffer_data[512]; /* Incoming reply TCP message */ dns_tcpmsg_t tcpmsg; isc_boolean_t tcpmsg_valid; dns_db_t *db; dns_dbversion_t *ver; dns_diff_t diff; /* Pending database changes */ int difflen; /* Number of pending tuples */ xfrin_state_t state; isc_uint32_t end_serial; isc_boolean_t is_ixfr; unsigned int nmsg; /* Number of messages recvd */ dns_tsigkey_t *tsigkey; /* Key used to create TSIG */ isc_buffer_t *lasttsig; /* The last TSIG */ dst_context_t *tsigctx; /* TSIG verification context */ unsigned int sincetsig; /* recvd since the last TSIG */ dns_xfrindone_t done; /* * AXFR- and IXFR-specific data. Only one is used at a time * according to the is_ixfr flag, so this could be a union, * but keeping them separate makes it a bit simpler to clean * things up when destroying the context. */ struct { dns_addrdatasetfunc_t add_func; dns_dbload_t *add_private; } axfr; struct { isc_uint32_t request_serial; isc_uint32_t current_serial; dns_journal_t *journal; } ixfr; }; #define XFRIN_MAGIC ISC_MAGIC('X', 'f', 'r', 'I') #define VALID_XFRIN(x) ISC_MAGIC_VALID(x, XFRIN_MAGIC) /**************************************************************************/ /* * Forward declarations. */ static isc_result_t xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_task_t *task, isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr, dns_name_t *zonename, dns_rdataclass_t rdclass, dns_rdatatype_t reqtype, isc_sockaddr_t *masteraddr, isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey, dns_xfrin_ctx_t **xfrp); static isc_result_t axfr_init(dns_xfrin_ctx_t *xfr); static isc_result_t axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp); static isc_result_t axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata); static isc_result_t axfr_apply(dns_xfrin_ctx_t *xfr); static isc_result_t axfr_commit(dns_xfrin_ctx_t *xfr); static isc_result_t ixfr_init(dns_xfrin_ctx_t *xfr); static isc_result_t ixfr_apply(dns_xfrin_ctx_t *xfr); static isc_result_t ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata); static isc_result_t ixfr_commit(dns_xfrin_ctx_t *xfr); static isc_result_t xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, isc_uint32_t ttl, dns_rdata_t *rdata); static isc_result_t xfrin_start(dns_xfrin_ctx_t *xfr); static void xfrin_connect_done(isc_task_t *task, isc_event_t *event); static isc_result_t xfrin_send_request(dns_xfrin_ctx_t *xfr); static void xfrin_send_done(isc_task_t *task, isc_event_t *event); static void xfrin_sendlen_done(isc_task_t *task, isc_event_t *event); static void xfrin_recv_done(isc_task_t *task, isc_event_t *event); static void xfrin_timeout(isc_task_t *task, isc_event_t *event); static void maybe_free(dns_xfrin_ctx_t *xfr); static void xfrin_fail(dns_xfrin_ctx_t *xfr, isc_result_t result, const char *msg); static isc_result_t render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf); static void xfrin_logv(int level, dns_name_t *zonename, dns_rdataclass_t rdclass, isc_sockaddr_t *masteraddr, const char *fmt, va_list ap) ISC_FORMAT_PRINTF(5, 0); static void xfrin_log1(int level, dns_name_t *zonename, dns_rdataclass_t rdclass, isc_sockaddr_t *masteraddr, const char *fmt, ...) ISC_FORMAT_PRINTF(5, 6); static void xfrin_log(dns_xfrin_ctx_t *xfr, unsigned int level, const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); /**************************************************************************/ /* * AXFR handling */ static isc_result_t axfr_init(dns_xfrin_ctx_t *xfr) { isc_result_t result; xfr->is_ixfr = ISC_FALSE; if (xfr->db != NULL) dns_db_detach(&xfr->db); CHECK(axfr_makedb(xfr, &xfr->db)); CHECK(dns_db_beginload(xfr->db, &xfr->axfr.add_func, &xfr->axfr.add_private)); result = ISC_R_SUCCESS; failure: return (result); } static isc_result_t axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp) { return (dns_db_create(xfr->mctx, /* XXX */ "rbt", /* XXX guess */ &xfr->name, dns_dbtype_zone, xfr->rdclass, 0, NULL, /* XXX guess */ dbp)); } static isc_result_t axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) { isc_result_t result; dns_difftuple_t *tuple = NULL; CHECK(dns_zone_checknames(xfr->zone, name, rdata)); CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata, &tuple)); dns_diff_append(&xfr->diff, &tuple); if (++xfr->difflen > 100) CHECK(axfr_apply(xfr)); result = ISC_R_SUCCESS; failure: return (result); } /* * Store a set of AXFR RRs in the database. */ static isc_result_t axfr_apply(dns_xfrin_ctx_t *xfr) { isc_result_t result; CHECK(dns_diff_load(&xfr->diff, xfr->axfr.add_func, xfr->axfr.add_private)); xfr->difflen = 0; dns_diff_clear(&xfr->diff); result = ISC_R_SUCCESS; failure: return (result); } static isc_result_t axfr_commit(dns_xfrin_ctx_t *xfr) { isc_result_t result; CHECK(axfr_apply(xfr)); CHECK(dns_db_endload(xfr->db, &xfr->axfr.add_private)); CHECK(dns_zone_replacedb(xfr->zone, xfr->db, ISC_TRUE)); result = ISC_R_SUCCESS; failure: return (result); } /**************************************************************************/ /* * IXFR handling */ static isc_result_t ixfr_init(dns_xfrin_ctx_t *xfr) { isc_result_t result; char *journalfile; if (xfr->reqtype != dns_rdatatype_ixfr) { xfrin_log(xfr, ISC_LOG_ERROR, "got incremental response to AXFR request"); return (DNS_R_FORMERR); } xfr->is_ixfr = ISC_TRUE; INSIST(xfr->db != NULL); xfr->difflen = 0; journalfile = dns_zone_getjournal(xfr->zone); if (journalfile != NULL) CHECK(dns_journal_open(xfr->mctx, journalfile, ISC_TRUE, &xfr->ixfr.journal)); result = ISC_R_SUCCESS; failure: return (result); } static isc_result_t ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) { isc_result_t result; dns_difftuple_t *tuple = NULL; if (op == DNS_DIFFOP_ADD) CHECK(dns_zone_checknames(xfr->zone, name, rdata)); CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata, &tuple)); dns_diff_append(&xfr->diff, &tuple); if (++xfr->difflen > 100) CHECK(ixfr_apply(xfr)); result = ISC_R_SUCCESS; failure: return (result); } /* * Apply a set of IXFR changes to the database. */ static isc_result_t ixfr_apply(dns_xfrin_ctx_t *xfr) { isc_result_t result; if (xfr->ver == NULL) { CHECK(dns_db_newversion(xfr->db, &xfr->ver)); if (xfr->ixfr.journal != NULL) CHECK(dns_journal_begin_transaction(xfr->ixfr.journal)); } CHECK(dns_diff_apply(&xfr->diff, xfr->db, xfr->ver)); if (xfr->ixfr.journal != NULL) { result = dns_journal_writediff(xfr->ixfr.journal, &xfr->diff); if (result != ISC_R_SUCCESS) goto failure; } dns_diff_clear(&xfr->diff); xfr->difflen = 0; result = ISC_R_SUCCESS; failure: return (result); } static isc_result_t ixfr_commit(dns_xfrin_ctx_t *xfr) { isc_result_t result; CHECK(ixfr_apply(xfr)); if (xfr->ver != NULL) { /* XXX enter ready-to-commit state here */ if (xfr->ixfr.journal != NULL) CHECK(dns_journal_commit(xfr->ixfr.journal)); dns_db_closeversion(xfr->db, &xfr->ver, ISC_TRUE); dns_zone_markdirty(xfr->zone); } result = ISC_R_SUCCESS; failure: return (result); } /**************************************************************************/ /* * Common AXFR/IXFR protocol code */ /* * Handle a single incoming resource record according to the current * state. */ static isc_result_t xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, isc_uint32_t ttl, dns_rdata_t *rdata) { isc_result_t result; redo: switch (xfr->state) { case XFRST_INITIALSOA: if (rdata->type != dns_rdatatype_soa) { xfrin_log(xfr, ISC_LOG_ERROR, "first RR in zone transfer must be SOA"); FAIL(DNS_R_FORMERR); } /* * Remember the serial number in the intial SOA. * We need it to recognize the end of an IXFR. */ xfr->end_serial = dns_soa_getserial(rdata); if (xfr->reqtype == dns_rdatatype_ixfr && ! DNS_SERIAL_GT(xfr->end_serial, xfr->ixfr.request_serial) && !dns_zone_isforced(xfr->zone)) { /* * This must be the single SOA record that is * sent when the current version on the master * is not newer than the version in the request. */ xfrin_log(xfr, ISC_LOG_DEBUG(3), "requested serial %u, " "master has %u, not updating", xfr->ixfr.request_serial, xfr->end_serial); FAIL(DNS_R_UPTODATE); } if (xfr->reqtype == dns_rdatatype_axfr) xfr->checkid = ISC_FALSE; xfr->state = XFRST_FIRSTDATA; break; case XFRST_FIRSTDATA: /* * If the transfer begins with one SOA record, it is an AXFR, * if it begins with two SOAs, it is an IXFR. */ if (xfr->reqtype == dns_rdatatype_ixfr && rdata->type == dns_rdatatype_soa && xfr->ixfr.request_serial == dns_soa_getserial(rdata)) { xfrin_log(xfr, ISC_LOG_DEBUG(3), "got incremental response"); CHECK(ixfr_init(xfr)); xfr->state = XFRST_IXFR_DELSOA; } else { xfrin_log(xfr, ISC_LOG_DEBUG(3), "got nonincremental response"); CHECK(axfr_init(xfr)); xfr->state = XFRST_AXFR; } goto redo; case XFRST_IXFR_DELSOA: INSIST(rdata->type == dns_rdatatype_soa); CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata)); xfr->state = XFRST_IXFR_DEL; break; case XFRST_IXFR_DEL: if (rdata->type == dns_rdatatype_soa) { isc_uint32_t soa_serial = dns_soa_getserial(rdata); xfr->state = XFRST_IXFR_ADDSOA; xfr->ixfr.current_serial = soa_serial; goto redo; } CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata)); break; case XFRST_IXFR_ADDSOA: INSIST(rdata->type == dns_rdatatype_soa); CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata)); xfr->state = XFRST_IXFR_ADD; break; case XFRST_IXFR_ADD: if (rdata->type == dns_rdatatype_soa) { isc_uint32_t soa_serial = dns_soa_getserial(rdata); CHECK(ixfr_commit(xfr)); if (soa_serial == xfr->end_serial) { xfr->state = XFRST_END; break; } else if (soa_serial != xfr->ixfr.current_serial) { xfrin_log(xfr, ISC_LOG_ERROR, "IXFR out of sync: " "expected serial %u, got %u", xfr->ixfr.current_serial, soa_serial); FAIL(DNS_R_FORMERR); } else { xfr->state = XFRST_IXFR_DELSOA; goto redo; } } if (rdata->type == dns_rdatatype_ns && dns_name_iswildcard(name)) FAIL(DNS_R_INVALIDNS); CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata)); break; case XFRST_AXFR: /* * Old BINDs sent cross class A records for non IN classes. */ if (rdata->type == dns_rdatatype_a && rdata->rdclass != xfr->rdclass && xfr->rdclass != dns_rdataclass_in) break; CHECK(axfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata)); if (rdata->type == dns_rdatatype_soa) { CHECK(axfr_commit(xfr)); xfr->state = XFRST_END; break; } break; case XFRST_END: FAIL(DNS_R_EXTRADATA); default: INSIST(0); break; } result = ISC_R_SUCCESS; failure: return (result); } isc_result_t dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, isc_sockaddr_t *masteraddr, dns_tsigkey_t *tsigkey, isc_mem_t *mctx, isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr, isc_task_t *task, dns_xfrindone_t done, dns_xfrin_ctx_t **xfrp) { isc_sockaddr_t sourceaddr; switch (isc_sockaddr_pf(masteraddr)) { case PF_INET: sourceaddr = *dns_zone_getxfrsource4(zone); break; case PF_INET6: sourceaddr = *dns_zone_getxfrsource6(zone); break; default: INSIST(0); } return(dns_xfrin_create2(zone, xfrtype, masteraddr, &sourceaddr, tsigkey, mctx, timermgr, socketmgr, task, done, xfrp)); } isc_result_t dns_xfrin_create2(dns_zone_t *zone, dns_rdatatype_t xfrtype, isc_sockaddr_t *masteraddr, isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey, isc_mem_t *mctx, isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr, isc_task_t *task, dns_xfrindone_t done, dns_xfrin_ctx_t **xfrp) { dns_name_t *zonename = dns_zone_getorigin(zone); dns_xfrin_ctx_t *xfr; isc_result_t result; dns_db_t *db = NULL; REQUIRE(xfrp != NULL && *xfrp == NULL); (void)dns_zone_getdb(zone, &db); CHECK(xfrin_create(mctx, zone, db, task, timermgr, socketmgr, zonename, dns_zone_getclass(zone), xfrtype, masteraddr, sourceaddr, tsigkey, &xfr)); CHECK(xfrin_start(xfr)); xfr->done = done; xfr->refcount++; *xfrp = xfr; failure: if (db != NULL) dns_db_detach(&db); if (result != ISC_R_SUCCESS) xfrin_log1(ISC_LOG_ERROR, zonename, dns_zone_getclass(zone), masteraddr, "zone transfer setup failed"); return (result); } void dns_xfrin_shutdown(dns_xfrin_ctx_t *xfr) { if (! xfr->shuttingdown) xfrin_fail(xfr, ISC_R_CANCELED, "shut down"); } void dns_xfrin_attach(dns_xfrin_ctx_t *source, dns_xfrin_ctx_t **target) { REQUIRE(target != NULL && *target == NULL); source->refcount++; *target = source; } void dns_xfrin_detach(dns_xfrin_ctx_t **xfrp) { dns_xfrin_ctx_t *xfr = *xfrp; INSIST(xfr->refcount > 0); xfr->refcount--; maybe_free(xfr); *xfrp = NULL; } static void xfrin_cancelio(dns_xfrin_ctx_t *xfr) { if (xfr->connects > 0) { isc_socket_cancel(xfr->socket, xfr->task, ISC_SOCKCANCEL_CONNECT); } else if (xfr->recvs > 0) { dns_tcpmsg_cancelread(&xfr->tcpmsg); } else if (xfr->sends > 0) { isc_socket_cancel(xfr->socket, xfr->task, ISC_SOCKCANCEL_SEND); } } static void xfrin_reset(dns_xfrin_ctx_t *xfr) { REQUIRE(VALID_XFRIN(xfr)); xfrin_log(xfr, ISC_LOG_INFO, "resetting"); xfrin_cancelio(xfr); if (xfr->socket != NULL) isc_socket_detach(&xfr->socket); if (xfr->lasttsig != NULL) isc_buffer_free(&xfr->lasttsig); dns_diff_clear(&xfr->diff); xfr->difflen = 0; if (xfr->ixfr.journal != NULL) dns_journal_destroy(&xfr->ixfr.journal); if (xfr->axfr.add_private != NULL) { (void)dns_db_endload(xfr->db, &xfr->axfr.add_private); xfr->axfr.add_func = NULL; } if (xfr->tcpmsg_valid) { dns_tcpmsg_invalidate(&xfr->tcpmsg); xfr->tcpmsg_valid = ISC_FALSE; } if (xfr->ver != NULL) dns_db_closeversion(xfr->db, &xfr->ver, ISC_FALSE); } static void xfrin_fail(dns_xfrin_ctx_t *xfr, isc_result_t result, const char *msg) { if (result != DNS_R_UPTODATE) { xfrin_log(xfr, ISC_LOG_ERROR, "%s: %s", msg, isc_result_totext(result)); if (xfr->is_ixfr) /* Pass special result code to force AXFR retry */ result = DNS_R_BADIXFR; } xfrin_cancelio(xfr); if (xfr->done != NULL) { (xfr->done)(xfr->zone, result); xfr->done = NULL; } xfr->shuttingdown = ISC_TRUE; maybe_free(xfr); } static isc_result_t xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_task_t *task, isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr, dns_name_t *zonename, dns_rdataclass_t rdclass, dns_rdatatype_t reqtype, isc_sockaddr_t *masteraddr, isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey, dns_xfrin_ctx_t **xfrp) { dns_xfrin_ctx_t *xfr = NULL; isc_result_t result; isc_uint32_t tmp; xfr = isc_mem_get(mctx, sizeof(*xfr)); if (xfr == NULL) return (ISC_R_NOMEMORY); xfr->mctx = mctx; xfr->refcount = 0; xfr->zone = NULL; dns_zone_iattach(zone, &xfr->zone); xfr->task = NULL; isc_task_attach(task, &xfr->task); xfr->timer = NULL; xfr->socketmgr = socketmgr; xfr->done = NULL; xfr->connects = 0; xfr->sends = 0; xfr->recvs = 0; xfr->shuttingdown = ISC_FALSE; dns_name_init(&xfr->name, NULL); xfr->rdclass = rdclass; isc_random_get(&tmp); xfr->checkid = ISC_TRUE; xfr->id = (isc_uint16_t)(tmp & 0xffff); xfr->reqtype = reqtype; /* sockaddr */ xfr->socket = NULL; /* qbuffer */ /* qbuffer_data */ /* tcpmsg */ xfr->tcpmsg_valid = ISC_FALSE; xfr->db = NULL; if (db != NULL) dns_db_attach(db, &xfr->db); xfr->ver = NULL; dns_diff_init(xfr->mctx, &xfr->diff); xfr->difflen = 0; xfr->state = XFRST_INITIALSOA; /* end_serial */ xfr->nmsg = 0; xfr->tsigkey = NULL; if (tsigkey != NULL) dns_tsigkey_attach(tsigkey, &xfr->tsigkey); xfr->lasttsig = NULL; xfr->tsigctx = NULL; xfr->sincetsig = 0; xfr->is_ixfr = ISC_FALSE; /* ixfr.request_serial */ /* ixfr.current_serial */ xfr->ixfr.journal = NULL; xfr->axfr.add_func = NULL; xfr->axfr.add_private = NULL; CHECK(dns_name_dup(zonename, mctx, &xfr->name)); CHECK(isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, task, xfrin_timeout, xfr, &xfr->timer)); CHECK(dns_timer_setidle(xfr->timer, dns_zone_getmaxxfrin(xfr->zone), dns_zone_getidlein(xfr->zone), ISC_FALSE)); xfr->masteraddr = *masteraddr; INSIST(isc_sockaddr_pf(masteraddr) == isc_sockaddr_pf(sourceaddr)); xfr->sourceaddr = *sourceaddr; isc_sockaddr_setport(&xfr->sourceaddr, 0); isc_buffer_init(&xfr->qbuffer, xfr->qbuffer_data, sizeof(xfr->qbuffer_data)); xfr->magic = XFRIN_MAGIC; *xfrp = xfr; return (ISC_R_SUCCESS); failure: xfrin_fail(xfr, result, "failed creating transfer context"); return (result); } static isc_result_t xfrin_start(dns_xfrin_ctx_t *xfr) { isc_result_t result; CHECK(isc_socket_create(xfr->socketmgr, isc_sockaddr_pf(&xfr->sourceaddr), isc_sockettype_tcp, &xfr->socket)); CHECK(isc_socket_bind(xfr->socket, &xfr->sourceaddr)); CHECK(isc_socket_connect(xfr->socket, &xfr->masteraddr, xfr->task, xfrin_connect_done, xfr)); xfr->connects++; return (ISC_R_SUCCESS); failure: xfrin_fail(xfr, result, "failed setting up socket"); return (result); } /* XXX the resolver could use this, too */ static isc_result_t render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf) { dns_compress_t cctx; isc_boolean_t cleanup_cctx = ISC_FALSE; isc_result_t result; CHECK(dns_compress_init(&cctx, -1, mctx)); cleanup_cctx = ISC_TRUE; CHECK(dns_message_renderbegin(msg, &cctx, buf)); CHECK(dns_message_rendersection(msg, DNS_SECTION_QUESTION, 0)); CHECK(dns_message_rendersection(msg, DNS_SECTION_ANSWER, 0)); CHECK(dns_message_rendersection(msg, DNS_SECTION_AUTHORITY, 0)); CHECK(dns_message_rendersection(msg, DNS_SECTION_ADDITIONAL, 0)); CHECK(dns_message_renderend(msg)); result = ISC_R_SUCCESS; failure: if (cleanup_cctx) dns_compress_invalidate(&cctx); return (result); } /* * A connection has been established. */ static void xfrin_connect_done(isc_task_t *task, isc_event_t *event) { isc_socket_connev_t *cev = (isc_socket_connev_t *) event; dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *) event->ev_arg; isc_result_t evresult = cev->result; isc_result_t result; char sourcetext[ISC_SOCKADDR_FORMATSIZE]; isc_sockaddr_t sockaddr; REQUIRE(VALID_XFRIN(xfr)); UNUSED(task); INSIST(event->ev_type == ISC_SOCKEVENT_CONNECT); isc_event_free(&event); xfr->connects--; if (xfr->shuttingdown) { maybe_free(xfr); return; } CHECK(evresult); result = isc_socket_getsockname(xfr->socket, &sockaddr); if (result == ISC_R_SUCCESS) { isc_sockaddr_format(&sockaddr, sourcetext, sizeof(sourcetext)); } else strcpy(sourcetext, ""); xfrin_log(xfr, ISC_LOG_INFO, "connected using %s", sourcetext); dns_tcpmsg_init(xfr->mctx, xfr->socket, &xfr->tcpmsg); xfr->tcpmsg_valid = ISC_TRUE; CHECK(xfrin_send_request(xfr)); failure: if (result != ISC_R_SUCCESS) xfrin_fail(xfr, result, "failed to connect"); } /* * Convert a tuple into a dns_name_t suitable for inserting * into the given dns_message_t. */ static isc_result_t tuple2msgname(dns_difftuple_t *tuple, dns_message_t *msg, dns_name_t **target) { isc_result_t result; dns_rdata_t *rdata = NULL; dns_rdatalist_t *rdl = NULL; dns_rdataset_t *rds = NULL; dns_name_t *name = NULL; REQUIRE(target != NULL && *target == NULL); CHECK(dns_message_gettemprdata(msg, &rdata)); dns_rdata_init(rdata); dns_rdata_clone(&tuple->rdata, rdata); CHECK(dns_message_gettemprdatalist(msg, &rdl)); dns_rdatalist_init(rdl); rdl->type = tuple->rdata.type; rdl->rdclass = tuple->rdata.rdclass; rdl->ttl = tuple->ttl; ISC_LIST_APPEND(rdl->rdata, rdata, link); CHECK(dns_message_gettemprdataset(msg, &rds)); dns_rdataset_init(rds); CHECK(dns_rdatalist_tordataset(rdl, rds)); CHECK(dns_message_gettempname(msg, &name)); dns_name_init(name, NULL); dns_name_clone(&tuple->name, name); ISC_LIST_APPEND(name->list, rds, link); *target = name; return (ISC_R_SUCCESS); failure: if (rds != NULL) dns_rdataset_disassociate(rds); dns_message_puttemprdataset(msg, &rds); if (rdl != NULL) { ISC_LIST_UNLINK(rdl->rdata, rdata, link); dns_message_puttemprdatalist(msg, &rdl); } if (rdata != NULL) dns_message_puttemprdata(msg, &rdata); return (result); } /* * Build an *XFR request and send its length prefix. */ static isc_result_t xfrin_send_request(dns_xfrin_ctx_t *xfr) { isc_result_t result; isc_region_t region; isc_region_t lregion; dns_rdataset_t *qrdataset = NULL; dns_message_t *msg = NULL; unsigned char length[2]; dns_difftuple_t *soatuple = NULL; dns_name_t *qname = NULL; dns_dbversion_t *ver = NULL; dns_name_t *msgsoaname = NULL; /* Create the request message */ CHECK(dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTRENDER, &msg)); CHECK(dns_message_settsigkey(msg, xfr->tsigkey)); /* Create a name for the question section. */ CHECK(dns_message_gettempname(msg, &qname)); dns_name_init(qname, NULL); dns_name_clone(&xfr->name, qname); /* Formulate the question and attach it to the question name. */ CHECK(dns_message_gettemprdataset(msg, &qrdataset)); dns_rdataset_init(qrdataset); dns_rdataset_makequestion(qrdataset, xfr->rdclass, xfr->reqtype); ISC_LIST_APPEND(qname->list, qrdataset, link); qrdataset = NULL; dns_message_addname(msg, qname, DNS_SECTION_QUESTION); qname = NULL; if (xfr->reqtype == dns_rdatatype_ixfr) { /* Get the SOA and add it to the authority section. */ /* XXX is using the current version the right thing? */ dns_db_currentversion(xfr->db, &ver); CHECK(dns_db_createsoatuple(xfr->db, ver, xfr->mctx, DNS_DIFFOP_EXISTS, &soatuple)); xfr->ixfr.request_serial = dns_soa_getserial(&soatuple->rdata); xfr->ixfr.current_serial = xfr->ixfr.request_serial; xfrin_log(xfr, ISC_LOG_DEBUG(3), "requesting IXFR for serial %u", xfr->ixfr.request_serial); CHECK(tuple2msgname(soatuple, msg, &msgsoaname)); dns_message_addname(msg, msgsoaname, DNS_SECTION_AUTHORITY); } xfr->checkid = ISC_TRUE; xfr->id++; msg->id = xfr->id; CHECK(render(msg, xfr->mctx, &xfr->qbuffer)); /* * Free the last tsig, if there is one. */ if (xfr->lasttsig != NULL) isc_buffer_free(&xfr->lasttsig); /* * Save the query TSIG and don't let message_destroy free it. */ CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig)); isc_buffer_usedregion(&xfr->qbuffer, ®ion); INSIST(region.length <= 65535); length[0] = region.length >> 8; length[1] = region.length & 0xFF; lregion.base = length; lregion.length = 2; CHECK(isc_socket_send(xfr->socket, &lregion, xfr->task, xfrin_sendlen_done, xfr)); xfr->sends++; failure: if (qname != NULL) dns_message_puttempname(msg, &qname); if (qrdataset != NULL) dns_message_puttemprdataset(msg, &qrdataset); if (msg != NULL) dns_message_destroy(&msg); if (soatuple != NULL) dns_difftuple_free(&soatuple); if (ver != NULL) dns_db_closeversion(xfr->db, &ver, ISC_FALSE); return (result); } /* XXX there should be library support for sending DNS TCP messages */ static void xfrin_sendlen_done(isc_task_t *task, isc_event_t *event) { isc_socketevent_t *sev = (isc_socketevent_t *) event; dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *) event->ev_arg; isc_result_t evresult = sev->result; isc_result_t result; isc_region_t region; REQUIRE(VALID_XFRIN(xfr)); UNUSED(task); INSIST(event->ev_type == ISC_SOCKEVENT_SENDDONE); isc_event_free(&event); xfr->sends--; if (xfr->shuttingdown) { maybe_free(xfr); return; } xfrin_log(xfr, ISC_LOG_DEBUG(3), "sent request length prefix"); CHECK(evresult); isc_buffer_usedregion(&xfr->qbuffer, ®ion); CHECK(isc_socket_send(xfr->socket, ®ion, xfr->task, xfrin_send_done, xfr)); xfr->sends++; failure: if (result != ISC_R_SUCCESS) xfrin_fail(xfr, result, "failed sending request length prefix"); } static void xfrin_send_done(isc_task_t *task, isc_event_t *event) { isc_socketevent_t *sev = (isc_socketevent_t *) event; dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *) event->ev_arg; isc_result_t result; REQUIRE(VALID_XFRIN(xfr)); UNUSED(task); INSIST(event->ev_type == ISC_SOCKEVENT_SENDDONE); xfr->sends--; xfrin_log(xfr, ISC_LOG_DEBUG(3), "sent request data"); CHECK(sev->result); CHECK(dns_tcpmsg_readmessage(&xfr->tcpmsg, xfr->task, xfrin_recv_done, xfr)); xfr->recvs++; failure: isc_event_free(&event); if (result != ISC_R_SUCCESS) xfrin_fail(xfr, result, "failed sending request data"); } static void xfrin_recv_done(isc_task_t *task, isc_event_t *ev) { dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *) ev->ev_arg; isc_result_t result; dns_message_t *msg = NULL; dns_name_t *name; dns_tcpmsg_t *tcpmsg; dns_name_t *tsigowner = NULL; REQUIRE(VALID_XFRIN(xfr)); UNUSED(task); INSIST(ev->ev_type == DNS_EVENT_TCPMSG); tcpmsg = ev->ev_sender; isc_event_free(&ev); xfr->recvs--; if (xfr->shuttingdown) { maybe_free(xfr); return; } CHECK(tcpmsg->result); xfrin_log(xfr, ISC_LOG_DEBUG(7), "received %u bytes", tcpmsg->buffer.used); CHECK(isc_timer_touch(xfr->timer)); CHECK(dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTPARSE, &msg)); CHECK(dns_message_settsigkey(msg, xfr->tsigkey)); CHECK(dns_message_setquerytsig(msg, xfr->lasttsig)); msg->tsigctx = xfr->tsigctx; if (xfr->nmsg > 0) msg->tcp_continuation = 1; result = dns_message_parse(msg, &tcpmsg->buffer, DNS_MESSAGEPARSE_PRESERVEORDER); if (result != ISC_R_SUCCESS || msg->rcode != dns_rcode_noerror || (xfr->checkid && msg->id != xfr->id)) { if (result == ISC_R_SUCCESS) result = ISC_RESULTCLASS_DNSRCODE + msg->rcode; /*XXX*/ if (result == ISC_R_SUCCESS || result == DNS_R_NOERROR) result = DNS_R_UNEXPECTEDID; if (xfr->reqtype == dns_rdatatype_axfr || xfr->reqtype == dns_rdatatype_soa) FAIL(result); xfrin_log(xfr, ISC_LOG_DEBUG(3), "got %s, retrying with AXFR", isc_result_totext(result)); try_axfr: dns_message_destroy(&msg); xfrin_reset(xfr); xfr->reqtype = dns_rdatatype_axfr; xfr->state = XFRST_INITIALSOA; (void)xfrin_start(xfr); return; } /* * Does the server know about IXFR? If it doesn't we will get * a message with a empty answer section or a potentially a CNAME / * DNAME, the later is handled by xfr_rr() which will return FORMERR * if the first RR in the answer section is not a SOA record. */ if (xfr->reqtype == dns_rdatatype_ixfr && xfr->state == XFRST_INITIALSOA && msg->counts[DNS_SECTION_ANSWER] == 0) { xfrin_log(xfr, ISC_LOG_DEBUG(3), "empty answer section, retrying with AXFR"); goto try_axfr; } if (xfr->reqtype == dns_rdatatype_soa && (msg->flags & DNS_MESSAGEFLAG_AA) == 0) { FAIL(DNS_R_NOTAUTHORITATIVE); } result = dns_message_checksig(msg, dns_zone_getview(xfr->zone)); if (result != ISC_R_SUCCESS) { xfrin_log(xfr, ISC_LOG_DEBUG(3), "TSIG check failed: %s", isc_result_totext(result)); FAIL(result); } for (result = dns_message_firstname(msg, DNS_SECTION_ANSWER); result == ISC_R_SUCCESS; result = dns_message_nextname(msg, DNS_SECTION_ANSWER)) { dns_rdataset_t *rds; name = NULL; dns_message_currentname(msg, DNS_SECTION_ANSWER, &name); for (rds = ISC_LIST_HEAD(name->list); rds != NULL; rds = ISC_LIST_NEXT(rds, link)) { for (result = dns_rdataset_first(rds); result == ISC_R_SUCCESS; result = dns_rdataset_next(rds)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(rds, &rdata); CHECK(xfr_rr(xfr, name, rds->ttl, &rdata)); } } } if (result != ISC_R_NOMORE) goto failure; if (dns_message_gettsig(msg, &tsigowner) != NULL) { /* * Reset the counter. */ xfr->sincetsig = 0; /* * Free the last tsig, if there is one. */ if (xfr->lasttsig != NULL) isc_buffer_free(&xfr->lasttsig); /* * Update the last tsig pointer. */ CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig)); } else if (dns_message_gettsigkey(msg) != NULL) { xfr->sincetsig++; if (xfr->sincetsig > 100 || xfr->nmsg == 0 || xfr->state == XFRST_END) { result = DNS_R_EXPECTEDTSIG; goto failure; } } /* * Update the number of messages received. */ xfr->nmsg++; /* * Copy the context back. */ xfr->tsigctx = msg->tsigctx; dns_message_destroy(&msg); if (xfr->state == XFRST_END) { /* * Inform the caller we succeeded. */ if (xfr->done != NULL) { (xfr->done)(xfr->zone, ISC_R_SUCCESS); xfr->done = NULL; } /* * We should have no outstanding events at this * point, thus maybe_free() should succeed. */ xfr->shuttingdown = ISC_TRUE; maybe_free(xfr); } else { /* * Read the next message. */ CHECK(dns_tcpmsg_readmessage(&xfr->tcpmsg, xfr->task, xfrin_recv_done, xfr)); xfr->recvs++; } return; failure: if (msg != NULL) dns_message_destroy(&msg); if (result != ISC_R_SUCCESS) xfrin_fail(xfr, result, "failed while receiving responses"); } static void xfrin_timeout(isc_task_t *task, isc_event_t *event) { dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *) event->ev_arg; REQUIRE(VALID_XFRIN(xfr)); UNUSED(task); isc_event_free(&event); /* * This will log "giving up: timeout". */ xfrin_fail(xfr, ISC_R_TIMEDOUT, "giving up"); } static void maybe_free(dns_xfrin_ctx_t *xfr) { REQUIRE(VALID_XFRIN(xfr)); if (! xfr->shuttingdown || xfr->refcount != 0 || xfr->connects != 0 || xfr->sends != 0 || xfr->recvs != 0) return; xfrin_log(xfr, ISC_LOG_INFO, "end of transfer"); if (xfr->socket != NULL) isc_socket_detach(&xfr->socket); if (xfr->timer != NULL) isc_timer_detach(&xfr->timer); if (xfr->task != NULL) isc_task_detach(&xfr->task); if (xfr->tsigkey != NULL) dns_tsigkey_detach(&xfr->tsigkey); if (xfr->lasttsig != NULL) isc_buffer_free(&xfr->lasttsig); dns_diff_clear(&xfr->diff); if (xfr->ixfr.journal != NULL) dns_journal_destroy(&xfr->ixfr.journal); if (xfr->axfr.add_private != NULL) (void)dns_db_endload(xfr->db, &xfr->axfr.add_private); if (xfr->tcpmsg_valid) dns_tcpmsg_invalidate(&xfr->tcpmsg); if ((xfr->name.attributes & DNS_NAMEATTR_DYNAMIC) != 0) dns_name_free(&xfr->name, xfr->mctx); if (xfr->ver != NULL) dns_db_closeversion(xfr->db, &xfr->ver, ISC_FALSE); if (xfr->db != NULL) dns_db_detach(&xfr->db); if (xfr->zone != NULL) dns_zone_idetach(&xfr->zone); isc_mem_put(xfr->mctx, xfr, sizeof(*xfr)); } /* * Log incoming zone transfer messages in a format like * transfer of from
: */ static void xfrin_logv(int level, dns_name_t *zonename, dns_rdataclass_t rdclass, isc_sockaddr_t *masteraddr, const char *fmt, va_list ap) { char zntext[DNS_NAME_FORMATSIZE]; char mastertext[ISC_SOCKADDR_FORMATSIZE]; char classtext[DNS_RDATACLASS_FORMATSIZE]; char msgtext[2048]; dns_name_format(zonename, zntext, sizeof(zntext)); dns_rdataclass_format(rdclass, classtext, sizeof(classtext)); isc_sockaddr_format(masteraddr, mastertext, sizeof(mastertext)); vsnprintf(msgtext, sizeof(msgtext), fmt, ap); isc_log_write(dns_lctx, DNS_LOGCATEGORY_XFER_IN, DNS_LOGMODULE_XFER_IN, level, "transfer of '%s/%s' from %s: %s", zntext, classtext, mastertext, msgtext); } /* * Logging function for use when a xfrin_ctx_t has not yet been created. */ static void xfrin_log1(int level, dns_name_t *zonename, dns_rdataclass_t rdclass, isc_sockaddr_t *masteraddr, const char *fmt, ...) { va_list ap; if (isc_log_wouldlog(dns_lctx, level) == ISC_FALSE) return; va_start(ap, fmt); xfrin_logv(level, zonename, rdclass, masteraddr, fmt, ap); va_end(ap); } /* * Logging function for use when there is a xfrin_ctx_t. */ static void xfrin_log(dns_xfrin_ctx_t *xfr, unsigned int level, const char *fmt, ...) { va_list ap; if (isc_log_wouldlog(dns_lctx, level) == ISC_FALSE) return; va_start(ap, fmt); xfrin_logv(level, &xfr->name, xfr->rdclass, &xfr->masteraddr, fmt, ap); va_end(ap); }