From: florian Date: Fri, 13 May 2022 15:48:29 +0000 (+0000) Subject: Update to nsd 4.5.0 X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=4564029f9cb3eebe0367746e84e30a977eed3985;p=openbsd Update to nsd 4.5.0 OK sthen --- diff --git a/usr.sbin/nsd/Makefile.in b/usr.sbin/nsd/Makefile.in index e28fc47cd32..b6b7eb37570 100644 --- a/usr.sbin/nsd/Makefile.in +++ b/usr.sbin/nsd/Makefile.in @@ -79,7 +79,7 @@ EDIT = $(SED) \ TARGETS=nsd nsd-checkconf nsd-checkzone nsd-control nsd.conf.sample nsd-control-setup.sh MANUALS=nsd.8 nsd-checkconf.8 nsd-checkzone.8 nsd-control.8 nsd.conf.5 -COMMON_OBJ=answer.o axfr.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o siphash.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o bitset.o popen3.o +COMMON_OBJ=answer.o axfr.o ixfr.o ixfrcreate.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o siphash.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o bitset.o popen3.o XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.o $(DNSTAP_OBJ) NSD_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) difffile.o ipc.o mini_event.o netio.o nsd.o server.o dbaccess.o dbcreate.o zlexer.o zonec.o zparser.o ALL_OBJ=$(NSD_OBJ) nsd-checkconf.o nsd-checkzone.o nsd-control.o nsd-mem.o xfr-inspect.o @@ -174,11 +174,11 @@ nsd-mem: $(NSD_MEM_OBJ) $(LIBOBJS) cutest: $(CUTEST_OBJ) $(LIBOBJS) popen3_echo $(LINK) -o $@ $(CUTEST_OBJ) $(LIBOBJS) $(SSL_LIBS) $(LIBS) -udb-inspect: udb-inspect.o $(COMMON_OBJ) $(LIBOBJS) - $(LINK) -o $@ udb-inspect.o $(COMMON_OBJ) $(LIBOBJS) $(LIBS) +udb-inspect: udb-inspect.o $(COMMON_OBJ) zonec.o zparser.o zlexer.o $(LIBOBJS) + $(LINK) -o $@ udb-inspect.o $(COMMON_OBJ) zonec.o zparser.o zlexer.o $(LIBOBJS) $(LIBS) -xfr-inspect: xfr-inspect.o $(COMMON_OBJ) $(LIBOBJS) - $(LINK) -o $@ xfr-inspect.o $(COMMON_OBJ) $(LIBOBJS) $(LIBS) +xfr-inspect: xfr-inspect.o $(COMMON_OBJ) zonec.o zparser.o zlexer.o $(LIBOBJS) + $(LINK) -o $@ xfr-inspect.o $(COMMON_OBJ) zonec.o zparser.o zlexer.o $(LIBOBJS) $(LIBS) popen3_echo: popen3.o popen3_echo.o $(LINK) -o $@ popen3.o popen3_echo.o @@ -417,9 +417,11 @@ depend: answer.o: $(srcdir)/answer.c config.h $(srcdir)/answer.h $(srcdir)/dns.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/packet.h $(srcdir)/query.h $(srcdir)/nsd.h \ $(srcdir)/edns.h $(srcdir)/tsig.h +ixfr.o: $(srcdir)/ixfr.c config.h $(srcdir)/ixfr.h $(srcdir)/query.h $(srcdir)/packet.h $(srcdir)/rdata.h $(srcdir)/axfr.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/zonec.h $(srcdir)/namedb.h $(srcdir)/nsd.h $(srcdir)/tsig.h $(srcdir)/dns.h $(srcdir)/region-allocator.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/buffer.h $(srcdir)/util.h +ixfrcreate.o: $(srcdir)/ixfrcreate.c config.h $(srcdir)/ixfrcreate.h $(srcdir)/namedb.h $(srcdir)/ixfr.h $(srcdir)/options.h $(srcdir)/dname.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/region-allocator.h $(srcdir)/buffer.h $(srcdir)/util.h axfr.o: $(srcdir)/axfr.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \ - $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/options.h + $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/options.h $(srcdir)/ixfr.h buffer.o: $(srcdir)/buffer.c config.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h configlexer.o: configlexer.c config.h $(srcdir)/options.h \ $(srcdir)/region-allocator.h $(srcdir)/rbtree.h configparser.h @@ -428,14 +430,14 @@ configparser.o: configparser.c config.h $(srcdir)/options.h $(srcdir)/region-all $(srcdir)/radtree.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/packet.h dbaccess.o: $(srcdir)/dbaccess.c config.h $(srcdir)/dns.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/options.h $(srcdir)/rdata.h $(srcdir)/udb.h \ - $(srcdir)/udbradtree.h $(srcdir)/udbzone.h $(srcdir)/zonec.h $(srcdir)/nsec3.h $(srcdir)/difffile.h $(srcdir)/nsd.h $(srcdir)/edns.h + $(srcdir)/udbradtree.h $(srcdir)/udbzone.h $(srcdir)/zonec.h $(srcdir)/nsec3.h $(srcdir)/difffile.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/ixfr.h $(srcdir)/ixfrcreate.h dbcreate.o: $(srcdir)/dbcreate.c config.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/udb.h $(srcdir)/udbradtree.h \ - $(srcdir)/udbzone.h $(srcdir)/options.h $(srcdir)/nsd.h $(srcdir)/edns.h + $(srcdir)/udbzone.h $(srcdir)/options.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/ixfr.h difffile.o: $(srcdir)/difffile.c config.h $(srcdir)/difffile.h $(srcdir)/rbtree.h $(srcdir)/region-allocator.h \ $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/options.h $(srcdir)/udb.h \ $(srcdir)/xfrd-disk.h $(srcdir)/packet.h $(srcdir)/rdata.h $(srcdir)/udbzone.h $(srcdir)/udbradtree.h $(srcdir)/nsec3.h $(srcdir)/nsd.h $(srcdir)/edns.h \ - $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/tsig.h + $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/tsig.h $(srcdir)/ixfr.h $(srcdir)/zonec.h dname.o: $(srcdir)/dname.c config.h $(srcdir)/dns.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h \ $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/packet.h $(srcdir)/tsig.h dns.o: $(srcdir)/dns.c config.h $(srcdir)/dns.h $(srcdir)/zonec.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h \ @@ -459,10 +461,10 @@ nsd-checkconf.o: $(srcdir)/nsd-checkconf.c config.h $(srcdir)/tsig.h $(srcdir)/b $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/rrl.h $(srcdir)/query.h \ $(srcdir)/namedb.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/packet.h nsd-checkzone.o: $(srcdir)/nsd-checkzone.c config.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ - $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/zonec.h $(srcdir)/namedb.h $(srcdir)/dname.h \ - $(srcdir)/radtree.h + $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/bitset.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/zonec.h $(srcdir)/namedb.h $(srcdir)/dname.h \ + $(srcdir)/radtree.h $(srcdir)/ixfr.h $(srcdir)/query.h $(srcdir)/packet.h $(srcdir)/ixfrcreate.h $(srcdir)/difffile.h $(srcdir)/udb.h nsd-control.o: $(srcdir)/nsd-control.c config.h $(srcdir)/util.h $(srcdir)/tsig.h $(srcdir)/buffer.h \ - $(srcdir)/region-allocator.h $(srcdir)/dname.h $(srcdir)/options.h $(srcdir)/rbtree.h + $(srcdir)/region-allocator.h $(srcdir)/dname.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/zonec.h nsd-mem.o: $(srcdir)/nsd-mem.c config.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/tsig.h $(srcdir)/dname.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/namedb.h \ $(srcdir)/radtree.h $(srcdir)/udb.h $(srcdir)/udbzone.h $(srcdir)/udbradtree.h @@ -494,7 +496,7 @@ rrl.o: $(srcdir)/rrl.c config.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/name server.o: $(srcdir)/server.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \ $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/netio.h $(srcdir)/xfrd.h $(srcdir)/options.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h \ - $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h + $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h $(srcdir)/ixfr.h siphash.o: $(srcdir)/siphash.c tsig.o: $(srcdir)/tsig.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h \ $(srcdir)/tsig-openssl.h $(srcdir)/dns.h $(srcdir)/packet.h $(srcdir)/namedb.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/query.h $(srcdir)/nsd.h \ diff --git a/usr.sbin/nsd/axfr.c b/usr.sbin/nsd/axfr.c index cd96bd1cfd7..dbf3eef914d 100644 --- a/usr.sbin/nsd/axfr.c +++ b/usr.sbin/nsd/axfr.c @@ -13,12 +13,13 @@ #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) +query_axfr(struct nsd *nsd, struct query *query, int wstats) { domain_type *closest_match; domain_type *closest_encloser; @@ -45,7 +46,9 @@ query_axfr(struct nsd *nsd, struct query *query) if (query->axfr_zone == NULL) { domain_type* qdomain; /* Start AXFR. */ - STATUP(nsd, raxfr); + if(wstats) { + STATUP(nsd, raxfr); + } exact = namedb_lookup(nsd->db, query->qname, &closest_match, @@ -63,7 +66,9 @@ query_axfr(struct nsd *nsd, struct query *query) RCODE_SET(query->packet, RCODE_NOTAUTH); return QUERY_PROCESSED; } - ZTATUP(nsd, query->axfr_zone, raxfr); + if(wstats) { + ZTATUP(nsd, query->axfr_zone, raxfr); + } query->axfr_current_domain = qdomain; query->axfr_current_rrset = NULL; @@ -163,64 +168,78 @@ return_answer: 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) { - struct acl_options *acl = NULL; /* Is it AXFR? */ switch (q->qtype) { case TYPE_AXFR: if (q->tcp) { - 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, "axfr for %s from %s refused, %s", - dname_to_string(q->qname, NULL), a, acl?"blocked":"no acl matches")); - } - DEBUG(DEBUG_XFRD,1, (LOG_INFO, "axfr refused, %s", - 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; - } + if(!axfr_ixfr_can_admit_query(nsd, q)) return QUERY_PROCESSED; - } - DEBUG(DEBUG_XFRD,1, (LOG_INFO, "axfr admitted acl %s %s", - 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 query_axfr(nsd, q); + return query_axfr(nsd, q, 1); } /* AXFR over UDP queries are discarded. */ RCODE_SET(q->packet, RCODE_IMPL); return QUERY_PROCESSED; case TYPE_IXFR: - /* get rid of authority section, if present */ - NSCOUNT_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); + 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; } - RCODE_SET(q->packet, RCODE_IMPL); - return QUERY_PROCESSED; + return query_ixfr(nsd, q); default: return QUERY_DISCARDED; } diff --git a/usr.sbin/nsd/axfr.h b/usr.sbin/nsd/axfr.h index 33a68629523..105cd5362d5 100644 --- a/usr.sbin/nsd/axfr.h +++ b/usr.sbin/nsd/axfr.h @@ -20,6 +20,6 @@ #define AXFR_MAX_MESSAGE_LEN MAX_COMPRESSION_OFFSET query_state_type answer_axfr_ixfr(struct nsd *nsd, struct query *q); -query_state_type query_axfr(struct nsd *nsd, struct query *query); +query_state_type query_axfr(struct nsd *nsd, struct query *query, int wstats); #endif /* _AXFR_H_ */ diff --git a/usr.sbin/nsd/configlexer.lex b/usr.sbin/nsd/configlexer.lex index a168834067a..57b16053183 100644 --- a/usr.sbin/nsd/configlexer.lex +++ b/usr.sbin/nsd/configlexer.lex @@ -288,6 +288,10 @@ min-refresh-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MIN_REFRESH_TIM max-retry-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MAX_RETRY_TIME;} min-retry-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MIN_RETRY_TIME;} min-expire-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MIN_EXPIRE_TIME;} +store-ixfr{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_STORE_IXFR;} +ixfr-size{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_IXFR_SIZE;} +ixfr-number{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_IXFR_NUMBER;} +create-ixfr{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_CREATE_IXFR;} multi-master-check{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MULTI_MASTER_CHECK;} tls-service-key{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_KEY;} tls-service-ocsp{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_OCSP;} diff --git a/usr.sbin/nsd/configparser.y b/usr.sbin/nsd/configparser.y index 0815a0a16af..297b580545f 100644 --- a/usr.sbin/nsd/configparser.y +++ b/usr.sbin/nsd/configparser.y @@ -181,6 +181,10 @@ static int parse_range(const char *str, long long *low, long long *high); %token VAR_SIZE_LIMIT_XFR %token VAR_ZONESTATS %token VAR_INCLUDE_PATTERN +%token VAR_STORE_IXFR +%token VAR_IXFR_SIZE +%token VAR_IXFR_NUMBER +%token VAR_CREATE_IXFR /* zone */ %token VAR_ZONE @@ -962,7 +966,27 @@ pattern_or_zone_option: } cfg_parser->pattern->min_expire_time = num; cfg_parser->pattern->min_expire_time_expr = expr; - }; + } + | VAR_STORE_IXFR boolean + { + cfg_parser->pattern->store_ixfr = $2; + cfg_parser->pattern->store_ixfr_is_default = 0; + } + | VAR_IXFR_SIZE number + { + cfg_parser->pattern->ixfr_size = $2; + cfg_parser->pattern->ixfr_size_is_default = 0; + } + | VAR_IXFR_NUMBER number + { + cfg_parser->pattern->ixfr_number = $2; + cfg_parser->pattern->ixfr_number_is_default = 0; + } + | VAR_CREATE_IXFR boolean + { + cfg_parser->pattern->create_ixfr = $2; + cfg_parser->pattern->create_ixfr_is_default = 0; + } ; ip_address: STRING diff --git a/usr.sbin/nsd/configure b/usr.sbin/nsd/configure index 0a5938d017b..0e87d157de6 100644 --- a/usr.sbin/nsd/configure +++ b/usr.sbin/nsd/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for NSD 4.4.0. +# Generated by GNU Autoconf 2.69 for NSD 4.5.0. # # Report bugs to . # @@ -580,8 +580,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='NSD' PACKAGE_TARNAME='nsd' -PACKAGE_VERSION='4.4.0' -PACKAGE_STRING='NSD 4.4.0' +PACKAGE_VERSION='4.5.0' +PACKAGE_STRING='NSD 4.5.0' PACKAGE_BUGREPORT='nsd-bugs@nlnetlabs.nl' PACKAGE_URL='' @@ -1328,7 +1328,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures NSD 4.4.0 to adapt to many kinds of systems. +\`configure' configures NSD 4.5.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1390,7 +1390,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of NSD 4.4.0:";; + short | recursive ) echo "Configuration of NSD 4.5.0:";; esac cat <<\_ACEOF @@ -1563,7 +1563,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -NSD configure 4.4.0 +NSD configure 4.5.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2272,7 +2272,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by NSD $as_me 4.4.0, which was +It was created by NSD $as_me 4.5.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -11155,7 +11155,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by NSD $as_me 4.4.0, which was +This file was extended by NSD $as_me 4.5.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -11217,7 +11217,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -NSD config.status 4.4.0 +NSD config.status 4.5.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/usr.sbin/nsd/configure.ac b/usr.sbin/nsd/configure.ac index 979b9ecda56..a85ad347ed6 100644 --- a/usr.sbin/nsd/configure.ac +++ b/usr.sbin/nsd/configure.ac @@ -5,7 +5,7 @@ dnl sinclude(acx_nlnetlabs.m4) sinclude(dnstap/dnstap.m4) -AC_INIT([NSD],[4.4.0],[nsd-bugs@nlnetlabs.nl]) +AC_INIT([NSD],[4.5.0],[nsd-bugs@nlnetlabs.nl]) AC_CONFIG_HEADERS([config.h]) # diff --git a/usr.sbin/nsd/dbaccess.c b/usr.sbin/nsd/dbaccess.c index 4849481f483..2f07c0051af 100644 --- a/usr.sbin/nsd/dbaccess.c +++ b/usr.sbin/nsd/dbaccess.c @@ -30,6 +30,8 @@ #include "nsec3.h" #include "difffile.h" #include "nsd.h" +#include "ixfr.h" +#include "ixfrcreate.h" static time_t udb_time = 0; static unsigned long udb_rrsets = 0; @@ -62,43 +64,11 @@ namedb_close_udb(struct namedb* db) } void -apex_rrset_checks(namedb_type* db, rrset_type* rrset, domain_type* domain) +namedb_free_ixfr(struct namedb* db) { - uint32_t soa_minimum; - unsigned i; - zone_type* zone = rrset->zone; - assert(domain == zone->apex); - (void)domain; - if (rrset_rrtype(rrset) == TYPE_SOA) { - zone->soa_rrset = rrset; - - /* BUG #103 add another soa with a tweaked ttl */ - if(zone->soa_nx_rrset == 0) { - zone->soa_nx_rrset = region_alloc(db->region, - sizeof(rrset_type)); - zone->soa_nx_rrset->rr_count = 1; - zone->soa_nx_rrset->next = 0; - zone->soa_nx_rrset->zone = zone; - zone->soa_nx_rrset->rrs = region_alloc(db->region, - sizeof(rr_type)); - } - memcpy(zone->soa_nx_rrset->rrs, rrset->rrs, sizeof(rr_type)); - - /* check the ttl and MINIMUM value and set accordingly */ - memcpy(&soa_minimum, rdata_atom_data(rrset->rrs->rdatas[6]), - rdata_atom_size(rrset->rrs->rdatas[6])); - if (rrset->rrs->ttl > ntohl(soa_minimum)) { - zone->soa_nx_rrset->rrs[0].ttl = ntohl(soa_minimum); - } - } else if (rrset_rrtype(rrset) == TYPE_NS) { - zone->ns_rrset = rrset; - } else if (rrset_rrtype(rrset) == TYPE_RRSIG) { - for (i = 0; i < rrset->rr_count; ++i) { - if(rr_rrsig_type_covered(&rrset->rrs[i])==TYPE_DNSKEY){ - zone->is_secure = 1; - break; - } - } + struct radnode* n; + for(n=radix_first(db->zonetree); n; n=radix_next(n)) { + zone_ixfr_free(((zone_type*)n->elem)->ixfr); } } @@ -269,6 +239,7 @@ namedb_zone_create(namedb_type* db, const dname_type* dname, zone->dshashtree = NULL; #endif zone->opts = zo; + zone->ixfr = NULL; zone->filename = NULL; zone->logstr = NULL; zone->mtime.tv_sec = 0; @@ -309,6 +280,7 @@ namedb_zone_delete(namedb_type* db, zone_type* zone) hash_tree_delete(db->region, zone->wchashtree); hash_tree_delete(db->region, zone->dshashtree); #endif + zone_ixfr_free(zone->ixfr); if(zone->filename) region_recycle(db->region, zone->filename, strlen(zone->filename)+1); @@ -522,6 +494,8 @@ namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb, int nonexist = 0; unsigned int errors; const char* fname; + struct ixfr_create* ixfrcr = NULL; + int ixfr_create_already_done = 0; if(!nsd->db || !zone || !zone->opts || !zone->opts->pattern->zonefile) return; mtime.tv_sec = 0; @@ -576,6 +550,15 @@ namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb, return; } } + if(ixfr_create_from_difference(zone, fname, + &ixfr_create_already_done)) { + ixfrcr = ixfr_create_start(zone, fname, + zone->opts->pattern->ixfr_size, 0); + if(!ixfrcr) { + /* leaves the ixfrcr at NULL, so it is not created */ + log_msg(LOG_ERR, "out of memory starting ixfr create"); + } + } assert(parser); /* wipe zone from memory */ @@ -603,6 +586,7 @@ namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb, zone->apex)), domain_dname(zone->apex)->name_size)) { /* tell that zone contents has been lost */ if(taskudb) task_new_soainfo(taskudb, last_task, zone, 0); + ixfr_create_cancel(ixfrcr); return; } /* read from udb */ @@ -648,6 +632,20 @@ namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb, strlen(zone->logstr)+1); zone->logstr = NULL; } + if(ixfr_create_already_done) { + ixfr_readup_exist(zone, nsd, fname); + } else if(ixfrcr) { + if(!ixfr_create_perform(ixfrcr, zone, 1, nsd, fname, + zone->opts->pattern->ixfr_number)) { + log_msg(LOG_ERR, "failed to create IXFR"); + } else { + VERBOSITY(2, (LOG_INFO, "zone %s created IXFR %s.ixfr", + zone->opts->name, fname)); + } + ixfr_create_free(ixfrcr); + } else if(zone_is_ixfr_enabled(zone)) { + ixfr_read_from_file(nsd, zone, fname); + } } if(taskudb) task_new_soainfo(taskudb, last_task, zone, 0); #ifdef NSEC3 diff --git a/usr.sbin/nsd/dbcreate.c b/usr.sbin/nsd/dbcreate.c index 13d4e9d444c..31b6b0c9b94 100644 --- a/usr.sbin/nsd/dbcreate.c +++ b/usr.sbin/nsd/dbcreate.c @@ -23,6 +23,7 @@ #include "udbzone.h" #include "options.h" #include "nsd.h" +#include "ixfr.h" /* pathname directory separator character */ #define PATHSEP '/' @@ -138,7 +139,7 @@ write_zone(udb_base* udb, udb_ptr* z, zone_type* zone) if(++c % ZONEC_PCT_COUNT == 0 && time(NULL) > t + ZONEC_PCT_TIME) { t = time(NULL); VERBOSITY(1, (LOG_INFO, "write %s %d %%", - zone->opts->name, (int)(c*((unsigned long)100)/n))); + zone->opts->name, (n==0)?0:(int)(c*((unsigned long)100)/n))); } } return 1; @@ -409,6 +410,8 @@ namedb_write_zonefile(struct nsd* nsd, struct zone_options* zopt) strlen(zone->logstr)+1); zone->logstr = NULL; } + if(zone_is_ixfr_enabled(zone) && zone->ixfr) + ixfr_write_to_file(zone, zfile); } } diff --git a/usr.sbin/nsd/difffile.c b/usr.sbin/nsd/difffile.c index 5a7926452c2..aa8271fa4b4 100644 --- a/usr.sbin/nsd/difffile.c +++ b/usr.sbin/nsd/difffile.c @@ -23,6 +23,8 @@ #include "nsec3.h" #include "nsd.h" #include "rrl.h" +#include "ixfr.h" +#include "zonec.h" static int write_64(FILE *out, uint64_t val) @@ -975,7 +977,7 @@ apply_ixfr(namedb_type* db, FILE *in, const char* zone, uint32_t serialno, struct nsd_options* opt, uint32_t seq_nr, uint32_t seq_total, int* is_axfr, int* delete_mode, int* rr_count, udb_ptr* udbz, struct zone** zone_res, const char* patname, int* bytes, - int* softfail) + int* softfail, struct ixfr_store* ixfr_store) { uint32_t msglen, checklen, pkttype; int qcount, ancount, counter; @@ -1067,6 +1069,7 @@ apply_ixfr(namedb_type* db, FILE *in, const char* zone, uint32_t serialno, dname_to_string(dname_zone, 0))); /* first RR: check if SOA and correct zone & serialno */ if(*rr_count == 0) { + size_t ttlpos; DEBUG(DEBUG_XFRD,2, (LOG_INFO, "diff: %s parse first RR", dname_to_string(dname_zone, 0))); dname = dname_make_from_packet(region, packet, 1, 1); @@ -1094,6 +1097,7 @@ apply_ixfr(namedb_type* db, FILE *in, const char* zone, uint32_t serialno, region_destroy(region); return 0; } + ttlpos = buffer_position(packet); buffer_skip(packet, sizeof(uint32_t)); /* ttl */ if(!buffer_available(packet, buffer_read_u16(packet)) || !packet_skip_dname(packet) /* skip prim_ns */ || @@ -1114,6 +1118,8 @@ apply_ixfr(namedb_type* db, FILE *in, const char* zone, uint32_t serialno, *rr_count = 1; *is_axfr = 0; *delete_mode = 0; + if(ixfr_store) + ixfr_store_add_newsoa(ixfr_store, packet, ttlpos); DEBUG(DEBUG_XFRD,2, (LOG_INFO, "diff: %s start count %d, ax %d, delmode %d", dname_to_string(dname_zone, 0), *rr_count, *is_axfr, *delete_mode)); } @@ -1159,6 +1165,10 @@ apply_ixfr(namedb_type* db, FILE *in, const char* zone, uint32_t serialno, /* add everything else (incl end SOA) */ *delete_mode = 0; *is_axfr = 1; + if(ixfr_store) { + ixfr_store_cancel(ixfr_store); + ixfr_store_delixfrs(zone_db); + } DEBUG(DEBUG_XFRD,2, (LOG_INFO, "diff: %s sawAXFR count %d, ax %d, delmode %d", dname_to_string(dname_zone, 0), *rr_count, *is_axfr, *delete_mode)); } @@ -1186,6 +1196,8 @@ apply_ixfr(namedb_type* db, FILE *in, const char* zone, uint32_t serialno, udb_zone_clear(db->udb, udbz); *delete_mode = 0; *is_axfr = 1; + if(ixfr_store) + ixfr_store_cancel(ixfr_store); } /* must have stuff in memory for a successful IXFR, * the serial number of the SOA has been checked @@ -1199,6 +1211,9 @@ apply_ixfr(namedb_type* db, FILE *in, const char* zone, uint32_t serialno, return 2; } buffer_set_position(packet, bufpos); + if(!*is_axfr && ixfr_store) + ixfr_store_add_oldsoa(ixfr_store, ttl, packet, + rrlen); } if(type == TYPE_SOA && !*is_axfr) { /* switch from delete-part to add-part and back again, @@ -1223,6 +1238,9 @@ apply_ixfr(namedb_type* db, FILE *in, const char* zone, uint32_t serialno, && seq_nr == seq_total-1) { continue; /* do not delete final SOA RR for IXFR */ } + if(ixfr_store) + ixfr_store_delrr(ixfr_store, dname, type, + klass, ttl, packet, rrlen, region); if(!delete_RR(db, dname, type, klass, packet, rrlen, zone_db, region, udbz, softfail)) { region_destroy(region); @@ -1232,6 +1250,9 @@ apply_ixfr(namedb_type* db, FILE *in, const char* zone, uint32_t serialno, else { /* add this rr */ + if(ixfr_store) + ixfr_store_addrr(ixfr_store, dname, type, + klass, ttl, packet, rrlen, region); if(!add_RR(db, dname, type, klass, ttl, packet, rrlen, zone_db, udbz, softfail)) { region_destroy(region); @@ -1338,8 +1359,12 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zonedb, FILE* in, int is_axfr=0, delete_mode=0, rr_count=0, softfail=0; const dname_type* apex = domain_dname_const(zonedb->apex); udb_ptr z; + struct ixfr_store* ixfr_store = NULL, ixfr_store_mem; DEBUG(DEBUG_XFRD,1, (LOG_INFO, "processing xfr: %s", zone_buf)); + if(zone_is_ixfr_enabled(zonedb)) + ixfr_store = ixfr_store_start(zonedb, &ixfr_store_mem, + old_serial, new_serial); memset(&z, 0, sizeof(z)); /* if udb==NULL, have &z defined */ if(nsd->db->udb) { if(udb_base_get_userflags(nsd->db->udb) != 0) { @@ -1356,6 +1381,7 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zonedb, FILE* in, /* out of disk space perhaps */ log_msg(LOG_ERR, "could not udb_create_zone " "%s, disk space full?", zone_buf); + ixfr_store_free(ixfr_store); return 0; } } @@ -1369,7 +1395,7 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zonedb, FILE* in, ret = apply_ixfr(nsd->db, in, zone_buf, new_serial, opt, i, num_parts, &is_axfr, &delete_mode, &rr_count, (nsd->db->udb?&z:NULL), &zonedb, - patname_buf, &num_bytes, &softfail); + patname_buf, &num_bytes, &softfail, ixfr_store); assert(zonedb); if(ret == 0) { log_msg(LOG_ERR, "bad ixfr packet part %d in diff file for %s", (int)i, zone_buf); @@ -1423,6 +1449,8 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zonedb, FILE* in, if(taskudb) task_new_soainfo(taskudb, last_task, zonedb, 0); } + if(ixfr_store) + ixfr_store_finish(ixfr_store, nsd, log_buf); if(1 <= verbosity) { double elapsed = (double)(time_end_0 - time_start_0)+ diff --git a/usr.sbin/nsd/doc/ChangeLog b/usr.sbin/nsd/doc/ChangeLog index 24b6e09c6c1..1d6034b9c69 100644 --- a/usr.sbin/nsd/doc/ChangeLog +++ b/usr.sbin/nsd/doc/ChangeLog @@ -1,5 +1,41 @@ +6 May 2022: Wouter + - Merge PR #209: IXFR out + This adds IXFR out functionality to NSD. NSD can copy IXFRs from + upstream to downstream clients, or create IXFRs from zonefiles. + The options store-ixfr: yes and create-ixfr: yes can be used to + turn this on. Default is turned off. The options ixfr-number and + ixfr-size can be used to tune the number of IXFR transfers and + total data size stored. This is configured per zone, the IXFRs + are served to the hosts that are allowed to perform zone transfers. + And if TSIG is configured, signed with the same key. The content + is stored to file if a zonefile is configured for the zone, in + the zonefile.ixfr and zonefile.ixfr.2, .. files. They contain + readable text format. The number of IXFRs is num.rixfr in + statistics output, also per zone if per zone statistics are enabled. + If offline, nsd-checkzone -i can create ixfr files. + NSD already supports requesting IXFRs, this addition allows NSD + to serve IXFR transfers to clients. + NSD stops responding with NOTIMPL to IXFR requests, also for zones + that do not have IXFR enabled. The clients gets a full zone reply + or a status reply if the serial is up to date. + - set version to 4.5.0 for feature change. + - Tag for 4.5.0rc1 release. It became the 4.5.0 release on 13 May 2022. + +14 April 2022: Wouter + - Update cirrus script FreeBSD version. + +25 March 2022: Wouter + - Fix spelling error in comment in svcbparam_lookup_key. + +2 March 2022: Wouter + - Fix code analyzer zero divide warning. + - Fix code analyzer large value with assertion. + - Fix another code analyzer zero divide warning. + - Fix code analyzer warning about uninitialized temp storage in loop. + 10 February 2022: Wouter - - Tag for 4.4.0rc1 release. + - Tag for 4.4.0rc1 release. This became 4.4.0 release on 17 Feb 2022, + the code repository continues with version 4.4.1. 9 February 2022: Wouter - Fix unit tests for nds-control-setup exit code and the diff --git a/usr.sbin/nsd/doc/RELNOTES b/usr.sbin/nsd/doc/RELNOTES index aa649184046..412cb52d2ce 100644 --- a/usr.sbin/nsd/doc/RELNOTES +++ b/usr.sbin/nsd/doc/RELNOTES @@ -1,5 +1,35 @@ NSD RELEASE NOTES +4.5.0 +================ +FEATURES: + - Merge PR #209: IXFR out + This adds IXFR out functionality to NSD. NSD can copy IXFRs from + upstream to downstream clients, or create IXFRs from zonefiles. + The options store-ixfr: yes and create-ixfr: yes can be used to + turn this on. Default is turned off. The options ixfr-number and + ixfr-size can be used to tune the number of IXFR transfers and + total data size stored. This is configured per zone, the IXFRs + are served to the hosts that are allowed to perform zone transfers. + And if TSIG is configured, signed with the same key. The content + is stored to file if a zonefile is configured for the zone, in + the zonefile.ixfr and zonefile.ixfr.2, .. files. They contain + readable text format. The number of IXFRs is num.rixfr in + statistics output, also per zone if per zone statistics are enabled. + If offline, nsd-checkzone -i can create ixfr files. + NSD already supports requesting IXFRs, this addition allows NSD + to serve IXFR transfers to clients. + NSD stops responding with NOTIMPL to IXFR requests, also for zones + that do not have IXFR enabled. The clients gets a full zone reply + or a status reply if the serial is up to date. +BUG FIXES: + - Fix code analyzer zero divide warning. + - Fix code analyzer large value with assertion. + - Fix another code analyzer zero divide warning. + - Fix code analyzer warning about uninitialized temp storage in loop. + - Fix spelling error in comment in svcbparam_lookup_key. + - Update cirrus script FreeBSD version. + 4.4.0 ================ FEATURES: diff --git a/usr.sbin/nsd/ipc.c b/usr.sbin/nsd/ipc.c index 87478f7e1d6..daa1d6adec5 100644 --- a/usr.sbin/nsd/ipc.c +++ b/usr.sbin/nsd/ipc.c @@ -284,6 +284,7 @@ stats_add(struct nsdst* total, struct nsdst* s) total->ednserr += s->ednserr; total->raxfr += s->raxfr; total->nona += s->nona; + total->rixfr += s->rixfr; total->db_disk = s->db_disk; total->db_mem = s->db_mem; @@ -317,6 +318,7 @@ stats_subtract(struct nsdst* total, struct nsdst* s) total->ednserr -= s->ednserr; total->raxfr -= s->raxfr; total->nona -= s->nona; + total->rixfr -= s->rixfr; } #define FINAL_STATS_TIMEOUT 10 /* seconds */ diff --git a/usr.sbin/nsd/ixfr.c b/usr.sbin/nsd/ixfr.c new file mode 100644 index 00000000000..b054122b848 --- /dev/null +++ b/usr.sbin/nsd/ixfr.c @@ -0,0 +1,2662 @@ +/* + * ixfr.c -- generating IXFR responses. + * + * Copyright (c) 2021, NLnet Labs. All rights reserved. + * + * See LICENSE for the license. + * + */ + +#include "config.h" + +#include +#include +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#include + +#include "ixfr.h" +#include "packet.h" +#include "rdata.h" +#include "axfr.h" +#include "options.h" +#include "zonec.h" + +/* + * For optimal compression IXFR response packets are limited in size + * to MAX_COMPRESSION_OFFSET. + */ +#define IXFR_MAX_MESSAGE_LEN MAX_COMPRESSION_OFFSET + +/* draft-ietf-dnsop-rfc2845bis-06, section 5.3.1 says to sign every packet */ +#define IXFR_TSIG_SIGN_EVERY_NTH 0 /* tsig sign every N packets. */ + +/* initial space in rrs data for storing records */ +#define IXFR_STORE_INITIAL_SIZE 4096 + +/* store compression for one name */ +struct rrcompress_entry { + /* rbtree node, key is this struct */ + struct rbnode node; + /* the uncompressed domain name */ + const uint8_t* dname; + /* the length of the dname, includes terminating 0 label */ + uint16_t len; + /* the offset of the dname in the packet */ + uint16_t offset; +}; + +/* structure to store compression data for the packet */ +struct pktcompression { + /* rbtree of rrcompress_entry. sorted by dname */ + struct rbtree tree; + /* allocation information, how many bytes allocated now */ + size_t alloc_now; + /* allocation information, total size in block */ + size_t alloc_max; + /* region to use if block full, this is NULL if unused */ + struct region* region; + /* block of temp data for allocation */ + uint8_t block[sizeof(struct rrcompress_entry)*1024]; +}; + +/* compare two elements in the compression tree. Returns -1, 0, or 1. */ +static int compression_cmp(const void* a, const void* b) +{ + struct rrcompress_entry* rra = (struct rrcompress_entry*)a; + struct rrcompress_entry* rrb = (struct rrcompress_entry*)b; + if(rra->len != rrb->len) { + if(rra->len < rrb->len) + return -1; + return 1; + } + return memcmp(rra->dname, rrb->dname, rra->len); +} + +/* init the pktcompression to a new packet */ +static void pktcompression_init(struct pktcompression* pcomp) +{ + pcomp->alloc_now = 0; + pcomp->alloc_max = sizeof(pcomp->block); + pcomp->region = NULL; + pcomp->tree.root = RBTREE_NULL; + pcomp->tree.count = 0; + pcomp->tree.region = NULL; + pcomp->tree.cmp = &compression_cmp; +} + +/* freeup the pktcompression data */ +static void pktcompression_freeup(struct pktcompression* pcomp) +{ + if(pcomp->region) { + region_destroy(pcomp->region); + pcomp->region = NULL; + } + pcomp->alloc_now = 0; + pcomp->tree.root = RBTREE_NULL; + pcomp->tree.count = 0; +} + +/* alloc data in pktcompression */ +static void* pktcompression_alloc(struct pktcompression* pcomp, size_t s) +{ + /* first attempt to allocate in the fixed block, + * that is very fast and on the stack in the pcomp struct */ + if(pcomp->alloc_now + s <= pcomp->alloc_max) { + void* ret = pcomp->block + pcomp->alloc_now; + pcomp->alloc_now += s; + return ret; + } + + /* if that fails, create a region to allocate in, + * it is freed in the freeup */ + if(!pcomp->region) { + pcomp->region = region_create(xalloc, free); + if(!pcomp->region) + return NULL; + } + return region_alloc(pcomp->region, s); +} + +/* find a pktcompression name, return offset if found */ +static uint16_t pktcompression_find(struct pktcompression* pcomp, + const uint8_t* dname, size_t len) +{ + struct rrcompress_entry key, *found; + key.node.key = &key; + key.dname = dname; + key.len = len; + found = (struct rrcompress_entry*)rbtree_search(&pcomp->tree, &key); + if(found) return found->offset; + return 0; +} + +/* insert a new domain name into the compression tree. + * it fails silently, no need to compress then. */ +static void pktcompression_insert(struct pktcompression* pcomp, + const uint8_t* dname, size_t len, uint16_t offset) +{ + struct rrcompress_entry* entry; + if(len > 65535) + return; + if(offset > MAX_COMPRESSION_OFFSET) + return; /* too far for a compression pointer */ + entry = pktcompression_alloc(pcomp, sizeof(*entry)); + if(!entry) + return; + memset(&entry->node, 0, sizeof(entry->node)); + entry->node.key = entry; + entry->dname = dname; + entry->len = len; + entry->offset = offset; + (void)rbtree_insert(&pcomp->tree, &entry->node); +} + +/* insert all the labels of a domain name */ +static void pktcompression_insert_with_labels(struct pktcompression* pcomp, + uint8_t* dname, size_t len, uint16_t offset) +{ + if(!dname) + return; + if(offset > MAX_COMPRESSION_OFFSET) + return; + + /* while we have not seen the end root label */ + while(len > 0 && dname[0] != 0) { + size_t lablen; + pktcompression_insert(pcomp, dname, len, offset); + lablen = (size_t)(dname[0]); + if( (lablen&0xc0) ) + return; /* the dname should be uncompressed */ + if(lablen+1 > len) + return; /* len should be uncompressed wireformat len */ + if(offset > MAX_COMPRESSION_OFFSET - lablen - 1) + return; /* offset moves too far for compression */ + /* skip label */ + len -= lablen+1; + dname += lablen+1; + offset += lablen+1; + } +} + +/* calculate length of dname in uncompressed wireformat in buffer */ +static size_t dname_length(const uint8_t* buf, size_t len) +{ + size_t l = 0; + if(!buf || len == 0) + return l; + while(len > 0 && buf[0] != 0) { + size_t lablen = (size_t)(buf[0]); + if( (lablen&0xc0) ) + return 0; /* the name should be uncompressed */ + if(lablen+1 > len) + return 0; /* should fit in the buffer */ + l += lablen+1; + len -= lablen+1; + buf += lablen+1; + } + if(len == 0) + return 0; /* end label should fit in buffer */ + if(buf[0] != 0) + return 0; /* must end in root label */ + l += 1; /* for the end root label */ + return l; +} + +/* write a compressed domain name into the packet, + * returns uncompressed wireformat length, + * 0 if it does not fit and -1 on failure, bad dname. */ +static int pktcompression_write_dname(struct buffer* packet, + struct pktcompression* pcomp, const uint8_t* rr, size_t rrlen) +{ + size_t wirelen = 0; + size_t dname_len = dname_length(rr, rrlen); + if(!rr || rrlen == 0 || dname_len == 0) + return 0; + while(rrlen > 0 && rr[0] != 0) { + size_t lablen = (size_t)(rr[0]); + uint16_t offset; + if( (lablen&0xc0) ) + return -1; /* name should be uncompressed */ + if(lablen+1 > rrlen) + return -1; /* name should fit */ + + /* see if the domain name has a compression pointer */ + if((offset=pktcompression_find(pcomp, rr, dname_len))!=0) { + if(!buffer_available(packet, 2)) + return 0; + buffer_write_u16(packet, (uint16_t)(0xc000 | offset)); + wirelen += dname_len; + return wirelen; + } else { + if(!buffer_available(packet, lablen+1)) + return 0; + /* insert the domain name at this position */ + pktcompression_insert(pcomp, rr, dname_len, + buffer_position(packet)); + /* write it */ + buffer_write(packet, rr, lablen+1); + } + + wirelen += lablen+1; + rr += lablen+1; + rrlen -= lablen+1; + dname_len -= lablen+1; + } + if(rrlen > 0 && rr[0] == 0) { + /* write end root label */ + if(!buffer_available(packet, 1)) + return 0; + buffer_write_u8(packet, 0); + wirelen += 1; + } + return wirelen; +} + +/* write an RR into the packet with compression for domain names, + * return 0 and resets position if it does not fit in the packet. */ +static int ixfr_write_rr_pkt(struct query* query, struct buffer* packet, + struct pktcompression* pcomp, const uint8_t* rr, size_t rrlen) +{ + size_t oldpos = buffer_position(packet); + size_t rdpos; + uint16_t tp; + int dname_len; + size_t rdlen; + size_t i; + rrtype_descriptor_type* descriptor; + + if(buffer_position(packet) > MAX_COMPRESSION_OFFSET + || query_overflow(query)) { + /* we are past the maximum length */ + return 0; + } + + /* write owner */ + dname_len = pktcompression_write_dname(packet, pcomp, rr, rrlen); + if(dname_len == -1) + return 1; /* attempt to skip this malformed rr, could assert */ + if(dname_len == 0) { + buffer_set_position(packet, oldpos); + return 0; + } + rr += dname_len; + rrlen -= dname_len; + + /* type, class, ttl, rdatalen */ + if(!buffer_available(packet, 10)) { + buffer_set_position(packet, oldpos); + return 0; + } + if(10 > rrlen) + return 1; /* attempt to skip this malformed rr, could assert */ + tp = read_uint16(rr); + buffer_write(packet, rr, 8); + rr += 8; + rrlen -= 8; + rdlen = read_uint16(rr); + rr += 2; + rrlen -= 2; + rdpos = buffer_position(packet); + buffer_write_u16(packet, 0); + if(rdlen > rrlen) + return 1; /* attempt to skip this malformed rr, could assert */ + + /* rdata */ + descriptor = rrtype_descriptor_by_type(tp); + for(i=0; imaximum; i++) { + size_t copy_len = 0; + if(rdlen == 0) + break; + + switch(rdata_atom_wireformat_type(tp, i)) { + case RDATA_WF_COMPRESSED_DNAME: + dname_len = pktcompression_write_dname(packet, pcomp, + rr, rdlen); + if(dname_len == -1) + return 1; /* attempt to skip malformed rr */ + if(dname_len == 0) { + buffer_set_position(packet, oldpos); + return 0; + } + rr += dname_len; + rdlen -= dname_len; + break; + case RDATA_WF_UNCOMPRESSED_DNAME: + case RDATA_WF_LITERAL_DNAME: + copy_len = rdlen; + break; + case RDATA_WF_BYTE: + copy_len = 1; + break; + case RDATA_WF_SHORT: + copy_len = 2; + break; + case RDATA_WF_LONG: + copy_len = 4; + break; + case RDATA_WF_TEXTS: + case RDATA_WF_LONG_TEXT: + copy_len = rdlen; + break; + case RDATA_WF_TEXT: + case RDATA_WF_BINARYWITHLENGTH: + copy_len = 1; + if(rdlen > copy_len) + copy_len += rr[0]; + break; + case RDATA_WF_A: + copy_len = 4; + break; + case RDATA_WF_AAAA: + copy_len = 16; + break; + case RDATA_WF_ILNP64: + copy_len = 8; + break; + case RDATA_WF_EUI48: + copy_len = EUI48ADDRLEN; + break; + case RDATA_WF_EUI64: + copy_len = EUI64ADDRLEN; + break; + case RDATA_WF_BINARY: + copy_len = rdlen; + break; + case RDATA_WF_APL: + copy_len = (sizeof(uint16_t) /* address family */ + + sizeof(uint8_t) /* prefix */ + + sizeof(uint8_t)); /* length */ + if(copy_len <= rdlen) + copy_len += (rr[copy_len-1]&APL_LENGTH_MASK); + break; + case RDATA_WF_IPSECGATEWAY: + copy_len = rdlen; + break; + case RDATA_WF_SVCPARAM: + copy_len = 4; + if(copy_len <= rdlen) + copy_len += read_uint16(rr+2); + break; + default: + copy_len = rdlen; + break; + } + if(copy_len) { + if(!buffer_available(packet, copy_len)) { + buffer_set_position(packet, oldpos); + return 0; + } + if(copy_len > rdlen) + return 1; /* assert of skip malformed */ + buffer_write(packet, rr, copy_len); + rr += copy_len; + rdlen -= copy_len; + } + } + /* write compressed rdata length */ + buffer_write_u16_at(packet, rdpos, buffer_position(packet)-rdpos-2); + if(query_overflow(query)) { + /* we are past the maximum length */ + buffer_set_position(packet, oldpos); + return 0; + } + return 1; +} + +/* parse the serial number from the IXFR query */ +static int parse_qserial(struct buffer* packet, uint32_t* qserial, + size_t* snip_pos) +{ + unsigned int i; + uint16_t type, rdlen; + /* we must have a SOA in the authority section */ + if(NSCOUNT(packet) == 0) + return 0; + /* skip over the question section, we want only one */ + buffer_set_position(packet, QHEADERSZ); + if(QDCOUNT(packet) != 1) + return 0; + if(!packet_skip_rr(packet, 1)) + return 0; + /* set position to snip off the authority section */ + *snip_pos = buffer_position(packet); + /* skip over the authority section RRs until we find the SOA */ + for(i=0; irdata_count < 3) + return 0; + if(rr->rdatas[2].data[0] < 4) + return 0; + return read_uint32(&rr->rdatas[2].data[1]); +} + +/* get the current serial from the zone */ +uint32_t zone_get_current_serial(struct zone* zone) +{ + if(!zone || !zone->soa_rrset) + return 0; + if(zone->soa_rrset->rr_count == 0) + return 0; + if(zone->soa_rrset->rrs[0].rdata_count < 3) + return 0; + if(zone->soa_rrset->rrs[0].rdatas[2].data[0] < 4) + return 0; + return read_uint32(&zone->soa_rrset->rrs[0].rdatas[2].data[1]); +} + +/* iterator over ixfr data. find first element, eg. oldest zone version + * change. + * The iterator can be started with the ixfr_data_first, but also with + * ixfr_data_last, or with an existing ixfr_data element to start from. + * Continue by using ixfr_data_next or ixfr_data_prev to ask for more elements + * until that returns NULL. NULL because end of list or loop was detected. + * The ixfr_data_prev uses a counter, start it at 0, it returns NULL when + * a loop is detected. + */ +static struct ixfr_data* ixfr_data_first(struct zone_ixfr* ixfr) +{ + struct ixfr_data* n; + if(!ixfr || !ixfr->data || ixfr->data->count==0) + return NULL; + n = (struct ixfr_data*)rbtree_search(ixfr->data, &ixfr->oldest_serial); + if(!n || n == (struct ixfr_data*)RBTREE_NULL) + return NULL; + return n; +} + +/* iterator over ixfr data. find last element, eg. newest zone version + * change. */ +static struct ixfr_data* ixfr_data_last(struct zone_ixfr* ixfr) +{ + struct ixfr_data* n; + if(!ixfr || !ixfr->data || ixfr->data->count==0) + return NULL; + n = (struct ixfr_data*)rbtree_search(ixfr->data, &ixfr->newest_serial); + if(!n || n == (struct ixfr_data*)RBTREE_NULL) + return NULL; + return n; +} + +/* iterator over ixfr data. fetch next item. If loop or nothing, NULL */ +static struct ixfr_data* ixfr_data_next(struct zone_ixfr* ixfr, + struct ixfr_data* cur) +{ + struct ixfr_data* n; + if(!cur || cur == (struct ixfr_data*)RBTREE_NULL) + return NULL; + if(cur->oldserial == ixfr->newest_serial) + return NULL; /* that was the last element */ + n = (struct ixfr_data*)rbtree_next(&cur->node); + if(n && n != (struct ixfr_data*)RBTREE_NULL && + cur->newserial == n->oldserial) { + /* the next rbtree item is the next ixfr data item */ + return n; + } + /* If the next item is last of tree, and we have to loop around, + * the search performs the lookup for the next item we need. + * If the next item exists, but also is not connected, the search + * finds the correct connected ixfr in the sorted tree. */ + /* try searching for the correct ixfr data item */ + n = (struct ixfr_data*)rbtree_search(ixfr->data, &cur->newserial); + if(!n || n == (struct ixfr_data*)RBTREE_NULL) + return NULL; + return n; +} + +/* iterator over ixfr data. fetch the previous item. If loop or nothing NULL.*/ +static struct ixfr_data* ixfr_data_prev(struct zone_ixfr* ixfr, + struct ixfr_data* cur, size_t* prevcount) +{ + struct ixfr_data* prev; + if(!cur || cur == (struct ixfr_data*)RBTREE_NULL) + return NULL; + if(cur->oldserial == ixfr->oldest_serial) + return NULL; /* this was the first element */ + prev = (struct ixfr_data*)rbtree_previous(&cur->node); + if(!prev || prev == (struct ixfr_data*)RBTREE_NULL) { + /* We hit the first element in the tree, go again + * at the last one. Wrap around. */ + prev = (struct ixfr_data*)rbtree_last(ixfr->data); + } + while(prev && prev != (struct ixfr_data*)RBTREE_NULL) { + if(prev->newserial == cur->oldserial) { + /* This is the correct matching previous ixfr data */ + /* Increase the prevcounter every time the routine + * returns an item, and if that becomes too large, we + * are in a loop. in that case, stop. */ + if(prevcount) { + (*prevcount)++; + if(*prevcount > ixfr->data->count + 12) { + /* Larger than the max number of items + * plus a small margin. The longest + * chain is all the ixfr elements in + * the tree. It loops. */ + return NULL; + } + } + return prev; + } + prev = (struct ixfr_data*)rbtree_previous(&prev->node); + if(!prev || prev == (struct ixfr_data*)RBTREE_NULL) { + /* We hit the first element in the tree, go again + * at the last one. Wrap around. */ + prev = (struct ixfr_data*)rbtree_last(ixfr->data); + } + } + /* no elements in list */ + return NULL; +} + +/* connect IXFRs, return true if connected, false if not. Return last serial */ +static int connect_ixfrs(struct zone_ixfr* ixfr, struct ixfr_data* data, + uint32_t* end_serial) +{ + struct ixfr_data* p = data; + while(p != NULL) { + struct ixfr_data* next = ixfr_data_next(ixfr, p); + if(next) { + if(p->newserial != next->oldserial) { + /* These ixfrs are not connected, + * during IXFR processing that could already + * have been deleted, but we check here + * in any case */ + return 0; + } + } else { + /* the chain of IXFRs ends in this serial number */ + *end_serial = p->newserial; + } + p = next; + } + return 1; +} + +/* Count length of next record in data */ +static size_t count_rr_length(const uint8_t* data, size_t data_len, + size_t current) +{ + uint8_t label_size; + uint16_t rdlen; + size_t i = current; + if(current >= data_len) + return 0; + /* pass the owner dname */ + while(1) { + if(i+1 > data_len) + return 0; + label_size = data[i++]; + if(label_size == 0) { + break; + } else if((label_size &0xc0) != 0) { + return 0; /* uncompressed dnames in IXFR store */ + } else if(i+label_size > data_len) { + return 0; + } else { + i += label_size; + } + } + /* after dname, we pass type, class, ttl, rdatalen */ + if(i+10 > data_len) + return 0; + i += 8; + rdlen = read_uint16(data+i); + i += 2; + /* pass over the rdata */ + if(i+((size_t)rdlen) > data_len) + return 0; + i += ((size_t)rdlen); + return i-current; +} + +/* Copy RRs into packet until packet full, return number RRs added */ +static uint16_t ixfr_copy_rrs_into_packet(struct query* query, + struct pktcompression* pcomp) +{ + uint16_t total_added = 0; + + /* Copy RRs into the packet until the answer is full, + * when an RR does not fit, we return and add no more. */ + + /* Add first SOA */ + if(query->ixfr_count_newsoa < query->ixfr_end_data->newsoa_len) { + /* the new SOA is added from the end_data segment, it is + * the final SOA of the result of the IXFR */ + if(ixfr_write_rr_pkt(query, query->packet, pcomp, + query->ixfr_end_data->newsoa, + query->ixfr_end_data->newsoa_len)) { + query->ixfr_count_newsoa = query->ixfr_end_data->newsoa_len; + total_added++; + query->ixfr_pos_of_newsoa = buffer_position(query->packet); + } else { + /* cannot add another RR, so return */ + return total_added; + } + } + + /* Add second SOA */ + if(query->ixfr_count_oldsoa < query->ixfr_data->oldsoa_len) { + if(ixfr_write_rr_pkt(query, query->packet, pcomp, + query->ixfr_data->oldsoa, + query->ixfr_data->oldsoa_len)) { + query->ixfr_count_oldsoa = query->ixfr_data->oldsoa_len; + total_added++; + } else { + /* cannot add another RR, so return */ + return total_added; + } + } + + /* Add del data, with deleted RRs and a SOA */ + while(query->ixfr_count_del < query->ixfr_data->del_len) { + size_t rrlen = count_rr_length(query->ixfr_data->del, + query->ixfr_data->del_len, query->ixfr_count_del); + if(rrlen && ixfr_write_rr_pkt(query, query->packet, pcomp, + query->ixfr_data->del + query->ixfr_count_del, + rrlen)) { + query->ixfr_count_del += rrlen; + total_added++; + } else { + /* the next record does not fit in the remaining + * space of the packet */ + return total_added; + } + } + + /* Add add data, with added RRs and a SOA */ + while(query->ixfr_count_add < query->ixfr_data->add_len) { + size_t rrlen = count_rr_length(query->ixfr_data->add, + query->ixfr_data->add_len, query->ixfr_count_add); + if(rrlen && ixfr_write_rr_pkt(query, query->packet, pcomp, + query->ixfr_data->add + query->ixfr_count_add, + rrlen)) { + query->ixfr_count_add += rrlen; + total_added++; + } else { + /* the next record does not fit in the remaining + * space of the packet */ + return total_added; + } + } + return total_added; +} + +query_state_type query_ixfr(struct nsd *nsd, struct query *query) +{ + uint16_t total_added = 0; + struct pktcompression pcomp; + + if (query->ixfr_is_done) + return QUERY_PROCESSED; + + pktcompression_init(&pcomp); + if (query->maxlen > IXFR_MAX_MESSAGE_LEN) + query->maxlen = IXFR_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->ixfr_data == NULL) { + /* This is the first packet, process the query further */ + uint32_t qserial = 0, current_serial = 0, end_serial = 0; + struct zone* zone; + struct ixfr_data* ixfr_data; + size_t oldpos; + + STATUP(nsd, rixfr); + /* parse the serial number from the IXFR request */ + oldpos = QHEADERSZ; + if(!parse_qserial(query->packet, &qserial, &oldpos)) { + NSCOUNT_SET(query->packet, 0); + ARCOUNT_SET(query->packet, 0); + buffer_set_position(query->packet, oldpos); + RCODE_SET(query->packet, RCODE_FORMAT); + return QUERY_PROCESSED; + } + NSCOUNT_SET(query->packet, 0); + ARCOUNT_SET(query->packet, 0); + buffer_set_position(query->packet, oldpos); + DEBUG(DEBUG_XFRD,1, (LOG_INFO, "ixfr query routine, %s IXFR=%u", + dname_to_string(query->qname, NULL), (unsigned)qserial)); + + /* do we have an IXFR with this serial number? If not, serve AXFR */ + zone = namedb_find_zone(nsd->db, query->qname); + if(!zone) { + /* no zone is present */ + RCODE_SET(query->packet, RCODE_NOTAUTH); + return QUERY_PROCESSED; + } + ZTATUP(nsd, zone, rixfr); + + /* if the query is for same or newer serial than our current + * serial, then serve a single SOA with our current serial */ + current_serial = zone_get_current_serial(zone); + if(compare_serial(qserial, current_serial) >= 0) { + if(!zone->soa_rrset || zone->soa_rrset->rr_count != 1){ + RCODE_SET(query->packet, RCODE_SERVFAIL); + return QUERY_PROCESSED; + } + query_add_compression_domain(query, zone->apex, + QHEADERSZ); + if(packet_encode_rr(query, zone->apex, + &zone->soa_rrset->rrs[0], + zone->soa_rrset->rrs[0].ttl)) { + ANCOUNT_SET(query->packet, 1); + } else { + RCODE_SET(query->packet, RCODE_SERVFAIL); + } + AA_SET(query->packet); + query_clear_compression_tables(query); + if(query->tsig.status == TSIG_OK) + query->tsig_sign_it = 1; + return QUERY_PROCESSED; + } + + if(!zone->ixfr) { + /* we have no ixfr information for the zone, make an AXFR */ + if(query->tsig_prepare_it) + query->tsig_sign_it = 1; + return query_axfr(nsd, query, 0); + } + ixfr_data = zone_ixfr_find_serial(zone->ixfr, qserial); + if(!ixfr_data) { + /* the specific version is not available, make an AXFR */ + if(query->tsig_prepare_it) + query->tsig_sign_it = 1; + return query_axfr(nsd, query, 0); + } + /* see if the IXFRs connect to the next IXFR, and if it ends + * at the current served zone, if not, AXFR */ + if(!connect_ixfrs(zone->ixfr, ixfr_data, &end_serial) || + end_serial != current_serial) { + if(query->tsig_prepare_it) + query->tsig_sign_it = 1; + return query_axfr(nsd, query, 0); + } + + query->zone = zone; + query->ixfr_data = ixfr_data; + query->ixfr_is_done = 0; + /* set up to copy the last version's SOA as first SOA */ + query->ixfr_end_data = ixfr_data_last(zone->ixfr); + query->ixfr_count_newsoa = 0; + query->ixfr_count_oldsoa = 0; + query->ixfr_count_del = 0; + query->ixfr_count_add = 0; + query->ixfr_pos_of_newsoa = 0; + /* the query name can be compressed to */ + pktcompression_insert_with_labels(&pcomp, + buffer_at(query->packet, QHEADERSZ), + query->qname->name_size, QHEADERSZ); + if(query->tsig.status == TSIG_OK) { + query->tsig_sign_it = 1; /* sign first packet in stream */ + } + } else { + /* + * Query name need not be repeated after the + * first response packet. + */ + buffer_set_limit(query->packet, QHEADERSZ); + QDCOUNT_SET(query->packet, 0); + query_prepare_response(query); + } + + total_added = ixfr_copy_rrs_into_packet(query, &pcomp); + + while(query->ixfr_count_add >= query->ixfr_data->add_len) { + struct ixfr_data* next = ixfr_data_next(query->zone->ixfr, + query->ixfr_data); + /* finished the ixfr_data */ + if(next) { + /* move to the next IXFR */ + query->ixfr_data = next; + /* we need to skip the SOA records, set len to done*/ + /* the newsoa count is already done, at end_data len */ + query->ixfr_count_oldsoa = next->oldsoa_len; + /* and then set up to copy the del and add sections */ + query->ixfr_count_del = 0; + query->ixfr_count_add = 0; + total_added += ixfr_copy_rrs_into_packet(query, &pcomp); + } else { + /* we finished the IXFR */ + /* sign the last packet */ + query->tsig_sign_it = 1; + query->ixfr_is_done = 1; + break; + } + } + + /* return the answer */ + AA_SET(query->packet); + ANCOUNT_SET(query->packet, total_added); + NSCOUNT_SET(query->packet, 0); + ARCOUNT_SET(query->packet, 0); + + if(!query->tcp && !query->ixfr_is_done) { + TC_SET(query->packet); + if(query->ixfr_pos_of_newsoa) { + /* if we recorded the newsoa in the result, snip off + * the rest of the response, the RFC1995 response for + * when it does not fit is only the latest SOA */ + buffer_set_position(query->packet, query->ixfr_pos_of_newsoa); + ANCOUNT_SET(query->packet, 1); + } + query->ixfr_is_done = 1; + } + + /* check if it needs tsig signatures */ + if(query->tsig.status == TSIG_OK) { +#if IXFR_TSIG_SIGN_EVERY_NTH > 0 + if(query->tsig.updates_since_last_prepare >= IXFR_TSIG_SIGN_EVERY_NTH) { +#endif + query->tsig_sign_it = 1; +#if IXFR_TSIG_SIGN_EVERY_NTH > 0 + } +#endif + } + pktcompression_freeup(&pcomp); + return QUERY_IN_IXFR; +} + +/* free ixfr_data structure */ +static void ixfr_data_free(struct ixfr_data* data) +{ + if(!data) + return; + free(data->newsoa); + free(data->oldsoa); + free(data->del); + free(data->add); + free(data->log_str); + free(data); +} + +size_t ixfr_data_size(struct ixfr_data* data) +{ + return sizeof(struct ixfr_data) + data->newsoa_len + data->oldsoa_len + + data->del_len + data->add_len; +} + +struct ixfr_store* ixfr_store_start(struct zone* zone, + struct ixfr_store* ixfr_store_mem, uint32_t old_serial, + uint32_t new_serial) +{ + struct ixfr_store* ixfr_store = ixfr_store_mem; + memset(ixfr_store, 0, sizeof(*ixfr_store)); + ixfr_store->zone = zone; + ixfr_store->data = xalloc_zero(sizeof(*ixfr_store->data)); + ixfr_store->data->oldserial = old_serial; + ixfr_store->data->newserial = new_serial; + return ixfr_store; +} + +void ixfr_store_cancel(struct ixfr_store* ixfr_store) +{ + ixfr_store->cancelled = 1; + ixfr_data_free(ixfr_store->data); + ixfr_store->data = NULL; +} + +void ixfr_store_free(struct ixfr_store* ixfr_store) +{ + if(!ixfr_store) + return; + ixfr_data_free(ixfr_store->data); +} + +/* make space in record data for the new size, grows the allocation */ +static void ixfr_rrs_make_space(uint8_t** rrs, size_t* len, size_t* capacity, + size_t added) +{ + size_t newsize = 0; + if(*rrs == NULL) { + newsize = IXFR_STORE_INITIAL_SIZE; + } else { + if(*len + added <= *capacity) + return; /* already enough space */ + newsize = (*capacity)*2; + } + if(*len + added > newsize) + newsize = *len + added; + if(*rrs == NULL) { + *rrs = xalloc(newsize); + } else { + *rrs = xrealloc(*rrs, newsize); + } + *capacity = newsize; +} + +/* put new SOA record after delrrs and addrrs */ +static void ixfr_put_newsoa(struct ixfr_store* ixfr_store, uint8_t** rrs, + size_t* len, size_t* capacity) +{ + uint8_t* soa; + size_t soa_len; + if(!ixfr_store->data) + return; /* data should be nonNULL, we are not cancelled */ + soa = ixfr_store->data->newsoa; + soa_len= ixfr_store->data->newsoa_len; + ixfr_rrs_make_space(rrs, len, capacity, soa_len); + if(!*rrs || *len + soa_len > *capacity) { + log_msg(LOG_ERR, "ixfr_store addrr: cannot allocate space"); + ixfr_store_cancel(ixfr_store); + return; + } + memmove(*rrs + *len, soa, soa_len); + *len += soa_len; +} + +/* trim unused storage from the rrs data */ +static void ixfr_trim_capacity(uint8_t** rrs, size_t* len, size_t* capacity) +{ + if(*rrs == NULL) + return; + if(*capacity == *len) + return; + *rrs = xrealloc(*rrs, *len); + *capacity = *len; +} + +void ixfr_store_finish_data(struct ixfr_store* ixfr_store) +{ + if(ixfr_store->data_trimmed) + return; + ixfr_store->data_trimmed = 1; + + /* put new serial SOA record after delrrs and addrrs */ + ixfr_put_newsoa(ixfr_store, &ixfr_store->data->del, + &ixfr_store->data->del_len, &ixfr_store->del_capacity); + ixfr_put_newsoa(ixfr_store, &ixfr_store->data->add, + &ixfr_store->data->add_len, &ixfr_store->add_capacity); + + /* trim the data in the store, the overhead from capacity is + * removed */ + if(!ixfr_store->data) + return; /* data should be nonNULL, we are not cancelled */ + ixfr_trim_capacity(&ixfr_store->data->del, + &ixfr_store->data->del_len, &ixfr_store->del_capacity); + ixfr_trim_capacity(&ixfr_store->data->add, + &ixfr_store->data->add_len, &ixfr_store->add_capacity); +} + +void ixfr_store_finish(struct ixfr_store* ixfr_store, struct nsd* nsd, + char* log_buf) +{ + if(ixfr_store->cancelled) { + ixfr_store_free(ixfr_store); + return; + } + + ixfr_store_finish_data(ixfr_store); + + if(ixfr_store->cancelled) { + ixfr_store_free(ixfr_store); + return; + } + + if(log_buf && !ixfr_store->data->log_str) + ixfr_store->data->log_str = strdup(log_buf); + + /* store the data in the zone */ + if(!ixfr_store->zone->ixfr) + ixfr_store->zone->ixfr = zone_ixfr_create(nsd); + zone_ixfr_make_space(ixfr_store->zone->ixfr, ixfr_store->zone, + ixfr_store->data, ixfr_store); + if(ixfr_store->cancelled) { + ixfr_store_free(ixfr_store); + return; + } + zone_ixfr_add(ixfr_store->zone->ixfr, ixfr_store->data, 1); + ixfr_store->data = NULL; + + /* free structure */ + ixfr_store_free(ixfr_store); +} + +/* read SOA rdata section for SOA storage */ +static int read_soa_rdata(struct buffer* packet, uint8_t* primns, + int* primns_len, uint8_t* email, int* email_len, + uint32_t* serial, uint32_t* refresh, uint32_t* retry, + uint32_t* expire, uint32_t* minimum, size_t* sz) +{ + if(!(*primns_len = dname_make_wire_from_packet(primns, packet, 1))) { + log_msg(LOG_ERR, "ixfr_store: cannot parse soa nsname in packet"); + return 0; + } + *sz += *primns_len; + if(!(*email_len = dname_make_wire_from_packet(email, packet, 1))) { + log_msg(LOG_ERR, "ixfr_store: cannot parse soa maintname in packet"); + return 0; + } + *sz += *email_len; + *serial = buffer_read_u32(packet); + *sz += 4; + *refresh = buffer_read_u32(packet); + *sz += 4; + *retry = buffer_read_u32(packet); + *sz += 4; + *expire = buffer_read_u32(packet); + *sz += 4; + *minimum = buffer_read_u32(packet); + *sz += 4; + return 1; +} + +/* store SOA record data in memory buffer */ +static void store_soa(uint8_t* soa, struct zone* zone, uint32_t ttl, + uint16_t rdlen_uncompressed, uint8_t* primns, int primns_len, + uint8_t* email, int email_len, uint32_t serial, uint32_t refresh, + uint32_t retry, uint32_t expire, uint32_t minimum) +{ + uint8_t* sp = soa; + memmove(sp, dname_name(domain_dname(zone->apex)), + domain_dname(zone->apex)->name_size); + sp += domain_dname(zone->apex)->name_size; + write_uint16(sp, TYPE_SOA); + sp += 2; + write_uint16(sp, CLASS_IN); + sp += 2; + write_uint32(sp, ttl); + sp += 4; + write_uint16(sp, rdlen_uncompressed); + sp += 2; + memmove(sp, primns, primns_len); + sp += primns_len; + memmove(sp, email, email_len); + sp += email_len; + write_uint32(sp, serial); + sp += 4; + write_uint32(sp, refresh); + sp += 4; + write_uint32(sp, retry); + sp += 4; + write_uint32(sp, expire); + sp += 4; + write_uint32(sp, minimum); +} + +void ixfr_store_add_newsoa(struct ixfr_store* ixfr_store, + struct buffer* packet, size_t ttlpos) +{ + size_t oldpos, sz = 0; + uint32_t ttl, serial, refresh, retry, expire, minimum; + uint16_t rdlen_uncompressed, rdlen_wire; + int primns_len = 0, email_len = 0; + uint8_t primns[MAXDOMAINLEN + 1], email[MAXDOMAINLEN + 1]; + + if(ixfr_store->cancelled) + return; + if(ixfr_store->data->newsoa) { + free(ixfr_store->data->newsoa); + ixfr_store->data->newsoa = NULL; + ixfr_store->data->newsoa_len = 0; + } + oldpos = buffer_position(packet); + buffer_set_position(packet, ttlpos); + + /* calculate the length */ + sz = domain_dname(ixfr_store->zone->apex)->name_size; + sz += 2 /* type */ + 2 /* class */; + /* read ttl */ + if(!buffer_available(packet, 4/*ttl*/+2/*rdlen*/)) { + /* not possible already parsed, but fail nicely anyway */ + log_msg(LOG_ERR, "ixfr_store: not enough space in packet"); + ixfr_store_cancel(ixfr_store); + buffer_set_position(packet, oldpos); + return; + } + ttl = buffer_read_u32(packet); + sz += 4; + rdlen_wire = buffer_read_u16(packet); + sz += 2; + if(!buffer_available(packet, rdlen_wire)) { + /* not possible already parsed, but fail nicely anyway */ + log_msg(LOG_ERR, "ixfr_store: not enough rdata space in packet"); + ixfr_store_cancel(ixfr_store); + buffer_set_position(packet, oldpos); + return; + } + if(!read_soa_rdata(packet, primns, &primns_len, email, &email_len, + &serial, &refresh, &retry, &expire, &minimum, &sz)) { + log_msg(LOG_ERR, "ixfr_store newsoa: cannot parse packet"); + ixfr_store_cancel(ixfr_store); + buffer_set_position(packet, oldpos); + return; + } + rdlen_uncompressed = primns_len + email_len + 4 + 4 + 4 + 4 + 4; + + /* store the soa record */ + ixfr_store->data->newsoa = xalloc(sz); + ixfr_store->data->newsoa_len = sz; + store_soa(ixfr_store->data->newsoa, ixfr_store->zone, ttl, + rdlen_uncompressed, primns, primns_len, email, email_len, + serial, refresh, retry, expire, minimum); + + buffer_set_position(packet, oldpos); +} + +void ixfr_store_add_oldsoa(struct ixfr_store* ixfr_store, uint32_t ttl, + struct buffer* packet, size_t rrlen) +{ + size_t oldpos, sz = 0; + uint32_t serial, refresh, retry, expire, minimum; + uint16_t rdlen_uncompressed; + int primns_len = 0, email_len = 0; + uint8_t primns[MAXDOMAINLEN + 1], email[MAXDOMAINLEN + 1]; + + if(ixfr_store->cancelled) + return; + if(ixfr_store->data->oldsoa) { + free(ixfr_store->data->oldsoa); + ixfr_store->data->oldsoa = NULL; + ixfr_store->data->oldsoa_len = 0; + } + /* we have the old SOA and thus we are sure it is an IXFR, make space*/ + zone_ixfr_make_space(ixfr_store->zone->ixfr, ixfr_store->zone, + ixfr_store->data, ixfr_store); + if(ixfr_store->cancelled) + return; + oldpos = buffer_position(packet); + + /* calculate the length */ + sz = domain_dname(ixfr_store->zone->apex)->name_size; + sz += 2 /*type*/ + 2 /*class*/ + 4 /*ttl*/ + 2 /*rdlen*/; + if(!buffer_available(packet, rrlen)) { + /* not possible already parsed, but fail nicely anyway */ + log_msg(LOG_ERR, "ixfr_store oldsoa: not enough rdata space in packet"); + ixfr_store_cancel(ixfr_store); + buffer_set_position(packet, oldpos); + return; + } + if(!read_soa_rdata(packet, primns, &primns_len, email, &email_len, + &serial, &refresh, &retry, &expire, &minimum, &sz)) { + log_msg(LOG_ERR, "ixfr_store oldsoa: cannot parse packet"); + ixfr_store_cancel(ixfr_store); + buffer_set_position(packet, oldpos); + return; + } + rdlen_uncompressed = primns_len + email_len + 4 + 4 + 4 + 4 + 4; + + /* store the soa record */ + ixfr_store->data->oldsoa = xalloc(sz); + ixfr_store->data->oldsoa_len = sz; + store_soa(ixfr_store->data->oldsoa, ixfr_store->zone, ttl, + rdlen_uncompressed, primns, primns_len, email, email_len, + serial, refresh, retry, expire, minimum); + + buffer_set_position(packet, oldpos); +} + +/* store RR in data segment */ +static int ixfr_putrr(const struct dname* dname, uint16_t type, uint16_t klass, + uint32_t ttl, rdata_atom_type* rdatas, ssize_t rdata_num, + uint8_t** rrs, size_t* rrs_len, size_t* rrs_capacity) +{ + size_t rdlen_uncompressed, sz; + uint8_t* sp; + int i; + + /* find rdatalen */ + rdlen_uncompressed = 0; + for(i=0; iname_size; + } else { + rdlen_uncompressed += rdatas[i].data[0]; + } + } + sz = dname->name_size + 2 /*type*/ + 2 /*class*/ + 4 /*ttl*/ + + 2 /*rdlen*/ + rdlen_uncompressed; + + /* store RR in IXFR data */ + ixfr_rrs_make_space(rrs, rrs_len, rrs_capacity, sz); + if(!*rrs || *rrs_len + sz > *rrs_capacity) { + return 0; + } + /* copy data into add */ + sp = *rrs + *rrs_len; + *rrs_len += sz; + memmove(sp, dname_name(dname), dname->name_size); + sp += dname->name_size; + write_uint16(sp, type); + sp += 2; + write_uint16(sp, klass); + sp += 2; + write_uint32(sp, ttl); + sp += 4; + write_uint16(sp, rdlen_uncompressed); + sp += 2; + for(i=0; iname_size); + sp += domain_dname(rdatas[i].domain)->name_size; + } else { + memmove(sp, &rdatas[i].data[1], rdatas[i].data[0]); + sp += rdatas[i].data[0]; + } + } + return 1; +} + +void ixfr_store_putrr(struct ixfr_store* ixfr_store, const struct dname* dname, + uint16_t type, uint16_t klass, uint32_t ttl, struct buffer* packet, + uint16_t rrlen, struct region* temp_region, uint8_t** rrs, + size_t* rrs_len, size_t* rrs_capacity) +{ + domain_table_type *temptable; + rdata_atom_type *rdatas; + ssize_t rdata_num; + size_t oldpos; + + if(ixfr_store->cancelled) + return; + + /* The SOA data is stored with separate calls. And then appended + * during the finish operation. We do not have to store it here + * when called from difffile's IXFR processing with type SOA. */ + if(type == TYPE_SOA) + return; + /* make space for these RRs we have now; basically once we + * grow beyond the current allowed amount an older IXFR is deleted. */ + zone_ixfr_make_space(ixfr_store->zone->ixfr, ixfr_store->zone, + ixfr_store->data, ixfr_store); + if(ixfr_store->cancelled) + return; + + /* parse rdata */ + oldpos = buffer_position(packet); + temptable = domain_table_create(temp_region); + rdata_num = rdata_wireformat_to_rdata_atoms(temp_region, temptable, + type, rrlen, packet, &rdatas); + buffer_set_position(packet, oldpos); + if(rdata_num == -1) { + log_msg(LOG_ERR, "ixfr_store addrr: cannot parse packet"); + ixfr_store_cancel(ixfr_store); + return; + } + + if(!ixfr_putrr(dname, type, klass, ttl, rdatas, rdata_num, + rrs, rrs_len, rrs_capacity)) { + log_msg(LOG_ERR, "ixfr_store addrr: cannot allocate space"); + ixfr_store_cancel(ixfr_store); + return; + } +} + +void ixfr_store_delrr(struct ixfr_store* ixfr_store, const struct dname* dname, + uint16_t type, uint16_t klass, uint32_t ttl, struct buffer* packet, + uint16_t rrlen, struct region* temp_region) +{ + ixfr_store_putrr(ixfr_store, dname, type, klass, ttl, packet, rrlen, + temp_region, &ixfr_store->data->del, + &ixfr_store->data->del_len, &ixfr_store->del_capacity); +} + +void ixfr_store_addrr(struct ixfr_store* ixfr_store, const struct dname* dname, + uint16_t type, uint16_t klass, uint32_t ttl, struct buffer* packet, + uint16_t rrlen, struct region* temp_region) +{ + ixfr_store_putrr(ixfr_store, dname, type, klass, ttl, packet, rrlen, + temp_region, &ixfr_store->data->add, + &ixfr_store->data->add_len, &ixfr_store->add_capacity); +} + +int ixfr_store_addrr_rdatas(struct ixfr_store* ixfr_store, + const struct dname* dname, uint16_t type, uint16_t klass, + uint32_t ttl, rdata_atom_type* rdatas, ssize_t rdata_num) +{ + if(ixfr_store->cancelled) + return 1; + if(type == TYPE_SOA) + return 1; + return ixfr_putrr(dname, type, klass, ttl, rdatas, rdata_num, + &ixfr_store->data->add, &ixfr_store->data->add_len, + &ixfr_store->add_capacity); +} + +int ixfr_store_add_newsoa_rdatas(struct ixfr_store* ixfr_store, + const struct dname* dname, uint16_t type, uint16_t klass, + uint32_t ttl, rdata_atom_type* rdatas, ssize_t rdata_num) +{ + size_t capacity = 0; + if(ixfr_store->cancelled) + return 1; + if(!ixfr_putrr(dname, type, klass, ttl, rdatas, rdata_num, + &ixfr_store->data->newsoa, &ixfr_store->data->newsoa_len, + &ixfr_store->add_capacity)) + return 0; + ixfr_trim_capacity(&ixfr_store->data->newsoa, + &ixfr_store->data->newsoa_len, &capacity); + return 1; +} + +/* store rr uncompressed */ +int ixfr_storerr_uncompressed(uint8_t* dname, size_t dname_len, uint16_t type, + uint16_t klass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, + uint8_t** rrs, size_t* rrs_len, size_t* rrs_capacity) +{ + size_t sz; + uint8_t* sp; + + /* find rdatalen */ + sz = dname_len + 2 /*type*/ + 2 /*class*/ + 4 /*ttl*/ + + 2 /*rdlen*/ + rdata_len; + + /* store RR in IXFR data */ + ixfr_rrs_make_space(rrs, rrs_len, rrs_capacity, sz); + if(!*rrs || *rrs_len + sz > *rrs_capacity) { + return 0; + } + /* copy data into add */ + sp = *rrs + *rrs_len; + *rrs_len += sz; + memmove(sp, dname, dname_len); + sp += dname_len; + write_uint16(sp, type); + sp += 2; + write_uint16(sp, klass); + sp += 2; + write_uint32(sp, ttl); + sp += 4; + write_uint16(sp, rdata_len); + sp += 2; + memmove(sp, rdata, rdata_len); + return 1; +} + +int ixfr_store_delrr_uncompressed(struct ixfr_store* ixfr_store, + uint8_t* dname, size_t dname_len, uint16_t type, uint16_t klass, + uint32_t ttl, uint8_t* rdata, size_t rdata_len) +{ + if(ixfr_store->cancelled) + return 1; + if(type == TYPE_SOA) + return 1; + return ixfr_storerr_uncompressed(dname, dname_len, type, klass, + ttl, rdata, rdata_len, &ixfr_store->data->del, + &ixfr_store->data->del_len, &ixfr_store->del_capacity); +} + +int ixfr_store_oldsoa_uncompressed(struct ixfr_store* ixfr_store, + uint8_t* dname, size_t dname_len, uint16_t type, uint16_t klass, + uint32_t ttl, uint8_t* rdata, size_t rdata_len) +{ + size_t capacity = 0; + if(ixfr_store->cancelled) + return 1; + if(!ixfr_storerr_uncompressed(dname, dname_len, type, klass, + ttl, rdata, rdata_len, &ixfr_store->data->oldsoa, + &ixfr_store->data->oldsoa_len, &capacity)) + return 0; + ixfr_trim_capacity(&ixfr_store->data->oldsoa, + &ixfr_store->data->oldsoa_len, &capacity); + return 1; +} + +int zone_is_ixfr_enabled(struct zone* zone) +{ + return zone->opts->pattern->store_ixfr; +} + +/* compare ixfr elements */ +static int ixfrcompare(const void* x, const void* y) +{ + uint32_t* serial_x = (uint32_t*)x; + uint32_t* serial_y = (uint32_t*)y; + if(*serial_x < *serial_y) + return -1; + if(*serial_x > *serial_y) + return 1; + return 0; +} + +struct zone_ixfr* zone_ixfr_create(struct nsd* nsd) +{ + struct zone_ixfr* ixfr = xalloc_zero(sizeof(struct zone_ixfr)); + ixfr->data = rbtree_create(nsd->region, &ixfrcompare); + return ixfr; +} + +/* traverse tree postorder */ +static void ixfr_tree_del(struct rbnode* node) +{ + if(node == NULL || node == RBTREE_NULL) + return; + ixfr_tree_del(node->left); + ixfr_tree_del(node->right); + ixfr_data_free((struct ixfr_data*)node); +} + +/* clear the ixfr data elements */ +static void zone_ixfr_clear(struct zone_ixfr* ixfr) +{ + if(!ixfr) + return; + if(ixfr->data) { + ixfr_tree_del(ixfr->data->root); + ixfr->data->root = RBTREE_NULL; + ixfr->data->count = 0; + } + ixfr->total_size = 0; + ixfr->oldest_serial = 0; + ixfr->newest_serial = 0; +} + +void zone_ixfr_free(struct zone_ixfr* ixfr) +{ + if(!ixfr) + return; + if(ixfr->data) { + ixfr_tree_del(ixfr->data->root); + ixfr->data = NULL; + } + free(ixfr); +} + +void ixfr_store_delixfrs(struct zone* zone) +{ + if(!zone) + return; + zone_ixfr_clear(zone->ixfr); +} + +/* remove the oldest data entry from the ixfr versions */ +static void zone_ixfr_remove_oldest(struct zone_ixfr* ixfr) +{ + if(ixfr->data->count > 0) { + struct ixfr_data* oldest = ixfr_data_first(ixfr); + if(ixfr->oldest_serial == oldest->oldserial) { + if(ixfr->data->count > 1) { + struct ixfr_data* next = ixfr_data_next(ixfr, oldest); + assert(next); + if(next) + ixfr->oldest_serial = next->oldserial; + else ixfr->oldest_serial = oldest->newserial; + } else { + ixfr->oldest_serial = 0; + } + } + if(ixfr->newest_serial == oldest->oldserial) { + ixfr->newest_serial = 0; + } + zone_ixfr_remove(ixfr, oldest); + } +} + +void zone_ixfr_make_space(struct zone_ixfr* ixfr, struct zone* zone, + struct ixfr_data* data, struct ixfr_store* ixfr_store) +{ + size_t addsize; + if(!ixfr || !data) + return; + if(zone->opts->pattern->ixfr_number == 0) { + ixfr_store_cancel(ixfr_store); + return; + } + + /* Check the number of IXFRs allowed for this zone, if too many, + * shorten the number to make space for another one */ + while(ixfr->data->count >= zone->opts->pattern->ixfr_number) { + zone_ixfr_remove_oldest(ixfr); + } + + if(zone->opts->pattern->ixfr_size == 0) { + /* no size limits imposed */ + return; + } + + /* Check the size of the current added data element 'data', and + * see if that overflows the maximum storage size for IXFRs for + * this zone, and if so, delete the oldest IXFR to make space */ + addsize = ixfr_data_size(data); + while(ixfr->data->count > 0 && ixfr->total_size + addsize > + zone->opts->pattern->ixfr_size) { + zone_ixfr_remove_oldest(ixfr); + } + + /* if deleting the oldest elements does not work, then this + * IXFR is too big to store and we cancel it */ + if(ixfr->data->count == 0 && ixfr->total_size + addsize > + zone->opts->pattern->ixfr_size) { + ixfr_store_cancel(ixfr_store); + return; + } +} + +void zone_ixfr_remove(struct zone_ixfr* ixfr, struct ixfr_data* data) +{ + rbtree_delete(ixfr->data, data->node.key); + ixfr->total_size -= ixfr_data_size(data); + ixfr_data_free(data); +} + +void zone_ixfr_add(struct zone_ixfr* ixfr, struct ixfr_data* data, int isnew) +{ + memset(&data->node, 0, sizeof(data->node)); + if(ixfr->data->count == 0) { + ixfr->oldest_serial = data->oldserial; + ixfr->newest_serial = data->oldserial; + } else if(isnew) { + /* newest entry is last there is */ + ixfr->newest_serial = data->oldserial; + } else { + /* added older entry, before the others */ + ixfr->oldest_serial = data->oldserial; + } + data->node.key = &data->oldserial; + rbtree_insert(ixfr->data, &data->node); + ixfr->total_size += ixfr_data_size(data); +} + +struct ixfr_data* zone_ixfr_find_serial(struct zone_ixfr* ixfr, + uint32_t qserial) +{ + struct ixfr_data* data; + if(!ixfr) + return NULL; + if(!ixfr->data) + return NULL; + data = (struct ixfr_data*)rbtree_search(ixfr->data, &qserial); + if(data) { + assert(data->oldserial == qserial); + return data; + } + /* not found */ + return NULL; +} + +/* calculate the number of files we want */ +static int ixfr_target_number_files(struct zone* zone) +{ + int dest_num_files; + if(!zone->ixfr || !zone->ixfr->data) + return 0; + if(!zone_is_ixfr_enabled(zone)) + return 0; + /* if we store ixfr, it is the configured number of files */ + dest_num_files = (int)zone->opts->pattern->ixfr_number; + /* but if the number of available transfers is smaller, store less */ + if(dest_num_files > (int)zone->ixfr->data->count) + dest_num_files = (int)zone->ixfr->data->count; + return dest_num_files; +} + +/* create ixfrfile name in buffer for file_num. The num is 1 .. number. */ +static void make_ixfr_name(char* buf, size_t len, const char* zfile, + int file_num) +{ + if(file_num == 1) + snprintf(buf, len, "%s.ixfr", zfile); + else snprintf(buf, len, "%s.ixfr.%d", zfile, file_num); +} + +/* create temp ixfrfile name in buffer for file_num. The num is 1 .. number. */ +static void make_ixfr_name_temp(char* buf, size_t len, const char* zfile, + int file_num, int temp) +{ + if(file_num == 1) + snprintf(buf, len, "%s.ixfr%s", zfile, (temp?".temp":"")); + else snprintf(buf, len, "%s.ixfr.%d%s", zfile, file_num, + (temp?".temp":"")); +} + +/* see if ixfr file exists */ +static int ixfr_file_exists_ctmp(const char* zfile, int file_num, int temp) +{ + struct stat statbuf; + char ixfrfile[1024+24]; + make_ixfr_name_temp(ixfrfile, sizeof(ixfrfile), zfile, file_num, temp); + memset(&statbuf, 0, sizeof(statbuf)); + if(stat(ixfrfile, &statbuf) < 0) { + if(errno == ENOENT) + return 0; + /* file is not usable */ + return 0; + } + return 1; +} + +int ixfr_file_exists(const char* zfile, int file_num) +{ + return ixfr_file_exists_ctmp(zfile, file_num, 0); +} + +/* see if ixfr file exists */ +static int ixfr_file_exists_temp(const char* zfile, int file_num) +{ + return ixfr_file_exists_ctmp(zfile, file_num, 1); +} + +/* unlink an ixfr file */ +static int ixfr_unlink_it_ctmp(const char* zname, const char* zfile, + int file_num, int silent_enoent, int temp) +{ + char ixfrfile[1024+24]; + make_ixfr_name_temp(ixfrfile, sizeof(ixfrfile), zfile, file_num, temp); + VERBOSITY(3, (LOG_INFO, "delete zone %s IXFR data file %s", + zname, ixfrfile)); + if(unlink(ixfrfile) < 0) { + if(silent_enoent && errno == ENOENT) + return 0; + log_msg(LOG_ERR, "error to delete file %s: %s", ixfrfile, + strerror(errno)); + return 0; + } + return 1; +} + +int ixfr_unlink_it(const char* zname, const char* zfile, int file_num, + int silent_enoent) +{ + return ixfr_unlink_it_ctmp(zname, zfile, file_num, silent_enoent, 0); +} + +/* unlink an ixfr file */ +static int ixfr_unlink_it_temp(const char* zname, const char* zfile, + int file_num, int silent_enoent) +{ + return ixfr_unlink_it_ctmp(zname, zfile, file_num, silent_enoent, 1); +} + +/* read ixfr file header */ +int ixfr_read_file_header(const char* zname, const char* zfile, + int file_num, uint32_t* oldserial, uint32_t* newserial, + size_t* data_size, int enoent_is_err) +{ + char ixfrfile[1024+24]; + char buf[1024]; + FILE* in; + int num_lines = 0, got_old = 0, got_new = 0, got_datasize = 0; + make_ixfr_name(ixfrfile, sizeof(ixfrfile), zfile, file_num); + in = fopen(ixfrfile, "r"); + if(!in) { + if((errno == ENOENT && enoent_is_err) || (errno != ENOENT)) + log_msg(LOG_ERR, "could not open %s: %s", ixfrfile, + strerror(errno)); + return 0; + } + /* read about 10 lines, this is where the header is */ + while(!(got_old && got_new && got_datasize) && num_lines < 10) { + buf[0]=0; + buf[sizeof(buf)-1]=0; + if(!fgets(buf, sizeof(buf), in)) { + log_msg(LOG_ERR, "could not read %s: %s", ixfrfile, + strerror(errno)); + fclose(in); + return 0; + } + num_lines++; + if(buf[0]!=0 && buf[strlen(buf)-1]=='\n') + buf[strlen(buf)-1]=0; + if(strncmp(buf, "; zone ", 7) == 0) { + if(strcmp(buf+7, zname) != 0) { + log_msg(LOG_ERR, "file has wrong zone, expected zone %s, but found %s in file %s", + zname, buf+7, ixfrfile); + fclose(in); + return 0; + } + } else if(strncmp(buf, "; from_serial ", 14) == 0) { + *oldserial = atoi(buf+14); + got_old = 1; + } else if(strncmp(buf, "; to_serial ", 12) == 0) { + *newserial = atoi(buf+12); + got_new = 1; + } else if(strncmp(buf, "; data_size ", 12) == 0) { + *data_size = (size_t)atoi(buf+12); + got_datasize = 1; + } + } + fclose(in); + if(!got_old) + return 0; + if(!got_new) + return 0; + if(!got_datasize) + return 0; + return 1; +} + +/* delete rest ixfr files, that are after the current item */ +static void ixfr_delete_rest_files(struct zone* zone, struct ixfr_data* from, + const char* zfile, int temp) +{ + size_t prevcount = 0; + struct ixfr_data* data = from; + while(data) { + if(data->file_num != 0) { + (void)ixfr_unlink_it_ctmp(zone->opts->name, zfile, + data->file_num, 0, temp); + data->file_num = 0; + } + data = ixfr_data_prev(zone->ixfr, data, &prevcount); + } +} + +void ixfr_delete_superfluous_files(struct zone* zone, const char* zfile, + int dest_num_files) +{ + int i = dest_num_files + 1; + if(!ixfr_file_exists(zfile, i)) + return; + while(ixfr_unlink_it(zone->opts->name, zfile, i, 1)) { + i++; + } +} + +int ixfr_rename_it(const char* zname, const char* zfile, int oldnum, + int oldtemp, int newnum, int newtemp) +{ + char ixfrfile_old[1024+24]; + char ixfrfile_new[1024+24]; + make_ixfr_name_temp(ixfrfile_old, sizeof(ixfrfile_old), zfile, oldnum, + oldtemp); + make_ixfr_name_temp(ixfrfile_new, sizeof(ixfrfile_new), zfile, newnum, + newtemp); + VERBOSITY(3, (LOG_INFO, "rename zone %s IXFR data file %s to %s", + zname, ixfrfile_old, ixfrfile_new)); + if(rename(ixfrfile_old, ixfrfile_new) < 0) { + log_msg(LOG_ERR, "error to rename file %s: %s", ixfrfile_old, + strerror(errno)); + return 0; + } + return 1; +} + +/* delete if we have too many items in memory */ +static void ixfr_delete_memory_items(struct zone* zone, int dest_num_files) +{ + if(!zone->ixfr || !zone->ixfr->data) + return; + if(dest_num_files == (int)zone->ixfr->data->count) + return; + if(dest_num_files > (int)zone->ixfr->data->count) { + /* impossible, dest_num_files should be smaller */ + return; + } + + /* delete oldest ixfr, until we have dest_num_files entries */ + while(dest_num_files < (int)zone->ixfr->data->count) { + zone_ixfr_remove_oldest(zone->ixfr); + } +} + +/* rename the ixfr files that need to change name */ +static int ixfr_rename_files(struct zone* zone, const char* zfile, + int dest_num_files) +{ + struct ixfr_data* data, *startspot = NULL; + size_t prevcount = 0; + int destnum; + if(!zone->ixfr || !zone->ixfr->data) + return 1; + + /* the oldest file is at the largest number */ + data = ixfr_data_first(zone->ixfr); + destnum = dest_num_files; + if(!data) + return 1; /* nothing to do */ + if(data->file_num == destnum) + return 1; /* nothing to do for rename */ + + /* rename the files to temporary files, because otherwise the + * items would overwrite each other when the list touches itself. + * On fail, the temporary files are removed and we end up with + * the newly written data plus the remaining files, in order. + * Thus, start the temporary rename at the oldest, then rename + * to the final names starting from the newest. */ + while(data && data->file_num != 0) { + /* if existing file at temporary name, delete that */ + if(ixfr_file_exists_temp(zfile, data->file_num)) { + (void)ixfr_unlink_it_temp(zone->opts->name, zfile, + data->file_num, 0); + } + + /* rename to temporary name */ + if(!ixfr_rename_it(zone->opts->name, zfile, data->file_num, 0, + data->file_num, 1)) { + /* failure, we cannot store files */ + /* delete the renamed files */ + ixfr_delete_rest_files(zone, data, zfile, 1); + return 0; + } + + /* the next cycle should start at the newest file that + * has been renamed to a temporary name */ + startspot = data; + data = ixfr_data_next(zone->ixfr, data); + destnum--; + } + + /* rename the files to their final name position */ + data = startspot; + while(data && data->file_num != 0) { + destnum++; + + /* if there is an existing file, delete it */ + if(ixfr_file_exists(zfile, destnum)) { + (void)ixfr_unlink_it(zone->opts->name, zfile, + destnum, 0); + } + + if(!ixfr_rename_it(zone->opts->name, zfile, data->file_num, 1, destnum, 0)) { + /* failure, we cannot store files */ + ixfr_delete_rest_files(zone, data, zfile, 1); + /* delete the previously renamed files, so in + * memory stays as is, on disk we have the current + * item (and newer transfers) okay. */ + return 0; + } + data->file_num = destnum; + + data = ixfr_data_prev(zone->ixfr, data, &prevcount); + } + return 1; +} + +/* write the ixfr data file header */ +static int ixfr_write_file_header(struct zone* zone, struct ixfr_data* data, + FILE* out) +{ + if(!fprintf(out, "; IXFR data file\n")) + return 0; + if(!fprintf(out, "; zone %s\n", zone->opts->name)) + return 0; + if(!fprintf(out, "; from_serial %u\n", (unsigned)data->oldserial)) + return 0; + if(!fprintf(out, "; to_serial %u\n", (unsigned)data->newserial)) + return 0; + if(!fprintf(out, "; data_size %u\n", (unsigned)ixfr_data_size(data))) + return 0; + if(data->log_str) { + if(!fprintf(out, "; %s\n", data->log_str)) + return 0; + } + return 1; +} + +/* print rdata on one line */ +static int +oneline_print_rdata(buffer_type *output, rrtype_descriptor_type *descriptor, + rr_type* record) +{ + size_t i; + size_t saved_position = buffer_position(output); + + for (i = 0; i < record->rdata_count; ++i) { + if (i == 0) { + buffer_printf(output, "\t"); + } else { + buffer_printf(output, " "); + } + if (!rdata_atom_to_string( + output, + (rdata_zoneformat_type) descriptor->zoneformat[i], + record->rdatas[i], record)) + { + buffer_set_position(output, saved_position); + return 0; + } + } + + return 1; +} + +/* parse wireformat RR into a struct RR in temp region */ +static int parse_wirerr_into_temp(struct zone* zone, char* fname, + struct region* temp, uint8_t* buf, size_t len, + const dname_type** dname, struct rr* rr) +{ + size_t bufpos = 0; + uint16_t rdlen; + ssize_t rdata_num; + buffer_type packet; + domain_table_type* owners; + owners = domain_table_create(temp); + memset(rr, 0, sizeof(*rr)); + *dname = dname_make(temp, buf, 1); + if(!*dname) { + log_msg(LOG_ERR, "failed to write zone %s IXFR data %s: failed to parse dname", zone->opts->name, fname); + return 0; + } + bufpos = (*dname)->name_size; + if(bufpos+10 > len) { + log_msg(LOG_ERR, "failed to write zone %s IXFR data %s: buffer too short", zone->opts->name, fname); + return 0; + } + rr->type = read_uint16(buf+bufpos); + bufpos += 2; + rr->klass = read_uint16(buf+bufpos); + bufpos += 2; + rr->ttl = read_uint32(buf+bufpos); + bufpos += 4; + rdlen = read_uint16(buf+bufpos); + bufpos += 2; + if(bufpos + rdlen > len) { + log_msg(LOG_ERR, "failed to write zone %s IXFR data %s: buffer too short for rdatalen", zone->opts->name, fname); + return 0; + } + buffer_create_from(&packet, buf+bufpos, rdlen); + rdata_num = rdata_wireformat_to_rdata_atoms( + temp, owners, rr->type, rdlen, &packet, &rr->rdatas); + if(rdata_num == -1) { + log_msg(LOG_ERR, "failed to write zone %s IXFR data %s: cannot parse rdata", zone->opts->name, fname); + return 0; + } + rr->rdata_count = rdata_num; + return 1; +} + +/* print RR on one line in output buffer. caller must zeroterminate, if + * that is needed. */ +static int print_rr_oneline(struct buffer* rr_buffer, const dname_type* dname, + struct rr* rr) +{ + rrtype_descriptor_type *descriptor; + descriptor = rrtype_descriptor_by_type(rr->type); + buffer_printf(rr_buffer, "%s", dname_to_string(dname, NULL)); + buffer_printf(rr_buffer, "\t%lu\t%s\t%s", (unsigned long)rr->ttl, + rrclass_to_string(rr->klass), rrtype_to_string(rr->type)); + if(!oneline_print_rdata(rr_buffer, descriptor, rr)) { + if(!rdata_atoms_to_unknown_string(rr_buffer, + descriptor, rr->rdata_count, rr->rdatas)) { + return 0; + } + } + return 1; +} + +/* write one RR to file, on one line */ +static int ixfr_write_rr(struct zone* zone, FILE* out, char* fname, + uint8_t* buf, size_t len, struct region* temp, buffer_type* rr_buffer) +{ + const dname_type* dname; + struct rr rr; + + if(!parse_wirerr_into_temp(zone, fname, temp, buf, len, &dname, &rr)) { + region_free_all(temp); + return 0; + } + + buffer_clear(rr_buffer); + if(!print_rr_oneline(rr_buffer, dname, &rr)) { + log_msg(LOG_ERR, "failed to write zone %s IXFR data %s: cannot spool RR string into buffer", zone->opts->name, fname); + region_free_all(temp); + return 0; + } + buffer_write_u8(rr_buffer, 0); + buffer_flip(rr_buffer); + + if(!fprintf(out, "%s\n", buffer_begin(rr_buffer))) { + log_msg(LOG_ERR, "failed to write zone %s IXFR data %s: cannot print RR string to file: %s", zone->opts->name, fname, strerror(errno)); + region_free_all(temp); + return 0; + } + region_free_all(temp); + return 1; +} + +/* write ixfr RRs to file */ +static int ixfr_write_rrs(struct zone* zone, FILE* out, char* fname, + uint8_t* buf, size_t len, struct region* temp, buffer_type* rr_buffer) +{ + size_t current = 0; + if(!buf || len == 0) + return 1; + while(current < len) { + size_t rrlen = count_rr_length(buf, len, current); + if(rrlen == 0) + return 0; + if(current + rrlen > len) + return 0; + if(!ixfr_write_rr(zone, out, fname, buf+current, rrlen, + temp, rr_buffer)) + return 0; + current += rrlen; + } + return 1; +} + +/* write the ixfr data file data */ +static int ixfr_write_file_data(struct zone* zone, struct ixfr_data* data, + FILE* out, char* fname) +{ + struct region* temp, *rrtemp; + buffer_type* rr_buffer; + temp = region_create(xalloc, free); + rrtemp = region_create(xalloc, free); + rr_buffer = buffer_create(rrtemp, MAX_RDLENGTH); + + if(!ixfr_write_rrs(zone, out, fname, data->newsoa, data->newsoa_len, + temp, rr_buffer)) { + region_destroy(temp); + region_destroy(rrtemp); + return 0; + } + if(!ixfr_write_rrs(zone, out, fname, data->oldsoa, data->oldsoa_len, + temp, rr_buffer)) { + region_destroy(temp); + region_destroy(rrtemp); + return 0; + } + if(!ixfr_write_rrs(zone, out, fname, data->del, data->del_len, + temp, rr_buffer)) { + region_destroy(temp); + region_destroy(rrtemp); + return 0; + } + if(!ixfr_write_rrs(zone, out, fname, data->add, data->add_len, + temp, rr_buffer)) { + region_destroy(temp); + region_destroy(rrtemp); + return 0; + } + region_destroy(temp); + region_destroy(rrtemp); + return 1; +} + +int ixfr_write_file(struct zone* zone, struct ixfr_data* data, + const char* zfile, int file_num) +{ + char ixfrfile[1024+24]; + FILE* out; + make_ixfr_name(ixfrfile, sizeof(ixfrfile), zfile, file_num); + VERBOSITY(1, (LOG_INFO, "writing zone %s IXFR data to file %s", + zone->opts->name, ixfrfile)); + out = fopen(ixfrfile, "w"); + if(!out) { + log_msg(LOG_ERR, "could not open for writing zone %s IXFR file %s: %s", + zone->opts->name, ixfrfile, strerror(errno)); + return 0; + } + + if(!ixfr_write_file_header(zone, data, out)) { + log_msg(LOG_ERR, "could not write file header for zone %s IXFR file %s: %s", + zone->opts->name, ixfrfile, strerror(errno)); + fclose(out); + return 0; + } + if(!ixfr_write_file_data(zone, data, out, ixfrfile)) { + fclose(out); + return 0; + } + + fclose(out); + data->file_num = file_num; + return 1; +} + +/* write the ixfr files that need to be stored on disk */ +static void ixfr_write_files(struct zone* zone, const char* zfile) +{ + size_t prevcount = 0; + int num; + struct ixfr_data* data; + if(!zone->ixfr || !zone->ixfr->data) + return; /* nothing to write */ + + /* write unwritten files to disk */ + data = ixfr_data_last(zone->ixfr); + num=1; + while(data && data->file_num == 0) { + if(!ixfr_write_file(zone, data, zfile, num)) { + /* There could be more files that are sitting on the + * disk, remove them, they are not used without + * this ixfr file. + * + * Give this element a file num, so it can be + * deleted, it failed to write. It may be partial, + * and we do not want to read that back in. + * We are left with the newer transfers, that form + * a correct list of transfers, that are wholly + * written. */ + data->file_num = num; + ixfr_delete_rest_files(zone, data, zfile, 0); + return; + } + num++; + data = ixfr_data_prev(zone->ixfr, data, &prevcount); + } +} + +void ixfr_write_to_file(struct zone* zone, const char* zfile) +{ + int dest_num_files = 0; + /* we just wrote the zonefile zfile, and it is time to write + * the IXFR contents to the disk too. */ + /* find out what the target number of files is that we want on + * the disk */ + dest_num_files = ixfr_target_number_files(zone); + + /* delete if we have more than we need */ + ixfr_delete_superfluous_files(zone, zfile, dest_num_files); + + /* delete if we have too much in memory */ + ixfr_delete_memory_items(zone, dest_num_files); + + /* rename the transfers that we have that already have a file */ + if(!ixfr_rename_files(zone, zfile, dest_num_files)) + return; + + /* write the transfers that are not written yet */ + ixfr_write_files(zone, zfile); +} + +/* skip whitespace */ +static char* skipwhite(char* str) +{ + while(isspace((unsigned char)*str)) + str++; + return str; +} + +/* read one RR from file */ +static int ixfr_data_readrr(struct zone* zone, FILE* in, const char* ixfrfile, + struct region* tempregion, struct domain_table* temptable, + struct zone* tempzone, struct rr** rr) +{ + char line[65536]; + char* str; + struct domain* domain_parsed = NULL; + int num_rrs = 0; + line[sizeof(line)-1]=0; + while(!feof(in)) { + if(!fgets(line, sizeof(line), in)) { + if(errno == 0) { + log_msg(LOG_ERR, "zone %s IXFR data %s: " + "unexpected end of file", zone->opts->name, ixfrfile); + return 0; + } + log_msg(LOG_ERR, "zone %s IXFR data %s: " + "cannot read: %s", zone->opts->name, ixfrfile, + strerror(errno)); + return 0; + } + str = skipwhite(line); + if(str[0] == 0) { + /* empty line */ + continue; + } + if(str[0] == ';') { + /* comment line */ + continue; + } + if(zonec_parse_string(tempregion, temptable, tempzone, + line, &domain_parsed, &num_rrs)) { + log_msg(LOG_ERR, "zone %s IXFR data %s: parse error", + zone->opts->name, ixfrfile); + return 0; + } + if(num_rrs != 1) { + log_msg(LOG_ERR, "zone %s IXFR data %s: parse error", + zone->opts->name, ixfrfile); + return 0; + } + *rr = &domain_parsed->rrsets->rrs[0]; + return 1; + } + log_msg(LOG_ERR, "zone %s IXFR data %s: file too short, no newsoa", + zone->opts->name, ixfrfile); + return 0; +} + +/* delete from domain table */ +static void domain_table_delete(struct domain_table* table, + struct domain* domain) +{ +#ifdef USE_RADIX_TREE + radix_delete(table->nametree, domain->rnode); +#else + rbtree_delete(table->names_to_domains, domain->node.key); +#endif +} + +/* can we delete temp domain */ +static int can_del_temp_domain(struct domain* domain) +{ + struct domain* n; + /* we want to keep the zone apex */ + if(domain->is_apex) + return 0; + if(domain->rrsets) + return 0; + if(domain->usage) + return 0; + /* check if there are domains under it */ + n = domain_next(domain); + if(n && domain_is_subdomain(n, domain)) + return 0; + return 1; +} + +/* delete temporary domain */ +static void ixfr_temp_deldomain(struct domain_table* temptable, + struct domain* domain) +{ + struct domain* p; + if(!can_del_temp_domain(domain)) + return; + p = domain->parent; + /* see if this domain is someones wildcard-child-closest-match, + * which can only be the parent, and then it should use the + * one-smaller than this domain as closest-match. */ + if(domain->parent && + domain->parent->wildcard_child_closest_match == domain) + domain->parent->wildcard_child_closest_match = + domain_previous_existing_child(domain); + domain_table_delete(temptable, domain); + while(p) { + struct domain* up = p->parent; + if(!can_del_temp_domain(p)) + break; + if(p->parent && p->parent->wildcard_child_closest_match == p) + p->parent->wildcard_child_closest_match = + domain_previous_existing_child(p); + domain_table_delete(temptable, p); + p = up; + } +} + +/* clear out the just read RR from the temp table */ +static void clear_temp_table_of_rr(struct domain_table* temptable, + struct zone* tempzone, struct rr* rr) +{ +#if 0 /* clear out by removing everything, alternate for the cleanout code */ + /* clear domains from the tempzone, + * the only domain left is the zone apex and its parents */ + domain_type* domain; +#ifdef USE_RADIX_TREE + struct radnode* first = radix_first(temptable->nametree); + domain = first?(domain_type*)first->elem:NULL; +#else + domain = (domain_type*)rbtree_first(temptable->names_to_domains); +#endif + while(domain != (domain_type*)RBTREE_NULL && domain) { + domain_type* next = domain_next(domain); + if(domain != tempzone->apex && + !domain_is_subdomain(tempzone->apex, domain)) { + domain_table_delete(temptable, domain); + } else { + if(!domain->parent /* is the root */ || + domain == tempzone->apex) + domain->usage = 1; + else domain->usage = 0; + } + domain = next; + } + + if(rr->owner == tempzone->apex) { + tempzone->apex->rrsets = NULL; + tempzone->soa_rrset = NULL; + tempzone->soa_nx_rrset = NULL; + tempzone->ns_rrset = NULL; + } + return; +#endif + + /* clear domains in the rdata */ + unsigned i; + for(i=0; irdata_count; i++) { + if(rdata_atom_is_domain(rr->type, i)) { + /* clear out that dname */ + struct domain* domain = + rdata_atom_domain(rr->rdatas[i]); + domain->usage --; + if(domain != tempzone->apex && domain->usage == 0) + ixfr_temp_deldomain(temptable, domain); + } + } + + /* clear domain_parsed */ + if(rr->owner == tempzone->apex) { + tempzone->apex->rrsets = NULL; + tempzone->soa_rrset = NULL; + tempzone->soa_nx_rrset = NULL; + tempzone->ns_rrset = NULL; + } else { + rr->owner->rrsets = NULL; + if(rr->owner->usage == 0) { + ixfr_temp_deldomain(temptable, rr->owner); + } + } +} + +/* read ixfr data new SOA */ +static int ixfr_data_readnewsoa(struct ixfr_data* data, struct zone* zone, + FILE* in, const char* ixfrfile, struct region* tempregion, + struct domain_table* temptable, struct zone* tempzone, + uint32_t dest_serial) +{ + struct rr* rr; + size_t capacity = 0; + if(!ixfr_data_readrr(zone, in, ixfrfile, tempregion, temptable, + tempzone, &rr)) + return 0; + if(rr->type != TYPE_SOA) { + log_msg(LOG_ERR, "zone %s ixfr data %s: IXFR data does not start with SOA", + zone->opts->name, ixfrfile); + return 0; + } + if(rr->klass != CLASS_IN) { + log_msg(LOG_ERR, "zone %s ixfr data %s: IXFR data is not class IN", + zone->opts->name, ixfrfile); + return 0; + } + if(!zone->apex) { + log_msg(LOG_ERR, "zone %s ixfr data %s: zone has no apex, no zone data", + zone->opts->name, ixfrfile); + return 0; + } + if(dname_compare(domain_dname(zone->apex), domain_dname(rr->owner)) != 0) { + log_msg(LOG_ERR, "zone %s ixfr data %s: IXFR data wrong SOA for zone %s", + zone->opts->name, ixfrfile, domain_to_string(rr->owner)); + return 0; + } + data->newserial = soa_rr_get_serial(rr); + if(data->newserial != dest_serial) { + log_msg(LOG_ERR, "zone %s ixfr data %s: IXFR data contains the wrong version, serial %u but want destination serial %u", + zone->opts->name, ixfrfile, data->newserial, + dest_serial); + return 0; + } + if(!ixfr_putrr(domain_dname(rr->owner), rr->type, rr->klass, rr->ttl, rr->rdatas, rr->rdata_count, &data->newsoa, &data->newsoa_len, &capacity)) { + log_msg(LOG_ERR, "zone %s ixfr data %s: cannot allocate space", + zone->opts->name, ixfrfile); + return 0; + } + clear_temp_table_of_rr(temptable, tempzone, rr); + region_free_all(tempregion); + ixfr_trim_capacity(&data->newsoa, &data->newsoa_len, &capacity); + return 1; +} + +/* read ixfr data old SOA */ +static int ixfr_data_readoldsoa(struct ixfr_data* data, struct zone* zone, + FILE* in, const char* ixfrfile, struct region* tempregion, + struct domain_table* temptable, struct zone* tempzone, + uint32_t* dest_serial) +{ + struct rr* rr; + size_t capacity = 0; + if(!ixfr_data_readrr(zone, in, ixfrfile, tempregion, temptable, + tempzone, &rr)) + return 0; + if(rr->type != TYPE_SOA) { + log_msg(LOG_ERR, "zone %s ixfr data %s: IXFR data 2nd RR is not SOA", + zone->opts->name, ixfrfile); + return 0; + } + if(rr->klass != CLASS_IN) { + log_msg(LOG_ERR, "zone %s ixfr data %s: IXFR data 2ndSOA is not class IN", + zone->opts->name, ixfrfile); + return 0; + } + if(!zone->apex) { + log_msg(LOG_ERR, "zone %s ixfr data %s: zone has no apex, no zone data", + zone->opts->name, ixfrfile); + return 0; + } + if(dname_compare(domain_dname(zone->apex), domain_dname(rr->owner)) != 0) { + log_msg(LOG_ERR, "zone %s ixfr data %s: IXFR data wrong 2nd SOA for zone %s", + zone->opts->name, ixfrfile, domain_to_string(rr->owner)); + return 0; + } + data->oldserial = soa_rr_get_serial(rr); + if(!ixfr_putrr(domain_dname(rr->owner), rr->type, rr->klass, rr->ttl, rr->rdatas, rr->rdata_count, &data->oldsoa, &data->oldsoa_len, &capacity)) { + log_msg(LOG_ERR, "zone %s ixfr data %s: cannot allocate space", + zone->opts->name, ixfrfile); + return 0; + } + clear_temp_table_of_rr(temptable, tempzone, rr); + region_free_all(tempregion); + ixfr_trim_capacity(&data->oldsoa, &data->oldsoa_len, &capacity); + *dest_serial = data->oldserial; + return 1; +} + +/* read ixfr data del section */ +static int ixfr_data_readdel(struct ixfr_data* data, struct zone* zone, + FILE* in, const char* ixfrfile, struct region* tempregion, + struct domain_table* temptable, struct zone* tempzone) +{ + struct rr* rr; + size_t capacity = 0; + while(1) { + if(!ixfr_data_readrr(zone, in, ixfrfile, tempregion, temptable, + tempzone, &rr)) + return 0; + if(!ixfr_putrr(domain_dname(rr->owner), rr->type, rr->klass, rr->ttl, rr->rdatas, rr->rdata_count, &data->del, &data->del_len, &capacity)) { + log_msg(LOG_ERR, "zone %s ixfr data %s: cannot allocate space", + zone->opts->name, ixfrfile); + return 0; + } + /* check SOA and also serial, because there could be other + * add and del sections from older versions collated, we can + * see this del section end when it has the serial */ + if(rr->type == TYPE_SOA && + soa_rr_get_serial(rr) == data->newserial) { + /* end of del section. */ + clear_temp_table_of_rr(temptable, tempzone, rr); + region_free_all(tempregion); + break; + } + clear_temp_table_of_rr(temptable, tempzone, rr); + region_free_all(tempregion); + } + ixfr_trim_capacity(&data->del, &data->del_len, &capacity); + return 1; +} + +/* read ixfr data add section */ +static int ixfr_data_readadd(struct ixfr_data* data, struct zone* zone, + FILE* in, const char* ixfrfile, struct region* tempregion, + struct domain_table* temptable, struct zone* tempzone) +{ + struct rr* rr; + size_t capacity = 0; + while(1) { + if(!ixfr_data_readrr(zone, in, ixfrfile, tempregion, temptable, + tempzone, &rr)) + return 0; + if(!ixfr_putrr(domain_dname(rr->owner), rr->type, rr->klass, rr->ttl, rr->rdatas, rr->rdata_count, &data->add, &data->add_len, &capacity)) { + log_msg(LOG_ERR, "zone %s ixfr data %s: cannot allocate space", + zone->opts->name, ixfrfile); + return 0; + } + if(rr->type == TYPE_SOA && + soa_rr_get_serial(rr) == data->newserial) { + /* end of add section. */ + clear_temp_table_of_rr(temptable, tempzone, rr); + region_free_all(tempregion); + break; + } + clear_temp_table_of_rr(temptable, tempzone, rr); + region_free_all(tempregion); + } + ixfr_trim_capacity(&data->add, &data->add_len, &capacity); + return 1; +} + +/* read ixfr data from file */ +static int ixfr_data_read(struct nsd* nsd, struct zone* zone, FILE* in, + const char* ixfrfile, uint32_t* dest_serial, int file_num) +{ + struct ixfr_data* data = NULL; + struct region* tempregion, *stayregion; + struct domain_table* temptable; + struct zone* tempzone; + + if(zone->ixfr && + zone->ixfr->data->count == zone->opts->pattern->ixfr_number) { + VERBOSITY(3, (LOG_INFO, "zone %s skip %s IXFR data because only %d ixfr-number configured", + zone->opts->name, ixfrfile, (int)zone->opts->pattern->ixfr_number)); + return 0; + } + + /* the file has header comments, new soa, old soa, delsection, + * addsection. The delsection and addsection end in a SOA of oldver + * and newver respectively. */ + data = xalloc_zero(sizeof(*data)); + data->file_num = file_num; + + /* the temp region is cleared after every RR */ + tempregion = region_create(xalloc, free); + /* the stay region holds the temporary data that stays between RRs */ + stayregion = region_create(xalloc, free); + temptable = domain_table_create(stayregion); + tempzone = region_alloc_zero(stayregion, sizeof(zone_type)); + if(!zone->apex) { + ixfr_data_free(data); + region_destroy(tempregion); + region_destroy(stayregion); + return 0; + } + tempzone->apex = domain_table_insert(temptable, + domain_dname(zone->apex)); + temptable->root->usage++; + tempzone->apex->usage++; + tempzone->opts = zone->opts; + /* switch to per RR region for new allocations in temp domain table */ + temptable->region = tempregion; + + if(!ixfr_data_readnewsoa(data, zone, in, ixfrfile, tempregion, + temptable, tempzone, *dest_serial)) { + ixfr_data_free(data); + region_destroy(tempregion); + region_destroy(stayregion); + return 0; + } + if(!ixfr_data_readoldsoa(data, zone, in, ixfrfile, tempregion, + temptable, tempzone, dest_serial)) { + ixfr_data_free(data); + region_destroy(tempregion); + region_destroy(stayregion); + return 0; + } + if(!ixfr_data_readdel(data, zone, in, ixfrfile, tempregion, temptable, + tempzone)) { + ixfr_data_free(data); + region_destroy(tempregion); + region_destroy(stayregion); + return 0; + } + if(!ixfr_data_readadd(data, zone, in, ixfrfile, tempregion, temptable, + tempzone)) { + ixfr_data_free(data); + region_destroy(tempregion); + region_destroy(stayregion); + return 0; + } + + region_destroy(tempregion); + region_destroy(stayregion); + + if(!zone->ixfr) + zone->ixfr = zone_ixfr_create(nsd); + if(zone->opts->pattern->ixfr_size != 0 && + zone->ixfr->total_size + ixfr_data_size(data) > + zone->opts->pattern->ixfr_size) { + VERBOSITY(3, (LOG_INFO, "zone %s skip %s IXFR data because only ixfr-size: %u configured, and it is %u size", + zone->opts->name, ixfrfile, (unsigned)zone->opts->pattern->ixfr_size, (unsigned)ixfr_data_size(data))); + ixfr_data_free(data); + return 0; + } + zone_ixfr_add(zone->ixfr, data, 0); + VERBOSITY(3, (LOG_INFO, "zone %s read %s IXFR data of %u bytes", + zone->opts->name, ixfrfile, (unsigned)ixfr_data_size(data))); + return 1; +} + +/* try to read the next ixfr file. returns false if it fails or if it + * does not fit in the configured sizes */ +static int ixfr_read_one_more_file(struct nsd* nsd, struct zone* zone, + const char* zfile, int num_files, uint32_t *dest_serial) +{ + char ixfrfile[1024+24]; + FILE* in; + int file_num = num_files+1; + make_ixfr_name(ixfrfile, sizeof(ixfrfile), zfile, file_num); + in = fopen(ixfrfile, "r"); + if(!in) { + if(errno == ENOENT) { + /* the file does not exist, we reached the end + * of the list of IXFR files */ + return 0; + } + log_msg(LOG_ERR, "could not read zone %s IXFR file %s: %s", + zone->opts->name, ixfrfile, strerror(errno)); + return 0; + } + warn_if_directory("IXFR data", in, ixfrfile); + if(!ixfr_data_read(nsd, zone, in, ixfrfile, dest_serial, file_num)) { + fclose(in); + return 0; + } + fclose(in); + return 1; +} + +void ixfr_read_from_file(struct nsd* nsd, struct zone* zone, const char* zfile) +{ + uint32_t serial; + int num_files = 0; + /* delete the existing data, the zone data in memory has likely + * changed, eg. due to reading a new zonefile. So that needs new + * IXFRs */ + zone_ixfr_clear(zone->ixfr); + + /* track the serial number that we need to end up with, and check + * that the IXFRs match up and result in the required version */ + serial = zone_get_current_serial(zone); + + while(ixfr_read_one_more_file(nsd, zone, zfile, num_files, &serial)) { + num_files++; + } + if(num_files > 0) { + VERBOSITY(1, (LOG_INFO, "zone %s read %d IXFR transfers with success", + zone->opts->name, num_files)); + } +} diff --git a/usr.sbin/nsd/ixfr.h b/usr.sbin/nsd/ixfr.h new file mode 100644 index 00000000000..0f201038bff --- /dev/null +++ b/usr.sbin/nsd/ixfr.h @@ -0,0 +1,268 @@ +/* + * ixfr.h -- generating IXFR responses. + * + * Copyright (c) 2021, NLnet Labs. All rights reserved. + * + * See LICENSE for the license. + * + */ + +#ifndef _IXFR_H_ +#define _IXFR_H_ +struct nsd; +#include "query.h" +#include "rbtree.h" +struct ixfr_data; +struct zone; +struct buffer; +struct region; + +/* default for number of ixfr versions in files */ +#define IXFR_NUMBER_DEFAULT 5 /* number of versions */ +/* default for IXFR storage */ +#define IXFR_SIZE_DEFAULT 1048576 /* in bytes, 1M */ + +/* data structure that stores IXFR contents for a zone. */ +struct zone_ixfr { + /* Items are of type ixfr_data. The key is old_serial. + * So it can be looked up on an incoming IXFR. They are sorted + * by old_serial, so the looked up and next are the versions needed. + * Tree of ixfr data for versions */ + struct rbtree* data; + /* total size stored at this time, in bytes, + * sum of sizes of the ixfr data elements */ + size_t total_size; + /* the oldest serial number in the tree, searchable by old_serial */ + uint32_t oldest_serial; + /* the newest serial number in the tree, that is searchable in the + * tree, so it is the old_serial of the newest data entry, that + * has an even newer new_serial of that entry */ + uint32_t newest_serial; +}; + +/* Data structure that stores one IXFR. + * The RRs are stored in uncompressed wireformat, that means + * an uncompressed domain name, type, class, TTL, rdatalen, + * uncompressed rdata in wireformat. + * + * The data structure is formatted like this so that making an IXFR + * that moves across several versions can be done by collating the + * pieces precisely from the versions involved. In particular, for + * an IXFR from olddata to newdata, for a combined output: + * newdata.newsoa olddata.oldsoa olddata.del olddata.add + * newdata.del newdata.add + * in sequence should produce a valid, non-condensed, IXFR with multiple + * versions inside. + */ +struct ixfr_data { + /* Node in the rbtree. Key is oldserial */ + struct rbnode node; + /* from what serial the IXFR starts from, the 'old' serial */ + uint32_t oldserial; + /* where to IXFR goes to, the 'new' serial */ + uint32_t newserial; + /* the new SOA record, with newserial */ + uint8_t* newsoa; + /* byte length of the uncompressed wireformat RR in newsoa */ + size_t newsoa_len; + /* the old SOA record, with oldserial */ + uint8_t* oldsoa; + /* byte length of the uncompressed wireformat RR in oldsoa*/ + size_t oldsoa_len; + /* the deleted RRs, ends with the newserial SOA record. + * if the ixfr is collated out multiple versions, then + * this deleted RRs section contains several add and del sections + * for the older versions, and ends with the last del section, + * and the SOA record with the newserial. + * That is everything except the final add section for newserial. */ + uint8_t* del; + /* byte length of the uncompressed wireformat RRs in del */ + size_t del_len; + /* the added RRs, ends with the newserial SOA record. */ + uint8_t* add; + /* byte length of the uncompressed wireformat RRs in add */ + size_t add_len; + /* log string (if not NULL) about where data is from */ + char* log_str; + /* the number of the ixfr. file on disk. If 0, there is no + * file. If 1, it is file ixfr. */ + int file_num; +}; + +/* process queries in IXFR state */ +query_state_type query_ixfr(struct nsd *nsd, struct query *query); + +/* + * While an IXFR is processed, in incoming IXFR that is downloaded by NSD, + * this structure keeps track of how to store the data from it. That data + * can then be used to answer IXFR queries. + * + * The structure keeps track of allocation data for the IXFR records. + * If it is cancelled, that is flagged so storage stops. + */ +struct ixfr_store { + /* the zone info, with options and zone ixfr reference */ + struct zone* zone; + /* are we cancelled, it is not an IXFR, no need to store information + * any more. */ + int cancelled; + /* data has been trimmed and newsoa added */ + int data_trimmed; + /* the ixfr data that we are storing into */ + struct ixfr_data* data; + /* capacity for the delrrs storage, size of ixfr del allocation */ + size_t del_capacity; + /* capacity for the addrrs storage, size of ixfr add allocation */ + size_t add_capacity; +}; + +/* + * Start the storage of the IXFR data from this IXFR. + * If it returns NULL, the IXFR storage stops. On malloc failure, the + * storage is returned NULL, or cancelled if failures happen later on. + * + * When done, the finish routine links the data into the memory for the zone. + * If it turns out to not be used, use the cancel routine. Or the free + * routine if the ixfr_store itself needs to be deleted too, like on error. + * + * zone: the zone structure + * ixfr_store_mem: preallocated by caller, used to allocate the store struct. + * old_serial: the start serial of the IXFR. + * new_serial: the end serial of the IXFR. + * return NULL or a fresh ixfr_store structure for adding records to the + * IXFR with this serial number. The NULL is on error. + */ +struct ixfr_store* ixfr_store_start(struct zone* zone, + struct ixfr_store* ixfr_store_mem, uint32_t old_serial, + uint32_t new_serial); + +/* + * Cancel the ixfr store in progress. The pointer remains valid, no store done. + * ixfr_store: this is set to cancel. + */ +void ixfr_store_cancel(struct ixfr_store* ixfr_store); + +/* + * Free ixfr store structure, it is no longer used. + * ixfr_store: deleted + */ +void ixfr_store_free(struct ixfr_store* ixfr_store); + +/* + * Finish ixfr store processing. Links the data into the zone ixfr data. + * ixfr_store: Data is linked into the zone struct. The ixfr_store is freed. + * nsd: nsd structure for allocation region and global options. + * log_buf: log string for the update. + */ +void ixfr_store_finish(struct ixfr_store* ixfr_store, struct nsd* nsd, + char* log_buf); + +/* finish just the data activities, trim up the storage and append newsoa */ +void ixfr_store_finish_data(struct ixfr_store* ixfr_store); + +/* + * Add the new SOA record to the ixfr store. + * ixfr_store: stores ixfr data that is collected. + * packet: DNS packet that contains the SOA. position restored on function + * exit. + * ttlpos: position, just before the ttl, rdatalen, rdata of the SOA record. + * we do not need to pass the name, because that is the zone name, or + * the type or class of the record, because we already know. + */ +void ixfr_store_add_newsoa(struct ixfr_store* ixfr_store, + struct buffer* packet, size_t ttlpos); + +/* + * Add the old SOA record to the ixfr store. + * ixfr_store: stores ixfr data that is collected. + * ttl: the TTL of the SOA record + * packet: DNS packet that contains the SOA. position restored on function + * exit. + * rrlen: wire rdata length of the SOA. + */ +void ixfr_store_add_oldsoa(struct ixfr_store* ixfr_store, uint32_t ttl, + struct buffer* packet, size_t rrlen); + +void ixfr_store_delrr(struct ixfr_store* ixfr_store, const struct dname* dname, + uint16_t type, uint16_t klass, uint32_t ttl, struct buffer* packet, + uint16_t rrlen, struct region* temp_region); +void ixfr_store_addrr(struct ixfr_store* ixfr_store, const struct dname* dname, + uint16_t type, uint16_t klass, uint32_t ttl, struct buffer* packet, + uint16_t rrlen, struct region* temp_region); +int ixfr_store_addrr_rdatas(struct ixfr_store* ixfr_store, + const struct dname* dname, uint16_t type, uint16_t klass, + uint32_t ttl, rdata_atom_type* rdatas, ssize_t rdata_num); +int ixfr_store_delrr_uncompressed(struct ixfr_store* ixfr_store, + uint8_t* dname, size_t dname_len, uint16_t type, uint16_t klass, + uint32_t ttl, uint8_t* rdata, size_t rdata_len); +int ixfr_store_add_newsoa_rdatas(struct ixfr_store* ixfr_store, + const struct dname* dname, uint16_t type, uint16_t klass, + uint32_t ttl, rdata_atom_type* rdatas, ssize_t rdata_num); +int ixfr_store_oldsoa_uncompressed(struct ixfr_store* ixfr_store, + uint8_t* dname, size_t dname_len, uint16_t type, uint16_t klass, + uint32_t ttl, uint8_t* rdata, size_t rdata_len); + +/* an AXFR has been received, the IXFRs do not connect in version number. + * Delete the unconnected IXFRs from memory */ +void ixfr_store_delixfrs(struct zone* zone); + +/* return if the zone has ixfr storage enabled for it */ +int zone_is_ixfr_enabled(struct zone* zone); + +/* create new zone_ixfr structure */ +struct zone_ixfr* zone_ixfr_create(struct nsd* nsd); + +/* free the zone_ixfr */ +void zone_ixfr_free(struct zone_ixfr* ixfr); + +/* make space to fit in the data */ +void zone_ixfr_make_space(struct zone_ixfr* ixfr, struct zone* zone, + struct ixfr_data* data, struct ixfr_store* ixfr_store); + +/* remove ixfr data from the zone_ixfr */ +void zone_ixfr_remove(struct zone_ixfr* ixfr, struct ixfr_data* data); + +/* add ixfr data to the zone_ixfr */ +void zone_ixfr_add(struct zone_ixfr* ixfr, struct ixfr_data* data, int isnew); + +/* find serial number in ixfr list, or NULL if not found */ +struct ixfr_data* zone_ixfr_find_serial(struct zone_ixfr* ixfr, + uint32_t qserial); + +/* size of the ixfr data */ +size_t ixfr_data_size(struct ixfr_data* data); + +/* write ixfr contents to file for the zone */ +void ixfr_write_to_file(struct zone* zone, const char* zfile); + +/* read ixfr contents from file for the zone */ +void ixfr_read_from_file(struct nsd* nsd, struct zone* zone, const char* zfile); + +/* get the current serial from the zone */ +uint32_t zone_get_current_serial(struct zone* zone); + +/* write the ixfr data to file */ +int ixfr_write_file(struct zone* zone, struct ixfr_data* data, + const char* zfile, int file_num); + +/* see if ixfr file exists */ +int ixfr_file_exists(const char* zfile, int file_num); + +/* rename the ixfr file */ +int ixfr_rename_it(const char* zname, const char* zfile, int oldnum, + int oldtemp, int newnum, int newtemp); + +/* read the file header of an ixfr file and return serial numbers. */ +int ixfr_read_file_header(const char* zname, const char* zfile, + int file_num, uint32_t* oldserial, uint32_t* newserial, + size_t* data_size, int enoent_is_err); + +/* unlink an ixfr file */ +int ixfr_unlink_it(const char* zname, const char* zfile, int file_num, + int silent_enoent); + +/* delete the ixfr files that are too many */ +void ixfr_delete_superfluous_files(struct zone* zone, const char* zfile, + int dest_num_files); + +#endif /* _IXFR_H_ */ diff --git a/usr.sbin/nsd/ixfrcreate.c b/usr.sbin/nsd/ixfrcreate.c new file mode 100644 index 00000000000..4ecf0ccd5c9 --- /dev/null +++ b/usr.sbin/nsd/ixfrcreate.c @@ -0,0 +1,1121 @@ +/* + * ixfrcreate.c -- generating IXFR differences from zone files. + * + * Copyright (c) 2021, NLnet Labs. All rights reserved. + * + * See LICENSE for the license. + * + */ + +#include "config.h" +#include +#include +#include +#include "ixfrcreate.h" +#include "namedb.h" +#include "ixfr.h" +#include "options.h" + +/* spool a uint16_t to file */ +static int spool_u16(FILE* out, uint16_t val) +{ + if(!fwrite(&val, sizeof(val), 1, out)) { + return 0; + } + return 1; +} + +/* spool a uint32_t to file */ +static int spool_u32(FILE* out, uint32_t val) +{ + if(!fwrite(&val, sizeof(val), 1, out)) { + return 0; + } + return 1; +} + +/* spool dname to file */ +static int spool_dname(FILE* out, dname_type* dname) +{ + uint16_t namelen = dname->name_size; + if(!fwrite(&namelen, sizeof(namelen), 1, out)) { + return 0; + } + if(!fwrite(dname_name(dname), namelen, 1, out)) { + return 0; + } + return 1; +} + +/* calculate the rdatalen of an RR */ +static size_t rr_rdatalen_uncompressed(rr_type* rr) +{ + int i; + size_t rdlen_uncompressed = 0; + for(i=0; irdata_count; i++) { + if(rdata_atom_is_domain(rr->type, i)) { + rdlen_uncompressed += domain_dname(rr->rdatas[i].domain) + ->name_size; + } else { + rdlen_uncompressed += rr->rdatas[i].data[0]; + } + } + return rdlen_uncompressed; +} + +/* spool the data for one rr into the file */ +static int spool_rr_data(FILE* out, rr_type* rr) +{ + int i; + uint16_t rdlen; + if(!spool_u32(out, rr->ttl)) + return 0; + rdlen = rr_rdatalen_uncompressed(rr); + if(!spool_u16(out, rdlen)) + return 0; + for(i=0; irdata_count; i++) { + if(rdata_atom_is_domain(rr->type, i)) { + if(!fwrite(dname_name(domain_dname( + rr->rdatas[i].domain)), domain_dname( + rr->rdatas[i].domain)->name_size, 1, out)) + return 0; + } else { + if(!fwrite(&rr->rdatas[i].data[1], + rr->rdatas[i].data[0], 1, out)) + return 0; + } + } + return 1; +} + +/* spool one rrset to file */ +static int spool_rrset(FILE* out, rrset_type* rrset) +{ + int i; + if(rrset->rr_count == 0) + return 1; + if(!spool_u16(out, rrset->rrs[0].type)) + return 0; + if(!spool_u16(out, rrset->rrs[0].klass)) + return 0; + if(!spool_u16(out, rrset->rr_count)) + return 0; + for(i=0; irr_count; i++) { + if(!spool_rr_data(out, &rrset->rrs[i])) + return 0; + } + return 1; +} + +/* spool rrsets to file */ +static int spool_rrsets(FILE* out, rrset_type* rrsets, struct zone* zone) +{ + rrset_type* s; + for(s=rrsets; s; s=s->next) { + if(s->zone != zone) + continue; + if(!spool_rrset(out, s)) { + return 0; + } + } + return 1; +} + +/* count number of rrsets for a domain */ +static size_t domain_count_rrsets(domain_type* domain, zone_type* zone) +{ + rrset_type* s; + size_t count = 0; + for(s=domain->rrsets; s; s=s->next) { + if(s->zone == zone) + count++; + } + return count; +} + +/* spool the domain names to file, each one in turn. end with enddelimiter */ +static int spool_domains(FILE* out, struct zone* zone) +{ + domain_type* domain; + for(domain = zone->apex; domain && domain_is_subdomain(domain, + zone->apex); domain = domain_next(domain)) { + uint32_t count = domain_count_rrsets(domain, zone); + if(count == 0) + continue; + /* write the name */ + if(!spool_dname(out, domain_dname(domain))) + return 0; + if(!spool_u32(out, count)) + return 0; + /* write the rrsets */ + if(!spool_rrsets(out, domain->rrsets, zone)) + return 0; + } + /* the end delimiter is a 0 length. domain names are not zero length */ + if(!spool_u16(out, 0)) + return 0; + return 1; +} + +/* spool the namedb zone to the file. print error on failure. */ +static int spool_zone_to_file(struct zone* zone, char* file_name, + uint32_t serial) +{ + FILE* out; + out = fopen(file_name, "w"); + if(!out) { + log_msg(LOG_ERR, "could not open %s for writing: %s", + file_name, strerror(errno)); + return 0; + } + if(!spool_dname(out, domain_dname(zone->apex))) { + log_msg(LOG_ERR, "could not write %s: %s", + file_name, strerror(errno)); + return 0; + } + if(!spool_u32(out, serial)) { + log_msg(LOG_ERR, "could not write %s: %s", + file_name, strerror(errno)); + return 0; + } + if(!spool_domains(out, zone)) { + log_msg(LOG_ERR, "could not write %s: %s", + file_name, strerror(errno)); + return 0; + } + fclose(out); + return 1; +} + +/* create ixfr spool file name */ +static int create_ixfr_spool_name(struct ixfr_create* ixfrcr, + const char* zfile) +{ + char buf[1024]; + snprintf(buf, sizeof(buf), "%s.spoolzone.%u", zfile, + (unsigned)getpid()); + ixfrcr->file_name = strdup(buf); + if(!ixfrcr->file_name) + return 0; + return 1; +} + +/* start ixfr creation */ +struct ixfr_create* ixfr_create_start(struct zone* zone, const char* zfile, + uint64_t ixfr_size, int errorcmdline) +{ + struct ixfr_create* ixfrcr = (struct ixfr_create*)calloc(1, + sizeof(*ixfrcr)); + if(!ixfrcr) { + log_msg(LOG_ERR, "malloc failure"); + return NULL; + } + ixfrcr->zone_name_len = domain_dname(zone->apex)->name_size; + ixfrcr->zone_name = (uint8_t*)malloc(ixfrcr->zone_name_len); + if(!ixfrcr->zone_name) { + free(ixfrcr); + log_msg(LOG_ERR, "malloc failure"); + return NULL; + } + memmove(ixfrcr->zone_name, dname_name(domain_dname(zone->apex)), + ixfrcr->zone_name_len); + + if(!create_ixfr_spool_name(ixfrcr, zfile)) { + ixfr_create_free(ixfrcr); + log_msg(LOG_ERR, "malloc failure"); + return NULL; + } + ixfrcr->old_serial = zone_get_current_serial(zone); + if(!spool_zone_to_file(zone, ixfrcr->file_name, ixfrcr->old_serial)) { + ixfr_create_free(ixfrcr); + return NULL; + } + if(zone->opts && zone->opts->pattern) + ixfrcr->max_size = (size_t)zone->opts->pattern->ixfr_size; + else ixfrcr->max_size = (size_t)ixfr_size; + ixfrcr->errorcmdline = errorcmdline; + return ixfrcr; +} + +/* free ixfr create */ +void ixfr_create_free(struct ixfr_create* ixfrcr) +{ + if(!ixfrcr) + return; + free(ixfrcr->file_name); + free(ixfrcr->zone_name); + free(ixfrcr); +} + +/* read uint16_t from spool */ +static int read_spool_u16(FILE* spool, uint16_t* val) +{ + if(!fread(val, sizeof(*val), 1, spool)) + return 0; + return 1; +} + +/* read uint32_t from spool */ +static int read_spool_u32(FILE* spool, uint32_t* val) +{ + if(!fread(val, sizeof(*val), 1, spool)) + return 0; + return 1; +} + +/* read dname from spool */ +static int read_spool_dname(FILE* spool, uint8_t* buf, size_t buflen, + size_t* dname_len) +{ + uint16_t len; + if(!fread(&len, sizeof(len), 1, spool)) + return 0; + if(len > buflen) { + log_msg(LOG_ERR, "dname too long"); + return 0; + } + if(len > 0) { + if(!fread(buf, len, 1, spool)) + return 0; + } + *dname_len = len; + return 1; +} + +/* read and check the spool file header */ +static int read_spool_header(FILE* spool, struct ixfr_create* ixfrcr) +{ + uint8_t dname[MAXDOMAINLEN+1]; + size_t dname_len; + uint32_t serial; + /* read apex */ + if(!read_spool_dname(spool, dname, sizeof(dname), &dname_len)) { + log_msg(LOG_ERR, "error reading file %s: %s", + ixfrcr->file_name, strerror(errno)); + return 0; + } + /* read serial */ + if(!read_spool_u32(spool, &serial)) { + log_msg(LOG_ERR, "error reading file %s: %s", + ixfrcr->file_name, strerror(errno)); + return 0; + } + + /* check */ + if(ixfrcr->zone_name_len != dname_len || + memcmp(ixfrcr->zone_name, dname, ixfrcr->zone_name_len) != 0) { + log_msg(LOG_ERR, "error file %s does not contain the correct zone apex", + ixfrcr->file_name); + return 0; + } + if(ixfrcr->old_serial != serial) { + log_msg(LOG_ERR, "error file %s does not contain the correct zone serial", + ixfrcr->file_name); + return 0; + } + return 1; +} + +/* store the old soa record when we encounter it on the spool */ +static int process_store_oldsoa(struct ixfr_store* store, uint8_t* dname, + size_t dname_len, uint16_t tp, uint16_t kl, uint32_t ttl, uint8_t* buf, + uint16_t rdlen) +{ + if(store->data->oldsoa) { + log_msg(LOG_ERR, "error spool contains multiple SOA records"); + return 0; + } + if(!ixfr_store_oldsoa_uncompressed(store, dname, dname_len, tp, kl, + ttl, buf, rdlen)) { + log_msg(LOG_ERR, "out of memory"); + return 0; + } + return 1; +} + +/* see if rdata matches, true if equal */ +static int rdata_match(struct rr* rr, uint8_t* rdata, uint16_t rdlen) +{ + size_t rdpos = 0; + int i; + for(i=0; irdata_count; i++) { + if(rdata_atom_is_domain(rr->type, i)) { + if(rdpos + domain_dname(rr->rdatas[i].domain)->name_size + > rdlen) + return 0; + if(memcmp(rdata+rdpos, + dname_name(domain_dname(rr->rdatas[i].domain)), + domain_dname(rr->rdatas[i].domain)->name_size) + != 0) + return 0; + rdpos += domain_dname(rr->rdatas[i].domain)->name_size; + } else { + if(rdpos + rr->rdatas[i].data[0] > rdlen) + return 0; + if(memcmp(rdata+rdpos, &rr->rdatas[i].data[1], + rr->rdatas[i].data[0]) != 0) + return 0; + rdpos += rr->rdatas[i].data[0]; + } + } + if(rdpos != rdlen) + return 0; + return 1; +} + +/* find an rdata in an rrset, true if found and sets index found */ +static int rrset_find_rdata(struct rrset* rrset, uint32_t ttl, uint8_t* rdata, + uint16_t rdlen, uint16_t* index) +{ + int i; + for(i=0; irr_count; i++) { + if(rrset->rrs[i].ttl != ttl) + continue; + if(rdata_match(&rrset->rrs[i], rdata, rdlen)) { + *index = i; + return 1; + } + } + return 0; +} + +/* sort comparison for uint16 elements */ +static int sort_uint16(const void* x, const void* y) +{ + const uint16_t* ax = (const uint16_t*)x; + const uint16_t* ay = (const uint16_t*)y; + if(*ax < *ay) + return -1; + if(*ax > *ay) + return 1; + return 0; +} + +/* spool read an rrset, it is a deleted RRset */ +static int process_diff_rrset(FILE* spool, struct ixfr_create* ixfrcr, + struct ixfr_store* store, struct domain* domain, + uint16_t tp, uint16_t kl, uint16_t rrcount, struct rrset* rrset) +{ + /* read RRs from file and see if they are added, deleted or in both */ + uint8_t buf[MAX_RDLENGTH]; + uint16_t marked[65536]; + size_t marked_num = 0, atmarked; + int i; + for(i=0; ifile_name, strerror(errno)); + return 0; + } + /* because rdlen is uint16_t always smaller than sizeof(buf)*/ + if(!fread(buf, rdlen, 1, spool)) { + log_msg(LOG_ERR, "error reading file %s: %s", + ixfrcr->file_name, strerror(errno)); + return 0; + } + if(tp == TYPE_SOA) { + if(!process_store_oldsoa(store, + (void*)dname_name(domain_dname(domain)), + domain_dname(domain)->name_size, tp, kl, ttl, + buf, rdlen)) + return 0; + } + /* see if the rr is in the RRset */ + if(rrset_find_rdata(rrset, ttl, buf, rdlen, &index)) { + /* it is in both, mark it */ + marked[marked_num++] = index; + } else { + /* not in new rrset, but only on spool, it is + * a deleted RR */ + if(!ixfr_store_delrr_uncompressed(store, + (void*)dname_name(domain_dname(domain)), + domain_dname(domain)->name_size, + tp, kl, ttl, buf, rdlen)) { + log_msg(LOG_ERR, "out of memory"); + return 0; + } + } + } + /* now that we are done, see if RRs in the rrset are not marked, + * and thus are new rrs that are added */ + qsort(marked, marked_num, sizeof(marked[0]), &sort_uint16); + atmarked = 0; + for(i=0; irr_count; i++) { + if(atmarked < marked_num && marked[atmarked] == i) { + /* the item is in the marked list, skip it */ + atmarked++; + continue; + } + /* not in the marked list, the RR is added */ + if(!ixfr_store_addrr_rdatas(store, domain_dname(domain), + rrset->rrs[i].type, rrset->rrs[i].klass, + rrset->rrs[i].ttl, rrset->rrs[i].rdatas, + rrset->rrs[i].rdata_count)) { + log_msg(LOG_ERR, "out of memory"); + return 0; + } + } + return 1; +} + +/* spool read an rrset, it is a deleted RRset */ +static int process_spool_delrrset(FILE* spool, struct ixfr_create* ixfrcr, + struct ixfr_store* store, uint8_t* dname, size_t dname_len, + uint16_t tp, uint16_t kl, uint16_t rrcount) +{ + /* read the RRs from file and add to del list. */ + uint8_t buf[MAX_RDLENGTH]; + int i; + for(i=0; ifile_name, strerror(errno)); + return 0; + } + /* because rdlen is uint16_t always smaller than sizeof(buf)*/ + if(!fread(buf, rdlen, 1, spool)) { + log_msg(LOG_ERR, "error reading file %s: %s", + ixfrcr->file_name, strerror(errno)); + return 0; + } + if(tp == TYPE_SOA) { + if(!process_store_oldsoa(store, dname, dname_len, + tp, kl, ttl, buf, rdlen)) + return 0; + } + if(!ixfr_store_delrr_uncompressed(store, dname, dname_len, tp, + kl, ttl, buf, rdlen)) { + log_msg(LOG_ERR, "out of memory"); + return 0; + } + } + return 1; +} + +/* add the rrset to the added list */ +static int process_add_rrset(struct ixfr_store* ixfr_store, + struct domain* domain, struct rrset* rrset) +{ + int i; + for(i=0; irr_count; i++) { + if(!ixfr_store_addrr_rdatas(ixfr_store, domain_dname(domain), + rrset->rrs[i].type, rrset->rrs[i].klass, + rrset->rrs[i].ttl, rrset->rrs[i].rdatas, + rrset->rrs[i].rdata_count)) { + log_msg(LOG_ERR, "out of memory"); + return 0; + } + } + return 1; +} + +/* add the RR types that are not in the marktypes list from the new zone */ +static int process_marktypes(struct ixfr_store* store, struct zone* zone, + struct domain* domain, uint16_t* marktypes, size_t marktypes_used) +{ + /* walk through the rrsets in the zone, if it is not in the + * marktypes list, then it is new and an added RRset */ + rrset_type* s; + size_t atmarktype = 0; + qsort(marktypes, marktypes_used, sizeof(marktypes[0]), &sort_uint16); + for(s=domain->rrsets; s; s=s->next) { + uint16_t tp; + if(s->zone != zone) + continue; + tp = rrset_rrtype(s); + if(atmarktype < marktypes_used && marktypes[atmarktype]==tp) { + /* the item is in the marked list, skip it */ + atmarktype++; + continue; + } + if(!process_add_rrset(store, domain, s)) + return 0; + } + return 1; +} + +/* check the difference between the domain and RRs from spool */ +static int process_diff_domain(FILE* spool, struct ixfr_create* ixfrcr, + struct ixfr_store* store, struct zone* zone, struct domain* domain) +{ + /* Read the RR types from spool. Mark off the ones seen, + * later, the notseen ones from the new zone are added RRsets. + * For the ones not in the new zone, they are deleted RRsets. + * If they exist in old and new, check for RR differences. */ + uint32_t spool_type_count, i; + uint16_t marktypes[65536]; + size_t marktypes_used = 0; + if(!read_spool_u32(spool, &spool_type_count)) { + log_msg(LOG_ERR, "error reading file %s: %s", + ixfrcr->file_name, strerror(errno)); + return 0; + } + for(i=0; ifile_name, strerror(errno)); + return 0; + } + rrset = domain_find_rrset(domain, zone, tp); + if(!rrset) { + /* rrset in spool but not in new zone, deleted RRset */ + if(!process_spool_delrrset(spool, ixfrcr, store, + (void*)dname_name(domain_dname(domain)), + domain_dname(domain)->name_size, tp, kl, + rrcount)) + return 0; + } else { + /* add to the marked types, this one is present in + * spool */ + marktypes[marktypes_used++] = tp; + /* rrset in old and in new zone, diff the RRset */ + if(!process_diff_rrset(spool, ixfrcr, store, domain, + tp, kl, rrcount, rrset)) + return 0; + } + } + /* process markoff to see if new zone has RRsets not in spool, + * those are added RRsets. */ + if(!process_marktypes(store, zone, domain, marktypes, marktypes_used)) + return 0; + return 1; +} + +/* add the RRs for the domain in new zone */ +static int process_domain_add_RRs(struct ixfr_store* store, struct zone* zone, + struct domain* domain) +{ + rrset_type* s; + for(s=domain->rrsets; s; s=s->next) { + if(s->zone != zone) + continue; + if(!process_add_rrset(store, domain, s)) + return 0; + } + return 1; +} + +/* del the RRs for the domain from the spool */ +static int process_domain_del_RRs(struct ixfr_create* ixfrcr, + struct ixfr_store* store, FILE* spool, uint8_t* dname, + size_t dname_len) +{ + uint32_t spool_type_count, i; + if(!read_spool_u32(spool, &spool_type_count)) { + log_msg(LOG_ERR, "error reading file %s: %s", + ixfrcr->file_name, strerror(errno)); + return 0; + } + for(i=0; ifile_name, strerror(errno)); + return 0; + } + if(!process_spool_delrrset(spool, ixfrcr, store, dname, + dname_len, tp, kl, rrcount)) + return 0; + } + return 1; +} + +/* init the spool dname iterator */ +static void spool_dname_iter_init(struct spool_dname_iterator* iter, + FILE* spool, char* file_name) +{ + memset(iter, 0, sizeof(*iter)); + iter->spool = spool; + iter->file_name = file_name; +} + +/* read the dname element into the buffer for the spool dname iterator */ +static int spool_dname_iter_read(struct spool_dname_iterator* iter) +{ + if(!read_spool_dname(iter->spool, iter->dname, sizeof(iter->dname), + &iter->dname_len)) { + log_msg(LOG_ERR, "error reading file %s: %s", + iter->file_name, strerror(errno)); + return 0; + } + return 1; +} + +/* get the next name to operate on, that is not processed yet, 0 on failure + * returns okay on endoffile, check with eof for that. + * when done with an element, set iter->is_processed on the element. */ +static int spool_dname_iter_next(struct spool_dname_iterator* iter) +{ + if(iter->eof) + return 1; + if(!iter->read_first) { + /* read the first one */ + if(!spool_dname_iter_read(iter)) + return 0; + if(iter->dname_len == 0) + iter->eof = 1; + iter->read_first = 1; + iter->is_processed = 0; + } + if(!iter->is_processed) { + /* the current one needs processing */ + return 1; + } + /* read the next one */ + if(!spool_dname_iter_read(iter)) + return 0; + if(iter->dname_len == 0) + iter->eof = 1; + iter->is_processed = 0; + return 1; +} + +/* check if the ixfr is too large */ +static int ixfr_create_too_large(struct ixfr_create* ixfrcr, + struct ixfr_store* store) +{ + if(store->cancelled) + return 1; + if(ixfrcr->max_size != 0 && + ixfr_data_size(store->data) > ixfrcr->max_size) { + if(ixfrcr->errorcmdline) { + log_msg(LOG_ERR, "the ixfr for %s exceeds size %u, it is not created", + wiredname2str(ixfrcr->zone_name), + (unsigned)ixfrcr->max_size); + } else { + VERBOSITY(2, (LOG_INFO, "the ixfr for %s exceeds size %u, it is not created", + wiredname2str(ixfrcr->zone_name), + (unsigned)ixfrcr->max_size)); + } + ixfr_store_cancel(store); + return 1; + } + return 0; +} + +/* process the spool input before the domain */ +static int process_spool_before_domain(FILE* spool, struct ixfr_create* ixfrcr, + struct ixfr_store* store, struct domain* domain, + struct spool_dname_iterator* iter, struct region* tmp_region) +{ + const dname_type* dname; + if(ixfr_create_too_large(ixfrcr, store)) + return 0; + /* read the domains and rrsets before the domain and those are from + * the old zone. If the domain is equal, return to have that processed + * if we bypass, that means the domain does not exist, do that */ + while(!iter->eof) { + if(!spool_dname_iter_next(iter)) + return 0; + if(iter->eof) + break; + /* see if we are at, before or after the domain */ + dname = dname_make(tmp_region, iter->dname, 1); + if(!dname) { + log_msg(LOG_ERR, "error in dname in %s", + iter->file_name); + return 0; + } + if(dname_compare(dname, domain_dname(domain)) < 0) { + /* the dname is smaller than the one from the zone. + * it must be deleted, process it */ + if(!process_domain_del_RRs(ixfrcr, store, spool, + iter->dname, iter->dname_len)) + return 0; + iter->is_processed = 1; + } else { + /* we are at or after the domain we are looking for, + * done here */ + return 1; + } + if(ixfr_create_too_large(ixfrcr, store)) + return 0; + } + /* no more domains on spool, done here */ + return 1; +} + +/* process the spool input for the domain */ +static int process_spool_for_domain(FILE* spool, struct ixfr_create* ixfrcr, + struct ixfr_store* store, struct zone* zone, struct domain* domain, + struct spool_dname_iterator* iter, struct region* tmp_region) +{ + /* process all the spool that is not the domain, that is before the + * domain in the new zone */ + if(!process_spool_before_domain(spool, ixfrcr, store, domain, iter, + tmp_region)) + return 0; + + if(ixfr_create_too_large(ixfrcr, store)) + return 0; + /* are we at the correct domain now? */ + if(iter->eof || iter->dname_len != domain_dname(domain)->name_size || + memcmp(iter->dname, dname_name(domain_dname(domain)), + iter->dname_len) != 0) { + /* the domain from the new zone is not present in the old zone, + * the content is in the added RRs set */ + if(!process_domain_add_RRs(store, zone, domain)) + return 0; + return 1; + } + + /* process the domain */ + /* the domain exists both in the old and new zone, + * check for RR differences */ + if(!process_diff_domain(spool, ixfrcr, store, zone, domain)) + return 0; + iter->is_processed = 1; + + return 1; +} + +/* process remaining spool items */ +static int process_spool_remaining(FILE* spool, struct ixfr_create* ixfrcr, + struct ixfr_store* store, struct spool_dname_iterator* iter) +{ + /* the remaining domain names in the spool file, that is after + * the last domain in the new zone. */ + if(ixfr_create_too_large(ixfrcr, store)) + return 0; + while(!iter->eof) { + if(!spool_dname_iter_next(iter)) + return 0; + if(iter->eof) + break; + /* the domain only exists in the spool, the old zone, + * and not in the new zone. That would be domains + * after the new zone domains, or there are no new + * zone domains */ + if(!process_domain_del_RRs(ixfrcr, store, spool, iter->dname, + iter->dname_len)) + return 0; + iter->is_processed = 1; + if(ixfr_create_too_large(ixfrcr, store)) + return 0; + } + return 1; +} + +/* walk through the zone and find the differences */ +static int ixfr_create_walk_zone(FILE* spool, struct ixfr_create* ixfrcr, + struct ixfr_store* store, struct zone* zone) +{ + struct domain* domain; + struct spool_dname_iterator iter; + struct region* tmp_region; + spool_dname_iter_init(&iter, spool, ixfrcr->file_name); + tmp_region = region_create(xalloc, free); + for(domain = zone->apex; domain && domain_is_subdomain(domain, + zone->apex); domain = domain_next(domain)) { + uint32_t count = domain_count_rrsets(domain, zone); + if(count == 0) + continue; + + /* the domain is a domain in the new zone */ + if(!process_spool_for_domain(spool, ixfrcr, store, zone, + domain, &iter, tmp_region)) { + region_destroy(tmp_region); + return 0; + } + region_free_all(tmp_region); + if(ixfr_create_too_large(ixfrcr, store)) + return 0; + } + if(!process_spool_remaining(spool, ixfrcr, store, &iter)) { + region_destroy(tmp_region); + return 0; + } + region_destroy(tmp_region); + return 1; +} + +/* see if the ixfr has already been created by reading the file header + * of the to-be-created file, if that file already exists */ +static int ixfr_create_already_done_serial(struct zone* zone, + const char* zfile, int checknew, uint32_t old_serial, + uint32_t new_serial) +{ + uint32_t file_oldserial = 0, file_newserial = 0; + size_t data_size = 0; + if(!ixfr_read_file_header(zone->opts->name, zfile, 1, &file_oldserial, + &file_newserial, &data_size, 0)) { + /* could not read, so it was not done */ + return 0; + } + if(file_oldserial == old_serial && + (!checknew || file_newserial == new_serial)) { + log_msg(LOG_INFO, "IXFR already exists in file %s.ixfr, nothing to do", + zfile); + return 1; + } + return 0; +} + +/* See the data size of the ixfr by reading the file header of the ixfr file */ +static int ixfr_read_header_data_size(const char* zname, + const char* zfile, int file_num, size_t* data_size) +{ + uint32_t file_oldserial = 0, file_newserial = 0; + if(!ixfr_read_file_header(zname, zfile, file_num, &file_oldserial, + &file_newserial, data_size, 0)) { + /* could not read */ + return 0; + } + return 1; +} + +/* see if the ixfr has already been created by reading the file header + * of the to-be-created file, if that file already exists */ +static int ixfr_create_already_done(struct ixfr_create* ixfrcr, + struct zone* zone, const char* zfile, int checknew) +{ + return ixfr_create_already_done_serial(zone, zfile, checknew, + ixfrcr->old_serial, ixfrcr->new_serial); +} + +/* store the new soa record for the ixfr */ +static int ixfr_create_store_newsoa(struct ixfr_store* store, + struct zone* zone) +{ + if(!zone || !zone->soa_rrset) { + log_msg(LOG_ERR, "error no SOA rrset"); + return 0; + } + if(zone->soa_rrset->rr_count == 0) { + log_msg(LOG_ERR, "error empty SOA rrset"); + return 0; + } + if(!ixfr_store_add_newsoa_rdatas(store, domain_dname(zone->apex), + zone->soa_rrset->rrs[0].type, zone->soa_rrset->rrs[0].klass, + zone->soa_rrset->rrs[0].ttl, zone->soa_rrset->rrs[0].rdatas, + zone->soa_rrset->rrs[0].rdata_count)) { + log_msg(LOG_ERR, "out of memory"); + return 0; + } + return 1; +} + +/* initialise ixfr_create perform, open spool, read header, get serial */ +static int ixfr_perform_init(struct ixfr_create* ixfrcr, struct zone* zone, + struct ixfr_store* store_mem, struct ixfr_store** store, FILE** spool) +{ + *spool = fopen(ixfrcr->file_name, "r"); + if(!*spool) { + log_msg(LOG_ERR, "could not open %s for reading: %s", + ixfrcr->file_name, strerror(errno)); + return 0; + } + if(!read_spool_header(*spool, ixfrcr)) { + fclose(*spool); + return 0; + } + ixfrcr->new_serial = zone_get_current_serial(zone); + *store = ixfr_store_start(zone, store_mem, ixfrcr->old_serial, + ixfrcr->new_serial); + if(!ixfr_create_store_newsoa(*store, zone)) { + fclose(*spool); + ixfr_store_free(*store); + return 0; + } + return 1; +} + +/* rename the other ixfr files */ +static int ixfr_create_rename_and_delete_files(const char* zname, + const char* zoptsname, const char* zfile, uint32_t ixfr_number, + size_t ixfr_size, size_t cur_data_size) +{ + size_t size_in_use = cur_data_size; + int dest_nr_files = (int)ixfr_number, maxsizehit = 0; + int num = 1; + while(ixfr_file_exists(zfile, num)) { + size_t fsize = 0; + if(!maxsizehit) { + if(!ixfr_read_header_data_size(zoptsname, zfile, num, + &fsize) || size_in_use + fsize > ixfr_size) { + /* no more than this because of storage size */ + dest_nr_files = num; + maxsizehit = 1; + } + size_in_use += fsize; + } + num++; + } + num--; + /* num is now the number of ixfr files that exist */ + while(num > 0) { + if(num+1 > dest_nr_files) { + (void)ixfr_unlink_it(zname, zfile, num, 0); + } else { + if(!ixfr_rename_it(zname, zfile, num, 0, num+1, 0)) + return 0; + } + num--; + } + return 1; +} + +/* finish up ixfr create processing */ +static void ixfr_create_finishup(struct ixfr_create* ixfrcr, + struct ixfr_store* store, struct zone* zone, int append_mem, + struct nsd* nsd, const char* zfile, uint32_t ixfr_number) +{ + char log_buf[1024], nowstr[128]; + /* create the log message */ + time_t now = time(NULL); + if(store->cancelled || ixfr_create_too_large(ixfrcr, store)) { + /* remove unneeded files. + * since this ixfr cannot be created the others are useless. */ + ixfr_delete_superfluous_files(zone, zfile, 0); + return; + } + snprintf(nowstr, sizeof(nowstr), "%s", ctime(&now)); + if(strchr(nowstr, '\n')) + *strchr(nowstr, '\n') = 0; + snprintf(log_buf, sizeof(log_buf), + "IXFR created by NSD %s for %s %u to %u of %u bytes at time %s", + PACKAGE_VERSION, wiredname2str(ixfrcr->zone_name), + (unsigned)ixfrcr->old_serial, (unsigned)ixfrcr->new_serial, + (unsigned)ixfr_data_size(store->data), nowstr); + store->data->log_str = strdup(log_buf); + if(!store->data->log_str) { + log_msg(LOG_ERR, "out of memory"); + ixfr_store_free(store); + return; + } + if(!ixfr_create_rename_and_delete_files( + wiredname2str(ixfrcr->zone_name), zone->opts->name, zfile, + ixfr_number, ixfrcr->max_size, ixfr_data_size(store->data))) { + log_msg(LOG_ERR, "could not rename other ixfr files"); + ixfr_store_free(store); + return; + } + if(!ixfr_write_file(zone, store->data, zfile, 1)) { + log_msg(LOG_ERR, "could not write to file"); + ixfr_store_free(store); + return; + } + if(append_mem) { + ixfr_store_finish(store, nsd, log_buf); + } +} + +void ixfr_readup_exist(struct zone* zone, struct nsd* nsd, + const char* zfile) +{ + /* the .ixfr file already exists with the correct serial numbers + * on the disk. Read up the ixfr files from the drive and put them + * in memory. To match the zone that has just been read. + * We can skip ixfr creation, and read up the files from the drive. + * If the files on the drive are consistent, we end up with exactly + * those ixfrs and that zone in memory. + * Presumably, the user has used nsd-checkzone to create an IXFR + * file and has put a new zone file, so we read up the data that + * we should have now. + * This also takes into account the config on number and size. */ + ixfr_read_from_file(nsd, zone, zfile); +} + +int ixfr_create_perform(struct ixfr_create* ixfrcr, struct zone* zone, + int append_mem, struct nsd* nsd, const char* zfile, + uint32_t ixfr_number) +{ + struct ixfr_store store_mem, *store; + FILE* spool; + if(!ixfr_perform_init(ixfrcr, zone, &store_mem, &store, &spool)) { + (void)unlink(ixfrcr->file_name); + return 0; + } + if(ixfrcr->new_serial == ixfrcr->old_serial || + compare_serial(ixfrcr->new_serial, ixfrcr->old_serial)<0) { + log_msg(LOG_ERR, "zone %s ixfr could not be created because the serial is the same or moves backwards, from %u to %u", + wiredname2str(ixfrcr->zone_name), + (unsigned)ixfrcr->old_serial, + (unsigned)ixfrcr->new_serial); + ixfr_store_cancel(store); + fclose(spool); + ixfr_store_free(store); + (void)unlink(ixfrcr->file_name); + ixfr_delete_superfluous_files(zone, zfile, 0); + if(append_mem) + ixfr_store_delixfrs(zone); + return 0; + } + if(ixfr_create_already_done(ixfrcr, zone, zfile, 1)) { + ixfr_store_cancel(store); + fclose(spool); + ixfr_store_free(store); + (void)unlink(ixfrcr->file_name); + if(append_mem) { + ixfr_readup_exist(zone, nsd, zfile); + } + return 0; + } + + if(!ixfr_create_walk_zone(spool, ixfrcr, store, zone)) { + fclose(spool); + ixfr_store_free(store); + (void)unlink(ixfrcr->file_name); + ixfr_delete_superfluous_files(zone, zfile, 0); + return 0; + } + if(store->data && !store->data->oldsoa) { + log_msg(LOG_ERR, "error spool file did not contain a SOA record"); + fclose(spool); + ixfr_store_free(store); + (void)unlink(ixfrcr->file_name); + return 0; + } + if(!store->cancelled) + ixfr_store_finish_data(store); + fclose(spool); + (void)unlink(ixfrcr->file_name); + + ixfr_create_finishup(ixfrcr, store, zone, append_mem, nsd, zfile, + ixfr_number); + return 1; +} + +void ixfr_create_cancel(struct ixfr_create* ixfrcr) +{ + if(!ixfrcr) + return; + (void)unlink(ixfrcr->file_name); + ixfr_create_free(ixfrcr); +} + +int ixfr_create_from_difference(struct zone* zone, const char* zfile, + int* ixfr_create_already_done_flag) +{ + uint32_t old_serial; + *ixfr_create_already_done_flag = 0; + /* only if the zone is ixfr enabled */ + if(!zone_is_ixfr_enabled(zone)) + return 0; + /* only if ixfr create is enabled */ + if(!zone->opts->pattern->create_ixfr) + return 0; + /* only if there is a zone in memory to compare with */ + if(!zone || !zone->soa_rrset || !zone->apex) + return 0; + + old_serial = zone_get_current_serial(zone); + if(ixfr_create_already_done_serial(zone, zfile, 0, old_serial, 0)) { + *ixfr_create_already_done_flag = 1; + return 0; + } + + return 1; +} diff --git a/usr.sbin/nsd/ixfrcreate.h b/usr.sbin/nsd/ixfrcreate.h new file mode 100644 index 00000000000..c09e0b1b745 --- /dev/null +++ b/usr.sbin/nsd/ixfrcreate.h @@ -0,0 +1,86 @@ +/* + * ixfrcreate.h -- generating IXFR differences from zonefiles. + * + * Copyright (c) 2021, NLnet Labs. All rights reserved. + * + * See LICENSE for the license. + * + */ + +#ifndef _IXFRCREATE_H_ +#define _IXFRCREATE_H_ +#include "dns.h" +struct zone; +struct nsd; + +/* the ixfr create data structure while the ixfr difference from zone files + * is created. */ +struct ixfr_create { + /* the old serial and new serial */ + uint32_t old_serial, new_serial; + /* the file with the spooled old zone data */ + char* file_name; + /* zone name in uncompressed wireformat */ + uint8_t* zone_name; + /* length of zone name */ + size_t zone_name_len; + /* max size of ixfr in bytes */ + size_t max_size; + /* we are in checkzone, errors should go to the console, not to the + * serverlog */ + int errorcmdline; +}; + +/* start ixfr creation */ +struct ixfr_create* ixfr_create_start(struct zone* zone, const char* zfile, + uint64_t ixfr_size, int errorcmdline); + +/* free ixfr create */ +void ixfr_create_free(struct ixfr_create* ixfrcr); + +/* create the IXFR from differences. The old zone is spooled to file + * and the new zone is in memory now. + * With append_mem it does not only write to file but sticks it into the + * memory lookup structure for IXFRs used by the server. */ +int ixfr_create_perform(struct ixfr_create* ixfrcr, struct zone* zone, + int append_mem, struct nsd* nsd, const char* zfile, + uint32_t ixfr_number); + +/* cancel ixfrcreation, that was started, but not performed yet. + * It removes the temporary file. */ +void ixfr_create_cancel(struct ixfr_create* ixfrcr); + +/* returns true if ixfr should be created by taking difference between + * zone file contents. Also checks if ixfr is enabled for the zone. */ +int ixfr_create_from_difference(struct zone* zone, const char* zfile, + int* ixfr_create_already_done_flag); + +/* readup existing file if it already exists */ +void ixfr_readup_exist(struct zone* zone, struct nsd* nsd, const char* zfile); + +/* + * Structure to keep track of spool domain name iterator. + * This reads from the spool file and steps over the domain name + * elements one by one. It keeps track of: is the first one read yet, + * are we at end nothing more, is the element processed yet that is + * current read into the buffer? + */ +struct spool_dname_iterator { + /* the domain name that has recently been read, but can be none + * if before first or after last. */ + uint8_t dname[MAXDOMAINLEN+1]; + /* length of the dname, if one is read, otherwise 0 */ + size_t dname_len; + /* if we are before the first element, hence nothing is read yet */ + int read_first; + /* if we are after the last element, nothing to read, end of file */ + int eof; + /* is the element processed that is currently in dname? */ + int is_processed; + /* the file to read from */ + FILE* spool; + /* filename for error printout */ + char* file_name; +}; + +#endif /* _IXFRCREATE_H_ */ diff --git a/usr.sbin/nsd/namedb.h b/usr.sbin/nsd/namedb.h index 089a8580217..10e19c76541 100644 --- a/usr.sbin/nsd/namedb.h +++ b/usr.sbin/nsd/namedb.h @@ -21,6 +21,7 @@ struct nsd_options; struct udb_base; struct udb_ptr; struct nsd; +struct zone_ixfr; typedef union rdata_atom rdata_atom_type; typedef struct rrset rrset_type; @@ -137,6 +138,7 @@ struct zone rbtree_type* dshashtree; /* tree, ds-parent-hash domains */ #endif struct zone_options* opts; + struct zone_ixfr* ixfr; char* filename; /* set if read from file, which file */ char* logstr; /* set for zone xfer, the log string */ struct timespec mtime; /* time of last modification */ @@ -381,6 +383,8 @@ int namedb_lookup (struct namedb* db, struct namedb *namedb_open(const char *filename, struct nsd_options* opt); void namedb_close_udb(struct namedb* db); void namedb_close(struct namedb* db); +/* free ixfr data stored for zones */ +void namedb_free_ixfr(struct namedb* db); void namedb_check_zonefiles(struct nsd* nsd, struct nsd_options* opt, struct udb_base* taskudb, struct udb_ptr* last_task); void namedb_check_zonefile(struct nsd* nsd, struct udb_base* taskudb, @@ -388,8 +392,6 @@ void namedb_check_zonefile(struct nsd* nsd, struct udb_base* taskudb, /** zone one zonefile into memory and revert on parse error, write to udb */ void namedb_read_zonefile(struct nsd* nsd, struct zone* zone, struct udb_base* taskudb, struct udb_ptr* last_task); -void apex_rrset_checks(struct namedb* db, rrset_type* rrset, - domain_type* domain); zone_type* namedb_zone_create(namedb_type* db, const dname_type* dname, struct zone_options* zopt); void namedb_zone_delete(namedb_type* db, zone_type* zone); diff --git a/usr.sbin/nsd/nsd-checkconf.8.in b/usr.sbin/nsd/nsd-checkconf.8.in index 60fb0693f40..029aabad132 100644 --- a/usr.sbin/nsd/nsd-checkconf.8.in +++ b/usr.sbin/nsd/nsd-checkconf.8.in @@ -1,4 +1,4 @@ -.TH "nsd\-checkconf" "8" "Feb 17, 2022" "NLnet Labs" "nsd 4.4.0" +.TH "nsd\-checkconf" "8" "May 13, 2022" "NLnet Labs" "nsd 4.5.0" .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" diff --git a/usr.sbin/nsd/nsd-checkconf.c b/usr.sbin/nsd/nsd-checkconf.c index b9c947288d0..f8c0c77bb63 100644 --- a/usr.sbin/nsd/nsd-checkconf.c +++ b/usr.sbin/nsd/nsd-checkconf.c @@ -21,6 +21,13 @@ extern char *optarg; extern int optind; static void usage(void) ATTR_NORETURN; +int zonec_parse_string(region_type* ATTR_UNUSED(region), + domain_table_type* ATTR_UNUSED(domains), zone_type* ATTR_UNUSED(zone), + char* ATTR_UNUSED(str), domain_type** ATTR_UNUSED(parsed), + int* ATTR_UNUSED(num_rrs)) +{ + return 0; +} #define ZONE_GET_ACL(NAME, VAR, PATTERN) \ if (strcasecmp(#NAME, (VAR)) == 0) { \ @@ -348,6 +355,10 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o, ZONE_GET_RRL(rrl_whitelist, o, zone->pattern); #endif ZONE_GET_BIN(multi_master_check, o, zone->pattern); + ZONE_GET_BIN(store_ixfr, o, zone->pattern); + ZONE_GET_INT(ixfr_size, o, zone->pattern); + ZONE_GET_INT(ixfr_number, o, zone->pattern); + ZONE_GET_BIN(create_ixfr, o, zone->pattern); printf("Zone option not handled: %s %s\n", z, o); exit(1); } else if(pat) { @@ -381,6 +392,10 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o, ZONE_GET_RRL(rrl_whitelist, o, p); #endif ZONE_GET_BIN(multi_master_check, o, p); + ZONE_GET_BIN(store_ixfr, o, p); + ZONE_GET_INT(ixfr_size, o, p); + ZONE_GET_INT(ixfr_number, o, p); + ZONE_GET_BIN(create_ixfr, o, p); printf("Pattern option not handled: %s %s\n", pat, o); exit(1); } else { @@ -525,6 +540,14 @@ static void print_zone_content_elems(pattern_options_type* pat) if(pat->size_limit_xfr != 0) printf("\tsize-limit-xfr: %llu\n", (long long unsigned)pat->size_limit_xfr); + if(!pat->store_ixfr_is_default) + printf("\tstore-ixfr: %s\n", pat->store_ixfr?"yes":"no"); + if(!pat->ixfr_number_is_default) + printf("\tixfr-number: %u\n", (unsigned)pat->ixfr_number); + if(!pat->ixfr_size_is_default) + printf("\tixfr-size: %u\n", (unsigned)pat->ixfr_size); + if(!pat->create_ixfr_is_default) + printf("\tcreate-ixfr: %s\n", pat->create_ixfr?"yes":"no"); } void diff --git a/usr.sbin/nsd/nsd-checkzone.8.in b/usr.sbin/nsd/nsd-checkzone.8.in index bc2cb47d1a5..5d985afdd51 100644 --- a/usr.sbin/nsd/nsd-checkzone.8.in +++ b/usr.sbin/nsd/nsd-checkzone.8.in @@ -1,4 +1,4 @@ -.TH "nsd\-checkzone" "8" "Feb 17, 2022" "NLnet Labs" "nsd 4.4.0" +.TH "nsd\-checkzone" "8" "May 13, 2022" "NLnet Labs" "nsd 4.5.0" .\" Copyright (c) 2014, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" @@ -31,6 +31,33 @@ Print the zone contents to stdout if the zone is ok. This prints the contents as it has been parsed, not literally a copy of the input, but as printed by the formatting routines in NSD, much like the nsd-control command write does. +.TP +.B \-i \fI +Create an IXFR from the differences between the old zone file and the +new zone file. The argument to the \-i option is the old zone file, +the other zonefile argument passed is the new zonefile. +.IP +The difference is computed between the two zonefiles by keeping one +version of the zone in memory, and another version in a temporary +file. The temporary file is located at the zonefile directory. This is +also where the result is written, to a file with the zonefile name, +ending with '.ixfr'. This is also where NSD reads it when IXFRs are +configured for the zone. +.IP +The other existing ixfr files are renamed to become older IXFR contents +for the zone, if any such files exist. If the output file already exists +with the correct contents, no new file is created. The contents of the +header of the output file are checked for that, if it already exists. +.TP +.B \-n \fI +The number of IXFR versions to store, at most. Default 5. This is the number +of files that is created with ixfr contents for the zone. Older stored IXFR +versions are deleted when the number is exceeded. +.TP +.B \-s \fI +The number of bytes of storage to use for IXFRs. Default is 1048576. If an +IXFR is bigger it is not created, and if the sum of IXFR storage exceeds it, +older IXFRs versions are deleted. .SH "SEE ALSO" \fInsd\fR(8), \fInsd-checkconf\fR(8) .SH "AUTHORS" diff --git a/usr.sbin/nsd/nsd-checkzone.c b/usr.sbin/nsd/nsd-checkzone.c index 701e7f62db8..b7ec3fcad62 100644 --- a/usr.sbin/nsd/nsd-checkzone.c +++ b/usr.sbin/nsd/nsd-checkzone.c @@ -21,6 +21,9 @@ #include "options.h" #include "util.h" #include "zonec.h" +#include "ixfr.h" +#include "ixfrcreate.h" +#include "difffile.h" struct nsd nsd; @@ -33,17 +36,22 @@ usage (void) { fprintf(stderr, "Usage: nsd-checkzone [-p] \n"); fprintf(stderr, "\t-p\tprint the zone if the zone is ok\n"); + fprintf(stderr, "\t-i \tcreate an IXFR from the differences between the\n\t\told zone file and the new zone file. Writes to \n\t\t.ixfr and renames other .ixfr files to\n\t\t.ixfr.num+1.\n"); + fprintf(stderr, "\t-n \tnumber of IXFR versions to store, at most.\n\t\tdefault %d.\n", (int)IXFR_NUMBER_DEFAULT); + fprintf(stderr, "\t-s \tsize of IXFR to store, at most. default %d.\n", (int)IXFR_SIZE_DEFAULT); fprintf(stderr, "Version %s. Report bugs to <%s>.\n", PACKAGE_VERSION, PACKAGE_BUGREPORT); } static void -check_zone(struct nsd* nsd, const char* name, const char* fname, FILE *out) +check_zone(struct nsd* nsd, const char* name, const char* fname, FILE *out, + const char* oldzone, uint32_t ixfr_number, uint64_t ixfr_size) { const dname_type* dname; zone_options_type* zo; zone_type* zone; unsigned errors; + struct ixfr_create* ixfrcr = NULL; /* init*/ nsd->db = namedb_open("", nsd->options); @@ -58,16 +66,43 @@ check_zone(struct nsd* nsd, const char* name, const char* fname, FILE *out) zo->name = name; zone = namedb_zone_create(nsd->db, dname, zo); + if(oldzone) { + errors = zonec_read(name, oldzone, zone); + if(errors > 0) { + printf("zone %s file %s has %u errors\n", name, oldzone, errors); + exit(1); + } + ixfrcr = ixfr_create_start(zone, fname, ixfr_size, 1); + if(!ixfrcr) { + error("out of memory"); + } + delete_zone_rrs(nsd->db, zone); + } + /* read the zone */ errors = zonec_read(name, fname, zone); if(errors > 0) { printf("zone %s file %s has %u errors\n", name, fname, errors); + ixfr_create_cancel(ixfrcr); #ifdef MEMCLEAN /* otherwise, the OS collects memory pages */ namedb_close(nsd->db); region_destroy(nsd->options->region); #endif exit(1); } + if(ixfrcr) { + if(!ixfr_create_perform(ixfrcr, zone, 0, nsd, fname, + ixfr_number)) { +#ifdef MEMCLEAN /* otherwise, the OS collects memory pages */ + namedb_close(nsd->db); + region_destroy(nsd->options->region); + ixfr_create_free(ixfrcr); +#endif + error("could not create IXFR"); + } + printf("zone %s created IXFR %s.ixfr\n", name, fname); + ixfr_create_free(ixfrcr); + } if (out) { print_rrs(out, zone); printf("; "); @@ -101,20 +136,32 @@ main(int argc, char *argv[]) /* Scratch variables... */ int c; int print_zone = 0; + uint32_t ixfr_number = IXFR_NUMBER_DEFAULT; + uint64_t ixfr_size = IXFR_SIZE_DEFAULT; + char* oldzone = NULL; struct nsd nsd; memset(&nsd, 0, sizeof(nsd)); log_init("nsd-checkzone"); /* Parse the command line... */ - while ((c = getopt(argc, argv, "hp")) != -1) { + while ((c = getopt(argc, argv, "hi:n:ps:")) != -1) { switch (c) { case 'h': usage(); exit(0); + case 'i': + oldzone = optarg; + break; + case 'n': + ixfr_number = (uint32_t)atoi(optarg); + break; case 'p': print_zone = 1; break; + case 's': + ixfr_size = (uint64_t)atoi(optarg); + break; case '?': default: usage(); @@ -137,7 +184,8 @@ main(int argc, char *argv[]) if (verbosity == 0) verbosity = nsd.options->verbosity; - check_zone(&nsd, argv[0], argv[1], print_zone ? stdout : NULL); + check_zone(&nsd, argv[0], argv[1], print_zone ? stdout : NULL, + oldzone, ixfr_number, ixfr_size); region_destroy(nsd.options->region); /* yylex_destroy(); but, not available in all versions of flex */ diff --git a/usr.sbin/nsd/nsd-control.8.in b/usr.sbin/nsd/nsd-control.8.in index 8242eeb8332..c7b5a7a5e0c 100644 --- a/usr.sbin/nsd/nsd-control.8.in +++ b/usr.sbin/nsd/nsd-control.8.in @@ -1,4 +1,4 @@ -.TH "nsd\-control" "8" "Feb 17, 2022" "NLnet Labs" "nsd 4.4.0" +.TH "nsd\-control" "8" "May 13, 2022" "NLnet Labs" "nsd 4.5.0" .\" Copyright (c) 2011, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" @@ -311,6 +311,9 @@ number of answers for which the transmit failed. .I num.raxfr number of AXFR requests from clients (that got served with reply). .TP +.I num.rixfr +number of IXFR requests from clients (that got served with reply). +.TP .I num.truncated number of answers with TC flag set. .TP diff --git a/usr.sbin/nsd/nsd-control.c b/usr.sbin/nsd/nsd-control.c index 528a58c3592..6ee296ab5e4 100644 --- a/usr.sbin/nsd/nsd-control.c +++ b/usr.sbin/nsd/nsd-control.c @@ -66,6 +66,7 @@ #include "util.h" #include "tsig.h" #include "options.h" +#include "zonec.h" static void usage(void) ATTR_NORETURN; static void ssl_err(const char* s) ATTR_NORETURN; @@ -74,6 +75,14 @@ static void ssl_path_err(const char* s, const char *path) ATTR_NORETURN; /** timeout to wait for connection over stream, in msec */ #define NSD_CONTROL_CONNECT_TIMEOUT 5000 +int zonec_parse_string(region_type* ATTR_UNUSED(region), + domain_table_type* ATTR_UNUSED(domains), zone_type* ATTR_UNUSED(zone), + char* ATTR_UNUSED(str), domain_type** ATTR_UNUSED(parsed), + int* ATTR_UNUSED(num_rrs)) +{ + return 0; +} + /** Give nsd-control usage, and exit (1). */ static void usage() diff --git a/usr.sbin/nsd/nsd.8.in b/usr.sbin/nsd/nsd.8.in index 9edbf09b85c..d637d94b882 100644 --- a/usr.sbin/nsd/nsd.8.in +++ b/usr.sbin/nsd/nsd.8.in @@ -1,9 +1,9 @@ -.TH "NSD" "8" "Feb 17, 2022" "NLnet Labs" "NSD 4.4.0" +.TH "NSD" "8" "May 13, 2022" "NLnet Labs" "NSD 4.5.0" .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" .B nsd -\- Name Server Daemon (NSD) version 4.4.0. +\- Name Server Daemon (NSD) version 4.5.0. .SH "SYNOPSIS" .B nsd .RB [ \-4 ] diff --git a/usr.sbin/nsd/nsd.c b/usr.sbin/nsd/nsd.c index e3e8896eee7..3370a96cf81 100644 --- a/usr.sbin/nsd/nsd.c +++ b/usr.sbin/nsd/nsd.c @@ -805,20 +805,20 @@ bind8_stats (struct nsd *nsd) /* XSTATS */ /* Only print it if we're in the main daemon or have anything to report... */ if (nsd->server_kind == NSD_SERVER_MAIN - || nsd->st.dropped || nsd->st.raxfr || (nsd->st.qudp + nsd->st.qudp6 - nsd->st.dropped) + || nsd->st.dropped || nsd->st.raxfr || nsd->st.rixfr || (nsd->st.qudp + nsd->st.qudp6 - nsd->st.dropped) || nsd->st.txerr || nsd->st.opcode[OPCODE_QUERY] || nsd->st.opcode[OPCODE_IQUERY] || nsd->st.wrongzone || nsd->st.ctcp + nsd->st.ctcp6 || nsd->st.rcode[RCODE_SERVFAIL] || nsd->st.rcode[RCODE_FORMAT] || nsd->st.nona || nsd->st.rcode[RCODE_NXDOMAIN] || nsd->st.opcode[OPCODE_UPDATE]) { log_msg(LOG_INFO, "XSTATS %lld %lu" - " RR=%lu RNXD=%lu RFwdR=%lu RDupR=%lu RFail=%lu RFErr=%lu RErr=%lu RAXFR=%lu" + " RR=%lu RNXD=%lu RFwdR=%lu RDupR=%lu RFail=%lu RFErr=%lu RErr=%lu RAXFR=%lu RIXFR=%lu" " RLame=%lu ROpts=%lu SSysQ=%lu SAns=%lu SFwdQ=%lu SDupQ=%lu SErr=%lu RQ=%lu" " RIQ=%lu RFwdQ=%lu RDupQ=%lu RTCP=%lu SFwdR=%lu SFail=%lu SFErr=%lu SNaAns=%lu" " SNXD=%lu RUQ=%lu RURQ=%lu RUXFR=%lu RUUpd=%lu", (long long) now, (unsigned long) nsd->st.boot, nsd->st.dropped, (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0, - (unsigned long)0, (unsigned long)0, nsd->st.raxfr, (unsigned long)0, (unsigned long)0, + (unsigned long)0, (unsigned long)0, nsd->st.raxfr, nsd->st.rixfr, (unsigned long)0, (unsigned long)0, (unsigned long)0, nsd->st.qudp + nsd->st.qudp6 - nsd->st.dropped, (unsigned long)0, (unsigned long)0, nsd->st.txerr, nsd->st.opcode[OPCODE_QUERY], nsd->st.opcode[OPCODE_IQUERY], nsd->st.wrongzone, diff --git a/usr.sbin/nsd/nsd.conf.5.in b/usr.sbin/nsd/nsd.conf.5.in index f1299c1db25..91a2329ec08 100644 --- a/usr.sbin/nsd/nsd.conf.5.in +++ b/usr.sbin/nsd/nsd.conf.5.in @@ -1,4 +1,4 @@ -.TH "nsd.conf" "5" "Feb 17, 2022" "NLnet Labs" "nsd 4.4.0" +.TH "nsd.conf" "5" "May 13, 2022" "NLnet Labs" "nsd 4.5.0" .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved. .\" See LICENSE for the license. .SH "NAME" @@ -651,6 +651,10 @@ The zone options such as .BR notify , .BR notify\-retry , .BR provide\-xfr , +.BR store\-ixfr , +.BR ixfr\-number , +.BR ixfr\-size , +.BR create\-ixfr , .BR zonestats , and .B outgoing\-interface @@ -810,6 +814,35 @@ A port number can be added using a suffix of @number, for example 1.2.3.4@5300. .RE .TP +.B store\-ixfr:\fR +If enabled, IXFR contents are stored and provided to the set of clients +specified in the provide\-xfr statement. Default is no. IXFR contents is +a smaller set of changes that differ between zone versions, where an AXFR +contains the full contents of the zone. +.TP +.B ixfr\-number:\fR +The number of IXFR versions to store for this zone, at most. Default is 5. +.TP +.B ixfr\-size:\fR +The max storage to use for IXFR versions for this zone, in bytes. +Default is 1048576. A value of 0 means unlimited, if you want to turn off +IXFR storage, use the store\-ixfr option. +NSD does not elide IXFR contents from versions that add and remove the same +data, that merges version changes together to shorten the data, but leaves +the data and change sequence as it was transmitted by another server. +.TP +.B create\-ixfr:\fR +If enabled, IXFR data is created when a zonefile is read by the server. +Also set store\-ixfr enabled, so that the contents are stored, when you use +this. Default is off. If the server is not running, the nsd\-checkzone \-i +option can be used to create an IXFR file. When an IXFR is created, the server +spools a version of the zone to a temporary file, at the location where the +ixfr files are stored. This creates IXFR data when the zone is read from file, +but not when a zone is read by AXFR transfer from a server, because then +the topmost server that originates the data is the one place where ixfr +differences are computed and those differences are then transmitted verbatim +to all the other servers. +.TP .B max\-refresh\-time:\fR Limit refresh time for secondary zones. This is the timer which checks to see if the zone has to be refetched when it expires. Normally the value from the diff --git a/usr.sbin/nsd/nsd.conf.sample.in b/usr.sbin/nsd/nsd.conf.sample.in index 0e006bafb4c..de0f0b02ec7 100644 --- a/usr.sbin/nsd/nsd.conf.sample.in +++ b/usr.sbin/nsd/nsd.conf.sample.in @@ -373,6 +373,14 @@ remote-control: #provide-xfr: 192.0.2.0/24 my_tsig_key_name # set the number of retries for notify. #notify-retry: 5 + # if yes, store and provide IXFRs. + #store-ixfr: no + # number of IXFR versions to store, at most. + #ixfr-number: 5 + # size in bytes of max storage to use for IXFR versions. + #ixfr-size: 1048576 + # if yes, create IXFR when a zonefile is read by the server. + #create-ixfr: no # uncomment to provide AXFR to all the world # provide-xfr: 0.0.0.0/0 NOKEY diff --git a/usr.sbin/nsd/nsd.h b/usr.sbin/nsd/nsd.h index 177d5d5cf8d..2472defa016 100644 --- a/usr.sbin/nsd/nsd.h +++ b/usr.sbin/nsd/nsd.h @@ -294,7 +294,7 @@ struct nsd stc_type rcode[17], opcode[6]; /* Rcodes & opcodes */ /* Dropped, truncated, queries for nonconfigured zone, tx errors */ stc_type dropped, truncated, wrongzone, txerr, rxerr; - stc_type edns, ednserr, raxfr, nona; + stc_type edns, ednserr, raxfr, nona, rixfr; uint64_t db_disk, db_mem; } st; /* per zone stats, each an array per zone-stat-idx, stats per zone is diff --git a/usr.sbin/nsd/nsec3.c b/usr.sbin/nsd/nsec3.c index 1075812a437..4ed55e68c14 100644 --- a/usr.sbin/nsd/nsec3.c +++ b/usr.sbin/nsd/nsec3.c @@ -677,7 +677,7 @@ nsec3_precompile_newparam(namedb_type* db, zone_type* zone) s = time(NULL); VERBOSITY(1, (LOG_INFO, "nsec3 %s %d %%", zone->opts->name, - (int)(c*((unsigned long)100)/n))); + (n==0)?0:(int)(c*((unsigned long)100)/n))); } } region_destroy(tmpregion); diff --git a/usr.sbin/nsd/options.c b/usr.sbin/nsd/options.c index c684698b175..863f7a975d0 100644 --- a/usr.sbin/nsd/options.c +++ b/usr.sbin/nsd/options.c @@ -17,6 +17,7 @@ #include "options.h" #include "query.h" #include "tsig.h" +#include "ixfr.h" #include "difffile.h" #include "rrl.h" #include "bitset.h" @@ -882,6 +883,14 @@ pattern_options_create(region_type* region) p->rrl_whitelist = 0; #endif p->multi_master_check = 0; + p->store_ixfr = 0; + p->store_ixfr_is_default = 1; + p->ixfr_size = IXFR_SIZE_DEFAULT; + p->ixfr_size_is_default = 1; + p->ixfr_number = IXFR_NUMBER_DEFAULT; + p->ixfr_number_is_default = 1; + p->create_ixfr = 0; + p->create_ixfr_is_default = 1; return p; } @@ -1026,6 +1035,14 @@ copy_pat_fixed(region_type* region, struct pattern_options* orig, orig->rrl_whitelist = p->rrl_whitelist; #endif orig->multi_master_check = p->multi_master_check; + orig->store_ixfr = p->store_ixfr; + orig->store_ixfr_is_default = p->store_ixfr_is_default; + orig->ixfr_size = p->ixfr_size; + orig->ixfr_size_is_default = p->ixfr_size_is_default; + orig->ixfr_number = p->ixfr_number; + orig->ixfr_number_is_default = p->ixfr_number_is_default; + orig->create_ixfr = p->create_ixfr; + orig->create_ixfr_is_default = p->create_ixfr_is_default; } void @@ -1119,6 +1136,14 @@ pattern_options_equal(struct pattern_options* p, struct pattern_options* q) #endif if(!booleq(p->multi_master_check,q->multi_master_check)) return 0; if(p->size_limit_xfr != q->size_limit_xfr) return 0; + if(!booleq(p->store_ixfr,q->store_ixfr)) return 0; + if(!booleq(p->store_ixfr_is_default,q->store_ixfr_is_default)) return 0; + if(p->ixfr_size != q->ixfr_size) return 0; + if(!booleq(p->ixfr_size_is_default,q->ixfr_size_is_default)) return 0; + if(p->ixfr_number != q->ixfr_number) return 0; + if(!booleq(p->ixfr_number_is_default,q->ixfr_number_is_default)) return 0; + if(!booleq(p->create_ixfr,q->create_ixfr)) return 0; + if(!booleq(p->create_ixfr_is_default,q->create_ixfr_is_default)) return 0; return 1; } @@ -1285,6 +1310,14 @@ pattern_options_marshal(struct buffer* b, struct pattern_options* p) marshal_u32(b, p->min_expire_time); marshal_u8(b, p->min_expire_time_expr); marshal_u8(b, p->multi_master_check); + marshal_u8(b, p->store_ixfr); + marshal_u8(b, p->store_ixfr_is_default); + marshal_u64(b, p->ixfr_size); + marshal_u8(b, p->ixfr_size_is_default); + marshal_u32(b, p->ixfr_number); + marshal_u8(b, p->ixfr_number_is_default); + marshal_u8(b, p->create_ixfr); + marshal_u8(b, p->create_ixfr_is_default); } struct pattern_options* @@ -1320,6 +1353,14 @@ pattern_options_unmarshal(region_type* r, struct buffer* b) p->min_expire_time = unmarshal_u32(b); p->min_expire_time_expr = unmarshal_u8(b); p->multi_master_check = unmarshal_u8(b); + p->store_ixfr = unmarshal_u8(b); + p->store_ixfr_is_default = unmarshal_u8(b); + p->ixfr_size = unmarshal_u64(b); + p->ixfr_size_is_default = unmarshal_u8(b); + p->ixfr_number = unmarshal_u32(b); + p->ixfr_number_is_default = unmarshal_u8(b); + p->create_ixfr = unmarshal_u8(b); + p->create_ixfr_is_default = unmarshal_u8(b); return p; } @@ -2117,6 +2158,22 @@ config_apply_pattern(struct pattern_options *dest, const char* name) dest->min_expire_time = pat->min_expire_time; dest->min_expire_time_expr = pat->min_expire_time_expr; } + if(!pat->store_ixfr_is_default) { + dest->store_ixfr = pat->store_ixfr; + dest->store_ixfr_is_default = 0; + } + if(!pat->ixfr_size_is_default) { + dest->ixfr_size = pat->ixfr_size; + dest->ixfr_size_is_default = 0; + } + if(!pat->ixfr_number_is_default) { + dest->ixfr_number = pat->ixfr_number; + dest->ixfr_number_is_default = 0; + } + if(!pat->create_ixfr_is_default) { + dest->create_ixfr = pat->create_ixfr; + dest->create_ixfr_is_default = 0; + } dest->size_limit_xfr = pat->size_limit_xfr; #ifdef RATELIMIT dest->rrl_whitelist |= pat->rrl_whitelist; diff --git a/usr.sbin/nsd/options.h b/usr.sbin/nsd/options.h index 54357c863a6..a69c068df61 100644 --- a/usr.sbin/nsd/options.h +++ b/usr.sbin/nsd/options.h @@ -258,6 +258,14 @@ struct pattern_options { uint8_t min_expire_time_expr; uint64_t size_limit_xfr; uint8_t multi_master_check; + uint8_t store_ixfr; + uint8_t store_ixfr_is_default; + uint64_t ixfr_size; + uint8_t ixfr_size_is_default; + uint32_t ixfr_number; + uint8_t ixfr_number_is_default; + uint8_t create_ixfr; + uint8_t create_ixfr_is_default; } ATTR_PACKED; #define PATTERN_IMPLICIT_MARKER "_implicit_" diff --git a/usr.sbin/nsd/query.c b/usr.sbin/nsd/query.c index d0fba064fab..9b34f029d09 100644 --- a/usr.sbin/nsd/query.c +++ b/usr.sbin/nsd/query.c @@ -245,6 +245,13 @@ query_reset(query_type *q, size_t maxlen, int is_tcp) q->axfr_current_rrset = NULL; q->axfr_current_rr = 0; + q->ixfr_is_done = 0; + q->ixfr_data = NULL; + q->ixfr_count_newsoa = 0; + q->ixfr_count_oldsoa = 0; + q->ixfr_count_del = 0; + q->ixfr_count_add = 0; + #ifdef RATELIMIT q->wildcard_domain = NULL; #endif @@ -1669,7 +1676,8 @@ query_process(query_type *q, nsd_type *nsd, uint32_t *now_p) } } query_state = answer_axfr_ixfr(nsd, q); - if (query_state == QUERY_PROCESSED || query_state == QUERY_IN_AXFR) { + if (query_state == QUERY_PROCESSED || query_state == QUERY_IN_AXFR + || query_state == QUERY_IN_IXFR) { return query_state; } if(q->qtype == TYPE_ANY && nsd->options->refuse_any && !q->tcp) { diff --git a/usr.sbin/nsd/query.h b/usr.sbin/nsd/query.h index af7ee7323bb..2f47c9371b1 100644 --- a/usr.sbin/nsd/query.h +++ b/usr.sbin/nsd/query.h @@ -17,11 +17,13 @@ #include "nsd.h" #include "packet.h" #include "tsig.h" +struct ixfr_data; enum query_state { QUERY_PROCESSED, QUERY_DISCARDED, - QUERY_IN_AXFR + QUERY_IN_AXFR, + QUERY_IN_IXFR }; typedef enum query_state query_state_type; @@ -117,6 +119,24 @@ struct query { rrset_type *axfr_current_rrset; uint16_t axfr_current_rr; + /* Used for IXFR processing, + * indicates if the zone transfer is done, connection can close. */ + int ixfr_is_done; + /* the ixfr data that is processed */ + struct ixfr_data* ixfr_data; + /* the ixfr data that is the last segment */ + struct ixfr_data* ixfr_end_data; + /* ixfr count of newsoa bytes added, 0 none, len means done */ + size_t ixfr_count_newsoa; + /* ixfr count of oldsoa bytes added, 0 none, len means done */ + size_t ixfr_count_oldsoa; + /* ixfr count of del bytes added, 0 none, len means done */ + size_t ixfr_count_del; + /* ixfr count of add bytes added, 0 none, len means done */ + size_t ixfr_count_add; + /* position for the end of SOA record, for UDP truncation */ + size_t ixfr_pos_of_newsoa; + #ifdef RATELIMIT /* if we encountered a wildcard, its domain */ domain_type *wildcard_domain; diff --git a/usr.sbin/nsd/rdata.c b/usr.sbin/nsd/rdata.c index 7628fdf0b1a..79361965a66 100644 --- a/usr.sbin/nsd/rdata.c +++ b/usr.sbin/nsd/rdata.c @@ -1006,6 +1006,7 @@ rdata_wireformat_to_rdata_atoms(region_type *region, } break; case RDATA_WF_IPSECGATEWAY: + assert(i>1); /* we are past the gateway type */ switch(rdata_atom_data(temp_rdatas[1])[0]) /* gateway type */ { default: case IPSECKEY_NOGATEWAY: diff --git a/usr.sbin/nsd/remote.c b/usr.sbin/nsd/remote.c index c94a1d90b1b..64b79f7d99a 100644 --- a/usr.sbin/nsd/remote.c +++ b/usr.sbin/nsd/remote.c @@ -2668,6 +2668,10 @@ print_stat_block(RES* ssl, char* n, char* d, struct nsdst* st) if(!ssl_printf(ssl, "%s%snum.raxfr=%lu\n", n, d, (unsigned long)st->raxfr)) return; + /* number of requested-ixfr, number of times ixfr served to clients */ + if(!ssl_printf(ssl, "%s%snum.rixfr=%lu\n", n, d, (unsigned long)st->rixfr)) + return; + /* truncated */ if(!ssl_printf(ssl, "%s%snum.truncated=%lu\n", n, d, (unsigned long)st->truncated)) diff --git a/usr.sbin/nsd/server.c b/usr.sbin/nsd/server.c index 1e29ccc92cc..0d614a3d262 100644 --- a/usr.sbin/nsd/server.c +++ b/usr.sbin/nsd/server.c @@ -81,6 +81,7 @@ #include "remote.h" #include "lookup3.h" #include "rrl.h" +#include "ixfr.h" #ifdef USE_DNSTAP #include "dnstap/dnstap_collector.h" #endif @@ -1536,6 +1537,7 @@ server_shutdown(struct nsd *nsd) #endif udb_base_free_keep_mmap(nsd->task[0]); udb_base_free_keep_mmap(nsd->task[1]); + namedb_free_ixfr(nsd->db); namedb_close_udb(nsd->db); /* keeps mmap */ namedb_close(nsd->db); nsd_options_destroy(nsd->options); @@ -3986,10 +3988,13 @@ handle_tcp_writing(int fd, short event, void* arg) assert(data->bytes_transmitted == q->tcplen + sizeof(q->tcplen)); - if (data->query_state == QUERY_IN_AXFR) { + if (data->query_state == QUERY_IN_AXFR || + data->query_state == QUERY_IN_IXFR) { /* Continue processing AXFR and writing back results. */ buffer_clear(q->packet); - data->query_state = query_axfr(data->nsd, q); + if(data->query_state == QUERY_IN_AXFR) + data->query_state = query_axfr(data->nsd, q, 0); + else data->query_state = query_ixfr(data->nsd, q); if (data->query_state != QUERY_PROCESSED) { query_add_optional(data->query, data->nsd, &now); @@ -4450,10 +4455,13 @@ handle_tls_writing(int fd, short event, void* arg) assert(data->bytes_transmitted == q->tcplen + sizeof(q->tcplen)); - if (data->query_state == QUERY_IN_AXFR) { + if (data->query_state == QUERY_IN_AXFR || + data->query_state == QUERY_IN_IXFR) { /* Continue processing AXFR and writing back results. */ buffer_clear(q->packet); - data->query_state = query_axfr(data->nsd, q); + if(data->query_state == QUERY_IN_AXFR) + data->query_state = query_axfr(data->nsd, q, 0); + else data->query_state = query_ixfr(data->nsd, q); if (data->query_state != QUERY_PROCESSED) { query_add_optional(data->query, data->nsd, &now); diff --git a/usr.sbin/nsd/udb.c b/usr.sbin/nsd/udb.c index 1131fb43e79..d94d41594ca 100644 --- a/usr.sbin/nsd/udb.c +++ b/usr.sbin/nsd/udb.c @@ -1280,12 +1280,15 @@ static int udb_alloc_exp_needed(size_t sz) { uint64_t asz = sz + sizeof(udb_chunk_d) + 1; + int exp; if(asz > UDB_ALLOC_CHUNK_SIZE) { return UDB_EXP_XL; } else if(asz <= UDB_ALLOC_CHUNK_MINSIZE) { return UDB_ALLOC_CHUNK_MINEXP; } - return udb_exp_size(asz); + exp = udb_exp_size(asz); + assert(exp <= UDB_ALLOC_CHUNKS_MAX); + return exp; } udb_void udb_alloc_space(udb_alloc* alloc, size_t sz) diff --git a/usr.sbin/nsd/zonec.c b/usr.sbin/nsd/zonec.c index bebd9b43e5f..3ca75f7e22e 100644 --- a/usr.sbin/nsd/zonec.c +++ b/usr.sbin/nsd/zonec.c @@ -776,7 +776,7 @@ svcbparam_lookup_key(const char *key, size_t key_len) if (!strncmp(key, "mandatory", sizeof("mandatory")-1)) return SVCB_KEY_MANDATORY; if (!strncmp(key, "echconfig", sizeof("echconfig")-1)) - return SVCB_KEY_ECH; /* allow "echconfig as well as "ech" */ + return SVCB_KEY_ECH; /* allow "echconfig" as well as "ech" */ break; case sizeof("alpn")-1: @@ -2369,3 +2369,44 @@ void check_sshfp(void) (int)size); } } + +void +apex_rrset_checks(namedb_type* db, rrset_type* rrset, domain_type* domain) +{ + uint32_t soa_minimum; + unsigned i; + zone_type* zone = rrset->zone; + assert(domain == zone->apex); + (void)domain; + if (rrset_rrtype(rrset) == TYPE_SOA) { + zone->soa_rrset = rrset; + + /* BUG #103 add another soa with a tweaked ttl */ + if(zone->soa_nx_rrset == 0) { + zone->soa_nx_rrset = region_alloc(db->region, + sizeof(rrset_type)); + zone->soa_nx_rrset->rr_count = 1; + zone->soa_nx_rrset->next = 0; + zone->soa_nx_rrset->zone = zone; + zone->soa_nx_rrset->rrs = region_alloc(db->region, + sizeof(rr_type)); + } + memcpy(zone->soa_nx_rrset->rrs, rrset->rrs, sizeof(rr_type)); + + /* check the ttl and MINIMUM value and set accordingly */ + memcpy(&soa_minimum, rdata_atom_data(rrset->rrs->rdatas[6]), + rdata_atom_size(rrset->rrs->rdatas[6])); + if (rrset->rrs->ttl > ntohl(soa_minimum)) { + zone->soa_nx_rrset->rrs[0].ttl = ntohl(soa_minimum); + } + } else if (rrset_rrtype(rrset) == TYPE_NS) { + zone->ns_rrset = rrset; + } else if (rrset_rrtype(rrset) == TYPE_RRSIG) { + for (i = 0; i < rrset->rr_count; ++i) { + if(rr_rrsig_type_covered(&rrset->rrs[i])==TYPE_DNSKEY){ + zone->is_secure = 1; + break; + } + } + } +} diff --git a/usr.sbin/nsd/zonec.h b/usr.sbin/nsd/zonec.h index 7c30a90b390..e1b1fdd5484 100644 --- a/usr.sbin/nsd/zonec.h +++ b/usr.sbin/nsd/zonec.h @@ -147,5 +147,7 @@ int zonec_parse_string(region_type* region, domain_table_type* domains, zone_type* zone, char* str, domain_type** parsed, int* num_rrs); /** check SSHFP type for failures and emit warnings */ void check_sshfp(void); +void apex_rrset_checks(struct namedb* db, rrset_type* rrset, + domain_type* domain); #endif /* _ZONEC_H_ */