Update to nsd 4.5.0
authorflorian <florian@openbsd.org>
Fri, 13 May 2022 15:48:29 +0000 (15:48 +0000)
committerflorian <florian@openbsd.org>
Fri, 13 May 2022 15:48:29 +0000 (15:48 +0000)
OK sthen

40 files changed:
usr.sbin/nsd/Makefile.in
usr.sbin/nsd/axfr.c
usr.sbin/nsd/axfr.h
usr.sbin/nsd/configlexer.lex
usr.sbin/nsd/configparser.y
usr.sbin/nsd/configure
usr.sbin/nsd/configure.ac
usr.sbin/nsd/dbaccess.c
usr.sbin/nsd/dbcreate.c
usr.sbin/nsd/difffile.c
usr.sbin/nsd/doc/ChangeLog
usr.sbin/nsd/doc/RELNOTES
usr.sbin/nsd/ipc.c
usr.sbin/nsd/ixfr.c [new file with mode: 0644]
usr.sbin/nsd/ixfr.h [new file with mode: 0644]
usr.sbin/nsd/ixfrcreate.c [new file with mode: 0644]
usr.sbin/nsd/ixfrcreate.h [new file with mode: 0644]
usr.sbin/nsd/namedb.h
usr.sbin/nsd/nsd-checkconf.8.in
usr.sbin/nsd/nsd-checkconf.c
usr.sbin/nsd/nsd-checkzone.8.in
usr.sbin/nsd/nsd-checkzone.c
usr.sbin/nsd/nsd-control.8.in
usr.sbin/nsd/nsd-control.c
usr.sbin/nsd/nsd.8.in
usr.sbin/nsd/nsd.c
usr.sbin/nsd/nsd.conf.5.in
usr.sbin/nsd/nsd.conf.sample.in
usr.sbin/nsd/nsd.h
usr.sbin/nsd/nsec3.c
usr.sbin/nsd/options.c
usr.sbin/nsd/options.h
usr.sbin/nsd/query.c
usr.sbin/nsd/query.h
usr.sbin/nsd/rdata.c
usr.sbin/nsd/remote.c
usr.sbin/nsd/server.c
usr.sbin/nsd/udb.c
usr.sbin/nsd/zonec.c
usr.sbin/nsd/zonec.h

index e28fc47..b6b7eb3 100644 (file)
@@ -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 \
index cd96bd1..dbf3eef 100644 (file)
 #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;
        }
index 33a6862..105cd53 100644 (file)
@@ -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_ */
index a168834..57b1605 100644 (file)
@@ -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;}
index 0815a0a..297b580 100644 (file)
@@ -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
index 0a5938d..0e87d15 100644 (file)
@@ -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 <nsd-bugs@nlnetlabs.nl>.
 #
@@ -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\\"
 
index 979b9ec..a85ad34 100644 (file)
@@ -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])
 
 #
index 4849481..2f07c00 100644 (file)
@@ -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
index 13d4e9d..31b6b0c 100644 (file)
@@ -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);
        }
 }
 
