FreeBSD: TLS: preload configuration items
authorJeremy Harris <jgh146exb@wizmail.org>
Thu, 8 Oct 2020 12:30:41 +0000 (13:30 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Fri, 9 Oct 2020 19:29:31 +0000 (20:29 +0100)
doc/doc-docbook/spec.xfpt
src/OS/os.h-FreeBSD
src/src/daemon.c
src/src/exim.h
src/src/functions.h
src/src/tls-gnu.c
src/src/tls-openssl.c
src/src/tls.c
test/confs/1103
test/runtest

index 31c8c5653a573fdc9dc2cd82cd317bd6696955be..74c9b083c4a28685c7cb57114f4b2013958eff83 100644 (file)
@@ -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.
index c1720a796d9313363912edf303b611f3c3c80859..0083642b432c0d9879d5b41ed76bf695d105fbe1 100644 (file)
@@ -70,4 +70,6 @@ extern ssize_t os_sendfile(int, int, off_t *, size_t);
 
 /*******************/
 
+#define EXIM_HAVE_KEVENT
+
 /* End */
index 4e90799e69794025ddbf14acaae3e82b82ed6497..899aa0dab539b5f74e123054da2493be2669d369 100644 (file)
@@ -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
index 6669e809e3e631635e1e1eeb76d707a17b9faa95..f27ed5cd8519ccbcb86e3868772767ab67c45f78 100644 (file)
@@ -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 */
 
index c6985196271f8f44c7a1e301f6b381dd208e0120..38309e7ddf5b97f320a74e12a1b3d281607a004b 100644 (file)
@@ -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);
index b6354b4b2ca4cf08f44e2e413eb223909ad2b8b3..9b684e3cd64446d3990146cdd96957c07903f605 100644 (file)
@@ -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. */
  
index b0b99779d460fca42f73cc2d4cfa00eb48f40865..050b36c40ff33749e8e58db009d212dfabcda8e8 100644 (file)
@@ -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. */
  
index a12211859a8709238cffe21c95ac8685de445400..a1e78d7cd58f7e6896e106aa70bbd961d9f3a15d 100644 (file)
@@ -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;
index b937ee99c7398eb0f96fad27471d4548b7018e1d..52f471b798ca069b87b7007cb80113ec4ba07793 100644 (file)
@@ -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 =  :
index 8a1e46edd75d3cc8115397557922db7c66aa9bdf..84227b9d3a22c892dc70155976630392d8e37583 100755 (executable)
@@ -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$/;