* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* 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
#endif
-/* Forward decl. */
-static void tls_client_resmption_key(tls_support *, smtp_connect_args *,
- smtp_transport_options_block *);
-
-
#if defined(MACRO_PREDEF) && !defined(DISABLE_TLS)
# include "macro_predef.h"
# ifdef USE_GNUTLS
static void tls_per_lib_daemon_init(void);
static void tls_per_lib_daemon_tick(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 void tls_daemon_creds_reload(void);
static BOOL opt_set_and_noexpand(const uschar *);
static BOOL opt_unset_or_noexpand(const uschar *);
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
+static void tls_server_creds_invalidate(void);
+static void tls_client_creds_invalidate(transport_instance *);
+#endif
+
/* This module is compiled only when it is specifically requested in the
*/
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)
+ {
+ f.expand_string_forcedfail = FALSE;
*result = NULL;
+ }
else if ( !(*result = expand_string(US s)) /* need to clean up const more */
&& !f.expand_string_forcedfail
)
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
for(transport_instance * t = transports; t; t = t->next)
if (Ustrcmp(t->driver_name, "smtp") == 0)
{
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
tls_client_creds_invalidate(t);
+#endif
tls_client_creds_init(t, watch);
}
}
tls_watch_invalidate();
#endif
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
tls_server_creds_invalidate();
+#endif
+
+/* _expire is for a time-limited selfsign server cert */
tls_creds_expire = (lifetime = tls_server_creds_init())
? time(NULL) + lifetime : 0;
/*************************************************
* 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"
BOOL
tls_is_name_for_cert(const uschar * namelist, void * cert)
{
-uschar * altnames = tls_cert_subject_altname(cert, US"dns");
-uschar * subjdn;
-uschar * certname;
+uschar * altnames, * subjdn, * certname, * cmpname;
int cmp_sep = 0;
-uschar * cmpname;
if ((altnames = tls_cert_subject_altname(cert, US"dns")))
{
int alt_sep = '\n';
+ DEBUG(D_tls|D_lookup) debug_printf_indent("cert has SAN\n");
while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
{
const uschar * an = altnames;
+ DEBUG(D_tls|D_lookup) debug_printf_indent(" %s in SANs?", cmpname);
while ((certname = string_nextinlist(&an, &alt_sep, NULL, 0)))
if (is_name_match(cmpname, certname))
+ {
+ DEBUG(D_tls|D_lookup) debug_printf_indent(" yes (matched %s)\n", certname);
return TRUE;
+ }
+ DEBUG(D_tls|D_lookup) debug_printf_indent(" no (end of SAN list)\n");
}
}
while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
{
const uschar * sn = subjdn;
+ DEBUG(D_tls|D_lookup) debug_printf_indent(" %s in SN?", cmpname);
while ((certname = string_nextinlist(&sn, &sn_sep, NULL, 0)))
if ( *certname++ == 'C'
&& *certname++ == 'N'
&& *certname++ == '='
&& is_name_match(cmpname, certname)
)
+ {
+ DEBUG(D_tls|D_lookup) debug_printf_indent(" yes (matched %s)\n", certname);
return TRUE;
+ }
+ DEBUG(D_tls|D_lookup) debug_printf_indent(" no (end of CN)\n");
}
}
return FALSE;
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;
-#ifdef EXIM_HAVE_SHA2
+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
+# else
exim_sha_init(h, HASH_SHA1);
-#endif
-
-// TODO: word from server EHLO resp /* how, fer gossakes? Add item to conn_args or tls_support? */
-
+# 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
+# ifdef EXIM_HAVE_ALPN
exim_sha_update_string(h, ob->tls_alpn);
-#endif
+# 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
}
+
+
+/* Start TLS as a client for an ajunct connection, eg. readsocket
+Return boolean success.
+*/
+
+BOOL
+tls_client_adjunct_start(host_item * host, client_conn_ctx * cctx,
+ const uschar * sni, uschar ** errmsg)
+{
+union sockaddr_46 interface_sock;
+EXIM_SOCKLEN_T size = sizeof(interface_sock);
+smtp_connect_args conn_args = {.host = host };
+tls_support tls_dummy = { .sni = NULL };
+uschar * errstr;
+
+if (getsockname(cctx->sock, (struct sockaddr *) &interface_sock, &size) == 0)
+ conn_args.sending_ip_address = host_ntoa(-1, &interface_sock, NULL, NULL);
+else
+ {
+ *errmsg = string_sprintf("getsockname failed: %s", strerror(errno));
+ return FALSE;
+ }
+
+/* To handle SNI we need to emulate more of a real transport because the
+base tls code assumes that is where the SNI string lives. */
+
+if (*sni)
+ {
+ transport_instance * tb;
+ smtp_transport_options_block * ob;
+
+ conn_args.tblock = tb = store_get(sizeof(*tb), GET_UNTAINTED);
+ memset(tb, 0, sizeof(*tb));
+
+ tb->options_block = ob = store_get(sizeof(*ob), GET_UNTAINTED);
+ memcpy(ob, &smtp_transport_option_defaults, sizeof(*ob));
+
+ ob->tls_sni = sni;
+ }
+
+if (!tls_client_start(cctx, &conn_args, NULL, &tls_dummy, &errstr))
+ {
+ *errmsg = string_sprintf("TLS connect failed: %s", errstr);
+ return FALSE;
+ }
+return TRUE;
+}
+
+
+
#endif /*!DISABLE_TLS*/
#endif /*!MACRO_PREDEF*/