summaryrefslogtreecommitdiffstats
path: root/src/tls
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
commitb7c15c31519dc44c1f691e0466badd556ffe9423 (patch)
treef944572f288bab482a615e09af627d9a2b6727d8 /src/tls
parentInitial commit. (diff)
downloadpostfix-b7c15c31519dc44c1f691e0466badd556ffe9423.tar.xz
postfix-b7c15c31519dc44c1f691e0466badd556ffe9423.zip
Adding upstream version 3.7.10.upstream/3.7.10
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
l---------src/tls/.indent.pro1
-rw-r--r--src/tls/Makefile.in601
-rw-r--r--src/tls/bad-back-to-back-keys.pem64
-rw-r--r--src/tls/bad-back-to-back-keys.pem.ref2
-rw-r--r--src/tls/bad-ec-cert-before-key.pem36
-rw-r--r--src/tls/bad-ec-cert-before-key.pem.ref2
-rw-r--r--src/tls/bad-key-cert-mismatch.pem36
-rw-r--r--src/tls/bad-key-cert-mismatch.pem.ref2
-rw-r--r--src/tls/bad-rsa-key-last.pem64
-rw-r--r--src/tls/bad-rsa-key-last.pem.ref2
-rw-r--r--src/tls/ecca-cert.pem10
-rw-r--r--src/tls/ecca-pkey.pem5
-rw-r--r--src/tls/ecee-cert.pem11
-rw-r--r--src/tls/ecee-pkey.pem5
-rw-r--r--src/tls/ecroot-cert.pem10
-rw-r--r--src/tls/ecroot-pkey.pem5
-rw-r--r--src/tls/good-mixed-keyfirst.pem45
-rw-r--r--src/tls/good-mixed-keyfirst.pem.ref12
-rw-r--r--src/tls/good-mixed-keylast.pem45
-rw-r--r--src/tls/good-mixed-keylast.pem.ref12
-rw-r--r--src/tls/good-mixed-keymiddle.pem45
-rw-r--r--src/tls/good-mixed-keymiddle.pem.ref12
-rw-r--r--src/tls/goodchains.pem121
-rw-r--r--src/tls/goodchains.pem.ref24
-rw-r--r--src/tls/mkcert.sh264
-rw-r--r--src/tls/rsaca-cert.pem19
-rw-r--r--src/tls/rsaca-pkey.pem28
-rw-r--r--src/tls/rsaee-cert.pem20
-rw-r--r--src/tls/rsaee-pkey.pem28
-rw-r--r--src/tls/rsaroot-cert.pem18
-rw-r--r--src/tls/rsaroot-pkey.pem28
-rw-r--r--src/tls/tls.h728
-rw-r--r--src/tls/tls_bio_ops.c296
-rw-r--r--src/tls/tls_certkey.c721
-rw-r--r--src/tls/tls_client.c1250
-rw-r--r--src/tls/tls_dane.c1357
-rw-r--r--src/tls/tls_dane.sh211
-rw-r--r--src/tls/tls_dh.c384
-rw-r--r--src/tls/tls_fprint.c435
-rw-r--r--src/tls/tls_level.c95
-rw-r--r--src/tls/tls_mgr.c486
-rw-r--r--src/tls/tls_mgr.h71
-rw-r--r--src/tls/tls_misc.c1712
-rw-r--r--src/tls/tls_prng.h50
-rw-r--r--src/tls/tls_prng_dev.c155
-rw-r--r--src/tls/tls_prng_egd.c166
-rw-r--r--src/tls/tls_prng_exch.c142
-rw-r--r--src/tls/tls_prng_file.c155
-rw-r--r--src/tls/tls_proxy.h287
-rw-r--r--src/tls/tls_proxy_client_misc.c130
-rw-r--r--src/tls/tls_proxy_client_print.c294
-rw-r--r--src/tls/tls_proxy_client_scan.c496
-rw-r--r--src/tls/tls_proxy_clnt.c300
-rw-r--r--src/tls/tls_proxy_context_print.c114
-rw-r--r--src/tls/tls_proxy_context_scan.c190
-rw-r--r--src/tls/tls_proxy_server_print.c143
-rw-r--r--src/tls/tls_proxy_server_scan.c245
-rw-r--r--src/tls/tls_rsa.c0
-rw-r--r--src/tls/tls_scache.c591
-rw-r--r--src/tls/tls_scache.h73
-rw-r--r--src/tls/tls_seed.c88
-rw-r--r--src/tls/tls_server.c1051
-rw-r--r--src/tls/tls_session.c185
-rw-r--r--src/tls/tls_stream.c160
-rw-r--r--src/tls/tls_verify.c425
-rw-r--r--src/tls/warn-mixed-multi-key.pem51
-rw-r--r--src/tls/warn-mixed-multi-key.pem.ref13
l---------src/tlsmgr/.indent.pro1
-rw-r--r--src/tlsmgr/Makefile.in100
-rw-r--r--src/tlsmgr/tlsmgr.c1115
l---------src/tlsproxy/.indent.pro1
-rw-r--r--src/tlsproxy/Makefile.in117
-rw-r--r--src/tlsproxy/tlsproxy.c1968
-rw-r--r--src/tlsproxy/tlsproxy.h69
-rw-r--r--src/tlsproxy/tlsproxy_state.c169
75 files changed, 18367 insertions, 0 deletions
diff --git a/src/tls/.indent.pro b/src/tls/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/tls/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/tls/Makefile.in b/src/tls/Makefile.in
new file mode 100644
index 0000000..948afab
--- /dev/null
+++ b/src/tls/Makefile.in
@@ -0,0 +1,601 @@
+SHELL = /bin/sh
+SRCS = tls_prng_dev.c tls_prng_egd.c tls_prng_file.c tls_fprint.c \
+ tls_prng_exch.c tls_stream.c tls_bio_ops.c tls_misc.c tls_dh.c \
+ tls_verify.c tls_dane.c tls_certkey.c tls_session.c \
+ tls_client.c tls_server.c tls_scache.c tls_mgr.c tls_seed.c \
+ tls_level.c \
+ tls_proxy_clnt.c tls_proxy_context_print.c tls_proxy_context_scan.c \
+ tls_proxy_client_init_print.c tls_proxy_client_init_scan.c \
+ tls_proxy_server_init_print.c tls_proxy_server_init_scan.c \
+ tls_proxy_client_start_print.c tls_proxy_client_start_scan.c \
+ tls_proxy_server_start_print.c tls_proxy_server_start_scan.c \
+ tls_proxy_client_misc.c
+OBJS = tls_prng_dev.o tls_prng_egd.o tls_prng_file.o tls_fprint.o \
+ tls_prng_exch.o tls_stream.o tls_bio_ops.o tls_misc.o tls_dh.o \
+ tls_verify.o tls_dane.o tls_certkey.o tls_session.o \
+ tls_client.o tls_server.o tls_scache.o tls_mgr.o tls_seed.o \
+ tls_level.o \
+ tls_proxy_clnt.o tls_proxy_context_print.o tls_proxy_context_scan.o \
+ tls_proxy_client_print.o tls_proxy_client_scan.o \
+ tls_proxy_server_print.o tls_proxy_server_scan.o \
+ tls_proxy_client_misc.o
+HDRS = tls.h tls_prng.h tls_scache.h tls_mgr.h tls_proxy.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = lib$(LIB_PREFIX)tls$(LIB_SUFFIX)
+TESTPROG= tls_dh tls_mgr tls_dane tls_certkey
+
+LIBS = ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+MAKES =
+
+.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: tls_certkey_tests
+
+tls_certkey_tests: test
+ @echo Testing loading of keys and certs
+ @for pem in goodchains.pem; do \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem: OK"; \
+ done; \
+ for pem in *-mixed-*.pem ; do \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -m $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem: OK"; \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -k $$pem $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem (with key in $$pem): OK"; \
+ case $$pem in good-*) \
+ ln -sf $$pem tmpkey.pem; \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -k tmpkey.pem $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem (with key in tmpkey.pem): OK"; \
+ rm -f tmpkey.pem;; \
+ esac; \
+ done; \
+ for pem in bad-*.pem; do \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey $$pem > $$pem.out 2>&1 && exit 1 || : ok; \
+ egrep -v 'TLS library problem' $$pem.out | diff $$pem.ref - || \
+ exit 1; \
+ echo " $$pem: OK"; \
+ done
+
+root_tests:
+
+$(LIB): $(OBJS)
+ $(AR) $(ARFL) $(LIB) $?
+ $(RANLIB) $(LIB)
+ $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(RANLIB) $(LIB_DIR)/$(LIB)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h 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 $(LIB) *core $(TESTPROG) junk *.pem.out
+ rm -rf printfck
+
+tidy: clean
+
+tls_dh: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_mgr: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_dane: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_certkey: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+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'
+tls_bio_ops.o: ../../include/argv.h
+tls_bio_ops.o: ../../include/check_arg.h
+tls_bio_ops.o: ../../include/dns.h
+tls_bio_ops.o: ../../include/iostuff.h
+tls_bio_ops.o: ../../include/msg.h
+tls_bio_ops.o: ../../include/myaddrinfo.h
+tls_bio_ops.o: ../../include/name_code.h
+tls_bio_ops.o: ../../include/name_mask.h
+tls_bio_ops.o: ../../include/sock_addr.h
+tls_bio_ops.o: ../../include/sys_defs.h
+tls_bio_ops.o: ../../include/vbuf.h
+tls_bio_ops.o: ../../include/vstream.h
+tls_bio_ops.o: ../../include/vstring.h
+tls_bio_ops.o: tls.h
+tls_bio_ops.o: tls_bio_ops.c
+tls_certkey.o: ../../include/argv.h
+tls_certkey.o: ../../include/check_arg.h
+tls_certkey.o: ../../include/dns.h
+tls_certkey.o: ../../include/mail_params.h
+tls_certkey.o: ../../include/msg.h
+tls_certkey.o: ../../include/myaddrinfo.h
+tls_certkey.o: ../../include/name_code.h
+tls_certkey.o: ../../include/name_mask.h
+tls_certkey.o: ../../include/sock_addr.h
+tls_certkey.o: ../../include/sys_defs.h
+tls_certkey.o: ../../include/vbuf.h
+tls_certkey.o: ../../include/vstream.h
+tls_certkey.o: ../../include/vstring.h
+tls_certkey.o: tls.h
+tls_certkey.o: tls_certkey.c
+tls_client.o: ../../include/argv.h
+tls_client.o: ../../include/check_arg.h
+tls_client.o: ../../include/dict.h
+tls_client.o: ../../include/dns.h
+tls_client.o: ../../include/iostuff.h
+tls_client.o: ../../include/mail_params.h
+tls_client.o: ../../include/midna_domain.h
+tls_client.o: ../../include/msg.h
+tls_client.o: ../../include/myaddrinfo.h
+tls_client.o: ../../include/myflock.h
+tls_client.o: ../../include/mymalloc.h
+tls_client.o: ../../include/name_code.h
+tls_client.o: ../../include/name_mask.h
+tls_client.o: ../../include/sock_addr.h
+tls_client.o: ../../include/stringops.h
+tls_client.o: ../../include/sys_defs.h
+tls_client.o: ../../include/vbuf.h
+tls_client.o: ../../include/vstream.h
+tls_client.o: ../../include/vstring.h
+tls_client.o: tls.h
+tls_client.o: tls_client.c
+tls_client.o: tls_mgr.h
+tls_client.o: tls_scache.h
+tls_dane.o: ../../include/argv.h
+tls_dane.o: ../../include/check_arg.h
+tls_dane.o: ../../include/ctable.h
+tls_dane.o: ../../include/dns.h
+tls_dane.o: ../../include/events.h
+tls_dane.o: ../../include/hex_code.h
+tls_dane.o: ../../include/mail_params.h
+tls_dane.o: ../../include/midna_domain.h
+tls_dane.o: ../../include/msg.h
+tls_dane.o: ../../include/myaddrinfo.h
+tls_dane.o: ../../include/mymalloc.h
+tls_dane.o: ../../include/name_code.h
+tls_dane.o: ../../include/name_mask.h
+tls_dane.o: ../../include/safe_ultostr.h
+tls_dane.o: ../../include/sock_addr.h
+tls_dane.o: ../../include/split_at.h
+tls_dane.o: ../../include/stringops.h
+tls_dane.o: ../../include/sys_defs.h
+tls_dane.o: ../../include/timecmp.h
+tls_dane.o: ../../include/vbuf.h
+tls_dane.o: ../../include/vstream.h
+tls_dane.o: ../../include/vstring.h
+tls_dane.o: tls.h
+tls_dane.o: tls_dane.c
+tls_dh.o: ../../include/argv.h
+tls_dh.o: ../../include/check_arg.h
+tls_dh.o: ../../include/dns.h
+tls_dh.o: ../../include/mail_params.h
+tls_dh.o: ../../include/msg.h
+tls_dh.o: ../../include/myaddrinfo.h
+tls_dh.o: ../../include/mymalloc.h
+tls_dh.o: ../../include/name_code.h
+tls_dh.o: ../../include/name_mask.h
+tls_dh.o: ../../include/sock_addr.h
+tls_dh.o: ../../include/stringops.h
+tls_dh.o: ../../include/sys_defs.h
+tls_dh.o: ../../include/vbuf.h
+tls_dh.o: ../../include/vstream.h
+tls_dh.o: ../../include/vstring.h
+tls_dh.o: tls.h
+tls_dh.o: tls_dh.c
+tls_fprint.o: ../../include/argv.h
+tls_fprint.o: ../../include/check_arg.h
+tls_fprint.o: ../../include/dns.h
+tls_fprint.o: ../../include/mail_params.h
+tls_fprint.o: ../../include/msg.h
+tls_fprint.o: ../../include/myaddrinfo.h
+tls_fprint.o: ../../include/mymalloc.h
+tls_fprint.o: ../../include/name_code.h
+tls_fprint.o: ../../include/name_mask.h
+tls_fprint.o: ../../include/sock_addr.h
+tls_fprint.o: ../../include/stringops.h
+tls_fprint.o: ../../include/sys_defs.h
+tls_fprint.o: ../../include/vbuf.h
+tls_fprint.o: ../../include/vstream.h
+tls_fprint.o: ../../include/vstring.h
+tls_fprint.o: tls.h
+tls_fprint.o: tls_fprint.c
+tls_level.o: ../../include/argv.h
+tls_level.o: ../../include/check_arg.h
+tls_level.o: ../../include/dns.h
+tls_level.o: ../../include/myaddrinfo.h
+tls_level.o: ../../include/name_code.h
+tls_level.o: ../../include/name_mask.h
+tls_level.o: ../../include/sock_addr.h
+tls_level.o: ../../include/sys_defs.h
+tls_level.o: ../../include/vbuf.h
+tls_level.o: ../../include/vstream.h
+tls_level.o: ../../include/vstring.h
+tls_level.o: tls.h
+tls_level.o: tls_level.c
+tls_mgr.o: ../../include/argv.h
+tls_mgr.o: ../../include/attr.h
+tls_mgr.o: ../../include/attr_clnt.h
+tls_mgr.o: ../../include/check_arg.h
+tls_mgr.o: ../../include/dict.h
+tls_mgr.o: ../../include/htable.h
+tls_mgr.o: ../../include/iostuff.h
+tls_mgr.o: ../../include/mail_params.h
+tls_mgr.o: ../../include/mail_proto.h
+tls_mgr.o: ../../include/msg.h
+tls_mgr.o: ../../include/myflock.h
+tls_mgr.o: ../../include/mymalloc.h
+tls_mgr.o: ../../include/nvtable.h
+tls_mgr.o: ../../include/stringops.h
+tls_mgr.o: ../../include/sys_defs.h
+tls_mgr.o: ../../include/vbuf.h
+tls_mgr.o: ../../include/vstream.h
+tls_mgr.o: ../../include/vstring.h
+tls_mgr.o: tls_mgr.c
+tls_mgr.o: tls_mgr.h
+tls_mgr.o: tls_scache.h
+tls_misc.o: ../../include/argv.h
+tls_misc.o: ../../include/check_arg.h
+tls_misc.o: ../../include/dict.h
+tls_misc.o: ../../include/dns.h
+tls_misc.o: ../../include/mail_conf.h
+tls_misc.o: ../../include/mail_params.h
+tls_misc.o: ../../include/maps.h
+tls_misc.o: ../../include/msg.h
+tls_misc.o: ../../include/myaddrinfo.h
+tls_misc.o: ../../include/myflock.h
+tls_misc.o: ../../include/mymalloc.h
+tls_misc.o: ../../include/name_code.h
+tls_misc.o: ../../include/name_mask.h
+tls_misc.o: ../../include/sock_addr.h
+tls_misc.o: ../../include/stringops.h
+tls_misc.o: ../../include/sys_defs.h
+tls_misc.o: ../../include/valid_hostname.h
+tls_misc.o: ../../include/vbuf.h
+tls_misc.o: ../../include/vstream.h
+tls_misc.o: ../../include/vstring.h
+tls_misc.o: tls.h
+tls_misc.o: tls_misc.c
+tls_prng_dev.o: ../../include/connect.h
+tls_prng_dev.o: ../../include/iostuff.h
+tls_prng_dev.o: ../../include/msg.h
+tls_prng_dev.o: ../../include/mymalloc.h
+tls_prng_dev.o: ../../include/sys_defs.h
+tls_prng_dev.o: tls_prng.h
+tls_prng_dev.o: tls_prng_dev.c
+tls_prng_egd.o: ../../include/connect.h
+tls_prng_egd.o: ../../include/iostuff.h
+tls_prng_egd.o: ../../include/msg.h
+tls_prng_egd.o: ../../include/mymalloc.h
+tls_prng_egd.o: ../../include/sys_defs.h
+tls_prng_egd.o: tls_prng.h
+tls_prng_egd.o: tls_prng_egd.c
+tls_prng_exch.o: ../../include/iostuff.h
+tls_prng_exch.o: ../../include/msg.h
+tls_prng_exch.o: ../../include/myflock.h
+tls_prng_exch.o: ../../include/mymalloc.h
+tls_prng_exch.o: ../../include/sys_defs.h
+tls_prng_exch.o: tls_prng.h
+tls_prng_exch.o: tls_prng_exch.c
+tls_prng_file.o: ../../include/connect.h
+tls_prng_file.o: ../../include/iostuff.h
+tls_prng_file.o: ../../include/msg.h
+tls_prng_file.o: ../../include/mymalloc.h
+tls_prng_file.o: ../../include/sys_defs.h
+tls_prng_file.o: tls_prng.h
+tls_prng_file.o: tls_prng_file.c
+tls_proxy_client_misc.o: ../../include/argv.h
+tls_proxy_client_misc.o: ../../include/attr.h
+tls_proxy_client_misc.o: ../../include/check_arg.h
+tls_proxy_client_misc.o: ../../include/dns.h
+tls_proxy_client_misc.o: ../../include/htable.h
+tls_proxy_client_misc.o: ../../include/mail_params.h
+tls_proxy_client_misc.o: ../../include/msg.h
+tls_proxy_client_misc.o: ../../include/myaddrinfo.h
+tls_proxy_client_misc.o: ../../include/mymalloc.h
+tls_proxy_client_misc.o: ../../include/name_code.h
+tls_proxy_client_misc.o: ../../include/name_mask.h
+tls_proxy_client_misc.o: ../../include/nvtable.h
+tls_proxy_client_misc.o: ../../include/sock_addr.h
+tls_proxy_client_misc.o: ../../include/sys_defs.h
+tls_proxy_client_misc.o: ../../include/vbuf.h
+tls_proxy_client_misc.o: ../../include/vstream.h
+tls_proxy_client_misc.o: ../../include/vstring.h
+tls_proxy_client_misc.o: tls.h
+tls_proxy_client_misc.o: tls_proxy.h
+tls_proxy_client_misc.o: tls_proxy_client_misc.c
+tls_proxy_client_print.o: ../../include/argv.h
+tls_proxy_client_print.o: ../../include/argv_attr.h
+tls_proxy_client_print.o: ../../include/attr.h
+tls_proxy_client_print.o: ../../include/check_arg.h
+tls_proxy_client_print.o: ../../include/dns.h
+tls_proxy_client_print.o: ../../include/htable.h
+tls_proxy_client_print.o: ../../include/mail_params.h
+tls_proxy_client_print.o: ../../include/msg.h
+tls_proxy_client_print.o: ../../include/myaddrinfo.h
+tls_proxy_client_print.o: ../../include/mymalloc.h
+tls_proxy_client_print.o: ../../include/name_code.h
+tls_proxy_client_print.o: ../../include/name_mask.h
+tls_proxy_client_print.o: ../../include/nvtable.h
+tls_proxy_client_print.o: ../../include/sock_addr.h
+tls_proxy_client_print.o: ../../include/sys_defs.h
+tls_proxy_client_print.o: ../../include/vbuf.h
+tls_proxy_client_print.o: ../../include/vstream.h
+tls_proxy_client_print.o: ../../include/vstring.h
+tls_proxy_client_print.o: tls.h
+tls_proxy_client_print.o: tls_proxy.h
+tls_proxy_client_print.o: tls_proxy_client_print.c
+tls_proxy_client_scan.o: ../../include/argv.h
+tls_proxy_client_scan.o: ../../include/argv_attr.h
+tls_proxy_client_scan.o: ../../include/attr.h
+tls_proxy_client_scan.o: ../../include/check_arg.h
+tls_proxy_client_scan.o: ../../include/dns.h
+tls_proxy_client_scan.o: ../../include/htable.h
+tls_proxy_client_scan.o: ../../include/mail_params.h
+tls_proxy_client_scan.o: ../../include/msg.h
+tls_proxy_client_scan.o: ../../include/myaddrinfo.h
+tls_proxy_client_scan.o: ../../include/mymalloc.h
+tls_proxy_client_scan.o: ../../include/name_code.h
+tls_proxy_client_scan.o: ../../include/name_mask.h
+tls_proxy_client_scan.o: ../../include/nvtable.h
+tls_proxy_client_scan.o: ../../include/sock_addr.h
+tls_proxy_client_scan.o: ../../include/sys_defs.h
+tls_proxy_client_scan.o: ../../include/vbuf.h
+tls_proxy_client_scan.o: ../../include/vstream.h
+tls_proxy_client_scan.o: ../../include/vstring.h
+tls_proxy_client_scan.o: tls.h
+tls_proxy_client_scan.o: tls_proxy.h
+tls_proxy_client_scan.o: tls_proxy_client_scan.c
+tls_proxy_clnt.o: ../../include/argv.h
+tls_proxy_clnt.o: ../../include/attr.h
+tls_proxy_clnt.o: ../../include/check_arg.h
+tls_proxy_clnt.o: ../../include/connect.h
+tls_proxy_clnt.o: ../../include/dns.h
+tls_proxy_clnt.o: ../../include/htable.h
+tls_proxy_clnt.o: ../../include/iostuff.h
+tls_proxy_clnt.o: ../../include/mail_params.h
+tls_proxy_clnt.o: ../../include/mail_proto.h
+tls_proxy_clnt.o: ../../include/msg.h
+tls_proxy_clnt.o: ../../include/myaddrinfo.h
+tls_proxy_clnt.o: ../../include/mymalloc.h
+tls_proxy_clnt.o: ../../include/name_code.h
+tls_proxy_clnt.o: ../../include/name_mask.h
+tls_proxy_clnt.o: ../../include/nvtable.h
+tls_proxy_clnt.o: ../../include/sock_addr.h
+tls_proxy_clnt.o: ../../include/stringops.h
+tls_proxy_clnt.o: ../../include/sys_defs.h
+tls_proxy_clnt.o: ../../include/vbuf.h
+tls_proxy_clnt.o: ../../include/vstream.h
+tls_proxy_clnt.o: ../../include/vstring.h
+tls_proxy_clnt.o: tls.h
+tls_proxy_clnt.o: tls_proxy.h
+tls_proxy_clnt.o: tls_proxy_clnt.c
+tls_proxy_context_print.o: ../../include/argv.h
+tls_proxy_context_print.o: ../../include/attr.h
+tls_proxy_context_print.o: ../../include/check_arg.h
+tls_proxy_context_print.o: ../../include/dns.h
+tls_proxy_context_print.o: ../../include/htable.h
+tls_proxy_context_print.o: ../../include/myaddrinfo.h
+tls_proxy_context_print.o: ../../include/mymalloc.h
+tls_proxy_context_print.o: ../../include/name_code.h
+tls_proxy_context_print.o: ../../include/name_mask.h
+tls_proxy_context_print.o: ../../include/nvtable.h
+tls_proxy_context_print.o: ../../include/sock_addr.h
+tls_proxy_context_print.o: ../../include/sys_defs.h
+tls_proxy_context_print.o: ../../include/vbuf.h
+tls_proxy_context_print.o: ../../include/vstream.h
+tls_proxy_context_print.o: ../../include/vstring.h
+tls_proxy_context_print.o: tls.h
+tls_proxy_context_print.o: tls_proxy.h
+tls_proxy_context_print.o: tls_proxy_context_print.c
+tls_proxy_context_scan.o: ../../include/argv.h
+tls_proxy_context_scan.o: ../../include/attr.h
+tls_proxy_context_scan.o: ../../include/check_arg.h
+tls_proxy_context_scan.o: ../../include/dns.h
+tls_proxy_context_scan.o: ../../include/htable.h
+tls_proxy_context_scan.o: ../../include/msg.h
+tls_proxy_context_scan.o: ../../include/myaddrinfo.h
+tls_proxy_context_scan.o: ../../include/mymalloc.h
+tls_proxy_context_scan.o: ../../include/name_code.h
+tls_proxy_context_scan.o: ../../include/name_mask.h
+tls_proxy_context_scan.o: ../../include/nvtable.h
+tls_proxy_context_scan.o: ../../include/sock_addr.h
+tls_proxy_context_scan.o: ../../include/sys_defs.h
+tls_proxy_context_scan.o: ../../include/vbuf.h
+tls_proxy_context_scan.o: ../../include/vstream.h
+tls_proxy_context_scan.o: ../../include/vstring.h
+tls_proxy_context_scan.o: tls.h
+tls_proxy_context_scan.o: tls_proxy.h
+tls_proxy_context_scan.o: tls_proxy_context_scan.c
+tls_proxy_server_print.o: ../../include/argv.h
+tls_proxy_server_print.o: ../../include/attr.h
+tls_proxy_server_print.o: ../../include/check_arg.h
+tls_proxy_server_print.o: ../../include/dns.h
+tls_proxy_server_print.o: ../../include/htable.h
+tls_proxy_server_print.o: ../../include/myaddrinfo.h
+tls_proxy_server_print.o: ../../include/mymalloc.h
+tls_proxy_server_print.o: ../../include/name_code.h
+tls_proxy_server_print.o: ../../include/name_mask.h
+tls_proxy_server_print.o: ../../include/nvtable.h
+tls_proxy_server_print.o: ../../include/sock_addr.h
+tls_proxy_server_print.o: ../../include/sys_defs.h
+tls_proxy_server_print.o: ../../include/vbuf.h
+tls_proxy_server_print.o: ../../include/vstream.h
+tls_proxy_server_print.o: ../../include/vstring.h
+tls_proxy_server_print.o: tls.h
+tls_proxy_server_print.o: tls_proxy.h
+tls_proxy_server_print.o: tls_proxy_server_print.c
+tls_proxy_server_scan.o: ../../include/argv.h
+tls_proxy_server_scan.o: ../../include/attr.h
+tls_proxy_server_scan.o: ../../include/check_arg.h
+tls_proxy_server_scan.o: ../../include/dns.h
+tls_proxy_server_scan.o: ../../include/htable.h
+tls_proxy_server_scan.o: ../../include/myaddrinfo.h
+tls_proxy_server_scan.o: ../../include/mymalloc.h
+tls_proxy_server_scan.o: ../../include/name_code.h
+tls_proxy_server_scan.o: ../../include/name_mask.h
+tls_proxy_server_scan.o: ../../include/nvtable.h
+tls_proxy_server_scan.o: ../../include/sock_addr.h
+tls_proxy_server_scan.o: ../../include/sys_defs.h
+tls_proxy_server_scan.o: ../../include/vbuf.h
+tls_proxy_server_scan.o: ../../include/vstream.h
+tls_proxy_server_scan.o: ../../include/vstring.h
+tls_proxy_server_scan.o: tls.h
+tls_proxy_server_scan.o: tls_proxy.h
+tls_proxy_server_scan.o: tls_proxy_server_scan.c
+tls_rsa.o: tls_rsa.c
+tls_scache.o: ../../include/argv.h
+tls_scache.o: ../../include/check_arg.h
+tls_scache.o: ../../include/dict.h
+tls_scache.o: ../../include/hex_code.h
+tls_scache.o: ../../include/msg.h
+tls_scache.o: ../../include/myflock.h
+tls_scache.o: ../../include/mymalloc.h
+tls_scache.o: ../../include/stringops.h
+tls_scache.o: ../../include/sys_defs.h
+tls_scache.o: ../../include/timecmp.h
+tls_scache.o: ../../include/vbuf.h
+tls_scache.o: ../../include/vstream.h
+tls_scache.o: ../../include/vstring.h
+tls_scache.o: tls_scache.c
+tls_scache.o: tls_scache.h
+tls_seed.o: ../../include/argv.h
+tls_seed.o: ../../include/check_arg.h
+tls_seed.o: ../../include/dict.h
+tls_seed.o: ../../include/dns.h
+tls_seed.o: ../../include/msg.h
+tls_seed.o: ../../include/myaddrinfo.h
+tls_seed.o: ../../include/myflock.h
+tls_seed.o: ../../include/name_code.h
+tls_seed.o: ../../include/name_mask.h
+tls_seed.o: ../../include/sock_addr.h
+tls_seed.o: ../../include/sys_defs.h
+tls_seed.o: ../../include/vbuf.h
+tls_seed.o: ../../include/vstream.h
+tls_seed.o: ../../include/vstring.h
+tls_seed.o: tls.h
+tls_seed.o: tls_mgr.h
+tls_seed.o: tls_scache.h
+tls_seed.o: tls_seed.c
+tls_server.o: ../../include/argv.h
+tls_server.o: ../../include/check_arg.h
+tls_server.o: ../../include/dict.h
+tls_server.o: ../../include/dns.h
+tls_server.o: ../../include/hex_code.h
+tls_server.o: ../../include/iostuff.h
+tls_server.o: ../../include/mail_params.h
+tls_server.o: ../../include/msg.h
+tls_server.o: ../../include/myaddrinfo.h
+tls_server.o: ../../include/myflock.h
+tls_server.o: ../../include/mymalloc.h
+tls_server.o: ../../include/name_code.h
+tls_server.o: ../../include/name_mask.h
+tls_server.o: ../../include/sock_addr.h
+tls_server.o: ../../include/stringops.h
+tls_server.o: ../../include/sys_defs.h
+tls_server.o: ../../include/vbuf.h
+tls_server.o: ../../include/vstream.h
+tls_server.o: ../../include/vstring.h
+tls_server.o: tls.h
+tls_server.o: tls_mgr.h
+tls_server.o: tls_scache.h
+tls_server.o: tls_server.c
+tls_session.o: ../../include/argv.h
+tls_session.o: ../../include/check_arg.h
+tls_session.o: ../../include/dns.h
+tls_session.o: ../../include/mail_params.h
+tls_session.o: ../../include/msg.h
+tls_session.o: ../../include/myaddrinfo.h
+tls_session.o: ../../include/mymalloc.h
+tls_session.o: ../../include/name_code.h
+tls_session.o: ../../include/name_mask.h
+tls_session.o: ../../include/sock_addr.h
+tls_session.o: ../../include/sys_defs.h
+tls_session.o: ../../include/vbuf.h
+tls_session.o: ../../include/vstream.h
+tls_session.o: ../../include/vstring.h
+tls_session.o: tls.h
+tls_session.o: tls_session.c
+tls_stream.o: ../../include/argv.h
+tls_stream.o: ../../include/check_arg.h
+tls_stream.o: ../../include/dns.h
+tls_stream.o: ../../include/iostuff.h
+tls_stream.o: ../../include/msg.h
+tls_stream.o: ../../include/myaddrinfo.h
+tls_stream.o: ../../include/name_code.h
+tls_stream.o: ../../include/name_mask.h
+tls_stream.o: ../../include/sock_addr.h
+tls_stream.o: ../../include/sys_defs.h
+tls_stream.o: ../../include/vbuf.h
+tls_stream.o: ../../include/vstream.h
+tls_stream.o: ../../include/vstring.h
+tls_stream.o: tls.h
+tls_stream.o: tls_stream.c
+tls_verify.o: ../../include/argv.h
+tls_verify.o: ../../include/check_arg.h
+tls_verify.o: ../../include/dns.h
+tls_verify.o: ../../include/msg.h
+tls_verify.o: ../../include/myaddrinfo.h
+tls_verify.o: ../../include/mymalloc.h
+tls_verify.o: ../../include/name_code.h
+tls_verify.o: ../../include/name_mask.h
+tls_verify.o: ../../include/sock_addr.h
+tls_verify.o: ../../include/stringops.h
+tls_verify.o: ../../include/sys_defs.h
+tls_verify.o: ../../include/vbuf.h
+tls_verify.o: ../../include/vstream.h
+tls_verify.o: ../../include/vstring.h
+tls_verify.o: tls.h
+tls_verify.o: tls_verify.c
diff --git a/src/tls/bad-back-to-back-keys.pem b/src/tls/bad-back-to-back-keys.pem
new file mode 100644
index 0000000..b1895ec
--- /dev/null
+++ b/src/tls/bad-back-to-back-keys.pem
@@ -0,0 +1,64 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/bad-back-to-back-keys.pem.ref b/src/tls/bad-back-to-back-keys.pem.ref
new file mode 100644
index 0000000..192e9ea
--- /dev/null
+++ b/src/tls/bad-back-to-back-keys.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: error loading certificate chain: key at index 1 in bad-back-to-back-keys.pem not followed by a certificate
+unknown: warning: error loading private keys and certificates from: bad-back-to-back-keys.pem: disabling TLS support
diff --git a/src/tls/bad-ec-cert-before-key.pem b/src/tls/bad-ec-cert-before-key.pem
new file mode 100644
index 0000000..520927a
--- /dev/null
+++ b/src/tls/bad-ec-cert-before-key.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/bad-ec-cert-before-key.pem.ref b/src/tls/bad-ec-cert-before-key.pem.ref
new file mode 100644
index 0000000..71e5f78
--- /dev/null
+++ b/src/tls/bad-ec-cert-before-key.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: error loading chain from bad-ec-cert-before-key.pem: key not first
+unknown: warning: error loading private keys and certificates from: bad-ec-cert-before-key.pem: disabling TLS support
diff --git a/src/tls/bad-key-cert-mismatch.pem b/src/tls/bad-key-cert-mismatch.pem
new file mode 100644
index 0000000..1e78833
--- /dev/null
+++ b/src/tls/bad-key-cert-mismatch.pem
@@ -0,0 +1,36 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC
+rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy
+b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/bad-key-cert-mismatch.pem.ref b/src/tls/bad-key-cert-mismatch.pem.ref
new file mode 100644
index 0000000..a32b657
--- /dev/null
+++ b/src/tls/bad-key-cert-mismatch.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: key at index 1 in bad-key-cert-mismatch.pem does not match next certificate
+unknown: warning: error loading private keys and certificates from: bad-key-cert-mismatch.pem: disabling TLS support
diff --git a/src/tls/bad-rsa-key-last.pem b/src/tls/bad-rsa-key-last.pem
new file mode 100644
index 0000000..de15808
--- /dev/null
+++ b/src/tls/bad-rsa-key-last.pem
@@ -0,0 +1,64 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
diff --git a/src/tls/bad-rsa-key-last.pem.ref b/src/tls/bad-rsa-key-last.pem.ref
new file mode 100644
index 0000000..a72c0d9
--- /dev/null
+++ b/src/tls/bad-rsa-key-last.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: No certs for key at index 5 in bad-rsa-key-last.pem
+unknown: warning: error loading private keys and certificates from: bad-rsa-key-last.pem: disabling TLS support
diff --git a/src/tls/ecca-cert.pem b/src/tls/ecca-cert.pem
new file mode 100644
index 0000000..54418f1
--- /dev/null
+++ b/src/tls/ecca-cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
diff --git a/src/tls/ecca-pkey.pem b/src/tls/ecca-pkey.pem
new file mode 100644
index 0000000..2d95f45
--- /dev/null
+++ b/src/tls/ecca-pkey.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC
+rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy
+b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV
+-----END PRIVATE KEY-----
diff --git a/src/tls/ecee-cert.pem b/src/tls/ecee-cert.pem
new file mode 100644
index 0000000..1afb2f5
--- /dev/null
+++ b/src/tls/ecee-cert.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
diff --git a/src/tls/ecee-pkey.pem b/src/tls/ecee-pkey.pem
new file mode 100644
index 0000000..ffec716
--- /dev/null
+++ b/src/tls/ecee-pkey.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
diff --git a/src/tls/ecroot-cert.pem b/src/tls/ecroot-cert.pem
new file mode 100644
index 0000000..4f5041c
--- /dev/null
+++ b/src/tls/ecroot-cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/ecroot-pkey.pem b/src/tls/ecroot-pkey.pem
new file mode 100644
index 0000000..0d36dc4
--- /dev/null
+++ b/src/tls/ecroot-pkey.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg85iR6mrXjfV9O38h
+9/KivPeTKxdjXHQ6j2VCWD8K8KahRANCAAR8MXvjqDNXBKh79SxW0WpJfUBIptgx
+1xwT2k4HkP98rlo+pmzL39gpIT1P78VwKBlrq4RE7JHnzdlQzJNAWgdG
+-----END PRIVATE KEY-----
diff --git a/src/tls/good-mixed-keyfirst.pem b/src/tls/good-mixed-keyfirst.pem
new file mode 100644
index 0000000..c7ba743
--- /dev/null
+++ b/src/tls/good-mixed-keyfirst.pem
@@ -0,0 +1,45 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
+
diff --git a/src/tls/good-mixed-keyfirst.pem.ref b/src/tls/good-mixed-keyfirst.pem.ref
new file mode 100644
index 0000000..95502d8
--- /dev/null
+++ b/src/tls/good-mixed-keyfirst.pem.ref
@@ -0,0 +1,12 @@
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/good-mixed-keylast.pem b/src/tls/good-mixed-keylast.pem
new file mode 100644
index 0000000..6ce758e
--- /dev/null
+++ b/src/tls/good-mixed-keylast.pem
@@ -0,0 +1,45 @@
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
diff --git a/src/tls/good-mixed-keylast.pem.ref b/src/tls/good-mixed-keylast.pem.ref
new file mode 100644
index 0000000..95502d8
--- /dev/null
+++ b/src/tls/good-mixed-keylast.pem.ref
@@ -0,0 +1,12 @@
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/good-mixed-keymiddle.pem b/src/tls/good-mixed-keymiddle.pem
new file mode 100644
index 0000000..b3dda42
--- /dev/null
+++ b/src/tls/good-mixed-keymiddle.pem
@@ -0,0 +1,45 @@
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/good-mixed-keymiddle.pem.ref b/src/tls/good-mixed-keymiddle.pem.ref
new file mode 100644
index 0000000..95502d8
--- /dev/null
+++ b/src/tls/good-mixed-keymiddle.pem.ref
@@ -0,0 +1,12 @@
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/goodchains.pem b/src/tls/goodchains.pem
new file mode 100644
index 0000000..81b2b32
--- /dev/null
+++ b/src/tls/goodchains.pem
@@ -0,0 +1,121 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1SU0Eg
+aXNzdWVyIENBMCAXDTE5MDEwODA2NDQzMloYDzIxMTkwMTA5MDY0NDMyWjAaMRgw
+FgYDVQQDDA9teDEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCu99Z6AB3EA2tAlWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxl
+BNHTk4c8HkobGqMTRU0x0qpzMnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX
++5ZnqS9gWbLZyjkCCqVQR/nyK0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakH
+ZIB8TYI6p6hanNQyz4NWq1PEpWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJki
+Hka24JzNCLx3GD/Q0GNGFGoE9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIo
+W0/dbG0KaKTTeHf9LegFXez2tJeLl+vpAgMBAAGjfjB8MB0GA1UdDgQWBBTCrBLC
+itbyxruhJFbJEaGYdG4NRjAfBgNVHSMEGDAWgBTCbuRdkXOuqXbXqJJnH5iQPhkT
+JTAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214
+MS5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAYOgZ5NaFJhmjT6V+GI5m
+cuTsMC7WB5nQ8nbf2wxw052jgBvnZWAgnshtgWvrK0T6402VcTOzHt8VKS0KwLwk
+FESvj2rdoDGvGqUcw6DhCns98PRuts2nMTDlKYJTYWab2Q08PPYxTtRt1Y+A2sp7
+et+bxMjYr2mdO0wkqK7guUA9H2bZkGYJck5bbna67hpzx17YomBjDUjTtMiagXKs
+E5QzOXXqCQxiSunlxsMqDwt/HRxGvQy+BHuNq4epuZfeMOndIjl2ZyKdN2ABy88G
+dYqRO6nBHUO2/ZxlAZf11xpueVZZ61SueimPfzbsw0B7ZvPSi9KdbPIGBbD4cCwt
+JA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjQyMjRaGA8yMTE5MDEwOTA2NDIyNFowGDEWMBQG
+A1UEAwwNUlNBIGlzc3VlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3tLZ4hc8RmIX0Yd0uj1Z31mK94KMcVN//L0iiR/ZOFr39ma2Ys7AL0UQeV
+H9s4hUaMM91n0fkwVL0r8NUsCkuPS/MvJClWxTbN3rEzzaajD+BdyW1Y1QfdEI5t
+AI2wSSieF3GtiTmxH43lDHTeZz0qhwh8NNzh+j09DwYS+TfQnO70LdmR5Vks0SoF
+DmxTs2/DRup/iCWGd853C3T/lfixdhkdVwGNuxtuF6/xSEQirT2OUjjnVIcCPGq0
+Zbz3hFJ0xHoqiwNBv9PytfLk4BNf+VWTANFXnzNvZqWrOO8yQWS+RHIdf2+a96G2
+EFIgWz/rwo4WP9tozETlPzQc6P8CAwEAAaNTMFEwHQYDVR0OBBYEFMJu5F2Rc66p
+dteokmcfmJA+GRMlMB8GA1UdIwQYMBaAFJGpciw0EYdhbO3y0sM62cWnHTtiMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD6ngGSUb9grVkhlb8M5
+geC4SEhBiysoXoxHleFW6FFe4k1v5dg/xbCbNwysToEG+2S498f2FfyNzKWVDSM7
+5al+4jYrKlwTbX6W9krH6iGhE0Sdz/YHWYdPCDYssdOvS46/+MGj16WPiOdj5UYY
+U0+TVKlqLOcpqkPKrz/9X7sM8bXi8FQHPme8ksvnB9qMdwcru/BN62tUOw1JHnmh
+CCUbI3TU5xE8fUCl4F43vsLq73EfaHs4TO9hhk2hBW7K0QOinNcFlcNy8AF7mF/T
+kY4FWI/kMfw6cXkg+F44Oy3j3KUF3hgByhWzTQimY2oRpk7U5tGuC8Xgh6eHPbRG
+Kck=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjE5NDJaGA8yMTE5MDEwOTA2MTk0MlowFjEUMBIG
+A1UEAwwLUlNBIHJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCoZoGzTiMbZcztQVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65An
+K2vHNrE37DDRl2YTmP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6
+yuO07nut+rNFMSSjgnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0S
+oxQVSkyOIr/phPAkLWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2
+X8lMaz8ooT9OI/4ln/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu
+1ViLHIsm+55CJj5OI676Q4JJAgMBAAGjUzBRMB0GA1UdDgQWBBSRqXIsNBGHYWzt
+8tLDOtnFpx07YjAfBgNVHSMEGDAWgBSRqXIsNBGHYWzt8tLDOtnFpx07YjAPBgNV
+HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCHEQ8DRqrWz8xibaNEv3Id
+SEJvgfRRsMA7jk2HAnE84bO1ZLkXF9HX7oqXbIMRw5IZRUrBXO0fUwcxz3DwTYZj
+ojRPP1yNfylmc8uxyd2goMoG4OgTdt2/Ewu8bli0S9NapH9fdBYbSDUBw3QiH+Wy
+ze9BiDVLwM7eERHGj1XKlC3iUaTZTOFWrr4y5eRjCD8oMzCB/73CWMRwBGPyF+Wg
+uP8FfF7i05B9rrKELAzCr4/hqYPDYNX8mv8QzcwXbn93PY35VN5y/wBuccggv0af
+iK5NfoNxvmTp9SVD4wA2prlkq1lbjMuqoAxrKpCGG0I0x8Dzn3bTaTNETal61Wt/
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/goodchains.pem.ref b/src/tls/goodchains.pem.ref
new file mode 100644
index 0000000..ade7562
--- /dev/null
+++ b/src/tls/goodchains.pem.ref
@@ -0,0 +1,24 @@
+depth = 0
+issuer = /CN=RSA issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=RSA root CA
+subject = /CN=RSA issuer CA
+
+depth = 2
+issuer = /CN=RSA root CA
+subject = /CN=RSA root CA
+
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/mkcert.sh b/src/tls/mkcert.sh
new file mode 100644
index 0000000..c15f4e1
--- /dev/null
+++ b/src/tls/mkcert.sh
@@ -0,0 +1,264 @@
+#! /bin/bash
+#
+# Copyright (c) 2016 Viktor Dukhovni <openssl-users@dukhovni.org>.
+# All rights reserved.
+#
+# Licensed under the terms of the Postfix SECURE MAILER license
+# included with the Postfix source code.
+#
+# This file is dual-licensed and is also available under other terms.
+# Please contact the author.
+
+# 100 years should be enough for now
+if [ -z "$DAYS" ]; then
+ DAYS=36525
+fi
+
+if [ -z "$OPENSSL_SIGALG" ]; then
+ OPENSSL_SIGALG=sha256
+fi
+
+case ${OPENSSL_SIGALG} in
+ null) dopts=();;
+ *) dopts=(-"${OPENSSL_SIGALG}");;
+esac
+
+if [ -z "$REQMASK" ]; then
+ REQMASK=utf8only
+fi
+
+stderr_onerror() {
+ (
+ err=$("$@" >&3 2>&1) || {
+ printf "%s\n" "$err" >&2
+ exit 1
+ }
+ ) 3>&1
+}
+
+key() {
+ local key=$1; shift
+
+ local alg=rsa
+ if [ -n "$OPENSSL_KEYALG" ]; then
+ alg=$OPENSSL_KEYALG
+ fi
+
+ local bits=2048
+ if [ -n "$OPENSSL_KEYBITS" ]; then
+ bits=$OPENSSL_KEYBITS
+ fi
+
+ if [ ! -f "${key}.pem" ]; then
+ args=(-algorithm "$alg")
+ case $alg in
+ rsa) args=("${args[@]}" -pkeyopt rsa_keygen_bits:$bits );;
+ ec) args=("${args[@]}" -pkeyopt "ec_paramgen_curve:$bits")
+ args=("${args[@]}" -pkeyopt ec_param_enc:named_curve);;
+ dsa) args=(-paramfile "$bits");;
+ ed25519) ;;
+ ed448) ;;
+ *) printf "Unsupported key algorithm: %s\n" "$alg" >&2; return 1;;
+ esac
+ stderr_onerror \
+ openssl genpkey "${args[@]}" -out "${key}.pem"
+ fi
+}
+
+# Usage: $0 req keyname dn1 dn2 ...
+req() {
+ local key=$1; shift
+
+ key "$key"
+ local errs
+
+ stderr_onerror \
+ openssl req "${dopts[@]}" -new -key "${key}.pem" \
+ -config <(printf "string_mask=%s\n[req]\n%s\n%s\n[dn]\n" \
+ "$REQMASK" "prompt = no" "distinguished_name = dn"
+ for dn in "$@"; do echo "$dn"; done)
+}
+
+req_nocn() {
+ local key=$1; shift
+
+ key "$key"
+ stderr_onerror \
+ openssl req "${dopts[@]}" -new -subj / -key "${key}.pem" \
+ -config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \
+ "distinguished_name = dn")
+}
+
+cert() {
+ local cert=$1; shift
+ local exts=$1; shift
+
+ stderr_onerror \
+ openssl x509 "${dopts[@]}" -req -out "${cert}.pem" \
+ -extfile <(printf "%s\n" "$exts") "$@"
+}
+
+genroot() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local skid="subjectKeyIdentifier = hash"
+ local akid="authorityKeyIdentifier = keyid"
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = critical,CA:true")
+ for eku in "$@"
+ do
+ exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
+ done
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days "${DAYS}"
+}
+
+genca() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local cacert=$1; shift
+ local skid="subjectKeyIdentifier = hash"
+ local akid="authorityKeyIdentifier = keyid"
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = critical,CA:true")
+ for eku in "$@"
+ do
+ exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
+ done
+ if [ -n "$NC" ]; then
+ exts=$(printf "%s\nnameConstraints = %s\n" "$exts" "$NC")
+ fi
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+gen_nonbc_ca() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local cacert=$1; shift
+ local skid="subjectKeyIdentifier = hash"
+ local akid="authorityKeyIdentifier = keyid"
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid")
+ exts=$(printf "%s\nkeyUsage = %s\n" "$exts" "keyCertSign, cRLSign")
+ for eku in "$@"
+ do
+ exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
+ done
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+# Usage: $0 genpc keyname certname eekeyname eecertname pcext1 pcext2 ...
+#
+# Note: takes csr on stdin, so must be used with $0 req like this:
+#
+# $0 req keyname dn | $0 genpc keyname certname eekeyname eecertname pcext ...
+genpc() {
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local ca=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer:always" \
+ "basicConstraints = CA:false" \
+ "proxyCertInfo = critical, @pcexts";
+ echo "[pcexts]";
+ for x in "$@"; do echo $x; done)
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+# Usage: $0 geneealt keyname certname eekeyname eecertname alt1 alt2 ...
+#
+# Note: takes csr on stdin, so must be used with $0 req like this:
+#
+# $0 req keyname dn | $0 geneealt keyname certname eekeyname eecertname alt ...
+geneealt() {
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local ca=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid" \
+ "basicConstraints = CA:false" \
+ "subjectAltName = @alts";
+ echo "[alts]";
+ for x in "$@"; do echo $x; done)
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+genee() {
+ local OPTIND=1
+ local purpose=serverAuth
+
+ while getopts p: o
+ do
+ case $o in
+ p) purpose="$OPTARG";;
+ *) echo "Usage: $0 genee [-p EKU] cn keyname certname cakeyname cacertname" >&2
+ return 1;;
+ esac
+ done
+
+ shift $((OPTIND - 1))
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local ca=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:false" \
+ "extendedKeyUsage = $purpose" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}" "$@"
+}
+
+genss() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:false" \
+ "extendedKeyUsage = serverAuth" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -signkey "${key}.pem" \
+ -set_serial 1 -days "${DAYS}" "$@"
+}
+
+gennocn() {
+ local key=$1; shift
+ local cert=$1; shift
+
+ csr=$(req_nocn "$key") || return 1
+ echo "$csr" |
+ cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days -1 "$@"
+}
+
+"$@"
diff --git a/src/tls/rsaca-cert.pem b/src/tls/rsaca-cert.pem
new file mode 100644
index 0000000..1be736a
--- /dev/null
+++ b/src/tls/rsaca-cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjQyMjRaGA8yMTE5MDEwOTA2NDIyNFowGDEWMBQG
+A1UEAwwNUlNBIGlzc3VlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3tLZ4hc8RmIX0Yd0uj1Z31mK94KMcVN//L0iiR/ZOFr39ma2Ys7AL0UQeV
+H9s4hUaMM91n0fkwVL0r8NUsCkuPS/MvJClWxTbN3rEzzaajD+BdyW1Y1QfdEI5t
+AI2wSSieF3GtiTmxH43lDHTeZz0qhwh8NNzh+j09DwYS+TfQnO70LdmR5Vks0SoF
+DmxTs2/DRup/iCWGd853C3T/lfixdhkdVwGNuxtuF6/xSEQirT2OUjjnVIcCPGq0
+Zbz3hFJ0xHoqiwNBv9PytfLk4BNf+VWTANFXnzNvZqWrOO8yQWS+RHIdf2+a96G2
+EFIgWz/rwo4WP9tozETlPzQc6P8CAwEAAaNTMFEwHQYDVR0OBBYEFMJu5F2Rc66p
+dteokmcfmJA+GRMlMB8GA1UdIwQYMBaAFJGpciw0EYdhbO3y0sM62cWnHTtiMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD6ngGSUb9grVkhlb8M5
+geC4SEhBiysoXoxHleFW6FFe4k1v5dg/xbCbNwysToEG+2S498f2FfyNzKWVDSM7
+5al+4jYrKlwTbX6W9krH6iGhE0Sdz/YHWYdPCDYssdOvS46/+MGj16WPiOdj5UYY
+U0+TVKlqLOcpqkPKrz/9X7sM8bXi8FQHPme8ksvnB9qMdwcru/BN62tUOw1JHnmh
+CCUbI3TU5xE8fUCl4F43vsLq73EfaHs4TO9hhk2hBW7K0QOinNcFlcNy8AF7mF/T
+kY4FWI/kMfw6cXkg+F44Oy3j3KUF3hgByhWzTQimY2oRpk7U5tGuC8Xgh6eHPbRG
+Kck=
+-----END CERTIFICATE-----
diff --git a/src/tls/rsaca-pkey.pem b/src/tls/rsaca-pkey.pem
new file mode 100644
index 0000000..2381ae6
--- /dev/null
+++ b/src/tls/rsaca-pkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC97S2eIXPEZiF9
+GHdLo9Wd9ZiveCjHFTf/y9Iokf2Tha9/ZmtmLOwC9FEHlR/bOIVGjDPdZ9H5MFS9
+K/DVLApLj0vzLyQpVsU2zd6xM82mow/gXcltWNUH3RCObQCNsEkonhdxrYk5sR+N
+5Qx03mc9KocIfDTc4fo9PQ8GEvk30Jzu9C3ZkeVZLNEqBQ5sU7Nvw0bqf4glhnfO
+dwt0/5X4sXYZHVcBjbsbbhev8UhEIq09jlI451SHAjxqtGW894RSdMR6KosDQb/T
+8rXy5OATX/lVkwDRV58zb2alqzjvMkFkvkRyHX9vmvehthBSIFs/68KOFj/baMxE
+5T80HOj/AgMBAAECggEBAIY9Ch4XRMzO5uKVFVRoEwcXXHjBNAkqTS9F719veEv5
+lEY2rLhGDfY0msUCOMboVwK6+7mEtNsstugSE6GIBCrNuH/ElQmG49NNhRW4KKWb
+4Q/TGhhoTgHF1PrlvhtnOv4zZxyY7EHTmBrhhoFf5JZN5a0wpOht7EG2U1UWugEg
+/fOXp9XUsmJRxwp1jukqIi71GDVtoq3XCK7clluvNdeSlBn7D8dZ8cYUnMkqqaZA
+YYamyP3qF6UdxblW3xzGC3/0sLQvmHZOlMBuSpWUgr6NDsQPG4qXq10XePeteViw
+WnUqu11ILp8Ivoc20wJkokj1dCN3MOO0MyGcCxEX+5ECgYEA/P+Y+0AXq1j9yjNM
+aaXT3EZwSSAP/5MtZRiomFVwCgYbH6/hUV+OVih20Yhsl3MA5vgQTpSThZmmcBPC
+c3bn53OXVi6jnSzNzuQW28R8pSGoNnGT6TzG+J+my7ZxA0NTCAa72qhDgbI0dqd/
+sjk1E6hRtOkEI8fD5/IipkvxCpcCgYEAwC4FA0AA6Oy2YOAi1KeACZpIKIDcOFJz
+TLfuxhwnNi5C/1i99d5W0doCzaneECyadhGKPbv0DlVS2fe9Xm06PQLD2Y/+vkag
+iQ7V31JfwvoTQNTPVqnGcRu2ZmzI8Nja4/BcEEoxpPbMVbQ1ualkqcwpYu66ZlbD
+yWpCLSGFadkCgYEAvAkoZYzkSqjwr4jjAR6L0QrVR7Q5z8VOlvX10Iqno/uXyzxI
+Zdd0jdqzPNZ9hy6lfATg8daBsmlZh7FX88NrZt3Fm/s8BYSYTm2+A4cM8RqL0DMo
+MNDIPV9Dc+LcKgWuv6dplYE78zhEv++L/CWCqmKOn7wUJJfDpi+Tyy9kLm8CgYAy
++gIKYqfbIS8fc1TJ48Rqx6nsVIIVzokXCJMlqcIc9RiAcyGwXlHZSGMF+tEUqUAv
+oWdyCLEsPCXF+5kXuxF/rYQV6cRA5Ksgr/a7TjZomb0RrWFyM4aX6inv8Vs7x8oI
+PHGvQH76qxx4f1zg6rXw9F7mBz0aeFlmy/DR19pzwQKBgQCJWwlv7zisuTDXAuG/
+KF4VV6D2Y00pEHsxVx/RGeUXm+3HmqrZbcnccgivxyNMIAfuE5+7s2wfnjG38nnw
+DbZtp+rEcKX1h+95QnOlFxiowdlfX/ueOC2J59+Dc+R7UoDxqkQrSbdE5advHIl9
+Xb64SvwMhLzJYSYacv+fogoAPw==
+-----END PRIVATE KEY-----
diff --git a/src/tls/rsaee-cert.pem b/src/tls/rsaee-cert.pem
new file mode 100644
index 0000000..9f923b4
--- /dev/null
+++ b/src/tls/rsaee-cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1SU0Eg
+aXNzdWVyIENBMCAXDTE5MDEwODA2NDQzMloYDzIxMTkwMTA5MDY0NDMyWjAaMRgw
+FgYDVQQDDA9teDEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCu99Z6AB3EA2tAlWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxl
+BNHTk4c8HkobGqMTRU0x0qpzMnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX
++5ZnqS9gWbLZyjkCCqVQR/nyK0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakH
+ZIB8TYI6p6hanNQyz4NWq1PEpWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJki
+Hka24JzNCLx3GD/Q0GNGFGoE9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIo
+W0/dbG0KaKTTeHf9LegFXez2tJeLl+vpAgMBAAGjfjB8MB0GA1UdDgQWBBTCrBLC
+itbyxruhJFbJEaGYdG4NRjAfBgNVHSMEGDAWgBTCbuRdkXOuqXbXqJJnH5iQPhkT
+JTAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214
+MS5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAYOgZ5NaFJhmjT6V+GI5m
+cuTsMC7WB5nQ8nbf2wxw052jgBvnZWAgnshtgWvrK0T6402VcTOzHt8VKS0KwLwk
+FESvj2rdoDGvGqUcw6DhCns98PRuts2nMTDlKYJTYWab2Q08PPYxTtRt1Y+A2sp7
+et+bxMjYr2mdO0wkqK7guUA9H2bZkGYJck5bbna67hpzx17YomBjDUjTtMiagXKs
+E5QzOXXqCQxiSunlxsMqDwt/HRxGvQy+BHuNq4epuZfeMOndIjl2ZyKdN2ABy88G
+dYqRO6nBHUO2/ZxlAZf11xpueVZZ61SueimPfzbsw0B7ZvPSi9KdbPIGBbD4cCwt
+JA==
+-----END CERTIFICATE-----
diff --git a/src/tls/rsaee-pkey.pem b/src/tls/rsaee-pkey.pem
new file mode 100644
index 0000000..aa4e498
--- /dev/null
+++ b/src/tls/rsaee-pkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
diff --git a/src/tls/rsaroot-cert.pem b/src/tls/rsaroot-cert.pem
new file mode 100644
index 0000000..3911451
--- /dev/null
+++ b/src/tls/rsaroot-cert.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjE5NDJaGA8yMTE5MDEwOTA2MTk0MlowFjEUMBIG
+A1UEAwwLUlNBIHJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCoZoGzTiMbZcztQVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65An
+K2vHNrE37DDRl2YTmP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6
+yuO07nut+rNFMSSjgnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0S
+oxQVSkyOIr/phPAkLWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2
+X8lMaz8ooT9OI/4ln/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu
+1ViLHIsm+55CJj5OI676Q4JJAgMBAAGjUzBRMB0GA1UdDgQWBBSRqXIsNBGHYWzt
+8tLDOtnFpx07YjAfBgNVHSMEGDAWgBSRqXIsNBGHYWzt8tLDOtnFpx07YjAPBgNV
+HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCHEQ8DRqrWz8xibaNEv3Id
+SEJvgfRRsMA7jk2HAnE84bO1ZLkXF9HX7oqXbIMRw5IZRUrBXO0fUwcxz3DwTYZj
+ojRPP1yNfylmc8uxyd2goMoG4OgTdt2/Ewu8bli0S9NapH9fdBYbSDUBw3QiH+Wy
+ze9BiDVLwM7eERHGj1XKlC3iUaTZTOFWrr4y5eRjCD8oMzCB/73CWMRwBGPyF+Wg
+uP8FfF7i05B9rrKELAzCr4/hqYPDYNX8mv8QzcwXbn93PY35VN5y/wBuccggv0af
+iK5NfoNxvmTp9SVD4wA2prlkq1lbjMuqoAxrKpCGG0I0x8Dzn3bTaTNETal61Wt/
+-----END CERTIFICATE-----
diff --git a/src/tls/rsaroot-pkey.pem b/src/tls/rsaroot-pkey.pem
new file mode 100644
index 0000000..a5509b7
--- /dev/null
+++ b/src/tls/rsaroot-pkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoZoGzTiMbZczt
+QVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65AnK2vHNrE37DDRl2YT
+mP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6yuO07nut+rNFMSSj
+gnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0SoxQVSkyOIr/phPAk
+LWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2X8lMaz8ooT9OI/4l
+n/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu1ViLHIsm+55CJj5O
+I676Q4JJAgMBAAECggEBAKLmzk7KpnFpb+yPC5SnJ+65M+tWRxC4FQmyuEUz03Ky
+j5pJKPTGeFVkO3b27fLGMTN798E2LR+DCo6or+3VbmA9n0kZvItWOuiYt2G54hrM
+vD3wJB6KdIdUp0BIzeFNBStQi7oIS4UCfgPiQckV3F/t9tyGG1kKLLz5aAvlY0Qw
+iXylE9h6dDZyufLPlkq0KVVuZA2Em1oa6UaqSYgKPX5KN0Oa4cVmzqXVt0BGq1tB
+SW80CAtKIH2AhCvHQ98ZlWbt1XgzFeP/3idZdjF2H0y2cg9rBDwRmFBwVTceQSbY
+Ezrh5WW4okFpldb/qxjURuhwkPpf3vMe/jOIBUoXjRECgYEA2/Xoz4Z8RmhgPjh2
+QOXwSuCWyKVpOXbCoP9hH9stVbUaxDuU7/ywSVW0P+x7hImUXnnKDp6pJ+NzO96b
+u6nBuUH6tNP+GTyWFgwHJf//6rEQg9WGIK0srX8Epg+S0wCRlvxggIwXWQe3HY9P
+KWFJJdBJ165AMoc/yeYHEsK536UCgYEAw/3xTrrOqNnstLwRJw2uKYj8BDTMgCA4
+ZW96t6hYI/Pwweuu4MXVCaCnt4tzyrYVP6FcYXK5Oa0G8yTF32mk45Nmro9Q4ZhX
+Wyavk/P37W0Ep+vSVtbJ7/zgRLTGVh8LPn3T7dVcoXx/JSvEW8Ofp49GXHKq5l2V
+0luXyZmfVtUCgYEAw0Ijnf1TWqkTLoiuqOO1kLKYB5uWshUzpvms/Ttyng+7qBEA
+IJ3e2+rBrLE/4KLE260faiT8IlWtmKr+8fM67jqc1GMPwNVgokehHOGJC4yNDYrB
+m0Y/T/Bebw+KFdb+Ztq4y8QQgc7whcQO0Lv01CV3N4gOowwe2xpgkw3bNKUCgYAf
+a/gjAiodwgqEE17Anx3cBN06o2hh5kiEYrIO/ctbwEFKJcn8uVrlVz9sWswupLCV
+af5QlT8C8y2ZD701i09nOPuOYuW5tV3T/EjL9KI8C21iqpknWPo5IpamDUF7DzET
+TMMMb1eRgE82G2U4vQ08pOjH645groJVnl+gb6OvHQKBgCurbKUMy0kPGECptAJ9
+dgqC9fricGX63bMPIcnz0V7uqvYbalNPhlmhwvMCkPNebWgzMnLQM3zrh+VnmOR2
+43MfgCm/+hqhRIyt3PQcgKS7shzrf2xPFZRMZVJFRk+sbDaD0MhUxXQ4g34tHtFz
+ozsnCRsyRaJNG8hC49dTTrlh
+-----END PRIVATE KEY-----
diff --git a/src/tls/tls.h b/src/tls/tls.h
new file mode 100644
index 0000000..b1fdfea
--- /dev/null
+++ b/src/tls/tls.h
@@ -0,0 +1,728 @@
+#ifndef _TLS_H_INCLUDED_
+#define _TLS_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls 3h
+/* SUMMARY
+/* libtls internal interfaces
+/* SYNOPSIS
+/* #include <tls.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <name_code.h>
+#include <argv.h>
+
+ /*
+ * TLS enforcement levels. Non-sentinel values may also be used to indicate
+ * the actual security level of a session.
+ *
+ * XXX TLS_LEV_NOTFOUND no longer belongs in this list. The SMTP client will
+ * have to use something else to report that policy table lookup failed.
+ *
+ * The order of levels matters, but we hide most of the details in macros.
+ *
+ * "dane" vs. "fingerprint", both must lie between "encrypt" and "verify".
+ *
+ * - With "may" and higher, TLS is enabled.
+ *
+ * - With "encrypt" and higher, TLS encryption must be applied.
+ *
+ * - Strictly above "encrypt", the peer certificate must match.
+ *
+ * - At "dane" and higher, the peer certificate must also be trusted. With
+ * "dane" the trust may be self-asserted, so we only log trust verification
+ * errors when TA associations are involved.
+ */
+#define TLS_LEV_INVALID -2 /* sentinel */
+#define TLS_LEV_NOTFOUND -1 /* XXX not in policy table */
+#define TLS_LEV_NONE 0 /* plain-text only */
+#define TLS_LEV_MAY 1 /* wildcard */
+#define TLS_LEV_ENCRYPT 2 /* encrypted connection */
+#define TLS_LEV_FPRINT 3 /* "peer" CA-less verification */
+#define TLS_LEV_HALF_DANE 4 /* DANE TLSA MX host, insecure MX RR */
+#define TLS_LEV_DANE 5 /* Opportunistic TLSA policy */
+#define TLS_LEV_DANE_ONLY 6 /* Required TLSA policy */
+#define TLS_LEV_VERIFY 7 /* certificate verified */
+#define TLS_LEV_SECURE 8 /* "secure" verification */
+
+#define TLS_REQUIRED(l) ((l) > TLS_LEV_MAY)
+#define TLS_MUST_MATCH(l) ((l) > TLS_LEV_ENCRYPT)
+#define TLS_MUST_PKIX(l) ((l) >= TLS_LEV_VERIFY)
+#define TLS_OPPORTUNISTIC(l) ((l) == TLS_LEV_MAY || (l) == TLS_LEV_DANE)
+#define TLS_DANE_BASED(l) \
+ ((l) >= TLS_LEV_HALF_DANE && (l) <= TLS_LEV_DANE_ONLY)
+#define TLS_NEVER_SECURED(l) ((l) == TLS_LEV_HALF_DANE)
+
+extern int tls_level_lookup(const char *);
+extern const char *str_tls_level(int);
+
+#ifdef USE_TLS
+
+ /*
+ * OpenSSL library.
+ */
+#include <openssl/lhash.h>
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/rand.h>
+#include <openssl/crypto.h> /* Legacy SSLEAY_VERSION_NUMBER */
+#include <openssl/evp.h> /* New OpenSSL 3.0 EVP_PKEY APIs */
+#include <openssl/opensslv.h> /* OPENSSL_VERSION_NUMBER */
+#include <openssl/ssl.h>
+#include <openssl/conf.h>
+
+ /* Appease indent(1) */
+#define x509_stack_t STACK_OF(X509)
+#define general_name_stack_t STACK_OF(GENERAL_NAME)
+#define ssl_cipher_stack_t STACK_OF(SSL_CIPHER)
+#define ssl_comp_stack_t STACK_OF(SSL_COMP)
+
+/*-
+ * Official way to check minimum OpenSSL API version from 3.0 onward.
+ * We simply define it false for all prior versions, where we typically also
+ * need the patch level to determine API compatibility.
+ */
+#ifndef OPENSSL_VERSION_PREREQ
+#define OPENSSL_VERSION_PREREQ(m,n) 0
+#endif
+
+#if (OPENSSL_VERSION_NUMBER < 0x1010100fUL)
+#error "OpenSSL releases prior to 1.1.1 are no longer supported"
+#endif
+
+ /*-
+ * Backwards compatibility with OpenSSL < 1.1.1a.
+ *
+ * In OpenSSL 1.1.1a the client-only interface SSL_get_server_tmp_key() was
+ * updated to work on both the client and the server, and was renamed to
+ * SSL_get_peer_tmp_key(), with the original name left behind as an alias. We
+ * use the new name when available.
+ */
+#if OPENSSL_VERSION_NUMBER < 0x1010101fUL
+#undef SSL_get_signature_nid
+#define SSL_get_signature_nid(ssl, pnid) (NID_undef)
+#define tls_get_peer_dh_pubkey SSL_get_server_tmp_key
+#else
+#define tls_get_peer_dh_pubkey SSL_get_peer_tmp_key
+#endif
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+#define TLS_PEEK_PEER_CERT(ssl) SSL_get0_peer_certificate(ssl)
+#define TLS_FREE_PEER_CERT(x) ((void) 0)
+#define tls_set_bio_callback BIO_set_callback_ex
+#else
+#define TLS_PEEK_PEER_CERT(ssl) SSL_get_peer_certificate(ssl)
+#define TLS_FREE_PEER_CERT(x) X509_free(x)
+#define tls_set_bio_callback BIO_set_callback
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <name_mask.h>
+#include <name_code.h>
+
+ /*
+ * TLS library.
+ */
+#include <dns.h>
+
+ /*
+ * TLS role, presently for logging.
+ */
+typedef enum {
+ TLS_ROLE_CLIENT, TLS_ROLE_SERVER,
+} TLS_ROLE;
+
+typedef enum {
+ TLS_USAGE_NEW, TLS_USAGE_USED,
+} TLS_USAGE;
+
+ /*
+ * Names of valid tlsmgr(8) session caches.
+ */
+#define TLS_MGR_SCACHE_SMTPD "smtpd"
+#define TLS_MGR_SCACHE_SMTP "smtp"
+#define TLS_MGR_SCACHE_LMTP "lmtp"
+
+ /*
+ * RFC 6698, 7671, 7672 DANE
+ */
+#define TLS_DANE_TA 0 /* Match trust-anchor digests */
+#define TLS_DANE_EE 1 /* Match end-entity digests */
+
+#define TLS_DANE_CERT 0 /* Match the certificate digest */
+#define TLS_DANE_PKEY 1 /* Match the public key digest */
+
+#define TLS_DANE_FLAG_NORRS (1<<0) /* Nothing found in DNS */
+#define TLS_DANE_FLAG_EMPTY (1<<1) /* Nothing usable found in DNS */
+#define TLS_DANE_FLAG_ERROR (1<<2) /* TLSA record lookup error */
+
+#define tls_dane_unusable(dane) ((dane)->flags & TLS_DANE_FLAG_EMPTY)
+#define tls_dane_notfound(dane) ((dane)->flags & TLS_DANE_FLAG_NORRS)
+
+#define TLS_DANE_CACHE_TTL_MIN 1 /* A lot can happen in ~2 seconds */
+#define TLS_DANE_CACHE_TTL_MAX 100 /* Comparable to max_idle */
+
+ /*
+ * Certificate and public key digests (typically from TLSA RRs), grouped by
+ * algorithm.
+ */
+typedef struct TLS_TLSA {
+ uint8_t usage; /* DANE certificate usage */
+ uint8_t selector; /* DANE selector */
+ uint8_t mtype; /* Algorithm for this digest list */
+ uint16_t length; /* Length of associated data */
+ unsigned char *data; /* Associated data */
+ struct TLS_TLSA *next; /* Chain to next algorithm */
+} TLS_TLSA;
+
+typedef struct TLS_DANE {
+ TLS_TLSA *tlsa; /* TLSA records */
+ char *base_domain; /* Base domain of TLSA RRset */
+ int flags; /* Lookup status */
+ time_t expires; /* Expiration time of this record */
+ int refs; /* Reference count */
+} TLS_DANE;
+
+ /*
+ * tls_dane.c
+ */
+extern int tls_dane_avail(void);
+extern void tls_dane_loglevel(const char *, const char *);
+extern void tls_dane_flush(void);
+extern TLS_DANE *tls_dane_alloc(void);
+extern void tls_tlsa_free(TLS_TLSA *);
+extern void tls_dane_free(TLS_DANE *);
+extern void tls_dane_add_fpt_digests(TLS_DANE *, const char *, const char *,
+ int);
+extern TLS_DANE *tls_dane_resolve(unsigned, const char *, DNS_RR *, int);
+extern int tls_dane_load_trustfile(TLS_DANE *, const char *);
+
+ /*
+ * TLS session context, also used by the VSTREAM call-back routines for SMTP
+ * input/output, and by OpenSSL call-back routines for key verification.
+ *
+ * Only some members are (read-only) accessible by the public.
+ */
+#define CCERT_BUFSIZ 256
+
+typedef struct {
+ /* Public, read-only. */
+ char *peer_CN; /* Peer Common Name */
+ char *issuer_CN; /* Issuer Common Name */
+ char *peer_sni; /* SNI sent to or by the peer */
+ char *peer_cert_fprint; /* ASCII certificate fingerprint */
+ char *peer_pkey_fprint; /* ASCII public key fingerprint */
+ int level; /* Effective security level */
+ int peer_status; /* Certificate and match status */
+ const char *protocol;
+ const char *cipher_name;
+ int cipher_usebits;
+ int cipher_algbits;
+ const char *kex_name; /* shared key-exchange algorithm */
+ const char *kex_curve; /* shared key-exchange ECDHE curve */
+ int kex_bits; /* shared FFDHE key exchange bits */
+ const char *clnt_sig_name; /* client's signature key algorithm */
+ const char *clnt_sig_curve; /* client's ECDSA curve name */
+ int clnt_sig_bits; /* client's RSA signature key bits */
+ const char *clnt_sig_dgst; /* client's signature digest */
+ const char *srvr_sig_name; /* server's signature key algorithm */
+ const char *srvr_sig_curve; /* server's ECDSA curve name */
+ int srvr_sig_bits; /* server's RSA signature key bits */
+ const char *srvr_sig_dgst; /* server's signature digest */
+ /* Private. */
+ SSL *con;
+ char *cache_type; /* tlsmgr(8) cache type if enabled */
+ int ticketed; /* Session ticket issued */
+ char *serverid; /* unique server identifier */
+ char *namaddr; /* nam[addr] for logging */
+ int log_mask; /* What to log */
+ int session_reused; /* this session was reused */
+ int am_server; /* Are we an SSL server or client? */
+ const char *mdalg; /* default message digest algorithm */
+ /* Built-in vs external SSL_accept/read/write/shutdown support. */
+ VSTREAM *stream; /* Blocking-mode SMTP session */
+ /* DANE TLSA trust input and verification state */
+ const TLS_DANE *dane; /* DANE TLSA digests */
+ X509 *errorcert; /* Error certificate closest to leaf */
+ int errordepth; /* Chain depth of error cert */
+ int errorcode; /* First error at error depth */
+ int must_fail; /* Failed to load trust settings */
+} TLS_SESS_STATE;
+
+ /*
+ * Peer status bits. TLS_CERT_FLAG_MATCHED implies TLS_CERT_FLAG_TRUSTED
+ * only in the case of a hostname match.
+ */
+#define TLS_CERT_FLAG_PRESENT (1<<0)
+#define TLS_CERT_FLAG_ALTNAME (1<<1)
+#define TLS_CERT_FLAG_TRUSTED (1<<2)
+#define TLS_CERT_FLAG_MATCHED (1<<3)
+#define TLS_CERT_FLAG_SECURED (1<<4)
+
+#define TLS_CERT_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_PRESENT))
+#define TLS_CERT_IS_ALTNAME(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_ALTNAME))
+#define TLS_CERT_IS_TRUSTED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_TRUSTED))
+#define TLS_CERT_IS_MATCHED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_MATCHED))
+#define TLS_CERT_IS_SECURED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_SECURED))
+
+ /*
+ * Opaque client context handle.
+ */
+typedef struct TLS_APPL_STATE TLS_APPL_STATE;
+
+#ifdef TLS_INTERNAL
+
+ /*
+ * Log mask details are internal to the library.
+ */
+extern int tls_log_mask(const char *, const char *);
+
+ /*
+ * What to log.
+ */
+#define TLS_LOG_NONE (1<<0)
+#define TLS_LOG_SUMMARY (1<<1)
+#define TLS_LOG_UNTRUSTED (1<<2)
+#define TLS_LOG_PEERCERT (1<<3)
+#define TLS_LOG_CERTMATCH (1<<4)
+#define TLS_LOG_VERBOSE (1<<5)
+#define TLS_LOG_CACHE (1<<6)
+#define TLS_LOG_DEBUG (1<<7)
+#define TLS_LOG_TLSPKTS (1<<8)
+#define TLS_LOG_ALLPKTS (1<<9)
+#define TLS_LOG_DANE (1<<10)
+
+ /*
+ * Client and Server application contexts
+ */
+struct TLS_APPL_STATE {
+ SSL_CTX *ssl_ctx;
+ SSL_CTX *sni_ctx;
+ int log_mask;
+ char *cache_type;
+};
+
+ /*
+ * tls_misc.c Application-context update and disposal.
+ */
+extern void tls_update_app_logmask(TLS_APPL_STATE *, int);
+extern void tls_free_app_context(TLS_APPL_STATE *);
+
+ /*
+ * tls_misc.c
+ */
+extern void tls_param_init(void);
+extern int tls_library_init(void);
+
+ /*
+ * Protocol selection.
+ */
+#define TLS_PROTOCOL_INVALID (~0) /* All protocol bits masked */
+
+#ifdef SSL_TXT_SSLV2
+#define TLS_PROTOCOL_SSLv2 (1<<0) /* SSLv2 */
+#else
+#define SSL_TXT_SSLV2 "SSLv2"
+#define TLS_PROTOCOL_SSLv2 0 /* Unknown */
+#undef SSL_OP_NO_SSLv2
+#define SSL_OP_NO_SSLv2 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_SSLV3
+#define TLS_PROTOCOL_SSLv3 (1<<1) /* SSLv3 */
+#else
+#define SSL_TXT_SSLV3 "SSLv3"
+#define TLS_PROTOCOL_SSLv3 0 /* Unknown */
+#undef SSL_OP_NO_SSLv3
+#define SSL_OP_NO_SSLv3 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_TLSV1
+#define TLS_PROTOCOL_TLSv1 (1<<2) /* TLSv1 */
+#else
+#define SSL_TXT_TLSV1 "TLSv1"
+#define TLS_PROTOCOL_TLSv1 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1
+#define SSL_OP_NO_TLSv1 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_TLSV1_1
+#define TLS_PROTOCOL_TLSv1_1 (1<<3) /* TLSv1_1 */
+#else
+#define SSL_TXT_TLSV1_1 "TLSv1.1"
+#define TLS_PROTOCOL_TLSv1_1 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1_1
+#define SSL_OP_NO_TLSv1_1 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_TLSV1_2
+#define TLS_PROTOCOL_TLSv1_2 (1<<4) /* TLSv1_2 */
+#else
+#define SSL_TXT_TLSV1_2 "TLSv1.2"
+#define TLS_PROTOCOL_TLSv1_2 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1_2
+#define SSL_OP_NO_TLSv1_2 0L /* Noop */
+#endif
+
+ /*
+ * OpenSSL 1.1.1 does not define a TXT macro for TLS 1.3, so we roll our
+ * own.
+ */
+#define TLS_PROTOCOL_TXT_TLSV1_3 "TLSv1.3"
+
+#if defined(TLS1_3_VERSION) && defined(SSL_OP_NO_TLSv1_3)
+#define TLS_PROTOCOL_TLSv1_3 (1<<5) /* TLSv1_3 */
+#else
+#define TLS_PROTOCOL_TLSv1_3 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1_3
+#define SSL_OP_NO_TLSv1_3 0L /* Noop */
+#endif
+
+/*
+ * Always used when defined, SMTP has no truncation attacks.
+ */
+#ifndef SSL_OP_IGNORE_UNEXPECTED_EOF
+#define SSL_OP_IGNORE_UNEXPECTED_EOF 0L
+#endif
+
+#define TLS_KNOWN_PROTOCOLS \
+ ( TLS_PROTOCOL_SSLv2 | TLS_PROTOCOL_SSLv3 | TLS_PROTOCOL_TLSv1 \
+ | TLS_PROTOCOL_TLSv1_1 | TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3 )
+#define TLS_SSL_OP_PROTOMASK(m) \
+ ((((m) & TLS_PROTOCOL_SSLv2) ? SSL_OP_NO_SSLv2 : 0L) \
+ | (((m) & TLS_PROTOCOL_SSLv3) ? SSL_OP_NO_SSLv3 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1) ? SSL_OP_NO_TLSv1 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1_1) ? SSL_OP_NO_TLSv1_1 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1_2) ? SSL_OP_NO_TLSv1_2 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1_3) ? SSL_OP_NO_TLSv1_3 : 0L))
+
+/*
+ * SSL options that are managed via dedicated Postfix features, rather than
+ * just exposed via hex codes or named elements of tls_ssl_options.
+ */
+#define TLS_SSL_OP_MANAGED_BITS \
+ (SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_IGNORE_UNEXPECTED_EOF | \
+ TLS_SSL_OP_PROTOMASK(~0))
+
+extern int tls_proto_mask_lims(const char *, int *, int *);
+
+ /*
+ * Cipher grade selection.
+ */
+#define TLS_CIPHER_NONE 0
+#define TLS_CIPHER_NULL 1
+#define TLS_CIPHER_EXPORT 2
+#define TLS_CIPHER_LOW 3
+#define TLS_CIPHER_MEDIUM 4
+#define TLS_CIPHER_HIGH 5
+
+extern const NAME_CODE tls_cipher_grade_table[];
+
+#define tls_cipher_grade(str) \
+ name_code(tls_cipher_grade_table, NAME_CODE_FLAG_NONE, (str))
+#define str_tls_cipher_grade(gr) \
+ str_name_code(tls_cipher_grade_table, (gr))
+
+ /*
+ * Cipher lists with exclusions.
+ */
+extern const char *tls_set_ciphers(TLS_SESS_STATE *, const char *,
+ const char *);
+
+ /*
+ * Populate TLS context with TLS 1.3-related signature parameters.
+ */
+extern void tls_get_signature_params(TLS_SESS_STATE *);
+
+#endif /* TLS_INTERNAL */
+
+ /*
+ * tls_client.c
+ */
+typedef struct {
+ const char *log_param;
+ const char *log_level;
+ int verifydepth;
+ const char *cache_type;
+ const char *chain_files;
+ const char *cert_file;
+ const char *key_file;
+ const char *dcert_file;
+ const char *dkey_file;
+ const char *eccert_file;
+ const char *eckey_file;
+ const char *CAfile;
+ const char *CApath;
+ const char *mdalg; /* default message digest algorithm */
+} TLS_CLIENT_INIT_PROPS;
+
+typedef struct {
+ TLS_APPL_STATE *ctx;
+ VSTREAM *stream;
+ int fd; /* Event-driven file descriptor */
+ int timeout;
+ int tls_level; /* Security level */
+ const char *nexthop; /* destination domain */
+ const char *host; /* MX hostname */
+ const char *namaddr; /* nam[addr] for logging */
+ const char *sni; /* optional SNI name when not DANE */
+ const char *serverid; /* Session cache key */
+ const char *helo; /* Server name from EHLO response */
+ const char *protocols; /* Enabled protocols */
+ const char *cipher_grade; /* Minimum cipher grade */
+ const char *cipher_exclusions; /* Ciphers to exclude */
+ const ARGV *matchargv; /* Cert match patterns */
+ const char *mdalg; /* default message digest algorithm */
+ const TLS_DANE *dane; /* DANE TLSA verification */
+} TLS_CLIENT_START_PROPS;
+
+extern TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *);
+extern TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *);
+extern TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *,
+ const TLS_CLIENT_START_PROPS *);
+
+#define tls_client_stop(ctx, stream, timeout, failure, TLScontext) \
+ tls_session_stop(ctx, (stream), (timeout), (failure), (TLScontext))
+
+#define TLS_CLIENT_INIT_ARGS(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14) \
+ (((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14), (props))
+
+#define TLS_CLIENT_INIT(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14) \
+ tls_client_init(TLS_CLIENT_INIT_ARGS(props, a1, a2, a3, a4, a5, \
+ a6, a7, a8, a9, a10, a11, a12, a13, a14))
+
+#define TLS_CLIENT_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14, a15, a16, a17) \
+ tls_client_start((((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \
+ ((props)->a16), ((props)->a17), (props)))
+
+ /*
+ * tls_server.c
+ */
+typedef struct {
+ const char *log_param;
+ const char *log_level;
+ int verifydepth;
+ const char *cache_type;
+ int set_sessid;
+ const char *chain_files;
+ const char *cert_file;
+ const char *key_file;
+ const char *dcert_file;
+ const char *dkey_file;
+ const char *eccert_file;
+ const char *eckey_file;
+ const char *CAfile;
+ const char *CApath;
+ const char *protocols;
+ const char *eecdh_grade;
+ const char *dh1024_param_file;
+ const char *dh512_param_file;
+ int ask_ccert;
+ const char *mdalg; /* default message digest algorithm */
+} TLS_SERVER_INIT_PROPS;
+
+typedef struct {
+ TLS_APPL_STATE *ctx; /* TLS application context */
+ VSTREAM *stream; /* Client stream */
+ int fd; /* Event-driven file descriptor */
+ int timeout; /* TLS handshake timeout */
+ int requirecert; /* Insist on client cert? */
+ const char *serverid; /* Server instance (salt cache key) */
+ const char *namaddr; /* Client nam[addr] for logging */
+ const char *cipher_grade;
+ const char *cipher_exclusions;
+ const char *mdalg; /* default message digest algorithm */
+} TLS_SERVER_START_PROPS;
+
+extern TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *);
+extern TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props);
+extern TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *);
+
+#define tls_server_stop(ctx, stream, timeout, failure, TLScontext) \
+ tls_session_stop(ctx, (stream), (timeout), (failure), (TLScontext))
+
+#define TLS_SERVER_INIT(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \
+ tls_server_init((((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \
+ ((props)->a16), ((props)->a17), ((props)->a18), ((props)->a19), \
+ ((props)->a20), (props)))
+
+#define TLS_SERVER_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \
+ tls_server_start((((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), (props)))
+
+ /*
+ * tls_session.c
+ */
+extern void tls_session_stop(TLS_APPL_STATE *, VSTREAM *, int, int, TLS_SESS_STATE *);
+
+ /*
+ * tls_misc.c
+ */
+extern const char *tls_compile_version(void);
+extern const char *tls_run_version(void);
+extern const char **tls_pkey_algorithms(void);
+extern void tls_log_summary(TLS_ROLE, TLS_USAGE, TLS_SESS_STATE *);
+extern void tls_pre_jail_init(TLS_ROLE);
+
+#ifdef TLS_INTERNAL
+
+#include <vstring.h>
+
+extern VSTRING *tls_session_passivate(SSL_SESSION *);
+extern SSL_SESSION *tls_session_activate(const char *, int);
+
+ /*
+ * tls_stream.c.
+ */
+extern void tls_stream_start(VSTREAM *, TLS_SESS_STATE *);
+extern void tls_stream_stop(VSTREAM *);
+
+ /*
+ * tls_bio_ops.c: a generic multi-personality driver that retries SSL
+ * operations until they are satisfied or until a hard error happens.
+ * Because of its ugly multi-personality user interface we invoke it via
+ * not-so-ugly single-personality wrappers.
+ */
+extern int tls_bio(int, int, TLS_SESS_STATE *,
+ int (*) (SSL *), /* handshake */
+ int (*) (SSL *, void *, int), /* read */
+ int (*) (SSL *, const void *, int), /* write */
+ void *, int);
+
+#define tls_bio_connect(fd, timeout, context) \
+ tls_bio((fd), (timeout), (context), SSL_connect, \
+ NULL, NULL, NULL, 0)
+#define tls_bio_accept(fd, timeout, context) \
+ tls_bio((fd), (timeout), (context), SSL_accept, \
+ NULL, NULL, NULL, 0)
+#define tls_bio_shutdown(fd, timeout, context) \
+ tls_bio((fd), (timeout), (context), SSL_shutdown, \
+ NULL, NULL, NULL, 0)
+#define tls_bio_read(fd, buf, len, timeout, context) \
+ tls_bio((fd), (timeout), (context), NULL, \
+ SSL_read, NULL, (buf), (len))
+#define tls_bio_write(fd, buf, len, timeout, context) \
+ tls_bio((fd), (timeout), (context), NULL, \
+ NULL, SSL_write, (buf), (len))
+
+ /*
+ * tls_dh.c
+ */
+extern void tls_set_dh_from_file(const char *);
+extern void tls_tmp_dh(SSL_CTX *, int);
+extern void tls_auto_eecdh_curves(SSL_CTX *, const char *);
+
+ /*
+ * tls_verify.c
+ */
+extern char *tls_peer_CN(X509 *, const TLS_SESS_STATE *);
+extern char *tls_issuer_CN(X509 *, const TLS_SESS_STATE *);
+extern int tls_verify_certificate_callback(int, X509_STORE_CTX *);
+extern void tls_log_verify_error(TLS_SESS_STATE *);
+
+ /*
+ * tls_dane.c
+ */
+extern void tls_dane_log(TLS_SESS_STATE *);
+extern void tls_dane_digest_init(SSL_CTX *, const EVP_MD *);
+extern int tls_dane_enable(TLS_SESS_STATE *);
+extern TLS_TLSA *tlsa_prepend(TLS_TLSA *, uint8_t, uint8_t, uint8_t,
+ const unsigned char *, uint16_t);
+
+ /*
+ * tls_fprint.c
+ */
+extern const EVP_MD *tls_digest_byname(const char *, EVP_MD_CTX **);
+extern char *tls_digest_encode(const unsigned char *, int);
+extern char *tls_cert_fprint(X509 *, const char *);
+extern char *tls_pkey_fprint(X509 *, const char *);
+extern char *tls_serverid_digest(TLS_SESS_STATE *,
+ const TLS_CLIENT_START_PROPS *, const char *);
+
+ /*
+ * tls_certkey.c
+ */
+extern int tls_set_ca_certificate_info(SSL_CTX *, const char *, const char *);
+extern int tls_load_pem_chain(SSL *, const char *, const char *);
+extern int tls_set_my_certificate_key_info(SSL_CTX *, /* All */ const char *,
+ /* RSA */ const char *, const char *,
+ /* DSA */ const char *, const char *,
+ /* ECDSA */ const char *, const char *);
+
+ /*
+ * tls_misc.c
+ */
+extern int TLScontext_index;
+
+extern TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *, SSL_CTX *, int);
+extern TLS_SESS_STATE *tls_alloc_sess_context(int, const char *);
+extern void tls_free_context(TLS_SESS_STATE *);
+extern void tls_check_version(void);
+extern long tls_bug_bits(void);
+extern void tls_print_errors(void);
+extern void tls_info_callback(const SSL *, int, int);
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+extern long tls_bio_dump_cb(BIO *, int, const char *, size_t, int, long,
+ int, size_t *);
+
+#else
+extern long tls_bio_dump_cb(BIO *, int, const char *, int, long, long);
+
+#endif
+extern const EVP_MD *tls_validate_digest(const char *);
+
+ /*
+ * tls_seed.c
+ */
+extern void tls_int_seed(void);
+extern int tls_ext_seed(int);
+
+#endif /* TLS_INTERNAL */
+
+/* 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
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+#endif /* USE_TLS */
+#endif /* _TLS_H_INCLUDED_ */
diff --git a/src/tls/tls_bio_ops.c b/src/tls/tls_bio_ops.c
new file mode 100644
index 0000000..9b66195
--- /dev/null
+++ b/src/tls/tls_bio_ops.c
@@ -0,0 +1,296 @@
+/*++
+/* NAME
+/* tls_bio_ops 3
+/* SUMMARY
+/* TLS network basic I/O management
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_bio_connect(fd, timeout, context)
+/* int fd;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_accept(fd, timeout, context)
+/* int fd;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_shutdown(fd, timeout, context)
+/* int fd;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_read(fd, buf, len, timeout, context)
+/* int fd;
+/* void *buf;
+/* int len;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_write(fd, buf, len, timeout, context)
+/* int fd;
+/* void *buf;
+/* int len;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/* DESCRIPTION
+/* This module enforces VSTREAM-style timeouts on non-blocking
+/* I/O while performing TLS handshake or input/output operations.
+/*
+/* The Postfix VSTREAM read/write routines invoke the
+/* tls_bio_read/write routines to send and receive plain-text
+/* data. In addition, this module provides tls_bio_connect/accept
+/* routines that trigger the initial TLS handshake. The
+/* tls_bio_xxx routines invoke the corresponding SSL routines
+/* that translate the requests into TLS protocol messages.
+/*
+/* Whenever an SSL operation indicates that network input (or
+/* output) needs to happen, the tls_bio_xxx routines wait for
+/* the network to become readable (or writable) within the
+/* timeout limit, then retry the SSL operation. This works
+/* because the network socket is in non-blocking mode.
+/*
+/* tls_bio_connect() performs the SSL_connect() operation.
+/*
+/* tls_bio_accept() performs the SSL_accept() operation.
+/*
+/* tls_bio_shutdown() performs the SSL_shutdown() operation.
+/*
+/* tls_bio_read() performs the SSL_read() operation.
+/*
+/* tls_bio_write() performs the SSL_write() operation.
+/*
+/* Arguments:
+/* .IP fd
+/* Network socket.
+/* .IP buf
+/* Read/write buffer.
+/* .IP len
+/* Read/write request size.
+/* .IP timeout
+/* Read/write timeout.
+/* .IP TLScontext
+/* TLS session state.
+/* DIAGNOSTICS
+/* A result value > 0 means successful completion.
+/*
+/* A result value < 0 means that the requested operation did
+/* not complete due to TLS protocol failure, system call
+/* failure, or for any reason described under "in addition"
+/* below.
+/*
+/* A result value of 0 from tls_bio_shutdown() means that the
+/* operation is in progress. A result value of 0 from other
+/* tls_bio_ops(3) operations means that the remote party either
+/* closed the network connection or that it sent a TLS shutdown
+/* request.
+/*
+/* Upon return from the tls_bio_ops(3) routines the global
+/* errno value is non-zero when the requested operation did not
+/* complete due to system call failure.
+/*
+/* In addition, the result value is set to -1, and the global
+/* errno value is set to ETIMEDOUT, when some network read/write
+/* operation did not complete within the time limit.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/time.h>
+
+#ifndef timersub
+/* res = a - b */
+#define timersub(a, b, res) do { \
+ (res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+ (res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
+ if ((res)->tv_usec < 0) { \
+ (res)->tv_sec--; \
+ (res)->tv_usec += 1000000; \
+ } \
+ } while (0)
+#endif
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* tls_bio - perform SSL input/output operation with extreme prejudice */
+
+int tls_bio(int fd, int timeout, TLS_SESS_STATE *TLScontext,
+ int (*hsfunc) (SSL *),
+ int (*rfunc) (SSL *, void *, int),
+ int (*wfunc) (SSL *, const void *, int),
+ void *buf, int num)
+{
+ const char *myname = "tls_bio";
+ int status;
+ int err;
+ int enable_deadline;
+ struct timeval time_left; /* amount of time left */
+ struct timeval time_deadline; /* time of deadline */
+ struct timeval time_now; /* time after SSL_mumble() call */
+
+ /*
+ * Compensation for interface mis-match: With VSTREAMs, timeout <= 0
+ * means wait forever; with the read/write_wait() calls below, we need to
+ * specify timeout < 0 instead.
+ *
+ * Safety: no time limit means no deadline.
+ */
+ if (timeout <= 0) {
+ timeout = -1;
+ enable_deadline = 0;
+ }
+
+ /*
+ * Deadline management is simpler than with VSTREAMs, because we don't
+ * need to decrement a per-stream time limit. We just work within the
+ * budget that is available for this tls_bio() call.
+ */
+ else {
+ enable_deadline =
+ vstream_fstat(TLScontext->stream, VSTREAM_FLAG_DEADLINE);
+ if (enable_deadline) {
+ GETTIMEOFDAY(&time_deadline);
+ time_deadline.tv_sec += timeout;
+ }
+ }
+
+ /*
+ * If necessary, retry the SSL handshake or read/write operation after
+ * handling any pending network I/O.
+ */
+ for (;;) {
+
+ /*
+ * Flush the per-thread SSL error queue. Otherwise, errors from other
+ * code that also uses TLS may confuse SSL_get_error(3).
+ */
+ ERR_clear_error();
+
+ if (hsfunc)
+ status = hsfunc(TLScontext->con);
+ else if (rfunc)
+ status = rfunc(TLScontext->con, buf, num);
+ else if (wfunc)
+ status = wfunc(TLScontext->con, buf, num);
+ else
+ msg_panic("%s: nothing to do here", myname);
+ err = SSL_get_error(TLScontext->con, status);
+
+ /*
+ * Correspondence between SSL_ERROR_* error codes and tls_bio_(read,
+ * write, accept, connect, shutdown) return values (for brevity:
+ * retval).
+ *
+ * SSL_ERROR_NONE corresponds with retval > 0. With SSL_(read, write)
+ * this is the number of plaintext bytes sent or received. With
+ * SSL_(accept, connect, shutdown) this means that the operation was
+ * completed successfully.
+ *
+ * SSL_ERROR_WANT_(WRITE, READ) start a new loop iteration, or force
+ * (retval = -1, errno = ETIMEDOUT) when the time limit is exceeded.
+ *
+ * All other SSL_ERROR_* cases correspond with retval <= 0. With
+ * SSL_(read, write, accept, connect) retval == 0 means that the
+ * remote party either closed the network connection or that it
+ * requested TLS shutdown; with SSL_shutdown() retval == 0 means that
+ * our own shutdown request is in progress. With all operations
+ * retval < 0 means that there was an error. In the latter case,
+ * SSL_ERROR_SYSCALL means that error details are returned via the
+ * errno value.
+ *
+ * Find out if we must retry the operation and/or if there is pending
+ * network I/O.
+ *
+ * XXX If we're the first to invoke SSL_shutdown(), then the operation
+ * isn't really complete when the call returns. We could hide that
+ * anomaly here and repeat the call.
+ */
+ switch (err) {
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ if (enable_deadline) {
+ GETTIMEOFDAY(&time_now);
+ timersub(&time_deadline, &time_now, &time_left);
+ timeout = time_left.tv_sec + (time_left.tv_usec > 0);
+ if (timeout <= 0) {
+ errno = ETIMEDOUT;
+ return (-1);
+ }
+ }
+ if (err == SSL_ERROR_WANT_WRITE) {
+ if (write_wait(fd, timeout) < 0)
+ return (-1); /* timeout error */
+ } else {
+ if (read_wait(fd, timeout) < 0)
+ return (-1); /* timeout error */
+ }
+ break;
+
+ /*
+ * Unhandled cases: SSL_ERROR_WANT_(ACCEPT, CONNECT, X509_LOOKUP)
+ * etc. Historically, Postfix silently treated these as ordinary
+ * I/O errors so we don't really know how common they are. For
+ * now, we just log a warning.
+ */
+ default:
+ msg_warn("%s: unexpected SSL_ERROR code %d", myname, err);
+ /* FALLTHROUGH */
+
+ /*
+ * With tls_timed_read() and tls_timed_write() the caller is the
+ * VSTREAM library module which is unaware of TLS, so we log the
+ * TLS error stack here. In a better world, each VSTREAM I/O
+ * object would provide an error reporting method in addition to
+ * the timed_read and timed_write methods, so that we would not
+ * need to have ad-hoc code like this.
+ */
+ case SSL_ERROR_SSL:
+ if (rfunc || wfunc)
+ tls_print_errors();
+ /* FALLTHROUGH */
+ case SSL_ERROR_ZERO_RETURN:
+ case SSL_ERROR_NONE:
+ errno = 0; /* avoid bogus warnings */
+ /* FALLTHROUGH */
+ case SSL_ERROR_SYSCALL:
+ return (status);
+ }
+ }
+}
+
+#endif
diff --git a/src/tls/tls_certkey.c b/src/tls/tls_certkey.c
new file mode 100644
index 0000000..09a35e0
--- /dev/null
+++ b/src/tls/tls_certkey.c
@@ -0,0 +1,721 @@
+/*++
+/* NAME
+/* tls_certkey 3
+/* SUMMARY
+/* public key certificate and private key loader
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_set_ca_certificate_info(ctx, CAfile, CApath)
+/* SSL_CTX *ctx;
+/* const char *CAfile;
+/* const char *CApath;
+/*
+/* int tls_set_my_certificate_key_info(ctx, chain_files,
+/* cert_file, key_file,
+/* dcert_file, dkey_file,
+/* eccert_file, eckey_file)
+/* SSL_CTX *ctx;
+/* const char *chain_files;
+/* const char *cert_file;
+/* const char *key_file;
+/* const char *dcert_file;
+/* const char *dkey_file;
+/* const char *eccert_file;
+/* const char *eckey_file;
+/*
+/* int tls_load_pem_chain(ssl, pem, origin);
+/* SSL *ssl;
+/* const char *pem;
+/* const char *origin;
+/* DESCRIPTION
+/* OpenSSL supports two options to specify CA certificates:
+/* either one file CAfile that contains all CA certificates,
+/* or a directory CApath with separate files for each
+/* individual CA, with symbolic links named after the hash
+/* values of the certificates. The second option is not
+/* convenient with a chrooted process.
+/*
+/* tls_set_ca_certificate_info() loads the CA certificate
+/* information for the specified TLS server or client context.
+/* The result is -1 on failure, 0 on success.
+/*
+/* tls_set_my_certificate_key_info() loads the public key
+/* certificates and private keys for the specified TLS server
+/* or client context. Up to 3 pairs of key pairs (RSA, DSA and
+/* ECDSA) may be specified; each certificate and key pair must
+/* match. The chain_files argument makes it possible to load
+/* keys and certificates for more than 3 algorithms, via either
+/* a single file, or a list of multiple files. The result is -1
+/* on failure, 0 on success.
+/*
+/* tls_load_pem_chain() loads one or more (key, cert, [chain])
+/* triples from an in-memory PEM blob. The "origin" argument
+/* is used for error logging, to identify the provenance of the
+/* PEM blob. "ssl" must be non-zero, and the keys and certificates
+/* will be loaded into that object.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* 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>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+#define PEM_LOAD_STATE_NOGO -2 /* Unusable object or sequence */
+#define PEM_LOAD_STATE_FAIL -1 /* Error in libcrypto */
+#define PEM_LOAD_STATE_DONE 0 /* End of PEM file, return value only */
+#define PEM_LOAD_STATE_INIT 1 /* No PEM objects seen */
+#define PEM_LOAD_STATE_PKEY 2 /* Last object was a private key */
+#define PEM_LOAD_STATE_CERT 3 /* Last object was a certificate */
+#define PEM_LOAD_STATE_BOTH 4 /* Unordered, key + first cert seen */
+
+#define PEM_LOAD_READ_LAST 0 /* Reading last file */
+#define PEM_LOAD_READ_MORE 1 /* More files to be read */
+
+typedef struct pem_load_state_t {
+ const char *origin; /* PEM chain origin description */
+ const char *source; /* PEM BIO origin description */
+ const char *keysrc; /* Source of last key */
+ BIO *pembio; /* PEM input stream */
+ SSL_CTX *ctx; /* SSL connection factory */
+ SSL *ssl; /* SSL connection handle */
+ EVP_PKEY *pkey; /* current key */
+ X509 *cert; /* current certificate */
+ x509_stack_t *chain; /* current chain */
+ int keynum; /* Index of last key */
+ int objnum; /* Index in current source */
+ int state; /* Current state, never "DONE" */
+ int mixed; /* Single file with key anywhere */
+} pem_load_state_t;
+
+/* init_pem_load_state - fill in initial pem_load_state structure */
+
+static void init_pem_load_state(pem_load_state_t *st, SSL_CTX *ctx, SSL *ssl,
+ const char *origin)
+{
+ st->origin = origin;
+ st->source = origin;
+ st->keysrc = 0;
+ st->pembio = 0;
+ st->ctx = ctx;
+ st->ssl = ssl;
+ st->pkey = 0;
+ st->cert = 0;
+ st->chain = 0;
+ st->keynum = 0;
+ st->objnum = 0;
+ st->state = PEM_LOAD_STATE_INIT;
+ st->mixed = 0;
+}
+
+/* use_chain - load cert, key and chain into ctx or ssl */
+
+static int use_chain(pem_load_state_t *st)
+{
+ int ret;
+ int replace = 0;
+
+ /*
+ * With replace == 0, an error is returned if the algorithm slot is
+ * already taken, and a previous key + chain of the same type would be
+ * clobbered.
+ */
+ if (st->ctx)
+ ret = SSL_CTX_use_cert_and_key(st->ctx, st->cert, st->pkey, st->chain,
+ replace);
+ else
+ ret = SSL_use_cert_and_key(st->ssl, st->cert, st->pkey, st->chain,
+ replace);
+
+ /*
+ * SSL_[CTX_]_use_cert_key() uprefs all the objects in question, so we
+ * must free ours.
+ */
+ X509_free(st->cert);
+ st->cert = 0;
+ EVP_PKEY_free(st->pkey);
+ st->pkey = 0;
+ sk_X509_pop_free(st->chain, X509_free);
+ st->chain = 0;
+
+ return ret;
+}
+
+/* load_cert - decode and load a DER-encoded X509 certificate */
+
+static void load_cert(pem_load_state_t *st, unsigned char *buf,
+ long buflen)
+{
+ const unsigned char *p = buf;
+ X509 *cert = d2i_X509(0, &p, buflen);
+
+ /*
+ * When expecting one or more keys, each key must precede the associated
+ * certificate (chain).
+ */
+ if (!st->mixed && st->state == PEM_LOAD_STATE_INIT) {
+ msg_warn("error loading chain from %s: key not first", st->source);
+ if (cert)
+ X509_free(cert);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+ if (!cert) {
+ msg_warn("error loading certificate (PEM object number %d) from %s",
+ st->objnum, st->source);
+ st->state = PEM_LOAD_STATE_FAIL;
+ return;
+ }
+ if (p - buf != buflen) {
+ msg_warn("error loading certificate (PEM object number %d) from %s:"
+ " excess data", st->objnum, st->source);
+ X509_free(cert);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+
+ /*
+ * The first certificate after a new key becomes the leaf certificate for
+ * that key. Subsequent certificates are added to the issuer chain.
+ *
+ * In "mixed" mode, the first certificate is either after the key, or else
+ * comes first.
+ */
+ switch (st->state) {
+ case PEM_LOAD_STATE_PKEY:
+ st->cert = cert;
+ st->state = st->mixed ? PEM_LOAD_STATE_BOTH : PEM_LOAD_STATE_CERT;
+ return;
+ case PEM_LOAD_STATE_INIT:
+ st->cert = cert;
+ st->state = PEM_LOAD_STATE_CERT;
+ return;
+ case PEM_LOAD_STATE_CERT:
+ case PEM_LOAD_STATE_BOTH:
+ if ((!st->chain && (st->chain = sk_X509_new_null()) == 0)
+ || !sk_X509_push(st->chain, cert)) {
+ X509_free(cert);
+ st->state = PEM_LOAD_STATE_FAIL;
+ }
+ return;
+ }
+}
+
+/* load_pkey - decode and load a DER-encoded private key */
+
+static void load_pkey(pem_load_state_t *st, int pkey_type,
+ unsigned char *buf, long buflen)
+{
+ const char *myname = "load_pkey";
+ const unsigned char *p = buf;
+ PKCS8_PRIV_KEY_INFO *p8;
+ EVP_PKEY *pkey = 0;
+
+ /*
+ * Keys are either algorithm-specific, or else (ideally) algorithm
+ * agnostic, in which case they are wrapped as PKCS#8 objects with an
+ * algorithm OID.
+ */
+ if (pkey_type != NID_undef) {
+ pkey = d2i_PrivateKey(pkey_type, 0, &p, buflen);
+ } else {
+ p8 = d2i_PKCS8_PRIV_KEY_INFO(NULL, &p, buflen);
+ if (p8) {
+ pkey = EVP_PKCS82PKEY(p8);
+ PKCS8_PRIV_KEY_INFO_free(p8);
+ }
+ }
+
+ /*
+ * Except in "mixed" mode, where a single key appears anywhere in a file
+ * with multiple certificates, a given key is either at the first object
+ * we process, or occurs after a previous key and one or more associated
+ * certificates. Thus, encountering a key in a state other than "INIT"
+ * or "CERT" is an error, except in "mixed" mode where a second key is
+ * ignored with a warning.
+ */
+ switch (st->state) {
+ case PEM_LOAD_STATE_CERT:
+
+ /*
+ * When processing the key of a "next" chain, we're in the "CERT"
+ * state, and first complete the processing of the previous chain.
+ */
+ if (!st->mixed && !use_chain(st)) {
+ msg_warn("error loading certificate chain: "
+ "key at index %d in %s does not match the certificate",
+ st->keynum, st->keysrc);
+ st->state = PEM_LOAD_STATE_FAIL;
+ return;
+ }
+ /* FALLTHROUGH */
+ case PEM_LOAD_STATE_INIT:
+
+ if (!pkey) {
+ msg_warn("error loading private key (PEM object number %d) from %s",
+ st->objnum, st->source);
+ st->state = PEM_LOAD_STATE_FAIL;
+ return;
+ }
+ /* Reject unexpected data beyond the end of the DER-encoded object */
+ if (p - buf != buflen) {
+ msg_warn("error loading private key (PEM object number %d) from"
+ " %s: excess data", st->objnum, st->source);
+ EVP_PKEY_free(pkey);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+ /* All's well, update the state */
+ st->pkey = pkey;
+ if (st->state == PEM_LOAD_STATE_INIT)
+ st->state = PEM_LOAD_STATE_PKEY;
+ else if (st->mixed)
+ st->state = PEM_LOAD_STATE_BOTH;
+ else
+ st->state = PEM_LOAD_STATE_PKEY;
+ return;
+
+ case PEM_LOAD_STATE_PKEY:
+ case PEM_LOAD_STATE_BOTH:
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ /* XXX: Legacy behavior was silent, should we stay silent? */
+ if (st->mixed) {
+ msg_warn("ignoring 2nd key at index %d in %s after 1st at %d",
+ st->objnum, st->source, st->keynum);
+ return;
+ }
+ /* else back-to-back keys */
+ msg_warn("error loading certificate chain: "
+ "key at index %d in %s not followed by a certificate",
+ st->keynum, st->keysrc);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+
+ default:
+ msg_error("%s: internal error: bad state: %d", myname, st->state);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+}
+
+/* load_pem_object - load next pkey or cert from open BIO */
+
+static int load_pem_object(pem_load_state_t *st)
+{
+ char *name = 0;
+ char *header = 0;
+ unsigned char *buf = 0;
+ long buflen;
+ int pkey_type = NID_undef;
+
+ if (!PEM_read_bio(st->pembio, &name, &header, &buf, &buflen)) {
+ if (ERR_GET_REASON(ERR_peek_last_error()) != PEM_R_NO_START_LINE)
+ return (st->state = PEM_LOAD_STATE_FAIL);
+
+ ERR_clear_error();
+ /* Clean EOF, preserve stored state for any next input file */
+ return (PEM_LOAD_STATE_DONE);
+ }
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ load_cert(st, buf, buflen);
+ } else if (strcmp(name, PEM_STRING_PKCS8INF) == 0
+ || ((pkey_type = EVP_PKEY_RSA) != NID_undef
+ && strcmp(name, PEM_STRING_RSA) == 0)
+ || ((pkey_type = EVP_PKEY_EC) != NID_undef
+ && strcmp(name, PEM_STRING_ECPRIVATEKEY) == 0)
+ || ((pkey_type = EVP_PKEY_DSA) != NID_undef
+ && strcmp(name, PEM_STRING_DSA) == 0)) {
+ load_pkey(st, pkey_type, buf, buflen);
+ } else if (!st->mixed) {
+ msg_warn("loading %s: ignoring PEM type: %s", st->source, name);
+ }
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(buf);
+ return (st->state);
+}
+
+/* load_pem_bio - load all key/certs from bio and free the bio */
+
+static int load_pem_bio(pem_load_state_t *st, int more)
+{
+ int state = st->state;
+
+ /* Don't report old news */
+ ERR_clear_error();
+
+ /*
+ * When "more" is PEM_LOAD_READ_MORE, more files will be loaded after the
+ * current file, and final processing for the last key and chain is
+ * deferred.
+ *
+ * When "more" is PEM_LOAD_READ_LAST, this is the last file in the list, and
+ * we validate the final chain.
+ *
+ * When st->mixed is true, this is the only file, and its key can occur at
+ * any location. In this case we load at most one key.
+ */
+ for (st->objnum = 1; state > PEM_LOAD_STATE_DONE; ++st->objnum) {
+ state = load_pem_object(st);
+ if ((st->mixed && st->keynum == 0 &&
+ (state == PEM_LOAD_STATE_PKEY || state == PEM_LOAD_STATE_BOTH))
+ || (!st->mixed && state == PEM_LOAD_STATE_PKEY)) {
+ /* Squirrel-away the current key location */
+ st->keynum = st->objnum;
+ st->keysrc = st->source;
+ }
+ }
+ /* We're responsible for unconditionally freeing the BIO */
+ BIO_free(st->pembio);
+
+ /* Success with current file, go back for more? */
+ if (more == PEM_LOAD_READ_MORE && state >= PEM_LOAD_STATE_DONE)
+ return 0;
+
+ /*
+ * If all is well so far, complete processing for the final chain.
+ */
+ switch (st->state) {
+ case PEM_LOAD_STATE_FAIL:
+ tls_print_errors();
+ break;
+ default:
+ break;
+ case PEM_LOAD_STATE_INIT:
+ msg_warn("No PEM data in %s", st->origin);
+ break;
+ case PEM_LOAD_STATE_PKEY:
+ msg_warn("No certs for key at index %d in %s", st->keynum, st->keysrc);
+ break;
+ case PEM_LOAD_STATE_CERT:
+ if (st->mixed) {
+ msg_warn("No private key found in %s", st->origin);
+ break;
+ }
+ /* FALLTHROUGH */
+ case PEM_LOAD_STATE_BOTH:
+ /* use_chain() frees the key and certs, and zeroes the pointers */
+ if (use_chain(st))
+ return (0);
+ msg_warn("key at index %d in %s does not match next certificate",
+ st->keynum, st->keysrc);
+ tls_print_errors();
+ break;
+ }
+ /* Free any left-over unused keys and certs */
+ EVP_PKEY_free(st->pkey);
+ X509_free(st->cert);
+ sk_X509_pop_free(st->chain, X509_free);
+
+ msg_warn("error loading private keys and certificates from: %s: %s",
+ st->origin, st->ctx ? "disabling TLS support" :
+ "aborting TLS handshake");
+ return (-1);
+}
+
+/* load_chain_files - load sequence of (key, cert, [chain]) from files */
+
+static int load_chain_files(SSL_CTX *ctx, const char *chain_files)
+{
+ pem_load_state_t st;
+ ARGV *files = argv_split(chain_files, CHARS_COMMA_SP);
+ char **filep;
+ int ret = 0;
+ int more;
+
+ init_pem_load_state(&st, ctx, 0, chain_files);
+ for (filep = files->argv; ret == 0 && *filep; ++filep) {
+ st.source = *filep;
+ if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) {
+ msg_warn("error opening chain file: %s: %m", st.source);
+ st.state = PEM_LOAD_STATE_NOGO;
+ break;
+ }
+ more = filep[1] ? PEM_LOAD_READ_MORE : PEM_LOAD_READ_LAST;
+ /* load_pem_bio() frees the BIO */
+ ret = load_pem_bio(&st, more);
+ }
+ argv_free(files);
+ return (ret);
+}
+
+/* load_mixed_file - load certs with single key anywhere in the file */
+
+static int load_mixed_file(SSL_CTX *ctx, const char *file)
+{
+ pem_load_state_t st;
+
+ init_pem_load_state(&st, ctx, 0, file);
+ if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) {
+ msg_warn("error opening chain file: %s: %m", st.source);
+ return (-1);
+ }
+ st.mixed = 1;
+ /* load_pem_bio() frees the BIO */
+ return load_pem_bio(&st, PEM_LOAD_READ_LAST);
+}
+
+/* tls_set_ca_certificate_info - load Certification Authority certificates */
+
+int tls_set_ca_certificate_info(SSL_CTX *ctx, const char *CAfile,
+ const char *CApath)
+{
+ if (*CAfile == 0)
+ CAfile = 0;
+ if (*CApath == 0)
+ CApath = 0;
+
+#define CA_PATH_FMT "%s%s%s"
+#define CA_PATH_ARGS(var, nextvar) \
+ var ? #var "=\"" : "", \
+ var ? var : "", \
+ var ? (nextvar ? "\", " : "\"") : ""
+
+ if (CAfile || CApath) {
+ if (!SSL_CTX_load_verify_locations(ctx, CAfile, CApath)) {
+ msg_info("cannot load Certification Authority data, "
+ CA_PATH_FMT CA_PATH_FMT ": disabling TLS support",
+ CA_PATH_ARGS(CAfile, CApath),
+ CA_PATH_ARGS(CApath, 0));
+ tls_print_errors();
+ return (-1);
+ }
+ if (var_tls_append_def_CA && !SSL_CTX_set_default_verify_paths(ctx)) {
+ msg_info("cannot set default OpenSSL certificate verification "
+ "paths: disabling TLS support");
+ tls_print_errors();
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+/* set_cert_stuff - specify certificate and key information */
+
+static int set_cert_stuff(SSL_CTX *ctx, const char *cert_type,
+ const char *cert_file,
+ const char *key_file)
+{
+
+ /*
+ * When the certfile and keyfile are one and the same, load both in a
+ * single pass, avoiding potential race conditions during key rollover.
+ */
+ if (strcmp(cert_file, key_file) == 0)
+ return (load_mixed_file(ctx, cert_file) == 0);
+
+ /*
+ * We need both the private key (in key_file) and the public key
+ * certificate (in cert_file).
+ *
+ * Code adapted from OpenSSL apps/s_cb.c.
+ */
+ ERR_clear_error();
+ if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) {
+ msg_warn("cannot get %s certificate from file \"%s\": "
+ "disabling TLS support", cert_type, cert_file);
+ tls_print_errors();
+ return (0);
+ }
+ if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) {
+ msg_warn("cannot get %s private key from file \"%s\": "
+ "disabling TLS support", cert_type, key_file);
+ tls_print_errors();
+ return (0);
+ }
+
+ /*
+ * Sanity check.
+ */
+ if (!SSL_CTX_check_private_key(ctx)) {
+ msg_warn("%s private key in %s does not match public key in %s: "
+ "disabling TLS support", cert_type, key_file, cert_file);
+ return (0);
+ }
+ return (1);
+}
+
+/* tls_set_my_certificate_key_info - load client or server certificates/keys */
+
+int tls_set_my_certificate_key_info(SSL_CTX *ctx, const char *chain_files,
+ const char *cert_file,
+ const char *key_file,
+ const char *dcert_file,
+ const char *dkey_file,
+ const char *eccert_file,
+ const char *eckey_file)
+{
+
+ /* The "chain_files" parameter overrides all the legacy parameters */
+ if (chain_files && *chain_files)
+ return load_chain_files(ctx, chain_files);
+
+ /*
+ * Lack of certificates is fine so long as we are prepared to use
+ * anonymous ciphers.
+ */
+ if (*cert_file && !set_cert_stuff(ctx, "RSA", cert_file, key_file))
+ return (-1); /* logged */
+ if (*dcert_file && !set_cert_stuff(ctx, "DSA", dcert_file, dkey_file))
+ return (-1); /* logged */
+#ifndef OPENSSL_NO_ECDH
+ if (*eccert_file && !set_cert_stuff(ctx, "ECDSA", eccert_file, eckey_file))
+ return (-1); /* logged */
+#else
+ if (*eccert_file)
+ msg_warn("ECDSA not supported. Ignoring ECDSA certificate file \"%s\"",
+ eccert_file);
+#endif
+ return (0);
+}
+
+/* tls_load_pem_chain - load in-memory PEM client or server chain */
+
+int tls_load_pem_chain(SSL *ssl, const char *pem, const char *origin)
+{
+ static VSTRING *obuf;
+ pem_load_state_t st;
+
+ if (!obuf)
+ obuf = vstring_alloc(100);
+ vstring_sprintf(obuf, "SNI data for %s", origin);
+ init_pem_load_state(&st, 0, ssl, vstring_str(obuf));
+
+ if ((st.pembio = BIO_new_mem_buf(pem, -1)) == NULL) {
+ msg_warn("error opening memory BIO for %s", st.origin);
+ tls_print_errors();
+ return (-1);
+ }
+ /* load_pem_bio() frees the BIO */
+ return (load_pem_bio(&st, PEM_LOAD_READ_LAST));
+}
+
+#ifdef TEST
+
+static NORETURN usage(void)
+{
+ fprintf(stderr, "usage: tls_certkey [-m] <chainfiles>\n");
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ int ch;
+ int mixed = 0;
+ int ret;
+ char *key_file = 0;
+ SSL_CTX *ctx;
+
+ if (!(ctx = SSL_CTX_new(TLS_client_method()))) {
+ tls_print_errors();
+ exit(1);
+ }
+ while ((ch = GETOPT(argc, argv, "mk:")) > 0) {
+ switch (ch) {
+ case 'k':
+ key_file = optarg;
+ break;
+ case 'm':
+ mixed = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ if (key_file)
+ ret = set_cert_stuff(ctx, "any", argv[0], key_file) == 0;
+ else if (mixed)
+ ret = load_mixed_file(ctx, argv[0]);
+ else
+ ret = load_chain_files(ctx, argv[0]);
+
+ if (ret != 0)
+ exit(1);
+
+ if (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_FIRST) != 1) {
+ fprintf(stderr, "error selecting first certificate\n");
+ tls_print_errors();
+ exit(1);
+ }
+ do {
+ STACK_OF(X509) *chain;
+ int i;
+
+ if (SSL_CTX_get0_chain_certs(ctx, &chain) != 1) {
+ fprintf(stderr, "error locating certificate chain\n");
+ tls_print_errors();
+ exit(1);
+ }
+ for (i = 0; i <= sk_X509_num(chain); ++i) {
+ char buf[CCERT_BUFSIZ];
+ X509 *cert;
+
+ if (i > 0)
+ cert = sk_X509_value(chain, i - 1);
+ else
+ cert = SSL_CTX_get0_certificate(ctx);
+
+ printf("depth = %d\n", i);
+
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
+ printf("issuer = %s\n", buf);
+
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
+ printf("subject = %s\n\n", buf);
+ }
+ } while (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_NEXT) != 0);
+
+ exit(0);
+}
+
+#endif
+
+#endif
diff --git a/src/tls/tls_client.c b/src/tls/tls_client.c
new file mode 100644
index 0000000..68f9255
--- /dev/null
+++ b/src/tls/tls_client.c
@@ -0,0 +1,1250 @@
+/*++
+/* NAME
+/* tls_client
+/* SUMMARY
+/* client-side TLS engine
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* TLS_APPL_STATE *tls_client_init(init_props)
+/* const TLS_CLIENT_INIT_PROPS *init_props;
+/*
+/* TLS_SESS_STATE *tls_client_start(start_props)
+/* const TLS_CLIENT_START_PROPS *start_props;
+/*
+/* TLS_SESS_STATE *tls_client_post_connect(TLScontext, start_props)
+/* TLS_SESS_STATE *TLScontext;
+/* const TLS_CLIENT_START_PROPS *start_props;
+/*
+/* void tls_client_stop(app_ctx, stream, failure, TLScontext)
+/* TLS_APPL_STATE *app_ctx;
+/* VSTREAM *stream;
+/* int failure;
+/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* This module is the interface between Postfix TLS clients,
+/* the OpenSSL library and the TLS entropy and cache manager.
+/*
+/* The SMTP client will attempt to verify the server hostname
+/* against the names listed in the server certificate. When
+/* a hostname match is required, the verification fails
+/* on certificate verification or hostname mis-match errors.
+/* When no hostname match is required, hostname verification
+/* failures are logged but they do not affect the TLS handshake
+/* or the SMTP session.
+/*
+/* The rules for peer name wild-card matching differ between
+/* RFC 2818 (HTTP over TLS) and RFC 2830 (LDAP over TLS), while
+/* RFC RFC3207 (SMTP over TLS) does not specify a rule at all.
+/* Postfix uses a restrictive match algorithm. One asterisk
+/* ('*') is allowed as the left-most component of a wild-card
+/* certificate name; it matches the left-most component of
+/* the peer hostname.
+/*
+/* Another area where RFCs aren't always explicit is the
+/* handling of dNSNames in peer certificates. RFC 3207 (SMTP
+/* over TLS) does not mention dNSNames. Postfix follows the
+/* strict rules in RFC 2818 (HTTP over TLS), section 3.1: The
+/* Subject Alternative Name/dNSName has precedence over
+/* CommonName. If at least one dNSName is provided, Postfix
+/* verifies those against the peer hostname and ignores the
+/* CommonName, otherwise Postfix verifies the CommonName
+/* against the peer hostname.
+/*
+/* tls_client_init() is called once when the SMTP client
+/* initializes.
+/* Certificate details are also decided during this phase,
+/* so peer-specific certificate selection is not possible.
+/*
+/* tls_client_start() activates the TLS session over an established
+/* stream. We expect that network buffers are flushed and
+/* the TLS handshake can begin immediately.
+/*
+/* tls_client_stop() sends the "close notify" alert via
+/* SSL_shutdown() to the peer and resets all connection specific
+/* TLS data. As RFC2487 does not specify a separate shutdown, it
+/* is assumed that the underlying TCP connection is shut down
+/* immediately afterwards. Any further writes to the channel will
+/* be discarded, and any further reads will report end-of-file.
+/* If the failure flag is set, no SSL_shutdown() handshake is performed.
+/*
+/* Once the TLS connection is initiated, information about the TLS
+/* state is available via the TLScontext structure:
+/* .IP TLScontext->protocol
+/* the protocol name (SSLv2, SSLv3, TLSv1),
+/* .IP TLScontext->cipher_name
+/* the cipher name (e.g. RC4/MD5),
+/* .IP TLScontext->cipher_usebits
+/* the number of bits actually used (e.g. 40),
+/* .IP TLScontext->cipher_algbits
+/* the number of bits the algorithm is based on (e.g. 128).
+/* .PP
+/* The last two values may differ from each other when export-strength
+/* encryption is used.
+/*
+/* If the peer offered a certificate, part of the certificate data are
+/* available as:
+/* .IP TLScontext->peer_status
+/* A bitmask field that records the status of the peer certificate
+/* verification. This consists of one or more of TLS_CERT_FLAG_PRESENT,
+/* TLS_CERT_FLAG_TRUSTED, TLS_CERT_FLAG_MATCHED and TLS_CERT_FLAG_SECURED.
+/* .IP TLScontext->peer_CN
+/* Extracted CommonName of the peer, or zero-length string if the
+/* information could not be extracted.
+/* .IP TLScontext->issuer_CN
+/* Extracted CommonName of the issuer, or zero-length string if the
+/* information could not be extracted.
+/* .IP TLScontext->peer_cert_fprint
+/* At the fingerprint security level, if the peer presented a certificate
+/* the fingerprint of the certificate.
+/* .PP
+/* If no peer certificate is presented the peer_status is set to 0.
+/* EVENT_DRIVEN APPLICATIONS
+/* .ad
+/* .fi
+/* Event-driven programs manage multiple I/O channels. Such
+/* programs cannot use the synchronous VSTREAM-over-TLS
+/* implementation that the TLS library historically provides,
+/* including tls_client_stop() and the underlying tls_stream(3)
+/* and tls_bio_ops(3) routines.
+/*
+/* With the current TLS library implementation, this means
+/* that an event-driven application is responsible for calling
+/* and retrying SSL_connect(), SSL_read(), SSL_write() and
+/* SSL_shutdown().
+/*
+/* To maintain control over TLS I/O, an event-driven client
+/* invokes tls_client_start() with a null VSTREAM argument and
+/* with an fd argument that specifies the I/O file descriptor.
+/* Then, tls_client_start() performs all the necessary
+/* preparations before the TLS handshake and returns a partially
+/* populated TLS context. The event-driven application is then
+/* responsible for invoking SSL_connect(), and if successful,
+/* for invoking tls_client_post_connect() to finish the work
+/* that was started by tls_client_start(). In case of unrecoverable
+/* failure, tls_client_post_connect() destroys the TLS context
+/* and returns a null pointer value.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* 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
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <argv.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <msg.h>
+#include <iostuff.h> /* non-blocking */
+#include <midna_domain.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* load_clnt_session - load session from client cache (non-callback) */
+
+static SSL_SESSION *load_clnt_session(TLS_SESS_STATE *TLScontext)
+{
+ const char *myname = "load_clnt_session";
+ SSL_SESSION *session = 0;
+ VSTRING *session_data = vstring_alloc(2048);
+
+ /*
+ * Prepare the query.
+ */
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("looking for session %s in %s cache",
+ TLScontext->serverid, TLScontext->cache_type);
+
+ /*
+ * We only get here if the cache_type is not empty. This code is not
+ * called unless caching is enabled and the cache_type is stored in the
+ * server SSL context.
+ */
+ if (TLScontext->cache_type == 0)
+ msg_panic("%s: null client session cache type in session lookup",
+ myname);
+
+ /*
+ * Look up and activate the SSL_SESSION object. Errors are non-fatal,
+ * since caching is only an optimization.
+ */
+ if (tls_mgr_lookup(TLScontext->cache_type, TLScontext->serverid,
+ session_data) == TLS_MGR_STAT_OK) {
+ session = tls_session_activate(STR(session_data), LEN(session_data));
+ if (session) {
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("reloaded session %s from %s cache",
+ TLScontext->serverid, TLScontext->cache_type);
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(session_data);
+
+ return (session);
+}
+
+/* new_client_session_cb - name new session and save it to client cache */
+
+static int new_client_session_cb(SSL *ssl, SSL_SESSION *session)
+{
+ const char *myname = "new_client_session_cb";
+ TLS_SESS_STATE *TLScontext;
+ VSTRING *session_data;
+
+ /*
+ * The cache name (if caching is enabled in tlsmgr(8)) and the cache ID
+ * string for this session are stored in the TLScontext. It cannot be
+ * null at this point.
+ */
+ if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
+ msg_panic("%s: null TLScontext in new session callback", myname);
+
+ /*
+ * We only get here if the cache_type is not empty. This callback is not
+ * set unless caching is enabled and the cache_type is stored in the
+ * server SSL context.
+ */
+ if (TLScontext->cache_type == 0)
+ msg_panic("%s: null session cache type in new session callback",
+ myname);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("save session %s to %s cache",
+ TLScontext->serverid, TLScontext->cache_type);
+
+ /*
+ * Passivate and save the session object. Errors are non-fatal, since
+ * caching is only an optimization.
+ */
+ if ((session_data = tls_session_passivate(session)) != 0) {
+ tls_mgr_update(TLScontext->cache_type, TLScontext->serverid,
+ STR(session_data), LEN(session_data));
+ vstring_free(session_data);
+ }
+
+ /*
+ * Clean up.
+ */
+ SSL_SESSION_free(session); /* 200502 */
+
+ return (1);
+}
+
+/* uncache_session - remove session from the external cache */
+
+static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
+{
+ SSL_SESSION *session = SSL_get_session(TLScontext->con);
+
+ SSL_CTX_remove_session(ctx, session);
+ if (TLScontext->cache_type == 0 || TLScontext->serverid == 0)
+ return;
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("remove session %s from client cache", TLScontext->serverid);
+
+ tls_mgr_delete(TLScontext->cache_type, TLScontext->serverid);
+}
+
+/* verify_extract_name - verify peer name and extract peer information */
+
+static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ int verbose;
+
+ verbose = TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT);
+
+ /*
+ * On exit both peer_CN and issuer_CN should be set.
+ */
+ TLScontext->issuer_CN = tls_issuer_CN(peercert, TLScontext);
+ TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext);
+
+ /*
+ * Is the certificate trust chain trusted and matched? Any required name
+ * checks are now performed internally in OpenSSL.
+ */
+ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
+ if (TLScontext->must_fail) {
+ msg_panic("%s: cert valid despite trust init failure",
+ TLScontext->namaddr);
+ } else if (TLS_MUST_MATCH(TLScontext->level)) {
+
+ /*
+ * Fully secured only if not insecure like half-dane. We use
+ * TLS_CERT_FLAG_MATCHED to satisfy policy, but
+ * TLS_CERT_FLAG_SECURED to log the effective security.
+ *
+ * Would ideally also exclude "verify" (as opposed to "secure")
+ * here, because that can be subject to insecure MX indirection,
+ * but that's rather incompatible (and not even the case with
+ * explicitly chosen non-default match patterns). Users have
+ * been warned.
+ */
+ if (!TLS_NEVER_SECURED(TLScontext->level))
+ TLScontext->peer_status |= TLS_CERT_FLAG_SECURED;
+ TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
+
+ if (verbose) {
+ const char *peername = SSL_get0_peername(TLScontext->con);
+
+ if (peername)
+ msg_info("%s: matched peername: %s",
+ TLScontext->namaddr, peername);
+ tls_dane_log(TLScontext);
+ }
+ }
+ }
+
+ /*
+ * Give them a clue. Problems with trust chain verification are logged
+ * when the session is first negotiated, before the session is stored
+ * into the cache. We don't want mystery failures, so log the fact the
+ * real problem is to be found in the past.
+ */
+ if (!TLS_CERT_IS_MATCHED(TLScontext)
+ && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+ if (TLScontext->session_reused == 0)
+ tls_log_verify_error(TLScontext);
+ else
+ msg_info("%s: re-using session with untrusted certificate, "
+ "look for details earlier in the log", props->namaddr);
+ }
+}
+
+/* add_namechecks - tell OpenSSL what names to check */
+
+static void add_namechecks(TLS_SESS_STATE *TLScontext,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ SSL *ssl = TLScontext->con;
+ int namechecks_count = 0;
+ int i;
+
+ /* RFC6125: No part-label 'foo*bar.example.com' wildcards for SMTP */
+ SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
+
+ for (i = 0; i < props->matchargv->argc; ++i) {
+ const char *name = props->matchargv->argv[i];
+ const char *aname;
+ int match_subdomain = 0;
+
+ if (strcasecmp(name, "nexthop") == 0) {
+ name = props->nexthop;
+ } else if (strcasecmp(name, "dot-nexthop") == 0) {
+ name = props->nexthop;
+ match_subdomain = 1;
+ } else if (strcasecmp(name, "hostname") == 0) {
+ name = props->host;
+ } else {
+ if (*name == '.') {
+ if (*++name == 0) {
+ msg_warn("%s: ignoring invalid match name: \".\"",
+ TLScontext->namaddr);
+ continue;
+ }
+ match_subdomain = 1;
+ }
+#ifndef NO_EAI
+ else {
+
+ /*
+ * Besides U+002E (full stop) IDNA2003 allows labels to be
+ * separated by any of the Unicode variants U+3002
+ * (ideographic full stop), U+FF0E (fullwidth full stop), and
+ * U+FF61 (halfwidth ideographic full stop). Their respective
+ * UTF-8 encodings are: E38082, EFBC8E and EFBDA1.
+ *
+ * IDNA2008 does not permit (upper) case and other variant
+ * differences in U-labels. The midna_domain_to_ascii()
+ * function, based on UTS46, normalizes such differences
+ * away.
+ *
+ * The IDNA to_ASCII conversion does not allow empty leading
+ * labels, so we handle these explicitly here.
+ */
+ unsigned char *cp = (unsigned char *) name;
+
+ if ((cp[0] == 0xe3 && cp[1] == 0x80 && cp[2] == 0x82)
+ || (cp[0] == 0xef && cp[1] == 0xbc && cp[2] == 0x8e)
+ || (cp[0] == 0xef && cp[1] == 0xbd && cp[2] == 0xa1)) {
+ if (name[3]) {
+ name = name + 3;
+ match_subdomain = 1;
+ }
+ }
+ }
+#endif
+ }
+
+ /*
+ * DNS subjectAltNames are required to be ASCII.
+ *
+ * Per RFC 6125 Section 6.4.4 Matching the CN-ID, follows the same rules
+ * (6.4.1, 6.4.2 and 6.4.3) that apply to subjectAltNames. In
+ * particular, 6.4.2 says that the reference identifier is coerced to
+ * ASCII, but no conversion is stated or implied for the CN-ID, so it
+ * seems it only matches if it is all ASCII. Otherwise, it is some
+ * other sort of name.
+ */
+#ifndef NO_EAI
+ if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", name, aname);
+ name = aname;
+ }
+#endif
+
+ if (!match_subdomain) {
+ if (SSL_add1_host(ssl, name))
+ ++namechecks_count;
+ else
+ msg_warn("%s: error loading match name: \"%s\"",
+ TLScontext->namaddr, name);
+ } else {
+ char *dot_name = concatenate(".", name, (char *) 0);
+
+ if (SSL_add1_host(ssl, dot_name))
+ ++namechecks_count;
+ else
+ msg_warn("%s: error loading match name: \"%s\"",
+ TLScontext->namaddr, dot_name);
+ myfree(dot_name);
+ }
+ }
+
+ /*
+ * If we failed to add any names, OpenSSL will perform no namechecks, so
+ * we set the "must_fail" bit to avoid verification false-positives.
+ */
+ if (namechecks_count == 0) {
+ msg_warn("%s: could not configure peer name checks",
+ TLScontext->namaddr);
+ TLScontext->must_fail = 1;
+ }
+}
+
+/* tls_auth_enable - set up TLS authentication */
+
+static int tls_auth_enable(TLS_SESS_STATE *TLScontext,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ const char *sni = 0;
+
+ if (props->sni && *props->sni) {
+#ifndef NO_EAI
+ const char *aname;
+
+#endif
+
+ /*
+ * MTA-STS policy plugin compatibility: with servername=hostname,
+ * Postfix must send the MX hostname (not CNAME expanded).
+ */
+ if (strcmp(props->sni, "hostname") == 0)
+ sni = props->host;
+ else if (strcmp(props->sni, "nexthop") == 0)
+ sni = props->nexthop;
+ else
+ sni = props->sni;
+
+ /*
+ * The SSL_set_tlsext_host_name() documentation does not promise that
+ * every implementation will convert U-label form to A-label form.
+ */
+#ifndef NO_EAI
+ if (!allascii(sni) && (aname = midna_domain_to_ascii(sni)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", sni, aname);
+ sni = aname;
+ }
+#endif
+ }
+ switch (TLScontext->level) {
+ case TLS_LEV_HALF_DANE:
+ case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
+
+ /*
+ * With DANE sessions, send an SNI hint. We don't care whether the
+ * server reports finding a matching certificate or not, so no
+ * callback is required to process the server response. Our use of
+ * SNI is limited to giving servers that make use of SNI the best
+ * opportunity to find the certificate they promised via the
+ * associated TLSA RRs.
+ *
+ * Since the hostname is DNSSEC-validated, it must be a DNS FQDN and
+ * therefore valid for use with SNI.
+ */
+ if (SSL_dane_enable(TLScontext->con, 0) <= 0) {
+ msg_warn("%s: error enabling DANE-based certificate validation",
+ TLScontext->namaddr);
+ tls_print_errors();
+ return (0);
+ }
+ /* RFC7672 Section 3.1.1 specifies no name checks for DANE-EE(3) */
+ SSL_dane_set_flags(TLScontext->con, DANE_FLAG_NO_DANE_EE_NAMECHECKS);
+
+ /* Per RFC7672 the SNI name is the TLSA base domain */
+ sni = props->dane->base_domain;
+ add_namechecks(TLScontext, props);
+ break;
+
+ case TLS_LEV_FPRINT:
+ /* Synthetic DANE for fingerprint security */
+ if (SSL_dane_enable(TLScontext->con, 0) <= 0) {
+ msg_warn("%s: error enabling fingerprint certificate validation",
+ props->namaddr);
+ tls_print_errors();
+ return (0);
+ }
+ SSL_dane_set_flags(TLScontext->con, DANE_FLAG_NO_DANE_EE_NAMECHECKS);
+ break;
+
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ if (TLScontext->dane != 0 && TLScontext->dane->tlsa != 0) {
+ /* Synthetic DANE for per-destination trust-anchors */
+ if (SSL_dane_enable(TLScontext->con, NULL) <= 0) {
+ msg_warn("%s: error configuring local trust anchors",
+ props->namaddr);
+ tls_print_errors();
+ return (0);
+ }
+ }
+ add_namechecks(TLScontext, props);
+ break;
+ default:
+ break;
+ }
+
+ if (sni) {
+ if (strlen(sni) > TLSEXT_MAXLEN_host_name) {
+ msg_warn("%s: ignoring too long SNI hostname: %.100s",
+ props->namaddr, sni);
+ return (0);
+ }
+
+ /*
+ * Failure to set a valid SNI hostname is a memory allocation error,
+ * and thus transient. Since we must not cache the session if we
+ * failed to send the SNI name, we have little choice but to abort.
+ */
+ if (!SSL_set_tlsext_host_name(TLScontext->con, sni)) {
+ msg_warn("%s: error setting SNI hostname to: %s", props->namaddr,
+ sni);
+ return (0);
+ }
+
+ /*
+ * The saved value is not presently used client-side, but could later
+ * be logged if acked by the server (requires new client-side
+ * callback to detect the ack). For now this just maintains symmetry
+ * with the server code, where do record the received SNI for
+ * logging.
+ */
+ TLScontext->peer_sni = mystrdup(sni);
+ if (TLScontext->log_mask & TLS_LOG_DEBUG)
+ msg_info("%s: SNI hostname: %s", props->namaddr, sni);
+ }
+ return (1);
+}
+
+/* tls_client_init - initialize client-side TLS engine */
+
+TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *props)
+{
+ SSL_CTX *client_ctx;
+ TLS_APPL_STATE *app_ctx;
+ const EVP_MD *fpt_alg;
+ long off = 0;
+ int cachable;
+ int scache_timeout;
+ int log_mask;
+
+ /*
+ * Convert user loglevel to internal logmask.
+ */
+ log_mask = tls_log_mask(props->log_param, props->log_level);
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("initializing the client-side TLS engine");
+
+ /*
+ * Load (mostly cipher related) TLS-library internal main.cf parameters.
+ */
+ tls_param_init();
+
+ /*
+ * Detect mismatch between compile-time headers and run-time library.
+ */
+ tls_check_version();
+
+ /*
+ * Initialize the OpenSSL library, possibly loading its configuration
+ * file.
+ */
+ if (tls_library_init() == 0)
+ return (0);
+
+ /*
+ * Create an application data index for SSL objects, so that we can
+ * attach TLScontext information; this information is needed inside
+ * tls_verify_certificate_callback().
+ */
+ if (TLScontext_index < 0) {
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) {
+ msg_warn("Cannot allocate SSL application data index: "
+ "disabling TLS support");
+ return (0);
+ }
+ }
+
+ /*
+ * If the administrator specifies an unsupported digest algorithm, fail
+ * now, rather than in the middle of a TLS handshake.
+ */
+ if ((fpt_alg = tls_validate_digest(props->mdalg)) == 0) {
+ msg_warn("disabling TLS support");
+ return (0);
+ }
+
+ /*
+ * Initialize the PRNG (Pseudo Random Number Generator) with some seed
+ * from external and internal sources. Don't enable TLS without some real
+ * entropy.
+ */
+ if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) {
+ msg_warn("no entropy for TLS key generation: disabling TLS support");
+ return (0);
+ }
+ tls_int_seed();
+
+ /*
+ * The SSL/TLS specifications require the client to send a message in the
+ * oldest specification it understands with the highest level it
+ * understands in the message. RFC2487 is only specified for TLSv1, but
+ * we want to be as compatible as possible, so we will start off with a
+ * SSLv2 greeting allowing the best we can offer: TLSv1. We can restrict
+ * this with the options setting later, anyhow.
+ */
+ ERR_clear_error();
+ client_ctx = SSL_CTX_new(TLS_client_method());
+ if (client_ctx == 0) {
+ msg_warn("cannot allocate client SSL_CTX: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+#ifdef SSL_SECOP_PEER
+ /* Backwards compatible security as a base for opportunistic TLS. */
+ SSL_CTX_set_security_level(client_ctx, 0);
+#endif
+
+ /*
+ * See the verify callback in tls_verify.c
+ */
+ SSL_CTX_set_verify_depth(client_ctx, props->verifydepth + 1);
+
+ /*
+ * This is a prerequisite for enabling DANE support in OpenSSL, but not a
+ * commitment to use DANE, thus suitable for both DANE and non-DANE TLS
+ * connections. Indeed we need this not just for DANE, but aslo for
+ * fingerprint and "tafile" support. Since it just allocates memory, it
+ * should never fail except when we're likely to fail anyway. Rather
+ * than try to run with crippled TLS support, just give up using TLS.
+ */
+ if (SSL_CTX_dane_enable(client_ctx) <= 0) {
+ msg_warn("OpenSSL DANE initialization failed: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+ tls_dane_digest_init(client_ctx, fpt_alg);
+
+ /*
+ * Presently we use TLS only with SMTP where truncation attacks are not
+ * possible as a result of application framing. If we ever use TLS in
+ * some other application protocol where truncation could be relevant,
+ * we'd need to disable truncation detection conditionally, or explicitly
+ * clear the option in that code path.
+ */
+ off |= SSL_OP_IGNORE_UNEXPECTED_EOF;
+
+ /*
+ * Protocol selection is destination dependent, so we delay the protocol
+ * selection options to the per-session SSL object.
+ */
+ off |= tls_bug_bits();
+ SSL_CTX_set_options(client_ctx, off);
+
+ /*
+ * Set the call-back routine for verbose logging.
+ */
+ if (log_mask & TLS_LOG_DEBUG)
+ SSL_CTX_set_info_callback(client_ctx, tls_info_callback);
+
+ /*
+ * Load the CA public key certificates for both the client cert and for
+ * the verification of server certificates. As provided by OpenSSL we
+ * support two types of CA certificate handling: One possibility is to
+ * add all CA certificates to one large CAfile, the other possibility is
+ * a directory pointed to by CApath, containing separate files for each
+ * CA with softlinks named after the hash values of the certificate. The
+ * first alternative has the advantage that the file is opened and read
+ * at startup time, so that you don't have the hassle to maintain another
+ * copy of the CApath directory for chroot-jail.
+ */
+ if (tls_set_ca_certificate_info(client_ctx,
+ props->CAfile, props->CApath) < 0) {
+ /* tls_set_ca_certificate_info() already logs a warning. */
+ SSL_CTX_free(client_ctx); /* 200411 */
+ return (0);
+ }
+
+ /*
+ * We do not need a client certificate, so the certificates are only
+ * loaded (and checked) if supplied. A clever client would handle
+ * multiple client certificates and decide based on the list of
+ * acceptable CAs, sent by the server, which certificate to submit.
+ * OpenSSL does however not do this and also has no call-back hooks to
+ * easily implement it.
+ *
+ * Load the client public key certificate and private key from file and
+ * check whether the cert matches the key. We can use RSA certificates
+ * ("cert") DSA certificates ("dcert") or ECDSA certificates ("eccert").
+ * All three can be made available at the same time. The CA certificates
+ * for all three are handled in the same setup already finished. Which
+ * one is used depends on the cipher negotiated (that is: the first
+ * cipher listed by the client which does match the server). The client
+ * certificate is presented after the server chooses the session cipher,
+ * so we will just present the right cert for the chosen cipher (if it
+ * uses certificates).
+ */
+ if (tls_set_my_certificate_key_info(client_ctx,
+ props->chain_files,
+ props->cert_file,
+ props->key_file,
+ props->dcert_file,
+ props->dkey_file,
+ props->eccert_file,
+ props->eckey_file) < 0) {
+ /* tls_set_my_certificate_key_info() already logs a warning. */
+ SSL_CTX_free(client_ctx); /* 200411 */
+ return (0);
+ }
+
+ /*
+ * With OpenSSL 1.0.2 and later the client EECDH curve list becomes
+ * configurable with the preferred curve negotiated via the supported
+ * curves extension.
+ */
+ tls_auto_eecdh_curves(client_ctx, var_tls_eecdh_auto);
+
+ /*
+ * Finally, the setup for the server certificate checking, done "by the
+ * book".
+ */
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE,
+ tls_verify_certificate_callback);
+
+ /*
+ * Initialize the session cache.
+ *
+ * Since the client does not search an internal cache, we simply disable it.
+ * It is only useful for expiring old sessions, but we do that in the
+ * tlsmgr(8).
+ *
+ * This makes SSL_CTX_remove_session() not useful for flushing broken
+ * sessions from the external cache, so we must delete them directly (not
+ * via a callback).
+ */
+ if (tls_mgr_policy(props->cache_type, &cachable,
+ &scache_timeout) != TLS_MGR_STAT_OK)
+ scache_timeout = 0;
+ if (scache_timeout <= 0)
+ cachable = 0;
+
+ /*
+ * Allocate an application context, and populate with mandatory protocol
+ * and cipher data.
+ */
+ app_ctx = tls_alloc_app_context(client_ctx, 0, log_mask);
+
+ /*
+ * The external session cache is implemented by the tlsmgr(8) process.
+ */
+ if (cachable) {
+
+ app_ctx->cache_type = mystrdup(props->cache_type);
+
+ /*
+ * OpenSSL does not use callbacks to load sessions from a client
+ * cache, so we must invoke that function directly. Apparently,
+ * OpenSSL does not provide a way to pass session names from here to
+ * call-back routines that do session lookup.
+ *
+ * OpenSSL can, however, automatically save newly created sessions for
+ * us by callback (we create the session name in the call-back
+ * function).
+ *
+ * XXX gcc 2.95 can't compile #ifdef .. #endif in the expansion of
+ * SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE |
+ * SSL_SESS_CACHE_NO_AUTO_CLEAR.
+ */
+#ifndef SSL_SESS_CACHE_NO_INTERNAL_STORE
+#define SSL_SESS_CACHE_NO_INTERNAL_STORE 0
+#endif
+
+ SSL_CTX_set_session_cache_mode(client_ctx,
+ SSL_SESS_CACHE_CLIENT |
+ SSL_SESS_CACHE_NO_INTERNAL_STORE |
+ SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ SSL_CTX_sess_set_new_cb(client_ctx, new_client_session_cb);
+
+ /*
+ * OpenSSL ignores timed-out sessions. We need to set the internal
+ * cache timeout at least as high as the external cache timeout. This
+ * applies even if no internal cache is used. We set the session to
+ * twice the cache lifetime. This way a session always lasts longer
+ * than its lifetime in the cache.
+ */
+ SSL_CTX_set_timeout(client_ctx, 2 * scache_timeout);
+ }
+ return (app_ctx);
+}
+
+ /*
+ * This is the actual startup routine for the connection. We expect that the
+ * buffers are flushed and the "220 Ready to start TLS" was received by us,
+ * so that we can immediately start the TLS handshake process.
+ */
+TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props)
+{
+ int sts;
+ int protomask;
+ int min_proto;
+ int max_proto;
+ const char *cipher_list;
+ SSL_SESSION *session = 0;
+ TLS_SESS_STATE *TLScontext;
+ TLS_APPL_STATE *app_ctx = props->ctx;
+ int log_mask = app_ctx->log_mask;
+
+ /*
+ * When certificate verification is required, log trust chain validation
+ * errors even when disabled by default for opportunistic sessions. For
+ * DANE this only applies when using trust-anchor associations.
+ */
+ if (TLS_MUST_MATCH(props->tls_level))
+ log_mask |= TLS_LOG_UNTRUSTED;
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("setting up TLS connection to %s", props->namaddr);
+
+ /*
+ * First make sure we have valid protocol and cipher parameters
+ *
+ * Per-session protocol restrictions must be applied to the SSL connection,
+ * as restrictions in the global context cannot be cleared.
+ */
+ protomask = tls_proto_mask_lims(props->protocols, &min_proto, &max_proto);
+ if (protomask == TLS_PROTOCOL_INVALID) {
+ /* tls_protocol_mask() logs no warning. */
+ msg_warn("%s: Invalid TLS protocol list \"%s\": aborting TLS session",
+ props->namaddr, props->protocols);
+ return (0);
+ }
+
+ /*
+ * Though RFC7672 set the floor at SSLv3, we really can and should
+ * require TLS 1.0, since e.g. we send SNI, which is a TLS 1.0 extension.
+ * No DANE domains have been observed to support only SSLv3.
+ *
+ * XXX: Would be nice to make that TLS 1.2 at some point. Users can choose
+ * to exclude TLS 1.0 and TLS 1.1 if they find they don't run into any
+ * problems doing that.
+ */
+ if (TLS_DANE_BASED(props->tls_level))
+ protomask |= TLS_PROTOCOL_SSLv2 | TLS_PROTOCOL_SSLv3;
+
+ /*
+ * Allocate a new TLScontext for the new connection and get an SSL
+ * structure. Add the location of TLScontext to the SSL to later retrieve
+ * the information inside the tls_verify_certificate_callback().
+ *
+ * If session caching was enabled when TLS was initialized, the cache type
+ * is stored in the client SSL context.
+ */
+ TLScontext = tls_alloc_sess_context(log_mask, props->namaddr);
+ TLScontext->cache_type = app_ctx->cache_type;
+ TLScontext->level = props->tls_level;
+
+ if ((TLScontext->con = SSL_new(app_ctx->ssl_ctx)) == NULL) {
+ msg_warn("Could not allocate 'TLScontext->con' with SSL_new()");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * Per session cipher selection for sessions with mandatory encryption
+ *
+ * The cipherlist is applied to the global SSL context, since it is likely
+ * to stay the same between connections, so we make use of a 1-element
+ * cache to return the same result for identical inputs.
+ */
+ cipher_list = tls_set_ciphers(TLScontext, props->cipher_grade,
+ props->cipher_exclusions);
+ if (cipher_list == 0) {
+ /* already warned */
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list);
+
+ TLScontext->stream = props->stream;
+ TLScontext->mdalg = props->mdalg;
+
+ /* Alias DANE digest info from props */
+ TLScontext->dane = props->dane;
+
+ if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
+ msg_warn("Could not set application data for 'TLScontext->con'");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+#define CARP_VERSION(which) do { \
+ if (which##_proto != 0) \
+ msg_warn("%s: error setting %simum TLS version to: 0x%04x", \
+ TLScontext->namaddr, #which, which##_proto); \
+ else \
+ msg_warn("%s: error clearing %simum TLS version", \
+ TLScontext->namaddr, #which); \
+ } while (0)
+
+ /*
+ * Apply session protocol restrictions.
+ */
+ if (protomask != 0)
+ SSL_set_options(TLScontext->con, TLS_SSL_OP_PROTOMASK(protomask));
+ if (!SSL_set_min_proto_version(TLScontext->con, min_proto))
+ CARP_VERSION(min);
+ if (!SSL_set_max_proto_version(TLScontext->con, max_proto))
+ CARP_VERSION(max);
+
+ /*
+ * When applicable, configure DNS-based or synthetic (fingerprint or
+ * local trust anchor) DANE authentication, enable an appropriate SNI
+ * name and peer name matching.
+ *
+ * NOTE, this can change the effective security level, and needs to happen
+ * early.
+ */
+ if (!tls_auth_enable(TLScontext, props)) {
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * Try to convey the configured TLSA records for this connection to the
+ * OpenSSL library. If none are "usable", we'll fall back to "encrypt"
+ * when authentication is not mandatory, otherwise we must arrange to
+ * ensure authentication failure.
+ */
+ if (TLScontext->dane && TLScontext->dane->tlsa) {
+ int usable = tls_dane_enable(TLScontext);
+ int must_fail = usable <= 0;
+
+ if (usable == 0) {
+ switch (TLScontext->level) {
+ case TLS_LEV_HALF_DANE:
+ case TLS_LEV_DANE:
+ msg_warn("%s: all TLSA records unusable, fallback to "
+ "unauthenticated TLS", TLScontext->namaddr);
+ must_fail = 0;
+ TLScontext->level = TLS_LEV_ENCRYPT;
+ break;
+
+ case TLS_LEV_FPRINT:
+ msg_warn("%s: all fingerprints unusable", TLScontext->namaddr);
+ break;
+ case TLS_LEV_DANE_ONLY:
+ msg_warn("%s: all TLSA records unusable", TLScontext->namaddr);
+ break;
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ msg_warn("%s: all trust anchors unusable", TLScontext->namaddr);
+ break;
+ }
+ }
+ TLScontext->must_fail |= must_fail;
+ }
+
+ /*
+ * We compute the policy digest after we compute the SNI name in
+ * tls_auth_enable() and possibly update the TLScontext security level.
+ *
+ * OpenSSL will ignore cached sessions that use the wrong protocol. So we do
+ * not need to filter out cached sessions with the "wrong" protocol,
+ * rather OpenSSL will simply negotiate a new session.
+ *
+ * We salt the session lookup key with the protocol list, so that sessions
+ * found in the cache are plausibly acceptable.
+ *
+ * By the time a TLS client is negotiating ciphers it has already offered to
+ * re-use a session, it is too late to renege on the offer. So we must
+ * not attempt to re-use sessions whose ciphers are too weak. We salt the
+ * session lookup key with the cipher list, so that sessions found in the
+ * cache are always acceptable.
+ *
+ * With DANE, (more generally any TLScontext where we specified explicit
+ * trust-anchor or end-entity certificates) the verification status of
+ * the SSL session depends on the specified list. Since we verify the
+ * certificate only during the initial handshake, we must segregate
+ * sessions with different TA lists. Note, that TA re-verification is
+ * not possible with cached sessions, since these don't hold the complete
+ * peer trust chain. Therefore, we compute a digest of the sorted TA
+ * parameters and append it to the serverid.
+ */
+ TLScontext->serverid =
+ tls_serverid_digest(TLScontext, props, cipher_list);
+
+ /*
+ * When authenticating the peer, use 80-bit plus OpenSSL security level
+ *
+ * XXX: We should perhaps use security level 1 also for mandatory
+ * encryption, with only "may" tolerating weaker algorithms. But that
+ * could mean no TLS 1.0 with OpenSSL >= 3.0 and encrypt, unless I get my
+ * patch in on time to conditionally re-enable SHA1 at security level 1,
+ * and we add code to make it so.
+ *
+ * That said, with "encrypt", we could reasonably require TLS 1.2?
+ */
+ if (TLS_MUST_MATCH(TLScontext->level))
+ SSL_set_security_level(TLScontext->con, 1);
+
+ /*
+ * XXX To avoid memory leaks we must always call SSL_SESSION_free() after
+ * calling SSL_set_session(), regardless of whether or not the session
+ * will be reused.
+ */
+ if (TLScontext->cache_type) {
+ session = load_clnt_session(TLScontext);
+ if (session) {
+ SSL_set_session(TLScontext->con, session);
+ SSL_SESSION_free(session); /* 200411 */
+ }
+ }
+
+ /*
+ * Before really starting anything, try to seed the PRNG a little bit
+ * more.
+ */
+ tls_int_seed();
+ (void) tls_ext_seed(var_tls_daemon_rand_bytes);
+
+ /*
+ * Connect the SSL connection with the network socket.
+ */
+ if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd :
+ vstream_fileno(props->stream)) != 1) {
+ msg_info("SSL_set_fd error to %s", props->namaddr);
+ tls_print_errors();
+ uncache_session(app_ctx->ssl_ctx, TLScontext);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * If the debug level selected is high enough, all of the data is dumped:
+ * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will
+ * dump everything.
+ *
+ * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
+ * Well there is a BIO below the SSL routines that is automatically
+ * created for us, so we can use it for debugging purposes.
+ */
+ if (log_mask & TLS_LOG_TLSPKTS)
+ tls_set_bio_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb);
+
+ /*
+ * If we don't trigger the handshake in the library, leave control over
+ * SSL_connect/read/write/etc with the application.
+ */
+ if (props->stream == 0)
+ return (TLScontext);
+
+ /*
+ * Turn on non-blocking I/O so that we can enforce timeouts on network
+ * I/O.
+ */
+ non_blocking(vstream_fileno(props->stream), NON_BLOCKING);
+
+ /*
+ * Start TLS negotiations. This process is a black box that invokes our
+ * call-backs for certificate verification.
+ *
+ * Error handling: If the SSL handshake fails, we print out an error message
+ * and remove all TLS state concerning this session.
+ */
+ sts = tls_bio_connect(vstream_fileno(props->stream), props->timeout,
+ TLScontext);
+ if (sts <= 0) {
+ if (ERR_peek_error() != 0) {
+ msg_info("SSL_connect error to %s: %d", props->namaddr, sts);
+ tls_print_errors();
+ } else if (errno != 0) {
+ msg_info("SSL_connect error to %s: %m", props->namaddr);
+ } else {
+ msg_info("SSL_connect error to %s: lost connection",
+ props->namaddr);
+ }
+ uncache_session(app_ctx->ssl_ctx, TLScontext);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ return (tls_client_post_connect(TLScontext, props));
+}
+
+/* tls_client_post_connect - post-handshake processing */
+
+TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *TLScontext,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ const SSL_CIPHER *cipher;
+ X509 *peercert;
+
+ /* Turn off packet dump if only dumping the handshake */
+ if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0)
+ tls_set_bio_callback(SSL_get_rbio(TLScontext->con), 0);
+
+ /*
+ * The caller may want to know if this session was reused or if a new
+ * session was negotiated.
+ */
+ TLScontext->session_reused = SSL_session_reused(TLScontext->con);
+ if ((TLScontext->log_mask & TLS_LOG_CACHE) && TLScontext->session_reused)
+ msg_info("%s: Reusing old session", TLScontext->namaddr);
+
+ /*
+ * Do peername verification if requested and extract useful information
+ * from the certificate for later use.
+ */
+ if ((peercert = TLS_PEEK_PEER_CERT(TLScontext->con)) != 0) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+
+ /*
+ * Peer name or fingerprint verification as requested.
+ * Unconditionally set peer_CN, issuer_CN and peer_cert_fprint. Check
+ * fingerprint first, and avoid logging verified as untrusted in the
+ * call to verify_extract_name().
+ */
+ TLScontext->peer_cert_fprint = tls_cert_fprint(peercert, props->mdalg);
+ TLScontext->peer_pkey_fprint = tls_pkey_fprint(peercert, props->mdalg);
+ verify_extract_name(TLScontext, peercert, props);
+
+ if (TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ msg_info("%s: subject_CN=%s, issuer_CN=%s, "
+ "fingerprint=%s, pkey_fingerprint=%s", props->namaddr,
+ TLScontext->peer_CN, TLScontext->issuer_CN,
+ TLScontext->peer_cert_fprint,
+ TLScontext->peer_pkey_fprint);
+ } else {
+ TLScontext->issuer_CN = mystrdup("");
+ TLScontext->peer_CN = mystrdup("");
+ TLScontext->peer_cert_fprint = mystrdup("");
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ }
+
+ /*
+ * Finally, collect information about protocol and cipher for logging
+ */
+ TLScontext->protocol = SSL_get_version(TLScontext->con);
+ cipher = SSL_get_current_cipher(TLScontext->con);
+ TLScontext->cipher_name = SSL_CIPHER_get_name(cipher);
+ TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher,
+ &(TLScontext->cipher_algbits));
+
+ /*
+ * The TLS engine is active. Switch to the tls_timed_read/write()
+ * functions and make the TLScontext available to those functions.
+ */
+ if (TLScontext->stream != 0)
+ tls_stream_start(props->stream, TLScontext);
+
+ /*
+ * With the handshake done, extract TLS 1.3 signature metadata.
+ */
+ tls_get_signature_params(TLScontext);
+
+ if (TLScontext->log_mask & TLS_LOG_SUMMARY)
+ tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW, TLScontext);
+
+ tls_int_seed();
+
+ return (TLScontext);
+}
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_dane.c b/src/tls/tls_dane.c
new file mode 100644
index 0000000..a2b9b80
--- /dev/null
+++ b/src/tls/tls_dane.c
@@ -0,0 +1,1357 @@
+/*++
+/* NAME
+/* tls_dane 3
+/* SUMMARY
+/* Support for RFC 6698, 7671, 7672 (DANE) certificate matching
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* void tls_dane_loglevel(log_param, log_level);
+/* const char *log_param;
+/* const char *log_level;
+/*
+/* int tls_dane_avail()
+/*
+/* void tls_dane_flush()
+/*
+/* TLS_DANE *tls_dane_alloc()
+/*
+/* void tls_tlsa_free(tlsa)
+/* TLS_TLSA *tlsa;
+/*
+/* void tls_dane_free(dane)
+/* TLS_DANE *dane;
+/*
+/* void tls_dane_add_fpt_digests(dane, digest, delim, smtp_mode)
+/* TLS_DANE *dane;
+/* const char *digest;
+/* const char *delim;
+/* int smtp_mode;
+/*
+/* TLS_TLSA *tlsa_prepend(tlsa, usage, selector, mtype, data, len)
+/* TLS_TLSA *tlsa;
+/* uint8_t usage;
+/* uint8_t selector;
+/* uint8_t mtype;
+/* const unsigned char *data;
+/* uint16_t length;
+/*
+/* int tls_dane_load_trustfile(dane, tafile)
+/* TLS_DANE *dane;
+/* const char *tafile;
+/*
+/* TLS_DANE *tls_dane_resolve(port, proto, hostrr, forcetlsa)
+/* unsigned port;
+/* const char *proto;
+/* DNS_RR *hostrr;
+/* int forcetlsa;
+/*
+/* void tls_dane_digest_init(ctx, fpt_alg)
+/* SSL_CTX *ctx;
+/* const EVP_MD *fpt_alg;
+/*
+/* void tls_dane_enable(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_dane_log(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* int tls_dane_unusable(dane)
+/* const TLS_DANE *dane;
+/*
+/* int tls_dane_notfound(dane)
+/* const TLS_DANE *dane;
+/* DESCRIPTION
+/* tls_dane_loglevel() allows the policy lookup functions in the DANE
+/* library to examine the application's TLS loglevel in and possibly
+/* produce a more detailed activity log.
+/*
+/* tls_dane_avail() returns true if the features required to support DANE
+/* are present in libresolv.
+/*
+/* tls_dane_flush() flushes all entries from the cache, and deletes
+/* the cache.
+/*
+/* tls_dane_alloc() returns a pointer to a newly allocated TLS_DANE
+/* structure with null ta and ee digest sublists.
+/*
+/* tls_tlsa_free() frees a TLSA record linked list.
+/*
+/* tls_dane_free() frees the structure allocated by tls_dane_alloc().
+/*
+/* tls_dane_digest_init() configures OpenSSL to support the configured
+/* DANE TLSA digests and private-use fingerprint digest.
+/*
+/* tlsa_prepend() prepends a TLSA record to the head of a linked list
+/* which may be null when the list is empty. The result value is the
+/* new list head.
+/*
+/* tls_dane_add_fpt_digests() splits "digest" using the characters in
+/* "delim" as delimiters and generates corresponding synthetic DANE TLSA
+/* records with matching type 255 (private-use), which we associated with
+/* the configured fingerprint digest algorithm. This is an incremental
+/* interface, that builds a TLS_DANE structure outside the cache by
+/* manually adding entries.
+/*
+/* tls_dane_load_trustfile() imports trust-anchor certificates and
+/* public keys from a file (rather than DNS TLSA records).
+/*
+/* tls_dane_resolve() maps a (port, protocol, hostrr) tuple to a
+/* corresponding TLS_DANE policy structure found in the DNS. The port
+/* argument is in network byte order. A null pointer is returned when
+/* the DNS query for the TLSA record tempfailed. In all other cases the
+/* return value is a pointer to the corresponding TLS_DANE structure.
+/* The caller must free the structure via tls_dane_free().
+/*
+/* tls_dane_enable() enables DANE-style certificate checks for connections
+/* that are configured with TLSA records. The TLSA records may be from
+/* DNS (at the "dane", "dane-only" and "half-dane" security levels), or be
+/* synthetic in support of either the "fingerprint" level or local trust
+/* anchor based validation with the "secure" and "verify" levels. The
+/* return value is the number of "usable" TLSA records loaded, or negative
+/* if a record failed to load due to an internal OpenSSL problems, rather
+/* than an issue with the record making that record "unusable".
+/*
+/* tls_dane_log() logs successful verification via DNS-based or
+/* synthetic DANE TLSA RRs (fingerprint or "tafile").
+/*
+/* tls_dane_unusable() checks whether a cached TLS_DANE record is
+/* the result of a validated RRset, with no usable elements. In
+/* this case, TLS is mandatory, but certificate verification is
+/* not DANE-based.
+/*
+/* tls_dane_notfound() checks whether a cached TLS_DANE record is
+/* the result of a validated DNS lookup returning NODATA. In
+/* this case, TLS is not required by RFC, though users may elect
+/* a mandatory TLS fallback policy.
+/*
+/* Arguments:
+/* .IP ctx
+/* SSL context to be configured with the chosen digest algorithms.
+/* .IP fpt_alg
+/* The OpenSSL EVP digest algorithm handle for the fingerprint digest.
+/* .IP tlsa
+/* TLSA record linked list head, initially NULL.
+/* .IP usage
+/* DANE TLSA certificate usage field.
+/* .IP selector
+/* DANE TLSA selector field.
+/* .IP mtype
+/* DANE TLSA matching type field
+/* .IP data
+/* DANE TLSA associated data field (raw binary form), copied for internal
+/* use. The caller is responsible for freeing his own copy.
+/* .IP length
+/* Length of DANE TLSA associated DATA field.
+/* .IP dane
+/* Pointer to a TLS_DANE structure that lists the valid trust-anchor
+/* and end-entity full-certificate and/or public-key digests.
+/* .IP port
+/* The TCP port in network byte order.
+/* .IP proto
+/* Almost certainly "tcp".
+/* .IP hostrr
+/* DNS_RR pointer to TLSA base domain data.
+/* .IP forcetlsa
+/* When true, TLSA lookups are performed even when the qname and rname
+/* are insecure. This is only useful in the unlikely case that DLV is
+/* used to secure the TLSA RRset in an otherwise insecure zone.
+/* .IP log_param
+/* The TLS log level parameter name whose value is the log_level argument.
+/* .IP log_level
+/* The application TLS log level, which may affect dane lookup verbosity.
+/* .IP digest
+/* The digest (or list of digests concatenated with characters from
+/* "delim") to be added to the TLS_DANE record.
+/* .IP delim
+/* The set of delimiter characters used above.
+/* .IP smtp_mode
+/* Is the caller an SMTP client or an LMTP client?
+/* .IP tafile;
+/* A file with trust anchor certificates or public keys in PEM format.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his 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
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <midna_domain.h>
+#include <vstring.h>
+#include <events.h> /* event_time() */
+#include <timecmp.h>
+#include <ctable.h>
+#include <hex_code.h>
+#include <safe_ultostr.h>
+#include <split_at.h>
+#include <name_code.h>
+
+#define STR(x) vstring_str(x)
+
+/* Global library */
+
+#include <mail_params.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#undef DANE_TLSA_SUPPORT
+
+#if RES_USE_DNSSEC && RES_USE_EDNS0
+#define DANE_TLSA_SUPPORT
+static int dane_tlsa_support = 1;
+
+#else
+static int dane_tlsa_support = 0;
+
+#endif
+
+/*
+ * A NULL alg field disables the algorithm at the codepoint passed to the
+ * SSL_CTX_dane_mtype_set(3) function. The ordinals are used for digest
+ * agility, higher is "better" (presumed stronger).
+ */
+typedef struct dane_mtype {
+ const EVP_MD *alg;
+ uint8_t ord;
+} dane_mtype;
+
+/*
+ * This is not intended to be a long-term cache of pre-parsed TLSA data,
+ * rather we primarily want to avoid fetching and parsing the TLSA records
+ * for a single multi-homed MX host more than once per delivery. Therefore,
+ * we keep the table reasonably small.
+ */
+#define CACHE_SIZE 20
+static CTABLE *dane_cache;
+
+static int log_mask;
+
+/* tls_dane_logmask - configure policy lookup logging */
+
+void tls_dane_loglevel(const char *log_param, const char *log_level)
+{
+ log_mask = tls_log_mask(log_param, log_level);
+}
+
+/* tls_dane_avail - check for availability of dane required digests */
+
+int tls_dane_avail(void)
+{
+ return (dane_tlsa_support);
+}
+
+/* tls_dane_alloc - allocate a TLS_DANE structure */
+
+TLS_DANE *tls_dane_alloc(void)
+{
+ TLS_DANE *dane = (TLS_DANE *) mymalloc(sizeof(*dane));
+
+ dane->tlsa = 0;
+ dane->base_domain = 0;
+ dane->flags = 0;
+ dane->expires = 0;
+ dane->refs = 1;
+ return (dane);
+}
+
+/* tls_tlsa_free - free a TLSA RR linked list */
+
+void tls_tlsa_free(TLS_TLSA *tlsa)
+{
+ TLS_TLSA *next;
+
+ for (; tlsa; tlsa = next) {
+ next = tlsa->next;
+ myfree(tlsa->data);
+ myfree(tlsa);
+ }
+}
+
+/* tls_dane_free - free a TLS_DANE structure */
+
+void tls_dane_free(TLS_DANE *dane)
+{
+ if (--dane->refs > 0)
+ return;
+ if (dane->base_domain)
+ myfree(dane->base_domain);
+ if (dane->tlsa)
+ tls_tlsa_free(dane->tlsa);
+ myfree((void *) dane);
+}
+
+/* tlsa_prepend - Prepend internal-form TLSA record to the RRset linked list */
+
+TLS_TLSA *tlsa_prepend(TLS_TLSA *tlsa, uint8_t usage, uint8_t selector,
+ uint8_t mtype, const unsigned char *data,
+ uint16_t data_len)
+{
+ TLS_TLSA *head;
+
+ head = (TLS_TLSA *) mymalloc(sizeof(*head));
+ head->usage = usage;
+ head->selector = selector;
+ head->mtype = mtype;
+ head->length = data_len;
+ head->data = (unsigned char *) mymemdup(data, data_len);
+ head->next = tlsa;
+ return (head);
+}
+
+#define MAX_HEAD_BYTES 32
+#define MAX_TAIL_BYTES 32
+#define MAX_DUMP_BYTES (MAX_HEAD_BYTES + MAX_TAIL_BYTES)
+
+/* tlsa_info - log import of a particular TLSA record */
+
+static void tlsa_info(const char *tag, const char *msg,
+ uint8_t u, uint8_t s, uint8_t m,
+ const unsigned char *data, ssize_t dlen)
+{
+ static VSTRING *top;
+ static VSTRING *bot;
+
+ if (top == 0)
+ top = vstring_alloc(2 * MAX_HEAD_BYTES);
+ if (bot == 0)
+ bot = vstring_alloc(2 * MAX_TAIL_BYTES);
+
+ if (dlen > MAX_DUMP_BYTES) {
+ hex_encode(top, (char *) data, MAX_HEAD_BYTES);
+ hex_encode(bot, (char *) data + dlen - MAX_TAIL_BYTES, MAX_TAIL_BYTES);
+ } else if (dlen > 0) {
+ hex_encode(top, (char *) data, dlen);
+ } else {
+ vstring_sprintf(top, "...");
+ }
+
+ msg_info("%s: %s: %u %u %u %s%s%s", tag, msg, u, s, m, STR(top),
+ dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
+}
+
+/* tlsa_carp - carp about a particular TLSA record */
+
+static void tlsa_carp(const char *s1, const char *s2, const char *s3,
+ const char *s4, uint8_t u, uint8_t s, uint8_t m,
+ const unsigned char *data, ssize_t dlen)
+{
+ static VSTRING *top;
+ static VSTRING *bot;
+
+ if (top == 0)
+ top = vstring_alloc(2 * MAX_HEAD_BYTES);
+ if (bot == 0)
+ bot = vstring_alloc(2 * MAX_TAIL_BYTES);
+
+ if (dlen > MAX_DUMP_BYTES) {
+ hex_encode(top, (char *) data, MAX_HEAD_BYTES);
+ hex_encode(bot, (char *) data + dlen - MAX_TAIL_BYTES, MAX_TAIL_BYTES);
+ } else if (dlen > 0) {
+ hex_encode(top, (char *) data, dlen);
+ } else {
+ vstring_sprintf(top, "...");
+ }
+
+ msg_warn("%s%s%s %s: %u %u %u %s%s%s", s1, s2, s3, s4, u, s, m, STR(top),
+ dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
+}
+
+/* tls_dane_flush - flush the cache */
+
+void tls_dane_flush(void)
+{
+ if (dane_cache)
+ ctable_free(dane_cache);
+ dane_cache = 0;
+}
+
+/* dane_free - ctable style */
+
+static void dane_free(void *dane, void *unused_context)
+{
+ tls_dane_free((TLS_DANE *) dane);
+}
+
+/* tls_dane_add_fpt_digests - map fingerprint list to DANE TLSA RRset */
+
+void tls_dane_add_fpt_digests(TLS_DANE *dane, const char *digest,
+ const char *delim, int smtp_mode)
+{
+ ARGV *values = argv_split(digest, delim);
+ ssize_t i;
+
+ if (smtp_mode) {
+ if (warn_compat_break_smtp_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTP_TLS_FPT_DGST "=md5 to compute certificate "
+ "fingerprints");
+ } else {
+ if (warn_compat_break_lmtp_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_LMTP_TLS_FPT_DGST "=md5 to compute certificate "
+ "fingerprints");
+ }
+
+ for (i = 0; i < values->argc; ++i) {
+ const char *cp = values->argv[i];
+ size_t ilen = strlen(cp);
+ VSTRING *raw;
+
+ /*
+ * Decode optionally colon-separated hex-encoded string, the input
+ * value requires at most 3 bytes per byte of payload, which must not
+ * exceed the size of the widest supported hash function.
+ */
+ if (ilen > 3 * EVP_MAX_MD_SIZE) {
+ msg_warn("malformed fingerprint value: %.100s...",
+ values->argv[i]);
+ continue;
+ }
+ raw = vstring_alloc(ilen / 2);
+ if (hex_decode_opt(raw, cp, ilen, HEX_DECODE_FLAG_ALLOW_COLON) == 0) {
+ myfree(raw);
+ msg_warn("malformed fingerprint value: %.384s", values->argv[i]);
+ continue;
+ }
+
+ /*
+ * At the "fingerprint" security level certificate digests and public
+ * key digests are interchangeable. Each leaf certificate is matched
+ * via either the public key digest or full certificate digest. The
+ * DER encoding of a certificate is not a valid public key, and
+ * conversely, the DER encoding of a public key is not a valid
+ * certificate. An attacker would need a 2nd-preimage that is
+ * feasible across types (given cert digest == some pkey digest) and
+ * yet presumably difficult within a type (e.g. given cert digest ==
+ * some other cert digest). No such attacks are known at this time,
+ * and it is expected that if any are found they would work within as
+ * well as across the cert/pkey data types.
+ *
+ * The private-use matching type "255" is mapped to the configured
+ * fingerprint digest, which may (harmlessly) coincide with one of
+ * the standard DANE digest algorithms. The private code point is
+ * however unconditionally enabled.
+ */
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("fingerprint", "digest as private-use TLSA record",
+ 3, 0, 255, (unsigned char *) STR(raw), VSTRING_LEN(raw));
+ dane->tlsa = tlsa_prepend(dane->tlsa, 3, 0, 255,
+ (unsigned char *) STR(raw), VSTRING_LEN(raw));
+ dane->tlsa = tlsa_prepend(dane->tlsa, 3, 1, 255,
+ (unsigned char *) STR(raw), VSTRING_LEN(raw));
+ vstring_free(raw);
+ }
+ argv_free(values);
+}
+
+/* parse_tlsa_rr - parse a validated TLSA RRset */
+
+static int parse_tlsa_rr(TLS_DANE *dane, DNS_RR *rr)
+{
+ const uint8_t *ip;
+ uint8_t usage;
+ uint8_t selector;
+ uint8_t mtype;
+ ssize_t dlen;
+ unsigned const char *data;
+ int iscname = strcasecmp(rr->rname, rr->qname);
+ const char *q = iscname ? rr->qname : "";
+ const char *a = iscname ? " -> " : "";
+ const char *r = rr->rname;
+
+ if (rr->type != T_TLSA)
+ msg_panic("%s%s%s: unexpected non-TLSA RR type: %u",
+ q, a, r, rr->type);
+
+ /* Drop truncated records */
+ if ((dlen = rr->data_len - 3) < 0) {
+ msg_warn("%s%s%s: truncated TLSA RR length == %u",
+ q, a, r, (unsigned) rr->data_len);
+ return (0);
+ }
+ ip = (const uint8_t *) rr->data;
+ usage = *ip++;
+ selector = *ip++;
+ mtype = *ip++;
+ data = (const unsigned char *) ip;
+
+ /*-
+ * Drop unsupported usages.
+ * Note: NO SUPPORT for usages 0/1 which do not apply to SMTP.
+ */
+ switch (usage) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ break;
+ default:
+ tlsa_carp(q, a, r, "unsupported TLSA certificate usage",
+ usage, selector, mtype, data, dlen);
+ return (0);
+ }
+
+ /*
+ * Drop private-use matching type, reserved for fingerprint matching.
+ */
+ if (mtype == 255) {
+ tlsa_carp(q, a, r, "reserved private-use matching type",
+ usage, selector, mtype, data, dlen);
+ return (0);
+ }
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("DNSSEC-signed TLSA record", r,
+ usage, selector, mtype, data, dlen);
+ dane->tlsa = tlsa_prepend(dane->tlsa, usage, selector, mtype, data, dlen);
+ return (1);
+}
+
+/* dane_lookup - TLSA record lookup, ctable style */
+
+static void *dane_lookup(const char *tlsa_fqdn, void *unused_ctx)
+{
+ static VSTRING *why = 0;
+ DNS_RR *rrs = 0;
+ DNS_RR *rr;
+ TLS_DANE *dane = tls_dane_alloc();
+ int ret;
+
+ if (why == 0)
+ why = vstring_alloc(10);
+
+ ret = dns_lookup(tlsa_fqdn, T_TLSA, RES_USE_DNSSEC, &rrs, 0, why);
+
+ switch (ret) {
+ case DNS_OK:
+ if (TLS_DANE_CACHE_TTL_MIN && rrs->ttl < TLS_DANE_CACHE_TTL_MIN)
+ rrs->ttl = TLS_DANE_CACHE_TTL_MIN;
+ if (TLS_DANE_CACHE_TTL_MAX && rrs->ttl > TLS_DANE_CACHE_TTL_MAX)
+ rrs->ttl = TLS_DANE_CACHE_TTL_MAX;
+
+ /* One more second to account for discrete time */
+ dane->expires = 1 + event_time() + rrs->ttl;
+
+ if (rrs->dnssec_valid) {
+ int n = 0;
+
+ for (rr = rrs; rr != 0; rr = rr->next)
+ n += parse_tlsa_rr(dane, rr);
+ if (n == 0)
+ dane->flags |= TLS_DANE_FLAG_EMPTY;
+ } else
+ dane->flags |= TLS_DANE_FLAG_NORRS;
+
+ if (rrs)
+ dns_rr_free(rrs);
+ break;
+
+ case DNS_NOTFOUND:
+ dane->flags |= TLS_DANE_FLAG_NORRS;
+ dane->expires = 1 + event_time() + TLS_DANE_CACHE_TTL_MIN;
+ break;
+
+ default:
+ msg_warn("DANE TLSA lookup problem: %s", STR(why));
+ dane->flags |= TLS_DANE_FLAG_ERROR;
+ break;
+ }
+
+ return (void *) dane;
+}
+
+/* resolve_host - resolve TLSA RRs for hostname (rname or qname) */
+
+static TLS_DANE *resolve_host(const char *host, const char *proto,
+ unsigned port)
+{
+ static VSTRING *query_domain;
+ TLS_DANE *dane;
+
+ if (query_domain == 0)
+ query_domain = vstring_alloc(64);
+
+ vstring_sprintf(query_domain, "_%u._%s.%s", ntohs(port), proto, host);
+ dane = (TLS_DANE *) ctable_locate(dane_cache, STR(query_domain));
+ if (timecmp(event_time(), dane->expires) > 0)
+ dane = (TLS_DANE *) ctable_refresh(dane_cache, STR(query_domain));
+ if (dane->base_domain == 0)
+ dane->base_domain = mystrdup(host);
+ /* Increment ref-count of cached entry */
+ ++dane->refs;
+ return (dane);
+}
+
+/* qname_secure - Lookup qname DNSSEC status */
+
+static int qname_secure(const char *qname)
+{
+ static VSTRING *why;
+ int ret = 0;
+ DNS_RR *rrs;
+
+ if (!why)
+ why = vstring_alloc(10);
+
+ /*
+ * We assume that qname is already an fqdn, and does not need any
+ * suffixes from RES_DEFNAME or RES_DNSRCH. This is typically the name
+ * of an MX host, and must be a complete DNS name. DANE initialization
+ * code in the SMTP client is responsible for checking that the default
+ * resolver flags do not include RES_DEFNAME and RES_DNSRCH.
+ */
+ ret = dns_lookup(qname, T_CNAME, RES_USE_DNSSEC, &rrs, 0, why);
+ if (ret == DNS_OK) {
+ ret = rrs->dnssec_valid;
+ dns_rr_free(rrs);
+ return (ret);
+ }
+ if (ret == DNS_NOTFOUND)
+ vstring_sprintf(why, "no longer a CNAME");
+ msg_warn("DNSSEC status lookup error for %s: %s", qname, STR(why));
+ return (-1);
+}
+
+/* tls_dane_resolve - cached map: (name, proto, port) -> TLS_DANE */
+
+TLS_DANE *tls_dane_resolve(unsigned port, const char *proto, DNS_RR *hostrr,
+ int forcetlsa)
+{
+ TLS_DANE *dane = 0;
+ int iscname = strcasecmp(hostrr->rname, hostrr->qname);
+ int isvalid = 1;
+
+ if (!tls_dane_avail())
+ return (0); /* Error */
+
+ /*
+ * By default suppress TLSA lookups for hosts in non-DNSSEC zones. If
+ * the host zone is not DNSSEC validated, the TLSA qname sub-domain is
+ * safely assumed to not be in a DNSSEC Look-aside Validation child zone.
+ */
+ if (!forcetlsa && !hostrr->dnssec_valid) {
+ isvalid = iscname ? qname_secure(hostrr->qname) : 0;
+ if (isvalid < 0)
+ return (0); /* Error */
+ }
+ if (!isvalid) {
+ dane = tls_dane_alloc();
+ dane->flags = TLS_DANE_FLAG_NORRS;
+ } else {
+ if (!dane_cache)
+ dane_cache = ctable_create(CACHE_SIZE, dane_lookup, dane_free, 0);
+
+ /*
+ * Try the rname first if secure, if nothing there, try the qname if
+ * different. Note, lookup errors are distinct from success with
+ * nothing found. If the rname lookup fails we don't try the qname.
+ */
+ if (hostrr->dnssec_valid) {
+ dane = resolve_host(hostrr->rname, proto, port);
+ if (tls_dane_notfound(dane) && iscname) {
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+ if (!dane)
+ dane = resolve_host(hostrr->qname, proto, port);
+ if (dane->flags & TLS_DANE_FLAG_ERROR) {
+ /* We don't return this object. */
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+
+ return (dane);
+}
+
+/* tls_dane_load_trustfile - load trust anchor certs or keys from file */
+
+int tls_dane_load_trustfile(TLS_DANE *dane, const char *tafile)
+{
+ BIO *bp;
+ char *name = 0;
+ char *header = 0;
+ unsigned char *data = 0;
+ long len;
+ int tacount;
+ char *errtype = 0; /* if error: cert or pkey? */
+
+ /* nop */
+ if (tafile == 0 || *tafile == 0)
+ return (1);
+
+ /*
+ * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio,
+ * calls PEM_read_bio() and then frees the bio. It is just as easy to
+ * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio()
+ * directly.
+ */
+ if ((bp = BIO_new_file(tafile, "r")) == NULL) {
+ msg_warn("error opening trust anchor file: %s: %m", tafile);
+ return (0);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ /*
+ * OpenSSL implements DANE strictly, with DANE-TA(2) only matching issuer
+ * certificates, and never the leaf cert. We also allow the
+ * trust-anchors to directly match the leaf certificate or public key.
+ */
+ for (tacount = 0;
+ errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len);
+ ++tacount) {
+ uint8_t daneta = DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION;
+ uint8_t daneee = DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE;
+ uint8_t mtype = DNS_TLSA_MATCHING_TYPE_NO_HASH_USED;
+
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ uint8_t selector = DNS_TLSA_SELECTOR_FULL_CERTIFICATE;
+
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("TA cert as TLSA record", tafile,
+ daneta, selector, mtype, data, len);
+ dane->tlsa =
+ tlsa_prepend(dane->tlsa, daneta, selector, mtype, data, len);
+ dane->tlsa =
+ tlsa_prepend(dane->tlsa, daneee, selector, mtype, data, len);
+ } else if (strcmp(name, PEM_STRING_PUBLIC) == 0) {
+ uint8_t selector = DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO;
+
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("TA pkey as TLSA record", tafile,
+ daneta, selector, mtype, data, len);
+ dane->tlsa =
+ tlsa_prepend(dane->tlsa, daneta, selector, mtype, data, len);
+ dane->tlsa = tlsa_prepend(dane->tlsa, daneee, selector, mtype, data, len);
+ }
+
+ /*
+ * If any of these were null, PEM_read() would have failed.
+ */
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ }
+ BIO_free(bp);
+
+ if (errtype) {
+ tls_print_errors();
+ msg_warn("error reading: %s: malformed trust-anchor %s",
+ tafile, errtype);
+ return (0);
+ }
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ /* Reached end of PEM file */
+ ERR_clear_error();
+ return (tacount > 0);
+ }
+ /* Some other PEM read error */
+ tls_print_errors();
+ return (0);
+}
+
+int tls_dane_enable(TLS_SESS_STATE *TLScontext)
+{
+ const TLS_DANE *dane = TLScontext->dane;
+ TLS_TLSA *tp;
+ SSL *ssl = TLScontext->con;
+ int usable = 0;
+ int ret;
+
+ for (tp = dane->tlsa; tp != 0; tp = tp->next) {
+ ret = SSL_dane_tlsa_add(ssl, tp->usage, tp->selector,
+ tp->mtype, tp->data, tp->length);
+ if (ret > 0) {
+ ++usable;
+ continue;
+ }
+ if (ret == 0) {
+ tlsa_carp(TLScontext->namaddr, ":", "", "unusable TLSA RR",
+ tp->usage, tp->selector, tp->mtype, tp->data,
+ tp->length);
+ continue;
+ }
+ /* Internal problem in OpenSSL */
+ tlsa_carp(TLScontext->namaddr, ":", "", "error loading trust settings",
+ tp->usage, tp->selector, tp->mtype, tp->data, tp->length);
+ tls_print_errors();
+ return (-1);
+ }
+ return (usable);
+}
+
+/* tls_dane_digest_init - configure supported DANE digests */
+
+void tls_dane_digest_init(SSL_CTX *ctx, const EVP_MD *fpt_alg)
+{
+ dane_mtype mtypes[256];
+ char *cp;
+ char *save;
+ char *algname;
+ uint8_t m;
+ uint8_t ord = 0;
+ uint8_t maxtype;
+
+ memset((char *) mtypes, 0, sizeof(mtypes));
+
+ /*
+ * The DANE SHA2-256(1) and SHA2-512(2) algorithms are disabled, unless
+ * explicitly enabled. Other codepoints can be disabled explicitly by
+ * giving them an empty digest name, which also implicitly disables all
+ * smaller codepoints that are not explicitly assigned.
+ *
+ * We reserve the private-use code point (255) for use with fingerprint
+ * matching. It MUST NOT be accepted in DNS replies.
+ */
+ mtypes[1].alg = NULL;
+ mtypes[2].alg = NULL;
+ mtypes[255].alg = fpt_alg;
+ maxtype = 2;
+
+ save = cp = mystrdup(var_tls_dane_digests);
+ while ((algname = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ char *algcode = split_at(algname, '=');
+ int codepoint = -1;
+
+ if (algcode && *algcode) {
+ unsigned long l;
+ char *endcp;
+
+ /*
+ * XXX: safe_strtoul() does not flag empty or white-space only
+ * input. Since we get algcode by splitting white-space/comma
+ * delimited tokens, this is not a problem here.
+ */
+ l = safe_strtoul(algcode, &endcp, 10);
+ if ((l == 0 && (errno == EINVAL || endcp == algcode))
+ || l >= 255 || *endcp) {
+ msg_warn("Invalid matching type number in %s: %s=%s",
+ VAR_TLS_DANE_DIGESTS, algname, algcode);
+ continue;
+ }
+ if (l == 0 || l == 255) {
+ msg_warn("Reserved matching type number in %s: %s=%s",
+ VAR_TLS_DANE_DIGESTS, algname, algcode);
+ continue;
+ }
+ codepoint = l;
+ }
+ /* Disable any codepoint gaps */
+ if (codepoint > maxtype) {
+ while (++maxtype < codepoint)
+ mtypes[codepoint].alg = NULL;
+ maxtype = codepoint;
+ }
+ /* Handle explicitly disabled codepoints */
+ if (*algname == 0) {
+ /* Skip empty specifiers */
+ if (codepoint < 0)
+ continue;
+ mtypes[codepoint].alg = NULL;
+ continue;
+ }
+ switch (codepoint) {
+ case -1:
+ if (strcasecmp(algname, LN_sha256) == 0)
+ codepoint = 1; /* SHA2-256(1) */
+ else if (strcasecmp(algname, LN_sha512) == 0)
+ codepoint = 2; /* SHA2-512(2) */
+ else {
+ msg_warn("%s: digest algorithm %s needs an explicit number",
+ VAR_TLS_DANE_DIGESTS, algname);
+ continue;
+ }
+ break;
+ case 1:
+ if (strcasecmp(algname, LN_sha256) != 0) {
+ msg_warn("%s: matching type 1 can only be %s",
+ VAR_TLS_DANE_DIGESTS, LN_sha256);
+ continue;
+ }
+ algname = LN_sha256;
+ break;
+ case 2:
+ if (strcasecmp(algname, LN_sha512) != 0) {
+ msg_warn("%s: matching type 2 can only be %s",
+ VAR_TLS_DANE_DIGESTS, LN_sha512);
+ continue;
+ }
+ algname = LN_sha512;
+ break;
+ default:
+ break;
+ }
+
+ if (mtypes[codepoint].ord != 0) {
+ msg_warn("%s: matching type %d specified more than once",
+ VAR_TLS_DANE_DIGESTS, codepoint);
+ continue;
+ }
+ mtypes[codepoint].ord = ++ord;
+
+ if ((mtypes[codepoint].alg = tls_digest_byname(algname, NULL)) == 0) {
+ msg_warn("%s: digest algorithm \"%s\"(%d) unknown",
+ VAR_TLS_DANE_DIGESTS, algname, codepoint);
+ continue;
+ }
+ }
+ myfree(save);
+
+ for (m = 1; m != 0; m = m != maxtype ? m + 1 : 255) {
+
+ /*
+ * In OpenSSL higher order ordinals are preferred, but we list the
+ * most preferred algorithms first, so the last ordinal becomes 1,
+ * next-to-last, 2, ...
+ *
+ * The ordinals of non-disabled algorithms are always positive, and the
+ * computed value cannot overflow 254 (the largest possible value of
+ * 'ord' after loading each valid codepoint at most once).
+ */
+ if (SSL_CTX_dane_mtype_set(ctx, mtypes[m].alg, m,
+ ord - mtypes[m].ord + 1) <= 0) {
+ msg_warn("%s: error configuring matching type %d",
+ VAR_TLS_DANE_DIGESTS, m);
+ tls_print_errors();
+ }
+ }
+}
+
+/* tls_dane_log - log DANE-based verification success */
+
+void tls_dane_log(TLS_SESS_STATE *TLScontext)
+{
+ static VSTRING *top;
+ static VSTRING *bot;
+ EVP_PKEY *mspki = 0;
+ int depth = SSL_get0_dane_authority(TLScontext->con, NULL, &mspki);
+ uint8_t u, s, m;
+ unsigned const char *data;
+ size_t dlen;
+
+ if (depth < 0)
+ return; /* No DANE auth */
+
+ switch (TLScontext->level) {
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ msg_info("%s: Matched trust anchor at depth %d",
+ TLScontext->namaddr, depth);
+ return;
+ }
+
+ if (top == 0)
+ top = vstring_alloc(2 * MAX_HEAD_BYTES);
+ if (bot == 0)
+ bot = vstring_alloc(2 * MAX_TAIL_BYTES);
+
+ (void) SSL_get0_dane_tlsa(TLScontext->con, &u, &s, &m, &data, &dlen);
+ if (dlen > MAX_DUMP_BYTES) {
+ hex_encode(top, (char *) data, MAX_HEAD_BYTES);
+ hex_encode(bot, (char *) data + dlen - MAX_TAIL_BYTES, MAX_TAIL_BYTES);
+ } else {
+ hex_encode(top, (char *) data, dlen);
+ }
+
+ switch (TLScontext->level) {
+ case TLS_LEV_FPRINT:
+ msg_info("%s: Matched fingerprint: %s%s%s", TLScontext->namaddr,
+ STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
+ return;
+
+ default:
+ msg_info("%s: Matched DANE %s at depth %d: %u %u %u %s%s%s",
+ TLScontext->namaddr, mspki ?
+ "TA public key verified certificate" : depth ?
+ "TA certificate" : "EE certificate", depth, u, s, m,
+ STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
+ return;
+ }
+}
+
+#ifdef TEST
+
+#include <unistd.h>
+#include <stdarg.h>
+
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <msg_vstream.h>
+
+static int verify_chain(SSL *ssl, x509_stack_t *chain, TLS_SESS_STATE *tctx)
+{
+ int ret;
+ X509 *cert;
+ X509_STORE_CTX *store_ctx;
+ SSL_CTX *ssl_ctx = SSL_get_SSL_CTX(ssl);
+ X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
+ int store_ctx_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+
+ cert = sk_X509_value(chain, 0);
+ if ((store_ctx = X509_STORE_CTX_new()) == NULL) {
+ SSLerr(SSL_F_SSL_VERIFY_CERT_CHAIN, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+ if (!X509_STORE_CTX_init(store_ctx, store, cert, chain)) {
+ X509_STORE_CTX_free(store_ctx);
+ return 0;
+ }
+ X509_STORE_CTX_set_ex_data(store_ctx, store_ctx_idx, ssl);
+
+ /* We're *verifying* a server chain */
+ X509_STORE_CTX_set_default(store_ctx, "ssl_server");
+ X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(store_ctx),
+ SSL_get0_param(ssl));
+ X509_STORE_CTX_set0_dane(store_ctx, SSL_get0_dane(ssl));
+
+ ret = X509_verify_cert(store_ctx);
+
+ SSL_set_verify_result(ssl, X509_STORE_CTX_get_error(store_ctx));
+ X509_STORE_CTX_free(store_ctx);
+
+ return (ret);
+}
+
+static void load_tlsa_args(SSL *ssl, char *argv[])
+{
+ const EVP_MD *md = 0;
+ X509 *cert = 0;
+ BIO *bp;
+ unsigned char *buf;
+ unsigned char *buf2;
+ int len;
+ uint8_t u = atoi(argv[1]);
+ uint8_t s = atoi(argv[2]);
+ uint8_t m = atoi(argv[3]);
+ EVP_PKEY *pkey;
+
+ /* Unsupported usages are fatal */
+ switch (u) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ break;
+ default:
+ msg_fatal("unsupported certificate usage %u", u);
+ }
+
+ /* Unsupported selectors are fatal */
+ switch (s) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ break;
+ default:
+ msg_fatal("unsupported selector %u", s);
+ }
+
+ /* Unsupported selectors are fatal */
+ switch (m) {
+ case DNS_TLSA_MATCHING_TYPE_NO_HASH_USED:
+ case DNS_TLSA_MATCHING_TYPE_SHA256:
+ case DNS_TLSA_MATCHING_TYPE_SHA512:
+ break;
+ default:
+ msg_fatal("unsupported matching type %u", m);
+ }
+
+ if ((bp = BIO_new_file(argv[4], "r")) == NULL)
+ msg_fatal("error opening %s: %m", argv[4]);
+ if (!PEM_read_bio_X509(bp, &cert, 0, 0)) {
+ tls_print_errors();
+ msg_fatal("error loading certificate from %s: %m", argv[4]);
+ }
+ BIO_free(bp);
+
+ /*
+ * Extract ASN.1 DER form of certificate or public key.
+ */
+ switch (s) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ len = i2d_X509(cert, NULL);
+ if (len > 0xffff)
+ msg_fatal("certificate too long: %d", len);
+ buf2 = buf = (unsigned char *) mymalloc(len);
+ i2d_X509(cert, &buf2);
+ break;
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ pkey = X509_get_pubkey(cert);
+ len = i2d_PUBKEY(pkey, NULL);
+ if (len > 0xffff)
+ msg_fatal("public key too long: %d", len);
+ buf2 = buf = (unsigned char *) mymalloc(len);
+ i2d_PUBKEY(pkey, &buf2);
+ EVP_PKEY_free(pkey);
+ break;
+ }
+ X509_free(cert);
+ OPENSSL_assert(buf2 - buf == len);
+
+ switch (m) {
+ case 0:
+ break;
+ case 1:
+ if ((md = tls_digest_byname(LN_sha256, NULL)) == 0)
+ msg_fatal("Digest %s not found", LN_sha256);
+ break;
+ case 2:
+ if ((md = tls_digest_byname(LN_sha512, NULL)) == 0)
+ msg_fatal("Digest %s not found", LN_sha512);
+ break;
+ default:
+ msg_fatal("Unsupported DANE mtype: %d", m);
+ }
+
+ if (md != 0) {
+ unsigned char mdbuf[EVP_MAX_MD_SIZE];
+ unsigned int mdlen = sizeof(mdbuf);
+
+ if (!EVP_Digest(buf, len, mdbuf, &mdlen, md, 0))
+ msg_fatal("Digest failure for mtype: %d", m);
+ myfree(buf);
+ buf = (unsigned char *) mymemdup(mdbuf, len = mdlen);
+ }
+ SSL_dane_tlsa_add(ssl, u, s, m, buf, len);
+ myfree((void *) buf);
+}
+
+static x509_stack_t *load_chain(const char *chainfile)
+{
+ BIO *bp;
+ char *name = 0;
+ char *header = 0;
+ unsigned char *data = 0;
+ long len;
+ int count;
+ char *errtype = 0; /* if error: cert or pkey? */
+ x509_stack_t *chain;
+ typedef X509 *(*d2i_X509_t) (X509 **, const unsigned char **, long);
+
+ if ((chain = sk_X509_new_null()) == 0) {
+ perror("malloc");
+ exit(1);
+ }
+
+ /*
+ * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio,
+ * calls PEM_read_bio() and then frees the bio. It is just as easy to
+ * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio()
+ * directly.
+ */
+ if ((bp = BIO_new_file(chainfile, "r")) == NULL) {
+ fprintf(stderr, "error opening chainfile: %s: %m\n", chainfile);
+ exit(1);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ for (count = 0;
+ errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len);
+ ++count) {
+ const unsigned char *p = data;
+
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_TRUSTED) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ d2i_X509_t d;
+ X509 *cert;
+
+ d = strcmp(name, PEM_STRING_X509_TRUSTED) ? d2i_X509_AUX : d2i_X509;
+ if ((cert = d(0, &p, len)) == 0 || (p - data) != len)
+ errtype = "certificate";
+ else if (sk_X509_push(chain, cert) == 0) {
+ perror("malloc");
+ exit(1);
+ }
+ } else {
+ fprintf(stderr, "unexpected chain file object: %s\n", name);
+ exit(1);
+ }
+
+ /*
+ * If any of these were null, PEM_read() would have failed.
+ */
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ }
+ BIO_free(bp);
+
+ if (errtype) {
+ tls_print_errors();
+ fprintf(stderr, "error reading: %s: malformed %s", chainfile, errtype);
+ exit(1);
+ }
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ /* Reached end of PEM file */
+ ERR_clear_error();
+ if (count > 0)
+ return chain;
+ fprintf(stderr, "no certificates found in: %s\n", chainfile);
+ exit(1);
+ }
+ /* Some other PEM read error */
+ tls_print_errors();
+ fprintf(stderr, "error reading: %s\n", chainfile);
+ exit(1);
+}
+
+static void usage(const char *progname)
+{
+ fprintf(stderr, "Usage: %s certificate-usage selector matching-type"
+ " certfile \\\n\t\tCAfile chainfile hostname [certname ...]\n",
+ progname);
+ fprintf(stderr, " where, certificate-usage = TLSA certificate usage,\n");
+ fprintf(stderr, "\t selector = TLSA selector,\n");
+ fprintf(stderr, "\t matching-type = empty string or OpenSSL digest algorithm name,\n");
+ fprintf(stderr, "\t PEM certfile provides certificate association data,\n");
+ fprintf(stderr, "\t PEM CAfile contains any usage 0/1 trusted roots,\n");
+ fprintf(stderr, "\t PEM chainfile = server chain file to verify\n");
+ fprintf(stderr, "\t hostname = destination hostname,\n");
+ fprintf(stderr, "\t each certname augments the hostname for name checks.\n");
+ exit(1);
+}
+
+static SSL_CTX *ctx_init(const char *CAfile)
+{
+ SSL_CTX *client_ctx;
+
+ tls_param_init();
+ tls_check_version();
+
+ if (!tls_validate_digest(LN_sha1))
+ msg_fatal("%s digest algorithm not available", LN_sha1);
+
+ if (TLScontext_index < 0)
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0)
+ msg_fatal("Cannot allocate SSL application data index");
+
+ ERR_clear_error();
+ if ((client_ctx = SSL_CTX_new(TLS_client_method())) == 0)
+ msg_fatal("cannot allocate client SSL_CTX");
+ SSL_CTX_set_verify_depth(client_ctx, 5);
+
+ /* Enable DANE support in OpenSSL */
+ if (SSL_CTX_dane_enable(client_ctx) <= 0) {
+ tls_print_errors();
+ msg_fatal("OpenSSL DANE initialization failed");
+ }
+ if (tls_set_ca_certificate_info(client_ctx, CAfile, "") < 0) {
+ tls_print_errors();
+ msg_fatal("cannot load CAfile: %s", CAfile);
+ }
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE,
+ tls_verify_certificate_callback);
+ return (client_ctx);
+}
+
+int main(int argc, char *argv[])
+{
+ SSL_CTX *ssl_ctx;
+ const EVP_MD *fpt_alg;
+ TLS_SESS_STATE *tctx;
+ x509_stack_t *chain;
+ int i;
+
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+ msg_vstream_init(var_procname, VSTREAM_OUT);
+
+ if (argc < 8)
+ usage(argv[0]);
+
+ ssl_ctx = ctx_init(argv[5]);
+ if (!tls_dane_avail())
+ msg_fatal("DANE TLSA support not available");
+
+ tctx = tls_alloc_sess_context(TLS_LOG_NONE, argv[7]);
+ tctx->namaddr = argv[7];
+ tctx->mdalg = LN_sha256;
+ tctx->dane = tls_dane_alloc();
+
+ if ((fpt_alg = tls_validate_digest(tctx->mdalg)) == 0)
+ msg_fatal("fingerprint digest algorithm %s not found",
+ tctx->mdalg);
+ tls_dane_digest_init(ssl_ctx, fpt_alg);
+
+ if ((tctx->con = SSL_new(ssl_ctx)) == 0
+ || !SSL_set_ex_data(tctx->con, TLScontext_index, tctx)) {
+ tls_print_errors();
+ msg_fatal("Error allocating SSL connection");
+ }
+ if (SSL_dane_enable(tctx->con, 0) <= 0) {
+ tls_print_errors();
+ msg_fatal("Error enabling DANE for SSL handle");
+ }
+ SSL_dane_set_flags(tctx->con, DANE_FLAG_NO_DANE_EE_NAMECHECKS);
+ SSL_dane_set_flags(tctx->con, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
+ for (i = 7; i < argc; ++i)
+ if (!SSL_add1_host(tctx->con, argv[i]))
+ msg_fatal("error adding hostname: %s", argv[i]);
+ load_tlsa_args(tctx->con, argv);
+ SSL_set_connect_state(tctx->con);
+
+ /* Verify saved server chain */
+ chain = load_chain(argv[6]);
+ i = verify_chain(tctx->con, chain, tctx);
+ tls_print_errors();
+
+ if (i > 0) {
+ const char *peername = SSL_get0_peername(tctx->con);
+
+ if (peername == 0)
+ peername = argv[7];
+ msg_info("Verified %s", peername);
+ } else {
+ i = SSL_get_verify_result(tctx->con);
+ msg_info("certificate verification failed for %s:%s: num=%d:%s",
+ argv[6], argv[7], i, X509_verify_cert_error_string(i));
+ }
+
+ return (i <= 0);
+}
+
+#endif /* TEST */
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_dane.sh b/src/tls/tls_dane.sh
new file mode 100644
index 0000000..a950857
--- /dev/null
+++ b/src/tls/tls_dane.sh
@@ -0,0 +1,211 @@
+#! /bin/bash
+
+set -e
+
+DOMAIN=example.com
+HOST=mail.${DOMAIN}
+TEST=./tls_dane
+
+key() {
+ local key=$1; shift
+
+ if [ ! -f "${key}.pem" ]; then
+ openssl genpkey 2>/dev/null \
+ -paramfile <(openssl ecparam -name prime256v1) \
+ -out "${key}.pem"
+ fi
+}
+
+req() {
+ local key=$1; shift
+ local cn=$1; shift
+
+ key "$key"
+ openssl req -new -sha256 -key "${key}.pem" 2>/dev/null \
+ -config <(printf "[req]\n%s\n%s\n[dn]\nCN=%s\n" \
+ "prompt = no" "distinguished_name = dn" "${cn}")
+}
+
+req_nocn() {
+ local key=$1; shift
+
+ key "$key"
+ openssl req -new -sha256 -subj / -key "${key}.pem" 2>/dev/null \
+ -config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \
+ "distinguished_name = dn")
+}
+
+cert() {
+ local cert=$1; shift
+ local exts=$1; shift
+
+ openssl x509 -req -sha256 -out "${cert}.pem" 2>/dev/null \
+ -extfile <(printf "%s\n" "$exts") "$@"
+}
+
+genroot() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local skid=$1; shift
+ local akid=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days 30
+}
+
+genca() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local skid=$1; shift
+ local akid=$1; shift
+ local ca=$1; shift
+ local cakey=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days 30 "$@"
+}
+
+genee() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local ca=$1; shift
+ local cakey=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:false" \
+ "extendedKeyUsage = serverAuth" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days 30 "$@"
+}
+
+genss() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:true" \
+ "extendedKeyUsage = serverAuth" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -set_serial 1 -days 30 -signkey "${key}.pem" "$@"
+}
+
+gennocn() {
+ local key=$1; shift
+ local cert=$1; shift
+
+ req_nocn "$key" |
+ cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days -1 "$@"
+}
+
+runtest() {
+ local desc=$1; shift
+ local usage=$1; shift
+ local selector=$1; shift
+ local mtype=$1; shift
+ local tlsa=$1; shift
+ local ca=$1; shift
+ local chain=$1; shift
+ local digest
+
+ case $mtype in
+ 0) digest="";;
+ 1) digest=sha256;;
+ 2) digest=sha512;;
+ *) echo "bad mtype: $mtype"; exit 1;;
+ esac
+
+ printf "%d %d %d %-24s %s: " "$usage" "$selector" "$mtype" "$tlsa" "$desc"
+
+ if [ -n "$ca" ]; then ca="$ca.pem"; fi
+ "$TEST" "$usage" "$selector" "$digest" "$tlsa.pem" "$ca" "$chain.pem" \
+ "$@" > /dev/null
+}
+
+checkpass() { runtest "$@" && { echo pass; } || { echo fail; exit 1; }; }
+checkfail() { runtest "$@" && { echo fail; exit 1; } || { echo pass; }; }
+
+#---------
+
+genss "$HOST" sskey sscert
+gennocn akey acert
+
+# Tests that might depend on akid/skid chaining
+#
+for rakid in "" \
+ "authorityKeyIdentifier = keyid,issuer" \
+ "authorityKeyIdentifier = issuer" \
+ "authorityKeyIdentifier = keyid"
+do
+for cakid in "" \
+ "authorityKeyIdentifier = keyid,issuer" \
+ "authorityKeyIdentifier = issuer" \
+ "authorityKeyIdentifier = keyid"
+do
+for rskid in "" "subjectKeyIdentifier = hash"
+do
+for caskid in "" "subjectKeyIdentifier = hash"
+do
+
+genroot "Root CA" rootkey rootcert "$rskid" "$rakid"
+genca "CA 1" cakey1 cacert1 "$caskid" "$cakid" rootcert rootkey
+genca "CA 2" cakey2 cacert2 "$caskid" "$cakid" cacert1 cakey1
+genee "$HOST" eekey eecert cacert2 cakey2
+cat eecert.pem cacert2.pem cacert1.pem rootcert.pem > chain.pem
+cat eecert.pem cacert2.pem cacert1.pem > chain1.pem
+
+for s in 0 1
+do
+ checkpass "OOB root TA" 2 "$s" 0 rootcert "" chain1 "$HOST"
+ checkpass "OOB TA" 2 "$s" 0 cacert2 "" eecert "$HOST"
+ checkpass "in-chain root TA" 2 "$s" 1 rootcert "" chain "$HOST"
+
+ for m in 0 1 2
+ do
+ checkpass "valid TA" 2 "$s" "$m" rootcert "" chain "$HOST"
+ for ca in "cacert1" "cacert2"; do
+ checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain "$HOST"
+ checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain1 "$HOST"
+ checkpass "valid TA+CA" 2 "$s" "$m" "$ca" rootcert chain1 "$HOST"
+ checkpass "sub-domain" 2 "$s" "$m" "$ca" "" chain1 whatever ".$DOMAIN"
+ checkfail "wrong name" 2 "$s" "$m" "$ca" "" chain1 "whatever"
+ done
+ done
+done
+
+done
+done
+done
+done
+
+# These tests don't depend in the akid/skid chaining:
+#
+for s in 0 1
+do
+ checkfail "missing TA" 2 "$s" 1 rootcert "" chain1 "$HOST"
+ for m in 0 1 2
+ do
+ checkpass "depth 0 TA" 2 "$s" "$m" sscert "" sscert "$HOST"
+ checkfail "non-TA" 2 "$s" "$m" eecert rootcert chain "$HOST"
+ checkfail "depth 0 TA namecheck" 2 "$s" "$m" sscert sscert sscert whatever
+
+ checkpass "valid EE" 3 "$s" "$m" eecert "" chain whatever
+ checkpass "key-only EE" 3 "$s" "$m" acert "" acert whatever
+ checkfail "wrong EE" 3 "$s" "$m" cacert2 "" chain whatever
+ done
+done
+
+rm -f *.pem
diff --git a/src/tls/tls_dh.c b/src/tls/tls_dh.c
new file mode 100644
index 0000000..9a6bd67
--- /dev/null
+++ b/src/tls/tls_dh.c
@@ -0,0 +1,384 @@
+/*++
+/* NAME
+/* tls_dh
+/* SUMMARY
+/* Diffie-Hellman parameter support
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* void tls_set_dh_from_file(path)
+/* const char *path;
+/*
+/* void tls_auto_eecdh_curves(ctx, configured)
+/* SSL_CTX *ctx;
+/* char *configured;
+/*
+/* void tls_tmp_dh(ctx, useauto)
+/* SSL_CTX *ctx;
+/* int useauto;
+/* DESCRIPTION
+/* This module maintains parameters for Diffie-Hellman key generation.
+/*
+/* tls_tmp_dh() returns the configured or compiled-in FFDHE
+/* group parameters.
+/*
+/* tls_set_dh_from_file() overrides compiled-in DH parameters
+/* with those specified in the named files. The file format
+/* is as expected by the PEM_read_DHparams() routine.
+/*
+/* tls_auto_eecdh_curves() enables negotiation of the most preferred curve
+/* among the curves specified by the "configured" argument. The useauto
+/* argument enables OpenSSL-builtin group selection in preference to our
+/* own compiled-in group. This may interoperate better with overly strict
+/* peers that accept only "standard" groups (bogus threat model).
+/* DIAGNOSTICS
+/* In case of error, tls_set_dh_from_file() logs a warning and
+/* ignores the request.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+ * Global library
+ */
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+#include <openssl/dh.h>
+#ifndef OPENSSL_NO_ECDH
+#include <openssl/ec.h>
+#endif
+#if OPENSSL_VERSION_PREREQ(3,0)
+#include <openssl/decoder.h>
+#endif
+
+/* Application-specific. */
+
+ /*
+ * Compiled-in FFDHE (finite-field ephemeral Diffie-Hellman) parameters.
+ * Used when no parameters are explicitly loaded from a site-specific file.
+ *
+ * With OpenSSL 3.0 and later when no explicit parameter file is specified by
+ * the administrator (or the setting is "auto"), we delegate group selection
+ * to OpenSSL via SSL_CTX_set_dh_auto(3).
+ *
+ * Using an ASN.1 DER encoding avoids the need to explicitly manipulate the
+ * internal representation of DH parameter objects.
+ *
+ * The FFDHE group is now 2048-bit, as 1024 bits is increasingly considered to
+ * weak by clients. When greater security is required, use EECDH.
+ */
+
+ /*-
+ * Generated via:
+ * $ openssl dhparam -2 -outform DER 2048 2>/dev/null |
+ * hexdump -ve '/1 "0x%02x, "' | fmt -73
+ * TODO: generate at compile-time. But that is no good for the majority of
+ * sites that install pre-compiled binaries, and breaks reproducible builds.
+ * Instead, generate at installation time and use main.cf configuration.
+ */
+static unsigned char builtin_der[] = {
+ 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xec, 0x02, 0x7b,
+ 0x74, 0xc6, 0xd4, 0xb4, 0x89, 0x68, 0xfd, 0xbc, 0xe0, 0x82, 0xae, 0xd6,
+ 0xf1, 0x4d, 0x93, 0xaa, 0x47, 0x07, 0x84, 0x3d, 0x86, 0xf8, 0x47, 0xf7,
+ 0xdf, 0x08, 0x7b, 0xca, 0x04, 0xa4, 0x72, 0xec, 0x11, 0xe2, 0x38, 0x43,
+ 0xb7, 0x94, 0xab, 0xaf, 0xe2, 0x85, 0x59, 0x43, 0x4e, 0x71, 0x85, 0xfe,
+ 0x52, 0x0c, 0xe0, 0x1c, 0xb6, 0xc7, 0xb0, 0x1b, 0x06, 0xb3, 0x4d, 0x1b,
+ 0x4f, 0xf6, 0x4b, 0x45, 0xbd, 0x1d, 0xb8, 0xe4, 0xa4, 0x48, 0x09, 0x28,
+ 0x19, 0xd7, 0xce, 0xb1, 0xe5, 0x9a, 0xc4, 0x94, 0x55, 0xde, 0x4d, 0x86,
+ 0x0f, 0x4c, 0x5e, 0x25, 0x51, 0x6c, 0x96, 0xca, 0xfa, 0xe3, 0x01, 0x69,
+ 0x82, 0x6c, 0x8f, 0xf5, 0xe7, 0x0e, 0xb7, 0x8e, 0x52, 0xf1, 0xcf, 0x0b,
+ 0x67, 0x10, 0xd0, 0xb3, 0x77, 0x79, 0xa4, 0xc1, 0xd0, 0x0f, 0x3f, 0xf5,
+ 0x5c, 0x35, 0xf9, 0x46, 0xd2, 0xc7, 0xfb, 0x97, 0x6d, 0xd5, 0xbe, 0xe4,
+ 0x8b, 0x5a, 0xf2, 0x88, 0xfa, 0x47, 0xdc, 0xc2, 0x4a, 0x4d, 0x69, 0xd3,
+ 0x2a, 0xdf, 0x55, 0x6c, 0x5f, 0x71, 0x11, 0x1e, 0x87, 0x03, 0x68, 0xe1,
+ 0xf4, 0x21, 0x06, 0x63, 0xd9, 0x65, 0xd4, 0x0c, 0x4d, 0xa7, 0x1f, 0x15,
+ 0x53, 0x3a, 0x50, 0x1a, 0xf5, 0x9b, 0x50, 0x35, 0xe0, 0x16, 0xa1, 0xd7,
+ 0xe6, 0xbf, 0xd7, 0xd9, 0xd9, 0x53, 0xe5, 0x8b, 0xf8, 0x7b, 0x45, 0x46,
+ 0xb6, 0xac, 0x50, 0x16, 0x46, 0x42, 0xca, 0x76, 0x38, 0x4b, 0x8e, 0x83,
+ 0xc6, 0x73, 0x13, 0x9c, 0x03, 0xd1, 0x7a, 0x3d, 0x8d, 0x99, 0x34, 0x10,
+ 0x79, 0x67, 0x21, 0x23, 0xf9, 0x6f, 0x48, 0x9a, 0xa6, 0xde, 0xbf, 0x7f,
+ 0x9c, 0x16, 0x53, 0xff, 0xf7, 0x20, 0x96, 0xeb, 0x34, 0xcb, 0x5b, 0x85,
+ 0x2b, 0x7c, 0x98, 0x00, 0x23, 0x47, 0xce, 0xc2, 0x58, 0x12, 0x86, 0x2c,
+ 0x57, 0x02, 0x01, 0x02,
+};
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+
+/* ------------------------------------- 3.0 API */
+
+static EVP_PKEY *dhp = 0;
+
+/* load_builtin - load compile-time FFDHE group */
+
+static void load_builtin(void)
+{
+ EVP_PKEY *tmp = 0;
+ OSSL_DECODER_CTX *d;
+ const unsigned char *endp = builtin_der;
+ size_t dlen = sizeof(builtin_der);
+
+ d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "DER", NULL, "DH",
+ OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ NULL, NULL);
+ /* Check decode succeeds and consumes all data (final dlen == 0) */
+ if (d && OSSL_DECODER_from_data(d, &endp, &dlen) && tmp && !dlen) {
+ dhp = tmp;
+ } else {
+ EVP_PKEY_free(tmp);
+ msg_warn("error loading compiled-in DH parameters");
+ tls_print_errors();
+ }
+ OSSL_DECODER_CTX_free(d);
+}
+
+/* tls_set_dh_from_file - set Diffie-Hellman parameters from file */
+
+void tls_set_dh_from_file(const char *path)
+{
+ FILE *fp;
+ EVP_PKEY *tmp = 0;
+ OSSL_DECODER_CTX *d;
+
+ /*
+ * This function is the first to set the DH parameters, but free any
+ * prior value just in case the call sequence changes some day.
+ */
+ if (dhp) {
+ EVP_PKEY_free(dhp);
+ dhp = 0;
+ }
+ if (strcmp(path, "auto") == 0)
+ return;
+
+ if ((fp = fopen(path, "r")) == 0) {
+ msg_warn("error opening DH parameter file \"%s\": %m"
+ " -- using compiled-in defaults", path);
+ return;
+ }
+ d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "PEM", NULL, "DH",
+ OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ NULL, NULL);
+ if (!d || !OSSL_DECODER_from_fp(d, fp) || !tmp) {
+ msg_warn("error decoding DH parameters from file \"%s\""
+ " -- using compiled-in defaults", path);
+ tls_print_errors();
+ } else {
+ dhp = tmp;
+ }
+ OSSL_DECODER_CTX_free(d);
+ (void) fclose(fp);
+}
+
+/* tls_tmp_dh - configure FFDHE group */
+
+void tls_tmp_dh(SSL_CTX *ctx, int useauto)
+{
+ if (!dhp && !useauto)
+ load_builtin();
+ if (!ctx)
+ return;
+ if (dhp) {
+ EVP_PKEY *tmp = EVP_PKEY_dup(dhp);
+
+ if (tmp && SSL_CTX_set0_tmp_dh_pkey(ctx, tmp) > 0)
+ return;
+ EVP_PKEY_free(tmp);
+ msg_warn("error configuring explicit DH parameters");
+ tls_print_errors();
+ } else {
+ if (SSL_CTX_set_dh_auto(ctx, 1) > 0)
+ return;
+ msg_warn("error configuring auto DH parameters");
+ tls_print_errors();
+ }
+}
+
+#else /* OPENSSL_VERSION_PREREQ(3,0) */
+
+/* ------------------------------------- 1.1.1 API */
+
+static DH *dhp = 0;
+
+static void load_builtin(void)
+{
+ DH *tmp = 0;
+ const unsigned char *endp = builtin_der;
+
+ if (d2i_DHparams(&tmp, &endp, sizeof(builtin_der))
+ && sizeof(builtin_der) == endp - builtin_der) {
+ dhp = tmp;
+ } else {
+ DH_free(tmp);
+ msg_warn("error loading compiled-in DH parameters");
+ tls_print_errors();
+ }
+}
+
+/* tls_set_dh_from_file - set Diffie-Hellman parameters from file */
+
+void tls_set_dh_from_file(const char *path)
+{
+ FILE *fp;
+
+ /*
+ * This function is the first to set the DH parameters, but free any
+ * prior value just in case the call sequence changes some day.
+ */
+ if (dhp) {
+ DH_free(dhp);
+ dhp = 0;
+ }
+
+ /*
+ * Forwards compatibility, support "auto" by using the builtin group when
+ * OpenSSL is < 3.0 and does not support automatic FFDHE group selection.
+ */
+ if (strcmp(path, "auto") == 0)
+ return;
+
+ if ((fp = fopen(path, "r")) == 0) {
+ msg_warn("cannot load DH parameters from file %s: %m"
+ " -- using compiled-in defaults", path);
+ return;
+ }
+ if ((dhp = PEM_read_DHparams(fp, 0, 0, 0)) == 0) {
+ msg_warn("cannot load DH parameters from file %s"
+ " -- using compiled-in defaults", path);
+ tls_print_errors();
+ }
+ (void) fclose(fp);
+}
+
+/* tls_tmp_dh - configure FFDHE group */
+
+void tls_tmp_dh(SSL_CTX *ctx, int useauto)
+{
+ if (!dhp)
+ load_builtin();
+ if (!ctx || !dhp || SSL_CTX_set_tmp_dh(ctx, dhp) > 0)
+ return;
+ msg_warn("error configuring explicit DH parameters");
+ tls_print_errors();
+}
+
+#endif /* OPENSSL_VERSION_PREREQ(3,0) */
+
+/* ------------------------------------- Common API */
+
+void tls_auto_eecdh_curves(SSL_CTX *ctx, const char *configured)
+{
+#ifndef OPENSSL_NO_ECDH
+ SSL_CTX *tmpctx;
+ int *nids;
+ int space = 5;
+ int n = 0;
+ int unknown = 0;
+ char *save;
+ char *curves;
+ char *curve;
+
+ if ((tmpctx = SSL_CTX_new(TLS_method())) == 0) {
+ msg_warn("cannot allocate temp SSL_CTX, using default ECDHE curves");
+ tls_print_errors();
+ return;
+ }
+ nids = mymalloc(space * sizeof(int));
+ curves = save = mystrdup(configured);
+#define RETURN do { \
+ myfree(save); \
+ myfree(nids); \
+ SSL_CTX_free(tmpctx); \
+ return; \
+ } while (0)
+
+ while ((curve = mystrtok(&curves, CHARS_COMMA_SP)) != 0) {
+ int nid = EC_curve_nist2nid(curve);
+
+ if (nid == NID_undef)
+ nid = OBJ_sn2nid(curve);
+ if (nid == NID_undef)
+ nid = OBJ_ln2nid(curve);
+ if (nid == NID_undef) {
+ msg_warn("ignoring unknown ECDHE curve \"%s\"",
+ curve);
+ continue;
+ }
+
+ /*
+ * Validate the NID by trying it as the sole EC curve for a
+ * throw-away SSL context. Silently skip unsupported code points.
+ * This way, we can list X25519 and X448 as soon as the nids are
+ * assigned, and before the supporting code is implemented. They'll
+ * be silently skipped when not yet supported.
+ */
+ if (SSL_CTX_set1_curves(tmpctx, &nid, 1) <= 0) {
+ ++unknown;
+ continue;
+ }
+ if (++n > space) {
+ space *= 2;
+ nids = myrealloc(nids, space * sizeof(int));
+ }
+ nids[n - 1] = nid;
+ }
+
+ if (n == 0) {
+ if (unknown > 0)
+ msg_warn("none of the configured ECDHE curves are supported");
+ RETURN;
+ }
+ if (SSL_CTX_set1_curves(ctx, nids, n) <= 0) {
+ msg_warn("failed to configure ECDHE curves");
+ tls_print_errors();
+ RETURN;
+ }
+ RETURN;
+#endif
+}
+
+#ifdef TEST
+
+int main(int unused_argc, char **unused_argv)
+{
+ tls_tmp_dh(0, 0);
+ return (dhp == 0);
+}
+
+#endif
+
+#endif
diff --git a/src/tls/tls_fprint.c b/src/tls/tls_fprint.c
new file mode 100644
index 0000000..8021570
--- /dev/null
+++ b/src/tls/tls_fprint.c
@@ -0,0 +1,435 @@
+/*++
+/* NAME
+/* tls_fprint 3
+/* SUMMARY
+/* Digests fingerprints and all that.
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr)
+/* const char *mdalg;
+/* EVP_MD_CTX **mdctxPtr;
+/*
+/* char *tls_serverid_digest(TLScontext, props, ciphers)
+/* TLS_SESS_STATE *TLScontext;
+/* const TLS_CLIENT_START_PROPS *props;
+/* const char *ciphers;
+/*
+/* char *tls_digest_encode(md_buf, md_len)
+/* const unsigned char *md_buf;
+/* const char *md_len;
+/*
+/* char *tls_cert_fprint(peercert, mdalg)
+/* X509 *peercert;
+/* const char *mdalg;
+/*
+/* char *tls_pkey_fprint(peercert, mdalg)
+/* X509 *peercert;
+/* const char *mdalg;
+/* DESCRIPTION
+/* tls_digest_byname() constructs, and optionally returns, an EVP_MD_CTX
+/* handle for performing digest operations with the algorithm named by the
+/* mdalg parameter. The return value is non-null on success, and holds a
+/* digest algorithm handle. If the mdctxPtr argument is non-null the
+/* created context is returned to the caller, who is then responsible for
+/* deleting it by calling EVP_MD_ctx_free() once it is no longer needed.
+/*
+/* tls_digest_encode() converts a binary message digest to a hex ASCII
+/* format with ':' separators between each pair of hex digits.
+/* The return value is dynamically allocated with mymalloc(),
+/* and the caller must eventually free it with myfree().
+/*
+/* tls_cert_fprint() returns a fingerprint of the given
+/* certificate using the requested message digest, formatted
+/* with tls_digest_encode(). Panics if the
+/* (previously verified) digest algorithm is not found. The return
+/* value is dynamically allocated with mymalloc(), and the caller
+/* must eventually free it with myfree().
+/*
+/* tls_pkey_fprint() returns a public-key fingerprint; in all
+/* other respects the function behaves as tls_cert_fprint().
+/* The var_tls_bc_pkey_fprint variable enables an incorrect
+/* algorithm that was used in Postfix versions 2.9.[0-5].
+/* The return value is dynamically allocated with mymalloc(),
+/* and the caller must eventually free it with myfree().
+/*
+/* tls_serverid_digest() suffixes props->serverid computed by the SMTP
+/* client with "&" plus a digest of additional parameters needed to ensure
+/* that re-used sessions are more likely to be reused and that they will
+/* satisfy all protocol and security requirements. The return value is
+/* dynamically allocated with mymalloc(), and the caller must eventually
+/* free it with myfree().
+/*
+/* Arguments:
+/* .IP mdalg
+/* A digest algorithm name, such as "sha256".
+/* .IP peercert
+/* Server or client X.509 certificate.
+/* .IP md_buf
+/* The raw binary digest.
+/* .IP md_len
+/* The digest length in bytes.
+/* .IP mdalg
+/* Name of a message digest algorithm suitable for computing secure
+/* (1st pre-image resistant) message digests of certificates. For now,
+/* md5, sha1, or member of SHA-2 family if supported by OpenSSL.
+/* .IP mdctxPtr
+/* Pointer to an (EVP_MD_CTX *) handle, or NULL if only probing for
+/* algorithm support without immediate use in mind.
+/* .IP buf
+/* Input data for the message digest algorithm mdalg.
+/* .IP len
+/* The length of the input data.
+/* .IP props
+/* The client start properties for the session, which contains the
+/* initial serverid from the SMTP client and the DANE verification
+/* parameters.
+/* .IP protomask
+/* The mask of protocol exclusions.
+/* .IP ciphers
+/* The SSL client cipherlist.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+static const char hexcodes[] = "0123456789ABCDEF";
+
+#define checkok(stillok) (ok = ok && (stillok))
+#define digest_object(p) digest_data((unsigned char *)(p), sizeof(*(p)))
+#define digest_data(p, l) checkok(digest_bytes(mdctx, (p), (l)))
+#define digest_string(s) checkok(digest_chars(mdctx, (s)))
+#define digest_dane(tlsa) checkok(tls_digest_tlsa(mdctx, tlsa))
+
+/* digest_bytes - hash octet string of given length */
+
+static int digest_bytes(EVP_MD_CTX *ctx, const unsigned char *buf, size_t len)
+{
+ return (EVP_DigestUpdate(ctx, buf, len));
+}
+
+/* digest_chars - hash string including trailing NUL */
+
+static int digest_chars(EVP_MD_CTX *ctx, const char *s)
+{
+ return (EVP_DigestUpdate(ctx, s, strlen(s) + 1));
+}
+
+/* tlsa_cmp - compare TLSA RRs for sorting to canonical order */
+
+static int tlsa_cmp(const void *a, const void *b)
+{
+ TLS_TLSA *p = *(TLS_TLSA **) a;
+ TLS_TLSA *q = *(TLS_TLSA **) b;
+ int d;
+
+ if ((d = (int) p->usage - (int) q->usage) != 0)
+ return d;
+ if ((d = (int) p->selector - (int) q->selector) != 0)
+ return d;
+ if ((d = (int) p->mtype - (int) q->mtype) != 0)
+ return d;
+ if ((d = (int) p->length - (int) q->length) != 0)
+ return d;
+ return (memcmp(p->data, q->data, p->length));
+}
+
+/* tls_digest_tlsa - fold in digest of TLSA records */
+
+static int tls_digest_tlsa(EVP_MD_CTX *mdctx, TLS_TLSA *tlsa)
+{
+ TLS_TLSA *p;
+ TLS_TLSA **arr;
+ int ok = 1;
+ int n;
+ int i;
+
+ for (n = 0, p = tlsa; p != 0; p = p->next)
+ ++n;
+ arr = (TLS_TLSA **) mymalloc(n * sizeof(*arr));
+ for (i = 0, p = tlsa; p; p = p->next)
+ arr[i++] = (void *) p;
+ qsort(arr, n, sizeof(arr[0]), tlsa_cmp);
+
+ digest_object(&n);
+ for (i = 0; i < n; ++i) {
+ digest_object(&arr[i]->usage);
+ digest_object(&arr[i]->selector);
+ digest_object(&arr[i]->mtype);
+ digest_object(&arr[i]->length);
+ digest_data(arr[i]->data, arr[i]->length);
+ }
+ myfree((void *) arr);
+ return (ok);
+}
+
+/* tls_digest_byname - test availability or prepare to use digest */
+
+const EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr)
+{
+ const EVP_MD *md;
+ EVP_MD_CTX *mdctx = NULL;
+ int ok = 1;
+
+ /*
+ * In OpenSSL 3.0, because of dynamically variable algorithm providers,
+ * there is a time-of-check/time-of-use issue that means that abstract
+ * algorithm handles returned by EVP_get_digestbyname() can (and not
+ * infrequently do) return ultimately unusable algorithms, to check for
+ * actual availability, one needs to use the new EVP_MD_fetch() API, or
+ * indirectly check usability by creating a concrete context. We take the
+ * latter approach here (works for 1.1.1 without #ifdef).
+ *
+ * Note that EVP_MD_CTX_{create,destroy} were renamed to, respectively,
+ * EVP_MD_CTX_{new,free} in OpenSSL 1.1.0.
+ */
+ checkok(md = EVP_get_digestbyname(mdalg));
+
+ /*
+ * Sanity check: Newer shared libraries could (hypothetical ABI break)
+ * allow larger digests, we avoid such poison algorithms.
+ */
+ checkok(EVP_MD_size(md) <= EVP_MAX_MD_SIZE);
+ checkok(mdctx = EVP_MD_CTX_new());
+ checkok(EVP_DigestInit_ex(mdctx, md, NULL));
+
+
+ if (ok && mdctxPtr != 0)
+ *mdctxPtr = mdctx;
+ else
+ EVP_MD_CTX_free(mdctx);
+ return (ok ? md : 0);
+}
+
+/* tls_serverid_digest - suffix props->serverid with parameter digest */
+
+char *tls_serverid_digest(TLS_SESS_STATE *TLScontext,
+ const TLS_CLIENT_START_PROPS *props,
+ const char *ciphers)
+{
+ EVP_MD_CTX *mdctx;
+ const char *mdalg;
+ unsigned char md_buf[EVP_MAX_MD_SIZE];
+ unsigned int md_len;
+ int ok = 1;
+ int i;
+ long sslversion;
+ VSTRING *result;
+
+ /*
+ * Try to use sha256: our serverid choice should be strong enough to
+ * resist 2nd-preimage attacks with a difficulty comparable to that of
+ * DANE TLSA digests. Failing that, we compute serverid digests with the
+ * default digest, but DANE requires sha256 and sha512, so if we must
+ * fall back to our default digest, DANE support won't be available. We
+ * panic if the fallback algorithm is not available, as it was verified
+ * available in tls_client_init() and must not simply vanish. Our
+ * provider set is not expected to change once the OpenSSL library is
+ * initialized.
+ */
+ if (tls_digest_byname(mdalg = LN_sha256, &mdctx) == 0
+ && tls_digest_byname(mdalg = props->mdalg, &mdctx) == 0)
+ msg_panic("digest algorithm \"%s\" not found", props->mdalg);
+
+ /* Salt the session lookup key with the OpenSSL runtime version. */
+ sslversion = OpenSSL_version_num();
+
+ digest_string(props->helo ? props->helo : "");
+ digest_object(&sslversion);
+ digest_string(props->protocols);
+ digest_string(ciphers);
+
+ /*
+ * Ensure separation of caches for sessions where DANE trust
+ * configuration succeeded from those where it did not. The latter
+ * should always see a certificate validation failure, both on initial
+ * handshake and on resumption.
+ */
+ digest_object(&TLScontext->must_fail);
+
+ /*
+ * DNS-based or synthetic DANE trust settings are potentially used at all
+ * levels above "encrypt".
+ */
+ if (TLScontext->level > TLS_LEV_ENCRYPT
+ && props->dane && props->dane->tlsa) {
+ digest_dane(props->dane->tlsa);
+ } else {
+ int none = 0; /* Record a TLSA RR count of zero */
+
+ digest_object(&none);
+ }
+
+ /*
+ * Include the chosen SNI name, which can affect server certificate
+ * selection.
+ */
+ if (TLScontext->level > TLS_LEV_ENCRYPT && TLScontext->peer_sni)
+ digest_string(TLScontext->peer_sni);
+ else
+ digest_string("");
+
+ checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
+ EVP_MD_CTX_destroy(mdctx);
+ if (!ok)
+ msg_fatal("error computing %s message digest", mdalg);
+
+ /* Check for OpenSSL contract violation */
+ if (md_len > EVP_MAX_MD_SIZE)
+ msg_panic("unexpectedly large %s digest size: %u", mdalg, md_len);
+
+ /*
+ * Append the digest to the serverid. We don't compare this digest to
+ * any user-specified fingerprints. Therefore, we don't need to use a
+ * colon-separated format, which saves space in the TLS session cache and
+ * makes logging of session cache lookup keys more readable.
+ *
+ * This does however duplicate a few lines of code from the digest encoder
+ * for colon-separated cert and pkey fingerprints. If that is a
+ * compelling reason to consolidate, we could use that and append the
+ * result.
+ */
+ result = vstring_alloc(strlen(props->serverid) + 1 + 2 * md_len);
+ vstring_strcpy(result, props->serverid);
+ VSTRING_ADDCH(result, '&');
+ for (i = 0; i < md_len; i++) {
+ VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0xf0) >> 4U]);
+ VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0x0f)]);
+ }
+ VSTRING_TERMINATE(result);
+ return (vstring_export(result));
+}
+
+/* tls_digest_encode - encode message digest binary blob as xx:xx:... */
+
+char *tls_digest_encode(const unsigned char *md_buf, int md_len)
+{
+ int i;
+ char *result = mymalloc(md_len * 3);
+
+ /* Check for contract violation */
+ if (md_len > EVP_MAX_MD_SIZE || md_len >= INT_MAX / 3)
+ msg_panic("unexpectedly large message digest size: %u", md_len);
+
+ /* No risk of overruns, len is bounded by OpenSSL digest length */
+ for (i = 0; i < md_len; i++) {
+ result[i * 3] = hexcodes[(md_buf[i] & 0xf0) >> 4U];
+ result[(i * 3) + 1] = hexcodes[(md_buf[i] & 0x0f)];
+ result[(i * 3) + 2] = (i + 1 != md_len) ? ':' : '\0';
+ }
+ return (result);
+}
+
+/* tls_data_fprint - compute and encode digest of binary object */
+
+static char *tls_data_fprint(const unsigned char *buf, int len, const char *mdalg)
+{
+ EVP_MD_CTX *mdctx = NULL;
+ unsigned char md_buf[EVP_MAX_MD_SIZE];
+ unsigned int md_len;
+ int ok = 1;
+
+ /* Previously available in "init" routine. */
+ if (tls_digest_byname(mdalg, &mdctx) == 0)
+ msg_panic("digest algorithm \"%s\" not found", mdalg);
+
+ digest_data(buf, len);
+ checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
+ EVP_MD_CTX_destroy(mdctx);
+ if (!ok)
+ msg_fatal("error computing %s message digest", mdalg);
+
+ return (tls_digest_encode(md_buf, md_len));
+}
+
+/* tls_cert_fprint - extract certificate fingerprint */
+
+char *tls_cert_fprint(X509 *peercert, const char *mdalg)
+{
+ int len;
+ unsigned char *buf;
+ unsigned char *buf2;
+ char *result;
+
+ len = i2d_X509(peercert, NULL);
+ buf2 = buf = mymalloc(len);
+ i2d_X509(peercert, &buf2);
+ if (buf2 - buf != len)
+ msg_panic("i2d_X509 invalid result length");
+
+ result = tls_data_fprint(buf, len, mdalg);
+ myfree(buf);
+
+ return (result);
+}
+
+/* tls_pkey_fprint - extract public key fingerprint from certificate */
+
+char *tls_pkey_fprint(X509 *peercert, const char *mdalg)
+{
+ if (var_tls_bc_pkey_fprint) {
+ const char *myname = "tls_pkey_fprint";
+ ASN1_BIT_STRING *key;
+ char *result;
+
+ key = X509_get0_pubkey_bitstr(peercert);
+ if (key == 0)
+ msg_fatal("%s: error extracting legacy public-key fingerprint: %m",
+ myname);
+
+ result = tls_data_fprint(key->data, key->length, mdalg);
+ return (result);
+ } else {
+ int len;
+ unsigned char *buf;
+ unsigned char *buf2;
+ char *result;
+
+ len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), NULL);
+ buf2 = buf = mymalloc(len);
+ i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), &buf2);
+ if (buf2 - buf != len)
+ msg_panic("i2d_X509_PUBKEY invalid result length");
+
+ result = tls_data_fprint(buf, len, mdalg);
+ myfree(buf);
+ return (result);
+ }
+}
+
+#endif
diff --git a/src/tls/tls_level.c b/src/tls/tls_level.c
new file mode 100644
index 0000000..eec15fd
--- /dev/null
+++ b/src/tls/tls_level.c
@@ -0,0 +1,95 @@
+/*++
+/* NAME
+/* tls_level 3
+/* SUMMARY
+/* TLS security level conversion
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* int tls_level_lookup(name)
+/* const char *name;
+/*
+/* const char *str_tls_level(level)
+/* int level;
+/* DESCRIPTION
+/* The functions in this module convert TLS levels from symbolic
+/* name to internal form and vice versa.
+/*
+/* tls_level_lookup() converts a TLS level from symbolic name
+/* to internal form. When an unknown level is specified,
+/* tls_level_lookup() logs no warning, and returns TLS_LEV_INVALID.
+/*
+/* str_tls_level() converts a TLS level from internal form to
+/* symbolic name. The result is a null pointer for an unknown
+/* level. The "halfdane" level is not a valid user-selected TLS level,
+/* it is generated internally and is only valid output for the
+/* str_tls_level() function.
+/* SEE ALSO
+/* name_code(3) name to number mapping
+/* 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
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <name_code.h>
+
+/* TLS library. */
+
+#include <tls.h>
+
+/* Application-specific. */
+
+ /*
+ * Numerical order of levels is critical (see tls.h):
+ *
+ * - With "may" and higher, TLS is enabled.
+ *
+ * - With "encrypt" and higher, TLS is required.
+ *
+ * - With "fingerprint" and higher, the peer certificate must match.
+ *
+ * - With "dane" and higher, the peer certificate must also be trusted,
+ * possibly via TLSA RRs that make it its own authority.
+ *
+ * The smtp(8) client will report trust failure in preference to reporting
+ * failure to match, so we make "dane" larger than "fingerprint".
+ */
+static const NAME_CODE tls_level_table[] = {
+ "none", TLS_LEV_NONE,
+ "may", TLS_LEV_MAY,
+ "encrypt", TLS_LEV_ENCRYPT,
+ "fingerprint", TLS_LEV_FPRINT,
+ "halfdane", TLS_LEV_HALF_DANE, /* output only */
+ "dane", TLS_LEV_DANE,
+ "dane-only", TLS_LEV_DANE_ONLY,
+ "verify", TLS_LEV_VERIFY,
+ "secure", TLS_LEV_SECURE,
+ 0, TLS_LEV_INVALID,
+};
+
+int tls_level_lookup(const char *name)
+{
+ int level = name_code(tls_level_table, NAME_CODE_FLAG_NONE, name);
+
+ return ((level != TLS_LEV_HALF_DANE) ? level : TLS_LEV_INVALID);
+}
+
+const char *str_tls_level(int level)
+{
+ return (str_name_code(tls_level_table, level));
+}
diff --git a/src/tls/tls_mgr.c b/src/tls/tls_mgr.c
new file mode 100644
index 0000000..b69ddf6
--- /dev/null
+++ b/src/tls/tls_mgr.c
@@ -0,0 +1,486 @@
+/*++
+/* NAME
+/* tls_mgr 3
+/* SUMMARY
+/* tlsmgr client interface
+/* SYNOPSIS
+/* #include <tls_mgr.h>
+/*
+/* int tls_mgr_seed(buf, len)
+/* VSTRING *buf;
+/* int len;
+/*
+/* int tls_mgr_policy(cache_type, cachable, timeout)
+/* const char *cache_type;
+/* int *cachable;
+/* int *timeout;
+/*
+/* int tls_mgr_update(cache_type, cache_id, buf, len)
+/* const char *cache_type;
+/* const char *cache_id;
+/* const char *buf;
+/* ssize_t len;
+/*
+/* int tls_mgr_lookup(cache_type, cache_id, buf)
+/* const char *cache_type;
+/* const char *cache_id;
+/* VSTRING *buf;
+/*
+/* int tls_mgr_delete(cache_type, cache_id)
+/* const char *cache_type;
+/* const char *cache_id;
+/*
+/* TLS_TICKET_KEY *tls_mgr_key(keyname, timeout)
+/* unsigned char *keyname;
+/* int timeout;
+/* DESCRIPTION
+/* These routines communicate with the tlsmgr(8) server for
+/* entropy and session cache management. Since these are
+/* non-critical services, requests are allowed to fail without
+/* disrupting Postfix.
+/*
+/* tls_mgr_seed() requests entropy from the tlsmgr(8)
+/* Pseudo Random Number Generator (PRNG) pool.
+/*
+/* tls_mgr_policy() requests the session caching policy.
+/*
+/* tls_mgr_lookup() loads the specified session from
+/* the specified session cache.
+/*
+/* tls_mgr_update() saves the specified session to
+/* the specified session cache.
+/*
+/* tls_mgr_delete() removes specified session from
+/* the specified session cache.
+/*
+/* tls_mgr_key() is used to retrieve the current TLS session ticket
+/* encryption or decryption keys.
+/*
+/* Arguments:
+/* .IP cache_type
+/* One of TLS_MGR_SCACHE_SMTPD, TLS_MGR_SCACHE_SMTP or
+/* TLS_MGR_SCACHE_LMTP.
+/* .IP cachable
+/* Pointer to int, set non-zero if the requested cache_type
+/* is enabled.
+/* .IP timeout
+/* Pointer to int, returns the cache entry timeout.
+/* .IP cache_id
+/* The session cache lookup key.
+/* .IP buf
+/* The result or input buffer.
+/* .IP len
+/* The length of the input buffer, or the amount of data requested.
+/* .IP keyname
+/* Is null when requesting the current encryption keys. Otherwise,
+/* keyname is a pointer to an array of TLS_TICKET_NAMELEN unsigned
+/* chars (not NUL terminated) that is an identifier for a key
+/* previously used to encrypt a session ticket. When encrypting
+/* a null result indicates that session tickets are not supported, when
+/* decrypting it indicates that no matching keys were found.
+/* .IP timeout
+/* The encryption key timeout. Once a key has been active for this many
+/* seconds it is retired and used only for decrypting previously issued
+/* session tickets for another timeout seconds, and is then destroyed.
+/* The timeout must not be longer than half the SSL session lifetime.
+/* DIAGNOSTICS
+/* All client functions return one of the following status codes:
+/* .IP TLS_MGR_STAT_OK
+/* The request completed, and the requested operation was
+/* successful (for example, the requested session was found,
+/* or the specified session was saved or removed).
+/* .IP TLS_MGR_STAT_ERR
+/* The request completed, but the requested operation failed
+/* (for example, the requested object was not found or the
+/* specified session was not saved or removed).
+/* .IP TLS_MGR_STAT_FAIL
+/* The request could not complete (the client could not
+/* communicate with the tlsmgr(8) server).
+/* SEE ALSO
+/* tlsmgr(8) TLS session and PRNG management
+/* 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>
+
+#ifdef USE_TLS
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <attr.h>
+#include <attr_clnt.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* TLS library. */
+#include <tls_mgr.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+static ATTR_CLNT *tls_mgr;
+
+/* tls_mgr_handshake - receive server protocol announcement */
+
+static int tls_mgr_handshake(VSTREAM *stream)
+{
+ return (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSMGR),
+ ATTR_TYPE_END));
+}
+
+/* tls_mgr_open - create client handle */
+
+static void tls_mgr_open(void)
+{
+ char *service;
+
+ /*
+ * Sanity check.
+ */
+ if (tls_mgr != 0)
+ msg_panic("tls_mgr_open: multiple initialization");
+
+ /*
+ * Use whatever IPC is preferred for internal use: UNIX-domain sockets or
+ * Solaris streams.
+ */
+ service = concatenate("local:" TLS_MGR_CLASS "/", var_tls_mgr_service,
+ (char *) 0);
+ tls_mgr = attr_clnt_create(service, var_ipc_timeout,
+ var_ipc_idle_limit, var_ipc_ttl_limit);
+ myfree(service);
+
+ attr_clnt_control(tls_mgr,
+ ATTR_CLNT_CTL_PROTO, attr_vprint, attr_vscan,
+ ATTR_CLNT_CTL_HANDSHAKE, tls_mgr_handshake,
+ ATTR_CLNT_CTL_END);
+}
+
+/* tls_mgr_seed - request PRNG seed */
+
+int tls_mgr_seed(VSTRING *buf, int len)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Request seed.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request attributes */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_SEED),
+ SEND_ATTR_INT(TLS_MGR_ATTR_SIZE, len),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_SEED, buf),
+ ATTR_TYPE_END) != 2)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_policy - request caching policy */
+
+int tls_mgr_policy(const char *cache_type, int *cachable, int *timeout)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Request policy.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request attributes */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_POLICY),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_INT(TLS_MGR_ATTR_CACHABLE, cachable),
+ RECV_ATTR_INT(TLS_MGR_ATTR_SESSTOUT, timeout),
+ ATTR_TYPE_END) != 3)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_lookup - request cached session */
+
+int tls_mgr_lookup(const char *cache_type, const char *cache_id,
+ VSTRING *buf)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_LOOKUP),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_SESSION, buf),
+ ATTR_TYPE_END) != 2)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_update - save session to cache */
+
+int tls_mgr_update(const char *cache_type, const char *cache_id,
+ const char *buf, ssize_t len)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_UPDATE),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_SESSION, len, buf),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_delete - remove cached session */
+
+int tls_mgr_delete(const char *cache_type, const char *cache_id)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_DELETE),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* request_scache_key - ask tlsmgr(8) for matching key */
+
+static TLS_TICKET_KEY *request_scache_key(unsigned char *keyname)
+{
+ TLS_TICKET_KEY tmp;
+ static VSTRING *keybuf;
+ char *name;
+ size_t len;
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ if (keybuf == 0)
+ keybuf = vstring_alloc(sizeof(tmp));
+
+ /* In tlsmgr requests we encode null key names as empty strings. */
+ name = keyname ? (char *) keyname : "";
+ len = keyname ? TLS_TICKET_NAMELEN : 0;
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_TKTKEY),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, len, name),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_KEYBUF, keybuf),
+ ATTR_TYPE_END) != 2
+ || status != TLS_MGR_STAT_OK
+ || LEN(keybuf) != sizeof(tmp))
+ return (0);
+
+ memcpy((void *) &tmp, STR(keybuf), sizeof(tmp));
+ return (tls_scache_key_rotate(&tmp));
+}
+
+/* tls_mgr_key - session ticket key lookup, local cache, then tlsmgr(8) */
+
+TLS_TICKET_KEY *tls_mgr_key(unsigned char *keyname, int timeout)
+{
+ TLS_TICKET_KEY *key = 0;
+ time_t now = time((time_t *) 0);
+
+ /* A zero timeout disables session tickets. */
+ if (timeout <= 0)
+ return (0);
+
+ if ((key = tls_scache_key(keyname, now, timeout)) == 0)
+ key = request_scache_key(keyname);
+ return (key);
+}
+
+#ifdef TEST
+
+/* System library. */
+
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <argv.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <hex_code.h>
+
+/* Global library. */
+
+#include <config.h>
+
+/* Application-specific. */
+
+int main(int unused_ac, char **av)
+{
+ VSTRING *inbuf = vstring_alloc(10);
+ int status;
+ ARGV *argv = 0;
+
+ msg_vstream_init(av[0], VSTREAM_ERR);
+
+ msg_verbose = 3;
+
+ mail_conf_read();
+ msg_info("using config files in %s", var_config_dir);
+
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) {
+ argv = argv_split(STR(inbuf), CHARS_SPACE);
+ if (argv->argc == 0) {
+ argv_free(argv);
+ continue;
+ }
+#define COMMAND(argv, str, len) \
+ (strcasecmp(argv->argv[0], str) == 0 && argv->argc == len)
+
+ if (COMMAND(argv, "policy", 2)) {
+ int cachable;
+ int timeout;
+
+ status = tls_mgr_policy(argv->argv[1], &cachable, &timeout);
+ vstream_printf("status=%d cachable=%d timeout=%d\n",
+ status, cachable, timeout);
+ } else if (COMMAND(argv, "seed", 2)) {
+ VSTRING *buf = vstring_alloc(10);
+ VSTRING *hex = vstring_alloc(10);
+ int len = atoi(argv->argv[1]);
+
+ status = tls_mgr_seed(buf, len);
+ hex_encode(hex, STR(buf), LEN(buf));
+ vstream_printf("status=%d seed=%s\n", status, STR(hex));
+ vstring_free(hex);
+ vstring_free(buf);
+ } else if (COMMAND(argv, "lookup", 3)) {
+ VSTRING *buf = vstring_alloc(10);
+
+ status = tls_mgr_lookup(argv->argv[1], argv->argv[2], buf);
+ vstream_printf("status=%d session=%.*s\n",
+ status, (int) LEN(buf), STR(buf));
+ vstring_free(buf);
+ } else if (COMMAND(argv, "update", 4)) {
+ status = tls_mgr_update(argv->argv[1], argv->argv[2],
+ argv->argv[3], strlen(argv->argv[3]));
+ vstream_printf("status=%d\n", status);
+ } else if (COMMAND(argv, "delete", 3)) {
+ status = tls_mgr_delete(argv->argv[1], argv->argv[2]);
+ vstream_printf("status=%d\n", status);
+ } else {
+ vstream_printf("usage:\n"
+ "seed byte_count\n"
+ "policy smtpd|smtp|lmtp\n"
+ "lookup smtpd|smtp|lmtp cache_id\n"
+ "update smtpd|smtp|lmtp cache_id session\n"
+ "delete smtpd|smtp|lmtp cache_id\n");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(argv);
+ }
+
+ vstring_free(inbuf);
+ return (0);
+}
+
+#endif /* TEST */
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_mgr.h b/src/tls/tls_mgr.h
new file mode 100644
index 0000000..c584175
--- /dev/null
+++ b/src/tls/tls_mgr.h
@@ -0,0 +1,71 @@
+#ifndef _TLS_MGR_CLNT_H_INCLUDED_
+#define _TLS_MGR_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_mgr 3h
+/* SUMMARY
+/* tlsmgr client interface
+/* SYNOPSIS
+/* #include <tls_mgr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * TLS library
+ */
+#include <tls_scache.h> /* Session ticket keys */
+
+ /*
+ * TLS manager protocol.
+ */
+#define TLS_MGR_SERVICE "tlsmgr"
+#define TLS_MGR_CLASS "private"
+
+#define TLS_MGR_ATTR_REQ "request"
+#define TLS_MGR_REQ_SEED "seed"
+#define TLS_MGR_REQ_POLICY "policy"
+#define TLS_MGR_REQ_LOOKUP "lookup"
+#define TLS_MGR_REQ_UPDATE "update"
+#define TLS_MGR_REQ_DELETE "delete"
+#define TLS_MGR_REQ_TKTKEY "tktkey"
+#define TLS_MGR_ATTR_CACHABLE "cachable"
+#define TLS_MGR_ATTR_CACHE_TYPE "cache_type"
+#define TLS_MGR_ATTR_SEED "seed"
+#define TLS_MGR_ATTR_CACHE_ID "cache_id"
+#define TLS_MGR_ATTR_SESSION "session"
+#define TLS_MGR_ATTR_SIZE "size"
+#define TLS_MGR_ATTR_STATUS "status"
+#define TLS_MGR_ATTR_KEYNAME "keyname"
+#define TLS_MGR_ATTR_KEYBUF "keybuf"
+#define TLS_MGR_ATTR_SESSTOUT "timeout"
+
+ /*
+ * TLS manager request status codes.
+ */
+#define TLS_MGR_STAT_OK 0 /* success */
+#define TLS_MGR_STAT_ERR (-1) /* object not found */
+#define TLS_MGR_STAT_FAIL (-2) /* protocol error */
+
+ /*
+ * Functional interface.
+ */
+extern int tls_mgr_seed(VSTRING *, int);
+extern int tls_mgr_policy(const char *, int *, int *);
+extern int tls_mgr_lookup(const char *, const char *, VSTRING *);
+extern int tls_mgr_update(const char *, const char *, const char *, ssize_t);
+extern int tls_mgr_delete(const char *, const char *);
+extern TLS_TICKET_KEY *tls_mgr_key(unsigned char *, int);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/tls/tls_misc.c b/src/tls/tls_misc.c
new file mode 100644
index 0000000..00ce10f
--- /dev/null
+++ b/src/tls/tls_misc.c
@@ -0,0 +1,1712 @@
+/*++
+/* NAME
+/* tls_misc 3
+/* SUMMARY
+/* miscellaneous TLS support routines
+/* SYNOPSIS
+/* .SH Public functions
+/* .nf
+/* .na
+/* #include <tls.h>
+/*
+/* void tls_log_summary(role, usage, TLScontext)
+/* TLS_ROLE role;
+/* TLS_USAGE usage;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* const char *tls_compile_version(void)
+/*
+/* const char *tls_run_version(void)
+/*
+/* const char **tls_pkey_algorithms(void)
+/*
+/* void tls_pre_jail_init(TLS_ROLE)
+/* TLS_ROLE role;
+/*
+/* .SH Internal functions
+/* .nf
+/* .na
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* char *var_tls_cnf_file;
+/* char *var_tls_cnf_name;
+/* char *var_tls_high_clist;
+/* char *var_tls_medium_clist;
+/* char *var_tls_low_clist;
+/* char *var_tls_export_clist;
+/* char *var_tls_null_clist;
+/* char *var_tls_eecdh_auto;
+/* char *var_tls_eecdh_strong;
+/* char *var_tls_eecdh_ultra;
+/* char *var_tls_dane_digests;
+/* int var_tls_daemon_rand_bytes;
+/* bool var_tls_append_def_CA;
+/* bool var_tls_preempt_clist;
+/* bool var_tls_bc_pkey_fprint;
+/* bool var_tls_multi_wildcard;
+/* char *var_tls_mgr_service;
+/* char *var_tls_tkt_cipher;
+/* char *var_openssl_path;
+/* char *var_tls_server_sni_maps;
+/* bool var_tls_fast_shutdown;
+/*
+/* TLS_APPL_STATE *tls_alloc_app_context(ssl_ctx, log_mask)
+/* SSL_CTX *ssl_ctx;
+/* int log_mask;
+/*
+/* void tls_free_app_context(app_ctx)
+/* void *app_ctx;
+/*
+/* TLS_SESS_STATE *tls_alloc_sess_context(log_mask, namaddr)
+/* int log_mask;
+/* const char *namaddr;
+/*
+/* void tls_free_context(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_check_version()
+/*
+/* long tls_bug_bits()
+/*
+/* void tls_param_init()
+/*
+/* int tls_library_init(void)
+/*
+/* int tls_proto_mask_lims(plist, floor, ceiling)
+/* const char *plist;
+/* int *floor;
+/* int *ceiling;
+/*
+/* int tls_cipher_grade(name)
+/* const char *name;
+/*
+/* const char *str_tls_cipher_grade(grade)
+/* int grade;
+/*
+/* const char *tls_set_ciphers(TLScontext, grade, exclusions)
+/* TLS_SESS_STATE *TLScontext;
+/* int grade;
+/* const char *exclusions;
+/*
+/* void tls_get_signature_params(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_print_errors()
+/*
+/* void tls_info_callback(ssl, where, ret)
+/* const SSL *ssl; /* unused */
+/* int where;
+/* int ret;
+/*
+/* long tls_bio_dump_cb(bio, cmd, argp, len, argi, argl, ret, processed)
+/* BIO *bio;
+/* int cmd;
+/* const char *argp;
+/* size_t len;
+/* int argi;
+/* long argl; /* unused */
+/* int ret;
+/* size_t *processed;
+/*
+/* int tls_log_mask(log_param, log_level)
+/* const char *log_param;
+/* const char *log_level;
+/*
+/* void tls_update_app_logmask(app_ctx, log_mask)
+/* TLS_APPL_STATE *app_ctx;
+/* int log_mask;
+/*
+/* const EVP_MD *tls_validate_digest(dgst)
+/* const char *dgst;
+/* DESCRIPTION
+/* This module implements public and internal routines that
+/* support the TLS client and server.
+/*
+/* tls_log_summary() logs a summary of a completed TLS connection.
+/* The "role" argument must be TLS_ROLE_CLIENT for outgoing client
+/* connections, or TLS_ROLE_SERVER for incoming server connections,
+/* and the "usage" must be TLS_USAGE_NEW or TLS_USAGE_USED.
+/*
+/* tls_compile_version() returns a text string description of
+/* the compile-time TLS library.
+/*
+/* tls_run_version() is just tls_compile_version() but with the runtime
+/* version instead of the compile-time version.
+/*
+/* tls_pkey_algorithms() returns a pointer to null-terminated
+/* array of string constants with the names of the supported
+/* public-key algorithms.
+/*
+/* tls_alloc_app_context() creates an application context that
+/* holds the SSL context for the application and related cached state.
+/*
+/* tls_free_app_context() deallocates the application context and its
+/* contents (the application context is stored outside the TLS library).
+/*
+/* tls_alloc_sess_context() creates an initialized TLS session context
+/* structure with the specified log mask and peer name[addr].
+/*
+/* tls_free_context() destroys a TLScontext structure
+/* together with OpenSSL structures that are attached to it.
+/*
+/* tls_check_version() logs a warning when the run-time OpenSSL
+/* library differs in its major, minor or micro number from
+/* the compile-time OpenSSL headers.
+/*
+/* tls_bug_bits() returns the bug compatibility mask appropriate
+/* for the run-time library. Some of the bug work-arounds are
+/* not appropriate for some library versions.
+/*
+/* tls_param_init() loads main.cf parameters used internally in
+/* TLS library. Any errors are fatal.
+/*
+/* tls_library_init() initializes the OpenSSL library, optionally
+/* loading an OpenSSL configuration file.
+/*
+/* tls_pre_jail_init() opens any tables that need to be opened before
+/* entering a chroot jail. The "role" parameter must be TLS_ROLE_CLIENT
+/* for clients and TLS_ROLE_SERVER for servers. Any errors are fatal.
+/*
+/* tls_proto_mask_lims() returns a bitmask of excluded protocols, and
+/* and the protocol version floor/ceiling, given a list (plist) of
+/* protocols to include or (preceded by a '!') exclude, or constraints
+/* of the form '>=name', '<=name', '>=hexvalue', '<=hexvalue'. If "plist"
+/* contains invalid protocol names, TLS_PROTOCOL_INVALID is returned and
+/* no warning is logged.
+/*
+/* tls_cipher_grade() converts a case-insensitive cipher grade
+/* name (high, medium, low, export, null) to the corresponding
+/* TLS_CIPHER_ constant. When the input specifies an unrecognized
+/* grade, tls_cipher_grade() logs no warning, and returns
+/* TLS_CIPHER_NONE.
+/*
+/* str_tls_cipher_grade() converts a cipher grade to a name.
+/* When the input specifies an undefined grade, str_tls_cipher_grade()
+/* logs no warning, returns a null pointer.
+/*
+/* tls_set_ciphers() applies the requested cipher grade and exclusions
+/* to the provided TLS session context, returning the resulting cipher
+/* list string. The return value is the cipherlist used and is
+/* overwritten upon each call. When the input is invalid,
+/* tls_set_ciphers() logs a warning, and returns a null result.
+/*
+/* tls_get_signature_params() updates the "TLScontext" with handshake
+/* signature parameters pertaining to TLS 1.3, where the ciphersuite
+/* no longer describes the asymmetric algorithms employed in the
+/* handshake, which are negotiated separately. This function
+/* has no effect for TLS 1.2 and earlier.
+/*
+/* tls_print_errors() queries the OpenSSL error stack,
+/* logs the error messages, and clears the error stack.
+/*
+/* tls_info_callback() is a call-back routine for the
+/* SSL_CTX_set_info_callback() routine. It logs SSL events
+/* to the Postfix logfile.
+/*
+/* tls_bio_dump_cb() is a call-back routine for the
+/* BIO_set_callback() routine. It logs SSL content to the
+/* Postfix logfile.
+/*
+/* tls_log_mask() converts a TLS log_level value from string
+/* to mask. The main.cf parameter name is passed along for
+/* diagnostics.
+/*
+/* tls_update_app_logmask() changes the log mask of the
+/* application TLS context to the new setting.
+/*
+/* tls_validate_digest() returns a static handle for the named
+/* digest algorithm, or NULL on error.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <vstream.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <argv.h>
+#include <name_mask.h>
+#include <name_code.h>
+#include <dict.h>
+#include <valid_hostname.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <maps.h>
+
+ /*
+ * TLS library.
+ */
+#define TLS_INTERNAL
+#include <tls.h>
+
+ /* Application-specific. */
+
+ /*
+ * Tunable parameters.
+ */
+char *var_tls_cnf_file;
+char *var_tls_cnf_name;
+char *var_tls_high_clist;
+char *var_tls_medium_clist;
+char *var_tls_low_clist;
+char *var_tls_export_clist;
+char *var_tls_null_clist;
+int var_tls_daemon_rand_bytes;
+char *var_tls_eecdh_auto;
+char *var_tls_eecdh_strong;
+char *var_tls_eecdh_ultra;
+char *var_tls_dane_digests;
+bool var_tls_append_def_CA;
+char *var_tls_bug_tweaks;
+char *var_tls_ssl_options;
+bool var_tls_bc_pkey_fprint;
+bool var_tls_multi_wildcard;
+char *var_tls_mgr_service;
+char *var_tls_tkt_cipher;
+char *var_openssl_path;
+char *var_tls_server_sni_maps;
+bool var_tls_fast_shutdown;
+bool var_tls_preempt_clist;
+
+#ifdef USE_TLS
+
+static MAPS *tls_server_sni_maps;
+
+ /*
+ * Index to attach TLScontext pointers to SSL objects, so that they can be
+ * accessed by call-back routines.
+ */
+int TLScontext_index = -1;
+
+ /*
+ * Protocol name <=> mask conversion.
+ */
+static const NAME_CODE protocol_table[] = {
+ SSL_TXT_SSLV2, TLS_PROTOCOL_SSLv2,
+ SSL_TXT_SSLV3, TLS_PROTOCOL_SSLv3,
+ SSL_TXT_TLSV1, TLS_PROTOCOL_TLSv1,
+ SSL_TXT_TLSV1_1, TLS_PROTOCOL_TLSv1_1,
+ SSL_TXT_TLSV1_2, TLS_PROTOCOL_TLSv1_2,
+ TLS_PROTOCOL_TXT_TLSV1_3, TLS_PROTOCOL_TLSv1_3,
+ 0, TLS_PROTOCOL_INVALID,
+};
+
+/*
+ * Protocol name => numeric version, for MinProtocol and MaxProtocol
+ */
+static const NAME_CODE tls_version_table[] = {
+ "None", 0,
+ SSL_TXT_SSLV3, SSL3_VERSION,
+ SSL_TXT_TLSV1, TLS1_VERSION,
+ SSL_TXT_TLSV1_1, TLS1_1_VERSION,
+ SSL_TXT_TLSV1_2, TLS1_2_VERSION,
+ TLS_PROTOCOL_TXT_TLSV1_3, TLS1_3_VERSION,
+ 0, -1,
+};
+
+ /*
+ * SSL_OP_MUMBLE bug work-around name <=> mask conversion.
+ */
+#define NAMEBUG(x) #x, SSL_OP_##x
+static const LONG_NAME_MASK ssl_bug_tweaks[] = {
+
+#ifndef SSL_OP_MICROSOFT_SESS_ID_BUG
+#define SSL_OP_MICROSOFT_SESS_ID_BUG 0
+#endif
+ NAMEBUG(MICROSOFT_SESS_ID_BUG),
+
+#ifndef SSL_OP_NETSCAPE_CHALLENGE_BUG
+#define SSL_OP_NETSCAPE_CHALLENGE_BUG 0
+#endif
+ NAMEBUG(NETSCAPE_CHALLENGE_BUG),
+
+#ifndef SSL_OP_LEGACY_SERVER_CONNECT
+#define SSL_OP_LEGACY_SERVER_CONNECT 0
+#endif
+ NAMEBUG(LEGACY_SERVER_CONNECT),
+
+#ifndef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+#define SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG 0
+#endif
+ NAMEBUG(NETSCAPE_REUSE_CIPHER_CHANGE_BUG),
+ "CVE-2010-4180", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG,
+
+#ifndef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+#define SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG 0
+#endif
+ NAMEBUG(SSLREF2_REUSE_CERT_TYPE_BUG),
+
+#ifndef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+#define SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER 0
+#endif
+ NAMEBUG(MICROSOFT_BIG_SSLV3_BUFFER),
+
+#ifndef SSL_OP_MSIE_SSLV2_RSA_PADDING
+#define SSL_OP_MSIE_SSLV2_RSA_PADDING 0
+#endif
+ NAMEBUG(MSIE_SSLV2_RSA_PADDING),
+ "CVE-2005-2969", SSL_OP_MSIE_SSLV2_RSA_PADDING,
+
+#ifndef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+#define SSL_OP_SSLEAY_080_CLIENT_DH_BUG 0
+#endif
+ NAMEBUG(SSLEAY_080_CLIENT_DH_BUG),
+
+#ifndef SSL_OP_TLS_D5_BUG
+#define SSL_OP_TLS_D5_BUG 0
+#endif
+ NAMEBUG(TLS_D5_BUG),
+
+#ifndef SSL_OP_TLS_BLOCK_PADDING_BUG
+#define SSL_OP_TLS_BLOCK_PADDING_BUG 0
+#endif
+ NAMEBUG(TLS_BLOCK_PADDING_BUG),
+
+#ifndef SSL_OP_TLS_ROLLBACK_BUG
+#define SSL_OP_TLS_ROLLBACK_BUG 0
+#endif
+ NAMEBUG(TLS_ROLLBACK_BUG),
+
+#ifndef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+#define SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS 0
+#endif
+ NAMEBUG(DONT_INSERT_EMPTY_FRAGMENTS),
+
+#ifndef SSL_OP_CRYPTOPRO_TLSEXT_BUG
+#define SSL_OP_CRYPTOPRO_TLSEXT_BUG 0
+#endif
+ NAMEBUG(CRYPTOPRO_TLSEXT_BUG),
+
+#ifndef SSL_OP_TLSEXT_PADDING
+#define SSL_OP_TLSEXT_PADDING 0
+#endif
+ NAMEBUG(TLSEXT_PADDING),
+
+#if 0
+
+ /*
+ * XXX: New with OpenSSL 1.1.1, this is turned on implicitly in
+ * SSL_CTX_new() and is not included in SSL_OP_ALL. Allowing users to
+ * disable this would thus be a code change that would require clearing
+ * bug work-around bits in SSL_CTX, after setting SSL_OP_ALL. Since this
+ * is presumably required for TLS 1.3 on today's Internet, the code
+ * change will be done separately later. For now this implicit bug
+ * work-around cannot be disabled via supported Postfix mechanisms.
+ */
+#ifndef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+#define SSL_OP_ENABLE_MIDDLEBOX_COMPAT 0
+#endif
+ NAMEBUG(ENABLE_MIDDLEBOX_COMPAT),
+#endif
+
+ 0, 0,
+};
+
+ /*
+ * SSL_OP_MUMBLE option name <=> mask conversion for options that are not
+ * (or may in the future not be) in SSL_OP_ALL. These enable optional
+ * behavior, rather than bug interoperability work-arounds.
+ */
+#define NAME_SSL_OP(x) #x, SSL_OP_##x
+static const LONG_NAME_MASK ssl_op_tweaks[] = {
+
+#ifndef SSL_OP_LEGACY_SERVER_CONNECT
+#define SSL_OP_LEGACY_SERVER_CONNECT 0
+#endif
+ NAME_SSL_OP(LEGACY_SERVER_CONNECT),
+
+#ifndef SSL_OP_NO_TICKET
+#define SSL_OP_NO_TICKET 0
+#endif
+ NAME_SSL_OP(NO_TICKET),
+
+#ifndef SSL_OP_NO_COMPRESSION
+#define SSL_OP_NO_COMPRESSION 0
+#endif
+ NAME_SSL_OP(NO_COMPRESSION),
+
+#ifndef SSL_OP_NO_RENEGOTIATION
+#define SSL_OP_NO_RENEGOTIATION 0
+#endif
+ NAME_SSL_OP(NO_RENEGOTIATION),
+
+#ifndef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+#define SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION 0
+#endif
+ NAME_SSL_OP(NO_SESSION_RESUMPTION_ON_RENEGOTIATION),
+
+#ifndef SSL_OP_PRIORITIZE_CHACHA
+#define SSL_OP_PRIORITIZE_CHACHA 0
+#endif
+ NAME_SSL_OP(PRIORITIZE_CHACHA),
+
+#ifndef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+#define SSL_OP_ENABLE_MIDDLEBOX_COMPAT 0
+#endif
+ NAME_SSL_OP(ENABLE_MIDDLEBOX_COMPAT),
+
+ 0, 0,
+};
+
+ /*
+ * Once these have been a NOOP long enough, they might some day be removed
+ * from OpenSSL. The defines below will avoid bitrot issues if/when that
+ * happens.
+ */
+#ifndef SSL_OP_SINGLE_DH_USE
+#define SSL_OP_SINGLE_DH_USE 0
+#endif
+#ifndef SSL_OP_SINGLE_ECDH_USE
+#define SSL_OP_SINGLE_ECDH_USE 0
+#endif
+
+ /*
+ * Ciphersuite name <=> code conversion.
+ */
+const NAME_CODE tls_cipher_grade_table[] = {
+ "high", TLS_CIPHER_HIGH,
+ "medium", TLS_CIPHER_MEDIUM,
+ "low", TLS_CIPHER_LOW,
+ "export", TLS_CIPHER_EXPORT,
+ "null", TLS_CIPHER_NULL,
+ "invalid", TLS_CIPHER_NONE,
+ 0, TLS_CIPHER_NONE,
+};
+
+ /*
+ * Log keyword <=> mask conversion.
+ */
+#define TLS_LOG_0 TLS_LOG_NONE
+#define TLS_LOG_1 TLS_LOG_SUMMARY
+#define TLS_LOG_2 (TLS_LOG_1 | TLS_LOG_VERBOSE | TLS_LOG_CACHE | TLS_LOG_DEBUG)
+#define TLS_LOG_3 (TLS_LOG_2 | TLS_LOG_TLSPKTS)
+#define TLS_LOG_4 (TLS_LOG_3 | TLS_LOG_ALLPKTS)
+
+static const NAME_MASK tls_log_table[] = {
+ "0", TLS_LOG_0,
+ "none", TLS_LOG_NONE,
+ "1", TLS_LOG_1,
+ "routine", TLS_LOG_1,
+ "2", TLS_LOG_2,
+ "debug", TLS_LOG_2,
+ "3", TLS_LOG_3,
+ "ssl-expert", TLS_LOG_3,
+ "4", TLS_LOG_4,
+ "ssl-developer", TLS_LOG_4,
+ "5", TLS_LOG_4, /* for good measure */
+ "6", TLS_LOG_4, /* for good measure */
+ "7", TLS_LOG_4, /* for good measure */
+ "8", TLS_LOG_4, /* for good measure */
+ "9", TLS_LOG_4, /* for good measure */
+ "summary", TLS_LOG_SUMMARY,
+ "untrusted", TLS_LOG_UNTRUSTED,
+ "peercert", TLS_LOG_PEERCERT,
+ "certmatch", TLS_LOG_CERTMATCH,
+ "verbose", TLS_LOG_VERBOSE, /* Postfix TLS library verbose */
+ "cache", TLS_LOG_CACHE,
+ "dane", TLS_LOG_DANE, /* DANE policy construction */
+ "ssl-debug", TLS_LOG_DEBUG, /* SSL library debug/verbose */
+ "ssl-handshake-packet-dump", TLS_LOG_TLSPKTS,
+ "ssl-session-packet-dump", TLS_LOG_TLSPKTS | TLS_LOG_ALLPKTS,
+ 0, 0,
+};
+
+ /*
+ * Parsed OpenSSL version number.
+ */
+typedef struct {
+ int major;
+ int minor;
+ int micro;
+ int patch;
+ int status;
+} TLS_VINFO;
+
+/* tls_log_mask - Convert user TLS loglevel to internal log feature mask */
+
+int tls_log_mask(const char *log_param, const char *log_level)
+{
+ int mask;
+
+ mask = name_mask_opt(log_param, tls_log_table, log_level,
+ NAME_MASK_ANY_CASE | NAME_MASK_RETURN);
+ return (mask);
+}
+
+/* tls_update_app_logmask - update log level after init */
+
+void tls_update_app_logmask(TLS_APPL_STATE *app_ctx, int log_mask)
+{
+ app_ctx->log_mask = log_mask;
+}
+
+/* parse_version - parse TLS protocol version name or hex number */
+
+static int parse_tls_version(const char *tok, int *version)
+{
+ int code = name_code(tls_version_table, NAME_CODE_FLAG_NONE, tok);
+ char *_end;
+ unsigned long ulval;
+
+ if (code != -1) {
+ *version = code;
+ return (0);
+ }
+ errno = 0;
+ ulval = strtoul(tok, &_end, 16);
+ if (*_end != 0
+ || (ulval == ULONG_MAX && errno == ERANGE)
+ || ulval > INT_MAX)
+ return TLS_PROTOCOL_INVALID;
+
+ *version = (int) ulval;
+ return (0);
+}
+
+/* tls_proto_mask_lims - protocols to exclude and floor/ceiling */
+
+int tls_proto_mask_lims(const char *plist, int *floor, int *ceiling)
+{
+ char *save;
+ char *tok;
+ char *cp;
+ int code;
+ int exclude = 0;
+ int include = 0;
+
+#define FREE_AND_RETURN(ptr, res) do { \
+ myfree(ptr); \
+ return (res); \
+ } while (0)
+
+ *floor = *ceiling = 0;
+
+ save = cp = mystrdup(plist);
+ while ((tok = mystrtok(&cp, CHARS_COMMA_SP ":")) != 0) {
+ if (strncmp(tok, ">=", 2) == 0)
+ code = parse_tls_version(tok + 2, floor);
+ else if (strncmp(tok, "<=", 2) == 0)
+ code = parse_tls_version(tok + 2, ceiling);
+ else if (*tok == '!')
+ exclude |= code =
+ name_code(protocol_table, NAME_CODE_FLAG_NONE, ++tok);
+ else
+ include |= code =
+ name_code(protocol_table, NAME_CODE_FLAG_NONE, tok);
+ if (code == TLS_PROTOCOL_INVALID)
+ FREE_AND_RETURN(save, TLS_PROTOCOL_INVALID);
+ }
+
+ /*
+ * When the include list is empty, use only the explicit exclusions.
+ * Otherwise, also exclude the complement of the include list from the
+ * built-in list of known protocols. There is no way to exclude protocols
+ * we don't know about at compile time, and this is unavoidable because
+ * the OpenSSL API works with compile-time *exclusion* bit-masks.
+ */
+ FREE_AND_RETURN(save,
+ (include ? (exclude | (TLS_KNOWN_PROTOCOLS & ~include)) : exclude));
+}
+
+/* tls_param_init - Load TLS related config parameters */
+
+void tls_param_init(void)
+{
+ /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TLS_CNF_FILE, DEF_TLS_CNF_FILE, &var_tls_cnf_file, 0, 0,
+ VAR_TLS_CNF_NAME, DEF_TLS_CNF_NAME, &var_tls_cnf_name, 0, 0,
+ VAR_TLS_HIGH_CLIST, DEF_TLS_HIGH_CLIST, &var_tls_high_clist, 1, 0,
+ VAR_TLS_MEDIUM_CLIST, DEF_TLS_MEDIUM_CLIST, &var_tls_medium_clist, 1, 0,
+ VAR_TLS_LOW_CLIST, DEF_TLS_LOW_CLIST, &var_tls_low_clist, 1, 0,
+ VAR_TLS_EXPORT_CLIST, DEF_TLS_EXPORT_CLIST, &var_tls_export_clist, 1, 0,
+ VAR_TLS_NULL_CLIST, DEF_TLS_NULL_CLIST, &var_tls_null_clist, 1, 0,
+ VAR_TLS_EECDH_AUTO, DEF_TLS_EECDH_AUTO, &var_tls_eecdh_auto, 1, 0,
+ VAR_TLS_EECDH_STRONG, DEF_TLS_EECDH_STRONG, &var_tls_eecdh_strong, 1, 0,
+ VAR_TLS_EECDH_ULTRA, DEF_TLS_EECDH_ULTRA, &var_tls_eecdh_ultra, 1, 0,
+ VAR_TLS_BUG_TWEAKS, DEF_TLS_BUG_TWEAKS, &var_tls_bug_tweaks, 0, 0,
+ VAR_TLS_SSL_OPTIONS, DEF_TLS_SSL_OPTIONS, &var_tls_ssl_options, 0, 0,
+ VAR_TLS_DANE_DIGESTS, DEF_TLS_DANE_DIGESTS, &var_tls_dane_digests, 1, 0,
+ VAR_TLS_MGR_SERVICE, DEF_TLS_MGR_SERVICE, &var_tls_mgr_service, 1, 0,
+ VAR_TLS_TKT_CIPHER, DEF_TLS_TKT_CIPHER, &var_tls_tkt_cipher, 0, 0,
+ VAR_OPENSSL_PATH, DEF_OPENSSL_PATH, &var_openssl_path, 1, 0,
+ 0,
+ };
+
+ /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_TLS_DAEMON_RAND_BYTES, DEF_TLS_DAEMON_RAND_BYTES, &var_tls_daemon_rand_bytes, 1, 0,
+ 0,
+ };
+
+ /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_TLS_APPEND_DEF_CA, DEF_TLS_APPEND_DEF_CA, &var_tls_append_def_CA,
+ VAR_TLS_BC_PKEY_FPRINT, DEF_TLS_BC_PKEY_FPRINT, &var_tls_bc_pkey_fprint,
+ VAR_TLS_PREEMPT_CLIST, DEF_TLS_PREEMPT_CLIST, &var_tls_preempt_clist,
+ VAR_TLS_MULTI_WILDCARD, DEF_TLS_MULTI_WILDCARD, &var_tls_multi_wildcard,
+ VAR_TLS_FAST_SHUTDOWN, DEF_TLS_FAST_SHUTDOWN, &var_tls_fast_shutdown,
+ 0,
+ };
+ static int init_done;
+
+ if (init_done)
+ return;
+ init_done = 1;
+
+ get_mail_conf_str_table(str_table);
+ get_mail_conf_int_table(int_table);
+ get_mail_conf_bool_table(bool_table);
+}
+
+/* tls_library_init - perform OpenSSL library initialization */
+
+int tls_library_init(void)
+{
+ OPENSSL_INIT_SETTINGS *init_settings;
+ char *conf_name = *var_tls_cnf_name ? var_tls_cnf_name : 0;
+ char *conf_file = 0;
+ unsigned long init_opts = 0;
+
+#define TLS_LIB_INIT_TODO (-1)
+#define TLS_LIB_INIT_ERR (0)
+#define TLS_LIB_INIT_OK (1)
+
+ static int init_res = TLS_LIB_INIT_TODO;
+
+ if (init_res != TLS_LIB_INIT_TODO)
+ return (init_res);
+
+ /*
+ * Backwards compatibility: skip this function unless the Postfix
+ * configuration actually has non-default tls_config_xxx settings.
+ */
+ if (strcmp(var_tls_cnf_file, DEF_TLS_CNF_FILE) == 0
+ && strcmp(var_tls_cnf_name, DEF_TLS_CNF_NAME) == 0) {
+ if (msg_verbose)
+ msg_info("tls_library_init: using backwards-compatible defaults");
+ return (init_res = TLS_LIB_INIT_OK);
+ }
+ if ((init_settings = OPENSSL_INIT_new()) == 0) {
+ msg_warn("error allocating OpenSSL init settings, "
+ "disabling TLS support");
+ return (init_res = TLS_LIB_INIT_ERR);
+ }
+#define TLS_LIB_INIT_RETURN(x) \
+ do { OPENSSL_INIT_free(init_settings); return (init_res = (x)); } while(0)
+
+#if OPENSSL_VERSION_NUMBER < 0x1010102fL
+
+ /*
+ * OpenSSL 1.1.0 through 1.1.1a, no support for custom configuration
+ * files, disabling loading of the file, or getting strict error
+ * handling. Thus, the only supported configuration file is "default".
+ */
+ if (strcmp(var_tls_cnf_file, "default") != 0) {
+ msg_warn("non-default %s = %s requires OpenSSL 1.1.1b or later, "
+ "disabling TLS support", VAR_TLS_CNF_FILE, var_tls_cnf_file);
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR);
+ }
+#else
+ {
+ unsigned long file_flags = 0;
+
+ /*-
+ * OpenSSL 1.1.1b or later:
+ * We can now use a non-default configuration file, or
+ * use none at all. We can also request strict error
+ * reporting.
+ */
+ if (strcmp(var_tls_cnf_file, "none") == 0) {
+ init_opts |= OPENSSL_INIT_NO_LOAD_CONFIG;
+ } else if (strcmp(var_tls_cnf_file, "default") == 0) {
+
+ /*
+ * The default global config file is optional. With "default"
+ * initialisation we don't insist on a match for the requested
+ * application name, allowing fallback to the default application
+ * name, even when a non-default application name is specified.
+ * Errors in loading the default configuration are ignored.
+ */
+ conf_file = 0;
+ file_flags |= CONF_MFLAGS_IGNORE_MISSING_FILE;
+ file_flags |= CONF_MFLAGS_DEFAULT_SECTION;
+ file_flags |= CONF_MFLAGS_IGNORE_RETURN_CODES | CONF_MFLAGS_SILENT;
+ } else if (*var_tls_cnf_file == '/') {
+
+ /*
+ * A custom config file must be present, error reporting is
+ * strict and the configuration section for the requested
+ * application name does not fall back to "openssl_conf" when
+ * missing.
+ */
+ conf_file = var_tls_cnf_file;
+ } else {
+ msg_warn("non-default %s = %s is not an absolute pathname, "
+ "disabling TLS support", VAR_TLS_CNF_FILE, var_tls_cnf_file);
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR);
+ }
+
+ OPENSSL_INIT_set_config_file_flags(init_settings, file_flags);
+ }
+#endif
+
+ if (conf_file)
+ OPENSSL_INIT_set_config_filename(init_settings, conf_file);
+ if (conf_name)
+ OPENSSL_INIT_set_config_appname(init_settings, conf_name);
+
+ if (OPENSSL_init_ssl(init_opts, init_settings) <= 0) {
+ if ((init_opts & OPENSSL_INIT_NO_LOAD_CONFIG) == 0)
+ msg_warn("error loading the '%s' settings from the %s OpenSSL "
+ "configuration file, disabling TLS support",
+ conf_name ? conf_name : "global",
+ conf_file ? conf_file : "default");
+ else
+ msg_warn("error initializing the OpenSSL library, "
+ "disabling TLS support");
+ tls_print_errors();
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR);
+ }
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_OK);
+}
+
+/* tls_pre_jail_init - Load TLS related pre-jail tables */
+
+void tls_pre_jail_init(TLS_ROLE role)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TLS_SERVER_SNI_MAPS, DEF_TLS_SERVER_SNI_MAPS, &var_tls_server_sni_maps, 0, 0,
+ 0,
+ };
+ int flags;
+
+ tls_param_init();
+
+ /* Nothing for clients at this time */
+ if (role != TLS_ROLE_SERVER)
+ return;
+
+ get_mail_conf_str_table(str_table);
+ if (*var_tls_server_sni_maps == 0)
+ return;
+
+ flags = DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX | DICT_FLAG_SRC_RHS_IS_FILE;
+ tls_server_sni_maps =
+ maps_create(VAR_TLS_SERVER_SNI_MAPS, var_tls_server_sni_maps, flags);
+}
+
+/* server_sni_callback - process client's SNI extension */
+
+static int server_sni_callback(SSL *ssl, int *alert, void *arg)
+{
+ SSL_CTX *sni_ctx = (SSL_CTX *) arg;
+ TLS_SESS_STATE *TLScontext = SSL_get_ex_data(ssl, TLScontext_index);
+ const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ const char *cp = sni;
+ const char *pem;
+
+ /* SNI is silently ignored when we don't care or is NULL or empty */
+ if (!sni_ctx || !tls_server_sni_maps || !sni || !*sni)
+ return SSL_TLSEXT_ERR_NOACK;
+
+ if (!valid_hostname(sni, DONT_GRIPE)) {
+ msg_warn("TLS SNI from %s is invalid: %s",
+ TLScontext->namaddr, sni);
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ /*
+ * With TLS 1.3, when the client's proposed key share is not supported by
+ * the server, the server may issue a HelloRetryRequest (HRR), and the
+ * client will then retry with a new key share on a curve supported by
+ * the server. This results in the SNI callback running twice for the
+ * same connection.
+ *
+ * When that happens, The client MUST send the essentially the same hello
+ * message, including the SNI name, and since we've already loaded our
+ * certificate chain, we don't need to do it again! Therefore, if we've
+ * already recorded the peer SNI name, just check that it has not
+ * changed, and return success.
+ */
+ if (TLScontext->peer_sni) {
+ if (strcmp(sni, TLScontext->peer_sni) == 0)
+ return SSL_TLSEXT_ERR_OK;
+ msg_warn("TLS SNI changed from %s initially %s, %s after hello retry",
+ TLScontext->namaddr, TLScontext->peer_sni, sni);
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ do {
+ /* Don't silently skip maps opened with the wrong flags. */
+ pem = maps_file_find(tls_server_sni_maps, cp, 0);
+ } while (!pem
+ && !tls_server_sni_maps->error
+ && (cp = strchr(cp + 1, '.')) != 0);
+
+ if (!pem) {
+ if (tls_server_sni_maps->error) {
+ msg_warn("%s: %s map lookup problem",
+ tls_server_sni_maps->title, sni);
+ *alert = SSL_AD_INTERNAL_ERROR;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ msg_info("TLS SNI %s from %s not matched, using default chain",
+ sni, TLScontext->namaddr);
+
+ /*
+ * XXX: We could lie and pretend to accept the name, but since we've
+ * previously not implemented the callback (with OpenSSL then
+ * declining the extension), and nothing bad happened, declining it
+ * explicitly should be safe.
+ */
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ SSL_set_SSL_CTX(ssl, sni_ctx);
+ if (tls_load_pem_chain(ssl, pem, sni) != 0) {
+ /* errors already logged */
+ *alert = SSL_AD_INTERNAL_ERROR;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ TLScontext->peer_sni = mystrdup(sni);
+ return SSL_TLSEXT_ERR_OK;
+}
+
+/* tls_set_ciphers - Set SSL context cipher list */
+
+const char *tls_set_ciphers(TLS_SESS_STATE *TLScontext, const char *grade,
+ const char *exclusions)
+{
+ const char *myname = "tls_set_ciphers";
+ static VSTRING *buf;
+ char *save;
+ char *cp;
+ char *tok;
+
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ VSTRING_RESET(buf);
+
+ switch (tls_cipher_grade(grade)) {
+ case TLS_CIPHER_NONE:
+ msg_warn("%s: invalid cipher grade: \"%s\"",
+ TLScontext->namaddr, grade);
+ return (0);
+ case TLS_CIPHER_HIGH:
+ vstring_strcpy(buf, var_tls_high_clist);
+ break;
+ case TLS_CIPHER_MEDIUM:
+ vstring_strcpy(buf, var_tls_medium_clist);
+ break;
+ case TLS_CIPHER_LOW:
+ vstring_strcpy(buf, var_tls_low_clist);
+ break;
+ case TLS_CIPHER_EXPORT:
+ vstring_strcpy(buf, var_tls_export_clist);
+ break;
+ case TLS_CIPHER_NULL:
+ vstring_strcpy(buf, var_tls_null_clist);
+ break;
+ default:
+ /* Internal error, valid grade, but missing case label. */
+ msg_panic("%s: unexpected cipher grade: %s", myname, grade);
+ }
+
+ /*
+ * The base lists for each grade can't be empty.
+ */
+ if (VSTRING_LEN(buf) == 0)
+ msg_panic("%s: empty \"%s\" cipherlist", myname, grade);
+
+ /*
+ * Apply locally-specified exclusions.
+ */
+#define CIPHER_SEP CHARS_COMMA_SP ":"
+ if (exclusions != 0) {
+ cp = save = mystrdup(exclusions);
+ while ((tok = mystrtok(&cp, CIPHER_SEP)) != 0) {
+
+ /*
+ * Can't exclude ciphers that start with modifiers.
+ */
+ if (strchr("!+-@", *tok)) {
+ msg_warn("%s: invalid unary '!+-@' in cipher exclusion: %s",
+ TLScontext->namaddr, tok);
+ return (0);
+ }
+ vstring_sprintf_append(buf, ":!%s", tok);
+ }
+ myfree(save);
+ }
+ ERR_clear_error();
+ if (SSL_set_cipher_list(TLScontext->con, vstring_str(buf)) == 0) {
+ msg_warn("%s: error setting cipher grade: \"%s\"",
+ TLScontext->namaddr, grade);
+ tls_print_errors();
+ return (0);
+ }
+ return (vstring_str(buf));
+}
+
+/* ec_curve_name - copy EC key curve group name */
+
+#ifndef OPENSSL_NO_EC
+static char *ec_curve_name(EVP_PKEY *pkey)
+{
+ char *curve = 0;
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+ size_t namelen;
+
+ if (EVP_PKEY_get_group_name(pkey, 0, 0, &namelen)) {
+ curve = mymalloc(++namelen);
+ if (!EVP_PKEY_get_group_name(pkey, curve, namelen, 0)) {
+ myfree(curve);
+ curve = 0;
+ }
+ }
+#else
+ EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pkey);
+ int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey));
+ const char *tmp = EC_curve_nid2nist(nid);
+
+ if (!tmp)
+ tmp = OBJ_nid2sn(nid);
+ if (tmp)
+ curve = mystrdup(tmp);
+#endif
+ return (curve);
+}
+
+#endif
+
+/* tls_get_signature_params - TLS 1.3 signature details */
+
+void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
+{
+ const char *kex_name = 0;
+ const char *locl_sig_name = 0;
+ const char *locl_sig_dgst = 0;
+ const char *peer_sig_name = 0;
+ const char *peer_sig_dgst = 0;
+ char *kex_curve = 0;
+ char *locl_sig_curve = 0;
+ char *peer_sig_curve = 0;
+ int nid;
+ SSL *ssl = TLScontext->con;
+ int srvr = SSL_is_server(ssl);
+ EVP_PKEY *dh_pkey = 0;
+ X509 *local_cert;
+ EVP_PKEY *local_pkey = 0;
+ X509 *peer_cert;
+ EVP_PKEY *peer_pkey = 0;
+
+#define SIG_PROP(c, s, p) (*((s) ? &c->srvr_sig_##p : &c->clnt_sig_##p))
+
+ if (SSL_version(ssl) < TLS1_3_VERSION)
+ return;
+
+ if (tls_get_peer_dh_pubkey(ssl, &dh_pkey)) {
+ switch (nid = EVP_PKEY_id(dh_pkey)) {
+ default:
+ kex_name = OBJ_nid2sn(EVP_PKEY_type(nid));
+ break;
+
+ case EVP_PKEY_DH:
+ kex_name = "DHE";
+ TLScontext->kex_bits = EVP_PKEY_bits(dh_pkey);
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ kex_name = "ECDHE";
+ kex_curve = ec_curve_name(dh_pkey);
+ break;
+#endif
+ }
+ EVP_PKEY_free(dh_pkey);
+ }
+
+ /*
+ * On the client end, the certificate may be preset, but not used, so we
+ * check via SSL_get_signature_nid(). This means that local signature
+ * data on clients requires at least 1.1.1a.
+ */
+ if (srvr || SSL_get_signature_nid(ssl, &nid))
+ local_cert = SSL_get_certificate(ssl);
+ else
+ local_cert = 0;
+
+ /* Signature algorithms for the local end of the connection */
+ if (local_cert) {
+ local_pkey = X509_get0_pubkey(local_cert);
+
+ /*
+ * Override the built-in name for the "ECDSA" algorithms OID, with
+ * the more familiar name. For "RSA" keys report "RSA-PSS", which
+ * must be used with TLS 1.3.
+ */
+ if ((nid = EVP_PKEY_type(EVP_PKEY_id(local_pkey))) != NID_undef) {
+ switch (nid) {
+ default:
+ locl_sig_name = OBJ_nid2sn(nid);
+ break;
+
+ case EVP_PKEY_RSA:
+ /* For RSA, TLS 1.3 mandates PSS signatures */
+ locl_sig_name = "RSA-PSS";
+ SIG_PROP(TLScontext, srvr, bits) = EVP_PKEY_bits(local_pkey);
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ locl_sig_name = "ECDSA";
+ locl_sig_curve = ec_curve_name(local_pkey);
+ break;
+#endif
+ }
+ /* No X509_free(local_cert) */
+ }
+
+ /*
+ * With Ed25519 and Ed448 there is no pre-signature digest, but the
+ * accessor does not fail, rather we get NID_undef.
+ */
+ if (SSL_get_signature_nid(ssl, &nid) && nid != NID_undef)
+ locl_sig_dgst = OBJ_nid2sn(nid);
+ }
+ /* Signature algorithms for the peer end of the connection */
+ if ((peer_cert = TLS_PEEK_PEER_CERT(ssl)) != 0) {
+ peer_pkey = X509_get0_pubkey(peer_cert);
+
+ /*
+ * Override the built-in name for the "ECDSA" algorithms OID, with
+ * the more familiar name. For "RSA" keys report "RSA-PSS", which
+ * must be used with TLS 1.3.
+ */
+ if ((nid = EVP_PKEY_type(EVP_PKEY_id(peer_pkey))) != NID_undef) {
+ switch (nid) {
+ default:
+ peer_sig_name = OBJ_nid2sn(nid);
+ break;
+
+ case EVP_PKEY_RSA:
+ /* For RSA, TLS 1.3 mandates PSS signatures */
+ peer_sig_name = "RSA-PSS";
+ SIG_PROP(TLScontext, !srvr, bits) = EVP_PKEY_bits(peer_pkey);
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ peer_sig_name = "ECDSA";
+ peer_sig_curve = ec_curve_name(peer_pkey);
+ break;
+#endif
+ }
+ }
+
+ /*
+ * With Ed25519 and Ed448 there is no pre-signature digest, but the
+ * accessor does not fail, rather we get NID_undef.
+ */
+ if (SSL_get_peer_signature_nid(ssl, &nid) && nid != NID_undef)
+ peer_sig_dgst = OBJ_nid2sn(nid);
+
+ TLS_FREE_PEER_CERT(peer_cert);
+ }
+ if (kex_name) {
+ TLScontext->kex_name = mystrdup(kex_name);
+ TLScontext->kex_curve = kex_curve;
+ }
+ if (locl_sig_name) {
+ SIG_PROP(TLScontext, srvr, name) = mystrdup(locl_sig_name);
+ SIG_PROP(TLScontext, srvr, curve) = locl_sig_curve;
+ if (locl_sig_dgst)
+ SIG_PROP(TLScontext, srvr, dgst) = mystrdup(locl_sig_dgst);
+ }
+ if (peer_sig_name) {
+ SIG_PROP(TLScontext, !srvr, name) = mystrdup(peer_sig_name);
+ SIG_PROP(TLScontext, !srvr, curve) = peer_sig_curve;
+ if (peer_sig_dgst)
+ SIG_PROP(TLScontext, !srvr, dgst) = mystrdup(peer_sig_dgst);
+ }
+}
+
+/* tls_log_summary - TLS loglevel 1 one-liner, embellished with TLS 1.3 details */
+
+void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
+{
+ VSTRING *msg = vstring_alloc(100);
+ const char *direction = (role == TLS_ROLE_CLIENT) ? "to" : "from";
+ const char *sni = (role == TLS_ROLE_CLIENT) ? 0 : ctx->peer_sni;
+
+ /*
+ * When SNI was sent and accepted, the server-side log message now
+ * includes a "to <sni-name>" detail after the "from <namaddr>" detail
+ * identifying the remote client. We don't presently log (purportedly)
+ * accepted SNI on the client side.
+ */
+ vstring_sprintf(msg, "%s TLS connection %s %s %s%s%s: %s"
+ " with cipher %s (%d/%d bits)",
+ !TLS_CERT_IS_PRESENT(ctx) ? "Anonymous" :
+ TLS_CERT_IS_SECURED(ctx) ? "Verified" :
+ TLS_CERT_IS_TRUSTED(ctx) ? "Trusted" : "Untrusted",
+ usage == TLS_USAGE_NEW ? "established" : "reused",
+ direction, ctx->namaddr, sni ? " to " : "", sni ? sni : "",
+ ctx->protocol, ctx->cipher_name, ctx->cipher_usebits,
+ ctx->cipher_algbits);
+
+ if (ctx->kex_name && *ctx->kex_name) {
+ vstring_sprintf_append(msg, " key-exchange %s", ctx->kex_name);
+ if (ctx->kex_curve && *ctx->kex_curve)
+ vstring_sprintf_append(msg, " (%s)", ctx->kex_curve);
+ else if (ctx->kex_bits > 0)
+ vstring_sprintf_append(msg, " (%d bits)", ctx->kex_bits);
+ }
+ if (ctx->srvr_sig_name && *ctx->srvr_sig_name) {
+ vstring_sprintf_append(msg, " server-signature %s",
+ ctx->srvr_sig_name);
+ if (ctx->srvr_sig_curve && *ctx->srvr_sig_curve)
+ vstring_sprintf_append(msg, " (%s)", ctx->srvr_sig_curve);
+ else if (ctx->srvr_sig_bits > 0)
+ vstring_sprintf_append(msg, " (%d bits)", ctx->srvr_sig_bits);
+ if (ctx->srvr_sig_dgst && *ctx->srvr_sig_dgst)
+ vstring_sprintf_append(msg, " server-digest %s",
+ ctx->srvr_sig_dgst);
+ }
+ if (ctx->clnt_sig_name && *ctx->clnt_sig_name) {
+ vstring_sprintf_append(msg, " client-signature %s",
+ ctx->clnt_sig_name);
+ if (ctx->clnt_sig_curve && *ctx->clnt_sig_curve)
+ vstring_sprintf_append(msg, " (%s)", ctx->clnt_sig_curve);
+ else if (ctx->clnt_sig_bits > 0)
+ vstring_sprintf_append(msg, " (%d bits)", ctx->clnt_sig_bits);
+ if (ctx->clnt_sig_dgst && *ctx->clnt_sig_dgst)
+ vstring_sprintf_append(msg, " client-digest %s",
+ ctx->clnt_sig_dgst);
+ }
+ msg_info("%s", vstring_str(msg));
+ vstring_free(msg);
+}
+
+/* tls_alloc_app_context - allocate TLS application context */
+
+TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *ssl_ctx, SSL_CTX *sni_ctx,
+ int log_mask)
+{
+ TLS_APPL_STATE *app_ctx;
+
+ app_ctx = (TLS_APPL_STATE *) mymalloc(sizeof(*app_ctx));
+
+ /* See portability note below with other memset() call. */
+ memset((void *) app_ctx, 0, sizeof(*app_ctx));
+ app_ctx->ssl_ctx = ssl_ctx;
+ app_ctx->sni_ctx = sni_ctx;
+ app_ctx->log_mask = log_mask;
+
+ /* See also: cache purging code in tls_set_ciphers(). */
+ app_ctx->cache_type = 0;
+
+ if (tls_server_sni_maps) {
+ SSL_CTX_set_tlsext_servername_callback(ssl_ctx, server_sni_callback);
+ SSL_CTX_set_tlsext_servername_arg(ssl_ctx, (void *) sni_ctx);
+ }
+ return (app_ctx);
+}
+
+/* tls_free_app_context - Free TLS application context */
+
+void tls_free_app_context(TLS_APPL_STATE *app_ctx)
+{
+ if (app_ctx->ssl_ctx)
+ SSL_CTX_free(app_ctx->ssl_ctx);
+ if (app_ctx->sni_ctx)
+ SSL_CTX_free(app_ctx->sni_ctx);
+ if (app_ctx->cache_type)
+ myfree(app_ctx->cache_type);
+ myfree((void *) app_ctx);
+}
+
+/* tls_alloc_sess_context - allocate TLS session context */
+
+TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr)
+{
+ TLS_SESS_STATE *TLScontext;
+
+ /*
+ * PORTABILITY: Do not assume that null pointers are all-zero bits. Use
+ * explicit assignments to initialize pointers.
+ *
+ * See the C language FAQ item 5.17, or if you have time to burn,
+ * http://www.google.com/search?q=zero+bit+null+pointer
+ *
+ * However, it's OK to use memset() to zero integer values.
+ */
+ TLScontext = (TLS_SESS_STATE *) mymalloc(sizeof(TLS_SESS_STATE));
+ memset((void *) TLScontext, 0, sizeof(*TLScontext));
+ TLScontext->con = 0;
+ TLScontext->cache_type = 0;
+ TLScontext->serverid = 0;
+ TLScontext->peer_CN = 0;
+ TLScontext->issuer_CN = 0;
+ TLScontext->peer_sni = 0;
+ TLScontext->peer_cert_fprint = 0;
+ TLScontext->peer_pkey_fprint = 0;
+ TLScontext->protocol = 0;
+ TLScontext->cipher_name = 0;
+ TLScontext->kex_name = 0;
+ TLScontext->kex_curve = 0;
+ TLScontext->clnt_sig_name = 0;
+ TLScontext->clnt_sig_curve = 0;
+ TLScontext->clnt_sig_dgst = 0;
+ TLScontext->srvr_sig_name = 0;
+ TLScontext->srvr_sig_curve = 0;
+ TLScontext->srvr_sig_dgst = 0;
+ TLScontext->log_mask = log_mask;
+ TLScontext->namaddr = lowercase(mystrdup(namaddr));
+ TLScontext->mdalg = 0; /* Alias for props->mdalg */
+ TLScontext->dane = 0; /* Alias for props->dane */
+ TLScontext->errordepth = -1;
+ TLScontext->errorcode = X509_V_OK;
+ TLScontext->errorcert = 0;
+
+ return (TLScontext);
+}
+
+/* tls_free_context - deallocate TLScontext and members */
+
+void tls_free_context(TLS_SESS_STATE *TLScontext)
+{
+
+ /*
+ * Free the SSL structure and the BIOs. Warning: the internal_bio is
+ * connected to the SSL structure and is automatically freed with it. Do
+ * not free it again (core dump)!! Only free the network_bio.
+ */
+ if (TLScontext->con != 0)
+ SSL_free(TLScontext->con);
+
+ if (TLScontext->namaddr)
+ myfree(TLScontext->namaddr);
+ if (TLScontext->serverid)
+ myfree(TLScontext->serverid);
+
+ if (TLScontext->peer_CN)
+ myfree(TLScontext->peer_CN);
+ if (TLScontext->issuer_CN)
+ myfree(TLScontext->issuer_CN);
+ if (TLScontext->peer_sni)
+ myfree(TLScontext->peer_sni);
+ if (TLScontext->peer_cert_fprint)
+ myfree(TLScontext->peer_cert_fprint);
+ if (TLScontext->peer_pkey_fprint)
+ myfree(TLScontext->peer_pkey_fprint);
+ if (TLScontext->kex_name)
+ myfree((void *) TLScontext->kex_name);
+ if (TLScontext->kex_curve)
+ myfree((void *) TLScontext->kex_curve);
+ if (TLScontext->clnt_sig_name)
+ myfree((void *) TLScontext->clnt_sig_name);
+ if (TLScontext->clnt_sig_curve)
+ myfree((void *) TLScontext->clnt_sig_curve);
+ if (TLScontext->clnt_sig_dgst)
+ myfree((void *) TLScontext->clnt_sig_dgst);
+ if (TLScontext->srvr_sig_name)
+ myfree((void *) TLScontext->srvr_sig_name);
+ if (TLScontext->srvr_sig_curve)
+ myfree((void *) TLScontext->srvr_sig_curve);
+ if (TLScontext->srvr_sig_dgst)
+ myfree((void *) TLScontext->srvr_sig_dgst);
+ if (TLScontext->errorcert)
+ X509_free(TLScontext->errorcert);
+
+ myfree((void *) TLScontext);
+}
+
+/* tls_version_split - Split OpenSSL version number into major, minor, ... */
+
+static void tls_version_split(unsigned long version, TLS_VINFO *info)
+{
+
+ /*
+ * OPENSSL_VERSION_NUMBER(3):
+ *
+ * OPENSSL_VERSION_NUMBER is a numeric release version identifier:
+ *
+ * MMNNFFPPS: major minor fix patch status
+ *
+ * The status nibble has one of the values 0 for development, 1 to e for
+ * betas 1 to 14, and f for release. Parsed OpenSSL version number. for
+ * example: 0x1010103f == 1.1.1c.
+ */
+ info->status = version & 0xf;
+ version >>= 4;
+ info->patch = version & 0xff;
+ version >>= 8;
+ info->micro = version & 0xff;
+ version >>= 8;
+ info->minor = version & 0xff;
+ version >>= 8;
+ info->major = version & 0xff;
+}
+
+/* tls_check_version - Detect mismatch between headers and library. */
+
+void tls_check_version(void)
+{
+ TLS_VINFO hdr_info;
+ TLS_VINFO lib_info;
+
+ tls_version_split(OPENSSL_VERSION_NUMBER, &hdr_info);
+ tls_version_split(OpenSSL_version_num(), &lib_info);
+
+ /*
+ * Warn if run-time library is different from compile-time library,
+ * allowing later run-time "micro" versions starting with 1.1.0.
+ */
+ if (lib_info.major != hdr_info.major
+ || lib_info.minor != hdr_info.minor
+ || (lib_info.micro != hdr_info.micro
+ && (lib_info.micro < hdr_info.micro
+ || hdr_info.major == 0
+ || (hdr_info.major == 1 && hdr_info.minor == 0))))
+ msg_warn("run-time library vs. compile-time header version mismatch: "
+ "OpenSSL %d.%d.%d may not be compatible with OpenSSL %d.%d.%d",
+ lib_info.major, lib_info.minor, lib_info.micro,
+ hdr_info.major, hdr_info.minor, hdr_info.micro);
+}
+
+/* tls_compile_version - compile-time OpenSSL version */
+
+const char *tls_compile_version(void)
+{
+ return (OPENSSL_VERSION_TEXT);
+}
+
+/* tls_run_version - run-time version "major.minor.micro" */
+
+const char *tls_run_version(void)
+{
+ return (OpenSSL_version(OPENSSL_VERSION));
+}
+
+const char **tls_pkey_algorithms(void)
+{
+
+ /*
+ * Return an array, not string, so that the result can be inspected
+ * without parsing. Sort the result alphabetically, not chronologically.
+ */
+ static const char *algs[] = {
+#ifndef OPENSSL_NO_DSA
+ "dsa",
+#endif
+#ifndef OPENSSL_NO_ECDSA
+ "ecdsa",
+#endif
+#ifndef OPENSSL_NO_RSA
+ "rsa",
+#endif
+ 0,
+ };
+
+ return (algs);
+}
+
+/* tls_bug_bits - SSL bug compatibility bits for this OpenSSL version */
+
+long tls_bug_bits(void)
+{
+ long bits = SSL_OP_ALL; /* Work around all known bugs */
+
+ /*
+ * Silently ignore any strings that don't appear in the tweaks table, or
+ * hex bits that are not in SSL_OP_ALL.
+ */
+ if (*var_tls_bug_tweaks) {
+ bits &= ~long_name_mask_opt(VAR_TLS_BUG_TWEAKS, ssl_bug_tweaks,
+ var_tls_bug_tweaks, NAME_MASK_ANY_CASE |
+ NAME_MASK_NUMBER | NAME_MASK_WARN);
+#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
+ /* Not relevant to SMTP */
+ bits &= ~SSL_OP_SAFARI_ECDHE_ECDSA_BUG;
+#endif
+ }
+
+ /*
+ * Allow users to set options not in SSL_OP_ALL, and not already managed
+ * via other Postfix parameters.
+ */
+ if (*var_tls_ssl_options) {
+ long enable;
+
+ enable = long_name_mask_opt(VAR_TLS_SSL_OPTIONS, ssl_op_tweaks,
+ var_tls_ssl_options, NAME_MASK_ANY_CASE |
+ NAME_MASK_NUMBER | NAME_MASK_WARN);
+ enable &= ~(SSL_OP_ALL | TLS_SSL_OP_MANAGED_BITS);
+ bits |= enable;
+ }
+
+ /*
+ * We unconditionally avoid re-use of ephemeral keys, note that we set DH
+ * keys via a callback, so reuse was never possible, but the ECDH key is
+ * set statically, so that is potentially subject to reuse. Set both
+ * options just in case.
+ */
+ bits |= SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE;
+ return (bits);
+}
+
+/* tls_print_errors - print and clear the error stack */
+
+void tls_print_errors(void)
+{
+ unsigned long err;
+ char buffer[1024]; /* XXX */
+ const char *file;
+ const char *data;
+ int line;
+ int flags;
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+/* XXX: We're ignoring the function name, do we want to log it? */
+#define ERRGET(fi, l, d, fl) ERR_get_error_all(fi, l, 0, d, fl)
+#else
+#define ERRGET(fi, l, d, fl) ERR_get_error_line_data(fi, l, d, fl)
+#endif
+
+ while ((err = ERRGET(&file, &line, &data, &flags)) != 0) {
+ ERR_error_string_n(err, buffer, sizeof(buffer));
+ if (flags & ERR_TXT_STRING)
+ msg_warn("TLS library problem: %s:%s:%d:%s:",
+ buffer, file, line, data);
+ else
+ msg_warn("TLS library problem: %s:%s:%d:", buffer, file, line);
+ }
+}
+
+/* tls_info_callback - callback for logging SSL events via Postfix */
+
+void tls_info_callback(const SSL *s, int where, int ret)
+{
+ char *str;
+ int w;
+
+ /* Adapted from OpenSSL apps/s_cb.c. */
+
+ w = where & ~SSL_ST_MASK;
+
+ if (w & SSL_ST_CONNECT)
+ str = "SSL_connect";
+ else if (w & SSL_ST_ACCEPT)
+ str = "SSL_accept";
+ else
+ str = "unknown";
+
+ if (where & SSL_CB_LOOP) {
+ msg_info("%s:%s", str, SSL_state_string_long((SSL *) s));
+ } else if (where & SSL_CB_ALERT) {
+ str = (where & SSL_CB_READ) ? "read" : "write";
+ if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY)
+ msg_info("SSL3 alert %s:%s:%s", str,
+ SSL_alert_type_string_long(ret),
+ SSL_alert_desc_string_long(ret));
+ } else if (where & SSL_CB_EXIT) {
+ if (ret == 0)
+ msg_info("%s:failed in %s",
+ str, SSL_state_string_long((SSL *) s));
+ else if (ret < 0) {
+#ifndef LOG_NON_ERROR_STATES
+ switch (SSL_get_error((SSL *) s, ret)) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ /* Don't log non-error states. */
+ break;
+ default:
+#endif
+ msg_info("%s:error in %s",
+ str, SSL_state_string_long((SSL *) s));
+#ifndef LOG_NON_ERROR_STATES
+ }
+#endif
+ }
+ }
+}
+
+ /*
+ * taken from OpenSSL crypto/bio/b_dump.c.
+ *
+ * Modified to save a lot of strcpy and strcat by Matti Aarnio.
+ *
+ * Rewritten by Wietse to eliminate fixed-size stack buffer, array index
+ * multiplication and division, sprintf() and strcpy(), and lots of strlen()
+ * calls. We could make it a little faster by using a fixed-size stack-based
+ * buffer.
+ *
+ * 200412 - use %lx to print pointers, after casting them to unsigned long.
+ */
+
+#define TRUNCATE_SPACE_NULL
+#define DUMP_WIDTH 16
+#define VERT_SPLIT 7
+
+static void tls_dump_buffer(const unsigned char *start, int len)
+{
+ VSTRING *buf = vstring_alloc(100);
+ const unsigned char *last = start + len - 1;
+ const unsigned char *row;
+ const unsigned char *col;
+ int ch;
+
+#ifdef TRUNCATE_SPACE_NULL
+ while (last >= start && (*last == ' ' || *last == 0))
+ last--;
+#endif
+
+ for (row = start; row <= last; row += DUMP_WIDTH) {
+ VSTRING_RESET(buf);
+ vstring_sprintf(buf, "%04lx ", (unsigned long) (row - start));
+ for (col = row; col < row + DUMP_WIDTH; col++) {
+ if (col > last) {
+ vstring_strcat(buf, " ");
+ } else {
+ ch = *col;
+ vstring_sprintf_append(buf, "%02x%c",
+ ch, col - row == VERT_SPLIT ? '|' : ' ');
+ }
+ }
+ VSTRING_ADDCH(buf, ' ');
+ for (col = row; col < row + DUMP_WIDTH; col++) {
+ if (col > last)
+ break;
+ ch = *col;
+ if (!ISPRINT(ch))
+ ch = '.';
+ VSTRING_ADDCH(buf, ch);
+ if (col - row == VERT_SPLIT)
+ VSTRING_ADDCH(buf, ' ');
+ }
+ VSTRING_TERMINATE(buf);
+ msg_info("%s", vstring_str(buf));
+ }
+#ifdef TRUNCATE_SPACE_NULL
+ if ((last + 1) - start < len)
+ msg_info("%04lx - <SPACES/NULLS>",
+ (unsigned long) ((last + 1) - start));
+#endif
+ vstring_free(buf);
+}
+
+/* taken from OpenSSL apps/s_cb.c */
+
+#if !OPENSSL_VERSION_PREREQ(3,0)
+long tls_bio_dump_cb(BIO *bio, int cmd, const char *argp, int argi,
+ long unused_argl, long ret)
+{
+ if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
+ msg_info("read from %08lX [%08lX] (%d bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, argi,
+ ret, (unsigned long) ret);
+ tls_dump_buffer((unsigned char *) argp, (int) ret);
+ } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
+ msg_info("write to %08lX [%08lX] (%d bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, argi,
+ ret, (unsigned long) ret);
+ tls_dump_buffer((unsigned char *) argp, (int) ret);
+ }
+ return (ret);
+}
+
+#else
+long tls_bio_dump_cb(BIO *bio, int cmd, const char *argp, size_t len,
+ int argi, long unused_argl, int ret, size_t *processed)
+{
+ size_t bytes = (ret > 0 && processed != NULL) ? *processed : len;
+
+ if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
+ if (ret > 0) {
+ msg_info("read from %08lX [%08lX] (%ld bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, (long) len,
+ (long) bytes, (long) bytes);
+ tls_dump_buffer((unsigned char *) argp, (int) bytes);
+ } else {
+ msg_info("read from %08lX [%08lX] (%ld bytes => %d)",
+ (unsigned long) bio, (unsigned long) argp,
+ (long) len, ret);
+ }
+ } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
+ if (ret > 0) {
+ msg_info("write to %08lX [%08lX] (%ld bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, (long) len,
+ (long) bytes, (long) bytes);
+ tls_dump_buffer((unsigned char *) argp, (int) bytes);
+ } else {
+ msg_info("write to %08lX [%08lX] (%ld bytes => %d)",
+ (unsigned long) bio, (unsigned long) argp,
+ (long) len, ret);
+ }
+ }
+ return ret;
+}
+
+#endif
+
+const EVP_MD *tls_validate_digest(const char *dgst)
+{
+ const EVP_MD *md_alg;
+
+ /*
+ * If the administrator specifies an unsupported digest algorithm, fail
+ * now, rather than in the middle of a TLS handshake.
+ */
+ if ((md_alg = tls_digest_byname(dgst, NULL)) == 0)
+ msg_warn("Digest algorithm \"%s\" not found", dgst);
+ return md_alg;
+}
+
+#else
+
+ /*
+ * Broken linker workaround.
+ */
+int tls_dummy_for_broken_linkers;
+
+#endif
diff --git a/src/tls/tls_prng.h b/src/tls/tls_prng.h
new file mode 100644
index 0000000..df7fad9
--- /dev/null
+++ b/src/tls/tls_prng.h
@@ -0,0 +1,50 @@
+#ifndef _TLS_PRNG_SRC_H_INCLUDED_
+#define _TLS_PRNG_SRC_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_prng_src 3h
+/* SUMMARY
+/* OpenSSL PRNG maintenance routines
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct TLS_PRNG_SRC {
+ int fd; /* file handle */
+ char *name; /* resource name */
+ int timeout; /* time limit of applicable */
+} TLS_PRNG_SRC;
+
+extern TLS_PRNG_SRC *tls_prng_egd_open(const char *, int);
+extern ssize_t tls_prng_egd_read(TLS_PRNG_SRC *, size_t);
+extern int tls_prng_egd_close(TLS_PRNG_SRC *);
+
+extern TLS_PRNG_SRC *tls_prng_dev_open(const char *, int);
+extern ssize_t tls_prng_dev_read(TLS_PRNG_SRC *, size_t);
+extern int tls_prng_dev_close(TLS_PRNG_SRC *);
+
+extern TLS_PRNG_SRC *tls_prng_file_open(const char *, int);
+extern ssize_t tls_prng_file_read(TLS_PRNG_SRC *, size_t);
+extern int tls_prng_file_close(TLS_PRNG_SRC *);
+
+extern TLS_PRNG_SRC *tls_prng_exch_open(const char *);
+extern void tls_prng_exch_update(TLS_PRNG_SRC *);
+extern void tls_prng_exch_close(TLS_PRNG_SRC *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/tls/tls_prng_dev.c b/src/tls/tls_prng_dev.c
new file mode 100644
index 0000000..47abf47
--- /dev/null
+++ b/src/tls/tls_prng_dev.c
@@ -0,0 +1,155 @@
+/*++
+/* NAME
+/* tls_prng_dev 3
+/* SUMMARY
+/* seed OpenSSL PRNG from entropy device
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_dev_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* ssize_t tls_prng_dev_read(dev, length)
+/* TLS_PRNG_SRC *dev;
+/* size_t length;
+/*
+/* int tls_prng_dev_close(dev)
+/* TLS_PRNG_SRC *dev;
+/* DESCRIPTION
+/* tls_prng_dev_open() opens the specified entropy device
+/* and returns a handle that should be used with all subsequent
+/* access.
+/*
+/* tls_prng_dev_read() reads the requested number of bytes from
+/* the entropy device and updates the OpenSSL PRNG.
+/*
+/* tls_prng_dev_close() closes the specified entropy device
+/* and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The pathname of the entropy device.
+/* .IP length
+/* The number of bytes to read from the entropy device.
+/* Request lengths will be truncated at 255 bytes.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* tls_prng_dev_open() returns a null pointer on error.
+/*
+/* tls_prng_dev_read() returns -1 on error, the number
+/* of bytes received on success.
+/*
+/* tls_prng_dev_close() returns -1 on error, 0 on success.
+/*
+/* In all cases the errno variable indicates the type of error.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* tls_prng_dev_open - open entropy device */
+
+TLS_PRNG_SRC *tls_prng_dev_open(const char *name, int timeout)
+{
+ const char *myname = "tls_prng_dev_open";
+ TLS_PRNG_SRC *dev;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY, 0)) < 0) {
+ if (msg_verbose)
+ msg_info("%s: cannot open entropy device %s: %m", myname, name);
+ return (0);
+ } else {
+ dev = (TLS_PRNG_SRC *) mymalloc(sizeof(*dev));
+ dev->fd = fd;
+ dev->name = mystrdup(name);
+ dev->timeout = timeout;
+ if (msg_verbose)
+ msg_info("%s: opened entropy device %s", myname, name);
+ return (dev);
+ }
+}
+
+/* tls_prng_dev_read - update internal PRNG from device */
+
+ssize_t tls_prng_dev_read(TLS_PRNG_SRC *dev, size_t len)
+{
+ const char *myname = "tls_prng_dev_read";
+ unsigned char buffer[UCHAR_MAX];
+ ssize_t count;
+ size_t rand_bytes;
+
+ if (len <= 0)
+ msg_panic("%s: bad read length: %ld", myname, (long) len);
+
+ if (len > sizeof(buffer))
+ rand_bytes = sizeof(buffer);
+ else
+ rand_bytes = len;
+ errno = 0;
+ count = timed_read(dev->fd, buffer, rand_bytes, dev->timeout, (void *) 0);
+ if (count > 0) {
+ if (msg_verbose)
+ msg_info("%s: read %ld bytes from entropy device %s",
+ myname, (long) count, dev->name);
+ RAND_seed(buffer, count);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: cannot read %ld bytes from entropy device %s: %m",
+ myname, (long) rand_bytes, dev->name);
+ }
+ return (count);
+}
+
+/* tls_prng_dev_close - disconnect from EGD server */
+
+int tls_prng_dev_close(TLS_PRNG_SRC *dev)
+{
+ const char *myname = "tls_prng_dev_close";
+ int err;
+
+ if (msg_verbose)
+ msg_info("%s: close entropy device %s", myname, dev->name);
+ err = close(dev->fd);
+ myfree(dev->name);
+ myfree((void *) dev);
+ return (err);
+}
+
+#endif
diff --git a/src/tls/tls_prng_egd.c b/src/tls/tls_prng_egd.c
new file mode 100644
index 0000000..e4a4cd5
--- /dev/null
+++ b/src/tls/tls_prng_egd.c
@@ -0,0 +1,166 @@
+/*++
+/* NAME
+/* tls_prng_egd 3
+/* SUMMARY
+/* seed OpenSSL PRNG from EGD server
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_egd_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* ssize_t tls_prng_egd_read(egd, length)
+/* TLS_PRNG_SRC *egd;
+/* size_t length;
+/*
+/* int tls_prng_egd_close(egd)
+/* TLS_PRNG_SRC *egd;
+/* DESCRIPTION
+/* tls_prng_egd_open() connect to the specified UNIX-domain service
+/* and returns a handle that should be used with all subsequent
+/* access.
+/*
+/* tls_prng_egd_read() reads the requested number of bytes from
+/* the EGD server and updates the OpenSSL PRNG.
+/*
+/* tls_prng_egd_close() disconnects from the specified EGD server
+/* and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The UNIX-domain pathname of the EGD service.
+/* .IP length
+/* The number of bytes to read from the EGD server.
+/* Request lengths will be truncated at 255 bytes.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* tls_prng_egd_open() returns a null pointer on error.
+/*
+/* tls_prng_egd_read() returns -1 on error, the number
+/* of bytes received on success.
+/*
+/* tls_prng_egd_close() returns -1 on error, 0 on success.
+/*
+/* In all cases the errno variable indicates the type of error.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <limits.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* tls_prng_egd_open - connect to EGD server */
+
+TLS_PRNG_SRC *tls_prng_egd_open(const char *name, int timeout)
+{
+ const char *myname = "tls_prng_egd_open";
+ TLS_PRNG_SRC *egd;
+ int fd;
+
+ if (msg_verbose)
+ msg_info("%s: connect to EGD server %s", myname, name);
+
+ if ((fd = unix_connect(name, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_info("%s: cannot connect to EGD server %s: %m", myname, name);
+ return (0);
+ } else {
+ egd = (TLS_PRNG_SRC *) mymalloc(sizeof(*egd));
+ egd->fd = fd;
+ egd->name = mystrdup(name);
+ egd->timeout = timeout;
+ if (msg_verbose)
+ msg_info("%s: connected to EGD server %s", myname, name);
+ return (egd);
+ }
+}
+
+/* tls_prng_egd_read - update internal PRNG from EGD server */
+
+ssize_t tls_prng_egd_read(TLS_PRNG_SRC *egd, size_t len)
+{
+ const char *myname = "tls_prng_egd_read";
+ unsigned char buffer[UCHAR_MAX];
+ ssize_t count;
+
+ if (len <= 0)
+ msg_panic("%s: bad length %ld", myname, (long) len);
+
+ buffer[0] = 1;
+ buffer[1] = (len > UCHAR_MAX ? UCHAR_MAX : len);
+
+ if (timed_write(egd->fd, buffer, 2, egd->timeout, (void *) 0) != 2) {
+ msg_info("cannot write to EGD server %s: %m", egd->name);
+ return (-1);
+ }
+ if (timed_read(egd->fd, buffer, 1, egd->timeout, (void *) 0) != 1) {
+ msg_info("cannot read from EGD server %s: %m", egd->name);
+ return (-1);
+ }
+ count = buffer[0];
+ if (count > sizeof(buffer))
+ count = sizeof(buffer);
+ if (count == 0) {
+ msg_info("EGD server %s reports zero bytes available", egd->name);
+ return (-1);
+ }
+ if (timed_read(egd->fd, buffer, count, egd->timeout, (void *) 0) != count) {
+ msg_info("cannot read %ld bytes from EGD server %s: %m",
+ (long) count, egd->name);
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: got %ld bytes from EGD server %s", myname,
+ (long) count, egd->name);
+ RAND_seed(buffer, count);
+ return (count);
+}
+
+/* tls_prng_egd_close - disconnect from EGD server */
+
+int tls_prng_egd_close(TLS_PRNG_SRC *egd)
+{
+ const char *myname = "tls_prng_egd_close";
+ int err;
+
+ if (msg_verbose)
+ msg_info("%s: close EGD server %s", myname, egd->name);
+ err = close(egd->fd);
+ myfree(egd->name);
+ myfree((void *) egd);
+ return (err);
+}
+
+#endif
diff --git a/src/tls/tls_prng_exch.c b/src/tls/tls_prng_exch.c
new file mode 100644
index 0000000..31523a3
--- /dev/null
+++ b/src/tls/tls_prng_exch.c
@@ -0,0 +1,142 @@
+/*++
+/* NAME
+/* tls_prng_exch 3
+/* SUMMARY
+/* maintain PRNG exchange file
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_exch_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* void tls_prng_exch_update(fh, length)
+/* TLS_PRNG_SRC *fh;
+/* size_t length;
+/*
+/* void tls_prng_exch_close(fh)
+/* TLS_PRNG_SRC *fh;
+/* DESCRIPTION
+/* tls_prng_exch_open() opens the specified PRNG exchange file
+/* and returns a handle that should be used with all subsequent
+/* access.
+/*
+/* tls_prng_exch_update() reads the requested number of bytes
+/* from the PRNG exchange file, updates the OpenSSL PRNG, and
+/* writes the requested number of bytes to the exchange file.
+/* The file is locked for exclusive access.
+/*
+/* tls_prng_exch_close() closes the specified PRNG exchange
+/* file and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The name of the PRNG exchange file.
+/* .IP length
+/* The number of bytes to read from/write to the entropy file.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <myflock.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* Application specific. */
+
+#define TLS_PRNG_EXCH_SIZE 1024 /* XXX Why not configurable? */
+
+/* tls_prng_exch_open - open PRNG exchange file */
+
+TLS_PRNG_SRC *tls_prng_exch_open(const char *name)
+{
+ const char *myname = "tls_prng_exch_open";
+ TLS_PRNG_SRC *eh;
+ int fd;
+
+ if ((fd = open(name, O_RDWR | O_CREAT, 0600)) < 0)
+ msg_fatal("%s: cannot open PRNG exchange file %s: %m", myname, name);
+ eh = (TLS_PRNG_SRC *) mymalloc(sizeof(*eh));
+ eh->fd = fd;
+ eh->name = mystrdup(name);
+ eh->timeout = 0;
+ if (msg_verbose)
+ msg_info("%s: opened PRNG exchange file %s", myname, name);
+ return (eh);
+}
+
+/* tls_prng_exch_update - update PRNG exchange file */
+
+void tls_prng_exch_update(TLS_PRNG_SRC *eh)
+{
+ unsigned char buffer[TLS_PRNG_EXCH_SIZE];
+ ssize_t count;
+
+ /*
+ * Update the PRNG exchange file. Since other processes may have added
+ * entropy, we use a read-stir-write cycle.
+ */
+ if (myflock(eh->fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) != 0)
+ msg_fatal("cannot lock PRNG exchange file %s: %m", eh->name);
+ if (lseek(eh->fd, 0, SEEK_SET) < 0)
+ msg_fatal("cannot seek PRNG exchange file %s: %m", eh->name);
+ if ((count = read(eh->fd, buffer, sizeof(buffer))) < 0)
+ msg_fatal("cannot read PRNG exchange file %s: %m", eh->name);
+
+ if (count > 0)
+ RAND_seed(buffer, count);
+ RAND_bytes(buffer, sizeof(buffer));
+
+ if (lseek(eh->fd, 0, SEEK_SET) < 0)
+ msg_fatal("cannot seek PRNG exchange file %s: %m", eh->name);
+ if (write(eh->fd, buffer, sizeof(buffer)) != sizeof(buffer))
+ msg_fatal("cannot write PRNG exchange file %s: %m", eh->name);
+ if (myflock(eh->fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) != 0)
+ msg_fatal("cannot unlock PRNG exchange file %s: %m", eh->name);
+}
+
+/* tls_prng_exch_close - close PRNG exchange file */
+
+void tls_prng_exch_close(TLS_PRNG_SRC *eh)
+{
+ const char *myname = "tls_prng_exch_close";
+
+ if (close(eh->fd) < 0)
+ msg_fatal("close PRNG exchange file %s: %m", eh->name);
+ if (msg_verbose)
+ msg_info("%s: closed PRNG exchange file %s", myname, eh->name);
+ myfree(eh->name);
+ myfree((void *) eh);
+}
+
+#endif
diff --git a/src/tls/tls_prng_file.c b/src/tls/tls_prng_file.c
new file mode 100644
index 0000000..23865be
--- /dev/null
+++ b/src/tls/tls_prng_file.c
@@ -0,0 +1,155 @@
+/*++
+/* NAME
+/* tls_prng_file 3
+/* SUMMARY
+/* seed OpenSSL PRNG from entropy file
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_file_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* ssize_t tls_prng_file_read(fh, length)
+/* TLS_PRNG_SRC *fh;
+/* size_t length;
+/*
+/* int tls_prng_file_close(fh)
+/* TLS_PRNG_SRC *fh;
+/* DESCRIPTION
+/* tls_prng_file_open() open the specified file and returns
+/* a handle that should be used with all subsequent access.
+/*
+/* tls_prng_file_read() reads the requested number of bytes from
+/* the entropy file and updates the OpenSSL PRNG. The file is not
+/* locked for shared or exclusive access.
+/*
+/* tls_prng_file_close() closes the specified entropy file
+/* and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The pathname of the entropy file.
+/* .IP length
+/* The number of bytes to read from the entropy file.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* tls_prng_file_open() returns a null pointer on error.
+/*
+/* tls_prng_file_read() returns -1 on error, the number
+/* of bytes received on success.
+/*
+/* tls_prng_file_close() returns -1 on error, 0 on success.
+/*
+/* In all cases the errno variable indicates the type of error.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* tls_prng_file_open - open entropy file */
+
+TLS_PRNG_SRC *tls_prng_file_open(const char *name, int timeout)
+{
+ const char *myname = "tls_prng_file_open";
+ TLS_PRNG_SRC *fh;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY, 0)) < 0) {
+ if (msg_verbose)
+ msg_info("%s: cannot open entropy file %s: %m", myname, name);
+ return (0);
+ } else {
+ fh = (TLS_PRNG_SRC *) mymalloc(sizeof(*fh));
+ fh->fd = fd;
+ fh->name = mystrdup(name);
+ fh->timeout = timeout;
+ if (msg_verbose)
+ msg_info("%s: opened entropy file %s", myname, name);
+ return (fh);
+ }
+}
+
+/* tls_prng_file_read - update internal PRNG from entropy file */
+
+ssize_t tls_prng_file_read(TLS_PRNG_SRC *fh, size_t len)
+{
+ const char *myname = "tls_prng_file_read";
+ char buffer[8192];
+ ssize_t to_read;
+ ssize_t count;
+
+ if (msg_verbose)
+ msg_info("%s: seed internal pool from file %s", myname, fh->name);
+
+ if (lseek(fh->fd, 0, SEEK_SET) < 0) {
+ if (msg_verbose)
+ msg_info("cannot seek entropy file %s: %m", fh->name);
+ return (-1);
+ }
+ errno = 0;
+ for (to_read = len; to_read > 0; to_read -= count) {
+ if ((count = timed_read(fh->fd, buffer, to_read > sizeof(buffer) ?
+ sizeof(buffer) : to_read,
+ fh->timeout, (void *) 0)) < 0) {
+ if (msg_verbose)
+ msg_info("cannot read entropy file %s: %m", fh->name);
+ return (-1);
+ }
+ if (count == 0)
+ break;
+ RAND_seed(buffer, count);
+ }
+ if (msg_verbose)
+ msg_info("read %ld bytes from entropy file %s: %m",
+ (long) (len - to_read), fh->name);
+ return (len - to_read);
+}
+
+/* tls_prng_file_close - close entropy file */
+
+int tls_prng_file_close(TLS_PRNG_SRC *fh)
+{
+ const char *myname = "tls_prng_file_close";
+ int err;
+
+ if (msg_verbose)
+ msg_info("%s: close entropy file %s", myname, fh->name);
+ err = close(fh->fd);
+ myfree(fh->name);
+ myfree((void *) fh);
+ return (err);
+}
+
+#endif
diff --git a/src/tls/tls_proxy.h b/src/tls/tls_proxy.h
new file mode 100644
index 0000000..1d96a8b
--- /dev/null
+++ b/src/tls/tls_proxy.h
@@ -0,0 +1,287 @@
+#ifndef _TLS_PROXY_H_INCLUDED_
+#define _TLS_PROXY_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_proxy_clnt 3h
+/* SUMMARY
+/* postscreen TLS proxy support
+/* SYNOPSIS
+/* #include <tls_proxy_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <attr.h>
+
+ /*
+ * TLS library.
+ */
+#include <tls.h>
+
+ /*
+ * External interface.
+ */
+#define TLS_PROXY_FLAG_ROLE_SERVER (1<<0) /* request server role */
+#define TLS_PROXY_FLAG_ROLE_CLIENT (1<<1) /* request client role */
+#define TLS_PROXY_FLAG_SEND_CONTEXT (1<<2) /* send TLS context */
+
+#ifdef USE_TLS
+
+ /*
+ * TLS_CLIENT_PARAMS structure. If this changes, update all
+ * TLS_CLIENT_PARAMS related functions in tls_proxy_client_*.c.
+ *
+ * In the serialization these attributes are identified by their configuration
+ * parameter names.
+ *
+ * NOTE: this does not include openssl_path.
+ *
+ * TODO: TLS_SERVER_PARAM structure, like TLS_CLIENT_PARAMS plus
+ * VAR_TLS_SERVER_SNI_MAPS.
+ */
+typedef struct TLS_CLIENT_PARAMS {
+ char *tls_cnf_file;
+ char *tls_cnf_name;
+ char *tls_high_clist;
+ char *tls_medium_clist;
+ char *tls_low_clist;
+ char *tls_export_clist;
+ char *tls_null_clist;
+ char *tls_eecdh_auto;
+ char *tls_eecdh_strong;
+ char *tls_eecdh_ultra;
+ char *tls_bug_tweaks;
+ char *tls_ssl_options;
+ char *tls_dane_digests;
+ char *tls_mgr_service;
+ char *tls_tkt_cipher;
+ int tls_daemon_rand_bytes;
+ int tls_append_def_CA;
+ int tls_bc_pkey_fprint;
+ int tls_preempt_clist;
+ int tls_multi_wildcard;
+} TLS_CLIENT_PARAMS;
+
+#define TLS_PROXY_PARAMS(params, a1, a2, a3, a4, a5, a6, a7, a8, \
+ a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \
+ (((params)->a1), ((params)->a2), ((params)->a3), \
+ ((params)->a4), ((params)->a5), ((params)->a6), ((params)->a7), \
+ ((params)->a8), ((params)->a9), ((params)->a10), ((params)->a11), \
+ ((params)->a12), ((params)->a13), ((params)->a14), ((params)->a15), \
+ ((params)->a16), ((params)->a17), ((params)->a18), ((params)->a19), \
+ ((params)->a20))
+
+ /*
+ * tls_proxy_client_param_misc.c, tls_proxy_client_param_print.c, and
+ * tls_proxy_client_param_scan.c.
+ */
+extern TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(TLS_CLIENT_PARAMS *);
+extern char *tls_proxy_client_param_serialize(ATTR_PRINT_COMMON_FN, VSTRING *, const TLS_CLIENT_PARAMS *);
+extern int tls_proxy_client_param_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern void tls_proxy_client_param_free(TLS_CLIENT_PARAMS *);
+extern int tls_proxy_client_param_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+ /*
+ * Functions that handle TLS_XXX_INIT_PROPS and TLS_XXX_START_PROPS. These
+ * data structures are defined elsewhere, because they are also used in
+ * non-proxied requests.
+ */
+#define tls_proxy_legacy_open(service, flags, peer_stream, peer_addr, \
+ peer_port, timeout, serverid) \
+ tls_proxy_open((service), (flags), (peer_stream), (peer_addr), \
+ (peer_port), (timeout), (timeout), (serverid), \
+ (void *) 0, (void *) 0, (void *) 0)
+
+extern VSTREAM *tls_proxy_open(const char *, int, VSTREAM *, const char *,
+ const char *, int, int, const char *,
+ void *, void *, void *);
+
+#define TLS_PROXY_CLIENT_INIT_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \
+ a9, a10, a11, a12, a13, a14) \
+ (((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14))
+
+#define TLS_PROXY_CLIENT_START_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \
+ a9, a10, a11, a12, a13, a14) \
+ (((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14))
+
+extern TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *);
+extern void tls_proxy_context_free(TLS_SESS_STATE *);
+extern int tls_proxy_context_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+extern int tls_proxy_client_init_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_client_init_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+extern void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *);
+extern char *tls_proxy_client_init_serialize(ATTR_PRINT_COMMON_FN, VSTRING *, const TLS_CLIENT_INIT_PROPS *);
+
+extern int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+extern void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *);
+
+extern int tls_proxy_server_init_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_server_init_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+extern void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *);
+
+extern int tls_proxy_server_start_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int tls_proxy_server_start_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *);
+
+#endif /* USE_TLS */
+
+ /*
+ * TLSPROXY attributes, unconditionally exposed.
+ */
+#define TLS_ATTR_REMOTE_ENDPT "remote_endpoint" /* name[addr]:port */
+#define TLS_ATTR_FLAGS "flags"
+#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_SERVERID "serverid"
+
+#ifdef USE_TLS
+
+ /*
+ * Misc attributes.
+ */
+#define TLS_ATTR_COUNT "count"
+
+ /*
+ * TLS_SESS_STATE attributes.
+ */
+#define TLS_ATTR_PEER_CN "peer_CN"
+#define TLS_ATTR_ISSUER_CN "issuer_CN"
+#define TLS_ATTR_PEER_CERT_FPT "peer_fingerprint"
+#define TLS_ATTR_PEER_PKEY_FPT "peer_pubkey_fingerprint"
+#define TLS_ATTR_SEC_LEVEL "level"
+#define TLS_ATTR_PEER_STATUS "peer_status"
+#define TLS_ATTR_CIPHER_PROTOCOL "cipher_protocol"
+#define TLS_ATTR_CIPHER_NAME "cipher_name"
+#define TLS_ATTR_CIPHER_USEBITS "cipher_usebits"
+#define TLS_ATTR_CIPHER_ALGBITS "cipher_algbits"
+#define TLS_ATTR_KEX_NAME "key_exchange"
+#define TLS_ATTR_KEX_CURVE "key_exchange_curve"
+#define TLS_ATTR_KEX_BITS "key_exchange_bits"
+#define TLS_ATTR_CLNT_SIG_NAME "clnt_signature"
+#define TLS_ATTR_CLNT_SIG_CURVE "clnt_signature_curve"
+#define TLS_ATTR_CLNT_SIG_BITS "clnt_signature_bits"
+#define TLS_ATTR_CLNT_SIG_DGST "clnt_signature_digest"
+#define TLS_ATTR_SRVR_SIG_NAME "srvr_signature"
+#define TLS_ATTR_SRVR_SIG_CURVE "srvr_signature_curve"
+#define TLS_ATTR_SRVR_SIG_BITS "srvr_signature_bits"
+#define TLS_ATTR_SRVR_SIG_DGST "srvr_signature_digest"
+#define TLS_ATTR_NAMADDR "namaddr"
+
+ /*
+ * TLS_SERVER_INIT_PROPS attributes.
+ */
+#define TLS_ATTR_LOG_PARAM "log_param"
+#define TLS_ATTR_LOG_LEVEL "log_level"
+#define TLS_ATTR_VERIFYDEPTH "verifydepth"
+#define TLS_ATTR_CACHE_TYPE "cache_type"
+#define TLS_ATTR_SET_SESSID "set_sessid"
+#define TLS_ATTR_CHAIN_FILES "chain_files"
+#define TLS_ATTR_CERT_FILE "cert_file"
+#define TLS_ATTR_KEY_FILE "key_file"
+#define TLS_ATTR_DCERT_FILE "dcert_file"
+#define TLS_ATTR_DKEY_FILE "dkey_file"
+#define TLS_ATTR_ECCERT_FILE "eccert_file"
+#define TLS_ATTR_ECKEY_FILE "eckey_file"
+#define TLS_ATTR_CAFILE "CAfile"
+#define TLS_ATTR_CAPATH "CApath"
+#define TLS_ATTR_PROTOCOLS "protocols"
+#define TLS_ATTR_EECDH_GRADE "eecdh_grade"
+#define TLS_ATTR_DH1K_PARAM_FILE "dh1024_param_file"
+#define TLS_ATTR_DH512_PARAM_FILE "dh512_param_file"
+#define TLS_ATTR_ASK_CCERT "ask_ccert"
+#define TLS_ATTR_MDALG "mdalg"
+
+ /*
+ * TLS_SERVER_START_PROPS attributes.
+ */
+#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_REQUIRECERT "requirecert"
+#define TLS_ATTR_SERVERID "serverid"
+#define TLS_ATTR_NAMADDR "namaddr"
+#define TLS_ATTR_CIPHER_GRADE "cipher_grade"
+#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions"
+#define TLS_ATTR_MDALG "mdalg"
+
+ /*
+ * TLS_CLIENT_INIT_PROPS attributes.
+ */
+#define TLS_ATTR_CNF_FILE "config_file"
+#define TLS_ATTR_CNF_NAME "config_name"
+#define TLS_ATTR_LOG_PARAM "log_param"
+#define TLS_ATTR_LOG_LEVEL "log_level"
+#define TLS_ATTR_VERIFYDEPTH "verifydepth"
+#define TLS_ATTR_CACHE_TYPE "cache_type"
+#define TLS_ATTR_CHAIN_FILES "chain_files"
+#define TLS_ATTR_CERT_FILE "cert_file"
+#define TLS_ATTR_KEY_FILE "key_file"
+#define TLS_ATTR_DCERT_FILE "dcert_file"
+#define TLS_ATTR_DKEY_FILE "dkey_file"
+#define TLS_ATTR_ECCERT_FILE "eccert_file"
+#define TLS_ATTR_ECKEY_FILE "eckey_file"
+#define TLS_ATTR_CAFILE "CAfile"
+#define TLS_ATTR_CAPATH "CApath"
+#define TLS_ATTR_MDALG "mdalg"
+
+ /*
+ * TLS_CLIENT_START_PROPS attributes.
+ */
+#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_TLS_LEVEL "tls_level"
+#define TLS_ATTR_NEXTHOP "nexthop"
+#define TLS_ATTR_HOST "host"
+#define TLS_ATTR_NAMADDR "namaddr"
+#define TLS_ATTR_SNI "sni"
+#define TLS_ATTR_SERVERID "serverid"
+#define TLS_ATTR_HELO "helo"
+#define TLS_ATTR_PROTOCOLS "protocols"
+#define TLS_ATTR_CIPHER_GRADE "cipher_grade"
+#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions"
+#define TLS_ATTR_MATCHARGV "matchargv"
+#define TLS_ATTR_MDALG "mdalg"
+#define TLS_ATTR_DANE "dane"
+
+ /*
+ * TLS_TLSA attributes.
+ */
+#define TLS_ATTR_USAGE "usage"
+#define TLS_ATTR_SELECTOR "selector"
+#define TLS_ATTR_MTYPE "mtype"
+#define TLS_ATTR_DATA "data"
+
+ /*
+ * TLS_DANE attributes.
+ */
+#define TLS_ATTR_DOMAIN "domain"
+
+#endif
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/tls/tls_proxy_client_misc.c b/src/tls/tls_proxy_client_misc.c
new file mode 100644
index 0000000..2191dce
--- /dev/null
+++ b/src/tls/tls_proxy_client_misc.c
@@ -0,0 +1,130 @@
+/*++
+/* NAME
+/* tls_proxy_client_misc 3
+/* SUMMARY
+/* TLS_CLIENT_XXX structure support
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(params)
+/* TLS_CLIENT_PARAMS *params;
+/*
+/* char *tls_proxy_client_param_serialize(print_fn, buf, params)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTRING *buf;
+/* const TLS_CLIENT_PARAMS *params;
+/*
+/* char *tls_proxy_client_init_serialize(print_fn, buf, init_props)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTRING *buf;
+/* const TLS_CLIENT_INIT_PROPS *init_props;
+/* DESCRIPTION
+/* tls_proxy_client_param_from_config() initializes a TLS_CLIENT_PARAMS
+/* structure from configuration parameters and returns its
+/* argument. Strings are not copied. The result must therefore
+/* not be passed to tls_proxy_client_param_free().
+/*
+/* tls_proxy_client_param_serialize() and
+/* tls_proxy_client_init_serialize() serialize the specified
+/* object to a memory buffer, using the specified print function
+/* (typically, attr_print_plain). The result can be used
+/* determine whether there are any differences between instances
+/* of the same object type.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_client_param_from_config - initialize TLS_CLIENT_PARAMS from configuration */
+
+TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(TLS_CLIENT_PARAMS *params)
+{
+ TLS_PROXY_PARAMS(params,
+ tls_cnf_file = var_tls_cnf_file,
+ tls_cnf_name = var_tls_cnf_name,
+ tls_high_clist = var_tls_high_clist,
+ tls_medium_clist = var_tls_medium_clist,
+ tls_low_clist = var_tls_low_clist,
+ tls_export_clist = var_tls_export_clist,
+ tls_null_clist = var_tls_null_clist,
+ tls_eecdh_auto = var_tls_eecdh_auto,
+ tls_eecdh_strong = var_tls_eecdh_strong,
+ tls_eecdh_ultra = var_tls_eecdh_ultra,
+ tls_bug_tweaks = var_tls_bug_tweaks,
+ tls_ssl_options = var_tls_ssl_options,
+ tls_dane_digests = var_tls_dane_digests,
+ tls_mgr_service = var_tls_mgr_service,
+ tls_tkt_cipher = var_tls_tkt_cipher,
+ tls_daemon_rand_bytes = var_tls_daemon_rand_bytes,
+ tls_append_def_CA = var_tls_append_def_CA,
+ tls_bc_pkey_fprint = var_tls_bc_pkey_fprint,
+ tls_preempt_clist = var_tls_preempt_clist,
+ tls_multi_wildcard = var_tls_multi_wildcard);
+ return (params);
+}
+
+/* tls_proxy_client_param_serialize - serialize TLS_CLIENT_PARAMS to string */
+
+char *tls_proxy_client_param_serialize(ATTR_PRINT_COMMON_FN print_fn,
+ VSTRING *buf,
+ const TLS_CLIENT_PARAMS *params)
+{
+ const char myname[] = "tls_proxy_client_param_serialize";
+ VSTREAM *mp;
+
+ if ((mp = vstream_memopen(buf, O_WRONLY)) == 0
+ || print_fn(mp, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_client_param_print,
+ (const void *) params),
+ ATTR_TYPE_END) != 0
+ || vstream_fclose(mp) != 0)
+ msg_fatal("%s: can't serialize properties: %m", myname);
+ return (vstring_str(buf));
+}
+
+/* tls_proxy_client_init_serialize - serialize to string */
+
+char *tls_proxy_client_init_serialize(ATTR_PRINT_COMMON_FN print_fn,
+ VSTRING *buf,
+ const TLS_CLIENT_INIT_PROPS *props)
+{
+ const char myname[] = "tls_proxy_client_init_serialize";
+ VSTREAM *mp;
+
+ if ((mp = vstream_memopen(buf, O_WRONLY)) == 0
+ || print_fn(mp, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_client_init_print,
+ (const void *) props),
+ ATTR_TYPE_END) != 0
+ || vstream_fclose(mp) != 0)
+ msg_fatal("%s: can't serialize properties: %m", myname);
+ return (vstring_str(buf));
+}
+
+#endif
diff --git a/src/tls/tls_proxy_client_print.c b/src/tls/tls_proxy_client_print.c
new file mode 100644
index 0000000..fe0b397
--- /dev/null
+++ b/src/tls/tls_proxy_client_print.c
@@ -0,0 +1,294 @@
+/*++
+/* NAME
+/* tls_proxy_client_print 3
+/* SUMMARY
+/* write TLS_CLIENT_XXX structures to stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_client_param_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* const void *ptr;
+/*
+/* int tls_proxy_client_init_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* const void *ptr;
+/*
+/* int tls_proxy_client_start_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* const void *ptr;
+/* DESCRIPTION
+/* tls_proxy_client_param_print() writes a TLS_CLIENT_PARAMS structure to
+/* the named stream using the specified attribute print routine.
+/* tls_proxy_client_param_print() is meant to be passed as a call-back to
+/* attr_print(), thusly:
+/*
+/* SEND_ATTR_FUNC(tls_proxy_client_param_print, (const void *) param), ...
+/*
+/* tls_proxy_client_init_print() writes a full TLS_CLIENT_INIT_PROPS
+/* structure to the named stream using the specified attribute
+/* print routine. tls_proxy_client_init_print() is meant to
+/* be passed as a call-back to attr_print(), thusly:
+/*
+/* SEND_ATTR_FUNC(tls_proxy_client_init_print, (const void *) init_props), ...
+/*
+/* tls_proxy_client_start_print() writes a TLS_CLIENT_START_PROPS
+/* structure, without stream or file descriptor members, to
+/* the named stream using the specified attribute print routine.
+/* tls_proxy_client_start_print() is meant to be passed as a
+/* call-back to attr_print(), thusly:
+/*
+/* SEND_ATTR_FUNC(tls_proxy_client_start_print, (const void *) start_props), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <argv_attr.h>
+#include <attr.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* tls_proxy_client_param_print - send TLS_CLIENT_PARAMS over stream */
+
+int tls_proxy_client_param_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_CLIENT_PARAMS *params = (const TLS_CLIENT_PARAMS *) ptr;
+ int ret;
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_param_print");
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_CNF_FILE, params->tls_cnf_file),
+ SEND_ATTR_STR(TLS_ATTR_CNF_NAME, params->tls_cnf_name),
+ SEND_ATTR_STR(VAR_TLS_HIGH_CLIST, params->tls_high_clist),
+ SEND_ATTR_STR(VAR_TLS_MEDIUM_CLIST,
+ params->tls_medium_clist),
+ SEND_ATTR_STR(VAR_TLS_LOW_CLIST, params->tls_low_clist),
+ SEND_ATTR_STR(VAR_TLS_EXPORT_CLIST,
+ params->tls_export_clist),
+ SEND_ATTR_STR(VAR_TLS_NULL_CLIST, params->tls_null_clist),
+ SEND_ATTR_STR(VAR_TLS_EECDH_AUTO, params->tls_eecdh_auto),
+ SEND_ATTR_STR(VAR_TLS_EECDH_STRONG,
+ params->tls_eecdh_strong),
+ SEND_ATTR_STR(VAR_TLS_EECDH_ULTRA,
+ params->tls_eecdh_ultra),
+ SEND_ATTR_STR(VAR_TLS_BUG_TWEAKS, params->tls_bug_tweaks),
+ SEND_ATTR_STR(VAR_TLS_SSL_OPTIONS,
+ params->tls_ssl_options),
+ SEND_ATTR_STR(VAR_TLS_DANE_DIGESTS,
+ params->tls_dane_digests),
+ SEND_ATTR_STR(VAR_TLS_MGR_SERVICE,
+ params->tls_mgr_service),
+ SEND_ATTR_STR(VAR_TLS_TKT_CIPHER, params->tls_tkt_cipher),
+ SEND_ATTR_INT(VAR_TLS_DAEMON_RAND_BYTES,
+ params->tls_daemon_rand_bytes),
+ SEND_ATTR_INT(VAR_TLS_APPEND_DEF_CA,
+ params->tls_append_def_CA),
+ SEND_ATTR_INT(VAR_TLS_BC_PKEY_FPRINT,
+ params->tls_bc_pkey_fprint),
+ SEND_ATTR_INT(VAR_TLS_PREEMPT_CLIST,
+ params->tls_preempt_clist),
+ SEND_ATTR_INT(VAR_TLS_MULTI_WILDCARD,
+ params->tls_multi_wildcard),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_param_print ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_init_print - send TLS_CLIENT_INIT_PROPS over stream */
+
+int tls_proxy_client_init_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_CLIENT_INIT_PROPS *props = (const TLS_CLIENT_INIT_PROPS *) ptr;
+ int ret;
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_init_print");
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_LOG_PARAM,
+ STRING_OR_EMPTY(props->log_param)),
+ SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL,
+ STRING_OR_EMPTY(props->log_level)),
+ SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth),
+ SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE,
+ STRING_OR_EMPTY(props->cache_type)),
+ SEND_ATTR_STR(TLS_ATTR_CHAIN_FILES,
+ STRING_OR_EMPTY(props->chain_files)),
+ SEND_ATTR_STR(TLS_ATTR_CERT_FILE,
+ STRING_OR_EMPTY(props->cert_file)),
+ SEND_ATTR_STR(TLS_ATTR_KEY_FILE,
+ STRING_OR_EMPTY(props->key_file)),
+ SEND_ATTR_STR(TLS_ATTR_DCERT_FILE,
+ STRING_OR_EMPTY(props->dcert_file)),
+ SEND_ATTR_STR(TLS_ATTR_DKEY_FILE,
+ STRING_OR_EMPTY(props->dkey_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE,
+ STRING_OR_EMPTY(props->eccert_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE,
+ STRING_OR_EMPTY(props->eckey_file)),
+ SEND_ATTR_STR(TLS_ATTR_CAFILE,
+ STRING_OR_EMPTY(props->CAfile)),
+ SEND_ATTR_STR(TLS_ATTR_CAPATH,
+ STRING_OR_EMPTY(props->CApath)),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_init_print ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_tlsa_print - send TLS_TLSA over stream */
+
+static int tls_proxy_client_tlsa_print(ATTR_PRINT_COMMON_FN print_fn,
+ VSTREAM *fp, int flags, const void *ptr)
+{
+ const TLS_TLSA *head = (const TLS_TLSA *) ptr;
+ const TLS_TLSA *tp;
+ int count;
+ int ret;
+
+ for (tp = head, count = 0; tp != 0; tp = tp->next)
+ ++count;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_print count=%d", count);
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_COUNT, count),
+ ATTR_TYPE_END);
+
+ for (tp = head; ret == 0 && tp != 0; tp = tp->next)
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_USAGE, tp->usage),
+ SEND_ATTR_INT(TLS_ATTR_SELECTOR, tp->selector),
+ SEND_ATTR_INT(TLS_ATTR_MTYPE, tp->mtype),
+ SEND_ATTR_DATA(TLS_ATTR_DATA, tp->length, tp->data),
+ ATTR_TYPE_END);
+
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_print ret=%d", count);
+ return (ret);
+}
+
+/* tls_proxy_client_dane_print - send TLS_DANE over stream */
+
+static int tls_proxy_client_dane_print(ATTR_PRINT_COMMON_FN print_fn,
+ VSTREAM *fp, int flags, const void *ptr)
+{
+ const TLS_DANE *dane = (const TLS_DANE *) ptr;
+ int ret;
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_DANE, dane != 0),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_print dane=%d", dane != 0);
+
+ if (ret == 0 && dane != 0) {
+ /* Send the base_domain and RRs, we don't need the other fields */
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_DOMAIN,
+ STRING_OR_EMPTY(dane->base_domain)),
+ SEND_ATTR_FUNC(tls_proxy_client_tlsa_print,
+ (const void *) dane->tlsa),
+ ATTR_TYPE_END);
+ }
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_print ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_start_print - send TLS_CLIENT_START_PROPS over stream */
+
+int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN print_fn,
+ VSTREAM *fp, int flags, const void *ptr)
+{
+ const TLS_CLIENT_START_PROPS *props = (const TLS_CLIENT_START_PROPS *) ptr;
+ int ret;
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_start_print");
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout),
+ SEND_ATTR_INT(TLS_ATTR_TLS_LEVEL, props->tls_level),
+ SEND_ATTR_STR(TLS_ATTR_NEXTHOP,
+ STRING_OR_EMPTY(props->nexthop)),
+ SEND_ATTR_STR(TLS_ATTR_HOST,
+ STRING_OR_EMPTY(props->host)),
+ SEND_ATTR_STR(TLS_ATTR_NAMADDR,
+ STRING_OR_EMPTY(props->namaddr)),
+ SEND_ATTR_STR(TLS_ATTR_SNI,
+ STRING_OR_EMPTY(props->sni)),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID,
+ STRING_OR_EMPTY(props->serverid)),
+ SEND_ATTR_STR(TLS_ATTR_HELO,
+ STRING_OR_EMPTY(props->helo)),
+ SEND_ATTR_STR(TLS_ATTR_PROTOCOLS,
+ STRING_OR_EMPTY(props->protocols)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE,
+ STRING_OR_EMPTY(props->cipher_grade)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ STRING_OR_EMPTY(props->cipher_exclusions)),
+ SEND_ATTR_FUNC(argv_attr_print,
+ (const void *) props->matchargv),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ SEND_ATTR_FUNC(tls_proxy_client_dane_print,
+ (const void *) props->dane),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_start_print ret=%d", ret);
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_client_scan.c b/src/tls/tls_proxy_client_scan.c
new file mode 100644
index 0000000..7083353
--- /dev/null
+++ b/src/tls/tls_proxy_client_scan.c
@@ -0,0 +1,496 @@
+/*++
+/* NAME
+/* tls_proxy_client_scan 3
+/* SUMMARY
+/* read TLS_CLIENT_XXX structures from stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_client_param_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_client_param_free(params)
+/* TLS_CLIENT_PARAMS *params;
+/*
+/* int tls_proxy_client_init_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_client_init_free(init_props)
+/* TLS_CLIENT_INIT_PROPS *init_props;
+/*
+/* int tls_proxy_client_start_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_client_start_free(start_props)
+/* TLS_CLIENT_START_PROPS *start_props;
+/* DESCRIPTION
+/* tls_proxy_client_param_scan() reads a TLS_CLIENT_PARAMS structure from
+/* the named stream using the specified attribute scan routine.
+/* tls_proxy_client_param_scan() is meant to be passed as a call-back
+/* function to attr_scan(), as shown below.
+/*
+/* tls_proxy_client_param_free() destroys a TLS_CLIENT_PARAMS structure
+/* that was created by tls_proxy_client_param_scan().
+/*
+/* TLS_CLIENT_PARAMS *param = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_client_param_scan, (void *) &param)
+/* ...
+/* if (param != 0)
+/* tls_proxy_client_param_free(param);
+/*
+/* tls_proxy_client_init_scan() reads a full TLS_CLIENT_INIT_PROPS
+/* structure from the named stream using the specified attribute
+/* scan routine. tls_proxy_client_init_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_client_init_free() destroys a TLS_CLIENT_INIT_PROPS
+/* structure that was created by tls_proxy_client_init_scan().
+/*
+/* TLS_CLIENT_INIT_PROPS *init_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_client_init_scan, (void *) &init_props)
+/* ...
+/* if (init_props != 0)
+/* tls_proxy_client_init_free(init_props);
+/*
+/* tls_proxy_client_start_scan() reads a TLS_CLIENT_START_PROPS
+/* structure, without the stream of file descriptor members,
+/* from the named stream using the specified attribute scan
+/* routine. tls_proxy_client_start_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_client_start_free() destroys a TLS_CLIENT_START_PROPS
+/* structure that was created by tls_proxy_client_start_scan().
+/*
+/* TLS_CLIENT_START_PROPS *start_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_client_start_scan, (void *) &start_props)
+/* ...
+/* if (start_props != 0)
+/* tls_proxy_client_start_free(start_props);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <argv_attr.h>
+#include <attr.h>
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+#include <tls_proxy.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* tls_proxy_client_param_free - destroy TLS_CLIENT_PARAMS structure */
+
+void tls_proxy_client_param_free(TLS_CLIENT_PARAMS *params)
+{
+ myfree(params->tls_cnf_file);
+ myfree(params->tls_cnf_name);
+ myfree(params->tls_high_clist);
+ myfree(params->tls_medium_clist);
+ myfree(params->tls_low_clist);
+ myfree(params->tls_export_clist);
+ myfree(params->tls_null_clist);
+ myfree(params->tls_eecdh_auto);
+ myfree(params->tls_eecdh_strong);
+ myfree(params->tls_eecdh_ultra);
+ myfree(params->tls_bug_tweaks);
+ myfree(params->tls_ssl_options);
+ myfree(params->tls_dane_digests);
+ myfree(params->tls_mgr_service);
+ myfree(params->tls_tkt_cipher);
+ myfree((void *) params);
+}
+
+/* tls_proxy_client_param_scan - receive TLS_CLIENT_PARAMS from stream */
+
+int tls_proxy_client_param_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_PARAMS *params
+ = (TLS_CLIENT_PARAMS *) mymalloc(sizeof(*params));
+ int ret;
+ VSTRING *cnf_file = vstring_alloc(25);
+ VSTRING *cnf_name = vstring_alloc(25);
+ VSTRING *tls_high_clist = vstring_alloc(25);
+ VSTRING *tls_medium_clist = vstring_alloc(25);
+ VSTRING *tls_low_clist = vstring_alloc(25);
+ VSTRING *tls_export_clist = vstring_alloc(25);
+ VSTRING *tls_null_clist = vstring_alloc(25);
+ VSTRING *tls_eecdh_auto = vstring_alloc(25);
+ VSTRING *tls_eecdh_strong = vstring_alloc(25);
+ VSTRING *tls_eecdh_ultra = vstring_alloc(25);
+ VSTRING *tls_bug_tweaks = vstring_alloc(25);
+ VSTRING *tls_ssl_options = vstring_alloc(25);
+ VSTRING *tls_dane_digests = vstring_alloc(25);
+ VSTRING *tls_mgr_service = vstring_alloc(25);
+ VSTRING *tls_tkt_cipher = vstring_alloc(25);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_param_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(params, 0, sizeof(*params));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_CNF_FILE, cnf_file),
+ RECV_ATTR_STR(TLS_ATTR_CNF_NAME, cnf_name),
+ RECV_ATTR_STR(VAR_TLS_HIGH_CLIST, tls_high_clist),
+ RECV_ATTR_STR(VAR_TLS_MEDIUM_CLIST, tls_medium_clist),
+ RECV_ATTR_STR(VAR_TLS_LOW_CLIST, tls_low_clist),
+ RECV_ATTR_STR(VAR_TLS_EXPORT_CLIST, tls_export_clist),
+ RECV_ATTR_STR(VAR_TLS_NULL_CLIST, tls_null_clist),
+ RECV_ATTR_STR(VAR_TLS_EECDH_AUTO, tls_eecdh_auto),
+ RECV_ATTR_STR(VAR_TLS_EECDH_STRONG, tls_eecdh_strong),
+ RECV_ATTR_STR(VAR_TLS_EECDH_ULTRA, tls_eecdh_ultra),
+ RECV_ATTR_STR(VAR_TLS_BUG_TWEAKS, tls_bug_tweaks),
+ RECV_ATTR_STR(VAR_TLS_SSL_OPTIONS, tls_ssl_options),
+ RECV_ATTR_STR(VAR_TLS_DANE_DIGESTS, tls_dane_digests),
+ RECV_ATTR_STR(VAR_TLS_MGR_SERVICE, tls_mgr_service),
+ RECV_ATTR_STR(VAR_TLS_TKT_CIPHER, tls_tkt_cipher),
+ RECV_ATTR_INT(VAR_TLS_DAEMON_RAND_BYTES,
+ &params->tls_daemon_rand_bytes),
+ RECV_ATTR_INT(VAR_TLS_APPEND_DEF_CA,
+ &params->tls_append_def_CA),
+ RECV_ATTR_INT(VAR_TLS_BC_PKEY_FPRINT,
+ &params->tls_bc_pkey_fprint),
+ RECV_ATTR_INT(VAR_TLS_PREEMPT_CLIST,
+ &params->tls_preempt_clist),
+ RECV_ATTR_INT(VAR_TLS_MULTI_WILDCARD,
+ &params->tls_multi_wildcard),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ params->tls_cnf_file = vstring_export(cnf_file);
+ params->tls_cnf_name = vstring_export(cnf_name);
+ params->tls_high_clist = vstring_export(tls_high_clist);
+ params->tls_medium_clist = vstring_export(tls_medium_clist);
+ params->tls_low_clist = vstring_export(tls_low_clist);
+ params->tls_export_clist = vstring_export(tls_export_clist);
+ params->tls_null_clist = vstring_export(tls_null_clist);
+ params->tls_eecdh_auto = vstring_export(tls_eecdh_auto);
+ params->tls_eecdh_strong = vstring_export(tls_eecdh_strong);
+ params->tls_eecdh_ultra = vstring_export(tls_eecdh_ultra);
+ params->tls_bug_tweaks = vstring_export(tls_bug_tweaks);
+ params->tls_ssl_options = vstring_export(tls_ssl_options);
+ params->tls_dane_digests = vstring_export(tls_dane_digests);
+ params->tls_mgr_service = vstring_export(tls_mgr_service);
+ params->tls_tkt_cipher = vstring_export(tls_tkt_cipher);
+
+ ret = (ret == 20 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_client_param_free(params);
+ params = 0;
+ }
+ *(TLS_CLIENT_PARAMS **) ptr = params;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_param_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_init_free - destroy TLS_CLIENT_INIT_PROPS structure */
+
+void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *props)
+{
+ myfree((void *) props->log_param);
+ myfree((void *) props->log_level);
+ myfree((void *) props->cache_type);
+ myfree((void *) props->chain_files);
+ myfree((void *) props->cert_file);
+ myfree((void *) props->key_file);
+ myfree((void *) props->dcert_file);
+ myfree((void *) props->dkey_file);
+ myfree((void *) props->eccert_file);
+ myfree((void *) props->eckey_file);
+ myfree((void *) props->CAfile);
+ myfree((void *) props->CApath);
+ myfree((void *) props->mdalg);
+ myfree((void *) props);
+}
+
+/* tls_proxy_client_init_scan - receive TLS_CLIENT_INIT_PROPS from stream */
+
+int tls_proxy_client_init_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_INIT_PROPS *props
+ = (TLS_CLIENT_INIT_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *log_param = vstring_alloc(25);
+ VSTRING *log_level = vstring_alloc(25);
+ VSTRING *cache_type = vstring_alloc(25);
+ VSTRING *chain_files = vstring_alloc(25);
+ VSTRING *cert_file = vstring_alloc(25);
+ VSTRING *key_file = vstring_alloc(25);
+ VSTRING *dcert_file = vstring_alloc(25);
+ VSTRING *dkey_file = vstring_alloc(25);
+ VSTRING *eccert_file = vstring_alloc(25);
+ VSTRING *eckey_file = vstring_alloc(25);
+ VSTRING *CAfile = vstring_alloc(25);
+ VSTRING *CApath = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_init_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param),
+ RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level),
+ RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth),
+ RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_STR(TLS_ATTR_CHAIN_FILES, chain_files),
+ RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file),
+ RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file),
+ RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file),
+ RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file),
+ RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file),
+ RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file),
+ RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile),
+ RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ props->log_param = vstring_export(log_param);
+ props->log_level = vstring_export(log_level);
+ props->cache_type = vstring_export(cache_type);
+ props->chain_files = vstring_export(chain_files);
+ props->cert_file = vstring_export(cert_file);
+ props->key_file = vstring_export(key_file);
+ props->dcert_file = vstring_export(dcert_file);
+ props->dkey_file = vstring_export(dkey_file);
+ props->eccert_file = vstring_export(eccert_file);
+ props->eckey_file = vstring_export(eckey_file);
+ props->CAfile = vstring_export(CAfile);
+ props->CApath = vstring_export(CApath);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 14 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_client_init_free(props);
+ props = 0;
+ }
+ *(TLS_CLIENT_INIT_PROPS **) ptr = props;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_init_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_start_free - destroy TLS_CLIENT_START_PROPS structure */
+
+void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *props)
+{
+ myfree((void *) props->nexthop);
+ myfree((void *) props->host);
+ myfree((void *) props->namaddr);
+ myfree((void *) props->sni);
+ myfree((void *) props->serverid);
+ myfree((void *) props->helo);
+ myfree((void *) props->protocols);
+ myfree((void *) props->cipher_grade);
+ myfree((void *) props->cipher_exclusions);
+ if (props->matchargv)
+ argv_free((ARGV *) props->matchargv);
+ myfree((void *) props->mdalg);
+ if (props->dane)
+ tls_dane_free((TLS_DANE *) props->dane);
+ myfree((void *) props);
+}
+
+/* tls_proxy_client_tlsa_scan - receive TLS_TLSA from stream */
+
+static int tls_proxy_client_tlsa_scan(ATTR_SCAN_COMMON_FN scan_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ static VSTRING *data;
+ TLS_TLSA *head;
+ int count;
+ int ret;
+
+ if (data == 0)
+ data = vstring_alloc(64);
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_COUNT, &count),
+ ATTR_TYPE_END);
+ if (ret == 1 && msg_verbose)
+ msg_info("tls_proxy_client_tlsa_scan count=%d", count);
+
+ for (head = 0; ret == 1 && count > 0; --count) {
+ int u, s, m;
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_USAGE, &u),
+ RECV_ATTR_INT(TLS_ATTR_SELECTOR, &s),
+ RECV_ATTR_INT(TLS_ATTR_MTYPE, &m),
+ RECV_ATTR_DATA(TLS_ATTR_DATA, data),
+ ATTR_TYPE_END);
+ if (ret == 4) {
+ ret = 1;
+ /* This makes a copy of the static vstring content */
+ head = tlsa_prepend(head, u, s, m, (unsigned char *) STR(data),
+ LEN(data));
+ } else
+ ret = -1;
+ }
+
+ if (ret != 1) {
+ tls_tlsa_free(head);
+ head = 0;
+ }
+ *(TLS_TLSA **) ptr = head;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_dane_scan - receive TLS_DANE from stream */
+
+static int tls_proxy_client_dane_scan(ATTR_SCAN_COMMON_FN scan_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ TLS_DANE *dane = 0;
+ int ret;
+ int have_dane = 0;
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_DANE, &have_dane),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_scan have_dane=%d", have_dane);
+
+ if (ret == 1 && have_dane) {
+ VSTRING *base_domain = vstring_alloc(25);
+
+ dane = tls_dane_alloc();
+ /* We only need the base domain and TLSA RRs */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_DOMAIN, base_domain),
+ RECV_ATTR_FUNC(tls_proxy_client_tlsa_scan,
+ &dane->tlsa),
+ ATTR_TYPE_END);
+
+ /* Always construct a well-formed structure. */
+ dane->base_domain = vstring_export(base_domain);
+ ret = (ret == 2 ? 1 : -1);
+ if (ret != 1) {
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+ *(TLS_DANE **) ptr = dane;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_start_scan - receive TLS_CLIENT_START_PROPS from stream */
+
+int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_START_PROPS *props
+ = (TLS_CLIENT_START_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *nexthop = vstring_alloc(25);
+ VSTRING *host = vstring_alloc(25);
+ VSTRING *namaddr = vstring_alloc(25);
+ VSTRING *sni = vstring_alloc(25);
+ VSTRING *serverid = vstring_alloc(25);
+ VSTRING *helo = vstring_alloc(25);
+ VSTRING *protocols = vstring_alloc(25);
+ VSTRING *cipher_grade = vstring_alloc(25);
+ VSTRING *cipher_exclusions = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_start_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ props->ctx = 0;
+ props->stream = 0;
+ props->fd = -1;
+ props->dane = 0; /* scan_fn may return early */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout),
+ RECV_ATTR_INT(TLS_ATTR_TLS_LEVEL, &props->tls_level),
+ RECV_ATTR_STR(TLS_ATTR_NEXTHOP, nexthop),
+ RECV_ATTR_STR(TLS_ATTR_HOST, host),
+ RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr),
+ RECV_ATTR_STR(TLS_ATTR_SNI, sni),
+ RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid),
+ RECV_ATTR_STR(TLS_ATTR_HELO, helo),
+ RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ cipher_exclusions),
+ RECV_ATTR_FUNC(argv_attr_scan, &props->matchargv),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ RECV_ATTR_FUNC(tls_proxy_client_dane_scan,
+ &props->dane),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ props->nexthop = vstring_export(nexthop);
+ props->host = vstring_export(host);
+ props->namaddr = vstring_export(namaddr);
+ props->sni = vstring_export(sni);
+ props->serverid = vstring_export(serverid);
+ props->helo = vstring_export(helo);
+ props->protocols = vstring_export(protocols);
+ props->cipher_grade = vstring_export(cipher_grade);
+ props->cipher_exclusions = vstring_export(cipher_exclusions);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 14 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_client_start_free(props);
+ props = 0;
+ }
+ *(TLS_CLIENT_START_PROPS **) ptr = props;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_start_scan ret=%d", ret);
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_clnt.c b/src/tls/tls_proxy_clnt.c
new file mode 100644
index 0000000..ca6a2e4
--- /dev/null
+++ b/src/tls/tls_proxy_clnt.c
@@ -0,0 +1,300 @@
+/*++
+/* NAME
+/* tlsproxy_clnt 3
+/* SUMMARY
+/* tlsproxy(8) client support
+/* SYNOPSIS
+/* #include <tlsproxy_clnt.h>
+/*
+/* VSTREAM *tls_proxy_open(service, flags, peer_stream, peer_addr,
+/* peer_port, handshake_timeout, session_timeout,
+/* serverid, tls_params, init_props, start_props)
+/* const char *service;
+/* int flags;
+/* VSTREAM *peer_stream;
+/* const char *peer_addr;
+/* const char *peer_port;
+/* int handshake_timeout;
+/* int session_timeout;
+/* const char *serverid;
+/* void *tls_params;
+/* void *init_props;
+/* void *start_props;
+/*
+/* TLS_SESS_STATE *tls_proxy_context_receive(proxy_stream)
+/* VSTREAM *proxy_stream;
+/* AUXILIARY FUNCTIONS
+/* VSTREAM *tls_proxy_legacy_open(service, flags, peer_stream,
+/* peer_addr, peer_port,
+/* timeout, serverid)
+/* const char *service;
+/* int flags;
+/* VSTREAM *peer_stream;
+/* const char *peer_addr;
+/* const char *peer_port;
+/* int timeout;
+/* const char *serverid;
+/* DESCRIPTION
+/* tls_proxy_open() prepares for inserting the tlsproxy(8)
+/* daemon between the current process and a remote peer (the
+/* actual insert operation is described in the next paragraph).
+/* The result value is a null pointer on failure. The peer_stream
+/* is not closed. The resulting proxy stream is single-buffered.
+/*
+/* After this, it is a good idea to use the CA_VSTREAM_CTL_SWAP_FD
+/* request to swap the file descriptors between the plaintext
+/* peer_stream and the proxy stream from tls_proxy_open().
+/* This avoids the loss of application-configurable VSTREAM
+/* attributes on the plaintext peer_stream (such as longjmp
+/* buffer, timeout, etc.). Once the file descriptors are
+/* swapped, the proxy stream should be closed.
+/*
+/* tls_proxy_context_receive() receives the TLS context object
+/* for the named proxy stream. This function must be called
+/* only if the TLS_PROXY_SEND_CONTEXT flag was specified in
+/* the tls_proxy_open() call. Note that this TLS context object
+/* is not compatible with tls_session_free(). It must be given
+/* to tls_proxy_context_free() instead.
+/*
+/* After this, the proxy_stream is ready for plain-text I/O.
+/*
+/* tls_proxy_legacy_open() is a backwards-compatibility feature
+/* that provides a historical interface.
+/*
+/* Arguments:
+/* .IP service
+/* The (base) name of the tlsproxy service.
+/* .IP flags
+/* Bit-wise OR of:
+/* .RS
+/* .IP TLS_PROXY_FLAG_ROLE_SERVER
+/* Request the TLS server proxy role.
+/* .IP TLS_PROXY_FLAG_ROLE_CLIENT
+/* Request the TLS client proxy role.
+/* .IP TLS_PROXY_FLAG_SEND_CONTEXT
+/* Send the TLS context object.
+/* .RE
+/* .IP peer_stream
+/* Stream that connects the current process to a remote peer.
+/* .IP peer_addr
+/* Printable IP address of the remote peer_stream endpoint.
+/* .IP peer_port
+/* Printable TCP port of the remote peer_stream endpoint.
+/* .IP handshake_timeout
+/* Time limit that the tlsproxy(8) daemon should use during
+/* the TLS handshake.
+/* .IP session_timeout
+/* Time limit that the tlsproxy(8) daemon should use after the
+/* TLS handshake.
+/* .IP serverid
+/* Unique service identifier.
+/* .IP tls_params
+/* Pointer to TLS_CLIENT_PARAMS or TLS_SERVER_PARAMS.
+/* .IP init_props
+/* Pointer to TLS_CLIENT_INIT_PROPS or TLS_SERVER_INIT_PROPS.
+/* .IP start_props
+/* Pointer to TLS_CLIENT_START_PROPS or TLS_SERVER_START_PROPS.
+/* .IP proxy_stream
+/* Stream from tls_proxy_open().
+/* .IP tls_context
+/* TLS session object from tls_proxy_context_receive().
+/* 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
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <stringops.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_params.h>
+
+/* TLS library-specific. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+#define TLSPROXY_INIT_TIMEOUT 10
+
+/* SLMs. */
+
+#define STR vstring_str
+
+/* tls_proxy_open - open negotiations with TLS proxy */
+
+VSTREAM *tls_proxy_open(const char *service, int flags,
+ VSTREAM *peer_stream,
+ const char *peer_addr,
+ const char *peer_port,
+ int handshake_timeout,
+ int session_timeout,
+ const char *serverid,
+ void *tls_params,
+ void *init_props,
+ void *start_props)
+{
+ const char myname[] = "tls_proxy_open";
+ VSTREAM *tlsproxy_stream;
+ int status;
+ int fd;
+ static VSTRING *tlsproxy_service = 0;
+ static VSTRING *remote_endpt = 0;
+
+ /*
+ * Initialize.
+ */
+ if (tlsproxy_service == 0) {
+ tlsproxy_service = vstring_alloc(20);
+ remote_endpt = vstring_alloc(20);
+ }
+
+ /*
+ * Connect to the tlsproxy(8) daemon.
+ */
+ vstring_sprintf(tlsproxy_service, "%s/%s", MAIL_CLASS_PRIVATE, service);
+ if ((fd = LOCAL_CONNECT(STR(tlsproxy_service), BLOCKING,
+ TLSPROXY_INIT_TIMEOUT)) < 0) {
+ msg_warn("connect to %s service: %m", STR(tlsproxy_service));
+ return (0);
+ }
+
+ /*
+ * Initial handshake. Send common data attributes now, and send the
+ * remote peer file descriptor in a later transaction.
+ */
+ tlsproxy_stream = vstream_fdopen(fd, O_RDWR);
+ if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY),
+ ATTR_TYPE_END) != 0) {
+ msg_warn("error receiving %s service initial response",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+ vstring_sprintf(remote_endpt, "[%s]:%s", peer_addr, peer_port);
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, STR(remote_endpt)),
+ SEND_ATTR_INT(TLS_ATTR_FLAGS, flags),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, handshake_timeout),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, session_timeout),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID, serverid),
+ ATTR_TYPE_END);
+ /* Do not flush the stream yet. */
+ if (vstream_ferror(tlsproxy_stream) != 0) {
+ msg_warn("error sending request to %s service: %m",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+ switch (flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) {
+ case TLS_PROXY_FLAG_ROLE_CLIENT:
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_client_param_print, tls_params),
+ SEND_ATTR_FUNC(tls_proxy_client_init_print, init_props),
+ SEND_ATTR_FUNC(tls_proxy_client_start_print, start_props),
+ ATTR_TYPE_END);
+ break;
+ case TLS_PROXY_FLAG_ROLE_SERVER:
+#if 0
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_server_param_print, tls_params),
+ SEND_ATTR_FUNC(tls_proxy_server_init_print, init_props),
+ SEND_ATTR_FUNC(tls_proxy_server_start_print, start_props),
+ ATTR_TYPE_END);
+#endif
+ break;
+ default:
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+ }
+ if (vstream_fflush(tlsproxy_stream) != 0) {
+ msg_warn("error sending request to %s service: %m",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+
+ /*
+ * Receive the "TLS is available" indication.
+ *
+ * This may seem out of order, but we must have a read transaction between
+ * sending the request attributes and sending the plaintext file
+ * descriptor. We can't assume UNIX-domain socket semantics here.
+ */
+ if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ /* TODO: informative message. */
+ ATTR_TYPE_END) != 1 || status == 0) {
+
+ /*
+ * The TLS proxy reports that the TLS engine is not available (due to
+ * configuration error, or other causes).
+ */
+ msg_warn("%s service role \"%s\" is not available",
+ STR(tlsproxy_service),
+ (flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "server" :
+ (flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "client" :
+ "bogus role");
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+
+ /*
+ * Send the remote peer file descriptor.
+ */
+ if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream),
+ vstream_fileno(peer_stream)) < 0) {
+
+ /*
+ * Some error: drop the TLS proxy stream.
+ */
+ msg_warn("sending file handle to %s service: %m",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+ return (tlsproxy_stream);
+}
+
+
+/* tls_proxy_context_receive - receive TLS session object from tlsproxy(8) */
+
+TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *proxy_stream)
+{
+ TLS_SESS_STATE *tls_context = 0;
+
+ if (attr_scan(proxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context),
+ ATTR_TYPE_END) != 1) {
+ if (tls_context)
+ tls_proxy_context_free(tls_context);
+ return (0);
+ } else {
+ return (tls_context);
+ }
+}
+
+#endif
diff --git a/src/tls/tls_proxy_context_print.c b/src/tls/tls_proxy_context_print.c
new file mode 100644
index 0000000..04123cb
--- /dev/null
+++ b/src/tls/tls_proxy_context_print.c
@@ -0,0 +1,114 @@
+/*++
+/* NAME
+/* tls_proxy_context_print
+/* SUMMARY
+/* write TLS_ATTR_STATE structure to stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_context_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* const void *ptr;
+/* DESCRIPTION
+/* tls_proxy_context_print() writes the public members of a
+/* TLS_ATTR_STATE structure to the named stream using the
+/* specified attribute print routine. tls_proxy_context_print()
+/* is meant to be passed as a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(tls_proxy_context_print, (const void *) tls_context), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_context_print - send TLS session state over stream */
+
+int tls_proxy_context_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_SESS_STATE *tp = (const TLS_SESS_STATE *) ptr;
+ int ret;
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_PEER_CN,
+ STRING_OR_EMPTY(tp->peer_CN)),
+ SEND_ATTR_STR(TLS_ATTR_ISSUER_CN,
+ STRING_OR_EMPTY(tp->issuer_CN)),
+ SEND_ATTR_STR(TLS_ATTR_PEER_CERT_FPT,
+ STRING_OR_EMPTY(tp->peer_cert_fprint)),
+ SEND_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT,
+ STRING_OR_EMPTY(tp->peer_pkey_fprint)),
+ SEND_ATTR_INT(TLS_ATTR_SEC_LEVEL,
+ tp->level),
+ SEND_ATTR_INT(TLS_ATTR_PEER_STATUS,
+ tp->peer_status),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL,
+ STRING_OR_EMPTY(tp->protocol)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_NAME,
+ STRING_OR_EMPTY(tp->cipher_name)),
+ SEND_ATTR_INT(TLS_ATTR_CIPHER_USEBITS,
+ tp->cipher_usebits),
+ SEND_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS,
+ tp->cipher_algbits),
+ SEND_ATTR_STR(TLS_ATTR_KEX_NAME,
+ STRING_OR_EMPTY(tp->kex_name)),
+ SEND_ATTR_STR(TLS_ATTR_KEX_CURVE,
+ STRING_OR_EMPTY(tp->kex_curve)),
+ SEND_ATTR_INT(TLS_ATTR_KEX_BITS,
+ tp->kex_bits),
+ SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME,
+ STRING_OR_EMPTY(tp->clnt_sig_name)),
+ SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE,
+ STRING_OR_EMPTY(tp->clnt_sig_curve)),
+ SEND_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS,
+ tp->clnt_sig_bits),
+ SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_DGST,
+ STRING_OR_EMPTY(tp->clnt_sig_dgst)),
+ SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_NAME,
+ STRING_OR_EMPTY(tp->srvr_sig_name)),
+ SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_CURVE,
+ STRING_OR_EMPTY(tp->srvr_sig_curve)),
+ SEND_ATTR_INT(TLS_ATTR_SRVR_SIG_BITS,
+ tp->srvr_sig_bits),
+ SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_DGST,
+ STRING_OR_EMPTY(tp->srvr_sig_dgst)),
+ SEND_ATTR_STR(TLS_ATTR_NAMADDR,
+ STRING_OR_EMPTY(tp->namaddr)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_context_scan.c b/src/tls/tls_proxy_context_scan.c
new file mode 100644
index 0000000..1d463ad
--- /dev/null
+++ b/src/tls/tls_proxy_context_scan.c
@@ -0,0 +1,190 @@
+/*++
+/* NAME
+/* tls_proxy_context_scan
+/* SUMMARY
+/* read TLS session state from stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_context_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_context_free(tls_context)
+/* TLS_SESS_STATE *tls_context;
+/* DESCRIPTION
+/* tls_proxy_context_scan() reads the public members of a
+/* TLS_ATTR_STATE structure from the named stream using the
+/* specified attribute scan routine. tls_proxy_context_scan()
+/* is meant to be passed as a call-back to attr_scan() as shown
+/* below.
+/*
+/* tls_proxy_context_free() destroys a TLS context object that
+/* was received with tls_proxy_context_scan().
+/*
+/* TLS_ATTR_STATE *tls_context = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context), ...
+/* ...
+/* if (tls_context)
+/* tls_proxy_context_free(tls_context);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* 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
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+#include <msg.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_context_scan - receive TLS session state from stream */
+
+int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SESS_STATE *tls_context
+ = (TLS_SESS_STATE *) mymalloc(sizeof(*tls_context));;
+ int ret;
+ VSTRING *peer_CN = vstring_alloc(25);
+ VSTRING *issuer_CN = vstring_alloc(25);
+ VSTRING *peer_cert_fprint = vstring_alloc(60); /* 60 for SHA-1 */
+ VSTRING *peer_pkey_fprint = vstring_alloc(60); /* 60 for SHA-1 */
+ VSTRING *protocol = vstring_alloc(25);
+ VSTRING *cipher_name = vstring_alloc(25);
+ VSTRING *kex_name = vstring_alloc(25);
+ VSTRING *kex_curve = vstring_alloc(25);
+ VSTRING *clnt_sig_name = vstring_alloc(25);
+ VSTRING *clnt_sig_curve = vstring_alloc(25);
+ VSTRING *clnt_sig_dgst = vstring_alloc(25);
+ VSTRING *srvr_sig_name = vstring_alloc(25);
+ VSTRING *srvr_sig_curve = vstring_alloc(25);
+ VSTRING *srvr_sig_dgst = vstring_alloc(25);
+ VSTRING *namaddr = vstring_alloc(100);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_context_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(tls_context, 0, sizeof(*tls_context));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_PEER_CN, peer_CN),
+ RECV_ATTR_STR(TLS_ATTR_ISSUER_CN, issuer_CN),
+ RECV_ATTR_STR(TLS_ATTR_PEER_CERT_FPT, peer_cert_fprint),
+ RECV_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT, peer_pkey_fprint),
+ RECV_ATTR_INT(TLS_ATTR_SEC_LEVEL,
+ &tls_context->level),
+ RECV_ATTR_INT(TLS_ATTR_PEER_STATUS,
+ &tls_context->peer_status),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL, protocol),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_NAME, cipher_name),
+ RECV_ATTR_INT(TLS_ATTR_CIPHER_USEBITS,
+ &tls_context->cipher_usebits),
+ RECV_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS,
+ &tls_context->cipher_algbits),
+ RECV_ATTR_STR(TLS_ATTR_KEX_NAME, kex_name),
+ RECV_ATTR_STR(TLS_ATTR_KEX_CURVE, kex_curve),
+ RECV_ATTR_INT(TLS_ATTR_KEX_BITS, &tls_context->kex_bits),
+ RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME, clnt_sig_name),
+ RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE, clnt_sig_curve),
+ RECV_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS, &tls_context->clnt_sig_bits),
+ RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_DGST, clnt_sig_dgst),
+ RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_NAME, srvr_sig_name),
+ RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_CURVE, srvr_sig_curve),
+ RECV_ATTR_INT(TLS_ATTR_SRVR_SIG_BITS, &tls_context->srvr_sig_bits),
+ RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_DGST, srvr_sig_dgst),
+ RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ tls_context->peer_CN = vstring_export(peer_CN);
+ tls_context->issuer_CN = vstring_export(issuer_CN);
+ tls_context->peer_cert_fprint = vstring_export(peer_cert_fprint);
+ tls_context->peer_pkey_fprint = vstring_export(peer_pkey_fprint);
+ tls_context->protocol = vstring_export(protocol);
+ tls_context->cipher_name = vstring_export(cipher_name);
+ tls_context->kex_name = vstring_export(kex_name);
+ tls_context->kex_curve = vstring_export(kex_curve);
+ tls_context->clnt_sig_name = vstring_export(clnt_sig_name);
+ tls_context->clnt_sig_curve = vstring_export(clnt_sig_curve);
+ tls_context->clnt_sig_dgst = vstring_export(clnt_sig_dgst);
+ tls_context->srvr_sig_name = vstring_export(srvr_sig_name);
+ tls_context->srvr_sig_curve = vstring_export(srvr_sig_curve);
+ tls_context->srvr_sig_dgst = vstring_export(srvr_sig_dgst);
+ tls_context->namaddr = vstring_export(namaddr);
+ ret = (ret == 22 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_context_free(tls_context);
+ tls_context = 0;
+ }
+ *(TLS_SESS_STATE **) ptr = tls_context;
+ if (msg_verbose)
+ msg_info("tls_proxy_context_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_context_free - destroy object from tls_proxy_context_receive() */
+
+void tls_proxy_context_free(TLS_SESS_STATE *tls_context)
+{
+ if (tls_context->peer_CN)
+ myfree(tls_context->peer_CN);
+ if (tls_context->issuer_CN)
+ myfree(tls_context->issuer_CN);
+ if (tls_context->peer_cert_fprint)
+ myfree(tls_context->peer_cert_fprint);
+ if (tls_context->peer_pkey_fprint)
+ myfree(tls_context->peer_pkey_fprint);
+ if (tls_context->protocol)
+ myfree((void *) tls_context->protocol);
+ if (tls_context->cipher_name)
+ myfree((void *) tls_context->cipher_name);
+ if (tls_context->kex_name)
+ myfree((void *) tls_context->kex_name);
+ if (tls_context->kex_curve)
+ myfree((void *) tls_context->kex_curve);
+ if (tls_context->clnt_sig_name)
+ myfree((void *) tls_context->clnt_sig_name);
+ if (tls_context->clnt_sig_curve)
+ myfree((void *) tls_context->clnt_sig_curve);
+ if (tls_context->clnt_sig_dgst)
+ myfree((void *) tls_context->clnt_sig_dgst);
+ if (tls_context->srvr_sig_name)
+ myfree((void *) tls_context->srvr_sig_name);
+ if (tls_context->srvr_sig_curve)
+ myfree((void *) tls_context->srvr_sig_curve);
+ if (tls_context->srvr_sig_dgst)
+ myfree((void *) tls_context->srvr_sig_dgst);
+ if (tls_context->namaddr)
+ myfree((void *) tls_context->namaddr);
+ myfree((void *) tls_context);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_server_print.c b/src/tls/tls_proxy_server_print.c
new file mode 100644
index 0000000..8d51422
--- /dev/null
+++ b/src/tls/tls_proxy_server_print.c
@@ -0,0 +1,143 @@
+/*++
+/* NAME
+/* tls_proxy_server_print 3
+/* SUMMARY
+/* write TLS_SERVER_XXX structures to stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_server_init_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* int tls_proxy_server_start_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* tls_proxy_server_init_print() writes a TLS_SERVER_INIT_PROPS
+/* structure to the named stream using the specified attribute print
+/* routine. tls_proxy_server_init_print() is meant to be passed as
+/* a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(tls_proxy_server_init_print, (const void *) init_props), ...
+/*
+/* tls_proxy_server_start_print() writes a TLS_SERVER_START_PROPS
+/* structure to the named stream using the specified attribute print
+/* routine. tls_proxy_server_start_print() is meant to be passed as
+/* a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(tls_proxy_server_start_print, (const void *) start_props), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_server_init_print - send TLS_SERVER_INIT_PROPS over stream */
+
+int tls_proxy_server_init_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_SERVER_INIT_PROPS *props = (const TLS_SERVER_INIT_PROPS *) ptr;
+ int ret;
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_LOG_PARAM,
+ STRING_OR_EMPTY(props->log_param)),
+ SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL,
+ STRING_OR_EMPTY(props->log_level)),
+ SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth),
+ SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE,
+ STRING_OR_EMPTY(props->cache_type)),
+ SEND_ATTR_INT(TLS_ATTR_SET_SESSID, props->set_sessid),
+ SEND_ATTR_STR(TLS_ATTR_CHAIN_FILES,
+ STRING_OR_EMPTY(props->chain_files)),
+ SEND_ATTR_STR(TLS_ATTR_CERT_FILE,
+ STRING_OR_EMPTY(props->cert_file)),
+ SEND_ATTR_STR(TLS_ATTR_KEY_FILE,
+ STRING_OR_EMPTY(props->key_file)),
+ SEND_ATTR_STR(TLS_ATTR_DCERT_FILE,
+ STRING_OR_EMPTY(props->dcert_file)),
+ SEND_ATTR_STR(TLS_ATTR_DKEY_FILE,
+ STRING_OR_EMPTY(props->dkey_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE,
+ STRING_OR_EMPTY(props->eccert_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE,
+ STRING_OR_EMPTY(props->eckey_file)),
+ SEND_ATTR_STR(TLS_ATTR_CAFILE,
+ STRING_OR_EMPTY(props->CAfile)),
+ SEND_ATTR_STR(TLS_ATTR_CAPATH,
+ STRING_OR_EMPTY(props->CApath)),
+ SEND_ATTR_STR(TLS_ATTR_PROTOCOLS,
+ STRING_OR_EMPTY(props->protocols)),
+ SEND_ATTR_STR(TLS_ATTR_EECDH_GRADE,
+ STRING_OR_EMPTY(props->eecdh_grade)),
+ SEND_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE,
+ STRING_OR_EMPTY(props->dh1024_param_file)),
+ SEND_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE,
+ STRING_OR_EMPTY(props->dh512_param_file)),
+ SEND_ATTR_INT(TLS_ATTR_ASK_CCERT, props->ask_ccert),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ return (ret);
+}
+
+/* tls_proxy_server_start_print - send TLS_SERVER_START_PROPS over stream */
+
+int tls_proxy_server_start_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ const TLS_SERVER_START_PROPS *props = (const TLS_SERVER_START_PROPS *) ptr;
+ int ret;
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout),
+ SEND_ATTR_INT(TLS_ATTR_REQUIRECERT, props->requirecert),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID,
+ STRING_OR_EMPTY(props->serverid)),
+ SEND_ATTR_STR(TLS_ATTR_NAMADDR,
+ STRING_OR_EMPTY(props->namaddr)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE,
+ STRING_OR_EMPTY(props->cipher_grade)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ STRING_OR_EMPTY(props->cipher_exclusions)),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_server_scan.c b/src/tls/tls_proxy_server_scan.c
new file mode 100644
index 0000000..92da66c
--- /dev/null
+++ b/src/tls/tls_proxy_server_scan.c
@@ -0,0 +1,245 @@
+/*++
+/* NAME
+/* tls_proxy_server_scan 3
+/* SUMMARY
+/* read TLS_SERVER_XXX structures from stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_server_init_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* tls_proxy_server_init_free(init_props)
+/* TLS_SERVER_INIT_PROPS *init_props;
+/*
+/* int tls_proxy_server_start_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_server_start_free(start_props)
+/* TLS_SERVER_START_PROPS *start_props;
+/* DESCRIPTION
+/* tls_proxy_server_init_scan() reads a TLS_SERVER_INIT_PROPS
+/* structure from the named stream using the specified attribute
+/* scan routine. tls_proxy_server_init_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_server_init_free() destroys a TLS_SERVER_INIT_PROPS
+/* structure that was created by tls_proxy_server_init_scan().
+/*
+/* TLS_SERVER_INIT_PROPS *init_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_server_init_scan, (void *) &init_props)
+/* ...
+/* if (init_props)
+/* tls_proxy_client_init_free(init_props);
+/*
+/* tls_proxy_server_start_scan() reads a TLS_SERVER_START_PROPS
+/* structure from the named stream using the specified attribute
+/* scan routine. tls_proxy_server_start_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_server_start_free() destroys a TLS_SERVER_START_PROPS
+/* structure that was created by tls_proxy_server_start_scan().
+/*
+/* TLS_SERVER_START_PROPS *start_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_server_start_scan, (void *) &start_props)
+/* ...
+/* if (start_props)
+/* tls_proxy_server_start_free(start_props);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_server_init_scan - receive TLS_SERVER_INIT_PROPS from stream */
+
+int tls_proxy_server_init_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SERVER_INIT_PROPS *props
+ = (TLS_SERVER_INIT_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *log_param = vstring_alloc(25);
+ VSTRING *log_level = vstring_alloc(25);
+ VSTRING *cache_type = vstring_alloc(25);
+ VSTRING *chain_files = vstring_alloc(25);
+ VSTRING *cert_file = vstring_alloc(25);
+ VSTRING *key_file = vstring_alloc(25);
+ VSTRING *dcert_file = vstring_alloc(25);
+ VSTRING *dkey_file = vstring_alloc(25);
+ VSTRING *eccert_file = vstring_alloc(25);
+ VSTRING *eckey_file = vstring_alloc(25);
+ VSTRING *CAfile = vstring_alloc(25);
+ VSTRING *CApath = vstring_alloc(25);
+ VSTRING *protocols = vstring_alloc(25);
+ VSTRING *eecdh_grade = vstring_alloc(25);
+ VSTRING *dh1024_param_file = vstring_alloc(25);
+ VSTRING *dh512_param_file = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param),
+ RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level),
+ RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth),
+ RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_INT(TLS_ATTR_SET_SESSID, &props->set_sessid),
+ RECV_ATTR_STR(TLS_ATTR_CHAIN_FILES, chain_files),
+ RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file),
+ RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file),
+ RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file),
+ RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file),
+ RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file),
+ RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file),
+ RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile),
+ RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath),
+ RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols),
+ RECV_ATTR_STR(TLS_ATTR_EECDH_GRADE, eecdh_grade),
+ RECV_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE, dh1024_param_file),
+ RECV_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE, dh512_param_file),
+ RECV_ATTR_INT(TLS_ATTR_ASK_CCERT, &props->ask_ccert),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ props->log_param = vstring_export(log_param);
+ props->log_level = vstring_export(log_level);
+ props->cache_type = vstring_export(cache_type);
+ props->chain_files = vstring_export(chain_files);
+ props->cert_file = vstring_export(cert_file);
+ props->key_file = vstring_export(key_file);
+ props->dcert_file = vstring_export(dcert_file);
+ props->dkey_file = vstring_export(dkey_file);
+ props->eccert_file = vstring_export(eccert_file);
+ props->eckey_file = vstring_export(eckey_file);
+ props->CAfile = vstring_export(CAfile);
+ props->CApath = vstring_export(CApath);
+ props->protocols = vstring_export(protocols);
+ props->eecdh_grade = vstring_export(eecdh_grade);
+ props->dh1024_param_file = vstring_export(dh1024_param_file);
+ props->dh512_param_file = vstring_export(dh512_param_file);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 20 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_server_init_free(props);
+ props = 0;
+ }
+ *(TLS_SERVER_INIT_PROPS **) ptr = props;
+ return (ret);
+}
+
+/* tls_proxy_server_init_free - destroy TLS_SERVER_INIT_PROPS structure */
+
+void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *props)
+{
+ myfree((void *) props->log_param);
+ myfree((void *) props->log_level);
+ myfree((void *) props->cache_type);
+ myfree((void *) props->chain_files);
+ myfree((void *) props->cert_file);
+ myfree((void *) props->key_file);
+ myfree((void *) props->dcert_file);
+ myfree((void *) props->dkey_file);
+ myfree((void *) props->eccert_file);
+ myfree((void *) props->eckey_file);
+ myfree((void *) props->CAfile);
+ myfree((void *) props->CApath);
+ myfree((void *) props->protocols);
+ myfree((void *) props->eecdh_grade);
+ myfree((void *) props->dh1024_param_file);
+ myfree((void *) props->dh512_param_file);
+ myfree((void *) props->mdalg);
+ myfree((void *) props);
+}
+
+/* tls_proxy_server_start_scan - receive TLS_SERVER_START_PROPS from stream */
+
+int tls_proxy_server_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SERVER_START_PROPS *props
+ = (TLS_SERVER_START_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *serverid = vstring_alloc(25);
+ VSTRING *namaddr = vstring_alloc(25);
+ VSTRING *cipher_grade = vstring_alloc(25);
+ VSTRING *cipher_exclusions = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ props->ctx = 0;
+ props->stream = 0;
+ /* XXX Caller sets fd. */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout),
+ RECV_ATTR_INT(TLS_ATTR_REQUIRECERT, &props->requirecert),
+ RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid),
+ RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ cipher_exclusions),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ ATTR_TYPE_END);
+ props->serverid = vstring_export(serverid);
+ props->namaddr = vstring_export(namaddr);
+ props->cipher_grade = vstring_export(cipher_grade);
+ props->cipher_exclusions = vstring_export(cipher_exclusions);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 7 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_server_start_free(props);
+ props = 0;
+ }
+ *(TLS_SERVER_START_PROPS **) ptr = props;
+ return (ret);
+}
+
+/* tls_proxy_server_start_free - destroy TLS_SERVER_START_PROPS structure */
+
+void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *props)
+{
+ /* XXX Caller closes fd. */
+ myfree((void *) props->serverid);
+ myfree((void *) props->namaddr);
+ myfree((void *) props->cipher_grade);
+ myfree((void *) props->cipher_exclusions);
+ myfree((void *) props->mdalg);
+ myfree((void *) props);
+}
+
+#endif
diff --git a/src/tls/tls_rsa.c b/src/tls/tls_rsa.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/tls/tls_rsa.c
diff --git a/src/tls/tls_scache.c b/src/tls/tls_scache.c
new file mode 100644
index 0000000..cf722ee
--- /dev/null
+++ b/src/tls/tls_scache.c
@@ -0,0 +1,591 @@
+/*++
+/* NAME
+/* tls_scache 3
+/* SUMMARY
+/* TLS session cache manager
+/* SYNOPSIS
+/* #include <tls_scache.h>
+/*
+/* TLS_SCACHE *tls_scache_open(dbname, cache_label, verbose, timeout)
+/* const char *dbname
+/* const char *cache_label;
+/* int verbose;
+/* int timeout;
+/*
+/* void tls_scache_close(cache)
+/* TLS_SCACHE *cache;
+/*
+/* int tls_scache_lookup(cache, cache_id, out_session)
+/* TLS_SCACHE *cache;
+/* const char *cache_id;
+/* VSTRING *out_session;
+/*
+/* int tls_scache_update(cache, cache_id, session, session_len)
+/* TLS_SCACHE *cache;
+/* const char *cache_id;
+/* const char *session;
+/* ssize_t session_len;
+/*
+/* int tls_scache_sequence(cache, first_next, out_cache_id,
+/* VSTRING *out_session)
+/* TLS_SCACHE *cache;
+/* int first_next;
+/* char **out_cache_id;
+/* VSTRING *out_session;
+/*
+/* int tls_scache_delete(cache, cache_id)
+/* TLS_SCACHE *cache;
+/* const char *cache_id;
+/*
+/* TLS_TICKET_KEY *tls_scache_key(keyname, now, timeout)
+/* unsigned char *keyname;
+/* time_t now;
+/* int timeout;
+/*
+/* TLS_TICKET_KEY *tls_scache_key_rotate(newkey)
+/* TLS_TICKET_KEY *newkey;
+/* DESCRIPTION
+/* This module maintains Postfix TLS session cache files.
+/* each session is stored under a lookup key (hostname or
+/* session ID).
+/*
+/* tls_scache_open() opens the specified TLS session cache
+/* and returns a handle that must be used for subsequent
+/* access.
+/*
+/* tls_scache_close() closes the specified TLS session cache
+/* and releases memory that was allocated by tls_scache_open().
+/*
+/* tls_scache_lookup() looks up the specified session in the
+/* specified cache, and applies session timeout restrictions.
+/* Entries that are too old are silently deleted.
+/*
+/* tls_scache_update() updates the specified TLS session cache
+/* with the specified session information.
+/*
+/* tls_scache_sequence() iterates over the specified TLS session
+/* cache and either returns the first or next entry that has not
+/* timed out, or returns no data. Entries that are too old are
+/* silently deleted. Specify TLS_SCACHE_SEQUENCE_NOTHING as the
+/* third and last argument to disable saving of cache entry
+/* content or cache entry ID information. This is useful when
+/* purging expired entries. A result value of zero means that
+/* the end of the cache was reached.
+/*
+/* tls_scache_delete() removes the specified cache entry from
+/* the specified TLS session cache.
+/*
+/* tls_scache_key() locates a TLS session ticket key in a 2-element
+/* in-memory cache. A null result is returned if no unexpired matching
+/* key is found.
+/*
+/* tls_scache_key_rotate() saves a TLS session tickets key in the
+/* in-memory cache.
+/*
+/* Arguments:
+/* .IP dbname
+/* The base name of the session cache file.
+/* .IP cache_label
+/* A string that is used in logging and error messages.
+/* .IP verbose
+/* Do verbose logging of cache operations? (zero == no)
+/* .IP timeout
+/* The time after which a session cache entry is considered too old.
+/* .IP first_next
+/* One of DICT_SEQ_FUN_FIRST (first cache element) or DICT_SEQ_FUN_NEXT
+/* (next cache element).
+/* .IP cache_id
+/* Session cache lookup key.
+/* .IP session
+/* Storage for session information.
+/* .IP session_len
+/* The size of the session information in bytes.
+/* .IP out_cache_id
+/* .IP out_session
+/* Storage for saving the cache_id or session information of the
+/* current cache entry.
+/*
+/* Specify TLS_SCACHE_DONT_NEED_CACHE_ID to avoid saving
+/* the session cache ID of the cache entry.
+/*
+/* Specify TLS_SCACHE_DONT_NEED_SESSION to avoid
+/* saving the session information in the cache entry.
+/* .IP keyname
+/* Is null when requesting the current encryption keys. Otherwise,
+/* keyname is a pointer to an array of TLS_TICKET_NAMELEN unsigned
+/* chars (not NUL terminated) that is an identifier for a key
+/* previously used to encrypt a session ticket.
+/* .IP now
+/* Current epoch time passed by caller.
+/* .IP timeout
+/* TLS session ticket encryption lifetime.
+/* .IP newkey
+/* TLS session ticket key obtained from tlsmgr(8) to be added to
+ * internal cache.
+/* DIAGNOSTICS
+/* These routines terminate with a fatal run-time error
+/* for unrecoverable database errors. This allows the
+/* program to restart and reset the database to an
+/* empty initial state.
+/*
+/* tls_scache_open() never returns on failure. All other
+/* functions return non-zero on success, zero when the
+/* operation could not be completed.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+#include <string.h>
+#include <stddef.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <dict.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <hex_code.h>
+#include <myflock.h>
+#include <vstring.h>
+#include <timecmp.h>
+
+/* Global library. */
+
+/* TLS library. */
+
+#include <tls_scache.h>
+
+/* Application-specific. */
+
+ /*
+ * Session cache entry format.
+ */
+typedef struct {
+ time_t timestamp; /* time when saved */
+ char session[1]; /* actually a bunch of bytes */
+} TLS_SCACHE_ENTRY;
+
+static TLS_TICKET_KEY *keys[2];
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* tls_scache_encode - encode TLS session cache entry */
+
+static VSTRING *tls_scache_encode(TLS_SCACHE *cp, const char *cache_id,
+ const char *session,
+ ssize_t session_len)
+{
+ TLS_SCACHE_ENTRY *entry;
+ VSTRING *hex_data;
+ ssize_t binary_data_len;
+
+ /*
+ * Assemble the TLS session cache entry.
+ *
+ * We could eliminate some copying by using incremental encoding, but
+ * sessions are so small that it really does not matter.
+ */
+ binary_data_len = session_len + offsetof(TLS_SCACHE_ENTRY, session);
+ entry = (TLS_SCACHE_ENTRY *) mymalloc(binary_data_len);
+ entry->timestamp = time((time_t *) 0);
+ memcpy(entry->session, session, session_len);
+
+ /*
+ * Encode the TLS session cache entry.
+ */
+ hex_data = vstring_alloc(2 * binary_data_len + 1);
+ hex_encode(hex_data, (char *) entry, binary_data_len);
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("write %s TLS cache entry %s: time=%ld [data %ld bytes]",
+ cp->cache_label, cache_id, (long) entry->timestamp,
+ (long) session_len);
+
+ /*
+ * Clean up.
+ */
+ myfree((void *) entry);
+
+ return (hex_data);
+}
+
+/* tls_scache_decode - decode TLS session cache entry */
+
+static int tls_scache_decode(TLS_SCACHE *cp, const char *cache_id,
+ const char *hex_data, ssize_t hex_data_len,
+ VSTRING *out_session)
+{
+ TLS_SCACHE_ENTRY *entry;
+ VSTRING *bin_data;
+
+ /*
+ * Sanity check.
+ */
+ if (hex_data_len < 2 * (offsetof(TLS_SCACHE_ENTRY, session))) {
+ msg_warn("%s TLS cache: truncated entry for %s: %.100s",
+ cp->cache_label, cache_id, hex_data);
+ return (0);
+ }
+
+ /*
+ * Disassemble the TLS session cache entry.
+ *
+ * No early returns or we have a memory leak.
+ */
+#define FREE_AND_RETURN(ptr, x) { vstring_free(ptr); return (x); }
+
+ bin_data = vstring_alloc(hex_data_len / 2 + 1);
+ if (hex_decode_opt(bin_data, hex_data, hex_data_len,
+ HEX_DECODE_FLAG_ALLOW_COLON) == 0) {
+ msg_warn("%s TLS cache: malformed entry for %s: %.100s",
+ cp->cache_label, cache_id, hex_data);
+ FREE_AND_RETURN(bin_data, 0);
+ }
+ entry = (TLS_SCACHE_ENTRY *) STR(bin_data);
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("read %s TLS cache entry %s: time=%ld [data %ld bytes]",
+ cp->cache_label, cache_id, (long) entry->timestamp,
+ (long) (LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session)));
+
+ /*
+ * Other mandatory restrictions.
+ */
+ if (entry->timestamp + cp->timeout < time((time_t *) 0))
+ FREE_AND_RETURN(bin_data, 0);
+
+ /*
+ * Optional output.
+ */
+ if (out_session != 0)
+ vstring_memcpy(out_session, entry->session,
+ LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session));
+
+ /*
+ * Clean up.
+ */
+ FREE_AND_RETURN(bin_data, 1);
+}
+
+/* tls_scache_lookup - load session from cache */
+
+int tls_scache_lookup(TLS_SCACHE *cp, const char *cache_id,
+ VSTRING *session)
+{
+ const char *hex_data;
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("lookup %s session id=%s", cp->cache_label, cache_id);
+
+ /*
+ * Initialize. Don't leak data.
+ */
+ if (session)
+ VSTRING_RESET(session);
+
+ /*
+ * Search the cache database.
+ */
+ if ((hex_data = dict_get(cp->db, cache_id)) == 0)
+ return (0);
+
+ /*
+ * Decode entry and delete if expired or malformed.
+ */
+ if (tls_scache_decode(cp, cache_id, hex_data, strlen(hex_data),
+ session) == 0) {
+ tls_scache_delete(cp, cache_id);
+ return (0);
+ } else {
+ return (1);
+ }
+}
+
+/* tls_scache_update - save session to cache */
+
+int tls_scache_update(TLS_SCACHE *cp, const char *cache_id,
+ const char *buf, ssize_t len)
+{
+ VSTRING *hex_data;
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("put %s session id=%s [data %ld bytes]",
+ cp->cache_label, cache_id, (long) len);
+
+ /*
+ * Encode the cache entry.
+ */
+ hex_data = tls_scache_encode(cp, cache_id, buf, len);
+
+ /*
+ * Store the cache entry.
+ *
+ * XXX Berkeley DB supports huge database keys and values. SDBM seems to
+ * have a finite limit, and DBM simply can't be used at all.
+ */
+ dict_put(cp->db, cache_id, STR(hex_data));
+
+ /*
+ * Clean up.
+ */
+ vstring_free(hex_data);
+
+ return (1);
+}
+
+/* tls_scache_sequence - get first/next TLS session cache entry */
+
+int tls_scache_sequence(TLS_SCACHE *cp, int first_next,
+ char **out_cache_id,
+ VSTRING *out_session)
+{
+ const char *member;
+ const char *value;
+ char *saved_cursor;
+ int found_entry;
+ int keep_entry;
+ char *saved_member;
+
+ /*
+ * XXX Deleting entries while enumerating a map can he tricky. Some map
+ * types have a concept of cursor and support a "delete the current
+ * element" operation. Some map types without cursors don't behave well
+ * when the current first/next entry is deleted (example: with Berkeley
+ * DB < 2, the "next" operation produces garbage). To avoid trouble, we
+ * delete an expired entry after advancing the current first/next
+ * position beyond it, and ignore client requests to delete the current
+ * entry.
+ */
+
+ /*
+ * Find the first or next database entry. Activate the passivated entry
+ * and check the time stamp. Schedule the entry for deletion if it is too
+ * old.
+ *
+ * Save the member (cache id) so that it will not be clobbered by the
+ * tls_scache_lookup() call below.
+ */
+ found_entry = (dict_seq(cp->db, first_next, &member, &value) == 0);
+ if (found_entry) {
+ keep_entry = tls_scache_decode(cp, member, value, strlen(value),
+ out_session);
+ if (keep_entry && out_cache_id)
+ *out_cache_id = mystrdup(member);
+ saved_member = mystrdup(member);
+ }
+
+ /*
+ * Delete behind. This is a no-op if an expired cache entry was updated
+ * in the mean time. Use the saved lookup criteria so that the "delete
+ * behind" operation works as promised.
+ *
+ * The delete-behind strategy assumes that all updates are made by a single
+ * process. Otherwise, delete-behind may remove an entry that was updated
+ * after it was scheduled for deletion.
+ */
+ if (cp->flags & TLS_SCACHE_FLAG_DEL_SAVED_CURSOR) {
+ cp->flags &= ~TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
+ saved_cursor = cp->saved_cursor;
+ cp->saved_cursor = 0;
+ tls_scache_lookup(cp, saved_cursor, (VSTRING *) 0);
+ myfree(saved_cursor);
+ }
+
+ /*
+ * Otherwise, clean up if this is not the first iteration.
+ */
+ else {
+ if (cp->saved_cursor)
+ myfree(cp->saved_cursor);
+ cp->saved_cursor = 0;
+ }
+
+ /*
+ * Protect the current first/next entry against explicit or implied
+ * client delete requests, and schedule a bad or expired entry for
+ * deletion. Save the lookup criteria so that the "delete behind"
+ * operation will work as promised.
+ */
+ if (found_entry) {
+ cp->saved_cursor = saved_member;
+ if (keep_entry == 0)
+ cp->flags |= TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
+ }
+ return (found_entry);
+}
+
+/* tls_scache_delete - delete session from cache */
+
+int tls_scache_delete(TLS_SCACHE *cp, const char *cache_id)
+{
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("delete %s session id=%s", cp->cache_label, cache_id);
+
+ /*
+ * Do it, unless we would delete the current first/next entry. Some map
+ * types don't have cursors, and some of those don't behave when the
+ * "current" entry is deleted.
+ */
+ return ((cp->saved_cursor != 0 && strcmp(cp->saved_cursor, cache_id) == 0)
+ || dict_del(cp->db, cache_id) == 0);
+}
+
+/* tls_scache_open - open TLS session cache file */
+
+TLS_SCACHE *tls_scache_open(const char *dbname, const char *cache_label,
+ int verbose, int timeout)
+{
+ TLS_SCACHE *cp;
+ DICT *dict;
+
+ /*
+ * Logging.
+ */
+ if (verbose)
+ msg_info("open %s TLS cache %s", cache_label, dbname);
+
+ /*
+ * Open the dictionary with O_TRUNC, so that we never have to worry about
+ * opening a damaged file after some process terminated abnormally.
+ */
+#define DICT_FLAGS \
+ (DICT_FLAG_DUP_REPLACE | DICT_FLAG_OPEN_LOCK | DICT_FLAG_SYNC_UPDATE \
+ | DICT_FLAG_UTF8_REQUEST)
+
+ dict = dict_open(dbname, O_RDWR | O_CREAT | O_TRUNC, DICT_FLAGS);
+
+ /*
+ * Sanity checks.
+ */
+ if (dict->update == 0)
+ msg_fatal("dictionary %s does not support update operations", dbname);
+ if (dict->delete == 0)
+ msg_fatal("dictionary %s does not support delete operations", dbname);
+ if (dict->sequence == 0)
+ msg_fatal("dictionary %s does not support sequence operations", dbname);
+
+ /*
+ * Create the TLS_SCACHE object.
+ */
+ cp = (TLS_SCACHE *) mymalloc(sizeof(*cp));
+ cp->flags = 0;
+ cp->db = dict;
+ cp->cache_label = mystrdup(cache_label);
+ cp->verbose = verbose;
+ cp->timeout = timeout;
+ cp->saved_cursor = 0;
+
+ return (cp);
+}
+
+/* tls_scache_close - close TLS session cache file */
+
+void tls_scache_close(TLS_SCACHE *cp)
+{
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("close %s TLS cache %s", cp->cache_label, cp->db->name);
+
+ /*
+ * Destroy the TLS_SCACHE object.
+ */
+ dict_close(cp->db);
+ myfree(cp->cache_label);
+ if (cp->saved_cursor)
+ myfree(cp->saved_cursor);
+ myfree((void *) cp);
+}
+
+/* tls_scache_key - find session ticket key for given key name */
+
+TLS_TICKET_KEY *tls_scache_key(unsigned char *keyname, time_t now, int timeout)
+{
+ int i;
+
+ /*
+ * The keys array contains 2 elements, the current signing key and the
+ * previous key.
+ *
+ * When name == 0 we are issuing a ticket, otherwise decrypting an existing
+ * ticket with the given key name. For new tickets we always use the
+ * current key if unexpired. For existing tickets, we use either the
+ * current or previous key with a validation expiration that is timeout
+ * longer than the signing expiration.
+ */
+ if (keyname) {
+ for (i = 0; i < 2 && keys[i]; ++i) {
+ if (memcmp(keyname, keys[i]->name, TLS_TICKET_NAMELEN) == 0) {
+ if (timecmp(keys[i]->tout + timeout, now) > 0)
+ return (keys[i]);
+ break;
+ }
+ }
+ } else if (keys[0]) {
+ if (timecmp(keys[0]->tout, now) > 0)
+ return (keys[0]);
+ }
+ return (0);
+}
+
+/* tls_scache_key_rotate - rotate session ticket keys */
+
+TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *newkey)
+{
+
+ /*
+ * Allocate or re-use storage of retired key, then overwrite it, since
+ * caller's key data is ephemeral.
+ */
+ if (keys[1] == 0)
+ keys[1] = (TLS_TICKET_KEY *) mymalloc(sizeof(*newkey));
+ *keys[1] = *newkey;
+ newkey = keys[1];
+
+ /*
+ * Rotate if required, ensuring that the keys are sorted by expiration
+ * time with keys[0] expiring last.
+ */
+ if (keys[0] == 0 || keys[0]->tout < keys[1]->tout) {
+ keys[1] = keys[0];
+ keys[0] = newkey;
+ }
+ return (newkey);
+}
+
+#endif
diff --git a/src/tls/tls_scache.h b/src/tls/tls_scache.h
new file mode 100644
index 0000000..06c727a
--- /dev/null
+++ b/src/tls/tls_scache.h
@@ -0,0 +1,73 @@
+#ifndef _TLS_SCACHE_H_INCLUDED_
+#define _TLS_SCACHE_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_scache 3h
+/* SUMMARY
+/* TLS session cache manager
+/* SYNOPSIS
+/* #include <tls_scache.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ int flags; /* see below */
+ DICT *db; /* database handle */
+ char *cache_label; /* "smtpd", "smtp" or "lmtp" */
+ int verbose; /* enable verbose logging */
+ int timeout; /* smtp(d)_tls_session_cache_timeout */
+ char *saved_cursor; /* cursor cache ID */
+} TLS_SCACHE;
+
+#define TLS_TICKET_NAMELEN 16 /* RFC 5077 ticket key name length */
+#define TLS_TICKET_IVLEN 16 /* RFC 5077 ticket IV length */
+#define TLS_TICKET_KEYLEN 32 /* AES-256-CBC key size */
+#define TLS_TICKET_MACLEN 32 /* RFC 5077 HMAC key size */
+#define TLS_SESSION_LIFEMIN 120 /* May you live to 120! */
+
+typedef struct TLS_TICKET_KEY {
+ unsigned char name[TLS_TICKET_NAMELEN];
+ unsigned char bits[TLS_TICKET_KEYLEN];
+ unsigned char hmac[TLS_TICKET_MACLEN];
+ time_t tout;
+} TLS_TICKET_KEY;
+
+#define TLS_SCACHE_FLAG_DEL_SAVED_CURSOR (1<<0)
+
+extern TLS_SCACHE *tls_scache_open(const char *, const char *, int, int);
+extern void tls_scache_close(TLS_SCACHE *);
+extern int tls_scache_lookup(TLS_SCACHE *, const char *, VSTRING *);
+extern int tls_scache_update(TLS_SCACHE *, const char *, const char *, ssize_t);
+extern int tls_scache_delete(TLS_SCACHE *, const char *);
+extern int tls_scache_sequence(TLS_SCACHE *, int, char **, VSTRING *);
+extern TLS_TICKET_KEY *tls_scache_key(unsigned char *, time_t, int);
+extern TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *);
+
+#define TLS_SCACHE_DONT_NEED_CACHE_ID ((char **) 0)
+#define TLS_SCACHE_DONT_NEED_SESSION ((VSTRING *) 0)
+
+#define TLS_SCACHE_SEQUENCE_NOTHING \
+ TLS_SCACHE_DONT_NEED_CACHE_ID, TLS_SCACHE_DONT_NEED_SESSION
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/tls/tls_seed.c b/src/tls/tls_seed.c
new file mode 100644
index 0000000..edb7cd9
--- /dev/null
+++ b/src/tls/tls_seed.c
@@ -0,0 +1,88 @@
+/*++
+/* NAME
+/* tls_seed 3
+/* SUMMARY
+/* TLS PRNG seeding routines
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_ext_seed(nbytes)
+/* int nbytes;
+/*
+/* void tls_int_seed()
+/* DESCRIPTION
+/* tls_ext_seed() requests the specified number of bytes
+/* from the tlsmgr(8) PRNG pool and updates the local PRNG.
+/* The result is zero in case of success, -1 otherwise.
+/*
+/* tls_int_seed() mixes the process ID and time of day into
+/* the PRNG pool. This adds a few bits of entropy with each
+/* call, provided that the calls aren't made frequently.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/time.h> /* gettimeofday() */
+#include <unistd.h> /* getpid() */
+
+#ifdef USE_TLS
+
+/* OpenSSL library. */
+
+#include <openssl/rand.h> /* RAND_seed() */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* TLS library. */
+
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+/* tls_int_seed - add entropy to the pool by adding the time and PID */
+
+void tls_int_seed(void)
+{
+ static struct {
+ pid_t pid;
+ struct timeval tv;
+ } randseed;
+
+ if (randseed.pid == 0)
+ randseed.pid = getpid();
+ GETTIMEOFDAY(&randseed.tv);
+ RAND_seed(&randseed, sizeof(randseed));
+}
+
+/* tls_ext_seed - request entropy from tlsmgr(8) server */
+
+int tls_ext_seed(int nbytes)
+{
+ VSTRING *buf;
+ int status;
+
+ buf = vstring_alloc(nbytes);
+ status = tls_mgr_seed(buf, nbytes);
+ RAND_seed(vstring_str(buf), VSTRING_LEN(buf));
+ vstring_free(buf);
+ return (status == TLS_MGR_STAT_OK ? 0 : -1);
+}
+
+#endif
diff --git a/src/tls/tls_server.c b/src/tls/tls_server.c
new file mode 100644
index 0000000..fb76f06
--- /dev/null
+++ b/src/tls/tls_server.c
@@ -0,0 +1,1051 @@
+/*++
+/* NAME
+/* tls_server 3
+/* SUMMARY
+/* server-side TLS engine
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* TLS_APPL_STATE *tls_server_init(props)
+/* const TLS_SERVER_INIT_PROPS *props;
+/*
+/* TLS_SESS_STATE *tls_server_start(props)
+/* const TLS_SERVER_START_PROPS *props;
+/*
+/* TLS_SESS_STATE *tls_server_post_accept(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_server_stop(app_ctx, stream, failure, TLScontext)
+/* TLS_APPL_STATE *app_ctx;
+/* VSTREAM *stream;
+/* int failure;
+/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* This module is the interface between Postfix TLS servers,
+/* the OpenSSL library, and the TLS entropy and cache manager.
+/*
+/* See "EVENT_DRIVEN APPLICATIONS" below for using this code
+/* in event-driven programs.
+/*
+/* tls_server_init() is called once when the SMTP server
+/* initializes.
+/* Certificate details are also decided during this phase,
+/* so that peer-specific behavior is not possible.
+/*
+/* tls_server_start() activates the TLS feature for the VSTREAM
+/* passed as argument. We assume that network buffers are flushed
+/* and the TLS handshake can begin immediately.
+/*
+/* tls_server_stop() sends the "close notify" alert via
+/* SSL_shutdown() to the peer and resets all connection specific
+/* TLS data. As RFC2487 does not specify a separate shutdown, it
+/* is assumed that the underlying TCP connection is shut down
+/* immediately afterwards. Any further writes to the channel will
+/* be discarded, and any further reads will report end-of-file.
+/* If the failure flag is set, no SSL_shutdown() handshake is performed.
+/*
+/* Once the TLS connection is initiated, information about the TLS
+/* state is available via the TLScontext structure:
+/* .IP TLScontext->protocol
+/* the protocol name (SSLv2, SSLv3, TLSv1),
+/* .IP TLScontext->cipher_name
+/* the cipher name (e.g. RC4/MD5),
+/* .IP TLScontext->cipher_usebits
+/* the number of bits actually used (e.g. 40),
+/* .IP TLScontext->cipher_algbits
+/* the number of bits the algorithm is based on (e.g. 128).
+/* .PP
+/* The last two values may differ from each other when export-strength
+/* encryption is used.
+/*
+/* If the peer offered a certificate, part of the certificate data are
+/* available as:
+/* .IP TLScontext->peer_status
+/* A bitmask field that records the status of the peer certificate
+/* verification. One or more of TLS_CERT_FLAG_PRESENT and
+/* TLS_CERT_FLAG_TRUSTED.
+/* .IP TLScontext->peer_CN
+/* Extracted CommonName of the peer, or zero-length string
+/* when information could not be extracted.
+/* .IP TLScontext->issuer_CN
+/* Extracted CommonName of the issuer, or zero-length string
+/* when information could not be extracted.
+/* .IP TLScontext->peer_cert_fprint
+/* Fingerprint of the certificate, or zero-length string when no peer
+/* certificate is available.
+/* .PP
+/* If no peer certificate is presented the peer_status is set to 0.
+/* EVENT_DRIVEN APPLICATIONS
+/* .ad
+/* .fi
+/* Event-driven programs manage multiple I/O channels. Such
+/* programs cannot use the synchronous VSTREAM-over-TLS
+/* implementation that the current TLS library provides,
+/* including tls_server_stop() and the underlying tls_stream(3)
+/* and tls_bio_ops(3) routines.
+/*
+/* With the current TLS library implementation, this means
+/* that the application is responsible for calling and retrying
+/* SSL_accept(), SSL_read(), SSL_write() and SSL_shutdown().
+/*
+/* To maintain control over TLS I/O, an event-driven server
+/* invokes tls_server_start() with a null VSTREAM argument and
+/* with an fd argument that specifies the I/O file descriptor.
+/* Then, tls_server_start() performs all the necessary
+/* preparations before the TLS handshake and returns a partially
+/* populated TLS context. The event-driven application is then
+/* responsible for invoking SSL_accept(), and if successful,
+/* for invoking tls_server_post_accept() to finish the work
+/* that was started by tls_server_start(). In case of unrecoverable
+/* failure, tls_server_post_accept() destroys the TLS context
+/* and returns a null pointer value.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <dict.h>
+#include <stringops.h>
+#include <msg.h>
+#include <hex_code.h>
+#include <iostuff.h> /* non-blocking */
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h>
+#if OPENSSL_VERSION_PREREQ(3,0)
+#include <openssl/core_names.h> /* EVP_MAC parameters */
+#endif
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* Application-specific. */
+
+ /*
+ * The session_id_context identifies the service that created a session.
+ * This information is used to distinguish between multiple TLS-based
+ * servers running on the same server. We use the name of the mail system.
+ */
+static const char server_session_id_context[] = "Postfix/TLS";
+
+#ifndef OPENSSL_NO_TLSEXT
+ /*
+ * We retain the cipher handle for the lifetime of the process.
+ */
+static const EVP_CIPHER *tkt_cipher;
+#endif
+
+#define GET_SID(s, v, lptr) ((v) = SSL_SESSION_get_id((s), (lptr)))
+
+typedef const unsigned char *session_id_t;
+
+/* get_server_session_cb - callback to retrieve session from server cache */
+
+static SSL_SESSION *get_server_session_cb(SSL *ssl, session_id_t session_id,
+ int session_id_length,
+ int *unused_copy)
+{
+ const char *myname = "get_server_session_cb";
+ TLS_SESS_STATE *TLScontext;
+ VSTRING *cache_id;
+ VSTRING *session_data = vstring_alloc(2048);
+ SSL_SESSION *session = 0;
+
+ if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
+ msg_panic("%s: null TLScontext in session lookup callback", myname);
+
+#define GEN_CACHE_ID(buf, id, len, service) \
+ do { \
+ buf = vstring_alloc(2 * (len + strlen(service))); \
+ hex_encode(buf, (char *) (id), (len)); \
+ vstring_sprintf_append(buf, "&s=%s", (service)); \
+ vstring_sprintf_append(buf, "&l=%ld", (long) OpenSSL_version_num()); \
+ } while (0)
+
+
+ GEN_CACHE_ID(cache_id, session_id, session_id_length, TLScontext->serverid);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: looking up session %s in %s cache", TLScontext->namaddr,
+ STR(cache_id), TLScontext->cache_type);
+
+ /*
+ * Load the session from cache and decode it.
+ */
+ if (tls_mgr_lookup(TLScontext->cache_type, STR(cache_id),
+ session_data) == TLS_MGR_STAT_OK) {
+ session = tls_session_activate(STR(session_data), LEN(session_data));
+ if (session && (TLScontext->log_mask & TLS_LOG_CACHE))
+ msg_info("%s: reloaded session %s from %s cache",
+ TLScontext->namaddr, STR(cache_id),
+ TLScontext->cache_type);
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(cache_id);
+ vstring_free(session_data);
+
+ return (session);
+}
+
+/* uncache_session - remove session from internal & external cache */
+
+static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
+{
+ VSTRING *cache_id;
+ SSL_SESSION *session = SSL_get_session(TLScontext->con);
+ const unsigned char *sid;
+ unsigned int sid_length;
+
+ SSL_CTX_remove_session(ctx, session);
+
+ if (TLScontext->cache_type == 0)
+ return;
+
+ GET_SID(session, sid, &sid_length);
+ GEN_CACHE_ID(cache_id, sid, sid_length, TLScontext->serverid);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: remove session %s from %s cache", TLScontext->namaddr,
+ STR(cache_id), TLScontext->cache_type);
+
+ tls_mgr_delete(TLScontext->cache_type, STR(cache_id));
+ vstring_free(cache_id);
+}
+
+/* new_server_session_cb - callback to save session to server cache */
+
+static int new_server_session_cb(SSL *ssl, SSL_SESSION *session)
+{
+ const char *myname = "new_server_session_cb";
+ VSTRING *cache_id;
+ TLS_SESS_STATE *TLScontext;
+ VSTRING *session_data;
+ const unsigned char *sid;
+ unsigned int sid_length;
+
+ if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
+ msg_panic("%s: null TLScontext in new session callback", myname);
+
+ GET_SID(session, sid, &sid_length);
+ GEN_CACHE_ID(cache_id, sid, sid_length, TLScontext->serverid);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: save session %s to %s cache", TLScontext->namaddr,
+ STR(cache_id), TLScontext->cache_type);
+
+ /*
+ * Passivate and save the session state.
+ */
+ session_data = tls_session_passivate(session);
+ if (session_data)
+ tls_mgr_update(TLScontext->cache_type, STR(cache_id),
+ STR(session_data), LEN(session_data));
+
+ /*
+ * Clean up.
+ */
+ if (session_data)
+ vstring_free(session_data);
+ vstring_free(cache_id);
+ SSL_SESSION_free(session); /* 200502 */
+
+ return (1);
+}
+
+#define NOENGINE ((ENGINE *) 0)
+#define TLS_TKT_NOKEYS -1 /* No keys for encryption */
+#define TLS_TKT_STALE 0 /* No matching keys for decryption */
+#define TLS_TKT_ACCEPT 1 /* Ticket decryptable and re-usable */
+#define TLS_TKT_REISSUE 2 /* Ticket decryptable, not re-usable */
+
+#if !defined(OPENSSL_NO_TLSEXT)
+
+#if OPENSSL_VERSION_PREREQ(3,0)
+
+/* ticket_cb - configure tls session ticket encrypt/decrypt context */
+
+static int ticket_cb(SSL *con, unsigned char name[], unsigned char iv[],
+ EVP_CIPHER_CTX *ctx, EVP_MAC_CTX *hctx, int create)
+{
+ OSSL_PARAM params[3];
+ TLS_TICKET_KEY *key;
+ TLS_SESS_STATE *TLScontext = SSL_get_ex_data(con, TLScontext_index);
+ int timeout = ((int) SSL_CTX_get_timeout(SSL_get_SSL_CTX(con))) / 2;
+
+ if ((key = tls_mgr_key(create ? 0 : name, timeout)) == 0
+ || (create && RAND_bytes(iv, TLS_TICKET_IVLEN) <= 0))
+ return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
+
+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
+ LN_sha256, 0);
+ params[1] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY,
+ (char *) key->hmac,
+ TLS_TICKET_MACLEN);
+ params[2] = OSSL_PARAM_construct_end();
+ if (!EVP_MAC_CTX_set_params(hctx, params))
+ return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
+
+ if (create) {
+ EVP_EncryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ memcpy((void *) name, (void *) key->name, TLS_TICKET_NAMELEN);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Issuing session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ } else {
+ EVP_DecryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Decrypting session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ }
+ TLScontext->ticketed = 1;
+ return (TLS_TKT_ACCEPT);
+}
+
+#else /* OPENSSL_VERSION_PREREQ(3,0) */
+
+/* ticket_cb - configure tls session ticket encrypt/decrypt context */
+
+static int ticket_cb(SSL *con, unsigned char name[], unsigned char iv[],
+ EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int create)
+{
+ static const EVP_MD *sha256;
+ TLS_TICKET_KEY *key;
+ TLS_SESS_STATE *TLScontext = SSL_get_ex_data(con, TLScontext_index);
+ int timeout = ((int) SSL_CTX_get_timeout(SSL_get_SSL_CTX(con))) / 2;
+
+ if ((!sha256 && (sha256 = EVP_sha256()) == 0)
+ || (key = tls_mgr_key(create ? 0 : name, timeout)) == 0
+ || (create && RAND_bytes(iv, TLS_TICKET_IVLEN) <= 0))
+ return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
+
+ HMAC_Init_ex(hctx, key->hmac, TLS_TICKET_MACLEN, sha256, NOENGINE);
+
+ if (create) {
+ EVP_EncryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ memcpy((void *) name, (void *) key->name, TLS_TICKET_NAMELEN);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Issuing session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ } else {
+ EVP_DecryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Decrypting session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ }
+ TLScontext->ticketed = 1;
+ return (TLS_TKT_ACCEPT);
+}
+
+#endif /* OPENSSL_VERSION_PREREQ(3,0) */
+
+#endif /* defined(SSL_OP_NO_TICKET) &&
+ * !defined(OPENSSL_NO_TLSEXT) */
+
+/* tls_server_init - initialize the server-side TLS engine */
+
+TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
+{
+ SSL_CTX *server_ctx;
+ SSL_CTX *sni_ctx;
+ X509_STORE *cert_store;
+ long off = 0;
+ int verify_flags = SSL_VERIFY_NONE;
+ int cachable;
+ int scache_timeout;
+ int ticketable = 0;
+ int protomask;
+ int min_proto;
+ int max_proto;
+ TLS_APPL_STATE *app_ctx;
+ int log_mask;
+
+ /*
+ * Convert user loglevel to internal logmask.
+ */
+ log_mask = tls_log_mask(props->log_param, props->log_level);
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("initializing the server-side TLS engine");
+
+ /*
+ * Load (mostly cipher related) TLS-library internal main.cf parameters.
+ */
+ tls_param_init();
+
+ /*
+ * Detect mismatch between compile-time headers and run-time library.
+ */
+ tls_check_version();
+
+ /*
+ * Initialize the OpenSSL library, possibly loading its configuration
+ * file.
+ */
+ if (tls_library_init() == 0)
+ return (0);
+
+ /*
+ * First validate the protocols. If these are invalid, we can't continue.
+ */
+ protomask = tls_proto_mask_lims(props->protocols, &min_proto, &max_proto);
+ if (protomask == TLS_PROTOCOL_INVALID) {
+ /* tls_protocol_mask() logs no warning. */
+ msg_warn("Invalid TLS protocol list \"%s\": disabling TLS support",
+ props->protocols);
+ return (0);
+ }
+
+ /*
+ * Create an application data index for SSL objects, so that we can
+ * attach TLScontext information; this information is needed inside
+ * tls_verify_certificate_callback().
+ */
+ if (TLScontext_index < 0) {
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) {
+ msg_warn("Cannot allocate SSL application data index: "
+ "disabling TLS support");
+ return (0);
+ }
+ }
+
+ /*
+ * If the administrator specifies an unsupported digest algorithm, fail
+ * now, rather than in the middle of a TLS handshake.
+ */
+ if (!tls_validate_digest(props->mdalg)) {
+ msg_warn("disabling TLS support");
+ return (0);
+ }
+
+ /*
+ * Initialize the PRNG (Pseudo Random Number Generator) with some seed
+ * from external and internal sources. Don't enable TLS without some real
+ * entropy.
+ */
+ if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) {
+ msg_warn("no entropy for TLS key generation: disabling TLS support");
+ return (0);
+ }
+ tls_int_seed();
+
+ /*
+ * The SSL/TLS specifications require the client to send a message in the
+ * oldest specification it understands with the highest level it
+ * understands in the message. Netscape communicator can still
+ * communicate with SSLv2 servers, so it sends out a SSLv2 client hello.
+ * To deal with it, our server must be SSLv2 aware (even if we don't like
+ * SSLv2), so we need to have the SSLv23 server here. If we want to limit
+ * the protocol level, we can add an option to not use SSLv2/v3/TLSv1
+ * later.
+ */
+ ERR_clear_error();
+ server_ctx = SSL_CTX_new(TLS_server_method());
+ if (server_ctx == 0) {
+ msg_warn("cannot allocate server SSL_CTX: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+ sni_ctx = SSL_CTX_new(TLS_server_method());
+ if (sni_ctx == 0) {
+ SSL_CTX_free(server_ctx);
+ msg_warn("cannot allocate server SNI SSL_CTX: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+#ifdef SSL_SECOP_PEER
+ /* Backwards compatible security as a base for opportunistic TLS. */
+ SSL_CTX_set_security_level(server_ctx, 0);
+ SSL_CTX_set_security_level(sni_ctx, 0);
+#endif
+
+ /*
+ * See the verify callback in tls_verify.c
+ */
+ SSL_CTX_set_verify_depth(server_ctx, props->verifydepth + 1);
+ SSL_CTX_set_verify_depth(sni_ctx, props->verifydepth + 1);
+
+ /*
+ * The session cache is implemented by the tlsmgr(8) server.
+ *
+ * XXX 200502 Surprise: when OpenSSL purges an entry from the in-memory
+ * cache, it also attempts to purge the entry from the on-disk cache.
+ * This is undesirable, especially when we set the in-memory cache size
+ * to 1. For this reason we don't allow OpenSSL to purge on-disk cache
+ * entries, and leave it up to the tlsmgr process instead. Found by
+ * Victor Duchovni.
+ */
+ if (tls_mgr_policy(props->cache_type, &cachable,
+ &scache_timeout) != TLS_MGR_STAT_OK)
+ scache_timeout = 0;
+ if (scache_timeout <= 0)
+ cachable = 0;
+
+ /*
+ * Presently we use TLS only with SMTP where truncation attacks are not
+ * possible as a result of application framing. If we ever use TLS in
+ * some other application protocol where truncation could be relevant,
+ * we'd need to disable truncation detection conditionally, or explicitly
+ * clear the option in that code path.
+ */
+ off |= SSL_OP_IGNORE_UNEXPECTED_EOF;
+
+ /*
+ * Protocol work-arounds, OpenSSL version dependent.
+ */
+ off |= tls_bug_bits();
+
+ /*
+ * Add SSL_OP_NO_TICKET when the timeout is zero or library support is
+ * incomplete.
+ */
+#ifndef OPENSSL_NO_TLSEXT
+ ticketable = (*var_tls_tkt_cipher && scache_timeout > 0
+ && !(off & SSL_OP_NO_TICKET));
+ if (ticketable) {
+#if OPENSSL_VERSION_PREREQ(3,0)
+ tkt_cipher = EVP_CIPHER_fetch(NULL, var_tls_tkt_cipher, NULL);
+#else
+ tkt_cipher = EVP_get_cipherbyname(var_tls_tkt_cipher);
+#endif
+ if (tkt_cipher == 0
+ || EVP_CIPHER_mode(tkt_cipher) != EVP_CIPH_CBC_MODE
+ || EVP_CIPHER_iv_length(tkt_cipher) != TLS_TICKET_IVLEN
+ || EVP_CIPHER_key_length(tkt_cipher) < TLS_TICKET_IVLEN
+ || EVP_CIPHER_key_length(tkt_cipher) > TLS_TICKET_KEYLEN) {
+ msg_warn("%s: invalid value: %s; session tickets disabled",
+ VAR_TLS_TKT_CIPHER, var_tls_tkt_cipher);
+ ticketable = 0;
+ }
+ }
+ if (ticketable) {
+#if OPENSSL_VERSION_PREREQ(3,0)
+ SSL_CTX_set_tlsext_ticket_key_evp_cb(server_ctx, ticket_cb);
+#else
+ SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_cb);
+#endif
+
+ /*
+ * OpenSSL 1.1.1 introduces support for TLS 1.3, which can issue more
+ * than one ticket per handshake. While this may be appropriate for
+ * communication between browsers and webservers, it is not terribly
+ * useful for MTAs, many of which other than Postfix don't do TLS
+ * session caching at all, and Postfix has no mechanism for storing
+ * multiple session tickets, if more than one sent, the second
+ * clobbers the first. OpenSSL 1.1.1 servers default to issuing two
+ * tickets for non-resumption handshakes, we reduce this to one. Our
+ * ticket decryption callback already (since 2.11) asks OpenSSL to
+ * avoid issuing new tickets when the presented ticket is re-usable.
+ */
+ SSL_CTX_set_num_tickets(server_ctx, 1);
+ }
+#endif
+ if (!ticketable)
+ off |= SSL_OP_NO_TICKET;
+
+ SSL_CTX_set_options(server_ctx, off);
+
+ /*
+ * Global protocol selection.
+ */
+ if (protomask != 0)
+ SSL_CTX_set_options(server_ctx, TLS_SSL_OP_PROTOMASK(protomask));
+ SSL_CTX_set_min_proto_version(server_ctx, min_proto);
+ SSL_CTX_set_max_proto_version(server_ctx, max_proto);
+ SSL_CTX_set_min_proto_version(sni_ctx, min_proto);
+ SSL_CTX_set_max_proto_version(sni_ctx, max_proto);
+
+ /*
+ * Some sites may want to give the client less rope. On the other hand,
+ * this could trigger inter-operability issues, the client should not
+ * offer ciphers it implements poorly, but this hasn't stopped some
+ * vendors from getting it wrong.
+ */
+ if (var_tls_preempt_clist)
+ SSL_CTX_set_options(server_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+ /* Done with server_ctx options, clone to sni_ctx */
+ SSL_CTX_clear_options(sni_ctx, ~0);
+ SSL_CTX_set_options(sni_ctx, SSL_CTX_get_options(server_ctx));
+
+ /*
+ * Set the call-back routine to debug handshake progress.
+ */
+ if (log_mask & TLS_LOG_DEBUG) {
+ SSL_CTX_set_info_callback(server_ctx, tls_info_callback);
+ SSL_CTX_set_info_callback(sni_ctx, tls_info_callback);
+ }
+
+ /*
+ * Load the CA public key certificates for both the server cert and for
+ * the verification of client certificates. As provided by OpenSSL we
+ * support two types of CA certificate handling: One possibility is to
+ * add all CA certificates to one large CAfile, the other possibility is
+ * a directory pointed to by CApath, containing separate files for each
+ * CA with softlinks named after the hash values of the certificate. The
+ * first alternative has the advantage that the file is opened and read
+ * at startup time, so that you don't have the hassle to maintain another
+ * copy of the CApath directory for chroot-jail.
+ */
+ if (tls_set_ca_certificate_info(server_ctx,
+ props->CAfile, props->CApath) < 0) {
+ /* tls_set_ca_certificate_info() already logs a warning. */
+ SSL_CTX_free(server_ctx); /* 200411 */
+ SSL_CTX_free(sni_ctx);
+ return (0);
+ }
+
+ /*
+ * Upref and share the cert store. Sadly we can't yet use
+ * SSL_CTX_set1_cert_store(3) which was added in OpenSSL 1.1.0.
+ */
+ cert_store = SSL_CTX_get_cert_store(server_ctx);
+ X509_STORE_up_ref(cert_store);
+ SSL_CTX_set_cert_store(sni_ctx, cert_store);
+
+ /*
+ * Load the server public key certificate and private key from file and
+ * check whether the cert matches the key. We can use RSA certificates
+ * ("cert") DSA certificates ("dcert") or ECDSA certificates ("eccert").
+ * All three can be made available at the same time. The CA certificates
+ * for all three are handled in the same setup already finished. Which
+ * one is used depends on the cipher negotiated (that is: the first
+ * cipher listed by the client which does match the server). A client
+ * with RSA only (e.g. Netscape) will use the RSA certificate only. A
+ * client with openssl-library will use RSA first if not especially
+ * changed in the cipher setup.
+ */
+ if (tls_set_my_certificate_key_info(server_ctx,
+ props->chain_files,
+ props->cert_file,
+ props->key_file,
+ props->dcert_file,
+ props->dkey_file,
+ props->eccert_file,
+ props->eckey_file) < 0) {
+ /* tls_set_my_certificate_key_info() already logs a warning. */
+ SSL_CTX_free(server_ctx); /* 200411 */
+ SSL_CTX_free(sni_ctx);
+ return (0);
+ }
+
+ /*
+ * Diffie-Hellman key generation parameters can either be loaded from
+ * files (preferred) or taken from compiled in values. First, set the
+ * callback that will select the values when requested, then load the
+ * (possibly) available DH parameters from files. We are generous with
+ * the error handling, since we do have default values compiled in, so we
+ * will not abort but just log the error message.
+ */
+ if (*props->dh1024_param_file != 0)
+ tls_set_dh_from_file(props->dh1024_param_file);
+ tls_tmp_dh(server_ctx, 1);
+ tls_tmp_dh(sni_ctx, 1);
+
+ /*
+ * Enable EECDH if available, errors are not fatal, we just keep going
+ * with any remaining key-exchange algorithms.
+ */
+ tls_auto_eecdh_curves(server_ctx, var_tls_eecdh_auto);
+ tls_auto_eecdh_curves(sni_ctx, var_tls_eecdh_auto);
+
+ /*
+ * If we want to check client certificates, we have to indicate it in
+ * advance. By now we only allow to decide on a global basis. If we want
+ * to allow certificate based relaying, we must ask the client to provide
+ * one with SSL_VERIFY_PEER. The client now can decide, whether it
+ * provides one or not. We can enforce a failure of the negotiation with
+ * SSL_VERIFY_FAIL_IF_NO_PEER_CERT, if we do not allow a connection
+ * without one. In the "server hello" following the initialization by the
+ * "client hello" the server must provide a list of CAs it is willing to
+ * accept. Some clever clients will then select one from the list of
+ * available certificates matching these CAs. Netscape Communicator will
+ * present the list of certificates for selecting the one to be sent, or
+ * it will issue a warning, if there is no certificate matching the
+ * available CAs.
+ *
+ * With regard to the purpose of the certificate for relaying, we might like
+ * a later negotiation, maybe relaying would already be allowed for other
+ * reasons, but this would involve severe changes in the internal postfix
+ * logic, so we have to live with it the way it is.
+ */
+ if (props->ask_ccert)
+ verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
+ SSL_CTX_set_verify(server_ctx, verify_flags,
+ tls_verify_certificate_callback);
+ SSL_CTX_set_verify(sni_ctx, verify_flags,
+ tls_verify_certificate_callback);
+ if (props->ask_ccert && *props->CAfile) {
+ STACK_OF(X509_NAME) *calist = SSL_load_client_CA_file(props->CAfile);
+
+ if (calist == 0) {
+ /* Not generally critical */
+ msg_warn("error loading client CA names from: %s",
+ props->CAfile);
+ tls_print_errors();
+ }
+ SSL_CTX_set_client_CA_list(server_ctx, calist);
+
+ if (calist != 0 && sk_X509_NAME_num(calist) > 0) {
+ calist = SSL_dup_CA_list(calist);
+
+ if (calist == 0) {
+ msg_warn("error duplicating client CA names for SNI");
+ tls_print_errors();
+ } else {
+ SSL_CTX_set_client_CA_list(sni_ctx, calist);
+ }
+ }
+ }
+
+ /*
+ * Initialize our own TLS server handle, before diving into the details
+ * of TLS session cache management.
+ */
+ app_ctx = tls_alloc_app_context(server_ctx, sni_ctx, log_mask);
+
+ if (cachable || ticketable || props->set_sessid) {
+
+ /*
+ * Initialize the session cache.
+ *
+ * With a large number of concurrent smtpd(8) processes, it is not a
+ * good idea to cache multiple large session objects in each process.
+ * We set the internal cache size to 1, and don't register a
+ * "remove_cb" so as to avoid deleting good sessions from the
+ * external cache prematurely (when the internal cache is full,
+ * OpenSSL removes sessions from the external cache also)!
+ *
+ * This makes SSL_CTX_remove_session() not useful for flushing broken
+ * sessions from the external cache, so we must delete them directly
+ * (not via a callback).
+ *
+ * Set a session id context to identify to what type of server process
+ * created a session. In our case, the context is simply the name of
+ * the mail system: "Postfix/TLS".
+ */
+ SSL_CTX_sess_set_cache_size(server_ctx, 1);
+ SSL_CTX_set_session_id_context(server_ctx,
+ (void *) &server_session_id_context,
+ sizeof(server_session_id_context));
+ SSL_CTX_set_session_cache_mode(server_ctx,
+ SSL_SESS_CACHE_SERVER |
+ SSL_SESS_CACHE_NO_INTERNAL |
+ SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ if (cachable) {
+ app_ctx->cache_type = mystrdup(props->cache_type);
+
+ SSL_CTX_sess_set_get_cb(server_ctx, get_server_session_cb);
+ SSL_CTX_sess_set_new_cb(server_ctx, new_server_session_cb);
+ }
+
+ /*
+ * OpenSSL ignores timed-out sessions. We need to set the internal
+ * cache timeout at least as high as the external cache timeout. This
+ * applies even if no internal cache is used. We set the session
+ * lifetime to twice the cache lifetime, which is also the issuing
+ * and retired key validation lifetime of session tickets keys. This
+ * way a session always lasts longer than the server's ability to
+ * decrypt its session ticket. Otherwise, a bug in OpenSSL may fail
+ * to re-issue tickets when sessions decrypt, but are expired.
+ */
+ SSL_CTX_set_timeout(server_ctx, 2 * scache_timeout);
+ } else {
+
+ /*
+ * If we have no external cache, disable all caching. No use wasting
+ * server memory resources with sessions they are unlikely to be able
+ * to reuse.
+ */
+ SSL_CTX_set_session_cache_mode(server_ctx, SSL_SESS_CACHE_OFF);
+ }
+
+ return (app_ctx);
+}
+
+ /*
+ * This is the actual startup routine for a new connection. We expect that
+ * the SMTP buffers are flushed and the "220 Ready to start TLS" was sent to
+ * the client, so that we can immediately start the TLS handshake process.
+ */
+TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props)
+{
+ int sts;
+ TLS_SESS_STATE *TLScontext;
+ const char *cipher_list;
+ TLS_APPL_STATE *app_ctx = props->ctx;
+ int log_mask = app_ctx->log_mask;
+
+ /*
+ * Implicitly enable logging of trust chain errors when verified certs
+ * are required.
+ */
+ if (props->requirecert)
+ log_mask |= TLS_LOG_UNTRUSTED;
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("setting up TLS connection from %s", props->namaddr);
+
+ /*
+ * Allocate a new TLScontext for the new connection and get an SSL
+ * structure. Add the location of TLScontext to the SSL to later retrieve
+ * the information inside the tls_verify_certificate_callback().
+ */
+ TLScontext = tls_alloc_sess_context(log_mask, props->namaddr);
+ TLScontext->cache_type = app_ctx->cache_type;
+
+ ERR_clear_error();
+ if ((TLScontext->con = (SSL *) SSL_new(app_ctx->ssl_ctx)) == 0) {
+ msg_warn("Could not allocate 'TLScontext->con' with SSL_new()");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ cipher_list = tls_set_ciphers(TLScontext, props->cipher_grade,
+ props->cipher_exclusions);
+ if (cipher_list == 0) {
+ /* already warned */
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list);
+
+ TLScontext->serverid = mystrdup(props->serverid);
+ TLScontext->am_server = 1;
+ TLScontext->stream = props->stream;
+ TLScontext->mdalg = props->mdalg;
+
+ if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
+ msg_warn("Could not set application data for 'TLScontext->con'");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+#ifdef SSL_SECOP_PEER
+ /* When authenticating the peer, use 80-bit plus OpenSSL security level */
+ if (props->requirecert)
+ SSL_set_security_level(TLScontext->con, 1);
+#endif
+
+ /*
+ * Before really starting anything, try to seed the PRNG a little bit
+ * more.
+ */
+ tls_int_seed();
+ (void) tls_ext_seed(var_tls_daemon_rand_bytes);
+
+ /*
+ * Connect the SSL connection with the network socket.
+ */
+ if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd :
+ vstream_fileno(props->stream)) != 1) {
+ msg_info("SSL_set_fd error to %s", props->namaddr);
+ tls_print_errors();
+ uncache_session(app_ctx->ssl_ctx, TLScontext);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * If the debug level selected is high enough, all of the data is dumped:
+ * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will
+ * dump everything.
+ *
+ * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
+ * Well there is a BIO below the SSL routines that is automatically
+ * created for us, so we can use it for debugging purposes.
+ */
+ if (log_mask & TLS_LOG_TLSPKTS)
+ tls_set_bio_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb);
+
+ /*
+ * If we don't trigger the handshake in the library, leave control over
+ * SSL_accept/read/write/etc with the application.
+ */
+ if (props->stream == 0)
+ return (TLScontext);
+
+ /*
+ * Turn on non-blocking I/O so that we can enforce timeouts on network
+ * I/O.
+ */
+ non_blocking(vstream_fileno(props->stream), NON_BLOCKING);
+
+ /*
+ * Start TLS negotiations. This process is a black box that invokes our
+ * call-backs for session caching and certificate verification.
+ *
+ * Error handling: If the SSL handshake fails, we print out an error message
+ * and remove all TLS state concerning this session.
+ */
+ sts = tls_bio_accept(vstream_fileno(props->stream), props->timeout,
+ TLScontext);
+ if (sts <= 0) {
+ if (ERR_peek_error() != 0) {
+ msg_info("SSL_accept error from %s: %d", props->namaddr, sts);
+ tls_print_errors();
+ } else if (errno != 0) {
+ msg_info("SSL_accept error from %s: %m", props->namaddr);
+ } else {
+ msg_info("SSL_accept error from %s: lost connection",
+ props->namaddr);
+ }
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ return (tls_server_post_accept(TLScontext));
+}
+
+/* tls_server_post_accept - post-handshake processing */
+
+TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
+{
+ const SSL_CIPHER *cipher;
+ X509 *peer;
+ char buf[CCERT_BUFSIZ];
+
+ /* Turn off packet dump if only dumping the handshake */
+ if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0)
+ tls_set_bio_callback(SSL_get_rbio(TLScontext->con), 0);
+
+ /*
+ * The caller may want to know if this session was reused or if a new
+ * session was negotiated.
+ */
+ TLScontext->session_reused = SSL_session_reused(TLScontext->con);
+ if ((TLScontext->log_mask & TLS_LOG_CACHE) && TLScontext->session_reused)
+ msg_info("%s: Reusing old session%s", TLScontext->namaddr,
+ TLScontext->ticketed ? " (RFC 5077 session ticket)" : "");
+
+ /*
+ * Let's see whether a peer certificate is available and what is the
+ * actual information. We want to save it for later use.
+ */
+ peer = TLS_PEEK_PEER_CERT(TLScontext->con);
+ if (peer != NULL) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK)
+ TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
+
+ if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
+ X509_NAME_oneline(X509_get_subject_name(peer),
+ buf, sizeof(buf));
+ msg_info("subject=%s", printable(buf, '?'));
+ X509_NAME_oneline(X509_get_issuer_name(peer),
+ buf, sizeof(buf));
+ msg_info("issuer=%s", printable(buf, '?'));
+ }
+ TLScontext->peer_CN = tls_peer_CN(peer, TLScontext);
+ TLScontext->issuer_CN = tls_issuer_CN(peer, TLScontext);
+ TLScontext->peer_cert_fprint = tls_cert_fprint(peer, TLScontext->mdalg);
+ TLScontext->peer_pkey_fprint = tls_pkey_fprint(peer, TLScontext->mdalg);
+
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) {
+ msg_info("%s: subject_CN=%s, issuer=%s, fingerprint=%s"
+ ", pkey_fingerprint=%s",
+ TLScontext->namaddr,
+ TLScontext->peer_CN, TLScontext->issuer_CN,
+ TLScontext->peer_cert_fprint,
+ TLScontext->peer_pkey_fprint);
+ }
+ TLS_FREE_PEER_CERT(peer);
+
+ /*
+ * Give them a clue. Problems with trust chain verification are
+ * logged when the session is first negotiated, before the session is
+ * stored into the cache. We don't want mystery failures, so log the
+ * fact the real problem is to be found in the past.
+ */
+ if (!TLS_CERT_IS_TRUSTED(TLScontext)
+ && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+ if (TLScontext->session_reused == 0)
+ tls_log_verify_error(TLScontext);
+ else
+ msg_info("%s: re-using session with untrusted certificate, "
+ "look for details earlier in the log",
+ TLScontext->namaddr);
+ }
+ } else {
+ TLScontext->peer_CN = mystrdup("");
+ TLScontext->issuer_CN = mystrdup("");
+ TLScontext->peer_cert_fprint = mystrdup("");
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ }
+
+ /*
+ * Finally, collect information about protocol and cipher for logging
+ */
+ TLScontext->protocol = SSL_get_version(TLScontext->con);
+ cipher = SSL_get_current_cipher(TLScontext->con);
+ TLScontext->cipher_name = SSL_CIPHER_get_name(cipher);
+ TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher,
+ &(TLScontext->cipher_algbits));
+
+ /*
+ * If the library triggered the SSL handshake, switch to the
+ * tls_timed_read/write() functions and make the TLScontext available to
+ * those functions. Otherwise, leave control over SSL_read/write/etc.
+ * with the application.
+ */
+ if (TLScontext->stream != 0)
+ tls_stream_start(TLScontext->stream, TLScontext);
+
+ /*
+ * With the handshake done, extract TLS 1.3 signature metadata.
+ */
+ tls_get_signature_params(TLScontext);
+
+ /*
+ * All the key facts in a single log entry.
+ */
+ if (TLScontext->log_mask & TLS_LOG_SUMMARY)
+ tls_log_summary(TLS_ROLE_SERVER, TLS_USAGE_NEW, TLScontext);
+
+ tls_int_seed();
+
+ return (TLScontext);
+}
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_session.c b/src/tls/tls_session.c
new file mode 100644
index 0000000..a4b7a8f
--- /dev/null
+++ b/src/tls/tls_session.c
@@ -0,0 +1,185 @@
+/*++
+/* NAME
+/* tls_session
+/* SUMMARY
+/* TLS client and server session routines
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* void tls_session_stop(ctx, stream, timeout, failure, TLScontext)
+/* TLS_APPL_STATE *ctx;
+/* VSTREAM *stream;
+/* int timeout;
+/* int failure;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* VSTRING *tls_session_passivate(session)
+/* SSL_SESSION *session;
+/*
+/* SSL_SESSION *tls_session_activate(session_data, session_data_len)
+/* char *session_data;
+/* int session_data_len;
+/* DESCRIPTION
+/* tls_session_stop() implements the tls_server_shutdown()
+/* and the tls_client_shutdown() routines.
+/*
+/* tls_session_passivate() converts an SSL_SESSION object to
+/* VSTRING. The result is a null pointer in case of problems,
+/* otherwise it should be disposed of with vstring_free().
+/*
+/* tls_session_activate() reanimates a passivated SSL_SESSION object.
+/* The result is a null pointer in case of problems,
+/* otherwise it should be disposed of with SSL_SESSION_free().
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* 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
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <vstream.h>
+#include <msg.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+
+/* tls_session_stop - shut down the TLS connection and reset state */
+
+void tls_session_stop(TLS_APPL_STATE *unused_ctx, VSTREAM *stream, int timeout,
+ int failure, TLS_SESS_STATE *TLScontext)
+{
+ const char *myname = "tls_session_stop";
+ int retval;
+
+ /*
+ * Sanity check.
+ */
+ if (TLScontext == 0)
+ msg_panic("%s: stream has no active TLS context", myname);
+
+ /*
+ * According to RFC 2246 (TLS 1.0), there is no requirement to wait for
+ * the peer's close-notify. If the application protocol provides
+ * sufficient session termination signaling, then there's no need to
+ * duplicate that at the TLS close-notify layer.
+ *
+ * https://tools.ietf.org/html/rfc2246#section-7.2.1
+ * https://tools.ietf.org/html/rfc4346#section-7.2.1
+ * https://tools.ietf.org/html/rfc5246#section-7.2.1
+ *
+ * Specify 'tls_fast_shutdown = no' to enable the historical behavior
+ * described below.
+ *
+ * Perform SSL_shutdown() twice, as the first attempt will send out the
+ * shutdown alert but it will not wait for the peer's shutdown alert.
+ * Therefore, when we are the first party to send the alert, we must call
+ * SSL_shutdown() again. On failure we don't want to resume the session,
+ * so we will not perform SSL_shutdown() and the session will be removed
+ * as being bad.
+ */
+ if (!failure && !SSL_in_init(TLScontext->con)) {
+ retval = tls_bio_shutdown(vstream_fileno(stream), timeout, TLScontext);
+ if (!var_tls_fast_shutdown && retval == 0)
+ tls_bio_shutdown(vstream_fileno(stream), timeout, TLScontext);
+ }
+ tls_free_context(TLScontext);
+ tls_stream_stop(stream);
+}
+
+/* tls_session_passivate - passivate SSL_SESSION object */
+
+VSTRING *tls_session_passivate(SSL_SESSION *session)
+{
+ const char *myname = "tls_session_passivate";
+ int estimate;
+ int actual_size;
+ VSTRING *session_data;
+ unsigned char *ptr;
+
+ /*
+ * First, find out how much memory is needed for the passivated
+ * SSL_SESSION object.
+ */
+ estimate = i2d_SSL_SESSION(session, (unsigned char **) 0);
+ if (estimate <= 0) {
+ msg_warn("%s: i2d_SSL_SESSION failed: unable to cache session", myname);
+ return (0);
+ }
+
+ /*
+ * Passivate the SSL_SESSION object. The use of a VSTRING is slightly
+ * wasteful but is convenient to combine data and length.
+ */
+ session_data = vstring_alloc(estimate);
+ ptr = (unsigned char *) STR(session_data);
+ actual_size = i2d_SSL_SESSION(session, &ptr);
+ if (actual_size != estimate) {
+ msg_warn("%s: i2d_SSL_SESSION failed: unable to cache session", myname);
+ vstring_free(session_data);
+ return (0);
+ }
+ vstring_set_payload_size(session_data, actual_size);
+
+ return (session_data);
+}
+
+/* tls_session_activate - activate passivated session */
+
+SSL_SESSION *tls_session_activate(const char *session_data, int session_data_len)
+{
+ SSL_SESSION *session;
+ const unsigned char *ptr;
+
+ /*
+ * Activate the SSL_SESSION object.
+ */
+ ptr = (const unsigned char *) session_data;
+ session = d2i_SSL_SESSION((SSL_SESSION **) 0, &ptr, session_data_len);
+ if (!session)
+ tls_print_errors();
+
+ return (session);
+}
+
+#endif
diff --git a/src/tls/tls_stream.c b/src/tls/tls_stream.c
new file mode 100644
index 0000000..4cc53a7
--- /dev/null
+++ b/src/tls/tls_stream.c
@@ -0,0 +1,160 @@
+/*++
+/* NAME
+/* tls_stream
+/* SUMMARY
+/* VSTREAM over TLS
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* void tls_stream_start(stream, context)
+/* VSTREAM *stream;
+/* TLS_SESS_STATE *context;
+/*
+/* void tls_stream_stop(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements the VSTREAM over TLS support user interface.
+/* The hard work is done elsewhere.
+/*
+/* tls_stream_start() enables TLS on the named stream. All read
+/* and write operations are directed through the TLS library,
+/* using the state information specified with the context argument.
+/*
+/* tls_stream_stop() replaces the VSTREAM read/write routines
+/* by dummies that have no side effects, and deletes the
+/* VSTREAM's reference to the TLS context.
+/* DIAGNOSTICS
+/* The tls_stream(3) read/write routines return the non-zero
+/* number of plaintext bytes read/written if successful; -1
+/* after TLS protocol failure, system-call failure, or for any
+/* reason described under "in addition" below; and zero when
+/* the remote party closed the connection or sent a TLS shutdown
+/* request.
+/*
+/* Upon return from the tls_stream(3) read/write routines the
+/* global errno value is non-zero when the requested operation
+/* did not complete due to system call failure.
+/*
+/* In addition, the result value is set to -1, and the global
+/* errno value is set to ETIMEDOUT, when a network read/write
+/* request did not complete within the time limit.
+/* SEE ALSO
+/* dummy_read(3), placebo read routine
+/* dummy_write(3), placebo write routine
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Based on code that was originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <iostuff.h>
+#include <vstream.h>
+#include <msg.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+ /*
+ * Interface mis-match compensation. The OpenSSL read/write routines return
+ * unspecified negative values when an operation fails, while the vstream(3)
+ * plaintext timed_read/write() functions follow the convention of UNIX
+ * system calls, and return -1 upon error. The macro below makes OpenSSL
+ * read/write results consistent with the UNIX system-call convention.
+ */
+#define NORMALIZED_VSTREAM_RETURN(retval) ((retval) < 0 ? -1 : (retval))
+
+/* tls_timed_read - read content from stream, then TLS decapsulate */
+
+static ssize_t tls_timed_read(int fd, void *buf, size_t len, int timeout,
+ void *context)
+{
+ const char *myname = "tls_timed_read";
+ ssize_t ret;
+ TLS_SESS_STATE *TLScontext;
+
+ TLScontext = (TLS_SESS_STATE *) context;
+ if (!TLScontext)
+ msg_panic("%s: no context", myname);
+
+ ret = tls_bio_read(fd, buf, len, timeout, TLScontext);
+ if (ret > 0 && (TLScontext->log_mask & TLS_LOG_ALLPKTS))
+ msg_info("Read %ld chars: %.*s",
+ (long) ret, (int) (ret > 40 ? 40 : ret), (char *) buf);
+ return (NORMALIZED_VSTREAM_RETURN(ret));
+}
+
+/* tls_timed_write - TLS encapsulate content, then write to stream */
+
+static ssize_t tls_timed_write(int fd, void *buf, size_t len, int timeout,
+ void *context)
+{
+ const char *myname = "tls_timed_write";
+ ssize_t ret;
+ TLS_SESS_STATE *TLScontext;
+
+ TLScontext = (TLS_SESS_STATE *) context;
+ if (!TLScontext)
+ msg_panic("%s: no context", myname);
+
+ if (TLScontext->log_mask & TLS_LOG_ALLPKTS)
+ msg_info("Write %ld chars: %.*s",
+ (long) len, (int) (len > 40 ? 40 : len), (char *) buf);
+ ret = tls_bio_write(fd, buf, len, timeout, TLScontext);
+ return (NORMALIZED_VSTREAM_RETURN(ret));
+}
+
+/* tls_stream_start - start VSTREAM over TLS */
+
+void tls_stream_start(VSTREAM *stream, TLS_SESS_STATE *context)
+{
+ vstream_control(stream,
+ CA_VSTREAM_CTL_READ_FN(tls_timed_read),
+ CA_VSTREAM_CTL_WRITE_FN(tls_timed_write),
+ CA_VSTREAM_CTL_CONTEXT(context),
+ CA_VSTREAM_CTL_END);
+}
+
+/* tls_stream_stop - stop VSTREAM over TLS */
+
+void tls_stream_stop(VSTREAM *stream)
+{
+
+ /*
+ * Prevent data leakage after TLS is turned off. The Postfix/TLS patch
+ * provided null function pointers; we use dummy routines that make less
+ * noise when used.
+ */
+ vstream_control(stream,
+ CA_VSTREAM_CTL_READ_FN(dummy_read),
+ CA_VSTREAM_CTL_WRITE_FN(dummy_write),
+ CA_VSTREAM_CTL_CONTEXT((void *) 0),
+ CA_VSTREAM_CTL_END);
+}
+
+#endif
diff --git a/src/tls/tls_verify.c b/src/tls/tls_verify.c
new file mode 100644
index 0000000..f32f32b
--- /dev/null
+++ b/src/tls/tls_verify.c
@@ -0,0 +1,425 @@
+/*++
+/* NAME
+/* tls_verify 3
+/* SUMMARY
+/* peer name and peer certificate verification
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_verify_certificate_callback(ok, ctx)
+/* int ok;
+/* X509_STORE_CTX *ctx;
+/*
+/* int tls_log_verify_error(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* char *tls_peer_CN(peercert, TLScontext)
+/* X509 *peercert;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* char *tls_issuer_CN(peercert, TLScontext)
+/* X509 *peercert;
+/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* tls_verify_certificate_callback() is called several times (directly
+/* or indirectly) from crypto/x509/x509_vfy.c. It collects errors
+/* and trust information at each element of the trust chain.
+/* The last call at depth 0 sets the verification status based
+/* on the cumulative winner (lowest depth) of errors vs. trust.
+/* We always return 1 (continue the handshake) and handle trust
+/* and peer-name verification problems at the application level.
+/*
+/* tls_log_verify_error() (called only when we care about the
+/* peer certificate, that is not when opportunistic) logs the
+/* reason why the certificate failed to be verified.
+/*
+/* tls_peer_CN() returns the text CommonName for the peer
+/* certificate subject, or an empty string if no CommonName was
+/* found. The result is allocated with mymalloc() and must be
+/* freed by the caller; it contains UTF-8 without non-printable
+/* ASCII characters.
+/*
+/* tls_issuer_CN() returns the text CommonName for the peer
+/* certificate issuer, or an empty string if no CommonName was
+/* found. The result is allocated with mymalloc() and must be
+/* freed by the caller; it contains UTF-8 without non-printable
+/* ASCII characters.
+/*
+/* Arguments:
+/* .IP ok
+/* Result of prior verification: non-zero means success. In
+/* order to reduce the noise level, some tests or error reports
+/* are disabled when verification failed because of some
+/* earlier problem.
+/* .IP ctx
+/* SSL application context. This links to the Postfix TLScontext
+/* with enforcement and logging options.
+/* .IP gn
+/* An OpenSSL GENERAL_NAME structure holding a DNS subjectAltName
+/* to be decoded and checked for validity.
+/* .IP peercert
+/* Server or client X.509 certificate.
+/* .IP TLScontext
+/* Server or client context for warning messages.
+/* DIAGNOSTICS
+/* tls_peer_CN() and tls_issuer_CN() log a warning when 1) the requested
+/* information is not available in the specified certificate, 2) the
+/* result exceeds a fixed limit, 3) the result contains NUL characters or
+/* the result contains non-printable or non-ASCII characters.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* update_error_state - safely stash away error state */
+
+static void update_error_state(TLS_SESS_STATE *TLScontext, int depth,
+ X509 *errorcert, int errorcode)
+{
+ /* No news is good news */
+ if (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth)
+ return;
+
+ /*
+ * The certificate pointer is stable during the verification callback,
+ * but may be freed after the callback returns. Since we delay error
+ * reporting till later, we bump the refcount so we can rely on it still
+ * being there until later.
+ */
+ if (TLScontext->errorcert != 0)
+ X509_free(TLScontext->errorcert);
+ if (errorcert != 0)
+ X509_up_ref(errorcert);
+ TLScontext->errorcert = errorcert;
+ TLScontext->errorcode = errorcode;
+ TLScontext->errordepth = depth;
+}
+
+/* tls_verify_certificate_callback - verify peer certificate info */
+
+int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
+{
+ char buf[CCERT_BUFSIZ];
+ X509 *cert;
+ int err;
+ int depth;
+ SSL *con;
+ TLS_SESS_STATE *TLScontext;
+
+ /* May be NULL as of OpenSSL 1.0, thanks for the API change! */
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ err = X509_STORE_CTX_get_error(ctx);
+ con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ TLScontext = SSL_get_ex_data(con, TLScontext_index);
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+
+ /*
+ * Transient failures to load the (DNS or synthetic TLSA) trust settings
+ * must poison certificate verification, since otherwise the default
+ * trust store may bless a certificate that would have failed
+ * verification with the preferred trust anchors (or fingerprints).
+ *
+ * Since we unconditionally continue, or in any case if verification is
+ * about to succeed, there is eventually a final depth 0 callback, at
+ * which point we force an "unspecified" error. The failure to load the
+ * trust settings was logged earlier.
+ */
+ if (TLScontext->must_fail) {
+ if (depth == 0) {
+ X509_STORE_CTX_set_error(ctx, err = X509_V_ERR_UNSPECIFIED);
+ update_error_state(TLScontext, depth, cert, err);
+ }
+ return (1);
+ }
+ if (ok == 0)
+ update_error_state(TLScontext, depth, cert, err);
+
+ if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
+ if (cert)
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
+ else
+ strcpy(buf, "<unknown>");
+ msg_info("%s: depth=%d verify=%d subject=%s",
+ TLScontext->namaddr, depth, ok, printable(buf, '?'));
+ }
+ return (1);
+}
+
+/* tls_log_verify_error - Report final verification error status */
+
+void tls_log_verify_error(TLS_SESS_STATE *TLScontext)
+{
+ char buf[CCERT_BUFSIZ];
+ int err = TLScontext->errorcode;
+ X509 *cert = TLScontext->errorcert;
+ int depth = TLScontext->errordepth;
+
+#define PURPOSE ((depth>0) ? "CA": TLScontext->am_server ? "client": "server")
+
+ if (err == X509_V_OK)
+ return;
+
+ /*
+ * Specific causes for verification failure.
+ */
+ switch (err) {
+ case X509_V_ERR_CERT_UNTRUSTED:
+
+ /*
+ * We expect the error cert to be the leaf, but it is likely
+ * sufficient to omit it from the log, even less user confusion.
+ */
+ msg_info("certificate verification failed for %s: "
+ "not trusted by local or TLSA policy", TLScontext->namaddr);
+ break;
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ msg_info("certificate verification failed for %s: "
+ "self-signed certificate", TLScontext->namaddr);
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+
+ /*
+ * There is no difference between issuing cert not provided and
+ * provided, but not found in CAfile/CApath. Either way, we don't
+ * trust it.
+ */
+ if (cert)
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
+ else
+ strcpy(buf, "<unknown>");
+ msg_info("certificate verification failed for %s: untrusted issuer %s",
+ TLScontext->namaddr, printable(buf, '?'));
+ break;
+ case X509_V_ERR_CERT_NOT_YET_VALID:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+ msg_info("%s certificate verification failed for %s: certificate not"
+ " yet valid", PURPOSE, TLScontext->namaddr);
+ break;
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+ msg_info("%s certificate verification failed for %s: certificate has"
+ " expired", PURPOSE, TLScontext->namaddr);
+ break;
+ case X509_V_ERR_INVALID_PURPOSE:
+ msg_info("certificate verification failed for %s: not designated for "
+ "use as a %s certificate", TLScontext->namaddr, PURPOSE);
+ break;
+ case X509_V_ERR_CERT_CHAIN_TOO_LONG:
+ msg_info("certificate verification failed for %s: "
+ "certificate chain longer than limit(%d)",
+ TLScontext->namaddr, depth - 1);
+ break;
+ default:
+ msg_info("%s certificate verification failed for %s: num=%d:%s",
+ PURPOSE, TLScontext->namaddr, err,
+ X509_verify_cert_error_string(err));
+ break;
+ }
+}
+
+#ifndef DONT_GRIPE
+#define DONT_GRIPE 0
+#define DO_GRIPE 1
+#endif
+
+/* tls_text_name - extract certificate property value by name */
+
+static char *tls_text_name(X509_NAME *name, int nid, const char *label,
+ const TLS_SESS_STATE *TLScontext, int gripe)
+{
+ const char *myname = "tls_text_name";
+ int pos;
+ X509_NAME_ENTRY *entry;
+ ASN1_STRING *entry_str;
+ int asn1_type;
+ int utf8_length;
+ unsigned char *utf8_value;
+ int ch;
+ unsigned char *cp;
+
+ if (name == 0 || (pos = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) {
+ if (gripe != DONT_GRIPE) {
+ msg_warn("%s: %s: peer certificate has no %s",
+ myname, TLScontext->namaddr, label);
+ tls_print_errors();
+ }
+ return (0);
+ }
+#if 0
+
+ /*
+ * If the match is required unambiguous, insist that that no other values
+ * be present.
+ */
+ if (X509_NAME_get_index_by_NID(name, nid, pos) >= 0) {
+ msg_warn("%s: %s: multiple %ss in peer certificate",
+ myname, TLScontext->namaddr, label);
+ return (0);
+ }
+#endif
+
+ if ((entry = X509_NAME_get_entry(name, pos)) == 0) {
+ /* This should not happen */
+ msg_warn("%s: %s: error reading peer certificate %s entry",
+ myname, TLScontext->namaddr, label);
+ tls_print_errors();
+ return (0);
+ }
+ if ((entry_str = X509_NAME_ENTRY_get_data(entry)) == 0) {
+ /* This should not happen */
+ msg_warn("%s: %s: error reading peer certificate %s data",
+ myname, TLScontext->namaddr, label);
+ tls_print_errors();
+ return (0);
+ }
+
+ /*
+ * XXX Convert everything into UTF-8. This is a super-set of ASCII, so we
+ * don't have to bother with separate code paths for ASCII-like content.
+ * If the payload is ASCII then we won't waste lots of CPU cycles
+ * converting it into UTF-8. It's up to OpenSSL to do something
+ * reasonable when converting ASCII formats that contain non-ASCII
+ * content.
+ *
+ * XXX Don't bother optimizing the string length error check. It is not
+ * worth the complexity.
+ */
+ asn1_type = ASN1_STRING_type(entry_str);
+ if ((utf8_length = ASN1_STRING_to_UTF8(&utf8_value, entry_str)) < 0) {
+ msg_warn("%s: %s: error decoding peer %s of ASN.1 type=%d",
+ myname, TLScontext->namaddr, label, asn1_type);
+ tls_print_errors();
+ return (0);
+ }
+
+ /*
+ * No returns without cleaning up. A good optimizer will replace multiple
+ * blocks of identical code by jumps to just one such block.
+ */
+#define TLS_TEXT_NAME_RETURN(x) do { \
+ char *__tls_text_name_temp = (x); \
+ OPENSSL_free(utf8_value); \
+ return (__tls_text_name_temp); \
+ } while (0)
+
+ /*
+ * Remove trailing null characters. They would give false alarms with the
+ * length check and with the embedded null check.
+ */
+#define TRIM0(s, l) do { while ((l) > 0 && (s)[(l)-1] == 0) --(l); } while (0)
+
+ TRIM0(utf8_value, utf8_length);
+
+ /*
+ * Enforce the length limit, because the caller will copy the result into
+ * a fixed-length buffer.
+ */
+ if (utf8_length >= CCERT_BUFSIZ) {
+ msg_warn("%s: %s: peer %s too long: %d",
+ myname, TLScontext->namaddr, label, utf8_length);
+ TLS_TEXT_NAME_RETURN(0);
+ }
+
+ /*
+ * Reject embedded nulls in ASCII or UTF-8 names. OpenSSL is responsible
+ * for producing properly-formatted UTF-8.
+ */
+ if (utf8_length != strlen((char *) utf8_value)) {
+ msg_warn("%s: %s: NULL character in peer %s",
+ myname, TLScontext->namaddr, label);
+ TLS_TEXT_NAME_RETURN(0);
+ }
+
+ /*
+ * Reject non-printable ASCII characters in UTF-8 content.
+ *
+ * Note: the code below does not find control characters in illegal UTF-8
+ * sequences. It's OpenSSL's job to produce valid UTF-8, and reportedly,
+ * it does validation.
+ */
+ for (cp = utf8_value; (ch = *cp) != 0; cp++) {
+ if (ISASCII(ch) && !ISPRINT(ch)) {
+ msg_warn("%s: %s: non-printable content in peer %s",
+ myname, TLScontext->namaddr, label);
+ TLS_TEXT_NAME_RETURN(0);
+ }
+ }
+ TLS_TEXT_NAME_RETURN(mystrdup((char *) utf8_value));
+}
+
+/* tls_peer_CN - extract peer common name from certificate */
+
+char *tls_peer_CN(X509 *peercert, const TLS_SESS_STATE *TLScontext)
+{
+ char *cn;
+ const char *san;
+
+ /* Absent a commonName, return a validated DNS-ID SAN */
+ cn = tls_text_name(X509_get_subject_name(peercert), NID_commonName,
+ "subject CN", TLScontext, DONT_GRIPE);
+ if (cn == 0 && (san = SSL_get0_peername(TLScontext->con)) != 0)
+ cn = mystrdup(san);
+ return (cn ? cn : mystrdup(""));
+}
+
+/* tls_issuer_CN - extract issuer common name from certificate */
+
+char *tls_issuer_CN(X509 *peer, const TLS_SESS_STATE *TLScontext)
+{
+ X509_NAME *name;
+ char *cn;
+
+ name = X509_get_issuer_name(peer);
+
+ /*
+ * If no issuer CN field, use Organization instead. CA certs without a CN
+ * are common, so we only complain if the organization is also missing.
+ */
+ if ((cn = tls_text_name(name, NID_commonName,
+ "issuer CN", TLScontext, DONT_GRIPE)) == 0)
+ cn = tls_text_name(name, NID_organizationName,
+ "issuer Organization", TLScontext, DONT_GRIPE);
+ return (cn ? cn : mystrdup(""));
+}
+
+#endif
diff --git a/src/tls/warn-mixed-multi-key.pem b/src/tls/warn-mixed-multi-key.pem
new file mode 100644
index 0000000..ab21d3a
--- /dev/null
+++ b/src/tls/warn-mixed-multi-key.pem
@@ -0,0 +1,51 @@
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC
+rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy
+b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV
+-----END PRIVATE KEY-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/warn-mixed-multi-key.pem.ref b/src/tls/warn-mixed-multi-key.pem.ref
new file mode 100644
index 0000000..c9ce8db
--- /dev/null
+++ b/src/tls/warn-mixed-multi-key.pem.ref
@@ -0,0 +1,13 @@
+unknown: warning: ignoring 2nd key at index 4 in warn-mixed-multi-key.pem after 1st at 2
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tlsmgr/.indent.pro b/src/tlsmgr/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/tlsmgr/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/tlsmgr/Makefile.in b/src/tlsmgr/Makefile.in
new file mode 100644
index 0000000..8e7aab6
--- /dev/null
+++ b/src/tlsmgr/Makefile.in
@@ -0,0 +1,100 @@
+SHELL = /bin/sh
+SRCS = tlsmgr.c
+OBJS = tlsmgr.o
+HDRS =
+TESTSRC = smtpd_token_test.c
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = tlsmgr
+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)
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h 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 *.db *.out *.tmp
+ rm -rf printfck
+
+tidy: clean
+
+tests:
+
+root_tests:
+
+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'
+tlsmgr.o: ../../include/argv.h
+tlsmgr.o: ../../include/attr.h
+tlsmgr.o: ../../include/check_arg.h
+tlsmgr.o: ../../include/data_redirect.h
+tlsmgr.o: ../../include/dict.h
+tlsmgr.o: ../../include/dns.h
+tlsmgr.o: ../../include/events.h
+tlsmgr.o: ../../include/htable.h
+tlsmgr.o: ../../include/iostuff.h
+tlsmgr.o: ../../include/mail_conf.h
+tlsmgr.o: ../../include/mail_params.h
+tlsmgr.o: ../../include/mail_proto.h
+tlsmgr.o: ../../include/mail_server.h
+tlsmgr.o: ../../include/mail_version.h
+tlsmgr.o: ../../include/master_proto.h
+tlsmgr.o: ../../include/msg.h
+tlsmgr.o: ../../include/myaddrinfo.h
+tlsmgr.o: ../../include/myflock.h
+tlsmgr.o: ../../include/mymalloc.h
+tlsmgr.o: ../../include/name_code.h
+tlsmgr.o: ../../include/name_mask.h
+tlsmgr.o: ../../include/nvtable.h
+tlsmgr.o: ../../include/set_eugid.h
+tlsmgr.o: ../../include/sock_addr.h
+tlsmgr.o: ../../include/stringops.h
+tlsmgr.o: ../../include/sys_defs.h
+tlsmgr.o: ../../include/tls.h
+tlsmgr.o: ../../include/tls_mgr.h
+tlsmgr.o: ../../include/tls_prng.h
+tlsmgr.o: ../../include/tls_scache.h
+tlsmgr.o: ../../include/vbuf.h
+tlsmgr.o: ../../include/vstream.h
+tlsmgr.o: ../../include/vstring.h
+tlsmgr.o: ../../include/vstring_vstream.h
+tlsmgr.o: ../../include/warn_stat.h
+tlsmgr.o: tlsmgr.c
diff --git a/src/tlsmgr/tlsmgr.c b/src/tlsmgr/tlsmgr.c
new file mode 100644
index 0000000..28ca961
--- /dev/null
+++ b/src/tlsmgr/tlsmgr.c
@@ -0,0 +1,1115 @@
+/*++
+/* NAME
+/* tlsmgr 8
+/* SUMMARY
+/* Postfix TLS session cache and PRNG manager
+/* SYNOPSIS
+/* \fBtlsmgr\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBtlsmgr\fR(8) manages the Postfix TLS session caches.
+/* It stores and retrieves cache entries on request by
+/* \fBsmtpd\fR(8) and \fBsmtp\fR(8) processes, and periodically
+/* removes entries that have expired.
+/*
+/* The \fBtlsmgr\fR(8) also manages the PRNG (pseudo random number
+/* generator) pool. It answers queries by the \fBsmtpd\fR(8)
+/* and \fBsmtp\fR(8)
+/* processes to seed their internal PRNG pools.
+/*
+/* The \fBtlsmgr\fR(8)'s PRNG pool is initially seeded from
+/* an external source (EGD, /dev/urandom, or regular file).
+/* It is updated at configurable pseudo-random intervals with
+/* data from the external source. It is updated periodically
+/* with data from TLS session cache entries and with the time
+/* of day, and is updated with the time of day whenever a
+/* process requests \fBtlsmgr\fR(8) service.
+/*
+/* The \fBtlsmgr\fR(8) saves the PRNG state to an exchange file
+/* periodically and when the process terminates, and reads
+/* the exchange file when initializing its PRNG.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBtlsmgr\fR(8) is not security-sensitive. The code that maintains
+/* the external and internal PRNG pools does not "trust" the
+/* data that it manipulates, and the code that maintains the
+/* TLS session cache does not touch the contents of the cached
+/* entries, except for seeding its internal PRNG pool.
+/*
+/* The \fBtlsmgr\fR(8) can be run chrooted and with reduced privileges.
+/* At process startup it connects to the entropy source and
+/* exchange file, and creates or truncates the optional TLS
+/* session cache files.
+/*
+/* With Postfix version 2.5 and later, the \fBtlsmgr\fR(8) no
+/* longer uses root privileges when opening cache files. These
+/* files should now be stored under the Postfix-owned
+/* \fBdata_directory\fR. As a migration aid, an attempt to
+/* open a cache file under a non-Postfix directory is redirected
+/* to the Postfix-owned \fBdata_directory\fR, and a warning
+/* is logged.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* There is no automatic means to limit the number of entries in the
+/* TLS session caches and/or the size of the TLS cache files.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are not picked up automatically,
+/* because \fBtlsmgr\fR(8) is a persistent processes. Use the
+/* command "\fBpostfix reload\fR" after a configuration change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* TLS SESSION CACHE
+/* .ad
+/* .fi
+/* .IP "\fBlmtp_tls_loglevel (0)\fR"
+/* The LMTP-specific version of the smtp_tls_loglevel
+/* configuration parameter.
+/* .IP "\fBlmtp_tls_session_cache_database (empty)\fR"
+/* The LMTP-specific version of the smtp_tls_session_cache_database
+/* configuration parameter.
+/* .IP "\fBlmtp_tls_session_cache_timeout (3600s)\fR"
+/* The LMTP-specific version of the smtp_tls_session_cache_timeout
+/* configuration parameter.
+/* .IP "\fBsmtp_tls_loglevel (0)\fR"
+/* Enable additional Postfix SMTP client logging of TLS activity.
+/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR"
+/* Name of the file containing the optional Postfix SMTP client
+/* TLS session cache.
+/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR"
+/* The expiration time of Postfix SMTP client TLS session cache
+/* information.
+/* .IP "\fBsmtpd_tls_loglevel (0)\fR"
+/* Enable additional Postfix SMTP server logging of TLS activity.
+/* .IP "\fBsmtpd_tls_session_cache_database (empty)\fR"
+/* Name of the file containing the optional Postfix SMTP server
+/* TLS session cache.
+/* .IP "\fBsmtpd_tls_session_cache_timeout (3600s)\fR"
+/* The expiration time of Postfix SMTP server TLS session cache
+/* information.
+/* PSEUDO RANDOM NUMBER GENERATOR
+/* .ad
+/* .fi
+/* .IP "\fBtls_random_source (see 'postconf -d' output)\fR"
+/* The external entropy source for the in-memory \fBtlsmgr\fR(8) pseudo
+/* random number generator (PRNG) pool.
+/* .IP "\fBtls_random_bytes (32)\fR"
+/* The number of bytes that \fBtlsmgr\fR(8) reads from $tls_random_source
+/* when (re)seeding the in-memory pseudo random number generator (PRNG)
+/* pool.
+/* .IP "\fBtls_random_exchange_name (see 'postconf -d' output)\fR"
+/* Name of the pseudo random number generator (PRNG) state file
+/* that is maintained by \fBtlsmgr\fR(8).
+/* .IP "\fBtls_random_prng_update_period (3600s)\fR"
+/* The time between attempts by \fBtlsmgr\fR(8) to save the state of
+/* the pseudo random number generator (PRNG) to the file specified
+/* with $tls_random_exchange_name.
+/* .IP "\fBtls_random_reseed_period (3600s)\fR"
+/* The maximal time between attempts by \fBtlsmgr\fR(8) to re-seed the
+/* in-memory pseudo random number generator (PRNG) pool from external
+/* sources.
+/* 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 "\fBdata_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix-writable data files (for example:
+/* caches, pseudo-random numbers).
+/* .IP "\fBdaemon_timeout (18000s)\fR"
+/* How much time a Postfix daemon process may take to handle a
+/* request before it is terminated by a built-in watchdog timer.
+/* .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
+/* smtp(8), Postfix SMTP client
+/* smtpd(8), Postfix SMTP server
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* TLS_README, Postfix TLS configuration and operation
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* This service was introduced with Postfix version 2.2.
+/* AUTHOR(S)
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Adapted by:
+/* 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 <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/time.h> /* gettimeofday, not POSIX */
+#include <limits.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <events.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <attr.h>
+#include <set_eugid.h>
+#include <htable.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <data_redirect.h>
+
+/* Master process interface. */
+
+#include <master_proto.h>
+#include <mail_server.h>
+
+/* TLS library. */
+
+#ifdef USE_TLS
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h> /* TLS_MGR_SCACHE_<type> */
+#include <tls_prng.h>
+#include <tls_scache.h>
+
+/* Application-specific. */
+
+ /*
+ * Tunables.
+ */
+char *var_tls_rand_source;
+int var_tls_rand_bytes;
+int var_tls_reseed_period;
+int var_tls_prng_exch_period;
+char *var_smtpd_tls_loglevel;
+char *var_smtpd_tls_scache_db;
+int var_smtpd_tls_scache_timeout;
+char *var_smtp_tls_loglevel;
+char *var_smtp_tls_scache_db;
+int var_smtp_tls_scache_timeout;
+char *var_lmtp_tls_loglevel;
+char *var_lmtp_tls_scache_db;
+int var_lmtp_tls_scache_timeout;
+char *var_tls_rand_exch_name;
+
+ /*
+ * Bound the time that we are willing to wait for an I/O operation. This
+ * produces better error messages than waiting until the watchdog timer
+ * kills the process.
+ */
+#define TLS_MGR_TIMEOUT 10
+
+ /*
+ * State for updating the PRNG exchange file.
+ */
+static TLS_PRNG_SRC *rand_exch;
+
+ /*
+ * State for seeding the internal PRNG from external source.
+ */
+static TLS_PRNG_SRC *rand_source_dev;
+static TLS_PRNG_SRC *rand_source_egd;
+static TLS_PRNG_SRC *rand_source_file;
+
+ /*
+ * The external entropy source type is encoded in the source name. The
+ * obvious alternative is to have separate configuration parameters per
+ * source type, so that one process can query multiple external sources.
+ */
+#define DEV_PREF "dev:"
+#define DEV_PREF_LEN (sizeof((DEV_PREF)) - 1)
+#define DEV_PATH(dev) ((dev) + EGD_PREF_LEN)
+
+#define EGD_PREF "egd:"
+#define EGD_PREF_LEN (sizeof((EGD_PREF)) - 1)
+#define EGD_PATH(egd) ((egd) + EGD_PREF_LEN)
+
+ /*
+ * State for TLS session caches.
+ */
+typedef struct {
+ char *cache_label; /* cache short-hand name */
+ TLS_SCACHE *cache_info; /* cache handle */
+ int cache_active; /* cache status */
+ char **cache_db; /* main.cf parameter value */
+ const char *log_param; /* main.cf parameter name */
+ char **log_level; /* main.cf parameter value */
+ int *cache_timeout; /* main.cf parameter value */
+} TLSMGR_SCACHE;
+
+static TLSMGR_SCACHE cache_table[] = {
+ TLS_MGR_SCACHE_SMTPD, 0, 0, &var_smtpd_tls_scache_db,
+ VAR_SMTPD_TLS_LOGLEVEL,
+ &var_smtpd_tls_loglevel, &var_smtpd_tls_scache_timeout,
+ TLS_MGR_SCACHE_SMTP, 0, 0, &var_smtp_tls_scache_db,
+ VAR_SMTP_TLS_LOGLEVEL,
+ &var_smtp_tls_loglevel, &var_smtp_tls_scache_timeout,
+ TLS_MGR_SCACHE_LMTP, 0, 0, &var_lmtp_tls_scache_db,
+ VAR_LMTP_TLS_LOGLEVEL,
+ &var_lmtp_tls_loglevel, &var_lmtp_tls_scache_timeout,
+ 0,
+};
+
+#define smtpd_cache (cache_table[0])
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define STREQ(x, y) (strcmp((x), (y)) == 0)
+
+/* tlsmgr_prng_exch_event - update PRNG exchange file */
+
+static void tlsmgr_prng_exch_event(int unused_event, void *dummy)
+{
+ const char *myname = "tlsmgr_prng_exch_event";
+ unsigned char randbyte;
+ int next_period;
+ struct stat st;
+
+ if (msg_verbose)
+ msg_info("%s: update PRNG exchange file", myname);
+
+ /*
+ * Sanity check. If the PRNG exchange file was removed, there is no point
+ * updating it further. Restart the process and update the new file.
+ */
+ if (fstat(rand_exch->fd, &st) < 0)
+ msg_fatal("cannot fstat() the PRNG exchange file: %m");
+ if (st.st_nlink == 0) {
+ msg_warn("PRNG exchange file was removed -- exiting to reopen");
+ sleep(1);
+ exit(0);
+ }
+ tls_prng_exch_update(rand_exch);
+
+ /*
+ * Make prediction difficult for outsiders and calculate the time for the
+ * next execution randomly.
+ */
+ RAND_bytes(&randbyte, 1);
+ next_period = (var_tls_prng_exch_period * randbyte) / UCHAR_MAX;
+ event_request_timer(tlsmgr_prng_exch_event, dummy, next_period);
+}
+
+/* tlsmgr_reseed_event - re-seed the internal PRNG pool */
+
+static void tlsmgr_reseed_event(int unused_event, void *dummy)
+{
+ int next_period;
+ unsigned char randbyte;
+ int must_exit = 0;
+
+ /*
+ * Reseed the internal PRNG from external source. Errors are recoverable.
+ * We simply restart and reconnect without making a fuss. This is OK
+ * because we do require that exchange file updates succeed. The exchange
+ * file is the only entropy source that really matters in the long term.
+ *
+ * If the administrator specifies an external randomness source that we
+ * could not open upon start-up, restart to see if we can open it now
+ * (and log a nagging warning if we can't).
+ */
+ if (*var_tls_rand_source) {
+
+ /*
+ * Source is a random device.
+ */
+ if (rand_source_dev) {
+ if (tls_prng_dev_read(rand_source_dev, var_tls_rand_bytes) <= 0) {
+ msg_info("cannot read from entropy device %s: %m -- "
+ "exiting to reopen", DEV_PATH(var_tls_rand_source));
+ must_exit = 1;
+ }
+ }
+
+ /*
+ * Source is an EGD compatible socket.
+ */
+ else if (rand_source_egd) {
+ if (tls_prng_egd_read(rand_source_egd, var_tls_rand_bytes) <= 0) {
+ msg_info("lost connection to EGD server %s -- "
+ "exiting to reconnect", EGD_PATH(var_tls_rand_source));
+ must_exit = 1;
+ }
+ }
+
+ /*
+ * Source is a regular file. Read the content once and close the
+ * file.
+ */
+ else if (rand_source_file) {
+ if (tls_prng_file_read(rand_source_file, var_tls_rand_bytes) <= 0)
+ msg_warn("cannot read from entropy file %s: %m",
+ var_tls_rand_source);
+ tls_prng_file_close(rand_source_file);
+ rand_source_file = 0;
+ var_tls_rand_source[0] = 0;
+ }
+
+ /*
+ * Could not open the external source upon start-up. See if we can
+ * open it this time. Save PRNG state before we exit.
+ */
+ else {
+ msg_info("exiting to reopen external entropy source %s",
+ var_tls_rand_source);
+ must_exit = 1;
+ }
+ }
+
+ /*
+ * Save PRNG state in case we must exit.
+ */
+ if (must_exit) {
+ if (rand_exch)
+ tls_prng_exch_update(rand_exch);
+ sleep(1);
+ exit(0);
+ }
+
+ /*
+ * Make prediction difficult for outsiders and calculate the time for the
+ * next execution randomly.
+ */
+ RAND_bytes(&randbyte, 1);
+ next_period = (var_tls_reseed_period * randbyte) / UCHAR_MAX;
+ event_request_timer(tlsmgr_reseed_event, dummy, next_period);
+}
+
+/* tlsmgr_cache_run_event - start TLS session cache scan */
+
+static void tlsmgr_cache_run_event(int unused_event, void *ctx)
+{
+ const char *myname = "tlsmgr_cache_run_event";
+ TLSMGR_SCACHE *cache = (TLSMGR_SCACHE *) ctx;
+
+ /*
+ * This routine runs when it is time for another TLS session cache scan.
+ * Make sure this routine gets called again in the future.
+ *
+ * Don't start a new scan when the timer goes off while cache cleanup is
+ * still in progress.
+ */
+ if (cache->cache_info->verbose)
+ msg_info("%s: start TLS %s session cache cleanup",
+ myname, cache->cache_label);
+
+ if (cache->cache_active == 0)
+ cache->cache_active =
+ tls_scache_sequence(cache->cache_info, DICT_SEQ_FUN_FIRST,
+ TLS_SCACHE_SEQUENCE_NOTHING);
+
+ event_request_timer(tlsmgr_cache_run_event, (void *) cache,
+ cache->cache_info->timeout);
+}
+
+/* tlsmgr_key - return matching or current RFC 5077 session ticket keys */
+
+static int tlsmgr_key(VSTRING *buffer, int timeout)
+{
+ TLS_TICKET_KEY *key;
+ TLS_TICKET_KEY tmp;
+ unsigned char *name;
+ time_t now = time((time_t *) 0);
+
+ /* In tlsmgr requests we encode null key names as empty strings. */
+ name = LEN(buffer) ? (unsigned char *) STR(buffer) : 0;
+
+ /*
+ * Each key's encrypt and subsequent decrypt-only timeout is half of the
+ * total session timeout.
+ */
+ timeout /= 2;
+
+ /* Attempt to locate existing key */
+ if ((key = tls_scache_key(name, now, timeout)) == 0) {
+ if (name == 0) {
+ /* Create new encryption key */
+ if (RAND_bytes(tmp.name, TLS_TICKET_NAMELEN) <= 0
+ || RAND_bytes(tmp.bits, TLS_TICKET_KEYLEN) <= 0
+ || RAND_bytes(tmp.hmac, TLS_TICKET_MACLEN) <= 0)
+ return (TLS_MGR_STAT_ERR);
+ tmp.tout = now + timeout - 1;
+ key = tls_scache_key_rotate(&tmp);
+ } else {
+ /* No matching decryption key found */
+ return (TLS_MGR_STAT_ERR);
+ }
+ }
+ /* Return value overwrites name buffer */
+ vstring_memcpy(buffer, (char *) key, sizeof(*key));
+ return (TLS_MGR_STAT_OK);
+}
+
+/* tlsmgr_loop - TLS manager main loop */
+
+static int tlsmgr_loop(char *unused_name, char **unused_argv)
+{
+ struct timeval tv;
+ int active = 0;
+ TLSMGR_SCACHE *ent;
+
+ /*
+ * Update the PRNG pool with the time of day. We do it here after every
+ * event (including internal timer events and external client request
+ * events), instead of doing it in individual event call-back routines.
+ */
+ GETTIMEOFDAY(&tv);
+ RAND_seed(&tv, sizeof(struct timeval));
+
+ /*
+ * This routine runs as part of the event handling loop, after the event
+ * manager has delivered a timer or I/O event, or after it has waited for
+ * a specified amount of time. The result value of tlsmgr_loop()
+ * specifies how long the event manager should wait for the next event.
+ *
+ * We use this loop to interleave TLS session cache cleanup with other
+ * activity. Interleaved processing is needed when we use a client-server
+ * protocol for entropy and session state exchange with smtp(8) and
+ * smtpd(8) processes.
+ */
+#define DONT_WAIT 0
+#define WAIT_FOR_EVENT (-1)
+
+ for (ent = cache_table; ent->cache_label; ++ent) {
+ if (ent->cache_info && ent->cache_active)
+ active |= ent->cache_active =
+ tls_scache_sequence(ent->cache_info, DICT_SEQ_FUN_NEXT,
+ TLS_SCACHE_SEQUENCE_NOTHING);
+ }
+
+ return (active ? DONT_WAIT : WAIT_FOR_EVENT);
+}
+
+/* tlsmgr_request_receive - receive request */
+
+static int tlsmgr_request_receive(VSTREAM *client_stream, VSTRING *request)
+{
+ int count;
+
+ /*
+ * Kluge: choose the protocol depending on the request size.
+ */
+ if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) {
+ msg_warn("timeout while waiting for data from %s",
+ VSTREAM_PATH(client_stream));
+ return (-1);
+ }
+ if ((count = peekfd(vstream_fileno(client_stream))) < 0) {
+ msg_warn("cannot examine read buffer of %s: %m",
+ VSTREAM_PATH(client_stream));
+ return (-1);
+ }
+
+ /*
+ * Short request: master trigger. Use the string+null protocol.
+ */
+ if (count <= 2) {
+ if (vstring_get_null(request, client_stream) == VSTREAM_EOF) {
+ msg_warn("end-of-input while reading request from %s: %m",
+ VSTREAM_PATH(client_stream));
+ return (-1);
+ }
+ }
+
+ /*
+ * Long request: real tlsmgr client. Use the attribute list protocol.
+ */
+ else {
+ if (attr_scan(client_stream,
+ ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_REQ, request),
+ ATTR_TYPE_END) != 1) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+/* tlsmgr_service - respond to external request */
+
+static void tlsmgr_service(VSTREAM *client_stream, char *unused_service,
+ char **argv)
+{
+ static VSTRING *request = 0;
+ static VSTRING *cache_type = 0;
+ static VSTRING *cache_id = 0;
+ static VSTRING *buffer = 0;
+ int len;
+ static char wakeup[] = { /* master wakeup request */
+ TRIGGER_REQ_WAKEUP,
+ 0,
+ };
+ TLSMGR_SCACHE *ent;
+ int status = TLS_MGR_STAT_FAIL;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Initialize. We're select threaded, so we can use static buffers.
+ */
+ if (request == 0) {
+ request = vstring_alloc(10);
+ cache_type = vstring_alloc(10);
+ cache_id = vstring_alloc(10);
+ buffer = vstring_alloc(10);
+ }
+
+ /*
+ * This routine runs whenever a client connects to the socket dedicated
+ * to the tlsmgr service (including wake up events sent by the master).
+ * All connection-management stuff is handled by the common code in
+ * multi_server.c.
+ */
+ if (tlsmgr_request_receive(client_stream, request) == 0) {
+
+ /*
+ * Load session from cache.
+ */
+ if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END) == 2) {
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (strcmp(ent->cache_label, STR(cache_type)) == 0)
+ break;
+ if (ent->cache_label == 0) {
+ msg_warn("bogus cache type \"%s\" in \"%s\" request",
+ STR(cache_type), TLS_MGR_REQ_LOOKUP);
+ VSTRING_RESET(buffer);
+ } else if (ent->cache_info == 0) {
+
+ /*
+ * Cache type valid, but not enabled
+ */
+ VSTRING_RESET(buffer);
+ } else {
+ status = tls_scache_lookup(ent->cache_info,
+ STR(cache_id), buffer) ?
+ TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_SESSION,
+ LEN(buffer), STR(buffer)),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Save session to cache.
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_SESSION, buffer),
+ ATTR_TYPE_END) == 3) {
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (strcmp(ent->cache_label, STR(cache_type)) == 0)
+ break;
+ if (ent->cache_label == 0) {
+ msg_warn("bogus cache type \"%s\" in \"%s\" request",
+ STR(cache_type), TLS_MGR_REQ_UPDATE);
+ } else if (ent->cache_info != 0) {
+ status =
+ tls_scache_update(ent->cache_info, STR(cache_id),
+ STR(buffer), LEN(buffer)) ?
+ TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Delete session from cache.
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END) == 2) {
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (strcmp(ent->cache_label, STR(cache_type)) == 0)
+ break;
+ if (ent->cache_label == 0) {
+ msg_warn("bogus cache type \"%s\" in \"%s\" request",
+ STR(cache_type), TLS_MGR_REQ_DELETE);
+ } else if (ent->cache_info != 0) {
+ status = tls_scache_delete(ent->cache_info,
+ STR(cache_id)) ?
+ TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * RFC 5077 TLS session ticket keys
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_TKTKEY)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, buffer),
+ ATTR_TYPE_END) == 1) {
+ if (LEN(buffer) != 0 && LEN(buffer) != TLS_TICKET_NAMELEN) {
+ msg_warn("invalid session ticket key name length: %ld",
+ (long) LEN(buffer));
+ VSTRING_RESET(buffer);
+ } else if (*smtpd_cache.cache_timeout <= 0) {
+ status = TLS_MGR_STAT_ERR;
+ VSTRING_RESET(buffer);
+ } else {
+ status = tlsmgr_key(buffer, *smtpd_cache.cache_timeout);
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_KEYBUF,
+ LEN(buffer), STR(buffer)),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Entropy request.
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) {
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(TLS_MGR_ATTR_SIZE, &len),
+ ATTR_TYPE_END) == 1) {
+ VSTRING_RESET(buffer);
+ if (len <= 0 || len > 255) {
+ msg_warn("bogus seed length \"%d\" in \"%s\" request",
+ len, TLS_MGR_REQ_SEED);
+ } else {
+ VSTRING_SPACE(buffer, len);
+ RAND_bytes((unsigned char *) STR(buffer), len);
+ vstring_set_payload_size(buffer, len);
+ status = TLS_MGR_STAT_OK;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_SEED,
+ LEN(buffer), STR(buffer)),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Caching policy request.
+ */
+ else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) {
+ int cachable = 0;
+ int timeout = 0;
+
+ if (attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ ATTR_TYPE_END) == 1) {
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (strcmp(ent->cache_label, STR(cache_type)) == 0)
+ break;
+ if (ent->cache_label == 0) {
+ msg_warn("bogus cache type \"%s\" in \"%s\" request",
+ STR(cache_type), TLS_MGR_REQ_POLICY);
+ } else {
+ cachable = (ent->cache_info != 0) ? 1 : 0;
+ timeout = *ent->cache_timeout;
+ status = TLS_MGR_STAT_OK;
+ }
+ }
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_INT(TLS_MGR_ATTR_CACHABLE, cachable),
+ SEND_ATTR_INT(TLS_MGR_ATTR_SESSTOUT, timeout),
+ ATTR_TYPE_END);
+ }
+
+ /*
+ * Master trigger. Normally, these triggers arrive only after some
+ * other process requested the tlsmgr's service. The purpose is to
+ * restart the tlsmgr after it aborted due to a fatal run-time error,
+ * so that it can continue its housekeeping even while nothing is
+ * using TLS.
+ *
+ * XXX Which begs the question, if TLS isn't used often, do we need a
+ * tlsmgr background process? It could terminate when the session
+ * caches are empty.
+ */
+ else if (STREQ(STR(request), wakeup)) {
+ if (msg_verbose)
+ msg_info("received master trigger");
+ multi_server_disconnect(client_stream);
+ return; /* NOT: vstream_fflush */
+ }
+ }
+
+ /*
+ * Protocol error.
+ */
+ else {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, TLS_MGR_STAT_FAIL),
+ ATTR_TYPE_END);
+ }
+ vstream_fflush(client_stream);
+}
+
+/* tlsmgr_pre_init - pre-jail initialization */
+
+static void tlsmgr_pre_init(char *unused_name, char **unused_argv)
+{
+ char *path;
+ struct timeval tv;
+ TLSMGR_SCACHE *ent;
+ VSTRING *redirect;
+ HTABLE *dup_filter;
+ const char *dup_label;
+
+ /*
+ * If nothing else works then at least this will get us a few bits of
+ * entropy.
+ *
+ * XXX This is our first call into the OpenSSL library. We should find out
+ * if this can be moved to the post-jail initialization phase, without
+ * breaking compatibility with existing installations.
+ */
+ GETTIMEOFDAY(&tv);
+ tv.tv_sec ^= getpid();
+ RAND_seed(&tv, sizeof(struct timeval));
+
+ /*
+ * Open the external entropy source. We will not be able to open it again
+ * after we are sent to chroot jail, so we keep it open. Errors are not
+ * fatal. The exchange file (see below) is the only entropy source that
+ * really matters in the long run.
+ *
+ * Security note: we open the entropy source while privileged, but we don't
+ * access the source until after we release privileges. This way, none of
+ * the OpenSSL code gets to execute while we are privileged.
+ */
+ if (*var_tls_rand_source) {
+
+ /*
+ * Source is a random device.
+ */
+ if (!strncmp(var_tls_rand_source, DEV_PREF, DEV_PREF_LEN)) {
+ path = DEV_PATH(var_tls_rand_source);
+ rand_source_dev = tls_prng_dev_open(path, TLS_MGR_TIMEOUT);
+ if (rand_source_dev == 0)
+ msg_warn("cannot open entropy device %s: %m", path);
+ }
+
+ /*
+ * Source is an EGD compatible socket.
+ */
+ else if (!strncmp(var_tls_rand_source, EGD_PREF, EGD_PREF_LEN)) {
+ path = EGD_PATH(var_tls_rand_source);
+ rand_source_egd = tls_prng_egd_open(path, TLS_MGR_TIMEOUT);
+ if (rand_source_egd == 0)
+ msg_warn("cannot connect to EGD server %s: %m", path);
+ }
+
+ /*
+ * Source is regular file. We read this only once.
+ */
+ else {
+ rand_source_file =
+ tls_prng_file_open(var_tls_rand_source, TLS_MGR_TIMEOUT);
+ }
+ } else {
+ msg_warn("no entropy source specified with parameter %s",
+ VAR_TLS_RAND_SOURCE);
+ msg_warn("encryption keys etc. may be predictable");
+ }
+
+ /*
+ * Security: don't create root-owned files that contain untrusted data.
+ * And don't create Postfix-owned files in root-owned directories,
+ * either. We want a correct relationship between (file/directory)
+ * ownership and (file/directory) content.
+ */
+ SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid);
+ redirect = vstring_alloc(100);
+
+ /*
+ * Open the PRNG exchange file before going to jail, but don't use root
+ * privileges. Start the exchange file read/update pseudo thread after
+ * dropping privileges.
+ */
+ if (*var_tls_rand_exch_name) {
+ rand_exch =
+ tls_prng_exch_open(data_redirect_file(redirect,
+ var_tls_rand_exch_name));
+ if (rand_exch == 0)
+ msg_fatal("cannot open PRNG exchange file %s: %m",
+ var_tls_rand_exch_name);
+ }
+
+ /*
+ * Open the session cache files and discard old information before going
+ * to jail, but don't use root privilege. Start the cache maintenance
+ * pseudo threads after dropping privileges.
+ */
+ dup_filter = htable_create(sizeof(cache_table) / sizeof(cache_table[0]));
+ for (ent = cache_table; ent->cache_label; ++ent) {
+ /* Sanitize session timeout */
+ if (*ent->cache_timeout > 0) {
+ if (*ent->cache_timeout < TLS_SESSION_LIFEMIN)
+ *ent->cache_timeout = TLS_SESSION_LIFEMIN;
+ } else {
+ *ent->cache_timeout = 0;
+ }
+ /* External cache database disabled if timeout is non-positive */
+ if (*ent->cache_timeout > 0 && **ent->cache_db) {
+ if ((dup_label = htable_find(dup_filter, *ent->cache_db)) != 0)
+ msg_fatal("do not use the same TLS cache file %s for %s and %s",
+ *ent->cache_db, dup_label, ent->cache_label);
+ htable_enter(dup_filter, *ent->cache_db, ent->cache_label);
+ ent->cache_info =
+ tls_scache_open(data_redirect_map(redirect, *ent->cache_db),
+ ent->cache_label,
+ tls_log_mask(ent->log_param,
+ *ent->log_level) & TLS_LOG_CACHE,
+ *ent->cache_timeout);
+ }
+ }
+ htable_free(dup_filter, (void (*) (void *)) 0);
+
+ /*
+ * Clean up and restore privilege.
+ */
+ vstring_free(redirect);
+ RESTORE_SAVED_EUGID();
+}
+
+/* tlsmgr_post_init - post-jail initialization */
+
+static void tlsmgr_post_init(char *unused_name, char **unused_argv)
+{
+ TLSMGR_SCACHE *ent;
+
+#define NULL_EVENT (0)
+#define NULL_CONTEXT ((char *) 0)
+
+ /*
+ * This routine runs after the skeleton code has entered the chroot jail,
+ * but before any client requests are serviced. Prevent automatic process
+ * suicide after a limited number of client requests or after a limited
+ * amount of idle time.
+ */
+ var_use_limit = 0;
+ var_idle_limit = 0;
+
+ /*
+ * Start the internal PRNG re-seeding pseudo thread first.
+ */
+ if (*var_tls_rand_source) {
+ if (var_tls_reseed_period > INT_MAX / UCHAR_MAX)
+ var_tls_reseed_period = INT_MAX / UCHAR_MAX;
+ tlsmgr_reseed_event(NULL_EVENT, NULL_CONTEXT);
+ }
+
+ /*
+ * Start the exchange file read/update pseudo thread.
+ */
+ if (*var_tls_rand_exch_name) {
+ if (var_tls_prng_exch_period > INT_MAX / UCHAR_MAX)
+ var_tls_prng_exch_period = INT_MAX / UCHAR_MAX;
+ tlsmgr_prng_exch_event(NULL_EVENT, NULL_CONTEXT);
+ }
+
+ /*
+ * Start the cache maintenance pseudo threads last. Strictly speaking
+ * there is nothing to clean up after we truncate the database to zero
+ * length, but early cleanup makes verbose logging more informative (we
+ * get positive confirmation that the cleanup threads are running).
+ */
+ for (ent = cache_table; ent->cache_label; ++ent)
+ if (ent->cache_info)
+ tlsmgr_cache_run_event(NULL_EVENT, (void *) ent);
+}
+
+/* tlsmgr_post_accept - announce our protocol */
+
+static void tlsmgr_post_accept(VSTREAM *stream, char *unused_name,
+ char **unused_argv, HTABLE *unused_table)
+{
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSMGR),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+}
+
+
+/* tlsmgr_before_exit - save PRNG state before exit */
+
+static void tlsmgr_before_exit(char *unused_service_name, char **unused_argv)
+{
+
+ /*
+ * Save state before we exit after "postfix reload".
+ */
+ if (rand_exch)
+ tls_prng_exch_update(rand_exch);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TLS_RAND_SOURCE, DEF_TLS_RAND_SOURCE, &var_tls_rand_source, 0, 0,
+ VAR_TLS_RAND_EXCH_NAME, DEF_TLS_RAND_EXCH_NAME, &var_tls_rand_exch_name, 0, 0,
+ VAR_SMTPD_TLS_SCACHE_DB, DEF_SMTPD_TLS_SCACHE_DB, &var_smtpd_tls_scache_db, 0, 0,
+ VAR_SMTP_TLS_SCACHE_DB, DEF_SMTP_TLS_SCACHE_DB, &var_smtp_tls_scache_db, 0, 0,
+ VAR_LMTP_TLS_SCACHE_DB, DEF_LMTP_TLS_SCACHE_DB, &var_lmtp_tls_scache_db, 0, 0,
+ VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0,
+ VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
+ VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_lmtp_tls_loglevel, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_TLS_RESEED_PERIOD, DEF_TLS_RESEED_PERIOD, &var_tls_reseed_period, 1, 0,
+ VAR_TLS_PRNG_UPD_PERIOD, DEF_TLS_PRNG_UPD_PERIOD, &var_tls_prng_exch_period, 1, 0,
+ VAR_SMTPD_TLS_SCACHTIME, DEF_SMTPD_TLS_SCACHTIME, &var_smtpd_tls_scache_timeout, 0, MAX_SMTPD_TLS_SCACHETIME,
+ VAR_SMTP_TLS_SCACHTIME, DEF_SMTP_TLS_SCACHTIME, &var_smtp_tls_scache_timeout, 0, MAX_SMTP_TLS_SCACHETIME,
+ VAR_LMTP_TLS_SCACHTIME, DEF_LMTP_TLS_SCACHTIME, &var_lmtp_tls_scache_timeout, 0, MAX_LMTP_TLS_SCACHETIME,
+ 0,
+ };
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_TLS_RAND_BYTES, DEF_TLS_RAND_BYTES, &var_tls_rand_bytes, 1, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Use the multi service skeleton, and require that no-one else is
+ * monitoring our service port while this process runs.
+ */
+ multi_server_main(argc, argv, tlsmgr_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_PRE_INIT(tlsmgr_pre_init),
+ CA_MAIL_SERVER_POST_INIT(tlsmgr_post_init),
+ CA_MAIL_SERVER_POST_ACCEPT(tlsmgr_post_accept),
+ CA_MAIL_SERVER_EXIT(tlsmgr_before_exit),
+ CA_MAIL_SERVER_LOOP(tlsmgr_loop),
+ CA_MAIL_SERVER_SOLITARY,
+ 0);
+}
+
+#else
+
+/* tlsmgr_service - respond to external trigger(s), non-TLS version */
+
+static void tlsmgr_service(VSTREAM *unused_stream, char *unused_service,
+ char **unused_argv)
+{
+ msg_info("TLS support is not compiled in -- exiting");
+}
+
+/* main - the main program, non-TLS version */
+
+int main(int argc, char **argv)
+{
+
+ /*
+ * 200411 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 multi_server_main() to complete program initialization
+ * (including logging) and then invoke the tlsmgr_service() routine to
+ * log the message that says why this program will not run.
+ */
+ multi_server_main(argc, argv, tlsmgr_service,
+ 0);
+}
+
+#endif
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..ccb3804
--- /dev/null
+++ b/src/tlsproxy/tlsproxy.c
@@ -0,0 +1,1968 @@
+/*++
+/* 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 allowlisted (including
+/* clients whose allowlist 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.
+/* .PP
+/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+/* .IP "\fBtls_fast_shutdown_enable (yes)\fR"
+/* A workaround for implementations that hang Postfix while shutting
+/* down a TLS session, until Postfix times out.
+/* .PP
+/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+/* .IP "\fBtls_config_file (default)\fR"
+/* Optional configuration file with baseline OpenSSL settings.
+/* .IP "\fBtls_config_name (empty)\fR"
+/* The application name passed by Postfix to OpenSSL library
+/* initialization functions.
+/* 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_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
+/* Available in Postfix version 3.4-3.6:
+/* .IP "\fBtlsproxy_client_level ($smtp_tls_security_level)\fR"
+/* The default TLS security level for the Postfix \fBtlsproxy\fR(8)
+/* client.
+/* .IP "\fBtlsproxy_client_policy ($smtp_tls_policy_maps)\fR"
+/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS
+/* security policy by next-hop destination.
+/* .PP
+/* Available in Postfix version 3.7 and later:
+/* .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.
+/* 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.
+/* .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.
+/* 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 disabled 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; if 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);
+ 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);
+ 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 equivalent 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;
+ 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
+ * allowlist 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)
+{
+ TLS_APPL_STATE *appl_state;
+ VSTRING *param_buf;
+ char *param_key;
+ VSTRING *init_buf;
+ char *init_key;
+ 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.
+ */
+ param_buf = vstring_alloc(100);
+ param_key = tls_proxy_client_param_serialize(attr_print_plain, param_buf,
+ tls_params);
+ init_buf = vstring_alloc(100);
+ init_key = tls_proxy_client_init_serialize(attr_print_plain, init_buf,
+ init_props);
+ 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)) == 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,
+ (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);
+ 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.
+ *
+ * The tlsproxy server does not enforce per-request read/write deadlines or
+ * minimal data rates. Instead, the tlsproxy server relies on the
+ * tlsproxy client to enforce these context-dependent limits. When a
+ * tlsproxy client decides to time out, it will close its end of the
+ * tlsproxy stream, and the tlsproxy server will handle that immediately.
+ */
+ 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);
+ 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);
+
+ (void) attr_print(plaintext_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY),
+ ATTR_TYPE_END);
+ if (vstream_fflush(plaintext_stream) != 0)
+ msg_warn("write %s attribute: %m", MAIL_ATTR_PROTO);
+
+ /*
+ * 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;
+
+ 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);
+ if (tlsp_client_init(&tls_params, &init_props) == 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