From 0293adff869bef654e2dabc84e51895a232aa46d Mon Sep 17 00:00:00 2001 From: Jeroen Koekkoek Date: Thu, 20 Feb 2020 11:58:18 +0100 Subject: [PATCH] Port zone verification from CreDNS to NSD Add support for verification of zones on updates. Allows the user to execute a script or program when a zone is updated to verify if the contents is correct. The verifier can keep the zone from being published if for example the zone was incorrectly signed. --- Makefile.in | 11 +- configlexer.lex | 12 +- configparser.y | 107 ++++++++- configure.ac | 1 + dbaccess.c | 2 + difffile.c | 10 +- difffile.h | 3 +- doc/README | 97 ++++++++ doc/RELNOTES | 3 +- namedb.h | 6 +- nsd-checkconf.c | 44 ++++ nsd.c | 101 ++++++-- nsd.conf.sample.in | 49 ++++ nsd.h | 10 + options.c | 76 ++++++ options.h | 24 ++ popen3.c | 52 ++--- popen3.h | 6 +- server.c | 149 ++++++++++++ verify.c | 564 +++++++++++++++++++++++++++++++++++++++++++++ verify.h | 77 +++++++ xfrd.c | 59 ++++- xfrd.h | 3 + 23 files changed, 1385 insertions(+), 81 deletions(-) create mode 100644 verify.c create mode 100644 verify.h diff --git a/Makefile.in b/Makefile.in index ec4e6063d..d7e555eaf 100644 --- a/Makefile.in +++ b/Makefile.in @@ -81,12 +81,12 @@ 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 udbradtree.o udbzone.o util.o bitset.o popen3.o XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.o $(DNSTAP_OBJ) -NSD_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) difffile.o ipc.o mini_event.o netio.o nsd.o server.o dbaccess.o dbcreate.o zlexer.o zonec.o zparser.o +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 -NSD_CHECKZONE_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o zonec.o zparser.o zlexer.o nsd-checkzone.o +NSD_CHECKZONE_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o zonec.o zparser.o zlexer.o nsd-checkzone.o verify.o NSD_CONTROL_OBJ=$(COMMON_OBJ) nsd-control.o -CUTEST_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o zonec.o zparser.o zlexer.o cutest_dname.o cutest_dns.o cutest_iterated_hash.o cutest_run.o cutest_radtree.o cutest_rbtree.o cutest_namedb.o cutest_options.o cutest_region.o cutest_rrl.o cutest_udb.o cutest_udbrad.o cutest_util.o cutest_bitset.o cutest_popen3.o cutest_iter.o cutest_event.o cutest.o qtest.o +CUTEST_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o verify.o zonec.o zparser.o zlexer.o cutest_dname.o cutest_dns.o cutest_iterated_hash.o cutest_run.o cutest_radtree.o cutest_rbtree.o cutest_namedb.o cutest_options.o cutest_region.o cutest_rrl.o cutest_udb.o cutest_udbrad.o cutest_util.o cutest_bitset.o cutest_popen3.o cutest_iter.o cutest_event.o cutest.o qtest.o NSD_MEM_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o zonec.o zparser.o zlexer.o nsd-mem.o all: $(TARGETS) $(MANUALS) @@ -494,7 +494,7 @@ rrl.o: $(srcdir)/rrl.c config.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/name server.o: $(srcdir)/server.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \ $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \ $(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/netio.h $(srcdir)/xfrd.h $(srcdir)/options.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h \ - $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h $(srcdir)/ixfr.h + $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h $(srcdir)/ixfr.h $(srcdir)/verify.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 \ @@ -509,6 +509,9 @@ udbzone.o: $(srcdir)/udbzone.c config.h $(srcdir)/udbzone.h $(srcdir)/udb.h $(sr 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 bitset.o: $(srcdir)/bitset.c $(srcdir)/bitset.h +verify.o: $(srcdir)/verify.c config.h $(srcdir)/region-allocator.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h \ + $(srcdir)/util.h config.h $(srcdir)/dns.h $(srcdir)/rbtree.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/options.h $(srcdir)/difffile.h \ + $(srcdir)/netio.h $(srcdir)/verify.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)/packet.h $(srcdir)/rdata.h \ diff --git a/configlexer.lex b/configlexer.lex index 57b160531..65b635cbc 100644 --- a/configlexer.lex +++ b/configlexer.lex @@ -280,8 +280,8 @@ dnstap-log-auth-query-messages{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_D dnstap-log-auth-response-messages{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_LOG_AUTH_RESPONSE_MESSAGES; } log-time-ascii{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_LOG_TIME_ASCII;} round-robin{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ROUND_ROBIN;} -minimal-responses{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MINIMAL_RESPONSES;} -confine-to-zone{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_CONFINE_TO_ZONE;} +minimal-responses{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MINIMAL_RESPONSES;} +confine-to-zone{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_CONFINE_TO_ZONE;} refuse-any{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_REFUSE_ANY;} max-refresh-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MAX_REFRESH_TIME;} min-refresh-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MIN_REFRESH_TIME;} @@ -303,6 +303,14 @@ cookie-secret{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET;} cookie-secret-file{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET_FILE;} xfrd-tcp-max{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_XFRD_TCP_MAX;} xfrd-tcp-pipeline{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_XFRD_TCP_PIPELINE;} +verify{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFY; } +enable{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ENABLE; } +verify-zone{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFY_ZONE; } +verify-zones{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFY_ZONES; } +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; } {NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;} servers={UNQUOTEDLETTER}* { diff --git a/configparser.y b/configparser.y index 08173f13f..953e52386 100644 --- a/configparser.y +++ b/configparser.y @@ -36,6 +36,12 @@ static int parse_boolean(const char *str, int *bln); 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); + +struct component { + struct component *next; + char *str; +}; + %} %union { @@ -45,6 +51,8 @@ static int parse_range(const char *str, long long *low, long long *high); struct ip_address_option *ip; struct range_option *range; struct cpu_option *cpu; + char **strv; + struct component *comp; } %token STRING @@ -53,6 +61,8 @@ static int parse_range(const char *str, long long *low, long long *high); %type ip_address %type service_cpu_affinity %type cpus +%type command +%type arguments /* server */ %token VAR_SERVER @@ -195,6 +205,16 @@ static int parse_range(const char *str, long long *low, long long *high); %token VAR_BINDTODEVICE %token VAR_SETFIB +/* verify */ +%token VAR_VERIFY +%token VAR_ENABLE +%token VAR_VERIFY_ZONE +%token VAR_VERIFY_ZONES +%token VAR_VERIFIER +%token VAR_VERIFIER_COUNT +%token VAR_VERIFIER_FEED_ZONE +%token VAR_VERIFIER_TIMEOUT + %% blocks: @@ -208,7 +228,8 @@ block: | key | tls_auth | pattern - | zone ; + | zone + | verify ; server: VAR_SERVER server_block ; @@ -986,6 +1007,90 @@ pattern_or_zone_option: { cfg_parser->pattern->create_ixfr = $2; cfg_parser->pattern->create_ixfr_is_default = 0; + } + | VAR_VERIFY_ZONE boolean + { cfg_parser->pattern->verify_zone = $2; } + | VAR_VERIFIER command + { cfg_parser->pattern->verifier = $2; } + | VAR_VERIFIER_FEED_ZONE boolean + { cfg_parser->pattern->verifier_feed_zone = $2; } + | VAR_VERIFIER_TIMEOUT number + { cfg_parser->pattern->verifier_timeout = $2; } ; + +verify: + VAR_VERIFY verify_block ; + +verify_block: + verify_block verify_option | ; + +verify_option: + VAR_ENABLE boolean + { cfg_parser->opt->verify_enable = $2; } + | VAR_IP_ADDRESS ip_address + { + struct ip_address_option *ip = cfg_parser->opt->verify_ip_addresses; + if(!ip) { + cfg_parser->opt->verify_ip_addresses = $2; + } else { + while(ip->next) { ip = ip->next; } + ip->next = $2; + } + } + | VAR_PORT number + { + /* port number, stored as a string */ + char buf[16]; + (void)snprintf(buf, sizeof(buf), "%lld", $2); + cfg_parser->opt->verify_port = region_strdup(cfg_parser->opt->region, buf); + } + | VAR_VERIFY_ZONES boolean + { cfg_parser->opt->verify_zones = $2; } + | VAR_VERIFIER command + { cfg_parser->opt->verifier = $2; } + | VAR_VERIFIER_COUNT number + { cfg_parser->opt->verifier_count = (int)$2; } + | VAR_VERIFIER_TIMEOUT number + { cfg_parser->opt->verifier_timeout = (int)$2; } + | VAR_VERIFIER_FEED_ZONE boolean + { cfg_parser->opt->verifier_feed_zone = $2; } ; + +command: + STRING arguments + { + char **argv; + size_t argc = 1; + for(struct component *i = $2; i; i = i->next) { + argc++; + } + argv = region_alloc_zero( + cfg_parser->opt->region, (argc + 1) * sizeof(char *)); + argc = 0; + argv[argc++] = $1; + for(struct component *j, *i = $2; i; i = j) { + j = i->next; + argv[argc++] = i->str; + region_recycle(cfg_parser->opt->region, i, sizeof(*i)); + } + $$ = argv; + } ; + +arguments: + { $$ = NULL; } + | arguments STRING + { + struct component *comp = region_alloc_zero( + cfg_parser->opt->region, sizeof(*comp)); + comp->str = region_strdup(cfg_parser->opt->region, $2); + if($1) { + struct component *tail = $1; + while(tail->next) { + tail = tail->next; + } + tail->next = comp; + $$ = $1; + } else { + $$ = comp; + } } ; ip_address: diff --git a/configure.ac b/configure.ac index 1e812943a..5bd5c0169 100644 --- a/configure.ac +++ b/configure.ac @@ -909,6 +909,7 @@ AC_DEFINE_UNQUOTED([TLS_PORT], ["853"], [Define to the default DNS over TLS port AC_DEFINE_UNQUOTED([MAXSYSLOGMSGLEN], [512], [Define to the maximum message length to pass to syslog.]) AC_DEFINE_UNQUOTED([NSD_CONTROL_PORT], [8952], [Define to the default nsd-control port.]) AC_DEFINE_UNQUOTED([NSD_CONTROL_VERSION], [1], [Define to nsd-control proto version.]) +AC_DEFINE_UNQUOTED([VERIFY_PORT], ["5347"], [Define to the default zone verification udp port.]) dnl dnl Determine the syslog facility to use diff --git a/dbaccess.c b/dbaccess.c index 2f07c0051..6fa20db24 100644 --- a/dbaccess.c +++ b/dbaccess.c @@ -247,6 +247,8 @@ namedb_zone_create(namedb_type* db, const dname_type* dname, zone->zonestatid = 0; zone->is_secure = 0; zone->is_changed = 0; + zone->is_updated = 0; + zone->is_bad = 0; zone->is_ok = 1; return zone; } diff --git a/difffile.c b/difffile.c index f03dedb22..2ff30ddfa 100644 --- a/difffile.c +++ b/difffile.c @@ -1459,6 +1459,7 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zonedb, FILE* in, if(zonedb) prehash_zone(nsd->db, zonedb); #endif /* NSEC3 */ zonedb->is_changed = 1; + zonedb->is_updated = 1; if(nsd->db->udb) { assert(z.base); ZONE(&z)->is_changed = 1; @@ -1485,11 +1486,9 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zonedb, FILE* in, "Zone %s contents is different from master, " "starting AXFR. Transfer %s", zone_buf, log_buf); /* add/del failures in IXFR, get an AXFR */ - task_new_soainfo( - taskudb, last_task, zonedb, soainfo_gone); - } else if(taskudb) { - task_new_soainfo( - taskudb, last_task, zonedb, soainfo_ok); + diff_update_commit( + zone_buf, DIFF_INCONSISTENT, nsd, xfrfilenr); + exit(1); } if(ixfr_store) ixfr_store_finish(ixfr_store, nsd, log_buf); @@ -2200,6 +2199,7 @@ task_process_apply_xfr(struct nsd* nsd, udb_base* udb, udb_ptr *last_task, if(!apply_ixfr_for_zone(nsd, zone, df, nsd->options, udb, last_task, TASKLIST(task)->yesno)) { /* there is no branch for xfrd failed-update */ + task_new_soainfo(udb, last_task, zone, soainfo_gone); } fclose(df); diff --git a/difffile.h b/difffile.h index 5a9aab018..a15e68313 100644 --- a/difffile.h +++ b/difffile.h @@ -67,7 +67,8 @@ int add_RR(namedb_type* db, const dname_type* dname, enum soainfo_hint { soainfo_ok, - soainfo_gone + soainfo_gone, + soainfo_bad }; /* task udb structure */ diff --git a/doc/README b/doc/README index 7e2c970b2..7537b6b74 100644 --- a/doc/README +++ b/doc/README @@ -14,6 +14,7 @@ 3.5 ... Diagnosing NSD log entries 3.6 ... Interfaces 3.7 ... Tuning +3.8 ... Zone verification 4.0 Support and Feedback 4.1 ... Your Support @@ -767,6 +768,102 @@ entirely on the hardware. cpu-affinity options are supported on Linux and FreeBSD. +3.8 Zone verification + +NSD can be configured to verify a zone is correct before publishing it. This +feature is primarily aimed at fortifying DNSSEC in the DNS +notify/transfer-chain, but can be used to carry out any checks desired. + +An external verifier can be configured per zone. When a zone with verification +enabled is received or updated via an (incremental) zone transfer, it will be +submitted to the verifier for evaluation. If the verifier deems the updated +zone correct (indicated with exit status 0), the zone will be served. NSD will +discard the update and continue to serve the zone before the update if the +exit status of the verifier is non-zero. + +Verifier options can be configured globally in the "verify:" clause, or +specifically for a zone/pattern in the respective "zone:" and "pattern:" +clauses. The global values are applied by default. + +The zone can be provided to the verifier in two ways. + + 1. The complete zone can be fed to the standard input of the verifier. + + This modus operandi is enabled by default and can be configured + with the "verifier-feed-zone:" option. + + Examples for verifiers that read from the standard input are: + "ldns-verify-zone -V2" (-V2 to suppress copying to stdout) or + "validns -" (don't forget the dash (-) to read the zone from stdin). + + 2. The zone can be served to the verifier. + + This is disabled by default and can be enabled by configuring ip- + addresses, with the "ip-address:" option in the "verify:" clause, + on which the zone to be assessed will be served. Addresses can + contain a port number to override the default, which is 5347 by + default, but can be overridden with the "port:" option in the + verify clause. + + For example to validate the SOA of a zone example.com by querying, + with a certain DS record as the trust anchor (in file example.com.ds), + the "verifier:" option could have the following value: + "drill -S -k example.com.ds @localhost -p 5347 example.com SOA" + +A verifier is informed about the domain name of the zone to be verified and +the accessibility of the system submitting the zone via environment variables. + + VERIFY_ZONE + Domain name of the zone to be verified. + + VERIFY_ZONE_ON_STDIN + Contains "yes" if the zone is fed over standard input, + otherwise "no". + + VERIFY_IP_ADDRESSES + Contains a list of @s on which the zone + to be verified can be queried. + + VERIFY_IPV6_ADDRESS and VERIFY_IPV6_PORT + Contains the first configured IPv6 address and port. + + VERIFY_IPV4_ADDRESS and VERIFY_IPV4_PORT + Contains the first configured IPv4 address and port. + + VERIFY_IP_ADDRESS and VERIFY_PORT + Contains the first configured address and port. + IPv6 is preferred over IPv4. + +For each zone one verifier will be run at the same time, but when multiple +to-be-verified zones are received, multiple verifiers may be run +simultaneously. The number of verifiers that may be run simultaneously is +configured with the "verifier-count:" option in the "verify:" clause and +defaults to 1. + +The time a verifier may take can be configured with the "verifier-timeout:" +option in the "verify:" clause (to make the general default) or in the "zone:" +or "pattern:" clause to set it for a specific zone. When the time the verifier +takes exceeds the timeout value, the zone to be verified will be considered +bad. By default the value is 0, which means that the verifier may take as long +as it needs. + +To enable verification for all zones. + + verify: + enable: yes + verifier: + +To enable verification only for a specific zone. + + verify: + enable: yes + verify-zones: no + + zone: + name: example.com + verify-zone: yes + + 4.0 Support and Feedback NLnet Labs is committed to support NSD and its other software products on diff --git a/doc/RELNOTES b/doc/RELNOTES index 8b2947ed8..e4c15c163 100644 --- a/doc/RELNOTES +++ b/doc/RELNOTES @@ -1,8 +1,9 @@ NSD RELEASE NOTES -4.5.1 (upcoming) +4.6.0 (upcoming) ================ FEATURES: + - Port zone-verification from CreDNS to NSD4. BUG FIXES: - Fix static analyzer reports on ixfrcreate temp file. - Fixup wrong ixfrcreate fread return check. diff --git a/namedb.h b/namedb.h index 10e19c765..d9c8468a8 100644 --- a/namedb.h +++ b/namedb.h @@ -144,8 +144,10 @@ struct zone struct timespec mtime; /* time of last modification */ unsigned zonestatid; /* array index for zone stats */ unsigned is_secure : 1; /* zone uses DNSSEC */ - unsigned is_ok : 1; /* zone has not expired. */ - unsigned is_changed : 1; /* zone was changed by AXFR */ + unsigned is_ok : 1; /* zone has not expired */ + unsigned is_changed : 1; /* zone changes must be written to disk */ + unsigned is_updated : 1; /* zone was changed by XFR */ + unsigned is_bad : 1; /* zone failed verification */ } ATTR_PACKED; /* a RR in DNS */ diff --git a/nsd-checkconf.c b/nsd-checkconf.c index f8c0c77bb..a286b5cff 100644 --- a/nsd-checkconf.c +++ b/nsd-checkconf.c @@ -548,6 +548,32 @@ static void print_zone_content_elems(pattern_options_type* pat) printf("\tixfr-size: %u\n", (unsigned)pat->ixfr_size); if(!pat->create_ixfr_is_default) printf("\tcreate-ixfr: %s\n", pat->create_ixfr?"yes":"no"); + if(pat->verify_zone != VERIFY_ZONE_INHERIT) { + printf("\tverify-zone: "); + if(pat->verify_zone) { + printf("yes\n"); + } else { + printf("no\n"); + } + } + if(pat->verifier) { + printf("\tverifier:"); + for(char *const *s = pat->verifier; *s; s++) { + printf(" \"%s\"", *s); + } + printf("\n"); + } + if(pat->verifier_feed_zone != VERIFIER_FEED_ZONE_INHERIT) { + printf("\tverifier-feed-zone: "); + if(pat->verifier_feed_zone) { + printf("yes\n"); + } else { + printf("no\n"); + } + } + if(pat->verifier_timeout != VERIFIER_TIMEOUT_INHERIT) { + printf("\tverifier-timeout: %d\n", pat->verifier_timeout); + } } void @@ -691,6 +717,24 @@ config_test_print_server(nsd_options_type* opt) print_string_var("control-key-file:", opt->control_key_file); print_string_var("control-cert-file:", opt->control_cert_file); + printf("\nverify:\n"); + printf("\tenable: %s\n", opt->verify_enable?"yes":"no"); + for(ip = opt->verify_ip_addresses; ip; ip=ip->next) { + print_string_var("ip-address:", ip->address); + } + printf("\tport: %s\n", opt->verify_port); + printf("\tverify-zones: %s\n", opt->verify_zones?"yes":"no"); + if(opt->verifier) { + printf("\tverifier:"); + for(char **s = opt->verifier; *s; s++) { + printf(" \"%s\"", *s); + } + printf("\n"); + } + printf("\tverifier-count: %d\n", opt->verifier_count); + printf("\tverifier-feed-zone: %s\n", opt->verifier_feed_zone?"yes":"no"); + printf("\tverifier-timeout: %d\n", opt->verifier_timeout); + RBTREE_FOR(key, key_options_type*, opt->keys) { printf("\nkey:\n"); diff --git a/nsd.c b/nsd.c index 61e2c6f8a..ab38f445e 100644 --- a/nsd.c +++ b/nsd.c @@ -150,6 +150,62 @@ version(void) exit(0); } +static void +setup_verifier_environment(void) +{ + size_t i; + int ret, ip4, ip6; + char *buf, host[NI_MAXHOST], serv[NI_MAXSERV]; + size_t size, cnt = 0; + + /* allocate large enough buffer to hold a list of all ip addresses. + ((" " + INET6_ADDRSTRLEN + "@" + "65535") * n) + "\0" */ + size = ((INET6_ADDRSTRLEN + 1 + 5 + 1) * nsd.verify_ifs) + 1; + buf = xalloc(size); + + ip4 = ip6 = 0; + for(i = 0; i < nsd.verify_ifs; i++) { + ret = getnameinfo( + (struct sockaddr *)&nsd.verify_udp[i].addr.ai_addr, + nsd.verify_udp[i].addr.ai_addrlen, + host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST | NI_NUMERICSERV); + if(ret != 0) { + log_msg(LOG_ERR, "error in getnameinfo: %s", + gai_strerror(ret)); + continue; + } + buf[cnt++] = ' '; + cnt += strlcpy(&buf[cnt], host, size - cnt); + assert(cnt < size); + buf[cnt++] = '@'; + cnt += strlcpy(&buf[cnt], serv, size - cnt); + assert(cnt < size); +#ifdef INET6 + if (nsd.verify_udp[i].addr.ai_family == AF_INET6 && !ip6) { + setenv("VERIFY_IPV6_ADDRESS", host, 1); + setenv("VERIFY_IPV6_PORT", serv, 1); + setenv("VERIFY_IP_ADDRESS", host, 1); + setenv("VERIFY_IP_PORT", serv, 1); + ip6 = 1; + } else +#endif + if (!ip4) { + assert(nsd.verify_udp[i].addr.ai_family == AF_INET); + setenv("VERIFY_IPV4_ADDRESS", host, 1); + setenv("VERIFY_IPV4_PORT", serv, 1); + if (!ip6) { + setenv("VERIFY_IP_ADDRESS", host, 1); + setenv("VERIFY_IP_PORT", serv, 1); + } + ip4 = 1; + } + } + + setenv("VERIFY_IP_ADDRESSES", &buf[1], 1); + free(buf); +} + static void copyaddrinfo(struct nsd_addrinfo *dest, struct addrinfo *src) { @@ -260,7 +316,7 @@ figure_socket_servers( static void figure_default_sockets( struct nsd_socket **udp, struct nsd_socket **tcp, size_t *ifs, - const char *udp_port, const char *tcp_port, + const char *node, const char *udp_port, const char *tcp_port, const struct addrinfo *hints) { size_t i = 0, n = 1; @@ -291,23 +347,22 @@ figure_default_sockets( #ifdef INET6 if(hints->ai_family == AF_UNSPEC) { /* - * With IPv6 we'd like to open two separate sockets, - * one for IPv4 and one for IPv6, both listening to - * the wildcard address (unless the -4 or -6 flags are - * specified). + * With IPv6 we'd like to open two separate sockets, one for + * IPv4 and one for IPv6, both listening to the wildcard + * address (unless the -4 or -6 flags are specified). * - * However, this is only supported on platforms where - * we can turn the socket option IPV6_V6ONLY _on_. - * Otherwise we just listen to a single IPv6 socket - * and any incoming IPv4 connections will be - * automatically mapped to our IPv6 socket. + * However, this is only supported on platforms where we can + * turn the socket option IPV6_V6ONLY _on_. Otherwise we just + * listen to a single IPv6 socket and any incoming IPv4 + * connections will be automatically mapped to our IPv6 + * socket. */ #ifdef IPV6_V6ONLY int r; struct addrinfo *addrs[2] = { NULL, NULL }; - if((r = getaddrinfo(NULL, udp_port, &ai[0], &addrs[0])) == 0 && - (r = getaddrinfo(NULL, tcp_port, &ai[1], &addrs[1])) == 0) + if((r = getaddrinfo(node, udp_port, &ai[0], &addrs[0])) == 0 && + (r = getaddrinfo(node, tcp_port, &ai[1], &addrs[1])) == 0) { (*udp)[i].flags |= NSD_SOCKET_IS_OPTIONAL; (*udp)[i].fib = -1; @@ -335,9 +390,9 @@ figure_default_sockets( #endif /* INET6 */ *ifs = i + 1; - setup_socket(&(*udp)[i], NULL, udp_port, &ai[0]); + setup_socket(&(*udp)[i], node, udp_port, &ai[0]); figure_socket_servers(&(*udp)[i], NULL); - setup_socket(&(*tcp)[i], NULL, tcp_port, &ai[1]); + setup_socket(&(*tcp)[i], node, tcp_port, &ai[1]); figure_socket_servers(&(*tcp)[i], NULL); } @@ -396,7 +451,7 @@ static void figure_sockets( struct nsd_socket **udp, struct nsd_socket **tcp, size_t *ifs, struct ip_address_option *ips, - const char *udp_port, const char *tcp_port, + const char *node, const char *udp_port, const char *tcp_port, const struct addrinfo *hints) { size_t i = 0; @@ -409,7 +464,7 @@ figure_sockets( if(!ips) { figure_default_sockets( - udp, tcp, ifs, udp_port, tcp_port, hints); + udp, tcp, ifs, node, udp_port, tcp_port, hints); return; } @@ -884,6 +939,7 @@ main(int argc, char *argv[]) struct addrinfo hints; const char *udp_port = 0; const char *tcp_port = 0; + const char *verify_port = 0; const char *configfile = CONFIGFILE; @@ -1153,6 +1209,11 @@ main(int argc, char *argv[]) tcp_port = TCP_PORT; } } + if(nsd.options->verify_port != 0) { + verify_port = nsd.options->verify_port; + } else { + verify_port = VERIFY_PORT; + } #ifdef BIND8_STATS if(nsd.st.period == 0) { nsd.st.period = nsd.options->statistics; @@ -1340,7 +1401,13 @@ main(int argc, char *argv[]) resolve_interface_names(nsd.options); figure_sockets(&nsd.udp, &nsd.tcp, &nsd.ifs, - nsd.options->ip_addresses, udp_port, tcp_port, &hints); + nsd.options->ip_addresses, NULL, udp_port, tcp_port, &hints); + + if(nsd.options->verify_enable) { + figure_sockets(&nsd.verify_udp, &nsd.verify_tcp, &nsd.verify_ifs, + nsd.options->verify_ip_addresses, "localhost", verify_port, verify_port, &hints); + setup_verifier_environment(); + } /* Parse the username into uid and gid */ nsd.gid = getgid(); diff --git a/nsd.conf.sample.in b/nsd.conf.sample.in index de0f0b02e..93bc80bde 100644 --- a/nsd.conf.sample.in +++ b/nsd.conf.sample.in @@ -264,6 +264,38 @@ server: # Transfers over TLS (XoT). Default is "" (default verify locations). # tls-cert-bundle: "path/to/ca-bundle.pem" +verify: + # Enable zone verification. Default is no. + # enable: no + + # Port to answer verifier queries on. Default is 5347. + # port: 5347 + + # Interfaces to bind for zone verification (default are the localhost + # interfaces, usually 127.0.0.1 and ::1). To bind to to multiple IP + # addresses, list them one by one. Socket options cannot be specified + # for verify ip-address options. + # ip-address: 127.0.0.1 + # ip-address: 127.0.0.1@5347 + # ip-address: ::1 + + # Verify zones by default. Default is yes. + # verify-zones: yes + + # Command to execute for zone verification. + # verifier: ldns-verify-zone + # verifier: validns - + # verifier: drill -k @127.0.0.1 -p 5347 example.com SOA + + # Maximum number of verifiers to run concurrently. Default is 1. + # verifier-count: 1 + + # Feed updated zone to verifier over standard input. Default is yes. + # verifier-feed-zone: yes + + # Number of seconds before verifier is killed (0 is forever). + # verifier-timeout: 0 + # DNSTAP config section, if compiled with that # dnstap: # set this to yes and set one or more of dnstap-log-..-messages to yes. @@ -431,6 +463,23 @@ remote-control: # (which master to request from first, which slave to notify first). #include-pattern: "common-masters" + # Verify zone before publishing. + # Default is value of verify-zones in verify. + # verify-zone: yes + + # Command to execute for zone verification. + # Default is verifier in verify. + # verifier: ldns-verify-zone + # verifier: validns - + # verifier: drill -k @127.0.0.1 -p 5347 example.com SOA + + # Feed updated zone to verifier over standard input. + # Default is value of verifier-feed-zone in verify. + # verifier-feed-zone: yes + + # Number of seconds before verifier is killed (0 is forever). + # Default is verifier-timeout in verify. + # verifier-timeout: 0 # 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. diff --git a/nsd.h b/nsd.h index 6a5c12586..eea082a94 100644 --- a/nsd.h +++ b/nsd.h @@ -274,6 +274,16 @@ struct nsd /* UDP specific configuration (array size ifs) */ struct nsd_socket* udp; + /* Interfaces used for zone verification */ + size_t verify_ifs; + struct nsd_socket *verify_tcp; + struct nsd_socket *verify_udp; + + struct zone *next_zone_to_verify; + size_t verifier_count; /* Number of active verifiers */ + size_t verifier_limit; /* Maximum number of active verifiers */ + struct verifier *verifiers; + edns_data_type edns_ipv4; #if defined(INET6) edns_data_type edns_ipv6; diff --git a/options.c b/options.c index c1e0ee79e..65e1a0c9d 100644 --- a/options.c +++ b/options.c @@ -144,6 +144,16 @@ nsd_options_create(region_type* region) opt->server_cert_file = CONFIGDIR"/nsd_server.pem"; opt->control_key_file = CONFIGDIR"/nsd_control.key"; opt->control_cert_file = CONFIGDIR"/nsd_control.pem"; + + opt->verify_enable = 0; + opt->verify_ip_addresses = NULL; + opt->verify_port = VERIFY_PORT; + opt->verify_zones = 1; + opt->verifier = NULL; + opt->verifier_count = 1; + opt->verifier_feed_zone = 1; + opt->verifier_timeout = 0; + return opt; } @@ -891,6 +901,11 @@ pattern_options_create(region_type* region) p->ixfr_number_is_default = 1; p->create_ixfr = 0; p->create_ixfr_is_default = 1; + p->verify_zone = VERIFY_ZONE_INHERIT; + p->verifier = NULL; + p->verifier_feed_zone = VERIFIER_FEED_ZONE_INHERIT; + p->verifier_timeout = VERIFIER_TIMEOUT_INHERIT; + return p; } @@ -945,6 +960,16 @@ pattern_options_remove(struct nsd_options* opt, const char* name) acl_list_delete(opt->region, p->allow_query); acl_list_delete(opt->region, p->outgoing_interface); + if(p->verifier != NULL) { + size_t n = 0; + while(p->verifier[n] != NULL) { + region_recycle( + opt->region, p->verifier[n], strlen(p->verifier[n]) + 1); + n++; + } + region_recycle(opt->region, p->verifier, (n + 1) * sizeof(char *)); + } + region_recycle(opt->region, p, sizeof(struct pattern_options)); } @@ -1278,6 +1303,49 @@ unmarshal_acl_list(region_type* r, struct buffer* b) return list; } +static void +marshal_strv(struct buffer* b, char **strv) +{ + uint32_t i, n; + + assert(b != NULL); + + if (strv == NULL) { + marshal_u32(b, 0); + return; + } + for(n = 0; strv[n]; n++) { + /* do nothing */ + } + marshal_u32(b, n); + for(i = 0; strv[i] != NULL; i++) { + marshal_str(b, strv[i]); + } + marshal_u8(b, 0); +} + +static char ** +unmarshal_strv(region_type* r, struct buffer* b) +{ + uint32_t i, n; + char **strv; + + assert(r != NULL); + assert(b != NULL); + + if ((n = unmarshal_u32(b)) == 0) { + return NULL; + } + strv = region_alloc_zero(r, (n + 1) * sizeof(char *)); + for(i = 0; i <= n; i++) { + strv[i] = unmarshal_str(r, b); + } + assert(i == n); + assert(strv[i] == NULL); + + return strv; +} + void pattern_options_marshal(struct buffer* b, struct pattern_options* p) { @@ -1318,6 +1386,10 @@ pattern_options_marshal(struct buffer* b, struct pattern_options* p) marshal_u8(b, p->ixfr_number_is_default); marshal_u8(b, p->create_ixfr); marshal_u8(b, p->create_ixfr_is_default); + marshal_u8(b, p->verify_zone); + marshal_strv(b, p->verifier); + marshal_u8(b, p->verifier_feed_zone); + marshal_u32(b, p->verifier_timeout); } struct pattern_options* @@ -1361,6 +1433,10 @@ pattern_options_unmarshal(region_type* r, struct buffer* b) p->ixfr_number_is_default = unmarshal_u8(b); p->create_ixfr = unmarshal_u8(b); p->create_ixfr_is_default = unmarshal_u8(b); + p->verify_zone = unmarshal_u8(b); + p->verifier = unmarshal_strv(r, b); + p->verifier_feed_zone = unmarshal_u8(b); + p->verifier_timeout = unmarshal_u32(b); return p; } diff --git a/options.h b/options.h index a69c068df..ee4dc74ba 100644 --- a/options.h +++ b/options.h @@ -31,6 +31,10 @@ typedef struct key_options key_options_type; typedef struct tls_auth_options tls_auth_options_type; 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) + /* * Options global for nsd. */ @@ -179,6 +183,22 @@ struct nsd_options { char *cookie_secret; /** path to cookie secret store */ char const* cookie_secret_file; + /** enable verify */ + int verify_enable; + /** list of ip addresses used to serve zones for verification */ + struct ip_address_option* verify_ip_addresses; + /** default port 5347 */ + char *verify_port; + /** verify zones by default */ + int verify_zones; + /** default command to verify zones with */ + char **verifier; + /** maximum number of verifiers that may run simultaneously */ + int verifier_count; + /** whether or not to feed the zone to the verifier over stdin */ + uint8_t verifier_feed_zone; + /** maximum number of seconds that a verifier may take */ + uint32_t verifier_timeout; region_type* region; }; @@ -266,6 +286,10 @@ struct pattern_options { uint8_t ixfr_number_is_default; uint8_t create_ixfr; uint8_t create_ixfr_is_default; + uint8_t verify_zone; + char **verifier; + uint8_t verifier_feed_zone; + int32_t verifier_timeout; } ATTR_PACKED; #define PATTERN_IMPLICIT_MARKER "_implicit_" diff --git a/popen3.c b/popen3.c index d457ef308..dcadbbc45 100644 --- a/popen3.c +++ b/popen3.c @@ -23,16 +23,15 @@ static void close_pipe(int fds[2]) } pid_t popen3(char *const *command, - FILE **finptr, - FILE **foutptr, - FILE **ferrptr) + int *fdinptr, + int *fdoutptr, + int *fderrptr) { int err = 0; int fdin[] = { -1, -1 }; int fdout[] = { -1, -1 }; int fderr[] = { -1, -1 }; int fdsig[] = { -1, -1 }; - FILE *fin, *fout, *ferr; pid_t pid; ssize_t discard; @@ -41,21 +40,13 @@ pid_t popen3(char *const *command, return -1; } - fin = fout = ferr = NULL; - - if(finptr != NULL && (pipe(fdin) == -1 || - (fin = fdopen(fdin[1], "w")) == NULL)) - { + if(fdinptr != NULL && pipe(fdin) == -1) { goto error; } - if(foutptr != NULL && (pipe(fdout) == -1 || - (fout = fdopen(fdout[0], "r")) == NULL)) - { + if(fdoutptr != NULL && pipe(fdout) == -1) { goto error; } - if(ferrptr != NULL && (pipe(fderr) == -1 || - (ferr = fdopen(fderr[0], "r")) == NULL)) - { + if(fderrptr != NULL && pipe(fderr) == -1) { goto error; } if(pipe(fdsig) == -1 || @@ -70,7 +61,7 @@ pid_t popen3(char *const *command, case -1: /* error */ goto error; case 0: /* child */ - if(ferrptr != NULL) { + if(fderrptr != NULL) { if(dup2(fderr[1], 2) == -1) { goto error_dup2; } @@ -78,7 +69,7 @@ pid_t popen3(char *const *command, } else { close(2); } - if(foutptr != NULL) { + if(fdoutptr != NULL) { if(dup2(fdout[1], 1) == -1) { goto error_dup2; } @@ -86,7 +77,7 @@ pid_t popen3(char *const *command, } else { close(1); } - if(finptr != NULL) { + if(fdinptr != NULL) { if(dup2(fdin[0], 0) == -1) { goto error_dup2; } @@ -134,17 +125,17 @@ pid_t popen3(char *const *command, break; } - if(finptr != NULL) { + if(fdinptr != NULL) { close(fdin[0]); - *finptr = fin; + *fdinptr = fdin[1]; } - if(foutptr != NULL) { + if(fdoutptr != NULL) { close(fdout[1]); - *foutptr = fout; + *fdoutptr = fdout[0]; } - if(ferrptr != NULL) { + if(fderrptr != NULL) { close(fderr[1]); - *ferrptr = ferr; + *fderrptr = fderr[0]; } return pid; @@ -152,19 +143,6 @@ pid_t popen3(char *const *command, error: err = errno; - if(fin != NULL) { - fclose(fin); - fdin[1] = -1; - } - if(fout != NULL) { - fclose(fout); - fdout[0] = -1; - } - if(ferr != NULL) { - fclose(ferr); - fderr[0] = -1; - } - close_pipe(fdin); close_pipe(fdout); close_pipe(fderr); diff --git a/popen3.h b/popen3.h index 7c09c42e2..ed95b500f 100644 --- a/popen3.h +++ b/popen3.h @@ -20,8 +20,8 @@ * of the pointers will have been set. */ pid_t popen3(char *const *command, - FILE **finptr, - FILE **foutptr, - FILE **ferrptr); + int *fdinptr, + int *fdoutptr, + int *fderrptr); #endif /* _POPEN3_H_ */ diff --git a/server.c b/server.c index dafd211fa..284ed4c03 100644 --- a/server.c +++ b/server.c @@ -85,6 +85,7 @@ #ifdef USE_DNSTAP #include "dnstap/dnstap_collector.h" #endif +#include "verify.h" #define RELOAD_SYNC_TIMEOUT 25 /* seconds */ @@ -1378,6 +1379,15 @@ server_init(struct nsd *nsd) nsd->reuseport = 0; } + /* open server interface ports for verifiers */ + for(i = 0; i < nsd->verify_ifs; i++) { + if(open_udp_socket(nsd, &nsd->verify_udp[i], NULL) == -1 || + open_tcp_socket(nsd, &nsd->verify_tcp[i], NULL) == -1) + { + return -1; + } + } + return 0; } @@ -2246,6 +2256,8 @@ reload_do_stats(int cmdfd, struct nsd* nsd, udb_ptr* last) } #endif /* BIND8_STATS */ +void server_verify(struct nsd *nsd, int cmdsocket); + /* * Reload the database, stop parent, re-fork children and continue. * as server_main. @@ -2259,6 +2271,8 @@ server_reload(struct nsd *nsd, region_type* server_region, netio_type* netio, int ret; udb_ptr last_task; struct sigaction old_sigchld, ign_sigchld; + struct radnode* node; + zone_type* zone; /* ignore SIGCHLD from the previous server_main that used this pid */ memset(&ign_sigchld, 0, sizeof(ign_sigchld)); ign_sigchld.sa_handler = SIG_IGN; @@ -2300,6 +2314,32 @@ server_reload(struct nsd *nsd, region_type* server_region, netio_type* netio, server_zonestat_switch(nsd); #endif + if(nsd->options->verify_enable) { + /* spin-up server and execute verifiers for each zone */ + server_verify(nsd, cmdsocket); + } + + for(node = radix_first(nsd->db->zonetree); + node != NULL; + node = radix_next(node)) + { + enum soainfo_hint hint = soainfo_ok; + zone = (zone_type *)node->elem; + if(zone->is_updated) { + if(zone->is_bad) { + nsd->mode = NSD_RELOAD_FAILED; + hint = soainfo_bad; + } + task_new_soainfo( + nsd->task[nsd->mytask], &last_task, zone, hint); + zone->is_updated = 0; + } + } + + if(nsd->mode == NSD_RELOAD_FAILED) { + exit(NSD_RELOAD_FAILED); + } + /* listen for the signals of failed children again */ sigaction(SIGCHLD, &old_sigchld, NULL); #ifdef USE_DNSTAP @@ -2951,6 +2991,115 @@ add_tcp_handler( data->event_added = 1; } +/* + * Serve DNS request to verifiers (short-lived) + */ +void server_verify(struct nsd *nsd, int cmdsocket) +{ + size_t size = 0; + struct event cmd_event, sigchld_event; + struct zone *zone; + + assert(nsd != NULL); + + zone = verify_next_zone(nsd, NULL); + if(zone == NULL) + return; + + nsd->server_region = region_create(xalloc, free); + nsd->event_base = nsd_child_event_base(); + + nsd->next_zone_to_verify = zone; + nsd->verifier_count = 0; + nsd->verifier_limit = nsd->options->verifier_count; + size = sizeof(struct verifier) * nsd->verifier_limit; + nsd->verifiers = region_alloc_zero(nsd->server_region, size); + + for(size_t i = 0; i < nsd->verifier_limit; i++) { + nsd->verifiers[i].nsd = nsd; + nsd->verifiers[i].zone = NULL; + nsd->verifiers[i].pid = -1; + nsd->verifiers[i].output_stream.fd = -1; + nsd->verifiers[i].output_stream.priority = LOG_INFO; + nsd->verifiers[i].error_stream.fd = -1; + nsd->verifiers[i].error_stream.priority = LOG_ERR; + } + + event_set(&cmd_event, cmdsocket, EV_READ|EV_PERSIST, verify_handle_command, nsd); + if(event_base_set(nsd->event_base, &cmd_event) != 0 || + event_add(&cmd_event, NULL) != 0) + { + log_msg(LOG_ERR, "verify: could not add command event"); + goto fail; + } + + event_set(&sigchld_event, SIGCHLD, EV_SIGNAL|EV_PERSIST, verify_handle_exit, nsd); + if(event_base_set(nsd->event_base, &sigchld_event) != 0 || + event_add(&sigchld_event, NULL) != 0) + { + log_msg(LOG_ERR, "verify: could not add SIGCHLD event"); + goto fail; + } + + memset(msgs, 0, sizeof(msgs)); + for (int i = 0; i < NUM_RECV_PER_SELECT; i++) { + queries[i] = query_create(nsd->server_region, + compressed_dname_offsets, + compression_table_size, compressed_dnames); + query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0); + iovecs[i].iov_base = buffer_begin(queries[i]->packet); + iovecs[i].iov_len = buffer_remaining(queries[i]->packet); + msgs[i].msg_hdr.msg_iov = &iovecs[i]; + msgs[i].msg_hdr.msg_iovlen = 1; + msgs[i].msg_hdr.msg_name = &queries[i]->addr; + msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen; + } + + for (size_t i = 0; i < nsd->verify_ifs; i++) { + struct udp_handler_data *data; + data = region_alloc_zero( + nsd->server_region, sizeof(*data)); + add_udp_handler(nsd, &nsd->verify_udp[i], data); + } + + tcp_accept_handler_count = nsd->verify_ifs; + tcp_accept_handlers = region_alloc_array(nsd->server_region, + nsd->verify_ifs, sizeof(*tcp_accept_handlers)); + + for (size_t i = 0; i < nsd->verify_ifs; i++) { + struct tcp_accept_handler_data *data; + data = &tcp_accept_handlers[i]; + memset(data, 0, sizeof(*data)); + add_tcp_handler(nsd, &nsd->verify_tcp[i], data); + } + + while(nsd->next_zone_to_verify != NULL && + nsd->verifier_count < nsd->verifier_limit) + { + verify_zone(nsd, nsd->next_zone_to_verify); + nsd->next_zone_to_verify + = verify_next_zone(nsd, nsd->next_zone_to_verify); + } + + /* short-lived main loop */ + event_base_dispatch(nsd->event_base); + + /* remove command and exit event handlers */ + event_del(&sigchld_event); + event_del(&cmd_event); + + assert(nsd->next_zone_to_verify == NULL); + assert(nsd->verifier_count == 0); +fail: + event_base_free(nsd->event_base); + region_destroy(nsd->server_region); + + nsd->event_base = NULL; + nsd->server_region = NULL; + nsd->verifier_limit = 0; + nsd->verifiers = NULL; +} + /* * Serve DNS requests. */ diff --git a/verify.c b/verify.c new file mode 100644 index 000000000..d31e3881d --- /dev/null +++ b/verify.c @@ -0,0 +1,564 @@ +/* + * verify.c -- running verifiers and serving the zone to be verified. + * + * Copyright (c) 2012-2020, NLnet Labs. All rights reserved. + * + * See LICENSE for the license. + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYSLOG_H +#include +#endif /* HAVE_SYSLOG_H */ +#include +#include +#include + +#include "region-allocator.h" +#include "namedb.h" +#include "nsd.h" +#include "options.h" +#include "difffile.h" +#include "verify.h" +#include "popen3.h" + +struct zone *verify_next_zone(struct nsd *nsd, struct zone *zone) +{ + int verify; + struct radnode *node; + + if(zone != NULL) { + node = radix_next(zone->node); + } else { + node = radix_first(nsd->db->zonetree); + } + + while(node != NULL) { + zone = (struct zone *)node->elem; + verify = zone->opts->pattern->verify_zone; + if(verify == VERIFY_ZONE_INHERIT) { + verify = nsd->options->verify_zones; + } + if(verify && zone->is_updated) { + return zone; + } + node = radix_next(node); + } + + return NULL; +} + +/* + * Log verifier output on STDOUT and STDERR. Lines longer than LOGLINELEN are + * split over multiple lines. Line-breaks are indicated in the log with "...". + */ +static void verify_handle_stream(int fd, short event, void *arg) +{ + size_t len; + ssize_t cnt; + char *end, *ptr; + const char *fmt; + struct verifier *verifier; + struct verifier_stream *stream; + + assert(event & EV_READ); + assert(arg != NULL); + + verifier = (struct verifier *)arg; + if (fd == verifier->output_stream.fd) { + stream = &verifier->output_stream; + } else { + assert(fd == verifier->error_stream.fd); + stream = &verifier->error_stream; + } + + assert(stream->fd != -1); + + do { + /* (re)fill buffer */ + errno = 0; + end = NULL; + cnt = read(stream->fd, + stream->buf + stream->cnt, + LOGBUFSIZE - stream->cnt); + if(cnt > 0) { + stream->buf[stream->cnt + (size_t)cnt] = '\0'; + end = strchr(stream->buf + stream->cnt, '\n'); + stream->cnt += (size_t)cnt; + if(end == NULL && stream->cnt == LOGBUFSIZE) { + end = stream->buf + stream->cnt; + } + } else if((cnt == 0) || + (cnt == -1 && !(errno == EAGAIN || errno == EINTR))) + { + /* flush what is available */ + end = stream->cnt > 0 ? stream->buf + stream->cnt : NULL; + } + while(end != NULL) { + ptr = stream->buf; + len = end - ptr; + while(len > LOGLINELEN) { + fmt = stream->cut ? ".. %.*s .." : "%.*s .."; + stream->cut = 1; /* split line */ + log_msg(stream->priority, fmt, LOGLINELEN, ptr); + ptr += LOGLINELEN; + len -= LOGLINELEN; + } + assert(len <= LOGLINELEN); + fmt = stream->cut ? ".. %.*s" : "%.*s"; + if(*end == '\n' || cnt <= 0) { + stream->cut = 0; + /* flush buffer unless more data is expected */ + *end = '\0'; + log_msg(stream->priority, fmt, len, ptr); + ptr += len; + len = 0; + } + if(cnt <= 0) { + break; + } + /* move data to head of buffer */ + len = (ptr - stream->buf); + memmove(stream->buf, ptr + 1, (stream->cnt - len) - 1); + stream->cnt -= len + 1; + stream->buf[stream->cnt] = '\0'; + end = stream->cnt > 0 ? strchr(stream->buf, '\n') : NULL; + } + } while(cnt > 0); + + if(cnt <= 0 && !(errno == EAGAIN || errno == EINTR)) { + event_del(&stream->event); + close(stream->fd); + stream->fd = -1; + } +} + +static void kill_verifier(struct verifier *verifier) +{ + assert(verifier != NULL); + assert(verifier->zone != NULL); + + if(kill(verifier->pid, SIGTERM) == -1) { + log_msg(LOG_ERR, "verify: cannot kill verifier for " + "zone %s (pid %d): %s", + verifier->zone->opts->name, + verifier->pid, + strerror(errno)); + } +} + +static void close_verifier(struct verifier *verifier) +{ + /* unregister events and close streams (in that order) */ + if(verifier->timeout.tv_sec > 0) { + event_del(&verifier->timeout_event); + verifier->timeout.tv_sec = 0; + verifier->timeout.tv_usec = 0; + } + + if(verifier->zone_feed.fh != NULL) { + event_del(&verifier->zone_feed.event); + fclose(verifier->zone_feed.fh); + verifier->zone_feed.fh = NULL; + region_destroy(verifier->zone_feed.region); + } + + if (verifier->output_stream.fd != -1) { + verify_handle_stream( + verifier->output_stream.fd, EV_READ, verifier); + verifier->output_stream.fd = -1; + } + + if (verifier->error_stream.fd != -1) { + verify_handle_stream( + verifier->error_stream.fd, EV_READ, verifier); + verifier->error_stream.fd = -1; + } + + verifier->zone->is_ok = verifier->was_ok; + verifier->pid = -1; + verifier->zone = NULL; +} + +/* + * Feed zone to verifier over STDIN as it becomes available. + */ +static void verify_handle_feed(int fd, short event, void *arg) +{ + struct verifier *verifier; + struct rr *rr; + + (void)fd; + assert(event == EV_WRITE); + assert(arg != NULL); + + verifier = (struct verifier *)arg; + if((rr = zone_rr_iter_next(&verifier->zone_feed.rriter)) != NULL) { + print_rr(verifier->zone_feed.fh, + verifier->zone_feed.rrprinter, + rr, + verifier->zone_feed.region, + verifier->zone_feed.buffer); + } else { + event_del(&verifier->zone_feed.event); + fclose(verifier->zone_feed.fh); + verifier->zone_feed.fh = NULL; + region_destroy(verifier->zone_feed.region); + } +} + +/* + * This handler will be called when a verifier-timeout alarm goes off. It just + * kills the verifier. server_verify_zones will make sure the zone will be + * considered bad. + */ +void verify_handle_timeout(int fd, short event, void *arg) +{ + struct verifier *verifier; + + (void)fd; + assert(event & EV_TIMEOUT); + assert(arg != NULL); + + verifier = (struct verifier *)arg; + verifier->zone->is_bad = 1; + + log_msg(LOG_ERR, "verify: verifier for zone %s (pid %d) timed out", + verifier->zone->opts->name, verifier->pid); + + /* kill verifier, process reaped by exit handler */ + kill_verifier(verifier); +} + +/* + * Reap process and update status of respective zone based on the exit code + * of a verifier. Everything from STDOUT and STDERR still available is read and + * written to the log as it might contain valuable information. + * + * NOTE: A timeout might have caused the verifier to be terminated. + */ +void verify_handle_exit(int sig, short event, void *arg) +{ + int wstatus; + pid_t pid; + struct nsd *nsd; + + assert(sig == SIGCHLD); + assert(event & EV_SIGNAL); + assert(arg != NULL); + + nsd = (struct nsd *)arg; + + while(((pid = waitpid(-1, &wstatus, WNOHANG)) == -1 && errno == EINTR) + || (pid > 0)) + { + struct verifier *verifier = NULL; + + for(size_t i = 0; !verifier && i < nsd->verifier_limit; i++) { + if(nsd->verifiers[i].zone != NULL && + nsd->verifiers[i].pid == pid) + { + verifier = &nsd->verifiers[i]; + } + } + + if(verifier == NULL) { + continue; + } + + if(!WIFEXITED(wstatus)) { + log_msg(LOG_ERR, "verify: verifier for zone %s " + "(pid %d) exited abnormally", + verifier->zone->opts->name, pid); + } else { + int priority = LOG_INFO; + int status = WEXITSTATUS(wstatus); + if(status != 0) { + priority = LOG_ERR; + verifier->zone->is_bad = 1; + } + log_msg(priority, "verify: verifier for zone %s " + "(pid %d) exited with %d", + verifier->zone->opts->name, pid, status); + } + + close_verifier(verifier); + nsd->verifier_count--; + } + + while(nsd->mode == NSD_RUN && + nsd->verifier_count < nsd->verifier_limit && + nsd->next_zone_to_verify != NULL) + { + verify_zone(nsd, nsd->next_zone_to_verify); + nsd->next_zone_to_verify + = verify_next_zone(nsd, nsd->next_zone_to_verify); + } + + if(nsd->next_zone_to_verify == NULL && nsd->verifier_count == 0) { + event_base_loopbreak(nsd->event_base); + return; + } +} + +/* + * A parent may be terminated (by the NSD_QUIT signal (nsdc stop command)). + * When a reload server process is running, the parent will then send a + * NSD_QUIT command to that server. This handler makes sure that this command + * is not neglected and that the reload server process will exit (gracefully). + */ +void +verify_handle_command(int fd, short event, void *arg) +{ + struct nsd *nsd = (struct nsd *)arg; + int len; + sig_atomic_t mode; + + assert(nsd != NULL); + assert(event & (EV_READ | EV_CLOSED)); + + if((len = read(fd, &mode, sizeof(mode))) == -1) { + log_msg(LOG_ERR, "verify: verify_handle_command: read: %s", + strerror(errno)); + return; + } else if(len == 0) { + log_msg(LOG_INFO, "verify: command channel closed"); + mode = NSD_QUIT; + } else if(mode != NSD_QUIT) { + log_msg(LOG_ERR, "verify: bad command: %d", (int)mode); + return; + } + + nsd->mode = mode; + + if(nsd->verifier_count == 0) { + event_base_loopbreak(nsd->event_base); + return; /* exit early if no verifiers are executing */ + } + + /* kill verifiers, processes reaped elsewhere */ + for(size_t i = 0; i < nsd->verifier_limit; i++) { + if(nsd->verifiers[i].zone != NULL) { + kill_verifier(&nsd->verifiers[i]); + } + } +} + +/* + * A verifier is executed for the specified zone (if a verifier is configured + * and the zone has not been verified before). If one of the verifiers exits + * with non-zero, the zone is marked bad and nsd drops the zone update and + * reloads again. + */ +void verify_zone(struct nsd *nsd, struct zone *zone) +{ + struct verifier *verifier = NULL; + int32_t timeout; + char **command; + FILE *fin; + int fdin, fderr, fdout, flags; + + assert(nsd != NULL); + assert(nsd->verifier_count < nsd->verifier_limit); + assert(zone != NULL); + + fin = NULL; + fdin = fdout = fderr = -1; + + /* search for available verifier slot */ + for(size_t i = 0; i < nsd->verifier_limit && !verifier; i++) { + if(nsd->verifiers[i].zone == NULL) { + verifier = &nsd->verifiers[i]; + } + } + + assert(verifier != NULL); + + if(zone->opts->pattern->verifier != NULL) { + command = zone->opts->pattern->verifier; + } else if (nsd->options->verifier != NULL) { + command = nsd->options->verifier; + } else { + log_msg(LOG_ERR, "verify: no verifier for zone %s", + zone->opts->name); + return; + } + + if(zone->opts->pattern->verifier_timeout + != VERIFIER_TIMEOUT_INHERIT) + { + timeout = zone->opts->pattern->verifier_timeout; + } else { + timeout = nsd->options->verifier_timeout; + } + + if(zone->opts->pattern->verifier_feed_zone + != VERIFIER_FEED_ZONE_INHERIT) + { + fdin = zone->opts->pattern->verifier_feed_zone ? -2 : -1; + } else { + fdin = nsd->options->verifier_feed_zone ? -2 : -1; + } + + assert(timeout >= 0); + + setenv("VERIFY_ZONE", zone->opts->name, 1); + setenv("VERIFY_ZONE_ON_STDIN", fdin == -2 ? "yes" : "no", 1); + + verifier->pid = popen3( + command, fdin == -2 ? &fdin : NULL, &fdout, &fderr); + if(verifier->pid == -1) { + log_msg(LOG_ERR, "verify: could not start verifier for zone " + "%s: %s", zone->opts->name, strerror(errno)); + goto fail_popen3; + } + flags = fcntl(fderr, F_GETFL, 0); + if (fcntl(fderr, F_SETFL, flags | O_NONBLOCK) == -1) { + log_msg(LOG_ERR, "verify: fcntl(stderr, ..., O_NONBLOCK) for " + "zone %s: %s", + zone->opts->name, strerror(errno)); + goto fail_fcntl; + } + flags = fcntl(fdout, F_GETFL, 0); + if(fcntl(fdout, F_SETFL, flags | O_NONBLOCK) == -1) { + log_msg(LOG_ERR, "verify: fcntl(stdout, ..., O_NONBLOCK) for " + "zone %s: %s", + zone->opts->name, strerror(errno)); + goto fail_fcntl; + } + if (fdin >= 0) { + if ((fin = fdopen(fdin, "w")) == NULL) { + log_msg(LOG_ERR, "verify: fdopen(stdin, ...) for " + "zone %s: %s", + zone->opts->name, strerror(errno)); + goto fail_fcntl; + } + /* write unbuffered */ + setbuf(fin, NULL); + } + + verifier->zone = zone; + verifier->was_ok = zone->is_ok; + + unsetenv("VERIFY_ZONE"); + unsetenv("VERIFY_ZONE_ON_STDIN"); + + verifier->error_stream.fd = fderr; + verifier->error_stream.cnt = 0; + verifier->error_stream.buf[0] = '\0'; + event_set(&verifier->error_stream.event, + verifier->error_stream.fd, + EV_READ|EV_PERSIST, + verify_handle_stream, + verifier); + event_base_set(nsd->event_base, &verifier->error_stream.event); + if(event_add(&verifier->error_stream.event, NULL) != 0) { + log_msg(LOG_ERR, "verify: could not add error event for " + "zone %s", zone->opts->name); + goto fail_stderr; + } + + verifier->output_stream.fd = fdout; + verifier->output_stream.cnt = 0; + verifier->output_stream.buf[0] = '\0'; + event_set(&verifier->output_stream.event, + verifier->output_stream.fd, + EV_READ|EV_PERSIST, + verify_handle_stream, + verifier); + event_base_set(nsd->event_base, &verifier->output_stream.event); + if(event_add(&verifier->output_stream.event, NULL) != 0) { + log_msg(LOG_ERR, "verify: could not add output event for " + "zone %s", zone->opts->name); + goto fail_stdout; + } + + if(fin != NULL) { + verifier->zone_feed.fh = fin; + + zone_rr_iter_init(&verifier->zone_feed.rriter, zone); + + verifier->zone_feed.rrprinter + = create_pretty_rr(nsd->server_region); + verifier->zone_feed.region + = region_create(xalloc, free); + verifier->zone_feed.buffer + = buffer_create(nsd->server_region, MAX_RDLENGTH); + + event_set(&verifier->zone_feed.event, + fileno(verifier->zone_feed.fh), + EV_WRITE|EV_PERSIST, + &verify_handle_feed, + verifier); + event_base_set(nsd->event_base, &verifier->zone_feed.event); + if(event_add(&verifier->zone_feed.event, NULL) != 0) { + log_msg(LOG_ERR, "verify: could not add input event " + "for zone %s", zone->opts->name); + goto fail_stdin; + } + } + + if(timeout > 0) { + verifier->timeout.tv_sec = timeout; + verifier->timeout.tv_usec = 0; + event_set(&verifier->timeout_event, + -1, + EV_TIMEOUT, + verify_handle_timeout, + verifier); + event_base_set(nsd->event_base, &verifier->timeout_event); + if(event_add(&verifier->timeout_event, &verifier->timeout) != 0) { + log_msg(LOG_ERR, "verify: could not add timeout event " + "for zone %s", zone->opts->name); + goto fail_timeout; + } + + log_msg(LOG_INFO, "verify: started verifier for zone %s " + "(pid %d), timeout is %d seconds", + zone->opts->name, verifier->pid, timeout); + } else { + log_msg(LOG_INFO, "verify: started verifier for zone %s " + "(pid %d)", zone->opts->name, verifier->pid); + } + + zone->is_ok = 1; + nsd->verifier_count++; + return; + +fail_timeout: + verifier->timeout.tv_sec = 0; + verifier->timeout.tv_usec = 0; + if(fin != NULL) { + event_del(&verifier->zone_feed.event); + } +fail_stdin: + verifier->zone_feed.fh = NULL; + event_del(&verifier->output_stream.event); +fail_stdout: + verifier->output_stream.fd = -1; + event_del(&verifier->error_stream.event); +fail_stderr: + verifier->error_stream.fd = -1; +fail_fcntl: + kill_verifier(verifier); + if(fin != NULL) { + fclose(fin); + } else if (fdin >= 0) { + close(fdin); + } + close(fdout); + close(fderr); +fail_popen3: + zone->is_bad = 1; + verifier->pid = -1; + verifier->zone = NULL; +} diff --git a/verify.h b/verify.h new file mode 100644 index 000000000..165d88c62 --- /dev/null +++ b/verify.h @@ -0,0 +1,77 @@ +/* + * verify.h + * + * Copyright (c) 2020, NLnet Labs. All rights reserved. + * + * See LICENSE for the license. + */ +#ifndef _VERIFY_H_ +#define _VERIFY_H_ + +#ifndef USE_MINI_EVENT +# ifdef HAVE_EVENT_H +# include +# else +# include +# include "event2/event_struct.h" +# include "event2/event_compat.h" +# endif +#else +# include "mini_event.h" +#endif + +/* + * Track position in zone to feed verifier more data as the input descriptor + * becomes available. + */ +struct verifier_zone_feed { + FILE *fh; + struct event event; + zone_rr_iter_type rriter; + struct state_pretty_rr *rrprinter; + struct region *region; + struct buffer *buffer; +}; + +/* 40 is (estimated) space already used on each logline. + * (time, pid, priority, etc) + */ +#define LOGLINELEN (MAXSYSLOGMSGLEN-40) + +#define LOGBUFSIZE (LOGLINELEN * 2) + +/* + * STDOUT and STDERR are logged per line. Lines that exceed LOGLINELEN, are + * split over multiple entries. Line breaks are indicated with "..." in the log + * before and after the break. + */ +struct verifier_stream { + int fd; + struct event event; + int priority; + int cut; + char buf[LOGBUFSIZE+1]; + size_t cnt; +}; + +struct verifier { + struct nsd *nsd; + struct zone *zone; + pid_t pid; + int was_ok; + struct timeval timeout; + struct event timeout_event; + struct verifier_zone_feed zone_feed; + struct verifier_stream output_stream; + struct verifier_stream error_stream; +}; + +struct zone *verify_next_zone(struct nsd *nsd, struct zone *zone); + +void verify_zone(struct nsd *nsd, struct zone *zone); + +void verify_handle_exit(int sig, short event, void *arg); + +void verify_handle_command(int fd, short event, void *arg); + +#endif /* _VERIFY_H_ */ diff --git a/xfrd.c b/xfrd.c index 778e85020..ee9bdba20 100644 --- a/xfrd.c +++ b/xfrd.c @@ -573,16 +573,16 @@ xfrd_process_soa_info_task(struct task_list_d* task) xfrd_xfr_type* xfr; xfrd_xfr_type* prev_xfr; enum soainfo_hint hint; - time_t before; + time_t before, now; DEBUG(DEBUG_IPC,1, (LOG_INFO, "xfrd: process SOAINFO %s", dname_to_string(task->zname, 0))); zone = (xfrd_zone_type*)rbtree_search(xfrd->zones, task->zname); hint = (enum soainfo_hint)task->yesno; if(task->size <= sizeof(struct task_list_d)+dname_total_size( task->zname)+sizeof(uint32_t)*6 + sizeof(uint8_t)*2) { - /* NSD has zone without any info */ - DEBUG(DEBUG_IPC,1, (LOG_INFO, "SOAINFO for %s lost zone", - dname_to_string(task->zname,0))); + DEBUG(DEBUG_IPC,1, (LOG_INFO, "SOAINFO for %s %s zone", + dname_to_string(task->zname,0), + hint == soainfo_bad ? "kept" : "lost")); soa_ptr = NULL; /* discard all updates */ before = xfrd_time(); @@ -647,12 +647,56 @@ xfrd_process_soa_info_task(struct task_list_d* task) /* update zone state */ switch(hint) { + case soainfo_bad: + /* "rollback" on-disk soa information */ + now = xfrd_time(); + zone->soa_disk_acquired = zone->soa_nsd_acquired; + zone->soa_disk = zone->soa_nsd; + + if(now - zone->soa_disk_acquired + >= (time_t)ntohl(zone->soa_disk.expire)) + { + /* zone expired */ + xfrd_set_zone_state(zone, xfrd_zone_expired); + xfrd_set_refresh_now(zone); + } else { + /* zone still ok, temporarily update refresh and retry + intervals to avoid flooding the primary with IXFR + and AXFR requests and reset timer */ + uint32_t refresh, retry; + refresh = + ntohl(zone->soa_disk.refresh) + + (now - zone->soa_disk_acquired); + retry = + ntohl(zone->soa_disk.retry) + + (now - zone->soa_disk_acquired); + zone->soa_disk.refresh = htonl(refresh); + zone->soa_disk.retry = htonl(retry); + xfrd_set_timer_refresh(zone); + zone->soa_disk = zone->soa_nsd; + } + + if(zone->soa_notified_acquired != 0 && + (zone->soa_notified.serial == 0 || + compare_serial(ntohl(zone->soa_disk.serial), + ntohl(zone->soa_notified.serial)) >= 0)) + { /* read was in response to this notification */ + zone->soa_notified_acquired = 0; + } + if(zone->soa_notified_acquired && zone->state == xfrd_zone_ok) + { + /* refresh because of notification */ + xfrd_set_zone_state(zone, xfrd_zone_refreshing); + xfrd_set_refresh_now(zone); + } + break; case soainfo_ok: if(xfrd->reload_failed) break; /* fall through */ case soainfo_gone: xfrd_handle_incoming_soa(zone, soa_ptr, xfrd_time()); + break; } } @@ -2527,10 +2571,9 @@ xfrd_check_failed_updates(void) /* delete all pending updates */ xfr = zone->latest_xfr; while(xfr) { - if(!xfr->acquired) - continue; prev_xfr = xfr->prev; - xfrd_delete_zone_xfr(zone, xfr); + if(xfr->acquired) + xfrd_delete_zone_xfr(zone, xfr); xfr = prev_xfr; } } @@ -2559,7 +2602,7 @@ xfrd_prepare_zones_for_reload(void) while(xfr && xfr->acquired) { /* skip updates that arrived after failed reload */ if(xfrd->reload_cmd_first_sent && !xfr->sent) - continue; + break; assert(!xfrd->reload_cmd_first_sent || xfrd->reload_cmd_first_sent >= xfr->acquired); if(send) { diff --git a/xfrd.h b/xfrd.h index 0aa3cb9d3..b6a840f81 100644 --- a/xfrd.h +++ b/xfrd.h @@ -219,6 +219,9 @@ struct xfrd_zone { int multi_master_update_check; /* -1: not update >0: last update master_num */ } ATTR_PACKED; +/* + * State for a single zone XFR + */ struct xfrd_xfr { xfrd_xfr_type *next; xfrd_xfr_type *prev;