diff --git a/.github/workflows/analysis_ports.yml b/.github/workflows/analysis_ports.yml index d71875076..f843eeb5e 100644 --- a/.github/workflows/analysis_ports.yml +++ b/.github/workflows/analysis_ports.yml @@ -31,12 +31,12 @@ jobs: clang_analysis: "yes" - name: OS X os: macos-latest - install_expat: "no" + install_expat: "yes" config: "--enable-debug --disable-flto --with-ssl=/opt/homebrew/opt/openssl --with-libexpat=/opt/homebrew/opt/expat" make_test: "yes" - name: Clang on OS X os: macos-latest - install_expat: "no" + install_expat: "yes" config: "CC=clang --enable-debug --disable-flto --with-ssl=/opt/homebrew/opt/openssl --with-libexpat=/opt/homebrew/opt/expat --disable-static" make_test: "yes" clang_analysis: "yes" diff --git a/contrib/unbound.service.in b/contrib/unbound.service.in index 8a5d3b2b0..cc8d0ed2d 100644 --- a/contrib/unbound.service.in +++ b/contrib/unbound.service.in @@ -38,11 +38,17 @@ ; - `LockPersonality=yes` locks down the personality system call so that the ; kernel execution domain may not be changed from the default. ; +; - With /etc/systemd/network/*.network a setting to make sure the network +; is not considered online too early, can reduce network unreachable +; errors on server start: +; [Link] +; RequiredForOnline=routable ; [Unit] Description=Validating, recursive, and caching DNS resolver Documentation=man:unbound(8) After=network-online.target +Wants=network-online.target Before=nss-lookup.target [Install] diff --git a/daemon/remote.c b/daemon/remote.c index 3843b6f4f..058517398 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -3221,10 +3221,13 @@ do_auth_zone_reload(RES* ssl, struct worker* worker, char* arg) (void)ssl_printf(ssl, "error: no SOA in zone after read %s\n", arg); return; } - if(xfr->have_zone) + if(xfr->have_zone) { xfr->lease_time = *worker->env.now; + xfr->soa_zone_acquired = *worker->env.now; + } lock_basic_unlock(&xfr->lock); } + z->soa_zone_acquired = *worker->env.now; auth_zone_verify_zonemd(z, &worker->env, &worker->env.mesh->mods, &reason, 0, 0); @@ -3373,7 +3376,7 @@ static void do_list_auth_zones(RES* ssl, struct auth_zones* az) { struct auth_zone* z; - char buf[LDNS_MAX_DOMAINLEN], buf2[256]; + char buf[LDNS_MAX_DOMAINLEN], buf2[256], buf3[256]; lock_rw_rdlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_rdlock(&z->lock); @@ -3382,18 +3385,41 @@ do_list_auth_zones(RES* ssl, struct auth_zones* az) snprintf(buf2, sizeof(buf2), "expired"); else { uint32_t serial = 0; - if(auth_zone_get_serial(z, &serial)) + if(auth_zone_get_serial(z, &serial)) { snprintf(buf2, sizeof(buf2), "serial %u", (unsigned)serial); - else snprintf(buf2, sizeof(buf2), "no serial"); + if(z->soa_zone_acquired != 0) { +#if defined(HAVE_STRFTIME) && defined(HAVE_LOCALTIME_R) + char tmbuf[32]; + struct tm tm; + struct tm *tm_p; + tm_p = localtime_r( + &z->soa_zone_acquired, &tm); + if(!strftime(tmbuf, sizeof(tmbuf), "%Y-%m-%dT%H:%M:%S", tm_p)) + snprintf(tmbuf, sizeof(tmbuf), "strftime-err-%u", (unsigned)z->soa_zone_acquired); + snprintf(buf3, sizeof(buf3), + "\t since %u %s", + (unsigned)z->soa_zone_acquired, + tmbuf); +#else + snprintf(buf3, sizeof(buf3), + "\t since %u", + (unsigned)z->soa_zone_acquired); +#endif + } else { + buf3[0]=0; + } + } else { + snprintf(buf2, sizeof(buf2), "no serial"); + buf3[0]=0; + } } - if(!ssl_printf(ssl, "%s\t%s\n", buf, buf2)) { + lock_rw_unlock(&z->lock); + if(!ssl_printf(ssl, "%s\t%s%s\n", buf, buf2, buf3)) { /* failure to print */ - lock_rw_unlock(&z->lock); lock_rw_unlock(&az->lock); return; } - lock_rw_unlock(&z->lock); } lock_rw_unlock(&az->lock); } @@ -3836,6 +3862,30 @@ do_print_cookie_secrets(RES* ssl, struct worker* worker) { explicit_bzero(secret_hex, sizeof(secret_hex)); } +/** check that there is no argument after a command that takes no arguments. */ +static int +cmd_no_args(RES* ssl, char* cmd, char* p) +{ + if(p && *p != 0) { + /* cmd contains the command that is called at the start, + * with space or tab after it. */ + char* c = cmd; + if(strchr(c, ' ') && strchr(c, '\t')) { + if(strchr(c, ' ') < strchr(c, '\t')) + *strchr(c, ' ')=0; + else *strchr(c, '\t')=0; + } else if(strchr(c, ' ')) { + *strchr(c, ' ')=0; + } else if(strchr(c, '\t')) { + *strchr(c, '\t')=0; + } + (void)ssl_printf(ssl, "error command %s takes no arguments," + " have '%s'\n", c, p); + return 1; + } + return 0; +} + /** check for name with end-of-string, space or tab after it */ static int cmdcmp(char* p, const char* cmd, size_t len) @@ -3851,27 +3901,41 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, char* p = skipwhite(cmd); /* compare command */ if(cmdcmp(p, "stop", 4)) { + if(cmd_no_args(ssl, p, skipwhite(p+4))) + return; do_stop(ssl, worker); return; } else if(cmdcmp(p, "reload_keep_cache", 17)) { + if(cmd_no_args(ssl, p, skipwhite(p+17))) + return; do_reload(ssl, worker, 1); return; } else if(cmdcmp(p, "reload", 6)) { + if(cmd_no_args(ssl, p, skipwhite(p+6))) + return; do_reload(ssl, worker, 0); return; } else if(cmdcmp(p, "fast_reload", 11)) { do_fast_reload(ssl, worker, s, skipwhite(p+11)); return; } else if(cmdcmp(p, "stats_noreset", 13)) { + if(cmd_no_args(ssl, p, skipwhite(p+13))) + return; do_stats(ssl, worker, 0); return; } else if(cmdcmp(p, "stats", 5)) { + if(cmd_no_args(ssl, p, skipwhite(p+5))) + return; do_stats(ssl, worker, 1); return; } else if(cmdcmp(p, "status", 6)) { + if(cmd_no_args(ssl, p, skipwhite(p+6))) + return; do_status(ssl, worker); return; } else if(cmdcmp(p, "dump_cache", 10)) { + if(cmd_no_args(ssl, p, skipwhite(p+10))) + return; #ifdef THREADS_DISABLED if(worker->daemon->num > 1) { (void)ssl_printf(ssl, "dump_cache/load_cache is not " @@ -3882,6 +3946,8 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, (void)dump_cache(ssl, worker); return; } else if(cmdcmp(p, "load_cache", 10)) { + if(cmd_no_args(ssl, p, skipwhite(p+10))) + return; #ifdef THREADS_DISABLED if(worker->daemon->num > 1) { /* The warning can't be printed when stdin is sending @@ -3892,18 +3958,28 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, if(load_cache(ssl, worker)) send_ok(ssl); return; } else if(cmdcmp(p, "list_forwards", 13)) { + if(cmd_no_args(ssl, p, skipwhite(p+13))) + return; do_list_forwards(ssl, worker); return; } else if(cmdcmp(p, "list_stubs", 10)) { + if(cmd_no_args(ssl, p, skipwhite(p+10))) + return; do_list_stubs(ssl, worker); return; } else if(cmdcmp(p, "list_insecure", 13)) { + if(cmd_no_args(ssl, p, skipwhite(p+13))) + return; do_insecure_list(ssl, worker); return; } else if(cmdcmp(p, "list_local_zones", 16)) { + if(cmd_no_args(ssl, p, skipwhite(p+16))) + return; do_list_local_zones(ssl, worker->daemon->local_zones); return; } else if(cmdcmp(p, "list_local_data", 15)) { + if(cmd_no_args(ssl, p, skipwhite(p+15))) + return; do_list_local_data(ssl, worker, worker->daemon->local_zones); return; } else if(cmdcmp(p, "view_list_local_zones", 21)) { @@ -3919,6 +3995,8 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, do_ip_ratelimit_list(ssl, worker, p+17); return; } else if(cmdcmp(p, "list_auth_zones", 15)) { + if(cmd_no_args(ssl, p, skipwhite(p+15))) + return; do_list_auth_zones(ssl, worker->env.auth_zones); return; } else if(cmdcmp(p, "auth_zone_reload", 16)) { @@ -3939,11 +4017,15 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, return; } else if(cmdcmp(p, "flush_stats", 11)) { /* must always distribute this cmd */ + if(cmd_no_args(ssl, p, skipwhite(p+11))) + return; if(rc) distribute_cmd(rc, ssl, cmd); do_flush_stats(ssl, worker); return; } else if(cmdcmp(p, "flush_requestlist", 17)) { /* must always distribute this cmd */ + if(cmd_no_args(ssl, p, skipwhite(p+17))) + return; if(rc) distribute_cmd(rc, ssl, cmd); do_flush_requestlist(ssl, worker); return; @@ -3957,15 +4039,23 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, * Each line needs to be distributed if THREADS_DISABLED. */ } else if(cmdcmp(p, "local_zones_remove", 18)) { + if(cmd_no_args(ssl, p, skipwhite(p+18))) + return; do_zones_remove(rc, ssl, worker); return; } else if(cmdcmp(p, "local_zones", 11)) { + if(cmd_no_args(ssl, p, skipwhite(p+11))) + return; do_zones_add(rc, ssl, worker); return; } else if(cmdcmp(p, "local_datas_remove", 18)) { + if(cmd_no_args(ssl, p, skipwhite(p+18))) + return; do_datas_remove(rc, ssl, worker); return; } else if(cmdcmp(p, "local_datas", 11)) { + if(cmd_no_args(ssl, p, skipwhite(p+11))) + return; do_datas_add(rc, ssl, worker); return; } else if(cmdcmp(p, "view_local_datas_remove", 23)){ @@ -3975,6 +4065,8 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, do_view_datas_add(rc, ssl, worker, skipwhite(p+16)); return; } else if(cmdcmp(p, "print_cookie_secrets", 20)) { + if(cmd_no_args(ssl, p, skipwhite(p+20))) + return; do_print_cookie_secrets(ssl, worker); return; } @@ -4024,10 +4116,16 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, } else if(cmdcmp(p, "flush", 5)) { do_flush_name(ssl, worker, skipwhite(p+5)); } else if(cmdcmp(p, "dump_requestlist", 16)) { + if(cmd_no_args(ssl, p, skipwhite(p+16))) + return; do_dump_requestlist(ssl, worker); } else if(cmdcmp(p, "dump_infra", 10)) { + if(cmd_no_args(ssl, p, skipwhite(p+10))) + return; do_dump_infra(ssl, worker); } else if(cmdcmp(p, "log_reopen", 10)) { + if(cmd_no_args(ssl, p, skipwhite(p+10))) + return; do_log_reopen(ssl, worker); } else if(cmdcmp(p, "set_option", 10)) { do_set_option(ssl, worker, skipwhite(p+10)); @@ -4044,8 +4142,12 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, } else if(cmdcmp(p, "add_cookie_secret", 17)) { do_add_cookie_secret(ssl, worker, skipwhite(p+17)); } else if(cmdcmp(p, "drop_cookie_secret", 18)) { + if(cmd_no_args(ssl, p, skipwhite(p+18))) + return; do_drop_cookie_secret(ssl, worker); } else if(cmdcmp(p, "activate_cookie_secret", 22)) { + if(cmd_no_args(ssl, p, skipwhite(p+22))) + return; do_activate_cookie_secret(ssl, worker); } else { (void)ssl_printf(ssl, "error unknown command '%s'\n", p); @@ -7394,6 +7496,7 @@ fr_worker_auth_add(struct worker* worker, struct fast_reload_auth_change* item, xfr->serial = 0; } } + auth_zone_pickup_initial_zone(item->new_z, &worker->env); lock_rw_unlock(&item->new_z->lock); lock_rw_unlock(&worker->env.auth_zones->lock); lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock); diff --git a/doc/Changelog b/doc/Changelog index 88cd08848..e87a49d2d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,21 @@ +26 August 2025: Wouter + - Fix ports workflow to install expat for macos. + +22 August 2025: Wouter + - For #1318: Fix compile warnings for DoH compile on windows. + - Fix sha1 enable environment variable in test code on windows. + - Fix #1319: [FR] zone status for Unbound auth-zones. + - Fix that the zone acquired timestamp is set after the + zonefile is read. + +21 August 2025: Wouter + - Fix to check for extraneous command arguments for unbound-control, + when the command takes no arguments but there are arguments present. + - Fix #1317: Unbound starts too early. Add + Wants=network-online.target under [Unit] in unbound.service. + - Fix for #1317: Fix contrib/unbound.service comment path for + systemd network configuration. + 15 August 2025: Wouter - unbound-control cache_lookup +t allows tld and root names. And subnet cache contents are printed. diff --git a/services/authzone.c b/services/authzone.c index c7aff82a1..594cc73a4 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -5119,6 +5119,7 @@ apply_axfr(struct auth_xfer* xfr, struct auth_zone* z, xfr->have_zone = 0; xfr->serial = 0; + xfr->soa_zone_acquired = 0; /* insert all RRs in to the zone */ /* insert the SOA only once, skip the last one */ @@ -5220,6 +5221,7 @@ apply_http(struct auth_xfer* xfr, struct auth_zone* z, xfr->have_zone = 0; xfr->serial = 0; + xfr->soa_zone_acquired = 0; chunk = xfr->task_transfer->chunks_first; chunk_pos = 0; @@ -5430,6 +5432,8 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, " (or malformed RR)", xfr->task_transfer->master->host); return 0; } + z->soa_zone_acquired = *env->now; + xfr->soa_zone_acquired = *env->now; /* release xfr lock while verifying zonemd because it may have * to spawn lookups in the state machines */ @@ -7150,13 +7154,23 @@ xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, comm_timer_set(xfr->task_nextprobe->timer, &tv); } +void auth_zone_pickup_initial_zone(struct auth_zone* z, struct module_env* env) +{ + /* Set the time, because we now have timestamp in env, + * (not earlier during startup and apply_cfg), and this + * notes the start time when the data was acquired. */ + z->soa_zone_acquired = *env->now; +} + void auth_xfer_pickup_initial_zone(struct auth_xfer* x, struct module_env* env) { /* set lease_time, because we now have timestamp in env, * (not earlier during startup and apply_cfg), and this * notes the start time when the data was acquired */ - if(x->have_zone) + if(x->have_zone) { x->lease_time = *env->now; + x->soa_zone_acquired = *env->now; + } if(x->task_nextprobe && x->task_nextprobe->worker == NULL) { xfr_set_timeout(x, env, 0, 1); } @@ -7167,7 +7181,13 @@ void auth_xfer_pickup_initial(struct auth_zones* az, struct module_env* env) { struct auth_xfer* x; + struct auth_zone* z; lock_rw_wrlock(&az->lock); + RBTREE_FOR(z, struct auth_zone*, &az->ztree) { + lock_rw_wrlock(&z->lock); + auth_zone_pickup_initial_zone(z, env); + lock_rw_unlock(&z->lock); + } RBTREE_FOR(x, struct auth_xfer*, &az->xtree) { lock_basic_lock(&x->lock); auth_xfer_pickup_initial_zone(x, env); @@ -7252,6 +7272,7 @@ auth_xfer_new(struct auth_zone* z) lock_protect(&xfr->lock, &xfr->notify_serial, sizeof(xfr->notify_serial)); lock_protect(&xfr->lock, &xfr->zone_expired, sizeof(xfr->zone_expired)); lock_protect(&xfr->lock, &xfr->have_zone, sizeof(xfr->have_zone)); + lock_protect(&xfr->lock, &xfr->soa_zone_acquired, sizeof(xfr->soa_zone_acquired)); lock_protect(&xfr->lock, &xfr->serial, sizeof(xfr->serial)); lock_protect(&xfr->lock, &xfr->retry, sizeof(xfr->retry)); lock_protect(&xfr->lock, &xfr->refresh, sizeof(xfr->refresh)); diff --git a/services/authzone.h b/services/authzone.h index 45195586e..5e41b1f6c 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -120,6 +120,8 @@ struct auth_zone { char* zonefile; /** fallback to the internet on failure or ttl-expiry of auth zone */ int fallback_enabled; + /** the time when zone was transferred from upstream */ + time_t soa_zone_acquired; /** the zone has expired (enabled by the xfer worker), fallback * happens if that option is enabled. */ int zone_expired; @@ -263,6 +265,8 @@ struct auth_xfer { int zone_expired; /** do we have a zone (if 0, no zone data at all) */ int have_zone; + /** the time when zone was transferred from upstream */ + time_t soa_zone_acquired; /** current serial (from SOA), if we have no zone, 0 */ uint32_t serial; @@ -818,6 +822,14 @@ size_t auth_zones_get_mem(struct auth_zones* zones); void auth_xfer_pickup_initial_zone(struct auth_xfer* x, struct module_env* env); +/** + * Initial pick up of the auth zone, it sets the acquired time. + * @param z: the zone, write locked by caller. + * @param env: environment of the worker, with current time. + */ +void auth_zone_pickup_initial_zone(struct auth_zone* z, + struct module_env* env); + /** * Delete auth xfer structure * @param xfr: delete this xfer and its tasks. diff --git a/testcode/testbound.c b/testcode/testbound.c index 410ec165c..063037df4 100644 --- a/testcode/testbound.c +++ b/testcode/testbound.c @@ -297,7 +297,11 @@ setup_config(FILE* in, int* lineno, int* pass_argc, char* pass_argv[]) /* Allow the use of SHA1 signatures for the test, * in case that OpenSSL disallows use of RSASHA1 * with rh-allow-sha1-signatures disabled. */ +#ifndef UB_ON_WINDOWS setenv("OPENSSL_ENABLE_SHA1_SIGNATURES", "1", 0); +#else + _putenv("OPENSSL_ENABLE_SHA1_SIGNATURES=1"); +#endif } fputs(line, cfg); } diff --git a/testcode/unitverify.c b/testcode/unitverify.c index 24274c58b..12d5205b0 100644 --- a/testcode/unitverify.c +++ b/testcode/unitverify.c @@ -638,7 +638,11 @@ verify_test(void) /* Allow the use of SHA1 signatures for the test, * in case that OpenSSL disallows use of RSASHA1 * with rh-allow-sha1-signatures disabled. */ +#ifndef UB_ON_WINDOWS setenv("OPENSSL_ENABLE_SHA1_SIGNATURES", "1", 0); +#else + _putenv("OPENSSL_ENABLE_SHA1_SIGNATURES=1"); +#endif } #endif diff --git a/util/netevent.c b/util/netevent.c index 0756dc26c..7a015e08e 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -5194,7 +5194,7 @@ ssize_t http2_recv_cb(nghttp2_session* ATTR_UNUSED(session), uint8_t* buf, } #endif /* HAVE_SSL */ - ret = recv(h2_session->c->fd, buf, len, MSG_DONTWAIT); + ret = recv(h2_session->c->fd, (void*)buf, len, MSG_DONTWAIT); if(ret == 0) { return NGHTTP2_ERR_EOF; } else if(ret < 0) { @@ -5522,7 +5522,7 @@ ssize_t http2_send_cb(nghttp2_session* ATTR_UNUSED(session), const uint8_t* buf, } #endif /* HAVE_SSL */ - ret = send(h2_session->c->fd, buf, len, 0); + ret = send(h2_session->c->fd, (void*)buf, len, 0); if(ret == 0) { return NGHTTP2_ERR_CALLBACK_FAILURE; } else if(ret < 0) {