Gitweb:
https://git.exim.org/exim.git/commitdiff/dc4ab0a186edc8b270c8fa486104fabc567d25e7
Commit: dc4ab0a186edc8b270c8fa486104fabc567d25e7
Parent: 625cd9501315e1010ecbf8718c88c8b79ce09e94
Author: Jeremy Harris <jgh146exb@???>
AuthorDate: Thu Oct 8 13:30:41 2020 +0100
Committer: Jeremy Harris <jgh146exb@???>
CommitDate: Fri Oct 9 20:29:31 2020 +0100
FreeBSD: TLS: preload configuration items
---
doc/doc-docbook/spec.xfpt | 4 +-
src/OS/os.h-FreeBSD | 2 +
src/src/daemon.c | 9 ++-
src/src/exim.h | 3 +
src/src/functions.h | 14 +++--
src/src/tls-gnu.c | 8 +--
src/src/tls-openssl.c | 9 ++-
src/src/tls.c | 146 ++++++++++++++++++++++++++++++++++++++++------
test/confs/1103 | 1 +
test/runtest | 10 +++-
10 files changed, 165 insertions(+), 41 deletions(-)
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 31c8c56..74c9b08 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -29308,7 +29308,7 @@ then the associated information is loaded at daemon startup.
It is made available
to child processes forked for handling received SMTP connections.
-This caching is currently only supported under Linux.
+This caching is currently only supported under Linux and FreeBSD.
If caching is not possible, for example if an item has to be dependent
on the peer host so contains a &$sender_host_name$& expansion, the load
@@ -29320,7 +29320,7 @@ containing files specified by these options.
The information specified by the main option &%tls_verify_certificates%&
is similarly cached so long as it specifies files explicitly
or (under GnuTLS) is the string &"system,cache"&.
-The latter case is not automatically invaludated;
+The latter case is not automatically invalidated;
it is the operator's responsibility to arrange for a daemon restart
any time the system certificate authority bundle is updated.
A HUP signal is sufficient for this.
diff --git a/src/OS/os.h-FreeBSD b/src/OS/os.h-FreeBSD
index c1720a7..0083642 100644
--- a/src/OS/os.h-FreeBSD
+++ b/src/OS/os.h-FreeBSD
@@ -70,4 +70,6 @@ extern ssize_t os_sendfile(int, int, off_t *, size_t);
/*******************/
+#define EXIM_HAVE_KEVENT
+
/* End */
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 4e90799..899aa0d 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -963,9 +963,8 @@ daemon_die(void)
{
int pid;
-#ifndef DISABLE_TLS
-if (tls_watch_fd >= 0)
- { close(tls_watch_fd); tls_watch_fd = -1; }
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
+tls_watch_invalidate();
#endif
if (daemon_notifier_fd >= 0)
@@ -2353,12 +2352,12 @@ for (;;)
if (!select_failed)
{
-#if defined(EXIM_HAVE_INOTIFY) && !defined(DISABLE_TLS)
+#if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
if (tls_watch_fd >= 0 && FD_ISSET(tls_watch_fd, &select_listen))
{
FD_CLR(tls_watch_fd, &select_listen);
tls_watch_trigger_time = time(NULL); /* Set up delayed event */
- (void) read(tls_watch_fd, big_buffer, big_buffer_size);
+ tls_watch_discard_event(tls_watch_fd);
break; /* to top of daemon loop */
}
#endif
diff --git a/src/src/exim.h b/src/src/exim.h
index 6669e80..f27ed5c 100644
--- a/src/src/exim.h
+++ b/src/src/exim.h
@@ -90,6 +90,9 @@ making unique names. */
#ifdef EXIM_HAVE_INOTIFY
# include <sys/inotify.h>
#endif
+#ifdef EXIM_HAVE_KEVENT
+# include <sys/event.h>
+#endif
/* C99 integer types, figure out how to undo this if needed for older systems */
diff --git a/src/src/functions.h b/src/src/functions.h
index c698519..38309e7 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -63,23 +63,27 @@ extern BOOL tls_dropprivs_validate_require_cipher(BOOL);
extern BOOL tls_export_cert(uschar *, size_t, void *);
extern int tls_feof(void);
extern int tls_ferror(void);
+extern uschar *tls_field_from_dn(uschar *, const uschar *);
extern void tls_free_cert(void **);
extern int tls_getc(unsigned);
extern uschar *tls_getbuf(unsigned *);
extern void tls_get_cache(void);
extern BOOL tls_import_cert(const uschar *, void **);
+extern BOOL tls_is_name_for_cert(const uschar *, void *);
+# ifdef USE_OPENSSL
+extern BOOL tls_openssl_options_parse(uschar *, long *);
+# endif
extern int tls_read(void *, uschar *, size_t);
extern int tls_server_start(uschar **);
extern BOOL tls_smtp_buffered(void);
extern int tls_ungetc(int);
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
+extern void tls_watch_discard_event(int);
+extern void tls_watch_invalidate(void);
+#endif
extern int tls_write(void *, const uschar *, size_t, BOOL);
extern uschar *tls_validate_require_cipher(void);
extern void tls_version_report(FILE *);
-# ifdef USE_OPENSSL
-extern BOOL tls_openssl_options_parse(uschar *, long *);
-# endif
-extern uschar * tls_field_from_dn(uschar *, const uschar *);
-extern BOOL tls_is_name_for_cert(const uschar *, void *);
# ifdef SUPPORT_DANE
extern int tlsa_lookup(const host_item *, dns_answer *, BOOL);
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index b6354b4..9b684e3 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -145,7 +145,7 @@ builtin_macro_create(US"_HAVE_TLS_OCSP");
# ifdef SUPPORT_SRV_OCSP_STACK
builtin_macro_create(US"_HAVE_TLS_OCSP_LIST");
# endif
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
builtin_macro_create(US"_HAVE_TLS_CA_CACHE");
# endif
}
@@ -1435,7 +1435,7 @@ if (gnutls_certificate_allocate_credentials(
}
creds_basic_init(state_server.lib_state.x509_cred, TRUE);
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
/* If tls_certificate has any $ indicating expansions, it is not good.
If tls_privatekey is set but has $, not good. Likewise for tls_ocsp_file.
If all good (and tls_certificate set), load the cert(s). Do not try
@@ -1550,7 +1550,7 @@ creds_basic_init(ob->tls_preload.x509_cred, FALSE);
tpt_dummy_state.session = NULL;
tpt_dummy_state.lib_state = ob->tls_preload;
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
if ( opt_set_and_noexpand(ob->tls_certificate)
&& opt_unset_or_noexpand(ob->tls_privatekey))
{
@@ -1614,7 +1614,7 @@ depends on DANE or plain usage. */
}
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
/* Invalidate the creds cached, by dropping the current ones.
Call when we notice one of the source files has changed. */
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index b0b9977..050b36c 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -1648,7 +1648,7 @@ if (opt_unset_or_noexpand(tls_eccurve))
state_server.lib_state.ecdh = TRUE;
}
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
/* If we can, preload the server-side cert, key and ocsp */
if ( opt_set_and_noexpand(tls_certificate)
@@ -1658,8 +1658,7 @@ if ( opt_set_and_noexpand(tls_certificate)
&& opt_unset_or_noexpand(tls_privatekey))
{
/* Set watches on the filenames. The implementation does de-duplication
- so we can just blindly do them all.
- */
+ so we can just blindly do them all. */
if ( tls_set_watch(tls_certificate, TRUE)
# ifndef DISABLE_OCSP
@@ -1759,7 +1758,7 @@ if (opt_unset_or_noexpand(tls_eccurve))
ob->tls_preload.ecdh = TRUE;
}
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
if ( opt_set_and_noexpand(ob->tls_certificate)
&& opt_unset_or_noexpand(ob->tls_privatekey))
{
@@ -1812,7 +1811,7 @@ else
}
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
/* Invalidate the creds cached, by dropping the current ones.
Call when we notice one of the source files has changed. */
diff --git a/src/src/tls.c b/src/src/tls.c
index a122118..a1e78d7 100644
--- a/src/src/tls.c
+++ b/src/src/tls.c
@@ -76,6 +76,11 @@ static int ssl_xfer_eof = FALSE;
static BOOL ssl_xfer_error = FALSE;
#endif
+#ifdef EXIM_HAVE_KEVENT
+# define KEV_SIZE 16 /* Eight file,dir pairs */
+static struct kevent kev[KEV_SIZE];
+static int kev_used = 0;
+#endif
/*************************************************
* Expand string; give error on failure *
@@ -110,7 +115,7 @@ return TRUE;
}
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
/* Add the directory for a filename to the inotify handle, creating that if
needed. This is enough to see changes to files in that dir.
Return boolean success.
@@ -121,26 +126,26 @@ directory it implies nor if the TLS library handles a watch for us.
The string "system,cache" is recognised and explicitly accepted without
setting a watch. This permits the system CA bundle to be cached even though
we have no way to tell when it gets modified by an update.
-
-We *might* try to run "openssl version -d" and set watches on the dir
-indicated in its output, plus the "certs" subdir of it (following
-synlimks for both). But this is undocumented even for OpenSSL, and
-who knows what GnuTLS might be doing.
+The call chain for OpenSSL uses a (undocumented) call into the library
+to discover the actual file. We don't know what GnuTLS uses.
A full set of caching including the CAs takes 35ms output off of the
server tls_init() (GnuTLS, Fedora 32, 2018-class x86_64 laptop hardware).
*/
static BOOL
tls_set_one_watch(const uschar * filename)
+# ifdef EXIM_HAVE_INOTIFY
{
uschar * s;
if (Ustrcmp(filename, "system,cache") == 0) return TRUE;
if (!(s = Ustrrchr(filename, '/'))) return FALSE;
-s = string_copyn(filename, s - filename);
+s = string_copyn(filename, s - filename); /* mem released by tls_set_watch */
DEBUG(D_tls) debug_printf("watch dir '%s'\n", s);
+/*XXX unclear what effect symlinked files will have for inotify */
+
if (inotify_add_watch(tls_watch_fd, CCS s,
IN_ONESHOT | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF
| IN_MOVED_FROM | IN_MOVED_TO | IN_MOVE_SELF) >= 0)
@@ -148,6 +153,66 @@ if (inotify_add_watch(tls_watch_fd, CCS s,
DEBUG(D_tls) debug_printf("add_watch: %s\n", strerror(errno));
return FALSE;
}
+# endif
+# ifdef EXIM_HAVE_KEVENT
+{
+uschar * s;
+int fd1, fd2, i, cnt = 0;
+struct stat sb;
+
+if (Ustrcmp(filename, "system,cache") == 0) return TRUE;
+
+for (;;)
+ {
+ if (!(s = Ustrrchr(filename, '/'))) return FALSE;
+ if ((lstat(filename, &sb)) < 0) return FALSE;
+ if (kev_used > KEV_SIZE-2) return FALSE;
+
+ /* The dir open will fail if there is a symlink on the path. Fine; it's too
+ much effort to handle all possible cases; just refuse the preload. */
+
+ if ((fd2 = open(s, O_RDONLY | O_NOFOLLOW)) < 0) return FALSE;
+
+ if (!S_ISLNK(sb.st_mode))
+ {
+ if ((fd1 = open(filename, O_RDONLY | O_NOFOLLOW)) < 0) return FALSE;
+ DEBUG(D_tls) debug_printf("watch file '%s'\n", filename);
+ EV_SET(&kev[++kev_used],
+ (uintptr_t)fd1,
+ EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_ONESHOT,
+ NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND
+ | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
+ 0,
+ NULL);
+ cnt++;
+ }
+ DEBUG(D_tls) debug_printf("watch dir '%s'\n", s);
+ EV_SET(&kev[++kev_used],
+ (uintptr_t)fd2,
+ EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_ONESHOT,
+ NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND
+ | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
+ 0,
+ NULL);
+ cnt++;
+
+ if (!(S_ISLNK(sb.st_mode))) break;
+
+ s = store_get(1024, FALSE);
+ if ((i = readlink(filename, s, 1024)) < 0) return FALSE;
+ filename = s;
+ *(s += i) = '\0';
+ store_release_above(s+1);
+ }
+
+if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt, NULL, 0, NULL) >= 0)
+ return TRUE;
+DEBUG(D_tls) debug_printf("add_watch: %d, %s\n", strerror(errno));
+return FALSE;
+}
+# endif /*EXIM_HAVE_KEVENT*/
/* Create an inotify facility if needed.
@@ -160,13 +225,23 @@ tls_set_watch(const uschar * filename, BOOL list)
rmark r;
BOOL rc = FALSE;
-if (tls_watch_fd < 0 && (tls_watch_fd = inotify_init1(O_CLOEXEC)) < 0)
- {
- DEBUG(D_tls) debug_printf("inotify_init: %s\n", strerror(errno));
- return FALSE;
- }
-
if (!filename || !*filename) return TRUE;
+if (Ustrncmp(filename, "system", 6) == 0) return TRUE;
+
+DEBUG(D_tls) debug_printf("tls_set_watch: '%s'\n", filename);
+
+if ( tls_watch_fd < 0
+# ifdef EXIM_HAVE_INOTIFY
+ && (tls_watch_fd = inotify_init1(O_CLOEXEC)) < 0
+# endif
+# ifdef EXIM_HAVE_KEVENT
+ && (tls_watch_fd = kqueue()) < 0
+# endif
+ )
+ {
+ DEBUG(D_tls) debug_printf("inotify_init: %s\n", strerror(errno));
+ return FALSE;
+ }
r = store_mark();
@@ -180,10 +255,24 @@ else
rc = tls_set_one_watch(filename);
store_reset(r);
+if (!rc) DEBUG(D_tls) debug_printf("tls_set_watch() fail on '%s': %s\n", filename, strerror(errno));
return rc;
}
+void
+tls_watch_discard_event(int fd)
+{
+#ifdef EXIM_HAVE_INOTIFY
+(void) read(fd, big_buffer, big_buffer_size);
+#endif
+#ifdef EXIM_HAVE_KEVENT
+struct kevent kev;
+struct timespec t = {0};
+(void) kevent(fd, NULL, 0, &kev, 1, &t);
+#endif
+}
+
/* Called, after a delay for multiple file ops to get done, from
the daemon when any of the watches added (above) fire.
@@ -194,12 +283,10 @@ static void
tls_watch_triggered(void)
{
DEBUG(D_tls) debug_printf("watch triggered\n");
-close(tls_watch_fd);
-tls_watch_fd = -1;
tls_daemon_creds_reload();
}
-#endif /* EXIM_HAVE_INOTIFY */
+#endif /*EXIM_HAVE_INOTIFY*/
void
@@ -213,9 +300,34 @@ for(transport_instance * t = transports; t; t = t->next)
}
}
+
+void
+tls_watch_invalidate(void)
+{
+if (tls_watch_fd < 0) return;
+
+#ifdef EXIM_HAVE_KEVENT
+/* Close the files we had open for kevent */
+for (int fd, i = 0; i < kev_used; i++)
+ {
+ (void) close((int) kev[i].ident);
+ kev[i].ident = (uintptr_t)-1;
+ }
+kev_used = 0;
+#endif
+
+close(tls_watch_fd);
+tls_watch_fd = -1;
+}
+
+
static void
tls_daemon_creds_reload(void)
{
+#ifdef EXIM_HAVE_KEVENT
+tls_watch_invalidate();
+#endif
+
tls_server_creds_invalidate();
tls_server_creds_init();
@@ -240,7 +352,7 @@ void
tls_daemon_tick(void)
{
tls_per_lib_daemon_tick();
-#ifdef EXIM_HAVE_INOTIFY
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
if (tls_watch_trigger_time && time(NULL) >= tls_watch_trigger_time + 5)
{
tls_watch_trigger_time = 0;
diff --git a/test/confs/1103 b/test/confs/1103
index b937ee9..52f471b 100644
--- a/test/confs/1103
+++ b/test/confs/1103
@@ -36,6 +36,7 @@ smtp:
hosts = 127.0.0.1
allow_localhost
port = PORT_D
+ hosts_try_fastopen = :
tls_certificate = DIR/aux-fixed/cert2
tls_verify_certificates = DIR/aux-fixed/cert1
tls_verify_cert_hostnames = :
diff --git a/test/runtest b/test/runtest
index 8a1e46e..84227b9 100755
--- a/test/runtest
+++ b/test/runtest
@@ -1072,12 +1072,12 @@ RESET_AFTER_EXTRA_LINE_READ:
# TLS preload
# only OpenSSL speaks of these
- next if /^TLS: preloading DH params for server/;
+ next if /^TLS: preloading (DH params|ECDH curve|CA bundle) for server/;
next if /^Diffie-Hellman initialized from default/;
- next if /^TLS: preloading ECDH curve for server/;
next if /^ECDH OpenSSL [< ]?[\d.+]+ temp key parameter settings:/;
next if /^ECDH: .'*prime256v1'/;
- next if /^watch dir/;
+ next if /^tls_verify_certificates: system$/;
+ next if /^tls_set_watch: .*\/cert.pem/;
# TLS preload
# only GnuTLS speaks of these
@@ -1087,6 +1087,10 @@ RESET_AFTER_EXTRA_LINE_READ:
s/^GnuTLS using default session cipher\/priority "NORMAL"$/TLS: not preloading cipher list for server/;
next if /^GnuTLS<2>: added \d+ protocols, \d+ ciphersuites, \d+ sig algos and \d+ groups into priority list$/;
+ # only kevent platforms (FreeBSD) say this
+ next if /^watch dir/;
+ next if /^watch file .*\/usr\/local/;
+
# TLS preload
# there happen in different orders for OpenSSL/GnuTLS/noTLS
next if /^TLS: not preloading (CA bundle|cipher list) for server$/;