Update to nsd 4.9.1
authorflorian <florian@openbsd.org>
Fri, 12 Apr 2024 15:53:34 +0000 (15:53 +0000)
committerflorian <florian@openbsd.org>
Fri, 12 Apr 2024 15:53:34 +0000 (15:53 +0000)
sparc64 built test by tb
OK tb, sthen

39 files changed:
usr.sbin/nsd/Makefile.in
usr.sbin/nsd/acx_nlnetlabs.m4
usr.sbin/nsd/buffer.h
usr.sbin/nsd/config.h.in
usr.sbin/nsd/configlexer.lex
usr.sbin/nsd/configparser.y
usr.sbin/nsd/configure
usr.sbin/nsd/configure.ac
usr.sbin/nsd/difffile.c
usr.sbin/nsd/difffile.h
usr.sbin/nsd/dname.c
usr.sbin/nsd/dname.h
usr.sbin/nsd/doc/ChangeLog
usr.sbin/nsd/doc/README
usr.sbin/nsd/doc/RELNOTES
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-control.8.in
usr.sbin/nsd/nsd-control.c
usr.sbin/nsd/nsd-mem.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/options.c
usr.sbin/nsd/options.h
usr.sbin/nsd/query.c
usr.sbin/nsd/remote.c
usr.sbin/nsd/server.c
usr.sbin/nsd/util.c
usr.sbin/nsd/util.h
usr.sbin/nsd/xfrd-catalog-zones.c [new file with mode: 0644]
usr.sbin/nsd/xfrd-catalog-zones.h [new file with mode: 0644]
usr.sbin/nsd/xfrd-disk.c
usr.sbin/nsd/xfrd-tcp.c
usr.sbin/nsd/xfrd.c
usr.sbin/nsd/xfrd.h

index c13186a..9d38d74 100644 (file)
@@ -18,10 +18,8 @@ datarootdir = @datarootdir@
 # NSD specific pathnames
 configdir = @configdir@
 piddir = @piddir@
-dbdir = @dbdir@
 pidfile = @pidfile@
 logfile = @logfile@
-dbfile = @dbfile@
 xfrdir = @xfrdir@
 xfrdfile = @xfrdfile@
 zonelistfile = @zonelistfile@
@@ -66,7 +64,6 @@ EDIT          = $(SED) \
                        -e 's,@chrootdir\@,$(chrootdir),g' \
                        -e 's,@pidfile\@,$(pidfile),g' \
                        -e 's,@logfile\@,$(logfile),g' \
-                       -e 's,@dbfile\@,$(dbfile),g' \
                        -e 's,@xfrdir\@,$(xfrdir),g' \
                        -e 's,@xfrdfile\@,$(xfrdfile),g' \
                        -e 's,@zonelistfile\@,$(zonelistfile),g' \
@@ -80,7 +77,7 @@ TARGETS=nsd nsd-checkconf nsd-checkzone nsd-control nsd.conf.sample nsd-control-
 MANUALS=nsd.8 nsd-checkconf.8 nsd-checkzone.8 nsd-control.8 nsd.conf.5
 
 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 util.o bitset.o popen3.o proxy_protocol.o
-XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.o $(DNSTAP_OBJ)
+XFRD_OBJ=xfrd-catalog-zones.o 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 verify.o
 ALL_OBJ=$(NSD_OBJ) nsd-checkconf.o nsd-checkzone.o nsd-control.o nsd-mem.o xfr-inspect.o
 NSD_CHECKCONF_OBJ=$(COMMON_OBJ) nsd-checkconf.o
@@ -129,7 +126,8 @@ orig-install: all
        $(INSTALL) -d $(DESTDIR)$(configdir)
        if test -n "$(piddir)"; then $(INSTALL) -d $(DESTDIR)$(piddir); fi
        $(INSTALL) -d $(DESTDIR)$(xfrdir)
-       $(INSTALL) -d $(DESTDIR)$(dbdir)
+       $(INSTALL) -d `dirname $(DESTDIR)$(xfrdfile)`
+       $(INSTALL) -d `dirname $(DESTDIR)$(zonelistfile)`
        $(INSTALL) -d $(DESTDIR)$(mandir)
        $(INSTALL) -d $(DESTDIR)$(mandir)/man8
        $(INSTALL) -d $(DESTDIR)$(mandir)/man5
@@ -152,7 +150,7 @@ uninstall:
        rm -f -- $(DESTDIR)$(mandir)/man8/nsd-checkconf.8 $(DESTDIR)$(mandir)/man8/nsd-checkzone.8 $(DESTDIR)$(mandir)/man8/nsd-control.8
        rm -f -- $(DESTDIR)$(pidfile)
        @echo
-       @echo "You still need to remove $(DESTDIR)$(configdir), $(DESTDIR)$(piddir), $(DESTDIR)$(dbfile) directory by hand."
+       @echo "You still need to remove $(DESTDIR)$(configdir), $(DESTDIR)$(piddir), $(DESTDIR)$(xfrdfile), $(DESTDIR)$(zonelistfile) directory by hand."
 
 test: 
 
@@ -435,15 +433,15 @@ configparser.o: configparser.c config.h $(srcdir)/options.h \
  $(srcdir)/namedb.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/packet.h configparser.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)/zonec.h $(srcdir)/nsec3.h $(srcdir)/difffile.h $(srcdir)/nsd.h $(srcdir)/edns.h \
- $(srcdir)/bitset.h $(srcdir)/ixfr.h $(srcdir)/query.h $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/ixfrcreate.h
+ $(srcdir)/udb.h $(srcdir)/zonec.h $(srcdir)/nsec3.h $(srcdir)/difffile.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/ixfr.h $(srcdir)/query.h \
+ $(srcdir)/packet.h $(srcdir)/tsig.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)/options.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/ixfr.h $(srcdir)/query.h $(srcdir)/packet.h $(srcdir)/tsig.h
+ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/udb.h $(srcdir)/options.h $(srcdir)/nsd.h \
+ $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/ixfr.h $(srcdir)/query.h $(srcdir)/packet.h $(srcdir)/tsig.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)/nsec3.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/tsig.h $(srcdir)/ixfr.h $(srcdir)/zonec.h
+ $(srcdir)/options.h $(srcdir)/udb.h $(srcdir)/xfrd-disk.h $(srcdir)/packet.h $(srcdir)/rdata.h $(srcdir)/nsec3.h $(srcdir)/nsd.h $(srcdir)/edns.h \
+ $(srcdir)/bitset.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/tsig.h $(srcdir)/ixfr.h $(srcdir)/zonec.h $(srcdir)/xfrd-catalog-zones.h $(srcdir)/xfrd.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)/bitset.h $(srcdir)/packet.h $(srcdir)/tsig.h
@@ -472,8 +470,7 @@ netio.o: $(srcdir)/netio.c config.h $(srcdir)/netio.h $(srcdir)/region-allocator
  $(srcdir)/util.h
 nsd.o: $(srcdir)/nsd.c config.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \
  $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/bitset.h $(srcdir)/options.h $(srcdir)/rbtree.h $(srcdir)/tsig.h $(srcdir)/dname.h \
- $(srcdir)/remote.h $(srcdir)/xfrd-disk.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/util/proxy_protocol.h \
- config.h
+ $(srcdir)/remote.h $(srcdir)/xfrd-disk.h $(srcdir)/ipc.h $(srcdir)/netio.h $(srcdir)/util/proxy_protocol.h config.h
 nsd-checkconf.o: $(srcdir)/nsd-checkconf.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h \
  $(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)/bitset.h $(srcdir)/packet.h
@@ -486,15 +483,15 @@ nsd-control.o: $(srcdir)/nsd-control.c config.h $(srcdir)/util.h $(srcdir)/tsig.
  $(srcdir)/dns.h $(srcdir)/radtree.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)/bitset.h $(srcdir)/tsig.h $(srcdir)/dname.h $(srcdir)/options.h $(srcdir)/rbtree.h \
- $(srcdir)/namedb.h $(srcdir)/radtree.h
+ $(srcdir)/namedb.h $(srcdir)/radtree.h $(srcdir)/difffile.h $(srcdir)/udb.h
 nsec3.o: $(srcdir)/nsec3.c config.h $(srcdir)/nsec3.h $(srcdir)/iterated_hash.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)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/answer.h $(srcdir)/packet.h $(srcdir)/query.h $(srcdir)/tsig.h \
- $(srcdir)/udb.h $(srcdir)/options.h
+ $(srcdir)/options.h
 options.o: $(srcdir)/options.c config.h $(srcdir)/options.h \
  $(srcdir)/region-allocator.h $(srcdir)/rbtree.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/util.h \
  $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/ixfr.h $(srcdir)/difffile.h \
- $(srcdir)/udb.h $(srcdir)/rrl.h configparser.h
+ $(srcdir)/udb.h $(srcdir)/rrl.h $(srcdir)/xfrd.h configparser.h
 packet.o: $(srcdir)/packet.c config.h $(srcdir)/packet.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)/query.h \
  $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/tsig.h $(srcdir)/rdata.h
@@ -511,8 +508,8 @@ region-allocator.o: $(srcdir)/region-allocator.c config.h \
  $(srcdir)/region-allocator.h $(srcdir)/util.h
 remote.o: $(srcdir)/remote.c config.h $(srcdir)/remote.h $(srcdir)/util.h $(srcdir)/xfrd.h \
  $(srcdir)/rbtree.h $(srcdir)/region-allocator.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/dns.h $(srcdir)/radtree.h \
- $(srcdir)/options.h $(srcdir)/tsig.h $(srcdir)/xfrd-notify.h $(srcdir)/xfrd-tcp.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h \
- $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/ipc.h $(srcdir)/netio.h
+ $(srcdir)/options.h $(srcdir)/tsig.h $(srcdir)/xfrd-catalog-zones.h $(srcdir)/xfrd-notify.h $(srcdir)/xfrd-tcp.h $(srcdir)/nsd.h \
+ $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/ipc.h $(srcdir)/netio.h
 rrl.o: $(srcdir)/rrl.c config.h $(srcdir)/rrl.h $(srcdir)/query.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)/nsd.h $(srcdir)/edns.h \
  $(srcdir)/bitset.h $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/lookup3.h $(srcdir)/options.h
@@ -520,15 +517,14 @@ server.o: $(srcdir)/server.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)
  $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/bitset.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)/rrl.h \
- $(srcdir)/ixfr.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/verify.h $(srcdir)/util/proxy_protocol.h config.h
+ $(srcdir)/ixfr.h $(srcdir)/verify.h $(srcdir)/util/proxy_protocol.h config.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 $(srcdir)/edns.h $(srcdir)/bitset.h
 tsig-openssl.o: $(srcdir)/tsig-openssl.c config.h $(srcdir)/tsig-openssl.h \
  $(srcdir)/region-allocator.h $(srcdir)/tsig.h $(srcdir)/buffer.h $(srcdir)/util.h $(srcdir)/dname.h
-udb.o: $(srcdir)/udb.c config.h $(srcdir)/udb.h $(srcdir)/lookup3.h $(srcdir)/util.h \
- $(srcdir)/radtree.h
+udb.o: $(srcdir)/udb.c config.h $(srcdir)/udb.h $(srcdir)/lookup3.h $(srcdir)/util.h
 util.o: $(srcdir)/util.c config.h $(srcdir)/util.h $(srcdir)/region-allocator.h $(srcdir)/dname.h \
  $(srcdir)/buffer.h $(srcdir)/namedb.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/rdata.h $(srcdir)/zonec.h $(srcdir)/nsd.h $(srcdir)/edns.h \
  $(srcdir)/bitset.h
@@ -537,9 +533,13 @@ verify.o: $(srcdir)/verify.c config.h $(srcdir)/region-allocator.h $(srcdir)/nam
  $(srcdir)/options.h $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/verify.h $(srcdir)/popen3.h
 xfrd.o: $(srcdir)/xfrd.c config.h $(srcdir)/xfrd.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)/tsig.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h $(srcdir)/xfrd-notify.h $(srcdir)/netio.h $(srcdir)/nsd.h \
- $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/packet.h $(srcdir)/rdata.h $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/rrl.h \
- $(srcdir)/query.h $(srcdir)/dnstap/dnstap_collector.h
+ $(srcdir)/options.h $(srcdir)/tsig.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h $(srcdir)/xfrd-notify.h \
+ $(srcdir)/xfrd-catalog-zones.h $(srcdir)/netio.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/packet.h $(srcdir)/rdata.h \
+ $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/rrl.h $(srcdir)/query.h
+xfrd-catalog-zones.o: $(srcdir)/xfrd-catalog-zones.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)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h $(srcdir)/packet.h \
+ $(srcdir)/xfrd-catalog-zones.h $(srcdir)/xfrd.h $(srcdir)/tsig.h $(srcdir)/xfrd-notify.h
 xfrd-disk.o: $(srcdir)/xfrd-disk.c config.h $(srcdir)/xfrd-disk.h $(srcdir)/xfrd.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)/tsig.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h
@@ -549,20 +549,20 @@ xfrd-notify.o: $(srcdir)/xfrd-notify.c config.h $(srcdir)/xfrd-notify.h \
 xfrd-tcp.o: $(srcdir)/xfrd-tcp.c config.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h \
  $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/bitset.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd.h $(srcdir)/rbtree.h \
  $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/options.h $(srcdir)/tsig.h $(srcdir)/packet.h $(srcdir)/xfrd-disk.h
-xfr-inspect.o: $(srcdir)/xfr-inspect.c config.h $(srcdir)/udb.h \
- $(srcdir)/dns.h $(srcdir)/util.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/packet.h $(srcdir)/namedb.h \
- $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/rdata.h $(srcdir)/difffile.h $(srcdir)/options.h
+xfr-inspect.o: $(srcdir)/xfr-inspect.c config.h $(srcdir)/util.h $(srcdir)/buffer.h \
+ $(srcdir)/region-allocator.h $(srcdir)/packet.h $(srcdir)/dns.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \
+ $(srcdir)/rdata.h $(srcdir)/difffile.h $(srcdir)/options.h $(srcdir)/udb.h
 zlexer.o: zlexer.c config.h $(srcdir)/zonec.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 zparser.h
 zonec.o: $(srcdir)/zonec.c config.h $(srcdir)/zonec.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)/rdata.h \
  zparser.h $(srcdir)/options.h $(srcdir)/nsec3.h
 zparser.o: zparser.c config.h $(srcdir)/dname.h $(srcdir)/buffer.h \
- $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/namedb.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/zonec.h
+ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/namedb.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/zonec.h \
+ zparser.h
 b64_ntop.o: $(srcdir)/compat/b64_ntop.c config.h
 b64_pton.o: $(srcdir)/compat/b64_pton.c config.h
 basename.o: $(srcdir)/compat/basename.c
-cpuset.o: $(srcdir)/compat/cpuset.c config.h
 explicit_bzero.o: $(srcdir)/compat/explicit_bzero.c config.h
 fake-rfc2553.o: $(srcdir)/compat/fake-rfc2553.c $(srcdir)/compat/fake-rfc2553.h config.h
 inet_aton.o: $(srcdir)/compat/inet_aton.c config.h
@@ -599,8 +599,7 @@ cutest_iter.o: $(srcdir)/tpkg/cutest/cutest_iter.c config.h $(srcdir)/nsd.h \
 cutest_namedb.o: $(srcdir)/tpkg/cutest/cutest_namedb.c config.h \
  $(srcdir)/tpkg/cutest/cutest.h $(srcdir)/region-allocator.h $(srcdir)/options.h $(srcdir)/region-allocator.h \
  $(srcdir)/rbtree.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/nsec3.h $(srcdir)/udb.h \
- $(srcdir)/udb.h $(srcdir)/difffile.h $(srcdir)/namedb.h $(srcdir)/options.h $(srcdir)/zonec.h $(srcdir)/nsd.h \
- $(srcdir)/edns.h $(srcdir)/bitset.h
+ $(srcdir)/difffile.h $(srcdir)/namedb.h $(srcdir)/options.h $(srcdir)/udb.h $(srcdir)/zonec.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/bitset.h
 cutest_options.o: $(srcdir)/tpkg/cutest/cutest_options.c config.h \
  $(srcdir)/tpkg/cutest/cutest.h $(srcdir)/region-allocator.h $(srcdir)/options.h $(srcdir)/region-allocator.h \
  $(srcdir)/rbtree.h $(srcdir)/util.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/util.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/bitset.h
index f27615b..6a01dc5 100644 (file)
@@ -2,7 +2,10 @@
 # Copyright 2009, Wouter Wijngaards, NLnet Labs.   
 # BSD licensed.
 #
-# Version 46
+# Version 48
+# 2024-01-16 fix to add -l:libssp.a to -lcrypto link check.
+#           and check for getaddrinfo with only header.
+# 2024-01-15 fix to add crypt32 to -lcrypto link check when checking for gdi32.
 # 2023-05-04 fix to remove unused whitespace.
 # 2023-01-26 fix -Wstrict-prototypes.
 # 2022-09-01 fix checking if nonblocking sockets work on OpenBSD.
@@ -707,7 +710,7 @@ AC_DEFUN([ACX_SSL_CHECKS], [
                    LIBSSL_LDFLAGS="$LIBSSL_LDFLAGS -L$ssldir_lib"
                    ACX_RUNTIME_PATH_ADD([$ssldir_lib])
            fi
-        
+
             AC_MSG_CHECKING([for EVP_sha256 in -lcrypto])
             LIBS="$LIBS -lcrypto"
             LIBSSL_LIBS="$LIBSSL_LIBS -lcrypto"
@@ -732,40 +735,73 @@ AC_DEFUN([ACX_SSL_CHECKS], [
                   ]])],[
                     AC_DEFINE([HAVE_EVP_SHA256], 1,
                         [If you have EVP_sha256])
-                    AC_MSG_RESULT(yes) 
+                    AC_MSG_RESULT(yes)
                   ],[
                     AC_MSG_RESULT(no)
                     LIBS="$BAKLIBS"
                     LIBSSL_LIBS="$BAKSSLLIBS"
-                    LIBS="$LIBS -ldl"
-                    LIBSSL_LIBS="$LIBSSL_LIBS -ldl"
-                    AC_MSG_CHECKING([if -lcrypto needs -ldl])
-                    AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[
-                        int EVP_sha256(void);
-                        (void)EVP_sha256();
-                      ]])],[
-                        AC_DEFINE([HAVE_EVP_SHA256], 1,
-                            [If you have EVP_sha256])
-                        AC_MSG_RESULT(yes) 
-                      ],[
-                        AC_MSG_RESULT(no)
-                        LIBS="$BAKLIBS"
-                        LIBSSL_LIBS="$BAKSSLLIBS"
-                        LIBS="$LIBS -ldl -pthread"
-                        LIBSSL_LIBS="$LIBSSL_LIBS -ldl -pthread"
-                        AC_MSG_CHECKING([if -lcrypto needs -ldl -pthread])
-                        AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[
-                            int EVP_sha256(void);
-                            (void)EVP_sha256();
-                          ]])],[
-                            AC_DEFINE([HAVE_EVP_SHA256], 1,
-                                [If you have EVP_sha256])
-                            AC_MSG_RESULT(yes) 
-                          ],[
-                            AC_MSG_RESULT(no)
-                            AC_MSG_ERROR([OpenSSL found in $ssldir, but version 0.9.7 or higher is required])
+
+                   LIBS="$LIBS -lgdi32 -lws2_32 -lcrypt32"
+                   LIBSSL_LIBS="$LIBSSL_LIBS -lgdi32 -lws2_32 -lcrypt32"
+                    AC_MSG_CHECKING([if -lcrypto needs -lgdi32 -lws2_32 -lcrypt32])
+                   AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[
+                       int EVP_sha256(void);
+                       (void)EVP_sha256();
+                     ]])],[
+                       AC_DEFINE([HAVE_EVP_SHA256], 1,
+                           [If you have EVP_sha256])
+                       AC_MSG_RESULT(yes)
+                     ],[
+                       AC_MSG_RESULT(no)
+                       LIBS="$BAKLIBS"
+                       LIBSSL_LIBS="$BAKSSLLIBS"
+
+                       LIBS="$LIBS -lgdi32 -lws2_32 -lcrypt32 -l:libssp.a"
+                       LIBSSL_LIBS="$LIBSSL_LIBS -lgdi32 -lws2_32 -lcrypt32 -l:libssp.a"
+                       AC_MSG_CHECKING([if -lcrypto needs -lgdi32 -lws2_32 -lcrypt32 -l:libssp.a])
+                       AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[
+                           int EVP_sha256(void);
+                           (void)EVP_sha256();
+                         ]])],[
+                           AC_DEFINE([HAVE_EVP_SHA256], 1,
+                               [If you have EVP_sha256])
+                           AC_MSG_RESULT(yes)
+                         ],[
+                           AC_MSG_RESULT(no)
+                           LIBS="$BAKLIBS"
+                           LIBSSL_LIBS="$BAKSSLLIBS"
+
+                           LIBS="$LIBS -ldl"
+                           LIBSSL_LIBS="$LIBSSL_LIBS -ldl"
+                           AC_MSG_CHECKING([if -lcrypto needs -ldl])
+                           AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[
+                               int EVP_sha256(void);
+                               (void)EVP_sha256();
+                             ]])],[
+                               AC_DEFINE([HAVE_EVP_SHA256], 1,
+                                   [If you have EVP_sha256])
+                               AC_MSG_RESULT(yes)
+                             ],[
+                               AC_MSG_RESULT(no)
+                               LIBS="$BAKLIBS"
+                               LIBSSL_LIBS="$BAKSSLLIBS"
+                               LIBS="$LIBS -ldl -pthread"
+                               LIBSSL_LIBS="$LIBSSL_LIBS -ldl -pthread"
+                               AC_MSG_CHECKING([if -lcrypto needs -ldl -pthread])
+                               AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[
+                                   int EVP_sha256(void);
+                                   (void)EVP_sha256();
+                                 ]])],[
+                                   AC_DEFINE([HAVE_EVP_SHA256], 1,
+                                       [If you have EVP_sha256])
+                                   AC_MSG_RESULT(yes)
+                                 ],[
+                                   AC_MSG_RESULT(no)
+                                   AC_MSG_ERROR([OpenSSL found in $ssldir, but version 0.9.7 or higher is required])
+                               ])
+                           ])
                        ])
-                    ])
+                   ])
                 ])
             ])
         fi
@@ -779,7 +815,7 @@ AC_CHECK_HEADERS([openssl/rand.h],,, [AC_INCLUDES_DEFAULT])
 
 dnl Check for SSL, where SSL is mandatory
 dnl Adds --with-ssl option, searches for openssl and defines HAVE_SSL if found
-dnl Setup of CPPFLAGS, CFLAGS.  Adds -lcrypto to LIBS. 
+dnl Setup of CPPFLAGS, CFLAGS.  Adds -lcrypto to LIBS.
 dnl Checks main header files of SSL.
 dnl
 AC_DEFUN([ACX_WITH_SSL],
@@ -872,7 +908,7 @@ dnl see if on windows
 if test "$ac_cv_header_windows_h" = "yes"; then
        AC_DEFINE(USE_WINSOCK, 1, [Whether the windows socket API is used])
        USE_WINSOCK="1"
-       if echo $LIBS | grep 'lws2_32' >/dev/null; then
+       if echo "$LIBS" | grep 'lws2_32' >/dev/null; then
                :
        else
                LIBS="$LIBS -lws2_32"
@@ -880,6 +916,24 @@ if test "$ac_cv_header_windows_h" = "yes"; then
 fi
 ],
 dnl no quick getaddrinfo, try mingw32 and winsock2 library.
+dnl perhaps getaddrinfo needs only the include
+AC_LINK_IFELSE(
+[AC_LANG_PROGRAM(
+[
+#ifdef HAVE_WS2TCPIP_H
+#include <ws2tcpip.h>
+#endif
+],
+[
+        (void)getaddrinfo(NULL, NULL, NULL, NULL);
+]
+)],
+[
+ac_cv_func_getaddrinfo="yes"
+AC_DEFINE(USE_WINSOCK, 1, [Whether the windows socket API is used])
+USE_WINSOCK="1"
+],
+
 ORIGLIBS="$LIBS"
 LIBS="$LIBS -lws2_32"
 AC_LINK_IFELSE(
@@ -904,6 +958,7 @@ ac_cv_func_getaddrinfo="no"
 LIBS="$ORIGLIBS"
 ])
 )
+)
 
 AC_MSG_RESULT($ac_cv_func_getaddrinfo)
 if test $ac_cv_func_getaddrinfo = yes; then
index 9cf5321..1967173 100644 (file)
@@ -260,6 +260,24 @@ buffer_write(buffer_type *buffer, const void *data, size_t count)
        buffer->_position += count;
 }
 
+static inline int
+try_buffer_write_at(buffer_type *buffer, size_t at, const void *data, size_t count)
+{
+       if(!buffer_available_at(buffer, at, count))
+               return 0;
+       memcpy(buffer->_data + at, data, count);
+       return 1;
+}
+
+static inline int
+try_buffer_write(buffer_type *buffer, const void *data, size_t count)
+{
+       if(!try_buffer_write_at(buffer, buffer->_position, data, count))
+               return 0;
+       buffer->_position += count;
+       return 1;
+}
+
 static inline void
 buffer_write_string_at(buffer_type *buffer, size_t at, const char *str)
 {
@@ -272,6 +290,18 @@ buffer_write_string(buffer_type *buffer, const char *str)
        buffer_write(buffer, str, strlen(str));
 }
 
+static inline int
+try_buffer_write_string_at(buffer_type *buffer, size_t at, const char *str)
+{
+       return try_buffer_write_at(buffer, at, str, strlen(str));
+}
+
+static inline int
+try_buffer_write_string(buffer_type *buffer, const char *str)
+{
+       return try_buffer_write(buffer, str, strlen(str));
+}
+
 static inline void
 buffer_write_u8_at(buffer_type *buffer, size_t at, uint8_t data)
 {
@@ -328,6 +358,78 @@ buffer_write_u64(buffer_type *buffer, uint64_t data)
        buffer->_position += sizeof(data);
 }
 
+static inline int
+try_buffer_write_u8_at(buffer_type *buffer, size_t at, uint8_t data)
+{
+       if(!buffer_available_at(buffer, at, sizeof(data)))
+               return 0;
+       buffer->_data[at] = data;
+       return 1;
+}
+
+static inline int
+try_buffer_write_u8(buffer_type *buffer, uint8_t data)
+{
+       if(!try_buffer_write_u8_at(buffer, buffer->_position, data))
+               return 0;
+       buffer->_position += sizeof(data);
+       return 1;
+}
+
+static inline int
+try_buffer_write_u16_at(buffer_type *buffer, size_t at, uint16_t data)
+{
+       if(!buffer_available_at(buffer, at, sizeof(data)))
+               return 0;
+       write_uint16(buffer->_data + at, data);
+       return 1;
+}
+
+static inline int
+try_buffer_write_u16(buffer_type *buffer, uint16_t data)
+{
+       if(!try_buffer_write_u16_at(buffer, buffer->_position, data))
+               return 0;
+       buffer->_position += sizeof(data);
+       return 1;
+}
+
+static inline int
+try_buffer_write_u32_at(buffer_type *buffer, size_t at, uint32_t data)
+{
+       if(!buffer_available_at(buffer, at, sizeof(data)))
+               return 0;
+       write_uint32(buffer->_data + at, data);
+       return 1;
+}
+
+static inline int
+try_buffer_write_u32(buffer_type *buffer, uint32_t data)
+{
+       if(!try_buffer_write_u32_at(buffer, buffer->_position, data))
+               return 0;
+       buffer->_position += sizeof(data);
+       return 1;
+}
+
+static inline int
+try_buffer_write_u64_at(buffer_type *buffer, size_t at, uint64_t data)
+{
+       if(!buffer_available_at(buffer, at, sizeof(data)))
+               return 0;
+       write_uint64(buffer->_data + at, data);
+       return 1;
+}
+
+static inline int
+try_buffer_write_u64(buffer_type *buffer, uint64_t data)
+{
+       if(!try_buffer_write_u64_at(buffer, buffer->_position, data))
+               return 0;
+       buffer->_position += sizeof(data);
+       return 1;
+}
+
 static inline void
 buffer_read_at(buffer_type *buffer, size_t at, void *data, size_t count)
 {
index eda3cf9..1a08215 100644 (file)
 /* Define to 1 to enable dnstap support */
 #undef USE_DNSTAP
 
+/* Define this to show the role of processes in the logfile for debugging
+   purposes. */
+#undef USE_LOG_PROCESS_ROLE
+
 /* Define if you want to use internal select based events */
 #undef USE_MINI_EVENT
 
index 9aed022..03fa21b 100644 (file)
@@ -298,7 +298,8 @@ 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;}
+multi-master-check{COLON}      { LEXOUT(("v(%s) ", yytext)); return VAR_MULTI_PRIMARY_CHECK;}
+multi-primary-check{COLON}     { LEXOUT(("v(%s) ", yytext)); return VAR_MULTI_PRIMARY_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;}
 tls-service-pem{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_PEM;}
@@ -318,6 +319,9 @@ verifier{COLON}             { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFIER; }
 verifier-count{COLON}  { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFIER_COUNT; }
 verifier-feed-zone{COLON}      { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFIER_FEED_ZONE; }
 verifier-timeout{COLON}                { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFIER_TIMEOUT; }
