summaryrefslogtreecommitdiffstats
path: root/src/lib-ssl-iostream
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-ssl-iostream/Makefile.am61
-rw-r--r--src/lib-ssl-iostream/Makefile.in970
-rw-r--r--src/lib-ssl-iostream/dovecot-openssl-common.c132
-rw-r--r--src/lib-ssl-iostream/dovecot-openssl-common.h16
-rw-r--r--src/lib-ssl-iostream/iostream-openssl-common.c343
-rw-r--r--src/lib-ssl-iostream/iostream-openssl-context.c755
-rw-r--r--src/lib-ssl-iostream/iostream-openssl.c946
-rw-r--r--src/lib-ssl-iostream/iostream-openssl.h129
-rw-r--r--src/lib-ssl-iostream/iostream-ssl-context-cache.c129
-rw-r--r--src/lib-ssl-iostream/iostream-ssl-private.h64
-rw-r--r--src/lib-ssl-iostream/iostream-ssl-test.c158
-rw-r--r--src/lib-ssl-iostream/iostream-ssl-test.h9
-rw-r--r--src/lib-ssl-iostream/iostream-ssl.c351
-rw-r--r--src/lib-ssl-iostream/iostream-ssl.h175
-rw-r--r--src/lib-ssl-iostream/istream-openssl.c130
-rw-r--r--src/lib-ssl-iostream/ostream-openssl.c339
-rw-r--r--src/lib-ssl-iostream/test-iostream-ssl.c559
17 files changed, 5266 insertions, 0 deletions
diff --git a/src/lib-ssl-iostream/Makefile.am b/src/lib-ssl-iostream/Makefile.am
new file mode 100644
index 0000000..5aaea5d
--- /dev/null
+++ b/src/lib-ssl-iostream/Makefile.am
@@ -0,0 +1,61 @@
+noinst_LTLIBRARIES = libssl_iostream.la
+
+NOPLUGIN_LDFLAGS =
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -DMODULE_DIR=\""$(moduledir)"\"
+
+if BUILD_OPENSSL
+module_LTLIBRARIES = libssl_iostream_openssl.la
+
+libssl_iostream_openssl_la_LDFLAGS = -module -avoid-version
+libssl_iostream_openssl_la_LIBADD = $(SSL_LIBS)
+libssl_iostream_openssl_la_SOURCES = \
+ dovecot-openssl-common.c \
+ iostream-openssl.c \
+ iostream-openssl-common.c \
+ iostream-openssl-context.c \
+ istream-openssl.c \
+ ostream-openssl.c
+endif
+
+libssl_iostream_la_SOURCES = \
+ iostream-ssl.c \
+ iostream-ssl-context-cache.c \
+ iostream-ssl-test.c
+
+noinst_HEADERS = \
+ dovecot-openssl-common.h
+
+headers = \
+ iostream-openssl.h \
+ iostream-ssl.h \
+ iostream-ssl-private.h \
+ iostream-ssl-test.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+if BUILD_OPENSSL
+test_libs = \
+ $(module_LTLIBRARIES) \
+ $(noinst_LTLIBRARIES) \
+ ../lib-test/libtest.la \
+ ../lib/liblib.la
+
+test_iostream_ssl_SOURCES = test-iostream-ssl.c
+test_iostream_ssl_LDADD = $(test_libs) $(SSL_LIBS) $(DLLIB)
+test_iostream_ssl_DEPENDENCIES = $(test_libs)
+
+test_programs = \
+ test-iostream-ssl
+
+noinst_PROGRAMS = $(test_programs)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+endif
diff --git a/src/lib-ssl-iostream/Makefile.in b/src/lib-ssl-iostream/Makefile.in
new file mode 100644
index 0000000..3fff60f
--- /dev/null
+++ b/src/lib-ssl-iostream/Makefile.in
@@ -0,0 +1,970 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@BUILD_OPENSSL_TRUE@noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/lib-ssl-iostream
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@BUILD_OPENSSL_TRUE@am__EXEEXT_1 = test-iostream-ssl$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(module_LTLIBRARIES) $(noinst_LTLIBRARIES)
+libssl_iostream_la_LIBADD =
+am_libssl_iostream_la_OBJECTS = iostream-ssl.lo \
+ iostream-ssl-context-cache.lo iostream-ssl-test.lo
+libssl_iostream_la_OBJECTS = $(am_libssl_iostream_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am__DEPENDENCIES_1 =
+@BUILD_OPENSSL_TRUE@libssl_iostream_openssl_la_DEPENDENCIES = \
+@BUILD_OPENSSL_TRUE@ $(am__DEPENDENCIES_1)
+am__libssl_iostream_openssl_la_SOURCES_DIST = \
+ dovecot-openssl-common.c iostream-openssl.c \
+ iostream-openssl-common.c iostream-openssl-context.c \
+ istream-openssl.c ostream-openssl.c
+@BUILD_OPENSSL_TRUE@am_libssl_iostream_openssl_la_OBJECTS = \
+@BUILD_OPENSSL_TRUE@ dovecot-openssl-common.lo \
+@BUILD_OPENSSL_TRUE@ iostream-openssl.lo \
+@BUILD_OPENSSL_TRUE@ iostream-openssl-common.lo \
+@BUILD_OPENSSL_TRUE@ iostream-openssl-context.lo \
+@BUILD_OPENSSL_TRUE@ istream-openssl.lo ostream-openssl.lo
+libssl_iostream_openssl_la_OBJECTS = \
+ $(am_libssl_iostream_openssl_la_OBJECTS)
+libssl_iostream_openssl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libssl_iostream_openssl_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+@BUILD_OPENSSL_TRUE@am_libssl_iostream_openssl_la_rpath = -rpath \
+@BUILD_OPENSSL_TRUE@ $(moduledir)
+am__test_iostream_ssl_SOURCES_DIST = test-iostream-ssl.c
+@BUILD_OPENSSL_TRUE@am_test_iostream_ssl_OBJECTS = \
+@BUILD_OPENSSL_TRUE@ test-iostream-ssl.$(OBJEXT)
+test_iostream_ssl_OBJECTS = $(am_test_iostream_ssl_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/dovecot-openssl-common.Plo \
+ ./$(DEPDIR)/iostream-openssl-common.Plo \
+ ./$(DEPDIR)/iostream-openssl-context.Plo \
+ ./$(DEPDIR)/iostream-openssl.Plo \
+ ./$(DEPDIR)/iostream-ssl-context-cache.Plo \
+ ./$(DEPDIR)/iostream-ssl-test.Plo ./$(DEPDIR)/iostream-ssl.Plo \
+ ./$(DEPDIR)/istream-openssl.Plo \
+ ./$(DEPDIR)/ostream-openssl.Plo \
+ ./$(DEPDIR)/test-iostream-ssl.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libssl_iostream_la_SOURCES) \
+ $(libssl_iostream_openssl_la_SOURCES) \
+ $(test_iostream_ssl_SOURCES)
+DIST_SOURCES = $(libssl_iostream_la_SOURCES) \
+ $(am__libssl_iostream_openssl_la_SOURCES_DIST) \
+ $(am__test_iostream_ssl_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libssl_iostream.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -DMODULE_DIR=\""$(moduledir)"\"
+
+@BUILD_OPENSSL_TRUE@module_LTLIBRARIES = libssl_iostream_openssl.la
+@BUILD_OPENSSL_TRUE@libssl_iostream_openssl_la_LDFLAGS = -module -avoid-version
+@BUILD_OPENSSL_TRUE@libssl_iostream_openssl_la_LIBADD = $(SSL_LIBS)
+@BUILD_OPENSSL_TRUE@libssl_iostream_openssl_la_SOURCES = \
+@BUILD_OPENSSL_TRUE@ dovecot-openssl-common.c \
+@BUILD_OPENSSL_TRUE@ iostream-openssl.c \
+@BUILD_OPENSSL_TRUE@ iostream-openssl-common.c \
+@BUILD_OPENSSL_TRUE@ iostream-openssl-context.c \
+@BUILD_OPENSSL_TRUE@ istream-openssl.c \
+@BUILD_OPENSSL_TRUE@ ostream-openssl.c
+
+libssl_iostream_la_SOURCES = \
+ iostream-ssl.c \
+ iostream-ssl-context-cache.c \
+ iostream-ssl-test.c
+
+noinst_HEADERS = \
+ dovecot-openssl-common.h
+
+headers = \
+ iostream-openssl.h \
+ iostream-ssl.h \
+ iostream-ssl-private.h \
+ iostream-ssl-test.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+@BUILD_OPENSSL_TRUE@test_libs = \
+@BUILD_OPENSSL_TRUE@ $(module_LTLIBRARIES) \
+@BUILD_OPENSSL_TRUE@ $(noinst_LTLIBRARIES) \
+@BUILD_OPENSSL_TRUE@ ../lib-test/libtest.la \
+@BUILD_OPENSSL_TRUE@ ../lib/liblib.la
+
+@BUILD_OPENSSL_TRUE@test_iostream_ssl_SOURCES = test-iostream-ssl.c
+@BUILD_OPENSSL_TRUE@test_iostream_ssl_LDADD = $(test_libs) $(SSL_LIBS) $(DLLIB)
+@BUILD_OPENSSL_TRUE@test_iostream_ssl_DEPENDENCIES = $(test_libs)
+@BUILD_OPENSSL_TRUE@test_programs = \
+@BUILD_OPENSSL_TRUE@ test-iostream-ssl
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-ssl-iostream/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-ssl-iostream/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libssl_iostream.la: $(libssl_iostream_la_OBJECTS) $(libssl_iostream_la_DEPENDENCIES) $(EXTRA_libssl_iostream_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libssl_iostream_la_OBJECTS) $(libssl_iostream_la_LIBADD) $(LIBS)
+
+libssl_iostream_openssl.la: $(libssl_iostream_openssl_la_OBJECTS) $(libssl_iostream_openssl_la_DEPENDENCIES) $(EXTRA_libssl_iostream_openssl_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libssl_iostream_openssl_la_LINK) $(am_libssl_iostream_openssl_la_rpath) $(libssl_iostream_openssl_la_OBJECTS) $(libssl_iostream_openssl_la_LIBADD) $(LIBS)
+
+test-iostream-ssl$(EXEEXT): $(test_iostream_ssl_OBJECTS) $(test_iostream_ssl_DEPENDENCIES) $(EXTRA_test_iostream_ssl_DEPENDENCIES)
+ @rm -f test-iostream-ssl$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_iostream_ssl_OBJECTS) $(test_iostream_ssl_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dovecot-openssl-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-openssl-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-openssl-context.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-openssl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-ssl-context-cache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-ssl-test.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-ssl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-openssl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-openssl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-iostream-ssl.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+@BUILD_OPENSSL_FALSE@check-local:
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/dovecot-openssl-common.Plo
+ -rm -f ./$(DEPDIR)/iostream-openssl-common.Plo
+ -rm -f ./$(DEPDIR)/iostream-openssl-context.Plo
+ -rm -f ./$(DEPDIR)/iostream-openssl.Plo
+ -rm -f ./$(DEPDIR)/iostream-ssl-context-cache.Plo
+ -rm -f ./$(DEPDIR)/iostream-ssl-test.Plo
+ -rm -f ./$(DEPDIR)/iostream-ssl.Plo
+ -rm -f ./$(DEPDIR)/istream-openssl.Plo
+ -rm -f ./$(DEPDIR)/ostream-openssl.Plo
+ -rm -f ./$(DEPDIR)/test-iostream-ssl.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/dovecot-openssl-common.Plo
+ -rm -f ./$(DEPDIR)/iostream-openssl-common.Plo
+ -rm -f ./$(DEPDIR)/iostream-openssl-context.Plo
+ -rm -f ./$(DEPDIR)/iostream-openssl.Plo
+ -rm -f ./$(DEPDIR)/iostream-ssl-context-cache.Plo
+ -rm -f ./$(DEPDIR)/iostream-ssl-test.Plo
+ -rm -f ./$(DEPDIR)/iostream-ssl.Plo
+ -rm -f ./$(DEPDIR)/istream-openssl.Plo
+ -rm -f ./$(DEPDIR)/ostream-openssl.Plo
+ -rm -f ./$(DEPDIR)/test-iostream-ssl.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+@BUILD_OPENSSL_TRUE@check-local:
+@BUILD_OPENSSL_TRUE@ for bin in $(test_programs); do \
+@BUILD_OPENSSL_TRUE@ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+@BUILD_OPENSSL_TRUE@ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-ssl-iostream/dovecot-openssl-common.c b/src/lib-ssl-iostream/dovecot-openssl-common.c
new file mode 100644
index 0000000..76f98bc
--- /dev/null
+++ b/src/lib-ssl-iostream/dovecot-openssl-common.c
@@ -0,0 +1,132 @@
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "randgen.h"
+#include "dovecot-openssl-common.h"
+
+#include <openssl/ssl.h>
+#include <openssl/engine.h>
+#include <openssl/rand.h>
+
+static int openssl_init_refcount = 0;
+static ENGINE *dovecot_openssl_engine;
+
+#ifdef HAVE_SSL_NEW_MEM_FUNCS
+static void *dovecot_openssl_malloc(size_t size, const char *u0 ATTR_UNUSED, int u1 ATTR_UNUSED)
+#else
+static void *dovecot_openssl_malloc(size_t size)
+#endif
+{
+ /* this may be performance critical, so don't use
+ i_malloc() or calloc() */
+ void *mem = malloc(size);
+ if (mem == NULL) {
+ i_fatal_status(FATAL_OUTOFMEM,
+ "OpenSSL: malloc(%zu): Out of memory", size);
+ }
+ return mem;
+}
+
+#ifdef HAVE_SSL_NEW_MEM_FUNCS
+static void *dovecot_openssl_realloc(void *ptr, size_t size, const char *u0 ATTR_UNUSED, int u1 ATTR_UNUSED)
+#else
+static void *dovecot_openssl_realloc(void *ptr, size_t size)
+#endif
+{
+ void *mem = realloc(ptr, size);
+ if (mem == NULL) {
+ i_fatal_status(FATAL_OUTOFMEM,
+ "OpenSSL: realloc(%zu): Out of memory", size);
+ }
+ return mem;
+}
+
+#ifdef HAVE_SSL_NEW_MEM_FUNCS
+static void dovecot_openssl_free(void *ptr, const char *u0 ATTR_UNUSED, int u1 ATTR_UNUSED)
+#else
+static void dovecot_openssl_free(void *ptr)
+#endif
+{
+ free(ptr);
+}
+
+void dovecot_openssl_common_global_ref(void)
+{
+ if (openssl_init_refcount++ > 0)
+ return;
+
+ /* use our own memory allocation functions that will die instead of
+ returning NULL. this avoids random failures on out-of-memory
+ conditions. */
+ if (CRYPTO_set_mem_functions(dovecot_openssl_malloc,
+ dovecot_openssl_realloc, dovecot_openssl_free) == 0) {
+ /*i_warning("CRYPTO_set_mem_functions() was called too late");*/
+ }
+
+ SSL_library_init();
+ SSL_load_error_strings();
+ OpenSSL_add_all_algorithms();
+}
+
+bool dovecot_openssl_common_global_unref(void)
+{
+ i_assert(openssl_init_refcount > 0);
+
+ if (--openssl_init_refcount > 0)
+ return TRUE;
+
+ if (dovecot_openssl_engine != NULL) {
+ ENGINE_finish(dovecot_openssl_engine);
+ dovecot_openssl_engine = NULL;
+ }
+ /* OBJ_cleanup() is called automatically by EVP_cleanup() in
+ newer versions. Doesn't hurt to call it anyway. */
+ OBJ_cleanup();
+#ifdef HAVE_SSL_COMP_FREE_COMPRESSION_METHODS
+ SSL_COMP_free_compression_methods();
+#endif
+ ENGINE_cleanup();
+ EVP_cleanup();
+ CRYPTO_cleanup_all_ex_data();
+#ifdef HAVE_OPENSSL_AUTO_THREAD_DEINIT
+ /* no cleanup needed */
+#elif defined(HAVE_OPENSSL_ERR_REMOVE_THREAD_STATE)
+ /* This was marked as deprecated in v1.1. */
+ ERR_remove_thread_state(NULL);
+#else
+ /* This was deprecated by ERR_remove_thread_state(NULL) in v1.0.0. */
+ ERR_remove_state(0);
+#endif
+ ERR_free_strings();
+#ifdef HAVE_OPENSSL_CLEANUP
+ OPENSSL_cleanup();
+#endif
+ return FALSE;
+}
+
+int dovecot_openssl_common_global_set_engine(const char *engine,
+ const char **error_r)
+{
+ if (dovecot_openssl_engine != NULL)
+ return 1;
+
+ ENGINE_load_builtin_engines();
+ dovecot_openssl_engine = ENGINE_by_id(engine);
+ if (dovecot_openssl_engine == NULL) {
+ *error_r = t_strdup_printf("Unknown engine '%s'", engine);
+ return 0;
+ }
+ if (ENGINE_init(dovecot_openssl_engine) == 0) {
+ *error_r = t_strdup_printf("ENGINE_init(%s) failed", engine);
+ ENGINE_free(dovecot_openssl_engine);
+ dovecot_openssl_engine = NULL;
+ return -1;
+ }
+ if (ENGINE_set_default(dovecot_openssl_engine, ENGINE_METHOD_ALL) == 0) {
+ *error_r = t_strdup_printf("ENGINE_set_default(%s) failed", engine);
+ ENGINE_free(dovecot_openssl_engine);
+ dovecot_openssl_engine = NULL;
+ return -1;
+ }
+ return 1;
+}
diff --git a/src/lib-ssl-iostream/dovecot-openssl-common.h b/src/lib-ssl-iostream/dovecot-openssl-common.h
new file mode 100644
index 0000000..31854d3
--- /dev/null
+++ b/src/lib-ssl-iostream/dovecot-openssl-common.h
@@ -0,0 +1,16 @@
+#ifndef DOVECOT_OPENSSL_COMMON_H
+#define DOVECOT_OPENSSL_COMMON_H
+
+/* Initialize OpenSSL if this is the first instance.
+ Increase initialization reference count. */
+void dovecot_openssl_common_global_ref(void);
+/* Deinitialize OpenSSL if this is the last instance. Returns TRUE if there
+ are more instances left. */
+bool dovecot_openssl_common_global_unref(void);
+
+/* Set OpenSSL engine if it's not already set. Returns 1 on success, 0 if engine
+ is unknown, -1 on other error. error_r is set on 0/-1. */
+int dovecot_openssl_common_global_set_engine(const char *engine,
+ const char **error_r);
+
+#endif
diff --git a/src/lib-ssl-iostream/iostream-openssl-common.c b/src/lib-ssl-iostream/iostream-openssl-common.c
new file mode 100644
index 0000000..04dc5ea
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-openssl-common.c
@@ -0,0 +1,343 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "net.h"
+#include "str.h"
+#include "iostream-openssl.h"
+
+#include <openssl/x509v3.h>
+#include <openssl/err.h>
+#include <arpa/inet.h>
+
+/* openssl_min_protocol_to_options() scans this array for name and returns
+ version and opt. opt is used with SSL_set_options() and version is used with
+ SSL_set_min_proto_version(). Using either method should enable the same
+ SSL protocol versions. */
+static const struct {
+ const char *name;
+ int version;
+ long opt;
+} protocol_versions[] = {
+#ifdef TLS_ANY_VERSION
+ { "ANY", TLS_ANY_VERSION, 0 },
+#else
+ { "ANY", SSL3_VERSION, 0 },
+#endif
+ { SSL_TXT_SSLV3, SSL3_VERSION, 0 },
+ { SSL_TXT_TLSV1, TLS1_VERSION, SSL_OP_NO_SSLv3 },
+ { SSL_TXT_TLSV1_1, TLS1_1_VERSION, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 },
+ { SSL_TXT_TLSV1_2, TLS1_2_VERSION,
+ SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 },
+#if defined(TLS1_3_VERSION)
+ { "TLSv1.3", TLS1_3_VERSION,
+ SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 |
+ SSL_OP_NO_TLSv1_2 },
+#endif
+ /* Use latest protocol version. If this is used on some
+ ancient system which does not support ssl_min_protocol,
+ ensure only TLSv1.2 is supported. */
+#ifdef TLS_MAX_VERSION
+ { "LATEST", TLS_MAX_VERSION,
+#else
+ { "LATEST", 0,
+#endif
+ SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 },
+};
+int openssl_min_protocol_to_options(const char *min_protocol, long *opt_r,
+ int *version_r)
+{
+ unsigned i = 0;
+ for (; i < N_ELEMENTS(protocol_versions); i++) {
+ if (strcasecmp(protocol_versions[i].name, min_protocol) == 0)
+ break;
+ }
+ if (i >= N_ELEMENTS(protocol_versions))
+ return -1;
+
+ if (opt_r != NULL)
+ *opt_r = protocol_versions[i].opt;
+ if (version_r != NULL)
+ *version_r = protocol_versions[i].version;
+ return 0;
+}
+
+#if !defined(HAVE_X509_CHECK_HOST) || !defined(HAVE_X509_CHECK_IP_ASC)
+static const char *asn1_string_to_c(ASN1_STRING *asn_str)
+{
+ const char *cstr;
+ unsigned int len;
+
+ len = ASN1_STRING_length(asn_str);
+ cstr = t_strndup(ASN1_STRING_get0_data(asn_str), len);
+ if (strlen(cstr) != len) {
+ /* NULs in the name - could be some MITM attack.
+ never allow. */
+ return "";
+ }
+ return cstr;
+}
+
+static const char *get_general_dns_name(const GENERAL_NAME *name)
+{
+ if (ASN1_STRING_type(name->d.ia5) != V_ASN1_IA5STRING)
+ return "";
+
+ return asn1_string_to_c(name->d.ia5);
+}
+
+static int get_general_ip_addr(const GENERAL_NAME *name, struct ip_addr *ip_r)
+{
+ if (ASN1_STRING_type(name->d.ip) != V_ASN1_OCTET_STRING)
+ return 0;
+ const unsigned char *data = ASN1_STRING_get0_data(name->d.ip);
+
+ if (name->d.ip->length == sizeof(ip_r->u.ip4.s_addr)) {
+ ip_r->family = AF_INET;
+ memcpy(&ip_r->u.ip4.s_addr, data, sizeof(ip_r->u.ip4.s_addr));
+ } else if (name->d.ip->length == sizeof(ip_r->u.ip6.s6_addr)) {
+ ip_r->family = AF_INET6;
+ memcpy(ip_r->u.ip6.s6_addr, data, sizeof(ip_r->u.ip6.s6_addr));
+ } else
+ return -1;
+ return 0;
+}
+
+static const char *get_cname(X509 *cert)
+{
+ X509_NAME *name;
+ X509_NAME_ENTRY *entry;
+ ASN1_STRING *str;
+ int cn_idx;
+
+ name = X509_get_subject_name(cert);
+ if (name == NULL)
+ return "";
+ cn_idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+ if (cn_idx == -1)
+ return "";
+ entry = X509_NAME_get_entry(name, cn_idx);
+ i_assert(entry != NULL);
+ str = X509_NAME_ENTRY_get_data(entry);
+ i_assert(str != NULL);
+ return asn1_string_to_c(str);
+}
+
+static bool openssl_hostname_equals(const char *ssl_name, const char *host)
+{
+ const char *p;
+
+ if (strcasecmp(ssl_name, host) == 0)
+ return TRUE;
+
+ /* check for *.example.com wildcard */
+ if (ssl_name[0] != '*' || ssl_name[1] != '.')
+ return FALSE;
+ p = strchr(host, '.');
+ return p != NULL && strcasecmp(ssl_name+2, p+1) == 0;
+}
+#endif
+
+bool openssl_cert_match_name(SSL *ssl, const char *verify_name,
+ const char **reason_r)
+{
+ X509 *cert;
+ bool ret;
+
+ *reason_r = NULL;
+
+ cert = SSL_get_peer_certificate(ssl);
+ i_assert(cert != NULL);
+
+#if defined(HAVE_X509_CHECK_HOST) && defined(HAVE_X509_CHECK_IP_ASC)
+ char *peername;
+ int check_res;
+
+ /* First check DNS name agains CommonName or SubjectAltNames.
+ If failed, check IP addresses. */
+ if ((check_res = X509_check_host(cert, verify_name, strlen(verify_name),
+ X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS,
+ &peername)) == 1) {
+ *reason_r = t_strdup_printf("Matched to %s", peername);
+ free(peername);
+ ret = TRUE;
+ } else if (check_res == 0 &&
+ (check_res = X509_check_ip_asc(cert, verify_name, 0)) == 1) {
+ *reason_r = t_strdup_printf("Matched to IP address %s", verify_name);
+ ret = TRUE;
+ } else if (check_res == 0) {
+ *reason_r = "did not match to any IP or DNS fields";
+ ret = FALSE;
+ } else {
+ *reason_r = "Malformed input";
+ ret = FALSE;
+ }
+#else
+ STACK_OF(GENERAL_NAME) *gnames;
+ const GENERAL_NAME *gn;
+ struct ip_addr ip;
+ const char *dnsname;
+ bool dns_names = FALSE;
+ unsigned int i, count;
+
+ /* verify against SubjectAltNames */
+ gnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+ count = gnames == NULL ? 0 : sk_GENERAL_NAME_num(gnames);
+
+ i_zero(&ip);
+ /* try to convert verify_name to IP */
+ if (inet_pton(AF_INET6, verify_name, &ip.u.ip6) == 1)
+ ip.family = AF_INET6;
+ else if (inet_pton(AF_INET, verify_name, &ip.u.ip4) == 1)
+ ip.family = AF_INET;
+ else
+ i_zero(&ip);
+
+ for (i = 0; i < count; i++) {
+ gn = sk_GENERAL_NAME_value(gnames, i);
+
+ if (gn->type == GEN_DNS) {
+ dns_names = TRUE;
+ dnsname = get_general_dns_name(gn);
+ if (openssl_hostname_equals(dnsname, verify_name)) {
+ *reason_r = t_strdup_printf(
+ "Matches DNS name in SubjectAltNames: %s", dnsname);
+ break;
+ }
+ } else if (gn->type == GEN_IPADD) {
+ struct ip_addr ip_2;
+ i_zero(&ip_2);
+ dns_names = TRUE;
+ if (get_general_ip_addr(gn, &ip_2) == 0 &&
+ net_ip_compare(&ip, &ip_2)) {
+ *reason_r = t_strdup_printf(
+ "Matches IP in SubjectAltNames: %s", net_ip2addr(&ip_2));
+ break;
+ }
+ }
+ }
+ sk_GENERAL_NAME_pop_free(gnames, GENERAL_NAME_free);
+
+ /* verify against CommonName only when there wasn't any DNS
+ SubjectAltNames */
+ if (dns_names) {
+ i_assert(*reason_r != NULL || i == count);
+ if (i == count) {
+ *reason_r = t_strdup_printf(
+ "No match to %u SubjectAltNames",
+ count);
+ ret = FALSE;
+ } else {
+ ret = TRUE;
+ }
+ } else {
+ const char *cname = get_cname(cert);
+
+ if (openssl_hostname_equals(cname, verify_name)) {
+ ret = TRUE;
+ *reason_r = t_strdup_printf(
+ "Matches to CommonName: %s", cname);
+ } else {
+ *reason_r = t_strdup_printf(
+ "No match to CommonName=%s or %u SubjectAltNames",
+ cname, count);
+ ret = FALSE;
+ }
+ }
+#endif
+ X509_free(cert);
+ return ret;
+}
+
+static const char *ssl_err2str(unsigned long err, const char *data, int flags)
+{
+ const char *ret;
+ char *buf;
+ size_t err_size = 256;
+
+ buf = t_malloc0(err_size);
+ ERR_error_string_n(err, buf, err_size-1);
+ ret = buf;
+
+ if ((flags & ERR_TXT_STRING) != 0)
+ ret = t_strdup_printf("%s: %s", buf, data);
+ return ret;
+}
+
+const char *openssl_iostream_error(void)
+{
+ string_t *errstr = NULL;
+ unsigned long err;
+ const char *data, *final_error;
+ int flags;
+
+ while ((err = ERR_get_error_line_data(NULL, NULL, &data, &flags)) != 0) {
+ if (ERR_GET_REASON(err) == ERR_R_MALLOC_FAILURE)
+ i_fatal_status(FATAL_OUTOFMEM, "OpenSSL malloc() failed");
+ if (ERR_peek_error() == 0)
+ break;
+ if (errstr == NULL)
+ errstr = t_str_new(128);
+ else
+ str_append(errstr, ", ");
+ str_append(errstr, ssl_err2str(err, data, flags));
+ }
+ if (err == 0) {
+ if (errno != 0)
+ final_error = strerror(errno);
+ else
+ final_error = "Unknown error";
+ } else {
+ final_error = ssl_err2str(err, data, flags);
+ }
+ if (errstr == NULL)
+ return final_error;
+ else {
+ str_printfa(errstr, ", %s", final_error);
+ return str_c(errstr);
+ }
+}
+
+const char *openssl_iostream_key_load_error(void)
+{
+ unsigned long err = ERR_peek_error();
+
+ if (ERR_GET_LIB(err) == ERR_LIB_X509 &&
+ ERR_GET_REASON(err) == X509_R_KEY_VALUES_MISMATCH)
+ return "Key is for a different cert than ssl_cert";
+ else
+ return openssl_iostream_error();
+}
+
+static bool is_pem_key(const char *cert)
+{
+ return strstr(cert, "PRIVATE KEY---") != NULL;
+}
+
+const char *
+openssl_iostream_use_certificate_error(const char *cert, const char *set_name)
+{
+ unsigned long err;
+
+ if (cert[0] == '\0')
+ return "The certificate is empty";
+
+ err = ERR_peek_error();
+ if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
+ ERR_GET_REASON(err) != PEM_R_NO_START_LINE)
+ return openssl_iostream_error();
+ else if (is_pem_key(cert)) {
+ return "The file contains a private key "
+ "(you've mixed ssl_cert and ssl_key settings)";
+ } else if (set_name != NULL && strchr(cert, '\n') == NULL) {
+ return t_strdup_printf("There is no valid PEM certificate. "
+ "(You probably forgot '<' from %s=<%s)", set_name, cert);
+ } else {
+ return "There is no valid PEM certificate.";
+ }
+}
+
+void openssl_iostream_clear_errors(void)
+{
+ while (ERR_get_error() != 0)
+ ;
+}
diff --git a/src/lib-ssl-iostream/iostream-openssl-context.c b/src/lib-ssl-iostream/iostream-openssl-context.c
new file mode 100644
index 0000000..fe9b059
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-openssl-context.c
@@ -0,0 +1,755 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "safe-memset.h"
+#include "iostream-openssl.h"
+#include "dovecot-openssl-common.h"
+
+#include <openssl/crypto.h>
+#include <openssl/rsa.h>
+#include <openssl/dh.h>
+#include <openssl/bn.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x10000000L
+# define HAVE_ECDH
+#endif
+
+struct ssl_iostream_password_context {
+ const char *password;
+ const char *error;
+};
+
+static bool ssl_global_initialized = FALSE;
+int dovecot_ssl_extdata_index;
+
+static RSA *ssl_gen_rsa_key(SSL *ssl ATTR_UNUSED,
+ int is_export ATTR_UNUSED, int keylength)
+{
+#ifdef HAVE_RSA_GENERATE_KEY_EX
+ BIGNUM *bn = BN_new();
+ RSA *rsa = RSA_new();
+
+ if (bn != NULL && BN_set_word(bn, RSA_F4) != 0 &&
+ RSA_generate_key_ex(rsa, keylength, bn, NULL) != 0) {
+ BN_free(bn);
+ return rsa;
+ }
+
+ if (bn != NULL)
+ BN_free(bn);
+ if (rsa != NULL)
+ RSA_free(rsa);
+ return NULL;
+#else
+ return RSA_generate_key(keylength, RSA_F4, NULL, NULL);
+#endif
+}
+
+static DH *ssl_tmp_dh_callback(SSL *ssl ATTR_UNUSED,
+ int is_export ATTR_UNUSED, int keylength ATTR_UNUSED)
+{
+ i_error("Diffie-Hellman key exchange requested, "
+ "but no DH parameters provided. Set ssl_dh=</path/to/dh.pem");
+ return NULL;
+}
+
+static int
+pem_password_callback(char *buf, int size, int rwflag ATTR_UNUSED,
+ void *userdata)
+{
+ struct ssl_iostream_password_context *ctx = userdata;
+
+ if (ctx->password == NULL) {
+ ctx->error = "SSL private key file is password protected, "
+ "but password isn't given";
+ return 0;
+ }
+
+ if (i_strocpy(buf, ctx->password, size) < 0) {
+ ctx->error = "SSL private key password is too long";
+ return 0;
+ }
+ return strlen(buf);
+}
+
+int openssl_iostream_load_key(const struct ssl_iostream_cert *set,
+ const char *set_name,
+ EVP_PKEY **pkey_r, const char **error_r)
+{
+ struct ssl_iostream_password_context ctx;
+ EVP_PKEY *pkey;
+ BIO *bio;
+ char *key;
+
+ key = t_strdup_noconst(set->key);
+ bio = BIO_new_mem_buf(key, strlen(key));
+ if (bio == NULL) {
+ *error_r = t_strdup_printf("BIO_new_mem_buf() failed: %s",
+ openssl_iostream_error());
+ safe_memset(key, 0, strlen(key));
+ return -1;
+ }
+
+ ctx.password = set->key_password;
+ ctx.error = NULL;
+
+ pkey = PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback, &ctx);
+ if (pkey == NULL && ctx.error == NULL) {
+ ctx.error = t_strdup_printf(
+ "Couldn't parse private SSL key (%s setting)%s: %s",
+ set_name,
+ ctx.password != NULL ?
+ " (maybe ssl_key_password is wrong?)" :
+ "",
+ openssl_iostream_error());
+ }
+ BIO_free(bio);
+
+ safe_memset(key, 0, strlen(key));
+ *pkey_r = pkey;
+ *error_r = ctx.error;
+ return pkey == NULL ? -1 : 0;
+}
+
+static
+int openssl_iostream_load_dh(const struct ssl_iostream_settings *set,
+ DH **dh_r, const char **error_r)
+{
+ DH *dh;
+ BIO *bio;
+ char *dhvalue;
+
+ dhvalue = t_strdup_noconst(set->dh);
+ bio = BIO_new_mem_buf(dhvalue, strlen(dhvalue));
+
+ if (bio == NULL) {
+ *error_r = t_strdup_printf("BIO_new_mem_buf() failed: %s",
+ openssl_iostream_error());
+ return -1;
+ }
+
+ dh = NULL;
+ dh = PEM_read_bio_DHparams(bio, &dh, NULL, NULL);
+
+ if (dh == NULL) {
+ *error_r = t_strdup_printf("Couldn't parse DH parameters: %s",
+ openssl_iostream_error());
+ }
+ BIO_free(bio);
+ *dh_r = dh;
+ return dh == NULL ? -1 : 0;
+}
+
+static int
+ssl_iostream_ctx_use_key(struct ssl_iostream_context *ctx, const char *set_name,
+ const struct ssl_iostream_cert *set,
+ const char **error_r)
+{
+ EVP_PKEY *pkey;
+ int ret = 0;
+
+ if (openssl_iostream_load_key(set, set_name, &pkey, error_r) < 0)
+ return -1;
+ if (SSL_CTX_use_PrivateKey(ctx->ssl_ctx, pkey) == 0) {
+ *error_r = t_strdup_printf(
+ "Can't load SSL private key (%s setting): %s",
+ set_name, openssl_iostream_key_load_error());
+ ret = -1;
+ }
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+static int
+ssl_iostream_ctx_use_dh(struct ssl_iostream_context *ctx,
+ const struct ssl_iostream_settings *set,
+ const char **error_r)
+{
+ DH *dh;
+ int ret = 0;
+ if (*set->dh == '\0') {
+ return 0;
+ }
+ if (openssl_iostream_load_dh(set, &dh, error_r) < 0)
+ return -1;
+ if (SSL_CTX_set_tmp_dh(ctx->ssl_ctx, dh) == 0) {
+ *error_r = t_strdup_printf(
+ "Can't load DH parameters (ssl_dh setting): %s",
+ openssl_iostream_key_load_error());
+ ret = -1;
+ }
+ DH_free(dh);
+ return ret;
+}
+
+static int ssl_ctx_use_certificate_chain(SSL_CTX *ctx, const char *cert)
+{
+ /* mostly just copy&pasted from SSL_CTX_use_certificate_chain_file() */
+ BIO *in;
+ X509 *x;
+ int ret = 0;
+
+ in = BIO_new_mem_buf(t_strdup_noconst(cert), strlen(cert));
+ if (in == NULL)
+ i_fatal("BIO_new_mem_buf() failed");
+
+ x = PEM_read_bio_X509(in, NULL, NULL, NULL);
+ if (x == NULL)
+ goto end;
+
+ ret = SSL_CTX_use_certificate(ctx, x);
+ if (ERR_peek_error() != 0)
+ ret = 0;
+
+ if (ret != 0) {
+#ifdef HAVE_SSL_CTX_SET_CURRENT_CERT
+ SSL_CTX_select_current_cert(ctx, x);
+#endif
+ /* If we could set up our certificate, now proceed to
+ * the CA certificates.
+ */
+ X509 *ca;
+ int r;
+ unsigned long err;
+
+ while ((ca = PEM_read_bio_X509(in,NULL,NULL,NULL)) != NULL) {
+#ifdef HAVE_SSL_CTX_ADD0_CHAIN_CERT
+ r = SSL_CTX_add0_chain_cert(ctx, ca);
+#else
+ r = SSL_CTX_add_extra_chain_cert(ctx, ca);
+#endif
+ if (r == 0) {
+ X509_free(ca);
+ ret = 0;
+ goto end;
+ }
+ }
+ /* When the while loop ends, it's usually just EOF. */
+ err = ERR_peek_last_error();
+ if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)
+ ERR_clear_error();
+ else
+ ret = 0; /* some real error */
+ }
+
+end:
+ if (x != NULL) X509_free(x);
+ BIO_free(in);
+#ifdef HAVE_SSL_CTX_SET_CURRENT_CERT
+ SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_FIRST);
+#endif
+ return ret;
+}
+
+static int load_ca(X509_STORE *store, const char *ca,
+ STACK_OF(X509_NAME) **xnames_r)
+{
+ /* mostly just copy&pasted from X509_load_cert_crl_file() */
+ STACK_OF(X509_INFO) *inf;
+ STACK_OF(X509_NAME) *xnames;
+ X509_INFO *itmp;
+ X509_NAME *xname;
+ BIO *bio;
+ int i;
+
+ bio = BIO_new_mem_buf(t_strdup_noconst(ca), strlen(ca));
+ if (bio == NULL)
+ i_fatal("BIO_new_mem_buf() failed");
+ inf = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+
+ if (inf == NULL)
+ return -1;
+
+ xnames = sk_X509_NAME_new_null();
+ if (xnames == NULL)
+ i_fatal("sk_X509_NAME_new_null() failed");
+ for(i = 0; i < sk_X509_INFO_num(inf); i++) {
+ itmp = sk_X509_INFO_value(inf, i);
+ if(itmp->x509 != NULL) {
+ X509_STORE_add_cert(store, itmp->x509);
+ xname = X509_get_subject_name(itmp->x509);
+ if (xname != NULL)
+ xname = X509_NAME_dup(xname);
+ if (xname != NULL)
+ sk_X509_NAME_push(xnames, xname);
+ }
+ if(itmp->crl != NULL)
+ X509_STORE_add_crl(store, itmp->crl);
+ }
+ sk_X509_INFO_pop_free(inf, X509_INFO_free);
+ *xnames_r = xnames;
+ return 0;
+}
+
+static int
+load_ca_locations(struct ssl_iostream_context *ctx, const char *ca_file,
+ const char *ca_dir, const char **error_r)
+{
+ if (SSL_CTX_load_verify_locations(ctx->ssl_ctx, ca_file, ca_dir) != 0)
+ return 0;
+
+ if (ca_dir == NULL) {
+ *error_r = t_strdup_printf(
+ "Can't load CA certs from %s "
+ "(ssl_client_ca_file setting): %s",
+ ca_file, openssl_iostream_error());
+ } else if (ca_file == NULL) {
+ *error_r = t_strdup_printf(
+ "Can't load CA certs from directory %s "
+ "(ssl_client_ca_dir setting): %s",
+ ca_dir, openssl_iostream_error());
+ } else {
+ *error_r = t_strdup_printf(
+ "Can't load CA certs from file %s and directory %s "
+ "(ssl_client_ca_* settings): %s",
+ ca_file, ca_dir, openssl_iostream_error());
+ }
+ return -1;
+}
+
+static void
+ssl_iostream_ctx_verify_remote_cert(struct ssl_iostream_context *ctx,
+ STACK_OF(X509_NAME) *ca_names)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x00907000L
+ if (!ctx->set.skip_crl_check) {
+ X509_STORE *store;
+
+ store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
+ X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
+ X509_V_FLAG_CRL_CHECK_ALL);
+ }
+#endif
+
+ SSL_CTX_set_client_CA_list(ctx->ssl_ctx, ca_names);
+}
+
+#ifdef HAVE_SSL_GET_SERVERNAME
+static int ssl_servername_callback(SSL *ssl, int *al ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+ struct ssl_iostream *ssl_io;
+ const char *host, *error;
+
+ ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index);
+ host = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ if (SSL_get_servername_type(ssl) != -1) {
+ i_free(ssl_io->sni_host);
+ ssl_io->sni_host = i_strdup(host);
+ } else if (ssl_io->verbose) {
+ i_debug("SSL_get_servername() failed");
+ }
+
+ if (ssl_io->sni_callback != NULL) {
+ if (ssl_io->sni_callback(ssl_io->sni_host, &error,
+ ssl_io->sni_context) < 0) {
+ openssl_iostream_set_error(ssl_io, error);
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ }
+ return SSL_TLSEXT_ERR_OK;
+}
+#endif
+
+static int
+ssl_iostream_context_load_ca(struct ssl_iostream_context *ctx,
+ const struct ssl_iostream_settings *set,
+ const char **error_r)
+{
+ X509_STORE *store;
+ STACK_OF(X509_NAME) *xnames = NULL;
+ const char *ca_file, *ca_dir;
+ bool have_ca = FALSE;
+
+ if (set->ca != NULL) {
+ store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
+ if (load_ca(store, set->ca, &xnames) < 0) {
+ *error_r = t_strdup_printf("Couldn't parse ssl_ca: %s",
+ openssl_iostream_error());
+ return -1;
+ }
+ ssl_iostream_ctx_verify_remote_cert(ctx, xnames);
+ have_ca = TRUE;
+ }
+ ca_file = set->ca_file == NULL || *set->ca_file == '\0' ?
+ NULL : set->ca_file;
+ ca_dir = set->ca_dir == NULL || *set->ca_dir == '\0' ?
+ NULL : set->ca_dir;
+ if (ca_file != NULL || ca_dir != NULL) {
+ if (load_ca_locations(ctx, ca_file, ca_dir, error_r) < 0)
+ return -1;
+ have_ca = TRUE;
+ }
+ if (!have_ca && ctx->client_ctx) {
+ if (SSL_CTX_set_default_verify_paths(ctx->ssl_ctx) != 1) {
+ *error_r = t_strdup_printf(
+ "Can't load default CA locations: %s (ssl_client_ca_* settings missing)",
+ openssl_iostream_error());
+ return -1;
+ }
+ } else if (!have_ca) {
+ *error_r = "Can't verify remote client certs without CA (ssl_ca setting)";
+ return -1;
+ }
+ return 0;
+}
+
+static int
+ssl_iostream_context_set(struct ssl_iostream_context *ctx,
+ const struct ssl_iostream_settings *set,
+ const char **error_r)
+{
+ ssl_iostream_settings_init_from(ctx->pool, &ctx->set, set);
+ if (set->cipher_list != NULL &&
+ SSL_CTX_set_cipher_list(ctx->ssl_ctx, set->cipher_list) == 0) {
+ *error_r = t_strdup_printf(
+ "Can't set cipher list to '%s' (ssl_cipher_list setting): %s",
+ set->cipher_list, openssl_iostream_error());
+ return -1;
+ }
+#ifdef HAVE_SSL_CTX_SET1_CURVES_LIST
+ if (set->curve_list != NULL && strlen(set->curve_list) > 0 &&
+ SSL_CTX_set1_curves_list(ctx->ssl_ctx, set->curve_list) == 0) {
+ *error_r = t_strdup_printf(
+ "Can't set curve list to '%s' (ssl_curve_list setting)",
+ set->curve_list);
+ return -1;
+ }
+#endif
+#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
+ if (set->ciphersuites != NULL &&
+ SSL_CTX_set_ciphersuites(ctx->ssl_ctx, set->ciphersuites) == 0) {
+ *error_r = t_strdup_printf("Can't set ciphersuites to '%s': %s",
+ set->ciphersuites, openssl_iostream_error());
+ return -1;
+ }
+#endif
+ if (set->prefer_server_ciphers) {
+ SSL_CTX_set_options(ctx->ssl_ctx,
+ SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
+ if (ctx->set.min_protocol != NULL) {
+ long opts;
+ int min_protocol;
+ if (openssl_min_protocol_to_options(ctx->set.min_protocol,
+ &opts, &min_protocol) < 0) {
+ *error_r = t_strdup_printf(
+ "Unknown ssl_min_protocol setting '%s'",
+ set->min_protocol);
+ return -1;
+ }
+#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
+ SSL_CTX_set_min_proto_version(ctx->ssl_ctx, min_protocol);
+#else
+ SSL_CTX_set_options(ctx->ssl_ctx, opts);
+#endif
+ }
+
+ if (set->cert.cert != NULL &&
+ ssl_ctx_use_certificate_chain(ctx->ssl_ctx, set->cert.cert) == 0) {
+ *error_r = t_strdup_printf(
+ "Can't load SSL certificate (ssl_cert setting): %s",
+ openssl_iostream_use_certificate_error(set->cert.cert, NULL));
+ return -1;
+ }
+ if (set->cert.key != NULL) {
+ if (ssl_iostream_ctx_use_key(ctx, "ssl_key", &set->cert, error_r) < 0)
+ return -1;
+ }
+ if (set->alt_cert.cert != NULL &&
+ ssl_ctx_use_certificate_chain(ctx->ssl_ctx, set->alt_cert.cert) == 0) {
+ *error_r = t_strdup_printf(
+ "Can't load alternative SSL certificate "
+ "(ssl_alt_cert setting): %s",
+ openssl_iostream_use_certificate_error(set->alt_cert.cert, NULL));
+ return -1;
+ }
+ if (set->alt_cert.key != NULL) {
+ if (ssl_iostream_ctx_use_key(ctx, "ssl_alt_key", &set->alt_cert, error_r) < 0)
+ return -1;
+ }
+
+ if (set->dh != NULL) {
+ if (ssl_iostream_ctx_use_dh(ctx, set, error_r) < 0)
+ return -1;
+ }
+
+ /* set trusted CA certs */
+ if (set->verify_remote_cert) {
+ if (ssl_iostream_context_load_ca(ctx, set, error_r) < 0)
+ return -1;
+ }
+
+ if (set->cert_username_field != NULL) {
+ ctx->username_nid = OBJ_txt2nid(set->cert_username_field);
+ if (ctx->username_nid == NID_undef) {
+ *error_r = t_strdup_printf(
+ "Invalid cert_username_field: %s",
+ set->cert_username_field);
+ return -1;
+ }
+ }
+#ifdef HAVE_SSL_GET_SERVERNAME
+ if (!ctx->client_ctx) {
+ if (SSL_CTX_set_tlsext_servername_callback(ctx->ssl_ctx,
+ ssl_servername_callback) != 1) {
+ if (set->verbose)
+ i_debug("OpenSSL library doesn't support SNI");
+ }
+ }
+#endif
+ return 0;
+}
+
+#if defined(HAVE_ECDH) && !defined(SSL_CTX_set_ecdh_auto)
+static int
+ssl_proxy_ctx_get_pkey_ec_curve_name(const struct ssl_iostream_settings *set,
+ int *nid_r, const char **error_r)
+{
+ int nid = 0;
+ EVP_PKEY *pkey;
+ EC_KEY *eckey;
+ const EC_GROUP *ecgrp;
+
+ if (set->cert.key != NULL) {
+ if (openssl_iostream_load_key(&set->cert, "ssl_key", &pkey, error_r) < 0)
+ return -1;
+
+ if ((eckey = EVP_PKEY_get1_EC_KEY(pkey)) != NULL &&
+ (ecgrp = EC_KEY_get0_group(eckey)) != NULL)
+ nid = EC_GROUP_get_curve_name(ecgrp);
+ else {
+ /* clear errors added by the above calls */
+ openssl_iostream_clear_errors();
+ }
+ EVP_PKEY_free(pkey);
+ }
+ if (nid == 0 && set->alt_cert.key != NULL) {
+ if (openssl_iostream_load_key(&set->alt_cert, "ssl_alt_key", &pkey, error_r) < 0)
+ return -1;
+
+ if ((eckey = EVP_PKEY_get1_EC_KEY(pkey)) != NULL &&
+ (ecgrp = EC_KEY_get0_group(eckey)) != NULL)
+ nid = EC_GROUP_get_curve_name(ecgrp);
+ else {
+ /* clear errors added by the above calls */
+ openssl_iostream_clear_errors();
+ }
+ EVP_PKEY_free(pkey);
+ }
+
+ *nid_r = nid;
+ return 0;
+}
+#endif
+
+static int
+ssl_proxy_ctx_set_crypto_params(SSL_CTX *ssl_ctx,
+ const struct ssl_iostream_settings *set,
+ const char **error_r ATTR_UNUSED)
+{
+#if defined(HAVE_ECDH) && !defined(SSL_CTX_set_ecdh_auto)
+ EC_KEY *ecdh;
+ int nid;
+ const char *curve_name;
+#endif
+ if (SSL_CTX_need_tmp_RSA(ssl_ctx) != 0)
+ SSL_CTX_set_tmp_rsa_callback(ssl_ctx, ssl_gen_rsa_key);
+ if (set->dh == NULL || *set->dh == '\0')
+ SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_tmp_dh_callback);
+#ifdef HAVE_ECDH
+ /* In the non-recommended situation where ECDH cipher suites are being
+ used instead of ECDHE, do not reuse the same ECDH key pair for
+ different sessions. This option improves forward secrecy. */
+ SSL_CTX_set_options(ssl_ctx, SSL_OP_SINGLE_ECDH_USE);
+#ifdef SSL_CTX_set_ecdh_auto
+ /* OpenSSL >= 1.0.2 automatically handles ECDH temporary key parameter
+ selection. The return value of this function changes is changed to
+ bool in OpenSSL 1.1 and is int in OpenSSL 1.0.2+ */
+ if ((long)(SSL_CTX_set_ecdh_auto(ssl_ctx, 1)) == 0) {
+ /* shouldn't happen */
+ *error_r = t_strdup_printf("SSL_CTX_set_ecdh_auto() failed: %s",
+ openssl_iostream_error());
+ return -1;
+ }
+#else
+ /* For OpenSSL < 1.0.2, ECDH temporary key parameter selection must be
+ performed manually. Attempt to select the same curve as that used
+ in the server's private EC key file. Otherwise fall back to the
+ NIST P-384 (secp384r1) curve to be compliant with RFC 6460 when
+ AES-256 TLS cipher suites are in use. This fall back option does
+ however make Dovecot non-compliant with RFC 6460 which requires
+ curve NIST P-256 (prime256v1) be used when AES-128 TLS cipher
+ suites are in use. At least the non-compliance is in the form of
+ providing too much security rather than too little. */
+ if (ssl_proxy_ctx_get_pkey_ec_curve_name(set, &nid, error_r) < 0)
+ return -1;
+ ecdh = EC_KEY_new_by_curve_name(nid);
+ if (ecdh == NULL) {
+ /* Fall back option */
+ nid = NID_secp384r1;
+ ecdh = EC_KEY_new_by_curve_name(nid);
+ }
+ if ((curve_name = OBJ_nid2sn(nid)) != NULL && set->verbose) {
+ i_debug("SSL: elliptic curve %s will be used for ECDH and"
+ " ECDHE key exchanges", curve_name);
+ }
+ if (ecdh != NULL) {
+ SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
+ EC_KEY_free(ecdh);
+ }
+#endif
+#endif
+#ifdef SSL_OP_SINGLE_DH_USE
+ /* Improves forward secrecy with DH parameters, especially if the
+ parameters used aren't strong primes. See OpenSSL manual. */
+ SSL_CTX_set_options(ssl_ctx, SSL_OP_SINGLE_DH_USE);
+#endif
+ return 0;
+}
+
+static int
+ssl_iostream_context_init_common(struct ssl_iostream_context *ctx,
+ const struct ssl_iostream_settings *set,
+ const char **error_r)
+{
+ unsigned long ssl_ops = SSL_OP_NO_SSLv2 |
+ (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
+
+ ctx->pool = pool_alloconly_create("ssl iostream context", 4096);
+
+ /* enable all SSL workarounds, except empty fragments as it
+ makes SSL more vulnerable against attacks */
+#ifdef SSL_OP_NO_COMPRESSION
+ if (!set->compression)
+ ssl_ops |= SSL_OP_NO_COMPRESSION;
+#endif
+#ifdef SSL_OP_NO_TICKET
+ if (!set->tickets)
+ ssl_ops |= SSL_OP_NO_TICKET;
+#endif
+ SSL_CTX_set_options(ctx->ssl_ctx, ssl_ops);
+#ifdef SSL_MODE_RELEASE_BUFFERS
+ SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+#endif
+#ifdef SSL_MODE_ENABLE_PARTIAL_WRITE
+ SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
+#endif
+#ifdef SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
+ SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+#endif
+ if (ssl_proxy_ctx_set_crypto_params(ctx->ssl_ctx, set, error_r) < 0)
+ return -1;
+
+ return ssl_iostream_context_set(ctx, set, error_r);
+}
+
+int openssl_iostream_context_init_client(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r)
+{
+ struct ssl_iostream_context *ctx;
+ SSL_CTX *ssl_ctx;
+
+ if ((ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
+ *error_r = t_strdup_printf("SSL_CTX_new() failed: %s",
+ openssl_iostream_error());
+ return -1;
+ }
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+ ctx = i_new(struct ssl_iostream_context, 1);
+ ctx->refcount = 1;
+ ctx->ssl_ctx = ssl_ctx;
+ ctx->client_ctx = TRUE;
+ if (ssl_iostream_context_init_common(ctx, set, error_r) < 0) {
+ ssl_iostream_context_unref(&ctx);
+ return -1;
+ }
+ *ctx_r = ctx;
+ return 0;
+}
+
+int openssl_iostream_context_init_server(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r)
+{
+ struct ssl_iostream_context *ctx;
+ SSL_CTX *ssl_ctx;
+
+ if ((ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
+ *error_r = t_strdup_printf("SSL_CTX_new() failed: %s",
+ openssl_iostream_error());
+ return -1;
+ }
+
+ ctx = i_new(struct ssl_iostream_context, 1);
+ ctx->refcount = 1;
+ ctx->ssl_ctx = ssl_ctx;
+ if (ssl_iostream_context_init_common(ctx, set, error_r) < 0) {
+ ssl_iostream_context_unref(&ctx);
+ return -1;
+ }
+ *ctx_r = ctx;
+ return 0;
+}
+
+void openssl_iostream_context_ref(struct ssl_iostream_context *ctx)
+{
+ i_assert(ctx->refcount > 0);
+ ctx->refcount++;
+}
+
+void openssl_iostream_context_unref(struct ssl_iostream_context *ctx)
+{
+ i_assert(ctx->refcount > 0);
+ if (--ctx->refcount > 0)
+ return;
+
+ SSL_CTX_free(ctx->ssl_ctx);
+ pool_unref(&ctx->pool);
+ i_free(ctx);
+}
+
+void openssl_iostream_global_deinit(void)
+{
+ if (!ssl_global_initialized)
+ return;
+ dovecot_openssl_common_global_unref();
+}
+
+int openssl_iostream_global_init(const struct ssl_iostream_settings *set,
+ const char **error_r)
+{
+ static char dovecot[] = "dovecot";
+ const char *error;
+
+ if (ssl_global_initialized)
+ return 0;
+
+ ssl_global_initialized = TRUE;
+ dovecot_openssl_common_global_ref();
+
+ dovecot_ssl_extdata_index =
+ SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL);
+
+ if (set->crypto_device != NULL && *set->crypto_device != '\0') {
+ switch (dovecot_openssl_common_global_set_engine(set->crypto_device, &error)) {
+ case 0:
+ error = t_strdup_printf(
+ "Unknown ssl_crypto_device: %s",
+ set->crypto_device);
+ /* fall through */
+ case -1:
+ *error_r = error;
+ /* we'll deinit at exit in any case */
+ return -1;
+ }
+ }
+ return 0;
+}
diff --git a/src/lib-ssl-iostream/iostream-openssl.c b/src/lib-ssl-iostream/iostream-openssl.c
new file mode 100644
index 0000000..6920c53
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-openssl.c
@@ -0,0 +1,946 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream-private.h"
+#include "ostream-private.h"
+#include "iostream-openssl.h"
+
+#include <openssl/rand.h>
+#include <openssl/err.h>
+
+static void openssl_iostream_free(struct ssl_iostream *ssl_io);
+
+void openssl_iostream_set_error(struct ssl_iostream *ssl_io, const char *str)
+{
+ char *new_str;
+
+ /* i_debug() may sometimes be overriden, making it write to this very
+ same SSL stream, in which case the provided str may be invalidated
+ before it is even used. Therefore, we duplicate it immediately. */
+ new_str = i_strdup(str);
+
+ if (ssl_io->verbose) {
+ /* This error should normally be logged by lib-ssl-iostream's
+ caller. But if verbose=TRUE, log it here as well to make
+ sure that the error is always logged. */
+ i_debug("%sSSL error: %s", ssl_io->log_prefix, new_str);
+ }
+ i_free(ssl_io->last_error);
+ ssl_io->last_error = new_str;
+}
+
+static void openssl_info_callback(const SSL *ssl, int where, int ret)
+{
+ struct ssl_iostream *ssl_io;
+
+ ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index);
+ if ((where & SSL_CB_ALERT) != 0) {
+ switch (ret & 0xff) {
+ case SSL_AD_CLOSE_NOTIFY:
+ i_debug("%sSSL alert: %s",
+ ssl_io->log_prefix,
+ SSL_alert_desc_string_long(ret));
+ break;
+ default:
+ i_debug("%sSSL alert: where=0x%x, ret=%d: %s %s",
+ ssl_io->log_prefix, where, ret,
+ SSL_alert_type_string_long(ret),
+ SSL_alert_desc_string_long(ret));
+ break;
+ }
+ } else if (ret == 0) {
+ i_debug("%sSSL failed: where=0x%x: %s",
+ ssl_io->log_prefix, where, SSL_state_string_long(ssl));
+ } else {
+ i_debug("%sSSL: where=0x%x, ret=%d: %s",
+ ssl_io->log_prefix, where, ret,
+ SSL_state_string_long(ssl));
+ }
+}
+
+static int
+openssl_iostream_use_certificate(struct ssl_iostream *ssl_io, const char *cert,
+ const char **error_r)
+{
+ BIO *in;
+ X509 *x;
+ int ret = 0;
+
+ in = BIO_new_mem_buf(t_strdup_noconst(cert), strlen(cert));
+ if (in == NULL) {
+ *error_r = t_strdup_printf("BIO_new_mem_buf() failed: %s",
+ openssl_iostream_error());
+ return -1;
+ }
+
+ x = PEM_read_bio_X509(in, NULL, NULL, NULL);
+ if (x != NULL) {
+ ret = SSL_use_certificate(ssl_io->ssl, x);
+ if (ERR_peek_error() != 0)
+ ret = 0;
+ X509_free(x);
+ }
+ BIO_free(in);
+
+ if (ret == 0) {
+ *error_r = t_strdup_printf("Can't load ssl_cert: %s",
+ openssl_iostream_use_certificate_error(cert, NULL));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+openssl_iostream_use_key(struct ssl_iostream *ssl_io, const char *set_name,
+ const struct ssl_iostream_cert *set,
+ const char **error_r)
+{
+ EVP_PKEY *pkey;
+ int ret = 0;
+
+ if (openssl_iostream_load_key(set, set_name, &pkey, error_r) < 0)
+ return -1;
+ if (SSL_use_PrivateKey(ssl_io->ssl, pkey) != 1) {
+ *error_r = t_strdup_printf(
+ "Can't load SSL private key (%s setting): %s",
+ set_name, openssl_iostream_key_load_error());
+ ret = -1;
+ }
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+static int
+openssl_iostream_verify_client_cert(int preverify_ok, X509_STORE_CTX *ctx)
+{
+ int ssl_extidx = SSL_get_ex_data_X509_STORE_CTX_idx();
+ SSL *ssl;
+ struct ssl_iostream *ssl_io;
+ char certname[1024];
+ X509_NAME *subject;
+
+ ssl = X509_STORE_CTX_get_ex_data(ctx, ssl_extidx);
+ ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index);
+ ssl_io->cert_received = TRUE;
+
+ subject = X509_get_subject_name(X509_STORE_CTX_get_current_cert(ctx));
+ if (subject == NULL ||
+ X509_NAME_oneline(subject, certname, sizeof(certname)) == NULL)
+ certname[0] = '\0';
+ else
+ certname[sizeof(certname)-1] = '\0'; /* just in case.. */
+ if (preverify_ok == 0) {
+ openssl_iostream_set_error(ssl_io, t_strdup_printf(
+ "Received invalid SSL certificate: %s: %s (check %s)",
+ X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx)), certname,
+ ssl_io->ctx->client_ctx ?
+ "ssl_client_ca_* settings?" :
+ "ssl_ca setting?"));
+ if (ssl_io->verbose_invalid_cert)
+ i_info("%s", ssl_io->last_error);
+ } else if (ssl_io->verbose) {
+ i_info("Received valid SSL certificate: %s", certname);
+ }
+ if (preverify_ok == 0) {
+ ssl_io->cert_broken = TRUE;
+ if (!ssl_io->allow_invalid_cert) {
+ ssl_io->handshake_failed = TRUE;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+openssl_iostream_set(struct ssl_iostream *ssl_io,
+ const struct ssl_iostream_settings *set,
+ const char **error_r)
+{
+ const struct ssl_iostream_settings *ctx_set = &ssl_io->ctx->set;
+ int verify_flags;
+
+ if (set->verbose)
+ SSL_set_info_callback(ssl_io->ssl, openssl_info_callback);
+
+ if (set->cipher_list != NULL &&
+ strcmp(ctx_set->cipher_list, set->cipher_list) != 0) {
+ if (SSL_set_cipher_list(ssl_io->ssl, set->cipher_list) == 0) {
+ *error_r = t_strdup_printf(
+ "Can't set cipher list to '%s': %s",
+ set->cipher_list, openssl_iostream_error());
+ return -1;
+ }
+ }
+#ifdef HAVE_SSL_CTX_SET1_CURVES_LIST
+ if (set->curve_list != NULL && strlen(set->curve_list) > 0 &&
+ (ctx_set->curve_list == NULL || strcmp(ctx_set->curve_list, set->curve_list) != 0)) {
+ if (SSL_set1_curves_list(ssl_io->ssl, set->curve_list) == 0) {
+ *error_r = t_strdup_printf(
+ "Failed to set curve list to '%s'",
+ set->curve_list);
+ return -1;
+ }
+ }
+#endif
+#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
+ if (set->ciphersuites != NULL &&
+ strcmp(ctx_set->ciphersuites, set->ciphersuites) != 0) {
+ if (SSL_set_ciphersuites(ssl_io->ssl, set->ciphersuites) == 0) {
+ *error_r = t_strdup_printf(
+ "Can't set ciphersuites to '%s': %s",
+ set->ciphersuites, openssl_iostream_error());
+ return -1;
+ }
+ }
+#endif
+ if (set->prefer_server_ciphers)
+ SSL_set_options(ssl_io->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ if (set->min_protocol != NULL) {
+#if defined(HAVE_SSL_CLEAR_OPTIONS)
+ SSL_clear_options(ssl_io->ssl, OPENSSL_ALL_PROTOCOL_OPTIONS);
+#endif
+ long opts;
+ int min_protocol;
+ if (openssl_min_protocol_to_options(set->min_protocol, &opts,
+ &min_protocol) < 0) {
+ *error_r = t_strdup_printf(
+ "Unknown ssl_min_protocol setting '%s'",
+ set->min_protocol);
+ return -1;
+ }
+#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
+ SSL_set_min_proto_version(ssl_io->ssl, min_protocol);
+#else
+ SSL_set_options(ssl_io->ssl, opts);
+#endif
+ }
+
+ if (set->cert.cert != NULL && strcmp(ctx_set->cert.cert, set->cert.cert) != 0) {
+ if (openssl_iostream_use_certificate(ssl_io, set->cert.cert, error_r) < 0)
+ return -1;
+ }
+ if (set->cert.key != NULL && strcmp(ctx_set->cert.key, set->cert.key) != 0) {
+ if (openssl_iostream_use_key(ssl_io, "ssl_key", &set->cert, error_r) < 0)
+ return -1;
+ }
+ if (set->alt_cert.cert != NULL && strcmp(ctx_set->alt_cert.cert, set->alt_cert.cert) != 0) {
+ if (openssl_iostream_use_certificate(ssl_io, set->alt_cert.cert, error_r) < 0)
+ return -1;
+ }
+ if (set->alt_cert.key != NULL && strcmp(ctx_set->alt_cert.key, set->alt_cert.key) != 0) {
+ if (openssl_iostream_use_key(ssl_io, "ssl_alt_key", &set->alt_cert, error_r) < 0)
+ return -1;
+ }
+ if (set->verify_remote_cert) {
+ if (ssl_io->ctx->client_ctx)
+ verify_flags = SSL_VERIFY_NONE;
+ else
+ verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
+ SSL_set_verify(ssl_io->ssl, verify_flags,
+ openssl_iostream_verify_client_cert);
+ }
+
+ if (set->cert_username_field != NULL) {
+ ssl_io->username_nid = OBJ_txt2nid(set->cert_username_field);
+ if (ssl_io->username_nid == NID_undef) {
+ *error_r = t_strdup_printf(
+ "Invalid cert_username_field: %s",
+ set->cert_username_field);
+ return -1;
+ }
+ } else {
+ ssl_io->username_nid = ssl_io->ctx->username_nid;
+ }
+
+ ssl_io->verbose = set->verbose;
+ ssl_io->verbose_invalid_cert = set->verbose_invalid_cert || set->verbose;
+ ssl_io->allow_invalid_cert = set->allow_invalid_cert;
+ return 0;
+}
+
+static int
+openssl_iostream_create(struct ssl_iostream_context *ctx, const char *host,
+ const struct ssl_iostream_settings *set,
+ struct istream **input, struct ostream **output,
+ struct ssl_iostream **iostream_r,
+ const char **error_r)
+{
+ struct ssl_iostream *ssl_io;
+ SSL *ssl;
+ BIO *bio_int, *bio_ext;
+
+ /* Don't allow an existing io_add_istream() to be use on the input.
+ It would seem to work, but it would also cause hangs. */
+ i_assert(i_stream_get_root_io(*input)->real_stream->io == NULL);
+
+ ssl = SSL_new(ctx->ssl_ctx);
+ if (ssl == NULL) {
+ *error_r = t_strdup_printf("SSL_new() failed: %s",
+ openssl_iostream_error());
+ return -1;
+ }
+
+ /* BIO pairs use default buffer sizes (17 kB in OpenSSL 0.9.8e).
+ Each of the BIOs have one "write buffer". BIO_write() copies data
+ to them, while BIO_read() reads from the other BIO's write buffer
+ into the given buffer. The bio_int is used by OpenSSL and bio_ext
+ is used by this library. */
+ if (BIO_new_bio_pair(&bio_int, 0, &bio_ext, 0) != 1) {
+ *error_r = t_strdup_printf("BIO_new_bio_pair() failed: %s",
+ openssl_iostream_error());
+ SSL_free(ssl);
+ return -1;
+ }
+
+ ssl_io = i_new(struct ssl_iostream, 1);
+ ssl_io->refcount = 1;
+ ssl_io->ctx = ctx;
+ ssl_iostream_context_ref(ssl_io->ctx);
+ ssl_io->ssl = ssl;
+ ssl_io->bio_ext = bio_ext;
+ ssl_io->plain_input = *input;
+ ssl_io->plain_output = *output;
+ ssl_io->connected_host = i_strdup(host);
+ ssl_io->log_prefix = host == NULL ? i_strdup("") :
+ i_strdup_printf("%s: ", host);
+ /* bio_int will be freed by SSL_free() */
+ SSL_set_bio(ssl_io->ssl, bio_int, bio_int);
+ SSL_set_ex_data(ssl_io->ssl, dovecot_ssl_extdata_index, ssl_io);
+#ifdef HAVE_SSL_GET_SERVERNAME
+ SSL_set_tlsext_host_name(ssl_io->ssl, host);
+#endif
+
+ if (openssl_iostream_set(ssl_io, set, error_r) < 0) {
+ openssl_iostream_free(ssl_io);
+ return -1;
+ }
+
+ o_stream_uncork(ssl_io->plain_output);
+
+ *input = openssl_i_stream_create_ssl(ssl_io);
+ ssl_io->ssl_input = *input;
+
+ *output = openssl_o_stream_create_ssl(ssl_io);
+ i_stream_set_name(*input, t_strconcat("SSL ",
+ i_stream_get_name(ssl_io->plain_input), NULL));
+ o_stream_set_name(*output, t_strconcat("SSL ",
+ o_stream_get_name(ssl_io->plain_output), NULL));
+
+ if (ssl_io->plain_output->real_stream->error_handling_disabled)
+ o_stream_set_no_error_handling(*output, TRUE);
+
+ ssl_io->ssl_output = *output;
+ *iostream_r = ssl_io;
+ return 0;
+}
+
+static void openssl_iostream_free(struct ssl_iostream *ssl_io)
+{
+ ssl_iostream_context_unref(&ssl_io->ctx);
+ o_stream_unref(&ssl_io->plain_output);
+ i_stream_unref(&ssl_io->plain_input);
+ BIO_free(ssl_io->bio_ext);
+ SSL_free(ssl_io->ssl);
+ i_free(ssl_io->plain_stream_errstr);
+ i_free(ssl_io->last_error);
+ i_free(ssl_io->connected_host);
+ i_free(ssl_io->sni_host);
+ i_free(ssl_io->log_prefix);
+ i_free(ssl_io);
+}
+
+static void openssl_iostream_unref(struct ssl_iostream *ssl_io)
+{
+ i_assert(ssl_io->refcount > 0);
+ if (--ssl_io->refcount > 0)
+ return;
+
+ openssl_iostream_free(ssl_io);
+}
+
+void openssl_iostream_shutdown(struct ssl_iostream *ssl_io)
+{
+ if (ssl_io->destroyed)
+ return;
+
+ i_assert(ssl_io->ssl_input != NULL);
+ i_assert(ssl_io->ssl_output != NULL);
+
+ ssl_io->destroyed = TRUE;
+ if (ssl_io->handshaked && SSL_shutdown(ssl_io->ssl) != 1) {
+ /* if bidirectional shutdown fails we need to clear
+ the error queue */
+ openssl_iostream_clear_errors();
+ }
+ if (ssl_io->handshaked) {
+ (void)openssl_iostream_bio_sync(ssl_io,
+ OPENSSL_IOSTREAM_SYNC_TYPE_WRITE);
+ }
+ (void)o_stream_flush(ssl_io->plain_output);
+ /* close the plain i/o streams, because their fd may be closed soon,
+ but we may still keep this ssl-iostream referenced until later. */
+ i_stream_close(ssl_io->plain_input);
+ o_stream_close(ssl_io->plain_output);
+}
+
+static void openssl_iostream_destroy(struct ssl_iostream *ssl_io)
+{
+ openssl_iostream_shutdown(ssl_io);
+ ssl_iostream_unref(&ssl_io);
+}
+
+static int openssl_iostream_bio_output_real(struct ssl_iostream *ssl_io)
+{
+ size_t bytes, max_bytes = 0;
+ ssize_t sent;
+ unsigned char buffer[IO_BLOCK_SIZE];
+ int result = 0;
+ int ret;
+
+ o_stream_cork(ssl_io->plain_output);
+ while ((bytes = BIO_ctrl_pending(ssl_io->bio_ext)) > 0) {
+ /* bytes contains how many SSL encrypted bytes we should be
+ sending out */
+ max_bytes = o_stream_get_buffer_avail_size(ssl_io->plain_output);
+ if (bytes > max_bytes) {
+ if (max_bytes == 0) {
+ /* wait until output buffer clears */
+ break;
+ }
+ bytes = max_bytes;
+ }
+ if (bytes > sizeof(buffer))
+ bytes = sizeof(buffer);
+
+ /* BIO_read() is guaranteed to return all the bytes that
+ BIO_ctrl_pending() returned */
+ ret = BIO_read(ssl_io->bio_ext, buffer, bytes);
+ i_assert(ret == (int)bytes);
+
+ /* we limited number of read bytes to plain_output's
+ available size. this send() is guaranteed to either
+ fully succeed or completely fail due to some error. */
+ sent = o_stream_send(ssl_io->plain_output, buffer, bytes);
+ if (sent < 0) {
+ o_stream_uncork(ssl_io->plain_output);
+ return -1;
+ }
+ i_assert(sent == (ssize_t)bytes);
+ result = 1;
+ }
+
+ ret = o_stream_uncork_flush(ssl_io->plain_output);
+ if (ret < 0)
+ return -1;
+ if (ret == 0 || (bytes > 0 && max_bytes == 0))
+ o_stream_set_flush_pending(ssl_io->plain_output, TRUE);
+
+ return result;
+}
+
+static int openssl_iostream_bio_output(struct ssl_iostream *ssl_io)
+{
+ int ret;
+
+ ret = openssl_iostream_bio_output_real(ssl_io);
+ if (ret < 0) {
+ i_assert(ssl_io->plain_output->stream_errno != 0);
+ i_free(ssl_io->plain_stream_errstr);
+ ssl_io->plain_stream_errstr =
+ i_strdup(o_stream_get_error(ssl_io->plain_output));
+ ssl_io->plain_stream_errno =
+ ssl_io->plain_output->stream_errno;
+ ssl_io->closed = TRUE;
+ }
+ return ret;
+}
+
+static ssize_t
+openssl_iostream_read_more(struct ssl_iostream *ssl_io,
+ enum openssl_iostream_sync_type type, size_t wanted,
+ const unsigned char **data_r, size_t *size_r)
+{
+ *data_r = i_stream_get_data(ssl_io->plain_input, size_r);
+ if (*size_r > 0)
+ return 0;
+
+ if (type == OPENSSL_IOSTREAM_SYNC_TYPE_CONTINUE_READ) {
+ /* only the first i_stream_read() call attempts to read more
+ input. the following reads will just process the buffered
+ data. */
+ return 0;
+ }
+
+ if (i_stream_read_limited(ssl_io->plain_input, data_r, size_r,
+ wanted) < 0)
+ return -1;
+ return 0;
+}
+
+static int
+openssl_iostream_bio_input(struct ssl_iostream *ssl_io,
+ enum openssl_iostream_sync_type type)
+{
+ const unsigned char *data;
+ size_t bytes, size;
+ int ret;
+ bool bytes_read = FALSE;
+
+ while ((bytes = BIO_ctrl_get_write_guarantee(ssl_io->bio_ext)) > 0) {
+ /* bytes contains how many bytes we can write to bio_ext */
+ ret = openssl_iostream_read_more(ssl_io, type, bytes,
+ &data, &size);
+ if (ret == -1 && size == 0 && !bytes_read) {
+ if (ssl_io->plain_input->stream_errno != 0) {
+ i_free(ssl_io->plain_stream_errstr);
+ ssl_io->plain_stream_errstr =
+ i_strdup(i_stream_get_error(ssl_io->plain_input));
+ ssl_io->plain_stream_errno =
+ ssl_io->plain_input->stream_errno;
+ }
+ ssl_io->closed = TRUE;
+ return -1;
+ }
+ if (size == 0) {
+ /* wait for more input */
+ break;
+ }
+ if (size > bytes)
+ size = bytes;
+
+ ret = BIO_write(ssl_io->bio_ext, data, size);
+ i_assert(ret == (ssize_t)size);
+
+ i_stream_skip(ssl_io->plain_input, size);
+ bytes_read = TRUE;
+ }
+ if (bytes == 0 && !bytes_read && ssl_io->want_read) {
+ /* shouldn't happen */
+ i_error("SSL BIO buffer size too small");
+ i_free(ssl_io->plain_stream_errstr);
+ ssl_io->plain_stream_errstr =
+ i_strdup("SSL BIO buffer size too small");
+ ssl_io->plain_stream_errno = EINVAL;
+ ssl_io->closed = TRUE;
+ return -1;
+ }
+ if (bytes_read) {
+ if (ssl_io->ostream_flush_waiting_input) {
+ ssl_io->ostream_flush_waiting_input = FALSE;
+ o_stream_set_flush_pending(ssl_io->plain_output, TRUE);
+ }
+ }
+ if (bytes_read || i_stream_get_data_size(ssl_io->plain_input) > 0) {
+ if (i_stream_get_data_size(ssl_io->plain_input) > 0 ||
+ (type != OPENSSL_IOSTREAM_SYNC_TYPE_FIRST_READ &&
+ type != OPENSSL_IOSTREAM_SYNC_TYPE_CONTINUE_READ))
+ i_stream_set_input_pending(ssl_io->ssl_input, TRUE);
+ ssl_io->want_read = FALSE;
+ }
+ return (bytes_read ? 1 : 0);
+}
+
+int openssl_iostream_bio_sync(struct ssl_iostream *ssl_io,
+ enum openssl_iostream_sync_type type)
+{
+ int ret;
+
+ i_assert(type != OPENSSL_IOSTREAM_SYNC_TYPE_NONE);
+
+ ret = openssl_iostream_bio_output(ssl_io);
+ if (ret >= 0 && openssl_iostream_bio_input(ssl_io, type) > 0)
+ ret = 1;
+ return ret;
+}
+
+static void openssl_iostream_closed(struct ssl_iostream *ssl_io)
+{
+ if (ssl_io->plain_stream_errno != 0) {
+ i_assert(ssl_io->plain_stream_errstr != NULL);
+ openssl_iostream_set_error(ssl_io, ssl_io->plain_stream_errstr);
+ errno = ssl_io->plain_stream_errno;
+ } else {
+ openssl_iostream_set_error(ssl_io, "Connection closed");
+ errno = EPIPE;
+ }
+}
+
+int openssl_iostream_handle_error(struct ssl_iostream *ssl_io, int ret,
+ enum openssl_iostream_sync_type type,
+ const char *func_name)
+{
+ const char *errstr = NULL;
+ int err;
+
+ err = SSL_get_error(ssl_io->ssl, ret);
+ switch (err) {
+ case SSL_ERROR_WANT_WRITE:
+ if (type != OPENSSL_IOSTREAM_SYNC_TYPE_NONE &&
+ openssl_iostream_bio_sync(ssl_io, type) == 0) {
+ if (type != OPENSSL_IOSTREAM_SYNC_TYPE_WRITE)
+ i_panic("SSL ostream buffer size not unlimited");
+ return 0;
+ }
+ if (ssl_io->closed) {
+ openssl_iostream_closed(ssl_io);
+ return -1;
+ }
+ if (type == OPENSSL_IOSTREAM_SYNC_TYPE_NONE)
+ return 0;
+ return 1;
+ case SSL_ERROR_WANT_READ:
+ ssl_io->want_read = TRUE;
+ if (type != OPENSSL_IOSTREAM_SYNC_TYPE_NONE)
+ (void)openssl_iostream_bio_sync(ssl_io, type);
+ if (ssl_io->closed) {
+ openssl_iostream_closed(ssl_io);
+ return -1;
+ }
+ if (type == OPENSSL_IOSTREAM_SYNC_TYPE_NONE)
+ return 0;
+ return ssl_io->want_read ? 0 : 1;
+ case SSL_ERROR_SYSCALL:
+ /* eat up the error queue */
+ if (ERR_peek_error() != 0) {
+ errstr = openssl_iostream_error();
+ errno = EINVAL;
+ } else if (ret == 0) {
+ /* EOF. */
+ errno = EPIPE;
+ errstr = "Disconnected";
+ break;
+ } else if (errno != 0) {
+ errstr = strerror(errno);
+ } else {
+ /* Seen this at least with v1.1.0l SSL_accept() */
+ errstr = "OpenSSL BUG: errno=0";
+ errno = EINVAL;
+ }
+ errstr = t_strdup_printf("%s syscall failed: %s",
+ func_name, errstr);
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ /* clean connection closing */
+ errno = EPIPE;
+ if (ssl_io->handshaked)
+ i_free_and_null(ssl_io->last_error);
+ else if (ssl_io->last_error == NULL) {
+ errstr = "SSL connection closed during handshake";
+ break;
+ }
+ return -1;
+ case SSL_ERROR_SSL:
+ errstr = t_strdup_printf("%s failed: %s",
+ func_name, openssl_iostream_error());
+ errno = EINVAL;
+ break;
+ default:
+ errstr = t_strdup_printf("%s failed: unknown failure %d (%s)",
+ func_name, err,
+ openssl_iostream_error());
+ errno = EINVAL;
+ break;
+ }
+
+ openssl_iostream_set_error(ssl_io, errstr);
+ return -1;
+}
+
+static bool
+openssl_iostream_cert_match_name(struct ssl_iostream *ssl_io,
+ const char *verify_name, const char **reason_r)
+{
+ if (!ssl_iostream_has_valid_client_cert(ssl_io)) {
+ *reason_r = "Invalid certificate";
+ return FALSE;
+ }
+
+ return openssl_cert_match_name(ssl_io->ssl, verify_name, reason_r);
+}
+
+static int openssl_iostream_handshake(struct ssl_iostream *ssl_io)
+{
+ const char *reason, *error = NULL;
+ int ret;
+
+ i_assert(!ssl_io->handshaked);
+
+ /* we are being destroyed, so do not do any more handshaking */
+ if (ssl_io->destroyed)
+ return 0;
+
+ if (ssl_io->ctx->client_ctx) {
+ while ((ret = SSL_connect(ssl_io->ssl)) <= 0) {
+ ret = openssl_iostream_handle_error(ssl_io, ret,
+ OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE, "SSL_connect()");
+ if (ret <= 0)
+ return ret;
+ }
+ } else {
+ while ((ret = SSL_accept(ssl_io->ssl)) <= 0) {
+ ret = openssl_iostream_handle_error(ssl_io, ret,
+ OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE, "SSL_accept()");
+ if (ret <= 0)
+ return ret;
+ }
+ }
+ /* handshake finished */
+ (void)openssl_iostream_bio_sync(ssl_io, OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE);
+
+ if (ssl_io->handshake_callback != NULL) {
+ if (ssl_io->handshake_callback(&error, ssl_io->handshake_context) < 0) {
+ i_assert(error != NULL);
+ openssl_iostream_set_error(ssl_io, error);
+ ssl_io->handshake_failed = TRUE;
+ }
+ } else if (ssl_io->connected_host != NULL && !ssl_io->handshake_failed &&
+ !ssl_io->allow_invalid_cert) {
+ if (ssl_iostream_check_cert_validity(ssl_io, ssl_io->connected_host, &reason) < 0) {
+ openssl_iostream_set_error(ssl_io, reason);
+ ssl_io->handshake_failed = TRUE;
+ }
+ }
+ if (ssl_io->handshake_failed) {
+ i_stream_close(ssl_io->plain_input);
+ o_stream_close(ssl_io->plain_output);
+ errno = EINVAL;
+ return -1;
+ }
+ i_free_and_null(ssl_io->last_error);
+ ssl_io->handshaked = TRUE;
+
+ if (ssl_io->ssl_output != NULL)
+ (void)o_stream_flush(ssl_io->ssl_output);
+ return 1;
+}
+
+static void
+openssl_iostream_set_handshake_callback(struct ssl_iostream *ssl_io,
+ ssl_iostream_handshake_callback_t *callback,
+ void *context)
+{
+ ssl_io->handshake_callback = callback;
+ ssl_io->handshake_context = context;
+}
+
+static void
+openssl_iostream_set_sni_callback(struct ssl_iostream *ssl_io,
+ ssl_iostream_sni_callback_t *callback,
+ void *context)
+{
+ ssl_io->sni_callback = callback;
+ ssl_io->sni_context = context;
+}
+
+static void
+openssl_iostream_change_context(struct ssl_iostream *ssl_io,
+ struct ssl_iostream_context *ctx)
+{
+ if (ctx != ssl_io->ctx) {
+ SSL_set_SSL_CTX(ssl_io->ssl, ctx->ssl_ctx);
+ ssl_iostream_context_ref(ctx);
+ ssl_iostream_context_unref(&ssl_io->ctx);
+ ssl_io->ctx = ctx;
+ }
+}
+
+static void openssl_iostream_set_log_prefix(struct ssl_iostream *ssl_io,
+ const char *prefix)
+{
+ i_free(ssl_io->log_prefix);
+ ssl_io->log_prefix = i_strdup(prefix);
+}
+
+static bool openssl_iostream_is_handshaked(const struct ssl_iostream *ssl_io)
+{
+ return ssl_io->handshaked;
+}
+
+static bool
+openssl_iostream_has_handshake_failed(const struct ssl_iostream *ssl_io)
+{
+ return ssl_io->handshake_failed;
+}
+
+static bool
+openssl_iostream_has_valid_client_cert(const struct ssl_iostream *ssl_io)
+{
+ return ssl_io->cert_received && !ssl_io->cert_broken;
+}
+
+static bool
+openssl_iostream_has_broken_client_cert(struct ssl_iostream *ssl_io)
+{
+ return ssl_io->cert_received && ssl_io->cert_broken;
+}
+
+static const char *
+openssl_iostream_get_peer_name(struct ssl_iostream *ssl_io)
+{
+ X509 *x509;
+ char *name;
+ int len;
+
+ if (!ssl_iostream_has_valid_client_cert(ssl_io))
+ return NULL;
+
+ x509 = SSL_get_peer_certificate(ssl_io->ssl);
+ i_assert(x509 != NULL);
+
+ len = X509_NAME_get_text_by_NID(X509_get_subject_name(x509),
+ ssl_io->username_nid, NULL, 0);
+ if (len < 0)
+ name = "";
+ else {
+ name = t_malloc0(len + 1);
+ if (X509_NAME_get_text_by_NID(X509_get_subject_name(x509),
+ ssl_io->username_nid,
+ name, len + 1) < 0)
+ name = "";
+ else if (strlen(name) != (size_t)len) {
+ /* NUL characters in name. Someone's trying to fake
+ being another user? Don't allow it. */
+ name = "";
+ }
+ }
+ X509_free(x509);
+
+ return *name == '\0' ? NULL : name;
+}
+
+static const char *openssl_iostream_get_server_name(struct ssl_iostream *ssl_io)
+{
+ return ssl_io->sni_host;
+}
+
+static const char *
+openssl_iostream_get_compression(struct ssl_iostream *ssl_io)
+{
+#if defined(HAVE_SSL_COMPRESSION) && !defined(OPENSSL_NO_COMP)
+ const COMP_METHOD *comp;
+
+ comp = SSL_get_current_compression(ssl_io->ssl);
+ return comp == NULL ? NULL : SSL_COMP_get_name(comp);
+#else
+ return NULL;
+#endif
+}
+
+static const char *
+openssl_iostream_get_security_string(struct ssl_iostream *ssl_io)
+{
+ const SSL_CIPHER *cipher;
+#if defined(HAVE_SSL_COMPRESSION) && !defined(OPENSSL_NO_COMP)
+ const COMP_METHOD *comp;
+#endif
+ const char *comp_str;
+ int bits, alg_bits;
+
+ if (!ssl_io->handshaked)
+ return "";
+
+ cipher = SSL_get_current_cipher(ssl_io->ssl);
+ bits = SSL_CIPHER_get_bits(cipher, &alg_bits);
+#if defined(HAVE_SSL_COMPRESSION) && !defined(OPENSSL_NO_COMP)
+ comp = SSL_get_current_compression(ssl_io->ssl);
+ comp_str = comp == NULL ? "" :
+ t_strconcat(" ", SSL_COMP_get_name(comp), NULL);
+#else
+ comp_str = "";
+#endif
+ return t_strdup_printf("%s with cipher %s (%d/%d bits)%s",
+ SSL_get_version(ssl_io->ssl),
+ SSL_CIPHER_get_name(cipher),
+ bits, alg_bits, comp_str);
+}
+
+static const char *
+openssl_iostream_get_last_error(struct ssl_iostream *ssl_io)
+{
+ return ssl_io->last_error;
+}
+
+static const char *
+openssl_iostream_get_cipher(struct ssl_iostream *ssl_io, unsigned int *bits_r)
+{
+ if (!ssl_io->handshaked)
+ return NULL;
+
+ const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl_io->ssl);
+ *bits_r = SSL_CIPHER_get_bits(cipher, NULL);
+ return SSL_CIPHER_get_name(cipher);
+}
+
+static const char *
+openssl_iostream_get_pfs(struct ssl_iostream *ssl_io)
+{
+ if (!ssl_io->handshaked)
+ return NULL;
+
+ const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl_io->ssl);
+#if defined(HAVE_SSL_CIPHER_get_kx_nid)
+ int nid = SSL_CIPHER_get_kx_nid(cipher);
+ return OBJ_nid2sn(nid);
+#else
+ char buf[128];
+ const char *desc, *ptr;
+ if ((desc = SSL_CIPHER_description(cipher, buf, sizeof(buf)))==NULL ||
+ (ptr = strstr(desc, "Kx=")) == NULL)
+ return "";
+ return t_strcut(ptr+3, ' ');
+#endif
+}
+
+static const char *
+openssl_iostream_get_protocol_name(struct ssl_iostream *ssl_io)
+{
+ if (!ssl_io->handshaked)
+ return NULL;
+ return SSL_get_version(ssl_io->ssl);
+}
+
+
+static const struct iostream_ssl_vfuncs ssl_vfuncs = {
+ .global_init = openssl_iostream_global_init,
+ .context_init_client = openssl_iostream_context_init_client,
+ .context_init_server = openssl_iostream_context_init_server,
+ .context_ref = openssl_iostream_context_ref,
+ .context_unref = openssl_iostream_context_unref,
+
+ .create = openssl_iostream_create,
+ .unref = openssl_iostream_unref,
+ .destroy = openssl_iostream_destroy,
+
+ .handshake = openssl_iostream_handshake,
+ .set_handshake_callback = openssl_iostream_set_handshake_callback,
+ .set_sni_callback = openssl_iostream_set_sni_callback,
+ .change_context = openssl_iostream_change_context,
+
+ .set_log_prefix = openssl_iostream_set_log_prefix,
+ .is_handshaked = openssl_iostream_is_handshaked,
+ .has_handshake_failed = openssl_iostream_has_handshake_failed,
+ .has_valid_client_cert = openssl_iostream_has_valid_client_cert,
+ .has_broken_client_cert = openssl_iostream_has_broken_client_cert,
+ .cert_match_name = openssl_iostream_cert_match_name,
+ .get_peer_name = openssl_iostream_get_peer_name,
+ .get_server_name = openssl_iostream_get_server_name,
+ .get_compression = openssl_iostream_get_compression,
+ .get_security_string = openssl_iostream_get_security_string,
+ .get_last_error = openssl_iostream_get_last_error,
+ .get_cipher = openssl_iostream_get_cipher,
+ .get_pfs = openssl_iostream_get_pfs,
+ .get_protocol_name = openssl_iostream_get_protocol_name,
+};
+
+void ssl_iostream_openssl_init(void)
+{
+ unsigned char buf;
+ if (RAND_bytes(&buf, 1) < 1)
+ i_fatal("OpenSSL RNG failed to initialize");
+ iostream_ssl_module_init(&ssl_vfuncs);
+}
+
+void ssl_iostream_openssl_deinit(void)
+{
+ openssl_iostream_global_deinit();
+}
diff --git a/src/lib-ssl-iostream/iostream-openssl.h b/src/lib-ssl-iostream/iostream-openssl.h
new file mode 100644
index 0000000..4449668
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-openssl.h
@@ -0,0 +1,129 @@
+#ifndef IOSTREAM_OPENSSL_H
+#define IOSTREAM_OPENSSL_H
+
+#include "iostream-ssl-private.h"
+
+#include <openssl/ssl.h>
+
+#ifndef HAVE_ASN1_STRING_GET0_DATA
+# define ASN1_STRING_get0_data(str) ASN1_STRING_data(str)
+#endif
+enum openssl_iostream_sync_type {
+ OPENSSL_IOSTREAM_SYNC_TYPE_NONE,
+ OPENSSL_IOSTREAM_SYNC_TYPE_FIRST_READ,
+ OPENSSL_IOSTREAM_SYNC_TYPE_CONTINUE_READ,
+ OPENSSL_IOSTREAM_SYNC_TYPE_WRITE,
+ OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE
+};
+
+struct ssl_iostream_context {
+ int refcount;
+ SSL_CTX *ssl_ctx;
+
+ pool_t pool;
+ struct ssl_iostream_settings set;
+
+ int username_nid;
+
+ bool client_ctx:1;
+};
+
+struct ssl_iostream {
+ int refcount;
+ struct ssl_iostream_context *ctx;
+
+ SSL *ssl;
+ BIO *bio_ext;
+
+ struct istream *plain_input;
+ struct ostream *plain_output;
+ struct istream *ssl_input;
+ struct ostream *ssl_output;
+
+ /* SSL clients: host where we connected to */
+ char *connected_host;
+ /* SSL servers: host requested by the client via SNI */
+ char *sni_host;
+ char *last_error;
+ char *log_prefix;
+ char *plain_stream_errstr;
+ int plain_stream_errno;
+
+ /* copied settings */
+ bool verbose, verbose_invalid_cert, allow_invalid_cert;
+ int username_nid;
+
+ ssl_iostream_handshake_callback_t *handshake_callback;
+ void *handshake_context;
+
+ ssl_iostream_sni_callback_t *sni_callback;
+ void *sni_context;
+
+ bool handshaked:1;
+ bool handshake_failed:1;
+ bool cert_received:1;
+ bool cert_broken:1;
+ bool want_read:1;
+ bool ostream_flush_waiting_input:1;
+ bool closed:1;
+ bool destroyed:1;
+};
+
+extern int dovecot_ssl_extdata_index;
+
+struct istream *openssl_i_stream_create_ssl(struct ssl_iostream *ssl_io);
+struct ostream *openssl_o_stream_create_ssl(struct ssl_iostream *ssl_io);
+
+int openssl_iostream_global_init(const struct ssl_iostream_settings *set,
+ const char **error_r);
+
+int openssl_iostream_context_init_client(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r);
+int openssl_iostream_context_init_server(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r);
+void openssl_iostream_context_ref(struct ssl_iostream_context *ctx);
+void openssl_iostream_context_unref(struct ssl_iostream_context *ctx);
+void openssl_iostream_global_deinit(void);
+
+int openssl_iostream_load_key(const struct ssl_iostream_cert *set,
+ const char *set_name,
+ EVP_PKEY **pkey_r, const char **error_r);
+bool openssl_cert_match_name(SSL *ssl, const char *verify_name,
+ const char **reason_r);
+#define OPENSSL_ALL_PROTOCOL_OPTIONS \
+ (SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1)
+/* opt_r is used with SSL_set_options() and version_r is used with
+ SSL_set_min_proto_version(). Using either method should enable the same SSL
+ protocol versions. */
+int openssl_min_protocol_to_options(const char *min_protocol, long *opt_r,
+ int *version_r) ATTR_NULL(2,3);
+
+/* Sync plain_input/plain_output streams with BIOs. Returns 1 if at least
+ one byte was read/written, 0 if nothing was written, and -1 if an error
+ occurred. */
+int openssl_iostream_bio_sync(struct ssl_iostream *ssl_io,
+ enum openssl_iostream_sync_type type);
+
+/* Returns 1 if the operation should be retried (we read/wrote more data),
+ 0 if the operation should retried later once more data has been
+ read/written, -1 if a fatal error occurred (errno is set). */
+int openssl_iostream_handle_error(struct ssl_iostream *ssl_io, int ret,
+ enum openssl_iostream_sync_type type,
+ const char *func_name);
+
+/* Perform clean shutdown for the connection. */
+void openssl_iostream_shutdown(struct ssl_iostream *ssl_io);
+
+void openssl_iostream_set_error(struct ssl_iostream *ssl_io, const char *str);
+const char *openssl_iostream_error(void);
+const char *openssl_iostream_key_load_error(void);
+const char *
+openssl_iostream_use_certificate_error(const char *cert, const char *set_name);
+void openssl_iostream_clear_errors(void);
+
+void ssl_iostream_openssl_init(void);
+void ssl_iostream_openssl_deinit(void);
+
+#endif
diff --git a/src/lib-ssl-iostream/iostream-ssl-context-cache.c b/src/lib-ssl-iostream/iostream-ssl-context-cache.c
new file mode 100644
index 0000000..a7245ce
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-ssl-context-cache.c
@@ -0,0 +1,129 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hash.h"
+#include "iostream-ssl-private.h"
+
+struct ssl_iostream_context_cache {
+ bool server;
+ struct ssl_iostream_settings set;
+};
+
+static pool_t ssl_iostream_contexts_pool;
+static HASH_TABLE(struct ssl_iostream_context_cache *,
+ struct ssl_iostream_context *) ssl_iostream_contexts;
+
+static unsigned int
+ssl_iostream_context_cache_hash(const struct ssl_iostream_context_cache *cache)
+{
+ unsigned int n, i, g, h = 0;
+ const char *const cert[] = { cache->set.cert.cert, cache->set.alt_cert.cert };
+
+ /* checking for different certs is typically good enough,
+ and it should be enough to check only the first few bytes (after the
+ "BEGIN CERTIFICATE" line). */
+ for (n = 0; n < N_ELEMENTS(cert); n++) {
+ if (cert[n] == NULL)
+ continue;
+
+ for (i = 0; i < 64 && cert[n][i] != '\0'; i++) {
+ h = (h << 4) + cert[n][i];
+ if ((g = h & 0xf0000000UL) != 0) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ }
+ }
+ return h ^ (cache->server ? 1 : 0);
+}
+
+static int
+ssl_iostream_context_cache_cmp(const struct ssl_iostream_context_cache *c1,
+ const struct ssl_iostream_context_cache *c2)
+{
+ if (c1->server != c2->server)
+ return -1;
+ return ssl_iostream_settings_equals(&c1->set, &c2->set) ? 0 : -1;
+}
+
+static int
+ssl_iostream_context_cache_get(const struct ssl_iostream_settings *set,
+ bool server,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r)
+{
+ struct ssl_iostream_context *ctx;
+ struct ssl_iostream_context_cache *cache;
+ struct ssl_iostream_context_cache lookup = {
+ .server = server,
+ .set = *set,
+ };
+
+ if (ssl_iostream_contexts_pool == NULL) {
+ ssl_iostream_contexts_pool =
+ pool_alloconly_create(MEMPOOL_GROWING"ssl iostream context cache", 1024);
+ hash_table_create(&ssl_iostream_contexts,
+ ssl_iostream_contexts_pool, 0,
+ ssl_iostream_context_cache_hash,
+ ssl_iostream_context_cache_cmp);
+ }
+ ssl_iostream_settings_drop_stream_only(&lookup.set);
+
+ ctx = hash_table_lookup(ssl_iostream_contexts, &lookup);
+ if (ctx != NULL) {
+ ssl_iostream_context_ref(ctx);
+ *ctx_r = ctx;
+ return 0;
+ }
+
+ /* add to cache */
+ if (server) {
+ if (ssl_iostream_context_init_server(&lookup.set, &ctx, error_r) < 0)
+ return -1;
+ } else {
+ if (ssl_iostream_context_init_client(&lookup.set, &ctx, error_r) < 0)
+ return -1;
+ }
+
+ cache = p_new(ssl_iostream_contexts_pool,
+ struct ssl_iostream_context_cache, 1);
+ cache->server = server;
+ ssl_iostream_settings_init_from(ssl_iostream_contexts_pool,
+ &cache->set, &lookup.set);
+ hash_table_insert(ssl_iostream_contexts, cache, ctx);
+
+ ssl_iostream_context_ref(ctx);
+ *ctx_r = ctx;
+ return 0;
+}
+
+int ssl_iostream_client_context_cache_get(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r)
+{
+ return ssl_iostream_context_cache_get(set, FALSE, ctx_r, error_r);
+}
+
+int ssl_iostream_server_context_cache_get(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r)
+{
+ return ssl_iostream_context_cache_get(set, TRUE, ctx_r, error_r);
+}
+
+void ssl_iostream_context_cache_free(void)
+{
+ struct hash_iterate_context *iter;
+ struct ssl_iostream_context_cache *lookup;
+ struct ssl_iostream_context *ctx;
+
+ if (ssl_iostream_contexts_pool == NULL)
+ return;
+
+ iter = hash_table_iterate_init(ssl_iostream_contexts);
+ while (hash_table_iterate(iter, ssl_iostream_contexts, &lookup, &ctx))
+ ssl_iostream_context_unref(&ctx);
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&ssl_iostream_contexts);
+ pool_unref(&ssl_iostream_contexts_pool);
+}
diff --git a/src/lib-ssl-iostream/iostream-ssl-private.h b/src/lib-ssl-iostream/iostream-ssl-private.h
new file mode 100644
index 0000000..c0f4a3a
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-ssl-private.h
@@ -0,0 +1,64 @@
+#ifndef IOSTREAM_SSL_PRIVATE_H
+#define IOSTREAM_SSL_PRIVATE_H
+
+#include "iostream-ssl.h"
+
+struct iostream_ssl_vfuncs {
+ int (*global_init)(const struct ssl_iostream_settings *set,
+ const char **error_r);
+ int (*context_init_client)(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r);
+ int (*context_init_server)(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r);
+ void (*context_ref)(struct ssl_iostream_context *ctx);
+ void (*context_unref)(struct ssl_iostream_context *ctx);
+
+ int (*create)(struct ssl_iostream_context *ctx, const char *host,
+ const struct ssl_iostream_settings *set,
+ struct istream **input, struct ostream **output,
+ struct ssl_iostream **iostream_r, const char **error_r);
+ void (*unref)(struct ssl_iostream *ssl_io);
+ void (*destroy)(struct ssl_iostream *ssl_io);
+
+ int (*handshake)(struct ssl_iostream *ssl_io);
+ void (*set_handshake_callback)(struct ssl_iostream *ssl_io,
+ ssl_iostream_handshake_callback_t *callback,
+ void *context);
+ void (*set_sni_callback)(struct ssl_iostream *ssl_io,
+ ssl_iostream_sni_callback_t *callback,
+ void *context);
+ void (*change_context)(struct ssl_iostream *ssl_io,
+ struct ssl_iostream_context *ctx);
+
+ void (*set_log_prefix)(struct ssl_iostream *ssl_io, const char *prefix);
+ bool (*is_handshaked)(const struct ssl_iostream *ssl_io);
+ bool (*has_handshake_failed)(const struct ssl_iostream *ssl_io);
+ bool (*has_valid_client_cert)(const struct ssl_iostream *ssl_io);
+ bool (*has_broken_client_cert)(struct ssl_iostream *ssl_io);
+ bool (*cert_match_name)(struct ssl_iostream *ssl_io, const char *name,
+ const char **reason_r);
+ const char *(*get_peer_name)(struct ssl_iostream *ssl_io);
+ const char *(*get_server_name)(struct ssl_iostream *ssl_io);
+ const char *(*get_compression)(struct ssl_iostream *ssl_io);
+ const char *(*get_security_string)(struct ssl_iostream *ssl_io);
+ const char *(*get_last_error)(struct ssl_iostream *ssl_io);
+ const char *(*get_cipher)(struct ssl_iostream *ssl_io, unsigned int *bits_r);
+ const char *(*get_pfs)(struct ssl_iostream *ssl_io);
+ const char *(*get_protocol_name)(struct ssl_iostream *ssl_io);
+};
+
+void iostream_ssl_module_init(const struct iostream_ssl_vfuncs *vfuncs);
+
+/* Returns TRUE if both settings are equal. Note that NULL and "" aren't
+ treated equal. */
+bool ssl_iostream_settings_equals(const struct ssl_iostream_settings *set1,
+ const struct ssl_iostream_settings *set2);
+/* Clear out all stream-only settings, so only settings useful for a context
+ are left. */
+void ssl_iostream_settings_drop_stream_only(struct ssl_iostream_settings *set);
+
+void ssl_iostream_unref(struct ssl_iostream **ssl_io);
+
+#endif
diff --git a/src/lib-ssl-iostream/iostream-ssl-test.c b/src/lib-ssl-iostream/iostream-ssl-test.c
new file mode 100644
index 0000000..50d0aab
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-ssl-test.c
@@ -0,0 +1,158 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#include "iostream-ssl.h"
+#include "iostream-ssl-test.h"
+
+static const char *test_ca_cert =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIF4TCCA8mgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgYQxCzAJBgNVBAYTAk5M\n"
+ "MRMwEQYDVQQIDApHZWxkZXJsYW5kMRIwEAYDVQQHDAlCYXJuZXZlbGQxGjAYBgNV\n"
+ "BAoMEUNoaWNrZW4gQ29vcCBCLlYuMR4wHAYDVQQLDBVDZXJ0aWZpY2F0ZSBBdXRo\n"
+ "b3JpdHkxEDAOBgNVBAMMB1Jvb3QgQ0EwIBcNMTgwMjA4MjEyODE2WhgPMjExODAx\n"
+ "MTUyMTI4MTZaMHgxCzAJBgNVBAYTAk5MMRMwEQYDVQQIDApHZWxkZXJsYW5kMRow\n"
+ "GAYDVQQKDBFDaGlja2VuIENvb3AgQi5WLjEeMBwGA1UECwwVQ2VydGlmaWNhdGUg\n"
+ "QXV0aG9yaXR5MRgwFgYDVQQDDA9JbnRlcm1lZGlhdGUgQ0EwggIiMA0GCSqGSIb3\n"
+ "DQEBAQUAA4ICDwAwggIKAoICAQDgwuwUQ387ALkBO2YAvLiOi0rhQMis+TY34tpN\n"
+ "96Xx9Jaa8gdiAW9y3l8hGFm1+Ens5ZukwMysUoP7rrI5s0XOgCTChzrB4dEnbWHj\n"
+ "2YUYUBVLTLqZ4PTbY6xyrjYHKol1govkU+wclmyeI+Os946U0HFubg+KuXGZ2oLM\n"
+ "iYAmur/oxickEwJX932KhzQS4xdT5o38cVv470ot6eNEAiZcufP/gBSjAyUd8Wge\n"
+ "bwpW64fE/0LyCXYZrK5LWG1dMPC8MpETb8uLAB33r6q3yLTcEWg79bes7SgNrQdx\n"
+ "ncUXBoh8YSJvniZQ6OhwENPGTNhZWzgltDZHASyKXY2ojV70D8iiy/uB+owPSTla\n"
+ "txnu7z8B4kVCBWhCUizk7upjZNA0aFutjEHyYLtxqbTon+iLYm7M4iaga23YBdMU\n"
+ "1QVtulmUY6dcjTJ8GG3uo+qglPKuSodLSb23ovxAdVdIF+BNukd18ZhEIAe08hbw\n"
+ "YBHUYsKNkTMYcwxSgK3yQ4tQw0Cky4wAdsDv1XBK0LZ8+wnuWjnrnO/TRvgLWRU4\n"
+ "qI36OEMk9T0bxi+UwP3mzu78OoMCCdf67ccZ2/zFfHg+dqTBc9zV0sYJ/RQvmEN1\n"
+ "KDgqJAhz+VkDzTBiQYxoztTgBv9yxYufFvwZX4uhsvtMXUuRfvoVwK16vPXnMHwF\n"
+ "muIvwwIDAQABo2YwZDAdBgNVHQ4EFgQUWv/zcVnDWf53C2iN9f6uyUmhp1EwHwYD\n"
+ "VR0jBBgwFoAUsSOnSayEpzvbN21MEQGEyVKx+kIwEgYDVR0TAQH/BAgwBgEB/wIB\n"
+ "ADAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAJUlgJIHnnclDQMn\n"
+ "PSmruCl7bCRvLZYQtSiIv3/K4kPXJMYOvULoXGLA0+CdHoo06k8/TLk7gF1gNCPr\n"
+ "23z2+SCIuvVrVlveWyqD684yQ3UYeWoJnOv90F19267uELrWX4UMVE+z1r7iULyw\n"
+ "cxQokw6EGjw7xHiVETvNlHlmA0/8IuZv89CTztOP54NAXqu8WeloL/ipEhgj1HRx\n"
+ "TkuDf6SlTQ+mXKbiVJFiEA6rOsYFGGWE/SDNbLfx60OoHz0rQ95zw0rv/wPs1Oai\n"
+ "x71Ccuz/i2iI7ItXjQcUEcXvIDFJEMNXDaccIVmk1uda0Cm92W/sGc4vG2LUwEpl\n"
+ "LRj/x4Q39WaZTCrZzd6p2+6tYA45VnoblZ0enYU5XcQzryR/GC7VWFH1OvzOcSG5\n"
+ "NhpKIiWZvuMG5ilXyw7yh7cnWiPvGp8zCO7w1IOyk8sETQBstxuiALeEdrgdz6R5\n"
+ "jV5oIqsCmsIihfRgNufx/SImTJvue4uYgrKa4jo1tw+CFkEWPd7zXjferkxyU8C5\n"
+ "Y+Fr3yMuis5O4qa5mb94r0AQhc8MbCAuInSqNGX0Iu/UTg6Z+56omA2CnKGt6Rwd\n"
+ "LxLo2vhT9gTF88QwTMBPlhVjBbjTRhmY+mHv9gh3GczQ/i5VRXyYQH4h7EBtKFFI\n"
+ "t4mBWMavY+hS/zVkufYzUcUR7D1P\n"
+ "-----END CERTIFICATE-----\n"
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIF8jCCA9qgAwIBAgIJAN0zFa9E/xyxMA0GCSqGSIb3DQEBCwUAMIGEMQswCQYD\n"
+ "VQQGEwJOTDETMBEGA1UECAwKR2VsZGVybGFuZDESMBAGA1UEBwwJQmFybmV2ZWxk\n"
+ "MRowGAYDVQQKDBFDaGlja2VuIENvb3AgQi5WLjEeMBwGA1UECwwVQ2VydGlmaWNh\n"
+ "dGUgQXV0aG9yaXR5MRAwDgYDVQQDDAdSb290IENBMCAXDTE4MDIwODIxMjA1NloY\n"
+ "DzIyMTcxMjIyMjEyMDU2WjCBhDELMAkGA1UEBhMCTkwxEzARBgNVBAgMCkdlbGRl\n"
+ "cmxhbmQxEjAQBgNVBAcMCUJhcm5ldmVsZDEaMBgGA1UECgwRQ2hpY2tlbiBDb29w\n"
+ "IEIuVi4xHjAcBgNVBAsMFUNlcnRpZmljYXRlIEF1dGhvcml0eTEQMA4GA1UEAwwH\n"
+ "Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKMvVLNtj1C+\n"
+ "ZQ4ypNIA5+zB8oseN65k8VqpyhcPAQv0M/HvOB8jXzWH4v1VfKOYkTxQXqu4v0RP\n"
+ "6k0awe/6FH0GDlYhKUuPNDH2djlGOVbq+qBdSXpC3UjEcksxIuigCmzdkuytnlhW\n"
+ "qQYnLVl6kXwYtzjWsetTZBGseCpSYBWnvdtG/MGQNozi03VsGFkj/fbwuLK7ZHVp\n"
+ "64QLk9j3IPZRPHUaFlnT+v2ySMjO8OncsZ/fMZ/nxmt8GJ/68cMy9czydauz2KZs\n"
+ "pQEFS6s/HCmRXT1VQZ7zw5V/PBnF7ecveTaQtxJoNO4Pr7sh77El/ChUxN1Acw4N\n"
+ "2UH/06k6xnirLsvJonCRbVX3bxPBoDzGHjPqb7r0AKD0WBvrzgjeooSjobEtcIvA\n"
+ "LntiGKp4KtvWKcANPWkutH9X71U7M773oMmrz5fWvz9yv3wVuyblZSaMBwrV16GX\n"
+ "mcym6KF+Oj7j86jNq4wxNtjiQVV0QZcBijtnWpHaD5EMhI/TZvLK9oCFyAL92Wzi\n"
+ "t95r8g3D/8ue0CHqB/EpodH88MdVwr7sgxLQ40KibpErOXb93CJnq/7MVMO/EzTj\n"
+ "4XiGGUOo0elLqEPjzBO6AiGEgXAE2iNoghX79cbMQFtk9sK7XdMVLoXwBvt+Naaz\n"
+ "w96+7R+rZ4SsfrtlrP7xoCPJXbeQ4YI9AgMBAAGjYzBhMB0GA1UdDgQWBBSxI6dJ\n"
+ "rISnO9s3bUwRAYTJUrH6QjAfBgNVHSMEGDAWgBSxI6dJrISnO9s3bUwRAYTJUrH6\n"
+ "QjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF\n"
+ "AAOCAgEAQ30mub/GBwThYX3h9p01kQh+/eXXQ47h1xG3B870EwJ2y36wlGbAhZmE\n"
+ "7o1kDZwhNMR4NxT2PSWQHn8m6RiuSGAG9DU2q55tPEZg2DqkQmoCFvV3n2MVIAwL\n"
+ "rZ8c9EaoM5RkeeDBmuVo8H1aCvd5oLJ5j64z6wkgsSRwVXkxQLOAEdmRSVHN/c/6\n"
+ "QEdg0Uh5wkeC7R5wiwQUEkhLie+XwUPG7dIJHWp9g5oVO7IN+KWBLWiqbAJhVFhF\n"
+ "evOSqGDRV/Q2kfwSqDRrokk7CaE8KO/i+AUTF4TFQc/ewCLSeBSkvV7ORXBbe7ob\n"
+ "ShGViL7WEngpGAVoDZEsSViXQ36a5zxCvYcGjHcsKUITPMiD55x0aKNWjc0XfEg4\n"
+ "JtWvYWwygxTcefbs9pxHrmEnyCPpyDB8cPj866JAeaEAhxhDtSqaBE/ek576aJ+Z\n"
+ "ZaGjBQDDhRndLhTPAx1EXB8jgl/yjD+KMUqHs39UowKH25iBxMiW3R1XDIyYFGyi\n"
+ "+UFP+5NgokW/z6JfpUYd4W3jRcareS10UrLQC8tk4vvixk+1MuNKmzBy2eRITYZz\n"
+ "KiYX6NTvbvRt6XsKil8ypHKvWH+i2Cn3JrnTaCzJ4y66lnbRs4/ZnRceqRz35i39\n"
+ "rNT5Ier3SjmyIulxnmoYXHInIcS0TSV1+byyaTUCHKHLx12RxAM=\n"
+ "-----END CERTIFICATE-----\n";
+
+static const char *test_server_cert =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIF/jCCA+agAwIBAgICEAMwDQYJKoZIhvcNAQELBQAweDELMAkGA1UEBhMCTkwx\n"
+ "EzARBgNVBAgMCkdlbGRlcmxhbmQxGjAYBgNVBAoMEUNoaWNrZW4gQ29vcCBCLlYu\n"
+ "MR4wHAYDVQQLDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxGDAWBgNVBAMMD0ludGVy\n"
+ "bWVkaWF0ZSBDQTAgFw0xOTAyMTkxMDI2MzdaGA8yMTIxMTAyMjEwMjYzN1owgbUx\n"
+ "CzAJBgNVBAYTAk5MMRMwEQYDVQQIDApHZWxkZXJsYW5kMRIwEAYDVQQHDAlCYXJu\n"
+ "ZXZlbGQxGjAYBgNVBAoMEUNoaWNrZW4gQ29vcCBCLlYuMSIwIAYDVQQLDBlDaGlj\n"
+ "a2VuIENvb3AgV2ViIFNlcnZpY2VzMRIwEAYDVQQDDAkxMjcuMC4wLjExKTAnBgkq\n"
+ "hkiG9w0BCQEWGmhlbm5pZUBjaGlja2VuY29vcC5leGFtcGxlMIIBIjANBgkqhkiG\n"
+ "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3mNxJKdYUP88HHAPP8FWSDYKofxg2ECJsBQP\n"
+ "cIEvhuDsOpU+kBp3CbVFL+it4kNq7KvYvQyGvag8JA3RFrvsj3/nFM99lPm6RP1y\n"
+ "h9hNYVWRHHWKyOTdwqhk9bOrLk8j8fxw4NfQ/dkURYLJ0OtVTtJELJsxif5BfIAP\n"
+ "ypHayfnFoNa3rFG3uGULzJlb5JOHJHr7vxOTrRUZGYFZeBFacKF14CHYMUkpr9dv\n"
+ "BLaGqKFF0Lx2TU0tzRIXGu2AIFydcM0eGhCFLOV8YqxwT0R+XyNoNG9jRVSewWx0\n"
+ "r7rAF/93vARdRQVDmVoLmxrUMldYN7mQ9MsuGkMBfecLO4HqrQIDAQABo4IBUDCC\n"
+ "AUwwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4QgENBCYW\n"
+ "JE9wZW5TU0wgR2VuZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU\n"
+ "Ez4hL16BDA+GMrQs+g3/1Do4Vr4wgbIGA1UdIwSBqjCBp4AUWv/zcVnDWf53C2iN\n"
+ "9f6uyUmhp1GhgYqkgYcwgYQxCzAJBgNVBAYTAk5MMRMwEQYDVQQIDApHZWxkZXJs\n"
+ "YW5kMRIwEAYDVQQHDAlCYXJuZXZlbGQxGjAYBgNVBAoMEUNoaWNrZW4gQ29vcCBC\n"
+ "LlYuMR4wHAYDVQQLDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxEDAOBgNVBAMMB1Jv\n"
+ "b3QgQ0GCAhAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAN\n"
+ "BgkqhkiG9w0BAQsFAAOCAgEAVbs9RDJiVvGAW2cQv5OvghvMECF2Lh0pxsyOeywz\n"
+ "AQBBEaz85ZTRjtQjTWxpJwgwnIFEmfMEpuZoDoffCWDKUNkt24DD9TN8kriHplPK\n"
+ "5P3qZsnuV/E6CiYxBNpYhEle001XO+sU3yjAMeynPZWSZVz1JnjAdh1+opVd62O8\n"
+ "RX7twWdaSUBdsw7JJ2tdtTPeYkyRqDbfQWZSbA4/3VOwLuOs38zgy5ZqHy+YF/MM\n"
+ "48D1cOI0K1qpJE4qYLzcoULSnJ6C8KsS9LsspiLSDGdEvFY0DnCOa/POG6WiuEGk\n"
+ "JGRD+bDbEPZmNU0OLWjzM3vC2qxjF2PC8Q4chhkm4s4cAT7DMf+nnwqTPGK1U3N3\n"
+ "S5xhHrQ1GMHDhrqmKIrBOxdc8Hg7CwCU+C7TXxIPuQlbdStqMW/lVfbRNKsm/Hr6\n"
+ "smOC/hTxuned5DxRkDTCKNof3bZt8k9dqFLjqj14txmITq7MLhJLcsXgECWe7HmX\n"
+ "imxYJzLHjJU6QoDqGXHEMNrIBp46aIOcTjJP6tfosgAzplj+o00xdpMH9Q9hG602\n"
+ "lpgS9JfemZI68jPTZeGie9wXilPp4kE4qmlxhKSsjh3RVHo6ju7uKI5vCBkk15C6\n"
+ "lQMIGAmkRJ5vkcpUWnO6dKCSQdru/gKn3X4JVAHwm4178gqfRkofT28xAZ+vZoFj\n"
+ "L7M=\n"
+ "-----END CERTIFICATE-----\n";
+
+static const char *test_server_key =
+ "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEowIBAAKCAQEA3mNxJKdYUP88HHAPP8FWSDYKofxg2ECJsBQPcIEvhuDsOpU+\n"
+ "kBp3CbVFL+it4kNq7KvYvQyGvag8JA3RFrvsj3/nFM99lPm6RP1yh9hNYVWRHHWK\n"
+ "yOTdwqhk9bOrLk8j8fxw4NfQ/dkURYLJ0OtVTtJELJsxif5BfIAPypHayfnFoNa3\n"
+ "rFG3uGULzJlb5JOHJHr7vxOTrRUZGYFZeBFacKF14CHYMUkpr9dvBLaGqKFF0Lx2\n"
+ "TU0tzRIXGu2AIFydcM0eGhCFLOV8YqxwT0R+XyNoNG9jRVSewWx0r7rAF/93vARd\n"
+ "RQVDmVoLmxrUMldYN7mQ9MsuGkMBfecLO4HqrQIDAQABAoIBAQCGV4Az6ju5wlXn\n"
+ "v/IWS4751Fub+z/tox8KFTQ2fHPfgORzh1Dh8HrUjIKdLGxOcPeYvT8TBQwoagba\n"
+ "qNYUa7W+Aj/wHF/6rNlPb+POGGa2U+BzVrZeIZOtUdibbMwOD5ThS+RMj1Ma5hYO\n"
+ "37FW2bMRCIhSgfXtLIEW2q2va2jF9VI+c7F9dL01COG65nwUSJaRSLPrK5WbVNZE\n"
+ "yNm2st88iiu3TtPJGty+wmFgyV0PjAYYqxVEPs6qcmYMuHrfKlrntDfYai6x7fb+\n"
+ "RDfHhmqmn2oV/LGl7YtH+jBML9WG+Rfw+M7pYsd3ffRjG4aRxSTotu0QZfjduP0a\n"
+ "NZL1C3OBAoGBAPrzC4X2/fPGfyHksg+ecPS2Pvnyr+L9Gtg2Dg9wAONbmYqGKYxa\n"
+ "7DUBr6WWbOy9qJKk1CHR2Gn5FK3KbHG1PhLiOuG00kjm6imSCFTpJ3sBXUCl9qMk\n"
+ "FJnBifT4//INLFIcLjFlygR20hHgqyLU8P2kKzM+7iPaF1GYdILYHnExAoGBAOLd\n"
+ "PmKFcwuD4VfzTwK/Va0L8FFi3Oo7O4OE+9PyfTCJ8fH9PW03e4DiddjM0djDovWW\n"
+ "sm1ySVTfHjE0rsnaMGngPvqhG5T6IMUtFowEa2VwqwfFrPzK4PUYeRV1G+rTL1ZX\n"
+ "RGV1nPxCRXog91gzDCuyYB7jblrjG+DP+ZfGI5I9AoGAJc8Mg2iNJndXnDGqqjPC\n"
+ "7PuwTVRFL7vWmZC7WZQUbizU20wPYngocmwInLgnPRvuE/oFg/rr0juW5ABFinQ2\n"
+ "H/45xNvLevRff1fjLXfbXOr9s8nNeRLsj6XbNS920G8vqEdaplKhtz53s/3Xiu3u\n"
+ "SSi84YGvu3MWZFLF6xjIrWECgYAI2tXahpbs9iLPigGle85eSL8CjjdNNS6nfYNO\n"
+ "zIIyaM/2wAmrv6SkbTJoWeY+7bPong8s0m8mTucgyIuh+VA2cbhDlBI9iF3LFG1y\n"
+ "3aFLflBOp1qPK2QIbQIc4ktKqR+J4TIcO7D676NClxLQcH2jHv09d2cRSRgHeFan\n"
+ "o+YziQKBgEtqnknKgedinexHheBhE/fo49bHwNYZBfA5ZKqQDguNBafU1BUOmPEO\n"
+ "eNb8S5cJDMR7zKeZJ9dHzf4j78sITigwxt8+Ee6VY92U/uTFkPKZNLpAr3OfDSdh\n"
+ "z+M/zgztKqdrSKhr64g/3Dbbe+XqdeGe8MIx+P+QN+SrJNNaNZ1r\n"
+ "-----END RSA PRIVATE KEY-----\n";
+
+void ssl_iostream_test_settings_server(struct ssl_iostream_settings *test_set)
+{
+ i_zero(test_set);
+ test_set->ca = test_ca_cert;
+ test_set->cert.cert = test_server_cert;
+ test_set->cert.key = test_server_key;
+ test_set->skip_crl_check = TRUE;
+}
+
+void ssl_iostream_test_settings_client(struct ssl_iostream_settings *test_set)
+{
+ i_zero(test_set);
+ test_set->ca = test_ca_cert;
+ test_set->skip_crl_check = TRUE;
+}
diff --git a/src/lib-ssl-iostream/iostream-ssl-test.h b/src/lib-ssl-iostream/iostream-ssl-test.h
new file mode 100644
index 0000000..82c5811
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-ssl-test.h
@@ -0,0 +1,9 @@
+#ifndef IOSTREAM_SSL_TEST_H
+#define IOSTREAM_SSL_TEST_H
+
+struct ssl_iostream_settings;
+
+void ssl_iostream_test_settings_server(struct ssl_iostream_settings *test_set);
+void ssl_iostream_test_settings_client(struct ssl_iostream_settings *test_set);
+
+#endif
diff --git a/src/lib-ssl-iostream/iostream-ssl.c b/src/lib-ssl-iostream/iostream-ssl.c
new file mode 100644
index 0000000..430e69d
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-ssl.c
@@ -0,0 +1,351 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "module-dir.h"
+#include "iostream-ssl-private.h"
+
+#define OFFSET(name) offsetof(struct ssl_iostream_settings, name)
+static const size_t ssl_iostream_settings_string_offsets[] = {
+ OFFSET(min_protocol),
+ OFFSET(cipher_list),
+ OFFSET(ciphersuites),
+ OFFSET(curve_list),
+ OFFSET(ca),
+ OFFSET(ca_file),
+ OFFSET(ca_dir),
+ OFFSET(cert.cert),
+ OFFSET(cert.key),
+ OFFSET(cert.key_password),
+ OFFSET(alt_cert.cert),
+ OFFSET(alt_cert.key),
+ OFFSET(alt_cert.key_password),
+ OFFSET(dh),
+ OFFSET(cert_username_field),
+ OFFSET(crypto_device),
+};
+
+static bool ssl_module_loaded = FALSE;
+#ifdef HAVE_SSL
+static struct module *ssl_module = NULL;
+#endif
+static const struct iostream_ssl_vfuncs *ssl_vfuncs = NULL;
+
+#ifdef HAVE_SSL
+static void ssl_module_unload(void)
+{
+ ssl_iostream_context_cache_free();
+ module_dir_unload(&ssl_module);
+}
+#endif
+
+void iostream_ssl_module_init(const struct iostream_ssl_vfuncs *vfuncs)
+{
+ ssl_vfuncs = vfuncs;
+ ssl_module_loaded = TRUE;
+}
+
+int ssl_module_load(const char **error_r)
+{
+#ifdef HAVE_SSL
+ const char *plugin_name = "ssl_iostream_openssl";
+ struct module_dir_load_settings mod_set;
+
+ i_zero(&mod_set);
+ mod_set.abi_version = DOVECOT_ABI_VERSION;
+ mod_set.setting_name = "<built-in lib-ssl-iostream lookup>";
+ mod_set.require_init_funcs = TRUE;
+ ssl_module = module_dir_load(MODULE_DIR, plugin_name, &mod_set);
+ if (module_dir_try_load_missing(&ssl_module, MODULE_DIR, plugin_name,
+ &mod_set, error_r) < 0)
+ return -1;
+ module_dir_init(ssl_module);
+ if (!ssl_module_loaded) {
+ *error_r = t_strdup_printf(
+ "%s didn't call iostream_ssl_module_init() - SSL not initialized",
+ plugin_name);
+ module_dir_unload(&ssl_module);
+ return -1;
+ }
+
+ /* Destroy SSL module after (most of) the others. Especially lib-fs
+ backends may still want to access SSL module in their own
+ atexit-callbacks. */
+ lib_atexit_priority(ssl_module_unload, LIB_ATEXIT_PRIORITY_LOW);
+ return 0;
+#else
+ *error_r = "SSL support not compiled in";
+ return -1;
+#endif
+}
+
+int io_stream_ssl_global_init(const struct ssl_iostream_settings *set,
+ const char **error_r)
+{
+ return ssl_vfuncs->global_init(set, error_r);
+}
+
+int ssl_iostream_context_init_client(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r)
+{
+ struct ssl_iostream_settings set_copy = *set;
+
+ /* ensure this is set to TRUE */
+ set_copy.verify_remote_cert = TRUE;
+
+ if (!ssl_module_loaded) {
+ if (ssl_module_load(error_r) < 0)
+ return -1;
+ }
+ if (io_stream_ssl_global_init(&set_copy, error_r) < 0)
+ return -1;
+ return ssl_vfuncs->context_init_client(&set_copy, ctx_r, error_r);
+}
+
+int ssl_iostream_context_init_server(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r)
+{
+ if (!ssl_module_loaded) {
+ if (ssl_module_load(error_r) < 0)
+ return -1;
+ }
+ if (io_stream_ssl_global_init(set, error_r) < 0)
+ return -1;
+ return ssl_vfuncs->context_init_server(set, ctx_r, error_r);
+}
+
+void ssl_iostream_context_ref(struct ssl_iostream_context *ctx)
+{
+ ssl_vfuncs->context_ref(ctx);
+}
+
+void ssl_iostream_context_unref(struct ssl_iostream_context **_ctx)
+{
+ struct ssl_iostream_context *ctx = *_ctx;
+
+ if (*_ctx == NULL)
+ return;
+ *_ctx = NULL;
+ ssl_vfuncs->context_unref(ctx);
+}
+
+int io_stream_create_ssl_client(struct ssl_iostream_context *ctx, const char *host,
+ const struct ssl_iostream_settings *set,
+ struct istream **input, struct ostream **output,
+ struct ssl_iostream **iostream_r,
+ const char **error_r)
+{
+ struct ssl_iostream_settings set_copy = *set;
+ set_copy.verify_remote_cert = TRUE;
+ return ssl_vfuncs->create(ctx, host, &set_copy, input, output,
+ iostream_r, error_r);
+}
+
+int io_stream_create_ssl_server(struct ssl_iostream_context *ctx,
+ const struct ssl_iostream_settings *set,
+ struct istream **input, struct ostream **output,
+ struct ssl_iostream **iostream_r,
+ const char **error_r)
+{
+ return ssl_vfuncs->create(ctx, NULL, set, input, output,
+ iostream_r, error_r);
+}
+
+void ssl_iostream_unref(struct ssl_iostream **_ssl_io)
+{
+ struct ssl_iostream *ssl_io = *_ssl_io;
+
+ *_ssl_io = NULL;
+ ssl_vfuncs->unref(ssl_io);
+}
+
+void ssl_iostream_destroy(struct ssl_iostream **_ssl_io)
+{
+ struct ssl_iostream *ssl_io;
+
+ if (_ssl_io == NULL || *_ssl_io == NULL)
+ return;
+
+ ssl_io = *_ssl_io;
+ *_ssl_io = NULL;
+ ssl_vfuncs->destroy(ssl_io);
+}
+
+void ssl_iostream_set_log_prefix(struct ssl_iostream *ssl_io,
+ const char *prefix)
+{
+ ssl_vfuncs->set_log_prefix(ssl_io, prefix);
+}
+
+int ssl_iostream_handshake(struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->handshake(ssl_io);
+}
+
+void ssl_iostream_set_handshake_callback(struct ssl_iostream *ssl_io,
+ ssl_iostream_handshake_callback_t *callback,
+ void *context)
+{
+ ssl_vfuncs->set_handshake_callback(ssl_io, callback, context);
+}
+
+void ssl_iostream_set_sni_callback(struct ssl_iostream *ssl_io,
+ ssl_iostream_sni_callback_t *callback,
+ void *context)
+{
+ ssl_vfuncs->set_sni_callback(ssl_io, callback, context);
+}
+
+void ssl_iostream_change_context(struct ssl_iostream *ssl_io,
+ struct ssl_iostream_context *ctx)
+{
+ ssl_vfuncs->change_context(ssl_io, ctx);
+}
+
+bool ssl_iostream_is_handshaked(const struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->is_handshaked(ssl_io);
+}
+
+bool ssl_iostream_has_handshake_failed(const struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->has_handshake_failed(ssl_io);
+}
+
+bool ssl_iostream_has_valid_client_cert(const struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->has_valid_client_cert(ssl_io);
+}
+
+bool ssl_iostream_has_broken_client_cert(struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->has_broken_client_cert(ssl_io);
+}
+
+bool ssl_iostream_cert_match_name(struct ssl_iostream *ssl_io, const char *name,
+ const char **reason_r)
+{
+ return ssl_vfuncs->cert_match_name(ssl_io, name, reason_r);
+}
+
+int ssl_iostream_check_cert_validity(struct ssl_iostream *ssl_io,
+ const char *host, const char **error_r)
+{
+ const char *reason;
+
+ if (!ssl_iostream_has_valid_client_cert(ssl_io)) {
+ if (!ssl_iostream_has_broken_client_cert(ssl_io))
+ *error_r = "SSL certificate not received";
+ else {
+ *error_r = t_strdup(ssl_iostream_get_last_error(ssl_io));
+ if (*error_r == NULL)
+ *error_r = "Received invalid SSL certificate";
+ }
+ return -1;
+ } else if (!ssl_iostream_cert_match_name(ssl_io, host, &reason)) {
+ *error_r = t_strdup_printf(
+ "SSL certificate doesn't match expected host name %s: %s",
+ host, reason);
+ return -1;
+ }
+ return 0;
+}
+
+const char *ssl_iostream_get_peer_name(struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->get_peer_name(ssl_io);
+}
+
+const char *ssl_iostream_get_server_name(struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->get_server_name(ssl_io);
+}
+
+const char *ssl_iostream_get_compression(struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->get_compression(ssl_io);
+}
+
+const char *ssl_iostream_get_security_string(struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->get_security_string(ssl_io);
+}
+
+const char *ssl_iostream_get_last_error(struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->get_last_error(ssl_io);
+}
+
+struct ssl_iostream_settings *ssl_iostream_settings_dup(pool_t pool,
+ const struct ssl_iostream_settings *old_set)
+{
+ struct ssl_iostream_settings *new_set;
+
+ new_set = p_new(pool, struct ssl_iostream_settings, 1);
+ ssl_iostream_settings_init_from(pool, new_set, old_set);
+ return new_set;
+}
+
+void ssl_iostream_settings_init_from(pool_t pool,
+ struct ssl_iostream_settings *dest,
+ const struct ssl_iostream_settings *src)
+{
+ unsigned int i;
+
+ *dest = *src;
+ for (i = 0; i < N_ELEMENTS(ssl_iostream_settings_string_offsets); i++) {
+ const size_t offset = ssl_iostream_settings_string_offsets[i];
+ const char *const *src_str = CONST_PTR_OFFSET(src, offset);
+ const char **dest_str = PTR_OFFSET(dest, offset);
+ *dest_str = p_strdup(pool, *src_str);
+ }
+}
+
+bool ssl_iostream_settings_equals(const struct ssl_iostream_settings *set1,
+ const struct ssl_iostream_settings *set2)
+{
+ struct ssl_iostream_settings set1_nonstr, set2_nonstr;
+ unsigned int i;
+
+ set1_nonstr = *set1;
+ set2_nonstr = *set2;
+ for (i = 0; i < N_ELEMENTS(ssl_iostream_settings_string_offsets); i++) {
+ const size_t offset = ssl_iostream_settings_string_offsets[i];
+ const char **str1 = PTR_OFFSET(&set1_nonstr, offset);
+ const char **str2 = PTR_OFFSET(&set2_nonstr, offset);
+
+ if (null_strcmp(*str1, *str2) != 0)
+ return FALSE;
+
+ /* clear away the string pointer from the settings struct */
+ *str1 = NULL;
+ *str2 = NULL;
+ }
+ /* The set*_nonstr no longer have any pointers, so we can compare them
+ directly. */
+ return memcmp(&set1_nonstr, &set2_nonstr, sizeof(set1_nonstr)) == 0;
+}
+
+void ssl_iostream_settings_drop_stream_only(struct ssl_iostream_settings *set)
+{
+ set->verbose = FALSE;
+ set->verbose_invalid_cert = FALSE;
+ set->allow_invalid_cert = FALSE;
+}
+
+const char *ssl_iostream_get_cipher(struct ssl_iostream *ssl_io,
+ unsigned int *bits_r)
+{
+ return ssl_vfuncs->get_cipher(ssl_io, bits_r);
+}
+
+const char *ssl_iostream_get_pfs(struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->get_pfs(ssl_io);
+}
+
+const char *ssl_iostream_get_protocol_name(struct ssl_iostream *ssl_io)
+{
+ return ssl_vfuncs->get_protocol_name(ssl_io);
+}
diff --git a/src/lib-ssl-iostream/iostream-ssl.h b/src/lib-ssl-iostream/iostream-ssl.h
new file mode 100644
index 0000000..3224486
--- /dev/null
+++ b/src/lib-ssl-iostream/iostream-ssl.h
@@ -0,0 +1,175 @@
+#ifndef IOSTREAM_SSL_H
+#define IOSTREAM_SSL_H
+
+struct ssl_iostream;
+struct ssl_iostream_context;
+
+struct ssl_iostream_cert {
+ const char *cert;
+ const char *key;
+ const char *key_password;
+};
+
+struct ssl_iostream_settings {
+ /* NOTE: when updating, remember to update:
+ ssl_iostream_settings_string_offsets[],
+ ssl_iostream_settings_drop_stream_only() */
+ const char *min_protocol; /* both */
+ const char *cipher_list; /* both */
+ const char *ciphersuites; /* both, TLSv1.3 only */
+ const char *curve_list; /* both */
+ const char *ca, *ca_file, *ca_dir; /* context-only */
+ /* alternative cert is for providing certificate using
+ different key algorithm */
+ struct ssl_iostream_cert cert; /* both */
+ struct ssl_iostream_cert alt_cert; /* both */
+ const char *dh; /* context-only */
+ const char *cert_username_field; /* both */
+ const char *crypto_device; /* context-only */
+
+ bool verbose, verbose_invalid_cert; /* stream-only */
+ bool skip_crl_check; /* context-only */
+ bool verify_remote_cert; /* neither/both */
+ bool allow_invalid_cert; /* stream-only */
+ bool prefer_server_ciphers; /* both */
+ bool compression; /* context-only */
+ bool tickets; /* context-only */
+};
+
+/* Load SSL module */
+int ssl_module_load(const char **error_r);
+
+/* Returns 0 if ok, -1 and sets error_r if failed. The returned error string
+ becomes available via ssl_iostream_get_last_error(). The callback most
+ likely should be calling ssl_iostream_check_cert_validity(). */
+typedef int
+ssl_iostream_handshake_callback_t(const char **error_r, void *context);
+/* Called when TLS SNI becomes available. */
+typedef int ssl_iostream_sni_callback_t(const char *name, const char **error_r,
+ void *context);
+
+/* Explicitly initialize SSL library globally. This is also done automatically
+ when the first SSL connection is created, but it may be useful to call it
+ earlier in case of chrooting. After the initialization is successful, any
+ further calls will just be ignored. Returns 0 on success, -1 on error. */
+int io_stream_ssl_global_init(const struct ssl_iostream_settings *set,
+ const char **error_r);
+
+int io_stream_create_ssl_client(struct ssl_iostream_context *ctx, const char *host,
+ const struct ssl_iostream_settings *set,
+ struct istream **input, struct ostream **output,
+ struct ssl_iostream **iostream_r,
+ const char **error_r);
+int io_stream_create_ssl_server(struct ssl_iostream_context *ctx,
+ const struct ssl_iostream_settings *set,
+ struct istream **input, struct ostream **output,
+ struct ssl_iostream **iostream_r,
+ const char **error_r);
+/* Shutdown SSL connection and unreference ssl iostream.
+ The returned input and output streams must also be unreferenced. */
+void ssl_iostream_destroy(struct ssl_iostream **ssl_io);
+
+/* If verbose logging is enabled, use the specified log prefix */
+void ssl_iostream_set_log_prefix(struct ssl_iostream *ssl_io,
+ const char *prefix);
+
+int ssl_iostream_handshake(struct ssl_iostream *ssl_io);
+/* Call the given callback when SSL handshake finishes. The callback must
+ verify whether the certificate and its hostname is valid. If there is no
+ callback, the default is to use ssl_iostream_check_cert_validity() with the
+ same host as given to io_stream_create_ssl_client()
+
+ Before the callback is called, certificate is only checked for issuer
+ and validity period. You should call ssl_iostream_check_cert_validity()
+ in your callback.
+*/
+void ssl_iostream_set_handshake_callback(struct ssl_iostream *ssl_io,
+ ssl_iostream_handshake_callback_t *callback,
+ void *context);
+/* Call the given callback when client sends SNI. The callback can change the
+ ssl_iostream's context (with different certificates) by using
+ ssl_iostream_change_context(). */
+void ssl_iostream_set_sni_callback(struct ssl_iostream *ssl_io,
+ ssl_iostream_sni_callback_t *callback,
+ void *context);
+void ssl_iostream_change_context(struct ssl_iostream *ssl_io,
+ struct ssl_iostream_context *ctx);
+
+bool ssl_iostream_is_handshaked(const struct ssl_iostream *ssl_io);
+/* Returns TRUE if the remote cert is invalid, or handshake callback returned
+ failure. */
+bool ssl_iostream_has_handshake_failed(const struct ssl_iostream *ssl_io);
+bool ssl_iostream_has_valid_client_cert(const struct ssl_iostream *ssl_io);
+bool ssl_iostream_has_broken_client_cert(struct ssl_iostream *ssl_io);
+/* Checks certificate validity based, also performs name checking. Called by
+ default in handshake, unless handshake callback is set with
+ ssl_iostream_check_cert_validity().
+
+ Host should be set as the name you want to validate the certificate name(s)
+ against. Usually this is the host name you connected to.
+
+ This function is same as calling ssl_iostream_has_valid_client_cert()
+ and ssl_iostream_cert_match_name().
+ */
+int ssl_iostream_check_cert_validity(struct ssl_iostream *ssl_io,
+ const char *host, const char **error_r);
+/* Returns TRUE if the given name matches the SSL stream's certificate.
+ The returned reason is a human-readable string explaining what exactly
+ matched the name, or why nothing matched. Note that this function works
+ only if the certificate was valid - using it when certificate is invalid
+ will always return FALSE before even checking the hostname. */
+bool ssl_iostream_cert_match_name(struct ssl_iostream *ssl_io, const char *name,
+ const char **reason_r);
+const char *ssl_iostream_get_peer_name(struct ssl_iostream *ssl_io);
+const char *ssl_iostream_get_compression(struct ssl_iostream *ssl_io);
+const char *ssl_iostream_get_server_name(struct ssl_iostream *ssl_io);
+const char *ssl_iostream_get_security_string(struct ssl_iostream *ssl_io);
+/* Returns SSL context's current used cipher algorithm. Returns NULL
+ if SSL handshake has not been performed.
+
+ This returns values like 'AESGCM'
+*/
+const char *ssl_iostream_get_cipher(struct ssl_iostream *ssl_io,
+ unsigned int *bits_r);
+/* Returns currently used forward secrecy algorithm, if available.
+ Returns NULL if handshake not done yet, empty string if missing.
+
+ This returns values like 'DH', 'ECDH' etc..
+*/
+const char *ssl_iostream_get_pfs(struct ssl_iostream *ssl_io);
+/* Returns currently used SSL protocol name. Returns NULL if handshake
+ has not yet been made.
+
+ This returns values like SSLv3, TLSv1, TLSv1.1, TLSv1.2
+*/
+const char *ssl_iostream_get_protocol_name(struct ssl_iostream *ssl_io);
+
+const char *ssl_iostream_get_last_error(struct ssl_iostream *ssl_io);
+
+int ssl_iostream_context_init_client(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r);
+int ssl_iostream_context_init_server(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r);
+void ssl_iostream_context_ref(struct ssl_iostream_context *ctx);
+void ssl_iostream_context_unref(struct ssl_iostream_context **ctx);
+
+struct ssl_iostream_settings *ssl_iostream_settings_dup(pool_t pool,
+ const struct ssl_iostream_settings *old_set);
+void ssl_iostream_settings_init_from(pool_t pool,
+ struct ssl_iostream_settings *dest,
+ const struct ssl_iostream_settings *src);
+
+/* Persistent cache of ssl_iostream_contexts. The context is permanently stored
+ until ssl_iostream_context_cache_free() is called. The returned context
+ must be unreferenced by the caller. */
+int ssl_iostream_client_context_cache_get(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r);
+int ssl_iostream_server_context_cache_get(const struct ssl_iostream_settings *set,
+ struct ssl_iostream_context **ctx_r,
+ const char **error_r);
+void ssl_iostream_context_cache_free(void);
+
+#endif
diff --git a/src/lib-ssl-iostream/istream-openssl.c b/src/lib-ssl-iostream/istream-openssl.c
new file mode 100644
index 0000000..f0446d9
--- /dev/null
+++ b/src/lib-ssl-iostream/istream-openssl.c
@@ -0,0 +1,130 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream-private.h"
+#include "iostream-openssl.h"
+
+struct ssl_istream {
+ struct istream_private istream;
+ struct ssl_iostream *ssl_io;
+ bool seen_eof;
+};
+
+static void i_stream_ssl_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct ssl_istream *sstream = (struct ssl_istream *)stream;
+
+ if (close_parent)
+ i_stream_close(sstream->ssl_io->plain_input);
+}
+
+static void i_stream_ssl_destroy(struct iostream_private *stream)
+{
+ struct ssl_istream *sstream = (struct ssl_istream *)stream;
+
+ openssl_iostream_shutdown(sstream->ssl_io);
+ i_stream_free_buffer(&sstream->istream);
+ sstream->ssl_io->ssl_input = NULL;
+ ssl_iostream_unref(&sstream->ssl_io);
+}
+
+static ssize_t i_stream_ssl_read(struct istream_private *stream)
+{
+ struct ssl_istream *sstream = (struct ssl_istream *)stream;
+ struct ssl_iostream *ssl_io = sstream->ssl_io;
+ size_t size;
+ ssize_t ret, total_ret;
+
+ if (sstream->seen_eof) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+
+ if (!ssl_io->handshaked) {
+ if ((ret = ssl_iostream_handshake(ssl_io)) <= 0) {
+ if (ret < 0) {
+ /* handshake failed */
+ i_assert(errno != 0);
+ io_stream_set_error(&stream->iostream,
+ "%s", ssl_io->last_error);
+ stream->istream.stream_errno = errno;
+ }
+ return ret;
+ }
+ }
+ if (openssl_iostream_bio_sync(ssl_io,
+ OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE) < 0) {
+ i_assert(ssl_io->plain_stream_errno != 0 &&
+ ssl_io->plain_stream_errstr != NULL);
+ io_stream_set_error(&stream->iostream,
+ "%s", ssl_io->plain_stream_errstr);
+ stream->istream.stream_errno = ssl_io->plain_stream_errno;
+ return -1;
+ }
+
+ total_ret = 0;
+ for (;;) {
+ int pending = SSL_pending(ssl_io->ssl);
+
+ /* Allocate buffer space if needed. */
+ i_assert(stream->buffer_size >= stream->pos);
+ size = stream->buffer_size - stream->pos;
+ if ((pending > 0 || size == 0) &&
+ !i_stream_try_alloc(stream, I_MAX(pending, 1), &size)) {
+ if (total_ret > 0)
+ break;
+ return -2;
+ }
+
+ ret = SSL_read(ssl_io->ssl, stream->w_buffer + stream->pos, size);
+ if (ret <= 0) {
+ /* failed to read anything */
+ ret = openssl_iostream_handle_error(ssl_io, ret,
+ (total_ret == 0 ?
+ OPENSSL_IOSTREAM_SYNC_TYPE_CONTINUE_READ :
+ OPENSSL_IOSTREAM_SYNC_TYPE_NONE), "SSL_read");
+ if (ret <= 0) {
+ if (ret == 0)
+ break;
+ if (ssl_io->last_error != NULL) {
+ io_stream_set_error(&stream->iostream,
+ "%s", ssl_io->last_error);
+ }
+ if (errno != EPIPE)
+ stream->istream.stream_errno = errno;
+ stream->istream.eof = TRUE;
+ sstream->seen_eof = TRUE;
+ if (total_ret > 0)
+ break;
+ return -1;
+ }
+ /* we did some BIO I/O, try reading again */
+ continue;
+ }
+ stream->pos += ret;
+ total_ret += ret;
+ }
+ if (SSL_pending(ssl_io->ssl) > 0)
+ i_stream_set_input_pending(ssl_io->ssl_input, TRUE);
+ return total_ret;
+}
+
+struct istream *openssl_i_stream_create_ssl(struct ssl_iostream *ssl_io)
+{
+ struct ssl_istream *sstream;
+
+ ssl_io->refcount++;
+
+ sstream = i_new(struct ssl_istream, 1);
+ sstream->ssl_io = ssl_io;
+ sstream->istream.iostream.close = i_stream_ssl_close;
+ sstream->istream.iostream.destroy = i_stream_ssl_destroy;
+ sstream->istream.max_buffer_size =
+ ssl_io->plain_input->real_stream->max_buffer_size;
+ sstream->istream.read = i_stream_ssl_read;
+
+ sstream->istream.istream.readable_fd = FALSE;
+ return i_stream_create(&sstream->istream, NULL,
+ i_stream_get_fd(ssl_io->plain_input), 0);
+}
diff --git a/src/lib-ssl-iostream/ostream-openssl.c b/src/lib-ssl-iostream/ostream-openssl.c
new file mode 100644
index 0000000..8434a9b
--- /dev/null
+++ b/src/lib-ssl-iostream/ostream-openssl.c
@@ -0,0 +1,339 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "istream.h"
+#include "ostream-private.h"
+#include "iostream-openssl.h"
+
+struct ssl_ostream {
+ struct ostream_private ostream;
+ struct ssl_iostream *ssl_io;
+ buffer_t *buffer;
+
+ bool shutdown:1;
+};
+
+static void
+o_stream_ssl_close(struct iostream_private *stream, bool close_parent)
+{
+ struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
+
+ if (close_parent)
+ o_stream_close(sstream->ssl_io->plain_output);
+}
+
+static void o_stream_ssl_destroy(struct iostream_private *stream)
+{
+ struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
+ struct istream *ssl_input = sstream->ssl_io->ssl_input;
+
+ openssl_iostream_shutdown(sstream->ssl_io);
+ sstream->ssl_io->ssl_output = NULL;
+ i_stream_unref(&ssl_input);
+ ssl_iostream_unref(&sstream->ssl_io);
+ buffer_free(&sstream->buffer);
+}
+
+static size_t get_buffer_avail_size(const struct ssl_ostream *sstream)
+{
+ if (sstream->ostream.max_buffer_size == 0) {
+ if (sstream->buffer == NULL)
+ return 0;
+ /* we're requested to use whatever space is available in
+ the buffer */
+ return buffer_get_writable_size(sstream->buffer) - sstream->buffer->used;
+ } else {
+ size_t buffer_used = (sstream->buffer == NULL ? 0 :
+ sstream->buffer->used);
+ return sstream->ostream.max_buffer_size > buffer_used ?
+ sstream->ostream.max_buffer_size - buffer_used : 0;
+ }
+}
+
+static size_t
+o_stream_ssl_buffer(struct ssl_ostream *sstream, const struct const_iovec *iov,
+ unsigned int iov_count, size_t bytes_sent)
+{
+ size_t avail, skip_left, size;
+ unsigned int i;
+
+ if (sstream->buffer == NULL)
+ sstream->buffer = buffer_create_dynamic(default_pool,
+ I_MIN(IO_BLOCK_SIZE, sstream->ostream.max_buffer_size));
+
+ skip_left = bytes_sent;
+ for (i = 0; i < iov_count; i++) {
+ if (skip_left < iov[i].iov_len)
+ break;
+ skip_left -= iov[i].iov_len;
+ }
+
+ avail = get_buffer_avail_size(sstream);
+ if (i < iov_count && skip_left > 0) {
+ size = I_MIN(iov[i].iov_len - skip_left, avail);
+ buffer_append(sstream->buffer,
+ CONST_PTR_OFFSET(iov[i].iov_base, skip_left),
+ size);
+ bytes_sent += size;
+ avail -= size;
+ if (size != iov[i].iov_len)
+ i = iov_count;
+ }
+ if (avail > 0)
+ o_stream_set_flush_pending(sstream->ssl_io->plain_output, TRUE);
+
+ for (; i < iov_count; i++) {
+ size = I_MIN(iov[i].iov_len, avail);
+ buffer_append(sstream->buffer, iov[i].iov_base, size);
+ bytes_sent += size;
+ avail -= size;
+
+ if (size != iov[i].iov_len)
+ break;
+ }
+
+ sstream->ostream.ostream.offset += bytes_sent;
+ return bytes_sent;
+}
+
+static int o_stream_ssl_flush_buffer(struct ssl_ostream *sstream)
+{
+ struct ssl_iostream *ssl_io = sstream->ssl_io;
+ size_t pos = 0;
+ int ret = 1;
+
+ i_assert(!sstream->shutdown);
+
+ while (pos < sstream->buffer->used) {
+ /* we're writing plaintext data to OpenSSL, which it encrypts
+ and writes to bio_int's buffer. ssl_iostream_bio_sync()
+ reads it from there and adds to plain_output stream. */
+ ret = SSL_write(ssl_io->ssl,
+ CONST_PTR_OFFSET(sstream->buffer->data, pos),
+ sstream->buffer->used - pos);
+ if (ret <= 0) {
+ ret = openssl_iostream_handle_error(
+ ssl_io, ret, OPENSSL_IOSTREAM_SYNC_TYPE_WRITE,
+ "SSL_write");
+ if (ret < 0) {
+ io_stream_set_error(
+ &sstream->ostream.iostream,
+ "%s", ssl_io->last_error);
+ sstream->ostream.ostream.stream_errno = errno;
+ break;
+ }
+ if (ret == 0)
+ break;
+ } else {
+ pos += ret;
+ ret = openssl_iostream_bio_sync(
+ ssl_io, OPENSSL_IOSTREAM_SYNC_TYPE_WRITE);
+ if (ret < 0) {
+ i_assert(ssl_io->plain_stream_errstr != NULL &&
+ ssl_io->plain_stream_errno != 0);
+ io_stream_set_error(
+ &sstream->ostream.iostream,
+ "%s", ssl_io->plain_stream_errstr);
+ sstream->ostream.ostream.stream_errno =
+ ssl_io->plain_stream_errno;
+ break;
+ }
+ }
+ }
+ buffer_delete(sstream->buffer, 0, pos);
+ return ret <= 0 ? ret : 1;
+}
+
+static int o_stream_ssl_flush(struct ostream_private *stream)
+{
+ struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
+ struct ssl_iostream *ssl_io = sstream->ssl_io;
+ struct ostream *plain_output = ssl_io->plain_output;
+ int ret = 1;
+
+ if (!ssl_io->handshaked) {
+ if ((ret = ssl_iostream_handshake(ssl_io)) < 0) {
+ /* handshake failed */
+ i_assert(errno != 0);
+ io_stream_set_error(&stream->iostream,
+ "%s", ssl_io->last_error);
+ stream->ostream.stream_errno = errno;
+ return ret;
+ }
+ }
+ if (ret > 0 &&
+ openssl_iostream_bio_sync(
+ ssl_io, OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE) < 0) {
+ i_assert(ssl_io->plain_stream_errno != 0 &&
+ ssl_io->plain_stream_errstr != NULL);
+ io_stream_set_error(&stream->iostream,
+ "%s", ssl_io->plain_stream_errstr);
+ stream->ostream.stream_errno = ssl_io->plain_stream_errno;
+ return -1;
+ }
+
+ if (ret > 0 && sstream->buffer != NULL && sstream->buffer->used > 0) {
+ /* we can try to send some of our buffered data */
+ ret = o_stream_ssl_flush_buffer(sstream);
+ }
+
+ /* Stream is finished; shutdown the SSL write direction once our buffer
+ is empty. */
+ if (stream->finished && !sstream->shutdown && ret >= 0 &&
+ (sstream->buffer == NULL || sstream->buffer->used == 0)) {
+ sstream->shutdown = TRUE;
+ if (SSL_shutdown(ssl_io->ssl) < 0) {
+ io_stream_set_error(
+ &sstream->ostream.iostream, "%s",
+ t_strdup_printf("SSL_shutdown() failed: %s",
+ openssl_iostream_error()));
+ sstream->ostream.ostream.stream_errno = EIO;
+ ret = -1;
+ }
+ }
+
+ if (ret == 0 && ssl_io->want_read) {
+ /* we need to read more data until we can continue. */
+ o_stream_set_flush_pending(plain_output, FALSE);
+ ssl_io->ostream_flush_waiting_input = TRUE;
+ ret = 1;
+ }
+
+ if (ret <= 0)
+ return ret;
+
+ /* return 1 only when the output buffer is empty, which is what the
+ caller expects. */
+ return o_stream_get_buffer_used_size(plain_output) == 0 ? 1 : 0;
+}
+
+static ssize_t
+o_stream_ssl_sendv(struct ostream_private *stream,
+ const struct const_iovec *iov, unsigned int iov_count)
+{
+ struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
+ size_t bytes_sent = 0;
+
+ i_assert(!sstream->shutdown);
+
+ bytes_sent = o_stream_ssl_buffer(sstream, iov, iov_count, bytes_sent);
+ if (sstream->ssl_io->handshaked &&
+ sstream->buffer->used == bytes_sent) {
+ /* buffer was empty before calling this. try to write it
+ immediately. */
+ if (o_stream_ssl_flush_buffer(sstream) < 0)
+ return -1;
+ }
+ return bytes_sent;
+}
+
+static void o_stream_ssl_switch_ioloop_to(struct ostream_private *stream,
+ struct ioloop *ioloop)
+{
+ struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
+
+ o_stream_switch_ioloop_to(sstream->ssl_io->plain_output, ioloop);
+}
+
+static int plain_flush_callback(struct ssl_ostream *sstream)
+{
+ struct ostream *ostream = &sstream->ostream.ostream;
+ int ret, ret2;
+
+ /* try to actually flush the pending data */
+ if ((ret = o_stream_flush(sstream->ssl_io->plain_output)) < 0)
+ return -1;
+
+ /* we may be able to copy more data, try it */
+ o_stream_ref(ostream);
+ if (sstream->ostream.callback != NULL)
+ ret2 = sstream->ostream.callback(sstream->ostream.context);
+ else
+ ret2 = o_stream_flush(&sstream->ostream.ostream);
+ if (ret2 == 0)
+ o_stream_set_flush_pending(sstream->ssl_io->plain_output, TRUE);
+ o_stream_unref(&ostream);
+ if (ret2 < 0)
+ return -1;
+ return ret > 0 && ret2 > 0 ? 1 : 0;
+}
+
+static size_t
+o_stream_ssl_get_buffer_used_size(const struct ostream_private *stream)
+{
+ const struct ssl_ostream *sstream = (const struct ssl_ostream *)stream;
+ BIO *bio = SSL_get_wbio(sstream->ssl_io->ssl);
+ size_t wbuf_avail = BIO_ctrl_get_write_guarantee(bio);
+ size_t wbuf_total_size = BIO_get_write_buf_size(bio, 0);
+ size_t buffer_used = (sstream->buffer == NULL ? 0 :
+ sstream->buffer->used);
+ i_assert(wbuf_avail <= wbuf_total_size);
+ return buffer_used + (wbuf_total_size - wbuf_avail) +
+ o_stream_get_buffer_used_size(sstream->ssl_io->plain_output);
+}
+
+static size_t
+o_stream_ssl_get_buffer_avail_size(const struct ostream_private *stream)
+{
+ const struct ssl_ostream *sstream = (const struct ssl_ostream *)stream;
+
+ return get_buffer_avail_size(sstream);
+}
+
+static void
+o_stream_ssl_flush_pending(struct ostream_private *_stream, bool set)
+{
+ struct ssl_ostream *sstream = (struct ssl_ostream *)_stream;
+
+ o_stream_set_flush_pending(sstream->ssl_io->plain_output, set);
+}
+
+static void o_stream_ssl_set_max_buffer_size(struct iostream_private *_stream,
+ size_t max_size)
+{
+ struct ssl_ostream *sstream = (struct ssl_ostream *)_stream;
+
+ sstream->ostream.max_buffer_size = max_size;
+ o_stream_set_max_buffer_size(sstream->ssl_io->plain_output, max_size);
+}
+
+struct ostream *openssl_o_stream_create_ssl(struct ssl_iostream *ssl_io)
+{
+ struct ssl_ostream *sstream;
+
+ ssl_io->refcount++;
+
+ /* When ostream is destroyed, it's flushed. With iostream-ssl the
+ flushing requires both istream and ostream to be available. The
+ istream is referenced here to make sure it's not destroyed before
+ the ostream. */
+ i_assert(ssl_io->ssl_input != NULL);
+ i_stream_ref(ssl_io->ssl_input);
+
+ sstream = i_new(struct ssl_ostream, 1);
+ sstream->ssl_io = ssl_io;
+ sstream->ostream.max_buffer_size =
+ ssl_io->plain_output->real_stream->max_buffer_size;
+ sstream->ostream.iostream.close = o_stream_ssl_close;
+ sstream->ostream.iostream.destroy = o_stream_ssl_destroy;
+ sstream->ostream.sendv = o_stream_ssl_sendv;
+ sstream->ostream.flush = o_stream_ssl_flush;
+ sstream->ostream.switch_ioloop_to = o_stream_ssl_switch_ioloop_to;
+
+ sstream->ostream.get_buffer_used_size =
+ o_stream_ssl_get_buffer_used_size;
+ sstream->ostream.get_buffer_avail_size =
+ o_stream_ssl_get_buffer_avail_size;
+ sstream->ostream.flush_pending = o_stream_ssl_flush_pending;
+ sstream->ostream.iostream.set_max_buffer_size =
+ o_stream_ssl_set_max_buffer_size;
+
+ sstream->ostream.callback = ssl_io->plain_output->real_stream->callback;
+ sstream->ostream.context = ssl_io->plain_output->real_stream->context;
+ o_stream_set_flush_callback(ssl_io->plain_output,
+ plain_flush_callback, sstream);
+
+ return o_stream_create(&sstream->ostream, NULL,
+ o_stream_get_fd(ssl_io->plain_output));
+}
diff --git a/src/lib-ssl-iostream/test-iostream-ssl.c b/src/lib-ssl-iostream/test-iostream-ssl.c
new file mode 100644
index 0000000..b2533ac
--- /dev/null
+++ b/src/lib-ssl-iostream/test-iostream-ssl.c
@@ -0,0 +1,559 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "buffer.h"
+#include "randgen.h"
+#include "istream.h"
+#include "ostream.h"
+#include "iostream-openssl.h"
+#include "iostream-ssl.h"
+#include "iostream-ssl-test.h"
+
+#include <sys/socket.h>
+
+#define MAX_SENT_BYTES 10000
+
+struct test_endpoint {
+ pool_t pool;
+ int fd;
+ const char *hostname;
+ const struct ssl_iostream_settings *set;
+ struct ssl_iostream_context *ctx;
+ struct ssl_iostream *iostream;
+ struct istream *input;
+ struct ostream *output;
+ struct io *io;
+ buffer_t *last_write;
+ ssize_t sent;
+ bool client;
+ bool failed;
+
+ struct test_endpoint *other;
+
+ bool finished:1;
+};
+
+static void send_output(struct test_endpoint *ep)
+{
+ ssize_t amt = i_rand_limit(10)+1;
+ char data[amt];
+ random_fill(data, amt);
+ buffer_append(ep->other->last_write, data, amt);
+ test_assert(o_stream_send(ep->output, data, amt) == amt);
+ ep->sent += amt;
+}
+
+static int flush_output(struct test_endpoint *ep, bool finish)
+{
+ int ret = (finish ?
+ o_stream_finish(ep->output) : o_stream_flush(ep->output));
+ test_assert(ret >= 0);
+
+ if (ret > 0) {
+ if (finish)
+ ep->finished = TRUE;
+ if (ep->other->finished)
+ io_loop_stop(current_ioloop);
+ }
+ return ret;
+}
+
+static void handshake_input_callback(struct test_endpoint *ep)
+{
+ if (ep->failed)
+ return;
+ if (ssl_iostream_is_handshaked(ep->iostream)) {
+ if (ssl_iostream_is_handshaked(ep->other->iostream))
+ io_loop_stop(current_ioloop);
+ return;
+ }
+ if (ssl_iostream_handshake(ep->iostream) < 0) {
+ ep->failed = TRUE;
+ io_loop_stop(current_ioloop);
+ }
+}
+
+static int bufsize_flush_callback(struct test_endpoint *ep)
+{
+ io_loop_stop(current_ioloop);
+ return flush_output(ep, FALSE);
+}
+
+static int bufsize_finish_callback(struct test_endpoint *ep)
+{
+ return flush_output(ep, TRUE);
+}
+
+static void bufsize_input_callback(struct test_endpoint *ep)
+{
+ const unsigned char *data;
+ size_t size, wanted = i_rand_limit(512);
+
+ io_loop_stop(current_ioloop);
+ if (wanted == 0)
+ return;
+
+ test_assert(i_stream_read_bytes(ep->input, &data, &size, wanted) > -1);
+ i_stream_skip(ep->input, I_MIN(size, wanted));
+}
+
+static void bufsize_discard_callback(struct test_endpoint *ep)
+{
+ const unsigned char *data;
+ size_t size;
+
+ test_assert(i_stream_read_bytes(ep->input, &data, &size, 1) > -1 ||
+ ep->input->stream_errno == 0);
+ i_stream_skip(ep->input, size);
+}
+
+static int small_packets_flush_callback(struct test_endpoint *ep)
+{
+ return flush_output(ep, FALSE);
+}
+
+static void small_packets_input_callback(struct test_endpoint *ep)
+{
+ const unsigned char *data;
+ size_t size, wanted = i_rand_limit(10);
+ int ret;
+
+ if (wanted == 0) {
+ i_stream_set_input_pending(ep->input, TRUE);
+ return;
+ }
+
+ size = 0;
+ test_assert((ret = i_stream_read_bytes(ep->input, &data, &size, wanted)) > -1 ||
+ ep->input->stream_errno == 0);
+
+ if (size > wanted)
+ i_stream_set_input_pending(ep->input, TRUE);
+
+ size = I_MIN(size, wanted);
+
+ i_stream_skip(ep->input, size);
+ if (size > 0) {
+ test_assert(ep->last_write->used >= size);
+ if (ep->last_write->used >= size) {
+ test_assert(memcmp(ep->last_write->data, data, size) == 0);
+ /* remove the data that was wanted */
+ buffer_delete(ep->last_write, 0, size);
+ }
+ }
+
+ if (ep->sent > MAX_SENT_BYTES)
+ (void)flush_output(ep, TRUE);
+ else
+ send_output(ep);
+}
+
+static struct test_endpoint *
+create_test_endpoint(int fd, const struct ssl_iostream_settings *set)
+{
+ pool_t pool = pool_alloconly_create("ssl endpoint", 2048);
+ struct test_endpoint *ep = p_new(pool, struct test_endpoint, 1);
+ ep->pool = pool;
+ ep->fd = fd;
+ ep->input = i_stream_create_fd(ep->fd, 512);
+ ep->output = o_stream_create_fd(ep->fd, 1024);
+ o_stream_uncork(ep->output);
+ ep->set = ssl_iostream_settings_dup(pool, set);
+ ep->last_write = buffer_create_dynamic(pool, 1024);
+ return ep;
+}
+
+static void destroy_test_endpoint(struct test_endpoint **_ep)
+{
+ struct test_endpoint *ep = *_ep;
+ _ep = NULL;
+
+ io_remove(&ep->io);
+
+ i_stream_unref(&ep->input);
+ o_stream_unref(&ep->output);
+ ssl_iostream_destroy(&ep->iostream);
+ i_close_fd(&ep->fd);
+ if (ep->ctx != NULL)
+ ssl_iostream_context_unref(&ep->ctx);
+ pool_unref(&ep->pool);
+}
+
+static int test_iostream_ssl_handshake_real(struct ssl_iostream_settings *server_set,
+ struct ssl_iostream_settings *client_set,
+ const char *hostname)
+{
+ const char *error;
+ struct test_endpoint *server, *client;
+ int fd[2], ret = 0;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
+ i_fatal("socketpair() failed: %m");
+ fd_set_nonblock(fd[0], TRUE);
+ fd_set_nonblock(fd[1], TRUE);
+
+ server = create_test_endpoint(fd[0], server_set);
+ client = create_test_endpoint(fd[1], client_set);
+ client->hostname = hostname;
+ client->client = TRUE;
+
+ server->other = client;
+ client->other = server;
+
+ if (ssl_iostream_context_init_server(server->set, &server->ctx,
+ &error) < 0) {
+ i_error("server: %s", error);
+ destroy_test_endpoint(&client);
+ destroy_test_endpoint(&server);
+ return -1;
+ }
+ if (ssl_iostream_context_init_client(client->set, &client->ctx,
+ &error) < 0) {
+ i_error("client: %s", error);
+ destroy_test_endpoint(&client);
+ destroy_test_endpoint(&server);
+ return -1;
+ }
+
+ if (io_stream_create_ssl_server(server->ctx, server->set,
+ &server->input, &server->output,
+ &server->iostream, &error) != 0) {
+ ret = -1;
+ }
+
+ if (io_stream_create_ssl_client(client->ctx, client->hostname, client->set,
+ &client->input, &client->output,
+ &client->iostream, &error) != 0) {
+ ret = -1;
+ }
+
+ client->io = io_add_istream(client->input, handshake_input_callback, client);
+ server->io = io_add_istream(server->input, handshake_input_callback, server);
+
+ if (ssl_iostream_handshake(client->iostream) < 0)
+ return -1;
+
+ io_loop_run(current_ioloop);
+
+ if (client->failed || server->failed)
+ ret = -1;
+
+ if (ssl_iostream_has_handshake_failed(client->iostream)) {
+ i_error("client: %s", ssl_iostream_get_last_error(client->iostream));
+ ret = -1;
+ } else if (ssl_iostream_has_handshake_failed(server->iostream)) {
+ i_error("server: %s", ssl_iostream_get_last_error(server->iostream));
+ ret = -1;
+ /* check hostname */
+ } else if (client->hostname != NULL &&
+ !client->set->allow_invalid_cert &&
+ ssl_iostream_check_cert_validity(client->iostream, client->hostname,
+ &error) != 0) {
+ i_error("client(%s): %s", client->hostname, error);
+ ret = -1;
+ /* client cert */
+ } else if (server->set->verify_remote_cert &&
+ ssl_iostream_check_cert_validity(server->iostream, NULL, &error) != 0) {
+ i_error("server: %s", error);
+ ret = -1;
+ }
+
+ i_stream_unref(&server->input);
+ o_stream_unref(&server->output);
+ i_stream_unref(&client->input);
+ o_stream_unref(&client->output);
+
+ destroy_test_endpoint(&client);
+ destroy_test_endpoint(&server);
+
+ return ret;
+}
+
+static void test_iostream_ssl_handshake(void)
+{
+ struct ssl_iostream_settings server_set, client_set;
+ struct ioloop *ioloop;
+ int idx = 0;
+
+ test_begin("ssl: handshake");
+
+ ioloop = io_loop_create();
+
+ /* allow invalid cert, connect to localhost */
+ ssl_iostream_test_settings_server(&server_set);
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.allow_invalid_cert = TRUE;
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "localhost") == 0, idx);
+ idx++;
+
+ /* allow invalid cert, connect to failhost */
+ ssl_iostream_test_settings_server(&server_set);
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.allow_invalid_cert = TRUE;
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "failhost") == 0, idx);
+ idx++;
+
+ /* verify remote cert */
+ ssl_iostream_test_settings_server(&server_set);
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.verify_remote_cert = TRUE;
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "127.0.0.1") == 0, idx);
+ idx++;
+
+ /* verify remote cert, missing hostname */
+ ssl_iostream_test_settings_server(&server_set);
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.verify_remote_cert = TRUE;
+ test_expect_error_string("client: SSL certificate doesn't "
+ "match expected host name failhost");
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "failhost") != 0, idx);
+ idx++;
+
+ /* verify remote cert, missing CA */
+ ssl_iostream_test_settings_server(&server_set);
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.verify_remote_cert = TRUE;
+ client_set.ca = NULL;
+ test_expect_error_string("client: Received invalid SSL certificate");
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "127.0.0.1") != 0, idx);
+ idx++;
+
+ /* verify remote cert, require CRL */
+ ssl_iostream_test_settings_server(&server_set);
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.verify_remote_cert = TRUE;
+ client_set.skip_crl_check = FALSE;
+ test_expect_error_string("client: Received invalid SSL certificate");
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "127.0.0.1") != 0, idx);
+ idx++;
+
+ /* missing server credentials */
+ ssl_iostream_test_settings_server(&server_set);
+ server_set.cert.key = NULL;
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.verify_remote_cert = TRUE;
+ test_expect_error_string("client(failhost): SSL certificate not received");
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "failhost") != 0, idx);
+ idx++;
+ ssl_iostream_test_settings_server(&server_set);
+ server_set.cert.cert = NULL;
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.verify_remote_cert = TRUE;
+ test_expect_error_string("client(failhost): SSL certificate not received");
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "failhost") != 0, idx);
+ idx++;
+
+ /* invalid client credentials: missing credentials */
+ ssl_iostream_test_settings_server(&server_set);
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.verify_remote_cert = TRUE;
+ server_set.verify_remote_cert = TRUE;
+ server_set.ca = client_set.ca;
+ test_expect_error_string("server: SSL certificate not received");
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "127.0.0.1") != 0, idx);
+ idx++;
+
+ /* invalid client credentials: incorrect extended usage */
+ ssl_iostream_test_settings_server(&server_set);
+ ssl_iostream_test_settings_client(&client_set);
+ client_set.verify_remote_cert = TRUE;
+ server_set.verify_remote_cert = TRUE;
+ server_set.ca = client_set.ca;
+ client_set.cert = server_set.cert;
+ test_expect_error_string("server: SSL_accept() failed: error:");
+ test_assert_idx(test_iostream_ssl_handshake_real(&server_set, &client_set,
+ "127.0.0.1") != 0, idx);
+ idx++;
+
+ io_loop_destroy(&ioloop);
+
+ test_end();
+}
+
+static void test_iostream_ssl_get_buffer_avail_size(void)
+{
+ struct ssl_iostream_settings set;
+ struct test_endpoint *server, *client;
+ struct ioloop *ioloop;
+ int fd[2];
+ const char *error;
+
+ test_begin("ssl: o_stream_get_buffer_avail_size");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
+ i_fatal("socketpair() failed: %m");
+ fd_set_nonblock(fd[0], TRUE);
+ fd_set_nonblock(fd[1], TRUE);
+
+ ioloop = io_loop_create();
+
+ ssl_iostream_test_settings_server(&set);
+ server = create_test_endpoint(fd[0], &set);
+ ssl_iostream_test_settings_client(&set);
+ set.allow_invalid_cert = TRUE;
+ client = create_test_endpoint(fd[1], &set);
+ client->client = TRUE;
+
+ client->other = server;
+ server->other = client;
+
+ test_assert(ssl_iostream_context_init_server(server->set, &server->ctx,
+ &error) == 0);
+ test_assert(ssl_iostream_context_init_client(client->set, &client->ctx,
+ &error) == 0);
+
+ test_assert(io_stream_create_ssl_server(server->ctx, server->set,
+ &server->input, &server->output,
+ &server->iostream, &error) == 0);
+ test_assert(io_stream_create_ssl_client(client->ctx, "localhost", client->set,
+ &client->input, &client->output,
+ &client->iostream, &error) == 0);
+
+ o_stream_set_flush_callback(server->output, bufsize_flush_callback, server);
+ o_stream_set_flush_callback(client->output, bufsize_flush_callback, client);
+
+ server->io = io_add_istream(server->input, bufsize_input_callback, server);
+ client->io = io_add_istream(client->input, bufsize_input_callback, client);
+
+ test_assert(ssl_iostream_handshake(client->iostream) == 0);
+ test_assert(ssl_iostream_handshake(server->iostream) == 0);
+
+ for (unsigned int i = 0; i < 100000 && !test_has_failed(); i++) {
+ size_t avail = o_stream_get_buffer_avail_size(server->output);
+ if (avail > 0) {
+ void *buf = i_malloc(avail);
+ random_fill(buf, avail);
+ test_assert(o_stream_send(server->output, buf, avail) ==
+ (ssize_t)avail);
+ i_free(buf);
+ }
+ avail = o_stream_get_buffer_avail_size(client->output);
+ if (avail > 0) {
+ void *buf = i_malloc(avail);
+ random_fill(buf, avail);
+ test_assert(o_stream_send(client->output, buf, avail) ==
+ (ssize_t)avail);
+ i_free(buf);
+ }
+ io_loop_run(ioloop);
+ }
+
+ io_remove(&server->io);
+ io_remove(&client->io);
+ o_stream_set_flush_callback(server->output, bufsize_finish_callback, server);
+ o_stream_set_flush_callback(client->output, bufsize_finish_callback, client);
+ server->io = io_add_istream(server->input, bufsize_discard_callback, server);
+ client->io = io_add_istream(client->input, bufsize_discard_callback, client);
+ o_stream_set_flush_pending(server->output, TRUE);
+ o_stream_set_flush_pending(client->output, TRUE);
+ io_loop_run(ioloop);
+
+ i_stream_unref(&server->input);
+ o_stream_unref(&server->output);
+ i_stream_unref(&client->input);
+ o_stream_unref(&client->output);
+
+ destroy_test_endpoint(&client);
+ destroy_test_endpoint(&server);
+
+ io_loop_destroy(&ioloop);
+
+ test_end();
+}
+
+static void test_iostream_ssl_small_packets(void)
+{
+ struct ssl_iostream_settings set;
+ struct test_endpoint *server, *client;
+ struct ioloop *ioloop;
+ int fd[2];
+ const char *error;
+
+ test_begin("ssl: small packets");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
+ i_fatal("socketpair() failed: %m");
+ fd_set_nonblock(fd[0], TRUE);
+ fd_set_nonblock(fd[1], TRUE);
+
+ ioloop = io_loop_create();
+
+ ssl_iostream_test_settings_server(&set);
+ server = create_test_endpoint(fd[0], &set);
+ ssl_iostream_test_settings_client(&set);
+ set.allow_invalid_cert = TRUE;
+ client = create_test_endpoint(fd[1], &set);
+ client->client = TRUE;
+
+ test_assert(ssl_iostream_context_init_server(server->set, &server->ctx,
+ &error) == 0);
+ test_assert(ssl_iostream_context_init_client(client->set, &client->ctx,
+ &error) == 0);
+
+ client->other = server;
+ server->other = client;
+
+ test_assert(io_stream_create_ssl_server(server->ctx, server->set,
+ &server->input, &server->output,
+ &server->iostream, &error) == 0);
+ test_assert(io_stream_create_ssl_client(client->ctx, "localhost", client->set,
+ &client->input, &client->output,
+ &client->iostream, &error) == 0);
+
+ o_stream_set_flush_callback(server->output, small_packets_flush_callback,
+ server);
+ o_stream_set_flush_callback(client->output, small_packets_flush_callback,
+ client);
+
+ server->io = io_add_istream(server->input, small_packets_input_callback,
+ server);
+ client->io = io_add_istream(client->input, small_packets_input_callback,
+ client);
+
+ test_assert(ssl_iostream_handshake(client->iostream) == 0);
+ test_assert(ssl_iostream_handshake(server->iostream) == 0);
+
+ struct timeout *to = timeout_add(5000, io_loop_stop, ioloop);
+
+ io_loop_run(ioloop);
+
+ timeout_remove(&to);
+
+ test_assert(server->sent > MAX_SENT_BYTES ||
+ client->sent > MAX_SENT_BYTES);
+
+ i_stream_unref(&server->input);
+ o_stream_unref(&server->output);
+ i_stream_unref(&client->input);
+ o_stream_unref(&client->output);
+
+ destroy_test_endpoint(&server);
+ destroy_test_endpoint(&client);
+
+ io_loop_destroy(&ioloop);
+
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_iostream_ssl_handshake,
+ test_iostream_ssl_get_buffer_avail_size,
+ test_iostream_ssl_small_packets,
+ NULL
+ };
+ ssl_iostream_openssl_init();
+ int ret = test_run(test_functions);
+ ssl_iostream_openssl_deinit();
+ return ret;
+}