index 5a79264..aa8271f 100644 (file)
@@ -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)+
index 24b6e09..1d6034b 100644 (file)
@@ -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
index aa64918..412cb52 100644 (file)
@@ -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:
index 87478f7..daa1d6a 100644 (file)
@@ -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 (file)
index 0000000..b054122
--- /dev/null
@@ -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 <errno.h>
+#include <string.h>
+#include <ctype.h>
+#ifdef HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#  include <sys/stat.h>
+#endif
+#include <unistd.h>
+
+#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; i<descriptor->maximum; 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; i<NSCOUNT(packet); i++) {
+               /* is this the SOA record? */
+               if(!packet_skip_dname(packet))
+                       return 0; /* malformed name */
+               if(!buffer_available(packet, 10))
+                       return 0; /* no type,class,ttl,rdatalen */
+               type = buffer_read_u16(packet);
+               buffer_skip(packet, 6);
+               rdlen = buffer_read_u16(packet);
+               if(!buffer_available(packet, rdlen))
+                       return 0;
+               if(type == TYPE_SOA) {
+                       /* read serial from rdata, skip two dnames, then
+                        * read the 32bit value */
+                       if(!packet_skip_dname(packet))
+                               return 0; /* malformed nsname */
+                       if(!packet_skip_dname(packet))
+                               return 0; /* malformed rname */
+                       if(!buffer_available(packet, 4))
+                               return 0;
+                       *qserial = buffer_read_u32(packet);
+                       return 1;
+               }
+               buffer_skip(packet, rdlen);
+       }
+       return 0;
+}
+
+/* get serial from SOA RR */
+static uint32_t soa_rr_get_serial(struct rr* rr)
+{
+       if(rr->rdata_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; i<rdata_num; i++) {
+               if(rdata_atom_is_domain(type, i)) {
+                       rdlen_uncompressed += domain_dname(rdatas[i].domain)
+                               ->name_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; i<rdata_num; i++) {
+               if(rdata_atom_is_domain(type, i)) {
+                       memmove(sp, dname_name(domain_dname(rdatas[i].domain)),
+                               domain_dname(rdatas[i].domain)->name_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; i<rr->rdata_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 (file)
index 0000000..0f20103
--- /dev/null
@@ -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.<num> file on disk. If 0, there is no
+        * file. If 1, it is file ixfr<nothingafterit>. */
+       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 (file)
index 0000000..4ecf0cc
--- /dev/null
@@ -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 <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#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; i<rr->rdata_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; i<rr->rdata_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; i<rrset->rr_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; i<rr->rdata_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; i<rrset->rr_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; i<rrcount; i++) {
+               uint16_t rdlen, index;
+               uint32_t ttl;
+               if(!read_spool_u32(spool, &ttl) ||
+                  !read_spool_u16(spool, &rdlen)) {
+                       log_msg(LOG_ERR, "error reading file %s: %s",
+                               ixfrcr->file_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; i<rrset->rr_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; i<rrcount; i++) {
+               uint16_t rdlen;
+               uint32_t ttl;
+               if(!read_spool_u32(spool, &ttl) ||
+                  !read_spool_u16(spool, &rdlen)) {
+                       log_msg(LOG_ERR, "error reading file %s: %s",
+                               ixfrcr->file_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; i<rrset->rr_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; i<spool_type_count; i++) {
+               uint16_t tp, kl, rrcount;
+               struct rrset* rrset;
+               if(!read_spool_u16(spool, &tp) ||
+                  !read_spool_u16(spool, &kl) ||
+                  !read_spool_u16(spool, &rrcount)) {
+                       log_msg(LOG_ERR, "error reading file %s: %s",
+                               ixfrcr->file_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; i<spool_type_count; i++) {
+               uint16_t tp, kl, rrcount;
+               if(!read_spool_u16(spool, &tp) ||
+                  !read_spool_u16(spool, &kl) ||
+                  !read_spool_u16(spool, &rrcount)) {
+                       log_msg(LOG_ERR, "error reading file %s: %s",
+                               ixfrcr->file_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 (file)
index 0000000..c09e0b1
--- /dev/null
@@ -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_ */
index 089a858..10e19c7 100644 (file)
@@ -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);
index 60fb069..029aaba 100644 (file)
@@ -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"
index b9c9472..f8c0c77 100644 (file)
 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
index bc2cb47..5d985af 100644 (file)
@@ -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<oldzonefile>
+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<ixfr number>
+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<ixfr size>
+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"
index 701e7f6..b7ec3fc 100644 (file)
@@ -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] <zone name> <zone file>\n");
        fprintf(stderr, "\t-p\tprint the zone if the zone is ok\n");
+       fprintf(stderr, "\t-i <old zone file>\tcreate an IXFR from the differences between the\n\t\told zone file and the new zone file. Writes to \n\t\t<zonefile>.ixfr and renames other <zonefile>.ixfr files to\n\t\t<zonefile>.ixfr.num+1.\n");
+       fprintf(stderr, "\t-n <ixfr number>\tnumber of IXFR versions to store, at most.\n\t\tdefault %d.\n", (int)IXFR_NUMBER_DEFAULT);
+       fprintf(stderr, "\t-s <ixfr size>\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 */
 
index 8242eeb..c7b5a7a 100644 (file)
@@ -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
index 528a58c..6ee296a 100644 (file)
@@ -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()
index 9edbf09..d637d94 100644 (file)
@@ -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 ] 
index e3e8896..3370a96 100644 (file)
@@ -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,
index f1299c1..91a2329 100644 (file)
@@ -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 <yes or no>
+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 <number>
+The number of IXFR versions to store for this zone, at most. Default is 5.
+.TP
+.B ixfr\-size:\fR <number>
+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 <yes or no>
+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 <seconds>
 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
index 0e006ba..de0f0b0 100644 (file)
@@ -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
index 177d5d5..2472def 100644 (file)
@@ -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
index 1075812..4ed55e6 100644 (file)
@@ -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);
index c684698..863f7a9 100644 (file)
@@ -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;
index 54357c8..a69c068 100644 (file)
@@ -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_"
index d0fba06..9b34f02 100644 (file)
@@ -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) {
index af7ee73..2f47c93 100644 (file)
 #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;
index 7628fdf..7936196 100644 (file)
@@ -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:
index c94a1d9..64b79f7 100644 (file)
@@ -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))
index 1e29ccc..0d614a3 100644 (file)
@@ -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);
 
index 1131fb4..d94d415 100644 (file)
@@ -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)
index bebd9b4..3ca75f7 100644 (file)
@@ -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;
+                       }
+               }
+       }
+}
index 7c30a90..e1b1fdd 100644 (file)
@@ -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_ */