* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
/* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is
based on a patch that was originally contributed by Steve Haslam. It was
static void tls_per_lib_daemon_init(void);
static void tls_per_lib_daemon_tick(void);
-static void tls_server_creds_init(void);
+static unsigned tls_server_creds_init(void);
static void tls_server_creds_invalidate(void);
static void tls_client_creds_init(transport_instance *, BOOL);
static void tls_client_creds_invalidate(transport_instance *);
static int kev_used = 0;
#endif
+static unsigned tls_creds_expire = 0;
+
/*************************************************
* Expand string; give error on failure *
*************************************************/
*/
static BOOL
-expand_check(const uschar *s, const uschar *name, uschar **result, uschar ** errstr)
+expand_check(const uschar * s, const uschar * name,
+ uschar ** result, uschar ** errstr)
{
if (!s)
*result = NULL;
tls_set_one_watch(const uschar * filename)
# ifdef EXIM_HAVE_INOTIFY
{
+uschar buf[PATH_MAX];
+ssize_t len;
uschar * s;
if (Ustrcmp(filename, "system,cache") == 0) return TRUE;
-
if (!(s = Ustrrchr(filename, '/'))) return FALSE;
+
+for (unsigned loop = 20;
+ (len = readlink(CCS filename, CS buf, sizeof(buf))) >= 0; )
+ { /* a symlink */
+ if (--loop == 0) { errno = ELOOP; return FALSE; }
+ filename = buf[0] == '/'
+ ? string_copyn(buf, (unsigned)len) /* mem released by tls_set_watch */
+ : string_sprintf("%.*s/%.*s", (int)(s - filename), filename, (int)len, buf);
+ s = Ustrrchr(filename, '/');
+ }
+if (errno != EINVAL)
+ return FALSE; /* other error */
+
+/* not a symlink */
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 */
+DEBUG(D_tls) debug_printf("watch dir '%s'\n", s);
if (inotify_add_watch(tls_watch_fd, CCS s,
IN_ONESHOT | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF
# endif
# ifdef EXIM_HAVE_KEVENT
{
-uschar * s;
-int fd1, fd2, i, cnt = 0;
+uschar * s, * t;
+int fd1, fd2, i, j, cnt = 0;
struct stat sb;
#ifdef OpenBSD
struct kevent k_dummy;
+struct timespec ts = {0};
#endif
errno = 0;
{
if ((fd1 = open(CCS filename, O_RDONLY | O_NOFOLLOW)) < 0)
{ s = US"open file"; goto bad; }
- DEBUG(D_tls) debug_printf("watch file '%s'\n", filename);
- EV_SET(&kev[++kev_used],
+ DEBUG(D_tls) debug_printf("watch file '%s':\t%d\n", filename, fd1);
+ EV_SET(&kev[kev_used++],
(uintptr_t)fd1,
EVFILT_VNODE,
EV_ADD | EV_ENABLE | EV_ONESHOT,
NULL);
cnt++;
}
- DEBUG(D_tls) debug_printf("watch dir '%s'\n", s);
- EV_SET(&kev[++kev_used],
+ DEBUG(D_tls) debug_printf("watch dir '%s':\t%d\n", s, fd2);
+ EV_SET(&kev[kev_used++],
(uintptr_t)fd2,
EVFILT_VNODE,
EV_ADD | EV_ENABLE | EV_ONESHOT,
if (!(S_ISLNK(sb.st_mode))) break;
- s = store_get(1024, FALSE);
- if ((i = readlink(CCS filename, (void *)s, 1024)) < 0) { s = US"readlink"; goto bad; }
- filename = s;
- *(s += i) = '\0';
- store_release_above(s+1);
+ t = store_get(1024, GET_UNTAINTED);
+ Ustrncpy(t, s, 1022);
+ j = Ustrlen(s);
+ t[j++] = '/';
+ if ((i = readlink(CCS filename, (void *)(t+j), 1023-j)) < 0) { s = US"readlink"; goto bad; }
+ filename = t;
+ *(t += i+j) = '\0';
+ store_release_above(t+1);
}
-if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt,
#ifdef OpenBSD
- &k_dummy, 1,
+if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt, &k_dummy, 1, &ts) >= 0)
+ return TRUE;
#else
- NULL, 0,
-#endif
- NULL) >= 0)
+if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt, NULL, 0, NULL) >= 0)
return TRUE;
+#endif
s = US"kevent";
bad:
(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.
-
-Dump the set of watches and arrange to reload cached creds (which
-will set up new watches). */
-
-static void
-tls_watch_triggered(void)
-{
-DEBUG(D_tls) debug_printf("watch triggered\n");
-
-tls_daemon_creds_reload();
-}
#endif /*EXIM_HAVE_INOTIFY*/
/* Close the files we had open for kevent */
for (int i = 0; i < kev_used; i++)
{
+ DEBUG(D_tls) debug_printf("closing watch fd: %d\n", (int) kev[i].ident);
(void) close((int) kev[i].ident);
kev[i].ident = (uintptr_t)-1;
}
static void
tls_daemon_creds_reload(void)
{
+unsigned lifetime;
+
#ifdef EXIM_HAVE_KEVENT
tls_watch_invalidate();
#endif
tls_server_creds_invalidate();
-tls_server_creds_init();
+
+/* _expire is for a time-limited selfsign server cert */
+tls_creds_expire = (lifetime = tls_server_creds_init())
+ ? time(NULL) + lifetime : 0;
tls_client_creds_reload(TRUE);
}
-/* Called every time round the daemon loop */
+/* Called every time round the daemon loop.
-void
+If we reloaded fd-watcher, return the old watch fd
+having modified the global for the new one. Otherwise
+return -1.
+*/
+
+int
tls_daemon_tick(void)
{
+int old_watch_fd = tls_watch_fd;
+
tls_per_lib_daemon_tick();
#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
-if (tls_watch_trigger_time && time(NULL) >= tls_watch_trigger_time + 5)
+if (tls_creds_expire && time(NULL) >= tls_creds_expire)
+ {
+ /* The server cert is a selfsign, with limited lifetime. Dump it and
+ generate a new one. Reload the rest of the creds also as the machinery
+ is all there. */
+
+ DEBUG(D_tls) debug_printf("selfsign cert rotate\n");
+ tls_creds_expire = 0;
+ tls_daemon_creds_reload();
+ return old_watch_fd;
+ }
+else if (tls_watch_trigger_time && time(NULL) >= tls_watch_trigger_time + 5)
{
- tls_watch_trigger_time = 0;
- tls_watch_triggered();
+ /* Called, after a delay for multiple file ops to get done, from
+ the daemon when any of the watches added (above) fire.
+ Dump the set of watches and arrange to reload cached creds (which
+ will set up new watches). */
+
+ DEBUG(D_tls) debug_printf("watch triggered\n");
+ tls_watch_trigger_time = tls_creds_expire = 0;
+ tls_daemon_creds_reload();
+ return old_watch_fd;
}
#endif
+return -1;
}
/* Called once at daemon startup */
/*************************************************
* Many functions are package-specific *
*************************************************/
+/* Forward decl. */
+static void tls_client_resmption_key(tls_support *, smtp_connect_args *,
+ smtp_transport_options_block *);
+
#ifdef USE_GNUTLS
# include "tls-gnu.c"
int
tls_ungetc(int ch)
{
+if (ssl_xfer_buffer_lwm <= 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in tls_ungetc");
+
ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch;
return ch;
}
return FALSE;
}
-
/* Environment cleanup: The GnuTLS library uses SSLKEYLOGFILE in the environment
and writes a file by that name. Our OpenSSL code does the same, using keying
info from the library API.
+static void
+tls_client_resmption_key(tls_support * tlsp, smtp_connect_args * conn_args,
+ smtp_transport_options_block * ob)
+{
+#ifndef DISABLE_TLS_RESUME
+hctx * h = &tlsp->resume_hctx;
+blob b;
+gstring * g;
+
+DEBUG(D_tls) if (conn_args->host_lbserver)
+ debug_printf("TLS: lbserver '%s'\n", conn_args->host_lbserver);
+
+# ifdef EXIM_HAVE_SHA2
+exim_sha_init(h, HASH_SHA2_256);
+# else
+exim_sha_init(h, HASH_SHA1);
+# endif
+exim_sha_update_string(h, conn_args->host_lbserver);
+# ifdef SUPPORT_DANE
+if (conn_args->dane)
+ exim_sha_update(h, CUS &conn_args->tlsa_dnsa, sizeof(dns_answer));
+# endif
+exim_sha_update_string(h, conn_args->host->address);
+exim_sha_update(h, CUS &conn_args->host->port, sizeof(conn_args->host->port));
+exim_sha_update_string(h, conn_args->sending_ip_address);
+exim_sha_update_string(h, openssl_options);
+exim_sha_update_string(h, ob->tls_require_ciphers);
+exim_sha_update_string(h, tlsp->sni);
+# ifdef EXIM_HAVE_ALPN
+exim_sha_update_string(h, ob->tls_alpn);
+# endif
+exim_sha_finish(h, &b);
+for (g = string_get(b.len*2+1); b.len-- > 0; )
+ g = string_fmt_append(g, "%02x", *b.data++);
+tlsp->resume_index = string_from_gstring(g);
+DEBUG(D_tls) debug_printf("TLS: resume session index %s\n", tlsp->resume_index);
+#endif
+}
+
#endif /*!DISABLE_TLS*/
#endif /*!MACRO_PREDEF*/