/* 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
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" "
])
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
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 */
+
+/* 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* bundle, *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");
+ }
+ }
+ (void)setup_listen_sslctx(&daemon->listen_dot_sslctx, 1, 0,
+ cfg, chroot);
+#ifdef HAVE_NGHTTP2_NGHTTP2_H
+ if(cfg_has_https(cfg)) {
+ (void)setup_listen_sslctx(&daemon->listen_doh_sslctx,
+ 0, 1, cfg, chroot);
+ }
+#endif
+#ifdef HAVE_NGTCP2
+ if(cfg_has_quic(cfg)) {
+ if(!(daemon->listen_quic_sslctx = quic_sslctx_create(
+ key, pem, NULL))) {
+ fatal_exit("could not set up quic SSL_CTX");
+ }
+ }
+#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));
+ }
+ bundle = cfg->tls_cert_bundle;
+ if(chroot && bundle && strncmp(bundle, chroot, strlen(chroot)) == 0)
+ bundle += strlen(chroot);
+ if(!(daemon->connect_dot_sslctx = connect_sslctx_create(NULL, NULL,
+ bundle, cfg->tls_win_cert)))
+ fatal_exit("could not set up connect SSL_CTX");
+#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
+}
+
+/** See if the SSL cert files have changed */
+static int
+ssl_cert_changed(struct daemon* daemon, struct config_file* cfg)
+{
+ time_t mtime = 0;
+ long ns = 0;
+ log_assert(daemon->ssl_service_key && cfg->ssl_service_key);
+ if(strcmp(daemon->ssl_service_key, cfg->ssl_service_key) != 0)
+ return 1;
+ if(strcmp(daemon->ssl_service_pem, cfg->ssl_service_pem) != 0)
+ return 1;
+ if(!file_get_mtime(daemon->ssl_service_key, &mtime, &ns, NULL)) {
+ log_err("Could not stat(%s): %s",
+ daemon->ssl_service_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(daemon->ssl_service_pem, &mtime, &ns, NULL)) {
+ log_err("Could not stat(%s): %s",
+ daemon->ssl_service_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)
{
#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 */
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();
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 */
*/
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);
+
#endif /* DAEMON_H */
#endif /* HAVE_DAEMON */
}
-#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)
-{
- 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 */
+/** setup the remote and ticket keys */
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 */
#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.*/
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.
+
9 March 2026: Wouter
- Fix compile failure in unbound-checkconf for older gcc compiler.
- Merge #1418: Apply cache TTL policy to DNAME and synthesized
#include "sldns/wire2str.h"
#include "sldns/parseutil.h"
#include "iterator/iterator.h"
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
#ifdef HAVE_GLOB_H
# include <glob.h>
#endif
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;
+}
*/
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 */
#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;
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;
}
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;
/**
* 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);