From: Yorgos Thessalonikefs Date: Fri, 6 Mar 2026 16:05:57 +0000 (+0100) Subject: - Warn for unused 'nodefault' local-zone configuration in X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=94ef1a8feeb918bc25325de5f3d4354bbef774a4;p=thirdparty%2Funbound.git - Warn for unused 'nodefault' local-zone configuration in unbound-checkconf (related to #1416). --- diff --git a/doc/Changelog b/doc/Changelog index eaa8e6f83..90223791c 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -4,6 +4,8 @@ 6 March 2026: Yorgos - Document the suggestion for a higher value for 'outgoing-range'; helps when the request list is full. + - Warn for unused 'nodefault' local-zone configuration in + unbound-checkconf (related to #1416). 5 March 2026: Wouter - Fix for DNS Rebinding Bypass via SVCB/HTTPS Records in Unbound. diff --git a/services/localzone.c b/services/localzone.c index ccbe0d522..52166ae2d 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -56,6 +56,24 @@ * with 16 bytes for an A record, a 64K packet has about 4000 max */ #define LOCALZONE_RRSET_COUNT_MAX 4096 +static const char* default_zones_reverse_array[] = { + "127.in-addr.arpa.", /* reverse ip4 zone */ + "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", /* reverse ip6 zone */ + 0 +}; +const char** local_zones_default_reverse = default_zones_reverse_array; + +static const char* default_zones_special_array[] = { + "test.", /* RFC 6761 */ + "invalid.", /* RFC 6761 */ + "onion.", /* RFC 7686 */ + "home.arpa.", /* RFC 8375 */ + "resolver.arpa.", /* RFC 9462 */ + "service.arpa.", /* RFC 9665 */ + 0 +}; +const char** local_zones_default_special = default_zones_special_array; + /** print all RRsets in local zone */ static void local_zone_out(struct local_zone* z) @@ -834,7 +852,7 @@ lz_nodefault(struct config_file* cfg, const char* name) for(p = cfg->local_zones_nodefault; p; p = p->next) { /* compare zone name, lowercase, compare without ending . */ - if(strncasecmp(p->str, name, len) == 0 && + if(strncasecmp(p->str, name, len) == 0 && (strlen(p->str) == len || (strlen(p->str)==len+1 && p->str[len] == '.'))) return 1; @@ -842,6 +860,45 @@ lz_nodefault(struct config_file* cfg, const char* name) return 0; } +/** enter reverse default zone */ +static int +add_reverse_default(struct local_zones* zones, struct config_file* cfg, + const char* name) +{ + struct local_zone* z; + char str[1024]; /* known long enough */ + if(lz_exists(zones, name) || lz_nodefault(cfg, name)) + return 1; /* do not enter default content */ + if(!(z=lz_enter_zone(zones, name, "static", LDNS_RR_CLASS_IN))) + return 0; + snprintf(str, sizeof(str), "%s 10800 IN SOA localhost. " + "nobody.invalid. 1 3600 1200 604800 10800", name); + if(!lz_enter_rr_into_zone(z, str)) { + lock_rw_unlock(&z->lock); + return 0; + } + snprintf(str, sizeof(str), "%s 10800 IN NS localhost. ", name); + if(!lz_enter_rr_into_zone(z, str)) { + lock_rw_unlock(&z->lock); + return 0; + } + if(strncasecmp("127.in-addr.arpa.", name, 17) == 0) { + if(!lz_enter_rr_into_zone(z, + "1.0.0.127.in-addr.arpa. 10800 IN PTR localhost.")) { + lock_rw_unlock(&z->lock); + return 0; + } + } else if(strncasecmp("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", name, 73) == 0) { + snprintf(str, sizeof(str), "%s 10800 IN PTR localhost.", name); + if(!lz_enter_rr_into_zone(z, str)) { + lock_rw_unlock(&z->lock); + return 0; + } + } + lock_rw_unlock(&z->lock); + return 1; +} + /** enter (AS112) empty default zone */ static int add_empty_default(struct local_zones* zones, struct config_file* cfg, @@ -902,72 +959,23 @@ int local_zone_enter_defaults(struct local_zones* zones, struct config_file* cfg } lock_rw_unlock(&z->lock); } - /* reverse ip4 zone */ - if(!lz_exists(zones, "127.in-addr.arpa.") && - !lz_nodefault(cfg, "127.in-addr.arpa.")) { - if(!(z=lz_enter_zone(zones, "127.in-addr.arpa.", "static", - LDNS_RR_CLASS_IN)) || - !lz_enter_rr_into_zone(z, - "127.in-addr.arpa. 10800 IN NS localhost.") || - !lz_enter_rr_into_zone(z, - "127.in-addr.arpa. 10800 IN SOA localhost. " - "nobody.invalid. 1 3600 1200 604800 10800") || - !lz_enter_rr_into_zone(z, - "1.0.0.127.in-addr.arpa. 10800 IN PTR localhost.")) { + + /* ip4 and ip6 reverse */ + for(zstr = local_zones_default_reverse; *zstr; zstr++) { + if(!add_reverse_default(zones, cfg, *zstr)) { log_err("out of memory adding default zone"); - if(z) { lock_rw_unlock(&z->lock); } return 0; } - lock_rw_unlock(&z->lock); } - /* reverse ip6 zone */ - if(!lz_exists(zones, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.") && - !lz_nodefault(cfg, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.")) { - if(!(z=lz_enter_zone(zones, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", "static", - LDNS_RR_CLASS_IN)) || - !lz_enter_rr_into_zone(z, - "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa. 10800 IN NS localhost.") || - !lz_enter_rr_into_zone(z, - "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa. 10800 IN SOA localhost. " - "nobody.invalid. 1 3600 1200 604800 10800") || - !lz_enter_rr_into_zone(z, - "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa. 10800 IN PTR localhost.")) { + + /* special-use zones */ + for(zstr = local_zones_default_special; *zstr; zstr++) { + if(!add_empty_default(zones, cfg, *zstr)) { log_err("out of memory adding default zone"); - if(z) { lock_rw_unlock(&z->lock); } return 0; } - lock_rw_unlock(&z->lock); - } - /* home.arpa. zone (RFC 8375) */ - if(!add_empty_default(zones, cfg, "home.arpa.")) { - log_err("out of memory adding default zone"); - return 0; - } - /* resolver.arpa. zone (RFC 9462) */ - if(!add_empty_default(zones, cfg, "resolver.arpa.")) { - log_err("out of memory adding default zone"); - return 0; - } - /* service.arpa. zone (draft-ietf-dnssd-srp-25) */ - if(!add_empty_default(zones, cfg, "service.arpa.")) { - log_err("out of memory adding default zone"); - return 0; - } - /* onion. zone (RFC 7686) */ - if(!add_empty_default(zones, cfg, "onion.")) { - log_err("out of memory adding default zone"); - return 0; - } - /* test. zone (RFC 6761) */ - if(!add_empty_default(zones, cfg, "test.")) { - log_err("out of memory adding default zone"); - return 0; - } - /* invalid. zone (RFC 6761) */ - if(!add_empty_default(zones, cfg, "invalid.")) { - log_err("out of memory adding default zone"); - return 0; } + /* block AS112 zones, unless asked not to */ if(!cfg->unblock_lan_zones) { for(zstr = as112_zones; *zstr; zstr++) { diff --git a/services/localzone.h b/services/localzone.h index 3dc89b058..76c011836 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -57,6 +57,9 @@ struct sldns_buffer; struct comm_reply; struct config_strlist; +extern const char** local_zones_default_special; +extern const char** local_zones_default_reverse; + /** * Local zone type * This type determines processing for queries that did not match diff --git a/smallapp/unbound-checkconf.c b/smallapp/unbound-checkconf.c index 91bc558dd..399c2fce9 100644 --- a/smallapp/unbound-checkconf.c +++ b/smallapp/unbound-checkconf.c @@ -44,6 +44,7 @@ #include "config.h" #include +#include "util/as112.h" #include "util/log.h" #include "util/config_file.h" #include "util/module.h" @@ -188,11 +189,55 @@ donotquerylocalhostcheck(struct config_file* cfg) } } +static void +nodefaultzonescheck(struct config_file* cfg) +{ + struct config_strlist* d; + const char** zstr; + size_t len; + +#define COMPARE_ZONE_NAME(confname, builtname, len) \ + (strncasecmp(confname, builtname, (len)) == 0 && \ + (strlen(confname) == (len) || \ + (strlen(confname) == (len) + 1 \ + && confname[(len)] == '.'))) + + for(d = cfg->local_zones_nodefault; d; d = d->next) { + if(!cfg->unblock_lan_zones) { + for(zstr = as112_zones; *zstr; zstr++) { + len = strlen(*zstr) - 1; /* trailing '.' */ + if(COMPARE_ZONE_NAME(d->str, *zstr, len)) + goto default_continue; + } + } + for(zstr = local_zones_default_special; *zstr; zstr++) { + len = strlen(*zstr) - 1; /* trailing '.' */ + if(COMPARE_ZONE_NAME(d->str, *zstr, len)) + goto default_continue; + } + for(zstr = local_zones_default_reverse; *zstr; zstr++) { + len = strlen(*zstr) - 1; /* trailing '.' */ + if(COMPARE_ZONE_NAME(d->str, *zstr, len)) + goto default_continue; + } + if(COMPARE_ZONE_NAME(d->str, "localhost.", 10 - 1)) + goto default_continue; + fprintf(stderr, "unbound-checkconf: warning: local-zone: '%s' " + "is configured as 'nodefault' but there is no such " + "default local-zone. Check the unbound.conf " + "documentation for default configured local-zones.\n", + d->str); +default_continue: + } +#undef COMPARE_ZONE_NAME +} + /** check localzones */ static void localzonechecks(struct config_file* cfg) { struct local_zones* zs; + nodefaultzonescheck(cfg); if(!(zs = local_zones_create())) fatal_exit("out of memory"); if(!local_zones_apply_cfg(zs, cfg)) diff --git a/util/configparser.y b/util/configparser.y index d9a7cd839..aa787fdce 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -2399,7 +2399,7 @@ server_local_zone: VAR_LOCAL_ZONE STRING_ARG STRING_ARG yyerror("local-zone type: expected static, deny, " "refuse, redirect, transparent, " "typetransparent, inform, inform_deny, " - "inform_redirect, always_transparent, block_a," + "inform_redirect, always_transparent, block_a, " "always_refuse, always_nxdomain, " "always_nodata, always_deny, always_null, " "noview, nodefault or ipset");