diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-ssl-iostream/Makefile.am | 61 | ||||
-rw-r--r-- | src/lib-ssl-iostream/Makefile.in | 970 | ||||
-rw-r--r-- | src/lib-ssl-iostream/dovecot-openssl-common.c | 132 | ||||
-rw-r--r-- | src/lib-ssl-iostream/dovecot-openssl-common.h | 16 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-openssl-common.c | 343 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-openssl-context.c | 755 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-openssl.c | 946 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-openssl.h | 129 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-ssl-context-cache.c | 129 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-ssl-private.h | 64 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-ssl-test.c | 158 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-ssl-test.h | 9 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-ssl.c | 351 | ||||
-rw-r--r-- | src/lib-ssl-iostream/iostream-ssl.h | 175 | ||||
-rw-r--r-- | src/lib-ssl-iostream/istream-openssl.c | 130 | ||||
-rw-r--r-- | src/lib-ssl-iostream/ostream-openssl.c | 339 | ||||
-rw-r--r-- | src/lib-ssl-iostream/test-iostream-ssl.c | 559 |
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; +} |