+catalog{COLON}         { LEXOUT(("v(%s) ", yytext)); return VAR_CATALOG; }
+catalog-member-pattern{COLON}  { LEXOUT(("v(%s) ", yytext)); return VAR_CATALOG_MEMBER_PATTERN; }
+catalog-producer-zone{COLON}   { LEXOUT(("v(%s) ", yytext)); return VAR_CATALOG_PRODUCER_ZONE; }
 {NEWLINE}              { LEXOUT(("NL\n")); cfg_parser->line++;}
 
 servers={UNQUOTEDLETTER}*      {
index 9af2c83..a5f4db6 100644 (file)
@@ -33,6 +33,7 @@ extern config_parser_state_type *cfg_parser;
 static void append_acl(struct acl_options **list, struct acl_options *acl);
 static void add_to_last_acl(struct acl_options **list, char *ac);
 static int parse_boolean(const char *str, int *bln);
+static int parse_catalog_role(const char *str, int *role);
 static int parse_expire_expr(const char *str, long long *num, uint8_t *expr);
 static int parse_number(const char *str, long long *num);
 static int parse_range(const char *str, long long *low, long long *high);
@@ -53,6 +54,7 @@ struct component {
   struct cpu_option *cpu;
   char **strv;
   struct component *comp;
+  int role;
 }
 
 %token <str> STRING
@@ -63,6 +65,7 @@ struct component {
 %type <cpu> cpus
 %type <strv> command
 %type <comp> arguments
+%type <role> catalog_role
 
 /* server */
 %token VAR_SERVER
@@ -194,7 +197,7 @@ struct component {
 %token VAR_MAX_RETRY_TIME
 %token VAR_MIN_RETRY_TIME
 %token VAR_MIN_EXPIRE_TIME
-%token VAR_MULTI_MASTER_CHECK
+%token VAR_MULTI_PRIMARY_CHECK
 %token VAR_SIZE_LIMIT_XFR
 %token VAR_ZONESTATS
 %token VAR_INCLUDE_PATTERN
@@ -202,6 +205,9 @@ struct component {
 %token VAR_IXFR_SIZE
 %token VAR_IXFR_NUMBER
 %token VAR_CREATE_IXFR
+%token VAR_CATALOG
+%token VAR_CATALOG_MEMBER_PATTERN
+%token VAR_CATALOG_PRODUCER_ZONE
 
 /* zone */
 %token VAR_ZONE
@@ -900,13 +906,15 @@ pattern_or_zone_option:
         yyerror("expected a number greater than zero");
       }
     }
-  | VAR_MULTI_MASTER_CHECK boolean
-    { cfg_parser->pattern->multi_master_check = (int)$2; }
+  | VAR_MULTI_PRIMARY_CHECK boolean
+    { cfg_parser->pattern->multi_primary_check = (int)$2; }
   | VAR_INCLUDE_PATTERN STRING
     { config_apply_pattern(cfg_parser->pattern, $2); }
   | VAR_REQUEST_XFR STRING STRING
     {
       acl_options_type *acl = parse_acl_info(cfg_parser->opt->region, $2, $3);
+      if(cfg_parser->pattern->catalog_role == CATALOG_ROLE_PRODUCER)
+        yyerror("catalog producer zones cannot be secondary zones");
       if(acl->blocked)
         yyerror("blocked address used for request-xfr");
       if(acl->rangetype != acl_range_single)
@@ -1035,7 +1043,32 @@ pattern_or_zone_option:
   | VAR_VERIFIER_FEED_ZONE boolean
     { cfg_parser->pattern->verifier_feed_zone = $2; }
   | VAR_VERIFIER_TIMEOUT number
-    { cfg_parser->pattern->verifier_timeout = $2; } ;
+    { cfg_parser->pattern->verifier_timeout = $2; } 
+  | VAR_CATALOG catalog_role
+    {
+      if($2 == CATALOG_ROLE_PRODUCER && cfg_parser->pattern->request_xfr)
+        yyerror("catalog producer zones cannot be secondary zones");
+      cfg_parser->pattern->catalog_role = $2;
+      cfg_parser->pattern->catalog_role_is_default = 0;
+    }
+  | VAR_CATALOG_MEMBER_PATTERN STRING 
+    { 
+      cfg_parser->pattern->catalog_member_pattern = region_strdup(cfg_parser->opt->region, $2); 
+    }
+  | VAR_CATALOG_PRODUCER_ZONE STRING 
+    {
+      dname_type *dname;
+
+      if(cfg_parser->zone) {
+        yyerror("catalog-producer-zone option is for patterns only and cannot "
+                "be used in a zone clause");
+      } else if(!(dname = (dname_type *)dname_parse(cfg_parser->opt->region, $2))) {
+        yyerror("bad catalog producer name %s", $2);
+      } else {
+        region_recycle(cfg_parser->opt->region, dname, dname_total_size(dname));
+        cfg_parser->pattern->catalog_producer_zone = region_strdup(cfg_parser->opt->region, $2); 
+      }
+    };
 
 verify:
     VAR_VERIFY verify_block ;
@@ -1146,6 +1179,15 @@ tlsauth_option:
        { char *tls_auth_name = region_strdup(cfg_parser->opt->region, $1);
          add_to_last_acl(&cfg_parser->pattern->request_xfr, tls_auth_name);} ;
 
+catalog_role:
+    STRING
+    {
+      if(!parse_catalog_role($1, &$$)) {
+        yyerror("expected consumer or producer");
+        YYABORT; /* trigger a parser error */
+      }
+    } ;
+
 %%
 
 static void
@@ -1264,3 +1306,18 @@ parse_range(const char *str, long long *low, long long *high)
 
        return 0;
 }
+
+static int
+parse_catalog_role(const char *str, int *role)
+{
+       if(strcasecmp(str, "consumer") == 0) {
+               *role = CATALOG_ROLE_CONSUMER;
+       } else if(strcmp(str, "producer") == 0) {
+               *role = CATALOG_ROLE_PRODUCER;
+       } else {
+               return 0;
+       }
+       return 1;
+}
+
+
index b6b378c..65cea56 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.8.0.
+# Generated by GNU Autoconf 2.69 for NSD 4.9.1.
 #
 # Report bugs to <https://github.com/NLnetLabs/nsd/issues or nsd-bugs@nlnetlabs.nl>.
 #
@@ -581,8 +581,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='NSD'
 PACKAGE_TARNAME='nsd'
-PACKAGE_VERSION='4.8.0'
-PACKAGE_STRING='NSD 4.8.0'
+PACKAGE_VERSION='4.9.1'
+PACKAGE_STRING='NSD 4.9.1'
 PACKAGE_BUGREPORT='https://github.com/NLnetLabs/nsd/issues or nsd-bugs@nlnetlabs.nl'
 PACKAGE_URL=''
 
@@ -738,6 +738,7 @@ enable_ipv6
 enable_bind8_stats
 enable_zone_stats
 enable_checking
+enable_log_role
 enable_memclean
 enable_ratelimit
 enable_ratelimit_default_is_off
@@ -1327,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.8.0 to adapt to many kinds of systems.
+\`configure' configures NSD 4.9.1 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1389,7 +1390,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of NSD 4.8.0:";;
+     short | recursive ) echo "Configuration of NSD 4.9.1:";;
    esac
   cat <<\_ACEOF
 
@@ -1413,6 +1414,8 @@ Optional Features:
   --enable-zone-stats     Enable per-zone statistics gathering (needs
                           --enable-bind8-stats)
   --enable-checking       Enable internal runtime checks
+  --enable-log-role       Shows the role of processes in the logfile (enable
+                          this only for debugging purposes)
   --enable-memclean       Cleanup memory (at exit) for eg. valgrind, memcheck
   --enable-ratelimit      Enable rate limiting
   --enable-ratelimit-default-is-off
@@ -1562,7 +1565,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-NSD configure 4.8.0
+NSD configure 4.9.1
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2271,7 +2274,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.8.0, which was
+It was created by NSD $as_me 4.9.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -9020,6 +9023,24 @@ $as_echo "#define NDEBUG /**/" >>confdefs.h
                 ;;
 esac
 
+# Check whether --enable-log-role was given.
+if test "${enable_log_role+set}" = set; then :
+  enableval=$enable_log_role;
+fi
+
+case "$enable_log_role" in
+        yes)
+
+cat >>confdefs.h <<_ACEOF
+#define USE_LOG_PROCESS_ROLE /**/
+_ACEOF
+
+               ;;
+        no|*)
+                ;;
+esac
+
+
 # Check whether --enable-memclean was given.
 if test "${enable_memclean+set}" = set; then :
   enableval=$enable_memclean;
@@ -10890,7 +10911,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.8.0, which was
+This file was extended by NSD $as_me 4.9.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -10952,7 +10973,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.8.0
+NSD config.status 4.9.1
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
index a516fc2..5422854 100644 (file)
@@ -5,7 +5,7 @@ dnl
 sinclude(acx_nlnetlabs.m4)
 sinclude(dnstap/dnstap.m4)
 
-AC_INIT([NSD],[4.8.0],[https://github.com/NLnetLabs/nsd/issues or nsd-bugs@nlnetlabs.nl])
+AC_INIT([NSD],[4.9.1],[https://github.com/NLnetLabs/nsd/issues or nsd-bugs@nlnetlabs.nl])
 AC_CONFIG_HEADERS([config.h])
 
 #
@@ -1004,6 +1004,16 @@ case "$enable_checking" in
                 ;;
 esac
 
+AC_ARG_ENABLE(log-role, AS_HELP_STRING([--enable-log-role],[Shows the role of processes in the logfile (enable this only for debugging purposes)]))
+case "$enable_log_role" in
+        yes)
+               AC_DEFINE_UNQUOTED([USE_LOG_PROCESS_ROLE], [], [Define this to show the role of processes in the logfile for debugging purposes.])
+               ;;
+        no|*)
+                ;;
+esac
+
+
 AC_ARG_ENABLE(memclean, AS_HELP_STRING([--enable-memclean],[Cleanup memory (at exit) for eg. valgrind, memcheck]))
 if test "$enable_memclean" = "yes"; then AC_DEFINE_UNQUOTED([MEMCLEAN], [1], [Define this to cleanup memory at exit (eg. for valgrind, etc.)])
 fi
index 51aa2d7..c4d18ad 100644 (file)
@@ -24,6 +24,7 @@
 #include "rrl.h"
 #include "ixfr.h"
 #include "zonec.h"
+#include "xfrd-catalog-zones.h"
 
 static int
 write_64(FILE *out, uint64_t val)
@@ -919,7 +920,7 @@ find_or_create_zone(namedb_type* db, const dname_type* zone_name,
                         * by xfrd, who wrote the AXFR or IXFR to disk, so we only
                         * need to add it to our config.
                         * This process does not need linesize and offset zonelist */
-                       zopt = zone_list_zone_insert(opt, zstr, patname, 0, 0);
+                       zopt = zone_list_zone_insert(opt, zstr, patname);
                        if(!zopt)
                                return 0;
                }
@@ -1273,7 +1274,7 @@ check_for_bad_serial(namedb_type* db, const char* zone_str, uint32_t old_serial)
        return 0;
 }
 
-static int
+int
 apply_ixfr_for_zone(nsd_type* nsd, zone_type* zone, FILE* in,
        struct nsd_options* ATTR_UNUSED(opt), udb_base* taskudb, udb_ptr* last_task,
        uint32_t xfrfilenr)
@@ -1348,7 +1349,7 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zone, FILE* in,
        if(check_for_bad_serial(nsd->db, zone_buf, old_serial)) {
                DEBUG(DEBUG_XFRD,1, (LOG_ERR,
                        "skipping diff file commit with bad serial"));
-               return 1;
+               return -2; /* Success in "main" process, failure in "xfrd" */
        }
 
        if(!zone->is_skipped)
@@ -1372,7 +1373,7 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zone, FILE* in,
                                diff_update_commit(
                                        zone_buf, DIFF_CORRUPT, nsd, xfrfilenr);
                                /* the udb is still dirty, it is bad */
-                               exit(1);
+                               return -1; /* Fatal! */
                        } else if(ret == 2) {
                                break;
                        }
@@ -1402,12 +1403,12 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zone, FILE* in,
                if(softfail && taskudb && !is_axfr) {
                        log_msg(LOG_ERR, "Failed to apply IXFR cleanly "
                                "(deletes nonexistent RRs, adds existing RRs). "
-                               "Zone %s contents is different from master, "
+                               "Zone %s contents is different from primary, "
                                "starting AXFR. Transfer %s", zone_buf, log_buf);
                        /* add/del failures in IXFR, get an AXFR */
                        diff_update_commit(
                                zone_buf, DIFF_INCONSISTENT, nsd, xfrfilenr);
-                       exit(1);
+                       return -1; /* Fatal! */
                }
                if(ixfr_store)
                        ixfr_store_finish(ixfr_store, nsd, log_buf);
@@ -1598,6 +1599,7 @@ void task_new_check_zonefiles(udb_base* udb, udb_ptr* last,
        const dname_type* zone)
 {
        udb_ptr e;
+       xfrd_check_catalog_consumer_zonefiles(zone);
        DEBUG(DEBUG_IPC,1, (LOG_INFO, "add task checkzonefiles"));
        if(!task_create_new_elem(udb, last, &e, sizeof(struct task_list_d) +
                (zone?dname_total_size(zone):0), zone)) {
@@ -2111,13 +2113,23 @@ task_process_apply_xfr(struct nsd* nsd, udb_base* udb, udb_ptr *last_task,
                return;
        }
        /* read and apply zone transfer */
-       if(!apply_ixfr_for_zone(nsd, zone, df, nsd->options, udb,
-               last_task, TASKLIST(task)->yesno)) {
+       switch(apply_ixfr_for_zone(nsd, zone, df, nsd->options, udb, last_task,
+                               TASKLIST(task)->yesno)) {
+       case 1: /* Success */
+               break;
+
+       case 0: /* Failure */
                /* soainfo_gone will be communicated from server_reload, unless
                   preceding updates have been applied  */
                zone->is_skipped = 1;
-       }
+               break;
+
+       case -1:/* Fatal */
+               exit(1);
+               break;
 
+       default:break;
+       }
        fclose(df);
 }
 
index ba777c8..4a88d4b 100644 (file)
@@ -66,6 +66,11 @@ int add_RR(namedb_type* db, const dname_type* dname,
        buffer_type* packet, size_t rdatalen, zone_type *zone,
        int* softfail);
 
+/* apply the xfr file identified by xfrfilenr to zone */
+int apply_ixfr_for_zone(struct nsd* nsd, zone_type* zone, FILE* in,
+        struct nsd_options* opt, udb_base* taskudb, udb_ptr* last_task,
+        uint32_t xfrfilenr);
+
 enum soainfo_hint {
        soainfo_ok,
        soainfo_gone,
index 487d253..eced171 100644 (file)
@@ -392,6 +392,12 @@ const char *
 dname_to_string(const dname_type *dname, const dname_type *origin)
 {
        static char buf[MAXDOMAINLEN * 5];
+       return dname_to_string_buf(dname, origin, buf);
+}
+
+const char *
+dname_to_string_buf(const dname_type *dname, const dname_type *origin, char buf[MAXDOMAINLEN * 5])
+{
        size_t i;
        size_t labels_to_convert = dname->label_count - 1;
        int absolute = 1;
@@ -399,7 +405,7 @@ dname_to_string(const dname_type *dname, const dname_type *origin)
        const uint8_t *src;
 
        if (dname->label_count == 1) {
-               strlcpy(buf, ".", sizeof(buf));
+               strlcpy(buf, ".", MAXDOMAINLEN * 5);
                return buf;
        }
 
index 0d5dc4a..e37cb23 100644 (file)
@@ -15,6 +15,7 @@
 
 #include "buffer.h"
 #include "region-allocator.h"
+#include "dns.h" /* for MAXDOMAINLEN */
 
 #if defined(NAMEDB_UPPERCASE) || defined(USE_NAMEDB_UPPERCASE)
 #define DNAME_NORMALIZE        toupper
@@ -346,6 +347,19 @@ label_next(const uint8_t *label)
 const char *dname_to_string(const dname_type *dname,
                            const dname_type *origin);
 
+/*
+ * Convert DNAME to its string representation.  The result if written
+ * to the provided buffer buf, which must be at least 5 times
+ * MAXDOMAINNAMELEN.
+ *
+ * If ORIGIN is provided and DNAME is a subdomain of ORIGIN the dname
+ * will be represented relative to ORIGIN.
+ *
+ * Pre: dname != NULL
+ */
+const char *dname_to_string_buf(const dname_type *dname,
+                                const dname_type *origin,
+                                char buf[MAXDOMAINLEN * 5]);
 
 /*
  * Create a dname containing the single label specified by STR
index 3e50194..a04481d 100644 (file)
@@ -1,5 +1,91 @@
+4 April 2024: Jeroen
+       - Use rooted temporary path in makedist.sh.
+
+3 April 2024: Jeroen
+       - Replace multiple strcat and strcpy by snprintf.
+       - Tag for 4.9.0.
+
+26 March 2024: Jeroen
+       - Test if debug is available in do-tests.
+       - Enforce timeout from NSD in ixfr_gone test.
+       - Update expressions in ixfr_and_restart test.
+       - Make algorithm explicit in control-repattern test.
+       - Switch algorithm to hmac-256 for testplan_mess test.
+       - Tag for 4.9.0rc1.
+
+25 March 2024: Jeroen
+       - Fix timing sensitivity in ixfr_outsync test.
+
+22 March 2024: Jeroen
+       - Set up doc/RELNOTES for upcoming release.
+
+26 February 2024: Willem
+       - Merge #316: Fix to reap defunct children by the reload process that
+         emerged when some serve child processes were still serving TCP
+         request while the others had already quit, while the reload process
+         was waiting for the signal from the backup/old main process that all
+         children exited.
+       - Fix (also from Merge #316) to reap exited children more frequently
+         from server main loop for processes that exited during reload, but
+         missed the initial reaping at start of the main loop because they
+         took somewhat longer to exit.
+
+16 February 2024: Wouter
+       - Fix compile with memclean for xfrd nsd.db close.
+       - In xfrd del secondary zone, the timer could perhaps have
+         event_added, and if so, it would not be event_del if a tcp connection
+         is active at the time. This could cause the libevent event lists
+         to fail. Also fix to make sure to set event_added for the
+         nsd-control ssl nonblocking handshake and check event_added there
+         too, for extra certainty.
+
+15 February 2024: Willem
+       - Merge #304: Support for Catalog zones version "2" as specified in
+         RFC 9432. Both the consumer as well as the producer role are
+         implemented, but only a single catalog consumer zone is allowed.
+         The "coo" property, only relevant with multiple catalog consumer,
+         is therefore not supported. The "group" property is supported.
+         Have a look at the nsd.conf man page for details on how to
+         configure and use catalog zones.
+
+12 February 2024: Willem
+       - Allow SOA apex queries to otherwise with allow-query protected zones
+         for clients matching a provide-xfr rule, because clients that are
+         allowed to transfer the zone need to be able to query SOA at the
+         apex preceding the actual transfer.
+
+6 February 2024: Wouter
+       - Fix #313: nsd 4.8 stats with implausible spikes.
+
+16 January 2024: Wouter
+       - Move acx_nlnetlabs.m4 to version 48, with ssp and getaddrinfo
+         include check.
+
+14 January 2024: Wouter
+       - Move acx_nlnetlabs.m4 to version 47, with crypt32 check.
+
+8 December 2023: Wouter
+       - Merge #309: More RFC 8499 compliance.
+       - Fix #310: NSD stats contain the terms "master" and "slave".
+       - Fix control-reconfig-xfrd test for zonestatus primary that is
+         printed by nsd-control zonestatus.
+
+7 December 2023: Wouter
+       - Merge #307 from anandb-ripencc: Many improvements to the nsd.conf
+         man page.
+       - Fix #308: Deprecate "multi-master-check" in favour of
+         "multi-primary-check".
+
+6 December 2023: Wouter
+       - Fix to sync the tests script file common.sh.
+       - Update test script file common.sh.
+       - Fix #306: Missing AC_SUBST(dbdir) breaks installation with 4.8.0.
+       - Fix for #306: Create directory for xfrd.state and zone.list files
+         in make install.
+
 29 November 2023: Wouter
-       - Tag for 4.8.0rc1.
+       - Tag for 4.8.0rc1. This became 4.8.0 release on 6 December 2023.
+         The repository continues with version 4.8.1 under development.
 
 28 November 2023: Wouter
        - Set up doc/RELNOTES for upcoming release.
index 1fd6240..1466efc 100644 (file)
@@ -21,7 +21,7 @@
 
 1.0 Introduction
 
-This is NSD Name Server Daemon (NSD) version 4.8.0.
+This is NSD Name Server Daemon (NSD) version 4.9.1.
 
 The NLnet Labs Name Server Daemon (NSD) is an authoritative RFC compliant 
 DNS nameserver. It was first conceived to allow for more genetic 
@@ -57,7 +57,7 @@ and uses a simple configuration file 'nsd.conf'.
 
 1.2 Quick build and install
 
-Step 1: Unpack the source with gtar -xzvf nsd-4.8.0.tar.gz
+Step 1: Unpack the source with gtar -xzvf nsd-4.9.1.tar.gz
 
 Step 2: Create user nsd or any other unprivileged user of your
         choice. In case of later make sure to use
@@ -111,9 +111,9 @@ Step 11: If desired add 'nsd-control write' to your superuser crontab to
 Use your favorite combination of tar and gnu zip to unpack the source,
 for example
 
-$ gtar -xzvf nsd-4.8.0.tar.gz
+$ gtar -xzvf nsd-4.9.1.tar.gz
 
-will unpack the source into the ./nsd-4.8.0 directory...
+will unpack the source into the ./nsd-4.9.1 directory...
 
 
 2.2 Configuring NSD
@@ -920,4 +920,4 @@ larger and regular donations please contact us at users@NLnetLabs.nl. Also
 see http://www.nlnetlabs.nl/labs/contributors/.
 
 
-$Id: README,v 1.7 2023/12/20 17:29:02 florian Exp $
+$Id: README,v 1.8 2024/04/12 15:53:34 florian Exp $
index e5e234d..d2c25ea 100644 (file)
@@ -1,5 +1,66 @@
 NSD RELEASE NOTES
 
+4.9.1
+================
+BUG FIXES:
+       - Use rooted temporary path in makedist.sh.
+
+4.9.0
+================
+FEATURES:
+       - Merge #315: Allow SOA apex queries to otherwise with allow-query
+         protected zones for clients matching a provide-xfr rule, because
+         clients that are allowed to transfer the zone need to be able to
+         query SOA at the apex preceding the actual transfer.
+       - Merge #304: Support for Catalog zones version "2" as specified in
+         RFC 9432. Both the consumer as well as the producer role are
+         implemented, but only a single catalog consumer zone is allowed.
+         The "coo" property, only relevant with multiple catalog consumer,
+         is therefore not supported. The "group" property is supported.
+         Have a look at the nsd.conf man page for details on how to
+         configure and use catalog zones.
+
+BUG FIXES:
+       - Fix to sync the tests script file common.sh.
+       - Update test script file common.sh.
+       - Fix #306: Missing AC_SUBST(dbdir) breaks installation with 4.8.0.
+       - Fix for #306: Create directory for xfrd.state and zone.list files
+         in make install.
+       - Merge #307 from anandb-ripencc: Many improvements to the nsd.conf
+         man page.
+       - Fix #308: Deprecate "multi-master-check" in favour of
+         "multi-primary-check".
+       - Merge #309: More RFC 8499 compliance.
+       - Fix control-reconfig-xfrd test for zonestatus primary that is
+         printed by nsd-control zonestatus.
+       - Move acx_nlnetlabs.m4 to version 47, with crypt32 check.
+       - Move acx_nlnetlabs.m4 to version 48, with ssp and getaddrinfo
+         include check.
+       - Fix #313: nsd 4.8 stats with implausible spikes.
+       - Fix compile with memclean for xfrd nsd.db close.
+       - In xfrd del secondary zone, the timer could perhaps have
+         event_added, and if so, it would not be event_del if a tcp connection
+         is active at the time. This could cause the libevent event lists
+         to fail. Also fix to make sure to set event_added for the
+         nsd-control ssl nonblocking handshake and check event_added there
+         too, for extra certainty.
+       - Merge #316: Fix to reap defunct children by the reload process that
+         emerged when some serve child processes were still serving TCP
+         request while the others had already quit, while the reload process
+         was waiting for the signal from the backup/old main process that all
+         children exited.
+       - Fix (also from Merge #316) to reap exited children more frequently
+         from server main loop for processes that exited during reload, but
+         missed the initial reaping at start of the main loop because they
+         took somewhat longer to exit.
+       - Fix timing sensitivity in ixfr_outsync test.
+       - Test if debug is available in do-tests.
+       - Enforce timeout from NSD in ixfr_gone test.
+       - Update expressions in ixfr_and_restart test.
+       - Make algorithm explicit in control-repattern test.
+       - Switch algorithm to hmac-256 for testplan_mess test.
+       - Replace multiple strcat and strcpy by snprintf.
+
 4.8.0
 ================
 FEATURES:
index 65cb1ae..032d0eb 100644 (file)
@@ -318,6 +318,10 @@ static inline int domain_is_subdomain(domain_type* d1, domain_type* d2)
 /* easy printout, to static buffer of dname_to_string, fqdn. */
 static inline const char* domain_to_string(domain_type* domain)
 { return dname_to_string(domain_dname(domain), NULL); }
+/* easy printout, to given buffer of dname_to_string, fqdn. */
+static inline const char* domain_to_string_buf(domain_type* domain, char *buf)
+{ return dname_to_string_buf(domain_dname(domain), NULL, buf); }
+
 
 /*
  * The type covered by the signature in the specified RRSIG RR.
@@ -391,6 +395,11 @@ void namedb_read_zonefile(struct nsd* nsd, struct zone* zone,
        struct udb_base* taskudb, struct udb_ptr* last_task);
 zone_type* namedb_zone_create(namedb_type* db, const dname_type* dname,
         struct zone_options* zopt);
+static inline zone_type*
+namedb_find_or_create_zone(namedb_type *db, const dname_type *dname,
+               struct zone_options* zopt)
+{ zone_type* zone = namedb_find_zone(db, dname);
+  return zone ? zone : namedb_zone_create(db, dname, zopt); }
 void namedb_zone_delete(namedb_type* db, zone_type* zone);
 void namedb_write_zonefile(struct nsd* nsd, struct zone_options* zopt);
 void namedb_write_zonefiles(struct nsd* nsd, struct nsd_options* options);
index 5e399bb..ba5f015 100644 (file)
@@ -1,4 +1,4 @@
-.TH "nsd\-checkconf" "8" "Dec  6, 2023" "NLnet Labs" "nsd 4.8.0"
+.TH "nsd\-checkconf" "8" "Apr  4, 2024" "NLnet Labs" "nsd 4.9.1"
 .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
index 15c96cb..e0fcaaa 100644 (file)
@@ -354,7 +354,7 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o,
 #ifdef RATELIMIT
                ZONE_GET_RRL(rrl_whitelist, o, zone->pattern);
 #endif
-               ZONE_GET_BIN(multi_master_check, o, zone->pattern);
+               ZONE_GET_BIN(multi_primary_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);
@@ -391,7 +391,7 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o,
 #ifdef RATELIMIT
                ZONE_GET_RRL(rrl_whitelist, o, p);
 #endif
-               ZONE_GET_BIN(multi_master_check, o, p);
+               ZONE_GET_BIN(multi_primary_check, o, p);
                ZONE_GET_BIN(store_ixfr, o, p);
                ZONE_GET_INT(ixfr_size, o, p);
                ZONE_GET_INT(ixfr_number, o, p);
@@ -524,8 +524,8 @@ static void print_zone_content_elems(pattern_options_type* pat)
        print_acl("allow_query:", pat->allow_query);
        print_acl("allow-notify:", pat->allow_notify);
        print_acl("request-xfr:", pat->request_xfr);
-       if(pat->multi_master_check)
-               printf("\tmulti-master-check: %s\n", pat->multi_master_check?"yes":"no");
+       if(pat->multi_primary_check)
+               printf("\tmulti-primary-check: %s\n", pat->multi_primary_check?"yes":"no");
        if(!pat->notify_retry_is_default)
                printf("\tnotify-retry: %d\n", pat->notify_retry);
        print_acl("notify:", pat->notify);
@@ -585,6 +585,20 @@ static void print_zone_content_elems(pattern_options_type* pat)
        if(pat->verifier_timeout != VERIFIER_TIMEOUT_INHERIT) {
                printf("\tverifier-timeout: %d\n", pat->verifier_timeout);
        }
+
+       if(!pat->catalog_role_is_default)
+           switch(pat->catalog_role) {
+       case CATALOG_ROLE_CONSUMER: printf("\tcatalog: consumer\n");
+                                   break;
+       case CATALOG_ROLE_PRODUCER: printf("\tcatalog: producer\n");
+                                   break;
+       default                   : break;
+       }
+
+       if(pat->catalog_member_pattern)
+               print_string_var("catalog-member-pattern:", pat->catalog_member_pattern);
+       if(pat->catalog_producer_zone)
+               print_string_var("catalog-producer-zone:", pat->catalog_producer_zone);
 }
 
 void
@@ -806,9 +820,9 @@ additional_checks(nsd_options_type* opt, const char* filename)
                                "is received?\n", filename, zone->name);
                        errors ++;
                }
-               if(!zone_is_slave(zone) && (!zone->pattern->zonefile ||
-                       zone->pattern->zonefile[0] == 0)) {
-                       fprintf(stderr, "%s: zone %s is a master zone but has "
+               if(!zone_is_slave(zone) && !zone_is_catalog_producer(zone)
+               && (!zone->pattern->zonefile || zone->pattern->zonefile[0] == 0)) {
+                       fprintf(stderr, "%s: zone %s is a primary zone but has "
                                "no zonefile. Where can the data come from?\n",
                                filename, zone->name);
                        errors ++;
@@ -963,7 +977,7 @@ main(int argc, char* argv[])
        /* read config file */
        options = nsd_options_create(region_create(xalloc, free));
        tsig_init(options->region);
-       if (!parse_options_file(options, configfile, NULL, NULL) ||
+       if (!parse_options_file(options, configfile, NULL, NULL, NULL) ||
           !additional_checks(options, configfile)) {
                exit(2);
        }
index 555ea58..8b27253 100644 (file)
@@ -1,4 +1,4 @@
-.TH "nsd\-checkzone" "8" "Dec  6, 2023" "NLnet Labs" "nsd 4.8.0"
+.TH "nsd\-checkzone" "8" "Apr  4, 2024" "NLnet Labs" "nsd 4.9.1"
 .\" Copyright (c) 2014, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
index ea3a26e..1fbee3d 100644 (file)
@@ -1,4 +1,4 @@
-.TH "nsd\-control" "8" "Dec  6, 2023" "NLnet Labs" "nsd 4.8.0"
+.TH "nsd\-control" "8" "Apr  4, 2024" "NLnet Labs" "nsd 4.9.1"
 .\" Copyright (c) 2011, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
@@ -16,7 +16,8 @@
 .B nsd\-control
 performs remote administration on the \fInsd\fR(8) DNS server.  It reads
 the configuration file, contacts the nsd server over SSL, sends the
-command and displays the result.
+command and displays the result.  Commands that require a reload are queued
+and the result indicates the command was accepted.
 .P
 The available options are:
 .TP
@@ -80,7 +81,7 @@ Same as stats, but does not zero the counters.
 .B addzone <zone name> <pattern name>
 Add a new zone to the running server.  The zone is added to the zonelist
 file on disk, so it stays after a restart.  The pattern name determines
-the options for the new zone.  For slave zones a zone transfer is
+the options for the new zone.  For secondary zones a zone transfer is
 immediately attempted.  For zones with a zonefile, the zone file is
 attempted to be read in.
 .TP
@@ -114,28 +115,28 @@ path are created if necessary. With argument that zone is written if it
 was modified, without argument, all modified zones are written.
 .TP
 .B notify [<zone>]
-Send NOTIFY messages to slave servers.  Sends to the IP addresses
-configured in the 'notify:' lists for the master zones hosted on this
-server.  Usually NSD sends NOTIFY messages right away when a master zone
+Send NOTIFY messages to secondary servers.  Sends to the IP addresses
+configured in the 'notify:' lists for the primary zones hosted on this
+server.  Usually NSD sends NOTIFY messages right away when a primary zone
 serial is updated.  If a zone is given, notifies are sent for that zone.
-These slave servers are supposed to initiate a zone transfer request
-later (to this server or another master), this can be allowed via
+These secondary servers are supposed to initiate a zone transfer request
+later (to this server or another primary), this can be allowed via
 the 'provide\-xfr:' acl list configuration. With argument that zone is
 processed, without argument, all zones are processed.
 .TP
 .B transfer [<zone>]
-Attempt to update slave zones that are hosted on this server by contacting
-the masters.  The masters are configured via 'request\-xfr:' lists.
+Attempt to update secondary zones that are hosted on this server by contacting
+the primaries.  The primaries are configured via 'request\-xfr:' lists.
 If a zone is given, that zone is updated.  Usually NSD receives a NOTIFY
-from the masters (configured via 'allow\-notify:' acl list) that a new zone
+from the primaries (configured via 'allow\-notify:' acl list) that a new zone
 serial has to be transferred.  For zones with no content, NSD may have backed
-off from asking often because the masters did not respond, but this command
+off from asking often because the primaries did not respond, but this command
 will reset the backoff to its initial timeout, for frequent retries. With
 argument that zone is transferred, without argument, all zones are transferred.
 .TP
 .B force_transfer [<zone>]
-Force update slave zones that are hosted on this server.  Even if the
-master hosts the same serial number of the zone, a full AXFR is performed
+Force update secondary zones that are hosted on this server.  Even if the
+primary hosts the same serial number of the zone, a full AXFR is performed
 to fetch it.  If you want to use IXFR and check that the serial number
 increases, use the 'transfer' command. With argument that zone is
 transferred, without argument, all zones are transferred.
@@ -143,9 +144,9 @@ transferred, without argument, all zones are transferred.
 .B zonestatus [<zone>]
 Print state of the zone, the serial numbers and since when they have
 been acquired.  Also prints the notify action (to which server), and
-zone transfer (and from which master) if there is activity right now.
-The state of the zone is printed as: 'master' (master zones), 'ok' (slave
-zone is up\-to\-date), 'expired' (slave zone has expired), 'refreshing' (slave
+zone transfer (and from which primary) if there is activity right now.
+The state of the zone is printed as: 'primary' (primary zones), 'ok' (secondary
+zone is up\-to\-date), 'expired' (secondary zone has expired), 'refreshing' (secondary
 zone has transfers active).  The serial numbers printed are
 the 'served\-serial' (currently active), the 'commit\-serial' (is in reload),
 the 'notified\-serial' (got notify, busy fetching the data).  The serial
@@ -323,13 +324,13 @@ number of answers with TC flag set.
 .I num.dropped
 number of queries that were dropped because they failed sanity check.
 .TP
-.I zone.master
-number of master zones served.  These are zones with no 'request\-xfr:'
-entries.
+.I zone.primary
+number of primary zones served.  These are zones with no 'request\-xfr:'
+entries. Also output as 'zone.master' for backwards compatibility.
 .TP
-.I zone.slave
-number of slave zones served.  These are zones with 'request\-xfr'
-entries.
+.I zone.secondary
+number of secondary zones served.  These are zones with 'request\-xfr'
+entries. Also output as 'zone.slave' for backwards compatibility.
 .SH "FILES"
 .TP
 .I @nsdconfigfile@
index ddd380d..3ead1fb 100644 (file)
@@ -124,9 +124,9 @@ usage()
        printf("  addzones                      add zone list on stdin {name space pattern newline}\n");
        printf("  delzones                      remove zone list on stdin {name newline}\n");
        printf("  write [<zone>]                write changed zonefiles to disk\n");
-       printf("  notify [<zone>]               send NOTIFY messages to slave servers\n");
-       printf("  transfer [<zone>]             try to update slave zones to newer serial\n");
-       printf("  force_transfer [<zone>]       update slave zones with AXFR, no serial check\n");
+       printf("  notify [<zone>]               send NOTIFY messages to secondary servers\n");
+       printf("  transfer [<zone>]             try to update secondary zones to newer serial\n");
+       printf("  force_transfer [<zone>]       update secondary zones with AXFR, no serial check\n");
        printf("  zonestatus [<zone>]           print state, serial, activity\n");
        printf("  serverpid                     get pid of server process\n");
        printf("  verbosity <number>            change logging detail\n");
@@ -523,7 +523,7 @@ go(const char* cfgfile, char* svr, int argc, char* argv[])
                exit(1);
        }
        tsig_init(opt->region);
-       if(!parse_options_file(opt, cfgfile, NULL, NULL)) {
+       if(!parse_options_file(opt, cfgfile, NULL, NULL, NULL)) {
                fprintf(stderr, "could not read config file\n");
                exit(1);
        }
index 9f7b699..cc84552 100644 (file)
@@ -274,7 +274,7 @@ main(int argc, char *argv[])
                DEFAULT_CHUNK_SIZE, DEFAULT_LARGE_OBJECT_SIZE,
                DEFAULT_INITIAL_CLEANUP_SIZE, 1));
        tsig_init(nsd.options->region);
-       if(!parse_options_file(nsd.options, configfile, NULL, NULL)) {
+       if(!parse_options_file(nsd.options, configfile, NULL, NULL, NULL)) {
                error("could not read config: %s\n", configfile);
        }
        if(!parse_zone_list_file(nsd.options)) {
index 1ff566a..b927b93 100644 (file)
@@ -1,9 +1,9 @@
-.TH "NSD" "8" "Dec  6, 2023" "NLnet Labs" "NSD 4.8.0"
+.TH "NSD" "8" "Apr  4, 2024" "NLnet Labs" "NSD 4.9.1"
 .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
 .B nsd
-\- Name Server Daemon (NSD) version 4.8.0.
+\- Name Server Daemon (NSD) version 4.9.1.
 .SH "SYNOPSIS"
 .B nsd
 .RB [ \-4 ] 
index 80b4cb0..c3b99c0 100644 (file)
@@ -1134,7 +1134,7 @@ main(int argc, char *argv[])
        pp_init(&write_uint16, &write_uint32);
 
        /* Read options */
-       if(!parse_options_file(nsd.options, configfile, NULL, NULL)) {
+       if(!parse_options_file(nsd.options, configfile, NULL, NULL, NULL)) {
                error("could not read config: %s\n", configfile);
        }
        if(!parse_zone_list_file(nsd.options)) {
index d9ab36d..357b8df 100644 (file)
@@ -1,4 +1,4 @@
-.TH "nsd.conf" "5" "Dec  6, 2023" "NLnet Labs" "nsd 4.8.0"
+.TH "nsd.conf" "5" "Apr  4, 2024" "NLnet Labs" "nsd 4.9.1"
 .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
@@ -7,18 +7,17 @@
 .SH "SYNOPSIS"
 .B nsd.conf
 .SH "DESCRIPTION"
-.B Nsd.conf
-is used to configure nsd(8). The file format has attributes and 
-values. Some attributes have attributes inside them. The notation 
-is: attribute: value. 
+This file is used to configure nsd(8). It specifies options for the nsd
+server, zone files, primaries and secondaries.
 .PP
-Comments start with # and last to the end of line. Empty lines are
-ignored as is whitespace at the beginning of a line. Quotes can be used,
-for names with spaces, eg. "file name.zone".
+The file format has attributes and values. Some attributes have attributes
+inside them. The notation is:
+.PP
+attribute: value
 .PP
-.B Nsd.conf
-specifies options for the nsd server, zone files, primaries and 
-secondaries.
+Comments start with # and last to the end of line. Empty lines are
+ignored, as is whitespace at the beginning of a line. Quotes must be used
+for values with spaces in them, eg. "file name.zone".
 .SH "EXAMPLE"
 An example of a short nsd.conf file is below.
 .LP
@@ -32,22 +31,19 @@ server:
 server-count: 1 # use this number of cpu cores
 .RE
 .RS 5
-database: ""  # or use "@dbfile@"
-.RE
-.RS 5
-zonelistfile: "@zonelistfile@"
+username: @user@
 .RE
 .RS 5
-username: @user@
+zonelistfile: @zonelistfile@
 .RE
 .RS 5
-logfile: "@logfile@"
+logfile: @logfile@
 .RE
 .RS 5
-pidfile: "@pidfile@"
+pidfile: @pidfile@
 .RE
 .RS 5
-xfrdfile: "@xfrdfile@"
+xfrdfile: @xfrdfile@
 .RE
 .TP
 zone:
@@ -55,18 +51,18 @@ zone:
 name: example.com
 .RE
 .RS 5
-zonefile: @configdir@/example.com.zone 
+zonefile: @configdir@/example.com.zone
 .RE
 .TP
 zone:
 .RS 5
-# this server is master, 192.0.2.1 is the secondary.
+# this server is the primary and 192.0.2.1 is the secondary.
 .RE
 .RS 5
-name: masterzone.com
+name: primaryzone.com
 .RE
 .RS 5
-zonefile: @configdir@/masterzone.com.zone 
+zonefile: @configdir@/primaryzone.com.zone
 .RE
 .RS 5
 notify: 192.0.2.1 NOKEY
@@ -77,13 +73,13 @@ provide-xfr: 192.0.2.1 NOKEY
 .TP
 zone:
 .RS 5
-# this server is secondary, 192.0.2.2 is master.
+# this server is the secondary and 192.0.2.2 is the primary.
 .RE
 .RS 5
-name: secondzone.com
+name: secondaryzone.com
 .RE
 .RS 5
-zonefile: @configdir@/secondzone.com.zone 
+zonefile: @configdir@/secondaryzone.com.zone
 .RE
 .RS 5
 allow-notify: 192.0.2.2 NOKEY
@@ -92,14 +88,14 @@ allow-notify: 192.0.2.2 NOKEY
 request-xfr: 192.0.2.2 NOKEY
 .RE
 .LP
-Then, use kill \-HUP to reload changes from master zone files.
+Then, use kill \-HUP to reload changes from primary zone files.
 And use kill \-TERM to stop the server.
 .SH "FILE FORMAT"
-There must be whitespace between keywords. Attribute keywords end 
-with a colon ':'. An attribute is followed by its containing 
-attributes, or a value. 
+There must be whitespace between keywords. Attribute keywords end
+with a colon ':'. An attribute is followed by its containing
+attributes, or a value.
 .P
-At the top level, only 
+At the top level, only
 .BR server: ,
 .BR verify: ,
 .BR key: ,
@@ -110,22 +106,22 @@ and
 .B remote-control:
 are allowed. These are followed by their attributes or a new top-level keyword. The
 .B zone:
-attribute is followed by zone options. The 
-.B server: 
-attribute is followed by global options for the 
-.B NSD 
+attribute is followed by zone options. The
+.B server:
+attribute is followed by global options for the
+.B NSD
 server. The
 .B verify:
 attribute is used to control zone verification. A
-.B key: 
+.B key:
 attribute is used to define keys for authentication. The
 .B pattern:
 attribute is followed by the zone options for zones that use the pattern.
-A 
+A
 .B tls-auth:
 attribute is used to define credentials for authenticating an outgoing TLS connection used for XFR-over-TLS.
 .P
-Files can be included using the 
+Files can be included using the
 .B include:
 directive. It can appear anywhere, and takes a single filename as an
 argument. Processing continues as if the text from the included file
@@ -138,17 +134,17 @@ and '~' work, see \fBglob\fR(7).  If no files match the pattern, this
 is not an error.
 .SS "Server Options"
 .LP
-The global options (if not overridden from the NSD commandline) are 
-taken from the 
-.B server: 
-clause. There may only be one 
-.B server: 
+The global options (if not overridden from the NSD command-line) are
+taken from the
+.B server:
+clause. There may only be one
+.B server:
 clause.
 .TP
 .B ip\-address:\fR <ip4 or ip6>[@port] [servers] [bindtodevice] [setfib]
-NSD will bind to the listed ip\-address. Can be given multiple times 
+NSD will bind to the listed ip\-address. Can be given multiple times
 to bind multiple ip\-addresses. Optionally, a port number can be given.
-If none are given NSD listens to the wildcard interface. Same as commandline option
+If none are given NSD listens to the wildcard interface. Same as command-line option
 .BR \-a.
 .IP
 To limit which NSD server(s) listen on the given interface, specify one or
@@ -183,7 +179,7 @@ that are down.  Similar to ip\-transparent.  Default is no.
 Use the SO_REUSEPORT socket option, and create file descriptors for every
 server in the server\-count.  This improves performance of the network
 stack.  Only really useful if you also configure a server\-count higher
-than 1 (such as, equal to the number of cpus).  The default is no. 
+than 1 (such as, equal to the number of cpus).  The default is no.
 It works on Linux, but does not work on FreeBSD, and likely does not
 work on other systems.
 .TP
@@ -194,11 +190,11 @@ Set the send buffer size for query-servicing sockets.  Set to 0 to use the defau
 Set the receive buffer size for query-servicing sockets.  Set to 0 to use the default settings.
 .TP
 .B debug\-mode:\fR <yes or no>
-Turns on debugging mode for nsd, does not fork a daemon process. 
-Default is no. Same as commandline option
+Turns on debugging mode for nsd, does not fork a daemon process.
+Default is no. Same as command-line option
 .BR \-d.
 If set to yes it does not fork and stays in the foreground, which can
-be helpful for commandline debugging, but is also used by certain
+be helpful for command-line debugging, but is also used by certain
 server supervisor processes to ascertain that the server is running.
 .TP
 .B do\-ip4:\fR <yes or no>
@@ -207,8 +203,11 @@ If yes, NSD listens to IPv4 connections.  Default yes.
 .B do\-ip6:\fR <yes or no>
 If yes, NSD listens to IPv6 connections.  Default yes.
 .TP
+.B database:\fR <filename>
+This option is ignored by NSD versions 4.8.0 and newer, because the database feature has been removed.
+.TP
 .B zonelistfile:\fR <filename>
-By default 
+By default
 .I @zonelistfile@
 is used. The specified file is used to store the dynamically added
 list of zones.  The list is written to by NSD to add and delete zones.
@@ -216,9 +215,9 @@ It is a text file with a zone\-name and pattern\-name on each line.
 This file is used for the nsd\-control addzone and delzone commands.
 .TP
 .B identity:\fR <string>
-Returns the specified identity when asked for CH TXT ID.SERVER. 
-Default is the name as returned by gethostname(3). Same as 
-commandline option 
+Returns the specified identity when asked for CH TXT ID.SERVER.
+Default is the name as returned by gethostname(3). Same as
+command-line option
 .BR \-i .
 See hide\-identity to set the server to not respond to such queries.
 .TP
@@ -230,12 +229,12 @@ See hide\-version to set the server to not respond to such queries.
 .B nsid:\fR <string>
 Add the specified nsid to the EDNS section of the answer when queried
 with an NSID EDNS enabled packet.  As a sequence of hex characters or
-with ascii_ prefix and then an ascii string.  Same as commandline option
+with ascii_ prefix and then an ascii string.  Same as command-line option
 .BR \-I .
 .TP
 .B logfile:\fR <filename>
-Log messages to the logfile. The default is to log to stderr and 
-syslog (with facility LOG_DAEMON). Same as commandline option 
+Log messages to the logfile. The default is to log to stderr and
+syslog (with facility LOG_DAEMON). Same as command-line option
 .BR \-l .
 .TP
 .B log\-only\-syslog:\fR <yes or no>
@@ -245,8 +244,8 @@ been opened, the server uses stderr.  Stderr is also used if syslog is
 not available.  Default is no.
 .TP
 .B server\-count:\fR <number>
-Start this many NSD servers. Default is 1. Same as commandline 
-option 
+Start this many NSD servers. Default is 1. Same as command-line
+option
 .BR \-N .
 .TP
 .B cpu\-affinity:\fR <number> <number> ...
@@ -266,8 +265,8 @@ enabled.
 .BR \-n
 .TP
 .B tcp\-count:\fR <number>
-The maximum number of concurrent, active TCP connections by each server. 
-Default is 100. Same as commandline option
+The maximum number of concurrent, active TCP connections by each server.
+Default is 100. Same as command-line option
 .BR \-n .
 .TP
 .B tcp\-reject\-overflow:\fR <yes or no>
@@ -284,7 +283,7 @@ The default is 120 seconds.
 .TP
 .B tcp-mss:\fR <number>
 Maximum segment size (MSS) of TCP socket on which the server responds
-to queries. Value lower than common MSS on Ethernet 
+to queries. Value lower than common MSS on Ethernet
 (1220 for example) will address path MTU problem.
 Note that not all platform supports socket option to set MSS (TCP_MAXSEG).
 Default is system default MSS determined by interface MTU and
@@ -322,21 +321,21 @@ Preferred EDNS buffer size for IPv4.  Default 1232.
 Preferred EDNS buffer size for IPv6.  Default 1232.
 .TP
 .B pidfile:\fR <filename>
-Use the pid file instead of the platform specific default, usually 
-.IR @pidfile@. 
-Same as commandline option 
+Use the pid file instead of the platform specific default, usually
+.IR @pidfile@.
+Same as command-line option
 .BR \-P .
 With "" there is no pidfile, for some startup management setups,
 where a pidfile is not useful to have.
 .TP
 .B port:\fR <number>
-Answer queries on the specified port. Default is 53. Same as 
-commandline option 
+Answer queries on the specified port. Default is 53. Same as
+command-line option
 .BR \-p .
 .TP
 .B statistics:\fR <number>
-If not present no statistics are dumped. Statistics are produced 
-every number seconds. Same as commandline option 
+If not present no statistics are dumped. Statistics are produced
+every number seconds. Same as command-line option
 .BR \-s .
 .TP
 .B chroot:\fR <directory>
@@ -346,32 +345,31 @@ inside the chroot, you have to prepend the \fBchroot\fR path. That way,
 you can switch the chroot option on and off without having to modify
 anything else in the configuration. Set the value to "" (the empty string)
 to disable the chroot. By default "\fI@chrootdir@\fR" is used. Same as
-commandline option 
+command-line option
 .BR \-t .
 .TP
 .B username:\fR <username>
-After binding the socket, drop user privileges and assume the 
-username. Can be username, id or id.gid. Same as commandline option 
+After binding the socket, drop user privileges and assume the
+username. Can be username, id or id.gid. Same as command-line option
 .BR \-u .
 .TP
 .B zonesdir:\fR <directory>
 Change the working directory to the specified directory before accessing
-zone files. Also, NSD will access \fBdatabase\fR, \fBzonelistfile\fR,
-\fBlogfile\fR, \fBpidfile\fR, \fBxfrdfile\fR, \fBxfrdir\fR,
-\fBserver-key-file\fR, \fBserver-cert-file\fR, \fBcontrol-key-file\fR and
-\fBcontrol-cert-file\fR
+zone files. Also, NSD will access \fBzonelistfile\fR, \fBlogfile\fR,
+\fBpidfile\fR, \fBxfrdfile\fR, \fBxfrdir\fR, \fBserver-key-file\fR,
+\fBserver-cert-file\fR, \fBcontrol-key-file\fR and \fBcontrol-cert-file\fR
 relative to this directory. Set the value to "" (the empty string)
 to disable the change of working directory. By default "\fI@zonesdir@\fR"
 is used.
 .TP
 .B difffile:\fR <filename>
-Ignored, for compatibility with NSD3 config files. 
+Ignored, for compatibility with NSD3 config files.
 .TP
 .B xfrdfile:\fR <filename>
 The soa timeout and zone transfer daemon in NSD will save its state to
 this file. State is read back after a restart. The state file can be
 deleted without too much harm, but timestamps of zones will be gone.
-If it is configured as "", the state file is not used, all slave zones
+If it is configured as "", the state file is not used, all secondary zones
 are checked for updates upon startup.  For more details see the section
 on zone expiry behavior of NSD. Default is
 .IR @xfrdfile@ .
@@ -382,14 +380,14 @@ is created here that is removed when NSD exits.  Default is
 .IR @xfrdir@ .
 .TP
 .B xfrd\-reload\-timeout:\fR <number>
-If this value is \-1, xfrd will not trigger a reload after a zone 
-transfer. If positive xfrd will trigger a reload after a zone 
-transfer, then it will wait for the number of seconds before it will 
-trigger a new reload. Setting this value throttles the reloads to 
+If this value is \-1, xfrd will not trigger a reload after a zone
+transfer. If positive xfrd will trigger a reload after a zone
+transfer, then it will wait for the number of seconds before it will
+trigger a new reload. Setting this value throttles the reloads to
 once per the number of seconds. The default is 1 second.
 .TP
 .B verbosity:\fR <level>
-This value specifies the verbosity level for (non\-debug) logging. 
+This value specifies the verbosity level for (non\-debug) logging.
 Default is 0. 1 gives more information about incoming notifies and
 zone transfers. 2 lists soft warnings that are encountered. 3 prints
 more information.
@@ -405,11 +403,11 @@ Verbosity 2 prints additionally soft errors, like connection resets over TCP.
 And notify refusal, and axfr request refusals.
 .TP
 .B hide\-version:\fR <yes or no>
-Prevent NSD from replying with the version string on CHAOS class 
+Prevent NSD from replying with the version string on CHAOS class
 queries.  Default is no.
 .TP
 .B hide\-identity:\fR <yes or no>
-Prevent NSD from replying with the identity string on CHAOS class 
+Prevent NSD from replying with the identity string on CHAOS class
 queries.  Default is no.
 .TP
 .B drop\-updates:\fR <yes or no>
@@ -440,7 +438,7 @@ The default is yes.
 .B confine\-to\-zone:\fR <yes or no>
 If set to yes, additional information will not be added to the response if the
 apex zone of the additional information does not match the apex zone of the
-initial query (E.G. CNAME resolution). Default is no. 
+initial query (E.G. CNAME resolution). Default is no.
 .TP
 .B refuse\-any:\fR <yes or no>
 Refuse queries of type ANY.  This is useful to stop query floods trying
@@ -456,13 +454,9 @@ The default is yes.  The nsd\-control reload command reloads zone files
 regardless of this option.
 .TP
 .B zonefiles\-write:\fR <seconds>
-Write changed secondary zones to their zonefile every N seconds.  If the
-zone (pattern) configuration has "" zonefile, it is not written.  Zones that
-have received zone transfer updates are written to their zonefile.
-Default is 0 (disabled) when there is a database, and 3600 (1 hour) when
-database is "".  The database also commits zone transfer contents.
-You can configure it away from the default by putting the config statement
-for zonefiles\-write: after the database: statement in the config file.
+Write updated secondary zones to their zonefile every N seconds.  If the
+zone or pattern's "zonefile" option is set to "" (empty string), no zonefile
+is written. The default is 3600 (1 hour).
 .\" rrlstart
 .TP
 .B rrl\-size:\fR <numbuckets>
@@ -485,7 +479,7 @@ the zone\-specific ratelimit options are updated).
 .TP
 .B rrl\-slip:\fR <numpackets>
 This option controls the number of packets discarded before we send back a SLIP response
-(a response with "truncated" bit set to one). 0 disables the sending of SLIP packets, 
+(a response with "truncated" bit set to one). 0 disables the sending of SLIP packets,
 1 means every query will get a SLIP response.  Default is 2, cuts traffic in
 half and legit users have a fair chance to get a +TC response.
 .TP
@@ -740,15 +734,17 @@ The zone options such as
 .BR verify\-zone ,
 .BR verifier ,
 .BR verifier\-feed\-zone ,
+.BR verifier\-timeout ,
+.BR catalog ,
 and
-.B verifier\-timeout
+.B catalog\-member\-pattern
 can be given.  They are applied to the patterns and zones that include
 this pattern.
 .SS "Zone Options"
-.LP 
-For every zone the options need to be specified in one 
-.B zone: 
-clause. The access control list elements can be given multiple 
+.LP
+For every zone the options need to be specified in one
+.B zone:
+clause. The access control list elements can be given multiple
 times to add multiple servers. These elements need to be added
 explicitly.
 .LP
@@ -758,9 +754,9 @@ and they cannot be deleted via delzone, but remove them from the config
 file and repattern.
 .TP
 .B name:\fR <string>
-The name of the zone. This is the domain name of the apex of the 
-zone. May end with a '.' (in FQDN notation). For example 
-"example.com", "sub.example.net.". This attribute must be present in 
+The name of the zone. This is the domain name of the apex of the
+zone. May end with a '.' (in FQDN notation). For example
+"example.com", "sub.example.net.". This attribute must be present in
 each zone.
 .TP
 .B zonefile:\fR <filename>
@@ -798,59 +794,59 @@ scanned for a match in the order of the statements.  Without
 without TSIG key (which is the default).
 .P
 .RS
-The ip\-spec is either a plain IP address (IPv4 or IPv6), or can be 
-a subnet of the form 1.2.3.4/24, or masked like 
-1.2.3.4&255.255.255.0 or a range of the form 1.2.3.4\-1.2.3.25. 
-Note the ip\-spec ranges do not use spaces around the /, &, @ and \- 
+The ip\-spec is either a plain IP address (IPv4 or IPv6), or can be
+a subnet of the form 1.2.3.4/24, or masked like
+1.2.3.4&255.255.255.0 or a range of the form 1.2.3.4\-1.2.3.25.
+Note the ip\-spec ranges do not use spaces around the /, &, @ and \-
 symbols.
 .RE
 .TP
 .B allow\-notify:\fR <ip\-spec> <key\-name | NOKEY | BLOCKED>
-Access control list. The listed (primary) address is allowed to 
-send notifies to this (secondary) server. Notifies from unlisted or 
-specifically BLOCKED addresses are discarded. If NOKEY is given no 
+Access control list. The listed (primary) address is allowed to
+send notifies to this (secondary) server. Notifies from unlisted or
+specifically BLOCKED addresses are discarded. If NOKEY is given no
 TSIG signature is required.
 BLOCKED supersedes other entries, other entries are scanned for a match
 in the order of the statements.
 .P
 .RS
-The ip\-spec is either a plain IP address (IPv4 or IPv6), or can be 
-a subnet of the form 1.2.3.4/24, or masked like 
-1.2.3.4&255.255.255.0 or a range of the form 1.2.3.4\-1.2.3.25. 
-A port number can be added using a suffix of @number, for example 
+The ip\-spec is either a plain IP address (IPv4 or IPv6), or can be
+a subnet of the form 1.2.3.4/24, or masked like
+1.2.3.4&255.255.255.0 or a range of the form 1.2.3.4\-1.2.3.25.
+A port number can be added using a suffix of @number, for example
 1.2.3.4@5300 or 1.2.3.4/24@5300 for port 5300.
-Note the ip\-spec ranges do not use spaces around the /, &, @ and \- 
+Note the ip\-spec ranges do not use spaces around the /, &, @ and \-
 symbols.
 .RE
 .TP
 .B request\-xfr:\fR [AXFR|UDP] <ip\-address> <key\-name | NOKEY> [tls\-auth\-name]
-Access control list. The listed address (the master) is queried for 
+Access control list. The listed address (the primary) is queried for
 AXFR/IXFR on update. A port number can be added using a suffix of @number,
 for example 1.2.3.4@5300. The specified key is used during AXFR/IXFR. If
 tls-auth-name is included, the specified tls-auth clause will be used to
 perform authenticated XFR-over-TLS.
 .P
 .RS
-If the AXFR option is given, the server will not be contacted with 
-IXFR queries but only AXFR requests will be made to the server. This 
-allows an NSD secondary to have a master server that runs NSD. If 
-the AXFR option is left out then both IXFR and AXFR requests are 
-made to the master server.
+If the AXFR option is given, the server will not be contacted with
+IXFR queries but only AXFR requests will be made to the server. This
+allows an NSD secondary to have a primary server that runs NSD. If
+the AXFR option is left out then both IXFR and AXFR requests are
+made to the primary server.
 .P
-If the UDP option is given, the secondary will use UDP to transmit the IXFR 
+If the UDP option is given, the secondary will use UDP to transmit the IXFR
 requests. You should deploy TSIG when allowing UDP transport, to authenticate
-notifies and zone transfers. Otherwise, NSD is more vulnerable for 
-Kaminsky\-style attacks. If the UDP option is left out then IXFR will be 
+notifies and zone transfers. Otherwise, NSD is more vulnerable for
+Kaminsky\-style attacks. If the UDP option is left out then IXFR will be
 transmitted using TCP.
 .P
 If a tls-auth-name is given then TLS (by default on port 853) will be used
-for all zone transfers for the zone. If authentication of the master based on
+for all zone transfers for the zone. If authentication of the primary based on
 the specified tls-auth authentication information fails, the XFR request will
 not be sent. Support for TLS 1.3 is required for XFR-over-TLS.
 .RE
 .TP
 .B allow\-axfr\-fallback:\fR <yes or no>
-This option should be accompanied by request\-xfr. It (dis)allows NSD (as secondary) 
+This option should be accompanied by request\-xfr. It (dis)allows NSD (as secondary)
 to fallback to AXFR if the primary name server does not support IXFR. Default is yes.
 .TP
 .B size\-limit\-xfr:\fR <number>
@@ -858,11 +854,11 @@ This option should be accompanied by request\-xfr. It specifies XFR temporary fi
 If this option is 0, unlimited. Default value is 0.
 .TP
 .B notify:\fR <ip\-address> <key\-name | NOKEY>
-Access control list. The listed address (a secondary) is notified 
+Access control list. The listed address (a secondary) is notified
 of updates to this zone. A port number can be added using a suffix of @number,
-for example 1.2.3.4@5300. The specified key is used to sign the 
-notify. Only on secondary configurations will NSD be able to detect 
-zone updates (as it gets notified itself, or refreshes after a 
+for example 1.2.3.4@5300. The specified key is used to sign the
+notify. Only on secondary configurations will NSD be able to detect
+zone updates (as it gets notified itself, or refreshes after a
 time).
 .TP
 .B notify\-retry:\fR <number>
@@ -870,29 +866,29 @@ This option should be accompanied by notify. It sets the number of retries
 when sending notifies.
 .TP
 .B provide\-xfr:\fR <ip\-spec> <key\-name | NOKEY | BLOCKED>
-Access control list. The listed address (a secondary) is allowed to 
-request XFR from this server. Zone data will be provided to the 
-address. The specified key is used during XFR. For unlisted or 
+Access control list. The listed address (a secondary) is allowed to
+request XFR from this server. Zone data will be provided to the
+address. The specified key is used during XFR. For unlisted or
 BLOCKED addresses no data is provided and requests are discarded.
 BLOCKED supersedes other entries and other entries are scanned for a match
 in the order of the statements.
 .P
 .RS
-The ip\-spec is either a plain IP address (IPv4 or IPv6), or can be 
-a subnet of the form 1.2.3.4/24, or masked like 
-1.2.3.4&255.255.255.0 or a range of the form 1.2.3.4\-1.2.3.25. 
-A port number can be added using a suffix of @number, for example 
-1.2.3.4@5300 or 1.2.3.4/24@5300 for port 5300. Note the ip\-spec 
+The ip\-spec is either a plain IP address (IPv4 or IPv6), or can be
+a subnet of the form 1.2.3.4/24, or masked like
+1.2.3.4&255.255.255.0 or a range of the form 1.2.3.4\-1.2.3.25.
+A port number can be added using a suffix of @number, for example
+1.2.3.4@5300 or 1.2.3.4/24@5300 for port 5300. Note the ip\-spec
 ranges do not use spaces around the /, &, @ and \- symbols.
 .RE
 .TP
 .B outgoing\-interface:\fR <ip\-address>
-Access control list. The listed address is used to request AXFR|IXFR (in case of 
-a secondary) or used to send notifies (in case of a primary). 
+Access control list. The listed address is used to request AXFR|IXFR (in case of
+a secondary) or used to send notifies (in case of a primary).
 .P
 .RS
 The ip\-address is a plain IP address (IPv4 or IPv6).
-A port number can be added using a suffix of @number, for example 
+A port number can be added using a suffix of @number, for example
 1.2.3.4@5300.
 .RE
 .TP
@@ -971,10 +967,13 @@ The RRL classification types are: nxdomain, error, referral, any, rrsig,
 wildcard, nodata, dnskey, positive, all.
 .\" rrlend
 .TP
+.B multi\-primary\-check:\fR <yes or no>
+Default no.  If enabled, checks all primaries for the last version.  It uses
+the higher version of all the configured primaries.  Useful if you have multiple
+primaries that have different version numbers served.
+.TP
 .B multi\-master\-check:\fR <yes or no>
-Default no.  If enabled, checks all masters for the last version.  It uses
-the higher version of all the configured masters.  Useful if you have multiple
-masters that have different version numbers served.
+It is the same as multi\-primary\-check.
 .TP
 .B verify\-zone:\fR <yes or no>
 Enable or disable verification for this zone. Default is value\-zones
@@ -990,14 +989,61 @@ Feed updated zone to verifier over standard input. Default is
 verifier\-feed\-zone configured in
 .B verify:\fR.
 .TP
-.B verifier\-timeout: <seconds>
+.B verifier\-timeout:\fR <seconds>
 Number of seconds before verifier is forcefully terminated. Specify 0 (zero)
 to not use a specific timeout. Default is verifier\-timeout from
 .B verify:\fR.
+.TP
+.B catalog:\fR <consumer or producer>
+If set to \fIconsumer\fR, catalog zone processing is enabled for the zone.
+Only a single zone may be configured as a catalog consumer zone. When more than
+one catalog consumer zone is configured, none of them will be processed.
+Member zones of the catalog will use the pattern specified by the group
+property, or if a group property is missing or invalid, the pattern specified
+by the \fBcatalog\-member\-pattern\fR option is used. Group properties are valid
+if there is only a single value matching the name of a for member zones valid
+pattern.
+.IP
+A zone with the option set to \fIproducer\fR, can be used to produce a
+catalog zone.  Member zones for catalog producer zones can be added with
+"\fInsd\-control addzone <zone> <pattern>\fR", where <pattern> has a
+\fBcatalog\-producer\-zone\fR option pointing to a catalog producer zone.
+Members will get a group property with the pattern name as value.
+Catalog producer zones must be primary zones and may not have a
+\fBrequest\-xfr\fR option. Catalog producer zones will \fInot\fR read content
+from zone files, but will reconstruct the zone on startup from the member zone
+entries in @zonelistfile@, specified with the \fBzonelistfile\fR option.
+.IP
+The status of both catalog consumer and producer zones can be verified with
+\fInsd\-control zonestatus\fR. It will show the number of member zones and, if
+the catalog zone is invalid, the reason for it to be invalid is shown.
+\fInsd\-control zonestatus\fR will also show the entry of a catalog member zone
+in the catalog (consumer or producer) zone as \fBcatalog-member-id:\fR.
+.IP
+A catalog zone can either be catalog consumer zone or a catalog producer zone
+but not both. Likewise, catalog member zones can be either a member of catalog
+consumer zone or a catalog producer zone but not both.
+.IP
+Catalog zones contain a list of zones that are served. Use \fBallow\-query:
+0.0.0.0/0 BLOCKED\fR and \fBallow\-query: ::0/0 BLOCKED\fR in a catalog zone 
+zone or pattern clause to prevent revealing the catalog. Also consider using
+transfers over TLS to further protect the catalog against eavesdroppers.
+.TP
+.B catalog\-member\-pattern:\fR <pattern\-name>
+If this option is provided for a catalog consumer zone, members of that catalog
+that have a missing or an invalid group property will be added using pattern 
+<pattern\-name>.
+.TP
+.B catalog\-producer\-zone:\fR <zone\-name>
+This option can only be used in a pattern. Adding a zone using
+"\fInsd\-control addzone <zone> <pattern>\fR with a <pattern> containing this
+option, will cause a catalog member entry to be created in the catalog producer
+zone <zone\-name>.  <zone\-name> must exist and must be a valid catalog
+producer zone.
 .SS "Key Declarations"
-The 
-.B key: 
-clause establishes a key for use in access control lists. It has 
+The
+.B key:
+clause establishes a key for use in access control lists. It has
 the following attributes.
 .TP
 .B name:\fR <string>
@@ -1013,7 +1059,7 @@ Algorithms are only available when they were compiled in (available in the
 crypto library).
 .TP
 .B secret:\fR <base64 blob>
-The base64 encoded shared secret. It is possible to put the 
+The base64 encoded shared secret. It is possible to put the
 .B secret:
 declaration (and base64 blob) into a different file, and then to
 .B include:
@@ -1023,10 +1069,10 @@ The content of the secret is the agreed base64 secret content.  To make it
 up, enter a password (its length must be a multiple of 4 characters, A\-Za\-z0\-9), or use
 dev-random output through a base64 encode filter.
 .SS "TLS Auth Declarations"
-The 
-.B tls-auth: 
+The
+.B tls-auth:
 clause establishes authentication attributes to use when authenticating
-the far end of an outgoing TLS connection used in access control lists for XFR-over-TLS. 
+the far end of an outgoing TLS connection used in access control lists for XFR-over-TLS.
 It has the following attributes.
 .TP
 .B name:\fR <string>
@@ -1106,20 +1152,20 @@ These are client queries to NSD.
 Enable to log auth response messages.  Default is no.
 These are responses from NSD to clients.
 .SH "NSD CONFIGURATION FOR BIND9 HACKERS"
-BIND9 is a name server implementation with its own configuration 
-file format, named.conf(5). BIND9 types zones as 'Master' or 'Slave'. 
+BIND9 is a name server implementation with its own configuration
+file format, named.conf(5). BIND9 types zones as 'Master' or 'Slave'.
 .SS "Slave zones"
-For a slave zone, the master servers are listed. The master servers are 
-queried for zone data, and are listened to for update notifications. 
-In NSD these two properties need to be configured separately, by listing 
-the master address in allow\-notify and request\-xfr statements. 
+For a secondary zone, the primary servers are listed. The primary servers are
+queried for zone data, and are listened to for update notifications.
+In NSD these two properties need to be configured separately, by listing
+the primary address in allow\-notify and request\-xfr statements.
 .P
 In BIND9 you only need to provide allow\-notify elements for
 any extra sources of notifications (i.e. the operators), NSD needs to have
-allow\-notify for both masters and operators. BIND9 allows 
+allow\-notify for both primaries and operators. BIND9 allows
 additional transfer sources, in NSD you list those as request\-xfr.
 .P
-Here is an example of a slave zone in BIND9 syntax.
+Here is an example of a secondary zone in BIND9 syntax.
 .P
 # Config file for example.org
 options {
@@ -1147,20 +1193,20 @@ keys { tsig.example.org. ; };
 .LP
 zone "example.org" {
 .RS 5
-type slave;
+type secondary;
 .RE
 .RS 5
 file "secondary/example.org.signed";
 .RE
 .RS 5
-masters { 162.0.4.49; };
+primaries { 162.0.4.49; };
 .RE
 };
 .P
-For NSD, DNSSEC is enabled automatically for zones that are signed. The 
-dnssec\-enable statement in the options clause is not needed. In NSD 
-keys are associated with an IP address in the access control list 
-statement, therefore the server{} statement is not needed. Below is 
+For NSD, DNSSEC is enabled automatically for zones that are signed. The
+dnssec\-enable statement in the options clause is not needed. In NSD
+keys are associated with an IP address in the access control list
+statement, therefore the server{} statement is not needed. Below is
 the same example in an NSD config file.
 .LP
 # Config file for example.org
@@ -1185,33 +1231,33 @@ name: "example.org"
 zonefile: "secondary/example.org.signed"
 .RE
 .RS 5
-# the master is allowed to notify and will provide zone data.
+# the primary is allowed to notify and will provide zone data.
 .RE
 .RS 5
-allow\-notify: 162.0.4.49 NOKEY 
+allow\-notify: 162.0.4.49 NOKEY
 .RE
 .RS 5
 request\-xfr: 162.0.4.49 tsig.example.org.
 .RE
 .P
-Notice that the master is listed twice, once to allow it to send notifies
-to this slave server and once to tell the slave server where to look for
-updates zone data. More allow\-notify and request\-xfr lines can be 
-added to specify more masters.
+Notice that the primary is listed twice, once to allow it to send notifies
+to this secondary server and once to tell the secondary server where to look for
+updates zone data. More allow\-notify and request\-xfr lines can be
+added to specify more primaries.
 .P
-It is possible to specify extra allow\-notify lines for addresses 
-that are also allowed to send notifications to this slave server.
+It is possible to specify extra allow\-notify lines for addresses
+that are also allowed to send notifications to this secondary server.
 .SS "Master zones"
-For a master zone in BIND9, the slave servers are listed. These slave
+For a primary zone in BIND9, the secondary servers are listed. These secondary
 servers are sent notifications of updated and are allowed to request
-transfer of the zone data. In NSD these two properties need to be 
+transfer of the zone data. In NSD these two properties need to be
 configured separately.
 .P
-Here is an example of a master zone in BIND9 syntax.
+Here is an example of a primary zone in BIND9 syntax.
 .LP
 zone "example.nl" {
 .RS 5
-type master;
+type primary;
 .RE
 .RS 5
 file "example.nl";
@@ -1238,7 +1284,7 @@ provide\-xfr: ::0/0 NOKEY
 .RE
 .P
 .RS 5
-# to list a slave server you would in general give
+# to list a secondary server you would in general give
 .RE
 .RS 5
 # provide\-xfr: 1.2.3.4 tsig\-key.name.
@@ -1247,31 +1293,25 @@ provide\-xfr: ::0/0 NOKEY
 # notify: 1.2.3.4 NOKEY
 .RE
 .SS "Other"
-NSD is an authoritative only DNS server. This means that it is 
-meant as a primary or secondary server for zones, providing DNS 
-data to DNS resolvers and caches. BIND9 can function as an 
-authoritative DNS server, the configuration options for that are 
-compared with those for NSD in this section. However, BIND9 can 
+NSD is an authoritative only DNS server. This means that it is
+meant as a primary or secondary server for zones, providing DNS
+data to DNS resolvers and caches. BIND9 can function as an
+authoritative DNS server, the configuration options for that are
+compared with those for NSD in this section. However, BIND9 can
 also function as a resolver or cache. The configuration options that
 BIND9 has for the resolver or caching thus have no equivalents for NSD.
 .SH "FILES"
 .TP
-"@dbfile@"
-default
-.B NSD
-database
-.TP
 @nsdconfigfile@
 default
 .B NSD
 configuration file
-.SH "SEE ALSO" 
+.SH "SEE ALSO"
 \fInsd\fR(8), \fInsd\-checkconf\fR(8), \fInsd\-control\fR(8)
 .SH "AUTHORS"
 .B NSD
-was written by NLnet Labs and RIPE NCC joint team. Please see 
+was written by a combined team from NLnet Labs and RIPE NCC. Please see the
 CREDITS file in the distribution for further details.
 .SH "BUGS"
 .B nsd.conf
-is parsed by a primitive parser, error messages may not be to the 
-point.
+is parsed by a primitive parser. Error messages may not be to the point.
index 1ffeacd..ddf4a8c 100644 (file)
@@ -399,12 +399,12 @@ remote-control:
        # which is only offered for transfer to secondaries over TLS.
        #allow-query: 192.0.2.0/24 NOKEY
 
-       # If no master and slave access control elements are provided,
+       # If no primary and secondary access control elements are provided,
        # this zone will not be served to/from other servers.
 
-       # A master zone needs notify: and provide-xfr: lists.  A slave
+       # A primary zone needs notify: and provide-xfr: lists.  A secondary
        # may also allow zone transfer (for debug or other secondaries).
-       # notify these slaves when the master zone changes, address TSIG|NOKEY
+       # notify these secondaries when the primary zone changes, address TSIG|NOKEY
        # IP can be ipv4 and ipv6, with @port for a nondefault port number.
        #notify: 192.0.2.1 NOKEY
        # allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
@@ -425,9 +425,9 @@ remote-control:
        # provide-xfr: 0.0.0.0/0 NOKEY
        # provide-xfr: ::0/0 NOKEY
 
-       # A slave zone needs allow-notify: and request-xfr: lists.
+       # A secondary zone needs allow-notify: and request-xfr: lists.
        #allow-notify: 2001:db8::0/64 my_tsig_key_name
-       # By default, a slave will request a zone transfer with IXFR/TCP.
+       # By default, a secondary will request a zone transfer with IXFR/TCP.
        # If you want to make use of IXFR/UDP use: UDP addr tsigkey
        # for a master that only speaks AXFR (like NSD) use AXFR addr tsigkey
        # If you want to require use of XFR-over-TLS use: addr tsigkey tlsauthname
@@ -451,9 +451,9 @@ remote-control:
        # retry values (limited to the bounds given with the above parameters), plus 1.
        #min-expire-time: 0
 
-       # Slave server tries zone transfer to all masters and picks highest
-       # zone version available, for when masters have different versions.
-       #multi-master-check: no
+       # Secondary server tries zone transfer to all primaries and picks highest
+       # zone version available, for when primaries have different versions.
+       #multi-primary-check: no
 
        # limit the zone transfer size (in bytes), stops very large transfers
        # 0 is no limits enforced.
@@ -467,7 +467,7 @@ remote-control:
        # from that pattern are inserted into this one (as if it were a
        # macro).  The statement can be given in between other statements,
        # because the order of access control elements can make a difference
-       # (which master to request from first, which slave to notify first).
+       # (which master to request from first, which secondary to notify first).
        #include-pattern: "common-masters"
 
        # Verify zone before publishing.
@@ -488,6 +488,24 @@ remote-control:
        # Default is verifier-timeout in verify.
        # verifier-timeout: 0
 
+       # Turn this zone into a catalog consumer zone.
+       # The catalog-member-pattern option is the default pattern that
+       # will be used for members without or with invalid group property.
+       # catalog: consumer
+       # catalog-member-pattern: "example-pattern"
+
+       # Turn this zone into a catalog producer zone.
+       # Member zones can be added using nsd-control addzone <zone> <pattern>
+       # where <pattern> is a pattern containing a catalog-producer-zone
+       # option pointing to this zone.
+       # catalog: producer
+
+       # Use this pattern to add catalog producer members. "catalog1.invalid"
+       # needs to be a valid catalog producer zone; i.e. a primary zone
+       # without a request-xfr option and with and catalog option set to
+       # producer.
+       # catalog-producer-member: "catalog1.invalid"
+
 # Fixed zone entries.  Here you can config zones that cannot be deleted.
 # Zones that are dynamically added and deleted are put in the zonelist file.
 #
index fb501ac..48fb8de 100644 (file)
@@ -21,6 +21,7 @@
 #include "difffile.h"
 #include "rrl.h"
 #include "bitset.h"
+#include "xfrd.h"
 
 #include "configparser.h"
 config_parser_state_type* cfg_parser = 0;
@@ -202,7 +203,8 @@ warn_if_directory(const char* filetype, FILE* f, const char* fname)
 
 int
 parse_options_file(struct nsd_options* opt, const char* file,
-       void (*err)(void*,const char*), void* err_arg)
+       void (*err)(void*,const char*), void* err_arg,
+       struct nsd_options* old_opts)
 {
        FILE *in = 0;
        struct pattern_options* pat;
@@ -246,6 +248,10 @@ parse_options_file(struct nsd_options* opt, const char* file,
 
        RBTREE_FOR(pat, struct pattern_options*, opt->patterns)
        {
+               struct pattern_options* old_pat =
+                       old_opts ? pattern_options_find(old_opts, pat->pname)
+                                : NULL;
+
                /* lookup keys for acls */
                for(acl=pat->allow_notify; acl; acl=acl->next)
                {
@@ -300,6 +306,43 @@ parse_options_file(struct nsd_options* opt, const char* file,
                                c_error("key %s in pattern %s could not be found",
                                        acl->key_name, pat->pname);
                }
+               /* lookup zones for catalog-producer-zone options */
+               if(pat->catalog_producer_zone) {
+                       struct zone_options* zopt;
+                       const dname_type *dname = dname_parse(opt->region,
+                                       pat->catalog_producer_zone);
+                       if(dname == NULL) {
+                               ; /* pass; already erred during parsing */
+
+                       } else if (!(zopt = zone_options_find(opt, dname))) {
+                               c_error("catalog producer zone %s in pattern "
+                                       "%s could not be found",
+                                       pat->catalog_producer_zone,
+                                       pat->pname);
+
+                       } else if (!zone_is_catalog_producer(zopt)) {
+                               c_error("catalog-producer-zone %s in pattern "
+                                       "%s is not configered as a "
+                                       "catalog: producer",
+                                       pat->catalog_producer_zone,
+                                       pat->pname);
+                       }
+               }
+               if( !old_opts /* Okay to add a cat producer member zone pat */
+               || (!old_pat) /* But not to add, change or del an existing */
+               || ( old_pat && !old_pat->catalog_producer_zone
+                            &&     !pat->catalog_producer_zone)
+               || ( old_pat &&  old_pat->catalog_producer_zone
+                            &&      pat->catalog_producer_zone
+                            && strcmp( old_pat->catalog_producer_zone
+                                     ,     pat->catalog_producer_zone) == 0)){
+                       ; /* No existing catalog producer member zone added
+                          * or changed. Everyting is fine: pass */
+               } else {
+                       c_error("catalog-producer-zone in pattern %s cannot "
+                               "be removed or changed on a running NSD",
+                               pat->pname);
+               }
        }
 
        if(cfg_parser->errors > 0)
@@ -372,18 +415,26 @@ zone_list_free_insert(struct nsd_options* opt, int linesize, off_t off)
        opt->zonefree_number++;
 }
 
-struct zone_options*
-zone_list_zone_insert(struct nsd_options* opt, const char* nm,
-       const char* patnm, int linesize, off_t off)
+static struct zone_options*
+zone_list_member_zone_insert(struct nsd_options* opt, const char* nm,
+       const char* patnm, int linesize, off_t off, const char* mem_idnm,
+       new_member_id_type new_member_id)
 {
        struct pattern_options* pat = pattern_options_find(opt, patnm);
+       struct catalog_member_zone* cmz = NULL;
        struct zone_options* zone;
+       char member_id_str[MAXDOMAINLEN * 5 + 3] = "ERROR!";
+       DEBUG(DEBUG_XFRD, 2, (LOG_INFO, "zone_list_zone_insert(\"%s\", \"%s\""
+               ", %d, \"%s\")", nm, patnm, linesize,
+               (mem_idnm ? mem_idnm : "<NULL>")));
        if(!pat) {
                log_msg(LOG_ERR, "pattern does not exist for zone %s "
                        "pattern %s", nm, patnm);
                return NULL;
        }
-       zone = zone_options_create(opt->region);
+       zone = pat->catalog_producer_zone
+            ? &(cmz = catalog_member_zone_create(opt->region))->options
+            : zone_options_create(opt->region);
        zone->part_of_config = 0;
        zone->name = region_strdup(opt->region, nm);
        zone->linesize = linesize;
@@ -396,9 +447,40 @@ zone_list_zone_insert(struct nsd_options* opt, const char* nm,
                region_recycle(opt->region, zone, sizeof(*zone));
                return NULL;
        }
+       if(!mem_idnm) {
+               if(cmz && new_member_id)
+                       new_member_id(cmz);
+               if(cmz && cmz->member_id) {
+                       /* Assume all bytes of member_id are printable.
+                        * plus 1 for space
+                        */
+                       zone->linesize += label_length(dname_name(cmz->member_id)) + 1;
+                       DEBUG(DEBUG_XFRD, 2, (LOG_INFO, "new linesize: %d",
+                               (int)zone->linesize));
+               }
+       } else if(!cmz)
+               log_msg(LOG_ERR, "member ID '%s' given, but no catalog-producer-"
+                       "zone value provided in zone '%s' or pattern '%s'",
+                       mem_idnm, nm, patnm);
+
+       else if(snprintf(member_id_str, sizeof(member_id_str),
+           "%s.zones.%s", mem_idnm, pat->catalog_producer_zone) >=
+           (int)sizeof(member_id_str))
+               log_msg(LOG_ERR, "syntax error in member ID '%s.zones.%s' for "
+                       "zone '%s'", mem_idnm, pat->catalog_producer_zone, nm);
+
+       else if(!(cmz->member_id = dname_parse(opt->region, member_id_str)))
+               log_msg(LOG_ERR, "parse error in member ID '%s' for "
+                       "zone '%s'", member_id_str, nm);
        return zone;
 }
 
+struct zone_options*
+zone_list_zone_insert(struct nsd_options* opt,const char* nm,const char* patnm)
+{
+       return zone_list_member_zone_insert(opt, nm, patnm, 0, 0, NULL, NULL);
+}
+
 int
 parse_zone_list_file(struct nsd_options* opt)
 {
@@ -465,8 +547,42 @@ parse_zone_list_file(struct nsd_options* opt)
 
                        /* store offset and line size for zone entry */
                        /* and create zone entry in zonetree */
-                       (void)zone_list_zone_insert(opt, nm, patnm, linesize,
-                               ftello(opt->zonelist)-linesize);
+                       (void)zone_list_member_zone_insert(opt, nm, patnm,
+                               linesize, ftello(opt->zonelist)-linesize,
+                               NULL, NULL);
+
+               } else if(strncmp(buf, "cat ", 4) == 0) {
+                       int linesize = strlen(buf);
+                       /* parse the 'add' line */
+                       /* pick last space on the line, so that the domain
+                        * name can have a space in it (but not the pattern)*/
+                       char* nm = buf + 4;
+                       char* mem_idnm = strrchr(nm, ' '), *patnm;
+                       if(!mem_idnm) {
+                               /* parse error */
+                               log_msg(LOG_ERR, "parse error in %s: '%s'",
+                                       opt->zonelistfile, buf);
+                               continue;
+                       }
+                       *mem_idnm++ = 0;
+                       patnm = strrchr(nm, ' ');
+                       if(!patnm) {
+                               *--mem_idnm = ' ';
+                               /* parse error */
+                               log_msg(LOG_ERR, "parse error in %s: '%s'",
+                                       opt->zonelistfile, buf);
+                               continue;
+                       }
+                       *patnm++ = 0;
+                       if(linesize && buf[linesize-1] == '\n')
+                               buf[linesize-1] = 0;
+
+                       /* store offset and line size for zone entry */
+                       /* and create zone entry in zonetree */
+                       (void)zone_list_member_zone_insert(opt, nm, patnm,
+                               linesize, ftello(opt->zonelist)-linesize,
+                               mem_idnm, NULL);
+
                } else if(strncmp(buf, "del ", 4) == 0) {
                        /* store offset and line size for deleted entry */
                        int linesize = strlen(buf);
@@ -485,26 +601,62 @@ parse_zone_list_file(struct nsd_options* opt)
 void
 zone_options_delete(struct nsd_options* opt, struct zone_options* zone)
 {
+       struct catalog_member_zone* member_zone = as_catalog_member_zone(zone);
+
        rbtree_delete(opt->zone_options, zone->node.key);
        region_recycle(opt->region, (void*)zone->node.key, dname_total_size(
                (dname_type*)zone->node.key));
-       region_recycle(opt->region, zone, sizeof(*zone));
+       if(!member_zone) {
+               region_recycle(opt->region, zone, sizeof(*zone));
+               return;
+       }
+       /* Because catalog member zones are in xfrd only deleted through
+        * catalog_del_consumer_member_zone() or through
+        * xfrd_del_catalog_producer_member(), which both clear the node,
+        * and because member zones in the main and serve processes are not
+        * indexed, *member_zone->node == *RBTREE_NULL.
+        * member_id is cleared too by those delete function, but there may be
+        * leftover member_id's from the initial zone.list processing, which
+        * made it to the main and serve processes.
+        */
+       assert(!memcmp(&member_zone->node, RBTREE_NULL, sizeof(*RBTREE_NULL)));
+       if(member_zone->member_id) {
+               region_recycle(opt->region, (void*)member_zone->member_id,
+                               dname_total_size(member_zone->member_id));
+       }
+       region_recycle(opt->region, member_zone, sizeof(*member_zone));
 }
 
+
 /* add a new zone to the zonelist */
 struct zone_options*
-zone_list_add(struct nsd_options* opt, const char* zname, const char* pname)
+zone_list_add_or_cat(struct nsd_options* opt, const char* zname,
+               const char* pname, new_member_id_type new_member_id)
 {
        int r;
        struct zonelist_free* e;
        struct zonelist_bucket* b;
-       int linesize = 6 + strlen(zname) + strlen(pname);
+       char zone_list_line[6 + 5 * MAXDOMAINLEN + 2024 + 65];
+       struct catalog_member_zone* cmz;
+
        /* create zone entry */
-       struct zone_options* zone = zone_list_zone_insert(opt, zname, pname,
-               linesize, 0);
+       struct zone_options* zone = zone_list_member_zone_insert(
+               opt, zname, pname, 6 + strlen(zname) + strlen(pname),
+               0, NULL, new_member_id);
        if(!zone)
                return NULL;
 
+       if(zone_is_catalog_producer_member(zone)
+       && (cmz = as_catalog_member_zone(zone))
+       && cmz->member_id) {
+               snprintf(zone_list_line, sizeof(zone_list_line),
+                       "cat %s %s %.*s\n", zname, pname,
+                       (int)label_length(dname_name(cmz->member_id)),
+                       (const char*)dname_name(cmz->member_id) + 1);
+       } else {
+               snprintf(zone_list_line, sizeof(zone_list_line),
+                       "add %s %s\n", zname, pname);
+       }
        /* use free entry or append to file or create new file */
        if(!opt->zonelist || opt->zonelist_off == 0) {
                /* create new file */
@@ -531,7 +683,7 @@ zone_list_add(struct nsd_options* opt, const char* zname, const char* pname)
                zone->off = ftello(opt->zonelist);
                if(zone->off == -1)
                        log_msg(LOG_ERR, "ftello(%s): %s", opt->zonelistfile, strerror(errno));
-               r = fprintf(opt->zonelist, "add %s %s\n", zname, pname);
+               r = fprintf(opt->zonelist, "%s", zone_list_line);
                if(r != zone->linesize) {
                        if(r == -1)
                                log_msg(LOG_ERR, "could not write to %s: %s",
@@ -561,7 +713,7 @@ zone_list_add(struct nsd_options* opt, const char* zname, const char* pname)
                        zone_options_delete(opt, zone);
                        return NULL;
                }
-               r = fprintf(opt->zonelist, "add %s %s\n", zname, pname);
+               r = fprintf(opt->zonelist, "%s", zone_list_line);
                if(r != zone->linesize) {
                        if(r == -1)
                                log_msg(LOG_ERR, "could not write to %s: %s",
@@ -572,7 +724,7 @@ zone_list_add(struct nsd_options* opt, const char* zname, const char* pname)
                        zone_options_delete(opt, zone);
                        return NULL;
                }
-               opt->zonelist_off += linesize;
+               opt->zonelist_off += zone->linesize;
                if(fflush(opt->zonelist) != 0) {
                        log_msg(LOG_ERR, "fflush %s: %s", opt->zonelistfile, strerror(errno));
                }
@@ -587,7 +739,7 @@ zone_list_add(struct nsd_options* opt, const char* zname, const char* pname)
                zone_options_delete(opt, zone);
                return NULL;
        }
-       r = fprintf(opt->zonelist, "add %s %s\n", zname, pname);
+       r = fprintf(opt->zonelist, "%s", zone_list_line);
        if(r != zone->linesize) {
                if(r == -1)
                        log_msg(LOG_ERR, "could not write to %s: %s",
@@ -617,6 +769,11 @@ zone_list_add(struct nsd_options* opt, const char* zname, const char* pname)
 void
 zone_list_del(struct nsd_options* opt, struct zone_options* zone)
 {
+       if (zone_is_catalog_consumer_member(zone)) {
+               /* catalog consumer member zones are not in the zones.list file */
+               zone_options_delete(opt, zone);
+               return;
+       }
        /* put its space onto the free entry */
        if(fseeko(opt->zonelist, zone->off, SEEK_SET) == -1) {
                log_msg(LOG_ERR, "fseeko(%s): %s", opt->zonelistfile, strerror(errno));
@@ -691,10 +848,21 @@ zone_list_compact(struct nsd_options* opt)
                return;
        }
        RBTREE_FOR(zone, struct zone_options*, opt->zone_options) {
+               struct catalog_member_zone* cmz;
+
                if(zone->part_of_config)
                        continue;
-               r = fprintf(out, "add %s %s\n", zone->name,
-                       zone->pattern->pname);
+               if(zone_is_catalog_producer_member(zone)
+               && (cmz = as_catalog_member_zone(zone))
+               && cmz->member_id) {
+                       r = fprintf(out, "cat %s %s %.*s\n", zone->name,
+                               zone->pattern->pname,
+                               (int)label_length(dname_name(cmz->member_id)),
+                               (const char*)dname_name(cmz->member_id) + 1);
+               } else {
+                       r = fprintf(out, "add %s %s\n", zone->name,
+                                       zone->pattern->pname);
+               }
                if(r < 0) {
                        log_msg(LOG_ERR, "write %s failed: %s", outname,
                                strerror(errno));
@@ -807,9 +975,26 @@ zone_options_create(region_type* region)
        zone->name = 0;
        zone->pattern = 0;
        zone->part_of_config = 0;
+       zone->is_catalog_member_zone = 0;
        return zone;
 }
 
+struct catalog_member_zone*
+catalog_member_zone_create(region_type* region)
+{
+       struct catalog_member_zone* member_zone;
+       member_zone = (struct catalog_member_zone*)region_alloc(region,
+                       sizeof(struct catalog_member_zone));
+       member_zone->options.node = *RBTREE_NULL;
+       member_zone->options.name = 0;
+       member_zone->options.pattern = 0;
+       member_zone->options.part_of_config = 0;
+       member_zone->options.is_catalog_member_zone = 1;
+       member_zone->member_id = NULL;
+       member_zone->node = *RBTREE_NULL;
+       return member_zone;
+}
+
 /* true is booleans are the same truth value */
 #define booleq(x,y) ( ((x) && (y)) || (!(x) && !(y)) )
 
@@ -896,7 +1081,7 @@ pattern_options_create(region_type* region)
 #ifdef RATELIMIT
        p->rrl_whitelist = 0;
 #endif
-       p->multi_master_check = 0;
+       p->multi_primary_check = 0;
        p->store_ixfr = 0;
        p->store_ixfr_is_default = 1;
        p->ixfr_size = IXFR_SIZE_DEFAULT;
@@ -912,7 +1097,10 @@ pattern_options_create(region_type* region)
        p->verifier_feed_zone_is_default = 1;
        p->verifier_timeout = VERIFIER_TIMEOUT_INHERIT;
        p->verifier_timeout_is_default = 1;
-
+       p->catalog_role = CATALOG_ROLE_INHERIT;
+       p->catalog_role_is_default = 1;
+       p->catalog_member_pattern = NULL;
+       p->catalog_producer_zone = NULL;
        return p;
 }
 
@@ -1099,7 +1287,7 @@ copy_pat_fixed(region_type* region, struct pattern_options* orig,
 #ifdef RATELIMIT
        orig->rrl_whitelist = p->rrl_whitelist;
 #endif
-       orig->multi_master_check = p->multi_master_check;
+       orig->multi_primary_check = p->multi_primary_check;
        orig->store_ixfr = p->store_ixfr;
        orig->store_ixfr_is_default = p->store_ixfr_is_default;
        orig->ixfr_size = p->ixfr_size;
@@ -1114,6 +1302,16 @@ copy_pat_fixed(region_type* region, struct pattern_options* orig,
        orig->verifier_timeout_is_default = p->verifier_timeout_is_default;
        orig->verifier_feed_zone = p->verifier_feed_zone;
        orig->verifier_feed_zone_is_default = p->verifier_feed_zone_is_default;
+       orig->catalog_role = p->catalog_role;
+       orig->catalog_role_is_default = p->catalog_role_is_default;
+       if(p->catalog_member_pattern)
+               orig->catalog_member_pattern =
+                       region_strdup(region, p->catalog_member_pattern);
+       else orig->catalog_member_pattern = NULL;
+       if(p->catalog_producer_zone)
+               orig->catalog_producer_zone =
+                       region_strdup(region, p->catalog_producer_zone);
+       else orig->catalog_producer_zone = NULL;
 }
 
 void
@@ -1227,7 +1425,7 @@ pattern_options_equal(struct pattern_options* p, struct pattern_options* q)
 #ifdef RATELIMIT
        if(p->rrl_whitelist != q->rrl_whitelist) return 0;
 #endif
-       if(!booleq(p->multi_master_check,q->multi_master_check)) return 0;
+       if(!booleq(p->multi_primary_check,q->multi_primary_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;
@@ -1248,6 +1446,19 @@ pattern_options_equal(struct pattern_options* p, struct pattern_options* q)
        if(p->verifier_timeout != q->verifier_timeout) return 0;
        if(!booleq(p->verifier_timeout_is_default,
                q->verifier_timeout_is_default)) return 0;
+       if(p->catalog_role != q->catalog_role) return 0;
+       if(!booleq(p->catalog_role_is_default,
+               q->catalog_role_is_default)) return 0;
+       if(!p->catalog_member_pattern && q->catalog_member_pattern) return 0;
+       else if(p->catalog_member_pattern && !q->catalog_member_pattern) return 0;
+       else if(p->catalog_member_pattern && q->catalog_member_pattern) {
+               if(strcmp(p->catalog_member_pattern, q->catalog_member_pattern) != 0) return 0;
+       }
+       if(!p->catalog_producer_zone && q->catalog_producer_zone) return 0;
+       else if(p->catalog_producer_zone && !q->catalog_producer_zone) return 0;
+       else if(p->catalog_producer_zone && q->catalog_producer_zone) {
+               if(strcmp(p->catalog_producer_zone, q->catalog_producer_zone) != 0) return 0;
+       }
        return 1;
 }
 
@@ -1456,7 +1667,7 @@ pattern_options_marshal(struct buffer* b, struct pattern_options* p)
        marshal_u8(b, p->min_retry_time_is_default);
        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->multi_primary_check);
        marshal_u8(b, p->store_ixfr);
        marshal_u8(b, p->store_ixfr_is_default);
        marshal_u64(b, p->ixfr_size);
@@ -1472,6 +1683,10 @@ pattern_options_marshal(struct buffer* b, struct pattern_options* p)
        marshal_u8(b, p->verifier_feed_zone_is_default);
        marshal_u32(b, p->verifier_timeout);
        marshal_u8(b, p->verifier_timeout_is_default);
+       marshal_u8(b, p->catalog_role);
+       marshal_u8(b, p->catalog_role_is_default);
+       marshal_str(b, p->catalog_member_pattern);
+       marshal_str(b, p->catalog_producer_zone);
 }
 
 struct pattern_options*
@@ -1506,7 +1721,7 @@ pattern_options_unmarshal(region_type* r, struct buffer* b)
        p->min_retry_time_is_default = unmarshal_u8(b);
        p->min_expire_time = unmarshal_u32(b);
        p->min_expire_time_expr = unmarshal_u8(b);
-       p->multi_master_check = unmarshal_u8(b);
+       p->multi_primary_check = unmarshal_u8(b);
        p->store_ixfr = unmarshal_u8(b);
        p->store_ixfr_is_default = unmarshal_u8(b);
        p->ixfr_size = unmarshal_u64(b);
@@ -1522,6 +1737,10 @@ pattern_options_unmarshal(region_type* r, struct buffer* b)
        p->verifier_feed_zone_is_default = unmarshal_u8(b);
        p->verifier_timeout = unmarshal_u32(b);
        p->verifier_timeout_is_default = unmarshal_u8(b);
+       p->catalog_role = unmarshal_u8(b);
+       p->catalog_role_is_default = unmarshal_u8(b);
+       p->catalog_member_pattern = unmarshal_str(r, b);
+       p->catalog_producer_zone = unmarshal_str(r, b);
        return p;
 }
 
@@ -2334,6 +2553,18 @@ config_apply_pattern(struct pattern_options *dest, const char* name)
                c_error("could not find pattern %s", name);
                return;
        }
+       if(strncmp(dest->pname, PATTERN_IMPLICIT_MARKER,
+                               strlen(PATTERN_IMPLICIT_MARKER)) == 0
+       && pat->catalog_producer_zone) {
+               c_error("patterns with an catalog-producer-zone option are to "
+                       "be used with \"nsd-control addzone\" only and cannot "
+                       "be included from zone clauses in the config file");
+               return;
+       }
+       if((dest->catalog_role == CATALOG_ROLE_PRODUCER &&  pat->request_xfr)
+       || ( pat->catalog_role == CATALOG_ROLE_PRODUCER && dest->request_xfr)){
+               c_error("catalog producer zones cannot be secondary zones");
+       }
 
        /* apply settings */
        if(pat->zonefile)
@@ -2397,8 +2628,8 @@ config_apply_pattern(struct pattern_options *dest, const char* name)
        copy_and_append_acls(&dest->provide_xfr, pat->provide_xfr);
        copy_and_append_acls(&dest->allow_query, pat->allow_query);
        copy_and_append_acls(&dest->outgoing_interface, pat->outgoing_interface);
-       if(pat->multi_master_check)
-               dest->multi_master_check = pat->multi_master_check;
+       if(pat->multi_primary_check)
+               dest->multi_primary_check = pat->multi_primary_check;
 
        if(!pat->verify_zone_is_default) {
                dest->verify_zone = pat->verify_zone;
@@ -2435,6 +2666,16 @@ config_apply_pattern(struct pattern_options *dest, const char* name)
                }
                dest->verifier = vec;
        }
+       if(!pat->catalog_role_is_default) {
+               dest->catalog_role = pat->catalog_role;
+               dest->catalog_role_is_default = 0;
+       }
+       if(pat->catalog_member_pattern)
+               dest->catalog_member_pattern = region_strdup(
+                       cfg_parser->opt->region, pat->catalog_member_pattern);
+       if(pat->catalog_producer_zone)
+               dest->catalog_producer_zone = region_strdup(
+                       cfg_parser->opt->region, pat->catalog_producer_zone);
 }
 
 void
index baf2579..da4a5b7 100644 (file)
@@ -20,6 +20,7 @@ struct buffer;
 struct nsd;
 struct proxy_protocol_port_list;
 
+
 typedef struct nsd_options nsd_options_type;
 typedef struct pattern_options pattern_options_type;
 typedef struct zone_options zone_options_type;
@@ -35,6 +36,9 @@ typedef struct config_parser_state config_parser_state_type;
 #define VERIFY_ZONE_INHERIT (2)
 #define VERIFIER_FEED_ZONE_INHERIT (2)
 #define VERIFIER_TIMEOUT_INHERIT (-1)
+#define CATALOG_ROLE_INHERIT  (0)
+#define CATALOG_ROLE_CONSUMER (1)
+#define CATALOG_ROLE_PRODUCER (2)
 
 /*
  * Options global for nsd.
@@ -292,7 +296,7 @@ struct pattern_options {
         */
        uint8_t min_expire_time_expr;
        uint64_t size_limit_xfr;
-       uint8_t multi_master_check;
+       uint8_t multi_primary_check;
        uint8_t store_ixfr;
        uint8_t store_ixfr_is_default;
        uint64_t ixfr_size;
@@ -308,6 +312,10 @@ struct pattern_options {
        uint8_t verifier_feed_zone_is_default;
        int32_t verifier_timeout;
        uint8_t verifier_timeout_is_default;
+       uint8_t catalog_role;
+       uint8_t catalog_role_is_default;
+       const char* catalog_member_pattern;
+       const char* catalog_producer_zone;
 } ATTR_PACKED;
 
 #define PATTERN_IMPLICIT_MARKER "_implicit_"
@@ -328,9 +336,27 @@ struct zone_options {
         * a anonymous pattern created in-place */
        struct pattern_options* pattern;
        /* zone is fixed into the main config, not in zonelist, cannot delete */
-       uint8_t part_of_config;
+       unsigned part_of_config        : 1;
+       unsigned is_catalog_member_zone: 1;
+} ATTR_PACKED;
+
+/*
+ * Options for catalog member zones
+ * assert(options->is_catalog_member_zone == 1)
+ * when options->pattern->catalog_producer_zone is set, this is a
+ * producer member zone, otherwise a consumer member zone.
+ * A catalog member zone is either a member zone of a catalog producer zone
+ * or a catalog consumer zone. They are mutually exclusive.
+ */
+struct catalog_member_zone {
+       struct zone_options          options;
+       const struct dname*          member_id;
+       /* node in the associated catalog consumer or producer zone */
+       rbnode_type                  node;
 } ATTR_PACKED;
 
+typedef void (*new_member_id_type)(struct catalog_member_zone* zone);
+
 union acl_addr_storage {
 #ifdef INET6
        struct in_addr addr;
@@ -459,9 +485,13 @@ int nsd_options_insert_pattern(struct nsd_options* opt,
 /* parses options file. Returns false on failure. callback, if nonNULL,
  * gets called with error strings, default prints. */
 int parse_options_file(struct nsd_options* opt, const char* file,
-       void (*err)(void*,const char*), void* err_arg);
+       void (*err)(void*,const char*), void* err_arg,
+       struct nsd_options* old_opts);
 struct zone_options* zone_options_create(region_type* region);
 void zone_options_delete(struct nsd_options* opt, struct zone_options* zone);
+struct catalog_member_zone* catalog_member_zone_create(region_type* region);
+static inline struct catalog_member_zone* as_catalog_member_zone(struct zone_options* zopt)
+{ return zopt && zopt->is_catalog_member_zone ? (struct catalog_member_zone*)zopt : NULL; }
 /* find a zone by apex domain name, or NULL if not found. */
 struct zone_options* zone_options_find(struct nsd_options* opt,
        const struct dname* apex);
@@ -488,12 +518,16 @@ void tls_auth_options_insert(struct nsd_options* opt, struct tls_auth_options* a
 struct tls_auth_options* tls_auth_options_find(struct nsd_options* opt, const char* name);
 /* read in zone list file. Returns false on failure */
 int parse_zone_list_file(struct nsd_options* opt);
+/* create (potential) catalog producer member entry and add to the zonelist */
+struct zone_options* zone_list_add_or_cat(struct nsd_options* opt,
+       const char* zname, const char* pname, new_member_id_type new_member_id);
 /* create zone entry and add to the zonelist file */
-struct zone_options* zone_list_add(struct nsd_options* opt, const char* zname,
-       const char* pname);
+static inline struct zone_options* zone_list_add(struct nsd_options* opt,
+       const char* zname, const char* pname)
+{ return zone_list_add_or_cat(opt, zname, pname, NULL); }
 /* create zonelist entry, do not insert in file (called by _add) */
 struct zone_options* zone_list_zone_insert(struct nsd_options* opt,
-       const char* nm, const char* patnm, int linesize, off_t off);
+       const char* nm, const char* patnm);
 void zone_list_del(struct nsd_options* opt, struct zone_options* zone);
 void zone_list_compact(struct nsd_options* opt);
 void zone_list_close(struct nsd_options* opt);
@@ -544,6 +578,20 @@ int acl_equal(struct acl_options* p, struct acl_options* q);
 
 /* see if a zone is a slave or a master zone */
 int zone_is_slave(struct zone_options* opt);
+/* see if a zone is a catalog consumer */
+static inline int zone_is_catalog_consumer(struct zone_options* opt)
+{ return opt && opt->pattern
+             && opt->pattern->catalog_role == CATALOG_ROLE_CONSUMER; }
+static inline int zone_is_catalog_producer(struct zone_options* opt)
+{ return opt && opt->pattern
+             && opt->pattern->catalog_role == CATALOG_ROLE_PRODUCER; }
+static inline int zone_is_catalog_member(struct zone_options* opt)
+{ return opt && opt->is_catalog_member_zone; }
+static inline const char* zone_is_catalog_producer_member(struct zone_options* opt)
+{ return opt && opt->pattern && opt->pattern->catalog_producer_zone
+                              ? opt->pattern->catalog_producer_zone : NULL; }
+static inline int zone_is_catalog_consumer_member(struct zone_options* opt)
+{ return zone_is_catalog_member(opt) && !zone_is_catalog_producer_member(opt); }
 /* create zonefile name, returns static pointer (perhaps to options data) */
 const char* config_make_zonefile(struct zone_options* zone, struct nsd* nsd);
 
index 3f92ffb..7881dbe 100644 (file)
@@ -1365,6 +1365,18 @@ answer_lookup_zone(struct nsd *nsd, struct query *q, answer_type *answer,
                                why->ip_address_spec,
                                why->nokey?"NOKEY":
                                (why->blocked?"BLOCKED":why->key_name)));
+               } else if(q->qtype == TYPE_SOA
+                      &&  0 == dname_compare(q->qname,
+                               (const dname_type*)q->zone->opts->node.key)
+                      && -1 != acl_check_incoming(
+                               q->zone->opts->pattern->provide_xfr, q,&why)) {
+                       assert(why);
+                       DEBUG(DEBUG_QUERY,1, (LOG_INFO, "SOA apex query %s "
+                               "passed request-xfr acl %s %s",
+                               dname_to_string(q->qname, NULL),
+                               why->ip_address_spec,
+                               why->nokey?"NOKEY":
+                               (why->blocked?"BLOCKED":why->key_name)));
                } else {
                        if (verbosity >= 2) {
                                char address[128];
index 13fd20f..39743dd 100644 (file)
@@ -74,6 +74,7 @@
 #include "remote.h"
 #include "util.h"
 #include "xfrd.h"
+#include "xfrd-catalog-zones.h"
 #include "xfrd-notify.h"
 #include "xfrd-tcp.h"
 #include "nsd.h"
 #define REMOTE_CONTROL_TCP_TIMEOUT 120
 
 /** repattern to master or slave */
-#define REPAT_SLAVE  1
-#define REPAT_MASTER 2
+#define REPAT_SLAVE                   1
+#define REPAT_MASTER                  2
+#define REPAT_CATALOG_CONSUMER        4
+#define REPAT_CATALOG_CONSUMER_DEINIT 8
 
 /** if you want zero to be inhibited in stats output.
  * it omits zeroes for types that have no acronym and unused-rcodes */
@@ -665,7 +668,8 @@ remote_accept_callback(int fd, short event, void* arg)
                n->ssl = SSL_new(rc->ctx);
                if(!n->ssl) {
                        log_crypto_err("could not SSL_new");
-                       event_del(&n->c);
+                       if(n->event_added)
+                               event_del(&n->c);
                        free(n);
                        goto close_exit;
                }
@@ -673,7 +677,8 @@ remote_accept_callback(int fd, short event, void* arg)
                (void)SSL_set_mode(n->ssl, SSL_MODE_AUTO_RETRY);
                if(!SSL_set_fd(n->ssl, newfd)) {
                        log_crypto_err("could not SSL_set_fd");
-                       event_del(&n->c);
+                       if(n->event_added)
+                               event_del(&n->c);
                        SSL_free(n->ssl);
                        free(n);
                        goto close_exit;
@@ -958,7 +963,7 @@ do_transfer(RES* ssl, xfrd_state_type* xfrd, char* arg)
                        xfrd_handle_notify_and_start_xfr(zone, NULL);
                        send_ok(ssl);
                } else {
-                       (void)ssl_printf(ssl, "error zone not slave\n");
+                       (void)ssl_printf(ssl, "error zone not secondary\n");
                }
        } else {
                RBTREE_FOR(zone, xfrd_zone_type*, xfrd->zones) {
@@ -999,7 +1004,7 @@ do_force_transfer(RES* ssl, xfrd_state_type* xfrd, char* arg)
                        force_transfer_zone(zone);
                        send_ok(ssl);
                } else {
-                       (void)ssl_printf(ssl, "error zone not slave\n");
+                       (void)ssl_printf(ssl, "error zone not secondary\n");
                }
        } else {
                RBTREE_FOR(zone, xfrd_zone_type*, xfrd->zones) {
@@ -1037,6 +1042,62 @@ print_zonestatus(RES* ssl, xfrd_state_type* xfrd, struct zone_options* zo)
                if(!ssl_printf(ssl, "   pattern: %s\n", zo->pattern->pname))
                        return 0;
        }
+       if(zone_is_catalog_consumer(zo)) {
+               zone_type* zone = namedb_find_zone(xfrd->nsd->db,
+                               (const dname_type*)zo->node.key);
+               struct xfrd_catalog_consumer_zone* consumer_zone =
+                       (struct xfrd_catalog_consumer_zone*)
+                       rbtree_search( xfrd->catalog_consumer_zones
+                                    , zo->node.key);
+
+               if(!ssl_printf(ssl, "   catalog: consumer"))
+                       return 0;
+               if(zone && zone->soa_rrset && zone->soa_rrset->rrs
+               && zone->soa_rrset->rrs[0].rdata_count > 2
+               && rdata_atom_size(zone->soa_rrset->rrs[0].rdatas[2]) ==
+                                                       sizeof(uint32_t)) {
+                       if(!ssl_printf(ssl, " (serial: %u, # members: %zu)\n",
+                                       read_uint32(rdata_atom_data(
+                                       zone->soa_rrset->rrs[0].rdatas[2])),
+                                         consumer_zone
+                                       ? consumer_zone->member_ids.count : 0))
+                               return 0;
+
+               } else if(!ssl_printf(ssl, "\n"))
+                       return 0;
+               if(invalid_catalog_consumer_zone(zo)) {
+                       if(!ssl_printf(ssl, "   catalog-invalid: %s\n",
+                                       invalid_catalog_consumer_zone(zo)))
+                               return 0;
+               }
+       }
+       if(zone_is_catalog_producer(zo)) {
+               struct xfrd_catalog_producer_zone* producer_zone =
+                       (struct xfrd_catalog_producer_zone*)
+                       rbtree_search( xfrd->catalog_producer_zones
+                                    , zo->node.key);
+               if(!ssl_printf(ssl, "   catalog: producer"))
+                       return 0;
+               if(producer_zone) {
+                       if(!ssl_printf(ssl, " (serial: %u, # members: %zu)\n",
+                                       (unsigned)producer_zone->serial,
+                                       producer_zone->member_ids.count))
+                               return 0;
+               } else if(!ssl_printf(ssl, "\n"))
+                       return 0;
+               if (zone_is_slave(zo)) {
+                       if(!ssl_printf(ssl, "   catalog-invalid: a catalog "
+                                       "producer cannot be a secondary zone"))
+                               return 0;
+               }
+       }
+       if(zone_is_catalog_member(zo)) {
+               if(!ssl_printf(ssl, "   catalog-member-id: %s\n",
+                  as_catalog_member_zone(zo)->member_id
+                ? dname_to_string(as_catalog_member_zone(zo)->member_id, NULL)
+                : "ERROR member-id is missing!"))
+                       return 0;
+       }
        if(nz) {
                if(nz->is_waiting) {
                        if(!ssl_printf(ssl, "   notify: \"waiting-for-fd\"\n"))
@@ -1057,7 +1118,7 @@ print_zonestatus(RES* ssl, xfrd_state_type* xfrd, struct zone_options* zo)
                }
        }
        if(!xz) {
-               if(!ssl_printf(ssl, "   state: master\n"))
+               if(!ssl_printf(ssl, "   state: primary\n"))
                        return 0;
                return 1;
        }
@@ -1267,6 +1328,15 @@ perform_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
                        dname = NULL;
                        return 0;
                }
+               if(zone_is_catalog_consumer_member(zopt)) {
+                       (void)ssl_printf(ssl, "Error: Zone is a catalog "
+                         "consumer member zone with id %s\nRepattern in the "
+                         "catalog with a group property.\n", dname_to_string(
+                         as_catalog_member_zone(zopt)->member_id, NULL));
+                       region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
+                       dname = NULL;
+                       return 0;
+               }
                /* found the zone, now delete it */
                /* create deletion task */
                /* this deletion task is processed before the addition task,
@@ -1281,6 +1351,10 @@ perform_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
                        xfrd_del_slave_zone(xfrd, dname);
                }
                xfrd_del_notify(xfrd, dname);
+               /* delete it in xfrd's catalog consumers list */
+               if(zone_is_catalog_consumer(zopt)) {
+                       xfrd_deinit_catalog_consumer_zone(xfrd, dname);
+               }
                /* delete from config */
                zone_list_del(xfrd->nsd->options, zopt);
        } else {
@@ -1290,7 +1364,8 @@ perform_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
        dname = NULL;
 
        /* add to zonelist and adds to config in memory */
-       zopt = zone_list_add(xfrd->nsd->options, arg, arg2);
+       zopt = zone_list_add_or_cat(xfrd->nsd->options, arg, arg2,
+                       xfrd_add_catalog_producer_member);
        if(!zopt) {
                /* also dname parse error here */
                (void)ssl_printf(ssl, "error could not add zonelist entry\n");
@@ -1302,6 +1377,10 @@ perform_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
                getzonestatid(xfrd->nsd->options, zopt));
        zonestat_inc_ifneeded(xfrd);
        xfrd_set_reload_now(xfrd);
+       /* add to xfrd - catalog consumer zones */
+       if (zone_is_catalog_consumer(zopt)) {
+               xfrd_init_catalog_consumer_zone(xfrd, zopt);
+       }
        /* add to xfrd - notify (for master and slaves) */
        init_notify_send(xfrd->notify_zones, xfrd->region, zopt);
        /* add to xfrd - slave */
@@ -1353,7 +1432,8 @@ perform_addzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
        dname = NULL;
 
        /* add to zonelist and adds to config in memory */
-       zopt = zone_list_add(xfrd->nsd->options, arg, arg2);
+       zopt = zone_list_add_or_cat(xfrd->nsd->options, arg, arg2,
+                       xfrd_add_catalog_producer_member);
        if(!zopt) {
                /* also dname parse error here */
                (void)ssl_printf(ssl, "error could not add zonelist entry\n");
@@ -1365,6 +1445,10 @@ perform_addzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
                getzonestatid(xfrd->nsd->options, zopt));
        zonestat_inc_ifneeded(xfrd);
        xfrd_set_reload_now(xfrd);
+       /* add to xfrd - catalog consumer zones */
+       if (zone_is_catalog_consumer(zopt)) {
+               xfrd_init_catalog_consumer_zone(xfrd, zopt);
+       }
        /* add to xfrd - notify (for master and slaves) */
        init_notify_send(xfrd->notify_zones, xfrd->region, zopt);
        /* add to xfrd - slave */
@@ -1380,6 +1464,8 @@ perform_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
 {
        const dname_type* dname;
        struct zone_options* zopt;
+       /* dont recycle dname when it becomes part of a xfrd_producer_member */
+       int recycle_dname = 1;
 
        dname = dname_parse(xfrd->region, arg);
        if(!dname) {
@@ -1406,7 +1492,17 @@ perform_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
                        "nsd.conf yourself and repattern\n");
                return 0;
        }
+       if(zone_is_catalog_consumer_member(zopt)
+       && as_catalog_member_zone(zopt)->member_id) {
+               (void)ssl_printf(ssl, "Error: Zone is a catalog consumer "
+                 "member zone with id %s\nRemove the member id from the "
+                 "catalog to delete this zone.\n", dname_to_string(
+                 as_catalog_member_zone(zopt)->member_id, NULL));
+               region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
+               dname = NULL;
+               return 0;
 
+       }
        /* create deletion task */
        task_new_del_zone(xfrd->nsd->task[xfrd->nsd->mytask],
                xfrd->last_task, dname);
@@ -1416,10 +1512,18 @@ perform_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
                xfrd_del_slave_zone(xfrd, dname);
        }
        xfrd_del_notify(xfrd, dname);
+       /* delete it in xfrd's catalog consumers list */
+       if(zone_is_catalog_consumer(zopt)) {
+               xfrd_deinit_catalog_consumer_zone(xfrd, dname);
+       } else {
+               recycle_dname = !xfrd_del_catalog_producer_member(xfrd, dname);
+       }
        /* delete from config */
        zone_list_del(xfrd->nsd->options, zopt);
 
-       region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
+       if(recycle_dname)
+               region_recycle(xfrd->region,
+                               (void*)dname, dname_total_size(dname));
        return 1;
 }
 
@@ -1580,6 +1684,10 @@ remove_cfgzone(xfrd_state_type* xfrd, const char* pname)
                xfrd_del_slave_zone(xfrd, dname);
        }
        xfrd_del_notify(xfrd, dname);
+       /* delete it in xfrd's catalog consumers list */
+       if(zone_is_catalog_consumer(zopt)) {
+               xfrd_deinit_catalog_consumer_zone(xfrd, dname);
+       }
 
        /* delete from zoneoptions */
        zone_options_delete(xfrd->nsd->options, zopt);
@@ -1614,6 +1722,10 @@ add_cfgzone(xfrd_state_type* xfrd, const char* pname)
                getzonestatid(xfrd->nsd->options, zopt));
        /* zonestat_inc is done after the entire config file has been done */
        xfrd_set_reload_now(xfrd);
+       /* add to xfrd - catalog consumer zones */
+       if (zone_is_catalog_consumer(zopt)) {
+               xfrd_init_catalog_consumer_zone(xfrd, zopt);
+       }
        /* add to xfrd - notify (for master and slaves) */
        init_notify_send(xfrd->notify_zones, xfrd->region, zopt);
        /* add to xfrd - slave */
@@ -1762,12 +1874,19 @@ repat_patterns(xfrd_state_type* xfrd, struct nsd_options* newopt)
                        } else if (!p->request_xfr && origp->request_xfr) {
                                newstate = REPAT_MASTER;
                        }
+                       if (   p->catalog_role == CATALOG_ROLE_CONSUMER
+                       && origp->catalog_role != CATALOG_ROLE_CONSUMER) {
+                               newstate |= REPAT_CATALOG_CONSUMER;
+                       } else if (p->catalog_role != CATALOG_ROLE_CONSUMER
+                           && origp->catalog_role == CATALOG_ROLE_CONSUMER) {
+                               newstate |= REPAT_CATALOG_CONSUMER_DEINIT;
+                       }
                        add_pat(xfrd, p);
                        if (p->implicit && newstate) {
                                const dname_type* dname =
                                        parse_implicit_name(xfrd, p->pname);
                                if (dname) {
-                                       if (newstate == REPAT_SLAVE) {
+                                       if (newstate & REPAT_SLAVE) {
                                                struct zone_options* zopt =
                                                        zone_options_find(
                                                        oldopt, dname);
@@ -1775,10 +1894,22 @@ repat_patterns(xfrd_state_type* xfrd, struct nsd_options* newopt)
                                                        xfrd_init_slave_zone(
                                                                xfrd, zopt);
                                                }
-                                       } else if (newstate == REPAT_MASTER) {
+                                       } else if (newstate & REPAT_MASTER) {
                                                xfrd_del_slave_zone(xfrd,
                                                        dname);
                                        }
+                                       if (newstate & REPAT_CATALOG_CONSUMER) {
+                                               struct zone_options* zopt =
+                                                       zone_options_find(
+                                                       oldopt, dname);
+                                               if (zopt) {
+                                                       xfrd_init_catalog_consumer_zone(
+                                                               xfrd, zopt);
+                                               }
+                                       } else if (newstate & REPAT_CATALOG_CONSUMER_DEINIT) {
+                                               xfrd_deinit_catalog_consumer_zone(
+                                                               xfrd, dname);
+                                       }
                                        region_recycle(xfrd->region,
                                                (void*)dname,
                                                dname_total_size(dname));
@@ -1797,15 +1928,23 @@ repat_patterns(xfrd_state_type* xfrd, struct nsd_options* newopt)
                RBTREE_FOR(zone_opt, struct zone_options*, oldopt->zone_options) {
                        struct pattern_options* oldp = zone_opt->pattern;
                        if (!oldp->implicit) {
-                               if (oldp->xfrd_flags == REPAT_SLAVE) {
+                               if (oldp->xfrd_flags & REPAT_SLAVE) {
                                        /* xfrd needs stable reference so get
                                         * it from the oldopt(modified) tree */
                                        xfrd_init_slave_zone(xfrd, zone_opt);
-                               } else if (oldp->xfrd_flags == REPAT_MASTER) {
+                               } else if (oldp->xfrd_flags & REPAT_MASTER) {
                                        xfrd_del_slave_zone(xfrd,
                                                (const dname_type*)
                                                zone_opt->node.key);
                                }
+                               if (oldp->xfrd_flags & REPAT_CATALOG_CONSUMER) {
+                                       xfrd_init_catalog_consumer_zone(xfrd,
+                                                       zone_opt);
+                               } else if (oldp->xfrd_flags & REPAT_CATALOG_CONSUMER_DEINIT) {
+                                       xfrd_deinit_catalog_consumer_zone(xfrd,
+                                               (const dname_type*)
+                                               zone_opt->node.key);
+                               }
                                oldp->xfrd_flags = 0;
                        }
                }
@@ -1883,7 +2022,8 @@ do_repattern(RES* ssl, xfrd_state_type* xfrd)
 
        (void)ssl_printf(ssl, "reconfig start, read %s\n", cfgfile);
        opt = nsd_options_create(region);
-       if(!parse_options_file(opt, cfgfile, &print_ssl_cfg_err, &ssl)) {
+       if(!parse_options_file(opt, cfgfile, &print_ssl_cfg_err, &ssl,
+                               xfrd->nsd->options)) {
                /* error already printed */
                region_destroy(region);
                return;
@@ -2461,7 +2601,8 @@ remote_handshake_later(struct daemon_remote* rc, struct rc_state* s, int fd,
                        return;
                }
                s->shake_state = rc_hs_read;
-               event_del(&s->c);
+               if(s->event_added)
+                       event_del(&s->c);
                memset(&s->c, 0, sizeof(s->c));
                event_set(&s->c, fd, EV_PERSIST|EV_TIMEOUT|EV_READ,
                        remote_control_callback, s);
@@ -2469,6 +2610,7 @@ remote_handshake_later(struct daemon_remote* rc, struct rc_state* s, int fd,
                        log_msg(LOG_ERR, "remote_accept: cannot set event_base");
                if(event_add(&s->c, &s->tval) != 0)
                        log_msg(LOG_ERR, "remote_accept: cannot add event");
+               s->event_added = 1;
                return;
        } else if(r2 == SSL_ERROR_WANT_WRITE) {
                if(s->shake_state == rc_hs_write) {
@@ -2476,7 +2618,8 @@ remote_handshake_later(struct daemon_remote* rc, struct rc_state* s, int fd,
                        return;
                }
                s->shake_state = rc_hs_write;
-               event_del(&s->c);
+               if(s->event_added)
+                       event_del(&s->c);
                memset(&s->c, 0, sizeof(s->c));
                event_set(&s->c, fd, EV_PERSIST|EV_TIMEOUT|EV_WRITE,
                        remote_control_callback, s);
@@ -2484,6 +2627,7 @@ remote_handshake_later(struct daemon_remote* rc, struct rc_state* s, int fd,
                        log_msg(LOG_ERR, "remote_accept: cannot set event_base");
                if(event_add(&s->c, &s->tval) != 0)
                        log_msg(LOG_ERR, "remote_accept: cannot add event");
+               s->event_added = 1;
                return;
        } else {
                if(r == 0)
@@ -2799,6 +2943,11 @@ print_stats(RES* ssl, xfrd_state_type* xfrd, struct timeval* now, int clear,
        print_stat_block(ssl, "", "", st);
 
        /* zone statistics */
+       if(!ssl_printf(ssl, "zone.primary=%lu\n",
+               (unsigned long)(xfrd->notify_zones->count - xfrd->zones->count)))
+               return;
+       if(!ssl_printf(ssl, "zone.secondary=%lu\n", (unsigned long)xfrd->zones->count))
+               return;
        if(!ssl_printf(ssl, "zone.master=%lu\n",
                (unsigned long)(xfrd->notify_zones->count - xfrd->zones->count)))
                return;
@@ -2927,7 +3076,9 @@ process_stats(RES* ssl, xfrd_state_type* xfrd, int peek)
        process_stats_manage_clear(xfrd, stats, peek);
        process_stats_add_total(xfrd, &total, stats);
        print_stats(ssl, xfrd, &stattime, !peek, &total, zonestats);
-       xfrd->nsd->rc->stats_time = stattime;
+       if(!peek) {
+               xfrd->nsd->rc->stats_time = stattime;
+       }
 
        free(stats);
 #ifdef USE_ZONE_STATS
index 3a022b7..4d1ba85 100644 (file)
@@ -1735,6 +1735,9 @@ server_start_xfrd(struct nsd *nsd, int del_db, int reload_active)
 #ifdef HAVE_SETPROCTITLE
                setproctitle("xfrd");
 #endif
+#ifdef USE_LOG_PROCESS_ROLE
+               log_set_process_role("xfrd");
+#endif
 #ifdef HAVE_CPUSET_T
                if(nsd->use_cpu_affinity) {
                        set_cpu_affinity(nsd->xfrd_cpuset);
@@ -1751,6 +1754,12 @@ server_start_xfrd(struct nsd *nsd, int del_db, int reload_active)
                        log_msg(LOG_ERR, "cannot fcntl pipe: %s", strerror(errno));
                }
                nsd->xfrd_listener->fd = sockets[0];
+#ifdef HAVE_SETPROCTITLE
+               setproctitle("main");
+#endif
+#ifdef USE_LOG_PROCESS_ROLE
+               log_set_process_role("main");
+#endif
                break;
        }
        /* server-parent only */
@@ -2308,6 +2317,78 @@ reload_process_tasks(struct nsd* nsd, udb_ptr* last_task, int cmdsocket)
 
 void server_verify(struct nsd *nsd, int cmdsocket);
 
+struct quit_sync_event_data {
+       struct event_base* base;
+       size_t read;
+       union {
+               uint8_t buf[sizeof(sig_atomic_t)];
+               sig_atomic_t cmd;
+       } to_read;
+};
+
+static void server_reload_handle_sigchld(int sig, short event,
+               void* ATTR_UNUSED(arg))
+{
+       assert(sig == SIGCHLD);
+       assert(event & EV_SIGNAL);
+
+       /* reap the exited old-serve child(s) */
+       while(waitpid(-1, NULL, WNOHANG) > 0) {
+               /* pass */
+       }
+}
+
+static void server_reload_handle_quit_sync_ack(int cmdsocket, short event,
+               void* arg)
+{
+       struct quit_sync_event_data* cb_data =
+               (struct quit_sync_event_data*)arg;
+       ssize_t r;
+
+       if(event & EV_TIMEOUT) {
+               sig_atomic_t cmd = NSD_QUIT_SYNC;
+
+               DEBUG(DEBUG_IPC,1, (LOG_INFO, "reload: ipc send quit to main"));
+               if (!write_socket(cmdsocket, &cmd, sizeof(cmd))) {
+                       log_msg(LOG_ERR, "problems sending command from "
+                               "reload to old-main: %s", strerror(errno));
+               }
+               /* Wait for cmdsocket to become readable or for next timeout,
+                * (this works because event is added EV_TIMEOUT|EV_PERSIST).
+                */
+               return;
+       }
+       assert(event & EV_READ);
+       assert(cb_data->read < sizeof(cb_data->to_read.cmd));
+
+       r = read(cmdsocket, cb_data->to_read.buf + cb_data->read,
+                       sizeof(cb_data->to_read.cmd) - cb_data->read);
+       if(r == 0) {
+               log_msg(LOG_ERR, "reload: old-main quit during quit sync");
+               cb_data->to_read.cmd = NSD_RELOAD;
+
+       } else if(r == -1) {
+               if(errno == EAGAIN || errno == EINTR)
+                       return;
+
+               log_msg(LOG_ERR, "reload: could not wait for parent to quit: "
+                       "%s", strerror(errno));
+               cb_data->to_read.cmd = NSD_RELOAD;
+
+       } else if (cb_data->read + r  < sizeof(cb_data->to_read.cmd)) {
+               /* More to read */
+               cb_data->read += r;
+               return;
+
+       } else {
+               assert(cb_data->read + r == sizeof(cb_data->to_read.cmd));
+               DEBUG(DEBUG_IPC,1, (LOG_INFO, "reload: ipc reply main %d",
+                                       (int)cb_data->to_read.cmd));
+       }
+       /* Done */
+       event_base_loopexit(cb_data->base, NULL);
+}
+
 /*
  * Reload the database, stop parent, re-fork children and continue.
  * as server_main.
@@ -2317,21 +2398,21 @@ server_reload(struct nsd *nsd, region_type* server_region, netio_type* netio,
        int cmdsocket)
 {
        pid_t mypid;
-       sig_atomic_t cmd = NSD_QUIT_SYNC;
-       int ret;
+       sig_atomic_t cmd;
        udb_ptr last_task;
        struct sigaction old_sigchld, ign_sigchld;
        struct radnode* node;
        zone_type* zone;
        enum soainfo_hint hint;
+       struct quit_sync_event_data cb_data;
+       struct event signal_event, cmd_event;
+       struct timeval reload_sync_timeout;
+
        /* ignore SIGCHLD from the previous server_main that used this pid */
        memset(&ign_sigchld, 0, sizeof(ign_sigchld));
        ign_sigchld.sa_handler = SIG_IGN;
        sigaction(SIGCHLD, &ign_sigchld, &old_sigchld);
 
-#ifdef HAVE_SETPROCTITLE
-       setproctitle("main");
-#endif
 #ifdef HAVE_CPUSET_T
        if(nsd->use_cpu_affinity) {
                set_cpu_affinity(nsd->cpuset);
@@ -2430,7 +2511,7 @@ server_reload(struct nsd *nsd, region_type* server_region, netio_type* netio,
                exit(1);
        }
 
-       /* if the parent has quit, we must quit too, poll the fd for cmds */
+       /* if the old-main has quit, we must quit too, poll the fd for cmds */
        if(block_read(nsd, cmdsocket, &cmd, sizeof(cmd), 0) == sizeof(cmd)) {
                DEBUG(DEBUG_IPC,1, (LOG_INFO, "reload: ipc command from main %d", (int)cmd));
                if(cmd == NSD_QUIT) {
@@ -2440,34 +2521,55 @@ server_reload(struct nsd *nsd, region_type* server_region, netio_type* netio,
                }
        }
 
-       /* Send quit command to parent: blocking, wait for receipt. */
-       do {
-               DEBUG(DEBUG_IPC,1, (LOG_INFO, "reload: ipc send quit to main"));
-               cmd = NSD_QUIT_SYNC;
-               if (!write_socket(cmdsocket, &cmd, sizeof(cmd)))
-               {
-                       log_msg(LOG_ERR, "problems sending command from reload to oldnsd: %s",
-                               strerror(errno));
-               }
-               /* blocking: wait for parent to really quit. (it sends RELOAD as ack) */
-               DEBUG(DEBUG_IPC,1, (LOG_INFO, "reload: ipc wait for ack main"));
-               ret = block_read(nsd, cmdsocket, &cmd, sizeof(cmd),
-                       RELOAD_SYNC_TIMEOUT);
-               if(ret == -2) {
-                       DEBUG(DEBUG_IPC, 1, (LOG_ERR, "reload timeout QUITSYNC. retry"));
-               }
-       } while (ret == -2);
-       if(ret == -1) {
-               log_msg(LOG_ERR, "reload: could not wait for parent to quit: %s",
+       /* Send quit command to old-main: blocking, wait for receipt.
+        * The old-main process asks the old-serve processes to quit, however
+        * if a reload succeeded before, this process is the parent of the
+        * old-serve processes, so we need to reap the children for it.
+        */
+       DEBUG(DEBUG_IPC,1, (LOG_INFO, "reload: ipc send quit to main"));
+       cmd = NSD_QUIT_SYNC;
+       if (!write_socket(cmdsocket, &cmd, sizeof(cmd)))
+       {
+               log_msg(LOG_ERR, "problems sending command from reload to oldnsd: %s",
                        strerror(errno));
        }
-       DEBUG(DEBUG_IPC,1, (LOG_INFO, "reload: ipc reply main %d %d", ret, (int)cmd));
+
+       reload_sync_timeout.tv_sec = RELOAD_SYNC_TIMEOUT;
+       reload_sync_timeout.tv_usec = 0;
+
+       cb_data.base = nsd_child_event_base();
+       cb_data.to_read.cmd = cmd;
+       cb_data.read = 0;
+
+       event_set(&signal_event, SIGCHLD, EV_SIGNAL|EV_PERSIST,
+           server_reload_handle_sigchld, NULL);
+       if(event_base_set(cb_data.base, &signal_event) != 0
+       || event_add(&signal_event, NULL) != 0) {
+               log_msg(LOG_ERR, "NSD quit sync: could not add signal event");
+       }
+
+       event_set(&cmd_event, cmdsocket, EV_READ|EV_TIMEOUT|EV_PERSIST,
+           server_reload_handle_quit_sync_ack, &cb_data);
+       if(event_base_set(cb_data.base, &cmd_event) != 0
+       || event_add(&cmd_event, &reload_sync_timeout) != 0) {
+               log_msg(LOG_ERR, "NSD quit sync: could not add command event");
+       }
+
+       /* short-lived main loop */
+       event_base_dispatch(cb_data.base);
+
+       /* remove command and signal event handlers */
+       event_del(&cmd_event);
+       event_del(&signal_event);
+       event_base_free(cb_data.base);
+       cmd = cb_data.to_read.cmd;
+
        if(cmd == NSD_QUIT) {
                /* small race condition possible here, parent got quit cmd. */
                send_children_quit(nsd);
                exit(1);
        }
-       assert(ret==-1 || ret == 0 || cmd == NSD_RELOAD);
+       assert(cmd == NSD_RELOAD);
        udb_ptr_unlink(&last_task, nsd->task[nsd->mytask]);
        task_process_sync(nsd->task[nsd->mytask]);
 #ifdef USE_ZONE_STATS
@@ -2602,6 +2704,12 @@ server_main(struct nsd *nsd)
                                        log_msg(LOG_WARNING,
                                               "Reload process %d failed with status %d, continuing with old database",
                                               (int) child_pid, status);
+#ifdef HAVE_SETPROCTITLE
+                                       setproctitle("main");
+#endif
+#ifdef USE_LOG_PROCESS_ROLE
+                                       log_set_process_role("main");
+#endif
                                        reload_pid = -1;
                                        if(reload_listener.fd != -1) close(reload_listener.fd);
                                        netio_remove_handler(netio, &reload_listener);
@@ -2679,7 +2787,7 @@ server_main(struct nsd *nsd)
                                break;
 
                        /* timeout to collect processes. In case no sigchild happens. */
-                       timeout_spec.tv_sec = 60;
+                       timeout_spec.tv_sec = 1;
                        timeout_spec.tv_nsec = 0;
 
                        /* listen on ports, timeout for collecting terminated children */
@@ -2700,6 +2808,12 @@ server_main(struct nsd *nsd)
                                log_msg(LOG_WARNING,
                                       "Reload process %d failed, continuing with old database",
                                       (int) reload_pid);
+#ifdef HAVE_SETPROCTITLE
+                               setproctitle("main");
+#endif
+#ifdef USE_LOG_PROCESS_ROLE
+                               log_set_process_role("main");
+#endif
                                reload_pid = -1;
                                if(reload_listener.fd != -1) close(reload_listener.fd);
                                netio_remove_handler(netio, &reload_listener);
@@ -2760,9 +2874,21 @@ server_main(struct nsd *nsd)
                        default:
                                /* PARENT */
                                close(reload_sockets[0]);
+#ifdef HAVE_SETPROCTITLE
+                               setproctitle("load");
+#endif
+#ifdef USE_LOG_PROCESS_ROLE
+                               log_set_process_role("load");
+#endif
                                server_reload(nsd, server_region, netio,
                                        reload_sockets[1]);
                                DEBUG(DEBUG_IPC,2, (LOG_INFO, "Reload exited to become new main"));
+#ifdef HAVE_SETPROCTITLE
+                               setproctitle("main");
+#endif
+#ifdef USE_LOG_PROCESS_ROLE
+                               log_set_process_role("main");
+#endif
                                close(reload_sockets[1]);
                                DEBUG(DEBUG_IPC,2, (LOG_INFO, "Reload closed"));
                                /* drop stale xfrd ipc data */
@@ -2779,6 +2905,12 @@ server_main(struct nsd *nsd)
                                /* server_main keep running until NSD_QUIT_SYNC
                                 * received from reload. */
                                close(reload_sockets[1]);
+#ifdef HAVE_SETPROCTITLE
+                               setproctitle("old-main");
+#endif
+#ifdef USE_LOG_PROCESS_ROLE
+                               log_set_process_role("old-main");
+#endif
                                reload_listener.fd = reload_sockets[0];
                                reload_listener.timeout = NULL;
                                reload_listener.user_data = nsd;
@@ -3213,6 +3345,9 @@ server_child(struct nsd *nsd)
        region_type *server_region = region_create(xalloc, free);
        struct event_base* event_base = nsd_child_event_base();
        sig_atomic_t mode;
+#ifdef USE_LOG_PROCESS_ROLE
+       static char child_name[20];
+#endif
 
        if(!event_base) {
                log_msg(LOG_ERR, "nsd server could not create event base");
@@ -3226,11 +3361,17 @@ server_child(struct nsd *nsd)
 #endif
 
        assert(nsd->server_kind != NSD_SERVER_MAIN);
-       DEBUG(DEBUG_IPC, 2, (LOG_INFO, "child process started"));
 
 #ifdef HAVE_SETPROCTITLE
        setproctitle("server %d", nsd->this_child->child_num + 1);
 #endif
+#ifdef USE_LOG_PROCESS_ROLE
+       snprintf(child_name, sizeof(child_name), "srv%d",
+               nsd->this_child->child_num + 1);
+       log_set_process_role(child_name);
+#endif
+       DEBUG(DEBUG_IPC, 2, (LOG_INFO, "child process started"));
+
 #ifdef HAVE_CPUSET_T
        if(nsd->use_cpu_affinity) {
                set_cpu_affinity(nsd->this_child->cpuset);
index 7afe34e..13a2ec9 100644 (file)
@@ -63,6 +63,14 @@ static log_function_type *current_log_function = log_file;
 static FILE *current_log_file = NULL;
 int log_time_asc = 1;
 
+#ifdef USE_LOG_PROCESS_ROLE
+void
+log_set_process_role(const char *process_role)
+{
+       global_ident = process_role;
+}
+#endif
+
 void
 log_init(const char *ident)
 {
index 97b7ba0..ab6315e 100644 (file)
@@ -43,6 +43,15 @@ struct nsd;
  */
 void log_init(const char *ident);
 
+#ifdef USE_LOG_PROCESS_ROLE
+/*
+ * Set the name of the role for the process (for debugging purposes)
+ */
+void log_set_process_role(const char* role);
+#else
+#define log_set_process_role(role) /* empty */
+#endif
+
 /*
  * Open the system log.  If FILENAME is not NULL, a log file is opened
  * as well.
diff --git a/usr.sbin/nsd/xfrd-catalog-zones.c b/usr.sbin/nsd/xfrd-catalog-zones.c
new file mode 100644 (file)
index 0000000..bc0baa0
--- /dev/null
@@ -0,0 +1,1285 @@
+/*
+ * xfrd-catalog-zones.c -- catalog zone implementation for NSD
+ *
+ * Copyright (c) 2024, NLnet Labs. All rights reserved.
+ *
+ * See LICENSE for the license.
+ */
+#include "config.h"
+#include "difffile.h"
+#include "nsd.h"
+#include "packet.h"
+#include "xfrd-catalog-zones.h"
+#include "xfrd-notify.h"
+
+
+/******************                                        ******************
+ ******************    catalog consumer zone processing    ******************
+ ******************                                        ******************/
+
+/** process a catalog consumer zone, load if needed */
+static void xfrd_process_catalog_consumer_zone(
+               struct xfrd_catalog_consumer_zone* consumer_zone);
+
+/** make the catalog consumer zone invalid for given reason */
+static void vmake_catalog_consumer_invalid(
+       struct xfrd_catalog_consumer_zone *consumer_zone,
+       const char *format, va_list args);
+
+/** return (static) dname with label prepended to dname */
+static dname_type* label_plus_dname(const char* label,const dname_type* dname);
+
+/** delete the catalog member zone */
+static void catalog_del_consumer_member_zone(
+               struct xfrd_catalog_consumer_zone* consumer_zone,
+               struct catalog_member_zone* consumer_member_zone);
+
+#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
+/* return a single catalog consumer zone from xfrd struct */
+static inline struct xfrd_catalog_consumer_zone*
+xfrd_one_catalog_consumer_zone()
+{
+       return xfrd
+           && xfrd->catalog_consumer_zones
+           && xfrd->catalog_consumer_zones->count == 1
+            ? (struct xfrd_catalog_consumer_zone*)
+              rbtree_first(xfrd->catalog_consumer_zones) : NULL;
+}
+#endif
+
+/** return the catalog-member-pattern or NULL on error if not present */
+static inline struct pattern_options*
+catalog_member_pattern(struct xfrd_catalog_consumer_zone* consumer_zone)
+{
+       if (!consumer_zone->options->pattern
+       ||  !consumer_zone->options->pattern->catalog_member_pattern)
+               return NULL;
+       return pattern_options_find(xfrd->nsd->options,
+               consumer_zone->options->pattern->catalog_member_pattern);
+}
+
+/** see if we have more zonestatistics entries and it has to be incremented */
+static inline void
+zonestat_inc_ifneeded()
+{
+#ifdef USE_ZONE_STATS
+        if(xfrd->nsd->options->zonestatnames->count != xfrd->zonestat_safe)
+                task_new_zonestat_inc(xfrd->nsd->task[xfrd->nsd->mytask],
+                        xfrd->last_task,
+                        xfrd->nsd->options->zonestatnames->count);
+#endif /* USE_ZONE_STATS */
+}
+
+
+/******************                                        ******************
+ ******************    catalog producer zone processing    ******************
+ ******************                                        ******************/
+
+/** process catalog producer zone producer_zone */
+static void xfrd_process_catalog_producer_zone(
+               struct xfrd_catalog_producer_zone* producer_zone);
+
+/** rbnode must be struct catalog_member_zone*; compares (key->member_id) */
+static int member_id_compare(const void *left, const void *right);
+
+/** return xfrd_catalog_producer_zone* pointed to by cmz' catalog-producer-zone
+ * pattern option. struct is created if necessary. returns NULL on failure. */
+static struct xfrd_catalog_producer_zone* xfrd_get_catalog_producer_zone(
+               struct catalog_member_zone* cmz);
+
+/** helper struct for generating XFR files, for conveying the catalog producer
+ *  zone content to the server process.
+ */
+struct xfrd_xfr_writer {
+       struct xfrd_catalog_producer_zone* producer_zone;
+       char packet_space[16384];
+       buffer_type packet;
+       uint32_t seq_nr; /* number of messages already handled */
+       uint32_t old_serial, new_serial; /* host byte order */
+       uint64_t xfrfilenumber; /* identifier for file to store xfr into */
+};
+
+/** initialize xfrd_xfr_writer struct xw */
+static void xfr_writer_init(struct xfrd_xfr_writer* xw,
+               struct xfrd_catalog_producer_zone* producer_zone);
+
+/** write packet from xfrd_xfr_writer struct xw to xfr file */
+static void xfr_writer_write_packet(struct xfrd_xfr_writer* xw);
+
+/** commit xfr file (send to server process), with provided log message */
+static void xfr_writer_commit(struct xfrd_xfr_writer* xw, const char *fmt,
+               ...);
+
+/** try writing SOA RR with serial to packet buffer. returns 0 on failure */
+static int try_buffer_write_SOA(buffer_type* packet, const dname_type* owner,
+               uint32_t serial);
+
+/** try writing RR to packet buffer. returns 0 on failure */
+static int try_buffer_write_RR(buffer_type* packet, const dname_type* owner,
+               uint16_t rr_type, uint16_t rdata_len, const void* rdata);
+
+/** try writing PTR RR to packet buffer. returns 0 on failure */
+static inline int try_buffer_write_PTR(buffer_type* packet,
+               const dname_type* owner, const dname_type* name);
+
+/** try writing TXT RR to packet buffer. returns 0 on failure */
+static int try_buffer_write_TXT(buffer_type* packet, const dname_type* name,
+               const char *txt);
+
+/** add SOA RR with serial serial to xfrd_xfr_writer xw */
+static inline void xfr_writer_add_SOA(struct xfrd_xfr_writer* xw,
+               const dname_type* owner, uint32_t serial)
+{
+       if(try_buffer_write_SOA(&xw->packet, owner, serial))
+               return;
+       xfr_writer_write_packet(xw);
+       assert(buffer_position(&xw->packet) == 12);
+       try_buffer_write_SOA(&xw->packet, owner, serial);
+}
+
+/** add RR to xfrd_xfr_writer xw */
+static inline void xfr_writer_add_RR(struct xfrd_xfr_writer* xw,
+               const dname_type* owner,
+               uint16_t rr_type, uint16_t rdata_len, const void* rdata)
+{
+       if(try_buffer_write_RR(&xw->packet, owner, rr_type, rdata_len, rdata))
+               return;
+       xfr_writer_write_packet(xw);
+       assert(buffer_position(&xw->packet) == 12);
+       try_buffer_write_RR(&xw->packet, owner, rr_type, rdata_len, rdata);
+}
+
+/** add PTR RR to xfrd_xfr_writer xw */
+static inline void xfr_writer_add_PTR(struct xfrd_xfr_writer* xw,
+               const dname_type* owner, const dname_type* name)
+{
+       if(try_buffer_write_PTR(&xw->packet, owner, name))
+               return;
+       xfr_writer_write_packet(xw);
+       assert(buffer_position(&xw->packet) == 12);
+       try_buffer_write_PTR(&xw->packet, owner, name);
+}
+
+/** add TXT RR to xfrd_xfr_writer xw */
+static inline void xfr_writer_add_TXT(struct xfrd_xfr_writer* xw,
+               const dname_type* owner, const char* txt)
+{
+       if(try_buffer_write_TXT(&xw->packet, owner, txt))
+               return;
+       xfr_writer_write_packet(xw);
+       assert(buffer_position(&xw->packet) == 12);
+       try_buffer_write_TXT(&xw->packet, owner, txt);
+}
+
+
+/******************                                        ******************
+ ******************    catalog consumer zone processing    ******************
+ ******************                                        ******************/
+
+void
+xfrd_init_catalog_consumer_zone(xfrd_state_type* xfrd,
+               struct zone_options* zone)
+{
+       struct xfrd_catalog_consumer_zone* consumer_zone;
+
+       if ((consumer_zone = (struct xfrd_catalog_consumer_zone*)rbtree_search(
+                       xfrd->catalog_consumer_zones, zone->node.key))) {
+               log_msg(LOG_ERR, "cannot initialize new catalog consumer zone:"
+                               " '%s: it already exists in xfrd's catalog "
+                               " consumer zones index", zone->name);
+               /* Maybe we need to reprocess it? */
+               make_catalog_consumer_valid(consumer_zone);
+               return;
+       }
+       consumer_zone = (struct xfrd_catalog_consumer_zone*)
+               region_alloc(xfrd->region,
+                       sizeof(struct xfrd_catalog_consumer_zone));
+        memset(consumer_zone, 0, sizeof(struct xfrd_catalog_consumer_zone));
+        consumer_zone->node.key = zone->node.key;
+        consumer_zone->options = zone;
+       consumer_zone->member_ids.region = xfrd->region;
+       consumer_zone->member_ids.root = RBTREE_NULL;
+       consumer_zone->member_ids.count = 0;
+       consumer_zone->member_ids.cmp = member_id_compare;
+       consumer_zone->mtime.tv_sec = 0;
+       consumer_zone->mtime.tv_nsec = 0;
+
+       consumer_zone->invalid = NULL;
+       rbtree_insert(xfrd->catalog_consumer_zones,
+                       (rbnode_type*)consumer_zone);
+#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
+       if ((int)xfrd->catalog_consumer_zones->count > 1) {
+               log_msg(LOG_ERR, "catalog consumer processing disabled: "
+                       "only one single catalog consumer zone allowed");
+       }
+#endif
+       if(zone->pattern && zone->pattern->store_ixfr) {
+               /* Don't process ixfrs from xfrd */
+               zone->pattern->store_ixfr = 0;
+       }
+}
+
+void
+xfrd_deinit_catalog_consumer_zone(xfrd_state_type* xfrd,
+               const dname_type* dname)
+{
+       struct xfrd_catalog_consumer_zone* consumer_zone;
+       zone_type* zone;
+
+       if (!(consumer_zone =(struct xfrd_catalog_consumer_zone*)rbtree_delete(
+                       xfrd->catalog_consumer_zones, dname))) {
+               log_msg(LOG_ERR, "cannot de-initialize catalog consumer zone:"
+                               " '%s: it did not exist in xfrd's catalog "
+                               " consumer zones index",
+                               dname_to_string(dname, NULL));
+               return;
+       }
+       if (consumer_zone->member_ids.count)
+               log_msg(LOG_WARNING, "de-initialize catalog consumer zone:"
+                               " '%s: will cause all member zones to be "
+                               " deleted", consumer_zone->options->name);
+
+       while (consumer_zone->member_ids.count) {
+               struct catalog_member_zone* cmz = (struct catalog_member_zone*)
+                       rbtree_first(&consumer_zone->member_ids)->key;
+
+               log_msg(LOG_INFO, "deleting member zone '%s' on "
+                       "de-initializing catalog consumer zone '%s'",
+                       cmz->options.name, consumer_zone->options->name);
+               catalog_del_consumer_member_zone(consumer_zone, cmz);
+       }
+       if ((zone = namedb_find_zone(xfrd->nsd->db, dname))) {
+               namedb_zone_delete(xfrd->nsd->db, zone);
+       }
+       region_recycle(xfrd->region, consumer_zone, sizeof(*consumer_zone));
+#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
+       if((consumer_zone = xfrd_one_catalog_consumer_zone())
+       &&  consumer_zone->options && consumer_zone->options->node.key) {
+               xfrd_zone_type* zone = (xfrd_zone_type*)rbtree_search(
+                       xfrd->zones,
+                       (const dname_type*)consumer_zone->options->node.key);
+
+               if(zone) {
+                       zone->soa_disk_acquired = 0;
+                       zone->soa_nsd_acquired = 0;
+                       xfrd_handle_notify_and_start_xfr(zone, NULL);
+               }
+       }
+#endif
+}
+
+/** make the catalog consumer zone invalid for given reason */
+static void
+vmake_catalog_consumer_invalid(
+               struct xfrd_catalog_consumer_zone *consumer_zone,
+               const char *format, va_list args)
+{
+       char message[MAXSYSLOGMSGLEN];
+       if (!consumer_zone || consumer_zone->invalid) return;
+        vsnprintf(message, sizeof(message), format, args);
+       log_msg(LOG_ERR, "invalid catalog consumer zone '%s': %s",
+               consumer_zone->options->name, message);
+       consumer_zone->invalid = region_strdup(xfrd->region, message);
+}
+
+void
+make_catalog_consumer_invalid(struct xfrd_catalog_consumer_zone *consumer_zone,
+               const char *format, ...)
+{
+       va_list args;
+       if (!consumer_zone || consumer_zone->invalid) return;
+       va_start(args, format);
+       vmake_catalog_consumer_invalid(consumer_zone, format, args);
+       va_end(args);
+}
+
+void
+make_catalog_consumer_valid(struct xfrd_catalog_consumer_zone *consumer_zone)
+{
+       if (consumer_zone->invalid) {
+               region_recycle(xfrd->region, consumer_zone->invalid,
+                               strlen(consumer_zone->invalid) + 1);
+               consumer_zone->invalid = NULL;
+       }
+}
+
+static dname_type*
+label_plus_dname(const char* label, const dname_type* dname)
+{
+       static struct {
+               dname_type dname;
+               uint8_t bytes[MAXDOMAINLEN + 128 /* max number of labels */];
+       } ATTR_PACKED name;
+       size_t i, ll;
+
+       if (!label || !dname || dname->label_count > 127)
+               return NULL;
+       ll = strlen(label);
+       if ((int)dname->name_size + ll + 1 > MAXDOMAINLEN)
+               return NULL;
+
+       /* In reversed order and first copy with memmove, so we can nest.
+        * i.e. label_plus_dname(label1, label_plus_dname(label2, dname))
+        */
+       memmove(name.bytes + dname->label_count
+                       + 1 /* label_count increases by one */
+                       + 1 /* label type/length byte for label */ + ll,
+               ((void*)dname) + sizeof(dname_type) + dname->label_count,
+               dname->name_size);
+       memcpy(name.bytes + dname->label_count
+                       + 1 /* label_count increases by one */
+                       + 1 /* label type/length byte for label */, label, ll);
+       name.bytes[dname->label_count + 1] = ll; /* label type/length byte */
+       name.bytes[dname->label_count] = 0; /* first label follows last
+                                            * label_offsets element */
+       for (i = 0; i < dname->label_count; i++)
+               name.bytes[i] = ((uint8_t*)(void*)dname)[sizeof(dname_type)+i]
+                       + 1 /* label type/length byte for label */ + ll;
+       name.dname.label_count = dname->label_count + 1 /* label_count incr. */;
+       name.dname.name_size   = dname->name_size   + ll
+                                                   + 1 /* label length */;
+       return &name.dname;
+}
+
+static void
+catalog_del_consumer_member_zone(
+               struct xfrd_catalog_consumer_zone* consumer_zone,
+               struct catalog_member_zone* consumer_member_zone)
+{
+       const dname_type* dname = consumer_member_zone->options.node.key;
+
+       /* create deletion task */
+       task_new_del_zone(xfrd->nsd->task[xfrd->nsd->mytask],
+                       xfrd->last_task, dname);
+       xfrd_set_reload_now(xfrd);
+       /* delete it in xfrd */
+       if(zone_is_slave(&consumer_member_zone->options)) {
+               xfrd_del_slave_zone(xfrd, dname);
+       }
+       xfrd_del_notify(xfrd, dname);
+#ifdef MULTIPLE_CATALOG_CONSUMER_ZONES
+       /* delete it in xfrd's catalog consumers list */
+       if(zone_is_catalog_consumer(&consumer_member_zone->options)) {
+               xfrd_deinit_catalog_consumer_zone(xfrd, dname);
+       }
+#endif
+       if(consumer_member_zone->member_id) {
+               rbtree_delete(&consumer_zone->member_ids,consumer_member_zone);
+               consumer_member_zone->node = *RBTREE_NULL;
+               region_recycle( xfrd->nsd->options->region,
+                       (void*)consumer_member_zone->member_id,
+                       dname_total_size(consumer_member_zone->member_id));
+               consumer_member_zone->member_id = NULL;
+       }
+       zone_options_delete(xfrd->nsd->options,&consumer_member_zone->options);
+}
+
+void xfrd_check_catalog_consumer_zonefiles(const dname_type* name)
+{
+       struct xfrd_catalog_consumer_zone* consumer_zone;
+
+#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
+       consumer_zone = xfrd_one_catalog_consumer_zone();
+       if (!consumer_zone)
+               return;
+       if (name && dname_compare(name, consumer_zone->node.key) != 0)
+               return;
+       name = consumer_zone->node.key;
+       DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Mark %s "
+               "for checking", consumer_zone->options->name));
+       make_catalog_consumer_valid(consumer_zone);
+       namedb_read_zonefile(xfrd->nsd, namedb_find_or_create_zone(
+               xfrd->nsd->db, name, consumer_zone->options), NULL, NULL);
+#else
+       if (!name) {
+               RBTREE_FOR(consumer_zone, struct xfrd_catalog_consumer_zone*,
+                               xfrd->catalog_consumer_zones) {
+                       make_catalog_consumer_valid(consumer_zone);
+                       namedb_read_zonefile(xfrd->nsd,
+                               namedb_find_or_create_zone(xfrd->nsd->db,
+                                       consumer_zone->options->node.key,
+                                       consumer_zone->options),
+                               NULL, NULL);
+               }
+       } else if ((consumer_zone = (struct xfrd_catalog_consumer_zone*)
+                       rbtree_search(xfrd->catalog_consumer_zones, name))) {
+               make_catalog_consumer_valid(consumer_zone);
+               namedb_read_zonefile(xfrd->nsd,
+                       namedb_find_or_create_zone(
+                               xfrd->nsd->db, name, consumer_zone->options),
+                       NULL, NULL);
+       }
+#endif
+}
+
+const char *invalid_catalog_consumer_zone(struct zone_options* zone)
+{
+       struct xfrd_catalog_consumer_zone* consumer_zone;
+       const char *msg;
+
+       if (!zone || !zone_is_catalog_consumer(zone))
+               msg = NULL;
+
+       else if (!xfrd) 
+               msg = "asked for catalog information outside of xfrd process";
+
+       else if (!xfrd->catalog_consumer_zones)
+               msg = "zone not found: "
+                     "xfrd's catalog consumer zones index is empty";
+
+#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
+       else if (xfrd->catalog_consumer_zones->count > 1)
+               return "not processing: more than one catalog consumer zone "
+                      "configured and only a single one allowed";
+#endif
+       else if (!(consumer_zone = (struct xfrd_catalog_consumer_zone*)
+                rbtree_search(xfrd->catalog_consumer_zones, zone->node.key)))
+               msg = "zone not found in xfrd's catalog consumer zones index";
+       else
+               return consumer_zone->invalid;
+
+       if (msg)
+               log_msg(LOG_ERR, "catalog consumer zone '%s': %s",
+                               zone->name, msg);
+
+       return msg;
+}
+
+void xfrd_process_catalog_consumer_zones()
+{
+       struct xfrd_catalog_consumer_zone* consumer_zone;
+
+#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
+       if((consumer_zone = xfrd_one_catalog_consumer_zone()))
+               xfrd_process_catalog_consumer_zone(consumer_zone);
+#else
+       RBTREE_FOR(consumer_zone, struct xfrd_catalog_consumer_zone*,
+                       xfrd->catalog_consumer_zones) {
+               xfrd_process_catalog_consumer_zone(consumer_zone);
+       }
+#endif
+}
+
+static inline struct catalog_member_zone* cursor_cmz(rbnode_type* node)
+{ return node != RBTREE_NULL ? (struct catalog_member_zone*)node->key : NULL; }
+static inline const dname_type* cursor_member_id(rbnode_type* node)
+{ return cursor_cmz(node) ? cursor_cmz(node)->member_id : NULL; }
+
+#if !defined(NDEBUG) && 1 /* Only disable for seriously slow debugging */
+static void debug_log_consumer_members(
+               struct xfrd_catalog_consumer_zone* consumer_zone)
+{
+       rbnode_type* cursor;
+       size_t i;
+
+       for ( cursor = rbtree_first(&consumer_zone->member_ids), i = 0
+           ; cursor != RBTREE_NULL; i++, cursor = rbtree_next(cursor)) {
+               DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Catalog member %.2zu: %s = %s",
+                     i, dname_to_string(cursor_member_id(cursor), NULL),
+                     cursor_cmz(cursor)->options.name));
+       }
+}
+#else
+# define debug_log_consumer_members(x) /* nothing */
+#endif
+
+static void
+xfrd_process_catalog_consumer_zone(
+               struct xfrd_catalog_consumer_zone* consumer_zone)
+{
+       zone_type* zone;
+       const dname_type* dname;
+       domain_type *match, *closest_encloser, *member_id, *group;
+       rrset_type *rrset;
+       size_t i;
+       uint8_t version_2_found;
+       /* Currect catalog member zone */
+       rbnode_type* cursor;
+       struct pattern_options *default_pattern = NULL;
+       /* A transfer of a catalog zone can contain deletion and adding of
+        * the same member zone. In such cases it can occur that the member
+        * is tried to be added before it is deleted. For these exceptional
+        * cases, we will rewalk the zone after the first pass, to retry
+        * adding those zones.
+        *
+        * Initial pass is mode "try_to_add".
+        * If a zone cannot be added, mode is set to "retry_to_add"
+        * If after the first pass the mode is "retry_to_add",
+        *    mode will be set to "just_add", and a second pass is done.
+        */
+       enum { try_to_add, retry_to_add, just_add } mode;
+
+       assert(consumer_zone);
+       if (!xfrd->nsd->db) {
+               xfrd->nsd->db = namedb_open(xfrd->nsd->options);
+       }
+       dname = (const dname_type*)consumer_zone->node.key;
+       if (dname->name_size > 247) {
+               make_catalog_consumer_invalid(consumer_zone, "name too long");
+               return;
+       }
+       if (dname->label_count > 126) {
+               make_catalog_consumer_invalid(consumer_zone,"too many labels");
+               return;
+       }
+       zone = namedb_find_zone(xfrd->nsd->db, dname);
+       if (!zone) {
+               zone = namedb_zone_create(xfrd->nsd->db, dname,
+                               consumer_zone->options);
+               namedb_read_zonefile(xfrd->nsd, zone, NULL, NULL);
+       }
+       if (timespec_compare(&consumer_zone->mtime, &zone->mtime) == 0) {
+               /* Not processing unchanged catalog consumer zone */
+               return;
+       }
+       consumer_zone->mtime = zone->mtime;
+       /* start processing */
+       /* Lookup version.<consumer_zone> TXT and check that it is version 2 */
+       if(!namedb_lookup(xfrd->nsd->db, label_plus_dname("version", dname),
+                               &match, &closest_encloser)
+       || !(rrset = domain_find_rrset(match, zone, TYPE_TXT))) {
+               make_catalog_consumer_invalid(consumer_zone,
+                       "'version.%s TXT RRset not found",
+                       consumer_zone->options->name);
+               return;
+       }
+       version_2_found = 0;
+       for (i = 0; i < rrset->rr_count; i++) {
+               if (rrset->rrs[i].rdata_count != 1)
+                       continue;
+               if (rrset->rrs[i].rdatas[0].data[0] == 2
+               &&  ((uint8_t*)(rrset->rrs[i].rdatas[0].data + 1))[0] == 1
+               &&  ((uint8_t*)(rrset->rrs[i].rdatas[0].data + 1))[1] == '2') {
+                       version_2_found = 1;
+                       break;
+               }
+       }
+       if (!version_2_found) {
+               make_catalog_consumer_invalid(consumer_zone,
+                       "'version.%s' TXT RR with value \"2\" not found",
+                       consumer_zone->options->name);
+               return;
+       }
+       /* Walk over all names under zones.<consumer_zone> */
+       if(!namedb_lookup(xfrd->nsd->db, label_plus_dname("zones", dname),
+                               &match, &closest_encloser)) {
+               /* zones.<consumer_zone> does not exist, so the catalog has no
+                * members. This is just fine. But there may be members that need
+                * to be deleted.
+                */
+               cursor = rbtree_first(&consumer_zone->member_ids);
+               mode = just_add;
+               goto delete_members;
+       }
+       mode = consumer_zone->member_ids.count ? try_to_add : just_add;
+retry_adding:
+       cursor = rbtree_first(&consumer_zone->member_ids);
+       for ( member_id = domain_next(match)
+           ; member_id && domain_is_subdomain(member_id, match)
+           ; member_id = domain_next(member_id)) {
+               domain_type *member_domain;
+               char member_domain_str[5 * MAXDOMAINLEN];
+               struct zone_options* zopt;
+               int valid_group_values;
+               struct pattern_options *pattern = NULL;
+               struct catalog_member_zone* to_add;
+
+               if (domain_dname(member_id)->label_count > dname->label_count+2
+               ||  !(rrset = domain_find_rrset(member_id, zone, TYPE_PTR)))
+                       continue;
+
+               /* RFC9432 Section 4.1. Member Zones:
+                *
+                * `` This PTR record MUST be the only record in the PTR RRset
+                *    with the same name. The presence of more than one record
+                *    in the RRset indicates a broken catalog zone that MUST
+                *    NOT be processed (see Section 5.1).
+                */
+               if (rrset->rr_count != 1) {
+                       make_catalog_consumer_invalid(consumer_zone, 
+                               "only a single PTR RR expected on '%s'",
+                               domain_to_string(member_id));
+                       return;
+               }
+               /* A PTR rr always has 1 rdata element which is a dname */
+               if (rrset->rrs[0].rdata_count != 1)
+                       continue;
+               member_domain = rrset->rrs[0].rdatas[0].domain;
+               domain_to_string_buf(member_domain, member_domain_str);
+               /* remove trailing dot */
+               member_domain_str[strlen(member_domain_str) - 1] = 0;
+
+               valid_group_values = 0;
+               /* Lookup group.<member_id> TXT for matching patterns  */
+               if(!namedb_lookup(xfrd->nsd->db, label_plus_dname("group",
+                                               domain_dname(member_id)),
+                                       &group, &closest_encloser)
+               || !(rrset = domain_find_rrset(group, zone, TYPE_TXT))) {
+                       ; /* pass */
+
+               } else for (i = 0; i < rrset->rr_count; i++) {
+                       /* Max single TXT rdata field length + '\x00' == 256 */
+                       char group_value[256];
+
+                       /* Looking for a single TXT rdata field */
+                       if (rrset->rrs[i].rdata_count != 1
+
+                           /* rdata field should be at least 1 char */
+                       ||  rrset->rrs[i].rdatas[0].data[0] < 2
+
+                           /* single rdata atom with single TXT rdata field */
+                       ||  (uint16_t)(((uint8_t*)(rrset->rrs[i].rdatas[0].data + 1))[0])
+                         != (uint16_t) (rrset->rrs[i].rdatas[0].data[0]-1))
+                               continue;
+
+                       memcpy( group_value
+                             , (uint8_t*)(rrset->rrs[i].rdatas[0].data+1) + 1
+                             ,((uint8_t*)(rrset->rrs[i].rdatas[0].data+1))[0]
+                             );
+                       group_value[
+                              ((uint8_t*)(rrset->rrs[i].rdatas[0].data+1))[0]
+                       ] = 0;
+                       if ((pattern = pattern_options_find(
+                                       xfrd->nsd->options, group_value)))
+                               valid_group_values += 1;
+               }
+               if (valid_group_values > 1) {
+                       log_msg(LOG_ERR, "member zone '%s': only a single "
+                               "group property that matches a pattern is "
+                               "allowed."
+                               "The pattern from \"catalog-member-pattern\" "
+                               "will be used instead.",
+                               domain_to_string(member_id));
+                       valid_group_values = 0;
+
+               } else if (valid_group_values == 1 && pattern
+                               && pattern->catalog_producer_zone) {
+                       log_msg(LOG_ERR, "member zone '%s': group property "
+                               "'%s' matches a catalog producer member zone "
+                               "pattern. In NSD, catalog member zones can be "
+                               "either a member of a catalog consumer zone or"
+                               " a catalog producer zone, but not both.",
+                               domain_to_string(member_id), pattern->pname);
+                       valid_group_values = 0;
+               }
+               if (valid_group_values == 1) {
+                       /* pass: pattern is already set */
+                       assert(pattern);
+
+               } else if (default_pattern)
+                       pattern = default_pattern; /* pass */
+
+               else if (!(pattern = default_pattern =
+                               catalog_member_pattern(consumer_zone))) {
+                       make_catalog_consumer_invalid(consumer_zone, 
+                               "missing 'group.%s' TXT RR and no default "
+                               "pattern from \"catalog-member-pattern\"",
+                               domain_to_string(member_id));
+                       return;
+               }
+               if (cursor == RBTREE_NULL)
+                       ; /* End of the current member zones list.
+                          * From here onwards, zones will only be added.
+                          */
+               else {
+                       int cmp = 0;
+#ifndef NDEBUG
+                       char member_id_str[5 * MAXDOMAINLEN];
+                       domain_to_string_buf(member_id, member_id_str);
+#endif
+                       DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Comparing %s with %s",
+                               member_id_str,
+                               dname_to_string(cursor_member_id(cursor),
+                                       NULL)));
+
+                       while (cursor != RBTREE_NULL && 
+                              (cmp = dname_compare(
+                                       domain_dname(member_id),
+                                       cursor_member_id(cursor))) > 0) {
+                               /* member_id is ahead of the current catalog
+                                * member zone pointed to by cursor.
+                                * The member zone must be deleted.
+                                */
+                               struct catalog_member_zone* to_delete =
+                                       cursor_cmz(cursor);
+#ifndef NDEBUG
+                               const char *member_id_to_delete_str =
+                                  dname_to_string(to_delete->member_id, NULL);
+#endif
+                               cursor = rbtree_next(cursor);
+
+                               DEBUG(DEBUG_XFRD,1, (LOG_INFO,
+                                       "%s > %s: delete %s",
+                                       member_id_str,
+                                       member_id_to_delete_str,
+                                       member_id_to_delete_str));
+                               catalog_del_consumer_member_zone(
+                                               consumer_zone, to_delete);
+                               if(cursor != RBTREE_NULL)
+                                       DEBUG(DEBUG_XFRD,1, (LOG_INFO,
+                                               "Comparing %s with %s",
+                                               member_id_str,
+                                               dname_to_string(
+                                                       cursor_member_id(cursor),
+                                                       NULL)));
+                       }
+                       if (cursor != RBTREE_NULL && cmp == 0) {
+                               /* member_id is also in an current catalog
+                                * member zone, and cursor is pointing
+                                * to it. So, move along ...
+                                */
+                               /* ... but first check if the pattern needs
+                                * a change
+                                */
+                               DEBUG(DEBUG_XFRD,1, (LOG_INFO, "%s == %s: "
+                                   "Compare pattern %s with %s",
+                                   member_id_str, member_id_str,
+                                   cursor_cmz(cursor)->options.pattern->pname,
+                                   pattern->pname));
+
+                               if (cursor_cmz(cursor)->options.pattern ==
+                                               pattern)
+                                       ; /* pass: Pattern remains the same */
+                               else {
+                                       /* Changing patterns is basically
+                                        * deleting and adding the zone again
+                                        */
+                                       zopt  = &cursor_cmz(cursor)->options;
+                                       dname = (dname_type *)zopt->node.key;
+                                       task_new_del_zone(
+                                           xfrd->nsd->task[xfrd->nsd->mytask],
+                                           xfrd->last_task,
+                                           dname);
+                                       xfrd_set_reload_now(xfrd);
+                                       if(zone_is_slave(zopt)) {
+                                               xfrd_del_slave_zone( xfrd
+                                                                  , dname);
+                                       }
+                                       xfrd_del_notify(xfrd, dname);
+#ifdef MULTIPLE_CATALOG_CONSUMER_ZONES
+                                       if(zone_is_catalog_consumer(zopt)) {
+                                               xfrd_deinit_catalog_consumer_zone(
+                                                               xfrd, dname);
+                                       }
+#endif
+                                       /* It is a catalog consumer member,
+                                        * so no need to check if it was a
+                                        * catalog producer member zone to 
+                                        * delete and add
+                                        */
+                                       zopt->pattern = pattern;
+                                       task_new_add_zone(
+                                           xfrd->nsd->task[xfrd->nsd->mytask],
+                                           xfrd->last_task, zopt->name,
+                                           pattern->pname,
+                                           getzonestatid( xfrd->nsd->options
+                                                        , zopt));
+                                       zonestat_inc_ifneeded();
+                                       xfrd_set_reload_now(xfrd);
+#ifdef MULTIPLE_CATALOG_CONSUMER_ZONES
+                                       if(zone_is_catalog_consumer(zopt)) {
+                                               xfrd_init_catalog_consumer_zone(
+                                                               xfrd, zopt);
+                                       }
+#endif
+                                       init_notify_send(xfrd->notify_zones,
+                                                       xfrd->region, zopt);
+                                       if(zone_is_slave(zopt)) {
+                                               xfrd_init_slave_zone(
+                                                               xfrd, zopt);
+                                       }
+                               }
+                               cursor = rbtree_next(cursor);
+                               continue;
+                       }
+                       /* member_id is not in the current catalog member zone
+                        * list, so it must be added
+                        */
+                       assert(cursor == RBTREE_NULL || cmp < 0);
+               }
+               /* See if the zone already exists */
+               zopt = zone_options_find(xfrd->nsd->options,
+                               domain_dname(member_domain));
+               if (zopt) {
+                       /* Produce warning if zopt is from other catalog.
+                        * Give debug message if zopt is not from this catalog.
+                        */
+                       switch(mode) {
+                       case try_to_add:
+                               mode = retry_to_add;
+                               break;
+                       case just_add:
+                               DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Cannot add "
+                                       "catalog member zone %s (from %s): "
+                                       "zone already exists",
+                                       member_domain_str,
+                                       domain_to_string(member_id)));
+                               break;
+                       default:
+                               break;
+                       }
+                       continue;
+               }
+               /* Add member zone if not already there */
+                log_msg(LOG_INFO, "Adding '%s' PTR '%s'",
+                               domain_to_string(member_id),
+                               member_domain_str);
+               DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Adding %s PTR %s",
+                       domain_to_string(member_id), member_domain_str));
+               to_add= catalog_member_zone_create(xfrd->nsd->options->region);
+               to_add->options.name = region_strdup(
+                               xfrd->nsd->options->region, member_domain_str);
+               to_add->options.pattern = pattern;
+               if (!nsd_options_insert_zone(xfrd->nsd->options,
+                                       &to_add->options)) {
+                       log_msg(LOG_ERR, "bad domain name  '%s' pattern %s",
+                               member_domain_str,
+                               ( pattern->pname ? pattern->pname: "<NULL>"));
+                       zone_options_delete(xfrd->nsd->options,
+                                       &to_add->options);
+                       continue;
+               }
+               to_add->member_id = dname_copy( xfrd->nsd->options->region
+                                          , domain_dname(member_id));
+               /* Insert into the members_id list */
+               to_add->node.key = to_add;
+               if(!rbtree_insert( &consumer_zone->member_ids, &to_add->node)){
+                       log_msg(LOG_ERR, "Error adding '%s' PTR '%s' to "
+                               "consumer_zone->member_ids",
+                               domain_to_string(member_id),
+                               member_domain_str);
+                       break;
+               } else
+                       cursor = rbtree_next(&to_add->node);
+               /* make addzone task and schedule reload */
+               task_new_add_zone(xfrd->nsd->task[xfrd->nsd->mytask],
+                       xfrd->last_task, member_domain_str,
+                       pattern->pname,
+                       getzonestatid(xfrd->nsd->options, &to_add->options));
+               zonestat_inc_ifneeded();
+               xfrd_set_reload_now(xfrd);
+#ifdef MULTIPLE_CATALOG_CONSUMER_ZONES
+               /* add to xfrd - catalog consumer zones */
+               if(zone_is_catalog_consumer(&to_add->options)) {
+                       xfrd_init_catalog_consumer_zone(xfrd,&to_add->options);
+               }
+#endif
+               /* add to xfrd - notify (for master and slaves) */
+               init_notify_send(xfrd->notify_zones, xfrd->region,
+                               &to_add->options);
+               /* add to xfrd - slave */
+               if(zone_is_slave(&to_add->options)) {
+                       xfrd_init_slave_zone(xfrd, &to_add->options);
+               }
+               DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Added catalog "
+                       "member zone %s (from %s)",
+                       member_domain_str, domain_to_string(member_id)));
+       }
+delete_members:
+       while (cursor != RBTREE_NULL) {
+               /* Any current catalog member zones remaining, don't have an
+                * member_id in the catalog anymore, so should be deleted too.
+                */
+               struct catalog_member_zone* to_delete = cursor_cmz(cursor);
+
+               cursor = rbtree_next(cursor);
+               catalog_del_consumer_member_zone(consumer_zone, to_delete);
+       }
+       if(mode == retry_to_add) {
+               mode = just_add;
+               goto retry_adding;
+       }
+       debug_log_consumer_members(consumer_zone);
+       make_catalog_consumer_valid(consumer_zone);
+}
+
+
+/******************                                        ******************
+ ******************    catalog producer zone processing    ******************
+ ******************                                        ******************/
+
+static int member_id_compare(const void *left, const void *right)
+{
+       return dname_compare( ((struct catalog_member_zone*)left )->member_id
+                           , ((struct catalog_member_zone*)right)->member_id);
+}
+
+static struct xfrd_catalog_producer_zone*
+xfrd_get_catalog_producer_zone(struct catalog_member_zone* cmz)
+{
+       struct zone_options *producer_zopt;
+       struct xfrd_catalog_producer_zone* producer_zone;
+       const dname_type* producer_name;
+       const char* producer_name_str;
+
+       assert(xfrd);
+       if(!cmz || !cmz->options.pattern->catalog_producer_zone)
+               return NULL;
+
+       /* TODO: Store as dname in pattern->catalog_producer_zone */
+       producer_name = dname_parse(xfrd->nsd->options->region,
+                       cmz->options.pattern->catalog_producer_zone);
+       producer_zopt = zone_options_find(xfrd->nsd->options, producer_name);
+       producer_name_str = dname_to_string(producer_name, NULL);
+       region_recycle( xfrd->nsd->options->region, (void *)producer_name
+                     , dname_total_size(producer_name));
+       if(!producer_zopt) {
+               log_msg(LOG_ERR, "catalog producer zone '%s' not found for "
+                       "zone '%s'", producer_name_str, cmz->options.name);
+               return NULL;
+       }
+       if(!zone_is_catalog_producer(producer_zopt)) {
+               log_msg(LOG_ERR, "cannot add catalog producer member "
+                       "zone '%s' to non producer zone '%s'",
+                       cmz->options.name, producer_zopt->name);
+               return NULL;
+       }
+       producer_name = (dname_type*)producer_zopt->node.key;
+       producer_zone = (struct xfrd_catalog_producer_zone*)
+               rbtree_search(xfrd->catalog_producer_zones, producer_name);
+       if (!producer_zone) {
+               /* Create a new one */
+               DEBUG(DEBUG_XFRD, 1, (LOG_INFO,"creating catalog producer zone"
+                       " '%s'", producer_zopt->name));
+               producer_zone = (struct xfrd_catalog_producer_zone*)
+                       region_alloc(xfrd->region, sizeof(*producer_zone));
+               memset(producer_zone , 0, sizeof(*producer_zone));
+               producer_zone->node.key = producer_zopt->node.key;
+               producer_zone->options = producer_zopt;
+               producer_zone->member_ids.region = xfrd->region;
+               producer_zone->member_ids.root = RBTREE_NULL;
+               producer_zone->member_ids.count = 0;
+               producer_zone->member_ids.cmp = member_id_compare;
+               producer_zone->serial = 0;
+               producer_zone->to_delete = NULL;
+               producer_zone->to_add = NULL;
+               producer_zone->latest_pxfr = NULL;
+               producer_zone->axfr = 1;
+               rbtree_insert(xfrd->catalog_producer_zones,
+                               (rbnode_type*)producer_zone);
+       }
+       return producer_zone;
+}
+
+void
+xfrd_add_catalog_producer_member(struct catalog_member_zone* cmz)
+{
+       struct xfrd_catalog_producer_zone* producer_zone;
+       const dname_type* producer_name;
+       struct xfrd_producer_member* to_add;
+
+       assert(xfrd);
+       if (!(producer_zone = xfrd_get_catalog_producer_zone(cmz))) {
+               return;
+       }
+       producer_name = producer_zone->node.key;
+       while(!cmz->member_id) {
+               /* Make new member_id with this catalog producer */
+               char id_label[sizeof(uint32_t)*2+1];
+               uint32_t new_id = (uint32_t)random_generate(0x7fffffff);
+
+               hex_ntop((void*)&new_id, sizeof(uint32_t), id_label, sizeof(id_label));
+               id_label[sizeof(uint32_t)*2] = 0;
+               cmz->member_id = label_plus_dname(id_label,
+                               label_plus_dname("zones", producer_name));
+               DEBUG(DEBUG_XFRD, 1, (LOG_INFO, "does member_id %s exist?",
+                       dname_to_string(cmz->member_id, NULL)));
+               if (!rbtree_search(&producer_zone->member_ids, cmz)) {
+                       cmz->member_id = dname_copy(xfrd->nsd->options->region,
+                               cmz->member_id);
+                       break;
+               }
+               cmz->member_id = NULL;
+       }
+       cmz->node.key = cmz;
+       rbtree_insert(&producer_zone->member_ids, &cmz->node);
+
+       /* Put data to be added to the producer zone to the to_add stack */
+       to_add = (struct xfrd_producer_member*)region_alloc(xfrd->region,
+                       sizeof(struct xfrd_producer_member));
+       to_add->member_id = cmz->member_id;
+       to_add->member_zone_name = (dname_type*)cmz->options.node.key;
+       to_add->group_name = cmz->options.pattern->pname;
+       to_add->next = producer_zone->to_add;
+       producer_zone->to_add = to_add;
+}
+
+int
+xfrd_del_catalog_producer_member(struct xfrd_state* xfrd,
+               const dname_type* member_zone_name)
+{
+       struct xfrd_producer_member* to_delete;
+       struct catalog_member_zone* cmz;
+       struct xfrd_catalog_producer_zone* producer_zone;
+
+       if(!(cmz = as_catalog_member_zone(zone_options_find(xfrd->nsd->options,
+                                               member_zone_name)))
+       || !(producer_zone = xfrd_get_catalog_producer_zone(cmz))
+       || !rbtree_delete(&producer_zone->member_ids, cmz))
+               return 0;
+       to_delete = (struct xfrd_producer_member*)region_alloc(xfrd->region,
+                       sizeof(struct xfrd_producer_member));
+       to_delete->member_id = cmz->member_id; cmz->member_id = NULL;
+       cmz->node = *RBTREE_NULL;
+       to_delete->member_zone_name = member_zone_name;
+       to_delete->group_name = cmz->options.pattern->pname;
+       to_delete->next = producer_zone->to_delete;
+       producer_zone->to_delete = to_delete;
+       return 1;
+}
+
+static int
+try_buffer_write_SOA(buffer_type* packet, const dname_type* owner,
+               uint32_t serial)
+{
+       size_t mark = buffer_position(packet);
+
+       if(try_buffer_write(packet, dname_name(owner), owner->name_size)
+       && try_buffer_write_u16(packet, TYPE_SOA)
+       && try_buffer_write_u16(packet, CLASS_IN)
+       && try_buffer_write_u32(packet, 0) /* TTL*/
+       && try_buffer_write_u16(packet, 9 + 9 + 5 * sizeof(uint32_t))
+       && try_buffer_write(packet, "\007invalid\000", 9) /* primary */
+       && try_buffer_write(packet, "\007invalid\000", 9) /* mailbox */
+       && try_buffer_write_u32(packet,     serial)       /* serial */
+       && try_buffer_write_u32(packet,       3600)       /* refresh*/
+       && try_buffer_write_u32(packet,        600)       /* retry */
+       && try_buffer_write_u32(packet, 2147483646)       /* expire */
+       && try_buffer_write_u32(packet,          0)       /* minimum */) {
+               ANCOUNT_SET(packet, ANCOUNT(packet) + 1);
+               return 1;
+       }
+       buffer_set_position(packet, mark);
+       return 0;
+}
+
+static int
+try_buffer_write_RR(buffer_type* packet, const dname_type* owner,
+               uint16_t rr_type, uint16_t rdata_len, const void* rdata)
+{
+       size_t mark = buffer_position(packet);
+
+       if(try_buffer_write(packet, dname_name(owner), owner->name_size)
+       && try_buffer_write_u16(packet, rr_type)
+       && try_buffer_write_u16(packet, CLASS_IN)
+       && try_buffer_write_u32(packet, 0) /* TTL*/
+       && try_buffer_write_u16(packet, rdata_len)
+       && try_buffer_write(packet, rdata, rdata_len)) {
+               ANCOUNT_SET(packet, ANCOUNT(packet) + 1);
+               return 1;
+       }
+       buffer_set_position(packet, mark);
+       return 0;
+}
+
+static inline int
+try_buffer_write_PTR(buffer_type* packet, const dname_type* owner,
+               const dname_type* name)
+{
+       return try_buffer_write_RR(packet, owner, TYPE_PTR,
+                       name->name_size, dname_name(name));
+}
+
+static int
+try_buffer_write_TXT(buffer_type* packet, const dname_type* name,
+               const char *txt)
+{
+       size_t mark = buffer_position(packet);
+       size_t len = strlen(txt);
+
+       if(len > 255) {
+               log_msg(LOG_ERR, "cannot make '%s 0 IN TXT \"%s\"': rdata "
+                       "field too long", dname_to_string(name, NULL), txt);
+               return 1;
+       }
+       if(try_buffer_write(packet, dname_name(name), name->name_size)
+       && try_buffer_write_u16(packet, TYPE_TXT)
+       && try_buffer_write_u16(packet, CLASS_IN)
+       && try_buffer_write_u32(packet, 0) /* TTL*/
+       && try_buffer_write_u16(packet, len + 1)
+       && try_buffer_write_u8(packet, len)
+       && try_buffer_write_string(packet, txt)) {
+               ANCOUNT_SET(packet, ANCOUNT(packet) + 1);
+               return 1;
+       }
+       buffer_set_position(packet, mark);
+       return 0; 
+}
+
+static void
+xfr_writer_init(struct xfrd_xfr_writer* xw,
+               struct xfrd_catalog_producer_zone* producer_zone)
+{
+       xw->producer_zone = producer_zone;
+       buffer_create_from( &xw->packet, &xw->packet_space
+                                      , sizeof(xw->packet_space));
+       buffer_write(&xw->packet, "\000\000\000\000\000\000"
+                                 "\000\000\000\000\000\000", 12); /* header */
+       xw->seq_nr = 0;
+       xw->old_serial = xw->producer_zone->serial;
+       xw->new_serial = (uint32_t)xfrd_time();
+       if(xw->new_serial <= xw->old_serial)
+               xw->new_serial = xw->old_serial + 1;
+       if(producer_zone->axfr) {
+               xw->old_serial = 0;
+               producer_zone->axfr = 0;
+       }
+       xw->xfrfilenumber = xfrd->xfrfilenumber++;
+}
+
+static void
+xfr_writer_write_packet(struct xfrd_xfr_writer* xw)
+{
+       const dname_type* producer_name =
+               (const dname_type*)xw->producer_zone->options->node.key;
+
+       /* We want some content at least, so not just a header
+        * This can occur when final SOA was already written.
+        */
+       if(buffer_position(&xw->packet) == 12)
+               return;
+       buffer_flip(&xw->packet);
+       diff_write_packet( dname_to_string(producer_name, NULL)
+                        , xw->producer_zone->options->pattern->pname
+                        , xw->old_serial, xw->new_serial, xw->seq_nr
+                        , buffer_begin(&xw->packet), buffer_limit(&xw->packet)
+                        , xfrd->nsd, xw->xfrfilenumber);
+       xw->seq_nr += 1;
+       buffer_clear(&xw->packet);
+       buffer_write(&xw->packet, "\000\000\000\000\000\000"
+                                 "\000\000\000\000\000\000", 12); /* header */
+}
+
+
+static void
+xfr_writer_commit(struct xfrd_xfr_writer* xw, const char *fmt, ...)
+{
+       va_list args;
+       char msg[1024];
+       const dname_type* producer_name =
+               (const dname_type*)xw->producer_zone->options->node.key;
+
+       va_start(args, fmt);
+       if (vsnprintf(msg, sizeof(msg), fmt, args) >= (int)sizeof(msg)) {
+               log_msg(LOG_WARNING, "truncated diff commit message: '%s'",
+                               msg);
+       }
+       xfr_writer_write_packet(xw); /* Write remaining data */
+       diff_write_commit( dname_to_string(producer_name, NULL)
+                        , xw->old_serial, xw->new_serial
+                        , xw->seq_nr /* Number of packets */
+                        , 1, msg, xfrd->nsd, xw->xfrfilenumber);
+       task_new_apply_xfr( xfrd->nsd->task[xfrd->nsd->mytask], xfrd->last_task
+                         , producer_name
+                         , xw->old_serial, xw->new_serial, xw->xfrfilenumber);
+       xfrd_set_reload_now(xfrd);
+}
+
+static void
+xfrd_process_catalog_producer_zone(
+               struct xfrd_catalog_producer_zone* producer_zone)
+{
+       struct xfrd_xfr_writer xw;
+       dname_type* producer_name;
+       struct xfrd_producer_xfr* pxfr;
+
+       if(!producer_zone->to_add && !producer_zone->to_delete)
+               return; /* No changes */
+
+       producer_name = (dname_type*)producer_zone->node.key;
+       xfr_writer_init(&xw, producer_zone);
+       xfr_writer_add_SOA(&xw, producer_name, xw.new_serial);
+
+       if(xw.old_serial == 0) {
+               /* initial deployment */
+               assert(producer_zone->to_add && !producer_zone->to_delete);
+
+               xfr_writer_add_RR (&xw, producer_name
+                                     , TYPE_NS, 9, "\007invalid\000");
+               xfr_writer_add_TXT(&xw, label_plus_dname("version"
+                                                       , producer_name), "2");
+               goto add_member_zones;
+       } 
+       /* IXFR */
+       xfr_writer_add_SOA(&xw, producer_name, xw.old_serial);
+       while(producer_zone->to_delete) {
+               struct xfrd_producer_member* to_delete =
+                       producer_zone->to_delete;
+
+               /* Pop to_delete from stack */
+               producer_zone->to_delete = to_delete->next;
+               to_delete->next = NULL;
+
+               /* Write <member_id> PTR <member_name> */
+               xfr_writer_add_PTR(&xw, to_delete->member_id
+                                     , to_delete->member_zone_name);
+
+               /* Write group.<member_id> TXT <pattern> */
+               xfr_writer_add_TXT( &xw
+                                 , label_plus_dname("group"
+                                                   , to_delete->member_id)
+                                 , to_delete->group_name);
+
+               region_recycle( xfrd->nsd->options->region
+                             , (void *)to_delete->member_id
+                             , dname_total_size(to_delete->member_id));
+               region_recycle( xfrd->region /* allocated in perform_delzone */
+                             , (void *)to_delete->member_zone_name
+                             , dname_total_size(to_delete->member_zone_name));
+               /* Don't recycle to_delete->group_name it's pattern->pname */
+               region_recycle( xfrd->region, to_delete, sizeof(*to_delete));
+       }
+       xfr_writer_add_SOA(&xw, producer_name, xw.new_serial);
+
+add_member_zones:
+       while(producer_zone->to_add) {
+               struct xfrd_producer_member* to_add = producer_zone->to_add;
+
+               /* Pop to_add from stack */
+               producer_zone->to_add = to_add->next;
+               to_add->next = NULL;
+
+               /* Write <member_id> PTR <member_name> */
+               xfr_writer_add_PTR(&xw, to_add->member_id,
+                               to_add->member_zone_name);
+
+               /* Write group.<member_id> TXT <pattern> */
+               xfr_writer_add_TXT( &xw
+                                 , label_plus_dname("group"
+                                                   , to_add->member_id)
+                                 , to_add->group_name);
+
+               /* Don't recycle any of the struct attributes as they come
+                * from zone_option's that are in use
+                */
+               region_recycle(xfrd->region, to_add, sizeof(*to_add));
+       }
+       xfr_writer_add_SOA(&xw, producer_name, xw.new_serial);
+       xfr_writer_commit(&xw, "xfr for catalog producer zone "
+                       "'%s' with %d members from %u to %u",
+                       dname_to_string(producer_name, NULL),
+                       producer_zone->member_ids.count,
+                       xw.old_serial, xw.new_serial);
+       producer_zone->serial = xw.new_serial;
+
+       /* Hook up an xfrd_producer_xfr, to delete the xfr file when applied */
+       pxfr = (struct xfrd_producer_xfr*)region_alloc(xfrd->region,
+                       sizeof(struct xfrd_producer_xfr));
+       pxfr->serial = xw.new_serial;
+       pxfr->xfrfilenumber = xw.xfrfilenumber;
+       if((pxfr->next = producer_zone->latest_pxfr))
+               pxfr->next->prev_next_ptr = &pxfr->next;
+       pxfr->prev_next_ptr = &producer_zone->latest_pxfr;
+       producer_zone->latest_pxfr = pxfr;
+}
+
+void xfrd_process_catalog_producer_zones()
+{
+       struct xfrd_catalog_producer_zone* producer_zone;
+
+       RBTREE_FOR(producer_zone, struct xfrd_catalog_producer_zone*,
+                       xfrd->catalog_producer_zones) {
+               xfrd_process_catalog_producer_zone(producer_zone);
+       }
+}
+
diff --git a/usr.sbin/nsd/xfrd-catalog-zones.h b/usr.sbin/nsd/xfrd-catalog-zones.h
new file mode 100644 (file)
index 0000000..17992b1
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * xfrd-catalog-zones.h -- catalog zone implementation for NSD
+ *
+ * Copyright (c) 2024, NLnet Labs. All rights reserved.
+ *
+ * See LICENSE for the license.
+ */
+#ifndef XFRD_CATALOG_ZONES_H
+#define XFRD_CATALOG_ZONES_H
+#include "xfrd.h"
+struct xfrd_producer_member;
+struct xfrd_producer_xfr;
+
+/**
+ * Catalog zones withing the xfrd context
+ */
+struct xfrd_catalog_consumer_zone {
+       /* For indexing in struc xfrd_state { rbtree_type* catalog_consumer_zones; } */
+       rbnode_type node;
+
+       /* Associated zone options with this catalog consumer zone */
+       struct zone_options* options;
+
+       /* Member zones indexed by member_id */
+       rbtree_type member_ids;
+
+       /* Last time processed, compare with zone->mtime to see if we need to process */
+       struct timespec mtime;
+
+       /* The reason for this zone to be invalid, or NULL if it is valid */
+       char *invalid;
+} ATTR_PACKED;
+
+/**
+ * Catalog producer zones withing the xfrd context
+ */
+struct xfrd_catalog_producer_zone {
+       /* For indexing in struc xfrd_state { rbtree_type* catalog_producer_zones; } */
+       rbnode_type node;
+
+       /* Associated zone options with this catalog consumer zone */
+       struct zone_options* options;
+
+       /* Member zones indexed by member_id */
+       rbtree_type member_ids;
+
+       /* SOA serial for this zone */
+       uint32_t serial;
+
+       /* Stack of members to delete from this catalog producer zone */
+       struct xfrd_producer_member* to_delete;
+
+       /* Stack of member zones to add to this catalog producer zone */
+       struct xfrd_producer_member* to_add;
+
+       /* To cleanup on disk xfr files */
+       struct xfrd_producer_xfr* latest_pxfr;
+
+       /* Set if next generated xfr for the producer zone should be axfr */
+       unsigned axfr: 1;
+} ATTR_PACKED;
+
+/**
+ * Data to add or remove from a catalog producer zone
+ */
+struct xfrd_producer_member {
+       const dname_type* member_id;
+       const dname_type* member_zone_name;
+       const char* group_name;
+       struct xfrd_producer_member* next;
+} ATTR_PACKED;
+
+/**
+ * To track applied generated transfers from catalog producer zones
+ */
+struct xfrd_producer_xfr {
+       uint32_t serial;
+       uint64_t xfrfilenumber;
+       struct xfrd_producer_xfr** prev_next_ptr;
+       struct xfrd_producer_xfr*  next;
+} ATTR_PACKED;
+
+/* Initialize as a catalog consumer zone */
+void xfrd_init_catalog_consumer_zone(xfrd_state_type* xfrd,
+               struct zone_options* zone);
+
+/* To be called if and a zone is no longer a catalog zone (changed pattern) */
+void xfrd_deinit_catalog_consumer_zone(xfrd_state_type* xfrd,
+               const dname_type* dname);
+
+/* make the catalog consumer zone invalid for given reason */
+void make_catalog_consumer_invalid(
+               struct xfrd_catalog_consumer_zone *consumer_zone,
+               const char *format, ...) ATTR_FORMAT(printf, 2, 3);
+
+/* Return the reason a zone is invalid, or NULL on a valid catalog */
+const char *invalid_catalog_consumer_zone(struct zone_options* zone);
+
+/* make the catalog consumer zone valid again */
+void make_catalog_consumer_valid(
+               struct xfrd_catalog_consumer_zone *consumer_zone);
+
+/* Check the catalog consumer zone files (or file if zone is given) */
+void xfrd_check_catalog_consumer_zonefiles(const dname_type* name);
+
+/* process the catalog consumer zones, load if needed */
+void xfrd_process_catalog_consumer_zones();
+
+
+/* Add (or change) <member_id> PTR <member_zone_name>, and 
+ * group.<member_id> TXT <pattern->pname> to the associated producer zone by 
+ * constructed xfr. make cmz->member_id if needed. */
+void xfrd_add_catalog_producer_member(struct catalog_member_zone* cmz);
+
+/* Delete <member_id> PTR <member_zone_name>, and 
+ * group.<member_id> TXT <pattern->pname> from the associated producer zone by
+ * constructed xfr. Return 1 if zone is deleted. In this case, member_zone_name
+ * is taken over by xfrd and cannot be recycled by the caller. member_zone_name
+ * must have been allocated int the xfrd->nsd->options->region
+ */
+int xfrd_del_catalog_producer_member(xfrd_state_type* xfrd,
+               const dname_type* dname);
+
+/* process the catalog producer zones */
+void xfrd_process_catalog_producer_zones();
+
+#endif
+
index 274d5a2..edb1321 100644 (file)
@@ -264,7 +264,7 @@ xfrd_read_state(struct xfrd_state* xfrd)
                zone->master = acl_find_num(zone->zone_options->pattern->
                        request_xfr, zone->master_num);
                if(!zone->master) {
-                       DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: masters changed for zone %s",
+                       DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: primaries changed for zone %s",
                                zone->apex_str));
                        zone->master = zone->zone_options->pattern->request_xfr;
                        zone->master_num = 0;
@@ -464,10 +464,10 @@ xfrd_write_state(struct xfrd_state* xfrd)
 
        fprintf(out, "%s\n", XFRD_FILE_MAGIC);
        fprintf(out, "# This file is written on exit by nsd xfr daemon.\n");
-       fprintf(out, "# This file contains slave zone information:\n");
+       fprintf(out, "# This file contains secondary zone information:\n");
        fprintf(out, "#         * timeouts (when was zone data acquired)\n");
        fprintf(out, "#         * state (OK, refreshing, expired)\n");
-       fprintf(out, "#         * which master transfer to attempt next\n");
+       fprintf(out, "#         * which primary transfer to attempt next\n");
        fprintf(out, "# The file is read on start (but not on reload) by nsd xfr daemon.\n");
        fprintf(out, "# You can edit; but do not change statement order\n");
        fprintf(out, "# and no fancy stuff (like quoted \"strings\").\n");
@@ -475,7 +475,7 @@ xfrd_write_state(struct xfrd_state* xfrd)
        fprintf(out, "# If you remove a zone entry, it will be refreshed.\n");
        fprintf(out, "# This can be useful for an expired zone; it revives\n");
        fprintf(out, "# the zone temporarily, from refresh-expiry time.\n");
-       fprintf(out, "# If you delete the file all slave zones are updated.\n");
+       fprintf(out, "# If you delete the file all secondary zones are updated.\n");
        fprintf(out, "#\n");
        fprintf(out, "# Note: if you edit this file while nsd is running,\n");
        fprintf(out, "#       it will be overwritten on exit by nsd.\n");
index 5b1d80b..776828e 100644 (file)
@@ -1513,7 +1513,7 @@ xfrd_tcp_read(struct xfrd_tcp_pipeline* tp)
                        /* fall through to remove zone from tp */
                        /* fallthrough */
                case xfrd_packet_transfer:
-                       if(zone->zone_options->pattern->multi_master_check) {
+                       if(zone->zone_options->pattern->multi_primary_check) {
                                xfrd_tcp_release(xfrd->tcp_set, zone);
                                xfrd_make_request(zone);
                                break;
index 9882e3d..e227e8b 100644 (file)
@@ -20,6 +20,7 @@
 #include "xfrd-tcp.h"
 #include "xfrd-disk.h"
 #include "xfrd-notify.h"
+#include "xfrd-catalog-zones.h"
 #include "options.h"
 #include "util.h"
 #include "netio.h"
@@ -315,6 +316,8 @@ xfrd_main(void)
        xfrd->shutdown = 0;
        while(!xfrd->shutdown)
        {
+               xfrd_process_catalog_producer_zones();
+               xfrd_process_catalog_consumer_zones();
                /* process activated zones before blocking in select again */
                xfrd_process_activated();
                /* dispatch may block for a longer period, so current is gone */
@@ -419,6 +422,10 @@ xfrd_shutdown()
        /* process-exit cleans up memory used by xfrd process */
        DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd shutdown complete"));
 #ifdef MEMCLEAN /* OS collects memory pages */
+       if(xfrd->nsd->db) {
+               namedb_close(xfrd->nsd->db);
+       }
+       /* TODO: cleanup xfrd->catalog_consumer_zones and xfrd->catalog_producer_zones */
        if(xfrd->zones) {
                xfrd_zone_type* z;
                RBTREE_FOR(z, xfrd_zone_type*, xfrd->zones) {
@@ -542,12 +549,25 @@ xfrd_init_zones()
                (int (*)(const void *, const void *)) dname_compare);
        xfrd->notify_zones = rbtree_create(xfrd->region,
                (int (*)(const void *, const void *)) dname_compare);
+       xfrd->catalog_consumer_zones = rbtree_create(xfrd->region,
+               (int (*)(const void *, const void *)) dname_compare);
+       xfrd->catalog_producer_zones = rbtree_create(xfrd->region,
+               (int (*)(const void *, const void *)) dname_compare);
 
        RBTREE_FOR(zone_opt, struct zone_options*, xfrd->nsd->options->zone_options)
        {
                DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: adding %s zone",
                        zone_opt->name));
 
+               if(zone_is_catalog_consumer(zone_opt)) {
+                       DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s "
+                               "is a catalog consumer zone", zone_opt->name));
+                       xfrd_init_catalog_consumer_zone(xfrd, zone_opt);
+               }
+               if(zone_is_catalog_producer_member(zone_opt)) {
+                       xfrd_add_catalog_producer_member(
+                                       as_catalog_member_zone(zone_opt));
+               }
                init_notify_send(xfrd->notify_zones, xfrd->region, zone_opt);
                if(!zone_is_slave(zone_opt)) {
                        DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s, "
@@ -558,7 +578,77 @@ xfrd_init_zones()
                xfrd_init_slave_zone(xfrd, zone_opt);
        }
        DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: started server %d "
-               "secondary zones", (int)xfrd->zones->count));
+               "secondary zones, %d catalog zones", (int)xfrd->zones->count,
+               (int)xfrd->catalog_consumer_zones->count));
+}
+
+static void
+apply_xfrs_to_consumer_zone(struct xfrd_catalog_consumer_zone* consumer_zone,
+               zone_type* dbzone, xfrd_xfr_type* xfr)
+{
+       FILE* df;
+
+       if(xfr->msg_is_ixfr) {
+               uint32_t soa_serial;
+               xfrd_xfr_type* prev;
+
+               if(dbzone->soa_rrset == NULL || dbzone->soa_rrset->rrs == NULL
+               || dbzone->soa_rrset->rrs[0].rdata_count <= 2
+               || rdata_atom_size(dbzone->soa_rrset->rrs[0].rdatas[2])
+                               != sizeof(uint32_t)) {
+
+                       make_catalog_consumer_invalid(consumer_zone,
+                              "could not apply ixfr on catalog consumer zone "
+                              "\'%s\': invalid SOA resource record",
+                              consumer_zone->options->name);
+                       return;
+               }
+               soa_serial = read_uint32(rdata_atom_data(
+                               dbzone->soa_rrset->rrs[0].rdatas[2]));
+               if(soa_serial == xfr->msg_old_serial) 
+                       goto apply_xfr;
+               for(prev = xfr->prev; prev; prev = prev->prev) {
+                       if(!prev->sent)
+                               continue;
+                       if(xfr->msg_old_serial != prev->msg_new_serial)
+                               continue;
+                       apply_xfrs_to_consumer_zone(consumer_zone, dbzone, prev);
+                       break;
+               }
+               if(!prev || xfr->msg_old_serial != read_uint32(rdata_atom_data(
+                                       dbzone->soa_rrset->rrs[0].rdatas[2]))){
+                       make_catalog_consumer_invalid(consumer_zone,
+                              "could not find and/or apply xfrs for catalog "
+                              "consumer zone \'%s\': to update to serial %u",
+                              consumer_zone->options->name,
+                              xfr->msg_new_serial);
+                       return;
+               }
+       }
+apply_xfr:
+       DEBUG(DEBUG_IPC,1, (LOG_INFO, "apply %sXFR %u -> %u to consumer zone "
+               "\'%s\'", (xfr->msg_is_ixfr ? "I" : "A"), xfr->msg_old_serial,
+               xfr->msg_new_serial, consumer_zone->options->name));
+
+       if(!(df = xfrd_open_xfrfile(xfrd->nsd, xfr->xfrfilenumber, "r"))) {
+               make_catalog_consumer_invalid(consumer_zone,
+                      "could not open transfer file %lld: %s",
+                      (long long)xfr->xfrfilenumber, strerror(errno));
+
+       } else if(0 >= apply_ixfr_for_zone(xfrd->nsd, dbzone, df,
+                       xfrd->nsd->options, NULL, NULL, xfr->xfrfilenumber)) {
+               make_catalog_consumer_invalid(consumer_zone,
+                       "error processing transfer file %lld",
+                       (long long)xfr->xfrfilenumber);
+               fclose(df);
+       } else {
+               /* Make valid for reprocessing */
+               make_catalog_consumer_valid(consumer_zone);
+               fclose(df);
+               DEBUG(DEBUG_IPC,1, (LOG_INFO, "%sXFR %u -> %u to consumer zone \'%s\' "
+                       "applied", (xfr->msg_is_ixfr ? "I" : "A"), xfr->msg_old_serial,
+                       xfr->msg_new_serial, consumer_zone->options->name));
+       }
 }
 
 static void
@@ -567,6 +657,9 @@ xfrd_process_soa_info_task(struct task_list_d* task)
        xfrd_soa_type soa;
        xfrd_soa_type* soa_ptr = &soa;
        xfrd_zone_type* zone;
+       struct xfrd_catalog_producer_zone* producer_zone;
+       struct xfrd_catalog_consumer_zone* consumer_zone = NULL;
+       zone_type* dbzone = NULL;
        xfrd_xfr_type* xfr;
        xfrd_xfr_type* prev_xfr;
        enum soainfo_hint hint;
@@ -627,14 +720,71 @@ xfrd_process_soa_info_task(struct task_list_d* task)
 #endif
        }
 
-       if(!zone) {
-               DEBUG(DEBUG_IPC,1, (LOG_INFO, "xfrd: zone %s master zone updated",
+       if(zone) 
+               ; /* pass */
+
+       else if((producer_zone = (struct xfrd_catalog_producer_zone*)
+                       rbtree_search(xfrd->catalog_producer_zones, task->zname))) {
+               struct xfrd_producer_xfr* pxfr, *next_pxfr;
+
+               DEBUG(DEBUG_IPC,1, (LOG_INFO, "Zone %s is catalog producer",
+                       dname_to_string(task->zname,0)));
+
+               if(hint != soainfo_ok)
+                       producer_zone->axfr = 1;
+
+               for(pxfr = producer_zone->latest_pxfr; pxfr; pxfr = next_pxfr) {
+                       next_pxfr = pxfr->next;
+
+                       DEBUG(DEBUG_IPC,1, (LOG_INFO, "pxfr for zone %s for serial %u",
+                               dname_to_string(task->zname,0), pxfr->serial));
+                       
+                       if(hint != soainfo_ok)
+                               ; /* pass */
+                       else if(!soa_ptr || soa_ptr->serial != htonl(pxfr->serial))
+                               continue;
+
+                       else if(xfrd->reload_failed) {
+                               DEBUG(DEBUG_IPC, 1,
+                                       (LOG_INFO, "xfrd: zone %s mark update "
+                                                  "to serial %u verified",
+                                                  producer_zone->options->name,
+                                                  pxfr->serial));
+                               diff_update_commit(
+                                       producer_zone->options->name,
+                                       DIFF_VERIFIED, xfrd->nsd,
+                                       pxfr->xfrfilenumber);
+                               return;
+                       }
+                       DEBUG(DEBUG_IPC, 1,
+                               (LOG_INFO, "xfrd: zone %s delete update to "
+                                "serial %u", producer_zone->options->name,
+                                pxfr->serial));
+                       xfrd_unlink_xfrfile(xfrd->nsd, pxfr->xfrfilenumber);
+                       if((*pxfr->prev_next_ptr = pxfr->next))
+                               pxfr->next->prev_next_ptr = pxfr->prev_next_ptr;
+                       region_recycle(xfrd->region, pxfr, sizeof(*pxfr));
+                       notify_handle_master_zone_soainfo(xfrd->notify_zones,
+                               task->zname, soa_ptr);
+               }
+               return;
+       } else {
+               DEBUG(DEBUG_IPC,1, (LOG_INFO, "xfrd: zone %s primary zone updated",
                        dname_to_string(task->zname,0)));
                notify_handle_master_zone_soainfo(xfrd->notify_zones,
                        task->zname, soa_ptr);
                return;
        }
-
+       if(xfrd->nsd->db
+       && xfrd->catalog_consumer_zones
+#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
+       && xfrd->catalog_consumer_zones->count == 1
+#endif
+       && (consumer_zone = (struct xfrd_catalog_consumer_zone*)rbtree_search(
+                       xfrd->catalog_consumer_zones, task->zname))) {
+               dbzone = namedb_find_or_create_zone( xfrd->nsd->db, task->zname
+                                                  , consumer_zone->options);
+       }
        /* soainfo_gone and soainfo_bad are straightforward, delete all updates
           that were transfered, i.e. acquired != 0. soainfo_ok is more
           complicated as it is possible that there are subsequent corrupt or
@@ -670,6 +820,9 @@ xfrd_process_soa_info_task(struct task_list_d* task)
                                        xfrd->nsd, xfr->xfrfilenumber);
                                return;
                        }
+                       if(consumer_zone && dbzone)
+                               apply_xfrs_to_consumer_zone(
+                                       consumer_zone, dbzone, xfr);
                }
                DEBUG(DEBUG_IPC, 1,
                        (LOG_INFO, "xfrd: zone %s delete update to serial %u",
@@ -857,7 +1010,8 @@ xfrd_del_slave_zone(xfrd_state_type* xfrd, const dname_type* dname)
                xfrd_tcp_release(xfrd->tcp_set, z);
        } else if(z->zone_handler.ev_fd != -1 && z->event_added) {
                xfrd_udp_release(z);
-       } else if(z->event_added)
+       }
+       if(z->event_added)
                event_del(&z->zone_handler);
 
        while(z->latest_xfr) xfrd_delete_zone_xfr(z, z->latest_xfr);
@@ -1023,7 +1177,7 @@ xfrd_make_request(xfrd_zone_type* zone)
        if(zone->next_master != -1) {
                /* we are told to use this next master */
                DEBUG(DEBUG_XFRD,1, (LOG_INFO,
-                       "xfrd zone %s use master %i",
+                       "xfrd zone %s use primary %i",
                        zone->apex_str, zone->next_master));
                zone->master_num = zone->next_master;
                zone->master = acl_find_num(zone->zone_options->pattern->
@@ -1063,13 +1217,13 @@ xfrd_make_request(xfrd_zone_type* zone)
        }
 
        /* multi-master-check */
-       if(zone->zone_options->pattern->multi_master_check) {
+       if(zone->zone_options->pattern->multi_primary_check) {
                if(zone->multi_master_first_master == zone->master_num &&
                        zone->round_num > 0 &&
                        zone->state != xfrd_zone_expired) {
                        /* tried all servers and update zone */
                        if(zone->multi_master_update_check >= 0) {
-                               VERBOSITY(2, (LOG_INFO, "xfrd: multi master "
+                               VERBOSITY(2, (LOG_INFO, "xfrd: multi primary "
                                        "check: zone %s completed transfers",
                                        zone->apex_str));
                        }
@@ -1088,7 +1242,7 @@ xfrd_make_request(xfrd_zone_type* zone)
        if (zone->master->ixfr_disabled &&
           (zone->master->ixfr_disabled + XFRD_NO_IXFR_CACHE) <= time(NULL)) {
                DEBUG(DEBUG_XFRD,1, (LOG_INFO, "clear negative caching ixfr "
-                                               "disabled for master %s num "
+                                               "disabled for primary %s num "
                                                "%d ",
                        zone->master->ip_address_spec, zone->master_num));
                zone->master->ixfr_disabled = 0;
@@ -1119,7 +1273,7 @@ xfrd_make_request(xfrd_zone_type* zone)
                        xfrd_tcp_obtain(xfrd->tcp_set, zone);
                } else {
                        DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd zone %s axfr "
-                               "fallback not allowed, skipping master %s.",
+                               "fallback not allowed, skipping primary %s.",
                                zone->apex_str, zone->master->ip_address_spec));
                }
        }
@@ -1527,7 +1681,7 @@ xfrd_udp_read(xfrd_zone_type* zone)
                        xfrd_tcp_obtain(xfrd->tcp_set, zone);
                        break;
                case xfrd_packet_transfer:
-                       if(zone->zone_options->pattern->multi_master_check) {
+                       if(zone->zone_options->pattern->multi_primary_check) {
                                xfrd_udp_release(zone);
                                xfrd_make_request(zone);
                                break;
@@ -2115,7 +2269,7 @@ xfrd_parse_received_xfr_packet(xfrd_zone_type* zone, buffer_type* packet,
                        xfrd_set_zone_state(zone, xfrd_zone_ok);
                        DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s is ok",
                                zone->apex_str));
-                       if(zone->zone_options->pattern->multi_master_check) {
+                       if(zone->zone_options->pattern->multi_primary_check) {
                                region_destroy(tempregion);
                                return xfrd_packet_drop;
                        }
@@ -2384,7 +2538,7 @@ xfrd_handle_received_xfr_packet(xfrd_zone_type* zone, buffer_type* packet)
                DEBUG(DEBUG_XFRD,1, (LOG_INFO,
                        "xfrd: zone %s is waiting for reload",
                        zone->apex_str));
-               if(zone->zone_options->pattern->multi_master_check) {
+               if(zone->zone_options->pattern->multi_primary_check) {
                        zone->multi_master_update_check = zone->master_num;
                        xfrd_set_reload_timeout();
                        return xfrd_packet_transfer;
@@ -2514,7 +2668,7 @@ xfrd_handle_passed_packet(buffer_type* packet,
                if(next != -1) {
                        zone->next_master = next;
                        DEBUG(DEBUG_XFRD,1, (LOG_INFO,
-                               "xfrd: notify set next master to query %d",
+                               "xfrd: notify set next primary to query %d",
                                next));
                }
        }
index f2b8215..72f3a05 100644 (file)
@@ -119,6 +119,12 @@ struct xfrd_state {
        int notify_udp_num;
        /* first and last notify_zone* entries waiting for a UDP socket */
        struct notify_zone *notify_waiting_first, *notify_waiting_last;
+
+       /* tree of catalog consumer zones. Processing is disabled if > 1. */
+       rbtree_type *catalog_consumer_zones;
+
+       /* tree of updated catalog producer zones for which the content to serve */
+       rbtree_type *catalog_producer_zones;
 };
 
 /*