/* * axfr.c -- generating AXFR responses. * * Copyright (c) 2001-2006, NLnet Labs. All rights reserved. * * See LICENSE for the license. * */ #include "config.h" #include "axfr.h" #include "dns.h" #include "packet.h" #include "options.h" #include "ixfr.h" /* draft-ietf-dnsop-rfc2845bis-06, section 5.3.1 says to sign every packet */ #define AXFR_TSIG_SIGN_EVERY_NTH 0 /* tsig sign every N packets. */ query_state_type query_axfr(struct nsd *nsd, struct query *query, int wstats) { domain_type *closest_match; domain_type *closest_encloser; int exact; int added; uint16_t total_added = 0; if (query->axfr_is_done) return QUERY_PROCESSED; if (query->maxlen > AXFR_MAX_MESSAGE_LEN) query->maxlen = AXFR_MAX_MESSAGE_LEN; assert(!query_overflow(query)); /* only keep running values for most packets */ query->tsig_prepare_it = 0; query->tsig_update_it = 1; if(query->tsig_sign_it) { /* prepare for next updates */ query->tsig_prepare_it = 1; query->tsig_sign_it = 0; } if (query->axfr_zone == NULL) { domain_type* qdomain; /* Start AXFR. */ if(wstats) { STATUP(nsd, raxfr); } exact = namedb_lookup(nsd->db, query->qname, &closest_match, &closest_encloser); qdomain = closest_encloser; query->axfr_zone = domain_find_zone(nsd->db, closest_encloser); if (!exact || query->axfr_zone == NULL || query->axfr_zone->apex != qdomain || query->axfr_zone->soa_rrset == NULL) { /* No SOA no transfer */ RCODE_SET(query->packet, RCODE_NOTAUTH); return QUERY_PROCESSED; } if(wstats) { ZTATUP(nsd, query->axfr_zone, raxfr); } query->axfr_current_domain = qdomain; query->axfr_current_rrset = NULL; query->axfr_current_rr = 0; if(query->tsig.status == TSIG_OK) { query->tsig_sign_it = 1; /* sign first packet in stream */ } query_add_compression_domain(query, qdomain, QHEADERSZ); assert(query->axfr_zone->soa_rrset->rr_count == 1); added = packet_encode_rr(query, query->axfr_zone->apex, &query->axfr_zone->soa_rrset->rrs[0], query->axfr_zone->soa_rrset->rrs[0].ttl); if (!added) { /* XXX: This should never happen... generate error code? */ abort(); } ++total_added; } else { /* * Query name and EDNS need not be repeated after the * first response packet. */ query->edns.status = EDNS_NOT_PRESENT; buffer_set_limit(query->packet, QHEADERSZ); QDCOUNT_SET(query->packet, 0); query_prepare_response(query); } /* Add zone RRs until answer is full. */ while (query->axfr_current_domain != NULL && domain_is_subdomain(query->axfr_current_domain, query->axfr_zone->apex)) { if (!query->axfr_current_rrset) { query->axfr_current_rrset = domain_find_any_rrset( query->axfr_current_domain, query->axfr_zone); query->axfr_current_rr = 0; } while (query->axfr_current_rrset) { if (query->axfr_current_rrset != query->axfr_zone->soa_rrset && query->axfr_current_rrset->zone == query->axfr_zone) { while (query->axfr_current_rr < query->axfr_current_rrset->rr_count) { size_t oldmaxlen = query->maxlen; if(total_added == 0) /* RR > 16K can be first RR */ query->maxlen = (query->tcp?TCP_MAX_MESSAGE_LEN:UDP_MAX_MESSAGE_LEN); added = packet_encode_rr( query, query->axfr_current_domain, &query->axfr_current_rrset->rrs[query->axfr_current_rr], query->axfr_current_rrset->rrs[query->axfr_current_rr].ttl); if(total_added == 0) { query->maxlen = oldmaxlen; if(query_overflow(query)) { if(added) { ++total_added; ++query->axfr_current_rr; goto return_answer; } } } if (!added) goto return_answer; ++total_added; ++query->axfr_current_rr; } } query->axfr_current_rrset = query->axfr_current_rrset->next; query->axfr_current_rr = 0; } assert(query->axfr_current_domain); query->axfr_current_domain = domain_next(query->axfr_current_domain); } /* Add terminating SOA RR. */ assert(query->axfr_zone->soa_rrset->rr_count == 1); added = packet_encode_rr(query, query->axfr_zone->apex, &query->axfr_zone->soa_rrset->rrs[0], query->axfr_zone->soa_rrset->rrs[0].ttl); if (added) { ++total_added; query->tsig_sign_it = 1; /* sign last packet */ query->axfr_is_done = 1; } return_answer: AA_SET(query->packet); ANCOUNT_SET(query->packet, total_added); NSCOUNT_SET(query->packet, 0); ARCOUNT_SET(query->packet, 0); /* check if it needs tsig signatures */ if(query->tsig.status == TSIG_OK) { #if AXFR_TSIG_SIGN_EVERY_NTH > 0 if(query->tsig.updates_since_last_prepare >= AXFR_TSIG_SIGN_EVERY_NTH) { #endif query->tsig_sign_it = 1; #if AXFR_TSIG_SIGN_EVERY_NTH > 0 } #endif } query_clear_compression_tables(query); return QUERY_IN_AXFR; } /* See if the query can be admitted. */ static int axfr_ixfr_can_admit_query(struct nsd* nsd, struct query* q) { struct acl_options *acl = NULL; struct zone_options* zone_opt; zone_opt = zone_options_find(nsd->options, q->qname); if(!zone_opt || acl_check_incoming(zone_opt->pattern->provide_xfr, q, &acl)==-1) { if (verbosity >= 2) { char a[128]; addr2str(&q->addr, a, sizeof(a)); VERBOSITY(2, (LOG_INFO, "%s for %s from %s refused, %s", (q->qtype==TYPE_AXFR?"axfr":"ixfr"), dname_to_string(q->qname, NULL), a, acl?"blocked":"no acl matches")); } DEBUG(DEBUG_XFRD,1, (LOG_INFO, "%s refused, %s", (q->qtype==TYPE_AXFR?"axfr":"ixfr"), acl?"blocked":"no acl matches")); if (!zone_opt) { RCODE_SET(q->packet, RCODE_NOTAUTH); } else { RCODE_SET(q->packet, RCODE_REFUSE); /* RFC8914 - Extended DNS Errors * 4.19. Extended DNS Error Code 18 - Prohibited */ q->edns.ede = EDE_PROHIBITED; } return 0; } DEBUG(DEBUG_XFRD,1, (LOG_INFO, "%s admitted acl %s %s", (q->qtype==TYPE_AXFR?"axfr":"ixfr"), acl->ip_address_spec, acl->key_name?acl->key_name:"NOKEY")); if (verbosity >= 1) { char a[128]; addr2str(&q->addr, a, sizeof(a)); VERBOSITY(1, (LOG_INFO, "%s for %s from %s", (q->qtype==TYPE_AXFR?"axfr":"ixfr"), dname_to_string(q->qname, NULL), a)); } return 1; } /* * Answer if this is an AXFR or IXFR query. */ query_state_type answer_axfr_ixfr(struct nsd *nsd, struct query *q) { /* Is it AXFR? */ switch (q->qtype) { case TYPE_AXFR: if (q->tcp) { if(!axfr_ixfr_can_admit_query(nsd, q)) return QUERY_PROCESSED; return query_axfr(nsd, q, 1); } /* AXFR over UDP queries are discarded. */ RCODE_SET(q->packet, RCODE_IMPL); return QUERY_PROCESSED; case TYPE_IXFR: if(!axfr_ixfr_can_admit_query(nsd, q)) { /* get rid of authority section, if present */ NSCOUNT_SET(q->packet, 0); ARCOUNT_SET(q->packet, 0); if(QDCOUNT(q->packet) > 0 && (size_t)QHEADERSZ+4+ q->qname->name_size <= buffer_limit(q->packet)) { buffer_set_position(q->packet, QHEADERSZ+4+ q->qname->name_size); } return QUERY_PROCESSED; } return query_ixfr(nsd, q); default: return QUERY_DISCARDED; } }