+
+/* 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.
+The GnuTLS support only works if exim is run by root, not taking advantage of
+the setuid bit.
+You can use either the external environment (modulo the keep_environment config)
+or the add_environment config option for SSLKEYLOGFILE; the latter takes
+precedence.
+
+If the path is absolute, require it starts with the spooldir; otherwise delete
+the env variable. If relative, prefix the spooldir.
+*/
+void
+tls_clean_env(void)
+{
+uschar * path = US getenv("SSLKEYLOGFILE");
+if (path)
+ if (!*path)
+ unsetenv("SSLKEYLOGFILE");
+ else if (*path != '/')
+ {
+ DEBUG(D_tls)
+ debug_printf("prepending spooldir to env SSLKEYLOGFILE\n");
+ setenv("SSLKEYLOGFILE", CCS string_sprintf("%s/%s", spool_directory, path), 1);
+ }
+ else if (Ustrncmp(path, spool_directory, Ustrlen(spool_directory)) != 0)
+ {
+ DEBUG(D_tls)
+ debug_printf("removing env SSLKEYLOGFILE=%s: not under spooldir\n", path);
+ unsetenv("SSLKEYLOGFILE");
+ }
+}
+
+/*************************************************
+* Drop privs for checking TLS config *
+*************************************************/
+
+/* We want to validate TLS options during readconf, but do not want to be
+root when we call into the TLS library, in case of library linkage errors
+which cause segfaults; before this check, those were always done as the Exim
+runtime user and it makes sense to continue with that.
+
+Assumes: tls_require_ciphers has been set, if it will be
+ exim_user has been set, if it will be
+ exim_group has been set, if it will be
+
+Returns: bool for "okay"; false will cause caller to immediately exit.
+*/
+
+BOOL
+tls_dropprivs_validate_require_cipher(BOOL nowarn)
+{
+const uschar *errmsg;
+pid_t pid;
+int rc, status;
+void (*oldsignal)(int);
+
+/* If TLS will never be used, no point checking ciphers */
+
+if ( !tls_advertise_hosts
+ || !*tls_advertise_hosts
+ || Ustrcmp(tls_advertise_hosts, ":") == 0
+ )
+ return TRUE;
+else if (!nowarn && !tls_certificate)
+ log_write(0, LOG_MAIN,
+ "Warning: No server certificate defined; will use a selfsigned one.\n"
+ " Suggested action: either install a certificate or change tls_advertise_hosts option");
+
+oldsignal = signal(SIGCHLD, SIG_DFL);
+
+fflush(NULL);
+if ((pid = exim_fork(US"cipher-validate")) < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork failed for TLS check");
+
+if (pid == 0)
+ {
+ /* in some modes, will have dropped privilege already */
+ if (!geteuid())
+ exim_setugid(exim_uid, exim_gid, FALSE,
+ US"calling tls_validate_require_cipher");
+
+ if ((errmsg = tls_validate_require_cipher()))
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+ "tls_require_ciphers invalid: %s", errmsg);
+ fflush(NULL);
+ exim_underbar_exit(EXIT_SUCCESS);
+ }
+
+do {
+ rc = waitpid(pid, &status, 0);
+} while (rc < 0 && errno == EINTR);
+
+DEBUG(D_tls)
+ debug_printf("tls_validate_require_cipher child %d ended: status=0x%x\n",
+ (int)pid, status);
+
+signal(SIGCHLD, oldsignal);
+
+return status == 0;
+}
+
+
+
+
+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
+}
+
+
+
+/* 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*/