diff --git a/config.h.in b/config.h.in index 5e3e1557b..96151cdc4 100644 --- a/config.h.in +++ b/config.h.in @@ -738,6 +738,12 @@ /* Define to 1 if `sun_len' is a member of `struct sockaddr_un'. */ #undef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN +/* Define to 1 if `st_mtimensec' is a member of `struct stat'. */ +#undef HAVE_STRUCT_STAT_ST_MTIMENSEC + +/* Define to 1 if `st_mtim.tv_nsec' is a member of `struct stat'. */ +#undef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + /* Define if you have Swig libraries and header files. */ #undef HAVE_SWIG diff --git a/configure b/configure index 896fac022..497173221 100755 --- a/configure +++ b/configure @@ -23295,6 +23295,23 @@ printf "%s\n" "no" >&6; } fi +fi + +ac_fn_c_check_member "$LINENO" "struct stat" "st_mtimensec" "ac_cv_member_struct_stat_st_mtimensec" "$ac_includes_default" +if test "x$ac_cv_member_struct_stat_st_mtimensec" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_STAT_ST_MTIMENSEC 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct stat" "st_mtim.tv_nsec" "ac_cv_member_struct_stat_st_mtim_tv_nsec" "$ac_includes_default" +if test "x$ac_cv_member_struct_stat_st_mtim_tv_nsec" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 1" >>confdefs.h + + fi ac_fn_c_check_member "$LINENO" "struct sockaddr_un" "sun_len" "ac_cv_member_struct_sockaddr_un_sun_len" " diff --git a/configure.ac b/configure.ac index 99f6e4e31..d7c5f116b 100644 --- a/configure.ac +++ b/configure.ac @@ -1778,6 +1778,7 @@ if test $ac_cv_func_daemon = yes; then ]) fi +AC_CHECK_MEMBERS([struct stat.st_mtimensec, struct stat.st_mtim.tv_nsec]) AC_CHECK_MEMBERS([struct sockaddr_un.sun_len],,,[ AC_INCLUDES_DEFAULT #ifdef HAVE_SYS_UN_H diff --git a/daemon/daemon.c b/daemon/daemon.c index 62c1fbc1f..71caf16a0 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -200,6 +200,244 @@ signal_handling_playback(struct worker* wrk) sig_record_reload = 0; } +#ifdef HAVE_SSL +/* setup a listening ssl context, fatal_exit() on any failure */ +static void +setup_listen_sslctx(void** ctx, int is_dot, int is_doh, + struct config_file* cfg, char* chroot) +{ + char* key = cfg->ssl_service_key; + char* pem = cfg->ssl_service_pem; + if(chroot && strncmp(key, chroot, strlen(chroot)) == 0) + key += strlen(chroot); + if(chroot && pem && strncmp(pem, chroot, strlen(chroot)) == 0) + pem += strlen(chroot); + if(!(*ctx = listen_sslctx_create(key, pem, NULL, + cfg->tls_ciphers, cfg->tls_ciphersuites, + (cfg->tls_session_ticket_keys.first && + cfg->tls_session_ticket_keys.first->str[0] != 0), + is_dot, is_doh, cfg->tls_use_system_policy_versions))) { + fatal_exit("could not set up listen SSL_CTX"); + } +} +#endif /* HAVE_SSL */ + +#ifdef HAVE_SSL +void* daemon_setup_listen_dot_sslctx(struct daemon* daemon, + struct config_file* cfg) +{ + void* ctx; + (void)setup_listen_sslctx(&ctx, 1, 0, cfg, daemon->chroot); + return ctx; +} +#endif /* HAVE_SSL */ + +#ifdef HAVE_SSL +#ifdef HAVE_NGHTTP2_NGHTTP2_H +void* daemon_setup_listen_doh_sslctx(struct daemon* daemon, + struct config_file* cfg) +{ + void* ctx; + (void)setup_listen_sslctx(&ctx, 0, 1, cfg, daemon->chroot); + return ctx; +} +#endif /* HAVE_NGHTTP2_NGHTTP2_H */ +#endif /* HAVE_SSL */ + +#ifdef HAVE_SSL +#ifdef HAVE_NGTCP2 +void* daemon_setup_listen_quic_sslctx(struct daemon* daemon, + struct config_file* cfg) +{ + void* ctx; + char* chroot = daemon->chroot; + char* key = cfg->ssl_service_key; + char* pem = cfg->ssl_service_pem; + if(chroot && strncmp(key, chroot, strlen(chroot)) == 0) + key += strlen(chroot); + if(chroot && pem && strncmp(pem, chroot, strlen(chroot)) == 0) + pem += strlen(chroot); + + if(!(ctx = quic_sslctx_create(key, pem, NULL))) { + fatal_exit("could not set up quic SSL_CTX"); + } + return ctx; +} +#endif /* HAVE_NGTCP2 */ +#endif /* HAVE_SSL */ + +#ifdef HAVE_SSL +void* daemon_setup_connect_dot_sslctx(struct daemon* daemon, + struct config_file* cfg) +{ + void* ctx; + char* bundle, *chroot = daemon->chroot; + bundle = cfg->tls_cert_bundle; + if(chroot && bundle && strncmp(bundle, chroot, strlen(chroot)) == 0) + bundle += strlen(chroot); + + if(!(ctx = connect_sslctx_create(NULL, NULL, bundle, + cfg->tls_win_cert))) + fatal_exit("could not set up connect SSL_CTX"); + return ctx; +} +#endif /* HAVE_SSL */ + +/* setups the needed ssl contexts, fatal_exit() on any failure */ +void +daemon_setup_sslctxs(struct daemon* daemon, struct config_file* cfg) +{ +#ifdef HAVE_SSL + char* chroot = daemon->chroot; + if(cfg->ssl_service_key && cfg->ssl_service_key[0]) { + char* key = cfg->ssl_service_key; + char* pem = cfg->ssl_service_pem; + if(chroot && strncmp(key, chroot, strlen(chroot)) == 0) + key += strlen(chroot); + if(chroot && pem && strncmp(pem, chroot, strlen(chroot)) == 0) + pem += strlen(chroot); + + /* setup the session keys; the callback to use them will be + * attached to each sslctx separately */ + if(cfg->tls_session_ticket_keys.first && + cfg->tls_session_ticket_keys.first->str[0] != 0) { + if(!listen_sslctx_setup_ticket_keys( + cfg->tls_session_ticket_keys.first, chroot)) { + fatal_exit("could not set session ticket SSL_CTX"); + } + } + daemon->listen_dot_sslctx = daemon_setup_listen_dot_sslctx( + daemon, cfg); +#ifdef HAVE_NGHTTP2_NGHTTP2_H + if(cfg_has_https(cfg)) { + daemon->listen_doh_sslctx = + daemon_setup_listen_doh_sslctx(daemon, cfg); + } +#endif +#ifdef HAVE_NGTCP2 + if(cfg_has_quic(cfg)) { + daemon->listen_quic_sslctx = + daemon_setup_listen_quic_sslctx(daemon, cfg); + } +#endif /* HAVE_NGTCP2 */ + + /* Store the file name and mtime to detect changes later. */ + daemon->ssl_service_key = strdup(cfg->ssl_service_key); + if(!daemon->ssl_service_key) + fatal_exit("could not setup ssl ctx: out of memory"); + daemon->ssl_service_pem = strdup(cfg->ssl_service_pem); + if(!daemon->ssl_service_pem) + fatal_exit("could not setup ssl ctx: out of memory"); + if(!file_get_mtime(key, + &daemon->mtime_ssl_service_key, + &daemon->mtime_ns_ssl_service_key, NULL)) + log_err("Could not stat(%s): %s", + key, strerror(errno)); + if(!file_get_mtime(pem, + &daemon->mtime_ssl_service_pem, + &daemon->mtime_ns_ssl_service_pem, NULL)) + log_err("Could not stat(%s): %s", + pem, strerror(errno)); + } + daemon->connect_dot_sslctx = daemon_setup_connect_dot_sslctx( + daemon, cfg); +#else /* HAVE_SSL */ + (void)daemon;(void)cfg; +#endif /* HAVE_SSL */ +} + +/** Delete the ssl ctxs */ +static void +daemon_delete_sslctxs(struct daemon* daemon) +{ +#ifdef HAVE_SSL + listen_sslctx_delete_ticket_keys(); + SSL_CTX_free((SSL_CTX*)daemon->listen_dot_sslctx); + daemon->listen_dot_sslctx = NULL; + SSL_CTX_free((SSL_CTX*)daemon->listen_doh_sslctx); + daemon->listen_doh_sslctx = NULL; + SSL_CTX_free((SSL_CTX*)daemon->connect_dot_sslctx); + daemon->connect_dot_sslctx = NULL; + free(daemon->ssl_service_key); + daemon->ssl_service_key = NULL; + free(daemon->ssl_service_pem); + daemon->ssl_service_pem = NULL; +#else + (void)daemon; +#endif +#ifdef HAVE_NGTCP2 + SSL_CTX_free((SSL_CTX*)daemon->listen_quic_sslctx); + daemon->listen_quic_sslctx = NULL; +#endif +} + +int +ssl_cert_changed(struct daemon* daemon, struct config_file* cfg) +{ + time_t mtime = 0; + long ns = 0; + char* chroot = daemon->chroot; + char* key = cfg->ssl_service_key; + char* pem = cfg->ssl_service_pem; + log_assert(daemon->ssl_service_key && cfg->ssl_service_key); + if(chroot && strncmp(key, chroot, strlen(chroot)) == 0) + key += strlen(chroot); + if(chroot && pem && strncmp(pem, chroot, strlen(chroot)) == 0) + pem += strlen(chroot); + + if(strcmp(daemon->ssl_service_key, cfg->ssl_service_key) != 0) + return 1; + if(daemon->ssl_service_pem && cfg->ssl_service_pem && + strcmp(daemon->ssl_service_pem, cfg->ssl_service_pem) != 0) + return 1; + if(!file_get_mtime(key, &mtime, &ns, NULL)) { + log_err("Could not stat(%s): %s", + key, strerror(errno)); + /* It has probably changed, but file read is likely going to + * fail. */ + return 0; + } + if(mtime != daemon->mtime_ssl_service_key || + ns != daemon->mtime_ns_ssl_service_key) + return 1; + if(!file_get_mtime(pem, &mtime, &ns, NULL)) { + log_err("Could not stat(%s): %s", + pem, strerror(errno)); + /* It has probably changed, but file read is likely going to + * fail. */ + return 0; + } + if(mtime != daemon->mtime_ssl_service_pem || + ns != daemon->mtime_ns_ssl_service_pem) + return 1; + return 0; +} + +/** Reload the sslctxs if they have changed */ +static void +daemon_reload_sslctxs(struct daemon* daemon) +{ +#ifdef HAVE_SSL + if(daemon->cfg->ssl_service_key && daemon->cfg->ssl_service_key[0]) { + /* See if changed */ + if(!daemon->ssl_service_key || + ssl_cert_changed(daemon,daemon->cfg)) { + verbose(VERB_ALGO, "Reloading certificates"); + daemon_delete_sslctxs(daemon); + daemon_setup_sslctxs(daemon, daemon->cfg); + } + } else { + /* See if sslctxs are removed from config. */ + if(daemon->ssl_service_key) { + verbose(VERB_ALGO, "Removing certificates"); + daemon_delete_sslctxs(daemon); + } + } +#else + (void)daemon; +#endif +} + struct daemon* daemon_init(void) { @@ -777,6 +1015,7 @@ daemon_fork(struct daemon* daemon) #endif log_assert(daemon); + daemon_reload_sslctxs(daemon); if(!(daemon->env->views = views_create())) fatal_exit("Could not create views: out of memory"); /* create individual views and their localzone/data trees */ @@ -1029,15 +1268,7 @@ daemon_delete(struct daemon* daemon) free(daemon->pidfile); free(daemon->cfgfile); free(daemon->env); -#ifdef HAVE_SSL - listen_sslctx_delete_ticket_keys(); - SSL_CTX_free((SSL_CTX*)daemon->listen_dot_sslctx); - SSL_CTX_free((SSL_CTX*)daemon->listen_doh_sslctx); - SSL_CTX_free((SSL_CTX*)daemon->connect_dot_sslctx); -#endif -#ifdef HAVE_NGTCP2 - SSL_CTX_free((SSL_CTX*)daemon->listen_quic_sslctx); -#endif + daemon_delete_sslctxs(daemon); free(daemon); /* lex cleanup */ ub_c_lex_destroy(); diff --git a/daemon/daemon.h b/daemon/daemon.h index fa5673063..020e2a23a 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -112,6 +112,18 @@ struct daemon { void* listen_doh_sslctx; /** ssl context for listening to quic */ void* listen_quic_sslctx; + /** the file name that the ssl context is made with, private key. */ + char* ssl_service_key; + /** the file name that the ssl context is made with, certificate. */ + char* ssl_service_pem; + /** modification time for ssl_service_key, in sec and ns. Like + * in a struct timespec, but without that for portability. */ + time_t mtime_ssl_service_key; + long mtime_ns_ssl_service_key; + /** modification time for ssl_service_pem, in sec and ns. Like + * in a struct timespec, but without that for portability. */ + time_t mtime_ssl_service_pem; + long mtime_ns_ssl_service_pem; /** num threads allocated */ int num; /** num threads allocated in the previous config or 0 at first */ @@ -234,4 +246,26 @@ void daemon_apply_cfg(struct daemon* daemon, struct config_file* cfg); */ int setup_acl_for_ports(struct acl_list* list, struct listen_port* port_list); +/* setups the needed ssl contexts, fatal_exit() on any failure */ +void daemon_setup_sslctxs(struct daemon* daemon, struct config_file* cfg); + +/** See if the SSL cert files have changed */ +int ssl_cert_changed(struct daemon* daemon, struct config_file* cfg); + +/** Setup the listening DoT SSL_CTX, returns the ssl ctx. */ +void* daemon_setup_listen_dot_sslctx(struct daemon* daemon, + struct config_file* cfg); + +/** Setup the listening DoH SSL_CTX, returns the ssl ctx. */ +void* daemon_setup_listen_doh_sslctx(struct daemon* daemon, + struct config_file* cfg); + +/** Setup the listening Quic SSL_CTX, returns the ssl ctx */ +void* daemon_setup_listen_quic_sslctx(struct daemon* daemon, + struct config_file* cfg); + +/** Setup the connect DoT SSL_CTX, returns the ssl ctx */ +void* daemon_setup_connect_dot_sslctx(struct daemon* daemon, + struct config_file* cfg); + #endif /* DAEMON_H */ diff --git a/daemon/remote.c b/daemon/remote.c index 141cfb9d6..598451e4b 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -4599,6 +4599,26 @@ fr_init_time(struct timeval* time_start, struct timeval* time_read, * are kept in here. They can then be deleted. */ struct fast_reload_construct { + /** ssl context for listening to dnstcp over ssl */ + void* listen_dot_sslctx; + /** ssl context for connecting to dnstcp over ssl */ + void* connect_dot_sslctx; + /** ssl context for listening to DoH */ + void* listen_doh_sslctx; + /** ssl context for listening to quic */ + void* listen_quic_sslctx; + /** the file name that the ssl context is made with, private key. */ + char* ssl_service_key; + /** the file name that the ssl context is made with, certificate. */ + char* ssl_service_pem; + /** modification time for ssl_service_key, in sec and ns. Like + * in a struct timespec, but without that for portability. */ + time_t mtime_ssl_service_key; + long mtime_ns_ssl_service_key; + /** modification time for ssl_service_pem, in sec and ns. Like + * in a struct timespec, but without that for portability. */ + time_t mtime_ssl_service_pem; + long mtime_ns_ssl_service_pem; /** construct for views */ struct views* views; /** construct for auth zones */ @@ -4902,9 +4922,6 @@ fr_check_compat_cfg(struct fast_reload_thread* fr, struct config_file* newcfg) FR_CHECK_CHANGED_CFG("http_notls_downstream", http_notls_downstream, changed_str); FR_CHECK_CHANGED_CFG("https-port", https_port, changed_str); FR_CHECK_CHANGED_CFG("tls-port", ssl_port, changed_str); - FR_CHECK_CHANGED_CFG_STR("tls-service-key", ssl_service_key, changed_str); - FR_CHECK_CHANGED_CFG_STR("tls-service-pem", ssl_service_pem, changed_str); - FR_CHECK_CHANGED_CFG_STR("tls-cert-bundle", tls_cert_bundle, changed_str); FR_CHECK_CHANGED_CFG_STRLIST("proxy-protocol-port", proxy_protocol_port, changed_str); FR_CHECK_CHANGED_CFG_STRLIST("tls-additional-port", tls_additional_port, changed_str); FR_CHECK_CHANGED_CFG_STR("interface-automatic-ports", if_automatic_ports, changed_str); @@ -5019,6 +5036,19 @@ fr_construct_clear(struct fast_reload_construct* ct) wait_limits_free(&ct->wait_limits_netblock); wait_limits_free(&ct->wait_limits_cookie_netblock); domain_limits_free(&ct->domain_limits); +#ifdef HAVE_SSL + /* The SSL contexts can be SSL_CTX_free here. It is reference + * counted. So ongoing transfers with can continue. + * Once they are done, the context is freed. */ + SSL_CTX_free((SSL_CTX*)ct->listen_dot_sslctx); + SSL_CTX_free((SSL_CTX*)ct->connect_dot_sslctx); + SSL_CTX_free((SSL_CTX*)ct->listen_doh_sslctx); +#endif /* HAVE_SSL */ +#ifdef HAVE_NGTCP2 + SSL_CTX_free((SSL_CTX*)ct->listen_quic_sslctx); +#endif + free(ct->ssl_service_key); + free(ct->ssl_service_pem); /* Delete the log identity here so that the global value is not * reset by config_delete. */ if(ct->oldcfg && ct->oldcfg->log_identity) { @@ -5271,6 +5301,8 @@ fr_printmem(struct fast_reload_thread* fr, size_t mem = 0; if(fr_poll_for_quit(fr)) return 1; + mem += getmem_str(ct->ssl_service_key); + mem += getmem_str(ct->ssl_service_pem); mem += views_get_mem(ct->views); mem += respip_set_get_mem(ct->respip_set); mem += auth_zones_get_mem(ct->auth_zones); @@ -5504,6 +5536,96 @@ auth_zones_check_changes(struct fast_reload_thread* fr, return 1; } +/** Check if the sslctxs have changed. */ +static int +fr_check_sslctx_change(struct fast_reload_thread* fr, + struct config_file* newcfg) +{ +#ifdef HAVE_SSL + struct daemon* daemon = fr->worker->daemon; + if(newcfg->ssl_service_key && newcfg->ssl_service_key[0]) { + if(!daemon->ssl_service_key || + ssl_cert_changed(daemon, newcfg)) + return 1; + } else { + if(daemon->ssl_service_key) + return 1; /* it is removed */ + } + if((daemon->cfg->tls_cert_bundle && !newcfg->tls_cert_bundle) || + (!daemon->cfg->tls_cert_bundle && newcfg->tls_cert_bundle) || + (daemon->cfg->tls_cert_bundle && newcfg->tls_cert_bundle && + strcmp(daemon->cfg->tls_cert_bundle, newcfg->tls_cert_bundle)!=0)) + return 1; /* The tls-cert-bundle has changed and return + true here makes it reload the connect_dot_sslctx. */ +#else + (void)fr; (void)newcfg; +#endif /* HAVE_SSL */ + return 0; +} + +/** Create the SSL CTXs when they have changed. */ +static int +ct_create_sslctxs(struct fast_reload_construct* ct, + struct config_file* newcfg, struct daemon* daemon) +{ +#ifdef HAVE_SSL + char* chroot = daemon->chroot; + char* key = newcfg->ssl_service_key; + char* pem = newcfg->ssl_service_pem; + + if(!(newcfg->ssl_service_key && newcfg->ssl_service_key[0])) { + /* Leave listen ctxs and file str at NULL */ + ct->connect_dot_sslctx = daemon_setup_connect_dot_sslctx( + daemon, newcfg); + return 1; + } + + if(chroot && strncmp(key, chroot, strlen(chroot)) == 0) + key += strlen(chroot); + if(chroot && pem && strncmp(pem, chroot, strlen(chroot)) == 0) + pem += strlen(chroot); + + ct->listen_dot_sslctx = daemon_setup_listen_dot_sslctx(daemon, newcfg); +#ifdef HAVE_NGHTTP2_NGHTTP2_H + if(cfg_has_https(newcfg)) { + ct->listen_doh_sslctx = daemon_setup_listen_doh_sslctx( + daemon, newcfg); + } +#endif +#ifdef HAVE_NGTCP2 + if(cfg_has_quic(newcfg)) { + ct->listen_quic_sslctx = daemon_setup_listen_quic_sslctx( + daemon, newcfg); + } +#endif /* HAVE_NGTCP2 */ + ct->connect_dot_sslctx = daemon_setup_connect_dot_sslctx(daemon, + newcfg); + + /* Store mtime and names */ + ct->ssl_service_key = strdup(newcfg->ssl_service_key); + if(!ct->ssl_service_key) { + log_err("ct_create_sslctxs: out of memory"); + return 0; + } + ct->ssl_service_pem = strdup(newcfg->ssl_service_pem); + if(!ct->ssl_service_pem) { + log_err("ct_create_sslctxs: out of memory"); + return 0; + } + if(!file_get_mtime(key, &ct->mtime_ssl_service_key, + &ct->mtime_ns_ssl_service_key, NULL)) + log_err("Could not stat(%s): %s", + key, strerror(errno)); + if(!file_get_mtime(pem, &ct->mtime_ssl_service_pem, + &ct->mtime_ns_ssl_service_pem, NULL)) + log_err("Could not stat(%s): %s", + pem, strerror(errno)); +#else + (void)ct; (void)newcfg; (void)daemon; +#endif /* HAVE_SSL */ + return 1; +} + /** fast reload thread, construct from config the new items */ static int fr_construct_from_config(struct fast_reload_thread* fr, @@ -5511,6 +5633,13 @@ fr_construct_from_config(struct fast_reload_thread* fr, { int have_view_respip_cfg = 0; + fr->sslctxs_changed = fr_check_sslctx_change(fr, newcfg); + if(fr->sslctxs_changed) { + if(!ct_create_sslctxs(ct, newcfg, fr->worker->daemon)) { + fr_construct_clear(ct); + return 0; + } + } if(!(ct->views = views_create())) { fr_construct_clear(ct); return 0; @@ -5788,6 +5917,44 @@ auth_zones_swap(struct auth_zones* az, struct auth_zones* data) * the xfer elements can continue to be their callbacks. */ } +/** Swap two void* */ +static void +void_ptr_swap(void** a, void **b) +{ + void* tmp = *a; + *a = *b; + *b = tmp; +} + +/** Swap two char* */ +static void +char_ptr_swap(char** a, char **b) +{ + char* tmp = *a; + *a = *b; + *b = tmp; +} + +/** Swap and set ssl ctx information */ +static void +sslctxs_swap(struct daemon* daemon, struct fast_reload_construct* ct) +{ + void_ptr_swap(&daemon->listen_dot_sslctx, &ct->listen_dot_sslctx); + void_ptr_swap(&daemon->connect_dot_sslctx, &ct->connect_dot_sslctx); +#ifdef HAVE_NGHTTP2_NGHTTP2_H + void_ptr_swap(&daemon->listen_doh_sslctx, &ct->listen_doh_sslctx); +#endif +#ifdef HAVE_NGTCP2 + void_ptr_swap(&daemon->listen_quic_sslctx, &ct->listen_quic_sslctx); +#endif /* HAVE_NGTCP2 */ + char_ptr_swap(&daemon->ssl_service_key, &ct->ssl_service_key); + char_ptr_swap(&daemon->ssl_service_pem, &ct->ssl_service_pem); + daemon->mtime_ssl_service_key = ct->mtime_ssl_service_key; + daemon->mtime_ns_ssl_service_key = ct->mtime_ns_ssl_service_key; + daemon->mtime_ssl_service_pem = ct->mtime_ssl_service_pem; + daemon->mtime_ns_ssl_service_pem = ct->mtime_ns_ssl_service_pem; +} + #if defined(ATOMIC_POINTER_LOCK_FREE) && defined(HAVE_LINK_ATOMIC_STORE) /** Fast reload thread, if atomics are available, copy the config items * one by one with atomic store operations. */ @@ -6169,6 +6336,15 @@ fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg, COPY_VAR_int(iter_scrub_cname); COPY_VAR_int(max_global_quota); COPY_VAR_int(iter_scrub_promiscuous); + +#undef COPY_VAR_int +#undef COPY_VAR_ptr +#undef COPY_VAR_unsigned_int +#undef COPY_VAR_size_t +#undef COPY_VAR_uint8_t +#undef COPY_VAR_uint16_t +#undef COPY_VAR_uint32_t +#undef COPY_VAR_int32_t } #endif /* ATOMIC_POINTER_LOCK_FREE && HAVE_LINK_ATOMIC_STORE */ @@ -6394,6 +6570,9 @@ fr_reload_config(struct fast_reload_thread* fr, struct config_file* newcfg, daemon->env->cachedb_enabled = cachedb_is_enabled(&daemon->mods, daemon->env); #endif + if(fr->sslctxs_changed) { + sslctxs_swap(daemon, ct); + } #ifdef USE_DNSTAP if(env->cfg->dnstap) { if(!fr->fr_nopause) @@ -7590,6 +7769,42 @@ fr_worker_pickup_auth_changes(struct worker* worker, } } +/** Fast reload, the worker picks up changes in listen_dnsport. */ +static void +fr_worker_pickup_listen_dnsport(struct worker* worker) +{ + struct listen_dnsport* front = worker->front; + struct daemon* daemon = worker->daemon; + if(worker->daemon->fast_reload_thread->sslctxs_changed) { + struct listen_list* ll; + void* dot_sslctx = daemon->listen_dot_sslctx; + void* doh_sslctx = daemon->listen_doh_sslctx; + void* quic_sslctx = daemon->listen_quic_sslctx; + for(ll = front->cps; ll; ll = ll->next) { + struct comm_point* cp = ll->com; + if(cp->type == comm_tcp_accept && + cp->tcp_handlers && + cp->max_tcp_count > 0 && + cp->tcp_handlers[0]->type == comm_http) { + if(cp->ssl) + cp->ssl = doh_sslctx; + } else if(cp->type == comm_tcp_accept) { + if(cp->ssl) + cp->ssl = dot_sslctx; +#ifdef HAVE_NGTCP2 + } else if(cp->type == comm_doq) { + if(cp->ssl) { + cp->ssl = quic_sslctx; + if(cp->doq_socket) + cp->doq_socket->ctx = + (SSL_CTX*)quic_sslctx; + } +#endif + } + } + } +} + /** Fast reload, the worker picks up changes in outside_network. */ static void fr_worker_pickup_outside_network(struct worker* worker) @@ -7605,6 +7820,8 @@ fr_worker_pickup_outside_network(struct worker* worker) outnet->tcp_reuse_timeout = cfg->tcp_reuse_timeout; outnet->tcp_auth_query_timeout = cfg->tcp_auth_query_timeout; outnet->delayclose = cfg->delay_close; + if(worker->daemon->fast_reload_thread->sslctxs_changed) + outnet->sslctx = worker->daemon->connect_dot_sslctx; if(outnet->delayclose) { #ifndef S_SPLINT_S outnet->delay_tv.tv_sec = cfg->delay_close/1000; @@ -7675,6 +7892,7 @@ fast_reload_worker_pickup_changes(struct worker* worker) #ifdef USE_CACHEDB worker->env.cachedb_enabled = worker->daemon->env->cachedb_enabled; #endif + fr_worker_pickup_listen_dnsport(worker); fr_worker_pickup_outside_network(worker); #ifdef USE_DNSTAP fr_worker_pickup_dnstap_changes(worker); diff --git a/daemon/remote.h b/daemon/remote.h index 77c00a597..e9c392fd8 100644 --- a/daemon/remote.h +++ b/daemon/remote.h @@ -255,6 +255,8 @@ struct fast_reload_thread { struct fast_reload_auth_change* auth_zone_change_list; /** the old tree of auth zones, to lookup. */ struct auth_zones* old_auth_zones; + /** If the ssl ctxs have changed. */ + int sslctxs_changed; }; /** diff --git a/daemon/unbound.c b/daemon/unbound.c index 688804743..a787b76fe 100644 --- a/daemon/unbound.c +++ b/daemon/unbound.c @@ -463,57 +463,13 @@ detach(void) #endif /* HAVE_DAEMON */ } -#ifdef HAVE_SSL -/* setup a listening ssl context, fatal_exit() on any failure */ +/** setup the remote and ticket keys */ static void -setup_listen_sslctx(void** ctx, int is_dot, int is_doh, struct config_file* cfg) -{ - if(!(*ctx = listen_sslctx_create( - cfg->ssl_service_key, cfg->ssl_service_pem, NULL, - cfg->tls_ciphers, cfg->tls_ciphersuites, - (cfg->tls_session_ticket_keys.first && - cfg->tls_session_ticket_keys.first->str[0] != 0), - is_dot, is_doh, cfg->tls_use_system_policy_versions))) { - fatal_exit("could not set up listen SSL_CTX"); - } -} -#endif /* HAVE_SSL */ - -/* setups the needed ssl contexts, fatal_exit() on any failure */ -static void -setup_sslctxs(struct daemon* daemon, struct config_file* cfg) +setup_sslctx_remote(struct daemon* daemon, struct config_file* cfg) { #ifdef HAVE_SSL if(!(daemon->rc = daemon_remote_create(cfg))) fatal_exit("could not set up remote-control"); - if(cfg->ssl_service_key && cfg->ssl_service_key[0]) { - /* setup the session keys; the callback to use them will be - * attached to each sslctx separately */ - if(cfg->tls_session_ticket_keys.first && - cfg->tls_session_ticket_keys.first->str[0] != 0) { - if(!listen_sslctx_setup_ticket_keys( - cfg->tls_session_ticket_keys.first)) { - fatal_exit("could not set session ticket SSL_CTX"); - } - } - (void)setup_listen_sslctx(&daemon->listen_dot_sslctx, 1, 0, cfg); -#ifdef HAVE_NGHTTP2_NGHTTP2_H - if(cfg_has_https(cfg)) { - (void)setup_listen_sslctx(&daemon->listen_doh_sslctx, 0, 1, cfg); - } -#endif -#ifdef HAVE_NGTCP2 - if(cfg_has_quic(cfg)) { - if(!(daemon->listen_quic_sslctx = quic_sslctx_create( - cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) { - fatal_exit("could not set up quic SSL_CTX"); - } - } -#endif /* HAVE_NGTCP2 */ - } - if(!(daemon->connect_dot_sslctx = connect_sslctx_create(NULL, NULL, - cfg->tls_cert_bundle, cfg->tls_win_cert))) - fatal_exit("could not set up connect SSL_CTX"); #else /* HAVE_SSL */ (void)daemon;(void)cfg; #endif /* HAVE_SSL */ @@ -545,7 +501,8 @@ perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode, #endif /* read ssl keys while superuser and outside chroot */ - (void)setup_sslctxs(daemon, cfg); + setup_sslctx_remote(daemon, cfg); + daemon_setup_sslctxs(daemon, cfg); /* init syslog (as root) if needed, before daemonize, otherwise * a fork error could not be printed since daemonize closed stderr.*/ diff --git a/doc/Changelog b/doc/Changelog index 19137495b..5981ac2fa 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,52 @@ +24 March 2026: Wouter + - Fix to check for invalid http content length and chunk size, + and to check the RR rdata field lengths when decompressing and + inserting RRs from an authority zone transfer. This stops + large memory use and heap buffer-overflow read errors. Thanks + to Haruto Kimura (Stella) for the report. + +20 March 2026: Wouter + - Fix for testcode pktview to check buffer size and log errors. + +13 March 2026: Yorgos + - Fix to ignore out-of-zone DNAME records for CNAME synthesis. Thanks + to Yuxiao Wu, Yiyi Wang, Zhang Chao, Baojun Liu, and Haixin Duan from + Tsinghua University. + +13 March 2026: Wouter + - Fix #278: DoT: complete unbound restart required on certificate + renew. Fix so that a reload checks if the files have changed, and + if so, reload the contexts. Also for DoH, DoQ and outgoing DoT. + - iana portlist updated. + - For #278: fast_reload can reload tls-service-key, tls-service-pem + and tls-cert-bundle changes. It checks the modification time of + the tls-service-key and tls-service-pem files for update. + - Fix detection of http listening port in fast_reload. + - Fix to add tls-service-key to memory printout for fast_reload. + +9 March 2026: Wouter + - Fix compile failure in unbound-checkconf for older gcc compiler. + - Merge #1418: Apply cache TTL policy to DNAME and synthesized + CNAME on wire path. + +6 March 2026: Wouter + - Merge #1415: Add lock unlock for view in memory error handling. + +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. + Thanks to Kunta Chu, School of Software, Tsinghua University, + Taofei Guo, Peking University, and Jianjun Chen, Institute for + Network Sciences and Cyberspace, Tsinghua University for the + report. The private-address option is fixed to also elide + SVCB and HTTPS records that match the filter. + - Update generated man pages. + 4 March 2026: Yorgos - For #1411: Introduce a failing case in the rpl test so that it only passes with the fix in place. diff --git a/doc/unbound-control.rst b/doc/unbound-control.rst index 2f5520824..d95d6dd0b 100644 --- a/doc/unbound-control.rst +++ b/doc/unbound-control.rst @@ -170,6 +170,8 @@ There are several commands that the server understands. :ref:`tcp-auth-query-timeout`, :ref:`delay-close`. :ref:`iter-scrub-promiscuous`. + :ref:`tls-service-key`. + :ref:`tls-service-pem`. It does not work with :ref:`interface` and diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 194a3c076..593a76189 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -382,6 +382,10 @@ Default depends on compile options. Larger numbers need extra resources from the operating system. For performance a very large value is best, use libevent to make this possible. +Should be higher (preferably double) than the value of +\fI\%num\-queries\-per\-thread\fP to +account for cases where the request list is full and avoid file descriptor +starvation. .sp Default: 4096 (libevent) / 960 (minievent) / 48 (windows) .UNINDENT @@ -2275,6 +2279,11 @@ This protects against so\-called DNS Rebinding, where a user browser is turned into a network proxy, allowing remote access through the browser to other parts of your private network. .sp +The option removes resource records of types A, AAAA, SVCB and HTTPS +that match the filter. +Inside the SVCB and HTTPS records, the svcparams of type ipv4hint +and ipv6hint are checked for matches. +.sp Some names can be allowed to contain your private addresses, by default all the \fI\%local\-data\fP that you configured is allowed to, and you can specify additional names using diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index b5fb32308..fb7486bfe 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -368,6 +368,10 @@ These options are part of the ``server:`` section. Larger numbers need extra resources from the operating system. For performance a very large value is best, use libevent to make this possible. + Should be higher (preferably double) than the value of + :ref:`num-queries-per-thread` to + account for cases where the request list is full and avoid file descriptor + starvation. Default: 4096 (libevent) / 960 (minievent) / 48 (windows) @@ -1046,9 +1050,13 @@ These options are part of the ``server:`` section. certificate is in the :ref:`tls-service-pem` file and it must also be specified if :ref:`tls-service-key` is specified. - Enabling or disabling this service requires a restart (a reload is not - enough), because the key is read while root permissions are held and before - chroot (if any). + If the key is stored with root permissions or outside of chroot, then + a change or enabling or disabling requires a restart (a reload is not + enough). + But if the key file (and tls-service-pem file) are accessible, then they + are read in on reload, and fast_reload. + The server checks the modification time of the file (and the filename) + to see if the file has changed for reload. The ports enabled implicitly or explicitly via :ref:`tls-port` and :ref:`https-port` do not provide normal DNS TCP @@ -2017,6 +2025,11 @@ These options are part of the ``server:`` section. turned into a network proxy, allowing remote access through the browser to other parts of your private network. + The option removes resource records of types A, AAAA, SVCB and HTTPS + that match the filter. + Inside the SVCB and HTTPS records, the svcparams of type ipv4hint + and ipv6hint are checked for matches. + Some names can be allowed to contain your private addresses, by default all the :ref:`local-data` that you configured is allowed to, and you can specify additional names using diff --git a/iterator/iter_priv.c b/iterator/iter_priv.c index be4219216..6f885cec7 100644 --- a/iterator/iter_priv.c +++ b/iterator/iter_priv.c @@ -207,6 +207,168 @@ size_t priv_get_mem(struct iter_priv* priv) return sizeof(*priv) + regional_get_mem(priv->region); } +/** + * Check if svcparam ipv4hint contains a private address. + * @param priv: private address lookup struct. + * @param d: the data bytes. + * @param data_len: number of data bytes in the svcparam. + * @param addr: address to return the private address to log in to. + * It has space for IPv4 and IPv6 addresses. + * @param addrlen: length of the addr. Returns the correct size for the addr. + * @return true if the rdata contains a private address. + */ +static int svcb_ipv4hint_contains_priv_addr(struct iter_priv* priv, + uint8_t* d, uint16_t data_len, struct sockaddr_storage* addr, + socklen_t* addrlen) +{ + struct sockaddr_in sa; + *addrlen = (socklen_t)sizeof(struct sockaddr_in); + memset(&sa, 0, sizeof(struct sockaddr_in)); + sa.sin_family = AF_INET; + sa.sin_port = (in_port_t)htons(UNBOUND_DNS_PORT); + + while(data_len >= LDNS_IP4ADDRLEN) { + memmove(&sa.sin_addr, d, LDNS_IP4ADDRLEN); + memmove(addr, &sa, *addrlen); + if(priv_lookup_addr(priv, addr, *addrlen)) + return 1; + + d += LDNS_IP4ADDRLEN; + data_len -= LDNS_IP4ADDRLEN; + } + /* if data_len != 0 here, then the svcparam is malformed. */ + return 0; +} + +/** + * Check if svcparam ipv6hint contains a private address. + * @param priv: private address lookup struct. + * @param d: the data bytes. + * @param data_len: number of data bytes in the svcparam. + * @param addr: address to return the private address to log in to. + * It has space for IPv4 and IPv6 addresses. + * @param addrlen: length of the addr. Returns the correct size for the addr. + * @return true if the rdata contains a private address. + */ +static int svcb_ipv6hint_contains_priv_addr(struct iter_priv* priv, + uint8_t* d, uint16_t data_len, struct sockaddr_storage* addr, + socklen_t* addrlen) +{ + struct sockaddr_in6 sa; + *addrlen = (socklen_t)sizeof(struct sockaddr_in6); + memset(&sa, 0, sizeof(struct sockaddr_in6)); + sa.sin6_family = AF_INET6; + sa.sin6_port = (in_port_t)htons(UNBOUND_DNS_PORT); + + while(data_len >= LDNS_IP6ADDRLEN) { + memmove(&sa.sin6_addr, d, LDNS_IP6ADDRLEN); + memmove(addr, &sa, *addrlen); + if(priv_lookup_addr(priv, addr, *addrlen)) + return 1; + + d += LDNS_IP6ADDRLEN; + data_len -= LDNS_IP6ADDRLEN; + } + /* if data_len != 0 here, then the svcparam is malformed. */ + return 0; +} + +/** + * Check if type SVCB and HTTPS rdata contains a private address. + * @param priv: private address lookup struct. + * @param pkt: the packet. + * @param rr: the rr with rdata to check. + * @param addr: address to return the private address to log in to. + * @param addrlen: length of the addr. Initially the total size, on + * return the correct size for the addr. + * @return true if the rdata contains a private address. + */ +static int svcb_rr_contains_priv_addr(struct iter_priv* priv, + sldns_buffer* pkt, struct rr_parse* rr, struct sockaddr_storage* addr, + socklen_t* addrlen) +{ + uint8_t* d = rr->ttl_data; + uint16_t svcparamkey, data_len, rdatalen; + size_t oldpos, dname_len, dname_start, dname_compr_len; + d += 4; /* skip TTL */ + rdatalen = sldns_read_uint16(d); /* read rdata length */ + d += 2; + + if(rdatalen < 2 /* priority */ + 1 /* 1 length target */) + return 0; /* malformed, too short */ + d += 2; /* skip priority */ + rdatalen -= 2; + oldpos = sldns_buffer_position(pkt); + sldns_buffer_set_position(pkt, (size_t)(d - sldns_buffer_begin(pkt))); + dname_start = sldns_buffer_position(pkt); + dname_len = pkt_dname_len(pkt); + dname_compr_len = sldns_buffer_position(pkt) - dname_start; + sldns_buffer_set_position(pkt, oldpos); + if(dname_len == 0) + return 0; /* dname malformed */ + if(dname_compr_len > rdatalen) + return 0; /* malformed */ + d += dname_compr_len; /* skip target */ + rdatalen -= dname_compr_len; + + while(rdatalen >= 4) { + svcparamkey = sldns_read_uint16(d); + data_len = sldns_read_uint16(d+2); + d += 4; + rdatalen -= 4; + + /* verify that we have data_len data */ + if(data_len > rdatalen) { + /* It is malformed, but if there are addresses + * in there it can be rejected. */ + data_len = rdatalen; + } + + if(!data_len) + continue; /* no data for the svcparamkey */ + + if(svcparamkey == SVCB_KEY_IPV4HINT) { + if(svcb_ipv4hint_contains_priv_addr(priv, d, data_len, + addr, addrlen)) + return 1; + } else if(svcparamkey == SVCB_KEY_IPV6HINT) { + if(svcb_ipv6hint_contains_priv_addr(priv, d, data_len, + addr, addrlen)) + return 1; + } + d += data_len; + rdatalen -= data_len; + } + /* If rdatalen != 0 here, then the svcb rdata is malformed. */ + return 0; +} + +/** + * Check if the SVCB and HTTPS rrset is bad. + * @param priv: private address lookup struct. + * @param pkt: the packet. + * @param rrset: the rrset to check. + * @return 1 if the entire rrset has to be removed. 0 if not. + * It removes RRs if they have private addresses, and log that. + */ +static int priv_svcb_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt, + struct rrset_parse* rrset) +{ + struct rr_parse* rr, *prev = NULL; + struct sockaddr_storage addr; + socklen_t addrlen = (socklen_t)sizeof(addr); + for(rr = rrset->rr_first; rr; rr = rr->next) { + if(svcb_rr_contains_priv_addr(priv, pkt, rr, &addr, + &addrlen)) { + if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, addrlen)) + return 1; + continue; + } + prev = rr; + } + return 0; +} + int priv_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt, struct rrset_parse* rrset) { @@ -268,7 +430,11 @@ int priv_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt, } prev = rr; } - } + } else if(rrset->type == LDNS_RR_TYPE_SVCB || + rrset->type == LDNS_RR_TYPE_HTTPS) { + if(priv_svcb_rrset_bad(priv, pkt, rrset)) + return 1; + } } return 0; } diff --git a/iterator/iter_scrub.c b/iterator/iter_scrub.c index 8507a3fb6..757d95a3d 100644 --- a/iterator/iter_scrub.c +++ b/iterator/iter_scrub.c @@ -285,6 +285,17 @@ synth_cname_rrset(uint8_t** sname, size_t* snamelen, uint8_t* alias, return NULL; memmove(cn->rr_first->ttl_data, rrset->rr_first->ttl_data, sizeof(uint32_t)); /* RFC6672: synth CNAME TTL == DNAME TTL */ + /* Apply cache TTL policy so DNAME and synthesized CNAME stay equal + * and respect cache-min-ttl/cache-max-ttl (same as rdata_copy path). */ + if(!SERVE_ORIGINAL_TTL) { + uint32_t ttl = sldns_read_uint32(cn->rr_first->ttl_data); + time_t ttl_t = (time_t)ttl; + if(ttl_t < MIN_TTL) ttl_t = MIN_TTL; + if(ttl_t > MAX_TTL) ttl_t = MAX_TTL; + ttl = (uint32_t)ttl_t; + sldns_write_uint32(cn->rr_first->ttl_data, ttl); + sldns_write_uint32(rrset->rr_first->ttl_data, ttl); + } sldns_write_uint16(cn->rr_first->ttl_data+4, aliaslen); memmove(cn->rr_first->ttl_data+6, alias, aliaslen); cn->rr_first->size = sizeof(uint16_t)+aliaslen; @@ -455,8 +466,9 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, pkt, msg, prev, &rrset); continue; } - if(rrset->type == LDNS_RR_TYPE_DNAME && - pkt_strict_sub(pkt, sname, rrset->dname)) { + if(rrset->type == LDNS_RR_TYPE_DNAME && + pkt_strict_sub(pkt, sname, rrset->dname) && + pkt_sub(pkt, rrset->dname, zonename)) { /* check if next rrset is correct CNAME. else, * synthesize a CNAME */ struct rrset_parse* nx = rrset->rrset_all_next; @@ -502,8 +514,6 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, log_err("out of memory synthesizing CNAME"); return 0; } - /* FIXME: resolve the conflict between synthesized - * CNAME ttls and the cache. */ rrset = nx; continue; @@ -525,7 +535,8 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, if(nx && nx->section == LDNS_SECTION_ANSWER && nx->type == LDNS_RR_TYPE_DNAME && nx->rr_count == 1 && - pkt_strict_sub(pkt, sname, nx->dname)) { + pkt_strict_sub(pkt, sname, nx->dname) && + pkt_sub(pkt, nx->dname, zonename)) { /* there is a DNAME after this CNAME, it * is in the ANSWER section, and the DNAME * applies to the name we cover */ @@ -972,8 +983,10 @@ scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg, } /* remove private addresses */ - if( (rrset->type == LDNS_RR_TYPE_A || - rrset->type == LDNS_RR_TYPE_AAAA)) { + if(rrset->type == LDNS_RR_TYPE_A || + rrset->type == LDNS_RR_TYPE_AAAA || + rrset->type == LDNS_RR_TYPE_SVCB || + rrset->type == LDNS_RR_TYPE_HTTPS) { /* do not set servfail since this leads to too * many drops of other people using rfc1918 space */ diff --git a/respip/respip.c b/respip/respip.c index f39ce5805..ff12114de 100644 --- a/respip/respip.c +++ b/respip/respip.c @@ -973,6 +973,9 @@ respip_rewrite_reply(const struct query_info* qinfo, lock_rw_unlock(&raddr->lock); lock_rw_unlock(&a->lock); lock_rw_unlock(&az->rpz_lock); + if(view) { + lock_rw_unlock(&view->lock); + } return 0; } if(rpz_used) { diff --git a/services/authzone.c b/services/authzone.c index 3965b5e17..9778cf1b7 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -1384,6 +1384,9 @@ decompress_rr_into_buffer(struct sldns_buffer* buf, uint8_t* pkt, len = 0; break; case LDNS_RDF_TYPE_STR: + /* Check rdlen for resilience, because it is + * checked above, that rdlen > 0 */ + if(rdlen < 1) return 0; /* malformed */ len = rd[0] + 1; break; default: @@ -1391,6 +1394,8 @@ decompress_rr_into_buffer(struct sldns_buffer* buf, uint8_t* pkt, break; } if(len) { + if(len > rdlen) + return 0; /* malformed */ if(!sldns_buffer_available(buf, len)) return 0; /* too long for buffer */ sldns_buffer_write(buf, rd, len); 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..a8e19241f 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,56 @@ 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: + ; /* statement to jump to, for older gcc. */ + } +#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/testcode/pktview.c b/testcode/pktview.c index 12e0d8edb..4cada926b 100644 --- a/testcode/pktview.c +++ b/testcode/pktview.c @@ -59,12 +59,16 @@ static void usage(char* argv[]) /** read hex input */ static void read_input(sldns_buffer* pkt, FILE* in) { - char buf[102400]; + /* Buffer for 64Kib packet, in hex, with spaces and comments. */ + char buf[1024000]; char* np = buf; while(fgets(np, (int)sizeof(buf) - (np-buf), in)) { if(buf[0] == ';') /* comment */ continue; np = &np[strlen(np)]; + if((size_t)(np-buf) >= sizeof(buf)-1) + fatal_exit("input too large (%lu bytes)", + (unsigned long)sizeof(buf)); } hex_to_buf(pkt, buf); } @@ -188,10 +192,16 @@ static void analyze(sldns_buffer* pkt) /** main program for pktview */ int main(int argc, char* argv[]) { - sldns_buffer* pkt = sldns_buffer_new(65553); + sldns_buffer* pkt; + + log_init(NULL, 0, NULL); + log_ident_set("pktview"); + if(argc != 1) { usage(argv); } + + pkt = sldns_buffer_new(65553); if(!pkt) fatal_exit("out of memory"); read_input(pkt, stdin); diff --git a/testdata/iter_priv_svcb.rpl b/testdata/iter_priv_svcb.rpl new file mode 100644 index 000000000..5deae4250 --- /dev/null +++ b/testdata/iter_priv_svcb.rpl @@ -0,0 +1,283 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + minimal-responses: yes + iter-scrub-promiscuous: yes + + private-address: 10.0.0.0/8 + private-address: 172.16.0.0/12 + private-address: 192.168.0.0/16 + private-address: 169.254.0.0/16 + private-address: fd00::/8 + private-address: fe80::/10 + + private-domain: "example.net" + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. + +CONFIG_END + +SCENARIO_BEGIN Test iterator scrubber with private addresses in SVCB. + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN A +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +; root server authoritative for example.net too. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +mail.example.net. IN SVCB +SECTION ANSWER +mail.example.net. IN SVCB 1 foo.example.net. ipv4hint=10.20.30.40 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.com. IN A +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.com. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.com. IN A +SECTION ANSWER +ns.example.com. IN A 1.2.3.4 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.com. IN AAAA +SECTION ANSWER +SECTION AUTHORITY +example.com. IN SOA ns.example.com. root.example.com. 4 14400 3600 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.com. IN SVCB +SECTION ANSWER +www.example.com. IN SVCB 1 foo.example.com. ipv4hint=192.20.30.40 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +mail.example.com. IN SVCB +SECTION ANSWER +mail.example.com. IN SVCB 1 foo.example.com. ipv6hint=fe80::15 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +foo.example.com. IN SVCB +SECTION ANSWER +foo.example.com. IN SVCB 1 foo.example.com. ipv4hint=10.20.30.40 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +toss.example.com. IN SVCB +SECTION ANSWER +toss.example.com. IN SVCB 1 foo.example.com. ipv4hint=10.20.30.40 +toss.example.com. IN SVCB 1 foo.example.com. ipv4hint=10.20.30.40 +toss.example.com. IN SVCB 1 foo.example.com. ipv4hint=1.2.3.4 +toss.example.com. IN SVCB 1 foo.example.com. ipv6hint=fe80::15 +toss.example.com. IN SVCB 1 foo.example.com. ipv4hint=10.20.30.41 +toss.example.com. IN SVCB 1 foo.example.com. ipv4hint=192.0.2.1,10.20.30.42,192.0.2.2 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; public address is not scrubbed +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN SVCB +ENTRY_END + +; recursion happens here. +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN SVCB +SECTION ANSWER +www.example.com. IN SVCB 1 foo.example.com. ipv4hint=192.20.30.40 +ENTRY_END + +; IPv4 address is scrubbed +STEP 3 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +foo.example.com. IN SVCB +ENTRY_END + +; recursion happens here. +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +foo.example.com. IN SVCB +SECTION ANSWER +; scrubbed away +ENTRY_END + +; IPv6 address is scrubbed +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +mail.example.com. IN SVCB +ENTRY_END + +STEP 30 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +mail.example.com. IN SVCB +SECTION ANSWER +ENTRY_END + +; allowed domain is not scrubbed. +STEP 40 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +mail.example.net. IN SVCB +ENTRY_END + +STEP 50 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +mail.example.net. IN SVCB +SECTION ANSWER +mail.example.net. IN SVCB 1 foo.example.net. ipv4hint=10.20.30.40 +ENTRY_END + +; rest of RRset intact, only 10/8 tossed away. +STEP 60 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +toss.example.com. IN SVCB +ENTRY_END + +STEP 70 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +toss.example.com. IN SVCB +SECTION ANSWER +toss.example.com. IN SVCB 1 foo.example.com. ipv4hint=1.2.3.4 +ENTRY_END + +SCENARIO_END diff --git a/testdata/iter_scrub_dname_out_of_zone.rpl b/testdata/iter_scrub_dname_out_of_zone.rpl new file mode 100644 index 000000000..8dbafdf72 --- /dev/null +++ b/testdata/iter_scrub_dname_out_of_zone.rpl @@ -0,0 +1,122 @@ +; config options +server: + harden-referral-path: no + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + minimal-responses: no + iter-scrub-promiscuous: no + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test scrub of out-of-zone DNAME in answer section + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +x.y.example.com. IN A +ENTRY_END + +; root prime is sent +STEP 20 CHECK_OUT_QUERY +ENTRY_BEGIN +MATCH qname qtype opcode +SECTION QUESTION +. IN NS +ENTRY_END +STEP 30 REPLY +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +; query sent to root server +STEP 40 CHECK_OUT_QUERY +ENTRY_BEGIN +MATCH qname qtype opcode +SECTION QUESTION +x.y.example.com. IN A +ENTRY_END +STEP 50 REPLY +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +x.y.example.com. IN A +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +; query sent to .com server +STEP 60 CHECK_OUT_QUERY +ENTRY_BEGIN +MATCH qname qtype opcode +SECTION QUESTION +x.y.example.com. IN A +ENTRY_END + +STEP 70 REPLY +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +x.y.example.com. IN A +SECTION AUTHORITY +example.com. IN NS ns1.example.com. +SECTION ADDITIONAL +ns1.example.com. IN A 168.192.2.2 +ENTRY_END + +STEP 80 CHECK_OUT_QUERY +ENTRY_BEGIN +MATCH qname qtype opcode +SECTION QUESTION +x.y.example.com. IN A +ENTRY_END + +STEP 90 REPLY +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +x.y.example.com. IN A +SECTION ANSWER +com. DNAME z.example.com. +SECTION AUTHORITY +example.com. IN NS ns1.example.com. +SECTION ADDITIONAL +ns1.example.com. IN A 168.192.2.2 +ENTRY_END + +; answer to first query +; nodata answer since the DNAME is ignored for synthesis and scrubbed +; all together. +STEP 120 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA +SECTION QUESTION +x.y.example.com. IN A +SECTION ANSWER +SECTION AUTHORITY +example.com. IN NS ns1.example.com. +SECTION ADDITIONAL +ns1.example.com. IN A 168.192.2.2 +ENTRY_END + +SCENARIO_END diff --git a/util/config_file.c b/util/config_file.c index f05ff48e0..d38ff516a 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -62,6 +62,9 @@ #include "sldns/wire2str.h" #include "sldns/parseutil.h" #include "iterator/iterator.h" +#ifdef HAVE_SYS_STAT_H +#include +#endif #ifdef HAVE_GLOB_H # include #endif @@ -3008,3 +3011,27 @@ cfg_has_quic(struct config_file* cfg) return 0; #endif } + +int +file_get_mtime(const char* file, time_t* mtime, long* ns, int* nonexist) +{ + struct stat s; + if(stat(file, &s) != 0) { + *mtime = 0; + *ns = 0; + if(nonexist) + *nonexist = (errno == ENOENT); + return 0; + } + if(nonexist) + *nonexist = 0; + *mtime = s.st_mtime; +#ifdef HAVE_STRUCT_STAT_ST_MTIMENSEC + *ns = s.st_mtimensec; +#elif defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) + *ns = s.st_mtim.tv_nsec; +#else + *ns = 0; +#endif + return 1; +} diff --git a/util/config_file.h b/util/config_file.h index 3ed8c5906..fe6595780 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -1504,4 +1504,7 @@ size_t getmem_str(char* str); */ int cfg_ports_list_contains(char* ports, int p); +/** get the file mtime stat (or error, with errno and nonexist) */ +int file_get_mtime(const char* file, time_t* mtime, long* ns, int* nonexist); + #endif /* UTIL_CONFIG_FILE_H */ diff --git a/util/configparser.y b/util/configparser.y index 84bcff19c..e55ae92b2 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -2403,7 +2403,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"); diff --git a/util/iana_ports.inc b/util/iana_ports.inc index a60f2307e..60db889a4 100644 --- a/util/iana_ports.inc +++ b/util/iana_ports.inc @@ -3978,6 +3978,7 @@ 4791, 4792, 4793, +4794, 4800, 4801, 4802, @@ -4947,6 +4948,7 @@ 9162, 9163, 9164, +9183, 9191, 9200, 9201, @@ -5431,6 +5433,8 @@ 34962, 34963, 34964, +34965, +34966, 34980, 35001, 35004, diff --git a/util/net_help.c b/util/net_help.c index 9ad9a3bb8..36ed7a05e 100644 --- a/util/net_help.c +++ b/util/net_help.c @@ -1799,7 +1799,7 @@ void ub_openssl_lock_delete(void) #endif /* OPENSSL_THREADS */ } -int listen_sslctx_setup_ticket_keys(struct config_strlist* tls_session_ticket_keys) { +int listen_sslctx_setup_ticket_keys(struct config_strlist* tls_session_ticket_keys, char* chroot) { #ifdef HAVE_SSL size_t s = 1; struct config_strlist* p; @@ -1817,14 +1817,18 @@ int listen_sslctx_setup_ticket_keys(struct config_strlist* tls_session_ticket_ke size_t n; unsigned char *data; FILE *f; + char* fstr; data = (unsigned char *)malloc(80); if(!data) return 0; - f = fopen(p->str, "rb"); + fstr = p->str; + if(chroot && strncmp(fstr, chroot, strlen(chroot)) == 0) + fstr += strlen(chroot); + f = fopen(fstr, "rb"); if(!f) { - log_err("could not read tls-session-ticket-key %s: %s", p->str, strerror(errno)); + log_err("could not read tls-session-ticket-key %s: %s", fstr, strerror(errno)); free(data); return 0; } @@ -1832,11 +1836,11 @@ int listen_sslctx_setup_ticket_keys(struct config_strlist* tls_session_ticket_ke fclose(f); if(n != 80) { - log_err("tls-session-ticket-key %s is %d bytes, must be 80 bytes", p->str, (int)n); + log_err("tls-session-ticket-key %s is %d bytes, must be 80 bytes", fstr, (int)n); free(data); return 0; } - verbose(VERB_OPS, "read tls-session-ticket-key: %s", p->str); + verbose(VERB_OPS, "read tls-session-ticket-key: %s", fstr); keys->key_name = data; keys->aes_key = data + 16; diff --git a/util/net_help.h b/util/net_help.h index 1f31a89c3..757aff855 100644 --- a/util/net_help.h +++ b/util/net_help.h @@ -567,9 +567,11 @@ void ub_openssl_lock_delete(void); /** * setup TLS session ticket * @param tls_session_ticket_keys: TLS ticket secret filenames + * @param chroot: if not NULL, the chroot that is in use. * @return false on failure (alloc failure). */ -int listen_sslctx_setup_ticket_keys(struct config_strlist* tls_session_ticket_keys); +int listen_sslctx_setup_ticket_keys( + struct config_strlist* tls_session_ticket_keys, char* chroot); /** Free memory used for TLS session ticket keys */ void listen_sslctx_delete_ticket_keys(void); diff --git a/util/netevent.c b/util/netevent.c index 01ef54dfe..a86e22518 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -4870,8 +4870,17 @@ http_process_initial_header(struct comm_point* c) return 0; } } else if(strncasecmp(line, "Content-Length: ", 16) == 0) { - if(!c->http_is_chunked) - c->tcp_byte_count = (size_t)atoi(line+16); + if(!c->http_is_chunked) { + char* end = NULL; + long long cl; + errno = 0; + cl = strtoll(line+16, &end, 10); + if(end == line+16 || errno != 0 || cl < 0) { + verbose(VERB_ALGO, "http invalid Content-Length: " ARG_LL "d", cl); + return 0; /* reject */ + } + c->tcp_byte_count = (size_t)cl; + } } else if(strncasecmp(line, "Transfer-Encoding: chunked", 19+7) == 0) { c->tcp_byte_count = 0; c->http_is_chunked = 1; @@ -4927,9 +4936,15 @@ http_process_chunk_header(struct comm_point* c) if(c->http_in_chunk_headers == 1) { /* read chunked start line */ char* end = NULL; - c->tcp_byte_count = (size_t)strtol(line, &end, 16); - if(end == line) + long chunk_sz; + errno = 0; + chunk_sz = strtol(line, &end, 16); + if(end == line || errno != 0 || chunk_sz < 0) { + verbose(VERB_ALGO, "http invalid chunk size: %ld", + chunk_sz); return 0; + } + c->tcp_byte_count = (size_t)chunk_sz; c->http_in_chunk_headers = 0; /* remove header text from front of buffer */ http_moveover_buffer(c->buffer);