diff --git a/dbaccess.c b/dbaccess.c index ecf7e982..230f0abb 100644 --- a/dbaccess.c +++ b/dbaccess.c @@ -75,6 +75,8 @@ namedb_zone_create(namedb_type* db, const dname_type* dname, zone->opts = zo; zone->ixfr = NULL; zone->filename = NULL; + zone->includes.count = 0; + zone->includes.paths = NULL; zone->logstr = NULL; zone->mtime.tv_sec = 0; zone->mtime.tv_nsec = 0; @@ -89,6 +91,34 @@ namedb_zone_create(namedb_type* db, const dname_type* dname, return zone; } +void +namedb_zone_free_filenames(namedb_type *db, zone_type* zone) +{ + assert(!zone->includes.paths == !zone->includes.count); + + if (zone->filename) { + region_recycle( + db->region, zone->filename, strlen(zone->filename) + 1); + zone->filename = NULL; + } + + if (zone->includes.count) { + for (size_t i=0; i < zone->includes.count; i++) { + region_recycle( + db->region, + zone->includes.paths[i], + strlen(zone->includes.paths[i]) + 1); + } + + region_recycle( + db->region, + zone->includes.paths, + zone->includes.count * sizeof(*zone->includes.paths)); + zone->includes.count = 0; + zone->includes.paths = NULL; + } +} + void namedb_zone_delete(namedb_type* db, zone_type* zone) { @@ -119,9 +149,7 @@ namedb_zone_delete(namedb_type* db, zone_type* zone) hash_tree_delete(db->region, zone->dshashtree); #endif zone_ixfr_free(zone->ixfr); - if(zone->filename) - region_recycle(db->region, zone->filename, - strlen(zone->filename)+1); + namedb_zone_free_filenames(db, zone); if(zone->logstr) region_recycle(db->region, zone->logstr, strlen(zone->logstr)+1); @@ -236,12 +264,27 @@ namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb, /* if zone_fname, then the file was acquired from reading it, * and see if filename changed or mtime newer to read it */ } else if(zone_fname && strcmp(zone_fname, fname) == 0 && - timespec_compare(&zone_mtime, &mtime) == 0) { - VERBOSITY(3, (LOG_INFO, "zonefile %s is not modified", - fname)); - return; + timespec_compare(&zone_mtime, &mtime) == 0) { + int changed = 0; + struct timespec include_mtime; + /* one of the includes may have been deleted, changed, etc */ + for (size_t i=0; i < zone->includes.count; i++) { + if (!file_get_mtime(zone->includes.paths[i], &include_mtime, &nonexist)) { + changed = 1; + } else if (timespec_compare(&zone_mtime, &include_mtime) < 0) { + mtime = include_mtime; + changed = 1; + } + } + + if (!changed) { + VERBOSITY(3, (LOG_INFO, "zonefile %s is not modified", + fname)); + return; + } } } + if(ixfr_create_from_difference(zone, fname, &ixfr_create_already_done)) { ixfrcr = ixfr_create_start(zone, fname, @@ -252,6 +295,9 @@ namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb, } } + namedb_zone_free_filenames(nsd->db, zone); + zone->filename = region_strdup(nsd->db->region, fname); + /* wipe zone from memory */ #ifdef NSEC3 nsec3_clear_precompile(nsd->db, zone); @@ -271,10 +317,7 @@ namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb, zone->nsec3_param = NULL; #endif delete_zone_rrs(nsd->db, zone); - if(zone->filename) - region_recycle(nsd->db->region, zone->filename, - strlen(zone->filename)+1); - zone->filename = NULL; + namedb_zone_free_filenames(nsd->db, zone); if(zone->logstr) region_recycle(nsd->db->region, zone->logstr, strlen(zone->logstr)+1); @@ -286,10 +329,6 @@ namedb_read_zonefile(struct nsd* nsd, struct zone* zone, udb_base* taskudb, zone->is_changed = 0; /* store zone into udb */ zone->mtime = mtime; - if(zone->filename) - region_recycle(nsd->db->region, zone->filename, - strlen(zone->filename)+1); - zone->filename = region_strdup(nsd->db->region, fname); if(zone->logstr) region_recycle(nsd->db->region, zone->logstr, strlen(zone->logstr)+1); diff --git a/difffile.c b/difffile.c index c4d18ad9..1c71822b 100644 --- a/difffile.c +++ b/difffile.c @@ -1396,10 +1396,7 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zone, FILE* in, region_recycle(nsd->db->region, zone->logstr, strlen(zone->logstr)+1); zone->logstr = region_strdup(nsd->db->region, log_buf); - if(zone->filename) - region_recycle(nsd->db->region, zone->filename, - strlen(zone->filename)+1); - zone->filename = NULL; + namedb_zone_free_filenames(nsd->db, zone); if(softfail && taskudb && !is_axfr) { log_msg(LOG_ERR, "Failed to apply IXFR cleanly " "(deletes nonexistent RRs, adds existing RRs). " diff --git a/doc/ChangeLog b/doc/ChangeLog index 0fb8249c..ac4777b7 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -10,6 +10,9 @@ format. - Fix to initialize log_time_iso value in options. +23 August 2024: Jeroen + - Fix #57: Track $INCLUDEs in zone files + 23 August 2024: Wouter - Merge #376: Point the user towards tcpdump for logging individual queries. diff --git a/doc/RELNOTES b/doc/RELNOTES index 3bc6b637..68c96fa9 100644 --- a/doc/RELNOTES +++ b/doc/RELNOTES @@ -7,6 +7,7 @@ FEATURES: - Fix #383: log timestamps in ISO8601 format with timezone. This adds the option `log-time-iso: yes` that logs in ISO8601 format. + BUG FIXES: - Fix title underline and declaration after statement warnings. - Add cross platform freebsd, openbsd and netbsd to github ci. @@ -15,6 +16,7 @@ BUG FIXES: prepended to fix detection. - Merge #376: Point the user towards tcpdump for logging individual queries. + - Track $INCLUDEs in zone files. - Fix ci to update macos-12 to the macos-15 runner image. - Merge #391: Update copyright lines (in version output). diff --git a/namedb.h b/namedb.h index 032d0eb3..37b4a038 100644 --- a/namedb.h +++ b/namedb.h @@ -139,7 +139,12 @@ struct zone #endif struct zone_options* opts; struct zone_ixfr* ixfr; - char* filename; /* set if read from file, which file */ + char *filename; /* set if read from file, which files */ + /* list of include files to monitor for changes */ + struct { + size_t count; + char **paths; + } includes; char* logstr; /* set for zone xfer, the log string */ struct timespec mtime; /* time of last modification */ unsigned zonestatid; /* array index for zone stats */ @@ -400,6 +405,7 @@ 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_free_filenames(namedb_type* db, zone_type* zone); 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); diff --git a/simdzone b/simdzone index 68356bd1..7c5aa09a 160000 --- a/simdzone +++ b/simdzone @@ -1 +1 @@ -Subproject commit 68356bd1949342493aae7437bee153efbab05a2f +Subproject commit 7c5aa09a50ba8b20cde80dff4a12fc7c0923dd85 diff --git a/tpkg/includes.tdir/includes.conf b/tpkg/includes.tdir/includes.conf new file mode 100644 index 00000000..dde17ca4 --- /dev/null +++ b/tpkg/includes.tdir/includes.conf @@ -0,0 +1,14 @@ +server: + zonesdir: "" + username: "" + chroot: "" + pidfile: nsd.pid + logfile: nsd.log + xfrdfile: nsd.xfrd + zonelistfile: "zone.list" + interface: 127.0.0.1 + zonefiles-check: yes + +zone: + name: example.com + zonefile: example.com diff --git a/tpkg/includes.tdir/includes.dsc b/tpkg/includes.tdir/includes.dsc new file mode 100644 index 00000000..081f38d0 --- /dev/null +++ b/tpkg/includes.tdir/includes.dsc @@ -0,0 +1,15 @@ +BaseName: includes +Version: 1.0 +Description: Test for tracking include dependencies +CreationDate: Fri Aug 23 9:47:00 CET 2024 +Maintainer: Jeroen Koekkoek +Category: +Component: +Depends: +Help: +Pre: +Post: +Test: includes.test +AuxFiles: includes.conf +Passed: +Failure: diff --git a/tpkg/includes.tdir/includes.test b/tpkg/includes.tdir/includes.test new file mode 100644 index 00000000..e5a18158 --- /dev/null +++ b/tpkg/includes.tdir/includes.test @@ -0,0 +1,60 @@ +#!/bin/bash +# #-- includes.test --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test +. ../common.sh +get_random_port 1 + +# start NSD +PRE="../.." +NSD="$PRE/nsd" + +example_com=" +\$ORIGIN example.com. +\$INCLUDE example.com.soa +\$INCLUDE example.com.data +" + +soa="@ IN SOA ns hostmaster 2024082300 6h 2h 7h 1h" + +data="\$INCLUDE example.com.hosts" + +hosts="www A 192.0.2.2" + +echo "$example_com" > example.com +echo "$soa" > example.com.soa +echo "$data" > example.com.data +echo "$hosts" > example.com.hosts + +$NSD -c includes.conf -p $RND_PORT +wait_nsd_up nsd.log +dig @127.0.0.1 -p $RND_PORT www.example.com +if dig @127.0.0.1 -p $RND_PORT www.example.com A | grep 192.0.2.2; then + echo "started successfully" +else + cat nsd.log + echo "failed to start" + kill_from_pidfile nsd.pid + exit 1 +fi + +hosts="www A 192.0.2.3" +echo "$hosts" > example.com.hosts +kill -1 `cat nsd.pid` + +wait_logfile nsd.log "SIGHUP received, reloading..." +sleep 1 +dig @127.0.0.1 -p $RND_PORT www.example.com +if dig @127.0.0.1 -p $RND_PORT www.example.com A | grep 192.0.2.3; then + echo "reloaded successfully" +else + cat nsd.log + echo "failed to reload" + kill_from_pidfile nsd.pid + exit 1 +fi + +kill_from_pidfile nsd.pid +rm -f example.com example.com.soa example.com.data example.com.hosts diff --git a/zonec.c b/zonec.c index 3601994f..f9137838 100644 --- a/zonec.c +++ b/zonec.c @@ -324,6 +324,43 @@ int32_t zonec_accept( return 0; } +static int32_t zonec_include( + zone_parser_t *parser, + const char *file, + const char *path, + void *user_data) +{ + char **paths; + struct zonec_state *state; + struct namedb *database; + struct zone *zone; + + (void)parser; + (void)file; + + state = (struct zonec_state *)user_data; + database = state->database; + zone = state->zone; + + assert((zone->includes.count == 0) == (zone->includes.paths == NULL)); + + for (size_t i=0; i < zone->includes.count; i++) + if (strcmp(path, zone->includes.paths[i]) == 0) + return 0; + + paths = region_alloc_array( + database->region, zone->includes.count + 1, sizeof(*paths)); + if (zone->includes.count) { + const size_t size = zone->includes.count * sizeof(*paths); + memcpy(paths, zone->includes.paths, size); + region_recycle(database->region, zone->includes.paths, size); + } + paths[zone->includes.count] = region_strdup(database->region, path); + zone->includes.count++; + zone->includes.paths = paths; + return 0; +} + static void zonec_log( zone_parser_t *parser, uint32_t category, @@ -395,6 +432,7 @@ zonec_read( options.pretty_ttls = true; /* non-standard, for backwards compatibility */ options.log.callback = &zonec_log; options.accept.callback = &zonec_accept; + options.include.callback = &zonec_include; /* Parse and process all RRs. */ if (zone_parse(&parser, &options, &buffers, zonefile, &state) != 0) {