diff options
Diffstat (limited to '')
l--------- | src/tlsproxy/.indent.pro | 1 | ||||
-rw-r--r-- | src/tlsproxy/Makefile.in | 117 | ||||
-rw-r--r-- | src/tlsproxy/tlsproxy.c | 1970 | ||||
-rw-r--r-- | src/tlsproxy/tlsproxy.h | 69 | ||||
-rw-r--r-- | src/tlsproxy/tlsproxy_state.c | 169 |
5 files changed, 2326 insertions, 0 deletions
diff --git a/src/tlsproxy/.indent.pro b/src/tlsproxy/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/tlsproxy/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/tlsproxy/Makefile.in b/src/tlsproxy/Makefile.in new file mode 100644 index 0000000..c72aa81 --- /dev/null +++ b/src/tlsproxy/Makefile.in @@ -0,0 +1,117 @@ +SHELL = /bin/sh +SRCS = tlsproxy.c tlsproxy_state.c +OBJS = tlsproxy.o tlsproxy_state.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = tlsproxy +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +tlsproxy.o: ../../include/argv.h +tlsproxy.o: ../../include/attr.h +tlsproxy.o: ../../include/been_here.h +tlsproxy.o: ../../include/check_arg.h +tlsproxy.o: ../../include/dns.h +tlsproxy.o: ../../include/events.h +tlsproxy.o: ../../include/htable.h +tlsproxy.o: ../../include/iostuff.h +tlsproxy.o: ../../include/mail_conf.h +tlsproxy.o: ../../include/mail_params.h +tlsproxy.o: ../../include/mail_proto.h +tlsproxy.o: ../../include/mail_server.h +tlsproxy.o: ../../include/mail_version.h +tlsproxy.o: ../../include/msg.h +tlsproxy.o: ../../include/myaddrinfo.h +tlsproxy.o: ../../include/mymalloc.h +tlsproxy.o: ../../include/name_code.h +tlsproxy.o: ../../include/name_mask.h +tlsproxy.o: ../../include/nbbio.h +tlsproxy.o: ../../include/nvtable.h +tlsproxy.o: ../../include/sock_addr.h +tlsproxy.o: ../../include/split_at.h +tlsproxy.o: ../../include/sys_defs.h +tlsproxy.o: ../../include/tls.h +tlsproxy.o: ../../include/tls_proxy.h +tlsproxy.o: ../../include/vbuf.h +tlsproxy.o: ../../include/vstream.h +tlsproxy.o: ../../include/vstring.h +tlsproxy.o: tlsproxy.c +tlsproxy.o: tlsproxy.h +tlsproxy_state.o: ../../include/argv.h +tlsproxy_state.o: ../../include/attr.h +tlsproxy_state.o: ../../include/check_arg.h +tlsproxy_state.o: ../../include/dns.h +tlsproxy_state.o: ../../include/events.h +tlsproxy_state.o: ../../include/htable.h +tlsproxy_state.o: ../../include/mail_conf.h +tlsproxy_state.o: ../../include/mail_server.h +tlsproxy_state.o: ../../include/msg.h +tlsproxy_state.o: ../../include/myaddrinfo.h +tlsproxy_state.o: ../../include/mymalloc.h +tlsproxy_state.o: ../../include/name_code.h +tlsproxy_state.o: ../../include/name_mask.h +tlsproxy_state.o: ../../include/nbbio.h +tlsproxy_state.o: ../../include/nvtable.h +tlsproxy_state.o: ../../include/sock_addr.h +tlsproxy_state.o: ../../include/sys_defs.h +tlsproxy_state.o: ../../include/tls.h +tlsproxy_state.o: ../../include/tls_proxy.h +tlsproxy_state.o: ../../include/vbuf.h +tlsproxy_state.o: ../../include/vstream.h +tlsproxy_state.o: ../../include/vstring.h +tlsproxy_state.o: tlsproxy.h +tlsproxy_state.o: tlsproxy_state.c diff --git a/src/tlsproxy/tlsproxy.c b/src/tlsproxy/tlsproxy.c new file mode 100644 index 0000000..216d000 --- /dev/null +++ b/src/tlsproxy/tlsproxy.c @@ -0,0 +1,1970 @@ +/*++ +/* NAME +/* tlsproxy 8 +/* SUMMARY +/* Postfix TLS proxy +/* SYNOPSIS +/* \fBtlsproxy\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBtlsproxy\fR(8) server implements a two-way TLS proxy. It +/* is used by the \fBpostscreen\fR(8) server to talk SMTP-over-TLS +/* with remote SMTP clients that are not whitelisted (including +/* clients whose whitelist status has expired), and by the +/* \fBsmtp\fR(8) client to support TLS connection reuse, but it +/* should also work for non-SMTP protocols. +/* +/* Although one \fBtlsproxy\fR(8) process can serve multiple +/* sessions at the same time, it is a good idea to allow the +/* number of processes to increase with load, so that the +/* service remains responsive. +/* PROTOCOL EXAMPLE +/* .ad +/* .fi +/* The example below concerns \fBpostscreen\fR(8). However, +/* the \fBtlsproxy\fR(8) server is agnostic of the application +/* protocol, and the example is easily adapted to other +/* applications. +/* +/* After receiving a valid remote SMTP client STARTTLS command, +/* the \fBpostscreen\fR(8) server sends the remote SMTP client +/* endpoint string, the requested role (server), and the +/* requested timeout to \fBtlsproxy\fR(8). \fBpostscreen\fR(8) +/* then receives a "TLS available" indication from \fBtlsproxy\fR(8). +/* If the TLS service is available, \fBpostscreen\fR(8) sends +/* the remote SMTP client file descriptor to \fBtlsproxy\fR(8), +/* and sends the plaintext 220 greeting to the remote SMTP +/* client. This triggers TLS negotiations between the remote +/* SMTP client and \fBtlsproxy\fR(8). Upon completion of the +/* TLS-level handshake, \fBtlsproxy\fR(8) translates between +/* plaintext from/to \fBpostscreen\fR(8) and ciphertext to/from +/* the remote SMTP client. +/* SECURITY +/* .ad +/* .fi +/* The \fBtlsproxy\fR(8) server is moderately security-sensitive. +/* It talks to untrusted clients on the network. The process +/* can be run chrooted at fixed low privilege. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are not picked up automatically, +/* as \fBtlsproxy\fR(8) processes may run for a long time +/* depending on mail server load. Use the command "\fBpostfix +/* reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* STARTTLS GLOBAL CONTROLS +/* .ad +/* .fi +/* The following settings are global and therefore cannot be +/* overruled by information specified in a \fBtlsproxy\fR(8) +/* client request. +/* .IP "\fBtls_append_default_CA (no)\fR" +/* Append the system-supplied default Certification Authority +/* certificates to the ones specified with *_tls_CApath or *_tls_CAfile. +/* .IP "\fBtls_daemon_random_bytes (32)\fR" +/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8) +/* process requests from the \fBtlsmgr\fR(8) server in order to seed its +/* internal pseudo random number generator (PRNG). +/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "high" grade ciphers. +/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "medium" or higher grade ciphers. +/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "low" or higher grade ciphers. +/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "export" or higher grade ciphers. +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" +/* The OpenSSL cipherlist for "NULL" grade ciphers that provide +/* authentication without encryption. +/* .IP "\fBtls_eecdh_strong_curve (prime256v1)\fR" +/* The elliptic curve used by the Postfix SMTP server for sensibly +/* strong +/* ephemeral ECDH key exchange. +/* .IP "\fBtls_eecdh_ultra_curve (secp384r1)\fR" +/* The elliptic curve used by the Postfix SMTP server for maximally +/* strong +/* ephemeral ECDH key exchange. +/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" +/* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .IP "\fBtls_preempt_cipherlist (no)\fR" +/* With SSLv3 and later, use the Postfix SMTP server's cipher +/* preference order instead of the remote client's cipher preference +/* order. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBtls_legacy_public_key_fingerprints (no)\fR" +/* A temporary migration aid for sites that use certificate +/* \fIpublic-key\fR fingerprints with Postfix 2.9.0..2.9.5, which use +/* an incorrect algorithm. +/* .PP +/* Available in Postfix version 2.11-3.1: +/* .IP "\fBtls_dane_digest_agility (on)\fR" +/* Configure RFC7671 DANE TLSA digest algorithm agility. +/* .IP "\fBtls_dane_trust_anchor_digest_enable (yes)\fR" +/* Enable support for RFC 6698 (DANE TLSA) DNS records that contain +/* digests of trust-anchors with certificate usage "2". +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" +/* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBtls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc)\fR" +/* Algorithm used to encrypt RFC5077 TLS session tickets. +/* .IP "\fBopenssl_path (openssl)\fR" +/* The location of the OpenSSL command line program \fBopenssl\fR(1). +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBtls_eecdh_auto_curves (see 'postconf -d' output)\fR" +/* The prioritized list of elliptic curves supported by the Postfix +/* SMTP client and server. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBtls_server_sni_maps (empty)\fR" +/* Optional lookup tables that map names received from remote SMTP +/* clients via the TLS Server Name Indication (SNI) extension to the +/* appropriate keys and certificate chains. +/* STARTTLS SERVER CONTROLS +/* .ad +/* .fi +/* These settings are clones of Postfix SMTP server settings. +/* They allow \fBtlsproxy\fR(8) to load the same certificate +/* and private key information as the Postfix SMTP server, +/* before dropping privileges, so that the key files can be +/* kept read-only for root. These settings can currently not +/* be overruled by information in a \fBtlsproxy\fR(8) client +/* request, but that limitation may be removed in a future +/* version. +/* .IP "\fBtlsproxy_tls_CAfile ($smtpd_tls_CAfile)\fR" +/* A file containing (PEM format) CA certificates of root CAs +/* trusted to sign either remote SMTP client certificates or intermediate +/* CA certificates. +/* .IP "\fBtlsproxy_tls_CApath ($smtpd_tls_CApath)\fR" +/* A directory containing (PEM format) CA certificates of root CAs +/* trusted to sign either remote SMTP client certificates or intermediate +/* CA certificates. +/* .IP "\fBtlsproxy_tls_always_issue_session_ids ($smtpd_tls_always_issue_session_ids)\fR" +/* Force the Postfix \fBtlsproxy\fR(8) server to issue a TLS session id, +/* even when TLS session caching is turned off. +/* .IP "\fBtlsproxy_tls_ask_ccert ($smtpd_tls_ask_ccert)\fR" +/* Ask a remote SMTP client for a client certificate. +/* .IP "\fBtlsproxy_tls_ccert_verifydepth ($smtpd_tls_ccert_verifydepth)\fR" +/* The verification depth for remote SMTP client certificates. +/* .IP "\fBtlsproxy_tls_cert_file ($smtpd_tls_cert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server RSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_tls_ciphers ($smtpd_tls_ciphers)\fR" +/* The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server +/* will use with opportunistic TLS encryption. +/* .IP "\fBtlsproxy_tls_dcert_file ($smtpd_tls_dcert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server DSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_tls_dh1024_param_file ($smtpd_tls_dh1024_param_file)\fR" +/* File with DH parameters that the Postfix \fBtlsproxy\fR(8) server +/* should use with non-export EDH ciphers. +/* .IP "\fBtlsproxy_tls_dh512_param_file ($smtpd_tls_dh512_param_file)\fR" +/* File with DH parameters that the Postfix \fBtlsproxy\fR(8) server +/* should use with export-grade EDH ciphers. +/* .IP "\fBtlsproxy_tls_dkey_file ($smtpd_tls_dkey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server DSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_tls_eccert_file ($smtpd_tls_eccert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server ECDSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_tls_eckey_file ($smtpd_tls_eckey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server ECDSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_tls_eecdh_grade ($smtpd_tls_eecdh_grade)\fR" +/* The Postfix \fBtlsproxy\fR(8) server security grade for ephemeral +/* elliptic-curve Diffie-Hellman (EECDH) key exchange. +/* .IP "\fBtlsproxy_tls_exclude_ciphers ($smtpd_tls_exclude_ciphers)\fR" +/* List of ciphers or cipher types to exclude from the \fBtlsproxy\fR(8) +/* server cipher list at all TLS security levels. +/* .IP "\fBtlsproxy_tls_fingerprint_digest ($smtpd_tls_fingerprint_digest)\fR" +/* The message digest algorithm to construct remote SMTP +/* client-certificate +/* fingerprints. +/* .IP "\fBtlsproxy_tls_key_file ($smtpd_tls_key_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server RSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_tls_loglevel ($smtpd_tls_loglevel)\fR" +/* Enable additional Postfix \fBtlsproxy\fR(8) server logging of TLS +/* activity. +/* .IP "\fBtlsproxy_tls_mandatory_ciphers ($smtpd_tls_mandatory_ciphers)\fR" +/* The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server +/* will use with mandatory TLS encryption. +/* .IP "\fBtlsproxy_tls_mandatory_exclude_ciphers ($smtpd_tls_mandatory_exclude_ciphers)\fR" +/* Additional list of ciphers or cipher types to exclude from the +/* \fBtlsproxy\fR(8) server cipher list at mandatory TLS security levels. +/* .IP "\fBtlsproxy_tls_mandatory_protocols ($smtpd_tls_mandatory_protocols)\fR" +/* The SSL/TLS protocols accepted by the Postfix \fBtlsproxy\fR(8) server +/* with mandatory TLS encryption. +/* .IP "\fBtlsproxy_tls_protocols ($smtpd_tls_protocols)\fR" +/* List of TLS protocols that the Postfix \fBtlsproxy\fR(8) server will +/* exclude or include with opportunistic TLS encryption. +/* .IP "\fBtlsproxy_tls_req_ccert ($smtpd_tls_req_ccert)\fR" +/* With mandatory TLS encryption, require a trusted remote SMTP +/* client certificate in order to allow TLS connections to proceed. +/* .IP "\fBtlsproxy_tls_security_level ($smtpd_tls_security_level)\fR" +/* The SMTP TLS security level for the Postfix \fBtlsproxy\fR(8) server; +/* when a non-empty value is specified, this overrides the obsolete +/* parameters smtpd_use_tls and smtpd_enforce_tls. +/* .IP "\fBtlsproxy_tls_chain_files ($smtpd_tls_chain_files)\fR" +/* Files with the Postfix \fBtlsproxy\fR(8) server keys and certificate +/* chains in PEM format. +/* STARTTLS CLIENT CONTROLS +/* .ad +/* .fi +/* These settings are clones of Postfix SMTP client settings. +/* They allow \fBtlsproxy\fR(8) to load the same certificate +/* and private key information as the Postfix SMTP client, +/* before dropping privileges, so that the key files can be +/* kept read-only for root. Some settings may be overruled by +/* information in a \fBtlsproxy\fR(8) client request. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBtlsproxy_client_CAfile ($smtp_tls_CAfile)\fR" +/* A file containing CA certificates of root CAs trusted to sign +/* either remote TLS server certificates or intermediate CA certificates. +/* .IP "\fBtlsproxy_client_CApath ($smtp_tls_CApath)\fR" +/* Directory with PEM format Certification Authority certificates +/* that the Postfix \fBtlsproxy\fR(8) client uses to verify a remote TLS +/* server certificate. +/* .IP "\fBtlsproxy_client_chain_files ($smtp_tls_chain_files)\fR" +/* Files with the Postfix \fBtlsproxy\fR(8) client keys and certificate +/* chains in PEM format. +/* .IP "\fBtlsproxy_client_cert_file ($smtp_tls_cert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client RSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_client_key_file ($smtp_tls_key_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client RSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_client_dcert_file ($smtp_tls_dcert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client DSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_client_dkey_file ($smtp_tls_dkey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client DSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_client_eccert_file ($smtp_tls_eccert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client ECDSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_client_eckey_file ($smtp_tls_eckey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client ECDSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_client_fingerprint_digest ($smtp_tls_fingerprint_digest)\fR" +/* The message digest algorithm used to construct remote TLS server +/* certificate fingerprints. +/* .IP "\fBtlsproxy_client_loglevel ($smtp_tls_loglevel)\fR" +/* Enable additional Postfix \fBtlsproxy\fR(8) client logging of TLS +/* activity. +/* .IP "\fBtlsproxy_client_loglevel_parameter (smtp_tls_loglevel)\fR" +/* The name of the parameter that provides the tlsproxy_client_loglevel +/* value. +/* .IP "\fBtlsproxy_client_scert_verifydepth ($smtp_tls_scert_verifydepth)\fR" +/* The verification depth for remote TLS server certificates. +/* .IP "\fBtlsproxy_client_security_level ($smtp_tls_security_level)\fR" +/* The default TLS security level for the Postfix \fBtlsproxy\fR(8) +/* client. +/* .IP "\fBtlsproxy_client_policy_maps ($smtp_tls_policy_maps)\fR" +/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS +/* security policy by next-hop destination. +/* .IP "\fBtlsproxy_client_use_tls ($smtp_use_tls)\fR" +/* Opportunistic mode: use TLS when a remote server announces TLS +/* support. +/* .IP "\fBtlsproxy_client_enforce_tls ($smtp_enforce_tls)\fR" +/* Enforcement mode: require that SMTP servers use TLS encryption. +/* .IP "\fBtlsproxy_client_per_site ($smtp_tls_per_site)\fR" +/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS +/* usage policy by next-hop destination and by remote TLS server +/* hostname. +/* .PP +/* Introduced with Postfix 3.4.6, 3.3.5, 3.2.10, and 3.1.13: +/* .IP "\fBtls_fast_shutdown_enable (yes)\fR" +/* A workaround for implementations that hang Postfix while shuting +/* down a TLS session, until Postfix times out. +/* OBSOLETE STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* These parameters are supported for compatibility with +/* \fBsmtpd\fR(8) legacy parameters. +/* .IP "\fBtlsproxy_use_tls ($smtpd_use_tls)\fR" +/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients, +/* but do not require that clients use TLS encryption. +/* .IP "\fBtlsproxy_enforce_tls ($smtpd_enforce_tls)\fR" +/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, and +/* require that clients use TLS encryption. +/* RESOURCE CONTROLS +/* .ad +/* .fi +/* .IP "\fBtlsproxy_watchdog_timeout (10s)\fR" +/* How much time a \fBtlsproxy\fR(8) process may take to process local +/* or remote I/O before it is terminated by a built-in watchdog timer. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* postscreen(8), Postfix zombie blocker +/* smtpd(8), Postfix SMTP server +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* This service was introduced with Postfix version 2.8. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <errno.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + + /* + * Utility library. + */ +#include <msg.h> +#include <vstream.h> +#include <iostuff.h> +#include <nbbio.h> +#include <mymalloc.h> +#include <split_at.h> + + /* + * Global library. + */ +#include <been_here.h> +#include <mail_proto.h> +#include <mail_params.h> +#include <mail_conf.h> +#include <mail_version.h> + + /* + * Master library. + */ +#include <mail_server.h> + + /* + * TLS library. + */ +#ifdef USE_TLS +#define TLS_INTERNAL /* XXX */ +#include <tls.h> +#include <tls_proxy.h> + + /* + * Application-specific. + */ +#include <tlsproxy.h> + + /* + * Tunable parameters. We define our clones of the smtpd(8) parameters to + * avoid any confusion about which parameters are used by this program. + */ +int var_smtpd_tls_ccert_vd; +char *var_smtpd_tls_loglevel; +bool var_smtpd_use_tls; +bool var_smtpd_enforce_tls; +bool var_smtpd_tls_ask_ccert; +bool var_smtpd_tls_req_ccert; +bool var_smtpd_tls_set_sessid; +char *var_smtpd_relay_ccerts; +char *var_smtpd_tls_chain_files; +char *var_smtpd_tls_cert_file; +char *var_smtpd_tls_key_file; +char *var_smtpd_tls_dcert_file; +char *var_smtpd_tls_dkey_file; +char *var_smtpd_tls_eccert_file; +char *var_smtpd_tls_eckey_file; +char *var_smtpd_tls_CAfile; +char *var_smtpd_tls_CApath; +char *var_smtpd_tls_ciph; +char *var_smtpd_tls_mand_ciph; +char *var_smtpd_tls_excl_ciph; +char *var_smtpd_tls_mand_excl; +char *var_smtpd_tls_proto; +char *var_smtpd_tls_mand_proto; +char *var_smtpd_tls_dh512_param_file; +char *var_smtpd_tls_dh1024_param_file; +char *var_smtpd_tls_eecdh; +char *var_smtpd_tls_fpt_dgst; +char *var_smtpd_tls_level; + +int var_tlsp_tls_ccert_vd; +char *var_tlsp_tls_loglevel; +bool var_tlsp_use_tls; +bool var_tlsp_enforce_tls; +bool var_tlsp_tls_ask_ccert; +bool var_tlsp_tls_req_ccert; +bool var_tlsp_tls_set_sessid; +char *var_tlsp_tls_chain_files; +char *var_tlsp_tls_cert_file; +char *var_tlsp_tls_key_file; +char *var_tlsp_tls_dcert_file; +char *var_tlsp_tls_dkey_file; +char *var_tlsp_tls_eccert_file; +char *var_tlsp_tls_eckey_file; +char *var_tlsp_tls_CAfile; +char *var_tlsp_tls_CApath; +char *var_tlsp_tls_ciph; +char *var_tlsp_tls_mand_ciph; +char *var_tlsp_tls_excl_ciph; +char *var_tlsp_tls_mand_excl; +char *var_tlsp_tls_proto; +char *var_tlsp_tls_mand_proto; +char *var_tlsp_tls_dh512_param_file; +char *var_tlsp_tls_dh1024_param_file; +char *var_tlsp_tls_eecdh; +char *var_tlsp_tls_fpt_dgst; +char *var_tlsp_tls_level; + +int var_tlsp_watchdog; + + /* + * Defaults for tlsp_clnt_*. + */ +char *var_smtp_tls_loglevel; +int var_smtp_tls_scert_vd; +char *var_smtp_tls_chain_files; +char *var_smtp_tls_cert_file; +char *var_smtp_tls_key_file; +char *var_smtp_tls_dcert_file; +char *var_smtp_tls_dkey_file; +char *var_smtp_tls_eccert_file; +char *var_smtp_tls_eckey_file; +char *var_smtp_tls_CAfile; +char *var_smtp_tls_CApath; +char *var_smtp_tls_fpt_dgst; +char *var_smtp_tls_level; +bool var_smtp_use_tls; +bool var_smtp_enforce_tls; +char *var_smtp_tls_per_site; +char *var_smtp_tls_policy; + +char *var_tlsp_clnt_loglevel; +char *var_tlsp_clnt_logparam; +int var_tlsp_clnt_scert_vd; +char *var_tlsp_clnt_chain_files; +char *var_tlsp_clnt_cert_file; +char *var_tlsp_clnt_key_file; +char *var_tlsp_clnt_dcert_file; +char *var_tlsp_clnt_dkey_file; +char *var_tlsp_clnt_eccert_file; +char *var_tlsp_clnt_eckey_file; +char *var_tlsp_clnt_CAfile; +char *var_tlsp_clnt_CApath; +char *var_tlsp_clnt_fpt_dgst; +char *var_tlsp_clnt_level; +bool var_tlsp_clnt_use_tls; +bool var_tlsp_clnt_enforce_tls; +char *var_tlsp_clnt_per_site; +char *var_tlsp_clnt_policy; + + /* + * TLS per-process status. + */ +static TLS_APPL_STATE *tlsp_server_ctx; +static bool tlsp_pre_jail_done; +static int ask_client_cert; +static char *tlsp_pre_jail_client_param_key; /* pre-jail global params */ +static char *tlsp_pre_jail_client_init_key; /* pre-jail init props */ + + /* + * TLS per-client status. + */ +static HTABLE *tlsp_client_app_cache; /* per-client init props */ +static BH_TABLE *tlsp_params_mismatch_filter; /* per-client nag filter */ + + /* + * Error handling: if a function detects an error, then that function is + * responsible for destroying TLSP_STATE. Exceptions to this principle are + * indicated in the code. + */ + + /* + * Internal status API. + */ +#define TLSP_STAT_OK 0 +#define TLSP_STAT_ERR (-1) + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + + /* + * The code that implements the TLS engine looks simpler than expected. That + * is the result of a great deal of effort, mainly in design and analysis. + * + * The initial use case was to provide TLS support for postscreen(8). + * + * By design, postscreen(8) is an event-driven server that must scale up to a + * large number of clients. This means that postscreen(8) must avoid doing + * CPU-intensive operations such as those in OpenSSL. + * + * tlsproxy(8) runs the OpenSSL code on behalf of postscreen(8), translating + * plaintext SMTP messages from postscreen(8) into SMTP-over-TLS messages to + * the remote SMTP client, and vice versa. As long as postscreen(8) does not + * receive email messages, the cost of doing TLS operations will be modest. + * + * Like postscreen(8), one tlsproxy(8) process services multiple remote SMTP + * clients. Unlike postscreen(8), there can be more than one tlsproxy(8) + * process, although their number is meant to be much smaller than the + * number of remote SMTP clients that talk TLS. + * + * As with postscreen(8), all I/O must be event-driven: encrypted traffic + * between tlsproxy(8) and remote SMTP clients, and plaintext traffic + * between tlsproxy(8) and postscreen(8). Event-driven plaintext I/O is + * straightforward enough that it could be abstracted away with the nbbio(3) + * module. + * + * The event-driven TLS I/O implementation is founded on on-line OpenSSL + * documentation, supplemented by statements from OpenSSL developers on + * public mailing lists. After some field experience with this code, we may + * be able to factor it out as a library module, like nbbio(3), that can + * become part of the TLS library. + * + * Later in the life cycle, tlsproxy(8) has also become an enabler for TLS + * connection reuse across different SMTP client processes. + */ + +static void tlsp_ciphertext_event(int, void *); + +#define TLSP_INIT_TIMEOUT 100 + +static void tlsp_plaintext_event(int event, void *context); + +/* tlsp_drain - delayed exit after "postfix reload" */ + +static void tlsp_drain(char *unused_service, char **unused_argv) +{ + int count; + + /* + * After "postfix reload", complete work-in-progress in the background, + * instead of dropping already-accepted connections on the floor. + * + * All error retry counts shall be limited. Instead of blocking here, we + * could retry failed fork() operations in the event call-back routines, + * but we don't need perfection. The host system is severely overloaded + * and service levels are already way down. + */ + for (count = 0; /* see below */ ; count++) { + if (count >= 5) { + msg_fatal("fork: %m"); + } else if (event_server_drain() != 0) { + msg_warn("fork: %m"); + sleep(1); + continue; + } else { + return; + } + } +} + +/* tlsp_eval_tls_error - translate TLS "error" result into action */ + +static int tlsp_eval_tls_error(TLSP_STATE *state, int err) +{ + int ciphertext_fd = state->ciphertext_fd; + + /* + * The ciphertext file descriptor is in non-blocking mode, meaning that + * each SSL_accept/connect/read/write/shutdown request may return an + * "error" indication that it needs to read or write more ciphertext. The + * purpose of this routine is to translate those "error" indications into + * the appropriate read/write/timeout event requests. + */ + switch (err) { + + /* + * No error means a successful SSL_accept/connect/shutdown request or + * sequence of SSL_read/write requests. Disable read/write events on + * the ciphertext stream. Keep the ciphertext stream timer alive as a + * safety mechanism for the case that the plaintext pseudothreads get + * stuck. + */ + case SSL_ERROR_NONE: + if (state->ssl_last_err != SSL_ERROR_NONE) { + event_disable_readwrite(ciphertext_fd); + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + state->ssl_last_err = SSL_ERROR_NONE; + } + return (TLSP_STAT_OK); + + /* + * The TLS engine wants to write to the network. Turn on + * write/timeout events on the ciphertext stream. + */ + case SSL_ERROR_WANT_WRITE: + if (state->ssl_last_err == SSL_ERROR_WANT_READ) + event_disable_readwrite(ciphertext_fd); + if (state->ssl_last_err != SSL_ERROR_WANT_WRITE) { + event_enable_write(ciphertext_fd, tlsp_ciphertext_event, + (void *) state); + state->ssl_last_err = SSL_ERROR_WANT_WRITE; + } + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + return (TLSP_STAT_OK); + + /* + * The TLS engine wants to read from the network. Turn on + * read/timeout events on the ciphertext stream. + */ + case SSL_ERROR_WANT_READ: + if (state->ssl_last_err == SSL_ERROR_WANT_WRITE) + event_disable_readwrite(ciphertext_fd); + if (state->ssl_last_err != SSL_ERROR_WANT_READ) { + event_enable_read(ciphertext_fd, tlsp_ciphertext_event, + (void *) state); + state->ssl_last_err = SSL_ERROR_WANT_READ; + } + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + return (TLSP_STAT_OK); + + /* + * Some error. Self-destruct. This automagically cleans up all + * pending read/write and timeout event requests, making state a + * dangling pointer. + */ + case SSL_ERROR_SSL: + tls_print_errors(); + /* FALLTHROUGH */ + default: + + /* + * Allow buffered-up plaintext output to trickle out. Permanently + * disable read/write activity on the ciphertext stream, so that this + * function will no longer be called. Keep the ciphertext stream + * timer alive as a safety mechanism for the case that the plaintext + * pseudothreads get stuck. Return into tlsp_strategy(), which will + * enable plaintext write events. + */ +#define TLSP_CAN_TRICKLE_OUT_PLAINTEXT(buf) \ + ((buf) && !NBBIO_ERROR_FLAGS(buf) && NBBIO_WRITE_PEND(buf)) + + if (TLSP_CAN_TRICKLE_OUT_PLAINTEXT(state->plaintext_buf)) { + event_disable_readwrite(ciphertext_fd); + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + state->flags |= TLSP_FLAG_NO_MORE_CIPHERTEXT_IO; + return (TLSP_STAT_OK); + } + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } +} + +/* tlsp_post_handshake - post-handshake processing */ + +static int tlsp_post_handshake(TLSP_STATE *state) +{ + + /* + * Do not assume that tls_server_post_accept() and + * tls_client_post_connect() will always succeed. + */ + if (state->is_server_role) + state->tls_context = tls_server_post_accept(state->tls_context); + else + state->tls_context = tls_client_post_connect(state->tls_context, + state->client_start_props); + if (state->tls_context == 0) { + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * Report TLS handshake results to the tlsproxy client. + * + * Security: this sends internal data over the same local plaintext stream + * that will also be used for sending decrypted remote content from an + * arbitrary remote peer. For this reason we enable decrypted I/O only + * after reporting the TLS handshake results. The Postfix attribute + * protocol is robust enough that an attacker cannot append content. + */ + if ((state->req_flags & TLS_PROXY_FLAG_SEND_CONTEXT) != 0 + && (attr_print(state->plaintext_stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_context_print, + (void *) state->tls_context), + ATTR_TYPE_END) != 0 + || vstream_fflush(state->plaintext_stream) != 0)) { + msg_warn("cannot send TLS context: %m"); + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * Initialize plaintext-related session state. Once we have this behind + * us, the TLSP_STATE destructor will automagically clean up requests for + * plaintext read/write/timeout events, which makes error recovery + * easier. + */ + state->plaintext_buf = + nbbio_create(vstream_fileno(state->plaintext_stream), + VSTREAM_BUFSIZE, state->server_id, + tlsp_plaintext_event, + (void *) state); + return (TLSP_STAT_OK); +} + +/* tlsp_strategy - decide what to read or write next. */ + +static void tlsp_strategy(TLSP_STATE *state) +{ + TLS_SESS_STATE *tls_context = state->tls_context; + NBBIO *plaintext_buf; + int ssl_stat; + int ssl_read_err; + int ssl_write_err; + int handshake_err; + + /* + * This function is called after every ciphertext or plaintext event, to + * schedule new ciphertext or plaintext I/O. + */ + + /* + * Try to make an SSL I/O request. If this fails with SSL_ERROR_WANT_READ + * or SSL_ERROR_WANT_WRITE, enable ciphertext read or write events, and + * retry the SSL I/O request in a later tlsp_strategy() call. + */ + if ((state->flags & TLSP_FLAG_NO_MORE_CIPHERTEXT_IO) == 0) { + + /* + * Do not enable plain-text I/O before completing the TLS handshake. + * Otherwise the remote peer can prepend plaintext to the optional + * TLS_SESS_STATE object. + */ + if (state->flags & TLSP_FLAG_DO_HANDSHAKE) { + state->timeout = state->handshake_timeout; + ERR_clear_error(); + if (state->is_server_role) + ssl_stat = SSL_accept(tls_context->con); + else + ssl_stat = SSL_connect(tls_context->con); + if (ssl_stat != 1) { + handshake_err = SSL_get_error(tls_context->con, ssl_stat); + tlsp_eval_tls_error(state, handshake_err); + /* At this point, state could be a dangling pointer. */ + return; + } + state->flags &= ~TLSP_FLAG_DO_HANDSHAKE; + state->timeout = state->session_timeout; + if (tlsp_post_handshake(state) != TLSP_STAT_OK) { + /* At this point, state is a dangling pointer. */ + return; + } + } + + /* + * Shutdown and self-destruct after NBBIO error. This automagically + * cleans up all pending read/write and timeout event requests. Before + * shutting down TLS, we stop all plain-text I/O events but keep the + * NBBIO error flags. + */ + plaintext_buf = state->plaintext_buf; + if (NBBIO_ERROR_FLAGS(plaintext_buf)) { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) + nbbio_disable_readwrite(state->plaintext_buf); + ERR_clear_error(); + if (!SSL_in_init(tls_context->con) + && (ssl_stat = SSL_shutdown(tls_context->con)) < 0) { + handshake_err = SSL_get_error(tls_context->con, ssl_stat); + tlsp_eval_tls_error(state, handshake_err); + /* At this point, state could be a dangling pointer. */ + return; + } + tlsp_state_free(state); + return; + } + + /* + * Try to move data from the plaintext input buffer to the TLS engine. + * + * XXX We're supposed to repeat the exact same SSL_write() call arguments + * after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE result. Rumor has + * it that this is because each SSL_write() call reads from the buffer + * incrementally, and returns > 0 only after the final byte is processed. + * Rumor also has it that setting SSL_MODE_ENABLE_PARTIAL_WRITE and + * SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER voids this requirement, and that + * repeating the request with an increased request size is OK. + * Unfortunately all this is not or poorly documented, and one has to + * rely on statements from OpenSSL developers in public mailing archives. + */ + ssl_write_err = SSL_ERROR_NONE; + while (NBBIO_READ_PEND(plaintext_buf) > 0) { + ERR_clear_error(); + ssl_stat = SSL_write(tls_context->con, NBBIO_READ_BUF(plaintext_buf), + NBBIO_READ_PEND(plaintext_buf)); + ssl_write_err = SSL_get_error(tls_context->con, ssl_stat); + if (ssl_write_err != SSL_ERROR_NONE) + break; + /* Allow the plaintext pseudothread to read more data. */ + NBBIO_READ_PEND(plaintext_buf) -= ssl_stat; + if (NBBIO_READ_PEND(plaintext_buf) > 0) + memmove(NBBIO_READ_BUF(plaintext_buf), + NBBIO_READ_BUF(plaintext_buf) + ssl_stat, + NBBIO_READ_PEND(plaintext_buf)); + } + + /* + * Try to move data from the TLS engine to the plaintext output buffer. + * Note: data may arrive as a side effect of calling SSL_write(), + * therefore we call SSL_read() after calling SSL_write(). + * + * XXX We're supposed to repeat the exact same SSL_read() call arguments + * after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE result. This + * supposedly means that our plaintext writer must not memmove() the + * plaintext output buffer until after the SSL_read() call succeeds. For + * now I'll ignore this, because 1) SSL_read() is documented to return + * the bytes available, instead of returning > 0 only after the entire + * buffer is processed like SSL_write() does; and 2) there is no "read" + * equivalent of the SSL_R_BAD_WRITE_RETRY, SSL_MODE_ENABLE_PARTIAL_WRITE + * or SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER features. + */ + ssl_read_err = SSL_ERROR_NONE; + while (NBBIO_WRITE_PEND(state->plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { + ERR_clear_error(); + ssl_stat = SSL_read(tls_context->con, + NBBIO_WRITE_BUF(plaintext_buf) + + NBBIO_WRITE_PEND(state->plaintext_buf), + NBBIO_BUFSIZE(plaintext_buf) + - NBBIO_WRITE_PEND(state->plaintext_buf)); + ssl_read_err = SSL_get_error(tls_context->con, ssl_stat); + if (ssl_read_err != SSL_ERROR_NONE) + break; + NBBIO_WRITE_PEND(plaintext_buf) += ssl_stat; + } + + /* + * Try to enable/disable ciphertext read/write events. If SSL_write() was + * satisfied, see if SSL_read() wants to do some work. In case of an + * unrecoverable error, this automagically destroys the session state + * after cleaning up all pending read/write and timeout event requests. + */ + if (tlsp_eval_tls_error(state, ssl_write_err != SSL_ERROR_NONE ? + ssl_write_err : ssl_read_err) < 0) + /* At this point, state is a dangling pointer. */ + return; + } + + /* + * Destroy state when the ciphertext I/O was permanently disbled and we + * can no longer trickle out plaintext. + */ + else { + plaintext_buf = state->plaintext_buf; + if (!TLSP_CAN_TRICKLE_OUT_PLAINTEXT(plaintext_buf)) { + tlsp_state_free(state); + return; + } + } + + /* + * Try to enable/disable plaintext read/write events. Basically, if we + * have nothing to write to the plaintext stream, see if there is + * something to read. If the write buffer is empty and the read buffer is + * full, suspend plaintext I/O until conditions change (but keep the + * timer active, as a safety mechanism in case ciphertext I/O gets + * stuck). + * + * XXX In theory, if the ciphertext peer keeps writing fast enough then we + * would never read from the plaintext stream and cause the latter to + * block. In practice, postscreen(8) limits the number of client + * commands, and thus postscreen(8)'s output will fit in a kernel buffer. + * A remote SMTP server is not supposed to flood the local SMTP client + * with massive replies; it it does, then the local SMTP client should + * deal with it. + */ + if (NBBIO_WRITE_PEND(plaintext_buf) > 0) { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ) + nbbio_disable_readwrite(plaintext_buf); + if ((NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_WRITE) == 0) + nbbio_enable_write(plaintext_buf, state->timeout); + } else if (NBBIO_READ_PEND(plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_WRITE) + nbbio_disable_readwrite(plaintext_buf); + if ((NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ) == 0) + nbbio_enable_read(plaintext_buf, state->timeout); + } else { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) + nbbio_slumber(plaintext_buf, state->timeout); + } +} + +/* tlsp_plaintext_event - plaintext was read/written */ + +static void tlsp_plaintext_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + /* + * Safety alert: the plaintext pseudothreads have "slumbered" for too + * long (see code above). This means that the ciphertext pseudothreads + * are stuck. + */ + if ((NBBIO_ERROR_FLAGS(state->plaintext_buf) & NBBIO_FLAG_TIMEOUT) != 0 + && NBBIO_ACTIVE_FLAGS(state->plaintext_buf) == 0) + msg_warn("deadlock on ciphertext stream for %s", state->remote_endpt); + + /* + * This is easy, because the NBBIO layer has already done the event + * decoding and plaintext I/O for us. All we need to do is decide if we + * want to read or write more plaintext. + */ + tlsp_strategy(state); + /* At this point, state could be a dangling pointer. */ +} + +/* tlsp_ciphertext_event - ciphertext is ready to read/write */ + +static void tlsp_ciphertext_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + /* + * Without a TLS quivalent of the NBBIO layer, we must decode the events + * ourselves and do the ciphertext I/O. Then, we can decide if we want to + * read or write more ciphertext. + */ + if (event == EVENT_READ || event == EVENT_WRITE) { + tlsp_strategy(state); + /* At this point, state could be a dangling pointer. */ + } else { + if (event == EVENT_TIME && state->ssl_last_err == SSL_ERROR_NONE) + msg_warn("deadlock on plaintext stream for %s", + state->remote_endpt); + else + msg_warn("ciphertext read/write %s for %s", + event == EVENT_TIME ? "timeout" : "error", + state->remote_endpt); + tlsp_state_free(state); + } +} + +/* tlsp_client_start_pre_handshake - turn on TLS or force disconnect */ + +static int tlsp_client_start_pre_handshake(TLSP_STATE *state) +{ + state->client_start_props->ctx = state->appl_state; + state->client_start_props->fd = state->ciphertext_fd; + /* These predicates and warning belong inside tls_client_start(). */ + if (!tls_dane_avail() /* mandatory side effects!! */ + + /* + * Why not test for TLS_DANE_BASED()? Because the tlsproxy(8) client has + * already converted its DANE TLSA records into trust anchors, and + * therefore TLS_DANE_HASTA() will be true instead. That exercises the + * code path that updates the shared SSL_CTX with custom X.509 + * verification callbacks for trust anchors. + */ + &&TLS_DANE_HASTA(state->client_start_props->dane)) + msg_warn("%s: DANE or local trust anchor based chain" + " verification requested, but not available", + state->client_start_props->namaddr); + else + state->tls_context = tls_client_start(state->client_start_props); + if (state->tls_context != 0) + return (TLSP_STAT_OK); + + tlsp_state_free(state); + return (TLSP_STAT_ERR); +} + +/* tlsp_server_start_pre_handshake - turn on TLS or force disconnect */ + +static int tlsp_server_start_pre_handshake(TLSP_STATE *state) +{ + TLS_SERVER_START_PROPS props; + static char *cipher_grade; + static VSTRING *cipher_exclusions; + + /* + * The code in this routine is pasted literally from smtpd(8). I am not + * going to sanitize this because doing so surely will break things in + * unexpected ways. + */ + + /* + * Perform the before-handshake portion of per-session initialization. + * Pass a null VSTREAM to indicate that this program will do the + * ciphertext I/O, not libtls. + * + * The cipher grade and exclusions don't change between sessions. Compute + * just once and cache. + */ +#define ADD_EXCLUDE(vstr, str) \ + do { \ + if (*(str)) \ + vstring_sprintf_append((vstr), "%s%s", \ + VSTRING_LEN(vstr) ? " " : "", (str)); \ + } while (0) + + if (cipher_grade == 0) { + cipher_grade = + var_tlsp_enforce_tls ? var_tlsp_tls_mand_ciph : var_tlsp_tls_ciph; + cipher_exclusions = vstring_alloc(10); + ADD_EXCLUDE(cipher_exclusions, var_tlsp_tls_excl_ciph); + if (var_tlsp_enforce_tls) + ADD_EXCLUDE(cipher_exclusions, var_tlsp_tls_mand_excl); + if (ask_client_cert) + ADD_EXCLUDE(cipher_exclusions, "aNULL"); + } + state->tls_context = + TLS_SERVER_START(&props, + ctx = tlsp_server_ctx, + stream = (VSTREAM *) 0,/* unused */ + fd = state->ciphertext_fd, + timeout = 0, /* unused */ + requirecert = (var_tlsp_tls_req_ccert + && var_tlsp_enforce_tls), + serverid = state->server_id, + namaddr = state->remote_endpt, + cipher_grade = cipher_grade, + cipher_exclusions = STR(cipher_exclusions), + mdalg = var_tlsp_tls_fpt_dgst); + + if (state->tls_context == 0) { + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * XXX Do we care about TLS session rate limits? Good postscreen(8) + * clients will occasionally require the tlsproxy to renew their + * whitelist status, but bad clients hammering the server can suck up + * lots of CPU cycles. Per-client concurrency limits in postscreen(8) + * will divert only naive security "researchers". + */ + return (TLSP_STAT_OK); +} + + /* + * From here on down is low-level code that sets up the plumbing before + * passing control to the TLS engine above. + */ + +/* tlsp_request_read_event - pre-handshake event boiler plate */ + +static void tlsp_request_read_event(int fd, EVENT_NOTIFY_FN handler, + int timeout, void *context) +{ + event_enable_read(fd, handler, context); + event_request_timer(handler, context, timeout); +} + +/* tlsp_accept_event - pre-handshake event boiler plate */ + +static void tlsp_accept_event(int event, EVENT_NOTIFY_FN handler, + void *context) +{ + if (event != EVENT_TIME) + event_cancel_timer(handler, context); + else + errno = ETIMEDOUT; + /* tlsp_state_free() disables pre-handshake plaintext I/O events. */ +} + +/* tlsp_get_fd_event - receive final connection hand-off information */ + +static void tlsp_get_fd_event(int event, void *context) +{ + const char *myname = "tlsp_get_fd_event"; + TLSP_STATE *state = (TLSP_STATE *) context; + int plaintext_fd = vstream_fileno(state->plaintext_stream); + int status; + + /* + * At this point we still manually manage plaintext read/write/timeout + * events. Disable I/O events on the plaintext stream until the TLS + * handshake is completed. Every code path must either destroy state, or + * request the next event, otherwise we have a file and memory leak. + */ + tlsp_accept_event(event, tlsp_get_fd_event, (void *) state); + event_disable_readwrite(plaintext_fd); + + if (event != EVENT_READ + || (state->ciphertext_fd = LOCAL_RECV_FD(plaintext_fd)) < 0) { + msg_warn("%s: receive remote SMTP peer file descriptor: %m", myname); + tlsp_state_free(state); + return; + } + + /* + * This is a bit early, to ensure that timer events for this file handle + * are guaranteed to be turned off by the TLSP_STATE destructor. + */ + state->ciphertext_timer = tlsp_ciphertext_event; + non_blocking(state->ciphertext_fd, NON_BLOCKING); + + /* + * Perform the TLS layer before-handshake initialization. We perform the + * remainder after the actual TLS handshake completes. + */ + if (state->is_server_role) + status = tlsp_server_start_pre_handshake(state); + else + status = tlsp_client_start_pre_handshake(state); + if (status != TLSP_STAT_OK) + /* At this point, state is a dangling pointer. */ + return; + + /* + * Trigger the initial proxy server I/Os. + */ + tlsp_strategy(state); + /* At this point, state could be a dangling pointer. */ +} + +/* tlsp_config_diff - report server-client config differences */ + +static void tlsp_log_config_diff(const char *server_cfg, const char *client_cfg) +{ + VSTRING *diff_summary = vstring_alloc(100); + char *saved_server = mystrdup(server_cfg); + char *saved_client = mystrdup(client_cfg); + char *server_field; + char *client_field; + char *server_next; + char *client_next; + + /* + * Not using argv_split(), because it would treat multiple consecutive + * newline characters as one. + */ + for (server_field = saved_server, client_field = saved_client; + server_field && client_field; + server_field = server_next, client_field = client_next) { + server_next = split_at(server_field, '\n'); + client_next = split_at(client_field, '\n'); + if (strcmp(server_field, client_field) != 0) { + if (LEN(diff_summary) > 0) + vstring_sprintf_append(diff_summary, "; "); + vstring_sprintf_append(diff_summary, + "(server) '%s' != (client) '%s'", + server_field, client_field); + } + } + msg_warn("%s", STR(diff_summary)); + + vstring_free(diff_summary); + myfree(saved_client); + myfree(saved_server); +} + +/* tlsp_client_init - initialize a TLS client engine */ + +static TLS_APPL_STATE *tlsp_client_init(TLS_CLIENT_PARAMS *tls_params, + TLS_CLIENT_INIT_PROPS *init_props, + int dane_based) +{ + TLS_APPL_STATE *appl_state; + VSTRING *param_buf; + char *param_key; + VSTRING *init_buf; + char *init_key; + VSTRING *init_buf_for_hashing; + char *init_key_for_hashing; + int log_hints = 0; + + /* + * Use one TLS_APPL_STATE object for all requests that specify the same + * TLS_CLIENT_INIT_PROPS. Each TLS_APPL_STATE owns an SSL_CTX, which is + * expensive to create. Bug: TLS_CLIENT_PARAMS are not used when creating + * a TLS_APPL_STATE instance. + * + * First, compute the TLS_APPL_STATE cache lookup key. Save a copy of the + * pre-jail request TLS_CLIENT_PARAMS and TLSPROXY_CLIENT_INIT_PROPS + * settings, so that we can detect post-jail requests that do not match. + * + * Workaround: salt the hash-table key with DANE on/off info. This avoids + * cross-talk between DANE and non-DANE sessions. Postfix DANE support + * modifies SSL_CTX to override certificate verification because there is + * no other way to do this before OpenSSL 1.1.0. + */ + param_buf = vstring_alloc(100); + param_key = tls_proxy_client_param_with_names_to_string( + param_buf, tls_params); + init_buf = vstring_alloc(100); + init_key = tls_proxy_client_init_with_names_to_string( + init_buf, init_props); + init_buf_for_hashing = vstring_alloc(100); + init_key_for_hashing = STR(vstring_sprintf(init_buf_for_hashing, "%s%d\n", + init_key, dane_based)); + if (tlsp_pre_jail_done == 0) { + if (tlsp_pre_jail_client_param_key == 0 + || tlsp_pre_jail_client_init_key == 0) { + tlsp_pre_jail_client_param_key = mystrdup(param_key); + tlsp_pre_jail_client_init_key = mystrdup(init_key); + } else if (strcmp(tlsp_pre_jail_client_param_key, param_key) != 0 + || strcmp(tlsp_pre_jail_client_init_key, init_key) != 0) { + msg_panic("tlsp_client_init: too many pre-jail calls"); + } + } + + /* + * Log a warning if a post-jail request uses unexpected TLS_CLIENT_PARAMS + * settings. Bug: TLS_CLIENT_PARAMS settings are not used when creating a + * TLS_APPL_STATE instance; this makes a mismatch of TLS_CLIENT_PARAMS + * settings problematic. + */ + if (tlsp_pre_jail_done + && !been_here_fixed(tlsp_params_mismatch_filter, param_key) + && strcmp(tlsp_pre_jail_client_param_key, param_key) != 0) { + msg_warn("request from tlsproxy client with unexpected settings"); + tlsp_log_config_diff(tlsp_pre_jail_client_param_key, param_key); + log_hints = 1; + } + + /* + * Look up the cached TLS_APPL_STATE for this tls_client_init request. + */ + if ((appl_state = (TLS_APPL_STATE *) + htable_find(tlsp_client_app_cache, init_key_for_hashing)) == 0) { + + /* + * Before creating a TLS_APPL_STATE instance, log a warning if a + * post-jail request differs from the saved pre-jail request AND the + * post-jail request specifies file/directory pathname arguments. + * Unexpected requests containing pathnames are problematic after + * chroot (pathname resolution) and after dropping privileges (key + * files must be root read-only). Unexpected requests are not a + * problem as long as they contain no pathnames (for example a + * tls_loglevel change). + * + * We could eliminate some of this complication by adding code that + * opens a cert/key lookup table at pre-jail time, and by reading + * cert/key info on-the-fly from that table. But then all requests + * would still have to specify the same table. + */ +#define NOT_EMPTY(x) ((x) && *(x)) + + if (tlsp_pre_jail_done + && strcmp(tlsp_pre_jail_client_init_key, init_key) != 0 + && (NOT_EMPTY(init_props->chain_files) + || NOT_EMPTY(init_props->cert_file) + || NOT_EMPTY(init_props->key_file) + || NOT_EMPTY(init_props->dcert_file) + || NOT_EMPTY(init_props->dkey_file) + || NOT_EMPTY(init_props->eccert_file) + || NOT_EMPTY(init_props->eckey_file) + || NOT_EMPTY(init_props->CAfile) + || NOT_EMPTY(init_props->CApath))) { + msg_warn("request from tlsproxy client with unexpected settings"); + tlsp_log_config_diff(tlsp_pre_jail_client_init_key, init_key); + log_hints = 1; + } + } + if (log_hints) + msg_warn("to avoid this warning, 1) identify the tlsproxy " + "client that is making this request, 2) configure " + "a custom tlsproxy service with settings that " + "match that tlsproxy client, and 3) configure " + "that tlsproxy client with a tlsproxy_service_name " + "setting that resolves to that custom tlsproxy " + "service"); + + /* + * TLS_APPL_STATE creation may fail when a post-jail request specifies + * unexpected cert/key information, but that is OK because we already + * logged a warning with configuration suggestions. + */ + if (appl_state == 0 + && (appl_state = tls_client_init(init_props)) != 0) { + (void) htable_enter(tlsp_client_app_cache, init_key_for_hashing, + (void *) appl_state); + + /* + * To maintain sanity, allow partial SSL_write() operations, and + * allow SSL_write() buffer pointers to change after a WANT_READ or + * WANT_WRITE result. This is based on OpenSSL developers talking on + * a mailing list, but is not supported by documentation. If this + * code stops working then no-one can be held responsible. + */ + SSL_CTX_set_mode(appl_state->ssl_ctx, + SSL_MODE_ENABLE_PARTIAL_WRITE + | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + } + vstring_free(init_buf_for_hashing); + vstring_free(init_buf); + vstring_free(param_buf); + return (appl_state); +} + +/* tlsp_close_event - pre-handshake plaintext-client close event */ + +static void tlsp_close_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + tlsp_accept_event(event, tlsp_close_event, (void *) state); + tlsp_state_free(state); +} + +/* tlsp_get_request_event - receive initial hand-off info */ + +static void tlsp_get_request_event(int event, void *context) +{ + const char *myname = "tlsp_get_request_event"; + TLSP_STATE *state = (TLSP_STATE *) context; + VSTREAM *plaintext_stream = state->plaintext_stream; + int plaintext_fd = vstream_fileno(plaintext_stream); + static VSTRING *remote_endpt; + static VSTRING *server_id; + int req_flags; + int handshake_timeout; + int session_timeout; + int ready = 0; + + /* + * At this point we still manually manage plaintext read/write/timeout + * events. Every code path must either destroy state or request the next + * event, otherwise this pseudo-thread is idle until the client goes + * away. + */ + tlsp_accept_event(event, tlsp_get_request_event, (void *) state); + + /* + * One-time initialization. + */ + if (remote_endpt == 0) { + remote_endpt = vstring_alloc(10); + server_id = vstring_alloc(10); + } + + /* + * Receive the initial request attributes. Receive the remainder after we + * figure out what role we are expected to play. + */ + if (event != EVENT_READ + || attr_scan(plaintext_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, remote_endpt), + RECV_ATTR_INT(TLS_ATTR_FLAGS, &req_flags), + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &handshake_timeout), + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &session_timeout), + RECV_ATTR_STR(TLS_ATTR_SERVERID, server_id), + ATTR_TYPE_END) != 5) { + msg_warn("%s: receive request attributes: %m", myname); + tlsp_state_free(state); + return; + } + + /* + * XXX We use the same fixed timeout throughout the entire session for + * both plaintext and ciphertext communication. This timeout is just a + * safety feature; the real timeout will be enforced by our plaintext + * peer (except during TLS the handshake, when we intentionally disable + * plaintext I/O). + */ + state->remote_endpt = mystrdup(STR(remote_endpt)); + state->server_id = mystrdup(STR(server_id)); + msg_info("CONNECT %s %s", + (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" : + (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" : + "(bogus_direction)", state->remote_endpt); + state->req_flags = req_flags; + /* state->is_server_role is set below. */ + state->handshake_timeout = handshake_timeout; + state->session_timeout = session_timeout + 10; /* XXX */ + + /* + * Receive the TLS preferences now, to reduce the number of protocol + * roundtrips. + */ + switch (req_flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) { + case TLS_PROXY_FLAG_ROLE_CLIENT: + state->is_server_role = 0; + if (attr_scan(plaintext_stream, ATTR_FLAG_STRICT, + RECV_ATTR_FUNC(tls_proxy_client_param_scan, + (void *) &state->tls_params), + RECV_ATTR_FUNC(tls_proxy_client_init_scan, + (void *) &state->client_init_props), + RECV_ATTR_FUNC(tls_proxy_client_start_scan, + (void *) &state->client_start_props), + ATTR_TYPE_END) != 3) { + msg_warn("%s: receive client TLS settings: %m", myname); + tlsp_state_free(state); + return; + } + state->appl_state = tlsp_client_init(state->tls_params, + state->client_init_props, + + /* + * Why not test for TLS_DANE_BASED()? Because the tlsproxy(8) client + * has already converted its DANE TLSA records into trust anchors, + * and therefore TLS_DANE_HASTA() will be true instead. That + * exercises the code path that updates the shared SSL_CTX with + * custom X.509 verification callbacks for trust anchors. + */ + TLS_DANE_HASTA(state->client_start_props->dane) != 0); + ready = state->appl_state != 0; + break; + case TLS_PROXY_FLAG_ROLE_SERVER: + state->is_server_role = 1; + ready = (tlsp_server_ctx != 0); + break; + default: + state->is_server_role = 0; + msg_warn("%s: bad request flags: 0x%x", myname, req_flags); + ready = 0; + } + + /* + * For portability we must send some data, after receiving the request + * attributes and before receiving the remote file descriptor. + * + * If the requested TLS engine is unavailable, hang up after making sure + * that the plaintext peer has received our "sorry" indication. + */ + if (attr_print(plaintext_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, ready), + ATTR_TYPE_END) != 0 + || vstream_fflush(plaintext_stream) != 0 + || ready == 0) { + tlsp_request_read_event(plaintext_fd, tlsp_close_event, + TLSP_INIT_TIMEOUT, (void *) state); + return; + } else { + tlsp_request_read_event(plaintext_fd, tlsp_get_fd_event, + TLSP_INIT_TIMEOUT, (void *) state); + return; + } +} + +/* tlsp_service - handle new client connection */ + +static void tlsp_service(VSTREAM *plaintext_stream, + char *service, + char **argv) +{ + TLSP_STATE *state; + int plaintext_fd = vstream_fileno(plaintext_stream); + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This program handles multiple connections, so it must not block. We + * use event-driven code for all operations that introduce latency. + * Except that attribute lists are sent/received synchronously, once the + * socket is found to be ready for transmission. + */ + non_blocking(plaintext_fd, NON_BLOCKING); + vstream_control(plaintext_stream, + CA_VSTREAM_CTL_PATH("plaintext"), + CA_VSTREAM_CTL_TIMEOUT(5), + CA_VSTREAM_CTL_END); + + /* + * Receive postscreen's remote SMTP client address/port and socket. + */ + state = tlsp_state_create(service, plaintext_stream); + tlsp_request_read_event(plaintext_fd, tlsp_get_request_event, + TLSP_INIT_TIMEOUT, (void *) state); +} + +/* pre_jail_init_server - pre-jail initialization */ + +static void pre_jail_init_server(void) +{ + TLS_SERVER_INIT_PROPS props; + const char *cert_file; + int have_server_cert; + int no_server_cert_ok; + int require_server_cert; + + /* + * The code in this routine is pasted literally from smtpd(8). I am not + * going to sanitize this because doing so surely will break things in + * unexpected ways. + */ + if (*var_tlsp_tls_level) { + switch (tls_level_lookup(var_tlsp_tls_level)) { + default: + msg_fatal("Invalid TLS level \"%s\"", var_tlsp_tls_level); + /* NOTREACHED */ + break; + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_FPRINT: + msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"", + VAR_TLSP_TLS_LEVEL, var_tlsp_tls_level); + /* FALLTHROUGH */ + case TLS_LEV_ENCRYPT: + var_tlsp_enforce_tls = var_tlsp_use_tls = 1; + break; + case TLS_LEV_MAY: + var_tlsp_enforce_tls = 0; + var_tlsp_use_tls = 1; + break; + case TLS_LEV_NONE: + var_tlsp_enforce_tls = var_tlsp_use_tls = 0; + break; + } + } + var_tlsp_use_tls = var_tlsp_use_tls || var_tlsp_enforce_tls; + if (!var_tlsp_use_tls) { + msg_warn("TLS server role is disabled with %s or %s", + VAR_TLSP_TLS_LEVEL, VAR_TLSP_USE_TLS); + return; + } + + /* + * Load TLS keys before dropping privileges. + * + * Can't use anonymous ciphers if we want client certificates. Must use + * anonymous ciphers if we have no certificates. + */ + ask_client_cert = require_server_cert = + (var_tlsp_tls_ask_ccert + || (var_tlsp_enforce_tls && var_tlsp_tls_req_ccert)); + if (strcasecmp(var_tlsp_tls_cert_file, "none") == 0) { + no_server_cert_ok = 1; + cert_file = ""; + } else { + no_server_cert_ok = 0; + cert_file = var_tlsp_tls_cert_file; + } + have_server_cert = + (*cert_file || *var_tlsp_tls_dcert_file || *var_tlsp_tls_eccert_file); + + if (*var_tlsp_tls_chain_files != 0) { + if (!have_server_cert) + have_server_cert = 1; + else + msg_warn("Both %s and one or more of the legacy " + " %s, %s or %s are non-empty; the legacy " + " parameters will be ignored", + VAR_TLSP_TLS_CHAIN_FILES, + VAR_TLSP_TLS_CERT_FILE, + VAR_TLSP_TLS_ECCERT_FILE, + VAR_TLSP_TLS_DCERT_FILE); + } + /* Some TLS configuration errors are not show stoppers. */ + if (!have_server_cert && require_server_cert) + msg_warn("Need a server cert to request client certs"); + if (!var_tlsp_enforce_tls && var_tlsp_tls_req_ccert) + msg_warn("Can't require client certs unless TLS is required"); + /* After a show-stopper error, log a warning. */ + if (have_server_cert || (no_server_cert_ok && !require_server_cert)) { + + tls_pre_jail_init(TLS_ROLE_SERVER); + + /* + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + tlsp_server_ctx = + TLS_SERVER_INIT(&props, + log_param = VAR_TLSP_TLS_LOGLEVEL, + log_level = var_tlsp_tls_loglevel, + verifydepth = var_tlsp_tls_ccert_vd, + cache_type = TLS_MGR_SCACHE_SMTPD, + set_sessid = var_tlsp_tls_set_sessid, + chain_files = var_tlsp_tls_chain_files, + cert_file = cert_file, + key_file = var_tlsp_tls_key_file, + dcert_file = var_tlsp_tls_dcert_file, + dkey_file = var_tlsp_tls_dkey_file, + eccert_file = var_tlsp_tls_eccert_file, + eckey_file = var_tlsp_tls_eckey_file, + CAfile = var_tlsp_tls_CAfile, + CApath = var_tlsp_tls_CApath, + dh1024_param_file + = var_tlsp_tls_dh1024_param_file, + dh512_param_file + = var_tlsp_tls_dh512_param_file, + eecdh_grade = var_tlsp_tls_eecdh, + protocols = var_tlsp_enforce_tls ? + var_tlsp_tls_mand_proto : + var_tlsp_tls_proto, + ask_ccert = ask_client_cert, + mdalg = var_tlsp_tls_fpt_dgst); + } else { + msg_warn("No server certs available. TLS can't be enabled"); + } + + /* + * To maintain sanity, allow partial SSL_write() operations, and allow + * SSL_write() buffer pointers to change after a WANT_READ or WANT_WRITE + * result. This is based on OpenSSL developers talking on a mailing list, + * but is not supported by documentation. If this code stops working then + * no-one can be held responsible. + */ + if (tlsp_server_ctx) + SSL_CTX_set_mode(tlsp_server_ctx->ssl_ctx, + SSL_MODE_ENABLE_PARTIAL_WRITE + | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); +} + +/* pre_jail_init_client - pre-jail initialization */ + +static void pre_jail_init_client(void) +{ + int clnt_use_tls; + + /* + * The cache with TLS_APPL_STATE instances for different TLS_CLIENT_INIT + * configurations. + */ + tlsp_client_app_cache = htable_create(10); + + /* + * Most sites don't use TLS client certs/keys. In that case, enabling + * tlsproxy-based connection caching is trivial. + * + * But some sites do use TLS client certs/keys, and that is challenging when + * tlsproxy runs in a post-jail environment: chroot breaks pathname + * resolution, and an unprivileged process should not be able to open + * files with secrets. The workaround: assume that most of those sites + * will use a fixed TLS client identity. In that case, tlsproxy can load + * the corresponding certs/keys at pre-jail time, so that secrets can + * remain read-only for root. As long as the tlsproxy pre-jail TLS client + * configuration with cert or key pathnames is the same as the one used + * in the Postfix SMTP client, sites can selectively or globally enable + * tlsproxy-based connection caching without additional TLS + * configuration. + * + * Loading one TLS client configuration at pre-jail time is not sufficient + * for the minority of sites that want to use TLS connection caching with + * multiple TLS client identities. To alert the operator, tlsproxy will + * log a warning when a TLS_CLIENT_INIT message specifies a different + * configuration than the tlsproxy pre-jail client configuration, and + * that different configuration specifies file/directory pathname + * arguments. The workaround is to have one tlsproxy process per TLS + * client identity. + * + * The general solution for single-identity or multi-identity clients is to + * stop loading certs and keys from individual files. Instead, have a + * cert/key map, indexed by client identity, read-only by root. After + * opening the map as root at pre-jail time, tlsproxy can read certs/keys + * on-the-fly as an unprivileged process at post-jail time. This is the + * approach that was already proposed for server-side SNI support, and it + * could be reused here. It would also end the proliferation of RSA + * cert/key parameters, DSA cert/key parameters, EC cert/key parameters, + * and so on. + * + * Horror: In order to create the same pre-jail TLS client context as the + * one used in the Postfix SMTP client, we have to duplicate intricate + * SMTP client code, including a handful configuration parameters that + * tlsproxy does not need. We must duplicate the logic, so that we only + * load certs and keys when the SMTP client would load them. + */ + if (*var_tlsp_clnt_level != 0) + switch (tls_level_lookup(var_tlsp_clnt_level)) { + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_DANE_ONLY: + case TLS_LEV_FPRINT: + case TLS_LEV_ENCRYPT: + var_tlsp_clnt_use_tls = var_tlsp_clnt_enforce_tls = 1; + break; + case TLS_LEV_DANE: + case TLS_LEV_MAY: + var_tlsp_clnt_use_tls = 1; + var_tlsp_clnt_enforce_tls = 0; + break; + case TLS_LEV_NONE: + var_tlsp_clnt_use_tls = var_tlsp_clnt_enforce_tls = 0; + break; + default: + /* tls_level_lookup() logs no warning. */ + /* session_tls_init() assumes that var_tlsp_clnt_level is sane. */ + msg_fatal("Invalid TLS level \"%s\"", var_tlsp_clnt_level); + } + clnt_use_tls = (var_tlsp_clnt_use_tls || var_tlsp_clnt_enforce_tls); + + /* + * Initialize the TLS data before entering the chroot jail. + */ + if (clnt_use_tls || var_tlsp_clnt_per_site[0] || var_tlsp_clnt_policy[0]) { + TLS_CLIENT_PARAMS tls_params; + TLS_CLIENT_INIT_PROPS init_props; + int dane_based_mode; + + tls_pre_jail_init(TLS_ROLE_CLIENT); + + /* + * We get stronger type safety and a cleaner interface by combining + * the various parameters into a single tls_client_props structure. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + (void) tls_proxy_client_param_from_config(&tls_params); + (void) TLS_CLIENT_INIT_ARGS(&init_props, + log_param = var_tlsp_clnt_logparam, + log_level = var_tlsp_clnt_loglevel, + verifydepth = var_tlsp_clnt_scert_vd, + cache_type = TLS_MGR_SCACHE_SMTP, + chain_files = var_tlsp_clnt_chain_files, + cert_file = var_tlsp_clnt_cert_file, + key_file = var_tlsp_clnt_key_file, + dcert_file = var_tlsp_clnt_dcert_file, + dkey_file = var_tlsp_clnt_dkey_file, + eccert_file = var_tlsp_clnt_eccert_file, + eckey_file = var_tlsp_clnt_eckey_file, + CAfile = var_tlsp_clnt_CAfile, + CApath = var_tlsp_clnt_CApath, + mdalg = var_tlsp_clnt_fpt_dgst); + for (dane_based_mode = 0; dane_based_mode < 2; dane_based_mode++) { + if (tlsp_client_init(&tls_params, &init_props, + dane_based_mode) == 0) + msg_warn("TLS client initialization failed"); + } + } +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Initialize roles separately. + */ + pre_jail_init_server(); + pre_jail_init_client(); + + /* + * tlsp_client_init() needs to know if it is called pre-jail or + * post-jail. + */ + tlsp_pre_jail_done = 1; + + /* + * Bug: TLS_CLIENT_PARAMS attributes are not used when creating a + * TLS_APPL_STATE instance; we can only warn about attribute mismatches. + */ + tlsp_params_mismatch_filter = been_here_init(BH_BOUND_NONE, BH_FLAG_NONE); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + + /* + * Each table below initializes the named variables to their implicit + * default value, or to the explicit value in main.cf or master.cf. Here, + * "compat" means that a table initializes a variable "smtpd_blah" or + * "smtp_blah" that provides the implicit default value for variable + * "tlsproxy_blah" which is initialized by a different table. To make + * this work, the variables in a "compat" table must be initialized + * before the variables in the corresponding non-compat table. + */ + static const CONFIG_INT_TABLE compat_int_table[] = { + VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, + VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, + 0, + }; + static const CONFIG_NINT_TABLE nint_table[] = { + VAR_TLSP_TLS_CCERT_VD, DEF_TLSP_TLS_CCERT_VD, &var_tlsp_tls_ccert_vd, 0, 0, + VAR_TLSP_CLNT_SCERT_VD, DEF_TLSP_CLNT_SCERT_VD, &var_tlsp_clnt_scert_vd, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_TLSP_WATCHDOG, DEF_TLSP_WATCHDOG, &var_tlsp_watchdog, 10, 0, + 0, + }; + static const CONFIG_BOOL_TABLE compat_bool_table[] = { + VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls, + VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls, + VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert, + VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert, + VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid, + VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls, + VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_TLSP_USE_TLS, DEF_TLSP_USE_TLS, &var_tlsp_use_tls, + VAR_TLSP_ENFORCE_TLS, DEF_TLSP_ENFORCE_TLS, &var_tlsp_enforce_tls, + VAR_TLSP_TLS_ACERT, DEF_TLSP_TLS_ACERT, &var_tlsp_tls_ask_ccert, + VAR_TLSP_TLS_RCERT, DEF_TLSP_TLS_RCERT, &var_tlsp_tls_req_ccert, + VAR_TLSP_TLS_SET_SESSID, DEF_TLSP_TLS_SET_SESSID, &var_tlsp_tls_set_sessid, + VAR_TLSP_CLNT_USE_TLS, DEF_TLSP_CLNT_USE_TLS, &var_tlsp_clnt_use_tls, + VAR_TLSP_CLNT_ENFORCE_TLS, DEF_TLSP_CLNT_ENFORCE_TLS, &var_tlsp_clnt_enforce_tls, + 0, + }; + static const CONFIG_STR_TABLE compat_str_table[] = { + VAR_SMTPD_TLS_CHAIN_FILES, DEF_SMTPD_TLS_CHAIN_FILES, &var_smtpd_tls_chain_files, 0, 0, + VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0, + VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0, + VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0, + VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0, + VAR_SMTPD_TLS_ECCERT_FILE, DEF_SMTPD_TLS_ECCERT_FILE, &var_smtpd_tls_eccert_file, 0, 0, + VAR_SMTPD_TLS_ECKEY_FILE, DEF_SMTPD_TLS_ECKEY_FILE, &var_smtpd_tls_eckey_file, 0, 0, + VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0, + VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0, + VAR_SMTPD_TLS_CIPH, DEF_SMTPD_TLS_CIPH, &var_smtpd_tls_ciph, 1, 0, + VAR_SMTPD_TLS_MAND_CIPH, DEF_SMTPD_TLS_MAND_CIPH, &var_smtpd_tls_mand_ciph, 1, 0, + VAR_SMTPD_TLS_EXCL_CIPH, DEF_SMTPD_TLS_EXCL_CIPH, &var_smtpd_tls_excl_ciph, 0, 0, + VAR_SMTPD_TLS_MAND_EXCL, DEF_SMTPD_TLS_MAND_EXCL, &var_smtpd_tls_mand_excl, 0, 0, + VAR_SMTPD_TLS_PROTO, DEF_SMTPD_TLS_PROTO, &var_smtpd_tls_proto, 0, 0, + VAR_SMTPD_TLS_MAND_PROTO, DEF_SMTPD_TLS_MAND_PROTO, &var_smtpd_tls_mand_proto, 0, 0, + VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0, + VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0, + VAR_SMTPD_TLS_EECDH, DEF_SMTPD_TLS_EECDH, &var_smtpd_tls_eecdh, 1, 0, + VAR_SMTPD_TLS_FPT_DGST, DEF_SMTPD_TLS_FPT_DGST, &var_smtpd_tls_fpt_dgst, 1, 0, + VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0, + VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0, + VAR_SMTP_TLS_CHAIN_FILES, DEF_SMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0, + VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, + VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0, + VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, + VAR_SMTP_TLS_LEVEL, DEF_SMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0, + VAR_SMTP_TLS_POLICY, DEF_SMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_TLSP_TLS_CHAIN_FILES, DEF_TLSP_TLS_CHAIN_FILES, &var_tlsp_tls_chain_files, 0, 0, + VAR_TLSP_TLS_CERT_FILE, DEF_TLSP_TLS_CERT_FILE, &var_tlsp_tls_cert_file, 0, 0, + VAR_TLSP_TLS_KEY_FILE, DEF_TLSP_TLS_KEY_FILE, &var_tlsp_tls_key_file, 0, 0, + VAR_TLSP_TLS_DCERT_FILE, DEF_TLSP_TLS_DCERT_FILE, &var_tlsp_tls_dcert_file, 0, 0, + VAR_TLSP_TLS_DKEY_FILE, DEF_TLSP_TLS_DKEY_FILE, &var_tlsp_tls_dkey_file, 0, 0, + VAR_TLSP_TLS_ECCERT_FILE, DEF_TLSP_TLS_ECCERT_FILE, &var_tlsp_tls_eccert_file, 0, 0, + VAR_TLSP_TLS_ECKEY_FILE, DEF_TLSP_TLS_ECKEY_FILE, &var_tlsp_tls_eckey_file, 0, 0, + VAR_TLSP_TLS_CA_FILE, DEF_TLSP_TLS_CA_FILE, &var_tlsp_tls_CAfile, 0, 0, + VAR_TLSP_TLS_CA_PATH, DEF_TLSP_TLS_CA_PATH, &var_tlsp_tls_CApath, 0, 0, + VAR_TLSP_TLS_CIPH, DEF_TLSP_TLS_CIPH, &var_tlsp_tls_ciph, 1, 0, + VAR_TLSP_TLS_MAND_CIPH, DEF_TLSP_TLS_MAND_CIPH, &var_tlsp_tls_mand_ciph, 1, 0, + VAR_TLSP_TLS_EXCL_CIPH, DEF_TLSP_TLS_EXCL_CIPH, &var_tlsp_tls_excl_ciph, 0, 0, + VAR_TLSP_TLS_MAND_EXCL, DEF_TLSP_TLS_MAND_EXCL, &var_tlsp_tls_mand_excl, 0, 0, + VAR_TLSP_TLS_PROTO, DEF_TLSP_TLS_PROTO, &var_tlsp_tls_proto, 0, 0, + VAR_TLSP_TLS_MAND_PROTO, DEF_TLSP_TLS_MAND_PROTO, &var_tlsp_tls_mand_proto, 0, 0, + VAR_TLSP_TLS_512_FILE, DEF_TLSP_TLS_512_FILE, &var_tlsp_tls_dh512_param_file, 0, 0, + VAR_TLSP_TLS_1024_FILE, DEF_TLSP_TLS_1024_FILE, &var_tlsp_tls_dh1024_param_file, 0, 0, + VAR_TLSP_TLS_EECDH, DEF_TLSP_TLS_EECDH, &var_tlsp_tls_eecdh, 1, 0, + VAR_TLSP_TLS_FPT_DGST, DEF_TLSP_TLS_FPT_DGST, &var_tlsp_tls_fpt_dgst, 1, 0, + VAR_TLSP_TLS_LOGLEVEL, DEF_TLSP_TLS_LOGLEVEL, &var_tlsp_tls_loglevel, 0, 0, + VAR_TLSP_TLS_LEVEL, DEF_TLSP_TLS_LEVEL, &var_tlsp_tls_level, 0, 0, + VAR_TLSP_CLNT_LOGLEVEL, DEF_TLSP_CLNT_LOGLEVEL, &var_tlsp_clnt_loglevel, 0, 0, + VAR_TLSP_CLNT_LOGPARAM, DEF_TLSP_CLNT_LOGPARAM, &var_tlsp_clnt_logparam, 0, 0, + VAR_TLSP_CLNT_CHAIN_FILES, DEF_TLSP_CLNT_CHAIN_FILES, &var_tlsp_clnt_chain_files, 0, 0, + VAR_TLSP_CLNT_CERT_FILE, DEF_TLSP_CLNT_CERT_FILE, &var_tlsp_clnt_cert_file, 0, 0, + VAR_TLSP_CLNT_KEY_FILE, DEF_TLSP_CLNT_KEY_FILE, &var_tlsp_clnt_key_file, 0, 0, + VAR_TLSP_CLNT_DCERT_FILE, DEF_TLSP_CLNT_DCERT_FILE, &var_tlsp_clnt_dcert_file, 0, 0, + VAR_TLSP_CLNT_DKEY_FILE, DEF_TLSP_CLNT_DKEY_FILE, &var_tlsp_clnt_dkey_file, 0, 0, + VAR_TLSP_CLNT_ECCERT_FILE, DEF_TLSP_CLNT_ECCERT_FILE, &var_tlsp_clnt_eccert_file, 0, 0, + VAR_TLSP_CLNT_ECKEY_FILE, DEF_TLSP_CLNT_ECKEY_FILE, &var_tlsp_clnt_eckey_file, 0, 0, + VAR_TLSP_CLNT_CAFILE, DEF_TLSP_CLNT_CAFILE, &var_tlsp_clnt_CAfile, 0, 0, + VAR_TLSP_CLNT_CAPATH, DEF_TLSP_CLNT_CAPATH, &var_tlsp_clnt_CApath, 0, 0, + VAR_TLSP_CLNT_FPT_DGST, DEF_TLSP_CLNT_FPT_DGST, &var_tlsp_clnt_fpt_dgst, 1, 0, + VAR_TLSP_CLNT_LEVEL, DEF_TLSP_CLNT_LEVEL, &var_tlsp_clnt_level, 0, 0, + VAR_TLSP_CLNT_PER_SITE, DEF_TLSP_CLNT_PER_SITE, &var_tlsp_clnt_per_site, 0, 0, + VAR_TLSP_CLNT_POLICY, DEF_TLSP_CLNT_POLICY, &var_tlsp_clnt_policy, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Pass control to the event-driven service skeleton. + */ + event_server_main(argc, argv, tlsp_service, + CA_MAIL_SERVER_INT_TABLE(compat_int_table), + CA_MAIL_SERVER_NINT_TABLE(nint_table), + CA_MAIL_SERVER_STR_TABLE(compat_str_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_BOOL_TABLE(compat_bool_table), + CA_MAIL_SERVER_NBOOL_TABLE(nbool_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_SLOW_EXIT(tlsp_drain), + CA_MAIL_SERVER_RETIRE_ME, + CA_MAIL_SERVER_WATCHDOG(&var_tlsp_watchdog), + CA_MAIL_SERVER_UNLIMITED, + 0); +} + +#else + +/* tlsp_service - respond to external trigger(s), non-TLS version */ + +static void tlsp_service(VSTREAM *stream, char *unused_service, + char **unused_argv) +{ + msg_info("TLS support is not compiled in -- exiting"); + event_server_disconnect(stream); +} + +/* main - the main program */ + +int main(int argc, char **argv) +{ + + /* + * We can't simply use msg_fatal() here, because the logging hasn't been + * initialized. The text would disappear because stderr is redirected to + * /dev/null. + * + * We invoke event_server_main() to complete program initialization + * (including logging) and then invoke the tlsp_service() routine to log + * the message that says why this program will not run. + */ + event_server_main(argc, argv, tlsp_service, + 0); +} + +#endif diff --git a/src/tlsproxy/tlsproxy.h b/src/tlsproxy/tlsproxy.h new file mode 100644 index 0000000..eacbb1f --- /dev/null +++ b/src/tlsproxy/tlsproxy.h @@ -0,0 +1,69 @@ +/*++ +/* NAME +/* tlsproxy 3h +/* SUMMARY +/* tlsproxy internal interfaces +/* SYNOPSIS +/* #include <tlsproxy.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstream.h> +#include <nbbio.h> + + /* + * TLS library. + */ +#include <tls.h> + + /* + * Internal interface. + */ +typedef struct { + int flags; /* see below */ + int req_flags; /* request flags, see tls_proxy.h */ + int is_server_role; /* avoid clumsy handler code */ + char *service; /* argv[0] */ + VSTREAM *plaintext_stream; /* local peer: postscreen(8), etc. */ + NBBIO *plaintext_buf; /* plaintext buffer */ + int ciphertext_fd; /* remote peer */ + EVENT_NOTIFY_FN ciphertext_timer; /* kludge */ + int timeout; /* read/write time limit */ + int handshake_timeout; /* in-handshake time limit */ + int session_timeout; /* post-handshake time limit */ + char *remote_endpt; /* printable remote endpoint */ + char *server_id; /* cache management */ + TLS_APPL_STATE *appl_state; /* libtls state */ + TLS_SESS_STATE *tls_context; /* libtls state */ + int ssl_last_err; /* TLS I/O state */ + TLS_CLIENT_PARAMS *tls_params; /* globals not part of init_props */ + TLS_SERVER_INIT_PROPS *server_init_props; + TLS_SERVER_START_PROPS *server_start_props; + TLS_CLIENT_INIT_PROPS *client_init_props; + TLS_CLIENT_START_PROPS *client_start_props; +} TLSP_STATE; + +#define TLSP_FLAG_DO_HANDSHAKE (1<<0) +#define TLSP_FLAG_NO_MORE_CIPHERTEXT_IO (1<<1) /* overrides DO_HANDSHAKE */ + +extern TLSP_STATE *tlsp_state_create(const char *, VSTREAM *); +extern void tlsp_state_free(TLSP_STATE *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/tlsproxy/tlsproxy_state.c b/src/tlsproxy/tlsproxy_state.c new file mode 100644 index 0000000..df6cbda --- /dev/null +++ b/src/tlsproxy/tlsproxy_state.c @@ -0,0 +1,169 @@ +/*++ +/* NAME +/* tlsproxy_state 3 +/* SUMMARY +/* Postfix SMTP server +/* SYNOPSIS +/* #include <tlsproxy.h> +/* +/* TLSP_STATE *tlsp_state_create(service, plaintext_stream) +/* const char *service; +/* VSTREAM *plaintext_stream; +/* +/* void tlsp_state_free(state) +/* TLSP_STATE *state; +/* DESCRIPTION +/* This module provides TLSP_STATE constructor and destructor +/* routines. +/* +/* tlsp_state_create() initializes session context. +/* +/* tlsp_state_free() destroys session context. If the handshake +/* was in progress, it logs a 'handshake failed' message. +/* +/* Arguments: +/* .IP service +/* The service name for the TLS library. This argument is copied. +/* The destructor will automatically destroy the string. +/* .IP plaintext_stream +/* The VSTREAM between postscreen(8) and tlsproxy(8). +/* The destructor will automatically close the stream. +/* .PP +/* Other structure members are set by the application. The +/* text below describes how the TLSP_STATE destructor +/* disposes of them. +/* .IP plaintext_buf +/* NBBIO for plaintext I/O. +/* The destructor will automatically turn off read/write/timeout +/* events and destroy the NBBIO. +/* .IP ciphertext_fd +/* The file handle for the remote SMTP client socket. +/* The destructor will automatically turn off read/write events +/* and close the file handle. +/* .IP ciphertext_timer +/* The destructor will automatically turn off this time event. +/* .IP timeout +/* Time limit for plaintext and ciphertext I/O. +/* .IP remote_endpt +/* Printable remote endpoint name. +/* The destructor will automatically destroy the string. +/* .IP server_id +/* TLS session cache identifier. +/* The destructor will automatically destroy the string. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library. + */ +#include <msg.h> +#include <mymalloc.h> +#include <nbbio.h> + + /* + * Master library. + */ +#include <mail_server.h> + + /* + * TLS library. + */ +#ifdef USE_TLS +#define TLS_INTERNAL /* XXX */ +#include <tls.h> +#include <tls_proxy.h> + + /* + * Application-specific. + */ +#include <tlsproxy.h> + +/* tlsp_state_create - create TLS proxy state object */ + +TLSP_STATE *tlsp_state_create(const char *service, + VSTREAM *plaintext_stream) +{ + TLSP_STATE *state = (TLSP_STATE *) mymalloc(sizeof(*state)); + + state->flags = TLSP_FLAG_DO_HANDSHAKE; + state->service = mystrdup(service); + state->plaintext_stream = plaintext_stream; + state->plaintext_buf = 0; + state->ciphertext_fd = -1; + state->ciphertext_timer = 0; + state->timeout = -1; + state->remote_endpt = 0; + state->server_id = 0; + state->tls_context = 0; + state->tls_params = 0; + state->server_init_props = 0; + state->server_start_props = 0; + state->client_init_props = 0; + state->client_start_props = 0; + + return (state); +} + +/* tlsp_state_free - destroy state objects, connection and events */ + +void tlsp_state_free(TLSP_STATE *state) +{ + /* Don't log failure after plaintext EOF. */ + if (state->remote_endpt && state->server_id + && (state->flags & TLSP_FLAG_DO_HANDSHAKE)) + msg_info("TLS handshake failed for service=%s peer=%s", + state->server_id, state->remote_endpt); + myfree(state->service); + if (state->plaintext_buf) /* turns off plaintext events */ + nbbio_free(state->plaintext_buf); + else + event_disable_readwrite(vstream_fileno(state->plaintext_stream)); + event_server_disconnect(state->plaintext_stream); + if (state->ciphertext_fd >= 0) { + event_disable_readwrite(state->ciphertext_fd); + (void) close(state->ciphertext_fd); + } + if (state->ciphertext_timer) + event_cancel_timer(state->ciphertext_timer, (void *) state); + if (state->remote_endpt) { + msg_info("DISCONNECT %s", state->remote_endpt); + myfree(state->remote_endpt); + } + if (state->server_id) + myfree(state->server_id); + if (state->tls_context) + tls_free_context(state->tls_context); + if (state->tls_params) + tls_proxy_client_param_free(state->tls_params); + if (state->server_init_props) + tls_proxy_server_init_free(state->server_init_props); + if (state->server_start_props) + tls_proxy_server_start_free(state->server_start_props); + if (state->client_init_props) + tls_proxy_client_init_free(state->client_init_props); + if (state->client_start_props) + tls_proxy_client_start_free(state->client_start_props); + myfree((void *) state); +} + +#endif |