[exim-cvs] FreeBSD: TLS: preload configuration items

Startseite
Nachricht löschen
Nachricht beantworten
Autor: Exim Git Commits Mailing List
Datum:  
To: exim-cvs
Betreff: [exim-cvs] FreeBSD: TLS: preload configuration items
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$/;