From f7548d6d28c313cf80e6f3ef89aed16a19815df1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:51:24 +0200 Subject: Adding upstream version 1:2.3.19.1+dfsg1. Signed-off-by: Daniel Baumann --- src/lib-smtp/Makefile.am | 192 ++ src/lib-smtp/Makefile.in | 1356 +++++++++ src/lib-smtp/fuzz-smtp-server.c | 102 + src/lib-smtp/smtp-address.c | 958 +++++++ src/lib-smtp/smtp-address.h | 214 ++ src/lib-smtp/smtp-client-command.c | 1580 +++++++++++ src/lib-smtp/smtp-client-command.h | 274 ++ src/lib-smtp/smtp-client-connection.c | 2522 +++++++++++++++++ src/lib-smtp/smtp-client-connection.h | 93 + src/lib-smtp/smtp-client-private.h | 340 +++ src/lib-smtp/smtp-client-transaction.c | 1656 +++++++++++ src/lib-smtp/smtp-client-transaction.h | 259 ++ src/lib-smtp/smtp-client.c | 142 + src/lib-smtp/smtp-client.h | 128 + src/lib-smtp/smtp-command-parser.c | 613 ++++ src/lib-smtp/smtp-command-parser.h | 51 + src/lib-smtp/smtp-command.h | 38 + src/lib-smtp/smtp-common.c | 91 + src/lib-smtp/smtp-common.h | 119 + src/lib-smtp/smtp-params.c | 1375 +++++++++ src/lib-smtp/smtp-params.h | 233 ++ src/lib-smtp/smtp-parser.c | 587 ++++ src/lib-smtp/smtp-parser.h | 89 + src/lib-smtp/smtp-reply-parser.c | 657 +++++ src/lib-smtp/smtp-reply-parser.h | 25 + src/lib-smtp/smtp-reply.c | 186 ++ src/lib-smtp/smtp-reply.h | 82 + src/lib-smtp/smtp-server-cmd-auth.c | 242 ++ src/lib-smtp/smtp-server-cmd-data.c | 679 +++++ src/lib-smtp/smtp-server-cmd-helo.c | 196 ++ src/lib-smtp/smtp-server-cmd-mail.c | 216 ++ src/lib-smtp/smtp-server-cmd-noop.c | 52 + src/lib-smtp/smtp-server-cmd-quit.c | 42 + src/lib-smtp/smtp-server-cmd-rcpt.c | 243 ++ src/lib-smtp/smtp-server-cmd-rset.c | 70 + src/lib-smtp/smtp-server-cmd-starttls.c | 179 ++ src/lib-smtp/smtp-server-cmd-vrfy.c | 73 + src/lib-smtp/smtp-server-cmd-xclient.c | 234 ++ src/lib-smtp/smtp-server-command.c | 901 ++++++ src/lib-smtp/smtp-server-connection.c | 1648 +++++++++++ src/lib-smtp/smtp-server-private.h | 404 +++ src/lib-smtp/smtp-server-recipient.c | 361 +++ src/lib-smtp/smtp-server-reply.c | 876 ++++++ src/lib-smtp/smtp-server-transaction.c | 361 +++ src/lib-smtp/smtp-server.c | 153 + src/lib-smtp/smtp-server.h | 802 ++++++ src/lib-smtp/smtp-submit-settings.c | 69 + src/lib-smtp/smtp-submit-settings.h | 17 + src/lib-smtp/smtp-submit.c | 505 ++++ src/lib-smtp/smtp-submit.h | 73 + src/lib-smtp/smtp-syntax.c | 327 +++ src/lib-smtp/smtp-syntax.h | 49 + src/lib-smtp/test-bin/sendmail-exit-1.sh | 4 + src/lib-smtp/test-bin/sendmail-success.sh | 4 + src/lib-smtp/test-smtp-address.c | 1382 +++++++++ src/lib-smtp/test-smtp-client-errors.c | 4374 +++++++++++++++++++++++++++++ src/lib-smtp/test-smtp-command-parser.c | 643 +++++ src/lib-smtp/test-smtp-params.c | 894 ++++++ src/lib-smtp/test-smtp-payload.c | 1148 ++++++++ src/lib-smtp/test-smtp-reply.c | 279 ++ src/lib-smtp/test-smtp-server-errors.c | 3883 +++++++++++++++++++++++++ src/lib-smtp/test-smtp-submit.c | 2199 +++++++++++++++ src/lib-smtp/test-smtp-syntax.c | 150 + 63 files changed, 37694 insertions(+) create mode 100644 src/lib-smtp/Makefile.am create mode 100644 src/lib-smtp/Makefile.in create mode 100644 src/lib-smtp/fuzz-smtp-server.c create mode 100644 src/lib-smtp/smtp-address.c create mode 100644 src/lib-smtp/smtp-address.h create mode 100644 src/lib-smtp/smtp-client-command.c create mode 100644 src/lib-smtp/smtp-client-command.h create mode 100644 src/lib-smtp/smtp-client-connection.c create mode 100644 src/lib-smtp/smtp-client-connection.h create mode 100644 src/lib-smtp/smtp-client-private.h create mode 100644 src/lib-smtp/smtp-client-transaction.c create mode 100644 src/lib-smtp/smtp-client-transaction.h create mode 100644 src/lib-smtp/smtp-client.c create mode 100644 src/lib-smtp/smtp-client.h create mode 100644 src/lib-smtp/smtp-command-parser.c create mode 100644 src/lib-smtp/smtp-command-parser.h create mode 100644 src/lib-smtp/smtp-command.h create mode 100644 src/lib-smtp/smtp-common.c create mode 100644 src/lib-smtp/smtp-common.h create mode 100644 src/lib-smtp/smtp-params.c create mode 100644 src/lib-smtp/smtp-params.h create mode 100644 src/lib-smtp/smtp-parser.c create mode 100644 src/lib-smtp/smtp-parser.h create mode 100644 src/lib-smtp/smtp-reply-parser.c create mode 100644 src/lib-smtp/smtp-reply-parser.h create mode 100644 src/lib-smtp/smtp-reply.c create mode 100644 src/lib-smtp/smtp-reply.h create mode 100644 src/lib-smtp/smtp-server-cmd-auth.c create mode 100644 src/lib-smtp/smtp-server-cmd-data.c create mode 100644 src/lib-smtp/smtp-server-cmd-helo.c create mode 100644 src/lib-smtp/smtp-server-cmd-mail.c create mode 100644 src/lib-smtp/smtp-server-cmd-noop.c create mode 100644 src/lib-smtp/smtp-server-cmd-quit.c create mode 100644 src/lib-smtp/smtp-server-cmd-rcpt.c create mode 100644 src/lib-smtp/smtp-server-cmd-rset.c create mode 100644 src/lib-smtp/smtp-server-cmd-starttls.c create mode 100644 src/lib-smtp/smtp-server-cmd-vrfy.c create mode 100644 src/lib-smtp/smtp-server-cmd-xclient.c create mode 100644 src/lib-smtp/smtp-server-command.c create mode 100644 src/lib-smtp/smtp-server-connection.c create mode 100644 src/lib-smtp/smtp-server-private.h create mode 100644 src/lib-smtp/smtp-server-recipient.c create mode 100644 src/lib-smtp/smtp-server-reply.c create mode 100644 src/lib-smtp/smtp-server-transaction.c create mode 100644 src/lib-smtp/smtp-server.c create mode 100644 src/lib-smtp/smtp-server.h create mode 100644 src/lib-smtp/smtp-submit-settings.c create mode 100644 src/lib-smtp/smtp-submit-settings.h create mode 100644 src/lib-smtp/smtp-submit.c create mode 100644 src/lib-smtp/smtp-submit.h create mode 100644 src/lib-smtp/smtp-syntax.c create mode 100644 src/lib-smtp/smtp-syntax.h create mode 100755 src/lib-smtp/test-bin/sendmail-exit-1.sh create mode 100755 src/lib-smtp/test-bin/sendmail-success.sh create mode 100644 src/lib-smtp/test-smtp-address.c create mode 100644 src/lib-smtp/test-smtp-client-errors.c create mode 100644 src/lib-smtp/test-smtp-command-parser.c create mode 100644 src/lib-smtp/test-smtp-params.c create mode 100644 src/lib-smtp/test-smtp-payload.c create mode 100644 src/lib-smtp/test-smtp-reply.c create mode 100644 src/lib-smtp/test-smtp-server-errors.c create mode 100644 src/lib-smtp/test-smtp-submit.c create mode 100644 src/lib-smtp/test-smtp-syntax.c (limited to 'src/lib-smtp') diff --git a/src/lib-smtp/Makefile.am b/src/lib-smtp/Makefile.am new file mode 100644 index 0000000..a16dc68 --- /dev/null +++ b/src/lib-smtp/Makefile.am @@ -0,0 +1,192 @@ +noinst_LTLIBRARIES = libsmtp.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-sasl \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-dns \ + -I$(top_srcdir)/src/lib-program-client \ + -I$(top_srcdir)/src/lib-mail \ + -DTEST_BIN_DIR=\"$(abs_srcdir)/test-bin\" + +smtp_server_cmds = \ + smtp-server-cmd-helo.c \ + smtp-server-cmd-starttls.c \ + smtp-server-cmd-auth.c \ + smtp-server-cmd-mail.c \ + smtp-server-cmd-rcpt.c \ + smtp-server-cmd-data.c \ + smtp-server-cmd-rset.c \ + smtp-server-cmd-noop.c \ + smtp-server-cmd-quit.c \ + smtp-server-cmd-vrfy.c \ + smtp-server-cmd-xclient.c + +libsmtp_la_SOURCES = \ + smtp-parser.c \ + smtp-syntax.c \ + smtp-address.c \ + smtp-common.c \ + smtp-params.c \ + smtp-reply.c \ + smtp-reply-parser.c \ + smtp-command-parser.c \ + smtp-client-command.c \ + smtp-client-transaction.c \ + smtp-client-connection.c \ + smtp-client.c \ + $(smtp_server_cmds) \ + smtp-server-reply.c \ + smtp-server-command.c \ + smtp-server-recipient.c \ + smtp-server-transaction.c \ + smtp-server-connection.c \ + smtp-server.c \ + smtp-submit-settings.c \ + smtp-submit.c + +headers = \ + smtp-parser.h \ + smtp-syntax.h \ + smtp-address.h \ + smtp-common.h \ + smtp-params.h \ + smtp-reply.h \ + smtp-reply-parser.h \ + smtp-command.h \ + smtp-command-parser.h \ + smtp-client-command.h \ + smtp-client-transaction.h \ + smtp-client-connection.h \ + smtp-client-private.h \ + smtp-client.h \ + smtp-server-private.h \ + smtp-server.h \ + smtp-submit-settings.h \ + smtp-submit.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + +test_programs = \ + test-smtp-syntax \ + test-smtp-address \ + test-smtp-params \ + test-smtp-reply \ + test-smtp-command-parser \ + test-smtp-payload \ + test-smtp-submit \ + test-smtp-client-errors \ + test-smtp-server-errors + +test_nocheck_programs = + +fuzz_programs = + +if USE_FUZZER +fuzz_programs += \ + fuzz-smtp-server +endif + +noinst_PROGRAMS = $(fuzz_programs) $(test_programs) $(test_nocheck_programs) + +EXTRA_DIST = \ + test-bin/sendmail-exit-1.sh \ + test-bin/sendmail-success.sh + +test_libs = \ + $(noinst_LTLIBRARIES) \ + ../lib-program-client/libprogram_client.la \ + ../lib-dns/libdns.la \ + ../lib-mail/libmail.la \ + ../lib-charset/libcharset.la \ + ../lib-master/libmaster.la \ + ../lib-auth/libauth.la \ + ../lib-ssl-iostream/libssl_iostream.la \ + ../lib-settings/libsettings.la \ + ../lib-sasl/libsasl.la \ + ../lib-test/libtest.la \ + ../lib/liblib.la \ + $(MODULE_LIBS) + +test_deps = \ + $(noinst_LTLIBRARIES) \ + ../lib-program-client/libprogram_client.la \ + ../lib-dns/libdns.la \ + ../lib-mail/libmail.la \ + ../lib-charset/libcharset.la \ + ../lib-master/libmaster.la \ + ../lib-auth/libauth.la \ + ../lib-ssl-iostream/libssl_iostream.la \ + ../lib-settings/libsettings.la \ + ../lib-sasl/libsasl.la \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_libs_ssl= +if BUILD_OPENSSL +test_libs_ssl += ../lib-ssl-iostream/libssl_iostream_openssl.la +endif + +test_smtp_syntax_SOURCES = test-smtp-syntax.c +test_smtp_syntax_LDADD = $(test_libs) +test_smtp_syntax_DEPENDENCIES = $(test_deps) + +test_smtp_address_SOURCES = test-smtp-address.c +test_smtp_address_LDFLAGS = -export-dynamic +test_smtp_address_LDADD = $(test_libs) +test_smtp_address_DEPENDENCIES = $(test_deps) + +test_smtp_params_SOURCES = test-smtp-params.c +test_smtp_params_LDFLAGS = -export-dynamic +test_smtp_params_LDADD = $(test_libs) +test_smtp_params_DEPENDENCIES = $(test_deps) + +test_smtp_reply_SOURCES = test-smtp-reply.c +test_smtp_reply_LDFLAGS = -export-dynamic +test_smtp_reply_LDADD = $(test_libs) +test_smtp_reply_DEPENDENCIES = $(test_deps) + +test_smtp_command_parser_SOURCES = test-smtp-command-parser.c +test_smtp_command_parser_LDFLAGS = -export-dynamic +test_smtp_command_parser_LDADD = $(test_libs) +test_smtp_command_parser_DEPENDENCIES = $(test_deps) + +test_smtp_payload_SOURCES = test-smtp-payload.c +test_smtp_payload_LDFLAGS = -export-dynamic +test_smtp_payload_LDADD = $(test_libs) $(test_libs_ssl) +test_smtp_payload_DEPENDENCIES = $(test_deps) + +test_smtp_submit_SOURCES = test-smtp-submit.c +test_smtp_submit_LDFLAGS = -export-dynamic +test_smtp_submit_LDADD = $(test_libs) +test_smtp_submit_DEPENDENCIES = $(test_deps) + +test_smtp_client_errors_SOURCES = test-smtp-client-errors.c +test_smtp_client_errors_LDFLAGS = -export-dynamic +test_smtp_client_errors_LDADD = $(test_libs) $(test_libs_ssl) +test_smtp_client_errors_DEPENDENCIES = $(test_deps) + +test_smtp_server_errors_SOURCES = test-smtp-server-errors.c +test_smtp_server_errors_LDFLAGS = -export-dynamic +test_smtp_server_errors_LDADD = $(test_libs) +test_smtp_server_errors_DEPENDENCIES = $(test_deps) + +nodist_EXTRA_fuzz_smtp_server_SOURCES = force-cxx-linking.cxx +fuzz_smtp_server_CPPFLAGS = $(FUZZER_CPPFLAGS) +fuzz_smtp_server_LDFLAGS = $(FUZZER_LDFLAGS) +fuzz_smtp_server_SOURCES = fuzz-smtp-server.c +fuzz_smtp_server_LDADD = $(test_libs) +fuzz_smtp_server_DEPENDENCIES = $(test_deps) + +check-local: + for bin in $(test_programs); do \ + if test "$$bin" = "test-smtp-submit"; then \ + if ! env NOCHILDREN=yes $(RUN_TEST) ./$$bin; then exit 1; fi; \ + else \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + fi \ + done diff --git a/src/lib-smtp/Makefile.in b/src/lib-smtp/Makefile.in new file mode 100644 index 0000000..8548807 --- /dev/null +++ b/src/lib-smtp/Makefile.in @@ -0,0 +1,1356 @@ +# 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@ +@USE_FUZZER_TRUE@am__append_1 = \ +@USE_FUZZER_TRUE@ fuzz-smtp-server + +noinst_PROGRAMS = $(am__EXEEXT_2) $(am__EXEEXT_3) $(am__EXEEXT_4) +@BUILD_OPENSSL_TRUE@am__append_2 = ../lib-ssl-iostream/libssl_iostream_openssl.la +subdir = src/lib-smtp +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 $(pkginc_lib_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +@USE_FUZZER_TRUE@am__EXEEXT_1 = fuzz-smtp-server$(EXEEXT) +am__EXEEXT_2 = $(am__EXEEXT_1) +am__EXEEXT_3 = test-smtp-syntax$(EXEEXT) test-smtp-address$(EXEEXT) \ + test-smtp-params$(EXEEXT) test-smtp-reply$(EXEEXT) \ + test-smtp-command-parser$(EXEEXT) test-smtp-payload$(EXEEXT) \ + test-smtp-submit$(EXEEXT) test-smtp-client-errors$(EXEEXT) \ + test-smtp-server-errors$(EXEEXT) +am__EXEEXT_4 = +PROGRAMS = $(noinst_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +libsmtp_la_LIBADD = +am__objects_1 = smtp-server-cmd-helo.lo smtp-server-cmd-starttls.lo \ + smtp-server-cmd-auth.lo smtp-server-cmd-mail.lo \ + smtp-server-cmd-rcpt.lo smtp-server-cmd-data.lo \ + smtp-server-cmd-rset.lo smtp-server-cmd-noop.lo \ + smtp-server-cmd-quit.lo smtp-server-cmd-vrfy.lo \ + smtp-server-cmd-xclient.lo +am_libsmtp_la_OBJECTS = smtp-parser.lo smtp-syntax.lo smtp-address.lo \ + smtp-common.lo smtp-params.lo smtp-reply.lo \ + smtp-reply-parser.lo smtp-command-parser.lo \ + smtp-client-command.lo smtp-client-transaction.lo \ + smtp-client-connection.lo smtp-client.lo $(am__objects_1) \ + smtp-server-reply.lo smtp-server-command.lo \ + smtp-server-recipient.lo smtp-server-transaction.lo \ + smtp-server-connection.lo smtp-server.lo \ + smtp-submit-settings.lo smtp-submit.lo +libsmtp_la_OBJECTS = $(am_libsmtp_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_fuzz_smtp_server_OBJECTS = \ + fuzz_smtp_server-fuzz-smtp-server.$(OBJEXT) +fuzz_smtp_server_OBJECTS = $(am_fuzz_smtp_server_OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = $(noinst_LTLIBRARIES) \ + ../lib-program-client/libprogram_client.la \ + ../lib-dns/libdns.la ../lib-mail/libmail.la \ + ../lib-charset/libcharset.la ../lib-master/libmaster.la \ + ../lib-auth/libauth.la ../lib-ssl-iostream/libssl_iostream.la \ + ../lib-settings/libsettings.la ../lib-sasl/libsasl.la \ + ../lib-test/libtest.la ../lib/liblib.la $(am__DEPENDENCIES_1) +fuzz_smtp_server_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(fuzz_smtp_server_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_smtp_address_OBJECTS = test-smtp-address.$(OBJEXT) +test_smtp_address_OBJECTS = $(am_test_smtp_address_OBJECTS) +test_smtp_address_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(test_smtp_address_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_test_smtp_client_errors_OBJECTS = \ + test-smtp-client-errors.$(OBJEXT) +test_smtp_client_errors_OBJECTS = \ + $(am_test_smtp_client_errors_OBJECTS) +test_smtp_client_errors_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(test_smtp_client_errors_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_smtp_command_parser_OBJECTS = \ + test-smtp-command-parser.$(OBJEXT) +test_smtp_command_parser_OBJECTS = \ + $(am_test_smtp_command_parser_OBJECTS) +test_smtp_command_parser_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(test_smtp_command_parser_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_smtp_params_OBJECTS = test-smtp-params.$(OBJEXT) +test_smtp_params_OBJECTS = $(am_test_smtp_params_OBJECTS) +test_smtp_params_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(test_smtp_params_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_test_smtp_payload_OBJECTS = test-smtp-payload.$(OBJEXT) +test_smtp_payload_OBJECTS = $(am_test_smtp_payload_OBJECTS) +test_smtp_payload_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(test_smtp_payload_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_test_smtp_reply_OBJECTS = test-smtp-reply.$(OBJEXT) +test_smtp_reply_OBJECTS = $(am_test_smtp_reply_OBJECTS) +test_smtp_reply_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(test_smtp_reply_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_test_smtp_server_errors_OBJECTS = \ + test-smtp-server-errors.$(OBJEXT) +test_smtp_server_errors_OBJECTS = \ + $(am_test_smtp_server_errors_OBJECTS) +test_smtp_server_errors_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(test_smtp_server_errors_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_smtp_submit_OBJECTS = test-smtp-submit.$(OBJEXT) +test_smtp_submit_OBJECTS = $(am_test_smtp_submit_OBJECTS) +test_smtp_submit_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(test_smtp_submit_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_test_smtp_syntax_OBJECTS = test-smtp-syntax.$(OBJEXT) +test_smtp_syntax_OBJECTS = $(am_test_smtp_syntax_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)/fuzz_smtp_server-force-cxx-linking.Po \ + ./$(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Po \ + ./$(DEPDIR)/smtp-address.Plo \ + ./$(DEPDIR)/smtp-client-command.Plo \ + ./$(DEPDIR)/smtp-client-connection.Plo \ + ./$(DEPDIR)/smtp-client-transaction.Plo \ + ./$(DEPDIR)/smtp-client.Plo \ + ./$(DEPDIR)/smtp-command-parser.Plo \ + ./$(DEPDIR)/smtp-common.Plo ./$(DEPDIR)/smtp-params.Plo \ + ./$(DEPDIR)/smtp-parser.Plo ./$(DEPDIR)/smtp-reply-parser.Plo \ + ./$(DEPDIR)/smtp-reply.Plo \ + ./$(DEPDIR)/smtp-server-cmd-auth.Plo \ + ./$(DEPDIR)/smtp-server-cmd-data.Plo \ + ./$(DEPDIR)/smtp-server-cmd-helo.Plo \ + ./$(DEPDIR)/smtp-server-cmd-mail.Plo \ + ./$(DEPDIR)/smtp-server-cmd-noop.Plo \ + ./$(DEPDIR)/smtp-server-cmd-quit.Plo \ + ./$(DEPDIR)/smtp-server-cmd-rcpt.Plo \ + ./$(DEPDIR)/smtp-server-cmd-rset.Plo \ + ./$(DEPDIR)/smtp-server-cmd-starttls.Plo \ + ./$(DEPDIR)/smtp-server-cmd-vrfy.Plo \ + ./$(DEPDIR)/smtp-server-cmd-xclient.Plo \ + ./$(DEPDIR)/smtp-server-command.Plo \ + ./$(DEPDIR)/smtp-server-connection.Plo \ + ./$(DEPDIR)/smtp-server-recipient.Plo \ + ./$(DEPDIR)/smtp-server-reply.Plo \ + ./$(DEPDIR)/smtp-server-transaction.Plo \ + ./$(DEPDIR)/smtp-server.Plo \ + ./$(DEPDIR)/smtp-submit-settings.Plo \ + ./$(DEPDIR)/smtp-submit.Plo ./$(DEPDIR)/smtp-syntax.Plo \ + ./$(DEPDIR)/test-smtp-address.Po \ + ./$(DEPDIR)/test-smtp-client-errors.Po \ + ./$(DEPDIR)/test-smtp-command-parser.Po \ + ./$(DEPDIR)/test-smtp-params.Po \ + ./$(DEPDIR)/test-smtp-payload.Po \ + ./$(DEPDIR)/test-smtp-reply.Po \ + ./$(DEPDIR)/test-smtp-server-errors.Po \ + ./$(DEPDIR)/test-smtp-submit.Po \ + ./$(DEPDIR)/test-smtp-syntax.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 = +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libsmtp_la_SOURCES) $(fuzz_smtp_server_SOURCES) \ + $(nodist_EXTRA_fuzz_smtp_server_SOURCES) \ + $(test_smtp_address_SOURCES) \ + $(test_smtp_client_errors_SOURCES) \ + $(test_smtp_command_parser_SOURCES) \ + $(test_smtp_params_SOURCES) $(test_smtp_payload_SOURCES) \ + $(test_smtp_reply_SOURCES) $(test_smtp_server_errors_SOURCES) \ + $(test_smtp_submit_SOURCES) $(test_smtp_syntax_SOURCES) +DIST_SOURCES = $(libsmtp_la_SOURCES) $(fuzz_smtp_server_SOURCES) \ + $(test_smtp_address_SOURCES) \ + $(test_smtp_client_errors_SOURCES) \ + $(test_smtp_command_parser_SOURCES) \ + $(test_smtp_params_SOURCES) $(test_smtp_payload_SOURCES) \ + $(test_smtp_reply_SOURCES) $(test_smtp_server_errors_SOURCES) \ + $(test_smtp_submit_SOURCES) $(test_smtp_syntax_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +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)$(pkginc_libdir)" +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 = @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 = libsmtp.la +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-sasl \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-dns \ + -I$(top_srcdir)/src/lib-program-client \ + -I$(top_srcdir)/src/lib-mail \ + -DTEST_BIN_DIR=\"$(abs_srcdir)/test-bin\" + +smtp_server_cmds = \ + smtp-server-cmd-helo.c \ + smtp-server-cmd-starttls.c \ + smtp-server-cmd-auth.c \ + smtp-server-cmd-mail.c \ + smtp-server-cmd-rcpt.c \ + smtp-server-cmd-data.c \ + smtp-server-cmd-rset.c \ + smtp-server-cmd-noop.c \ + smtp-server-cmd-quit.c \ + smtp-server-cmd-vrfy.c \ + smtp-server-cmd-xclient.c + +libsmtp_la_SOURCES = \ + smtp-parser.c \ + smtp-syntax.c \ + smtp-address.c \ + smtp-common.c \ + smtp-params.c \ + smtp-reply.c \ + smtp-reply-parser.c \ + smtp-command-parser.c \ + smtp-client-command.c \ + smtp-client-transaction.c \ + smtp-client-connection.c \ + smtp-client.c \ + $(smtp_server_cmds) \ + smtp-server-reply.c \ + smtp-server-command.c \ + smtp-server-recipient.c \ + smtp-server-transaction.c \ + smtp-server-connection.c \ + smtp-server.c \ + smtp-submit-settings.c \ + smtp-submit.c + +headers = \ + smtp-parser.h \ + smtp-syntax.h \ + smtp-address.h \ + smtp-common.h \ + smtp-params.h \ + smtp-reply.h \ + smtp-reply-parser.h \ + smtp-command.h \ + smtp-command-parser.h \ + smtp-client-command.h \ + smtp-client-transaction.h \ + smtp-client-connection.h \ + smtp-client-private.h \ + smtp-client.h \ + smtp-server-private.h \ + smtp-server.h \ + smtp-submit-settings.h \ + smtp-submit.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +test_programs = \ + test-smtp-syntax \ + test-smtp-address \ + test-smtp-params \ + test-smtp-reply \ + test-smtp-command-parser \ + test-smtp-payload \ + test-smtp-submit \ + test-smtp-client-errors \ + test-smtp-server-errors + +test_nocheck_programs = +fuzz_programs = $(am__append_1) +EXTRA_DIST = \ + test-bin/sendmail-exit-1.sh \ + test-bin/sendmail-success.sh + +test_libs = \ + $(noinst_LTLIBRARIES) \ + ../lib-program-client/libprogram_client.la \ + ../lib-dns/libdns.la \ + ../lib-mail/libmail.la \ + ../lib-charset/libcharset.la \ + ../lib-master/libmaster.la \ + ../lib-auth/libauth.la \ + ../lib-ssl-iostream/libssl_iostream.la \ + ../lib-settings/libsettings.la \ + ../lib-sasl/libsasl.la \ + ../lib-test/libtest.la \ + ../lib/liblib.la \ + $(MODULE_LIBS) + +test_deps = \ + $(noinst_LTLIBRARIES) \ + ../lib-program-client/libprogram_client.la \ + ../lib-dns/libdns.la \ + ../lib-mail/libmail.la \ + ../lib-charset/libcharset.la \ + ../lib-master/libmaster.la \ + ../lib-auth/libauth.la \ + ../lib-ssl-iostream/libssl_iostream.la \ + ../lib-settings/libsettings.la \ + ../lib-sasl/libsasl.la \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_libs_ssl = $(am__append_2) +test_smtp_syntax_SOURCES = test-smtp-syntax.c +test_smtp_syntax_LDADD = $(test_libs) +test_smtp_syntax_DEPENDENCIES = $(test_deps) +test_smtp_address_SOURCES = test-smtp-address.c +test_smtp_address_LDFLAGS = -export-dynamic +test_smtp_address_LDADD = $(test_libs) +test_smtp_address_DEPENDENCIES = $(test_deps) +test_smtp_params_SOURCES = test-smtp-params.c +test_smtp_params_LDFLAGS = -export-dynamic +test_smtp_params_LDADD = $(test_libs) +test_smtp_params_DEPENDENCIES = $(test_deps) +test_smtp_reply_SOURCES = test-smtp-reply.c +test_smtp_reply_LDFLAGS = -export-dynamic +test_smtp_reply_LDADD = $(test_libs) +test_smtp_reply_DEPENDENCIES = $(test_deps) +test_smtp_command_parser_SOURCES = test-smtp-command-parser.c +test_smtp_command_parser_LDFLAGS = -export-dynamic +test_smtp_command_parser_LDADD = $(test_libs) +test_smtp_command_parser_DEPENDENCIES = $(test_deps) +test_smtp_payload_SOURCES = test-smtp-payload.c +test_smtp_payload_LDFLAGS = -export-dynamic +test_smtp_payload_LDADD = $(test_libs) $(test_libs_ssl) +test_smtp_payload_DEPENDENCIES = $(test_deps) +test_smtp_submit_SOURCES = test-smtp-submit.c +test_smtp_submit_LDFLAGS = -export-dynamic +test_smtp_submit_LDADD = $(test_libs) +test_smtp_submit_DEPENDENCIES = $(test_deps) +test_smtp_client_errors_SOURCES = test-smtp-client-errors.c +test_smtp_client_errors_LDFLAGS = -export-dynamic +test_smtp_client_errors_LDADD = $(test_libs) $(test_libs_ssl) +test_smtp_client_errors_DEPENDENCIES = $(test_deps) +test_smtp_server_errors_SOURCES = test-smtp-server-errors.c +test_smtp_server_errors_LDFLAGS = -export-dynamic +test_smtp_server_errors_LDADD = $(test_libs) +test_smtp_server_errors_DEPENDENCIES = $(test_deps) +nodist_EXTRA_fuzz_smtp_server_SOURCES = force-cxx-linking.cxx +fuzz_smtp_server_CPPFLAGS = $(FUZZER_CPPFLAGS) +fuzz_smtp_server_LDFLAGS = $(FUZZER_LDFLAGS) +fuzz_smtp_server_SOURCES = fuzz-smtp-server.c +fuzz_smtp_server_LDADD = $(test_libs) +fuzz_smtp_server_DEPENDENCIES = $(test_deps) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .cxx .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-smtp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib-smtp/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 + +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}; \ + } + +libsmtp.la: $(libsmtp_la_OBJECTS) $(libsmtp_la_DEPENDENCIES) $(EXTRA_libsmtp_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libsmtp_la_OBJECTS) $(libsmtp_la_LIBADD) $(LIBS) + +fuzz-smtp-server$(EXEEXT): $(fuzz_smtp_server_OBJECTS) $(fuzz_smtp_server_DEPENDENCIES) $(EXTRA_fuzz_smtp_server_DEPENDENCIES) + @rm -f fuzz-smtp-server$(EXEEXT) + $(AM_V_CXXLD)$(fuzz_smtp_server_LINK) $(fuzz_smtp_server_OBJECTS) $(fuzz_smtp_server_LDADD) $(LIBS) + +test-smtp-address$(EXEEXT): $(test_smtp_address_OBJECTS) $(test_smtp_address_DEPENDENCIES) $(EXTRA_test_smtp_address_DEPENDENCIES) + @rm -f test-smtp-address$(EXEEXT) + $(AM_V_CCLD)$(test_smtp_address_LINK) $(test_smtp_address_OBJECTS) $(test_smtp_address_LDADD) $(LIBS) + +test-smtp-client-errors$(EXEEXT): $(test_smtp_client_errors_OBJECTS) $(test_smtp_client_errors_DEPENDENCIES) $(EXTRA_test_smtp_client_errors_DEPENDENCIES) + @rm -f test-smtp-client-errors$(EXEEXT) + $(AM_V_CCLD)$(test_smtp_client_errors_LINK) $(test_smtp_client_errors_OBJECTS) $(test_smtp_client_errors_LDADD) $(LIBS) + +test-smtp-command-parser$(EXEEXT): $(test_smtp_command_parser_OBJECTS) $(test_smtp_command_parser_DEPENDENCIES) $(EXTRA_test_smtp_command_parser_DEPENDENCIES) + @rm -f test-smtp-command-parser$(EXEEXT) + $(AM_V_CCLD)$(test_smtp_command_parser_LINK) $(test_smtp_command_parser_OBJECTS) $(test_smtp_command_parser_LDADD) $(LIBS) + +test-smtp-params$(EXEEXT): $(test_smtp_params_OBJECTS) $(test_smtp_params_DEPENDENCIES) $(EXTRA_test_smtp_params_DEPENDENCIES) + @rm -f test-smtp-params$(EXEEXT) + $(AM_V_CCLD)$(test_smtp_params_LINK) $(test_smtp_params_OBJECTS) $(test_smtp_params_LDADD) $(LIBS) + +test-smtp-payload$(EXEEXT): $(test_smtp_payload_OBJECTS) $(test_smtp_payload_DEPENDENCIES) $(EXTRA_test_smtp_payload_DEPENDENCIES) + @rm -f test-smtp-payload$(EXEEXT) + $(AM_V_CCLD)$(test_smtp_payload_LINK) $(test_smtp_payload_OBJECTS) $(test_smtp_payload_LDADD) $(LIBS) + +test-smtp-reply$(EXEEXT): $(test_smtp_reply_OBJECTS) $(test_smtp_reply_DEPENDENCIES) $(EXTRA_test_smtp_reply_DEPENDENCIES) + @rm -f test-smtp-reply$(EXEEXT) + $(AM_V_CCLD)$(test_smtp_reply_LINK) $(test_smtp_reply_OBJECTS) $(test_smtp_reply_LDADD) $(LIBS) + +test-smtp-server-errors$(EXEEXT): $(test_smtp_server_errors_OBJECTS) $(test_smtp_server_errors_DEPENDENCIES) $(EXTRA_test_smtp_server_errors_DEPENDENCIES) + @rm -f test-smtp-server-errors$(EXEEXT) + $(AM_V_CCLD)$(test_smtp_server_errors_LINK) $(test_smtp_server_errors_OBJECTS) $(test_smtp_server_errors_LDADD) $(LIBS) + +test-smtp-submit$(EXEEXT): $(test_smtp_submit_OBJECTS) $(test_smtp_submit_DEPENDENCIES) $(EXTRA_test_smtp_submit_DEPENDENCIES) + @rm -f test-smtp-submit$(EXEEXT) + $(AM_V_CCLD)$(test_smtp_submit_LINK) $(test_smtp_submit_OBJECTS) $(test_smtp_submit_LDADD) $(LIBS) + +test-smtp-syntax$(EXEEXT): $(test_smtp_syntax_OBJECTS) $(test_smtp_syntax_DEPENDENCIES) $(EXTRA_test_smtp_syntax_DEPENDENCIES) + @rm -f test-smtp-syntax$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_smtp_syntax_OBJECTS) $(test_smtp_syntax_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_smtp_server-force-cxx-linking.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-address.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-client-command.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-client-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-client-transaction.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-command-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-common.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-params.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-reply-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-reply.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-auth.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-data.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-helo.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-mail.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-noop.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-quit.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-rcpt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-rset.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-starttls.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-vrfy.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-cmd-xclient.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-command.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-recipient.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-reply.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server-transaction.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-server.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-submit-settings.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-submit.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp-syntax.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-smtp-address.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-smtp-client-errors.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-smtp-command-parser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-smtp-params.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-smtp-payload.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-smtp-reply.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-smtp-server-errors.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-smtp-submit.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-smtp-syntax.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 $@ $< + +fuzz_smtp_server-fuzz-smtp-server.o: fuzz-smtp-server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_smtp_server_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_smtp_server-fuzz-smtp-server.o -MD -MP -MF $(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Tpo -c -o fuzz_smtp_server-fuzz-smtp-server.o `test -f 'fuzz-smtp-server.c' || echo '$(srcdir)/'`fuzz-smtp-server.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Tpo $(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-smtp-server.c' object='fuzz_smtp_server-fuzz-smtp-server.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_smtp_server_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_smtp_server-fuzz-smtp-server.o `test -f 'fuzz-smtp-server.c' || echo '$(srcdir)/'`fuzz-smtp-server.c + +fuzz_smtp_server-fuzz-smtp-server.obj: fuzz-smtp-server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_smtp_server_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_smtp_server-fuzz-smtp-server.obj -MD -MP -MF $(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Tpo -c -o fuzz_smtp_server-fuzz-smtp-server.obj `if test -f 'fuzz-smtp-server.c'; then $(CYGPATH_W) 'fuzz-smtp-server.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-smtp-server.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Tpo $(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-smtp-server.c' object='fuzz_smtp_server-fuzz-smtp-server.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_smtp_server_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_smtp_server-fuzz-smtp-server.obj `if test -f 'fuzz-smtp-server.c'; then $(CYGPATH_W) 'fuzz-smtp-server.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-smtp-server.c'; fi` + +.cxx.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cxx.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cxx.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +fuzz_smtp_server-force-cxx-linking.o: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_smtp_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_smtp_server-force-cxx-linking.o -MD -MP -MF $(DEPDIR)/fuzz_smtp_server-force-cxx-linking.Tpo -c -o fuzz_smtp_server-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_smtp_server-force-cxx-linking.Tpo $(DEPDIR)/fuzz_smtp_server-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_smtp_server-force-cxx-linking.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_smtp_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_smtp_server-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx + +fuzz_smtp_server-force-cxx-linking.obj: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_smtp_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_smtp_server-force-cxx-linking.obj -MD -MP -MF $(DEPDIR)/fuzz_smtp_server-force-cxx-linking.Tpo -c -o fuzz_smtp_server-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_smtp_server-force-cxx-linking.Tpo $(DEPDIR)/fuzz_smtp_server-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_smtp_server-force-cxx-linking.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_smtp_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_smtp_server-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` + +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 +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(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-noinstLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/fuzz_smtp_server-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Po + -rm -f ./$(DEPDIR)/smtp-address.Plo + -rm -f ./$(DEPDIR)/smtp-client-command.Plo + -rm -f ./$(DEPDIR)/smtp-client-connection.Plo + -rm -f ./$(DEPDIR)/smtp-client-transaction.Plo + -rm -f ./$(DEPDIR)/smtp-client.Plo + -rm -f ./$(DEPDIR)/smtp-command-parser.Plo + -rm -f ./$(DEPDIR)/smtp-common.Plo + -rm -f ./$(DEPDIR)/smtp-params.Plo + -rm -f ./$(DEPDIR)/smtp-parser.Plo + -rm -f ./$(DEPDIR)/smtp-reply-parser.Plo + -rm -f ./$(DEPDIR)/smtp-reply.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-auth.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-data.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-helo.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-mail.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-noop.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-quit.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-rcpt.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-rset.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-starttls.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-vrfy.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-xclient.Plo + -rm -f ./$(DEPDIR)/smtp-server-command.Plo + -rm -f ./$(DEPDIR)/smtp-server-connection.Plo + -rm -f ./$(DEPDIR)/smtp-server-recipient.Plo + -rm -f ./$(DEPDIR)/smtp-server-reply.Plo + -rm -f ./$(DEPDIR)/smtp-server-transaction.Plo + -rm -f ./$(DEPDIR)/smtp-server.Plo + -rm -f ./$(DEPDIR)/smtp-submit-settings.Plo + -rm -f ./$(DEPDIR)/smtp-submit.Plo + -rm -f ./$(DEPDIR)/smtp-syntax.Plo + -rm -f ./$(DEPDIR)/test-smtp-address.Po + -rm -f ./$(DEPDIR)/test-smtp-client-errors.Po + -rm -f ./$(DEPDIR)/test-smtp-command-parser.Po + -rm -f ./$(DEPDIR)/test-smtp-params.Po + -rm -f ./$(DEPDIR)/test-smtp-payload.Po + -rm -f ./$(DEPDIR)/test-smtp-reply.Po + -rm -f ./$(DEPDIR)/test-smtp-server-errors.Po + -rm -f ./$(DEPDIR)/test-smtp-submit.Po + -rm -f ./$(DEPDIR)/test-smtp-syntax.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-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)/fuzz_smtp_server-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_smtp_server-fuzz-smtp-server.Po + -rm -f ./$(DEPDIR)/smtp-address.Plo + -rm -f ./$(DEPDIR)/smtp-client-command.Plo + -rm -f ./$(DEPDIR)/smtp-client-connection.Plo + -rm -f ./$(DEPDIR)/smtp-client-transaction.Plo + -rm -f ./$(DEPDIR)/smtp-client.Plo + -rm -f ./$(DEPDIR)/smtp-command-parser.Plo + -rm -f ./$(DEPDIR)/smtp-common.Plo + -rm -f ./$(DEPDIR)/smtp-params.Plo + -rm -f ./$(DEPDIR)/smtp-parser.Plo + -rm -f ./$(DEPDIR)/smtp-reply-parser.Plo + -rm -f ./$(DEPDIR)/smtp-reply.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-auth.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-data.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-helo.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-mail.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-noop.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-quit.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-rcpt.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-rset.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-starttls.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-vrfy.Plo + -rm -f ./$(DEPDIR)/smtp-server-cmd-xclient.Plo + -rm -f ./$(DEPDIR)/smtp-server-command.Plo + -rm -f ./$(DEPDIR)/smtp-server-connection.Plo + -rm -f ./$(DEPDIR)/smtp-server-recipient.Plo + -rm -f ./$(DEPDIR)/smtp-server-reply.Plo + -rm -f ./$(DEPDIR)/smtp-server-transaction.Plo + -rm -f ./$(DEPDIR)/smtp-server.Plo + -rm -f ./$(DEPDIR)/smtp-submit-settings.Plo + -rm -f ./$(DEPDIR)/smtp-submit.Plo + -rm -f ./$(DEPDIR)/smtp-syntax.Plo + -rm -f ./$(DEPDIR)/test-smtp-address.Po + -rm -f ./$(DEPDIR)/test-smtp-client-errors.Po + -rm -f ./$(DEPDIR)/test-smtp-command-parser.Po + -rm -f ./$(DEPDIR)/test-smtp-params.Po + -rm -f ./$(DEPDIR)/test-smtp-payload.Po + -rm -f ./$(DEPDIR)/test-smtp-reply.Po + -rm -f ./$(DEPDIR)/test-smtp-server-errors.Po + -rm -f ./$(DEPDIR)/test-smtp-submit.Po + -rm -f ./$(DEPDIR)/test-smtp-syntax.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-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-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-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-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if test "$$bin" = "test-smtp-submit"; then \ + if ! env NOCHILDREN=yes $(RUN_TEST) ./$$bin; then exit 1; fi; \ + else \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + fi \ + 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-smtp/fuzz-smtp-server.c b/src/lib-smtp/fuzz-smtp-server.c new file mode 100644 index 0000000..4b5d21d --- /dev/null +++ b/src/lib-smtp/fuzz-smtp-server.c @@ -0,0 +1,102 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "fuzzer.h" +#include "istream.h" +#include "ioloop.h" +#include "smtp-server.h" + +static struct { + struct istream *data_input; +} state = { + .data_input = NULL, +}; + +static int +server_cmd_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + return 1; +} + +static int +server_cmd_data_continue(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED) +{ + struct istream *data_input = state.data_input; + const unsigned char *data; + size_t size; + ssize_t ret; + + while ((ret = i_stream_read(data_input)) > 0 || ret == -2) { + data = i_stream_get_data(data_input, &size); + i_stream_skip(data_input, size); + if (!smtp_server_cmd_data_check_size(cmd)) + return -1; + } + + if (ret == 0) + return 0; + if (ret < 0 && data_input->stream_errno != 0) { + /* Client probably disconnected */ + return -1; + } + + smtp_server_reply_all(cmd, 250, "2.0.0", "Accepted"); + return 1; +} + +static int +server_cmd_data_begin(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input) +{ + state.data_input = data_input; + return 0; +} + +static void server_connection_free(void *context) +{ + struct fuzzer_context *ctx = context; + io_loop_stop(ctx->ioloop); +} + +static void test_server_continue(struct fuzzer_context *ctx) +{ + //instead of simple io_loop_stop so as to free input io + io_loop_stop_delayed(ctx->ioloop); +} + +FUZZ_BEGIN_FD +{ + struct smtp_server_connection *conn; + struct smtp_server_settings smtp_server_set = { + .max_client_idle_time_msecs = 500, + .max_pipelined_commands = 16, + .auth_optional = TRUE, + }; + struct smtp_server_callbacks server_callbacks = { + .conn_cmd_rcpt = server_cmd_rcpt, + .conn_cmd_data_begin = server_cmd_data_begin, + .conn_cmd_data_continue = server_cmd_data_continue, + .conn_free = server_connection_free, + }; + struct smtp_server *smtp_server = NULL; + struct timeout *to; + + to = timeout_add_short(10, test_server_continue, &fuzz_ctx); + smtp_server = smtp_server_init(&smtp_server_set); + + conn = smtp_server_connection_create(smtp_server, fuzz_ctx.fd, fuzz_ctx.fd, NULL, 0, + FALSE, NULL, &server_callbacks, &fuzz_ctx); + smtp_server_connection_start(conn); + + io_loop_run(fuzz_ctx.ioloop); + + smtp_server_deinit(&smtp_server); + timeout_remove(&to); +} +FUZZ_END diff --git a/src/lib-smtp/smtp-address.c b/src/lib-smtp/smtp-address.c new file mode 100644 index 0000000..eac25e1 --- /dev/null +++ b/src/lib-smtp/smtp-address.c @@ -0,0 +1,958 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "message-address.h" +#include "smtp-parser.h" +#include "smtp-address.h" + +/* From RFC 5321: + + Reverse-path = Path / "<>" + Forward-path = Path + + Path = "<" [ A-d-l ":" ] Mailbox ">" + A-d-l = At-domain *( "," At-domain ) + ; Note that this form, the so-called "source + ; route", MUST BE accepted, SHOULD NOT be + ; generated, and SHOULD be ignored. + At-domain = "@" Domain + + Domain = sub-domain *("." sub-domain) + + sub-domain = Let-dig [Ldh-str] + Let-dig = ALPHA / DIGIT + Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig + + address-literal = "[" ( IPv4-address-literal / + IPv6-address-literal / + General-address-literal ) "]" + ; See Section 4.1.3 + + Mailbox = Local-part "@" ( Domain / address-literal ) + + Local-part = Dot-string / Quoted-string + ; MAY be case-sensitive + Dot-string = Atom *("." Atom) + Atom = 1*atext + */ + +/* + * SMTP address parsing + */ + +struct smtp_address_parser { + struct smtp_parser parser; + + struct smtp_address address; + const unsigned char *address_end; + + bool parse:1; + bool path:1; + bool parsed_any:1; + bool totally_broken:1; +}; + +static int +smtp_parser_parse_dot_string(struct smtp_parser *parser, const char **value_r) +{ + const unsigned char *pbegin = parser->cur; + + /* Dot-string = Atom *("." Atom) + */ + + /* NOTE: this deviates from Dot-String syntax to allow some Japanese + mail addresses with dots at non-standard places to be accepted. */ + + if (parser->cur >= parser->end || + (!smtp_char_is_atext(*parser->cur) && *parser->cur != '.')) + return 0; + parser->cur++; + + while (parser->cur < parser->end && + (smtp_char_is_atext(*parser->cur) || *parser->cur == '.')) + parser->cur++; + + if (value_r != NULL) + *value_r = t_strndup(pbegin, parser->cur - pbegin); + return 1; +} + +static int +smtp_parse_localpart(struct smtp_parser *parser, const char **localpart_r) +{ + int ret; + + if ((ret = smtp_parser_parse_quoted_string(parser, localpart_r)) != 0) + return ret; + + return smtp_parser_parse_dot_string(parser, localpart_r); +} + +static int +smtp_address_parser_find_end(struct smtp_address_parser *aparser, + enum smtp_address_parse_flags flags) +{ + struct smtp_parser *parser = &aparser->parser; + const char *begin = (const char *)parser->begin, *end; + const char **address_p = NULL; + + if (aparser->address_end != NULL) + return 0; + + if (aparser->parse && + HAS_ALL_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW)) + address_p = &aparser->address.raw; + if (smtp_address_parse_any(begin, address_p, &end) < 0) { + parser->error = "Invalid character"; + aparser->totally_broken = TRUE; + return -1; + } + aparser->parsed_any = TRUE; + aparser->address_end = (const unsigned char *)end; + if (aparser->path) { + i_assert(aparser->address_end > parser->begin); + aparser->address_end--; + } + return 0; +} + +static int +smtp_parse_mailbox(struct smtp_address_parser *aparser, + enum smtp_address_parse_flags flags) +{ + struct smtp_parser *parser = &aparser->parser; + const char **value = NULL; + const unsigned char *p, *dp; + int ret; + + /* Mailbox = Local-part "@" ( Domain / address-literal ) + */ + + value = (aparser->parse ? &aparser->address.localpart : NULL); + if ((flags & SMTP_ADDRESS_PARSE_FLAG_STRICT) != 0 || + (flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART) == 0 || + aparser->path || *parser->cur == '\"') { + if ((ret = smtp_parse_localpart(parser, value)) <= 0) + return ret; + } else { + /* find the end of the address */ + if (smtp_address_parser_find_end(aparser, flags) < 0) + return -1; + /* use the right-most '@' as separator */ + dp = aparser->address_end - 1; + while (dp > parser->cur && *dp != '@') + dp--; + if (dp == parser->cur) + dp = aparser->address_end; + /* check whether the resulting localpart could be encoded as + quoted string */ + for (p = parser->cur; p < dp; p++) { + if (!smtp_char_is_qtext(*p) && + !smtp_char_is_qpair(*p)) { + parser->error = + "Invalid character in localpart"; + return -1; + } + } + if (aparser->parse) { + aparser->address.localpart = + p_strdup_until(parser->pool, parser->cur, dp); + } + parser->cur = dp; + } + + if ((parser->cur >= parser->end || *parser->cur != '@') && + (flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART) == 0) { + if (parser->cur >= parser->end || + (aparser->path && *parser->cur == '>')) + parser->error = "Missing domain"; + else + parser->error = "Invalid character in localpart"; + return -1; + } + + if (parser->cur >= parser->end || *parser->cur != '@') + return 1; + parser->cur++; + + value = (aparser->parse ? &aparser->address.domain : NULL); + if ((ret = smtp_parser_parse_domain(parser, value)) == 0 && + (ret = smtp_parser_parse_address_literal( + parser, value, NULL)) == 0) { + if (parser->cur >= parser->end || + (aparser->path && *parser->cur == '>')) { + parser->error = "Missing domain after '@'"; + return -1; + } else { + parser->error = "Invalid domain"; + return -1; + } + } + return ret; +} + +static int smtp_parse_source_route(struct smtp_parser *parser) +{ + /* Source-route = [ A-d-l ":" ] + A-d-l = At-domain *( "," At-domain ) + At-domain = "@" Domain + */ + + /* "@" Domain */ + if (parser->cur >= parser->end || *parser->cur != '@') + return 0; + parser->cur++; + + for (;;) { + /* Domain */ + if (smtp_parser_parse_domain(parser, NULL) <= 0) { + parser->error = + "Missing domain after '@' in source route"; + return -1; + } + + /* *( "," At-domain ) */ + if (parser->cur >= parser->end || *parser->cur != ',') + break; + parser->cur++; + + /* "@" Domain */ + if (parser->cur >= parser->end || *parser->cur != '@') { + parser->error = "Missing '@' after ',' in source route"; + return -1; + } + parser->cur++; + } + + /* ":" */ + if (parser->cur >= parser->end || *parser->cur != ':') { + parser->error = "Missing ':' at end of source route"; + return -1; + } + parser->cur++; + return 1; +} + +static int +smtp_parse_path(struct smtp_address_parser *aparser, + enum smtp_address_parse_flags flags) +{ + struct smtp_parser *parser = &aparser->parser; + int ret, sret = 0; + + /* Path = "<" [ A-d-l ":" ] Mailbox ">" + */ + + /* "<" */ + if (parser->cur < parser->end && *parser->cur == '<') { + aparser->path = TRUE; + parser->cur++; + } else if ((flags & SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL) == 0) { + return 0; + } + + /* [ A-d-l ":" ] */ + if (aparser->path && (sret = smtp_parse_source_route(parser)) < 0) + return -1; + + /* Mailbox */ + if ((ret = smtp_parse_mailbox(aparser, flags)) < 0) + return -1; + if (ret == 0) { + if (parser->cur < parser->end && *parser->cur == '>') { + if (sret > 0) { + parser->error = + "Path only consists of source route"; + return -1; + } + if ((flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY) + == 0) { + parser->error = "Null path not allowed"; + return -1; + } + } else { + parser->error = "Invalid character in localpart"; + return -1; + } + } + + /* ">" */ + if (aparser->path) { + if (parser->cur >= parser->end || *parser->cur != '>') { + parser->error = "Missing '>' at end of path"; + return -1; + } + parser->cur++; + } else if (parser->cur < parser->end && *parser->cur == '>') { + parser->error = "Unmatched '>' at end of path"; + return -1; + } + return 1; +} + +int smtp_address_parse_mailbox(pool_t pool, const char *mailbox, + enum smtp_address_parse_flags flags, + struct smtp_address **address_r, + const char **error_r) +{ + struct smtp_address_parser aparser; + int ret; + + if (address_r != NULL) + *address_r = NULL; + if (error_r != NULL) + *error_r = NULL; + + if (error_r != NULL) + *error_r = NULL; + + if ((mailbox == NULL || *mailbox == '\0')) { + if ((flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY) == 0) { + if (error_r != NULL) + *error_r = "Mailbox is empty string"; + return -1; + } + + if (address_r != NULL) + *address_r = p_new(pool, struct smtp_address, 1); + return 0; + } + + i_zero(&aparser); + smtp_parser_init(&aparser.parser, pool_datastack_create(), mailbox); + aparser.address_end = aparser.parser.end; + aparser.parse = (address_r != NULL); + + if ((ret = smtp_parse_mailbox(&aparser, flags)) <= 0) { + if (error_r != NULL) { + *error_r = (ret < 0 ? aparser.parser.error : + "Invalid character in localpart"); + } + return -1; + } + if (aparser.parser.cur != aparser.parser.end) { + if (error_r != NULL) + *error_r = "Invalid character in mailbox"; + return -1; + } + + if (address_r != NULL) + *address_r = smtp_address_clone(pool, &aparser.address); + return 0; +} + +static int +smtp_address_parse_path_broken(struct smtp_address_parser *aparser, + enum smtp_address_parse_flags flags, + const char **endp_r) ATTR_NULL(3) +{ + struct smtp_parser *parser = &aparser->parser; + const char *begin = (const char *)parser->begin, *end; + const char *raw = aparser->address.raw; + const char **address_p = NULL; + + i_zero(&aparser->address); + aparser->address.raw = raw; + + if (aparser->totally_broken || + HAS_NO_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN)) + return -1; + if (*begin != '<' && + HAS_NO_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL)) { + /* brackets missing; totally broken */ + return -1; + } + i_assert(aparser->parse); + if (aparser->parsed_any) { + if (endp_r != NULL) + *endp_r = (const char *)aparser->address_end; + return 0; + } + + if (HAS_ALL_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW)) + address_p = &aparser->address.raw; + if (smtp_address_parse_any(begin, address_p, &end) < 0) { + /* totally broken */ + return -1; + } + if (endp_r != NULL) + *endp_r = end; + return 0; +} + +int smtp_address_parse_path_full(pool_t pool, const char *path, + enum smtp_address_parse_flags flags, + struct smtp_address **address_r, + const char **error_r, const char **endp_r) +{ + struct smtp_address_parser aparser; + int ret; + + if (address_r != NULL) + *address_r = NULL; + if (error_r != NULL) + *error_r = NULL; + if (endp_r != NULL) + *endp_r = NULL; + + if (path == NULL || *path == '\0') { + if ((flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY) == 0 || + (flags & SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL) == 0) { + if (error_r != NULL) + *error_r = "Path is empty string"; + return -1; + } + if (address_r != NULL) + *address_r = p_new(pool, struct smtp_address, 1); + if (endp_r != NULL) + *endp_r = path; + return 0; + } + + i_zero(&aparser); + smtp_parser_init(&aparser.parser, pool_datastack_create(), path); + aparser.address_end = (endp_r != NULL ? NULL : aparser.parser.end); + aparser.parse = (address_r != NULL); + + if ((ret = smtp_parse_path(&aparser, flags)) <= 0) { + if (error_r != NULL) { + *error_r = (ret < 0 ? aparser.parser.error : + "Missing '<' at beginning of path"); + } + ret = -1; + } else if (endp_r != NULL) { + if (aparser.parser.cur == aparser.parser.end || + *aparser.parser.cur == ' ' || + HAS_NO_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN)) { + *endp_r = (const char *)aparser.parser.cur; + ret = 0; + } else { + if (error_r != NULL) + *error_r = "Invalid character in path"; + ret = -1; + } + } else if (aparser.parser.cur == aparser.parser.end) { + ret = 0; + } else { + if (error_r != NULL) + *error_r = "Invalid character in path"; + ret = -1; + } + + if (ret < 0) { + /* normal parsing failed */ + if (smtp_address_parse_path_broken(&aparser, flags, + endp_r) < 0) { + /* failed to parse it as a broken address as well */ + return -1; + } + /* broken address */ + } else if (HAS_ALL_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW) && + aparser.address.localpart != NULL) { + if (aparser.path && + ((const unsigned char *)(path + 1) < aparser.parser.cur)) { + aparser.address.raw = t_strdup_until( + path + 1, aparser.parser.cur - 1); + } else { + aparser.address.raw = t_strdup_until( + path, aparser.parser.cur); + } + } + + if (address_r != NULL) + *address_r = smtp_address_clone(pool, &aparser.address); + return ret; +} + +int smtp_address_parse_path(pool_t pool, const char *path, + enum smtp_address_parse_flags flags, + struct smtp_address **address_r, + const char **error_r) +{ + return smtp_address_parse_path_full(pool, path, flags, + address_r, error_r, NULL); +} + +int smtp_address_parse_username(pool_t pool, const char *username, + struct smtp_address **address_r, + const char **error_r) +{ + enum smtp_address_parse_flags flags = + SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART; + + struct smtp_address_parser aparser; + int ret; + + if (address_r != NULL) + *address_r = NULL; + if (error_r != NULL) + *error_r = NULL; + + if ((username == NULL || *username == '\0')) { + if (error_r != NULL) + *error_r = "Username is empty string"; + return -1; + } + + i_zero(&aparser); + smtp_parser_init(&aparser.parser, pool_datastack_create(), username); + aparser.address_end = aparser.parser.end; + aparser.parse = (address_r != NULL); + + if ((ret = smtp_parse_mailbox(&aparser, flags)) <= 0) { + if (error_r != NULL) { + *error_r = (ret < 0 ? aparser.parser.error : + "Invalid character in user name"); + } + return -1; + } + if (aparser.parser.cur != aparser.parser.end) { + if (error_r != NULL) + *error_r = "Invalid character in user name"; + return -1; + } + + if (address_r != NULL) + *address_r = smtp_address_clone(pool, &aparser.address); + return 0; +} + +void smtp_address_detail_parse(pool_t pool, const char *delimiters, + struct smtp_address *address, + const char **username_r, char *delim_r, + const char **detail_r) +{ + const char *localpart; + const char *user, *p; + size_t idx; + + i_assert(!smtp_address_isnull(address)); + + localpart = address->localpart; + user = localpart; + *detail_r = ""; + *delim_r = '\0'; + + /* first character that matches the recipient_delimiter */ + idx = strcspn(localpart, delimiters); + p = (localpart[idx] != '\0' ? &localpart[idx] : NULL); + + if (p != NULL) { + *delim_r = *p; + /* user+detail */ + user = p_strdup_until(pool, localpart, p); + *detail_r = p+1; + } + + if (address->domain == NULL || *address->domain == '\0') + *username_r = user; + else if (strchr(user, '@') == NULL) { + /* username is just glued to the domain... no SMTP escaping */ + *username_r = p_strconcat(pool, user, "@", address->domain, + NULL); + } else { + struct smtp_address uaddr; + + /* username contains '@'; apply escaping */ + smtp_address_init(&uaddr, user, address->domain); + if (pool->datastack_pool) + *username_r = smtp_address_encode(&uaddr); + else { + *username_r = + p_strdup(pool, smtp_address_encode(&uaddr)); + } + } +} + +void smtp_address_detail_parse_temp(const char *delimiters, + struct smtp_address *address, + const char **username_r, char *delim_r, + const char **detail_r) +{ + smtp_address_detail_parse(pool_datastack_create(), delimiters, + address, username_r, delim_r, detail_r); +} + +int smtp_address_parse_any(const char *in, const char **address_r, + const char **endp_r) +{ + const unsigned char *p, *pend, *poffset; + bool path = FALSE; + bool quoted = FALSE; + + if (endp_r != NULL) + *endp_r = in; + + poffset = p = (const unsigned char *)in; + pend = p + strlen(in); + if (*p == '<') { + path = TRUE; + p++; + poffset = p; + } + if (*p == '"') { + quoted = TRUE; + p++; + } + + while (p < pend) { + if (quoted && *p == '\\') { + p++; + if (p == pend || *p < 0x20) + return -1; + p++; + if (p == pend) + break; + } + switch (*p) { + case '"': + quoted = FALSE; + break; + case ' ': + if (!quoted) { + if (path) + return -1; + if (address_r != NULL) + *address_r = t_strdup_until(poffset, p); + if (endp_r != NULL) + *endp_r = (const char *)p; + return 0; + } + break; + case '>': + if (!quoted) { + if (address_r != NULL) + *address_r = t_strdup_until(poffset, p); + if (endp_r != NULL) + *endp_r = (const char *)(p + 1); + return 0; + } + break; + default: + if (*p < 0x20) + return -1; + break; + } + p++; + } + if (quoted || path) + return -1; + if (address_r != NULL) + *address_r = t_strdup_until(poffset, p); + if (endp_r != NULL) + *endp_r = (const char *)p; + return 0; +} + +/* + * SMTP address construction + */ + +void smtp_address_write(string_t *out, const struct smtp_address *address) + ATTR_NULL(2) +{ + bool quoted = FALSE; + const unsigned char *p, *pend, *pblock; + size_t begin; + + if (smtp_address_isnull(address)) + return; + begin = str_len(out); + + /* encode localpart */ + p = (const unsigned char *)address->localpart; + pend = p + strlen(address->localpart); + pblock = p; + while (p < pend) { + while (p < pend && smtp_char_is_atext(*p)) + p++; + + if (!quoted && p < pend && (*p != '.' || p == pblock)) { + quoted = TRUE; + str_insert(out, begin, "\""); + } + + str_append_data(out, pblock, p - pblock); + if (p >= pend) + break; + + if (!quoted) { + str_append_c(out, '.'); + } else { + i_assert(smtp_char_is_qpair(*p)); + if (!smtp_char_is_qtext(*p)) + str_append_c(out, '\\'); + str_append_c(out, *p); + } + + p++; + pblock = p; + } + + if (p == pblock && !quoted) { + quoted = TRUE; + str_insert(out, begin, "\""); + } + + if (quoted) + str_append_c(out, '\"'); + + if (address->domain == NULL || *address->domain == '\0') + return; + + str_append_c(out, '@'); + str_append(out, address->domain); +} + +void smtp_address_write_path(string_t *out, const struct smtp_address *address) +{ + str_append_c(out, '<'); + smtp_address_write(out, address); + str_append_c(out, '>'); +} + +const char *smtp_address_encode(const struct smtp_address *address) +{ + string_t *str = t_str_new(256); + smtp_address_write(str, address); + return str_c(str); +} + +const char *smtp_address_encode_path(const struct smtp_address *address) +{ + string_t *str = t_str_new(256); + smtp_address_write_path(str, address); + return str_c(str); +} + +const char *smtp_address_encode_raw(const struct smtp_address *address) +{ + if (address != NULL && address->raw != NULL && *address->raw != '\0') + return address->raw; + + return smtp_address_encode(address); +} + +const char *smtp_address_encode_raw_path(const struct smtp_address *address) +{ + if (address != NULL && address->raw != NULL && *address->raw != '\0') + return t_strconcat("<", address->raw, ">", NULL); + + return smtp_address_encode_path(address); +} + +/* + * SMTP address manipulation + */ + +void smtp_address_init(struct smtp_address *address, + const char *localpart, const char *domain) +{ + i_zero(address); + if (localpart == NULL || *localpart == '\0') + return; + + address->localpart = localpart; + if (domain != NULL && *domain != '\0') + address->domain = domain; +} + +int smtp_address_init_from_msg(struct smtp_address *address, + const struct message_address *msg_addr) +{ + const unsigned char *p; + + i_zero(address); + if (msg_addr->mailbox == NULL || *msg_addr->mailbox == '\0') + return 0; + + /* The message_address_parse() function allows UTF-8 codepoints in + the localpart. For SMTP addresses that is not an option, so we + need to check this upon conversion. */ + for (p = (const unsigned char *)msg_addr->mailbox; *p != '\0'; p++) { + if (!smtp_char_is_qpair(*p)) + return -1; + } + + address->localpart = msg_addr->mailbox; + if (msg_addr->domain != NULL && *msg_addr->domain != '\0') + address->domain = msg_addr->domain; + return 0; +} + +struct smtp_address * +smtp_address_clone(pool_t pool, const struct smtp_address *src) +{ + struct smtp_address *new; + size_t size, lpsize = 0, dsize = 0, rsize = 0; + char *data, *localpart = NULL, *domain = NULL, *raw = NULL; + + if (src == NULL) + return NULL; + + /* @UNSAFE */ + + size = sizeof(struct smtp_address); + if (!smtp_address_isnull(src)) { + lpsize = strlen(src->localpart) + 1; + size = MALLOC_ADD(size, lpsize); + } + if (src->domain != NULL && *src->domain != '\0') { + dsize = strlen(src->domain) + 1; + size = MALLOC_ADD(size, dsize); + } + if (src->raw != NULL && *src->raw != '\0') { + rsize = strlen(src->raw) + 1; + size = MALLOC_ADD(size, rsize); + } + + data = p_malloc(pool, size); + new = (struct smtp_address *)data; + if (lpsize > 0) { + localpart = PTR_OFFSET(data, sizeof(*new)); + memcpy(localpart, src->localpart, lpsize); + } + if (dsize > 0) { + domain = PTR_OFFSET(data, sizeof(*new) + lpsize); + memcpy(domain, src->domain, dsize); + } + if (rsize > 0) { + raw = PTR_OFFSET(data, sizeof(*new) + lpsize + dsize); + memcpy(raw, src->raw, rsize); + } + new->localpart = localpart; + new->domain = domain; + new->raw = raw; + + return new; +} + +struct smtp_address * +smtp_address_create(pool_t pool, const char *localpart, const char *domain) +{ + struct smtp_address addr; + + smtp_address_init(&addr, localpart, domain); + return smtp_address_clone(pool, &addr); +} + + +int smtp_address_create_from_msg(pool_t pool, + const struct message_address *msg_addr, + struct smtp_address **address_r) +{ + struct smtp_address addr; + + if (smtp_address_init_from_msg(&addr, msg_addr) < 0) { + *address_r = NULL; + return -1; + } + *address_r = smtp_address_clone(pool, &addr); + return 0; +} + +struct smtp_address *smtp_address_clone_temp(const struct smtp_address *src) +{ + struct smtp_address *new; + + if (src == NULL) + return NULL; + + new = t_new(struct smtp_address, 1); + new->localpart = t_strdup_empty(src->localpart); + new->domain = t_strdup_empty(src->domain); + new->raw = t_strdup_empty(src->raw); + return new; +} + +struct smtp_address * +smtp_address_create_temp(const char *localpart, const char *domain) +{ + struct smtp_address addr; + + smtp_address_init(&addr, localpart, domain); + return smtp_address_clone_temp(&addr); +} + +int smtp_address_create_from_msg_temp(const struct message_address *msg_addr, + struct smtp_address **address_r) +{ + struct smtp_address addr; + + if (smtp_address_init_from_msg(&addr, msg_addr) < 0) { + *address_r = NULL; + return -1; + } + *address_r = smtp_address_clone_temp(&addr); + return 0; +} + +struct smtp_address * +smtp_address_add_detail(pool_t pool, const struct smtp_address *address, + const char *detail, char delim_c) +{ + struct smtp_address *new_addr; + const char delim[] = {delim_c, '\0'}; + + i_assert(!smtp_address_isnull(address)); + + new_addr = p_new(pool, struct smtp_address, 1); + new_addr->localpart = p_strconcat(pool, address->localpart, delim, + detail, NULL); + new_addr->domain = p_strdup_empty(pool, address->domain); + + return new_addr; +} + +struct smtp_address * +smtp_address_add_detail_temp(const struct smtp_address *address, + const char *detail, char delim_c) +{ + struct smtp_address *new_addr; + const char delim[] = {delim_c, '\0'}; + + i_assert(!smtp_address_isnull(address)); + + new_addr = t_new(struct smtp_address, 1); + new_addr->localpart = t_strconcat(address->localpart, delim, detail, + NULL); + new_addr->domain = t_strdup_empty(address->domain); + + return new_addr; +} + +int smtp_address_cmp(const struct smtp_address *address1, + const struct smtp_address *address2) +{ + bool null1, null2; + int ret; + + null1 = smtp_address_isnull(address1); + null2 = smtp_address_isnull(address2); + if (null1) + return (null2 ? 0 : -1); + else if (null2) + return 1; + if ((ret = null_strcasecmp(address1->domain, address2->domain)) != 0) + return ret; + return null_strcmp(address1->localpart, address2->localpart); +} + +int smtp_address_cmp_icase(const struct smtp_address *address1, + const struct smtp_address *address2) +{ + bool null1, null2; + int ret; + + null1 = smtp_address_isnull(address1); + null2 = smtp_address_isnull(address2); + if (null1) + return (null2 ? 0 : -1); + else if (null2) + return 1; + if ((ret = null_strcasecmp(address1->domain, address2->domain)) != 0) + return ret; + return null_strcasecmp(address1->localpart, address2->localpart); +} diff --git a/src/lib-smtp/smtp-address.h b/src/lib-smtp/smtp-address.h new file mode 100644 index 0000000..326a673 --- /dev/null +++ b/src/lib-smtp/smtp-address.h @@ -0,0 +1,214 @@ +#ifndef SMTP_ADDRESS_H +#define SMTP_ADDRESS_H + +#include "array-decl.h" + +struct message_address; + +enum smtp_address_parse_flags { + /* Strictly enforce the RFC 5321 syntax */ + SMTP_ADDRESS_PARSE_FLAG_STRICT = BIT(0), + /* Allow an empty/NULL address */ + SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY = BIT(1), + /* Allow an address without a domain part */ + SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART = BIT(2), + /* Allow omission of the <...> brackets in a path. This flag is only + relevant for smtp_address_parse_path(). */ + SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL = BIT(3), + /* Allow localpart to have all kinds of bad unquoted characters by + parsing the last '@' in the string directly as the localpart/domain + separator. Addresses starting with `<' or `"' are parsed as normal. + The address is rejected when the resulting localpart and domain + cannot be used to construct a valid RFC 5321 address. + */ + SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART = BIT(4), + /* Store an unparsed copy of the address in the `raw' field of struct + smtp_address. When combined with SMTP_ADDRESS_PARSE_FLAG_SKIP_BROKEN, + the broken address will be stored there. This flag is only relevant + for smtp_address_parse_path(). */ + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW = BIT(5), + /* Try to skip over a broken address to allow working around syntax + errors in e.g. the sender address for the MAIL command. This flag is + only relevant for smtp_address_parse_path*(). The parser will return + failure, but it will return a broken address which is be equivalent + to <>. The raw broken address string is available in the address->raw + field. When the broken address contains control characters or is + badly delimited, parsing will still fail completely. */ + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN = BIT(6), +}; + +struct smtp_address { + /* Localpart */ + const char *localpart; + /* Domain */ + const char *domain; + /* Raw, unparsed address. If localpart == NULL, the value of this field + is syntactically invalid and MUST NOT be used for any purposes that + may be visible to external systems. It can be e.g. used for logging. + This is always in mailbox format, meaning that there are no + surrounding '<' and '>'. + */ + const char *raw; +}; + +ARRAY_DEFINE_TYPE(smtp_address, struct smtp_address *); +ARRAY_DEFINE_TYPE(smtp_address_const, const struct smtp_address *); + +/* + * SMTP address parsing + */ + + +/* Parse the RFC 5321 address from the provided mailbox string. Returns 0 when + the address was parsed successfully and -1 upon error. The address is + returned in address_r. When address_r is NULL, the provided string will be + verified for validity as a mailbox only. */ +int smtp_address_parse_mailbox(pool_t pool, const char *mailbox, + enum smtp_address_parse_flags flags, + struct smtp_address **address_r, + const char **error_r) ATTR_NULL(4, 5); +/* Parse the RFC 5321 address from the provided path string. Returns 0 when + the address was parsed successfully and -1 upon error. The address is + returned in address_r. When address_r is NULL, the provided string will be + verified for validity as a path only. The endp_r parameter is used to + return a pointer to the end of the path string, so that the caller can + continue parsing from there. When the SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN + flag is set, a broken address will be returned, even when the return value + is -1 (see above). If it is totally broken, *endp_r will be then be NULL. + */ +int smtp_address_parse_path_full(pool_t pool, const char *path, + enum smtp_address_parse_flags flags, + struct smtp_address **address_r, + const char **error_r, const char **endp_r) + ATTR_NULL(4, 5, 6); +/* Parse the RFC 5321 address from the provided path string. Returns 0 when + the address was parsed successfully and -1 upon error. The address is + returned in address_r. When address_r is NULL, the provided string will be + verified for validity as a path only. When the + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN flag is set, a broken address will be + returned, even when the return value is -1 (see above). */ +int smtp_address_parse_path(pool_t pool, const char *path, + enum smtp_address_parse_flags flags, + struct smtp_address **address_r, + const char **error_r) ATTR_NULL(4, 5); +/* Parse the RFC 5321 address from the provided username string. A username + string is not strictly parsed as an RFC 5321 mailbox; it allows a more + lenient syntax. If the address obtained from splitting the string at the last + `@' can be encoded back into a valid RFC 5321 mailbox string, parsing the + username will succeeded. Returns 0 when the address was parsed successfully + and -1 upon error. The address is returned in address_r. When address_r is + NULL, the provided string will be verified for validity as a username only. + */ +int smtp_address_parse_username(pool_t pool, const char *username, + struct smtp_address **address_r, + const char **error_r) ATTR_NULL(3, 4); + +/* Parse address+detail@domain into address@domain and detail + using given delimiters. Returns used delimiter. */ +void smtp_address_detail_parse(pool_t pool, const char *delimiters, + struct smtp_address *address, + const char **username_r, char *delim_r, + const char **detail_r); +void smtp_address_detail_parse_temp(const char *delimiters, + struct smtp_address *address, + const char **username_r, char *delim_r, + const char **detail_r); + +/* Parse any (possibly broken) address on the input to the best of our ability + until end of input or unquoted ` '. Things that are truly evil (unending + quoted string, control characters and a path without a closing '>') will + still fail and return -1. If the parse was successful, it will return 0. + The parsed address string is returned in address_r. Any outer < and > are + omitted in the parsed address. The endp_r parameter is used to return a + pointer to the end of the path string, so that the caller can continue + parsing from there.*/ +int smtp_address_parse_any(const char *in, const char **address_r, + const char **endp_r) ATTR_NULL(2, 3); + +/* + * SMTP address construction + */ + +void smtp_address_write(string_t *out, const struct smtp_address *address) + ATTR_NULL(2); +void smtp_address_write_path(string_t *out, const struct smtp_address *address) + ATTR_NULL(2); + +const char *smtp_address_encode(const struct smtp_address *address) + ATTR_NULL(1); +const char *smtp_address_encode_path(const struct smtp_address *address) + ATTR_NULL(1); + +const char * +smtp_address_encode_raw(const struct smtp_address *address) ATTR_NULL(1); +const char * +smtp_address_encode_raw_path(const struct smtp_address *address) ATTR_NULL(1); + +/* + * SMTP address manipulation + */ + +void smtp_address_init(struct smtp_address *address, + const char *localpart, const char *domain) + ATTR_NULL(2,3); +int smtp_address_init_from_msg(struct smtp_address *address, + const struct message_address *msg_addr); + +struct smtp_address * +smtp_address_clone(pool_t pool, const struct smtp_address *address) + ATTR_NULL(2); +struct smtp_address * +smtp_address_create(pool_t pool, const char *localpart, const char *domain) + ATTR_NULL(2, 3); +int smtp_address_create_from_msg(pool_t pool, + const struct message_address *msg_addr, + struct smtp_address **address_r); + +struct smtp_address * +smtp_address_clone_temp(const struct smtp_address *address) ATTR_NULL(1); +struct smtp_address * +smtp_address_create_temp(const char *localpart, const char *domain) + ATTR_NULL(2, 3); +int smtp_address_create_from_msg_temp(const struct message_address *msg_addr, + struct smtp_address **address_r); + +struct smtp_address * +smtp_address_add_detail(pool_t pool, const struct smtp_address *address, + const char *detail, char delim_c); +struct smtp_address * +smtp_address_add_detail_temp(const struct smtp_address *address, + const char *detail, char delim_c); + +int smtp_address_cmp(const struct smtp_address *address1, + const struct smtp_address *address2) ATTR_NULL(1, 2); +int smtp_address_cmp_icase(const struct smtp_address *address1, + const struct smtp_address *address2) ATTR_NULL(1, 2); + +static inline bool ATTR_NULL(1, 2) +smtp_address_equals(const struct smtp_address *address1, + const struct smtp_address *address2) +{ + return (smtp_address_cmp(address1, address2) == 0); +} +static inline bool ATTR_NULL(1, 2) +smtp_address_equals_icase(const struct smtp_address *address1, + const struct smtp_address *address2) +{ + return (smtp_address_cmp_icase(address1, address2) == 0); +} + +static inline bool ATTR_NULL(1) ATTR_PURE +smtp_address_isnull(const struct smtp_address *address) +{ + return (address == NULL || address->localpart == NULL); +} + +static inline bool ATTR_NULL(1) ATTR_PURE +smtp_address_is_broken(const struct smtp_address *address) +{ + return (address != NULL && + smtp_address_isnull(address) && + (address->raw != NULL && *address->raw != '\0')); +} + +#endif diff --git a/src/lib-smtp/smtp-client-command.c b/src/lib-smtp/smtp-client-command.c new file mode 100644 index 0000000..9eaf76d --- /dev/null +++ b/src/lib-smtp/smtp-client-command.c @@ -0,0 +1,1580 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "array.h" +#include "str.h" +#include "str-sanitize.h" +#include "llist.h" +#include "istream.h" +#include "ostream.h" +#include "ostream-dot.h" +#include "smtp-common.h" +#include "smtp-syntax.h" +#include "smtp-params.h" +#include "smtp-client-private.h" + +static const char * +smtp_client_command_get_name(struct smtp_client_command *cmd) +{ + const unsigned char *p, *pend; + + if (cmd->name != NULL) + return cmd->name; + + if (cmd->plug) + return NULL; + if (cmd->data == NULL || cmd->data->used == 0) + return NULL; + + p = cmd->data->data; + pend = p + cmd->data->used; + for (;p < pend; p++) { + if (*p == ' ' || *p == '\r' || *p == '\n') + break; + } + cmd->name = p_strdup(cmd->pool, + t_str_ucase(t_strdup_until(cmd->data->data, p))); + return cmd->name; +} + +static const char * +smtp_client_command_get_label(struct smtp_client_command *cmd) +{ + if (cmd->plug) + return "[plug]"; + if (cmd->data == NULL || cmd->data->used == 0) { + if (!cmd->has_stream) + return "[empty]"; + return "[data]"; + } + return smtp_client_command_get_name(cmd); +} + +static void smtp_client_command_update_event(struct smtp_client_command *cmd) +{ + const char *label = smtp_client_command_get_label(cmd); + + event_add_str(cmd->event, "cmd_name", + smtp_client_command_get_name(cmd)); + event_set_append_log_prefix( + cmd->event, t_strdup_printf("command %s: ", + str_sanitize(label, 128))); +} + +static struct smtp_client_command * +smtp_client_command_create(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + smtp_client_command_callback_t *callback, + void *context) +{ + struct smtp_client_command *cmd; + pool_t pool; + + pool = pool_alloconly_create("smtp client command", 2048); + cmd = p_new(pool, struct smtp_client_command, 1); + cmd->pool = pool; + cmd->refcount = 1; + cmd->conn = conn; + cmd->flags = flags; + cmd->replies_expected = 1; + cmd->callback = callback; + cmd->context = context; + cmd->event = event_create(conn->event); + smtp_client_command_update_event(cmd); + return cmd; +} + +#undef smtp_client_command_new +struct smtp_client_command * +smtp_client_command_new(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + smtp_client_command_callback_t *callback, + void *context) +{ + i_assert(callback != NULL); + return smtp_client_command_create(conn, flags, callback, context); +} + +struct smtp_client_command * +smtp_client_command_plug(struct smtp_client_connection *conn, + struct smtp_client_command *after) +{ + struct smtp_client_command *cmd; + + cmd = smtp_client_command_create(conn, 0, NULL, NULL); + cmd->plug = TRUE; + smtp_client_command_submit_after(cmd, after); + return cmd; +} + +void smtp_client_command_ref(struct smtp_client_command *cmd) +{ + cmd->refcount++; +} + +bool smtp_client_command_unref(struct smtp_client_command **_cmd) +{ + struct smtp_client_command *cmd = *_cmd; + + *_cmd = NULL; + + if (cmd == NULL) + return FALSE; + + struct smtp_client_connection *conn = cmd->conn; + + i_assert(cmd->refcount > 0); + if (--cmd->refcount > 0) + return TRUE; + + e_debug(cmd->event, "Destroy (%u commands pending, %u commands queued)", + conn->cmd_wait_list_count, conn->cmd_send_queue_count); + + i_assert(cmd->state >= SMTP_CLIENT_COMMAND_STATE_FINISHED); + i_assert(cmd != conn->cmd_streaming); + + i_stream_unref(&cmd->stream); + event_unref(&cmd->event); + pool_unref(&cmd->pool); + + return FALSE; +} + +bool smtp_client_command_name_equals(struct smtp_client_command *cmd, + const char *name) +{ + const unsigned char *data; + size_t name_len, data_len; + + if (cmd->data == NULL) + return FALSE; + + name_len = strlen(name); + data = cmd->data->data; + data_len = cmd->data->used; + + if (data_len < name_len || i_memcasecmp(data, name, name_len) != 0) + return FALSE; + return (data_len == name_len || + data[name_len] == ' ' || data[name_len] == '\r'); +} + +void smtp_client_command_lock(struct smtp_client_command *cmd) +{ + if (cmd->plug) + return; + cmd->locked = TRUE; +} + +void smtp_client_command_unlock(struct smtp_client_command *cmd) +{ + if (cmd->plug) + return; + if (cmd->locked) { + cmd->locked = FALSE; + if (!cmd->conn->corked) + smtp_client_connection_trigger_output(cmd->conn); + } +} + +void smtp_client_command_abort(struct smtp_client_command **_cmd) +{ + struct smtp_client_command *cmd = *_cmd; + + if (cmd == NULL) + return; + *_cmd = NULL; + + struct smtp_client_connection *conn = cmd->conn; + enum smtp_client_command_state state = cmd->state; + bool disconnected = + (conn->state == SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED); + bool was_locked = + (state >= SMTP_CLIENT_COMMAND_STATE_SUBMITTED) && + (cmd->locked || cmd->plug); + bool was_sent = + (!disconnected && state > SMTP_CLIENT_COMMAND_STATE_SUBMITTED && + state < SMTP_CLIENT_COMMAND_STATE_FINISHED); + + smtp_client_command_drop_callback(cmd); + + if ((!disconnected && !cmd->plug && cmd->aborting) || + state >= SMTP_CLIENT_COMMAND_STATE_FINISHED) + return; + + struct event_passthrough *e = event_create_passthrough(cmd->event); + if (!cmd->event_finished) { + struct smtp_reply failure; + + smtp_reply_init(&failure, + SMTP_CLIENT_COMMAND_ERROR_ABORTED, "Aborted"); + failure.enhanced_code = SMTP_REPLY_ENH_CODE(9, 0, 0); + + e->set_name("smtp_client_command_finished"); + smtp_reply_add_to_event(&failure, e); + cmd->event_finished = TRUE; + } + e_debug(e->event(), "Aborted%s", + (was_sent ? " (already sent)" : "")); + + if (!was_sent) { + cmd->state = SMTP_CLIENT_COMMAND_STATE_ABORTED; + } else { + i_assert(state < SMTP_CLIENT_COMMAND_STATE_FINISHED); + cmd->aborting = TRUE; + } + cmd->locked = FALSE; + + i_assert(!cmd->plug || state <= SMTP_CLIENT_COMMAND_STATE_SUBMITTED); + + switch (state) { + case SMTP_CLIENT_COMMAND_STATE_NEW: + if (cmd->delaying_failure) { + DLLIST_REMOVE(&conn->cmd_fail_list, cmd); + if (conn->cmd_fail_list == NULL) + timeout_remove(&conn->to_cmd_fail); + } + break; + case SMTP_CLIENT_COMMAND_STATE_SENDING: + if (!disconnected) { + /* It is being sent; cannot truly abort it now */ + break; + } + /* fall through */ + case SMTP_CLIENT_COMMAND_STATE_SUBMITTED: + /* Not yet sent */ + e_debug(cmd->event, "Removed from send queue"); + i_assert(conn->cmd_send_queue_count > 0); + DLLIST2_REMOVE(&conn->cmd_send_queue_head, + &conn->cmd_send_queue_tail, cmd); + i_assert(conn->cmd_send_queue_count > 1 || + (cmd->prev == NULL && cmd->next == NULL)); + conn->cmd_send_queue_count--; + break; + case SMTP_CLIENT_COMMAND_STATE_WAITING: + if (!disconnected) { + /* We're expecting a reply; cannot truly abort it now */ + break; + } + e_debug(cmd->event, "Removed from wait list"); + i_assert(conn->cmd_wait_list_count > 0); + DLLIST2_REMOVE(&conn->cmd_wait_list_head, + &conn->cmd_wait_list_tail, cmd); + conn->cmd_wait_list_count--; + break; + default: + i_unreached(); + } + + if (cmd->abort_callback != NULL) { + cmd->abort_callback(cmd->abort_context); + cmd->abort_callback = NULL; + } + + if (disconnected || cmd->plug || + state <= SMTP_CLIENT_COMMAND_STATE_SUBMITTED) { + /* Can only destroy it when it is not pending */ + smtp_client_command_unref(&cmd); + } + + if (!disconnected && was_locked && !conn->corked) + smtp_client_connection_trigger_output(conn); +} + +void smtp_client_command_drop_callback(struct smtp_client_command *cmd) +{ + cmd->callback = NULL; + cmd->context = NULL; +} + +void smtp_client_command_fail_reply(struct smtp_client_command **_cmd, + const struct smtp_reply *reply) +{ + struct smtp_client_command *cmd = *_cmd, *tmp_cmd; + + if (cmd == NULL) + return; + *_cmd = NULL; + + struct smtp_client_connection *conn = cmd->conn; + enum smtp_client_command_state state = cmd->state; + smtp_client_command_callback_t *callback = cmd->callback; + + if (state >= SMTP_CLIENT_COMMAND_STATE_FINISHED) + return; + + if (cmd->delay_failure) { + i_assert(cmd->delayed_failure == NULL); + i_assert(state < SMTP_CLIENT_COMMAND_STATE_SUBMITTED); + + e_debug(cmd->event, "Fail (delay)"); + + cmd->delayed_failure = smtp_reply_clone(cmd->pool, reply); + cmd->delaying_failure = TRUE; + if (conn->to_cmd_fail == NULL) { + conn->to_cmd_fail = timeout_add_short( + 0, smtp_client_commands_fail_delayed, conn); + } + DLLIST_PREPEND(&conn->cmd_fail_list, cmd); + return; + } + + cmd->callback = NULL; + + smtp_client_connection_ref(conn); + smtp_client_command_ref(cmd); + + if (!cmd->aborting) { + cmd->failed = TRUE; + + struct event_passthrough *e = + event_create_passthrough(cmd->event); + if (!cmd->event_finished) { + e->set_name("smtp_client_command_finished"); + smtp_reply_add_to_event(reply, e); + cmd->event_finished = TRUE; + } + e_debug(e->event(), "Failed: %s", smtp_reply_log(reply)); + + if (callback != NULL) + (void)callback(reply, cmd->context); + } + + tmp_cmd = cmd; + smtp_client_command_abort(&tmp_cmd); + + smtp_client_command_unref(&cmd); + smtp_client_connection_unref(&conn); +} + +void smtp_client_command_fail(struct smtp_client_command **_cmd, + unsigned int status, const char *error) +{ + struct smtp_reply reply; + const char *text_lines[] = {error, NULL}; + + i_zero(&reply); + reply.status = status; + reply.text_lines = text_lines; + reply.enhanced_code.x = 9; + + smtp_client_command_fail_reply(_cmd, &reply); +} + +static void smtp_client_command_fail_delayed(struct smtp_client_command **_cmd) +{ + struct smtp_client_command *cmd = *_cmd; + + e_debug(cmd->event, "Fail delayed"); + + i_assert(!cmd->delay_failure); + i_assert(cmd->state < SMTP_CLIENT_COMMAND_STATE_FINISHED); + smtp_client_command_fail_reply(_cmd, cmd->delayed_failure); +} + +void smtp_client_commands_list_abort(struct smtp_client_command *cmds_list, + unsigned int cmds_list_count) +{ + struct smtp_client_command *cmd; + ARRAY(struct smtp_client_command *) cmds_arr; + struct smtp_client_command **cmds; + unsigned int count, i; + + if (cmds_list == NULL) + return; + i_assert(cmds_list_count > 0); + + /* Copy the array and reference the commands to be robust against more + than one command disappearing from the list */ + t_array_init(&cmds_arr, cmds_list_count); + for (cmd = cmds_list; cmd != NULL; cmd = cmd->next) { + smtp_client_command_ref(cmd); + array_push_back(&cmds_arr, &cmd); + } + + cmds = array_get_modifiable(&cmds_arr, &count); + for (i = 0; i < count; i++) { + cmd = cmds[i]; + /* Fail the reply */ + smtp_client_command_abort(&cmds[i]); + /* Drop our reference */ + smtp_client_command_unref(&cmd); + } +} + +void smtp_client_commands_list_fail_reply(struct smtp_client_command *cmds_list, + unsigned int cmds_list_count, + const struct smtp_reply *reply) +{ + struct smtp_client_command *cmd; + ARRAY(struct smtp_client_command *) cmds_arr; + struct smtp_client_command **cmds; + unsigned int count, i; + + if (cmds_list == NULL) + return; + i_assert(cmds_list_count > 0); + + /* Copy the array and reference the commands to be robust against more + than one command disappearing from the list */ + t_array_init(&cmds_arr, cmds_list_count); + for (cmd = cmds_list; cmd != NULL; cmd = cmd->next) { + smtp_client_command_ref(cmd); + array_push_back(&cmds_arr, &cmd); + } + + cmds = array_get_modifiable(&cmds_arr, &count); + for (i = 0; i < count; i++) { + cmd = cmds[i]; + /* Fail the reply */ + smtp_client_command_fail_reply(&cmds[i], reply); + /* Drop our reference */ + smtp_client_command_unref(&cmd); + } +} + +void smtp_client_commands_abort_delayed(struct smtp_client_connection *conn) +{ + struct smtp_client_command *cmd; + + timeout_remove(&conn->to_cmd_fail); + + cmd = conn->cmd_fail_list; + conn->cmd_fail_list = NULL; + while (cmd != NULL) { + struct smtp_client_command *cmd_next = cmd->next; + + cmd->delaying_failure = FALSE; + smtp_client_command_abort(&cmd); + cmd = cmd_next; + } +} + +void smtp_client_commands_fail_delayed(struct smtp_client_connection *conn) +{ + struct smtp_client_command *cmd; + + timeout_remove(&conn->to_cmd_fail); + + cmd = conn->cmd_fail_list; + conn->cmd_fail_list = NULL; + while (cmd != NULL) { + struct smtp_client_command *cmd_next = cmd->next; + + cmd->delaying_failure = FALSE; + smtp_client_command_fail_delayed(&cmd); + cmd = cmd_next; + } +} + +void smtp_client_command_set_abort_callback(struct smtp_client_command *cmd, + void (*callback)(void *context), + void *context) +{ + cmd->abort_callback = callback; + cmd->abort_context = context; +} + +void smtp_client_command_set_sent_callback(struct smtp_client_command *cmd, + void (*callback)(void *context), + void *context) +{ + cmd->sent_callback = callback; + cmd->sent_context = context; +} + +void smtp_client_command_set_replies(struct smtp_client_command *cmd, + unsigned int replies) +{ + i_assert(cmd->replies_expected == 1 || + cmd->replies_expected == replies); + i_assert(replies > 0); + i_assert(cmd->replies_seen <= 1); + cmd->replies_expected = replies; +} + +static void smtp_client_command_sent(struct smtp_client_command *cmd) +{ + struct event_passthrough *e; + + e = event_create_passthrough(cmd->event)-> + set_name("smtp_client_command_sent"); + + if (cmd->data == NULL) + e_debug(e->event(), "Sent"); + else { + i_assert(str_len(cmd->data) > 2); + str_truncate(cmd->data, str_len(cmd->data)-2); + e_debug(e->event(), "Sent: %s", str_c(cmd->data)); + } + + if (smtp_client_command_name_equals(cmd, "QUIT")) + cmd->conn->sent_quit = TRUE; + + if (cmd->sent_callback != NULL) { + cmd->sent_callback(cmd->sent_context); + cmd->sent_callback = NULL; + } +} + +static int +smtp_client_command_finish_dot_stream(struct smtp_client_command *cmd) +{ + struct smtp_client_connection *conn = cmd->conn; + int ret; + + i_assert(cmd->stream_dot); + i_assert(conn->dot_output != NULL); + + /* This concludes the dot stream with CRLF.CRLF */ + if ((ret = o_stream_finish(conn->dot_output)) < 0) { + o_stream_unref(&conn->dot_output); + smtp_client_connection_handle_output_error(conn); + return -1; + } + if (ret == 0) + return 0; + o_stream_unref(&conn->dot_output); + return 1; +} + +static void smtp_client_command_payload_input(struct smtp_client_command *cmd) +{ + struct smtp_client_connection *conn = cmd->conn; + + io_remove(&conn->io_cmd_payload); + + smtp_client_connection_trigger_output(conn); +} + +static int smtp_client_command_send_stream(struct smtp_client_command *cmd) +{ + struct smtp_client_connection *conn = cmd->conn; + struct istream *stream = cmd->stream; + struct ostream *output = conn->conn.output; + enum ostream_send_istream_result res; + int ret; + + io_remove(&conn->io_cmd_payload); + + if (cmd->stream_finished) { + if ((ret = smtp_client_command_finish_dot_stream(cmd)) <= 0) + return ret; + /* Done sending payload */ + e_debug(cmd->event, "Finished sending payload"); + i_stream_unref(&cmd->stream); + return 1; + } + if (cmd->stream_dot) { + if (conn->dot_output == NULL) + conn->dot_output = o_stream_create_dot(output, FALSE); + output = conn->dot_output; + } + + /* We're sending the stream now */ + o_stream_set_max_buffer_size(output, IO_BLOCK_SIZE); + res = o_stream_send_istream(output, stream); + o_stream_set_max_buffer_size(output, SIZE_MAX); + + switch (res) { + case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: + i_assert(cmd->stream_size == 0 || + stream->v_offset == cmd->stream_size); + /* Finished with the stream */ + e_debug(cmd->event, "Finished reading payload stream"); + cmd->stream_finished = TRUE; + if (cmd->stream_dot) { + ret = smtp_client_command_finish_dot_stream(cmd); + if (ret <= 0) + return ret; + } + /* Done sending payload */ + e_debug(cmd->event, "Finished sending payload"); + i_stream_unref(&cmd->stream); + return 1; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: + /* Input is blocking (client needs to act; disable timeout) */ + conn->io_cmd_payload = io_add_istream( + stream, smtp_client_command_payload_input, cmd); + return 0; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: + e_debug(cmd->event, "Partially sent payload"); + i_assert(cmd->stream_size == 0 || + stream->v_offset < cmd->stream_size); + return 0; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: + /* The provided payload stream is broken; + fail this command separately */ + smtp_client_command_fail( + &cmd, SMTP_CLIENT_COMMAND_ERROR_BROKEN_PAYLOAD, + "Broken payload stream"); + /* We're in the middle of sending a command, so the connection + will also have to be aborted */ + o_stream_unref(&conn->dot_output); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST, + t_strdup_printf("read(%s) failed: %s", + i_stream_get_name(stream), + i_stream_get_error(stream)), + "Broken payload stream"); + return -1; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: + /* Normal connection failure */ + o_stream_unref(&conn->dot_output); + smtp_client_connection_handle_output_error(conn); + return -1; + } + i_unreached(); +} + +static int smtp_client_command_send_line(struct smtp_client_command *cmd) +{ + struct smtp_client_connection *conn = cmd->conn; + const char *data; + size_t size; + ssize_t sent; + + if (cmd->data == NULL) + return 1; + + while (cmd->send_pos < cmd->data->used) { + data = CONST_PTR_OFFSET(cmd->data->data, cmd->send_pos); + size = cmd->data->used - cmd->send_pos; + + sent = o_stream_send(conn->conn.output, data, size); + if (sent <= 0) { + if (sent < 0) { + smtp_client_connection_handle_output_error(conn); + return -1; + } + e_debug(cmd->event, "Blocked while sending"); + return 0; + } + cmd->send_pos += sent; + } + + i_assert(cmd->send_pos == cmd->data->used); + return 1; +} + +static bool +smtp_client_command_pipeline_is_open(struct smtp_client_connection *conn) +{ + struct smtp_client_command *cmd = conn->cmd_send_queue_head; + + if (cmd == NULL) + return TRUE; + + if (cmd->plug) { + e_debug(cmd->event, "Pipeline is plugged"); + return FALSE; + } + + if (conn->state < SMTP_CLIENT_CONNECTION_STATE_READY && + (cmd->flags & SMTP_CLIENT_COMMAND_FLAG_PRELOGIN) == 0) { + /* Wait until we're fully connected */ + e_debug(cmd->event, "Connection not ready [state=%s]", + smtp_client_connection_state_names[conn->state]); + return FALSE; + } + + cmd = conn->cmd_wait_list_head; + if (cmd != NULL && + (conn->caps.standard & SMTP_CAPABILITY_PIPELINING) == 0) { + /* Cannot pipeline; wait for reply */ + e_debug(cmd->event, "Pipeline occupied"); + return FALSE; + } + while (cmd != NULL) { + if ((conn->caps.standard & SMTP_CAPABILITY_PIPELINING) == 0 || + (cmd->flags & SMTP_CLIENT_COMMAND_FLAG_PIPELINE) == 0 || + cmd->locked) { + /* Cannot pipeline with previous command; + wait for reply */ + e_debug(cmd->event, "Pipeline blocked"); + return FALSE; + } + cmd = cmd->next; + } + + return TRUE; +} + +static void smtp_cient_command_wait(struct smtp_client_command *cmd) +{ + struct smtp_client_connection *conn = cmd->conn; + + /* Move command to wait list. */ + i_assert(conn->cmd_send_queue_count > 0); + i_assert(conn->cmd_send_queue_count > 1 || + (cmd->prev == NULL && cmd->next == NULL)); + DLLIST2_REMOVE(&conn->cmd_send_queue_head, + &conn->cmd_send_queue_tail, cmd); + conn->cmd_send_queue_count--; + DLLIST2_APPEND(&conn->cmd_wait_list_head, + &conn->cmd_wait_list_tail, cmd); + conn->cmd_wait_list_count++; +} + +static int smtp_client_command_do_send_more(struct smtp_client_connection *conn) +{ + struct smtp_client_command *cmd; + int ret; + + if (conn->cmd_streaming != NULL) { + cmd = conn->cmd_streaming; + i_assert(cmd->stream != NULL); + } else { + /* Check whether we can send anything */ + cmd = conn->cmd_send_queue_head; + if (cmd == NULL) + return 0; + if (!smtp_client_command_pipeline_is_open(conn)) + return 0; + + cmd->state = SMTP_CLIENT_COMMAND_STATE_SENDING; + conn->sending_command = TRUE; + + if ((ret = smtp_client_command_send_line(cmd)) <= 0) + return ret; + + /* Command line sent. move command to wait list. */ + smtp_cient_command_wait(cmd); + cmd->state = SMTP_CLIENT_COMMAND_STATE_WAITING; + } + + if (cmd->stream != NULL && + (ret = smtp_client_command_send_stream(cmd)) <= 0) { + if (ret < 0) + return -1; + e_debug(cmd->event, "Blocked while sending payload"); + if (conn->cmd_streaming != cmd) { + i_assert(conn->cmd_streaming == NULL); + conn->cmd_streaming = cmd; + smtp_client_command_ref(cmd); + } + return 0; + } + + conn->sending_command = FALSE; + if (conn->cmd_streaming != cmd || + smtp_client_command_unref(&conn->cmd_streaming)) + smtp_client_command_sent(cmd); + return 1; +} + +int smtp_client_command_send_more(struct smtp_client_connection *conn) +{ + int ret; + + while ((ret = smtp_client_command_do_send_more(conn)) > 0); + if (ret < 0) + return -1; + + smtp_client_connection_update_cmd_timeout(conn); + return ret; +} + +static void +smtp_client_command_disconnected(struct smtp_client_connection *conn) +{ + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST, + NULL, "Disconnected"); +} + +static void +smtp_client_command_insert_prioritized(struct smtp_client_command *cmd, + enum smtp_client_command_flags flag) +{ + struct smtp_client_connection *conn = cmd->conn; + struct smtp_client_command *cmd_cur, *cmd_prev; + + cmd_cur = conn->cmd_send_queue_head; + if (cmd_cur == NULL || (cmd_cur->flags & flag) == 0) { + DLLIST2_PREPEND(&conn->cmd_send_queue_head, + &conn->cmd_send_queue_tail, cmd); + conn->cmd_send_queue_count++; + } else { + cmd_prev = cmd_cur; + cmd_cur = cmd_cur->next; + while (cmd_cur != NULL && (cmd_cur->flags & flag) != 0) { + cmd_prev = cmd_cur; + cmd_cur = cmd_cur->next; + } + DLLIST2_INSERT_AFTER(&conn->cmd_send_queue_head, + &conn->cmd_send_queue_tail, cmd_prev, cmd); + conn->cmd_send_queue_count++; + } +} + +void smtp_client_command_submit_after(struct smtp_client_command *cmd, + struct smtp_client_command *after) +{ + struct smtp_client_connection *conn = cmd->conn; + struct event_passthrough *e; + + i_assert(after == NULL || cmd->conn == after->conn); + + smtp_client_command_update_event(cmd); + e = event_create_passthrough(cmd->event)-> + set_name("smtp_client_command_started"); + + cmd->state = SMTP_CLIENT_COMMAND_STATE_SUBMITTED; + + if (smtp_client_command_name_equals(cmd, "EHLO")) + cmd->ehlo = TRUE; + + if (conn->state == SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED) { + /* Add commands to send queue for delayed failure reply from + ioloop */ + DLLIST2_APPEND(&conn->cmd_send_queue_head, + &conn->cmd_send_queue_tail, cmd); + conn->cmd_send_queue_count++; + if (conn->to_commands == NULL) { + conn->to_commands = timeout_add_short( + 0, smtp_client_command_disconnected, conn); + } + e_debug(e->event(), "Submitted, but disconnected"); + return; + } + + if (cmd->data != NULL) + str_append(cmd->data, "\r\n"); + + if ((cmd->flags & SMTP_CLIENT_COMMAND_FLAG_PRELOGIN) != 0 && + conn->state < SMTP_CLIENT_CONNECTION_STATE_READY) { + /* Pre-login commands get inserted before everything else */ + smtp_client_command_insert_prioritized( + cmd, SMTP_CLIENT_COMMAND_FLAG_PRELOGIN); + if (!conn->corked) + smtp_client_connection_trigger_output(conn); + e_debug(e->event(), "Submitted with priority"); + return; + } + + if (after != NULL) { + if (after->state >= SMTP_CLIENT_COMMAND_STATE_WAITING) { + /* Not in the send queue anymore; just prepend */ + DLLIST2_PREPEND(&conn->cmd_send_queue_head, + &conn->cmd_send_queue_tail, cmd); + conn->cmd_send_queue_count++; + } else { + /* Insert after indicated command */ + DLLIST2_INSERT_AFTER(&conn->cmd_send_queue_head, + &conn->cmd_send_queue_tail, + after, cmd); + conn->cmd_send_queue_count++; + } + } else if ((cmd->flags & SMTP_CLIENT_COMMAND_FLAG_PRIORITY) != 0) { + /* Insert at beginning of queue for priority commands */ + smtp_client_command_insert_prioritized( + cmd, SMTP_CLIENT_COMMAND_FLAG_PRIORITY); + } else { + /* Just append at end of queue */ + DLLIST2_APPEND(&conn->cmd_send_queue_head, + &conn->cmd_send_queue_tail, cmd); + conn->cmd_send_queue_count++; + } + + if (conn->state >= SMTP_CLIENT_CONNECTION_STATE_READY) + smtp_client_connection_start_cmd_timeout(conn); + + if (!conn->corked) + smtp_client_connection_trigger_output(conn); + e_debug(e->event(), "Submitted"); +} + +void smtp_client_command_submit(struct smtp_client_command *cmd) +{ + smtp_client_command_submit_after(cmd, NULL); +} + +void smtp_client_command_set_flags(struct smtp_client_command *cmd, + enum smtp_client_command_flags flags) +{ + cmd->flags = flags; +} + +void smtp_client_command_write(struct smtp_client_command *cmd, + const char *cmd_str) +{ + unsigned int len = strlen(cmd_str); + + i_assert(cmd->state < SMTP_CLIENT_COMMAND_STATE_SUBMITTED); + if (cmd->data == NULL) + cmd->data = str_new(cmd->pool, len + 2); + str_append(cmd->data, cmd_str); +} + +void smtp_client_command_printf(struct smtp_client_command *cmd, + const char *cmd_fmt, ...) +{ + va_list args; + + va_start(args, cmd_fmt); + smtp_client_command_vprintf(cmd, cmd_fmt, args); + va_end(args); +} + +void smtp_client_command_vprintf(struct smtp_client_command *cmd, + const char *cmd_fmt, va_list args) +{ + if (cmd->data == NULL) + cmd->data = str_new(cmd->pool, 128); + str_vprintfa(cmd->data, cmd_fmt, args); +} + +void smtp_client_command_set_stream(struct smtp_client_command *cmd, + struct istream *input, bool dot) +{ + int ret; + + cmd->stream = input; + i_stream_ref(input); + + if ((ret = i_stream_get_size(input, TRUE, &cmd->stream_size)) <= 0) { + if (ret < 0) { + e_error(cmd->event, "i_stream_get_size(%s) failed: %s", + i_stream_get_name(input), + i_stream_get_error(input)); + } + /* Size must be known if stream is to be sent in chunks */ + i_assert(dot); + cmd->stream_size = 0; + } + + cmd->stream_dot = dot; + cmd->has_stream = TRUE; +} + +int smtp_client_command_input_reply(struct smtp_client_command *cmd, + const struct smtp_reply *reply) +{ + struct smtp_client_connection *conn = cmd->conn; + smtp_client_command_callback_t *callback = cmd->callback; + void *context = cmd->context; + bool finished; + + i_assert(cmd->replies_seen < cmd->replies_expected); + finished = (++cmd->replies_seen == cmd->replies_expected); + + /* Finish command event at final reply or first failure */ + struct event_passthrough *e = event_create_passthrough(cmd->event); + if (!cmd->event_finished && + (finished || !smtp_reply_is_success(reply))) { + e->set_name("smtp_client_command_finished"); + smtp_reply_add_to_event(reply, e); + cmd->event_finished = TRUE; + } + e_debug(e->event(), "Got reply (%u/%u): %s " + "(%u commands pending, %u commands queued)", + cmd->replies_seen, cmd->replies_expected, + smtp_reply_log(reply), conn->cmd_wait_list_count, + conn->cmd_send_queue_count); + + if (finished) { + i_assert(conn->cmd_wait_list_count > 0); + DLLIST2_REMOVE(&conn->cmd_wait_list_head, + &conn->cmd_wait_list_tail, cmd); + conn->cmd_wait_list_count--; + if (cmd->aborting) + cmd->state = SMTP_CLIENT_COMMAND_STATE_ABORTED; + else if (cmd->state != SMTP_CLIENT_COMMAND_STATE_ABORTED) + cmd->state = SMTP_CLIENT_COMMAND_STATE_FINISHED; + + smtp_client_connection_update_cmd_timeout(conn); + smtp_client_command_drop_callback(cmd); + } + + if (!cmd->aborting && callback != NULL) + callback(reply, context); + + if (finished) { + smtp_client_command_unref(&cmd); + smtp_client_connection_trigger_output(conn); + } + return 1; +} + +enum smtp_client_command_state +smtp_client_command_get_state(struct smtp_client_command *cmd) +{ + return cmd->state; +} + +/* + * Standard commands + */ + +/* NOTE: Pipelining is only enabled for certain commands: + + From RFC 2920, Section 3.1: + + Once the client SMTP has confirmed that support exists for the + pipelining extension, the client SMTP may then elect to transmit + groups of SMTP commands in batches without waiting for a response to + each individual command. In particular, the commands RSET, MAIL FROM, + SEND FROM, SOML FROM, SAML FROM, and RCPT TO can all appear anywhere + in a pipelined command group. The EHLO, DATA, VRFY, EXPN, TURN, + QUIT, and NOOP commands can only appear as the last command in a + group since their success or failure produces a change of state which + the client SMTP must accommodate. (NOOP is included in this group so + it can be used as a synchronization point.) + + Additional commands added by other SMTP extensions may only appear as + the last command in a group unless otherwise specified by the + extensions that define the commands. + */ + +/* NOOP */ + +#undef smtp_client_command_noop_submit_after +struct smtp_client_command * +smtp_client_command_noop_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + smtp_client_command_callback_t *callback, + void *context) +{ + struct smtp_client_command *cmd; + + cmd = smtp_client_command_new(conn, flags, callback, context); + smtp_client_command_write(cmd, "NOOP"); + smtp_client_command_submit_after(cmd, after); + return cmd; +} + +#undef smtp_client_command_noop_submit +struct smtp_client_command * +smtp_client_command_noop_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + smtp_client_command_callback_t *callback, + void *context) +{ + return smtp_client_command_noop_submit_after(conn, flags, NULL, + callback, context); +} + +/* VRFY */ + +#undef smtp_client_command_vrfy_submit_after +struct smtp_client_command * +smtp_client_command_vrfy_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + const char *param, + smtp_client_command_callback_t *callback, + void *context) +{ + struct smtp_client_command *cmd; + + cmd = smtp_client_command_new(conn, flags, callback, context); + smtp_client_command_write(cmd, "VRFY "); + smtp_string_write(cmd->data, param); + smtp_client_command_submit_after(cmd, after); + return cmd; +} + +#undef smtp_client_command_vrfy_submit +struct smtp_client_command * +smtp_client_command_vrfy_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + const char *param, + smtp_client_command_callback_t *callback, + void *context) +{ + return smtp_client_command_vrfy_submit_after(conn, flags, NULL, param, + callback, context); +} + +/* RSET */ + +#undef smtp_client_command_rset_submit_after +struct smtp_client_command * +smtp_client_command_rset_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + smtp_client_command_callback_t *callback, + void *context) +{ + struct smtp_client_command *cmd; + + cmd = smtp_client_command_new(conn, + flags | SMTP_CLIENT_COMMAND_FLAG_PIPELINE, + callback, context); + smtp_client_command_write(cmd, "RSET"); + smtp_client_command_submit_after(cmd, after); + return cmd; +} + +#undef smtp_client_command_rset_submit +struct smtp_client_command * +smtp_client_command_rset_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + smtp_client_command_callback_t *callback, + void *context) +{ + return smtp_client_command_rset_submit_after(conn, flags, NULL, + callback, context); +} + +/* MAIL FROM: */ + +#undef smtp_client_command_mail_submit_after +struct smtp_client_command * +smtp_client_command_mail_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + const struct smtp_address *from, + const struct smtp_params_mail *params, + smtp_client_command_callback_t *callback, + void *context) +{ + struct smtp_client_command *cmd; + + smtp_client_connection_send_xclient(conn); + + flags |= SMTP_CLIENT_COMMAND_FLAG_PIPELINE; + cmd = smtp_client_command_new(conn, flags, callback, context); + if (!conn->set.mail_send_broken_path || !smtp_address_is_broken(from)) { + /* Compose MAIL command with normalized path. */ + smtp_client_command_printf(cmd, "MAIL FROM:<%s>", + smtp_address_encode(from)); + } else { + /* Compose MAIL command with broken path (for proxy). */ + smtp_client_command_printf(cmd, "MAIL FROM:<%s>", + smtp_address_encode_raw(from)); + } + if (params != NULL) { + size_t orig_len = str_len(cmd->data); + const char *const *extensions = NULL; + + if (array_is_created(&conn->caps.mail_param_extensions)) { + extensions = + array_front(&conn->caps.mail_param_extensions); + } + + str_append_c(cmd->data, ' '); + smtp_params_mail_write(cmd->data, conn->caps.standard, + extensions, params); + if (str_len(cmd->data) == orig_len + 1) + str_truncate(cmd->data, orig_len); + } + smtp_client_command_submit_after(cmd, after); + return cmd; +} + +#undef smtp_client_command_mail_submit +struct smtp_client_command * +smtp_client_command_mail_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + const struct smtp_address *from, + const struct smtp_params_mail *params, + smtp_client_command_callback_t *callback, + void *context) +{ + return smtp_client_command_mail_submit_after(conn, flags, NULL, + from, params, + callback, context); +} + +/* RCPT TO: */ + +#undef smtp_client_command_rcpt_submit_after +struct smtp_client_command * +smtp_client_command_rcpt_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + const struct smtp_address *to, + const struct smtp_params_rcpt *params, + smtp_client_command_callback_t *callback, + void *context) +{ + struct smtp_client_command *cmd; + + flags |= SMTP_CLIENT_COMMAND_FLAG_PIPELINE; + cmd = smtp_client_command_new(conn, flags, callback, context); + smtp_client_command_printf(cmd, "RCPT TO:<%s>", + smtp_address_encode(to)); + if (params != NULL) { + size_t orig_len = str_len(cmd->data); + const char *const *extensions = NULL; + + if (array_is_created(&conn->caps.rcpt_param_extensions)) { + extensions = + array_front(&conn->caps.rcpt_param_extensions); + } + + str_append_c(cmd->data, ' '); + smtp_params_rcpt_write(cmd->data, conn->caps.standard, + extensions, params); + if (str_len(cmd->data) == orig_len + 1) + str_truncate(cmd->data, orig_len); + } + smtp_client_command_submit_after(cmd, after); + return cmd; +} + +#undef smtp_client_command_rcpt_submit +struct smtp_client_command * +smtp_client_command_rcpt_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + const struct smtp_address *from, + const struct smtp_params_rcpt *params, + smtp_client_command_callback_t *callback, + void *context) +{ + return smtp_client_command_rcpt_submit_after(conn, flags, NULL, from, + params, callback, context); +} + +/* DATA or BDAT */ + +struct _cmd_data_context { + struct smtp_client_connection *conn; + pool_t pool; + + struct smtp_client_command *cmd_data, *cmd_first; + ARRAY(struct smtp_client_command *) cmds; + + struct istream *data; + uoff_t data_offset, data_left; +}; + +static void +_cmd_bdat_send_chunks(struct _cmd_data_context *ctx, + struct smtp_client_command *after); + +static void _cmd_data_context_free(struct _cmd_data_context *ctx) +{ + if (ctx->cmd_data != NULL) { + /* Abort the main (possibly unsubmitted) data command */ + smtp_client_command_set_abort_callback(ctx->cmd_data, + NULL, NULL); + ctx->cmd_data = NULL; + } + i_stream_unref(&ctx->data); +} + +static void _cmd_data_abort(struct _cmd_data_context *ctx) +{ + struct smtp_client_command **cmds; + unsigned int count, i; + + /* Drop all pending commands */ + cmds = array_get_modifiable(&ctx->cmds, &count); + for (i = 0; i < count; i++) { + smtp_client_command_set_abort_callback(cmds[i], NULL, NULL); + smtp_client_command_abort(&cmds[i]); + } +} + +static void _cmd_data_abort_cb(void *context) +{ + struct _cmd_data_context *ctx = (struct _cmd_data_context *)context; + + /* The main (possibly unsubmitted) data command got aborted */ + _cmd_data_abort(ctx); + _cmd_data_context_free(ctx); +} + +static void +_cmd_data_error(struct _cmd_data_context *ctx, const struct smtp_reply *reply) +{ + struct smtp_client_command *cmd = ctx->cmd_data; + + if (cmd != NULL) { + /* Fail the main (possibly unsubmitted) data command so that + the caller gets notified */ + smtp_client_command_fail_reply(&cmd, reply); + } +} + +static void _cmd_data_cb(const struct smtp_reply *reply, void *context) +{ + struct _cmd_data_context *ctx = (struct _cmd_data_context *)context; + struct smtp_client_command *const *cmds, *cmd; + unsigned int count; + + /* Got DATA reply; one command must be pending */ + cmds = array_get(&ctx->cmds, &count); + i_assert(count > 0); + + if (reply->status == 354) { + /* Submit second stage: which is a command with only a stream */ + cmd = ctx->cmd_data; + smtp_client_command_submit_after(cmd, cmds[0]); + + /* Nothing else to do, so drop the context already */ + _cmd_data_context_free(ctx); + } else { + /* Error */ + _cmd_data_error(ctx, reply); + } +} + +static void _cmd_bdat_cb(const struct smtp_reply *reply, void *context) +{ + struct _cmd_data_context *ctx = (struct _cmd_data_context *)context; + + /* Got BDAT reply, so there must be ones pending */ + i_assert(array_count(&ctx->cmds) > 0); + + if ((reply->status / 100) != 2) { + /* Error */ + _cmd_data_error(ctx, reply); + return; + } + + /* Drop the command from the list */ + array_pop_front(&ctx->cmds); + + /* Send more BDAT commands if necessary */ + (void)_cmd_bdat_send_chunks(ctx, NULL); + + if (array_count(&ctx->cmds) == 0) { + /* All of the BDAT commands finished already */ + _cmd_data_context_free(ctx); + } +} + +static void _cmd_bdat_sent_cb(void *context) +{ + struct _cmd_data_context *ctx = (struct _cmd_data_context *)context; + + /* Send more BDAT commands if possible */ + (void)_cmd_bdat_send_chunks(ctx, NULL); +} + +static int +_cmd_bdat_read_data(struct _cmd_data_context *ctx, size_t *data_size_r) +{ + int ret; + + while ((ret = i_stream_read(ctx->data)) > 0); + + if (ret < 0) { + if (ret != -2 && ctx->data->stream_errno != 0) { + e_error(ctx->cmd_data->event, + "Failed to read DATA stream: %s", + i_stream_get_error(ctx->data)); + smtp_client_command_fail( + &ctx->cmd_data, + SMTP_CLIENT_COMMAND_ERROR_BROKEN_PAYLOAD, + "Broken payload stream"); + return -1; + } + } + + *data_size_r = i_stream_get_data_size(ctx->data); + return 0; +} + +static void +_cmd_bdat_send_chunks(struct _cmd_data_context *ctx, + struct smtp_client_command *after) +{ + struct smtp_client_connection *conn = ctx->conn; + const struct smtp_client_settings *set = &conn->set; + struct smtp_client_command *const *cmds, *cmd, *cmd_prev; + unsigned int count; + struct istream *chunk; + size_t data_size, max_chunk_size; + + if (smtp_client_command_get_state(ctx->cmd_data) >= + SMTP_CLIENT_COMMAND_STATE_SUBMITTED) { + /* Finished or aborted */ + return; + } + + /* Pipeline management: determine where to submit the next command */ + cmds = array_get(&ctx->cmds, &count); + cmd_prev = NULL; + if (after != NULL) { + i_assert(count == 0); + cmd_prev = after; + } else if (count > 0) { + cmd_prev = cmds[count-1]; + smtp_client_command_unlock(cmd_prev); + } + + data_size = ctx->data_left; + if (data_size > 0) { + max_chunk_size = set->max_data_chunk_size; + } else { + if (ctx->data->v_offset < ctx->data_offset) { + /* Previous BDAT command not completely sent */ + return; + } + max_chunk_size = i_stream_get_max_buffer_size(ctx->data); + if (set->max_data_chunk_size < max_chunk_size) + max_chunk_size = set->max_data_chunk_size; + if (_cmd_bdat_read_data(ctx, &data_size) < 0) + return; + } + + /* Keep sending more chunks until pipeline is filled to the limit */ + cmd = NULL; + while (data_size > max_chunk_size || + (data_size == max_chunk_size && !ctx->data->eof)) { + enum smtp_client_command_flags flags = ctx->cmd_data->flags; + size_t size = (data_size > set->max_data_chunk_size ? + set->max_data_chunk_size : data_size); + chunk = i_stream_create_range(ctx->data, ctx->data_offset, + size); + + flags |= SMTP_CLIENT_COMMAND_FLAG_PIPELINE; + cmd = smtp_client_command_new(conn, flags, _cmd_bdat_cb, ctx); + smtp_client_command_set_abort_callback(cmd, _cmd_data_abort_cb, + ctx); + smtp_client_command_set_stream(cmd, chunk, FALSE); + i_stream_unref(&chunk); + smtp_client_command_printf(cmd, "BDAT %"PRIuUOFF_T, + (uoff_t)size); + smtp_client_command_submit_after(cmd, cmd_prev); + array_push_back(&ctx->cmds, &cmd); + + ctx->data_offset += size; + data_size -= size; + + if (array_count(&ctx->cmds) >= set->max_data_chunk_pipeline) { + /* Pipeline full */ + if (ctx->data_left != 0) { + /* Data stream size known: + record where we left off */ + ctx->data_left = data_size; + } + smtp_client_command_lock(cmd); + return; + } + + cmd_prev = cmd; + } + + if (ctx->data_left != 0) { + /* Data stream size known: record where we left off */ + ctx->data_left = data_size; + } else if (!ctx->data->eof) { + /* More to read */ + if (cmd != NULL) { + smtp_client_command_set_sent_callback( + cmd, _cmd_bdat_sent_cb, ctx); + } + return; + } + + /* The last chunk, which may actually be empty */ + chunk = i_stream_create_range(ctx->data, ctx->data_offset, data_size); + + /* Submit final command */ + cmd = ctx->cmd_data; + smtp_client_command_set_stream(cmd, chunk, FALSE); + i_stream_unref(&chunk); + smtp_client_command_printf(cmd, "BDAT %zu LAST", data_size); + smtp_client_command_submit_after(cmd, cmd_prev); + + if (array_count(&ctx->cmds) == 0) { + /* All of the previous BDAT commands got replies already */ + _cmd_data_context_free(ctx); + } +} + +#undef smtp_client_command_data_submit_after +struct smtp_client_command * +smtp_client_command_data_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + struct istream *data, + smtp_client_command_callback_t *callback, + void *context) +{ + const struct smtp_client_settings *set = &conn->set; + struct _cmd_data_context *ctx; + struct smtp_client_command *cmd, *cmd_data; + + /* Create the final command early for reference by the caller; + it will not be submitted for now. The DATA command is handled in + two stages (== command submissions), the BDAT command in one or more. + */ + cmd = cmd_data = smtp_client_command_create(conn, flags, + callback, context); + + /* Protect against race conditions */ + cmd_data->delay_failure = TRUE; + + /* Create context in the final command's pool */ + ctx = p_new(cmd->pool, struct _cmd_data_context, 1); + ctx->conn = conn; + ctx->pool = cmd->pool; + ctx->cmd_data = cmd; + + /* Capture abort event with our context */ + smtp_client_command_set_abort_callback(cmd, _cmd_data_abort_cb, ctx); + + ctx->data = data; + i_stream_ref(data); + + if ((conn->caps.standard & SMTP_CAPABILITY_CHUNKING) == 0) { + /* DATA */ + p_array_init(&ctx->cmds, ctx->pool, 1); + + /* Data stream is sent in one go in the second stage. Since the + data is sent in a '.'-terminated stream, it size + is not relevant here. */ + smtp_client_command_set_stream(cmd, ctx->data, TRUE); + + /* Submit the initial DATA command */ + cmd = smtp_client_command_new(conn, flags, _cmd_data_cb, ctx); + smtp_client_command_set_abort_callback(cmd, _cmd_data_abort_cb, + ctx); + smtp_client_command_write(cmd, "DATA"); + smtp_client_command_submit_after(cmd, after); + array_push_back(&ctx->cmds, &cmd); + } else { + /* BDAT */ + p_array_init(&ctx->cmds, ctx->pool, + conn->set.max_data_chunk_pipeline); + + /* The data stream is sent in multiple chunks. Either the size + of the data stream is known or it is not. These cases are + handled a little differently. */ + if (i_stream_get_size(data, TRUE, &ctx->data_left) > 0) { + /* Size is known */ + i_assert(ctx->data_left >= data->v_offset); + ctx->data_left -= data->v_offset; + } else { + /* Size is unknown */ + ctx->data_left = 0; + + /* Make sure we can send chunks of sufficient size by + making the data stream buffer size limit at least + equally large. */ + if (i_stream_get_max_buffer_size(ctx->data) < + set->max_data_chunk_size) { + i_stream_set_max_buffer_size( + ctx->data, set->max_data_chunk_size); + } + } + + /* Send the first BDAT command(s) */ + ctx->data_offset = data->v_offset; + _cmd_bdat_send_chunks(ctx, after); + } + + cmd_data->delay_failure = FALSE; + return cmd_data; +} + +#undef smtp_client_command_data_submit +struct smtp_client_command * +smtp_client_command_data_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct istream *data, + smtp_client_command_callback_t *callback, + void *context) +{ + return smtp_client_command_data_submit_after(conn, flags, NULL, data, + callback, context); +} diff --git a/src/lib-smtp/smtp-client-command.h b/src/lib-smtp/smtp-client-command.h new file mode 100644 index 0000000..70dba92 --- /dev/null +++ b/src/lib-smtp/smtp-client-command.h @@ -0,0 +1,274 @@ +#ifndef SMTP_CLIENT_COMMAND +#define SMTP_CLIENT_COMMAND + +struct smtp_reply; +struct smtp_params_mail; +struct smtp_params_rcpt; +struct smtp_client_command; +struct smtp_client_connection; + +enum smtp_client_command_state { + SMTP_CLIENT_COMMAND_STATE_NEW = 0, + SMTP_CLIENT_COMMAND_STATE_SUBMITTED, + SMTP_CLIENT_COMMAND_STATE_SENDING, + SMTP_CLIENT_COMMAND_STATE_WAITING, + SMTP_CLIENT_COMMAND_STATE_FINISHED, + SMTP_CLIENT_COMMAND_STATE_ABORTED +}; + +enum smtp_client_command_flags { + /* The command is sent to server before login (or is the login + command itself). Non-prelogin commands will be queued until login + is successful. */ + SMTP_CLIENT_COMMAND_FLAG_PRELOGIN = 0x01, + /* This command may be positioned anywhere in a PIPELINING group. */ + SMTP_CLIENT_COMMAND_FLAG_PIPELINE = 0x02, + /* This command has priority and needs to be inserted before anything + else. This is e.g. used to make sure that the initial handshake + commands are sent before any other command that may already be + submitted to the connection. */ + SMTP_CLIENT_COMMAND_FLAG_PRIORITY = 0x04 +}; + +/* Called when reply is received for command. */ +typedef void smtp_client_command_callback_t(const struct smtp_reply *reply, + void *context); + +struct smtp_client_command * +smtp_client_command_new(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_new(conn, flags, callback, context) \ + smtp_client_command_new(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + (smtp_client_command_callback_t *)callback, context) + +/* Create a plug command, which is a dummy command that blocks the send queue. + This is used by transactions to prevent subsequently submitted + transactions from messing up the command sequence while the present + transaction is still submitting commands. The plug command is aborted once + the send queue is to be released. */ +struct smtp_client_command * +smtp_client_command_plug(struct smtp_client_connection *conn, + struct smtp_client_command *after); + +void smtp_client_command_ref(struct smtp_client_command *cmd); +bool smtp_client_command_unref(struct smtp_client_command **_cmd) + ATTR_NOWARN_UNUSED_RESULT; + +bool smtp_client_command_name_equals(struct smtp_client_command *cmd, + const char *name); + +/* Lock the command; no commands after this one will be sent until this one + finishes */ +void smtp_client_command_lock(struct smtp_client_command *cmd); +void smtp_client_command_unlock(struct smtp_client_command *cmd); + +void smtp_client_command_set_flags(struct smtp_client_command *cmd, + enum smtp_client_command_flags flags); +void smtp_client_command_set_stream(struct smtp_client_command *cmd, + struct istream *input, bool dot); + +void smtp_client_command_write(struct smtp_client_command *cmd, + const char *cmd_str); +void smtp_client_command_printf(struct smtp_client_command *cmd, + const char *cmd_fmt, ...) ATTR_FORMAT(2, 3); +void smtp_client_command_vprintf(struct smtp_client_command *cmd, + const char *cmd_fmt, va_list args) + ATTR_FORMAT(2, 0); + +void smtp_client_command_submit_after(struct smtp_client_command *cmd, + struct smtp_client_command *after); +void smtp_client_command_submit(struct smtp_client_command *cmd); + +void smtp_client_command_abort(struct smtp_client_command **_cmd); +void smtp_client_command_set_abort_callback(struct smtp_client_command *cmd, + void (*callback)(void *context), + void *context); + +void smtp_client_command_set_sent_callback(struct smtp_client_command *cmd, + void (*callback)(void *context), + void *context); + +void smtp_client_command_set_replies(struct smtp_client_command *cmd, + unsigned int replies); + +enum smtp_client_command_state +smtp_client_command_get_state(struct smtp_client_command *cmd) ATTR_PURE; + + +/* + * Standard commands + */ + +/* Send NOOP */ +struct smtp_client_command * +smtp_client_command_noop_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_noop_submit_after(conn, flags, after, \ + callback, context) \ + smtp_client_command_noop_submit_after(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + after, (smtp_client_command_callback_t *)callback, context) +struct smtp_client_command * +smtp_client_command_noop_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_noop_submit(conn, flags, callback, context) \ + smtp_client_command_noop_submit(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + (smtp_client_command_callback_t *)callback, context) + +/* Send VRFY */ +struct smtp_client_command * +smtp_client_command_vrfy_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + const char *param, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_vrfy_submit_after(conn, flags, after, param, \ + callback, context) \ + smtp_client_command_vrfy_submit_after(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + after, param, \ + (smtp_client_command_callback_t *)callback, context) +struct smtp_client_command * +smtp_client_command_vrfy_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + const char *param, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_vrfy_submit(conn, flags, param, callback, context) \ + smtp_client_command_vrfy_submit(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + param, (smtp_client_command_callback_t *)callback, context) + +/* Send RSET */ +struct smtp_client_command * +smtp_client_command_rset_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_rset_submit_after(conn, flags, after, \ + callback, context) \ + smtp_client_command_rset_submit_after(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + after, (smtp_client_command_callback_t *)callback, context) +struct smtp_client_command * +smtp_client_command_rset_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_rset_submit(conn, flags, callback, context) \ + smtp_client_command_rset_submit(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + (smtp_client_command_callback_t *)callback, context) + +/* Send MAIL FROM:
*/ +struct smtp_client_command * +smtp_client_command_mail_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + const struct smtp_address *from, + const struct smtp_params_mail *params, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_mail_submit_after(conn, flags, after, \ + address, params, \ + callback, context) \ + smtp_client_command_mail_submit_after(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + after, address, params, \ + (smtp_client_command_callback_t *)callback, context) +struct smtp_client_command * +smtp_client_command_mail_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + const struct smtp_address *from, + const struct smtp_params_mail *params, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_mail_submit(conn, flags, address, params, \ + callback, context) \ + smtp_client_command_mail_submit(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + address, params, \ + (smtp_client_command_callback_t *)callback, context) + +/* send RCPT TO:
parameters */ +struct smtp_client_command * +smtp_client_command_rcpt_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + const struct smtp_address *to, + const struct smtp_params_rcpt *params, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_rcpt_submit_after(conn, flags, after, to, params, \ + callback, context) \ + smtp_client_command_rcpt_submit_after(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + after, to, params, \ + (smtp_client_command_callback_t *)callback, context) +struct smtp_client_command * +smtp_client_command_rcpt_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + const struct smtp_address *to, + const struct smtp_params_rcpt *params, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_rcpt_submit(conn, flags, to, params, \ + callback, context) \ + smtp_client_command_rcpt_submit(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + to, params, \ + (smtp_client_command_callback_t *)callback, context) + +/* Send message data using DATA or BDAT (preferred if supported). + This handles the DATA 354 response implicitly. Making sure that the data has + CRLF line endings consistently is the responsibility of the caller. + */ +struct smtp_client_command * +smtp_client_command_data_submit_after(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct smtp_client_command *after, + struct istream *data, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_data_submit_after(conn, flags, after, data, \ + callback, context) \ + smtp_client_command_data_submit_after(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + after, data, \ + (smtp_client_command_callback_t *)callback, context) +struct smtp_client_command * +smtp_client_command_data_submit(struct smtp_client_connection *conn, + enum smtp_client_command_flags flags, + struct istream *data, + smtp_client_command_callback_t *callback, + void *context); +#define smtp_client_command_data_submit(conn, flags, data, callback, context) \ + smtp_client_command_data_submit(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + data, (smtp_client_command_callback_t *)callback, context) + +#endif diff --git a/src/lib-smtp/smtp-client-connection.c b/src/lib-smtp/smtp-client-connection.c new file mode 100644 index 0000000..47862ad --- /dev/null +++ b/src/lib-smtp/smtp-client-connection.c @@ -0,0 +1,2522 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "array.h" +#include "safe-memset.h" +#include "ioloop.h" +#include "net.h" +#include "base64.h" +#include "istream.h" +#include "ostream.h" +#include "ostream-dot.h" +#include "iostream-rawlog.h" +#include "iostream-ssl.h" +#include "str.h" +#include "dsasl-client.h" +#include "dns-lookup.h" +#include "smtp-syntax.h" +#include "smtp-reply-parser.h" +#include "smtp-client-private.h" + +#include + +#define SMTP_CLIENT_ERROR_TEXT_CONNECT_FAILED \ + "Failed to connect to remote server" + +const char *const smtp_client_connection_state_names[] = { + "disconnected", + "connecting", + "handshaking", + "authenticating", + "ready", + "transaction" +}; + +static int +smtp_client_connection_ssl_init(struct smtp_client_connection *conn, + const char **error_r); +static void +smtp_client_connection_handshake(struct smtp_client_connection *conn); +static void +smtp_client_connection_established(struct smtp_client_connection *conn); +static void +smtp_client_connection_start_transaction(struct smtp_client_connection *conn); +static void +smtp_client_connection_connect_next_ip(struct smtp_client_connection *conn); +static bool +smtp_client_connection_last_ip(struct smtp_client_connection *conn); + +/* + * Capabilities + */ + +enum smtp_capability +smtp_client_connection_get_capabilities(struct smtp_client_connection *conn) +{ + return conn->caps.standard; +} + +uoff_t smtp_client_connection_get_size_capability( + struct smtp_client_connection *conn) +{ + return conn->caps.size; +} + +static const struct smtp_client_capability_extra * +smtp_client_connection_find_extra_capability( + struct smtp_client_connection *conn, const char *cap_name) +{ + const struct smtp_client_capability_extra *cap; + + if (!array_is_created(&conn->extra_capabilities)) + return NULL; + array_foreach(&conn->extra_capabilities, cap) { + if (strcasecmp(cap->name, cap_name) == 0) + return cap; + } + return NULL; +} + +void smtp_client_connection_accept_extra_capability( + struct smtp_client_connection *conn, + const struct smtp_client_capability_extra *cap) +{ + i_assert(smtp_client_connection_find_extra_capability(conn, cap->name) + == NULL); + + if (!array_is_created(&conn->extra_capabilities)) + p_array_init(&conn->extra_capabilities, conn->pool, 8); + + struct smtp_client_capability_extra cap_new = { + .name = p_strdup(conn->pool, cap->name), + }; + + if (cap->mail_param_extensions != NULL) { + cap_new.mail_param_extensions = + p_strarray_dup(conn->pool, cap->mail_param_extensions); + } + if (cap->rcpt_param_extensions != NULL) { + cap_new.rcpt_param_extensions = + p_strarray_dup(conn->pool, cap->rcpt_param_extensions); + } + + array_push_back(&conn->extra_capabilities, &cap_new); +} + +const struct smtp_capability_extra * +smtp_client_connection_get_extra_capability(struct smtp_client_connection *conn, + const char *name) +{ + const struct smtp_capability_extra *cap; + + if (!array_is_created(&conn->caps.extra)) + return NULL; + + array_foreach(&conn->caps.extra, cap) { + if (strcasecmp(cap->name, name) == 0) + return cap; + } + + return NULL; +} + +/* + * + */ + +static void +smtp_client_connection_commands_abort(struct smtp_client_connection *conn) +{ + smtp_client_commands_list_abort(conn->cmd_wait_list_head, + conn->cmd_wait_list_count); + smtp_client_commands_list_abort(conn->cmd_send_queue_head, + conn->cmd_send_queue_count); + smtp_client_commands_abort_delayed(conn); +} + +static void +smtp_client_connection_commands_fail_reply(struct smtp_client_connection *conn, + const struct smtp_reply *reply) +{ + smtp_client_commands_list_fail_reply(conn->cmd_wait_list_head, + conn->cmd_wait_list_count, reply); + smtp_client_commands_list_fail_reply(conn->cmd_send_queue_head, + conn->cmd_send_queue_count, reply); + smtp_client_commands_fail_delayed(conn); +} + +static void +smtp_client_connection_commands_fail(struct smtp_client_connection *conn, + unsigned int status, const char *error) +{ + struct smtp_reply reply; + + smtp_reply_init(&reply, status, error); + reply.enhanced_code.x = 9; + + smtp_client_connection_commands_fail_reply(conn, &reply); +} + +static void +smtp_client_connection_transactions_abort(struct smtp_client_connection *conn) +{ + struct smtp_client_transaction *trans; + + trans = conn->transactions_head; + while (trans != NULL) { + struct smtp_client_transaction *trans_next = trans->next; + smtp_client_transaction_abort(trans); + trans = trans_next; + } +} + +static void +smtp_client_connection_transactions_fail_reply( + struct smtp_client_connection *conn, const struct smtp_reply *reply) +{ + struct smtp_client_transaction *trans; + + trans = conn->transactions_head; + while (trans != NULL) { + struct smtp_client_transaction *trans_next = trans->next; + smtp_client_transaction_connection_result(trans, reply); + trans = trans_next; + } +} + +static void +smtp_client_connection_transactions_fail(struct smtp_client_connection *conn, + unsigned int status, const char *error) +{ + struct smtp_reply reply; + + smtp_reply_init(&reply, status, error); + reply.enhanced_code.x = 9; + + smtp_client_connection_transactions_fail_reply(conn, &reply); +} + +static void +smtp_client_connection_transactions_drop(struct smtp_client_connection *conn) +{ + struct smtp_client_transaction *trans; + + trans = conn->transactions_head; + while (trans != NULL) { + struct smtp_client_transaction *trans_next = trans->next; + smtp_client_transaction_connection_destroyed(trans); + trans = trans_next; + } +} + +static void +smtp_client_connection_login_callback(struct smtp_client_connection *conn, + const struct smtp_reply *reply) +{ + const struct smtp_client_login_callback *cb; + ARRAY(struct smtp_client_login_callback) login_cbs; + + if (conn->state_data.login_reply == NULL) { + conn->state_data.login_reply = + smtp_reply_clone(conn->state_pool, reply); + } + + if (!array_is_created(&conn->login_callbacks) || + array_count(&conn->login_callbacks) == 0) + return; + + t_array_init(&login_cbs, array_count(&conn->login_callbacks)); + array_copy(&login_cbs.arr, 0, &conn->login_callbacks.arr, 0, + array_count(&conn->login_callbacks)); + array_foreach(&login_cbs, cb) { + i_assert(cb->callback != NULL); + if (conn->closed) + break; + if (cb->callback != NULL) + cb->callback(reply, cb->context); + } + array_clear(&conn->login_callbacks); +} + +static void +smtp_client_connection_login_fail(struct smtp_client_connection *conn, + unsigned int status, const char *error) +{ + struct smtp_reply reply; + + smtp_reply_init(&reply, status, error); + reply.enhanced_code.x = 9; + + smtp_client_connection_login_callback(conn, &reply); +} + +static void +smtp_client_connection_set_state(struct smtp_client_connection *conn, + enum smtp_client_connection_state state) +{ + conn->state = state; +} + +void smtp_client_connection_cork(struct smtp_client_connection *conn) +{ + conn->corked = TRUE; + if (conn->conn.output != NULL) + o_stream_cork(conn->conn.output); +} + +void smtp_client_connection_uncork(struct smtp_client_connection *conn) +{ + conn->corked = FALSE; + if (conn->conn.output != NULL) { + if (o_stream_uncork_flush(conn->conn.output) < 0) { + smtp_client_connection_handle_output_error(conn); + return; + } + smtp_client_connection_trigger_output(conn); + } +} + +enum smtp_client_connection_state +smtp_client_connection_get_state(struct smtp_client_connection *conn) +{ + return conn->state; +} + +static void +smtp_client_command_timeout(struct smtp_client_connection *conn) +{ + smtp_client_connection_ref(conn); + + smtp_client_connection_fail(conn, SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT, + "Command timed out, disconnecting", + "Command timed out"); + smtp_client_connection_unref(&conn); +} + +void smtp_client_connection_start_cmd_timeout( + struct smtp_client_connection *conn) +{ + unsigned int msecs = conn->set.command_timeout_msecs; + + if (conn->state < SMTP_CLIENT_CONNECTION_STATE_READY) { + /* pre-login uses connect timeout */ + return; + } + if (msecs == 0) { + /* no timeout configured */ + timeout_remove(&conn->to_commands); + return; + } + if (conn->cmd_wait_list_head == NULL && !conn->sending_command) { + /* no commands pending */ + timeout_remove(&conn->to_commands); + return; + } + + e_debug(conn->event, "Start timeout"); + if (conn->to_commands == NULL) { + conn->to_commands = timeout_add( + msecs, smtp_client_command_timeout, conn); + } +} + +void smtp_client_connection_update_cmd_timeout( + struct smtp_client_connection *conn) +{ + unsigned int msecs = conn->set.command_timeout_msecs; + + if (conn->state < SMTP_CLIENT_CONNECTION_STATE_READY) { + /* pre-login uses connect timeout */ + return; + } + if (msecs == 0) { + /* no timeout configured */ + timeout_remove(&conn->to_commands); + return; + } + + if (conn->cmd_wait_list_head == NULL && !conn->sending_command) { + if (conn->to_commands != NULL) { + e_debug(conn->event, + "No commands pending; stop timeout"); + } + timeout_remove(&conn->to_commands); + } else if (conn->to_commands != NULL) { + e_debug(conn->event, "Reset timeout"); + timeout_reset(conn->to_commands); + } else { + smtp_client_connection_start_cmd_timeout(conn); + } +} + +static void +smtp_client_connection_fail_reply(struct smtp_client_connection *conn, + const struct smtp_reply *reply) +{ + e_debug(conn->event, "Connection failed: %s", smtp_reply_log(reply)); + + smtp_client_connection_ref(conn); + conn->failing = TRUE; + + smtp_client_connection_disconnect(conn); + smtp_client_connection_login_callback(conn, reply); + + smtp_client_connection_transactions_fail_reply(conn, reply); + smtp_client_connection_commands_fail_reply(conn, reply); + + conn->failing = FALSE; + smtp_client_connection_unref(&conn); +} + +void smtp_client_connection_fail(struct smtp_client_connection *conn, + unsigned int status, const char *error, + const char *user_error) +{ + struct smtp_reply reply; + const char *text_lines[2]; + + if (error != NULL) + e_error(conn->event, "%s", error); + + i_zero(&text_lines); + i_assert(user_error != NULL); + if (conn->set.verbose_user_errors && error != NULL) + text_lines[0] = error; + else + text_lines[0] = user_error; + + timeout_remove(&conn->to_connect); + + if (status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED && + !smtp_client_connection_last_ip(conn)) { + conn->to_connect = timeout_add_short( + 0, smtp_client_connection_connect_next_ip, conn); + return; + } + + i_zero(&reply); + reply.status = status; + reply.text_lines = text_lines; + reply.enhanced_code.x = 9; + + smtp_client_connection_fail_reply(conn, &reply); +} + +static void +smtp_client_connection_fail_equal(struct smtp_client_connection *conn, + unsigned int status, const char *error) +{ + smtp_client_connection_fail(conn, status, error, error); +} + +static void +smtp_client_connection_lost(struct smtp_client_connection *conn, + const char *error, const char *user_error) +{ + if (error != NULL) + error = t_strdup_printf("Connection lost: %s", error); + + if (user_error == NULL) + user_error = "Lost connection to remote server"; + else { + user_error = t_strdup_printf( + "Lost connection to remote server: %s", + user_error); + } + + if (conn->ssl_iostream != NULL) { + const char *sslerr = + ssl_iostream_get_last_error(conn->ssl_iostream); + + if (error != NULL && sslerr != NULL) { + error = t_strdup_printf("%s (last SSL error: %s)", + error, sslerr); + } else if (sslerr != NULL) { + error = t_strdup_printf( + "Connection lost (last SSL error: %s)", sslerr); + } + if (ssl_iostream_has_handshake_failed(conn->ssl_iostream)) { + /* This isn't really a "connection lost", but that we + don't trust the remote's SSL certificate. */ + i_assert(error != NULL); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + error, user_error); + return; + } + } + + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST, + error, user_error); +} + +void smtp_client_connection_handle_output_error( + struct smtp_client_connection *conn) +{ + struct ostream *output = conn->conn.output; + + if (output->stream_errno != EPIPE && + output->stream_errno != ECONNRESET) { + smtp_client_connection_lost( + conn, + t_strdup_printf("write(%s) failed: %s", + o_stream_get_name(conn->conn.output), + o_stream_get_error(conn->conn.output)), + "Write failure"); + } else { + smtp_client_connection_lost( + conn, "Remote disconnected while writing output", + "Remote closed connection unexpectedly"); + } +} + +static void +stmp_client_connection_ready(struct smtp_client_connection *conn, + const struct smtp_reply *reply) +{ + timeout_remove(&conn->to_connect); + + smtp_client_connection_set_state( + conn, SMTP_CLIENT_CONNECTION_STATE_READY); + conn->reset_needed = FALSE; + + e_debug(conn->event, "Connection ready"); + + smtp_client_connection_login_callback(conn, reply); + + smtp_client_connection_update_cmd_timeout(conn); + + smtp_client_connection_start_transaction(conn); +} + +static void +smtp_client_connection_xclient_cb(const struct smtp_reply *reply, + struct smtp_client_connection *conn) +{ + e_debug(conn->event, "Received XCLIENT handshake reply: %s", + smtp_reply_log(reply)); + + i_assert(conn->xclient_replies_expected > 0); + + if (reply->status == 421) { + smtp_client_connection_fail_reply(conn, reply); + return; + } + if (conn->state == SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED) + return; + + if (conn->to_connect != NULL) + timeout_reset(conn->to_connect); + if (--conn->xclient_replies_expected == 0) + smtp_client_connection_handshake(conn); +} + +static void +smtp_client_connection_xclient_submit(struct smtp_client_connection *conn, + const char *cmdstr) +{ + struct smtp_client_command *cmd; + enum smtp_client_command_flags flags; + + e_debug(conn->event, "Sending XCLIENT handshake"); + + flags = SMTP_CLIENT_COMMAND_FLAG_PRELOGIN | + SMTP_CLIENT_COMMAND_FLAG_PRIORITY; + + cmd = smtp_client_command_new(conn, flags, + smtp_client_connection_xclient_cb, conn); + smtp_client_command_write(cmd, cmdstr); + smtp_client_command_submit(cmd); + + conn->xclient_replies_expected++; +} + +static void +smtp_client_connection_xclient_add(struct smtp_client_connection *conn, + string_t *str, size_t offset, + const char *field, const char *value) +{ + size_t prev_offset = str_len(str); + const char *new_field; + + i_assert(prev_offset >= offset); + + str_append_c(str, ' '); + str_append(str, field); + str_append_c(str, '='); + smtp_xtext_encode_cstr(str, value); + + if (prev_offset == offset || + str_len(str) <= SMTP_BASE_LINE_LENGTH_LIMIT) + return; + + /* preserve field we just added */ + new_field = t_strdup(str_c(str) + prev_offset); + + /* revert to previous position */ + str_truncate(str, prev_offset); + + /* send XCLIENT command */ + smtp_client_connection_xclient_submit(conn, str_c(str)); + + /* start next XCLIENT command with new field */ + str_truncate(str, offset); + str_append(str, new_field); +} + +static void ATTR_FORMAT(5, 6) +smtp_client_connection_xclient_addf(struct smtp_client_connection *conn, + string_t *str, size_t offset, + const char *field, const char *format, ...) +{ + va_list args; + + va_start(args, format); + smtp_client_connection_xclient_add(conn, str, offset, field, + t_strdup_vprintf(format, args)); + va_end(args); +} + +void smtp_client_connection_send_xclient(struct smtp_client_connection *conn) +{ + const struct smtp_proxy_data *xclient = &conn->set.proxy_data; + const char **xclient_args = conn->caps.xclient_args; + size_t offset; + string_t *str; + + if (!conn->set.peer_trusted) + return; + if (conn->xclient_sent) + return; + if ((conn->caps.standard & SMTP_CAPABILITY_XCLIENT) == 0 || + conn->caps.xclient_args == NULL) + return; + + i_assert(conn->xclient_replies_expected == 0); + + /* http://www.postfix.org/XCLIENT_README.html: + + The client must not send XCLIENT commands that exceed the 512 + character limit for SMTP commands. To avoid exceeding the limit the + client should send the information in multiple XCLIENT commands; for + example, send NAME and ADDR last, after HELO and PROTO. Once ADDR is + sent, the client is usually no longer authorized to send XCLIENT + commands. + */ + + str = t_str_new(64); + str_append(str, "XCLIENT"); + offset = str_len(str); + + /* HELO */ + if (xclient->helo != NULL && + str_array_icase_find(xclient_args, "HELO")) { + smtp_client_connection_xclient_add(conn, str, offset, + "HELO", xclient->helo); + } + + /* PROTO */ + if (str_array_icase_find(xclient_args, "PROTO")) { + switch (xclient->proto) { + case SMTP_PROXY_PROTOCOL_SMTP: + smtp_client_connection_xclient_add(conn, str, offset, + "PROTO", "SMTP"); + break; + case SMTP_PROXY_PROTOCOL_ESMTP: + smtp_client_connection_xclient_add(conn, str, offset, + "PROTO", "ESMTP"); + break; + case SMTP_PROXY_PROTOCOL_LMTP: + smtp_client_connection_xclient_add(conn, str, offset, + "PROTO", "LMTP"); + break; + default: + break; + } + } + + /* LOGIN */ + if (xclient->login != NULL && + str_array_icase_find(xclient_args, "LOGIN")) { + smtp_client_connection_xclient_add(conn, str, offset, + "LOGIN", xclient->login); + } + + /* SESSION */ + if (xclient->session != NULL && + str_array_icase_find(xclient_args, "SESSION")) { + smtp_client_connection_xclient_add(conn, str, offset, + "SESSION", xclient->session); + } + + /* TTL */ + if (xclient->ttl_plus_1 > 0 && + str_array_icase_find(xclient_args, "TTL")) { + smtp_client_connection_xclient_addf(conn, str, offset, + "TTL", "%u", + xclient->ttl_plus_1-1); + } + + /* TIMEOUT */ + if (xclient->timeout_secs > 0 && + str_array_icase_find(xclient_args, "TIMEOUT")) { + smtp_client_connection_xclient_addf(conn, str, offset, + "TIMEOUT", "%u", + xclient->timeout_secs); + } + + /* PORT */ + if (xclient->source_port != 0 && + str_array_icase_find(xclient_args, "PORT")) { + smtp_client_connection_xclient_addf(conn, str, offset, + "PORT", "%u", + xclient->source_port); + } + + /* ADDR */ + if (xclient->source_ip.family != 0 && + str_array_icase_find(xclient_args, "ADDR")) { + const char *addr = net_ip2addr(&xclient->source_ip); + + /* Older versions of Dovecot LMTP don't quite follow Postfix' + specification of the XCLIENT command regarding IPv6 + addresses: the "IPV6:" prefix is omitted. For now, we + maintain this deviation for LMTP. Newer versions of Dovecot + LMTP can work with or without the prefix. */ + if (conn->protocol != SMTP_PROTOCOL_LMTP && + xclient->source_ip.family == AF_INET6) + addr = t_strconcat("IPV6:", addr, NULL); + smtp_client_connection_xclient_add(conn, str, offset, + "ADDR", addr); + } + + /* final XCLIENT command */ + if (str_len(str) > offset) + smtp_client_connection_xclient_submit(conn, str_c(str)); + + conn->xclient_sent = TRUE; +} + +static void +smtp_client_connection_clear_password(struct smtp_client_connection *conn) +{ + if (conn->set.remember_password) + return; + if (conn->password == NULL) + return; + safe_memset(conn->password, 0, strlen(conn->password)); + conn->set.password = NULL; + conn->password = NULL; +} + +static void +smtp_client_connection_auth_deinit(struct smtp_client_connection *conn) +{ + dsasl_client_free(&conn->sasl_client); + i_free(conn->sasl_ir); +} + +static void +smtp_client_connection_auth_cb(const struct smtp_reply *reply, + struct smtp_client_connection *conn) +{ + struct smtp_client_command *cmd, *cmd_auth = conn->cmd_auth; + const char *error; + + conn->cmd_auth = NULL; + i_assert(cmd_auth != NULL); + + if (reply->status == 334) { + const unsigned char *sasl_output; + size_t sasl_output_len, input_len; + buffer_t *buf; + + if (reply->text_lines[1] != NULL) { + error = t_strdup_printf( + "Authentication failed: " + "Server returned multi-line reply: %s", + smtp_reply_log(reply)); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED, + error, "Authentication protocol error"); + return; + } + if (conn->sasl_ir != NULL) { + if (*reply->text_lines[0] == '\0') { + /* Send initial response */ + cmd = smtp_client_command_new( + conn, SMTP_CLIENT_COMMAND_FLAG_PRELOGIN, + smtp_client_connection_auth_cb, conn); + smtp_client_command_write(cmd, conn->sasl_ir); + smtp_client_command_submit_after(cmd, cmd_auth); + conn->cmd_auth = cmd; + i_free(conn->sasl_ir); + return; + } + error = t_strdup_printf( + "Authentication failed: " + "Server sent unexpected server-first challenge: %s", + smtp_reply_log(reply)); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED, + error, "Authentication protocol error"); + return; + } + + input_len = strlen(reply->text_lines[0]); + buf = buffer_create_dynamic(pool_datastack_create(), + MAX_BASE64_DECODED_SIZE(input_len)); + if (base64_decode(reply->text_lines[0], input_len, + NULL, buf) < 0) { + error = t_strdup_printf( + "Authentication failed: " + "Server sent non-base64 input for AUTH: %s", + reply->text_lines[0]); + } else if (dsasl_client_input(conn->sasl_client, + buf->data, buf->used, + &error) < 0) { + error = t_strdup_printf("Authentication failed: %s", + error); + } else if (dsasl_client_output(conn->sasl_client, &sasl_output, + &sasl_output_len, &error) < 0) { + error = t_strdup_printf("Authentication failed: %s", + error); + } else { + string_t *smtp_output = t_str_new( + MAX_BASE64_ENCODED_SIZE(sasl_output_len) + 2); + base64_encode(sasl_output, sasl_output_len, + smtp_output); + cmd = smtp_client_command_new( + conn, SMTP_CLIENT_COMMAND_FLAG_PRELOGIN, + smtp_client_connection_auth_cb, conn); + smtp_client_command_write(cmd, conn->sasl_ir); + smtp_client_command_submit_after(cmd, cmd_auth); + conn->cmd_auth = cmd; + return; + } + + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED, + error, "Authentication failed"); + return; + } + + if ((reply->status / 100) != 2) { + e_error(conn->event, "Authentication failed: %s", + smtp_reply_log(reply)); + smtp_client_connection_fail_reply(conn, reply); + return; + } + + smtp_client_connection_clear_password(conn); + smtp_client_connection_auth_deinit(conn); + + e_debug(conn->event, "Authenticated successfully"); + + if (conn->to_connect != NULL) + timeout_reset(conn->to_connect); + conn->authenticated = TRUE; + smtp_client_connection_handshake(conn); +} + +static int +smtp_client_connection_get_sasl_mech(struct smtp_client_connection *conn, + const struct dsasl_client_mech **mech_r, + const char **error_r) +{ + const struct smtp_client_settings *set = &conn->set; + const char *const *mechanisms; + + if (set->sasl_mech != NULL) { + const char *mech = dsasl_client_mech_get_name(set->sasl_mech); + + if (!str_array_icase_find(conn->caps.auth_mechanisms, mech)) { + *error_r = t_strdup_printf( + "Server doesn't support `%s' SASL mechanism", + mech); + return -1; + } + *mech_r = set->sasl_mech; + return 0; + } + if (set->sasl_mechanisms == NULL || + set->sasl_mechanisms[0] == '\0') { + *mech_r = &dsasl_client_mech_plain; + return 0; + } + + /* find one of the specified SASL mechanisms */ + mechanisms = t_strsplit_spaces(set->sasl_mechanisms, ", "); + for (; *mechanisms != NULL; mechanisms++) { + if (str_array_icase_find(conn->caps.auth_mechanisms, + *mechanisms)) { + *mech_r = dsasl_client_mech_find(*mechanisms); + if (*mech_r != NULL) + return 0; + + *error_r = t_strdup_printf( + "Support for SASL mechanism `%s' is missing", + *mechanisms); + return -1; + } + } + *error_r = t_strdup_printf( + "Server doesn't support any of " + "the requested SASL mechanisms: %s", set->sasl_mechanisms); + return -1; +} + +static bool +smtp_client_connection_authenticate(struct smtp_client_connection *conn) +{ + const struct smtp_client_settings *set = &conn->set; + struct dsasl_client_settings sasl_set; + const struct dsasl_client_mech *sasl_mech = NULL; + struct smtp_client_command *cmd; + const unsigned char *sasl_output; + size_t sasl_output_len; + string_t *sasl_output_base64; + const char *error; + + if (set->username == NULL && set->sasl_mech == NULL) { + if (!conn->set.xclient_defer) + smtp_client_connection_send_xclient(conn); + return (conn->xclient_replies_expected == 0); + } + + smtp_client_connection_send_xclient(conn); + if (conn->xclient_replies_expected > 0) + return FALSE; + if (conn->authenticated) + return TRUE; + + if ((conn->caps.standard & SMTP_CAPABILITY_AUTH) == 0) { + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED, + NULL, "Authentication not supported"); + return FALSE; + } + + if (set->master_user != NULL) { + e_debug(conn->event, "Authenticating as %s for user %s", + set->master_user, set->username); + } else if (set->username == NULL) { + e_debug(conn->event, "Authenticating"); + } else { + e_debug(conn->event, "Authenticating as %s", set->username); + } + + if (smtp_client_connection_get_sasl_mech(conn, &sasl_mech, + &error) < 0) { + error = t_strdup_printf("Authentication failed: %s", error); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED, + error, "Server authentication mechanisms incompatible"); + return FALSE; + } + + i_zero(&sasl_set); + if (set->master_user == NULL) + sasl_set.authid = set->username; + else { + sasl_set.authid = set->master_user; + sasl_set.authzid = set->username; + } + sasl_set.password = set->password; + + conn->sasl_client = dsasl_client_new(sasl_mech, &sasl_set); + + if (dsasl_client_output(conn->sasl_client, &sasl_output, + &sasl_output_len, &error) < 0) { + error = t_strdup_printf( + "Failed to create initial %s SASL reply: %s", + dsasl_client_mech_get_name(sasl_mech), error); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED, + error, "Internal authentication failure"); + return FALSE; + } + + sasl_output_base64 = t_str_new( + MAX_BASE64_ENCODED_SIZE(sasl_output_len)); + base64_encode(sasl_output, sasl_output_len, sasl_output_base64); + + /* RFC 4954, Section 4: + + Note that the AUTH command is still subject to the line length + limitations defined in [SMTP]. If use of the initial response + argument would cause the AUTH command to exceed this length, the + client MUST NOT use the initial response parameter (and instead + proceed as defined in Section 5.1 of [SASL]). + + If the client is transmitting an initial response of zero length, it + MUST instead transmit the response as a single equals sign ("="). + This indicates that the response is present, but contains no data. + */ + + const char *init_resp = ""; + const char *mech_name = dsasl_client_mech_get_name(sasl_mech); + + i_assert(conn->sasl_ir == NULL); + if (str_len(sasl_output_base64) == 0) + init_resp = "="; + else if ((5 + strlen(mech_name) + 1 + str_len(sasl_output_base64)) > + SMTP_BASE_LINE_LENGTH_LIMIT) + conn->sasl_ir = i_strdup(str_c(sasl_output_base64)); + else + init_resp = str_c(sasl_output_base64); + + cmd = smtp_client_command_new(conn, SMTP_CLIENT_COMMAND_FLAG_PRELOGIN, + smtp_client_connection_auth_cb, conn); + if (*init_resp == '\0') + smtp_client_command_printf(cmd, "AUTH %s", mech_name); + else { + smtp_client_command_printf(cmd, "AUTH %s %s", + mech_name, init_resp); + } + smtp_client_command_submit(cmd); + conn->cmd_auth = cmd; + + smtp_client_connection_set_state( + conn, SMTP_CLIENT_CONNECTION_STATE_AUTHENTICATING); + return FALSE; +} + +static void +smtp_client_connection_starttls_cb(const struct smtp_reply *reply, + struct smtp_client_connection *conn) +{ + const char *error; + + e_debug(conn->event, "Received STARTTLS reply: %s", + smtp_reply_log(reply)); + + if ((reply->status / 100) != 2) { + smtp_client_connection_fail_reply(conn, reply); + return; + } + + if (smtp_client_connection_ssl_init(conn, &error) < 0) { + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + error, SMTP_CLIENT_ERROR_TEXT_CONNECT_FAILED); + } else { + if (conn->to_connect != NULL) + timeout_reset(conn->to_connect); + smtp_client_connection_handshake(conn); + } +} + +static bool smtp_client_connection_starttls(struct smtp_client_connection *conn) +{ + struct smtp_client_command *cmd; + + if (conn->ssl_mode == SMTP_CLIENT_SSL_MODE_STARTTLS && + conn->ssl_iostream == NULL) { + if ((conn->caps.standard & SMTP_CAPABILITY_STARTTLS) == 0) { + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + "Requested STARTTLS, " + "but server doesn't support it", + "STARTTLS not supported"); + return FALSE; + } + + e_debug(conn->event, "Starting TLS"); + + cmd = smtp_client_command_new( + conn, SMTP_CLIENT_COMMAND_FLAG_PRELOGIN, + smtp_client_connection_starttls_cb, conn); + smtp_client_command_write(cmd, "STARTTLS"); + smtp_client_command_submit(cmd); + return FALSE; + } + + return smtp_client_connection_authenticate(conn); +} + +static void +smtp_client_connection_record_param_extensions( + struct smtp_client_connection *conn, ARRAY_TYPE(const_string) *arr, + const char *const *extensions) +{ + pool_t pool = conn->cap_pool; + + if (extensions == NULL || *extensions == NULL) + return; + + if (!array_is_created(arr)) + p_array_init(arr, pool, 4); + else { + const char *const *end; + + /* Drop end marker */ + i_assert(array_count(arr) > 0); + end = array_back(arr); + i_assert(*end == NULL); + array_pop_back(arr); + } + + const char *const *new_p; + for (new_p = extensions; *new_p != NULL; new_p++) { + /* Drop duplicates */ + if (array_lsearch(arr, new_p, i_strcasecmp_p) != NULL) + continue; + + array_push_back(arr, new_p); + } + + /* Add new end marker */ + array_append_zero(arr); +} + +static void +smtp_client_connection_record_extra_capability( + struct smtp_client_connection *conn, const char *cap_name, + const char *const *params) +{ + const struct smtp_client_capability_extra *ccap_extra; + struct smtp_capability_extra cap_extra; + pool_t pool = conn->cap_pool; + + ccap_extra = smtp_client_connection_find_extra_capability( + conn, cap_name); + if (ccap_extra == NULL) + return; + if (smtp_client_connection_get_extra_capability(conn, cap_name) != NULL) + return; + + if (!array_is_created(&conn->caps.extra)) + p_array_init(&conn->caps.extra, pool, 4); + + i_zero(&cap_extra); + cap_extra.name = p_strdup(pool, ccap_extra->name); + cap_extra.params = p_strarray_dup(pool, params); + + array_push_back(&conn->caps.extra, &cap_extra); + + smtp_client_connection_record_param_extensions( + conn, &conn->caps.mail_param_extensions, + ccap_extra->mail_param_extensions); + smtp_client_connection_record_param_extensions( + conn, &conn->caps.rcpt_param_extensions, + ccap_extra->rcpt_param_extensions); +} + +static void +smtp_client_connection_handshake_cb(const struct smtp_reply *reply, + struct smtp_client_connection *conn) +{ + const char *const *lines; + + e_debug(conn->event, "Received handshake reply"); + + /* check reply status */ + if ((reply->status / 100) != 2) { + /* RFC 5321, Section 3.2: + For a particular connection attempt, if the server returns a + "command not recognized" response to EHLO, the client SHOULD + be able to fall back and send HELO. */ + if (conn->protocol == SMTP_PROTOCOL_SMTP && !conn->old_smtp && + (reply->status == 500 || reply->status == 502)) { + /* try HELO */ + conn->old_smtp = TRUE; + smtp_client_connection_handshake(conn); + return; + } + /* failed */ + smtp_client_connection_fail_reply(conn, reply); + return; + } + + /* reset capabilities */ + p_clear(conn->cap_pool); + i_zero(&conn->caps); + conn->caps.standard = conn->set.forced_capabilities; + + lines = reply->text_lines; + if (*lines == NULL) { + smtp_client_connection_fail_equal( + conn, SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY, + "Invalid handshake reply"); + return; + } + + /* greeting line */ + lines++; + + /* capability lines */ + while (*lines != NULL) { + enum smtp_capability cap; + const char *const *params; + const char *cap_name, *error; + + if (smtp_ehlo_line_parse(*lines, &cap_name, ¶ms, + &error) <= 0) { + e_warning(conn->event, + "Received invalid EHLO response line: %s", + error); + lines++; + continue; + } + + cap = smtp_capability_find_by_name(cap_name); + switch (cap) { + case SMTP_CAPABILITY_AUTH: + conn->caps.auth_mechanisms = + p_strarray_dup(conn->cap_pool, params); + break; + case SMTP_CAPABILITY_SIZE: + if (params == NULL || *params == NULL) + break; + if (str_to_uoff(*params, &conn->caps.size) < 0) { + e_warning(conn->event, + "Received invalid SIZE capability " + "in EHLO response line"); + cap = SMTP_CAPABILITY_NONE; + } + break; + case SMTP_CAPABILITY_XCLIENT: + conn->caps.xclient_args = + p_strarray_dup(conn->cap_pool, params); + break; + case SMTP_CAPABILITY_NONE: + smtp_client_connection_record_extra_capability( + conn, cap_name, params); + break; + default: + break; + } + + conn->caps.standard |= cap; + lines++; + } + + e_debug(conn->event, "Received server capabilities"); + + if (conn->to_connect != NULL) + timeout_reset(conn->to_connect); + if (smtp_client_connection_starttls(conn)) { + stmp_client_connection_ready(conn, reply); + } +} + +static void +smtp_client_connection_handshake(struct smtp_client_connection *conn) +{ + struct smtp_client_command *cmd; + enum smtp_client_command_flags flags; + const char *command; + + flags = SMTP_CLIENT_COMMAND_FLAG_PRELOGIN | + SMTP_CLIENT_COMMAND_FLAG_PRIORITY; + + switch (conn->protocol) { + case SMTP_PROTOCOL_SMTP: + command = (conn->old_smtp ? "HELO" : "EHLO"); + break; + case SMTP_PROTOCOL_LMTP: + command = "LHLO"; + break; + default: + i_unreached(); + } + + e_debug(conn->event, "Sending %s handshake", command); + + cmd = smtp_client_command_new( + conn, flags, smtp_client_connection_handshake_cb, conn); + smtp_client_command_write(cmd, command); + smtp_client_command_write(cmd, " "); + smtp_client_command_write(cmd, conn->set.my_hostname); + smtp_client_command_submit(cmd); + smtp_client_connection_set_state( + conn, SMTP_CLIENT_CONNECTION_STATE_HANDSHAKING); +} + +static int +smtp_client_connection_input_reply(struct smtp_client_connection *conn, + const struct smtp_reply *reply) +{ + int ret; + + /* initial greeting? */ + if (conn->state == SMTP_CLIENT_CONNECTION_STATE_CONNECTING) { + e_debug(conn->event, "Received greeting from server: %s", + smtp_reply_log(reply)); + if (reply->status != 220) { + if (smtp_reply_is_success(reply)) { + smtp_client_connection_fail_equal( + conn, + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY, + "Received inappropriate greeting"); + } else { + smtp_client_connection_fail_reply(conn, reply); + } + return -1; + } + smtp_client_connection_handshake(conn); + return 1; + } + + if (reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED) { + smtp_client_connection_fail_reply(conn, reply); + return -1; + } + + /* unexpected reply? */ + if (conn->cmd_wait_list_head == NULL) { + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY, + t_strdup_printf("Unexpected reply: %s", + smtp_reply_log(reply)), + "Got unexpected reply"); + return -1; + } + + /* replied early? */ + if (conn->cmd_wait_list_head == conn->cmd_streaming && + !conn->cmd_wait_list_head->stream_finished) { + if (!smtp_reply_is_success(reply)) { + e_debug(conn->event, "Early reply: %s", + smtp_reply_log(reply)); + } else { + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY, + t_strdup_printf( + "Got early success reply: %s", + smtp_reply_log(reply)), + "Got early success reply"); + return -1; + } + } + + /* command reply */ + ret = smtp_client_command_input_reply(conn->cmd_wait_list_head, reply); + + if (conn->state == SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED || + conn->conn.output == NULL) + return -1; + return ret; +} + +static void smtp_client_connection_input(struct connection *_conn) +{ + struct smtp_client_connection *conn = + (struct smtp_client_connection *)_conn; + bool enhanced_codes = ((conn->caps.standard & + SMTP_CAPABILITY_ENHANCEDSTATUSCODES) != 0); + struct smtp_reply *reply; + const char *error = NULL; + int ret; + + if (conn->ssl_iostream != NULL && + !ssl_iostream_is_handshaked(conn->ssl_iostream)) { + /* finish SSL negotiation by reading from input stream */ + while ((ret = i_stream_read(conn->conn.input)) > 0 || + ret == -2) { + if (ssl_iostream_is_handshaked(conn->ssl_iostream)) + break; + } + if (ret < 0) { + /* failed somehow */ + i_assert(ret != -2); + error = t_strdup_printf( + "SSL handshaking with %s failed: " + "read(%s) failed: %s", _conn->name, + i_stream_get_name(conn->conn.input), + i_stream_get_error(conn->conn.input)); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + error, SMTP_CLIENT_ERROR_TEXT_CONNECT_FAILED); + return; + } + + if (!ssl_iostream_is_handshaked(conn->ssl_iostream)) { + /* not finished */ + i_assert(ret == 0); + return; + } + + if (conn->to_connect != NULL) + timeout_reset(conn->to_connect); + } + + if (!conn->connect_succeeded) { + /* just got ready for SMTP handshake */ + smtp_client_connection_established(conn); + } + + smtp_client_connection_ref(conn); + o_stream_cork(conn->conn.output); + for (;;) { + if (conn->cmd_wait_list_head != NULL && + conn->cmd_wait_list_head->ehlo) { + if ((ret = smtp_reply_parse_ehlo(conn->reply_parser, + &reply, &error)) <= 0) + break; + } else { + if ((ret = smtp_reply_parse_next(conn->reply_parser, + enhanced_codes, + &reply, &error)) <= 0) + break; + } + + T_BEGIN { + ret = smtp_client_connection_input_reply(conn, reply); + } T_END; + if (ret < 0) { + if (conn->conn.output != NULL && !conn->corked) + o_stream_uncork(conn->conn.output); + smtp_client_connection_unref(&conn); + return; + } + } + + if (ret < 0 || (ret == 0 && conn->conn.input->eof)) { + if (conn->conn.input->stream_errno == ENOBUFS) { + smtp_client_connection_fail_equal( + conn, SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY, + "Command reply line too long"); + } else if (conn->conn.input->stream_errno != 0) { + smtp_client_connection_lost( + conn, + t_strdup_printf( + "read(%s) failed: %s", + i_stream_get_name(conn->conn.input), + i_stream_get_error(conn->conn.input)), + "Read failure"); + } else if (!i_stream_have_bytes_left(conn->conn.input)) { + if (conn->sent_quit) { + smtp_client_connection_lost( + conn, NULL, + "Remote closed connection"); + } else { + smtp_client_connection_lost( + conn, NULL, + "Remote closed connection unexpectedly"); + } + } else { + i_assert(error != NULL); + error = t_strdup_printf("Invalid command reply: %s", + error); + smtp_client_connection_fail_equal( + conn, SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY, + error); + } + } + if (ret >= 0 && conn->conn.output != NULL && !conn->corked) { + if (o_stream_uncork_flush(conn->conn.output) < 0) + smtp_client_connection_handle_output_error(conn); + } + smtp_client_connection_unref(&conn); +} + +static int smtp_client_connection_output(struct smtp_client_connection *conn) +{ + int ret; + + if (conn->to_connect != NULL) + timeout_reset(conn->to_connect); + + ret = o_stream_flush(conn->conn.output); + if (ret <= 0) { + if (ret < 0) + smtp_client_connection_handle_output_error(conn); + return ret; + } + + smtp_client_connection_ref(conn); + o_stream_cork(conn->conn.output); + if (smtp_client_command_send_more(conn) < 0) + ret = -1; + if (ret >= 0 && conn->conn.output != NULL && !conn->corked) { + if (o_stream_uncork_flush(conn->conn.output) < 0) + smtp_client_connection_handle_output_error(conn); + } + smtp_client_connection_unref(&conn); + return ret; +} + +void smtp_client_connection_trigger_output(struct smtp_client_connection *conn) +{ + if (conn->conn.output != NULL) + o_stream_set_flush_pending(conn->conn.output, TRUE); +} + +static void smtp_client_connection_destroy(struct connection *_conn) +{ + struct smtp_client_connection *conn = + (struct smtp_client_connection *)_conn; + const char *error; + + switch (_conn->disconnect_reason) { + case CONNECTION_DISCONNECT_NOT: + break; + case CONNECTION_DISCONNECT_DEINIT: + e_debug(conn->event, "Connection deinit"); + smtp_client_connection_close(&conn); + break; + case CONNECTION_DISCONNECT_CONNECT_TIMEOUT: + error = t_strdup_printf( + "connect(%s) failed: Connection timed out", + _conn->name); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + error, "Connect timed out"); + break; + default: + case CONNECTION_DISCONNECT_CONN_CLOSED: + if (conn->connect_failed) { + smtp_client_connection_fail(conn, + SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + NULL, SMTP_CLIENT_ERROR_TEXT_CONNECT_FAILED); + break; + } + if (_conn->input != NULL && _conn->input->stream_errno != 0) { + smtp_client_connection_lost( + conn, + t_strdup_printf("read(%s) failed: %s", + i_stream_get_name(conn->conn.input), + i_stream_get_error(conn->conn.input)), + "Read failure"); + break; + } + smtp_client_connection_lost( + conn, "Remote disconnected", + "Remote closed connection unexpectedly"); + break; + } +} + +static void +smtp_client_connection_established(struct smtp_client_connection *conn) +{ + i_assert(!conn->connect_succeeded); + conn->connect_succeeded = TRUE; + + if (conn->to_connect != NULL) + timeout_reset(conn->to_connect); + + /* set flush callback */ + o_stream_set_flush_callback(conn->conn.output, + smtp_client_connection_output, conn); +} + +static int +smtp_client_connection_ssl_handshaked(const char **error_r, void *context) +{ + struct smtp_client_connection *conn = context; + const char *error, *host = conn->host; + + if (ssl_iostream_check_cert_validity(conn->ssl_iostream, + host, &error) == 0) { + e_debug(conn->event, "SSL handshake successful"); + } else if (conn->set.ssl->allow_invalid_cert) { + e_debug(conn->event, "SSL handshake successful, " + "ignoring invalid certificate: %s", error); + } else { + *error_r = error; + return -1; + } + return 0; +} + +static void +smtp_client_connection_streams_changed(struct smtp_client_connection *conn) +{ + struct stat st; + + if (conn->set.rawlog_dir != NULL && + stat(conn->set.rawlog_dir, &st) == 0) { + iostream_rawlog_create(conn->set.rawlog_dir, + &conn->conn.input, &conn->conn.output); + } + + if (conn->reply_parser == NULL) { + conn->reply_parser = smtp_reply_parser_init( + conn->conn.input, conn->set.max_reply_size); + } else { + smtp_reply_parser_set_stream(conn->reply_parser, + conn->conn.input); + } + + connection_streams_changed(&conn->conn); +} + +static int +smtp_client_connection_init_ssl_ctx(struct smtp_client_connection *conn, + const char **error_r) +{ + struct smtp_client *client = conn->client; + const char *error; + + if (conn->ssl_ctx != NULL) + return 0; + + if (conn->set.ssl == client->set.ssl) { + if (smtp_client_init_ssl_ctx(client, error_r) < 0) + return -1; + conn->ssl_ctx = client->ssl_ctx; + ssl_iostream_context_ref(conn->ssl_ctx); + return 0; + } + + if (conn->set.ssl == NULL) { + *error_r = + "Requested SSL connection, but no SSL settings given"; + return -1; + } + if (ssl_iostream_client_context_cache_get(conn->set.ssl, &conn->ssl_ctx, + &error) < 0) { + *error_r = t_strdup_printf( + "Couldn't initialize SSL context: %s", error); + return -1; + } + return 0; +} + +static int +smtp_client_connection_ssl_init(struct smtp_client_connection *conn, + const char **error_r) +{ + const char *error; + + if (smtp_client_connection_init_ssl_ctx(conn, &error) < 0) { + *error_r = t_strdup_printf( + "Failed to initialize SSL: %s", error); + return -1; + } + + e_debug(conn->event, "Starting SSL handshake"); + + if (conn->raw_input != conn->conn.input) { + /* recreate rawlog after STARTTLS */ + i_stream_ref(conn->raw_input); + o_stream_ref(conn->raw_output); + i_stream_destroy(&conn->conn.input); + o_stream_destroy(&conn->conn.output); + conn->conn.input = conn->raw_input; + conn->conn.output = conn->raw_output; + } + + connection_input_halt(&conn->conn); + if (io_stream_create_ssl_client( + conn->ssl_ctx, conn->host, conn->set.ssl, + &conn->conn.input, &conn->conn.output, + &conn->ssl_iostream, &error) < 0) { + *error_r = t_strdup_printf( + "Couldn't initialize SSL client for %s: %s", + conn->conn.name, error); + return -1; + } + connection_input_resume(&conn->conn); + smtp_client_connection_streams_changed(conn); + + ssl_iostream_set_handshake_callback( + conn->ssl_iostream, smtp_client_connection_ssl_handshaked, + conn); + if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { + *error_r = t_strdup_printf( + "SSL handshake to %s failed: %s", conn->conn.name, + ssl_iostream_get_last_error(conn->ssl_iostream)); + return -1; + } + + if (ssl_iostream_is_handshaked(conn->ssl_iostream) && + !conn->connect_succeeded) { + smtp_client_connection_established(conn); + } else { + /* wait for handshake to complete; connection input handler + does the rest by reading from the input stream */ + o_stream_set_flush_callback( + conn->conn.output, smtp_client_connection_output, conn); + } + return 0; +} + +static void +smtp_client_connection_connected(struct connection *_conn, bool success) +{ + struct smtp_client_connection *conn = + (struct smtp_client_connection *)_conn; + const struct smtp_client_settings *set = &conn->set; + const char *error; + + if (!success) { + e_error(conn->event, "connect(%s) failed: %m", _conn->name); + conn->connect_failed = TRUE; + return; + } + + if (conn->set.debug) { + struct ip_addr local_ip; + in_port_t local_port; + int ret; + + ret = net_getsockname(_conn->fd_in, &local_ip, &local_port); + i_assert(ret == 0); + e_debug(conn->event, "Connected to server (from %s:%u)", + net_ip2addr(&local_ip), local_port); + } + + (void)net_set_tcp_nodelay(_conn->fd_out, TRUE); + if (set->socket_send_buffer_size > 0 && + net_set_send_buffer_size(_conn->fd_out, + set->socket_send_buffer_size) < 0) { + e_error(conn->event, + "net_set_send_buffer_size(%zu) failed: %m", + set->socket_send_buffer_size); + } + if (set->socket_recv_buffer_size > 0 && + net_set_recv_buffer_size(_conn->fd_in, + set->socket_recv_buffer_size) < 0) { + e_error(conn->event, + "net_set_recv_buffer_size(%zu) failed: %m", + set->socket_recv_buffer_size); + } + + conn->raw_input = conn->conn.input; + conn->raw_output = conn->conn.output; + smtp_client_connection_streams_changed(conn); + + if (conn->ssl_mode == SMTP_CLIENT_SSL_MODE_IMMEDIATE) { + if (smtp_client_connection_ssl_init(conn, &error) < 0) { + error = t_strdup_printf("connect(%s) failed: %s", + _conn->name, error); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + error, SMTP_CLIENT_ERROR_TEXT_CONNECT_FAILED); + } + } else { + smtp_client_connection_established(conn); + smtp_client_connection_input(_conn); + } +} + +static void +smtp_client_connection_connect_timeout(struct smtp_client_connection *conn) +{ + switch (conn->state) { + case SMTP_CLIENT_CONNECTION_STATE_CONNECTING: + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + t_strdup_printf( + "Connection timed out after %u seconds", + conn->set.connect_timeout_msecs/1000), + "Connect timed out"); + break; + case SMTP_CLIENT_CONNECTION_STATE_HANDSHAKING: + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + t_strdup_printf( + "SMTP handshake timed out after %u seconds", + conn->set.connect_timeout_msecs/1000), + "Handshake timed out"); + break; + case SMTP_CLIENT_CONNECTION_STATE_AUTHENTICATING: + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED, + t_strdup_printf( + "Authentication timed out after %u seconds", + conn->set.connect_timeout_msecs/1000), + "Authentication timed out"); + break; + default: + i_unreached(); + } +} + +static void +smtp_client_connection_delayed_connect_error( + struct smtp_client_connection *conn) +{ + e_debug(conn->event, "Delayed connect error"); + + timeout_remove(&conn->to_connect); + errno = conn->connect_errno; + smtp_client_connection_connected(&conn->conn, FALSE); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + NULL, SMTP_CLIENT_ERROR_TEXT_CONNECT_FAILED); +} + +static void +smtp_client_connection_do_connect(struct smtp_client_connection *conn) +{ + unsigned int msecs; + + if (conn->closed || conn->failing) + return; + + /* Clear state data */ + i_zero(&conn->state_data); + p_clear(conn->state_pool); + + if (connection_client_connect(&conn->conn) < 0) { + conn->connect_errno = errno; + e_debug(conn->event, "Connect failed: %m"); + conn->to_connect = timeout_add_short( + 0, smtp_client_connection_delayed_connect_error, conn); + return; + } + + /* don't use connection.h timeout because we want this timeout + to include also the SSL handshake */ + msecs = conn->set.connect_timeout_msecs; + if (msecs == 0) + msecs = conn->set.command_timeout_msecs; + i_assert(conn->to_connect == NULL); + if (msecs > 0) { + conn->to_connect = timeout_add( + msecs, smtp_client_connection_connect_timeout, conn); + } +} + +static bool smtp_client_connection_last_ip(struct smtp_client_connection *conn) +{ + i_assert(conn->prev_connect_idx < conn->ips_count); + return (conn->prev_connect_idx + 1) % conn->ips_count == 0; +} + +static void +smtp_client_connection_connect_next_ip(struct smtp_client_connection *conn) +{ + const struct ip_addr *ip, *my_ip = &conn->set.my_ip; + + timeout_remove(&conn->to_connect); + + conn->prev_connect_idx = (conn->prev_connect_idx+1) % conn->ips_count; + ip = &conn->ips[conn->prev_connect_idx]; + + if (my_ip->family != 0) { + e_debug(conn->event, "Connecting to %s:%u (from %s)", + net_ip2addr(ip), conn->port, net_ip2addr(my_ip)); + } else { + e_debug(conn->event, "Connecting to %s:%u", + net_ip2addr(ip), conn->port); + } + + connection_init_client_ip_from(conn->client->conn_list, &conn->conn, + (conn->host_is_ip ? NULL : conn->host), + ip, conn->port, my_ip); + + smtp_client_connection_do_connect(conn); +} + +static void +smtp_client_connection_connect_unix(struct smtp_client_connection *conn) +{ + timeout_remove(&conn->to_connect); + + e_debug(conn->event, "Connecting to socket %s", conn->path); + + connection_init_client_unix(conn->client->conn_list, &conn->conn, + conn->path); + + smtp_client_connection_do_connect(conn); +} + +static void +smtp_client_connection_delayed_host_lookup_failure( + struct smtp_client_connection *conn) +{ + e_debug(conn->event, "Delayed host lookup failure"); + + i_assert(conn->to_connect != NULL); + timeout_remove(&conn->to_connect); + smtp_client_connection_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED, + NULL, "Failed to lookup remote server"); +} + +static void +smtp_client_connection_dns_callback(const struct dns_lookup_result *result, + struct smtp_client_connection *conn) +{ + conn->dns_lookup = NULL; + + if (result->ret != 0) { + e_error(conn->event, "dns_lookup(%s) failed: %s", + conn->host, result->error); + timeout_remove(&conn->to_connect); + conn->to_connect = timeout_add_short( + 0, smtp_client_connection_delayed_host_lookup_failure, + conn); + return; + } + + e_debug(conn->event, "DNS lookup successful; got %d IPs", + result->ips_count); + + i_assert(result->ips_count > 0); + conn->ips_count = result->ips_count; + conn->ips = i_new(struct ip_addr, conn->ips_count); + memcpy(conn->ips, result->ips, sizeof(*conn->ips) * conn->ips_count); + conn->prev_connect_idx = conn->ips_count - 1; + + smtp_client_connection_connect_next_ip(conn); +} + +static void +smtp_client_connection_lookup_ip(struct smtp_client_connection *conn) +{ + struct dns_lookup_settings dns_set; + struct ip_addr ip, *ips; + unsigned int ips_count; + int ret; + + if (conn->ips_count != 0) + return; + + e_debug(conn->event, "Looking up IP address"); + + if (net_addr2ip(conn->host, &ip) == 0) { + /* IP address */ + conn->ips_count = 1; + conn->ips = i_new(struct ip_addr, conn->ips_count); + conn->ips[0] = ip; + conn->host_is_ip = TRUE; + } else if (conn->set.dns_client != NULL) { + e_debug(conn->event, "Performing asynchronous DNS lookup"); + (void)dns_client_lookup( + conn->set.dns_client, conn->host, + smtp_client_connection_dns_callback, conn, + &conn->dns_lookup); + } else if (conn->set.dns_client_socket_path != NULL) { + i_zero(&dns_set); + dns_set.dns_client_socket_path = + conn->set.dns_client_socket_path; + dns_set.timeout_msecs = conn->set.connect_timeout_msecs; + dns_set.event_parent = conn->event; + e_debug(conn->event, "Performing asynchronous DNS lookup"); + (void)dns_lookup(conn->host, &dns_set, + smtp_client_connection_dns_callback, conn, + &conn->dns_lookup); + } else { + /* no dns-conn, use blocking lookup */ + ret = net_gethostbyname(conn->host, &ips, &ips_count); + if (ret != 0) { + e_error(conn->event, "net_gethostbyname(%s) failed: %s", + conn->host, net_gethosterror(ret)); + timeout_remove(&conn->to_connect); + conn->to_connect = timeout_add_short( + 0, + smtp_client_connection_delayed_host_lookup_failure, + conn); + return; + } + + e_debug(conn->event, "DNS lookup successful; got %d IPs", + ips_count); + + conn->ips_count = ips_count; + conn->ips = i_new(struct ip_addr, ips_count); + memcpy(conn->ips, ips, ips_count * sizeof(*ips)); + } +} + +static void +smtp_client_connection_already_connected(struct smtp_client_connection *conn) +{ + i_assert(conn->state_data.login_reply != NULL); + + timeout_remove(&conn->to_connect); + + e_debug(conn->event, "Already connected"); + + smtp_client_connection_login_callback( + conn, conn->state_data.login_reply); +} + +static void +smtp_client_connection_connect_more(struct smtp_client_connection *conn) +{ + if (!array_is_created(&conn->login_callbacks) || + array_count(&conn->login_callbacks) == 0) { + /* No login callbacks required */ + return; + } + if (conn->state < SMTP_CLIENT_CONNECTION_STATE_READY) { + /* Login callbacks will be called once the connection succeeds + or fails. */ + return; + } + + if (array_count(&conn->login_callbacks) > 1) { + /* Another login callback is already pending */ + i_assert(conn->to_connect != NULL); + return; + } + + /* Schedule immediate login callback */ + i_assert(conn->to_connect == NULL); + conn->to_connect = timeout_add( + 0, smtp_client_connection_already_connected, conn); +} + +void smtp_client_connection_connect( + struct smtp_client_connection *conn, + smtp_client_command_callback_t login_callback, void *login_context) +{ + struct smtp_client_login_callback *login_cb; + + if (conn->closed) + return; + + if (login_callback != NULL) { + if (!array_is_created(&conn->login_callbacks)) + i_array_init(&conn->login_callbacks, 4); + + login_cb = array_append_space(&conn->login_callbacks); + login_cb->callback = login_callback; + login_cb->context = login_context; + } + + if (conn->state != SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED) { + /* Already connecting or connected */ + smtp_client_connection_connect_more(conn); + return; + } + if (conn->failing) + return; + + e_debug(conn->event, "Disconnected"); + + conn->xclient_replies_expected = 0; + conn->authenticated = FALSE; + conn->xclient_sent = FALSE; + conn->connect_failed = FALSE; + conn->connect_succeeded = FALSE; + conn->handshake_failed = FALSE; + conn->sent_quit = FALSE; + conn->reset_needed = FALSE; + + smtp_client_connection_set_state( + conn, SMTP_CLIENT_CONNECTION_STATE_CONNECTING); + + if (conn->path == NULL) { + smtp_client_connection_lookup_ip(conn); + if (conn->ips_count == 0) + return; + + /* always work asynchronously */ + timeout_remove(&conn->to_connect); + conn->to_connect = timeout_add( + 0, smtp_client_connection_connect_next_ip, conn); + } else { + /* always work asynchronously */ + timeout_remove(&conn->to_connect); + conn->to_connect = timeout_add( + 0, smtp_client_connection_connect_unix, conn); + } +} + +static const struct connection_settings smtp_client_connection_set = { + .input_max_size = SIZE_MAX, + .output_max_size = SIZE_MAX, + .client = TRUE, + .delayed_unix_client_connected_callback = TRUE, + .log_connection_id = TRUE, +}; + +static const struct connection_vfuncs smtp_client_connection_vfuncs = { + .destroy = smtp_client_connection_destroy, + .input = smtp_client_connection_input, + .client_connected = smtp_client_connection_connected +}; + +struct connection_list *smtp_client_connection_list_init(void) +{ + return connection_list_init(&smtp_client_connection_set, + &smtp_client_connection_vfuncs); +} + +void smtp_client_connection_disconnect(struct smtp_client_connection *conn) +{ + if (conn->state == SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED) + return; + + e_debug(conn->event, "Disconnected"); + + smtp_client_connection_clear_password(conn); + + if (conn->conn.output != NULL && !conn->sent_quit && + !conn->sending_command) { + /* Close the connection gracefully if possible */ + o_stream_nsend_str(conn->conn.output, "QUIT\r\n"); + o_stream_uncork(conn->conn.output); + } + + if (conn->dns_lookup != NULL) + dns_lookup_abort(&conn->dns_lookup); + io_remove(&conn->io_cmd_payload); + timeout_remove(&conn->to_connect); + timeout_remove(&conn->to_trans); + timeout_remove(&conn->to_commands); + timeout_remove(&conn->to_cmd_fail); + + ssl_iostream_destroy(&conn->ssl_iostream); + if (conn->ssl_ctx != NULL) + ssl_iostream_context_unref(&conn->ssl_ctx); + smtp_client_connection_auth_deinit(conn); + + o_stream_destroy(&conn->dot_output); + + connection_disconnect(&conn->conn); + + smtp_client_connection_set_state( + conn, SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED); + + if (!conn->failing) { + smtp_client_connection_login_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_ABORTED, + "Disconnected from server"); + smtp_client_connection_transactions_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_ABORTED, + "Disconnected from server"); + smtp_client_connection_commands_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_ABORTED, + "Disconnected from server"); + } + smtp_client_command_unref(&conn->cmd_streaming); +} + +static struct smtp_client_connection * +smtp_client_connection_do_create(struct smtp_client *client, const char *name, + enum smtp_protocol protocol, + const struct smtp_client_settings *set) +{ + struct smtp_client_connection *conn; + struct event *conn_event; + pool_t pool; + + pool = pool_alloconly_create("smtp client connection", 2048); + conn = p_new(pool, struct smtp_client_connection, 1); + conn->refcount = 1; + conn->pool = pool; + + conn->client = client; + conn->protocol = protocol; + + conn->set = client->set; + if (set != NULL) { + if (set->my_ip.family != 0) + conn->set.my_ip = set->my_ip; + if (set->my_hostname != NULL && *set->my_hostname != '\0') { + conn->set.my_hostname = + p_strdup(pool, set->my_hostname); + } + + conn->set.forced_capabilities |= set->forced_capabilities; + if (set->extra_capabilities != NULL) { + conn->set.extra_capabilities = + p_strarray_dup(pool, set->extra_capabilities); + } + + if (set->rawlog_dir != NULL && *set->rawlog_dir != '\0') { + conn->set.rawlog_dir = + p_strdup_empty(pool, set->rawlog_dir); + } + + if (set->ssl != NULL) { + conn->set.ssl = + ssl_iostream_settings_dup(pool, set->ssl); + } + + if (set->master_user != NULL && *set->master_user != '\0') { + conn->set.master_user = + p_strdup_empty(pool, set->master_user); + } + if (set->username != NULL && *set->username != '\0') { + conn->set.username = + p_strdup_empty(pool, set->username); + } + if (set->password != NULL && *set->password != '\0') { + conn->password = p_strdup(pool, set->password); + conn->set.password = conn->password; + } + if (set->sasl_mech != NULL) + conn->set.sasl_mech = set->sasl_mech; + else if (set->sasl_mechanisms != NULL && + *set->sasl_mechanisms != '\0') { + conn->set.sasl_mechanisms = + p_strdup(pool, set->sasl_mechanisms); + } + conn->set.remember_password = set->remember_password; + + if (set->command_timeout_msecs > 0) { + conn->set.command_timeout_msecs = + set->command_timeout_msecs; + } + if (set->connect_timeout_msecs > 0) { + conn->set.connect_timeout_msecs = + set->connect_timeout_msecs; + } + if (set->max_reply_size > 0) + conn->set.max_reply_size = set->max_reply_size; + if (set->max_data_chunk_size > 0) { + conn->set.max_data_chunk_size = + set->max_data_chunk_size; + } + if (set->max_data_chunk_pipeline > 0) { + conn->set.max_data_chunk_pipeline = + set->max_data_chunk_pipeline; + } + + if (set->socket_send_buffer_size > 0) { + conn->set.socket_send_buffer_size = + set->socket_send_buffer_size; + } + if (set->socket_recv_buffer_size > 0) { + conn->set.socket_recv_buffer_size = + set->socket_recv_buffer_size; + } + conn->set.debug = conn->set.debug || set->debug; + + smtp_proxy_data_merge(conn->pool, &conn->set.proxy_data, + &set->proxy_data); + conn->set.xclient_defer = set->xclient_defer; + conn->set.peer_trusted = set->peer_trusted; + + conn->set.mail_send_broken_path = set->mail_send_broken_path; + + conn->set.verbose_user_errors = + conn->set.verbose_user_errors || + set->verbose_user_errors; + } + + if (set != NULL && set->extra_capabilities != NULL) { + const char *const *extp; + + p_array_init(&conn->extra_capabilities, pool, + str_array_length(set->extra_capabilities) + 8); + for (extp = set->extra_capabilities; *extp != NULL; extp++) { + struct smtp_client_capability_extra cap = { + .name = p_strdup(pool, *extp), + }; + + array_push_back(&conn->extra_capabilities, &cap); + } + } + + i_assert(conn->set.my_hostname != NULL && + *conn->set.my_hostname != '\0'); + + conn->caps.standard = conn->set.forced_capabilities; + conn->cap_pool = pool_alloconly_create( + "smtp client connection capabilities", 128); + conn->state_pool = pool_alloconly_create( + "smtp client connection state", 256); + + if (set != NULL && set->event_parent != NULL) + conn_event = event_create(set->event_parent); + else + conn_event = event_create(client->event); + event_set_append_log_prefix( + conn_event, + t_strdup_printf("%s-client: ", + smtp_protocol_name(conn->protocol))); + event_add_str(conn_event, "protocol", + smtp_protocol_name(conn->protocol)); + event_set_forced_debug(conn_event, (set != NULL && set->debug)); + + conn->conn.event_parent = conn_event; + connection_init(conn->client->conn_list, &conn->conn, name); + conn->event = conn->conn.event; + event_unref(&conn_event); + + return conn; +} + +struct smtp_client_connection * +smtp_client_connection_create(struct smtp_client *client, + enum smtp_protocol protocol, + const char *host, in_port_t port, + enum smtp_client_connection_ssl_mode ssl_mode, + const struct smtp_client_settings *set) +{ + struct smtp_client_connection *conn; + const char *name = t_strdup_printf("%s:%u", host, port); + + conn = smtp_client_connection_do_create(client, name, protocol, set); + conn->host = p_strdup(conn->pool, host); + conn->port = port; + conn->ssl_mode = ssl_mode; + + event_add_str(conn->event, "host", host); + + e_debug(conn->event, "Connection created"); + + return conn; +} + +struct smtp_client_connection * +smtp_client_connection_create_ip(struct smtp_client *client, + enum smtp_protocol protocol, + const struct ip_addr *ip, in_port_t port, + const char *hostname, + enum smtp_client_connection_ssl_mode ssl_mode, + const struct smtp_client_settings *set) +{ + struct smtp_client_connection *conn; + bool host_is_ip = FALSE; + + if (hostname == NULL) { + hostname = net_ip2addr(ip); + host_is_ip = TRUE; + } + + conn = smtp_client_connection_create(client, protocol, hostname, port, + ssl_mode, set); + conn->ips_count = 1; + conn->ips = i_new(struct ip_addr, conn->ips_count); + conn->ips[0] = *ip; + conn->host_is_ip = host_is_ip; + return conn; +} + +struct smtp_client_connection * +smtp_client_connection_create_unix(struct smtp_client *client, + enum smtp_protocol protocol, + const char *path, + const struct smtp_client_settings *set) +{ + struct smtp_client_connection *conn; + const char *name = t_strconcat("unix:", path, NULL); + + conn = smtp_client_connection_do_create(client, name, protocol, set); + conn->path = p_strdup(conn->pool, path); + + e_debug(conn->event, "Connection created"); + + return conn; +} + +void smtp_client_connection_ref(struct smtp_client_connection *conn) +{ + i_assert(conn->refcount >= 0); + conn->refcount++; +} + +void smtp_client_connection_unref(struct smtp_client_connection **_conn) +{ + struct smtp_client_connection *conn = *_conn; + + *_conn = NULL; + + i_assert(conn->refcount > 0); + if (--conn->refcount > 0) + return; + if (conn->destroying) + return; + + conn->destroying = TRUE; + + smtp_client_connection_clear_password(conn); + smtp_client_connection_disconnect(conn); + + /* could have been created while already disconnected */ + timeout_remove(&conn->to_commands); + timeout_remove(&conn->to_cmd_fail); + + e_debug(conn->event, "Destroy"); + + if (conn->reply_parser != NULL) + smtp_reply_parser_deinit(&conn->reply_parser); + + smtp_client_connection_login_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_ABORTED, + "Connection destroy"); + smtp_client_connection_transactions_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_ABORTED, + "Connection destroy"); + smtp_client_connection_commands_fail( + conn, SMTP_CLIENT_COMMAND_ERROR_ABORTED, + "Connection destroy"); + smtp_client_connection_transactions_drop(conn); + + connection_deinit(&conn->conn); + + i_free(conn->ips); + array_free(&conn->login_callbacks); + pool_unref(&conn->cap_pool); + pool_unref(&conn->state_pool); + pool_unref(&conn->pool); +} + +void smtp_client_connection_close(struct smtp_client_connection **_conn) +{ + struct smtp_client_connection *conn = *_conn; + + *_conn = NULL; + + if (conn->closed) + return; + conn->closed = TRUE; + + smtp_client_connection_transactions_abort(conn); + smtp_client_connection_commands_abort(conn); + smtp_client_connection_disconnect(conn); + + /* could have been created while already disconnected */ + timeout_remove(&conn->to_commands); + timeout_remove(&conn->to_cmd_fail); + + smtp_client_connection_unref(&conn); +} + +void smtp_client_connection_update_proxy_data( + struct smtp_client_connection *conn, + const struct smtp_proxy_data *proxy_data) +{ + if (conn->xclient_sent) + return; + + smtp_proxy_data_merge(conn->pool, &conn->set.proxy_data, proxy_data); +} + +void smtp_client_connection_switch_ioloop(struct smtp_client_connection *conn) +{ + struct smtp_client_transaction *trans; + + if (conn->io_cmd_payload != NULL) + conn->io_cmd_payload = io_loop_move_io(&conn->io_cmd_payload); + if (conn->to_connect != NULL) + conn->to_connect = io_loop_move_timeout(&conn->to_connect); + if (conn->to_trans != NULL) + conn->to_trans = io_loop_move_timeout(&conn->to_trans); + if (conn->to_commands != NULL) + conn->to_commands = io_loop_move_timeout(&conn->to_commands); + if (conn->to_cmd_fail != NULL) + conn->to_cmd_fail = io_loop_move_timeout(&conn->to_cmd_fail); + connection_switch_ioloop(&conn->conn); + + trans = conn->transactions_head; + while (trans != NULL) { + smtp_client_transaction_switch_ioloop(trans); + trans = trans->next; + } +} + +static void +smtp_client_connection_rset_dummy_cb( + const struct smtp_reply *reply ATTR_UNUSED, + struct smtp_client_connection *conn ATTR_UNUSED) +{ + /* nothing */ +} + +static void +smtp_client_connection_reset(struct smtp_client_connection *conn) +{ + e_debug(conn->event, "Submitting RSET command"); + + conn->reset_needed = FALSE; + + (void)smtp_client_command_rset_submit( + conn, SMTP_CLIENT_COMMAND_FLAG_PRIORITY, + smtp_client_connection_rset_dummy_cb, conn); +} + +static void +smtp_client_connection_do_start_transaction(struct smtp_client_connection *conn) +{ + struct smtp_reply reply; + + timeout_remove(&conn->to_trans); + + if (conn->state != SMTP_CLIENT_CONNECTION_STATE_TRANSACTION) + return; + if (conn->transactions_head == NULL) { + smtp_client_connection_set_state( + conn, SMTP_CLIENT_CONNECTION_STATE_READY); + return; + } + + if (conn->reset_needed) + smtp_client_connection_reset(conn); + + e_debug(conn->event, "Start next transaction"); + + smtp_reply_init(&reply, 200, "Connection ready"); + smtp_client_transaction_connection_result( + conn->transactions_head, &reply); +} + +static void +smtp_client_connection_start_transaction(struct smtp_client_connection *conn) +{ + if (conn->state != SMTP_CLIENT_CONNECTION_STATE_READY) + return; + if (conn->transactions_head == NULL) + return; + if (conn->to_trans != NULL) + return; + + smtp_client_connection_set_state( + conn, SMTP_CLIENT_CONNECTION_STATE_TRANSACTION); + conn->to_trans = timeout_add_short( + 0, smtp_client_connection_do_start_transaction, conn); +} + +void smtp_client_connection_add_transaction( + struct smtp_client_connection *conn, + struct smtp_client_transaction *trans) +{ + e_debug(conn->event, "Add transaction"); + + DLLIST2_APPEND(&conn->transactions_head, &conn->transactions_tail, + trans); + + smtp_client_connection_connect(conn, NULL, NULL); + smtp_client_connection_start_transaction(conn); +} + +void smtp_client_connection_abort_transaction( + struct smtp_client_connection *conn, + struct smtp_client_transaction *trans) +{ + bool was_first = (trans == conn->transactions_head); + + e_debug(conn->event, "Abort transaction"); + + DLLIST2_REMOVE(&conn->transactions_head, &conn->transactions_tail, + trans); + + if (!was_first) + return; + i_assert(conn->state != SMTP_CLIENT_CONNECTION_STATE_READY); + if (conn->state != SMTP_CLIENT_CONNECTION_STATE_TRANSACTION) + return; + + /* transaction messed up; protocol state needs to be reset for + next transaction */ + conn->reset_needed = TRUE; + + smtp_client_connection_set_state( + conn, SMTP_CLIENT_CONNECTION_STATE_READY); + smtp_client_connection_start_transaction(conn); +} + +void smtp_client_connection_next_transaction( + struct smtp_client_connection *conn, + struct smtp_client_transaction *trans) +{ + e_debug(conn->event, "Initiate next transaction"); + + i_assert(trans == conn->transactions_head); + + DLLIST2_REMOVE(&conn->transactions_head, &conn->transactions_tail, + trans); + + i_assert(conn->state != SMTP_CLIENT_CONNECTION_STATE_READY); + if (conn->state != SMTP_CLIENT_CONNECTION_STATE_TRANSACTION) + return; + + smtp_client_connection_set_state( + conn, SMTP_CLIENT_CONNECTION_STATE_READY); + smtp_client_connection_start_transaction(conn); +} diff --git a/src/lib-smtp/smtp-client-connection.h b/src/lib-smtp/smtp-client-connection.h new file mode 100644 index 0000000..b406e2d --- /dev/null +++ b/src/lib-smtp/smtp-client-connection.h @@ -0,0 +1,93 @@ +#ifndef SMTP_CLIENT_CONNECTION_H +#define SMTP_CLIENT_CONNECTION_H + +#include "net.h" +#include "smtp-common.h" + +#include "smtp-client-command.h" + +enum smtp_capability; + +struct smtp_reply; +struct smtp_client; +struct smtp_client_capability_extra; +struct smtp_client_settings; +struct smtp_client_command; + +enum smtp_client_connection_ssl_mode { + SMTP_CLIENT_SSL_MODE_NONE = 0, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, + SMTP_CLIENT_SSL_MODE_STARTTLS +}; + +enum smtp_client_connection_state { + /* No connection */ + SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED = 0, + /* Trying to connect */ + SMTP_CLIENT_CONNECTION_STATE_CONNECTING, + /* Connected, performing handshake */ + SMTP_CLIENT_CONNECTION_STATE_HANDSHAKING, + /* Handshake ready, trying to authenticate */ + SMTP_CLIENT_CONNECTION_STATE_AUTHENTICATING, + /* Authenticated, ready to accept commands */ + SMTP_CLIENT_CONNECTION_STATE_READY, + /* Involved in active transaction */ + SMTP_CLIENT_CONNECTION_STATE_TRANSACTION +}; +extern const char *const smtp_client_connection_state_names[]; + +struct smtp_client_connection * +smtp_client_connection_create(struct smtp_client *client, + enum smtp_protocol protocol, + const char *host, in_port_t port, + enum smtp_client_connection_ssl_mode ssl_mode, + const struct smtp_client_settings *set) + ATTR_NULL(6); +struct smtp_client_connection * +smtp_client_connection_create_ip(struct smtp_client *client, + enum smtp_protocol protocol, + const struct ip_addr *ip, in_port_t port, + const char *hostname, + enum smtp_client_connection_ssl_mode ssl_mode, + const struct smtp_client_settings *set) + ATTR_NULL(5, 7); +struct smtp_client_connection * +smtp_client_connection_create_unix(struct smtp_client *client, + enum smtp_protocol protocol, + const char *path, + const struct smtp_client_settings *set) + ATTR_NULL(4); + +void smtp_client_connection_ref(struct smtp_client_connection *conn); +void smtp_client_connection_unref(struct smtp_client_connection **_conn); +void smtp_client_connection_close(struct smtp_client_connection **_conn); + +void smtp_client_connection_update_proxy_data( + struct smtp_client_connection *conn, + const struct smtp_proxy_data *proxy_data); + +void smtp_client_connection_cork(struct smtp_client_connection *conn); +void smtp_client_connection_uncork(struct smtp_client_connection *conn); + +void smtp_client_connection_connect( + struct smtp_client_connection *conn, + smtp_client_command_callback_t login_callback, void *login_context); +void smtp_client_connection_disconnect(struct smtp_client_connection *conn); + +void smtp_client_connection_switch_ioloop(struct smtp_client_connection *conn); + +enum smtp_capability +smtp_client_connection_get_capabilities(struct smtp_client_connection *conn); +uoff_t smtp_client_connection_get_size_capability( + struct smtp_client_connection *conn); +void smtp_client_connection_accept_extra_capability( + struct smtp_client_connection *conn, + const struct smtp_client_capability_extra *cap); +const struct smtp_capability_extra * +smtp_client_connection_get_extra_capability(struct smtp_client_connection *conn, + const char *name); + +enum smtp_client_connection_state +smtp_client_connection_get_state(struct smtp_client_connection *conn); + +#endif diff --git a/src/lib-smtp/smtp-client-private.h b/src/lib-smtp/smtp-client-private.h new file mode 100644 index 0000000..40f50f9 --- /dev/null +++ b/src/lib-smtp/smtp-client-private.h @@ -0,0 +1,340 @@ +#ifndef SMTP_CLIENT_PRIVATE_H +#define SMTP_CLIENT_PRIVATE_H + +#include "connection.h" + +#include "smtp-common.h" +#include "smtp-params.h" +#include "smtp-client.h" +#include "smtp-client-command.h" +#include "smtp-client-transaction.h" +#include "smtp-client-connection.h" + +#define SMTP_CLIENT_DATA_CHUNK_SIZE IO_BLOCK_SIZE + +struct smtp_client_command { + pool_t pool; + int refcount; + struct event *event; + + struct smtp_client_command *prev, *next; + + buffer_t *data; + unsigned int send_pos; + const char *name; + + enum smtp_client_command_flags flags; + + struct smtp_client_connection *conn; + enum smtp_client_command_state state; + unsigned int replies_expected; + unsigned int replies_seen; + + struct istream *stream; + uoff_t stream_size; + + struct smtp_reply *delayed_failure; + + smtp_client_command_callback_t *callback; + void *context; + + void (*abort_callback)(void *context); + void *abort_context; + + void (*sent_callback)(void *context); + void *sent_context; + + bool has_stream:1; + bool stream_dot:1; + bool stream_finished:1; + bool ehlo:1; + bool locked:1; + bool plug:1; + bool failed:1; + bool aborting:1; + bool delay_failure:1; + bool delaying_failure:1; + bool event_finished:1; +}; + +struct smtp_client_transaction_mail { + pool_t pool; + struct smtp_client_transaction *trans; + + struct smtp_client_transaction_mail *prev, *next; + + struct smtp_address *mail_from; + struct smtp_params_mail mail_params; + + smtp_client_command_callback_t *mail_callback; + void *context; + + struct smtp_client_command *cmd_mail_from; +}; + +struct smtp_client_transaction_rcpt { + pool_t pool; + struct smtp_client_transaction *trans; + struct event *event; + + struct smtp_client_transaction_rcpt *prev, *next; + + struct smtp_address *rcpt_to; + struct smtp_params_rcpt rcpt_params; + + smtp_client_command_callback_t *rcpt_callback; + void *context; + + smtp_client_command_callback_t *data_callback; + void *data_context; + + struct smtp_client_command *cmd_rcpt_to; + + bool external_pool:1; + bool queued:1; + bool finished:1; +}; + +struct smtp_client_transaction { + pool_t pool; + int refcount; + struct event *event; + + struct smtp_client_transaction *prev, *next; + + struct smtp_client_connection *conn; + enum smtp_client_transaction_flags flags; + + enum smtp_client_transaction_state state; + struct smtp_client_command *cmd_data, *cmd_rset; + struct smtp_client_command *cmd_plug, *cmd_last; + struct smtp_reply *failure, *mail_failure, *data_failure; + + struct smtp_client_transaction_mail *mail_head, *mail_tail; + struct smtp_client_transaction_mail *mail_send; + + struct smtp_client_transaction_rcpt *rcpts_queue_head, *rcpts_queue_tail; + struct smtp_client_transaction_rcpt *rcpts_send; + struct smtp_client_transaction_rcpt *rcpts_head, *rcpts_tail; + struct smtp_client_transaction_rcpt *rcpts_data; + unsigned int rcpts_queue_count; + unsigned int rcpts_count; + + unsigned int rcpts_total; + unsigned int rcpts_aborted; + unsigned int rcpts_denied; + unsigned int rcpts_failed; + unsigned int rcpts_succeeded; + + struct istream *data_input; + smtp_client_command_callback_t *data_callback; + void *data_context; + + smtp_client_command_callback_t *reset_callback; + void *reset_context; + + smtp_client_transaction_callback_t *callback; + void *context; + + struct smtp_client_transaction_times times; + + unsigned int finish_timeout_msecs; + struct timeout *to_finish, *to_send; + + bool immediate:1; + bool sender_accepted:1; + bool data_provided:1; + bool reset:1; + bool finished:1; + bool submitting:1; + bool failing:1; + bool submitted_data:1; +}; + +struct smtp_client_login_callback { + smtp_client_command_callback_t *callback; + void *context; +}; + +struct smtp_client_connection { + struct connection conn; + pool_t pool; + int refcount; + struct event *event; + + struct smtp_client *client; + + enum smtp_protocol protocol; + const char *path, *host; + in_port_t port; + enum smtp_client_connection_ssl_mode ssl_mode; + + int connect_errno; + + struct smtp_client_settings set; + char *password; + ARRAY(struct smtp_client_capability_extra) extra_capabilities; + + pool_t cap_pool; + struct { + enum smtp_capability standard; + ARRAY(struct smtp_capability_extra) extra; + const char **auth_mechanisms; + const char **xclient_args; + uoff_t size; + + /* Lists of custom MAIL/RCPT parameters supported by peer. These + arrays always end in NULL pointer once created. */ + ARRAY_TYPE(const_string) mail_param_extensions; + ARRAY_TYPE(const_string) rcpt_param_extensions; + } caps; + + struct smtp_reply_parser *reply_parser; + struct smtp_reply reply; + unsigned int xclient_replies_expected; + + struct dns_lookup *dns_lookup; + + struct dsasl_client *sasl_client; + char *sasl_ir; + struct smtp_client_command *cmd_auth; + + struct timeout *to_connect, *to_trans, *to_commands, *to_cmd_fail; + struct io *io_cmd_payload; + + struct istream *raw_input; + struct ostream *raw_output, *dot_output; + + struct ssl_iostream_context *ssl_ctx; + struct ssl_iostream *ssl_iostream; + + enum smtp_client_connection_state state; + pool_t state_pool; + struct { + struct smtp_reply *login_reply; + } state_data; + + ARRAY(struct smtp_client_login_callback) login_callbacks; + + /* commands pending in queue to be sent */ + struct smtp_client_command *cmd_send_queue_head, *cmd_send_queue_tail; + unsigned int cmd_send_queue_count; + /* commands that have been (mostly) sent, waiting for response */ + struct smtp_client_command *cmd_wait_list_head, *cmd_wait_list_tail; + unsigned int cmd_wait_list_count; + /* commands that have failed before submission */ + struct smtp_client_command *cmd_fail_list; + /* command sending data stream */ + struct smtp_client_command *cmd_streaming; + + /* active transactions */ + struct smtp_client_transaction *transactions_head, *transactions_tail; + + unsigned int ips_count, prev_connect_idx; + struct ip_addr *ips; + + bool host_is_ip:1; + bool old_smtp:1; + bool authenticated:1; + bool xclient_sent:1; + bool connect_failed:1; + bool connect_succeeded:1; + bool handshake_failed:1; + bool corked:1; + bool sent_quit:1; + bool sending_command:1; + bool reset_needed:1; + bool failing:1; + bool destroying:1; + bool closed:1; +}; + +struct smtp_client { + pool_t pool; + + struct smtp_client_settings set; + + struct event *event; + struct ioloop *ioloop; + struct ssl_iostream_context *ssl_ctx; + + struct connection_list *conn_list; +}; + +/* + * Command + */ + +void smtp_client_command_free(struct smtp_client_command *cmd); +int smtp_client_command_send_more(struct smtp_client_connection *conn); +int smtp_client_command_input_reply(struct smtp_client_command *cmd, + const struct smtp_reply *reply); + +void smtp_client_command_drop_callback(struct smtp_client_command *cmd); + +void smtp_client_command_fail(struct smtp_client_command **_cmd, + unsigned int status, const char *error); +void smtp_client_command_fail_reply(struct smtp_client_command **_cmd, + const struct smtp_reply *reply); + +void smtp_client_commands_list_abort(struct smtp_client_command *cmds_list, + unsigned int cmds_list_count); +void smtp_client_commands_list_fail_reply( + struct smtp_client_command *cmds_list, unsigned int cmds_list_count, + const struct smtp_reply *reply); + +void smtp_client_commands_abort_delayed(struct smtp_client_connection *conn); +void smtp_client_commands_fail_delayed(struct smtp_client_connection *conn); + +/* + * Transaction + */ + +void smtp_client_transaction_connection_result( + struct smtp_client_transaction *trans, + const struct smtp_reply *reply); +void smtp_client_transaction_connection_destroyed( + struct smtp_client_transaction *trans); + +void smtp_client_transaction_switch_ioloop( + struct smtp_client_transaction *trans); + +/* + * Connection + */ + +struct connection_list *smtp_client_connection_list_init(void); + +void smtp_client_connection_send_xclient(struct smtp_client_connection *conn); + +void smtp_client_connection_fail(struct smtp_client_connection *conn, + unsigned int status, const char *error, + const char *user_error) ATTR_NULL(3); + +void smtp_client_connection_handle_output_error( + struct smtp_client_connection *conn); +void smtp_client_connection_trigger_output( + struct smtp_client_connection *conn); + +void smtp_client_connection_start_cmd_timeout( + struct smtp_client_connection *conn); +void smtp_client_connection_update_cmd_timeout( + struct smtp_client_connection *conn); + +void smtp_client_connection_add_transaction( + struct smtp_client_connection *conn, + struct smtp_client_transaction *trans); +void smtp_client_connection_abort_transaction( + struct smtp_client_connection *conn, + struct smtp_client_transaction *trans); +void smtp_client_connection_next_transaction( + struct smtp_client_connection *conn, + struct smtp_client_transaction *trans); + +/* + * Client + */ + +int smtp_client_init_ssl_ctx(struct smtp_client *client, const char **error_r); + +#endif diff --git a/src/lib-smtp/smtp-client-transaction.c b/src/lib-smtp/smtp-client-transaction.c new file mode 100644 index 0000000..0112021 --- /dev/null +++ b/src/lib-smtp/smtp-client-transaction.c @@ -0,0 +1,1656 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "ioloop.h" +#include "net.h" +#include "istream.h" +#include "istream-crlf.h" +#include "ostream.h" +#include "str.h" +#include "str-sanitize.h" +#include "dns-lookup.h" + +#include "smtp-common.h" +#include "smtp-address.h" +#include "smtp-params.h" +#include "smtp-client-private.h" +#include "smtp-client-command.h" +#include "smtp-client-transaction.h" + +#include + +const char *const smtp_client_transaction_state_names[] = { + "new", + "pending", + "mail_from", + "rcpt_to", + "data", + "reset", + "finished", + "aborted" +}; + +static void +smtp_client_transaction_submit_more(struct smtp_client_transaction *trans); +static void +smtp_client_transaction_submit(struct smtp_client_transaction *trans, + bool start); + +static void +smtp_client_transaction_try_complete(struct smtp_client_transaction *trans); + +static void +smtp_client_transaction_send_data(struct smtp_client_transaction *trans); +static void +smtp_client_transaction_send_reset(struct smtp_client_transaction *trans); + +/* + * Sender + */ + +static struct smtp_client_transaction_mail * +smtp_client_transaction_mail_new(struct smtp_client_transaction *trans, + const struct smtp_address *mail_from, + const struct smtp_params_mail *mail_params) +{ + struct smtp_client_transaction_mail *mail; + pool_t pool; + + pool = pool_alloconly_create("smtp transaction mail", 512); + mail = p_new(pool, struct smtp_client_transaction_mail, 1); + mail->pool = pool; + mail->trans = trans; + mail->mail_from = smtp_address_clone(pool, mail_from); + smtp_params_mail_copy(pool, &mail->mail_params, mail_params); + + DLLIST2_APPEND(&trans->mail_head, &trans->mail_tail, mail); + if (trans->mail_send == NULL) + trans->mail_send = mail; + + return mail; +} + +static void +smtp_client_transaction_mail_free(struct smtp_client_transaction_mail **_mail) +{ + struct smtp_client_transaction_mail *mail = *_mail; + + if (mail == NULL) + return; + *_mail = NULL; + + struct smtp_client_transaction *trans = mail->trans; + + if (mail->cmd_mail_from != NULL) + smtp_client_command_abort(&mail->cmd_mail_from); + DLLIST2_REMOVE(&trans->mail_head, &trans->mail_tail, mail); + pool_unref(&mail->pool); +} + +static void +smtp_client_transaction_mail_replied( + struct smtp_client_transaction_mail **_mail, + const struct smtp_reply *reply) +{ + struct smtp_client_transaction_mail *mail = *_mail; + + if (mail == NULL) + return; + *_mail = NULL; + + smtp_client_command_callback_t *mail_callback = mail->mail_callback; + void *context = mail->context; + + mail->mail_callback = NULL; + smtp_client_transaction_mail_free(&mail); + + /* Call the callback */ + if (mail_callback != NULL) + mail_callback(reply, context); +} + +void smtp_client_transaction_mail_abort( + struct smtp_client_transaction_mail **_mail) +{ + struct smtp_client_transaction_mail *mail = *_mail; + + if (mail == NULL) + return; + *_mail = NULL; + + struct smtp_client_transaction *trans = mail->trans; + + i_assert(trans->state <= SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM || + trans->state == SMTP_CLIENT_TRANSACTION_STATE_ABORTED); + + smtp_client_transaction_mail_free(&mail); +} + +static void +smtp_client_transaction_mail_fail_reply( + struct smtp_client_transaction_mail **_mail, + const struct smtp_reply *reply) +{ + struct smtp_client_transaction_mail *mail = *_mail; + + if (mail == NULL) + return; + *_mail = NULL; + + smtp_client_command_callback_t *callback = mail->mail_callback; + void *context = mail->context; + + mail->mail_callback = NULL; + smtp_client_transaction_mail_free(&mail); + + if (callback != NULL) + callback(reply, context); +} + +/* + * Recipient + */ + +static void +smtp_client_transaction_rcpt_update_event( + struct smtp_client_transaction_rcpt *rcpt) +{ + const char *to = smtp_address_encode(rcpt->rcpt_to); + + event_set_append_log_prefix(rcpt->event, + t_strdup_printf("rcpt <%s>: ", + str_sanitize(to, 128))); + event_add_str(rcpt->event, "rcpt_to", to); + smtp_params_rcpt_add_to_event(&rcpt->rcpt_params, rcpt->event); +} + +static struct smtp_client_transaction_rcpt * +smtp_client_transaction_rcpt_new(struct smtp_client_transaction *trans, + pool_t pool, + const struct smtp_address *rcpt_to, + const struct smtp_params_rcpt *rcpt_params) +{ + struct smtp_client_transaction_rcpt *rcpt; + + pool_ref(pool); + + rcpt = p_new(pool, struct smtp_client_transaction_rcpt, 1); + rcpt->pool = pool; + rcpt->trans = trans; + rcpt->rcpt_to = smtp_address_clone(pool, rcpt_to); + smtp_params_rcpt_copy(pool, &rcpt->rcpt_params, rcpt_params); + + DLLIST2_APPEND(&trans->rcpts_queue_head, &trans->rcpts_queue_tail, + rcpt); + trans->rcpts_queue_count++; + rcpt->queued = TRUE; + if (trans->rcpts_send == NULL) + trans->rcpts_send = rcpt; + + rcpt->event = event_create(trans->event); + smtp_client_transaction_rcpt_update_event(rcpt); + + trans->rcpts_total++; + + return rcpt; +} + +static void +smtp_client_transaction_rcpt_free( + struct smtp_client_transaction_rcpt **_rcpt) +{ + struct smtp_client_transaction_rcpt *rcpt = *_rcpt; + + if (rcpt == NULL) + return; + *_rcpt = NULL; + + struct smtp_client_transaction *trans = rcpt->trans; + + smtp_client_command_abort(&rcpt->cmd_rcpt_to); + if (trans->rcpts_send == rcpt) + trans->rcpts_send = rcpt->next; + if (trans->rcpts_data == rcpt) + trans->rcpts_data = rcpt->next; + if (rcpt->queued) { + DLLIST2_REMOVE(&trans->rcpts_queue_head, + &trans->rcpts_queue_tail, rcpt); + trans->rcpts_queue_count--; + } else { + DLLIST2_REMOVE(&trans->rcpts_head, + &trans->rcpts_tail, rcpt); + trans->rcpts_count--; + } + + if (!rcpt->finished) { + struct smtp_reply failure; + + trans->rcpts_aborted++; + + smtp_reply_init(&failure, + SMTP_CLIENT_COMMAND_ERROR_ABORTED, "Aborted"); + failure.enhanced_code = SMTP_REPLY_ENH_CODE(9, 0, 0); + + struct event_passthrough *e = + event_create_passthrough(rcpt->event)-> + set_name("smtp_client_transaction_rcpt_finished"); + smtp_reply_add_to_event(&failure, e); + e_debug(e->event(), "Aborted"); + } + + event_unref(&rcpt->event); + + if (rcpt->queued || rcpt->external_pool) { + i_assert(rcpt->pool != NULL); + pool_unref(&rcpt->pool); + } +} + +static void +smtp_client_transaction_rcpt_approved( + struct smtp_client_transaction_rcpt **_rcpt) +{ + struct smtp_client_transaction_rcpt *prcpt = *_rcpt; + + i_assert(prcpt != NULL); + + struct smtp_client_transaction *trans = prcpt->trans; + struct smtp_client_transaction_rcpt *rcpt; + pool_t pool; + + i_assert(prcpt->queued); + + if (prcpt->external_pool) { + /* Allocated externally; just remove it from the queue */ + prcpt->queued = FALSE; + if (trans->rcpts_send == prcpt) + trans->rcpts_send = prcpt->next; + DLLIST2_REMOVE(&trans->rcpts_queue_head, + &trans->rcpts_queue_tail, prcpt); + trans->rcpts_queue_count--; + + rcpt = prcpt; + } else { + /* Move to transaction pool */ + pool = trans->pool; + rcpt = p_new(pool, struct smtp_client_transaction_rcpt, 1); + rcpt->trans = trans; + rcpt->rcpt_to = smtp_address_clone(pool, prcpt->rcpt_to); + smtp_params_rcpt_copy(pool, &rcpt->rcpt_params, + &prcpt->rcpt_params); + rcpt->data_callback = prcpt->data_callback; + rcpt->data_context = prcpt->data_context; + + rcpt->event = prcpt->event; + event_ref(rcpt->event); + + /* Free the old object, thereby removing it from the queue */ + smtp_client_transaction_rcpt_free(&prcpt); + } + + /* Recipient is approved */ + DLLIST2_APPEND(&trans->rcpts_head, &trans->rcpts_tail, rcpt); + trans->rcpts_count++; + if (trans->rcpts_data == NULL) + trans->rcpts_data = trans->rcpts_head; + + *_rcpt = rcpt; +} + +static void +smtp_client_transaction_rcpt_denied( + struct smtp_client_transaction_rcpt **_rcpt, + const struct smtp_reply *reply) +{ + struct smtp_client_transaction_rcpt *prcpt = *_rcpt; + + *_rcpt = NULL; + i_assert(prcpt != NULL); + + struct smtp_client_transaction *trans = prcpt->trans; + + trans->rcpts_denied++; + trans->rcpts_failed++; + + struct event_passthrough *e = + event_create_passthrough(prcpt->event)-> + set_name("smtp_client_transaction_rcpt_finished"); + smtp_reply_add_to_event(reply, e); + e_debug(e->event(), "Denied"); + + /* Not pending anymore */ + smtp_client_transaction_rcpt_free(&prcpt); +} + +static void +smtp_client_transaction_rcpt_replied( + struct smtp_client_transaction_rcpt **_rcpt, + const struct smtp_reply *reply) +{ + struct smtp_client_transaction_rcpt *rcpt = *_rcpt; + + *_rcpt = NULL; + if (rcpt == NULL) + return; + + bool success = smtp_reply_is_success(reply); + smtp_client_command_callback_t *rcpt_callback = rcpt->rcpt_callback; + void *context = rcpt->context; + + rcpt->rcpt_callback = NULL; + + if (rcpt->finished) + return; + rcpt->finished = !success; + + if (success) + smtp_client_transaction_rcpt_approved(&rcpt); + else + smtp_client_transaction_rcpt_denied(&rcpt, reply); + + /* Call the callback */ + if (rcpt_callback != NULL) + rcpt_callback(reply, context); +} + +void smtp_client_transaction_rcpt_abort( + struct smtp_client_transaction_rcpt **_rcpt) +{ + struct smtp_client_transaction_rcpt *rcpt = *_rcpt; + + if (rcpt == NULL) + return; + *_rcpt = NULL; + + struct smtp_client_transaction *trans = rcpt->trans; + + i_assert(rcpt->queued || rcpt->external_pool); + + i_assert(trans->state <= SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO || + trans->state == SMTP_CLIENT_TRANSACTION_STATE_ABORTED); + + smtp_client_transaction_rcpt_free(&rcpt); +} + +static void +smtp_client_transaction_rcpt_fail_reply( + struct smtp_client_transaction_rcpt **_rcpt, + const struct smtp_reply *reply) +{ + struct smtp_client_transaction_rcpt *rcpt = *_rcpt; + + if (rcpt == NULL) + return; + *_rcpt = NULL; + + struct smtp_client_transaction *trans = rcpt->trans; + smtp_client_command_callback_t *callback; + void *context; + + if (rcpt->finished) + return; + rcpt->finished = TRUE; + + trans->rcpts_failed++; + + if (rcpt->queued) { + callback = rcpt->rcpt_callback; + context = rcpt->context; + } else { + callback = rcpt->data_callback; + context = rcpt->data_context; + } + rcpt->rcpt_callback = NULL; + rcpt->data_callback = NULL; + + struct event_passthrough *e = + event_create_passthrough(rcpt->event)-> + set_name("smtp_client_transaction_rcpt_finished"); + smtp_reply_add_to_event(reply, e); + e_debug(e->event(), "Failed"); + + smtp_client_transaction_rcpt_free(&rcpt); + + if (callback != NULL) + callback(reply, context); +} + +static void +smtp_client_transaction_rcpt_finished(struct smtp_client_transaction_rcpt *rcpt, + const struct smtp_reply *reply) +{ + struct smtp_client_transaction *trans = rcpt->trans; + + i_assert(!rcpt->finished); + rcpt->finished = TRUE; + + if (smtp_reply_is_success(reply)) + trans->rcpts_succeeded++; + else + trans->rcpts_failed++; + + struct event_passthrough *e = + event_create_passthrough(rcpt->event)-> + set_name("smtp_client_transaction_rcpt_finished"); + smtp_reply_add_to_event(reply, e); + e_debug(e->event(), "Finished"); + + if (rcpt->data_callback != NULL) + rcpt->data_callback(reply, rcpt->data_context); + rcpt->data_callback = NULL; +} + +#undef smtp_client_transaction_rcpt_set_data_callback +void smtp_client_transaction_rcpt_set_data_callback( + struct smtp_client_transaction_rcpt *rcpt, + smtp_client_command_callback_t *callback, void *context) +{ + i_assert(!rcpt->finished); + + rcpt->data_callback = callback; + rcpt->data_context = context; +} + +/* + * Transaction + */ + +static void +smtp_client_transaction_update_event(struct smtp_client_transaction *trans) +{ + event_set_append_log_prefix(trans->event, "transaction: "); +} + +static struct event_passthrough * +smtp_client_transaction_result_event(struct smtp_client_transaction *trans, + const struct smtp_reply *reply) +{ + struct event_passthrough *e; + unsigned int rcpts_aborted = (trans->rcpts_aborted + + trans->rcpts_queue_count); + + e = event_create_passthrough(trans->event)-> + set_name("smtp_client_transaction_finished")-> + add_int("recipients", trans->rcpts_total)-> + add_int("recipients_aborted", rcpts_aborted)-> + add_int("recipients_denied", trans->rcpts_denied)-> + add_int("recipients_failed", trans->rcpts_failed)-> + add_int("recipients_succeeded", trans->rcpts_succeeded); + + smtp_reply_add_to_event(reply, e); + if (trans->reset) + e->add_str("is_reset", "yes"); + return e; +} + +#undef smtp_client_transaction_create_empty +struct smtp_client_transaction * +smtp_client_transaction_create_empty( + struct smtp_client_connection *conn, + enum smtp_client_transaction_flags flags, + smtp_client_transaction_callback_t *callback, void *context) +{ + struct smtp_client_transaction *trans; + pool_t pool; + + if (conn->protocol == SMTP_PROTOCOL_LMTP) + flags |= SMTP_CLIENT_TRANSACTION_FLAG_REPLY_PER_RCPT; + + pool = pool_alloconly_create("smtp transaction", 4096); + trans = p_new(pool, struct smtp_client_transaction, 1); + trans->refcount = 1; + trans->pool = pool; + trans->flags = flags; + trans->callback = callback; + trans->context = context; + + trans->event = event_create(conn->event); + smtp_client_transaction_update_event(trans); + + trans->conn = conn; + smtp_client_connection_ref(conn); + + e_debug(trans->event, "Created"); + + return trans; +} + +#undef smtp_client_transaction_create +struct smtp_client_transaction * +smtp_client_transaction_create(struct smtp_client_connection *conn, + const struct smtp_address *mail_from, + const struct smtp_params_mail *mail_params, + enum smtp_client_transaction_flags flags, + smtp_client_transaction_callback_t *callback, + void *context) +{ + struct smtp_client_transaction *trans; + + trans = smtp_client_transaction_create_empty(conn, flags, + callback, context); + (void)smtp_client_transaction_mail_new(trans, mail_from, mail_params); + return trans; +} + +static void +smtp_client_transaction_finish(struct smtp_client_transaction *trans, + const struct smtp_reply *final_reply) +{ + struct smtp_client_connection *conn = trans->conn; + + if (trans->state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED) + return; + + timeout_remove(&trans->to_finish); + + struct event_passthrough *e = + smtp_client_transaction_result_event(trans, final_reply); + e_debug(e->event(), "Finished"); + + io_loop_time_refresh(); + trans->times.finished = ioloop_timeval; + + i_assert(trans->to_send == NULL); + + trans->state = SMTP_CLIENT_TRANSACTION_STATE_FINISHED; + i_assert(trans->callback != NULL); + trans->callback(trans->context); + + if (!trans->submitted_data) + smtp_client_connection_abort_transaction(conn, trans); + + smtp_client_transaction_unref(&trans); +} + +void smtp_client_transaction_abort(struct smtp_client_transaction *trans) +{ + struct smtp_client_connection *conn = trans->conn; + + if (trans->failing) { + e_debug(trans->event, "Abort (already failing)"); + return; + } + + e_debug(trans->event, "Abort"); + + /* Clean up */ + i_stream_unref(&trans->data_input); + timeout_remove(&trans->to_send); + timeout_remove(&trans->to_finish); + + trans->cmd_last = NULL; + + /* Abort any pending commands */ + while (trans->mail_head != NULL) { + struct smtp_client_transaction_mail *mail = trans->mail_head; + + smtp_client_transaction_mail_free(&mail); + } + while (trans->rcpts_queue_count > 0) { + struct smtp_client_transaction_rcpt *rcpt = + trans->rcpts_queue_head; + + smtp_client_transaction_rcpt_free(&rcpt); + } + if (trans->cmd_data != NULL) + smtp_client_command_abort(&trans->cmd_data); + if (trans->cmd_rset != NULL) + smtp_client_command_abort(&trans->cmd_rset); + if (trans->cmd_plug != NULL) + smtp_client_command_abort(&trans->cmd_plug); + trans->cmd_data = NULL; + trans->cmd_rset = NULL; + trans->cmd_plug = NULL; + + smtp_client_connection_abort_transaction(conn, trans); + + /* Abort if not finished */ + if (trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED) { + struct event_passthrough *e; + + if (trans->failure != NULL) { + e = smtp_client_transaction_result_event( + trans, trans->failure); + e_debug(e->event(), "Failed"); + } else { + struct smtp_reply failure; + + smtp_reply_init(&failure, + SMTP_CLIENT_COMMAND_ERROR_ABORTED, + "Aborted"); + failure.enhanced_code = SMTP_REPLY_ENH_CODE(9, 0, 0); + + e = smtp_client_transaction_result_event( + trans, &failure); + e_debug(e->event(), "Aborted"); + } + + trans->state = SMTP_CLIENT_TRANSACTION_STATE_ABORTED; + i_assert(trans->callback != NULL); + trans->callback(trans->context); + + smtp_client_transaction_unref(&trans); + } +} + +void smtp_client_transaction_ref(struct smtp_client_transaction *trans) +{ + trans->refcount++; +} + +void smtp_client_transaction_unref(struct smtp_client_transaction **_trans) +{ + struct smtp_client_transaction *trans = *_trans; + struct smtp_client_connection *conn; + + *_trans = NULL; + + if (trans == NULL) + return; + conn = trans->conn; + + i_assert(trans->refcount > 0); + if (--trans->refcount > 0) + return; + + e_debug(trans->event, "Destroy"); + + i_stream_unref(&trans->data_input); + smtp_client_transaction_abort(trans); + + while (trans->rcpts_count > 0) { + struct smtp_client_transaction_rcpt *rcpt = + trans->rcpts_head; + smtp_client_transaction_rcpt_free(&rcpt); + } + + i_assert(trans->state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED); + event_unref(&trans->event); + pool_unref(&trans->pool); + + smtp_client_connection_unref(&conn); +} + +void smtp_client_transaction_destroy(struct smtp_client_transaction **_trans) +{ + struct smtp_client_transaction *trans = *_trans; + struct smtp_client_transaction_mail *mail; + struct smtp_client_transaction_rcpt *rcpt; + + *_trans = NULL; + + if (trans == NULL) + return; + + smtp_client_transaction_ref(trans); + smtp_client_transaction_abort(trans); + + /* Make sure this transaction doesn't produce any more callbacks. + We cannot fully abort (destroy) these commands, as this may be + called from a callback. */ + for (mail = trans->mail_head; mail != NULL; mail = mail->next) { + if (mail->cmd_mail_from != NULL) + smtp_client_command_drop_callback(mail->cmd_mail_from); + } + for (rcpt = trans->rcpts_queue_head; rcpt != NULL; rcpt = rcpt->next) { + if (rcpt->cmd_rcpt_to != NULL) + smtp_client_command_drop_callback(rcpt->cmd_rcpt_to); + } + if (trans->cmd_data != NULL) + smtp_client_command_drop_callback(trans->cmd_data); + if (trans->cmd_rset != NULL) + smtp_client_command_drop_callback(trans->cmd_rset); + if (trans->cmd_plug != NULL) + smtp_client_command_abort(&trans->cmd_plug); + + /* Free any approved recipients early */ + while (trans->rcpts_count > 0) { + struct smtp_client_transaction_rcpt *rcpt = + trans->rcpts_head; + smtp_client_transaction_rcpt_free(&rcpt); + } + + if (trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED) { + struct smtp_client_transaction *trans_tmp = trans; + + trans->state = SMTP_CLIENT_TRANSACTION_STATE_ABORTED; + smtp_client_transaction_unref(&trans_tmp); + } + + smtp_client_transaction_unref(&trans); +} + +void smtp_client_transaction_fail_reply(struct smtp_client_transaction *trans, + const struct smtp_reply *reply) +{ + struct smtp_client_transaction_rcpt *rcpt, *rcpt_next; + + if (reply == NULL) + reply = trans->failure; + i_assert(reply != NULL); + + if (trans->failing) { + e_debug(trans->event, "Already failing: %s", + smtp_reply_log(reply)); + return; + } + trans->failing = TRUE; + + e_debug(trans->event, "Returning failure: %s", smtp_reply_log(reply)); + + /* Hold a reference to prevent early destruction in a callback */ + smtp_client_transaction_ref(trans); + + trans->cmd_last = NULL; + + timeout_remove(&trans->to_send); + + /* MAIL */ + while (trans->mail_head != NULL) { + struct smtp_client_transaction_mail *mail = trans->mail_head; + + smtp_client_transaction_mail_fail_reply(&mail, reply); + } + + /* RCPT */ + rcpt = trans->rcpts_queue_head; + while (rcpt != NULL) { + struct smtp_client_command *cmd = rcpt->cmd_rcpt_to; + + rcpt_next = rcpt->next; + + rcpt->cmd_rcpt_to = NULL; + if (cmd != NULL) + smtp_client_command_fail_reply(&cmd, reply); + else + smtp_client_transaction_rcpt_fail_reply(&rcpt, reply); + + rcpt = rcpt_next; + } + + /* DATA / RSET */ + if (!trans->data_provided && !trans->reset) { + /* None of smtp_client_transaction_send() and + smtp_client_transaction_reset() was called so far + */ + } else if (trans->cmd_data != NULL) { + /* The DATA command is still pending; handle the failure by + failing the DATA command. */ + smtp_client_command_fail_reply(&trans->cmd_data, reply); + } else if (trans->cmd_rset != NULL) { + /* The RSET command is still pending; handle the failure by + failing the RSET command. */ + smtp_client_command_fail_reply(&trans->cmd_rset, reply); + } else { + i_assert(!trans->reset); + + /* The DATA command was not sent yet; call all DATA callbacks + for the recipients that were previously accepted. */ + rcpt = trans->rcpts_data; + while (rcpt != NULL) { + rcpt_next = rcpt->next; + smtp_client_transaction_rcpt_fail_reply(&rcpt, reply); + rcpt = rcpt_next; + } + if (trans->data_callback != NULL) + trans->data_callback(reply, trans->data_context); + trans->data_callback = NULL; + } + + /* Plug */ + if (trans->failure == NULL) + trans->failure = smtp_reply_clone(trans->pool, reply); + if (trans->cmd_plug != NULL) + smtp_client_command_abort(&trans->cmd_plug); + trans->cmd_plug = NULL; + + trans->failing = FALSE; + + if (trans->data_provided || trans->reset) { + /* Abort the transaction only if smtp_client_transaction_send() + or smtp_client_transaction_reset() was called (and if it is + not aborted already) */ + smtp_client_transaction_abort(trans); + } + + /* Drop reference held earlier in this function */ + smtp_client_transaction_unref(&trans); +} + +void smtp_client_transaction_fail(struct smtp_client_transaction *trans, + unsigned int status, const char *error) +{ + struct smtp_reply reply; + + smtp_reply_init(&reply, status, error); + smtp_client_transaction_fail_reply(trans, &reply); +} + +void smtp_client_transaction_set_event(struct smtp_client_transaction *trans, + struct event *event) +{ + i_assert(trans->conn != NULL); + event_unref(&trans->event); + trans->event = event_create(event); + event_set_forced_debug(trans->event, trans->conn->set.debug); + smtp_client_transaction_update_event(trans); +} + +static void +smtp_client_transaction_timeout(struct smtp_client_transaction *trans) +{ + struct smtp_reply reply; + + smtp_reply_printf( + &reply, 451, "Remote server not answering " + "(transaction timed out while %s)", + smtp_client_transaction_get_state_destription(trans)); + reply.enhanced_code = SMTP_REPLY_ENH_CODE(4, 4, 0); + + smtp_client_transaction_fail_reply(trans, &reply); +} + +void smtp_client_transaction_set_timeout(struct smtp_client_transaction *trans, + unsigned int timeout_msecs) +{ + i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); + + trans->finish_timeout_msecs = timeout_msecs; + + if (trans->data_input != NULL && timeout_msecs > 0) { + /* Adjust timeout if it is already started */ + timeout_remove(&trans->to_finish); + trans->to_finish = timeout_add(trans->finish_timeout_msecs, + smtp_client_transaction_timeout, + trans); + } +} + +static void +smtp_client_transaction_mail_cb(const struct smtp_reply *reply, + struct smtp_client_transaction *trans) +{ + struct smtp_client_transaction_mail *mail = trans->mail_head; + bool success = smtp_reply_is_success(reply); + + e_debug(trans->event, "Got MAIL reply: %s", smtp_reply_log(reply)); + + i_assert(mail != NULL); + i_assert(trans->conn != NULL); + + if (success) { + if (trans->sender_accepted) { + smtp_client_transaction_fail( + trans, SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY, + "Server accepted more than a single MAIL command."); + return; + } + trans->mail_failure = NULL; + trans->sender_accepted = TRUE; + } + + /* Plug command line pipeline if no RCPT commands are yet issued */ + if (!trans->immediate && mail->next == NULL && + mail->cmd_mail_from == trans->cmd_last) { + trans->cmd_plug = trans->cmd_last = + smtp_client_command_plug(trans->conn, trans->cmd_last); + } + mail->cmd_mail_from = NULL; + + if (trans->rcpts_queue_count > 0) + trans->state = SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO; + else if (trans->reset) + trans->state = SMTP_CLIENT_TRANSACTION_STATE_RESET; + + { + enum smtp_client_transaction_state state; + struct smtp_client_transaction *tmp_trans = trans; + + smtp_client_transaction_ref(tmp_trans); + + smtp_client_transaction_mail_replied(&mail, reply); + + state = trans->state; + smtp_client_transaction_unref(&tmp_trans); + if (state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED) + return; + } + + if (!trans->sender_accepted && trans->mail_head != NULL) { + /* Update transaction with next MAIL command candidate */ + mail = trans->mail_head; + event_add_str(trans->event, "mail_from", + smtp_address_encode(mail->mail_from)); + smtp_params_mail_add_to_event(&mail->mail_params, + trans->event); + } + + if (!success && !trans->sender_accepted) { + if (trans->state > SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) + smtp_client_transaction_fail_reply(trans, reply); + else if (trans->mail_failure == NULL) { + trans->mail_failure = + smtp_reply_clone(trans->pool, reply); + } + } +} + +#undef smtp_client_transaction_add_mail +struct smtp_client_transaction_mail * +smtp_client_transaction_add_mail(struct smtp_client_transaction *trans, + const struct smtp_address *mail_from, + const struct smtp_params_mail *mail_params, + smtp_client_command_callback_t *mail_callback, + void *context) +{ + struct smtp_client_transaction_mail *mail; + + e_debug(trans->event, "Add MAIL command"); + + i_assert(!trans->data_provided); + i_assert(!trans->reset); + + i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO); + + mail = smtp_client_transaction_mail_new(trans, mail_from, mail_params); + mail->mail_callback = mail_callback; + mail->context = context; + + smtp_client_transaction_submit(trans, FALSE); + + return mail; +} + +static void +smtp_client_transaction_connection_ready(struct smtp_client_transaction *trans) +{ + if (trans->state != SMTP_CLIENT_TRANSACTION_STATE_PENDING) + return; + + e_debug(trans->event, "Connecton is ready for transaction"); + + trans->state = SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM; + + smtp_client_transaction_submit_more(trans); +} + +#undef smtp_client_transaction_start +void smtp_client_transaction_start( + struct smtp_client_transaction *trans, + smtp_client_command_callback_t *mail_callback, void *context) +{ + struct smtp_client_connection *conn = trans->conn; + struct smtp_client_transaction_mail *mail = trans->mail_head; + + i_assert(trans->state == SMTP_CLIENT_TRANSACTION_STATE_NEW); + i_assert(trans->conn != NULL); + + i_assert(mail != NULL); + event_add_str(trans->event, "mail_from", + smtp_address_encode(mail->mail_from)); + event_add_str(trans->event, "mail_from_raw", + smtp_address_encode_raw(mail->mail_from)); + smtp_params_mail_add_to_event(&mail->mail_params, + trans->event); + + struct event_passthrough *e = + event_create_passthrough(trans->event)-> + set_name("smtp_client_transaction_started"); + e_debug(e->event(), "Start"); + + io_loop_time_refresh(); + trans->times.started = ioloop_timeval; + + i_assert(mail->mail_callback == NULL); + + mail->mail_callback = mail_callback; + mail->context = context; + + trans->state = SMTP_CLIENT_TRANSACTION_STATE_PENDING; + + smtp_client_connection_add_transaction(conn, trans); + + if (trans->immediate && + conn->state == SMTP_CLIENT_CONNECTION_STATE_READY) { + trans->state = SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM; + + if (!trans->submitting) + smtp_client_transaction_submit_more(trans); + } else if (trans->cmd_last == NULL) { + trans->cmd_plug = trans->cmd_last = + smtp_client_command_plug(trans->conn, NULL); + } +} + +#undef smtp_client_transaction_start_empty +void smtp_client_transaction_start_empty( + struct smtp_client_transaction *trans, + const struct smtp_address *mail_from, + const struct smtp_params_mail *mail_params, + smtp_client_command_callback_t *mail_callback, void *context) +{ + i_assert(trans->mail_head == NULL); + + (void)smtp_client_transaction_mail_new(trans, mail_from, mail_params); + + smtp_client_transaction_start(trans, mail_callback, context); +} + +static void +smtp_client_transaction_rcpt_cb(const struct smtp_reply *reply, + struct smtp_client_transaction_rcpt *rcpt) +{ + struct smtp_client_transaction *trans = rcpt->trans; + + i_assert(trans->conn != NULL); + + e_debug(trans->event, "Got RCPT reply: %s", smtp_reply_log(reply)); + + /* Plug command line pipeline if DATA command is not yet issued */ + if (!trans->immediate && !trans->reset && + rcpt->cmd_rcpt_to == trans->cmd_last && trans->cmd_data == NULL) { + trans->cmd_plug = trans->cmd_last = + smtp_client_command_plug(trans->conn, trans->cmd_last); + } + rcpt->cmd_rcpt_to = NULL; + + { + enum smtp_client_transaction_state state; + struct smtp_client_transaction *tmp_trans = trans; + + smtp_client_transaction_ref(tmp_trans); + + smtp_client_transaction_rcpt_replied(&rcpt, reply); + + state = trans->state; + smtp_client_transaction_unref(&tmp_trans); + if (state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED) + return; + } + + smtp_client_transaction_try_complete(trans); +} + +#undef smtp_client_transaction_add_rcpt +struct smtp_client_transaction_rcpt * +smtp_client_transaction_add_rcpt(struct smtp_client_transaction *trans, + const struct smtp_address *rcpt_to, + const struct smtp_params_rcpt *rcpt_params, + smtp_client_command_callback_t *rcpt_callback, + smtp_client_command_callback_t *data_callback, + void *context) +{ + struct smtp_client_transaction_rcpt *rcpt; + pool_t pool; + + e_debug(trans->event, "Add recipient"); + + i_assert(!trans->data_provided); + i_assert(!trans->reset); + + i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); + + if (trans->mail_head == NULL && + trans->state == SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) + trans->state = SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO; + + pool = pool_alloconly_create("smtp transaction rcpt", 512); + rcpt = smtp_client_transaction_rcpt_new(trans, pool, + rcpt_to, rcpt_params); + pool_unref(&pool); + + rcpt->rcpt_callback = rcpt_callback; + rcpt->context = context; + + rcpt->data_callback = data_callback; + rcpt->data_context = context; + + smtp_client_transaction_submit(trans, FALSE); + + return rcpt; +} + +#undef smtp_client_transaction_add_pool_rcpt +struct smtp_client_transaction_rcpt * +smtp_client_transaction_add_pool_rcpt( + struct smtp_client_transaction *trans, pool_t pool, + const struct smtp_address *rcpt_to, + const struct smtp_params_rcpt *rcpt_params, + smtp_client_command_callback_t *rcpt_callback, void *context) +{ + struct smtp_client_transaction_rcpt *rcpt; + + e_debug(trans->event, "Add recipient (external pool)"); + + i_assert(!trans->data_provided); + i_assert(!trans->reset); + + i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); + + if (trans->mail_head == NULL && + trans->state == SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) + trans->state = SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO; + + rcpt = smtp_client_transaction_rcpt_new(trans, pool, + rcpt_to, rcpt_params); + rcpt->rcpt_callback = rcpt_callback; + rcpt->context = context; + rcpt->external_pool = TRUE; + + smtp_client_transaction_submit(trans, FALSE); + + return rcpt; +} + +static void +smtp_client_transaction_data_cb(const struct smtp_reply *reply, + struct smtp_client_transaction *trans) +{ + bool reply_per_rcpt = HAS_ALL_BITS( + trans->flags, SMTP_CLIENT_TRANSACTION_FLAG_REPLY_PER_RCPT); + + i_assert(!trans->reset); + + smtp_client_transaction_ref(trans); + + if (trans->data_input != NULL) { + event_add_int(trans->event, "data_sent", + trans->data_input->v_offset); + i_stream_unref(&trans->data_input); + } + + if (reply_per_rcpt && + trans->cmd_data != NULL && /* NULL when failed early */ + trans->rcpts_data == NULL && trans->rcpts_count > 0) { + smtp_client_command_set_replies(trans->cmd_data, + trans->rcpts_count); + } + while (trans->rcpts_data != NULL) { + struct smtp_client_transaction_rcpt *rcpt = trans->rcpts_data; + + trans->rcpts_data = trans->rcpts_data->next; + smtp_client_transaction_rcpt_finished(rcpt, reply); + if (HAS_ALL_BITS(trans->flags, + SMTP_CLIENT_TRANSACTION_FLAG_REPLY_PER_RCPT)) + break; + } + + if (reply_per_rcpt && trans->rcpts_count > 1 && + !smtp_reply_is_success(reply) && trans->data_failure == NULL) + trans->data_failure = smtp_reply_clone(trans->pool, reply); + if (trans->rcpts_data != NULL) { + smtp_client_transaction_unref(&trans); + return; + } + + trans->cmd_data = NULL; + + if (trans->data_callback != NULL) + trans->data_callback(reply, trans->data_context); + trans->data_callback = NULL; + + /* finished */ + smtp_client_transaction_finish( + trans, (trans->data_failure == NULL ? + reply : trans->data_failure)); + + smtp_client_transaction_unref(&trans); +} + +static void +smtp_client_transaction_send_data(struct smtp_client_transaction *trans) +{ + struct smtp_reply failure; + + i_assert(!trans->reset); + i_assert(trans->data_input != NULL); + + e_debug(trans->event, "Sending data"); + + timeout_remove(&trans->to_send); + + i_zero(&failure); + if (trans->failure != NULL) { + smtp_client_transaction_fail_reply(trans, trans->failure); + failure = *trans->failure; + i_assert(failure.status != 0); + } else if ((trans->rcpts_count + trans->rcpts_queue_count) == 0) { + e_debug(trans->event, "No valid recipients"); + if (trans->failure != NULL) + failure = *trans->failure; + else { + smtp_reply_init(&failure, 554, "No valid recipients"); + failure.enhanced_code = SMTP_REPLY_ENH_CODE(5, 5, 0); + } + i_assert(failure.status != 0); + } else { + i_assert(trans->conn != NULL); + + trans->cmd_data = smtp_client_command_data_submit_after( + trans->conn, 0, trans->cmd_last, trans->data_input, + smtp_client_transaction_data_cb, trans); + trans->submitted_data = TRUE; + + if (trans->cmd_last != NULL) + smtp_client_command_unlock(trans->cmd_last); + + smtp_client_transaction_try_complete(trans); + } + + if (trans->cmd_plug != NULL) + smtp_client_command_abort(&trans->cmd_plug); + trans->cmd_last = NULL; + + if (failure.status != 0) + smtp_client_transaction_finish(trans, &failure); +} + +#undef smtp_client_transaction_send +void smtp_client_transaction_send( + struct smtp_client_transaction *trans, struct istream *data_input, + smtp_client_command_callback_t *data_callback, void *data_context) +{ + i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); + i_assert(!trans->data_provided); + i_assert(!trans->reset); + + if (trans->rcpts_queue_count == 0) + e_debug(trans->event, "Got all RCPT replies"); + + e_debug(trans->event, "Send"); + + trans->data_provided = TRUE; + + i_assert(trans->data_input == NULL); + trans->data_input = i_stream_create_crlf(data_input); + + trans->data_callback = data_callback; + trans->data_context = data_context; + + if (trans->finish_timeout_msecs > 0) { + i_assert(trans->to_finish == NULL); + trans->to_finish = timeout_add(trans->finish_timeout_msecs, + smtp_client_transaction_timeout, + trans); + } + + smtp_client_transaction_submit(trans, TRUE); +} + +static void +smtp_client_transaction_rset_cb(const struct smtp_reply *reply, + struct smtp_client_transaction *trans) +{ + smtp_client_transaction_ref(trans); + + trans->cmd_rset = NULL; + + if (trans->reset_callback != NULL) + trans->reset_callback(reply, trans->reset_context); + trans->reset_callback = NULL; + + /* Finished */ + smtp_client_transaction_finish(trans, reply); + + smtp_client_transaction_unref(&trans); +} + +static void +smtp_client_transaction_send_reset(struct smtp_client_transaction *trans) +{ + struct smtp_reply failure; + + i_assert(trans->reset); + + e_debug(trans->event, "Sending reset"); + + timeout_remove(&trans->to_send); + + i_zero(&failure); + if (trans->failure != NULL) { + smtp_client_transaction_fail_reply(trans, trans->failure); + failure = *trans->failure; + i_assert(failure.status != 0); + } else { + i_assert(trans->conn != NULL); + + trans->cmd_rset = smtp_client_command_rset_submit_after( + trans->conn, 0, trans->cmd_last, + smtp_client_transaction_rset_cb, trans); + + if (trans->cmd_last != NULL) + smtp_client_command_unlock(trans->cmd_last); + + smtp_client_transaction_try_complete(trans); + } + + if (trans->cmd_plug != NULL) + smtp_client_command_abort(&trans->cmd_plug); + trans->cmd_last = NULL; + + if (failure.status != 0) + smtp_client_transaction_finish(trans, &failure); +} + +#undef smtp_client_transaction_reset +void smtp_client_transaction_reset( + struct smtp_client_transaction *trans, + smtp_client_command_callback_t *reset_callback, void *reset_context) +{ + i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); + i_assert(!trans->data_provided); + i_assert(!trans->reset); + + e_debug(trans->event, "Reset"); + + trans->reset = TRUE; + + trans->reset_callback = reset_callback; + trans->reset_context = reset_context; + + if (trans->finish_timeout_msecs > 0) { + i_assert(trans->to_finish == NULL); + trans->to_finish = timeout_add(trans->finish_timeout_msecs, + smtp_client_transaction_timeout, + trans); + } + + smtp_client_transaction_submit(trans, TRUE); +} + +static void +smtp_client_transaction_do_submit_more(struct smtp_client_transaction *trans) +{ + timeout_remove(&trans->to_send); + + /* Check whether we already failed */ + if (trans->failure == NULL && + trans->state > SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) + trans->failure = trans->mail_failure; + if (trans->failure != NULL) { + smtp_client_transaction_fail_reply(trans, trans->failure); + return; + } + + i_assert(trans->conn != NULL); + + /* Make sure transaction is started */ + if (trans->state == SMTP_CLIENT_TRANSACTION_STATE_NEW) { + enum smtp_client_transaction_state state; + struct smtp_client_transaction *tmp_trans = trans; + + smtp_client_transaction_ref(tmp_trans); + smtp_client_transaction_start(tmp_trans, NULL, NULL); + state = trans->state; + smtp_client_transaction_unref(&tmp_trans); + if (state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED) + return; + } + + if (trans->state <= SMTP_CLIENT_TRANSACTION_STATE_PENDING) { + if (trans->cmd_last == NULL) { + trans->cmd_plug = trans->cmd_last = + smtp_client_command_plug(trans->conn, NULL); + } + return; + } + + /* MAIL */ + if (trans->mail_send != NULL) { + e_debug(trans->event, "Sending MAIL command"); + + i_assert(trans->state == + SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM); + + if (trans->cmd_last != NULL) + smtp_client_command_unlock(trans->cmd_last); + + while (trans->mail_send != NULL) { + struct smtp_client_transaction_mail *mail = + trans->mail_send; + + trans->mail_send = trans->mail_send->next; + mail->cmd_mail_from = trans->cmd_last = + smtp_client_command_mail_submit_after( + trans->conn, 0, trans->cmd_last, + mail->mail_from, &mail->mail_params, + smtp_client_transaction_mail_cb, trans); + } + } else if (trans->immediate) + trans->cmd_last = NULL; + + /* RCPT */ + if (trans->rcpts_send != NULL) { + e_debug(trans->event, "Sending recipients"); + + if (trans->cmd_last != NULL) + smtp_client_command_unlock(trans->cmd_last); + + while (trans->rcpts_send != NULL) { + struct smtp_client_transaction_rcpt *rcpt = + trans->rcpts_send; + + trans->rcpts_send = trans->rcpts_send->next; + rcpt->cmd_rcpt_to = trans->cmd_last = + smtp_client_command_rcpt_submit_after( + trans->conn, 0, trans->cmd_last, + rcpt->rcpt_to, &rcpt->rcpt_params, + smtp_client_transaction_rcpt_cb, rcpt); + } + } + + if (trans->cmd_plug != NULL && + (trans->immediate || trans->cmd_last != trans->cmd_plug)) + smtp_client_command_abort(&trans->cmd_plug); + if (trans->cmd_last != NULL && !trans->immediate) + smtp_client_command_lock(trans->cmd_last); + + /* DATA / RSET */ + if (trans->reset) { + smtp_client_transaction_send_reset(trans); + } else if (trans->data_input != NULL) { + smtp_client_transaction_send_data(trans); + } +} + +static void +smtp_client_transaction_submit_more(struct smtp_client_transaction *trans) +{ + smtp_client_transaction_ref(trans); + trans->submitting = TRUE; + smtp_client_transaction_do_submit_more(trans); + trans->submitting = FALSE; + smtp_client_transaction_unref(&trans); +} + +static void +smtp_client_transaction_submit(struct smtp_client_transaction *trans, + bool start) +{ + if (trans->failure == NULL && !start && + trans->state <= SMTP_CLIENT_TRANSACTION_STATE_PENDING) { + /* Cannot submit commands at this time */ + return; + } + + if (trans->immediate) { + /* Submit immediately if not failed already: avoid calling + failure callbacks directly (which is the first thing + smtp_client_transaction_submit_more() would do). */ + if (trans->failure == NULL && + trans->state > SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) + trans->failure = trans->mail_failure; + if (trans->failure == NULL) { + smtp_client_transaction_submit_more(trans); + return; + } + } + + if (trans->to_send != NULL) { + /* Already scheduled command submission */ + return; + } + + trans->to_send = timeout_add_short(0, + smtp_client_transaction_submit_more, trans); +} + +static void +smtp_client_transaction_try_complete(struct smtp_client_transaction *trans) +{ + i_assert(trans->conn != NULL); + + if (trans->rcpts_queue_count > 0) { + /* Not all RCPT replies have come in yet */ + e_debug(trans->event, "RCPT replies are still pending (%u/%u)", + trans->rcpts_queue_count, + (trans->rcpts_queue_count + trans->rcpts_count)); + return; + } + if (!trans->data_provided && !trans->reset) { + /* Still waiting for application to issue either + smtp_client_transaction_send() or + smtp_client_transaction_reset() */ + e_debug(trans->event, "Transaction is not yet complete"); + return; + } + + if (trans->state == SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO) { + /* Completed at this instance */ + e_debug(trans->event, + "Got all RCPT replies and transaction is complete"); + } + + if (trans->reset) { + /* Entering reset state */ + trans->state = SMTP_CLIENT_TRANSACTION_STATE_RESET; + + if (trans->cmd_rset == NULL) + return; + } else { + /* Entering data state */ + trans->state = SMTP_CLIENT_TRANSACTION_STATE_DATA; + + if (trans->rcpts_count == 0) { + /* abort transaction if all recipients failed */ + smtp_client_transaction_abort(trans); + return; + } + + if (trans->cmd_data == NULL) + return; + + if (HAS_ALL_BITS(trans->flags, + SMTP_CLIENT_TRANSACTION_FLAG_REPLY_PER_RCPT)) { + smtp_client_command_set_replies(trans->cmd_data, + trans->rcpts_count); + } + } + + /* Got replies for all recipients and submitted our last command; + the next transaction can submit its commands now. */ + smtp_client_connection_next_transaction(trans->conn, trans); +} + +void smtp_client_transaction_set_immediate( + struct smtp_client_transaction *trans, bool immediate) +{ + trans->immediate = immediate; +} + +void smtp_client_transaction_connection_result( + struct smtp_client_transaction *trans, + const struct smtp_reply *reply) +{ + if (!smtp_reply_is_success(reply)) { + if (trans->state <= SMTP_CLIENT_TRANSACTION_STATE_PENDING) { + e_debug(trans->event, "Failed to connect: %s", + smtp_reply_log(reply)); + } else { + e_debug(trans->event, "Connection lost: %s", + smtp_reply_log(reply)); + } + smtp_client_transaction_fail_reply(trans, reply); + return; + } + + smtp_client_transaction_connection_ready(trans); +} + +void smtp_client_transaction_connection_destroyed( + struct smtp_client_transaction *trans) +{ + i_assert(trans->failure != NULL); + smtp_client_connection_unref(&trans->conn); +} + +const struct smtp_client_transaction_times * +smtp_client_transaction_get_times(struct smtp_client_transaction *trans) +{ + return &trans->times; +} + +enum smtp_client_transaction_state +smtp_client_transaction_get_state(struct smtp_client_transaction *trans) +{ + return trans->state; +} + +const char * +smtp_client_transaction_get_state_name(struct smtp_client_transaction *trans) +{ + i_assert(trans->state >= SMTP_CLIENT_TRANSACTION_STATE_NEW && + trans->state <= SMTP_CLIENT_TRANSACTION_STATE_ABORTED); + return smtp_client_transaction_state_names[trans->state]; +} + +const char * +smtp_client_transaction_get_state_destription( + struct smtp_client_transaction *trans) +{ + enum smtp_client_connection_state conn_state; + + switch (trans->state) { + case SMTP_CLIENT_TRANSACTION_STATE_NEW: + break; + case SMTP_CLIENT_TRANSACTION_STATE_PENDING: + i_assert(trans->conn != NULL); + conn_state = smtp_client_connection_get_state(trans->conn); + switch (conn_state) { + case SMTP_CLIENT_CONNECTION_STATE_CONNECTING: + case SMTP_CLIENT_CONNECTION_STATE_HANDSHAKING: + case SMTP_CLIENT_CONNECTION_STATE_AUTHENTICATING: + return smtp_client_connection_state_names[conn_state]; + case SMTP_CLIENT_CONNECTION_STATE_TRANSACTION: + return "waiting for connection"; + case SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED: + case SMTP_CLIENT_CONNECTION_STATE_READY: + default: + break; + } + break; + case SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM: + return "waiting for reply to MAIL FROM"; + case SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO: + return "waiting for reply to RCPT TO"; + case SMTP_CLIENT_TRANSACTION_STATE_DATA: + return "waiting for reply to DATA"; + case SMTP_CLIENT_TRANSACTION_STATE_RESET: + return "waiting for reply to RESET"; + case SMTP_CLIENT_TRANSACTION_STATE_FINISHED: + return "finished"; + case SMTP_CLIENT_TRANSACTION_STATE_ABORTED: + return "aborted"; + } + i_unreached(); +} + +void smtp_client_transaction_switch_ioloop( + struct smtp_client_transaction *trans) +{ + if (trans->to_send != NULL) + trans->to_send = io_loop_move_timeout(&trans->to_send); + if (trans->to_finish != NULL) + trans->to_finish = io_loop_move_timeout(&trans->to_finish); +} diff --git a/src/lib-smtp/smtp-client-transaction.h b/src/lib-smtp/smtp-client-transaction.h new file mode 100644 index 0000000..d0cc58b --- /dev/null +++ b/src/lib-smtp/smtp-client-transaction.h @@ -0,0 +1,259 @@ +#ifndef SMTP_CLIENT_TRANSACTION_H +#define SMTP_CLIENT_TRANSACTION_H + +#include "net.h" +#include "istream.h" + +struct smtp_address; +struct smtp_client_transaction; +struct smtp_client_transaction_mail; +struct smtp_client_transaction_rcpt; + +enum smtp_client_transaction_flags { + SMTP_CLIENT_TRANSACTION_FLAG_REPLY_PER_RCPT = BIT(0), +}; + +enum smtp_client_transaction_state { + SMTP_CLIENT_TRANSACTION_STATE_NEW = 0, + SMTP_CLIENT_TRANSACTION_STATE_PENDING, + SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM, + SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO, + SMTP_CLIENT_TRANSACTION_STATE_DATA, + SMTP_CLIENT_TRANSACTION_STATE_RESET, + SMTP_CLIENT_TRANSACTION_STATE_FINISHED, + SMTP_CLIENT_TRANSACTION_STATE_ABORTED + /* NOTE: keep synced with smtp_client_transaction_state_names[] */ +}; +extern const char *const smtp_client_transaction_state_names[]; + +struct smtp_client_transaction_times { + struct timeval started; + struct timeval finished; +}; + +/* Called when the transaction is finished, either because the MAIL FROM + failed, all RCPT TOs failed or because all DATA replies have been + received. */ +typedef void +smtp_client_transaction_callback_t(void *context); + +/* Create an empty transaction (i.e. even without the parameters for the + MAIL FROM command) */ +struct smtp_client_transaction * +smtp_client_transaction_create_empty( + struct smtp_client_connection *conn, + enum smtp_client_transaction_flags flags, + smtp_client_transaction_callback_t *callback, void *context) + ATTR_NULL(4); +#define smtp_client_transaction_create_empty(conn, flags, callback, context) \ + smtp_client_transaction_create_empty(conn, flags - \ + CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ + (smtp_client_transaction_callback_t *)callback, context) +/* Create a new transaction, including the parameters for the MAIL FROM + command */ +struct smtp_client_transaction * +smtp_client_transaction_create(struct smtp_client_connection *conn, + const struct smtp_address *mail_from, + const struct smtp_params_mail *mail_params, + enum smtp_client_transaction_flags flags, + smtp_client_transaction_callback_t *callback, void *context) + ATTR_NULL(2, 3, 6); +#define smtp_client_transaction_create(conn, \ + mail_from, mail_params, flags, callback, context) \ + smtp_client_transaction_create(conn, mail_from, mail_params, flags - \ + CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ + (smtp_client_transaction_callback_t *)callback, context) + +void smtp_client_transaction_ref(struct smtp_client_transaction *trans); +void smtp_client_transaction_unref(struct smtp_client_transaction **_trans); +void smtp_client_transaction_destroy(struct smtp_client_transaction **trans); + +void smtp_client_transaction_abort(struct smtp_client_transaction *trans); +void smtp_client_transaction_fail_reply(struct smtp_client_transaction *trans, + const struct smtp_reply *reply); +void smtp_client_transaction_fail(struct smtp_client_transaction *trans, + unsigned int status, const char *error); + +void smtp_client_transaction_set_event(struct smtp_client_transaction *trans, + struct event *event); +void smtp_client_transaction_set_timeout(struct smtp_client_transaction *trans, + unsigned int timeout_msecs); + +/* Start the transaction with a MAIL command. The mail_from_callback is + called once the server replies to the MAIL FROM command. Calling this + function is not mandatory; it is called implicitly by + smtp_client_transaction_send() if the transaction wasn't already started. + */ +void smtp_client_transaction_start(struct smtp_client_transaction *trans, + smtp_client_command_callback_t *mail_callback, void *context); +#define smtp_client_transaction_start(trans, mail_callback, context) \ + smtp_client_transaction_start(trans, \ + (smtp_client_command_callback_t *)mail_callback, TRUE ? context : \ + CALLBACK_TYPECHECK(mail_callback, void (*)( \ + const struct smtp_reply *reply, typeof(context)))) +/* Start the transaction with a MAIL command. This function allows providing the + parameters for the MAIL FROM command for when the transaction was created + empty. The mail_from_callback is called once the server replies to the MAIL + FROM command. Calling this function is not mandatory; it is called implicitly + by smtp_client_transaction_send() if the transaction wasn't already started. + In that case, the NULL sender ("<>") will be used when the transaction was + created empty. + */ +void smtp_client_transaction_start_empty( + struct smtp_client_transaction *trans, + const struct smtp_address *mail_from, + const struct smtp_params_mail *mail_params, + smtp_client_command_callback_t *mail_callback, void *context); +#define smtp_client_transaction_start_empty(trans, mail_from, mail_params, \ + mail_callback, context) \ + smtp_client_transaction_start_empty(trans, mail_from, mail_params, \ + (smtp_client_command_callback_t *)mail_callback, TRUE ? context : \ + CALLBACK_TYPECHECK(mail_callback, void (*)( \ + const struct smtp_reply *reply, typeof(context)))) + +/* Add an extra pipelined MAIL command to the transaction. The mail_callback is + called once the server replies to the MAIL command. This is usually only + useful for forwarding pipelined SMTP transactions, which can involve more + than a single MAIL command (e.g. to have an implicit fallback sender address + in the pipeline when the first one fails). Of course, only one MAIL command + will succeed and therefore error replies for the others will not abort the + transaction. This function returns a struct that can be used to abort the + MAIL command prematurely (see below). */ +struct smtp_client_transaction_mail * +smtp_client_transaction_add_mail(struct smtp_client_transaction *trans, + const struct smtp_address *mail_from, + const struct smtp_params_mail *mail_params, + smtp_client_command_callback_t *mail_callback, + void *context) + ATTR_NOWARN_UNUSED_RESULT ATTR_NULL(3,5); +#define smtp_client_transaction_add_mail(trans, \ + mail_from, mail_params, mail_callback, context) \ + smtp_client_transaction_add_mail(trans, mail_from - \ + CALLBACK_TYPECHECK(mail_callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + mail_params, \ + (smtp_client_command_callback_t *)mail_callback, context) +/* Abort the MAIL command prematurely. This function must not be called after + the mail_callback from smtp_client_transaction_add_mail() is called. */ +void smtp_client_transaction_mail_abort( + struct smtp_client_transaction_mail **_mail); + +/* Add recipient to the transaction with a RCPT TO command. The + rcpt_to_callback is called once the server replies to the RCPT TO command. + If RCPT TO succeeded, the data_callback is called once the server replies + to the DATA command. The data_callback will not be called until + smtp_client_transaction_send() is called for the transaction (see below). + Until that time, any failure is remembered. This function returns a struct + that can be used to abort the RCPT command prematurely (see below). This + struct must not be used after the rcpt_callback is called. */ +struct smtp_client_transaction_rcpt * +smtp_client_transaction_add_rcpt(struct smtp_client_transaction *trans, + const struct smtp_address *rcpt_to, + const struct smtp_params_rcpt *rcpt_params, + smtp_client_command_callback_t *rcpt_callback, + smtp_client_command_callback_t *data_callback, + void *context) + ATTR_NOWARN_UNUSED_RESULT ATTR_NULL(3,5,6); +#define smtp_client_transaction_add_rcpt(trans, \ + rcpt_to, rcpt_params, rcpt_callback, data_callback, context) \ + smtp_client_transaction_add_rcpt(trans, rcpt_to - \ + CALLBACK_TYPECHECK(rcpt_callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))) - \ + CALLBACK_TYPECHECK(data_callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + rcpt_params, \ + (smtp_client_command_callback_t *)rcpt_callback, \ + (smtp_client_command_callback_t *)data_callback, context) +/* Add recipient to the transaction with a RCPT TO command. The + rcpt_to_callback is called once the server replies to the RCPT TO command. + This function returns a struct that can be used to abort the RCPT command + prematurely (see below). This struct is allocated on the provided pool (the + pool is referenced) and remains valid until the destruction of the + transaction. + */ +struct smtp_client_transaction_rcpt * +smtp_client_transaction_add_pool_rcpt( + struct smtp_client_transaction *trans, pool_t pool, + const struct smtp_address *rcpt_to, + const struct smtp_params_rcpt *rcpt_params, + smtp_client_command_callback_t *rcpt_callback, void *context) + ATTR_NOWARN_UNUSED_RESULT ATTR_NULL(4,6,7); +#define smtp_client_transaction_add_pool_rcpt(trans, pool, \ + rcpt_to, rcpt_params, rcpt_callback, context) \ + smtp_client_transaction_add_pool_rcpt(trans, pool, rcpt_to - \ + CALLBACK_TYPECHECK(rcpt_callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))), \ + rcpt_params, \ + (smtp_client_command_callback_t *)rcpt_callback, context) +/* Abort the RCPT command prematurely. This function must not be called after + the rcpt_callback from smtp_client_transaction_add_rcpt() is called. */ +void smtp_client_transaction_rcpt_abort( + struct smtp_client_transaction_rcpt **_rcpt); +/* Set the DATA callback for this recipient. If RCPT TO succeeded, the callback + is called once the server replies to the DATA command. Until that time, any + failure is remembered. The callback will not be called until + smtp_client_transaction_send() is called for the transaction (see below). */ +void smtp_client_transaction_rcpt_set_data_callback( + struct smtp_client_transaction_rcpt *rcpt, + smtp_client_command_callback_t *callback, void *context) + ATTR_NULL(3); +#define smtp_client_transaction_rcpt_set_data_callback(trans, \ + callback, context) \ + smtp_client_transaction_rcpt_set_data_callback(trans, \ + (smtp_client_command_callback_t *)callback, \ + (TRUE ? context : \ + CALLBACK_TYPECHECK(callback, void (*)( \ + const struct smtp_reply *reply, typeof(context))))) + +/* Start sending input stream as DATA. This completes the transaction, which + means that any pending failures that got recorded before this function was + called will be triggered now. If any RCPT TO succeeded, the provided + data_callback is called once the server replies to the DATA command. This + callback is mainly useful for SMTP, for LMTP it will only yield the reply for + the last recipient. This function starts the transaction implicitly. */ +void smtp_client_transaction_send( + struct smtp_client_transaction *trans, struct istream *data_input, + smtp_client_command_callback_t *data_callback, void *data_context); +#define smtp_client_transaction_send(trans, \ + data_input, data_callback, data_context) \ + smtp_client_transaction_send(trans, data_input - \ + CALLBACK_TYPECHECK(data_callback, void (*)( \ + const struct smtp_reply *reply, typeof(data_context))), \ + (smtp_client_command_callback_t *)data_callback, data_context) + +/* Gracefully reset the transaction by sending the RSET command and waiting for + the response. This does not try to abort pending MAIL and RCPT commands, + allowing the transaction to be evaluated without proceeding with the DATA + command. */ +void smtp_client_transaction_reset( + struct smtp_client_transaction *trans, + smtp_client_command_callback_t *reset_callback, void *reset_context); +#define smtp_client_transaction_reset(trans, reset_callback, reset_context) \ + smtp_client_transaction_reset(trans, \ + (smtp_client_command_callback_t *)reset_callback, \ + TRUE ? reset_context : \ + CALLBACK_TYPECHECK(reset_callback, void (*)( \ + const struct smtp_reply *reply, typeof(reset_context)))) + +/* Enables mode in which all commands are submitted immediately and (non- + transaction) commands can be interleaved. This is mainly important for + relaying SMTP in realtime. */ +void smtp_client_transaction_set_immediate( + struct smtp_client_transaction *trans, bool immediate); + +/* Return transaction statistics. */ +const struct smtp_client_transaction_times * +smtp_client_transaction_get_times(struct smtp_client_transaction *trans); + +/* Return transaction state */ +enum smtp_client_transaction_state +smtp_client_transaction_get_state(struct smtp_client_transaction *trans) + ATTR_PURE; +const char * +smtp_client_transaction_get_state_name(struct smtp_client_transaction *trans) + ATTR_PURE; +const char * +smtp_client_transaction_get_state_destription( + struct smtp_client_transaction *trans); + +#endif diff --git a/src/lib-smtp/smtp-client.c b/src/lib-smtp/smtp-client.c new file mode 100644 index 0000000..653dd0e --- /dev/null +++ b/src/lib-smtp/smtp-client.c @@ -0,0 +1,142 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "str.h" +#include "hash.h" +#include "array.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "connection.h" +#include "dns-lookup.h" +#include "iostream-rawlog.h" +#include "iostream-ssl.h" + +#include "smtp-client-private.h" + +#define SMTP_DEFAULT_PORT 80 +#define SSMTP_DEFAULT_PORT 465 + +static struct event_category event_category_smtp_client = { + .name = "smtp-client" +}; + +/* + * Client + */ + +struct smtp_client *smtp_client_init(const struct smtp_client_settings *set) +{ + struct smtp_client *client; + pool_t pool; + + pool = pool_alloconly_create("smtp client", 1024); + client = p_new(pool, struct smtp_client, 1); + client->pool = pool; + + client->set.my_ip = set->my_ip; + client->set.my_hostname = p_strdup(pool, set->my_hostname); + + client->set.forced_capabilities = set->forced_capabilities; + if (set->extra_capabilities != NULL) { + client->set.extra_capabilities = + p_strarray_dup(pool, set->extra_capabilities); + } + + client->set.dns_client = set->dns_client; + client->set.dns_client_socket_path = + p_strdup(pool, set->dns_client_socket_path); + client->set.rawlog_dir = p_strdup_empty(pool, set->rawlog_dir); + + if (set->ssl != NULL) { + client->set.ssl = + ssl_iostream_settings_dup(client->pool, set->ssl); + } + + client->set.master_user = p_strdup_empty(pool, set->master_user); + client->set.username = p_strdup_empty(pool, set->username); + client->set.sasl_mech = set->sasl_mech; + if (set->sasl_mech == NULL) { + client->set.sasl_mechanisms = + p_strdup(pool, set->sasl_mechanisms); + } + + client->set.connect_timeout_msecs = set->connect_timeout_msecs != 0 ? + set->connect_timeout_msecs : + SMTP_DEFAULT_CONNECT_TIMEOUT_MSECS; + client->set.command_timeout_msecs = set->command_timeout_msecs != 0 ? + set->command_timeout_msecs : + SMTP_DEFAULT_COMMAND_TIMEOUT_MSECS; + client->set.max_reply_size = set->max_reply_size != 0 ? + set->max_reply_size : SMTP_DEFAULT_MAX_REPLY_SIZE; + client->set.max_data_chunk_size = set->max_data_chunk_size != 0 ? + set->max_data_chunk_size : SMTP_DEFAULT_MAX_DATA_CHUNK_SIZE; + client->set.max_data_chunk_pipeline = set->max_data_chunk_pipeline != 0 ? + set->max_data_chunk_pipeline : SMTP_DEFAULT_MAX_DATA_CHUNK_PIPELINE; + + client->set.socket_send_buffer_size = set->socket_send_buffer_size; + client->set.socket_recv_buffer_size = set->socket_recv_buffer_size; + client->set.debug = set->debug; + client->set.verbose_user_errors = set->verbose_user_errors; + + smtp_proxy_data_merge(pool, &client->set.proxy_data, &set->proxy_data); + + client->conn_list = smtp_client_connection_list_init(); + + /* There is no event log prefix added here, since the client itself does + not log anything and the prefix is protocol-dependent. */ + client->event = event_create(set->event_parent); + event_add_category(client->event, &event_category_smtp_client); + event_set_forced_debug(client->event, set->debug); + + return client; +} + +void smtp_client_deinit(struct smtp_client **_client) +{ + struct smtp_client *client = *_client; + + connection_list_deinit(&client->conn_list); + + if (client->ssl_ctx != NULL) + ssl_iostream_context_unref(&client->ssl_ctx); + event_unref(&client->event); + pool_unref(&client->pool); + *_client = NULL; +} + +void smtp_client_switch_ioloop(struct smtp_client *client) +{ + struct connection *_conn = client->conn_list->connections; + + /* move connections */ + for (; _conn != NULL; _conn = _conn->next) { + struct smtp_client_connection *conn = + (struct smtp_client_connection *)_conn; + + smtp_client_connection_switch_ioloop(conn); + } +} + +int smtp_client_init_ssl_ctx(struct smtp_client *client, const char **error_r) +{ + const char *error; + + if (client->ssl_ctx != NULL) + return 0; + + if (client->set.ssl == NULL) { + *error_r = "Requested SSL connection, but no SSL settings given"; + return -1; + } + if (ssl_iostream_client_context_cache_get(client->set.ssl, + &client->ssl_ctx, &error) < 0) { + *error_r = t_strdup_printf("Couldn't initialize SSL context: %s", + error); + return -1; + } + return 0; +} + +// FIXME: Implement smtp_client_run() diff --git a/src/lib-smtp/smtp-client.h b/src/lib-smtp/smtp-client.h new file mode 100644 index 0000000..75608e9 --- /dev/null +++ b/src/lib-smtp/smtp-client.h @@ -0,0 +1,128 @@ +#ifndef SMTP_CLIENT_H +#define SMTP_CLIENT_H + +#include "net.h" +#include "smtp-common.h" +#include "smtp-address.h" +#include "smtp-reply.h" + +struct smtp_client; +struct smtp_client_request; + +#define SMTP_DEFAULT_CONNECT_TIMEOUT_MSECS (1000*30) +#define SMTP_DEFAULT_COMMAND_TIMEOUT_MSECS (1000*60*5) +#define SMTP_DEFAULT_MAX_REPLY_SIZE (SIZE_MAX) +#define SMTP_DEFAULT_MAX_DATA_CHUNK_SIZE NET_BLOCK_SIZE +#define SMTP_DEFAULT_MAX_DATA_CHUNK_PIPELINE 4 + +enum smtp_client_command_error { + /* Server closed the connection */ + SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED = 421, + /* The command was aborted */ + SMTP_CLIENT_COMMAND_ERROR_ABORTED = 9000, + /* DNS lookup failed */ + SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED, + /* Failed to establish the connection */ + SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED, + /* Failed to authenticate using the provided credentials */ + SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED, + /* Lost the connection after initially succeeded */ + SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST, + /* Got an invalid reply from the server */ + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY, + /* We sent a command with a payload stream that broke while reading + from it */ + SMTP_CLIENT_COMMAND_ERROR_BROKEN_PAYLOAD, + /* The server failed to respond before the command timed out */ + SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT +}; + +struct smtp_client_capability_extra { + const char *name; + + /* Send these additional custom MAIL parameters if available. */ + const char *const *mail_param_extensions; + /* Send these additional custom RCPT parameters if available. */ + const char *const *rcpt_param_extensions; +}; + +struct smtp_client_settings { + struct ip_addr my_ip; + const char *my_hostname; + const char *temp_path_prefix; + + /* Capabilities that are assumed to be enabled no matter whether the + server indicates support. */ + enum smtp_capability forced_capabilities; + /* Record these extra capabilities if returned in the EHLO response */ + const char *const *extra_capabilities; + + struct dns_client *dns_client; + const char *dns_client_socket_path; + + const struct ssl_iostream_settings *ssl; + + const char *master_user; + const char *username; + const char *password; + const struct dsasl_client_mech *sasl_mech; + /* Space-separated list of SASL mechanisms to try (in the specified + order). The default is to use only SASL PLAIN. */ + const char *sasl_mechanisms; + + const char *rawlog_dir; + + /* Timeout for SMTP commands. Reset every time more data is being + sent or received. + (default = unlimited) */ + unsigned int command_timeout_msecs; + /* Timeout for loggging in + (default = cmd_timeout_msecs) */ + unsigned int connect_timeout_msecs; + + /* Max total size of reply */ + size_t max_reply_size; + + /* Maximum BDAT chunk size for the CHUNKING capability */ + uoff_t max_data_chunk_size; + /* Maximum pipelined BDAT commands */ + unsigned int max_data_chunk_pipeline; + + /* if remote server supports XCLIENT capability, + send this data */ + struct smtp_proxy_data proxy_data; + + /* the kernel send/receive buffer sizes used for the connection sockets. + Configuring this is mainly useful for the test suite. The kernel + defaults are used when these settings are 0. */ + size_t socket_send_buffer_size; + size_t socket_recv_buffer_size; + + /* Event to use as the parent for the smtp client/connection event. For + specific transactions this can be overridden with + smtp_client_transaction_set_event(). */ + struct event *event_parent; + + /* enable logging debug messages */ + bool debug; + /* peer is trusted, so e.g. attempt sending XCLIENT data */ + bool peer_trusted; + /* defer sending XCLIENT command until authentication or first mail + transaction. */ + bool xclient_defer; + /* don't clear password after first successful authentication */ + bool remember_password; + /* sending even broken MAIL command path (otherwise a broken address + is sent as <>) */ + bool mail_send_broken_path; + /* Yield verbose user-visible errors for commands and connections that + failed locally. */ + bool verbose_user_errors; +}; + +struct smtp_client *smtp_client_init(const struct smtp_client_settings *set); +void smtp_client_deinit(struct smtp_client **_client); + +void smtp_client_switch_ioloop(struct smtp_client *client); + +#endif diff --git a/src/lib-smtp/smtp-command-parser.c b/src/lib-smtp/smtp-command-parser.c new file mode 100644 index 0000000..f50abc9 --- /dev/null +++ b/src/lib-smtp/smtp-command-parser.c @@ -0,0 +1,613 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "unichar.h" +#include "istream.h" +#include "istream-failure-at.h" +#include "istream-sized.h" +#include "istream-dot.h" + +#include "smtp-parser.h" +#include "smtp-command-parser.h" + +#include + +#define SMTP_COMMAND_PARSER_MAX_COMMAND_LENGTH 32 + +enum smtp_command_parser_state { + SMTP_COMMAND_PARSE_STATE_INIT = 0, + SMTP_COMMAND_PARSE_STATE_SKIP_LINE, + SMTP_COMMAND_PARSE_STATE_COMMAND, + SMTP_COMMAND_PARSE_STATE_SP, + SMTP_COMMAND_PARSE_STATE_PARAMETERS, + SMTP_COMMAND_PARSE_STATE_CR, + SMTP_COMMAND_PARSE_STATE_LF, + SMTP_COMMAND_PARSE_STATE_ERROR, +}; + +struct smtp_command_parser_state_data { + enum smtp_command_parser_state state; + + char *cmd_name; + char *cmd_params; + + size_t poff; +}; + +struct smtp_command_parser { + struct istream *input; + + struct smtp_command_limits limits; + + const unsigned char *cur, *end; + buffer_t *line_buffer; + struct istream *data; + + struct smtp_command_parser_state_data state; + + enum smtp_command_parse_error error_code; + char *error; + + bool auth_response:1; +}; + +static inline void ATTR_FORMAT(3, 4) +smtp_command_parser_error(struct smtp_command_parser *parser, + enum smtp_command_parse_error code, + const char *format, ...) +{ + va_list args; + + parser->state.state = SMTP_COMMAND_PARSE_STATE_ERROR; + + i_free(parser->error); + parser->error_code = code; + + va_start(args, format); + parser->error = i_strdup_vprintf(format, args); + va_end(args); +} + +struct smtp_command_parser * +smtp_command_parser_init(struct istream *input, + const struct smtp_command_limits *limits) +{ + struct smtp_command_parser *parser; + + parser = i_new(struct smtp_command_parser, 1); + parser->input = input; + i_stream_ref(input); + + if (limits != NULL) + parser->limits = *limits; + if (parser->limits.max_parameters_size == 0) { + parser->limits.max_parameters_size = + SMTP_COMMAND_DEFAULT_MAX_PARAMETERS_SIZE; + } + if (parser->limits.max_auth_size == 0) { + parser->limits.max_auth_size = + SMTP_COMMAND_DEFAULT_MAX_AUTH_SIZE; + } + if (parser->limits.max_data_size == 0) { + parser->limits.max_data_size = + SMTP_COMMAND_DEFAULT_MAX_DATA_SIZE; + } + + return parser; +} + +void smtp_command_parser_deinit(struct smtp_command_parser **_parser) +{ + struct smtp_command_parser *parser = *_parser; + + i_stream_unref(&parser->data); + buffer_free(&parser->line_buffer); + i_free(parser->state.cmd_name); + i_free(parser->state.cmd_params); + i_free(parser->error); + i_stream_unref(&parser->input); + i_free(parser); + *_parser = NULL; +} + +static void smtp_command_parser_restart(struct smtp_command_parser *parser) +{ + buffer_free(&parser->line_buffer); + i_free(parser->state.cmd_name); + i_free(parser->state.cmd_params); + + i_zero(&parser->state); +} + +void smtp_command_parser_set_stream(struct smtp_command_parser *parser, + struct istream *input) +{ + i_stream_unref(&parser->input); + if (input != NULL) { + parser->input = input; + i_stream_ref(parser->input); + } +} + +static inline const char *_chr_sanitize(unsigned char c) +{ + if (c >= 0x20 && c < 0x7F) + return t_strdup_printf("`%c'", c); + if (c == 0x0a) + return ""; + if (c == 0x0d) + return ""; + return t_strdup_printf("<0x%02x>", c); +} + +static int smtp_command_parse_identifier(struct smtp_command_parser *parser) +{ + const unsigned char *p; + + /* The commands themselves are alphabetic characters. + */ + p = parser->cur + parser->state.poff; + i_assert(p <= parser->end); + while (p < parser->end && i_isalpha(*p)) + p++; + if ((p - parser->cur) > SMTP_COMMAND_PARSER_MAX_COMMAND_LENGTH) { + smtp_command_parser_error( + parser, SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + "Command name is too long"); + return -1; + } + parser->state.poff = p - parser->cur; + if (p == parser->end) + return 0; + parser->state.cmd_name = i_strdup_until(parser->cur, p); + parser->cur = p; + parser->state.poff = 0; + return 1; +} + +static int smtp_command_parse_parameters(struct smtp_command_parser *parser) +{ + const unsigned char *p, *mp; + size_t max_size = (parser->auth_response ? + parser->limits.max_auth_size : + parser->limits.max_parameters_size); + size_t buf_size = (parser->line_buffer == NULL ? + 0 : parser->line_buffer->used); + int nch = 1; + + i_assert(max_size == 0 || buf_size <= max_size); + if (max_size > 0 && buf_size == max_size) { + smtp_command_parser_error( + parser, SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG, + "%s line is too long", + (parser->auth_response ? "AUTH response" : "Command")); + return -1; + } + + /* We assume parameters to match textstr (HT, SP, Printable US-ASCII). + For command parameters, we also accept valid UTF-8 characters. + */ + p = parser->cur + parser->state.poff; + while (p < parser->end) { + unichar_t ch; + + if (parser->auth_response) + ch = *p; + else { + nch = uni_utf8_get_char_n(p, (size_t)(parser->end - p), + &ch); + } + if (nch == 0) + break; + if (nch < 0) { + smtp_command_parser_error( + parser, SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + "Invalid UTF-8 character in command parameters"); + return -1; + } + if (nch == 1 && !smtp_char_is_textstr((unsigned char)ch)) + break; + p += nch; + } + if (max_size > 0 && (size_t)(p - parser->cur) > (max_size - buf_size)) { + smtp_command_parser_error( + parser, SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG, + "%s line is too long", + (parser->auth_response ? "AUTH response" : "Command")); + return -1; + } + parser->state.poff = p - parser->cur; + if (p == parser->end || nch == 0) { + /* Parsed up to end of what is currently buffered in the input + stream. */ + unsigned int ch_size = (p == parser->end ? + 0 : uni_utf8_char_bytes(*p)); + size_t max_input = i_stream_get_max_buffer_size(parser->input); + + /* Move parsed data to parser's line buffer if the input stream + buffer is full. This can happen when the parser's limits + exceed the input stream max buffer size. */ + if ((parser->state.poff + ch_size) >= max_input) { + if (parser->line_buffer == NULL) { + buf_size = (max_input < SIZE_MAX / 2 ? + max_input * 2 : SIZE_MAX); + buf_size = I_MAX(buf_size, 2048); + buf_size = I_MIN(buf_size, max_size); + + parser->line_buffer = buffer_create_dynamic( + default_pool, buf_size); + } + buffer_append(parser->line_buffer, parser->cur, + (p - parser->cur)); + + parser->cur = p; + parser->state.poff = 0; + } + return 0; + } + + /* In the interest of improved interoperability, SMTP receivers SHOULD + tolerate trailing white space before the terminating . + + WSP = SP / HTAB ; white space + + --> Trim the end of the buffer + */ + mp = p; + if (mp > parser->cur) { + while (mp > parser->cur && (*(mp-1) == ' ' || *(mp-1) == '\t')) + mp--; + } + + if (!parser->auth_response && mp > parser->cur && *parser->cur == ' ') { + smtp_command_parser_error( + parser, SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + "Duplicate space after command name"); + return -1; + } + + if (parser->line_buffer == NULL) { + /* Buffered only in input stream */ + parser->state.cmd_params = i_strdup_until(parser->cur, mp); + } else { + /* Buffered also in the parser */ + buffer_append(parser->line_buffer, parser->cur, + (mp - parser->cur)); + parser->state.cmd_params = + buffer_free_without_data(&parser->line_buffer); + } + parser->cur = p; + parser->state.poff = 0; + return 1; +} + +static int smtp_command_parse_line(struct smtp_command_parser *parser) +{ + int ret; + + /* RFC 5321, Section 4.1.1: + + SMTP commands are character strings terminated by . The + commands themselves are alphabetic characters terminated by if + parameters follow and otherwise. (In the interest of improved + interoperability, SMTP receivers SHOULD tolerate trailing white space + before the terminating .) + */ + for (;;) { + switch (parser->state.state) { + case SMTP_COMMAND_PARSE_STATE_INIT: + smtp_command_parser_restart(parser); + if (parser->auth_response) { + /* Parse AUTH response as bare parameters */ + parser->state.state = + SMTP_COMMAND_PARSE_STATE_PARAMETERS; + } else { + parser->state.state = + SMTP_COMMAND_PARSE_STATE_COMMAND; + } + if (parser->cur == parser->end) + return 0; + if (parser->auth_response) + break; + /* fall through */ + case SMTP_COMMAND_PARSE_STATE_COMMAND: + ret = smtp_command_parse_identifier(parser); + if (ret <= 0) + return ret; + parser->state.state = SMTP_COMMAND_PARSE_STATE_SP; + if (parser->cur == parser->end) + return 0; + /* fall through */ + case SMTP_COMMAND_PARSE_STATE_SP: + if (*parser->cur == '\r') { + parser->state.state = + SMTP_COMMAND_PARSE_STATE_CR; + break; + } else if (*parser->cur == '\n') { + parser->state.state = + SMTP_COMMAND_PARSE_STATE_LF; + break; + } else if (*parser->cur != ' ') { + smtp_command_parser_error(parser, + SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + "Unexpected character %s in command name", + _chr_sanitize(*parser->cur)); + return -1; + } + parser->cur++; + parser->state.state = + SMTP_COMMAND_PARSE_STATE_PARAMETERS; + if (parser->cur >= parser->end) + return 0; + /* fall through */ + case SMTP_COMMAND_PARSE_STATE_PARAMETERS: + ret = smtp_command_parse_parameters(parser); + if (ret <= 0) + return ret; + parser->state.state = SMTP_COMMAND_PARSE_STATE_CR; + if (parser->cur == parser->end) + return 0; + /* fall through */ + case SMTP_COMMAND_PARSE_STATE_CR: + if (*parser->cur == '\r') { + parser->cur++; + } else if (*parser->cur != '\n') { + smtp_command_parser_error(parser, + SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + "Unexpected character %s in %s", + _chr_sanitize(*parser->cur), + (parser->auth_response ? + "AUTH response" : + "command parameters")); + return -1; + } + parser->state.state = SMTP_COMMAND_PARSE_STATE_LF; + if (parser->cur == parser->end) + return 0; + /* fall through */ + case SMTP_COMMAND_PARSE_STATE_LF: + if (*parser->cur != '\n') { + smtp_command_parser_error(parser, + SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + "Expected LF after CR at end of %s, " + "but found %s", + (parser->auth_response ? + "AUTH response" : "command"), + _chr_sanitize(*parser->cur)); + return -1; + } + parser->cur++; + parser->state.state = SMTP_COMMAND_PARSE_STATE_INIT; + return 1; + case SMTP_COMMAND_PARSE_STATE_ERROR: + /* Skip until end of line */ + while (parser->cur < parser->end && + *parser->cur != '\n') + parser->cur++; + if (parser->cur == parser->end) + return 0; + parser->cur++; + parser->state.state = SMTP_COMMAND_PARSE_STATE_INIT; + break; + default: + i_unreached(); + } + } + + i_unreached(); + return -1; +} + +static int smtp_command_parse(struct smtp_command_parser *parser) +{ + const unsigned char *begin; + size_t size, old_bytes = 0; + int ret; + + while ((ret = i_stream_read_data(parser->input, &begin, &size, + old_bytes)) > 0) { + parser->cur = begin; + parser->end = parser->cur + size; + + ret = smtp_command_parse_line(parser); + i_stream_skip(parser->input, parser->cur - begin); + if (ret != 0) + return ret; + old_bytes = i_stream_get_data_size(parser->input); + } + i_assert(ret != -2); + + if (ret < 0) { + i_assert(parser->input->eof); + if (parser->input->stream_errno == 0) { + if (parser->state.state == + SMTP_COMMAND_PARSE_STATE_INIT) + ret = -2; + smtp_command_parser_error( + parser, SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND, + "Premature end of input"); + } else { + smtp_command_parser_error( + parser, SMTP_COMMAND_PARSE_ERROR_BROKEN_STREAM, + "%s", i_stream_get_disconnect_reason(parser->input)); + } + } + return ret; +} + +bool smtp_command_parser_pending_data(struct smtp_command_parser *parser) +{ + if (parser->data == NULL) + return FALSE; + return i_stream_have_bytes_left(parser->data); +} + +static int smtp_command_parse_finish_data(struct smtp_command_parser *parser) +{ + const unsigned char *data; + size_t size; + int ret; + + parser->error_code = SMTP_COMMAND_PARSE_ERROR_NONE; + parser->error = NULL; + + if (parser->data == NULL) + return 1; + if (parser->data->eof) { + i_stream_unref(&parser->data); + return 1; + } + + while ((ret = i_stream_read_data(parser->data, &data, &size, 0)) > 0) + i_stream_skip(parser->data, size); + if (ret == 0 || parser->data->stream_errno != 0) { + switch (parser->data->stream_errno) { + case 0: + return 0; + case EMSGSIZE: + smtp_command_parser_error( + parser, SMTP_COMMAND_PARSE_ERROR_DATA_TOO_LARGE, + "Command data too large"); + break; + default: + smtp_command_parser_error( + parser, SMTP_COMMAND_PARSE_ERROR_BROKEN_STREAM, + "%s", i_stream_get_disconnect_reason(parser->data)); + } + return -1; + } + i_stream_unref(&parser->data); + return 1; +} + +int smtp_command_parse_next(struct smtp_command_parser *parser, + const char **cmd_name_r, const char **cmd_params_r, + enum smtp_command_parse_error *error_code_r, + const char **error_r) +{ + int ret; + + i_assert(!parser->auth_response || + parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT || + parser->state.state == SMTP_COMMAND_PARSE_STATE_ERROR); + parser->auth_response = FALSE; + + *error_code_r = parser->error_code = SMTP_COMMAND_PARSE_ERROR_NONE; + *error_r = NULL; + + i_free_and_null(parser->error); + + /* Make sure we finished streaming payload from previous command + before we continue. */ + ret = smtp_command_parse_finish_data(parser); + if (ret <= 0) { + if (ret < 0) { + *error_code_r = parser->error_code; + *error_r = parser->error; + } + return ret; + } + + ret = smtp_command_parse(parser); + if (ret <= 0) { + if (ret < 0) { + *error_code_r = parser->error_code; + *error_r = parser->error; + parser->state.state = SMTP_COMMAND_PARSE_STATE_ERROR; + } + return ret; + } + + i_assert(parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT); + *cmd_name_r = parser->state.cmd_name; + *cmd_params_r = (parser->state.cmd_params == NULL ? + "" : parser->state.cmd_params); + return 1; +} + +struct istream * +smtp_command_parse_data_with_size(struct smtp_command_parser *parser, + uoff_t size) +{ + i_assert(parser->data == NULL); + if (size > parser->limits.max_data_size) { + /* Not supposed to happen; command should check size */ + parser->data = i_stream_create_error_str(EMSGSIZE, + "Command data size exceeds maximum " + "(%"PRIuUOFF_T" > %"PRIuUOFF_T")", + size, parser->limits.max_data_size); + } else { + // FIXME: Make exact_size stream type + struct istream *limit_input = + i_stream_create_limit(parser->input, size); + parser->data = i_stream_create_min_sized(limit_input, size); + i_stream_unref(&limit_input); + } + i_stream_ref(parser->data); + return parser->data; +} + +struct istream * +smtp_command_parse_data_with_dot(struct smtp_command_parser *parser) +{ + struct istream *data; + i_assert(parser->data == NULL); + + data = i_stream_create_dot(parser->input, TRUE); + if (parser->limits.max_data_size != UOFF_T_MAX) { + parser->data = i_stream_create_failure_at( + data, parser->limits.max_data_size, EMSGSIZE, + t_strdup_printf("Command data size exceeds maximum " + "(> %"PRIuUOFF_T")", + parser->limits.max_data_size)); + i_stream_unref(&data); + } else { + parser->data = data; + } + i_stream_ref(parser->data); + return parser->data; +} + +int smtp_command_parse_auth_response( + struct smtp_command_parser *parser, const char **line_r, + enum smtp_command_parse_error *error_code_r, const char **error_r) +{ + int ret; + + i_assert(parser->auth_response || + parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT || + parser->state.state == SMTP_COMMAND_PARSE_STATE_ERROR); + parser->auth_response = TRUE; + + *error_code_r = parser->error_code = SMTP_COMMAND_PARSE_ERROR_NONE; + *error_r = NULL; + + i_free_and_null(parser->error); + + /* Make sure we finished streaming payload from previous command + before we continue. */ + ret = smtp_command_parse_finish_data(parser); + if (ret <= 0) { + if (ret < 0) { + *error_code_r = parser->error_code; + *error_r = parser->error; + } + return ret; + } + + ret = smtp_command_parse(parser); + if (ret <= 0) { + if (ret < 0) { + *error_code_r = parser->error_code; + *error_r = parser->error; + parser->state.state = SMTP_COMMAND_PARSE_STATE_ERROR; + } + return ret; + } + + i_assert(parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT); + *line_r = parser->state.cmd_params; + parser->auth_response = FALSE; + return 1; +} diff --git a/src/lib-smtp/smtp-command-parser.h b/src/lib-smtp/smtp-command-parser.h new file mode 100644 index 0000000..c9b56a1 --- /dev/null +++ b/src/lib-smtp/smtp-command-parser.h @@ -0,0 +1,51 @@ +#ifndef SMTP_COMMAND_PARSER_H +#define SMTP_COMMAND_PARSER_H + +#include "smtp-command.h" + +/* FIXME: drop unused */ +enum smtp_command_parse_error { + /* No error */ + SMTP_COMMAND_PARSE_ERROR_NONE = 0, + /* Stream error */ + SMTP_COMMAND_PARSE_ERROR_BROKEN_STREAM, + /* Unrecoverable generic error */ + SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND, + /* Recoverable generic error */ + SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + /* Stream error */ + SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG, + /* Data too large (fatal) */ + SMTP_COMMAND_PARSE_ERROR_DATA_TOO_LARGE +}; + +struct smtp_command_parser * +smtp_command_parser_init(struct istream *input, + const struct smtp_command_limits *limits) ATTR_NULL(2); +void smtp_command_parser_deinit(struct smtp_command_parser **_parser); + +void smtp_command_parser_set_stream(struct smtp_command_parser *parser, + struct istream *input); + +/* Returns 1 if a command was returned, 0 if more data is needed, -1 on error, + -2 if disconnected in SMTP_COMMAND_PARSE_STATE_INIT state. -2 is mainly for + unit tests - it can normally be treated the same as -1. */ +int smtp_command_parse_next(struct smtp_command_parser *parser, + const char **cmd_name_r, const char **cmd_params_r, + enum smtp_command_parse_error *error_code_r, + const char **error_r); + +struct istream * +smtp_command_parse_data_with_size(struct smtp_command_parser *parser, + uoff_t size); +struct istream * +smtp_command_parse_data_with_dot(struct smtp_command_parser *parser); +bool smtp_command_parser_pending_data(struct smtp_command_parser *parser); + +/* Returns the same as smtp_command_parse_next() */ +int smtp_command_parse_auth_response(struct smtp_command_parser *parser, + const char **line_r, + enum smtp_command_parse_error *error_code_r, + const char **error_r); + +#endif diff --git a/src/lib-smtp/smtp-command.h b/src/lib-smtp/smtp-command.h new file mode 100644 index 0000000..94f2251 --- /dev/null +++ b/src/lib-smtp/smtp-command.h @@ -0,0 +1,38 @@ +#ifndef SMTP_COMMAND_H +#define SMTP_COMMAND_H + +#define SMTP_COMMAND_DEFAULT_MAX_PARAMETERS_SIZE 4*1024 +#define SMTP_COMMAND_DEFAULT_MAX_AUTH_SIZE 8*1024 +#define SMTP_COMMAND_DEFAULT_MAX_DATA_SIZE 40*1024*1024 + +struct smtp_command_limits { + /* Maximum size of command parameters, starting after first space */ + size_t max_parameters_size; + /* Maximum size of authentication response */ + size_t max_auth_size; + /* Absolute maximum size of command data, beyond which the parser yields + a fatal error; i.e. closing the connection in the server. This should + be higher than a normal message size limit, which would return a + normal informative error. The limit here just serves to protect + against abuse. */ + uoff_t max_data_size; +}; + +struct smtp_command { + const char *name; + const char *parameters; +}; + +static inline void +smtp_command_limits_merge(struct smtp_command_limits *limits, + const struct smtp_command_limits *new_limits) +{ + if (new_limits->max_parameters_size > 0) + limits->max_parameters_size = new_limits->max_parameters_size; + if (new_limits->max_auth_size > 0) + limits->max_auth_size = new_limits->max_auth_size; + if (new_limits->max_data_size > 0) + limits->max_data_size = new_limits->max_data_size; +} + +#endif diff --git a/src/lib-smtp/smtp-common.c b/src/lib-smtp/smtp-common.c new file mode 100644 index 0000000..1771169 --- /dev/null +++ b/src/lib-smtp/smtp-common.c @@ -0,0 +1,91 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "smtp-common.h" + +/* + * Capabilities + */ + +const struct smtp_capability_name smtp_capability_names[] = { + { "AUTH", SMTP_CAPABILITY_AUTH }, + { "STARTTLS", SMTP_CAPABILITY_STARTTLS }, + { "PIPELINING", SMTP_CAPABILITY_PIPELINING }, + { "SIZE", SMTP_CAPABILITY_SIZE }, + { "ENHANCEDSTATUSCODES", SMTP_CAPABILITY_ENHANCEDSTATUSCODES }, + { "8BITMIME", SMTP_CAPABILITY_8BITMIME }, + { "CHUNKING", SMTP_CAPABILITY_CHUNKING }, + { "BINARYMIME", SMTP_CAPABILITY_BINARYMIME }, + { "BURL", SMTP_CAPABILITY_BURL }, + { "DSN", SMTP_CAPABILITY_DSN }, + { "VRFY", SMTP_CAPABILITY_VRFY }, + { "ETRN", SMTP_CAPABILITY_ETRN }, + { "XCLIENT", SMTP_CAPABILITY_XCLIENT }, + { NULL, 0 } +}; + +enum smtp_capability smtp_capability_find_by_name(const char *cap_name) +{ + const struct smtp_capability_name *cap; + unsigned int i; + + for (i = 0; smtp_capability_names[i].name != NULL; i++) { + cap = &smtp_capability_names[i]; + + if (strcasecmp(cap_name, cap->name) == 0) + return cap->capability; + } + + return SMTP_CAPABILITY_NONE; +} + +/* + * SMTP proxy data + */ + +static void +smtp_proxy_data_merge_extra_fields(pool_t pool, struct smtp_proxy_data *dst, + const struct smtp_proxy_data *src) +{ + const struct smtp_proxy_data_field *sefields; + struct smtp_proxy_data_field *defields; + unsigned int i; + + if (src->extra_fields_count == 0) + return; + + sefields = src->extra_fields; + defields = p_new(pool, struct smtp_proxy_data_field, + src->extra_fields_count); + for (i = 0; i < src->extra_fields_count; i++) { + defields[i].name = p_strdup(pool, sefields[i].name); + defields[i].value = p_strdup(pool, sefields[i].value); + } + + dst->extra_fields = defields; + dst->extra_fields_count = src->extra_fields_count; +} + +void smtp_proxy_data_merge(pool_t pool, struct smtp_proxy_data *dst, + const struct smtp_proxy_data *src) +{ + if (src->proto != SMTP_PROXY_PROTOCOL_UNKNOWN) + dst->proto = src->proto; + if (src->source_ip.family != 0) { + dst->source_ip = src->source_ip; + if (src->source_port != 0) + dst->source_port = src->source_port; + } + if (src->helo != NULL && *src->helo != '\0') + dst->helo = p_strdup(pool, src->helo); + if (src->login != NULL && *src->login != '\0') + dst->login = p_strdup(pool, src->login); + if (src->session != NULL && *src->session != '\0') + dst->session = p_strdup(pool, src->session); + if (src->ttl_plus_1 > 0) + dst->ttl_plus_1 = src->ttl_plus_1; + if (src->timeout_secs > 0) + dst->timeout_secs = src->timeout_secs; + + smtp_proxy_data_merge_extra_fields(pool, dst, src); +}; diff --git a/src/lib-smtp/smtp-common.h b/src/lib-smtp/smtp-common.h new file mode 100644 index 0000000..ec95dc0 --- /dev/null +++ b/src/lib-smtp/smtp-common.h @@ -0,0 +1,119 @@ +#ifndef SMTP_COMMON_H +#define SMTP_COMMON_H + +#include "net.h" + +/* + * Limits + */ + +#define SMTP_BASE_LINE_LENGTH_LIMIT (512 - 2) + +/* + * SMTP protocols + */ + +enum smtp_protocol { + SMTP_PROTOCOL_SMTP = 0, + SMTP_PROTOCOL_LMTP +}; + +static inline const char * +smtp_protocol_name(enum smtp_protocol proto) +{ + switch (proto) { + case SMTP_PROTOCOL_SMTP: + return "smtp"; + case SMTP_PROTOCOL_LMTP: + return "lmtp"; + default: + break; + } + i_unreached(); +} + +/* SMTP capabilities */ + +enum smtp_capability { + SMTP_CAPABILITY_NONE = 0, + + SMTP_CAPABILITY_AUTH = BIT(0), + SMTP_CAPABILITY_STARTTLS = BIT(1), + SMTP_CAPABILITY_PIPELINING = BIT(2), + SMTP_CAPABILITY_SIZE = BIT(3), + SMTP_CAPABILITY_ENHANCEDSTATUSCODES = BIT(4), + SMTP_CAPABILITY_8BITMIME = BIT(5), + SMTP_CAPABILITY_CHUNKING = BIT(6), + SMTP_CAPABILITY_BINARYMIME = BIT(7), + SMTP_CAPABILITY_BURL = BIT(8), + SMTP_CAPABILITY_DSN = BIT(9), + SMTP_CAPABILITY_VRFY = BIT(10), + SMTP_CAPABILITY_ETRN = BIT(11), + SMTP_CAPABILITY_XCLIENT = BIT(12), + + SMTP_CAPABILITY__ORCPT = BIT(24), +}; + +struct smtp_capability_name { + const char *name; + enum smtp_capability capability; +}; + +struct smtp_capability_extra { + const char *name; + const char *const *params; +}; + +extern const struct smtp_capability_name smtp_capability_names[]; + +enum smtp_capability smtp_capability_find_by_name(const char *cap_name); + +/* + * SMTP proxy data + */ + +enum smtp_proxy_protocol { + SMTP_PROXY_PROTOCOL_UNKNOWN = 0, + SMTP_PROXY_PROTOCOL_SMTP, + SMTP_PROXY_PROTOCOL_ESMTP, + SMTP_PROXY_PROTOCOL_LMTP +}; + +struct smtp_proxy_data_field { + const char *name; + const char *value; +}; +ARRAY_DEFINE_TYPE(smtp_proxy_data_field, struct smtp_proxy_data_field); + +struct smtp_proxy_data { + /* PROTO */ + enum smtp_proxy_protocol proto; + /* ADDR */ + struct ip_addr source_ip; + /* PORT */ + in_port_t source_port; + /* HELO, LOGIN */ + const char *helo, *login; + /* SESSION */ + const char *session; + + /* TTL: send as this -1, so the default 0 means "don't send it" */ + unsigned int ttl_plus_1; + /* TIMEOUT: remote is notified that the connection is going to be closed + after this many seconds, so it should try to keep lock waits and such + lower than this. */ + unsigned int timeout_secs; + + /* additional fields */ + const struct smtp_proxy_data_field *extra_fields; + unsigned int extra_fields_count; +}; + +/* + * SMTP proxy data + */ + +void smtp_proxy_data_merge(pool_t pool, struct smtp_proxy_data *dst, + const struct smtp_proxy_data *src); + +#endif diff --git a/src/lib-smtp/smtp-params.c b/src/lib-smtp/smtp-params.c new file mode 100644 index 0000000..3994ce5 --- /dev/null +++ b/src/lib-smtp/smtp-params.c @@ -0,0 +1,1375 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "message-address.h" +#include "smtp-common.h" +#include "smtp-parser.h" +#include "smtp-syntax.h" +#include "smtp-address.h" + +#include "smtp-params.h" + +#include + +/* + * Common + */ + +/* parse */ + +static int +smtp_param_do_parse(struct smtp_parser *parser, struct smtp_param *param_r) +{ + const unsigned char *pbegin = parser->cur; + + /* esmtp-param = esmtp-keyword ["=" esmtp-value] + esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") + esmtp-value = 1*(%d33-60 / %d62-126) + ; any CHAR excluding "=", SP, and control + ; characters. If this string is an email address, + ; i.e., a Mailbox, then the "xtext" syntax [32] + ; SHOULD be used. + */ + + if (parser->cur >= parser->end || !i_isalnum(*parser->cur)) { + parser->error = "Unexpected character in parameter keyword"; + return -1; + } + parser->cur++; + + while (parser->cur < parser->end && + (i_isalnum(*parser->cur) || *parser->cur == '-')) + parser->cur++; + param_r->keyword = t_strndup(pbegin, parser->cur - pbegin); + + if (parser->cur >= parser->end) { + param_r->value = NULL; + return 1; + } + if (*parser->cur != '=') { + parser->error = "Unexpected character in parameter keyword"; + return -1; + } + parser->cur++; + + pbegin = parser->cur; + while (parser->cur < parser->end && + smtp_char_is_esmtp_value(*parser->cur)) + parser->cur++; + + if (parser->cur < parser->end) { + parser->error = "Unexpected character in parameter value"; + return -1; + } + param_r->value = t_strndup(pbegin, parser->cur - pbegin); + return 1; +} + +int smtp_param_parse(pool_t pool, const char *text, + struct smtp_param *param_r, const char **error_r) +{ + struct smtp_parser parser; + + i_zero(param_r); + + if (text == NULL || *text == '\0') { + if (error_r != NULL) + *error_r = "Parameter is empty"; + return -1; + } + + smtp_parser_init(&parser, pool, text); + + if (smtp_param_do_parse(&parser, param_r) <= 0) { + if (error_r != NULL) + *error_r = parser.error; + return -1; + } + return 1; +} + +/* manipulate */ + +void smtp_params_copy(pool_t pool, ARRAY_TYPE(smtp_param) *dst, + const ARRAY_TYPE(smtp_param) *src) +{ + const struct smtp_param *param; + + if (!array_is_created(src)) + return; + + p_array_init(dst, pool, array_count(src)); + array_foreach(src, param) { + struct smtp_param param_new; + + param_new.keyword = p_strdup(pool, param->keyword); + param_new.value = p_strdup(pool, param->value); + array_push_back(dst, ¶m_new); + } +} + +void smtp_params_add_one(ARRAY_TYPE(smtp_param) *params, pool_t pool, + const char *keyword, const char *value) +{ + struct smtp_param param; + + if (!array_is_created(params)) + p_array_init(params, pool, 4); + + i_zero(¶m); + param.keyword = p_strdup(pool, keyword); + param.value = p_strdup(pool, value); + array_push_back(params, ¶m); +} + +void smtp_params_add_encoded(ARRAY_TYPE(smtp_param) *params, pool_t pool, + const char *keyword, const unsigned char *value, + size_t value_len) +{ + string_t *value_enc = t_str_new(value_len * 2); + + smtp_xtext_encode(value_enc, value, value_len); + smtp_params_add_one(params, pool, keyword, str_c(value_enc)); +} + +bool smtp_params_drop_one(ARRAY_TYPE(smtp_param) *params, const char *keyword, + const char **value_r) +{ + const struct smtp_param *param; + + if (!array_is_created(params)) + return FALSE; + + array_foreach(params, param) { + if (strcasecmp(param->keyword, keyword) == 0) { + if (value_r != NULL) + *value_r = param->value; + array_delete(params, + array_foreach_idx(params, param), 1); + return TRUE; + } + } + return FALSE; +} + +/* write */ + +static bool smtp_param_value_valid(const char *value) +{ + const char *p = value; + + while (*p != '\0' && smtp_char_is_esmtp_value(*p)) + p++; + return (*p == '\0'); +} + +void smtp_param_write(string_t *out, const struct smtp_param *param) +{ + str_append(out, t_str_ucase(param->keyword)); + if (param->value != NULL) { + i_assert(smtp_param_value_valid(param->value)); + str_append_c(out, '='); + str_append(out, param->value); + } +} + +static void +smtp_params_write(string_t *buffer, const char *const *param_keywords, + const ARRAY_TYPE(smtp_param) *params) ATTR_NULL(2) +{ + const struct smtp_param *param; + + if (param_keywords == NULL || *param_keywords == NULL) + return; + if (!array_is_created(params)) + return; + + array_foreach(params, param) { + if (str_array_icase_find(param_keywords, param->keyword)) + smtp_param_write(buffer, param); + str_append_c(buffer, ' '); + } +} + +/* evaluate */ + +const struct smtp_param * +smtp_params_get_param(const ARRAY_TYPE(smtp_param) *params, + const char *keyword) +{ + const struct smtp_param *param; + + if (!array_is_created(params)) + return NULL; + + array_foreach(params, param) { + if (strcasecmp(param->keyword, keyword) == 0) + return param; + } + return NULL; +} + +int smtp_params_decode_param(const ARRAY_TYPE(smtp_param) *params, + const char *keyword, string_t **value_r, + bool allow_nul, const char **error_r) +{ + const struct smtp_param *param; + + param = smtp_params_get_param(params, keyword); + if (param == NULL) + return 0; + + *value_r = t_str_new(strlen(param->value) * 2); + if (smtp_xtext_decode(*value_r, param->value, allow_nul, error_r) <= 0) + return -1; + return 1; +} + +bool smtp_params_equal(const ARRAY_TYPE(smtp_param) *params1, + const ARRAY_TYPE(smtp_param) *params2) +{ + const struct smtp_param *param1, *param2; + + if (!array_is_created(params1) || array_count(params1) == 0) { + return (!array_is_created(params2) || + array_count(params2) == 0); + } + if (!array_is_created(params2) || array_count(params2) == 0) + return FALSE; + + if (array_count(params1) != array_count(params2)) + return FALSE; + + array_foreach(params1, param1) { + param2 = smtp_params_get_param(params2, param1->keyword); + if (param2 == NULL) + return FALSE; + if (null_strcmp(param1->value, param2->value) != 0) + return FALSE; + } + return TRUE; +} + +/* + * MAIL parameters + */ + +/* parse */ + +struct smtp_params_mail_parser { + pool_t pool; + struct smtp_params_mail *params; + enum smtp_capability caps; + + enum smtp_param_parse_error error_code; + const char *error; +}; + +static int +smtp_params_mail_parse_auth(struct smtp_params_mail_parser *pmparser, + const char *xtext) +{ + struct smtp_params_mail *params = pmparser->params; + struct smtp_address *auth_addr; + const char *value, *error; + + /* AUTH=: RFC 4954, Section 5 + + We ignore this parameter, but we do check it for validity + */ + + /* cannot specify this multiple times */ + if (params->auth != NULL) { + pmparser->error = "Duplicate AUTH= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (xtext == NULL) { + pmparser->error = "Missing AUTH= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + if (smtp_xtext_parse(xtext, &value, &error) < 0) { + pmparser->error = t_strdup_printf( + "Invalid AUTH= parameter value: %s", error); + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + if (strcmp(value, "<>") == 0) { + params->auth = p_new(pmparser->pool, struct smtp_address, 1); + } else if (smtp_address_parse_mailbox( + pmparser->pool, value, + SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART, + &auth_addr, &error) < 0) { + pmparser->error = t_strdup_printf( + "Invalid AUTH= address value: %s", error); + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } else { + params->auth = auth_addr; + } + /* ignore, our own AUTH data is added below */ + return 0; +} + +static int +smtp_params_mail_parse_body(struct smtp_params_mail_parser *pmparser, + const char *value, const char *const *extensions) +{ + struct smtp_params_mail *params = pmparser->params; + enum smtp_capability caps = pmparser->caps; + + /* BODY=: RFC 6152 */ + + /* cannot specify this multiple times */ + if (params->body.type != SMTP_PARAM_MAIL_BODY_TYPE_UNSPECIFIED) { + pmparser->error = "Duplicate BODY= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + pmparser->error = "Missing BODY= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + value = t_str_ucase(value); + params->body.ext = NULL; + /* =7BIT: RFC 6152 */ + if (strcmp(value, "7BIT") == 0) { + params->body.type = SMTP_PARAM_MAIL_BODY_TYPE_7BIT; + /* =8BITMIME: RFC 6152 */ + } else if ((caps & SMTP_CAPABILITY_8BITMIME) != 0 && + strcmp(value, "8BITMIME") == 0) { + params->body.type = SMTP_PARAM_MAIL_BODY_TYPE_8BITMIME; + /* =BINARYMIME: RFC 3030 */ + } else if ((caps & SMTP_CAPABILITY_BINARYMIME) != 0 && + (caps & SMTP_CAPABILITY_CHUNKING) != 0 && + strcmp(value, "BINARYMIME") == 0) { + params->body.type = SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME; + /* =?? */ + } else if (extensions != NULL && + str_array_icase_find(extensions, value)) { + params->body.type = SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION; + params->body.ext = p_strdup(pmparser->pool, value); + } else { + pmparser->error = "Unsupported mail BODY type"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + return 0; +} + +static int +smtp_params_mail_parse_envid(struct smtp_params_mail_parser *pmparser, + const char *xtext) +{ + struct smtp_params_mail *params = pmparser->params; + const unsigned char *p, *pend; + const char *envid, *error; + + /* ENVID=: RFC 3461 */ + + /* cannot specify this multiple times */ + if (params->envid != NULL) { + pmparser->error = "Duplicate ENVID= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (xtext == NULL) { + pmparser->error = "Missing ENVID= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* check xtext */ + if (smtp_xtext_parse(xtext, &envid, &error) < 0) { + pmparser->error = t_strdup_printf( + "Invalid ENVID= parameter value: %s", error); + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* RFC 3461, Section 4.4: + + Due to limitations in the Delivery Status Notification format, the + value of the ENVID parameter prior to encoding as "xtext" MUST + consist entirely of printable (graphic and white space) characters + from the US-ASCII repertoire. + */ + p = (const unsigned char *)envid; + pend = p + strlen(envid); + while (p < pend && smtp_char_is_textstr(*p)) + p++; + if (p < pend) { + pmparser->error = + "Invalid ENVID= parameter value: " + "Contains non-printable characters"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + params->envid = p_strdup(pmparser->pool, envid); + return 0; +} + +static int +smtp_params_mail_parse_ret(struct smtp_params_mail_parser *pmparser, + const char *value) +{ + struct smtp_params_mail *params = pmparser->params; + + /* RET=: RFC 3461 */ + + /* cannot specify this multiple times */ + if (params->ret != SMTP_PARAM_MAIL_RET_UNSPECIFIED) { + pmparser->error = "Duplicate RET= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + pmparser->error = "Missing RET= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + value = t_str_ucase(value); + /* =FULL */ + if (strcmp(value, "FULL") == 0) { + params->ret = SMTP_PARAM_MAIL_RET_FULL; + /* =HDRS */ + } else if (strcmp(value, "HDRS") == 0) { + params->ret = SMTP_PARAM_MAIL_RET_HDRS; + } else { + pmparser->error = "Unsupported RET= parameter keyword"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + return 0; +} + +static int +smtp_params_mail_parse_size(struct smtp_params_mail_parser *pmparser, + const char *value) +{ + struct smtp_params_mail *params = pmparser->params; + + /* SIZE=: RFC 1870 */ + + /* cannot specify this multiple times */ + if (params->size != 0) { + pmparser->error = "Duplicate SIZE= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + pmparser->error = "Missing SIZE= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + /* size-value ::= 1*20DIGIT */ + if (str_to_uoff(value, ¶ms->size) < 0) { + pmparser->error = "Unsupported SIZE parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + return 0; +} + +int smtp_params_mail_parse(pool_t pool, const char *args, + enum smtp_capability caps, + const char *const *extensions, + const char *const *body_extensions, + struct smtp_params_mail *params_r, + enum smtp_param_parse_error *error_code_r, + const char **error_r) +{ + struct smtp_params_mail_parser pmparser; + struct smtp_param param; + const char *const *argv; + const char *error; + int ret = 0; + + i_zero(params_r); + + i_zero(&pmparser); + pmparser.pool = pool; + pmparser.params = params_r; + pmparser.caps = caps; + + argv = t_strsplit(args, " "); + for (; *argv != NULL; argv++) { + if (smtp_param_parse(pool_datastack_create(), *argv, + ¶m, &error) < 0) { + *error_r = t_strdup_printf( + "Invalid MAIL parameter: %s", error); + *error_code_r = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + /* parse known parameters */ + param.keyword = t_str_ucase(param.keyword); + if ((caps & SMTP_CAPABILITY_AUTH) != 0 && + strcmp(param.keyword, "AUTH") == 0) { + if (smtp_params_mail_parse_auth( + &pmparser, param.value) < 0) { + ret = -1; + break; + } + } else if (strcmp(param.keyword, "BODY") == 0) { + if (smtp_params_mail_parse_body(&pmparser, param.value, + body_extensions) < 0) { + ret = -1; + break; + } + } else if ((caps & SMTP_CAPABILITY_DSN) != 0 && + strcmp(param.keyword, "ENVID") == 0) { + if (smtp_params_mail_parse_envid(&pmparser, + param.value) < 0) { + ret = -1; + break; + } + } else if ((caps & SMTP_CAPABILITY_DSN) != 0 && + strcmp(param.keyword, "RET") == 0) { + if (smtp_params_mail_parse_ret(&pmparser, + param.value) < 0) { + ret = -1; + break; + } + } else if ((caps & SMTP_CAPABILITY_SIZE) != 0 && + strcmp(param.keyword, "SIZE") == 0) { + if (smtp_params_mail_parse_size(&pmparser, + param.value) < 0) { + ret = -1; + break; + } + } else if (extensions != NULL && + str_array_icase_find(extensions, param.keyword)) { + /* add the rest to ext_param for specific + applications */ + smtp_params_mail_add_extra(params_r, pool, + param.keyword, param.value); + } else { + /* RFC 5321, Section 4.1.1.11: + If the server SMTP does not recognize or cannot + implement one or more of the parameters associated + with a particular MAIL FROM or RCPT TO command, it + will return code 555. */ + *error_r = "Unsupported parameters"; + *error_code_r = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + } + + if (ret < 0) { + *error_r = pmparser.error; + *error_code_r = pmparser.error_code; + } + return ret; +} + +/* manipulate */ + +void smtp_params_mail_copy(pool_t pool, struct smtp_params_mail *dst, + const struct smtp_params_mail *src) +{ + i_zero(dst); + + if (src == NULL) + return; + + dst->auth = smtp_address_clone(pool, src->auth); + dst->body.type = src->body.type; + dst->body.ext = p_strdup(pool, src->body.ext); + dst->envid = p_strdup(pool, src->envid); + dst->ret = src->ret; + dst->size = src->size; + + smtp_params_copy(pool, &dst->extra_params, &src->extra_params); +} + +void smtp_params_mail_add_extra(struct smtp_params_mail *params, pool_t pool, + const char *keyword, const char *value) +{ + smtp_params_add_one(¶ms->extra_params, pool, keyword, value); +} + +void smtp_params_mail_encode_extra(struct smtp_params_mail *params, pool_t pool, + const char *keyword, + const unsigned char *value, + size_t value_len) +{ + smtp_params_add_encoded(¶ms->extra_params, pool, + keyword, value, value_len); +} + +bool smtp_params_mail_drop_extra(struct smtp_params_mail *params, + const char *keyword, const char **value_r) +{ + return smtp_params_drop_one(¶ms->extra_params, keyword, value_r); +} + +/* write */ + +static void +smtp_params_mail_write_auth(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + /* add AUTH= parameter */ + string_t *auth_addr; + + if (params->auth == NULL) + return; + if ((caps & SMTP_CAPABILITY_AUTH) == 0) + return; + + auth_addr = t_str_new(256); + + if (params->auth->localpart == NULL) + str_append(auth_addr, "<>"); + else + smtp_address_write(auth_addr, params->auth); + str_append(buffer, "AUTH="); + smtp_xtext_encode(buffer, str_data(auth_addr), str_len(auth_addr)); + str_append_c(buffer, ' '); +} + +static void +smtp_params_mail_write_body(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + /* BODY=: RFC 6152 */ + /* =7BIT: RFC 6152 */ + switch (params->body.type) { + case SMTP_PARAM_MAIL_BODY_TYPE_UNSPECIFIED: + break; + case SMTP_PARAM_MAIL_BODY_TYPE_7BIT: + str_append(buffer, "BODY=7BIT "); + break; + /* =8BITMIME: RFC 6152 */ + case SMTP_PARAM_MAIL_BODY_TYPE_8BITMIME: + i_assert((caps & SMTP_CAPABILITY_8BITMIME) != 0); + str_append(buffer, "BODY=8BITMIME "); + break; + /* =BINARYMIME: RFC 3030 */ + case SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME: + i_assert((caps & SMTP_CAPABILITY_BINARYMIME) != 0 && + (caps & SMTP_CAPABILITY_CHUNKING) != 0); + str_append(buffer, "BODY=BINARYMIME "); + break; + case SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION: + str_append(buffer, "BODY="); + str_append(buffer, params->body.ext); + str_append_c(buffer, ' '); + break; + default: + i_unreached(); + } +} + +static void +smtp_params_mail_write_envid(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + const char *envid = params->envid; + + /* ENVID=: RFC 3461 */ + + if (envid == NULL) + return; + if ((caps & SMTP_CAPABILITY_DSN) == 0) + return; + + str_append(buffer, "ENVID="); + smtp_xtext_encode(buffer, (const unsigned char *)envid, strlen(envid)); + str_append_c(buffer, ' '); +} + +static void +smtp_params_mail_write_ret(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + if ((caps & SMTP_CAPABILITY_DSN) == 0) + return; + /* RET=: RFC 3461 */ + switch (params->ret) { + case SMTP_PARAM_MAIL_RET_UNSPECIFIED: + break; + case SMTP_PARAM_MAIL_RET_HDRS: + str_append(buffer, "RET=HDRS "); + break; + case SMTP_PARAM_MAIL_RET_FULL: + str_append(buffer, "RET=FULL "); + break; + default: + i_unreached(); + } +} + +static void +smtp_params_mail_write_size(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + /* SIZE=: RFC 1870 */ + + if (params->size == 0) + return; + if ((caps & SMTP_CAPABILITY_SIZE) == 0) + return; + + /* proxy the SIZE parameter (account for additional size) */ + str_printfa(buffer, "SIZE=%"PRIuUOFF_T" ", params->size); +} + +void smtp_params_mail_write(string_t *buffer, enum smtp_capability caps, + const char *const *extra_params, + const struct smtp_params_mail *params) +{ + size_t init_len = str_len(buffer); + + smtp_params_mail_write_auth(buffer, caps, params); + smtp_params_mail_write_body(buffer, caps, params); + smtp_params_mail_write_envid(buffer, caps, params); + smtp_params_mail_write_ret(buffer, caps, params); + smtp_params_mail_write_size(buffer, caps, params); + + smtp_params_write(buffer, extra_params, ¶ms->extra_params); + + if (str_len(buffer) > init_len) + str_truncate(buffer, str_len(buffer)-1); +} + +/* evaluate */ + +const struct smtp_param * +smtp_params_mail_get_extra(const struct smtp_params_mail *params, + const char *keyword) +{ + return smtp_params_get_param(¶ms->extra_params, keyword); +} + +int smtp_params_mail_decode_extra(const struct smtp_params_mail *params, + const char *keyword, string_t **value_r, + bool allow_nul, const char **error_r) +{ + return smtp_params_decode_param(¶ms->extra_params, + keyword, value_r, allow_nul, error_r); +} + +/* events */ + +static void +smtp_params_mail_add_auth_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* AUTH: RFC 4954 */ + if (params->auth == NULL) + return; + + event_add_str(event, "mail_param_auth", + smtp_address_encode(params->auth)); +} + +static void +smtp_params_mail_add_body_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* BODY: RFC 6152 */ + switch (params->body.type) { + case SMTP_PARAM_MAIL_BODY_TYPE_UNSPECIFIED: + break; + case SMTP_PARAM_MAIL_BODY_TYPE_7BIT: + event_add_str(event, "mail_param_body", "7BIT"); + break; + case SMTP_PARAM_MAIL_BODY_TYPE_8BITMIME: + event_add_str(event, "mail_param_body", "8BITMIME"); + break; + case SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME: + event_add_str(event, "mail_param_body", "BINARYMIME"); + break; + case SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION: + event_add_str(event, "mail_param_body", params->body.ext); + break; + default: + i_unreached(); + } +} + +static void +smtp_params_mail_add_envid_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* ENVID: RFC 3461, Section 4.4 */ + if (params->envid == NULL) + return; + + event_add_str(event, "mail_param_envid", params->envid); +} + +static void +smtp_params_mail_add_ret_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* RET: RFC 3461, Section 4.3 */ + switch (params->ret) { + case SMTP_PARAM_MAIL_RET_UNSPECIFIED: + break; + case SMTP_PARAM_MAIL_RET_HDRS: + event_add_str(event, "mail_param_ret", "HDRS"); + break; + case SMTP_PARAM_MAIL_RET_FULL: + event_add_str(event, "mail_param_ret", "FULL"); + break; + default: + i_unreached(); + } +} + +static void +smtp_params_mail_add_size_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* SIZE: RFC 1870 */ + if (params->size == 0) + return; + + event_add_int(event, "mail_param_size", params->size); +} + +void smtp_params_mail_add_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + smtp_params_mail_add_auth_to_event(params, event); + smtp_params_mail_add_body_to_event(params, event); + smtp_params_mail_add_envid_to_event(params, event); + smtp_params_mail_add_ret_to_event(params, event); + smtp_params_mail_add_size_to_event(params, event); +} + +/* + * RCPT parameters + */ + +/* parse */ + +struct smtp_params_rcpt_parser { + pool_t pool; + struct smtp_params_rcpt *params; + enum smtp_param_rcpt_parse_flags flags; + enum smtp_capability caps; + + enum smtp_param_parse_error error_code; + const char *error; +}; + +static int +smtp_params_rcpt_parse_notify(struct smtp_params_rcpt_parser *prparser, + const char *value) +{ + struct smtp_params_rcpt *params = prparser->params; + const char *const *list; + bool valid, unsupported; + + /* NOTIFY=: RFC 3461 + + notify-esmtp-value = "NEVER" / 1#notify-list-element + notify-list-element = "SUCCESS" / "FAILURE" / "DELAY" + + We check and normalize this parameter. + */ + + /* cannot specify this multiple times */ + if (params->notify != SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) { + prparser->error = "Duplicate NOTIFY= parameter"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + prparser->error = "Missing NOTIFY= parameter value"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + valid = TRUE; + unsupported = FALSE; + list = t_strsplit(value, ","); /* RFC 822, Section 2.7 */ + while (*list != NULL) { + if (**list != '\0') { + /* NEVER */ + if (strcasecmp(*list, "NEVER") == 0) { + if (params->notify != SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) + valid = FALSE; + params->notify = SMTP_PARAM_RCPT_NOTIFY_NEVER; + /* SUCCESS */ + } else if (strcasecmp(*list, "SUCCESS") == 0) { + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) + valid = FALSE; + params->notify |= SMTP_PARAM_RCPT_NOTIFY_SUCCESS; + /* FAILURE */ + } else if (strcasecmp(*list, "FAILURE") == 0) { + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) + valid = FALSE; + params->notify |= SMTP_PARAM_RCPT_NOTIFY_FAILURE; + /* DELAY */ + } else if (strcasecmp(*list, "DELAY") == 0) { + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) + valid = FALSE; + params->notify |= SMTP_PARAM_RCPT_NOTIFY_DELAY; + } else { + unsupported = TRUE; + } + } + list++; + } + + if (!valid || unsupported || + params->notify == SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) { + prparser->error = "Invalid NOTIFY= parameter value"; + prparser->error_code = ((valid && unsupported) ? + SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED : + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX); + return -1; + } + return 0; +} + +static int +smtp_params_rcpt_parse_orcpt_rfc822(struct smtp_params_rcpt_parser *prparser, + const char *addr_str, pool_t pool, + const struct smtp_address **addr_r) +{ + struct message_address *rfc822_addr; + struct smtp_address *addr; + + rfc822_addr = message_address_parse(pool_datastack_create(), + (const unsigned char *)addr_str, + strlen(addr_str), 2, 0); + if (rfc822_addr == NULL || rfc822_addr->next != NULL) + return -1; + if (rfc822_addr->invalid_syntax) { + if (HAS_NO_BITS(prparser->flags, + SMTP_PARAM_RCPT_FLAG_ORCPT_ALLOW_LOCALPART) || + rfc822_addr->mailbox == NULL || + *rfc822_addr->mailbox == '\0') + return -1; + rfc822_addr->invalid_syntax = FALSE; + } + if (smtp_address_create_from_msg(pool, rfc822_addr, &addr) < 0) + return -1; + *addr_r = addr; + return 0; +} + +static int +smtp_params_rcpt_parse_orcpt(struct smtp_params_rcpt_parser *prparser, + const char *value) +{ + struct smtp_params_rcpt *params = prparser->params; + struct smtp_parser parser; + const unsigned char *p, *pend; + string_t *address; + const char *addr_type; + int ret; + + /* ORCPT=
: RFC 3461 + + orcpt-parameter = "ORCPT=" original-recipient-address + original-recipient-address = addr-type ";" xtext + addr-type = atom + + We check and normalize this parameter. + */ + + /* cannot specify this multiple times */ + if (params->orcpt.addr_type != NULL) { + prparser->error = "Duplicate ORCPT= parameter"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + prparser->error = "Missing ORCPT= parameter value"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + /* check addr-type */ + smtp_parser_init(&parser, pool_datastack_create(), value); + if (smtp_parser_parse_atom(&parser, &addr_type) <= 0 || + parser.cur >= parser.end || *parser.cur != ';') { + prparser->error = "Invalid addr-type for ORCPT= parameter"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + params->orcpt.addr_type = p_strdup(prparser->pool, addr_type); + parser.cur++; + + /* check xtext */ + address = t_str_new(256); + if ((ret=smtp_parser_parse_xtext(&parser, address)) <= 0 || + parser.cur < parser.end) { + if (ret < 0) { + prparser->error = t_strdup_printf( + "Invalid ORCPT= parameter: %s", + parser.error); + prparser->error_code = + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + } else if (parser.cur < parser.end) { + prparser->error = "Invalid ORCPT= parameter: " + "Invalid character in xtext"; + prparser->error_code = + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + } else { + prparser->error = "Invalid ORCPT= parameter: " + "Empty address value"; + prparser->error_code = + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + } + return -1; + } + + /* RFC 3461, Section 4.2: + + Due to limitations in the Delivery Status Notification format, the + value of the original recipient address prior to encoding as "xtext" + MUST consist entirely of printable (graphic and white space) + characters from the US-ASCII repertoire. + */ + p = str_data(address); + pend = p + str_len(address); + while (p < pend && smtp_char_is_textstr(*p)) + p++; + if (p < pend) { + prparser->error = + "Invalid ORCPT= address value: " + "Contains non-printable characters"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + params->orcpt.addr_raw = p_strdup(prparser->pool, str_c(address)); + + if (strcasecmp(params->orcpt.addr_type, "rfc822") == 0) { + if (smtp_params_rcpt_parse_orcpt_rfc822( + prparser, params->orcpt.addr_raw, prparser->pool, + ¶ms->orcpt.addr) < 0) { + prparser->error = "Invalid ORCPT= address value: " + "Invalid RFC822 address"; + prparser->error_code = + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + } + return 0; +} + +int smtp_params_rcpt_parse(pool_t pool, const char *args, + enum smtp_param_rcpt_parse_flags flags, + enum smtp_capability caps, + const char *const *extensions, + struct smtp_params_rcpt *params_r, + enum smtp_param_parse_error *error_code_r, + const char **error_r) +{ + struct smtp_params_rcpt_parser prparser; + struct smtp_param param; + const char *const *argv; + const char *error; + int ret = 0; + + i_zero(params_r); + + i_zero(&prparser); + prparser.pool = pool; + prparser.params = params_r; + prparser.flags = flags; + prparser.caps = caps; + + argv = t_strsplit(args, " "); + for (; *argv != NULL; argv++) { + if (smtp_param_parse(pool_datastack_create(), *argv, + ¶m, &error) < 0) { + *error_r = t_strdup_printf( + "Invalid RCPT parameter: %s", error); + *error_code_r = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + /* parse known parameters */ + param.keyword = t_str_ucase(param.keyword); + if ((caps & SMTP_CAPABILITY_DSN) != 0 && + strcmp(param.keyword, "NOTIFY") == 0) { + if (smtp_params_rcpt_parse_notify + (&prparser, param.value) < 0) { + ret = -1; + break; + } + } else if (((caps & SMTP_CAPABILITY_DSN) != 0 || + (caps & SMTP_CAPABILITY__ORCPT) != 0) && + strcmp(param.keyword, "ORCPT") == 0) { + if (smtp_params_rcpt_parse_orcpt + (&prparser, param.value) < 0) { + ret = -1; + break; + } + } else if (extensions != NULL && + str_array_icase_find(extensions, param.keyword)) { + /* add the rest to ext_param for specific applications + */ + smtp_params_rcpt_add_extra(params_r, pool, + param.keyword, param.value); + } else { + /* RFC 5321, Section 4.1.1.11: + If the server SMTP does not recognize or cannot + implement one or more of the parameters associated + with a particular MAIL FROM or RCPT TO command, it + will return code 555. */ + *error_r = "Unsupported parameters"; + *error_code_r = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + } + + if (ret < 0) { + *error_r = prparser.error; + *error_code_r = prparser.error_code; + } + return ret; +} + +/* manipulate */ + +void smtp_params_rcpt_copy(pool_t pool, struct smtp_params_rcpt *dst, + const struct smtp_params_rcpt *src) +{ + i_zero(dst); + + if (src == NULL) + return; + + dst->notify = src->notify; + dst->orcpt.addr_type = p_strdup(pool, src->orcpt.addr_type); + dst->orcpt.addr_raw = p_strdup(pool, src->orcpt.addr_raw); + dst->orcpt.addr = smtp_address_clone(pool, src->orcpt.addr); + + smtp_params_copy(pool, &dst->extra_params, &src->extra_params); +} + +void smtp_params_rcpt_add_extra(struct smtp_params_rcpt *params, pool_t pool, + const char *keyword, const char *value) +{ + smtp_params_add_one(¶ms->extra_params, pool, keyword, value); +} + +void smtp_params_rcpt_encode_extra(struct smtp_params_rcpt *params, pool_t pool, + const char *keyword, + const unsigned char *value, + size_t value_len) +{ + smtp_params_add_encoded(¶ms->extra_params, pool, + keyword, value, value_len); +} + +bool smtp_params_rcpt_drop_extra(struct smtp_params_rcpt *params, + const char *keyword, const char **value_r) +{ + return smtp_params_drop_one(¶ms->extra_params, keyword, value_r); +} + +void smtp_params_rcpt_set_orcpt(struct smtp_params_rcpt *params, pool_t pool, + struct smtp_address *rcpt) +{ + params->orcpt.addr_type = "rfc822"; + params->orcpt.addr = smtp_address_clone(pool, rcpt); + params->orcpt.addr_raw = p_strdup(pool, smtp_address_encode(rcpt)); +} + +/* write */ + +static void +smtp_params_rcpt_write_notify(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_rcpt *params) +{ + if (params->notify == SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) + return; + if ((caps & SMTP_CAPABILITY_DSN) == 0) + return; + + /* NOTIFY=: RFC 3461 + + notify-esmtp-value = "NEVER" / 1#notify-list-element + notify-list-element = "SUCCESS" / "FAILURE" / "DELAY" + */ + + str_append(buffer, "NOTIFY="); + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) { + i_assert(params->notify == SMTP_PARAM_RCPT_NOTIFY_NEVER); + str_append(buffer, "NEVER"); + } else { + bool comma = FALSE; + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_SUCCESS) != 0) { + str_append(buffer, "SUCCESS"); + comma = TRUE; + } + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_FAILURE) != 0) { + if (comma) + str_append_c(buffer, ','); + str_append(buffer, "FAILURE"); + comma = TRUE; + } + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_DELAY) != 0) { + if (comma) + str_append_c(buffer, ','); + str_append(buffer, "DELAY"); + } + } + str_append_c(buffer, ' '); +} + +static void +smtp_params_rcpt_write_orcpt(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_rcpt *params) +{ + if (!smtp_params_rcpt_has_orcpt(params)) + return; + if ((caps & SMTP_CAPABILITY_DSN) == 0 && + (caps & SMTP_CAPABILITY__ORCPT) == 0) + return; + + /* ORCPT=
: RFC 3461 */ + + str_printfa(buffer, "ORCPT=%s;", params->orcpt.addr_type); + if (strcasecmp(params->orcpt.addr_type, "rfc822") == 0) { + smtp_xtext_encode_cstr( + buffer, smtp_address_encode(params->orcpt.addr)); + } else { + i_assert(params->orcpt.addr_raw != NULL); + smtp_xtext_encode_cstr(buffer, params->orcpt.addr_raw); + } + str_append_c(buffer, ' '); +} + +void smtp_params_rcpt_write(string_t *buffer, enum smtp_capability caps, + const char *const *extra_params, + const struct smtp_params_rcpt *params) +{ + size_t init_len = str_len(buffer); + + smtp_params_rcpt_write_notify(buffer, caps, params); + smtp_params_rcpt_write_orcpt(buffer, caps, params); + + smtp_params_write(buffer, extra_params, ¶ms->extra_params); + + if (str_len(buffer) > init_len) + str_truncate(buffer, str_len(buffer)-1); +} + +/* evaluate */ + +const struct smtp_param * +smtp_params_rcpt_get_extra(const struct smtp_params_rcpt *params, + const char *keyword) +{ + return smtp_params_get_param(¶ms->extra_params, keyword); +} + +int smtp_params_rcpt_decode_extra(const struct smtp_params_rcpt *params, + const char *keyword, string_t **value_r, + bool allow_nul, const char **error_r) +{ + return smtp_params_decode_param(¶ms->extra_params, + keyword, value_r, allow_nul, error_r); +} + +bool smtp_params_rcpt_equal(const struct smtp_params_rcpt *params1, + const struct smtp_params_rcpt *params2) +{ + if (params1 == NULL || params2 == NULL) + return (params1 == params2); + + /* NOTIFY: RFC 3461, Section 4.1 */ + if (params1->notify != params2->notify) + return FALSE; + + /* ORCPT: RFC 3461, Section 4.2 */ + if (null_strcasecmp(params1->orcpt.addr_type, + params2->orcpt.addr_type) != 0) + return FALSE; + if (null_strcasecmp(params1->orcpt.addr_type, "rfc822") == 0) { + if (!smtp_address_equals(params1->orcpt.addr, + params2->orcpt.addr)) + return FALSE; + } else { + if (null_strcmp(params1->orcpt.addr_raw, + params2->orcpt.addr_raw) != 0) + return FALSE; + } + + /* extra parameters */ + return smtp_params_equal(¶ms1->extra_params, + ¶ms2->extra_params); +} + +/* events */ + +static void +smtp_params_rcpt_add_notify_to_event(const struct smtp_params_rcpt *params, + struct event *event) +{ + /* NOTIFY: RFC 3461, Section 4.1 */ + if (params->notify == SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) + return; + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) { + i_assert(params->notify == + SMTP_PARAM_RCPT_NOTIFY_NEVER); + event_add_str(event, "rcpt_param_notify", "NEVER"); + } else { + string_t *str = t_str_new(32); + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_SUCCESS) != 0) + str_append(str, "SUCCESS"); + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_FAILURE) != 0) { + if (str_len(str) > 0) + str_append_c(str, ','); + str_append(str, "FAILURE"); + } + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_DELAY) != 0) { + if (str_len(str) > 0) + str_append_c(str, ','); + str_append(str, "DELAY"); + } + event_add_str(event, "rcpt_param_notify", str_c(str)); + } +} + +static void +smtp_params_rcpt_add_orcpt_to_event(const struct smtp_params_rcpt *params, + struct event *event) +{ + /* ORCPT: RFC 3461, Section 4.2 */ + if (params->orcpt.addr_type == NULL) + return; + + event_add_str(event, "rcpt_param_orcpt_type", + params->orcpt.addr_type); + if (strcasecmp(params->orcpt.addr_type, "rfc822") == 0) { + event_add_str(event, "rcpt_param_orcpt", + smtp_address_encode(params->orcpt.addr)); + } else { + i_assert(params->orcpt.addr_raw != NULL); + event_add_str(event, "rcpt_param_orcpt", + params->orcpt.addr_raw); + } +} + +void smtp_params_rcpt_add_to_event(const struct smtp_params_rcpt *params, + struct event *event) +{ + smtp_params_rcpt_add_notify_to_event(params, event); + smtp_params_rcpt_add_orcpt_to_event(params, event); +} diff --git a/src/lib-smtp/smtp-params.h b/src/lib-smtp/smtp-params.h new file mode 100644 index 0000000..34568ce --- /dev/null +++ b/src/lib-smtp/smtp-params.h @@ -0,0 +1,233 @@ +#ifndef SMTP_PARAMS_H +#define SMTP_PARAMS_H + +#include "array-decl.h" + +#include "smtp-common.h" + +struct smtp_param; + +ARRAY_DEFINE_TYPE(smtp_param, struct smtp_param); + +enum smtp_param_mail_body_type { + SMTP_PARAM_MAIL_BODY_TYPE_UNSPECIFIED = 0, + SMTP_PARAM_MAIL_BODY_TYPE_7BIT, + SMTP_PARAM_MAIL_BODY_TYPE_8BITMIME, + SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME, + SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION +}; + +enum smtp_param_mail_ret { + SMTP_PARAM_MAIL_RET_UNSPECIFIED = 0, + SMTP_PARAM_MAIL_RET_HDRS, + SMTP_PARAM_MAIL_RET_FULL, +}; + +enum smtp_param_rcpt_notify { + SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED = 0x00, + SMTP_PARAM_RCPT_NOTIFY_SUCCESS = 0x01, + SMTP_PARAM_RCPT_NOTIFY_FAILURE = 0x02, + SMTP_PARAM_RCPT_NOTIFY_DELAY = 0x04, + SMTP_PARAM_RCPT_NOTIFY_NEVER = 0x80 +}; + +struct smtp_param { + const char *keyword; + const char *value; +}; + +struct smtp_params_mail { + /* AUTH: RFC 4954 */ + const struct smtp_address *auth; + /* BODY: RFC 6152 */ + struct { + enum smtp_param_mail_body_type type; + const char *ext; + } body; + /* ENVID: RFC 3461, Section 4.4 */ + const char *envid; + /* RET: RFC 3461, Section 4.3 */ + enum smtp_param_mail_ret ret; + /* SIZE: RFC 1870 */ + uoff_t size; + /* extra parameters */ + ARRAY_TYPE(smtp_param) extra_params; +}; + +struct smtp_params_rcpt { + /* ORCPT: RFC 3461, Section 4.2 */ + struct { + const char *addr_type; + /* addr_type=rfc822 */ + const struct smtp_address *addr; + /* raw value */ + const char *addr_raw; + } orcpt; + /* NOTIFY: RFC 3461, Section 4.1 */ + enum smtp_param_rcpt_notify notify; + /* extra parameters */ + ARRAY_TYPE(smtp_param) extra_params; +}; + +enum smtp_param_parse_error { + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX = 0, + SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED +}; + +/* + * Common + */ + +/* parse */ + +int smtp_param_parse(pool_t pool, const char *text, + struct smtp_param *param_r, const char **error_r); + +/* manipulate */ + +void smtp_params_copy(pool_t pool, ARRAY_TYPE(smtp_param) *dst, + const ARRAY_TYPE(smtp_param) *src) ATTR_NULL(3); + +void smtp_params_add_one(ARRAY_TYPE(smtp_param) *params, pool_t pool, + const char *keyword, const char *value); +void smtp_params_add_encoded(ARRAY_TYPE(smtp_param) *params, pool_t pool, + const char *keyword, const unsigned char *value, + size_t value_len); + +bool smtp_params_drop_one(ARRAY_TYPE(smtp_param) *params, const char *keyword, + const char **value_r); + +/* write */ + +void smtp_param_write(string_t *out, const struct smtp_param *param); + +/* evaluate */ + +const struct smtp_param * +smtp_params_get_param(const ARRAY_TYPE(smtp_param) *params, + const char *keyword); +int smtp_params_decode_param(const ARRAY_TYPE(smtp_param) *params, + const char *keyword, string_t **value_r, + bool allow_nul, const char **error_r); + +bool smtp_params_equal(const ARRAY_TYPE(smtp_param) *params1, + const ARRAY_TYPE(smtp_param) *params2); + +/* + * MAIL parameters + */ + +/* parse */ + +int smtp_params_mail_parse(pool_t pool, const char *args, + enum smtp_capability caps, + const char *const *param_extensions, + const char *const *body_param_extensions, + struct smtp_params_mail *params_r, + enum smtp_param_parse_error *error_code_r, + const char **error_r) ATTR_NULL(4, 5); + +/* manipulate */ + +void smtp_params_mail_copy(pool_t pool, struct smtp_params_mail *dst, + const struct smtp_params_mail *src); + +void smtp_params_mail_add_extra(struct smtp_params_mail *params, pool_t pool, + const char *keyword, const char *value) + ATTR_NULL(4); +void smtp_params_mail_encode_extra(struct smtp_params_mail *params, pool_t pool, + const char *keyword, + const unsigned char *value, + size_t value_len); +bool smtp_params_mail_drop_extra(struct smtp_params_mail *params, + const char *keyword, const char **value_r) + ATTR_NULL(3); + +/* write */ + +void smtp_params_mail_write(string_t *buffer, enum smtp_capability caps, + const char *const *extra_params, + const struct smtp_params_mail *params) ATTR_NULL(3); + +/* evaluate */ + +const struct smtp_param * +smtp_params_mail_get_extra(const struct smtp_params_mail *params, + const char *keyword); +int smtp_params_mail_decode_extra(const struct smtp_params_mail *params, + const char *keyword, string_t **value_r, + bool allow_nul, const char **error_r); + +/* events */ + +void smtp_params_mail_add_to_event(const struct smtp_params_mail *params, + struct event *event); + +/* + * RCPT parameters + */ + +/* parse */ + +enum smtp_param_rcpt_parse_flags { + /* Allow address values without a domain part */ + SMTP_PARAM_RCPT_FLAG_ORCPT_ALLOW_LOCALPART = BIT(0), +}; + +int smtp_params_rcpt_parse(pool_t pool, const char *args, + enum smtp_param_rcpt_parse_flags flags, + enum smtp_capability caps, + const char *const *param_extensions, + struct smtp_params_rcpt *params_r, + enum smtp_param_parse_error *error_code_r, + const char **error_r) ATTR_NULL(4); + +/* manipulate */ + +void smtp_params_rcpt_copy(pool_t pool, struct smtp_params_rcpt *dst, + const struct smtp_params_rcpt *src) ATTR_NULL(3); + +void smtp_params_rcpt_add_extra(struct smtp_params_rcpt *params, pool_t pool, + const char *keyword, const char *value) + ATTR_NULL(4); +void smtp_params_rcpt_encode_extra(struct smtp_params_rcpt *params, pool_t pool, + const char *keyword, + const unsigned char *value, + size_t value_len); +bool smtp_params_rcpt_drop_extra(struct smtp_params_rcpt *params, + const char *keyword, const char **value_r) + ATTR_NULL(3); + +void smtp_params_rcpt_set_orcpt(struct smtp_params_rcpt *params, pool_t pool, + struct smtp_address *rcpt); + +/* write */ + +void smtp_params_rcpt_write(string_t *buffer, enum smtp_capability caps, + const char *const *extra_params, + const struct smtp_params_rcpt *params) ATTR_NULL(3); + +/* evaluate */ + +const struct smtp_param * +smtp_params_rcpt_get_extra(const struct smtp_params_rcpt *params, + const char *keyword); +int smtp_params_rcpt_decode_extra(const struct smtp_params_rcpt *params, + const char *keyword, string_t **value_r, + bool allow_nul, const char **error_r); + +bool smtp_params_rcpt_equal(const struct smtp_params_rcpt *params1, + const struct smtp_params_rcpt *params2); + +static inline bool +smtp_params_rcpt_has_orcpt(const struct smtp_params_rcpt *params) +{ + return (params->orcpt.addr_type != NULL); +} + +/* events */ + +void smtp_params_rcpt_add_to_event(const struct smtp_params_rcpt *params, + struct event *event); + +#endif diff --git a/src/lib-smtp/smtp-parser.c b/src/lib-smtp/smtp-parser.c new file mode 100644 index 0000000..5672ff0 --- /dev/null +++ b/src/lib-smtp/smtp-parser.c @@ -0,0 +1,587 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "str.h" +#include "strescape.h" + +#include "smtp-parser.h" + +#include + +/* Character definitions from RFC 5321/5322: + + textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII + = 1*(%x09 / %x20-7e) + ehlo-param = 1*(%d33-126) + = 1*(%x21-7e) + ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127) + = 1*(%x00-09 / %x0b-0c / %x0e-7f) + qtext = %d32-33 / %d35-91 / %d93-126 + = %x20-21 / %x23-5B / %x5d-7e + quoted-pair = %d92 %d32-126 + = %x5c %x20-7e + atext = ALPHA / DIGIT / ; Printable US-ASCII + "!" / "#" / ; characters not including + "$" / "%" / ; specials. Used for atoms. + "&" / "'" / + "*" / "+" / + "-" / "/" / + "=" / "?" / + "^" / "_" / + "`" / "{" / + "|" / "}" / + "~" + = %x21 / %x23-27 / %x2a-2b / %x2d / %x2f-39 / %x3d / + %d3f / %x41-5a / %x5e-7e / + esmtp-value = 1*(%d33-60 / %d62-126) + = 1*(%x21-3c / %x3e-7e) + dcontent = %d33-90 / ; Printable US-ASCII + %d94-126 ; excl. "[", "\", "]" + = %x21-5a / %x5e-7e + xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive, + except for "+" and "=". [RFC 3461] + = %x21-2a / %2c-3c / %x3e-7e + + Bit mappings (FIXME: rearrange): + + (1<<0) => %x21-2a / %2c-3c / %x3e-7e (xtext) + (1<<1) => %x21 / %x23-27 / %x2a-2b / %x2d / %x2f-39 / %x3d / + %d3f / %x41-5a / %x5e-7e / + (1<<2) => %x28-29 / %x2c / %x2e / %x3a-3c / %x3e / %x40 + (1<<8) => %x00-09 / %x0b-0c / %x0e-20 / %x7f + (1<<5) => %x09 / %5b-5d + (1<<4) => %x5b / %x5d + (1<<3) => %x20 + (1<<9) => %x22 + (1<<6) => %x2b + (1<<7) => %x3d + */ + +/* xtext */ +const uint16_t smtp_xtext_char_mask = (1<<0); +/* atext */ +const uint16_t smtp_atext_char_mask = (1<<1); +/* dcontent */ +const uint16_t smtp_dcontent_char_mask = (1<<1)|(1<<2)|(1<<9); +/* qtext */ +const uint16_t smtp_qtext_char_mask = (1<<1)|(1<<2)|(1<<3)|(1<<4); +/* textstring */ +const uint16_t smtp_textstr_char_mask = (1<<1)|(1<<2)|(1<<9)|(1<<3)|(1<<5); +/* esmtp-value */ +const uint16_t smtp_esmtp_value_char_mask = (1<<0)|(1<<6); +/* ehlo-param */ +const uint16_t smtp_ehlo_param_char_mask = (1<<0)|(1<<6)|(1<<7); +/* ehlo-greet */ +const uint16_t smtp_ehlo_greet_char_mask = (1<<0)|(1<<6)|(1<<7)|(1<<8); +/* quoted-pair */ +const uint16_t smtp_qpair_char_mask = (1<<0)|(1<<3)|(1<<6)|(1<<7); + +const uint16_t smtp_char_lookup[256] = { + 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, // 00 + 0x100, 0x120, 0x000, 0x100, 0x100, 0x000, 0x100, 0x100, // 08 + 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, // 10 + 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, // 18 + 0x108, 0x003, 0x201, 0x003, 0x003, 0x003, 0x003, 0x003, // 20 + 0x005, 0x005, 0x003, 0x042, 0x005, 0x003, 0x005, 0x003, // 28 + 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, // 30 + 0x003, 0x003, 0x005, 0x005, 0x005, 0x082, 0x005, 0x003, // 38 + 0x005, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, // 40 + 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, // 48 + 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, // 50 + 0x003, 0x003, 0x003, 0x031, 0x021, 0x031, 0x003, 0x003, // 58 + 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, // 60 + 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, // 68 + 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, // 70 + 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x100, // 78 + + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // 80 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // 88 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // 90 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // 98 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // a0 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // a8 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // b0 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // b8 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // c0 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // c8 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // d0 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // d8 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // e0 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // e8 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // f0 + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, // f8 +}; + +/* + * Parser + */ + +void smtp_parser_init(struct smtp_parser *parser, + pool_t pool, const char *data) +{ + parser->pool = pool; + parser->begin = parser->cur = (const unsigned char *)data; + parser->end = parser->begin + strlen(data); + parser->error = NULL; +} + +/* + * Common syntax + */ + +static int +smtp_parser_parse_ldh_str(struct smtp_parser *parser, + string_t *out) +{ + const unsigned char *pbegin = parser->cur, *palnum; + + /* Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig + Let-dig = ALPHA / DIGIT + */ + + /* Ldh-str */ + palnum = NULL; + while (parser->cur < parser->end) { + if (i_isalnum(*parser->cur)) + palnum = parser->cur; + else if (*parser->cur != '-') + break; + parser->cur++; + } + if (parser->cur == pbegin || palnum == NULL) { + parser->cur = pbegin; + return 0; + } + + parser->cur = palnum+1; + if (out != NULL) + str_append_data(out, pbegin, parser->cur - pbegin); + return 1; +} + +int smtp_parser_parse_domain(struct smtp_parser *parser, + const char **value_r) +{ + string_t *value = NULL; + + /* Domain = sub-domain *("." sub-domain) + sub-domain = Let-dig [Ldh-str] + Let-dig = ALPHA / DIGIT + Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig + + NOTE: A more generic syntax is accepted to be lenient towards + systems that don't adhere to the standards. It allows + '-' and '_' to occur anywhere in a sub-domain. + */ + + /* Let-dig (first) (nope) */ + if (parser->cur >= parser->end || + (!i_isalnum(*parser->cur) && *parser->cur != '-' && + *parser->cur != '_')) + return 0; + + if (value_r != NULL) + value = t_str_new(256); + + for (;;) { + /* Let-dig (nope) */ + if (parser->cur >= parser->end || *parser->cur == '.') { + parser->error = "Empty sub-domain"; + return -1; + } + if (!i_isalnum(*parser->cur) && *parser->cur != '-' && + *parser->cur != '_') { + parser->error = "Invalid character in domain"; + return -1; + } + if (value_r != NULL) + str_append_c(value, *parser->cur); + parser->cur++; + + /* Ldh-str (nope) */ + while (parser->cur < parser->end) { + if (!i_isalnum(*parser->cur) && *parser->cur != '-' && + *parser->cur != '_') + break; + + if (value_r != NULL) + str_append_c(value, *parser->cur); + parser->cur++; + } + + /* *("." sub-domain) */ + if (parser->cur >= parser->end || *parser->cur != '.') + break; + + if (value_r != NULL) + str_append_c(value, '.'); + parser->cur++; + } + + if (value_r != NULL) + *value_r = str_c(value); + return 1; +} + +static int +smtp_parser_parse_snum(struct smtp_parser *parser, string_t *literal, + uint8_t *octet_r) +{ + const unsigned char *pbegin = parser->cur; + uint8_t octet = 0; + + /* Snum = 1*3DIGIT + ; representing a decimal integer + ; value in the range 0 through 255 + */ + + if (*parser->cur < '0' || *parser->cur > '9') + return 0; + do { + if (octet >= ((uint8_t)-1 / 10)) { + if (octet > (uint8_t)-1 / 10) + return -1; + if ((uint8_t)(*parser->cur - '0') > ((uint8_t)-1 % 10)) + return -1; + } + octet = octet * 10 + (*parser->cur - '0'); + parser->cur++; + } while (*parser->cur >= '0' && *parser->cur <= '9'); + + if (literal != NULL) + str_append_data(literal, pbegin, parser->cur - pbegin); + *octet_r = octet; + return 1; +} + +static int +smtp_parser_parse_ipv4_address(struct smtp_parser *parser, + string_t *literal, struct in_addr *ip4_r) +{ + uint8_t octet; + uint32_t ip = 0; + int ret; + int i; + + /* IPv4-address-literal = Snum 3("." Snum) */ + if ((ret = smtp_parser_parse_snum(parser, literal, &octet)) <= 0) + return ret; + ip = octet; + + for (i = 0; i < 3 && parser->cur < parser->end; i++) { + if (*parser->cur != '.') + return -1; + + if (literal != NULL) + str_append_c(literal, '.'); + parser->cur++; + + if (smtp_parser_parse_snum(parser, literal, &octet) <= 0) + return -1; + ip = (ip << 8) + octet; + } + + if (ip4_r != NULL) + ip4_r->s_addr = htonl(ip); + return 1; +} + +int smtp_parser_parse_address_literal(struct smtp_parser *parser, + const char **value_r, struct ip_addr *ip_r) +{ + const unsigned char *pblock; + struct in_addr ip4; + struct in6_addr ip6; + bool ipv6 = FALSE; + string_t *value = NULL, *tagbuf; + int ret; + + /* address-literal = "[" ( IPv4-address-literal / + IPv6-address-literal / + General-address-literal ) "]" + ; See Section 4.1.3 + + IPv6-address-literal = "IPv6:" IPv6-addr + General-address-literal = Standardized-tag ":" 1*dcontent + Standardized-tag = Ldh-str + ; Standardized-tag MUST be specified in a + ; Standards-Track RFC and registered with + ; IANA + dcontent = %d33-90 / ; Printable US-ASCII + %d94-126 ; excl. "[", "\", "]" + */ + + /* "[" */ + if (parser->cur >= parser->end || *parser->cur != '[') + return 0; + parser->cur++; + + if (value_r != NULL) { + value = t_str_new(128); + str_append_c(value, '['); + } + if (ip_r != NULL) + i_zero(ip_r); + + /* IPv4-address-literal / ... */ + i_zero(&ip4); + if ((ret=smtp_parser_parse_ipv4_address(parser, value, &ip4)) != 0) { + if (ret < 0) { + parser->error = "Invalid IPv4 address literal"; + return -1; + } + if (ip_r != NULL) { + ip_r->family = AF_INET; + ip_r->u.ip4 = ip4; + } + + /* ... / IPv6-address-literal / General-address-literal */ + } else { + /* IPv6-address-literal = "IPv6:" IPv6-addr + General-address-literal = Standardized-tag ":" 1*dcontent + Standardized-tag = Ldh-str + */ + if (value_r != NULL) { + tagbuf = value; + } else { + tagbuf = t_str_new(16); + str_append_c(tagbuf, '['); + } + if (smtp_parser_parse_ldh_str(parser, tagbuf) <= 0 || + parser->cur >= parser->end || *parser->cur != ':') { + parser->error = "Invalid address literal"; + return -1; + } + if (strcasecmp(str_c(tagbuf)+1, "IPv6") == 0) + ipv6 = TRUE; + else if (value_r == NULL) { + parser->error = t_strdup_printf( + "Unsupported %s address literal", + str_c(tagbuf)+1); + return -1; + } + parser->cur++; + if (value_r != NULL) + str_append_c(value, ':'); + + /* 1*dcontent */ + pblock = parser->cur; + while (parser->cur < parser->end && + smtp_char_is_dcontent(*parser->cur)) + parser->cur++; + + if (parser->cur == pblock) { + parser->error = "Empty address literal"; + return -1; + } + if (value_r != NULL) + str_append_data(value, pblock, parser->cur - pblock); + + if (ipv6) { + i_zero(&ip6); + if (inet_pton(AF_INET6, t_strndup(pblock, + parser->cur - pblock), &ip6) <= 0) { + parser->error = "Invalid IPv6 address literal"; + return -1; + } + if (ip_r != NULL) { + ip_r->family = AF_INET6; + ip_r->u.ip6 = ip6; + } + } + } + + /* ']' */ + if (parser->cur >= parser->end) { + parser->error = "Missing ']' at end of address literal"; + return -1; + } else if (*parser->cur != ']') { + parser->error = "Invalid character in address literal"; + return -1; + } + + parser->cur++; + if (value_r != NULL) { + str_append_c(value, ']'); + *value_r = str_c(value); + } + return 1; +} + +int smtp_parser_parse_quoted_string(struct smtp_parser *parser, + const char **value_r) +{ + string_t *value = NULL; + const unsigned char *pbegin; + + /* Quoted-string = DQUOTE *QcontentSMTP DQUOTE + QcontentSMTP = qtextSMTP / quoted-pairSMTP + quoted-pairSMTP = %d92 %d32-126 + ; i.e., backslash followed by any ASCII + ; graphic (including itself) or SPace + qtextSMTP = %d32-33 / %d35-91 / %d93-126 + ; i.e., within a quoted string, any + ; ASCII graphic or space is permitted + ; without blackslash-quoting except + ; double-quote and the backslash itself. + */ + + /* DQUOTE */ + if (parser->cur >= parser->end || *parser->cur != '"') + return 0; + parser->cur++; + + if (value_r != NULL) + value = t_str_new(256); + + /* *QcontentSMTP */ + while (parser->cur < parser->end) { + pbegin = parser->cur; + while (parser->cur < parser->end && + smtp_char_is_qtext(*parser->cur)) { + /* qtextSMTP */ + parser->cur++; + } + + if (value_r != NULL) + str_append_data(value, pbegin, parser->cur - pbegin); + + if (parser->cur >= parser->end || *parser->cur != '\\') + break; + parser->cur++; + + /* quoted-pairSMTP */ + if (parser->cur >= parser->end || + !smtp_char_is_qpair(*parser->cur)) { + parser->error = + "Invalid character after '\\' in quoted string"; + return -1; + } + + if (value_r != NULL) + str_append_c(value, *parser->cur); + parser->cur++; + } + + /* DQUOTE */ + if (parser->cur >= parser->end) { + parser->error = "Premature end of quoted string"; + return -1; + } + if (*parser->cur != '"') { + parser->error = "Invalid character in quoted string"; + return -1; + } + parser->cur++; + if (value_r != NULL) + *value_r = str_c(value); + return 1; +} + +static int +smtp_parser_skip_atom(struct smtp_parser *parser) +{ + /* Atom = 1*atext */ + + if (parser->cur >= parser->end || !smtp_char_is_atext(*parser->cur)) + return 0; + parser->cur++; + + while (parser->cur < parser->end && smtp_char_is_atext(*parser->cur)) + parser->cur++; + return 1; +} + +int smtp_parser_parse_atom(struct smtp_parser *parser, + const char **value_r) +{ + const unsigned char *pbegin = parser->cur; + int ret; + + if ((ret=smtp_parser_skip_atom(parser)) <= 0) + return ret; + + if (value_r != NULL) + *value_r = t_strndup(pbegin, parser->cur - pbegin); + return 1; +} + +int smtp_parser_parse_string(struct smtp_parser *parser, + const char **value_r) +{ + int ret; + + /* String = Atom / Quoted-string */ + + if ((ret=smtp_parser_parse_quoted_string(parser, value_r)) != 0) + return ret; + return smtp_parser_parse_atom(parser, value_r); +} + +static bool +smtp_parse_xtext_hexdigit(const unsigned char digit, + unsigned char *hexvalue) +{ + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + *hexvalue = (*hexvalue) << 4; + *hexvalue += digit - '0'; + break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + *hexvalue = (*hexvalue) << 4; + *hexvalue += digit - 'A' + 10; + break; + default: + return FALSE; + } + return TRUE; +} + +int smtp_parser_parse_xtext(struct smtp_parser *parser, + string_t *out) +{ + unsigned char hexchar; + + /* xtext = *( xchar / hexchar ) + xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive, + except for "+" and "=". + hexchar = ASCII "+" immediately followed by two upper case + hexadecimal digits + */ + if (parser->cur >= parser->end || + (!smtp_char_is_xtext(*parser->cur) && *parser->cur != '+')) + return 0; + + while (parser->cur < parser->end) { + const unsigned char *pbegin = parser->cur; + + while (parser->cur < parser->end && + smtp_char_is_xtext(*parser->cur)) + parser->cur++; + + if (out != NULL) + str_append_data(out, pbegin, parser->cur - pbegin); + + if (parser->cur >= parser->end || *parser->cur != '+') + break; + parser->cur++; + + hexchar = 0; + if (smtp_parse_xtext_hexdigit(*parser->cur, &hexchar)) { + parser->cur++; + if (smtp_parse_xtext_hexdigit(*parser->cur, &hexchar)) { + parser->cur++; + if (out != NULL) + str_append_c(out, hexchar); + continue; + } + } + + parser->error = "Invalid hexchar after '+' in xtext"; + return -1; + } + + return 1; +} diff --git a/src/lib-smtp/smtp-parser.h b/src/lib-smtp/smtp-parser.h new file mode 100644 index 0000000..9b76d2c --- /dev/null +++ b/src/lib-smtp/smtp-parser.h @@ -0,0 +1,89 @@ +#ifndef SMTP_PARSER_H +#define SMTP_PARSER_H + +/* + * Character definitions + */ + +extern const uint16_t smtp_xtext_char_mask; +extern const uint16_t smtp_atext_char_mask; +extern const uint16_t smtp_dcontent_char_mask; +extern const uint16_t smtp_qtext_char_mask; +extern const uint16_t smtp_textstr_char_mask; +extern const uint16_t smtp_esmtp_value_char_mask; +extern const uint16_t smtp_ehlo_param_char_mask; +extern const uint16_t smtp_ehlo_greet_char_mask; +extern const uint16_t smtp_qpair_char_mask; + +extern const uint16_t smtp_char_lookup[256]; + +static inline bool +smtp_char_is_xtext(unsigned char ch) { + return (smtp_char_lookup[ch] & smtp_xtext_char_mask) != 0; +} +static inline bool +smtp_char_is_atext(unsigned char ch) { + return (smtp_char_lookup[ch] & smtp_atext_char_mask) != 0; +} +static inline bool +smtp_char_is_dcontent(unsigned char ch) { + return (smtp_char_lookup[ch] & smtp_dcontent_char_mask) != 0; +} +static inline bool +smtp_char_is_qtext(unsigned char ch) { + return (smtp_char_lookup[ch] & smtp_qtext_char_mask) != 0; +} +static inline bool +smtp_char_is_textstr(unsigned char ch) { + return (smtp_char_lookup[ch] & smtp_textstr_char_mask) != 0; +} +static inline bool +smtp_char_is_esmtp_value(unsigned char ch) { + return (smtp_char_lookup[ch] & smtp_esmtp_value_char_mask) != 0; +} +static inline bool +smtp_char_is_ehlo_param(unsigned char ch) { + return (smtp_char_lookup[ch] & smtp_ehlo_param_char_mask) != 0; +} +static inline bool +smtp_char_is_ehlo_greet(unsigned char ch) { + return (smtp_char_lookup[ch] & smtp_ehlo_greet_char_mask) != 0; +} +static inline bool +smtp_char_is_qpair(unsigned char ch) { + return (smtp_char_lookup[ch] & smtp_qpair_char_mask) != 0; +} + +/* + * SMTP parser + */ + +struct smtp_parser { + pool_t pool; + const char *error; + + const unsigned char *begin, *cur, *end; +}; + +void smtp_parser_init(struct smtp_parser *parser, + pool_t pool, const char *data); +string_t *smtp_parser_get_tmpbuf(struct smtp_parser *parser, size_t size); + +/* + * Common syntax + */ + +int smtp_parser_parse_domain(struct smtp_parser *parser, + const char **value_r); +int smtp_parser_parse_address_literal(struct smtp_parser *parser, + const char **value_r, struct ip_addr *ip_r); +int smtp_parser_parse_atom(struct smtp_parser *parser, + const char **value_r); +int smtp_parser_parse_quoted_string(struct smtp_parser *parser, + const char **value_r); +int smtp_parser_parse_string(struct smtp_parser *parser, + const char **value_r); +int smtp_parser_parse_xtext(struct smtp_parser *parser, + string_t *out); + +#endif diff --git a/src/lib-smtp/smtp-reply-parser.c b/src/lib-smtp/smtp-reply-parser.c new file mode 100644 index 0000000..34dc27d --- /dev/null +++ b/src/lib-smtp/smtp-reply-parser.c @@ -0,0 +1,657 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strfuncs.h" +#include "istream.h" +#include "smtp-parser.h" + +#include "smtp-reply-parser.h" + +#include + +/* From RFC 5321: + + Reply-line = *( Reply-code "-" [ textstring ] CRLF ) + Reply-code [ SP textstring ] CRLF + Reply-code = %x32-35 %x30-35 %x30-39 + textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII + + Greeting = ( "220 " (Domain / address-literal) + [ SP textstring ] CRLF ) / + ( "220-" (Domain / address-literal) + [ SP textstring ] CRLF + *( "220-" [ textstring ] CRLF ) + "220" [ SP textstring ] CRLF ) + + ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF ) + / ( "250-" Domain [ SP ehlo-greet ] CRLF + *( "250-" ehlo-line CRLF ) + "250" SP ehlo-line CRLF ) + ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127) + ; string of any characters other than CR or LF + ehlo-line = ehlo-keyword *( SP ehlo-param ) + ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") + ; additional syntax of ehlo-params depends on + ; ehlo-keyword + ehlo-param = 1*(%d33-126) + ; any CHAR excluding and all + ; control characters (US-ASCII 0-31 and 127 + ; inclusive) + + From RFC 2034: + + status-code ::= class "." subject "." detail + class ::= "2" / "4" / "5" + subject ::= 1*3digit + detail ::= 1*3digit + */ + +enum smtp_reply_parser_state { + SMTP_REPLY_PARSE_STATE_INIT = 0, + SMTP_REPLY_PARSE_STATE_CODE, + SMTP_REPLY_PARSE_STATE_SEP, + SMTP_REPLY_PARSE_STATE_TEXT, + SMTP_REPLY_PARSE_STATE_EHLO_SPACE, + SMTP_REPLY_PARSE_STATE_EHLO_GREET, + SMTP_REPLY_PARSE_STATE_CR, + SMTP_REPLY_PARSE_STATE_CRLF, + SMTP_REPLY_PARSE_STATE_LF +}; + +struct smtp_reply_parser_state_data { + enum smtp_reply_parser_state state; + unsigned int line; + + struct smtp_reply *reply; + ARRAY_TYPE(const_string) reply_lines; + size_t reply_size; + + bool last_line:1; +}; + +struct smtp_reply_parser { + struct istream *input; + + size_t max_reply_size; + + const unsigned char *begin, *cur, *end; + + string_t *strbuf; + + struct smtp_reply_parser_state_data state; + pool_t reply_pool; + + char *error; + + bool enhanced_codes:1; + bool ehlo:1; +}; + +bool smtp_reply_parse_enhanced_code(const char *text, + struct smtp_reply_enhanced_code *enh_code_r, + const char **pos_r) +{ + const char *p = text; + unsigned int digits, x, y, z; + + i_zero(enh_code_r); + + /* status-code ::= class "." subject "." detail + class ::= "2" / "4" / "5" + subject ::= 1*3digit + detail ::= 1*3digit + */ + + /* class */ + if (p[1] != '.' || (p[0] != '2' && p[0] != '4' && p[0] != '5')) + return FALSE; + x = p[0] - '0'; + p += 2; + + /* subject */ + digits = 0; + y = 0; + while (*p != '\0' && i_isdigit(*p) && digits++ < 3) { + y = y*10 + (*p - '0'); + p++; + } + if (digits == 0 || *p != '.') + return FALSE; + p++; + + /* detail */ + digits = 0; + z = 0; + while (*p != '\0' && i_isdigit(*p) && digits++ < 3) { + z = z*10 + (*p - '0'); + p++; + } + if (digits == 0 || (pos_r == NULL && *p != '\0')) + return FALSE; + + if (pos_r != NULL) { + /* code is syntactically valid; strip code from textstring */ + *pos_r = p; + } + + enh_code_r->x = x; + enh_code_r->y = y; + enh_code_r->z = z; + return TRUE; +} + +static inline void ATTR_FORMAT(2, 3) +smtp_reply_parser_error(struct smtp_reply_parser *parser, + const char *format, ...) +{ + va_list args; + + i_free(parser->error); + + va_start(args, format); + parser->error = i_strdup_vprintf(format, args); + va_end(args); +} + +struct smtp_reply_parser * +smtp_reply_parser_init(struct istream *input, size_t max_reply_size) +{ + struct smtp_reply_parser *parser; + + parser = i_new(struct smtp_reply_parser, 1); + parser->max_reply_size = + (max_reply_size > 0 ? max_reply_size : SIZE_MAX); + parser->input = input; + i_stream_ref(input); + parser->strbuf = str_new(default_pool, 128); + return parser; +} + +void smtp_reply_parser_deinit(struct smtp_reply_parser **_parser) +{ + struct smtp_reply_parser *parser = *_parser; + + *_parser = NULL; + + str_free(&parser->strbuf); + pool_unref(&parser->reply_pool); + i_stream_unref(&parser->input); + i_free(parser->error); + i_free(parser); +} + +void smtp_reply_parser_set_stream(struct smtp_reply_parser *parser, + struct istream *input) +{ + i_stream_unref(&parser->input); + if (input != NULL) { + parser->input = input; + i_stream_ref(parser->input); + } +} + +static void +smtp_reply_parser_restart(struct smtp_reply_parser *parser) +{ + str_truncate(parser->strbuf, 0); + pool_unref(&parser->reply_pool); + i_zero(&parser->state); + + parser->reply_pool = pool_alloconly_create("smtp_reply", 1024); + parser->state.reply = p_new(parser->reply_pool, struct smtp_reply, 1); + p_array_init(&parser->state.reply_lines, parser->reply_pool, 8); + +} + +static int smtp_reply_parse_code +(struct smtp_reply_parser *parser, unsigned int *code_r) +{ + const unsigned char *first = parser->cur; + const unsigned char *p; + + /* Reply-code = %x32-35 %x30-35 %x30-39 + */ + while (parser->cur < parser->end && i_isdigit(*parser->cur)) + parser->cur++; + + if (str_len(parser->strbuf) + (parser->cur-first) > 3) + return -1; + + str_append_data(parser->strbuf, first, parser->cur - first); + if (parser->cur == parser->end) + return 0; + if (str_len(parser->strbuf) != 3) + return -1; + p = str_data(parser->strbuf); + if (p[0] < '2' || p[0] > '5' || p[1] > '5') + return -1; + *code_r = (p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0'); + str_truncate(parser->strbuf, 0); + return 1; +} + +static int smtp_reply_parse_textstring(struct smtp_reply_parser *parser) +{ + const unsigned char *first = parser->cur; + + /* textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII + */ + while (parser->cur < parser->end && smtp_char_is_textstr(*parser->cur)) + parser->cur++; + + if (((parser->cur-first) + parser->state.reply_size + + str_len(parser->strbuf)) > parser->max_reply_size) { + smtp_reply_parser_error(parser, + "Reply exceeds size limit"); + return -1; + } + + str_append_data(parser->strbuf, first, parser->cur - first); + if (parser->cur == parser->end) + return 0; + return 1; +} + +static int smtp_reply_parse_ehlo_domain(struct smtp_reply_parser *parser) +{ + const unsigned char *first = parser->cur; + + /* Domain [ SP ... + */ + while (parser->cur < parser->end && *parser->cur != ' ' && + smtp_char_is_textstr(*parser->cur)) + parser->cur++; + + if (((parser->cur-first) + parser->state.reply_size + + str_len(parser->strbuf)) > parser->max_reply_size) { + smtp_reply_parser_error(parser, + "Reply exceeds size limit"); + return -1; + } + str_append_data(parser->strbuf, first, parser->cur - first); + if (parser->cur == parser->end) + return 0; + return 1; +} + +static int smtp_reply_parse_ehlo_greet(struct smtp_reply_parser *parser) +{ + const unsigned char *first = parser->cur; + + /* ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127) + * + * The greet is not supposed to be empty, but we don't really care + */ + + if (parser->cur == parser->end) + return 0; + if (smtp_char_is_ehlo_greet(*parser->cur)) { + for (;;) { + while (parser->cur < parser->end && + smtp_char_is_textstr(*parser->cur)) + parser->cur++; + + if (((parser->cur-first) + parser->state.reply_size + + str_len(parser->strbuf)) > + parser->max_reply_size) { + smtp_reply_parser_error(parser, + "Reply exceeds size limit"); + return -1; + } + + /* sanitize bad characters */ + str_append_data(parser->strbuf, + first, parser->cur - first); + + if (parser->cur == parser->end) + return 0; + if (!smtp_char_is_ehlo_greet(*parser->cur)) + break; + str_append_c(parser->strbuf, ' '); + parser->cur++; + first = parser->cur; + } + } + return 1; +} + +static inline const char *_chr_sanitize(unsigned char c) +{ + if (c >= 0x20 && c < 0x7F) + return t_strdup_printf("'%c'", c); + return t_strdup_printf("0x%02x", c); +} + +static void +smtp_reply_parser_parse_enhanced_code(const char *text, + struct smtp_reply_parser *parser, + const char **pos_r) +{ + struct smtp_reply_enhanced_code code; + struct smtp_reply_enhanced_code *cur_code = + &parser->state.reply->enhanced_code; + + if (cur_code->x == 9) + return; /* failed on earlier line */ + + if (!smtp_reply_parse_enhanced_code(text, &code, pos_r)) { + /* failed to parse an enhanced code */ + i_zero(cur_code); + cur_code->x = 9; + return; + } + + if (**pos_r != ' ' && **pos_r != '\r' && **pos_r != '\n') + return; + (*pos_r)++; + + /* check for match with status */ + if (code.x != parser->state.reply->status / 100) { + /* ignore code */ + return; + } + + /* check for code consistency */ + if (parser->state.line > 0 && + (cur_code->x != code.x || cur_code->y != code.y || + cur_code->z != code.z)) { + /* ignore code */ + return; + } + + *cur_code = code; +} + +static void smtp_reply_parser_finish_line(struct smtp_reply_parser *parser) +{ + const char *text = str_c(parser->strbuf); + + if (parser->enhanced_codes && str_len(parser->strbuf) > 5) + smtp_reply_parser_parse_enhanced_code(text, parser, &text); + + parser->state.line++; + parser->state.reply_size += str_len(parser->strbuf); + text = p_strdup(parser->reply_pool, text); + array_push_back(&parser->state.reply_lines, &text); + str_truncate(parser->strbuf, 0); +} + +static int smtp_reply_parse_more(struct smtp_reply_parser *parser) +{ + unsigned int status; + int ret; + + /* + Reply-line = *( Reply-code "-" [ textstring ] CRLF ) + Reply-code [ SP textstring ] CRLF + Reply-code = %x32-35 %x30-35 %x30-39 + + ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF ) + / ( "250-" Domain [ SP ehlo-greet ] CRLF + *( "250-" ehlo-line CRLF ) + "250" SP ehlo-line CRLF ) + */ + + for (;;) { + switch (parser->state.state) { + case SMTP_REPLY_PARSE_STATE_INIT: + smtp_reply_parser_restart(parser); + parser->state.state = SMTP_REPLY_PARSE_STATE_CODE; + /* fall through */ + /* Reply-code */ + case SMTP_REPLY_PARSE_STATE_CODE: + if ((ret=smtp_reply_parse_code(parser, &status)) <= 0) { + if (ret < 0) { + smtp_reply_parser_error(parser, + "Invalid status code in reply"); + } + return ret; + } + if (parser->state.line == 0) { + parser->state.reply->status = status; + } else if (status != parser->state.reply->status) { + smtp_reply_parser_error(parser, + "Inconsistent status codes in reply"); + return -1; + } + parser->state.state = SMTP_REPLY_PARSE_STATE_SEP; + if (parser->cur == parser->end) + return 0; + /* fall through */ + /* "-" / SP / CRLF */ + case SMTP_REPLY_PARSE_STATE_SEP: + switch (*parser->cur) { + /* "-" [ textstring ] CRLF */ + case '-': + parser->cur++; + parser->state.last_line = FALSE; + parser->state.state = + SMTP_REPLY_PARSE_STATE_TEXT; + break; + /* SP [ textstring ] CRLF ; allow missing text */ + case ' ': + parser->cur++; + parser->state.state = + SMTP_REPLY_PARSE_STATE_TEXT; + parser->state.last_line = TRUE; + break; + /* CRLF */ + case '\r': + case '\n': + parser->state.last_line = TRUE; + parser->state.state = SMTP_REPLY_PARSE_STATE_CR; + break; + default: + smtp_reply_parser_error(parser, + "Encountered unexpected %s after reply status code", + _chr_sanitize(*parser->cur)); + return -1; + } + if (parser->state.state != SMTP_REPLY_PARSE_STATE_TEXT) + break; + /* fall through */ + /* textstring / (Domain [ SP ehlo-greet ]) */ + case SMTP_REPLY_PARSE_STATE_TEXT: + if (parser->ehlo && + parser->state.reply->status == 250 && + parser->state.line == 0) { + /* handle first line of EHLO success response + differently because it can contain control + characters (WHY??!) */ + if ((ret=smtp_reply_parse_ehlo_domain(parser)) <= 0) + return ret; + parser->state.state = + SMTP_REPLY_PARSE_STATE_EHLO_SPACE; + if (parser->cur == parser->end) + return 0; + break; + } + if ((ret=smtp_reply_parse_textstring(parser)) <= 0) + return ret; + parser->state.state = SMTP_REPLY_PARSE_STATE_CR; + if (parser->cur == parser->end) + return 0; + /* fall through */ + /* CR */ + case SMTP_REPLY_PARSE_STATE_CR: + if (*parser->cur == '\r') { + parser->cur++; + parser->state.state = + SMTP_REPLY_PARSE_STATE_CRLF; + } else { + parser->state.state = + SMTP_REPLY_PARSE_STATE_LF; + } + if (parser->cur == parser->end) + return 0; + /* fall through */ + /* CRLF / LF */ + case SMTP_REPLY_PARSE_STATE_CRLF: + case SMTP_REPLY_PARSE_STATE_LF: + if (*parser->cur != '\n') { + if (parser->state.state == + SMTP_REPLY_PARSE_STATE_CRLF) { + smtp_reply_parser_error(parser, + "Encountered stray CR in reply text"); + } else { + smtp_reply_parser_error(parser, + "Encountered stray %s in reply text", + _chr_sanitize(*parser->cur)); + } + return -1; + } + parser->cur++; + smtp_reply_parser_finish_line(parser); + if (parser->state.last_line) { + parser->state.state = + SMTP_REPLY_PARSE_STATE_INIT; + return 1; + } + parser->state.state = SMTP_REPLY_PARSE_STATE_CODE; + break; + /* SP ehlo-greet */ + case SMTP_REPLY_PARSE_STATE_EHLO_SPACE: + if (*parser->cur != ' ') { + parser->state.state = SMTP_REPLY_PARSE_STATE_CR; + break; + } + parser->cur++; + str_append_c(parser->strbuf, ' '); + parser->state.state = SMTP_REPLY_PARSE_STATE_EHLO_GREET; + if (parser->cur == parser->end) + return 0; + /* fall through */ + /* ehlo-greet */ + case SMTP_REPLY_PARSE_STATE_EHLO_GREET: + if ((ret=smtp_reply_parse_ehlo_greet(parser)) <= 0) + return ret; + parser->state.state = SMTP_REPLY_PARSE_STATE_CR; + if (parser->cur == parser->end) + return 0; + break; + default: + i_unreached(); + } + } + + i_unreached(); + return -1; +} + +static int smtp_reply_parse(struct smtp_reply_parser *parser) +{ + size_t size; + int ret; + + while ((ret = i_stream_read_more(parser->input, + &parser->begin, &size)) > 0) { + parser->cur = parser->begin; + parser->end = parser->cur + size; + + if ((ret = smtp_reply_parse_more(parser)) < 0) + return -1; + + i_stream_skip(parser->input, parser->cur - parser->begin); + if (ret > 0) + return 1; + } + + i_assert(ret != -2); + if (ret < 0) { + i_assert(parser->input->eof); + if (parser->input->stream_errno == 0) { + if (parser->state.state == SMTP_REPLY_PARSE_STATE_INIT) + return 0; + smtp_reply_parser_error(parser, + "Premature end of input"); + } else { + smtp_reply_parser_error(parser, + "Stream error: %s", + i_stream_get_error(parser->input)); + } + } + return ret; +} + +int smtp_reply_parse_next(struct smtp_reply_parser *parser, + bool enhanced_codes, struct smtp_reply **reply_r, + const char **error_r) +{ + int ret; + + i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT || + (parser->enhanced_codes == enhanced_codes && !parser->ehlo)); + + parser->enhanced_codes = enhanced_codes; + parser->ehlo = FALSE; + + i_free_and_null(parser->error); + + /* + Reply-line = *( Reply-code "-" [ textstring ] CRLF ) + Reply-code [ SP textstring ] CRLF + Reply-code = %x32-35 %x30-35 %x30-39 + textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII + + Greeting is not handled specially here. + */ + if ((ret=smtp_reply_parse(parser)) <= 0) { + *error_r = parser->error; + return ret; + } + + i_assert(array_count(&parser->state.reply_lines) > 0); + array_append_zero(&parser->state.reply_lines); + + parser->state.state = SMTP_REPLY_PARSE_STATE_INIT; + parser->state.reply->text_lines = + array_front(&parser->state.reply_lines); + *reply_r = parser->state.reply; + return 1; +} + +int smtp_reply_parse_ehlo(struct smtp_reply_parser *parser, + struct smtp_reply **reply_r, const char **error_r) +{ + int ret; + + i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT || + (!parser->enhanced_codes && parser->ehlo)); + + parser->enhanced_codes = FALSE; + parser->ehlo = TRUE; + + i_free_and_null(parser->error); + + /* + ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF ) + / ( "250-" Domain [ SP ehlo-greet ] CRLF + *( "250-" ehlo-line CRLF ) + "250" SP ehlo-line CRLF ) + ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127) + ; string of any characters other than CR or LF + ehlo-line = ehlo-keyword *( SP ehlo-param ) + ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") + ; additional syntax of ehlo-params depends on + ; ehlo-keyword + ehlo-param = 1*(%d33-126) + ; any CHAR excluding and all + ; control characters (US-ASCII 0-31 and 127 + ; inclusive) + */ + if ((ret=smtp_reply_parse(parser)) <= 0) { + *error_r = parser->error; + return ret; + } + + i_assert(array_count(&parser->state.reply_lines) > 0); + array_append_zero(&parser->state.reply_lines); + + parser->state.state = SMTP_REPLY_PARSE_STATE_INIT; + parser->state.reply->text_lines = + array_front(&parser->state.reply_lines); + *reply_r = parser->state.reply; + return 1; +} diff --git a/src/lib-smtp/smtp-reply-parser.h b/src/lib-smtp/smtp-reply-parser.h new file mode 100644 index 0000000..4ffb32a --- /dev/null +++ b/src/lib-smtp/smtp-reply-parser.h @@ -0,0 +1,25 @@ +#ifndef SMTP_REPLY_PARSER_H +#define SMTP_REPLY_PARSER_H + +#include "smtp-reply.h" + +struct smtp_reply_parser; + +bool smtp_reply_parse_enhanced_code(const char *text, + struct smtp_reply_enhanced_code *enh_code_r, + const char **pos_r) ATTR_NULL(3); + +struct smtp_reply_parser * +smtp_reply_parser_init(struct istream *input, size_t max_reply_size); +void smtp_reply_parser_deinit(struct smtp_reply_parser **_parser); + +void smtp_reply_parser_set_stream(struct smtp_reply_parser *parser, + struct istream *input); + +int smtp_reply_parse_next(struct smtp_reply_parser *parser, + bool enhanced_codes, struct smtp_reply **reply_r, + const char **error_r); +int smtp_reply_parse_ehlo(struct smtp_reply_parser *parser, + struct smtp_reply **reply_r, const char **error_r); + +#endif diff --git a/src/lib-smtp/smtp-reply.c b/src/lib-smtp/smtp-reply.c new file mode 100644 index 0000000..3ee27da --- /dev/null +++ b/src/lib-smtp/smtp-reply.c @@ -0,0 +1,186 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strfuncs.h" +#include "smtp-reply.h" + +void smtp_reply_init(struct smtp_reply *reply, unsigned int status, + const char *text) +{ + const char **text_lines = t_new(const char *, 2); + + text_lines[0] = text; + text_lines[1] = NULL; + + i_zero(reply); + reply->status = status; + reply->text_lines = text_lines; +} + +void smtp_reply_printf(struct smtp_reply *reply, unsigned int status, + const char *format, ...) +{ + va_list args; + + va_start(args, format); + smtp_reply_init(reply, status, t_strdup_vprintf(format, args)); + va_end(args); +} + +const char * +smtp_reply_get_enh_code(const struct smtp_reply *reply) +{ + if (reply->enhanced_code.x < 2) + return NULL; + if (reply->enhanced_code.x >= 6) + return NULL; + + return t_strdup_printf("%u.%u.%u", + reply->enhanced_code.x, reply->enhanced_code.y, reply->enhanced_code.z); +} + +const char *const * +smtp_reply_get_text_lines_omit_prefix(const struct smtp_reply *reply) +{ + unsigned int lines_count, i; + const char **lines; + const char *p; + + if ((p=strchr(reply->text_lines[0], ' ')) == NULL) + return reply->text_lines; + + lines_count = str_array_length(reply->text_lines); + lines = t_new(const char *, lines_count + 1); + + lines[0] = p + 1; + for (i = 1; i < lines_count; i++) + lines[i] = reply->text_lines[i]; + + return lines; +} + +void +smtp_reply_write(string_t *out, const struct smtp_reply *reply) +{ + const char *prefix, *enh_code; + const char *const *lines; + + i_assert(reply->status < 560); + i_assert(reply->enhanced_code.x < 6); + + prefix = t_strdup_printf("%03u", reply->status); + enh_code = smtp_reply_get_enh_code(reply); + + if (reply->text_lines == NULL || *reply->text_lines == NULL) { + str_append(out, prefix); + if (enh_code != NULL) { + str_append_c(out, ' '); + str_append(out, enh_code); + } + str_append(out, " \r\n"); + return; + } + + lines = reply->text_lines; + while (*lines != NULL) { + str_append(out, prefix); + if (*(lines+1) == NULL) + str_append_c(out, ' '); + else + str_append_c(out, '-'); + if (enh_code != NULL) { + str_append(out, enh_code); + str_append_c(out, ' '); + } + str_append(out, *lines); + str_append(out, "\r\n"); + lines++; + } +} + +static void +smtp_reply_write_message_one_line(string_t *out, const struct smtp_reply *reply) +{ + const char *const *lines; + + lines = reply->text_lines; + while (*lines != NULL) { + if (str_len(out) > 0) + str_append_c(out, ' '); + str_append(out, *lines); + lines++; + } +} + +void smtp_reply_write_one_line(string_t *out, const struct smtp_reply *reply) +{ + const char *enh_code = smtp_reply_get_enh_code(reply); + + i_assert(reply->status < 560); + i_assert(reply->enhanced_code.x < 6); + + str_printfa(out, "%03u", reply->status); + if (enh_code != NULL) { + str_append_c(out, ' '); + str_append(out, enh_code); + } + + smtp_reply_write_message_one_line(out, reply); +} + +const char *smtp_reply_log(const struct smtp_reply *reply) +{ + string_t *msg = t_str_new(256); + + if (smtp_reply_is_remote(reply)) { + const char *enh_code = smtp_reply_get_enh_code(reply); + + str_printfa(msg, "%03u", reply->status); + if (enh_code != NULL) { + str_append_c(msg, ' '); + str_append(msg, enh_code); + } + } + + smtp_reply_write_message_one_line(msg, reply); + return str_c(msg); +} + +const char *smtp_reply_get_message(const struct smtp_reply *reply) +{ + string_t *msg = t_str_new(256); + + smtp_reply_write_message_one_line(msg, reply); + return str_c(msg); +} + +void smtp_reply_copy(pool_t pool, struct smtp_reply *dst, + const struct smtp_reply *src) +{ + *dst = *src; + dst->text_lines = p_strarray_dup(pool, src->text_lines); +} + +struct smtp_reply *smtp_reply_clone(pool_t pool, + const struct smtp_reply *src) +{ + struct smtp_reply *dst; + + dst = p_new(pool, struct smtp_reply, 1); + smtp_reply_copy(pool, dst, src); + + return dst; +} + +void smtp_reply_add_to_event(const struct smtp_reply *reply, + struct event_passthrough *e) +{ + const char *enh_code = smtp_reply_get_enh_code(reply); + + e->add_int("status_code", reply->status); + e->add_str("enhanced_code", enh_code); + if (!smtp_reply_is_success(reply)) + e->add_str("error", smtp_reply_get_message(reply)); +} diff --git a/src/lib-smtp/smtp-reply.h b/src/lib-smtp/smtp-reply.h new file mode 100644 index 0000000..713ce1f --- /dev/null +++ b/src/lib-smtp/smtp-reply.h @@ -0,0 +1,82 @@ +#ifndef SMTP_REPLY_H +#define SMTP_REPLY_H + +struct smtp_reply_enhanced_code { + /* x:class, y:subject, z:detail; + x==0 means no enhanced code present + x==9 means invalid/missing enhanced code in reply + */ + unsigned int x, y, z; +}; + +struct smtp_reply { + unsigned int status; + + struct smtp_reply_enhanced_code enhanced_code; + + const char *const *text_lines; +}; + +#define SMTP_REPLY_ENH_CODE(x, y, z) \ + (const struct smtp_reply_enhanced_code){(x), (y), (z)} +#define SMTP_REPLY_ENH_CODE_NONE SMTP_REPLY_ENH_CODE(0, 0, 0) + +static inline bool +smtp_reply_has_enhanced_code(const struct smtp_reply *reply) +{ + return reply->enhanced_code.x > 1 && reply->enhanced_code.x < 6; +} + +static inline bool +smtp_reply_is_success(const struct smtp_reply *reply) +{ + return ((reply->status / 100) == 2); +} + +static inline bool +smtp_reply_is_remote(const struct smtp_reply *reply) +{ + return (reply->status >= 200 && reply->status < 560); +} + +static inline bool +smtp_reply_is_temp_fail(const struct smtp_reply *reply) +{ + return ((reply->status / 100) == 4); +} + +void smtp_reply_init(struct smtp_reply *reply, unsigned int status, + const char *text); +void smtp_reply_printf(struct smtp_reply *reply, unsigned int status, + const char *format, ...) ATTR_FORMAT(3, 4); + +const char * +smtp_reply_get_enh_code(const struct smtp_reply *reply); +const char *const * +smtp_reply_get_text_lines_omit_prefix(const struct smtp_reply *reply); + +/* Write the SMTP reply as a sequence of lines according to the SMTP syntax, + each terminated by CRLF. */ +void smtp_reply_write(string_t *out, const struct smtp_reply *reply); +/* Write the SMTP reply as a single line without CRLF, even when it consists + of multiple lines. This function cannot be used with internal client error + replies (status code >= 560). */ +void smtp_reply_write_one_line(string_t *out, const struct smtp_reply *reply); +/* Create a log line from the SMTP reply. This also properly handles internal + client error replies (status_code >= 560). */ +const char *smtp_reply_log(const struct smtp_reply *reply); +/* Returns the message of the reply as a single line without status codes and + without CRLF. + */ +const char *smtp_reply_get_message(const struct smtp_reply *reply); + +void smtp_reply_copy(pool_t pool, struct smtp_reply *dst, + const struct smtp_reply *src); +struct smtp_reply *smtp_reply_clone(pool_t pool, + const struct smtp_reply *src); + +/* Set standard reply fields in provided pass-through event */ +void smtp_reply_add_to_event(const struct smtp_reply *reply, + struct event_passthrough *e); + +#endif diff --git a/src/lib-smtp/smtp-server-cmd-auth.c b/src/lib-smtp/smtp-server-cmd-auth.c new file mode 100644 index 0000000..841e186 --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-auth.c @@ -0,0 +1,242 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "smtp-syntax.h" +#include "smtp-command-parser.h" + +#include "smtp-server-private.h" + +/* AUTH command (RFC 4954) */ + + +static bool +cmd_auth_check_state(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + + /* RFC 4954, Section 4: + After an AUTH command has been successfully completed, no more + AUTH commands may be issued in the same session. After a + successful AUTH command completes, a server MUST reject any + further AUTH commands with a 503 reply. */ + if (conn->authenticated) { + smtp_server_reply(cmd, + 503, "5.5.0", "Already authenticated"); + return FALSE; + } + + /* RFC 4954, Section 4: + The AUTH command is not permitted during a mail transaction. + An AUTH command issued during a mail transaction MUST be + rejected with a 503 reply. */ + if (conn->state.trans != NULL) { + smtp_server_reply(cmd, 503, "5.5.0", + "Authentication not permitted during a mail transaction"); + return FALSE; + } + return TRUE; +} + +void smtp_server_cmd_auth_success(struct smtp_server_cmd_ctx *cmd, + const char *username, const char *success_msg) +{ + cmd->conn->username = i_strdup(username); + + smtp_server_reply(cmd, 235, "2.7.0", "%s", + (success_msg == NULL ? "Logged in." : success_msg)); +} + +static void +cmd_auth_completed(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_auth *data ATTR_UNUSED) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(smtp_server_command_is_replied(command)); + if (smtp_server_command_replied_success(command)) { + /* only one valid success status for AUTH command */ + i_assert(smtp_server_command_reply_status_equals(command, 235)); + conn->authenticated = TRUE; + } +} + +static void cmd_auth_input(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + struct smtp_server_command *command = cmd->cmd; + enum smtp_command_parse_error error_code; + const char *auth_response, *error; + int ret; + + /* parse response */ + if ((ret=smtp_command_parse_auth_response(conn->smtp_parser, + &auth_response, &error_code, &error)) <= 0) { + /* check for disconnect */ + if (conn->conn.input->eof) { + smtp_server_connection_close(&conn, + i_stream_get_disconnect_reason(conn->conn.input)); + return; + } + /* handle syntax error */ + if (ret < 0) { + e_debug(conn->event, + "Client sent invalid AUTH response: %s", error); + + smtp_server_command_input_lock(cmd); + switch (error_code) { + case SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND: + conn->input_broken = TRUE; + /* fall through */ + case SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND: + smtp_server_reply(cmd, 500, "5.5.2", + "Invalid AUTH response syntax"); + break; + case SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG: + smtp_server_reply(cmd, 500, "5.5.2", + "Line too long"); + break; + default: + i_unreached(); + } + } + if (conn->input_broken || conn->closing) + smtp_server_connection_input_halt(conn); + return; + } + + e_debug(conn->event, "Received AUTH response: %s", auth_response); + + smtp_server_command_input_lock(cmd); + + /* continue authentication */ + smtp_server_command_ref(command); + i_assert(callbacks != NULL && + callbacks->conn_cmd_auth_continue != NULL); + if ((ret=callbacks->conn_cmd_auth_continue(conn->context, + cmd, auth_response)) <= 0) { + /* command is waiting for external event or it failed */ + i_assert(ret == 0 || smtp_server_command_is_replied(command)); + smtp_server_command_unref(&command); + return; + } + if (!smtp_server_command_is_replied(command)) { + /* set generic AUTH success reply if none is provided */ + smtp_server_reply(cmd, 235, "2.7.0", "Logged in."); + } + conn->authenticated = TRUE; + smtp_server_command_unref(&command); +} + +void smtp_server_cmd_auth_send_challenge(struct smtp_server_cmd_ctx *cmd, + const char *challenge) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(command->prev == NULL && + command->reg->func == smtp_server_cmd_auth); + + smtp_server_connection_reply_immediate(conn, 334, "%s", challenge); + smtp_server_connection_timeout_reset(conn); + + /* start AUTH-specific input handling */ + smtp_server_command_input_capture(cmd, cmd_auth_input); +} + +static void +cmd_auth_start(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_auth *data) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + int ret; + + /* all preceeding commands have finished and now the transaction state + is clear. This provides the opportunity to re-check the protocol + state */ + if (!cmd_auth_check_state(cmd)) + return; + + /* advance state */ + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_AUTH, NULL); + + smtp_server_command_ref(command); + i_assert(callbacks != NULL && callbacks->conn_cmd_auth != NULL); + + /* specific implementation of AUTH command */ + ret = callbacks->conn_cmd_auth(conn->context, cmd, data); + i_assert(ret == 0 || smtp_server_command_is_replied(command)); + + if (ret == 0) + smtp_server_connection_timeout_stop(conn); + + smtp_server_command_unref(&command); + return; +} + +void smtp_server_cmd_auth(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_cmd_auth *auth_data; + const char *sasl_mech, *initial_response; + const char *const *argv; + int ret = 1; + + if ((conn->set.capabilities & SMTP_CAPABILITY_AUTH) == 0) { + smtp_server_reply(cmd, + 502, "5.5.1", "Unsupported command"); + return; + } + + /* RFC 4954, Section 8: + + auth-command = "AUTH" SP sasl-mech [SP initial-response] + *(CRLF [base64]) [CRLF cancel-response] + CRLF + ;; is defined in [SASL] + + initial-response = base64 / "=" + */ + argv = t_strsplit(params, " "); + initial_response = sasl_mech = NULL; + if (argv[0] == NULL) { + smtp_server_reply(cmd, + 501, "5.5.4", "Missing SASL mechanism parameter"); + ret = -1; + } else { + sasl_mech = argv[0]; + + if (argv[1] != NULL) { + if (argv[2] != NULL) { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid parameters"); + ret = -1; + } else { + initial_response = argv[1]; + } + } + } + if (ret < 0) + return; + + /* check protocol state */ + if (!cmd_auth_check_state(cmd)) + return; + + smtp_server_command_input_lock(cmd); + + auth_data = p_new(cmd->pool, struct smtp_server_cmd_auth, 1); + auth_data->sasl_mech = p_strdup(cmd->pool, sasl_mech); + auth_data->initial_response = p_strdup(cmd->pool, initial_response); + + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT, + cmd_auth_start, auth_data); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED, + cmd_auth_completed, auth_data); +} diff --git a/src/lib-smtp/smtp-server-cmd-data.c b/src/lib-smtp/smtp-server-cmd-data.c new file mode 100644 index 0000000..7f33fdf --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-data.c @@ -0,0 +1,679 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "istream.h" +#include "istream-chain.h" + +#include "smtp-command-parser.h" +#include "smtp-server-private.h" + +/* DATA/BDAT/B... commands */ + +struct cmd_data_context { + struct istream *main_input; + struct istream *chunk_input; + uoff_t chunk_size; + + bool chunking:1; + bool client_input:1; + bool chunk_first:1; + bool chunk_last:1; +}; + +static void +smtp_server_cmd_data_size_limit_exceeded(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + + smtp_server_command_fail(command, 552, "5.2.3", + "Message size exceeds administrative limit"); +} + +bool smtp_server_cmd_data_check_size(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + const struct smtp_server_settings *set = &conn->set; + + i_assert(conn->state.state == SMTP_SERVER_STATE_DATA); + + if (conn->state.data_input == NULL) + return TRUE; + if (set->max_message_size == 0) + return TRUE; + if (conn->state.data_input->v_offset <= set->max_message_size) + return TRUE; + + smtp_server_cmd_data_size_limit_exceeded(cmd); + return FALSE; +} + +bool smtp_server_connection_data_check_state(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + struct cmd_data_context *data_cmd = command->data; + + if (conn->state.data_chunks > 0 && conn->state.data_failed) { + // FIXME: should it even reply anything? RFC is unclear. + smtp_server_command_fail(command, 503, "5.5.0", + "Previous data chunk failed, issue RSET first"); + return FALSE; + } + + /* check valid MAIL */ + if (conn->state.trans == NULL + && conn->state.pending_mail_cmds == 0) { + smtp_server_command_fail(command, + 503, "5.5.0", "MAIL needed first"); + return FALSE; + } + if (conn->state.trans != NULL && + (conn->state.trans->params.body.type == + SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME) && + !data_cmd->chunking) { + /* RFC 3030, Section 3: + BINARYMIME cannot be used with the DATA command. If a DATA + command is issued after a MAIL command containing the + body-value of "BINARYMIME", a 503 "Bad sequence of commands" + response MUST be sent. The resulting state from this error + condition is indeterminate and the transaction MUST be reset + with the RSET command. */ + smtp_server_command_fail(command, + 503, "5.5.0", "DATA cannot be used with BINARYMIME"); + return FALSE; + } + + /* Can only decide whether we have valid recipients once there are no + pending RCPT commands */ + if (conn->state.pending_rcpt_cmds > 0) + return TRUE; + + /* special handling for LMTP */ + if (conn->set.protocol == SMTP_PROTOCOL_LMTP) { + /* check valid RCPT (at least one) */ + if (conn->state.trans == NULL || + !smtp_server_transaction_has_rcpt(conn->state.trans)) { + if (data_cmd->chunk_size > 0 && data_cmd->chunk_last) { + /* RFC 2033, Section 4.3: + If there were no previously successful RCPT + commands in the mail transaction, then the + BDAT LAST command returns zero replies. + */ + smtp_server_command_abort(&command); + } else { + /* RFC 2033, Section 4.2: + The additional restriction is that when there + have been no successful RCPT commands in the + mail transaction, the DATA command MUST fail + with a 503 reply code. + */ + smtp_server_command_fail(command, + 503, "5.5.0", "No valid recipients"); + } + return FALSE; + } + + } else { + /* check valid RCPT (at least one) */ + if (conn->state.trans == NULL || + !smtp_server_transaction_has_rcpt(conn->state.trans)) { + smtp_server_command_fail(command, + 554, "5.5.0", "No valid recipients"); + return FALSE; + } + } + return TRUE; +} + +static void +cmd_data_destroy(struct smtp_server_cmd_ctx *cmd, + struct cmd_data_context *data_cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(data_cmd != NULL); + + if (data_cmd->main_input == conn->state.data_input && + (data_cmd->chunk_last || + !smtp_server_command_replied_success(command))) { + /* clean up */ + i_stream_destroy(&conn->state.data_input); + i_stream_destroy(&conn->state.data_chain_input); + conn->state.data_chain = NULL; + } + + i_stream_unref(&data_cmd->chunk_input); +} + +static void +cmd_data_replied_one(struct smtp_server_cmd_ctx *cmd, + struct cmd_data_context *data_cmd ATTR_UNUSED) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_transaction *trans = conn->state.trans; + struct smtp_server_recipient *rcpt; + + if (trans == NULL || !array_is_created(&trans->rcpt_to)) + return; + + array_foreach_elem(&trans->rcpt_to, rcpt) + smtp_server_recipient_data_replied(rcpt); +} + +static void +cmd_data_replied(struct smtp_server_cmd_ctx *cmd, + struct cmd_data_context *data_cmd ATTR_UNUSED) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(conn->state.pending_data_cmds > 0); + conn->state.pending_data_cmds--; + + smtp_server_command_input_lock(cmd); + if (!smtp_server_command_replied_success(command)) + smtp_server_command_input_unlock(cmd); +} + +static void +cmd_data_completed(struct smtp_server_cmd_ctx *cmd, + struct cmd_data_context *data_cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + + i_assert(data_cmd != NULL); + i_stream_unref(&data_cmd->chunk_input); + + i_assert(conn->state.trans != NULL); + smtp_server_transaction_finished(conn->state.trans, cmd); + + /* reset state */ + smtp_server_connection_reset_state(conn); +} + +static void +cmd_data_chunk_replied(struct smtp_server_cmd_ctx *cmd, + struct cmd_data_context *data_cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(data_cmd != NULL); + + i_assert(conn->state.pending_data_cmds > 0); + conn->state.pending_data_cmds--; + + i_assert(smtp_server_command_is_replied(command)); + if (!smtp_server_command_replied_success(command) && + conn->state.pending_data_cmds == 0) + conn->state.data_failed = TRUE; +} + +static void +cmd_data_chunk_completed(struct smtp_server_cmd_ctx *cmd, + struct cmd_data_context *data_cmd ATTR_UNUSED) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + if (!smtp_server_command_replied_success(command)) + conn->state.data_failed = TRUE; +} + +static void +cmd_data_chunk_finish(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + struct cmd_data_context *data_cmd = command->data; + + smtp_server_command_input_lock(cmd); + i_stream_unref(&data_cmd->chunk_input); + + /* re-check transaction state (for BDAT/B... command) */ + if (!smtp_server_connection_data_check_state(cmd)) + return; + + smtp_server_reply(cmd, 250, "2.0.0", + "Added %"PRIuUOFF_T" octets", data_cmd->chunk_size); +} + +static void cmd_data_input_error(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + struct cmd_data_context *data_cmd = command->data; + struct istream *data_input = conn->state.data_input; + const char *error; + + conn->state.data_failed = TRUE; + + if (!data_cmd->client_input) { + if (!smtp_server_command_is_replied(command)) { + smtp_server_command_fail(command, + 400, "4.0.0", "Failed to add data"); + } + return; + } + + error = i_stream_get_disconnect_reason(data_input); + e_debug(conn->event, "Connection lost during data transfer: %s", error); + smtp_server_connection_close(&conn, error); +} + +static int cmd_data_do_handle_input(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + struct smtp_server_command *command = cmd->cmd; + struct cmd_data_context *data_cmd = command->data; + int ret; + + i_assert(data_cmd != NULL); + + i_assert(callbacks != NULL && + callbacks->conn_cmd_data_continue != NULL); + struct event_reason *reason = + smtp_server_connection_reason_begin(conn, "cmd_data"); + ret = callbacks->conn_cmd_data_continue(conn->context, + cmd, conn->state.trans); + event_reason_end(&reason); + if (ret >= 0) { + if (!smtp_server_cmd_data_check_size(cmd)) { + return -1; + } else if (!i_stream_have_bytes_left(conn->state.data_input)) { + e_debug(cmd->event, "End of data"); + smtp_server_transaction_received( + conn->state.trans, + conn->state.data_input->v_offset); + smtp_server_command_input_lock(cmd); + smtp_server_connection_timeout_stop(conn); + } else if (!data_cmd->chunk_last && + !i_stream_have_bytes_left(data_cmd->chunk_input)) { + e_debug(cmd->event, "End of chunk"); + cmd_data_chunk_finish(cmd); + } else if (i_stream_get_data_size( + conn->state.data_input) > 0) { + e_debug(cmd->event, "Not all client data read"); + smtp_server_connection_timeout_stop(cmd->conn); + } else { + smtp_server_connection_timeout_start(cmd->conn); + } + } else { + if (conn->state.data_input->stream_errno != 0) { + cmd_data_input_error(cmd); + return -1; + } + /* command is waiting for external event or it failed */ + i_assert(smtp_server_command_is_replied(command)); + } + + return 1; +} + +static int cmd_data_handle_input(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + int ret; + + if (!smtp_server_cmd_data_check_size(cmd)) + return -1; + + smtp_server_connection_ref(conn); + smtp_server_command_ref(command); + + /* continue reading from client */ + ret = cmd_data_do_handle_input(cmd); + + smtp_server_command_unref(&command); + smtp_server_connection_unref(&conn); + + return ret; +} + +static void cmd_data_input(struct smtp_server_cmd_ctx *cmd) +{ + smtp_server_connection_timeout_reset(cmd->conn); + (void)cmd_data_handle_input(cmd); +} + +static void +cmd_data_next(struct smtp_server_cmd_ctx *cmd, + struct cmd_data_context *data_cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_transaction *trans = conn->state.trans; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + struct smtp_server_command *command = cmd->cmd; + + /* this command is next to send a reply */ + + i_assert(data_cmd != NULL); + i_assert(trans != NULL); + + /* DATA command stops the pipeline, so if it is next to reply, nothing + else can be pending. */ + i_assert(conn->state.pending_mail_cmds == 0 && + conn->state.pending_rcpt_cmds == 0); + + e_debug(cmd->event, "Command is next to be replied"); + + smtp_server_transaction_data_command(trans, cmd); + + /* check whether we have had successful mail and rcpt commands */ + if (!smtp_server_connection_data_check_state(cmd)) + return; + + if (data_cmd->chunk_last) { + /* LMTP 'DATA' and 'BDAT LAST' commands need to send more than + one reply per recipient */ + if (HAS_ALL_BITS(trans->flags, + SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)) { + smtp_server_command_set_reply_count(command, + array_count(&trans->rcpt_to)); + } + } + + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_DATA, NULL); + + /* chain data streams in the correct order */ + if (conn->state.data_chain != NULL) { + i_assert(data_cmd->chunk_input != NULL); + i_stream_chain_append(conn->state.data_chain, + data_cmd->chunk_input); + if (data_cmd->chunk_last) { + e_debug(cmd->event, "Seen the last chunk"); + i_stream_chain_append_eof(conn->state.data_chain); + } + } + + if (data_cmd->chunk_first) { + struct smtp_server_command *cmd_temp = command; + + e_debug(cmd->event, "First chunk"); + + smtp_server_command_ref(cmd_temp); + i_assert(callbacks != NULL && + callbacks->conn_cmd_data_begin != NULL); + i_assert(conn->state.data_input != NULL); + struct event_reason *reason = + smtp_server_connection_reason_begin(conn, "cmd_data"); + int ret = callbacks->conn_cmd_data_begin(conn->context, + cmd, conn->state.trans, conn->state.data_input); + event_reason_end(&reason); + if (ret < 0) { + i_assert(smtp_server_command_is_replied(cmd_temp)); + /* command failed */ + smtp_server_command_unref(&cmd_temp); + return; + } + if (!smtp_server_command_unref(&cmd_temp)) + return; + } + + if (smtp_server_command_is_replied(command)) { + smtp_server_command_input_unlock(cmd); + } else { + if (data_cmd->client_input) { + /* using input from client connection; + capture I/O event */ + smtp_server_connection_timeout_start(conn); + smtp_server_command_input_capture(cmd, cmd_data_input); + } + + (void)cmd_data_handle_input(cmd); + } +} + +static void +cmd_data_start_input(struct smtp_server_cmd_ctx *cmd, + struct cmd_data_context *data_cmd, struct istream *input) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(data_cmd != NULL); + + if (input != NULL) { + i_assert(conn->state.data_input == NULL); + conn->state.data_input = input; + i_stream_ref(input); + } + data_cmd->main_input = conn->state.data_input; + + if (data_cmd->client_input) + smtp_server_command_input_lock(cmd); + + if (data_cmd->chunk_last) { + smtp_server_command_add_hook( + command, SMTP_SERVER_COMMAND_HOOK_COMPLETED, + cmd_data_completed, data_cmd); + } else { + smtp_server_command_add_hook( + command, SMTP_SERVER_COMMAND_HOOK_COMPLETED, + cmd_data_chunk_completed, data_cmd); + } + + if (conn->state.pending_mail_cmds == 0 && + conn->state.pending_rcpt_cmds == 0) { + cmd_data_next(cmd, data_cmd); + } else { + smtp_server_command_add_hook( + command, SMTP_SERVER_COMMAND_HOOK_NEXT, + cmd_data_next, data_cmd); + } +} + +/* DATA command */ + +static void +cmd_data_start(struct smtp_server_cmd_ctx *cmd, + struct cmd_data_context *data_cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_transaction *trans = conn->state.trans; + struct istream *dot_input; + + /* called when all previous commands were finished */ + i_assert(conn->state.pending_mail_cmds == 0 && + conn->state.pending_rcpt_cmds == 0); + + if (trans != NULL) + smtp_server_transaction_data_command(trans, cmd); + + /* check whether we have had successful mail and rcpt commands */ + if (!smtp_server_connection_data_check_state(cmd)) + return; + + /* don't allow classic DATA when CHUNKING sequence was started before */ + if (conn->state.data_chunks > 0) { + smtp_server_command_fail(cmd->cmd, + 503, "5.5.0", "Bad sequence of commands"); + return; + } + + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_DATA, NULL); + + /* confirm initial success to client */ + smtp_server_connection_reply_immediate(conn, 354, "OK"); + + /* start reading message data from client */ + dot_input = smtp_command_parse_data_with_dot(conn->smtp_parser); + cmd_data_start_input(cmd, data_cmd, dot_input); + i_stream_unref(&dot_input); +} + +void smtp_server_cmd_data(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + struct cmd_data_context *data_cmd; + + /* data = "DATA" CRLF */ + if (*params != '\0') { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid parameters"); + return; + } + + smtp_server_command_input_lock(cmd); + + data_cmd = p_new(cmd->pool, struct cmd_data_context, 1); + data_cmd->chunk_first = TRUE; + data_cmd->chunk_last = TRUE; + data_cmd->client_input = TRUE; + command->data = data_cmd; + + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT, + cmd_data_start, data_cmd); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_REPLIED_ONE, + cmd_data_replied_one, data_cmd); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_REPLIED, + cmd_data_replied, data_cmd); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_DESTROY, + cmd_data_destroy, data_cmd); + + conn->state.pending_data_cmds++; +} + +/* BDAT/B... commands */ + +void smtp_server_connection_data_chunk_init(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + struct cmd_data_context *data_cmd; + + data_cmd = p_new(cmd->pool, struct cmd_data_context, 1); + data_cmd->chunking = TRUE; + data_cmd->chunk_first = (conn->state.data_chunks++ == 0); + command->data = data_cmd; + + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_REPLIED, + cmd_data_chunk_replied, data_cmd); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_DESTROY, + cmd_data_destroy, data_cmd); + + conn->state.pending_data_cmds++; + + if (!conn->state.data_failed && conn->state.data_chain == NULL) { + i_assert(data_cmd->chunk_first); + i_assert(conn->state.data_chain_input == NULL); + conn->state.data_chain_input = + i_stream_create_chain(&conn->state.data_chain, + IO_BLOCK_SIZE); + } +} + +int smtp_server_connection_data_chunk_add(struct smtp_server_cmd_ctx *cmd, + struct istream *chunk, uoff_t chunk_size, bool chunk_last, + bool client_input) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_transaction *trans = conn->state.trans; + const struct smtp_server_settings *set = &conn->set; + struct smtp_server_command *command = cmd->cmd; + struct cmd_data_context *data_cmd = command->data; + uoff_t new_size; + + i_assert(data_cmd != NULL); + + if (trans != NULL) + smtp_server_transaction_data_command(trans, cmd); + + if (!smtp_server_connection_data_check_state(cmd)) + return -1; + + /* check message size increase early */ + new_size = conn->state.data_size + chunk_size; + if (new_size < conn->state.data_size || + (set->max_message_size > 0 && new_size > set->max_message_size)) { + smtp_server_cmd_data_size_limit_exceeded(cmd); + return -1; + } + conn->state.data_size = new_size; + + if (chunk_last) { + smtp_server_command_remove_hook( + command, SMTP_SERVER_COMMAND_HOOK_REPLIED, + cmd_data_chunk_replied); + smtp_server_command_add_hook( + command, SMTP_SERVER_COMMAND_HOOK_REPLIED, + cmd_data_replied, data_cmd); + } + + data_cmd->chunk_input = chunk; + data_cmd->chunk_size = chunk_size; + data_cmd->chunk_last = chunk_last; + data_cmd->client_input = client_input; + i_stream_ref(chunk); + + cmd_data_start_input(cmd, data_cmd, conn->state.data_chain_input); + i_stream_unref(&conn->state.data_chain_input); + return 0; +} + +/* BDAT command */ + +void smtp_server_cmd_bdat(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + struct istream *input = NULL; + uoff_t size = 0; + const char *const *argv; + bool chunk_last = FALSE; + int ret = 1; + + if ((conn->set.capabilities & SMTP_CAPABILITY_CHUNKING) == 0) { + smtp_server_reply(cmd, + 502, "5.5.1", "Unsupported command"); + return; + } + + smtp_server_connection_data_chunk_init(cmd); + + /* bdat-cmd = "BDAT" SP chunk-size [ SP end-marker ] CR LF + chunk-size = 1*DIGIT + end-marker = "LAST" + */ + argv = t_strsplit(params, " "); + if (argv[0] == NULL || str_to_uoff(argv[0], &size) < 0) { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid chunk size parameter"); + size = 0; + ret = -1; + } else if (argv[1] != NULL) { + if (argv[2] != NULL) { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid parameters"); + ret = -1; + } else if (strcasecmp(argv[1], "LAST") != 0) { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid end marker parameter"); + ret = -1; + } else { + chunk_last = TRUE; + } + } + + if (ret > 0 || (size > 0 && !conn->disconnected)) { + /* Read/skip data even in case of error, as long as size is + known and connection is still usable. */ + input = smtp_command_parse_data_with_size(conn->smtp_parser, + size); + } + + if (ret < 0) { + i_stream_unref(&input); + return; + } + + (void)smtp_server_connection_data_chunk_add(cmd, + input, size, chunk_last, TRUE); + i_stream_unref(&input); +} diff --git a/src/lib-smtp/smtp-server-cmd-helo.c b/src/lib-smtp/smtp-server-cmd-helo.c new file mode 100644 index 0000000..2f03ceb --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-helo.c @@ -0,0 +1,196 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "smtp-syntax.h" + +#include "smtp-server-private.h" + +/* EHLO, HELO commands */ + +static void +cmd_helo_completed(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_helo *data) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(smtp_server_command_is_replied(command)); + if (!smtp_server_command_replied_success(command)) { + /* Failure */ + return; + } + + if (conn->pending_helo == &data->helo) + conn->pending_helo = NULL; + + /* Success */ + smtp_server_connection_reset_state(conn); + + i_free(conn->helo_domain); + conn->helo_domain = i_strdup(data->helo.domain); + conn->helo.domain = conn->helo_domain; + conn->helo.domain_valid = data->helo.domain_valid; + conn->helo.old_smtp = data->helo.old_smtp; +} + +static void +cmd_helo_next(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_helo *data) +{ + struct smtp_server_connection *conn = cmd->conn; + + if (null_strcmp(conn->helo.domain, data->helo.domain) != 0 || + conn->helo.old_smtp != data->helo.old_smtp) + data->changed = TRUE; /* Definitive assessment */ +} + +static void +smtp_server_cmd_helo_run(struct smtp_server_cmd_ctx *cmd, const char *params, + bool old_smtp) +{ + struct smtp_server_connection *conn = cmd->conn; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + struct smtp_server_cmd_helo *helo_data; + struct smtp_server_command *command = cmd->cmd; + bool first = (conn->pending_helo == NULL && conn->helo.domain == NULL); + const char *domain = NULL; + int ret; + + /* Parse domain argument */ + + if (*params == '\0') { + smtp_server_reply(cmd, 501, "", "Missing hostname"); + return; + } + ret = smtp_helo_domain_parse(params, !old_smtp, &domain); + + smtp_server_command_pipeline_block(cmd); + if (conn->state.state == SMTP_SERVER_STATE_GREETING) { + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_HELO, + NULL); + } + + helo_data = p_new(cmd->pool, struct smtp_server_cmd_helo, 1); + helo_data->helo.domain = p_strdup(cmd->pool, domain); + helo_data->helo.domain_valid = ( ret >= 0 ); + helo_data->helo.old_smtp = old_smtp; + helo_data->first = first; + command->data = helo_data; + + if (null_strcmp(conn->helo.domain, domain) != 0 || + conn->helo.old_smtp != old_smtp) + helo_data->changed = TRUE; /* Preliminary assessment */ + + if (conn->pending_helo == NULL) + conn->pending_helo = &helo_data->helo; + + smtp_server_command_add_hook( + command, SMTP_SERVER_COMMAND_HOOK_NEXT, + cmd_helo_next, helo_data); + smtp_server_command_add_hook( + command, SMTP_SERVER_COMMAND_HOOK_COMPLETED, + cmd_helo_completed, helo_data); + + smtp_server_command_ref(command); + if (callbacks != NULL && callbacks->conn_cmd_helo != NULL) { + /* Specific implementation of EHLO command */ + ret = callbacks->conn_cmd_helo(conn->context, cmd, helo_data); + if (ret <= 0) { + i_assert(ret == 0 || + smtp_server_command_is_replied(command)); + /* Command is waiting for external event or it failed */ + smtp_server_command_unref(&command); + return; + } + } + + if (!smtp_server_command_is_replied(command)) { + /* Submit default EHLO reply if none is provided */ + smtp_server_cmd_ehlo_reply_default(cmd); + } + smtp_server_command_unref(&command); +} + +void smtp_server_cmd_ehlo(struct smtp_server_cmd_ctx *cmd, const char *params) +{ + /* ehlo = "EHLO" SP ( Domain / address-literal ) CRLF */ + + smtp_server_cmd_helo_run(cmd, params, FALSE); +} + +void smtp_server_cmd_helo(struct smtp_server_cmd_ctx *cmd, const char *params) +{ + /* helo = "HELO" SP Domain CRLF */ + + smtp_server_cmd_helo_run(cmd, params, TRUE); +} + +struct smtp_server_reply * +smtp_server_cmd_ehlo_reply_create(struct smtp_server_cmd_ctx *cmd) +{ + static struct { + const char *name; + void (*add)(struct smtp_server_reply *reply); + } standard_caps[] = { + /* Sorted alphabetically */ + { "8BITMIME", smtp_server_reply_ehlo_add_8bitmime }, + { "BINARYMIME", smtp_server_reply_ehlo_add_binarymime }, + { "CHUNKING", smtp_server_reply_ehlo_add_chunking }, + { "DSN", smtp_server_reply_ehlo_add_dsn }, + { "ENHANCEDSTATUSCODES", + smtp_server_reply_ehlo_add_enhancedstatuscodes }, + { "PIPELINING", smtp_server_reply_ehlo_add_pipelining }, + { "SIZE", smtp_server_reply_ehlo_add_size }, + { "STARTTLS", smtp_server_reply_ehlo_add_starttls }, + { "VRFY", smtp_server_reply_ehlo_add_vrfy }, + { "XCLIENT", smtp_server_reply_ehlo_add_xclient } + }; + const unsigned int standard_caps_count = N_ELEMENTS(standard_caps); + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_cmd_helo *helo_data = command->data; + const struct smtp_capability_extra *extra_caps = NULL; + unsigned int extra_caps_count, i, j; + struct smtp_server_reply *reply; + + reply = smtp_server_reply_create_ehlo(cmd->cmd); + + if (helo_data->helo.old_smtp) { + i_assert(cmd->cmd->reg->func == smtp_server_cmd_helo); + return reply; + } + i_assert(cmd->cmd->reg->func == smtp_server_cmd_ehlo); + + extra_caps_count = 0; + if (array_is_created(&conn->extra_capabilities)) { + extra_caps = array_get(&conn->extra_capabilities, + &extra_caps_count); + } + + i = j = 0; + while (i < standard_caps_count || j < extra_caps_count) { + if (i < standard_caps_count && + (j >= extra_caps_count || + strcasecmp(standard_caps[i].name, + extra_caps[j].name) < 0)) { + standard_caps[i].add(reply); + i++; + } else { + smtp_server_reply_ehlo_add_params( + reply, extra_caps[j].name, + extra_caps[j].params); + j++; + } + } + return reply; +} + +void smtp_server_cmd_ehlo_reply_default(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_reply *reply; + + reply = smtp_server_cmd_ehlo_reply_create(cmd); + smtp_server_reply_submit(reply); +} diff --git a/src/lib-smtp/smtp-server-cmd-mail.c b/src/lib-smtp/smtp-server-cmd-mail.c new file mode 100644 index 0000000..82b8e81 --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-mail.c @@ -0,0 +1,216 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "smtp-parser.h" +#include "smtp-syntax.h" +#include "smtp-address.h" + +#include "smtp-server-private.h" + +/* MAIL command */ + +static bool cmd_mail_check_state(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + if (smtp_server_command_is_replied(command) && + !smtp_server_command_replied_success(command) && + !smtp_server_command_reply_is_forwarded(command)) + return FALSE; + + if (conn->state.trans != NULL) { + smtp_server_reply(cmd, 503, "5.5.0", "MAIL already given"); + return FALSE; + } + return TRUE; +} + +static void +cmd_mail_completed(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_mail *data) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(conn->state.pending_mail_cmds > 0); + conn->state.pending_mail_cmds--; + + i_assert(smtp_server_command_is_replied(command)); + i_assert(conn->state.state == SMTP_SERVER_STATE_MAIL_FROM || + !smtp_server_command_replied_success(command)); + + if (!smtp_server_command_replied_success(command)) { + /* Failure */ + return; + } + + /* Success */ + conn->state.trans = smtp_server_transaction_create(conn, data); +} + +static void +cmd_mail_recheck(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_mail *data) +{ + struct smtp_server_connection *conn = cmd->conn; + + /* All preceeding commands have finished and now the transaction state + is clear. This provides the opportunity to re-check the transaction + state */ + if (!cmd_mail_check_state(cmd)) + return; + + /* Advance state */ + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_MAIL_FROM, + smtp_address_encode(data->path)); +} + +void smtp_server_cmd_mail(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + const struct smtp_server_settings *set = &conn->set; + enum smtp_capability caps = set->capabilities; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_cmd_mail *mail_data; + enum smtp_address_parse_flags path_parse_flags; + const char *const *param_extensions = NULL; + struct smtp_address *path = NULL; + enum smtp_param_parse_error pperror; + const char *error; + int ret; + + /* mail = "MAIL FROM:" Reverse-path [SP Mail-parameters] CRLF + Reverse-path = Path / "<>" + */ + + /* Check transaction state as far as possible */ + if (!cmd_mail_check_state(cmd)) + return; + + /* Reverse-path */ + if (params == NULL || strncasecmp(params, "FROM:", 5) != 0) { + smtp_server_reply(cmd, 501, "5.5.4", "Invalid parameters"); + return; + } + if (params[5] != ' ' && params[5] != '\t') { + params += 5; + } else if ((set->workarounds & + SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH) != 0) { + params += 5; + while (*params == ' ' || *params == '\t') + params++; + } else { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid FROM: " + "Unexpected whitespace before path"); + return; + } + path_parse_flags = + SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW; + if (*params != '\0' && + (set->workarounds & SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH) != 0) + path_parse_flags |= SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL; + if (set->mail_path_allow_broken) { + path_parse_flags |= + SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN; + } + ret = smtp_address_parse_path_full(pool_datastack_create(), params, + path_parse_flags, &path, &error, + ¶ms); + if (ret < 0 && !smtp_address_is_broken(path)) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid FROM: %s", error); + return; + } + if (*params == ' ') + params++; + else if (*params != '\0') { + smtp_server_reply( + cmd, 501, "5.5.4", + "Invalid FROM: Invalid character in path"); + return; + } + if (ret < 0) { + i_assert(set->mail_path_allow_broken); + e_debug(conn->event, "Invalid FROM: %s " + "(proceeding with <> as sender)", error); + } + + mail_data = p_new(cmd->pool, struct smtp_server_cmd_mail, 1); + + if (conn->set.protocol == SMTP_PROTOCOL_LMTP) + mail_data->flags |= SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT; + + /* [SP Mail-parameters] */ + if (array_is_created(&conn->mail_param_extensions)) + param_extensions = array_front(&conn->mail_param_extensions); + if (smtp_params_mail_parse(cmd->pool, params, caps, param_extensions, + NULL, &mail_data->params, &pperror, + &error) < 0) { + switch (pperror) { + case SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX: + smtp_server_reply(cmd, 501, "5.5.4", "%s", error); + break; + case SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED: + smtp_server_reply(cmd, 555, "5.5.4", "%s", error); + break; + default: + i_unreached(); + } + return; + } + + if ((caps & SMTP_CAPABILITY_SIZE) != 0 && set->max_message_size > 0 && + mail_data->params.size > set->max_message_size) { + smtp_server_reply(cmd, 552, "5.2.3", + "Message size exceeds administrative limit"); + return; + } + + mail_data->path = smtp_address_clone(cmd->pool, path); + mail_data->timestamp = ioloop_timeval; + + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT, + cmd_mail_recheck, mail_data); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED, + cmd_mail_completed, mail_data); + + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_MAIL_FROM, + smtp_address_encode(mail_data->path)); + conn->state.pending_mail_cmds++; + + smtp_server_command_ref(command); + if (callbacks != NULL && callbacks->conn_cmd_mail != NULL) { + /* Specific implementation of MAIL command */ + struct event_reason *reason = + smtp_server_connection_reason_begin(conn, "cmd_mail"); + ret = callbacks->conn_cmd_mail(conn->context, cmd, mail_data); + event_reason_end(&reason); + if (ret <= 0) { + i_assert(ret == 0 || + smtp_server_command_is_replied(command)); + /* Command is waiting for external event or it failed */ + smtp_server_command_unref(&command); + return; + } + } + if (!smtp_server_command_is_replied(command)) { + /* Set generic MAIL success reply if none is provided */ + smtp_server_cmd_mail_reply_success(cmd); + } + smtp_server_command_unref(&command); +} + +void smtp_server_cmd_mail_reply_success(struct smtp_server_cmd_ctx *cmd) +{ + i_assert(cmd->cmd->reg->func == smtp_server_cmd_mail); + + smtp_server_reply(cmd, 250, "2.1.0", "OK"); +} diff --git a/src/lib-smtp/smtp-server-cmd-noop.c b/src/lib-smtp/smtp-server-cmd-noop.c new file mode 100644 index 0000000..86e426f --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-noop.c @@ -0,0 +1,52 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "smtp-syntax.h" + +#include "smtp-server-private.h" + +/* NOOP command */ + +void smtp_server_cmd_noop(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + const char *param, *error; + int ret; + + /* "NOOP" [ SP String ] CRLF */ + ret = smtp_string_parse(params, ¶m, &error); + if (ret < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid string parameter: %s", + error); + return; + } + + smtp_server_command_input_lock(cmd); + + smtp_server_command_ref(command); + if (callbacks != NULL && callbacks->conn_cmd_noop != NULL) { + /* specific implementation of NOOP command */ + ret = callbacks->conn_cmd_noop(conn->context, cmd); + if (ret <= 0) { + i_assert(ret == 0 || + smtp_server_command_is_replied(command)); + /* command is waiting for external event or it failed */ + smtp_server_command_unref(&command); + return; + } + } + if (!smtp_server_command_is_replied(command)) + smtp_server_cmd_noop_reply_success(cmd); + smtp_server_command_unref(&command); +} + +void smtp_server_cmd_noop_reply_success(struct smtp_server_cmd_ctx *cmd) +{ + i_assert(cmd->cmd->reg->func == smtp_server_cmd_noop); + + smtp_server_reply(cmd, 250, "2.0.0", "OK"); +} diff --git a/src/lib-smtp/smtp-server-cmd-quit.c b/src/lib-smtp/smtp-server-cmd-quit.c new file mode 100644 index 0000000..04764fd --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-quit.c @@ -0,0 +1,42 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#include "smtp-server-private.h" + +/* QUIT command */ + +void smtp_server_cmd_quit(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + int ret; + + /* "QUIT" CRLF */ + if (*params != '\0') { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid parameters"); + return; + } + + smtp_server_connection_input_halt(conn); + + smtp_server_command_ref(command); + if (callbacks != NULL && callbacks->conn_cmd_quit != NULL) { + /* specific implementation of QUIT command */ + if ((ret = callbacks->conn_cmd_quit(conn->context, cmd)) <= 0) { + i_assert(ret == 0 || + smtp_server_command_is_replied(command)); + /* command is waiting for external event or it failed */ + smtp_server_command_unref(&command); + return; + } + } + if (!smtp_server_command_is_replied(command)) { + /* set generic QUIT success reply if none is provided */ + smtp_server_reply_quit(cmd); + } + smtp_server_command_unref(&command); +} diff --git a/src/lib-smtp/smtp-server-cmd-rcpt.c b/src/lib-smtp/smtp-server-cmd-rcpt.c new file mode 100644 index 0000000..85e9a93 --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-rcpt.c @@ -0,0 +1,243 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "smtp-parser.h" +#include "smtp-address.h" +#include "smtp-reply.h" +#include "smtp-syntax.h" + +#include "smtp-server-private.h" + +/* RCPT command */ + +struct smtp_server_cmd_rcpt { + struct smtp_server_recipient *rcpt; +}; + +static void +cmd_rcpt_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_cmd_rcpt *data) +{ + smtp_server_recipient_destroy(&data->rcpt); +} + +static bool +cmd_rcpt_check_state(struct smtp_server_cmd_ctx *cmd, bool next_to_reply) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_transaction *trans = conn->state.trans; + + if (smtp_server_command_is_replied(command) && + !smtp_server_command_replied_success(command) && + !smtp_server_command_reply_is_forwarded(command)) + return FALSE; + + if (trans == NULL && + (conn->state.pending_mail_cmds == 0 || next_to_reply)) { + smtp_server_reply(cmd, + 503, "5.5.0", "MAIL needed first"); + return FALSE; + } + if (conn->set.max_recipients > 0 && trans != NULL && + smtp_server_transaction_rcpt_count(trans) >= + conn->set.max_recipients) { + smtp_server_reply(cmd, + 451, "4.5.3", "Too many recipients"); + return FALSE; + } + + return TRUE; +} + +static void +cmd_rcpt_completed(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_rcpt *data) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_recipient *rcpt = data->rcpt; + + i_assert(conn->state.pending_rcpt_cmds > 0); + conn->state.pending_rcpt_cmds--; + + i_assert(smtp_server_command_is_replied(command)); + i_assert(conn->state.state == SMTP_SERVER_STATE_RCPT_TO || + !smtp_server_command_replied_success(command)); + + if (!smtp_server_command_replied_success(command)) { + /* Failure */ + conn->state.denied_rcpt_cmds++; + smtp_server_recipient_denied( + rcpt, smtp_server_command_get_reply(cmd->cmd, 0)); + return; + } + + /* Success */ + data->rcpt = NULL; /* clear to prevent destruction */ + (void)smtp_server_recipient_approved(&rcpt); +} + +static void +cmd_rcpt_recheck(struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_rcpt *data ATTR_UNUSED) +{ + struct smtp_server_connection *conn = cmd->conn; + + /* All preceeding commands have finished and now the transaction state + is clear. This provides the opportunity to re-check the transaction + state and abort the pending proxied mail command if it is bound to + fail */ + if (!cmd_rcpt_check_state(cmd, TRUE)) + return; + + /* Advance state */ + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_RCPT_TO, + smtp_address_encode(data->rcpt->path)); +} + +void smtp_server_cmd_rcpt(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + const struct smtp_server_settings *set = &conn->set; + enum smtp_capability caps = set->capabilities; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_cmd_rcpt *rcpt_data; + struct smtp_server_recipient *rcpt; + enum smtp_address_parse_flags path_parse_flags; + enum smtp_param_rcpt_parse_flags param_parse_flags; + const char *const *param_extensions = NULL; + struct smtp_address *path; + struct smtp_params_rcpt rcpt_params; + enum smtp_param_parse_error pperror; + const char *error; + int ret; + + /* rcpt = "RCPT TO:" ( "" / + "" / Forward-path ) [SP Rcpt-parameters] CRLF + Forward-path = Path + */ + + /* Check transaction state as far as possible */ + if (!cmd_rcpt_check_state(cmd, FALSE)) + return; + + /* ( "" / "" / Forward-path ) */ + if (params == NULL || strncasecmp(params, "TO:", 3) != 0) { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid parameters"); + return; + } + if (params[3] != ' ' && params[3] != '\t') { + params += 3; + } else if ((set->workarounds & + SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH) != 0) { + params += 3; + while (*params == ' ' || *params == '\t') + params++; + } else { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid TO: " + "Unexpected whitespace before path"); + return; + } + path_parse_flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART; + if ((set->workarounds & SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH) != 0) + path_parse_flags |= SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL; + if (smtp_address_parse_path_full(pool_datastack_create(), params, + path_parse_flags, &path, &error, + ¶ms) < 0) { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid TO: %s", error); + return; + } + if (*params == ' ') + params++; + else if (*params != '\0') { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid TO: Invalid character in path"); + return; + } + if (path->domain == NULL && !conn->set.rcpt_domain_optional && + strcasecmp(path->localpart, "postmaster") != 0) { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid TO: Missing domain"); + return; + } + + /* [SP Rcpt-parameters] */ + param_parse_flags = 0; + if (conn->set.rcpt_domain_optional) + param_parse_flags |= SMTP_PARAM_RCPT_FLAG_ORCPT_ALLOW_LOCALPART; + if (array_is_created(&conn->rcpt_param_extensions)) + param_extensions = array_front(&conn->rcpt_param_extensions); + if (smtp_params_rcpt_parse(pool_datastack_create(), params, + param_parse_flags, caps, param_extensions, + &rcpt_params, &pperror, &error) < 0) { + switch (pperror) { + case SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX: + smtp_server_reply(cmd, + 501, "5.5.4", "%s", error); + break; + case SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED: + smtp_server_reply(cmd, + 555, "5.5.4", "%s", error); + break; + default: + i_unreached(); + } + return; + } + + rcpt = smtp_server_recipient_create(cmd, path, &rcpt_params); + + rcpt_data = p_new(cmd->pool, struct smtp_server_cmd_rcpt, 1); + rcpt_data->rcpt = rcpt; + command->data = rcpt_data; + + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT, + cmd_rcpt_recheck, rcpt_data); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED, + cmd_rcpt_completed, rcpt_data); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_DESTROY, + cmd_rcpt_destroy, rcpt_data); + + conn->state.pending_rcpt_cmds++; + + smtp_server_command_ref(command); + i_assert(callbacks != NULL && callbacks->conn_cmd_rcpt != NULL); + + struct event_reason *reason = + smtp_server_connection_reason_begin(conn, "cmd_rcpt"); + ret = callbacks->conn_cmd_rcpt(conn->context, cmd, rcpt); + event_reason_end(&reason); + if (ret <= 0) { + i_assert(ret == 0 || smtp_server_command_is_replied(command)); + /* Command is waiting for external event or it failed */ + smtp_server_command_unref(&command); + return; + } + if (!smtp_server_command_is_replied(command)) { + /* Set generic RCPT success reply if none is provided */ + smtp_server_cmd_rcpt_reply_success(cmd); + } + smtp_server_command_unref(&command); +} + +bool smtp_server_command_is_rcpt(struct smtp_server_cmd_ctx *cmd) +{ + return (cmd->cmd->reg->func == smtp_server_cmd_rcpt); +} + +void smtp_server_cmd_rcpt_reply_success(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_cmd_rcpt *rcpt_data = cmd->cmd->data; + + i_assert(smtp_server_command_is_rcpt(cmd)); + + smtp_server_recipient_reply(rcpt_data->rcpt, 250, "2.1.5", "OK"); +} diff --git a/src/lib-smtp/smtp-server-cmd-rset.c b/src/lib-smtp/smtp-server-cmd-rset.c new file mode 100644 index 0000000..b07478b --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-rset.c @@ -0,0 +1,70 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#include "smtp-server-private.h" + +/* RSET command */ + +static void +cmd_rset_completed(struct smtp_server_cmd_ctx *cmd, void *context ATTR_UNUSED) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(smtp_server_command_is_replied(command)); + if (!smtp_server_command_replied_success(command)) { + /* failure */ + return; + } + + /* success */ + if (conn->state.trans != NULL) + smtp_server_transaction_reset(conn->state.trans); + smtp_server_connection_reset_state(conn); +} + +void smtp_server_cmd_rset(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + struct smtp_server_command *command = cmd->cmd; + int ret; + + /* rset = "RSET" CRLF */ + if (*params != '\0') { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid parameters"); + return; + } + + smtp_server_command_pipeline_block(cmd); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED, + cmd_rset_completed, NULL); + + smtp_server_command_ref(command); + if (callbacks != NULL && callbacks->conn_cmd_rset != NULL) { + /* specific implementation of RSET command */ + if ((ret=callbacks->conn_cmd_rset(conn->context, cmd)) <= 0) { + i_assert(ret == 0 || + smtp_server_command_is_replied(command)); + /* command is waiting for external event or it failed */ + smtp_server_command_unref(&command); + return; + } + } + + if (!smtp_server_command_is_replied(command)) { + /* set generic RSET success reply if none is provided */ + smtp_server_cmd_rset_reply_success(cmd); + } + smtp_server_command_unref(&command);; +} + +void smtp_server_cmd_rset_reply_success(struct smtp_server_cmd_ctx *cmd) +{ + i_assert(cmd->cmd->reg->func == smtp_server_cmd_rset); + + smtp_server_reply(cmd, 250, "2.0.0", "OK"); +} diff --git a/src/lib-smtp/smtp-server-cmd-starttls.c b/src/lib-smtp/smtp-server-cmd-starttls.c new file mode 100644 index 0000000..de53b39 --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-starttls.c @@ -0,0 +1,179 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "ostream.h" +#include "iostream-ssl.h" +#include "master-service.h" +#include "master-service-ssl.h" +#include "smtp-syntax.h" + +#include "smtp-server-private.h" + +/* STARTTLS command (RFC 3207) */ + +static int cmd_starttls_start(struct smtp_server_connection *conn) +{ + const struct smtp_server_callbacks *callbacks = conn->callbacks; + + e_debug(conn->event, "Starting TLS"); + + if (callbacks != NULL && callbacks->conn_start_tls != NULL) { + struct smtp_server_connection *tmp_conn = conn; + struct istream *input = conn->conn.input; + struct ostream *output = conn->conn.output; + int ret; + + smtp_server_connection_ref(tmp_conn); + ret = callbacks->conn_start_tls(tmp_conn->context, + &input, &output); + if (!smtp_server_connection_unref(&tmp_conn) || ret < 0) + return -1; + + smtp_server_connection_set_ssl_streams(conn, input, output); + } else if (smtp_server_connection_ssl_init(conn) < 0) { + smtp_server_connection_close(&conn, + "SSL Initialization failed"); + return -1; + } + + /* The command queue must be empty at this point. If anything were to be + queued somehow, this connection is vulnerable to STARTTLS command + insertion. + */ + i_assert(conn->command_queue_count == 0 && + conn->command_queue_head == NULL); + + /* RFC 3207, Section 4.2: + + Upon completion of the TLS handshake, the SMTP protocol is reset to + the initial state (the state in SMTP after a server issues a 220 + service ready greeting). The server MUST discard any knowledge + obtained from the client, such as the argument to the EHLO command, + which was not obtained from the TLS negotiation itself. + */ + smtp_server_connection_clear(conn); + smtp_server_connection_input_unlock(conn); + + return 0; +} + +static int cmd_starttls_output(struct smtp_server_connection *conn) +{ + int ret; + + if ((ret=smtp_server_connection_flush(conn)) < 0) + return 1; + + if (ret > 0) { + o_stream_unset_flush_callback(conn->conn.output); + if (cmd_starttls_start(conn) < 0) + return -1; + } + return 1; +} + +static void +cmd_starttls_destroy(struct smtp_server_cmd_ctx *cmd, void *context ATTR_UNUSED) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + int ret; + + if (conn->conn.output == NULL) + return; + + if (smtp_server_command_replied_success(command)) { + /* only one valid success status for STARTTLS command */ + i_assert(smtp_server_command_reply_status_equals(command, 220)); + + /* uncork */ + o_stream_uncork(conn->conn.output); + + /* flush */ + if ((ret=smtp_server_connection_flush(conn)) < 0) { + return; + } else if (ret == 0) { + /* the buffer has to be flushed */ + i_assert(!conn->conn.output->closed); + o_stream_set_flush_callback(conn->conn.output, + cmd_starttls_output, + conn); + o_stream_set_flush_pending(conn->conn.output, TRUE); + } else { + cmd_starttls_start(conn); + } + } +} + +static void +cmd_starttls_next(struct smtp_server_cmd_ctx *cmd, void *context ATTR_UNUSED) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + int ret; + + /* The command queue can only contain the STARTTLS command at this + point. If anything beyond the STARTTLS were queued somehow, this + connection is vulnerable to STARTTLS command insertion. + */ + i_assert(conn->command_queue_count == 1 && + conn->command_queue_tail == command); + + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_STARTTLS, + NULL); + + smtp_server_command_ref(command); + if (callbacks != NULL && callbacks->conn_cmd_starttls != NULL) + ret = callbacks->conn_cmd_starttls(conn->context, cmd); + else + ret = 1; + + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_DESTROY, + cmd_starttls_destroy, NULL); + + if (ret <= 0) { + i_assert(ret == 0 || smtp_server_command_is_replied(command)); + /* command is waiting for external event or it failed */ + smtp_server_command_unref(&command); + return; + } + if (!smtp_server_command_is_replied(command)) { + smtp_server_reply(cmd, + 220, "2.0.0", "Begin TLS negotiation now."); + } + smtp_server_command_unref(&command); +} + +void smtp_server_cmd_starttls(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + enum smtp_capability capabilities = conn->set.capabilities; + + if (conn->ssl_secured) { + i_assert((capabilities & SMTP_CAPABILITY_STARTTLS) == 0); + smtp_server_reply(cmd, + 502, "5.5.1", "TLS is already active."); + return; + } else if ((capabilities & SMTP_CAPABILITY_STARTTLS) == 0) { + smtp_server_reply(cmd, + 502, "5.5.1", "TLS support is not enabled."); + return; + } + + /* "STARTTLS" CRLF */ + if (*params != '\0') { + smtp_server_reply(cmd, + 501, "5.5.4", "Invalid parameters"); + return; + } + + smtp_server_command_input_lock(cmd); + smtp_server_connection_input_lock(conn); + + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT, + cmd_starttls_next, NULL); +} diff --git a/src/lib-smtp/smtp-server-cmd-vrfy.c b/src/lib-smtp/smtp-server-cmd-vrfy.c new file mode 100644 index 0000000..6eb2ae8 --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-vrfy.c @@ -0,0 +1,73 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "smtp-syntax.h" + +#include "smtp-server-private.h" + +/* VRFY command */ + +void smtp_server_cmd_vrfy(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + const char *param, *error; + int ret; + + /* vrfy = "VRFY" SP String CRLF */ + ret = smtp_string_parse(params, ¶m, &error); + if (ret < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid string parameter: %s", error); + return; + } else if (ret == 0) { + smtp_server_reply(cmd, 501, "5.5.4", "Invalid parameters"); + return; + } + + smtp_server_command_ref(command); + if (callbacks != NULL && callbacks->conn_cmd_vrfy != NULL) { + /* specific implementation of VRFY command */ + ret = callbacks->conn_cmd_vrfy(conn->context, cmd, param); + if (ret <= 0) { + i_assert(ret == 0 || + smtp_server_command_is_replied(command)); + /* command is waiting for external event or it failed */ + smtp_server_command_unref(&command); + return; + } + } + + /* RFC 5321, Section 3.5.3: + + A server MUST NOT return a 250 code in response to a VRFY or EXPN + command unless it has actually verified the address. In particular, + a server MUST NOT return 250 if all it has done is to verify that the + syntax given is valid. In that case, 502 (Command not implemented) + or 500 (Syntax error, command unrecognized) SHOULD be returned. As + stated elsewhere, implementation (in the sense of actually validating + addresses and returning information) of VRFY and EXPN are strongly + recommended. Hence, implementations that return 500 or 502 for VRFY + are not in full compliance with this specification. + + There may be circumstances where an address appears to be valid but + cannot reasonably be verified in real time, particularly when a + server is acting as a mail exchanger for another server or domain. + "Apparent validity", in this case, would normally involve at least + syntax checking and might involve verification that any domains + specified were ones to which the host expected to be able to relay + mail. In these situations, reply code 252 SHOULD be returned. + */ + if (!smtp_server_command_is_replied(command)) + smtp_server_cmd_vrfy_reply_default(cmd); + smtp_server_command_unref(&command); +} + +void smtp_server_cmd_vrfy_reply_default(struct smtp_server_cmd_ctx *cmd) +{ + i_assert(cmd->cmd->reg->func == smtp_server_cmd_vrfy); + + smtp_server_reply(cmd, 252, "2.3.3", "Try RCPT instead"); +} diff --git a/src/lib-smtp/smtp-server-cmd-xclient.c b/src/lib-smtp/smtp-server-cmd-xclient.c new file mode 100644 index 0000000..d926b45 --- /dev/null +++ b/src/lib-smtp/smtp-server-cmd-xclient.c @@ -0,0 +1,234 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "smtp-syntax.h" +#include "smtp-reply.h" + +#include "smtp-server-private.h" + +/* XCLIENT command (http://www.postfix.org/XCLIENT_README.html) */ + +static bool +cmd_xclient_check_state(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = cmd->conn; + + /* http://www.postfix.org/XCLIENT_README.html: + + The XCLIENT command may be sent at any time, except in the middle + of a mail delivery transaction (i.e. between MAIL and DOT, or MAIL + and RSET). */ + if (conn->state.trans != NULL) { + smtp_server_reply(cmd, 503, "5.5.0", + "XCLIENT not permitted during a mail transaction"); + return FALSE; + } + return TRUE; +} + +static void +cmd_xclient_completed(struct smtp_server_cmd_ctx *cmd, + struct smtp_proxy_data *proxy_data) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + + i_assert(smtp_server_command_is_replied(command)); + if (!smtp_server_command_replied_success(command)) { + /* failure */ + return; + } + + /* success */ + smtp_server_connection_reset_state(conn); + smtp_server_connection_set_proxy_data(conn, proxy_data); +} + +static void +cmd_xclient_recheck(struct smtp_server_cmd_ctx *cmd, + struct smtp_proxy_data *proxy_data ATTR_UNUSED) +{ + struct smtp_server_connection *conn = cmd->conn; + + /* all preceeding commands have finished and now the transaction state is + clear. This provides the opportunity to re-check the protocol state */ + if (!cmd_xclient_check_state(cmd)) + return; + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_XCLIENT, NULL); + + /* succes; send greeting */ + smtp_server_reply(cmd, 220, NULL, "%s %s", + conn->set.hostname, conn->set.login_greeting); + return; +} + +static void +smtp_server_cmd_xclient_extra_field(struct smtp_server_connection *conn, + pool_t pool, const struct smtp_param *param, + ARRAY_TYPE(smtp_proxy_data_field) *fields) +{ + struct smtp_proxy_data_field *field; + + if (conn->set.xclient_extensions == NULL || + !str_array_icase_find(conn->set.xclient_extensions, param->keyword)) + return; + + if (!array_is_created(fields)) + p_array_init(fields, pool, 8); + field = array_append_space(fields); + field->name = p_strdup(pool, param->keyword); + field->value = p_strdup(pool, param->value); +} + +void smtp_server_cmd_xclient(struct smtp_server_cmd_ctx *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->conn; + struct smtp_server_command *command = cmd->cmd; + const struct smtp_server_callbacks *callbacks = conn->callbacks; + struct smtp_proxy_data *proxy_data; + ARRAY_TYPE(smtp_proxy_data_field) extra_fields = ARRAY_INIT; + const char *const *argv; + + /* xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value ) + attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN ) + attribute-value = xtext + */ + + if ((conn->set.capabilities & SMTP_CAPABILITY_XCLIENT) == 0) { + smtp_server_reply(cmd, + 502, "5.5.1", "Unsupported command"); + return; + } + + /* check transaction state as far as possible */ + if (!cmd_xclient_check_state(cmd)) + return; + + /* check whether client is trusted */ + if (!smtp_server_connection_is_trusted(conn)) { + smtp_server_reply(cmd, 550, "5.7.14", + "You are not from trusted IP"); + return; + } + + proxy_data = p_new(cmd->pool, struct smtp_proxy_data, 1); + + argv = t_strsplit(params, " "); + for (; *argv != NULL; argv++) { + struct smtp_param param; + const char *error; + + if (smtp_param_parse(pool_datastack_create(), *argv, + ¶m, &error) < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid parameter: %s", error); + return; + } + + param.keyword = t_str_ucase(param.keyword); + + if (smtp_xtext_parse(param.value, ¶m.value, &error) < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid %s parameter: %s", + param.keyword, error); + return; + } + + if (strcmp(param.keyword, "ADDR") == 0) { + bool ipv6 = FALSE; + if (strcasecmp(param.value, "[UNAVAILABLE]") == 0) + continue; + if (strncasecmp(param.value, "IPV6:", 5) == 0) { + ipv6 = TRUE; + param.value += 5; + } + if (net_addr2ip(param.value, &proxy_data->source_ip) < 0 || + (ipv6 && proxy_data->source_ip.family != AF_INET6)) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid ADDR parameter"); + return; + } + } else if (strcmp(param.keyword, "HELO") == 0) { + if (strcasecmp(param.value, "[UNAVAILABLE]") == 0) + continue; + if (smtp_helo_domain_parse + (param.value, TRUE, &proxy_data->helo) >= 0) + proxy_data->helo = + p_strdup(cmd->pool, proxy_data->helo); + } else if (strcmp(param.keyword, "LOGIN") == 0) { + if (strcasecmp(param.value, "[UNAVAILABLE]") == 0) + continue; + proxy_data->login = p_strdup(cmd->pool, param.value); + } else if (strcmp(param.keyword, "PORT") == 0) { + if (strcasecmp(param.value, "[UNAVAILABLE]") == 0) + continue; + if (net_str2port(param.value, &proxy_data->source_port) < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid PORT parameter"); + return; + } + } else if (strcmp(param.keyword, "PROTO") == 0) { + param.value = t_str_ucase(param.value); + if (strcmp(param.value, "SMTP") == 0) + proxy_data->proto = SMTP_PROXY_PROTOCOL_SMTP; + else if (strcmp(param.value, "ESMTP") == 0) + proxy_data->proto = SMTP_PROXY_PROTOCOL_ESMTP; + else if (strcmp(param.value, "LMTP") == 0) + proxy_data->proto = SMTP_PROXY_PROTOCOL_LMTP; + else { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid PROTO parameter"); + return; + } + } else if (strcmp(param.keyword, "SESSION") == 0) { + if (strcasecmp(param.value, "[UNAVAILABLE]") == 0) + continue; + proxy_data->session = p_strdup(cmd->pool, param.value); + } else if (strcmp(param.keyword, "TIMEOUT") == 0) { + if (str_to_uint(param.value, + &proxy_data->timeout_secs) < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid TIMEOUT parameter"); + return; + } + } else if (strcmp(param.keyword, "TTL") == 0) { + if (str_to_uint(param.value, + &proxy_data->ttl_plus_1) < 0) { + smtp_server_reply(cmd, 501, "5.5.4", + "Invalid TTL parameter"); + return; + } + proxy_data->ttl_plus_1++; + } else { + smtp_server_cmd_xclient_extra_field(conn, + cmd->pool, ¶m, &extra_fields); + } + } + + if (array_is_created(&extra_fields)) { + proxy_data->extra_fields = array_get(&extra_fields, + &proxy_data->extra_fields_count); + } + + smtp_server_command_input_lock(cmd); + + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT, + cmd_xclient_recheck, proxy_data); + smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED, + cmd_xclient_completed, proxy_data); + + if (conn->state.state == SMTP_SERVER_STATE_GREETING) { + smtp_server_connection_set_state( + conn, SMTP_SERVER_STATE_XCLIENT, NULL); + } + + smtp_server_command_ref(command); + if (callbacks != NULL && callbacks->conn_cmd_xclient != NULL) { + /* specific implementation of XCLIENT command */ + callbacks->conn_cmd_xclient(conn->context, cmd, proxy_data); + } + smtp_server_command_unref(&command); +} diff --git a/src/lib-smtp/smtp-server-command.c b/src/lib-smtp/smtp-server-command.c new file mode 100644 index 0000000..992a345 --- /dev/null +++ b/src/lib-smtp/smtp-server-command.c @@ -0,0 +1,901 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "array.h" + +#include "smtp-reply.h" +#include "smtp-server-private.h" + +/* + * Command registry + */ + +void smtp_server_command_register(struct smtp_server *server, const char *name, + smtp_server_cmd_start_func_t *func, + enum smtp_server_command_flags flags) +{ + struct smtp_server_command_reg cmd; + + i_zero(&cmd); + cmd.name = name; + cmd.func = func; + cmd.flags = flags; + array_push_back(&server->commands_reg, &cmd); + + server->commands_unsorted = TRUE; +} + +static bool ATTR_NOWARN_UNUSED_RESULT +smtp_server_command_do_unregister(struct smtp_server *server, + const char *name) +{ + const struct smtp_server_command_reg *cmd; + unsigned int i, count; + + cmd = array_get(&server->commands_reg, &count); + for (i = 0; i < count; i++) { + if (strcasecmp(cmd[i].name, name) == 0) { + array_delete(&server->commands_reg, i, 1); + return TRUE; + } + } + + return FALSE; +} + +void smtp_server_command_unregister(struct smtp_server *server, + const char *name) +{ + if (smtp_server_command_do_unregister(server, name)) + return; + i_panic("smtp-server: Trying to unregister unknown command '%s'", name); +} + +void smtp_server_command_override(struct smtp_server *server, const char *name, + smtp_server_cmd_start_func_t *func, + enum smtp_server_command_flags flags) +{ + smtp_server_command_do_unregister(server, name); + smtp_server_command_register(server, name, func, flags); +} + +static int +smtp_server_command_cmp(const struct smtp_server_command_reg *c1, + const struct smtp_server_command_reg *c2) +{ + return strcasecmp(c1->name, c2->name); +} + +static int +smtp_server_command_bsearch(const char *name, + const struct smtp_server_command_reg *cmd) +{ + return strcasecmp(name, cmd->name); +} + +static struct smtp_server_command_reg * +smtp_server_command_find(struct smtp_server *server, const char *name) +{ + if (server->commands_unsorted) { + array_sort(&server->commands_reg, smtp_server_command_cmp); + server->commands_unsorted = FALSE; + } + + return array_bsearch(&server->commands_reg, + name, smtp_server_command_bsearch); +} + +void smtp_server_commands_init(struct smtp_server *server) +{ + p_array_init(&server->commands_reg, server->pool, 16); + + switch (server->set.protocol) { + case SMTP_PROTOCOL_SMTP: + smtp_server_command_register( + server, "EHLO", smtp_server_cmd_ehlo, + SMTP_SERVER_CMD_FLAG_PRETLS | + SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register( + server, "HELO", smtp_server_cmd_helo, + SMTP_SERVER_CMD_FLAG_PREAUTH); + break; + case SMTP_PROTOCOL_LMTP: + smtp_server_command_register( + server, "LHLO", smtp_server_cmd_ehlo, + SMTP_SERVER_CMD_FLAG_PRETLS | + SMTP_SERVER_CMD_FLAG_PREAUTH); + break; + } + + smtp_server_command_register( + server, "AUTH", smtp_server_cmd_auth, + SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register( + server, "STARTTLS", smtp_server_cmd_starttls, + SMTP_SERVER_CMD_FLAG_PRETLS | SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register( + server, "MAIL", smtp_server_cmd_mail, 0); + smtp_server_command_register(server, "RCPT", smtp_server_cmd_rcpt, 0); + smtp_server_command_register(server, "DATA", smtp_server_cmd_data, 0); + smtp_server_command_register(server, "BDAT", smtp_server_cmd_bdat, 0); + smtp_server_command_register( + server, "RSET", smtp_server_cmd_rset, + SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register(server, "VRFY", smtp_server_cmd_vrfy, 0); + smtp_server_command_register( + server, "NOOP", smtp_server_cmd_noop, + SMTP_SERVER_CMD_FLAG_PRETLS | SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register( + server, "QUIT", smtp_server_cmd_quit, + SMTP_SERVER_CMD_FLAG_PRETLS | SMTP_SERVER_CMD_FLAG_PREAUTH); + + smtp_server_command_register( + server, "XCLIENT", smtp_server_cmd_xclient, + SMTP_SERVER_CMD_FLAG_PREAUTH); +} + +/* + * + */ + +static void smtp_server_command_update_event(struct smtp_server_command *cmd) +{ + struct event *event = cmd->context.event; + const char *label = (cmd->context.name == NULL ? + "[unknown]" : + t_str_ucase(cmd->context.name)); + + if (cmd->reg != NULL) + event_add_str(event, "cmd_name", cmd->reg->name); + else + event_add_str(event, "cmd_name", "unknown"); + event_add_str(event, "cmd_input_name", cmd->context.name); + event_set_append_log_prefix(event, + t_strdup_printf("command %s: ", label)); +} + +static struct smtp_server_command * +smtp_server_command_alloc(struct smtp_server_connection *conn) +{ + struct smtp_server_command *cmd; + pool_t pool; + + pool = pool_alloconly_create("smtp_server_command", 1024); + cmd = p_new(pool, struct smtp_server_command, 1); + cmd->context.pool = pool; + cmd->context.cmd = cmd; + cmd->context.event = event_create(conn->event); + cmd->refcount = 1; + cmd->context.conn = conn; + cmd->context.server = conn->server; + cmd->replies_expected = 1; + + DLLIST2_APPEND(&conn->command_queue_head, + &conn->command_queue_tail, cmd); + conn->command_queue_count++; + + return cmd; +} + +struct smtp_server_command * +smtp_server_command_new_invalid(struct smtp_server_connection *conn) +{ + struct smtp_server_command *cmd; + + cmd = smtp_server_command_alloc(conn); + smtp_server_command_update_event(cmd); + + e_debug(cmd->context.event, "Invalid command"); + + return cmd; +} + +struct smtp_server_command * +smtp_server_command_new(struct smtp_server_connection *conn, + const char *name) +{ + struct smtp_server *server = conn->server; + struct smtp_server_command *cmd; + + cmd = smtp_server_command_alloc(conn); + cmd->context.name = p_strdup(cmd->context.pool, name); + cmd->reg = smtp_server_command_find(server, name); + + smtp_server_command_update_event(cmd); + + e_debug(cmd->context.event, "New command"); + + return cmd; +} + +void smtp_server_command_execute(struct smtp_server_command *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->context.conn; + + event_add_str(cmd->context.event, "cmd_args", params); + event_add_str(cmd->context.event, "cmd_human_args", params); + + struct event_passthrough *e = + event_create_passthrough(cmd->context.event)-> + set_name("smtp_server_command_started"); + e_debug(e->event(), "Execute command"); + + if (cmd->reg == NULL) { + /* RFC 5321, Section 4.2.4: Reply Code 502 + + Questions have been raised as to when reply code 502 (Command + not implemented) SHOULD be returned in preference to other + codes. 502 SHOULD be used when the command is actually + recognized by the SMTP server, but not implemented. If the + command is not recognized, code 500 SHOULD be returned. + */ + smtp_server_command_fail(cmd, + 500, "5.5.1", "Unknown command"); + + } else if (!conn->ssl_secured && conn->set.tls_required && + (cmd->reg->flags & SMTP_SERVER_CMD_FLAG_PRETLS) == 0) { + /* RFC 3207, Section 4: + + A SMTP server that is not publicly referenced may choose to + require that the client perform a TLS negotiation before + accepting any commands. In this case, the server SHOULD + return the reply code: + + 530 Must issue a STARTTLS command first + + to every command other than NOOP, EHLO, STARTTLS, or QUIT. If + the client and server are using the ENHANCEDSTATUSCODES ESMTP + extension [RFC2034], the status code to be returned SHOULD be + 5.7.0. + */ + smtp_server_command_fail(cmd, + 530, "5.7.0", "TLS required."); + + } else if (!conn->authenticated && !conn->set.auth_optional && + (cmd->reg->flags & SMTP_SERVER_CMD_FLAG_PREAUTH) == 0) { + /* RFC 4954, Section 6: Status Codes + + 530 5.7.0 Authentication required + + This response SHOULD be returned by any command other than + AUTH, EHLO, HELO, NOOP, RSET, or QUIT when server policy + requires authentication in order to perform the requested + action and authentication is not currently in force. + */ + smtp_server_command_fail(cmd, + 530, "5.7.0", "Authentication required."); + + } else { + struct smtp_server_command *tmp_cmd = cmd; + + i_assert(cmd->reg->func != NULL); + smtp_server_command_ref(tmp_cmd); + cmd->reg->func(&tmp_cmd->context, params); + if (tmp_cmd->state == SMTP_SERVER_COMMAND_STATE_NEW) + tmp_cmd->state = SMTP_SERVER_COMMAND_STATE_PROCESSING; + if (!smtp_server_command_unref(&tmp_cmd)) + cmd = NULL; + } +} + +void smtp_server_command_ref(struct smtp_server_command *cmd) +{ + if (cmd->destroying) + return; + cmd->refcount++; +} + +bool smtp_server_command_unref(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + struct smtp_server_connection *conn = cmd->context.conn; + + *_cmd = NULL; + + if (cmd->destroying) + return FALSE; + + i_assert(cmd->refcount > 0); + if (--cmd->refcount > 0) + return TRUE; + cmd->destroying = TRUE; + + if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED) { + e_debug(cmd->context.event, "Destroy"); + } else { + struct event_passthrough *e = + event_create_passthrough(cmd->context.event)-> + set_name("smtp_server_command_finished"); + e->add_int("status_code", 9000); + e->add_str("enhanced_code", "9.0.0"); + e->add_str("error", "Aborted"); + e_debug(e->event(), "Destroy"); + + cmd->state = SMTP_SERVER_COMMAND_STATE_ABORTED; + DLLIST2_REMOVE(&conn->command_queue_head, + &conn->command_queue_tail, cmd); + conn->command_queue_count--; + } + + /* Execute hooks */ + if (!smtp_server_command_call_hooks( + &cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, TRUE)) + i_unreached(); + + smtp_server_command_pipeline_unblock(&cmd->context); + + smtp_server_reply_free(cmd); + event_unref(&cmd->context.event); + pool_unref(&cmd->context.pool); + return FALSE; +} + +void smtp_server_command_abort(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + struct smtp_server_connection *conn = cmd->context.conn; + + /* Preemptively remove command from queue (references may still exist) + */ + if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED) { + e_debug(cmd->context.event, "Abort"); + } else { + struct event_passthrough *e = + event_create_passthrough(cmd->context.event)-> + set_name("smtp_server_command_finished"); + e->add_int("status_code", 9000); + e->add_str("enhanced_code", "9.0.0"); + e->add_str("error", "Aborted"); + e_debug(e->event(), "Abort"); + + cmd->state = SMTP_SERVER_COMMAND_STATE_ABORTED; + DLLIST2_REMOVE(&conn->command_queue_head, + &conn->command_queue_tail, cmd); + conn->command_queue_count--; + } + smtp_server_reply_free(cmd); + + smtp_server_command_pipeline_unblock(&cmd->context); + smtp_server_command_unref(_cmd); +} + +#undef smtp_server_command_add_hook +void smtp_server_command_add_hook(struct smtp_server_command *cmd, + enum smtp_server_command_hook_type type, + smtp_server_cmd_func_t func, + void *context) +{ + struct smtp_server_command_hook *hook; + + i_assert(func != NULL); + + hook = cmd->hooks_head; + while (hook != NULL) { + /* No double registrations */ + i_assert(hook->type != type || hook->func != func); + + hook = hook->next; + } + + hook = p_new(cmd->context.pool, struct smtp_server_command_hook, 1); + hook->type = type; + hook->func = func; + hook->context = context; + + DLLIST2_APPEND(&cmd->hooks_head, &cmd->hooks_tail, hook); +} + +#undef smtp_server_command_remove_hook +void smtp_server_command_remove_hook(struct smtp_server_command *cmd, + enum smtp_server_command_hook_type type, + smtp_server_cmd_func_t *func) +{ + struct smtp_server_command_hook *hook; + bool found = FALSE; + + hook = cmd->hooks_head; + while (hook != NULL) { + struct smtp_server_command_hook *hook_next = hook->next; + + if (hook->type == type && hook->func == func) { + DLLIST2_REMOVE(&cmd->hooks_head, &cmd->hooks_tail, + hook); + found = TRUE; + break; + } + + hook = hook_next; + } + i_assert(found); +} + +bool smtp_server_command_call_hooks(struct smtp_server_command **_cmd, + enum smtp_server_command_hook_type type, + bool remove) +{ + struct smtp_server_command *cmd = *_cmd; + struct smtp_server_command_hook *hook; + + if (type != SMTP_SERVER_COMMAND_HOOK_DESTROY) { + if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED) + return FALSE; + smtp_server_command_ref(cmd); + } + + hook = cmd->hooks_head; + while (hook != NULL) { + struct smtp_server_command_hook *hook_next = hook->next; + + if (hook->type == type) { + if (remove) { + DLLIST2_REMOVE(&cmd->hooks_head, + &cmd->hooks_tail, hook); + } + hook->func(&cmd->context, hook->context); + } + + hook = hook_next; + } + + if (type != SMTP_SERVER_COMMAND_HOOK_DESTROY) { + if (!smtp_server_command_unref(&cmd)) { + *_cmd = NULL; + return FALSE; + } + } + return TRUE; +} + +void smtp_server_command_remove_hooks(struct smtp_server_command *cmd, + enum smtp_server_command_hook_type type) +{ + struct smtp_server_command_hook *hook; + + hook = cmd->hooks_head; + while (hook != NULL) { + struct smtp_server_command_hook *hook_next = hook->next; + + if (hook->type == type) { + DLLIST2_REMOVE(&cmd->hooks_head, &cmd->hooks_tail, + hook); + } + + hook = hook_next; + } +} + +void smtp_server_command_set_reply_count(struct smtp_server_command *cmd, + unsigned int count) +{ + i_assert(count > 0); + i_assert(!array_is_created(&cmd->replies)); + cmd->replies_expected = count; +} + +unsigned int +smtp_server_command_get_reply_count(struct smtp_server_command *cmd) +{ + i_assert(cmd->replies_expected > 0); + return cmd->replies_expected; +} + +bool smtp_server_command_next_to_reply(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + + e_debug(cmd->context.event, "Next to reply"); + + if (!smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_NEXT, TRUE)) + return FALSE; + + smtp_server_command_remove_hooks(cmd, SMTP_SERVER_COMMAND_HOOK_NEXT); + return TRUE; +} + +void smtp_server_command_ready_to_reply(struct smtp_server_command *cmd) +{ + cmd->state = SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY; + e_debug(cmd->context.event, "Ready to reply"); + smtp_server_connection_trigger_output(cmd->context.conn); +} + +static bool +smtp_server_command_replied(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + + if (cmd->replies_submitted < cmd->replies_expected) { + e_debug(cmd->context.event, "Replied (one)"); + + return smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED_ONE, FALSE); + } + + e_debug(cmd->context.event, "Replied"); + + return (smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED_ONE, TRUE) && + smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED, TRUE)); +} + +bool smtp_server_command_completed(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + + if (cmd->replies_submitted < cmd->replies_expected) + return TRUE; + + e_debug(cmd->context.event, "Completed"); + + if (cmd->pipeline_blocked) + smtp_server_command_pipeline_unblock(&cmd->context); + + return smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_COMPLETED, TRUE); +} + +static bool +smtp_server_command_handle_reply(struct smtp_server_command *cmd) +{ + struct smtp_server_connection *conn = cmd->context.conn; + + smtp_server_connection_ref(conn); + + if (!smtp_server_command_replied(&cmd)) + return smtp_server_connection_unref(&conn); + + if (cmd->input_locked) + smtp_server_command_input_unlock(&cmd->context); + + /* Submit reply */ + switch (cmd->state) { + case SMTP_SERVER_COMMAND_STATE_NEW: + case SMTP_SERVER_COMMAND_STATE_PROCESSING: + if (!smtp_server_command_is_complete(cmd)) { + e_debug(cmd->context.event, "Not ready to reply"); + cmd->state = SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY; + break; + } + smtp_server_command_ready_to_reply(cmd); + break; + case SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY: + case SMTP_SERVER_COMMAND_STATE_ABORTED: + break; + default: + i_unreached(); + } + + return smtp_server_connection_unref(&conn); +} + +void smtp_server_command_submit_reply(struct smtp_server_command *cmd) +{ + struct smtp_server_connection *conn = cmd->context.conn; + unsigned int i, submitted; + bool is_bad = FALSE; + + i_assert(conn != NULL && array_is_created(&cmd->replies)); + + submitted = 0; + for (i = 0; i < cmd->replies_expected; i++) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + if (!reply->submitted) + continue; + submitted++; + + i_assert(reply->content != NULL); + switch (reply->content->status) { + case 500: + case 501: + case 503: + is_bad = TRUE; + break; + } + } + + i_assert(submitted == cmd->replies_submitted); + + /* Limit number of consecutive bad commands */ + if (is_bad) + conn->bad_counter++; + else if (cmd->replies_submitted == cmd->replies_expected) + conn->bad_counter = 0; + + if (!smtp_server_command_handle_reply(cmd)) + return; + + if (conn != NULL && conn->bad_counter > conn->set.max_bad_commands) { + smtp_server_connection_terminate(&conn, + "4.7.0", "Too many invalid commands."); + return; + } +} + +bool smtp_server_command_is_replied(struct smtp_server_command *cmd) +{ + unsigned int i; + + if (!array_is_created(&cmd->replies)) + return FALSE; + + for (i = 0; i < cmd->replies_expected; i++) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + if (!reply->submitted) + return FALSE; + } + + return TRUE; +} + +bool smtp_server_command_reply_is_forwarded(struct smtp_server_command *cmd) +{ + unsigned int i; + + if (!array_is_created(&cmd->replies)) + return FALSE; + + for (i = 0; i < cmd->replies_expected; i++) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + if (!reply->submitted) + return FALSE; + if (reply->forwarded) + return TRUE; + } + + return FALSE; +} + +struct smtp_server_reply * +smtp_server_command_get_reply(struct smtp_server_command *cmd, + unsigned int idx) +{ + struct smtp_server_reply *reply; + + i_assert(idx < cmd->replies_expected); + + if (!array_is_created(&cmd->replies)) + return NULL; + + reply = array_idx_get_space(&cmd->replies, idx); + if (!reply->submitted) + return NULL; + return reply; +} + +bool smtp_server_command_reply_status_equals(struct smtp_server_command *cmd, + unsigned int status) +{ + struct smtp_server_reply *reply; + + i_assert(cmd->replies_expected == 1); + reply = smtp_server_command_get_reply(cmd, 0); + + return (reply->content != NULL && reply->content->status == status); +} + +bool smtp_server_command_replied_success(struct smtp_server_command *cmd) +{ + bool success = FALSE; + unsigned int i; + + if (!array_is_created(&cmd->replies)) + return FALSE; + + for (i = 0; i < cmd->replies_expected; i++) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + if (!reply->submitted) + return FALSE; + if (smtp_server_reply_is_success(reply)) + success = TRUE; + } + + return success; +} + +static int +smtp_server_command_send_more_replies(struct smtp_server_command *cmd) +{ + unsigned int i; + int ret = 1; + + smtp_server_command_ref(cmd); + + // FIXME: handle LMTP DATA command with enormous number of recipients; + // i.e. don't keep filling output stream with replies indefinitely. + for (i = 0; i < cmd->replies_expected; i++) { + struct smtp_server_reply *reply; + + reply = array_idx_modifiable(&cmd->replies, i); + + if (!reply->submitted) { + i_assert(!reply->sent); + ret = 0; + break; + } + if (smtp_server_reply_send(reply) < 0) { + ret = -1; + break; + } + } + + if (!smtp_server_command_unref(&cmd)) + return -1; + return ret; +} + +bool smtp_server_command_send_replies(struct smtp_server_command *cmd) +{ + int ret; + + if (!smtp_server_command_next_to_reply(&cmd)) + return FALSE; + if (cmd->state < SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY) + return FALSE; + + i_assert(cmd->state == SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY && + array_is_created(&cmd->replies)); + + if (!smtp_server_command_completed(&cmd)) + return TRUE; + + /* Send command replies */ + ret = smtp_server_command_send_more_replies(cmd); + if (ret < 0) + return FALSE; + if (ret == 0) { + cmd->state = SMTP_SERVER_COMMAND_STATE_PROCESSING; + return FALSE; + } + + smtp_server_command_finished(cmd); + return TRUE; +} + +void smtp_server_command_finished(struct smtp_server_command *cmd) +{ + struct smtp_server_connection *conn = cmd->context.conn; + struct smtp_server_reply *reply; + + i_assert(cmd->state < SMTP_SERVER_COMMAND_STATE_FINISHED); + cmd->state = SMTP_SERVER_COMMAND_STATE_FINISHED; + + DLLIST2_REMOVE(&conn->command_queue_head, + &conn->command_queue_tail, cmd); + conn->command_queue_count--; + conn->stats.reply_count++; + + i_assert(array_is_created(&cmd->replies)); + reply = array_front_modifiable(&cmd->replies); + i_assert(reply->content != NULL); + + struct event_passthrough *e = + event_create_passthrough(cmd->context.event)-> + set_name("smtp_server_command_finished"); + smtp_server_reply_add_to_event(reply, e); + e_debug(e->event(), "Finished"); + + if (reply->content->status == 221 || reply->content->status == 421) { + i_assert(cmd->replies_expected == 1); + if (reply->content->status == 421) { + smtp_server_connection_close(&conn, t_strdup_printf( + "Server closed the connection: %s", + smtp_server_reply_get_one_line(reply))); + + } else if (conn->set.auth_optional || conn->authenticated) { + smtp_server_connection_close(&conn, "Logged out"); + } else { + smtp_server_connection_close(&conn, + "Aborted login by logging out"); + } + smtp_server_command_unref(&cmd); + return; + } + if (cmd->input_locked) + smtp_server_command_input_unlock(&cmd->context); + if (cmd->pipeline_blocked) + smtp_server_command_pipeline_unblock(&cmd->context); + + smtp_server_command_unref(&cmd); + smtp_server_connection_trigger_output(conn); +} + +void smtp_server_command_fail(struct smtp_server_command *cmd, + unsigned int status, const char *enh_code, + const char *fmt, ...) +{ + unsigned int i; + va_list args; + + i_assert(status / 100 > 2); + + va_start(args, fmt); + if (cmd->replies_expected == 1) { + smtp_server_reply_indexv(&cmd->context, 0, + status, enh_code, fmt, args); + } else for (i = 0; i < cmd->replies_expected; i++) { + bool sent = FALSE; + + if (array_is_created(&cmd->replies)) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + sent = reply->sent; + } + + /* Send the same reply for all */ + if (!sent) { + va_list args_copy; + VA_COPY(args_copy, args); + smtp_server_reply_indexv(&cmd->context, i, + status, enh_code, fmt, args_copy); + va_end(args_copy); + } + } + va_end(args); +} + +void smtp_server_command_input_lock(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + command->input_locked = TRUE; + smtp_server_connection_input_halt(conn); +} + +void smtp_server_command_input_unlock(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + command->input_locked = FALSE; + if (command->input_captured) { + command->input_captured = FALSE; + smtp_server_connection_input_halt(conn); + } + smtp_server_connection_input_resume(conn); +} + +void smtp_server_command_input_capture( + struct smtp_server_cmd_ctx *cmd, + smtp_server_cmd_input_callback_t *callback) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + smtp_server_connection_input_capture(conn, *callback, cmd); + command->input_locked = TRUE; + command->input_captured = TRUE; +} + +void smtp_server_command_pipeline_block(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + e_debug(cmd->event, "Pipeline blocked"); + + command->pipeline_blocked = TRUE; + smtp_server_connection_input_lock(conn); +} + +void smtp_server_command_pipeline_unblock(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + if (!command->pipeline_blocked) + return; + + command->pipeline_blocked = FALSE; + smtp_server_connection_input_unlock(conn); + + e_debug(cmd->event, "Pipeline unblocked"); +} diff --git a/src/lib-smtp/smtp-server-connection.c b/src/lib-smtp/smtp-server-connection.c new file mode 100644 index 0000000..f301e82 --- /dev/null +++ b/src/lib-smtp/smtp-server-connection.c @@ -0,0 +1,1648 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "array.h" +#include "str.h" +#include "guid.h" +#include "base64.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "iostream.h" +#include "connection.h" +#include "iostream-rawlog.h" +#include "iostream-ssl.h" +#include "master-service.h" +#include "master-service-ssl.h" + +#include "smtp-syntax.h" +#include "smtp-reply-parser.h" +#include "smtp-command-parser.h" +#include "smtp-server-private.h" + +const char *const smtp_server_state_names[] = { + "GREETING", + "XCLIENT", + "HELO", + "STARTTLS", + "AUTH", + "READY", + "MAIL FROM", + "RCPT TO", + "DATA" +}; + +/* + * Connection + */ + +static void smtp_server_connection_input(struct connection *_conn); +static int smtp_server_connection_output(struct smtp_server_connection *conn); +static void +smtp_server_connection_disconnect(struct smtp_server_connection *conn, + const char *reason) ATTR_NULL(2); + +static void +smtp_server_connection_update_stats(struct smtp_server_connection *conn) +{ + if (conn->conn.input != NULL) + conn->stats.input = conn->conn.input->v_offset; + if (conn->conn.output != NULL) + conn->stats.output = conn->conn.output->offset; +} + +const struct smtp_server_stats * +smtp_server_connection_get_stats(struct smtp_server_connection *conn) +{ + smtp_server_connection_update_stats(conn); + return &conn->stats; +} + +static bool +smtp_server_connection_check_pipeline(struct smtp_server_connection *conn) +{ + unsigned int pipeline = conn->command_queue_count; + + if (conn->command_queue_tail != NULL) { + i_assert(pipeline > 0); + if (conn->command_queue_tail->state == + SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY) + pipeline--; + } + + if (pipeline >= conn->set.max_pipelined_commands) { + e_debug(conn->event, "Command pipeline is full " + "(pipelined commands %u > limit %u)", + pipeline, conn->set.max_pipelined_commands); + return FALSE; + } + return TRUE; +} + +void smtp_server_connection_input_halt(struct smtp_server_connection *conn) +{ + connection_input_halt(&conn->conn); +} + +void smtp_server_connection_input_resume(struct smtp_server_connection *conn) +{ + struct smtp_server_command *cmd; + bool cmd_locked = FALSE; + + if (conn->conn.io == NULL) { + /* Only resume when we actually can */ + if (conn->input_locked || conn->input_broken || + conn->disconnected) + return; + if (!smtp_server_connection_check_pipeline(conn)) + return; + + /* Is queued command still blocking input? */ + cmd = conn->command_queue_head; + while (cmd != NULL) { + if (cmd->input_locked || cmd->pipeline_blocked) { + cmd_locked = TRUE; + break; + } + cmd = cmd->next; + } + if (cmd_locked) + return; + + /* Restore input handler */ + connection_input_resume(&conn->conn); + } + + if (conn->conn.io != NULL && + i_stream_have_bytes_left(conn->conn.input)) { + io_set_pending(conn->conn.io); + } +} + +void smtp_server_connection_input_lock(struct smtp_server_connection *conn) +{ + conn->input_locked = TRUE; + smtp_server_connection_input_halt(conn); +} + +void smtp_server_connection_input_unlock(struct smtp_server_connection *conn) +{ + conn->input_locked = FALSE; + smtp_server_connection_input_resume(conn); +} + +#undef smtp_server_connection_input_capture +void smtp_server_connection_input_capture(struct smtp_server_connection *conn, + smtp_server_input_callback_t *callback, void *context) +{ + i_assert(!conn->input_broken && !conn->disconnected); + connection_input_halt(&conn->conn); + conn->conn.io = io_add_istream(conn->conn.input, *callback, context); +} + +static void +smtp_server_connection_update_rawlog(struct smtp_server_connection *conn) +{ + struct stat st; + + if (conn->set.rawlog_dir == NULL) + return; + + if (!conn->rawlog_checked) { + conn->rawlog_checked = TRUE; + if (stat(conn->set.rawlog_dir, &st) == 0) + conn->rawlog_enabled = TRUE; + } + if (conn->rawlog_enabled) { + iostream_rawlog_create(conn->set.rawlog_dir, + &conn->conn.input, &conn->conn.output); + } +} + +static void +smtp_server_connection_streams_changed(struct smtp_server_connection *conn) +{ + smtp_server_connection_update_rawlog(conn); + smtp_command_parser_set_stream(conn->smtp_parser, conn->conn.input); + + o_stream_set_flush_callback(conn->conn.output, + smtp_server_connection_output, conn); + o_stream_set_flush_pending(conn->conn.output, TRUE); +} + +void smtp_server_connection_set_streams(struct smtp_server_connection *conn, + struct istream *input, + struct ostream *output) +{ + struct istream *old_input = conn->conn.input; + struct ostream *old_output = conn->conn.output; + + i_assert(conn->created_from_streams); + + conn->conn.input = input; + i_stream_ref(conn->conn.input); + + conn->conn.output = output; + o_stream_ref(conn->conn.output); + o_stream_set_no_error_handling(conn->conn.output, TRUE); + + i_stream_unref(&old_input); + o_stream_unref(&old_output); + + smtp_server_connection_streams_changed(conn); +} + +void smtp_server_connection_set_ssl_streams(struct smtp_server_connection *conn, + struct istream *input, + struct ostream *output) +{ + conn->ssl_secured = TRUE; + conn->set.capabilities &= ENUM_NEGATE(SMTP_CAPABILITY_STARTTLS); + + smtp_server_connection_set_streams(conn, input, output); +} + +static void +smtp_server_connection_idle_timeout(struct smtp_server_connection *conn) +{ + smtp_server_connection_terminate( + &conn, "4.4.2", "Disconnected for inactivity"); +} + +void smtp_server_connection_timeout_stop(struct smtp_server_connection *conn) +{ + if (conn->to_idle != NULL) { + e_debug(conn->event, "Timeout stop"); + + timeout_remove(&conn->to_idle); + } +} + +void smtp_server_connection_timeout_start(struct smtp_server_connection *conn) +{ + if (conn->disconnected) + return; + + if (conn->to_idle == NULL && + conn->set.max_client_idle_time_msecs > 0) { + e_debug(conn->event, "Timeout start"); + + conn->to_idle = timeout_add( + conn->set.max_client_idle_time_msecs, + smtp_server_connection_idle_timeout, conn); + } +} + +void smtp_server_connection_timeout_reset(struct smtp_server_connection *conn) +{ + if (conn->to_idle != NULL) + timeout_reset(conn->to_idle); +} + +static void +smtp_server_connection_timeout_update(struct smtp_server_connection *conn) +{ + struct smtp_server_command *cmd = conn->command_queue_head; + + if (cmd == NULL) { + smtp_server_connection_timeout_start(conn); + return; + } + + switch (cmd->state) { + case SMTP_SERVER_COMMAND_STATE_NEW: + smtp_server_connection_timeout_start(conn); + break; + case SMTP_SERVER_COMMAND_STATE_PROCESSING: + if (cmd->input_captured) { + /* Command updates timeout internally */ + return; + } + smtp_server_connection_timeout_stop(conn); + break; + case SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY: + case SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY: + smtp_server_connection_timeout_stop(conn); + break; + case SMTP_SERVER_COMMAND_STATE_FINISHED: + case SMTP_SERVER_COMMAND_STATE_ABORTED: + i_unreached(); + } +} + +static void smtp_server_connection_ready(struct smtp_server_connection *conn) +{ + conn->raw_input = conn->conn.input; + conn->raw_output = conn->conn.output; + + smtp_server_connection_update_rawlog(conn); + + conn->smtp_parser = smtp_command_parser_init(conn->conn.input, + &conn->set.command_limits); + o_stream_set_flush_callback(conn->conn.output, + smtp_server_connection_output, conn); + + o_stream_cork(conn->conn.output); + if (conn->set.no_greeting) { + /* Don't send greeting or login reply. */ + } else if (conn->authenticated) { + /* RFC 4954, Section 4: + Should the client successfully complete the exchange, the + SMTP server issues a 235 reply. */ + smtp_server_connection_send_line( + conn, "235 2.7.0 Logged in."); + } else { + smtp_server_connection_send_line( + conn, "220 %s %s", conn->set.hostname, + conn->set.login_greeting); + } + if (!conn->corked) + o_stream_uncork(conn->conn.output); +} + +static void smtp_server_connection_destroy(struct connection *_conn) +{ + struct smtp_server_connection *conn = + (struct smtp_server_connection *)_conn; + + smtp_server_connection_disconnect(conn, NULL); + smtp_server_connection_unref(&conn); +} + +static bool +smtp_server_connection_handle_command(struct smtp_server_connection *conn, + const char *cmd_name, const char *cmd_params) +{ + struct smtp_server_connection *tmp_conn = conn; + struct smtp_server_command *cmd; + bool finished; + + cmd = smtp_server_command_new(tmp_conn, cmd_name); + + smtp_server_command_ref(cmd); + + smtp_server_connection_ref(tmp_conn); + smtp_server_command_execute(cmd, cmd_params); + if (!smtp_server_connection_unref(&tmp_conn)) { + /* The command start callback managed to get this connection + destroyed */ + smtp_server_command_unref(&cmd); + return FALSE; + } + + if (conn->command_queue_head == cmd) + (void)smtp_server_command_next_to_reply(&cmd); + + smtp_server_connection_timeout_update(conn); + + finished = !cmd->input_locked; + return (!smtp_server_command_unref(&cmd) || finished); +} + +static int +smtp_server_connection_init_ssl_ctx(struct smtp_server_connection *conn, + const char **error_r) +{ + struct smtp_server *server = conn->server; + const char *error; + + if (conn->ssl_ctx != NULL || conn->set.ssl == NULL) + return 0; + if (conn->set.ssl == server->set.ssl) { + if (smtp_server_init_ssl_ctx(server, error_r) < 0) + return -1; + conn->ssl_ctx = server->ssl_ctx; + ssl_iostream_context_ref(conn->ssl_ctx); + return 0; + } + + if (ssl_iostream_server_context_cache_get(conn->set.ssl, &conn->ssl_ctx, + &error) < 0) { + *error_r = t_strdup_printf( + "Couldn't initialize SSL context: %s", error); + return -1; + } + return 0; +} + +int smtp_server_connection_ssl_init(struct smtp_server_connection *conn) +{ + const char *error; + int ret; + + if (smtp_server_connection_init_ssl_ctx(conn, &error) < 0) { + e_error(conn->event, "Couldn't initialize SSL: %s", error); + return -1; + } + + e_debug(conn->event, "Starting SSL handshake"); + + if (conn->raw_input != conn->conn.input) { + /* Recreate rawlog after STARTTLS */ + i_stream_ref(conn->raw_input); + o_stream_ref(conn->raw_output); + i_stream_destroy(&conn->conn.input); + o_stream_destroy(&conn->conn.output); + conn->conn.input = conn->raw_input; + conn->conn.output = conn->raw_output; + } + + smtp_server_connection_input_halt(conn); + if (conn->ssl_ctx == NULL) { + ret = master_service_ssl_init( + master_service, &conn->conn.input, &conn->conn.output, + &conn->ssl_iostream, &error); + } else { + ret = io_stream_create_ssl_server( + conn->ssl_ctx, conn->set.ssl, + &conn->conn.input, &conn->conn.output, + &conn->ssl_iostream, &error); + } + if (ret < 0) { + e_error(conn->event, + "Couldn't initialize SSL server for %s: %s", + conn->conn.name, error); + return -1; + } + smtp_server_connection_input_resume(conn); + + conn->ssl_secured = TRUE; + conn->set.capabilities &= ENUM_NEGATE(SMTP_CAPABILITY_STARTTLS); + + if (conn->ssl_start) + smtp_server_connection_ready(conn); + else + smtp_server_connection_streams_changed(conn); + return 0; +} + +static void +smtp_server_connection_handle_input(struct smtp_server_connection *conn) +{ + struct smtp_server_command *pending_command; + enum smtp_command_parse_error error_code; + const char *cmd_name, *cmd_params, *error; + int ret; + + /* Check whether we are continuing a command */ + pending_command = NULL; + if (conn->command_queue_tail != NULL) { + pending_command = + ((conn->command_queue_tail->state == + SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY) ? + conn->command_queue_tail : NULL); + } + + smtp_server_connection_timeout_reset(conn); + + /* Parse commands */ + ret = 1; + while (!conn->closing && !conn->input_locked && ret != 0) { + while ((ret = smtp_command_parse_next( + conn->smtp_parser, &cmd_name, &cmd_params, + &error_code, &error)) > 0) { + + if (pending_command != NULL) { + /* Previous command is now fully read and ready + to reply */ + smtp_server_command_ready_to_reply(pending_command); + pending_command = NULL; + } + + e_debug(conn->event, "Received new command: %s %s", + cmd_name, cmd_params); + + conn->stats.command_count++; + + /* Handle command (cmd may be destroyed after this) */ + if (!smtp_server_connection_handle_command(conn, + cmd_name, cmd_params)) + return; + + if (conn->disconnected) + return; + /* Last command locked the input; stop trying to read + more. */ + if (conn->input_locked) + break; + /* Client indicated it will close after this command; + stop trying to read more. */ + if (conn->closing) + break; + + if (!smtp_server_connection_check_pipeline(conn)) { + smtp_server_connection_input_halt(conn); + return; + } + + if (conn->command_queue_tail != NULL) { + pending_command = + ((conn->command_queue_tail->state == + SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY) ? + conn->command_queue_tail : NULL); + } + } + + if (ret < 0 && conn->conn.input->eof) { + const char *error = + i_stream_get_disconnect_reason(conn->conn.input); + e_debug(conn->event, "Remote closed connection: %s", + error); + + if (conn->command_queue_head == NULL || + conn->command_queue_head->state < + SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY) { + /* No pending commands or unfinished + command; close */ + smtp_server_connection_close(&conn, error); + } else { + /* A command is still processing; + only drop input io for now */ + conn->input_broken = TRUE; + smtp_server_connection_input_halt(conn); + } + return; + } + + if (ret < 0) { + struct smtp_server_command *cmd; + + e_debug(conn->event, + "Client sent invalid command: %s", error); + + switch (error_code) { + case SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND: + conn->input_broken = TRUE; + /* fall through */ + case SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND: + cmd = smtp_server_command_new_invalid(conn); + smtp_server_command_fail( + cmd, 500, "5.5.2", + "Invalid command syntax"); + break; + case SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG: + cmd = smtp_server_command_new_invalid(conn); + smtp_server_command_fail( + cmd, 500, "5.5.2", "Line too long"); + break; + case SMTP_COMMAND_PARSE_ERROR_DATA_TOO_LARGE: + /* Command data size exceeds the absolute limit; + i.e. beyond which we don't even want to skip + data anymore. The command error is usually + already submitted by the application and sent + to the client. */ + smtp_server_connection_close(&conn, + "Command data size exceeds absolute limit"); + return; + case SMTP_COMMAND_PARSE_ERROR_BROKEN_STREAM: + smtp_server_connection_close(&conn, error); + return; + default: + i_unreached(); + } + } + + if (conn->disconnected) + return; + if (conn->input_broken || conn->closing) { + smtp_server_connection_input_halt(conn); + return; + } + + if (ret == 0 && pending_command != NULL && + !smtp_command_parser_pending_data(conn->smtp_parser)) { + /* Previous command is now fully read and ready to + reply */ + smtp_server_command_ready_to_reply(pending_command); + } + } +} + +static void smtp_server_connection_input(struct connection *_conn) +{ + struct smtp_server_connection *conn = + (struct smtp_server_connection *)_conn; + + i_assert(!conn->input_broken); + + if (conn->handling_input) + return; + + smtp_server_connection_timeout_reset(conn); + + if (conn->ssl_start && conn->ssl_iostream == NULL) { + if (smtp_server_connection_ssl_init(conn) < 0) { + smtp_server_connection_close(&conn, + "SSL Initialization failed"); + return; + } + if (conn->halted) { + smtp_server_connection_input_lock(conn); + return; + } + } + i_assert(!conn->halted); + + + if (!smtp_server_connection_check_pipeline(conn)) { + smtp_server_connection_input_halt(conn); + return; + } + + smtp_server_connection_ref(conn); + conn->handling_input = TRUE; + if (conn->callbacks != NULL && + conn->callbacks->conn_cmd_input_pre != NULL) + conn->callbacks->conn_cmd_input_pre(conn->context); + smtp_server_connection_handle_input(conn); + if (conn->callbacks != NULL && + conn->callbacks->conn_cmd_input_post != NULL) + conn->callbacks->conn_cmd_input_post(conn->context); + conn->handling_input = FALSE; + smtp_server_connection_unref(&conn); +} + +bool smtp_server_connection_pending_command_data( + struct smtp_server_connection *conn) +{ + if (conn->smtp_parser == NULL) + return FALSE; + return smtp_command_parser_pending_data(conn->smtp_parser); +} + +/* + * Command reply handling + */ + +void smtp_server_connection_handle_output_error( + struct smtp_server_connection *conn) +{ + smtp_server_connection_close(&conn, + o_stream_get_disconnect_reason(conn->conn.output)); +} + +static bool +smtp_server_connection_next_reply(struct smtp_server_connection *conn) +{ + struct smtp_server_command *cmd; + + cmd = conn->command_queue_head; + if (cmd == NULL) { + /* No commands pending */ + e_debug(conn->event, "No more commands pending"); + return FALSE; + } + + return smtp_server_command_send_replies(cmd); +} + +void smtp_server_connection_cork(struct smtp_server_connection *conn) +{ + conn->corked = TRUE; + if (conn->conn.output != NULL) + o_stream_cork(conn->conn.output); +} + +void smtp_server_connection_uncork(struct smtp_server_connection *conn) +{ + conn->corked = FALSE; + if (conn->conn.output != NULL) { + if (o_stream_uncork_flush(conn->conn.output) < 0) { + smtp_server_connection_handle_output_error(conn); + return; + } + smtp_server_connection_trigger_output(conn); + } +} + +static void +smtp_server_connection_send_replies(struct smtp_server_connection *conn) +{ + /* Send more replies until no more replies remain, the output + blocks again, or the connection is closed */ + while (!conn->disconnected && smtp_server_connection_next_reply(conn)); + + smtp_server_connection_timeout_update(conn); + + /* Accept more commands if possible */ + smtp_server_connection_input_resume(conn); +} + +int smtp_server_connection_flush(struct smtp_server_connection *conn) +{ + struct ostream *output = conn->conn.output; + int ret; + + if ((ret = o_stream_flush(output)) <= 0) { + if (ret < 0) + smtp_server_connection_handle_output_error(conn); + return ret; + } + return 1; +} + +static int smtp_server_connection_output(struct smtp_server_connection *conn) +{ + int ret; + + e_debug(conn->event, "Sending replies"); + + smtp_server_connection_ref(conn); + o_stream_cork(conn->conn.output); + ret = smtp_server_connection_flush(conn); + if (ret > 0) { + smtp_server_connection_timeout_reset(conn); + smtp_server_connection_send_replies(conn); + } + if (ret >= 0 && !conn->corked && conn->conn.output != NULL) + ret = o_stream_uncork_flush(conn->conn.output); + if (conn->conn.output != NULL && conn->conn.output->closed) { + smtp_server_connection_handle_output_error(conn); + ret = -1; + } + smtp_server_connection_unref(&conn); + return ret; +} + +void smtp_server_connection_trigger_output(struct smtp_server_connection *conn) +{ + if (conn->conn.output != NULL) { + e_debug(conn->event, "Trigger output"); + o_stream_set_flush_pending(conn->conn.output, TRUE); + } +} + +/* + * + */ + +static struct connection_settings smtp_server_connection_set = { + .input_max_size = SIZE_MAX, + .output_max_size = SIZE_MAX, + .client = FALSE, + .log_connection_id = TRUE, +}; + +static const struct connection_vfuncs smtp_server_connection_vfuncs = { + .destroy = smtp_server_connection_destroy, + .input = smtp_server_connection_input, +}; + +struct connection_list *smtp_server_connection_list_init(void) +{ + return connection_list_init(&smtp_server_connection_set, + &smtp_server_connection_vfuncs); +} + +static struct event * +smtp_server_connection_event_create(struct smtp_server *server, + const struct smtp_server_settings *set) +{ + struct event *conn_event; + + if (set != NULL && set->event_parent != NULL) { + conn_event = event_create(set->event_parent); + smtp_server_event_init(server, conn_event); + } else + conn_event = event_create(server->event); + event_set_append_log_prefix(conn_event, t_strdup_printf( + "%s-server: ", smtp_protocol_name(server->set.protocol))); + event_set_forced_debug(conn_event, (set != NULL && set->debug)); + + return conn_event; +} + +static void +smtp_server_connection_update_event(struct smtp_server_connection *conn) +{ + event_add_str(conn->event, "connection_id", conn->session_id); + event_add_str(conn->event, "session", conn->session_id); +} + +static void +smtp_server_connection_init_session(struct smtp_server_connection *conn) +{ + guid_128_t guid; + string_t *session_id; + + session_id = t_str_new(30); + guid_128_generate(guid); + base64_encode(guid, sizeof(guid), session_id); + + /* drop trailing "==" */ + i_assert(str_c(session_id)[str_len(session_id)-2] == '='); + str_truncate(session_id, str_len(session_id)-2); + + conn->session_id = i_strdup(str_c(session_id)); +} + +static struct smtp_server_connection * ATTR_NULL(5, 6) +smtp_server_connection_alloc(struct smtp_server *server, + const struct smtp_server_settings *set, + int fd_in, int fd_out, + const struct smtp_server_callbacks *callbacks, + void *context) +{ + struct smtp_server_connection *conn; + pool_t pool; + + pool = pool_alloconly_create("smtp server", 1024); + conn = p_new(pool, struct smtp_server_connection, 1); + conn->pool = pool; + conn->refcount = 1; + conn->server = server; + conn->callbacks = callbacks; + conn->context = context; + + /* Merge settings with global server settings */ + conn->set = server->set; + if (set != NULL) { + conn->set.protocol = server->set.protocol; + if (set->rawlog_dir != NULL && *set->rawlog_dir != '\0') + conn->set.rawlog_dir = p_strdup(pool, set->rawlog_dir); + + if (set->ssl != NULL) + conn->set.ssl = ssl_iostream_settings_dup(pool, set->ssl); + + if (set->hostname != NULL && *set->hostname != '\0') + conn->set.hostname = p_strdup(pool, set->hostname); + if (set->login_greeting != NULL && + *set->login_greeting != '\0') { + conn->set.login_greeting = + p_strdup(pool, set->login_greeting); + } + if (set->capabilities != 0) + conn->set.capabilities = set->capabilities; + conn->set.workarounds |= set->workarounds; + + if (set->max_client_idle_time_msecs > 0) { + conn->set.max_client_idle_time_msecs = + set->max_client_idle_time_msecs; + } + if (set->max_pipelined_commands > 0) { + conn->set.max_pipelined_commands = + set->max_pipelined_commands; + } + if (set->max_bad_commands > 0) { + conn->set.max_bad_commands = set->max_bad_commands; + } + if (set->max_recipients > 0) + conn->set.max_recipients = set->max_recipients; + smtp_command_limits_merge(&conn->set.command_limits, + &set->command_limits); + + conn->set.max_message_size = set->max_message_size; + if (set->max_message_size == 0 || + set->max_message_size == UOFF_T_MAX) { + conn->set.command_limits.max_data_size = UOFF_T_MAX; + } else if (conn->set.command_limits.max_data_size != 0) { + /* Explicit limit given */ + } else if (set->max_message_size > + (UOFF_T_MAX - SMTP_SERVER_DEFAULT_MAX_SIZE_EXCESS_LIMIT)) { + /* Very high limit */ + conn->set.command_limits.max_data_size = UOFF_T_MAX; + } else { + /* Absolute maximum before connection is closed in DATA + command */ + conn->set.command_limits.max_data_size = + set->max_message_size + + SMTP_SERVER_DEFAULT_MAX_SIZE_EXCESS_LIMIT; + } + + if (set->mail_param_extensions != NULL) { + conn->set.mail_param_extensions = + p_strarray_dup(pool, set->mail_param_extensions); + } + if (set->rcpt_param_extensions != NULL) { + conn->set.rcpt_param_extensions = + p_strarray_dup(pool, set->rcpt_param_extensions); + } + if (set->xclient_extensions != NULL) { + conn->set.xclient_extensions = + p_strarray_dup(pool, set->xclient_extensions); + } + + if (set->socket_send_buffer_size > 0) { + conn->set.socket_send_buffer_size = + set->socket_send_buffer_size; + } + if (set->socket_recv_buffer_size > 0) { + conn->set.socket_recv_buffer_size = + set->socket_recv_buffer_size; + } + + conn->set.tls_required = + conn->set.tls_required || set->tls_required; + conn->set.auth_optional = + conn->set.auth_optional || set->auth_optional; + conn->set.mail_path_allow_broken = + conn->set.mail_path_allow_broken || + set->mail_path_allow_broken; + conn->set.rcpt_domain_optional = + conn->set.rcpt_domain_optional || + set->rcpt_domain_optional; + conn->set.no_greeting = + conn->set.no_greeting || set->no_greeting; + conn->set.debug = conn->set.debug || set->debug; + } + + if (set != NULL && set->mail_param_extensions != NULL) { + const char *const *extp; + + p_array_init(&conn->mail_param_extensions, pool, + str_array_length(set->mail_param_extensions) + 8); + for (extp = set->mail_param_extensions; *extp != NULL; extp++) { + const char *ext = p_strdup(pool, *extp); + array_push_back(&conn->mail_param_extensions, &ext); + } + array_append_zero(&conn->mail_param_extensions); + } + if (set != NULL && set->rcpt_param_extensions != NULL) { + const char *const *extp; + + p_array_init(&conn->rcpt_param_extensions, pool, + str_array_length(set->rcpt_param_extensions) + 8); + for (extp = set->rcpt_param_extensions; *extp != NULL; extp++) { + const char *ext = p_strdup(pool, *extp); + array_push_back(&conn->rcpt_param_extensions, &ext); + } + array_append_zero(&conn->rcpt_param_extensions); + } + + net_set_nonblock(fd_in, TRUE); + if (fd_in != fd_out) + net_set_nonblock(fd_out, TRUE); + (void)net_set_tcp_nodelay(fd_out, TRUE); + + set = &conn->set; + if (set->socket_send_buffer_size > 0 && + net_set_send_buffer_size(fd_out, + set->socket_send_buffer_size) < 0) { + e_error(conn->event, + "net_set_send_buffer_size(%zu) failed: %m", + set->socket_send_buffer_size); + } + if (set->socket_recv_buffer_size > 0 && + net_set_recv_buffer_size(fd_in, + set->socket_recv_buffer_size) < 0) { + e_error(conn->event, + "net_set_recv_buffer_size(%zu) failed: %m", + set->socket_recv_buffer_size); + } + + smtp_server_connection_init_session(conn); + + return conn; +} + +struct smtp_server_connection * +smtp_server_connection_create( + struct smtp_server *server, int fd_in, int fd_out, + const struct ip_addr *remote_ip, in_port_t remote_port, + bool ssl_start, const struct smtp_server_settings *set, + const struct smtp_server_callbacks *callbacks, void *context) +{ + struct smtp_server_connection *conn; + struct event *conn_event; + + conn = smtp_server_connection_alloc(server, set, fd_in, fd_out, + callbacks, context); + conn_event = smtp_server_connection_event_create(server, set); + conn->conn.event_parent = conn_event; + connection_init_server_ip(server->conn_list, &conn->conn, NULL, + fd_in, fd_out, remote_ip, remote_port); + conn->event = conn->conn.event; + smtp_server_connection_update_event(conn); + event_unref(&conn_event); + + conn->ssl_start = ssl_start; + if (ssl_start) + conn->set.capabilities &= ENUM_NEGATE(SMTP_CAPABILITY_STARTTLS); + + /* Halt input until started */ + smtp_server_connection_halt(conn); + + e_debug(conn->event, "Connection created"); + + return conn; +} + +struct smtp_server_connection * +smtp_server_connection_create_from_streams( + struct smtp_server *server, + struct istream *input, struct ostream *output, + const struct ip_addr *remote_ip, in_port_t remote_port, + const struct smtp_server_settings *set, + const struct smtp_server_callbacks *callbacks, void *context) +{ + struct smtp_server_connection *conn; + struct event *conn_event; + int fd_in, fd_out; + + fd_in = i_stream_get_fd(input); + fd_out = o_stream_get_fd(output); + i_assert(fd_in >= 0); + i_assert(fd_out >= 0); + + conn = smtp_server_connection_alloc(server, set, fd_in, fd_out, + callbacks, context); + if (remote_ip != NULL && remote_ip->family != 0) + conn->conn.remote_ip = *remote_ip; + if (remote_port != 0) + conn->conn.remote_port = remote_port; + conn_event = smtp_server_connection_event_create(server, set); + conn->conn.event_parent = conn_event; + connection_init_from_streams(server->conn_list, &conn->conn, NULL, + input, output); + conn->created_from_streams = TRUE; + conn->event = conn->conn.event; + smtp_server_connection_update_event(conn); + event_unref(&conn_event); + + /* Halt input until started */ + smtp_server_connection_halt(conn); + + e_debug(conn->event, "Connection created"); + + return conn; +} + +void smtp_server_connection_ref(struct smtp_server_connection *conn) +{ + conn->refcount++; +} + +static const char * +smtp_server_connection_get_disconnect_reason( + struct smtp_server_connection *conn) +{ + const char *err; + + if (conn->ssl_iostream != NULL && + !ssl_iostream_is_handshaked(conn->ssl_iostream)) { + err = ssl_iostream_get_last_error(conn->ssl_iostream); + if (err != NULL) { + return t_strdup_printf( + "TLS handshaking failed: %s", err); + } + } + + return io_stream_get_disconnect_reason(conn->conn.input, + conn->conn.output); +} + +static void +smtp_server_connection_disconnect(struct smtp_server_connection *conn, + const char *reason) +{ + struct smtp_server_command *cmd, *cmd_next; + + if (conn->disconnected) + return; + conn->disconnected = TRUE; + + if (reason == NULL) + reason = smtp_server_connection_get_disconnect_reason(conn); + else + reason = t_str_oneline(reason); + + cmd = conn->command_queue_head; + if (cmd != NULL && cmd->reg != NULL) { + /* Unfinished command - include it in the reason string */ + reason = t_strdup_printf("%s (unfinished %s command)", + reason, cmd->reg->name); + } + if (!conn->set.no_state_in_reason) { + reason = t_strdup_printf("%s (state=%s)", reason, + smtp_server_state_names[conn->state.state]); + } + + e_debug(conn->event, "Disconnected: %s", reason); + + /* Preserve statistics */ + smtp_server_connection_update_stats(conn); + + /* Drop transaction */ + smtp_server_connection_reset_state(conn); + + /* Clear command queue */ + cmd = conn->command_queue_head; + while (cmd != NULL) { + cmd_next = cmd->next; + smtp_server_command_abort(&cmd); + cmd = cmd_next; + } + + smtp_server_connection_timeout_stop(conn); + if (conn->conn.output != NULL) + o_stream_uncork(conn->conn.output); + if (conn->smtp_parser != NULL) + smtp_command_parser_deinit(&conn->smtp_parser); + ssl_iostream_destroy(&conn->ssl_iostream); + if (conn->ssl_ctx != NULL) + ssl_iostream_context_unref(&conn->ssl_ctx); + + if (conn->callbacks != NULL && + conn->callbacks->conn_disconnect != NULL) { + /* The callback may close the fd, so remove IO before that */ + io_remove(&conn->conn.io); + conn->callbacks->conn_disconnect(conn->context, reason); + } + + if (!conn->created_from_streams) + connection_disconnect(&conn->conn); + else { + conn->conn.fd_in = conn->conn.fd_out = -1; + io_remove(&conn->conn.io); + i_stream_unref(&conn->conn.input); + o_stream_unref(&conn->conn.output); + } +} + +bool smtp_server_connection_unref(struct smtp_server_connection **_conn) +{ + struct smtp_server_connection *conn = *_conn; + + *_conn = NULL; + + i_assert(conn->refcount > 0); + if (--conn->refcount > 0) + return TRUE; + + smtp_server_connection_disconnect(conn, NULL); + + e_debug(conn->event, "Connection destroy"); + + if (conn->callbacks != NULL && conn->callbacks->conn_free != NULL) + conn->callbacks->conn_free(conn->context); + + connection_deinit(&conn->conn); + + i_free(conn->proxy_helo); + i_free(conn->helo_domain); + i_free(conn->username); + i_free(conn->session_id); + event_unref(&conn->next_trans_event); + pool_unref(&conn->pool); + return FALSE; +} + +void smtp_server_connection_send_line(struct smtp_server_connection *conn, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + T_BEGIN { + string_t *str; + + str = t_str_new(256); + str_vprintfa(str, fmt, args); + + e_debug(conn->event, "Sent: %s", str_c(str)); + + str_append(str, "\r\n"); + o_stream_nsend(conn->conn.output, str_data(str), str_len(str)); + } T_END; + va_end(args); +} + +void smtp_server_connection_reply_lines(struct smtp_server_connection *conn, + unsigned int status, + const char *enh_code, + const char *const *text_lines) +{ + struct smtp_reply reply; + + i_zero(&reply); + reply.status = status; + reply.text_lines = text_lines; + + if (!smtp_reply_parse_enhanced_code( + enh_code, &reply.enhanced_code, NULL)) + reply.enhanced_code = SMTP_REPLY_ENH_CODE(status / 100, 0, 0); + + T_BEGIN { + string_t *str; + + e_debug(conn->event, "Sent: %s", smtp_reply_log(&reply)); + + str = t_str_new(256); + smtp_reply_write(str, &reply); + o_stream_nsend(conn->conn.output, str_data(str), str_len(str)); + } T_END; +} + +void smtp_server_connection_reply_immediate( + struct smtp_server_connection *conn, + unsigned int status, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + T_BEGIN { + string_t *str; + + str = t_str_new(256); + str_printfa(str, "%03u ", status); + str_vprintfa(str, fmt, args); + + e_debug(conn->event, "Sent: %s", str_c(str)); + + str_append(str, "\r\n"); + o_stream_nsend(conn->conn.output, str_data(str), str_len(str)); + } T_END; + va_end(args); + + /* Send immediately */ + if (o_stream_is_corked(conn->conn.output)) { + o_stream_uncork(conn->conn.output); + o_stream_cork(conn->conn.output); + } +} + +void smtp_server_connection_login(struct smtp_server_connection *conn, + const char *username, const char *helo, + const unsigned char *pdata, + unsigned int pdata_len, bool ssl_secured) +{ + i_assert(!conn->started); + + conn->set.capabilities &= ENUM_NEGATE(SMTP_CAPABILITY_STARTTLS); + i_free(conn->username); + conn->username = i_strdup(username); + if (helo != NULL && *helo != '\0') { + i_free(conn->helo_domain); + conn->helo_domain = i_strdup(helo); + conn->helo.domain = conn->helo_domain; + conn->helo.domain_valid = TRUE; + } + conn->authenticated = TRUE; + conn->ssl_secured = ssl_secured; + + if (pdata_len > 0) { + if (!i_stream_add_data(conn->conn.input, pdata, pdata_len)) + i_panic("Couldn't add client input to stream"); + } +} + +void smtp_server_connection_start_pending(struct smtp_server_connection *conn) +{ + i_assert(!conn->started); + conn->started = TRUE; + + conn->raw_input = conn->conn.input; + conn->raw_output = conn->conn.output; + + if (!conn->ssl_start) + smtp_server_connection_ready(conn); + else if (conn->ssl_iostream == NULL) + smtp_server_connection_input_unlock(conn); +} + +void smtp_server_connection_start(struct smtp_server_connection *conn) +{ + smtp_server_connection_start_pending(conn); + smtp_server_connection_resume(conn); +} + +void smtp_server_connection_abort(struct smtp_server_connection **_conn, + unsigned int status, const char *enh_code, + const char *reason) +{ + struct smtp_server_connection *conn = *_conn; + const char **reason_lines; + + if (conn == NULL) + return; + *_conn = NULL; + + i_assert(!conn->started); + conn->started = TRUE; + + if (conn->authenticated) { + reason_lines = t_strsplit_spaces(reason, "\r\n"); + smtp_server_connection_reply_lines( + conn, status, enh_code, reason_lines); + smtp_server_connection_terminate( + &conn, "4.3.2", "Shutting down due to fatal error"); + } else { + smtp_server_connection_terminate(&conn, enh_code, reason); + } +} + +void smtp_server_connection_halt(struct smtp_server_connection *conn) +{ + conn->halted = TRUE; + smtp_server_connection_timeout_stop(conn); + if (!conn->started || !conn->ssl_start || conn->ssl_iostream != NULL) + smtp_server_connection_input_lock(conn); +} + +void smtp_server_connection_resume(struct smtp_server_connection *conn) +{ + smtp_server_connection_input_unlock(conn); + smtp_server_connection_timeout_update(conn); + conn->halted = FALSE; +} + +void smtp_server_connection_close(struct smtp_server_connection **_conn, + const char *reason) +{ + struct smtp_server_connection *conn = *_conn; + + *_conn = NULL; + + if (conn->closed) + return; + conn->closed = TRUE; + + smtp_server_connection_disconnect(conn, reason); + smtp_server_connection_unref(&conn); +} + +void smtp_server_connection_terminate(struct smtp_server_connection **_conn, + const char *enh_code, const char *reason) +{ + struct smtp_server_connection *conn = *_conn; + const char **reason_lines; + + *_conn = NULL; + + if (conn->closed) + return; + + i_assert(enh_code[0] == '4' && enh_code[1] == '.'); + + T_BEGIN { + /* Add hostname prefix */ + reason_lines = t_strsplit_spaces(reason, "\r\n"); + reason_lines[0] = t_strconcat(conn->set.hostname, " ", + reason_lines[0], NULL); + + smtp_server_connection_reply_lines(conn, 421, enh_code, + reason_lines); + + smtp_server_connection_close(&conn, reason); + } T_END; +} + +struct smtp_server_helo_data * +smtp_server_connection_get_helo_data(struct smtp_server_connection *conn) +{ + return &conn->helo; +} + +enum smtp_server_state +smtp_server_connection_get_state(struct smtp_server_connection *conn, + const char **args_r) +{ + if (args_r != NULL) + *args_r = conn->state.args; + return conn->state.state; +} + +void smtp_server_connection_set_state(struct smtp_server_connection *conn, + enum smtp_server_state state, + const char *args) +{ + bool changed = FALSE; + + if (conn->state.state != state) { + conn->state.state = state; + changed = TRUE; + } + if (null_strcmp(args, conn->state.args) != 0) { + i_free(conn->state.args); + conn->state.args = i_strdup(args); + changed = TRUE; + } + + if (changed && conn->callbacks != NULL && + conn->callbacks->conn_state_changed != NULL) + conn->callbacks->conn_state_changed(conn->context, state, args); +} + +const char * +smtp_server_connection_get_security_string(struct smtp_server_connection *conn) +{ + if (conn->ssl_iostream == NULL) + return NULL; + return ssl_iostream_get_security_string(conn->ssl_iostream); +} + +void smtp_server_connection_reset_state(struct smtp_server_connection *conn) +{ + e_debug(conn->event, "Connection state reset"); + + i_free(conn->state.args); + + if (conn->state.trans != NULL) + smtp_server_transaction_free(&conn->state.trans); + + /* RFC 3030, Section 2: + The RSET command, when issued after the first BDAT and before the + BDAT LAST, clears all segments sent during that transaction and resets + the session. + */ + i_stream_destroy(&conn->state.data_input); + i_stream_destroy(&conn->state.data_chain_input); + conn->state.data_chain = NULL; + + /* Reset state */ + i_zero(&conn->state); + smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_READY, NULL); +} + +void smtp_server_connection_clear(struct smtp_server_connection *conn) +{ + e_debug(conn->event, "Connection clear"); + + i_free(conn->helo_domain); + i_zero(&conn->helo); + smtp_server_connection_reset_state(conn); +} + +void smtp_server_connection_set_capabilities( + struct smtp_server_connection *conn, enum smtp_capability capabilities) +{ + conn->set.capabilities = capabilities; +} + +void smtp_server_connection_add_extra_capability( + struct smtp_server_connection *conn, + const struct smtp_capability_extra *cap) +{ + const struct smtp_capability_extra *cap_idx; + struct smtp_capability_extra cap_new; + unsigned int insert_idx; + pool_t pool = conn->pool; + + /* Avoid committing protocol errors */ + i_assert(smtp_ehlo_keyword_is_valid(cap->name)); + i_assert(smtp_ehlo_params_are_valid(cap->params)); + + /* Cannot override standard capabiltiies */ + i_assert(smtp_capability_find_by_name(cap->name) + == SMTP_CAPABILITY_NONE); + + if (!array_is_created(&conn->extra_capabilities)) + p_array_init(&conn->extra_capabilities, pool, 4); + + /* Keep array sorted */ + insert_idx = array_count(&conn->extra_capabilities); + array_foreach(&conn->extra_capabilities, cap_idx) { + int cmp = strcasecmp(cap_idx->name, cap->name); + + /* Prohibit duplicates */ + i_assert(cmp != 0); + + if (cmp > 0) { + insert_idx = array_foreach_idx( + &conn->extra_capabilities, cap_idx); + break; + } + } + + i_zero(&cap_new); + cap_new.name = p_strdup(pool, cap->name); + if (cap->params != NULL) + cap_new.params = p_strarray_dup(pool, cap->params); + + array_insert(&conn->extra_capabilities, insert_idx, &cap_new, 1); +} + +void *smtp_server_connection_get_context(struct smtp_server_connection *conn) +{ + return conn->context; +} + +bool smtp_server_connection_is_ssl_secured(struct smtp_server_connection *conn) +{ + return conn->ssl_secured; +} + +bool smtp_server_connection_is_trusted(struct smtp_server_connection *conn) +{ + if (conn->callbacks == NULL || conn->callbacks->conn_is_trusted == NULL) + return FALSE; + return conn->callbacks->conn_is_trusted(conn->context); +} + +enum smtp_protocol +smtp_server_connection_get_protocol(struct smtp_server_connection *conn) +{ + return conn->set.protocol; +} + +const char * +smtp_server_connection_get_protocol_name(struct smtp_server_connection *conn) +{ + string_t *pname = t_str_new(16); + + switch (conn->set.protocol) { + case SMTP_PROTOCOL_SMTP: + if (conn->helo.old_smtp) + str_append(pname, "SMTP"); + else + str_append(pname, "ESMTP"); + break; + case SMTP_PROTOCOL_LMTP: + str_append(pname, "LMTP"); + break; + default: + i_unreached(); + } + if (conn->ssl_secured) + str_append_c(pname, 'S'); + if (conn->authenticated) + str_append_c(pname, 'A'); + return str_c(pname); +} + +struct smtp_server_transaction * +smtp_server_connection_get_transaction(struct smtp_server_connection *conn) +{ + return conn->state.trans; +} + +const char * +smtp_server_connection_get_transaction_id(struct smtp_server_connection *conn) +{ + if (conn->state.trans == NULL) + return NULL; + return conn->state.trans->id; +} + +void smtp_server_connection_get_proxy_data(struct smtp_server_connection *conn, + struct smtp_proxy_data *proxy_data) +{ + i_zero(proxy_data); + proxy_data->source_ip = conn->conn.remote_ip; + proxy_data->source_port = conn->conn.remote_port; + if (conn->proxy_helo != NULL) + proxy_data->helo = conn->proxy_helo; + else if (conn->helo.domain_valid) + proxy_data->helo = conn->helo.domain; + proxy_data->login = conn->username; + proxy_data->session = conn->session_id; + + if (conn->proxy_proto != SMTP_PROXY_PROTOCOL_UNKNOWN) + proxy_data->proto = conn->proxy_proto; + else if (conn->set.protocol == SMTP_PROTOCOL_LMTP) + proxy_data->proto = SMTP_PROXY_PROTOCOL_LMTP; + else if (conn->helo.old_smtp) + proxy_data->proto = SMTP_PROXY_PROTOCOL_SMTP; + else + proxy_data->proto = SMTP_PROXY_PROTOCOL_ESMTP; + + proxy_data->ttl_plus_1 = conn->proxy_ttl_plus_1; + proxy_data->timeout_secs = conn->proxy_timeout_secs; +} + +void smtp_server_connection_set_proxy_data( + struct smtp_server_connection *conn, + const struct smtp_proxy_data *proxy_data) +{ + if (proxy_data->source_ip.family != 0) + conn->conn.remote_ip = proxy_data->source_ip; + if (proxy_data->source_port != 0) + conn->conn.remote_port = proxy_data->source_port; + if (proxy_data->helo != NULL) { + i_free(conn->helo_domain); + conn->helo_domain = i_strdup(proxy_data->helo); + conn->helo.domain = conn->helo_domain; + conn->helo.domain_valid = TRUE; + if (conn->helo.domain_valid) { + i_free(conn->proxy_helo); + conn->proxy_helo = i_strdup(proxy_data->helo); + } + } + if (proxy_data->login != NULL) { + i_free(conn->username); + conn->username = i_strdup(proxy_data->login); + } + if (proxy_data->proto != SMTP_PROXY_PROTOCOL_UNKNOWN) + conn->proxy_proto = proxy_data->proto; + if (proxy_data->session != NULL && + strcmp(proxy_data->session, conn->session_id) != 0) { + e_debug(conn->event, "Updated session ID from %s to %s", + conn->session_id, proxy_data->session); + i_free(conn->session_id); + conn->session_id = i_strdup(proxy_data->session); + } + + if (proxy_data->ttl_plus_1 > 0) + conn->proxy_ttl_plus_1 = proxy_data->ttl_plus_1; + if (conn->proxy_timeout_secs > 0) + conn->proxy_timeout_secs = proxy_data->timeout_secs; + + connection_update_properties(&conn->conn); + smtp_server_connection_update_event(conn); + + if (conn->callbacks != NULL && + conn->callbacks->conn_proxy_data_updated != NULL) { + struct smtp_proxy_data full_data; + + smtp_server_connection_get_proxy_data(conn, &full_data); + + conn->callbacks-> + conn_proxy_data_updated(conn->context, &full_data); + } +} + +void smtp_server_connection_register_mail_param( + struct smtp_server_connection *conn, const char *param) +{ + param = p_strdup(conn->pool, param); + + if (!array_is_created(&conn->mail_param_extensions)) { + p_array_init(&conn->mail_param_extensions, conn->pool, 8); + array_push_back(&conn->mail_param_extensions, ¶m); + } else { + unsigned int count = array_count(&conn->mail_param_extensions); + + i_assert(count > 0); + array_idx_set(&conn->mail_param_extensions, + count - 1, ¶m); + } + array_append_zero(&conn->mail_param_extensions); +} + +void smtp_server_connection_register_rcpt_param( + struct smtp_server_connection *conn, const char *param) +{ + param = p_strdup(conn->pool, param); + + if (!array_is_created(&conn->rcpt_param_extensions)) { + p_array_init(&conn->rcpt_param_extensions, conn->pool, 8); + array_push_back(&conn->rcpt_param_extensions, ¶m); + } else { + unsigned int count = array_count(&conn->rcpt_param_extensions); + + i_assert(count > 0); + array_idx_set(&conn->rcpt_param_extensions, + count - 1, ¶m); + } + array_append_zero(&conn->rcpt_param_extensions); +} + +void smtp_server_connection_switch_ioloop(struct smtp_server_connection *conn) +{ + if (conn->to_idle != NULL) + conn->to_idle = io_loop_move_timeout(&conn->to_idle); + connection_switch_ioloop(&conn->conn); +} + +struct event_reason * +smtp_server_connection_reason_begin(struct smtp_server_connection *conn, + const char *name) +{ + if (conn->set.reason_code_module == NULL) + return NULL; + const char *reason_code = + event_reason_code(conn->set.reason_code_module, name); + return event_reason_begin(reason_code); +} diff --git a/src/lib-smtp/smtp-server-private.h b/src/lib-smtp/smtp-server-private.h new file mode 100644 index 0000000..7a92336 --- /dev/null +++ b/src/lib-smtp/smtp-server-private.h @@ -0,0 +1,404 @@ +#ifndef SMTP_SERVER_PRIVATE_H +#define SMTP_SERVER_PRIVATE_H + +#include "connection.h" + +#include "smtp-server.h" + +#define SMTP_SERVER_COMMAND_POOL_MAX (8 * 1024) + +#define SMTP_SERVER_DEFAULT_MAX_COMMAND_LINE (4 * 1024) +#define SMTP_SERVER_DEFAULT_MAX_BAD_COMMANDS 10 +#define SMTP_SERVER_DEFAULT_MAX_SIZE_EXCESS_LIMIT (1024*1024) + +#define SMTP_SERVER_DEFAULT_CAPABILITIES \ + (SMTP_CAPABILITY_SIZE | SMTP_CAPABILITY_ENHANCEDSTATUSCODES | \ + SMTP_CAPABILITY_8BITMIME | SMTP_CAPABILITY_CHUNKING) + +struct smtp_server_cmd_hook; +struct smtp_server_reply; +struct smtp_server_command; +struct smtp_server_connection; + +ARRAY_DEFINE_TYPE(smtp_server_reply, struct smtp_server_reply); +ARRAY_DEFINE_TYPE(smtp_server_cmd_hook, struct smtp_server_cmd_hook); + +enum smtp_server_command_state { + /* New command; callback to command start handler executing. */ + SMTP_SERVER_COMMAND_STATE_NEW = 0, + /* This command is being processed; command data is fully read, but no + reply is yet submitted */ + SMTP_SERVER_COMMAND_STATE_PROCESSING, + /* A reply is submitted for this command. If not all command data was + read by the handler, it is first skipped on the input. If this is a + multi-reply command (LMTP->DATA), not all replies may be submitted + yet. */ + SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY, + /* Request is ready for sending reply; a reply is submitted and the + command payload is fully read. If this is a multi-reply command + (LMTP->DATA), not all replies may be submitted yet. In that case the + command state goes back to PROCESSING once the all submitted replies + are sent. */ + SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY, + /* The reply for the command is sent */ + SMTP_SERVER_COMMAND_STATE_FINISHED, + /* Request is aborted; still lingering due to references */ + SMTP_SERVER_COMMAND_STATE_ABORTED +}; + +struct smtp_server_command_hook { + enum smtp_server_command_hook_type type; + struct smtp_server_command_hook *prev, *next; + + smtp_server_cmd_func_t *func; + void *context; +}; + +struct smtp_server_recipient_hook { + enum smtp_server_recipient_hook_type type; + struct smtp_server_recipient_hook *prev, *next; + + smtp_server_rcpt_func_t *func; + void *context; +}; + +struct smtp_server_reply_content { + unsigned int status; + const char *enhanced_code; + const char *status_prefix; + + string_t *text; + size_t last_line; +}; + +struct smtp_server_reply { + struct smtp_server_command *command; + unsigned int index; + struct event *event; + + /* Replies may share content */ + struct smtp_server_reply_content *content; + + bool submitted:1; + bool sent:1; + bool forwarded:1; +}; + +struct smtp_server_command_reg { + const char *name; + enum smtp_server_command_flags flags; + smtp_server_cmd_start_func_t *func; +}; + +struct smtp_server_command { + struct smtp_server_cmd_ctx context; + const struct smtp_server_command_reg *reg; + int refcount; + + enum smtp_server_command_state state; + + struct smtp_server_command *prev, *next; + + struct smtp_server_command_hook *hooks_head, *hooks_tail; + void *data; + + ARRAY_TYPE(smtp_server_reply) replies; + unsigned int replies_expected; + unsigned int replies_submitted; + + bool input_locked:1; + bool input_captured:1; + bool pipeline_blocked:1; + bool reply_early:1; + bool destroying:1; +}; + +struct smtp_server_recipient_private { + struct smtp_server_recipient rcpt; + int refcount; + + struct smtp_server_recipient_hook *hooks_head, *hooks_tail; + + bool destroying:1; +}; + +struct smtp_server_state_data { + enum smtp_server_state state; + char *args; + time_t timestamp; + + unsigned int pending_mail_cmds; + unsigned int pending_rcpt_cmds, denied_rcpt_cmds; + unsigned int pending_data_cmds; + + struct smtp_server_transaction *trans; + struct istream *data_input, *data_chain_input; + struct istream_chain *data_chain; + unsigned int data_chunks; + uoff_t data_size; + + bool data_failed:1; +}; + +struct smtp_server_connection { + struct connection conn; + struct smtp_server *server; + pool_t pool; + int refcount; + struct event *event, *next_trans_event; + + struct smtp_server_settings set; + + ARRAY(struct smtp_capability_extra) extra_capabilities; + ARRAY_TYPE(const_string) mail_param_extensions; /* NULL-terminated */ + ARRAY_TYPE(const_string) rcpt_param_extensions; /* NULL-terminated */ + + const struct smtp_server_callbacks *callbacks; + void *context; + + enum smtp_proxy_protocol proxy_proto; + unsigned int proxy_ttl_plus_1; + unsigned int proxy_timeout_secs; + char *proxy_helo; + + struct smtp_server_helo_data helo, *pending_helo; + char *helo_domain, *username; + + char *session_id; + unsigned int transaction_seq; + + struct timeout *to_idle; + struct istream *raw_input; + struct ostream *raw_output; + struct ssl_iostream_context *ssl_ctx; + struct ssl_iostream *ssl_iostream; + struct smtp_command_parser *smtp_parser; + + struct smtp_server_command *command_queue_head, *command_queue_tail; + unsigned int command_queue_count; + unsigned int bad_counter; + + struct smtp_server_state_data state; + + struct smtp_server_stats stats; + + bool started:1; + bool halted:1; + bool ssl_start:1; + bool ssl_secured:1; + bool authenticated:1; + bool created_from_streams:1; + bool corked:1; + bool disconnected:1; + bool closing:1; + bool closed:1; + bool input_broken:1; + bool input_locked:1; + bool handling_input:1; + bool rawlog_checked:1; + bool rawlog_enabled:1; +}; + +struct smtp_server { + pool_t pool; + + struct smtp_server_settings set; + + struct event *event; + struct ssl_iostream_context *ssl_ctx; + + ARRAY(struct smtp_server_command_reg) commands_reg; + + struct connection_list *conn_list; + + bool commands_unsorted:1; +}; + +bool smtp_server_connection_pending_command_data( + struct smtp_server_connection *conn); + +/* + * Reply + */ + +void smtp_server_reply_free(struct smtp_server_command *cmd); + +int smtp_server_reply_send(struct smtp_server_reply *resp); + +const char * +smtp_server_reply_get_one_line(const struct smtp_server_reply *reply); +const char * +smtp_server_reply_get_message(const struct smtp_server_reply *reply); + +void smtp_server_reply_add_to_event(const struct smtp_server_reply *reply, + struct event_passthrough *e); + +/* + * Command + */ + +void smtp_server_commands_init(struct smtp_server *server); + +void smtp_server_command_debug(struct smtp_server_cmd_ctx *cmd, + const char *format, ...) ATTR_FORMAT(2, 3); + +struct smtp_server_command * +smtp_server_command_new_invalid(struct smtp_server_connection *conn); +struct smtp_server_command * +smtp_server_command_new(struct smtp_server_connection *conn, const char *name); + +void smtp_server_command_execute(struct smtp_server_command *cmd, + const char *params); + +void smtp_server_command_ref(struct smtp_server_command *cmd); +bool smtp_server_command_unref(struct smtp_server_command **_cmd); +void smtp_server_command_abort(struct smtp_server_command **_cmd); + +bool smtp_server_command_call_hooks(struct smtp_server_command **_cmd, + enum smtp_server_command_hook_type type, + bool remove); +void smtp_server_command_remove_hooks(struct smtp_server_command *cmd, + enum smtp_server_command_hook_type type); + +void smtp_server_command_submit_reply(struct smtp_server_command *cmd); + +int smtp_server_connection_flush(struct smtp_server_connection *conn); + +void smtp_server_command_ready_to_reply(struct smtp_server_command *cmd); +bool smtp_server_command_send_replies(struct smtp_server_command *cmd); +void smtp_server_command_finished(struct smtp_server_command *cmd); + +bool smtp_server_command_next_to_reply(struct smtp_server_command **_cmd); +bool smtp_server_command_completed(struct smtp_server_command **_cmd); + +static inline bool +smtp_server_command_is_complete(struct smtp_server_command *cmd) +{ + struct smtp_server_connection *conn = cmd->context.conn; + + return (conn->input_broken || (cmd->next != NULL) || cmd->reply_early || + !smtp_server_connection_pending_command_data(conn)); +} + +/* + * Connection + */ + +typedef void smtp_server_input_callback_t(void *context); + +void smtp_server_connection_debug(struct smtp_server_connection *conn, + const char *format, ...) ATTR_FORMAT(2, 3); + +struct connection_list *smtp_server_connection_list_init(void); + +struct event_reason * +smtp_server_connection_reason_begin(struct smtp_server_connection *conn, + const char *name); + +void smtp_server_connection_switch_ioloop(struct smtp_server_connection *conn); + +void smtp_server_connection_handle_output_error( + struct smtp_server_connection *conn); +void smtp_server_connection_trigger_output(struct smtp_server_connection *conn); +bool smtp_server_connection_pending_payload(struct smtp_server_connection *conn); + +void smtp_server_connection_cork(struct smtp_server_connection *conn); +void smtp_server_connection_uncork(struct smtp_server_connection *conn); + +void smtp_server_connection_input_halt(struct smtp_server_connection *conn); +void smtp_server_connection_input_resume(struct smtp_server_connection *conn); +void smtp_server_connection_input_capture( + struct smtp_server_connection *conn, + smtp_server_input_callback_t *callback, void *context); +#define smtp_server_connection_input_capture(conn, callback, context) \ + smtp_server_connection_input_capture(conn - \ + CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ + (smtp_server_input_callback_t *)callback, context) + +void smtp_server_connection_timeout_stop(struct smtp_server_connection *conn); +void smtp_server_connection_timeout_start(struct smtp_server_connection *conn); +void smtp_server_connection_timeout_reset(struct smtp_server_connection *conn); + +void smtp_server_connection_send_line(struct smtp_server_connection *conn, + const char *fmt, ...) ATTR_FORMAT(2, 3); +void smtp_server_connection_reply_lines(struct smtp_server_connection *conn, + unsigned int status, + const char *enh_code, + const char *const *text_lines); +void smtp_server_connection_reply_immediate( + struct smtp_server_connection *conn, unsigned int status, + const char *fmt, ...) ATTR_FORMAT(3, 4); + +void smtp_server_connection_reset_state(struct smtp_server_connection *conn); +void smtp_server_connection_set_state(struct smtp_server_connection *conn, + enum smtp_server_state state, + const char *args) ATTR_NULL(3); + +int smtp_server_connection_ssl_init(struct smtp_server_connection *conn); + +void smtp_server_connection_clear(struct smtp_server_connection *conn); + +struct smtp_server_transaction * +smtp_server_connection_get_transaction(struct smtp_server_connection *conn); + +/* + * Recipient + */ + +struct smtp_server_recipient * +smtp_server_recipient_create(struct smtp_server_cmd_ctx *cmd, + const struct smtp_address *rcpt_to, + const struct smtp_params_rcpt *params); +void smtp_server_recipient_ref(struct smtp_server_recipient *rcpt); +bool smtp_server_recipient_unref(struct smtp_server_recipient **_rcpt); +void smtp_server_recipient_destroy(struct smtp_server_recipient **_rcpt); + +bool smtp_server_recipient_approved(struct smtp_server_recipient **_rcpt); +void smtp_server_recipient_denied(struct smtp_server_recipient *rcpt, + const struct smtp_server_reply *reply); + +void smtp_server_recipient_data_command(struct smtp_server_recipient *rcpt, + struct smtp_server_cmd_ctx *cmd); +void smtp_server_recipient_data_replied(struct smtp_server_recipient *rcpt); + +void smtp_server_recipient_reset(struct smtp_server_recipient *rcpt); +void smtp_server_recipient_finished(struct smtp_server_recipient *rcpt, + const struct smtp_server_reply *reply); + +bool smtp_server_recipient_call_hooks( + struct smtp_server_recipient **_rcpt, + enum smtp_server_recipient_hook_type type); + +/* + * Transaction + */ + +struct smtp_server_transaction * +smtp_server_transaction_create(struct smtp_server_connection *conn, + const struct smtp_server_cmd_mail *mail_data); +void smtp_server_transaction_free(struct smtp_server_transaction **_trans); + +void smtp_server_transaction_add_rcpt(struct smtp_server_transaction *trans, + struct smtp_server_recipient *rcpt); +bool smtp_server_transaction_has_rcpt(struct smtp_server_transaction *trans); +unsigned int +smtp_server_transaction_rcpt_count(struct smtp_server_transaction *trans); + +void smtp_server_transaction_data_command(struct smtp_server_transaction *trans, + struct smtp_server_cmd_ctx *cmd); + +void smtp_server_transaction_received(struct smtp_server_transaction *trans, + uoff_t data_size); + +void smtp_server_transaction_reset(struct smtp_server_transaction *trans); +void smtp_server_transaction_finished(struct smtp_server_transaction *trans, + struct smtp_server_cmd_ctx *cmd); + +/* + * Server + */ + +void smtp_server_event_init(struct smtp_server *server, struct event *event); +int smtp_server_init_ssl_ctx(struct smtp_server *server, const char **error_r); + +#endif diff --git a/src/lib-smtp/smtp-server-recipient.c b/src/lib-smtp/smtp-server-recipient.c new file mode 100644 index 0000000..fbae663 --- /dev/null +++ b/src/lib-smtp/smtp-server-recipient.c @@ -0,0 +1,361 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "str-sanitize.h" +#include "smtp-address.h" +#include "smtp-reply.h" + +#include "smtp-server-private.h" + +static void +smtp_server_recipient_update_event(struct smtp_server_recipient_private *prcpt) +{ + struct event *event = prcpt->rcpt.event; + const char *path = smtp_address_encode(prcpt->rcpt.path); + + event_add_str(event, "rcpt_to", path); + smtp_params_rcpt_add_to_event(&prcpt->rcpt.params, event); + event_set_append_log_prefix( + event, t_strdup_printf("rcpt %s: ", str_sanitize(path, 128))); +} + +static void +smtp_server_recipient_create_event(struct smtp_server_recipient_private *prcpt) +{ + struct smtp_server_recipient *rcpt = &prcpt->rcpt; + struct smtp_server_connection *conn = rcpt->conn; + + if (rcpt->event != NULL) + return; + + if (conn->state.trans == NULL) { + /* Create event for the transaction early. */ + if (conn->next_trans_event == NULL) { + conn->next_trans_event = event_create(conn->event); + event_set_append_log_prefix(conn->next_trans_event, + "trans: "); + } + rcpt->event = event_create(conn->next_trans_event); + } else { + /* Use existing transaction event. */ + rcpt->event = event_create(conn->state.trans->event); + } + /* Drop transaction log prefix so that the connection event prefix + remains. */ + event_drop_parent_log_prefixes(rcpt->event, 1); + + smtp_server_recipient_update_event(prcpt); +} + +struct smtp_server_recipient * +smtp_server_recipient_create(struct smtp_server_cmd_ctx *cmd, + const struct smtp_address *rcpt_to, + const struct smtp_params_rcpt *params) +{ + struct smtp_server_recipient_private *prcpt; + pool_t pool; + + pool = pool_alloconly_create("smtp server recipient", 512); + prcpt = p_new(pool, struct smtp_server_recipient_private, 1); + prcpt->refcount = 1; + prcpt->rcpt.pool = pool; + prcpt->rcpt.conn = cmd->conn; + prcpt->rcpt.cmd = cmd; + prcpt->rcpt.path = smtp_address_clone(pool, rcpt_to); + smtp_params_rcpt_copy(pool, &prcpt->rcpt.params, params); + + smtp_server_recipient_create_event(prcpt); + + return &prcpt->rcpt; +} + +void smtp_server_recipient_ref(struct smtp_server_recipient *rcpt) +{ + struct smtp_server_recipient_private *prcpt = + (struct smtp_server_recipient_private *)rcpt; + + if (prcpt->destroying) + return; + i_assert(prcpt->refcount > 0); + prcpt->refcount++; +} + +bool smtp_server_recipient_unref(struct smtp_server_recipient **_rcpt) +{ + struct smtp_server_recipient *rcpt = *_rcpt; + struct smtp_server_recipient_private *prcpt = + (struct smtp_server_recipient_private *)rcpt; + + *_rcpt = NULL; + + if (rcpt == NULL) + return FALSE; + if (prcpt->destroying) + return FALSE; + + i_assert(prcpt->refcount > 0); + if (--prcpt->refcount > 0) + return TRUE; + prcpt->destroying = TRUE; + + if (!smtp_server_recipient_call_hooks( + &rcpt, SMTP_SERVER_RECIPIENT_HOOK_DESTROY)) + i_unreached(); + + if (!rcpt->finished) { + smtp_server_recipient_create_event(prcpt); + + struct event_passthrough *e = + event_create_passthrough(rcpt->event)-> + set_name("smtp_server_transaction_rcpt_finished"); + e->add_int("status_code", 9000); + e->add_str("enhanced_code", "9.0.0"); + e->add_str("error", "Aborted"); + + e_debug(e->event(), "Aborted"); + } + + event_unref(&rcpt->event); + pool_unref(&rcpt->pool); + return FALSE; +} + +void smtp_server_recipient_destroy(struct smtp_server_recipient **_rcpt) +{ + smtp_server_recipient_unref(_rcpt); +} + +const struct smtp_address * +smtp_server_recipient_get_original(struct smtp_server_recipient *rcpt) +{ + if (rcpt->params.orcpt.addr == NULL) + return rcpt->path; + return rcpt->params.orcpt.addr; +} + +bool smtp_server_recipient_approved(struct smtp_server_recipient **_rcpt) +{ + struct smtp_server_recipient *rcpt = *_rcpt; + struct smtp_server_transaction *trans = rcpt->conn->state.trans; + + i_assert(trans != NULL); + i_assert(rcpt->event != NULL); + + e_debug(rcpt->event, "Approved"); + + rcpt->cmd = NULL; + smtp_server_transaction_add_rcpt(trans, rcpt); + + return smtp_server_recipient_call_hooks( + _rcpt, SMTP_SERVER_RECIPIENT_HOOK_APPROVED); +} + +void smtp_server_recipient_denied(struct smtp_server_recipient *rcpt, + const struct smtp_server_reply *reply) +{ + i_assert(!rcpt->finished); + i_assert(rcpt->event != NULL); + + rcpt->finished = TRUE; + + struct event_passthrough *e = + event_create_passthrough(rcpt->event)-> + set_name("smtp_server_transaction_rcpt_finished"); + smtp_server_reply_add_to_event(reply, e); + + e_debug(e->event(), "Denied"); +} + +void smtp_server_recipient_data_command(struct smtp_server_recipient *rcpt, + struct smtp_server_cmd_ctx *cmd) +{ + rcpt->cmd = cmd; +} + +void smtp_server_recipient_data_replied(struct smtp_server_recipient *rcpt) +{ + if (rcpt->replied) + return; + if (smtp_server_recipient_get_reply(rcpt) == NULL) + return; + rcpt->replied = TRUE; + if (!smtp_server_recipient_call_hooks( + &rcpt, SMTP_SERVER_RECIPIENT_HOOK_DATA_REPLIED)) { + /* Nothing to do */ + } +} + +struct smtp_server_reply * +smtp_server_recipient_get_reply(struct smtp_server_recipient *rcpt) +{ + if (!HAS_ALL_BITS(rcpt->trans->flags, + SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)) + return smtp_server_command_get_reply(rcpt->cmd->cmd, 0); + return smtp_server_command_get_reply(rcpt->cmd->cmd, rcpt->index); +} + +bool smtp_server_recipient_is_replied(struct smtp_server_recipient *rcpt) +{ + i_assert(rcpt->cmd != NULL); + + return smtp_server_command_is_replied(rcpt->cmd->cmd); +} + +void smtp_server_recipient_replyv(struct smtp_server_recipient *rcpt, + unsigned int status, const char *enh_code, + const char *fmt, va_list args) +{ + i_assert(rcpt->cmd != NULL); + + if (smtp_server_command_is_rcpt(rcpt->cmd) && (status / 100) == 2) { + smtp_server_reply_indexv(rcpt->cmd, rcpt->index, + status, enh_code, fmt, args); + return; + } + + smtp_server_reply_index(rcpt->cmd, rcpt->index, status, enh_code, + "<%s> %s", smtp_address_encode(rcpt->path), + t_strdup_vprintf(fmt, args)); +} + +void smtp_server_recipient_reply(struct smtp_server_recipient *rcpt, + unsigned int status, const char *enh_code, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + smtp_server_recipient_replyv(rcpt, status, enh_code, fmt, args); + va_end(args); +} + +void smtp_server_recipient_reply_forward(struct smtp_server_recipient *rcpt, + const struct smtp_reply *from) +{ + bool add_path = (!smtp_server_command_is_rcpt(rcpt->cmd) || + !smtp_reply_is_success(from)); + struct smtp_server_reply *reply; + + reply = smtp_server_reply_create_forward(rcpt->cmd->cmd, rcpt->index, + from); + smtp_server_reply_replace_path(reply, rcpt->path, add_path); + smtp_server_reply_submit(reply); +} + +void smtp_server_recipient_reset(struct smtp_server_recipient *rcpt) +{ + i_assert(!rcpt->finished); + rcpt->finished = TRUE; + + struct event_passthrough *e = + event_create_passthrough(rcpt->event)-> + set_name("smtp_server_transaction_rcpt_finished"); + e->add_int("status_code", 9000); + e->add_str("enhanced_code", "9.0.0"); + e->add_str("error", "Reset"); + + e_debug(e->event(), "Reset"); +} + +void smtp_server_recipient_finished(struct smtp_server_recipient *rcpt, + const struct smtp_server_reply *reply) +{ + i_assert(!rcpt->finished); + rcpt->finished = TRUE; + + struct event_passthrough *e = + event_create_passthrough(rcpt->event)-> + set_name("smtp_server_transaction_rcpt_finished"); + smtp_server_reply_add_to_event(reply, e); + + e_debug(e->event(), "Finished"); +} + +#undef smtp_server_recipient_add_hook +void smtp_server_recipient_add_hook(struct smtp_server_recipient *rcpt, + enum smtp_server_recipient_hook_type type, + smtp_server_rcpt_func_t func, void *context) +{ + struct smtp_server_recipient_private *prcpt = + (struct smtp_server_recipient_private *)rcpt; + struct smtp_server_recipient_hook *hook; + + i_assert(func != NULL); + + hook = prcpt->hooks_head; + while (hook != NULL) { + /* No double registrations */ + i_assert(hook->type != type || hook->func != func); + + hook = hook->next; + } + + hook = p_new(rcpt->pool, struct smtp_server_recipient_hook, 1); + hook->type = type; + hook->func = func; + hook->context = context; + + DLLIST2_APPEND(&prcpt->hooks_head, &prcpt->hooks_tail, hook); +} + +#undef smtp_server_recipient_remove_hook +void smtp_server_recipient_remove_hook( + struct smtp_server_recipient *rcpt, + enum smtp_server_recipient_hook_type type, + smtp_server_rcpt_func_t *func) +{ + struct smtp_server_recipient_private *prcpt = + (struct smtp_server_recipient_private *)rcpt; + struct smtp_server_recipient_hook *hook; + bool found = FALSE; + + hook = prcpt->hooks_head; + while (hook != NULL) { + struct smtp_server_recipient_hook *hook_next = hook->next; + + if (hook->type == type && hook->func == func) { + DLLIST2_REMOVE(&prcpt->hooks_head, &prcpt->hooks_tail, + hook); + found = TRUE; + break; + } + + hook = hook_next; + } + i_assert(found); +} + +bool smtp_server_recipient_call_hooks( + struct smtp_server_recipient **_rcpt, + enum smtp_server_recipient_hook_type type) +{ + struct smtp_server_recipient *rcpt = *_rcpt; + struct smtp_server_recipient_private *prcpt = + (struct smtp_server_recipient_private *)rcpt; + struct smtp_server_recipient_hook *hook; + + if (type != SMTP_SERVER_RECIPIENT_HOOK_DESTROY) + smtp_server_recipient_ref(rcpt); + + hook = prcpt->hooks_head; + while (hook != NULL) { + struct smtp_server_recipient_hook *hook_next = hook->next; + + if (hook->type == type) { + DLLIST2_REMOVE(&prcpt->hooks_head, &prcpt->hooks_tail, + hook); + hook->func(rcpt, hook->context); + } + + hook = hook_next; + } + + if (type != SMTP_SERVER_RECIPIENT_HOOK_DESTROY) { + if (!smtp_server_recipient_unref(&rcpt)) { + *_rcpt = NULL; + return FALSE; + } + } + return TRUE; +} diff --git a/src/lib-smtp/smtp-server-reply.c b/src/lib-smtp/smtp-server-reply.c new file mode 100644 index 0000000..60342db --- /dev/null +++ b/src/lib-smtp/smtp-server-reply.c @@ -0,0 +1,876 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "istream.h" +#include "ostream.h" +#include "smtp-address.h" +#include "smtp-reply.h" + +#include "smtp-server-private.h" + +/* + * Reply + */ + +static void smtp_server_reply_destroy(struct smtp_server_reply *reply) +{ + if (reply->command == NULL) + return; + + if (reply->event != NULL) { + e_debug(reply->event, "Destroy"); + event_unref(&reply->event); + } + + if (reply->content == NULL) + return; + str_free(&reply->content->text); +} + +static void smtp_server_reply_clear(struct smtp_server_reply *reply) +{ + smtp_server_reply_destroy(reply); + if (reply->submitted) { + i_assert(reply->command->replies_submitted > 0); + reply->command->replies_submitted--; + } + reply->submitted = FALSE; + reply->forwarded = FALSE; +} + +static void smtp_server_reply_update_event(struct smtp_server_reply *reply) +{ + struct smtp_server_command *command = reply->command; + + event_add_int(reply->event, "index", reply->index); + event_add_int(reply->event, "status", reply->content->status); + + if (command->replies_expected > 1) { + event_set_append_log_prefix(reply->event, + t_strdup_printf("%u reply [%u/%u]: ", + reply->content->status, + reply->index+1, + command->replies_expected)); + } else { + event_set_append_log_prefix(reply->event, + t_strdup_printf("%u reply: ", + reply->content->status)); + } +} + +static struct smtp_server_reply * +smtp_server_reply_alloc(struct smtp_server_command *cmd, unsigned int index) +{ + struct smtp_server_reply *reply; + pool_t pool = cmd->context.pool; + + if (array_is_created(&cmd->replies)) { + reply = array_idx_modifiable(&cmd->replies, index); + /* get rid of any existing reply */ + i_assert(!reply->sent); + smtp_server_reply_clear(reply); + } else { + p_array_init(&cmd->replies, pool, cmd->replies_expected); + array_idx_clear(&cmd->replies, cmd->replies_expected - 1); + reply = array_idx_modifiable(&cmd->replies, index); + } + reply->event = event_create(cmd->context.event); + + return reply; +} + +static void +smtp_server_reply_update_prefix(struct smtp_server_reply *reply, + unsigned int status, const char *enh_code) +{ + pool_t pool = reply->command->context.pool; + string_t *textbuf, *new_text; + const char *new_prefix, *text, *p; + size_t text_len, prefix_len, line_len; + + if (enh_code == NULL || *enh_code == '\0') { + new_prefix = p_strdup_printf(pool, "%03u-", status); + } else { + new_prefix = p_strdup_printf(pool, "%03u-%s ", + status, enh_code); + } + + i_assert(reply->content != NULL); + textbuf = reply->content->text; + + if (textbuf == NULL || str_len(textbuf) == 0) { + reply->content->status_prefix = new_prefix; + return; + } + new_text = str_new(default_pool, 256); + + prefix_len = strlen(reply->content->status_prefix); + text = str_c(textbuf); + text_len = str_len(textbuf); + + i_assert(text_len > prefix_len); + text_len -= prefix_len; + text += prefix_len; + + for (;;) { + reply->content->last_line = str_len(new_text); + + p = strchr(text, '\n'); + i_assert(p != NULL && p > text && *(p-1) == '\r'); + p++; + + str_append(new_text, new_prefix); + str_append_data(new_text, text, p - text); + + line_len = (size_t)(p - text); + i_assert(text_len >= line_len); + text_len -= line_len; + text = p; + + if (text_len <= prefix_len) + break; + + text_len -= prefix_len; + text += prefix_len; + } + + str_free(&textbuf); + reply->content->text = new_text; + reply->content->status_prefix = new_prefix; +} + +void smtp_server_reply_set_status(struct smtp_server_reply *reply, + unsigned int status, const char *enh_code) +{ + pool_t pool = reply->command->context.pool; + + /* RFC 5321, Section 4.2: + + In the absence of extensions negotiated with the client, SMTP servers + MUST NOT send reply codes whose first digits are other than 2, 3, 4, + or 5. Clients that receive such out-of-range codes SHOULD normally + treat them as fatal errors and terminate the mail transaction. + */ + i_assert(status >= 200 && status < 560); + + /* RFC 2034, Section 4: + + All status codes returned by the server must agree with the primary + response code, that is, a 2xx response must incorporate a 2.X.X code, + a 4xx response must incorporate a 4.X.X code, and a 5xx response must + incorporate a 5.X.X code. + */ + i_assert(enh_code == NULL || *enh_code == '\0' || + ((unsigned int)(enh_code[0] - '0') == (status / 100) + && enh_code[1] == '.')); + + if (reply->content->status == status && + null_strcmp(reply->content->enhanced_code, enh_code) == 0) + return; + + smtp_server_reply_update_prefix(reply, status, enh_code); + reply->content->status = status; + reply->content->enhanced_code = p_strdup(pool, enh_code); +} + +unsigned int smtp_server_reply_get_status(struct smtp_server_reply *reply, + const char **enh_code_r) +{ + if (enh_code_r != NULL) + *enh_code_r = reply->content->enhanced_code; + return reply->content->status; +} + +struct smtp_server_reply * +smtp_server_reply_create_index(struct smtp_server_command *cmd, + unsigned int index, unsigned int status, + const char *enh_code) +{ + struct smtp_server_reply *reply; + pool_t pool = cmd->context.pool; + + i_assert(cmd->replies_expected > 0); + i_assert(index < cmd->replies_expected); + + reply = smtp_server_reply_alloc(cmd, index); + reply->index = index; + reply->command = cmd; + + if (reply->content == NULL) + reply->content = p_new(pool, struct smtp_server_reply_content, 1); + smtp_server_reply_set_status(reply, status, enh_code); + reply->content->text = str_new(default_pool, 256); + + smtp_server_reply_update_event(reply); + + return reply; +} + +struct smtp_server_reply * +smtp_server_reply_create(struct smtp_server_command *cmd, + unsigned int status, const char *enh_code) +{ + return smtp_server_reply_create_index(cmd, 0, status, enh_code); +} + +struct smtp_server_reply * +smtp_server_reply_create_forward(struct smtp_server_command *cmd, + unsigned int index, const struct smtp_reply *from) +{ + struct smtp_server_reply *reply; + string_t *textbuf; + char *text; + size_t last_line, i; + + reply = smtp_server_reply_create_index(cmd, index, + from->status, smtp_reply_get_enh_code(from)); + smtp_reply_write(reply->content->text, from); + + i_assert(reply->content != NULL); + textbuf = reply->content->text; + text = str_c_modifiable(textbuf); + + /* Find the last line */ + reply->content->last_line = last_line = 0; + for (i = 0; i < str_len(textbuf); i++) { + if (text[i] == '\n') { + reply->content->last_line = last_line; + last_line = i + 1; + } + } + + /* Make this reply suitable for further amendment with + smtp_server_reply_add_text() */ + if ((reply->content->last_line + 3) < str_len(textbuf)) { + i_assert(text[reply->content->last_line + 3] == ' '); + text[reply->content->last_line + 3] = '-'; + } else { + str_append_c(textbuf, '-'); + } + + reply->forwarded = TRUE; + + return reply; +} + +void smtp_server_reply_free(struct smtp_server_command *cmd) +{ + unsigned int i; + + if (!array_is_created(&cmd->replies)) + return; + + for (i = 0; i < cmd->replies_expected; i++) { + struct smtp_server_reply *reply = + array_idx_modifiable(&cmd->replies, i); + smtp_server_reply_destroy(reply); + } +} + +void smtp_server_reply_add_text(struct smtp_server_reply *reply, + const char *text) +{ + string_t *textbuf = reply->content->text; + + i_assert(!reply->submitted); + + if (*text == '\0') + return; + + do { + const char *p; + + reply->content->last_line = str_len(textbuf); + + p = strchr(text, '\n'); + str_append(textbuf, reply->content->status_prefix); + if (p == NULL) { + str_append(textbuf, text); + text = NULL; + } else { + if (p > text && *(p-1) == '\r') + str_append_data(textbuf, text, p - text - 1); + else + str_append_data(textbuf, text, p - text); + text = p + 1; + } + str_append(textbuf, "\r\n"); + } while (text != NULL && *text != '\0'); +} + +static size_t +smtp_server_reply_get_path_len(struct smtp_server_reply *reply) +{ + size_t prefix_len = strlen(reply->content->status_prefix); + size_t text_len = str_len(reply->content->text), line_len, path_len; + const char *text = str_c(reply->content->text); + const char *text_end = text + text_len, *line_end; + + i_assert(prefix_len <= text_len); + + line_end = strchr(text, '\r'); + if (line_end == NULL) { + line_end = text_end; + line_len = text_len; + } else { + i_assert(line_end + 1 < text_end); + i_assert(*(line_end + 1) == '\n'); + line_len = line_end - text; + } + + if (prefix_len == line_len || text[prefix_len] != '<') { + path_len = 0; + } else { + const char *path_begin = &text[prefix_len], *path_end; + + path_end = strchr(path_begin, '>'); + if (path_end == NULL || path_end > line_end) + path_len = 0; + else { + i_assert(path_end < line_end); + path_end++; + path_len = path_end - path_begin; + if (path_end < line_end && *path_end != ' ') + path_len = 0; + } + } + + i_assert(prefix_len + path_len <= text_len); + return path_len; +} + +void smtp_server_reply_prepend_text(struct smtp_server_reply *reply, + const char *text_prefix) +{ + const char *text = str_c(reply->content->text); + size_t tlen = str_len(reply->content->text), offset; + + i_assert(!reply->sent); + i_assert(reply->content != NULL); + i_assert(reply->content->text != NULL); + + offset = strlen(reply->content->status_prefix) + + smtp_server_reply_get_path_len(reply); + i_assert(offset < tlen); + if (text[offset] == ' ') + offset++; + + str_insert(reply->content->text, offset, text_prefix); + + if (reply->content->last_line > 0) + reply->content->last_line += strlen(text_prefix); +} + +void smtp_server_reply_replace_path(struct smtp_server_reply *reply, + struct smtp_address *path, bool add) +{ + size_t prefix_len, path_len; + const char *path_text; + + i_assert(!reply->sent); + i_assert(reply->content != NULL); + i_assert(reply->content->text != NULL); + + prefix_len = strlen(reply->content->status_prefix); + path_len = smtp_server_reply_get_path_len(reply); + + if (path_len > 0) { + path_text = smtp_address_encode_path(path); + str_replace(reply->content->text, prefix_len, path_len, + path_text); + } else if (add) { + path_text = t_strdup_printf( + "<%s> ", smtp_address_encode(path)); + str_insert(reply->content->text, prefix_len, path_text); + } +} + +void smtp_server_reply_submit(struct smtp_server_reply *reply) +{ + i_assert(!reply->submitted); + i_assert(reply->content != NULL); + i_assert(str_len(reply->content->text) >= 5); + e_debug(reply->event, "Submitted"); + + reply->command->replies_submitted++; + reply->submitted = TRUE; + smtp_server_command_submit_reply(reply->command); +} + +void smtp_server_reply_submit_duplicate(struct smtp_server_cmd_ctx *_cmd, + unsigned int index, + unsigned int from_index) +{ + struct smtp_server_command *cmd = _cmd->cmd; + struct smtp_server_reply *reply, *from_reply; + + i_assert(cmd->replies_expected > 0); + i_assert(index < cmd->replies_expected); + i_assert(from_index < cmd->replies_expected); + i_assert(array_is_created(&cmd->replies)); + + from_reply = array_idx_modifiable(&cmd->replies, from_index); + i_assert(from_reply->content != NULL); + i_assert(from_reply->submitted); + + reply = smtp_server_reply_alloc(cmd, index); + reply->index = index; + reply->command = cmd; + reply->content = from_reply->content; + smtp_server_reply_update_event(reply); + + smtp_server_reply_submit(reply); +} + +void smtp_server_reply_indexv(struct smtp_server_cmd_ctx *_cmd, + unsigned int index, unsigned int status, const char *enh_code, + const char *fmt, va_list args) +{ + struct smtp_server_command *cmd = _cmd->cmd; + struct smtp_server_reply *reply; + + reply = smtp_server_reply_create_index(cmd, index, status, enh_code); + smtp_server_reply_add_text(reply, t_strdup_vprintf(fmt, args)); + smtp_server_reply_submit(reply); +} + +void smtp_server_reply(struct smtp_server_cmd_ctx *_cmd, + unsigned int status, const char *enh_code, const char *fmt, ...) +{ + struct smtp_server_command *cmd = _cmd->cmd; + va_list args; + + i_assert(cmd->replies_expected <= 1); + + va_start(args, fmt); + smtp_server_reply_indexv(_cmd, 0, status, enh_code, fmt, args); + va_end(args); +} + +void smtp_server_reply_index(struct smtp_server_cmd_ctx *_cmd, + unsigned int index, unsigned int status, const char *enh_code, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + smtp_server_reply_indexv(_cmd, index, status, enh_code, fmt, args); + va_end(args); +} + +void smtp_server_reply_index_forward(struct smtp_server_cmd_ctx *cmd, + unsigned int index, const struct smtp_reply *from) +{ + smtp_server_reply_submit( + smtp_server_reply_create_forward(cmd->cmd, index, from)); +} + +void smtp_server_reply_forward(struct smtp_server_cmd_ctx *_cmd, + const struct smtp_reply *from) +{ + struct smtp_server_command *cmd = _cmd->cmd; + + i_assert(cmd->replies_expected <= 1); + + smtp_server_reply_submit( + smtp_server_reply_create_forward(cmd, 0, from)); +} + +static void ATTR_FORMAT(4, 0) +smtp_server_reply_allv(struct smtp_server_cmd_ctx *_cmd, + unsigned int status, const char *enh_code, + const char *fmt, va_list args) +{ + struct smtp_server_command *cmd = _cmd->cmd; + struct smtp_server_reply *reply; + const char *text; + unsigned int first, i = 0; + + /* find the first unsent reply */ + if (array_is_created(&cmd->replies)) { + for (; i < cmd->replies_expected; i++) { + struct smtp_server_reply *reply = + array_idx_modifiable(&cmd->replies, i); + if (!reply->sent) + break; + } + i_assert (i < cmd->replies_expected); + } + first = i++; + + /* compose the reply text */ + text = t_strdup_vprintf(fmt, args); + + /* submit the first remaining reply */ + reply = smtp_server_reply_create_index(cmd, first, status, enh_code); + smtp_server_reply_add_text(reply, text); + smtp_server_reply_submit(reply); + + /* duplicate the rest from it */ + for (; i < cmd->replies_expected; i++) + smtp_server_reply_submit_duplicate(_cmd, i, first); +} + +void smtp_server_reply_all(struct smtp_server_cmd_ctx *_cmd, + unsigned int status, const char *enh_code, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + smtp_server_reply_allv(_cmd, status, enh_code, fmt, args); + va_end(args); +} + +void smtp_server_reply_early(struct smtp_server_cmd_ctx *_cmd, + unsigned int status, const char *enh_code, + const char *fmt, ...) +{ + va_list args; + + _cmd->cmd->reply_early = TRUE; + + va_start(args, fmt); + smtp_server_reply_allv(_cmd, status, enh_code, fmt, args); + va_end(args); +} + +void smtp_server_reply_quit(struct smtp_server_cmd_ctx *_cmd) +{ + struct smtp_server_command *cmd = _cmd->cmd; + struct smtp_server_reply *reply; + + reply = smtp_server_reply_create(cmd, 221, "2.0.0"); + smtp_server_reply_add_text(reply, "Bye"); + smtp_server_reply_submit(reply); +} + +static void +smtp_server_reply_write_one_line(const struct smtp_server_reply *reply, + string_t *str, bool skip_status) +{ + string_t *textbuf; + const char *text, *p; + size_t text_len, prefix_len, line_len; + + i_assert(reply->content != NULL); + textbuf = reply->content->text; + i_assert(str_len(textbuf) > 0); + + prefix_len = strlen(reply->content->status_prefix); + text = str_c(textbuf); + text_len = str_len(textbuf); + + if (skip_status) { + i_assert(text_len > prefix_len); + text_len -= prefix_len; + text += prefix_len; + } + + for (;;) { + p = strchr(text, '\n'); + i_assert(p != NULL && p > text && *(p-1) == '\r'); + str_append_data(str, text, p - text - 1); + line_len = (size_t)(p - text) + 1; + i_assert(text_len >= line_len); + text_len -= line_len; + text = p + 1; + + if (text_len <= prefix_len) + break; + + text_len -= prefix_len; + text += prefix_len; + str_append_c(str, ' '); + } +} + +const char * +smtp_server_reply_get_one_line(const struct smtp_server_reply *reply) +{ + string_t *str = t_str_new(256); + + smtp_server_reply_write_one_line(reply, str, FALSE); + return str_c(str); +} + +const char * +smtp_server_reply_get_message(const struct smtp_server_reply *reply) +{ + string_t *str = t_str_new(256); + + smtp_server_reply_write_one_line(reply, str, TRUE); + return str_c(str); +} + +static int smtp_server_reply_send_real(struct smtp_server_reply *reply) +{ + struct smtp_server_command *cmd = reply->command; + struct smtp_server_connection *conn = cmd->context.conn; + struct ostream *output = conn->conn.output; + string_t *textbuf; + char *text; + int ret = 0; + + i_assert(reply->content != NULL); + textbuf = reply->content->text; + i_assert(str_len(textbuf) > 0); + + /* substitute '-' with ' ' in last line */ + text = str_c_modifiable(textbuf); + text = text + reply->content->last_line + 3; + if (text[0] != ' ') { + i_assert(text[0] == '-'); + text[0] = ' '; + } + + if (o_stream_send(output, str_data(textbuf), str_len(textbuf)) < 0) { + e_debug(reply->event, "Send failed: %s", + o_stream_get_disconnect_reason(output)); + smtp_server_connection_handle_output_error(conn); + return -1; + } + + e_debug(reply->event, "Sent: %s", + smtp_server_reply_get_one_line(reply)); + return ret; +} + +int smtp_server_reply_send(struct smtp_server_reply *reply) +{ + int ret; + + if (reply->sent) + return 0; + + T_BEGIN { + ret = smtp_server_reply_send_real(reply); + } T_END; + + reply->sent = TRUE; + return ret; +} + +bool smtp_server_reply_is_success(const struct smtp_server_reply *reply) +{ + i_assert(reply->content != NULL); + return (reply->content->status / 100 == 2); +} + +void smtp_server_reply_add_to_event(const struct smtp_server_reply *reply, + struct event_passthrough *e) +{ + i_assert(reply->content != NULL); + e->add_int("status_code", reply->content->status); + if (reply->content->enhanced_code != NULL && + reply->content->enhanced_code[0] != '\0') + e->add_str("enhanced_code", reply->content->enhanced_code); + if (!smtp_server_reply_is_success(reply)) + e->add_str("error", smtp_server_reply_get_message(reply)); +} + +/* + * EHLO reply + */ + +struct smtp_server_reply * +smtp_server_reply_create_ehlo(struct smtp_server_command *cmd) +{ + struct smtp_server_connection *conn = cmd->context.conn; + struct smtp_server_reply *reply; + string_t *textbuf; + + reply = smtp_server_reply_create(cmd, 250, ""); + textbuf = reply->content->text; + str_append(textbuf, reply->content->status_prefix); + str_append(textbuf, conn->set.hostname); + str_append(textbuf, "\r\n"); + + return reply; +} + +void smtp_server_reply_ehlo_add(struct smtp_server_reply *reply, + const char *keyword) +{ + string_t *textbuf; + + i_assert(!reply->submitted); + i_assert(reply->content != NULL); + textbuf = reply->content->text; + + reply->content->last_line = str_len(textbuf); + str_append(textbuf, reply->content->status_prefix); + str_append(textbuf, keyword); + str_append(textbuf, "\r\n"); +} + +void smtp_server_reply_ehlo_add_param(struct smtp_server_reply *reply, + const char *keyword, const char *param_fmt, ...) +{ + va_list args; + string_t *textbuf; + + i_assert(!reply->submitted); + i_assert(reply->content != NULL); + textbuf = reply->content->text; + + reply->content->last_line = str_len(textbuf); + str_append(textbuf, reply->content->status_prefix); + str_append(textbuf, keyword); + if (*param_fmt != '\0') { + va_start(args, param_fmt); + str_append_c(textbuf, ' '); + str_vprintfa(textbuf, param_fmt, args); + va_end(args); + } + str_append(textbuf, "\r\n"); +} + +void smtp_server_reply_ehlo_add_params(struct smtp_server_reply *reply, + const char *keyword, + const char *const *params) +{ + string_t *textbuf; + + i_assert(!reply->submitted); + i_assert(reply->content != NULL); + textbuf = reply->content->text; + + reply->content->last_line = str_len(textbuf); + str_append(textbuf, reply->content->status_prefix); + str_append(textbuf, keyword); + if (params != NULL) { + while (*params != NULL) { + str_append_c(textbuf, ' '); + str_append(textbuf, *params); + params++; + } + } + str_append(textbuf, "\r\n"); +} + +void smtp_server_reply_ehlo_add_8bitmime(struct smtp_server_reply *reply) +{ + struct smtp_server_cmd_ctx *cmd = &reply->command->context; + struct smtp_server_connection *conn = cmd->conn; + enum smtp_capability caps = conn->set.capabilities; + + if ((caps & SMTP_CAPABILITY_8BITMIME) == 0) + return; + smtp_server_reply_ehlo_add(reply, "8BITMIME"); +} + +void smtp_server_reply_ehlo_add_binarymime(struct smtp_server_reply *reply) +{ + struct smtp_server_cmd_ctx *cmd = &reply->command->context; + struct smtp_server_connection *conn = cmd->conn; + enum smtp_capability caps = conn->set.capabilities; + + if ((caps & SMTP_CAPABILITY_BINARYMIME) == 0 || + (caps & SMTP_CAPABILITY_CHUNKING) == 0) + return; + smtp_server_reply_ehlo_add(reply, "BINARYMIME"); +} + +void smtp_server_reply_ehlo_add_chunking(struct smtp_server_reply *reply) +{ + struct smtp_server_cmd_ctx *cmd = &reply->command->context; + struct smtp_server_connection *conn = cmd->conn; + enum smtp_capability caps = conn->set.capabilities; + + if ((caps & SMTP_CAPABILITY_CHUNKING) == 0) + return; + smtp_server_reply_ehlo_add(reply, "CHUNKING"); +} + +void smtp_server_reply_ehlo_add_dsn(struct smtp_server_reply *reply) +{ + struct smtp_server_cmd_ctx *cmd = &reply->command->context; + struct smtp_server_connection *conn = cmd->conn; + enum smtp_capability caps = conn->set.capabilities; + + if ((caps & SMTP_CAPABILITY_DSN) == 0) + return; + smtp_server_reply_ehlo_add(reply, "DSN"); +} + +void smtp_server_reply_ehlo_add_enhancedstatuscodes( + struct smtp_server_reply *reply) +{ + struct smtp_server_cmd_ctx *cmd = &reply->command->context; + struct smtp_server_connection *conn = cmd->conn; + enum smtp_capability caps = conn->set.capabilities; + + if ((caps & SMTP_CAPABILITY_ENHANCEDSTATUSCODES) == 0) + return; + smtp_server_reply_ehlo_add(reply, "ENHANCEDSTATUSCODES"); +} + +void smtp_server_reply_ehlo_add_pipelining(struct smtp_server_reply *reply) +{ + smtp_server_reply_ehlo_add(reply, "PIPELINING"); +} + +void smtp_server_reply_ehlo_add_size(struct smtp_server_reply *reply) +{ + struct smtp_server_cmd_ctx *cmd = &reply->command->context; + struct smtp_server_connection *conn = cmd->conn; + enum smtp_capability caps = conn->set.capabilities; + uoff_t cap_size = conn->set.max_message_size; + + if ((caps & SMTP_CAPABILITY_SIZE) == 0) + return; + + if (cap_size > 0 && cap_size != UOFF_T_MAX) { + smtp_server_reply_ehlo_add_param(reply, + "SIZE", "%"PRIuUOFF_T, cap_size); + } else { + smtp_server_reply_ehlo_add(reply, "SIZE"); + } +} + +void smtp_server_reply_ehlo_add_starttls(struct smtp_server_reply *reply) +{ + struct smtp_server_cmd_ctx *cmd = &reply->command->context; + struct smtp_server_connection *conn = cmd->conn; + enum smtp_capability caps = conn->set.capabilities; + + if ((caps & SMTP_CAPABILITY_STARTTLS) == 0) + return; + smtp_server_reply_ehlo_add(reply, "STARTTLS"); +} + +void smtp_server_reply_ehlo_add_vrfy(struct smtp_server_reply *reply) +{ + struct smtp_server_cmd_ctx *cmd = &reply->command->context; + struct smtp_server_connection *conn = cmd->conn; + enum smtp_capability caps = conn->set.capabilities; + + if ((caps & SMTP_CAPABILITY_VRFY) == 0) + return; + smtp_server_reply_ehlo_add(reply, "VRFY"); +} + +void smtp_server_reply_ehlo_add_xclient(struct smtp_server_reply *reply) +{ + static const char *base_fields = + "ADDR PORT PROTO HELO LOGIN SESSION TTL TIMEOUT"; + struct smtp_server_cmd_ctx *cmd = &reply->command->context; + struct smtp_server_connection *conn = cmd->conn; + + if (!smtp_server_connection_is_trusted(conn)) + return; + if (conn->set.xclient_extensions == NULL || + *conn->set.xclient_extensions == NULL) { + smtp_server_reply_ehlo_add_param(reply, "XCLIENT", "%s", + base_fields); + return; + } + + smtp_server_reply_ehlo_add_param(reply, "XCLIENT", "%s", + t_strconcat(base_fields, " ", + t_strarray_join(conn->set.xclient_extensions, " "), + NULL)); +} diff --git a/src/lib-smtp/smtp-server-transaction.c b/src/lib-smtp/smtp-server-transaction.c new file mode 100644 index 0000000..7d9b839 --- /dev/null +++ b/src/lib-smtp/smtp-server-transaction.c @@ -0,0 +1,361 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strfuncs.h" +#include "message-date.h" +#include "smtp-address.h" +#include "smtp-params.h" + +#include "smtp-server-private.h" + +static void +smtp_server_transaction_update_event(struct smtp_server_transaction *trans) +{ + struct event *event = trans->event; + + event_add_str(event, "transaction_id", trans->id); + event_add_str(event, "session", trans->id); + event_add_str(event, "mail_from", + smtp_address_encode(trans->mail_from)); + event_add_str(event, "mail_from_raw", + smtp_address_encode_raw(trans->mail_from)); + smtp_params_mail_add_to_event(&trans->params, event); + event_set_append_log_prefix(event, + t_strdup_printf("trans <%s>: ", trans->id)); +} + +struct smtp_server_transaction * +smtp_server_transaction_create(struct smtp_server_connection *conn, + const struct smtp_server_cmd_mail *mail_data) +{ + struct smtp_server_transaction *trans; + pool_t pool; + + /* create new transaction */ + pool = pool_alloconly_create("smtp server transaction", 4096); + trans = p_new(pool, struct smtp_server_transaction, 1); + trans->pool = pool; + trans->conn = conn; + + /* generate transaction ID */ + if (conn->transaction_seq++ == 0) + trans->id = conn->session_id; + else { + trans->id = p_strdup_printf(pool, "%s:T%u", conn->session_id, + conn->transaction_seq); + } + + trans->flags = mail_data->flags; + trans->mail_from = smtp_address_clone(trans->pool, mail_data->path); + smtp_params_mail_copy(pool, &trans->params, &mail_data->params); + trans->timestamp = mail_data->timestamp; + + if (conn->next_trans_event == NULL) + trans->event = event_create(conn->event); + else { + trans->event = conn->next_trans_event; + conn->next_trans_event = NULL; + } + smtp_server_transaction_update_event(trans); + + struct event_passthrough *e = + event_create_passthrough(trans->event)-> + set_name("smtp_server_transaction_started"); + + e_debug(e->event(), "Start"); + + if (conn->callbacks != NULL && + conn->callbacks->conn_trans_start != NULL) + conn->callbacks->conn_trans_start(conn->context, trans); + + return trans; +} + +void smtp_server_transaction_free(struct smtp_server_transaction **_trans) +{ + struct smtp_server_transaction *trans = *_trans; + struct smtp_server_connection *conn = trans->conn; + struct smtp_server_recipient **rcpts; + unsigned int rcpts_total, rcpts_aborted, rcpts_failed; + unsigned int rcpts_count, i; + + *_trans = NULL; + + if (conn->callbacks != NULL && + conn->callbacks->conn_trans_free != NULL) + conn->callbacks->conn_trans_free(conn->context, trans); + + rcpts_count = 0; + if (array_is_created(&trans->rcpt_to)) + rcpts = array_get_modifiable(&trans->rcpt_to, &rcpts_count); + + rcpts_aborted = rcpts_count + conn->state.pending_rcpt_cmds; + rcpts_failed = conn->state.denied_rcpt_cmds; + rcpts_total = rcpts_aborted + rcpts_failed; + + for (i = 0; i < rcpts_count; i++) + smtp_server_recipient_destroy(&rcpts[i]); + + if (!trans->finished) { + struct event_passthrough *e = + event_create_passthrough(trans->event)-> + set_name("smtp_server_transaction_finished")-> + add_int("recipients", rcpts_total)-> + add_int("recipients_denied", rcpts_failed)-> + add_int("recipients_aborted", rcpts_aborted)-> + add_int("recipients_failed", rcpts_failed)-> + add_int("recipients_succeeded", 0); + e->add_int("status_code", 9000); + e->add_str("enhanced_code", "9.0.0"); + e->add_str("error", "Aborted"); + + e_debug(e->event(), "Aborted"); + } + + event_unref(&trans->event); + pool_unref(&trans->pool); +} + +struct smtp_server_recipient * +smtp_server_transaction_find_rcpt_duplicate( + struct smtp_server_transaction *trans, + struct smtp_server_recipient *rcpt) +{ + struct smtp_server_recipient *drcpt; + + i_assert(array_is_created(&trans->rcpt_to)); + array_foreach_elem(&trans->rcpt_to, drcpt) { + if (drcpt == rcpt) + continue; + if (smtp_address_equals(drcpt->path, rcpt->path) && + smtp_params_rcpt_equal(&drcpt->params, &rcpt->params)) + return drcpt; + } + return NULL; +} + +void smtp_server_transaction_add_rcpt(struct smtp_server_transaction *trans, + struct smtp_server_recipient *rcpt) +{ + if (!array_is_created(&trans->rcpt_to)) + p_array_init(&trans->rcpt_to, trans->pool, 8); + + rcpt->trans = trans; + rcpt->index = array_count(&trans->rcpt_to); + + array_push_back(&trans->rcpt_to, &rcpt); +} + +bool smtp_server_transaction_has_rcpt(struct smtp_server_transaction *trans) +{ + return (array_is_created(&trans->rcpt_to) && + array_count(&trans->rcpt_to) > 0); +} + +unsigned int +smtp_server_transaction_rcpt_count(struct smtp_server_transaction *trans) +{ + if (!array_is_created(&trans->rcpt_to)) + return 0; + return array_count(&trans->rcpt_to); +} + +void smtp_server_transaction_data_command(struct smtp_server_transaction *trans, + struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_recipient *rcpt; + + trans->cmd = cmd; + + if (!array_is_created(&trans->rcpt_to)) + return; + array_foreach_elem(&trans->rcpt_to, rcpt) + smtp_server_recipient_data_command(rcpt, cmd); +} + +void smtp_server_transaction_received(struct smtp_server_transaction *trans, + uoff_t data_size) +{ + event_add_int(trans->event, "data_size", data_size); +} + +void smtp_server_transaction_reset(struct smtp_server_transaction *trans) +{ + struct smtp_server_connection *conn = trans->conn; + struct smtp_server_recipient *const *rcpts = NULL; + unsigned int rcpts_total, rcpts_failed, rcpts_aborted; + unsigned int rcpts_count, i; + + i_assert(!trans->finished); + trans->finished = TRUE; + + rcpts_count = 0; + if (array_is_created(&trans->rcpt_to)) + rcpts = array_get(&trans->rcpt_to, &rcpts_count); + + rcpts_aborted = rcpts_count + conn->state.pending_rcpt_cmds; + rcpts_failed = conn->state.denied_rcpt_cmds; + rcpts_total = rcpts_aborted + rcpts_failed; + + for (i = 0; i < rcpts_count; i++) + smtp_server_recipient_reset(rcpts[i]); + + struct event_passthrough *e = + event_create_passthrough(trans->event)-> + set_name("smtp_server_transaction_finished")-> + add_int("recipients", rcpts_total)-> + add_int("recipients_denied", rcpts_failed)-> + add_int("recipients_aborted", rcpts_aborted)-> + add_int("recipients_failed", rcpts_failed)-> + add_int("recipients_succeeded", 0)-> + add_str("is_reset", "yes"); + e_debug(e->event(), "Finished"); +} + +void smtp_server_transaction_finished(struct smtp_server_transaction *trans, + struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_connection *conn = trans->conn; + struct smtp_server_recipient *const *rcpts = NULL; + const struct smtp_server_reply *trans_reply = NULL; + unsigned int rcpts_total, rcpts_denied, rcpts_failed, rcpts_succeeded; + unsigned int rcpts_count, i; + + i_assert(conn->state.pending_rcpt_cmds == 0); + i_assert(!trans->finished); + trans->finished = TRUE; + + rcpts_count = 0; + if (array_is_created(&trans->rcpt_to)) + rcpts = array_get(&trans->rcpt_to, &rcpts_count); + + rcpts_succeeded = 0; + rcpts_denied = conn->state.denied_rcpt_cmds; + rcpts_failed = conn->state.denied_rcpt_cmds; + rcpts_total = rcpts_count + conn->state.denied_rcpt_cmds; + for (i = 0; i < rcpts_count; i++) { + struct smtp_server_reply *reply; + + if ((trans->flags & + SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT) != 0) + reply = smtp_server_command_get_reply(cmd->cmd, i); + else + reply = smtp_server_command_get_reply(cmd->cmd, 0); + smtp_server_recipient_finished(rcpts[i], reply); + + if (smtp_server_reply_is_success(reply)) + rcpts_succeeded++; + else { + rcpts_failed++; + if (trans_reply == NULL) + trans_reply = reply; + } + } + + if (trans_reply == NULL) { + /* record first success reply in transaction */ + trans_reply = smtp_server_command_get_reply(cmd->cmd, 0); + } + + struct event_passthrough *e = + event_create_passthrough(trans->event)-> + set_name("smtp_server_transaction_finished")-> + add_int("recipients", rcpts_total)-> + add_int("recipients_denied", rcpts_denied)-> + add_int("recipients_aborted", 0)-> + add_int("recipients_failed", rcpts_failed)-> + add_int("recipients_succeeded", rcpts_succeeded); + smtp_server_reply_add_to_event(trans_reply, e); + + e_debug(e->event(), "Finished"); +} + +void smtp_server_transaction_fail_data(struct smtp_server_transaction *trans, + struct smtp_server_cmd_ctx *data_cmd, + unsigned int status, const char *enh_code, + const char *fmt, va_list args) +{ + struct smtp_server_recipient *const *rcpts; + const char *msg; + unsigned int count, i; + + msg = t_strdup_vprintf(fmt, args); + rcpts = array_get(&trans->rcpt_to, &count); + for (i = 0; i < count; i++) { + smtp_server_reply_index(data_cmd, i, + status, enh_code, "<%s> %s", + smtp_address_encode(rcpts[i]->path), msg); + } +} + +void smtp_server_transaction_write_trace_record( + string_t *str, struct smtp_server_transaction *trans, + enum smtp_server_trace_rcpt_to_address rcpt_to_address) +{ + struct smtp_server_connection *conn = trans->conn; + const struct smtp_server_helo_data *helo_data = &conn->helo; + const char *host, *secstr, *rcpt_to = NULL; + + if (array_count(&trans->rcpt_to) == 1) { + struct smtp_server_recipient *const *rcpts = + array_front(&trans->rcpt_to); + + switch (rcpt_to_address) { + case SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_NONE: + break; + case SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_FINAL: + rcpt_to = smtp_address_encode(rcpts[0]->path); + break; + case SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_ORIGINAL: + rcpt_to = smtp_address_encode( + smtp_server_recipient_get_original(rcpts[0])); + break; + } + } + + /* from */ + str_append(str, "Received: from "); + if (helo_data->domain_valid) + str_append(str, helo_data->domain); + else + str_append(str, "unknown"); + host = ""; + if (conn->conn.remote_ip.family != 0) + host = net_ip2addr(&conn->conn.remote_ip); + if (host[0] != '\0') { + str_append(str, " (["); + str_append(str, host); + str_append(str, "])"); + } + /* (using) */ + secstr = smtp_server_connection_get_security_string(conn); + if (secstr != NULL) { + str_append(str, "\r\n\t(using "); + str_append(str, secstr); + str_append(str, ")"); + } + /* by, with */ + str_append(str, "\r\n\tby "); + str_append(str, conn->set.hostname); + str_append(str, " with "); + str_append(str, smtp_server_connection_get_protocol_name(conn)); + /* id */ + str_append(str, "\r\n\tid "); + str_append(str, trans->id); + /* (envelope-from) */ + str_append(str, "\r\n\t(envelope-from <"); + smtp_address_write(str, trans->mail_from); + str_append(str, ">)"); + /* for */ + if (rcpt_to != NULL) { + str_append(str, "\r\n\tfor <"); + str_append(str, rcpt_to); + str_append(str, ">"); + } + str_append(str, "; "); + /* date */ + str_append(str, message_date_create(trans->timestamp.tv_sec)); + str_printfa(str, "\r\n"); +} diff --git a/src/lib-smtp/smtp-server.c b/src/lib-smtp/smtp-server.c new file mode 100644 index 0000000..e0afde3 --- /dev/null +++ b/src/lib-smtp/smtp-server.c @@ -0,0 +1,153 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "str.h" +#include "hash.h" +#include "hostpid.h" +#include "array.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "connection.h" +#include "dns-lookup.h" +#include "iostream-rawlog.h" +#include "iostream-ssl.h" + +#include "smtp-server-private.h" + +static struct event_category event_category_smtp_server = { + .name = "smtp-server" +}; + +/* + * Server + */ + +struct smtp_server *smtp_server_init(const struct smtp_server_settings *set) +{ + struct smtp_server *server; + pool_t pool; + + pool = pool_alloconly_create("smtp server", 1024); + server = p_new(pool, struct smtp_server, 1); + server->pool = pool; + server->set.protocol = set->protocol; + server->set.reason_code_module = + p_strdup(pool, set->reason_code_module); + server->set.rawlog_dir = p_strdup_empty(pool, set->rawlog_dir); + + if (set->ssl != NULL) { + server->set.ssl = + ssl_iostream_settings_dup(server->pool, set->ssl); + } + + if (set->hostname != NULL && *set->hostname != '\0') + server->set.hostname = p_strdup(pool, set->hostname); + else + server->set.hostname = p_strdup(pool, my_hostdomain()); + if (set->login_greeting != NULL && *set->login_greeting != '\0') + server->set.login_greeting = p_strdup(pool, set->login_greeting); + else + server->set.login_greeting = PACKAGE_NAME" ready."; + if (set->capabilities == 0) { + server->set.capabilities = SMTP_SERVER_DEFAULT_CAPABILITIES; + } else { + server->set.capabilities = set->capabilities; + } + server->set.workarounds = set->workarounds; + server->set.max_client_idle_time_msecs = set->max_client_idle_time_msecs; + server->set.max_pipelined_commands = (set->max_pipelined_commands > 0 ? + set->max_pipelined_commands : 1); + server->set.max_bad_commands = (set->max_bad_commands > 0 ? + set->max_bad_commands : SMTP_SERVER_DEFAULT_MAX_BAD_COMMANDS); + server->set.max_recipients = set->max_recipients; + server->set.command_limits = set->command_limits; + server->set.max_message_size = set->max_message_size; + + if (set->mail_param_extensions != NULL) { + server->set.mail_param_extensions = + p_strarray_dup(pool, set->mail_param_extensions); + } + if (set->rcpt_param_extensions != NULL) { + server->set.rcpt_param_extensions = + p_strarray_dup(pool, set->rcpt_param_extensions); + } + if (set->xclient_extensions != NULL) { + server->set.xclient_extensions = + p_strarray_dup(pool, set->xclient_extensions); + } + + server->set.socket_send_buffer_size = set->socket_send_buffer_size; + server->set.socket_recv_buffer_size = set->socket_recv_buffer_size; + + server->set.tls_required = set->tls_required; + server->set.auth_optional = set->auth_optional; + server->set.rcpt_domain_optional = set->rcpt_domain_optional; + server->set.mail_path_allow_broken = set->mail_path_allow_broken; + server->set.no_greeting = set->no_greeting; + server->set.debug = set->debug; + server->set.no_state_in_reason = set->no_state_in_reason; + + /* There is no event log prefix added here, since the server itself does + not log anything. */ + server->event = event_create(set->event_parent); + smtp_server_event_init(server, server->event); + event_set_forced_debug(server->event, set->debug); + + server->conn_list = smtp_server_connection_list_init(); + smtp_server_commands_init(server); + return server; +} + +void smtp_server_event_init(struct smtp_server *server, struct event *event) +{ + event_add_category(event, &event_category_smtp_server); + event_add_str(event, "protocol", + smtp_protocol_name(server->set.protocol)); +} + +void smtp_server_deinit(struct smtp_server **_server) +{ + struct smtp_server *server = *_server; + + connection_list_deinit(&server->conn_list); + + if (server->ssl_ctx != NULL) + ssl_iostream_context_unref(&server->ssl_ctx); + event_unref(&server->event); + pool_unref(&server->pool); + *_server = NULL; +} + +void smtp_server_switch_ioloop(struct smtp_server *server) +{ + struct connection *_conn = server->conn_list->connections; + + /* move connections */ + /* FIXME: we wouldn't necessarily need to switch all of them + immediately, only those that have commands now. but also connections + that get new commands before ioloop is switched again.. */ + for (; _conn != NULL; _conn = _conn->next) { + struct smtp_server_connection *conn = + (struct smtp_server_connection *)_conn; + + smtp_server_connection_switch_ioloop(conn); + } +} + +int smtp_server_init_ssl_ctx(struct smtp_server *server, const char **error_r) +{ + const char *error; + + if (server->ssl_ctx != NULL || server->set.ssl == NULL) + return 0; + + if (ssl_iostream_server_context_cache_get(server->set.ssl, + &server->ssl_ctx, &error) < 0) { + *error_r = t_strdup_printf("Couldn't initialize SSL context: %s", + error); + return -1; + } + return 0; +} diff --git a/src/lib-smtp/smtp-server.h b/src/lib-smtp/smtp-server.h new file mode 100644 index 0000000..5c13491 --- /dev/null +++ b/src/lib-smtp/smtp-server.h @@ -0,0 +1,802 @@ +#ifndef SMTP_SERVER_H +#define SMTP_SERVER_H + +#include "smtp-common.h" +#include "smtp-command.h" +#include "smtp-params.h" + +struct smtp_address; +struct smtp_reply; +struct smtp_command; + +struct smtp_server_helo_data; + +struct smtp_server_esmtp_param; +struct smtp_server_cmd_ehlo; +struct smtp_server_cmd_mail; +struct smtp_server_cmd_ctx; +struct smtp_server_command; +struct smtp_server_reply; +struct smtp_server_recipient; +struct smtp_server_transaction; + +struct smtp_server; + +/* + * Types + */ + +enum smtp_server_state { + SMTP_SERVER_STATE_GREETING = 0, + SMTP_SERVER_STATE_XCLIENT, + SMTP_SERVER_STATE_HELO, + SMTP_SERVER_STATE_STARTTLS, + SMTP_SERVER_STATE_AUTH, + SMTP_SERVER_STATE_READY, + SMTP_SERVER_STATE_MAIL_FROM, + SMTP_SERVER_STATE_RCPT_TO, + SMTP_SERVER_STATE_DATA, +}; +extern const char *const smtp_server_state_names[]; + +struct smtp_server_helo_data { + const char *domain; + + bool domain_valid:1; /* Valid domain/literal specified */ + bool old_smtp:1; /* Client sent HELO rather than EHLO */ +}; + +/* + * Recipient + */ + +enum smtp_server_recipient_hook_type { + /* approved: the server is about to approve this recipient by sending + a success reply to the RCPT command. */ + SMTP_SERVER_RECIPIENT_HOOK_APPROVED, + /* data_replied: the DATA command is replied for this recipient */ + SMTP_SERVER_RECIPIENT_HOOK_DATA_REPLIED, + /* destroy: recipient is about to be destroyed. */ + SMTP_SERVER_RECIPIENT_HOOK_DESTROY +}; + +typedef void smtp_server_rcpt_func_t(struct smtp_server_recipient *rcpt, + void *context); + +struct smtp_server_recipient { + pool_t pool; + struct smtp_server_connection *conn; + struct smtp_server_transaction *trans; + struct event *event; + + struct smtp_address *path; + struct smtp_params_rcpt params; + + /* The associated RCPT or DATA command (whichever applies). This is NULL + when no command is active. */ + struct smtp_server_cmd_ctx *cmd; + + /* The index in the list of approved recipients */ + unsigned int index; + + void *context; + + bool replied:1; + bool finished:1; +}; +ARRAY_DEFINE_TYPE(smtp_server_recipient, struct smtp_server_recipient *); + +/* Returns the original recipient path if available. Otherwise, it returns the + final path. */ +const struct smtp_address * +smtp_server_recipient_get_original(struct smtp_server_recipient *rcpt); + +struct smtp_server_reply * +smtp_server_recipient_get_reply(struct smtp_server_recipient *rcpt); +bool smtp_server_recipient_is_replied(struct smtp_server_recipient *rcpt); +void smtp_server_recipient_replyv(struct smtp_server_recipient *rcpt, + unsigned int status, const char *enh_code, + const char *fmt, va_list args) + ATTR_FORMAT(4, 0); +void smtp_server_recipient_reply(struct smtp_server_recipient *rcpt, + unsigned int status, const char *enh_code, + const char *fmt, ...) ATTR_FORMAT(4, 5); +void smtp_server_recipient_reply_forward(struct smtp_server_recipient *rcpt, + const struct smtp_reply *from); + +/* Hooks */ + +void smtp_server_recipient_add_hook(struct smtp_server_recipient *rcpt, + enum smtp_server_recipient_hook_type type, + smtp_server_rcpt_func_t func, + void *context); +#define smtp_server_recipient_add_hook(_rcpt, _type, _func, _context) \ + smtp_server_recipient_add_hook((_rcpt), (_type) - \ + CALLBACK_TYPECHECK(_func, void (*)( \ + struct smtp_server_recipient *, typeof(_context))), \ + (smtp_server_rcpt_func_t *)(_func), (_context)) +void smtp_server_recipient_remove_hook( + struct smtp_server_recipient *rcpt, + enum smtp_server_recipient_hook_type type, + smtp_server_rcpt_func_t *func); +#define smtp_server_recipient_remove_hook(_rcpt, _type, _func) \ + smtp_server_recipient_remove_hook((_rcpt), (_type), \ + (smtp_server_rcpt_func_t *)(_func)); + +/* + * Transaction + */ + +enum smtp_server_trace_rcpt_to_address { + /* Don't add recipient address to trace header. */ + SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_NONE, + /* Add final recipient address to trace header. */ + SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_FINAL, + /* Add original recipient address to trace header. */ + SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_ORIGINAL, +}; + +enum smtp_server_transaction_flags { + SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT = BIT(0), +}; + +struct smtp_server_transaction { + pool_t pool; + struct smtp_server_connection *conn; + struct event *event; + const char *id; + struct timeval timestamp; + + enum smtp_server_transaction_flags flags; + + struct smtp_address *mail_from; + struct smtp_params_mail params; + ARRAY_TYPE(smtp_server_recipient) rcpt_to; + + /* The associated DATA command. This is NULL until the last DATA/BDAT + command is issued. + */ + struct smtp_server_cmd_ctx *cmd; + + void *context; + + bool finished:1; +}; + +struct smtp_server_recipient * +smtp_server_transaction_find_rcpt_duplicate( + struct smtp_server_transaction *trans, + struct smtp_server_recipient *rcpt); + +void smtp_server_transaction_fail_data(struct smtp_server_transaction *trans, + struct smtp_server_cmd_ctx *data_cmd, + unsigned int status, + const char *enh_code, + const char *fmt, va_list args) + ATTR_FORMAT(5, 0); + +void smtp_server_transaction_write_trace_record( + string_t *str, struct smtp_server_transaction *trans, + enum smtp_server_trace_rcpt_to_address rcpt_to_address); + +/* + * Callbacks + */ + +struct smtp_server_cmd_helo { + struct smtp_server_helo_data helo; + + bool first:1; /* This is the first */ + bool changed:1; /* This EHLO/HELO/LHLO is the first or different + from a previous one */ +}; + +struct smtp_server_cmd_mail { + struct smtp_address *path; + struct smtp_params_mail params; + + struct timeval timestamp; + + enum smtp_server_transaction_flags flags; +}; + +struct smtp_server_cmd_auth { + const char *sasl_mech; + const char *initial_response; +}; + +struct smtp_server_callbacks { + /* Command callbacks: + + These are used to override/implement the behavior of the various core + SMTP commands. Commands are handled asynchronously, which means that + the command is not necessarily finished when the callback ends. A + command is finished either when 1 is returned or a reply is submitted + for it. When a callback returns 0, the command implementation is + waiting for an external event and when it returns -1 an error + occurred. When 1 is returned, a default success reply is set if no + reply was submitted. Not submitting an error reply when -1 is + returned causes an assert fail. Except for RCPT and DATA, all these + callbacks are optional to implement; appropriate default behavior is + provided. + + The SMTP server API takes care of transaction state checking. + However, until all previous commands are handled, a transaction + command cannot rely on the transaction state being final. Use + cmd->hook_next to get notified when all previous commands are + finished and the current command is next in line to reply. + + If the implementation does not need asynchronous behavior, set + max_pipelined_commands=1 and don't return 0 from any command handler. + */ + + /* HELO/EHLO/LHLO */ + int (*conn_cmd_helo)(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_helo *data); + /* STARTTLS */ + int (*conn_cmd_starttls)(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd); + /* AUTH */ + int (*conn_cmd_auth)(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_auth *data); + int (*conn_cmd_auth_continue)(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd, + const char *response); + /* MAIL */ + int (*conn_cmd_mail)(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_mail *data); + /* RCPT */ + int (*conn_cmd_rcpt)(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_recipient *rcpt); + /* RSET */ + int (*conn_cmd_rset)(void *conn_ctx, struct smtp_server_cmd_ctx *cmd); + /* DATA */ + int (*conn_cmd_data_begin)(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, + struct istream *data_input); + int (*conn_cmd_data_continue)(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans); + /* VRFY */ + int (*conn_cmd_vrfy)(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + const char *param); + /* NOOP */ + int (*conn_cmd_noop)(void *conn_ctx, struct smtp_server_cmd_ctx *cmd); + /* QUIT */ + int (*conn_cmd_quit)(void *conn_ctx, struct smtp_server_cmd_ctx *cmd); + /* XCLIENT */ + void (*conn_cmd_xclient)(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd, + struct smtp_proxy_data *data); + + /* Command input callbacks: + + These can be used to do stuff before and after a pipelined group of + commands is read. + */ + void (*conn_cmd_input_pre)(void *context); + void (*conn_cmd_input_post)(void *context); + + /* Transaction events */ + void (*conn_trans_start)(void *context, + struct smtp_server_transaction *trans); + void (*conn_trans_free)(void *context, + struct smtp_server_transaction *trans); + + /* Protocol state events */ + void (*conn_state_changed)(void *context, + enum smtp_server_state new_state, + const char *new_args) ATTR_NULL(3); + + /* Proxy data */ + void (*conn_proxy_data_updated)(void *conn_ctx, + const struct smtp_proxy_data *data); + + /* Connection */ + int (*conn_start_tls)(void *conn_ctx, + struct istream **input, struct ostream **output); + /* Connection is disconnected. This is always called before + conn_free(). */ + void (*conn_disconnect)(void *context, const char *reason); + /* The last reference to connection is dropped, causing the connection + to be freed. */ + void (*conn_free)(void *context); + + /* Security */ + bool (*conn_is_trusted)(void *context); +}; + +/* + * Server + */ + +enum smtp_server_workarounds { + SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH = BIT(0), + SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH = BIT(1) +}; + +struct smtp_server_settings { + /* The protocol we are serving */ + enum smtp_protocol protocol; + /* Standard capabilities supported by the server */ + enum smtp_capability capabilities; + /* Enabled workarounds for client protocol deviations */ + enum smtp_server_workarounds workarounds; + + /* Module name for event reason codes. */ + const char *reason_code_module; + /* Our hostname as presented to the client */ + const char *hostname; + /* The message sent in the SMTP server greeting */ + const char *login_greeting; + /* The directory that - if it exists and is accessible - is used to + write raw protocol logs for debugging */ + const char *rawlog_dir; + + /* SSL settings; if NULL, master_service_ssl_init() is used instead */ + const struct ssl_iostream_settings *ssl; + + /* The maximum time in milliseconds a client is allowed to be idle + before it is disconnected. */ + unsigned int max_client_idle_time_msecs; + + /* Maximum number of commands in pipeline per connection (default = 1) + */ + unsigned int max_pipelined_commands; + + /* Maximum number of sequential bad commands */ + unsigned int max_bad_commands; + + /* Maximum number of recipients in a transaction + (0 means unlimited, which is the default) */ + unsigned int max_recipients; + + /* Command limits */ + struct smtp_command_limits command_limits; + + /* Message size limit */ + uoff_t max_message_size; + + /* Accept these additional custom MAIL parameters */ + const char *const *mail_param_extensions; + /* Accept these additional custom RCPT parameters */ + const char *const *rcpt_param_extensions; + /* Accept these additional custom XCLIENT fields */ + const char *const *xclient_extensions; + + /* The kernel send/receive buffer sizes used for the connection sockets. + Configuring this is mainly useful for the test suite. The kernel + defaults are used when these settings are 0. */ + size_t socket_send_buffer_size; + size_t socket_recv_buffer_size; + + /* Event to use for the smtp server. */ + struct event *event_parent; + + /* Enable logging debug messages */ + bool debug:1; + /* Authentication is not required for this service */ + bool auth_optional:1; + /* TLS security is required for this service */ + bool tls_required:1; + /* The path provided to the MAIL command does not need to be valid. A + completely invalid path will parse as <>. Paths that can still be + fixed by splitting it on the last `@' yielding a usable localpart and + domain, will be parsed as such. There are limits though; when the + path is badly delimited or contains control characters, the MAIL + command will still fail. The unparsed broken address will be + available in the `raw' field of struct smtp_address for logging etc. + */ + bool mail_path_allow_broken:1; + /* The path provided to the RCPT command does not need to have the + domain part. */ + bool rcpt_domain_optional:1; + /* Don't include "(state=%s)" in the disconnection reason string. */ + bool no_state_in_reason:1; + /* Don't send a greeting or login success message to the client upon + connection start. */ + bool no_greeting:1; +}; + +struct smtp_server_stats { + unsigned int command_count, reply_count; + uoff_t input, output; +}; + +/* + * Server + */ + +struct smtp_server *smtp_server_init(const struct smtp_server_settings *set); +void smtp_server_deinit(struct smtp_server **_server); + +void smtp_server_switch_ioloop(struct smtp_server *server); + +/* + * Connection + */ + +/* Create connection. It is still inactive and needs to be started with + one of the functions below. */ +struct smtp_server_connection * +smtp_server_connection_create( + struct smtp_server *server, int fd_in, int fd_out, + const struct ip_addr *remote_ip, in_port_t remote_port, bool ssl_start, + const struct smtp_server_settings *set, + const struct smtp_server_callbacks *callbacks, void *context) + ATTR_NULL(4, 6, 8); +struct smtp_server_connection * +smtp_server_connection_create_from_streams( + struct smtp_server *server, + struct istream *input, struct ostream *output, + const struct ip_addr *remote_ip, in_port_t remote_port, + const struct smtp_server_settings *set, + const struct smtp_server_callbacks *callbacks, void *context) + ATTR_NULL(4, 6, 8); + +void smtp_server_connection_ref(struct smtp_server_connection *conn); +bool smtp_server_connection_unref(struct smtp_server_connection **_conn); + +/* Initialize the connection with state and data from login service */ +void smtp_server_connection_login(struct smtp_server_connection *conn, + const char *username, const char *helo, + const unsigned char *pdata, + unsigned int pdata_len, bool ssl_secured); + +/* Start the connection. Establishes SSL layer immediately if instructed, + and sends the greeting once the connection is ready for commands. */ +void smtp_server_connection_start(struct smtp_server_connection *conn); +/* Start the connection, but only establish SSL layer and send greeting; + handling command input is held off until smtp_server_connection_resume() is + called. */ +void smtp_server_connection_start_pending(struct smtp_server_connection *conn); +/* Abort the connection prematurely (before it is started). */ +void smtp_server_connection_abort(struct smtp_server_connection **_conn, + unsigned int status, const char *enh_code, + const char *reason); + +/* Halt connection command input and idle timeout entirely. */ +void smtp_server_connection_halt(struct smtp_server_connection *conn); +/* Resume connection command input and idle timeout. */ +void smtp_server_connection_resume(struct smtp_server_connection *conn); + +void smtp_server_connection_input_lock(struct smtp_server_connection *conn); +void smtp_server_connection_input_unlock(struct smtp_server_connection *conn); + +void smtp_server_connection_set_streams(struct smtp_server_connection *conn, + struct istream *input, + struct ostream *output); +void smtp_server_connection_set_ssl_streams(struct smtp_server_connection *conn, + struct istream *input, + struct ostream *output); + +void smtp_server_connection_close(struct smtp_server_connection **_conn, + const char *reason) ATTR_NULL(2); +void smtp_server_connection_terminate(struct smtp_server_connection **_conn, + const char *enh_code, const char *reason) + ATTR_NULL(3); + +bool smtp_server_connection_data_check_state(struct smtp_server_cmd_ctx *cmd); +void smtp_server_connection_data_chunk_init(struct smtp_server_cmd_ctx *cmd); +int smtp_server_connection_data_chunk_add(struct smtp_server_cmd_ctx *cmd, + struct istream *chunk, + uoff_t chunk_size, bool chunk_last, + bool client_input); + +enum smtp_server_state +smtp_server_connection_get_state(struct smtp_server_connection *conn, + const char **args_r) ATTR_NULL(2); +const char * +smtp_server_connection_get_security_string(struct smtp_server_connection *conn); +struct smtp_server_transaction * +smtp_server_connection_get_transaction(struct smtp_server_connection *conn); +const char * +smtp_server_connection_get_transaction_id(struct smtp_server_connection *conn); +const struct smtp_server_stats * +smtp_server_connection_get_stats(struct smtp_server_connection *conn); +void *smtp_server_connection_get_context(struct smtp_server_connection *conn) + ATTR_PURE; +enum smtp_protocol +smtp_server_connection_get_protocol(struct smtp_server_connection *conn) + ATTR_PURE; +const char * +smtp_server_connection_get_protocol_name(struct smtp_server_connection *conn); +struct smtp_server_helo_data * +smtp_server_connection_get_helo_data(struct smtp_server_connection *conn); + +void smtp_server_connection_get_proxy_data(struct smtp_server_connection *conn, + struct smtp_proxy_data *proxy_data); +void smtp_server_connection_set_proxy_data( + struct smtp_server_connection *conn, + const struct smtp_proxy_data *proxy_data); + +void smtp_server_connection_set_capabilities( + struct smtp_server_connection *conn, enum smtp_capability capabilities); +void smtp_server_connection_add_extra_capability( + struct smtp_server_connection *conn, + const struct smtp_capability_extra *cap); + +void smtp_server_connection_register_mail_param( + struct smtp_server_connection *conn, const char *param); +void smtp_server_connection_register_rcpt_param( + struct smtp_server_connection *conn, const char *param); + +bool smtp_server_connection_is_ssl_secured(struct smtp_server_connection *conn); +bool smtp_server_connection_is_trusted(struct smtp_server_connection *conn); + +/* + * Command + */ + +enum smtp_server_command_flags { + SMTP_SERVER_CMD_FLAG_PRETLS = BIT(0), + SMTP_SERVER_CMD_FLAG_PREAUTH = BIT(1) +}; + +enum smtp_server_command_hook_type { + /* next: command is next to reply but has not submittted all replies + yet. */ + SMTP_SERVER_COMMAND_HOOK_NEXT, + /* replied_one: command has submitted one reply. */ + SMTP_SERVER_COMMAND_HOOK_REPLIED_ONE, + /* replied: command has submitted all replies. */ + SMTP_SERVER_COMMAND_HOOK_REPLIED, + /* completed: server is about to send last replies for this command. */ + SMTP_SERVER_COMMAND_HOOK_COMPLETED, + /* destroy: command is about to be destroyed. */ + SMTP_SERVER_COMMAND_HOOK_DESTROY +}; + +/* Commands are handled asynchronously, which means that the command is not + necessary finished when the start function ends. A command is finished + when a reply is submitted for it. Several command hooks are available to + get notified about events in the command's life cycle. + */ + +typedef void smtp_server_cmd_input_callback_t(struct smtp_server_cmd_ctx *cmd); +typedef void smtp_server_cmd_start_func_t(struct smtp_server_cmd_ctx *cmd, + const char *params); +typedef void smtp_server_cmd_func_t(struct smtp_server_cmd_ctx *cmd, + void *context); + +struct smtp_server_cmd_ctx { + pool_t pool; + struct event *event; + const char *name; + + struct smtp_server *server; + struct smtp_server_connection *conn; + struct smtp_server_command *cmd; +}; + +/* Hooks: + + */ + +void smtp_server_command_add_hook(struct smtp_server_command *cmd, + enum smtp_server_command_hook_type type, + smtp_server_cmd_func_t func, + void *context); +#define smtp_server_command_add_hook(_cmd, _type, _func, _context) \ + smtp_server_command_add_hook((_cmd), (_type) - \ + CALLBACK_TYPECHECK(_func, void (*)( \ + struct smtp_server_cmd_ctx *, typeof(_context))), \ + (smtp_server_cmd_func_t *)(_func), (_context)) +void smtp_server_command_remove_hook(struct smtp_server_command *cmd, + enum smtp_server_command_hook_type type, + smtp_server_cmd_func_t *func); +#define smtp_server_command_remove_hook(_cmd, _type, _func) \ + smtp_server_command_remove_hook((_cmd), (_type), \ + (smtp_server_cmd_func_t *)(_func)); + +/* The core SMTP commands are pre-registered. Special connection callbacks are + provided for the core SMTP commands. Only use this command registration API + when custom/extension SMTP commands are required. It is also possible to + completely override the default implementations. + */ +void smtp_server_command_register(struct smtp_server *server, const char *name, + smtp_server_cmd_start_func_t *func, + enum smtp_server_command_flags); +void smtp_server_command_unregister(struct smtp_server *server, + const char *name); +void smtp_server_command_override(struct smtp_server *server, const char *name, + smtp_server_cmd_start_func_t *func, + enum smtp_server_command_flags flags); + +void smtp_server_command_set_reply_count(struct smtp_server_command *cmd, + unsigned int count); +unsigned int +smtp_server_command_get_reply_count(struct smtp_server_command *cmd); + +void smtp_server_command_fail(struct smtp_server_command *cmd, + unsigned int status, const char *enh_code, + const char *fmt, ...) ATTR_FORMAT(4, 5); + +struct smtp_server_reply * +smtp_server_command_get_reply(struct smtp_server_command *cmd, + unsigned int idx); +bool smtp_server_command_reply_status_equals(struct smtp_server_command *cmd, + unsigned int status); +bool smtp_server_command_is_replied(struct smtp_server_command *cmd); +bool smtp_server_command_reply_is_forwarded(struct smtp_server_command *cmd); +bool smtp_server_command_replied_success(struct smtp_server_command *cmd); + +void smtp_server_command_input_lock(struct smtp_server_cmd_ctx *cmd); +void smtp_server_command_input_unlock(struct smtp_server_cmd_ctx *cmd); +void smtp_server_command_input_capture( + struct smtp_server_cmd_ctx *cmd, + smtp_server_cmd_input_callback_t *callback); + +void smtp_server_command_pipeline_block(struct smtp_server_cmd_ctx *cmd); +void smtp_server_command_pipeline_unblock(struct smtp_server_cmd_ctx *cmd); + +/* EHLO */ + +void smtp_server_cmd_ehlo(struct smtp_server_cmd_ctx *cmd, const char *params); +void smtp_server_cmd_helo(struct smtp_server_cmd_ctx *cmd, const char *params); + +struct smtp_server_reply * +smtp_server_cmd_ehlo_reply_create(struct smtp_server_cmd_ctx *cmd); +void smtp_server_cmd_ehlo_reply_default(struct smtp_server_cmd_ctx *cmd); + +/* STARTTLS */ + +void smtp_server_cmd_starttls(struct smtp_server_cmd_ctx *cmd, + const char *params); + +/* AUTH */ + +void smtp_server_cmd_auth(struct smtp_server_cmd_ctx *cmd, const char *params); + +void smtp_server_cmd_auth_send_challenge(struct smtp_server_cmd_ctx *cmd, + const char *challenge); +void smtp_server_cmd_auth_success(struct smtp_server_cmd_ctx *cmd, + const char *username, const char *success_msg) + ATTR_NULL(3); + +/* MAIL */ + +void smtp_server_cmd_mail(struct smtp_server_cmd_ctx *cmd, const char *params); + +void smtp_server_cmd_mail_reply_success(struct smtp_server_cmd_ctx *cmd); + +/* RCPT */ + +void smtp_server_cmd_rcpt(struct smtp_server_cmd_ctx *cmd, const char *params); + +bool smtp_server_command_is_rcpt(struct smtp_server_cmd_ctx *cmd); +void smtp_server_cmd_rcpt_reply_success(struct smtp_server_cmd_ctx *cmd); + +/* RSET */ + +void smtp_server_cmd_rset(struct smtp_server_cmd_ctx *cmd, const char *params); + +void smtp_server_cmd_rset_reply_success(struct smtp_server_cmd_ctx *cmd); + +/* DATA */ + +void smtp_server_cmd_data(struct smtp_server_cmd_ctx *cmd, const char *params); +void smtp_server_cmd_bdat(struct smtp_server_cmd_ctx *cmd, const char *params); + +bool smtp_server_cmd_data_check_size(struct smtp_server_cmd_ctx *cmd); + +/* VRFY */ + +void smtp_server_cmd_vrfy(struct smtp_server_cmd_ctx *cmd, const char *params); + +void smtp_server_cmd_vrfy_reply_default(struct smtp_server_cmd_ctx *cmd); + +/* NOOP */ + +void smtp_server_cmd_noop(struct smtp_server_cmd_ctx *cmd, const char *params); + +void smtp_server_cmd_noop_reply_success(struct smtp_server_cmd_ctx *cmd); + +/* QUIT */ + +void smtp_server_cmd_quit(struct smtp_server_cmd_ctx *cmd, const char *params); + +/* XCLIENT */ + +void smtp_server_cmd_xclient(struct smtp_server_cmd_ctx *cmd, + const char *params); + +/* + * Reply + */ + +struct smtp_server_reply * +smtp_server_reply_create_index(struct smtp_server_command *cmd, + unsigned int index, unsigned int status, + const char *enh_code) ATTR_NULL(3); +struct smtp_server_reply * +smtp_server_reply_create(struct smtp_server_command *cmd, unsigned int status, + const char *enh_code) ATTR_NULL(3); +struct smtp_server_reply * +smtp_server_reply_create_forward(struct smtp_server_command *cmd, + unsigned int index, + const struct smtp_reply *from); + +void smtp_server_reply_set_status(struct smtp_server_reply *reply, + unsigned int status, const char *enh_code) + ATTR_NULL(3); +unsigned int smtp_server_reply_get_status(struct smtp_server_reply *reply, + const char **enh_code_r) ATTR_NULL(3); + +void smtp_server_reply_add_text(struct smtp_server_reply *reply, + const char *line); +void smtp_server_reply_prepend_text(struct smtp_server_reply *reply, + const char *text_prefix); +void smtp_server_reply_replace_path(struct smtp_server_reply *reply, + struct smtp_address *path, bool add); + +void smtp_server_reply_submit(struct smtp_server_reply *reply); +void smtp_server_reply_submit_duplicate(struct smtp_server_cmd_ctx *_cmd, + unsigned int index, + unsigned int from_index); + +/* Submit a reply for the command at the specified index (> 0 only if more than + a single reply is expected). */ +void smtp_server_reply_indexv(struct smtp_server_cmd_ctx *_cmd, + unsigned int index, unsigned int status, + const char *enh_code, + const char *fmt, va_list args) ATTR_FORMAT(5, 0); +void smtp_server_reply_index(struct smtp_server_cmd_ctx *_cmd, + unsigned int index, unsigned int status, + const char *enh_code, const char *fmt, ...) + ATTR_FORMAT(5, 6); +/* Submit the reply for the specified command. */ +void smtp_server_reply(struct smtp_server_cmd_ctx *_cmd, unsigned int status, + const char *enh_code, const char *fmt, ...) + ATTR_FORMAT(4, 5); +/* Forward a reply for the command at the specified index (> 0 only if more + than a single reply is expected). */ +void smtp_server_reply_index_forward(struct smtp_server_cmd_ctx *cmd, + unsigned int index, + const struct smtp_reply *from); +/* Forward the reply for the specified command. */ +void smtp_server_reply_forward(struct smtp_server_cmd_ctx *cmd, + const struct smtp_reply *from); +/* Submit the same message for all expected replies for this command. */ +void smtp_server_reply_all(struct smtp_server_cmd_ctx *_cmd, + unsigned int status, const char *enh_code, + const char *fmt, ...) ATTR_FORMAT(4, 5); +/* Submit and send the same message for all expected replies for this command + early; i.e., no matter whether all command data is received completely. */ +void smtp_server_reply_early(struct smtp_server_cmd_ctx *_cmd, + unsigned int status, const char *enh_code, + const char *fmt, ...) ATTR_FORMAT(4, 5); + +/* Reply the command with a 221 bye message */ +void smtp_server_reply_quit(struct smtp_server_cmd_ctx *_cmd); + +bool smtp_server_reply_is_success(const struct smtp_server_reply *reply); + +/* EHLO */ + +struct smtp_server_reply * +smtp_server_reply_create_ehlo(struct smtp_server_command *cmd); +void smtp_server_reply_ehlo_add(struct smtp_server_reply *reply, + const char *keyword); +void smtp_server_reply_ehlo_add_param(struct smtp_server_reply *reply, + const char *keyword, + const char *param_fmt, ...) + ATTR_FORMAT(3, 4); +void smtp_server_reply_ehlo_add_params(struct smtp_server_reply *reply, + const char *keyword, + const char *const *params) ATTR_NULL(3); + +void smtp_server_reply_ehlo_add_8bitmime(struct smtp_server_reply *reply); +void smtp_server_reply_ehlo_add_binarymime(struct smtp_server_reply *reply); +void smtp_server_reply_ehlo_add_chunking(struct smtp_server_reply *reply); +void smtp_server_reply_ehlo_add_dsn(struct smtp_server_reply *reply); +void smtp_server_reply_ehlo_add_enhancedstatuscodes( + struct smtp_server_reply *reply); +void smtp_server_reply_ehlo_add_pipelining(struct smtp_server_reply *reply); +void smtp_server_reply_ehlo_add_size(struct smtp_server_reply *reply); +void smtp_server_reply_ehlo_add_starttls(struct smtp_server_reply *reply); +void smtp_server_reply_ehlo_add_vrfy(struct smtp_server_reply *reply); +void smtp_server_reply_ehlo_add_xclient(struct smtp_server_reply *reply); + +#endif diff --git a/src/lib-smtp/smtp-submit-settings.c b/src/lib-smtp/smtp-submit-settings.c new file mode 100644 index 0000000..cfe4afd --- /dev/null +++ b/src/lib-smtp/smtp-submit-settings.c @@ -0,0 +1,69 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "hostpid.h" +#include "settings-parser.h" +#include "smtp-submit-settings.h" + +#include + +static bool smtp_submit_settings_check(void *_set, pool_t pool, const char **error_r); + +#undef DEF +#undef DEFLIST +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct smtp_submit_settings) +#define DEFLIST(field, name, defines) \ + { .type = SET_DEFLIST, .key = name, \ + .offset = offsetof(struct smtp_submit_settings, field), \ + .list_info = defines } + +static const struct setting_define smtp_submit_setting_defines[] = { + DEF(STR, hostname), + DEF(BOOL, mail_debug), + + DEF(STR_VARS, submission_host), + DEF(STR_VARS, sendmail_path), + DEF(TIME, submission_timeout), + + DEF(ENUM, submission_ssl), + + SETTING_DEFINE_LIST_END +}; + +static const struct smtp_submit_settings smtp_submit_default_settings = { + .hostname = "", + .mail_debug = FALSE, + + .submission_host = "", + .sendmail_path = "/usr/sbin/sendmail", + .submission_timeout = 30, + + .submission_ssl = "no:smtps:submissions:starttls", +}; + +const struct setting_parser_info smtp_submit_setting_parser_info = { + .module_name = "smtp-submit", + .defines = smtp_submit_setting_defines, + .defaults = &smtp_submit_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct smtp_submit_settings), + + .parent_offset = SIZE_MAX, + +#ifndef CONFIG_BINARY + .check_func = smtp_submit_settings_check, +#endif +}; + +static bool +smtp_submit_settings_check(void *_set, pool_t pool, + const char **error_r ATTR_UNUSED) +{ + struct smtp_submit_settings *set = _set; + + if (*set->hostname == '\0') + set->hostname = p_strdup(pool, my_hostdomain()); + return TRUE; +} diff --git a/src/lib-smtp/smtp-submit-settings.h b/src/lib-smtp/smtp-submit-settings.h new file mode 100644 index 0000000..08c4243 --- /dev/null +++ b/src/lib-smtp/smtp-submit-settings.h @@ -0,0 +1,17 @@ +#ifndef SMTP_SUBMIT_SETTINGS_H +#define SMTP_SUBMIT_SETTINGS_H + +struct smtp_submit_settings { + const char *hostname; + bool mail_debug; + + const char *submission_host; + const char *sendmail_path; + unsigned int submission_timeout; + + const char *submission_ssl; +}; + +extern const struct setting_parser_info smtp_submit_setting_parser_info; + +#endif diff --git a/src/lib-smtp/smtp-submit.c b/src/lib-smtp/smtp-submit.c new file mode 100644 index 0000000..0328b95 --- /dev/null +++ b/src/lib-smtp/smtp-submit.c @@ -0,0 +1,505 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "iostream-temp.h" +#include "iostream-ssl.h" +#include "master-service.h" +#include "program-client.h" +#include "smtp-client.h" +#include "smtp-client-connection.h" +#include "smtp-client-transaction.h" +#include "smtp-submit.h" + +#include +#include +#include +#include + +#define DEFAULT_SUBMISSION_PORT 25 + +static struct event_category event_category_smtp_submit = { + .name = "smtp-submit" +}; + +struct smtp_submit_session { + pool_t pool; + struct smtp_submit_settings set; + struct ssl_iostream_settings ssl_set; + struct event *event; + bool allow_root:1; +}; + +struct smtp_submit { + pool_t pool; + + struct smtp_submit_session *session; + struct event *event; + + struct ostream *output; + struct istream *input; + + struct smtp_address *mail_from; + ARRAY_TYPE(smtp_address) rcpt_to; + + struct timeout *to_error; + int status; + const char *error; + + struct program_client *prg_client; + struct smtp_client *smtp_client; + struct smtp_client_transaction *smtp_trans; + + smtp_submit_callback_t *callback; + void *context; + + bool simple:1; +}; + +struct smtp_submit_session * +smtp_submit_session_init(const struct smtp_submit_input *input, + const struct smtp_submit_settings *set) +{ + struct smtp_submit_session *session; + pool_t pool; + + pool = pool_alloconly_create("smtp submit session", 128); + session = p_new(pool, struct smtp_submit_session, 1); + session->pool = pool; + + session->set = *set; + session->set.hostname = + p_strdup_empty(pool, set->hostname); + session->set.submission_host = + p_strdup_empty(pool, set->submission_host); + session->set.sendmail_path = + p_strdup_empty(pool, set->sendmail_path); + session->set.submission_ssl = + p_strdup_empty(pool, set->submission_ssl); + + if (input->ssl != NULL) { + ssl_iostream_settings_init_from(pool, &session->ssl_set, + input->ssl); + } + session->allow_root = input->allow_root; + + session->event = event_create(input->event_parent); + event_add_category(session->event, &event_category_smtp_submit); + + return session; +} + +void smtp_submit_session_deinit(struct smtp_submit_session **_session) +{ + struct smtp_submit_session *session = *_session; + + *_session = NULL; + + event_unref(&session->event); + pool_unref(&session->pool); +} + +struct smtp_submit * +smtp_submit_init(struct smtp_submit_session *session, + const struct smtp_address *mail_from) +{ + struct smtp_submit *subm; + pool_t pool; + + pool = pool_alloconly_create("smtp submit", 256); + subm = p_new(pool, struct smtp_submit, 1); + subm->session = session; + subm->pool = pool; + + subm->mail_from = smtp_address_clone(pool, mail_from);; + p_array_init(&subm->rcpt_to, pool, 2); + + subm->event = event_create(session->event); + event_add_str(subm->event, "mail_from", + smtp_address_encode(subm->mail_from)); + + return subm; +} + +struct smtp_submit * +smtp_submit_init_simple(const struct smtp_submit_input *input, + const struct smtp_submit_settings *set, + const struct smtp_address *mail_from) +{ + struct smtp_submit_session *session; + struct smtp_submit *subm; + + session = smtp_submit_session_init(input, set); + subm = smtp_submit_init(session, mail_from); + subm->simple = TRUE; + return subm; +} + +void smtp_submit_deinit(struct smtp_submit **_subm) +{ + struct smtp_submit *subm = *_subm; + + *_subm = NULL; + + if (subm->output != NULL) + o_stream_destroy(&subm->output); + if (subm->input != NULL) + i_stream_destroy(&subm->input); + + if (subm->prg_client != NULL) + program_client_destroy(&subm->prg_client); + if (subm->smtp_trans != NULL) + smtp_client_transaction_destroy(&subm->smtp_trans); + if (subm->smtp_client != NULL) + smtp_client_deinit(&subm->smtp_client); + + timeout_remove(&subm->to_error); + + if (subm->simple) + smtp_submit_session_deinit(&subm->session); + event_unref(&subm->event); + pool_unref(&subm->pool); +} + +void smtp_submit_add_rcpt(struct smtp_submit *subm, + const struct smtp_address *rcpt_to) +{ + struct smtp_address *rcpt; + + i_assert(subm->output == NULL); + i_assert(!smtp_address_isnull(rcpt_to)); + + rcpt = smtp_address_clone(subm->pool, rcpt_to); + array_push_back(&subm->rcpt_to, &rcpt); +} + +struct ostream *smtp_submit_send(struct smtp_submit *subm) +{ + i_assert(subm->output == NULL); + i_assert(array_count(&subm->rcpt_to) > 0); + + event_add_int(subm->event, "recipients", array_count(&subm->rcpt_to)); + + subm->output = iostream_temp_create + (t_strconcat("/tmp/dovecot.", + master_service_get_name(master_service), NULL), 0); + o_stream_set_no_error_handling(subm->output, TRUE); + return subm->output; +} + +static void +smtp_submit_callback(struct smtp_submit *subm, int status, + const char *error) +{ + struct smtp_submit_result result; + smtp_submit_callback_t *callback; + + timeout_remove(&subm->to_error); + + struct event_passthrough *e = + event_create_passthrough(subm->event)-> + set_name("smtp_submit_finished"); + if (status > 0) + e_debug(e->event(), "Sent message successfully"); + else { + e->add_str("error", error); + e_debug(e->event(), "Failed to send message: %s", error); + } + + i_zero(&result); + result.status = status; + result.error = error; + + callback = subm->callback; + subm->callback = NULL; + callback(&result, subm->context); +} + +static void +smtp_submit_delayed_error_callback(struct smtp_submit *subm) +{ + smtp_submit_callback(subm, -1, subm->error); +} + +static void +smtp_submit_delayed_error(struct smtp_submit *subm, + const char *error) +{ + subm->status = -1; + subm->error = p_strdup(subm->pool, error); + subm->to_error = timeout_add_short(0, + smtp_submit_delayed_error_callback, subm); +} + +static void +smtp_submit_error(struct smtp_submit *subm, + int status, const char *error) +{ + const struct smtp_submit_settings *set = &subm->session->set; + i_assert(status <= 0); + if (subm->error != NULL) + return; + + subm->status = status; + subm->error = p_strdup_printf(subm->pool, + "smtp(%s): %s", + set->submission_host, error); +} + +static void +smtp_submit_success(struct smtp_submit *subm) +{ + if (subm->error != NULL) + return; + subm->status = 1; +} + +static void +smtp_submit_send_host_finished(struct smtp_submit *subm) +{ + i_assert(subm->status > 0 || subm->error != NULL); + smtp_submit_callback(subm, subm->status, subm->error); + subm->smtp_trans = NULL; +} + +static bool +reply_is_temp_fail(const struct smtp_reply *reply) +{ + return (smtp_reply_is_temp_fail(reply) || + !smtp_reply_is_remote(reply)); +} + +static void +rcpt_to_callback(const struct smtp_reply *reply, + struct smtp_submit *subm) +{ + if (!smtp_reply_is_success(reply)) { + smtp_submit_error(subm, + (reply_is_temp_fail(reply) ? -1 : 0), + t_strdup_printf("RCPT TO failed: %s", + smtp_reply_log(reply))); + } +} + +static void +data_callback(const struct smtp_reply *reply, + struct smtp_submit *subm) +{ + if (!smtp_reply_is_success(reply)) { + smtp_submit_error(subm, + (reply_is_temp_fail(reply) ? -1 : 0), + t_strdup_printf("DATA failed: %s", + smtp_reply_log(reply))); + return; + } + + smtp_submit_success(subm); +} + +static void +data_dummy_callback(const struct smtp_reply *reply ATTR_UNUSED, + struct smtp_submit *subm ATTR_UNUSED) +{ + /* nothing */ +} + +static void +smtp_submit_send_host(struct smtp_submit *subm) +{ + const struct smtp_submit_settings *set = &subm->session->set; + struct smtp_client_settings smtp_set; + struct smtp_client *smtp_client; + struct smtp_client_connection *smtp_conn; + struct smtp_client_transaction *smtp_trans; + enum smtp_client_connection_ssl_mode ssl_mode; + struct smtp_address *rcpt; + const char *host; + in_port_t port; + + if (net_str2hostport(set->submission_host, + DEFAULT_SUBMISSION_PORT, &host, &port) < 0) { + smtp_submit_delayed_error(subm, t_strdup_printf( + "Invalid submission_host: %s", host)); + return; + } + + i_zero(&smtp_set); + smtp_set.my_hostname = set->hostname; + smtp_set.connect_timeout_msecs = set->submission_timeout*1000; + smtp_set.command_timeout_msecs = set->submission_timeout*1000; + smtp_set.debug = set->mail_debug; + smtp_set.ssl = &subm->session->ssl_set; + smtp_set.event_parent = subm->event; + + ssl_mode = SMTP_CLIENT_SSL_MODE_NONE; + if (set->submission_ssl != NULL) { + if (strcasecmp(set->submission_ssl, "smtps") == 0 || + strcasecmp(set->submission_ssl, "submissions") == 0) + ssl_mode = SMTP_CLIENT_SSL_MODE_IMMEDIATE; + else if (strcasecmp(set->submission_ssl, "starttls") == 0) + ssl_mode = SMTP_CLIENT_SSL_MODE_STARTTLS; + } + + smtp_client = smtp_client_init(&smtp_set); + smtp_conn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, host, port, ssl_mode, NULL); + + smtp_trans = smtp_client_transaction_create(smtp_conn, + subm->mail_from, NULL, 0, smtp_submit_send_host_finished, subm); + smtp_client_connection_unref(&smtp_conn); + + array_foreach_elem(&subm->rcpt_to, rcpt) { + smtp_client_transaction_add_rcpt(smtp_trans, + rcpt, NULL, rcpt_to_callback, data_dummy_callback, subm); + } + + subm->smtp_client = smtp_client; + subm->smtp_trans = smtp_trans; + + smtp_client_transaction_send + (smtp_trans, subm->input, data_callback, subm); + i_stream_unref(&subm->input); +} + +static void +smtp_submit_sendmail_callback(enum program_client_exit_status status, + struct smtp_submit *subm) +{ + if (status == PROGRAM_CLIENT_EXIT_STATUS_INTERNAL_FAILURE) { + smtp_submit_callback(subm, -1, + "Failed to execute sendmail"); + return; + } + if (status == PROGRAM_CLIENT_EXIT_STATUS_FAILURE) { + smtp_submit_callback(subm, -1, + "Sendmail program returned error"); + return; + } + + smtp_submit_callback(subm, 1, NULL); +} + +static void +smtp_submit_send_sendmail(struct smtp_submit *subm) +{ + const struct smtp_submit_settings *set = &subm->session->set; + const char *const *sendmail_args, *sendmail_bin, *str; + ARRAY_TYPE(const_string) args; + struct smtp_address *rcpt; + unsigned int i; + struct program_client_settings pc_set; + struct program_client *pc; + + sendmail_args = t_strsplit(set->sendmail_path, " "); + t_array_init(&args, 16); + i_assert(sendmail_args[0] != NULL); + sendmail_bin = sendmail_args[0]; + for (i = 1; sendmail_args[i] != NULL; i++) + array_push_back(&args, &sendmail_args[i]); + + str = "-i"; array_push_back(&args, &str); /* ignore dots */ + str = "-f"; array_push_back(&args, &str); + str = !smtp_address_isnull(subm->mail_from) ? + smtp_address_encode(subm->mail_from) : "<>"; + array_push_back(&args, &str); + + str = "--"; array_push_back(&args, &str); + array_foreach_elem(&subm->rcpt_to, rcpt) { + const char *rcpt_encoded = smtp_address_encode(rcpt); + array_push_back(&args, &rcpt_encoded); + } + array_append_zero(&args); + + i_zero(&pc_set); + pc_set.client_connect_timeout_msecs = set->submission_timeout * 1000; + pc_set.input_idle_timeout_msecs = set->submission_timeout * 1000; + pc_set.debug = set->mail_debug; + pc_set.event = subm->event; + pc_set.allow_root = subm->session->allow_root; + restrict_access_init(&pc_set.restrict_set); + + pc = program_client_local_create + (sendmail_bin, array_front(&args), &pc_set); + + program_client_set_input(pc, subm->input); + i_stream_unref(&subm->input); + + subm->prg_client = pc; + + program_client_run_async(pc, smtp_submit_sendmail_callback, subm); +} + +struct smtp_submit_run_context { + int status; + char *error; +}; + +static void +smtp_submit_run_callback(const struct smtp_submit_result *result, + struct smtp_submit_run_context *rctx) +{ + rctx->error = i_strdup(result->error); + rctx->status = result->status; + io_loop_stop(current_ioloop); +} + +int smtp_submit_run(struct smtp_submit *subm, + const char **error_r) +{ + struct smtp_submit_run_context rctx; + struct ioloop *ioloop; + + ioloop = io_loop_create(); + io_loop_set_running(ioloop); + + i_zero(&rctx); + smtp_submit_run_async(subm, + smtp_submit_run_callback, &rctx); + + if (io_loop_is_running(ioloop)) + io_loop_run(ioloop); + + io_loop_destroy(&ioloop); + + if (rctx.error == NULL) + *error_r = NULL; + else { + *error_r = t_strdup(rctx.error); + i_free(rctx.error); + } + + return rctx.status; +} + +#undef smtp_submit_run_async +void smtp_submit_run_async(struct smtp_submit *subm, + smtp_submit_callback_t *callback, void *context) +{ + const struct smtp_submit_settings *set = &subm->session->set; + uoff_t data_size; + + subm->callback = callback; + subm->context = context; + + /* the mail has been written to a file. now actually send it. */ + subm->input = iostream_temp_finish + (&subm->output, IO_BLOCK_SIZE); + + if (i_stream_get_size(subm->input, TRUE, &data_size) > 0) + event_add_int(subm->event, "data_size", data_size); + + struct event_passthrough *e = + event_create_passthrough(subm->event)-> + set_name("smtp_submit_started"); + e_debug(e->event(), "Started sending message"); + + if (set->submission_host != NULL) { + smtp_submit_send_host(subm); + } else { + smtp_submit_send_sendmail(subm); + } +} diff --git a/src/lib-smtp/smtp-submit.h b/src/lib-smtp/smtp-submit.h new file mode 100644 index 0000000..6a00f56 --- /dev/null +++ b/src/lib-smtp/smtp-submit.h @@ -0,0 +1,73 @@ +#ifndef SMTP_SUBMIT_H +#define SMTP_SUBMIT_H + +#include "smtp-submit-settings.h" + +struct ssl_iostream_settings; +struct smtp_address; +struct smtp_submit_settings; +struct smtp_submit_session; +struct smtp_submit; + +struct smtp_submit_input { + /* SSL settings */ + const struct ssl_iostream_settings *ssl; + + /* Event to use as parent for the submit event */ + struct event *event_parent; + + /* Allow running sendmail as root */ + bool allow_root:1; +}; + +struct smtp_submit_result { + /* 1 on success, + 0 on permanent failure (e.g. invalid destination), + -1 on temporary failure */ + int status; + + const char *error; +}; + +typedef void +smtp_submit_callback_t(const struct smtp_submit_result *result, + void *context); + +/* Use submit session to reuse resources (e.g. SMTP connections) between + submissions (FIXME: actually implement this) */ +struct smtp_submit_session * +smtp_submit_session_init(const struct smtp_submit_input *input, + const struct smtp_submit_settings *set); +void smtp_submit_session_deinit(struct smtp_submit_session **_session); + +struct smtp_submit * +smtp_submit_init(struct smtp_submit_session *session, + const struct smtp_address *mail_from); +struct smtp_submit * +smtp_submit_init_simple(const struct smtp_submit_input *input, + const struct smtp_submit_settings *set, + const struct smtp_address *mail_from) ATTR_NULL(2); +void smtp_submit_deinit(struct smtp_submit **_submit); + +/* Add a new recipient */ +void smtp_submit_add_rcpt(struct smtp_submit *subm, + const struct smtp_address *rcpt_to); +/* Get an output stream where the message can be written to. The recipients + must already be added before calling this. */ +struct ostream *smtp_submit_send(struct smtp_submit *subm); + +/* Submit the message. Callback is called once the message submission + finishes. */ +void smtp_submit_run_async(struct smtp_submit *subm, + smtp_submit_callback_t *callback, void *context); +#define smtp_submit_run_async(subm, callback, context) \ + smtp_submit_run_async(subm, \ + (smtp_submit_callback_t*)callback, \ + (char*)context - CALLBACK_TYPECHECK(callback, \ + void (*)(const struct smtp_submit_result *result, typeof(context)))) + +/* Returns 1 on success, 0 on permanent failure (e.g. invalid destination), + -1 on temporary failure. */ +int smtp_submit_run(struct smtp_submit *subm, const char **error_r); + +#endif diff --git a/src/lib-smtp/smtp-syntax.c b/src/lib-smtp/smtp-syntax.c new file mode 100644 index 0000000..dc7b744 --- /dev/null +++ b/src/lib-smtp/smtp-syntax.c @@ -0,0 +1,327 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "smtp-parser.h" + +#include "smtp-syntax.h" + +#include + +/* + * String + */ + +int smtp_string_parse(const char *string, const char **value_r, + const char **error_r) +{ + struct smtp_parser parser; + + *value_r = NULL; + *error_r = NULL; + + if (string == NULL || *string == '\0') { + *value_r = ""; + return 0; + } + + smtp_parser_init(&parser, pool_datastack_create(), string); + + if (smtp_parser_parse_string(&parser, value_r) < 0) { + *error_r = parser.error; + return -1; + } + if (parser.cur < parser.end) { + *error_r = "Invalid character in string"; + return -1; + } + return 1; +} + +void smtp_string_write(string_t *out, const char *value) +{ + bool quoted = FALSE; + const unsigned char *p, *pend, *pblock; + size_t begin = str_len(out); + + if (value == NULL) + return; + p = (const unsigned char *)value; + pend = p + strlen(value); + while (p < pend) { + pblock = p; + while (p < pend && smtp_char_is_atext(*p)) + p++; + + if (!quoted && p < pend) { + quoted = TRUE; + str_insert(out, begin, "\""); + } + + str_append_data(out, pblock, p-pblock); + if (p >= pend) + break; + + i_assert(quoted); + i_assert(smtp_char_is_qpair(*p)); + + if (!smtp_char_is_qtext(*p)) + str_append_c(out, '\\'); + str_append_c(out, *p); + + p++; + } + + if (quoted) + str_append_c(out, '\"'); +} + +/* + * Xtext encoding + */ + +int smtp_xtext_decode(string_t *out, const char *xtext, bool allow_nul, + const char **error_r) +{ + struct smtp_parser parser; + + if (xtext == NULL || *xtext == '\0') + return 1; + + smtp_parser_init(&parser, pool_datastack_create(), xtext); + + if (smtp_parser_parse_xtext(&parser, out) < 0) { + *error_r = parser.error; + return -1; + } + if (parser.cur < parser.end) { + *error_r = "Invalid character in xtext"; + return -1; + } + if (!allow_nul && strlen(str_c(out)) != str_len(out)) { + *error_r = "Encountered NUL character in xtext"; + return -1; + } + return 1; +} + +int smtp_xtext_parse(const char *xtext, const char **value_r, + const char **error_r) +{ + string_t *value; + int ret; + + *value_r = NULL; + *error_r = NULL; + + if (xtext == NULL || *xtext == '\0') { + *value_r = ""; + return 1; + } + + value = t_str_new(256); + ret = smtp_xtext_decode(value, xtext, FALSE, error_r); + if (ret <= 0) + return ret; + + *value_r = str_c(value); + return 1; +} + +void smtp_xtext_encode(string_t *out, const unsigned char *data, size_t size) +{ + const unsigned char *p, *pbegin, *pend; + + p = data; + pend = p + size; + while (p < pend) { + pbegin = p; + while (p < pend && smtp_char_is_xtext(*p)) + p++; + + str_append_data(out, pbegin, p-pbegin); + if (p >= pend) + break; + + str_printfa(out, "+%02X", (unsigned int)*p); + p++; + } +} + +/* + * HELO domain + */ + +int smtp_helo_domain_parse(const char *helo, bool allow_literal, + const char **domain_r) +{ + struct smtp_parser parser; + int ret; + + smtp_parser_init(&parser, pool_datastack_create(), helo); + + ret = smtp_parser_parse_domain(&parser, domain_r); + if (ret == 0) { + if (allow_literal) { + ret = smtp_parser_parse_address_literal( + &parser, domain_r, NULL); + } + } + + if (ret <= 0 || (parser.cur < parser.end && *parser.cur != ' ')) + return -1; + return 0; +} + +/* + * EHLO reply + */ + +bool smtp_ehlo_keyword_is_valid(const char *keyword) +{ + const char *p; + + for (p = keyword; *p != '\0'; p++) { + if (!i_isalnum(*p)) + return FALSE; + } + return TRUE; +} + +bool smtp_ehlo_param_is_valid(const char *param) +{ + const char *p; + + for (p = param; *p != '\0'; p++) { + if (!smtp_char_is_ehlo_param(*p)) + return FALSE; + } + return TRUE; +} + +bool smtp_ehlo_params_are_valid(const char *const *params) +{ + if (params == NULL) + return TRUE; + + while (*params != NULL) { + if (!smtp_ehlo_param_is_valid(*params)) + return FALSE; + params++; + } + + return TRUE; +} + +bool smtp_ehlo_params_str_is_valid(const char *params) +{ + const char *p; + bool space = FALSE; + + for (p = params; *p != '\0'; p++) { + if (*p == ' ') { + if (space) + return FALSE; + space = TRUE; + continue; + } + space = FALSE; + + if (!smtp_char_is_ehlo_param(*p)) + return FALSE; + } + return TRUE; +} + +static int +smtp_parse_ehlo_line(struct smtp_parser *parser, const char **key_r, + const char *const **params_r) +{ + const unsigned char *pbegin = parser->cur; + ARRAY_TYPE(const_string) params = ARRAY_INIT; + const char *param; + + /* ehlo-line = ehlo-keyword *( SP ehlo-param ) + ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") + ; additional syntax of ehlo-params depends on + ; ehlo-keyword + ehlo-param = 1*(%d33-126) + ; any CHAR excluding and all + ; control characters (US-ASCII 0-31 and 127 + ; inclusive) + */ + + if (parser->cur >= parser->end || !i_isalnum(*parser->cur)) { + parser->error = "Unexpected character in EHLO keyword"; + return -1; + } + parser->cur++; + + while (parser->cur < parser->end && + (i_isalnum(*parser->cur) || *parser->cur == '-')) + parser->cur++; + + *key_r = p_strdup_until(parser->pool, pbegin, parser->cur); + + if (parser->cur >= parser->end) { + *params_r = p_new(parser->pool, const char *, 1); + return 1; + } + if (*parser->cur != ' ') { + parser->error = "Unexpected character in EHLO keyword"; + return -1; + } + parser->cur++; + + pbegin = parser->cur; + p_array_init(¶ms, parser->pool, 32); + while (parser->cur < parser->end) { + if (*parser->cur == ' ') { + if (parser->cur+1 >= parser->end || + *(parser->cur+1) == ' ') { + parser->error = + "Missing EHLO parameter after ' '"; + return -1; + } + param = p_strdup_until(parser->pool, pbegin, + parser->cur); + array_push_back(¶ms, ¶m); + pbegin = parser->cur + 1; + } else if (!smtp_char_is_ehlo_param(*parser->cur)) { + parser->error = + "Unexpected character in EHLO parameter"; + return -1; + } + parser->cur++; + } + + param = p_strdup_until(parser->pool, pbegin, parser->cur); + array_push_back(¶ms, ¶m); + array_append_zero(¶ms); + *params_r = array_front(¶ms); + return 1; +} + +int smtp_ehlo_line_parse(const char *ehlo_line, const char **key_r, + const char *const **params_r, const char **error_r) +{ + struct smtp_parser parser; + + *key_r = NULL; + *params_r = NULL; + *error_r = NULL; + + if (ehlo_line == NULL || *ehlo_line == '\0') { + *error_r = "Parameter is empty"; + return -1; + } + + smtp_parser_init(&parser, pool_datastack_create(), ehlo_line); + + if (smtp_parse_ehlo_line(&parser, key_r, params_r) <= 0) { + *error_r = parser.error; + return -1; + } + return 1; +} diff --git a/src/lib-smtp/smtp-syntax.h b/src/lib-smtp/smtp-syntax.h new file mode 100644 index 0000000..f9f960c --- /dev/null +++ b/src/lib-smtp/smtp-syntax.h @@ -0,0 +1,49 @@ +#ifndef SMTP_SYNTAX_H +#define SMTP_SYNTAX_H + +struct smtp_parser; + +/* + * String + */ + +int smtp_string_parse(const char *string, const char **value_r, + const char **error_r); +void smtp_string_write(string_t *out, const char *value); + +/* + * Xtext encoding + */ + +int smtp_xtext_decode(string_t *out, const char *xtext, bool allow_nul, + const char **error_r); +int smtp_xtext_parse(const char *xtext, const char **value_r, + const char **error_r); + +void smtp_xtext_encode(string_t *out, const unsigned char *data, size_t size); +static inline void +smtp_xtext_encode_cstr(string_t *out, const char *data) +{ + smtp_xtext_encode(out, (const unsigned char *)data, strlen(data)); +} + +/* + * HELO domain + */ + +int smtp_helo_domain_parse(const char *helo, bool allow_literal, + const char **domain_r); + +/* + * EHLO reply + */ + +bool smtp_ehlo_keyword_is_valid(const char *keyword); +bool smtp_ehlo_param_is_valid(const char *param); +bool smtp_ehlo_params_are_valid(const char *const *params) ATTR_NULL(1); +bool smtp_ehlo_params_str_is_valid(const char *params); + +int smtp_ehlo_line_parse(const char *ehlo_line, const char **key_r, + const char *const **params_r, const char **error_r); + +#endif diff --git a/src/lib-smtp/test-bin/sendmail-exit-1.sh b/src/lib-smtp/test-bin/sendmail-exit-1.sh new file mode 100755 index 0000000..11b5422 --- /dev/null +++ b/src/lib-smtp/test-bin/sendmail-exit-1.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cat > /dev/null +exit 1; diff --git a/src/lib-smtp/test-bin/sendmail-success.sh b/src/lib-smtp/test-bin/sendmail-success.sh new file mode 100755 index 0000000..bb09b2b --- /dev/null +++ b/src/lib-smtp/test-bin/sendmail-success.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cat > $1 +exit 0; diff --git a/src/lib-smtp/test-smtp-address.c b/src/lib-smtp/test-smtp-address.c new file mode 100644 index 0000000..f16ac4c --- /dev/null +++ b/src/lib-smtp/test-smtp-address.c @@ -0,0 +1,1382 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "test-common.h" +#include "smtp-address.h" + +/* + * Valid mailbox parse tests + */ + +struct valid_mailbox_parse_test { + const char *input, *output; + enum smtp_address_parse_flags flags; + + struct smtp_address address; +}; + +static const struct valid_mailbox_parse_test +valid_mailbox_parse_tests[] = { + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY, + .address = { .localpart = NULL, .domain = NULL }, + }, + { + .input = "user", + .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART, + .address = { .localpart = "user", .domain = NULL }, + }, + { + .input = "user@domain.tld", + .address = { .localpart = "user", .domain = "domain.tld" }, + }, + { + .input = "1234567890@domain.tld", + .address = { + .localpart = "1234567890", + .domain = "domain.tld" }, + }, + { + .input = "_______@domain.tld", + .address = { + .localpart = "_______", + .domain = "domain.tld" }, + }, + { + .input = "firstname.lastname@domain.tld", + .address = { + .localpart = "firstname.lastname", + .domain = "domain.tld" }, + }, + { + .input = "firstname+lastname@domain.tld", + .address = { + .localpart = "firstname+lastname", + .domain = "domain.tld" }, + }, + { + .input = "firstname-lastname@domain.tld", + .address = { + .localpart = "firstname-lastname", + .domain = "domain.tld" }, + }, + { + .input = "\"user\"@domain.tld", + .address = { .localpart = "user", .domain = "domain.tld" }, + .output = "user@domain.tld" + }, + { + .input = "\"user@frop\"@domain.tld", + .address = { .localpart = "user@frop", .domain = "domain.tld" }, + .output = "\"user@frop\"@domain.tld" + }, + { + .input = "user@127.0.0.1", + .address = { .localpart = "user", .domain = "127.0.0.1" }, + }, + { + .input = "user@[127.0.0.1]", + .address = { .localpart = "user", .domain = "[127.0.0.1]" }, + }, + { + .input = "user@[IPv6:::1]", + .address = { .localpart = "user", .domain = "[IPv6:::1]" }, + }, + { + .input = "user@[IPv6:::127.0.0.1]", + .address = { .localpart = "user", .domain = "[IPv6:::127.0.0.1]" }, + /* Japanese deviations */ + }, + { + .input = "email@-example.com", + .address = { .localpart = "email", .domain = "-example.com" }, + }, + { + .input = ".email@example.com", + .output = "\".email\"@example.com", + .address = { .localpart = ".email", .domain = "example.com" }, + }, + { + .input = "email.@example.com", + .output = "\"email.\"@example.com", + .address = { .localpart = "email.", .domain = "example.com" }, + }, + { + .input = "email..email@example.com", + .output = "\"email..email\"@example.com", + .address = { .localpart = "email..email", .domain = "example.com" }, + }, + { + .input = "Abc..123@example.com", + .output = "\"Abc..123\"@example.com", + .address = { .localpart = "Abc..123", .domain = "example.com" }, + }, + { + .input = "Abc..@example.com", + .output = "\"Abc..\"@example.com", + .address = { .localpart = "Abc..", .domain = "example.com" }, + }, +}; + +unsigned int valid_mailbox_parse_test_count = + N_ELEMENTS(valid_mailbox_parse_tests); + +static void +test_smtp_mailbox_equal(const struct smtp_address *test, + const struct smtp_address *parsed) +{ + if (parsed->localpart == NULL) { + test_out("address->localpart = (null)", + (parsed->localpart == test->localpart)); + } else { + test_out(t_strdup_printf("address->localpart = \"%s\"", + parsed->localpart), + null_strcmp(parsed->localpart, test->localpart) == 0); + } + if (parsed->domain == NULL) { + test_out(t_strdup_printf("address->domain = (null)"), + (parsed->domain == test->domain)); + } else { + test_out(t_strdup_printf("address->domain = \"%s\"", + parsed->domain), + null_strcmp(parsed->domain, test->domain) == 0); + } +} + +static void test_smtp_mailbox_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_mailbox_parse_test_count; i++) T_BEGIN { + const struct valid_mailbox_parse_test *test; + struct smtp_address *address; + const char *error = NULL, *output, *encoded; + int ret; + + test = &valid_mailbox_parse_tests[i]; + ret = smtp_address_parse_mailbox(pool_datastack_create(), + test->input, test->flags, + &address, &error); + + test_begin(t_strdup_printf("smtp mailbox valid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret == 0, error); + + if (!test_has_failed()) { + test_smtp_mailbox_equal(&test->address, address); + + encoded = smtp_address_encode(address); + output = (test->output == NULL ? + test->input : test->output); + test_out(t_strdup_printf("encode() = \"%s\"", encoded), + strcmp(encoded, output) == 0); + } + test_end(); + } T_END; +} + +/* + * Valid path parse tests + */ + +struct valid_path_parse_test { + const char *input, *output; + enum smtp_address_parse_flags flags; + + struct smtp_address address; +}; + +static const struct valid_path_parse_test +valid_path_parse_tests[] = { + { + .input = "<>", + .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY, + .address = { .localpart = NULL, .domain = NULL } + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART, + .address = { .localpart = "user", .domain = NULL } + }, + { + .input = "", + .address = { .localpart = "user", .domain = "domain.tld" } + }, + { + .input = "<@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld>", + .address = { .localpart = "user", .domain = "domain.tld" }, + .output = "" + }, + { + .input = "user@domain.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL, + .address = { .localpart = "user", .domain = "domain.tld" }, + .output = "" + }, + /* Raw */ + { + .input = "<>", + .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW, + .address = { .localpart = NULL, .domain = NULL, .raw = NULL } + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW, + .address = { .localpart = "user", .domain = NULL, + .raw = "user" } + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW, + .address = { .localpart = "user", .domain = "domain.tld", + .raw = "user@domain.tld" } + }, + { + .input = "<@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld>", + .flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW, + .address = { .localpart = "user", .domain = "domain.tld", + .raw = "@otherdomain.tld,@yetanotherdomain.tld:" + "user@domain.tld" }, + .output = "" + }, + { + .input = "user@domain.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW, + .address = { .localpart = "user", .domain = "domain.tld", + .raw = "user@domain.tld"}, + .output = "" + }, + /* Broken */ + { + .input = "<>", + .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = NULL, .domain = NULL, .raw = NULL } + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = "user", .domain = NULL, + .raw = "user" } + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = "user", .domain = "domain.tld", + .raw = "user@domain.tld" } + }, + { + .input = "<@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld>", + .flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = "user", .domain = "domain.tld", + .raw = "@otherdomain.tld,@yetanotherdomain.tld:" + "user@domain.tld" }, + .output = "" + }, + { + .input = "user@domain.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = "user", .domain = "domain.tld", + .raw = "user@domain.tld"}, + .output = "" + }, + { + .input = "u\"ser", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | + SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = NULL, .domain = NULL, + .raw = "u\"ser" }, + .output = "<>", + }, + { + .input = "user\"@domain.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = NULL, .domain = NULL, + .raw = "user\"@domain.tld" }, + .output = "<>", + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = NULL, .domain = NULL, + .raw = "u\"ser" }, + .output = "<>", + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = NULL, .domain = NULL, + .raw = "user\"@domain.tld" }, + .output = "<>", + }, + { + .input = "bla$die%bla@die&bla", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = NULL, .domain = NULL, + .raw = "bla$die%bla@die&bla" }, + .output = "<>", + }, + { + .input = "/@)$@)BLAARGH!@#$$", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = NULL, .domain = NULL, + .raw = "/@)$@)BLAARGH!@#$$" }, + .output = "<>", + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + .address = { .localpart = NULL, .domain = NULL, + .raw = "/@)$@)BLAARGH!@#$$" }, + .output = "<>", + }, + { + .input = "/@)$@)BLAARGH!@#$$", + .flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN | + SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL, + .address = { .localpart = NULL, .domain = NULL, + .raw = "/@)$@)BLAARGH!@#$$" }, + .output = "<>", + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN | + SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL, + .address = { .localpart = NULL, .domain = NULL, + .raw = "/@)$@)BLAARGH!@#$$" }, + .output = "<>", + }, + { + .input = "f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | + SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN | + SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL, + .address = { .localpart = NULL, .domain = NULL, + .raw = "f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4" }, + .output = "<>", + }, + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW | + SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN | + SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL, + .address = { .localpart = NULL, .domain = NULL, + .raw = "f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4" }, + .output = "<>", + }, +}; + +unsigned int valid_path_parse_test_count = + N_ELEMENTS(valid_path_parse_tests); + +static void +test_smtp_path_equal(const struct smtp_address *test, + const struct smtp_address *parsed) +{ + if (smtp_address_isnull(parsed) || smtp_address_isnull(test)) { + test_out("address = <>", + (smtp_address_isnull(parsed) && + smtp_address_isnull(test))); + } else { + test_out(t_strdup_printf("address->localpart = \"%s\"", + parsed->localpart), + null_strcmp(parsed->localpart, test->localpart) == 0); + } + if (smtp_address_isnull(parsed)) { + /* nothing */ + } else if (parsed->domain == NULL) { + test_out("address->domain = (null)", + (parsed->domain == test->domain)); + } else { + test_out(t_strdup_printf("address->domain = \"%s\"", + parsed->domain), + null_strcmp(parsed->domain, test->domain) == 0); + } + if (parsed == NULL) { + test_out_quiet(t_strdup_printf("address = (null)"), + (test->raw == NULL)); + } else if (parsed->raw == NULL) { + test_out_quiet(t_strdup_printf("address->raw = (null)"), + (parsed->raw == test->raw)); + } else { + test_out_quiet(t_strdup_printf("address->raw = \"%s\"", + parsed->raw), + null_strcmp(parsed->raw, test->raw) == 0); + } +} + +static void test_smtp_path_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_path_parse_test_count; i++) T_BEGIN { + const struct valid_path_parse_test *test; + bool ignore_broken; + struct smtp_address *address; + const char *error = NULL, *output, *encoded; + int ret; + + test = &valid_path_parse_tests[i]; + ignore_broken = HAS_ALL_BITS( + test->flags, SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN); + ret = smtp_address_parse_path(pool_datastack_create(), + test->input, test->flags, + &address, &error); + + test_begin(t_strdup_printf("smtp path valid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + (ret == 0 || ignore_broken), error); + + if (!test_has_failed()) { + test_smtp_path_equal(&test->address, address); + + encoded = smtp_address_encode_path(address); + output = (test->output == NULL ? + test->input : test->output); + test_out(t_strdup_printf("encode() = \"%s\"", encoded), + strcmp(encoded, output) == 0); + } + test_end(); + } T_END; +} + +/* + * Valid username parse tests + */ + +struct valid_username_parse_test { + const char *input, *output; + + struct smtp_address address; +}; + +static const struct valid_username_parse_test +valid_username_parse_tests[] = { + { + .input = "user", + .address = { + .localpart = "user", + .domain = NULL }, + }, + { + .input = "user@domain.tld", + .address = { + .localpart = "user", + .domain = "domain.tld" }, + }, + { + .input = "user@domain.tld", + .address = { + .localpart = "user", + .domain = "domain.tld" }, + }, + { + .input = "1234567890@domain.tld", + .address = { + .localpart = "1234567890", + .domain = "domain.tld" }, + }, + { + .input = "_______@domain.tld", + .address = { + .localpart = "_______", + .domain = "domain.tld" }, + }, + { + .input = "firstname.lastname@domain.tld", + .address = { + .localpart = "firstname.lastname", + .domain = "domain.tld" }, + }, + { + .input = "firstname+lastname@domain.tld", + .address = { + .localpart = "firstname+lastname", + .domain = "domain.tld" }, + }, + { + .input = "firstname-lastname@domain.tld", + .address = { + .localpart = "firstname-lastname", + .domain = "domain.tld" }, + }, + { + .input = "\"user\"@domain.tld", + .address = { .localpart = "user", .domain = "domain.tld" }, + .output = "user@domain.tld" + }, + { + .input = "\"user@frop\"@domain.tld", + .address = { .localpart = "user@frop", .domain = "domain.tld" }, + .output = "\"user@frop\"@domain.tld" + }, + { + .input = "user@frop@domain.tld", + .address = { .localpart = "user@frop", .domain = "domain.tld" }, + .output = "\"user@frop\"@domain.tld" + }, + { + .input = "user frop@domain.tld", + .address = { .localpart = "user frop", .domain = "domain.tld" }, + .output = "\"user frop\"@domain.tld" + }, + { + .input = "user\"frop@domain.tld", + .address = { .localpart = "user\"frop", .domain = "domain.tld" }, + .output = "\"user\\\"frop\"@domain.tld" + }, + { + .input = "user\\frop@domain.tld", + .address = { .localpart = "user\\frop", .domain = "domain.tld" }, + .output = "\"user\\\\frop\"@domain.tld" + }, + { + .input = "user@127.0.0.1", + .address = { .localpart = "user", .domain = "127.0.0.1" }, + }, + { + .input = "user@[127.0.0.1]", + .address = { .localpart = "user", .domain = "[127.0.0.1]" }, + }, + { + .input = "user@[IPv6:::1]", + .address = { .localpart = "user", .domain = "[IPv6:::1]" }, + }, + { + .input = "user@[IPv6:::127.0.0.1]", + .address = { .localpart = "user", .domain = "[IPv6:::127.0.0.1]" }, + }, +}; + +unsigned int valid_username_parse_test_count = + N_ELEMENTS(valid_username_parse_tests); + +static void test_smtp_username_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_username_parse_test_count; i++) T_BEGIN { + const struct valid_username_parse_test *test; + struct smtp_address *address; + const char *error = NULL, *output, *encoded; + int ret; + + test = &valid_username_parse_tests[i]; + ret = smtp_address_parse_username(pool_datastack_create(), + test->input, + &address, &error); + + test_begin(t_strdup_printf("smtp username valid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret == 0, error); + + if (!test_has_failed()) { + test_smtp_path_equal(&test->address, address); + + encoded = smtp_address_encode(address); + output = (test->output == NULL ? + test->input : test->output); + test_out(t_strdup_printf("encode() = \"%s\"", encoded), + strcmp(encoded, output) == 0); + } + test_end(); + } T_END; +} + +/* + * Invalid mailbox parse tests + */ + +struct invalid_mailbox_parse_test { + const char *input; + enum smtp_address_parse_flags flags; +}; + +static const struct invalid_mailbox_parse_test +invalid_mailbox_parse_tests[] = { + { + .input = "", + }, + { + .input = "user", + }, + { + .input = "\"user@domain.tld", + }, + { + .input = "us\"er@domain.tld", + }, + { + .input = "user@frop@domain.tld", + }, + { + .input = "user@.tld", + }, + { + .input = "user@a$.tld", + }, + { + .input = "user@a..tld", + }, + { + .input = "user@[]", + }, + { + .input = "user@[", + }, + { + .input = "user@[AA]", + }, + { + .input = "user@[AA", + }, + { + .input = "user@[127.0.0]", + }, + { + .input = "user@[256.256.256.256]", + }, + { + .input = "user@[127.0.0.1", + }, + { + .input = "user@[::1]", + }, + { + .input = "user@[IPv6:flierp]", + }, + { + .input = "user@[IPv6:aa:bb::cc::dd]", + }, + { + .input = "user@[IPv6::1]", + }, + { + .input = "user@[IPv6:::1", + }, + { + .input = "user@[Gen:]", + }, + { + .input = "user@[Gen:Hopsa", + }, + { + .input = "user@[Gen-:Hopsa]", + }, + { + .input = "#@%^%#$@#$@#.com", + }, + { + .input = "@example.com", + }, + { + .input = "Eric Mail ", + }, + { + .input = "email.example.com", + }, + { + .input = "email@example@example.com", + }, + { + .input = "あいうえお@example.com", + }, + { + .input = "email@example.com (Eric Mail)", + }, + { + .input = "email@example..com", +#if 0 /* These deviations are allowed (maybe implement strict mode) */ + }, + { + .input = "email@-example.com", + }, + { + .input = ".email@example.com", + }, + { + .input = "email.@example.com", + }, + { + .input = "email..email@example.com", + }, + { + .input = "Abc..123@example.com" +#endif + }, +}; + +unsigned int invalid_mailbox_parse_test_count = + N_ELEMENTS(invalid_mailbox_parse_tests); + +static void test_smtp_mailbox_parse_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_mailbox_parse_test_count; i++) T_BEGIN { + const struct invalid_mailbox_parse_test *test; + struct smtp_address *address; + const char *error = NULL; + int ret; + + test = &invalid_mailbox_parse_tests[i]; + ret = smtp_address_parse_mailbox(pool_datastack_create(), + test->input, test->flags, + &address, &error); + + test_begin(t_strdup_printf("smtp mailbox invalid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret < 0, error); + test_end(); + } T_END; +} + +/* + * Invalid path parse tests + */ + +struct invalid_path_parse_test { + const char *input; + enum smtp_address_parse_flags flags; +}; + +static const struct invalid_path_parse_test +invalid_path_parse_tests[] = { + { + .input = "", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "\"user@domain.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "us\"er@domain.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@frop@domain.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@a$.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@a..tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[AA]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[AA", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[127.0.0]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[256.256.256.256]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[127.0.0.1", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[::1]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[IPv6:flierp]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[IPv6:aa:bb::cc::dd]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[IPv6::1]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[IPv6:::1", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[Gen:]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[Gen:Hopsa", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@[Gen-:Hopsa]", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "#@%^%#$@#$@#.com", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "@example.com", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "Eric Mail ", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "email.example.com", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "email@example@example.com", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "あいうえお@example.com", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "email@example.com (Eric Mail)", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "email@example..com", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL + }, + { + .input = "user@domain.tld", + .flags = SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN, + }, + { + .input = "<>", + }, + { + .input = "", + }, + { + .input = "<\"user@domain.tld>", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "<#@%^%#$@#$@#.com>", + }, + { + .input = "<@example.com>", + }, + { + .input = "Eric Mail ", + }, + { + .input = "", + }, + { + .input = "", + }, + { + .input = "<あいうえお@example.com>", + }, + { + .input = " (Eric Mail)", + }, + { + .input = "", + }, + { + .input = "input, test->flags, + &address, &error); + + test_begin(t_strdup_printf("smtp path invalid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + (ret < 0 && !smtp_address_is_broken(address)), + error); + test_end(); + } T_END; +} + +/* + * Invalid username parse tests + */ + +struct invalid_username_parse_test { + const char *input; + enum smtp_address_parse_flags flags; +}; + +static const struct invalid_username_parse_test +invalid_username_parse_tests[] = { + { + .input = "frop@$%^$%^.tld", + }, + { + .input = "fr\top@domain.tld", + } +}; + +unsigned int invalid_username_parse_test_count = + N_ELEMENTS(invalid_username_parse_tests); + +static void test_smtp_username_parse_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_username_parse_test_count; i++) T_BEGIN { + const struct invalid_username_parse_test *test; + struct smtp_address *address; + const char *error = NULL; + int ret; + + test = &invalid_username_parse_tests[i]; + ret = smtp_address_parse_username(pool_datastack_create(), + test->input, + &address, &error); + + test_begin(t_strdup_printf("smtp username invalid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret < 0, error); + test_end(); + } T_END; +} + +/* + * Address detail parsing + */ + +struct address_detail_parse_test { + const char *delimiters; + const char *address; + const char *username; + const char *detail; + char delim; +}; + +static const struct address_detail_parse_test +address_detail_parse_tests[] = { + { "", "test", "test", "", '\0' }, + { "", "test+address", "test+address", "", '\0' }, + { "", "\"test:address\"", "test:address", "", '\0' }, + { "", "\"test-address:another+delim\"", "test-address:another+delim", + "", '\0' }, + { "", "test@domain", "test@domain", "", '\0' }, + { "", "test+address@domain", "test+address@domain", "", '\0' }, + { "", "\"test:address\"@domain", "test:address@domain", "", '\0' }, + { "", "\"test-address:another+delim\"@domain", + "test-address:another+delim@domain", "", '\0' }, + + { "+-:", "test", "test", "", '\0' }, + { "+-:", "test+address", "test", "address", '+' }, + { "+-:", "\"test:address\"", "test", "address", ':' }, + { "+-:", "\"test-address:another+delim\"", + "test", "address:another+delim", '-' }, + { "+-:", "test@domain", "test@domain", "", '\0' }, + { "+-:", "test+address@domain", "test@domain", "address", '+' }, + { "+-:", "\"test:address\"@domain", "test@domain", "address", ':' }, + { "+-:", "\"test-address:another+delim\"@domain", "test@domain", + "address:another+delim", '-' }, +}; + +unsigned int addresss_detail_parse_test_count = + N_ELEMENTS(address_detail_parse_tests); + +static void test_smtp_address_detail_parse(void) +{ + unsigned int i; + + + for (i = 0; i < N_ELEMENTS(address_detail_parse_tests); i++) T_BEGIN { + const struct address_detail_parse_test *test = + &address_detail_parse_tests[i]; + struct smtp_address *address; + const char *username, *detail, *error; + char delim; + int ret; + + test_begin(t_strdup_printf( + "smtp address detail parsing [%d]", i)); + + ret = smtp_address_parse_path( + pool_datastack_create(), test->address, + SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL, + &address, &error); + test_out_reason("address parse", ret == 0, error); + + if (!test_has_failed()) { + smtp_address_detail_parse_temp(test->delimiters, + address, &username, + &delim, &detail); + test_assert(strcmp(username, test->username) == 0); + test_assert(strcmp(detail, test->detail) == 0); + test_assert(delim == test->delim); + } + + test_end(); + } T_END; +} + +/* + * Skip address tests + */ + +struct any_address_parse_test { + const char *input; + const char *address; + size_t pos; + int ret; +}; + +static const struct any_address_parse_test +any_address_parse_tests[] = { + { + .input = "", + .address = "", + .pos = 0, + .ret = 0, + }, + { + .input = " ", + .address = "", + .pos = 0, + .ret = 0, + }, + { + .input = "frop@example.com", + .address = "frop@example.com", + .pos = 16, + .ret = 0, + }, + { + .input = "frop@example.com ", + .address = "frop@example.com", + .pos = 16, + .ret = 0, + }, + { + .input = "", + .address = "frop@example.com", + .pos = 18, + .ret = 0, + }, + { + .input = " ", + .address = "frop@example.com", + .pos = 18, + .ret = 0, + }, + { + .input = "op\"@example.com>", + .address = "\"fr>op\"@example.com", + .pos = 21, + .ret = 0, + }, + { + .input = "<\"fr>op\"@example.com> ", + .address = "\"fr>op\"@example.com", + .pos = 21, + .ret = 0, + }, + { + .input = "<\"fr>op\"@example.com", + .pos = 0, + .ret = -1, + }, + { + .input = "<\"fr>op\"@example.com ", + .pos = 0, + .ret = -1, + }, + { + .input = "<\"frop\">", + .address = "\"frop\"", + .pos = 8, + .ret = 0, + }, + { + .input = "<\"frop\"> ", + .address = "\"frop\"", + .pos = 8, + .ret = 0, + }, + { + .input = "<\"frop\"", + .pos = 0, + .ret = -1, + }, + { + .input = "<\"frop\" ", + .pos = 0, + .ret = -1, + }, + { + .input = "\"frop\\\" ", + .pos = 0, + .ret = -1, + }, + { + .input = "\"frop\\\"", + .pos = 0, + .ret = -1, + }, +}; + +unsigned int any_address_parse_tests_count = + N_ELEMENTS(any_address_parse_tests); + +static void test_smtp_parse_any_address(void) +{ + unsigned int i; + + for (i = 0; i < any_address_parse_tests_count; i++) T_BEGIN { + const struct any_address_parse_test *test; + const char *address = NULL, *pos = NULL; + int ret; + + test = &any_address_parse_tests[i]; + ret = smtp_address_parse_any(test->input, &address, &pos); + + test_begin(t_strdup_printf("smtp parse any [%d]", i)); + test_out_quiet(t_strdup_printf("parse(\"%s\")", + str_sanitize(test->input, 256)), + (ret == test->ret) && + ((size_t)(pos - test->input) == test->pos) && + (null_strcmp(test->address, address ) == 0)); + test_end(); + } T_END; +} + +/* + * Tests + */ + +int main(void) +{ + static void (*test_functions[])(void) = { + test_smtp_mailbox_parse_valid, + test_smtp_path_parse_valid, + test_smtp_username_parse_valid, + test_smtp_mailbox_parse_invalid, + test_smtp_path_parse_invalid, + test_smtp_username_parse_invalid, + test_smtp_address_detail_parse, + test_smtp_parse_any_address, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-smtp/test-smtp-client-errors.c b/src/lib-smtp/test-smtp-client-errors.c new file mode 100644 index 0000000..673ed04 --- /dev/null +++ b/src/lib-smtp/test-smtp-client-errors.c @@ -0,0 +1,4374 @@ +/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "hostpid.h" +#include "ioloop.h" +#include "istream.h" +#include "istream-dot.h" +#include "istream-chain.h" +#include "istream-failure-at.h" +#include "ostream.h" +#include "iostream-ssl.h" +#include "iostream-ssl-test.h" +#ifdef HAVE_OPENSSL +# include "iostream-openssl.h" +#endif +#include "time-util.h" +#include "sleep.h" +#include "connection.h" +#include "test-common.h" +#include "test-subprocess.h" +#include "smtp-client.h" +#include "smtp-client-connection.h" +#include "smtp-client-transaction.h" + +#include + +#define CLIENT_PROGRESS_TIMEOUT 10 +#define SERVER_KILL_TIMEOUT_SECS 20 + +static void main_deinit(void); + +/* + * Types + */ + +enum server_connection_state { + SERVER_CONNECTION_STATE_EHLO = 0, + SERVER_CONNECTION_STATE_MAIL_FROM, + SERVER_CONNECTION_STATE_RCPT_TO, + SERVER_CONNECTION_STATE_DATA, + SERVER_CONNECTION_STATE_FINISH +}; + +struct server_connection { + struct connection conn; + void *context; + + struct ssl_iostream *ssl_iostream; + + enum server_connection_state state; + char *file_path; + struct istream *dot_input; + + pool_t pool; + + bool version_sent:1; +}; + +typedef void (*test_server_init_t)(unsigned int index); +typedef bool +(*test_client_init_t)(const struct smtp_client_settings *client_set); +typedef void (*test_dns_init_t)(void); + +/* + * State + */ + +/* common */ +static struct ip_addr bind_ip; +static in_port_t *bind_ports = 0; +static struct ioloop *ioloop; +static bool debug = FALSE; + +/* server */ +static struct io *io_listen; +static int fd_listen = -1; +static struct connection_list *server_conn_list; +static unsigned int server_index; +struct ssl_iostream_context *server_ssl_ctx = NULL; +bool test_server_ssl = FALSE; +static void (*test_server_input)(struct server_connection *conn); +static int +(*test_server_input_line)(struct server_connection *conn, const char *line); +static int +(*test_server_input_data)(struct server_connection *conn, + const unsigned char *data, size_t size); +static int (*test_server_init)(struct server_connection *conn); +static void (*test_server_deinit)(struct server_connection *conn); + +/* client */ +static struct timeout *to_client_progress = NULL; +static struct smtp_client *smtp_client = NULL; + +/* + * Forward declarations + */ + +/* server */ +static void test_server_run(unsigned int index); +static void server_connection_deinit(struct server_connection **_conn); + +/* client */ +static void test_client_defaults(struct smtp_client_settings *smtp_set); +static void test_client_deinit(void); + +/* test*/ +static void +test_run_client_server(const struct smtp_client_settings *client_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count, + test_dns_init_t dns_test) ATTR_NULL(3); + +/* + * Unconfigured SSL + */ + +/* server */ + +static void +test_server_unconfigured_ssl_input(struct server_connection *conn ATTR_UNUSED) +{ + /* nothing */ +} + +static void test_server_unconfigured_ssl(unsigned int index) +{ + i_sleep_intr_secs(100); + test_server_input = test_server_unconfigured_ssl_input; + test_server_run(index); +} + +/* client */ + +struct _unconfigured_ssl { + unsigned int count; +}; + +static void +test_client_unconfigured_ssl_reply(const struct smtp_reply *reply, + void *context) +{ + struct _unconfigured_ssl *ctx = (struct _unconfigured_ssl *)context; + + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_unconfigured_ssl(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct _unconfigured_ssl *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _unconfigured_ssl, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "127.0.0.1", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, + test_client_unconfigured_ssl_reply, ctx); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "127.0.0.1", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect( + sconn, test_client_unconfigured_ssl_reply, ctx); + + return TRUE; +} + +/* test */ + +static void test_unconfigured_ssl(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("unconfigured ssl"); + test_run_client_server(&smtp_client_set, + test_client_unconfigured_ssl, + test_server_unconfigured_ssl, 1, NULL); + test_end(); +} + +/* + * Unconfigured SSL abort + */ + +/* server */ + +static void +test_server_unconfigured_ssl_abort_input( + struct server_connection *conn ATTR_UNUSED) +{ + /* nothing */ +} + +static void test_server_unconfigured_ssl_abort(unsigned int index) +{ + i_sleep_intr_secs(100); + test_server_input = test_server_unconfigured_ssl_abort_input; + test_server_run(index); +} + +/* client */ + +struct _unconfigured_ssl_abort { + unsigned int count; +}; + +static void +test_client_unconfigured_ssl_abort_reply1( + const struct smtp_reply *reply, + struct _unconfigured_ssl_abort *ctx ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_out_quiet("inappropriate callback", FALSE); +} + +static void +test_client_unconfigured_ssl_abort_reply2(const struct smtp_reply *reply, + struct _unconfigured_ssl_abort *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED); + + i_free(ctx); + io_loop_stop(ioloop); +} + +static bool +test_client_unconfigured_ssl_abort( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _unconfigured_ssl_abort *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _unconfigured_ssl_abort, 1); + ctx->count = 1; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "127.0.0.1", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_unconfigured_ssl_abort_reply1, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + smtp_client_command_abort(&scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "127.0.0.1", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_unconfigured_ssl_abort_reply2, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_unconfigured_ssl_abort(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("unconfigured ssl abort"); + test_run_client_server(&smtp_client_set, + test_client_unconfigured_ssl_abort, + test_server_unconfigured_ssl_abort, 1, NULL); + test_end(); +} + +/* + * Host lookup failed + */ + +/* client */ + +struct _host_lookup_failed { + unsigned int count; +}; + +static void +test_client_host_lookup_failed_reply(const struct smtp_reply *reply, + struct _host_lookup_failed *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_host_lookup_failed(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _host_lookup_failed *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _host_lookup_failed, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "host.in-addr.arpa", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_host_lookup_failed_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "host.in-addr.arpa", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_host_lookup_failed_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_host_lookup_failed(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("host lookup failed"); + test_run_client_server(&smtp_client_set, + test_client_host_lookup_failed, NULL, 0, NULL); + test_end(); +} + +/* + * Connection refused + */ + +/* server */ + +static void test_server_connection_refused(unsigned int index ATTR_UNUSED) +{ + i_close_fd(&fd_listen); +} + +/* client */ + +struct _connection_refused { + unsigned int count; +}; + +static void +test_client_connection_refused_reply(const struct smtp_reply *reply, + struct _connection_refused *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_refused(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _connection_refused *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _connection_refused, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_connection_refused_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_connection_refused_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_connection_refused(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("connection refused"); + test_run_client_server(&smtp_client_set, + test_client_connection_refused, + test_server_connection_refused, 1, NULL); + test_end(); +} + +/* + * Connection lost prematurely + */ + +/* server */ + +static void +test_connection_lost_prematurely_input(struct server_connection *conn) +{ + const char *line; + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof || + conn->conn.input->stream_errno != 0) { + server_connection_deinit(&conn); + } + return; + } + server_connection_deinit(&conn); +} + +static int test_connection_lost_prematurely_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Frop/GNU)\r\n"); + return 1; +} + +static void test_server_connection_lost_prematurely(unsigned int index) +{ + test_server_init = test_connection_lost_prematurely_init; + test_server_input = test_connection_lost_prematurely_input; + test_server_run(index); +} + +/* client */ + +struct _connection_lost_prematurely { + unsigned int count; +}; + +static void +test_client_connection_lost_prematurely_reply( + const struct smtp_reply *reply, + struct _connection_lost_prematurely *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_lost_prematurely( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _connection_lost_prematurely *ctx; + + ctx = i_new(struct _connection_lost_prematurely, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_connection_lost_prematurely_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_connection_lost_prematurely_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_connection_lost_prematurely(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("connection lost prematurely"); + test_run_client_server(&smtp_client_set, + test_client_connection_lost_prematurely, + test_server_connection_lost_prematurely, + 1, NULL); + test_end(); +} + +/* + * Connection timed out + */ + +/* server */ + +static void test_server_connection_timed_out(unsigned int index ATTR_UNUSED) +{ + i_sleep_intr_secs(10); +} + +/* client */ + +struct _connection_timed_out { + unsigned int count; +}; + +static void +test_client_connection_timed_out_reply(const struct smtp_reply *reply, + struct _connection_timed_out *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_timed_out(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _connection_timed_out *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _connection_timed_out, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_connection_timed_out_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_connection_timed_out_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_connection_timed_out(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.connect_timeout_msecs = 1000; + + test_begin("connection timed out"); + test_run_client_server(&smtp_client_set, + test_client_connection_timed_out, + test_server_connection_timed_out, 1, NULL); + test_end(); +} + +/* + * Broken payload + */ + +/* server */ + +static int +test_broken_payload_input_line(struct server_connection *conn ATTR_UNUSED, + const char *line ATTR_UNUSED) +{ + return 0; +} + +static void test_server_broken_payload(unsigned int index) +{ + test_server_input_line = test_broken_payload_input_line; + test_server_run(index); +} + +static int +test_broken_payload_chunking_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) { + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-CHUNKING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250 DSN\r\n"); + return 1; + } + return 0; +} + +static void test_server_broken_payload_chunking(unsigned int index) +{ + test_server_input_line = test_broken_payload_chunking_input_line; + test_server_run(index); +} + +/* client */ + +static void +test_client_broken_payload_rcpt_to_cb(const struct smtp_reply *reply, + void *context ATTR_UNUSED) +{ + test_assert(smtp_reply_is_success(reply)); +} + +static void +test_client_broken_payload_rcpt_data_cb(const struct smtp_reply *reply, + void *context ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_BROKEN_PAYLOAD); +} + +static void +test_client_broken_payload_data_cb(const struct smtp_reply *reply, + void *context ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_BROKEN_PAYLOAD); +} + +static void test_client_broken_payload_finished(void *context ATTR_UNUSED) +{ + io_loop_stop(ioloop); +} + +static bool +test_client_broken_payload(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_transaction *strans; + struct istream *input; + + test_expect_errors(2); + + input = i_stream_create_error_str(EIO, "Moehahahaha!!"); + i_stream_set_name(input, "PURE EVIL"); + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + strans = smtp_client_transaction_create( + sconn, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"}), NULL, 0, + test_client_broken_payload_finished, NULL); + smtp_client_connection_unref(&sconn); + + smtp_client_transaction_add_rcpt( + strans, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"}), NULL, + test_client_broken_payload_rcpt_to_cb, + test_client_broken_payload_rcpt_data_cb, NULL); + smtp_client_transaction_send( + strans, input, test_client_broken_payload_data_cb, NULL); + i_stream_unref(&input); + + return TRUE; +} + +static bool +test_client_broken_payload_later(const struct smtp_client_settings *client_set) +{ + static const char *message = + "From: lucifer@example.com\r\n" + "To: lostsoul@example.com\r\n" + "Subject: Moehahaha!\r\n" + "\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n"; + struct smtp_client_connection *sconn; + struct smtp_client_transaction *strans; + struct istream *input, *msg_input; + + test_expect_errors(1); + + msg_input = i_stream_create_from_data(message, strlen(message)); + input = i_stream_create_failure_at(msg_input, 666, + EIO, "Moehahahaha!!"); + i_stream_unref(&msg_input); + i_stream_set_name(input, "PURE EVIL"); + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + strans = smtp_client_transaction_create( + sconn, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"}), NULL, 0, + test_client_broken_payload_finished, NULL); + smtp_client_connection_unref(&sconn); + + smtp_client_transaction_add_rcpt( + strans, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"}), NULL, + test_client_broken_payload_rcpt_to_cb, + test_client_broken_payload_rcpt_data_cb, NULL); + smtp_client_transaction_send( + strans, input, test_client_broken_payload_data_cb, NULL); + i_stream_unref(&input); + + return TRUE; +} + +/* test */ + +static void test_broken_payload(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.connect_timeout_msecs = 1000; + + test_begin("broken payload"); + test_run_client_server(&smtp_client_set, + test_client_broken_payload, + test_server_broken_payload, 1, NULL); + test_end(); + + test_begin("broken payload (later)"); + test_run_client_server(&smtp_client_set, + test_client_broken_payload_later, + test_server_broken_payload, 1, NULL); + test_end(); + + test_begin("broken payload (later, chunking)"); + test_run_client_server(&smtp_client_set, + test_client_broken_payload_later, + test_server_broken_payload_chunking, 1, NULL); + test_end(); +} + +/* + * Connection lost + */ + +/* server */ + +static int +test_connection_lost_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + if (server_index == 0) { + conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; + i_sleep_intr_secs(1); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_MAIL_FROM: + if (server_index == 1) { + conn->state = SERVER_CONNECTION_STATE_RCPT_TO; + i_sleep_intr_secs(1); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_RCPT_TO: + if (server_index == 2) { + conn->state = SERVER_CONNECTION_STATE_DATA; + i_sleep_intr_secs(1); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_DATA: + if (server_index == 3) { + conn->state = SERVER_CONNECTION_STATE_FINISH; + i_sleep_intr_secs(1); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_FINISH: + break; + } + return 0; +} + +static int +test_connection_lost_input_data(struct server_connection *conn, + const unsigned char *data ATTR_UNUSED, + size_t size ATTR_UNUSED) +{ + i_sleep_intr_secs(1); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_connection_lost(unsigned int index) +{ + test_server_input_line = test_connection_lost_input_line; + test_server_input_data = test_connection_lost_input_data; + test_server_run(index); +} + +/* client */ + +struct _connection_lost { + unsigned int count; +}; + +struct _connection_lost_peer { + struct _connection_lost *context; + unsigned int index; +}; + +static void +test_client_connection_lost_rcpt_to_cb(const struct smtp_reply *reply, + struct _connection_lost_peer *pctx) +{ + if (debug) { + i_debug("RCPT TO REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + break; + case 1: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + break; + case 2: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + break; + case 3: + test_assert(smtp_reply_is_success(reply)); + break; + } +} + +static void +test_client_connection_lost_rcpt_data_cb(const struct smtp_reply *reply, + struct _connection_lost_peer *pctx) +{ + if (debug) { + i_debug("RCPT DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(FALSE); + break; + case 1: + test_assert(FALSE); + break; + case 2: + test_assert(FALSE); + break; + case 3: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + break; + } +} + +static void +test_client_connection_lost_data_cb(const struct smtp_reply *reply, + struct _connection_lost_peer *pctx) +{ + if (debug) { + i_debug("DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); +} + +static void +test_client_connection_lost_finished(struct _connection_lost_peer *pctx) +{ + struct _connection_lost *ctx = pctx->context; + + if (debug) + i_debug("FINISHED[%u]", pctx->index); + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } + i_free(pctx); +} + +static void +test_client_connection_lost_submit(struct _connection_lost *ctx, + unsigned int index) +{ + static const char *message = + "From: stephan@example.com\r\n" + "To: timo@example.com\r\n" + "Subject: Frop!\r\n" + "\r\n" + "Frop!\r\n"; + struct _connection_lost_peer *pctx; + struct smtp_client_connection *sconn; + struct smtp_client_transaction *strans; + struct istream *input; + + pctx = i_new(struct _connection_lost_peer, 1); + pctx->context = ctx; + pctx->index = index; + + input = i_stream_create_from_data(message, strlen(message)); + i_stream_set_name(input, "message"); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + strans = smtp_client_transaction_create( + sconn, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"}), NULL, 0, + test_client_connection_lost_finished, pctx); + smtp_client_connection_unref(&sconn); + + smtp_client_transaction_add_rcpt( + strans, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"}), NULL, + test_client_connection_lost_rcpt_to_cb, + test_client_connection_lost_rcpt_data_cb, pctx); + smtp_client_transaction_send( + strans, input, test_client_connection_lost_data_cb, pctx); + i_stream_unref(&input); +} + +static bool +test_client_connection_lost(const struct smtp_client_settings *client_set) +{ + struct _connection_lost *ctx; + unsigned int i; + + ctx = i_new(struct _connection_lost, 1); + ctx->count = 5; + + smtp_client = smtp_client_init(client_set); + + for (i = 0; i < ctx->count; i++) + test_client_connection_lost_submit(ctx, i); + + return TRUE; +} + +/* test */ + +static void test_connection_lost(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("connection lost"); + test_run_client_server(&smtp_client_set, + test_client_connection_lost, + test_server_connection_lost, 5, NULL); + test_end(); +} + +/* + * Unexpected reply + */ + +/* server */ + +static int +test_unexpected_reply_init(struct server_connection *conn) +{ + if (server_index == 5) { + o_stream_nsend_str(conn->conn.output, "220 testserver " + "ESMTP Testfix (Debian/GNU)\r\n"); + o_stream_nsend_str(conn->conn.output, "421 testserver " + "Server shutting down for maintenance\r\n"); + i_sleep_intr_secs(4); + server_connection_deinit(&conn); + return 1; + } + return 0; +} + +static int +test_unexpected_reply_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + if (server_index == 4) { + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250 DSN\r\n"); + o_stream_nsend_str( + conn->conn.output, "421 testserver " + "Server shutting down for maintenance\r\n"); + i_sleep_intr_secs(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_MAIL_FROM: + if (server_index == 3) { + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + o_stream_nsend_str( + conn->conn.output, "421 testserver " + "Server shutting down for maintenance\r\n"); + i_sleep_intr_secs(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_RCPT_TO: + if (server_index == 2) { + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + o_stream_nsend_str( + conn->conn.output, "421 testserver " + "Server shutting down for maintenance\r\n"); + i_sleep_intr_secs(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_DATA: + if (server_index == 1) { + o_stream_nsend_str( + conn->conn.output, + "354 End data with .\r\n"); + o_stream_nsend_str( + conn->conn.output, "421 testserver " + "Server shutting down for maintenance\r\n"); + i_sleep_intr_secs(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_FINISH: + break; + } + return 0; +} + +static void test_server_unexpected_reply(unsigned int index) +{ + test_server_init = test_unexpected_reply_init; + test_server_input_line = test_unexpected_reply_input_line; + test_server_run(index); +} + +/* client */ + +struct _unexpected_reply { + unsigned int count; +}; + +struct _unexpected_reply_peer { + struct _unexpected_reply *context; + unsigned int index; + + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; + struct timeout *to; + + bool login_callback:1; + bool mail_from_callback:1; + bool rcpt_to_callback:1; + bool rcpt_data_callback:1; + bool data_callback:1; +}; + +static void +test_client_unexpected_reply_login_cb(const struct smtp_reply *reply, + void *context) +{ + struct _unexpected_reply_peer *pctx = + (struct _unexpected_reply_peer *)context; + + pctx->login_callback = TRUE; + + if (debug) { + i_debug("LOGIN REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: case 1: case 2: case 3: case 4: + test_assert(smtp_reply_is_success(reply)); + break; + case 5: + test_assert(reply->status == 421); + break; + } +} + +static void +test_client_unexpected_reply_mail_from_cb(const struct smtp_reply *reply, + struct _unexpected_reply_peer *pctx) +{ + if (debug) { + i_debug("MAIL FROM REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->mail_from_callback = TRUE; + + switch (pctx->index) { + case 0: case 1: case 2: case 3: + test_assert(smtp_reply_is_success(reply)); + break; + case 4: case 5: + test_assert(reply->status == 421); + break; + } +} + +static void +test_client_unexpected_reply_rcpt_to_cb(const struct smtp_reply *reply, + struct _unexpected_reply_peer *pctx) +{ + if (debug) { + i_debug("RCPT TO REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->rcpt_to_callback = TRUE; + + switch (pctx->index) { + case 0: case 1: case 2: + test_assert(smtp_reply_is_success(reply)); + break; + case 3: case 4: case 5: + test_assert(reply->status == 421); + break; + } +} + +static void +test_client_unexpected_reply_rcpt_data_cb(const struct smtp_reply *reply, + struct _unexpected_reply_peer *pctx) +{ + if (debug) { + i_debug("RCPT DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->rcpt_data_callback = TRUE; + + switch (pctx->index) { + case 0: + test_assert(smtp_reply_is_success(reply)); + break; + case 1: case 2: + test_assert(reply->status == 421); + break; + case 3: case 4: case 5: + i_unreached(); + } +} + +static void +test_client_unexpected_reply_data_cb(const struct smtp_reply *reply, + struct _unexpected_reply_peer *pctx) +{ + if (debug) { + i_debug("DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->data_callback = TRUE; + + switch (pctx->index) { + case 0: + test_assert(smtp_reply_is_success(reply)); + break; + case 1: case 2: case 3: case 4: case 5: + test_assert(reply->status == 421); + break; + } +} + +static void +test_client_unexpected_reply_finished(struct _unexpected_reply_peer *pctx) +{ + struct _unexpected_reply *ctx = pctx->context; + + if (debug) + i_debug("FINISHED[%u]", pctx->index); + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } + + switch (pctx->index) { + case 0: case 1: case 2: + test_assert(pctx->mail_from_callback); + test_assert(pctx->rcpt_to_callback); + test_assert(pctx->rcpt_data_callback); + test_assert(pctx->data_callback); + break; + case 3: case 4: case 5: + test_assert(pctx->mail_from_callback); + test_assert(pctx->rcpt_to_callback); + test_assert(!pctx->rcpt_data_callback); + test_assert(pctx->data_callback); + break; + } + + pctx->trans = NULL; + timeout_remove(&pctx->to); + i_free(pctx); +} + +static void +test_client_unexpected_reply_submit2(struct _unexpected_reply_peer *pctx) +{ + struct smtp_client_transaction *strans = pctx->trans; + static const char *message = + "From: stephan@example.com\r\n" + "To: timo@example.com\r\n" + "Subject: Frop!\r\n" + "\r\n" + "Frop!\r\n"; + struct istream *input; + + timeout_remove(&pctx->to); + + input = i_stream_create_from_data(message, strlen(message)); + i_stream_set_name(input, "message"); + + smtp_client_transaction_send( + strans, input, test_client_unexpected_reply_data_cb, pctx); + i_stream_unref(&input); +} + +static void +test_client_unexpected_reply_submit1(struct _unexpected_reply_peer *pctx) +{ + timeout_remove(&pctx->to); + + smtp_client_transaction_add_rcpt( + pctx->trans, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"}), NULL, + test_client_unexpected_reply_rcpt_to_cb, + test_client_unexpected_reply_rcpt_data_cb, pctx); + + pctx->to = timeout_add_short( + 500, test_client_unexpected_reply_submit2, pctx); +} + +static void +test_client_unexpected_reply_submit(struct _unexpected_reply *ctx, + unsigned int index) +{ + struct _unexpected_reply_peer *pctx; + + pctx = i_new(struct _unexpected_reply_peer, 1); + pctx->context = ctx; + pctx->index = index; + + pctx->conn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + pctx->trans = smtp_client_transaction_create( + pctx->conn, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"}), NULL, 0, + test_client_unexpected_reply_finished, pctx); + smtp_client_connection_connect( + pctx->conn, test_client_unexpected_reply_login_cb, + (void *)pctx); + smtp_client_transaction_start( + pctx->trans, test_client_unexpected_reply_mail_from_cb, pctx); + smtp_client_connection_unref(&pctx->conn); + + pctx->to = timeout_add_short( + 500, test_client_unexpected_reply_submit1, pctx); +} + +static bool +test_client_unexpected_reply(const struct smtp_client_settings *client_set) +{ + struct _unexpected_reply *ctx; + unsigned int i; + + ctx = i_new(struct _unexpected_reply, 1); + ctx->count = 6; + + smtp_client = smtp_client_init(client_set); + + for (i = 0; i < ctx->count; i++) + test_client_unexpected_reply_submit(ctx, i); + + return TRUE; +} + +/* test */ + +static void test_unexpected_reply(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("unexpected reply"); + test_run_client_server(&smtp_client_set, + test_client_unexpected_reply, + test_server_unexpected_reply, 6, NULL); + test_end(); +} + +/* + * Partial reply + */ + +/* server */ + +static int +test_partial_reply_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + o_stream_nsend_str(conn->conn.output, + "500 Command not"); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_partial_reply(unsigned int index) +{ + test_server_input_line = test_partial_reply_input_line; + test_server_run(index); +} + +/* client */ + +struct _partial_reply { + unsigned int count; +}; + +static void +test_client_partial_reply_reply(const struct smtp_reply *reply, + struct _partial_reply *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_partial_reply(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _partial_reply *ctx; + + ctx = i_new(struct _partial_reply, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_partial_reply_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_partial_reply_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_partial_reply(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("partial reply"); + test_run_client_server(&smtp_client_set, + test_client_partial_reply, + test_server_partial_reply, 1, NULL); + test_end(); +} + +/* + * Premature reply + */ + +/* server */ + +static int +test_premature_reply_init(struct server_connection *conn) +{ + if (server_index == 5) { + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n" + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250 DSN\r\n"); + i_sleep_intr_secs(4); + server_connection_deinit(&conn); + return 1; + } + return 0; +} + +static int +test_premature_reply_input_line(struct server_connection *conn, const char *line) +{ + if (debug) + i_debug("[%u] GOT LINE: %s", server_index, line); + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + if (debug) + i_debug("[%u] EHLO", server_index); + if (server_index == 4) { + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250 DSN\r\n" + "250 2.1.0 Ok\r\n"); + conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; + return 1; + } + break; + case SERVER_CONNECTION_STATE_MAIL_FROM: + if (server_index == 4) { + conn->state = SERVER_CONNECTION_STATE_RCPT_TO; + return 1; + } + if (server_index == 3) { + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n" + "250 2.1.5 Ok\r\n"); + i_sleep_intr_secs(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_RCPT_TO: + if (server_index == 2) { + o_stream_nsend_str( + conn->conn.output, + "250 2.1.5 Ok\r\n" + "354 End data with .\r\n"); + i_sleep_intr_secs(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_DATA: + if (server_index == 1) { + o_stream_nsend_str( + conn->conn.output, + "354 End data with .\r\n" + "250 2.0.0 Ok: queued as 35424ed4af24\r\n"); + i_sleep_intr_secs(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_FINISH: + break; + } + return 0; +} + +static void test_server_premature_reply(unsigned int index) +{ + test_server_init = test_premature_reply_init; + test_server_input_line = test_premature_reply_input_line; + test_server_run(index); +} + +/* client */ + +struct _premature_reply { + unsigned int count; +}; + +struct _premature_reply_peer { + struct _premature_reply *context; + unsigned int index; + + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; + struct timeout *to; + + bool login_callback:1; + bool mail_from_callback:1; + bool rcpt_to_callback:1; + bool rcpt_data_callback:1; + bool data_callback:1; +}; + +static void +test_client_premature_reply_login_cb(const struct smtp_reply *reply, + void *context) +{ + struct _premature_reply_peer *pctx = + (struct _premature_reply_peer *)context; + + pctx->login_callback = TRUE; + + if (debug) { + i_debug("LOGIN REPLY[%u]: %s", pctx->index, + smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: case 1: case 2: case 3: case 4: + test_assert(smtp_reply_is_success(reply)); + break; + case 5: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + /* Don't bother continueing with this test. Second try after + smtp_client_transaction_start() will have the same result. */ + smtp_client_transaction_abort(pctx->trans); + break; + } +} + +static void +test_client_premature_reply_mail_from_cb(const struct smtp_reply *reply, + struct _premature_reply_peer *pctx) +{ + if (debug) { + i_debug("MAIL FROM REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->mail_from_callback = TRUE; + + switch (pctx->index) { + case 0: case 1: case 2: case 3: + test_assert(smtp_reply_is_success(reply)); + break; + case 4: case 5: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + } +} + +static void +test_client_premature_reply_rcpt_to_cb(const struct smtp_reply *reply, + struct _premature_reply_peer *pctx) +{ + if (debug) { + i_debug("RCPT TO REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->rcpt_to_callback = TRUE; + + switch (pctx->index) { + case 0: case 1: case 2: + test_assert(smtp_reply_is_success(reply)); + break; + case 3: case 4: case 5: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + } +} + +static void +test_client_premature_reply_rcpt_data_cb(const struct smtp_reply *reply, + struct _premature_reply_peer *pctx) +{ + if (debug) { + i_debug("RCPT DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->rcpt_data_callback = TRUE; + + switch (pctx->index) { + case 0: + test_assert(smtp_reply_is_success(reply)); + break; + case 1: case 2: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + case 3: case 4: case 5: + i_unreached(); + } +} + +static void +test_client_premature_reply_data_cb(const struct smtp_reply *reply, + struct _premature_reply_peer *pctx) +{ + if (debug) { + i_debug("DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->data_callback = TRUE; + + switch (pctx->index) { + case 0: + test_assert(smtp_reply_is_success(reply)); + break; + case 1: case 2: case 3: case 4: case 5: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + } +} + +static void +test_client_premature_reply_finished(struct _premature_reply_peer *pctx) +{ + struct _premature_reply *ctx = pctx->context; + + if (debug) + i_debug("FINISHED[%u]", pctx->index); + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } + + switch (pctx->index) { + case 0: case 1: case 2: + test_assert(pctx->mail_from_callback); + test_assert(pctx->rcpt_to_callback); + test_assert(pctx->rcpt_data_callback); + test_assert(pctx->data_callback); + break; + case 3: case 4: + test_assert(pctx->mail_from_callback); + test_assert(pctx->rcpt_to_callback); + test_assert(!pctx->rcpt_data_callback); + test_assert(pctx->data_callback); + break; + case 5: + test_assert(!pctx->mail_from_callback); + test_assert(!pctx->rcpt_to_callback); + test_assert(!pctx->rcpt_data_callback); + test_assert(!pctx->data_callback); + } + + pctx->trans = NULL; + timeout_remove(&pctx->to); + i_free(pctx); +} + +static void +test_client_premature_reply_submit3(struct _premature_reply_peer *pctx) +{ + struct smtp_client_transaction *strans = pctx->trans; + static const char *message = + "From: stephan@example.com\r\n" + "To: timo@example.com\r\n" + "Subject: Frop!\r\n" + "\r\n" + "Frop!\r\n"; + struct istream *input; + + timeout_remove(&pctx->to); + + if (debug) + i_debug("SUBMIT3[%u]", pctx->index); + + input = i_stream_create_from_data(message, strlen(message)); + i_stream_set_name(input, "message"); + + smtp_client_transaction_send( + strans, input, test_client_premature_reply_data_cb, pctx); + i_stream_unref(&input); +} + +static void +test_client_premature_reply_submit2(struct _premature_reply_peer *pctx) +{ + timeout_remove(&pctx->to); + + if (debug) + i_debug("SUBMIT2[%u]", pctx->index); + + smtp_client_transaction_add_rcpt( + pctx->trans, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"}), NULL, + test_client_premature_reply_rcpt_to_cb, + test_client_premature_reply_rcpt_data_cb, pctx); + + pctx->to = timeout_add_short( + 500, test_client_premature_reply_submit3, pctx); +} + +static void +test_client_premature_reply_submit1(struct _premature_reply_peer *pctx) +{ + timeout_remove(&pctx->to); + + if (debug) + i_debug("SUBMIT1[%u]", pctx->index); + + smtp_client_transaction_start( + pctx->trans, test_client_premature_reply_mail_from_cb, pctx); + + pctx->to = timeout_add_short( + 500, test_client_premature_reply_submit2, pctx); +} + +static void +test_client_premature_reply_submit(struct _premature_reply *ctx, + unsigned int index) +{ + struct _premature_reply_peer *pctx; + struct smtp_client_connection *conn; + + pctx = i_new(struct _premature_reply_peer, 1); + pctx->context = ctx; + pctx->index = index; + + pctx->conn = conn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + pctx->trans = smtp_client_transaction_create( + conn, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"}), NULL, 0, + test_client_premature_reply_finished, pctx); + smtp_client_connection_connect( + conn, test_client_premature_reply_login_cb, (void *)pctx); + smtp_client_connection_unref(&conn); + + pctx->to = timeout_add_short( + 500, test_client_premature_reply_submit1, pctx); +} + +static bool +test_client_premature_reply(const struct smtp_client_settings *client_set) +{ + struct _premature_reply *ctx; + unsigned int i; + + test_expect_errors(6); + + ctx = i_new(struct _premature_reply, 1); + ctx->count = 6; + + smtp_client = smtp_client_init(client_set); + + for (i = 0; i < ctx->count; i++) + test_client_premature_reply_submit(ctx, i); + + return TRUE; +} + +/* test */ + +static void test_premature_reply(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("premature reply"); + test_run_client_server(&smtp_client_set, + test_client_premature_reply, + test_server_premature_reply, 6, NULL); + test_end(); +} + +/* + * Early data reply + */ + +/* server */ + +static int +test_early_data_reply_input_line(struct server_connection *conn ATTR_UNUSED, + const char *line) +{ + if (debug) + i_debug("[%u] GOT LINE: %s", server_index, line); + + switch (conn->state) { + case SERVER_CONNECTION_STATE_DATA: + break; + default: + return 0; + } + + if ((uintptr_t)conn->context == 0) { + if (debug) + i_debug("[%u] REPLIED 354", server_index); + o_stream_nsend_str(conn->conn.output, + "354 End data with .\r\n"); + conn->context = (void*)1; + return 1; + } + + if (server_index == 2 && strcmp(line, ".") == 0) { + if (debug) + i_debug("[%u] FINISHED TRANSACTION", + server_index); + o_stream_nsend_str(conn->conn.output, + "250 2.0.0 Ok: queued as 73BDE342129\r\n"); + return 1; + } + + if ((uintptr_t)conn->context == 5 && server_index < 2) { + if (debug) + i_debug("[%u] FINISHED TRANSACTION EARLY", + server_index); + + if (server_index == 0) { + o_stream_nsend_str( + conn->conn.output, + "250 2.0.0 Ok: queued as 73BDE342129\r\n"); + } else { + o_stream_nsend_str( + conn->conn.output, + "452 4.3.1 Mail system full\r\n"); + } + } + if ((uintptr_t)conn->context > 5) { + o_stream_nsend_str(conn->conn.output, + "250 2.0.0 OK\r\n"); + return 1; + } + conn->context = (void*)(((uintptr_t)conn->context) + 1); + return 1; +} + +static void test_server_early_data_reply(unsigned int index) +{ + test_server_input_line = test_early_data_reply_input_line; + test_server_run(index); +} + +/* client */ + +struct _early_data_reply { + unsigned int count; +}; + +struct _early_data_reply_peer { + struct _early_data_reply *context; + unsigned int index; + + struct ostream *output; + + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; + struct timeout *to; + + bool data_callback:1; +}; + +static void +test_client_early_data_reply_submit1(struct _early_data_reply_peer *pctx); + +static void +test_client_early_data_reply_login_cb(const struct smtp_reply *reply, + void *context) +{ + struct _early_data_reply_peer *pctx = context; + + if (debug) { + i_debug("LOGIN REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + test_assert(smtp_reply_is_success(reply)); +} + +static void +test_client_early_data_reply_mail_from_cb(const struct smtp_reply *reply, + struct _early_data_reply_peer *pctx) +{ + if (debug) { + i_debug("MAIL FROM REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + test_assert(smtp_reply_is_success(reply)); +} + +static void +test_client_early_data_reply_rcpt_to_cb(const struct smtp_reply *reply, + struct _early_data_reply_peer *pctx) +{ + if (debug) { + i_debug("RCPT TO REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + test_assert(smtp_reply_is_success(reply)); + + pctx->to = timeout_add_short( + 1000, test_client_early_data_reply_submit1, pctx); +} + +static void +test_client_early_data_reply_rcpt_data_cb(const struct smtp_reply *reply, + struct _early_data_reply_peer *pctx) +{ + if (debug) { + i_debug("RCPT DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + case 1: + test_assert(reply->status == 452); + break; + case 2: + test_assert(smtp_reply_is_success(reply)); + break; + } +} + +static void +test_client_early_data_reply_data_cb(const struct smtp_reply *reply, + struct _early_data_reply_peer *pctx) +{ + if (debug) { + i_debug("DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->data_callback = TRUE; + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + case 1: + test_assert(reply->status == 452); + break; + case 2: + test_assert(smtp_reply_is_success(reply)); + break; + } +} + +static void +test_client_early_data_reply_noop_cb(const struct smtp_reply *reply, + struct _early_data_reply_peer *pctx) +{ + struct _early_data_reply *ctx = pctx->context; + + if (debug) { + i_debug("NOOP REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + case 1: + case 2: + test_assert(smtp_reply_is_success(reply)); + break; + } + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } + + test_assert(pctx->data_callback); + + pctx->trans = NULL; + timeout_remove(&pctx->to); + o_stream_destroy(&pctx->output); + smtp_client_connection_unref(&pctx->conn); + i_free(pctx); +} + +static void +test_client_early_data_reply_finished(struct _early_data_reply_peer *pctx) +{ + if (debug) + i_debug("FINISHED[%u]", pctx->index); + + /* Send NOOP command to check that connection is still viable. + */ + smtp_client_command_noop_submit( + pctx->conn, 0, + test_client_early_data_reply_noop_cb, pctx); +} + +static void +test_client_early_data_reply_submit1(struct _early_data_reply_peer *pctx) +{ + if (debug) + i_debug("FINISH DATA[%u]", pctx->index); + + timeout_remove(&pctx->to); + + if (o_stream_finish(pctx->output) < 0) { + i_error("Failed to finish output: %s", + o_stream_get_error(pctx->output)); + } + o_stream_destroy(&pctx->output); +} + +static void +test_client_early_data_reply_submit(struct _early_data_reply *ctx, + unsigned int index) +{ + struct _early_data_reply_peer *pctx; + struct smtp_client_connection *conn; + static const char *message = + "From: stephan@example.com\r\n" + "To: timo@example.com\r\n" + "Subject: Frop!\r\n" + "\r\n" + "Frop!\r\n"; + int pipefd[2]; + struct istream *input; + + pctx = i_new(struct _early_data_reply_peer, 1); + pctx->context = ctx; + pctx->index = index; + + if (pipe(pipefd) < 0) + i_fatal("Failed to create pipe: %m"); + + fd_set_nonblock(pipefd[0], TRUE); + fd_set_nonblock(pipefd[1], TRUE); + + input = i_stream_create_fd_autoclose(&pipefd[0], 1024); + pctx->output = o_stream_create_fd_autoclose(&pipefd[1], 1024); + + pctx->conn = conn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(conn, + test_client_early_data_reply_login_cb, (void *)pctx); + + pctx->trans = smtp_client_transaction_create( + conn, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"}), NULL, 0, + test_client_early_data_reply_finished, pctx); + smtp_client_transaction_add_rcpt( + pctx->trans, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"}), NULL, + test_client_early_data_reply_rcpt_to_cb, + test_client_early_data_reply_rcpt_data_cb, pctx); + smtp_client_transaction_start(pctx->trans, + test_client_early_data_reply_mail_from_cb, pctx); + + smtp_client_transaction_send( + pctx->trans, input, test_client_early_data_reply_data_cb, pctx); + i_stream_unref(&input); + + o_stream_nsend(pctx->output, message, strlen(message)); +} + +static bool +test_client_early_data_reply(const struct smtp_client_settings *client_set) +{ + struct _early_data_reply *ctx; + unsigned int i; + + test_expect_errors(2); + + ctx = i_new(struct _early_data_reply, 1); + ctx->count = 3; + + smtp_client = smtp_client_init(client_set); + + for (i = 0; i < ctx->count; i++) + test_client_early_data_reply_submit(ctx, i); + + return TRUE; +} + +/* test */ + +static void test_early_data_reply(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("early data reply"); + test_run_client_server(&smtp_client_set, + test_client_early_data_reply, + test_server_early_data_reply, 3, NULL); + test_end(); +} + +/* + * Bad reply + */ + +/* server */ + +static int +test_bad_reply_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + o_stream_nsend_str(conn->conn.output, + "666 Really bad reply\r\n"); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_bad_reply(unsigned int index) +{ + test_server_input_line = test_bad_reply_input_line; + test_server_run(index); +} + +/* client */ + +struct _bad_reply { + unsigned int count; +}; + +static void +test_client_bad_reply_reply(const struct smtp_reply *reply, + struct _bad_reply *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_bad_reply( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _bad_reply *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _bad_reply, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_bad_reply_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_bad_reply_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_bad_reply(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("bad reply"); + test_run_client_server(&smtp_client_set, + test_client_bad_reply, + test_server_bad_reply, 1, NULL); + test_end(); +} + +/* + * Bad greeting + */ + +/* server */ + +static int test_bad_greeting_init(struct server_connection *conn) +{ + switch (server_index) { + case 0: + o_stream_nsend_str(conn->conn.output, + "666 Mouhahahaha!!\r\n"); + break; + case 1: + o_stream_nsend_str(conn->conn.output, + "446 Not right now, sorry.\r\n"); + break; + case 2: + o_stream_nsend_str(conn->conn.output, + "233 Gimme all your mail, NOW!!\r\n"); + break; + } + server_connection_deinit(&conn); + return -1; +} + +static void test_server_bad_greeting(unsigned int index) +{ + test_server_init = test_bad_greeting_init; + test_server_run(index); +} + +/* client */ + +struct _bad_greeting { + unsigned int count; +}; + +struct _bad_greeting_peer { + struct _bad_greeting *context; + unsigned int index; +}; + +static void +test_client_bad_greeting_reply(const struct smtp_reply *reply, + struct _bad_greeting_peer *pctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + case 1: + test_assert(reply->status == 446); + break; + case 2: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + } + + if (--pctx->context->count == 0) { + i_free(pctx->context); + io_loop_stop(ioloop); + } + i_free(pctx); +} + +static void +test_client_bad_greeting_submit(struct _bad_greeting *ctx, unsigned int index) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _bad_greeting_peer *pctx; + + pctx = i_new(struct _bad_greeting_peer, 1); + pctx->context = ctx; + pctx->index = index; + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_bad_greeting_reply, pctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); +} + +static bool +test_client_bad_greeting(const struct smtp_client_settings *client_set) +{ + struct _bad_greeting *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _bad_greeting, 1); + ctx->count = 3; + + smtp_client = smtp_client_init(client_set); + + test_client_bad_greeting_submit(ctx, 0); + test_client_bad_greeting_submit(ctx, 1); + test_client_bad_greeting_submit(ctx, 2); + return TRUE; +} + +/* test */ + +static void test_bad_greeting(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("bad greeting"); + test_run_client_server(&smtp_client_set, + test_client_bad_greeting, + test_server_bad_greeting, 3, NULL); + test_end(); +} + +/* + * Command timeout + */ + +/* server */ + +static int +test_command_timed_out_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + i_sleep_intr_secs(10); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_command_timed_out(unsigned int index) +{ + test_server_input_line = test_command_timed_out_input_line; + test_server_run(index); +} + +/* client */ + +struct _command_timed_out { + unsigned int count; +}; + +static void +test_client_command_timed_out_reply(const struct smtp_reply *reply, + struct _command_timed_out *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_command_timed_out(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _command_timed_out *ctx; + + test_expect_errors(1); + + ctx = i_new(struct _command_timed_out, 1); + ctx->count = 1; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_command_timed_out_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_command_timed_out(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.command_timeout_msecs = 1000; + + test_begin("command timed out"); + test_run_client_server(&smtp_client_set, + test_client_command_timed_out, + test_server_command_timed_out, 1, NULL); + test_end(); +} + +/* + * Command aborted early + */ + +/* server */ + +static int +test_command_aborted_early_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + + i_sleep_intr_secs(1); + o_stream_nsend_str(conn->conn.output, "200 OK\r\n"); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_command_aborted_early(unsigned int index) +{ + test_server_input_line = test_command_aborted_early_input_line; + test_server_run(index); +} + +/* client */ + +struct _command_aborted_early { + struct smtp_client_command *cmd; + struct timeout *to; +}; + +static void +test_client_command_aborted_early_reply( + const struct smtp_reply *reply, + struct _command_aborted_early *ctx ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + /* abort does not trigger callback */ + test_assert(FALSE); +} + +static void +test_client_command_aborted_early_timeout(struct _command_aborted_early *ctx) +{ + timeout_remove(&ctx->to); + + if (ctx->cmd != NULL) { + if (debug) + i_debug("ABORT"); + + /* abort early */ + smtp_client_command_abort(&ctx->cmd); + + /* wait a little for server to actually respond to an + already aborted request */ + ctx->to = timeout_add_short( + 1000, test_client_command_aborted_early_timeout, ctx); + } else { + if (debug) + i_debug("FINISHED"); + + /* all done */ + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_command_aborted_early(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct _command_aborted_early *ctx; + + ctx = i_new(struct _command_aborted_early, 1); + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + ctx->cmd = smtp_client_command_new(sconn, 0, + test_client_command_aborted_early_reply, ctx); + smtp_client_command_write(ctx->cmd, "FROP"); + smtp_client_command_submit(ctx->cmd); + + ctx->to = timeout_add_short(500, + test_client_command_aborted_early_timeout, ctx); + + return TRUE; +} + +/* test */ + +static void test_command_aborted_early(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("command aborted early"); + test_run_client_server(&smtp_client_set, + test_client_command_aborted_early, + test_server_command_aborted_early, 1, NULL); + test_end(); +} + +/* + * Client deinit early + */ + +/* server */ + +static int +test_client_deinit_early_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + + i_sleep_intr_secs(1); + o_stream_nsend_str(conn->conn.output, "200 OK\r\n"); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_client_deinit_early(unsigned int index) +{ + test_server_input_line = test_client_deinit_early_input_line; + test_server_run(index); +} + +/* client */ + +struct _client_deinit_early { + struct smtp_client_command *cmd; + struct timeout *to; +}; + +static void +test_client_client_deinit_early_reply( + const struct smtp_reply *reply, + struct _client_deinit_early *ctx ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + /* abort does not trigger callback */ + test_assert(FALSE); +} + +static void +test_client_client_deinit_early_timeout(struct _client_deinit_early *ctx) +{ + timeout_remove(&ctx->to); + + /* deinit early */ + smtp_client_deinit(&smtp_client); + + /* all done */ + i_free(ctx); + io_loop_stop(ioloop); +} + +static bool +test_client_client_deinit_early(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct _client_deinit_early *ctx; + + ctx = i_new(struct _client_deinit_early, 1); + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + ctx->cmd = smtp_client_command_new( + sconn, 0, test_client_client_deinit_early_reply, ctx); + smtp_client_command_write(ctx->cmd, "FROP"); + smtp_client_command_submit(ctx->cmd); + + ctx->to = timeout_add_short( + 500, test_client_client_deinit_early_timeout, ctx); + + return TRUE; +} + +/* test */ + +static void test_client_deinit_early(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + + test_begin("client deinit early"); + test_run_client_server(&smtp_client_set, + test_client_client_deinit_early, + test_server_client_deinit_early, 1, NULL); + test_end(); +} + +/* + * DNS service failure + */ + +/* client */ + +struct _dns_service_failure { + unsigned int count; +}; + +static void +test_client_dns_service_failure_reply(const struct smtp_reply *reply, + struct _dns_service_failure *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_dns_service_failure(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _dns_service_failure *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _dns_service_failure, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "host.in-addr.arpa", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_dns_service_failure_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "host.in-addr.arpa", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_dns_service_failure_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_dns_service_failure(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.dns_client_socket_path = "./frop"; + + test_begin("dns service failure"); + test_run_client_server(&smtp_client_set, + test_client_dns_service_failure, NULL, 0, NULL); + test_end(); +} + +/* + * DNS timeout + */ + +/* dns */ + +static void test_dns_timeout_input(struct server_connection *conn ATTR_UNUSED) +{ + /* hang */ + i_sleep_intr_secs(100); + + io_loop_stop(current_ioloop); + io_remove(&io_listen); + i_close_fd(&fd_listen); + server_connection_deinit(&conn); +} + +static void test_dns_dns_timeout(void) +{ + test_server_input = test_dns_timeout_input; + test_server_run(0); +} + +/* client */ + +struct _dns_timeout { + unsigned int count; +}; + +static void +test_client_dns_timeout_reply(const struct smtp_reply *reply, + struct _dns_timeout *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_dns_timeout(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _dns_timeout *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _dns_timeout, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "example.com", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_dns_timeout_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "example.com", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_dns_timeout_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_dns_timeout(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.connect_timeout_msecs = 2000; + smtp_client_set.dns_client_socket_path = "./dns-test"; + + test_begin("dns timeout"); + test_run_client_server(&smtp_client_set, + test_client_dns_timeout, NULL, 0, + test_dns_dns_timeout); + test_end(); +} + +/* + * DNS lookup failure + */ + +/* dns */ + +static void +test_dns_lookup_failure_input(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + t_strdup_printf("VERSION\tdns\t1\t0\n%d\tFAIL\n", EAI_FAIL)); + server_connection_deinit(&conn); +} + +static void test_dns_dns_lookup_failure(void) +{ + test_server_input = test_dns_lookup_failure_input; + test_server_run(0); +} + +/* client */ + +struct _dns_lookup_failure { + unsigned int count; +}; + +static void +test_client_dns_lookup_failure_reply(const struct smtp_reply *reply, + struct _dns_lookup_failure *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_dns_lookup_failure(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _dns_lookup_failure *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _dns_lookup_failure, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "example.com", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_dns_lookup_failure_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "example.com", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_dns_lookup_failure_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_dns_lookup_failure(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.dns_client_socket_path = "./dns-test"; + + test_begin("dns lookup failure"); + test_run_client_server(&smtp_client_set, + test_client_dns_lookup_failure, NULL, 0, + test_dns_dns_lookup_failure); + test_end(); +} + +/* + * Authentication failed + */ + +/* server */ + +static int +test_authentication_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + if (server_index > 0) { + o_stream_nsend_str( + conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-AUTH PLAIN\r\n" + "250 DSN\r\n"); + conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; + return 1; + } + break; + case SERVER_CONNECTION_STATE_MAIL_FROM: + switch (server_index ) { + case 1: + o_stream_nsend_str( + conn->conn.output, + "535 5.7.8 " + "Authentication credentials invalid\r\n"); + i_sleep_intr_secs(10); + server_connection_deinit(&conn); + return -1; + case 3: case 5: + if (str_begins(line, "AUTH ")) { + o_stream_nsend_str(conn->conn.output, + "334 \r\n"); + return 1; + } + if (str_begins(line, "EHLO ")) { + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-AUTH PLAIN\r\n" + "250 DSN\r\n"); + return 1; + } + if (!str_begins(line, "MAIL ")) { + o_stream_nsend_str( + conn->conn.output, "235 2.7.0 " + "Authentication successful\r\n"); + return 1; + } + } + break; + default: + break; + } + return 0; +} + +static void test_server_authentication(unsigned int index) +{ + test_server_input_line = test_authentication_input_line; + test_server_run(index); +} + +/* client */ + +struct _authentication { + unsigned int count; +}; + +struct _authentication_peer { + struct _authentication *context; + unsigned int index; + + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; +}; + +static void +test_client_authentication_login_cb(const struct smtp_reply *reply, + void *context) +{ + struct _authentication_peer *pctx = + (struct _authentication_peer *)context; + + if (debug) { + i_debug("LOGIN REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED); + break; + case 1: + test_assert(reply->status == 535); + break; + case 2: case 3: case 4: case 5: + test_assert(reply->status == 250); + break; + } +} + +static void +test_client_authentication_mail_from_cb( + const struct smtp_reply *reply, + struct _authentication_peer *pctx) +{ + if (debug) { + i_debug("MAIL FROM REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED); + break; + case 1: + test_assert(reply->status == 535); + break; + case 2: case 3: case 4: case 5: + test_assert(reply->status == 250); + break; + } +} + +static void +test_client_authentication_rcpt_to_cb( + const struct smtp_reply *reply, + struct _authentication_peer *pctx) +{ + if (debug) { + i_debug("RCPT TO REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED); + break; + case 1: + test_assert(reply->status == 535); + break; + case 2: case 3: case 4: case 5: + test_assert(reply->status == 250); + break; + } +} + +static void +test_client_authentication_rcpt_data_cb( + const struct smtp_reply *reply, + struct _authentication_peer *pctx) +{ + if (debug) { + i_debug("RCPT DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + + switch (pctx->index) { + case 0: + case 1: + test_assert(FALSE); + break; + case 2: case 3: case 4: case 5: + test_assert(TRUE); + break; + } +} + +static void +test_client_authentication_data_cb( + const struct smtp_reply *reply, + struct _authentication_peer *pctx) +{ + if (debug) { + i_debug("DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED); + break; + case 1: + test_assert(reply->status == 535); + break; + case 2: case 3: case 4: case 5: + test_assert(reply->status == 250); + break; + } +} + +static void +test_client_authentication_finished( + struct _authentication_peer *pctx) +{ + struct _authentication *ctx = pctx->context; + + if (debug) + i_debug("FINISHED[%u]", pctx->index); + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } + + pctx->trans = NULL; + i_free(pctx); +} + +static void +test_client_authentication_submit(struct _authentication *ctx, + unsigned int index) +{ + struct _authentication_peer *pctx; + struct smtp_client_settings smtp_set; + static const char *message = + "From: stephan@example.com\r\n" + "To: timo@example.com\r\n" + "Subject: Frop!\r\n" + "\r\n" + "Frop!\r\n"; + struct istream *input; + + pctx = i_new(struct _authentication_peer, 1); + pctx->context = ctx; + pctx->index = index; + + i_zero(&smtp_set); + smtp_set.username = "peter.wolfsen"; + + switch (index) { + case 3: /* Much too large for initial response */ + smtp_set.password = + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef"; + break; + case 4: /* Just small enough for initial response */ + smtp_set.password = + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "01234"; + break; + case 5: /* Just too large for initial response */ + smtp_set.password = + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "0123456789abcdef0123456789abcdef" + "012345"; + break; + default: + smtp_set.password = "crybaby"; + break; + } + + pctx->conn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, &smtp_set); + pctx->trans = smtp_client_transaction_create( + pctx->conn, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"}), NULL, 0, + test_client_authentication_finished, pctx); + smtp_client_connection_connect( + pctx->conn, test_client_authentication_login_cb, + (void *)pctx); + smtp_client_transaction_start( + pctx->trans, test_client_authentication_mail_from_cb, + pctx); + smtp_client_connection_unref(&pctx->conn); + + smtp_client_transaction_add_rcpt( + pctx->trans, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"}), NULL, + test_client_authentication_rcpt_to_cb, + test_client_authentication_rcpt_data_cb, pctx); + + input = i_stream_create_from_data(message, strlen(message)); + i_stream_set_name(input, "message"); + + smtp_client_transaction_send( + pctx->trans, input, + test_client_authentication_data_cb, pctx); + i_stream_unref(&input); +} + +static bool +test_client_authentication(const struct smtp_client_settings *client_set) +{ + struct _authentication *ctx; + unsigned int i; + + test_expect_errors(2); + + ctx = i_new(struct _authentication, 1); + ctx->count = 6; + + smtp_client = smtp_client_init(client_set); + + for (i = 0; i < ctx->count; i++) + test_client_authentication_submit(ctx, i); + + return TRUE; +} + +/* test */ + +static void test_authentication(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("authentication"); + test_run_client_server(&smtp_client_set, + test_client_authentication, + test_server_authentication, 6, NULL); + test_end(); +} + +/* + * Transaction timeout + */ + +/* server */ + +static int +test_transaction_timeout_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + break; + case SERVER_CONNECTION_STATE_MAIL_FROM: + if (server_index == 0) + i_sleep_intr_secs(20); + break; + case SERVER_CONNECTION_STATE_RCPT_TO: + if (server_index == 1) + i_sleep_intr_secs(20); + break; + case SERVER_CONNECTION_STATE_DATA: + if (server_index == 2) + i_sleep_intr_secs(20); + break; + case SERVER_CONNECTION_STATE_FINISH: + break; + } + return 0; +} + +static void test_server_transaction_timeout(unsigned int index) +{ + test_expect_errors(1); + test_server_input_line = test_transaction_timeout_input_line; + test_server_run(index); +} + +/* client */ + +struct _transaction_timeout { + unsigned int count; +}; + +struct _transaction_timeout_peer { + struct _transaction_timeout *context; + unsigned int index; + + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; + struct timeout *to; + + bool login_callback:1; + bool mail_from_callback:1; + bool rcpt_to_callback:1; + bool rcpt_data_callback:1; + bool data_callback:1; +}; + +static void +test_client_transaction_timeout_mail_from_cb( + const struct smtp_reply *reply, + struct _transaction_timeout_peer *pctx) +{ + if (debug) { + i_debug("MAIL FROM REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->mail_from_callback = TRUE; + + switch (pctx->index) { + case 0: + test_assert(reply->status == 451); + break; + case 1: case 2: case 3: + test_assert(smtp_reply_is_success(reply)); + break; + } +} + +static void +test_client_transaction_timeout_rcpt_to_cb( + const struct smtp_reply *reply, struct _transaction_timeout_peer *pctx) +{ + if (debug) { + i_debug("RCPT TO REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->rcpt_to_callback = TRUE; + + switch (pctx->index) { + case 0: case 1: + test_assert(reply->status == 451); + break; + case 2: case 3: + test_assert(smtp_reply_is_success(reply)); + break; + } +} + +static void +test_client_transaction_timeout_rcpt_data_cb( + const struct smtp_reply *reply, struct _transaction_timeout_peer *pctx) +{ + if (debug) { + i_debug("RCPT DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->rcpt_data_callback = TRUE; + + switch (pctx->index) { + case 0: case 1: + i_unreached(); + case 2: + test_assert(reply->status == 451); + break; + case 3: + test_assert(smtp_reply_is_success(reply)); + break; + } +} + +static void +test_client_transaction_timeout_data_cb(const struct smtp_reply *reply, + struct _transaction_timeout_peer *pctx) +{ + if (debug) { + i_debug("DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + pctx->data_callback = TRUE; + + switch (pctx->index) { + case 0: case 1: case 2: + test_assert(reply->status == 451); + break; + case 3: + test_assert(smtp_reply_is_success(reply)); + break; + } +} + +static void +test_client_transaction_timeout_finished(struct _transaction_timeout_peer *pctx) +{ + struct _transaction_timeout *ctx = pctx->context; + + if (debug) + i_debug("FINISHED[%u]", pctx->index); + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } + + switch (pctx->index) { + case 0: case 1: + test_assert(pctx->mail_from_callback); + test_assert(pctx->rcpt_to_callback); + test_assert(!pctx->rcpt_data_callback); + test_assert(pctx->data_callback); + break; + case 2: case 3: + test_assert(pctx->mail_from_callback); + test_assert(pctx->rcpt_to_callback); + test_assert(pctx->rcpt_data_callback); + test_assert(pctx->data_callback); + break; + } + + pctx->trans = NULL; + timeout_remove(&pctx->to); + i_free(pctx); +} + +static void +test_client_transaction_timeout_submit2(struct _transaction_timeout_peer *pctx) +{ + struct smtp_client_transaction *strans = pctx->trans; + static const char *message = + "From: stephan@example.com\r\n" + "To: timo@example.com\r\n" + "Subject: Frop!\r\n" + "\r\n" + "Frop!\r\n"; + struct istream *input; + + timeout_remove(&pctx->to); + + input = i_stream_create_from_data(message, strlen(message)); + i_stream_set_name(input, "message"); + + smtp_client_transaction_send( + strans, input, test_client_transaction_timeout_data_cb, pctx); + i_stream_unref(&input); +} + +static void +test_client_transaction_timeout_submit1(struct _transaction_timeout_peer *pctx) +{ + timeout_remove(&pctx->to); + + smtp_client_transaction_add_rcpt( + pctx->trans, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"}), NULL, + test_client_transaction_timeout_rcpt_to_cb, + test_client_transaction_timeout_rcpt_data_cb, pctx); + + pctx->to = timeout_add_short( + 500, test_client_transaction_timeout_submit2, pctx); +} + +static void +test_client_transaction_timeout_submit(struct _transaction_timeout *ctx, + unsigned int index) +{ + struct _transaction_timeout_peer *pctx; + + pctx = i_new(struct _transaction_timeout_peer, 1); + pctx->context = ctx; + pctx->index = index; + + pctx->conn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, + net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + pctx->trans = smtp_client_transaction_create( + pctx->conn, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"}), NULL, 0, + test_client_transaction_timeout_finished, pctx); + smtp_client_transaction_set_timeout(pctx->trans, 1000); + smtp_client_transaction_start( + pctx->trans, test_client_transaction_timeout_mail_from_cb, + pctx); + smtp_client_connection_unref(&pctx->conn); + + pctx->to = timeout_add_short( + 500, test_client_transaction_timeout_submit1, pctx); +} + +static bool +test_client_transaction_timeout(const struct smtp_client_settings *client_set) +{ + struct _transaction_timeout *ctx; + unsigned int i; + + ctx = i_new(struct _transaction_timeout, 1); + ctx->count = 4; + + smtp_client = smtp_client_init(client_set); + + for (i = 0; i < ctx->count; i++) + test_client_transaction_timeout_submit(ctx, i); + + return TRUE; +} + +/* test */ + +static void test_transaction_timeout(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("transaction timeout"); + test_run_client_server(&smtp_client_set, + test_client_transaction_timeout, + test_server_transaction_timeout, 6, NULL); + test_end(); +} + +/* + * Invalid SSL certificate + */ + +#ifdef HAVE_OPENSSL + +/* dns */ + +static void +test_dns_invalid_ssl_certificate_input(struct server_connection *conn) +{ + const char *line; + + if (!conn->version_sent) { + conn->version_sent = TRUE; + o_stream_nsend_str(conn->conn.output, "VERSION\tdns\t1\t0\n"); + } + + + while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { + if (debug) + i_debug("DNS REQUEST: %s", line); + + o_stream_nsend_str(conn->conn.output, + t_strdup_printf("0\t%s\n", + net_ip2addr(&bind_ip))); + } +} + +static void test_dns_invalid_ssl_certificate(void) +{ + test_server_input = test_dns_invalid_ssl_certificate_input; + test_server_run(0); +} + +/* server */ + +static void +test_invalid_ssl_certificate_input(struct server_connection *conn) +{ + const char *line; + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + server_connection_deinit(&conn); +} + +static int +test_invalid_ssl_certificate_init(struct server_connection *conn) +{ + sleep(1); + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Frop/GNU)\r\n"); + return 1; +} + +static void test_server_invalid_ssl_certificate(unsigned int index) +{ + test_server_ssl = TRUE; + test_server_init = test_invalid_ssl_certificate_init; + test_server_input = test_invalid_ssl_certificate_input; + test_server_run(index); +} + +/* client */ + +struct _invalid_ssl_certificate { + unsigned int count; +}; + +static void +test_client_invalid_ssl_certificate_reply(const struct smtp_reply *reply, + struct _invalid_ssl_certificate *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_invalid_ssl_certificate( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _invalid_ssl_certificate *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _invalid_ssl_certificate, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "example.com", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_invalid_ssl_certificate_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create( + smtp_client, SMTP_PROTOCOL_SMTP, "example.com", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new( + sconn, 0, test_client_invalid_ssl_certificate_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_invalid_ssl_certificate(void) +{ + struct smtp_client_settings smtp_client_set; + struct ssl_iostream_settings ssl_set; + + /* ssl settings */ + ssl_iostream_test_settings_client(&ssl_set); + ssl_set.verbose = debug; + + test_client_defaults(&smtp_client_set); + smtp_client_set.dns_client_socket_path = "./dns-test"; + smtp_client_set.ssl = &ssl_set; + + test_begin("invalid ssl certificate"); + test_run_client_server(&smtp_client_set, + test_client_invalid_ssl_certificate, + test_server_invalid_ssl_certificate, 1, + test_dns_invalid_ssl_certificate); + test_end(); +} + +#endif + +/* + * All tests + */ + +static void (*const test_functions[])(void) = { + test_unconfigured_ssl, + test_unconfigured_ssl_abort, + test_host_lookup_failed, + test_connection_refused, + test_connection_lost_prematurely, + test_connection_timed_out, + test_broken_payload, + test_connection_lost, + test_unexpected_reply, + test_premature_reply, + test_early_data_reply, + test_partial_reply, + test_bad_reply, + test_bad_greeting, + test_command_timed_out, + test_command_aborted_early, + test_client_deinit_early, + test_dns_service_failure, + test_dns_timeout, + test_dns_lookup_failure, + test_authentication, + test_transaction_timeout, +#ifdef HAVE_OPENSSL + test_invalid_ssl_certificate, +#endif + NULL +}; + +/* + * Test client + */ + +static void test_client_defaults(struct smtp_client_settings *smtp_set) +{ + /* client settings */ + i_zero(smtp_set); + smtp_set->my_hostname = "frop.example.com"; + smtp_set->debug = debug; +} + +static void test_client_progress_timeout(void *context ATTR_UNUSED) +{ + /* Terminate test due to lack of progress */ + test_assert(FALSE); + timeout_remove(&to_client_progress); + io_loop_stop(current_ioloop); +} + +static bool +test_client_init(test_client_init_t client_test, + const struct smtp_client_settings *client_set) +{ + i_assert(client_test != NULL); + if (!client_test(client_set)) + return FALSE; + + to_client_progress = timeout_add(CLIENT_PROGRESS_TIMEOUT*1000, + test_client_progress_timeout, NULL); + + return TRUE; +} + +static void test_client_deinit(void) +{ + timeout_remove(&to_client_progress); + + if (smtp_client != NULL) + smtp_client_deinit(&smtp_client); +} + +static void +test_client_run(test_client_init_t client_test, + const struct smtp_client_settings *client_set) +{ + if (test_client_init(client_test, client_set)) + io_loop_run(ioloop); + test_client_deinit(); +} + +/* + * Test server + */ + +/* client connection */ + +static int +server_connection_init_ssl(struct server_connection *conn) +{ + struct ssl_iostream_settings ssl_set; + const char *error; + + if (!test_server_ssl) + return 0; + + connection_input_halt(&conn->conn); + + ssl_iostream_test_settings_server(&ssl_set); + ssl_set.verbose = debug; + + if (server_ssl_ctx == NULL && + ssl_iostream_context_init_server(&ssl_set, &server_ssl_ctx, + &error) < 0) { + i_error("SSL context initialization failed: %s", error); + return -1; + } + + if (io_stream_create_ssl_server(server_ssl_ctx, &ssl_set, + &conn->conn.input, &conn->conn.output, + &conn->ssl_iostream, &error) < 0) { + i_error("SSL init failed: %s", error); + return -1; + } + if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { + i_error("SSL handshake failed: %s", + ssl_iostream_get_last_error(conn->ssl_iostream)); + return -1; + } + + connection_input_resume(&conn->conn); + return 0; +} + +static void +server_connection_input(struct connection *_conn) +{ + struct server_connection *conn = (struct server_connection *)_conn; + const char *line; + int ret; + + if (test_server_input != NULL) { + test_server_input(conn); + return; + } + + for (;;) { + if (conn->state == SERVER_CONNECTION_STATE_FINISH) { + const unsigned char *data; + size_t size; + int ret; + + if (conn->dot_input == NULL) { + conn->dot_input = i_stream_create_dot( + conn->conn.input, TRUE); + } + while ((ret = i_stream_read_more(conn->dot_input, + &data, &size)) > 0) { + if (test_server_input_data != NULL) { + if (test_server_input_data( + conn, data, size) < 0) + return; + } + i_stream_skip(conn->dot_input, size); + } + + if (ret == 0) + return; + if (conn->dot_input->stream_errno != 0) { + if (debug) { + i_debug("Failed to read message payload: %s", + i_stream_get_error(conn->dot_input)); + } + server_connection_deinit(&conn); + return; + } + + o_stream_nsend_str( + conn->conn.output, + "250 2.0.0 Ok: queued as 73BDE342129\r\n"); + conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; + continue; + } + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + if (test_server_input_line != NULL) { + if ((ret = test_server_input_line(conn, line)) < 0) + return; + if (ret > 0) + continue; + } + + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250 DSN\r\n"); + conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; + return; + case SERVER_CONNECTION_STATE_MAIL_FROM: + if (str_begins(line, "AUTH ")) { + o_stream_nsend_str( + conn->conn.output, "235 2.7.0 " + "Authentication successful\r\n"); + continue; + } + if (str_begins(line, "EHLO ")) { + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-AUTH PLAIN\r\n" + "250 DSN\r\n"); + continue; + } + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + conn->state = SERVER_CONNECTION_STATE_RCPT_TO; + continue; + case SERVER_CONNECTION_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + conn->state = SERVER_CONNECTION_STATE_DATA; + continue; + case SERVER_CONNECTION_STATE_DATA: + o_stream_nsend_str( + conn->conn.output, + "354 End data with .\r\n"); + conn->state = SERVER_CONNECTION_STATE_FINISH; + continue; + case SERVER_CONNECTION_STATE_FINISH: + break; + } + i_unreached(); + } +} + +static void server_connection_init(int fd) +{ + struct server_connection *conn; + pool_t pool; + + net_set_nonblock(fd, TRUE); + + pool = pool_alloconly_create("server connection", 256); + conn = p_new(pool, struct server_connection, 1); + conn->pool = pool; + + connection_init_server(server_conn_list, &conn->conn, + "server connection", fd, fd); + + if (server_connection_init_ssl(conn) < 0) { + server_connection_deinit(&conn); + return; + } + + if (test_server_init != NULL) { + if (test_server_init(conn) != 0) + return; + } + + if (test_server_input == NULL) { + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); + } +} + +static void server_connection_deinit(struct server_connection **_conn) +{ + struct server_connection *conn = *_conn; + + *_conn = NULL; + + if (test_server_deinit != NULL) + test_server_deinit(conn); + + i_stream_unref(&conn->dot_input); + + ssl_iostream_destroy(&conn->ssl_iostream); + connection_deinit(&conn->conn); + pool_unref(&conn->pool); +} + +static void server_connection_destroy(struct connection *_conn) +{ + struct server_connection *conn = + (struct server_connection *)_conn; + + server_connection_deinit(&conn); +} + +static void server_connection_accept(void *context ATTR_UNUSED) +{ + int fd; + + /* accept new client */ + fd = net_accept(fd_listen, NULL, NULL); + if (fd == -1) + return; + if (fd == -2) + i_fatal("test server: accept() failed: %m"); + + server_connection_init(fd); +} + +/* */ + +static struct connection_settings server_connection_set = { + .input_max_size = SIZE_MAX, + .output_max_size = SIZE_MAX, + .client = FALSE, +}; + +static const struct connection_vfuncs server_connection_vfuncs = { + .destroy = server_connection_destroy, + .input = server_connection_input, +}; + +static void test_server_run(unsigned int index) +{ + server_index = index; + + /* open server socket */ + io_listen = io_add(fd_listen, IO_READ, server_connection_accept, NULL); + + server_conn_list = connection_list_init(&server_connection_set, + &server_connection_vfuncs); + + io_loop_run(ioloop); + + /* close server socket */ + io_remove(&io_listen); + + connection_list_deinit(&server_conn_list); + + if (server_ssl_ctx != NULL) + ssl_iostream_context_unref(&server_ssl_ctx); +} + +/* + * Tests + */ + +struct test_server_data { + unsigned int index; + test_server_init_t server_test; +}; + +static int test_open_server_fd(in_port_t *bind_port) +{ + int fd = net_listen(&bind_ip, bind_port, 128); + if (debug) + i_debug("server listening on %u", *bind_port); + if (fd == -1) { + i_fatal("listen(%s:%u) failed: %m", + net_ip2addr(&bind_ip), *bind_port); + } + return fd; +} + +static int test_run_server(struct test_server_data *data) +{ + i_set_failure_prefix("SERVER[%u]: ", data->index + 1); + + if (debug) + i_debug("PID=%s", my_pid); + + server_ssl_ctx = NULL; + + ioloop = io_loop_create(); + data->server_test(data->index); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); + + i_close_fd(&fd_listen); + i_free(bind_ports); + main_deinit(); + return 0; +} + +static int test_run_dns(test_dns_init_t dns_test) +{ + i_set_failure_prefix("DNS: "); + + if (debug) + i_debug("PID=%s", my_pid); + + ioloop = io_loop_create(); + dns_test(); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); + + i_close_fd(&fd_listen); + i_free(bind_ports); + main_deinit(); + return 0; +} + +static void +test_run_client(const struct smtp_client_settings *client_set, + test_client_init_t client_test) +{ + i_set_failure_prefix("CLIENT: "); + + if (debug) + i_debug("PID=%s", my_pid); + + i_sleep_msecs(100); /* wait a little for server setup */ + + ioloop = io_loop_create(); + test_client_run(client_test, client_set); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); +} + +static void +test_run_client_server(const struct smtp_client_settings *client_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count, + test_dns_init_t dns_test) +{ + unsigned int i; + + if (server_tests_count > 0) { + int fds[server_tests_count]; + + bind_ports = i_new(in_port_t, server_tests_count); + for (i = 0; i < server_tests_count; i++) + fds[i] = test_open_server_fd(&bind_ports[i]); + + for (i = 0; i < server_tests_count; i++) { + struct test_server_data data; + + i_zero(&data); + data.index = i; + data.server_test = server_test; + + /* Fork server */ + fd_listen = fds[i]; + test_subprocess_fork(test_run_server, &data, FALSE); + i_close_fd(&fd_listen); + } + } + + if (dns_test != NULL) { + int fd; + + i_unlink_if_exists("./dns-test"); + fd = net_listen_unix("./dns-test", 128); + if (fd == -1) { + i_fatal("listen(./dns-test) failed: %m"); + } + + /* Fork DNS service */ + fd_listen = fd; + test_subprocess_fork(test_run_dns, dns_test, FALSE); + i_close_fd(&fd_listen); + } + + /* Run client */ + test_run_client(client_set, client_test); + + i_unset_failure_prefix(); + test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS); + i_free(bind_ports); + + i_unlink_if_exists("./dns-test"); +} + +/* + * Main + */ + +static void main_init(void) +{ +#ifdef HAVE_OPENSSL + ssl_iostream_openssl_init(); +#endif +} + +static void main_deinit(void) +{ + ssl_iostream_context_cache_free(); +#ifdef HAVE_OPENSSL + ssl_iostream_openssl_deinit(); +#endif +} + +int main(int argc, char *argv[]) +{ + int c; + int ret; + + lib_init(); + main_init(); + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + + test_subprocesses_init(debug); + + /* listen on localhost */ + i_zero(&bind_ip); + bind_ip.family = AF_INET; + bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); + + ret = test_run(test_functions); + + test_subprocesses_deinit(); + main_deinit(); + lib_deinit(); + + return ret; +} diff --git a/src/lib-smtp/test-smtp-command-parser.c b/src/lib-smtp/test-smtp-command-parser.c new file mode 100644 index 0000000..9c53c18 --- /dev/null +++ b/src/lib-smtp/test-smtp-command-parser.c @@ -0,0 +1,643 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "array.h" +#include "buffer.h" +#include "str.h" +#include "str-sanitize.h" +#include "istream.h" +#include "ostream.h" +#include "test-common.h" +#include "smtp-command-parser.h" + +#include + +/* + * Valid command tests + */ + +struct smtp_command_parse_valid_test { + const char *command; + + struct smtp_command_limits limits; + + const char *cmd_name; + const char *cmd_params; +}; + +static const struct smtp_command_parse_valid_test +valid_command_parse_tests[] = { + { + .command = "RSET\r\n", + .cmd_name = "RSET", + .cmd_params = "", + }, + { + .command = "RSET \r\n", + .cmd_name = "RSET", + .cmd_params = "", + }, + { + .command = "EHLO example.com\r\n", + .cmd_name = "EHLO", + .cmd_params = "example.com", + }, + { + .command = "EHLO example.com \r\n", + .cmd_name = "EHLO", + .cmd_params = "example.com", + }, + { + .command = "MAIL FROM: ENVID=frop\r\n", + .cmd_name = "MAIL", + .cmd_params = "FROM: ENVID=frop", + }, + { + .command = "VRFY \"Sherlock Holmes\"\r\n", + .cmd_name = "VRFY", + .cmd_params = "\"Sherlock Holmes\"", + }, + { + .command = "RCPT TO: NOTIFY=NEVER\r\n", + .limits = { .max_parameters_size = 39 }, + .cmd_name = "RCPT", + .cmd_params = "TO: NOTIFY=NEVER", + }, + { + .command = "MAIL FROM:\r\n", + .cmd_name = "MAIL", + .cmd_params = "FROM:", + }, +}; + +unsigned int valid_command_parse_test_count = + N_ELEMENTS(valid_command_parse_tests); + +static void +test_smtp_command_parse_valid_check( + const struct smtp_command_parse_valid_test *test, + const char *cmd_name, const char *cmd_params) +{ + test_out(t_strdup_printf("command name = `%s'", test->cmd_name), + null_strcmp(cmd_name, test->cmd_name) == 0); + test_out(t_strdup_printf("command params = `%s'", + str_sanitize(test->cmd_params, 24)), + null_strcmp(cmd_params, test->cmd_params) == 0); +} + +static void test_smtp_command_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_command_parse_test_count; i++) T_BEGIN { + struct istream *input; + const struct smtp_command_parse_valid_test *test; + struct smtp_command_parser *parser; + const char *command_text, *cmd_name, *cmd_params, *error; + enum smtp_command_parse_error error_code; + unsigned int pos, command_text_len; + int ret; + + test_begin(t_strdup_printf("smtp command valid [%d]", i)); + + cmd_name = cmd_params = error = NULL; + + test = &valid_command_parse_tests[i]; + command_text = test->command; + command_text_len = strlen(command_text); + + /* Fully buffered input */ + input = i_stream_create_from_data(command_text, + command_text_len); + parser = smtp_command_parser_init(input, &test->limits); + + while ((ret = smtp_command_parse_next( + parser, &cmd_name, &cmd_params, + &error_code, &error)) > 0); + + test_out_reason("parse success [buffer]", ret == -2, + (ret == -2 ? NULL : error)); + if (ret == 0) { + /* Verify last command only */ + test_smtp_command_parse_valid_check( + test, cmd_name, cmd_params); + } + + smtp_command_parser_deinit(&parser); + i_stream_unref(&input); + + error = NULL; + error_code = SMTP_COMMAND_PARSE_ERROR_NONE; + ret = 0; + + /* Trickle stream */ + input = test_istream_create_data(command_text, + command_text_len); + parser = smtp_command_parser_init(input, &test->limits); + + for (pos = 0; pos <= command_text_len && ret == 0; pos++) { + test_istream_set_size(input, pos); + ret = smtp_command_parse_next( + parser, &cmd_name, &cmd_params, + &error_code, &error); + } + test_istream_set_size(input, command_text_len); + if (ret >= 0) { + while ((ret = smtp_command_parse_next( + parser, &cmd_name, &cmd_params, + &error_code, &error)) > 0); + } + + test_out_reason("parse success [stream]", ret == -2, + (ret == -2 ? NULL : error)); + if (ret == 0) { + /* Verify last command only */ + test_smtp_command_parse_valid_check( + test, cmd_name, cmd_params); + } + + smtp_command_parser_deinit(&parser); + i_stream_unref(&input); + + test_end(); + } T_END; +} + +/* + * Invalid command tests + */ + +struct smtp_command_parse_invalid_test { + const char *command; + + struct smtp_command_limits limits; + + enum smtp_command_parse_error error_code; +}; + +static const struct smtp_command_parse_invalid_test + invalid_command_parse_tests[] = { + { + .command = "B52\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .command = "BELL\x08\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .command = "EHLO example.com\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .command = "NOOP \"\x01\x02\x03\"\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .command = "RSET\rQUIT\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .command = "INSANELYREDICULOUSLYLONGCOMMANDNAME\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .command = "RCPT TO: NOTIFY=NEVER\r\n", + .limits = { .max_parameters_size = 38 }, + .error_code = SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG, + }, + { + .command = "MAIL FROM:\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .command = "MAIL FROM:f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .command = "MAIL FROM:f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3", + .error_code = SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND, + }, + { + .command = "FROP \xF1", + .error_code = SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND, + }, + { + .command = "FROP \xF1\x80", + .error_code = SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND, + }, + { + .command = "FROP \xF1\x80\x80", + .error_code = SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND, + }, + { + .command = "FROP \xF1\x80\x80\x80", + .error_code = SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND, + }, +}; + +unsigned int invalid_command_parse_test_count = + N_ELEMENTS(invalid_command_parse_tests); + +static void test_smtp_command_parse_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_command_parse_test_count; i++) T_BEGIN { + const struct smtp_command_parse_invalid_test *test; + struct istream *input; + struct smtp_command_parser *parser; + const char *command_text, *cmd_name, *cmd_params, *error; + enum smtp_command_parse_error error_code; + unsigned int pos, command_text_len; + int ret; + + test_begin(t_strdup_printf("smtp command invalid [%d]", i)); + + test = &invalid_command_parse_tests[i]; + command_text = test->command; + command_text_len = strlen(command_text); + + /* Fully buffered input */ + input = i_stream_create_from_data(command_text, + command_text_len); + parser = smtp_command_parser_init(input, &test->limits); + + while ((ret = smtp_command_parse_next( + parser, &cmd_name, &cmd_params, + &error_code, &error)) > 0); + + test_out_reason(t_strdup_printf("parse(\"%s\") [buffer]", + str_sanitize(command_text, 28)), + ret == -1, error); + test_out_quiet("error code", error_code == test->error_code); + + smtp_command_parser_deinit(&parser); + i_stream_unref(&input); + + error = NULL; + error_code = SMTP_COMMAND_PARSE_ERROR_NONE; + ret = 0; + + /* Trickle stream */ + input = test_istream_create_data(command_text, + command_text_len); + parser = smtp_command_parser_init(input, &test->limits); + + for (pos = 0; pos <= command_text_len && ret == 0; pos++) { + test_istream_set_size(input, pos); + ret = smtp_command_parse_next( + parser, &cmd_name, &cmd_params, + &error_code, &error); + } + test_istream_set_size(input, command_text_len); + if (ret >= 0) { + while ((ret = smtp_command_parse_next( + parser, &cmd_name, &cmd_params, + &error_code, &error)) > 0); + } + + test_out_reason(t_strdup_printf("parse(\"%s\") [stream]", + str_sanitize(command_text, 28)), + ret == -1, error); + test_out_quiet("error code", error_code == test->error_code); + + smtp_command_parser_deinit(&parser); + i_stream_unref(&input); + + test_end(); + } T_END; +} + +/* + * Valid auth response tests + */ + +struct smtp_auth_response_parse_valid_test { + const char *auth_response; + + struct smtp_command_limits limits; + + const char *line; +}; + +static const struct smtp_auth_response_parse_valid_test +valid_auth_response_parse_tests[] = { + { + .auth_response = "U3R1cGlkIEJhc2U2NCB0ZXN0\r\n", + .line = "U3R1cGlkIEJhc2U2NCB0ZXN0", + }, + { + .auth_response = "U3R1cGlkIEJhc2U2NCB0ZXN0 \r\n", + .line = "U3R1cGlkIEJhc2U2NCB0ZXN0", + }, + { + .auth_response = + "U3R1cGlkIHZlcnkgdmVyeSB2ZXJ5IHZlcnkgdmVyeS" + "B2ZXJ5IHZlcnkgdmVyeSBsb25nIEJhc2U2NCB0ZXN0\r\n", + .limits = { .max_auth_size = 84 }, + .line = "U3R1cGlkIHZlcnkgdmVyeSB2ZXJ5IHZlcnkgdmVyeS" + "B2ZXJ5IHZlcnkgdmVyeSBsb25nIEJhc2U2NCB0ZXN0", + }, + { + .auth_response = + "dXNlcj10ZXN0dXNlcjEBYXV0aD1CZWFyZXIgZXlKaG" + "JHY2lPaUpTVXpJMU5pSXNJblI1Y0NJZ09pQWlTbGRV" + "SWl3aWEybGtJaUE2SUNKdVRIRlVlRnBXWVhKSlgwWn" + "dSa0Z3Umt3MloyUnhiak4xV1VSS2R6WnNWVjlMYVZo" + "a2JWazJialpSSW4wLmV5SmxlSEFpT2pFMk16UTJNem" + "MyTlRFc0ltbGhkQ0k2TVRZek5EWXpOek0xTVN3aWFu" + "UnBJam9pT1RFM1lUYzFaalF0WTJZME9DMDBOVEEyTF" + "RnNVpXSXRNRE13WldaaU5tSTVOMlZrSWl3aWFYTnpJ" + "am9pYUhSMGNEb3ZMekU1TWk0eE5qZ3VNUzR5TVRveE" + "9EQTRNQzloZFhSb0wzSmxZV3h0Y3k5eVpXeDBaWE4w" + "SWl3aVlYVmtJam9pWVdOamIzVnVkQ0lzSW5OMVlpST" + "ZJamhsWVRRME1UWTNMVGN6TTJVdE5EVTBZeTFpT0dJ" + "MUxXTmpabVl3WkRnek1URTVaQ0lzSW5SNWNDSTZJa0" + "psWVhKbGNpSXNJbUY2Y0NJNkltUnZkbVZqYjNRaUxD" + "SnpaWE56YVc5dVgzTjBZWFJsSWpvaU1tTTNPVEUzWl" + "dJdE16QTFOUzAwTkRZeExXSXdZell0WTJVeFlUbGlN" + "VEZoTWpReklpd2lZV055SWpvaU1TSXNJbkpsWVd4dF" + "gyRmpZMlZ6Y3lJNmV5SnliMnhsY3lJNld5SnZabVpz" + "YVc1bFgyRmpZMlZ6Y3lJc0luVnRZVjloZFhSb2IzSn" + "BlbUYwYVc5dUlsMTlMQ0p5WlhOdmRYSmpaVjloWTJO" + "bGMzTWlPbnNpWVdOamIzVnVkQ0k2ZXlKeWIyeGxjeU" + "k2V3lKdFlXNWhaMlV0WVdOamIzVnVkQ0lzSW0xaGJt" + "Rm5aUzFoWTJOdmRXNTBMV3hwYm10eklpd2lkbWxsZH" + "kxd2NtOW1hV3hsSWwxOWZTd2ljMk52Y0dVaU9pSndj" + "bTltYVd4bElHVnRZV2xzSWl3aVpXMWhhV3hmZG1WeW" + "FXWnBaV1FpT21aaGJITmxMQ0p1WVcxbElqb2lkR1Z6" + "ZEhWelpYSXhJRUYxZEc5SFpXNWxjbUYwWldRaUxDSn" + "djbVZtWlhKeVpXUmZkWE5sY201aGJXVWlPaUowWlhO" + "MGRYTmxjakVpTENKbmFYWmxibDl1WVcxbElqb2lkR1" + "Z6ZEhWelpYSXhJaXdpWm1GdGFXeDVYMjVoYldVaU9p" + "SkJkWFJ2UjJWdVpYSmhkR1ZrSWl3aVpXMWhhV3dpT2" + "lKMFpYTjBkWE5sY2pGQWJYbGtiMjFoYVc0dWIzZ2lm" + "US5ta2JGSURpT0FhbENCcVMwODRhVHJURjBIdDk1c1" + "Z4cGlSbTFqZnhJd0JiN1hMM2gzWUJkdXVrVXlZdDJq" + "X1pqUFlhMDhDcVVYNWFrLVBOSjdSVWRTUXNmUlgwM1" + "ZicXA4MHFZZjNGYzJpcDR0YmhHLXFEV0R6NzdhZDhW" + "cEFNei16YWlSamZCclZ2R3hBT3ZsZnFDVWhaZTJDR3" + "ZqWjZ1Q3RKTlFaS0dyazZHOXoxX2pqekZkTjBXWjUx" + "bEZsUS1JdE5LREpoTjNIekJ5SW93M19qQU9kWEI0R0" + "w4R3JHM1hqU09rSFVRam5GTEQwQUF1QXY4SkxmTXY1" + "NGc1a2tKaklxRFgxZlgyWVo0Y2JQOWV3TUp6UV84ZW" + "dLeW5TVV9XSk8xRU9Qa1NVZjlMX19RX3FwY0dNbzFt" + "TkxuTURKUlU2dmZFY3JrM2k0cVNzMXRPdHdLaHcBAQ" + "==\r\n", + .line = + "dXNlcj10ZXN0dXNlcjEBYXV0aD1CZWFyZXIgZXlKaG" + "JHY2lPaUpTVXpJMU5pSXNJblI1Y0NJZ09pQWlTbGRV" + "SWl3aWEybGtJaUE2SUNKdVRIRlVlRnBXWVhKSlgwWn" + "dSa0Z3Umt3MloyUnhiak4xV1VSS2R6WnNWVjlMYVZo" + "a2JWazJialpSSW4wLmV5SmxlSEFpT2pFMk16UTJNem" + "MyTlRFc0ltbGhkQ0k2TVRZek5EWXpOek0xTVN3aWFu" + "UnBJam9pT1RFM1lUYzFaalF0WTJZME9DMDBOVEEyTF" + "RnNVpXSXRNRE13WldaaU5tSTVOMlZrSWl3aWFYTnpJ" + "am9pYUhSMGNEb3ZMekU1TWk0eE5qZ3VNUzR5TVRveE" + "9EQTRNQzloZFhSb0wzSmxZV3h0Y3k5eVpXeDBaWE4w" + "SWl3aVlYVmtJam9pWVdOamIzVnVkQ0lzSW5OMVlpST" + "ZJamhsWVRRME1UWTNMVGN6TTJVdE5EVTBZeTFpT0dJ" + "MUxXTmpabVl3WkRnek1URTVaQ0lzSW5SNWNDSTZJa0" + "psWVhKbGNpSXNJbUY2Y0NJNkltUnZkbVZqYjNRaUxD" + "SnpaWE56YVc5dVgzTjBZWFJsSWpvaU1tTTNPVEUzWl" + "dJdE16QTFOUzAwTkRZeExXSXdZell0WTJVeFlUbGlN" + "VEZoTWpReklpd2lZV055SWpvaU1TSXNJbkpsWVd4dF" + "gyRmpZMlZ6Y3lJNmV5SnliMnhsY3lJNld5SnZabVpz" + "YVc1bFgyRmpZMlZ6Y3lJc0luVnRZVjloZFhSb2IzSn" + "BlbUYwYVc5dUlsMTlMQ0p5WlhOdmRYSmpaVjloWTJO" + "bGMzTWlPbnNpWVdOamIzVnVkQ0k2ZXlKeWIyeGxjeU" + "k2V3lKdFlXNWhaMlV0WVdOamIzVnVkQ0lzSW0xaGJt" + "Rm5aUzFoWTJOdmRXNTBMV3hwYm10eklpd2lkbWxsZH" + "kxd2NtOW1hV3hsSWwxOWZTd2ljMk52Y0dVaU9pSndj" + "bTltYVd4bElHVnRZV2xzSWl3aVpXMWhhV3hmZG1WeW" + "FXWnBaV1FpT21aaGJITmxMQ0p1WVcxbElqb2lkR1Z6" + "ZEhWelpYSXhJRUYxZEc5SFpXNWxjbUYwWldRaUxDSn" + "djbVZtWlhKeVpXUmZkWE5sY201aGJXVWlPaUowWlhO" + "MGRYTmxjakVpTENKbmFYWmxibDl1WVcxbElqb2lkR1" + "Z6ZEhWelpYSXhJaXdpWm1GdGFXeDVYMjVoYldVaU9p" + "SkJkWFJ2UjJWdVpYSmhkR1ZrSWl3aVpXMWhhV3dpT2" + "lKMFpYTjBkWE5sY2pGQWJYbGtiMjFoYVc0dWIzZ2lm" + "US5ta2JGSURpT0FhbENCcVMwODRhVHJURjBIdDk1c1" + "Z4cGlSbTFqZnhJd0JiN1hMM2gzWUJkdXVrVXlZdDJq" + "X1pqUFlhMDhDcVVYNWFrLVBOSjdSVWRTUXNmUlgwM1" + "ZicXA4MHFZZjNGYzJpcDR0YmhHLXFEV0R6NzdhZDhW" + "cEFNei16YWlSamZCclZ2R3hBT3ZsZnFDVWhaZTJDR3" + "ZqWjZ1Q3RKTlFaS0dyazZHOXoxX2pqekZkTjBXWjUx" + "bEZsUS1JdE5LREpoTjNIekJ5SW93M19qQU9kWEI0R0" + "w4R3JHM1hqU09rSFVRam5GTEQwQUF1QXY4SkxmTXY1" + "NGc1a2tKaklxRFgxZlgyWVo0Y2JQOWV3TUp6UV84ZW" + "dLeW5TVV9XSk8xRU9Qa1NVZjlMX19RX3FwY0dNbzFt" + "TkxuTURKUlU2dmZFY3JrM2k0cVNzMXRPdHdLaHcBAQ", + }, +}; + +unsigned int valid_auth_response_parse_test_count = + N_ELEMENTS(valid_auth_response_parse_tests); + +static void +test_smtp_auth_response_parse_valid_check( + const struct smtp_auth_response_parse_valid_test *test, + const char *line) +{ + test_out(t_strdup_printf("line = `%s'", + str_sanitize(test->line, 24)), + null_strcmp(line, test->line) == 0); +} + +static void test_smtp_auth_response_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_auth_response_parse_test_count; i++) T_BEGIN { + struct istream *input; + const struct smtp_auth_response_parse_valid_test *test; + struct smtp_command_parser *parser; + const char *response_text, *line, *error; + enum smtp_command_parse_error error_code; + unsigned int pos, response_text_len; + int ret; + + test_begin(t_strdup_printf("smtp auth_response valid [%d]", i)); + + line = error = NULL; + + test = &valid_auth_response_parse_tests[i]; + response_text = test->auth_response; + response_text_len = strlen(response_text); + + /* Fully buffered input */ + input = i_stream_create_from_data(response_text, + response_text_len); + parser = smtp_command_parser_init(input, &test->limits); + + while ((ret = smtp_command_parse_auth_response( + parser, &line, &error_code, &error)) > 0); + + test_out_reason("parse success [buffer]", ret == -2, + (ret == -2 ? NULL : error)); + if (ret == 0) { + /* Verify last reponse only */ + test_smtp_auth_response_parse_valid_check(test, line); + } + + smtp_command_parser_deinit(&parser); + i_stream_unref(&input); + + error = NULL; + error_code = SMTP_COMMAND_PARSE_ERROR_NONE; + ret = 0; + + /* Trickle stream */ + input = test_istream_create_data(response_text, + response_text_len); + parser = smtp_command_parser_init(input, &test->limits); + + for (pos = 0; pos <= response_text_len && ret == 0; pos++) { + test_istream_set_size(input, pos); + ret = smtp_command_parse_auth_response( + parser, &line, &error_code, &error); + } + test_istream_set_size(input, response_text_len); + if (ret >= 0) { + while ((ret = smtp_command_parse_auth_response( + parser, &line, &error_code, &error)) > 0); + } + + test_out_reason("parse success [stream]", ret == -2, + (ret == -2 ? NULL : error)); + if (ret == 0) { + /* Verify last reponse only */ + test_smtp_auth_response_parse_valid_check(test, line); + } + + smtp_command_parser_deinit(&parser); + i_stream_unref(&input); + + test_end(); + } T_END; +} + +/* + * Invalid auth response tests + */ + +struct smtp_auth_response_parse_invalid_test { + const char *auth_response; + + struct smtp_command_limits limits; + + enum smtp_command_parse_error error_code; +}; + +static const struct smtp_auth_response_parse_invalid_test + invalid_auth_response_parse_tests[] = { + { + .auth_response = "\x01\x02\x03\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .auth_response = "U3R1cGlkIEJhc\r2U2NCB0ZXN0\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, + { + .auth_response = + "U3R1cGlkIHZlcnkgdmVyeSB2ZXJ5IHZlcnkgdmVyeS" + "B2ZXJ5IHZlcnkgdmVyeSBsb25nIEJhc2U2NCB0ZXN0\r\n", + .limits = { .max_auth_size = 83 }, + .error_code = SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG, + }, + { + .auth_response = "\xc3\xb6\xc3\xa4\xc3\xb6\xc3\xa4\r\n", + .error_code = SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND, + }, +}; + +unsigned int invalid_auth_response_parse_test_count = + N_ELEMENTS(invalid_auth_response_parse_tests); + +static void test_smtp_auth_response_parse_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_auth_response_parse_test_count; i++) T_BEGIN { + const struct smtp_auth_response_parse_invalid_test *test; + struct istream *input; + struct smtp_command_parser *parser; + const char *response_text, *line, *error; + enum smtp_command_parse_error error_code; + unsigned int pos, response_text_len; + int ret; + + test_begin( + t_strdup_printf("smtp auth response invalid [%d]", i)); + + test = &invalid_auth_response_parse_tests[i]; + response_text = test->auth_response; + response_text_len = strlen(response_text); + + /* Fully buffered input */ + input = i_stream_create_from_data(response_text, + strlen(response_text)); + parser = smtp_command_parser_init(input, &test->limits); + + while ((ret = smtp_command_parse_auth_response( + parser, &line, &error_code, &error)) > 0); + + test_out_reason(t_strdup_printf("parse(\"%s\") [buffer]", + str_sanitize(response_text, + 28)), + ret == -1, error); + test_out_quiet("error code", error_code == test->error_code); + + smtp_command_parser_deinit(&parser); + i_stream_unref(&input); + + error = NULL; + error_code = SMTP_COMMAND_PARSE_ERROR_NONE; + ret = 0; + + /* Trickle stream */ + input = test_istream_create_data(response_text, + response_text_len); + parser = smtp_command_parser_init(input, &test->limits); + + for (pos = 0; pos <= response_text_len && ret == 0; pos++) { + test_istream_set_size(input, pos); + ret = smtp_command_parse_auth_response( + parser, &line, &error_code, &error); + } + test_istream_set_size(input, response_text_len); + if (ret >= 0) { + while ((ret = smtp_command_parse_auth_response( + parser, &line, &error_code, &error)) > 0); + } + + test_out_reason(t_strdup_printf("parse(\"%s\") [stream]", + str_sanitize(response_text, + 28)), + ret == -1, error); + test_out_quiet("error code", error_code == test->error_code); + + smtp_command_parser_deinit(&parser); + i_stream_unref(&input); + + test_end(); + } T_END; +} + +/* + * Tests + */ + +int main(void) +{ + static void (*test_functions[])(void) = { + test_smtp_command_parse_valid, + test_smtp_command_parse_invalid, + test_smtp_auth_response_parse_valid, + test_smtp_auth_response_parse_invalid, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-smtp/test-smtp-params.c b/src/lib-smtp/test-smtp-params.c new file mode 100644 index 0000000..2847441 --- /dev/null +++ b/src/lib-smtp/test-smtp-params.c @@ -0,0 +1,894 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "str.h" +#include "array.h" +#include "test-common.h" +#include "smtp-common.h" +#include "smtp-address.h" +#include "smtp-params.h" + +static const char *test_extensions[] = { "FROP", "FRUP", NULL }; + +static struct smtp_address test_address1 = + { .localpart = NULL, .domain = NULL }; +static struct smtp_address test_address2 = + { .localpart = "user+detail", .domain = NULL }; +static struct smtp_address test_address3 = + { .localpart = "e=mc2", .domain = "example.com" }; + +static struct smtp_param test_params1[] = { + { .keyword = "FROP", .value = "friep" } +}; +static struct smtp_param test_params2[] = { + { .keyword = "FROP", .value = "friep" }, + { .keyword = "FRUP", .value = "frml" } +}; + +static struct buffer test_params_buffer1 = { + .data = (void*)&test_params1, + .used = sizeof(test_params1) +}; +static struct buffer test_params_buffer2 = { + .data = (void*)&test_params2, + .used = sizeof(test_params2) +}; + +/* Valid mail params tests */ + +struct valid_mail_params_parse_test { + const char *input, *output; + + enum smtp_capability caps; + const char *const *extensions; + const char *const *body_extensions; + + struct smtp_params_mail params; +}; + +static const struct valid_mail_params_parse_test +valid_mail_params_parse_tests[] = { + /* AUTH */ + { + .input = "AUTH=<>", + .caps = SMTP_CAPABILITY_AUTH, + .params = { + .auth = &test_address1 + } + }, + { + .input = "AUTH=user+2Bdetail", + .caps = SMTP_CAPABILITY_AUTH, + .params = { + .auth = &test_address2 + } + }, + { + .input = "AUTH=e+3Dmc2@example.com", + .caps = SMTP_CAPABILITY_AUTH, + .params = { + .auth = &test_address3 + } + }, + /* BODY */ + { + .input = "", + .caps = SMTP_CAPABILITY_8BITMIME, + .params = { + .body = { + .type = SMTP_PARAM_MAIL_BODY_TYPE_UNSPECIFIED, + } + } + }, + { + .input = "BODY=7BIT", + .caps = SMTP_CAPABILITY_8BITMIME, + .params = { + .body = { + .type = SMTP_PARAM_MAIL_BODY_TYPE_7BIT, + } + } + }, + { + .input = "BODY=8BITMIME", + .caps = SMTP_CAPABILITY_8BITMIME, + .params = { + .body = { + .type = SMTP_PARAM_MAIL_BODY_TYPE_8BITMIME, + } + } + }, + { + .input = "BODY=BINARYMIME", + .caps = SMTP_CAPABILITY_8BITMIME | + SMTP_CAPABILITY_BINARYMIME | + SMTP_CAPABILITY_CHUNKING, + .params = { + .body = { + .type = SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME, + } + } + }, + { + .input = "BODY=FROP", + .caps = SMTP_CAPABILITY_8BITMIME | + SMTP_CAPABILITY_BINARYMIME | + SMTP_CAPABILITY_CHUNKING, + .body_extensions = test_extensions, + .params = { + .body = { + .type = SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION, + .ext = "FROP" + } + } + }, + /* ENVID */ + { + .input = "", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .envid = NULL, + } + }, + { + .input = "ENVID=", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .envid = "", + } + }, + { + .input = "ENVID=AABBCCDD", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .envid = "AABBCCDD", + } + }, + { + .input = "ENVID=AA+2BBB+3DCC+2BDD", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .envid = "AA+BB=CC+DD", + } + }, + /* RET */ + { + .input = "", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .ret = SMTP_PARAM_MAIL_RET_UNSPECIFIED, + } + }, + { + .input = "RET=HDRS", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .ret = SMTP_PARAM_MAIL_RET_HDRS, + } + }, + { + .input = "RET=FULL", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .ret = SMTP_PARAM_MAIL_RET_FULL, + } + }, + /* SIZE */ + { + .input = "", + .caps = SMTP_CAPABILITY_SIZE, + .params = { + .size = 0 + } + }, + { + .input = "SIZE=267914296", + .caps = SMTP_CAPABILITY_SIZE, + .params = { + .size = 267914296 + } + }, + /* */ + { + .input = "FROP=friep", + .caps = SMTP_CAPABILITY_SIZE, + .extensions = test_extensions, + .params = { + .extra_params = { + .arr = { + .buffer = &test_params_buffer1, + .element_size = sizeof(struct smtp_param) + } + } + } + }, + { + .input = "FROP=friep FRUP=frml", + .extensions = test_extensions, + .params = { + .extra_params = { + .arr = { + .buffer = &test_params_buffer2, + .element_size = sizeof(struct smtp_param) + } + } + } + } +}; + +unsigned int valid_mail_params_parse_test_count = + N_ELEMENTS(valid_mail_params_parse_tests); + +static void +test_smtp_mail_params_auth(const struct smtp_params_mail *test, + const struct smtp_params_mail *parsed) +{ + if (parsed->auth->localpart == NULL || + test->auth->localpart == NULL) { + test_out(t_strdup_printf("params.auth->localpart = %s", + parsed->auth->localpart), + (parsed->auth->localpart == test->auth->localpart)); + } else { + test_out(t_strdup_printf("params.auth->localpart = \"%s\"", + parsed->auth->localpart), + strcmp(parsed->auth->localpart, + test->auth->localpart) == 0); + } + if (parsed->auth->domain == NULL || + test->auth->domain == NULL) { + test_out(t_strdup_printf("params.auth->domain = %s", + parsed->auth->domain), + (parsed->auth->domain == test->auth->domain)); + } else { + test_out(t_strdup_printf("params.auth->domain = \"%s\"", + parsed->auth->domain), + strcmp(parsed->auth->domain, + test->auth->domain) == 0); + } +} + +static void +test_smtp_mail_params_body(const struct smtp_params_mail *test, + const struct smtp_params_mail *parsed) +{ + const char *type_name = NULL; + + switch (parsed->body.type) { + case SMTP_PARAM_MAIL_BODY_TYPE_UNSPECIFIED: + type_name = ""; + break; + case SMTP_PARAM_MAIL_BODY_TYPE_7BIT: + type_name = "7BIT"; + break; + case SMTP_PARAM_MAIL_BODY_TYPE_8BITMIME: + type_name = "8BITMIME"; + break; + case SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME: + type_name = "BINARYMIME"; + break; + case SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION: + type_name = parsed->body.ext; + break; + default: + i_unreached(); + } + + test_out(t_strdup_printf("params.body.type = %s", type_name), + (parsed->body.type == test->body.type && + (parsed->body.type != SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION || + (parsed->body.ext != NULL && + strcmp(parsed->body.ext, test->body.ext) == 0)))); +} + +static void +test_smtp_mail_params_envid(const struct smtp_params_mail *test, + const struct smtp_params_mail *parsed) +{ + if (parsed->envid == NULL || test->envid == NULL) { + test_out(t_strdup_printf("params.auth->localpart = %s", + parsed->envid), + (parsed->envid == test->envid)); + } else { + test_out(t_strdup_printf("params.auth->localpart = \"%s\"", + parsed->envid), + strcmp(parsed->envid, test->envid) == 0); + } +} + +static void +test_smtp_mail_params_ret(const struct smtp_params_mail *test, + const struct smtp_params_mail *parsed) +{ + const char *ret_name = NULL; + + switch (parsed->ret) { + case SMTP_PARAM_MAIL_RET_UNSPECIFIED: + ret_name = ""; + break; + case SMTP_PARAM_MAIL_RET_HDRS: + ret_name = "HDRS"; + break; + case SMTP_PARAM_MAIL_RET_FULL: + ret_name = "FULL"; + break; + default: + i_unreached(); + } + + test_out(t_strdup_printf("params.ret = %s", ret_name), + parsed->ret == test->ret); +} + +static void +test_smtp_mail_params_size(const struct smtp_params_mail *test, + const struct smtp_params_mail *parsed) +{ + test_out(t_strdup_printf("params.size = %"PRIuUOFF_T, parsed->size), + parsed->size == test->size); +} + +static void +test_smtp_mail_params_extensions(const struct smtp_params_mail *test, + const struct smtp_params_mail *parsed) +{ + const struct smtp_param *tparam, *pparam; + unsigned int i; + + if (!array_is_created(&test->extra_params) || + array_count(&test->extra_params) == 0) { + test_out(t_strdup_printf("params.extra_params.count = %u", + (!array_is_created(&parsed->extra_params) ? + 0 : array_count(&parsed->extra_params))), + (!array_is_created(&parsed->extra_params) || + array_count(&parsed->extra_params) == 0)); + return; + } + + if (!array_is_created(&parsed->extra_params) || + array_count(&parsed->extra_params) == 0) { + test_out("params.extra_params.count = 0", FALSE); + return; + } + + if (array_count(&test->extra_params) != + array_count(&parsed->extra_params)) { + test_out(t_strdup_printf("params.extra_params.count = %u", + (!array_is_created(&parsed->extra_params) ? + 0 : array_count(&parsed->extra_params))), FALSE); + return; + } + + for (i = 0; i < array_count(&test->extra_params); i++) { + tparam = array_idx(&test->extra_params, i); + pparam = array_idx(&parsed->extra_params, i); + + test_out(t_strdup_printf("params.extra_params[%u] = [\"%s\"=\"%s\"]", + i, pparam->keyword, pparam->value), + strcmp(pparam->keyword, tparam->keyword) == 0 && + ((pparam->value == NULL && tparam->value == NULL) || + (pparam->value != NULL && tparam->value != NULL && + strcmp(pparam->value, tparam->value) == 0))); + } +} + +static void test_smtp_mail_params_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_mail_params_parse_test_count; i++) T_BEGIN { + const struct valid_mail_params_parse_test *test; + struct smtp_params_mail params; + enum smtp_param_parse_error error_code; + const char *error = NULL, *output; + int ret; + + test = &valid_mail_params_parse_tests[i]; + ret = smtp_params_mail_parse(pool_datastack_create(), + test->input, test->caps, test->extensions, + test->body_extensions, ¶ms, &error_code, &error); + + test_begin(t_strdup_printf("smtp mail params valid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret >= 0, error); + + if (ret >= 0) { + string_t *encoded; + + /* AUTH */ + if ((test->caps & SMTP_CAPABILITY_AUTH) != 0) + test_smtp_mail_params_auth(&test->params, ¶ms); + /* BODY */ + if ((test->caps & SMTP_CAPABILITY_8BITMIME) != 0 || + (test->caps & SMTP_CAPABILITY_BINARYMIME) != 0) + test_smtp_mail_params_body(&test->params, ¶ms); + /* ENVID */ + if ((test->caps & SMTP_CAPABILITY_DSN) != 0) + test_smtp_mail_params_envid(&test->params, ¶ms); + /* RET */ + if ((test->caps & SMTP_CAPABILITY_DSN) != 0) + test_smtp_mail_params_ret(&test->params, ¶ms); + /* SIZE */ + if ((test->caps & SMTP_CAPABILITY_SIZE) != 0) + test_smtp_mail_params_size(&test->params, ¶ms); + /* */ + if (test->extensions != NULL) + test_smtp_mail_params_extensions(&test->params, ¶ms); + + encoded = t_str_new(256); + smtp_params_mail_write(encoded, test->caps, + test->extensions, ¶ms); + + output = (test->output == NULL ? test->input : test->output); + test_out(t_strdup_printf("encode() = \"%s\"", + str_c(encoded)), + strcmp(str_c(encoded), output) == 0); + } + test_end(); + } T_END; +} + +/* Invalid mail params tests */ + +struct invalid_mail_params_parse_test { + const char *input; + + enum smtp_capability caps; + const char *const *extensions; +}; + +static const struct invalid_mail_params_parse_test +invalid_mail_params_parse_tests[] = { + /* AUTH */ + { + .input = "AUTH=<>", + }, + { + .input = "AUTH=++", + .caps = SMTP_CAPABILITY_AUTH + }, + /* BODY */ + { + .input = "BODY=8BITMIME", + }, + { + .input = "BODY=BINARYMIME", + }, + { + .input = "BODY=BINARYMIME", + .caps = SMTP_CAPABILITY_BINARYMIME + }, + { + .input = "BODY=FROP", + .caps = SMTP_CAPABILITY_8BITMIME + }, + /* ENVID */ + { + .input = "ENVID=AABBCC", + }, + { + .input = "ENVID=++", + .caps = SMTP_CAPABILITY_DSN + }, + /* RET */ + { + .input = "RET=FULL", + }, + { + .input = "RET=HDR", + }, + { + .input = "RET=FROP", + .caps = SMTP_CAPABILITY_DSN + }, + /* SIZE */ + { + .input = "SIZE=13", + }, + { + .input = "SIZE=ABC", + .caps = SMTP_CAPABILITY_SIZE + } +}; + +unsigned int invalid_mail_params_parse_test_count = + N_ELEMENTS(invalid_mail_params_parse_tests); + +static void test_smtp_mail_params_parse_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_mail_params_parse_test_count; i++) T_BEGIN { + const struct invalid_mail_params_parse_test *test; + struct smtp_params_mail params; + enum smtp_param_parse_error error_code; + const char *error = NULL; + int ret; + + test = &invalid_mail_params_parse_tests[i]; + ret = smtp_params_mail_parse(pool_datastack_create(), + test->input, test->caps, + test->extensions, NULL, + ¶ms, &error_code, &error); + + test_begin(t_strdup_printf("smtp mail params invalid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret < 0, error); + test_end(); + } T_END; +} + +/* Valid rcpt params tests */ + +struct valid_rcpt_params_parse_test { + const char *input, *output; + + enum smtp_param_rcpt_parse_flags flags; + enum smtp_capability caps; + const char *const *extensions; + + struct smtp_params_rcpt params; +}; + +static const struct valid_rcpt_params_parse_test +valid_rcpt_params_parse_tests[] = { + /* ORCPT */ + { + .input = "ORCPT=rfc822;e+3Dmc2@example.com", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .orcpt = { + .addr = &test_address3 + } + } + }, + { + .input = "ORCPT=rfc822;", + .output = "ORCPT=rfc822;e+3Dmc2@example.com", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .orcpt = { + .addr = &test_address3 + } + } + }, + { + .input = "ORCPT=rfc822;user+2Bdetail", + .flags = SMTP_PARAM_RCPT_FLAG_ORCPT_ALLOW_LOCALPART, + .caps = SMTP_CAPABILITY_DSN, + .params = { + .orcpt = { + .addr = &test_address2 + } + } + }, + { + .input = "ORCPT=rfc822;", + .output = "ORCPT=rfc822;user+2Bdetail", + .flags = SMTP_PARAM_RCPT_FLAG_ORCPT_ALLOW_LOCALPART, + .caps = SMTP_CAPABILITY_DSN, + .params = { + .orcpt = { + .addr = &test_address2 + } + } + }, + /* NOTIFY */ + { + .input = "", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .notify = SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED, + } + }, + { + .input = "NOTIFY=SUCCESS", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .notify = SMTP_PARAM_RCPT_NOTIFY_SUCCESS, + } + }, + { + .input = "NOTIFY=FAILURE", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .notify = SMTP_PARAM_RCPT_NOTIFY_FAILURE, + } + }, + { + .input = "NOTIFY=DELAY", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .notify = SMTP_PARAM_RCPT_NOTIFY_DELAY, + } + }, + { + .input = "NOTIFY=NEVER", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .notify = SMTP_PARAM_RCPT_NOTIFY_NEVER, + } + }, + { + .input = "NOTIFY=SUCCESS,FAILURE,DELAY", + .caps = SMTP_CAPABILITY_DSN, + .params = { + .notify = SMTP_PARAM_RCPT_NOTIFY_SUCCESS | + SMTP_PARAM_RCPT_NOTIFY_FAILURE | + SMTP_PARAM_RCPT_NOTIFY_DELAY, + } + }, + /* */ + { + .input = "FROP=friep", + .caps = SMTP_CAPABILITY_SIZE, + .extensions = test_extensions, + .params = { + .extra_params = { + .arr = { + .buffer = &test_params_buffer1, + .element_size = sizeof(struct smtp_param) + } + } + } + }, + { + .input = "FROP=friep FRUP=frml", + .extensions = test_extensions, + .params = { + .extra_params = { + .arr = { + .buffer = &test_params_buffer2, + .element_size = sizeof(struct smtp_param) + } + } + } + } +}; + +unsigned int valid_rcpt_params_parse_test_count = + N_ELEMENTS(valid_rcpt_params_parse_tests); + +static void +test_smtp_rcpt_params_orcpt(const struct smtp_params_rcpt *test, + const struct smtp_params_rcpt *parsed) +{ + if (parsed->orcpt.addr == NULL) { + test_out("params.orcpt.addr = NULL", + test->orcpt.addr == NULL); + return; + } + + if (parsed->orcpt.addr->localpart == NULL || + test->orcpt.addr->localpart == NULL) { + test_out(t_strdup_printf("params.orcpt.addr->localpart = %s", + parsed->orcpt.addr->localpart), + (parsed->orcpt.addr->localpart == + test->orcpt.addr->localpart)); + } else { + test_out(t_strdup_printf("params.orcpt.addr->localpart = \"%s\"", + parsed->orcpt.addr->localpart), + strcmp(parsed->orcpt.addr->localpart, + test->orcpt.addr->localpart) == 0); + } + if (parsed->orcpt.addr->domain == NULL || + test->orcpt.addr->domain == NULL) { + test_out(t_strdup_printf("params.orcpt.addr->domain = %s", + parsed->orcpt.addr->domain), + (parsed->orcpt.addr->domain == + test->orcpt.addr->domain)); + } else { + test_out(t_strdup_printf("params.orcpt.addr->domain = \"%s\"", + parsed->orcpt.addr->domain), + strcmp(parsed->orcpt.addr->domain, + test->orcpt.addr->domain) == 0); + } +} + + +static void +test_smtp_rcpt_params_notify(const struct smtp_params_rcpt *test, + const struct smtp_params_rcpt *parsed) +{ + string_t *notify_name; + + notify_name = t_str_new(64); + if (parsed->notify == 0) { + str_append(notify_name, ""); + } else if ((parsed->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) { + i_assert((parsed->notify & SMTP_PARAM_RCPT_NOTIFY_SUCCESS) == 0); + i_assert((parsed->notify & SMTP_PARAM_RCPT_NOTIFY_FAILURE) == 0); + i_assert((parsed->notify & SMTP_PARAM_RCPT_NOTIFY_DELAY) == 0); + str_append(notify_name, "NEVER"); + } else { + if ((parsed->notify & SMTP_PARAM_RCPT_NOTIFY_SUCCESS) != 0) + str_append(notify_name, "SUCCESS"); + if ((parsed->notify & SMTP_PARAM_RCPT_NOTIFY_FAILURE) != 0) { + if (str_len(notify_name) > 0) + str_append_c(notify_name, ','); + str_append(notify_name, "FAILURE"); + } + if ((parsed->notify & SMTP_PARAM_RCPT_NOTIFY_DELAY) != 0) { + if (str_len(notify_name) > 0) + str_append_c(notify_name, ','); + str_append(notify_name, "DELAY"); + } + } + + test_out(t_strdup_printf("params.notify = %s", str_c(notify_name)), + parsed->notify == test->notify); +} + +static void +test_smtp_rcpt_params_extensions(const struct smtp_params_rcpt *test, + const struct smtp_params_rcpt *parsed) +{ + const struct smtp_param *tparam, *pparam; + unsigned int i; + + if (!array_is_created(&test->extra_params) || + array_count(&test->extra_params) == 0) { + test_out(t_strdup_printf("params.extra_params.count = %u", + (!array_is_created(&parsed->extra_params) ? + 0 : array_count(&parsed->extra_params))), + (!array_is_created(&parsed->extra_params) || + array_count(&parsed->extra_params) == 0)); + return; + } + + if (!array_is_created(&parsed->extra_params) || + array_count(&parsed->extra_params) == 0) { + test_out("params.extra_params.count = 0", FALSE); + return; + } + + if (array_count(&test->extra_params) != + array_count(&parsed->extra_params)) { + test_out(t_strdup_printf("params.extra_params.count = %u", + (!array_is_created(&parsed->extra_params) ? 0 : + array_count(&parsed->extra_params))), FALSE); + return; + } + + for (i = 0; i < array_count(&test->extra_params); i++) { + tparam = array_idx(&test->extra_params, i); + pparam = array_idx(&parsed->extra_params, i); + + test_out(t_strdup_printf("params.extra_params[%u] = [\"%s\"=\"%s\"]", + i, pparam->keyword, pparam->value), + strcmp(pparam->keyword, tparam->keyword) == 0 && + ((pparam->value == NULL && tparam->value == NULL) || + (pparam->value != NULL && tparam->value != NULL && + strcmp(pparam->value, tparam->value) == 0))); + } +} + +static void test_smtp_rcpt_params_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_rcpt_params_parse_test_count; i++) T_BEGIN { + const struct valid_rcpt_params_parse_test *test; + struct smtp_params_rcpt params; + enum smtp_param_parse_error error_code; + const char *error = NULL, *output; + int ret; + + test = &valid_rcpt_params_parse_tests[i]; + ret = smtp_params_rcpt_parse(pool_datastack_create(), + test->input, test->flags, + test->caps, test->extensions, + ¶ms, &error_code, &error); + + test_begin(t_strdup_printf("smtp rcpt params valid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", + test->input), ret >= 0, error); + + if (ret >= 0) { + string_t *encoded; + + /* ORCPT */ + if ((test->caps & SMTP_CAPABILITY_DSN) != 0) + test_smtp_rcpt_params_orcpt(&test->params, ¶ms); + /* NOTIFY */ + if ((test->caps & SMTP_CAPABILITY_DSN) != 0) + test_smtp_rcpt_params_notify(&test->params, ¶ms); + /* */ + if (test->extensions != NULL) + test_smtp_rcpt_params_extensions(&test->params, ¶ms); + + encoded = t_str_new(256); + smtp_params_rcpt_write(encoded, test->caps, + test->extensions, ¶ms); + + output = (test->output == NULL ? test->input : test->output); + test_out(t_strdup_printf("encode() = \"%s\"", + str_c(encoded)), + strcmp(str_c(encoded), output) == 0); + } + test_end(); + } T_END; +} + +/* Invalid rcpt params tests */ + +struct invalid_rcpt_params_parse_test { + const char *input; + + enum smtp_param_rcpt_parse_flags flags; + enum smtp_capability caps; + const char *const *extensions; +}; + +static const struct invalid_rcpt_params_parse_test +invalid_rcpt_params_parse_tests[] = { + /* DSN */ + { + .input = "ORCPT=rfc822;frop@example.com", + }, + { + .input = "ORCPT=rfc822;<>", + .caps = SMTP_CAPABILITY_DSN + }, + { + .input = "ORCPT=rfc822;", + .caps = SMTP_CAPABILITY_DSN + }, + { + .input = "ORCPT=++", + .caps = SMTP_CAPABILITY_DSN + }, + { + .input = "ORCPT=rfc822;++", + .caps = SMTP_CAPABILITY_DSN + }, + { + .input = "NOTIFY=SUCCESS", + }, + { + .input = "NOTIFY=FROP", + .caps = SMTP_CAPABILITY_DSN + }, + { + .input = "NOTIFY=NEVER,SUCCESS", + .caps = SMTP_CAPABILITY_DSN + } +}; + +unsigned int invalid_rcpt_params_parse_test_count = + N_ELEMENTS(invalid_rcpt_params_parse_tests); + +static void test_smtp_rcpt_params_parse_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_rcpt_params_parse_test_count; i++) T_BEGIN { + const struct invalid_rcpt_params_parse_test *test; + struct smtp_params_rcpt params; + enum smtp_param_parse_error error_code; + const char *error = NULL; + int ret; + + test = &invalid_rcpt_params_parse_tests[i]; + ret = smtp_params_rcpt_parse(pool_datastack_create(), + test->input, test->flags, + test->caps, test->extensions, + ¶ms, &error_code, &error); + + test_begin(t_strdup_printf("smtp rcpt params invalid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret < 0, error); + test_end(); + } T_END; +} + +int main(void) +{ + static void (*test_functions[])(void) = { + test_smtp_mail_params_parse_valid, + test_smtp_mail_params_parse_invalid, + test_smtp_rcpt_params_parse_valid, + test_smtp_rcpt_params_parse_invalid, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-smtp/test-smtp-payload.c b/src/lib-smtp/test-smtp-payload.c new file mode 100644 index 0000000..7363736 --- /dev/null +++ b/src/lib-smtp/test-smtp-payload.c @@ -0,0 +1,1148 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "llist.h" +#include "array.h" +#include "path-util.h" +#include "hostpid.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "istream-base64.h" +#include "istream-crlf.h" +#include "iostream-temp.h" +#include "iostream-ssl.h" +#include "iostream-ssl-test.h" +#ifdef HAVE_OPENSSL +#include "iostream-openssl.h" +#endif +#include "connection.h" +#include "test-common.h" +#include "test-subprocess.h" +#include "smtp-server.h" +#include "smtp-client.h" +#include "smtp-client-connection.h" +#include "smtp-client-transaction.h" + +#include +#include +#include +#include +#include + +#define CLIENT_PROGRESS_TIMEOUT 60 +#define SERVER_KILL_TIMEOUT_SECS 20 +#define MAX_PARALLEL_PENDING 200 + +static bool debug = FALSE; +static bool small_socket_buffers = FALSE; +static const char *failure = NULL; + +enum test_ssl_mode { + TEST_SSL_MODE_NONE = 0, + TEST_SSL_MODE_IMMEDIATE, + TEST_SSL_MODE_STARTTLS +}; + +static unsigned int test_max_pending = 1; +static bool test_unknown_size = FALSE; +static enum test_ssl_mode test_ssl_mode = TEST_SSL_MODE_NONE; + +static struct ip_addr bind_ip; +static in_port_t bind_port = 0; +static int fd_listen = -1; + +static void main_deinit(void); + +/* + * Test files + */ + +static ARRAY_TYPE(const_string) files; +static pool_t files_pool; + +static void test_files_read_dir(const char *path) +{ + DIR *dirp; + + /* open the directory */ + if ((dirp = opendir(path)) == NULL) { + if (errno == ENOENT || errno == EACCES) + return; + i_fatal("test files: " + "failed to open directory %s: %m", path); + } + + /* read entries */ + for (;;) { + const char *file; + struct dirent *dp; + struct stat st; +#if 0 + if (array_count(&files) > 10) + break; +#endif + errno = 0; + if ((dp = readdir(dirp)) == NULL) + break; + if (*dp->d_name == '.') + continue; + + file = t_abspath_to(dp->d_name, path); + if (stat(file, &st) == 0) { + if (S_ISREG(st.st_mode)) { + file += 2; /* skip "./" */ + file = p_strdup(files_pool, file); + array_push_back(&files, &file); + } else if (S_ISDIR(st.st_mode)) { + test_files_read_dir(file); + } + } + } + + if (errno != 0) + i_fatal("test files: " + "failed to read directory %s: %m", path); + + /* Close the directory */ + if (closedir(dirp) < 0) + i_error("test files: " + "failed to close directory %s: %m", path); +} + +static void test_files_init(void) +{ + /* initialize file array */ + files_pool = pool_alloconly_create( + MEMPOOL_GROWING"smtp_server_request", 4096); + p_array_init(&files, files_pool, 512); + + /* obtain all filenames */ + test_files_read_dir("."); +} + +static void test_files_deinit(void) +{ + pool_unref(&files_pool); +} + +static struct istream *test_file_open(const char *path) +{ + struct istream *file; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) { + if (errno != ENOENT && errno != EACCES) { + i_fatal("test files: " + "open(%s) failed: %m", path); + } + if (debug) { + i_debug("test files: " + "open(%s) failed: %m", path); + } + return NULL; + } + + file = i_stream_create_fd_autoclose(&fd, 40960); + i_stream_set_name(file, path); + return file; +} + +/* + * Test server + */ + +struct client { + pool_t pool; + struct client *prev, *next; + + struct smtp_server_connection *smtp_conn; +}; + +struct client_transaction { + struct client *client; + struct smtp_server_cmd_ctx *data_cmd; + struct smtp_server_transaction *trans; + + const char *path; + + struct istream *payload, *file; +}; + +static struct smtp_server *smtp_server; + +static struct io *io_listen; +static struct client *clients; + +static int +client_transaction_read_more(struct client_transaction *ctrans) +{ + struct istream *payload = ctrans->payload; + const unsigned char *pdata, *fdata; + size_t psize, fsize, pleft; + off_t ret; + + if (debug) { + i_debug("test server: read more payload for [%s]", + ctrans->path); + } + + /* read payload */ + while ((ret = i_stream_read_more(payload, &pdata, &psize)) > 0) { + if (debug) { + i_debug("test server: " + "got data for [%s] (size=%d)", + ctrans->path, (int)psize); + } + /* compare with file on disk */ + pleft = psize; + while ((ret = i_stream_read_more(ctrans->file, + &fdata, &fsize)) > 0 && + pleft > 0) { + fsize = (fsize > pleft ? pleft : fsize); + if (memcmp(pdata, fdata, fsize) != 0) { + i_fatal("test server: " + "received data does not match file [%s] " + "(%"PRIuUOFF_T":%"PRIuUOFF_T")", + ctrans->path, payload->v_offset, + ctrans->file->v_offset); + } + i_stream_skip(ctrans->file, fsize); + pleft -= fsize; + pdata += fsize; + } + if (ret < 0 && ctrans->file->stream_errno != 0) { + i_fatal("test server: " + "failed to read file: %s", + i_stream_get_error(ctrans->file)); + } + i_stream_skip(payload, psize); + } + + if (ret == 0) { + if (debug) { + i_debug("test server: " + "need more data for [%s]", + ctrans->path); + } + /* we will be called again for this request */ + return 0; + } + + (void)i_stream_read(ctrans->file); + if (payload->stream_errno != 0) { + i_fatal("test server: " + "failed to read transaction payload: %s", + i_stream_get_error(payload)); + } + if (i_stream_have_bytes_left(ctrans->file)) { + if (i_stream_read_more(ctrans->file, &fdata, &fsize) <= 0) + fsize = 0; + i_fatal("test server: " + "payload ended prematurely " + "(at least %zu bytes left)", fsize); + } + + if (debug) { + i_debug("test server: " + "finished transaction for [%s]", + ctrans->path); + } + + /* dereference payload stream; finishes the request */ + i_stream_unref(&payload); + ctrans->payload = NULL; + i_stream_unref(&ctrans->file); + + /* finished */ + smtp_server_reply_all(ctrans->data_cmd, 250, "2.0.0", "OK"); + return 1; +} + +static void +client_transaction_handle_payload(struct client_transaction *ctrans, + const char *path, struct istream *data_input) +{ + struct smtp_server_transaction *trans = ctrans->trans; + struct istream *fstream; + + ctrans->path = p_strdup(trans->pool, path); + + if (debug) { + i_debug("test server: got transaction for: %s", + path); + } + + fstream = test_file_open(path); + if (fstream == NULL) + i_fatal("test server: failed to open: %s", path); + + i_stream_ref(data_input); + ctrans->payload = data_input; + i_assert(ctrans->payload != NULL); + + ctrans->file = i_stream_create_base64_encoder(fstream, 80, TRUE), + i_stream_unref(&fstream); + + (void)client_transaction_read_more(ctrans); +} + +/* transaction */ + +static struct client_transaction * +client_transaction_init(struct client *client, + struct smtp_server_cmd_ctx *data_cmd, + struct smtp_server_transaction *trans) +{ + struct client_transaction *ctrans; + pool_t pool = trans->pool; + + ctrans = p_new(pool, struct client_transaction, 1); + ctrans->client = client; + ctrans->trans = trans; + ctrans->data_cmd = data_cmd; + + return ctrans; +} + +static void client_transaction_deinit(struct client_transaction **_ctrans) +{ + struct client_transaction *ctrans = *_ctrans; + + *_ctrans = NULL; + + i_stream_unref(&ctrans->payload); + i_stream_unref(&ctrans->file); +} + +static void +test_server_conn_trans_free(void *context ATTR_UNUSED, + struct smtp_server_transaction *trans) +{ + struct client_transaction *ctrans = + (struct client_transaction *)trans->context; + client_transaction_deinit(&ctrans); +} + +static int +test_server_conn_cmd_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt) +{ + if (debug) { + i_debug("test server: RCPT TO:%s", + smtp_address_encode(rcpt->path)); + } + + return 1; +} + +static int +test_server_conn_cmd_data_begin(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, + struct istream *data_input) +{ + struct client *client = (struct client *)conn_ctx; + const char *fpath = trans->params.envid; + struct client_transaction *ctrans; + + i_assert(fpath != NULL); + + if (debug) + i_debug("test server: DATA (file path = %s)", fpath); + + ctrans = client_transaction_init(client, cmd, trans); + client_transaction_handle_payload(ctrans, fpath, data_input); + trans->context = ctrans; + return 0; +} + +static int +test_server_conn_cmd_data_continue(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans) +{ + struct client_transaction *ctrans = + (struct client_transaction *)trans->context; + + if (debug) + i_debug("test server: DATA continue"); + + ctrans->data_cmd = cmd; + + return client_transaction_read_more(ctrans); +} + +/* client connection */ + +static void test_server_connection_free(void *context); + +static const struct smtp_server_callbacks server_callbacks = +{ + .conn_cmd_rcpt = test_server_conn_cmd_rcpt, + .conn_cmd_data_begin = test_server_conn_cmd_data_begin, + .conn_cmd_data_continue = test_server_conn_cmd_data_continue, + + .conn_trans_free = test_server_conn_trans_free, + + .conn_free = test_server_connection_free, +}; + +static void client_init(int fd) +{ + struct client *client; + pool_t pool; + + net_set_nonblock(fd, TRUE); + + pool = pool_alloconly_create("client", 256); + client = p_new(pool, struct client, 1); + client->pool = pool; + + client->smtp_conn = smtp_server_connection_create( + smtp_server, fd, fd, NULL, 0, + (test_ssl_mode == TEST_SSL_MODE_IMMEDIATE), + NULL, &server_callbacks, client); + smtp_server_connection_start(client->smtp_conn); + DLLIST_PREPEND(&clients, client); +} + +static void client_deinit(struct client **_client) +{ + struct client *client = *_client; + + *_client = NULL; + + DLLIST_REMOVE(&clients, client); + + if (client->smtp_conn != NULL) { + smtp_server_connection_terminate(&client->smtp_conn, + NULL, "deinit"); + } + pool_unref(&client->pool); +} + +static void test_server_connection_free(void *context) +{ + struct client *client = context; + + client->smtp_conn = NULL; + client_deinit(&client); +} + +static void client_accept(void *context ATTR_UNUSED) +{ + int fd; + + for (;;) { + /* accept new client */ + if ((fd = net_accept(fd_listen, NULL, NULL)) < 0) { + if (errno == EAGAIN) + break; + if (errno == ECONNABORTED) + continue; + i_fatal("test server: accept() failed: %m"); + } + + client_init(fd); + } +} + +/* */ + +static void test_server_init(const struct smtp_server_settings *server_set) +{ + /* open server socket */ + io_listen = io_add(fd_listen, IO_READ, client_accept, NULL); + + smtp_server = smtp_server_init(server_set); +} + +static void test_server_deinit(void) +{ + /* close server socket */ + io_remove(&io_listen); + + /* deinitialize */ + smtp_server_deinit(&smtp_server); +} + +/* + * Test client + */ + +struct test_client_connection { + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; +}; + +struct test_client_transaction { + struct test_client_transaction *prev, *next; + struct test_client_connection *conn; + + struct io *io; + struct istream *file; + unsigned int files_idx; +}; + +static struct test_client_connection test_conns[MAX_PARALLEL_PENDING]; +static struct smtp_client *smtp_client; +static enum smtp_protocol client_protocol; +static struct test_client_transaction *client_requests; +static unsigned int client_files_first, client_files_last; +static struct timeout *client_to = NULL; +struct timeout *to_client_progress = NULL; + +static struct test_client_connection *test_client_connection_get(void) +{ + unsigned int i; + enum smtp_client_connection_ssl_mode ssl_mode; + + for (i = 0; i < MAX_PARALLEL_PENDING; i++) { + if (test_conns[i].trans == NULL) + break; + } + + i_assert(i < MAX_PARALLEL_PENDING); + + switch (test_ssl_mode) { + case TEST_SSL_MODE_NONE: + default: + ssl_mode = SMTP_CLIENT_SSL_MODE_NONE; + break; + case TEST_SSL_MODE_IMMEDIATE: + ssl_mode = SMTP_CLIENT_SSL_MODE_IMMEDIATE; + break; + case TEST_SSL_MODE_STARTTLS: + ssl_mode = SMTP_CLIENT_SSL_MODE_STARTTLS; + break; + } + + if (test_conns[i].conn == NULL) { + test_conns[i].conn = smtp_client_connection_create( + smtp_client, client_protocol, + net_ip2addr(&bind_ip), bind_port, + ssl_mode, NULL); + } + return &test_conns[i]; +} + +static struct test_client_transaction *test_client_transaction_new(void) +{ + struct test_client_transaction *tctrans; + + tctrans = i_new(struct test_client_transaction, 1); + DLLIST_PREPEND(&client_requests, tctrans); + + return tctrans; +} + +static void +test_client_transaction_destroy(struct test_client_transaction *tctrans) +{ + smtp_client_transaction_destroy(&tctrans->conn->trans); + io_remove(&tctrans->io); + i_stream_unref(&tctrans->file); + + DLLIST_REMOVE(&client_requests, tctrans); + i_free(tctrans); +} + +static void test_client_continue(void *dummy); + +static void test_client_finished(unsigned int files_idx) +{ + const char **paths; + unsigned int count; + + if (debug) { + i_debug("test client: " + "finished [%u]", files_idx); + } + + paths = array_get_modifiable(&files, &count); + i_assert(files_idx < count); + i_assert(client_files_first < count); + i_assert(paths[files_idx] != NULL); + + paths[files_idx] = NULL; + if (client_to == NULL) + client_to = timeout_add_short(0, test_client_continue, NULL); +} + +static void +test_client_transaction_finish(struct test_client_transaction *tctrans) +{ + tctrans->conn->trans = NULL; + if (io_loop_is_running(current_ioloop)) + test_client_finished(tctrans->files_idx); + test_client_transaction_destroy(tctrans); +} + +static void +test_client_transaction_rcpt(const struct smtp_reply *reply, + struct test_client_transaction *tctrans) +{ + const char **paths; + const char *path; + unsigned int count; + + if (to_client_progress != NULL) + timeout_reset(to_client_progress); + + paths = array_get_modifiable(&files, &count); + i_assert(tctrans->files_idx < count); + i_assert(client_files_first < count); + path = paths[tctrans->files_idx]; + i_assert(path != NULL); + + if (reply->status / 100 != 2) { + i_fatal("test client: " + "SMTP RCPT for %s failed: %s", + path, smtp_reply_log(reply)); + } +} + +static void +test_client_transaction_rcpt_data(const struct smtp_reply *reply ATTR_UNUSED, + struct test_client_transaction *tctrans) +{ + const char **paths; + const char *path; + unsigned int count; + + if (to_client_progress != NULL) + timeout_reset(to_client_progress); + + paths = array_get_modifiable(&files, &count); + i_assert(tctrans->files_idx < count); + i_assert(client_files_first < count); + path = paths[tctrans->files_idx]; + i_assert(path != NULL); + + if (reply->status / 100 != 2) { + i_fatal("test client: " + "SMTP DATA for %s failed: %s", + path, smtp_reply_log(reply)); + } +} + +static void +test_client_transaction_data(const struct smtp_reply *reply, + struct test_client_transaction *tctrans) +{ + const char **paths; + const char *path; + unsigned int count; + + if (to_client_progress != NULL) + timeout_reset(to_client_progress); + + if (debug) { + i_debug("test client: " + "got response for DATA [%u]", + tctrans->files_idx); + } + + paths = array_get_modifiable(&files, &count); + i_assert(tctrans->files_idx < count); + i_assert(client_files_first < count); + path = paths[tctrans->files_idx]; + i_assert(path != NULL); + + if (debug) { + i_debug("test client: " + "path for [%u]: %s", + tctrans->files_idx, path); + } + + if (reply->status / 100 != 2) { + i_fatal("test client: " + "SMTP transaction for %s failed: %s", + path, smtp_reply_log(reply)); + } +} + +static void test_client_continue(void *dummy ATTR_UNUSED) +{ + struct test_client_transaction *tctrans; + struct smtp_params_mail mail_params; + const char **paths; + unsigned int count, pending_count, i; + + if (debug) + i_debug("test client: continue"); + + timeout_remove(&client_to); + if (to_client_progress != NULL) + timeout_reset(to_client_progress); + + paths = array_get_modifiable(&files, &count); + + i_assert(client_files_first <= count); + i_assert(client_files_last <= count); + + i_assert(client_files_first <= client_files_last); + for (; (client_files_first < client_files_last && + paths[client_files_first] == NULL); client_files_first++); + + pending_count = 0; + for (i = client_files_first; i < client_files_last; i++) { + if (paths[i] != NULL) + pending_count++; + } + + if (debug) { + i_debug("test client: finished until [%u/%u]; " + "sending until [%u/%u] (%u pending)", + client_files_first-1, count, + client_files_last, count, pending_count); + } + + if (debug && client_files_first < count) { + const char *path = paths[client_files_first]; + i_debug("test client: " + "next blocking: %s [%d]", + (path == NULL ? "none" : path), + client_files_first); + } + + if (client_files_first >= count) { + io_loop_stop(current_ioloop); + return; + } + + for (; client_files_last < count && pending_count < test_max_pending; + client_files_last++, pending_count++) { + struct istream *fstream, *payload; + const char *path = paths[client_files_last]; + unsigned int r, rcpts; + + fstream = test_file_open(path); + if (fstream == NULL) { + paths[client_files_last] = NULL; + if (debug) { + i_debug("test client: " + "skipping %s [%u]", + path, client_files_last); + } + if (client_to == NULL) { + client_to = timeout_add_short( + 0, test_client_continue, NULL); + } + continue; + } + + if (debug) { + i_debug("test client: " + "retrieving %s [%u]", + path, client_files_last); + } + + tctrans = test_client_transaction_new(); + tctrans->files_idx = client_files_last; + tctrans->conn = test_client_connection_get(); + + i_zero(&mail_params); + mail_params.envid = path; + + tctrans->conn->trans = smtp_client_transaction_create( + tctrans->conn->conn, + &((struct smtp_address){.localpart = "user", + .domain = "example.com"}), + &mail_params, 0, + test_client_transaction_finish, tctrans); + + rcpts = tctrans->files_idx % 10 + 1; + for (r = 1; r <= rcpts; r++) { + smtp_client_transaction_add_rcpt( + tctrans->conn->trans, + smtp_address_create_temp( + t_strdup_printf("rcpt%u", r), + "example.com"), NULL, + test_client_transaction_rcpt, + test_client_transaction_rcpt_data, tctrans); + } + + if (!test_unknown_size) { + payload = i_stream_create_base64_encoder( + fstream, 80, TRUE); + } else { + struct istream *b64_stream = + i_stream_create_base64_encoder( + fstream, 80, FALSE); + payload = i_stream_create_crlf(b64_stream); + i_stream_unref(&b64_stream); + } + + if (debug) { + uoff_t raw_size = UOFF_T_MAX, b64_size = UOFF_T_MAX; + + (void)i_stream_get_size(fstream, TRUE, &raw_size); + (void)i_stream_get_size(payload, TRUE, &b64_size); + i_debug("test client: " + "sending %"PRIuUOFF_T"/%"PRIuUOFF_T" bytes payload %s [%u]", + raw_size, b64_size, path, client_files_last); + } + + smtp_client_transaction_send(tctrans->conn->trans, payload, + test_client_transaction_data, + tctrans); + + i_stream_unref(&payload); + i_stream_unref(&fstream); + } +} + +static void test_client_progress_timeout(void *context ATTR_UNUSED) +{ + /* Terminate test due to lack of progress */ + failure = "Test is hanging"; + timeout_remove(&to_client_progress); + io_loop_stop(current_ioloop); +} + +static void +test_client(enum smtp_protocol protocol, + const struct smtp_client_settings *client_set) +{ + client_protocol = protocol; + + if (!small_socket_buffers) { + to_client_progress = timeout_add( + CLIENT_PROGRESS_TIMEOUT*1000, + test_client_progress_timeout, NULL); + } + + /* create client */ + smtp_client = smtp_client_init(client_set); + + /* start querying server */ + client_files_first = client_files_last = 0; + test_client_continue(NULL); +} + +static void test_client_init(void) +{ + i_zero(&test_conns); +} + +static void test_client_deinit(void) +{ + timeout_remove(&client_to); + timeout_remove(&to_client_progress); + smtp_client_deinit(&smtp_client); + + i_zero(&test_conns); +} + +/* + * Tests + */ + +struct test_server_data { + const struct smtp_server_settings *set; +}; + +static void test_open_server_fd(void) +{ + if (fd_listen != -1) + i_close_fd(&fd_listen); + fd_listen = net_listen(&bind_ip, &bind_port, 128); + if (fd_listen == -1) { + i_fatal("listen(%s:%u) failed: %m", + net_ip2addr(&bind_ip), bind_port); + } + net_set_nonblock(fd_listen, TRUE); +} + +static int test_run_server(struct test_server_data *data) +{ + const struct smtp_server_settings *server_set = data->set; + struct ioloop *ioloop; + + i_set_failure_prefix("SERVER: "); + + if (debug) + i_debug("PID=%s", my_pid); + + ioloop = io_loop_create(); + test_server_init(server_set); + io_loop_run(ioloop); + test_server_deinit(); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); + + i_close_fd(&fd_listen); + test_files_deinit(); + main_deinit(); + return 0; +} + +static void +test_run_client( + enum smtp_protocol protocol, struct smtp_client_settings *client_set, + void (*client_init)(enum smtp_protocol protocol, + const struct smtp_client_settings *client_set)) +{ + struct ioloop *ioloop; + + i_set_failure_prefix("CLIENT: "); + + if (debug) + i_debug("client: PID=%s", my_pid); + + ioloop = io_loop_create(); + test_client_init(); + client_init(protocol, client_set); + io_loop_run(ioloop); + test_client_deinit(); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); +} + +static void +test_run_client_server( + enum smtp_protocol protocol, + struct smtp_client_settings *client_set, + struct smtp_server_settings *server_set, + void (*client_init)(enum smtp_protocol protocol, + const struct smtp_client_settings *client_set)) +{ + struct test_server_data data; + + if (test_ssl_mode == TEST_SSL_MODE_STARTTLS) + server_set->capabilities |= SMTP_CAPABILITY_STARTTLS; + + failure = NULL; + + i_zero(&data); + data.set = server_set; + + test_files_init(); + + /* Fork server */ + test_open_server_fd(); + test_subprocess_fork(test_run_server, &data, FALSE); + i_close_fd(&fd_listen); + + /* Run client */ + test_run_client(protocol, client_set, client_init); + + i_unset_failure_prefix(); + bind_port = 0; + test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS); + test_files_deinit(); +} + +static void +test_run_scenarios( + enum smtp_protocol protocol, + enum smtp_capability capabilities, + void (*client_init)(enum smtp_protocol protocol, + const struct smtp_client_settings *client_set)) +{ + struct smtp_server_settings smtp_server_set; + struct smtp_client_settings smtp_client_set; + struct ssl_iostream_settings ssl_server_set, ssl_client_set; + + /* ssl settings */ + ssl_iostream_test_settings_server(&ssl_server_set); + ssl_server_set.verbose = debug; + ssl_iostream_test_settings_client(&ssl_client_set); + ssl_client_set.verbose = debug; + + /* server settings */ + i_zero(&smtp_server_set); + smtp_server_set.protocol = protocol; + smtp_server_set.capabilities = capabilities; + smtp_server_set.hostname = "localhost"; + smtp_server_set.max_client_idle_time_msecs = + CLIENT_PROGRESS_TIMEOUT*1000; + smtp_server_set.max_pipelined_commands = 1; + smtp_server_set.auth_optional = TRUE; + smtp_server_set.ssl = &ssl_server_set; + smtp_server_set.debug = debug; + + /* client settings */ + i_zero(&smtp_client_set); + smtp_client_set.my_hostname = "localhost"; + smtp_client_set.temp_path_prefix = "/tmp"; + smtp_client_set.command_timeout_msecs = CLIENT_PROGRESS_TIMEOUT*1000; + smtp_client_set.connect_timeout_msecs = CLIENT_PROGRESS_TIMEOUT*1000; + smtp_client_set.ssl = &ssl_client_set; + smtp_client_set.debug = debug; + + if (small_socket_buffers) { + smtp_client_set.socket_send_buffer_size = 4096; + smtp_client_set.socket_recv_buffer_size = 4096; + smtp_client_set.command_timeout_msecs = 20*60*1000; + smtp_client_set.connect_timeout_msecs = 20*60*1000; + smtp_server_set.socket_send_buffer_size = 4096; + smtp_server_set.socket_recv_buffer_size = 4096; + } + + test_max_pending = 1; + test_unknown_size = FALSE; + test_ssl_mode = TEST_SSL_MODE_NONE; + test_run_client_server(protocol, &smtp_client_set, &smtp_server_set, + client_init); + + test_out_reason("sequential", (failure == NULL), failure); + + test_max_pending = MAX_PARALLEL_PENDING; + test_unknown_size = FALSE; + test_ssl_mode = TEST_SSL_MODE_NONE; + test_run_client_server(protocol, &smtp_client_set, &smtp_server_set, + client_init); + + test_out_reason("parallel", (failure == NULL), failure); + + smtp_server_set.max_pipelined_commands = 5; + smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING; + test_max_pending = MAX_PARALLEL_PENDING; + test_unknown_size = FALSE; + test_ssl_mode = TEST_SSL_MODE_NONE; + test_run_client_server(protocol, &smtp_client_set, &smtp_server_set, + client_init); + + test_out_reason("parallel pipelining", (failure == NULL), failure); + + smtp_server_set.max_pipelined_commands = 5; + smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING; + test_max_pending = MAX_PARALLEL_PENDING; + test_unknown_size = TRUE; + test_ssl_mode = TEST_SSL_MODE_NONE; + test_run_client_server(protocol, &smtp_client_set, &smtp_server_set, + client_init); + + test_out_reason("unknown payload size", (failure == NULL), failure); + +#ifdef HAVE_OPENSSL + smtp_server_set.max_pipelined_commands = 5; + smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING; + test_max_pending = MAX_PARALLEL_PENDING; + test_unknown_size = FALSE; + test_ssl_mode = TEST_SSL_MODE_IMMEDIATE; + test_run_client_server(protocol, &smtp_client_set, &smtp_server_set, + client_init); + + test_out_reason("parallel pipelining ssl", + (failure == NULL), failure); + + smtp_server_set.max_pipelined_commands = 5; + smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING; + test_max_pending = MAX_PARALLEL_PENDING; + test_unknown_size = FALSE; + test_ssl_mode = TEST_SSL_MODE_STARTTLS; + test_run_client_server(protocol, &smtp_client_set, &smtp_server_set, + client_init); + + test_out_reason("parallel pipelining startls", + (failure == NULL), failure); +#endif +} + +static void test_smtp_normal(void) +{ + test_begin("smtp payload - normal"); + test_run_scenarios(SMTP_PROTOCOL_SMTP, + SMTP_CAPABILITY_DSN, test_client); + test_end(); +} + +static void test_smtp_chunking(void) +{ + test_begin("smtp payload - chunking"); + test_run_scenarios(SMTP_PROTOCOL_SMTP, + SMTP_CAPABILITY_DSN | SMTP_CAPABILITY_CHUNKING, + test_client); + test_end(); +} + +static void test_lmtp_normal(void) +{ + test_begin("lmtp payload - normal"); + test_run_scenarios(SMTP_PROTOCOL_LMTP, + SMTP_CAPABILITY_DSN, test_client); + test_end(); +} + +static void test_lmtp_chunking(void) +{ + test_begin("lmtp payload - chunking"); + test_run_scenarios(SMTP_PROTOCOL_LMTP, + SMTP_CAPABILITY_DSN | SMTP_CAPABILITY_CHUNKING, + test_client); + test_end(); +} + +static void (*const test_functions[])(void) = { + test_smtp_normal, + test_smtp_chunking, + test_lmtp_normal, + test_lmtp_chunking, + NULL +}; + +/* + * Main + */ + +static void main_init(void) +{ +#ifdef HAVE_OPENSSL + ssl_iostream_openssl_init(); +#endif +} + +static void main_deinit(void) +{ + ssl_iostream_context_cache_free(); +#ifdef HAVE_OPENSSL + ssl_iostream_openssl_deinit(); +#endif +} + +int main(int argc, char *argv[]) +{ + int c; + int ret; + + lib_init(); + main_init(); + + while ((c = getopt(argc, argv, "DS")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + case 'S': + small_socket_buffers = TRUE; + break; + default: + i_fatal("Usage: %s [-D][-S]", argv[0]); + } + } + + test_subprocesses_init(debug); + + /* listen on localhost */ + i_zero(&bind_ip); + bind_ip.family = AF_INET; + bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); + + ret = test_run(test_functions); + + test_subprocesses_deinit(); + main_deinit(); + lib_deinit(); + + return ret; +} diff --git a/src/lib-smtp/test-smtp-reply.c b/src/lib-smtp/test-smtp-reply.c new file mode 100644 index 0000000..69ded10 --- /dev/null +++ b/src/lib-smtp/test-smtp-reply.c @@ -0,0 +1,279 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "array.h" +#include "buffer.h" +#include "str.h" +#include "str-sanitize.h" +#include "istream.h" +#include "ostream.h" +#include "test-common.h" +#include "smtp-reply-parser.h" + +#include + +struct smtp_reply_parse_valid_test { + const char *input, *output; + unsigned int status; + bool ehlo; + size_t max_size; + struct { + unsigned int x, y, z; + } enhanced_code; + const char *const *text_lines; +}; + +/* Valid reply tests */ + +static const struct smtp_reply_parse_valid_test +valid_reply_parse_tests[] = { + { + .input = "220\r\n", + .output = "220 \r\n", + .status = 220, + .text_lines = (const char *[]){ "", NULL } + },{ + .input = "220 \r\n", + .status = 220, + .text_lines = (const char *[]){ "", NULL } + },{ + .input = "220 OK\r\n", + .status = 220, + .text_lines = (const char *[]){ "OK", NULL } + },{ + .input = "550 Requested action not taken: mailbox unavailable\r\n", + .status = 550, + .text_lines = (const char *[]) + { "Requested action not taken: mailbox unavailable", NULL } + },{ + .input = + "250-smtp.example.com Hello client.example.org [10.0.0.1]\r\n" + "250-SIZE 52428800\r\n" + "250-PIPELINING\r\n" + "250-STARTTLS\r\n" + "250 HELP\r\n", + .ehlo = TRUE, + .status = 250, + .text_lines = (const char *[]) { + "smtp.example.com Hello client.example.org [10.0.0.1]", + "SIZE 52428800", + "PIPELINING", + "STARTTLS", + "HELP", + NULL + } + },{ + .input = + "250-smtp.example.com We got some nice '\x03' and '\x04'\r\n" + "250 HELP\r\n", + .output = + "250-smtp.example.com We got some nice ' ' and ' '\r\n" + "250 HELP\r\n", + .ehlo = TRUE, + .status = 250, + .text_lines = (const char *[]) { + "smtp.example.com We got some nice ' ' and ' '", + "HELP", + NULL + } + },{ + .input = + "250 smtp.example.com We got some nice '\x08'\r\n", + .output = + "250 smtp.example.com We got some nice ' '\r\n", + .ehlo = TRUE, + .status = 250, + .text_lines = (const char *[]) { + "smtp.example.com We got some nice ' '", + NULL + } + },{ + .input = "250 2.1.0 Originator ok\r\n", + .status = 250, + .enhanced_code = { 2, 1, 0 }, + .text_lines = (const char *[]){ + "Originator ok", NULL + } + },{ + .input = + "551-5.7.1 Forwarding to remote hosts disabled\r\n" + "551 5.7.1 Select another host to act as your forwarder\r\n", + .status = 551, + .enhanced_code = { 5, 7, 1 }, + .text_lines = (const char *[]) { + "Forwarding to remote hosts disabled", + "Select another host to act as your forwarder", + NULL + } + } +}; + +unsigned int valid_reply_parse_test_count = + N_ELEMENTS(valid_reply_parse_tests); + +static void test_smtp_reply_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_reply_parse_test_count; i++) T_BEGIN { + struct istream *input; + const struct smtp_reply_parse_valid_test *test; + struct smtp_reply_parser *parser; + struct smtp_reply *reply; + const char *error; + int ret; + + test = &valid_reply_parse_tests[i]; + input = i_stream_create_from_data(test->input, + strlen(test->input)); + parser = smtp_reply_parser_init(input, test->max_size); + i_stream_unref(&input); + + test_begin(t_strdup_printf("smtp reply valid [%d]", i)); + + if (test->ehlo) { + while ((ret=smtp_reply_parse_ehlo + (parser, &reply, &error)) > 0) { + } + } else { + while ((ret=smtp_reply_parse_next + (parser, test->enhanced_code.x > 0, &reply, &error)) > 0) { + } + } + + test_out_reason("parse success", ret == 0, error); + + if (ret == 0) { + const char *output; + string_t *encoded; + + /* verify last response only */ + test_out(t_strdup_printf("reply->status = %d", test->status), + reply->status == test->status); + if (test->enhanced_code.x > 0) { + test_out(t_strdup_printf("reply->enhanced_code = %d.%d.%d", + test->enhanced_code.x, test->enhanced_code.y, test->enhanced_code.z), + (reply->enhanced_code.x == test->enhanced_code.x && + reply->enhanced_code.y == test->enhanced_code.y && + reply->enhanced_code.z == test->enhanced_code.z)); + } + if (test->text_lines != NULL) { + const char *const *line = test->text_lines; + const char *const *reply_line = reply->text_lines; + unsigned int index = 0; + + while (*line != NULL) { + if (*reply_line == NULL) { + test_out( + t_strdup_printf("reply->text_lines[%d] = NULL", index), + FALSE); + break; + } + test_out(t_strdup_printf( + "reply->text_lines[%d] = \"%s\"", index, *reply_line), + strcmp(*line, *reply_line) == 0); + line++; + reply_line++; + index++; + } + } else { + test_out("reply->text_lines = NULL", reply->text_lines == NULL); + } + + encoded = t_str_new(512); + smtp_reply_write(encoded, reply); + + output = (test->output == NULL ? test->input : test->output); + test_out("write() = input", + strcmp(str_c(encoded), output) == 0); + } + test_end(); + smtp_reply_parser_deinit(&parser); + } T_END; +} + +struct smtp_reply_parse_invalid_test { + const char *reply; + bool ehlo; + size_t max_size; +}; + +static const struct smtp_reply_parse_invalid_test + invalid_reply_parse_tests[] = { + { + .reply = "22X OK\r\n" + },{ + .reply = "220OK\r\n" + },{ + .reply = + "200-This is\r\n" + "250 inconsistent.\r\n" + },{ + .reply = "400 This \r is wrong\r\n" + },{ + .reply = "500 This is \x03 worse\r\n" + },{ + .reply = "699 Obscure\r\n" + },{ + .reply = "100 Invalid\r\n" + },{ + .reply = "400 Interrupted\r" + },{ + .reply = "251 example.com We got '\x04'\r\n", + .ehlo = TRUE + },{ + .reply = + "250-example.com Hello\r\n" + "250 We got some '\x08' for you\r\n", + .ehlo = TRUE + },{ + .reply = + "556-This is a very long reply\r\n" + "556 that exceeds the very low limit.\r\n", + .max_size = 50 + } +}; + +unsigned int invalid_reply_parse_test_count = + N_ELEMENTS(invalid_reply_parse_tests); + +static void test_smtp_reply_parse_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_reply_parse_test_count; i++) T_BEGIN { + const struct smtp_reply_parse_invalid_test *test; + struct istream *input; + struct smtp_reply_parser *parser; + struct smtp_reply *reply; + const char *reply_text, *error; + int ret; + + test = &invalid_reply_parse_tests[i]; + reply_text = test->reply; + input = i_stream_create_from_data(reply_text, strlen(reply_text)); + parser = smtp_reply_parser_init(input, test->max_size); + i_stream_unref(&input); + + test_begin(t_strdup_printf("smtp reply invalid [%d]", i)); + + if (test->ehlo) + while ((ret=smtp_reply_parse_ehlo(parser, &reply, &error)) > 0); + else + while ((ret=smtp_reply_parse_next(parser, FALSE, &reply, &error)) > 0); + test_out_reason(t_strdup_printf("parse(\"%s\")", + str_sanitize(reply_text, 80)), ret < 0, error); + test_end(); + smtp_reply_parser_deinit(&parser); + } T_END; +} + +int main(void) +{ + static void (*test_functions[])(void) = { + test_smtp_reply_parse_valid, + test_smtp_reply_parse_invalid, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-smtp/test-smtp-server-errors.c b/src/lib-smtp/test-smtp-server-errors.c new file mode 100644 index 0000000..d3e528c --- /dev/null +++ b/src/lib-smtp/test-smtp-server-errors.c @@ -0,0 +1,3883 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "hostpid.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "time-util.h" +#include "sleep.h" +#include "connection.h" +#include "test-common.h" +#include "test-subprocess.h" +#include "smtp-address.h" +#include "smtp-reply-parser.h" +#include "smtp-server.h" + +#include + +#define SERVER_MAX_TIMEOUT_MSECS 10*1000 +#define CLIENT_KILL_TIMEOUT_SECS 20 + +static void main_deinit(void); + +/* + * Types + */ + +struct server_connection { + void *context; +}; + +struct client_connection { + struct connection conn; + void *context; + + pool_t pool; +}; + +typedef void +(*test_server_init_t)(const struct smtp_server_settings *server_set); +typedef void (*test_client_init_t)(unsigned int index); + +/* + * State + */ + +/* common */ +static struct ip_addr bind_ip; +static in_port_t bind_port = 0; +static struct ioloop *ioloop; +static bool debug = FALSE; + +/* server */ +static struct smtp_server *smtp_server = NULL; +static struct io *io_listen; +static int fd_listen = -1; +static size_t server_io_buffer_size = 0; +static struct smtp_server_callbacks server_callbacks; +static unsigned int server_pending; + +/* client */ +static struct connection_list *client_conn_list; +static unsigned int client_index; +static void (*test_client_connected)(struct client_connection *conn); +static void (*test_client_input)(struct client_connection *conn); +static void (*test_client_deinit)(struct client_connection *conn); + +/* + * Forward declarations + */ + +/* server */ +static void test_server_defaults(struct smtp_server_settings *smtp_set); +static void test_server_run(const struct smtp_server_settings *smtp_set); + +/* client */ +static void test_client_run(unsigned int index); + +/* test*/ +static void +test_run_client_server(const struct smtp_server_settings *server_set, + test_server_init_t server_test, + test_client_init_t client_test, + unsigned int client_tests_count) ATTR_NULL(3); + +/* + * Slow server + */ + +/* client */ + +static void test_slow_server_input(struct client_connection *conn ATTR_UNUSED) +{ + /* do nothing */ + sleep(10); +} + +static void test_slow_server_connected(struct client_connection *conn) +{ + if (debug) + i_debug("CONNECTED"); + + o_stream_nsend_str(conn->conn.output, + "EHLO frop\r\n"); +} + +static void test_client_slow_server(unsigned int index) +{ + test_client_input = test_slow_server_input; + test_client_connected = test_slow_server_connected; + test_client_run(index); +} + +/* server */ + +struct _slow_server { + struct smtp_server_cmd_ctx *cmd; + struct timeout *to_delay; + bool serviced:1; +}; + +static void +test_server_slow_server_destroyed(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct _slow_server *ctx) +{ + test_assert(ctx->serviced); + timeout_remove(&ctx->to_delay); + i_free(ctx); + io_loop_stop(ioloop); +} + +static void test_server_slow_server_delayed(struct _slow_server *ctx) +{ + struct smtp_server_reply *reply; + struct smtp_server_cmd_ctx *cmd = ctx->cmd; + + reply = smtp_server_reply_create_ehlo(cmd->cmd); + smtp_server_reply_ehlo_add(reply, "FROP"); + + smtp_server_reply_submit(reply); + ctx->serviced = TRUE; +} + +static int +test_server_slow_server_cmd_helo(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_helo *data ATTR_UNUSED) +{ + struct _slow_server *ctx; + + if (debug) + i_debug("HELO"); + + ctx = i_new(struct _slow_server, 1); + ctx->cmd = cmd; + + smtp_server_command_add_hook(cmd->cmd, + SMTP_SERVER_COMMAND_HOOK_DESTROY, + test_server_slow_server_destroyed, ctx); + + ctx->to_delay = timeout_add(4000, test_server_slow_server_delayed, ctx); + + return 0; +} + +static void +test_server_slow_server(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_cmd_helo = test_server_slow_server_cmd_helo; + test_server_run(server_set); +} + +/* test */ + +static void test_slow_server(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("slow server"); + test_run_client_server(&smtp_server_set, + test_server_slow_server, + test_client_slow_server, 1); + test_end(); +} + +/* + * Slow client + */ + +/* client */ + +static void test_slow_client_input(struct client_connection *conn ATTR_UNUSED) +{ + /* nothing */ +} + +static void test_slow_client_connected(struct client_connection *conn) +{ + if (debug) + i_debug("CONNECTED"); + + o_stream_nsend_str(conn->conn.output, "EHLO frop\r\n"); +} + +static void test_client_slow_client(unsigned int index) +{ + test_client_input = test_slow_client_input; + test_client_connected = test_slow_client_connected; + test_client_run(index); +} + +/* server */ + +struct _slow_client { + struct smtp_server_cmd_ctx *cmd; + struct timeout *to_delay; + struct timeout *to_disconnect; + bool serviced:1; +}; + +static void test_server_slow_client_disconnect_timeout(struct _slow_client *ctx) +{ + test_assert(FALSE); + + timeout_remove(&ctx->to_disconnect); + i_free(ctx); + io_loop_stop(ioloop); +} + +static void +test_server_slow_client_disconnect(void *conn_ctx, const char *reason) +{ + struct server_connection *conn = (struct server_connection *)conn_ctx; + struct _slow_client *ctx = (struct _slow_client *)conn->context; + + if (debug) + i_debug("DISCONNECTED: %s", reason); + + timeout_remove(&ctx->to_disconnect); + i_free(ctx); + io_loop_stop(ioloop); +} + +static void +test_server_slow_client_cmd_destroyed( + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct _slow_client *ctx) +{ + test_assert(ctx->serviced); + timeout_remove(&ctx->to_delay); +} + +static void test_server_slow_client_delayed(struct _slow_client *ctx) +{ + struct smtp_server_reply *reply; + struct smtp_server_cmd_ctx *cmd = ctx->cmd; + + timeout_remove(&ctx->to_delay); + + reply = smtp_server_reply_create_ehlo(cmd->cmd); + smtp_server_reply_ehlo_add(reply, "FROP"); + + ctx->to_disconnect = timeout_add( + 2000, test_server_slow_client_disconnect_timeout, ctx); + + smtp_server_reply_submit(reply); + ctx->serviced = TRUE; +} + +static int +test_server_slow_client_cmd_helo(void *conn_ctx, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_helo *data ATTR_UNUSED) +{ + struct server_connection *conn = (struct server_connection *)conn_ctx; + struct _slow_client *ctx; + + if (debug) + i_debug("HELO"); + + ctx = i_new(struct _slow_client, 1); + ctx->cmd = cmd; + + conn->context = ctx; + + smtp_server_command_add_hook(cmd->cmd, + SMTP_SERVER_COMMAND_HOOK_DESTROY, + test_server_slow_client_cmd_destroyed, + ctx); + + ctx->to_delay = timeout_add_short(500, + test_server_slow_client_delayed, ctx); + + return 0; +} + +static void +test_server_slow_client(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = test_server_slow_client_disconnect; + server_callbacks.conn_cmd_helo = test_server_slow_client_cmd_helo; + test_server_run(server_set); +} + +/* test */ + +static void test_slow_client(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("slow client"); + test_run_client_server(&smtp_server_set, + test_server_slow_client, + test_client_slow_client, 1); + test_end(); +} + +/* + * Hanging command payload + */ + +/* client */ + +static void +test_hanging_command_payload_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "EHLO frop\r\n" + "MAIL FROM:\r\n" + "RCPT TO:\r\n" + "DATA\r\n" + "To be continued... or not"); +} + +static void test_client_hanging_command_payload(unsigned int index) +{ + test_client_connected = test_hanging_command_payload_connected; + test_client_run(index); +} + +/* server */ + +struct _hanging_command_payload { + struct istream *payload_input; + struct io *io; + + bool serviced:1; +}; + +static void +test_server_hanging_command_payload_trans_free( + void *conn_ctx ATTR_UNUSED, struct smtp_server_transaction *trans) +{ + struct _hanging_command_payload *ctx = + (struct _hanging_command_payload *)trans->context; + + test_assert(!ctx->serviced); + i_free(ctx); + io_loop_stop(ioloop); +} + +static int +test_server_hanging_command_payload_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt) +{ + if (debug) { + i_debug("RCPT TO:%s", + smtp_address_encode(rcpt->path)); + } + + return 1; +} + +static int +test_server_hanging_command_payload_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans, struct istream *data_input) +{ + struct _hanging_command_payload *ctx; + + if (debug) + i_debug("DATA"); + + ctx = i_new(struct _hanging_command_payload, 1); + trans->context = ctx; + + ctx->payload_input = data_input; + return 0; +} + +static int +test_server_hanging_command_payload_data_continue( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans) +{ + struct _hanging_command_payload *ctx = + (struct _hanging_command_payload *)trans->context; + const unsigned char *data; + size_t size; + int ret; + + + if (debug) + i_debug("DATA continue"); + + while ((ret = i_stream_read_data(ctx->payload_input, + &data, &size, 0)) > 0) + i_stream_skip(ctx->payload_input, size); + + if (ret == 0) + return 0; + if (ctx->payload_input->stream_errno != 0) { + i_error("failed to read DATA payload: %s", + i_stream_get_error(ctx->payload_input)); + return -1; + } + + i_assert(ctx->payload_input->eof); + + smtp_server_reply(cmd, 250, "2.0.0", "OK"); + ctx->serviced = TRUE; + + i_stream_unref(&ctx->payload_input); + return 1; +} + +static void +test_server_hanging_command_payload( + const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_trans_free = + test_server_hanging_command_payload_trans_free; + + server_callbacks.conn_cmd_rcpt = + test_server_hanging_command_payload_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_hanging_command_payload_data_begin; + server_callbacks.conn_cmd_data_continue = + test_server_hanging_command_payload_data_continue; + test_server_run(server_set); +} + +/* test */ + +static void test_hanging_command_payload(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("hanging command payload"); + test_run_client_server(&smtp_server_set, + test_server_hanging_command_payload, + test_client_hanging_command_payload, 1); + test_end(); +} + +/* + * Bad command + */ + +/* client */ + +static void +test_bad_command_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, "EHLO\tfrop\r\n"); +} + +static void test_client_bad_command(unsigned int index) +{ + test_client_connected = test_bad_command_connected; + test_client_run(index); +} + +/* server */ + +struct _bad_command { + struct istream *payload_input; + struct io *io; + + bool serviced:1; +}; + +static void +test_server_bad_command_disconnect(void *context ATTR_UNUSED, + const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); + io_loop_stop(ioloop); +} + +static int +test_server_bad_command_helo(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_cmd_helo *data ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_bad_command_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + return 1; +} + +static int +test_server_bad_command_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + smtp_server_reply(cmd, 250, "2.0.0", "OK"); + return 1; +} + +static void +test_server_bad_command(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_bad_command_disconnect; + + server_callbacks.conn_cmd_helo = + test_server_bad_command_helo; + server_callbacks.conn_cmd_rcpt = + test_server_bad_command_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_bad_command_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_command(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("bad command"); + test_run_client_server(&smtp_server_set, + test_server_bad_command, + test_client_bad_command, 1); + test_end(); +} + +/* + * Many bad commands + */ + +/* client */ + +struct _many_bad_commands_client { + struct smtp_reply_parser *parser; + unsigned int reply; + bool replied:1; +}; + +static void +test_many_bad_commands_client_input(struct client_connection *conn) +{ + struct _many_bad_commands_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + while ((ret=smtp_reply_parse_next(ctx->parser, FALSE, + &reply, &error)) > 0) { + if (debug) + i_debug("REPLY #%u: %s", ctx->reply, smtp_reply_log(reply)); + + switch (ctx->reply++) { + /* greeting */ + case 0: + i_assert(reply->status == 220); + break; + /* bad command reply */ + case 1: case 2: case 3: case 4: case 5: + case 6: case 7: case 8: case 9: case 10: + i_assert(reply->status == 500); + break; + case 11: + i_assert(reply->status == 421); + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret >= 0); +} + +static void +test_many_bad_commands_client_connected(struct client_connection *conn) +{ + struct _many_bad_commands_client *ctx; + + ctx = p_new(conn->pool, struct _many_bad_commands_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + switch (client_index) { + case 0: + o_stream_nsend_str(conn->conn.output, + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + break; + case 1: + o_stream_nsend_str(conn->conn.output, + "a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\n" + "i\r\nj\r\nk\r\nl\r\nm\r\nn\r\no\r\np\r\n"); + break; + default: + i_unreached(); + } +} + +static void +test_many_bad_commands_client_deinit(struct client_connection *conn) +{ + struct _many_bad_commands_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_many_bad_commands(unsigned int index) +{ + test_client_input = test_many_bad_commands_client_input; + test_client_connected = test_many_bad_commands_client_connected; + test_client_deinit = test_many_bad_commands_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_many_bad_commands_disconnect(void *context ATTR_UNUSED, + const char *reason) +{ + struct server_connection *sconn = context; + + if (debug) + i_debug("Disconnect: %s", reason); + + sconn->context = POINTER_CAST(POINTER_CAST_TO(sconn->context, + unsigned int) + 1); + + if (POINTER_CAST_TO(sconn->context, unsigned int) == 2) + io_loop_stop(ioloop); +} + +static int +test_server_many_bad_commands_helo( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_cmd_helo *data ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_many_bad_commands_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_many_bad_commands_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static void test_server_many_bad_commands +(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_many_bad_commands_disconnect; + + server_callbacks.conn_cmd_helo = + test_server_many_bad_commands_helo; + server_callbacks.conn_cmd_rcpt = + test_server_many_bad_commands_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_many_bad_commands_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_many_bad_commands(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.max_bad_commands = 10; + + test_begin("many bad commands"); + test_run_client_server(&smtp_server_set, + test_server_many_bad_commands, + test_client_many_bad_commands, 2); + test_end(); +} + +/* + * Long command + */ + +/* client */ + +static void test_long_command_connected(struct client_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "EHLO some.very.very.very.very.very.long.domain\r\n"); +} + +static void test_client_long_command(unsigned int index) +{ + test_client_connected = test_long_command_connected; + test_client_run(index); +} + +/* server */ + +struct _long_command { + struct istream *payload_input; + struct io *io; + + bool serviced:1; +}; + +static void +test_server_long_command_disconnect(void *context ATTR_UNUSED, + const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); + io_loop_stop(ioloop); +} + +static int +test_server_long_command_helo(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_cmd_helo *data ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_long_command_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + return 1; +} + +static int +test_server_long_command_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + smtp_server_reply(cmd, 250, "2.0.0", "OK"); + return 1; +} + +static void +test_server_long_command(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_long_command_disconnect; + + server_callbacks.conn_cmd_helo = + test_server_long_command_helo; + server_callbacks.conn_cmd_rcpt = + test_server_long_command_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_long_command_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_long_command(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.command_limits.max_parameters_size = 32; + + test_begin("long command"); + test_run_client_server(&smtp_server_set, + test_server_long_command, + test_client_long_command, 1); + test_end(); +} + +/* + * Long auth line + */ + +/* client */ + +#define _LONG_AUTH_LINE_DATA \ + "dXNlcj10ZXN0dXNlcjEBYXV0aD1CZWFyZXIgZXlKaGJHY2lPaUpTVXpJMU5pSXNJ" \ + "blI1Y0NJZ09pQWlTbGRVSWl3aWEybGtJaUE2SUNKdVRIRlVlRnBXWVhKSlgwWndS" \ + "a0Z3Umt3MloyUnhiak4xV1VSS2R6WnNWVjlMYVZoa2JWazJialpSSW4wLmV5Smxl" \ + "SEFpT2pFMk16UTJNemMyTlRFc0ltbGhkQ0k2TVRZek5EWXpOek0xTVN3aWFuUnBJ" \ + "am9pT1RFM1lUYzFaalF0WTJZME9DMDBOVEEyTFRnNVpXSXRNRE13WldaaU5tSTVO" \ + "MlZrSWl3aWFYTnpJam9pYUhSMGNEb3ZMekU1TWk0eE5qZ3VNUzR5TVRveE9EQTRN" \ + "QzloZFhSb0wzSmxZV3h0Y3k5eVpXeDBaWE4wSWl3aVlYVmtJam9pWVdOamIzVnVk" \ + "Q0lzSW5OMVlpSTZJamhsWVRRME1UWTNMVGN6TTJVdE5EVTBZeTFpT0dJMUxXTmpa" \ + "bVl3WkRnek1URTVaQ0lzSW5SNWNDSTZJa0psWVhKbGNpSXNJbUY2Y0NJNkltUnZk" \ + "bVZqYjNRaUxDSnpaWE56YVc5dVgzTjBZWFJsSWpvaU1tTTNPVEUzWldJdE16QTFO" \ + "UzAwTkRZeExXSXdZell0WTJVeFlUbGlNVEZoTWpReklpd2lZV055SWpvaU1TSXNJ" \ + "bkpsWVd4dFgyRmpZMlZ6Y3lJNmV5SnliMnhsY3lJNld5SnZabVpzYVc1bFgyRmpZ" \ + "MlZ6Y3lJc0luVnRZVjloZFhSb2IzSnBlbUYwYVc5dUlsMTlMQ0p5WlhOdmRYSmpa" \ + "VjloWTJObGMzTWlPbnNpWVdOamIzVnVkQ0k2ZXlKeWIyeGxjeUk2V3lKdFlXNWha" \ + "MlV0WVdOamIzVnVkQ0lzSW0xaGJtRm5aUzFoWTJOdmRXNTBMV3hwYm10eklpd2lk" \ + "bWxsZHkxd2NtOW1hV3hsSWwxOWZTd2ljMk52Y0dVaU9pSndjbTltYVd4bElHVnRZ" \ + "V2xzSWl3aVpXMWhhV3hmZG1WeWFXWnBaV1FpT21aaGJITmxMQ0p1WVcxbElqb2lk" \ + "R1Z6ZEhWelpYSXhJRUYxZEc5SFpXNWxjbUYwWldRaUxDSndjbVZtWlhKeVpXUmZk" \ + "WE5sY201aGJXVWlPaUowWlhOMGRYTmxjakVpTENKbmFYWmxibDl1WVcxbElqb2lk" \ + "R1Z6ZEhWelpYSXhJaXdpWm1GdGFXeDVYMjVoYldVaU9pSkJkWFJ2UjJWdVpYSmhk" \ + "R1ZrSWl3aVpXMWhhV3dpT2lKMFpYTjBkWE5sY2pGQWJYbGtiMjFoYVc0dWIzZ2lm" \ + "US5ta2JGSURpT0FhbENCcVMwODRhVHJURjBIdDk1c1Z4cGlSbTFqZnhJd0JiN1hM" \ + "M2gzWUJkdXVrVXlZdDJqX1pqUFlhMDhDcVVYNWFrLVBOSjdSVWRTUXNmUlgwM1Zi" \ + "cXA4MHFZZjNGYzJpcDR0YmhHLXFEV0R6NzdhZDhWcEFNei16YWlSamZCclZ2R3hB" \ + "T3ZsZnFDVWhaZTJDR3ZqWjZ1Q3RKTlFaS0dyazZHOXoxX2pqekZkTjBXWjUxbEZs" \ + "US1JdE5LREpoTjNIekJ5SW93M19qQU9kWEI0R0w4R3JHM1hqU09rSFVRam5GTEQw" \ + "QUF1QXY4SkxmTXY1NGc1a2tKaklxRFgxZlgyWVo0Y2JQOWV3TUp6UV84ZWdLeW5T" \ + "VV9XSk8xRU9Qa1NVZjlMX19RX3FwY0dNbzFtTkxuTURKUlU2dmZFY3JrM2k0cVNz" \ + "MXRPdHdLaHcBAQ" + +struct _long_auth_line_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void test_long_auth_line_client_input(struct client_connection *conn) +{ + struct _long_auth_line_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + while ((ret = smtp_reply_parse_next(ctx->parser, FALSE, + &reply, &error)) > 0) { + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (ctx->reply++) { + case 0: /* greeting */ + i_assert(reply->status == 220); + break; + case 1: /* EHLO reply */ + i_assert(reply->status == 250); + break; + case 2: /* AUTH continue */ + i_assert(reply->status == 334); + break; + case 3: /* AUTH reply */ + switch (client_index) { + case 0: + i_assert(reply->status == 235); + break; + case 1: + i_assert(reply->status == 235); + break; + case 2: + i_assert(reply->status == 500); + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + break; + case 4: /* MAIL reply */ + i_assert(client_index < 2); + i_assert(reply->status == 250); + break; + case 5: /* RCPT reply */ + i_assert(client_index < 2); + i_assert(reply->status == 250); + break; + case 6: /* DATA initial reply */ + i_assert(client_index < 2); + i_assert(reply->status == 354); + break; + case 7: /* DATA reply */ + i_assert(client_index < 2); + i_assert(reply->status == 250); + break; + case 8: /* QUIT reply */ + i_assert(client_index < 2); + i_assert(reply->status == 221); + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret >= 0); +} + +static void test_long_auth_line_client_connected(struct client_connection *conn) +{ + struct _long_auth_line_client *ctx; + unsigned int i; + + ctx = p_new(conn->pool, struct _long_auth_line_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + o_stream_nsend_str( + conn->conn.output, + "EHLO frop\r\n" + "AUTH XOAUTH2\r\n"); + for (i = 0; i < (client_index > 1 ? 6 : 1); i++) + o_stream_nsend_str(conn->conn.output, _LONG_AUTH_LINE_DATA); + o_stream_nsend_str( + conn->conn.output, + "=="); + if (client_index == 1) { + o_stream_nsend_str( + conn->conn.output, + " "); + } + o_stream_nsend_str( + conn->conn.output, + "\r\n" + "MAIL FROM:\r\n" + "RCPT TO:\r\n" + "DATA\r\n" + "frop\r\n" + ".\r\n" + "QUIT\r\n"); +} + +static void test_long_auth_line_client_deinit(struct client_connection *conn) +{ + struct _long_auth_line_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_long_auth_line(unsigned int index) +{ + test_client_input = test_long_auth_line_client_input; + test_client_connected = test_long_auth_line_client_connected; + test_client_deinit = test_long_auth_line_client_deinit; + test_client_run(index); +} + +/* server */ + +struct _long_auth_line { + struct istream *payload_input; +}; + +static void +test_server_long_auth_line_disconnect(void *context ATTR_UNUSED, + const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_long_auth_line_helo(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_cmd_helo *data ATTR_UNUSED) +{ + return 1; +} + +static int +test_server_long_auth_line_auth(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_cmd_auth *data ATTR_UNUSED) +{ + smtp_server_cmd_auth_send_challenge(cmd, ""); + return 0; +} + +static int +test_server_long_auth_line_auth_continue(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd, + const char *response) +{ + if (strcmp(response, _LONG_AUTH_LINE_DATA"==") == 0) + smtp_server_cmd_auth_success(cmd, "user", NULL); + else { + smtp_server_reply(cmd, 535, "5.7.8", + "Authentication credentials invalid"); + } + return 1; +} + +static int +test_server_long_auth_line_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + if (debug) + i_debug("RCPT"); + return 1; +} + +static int +test_server_long_auth_line_data_begin(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans, + struct istream *data_input) +{ + struct _long_auth_line *ctx; + + if (debug) + i_debug("DATA"); + + ctx = p_new(trans->pool, struct _long_auth_line, 1); + trans->context = ctx; + + ctx->payload_input = data_input; + return 0; +} + +static int +test_server_long_auth_line_data_continue(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans) +{ + struct _long_auth_line *ctx = (struct _long_auth_line *)trans->context; + struct istream *data_input = ctx->payload_input; + size_t size; + ssize_t ret; + + if (debug) + i_debug("DATA continue"); + + while ((ret = i_stream_read(data_input)) > 0 || ret == -2) { + (void)i_stream_get_data(data_input, &size); + i_stream_skip(data_input, size); + if (!smtp_server_cmd_data_check_size(cmd)) + return -1; + } + + if (ret == 0) + return 0; + if (ret < 0 && data_input->stream_errno != 0) { + /* Client probably disconnected */ + return -1; + } + + smtp_server_reply_all(cmd, 250, "2.0.0", "Accepted"); + return 1; +} + +static void +test_server_long_auth_line(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_long_auth_line_disconnect; + + server_callbacks.conn_cmd_helo = + test_server_long_auth_line_helo; + server_callbacks.conn_cmd_auth = + test_server_long_auth_line_auth; + server_callbacks.conn_cmd_auth_continue = + test_server_long_auth_line_auth_continue; + server_callbacks.conn_cmd_rcpt = + test_server_long_auth_line_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_long_auth_line_data_begin; + server_callbacks.conn_cmd_data_continue = + test_server_long_auth_line_data_continue; + test_server_run(server_set); +} + +static void +test_server_long_auth_line_small_buf( + const struct smtp_server_settings *server_set) +{ + server_io_buffer_size = 1024; + + server_callbacks.conn_disconnect = + test_server_long_auth_line_disconnect; + + server_callbacks.conn_cmd_helo = + test_server_long_auth_line_helo; + server_callbacks.conn_cmd_auth = + test_server_long_auth_line_auth; + server_callbacks.conn_cmd_auth_continue = + test_server_long_auth_line_auth_continue; + server_callbacks.conn_cmd_rcpt = + test_server_long_auth_line_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_long_auth_line_data_begin; + server_callbacks.conn_cmd_data_continue = + test_server_long_auth_line_data_continue; + test_server_run(server_set); +} + +/* test */ + +static void test_long_auth_line(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.capabilities = SMTP_CAPABILITY_AUTH; + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("long auth line"); + test_run_client_server(&smtp_server_set, + test_server_long_auth_line, + test_client_long_auth_line, 3); + test_end(); +} + +static void test_long_auth_line_small_buf(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.capabilities = SMTP_CAPABILITY_AUTH; + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("long auth line (small i/o buffers)"); + test_run_client_server(&smtp_server_set, + test_server_long_auth_line_small_buf, + test_client_long_auth_line, 3); + test_end(); +} + + +/* + * Big data + */ + +/* client */ + +static void +test_big_data_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "EHLO frop\r\n" + "MAIL FROM:\r\n" + "RCPT TO:\r\n" + "DATA\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + ".\r\n"); +} + +static void test_client_big_data(unsigned int index) +{ + test_client_connected = test_big_data_connected; + test_client_run(index); +} + +/* server */ + +struct _big_data { + struct istream *payload_input; + struct io *io; +}; + +static void +test_server_big_data_trans_free(void *conn_ctx ATTR_UNUSED, + struct smtp_server_transaction *trans) +{ + struct _big_data *ctx = (struct _big_data *)trans->context; + + i_free(ctx); + io_loop_stop(ioloop); +} + +static int +test_server_big_data_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt) +{ + if (debug) { + i_debug("RCPT TO:%s", + smtp_address_encode(rcpt->path)); + } + return 1; +} + +static int +test_server_big_data_data_begin(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans, + struct istream *data_input) +{ + struct _big_data *ctx; + + if (debug) + i_debug("DATA"); + + ctx = i_new(struct _big_data, 1); + trans->context = ctx; + + ctx->payload_input = data_input; + return 0; +} + +static int +test_server_big_data_data_continue(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans) +{ + static const size_t max_size = 32; + struct _big_data *ctx = (struct _big_data *)trans->context; + const unsigned char *data; + size_t size; + int ret; + + + if (debug) + i_debug("DATA continue"); + + while (ctx->payload_input->v_offset < max_size && + (ret = i_stream_read_data(ctx->payload_input, + &data, &size, 0)) > 0) { + if (ctx->payload_input->v_offset + size > max_size) { + if (ctx->payload_input->v_offset >= max_size) + size = 0; + else + size = max_size - ctx->payload_input->v_offset; + } + i_stream_skip(ctx->payload_input, size); + + if (ctx->payload_input->v_offset >= max_size) + break; + } + + if (ctx->payload_input->v_offset >= max_size) { + smtp_server_reply_early(cmd, 552, "5.3.4", + "Message too big for system"); + return -1; + } + + if (ret == 0) + return 0; + + test_assert(FALSE); + return 1; +} + +static void test_server_big_data(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_trans_free = + test_server_big_data_trans_free; + + server_callbacks.conn_cmd_rcpt = + test_server_big_data_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_big_data_data_begin; + server_callbacks.conn_cmd_data_continue = + test_server_big_data_data_continue; + test_server_run(server_set); +} + +/* test */ + +static void test_big_data(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.command_limits.max_data_size = 64; + + test_begin("big_data"); + test_run_client_server(&smtp_server_set, + test_server_big_data, + test_client_big_data, 1); + test_end(); +} + +/* + * Bad HELO + */ + +/* client */ + +struct _bad_helo_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void test_bad_helo_client_input(struct client_connection *conn) +{ + struct _bad_helo_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + for (;;) { + if (ctx->reply != 1 || + client_index == 0 || client_index == 2) { + ret = smtp_reply_parse_next(ctx->parser, FALSE, &reply, + &error); + } else { + ret = smtp_reply_parse_ehlo(ctx->parser, &reply, + &error); + } + if (ret <= 0) + break; + + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (ctx->reply++) { + case 0: /* greeting */ + i_assert(reply->status == 220); + break; + case 1: /* bad command reply */ + switch (client_index) { + case 0: case 1: + i_assert(reply->status == 501); + break; + case 2: case 3: + i_assert(reply->status == 250); + break; + default: + i_unreached(); + } + if (debug) + i_debug("REPLIED"); + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret >= 0); +} + +static void test_bad_helo_client_connected(struct client_connection *conn) +{ + struct _bad_helo_client *ctx; + + ctx = p_new(conn->pool, struct _bad_helo_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + switch (client_index) { + case 0: + o_stream_nsend_str(conn->conn.output, "HELO\r\n"); + break; + case 1: + o_stream_nsend_str(conn->conn.output, "EHLO\r\n"); + break; + case 2: + o_stream_nsend_str(conn->conn.output, "HELO frop\r\n"); + break; + case 3: + o_stream_nsend_str(conn->conn.output, "EHLO frop\r\n"); + break; + default: + i_unreached(); + } +} + +static void test_bad_helo_client_deinit(struct client_connection *conn) +{ + struct _bad_helo_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_bad_helo(unsigned int index) +{ + test_client_input = test_bad_helo_client_input; + test_client_connected = test_bad_helo_client_connected; + test_client_deinit = test_bad_helo_client_deinit; + test_client_run(index); +} + +/* server */ + +struct _bad_helo { + struct istream *payload_input; + struct io *io; + + bool serviced:1; +}; + +static void +test_server_bad_helo_disconnect(void *context ATTR_UNUSED, const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_bad_helo_helo(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_cmd_helo *data ATTR_UNUSED) +{ + return 1; +} + +static int +test_server_bad_helo_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + return 1; +} + +static int +test_server_bad_helo_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + smtp_server_reply(cmd, 250, "2.0.0", "OK"); + return 1; +} + +static void test_server_bad_helo(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_bad_helo_disconnect; + + server_callbacks.conn_cmd_helo = + test_server_bad_helo_helo; + server_callbacks.conn_cmd_rcpt = + test_server_bad_helo_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_bad_helo_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_helo(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("bad HELO"); + test_run_client_server(&smtp_server_set, + test_server_bad_helo, + test_client_bad_helo, 4); + test_end(); +} + +/* + * Bad MAIL + */ + +/* client */ + +struct _bad_mail_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void test_bad_mail_client_input(struct client_connection *conn) +{ + struct _bad_mail_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + while ((ret = smtp_reply_parse_next(ctx->parser, FALSE, + &reply, &error)) > 0) { + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (ctx->reply++) { + case 0: /* greeting */ + i_assert(reply->status == 220); + break; + case 1: /* bad command reply */ + switch (client_index) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: + i_assert(reply->status == 501); + break; + case 7: case 8: + i_assert(reply->status == 250); + break; + default: + i_unreached(); + } + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret >= 0); +} + +static void test_bad_mail_client_connected(struct client_connection *conn) +{ + struct _bad_mail_client *ctx; + + ctx = p_new(conn->pool, struct _bad_mail_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + switch (client_index) { + case 0: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: \r\n"); + break; + case 1: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:hendrik@example.com\r\n"); + break; + case 2: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: hendrik@example.com\r\n"); + break; + case 3: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + case 4: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: \r\n"); + break; + case 5: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: BODY=7BIT\r\n"); + break; + case 6: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: <>\r\n"); + break; + case 7: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + case 8: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:<>\r\n"); + break; + default: + i_unreached(); + } +} + +static void test_bad_mail_client_deinit(struct client_connection *conn) +{ + struct _bad_mail_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_bad_mail(unsigned int index) +{ + test_client_input = test_bad_mail_client_input; + test_client_connected = test_bad_mail_client_connected; + test_client_deinit = test_bad_mail_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_bad_mail_disconnect(void *context ATTR_UNUSED, const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_bad_mail_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_bad_mail_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static void test_server_bad_mail(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_bad_mail_disconnect; + + server_callbacks.conn_cmd_rcpt = + test_server_bad_mail_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_bad_mail_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_mail(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("bad MAIL"); + test_run_client_server(&smtp_server_set, + test_server_bad_mail, + test_client_bad_mail, 9); + test_end(); +} + +/* + * Bad RCPT + */ + +/* client */ + +struct _bad_rcpt_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void +test_bad_rcpt_client_input(struct client_connection *conn) +{ + struct _bad_rcpt_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + while ((ret = smtp_reply_parse_next(ctx->parser, FALSE, + &reply, &error)) > 0) { + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (ctx->reply++) { + case 0: /* greeting */ + i_assert(reply->status == 220); + break; + case 1: /* MAIL FROM */ + i_assert(reply->status == 250); + break; + case 2: /* bad command reply */ + switch (client_index) { + case 0: case 1: case 2: case 3: case 4: case 5: + i_assert(reply->status == 501); + break; + case 6: + i_assert(reply->status == 250); + break; + default: + i_unreached(); + } + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret >= 0); +} + +static void test_bad_rcpt_client_connected(struct client_connection *conn) +{ + struct _bad_rcpt_client *ctx; + + ctx = p_new(conn->pool, struct _bad_rcpt_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + switch (client_index) { + case 0: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO: \r\n"); + break; + case 1: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:harrie@example.com\r\n"); + break; + case 2: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO: harrie@example.com\r\n"); + break; + case 3: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:\r\n"); + break; + case 4: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO: \r\n"); + break; + case 5: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO: NOTIFY=NEVER\r\n"); + break; + case 6: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:\r\n"); + break; + default: + i_unreached(); + } +} + +static void test_bad_rcpt_client_deinit(struct client_connection *conn) +{ + struct _bad_rcpt_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_bad_rcpt(unsigned int index) +{ + test_client_input = test_bad_rcpt_client_input; + test_client_connected = test_bad_rcpt_client_connected; + test_client_deinit = test_bad_rcpt_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_bad_rcpt_disconnect(void *context ATTR_UNUSED, const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_bad_rcpt_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + return 1; +} + +static int +test_server_bad_rcpt_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static void test_server_bad_rcpt(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_bad_rcpt_disconnect; + + server_callbacks.conn_cmd_rcpt = + test_server_bad_rcpt_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_bad_rcpt_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_rcpt(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("bad RCPT"); + test_run_client_server(&smtp_server_set, + test_server_bad_rcpt, + test_client_bad_rcpt, 7); + test_end(); +} + +/* + * Bad VRFY + */ + +/* client */ + +struct _bad_vrfy_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void +test_bad_vrfy_client_input(struct client_connection *conn) +{ + struct _bad_vrfy_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + while ((ret = smtp_reply_parse_next(ctx->parser, FALSE, + &reply, &error)) > 0) { + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (ctx->reply++) { + case 0: /* greeting */ + i_assert(reply->status == 220); + break; + case 1: /* bad command reply */ + switch (client_index) { + case 0: case 1: case 2: + i_assert(reply->status == 501); + break; + case 3: + i_assert(smtp_reply_is_success(reply)); + break; + default: + i_unreached(); + } + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret == 0); +} + +static void +test_bad_vrfy_client_connected(struct client_connection *conn) +{ + struct _bad_vrfy_client *ctx; + + ctx = p_new(conn->pool, struct _bad_vrfy_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + switch (client_index) { + case 0: + o_stream_nsend_str(conn->conn.output, + "VRFY\r\n"); + break; + case 1: + o_stream_nsend_str(conn->conn.output, + "VRFY \"hendrik\r\n"); + break; + case 2: + o_stream_nsend_str(conn->conn.output, + "VRFY hen\"drik\r\n"); + break; + case 3: + o_stream_nsend_str(conn->conn.output, + "VRFY \"hendrik\"\r\n"); + break; + default: + i_unreached(); + } +} + +static void +test_bad_vrfy_client_deinit(struct client_connection *conn) +{ + struct _bad_vrfy_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_bad_vrfy(unsigned int index) +{ + test_client_input = test_bad_vrfy_client_input; + test_client_connected = test_bad_vrfy_client_connected; + test_client_deinit = test_bad_vrfy_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_bad_vrfy_disconnect(void *context ATTR_UNUSED, const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_bad_vrfy_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_bad_vrfy_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static void +test_server_bad_vrfy(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = test_server_bad_vrfy_disconnect; + + server_callbacks.conn_cmd_rcpt = test_server_bad_vrfy_rcpt; + server_callbacks.conn_cmd_data_begin = test_server_bad_vrfy_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_vrfy(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("bad VRFY"); + test_run_client_server(&smtp_server_set, + test_server_bad_vrfy, + test_client_bad_vrfy, 4); + test_end(); +} + +/* + * Bad NOOP + */ + +/* client */ + +struct _bad_noop_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void +test_bad_noop_client_input(struct client_connection *conn) +{ + struct _bad_noop_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + while ((ret = smtp_reply_parse_next(ctx->parser, FALSE, + &reply, &error)) > 0) { + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (ctx->reply++) { + case 0: /* greeting */ + i_assert(reply->status == 220); + break; + case 1: /* bad command reply */ + switch (client_index) { + case 1: case 2: + i_assert(reply->status == 501); + break; + case 0: case 3: + i_assert(smtp_reply_is_success(reply)); + break; + default: + i_unreached(); + } + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret == 0); +} + +static void +test_bad_noop_client_connected(struct client_connection *conn) +{ + struct _bad_noop_client *ctx; + + ctx = p_new(conn->pool, struct _bad_noop_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + switch (client_index) { + case 0: + o_stream_nsend_str(conn->conn.output, + "NOOP\r\n"); + break; + case 1: + o_stream_nsend_str(conn->conn.output, + "NOOP \"frop\r\n"); + break; + case 2: + o_stream_nsend_str(conn->conn.output, + "NOOP fr\"op\r\n"); + break; + case 3: + o_stream_nsend_str(conn->conn.output, + "NOOP \"frop\"\r\n"); + break; + default: + i_unreached(); + } +} + +static void +test_bad_noop_client_deinit(struct client_connection *conn) +{ + struct _bad_noop_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_bad_noop(unsigned int index) +{ + test_client_input = test_bad_noop_client_input; + test_client_connected = test_bad_noop_client_connected; + test_client_deinit = test_bad_noop_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_bad_noop_disconnect(void *context ATTR_UNUSED, const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_bad_noop_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_bad_noop_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static void +test_server_bad_noop(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = test_server_bad_noop_disconnect; + + server_callbacks.conn_cmd_rcpt = test_server_bad_noop_rcpt; + server_callbacks.conn_cmd_data_begin = test_server_bad_noop_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_noop(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("bad NOOP"); + test_run_client_server(&smtp_server_set, + test_server_bad_noop, + test_client_bad_noop, 4); + test_end(); +} + +/* + * MAIL workarounds + */ + +/* client */ + +struct _mail_workarounds_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void test_mail_workarounds_client_input(struct client_connection *conn) +{ + struct _mail_workarounds_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + while ((ret = smtp_reply_parse_next(ctx->parser, FALSE, + &reply, &error)) > 0) { + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (ctx->reply++) { + case 0: /* greeting */ + i_assert(reply->status == 220); + break; + case 1: /* bad command reply */ + switch (client_index) { + case 5: case 6: case 7: + i_assert(reply->status == 501); + break; + case 0: case 1: case 2: case 3: case 4: case 8: + case 9: case 10: + i_assert(reply->status == 250); + break; + default: + i_unreached(); + } + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret >= 0); +} + +static void +test_mail_workarounds_client_connected(struct client_connection *conn) +{ + struct _mail_workarounds_client *ctx; + + ctx = p_new(conn->pool, struct _mail_workarounds_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + switch (client_index) { + case 0: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: \r\n"); + break; + case 1: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\t\r\n"); + break; + case 2: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\t \r\n"); + break; + case 3: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:hendrik@example.com\r\n"); + break; + case 4: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: hendrik@example.com\r\n"); + break; + case 5: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + case 6: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: \r\n"); + break; + case 7: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: BODY=7BIT\r\n"); + break; + case 8: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: <>\r\n"); + break; + case 9: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + case 10: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:<>\r\n"); + break; + default: + i_unreached(); + } +} + +static void +test_mail_workarounds_client_deinit(struct client_connection *conn) +{ + struct _mail_workarounds_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_mail_workarounds(unsigned int index) +{ + test_client_input = test_mail_workarounds_client_input; + test_client_connected = test_mail_workarounds_client_connected; + test_client_deinit = test_mail_workarounds_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_mail_workarounds_disconnect(void *context ATTR_UNUSED, + const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_mail_workarounds_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_mail_workarounds_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static void +test_server_mail_workarounds(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_mail_workarounds_disconnect; + + server_callbacks.conn_cmd_rcpt = + test_server_mail_workarounds_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_mail_workarounds_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_mail_workarounds(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.workarounds = + SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH | + SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH; + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("MAIL workarounds"); + test_run_client_server(&smtp_server_set, + test_server_mail_workarounds, + test_client_mail_workarounds, 11); + test_end(); +} + +/* + * RCPT workarounds + */ + +/* client */ + +struct _rcpt_workarounds_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void test_rcpt_workarounds_client_input(struct client_connection *conn) +{ + struct _rcpt_workarounds_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + while ((ret = smtp_reply_parse_next(ctx->parser, FALSE, + &reply, &error)) > 0) { + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (ctx->reply++) { + case 0: /* greeting */ + i_assert(reply->status == 220); + break; + case 1: /* MAIL FROM */ + i_assert(reply->status == 250); + break; + case 2: /* bad command reply */ + switch (client_index) { + case 5: case 6: case 7: + i_assert(reply->status == 501); + break; + case 0: case 1: case 2: case 3: case 4: case 8: + i_assert(reply->status == 250); + break; + default: + i_unreached(); + } + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret >= 0); +} + +static void +test_rcpt_workarounds_client_connected(struct client_connection *conn) +{ + struct _rcpt_workarounds_client *ctx; + + ctx = p_new(conn->pool, struct _rcpt_workarounds_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + switch (client_index) { + case 0: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO: \r\n"); + break; + case 1: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:\t\r\n"); + break; + case 2: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:\t \r\n"); + break; + case 3: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:harrie@example.com\r\n"); + break; + case 4: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO: harrie@example.com\r\n"); + break; + case 5: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:\r\n"); + break; + case 6: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO: \r\n"); + break; + case 7: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO: NOTIFY=NEVER\r\n"); + break; + case 8: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:\r\n"); + break; + default: + i_unreached(); + } +} + +static void test_rcpt_workarounds_client_deinit(struct client_connection *conn) +{ + struct _rcpt_workarounds_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_rcpt_workarounds(unsigned int index) +{ + test_client_input = test_rcpt_workarounds_client_input; + test_client_connected = test_rcpt_workarounds_client_connected; + test_client_deinit = test_rcpt_workarounds_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_rcpt_workarounds_disconnect(void *context ATTR_UNUSED, + const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_rcpt_workarounds_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + return 1; +} + +static int +test_server_rcpt_workarounds_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static void +test_server_rcpt_workarounds(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_rcpt_workarounds_disconnect; + + server_callbacks.conn_cmd_rcpt = + test_server_rcpt_workarounds_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_rcpt_workarounds_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_rcpt_workarounds(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.workarounds = + SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH | + SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH; + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("RCPT workarounds"); + test_run_client_server(&smtp_server_set, + test_server_rcpt_workarounds, + test_client_rcpt_workarounds, 9); + test_end(); +} + +/* + * Too many recipients + */ + +/* client */ + +static void test_too_many_recipients_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "EHLO frop\r\n" + "MAIL FROM:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "DATA\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + "0123456789ABCDEF0123456789ABCDEF\r\n" + ".\r\n"); +} + +static void test_client_too_many_recipients(unsigned int index) +{ + test_client_connected = test_too_many_recipients_connected; + test_client_run(index); +} + +/* server */ + +static void +test_server_too_many_recipients_trans_free( + void *conn_ctx ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED) +{ + io_loop_stop(ioloop); +} + +static int +test_server_too_many_recipients_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt) +{ + if (debug) { + i_debug("RCPT TO:%s", + smtp_address_encode(rcpt->path)); + } + return 1; +} + +static int +test_server_too_many_recipients_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans, + struct istream *data_input ATTR_UNUSED) +{ + test_assert(array_count(&trans->rcpt_to) == 10); + + smtp_server_reply(cmd, 250, "2.0.0", "OK"); + return 1; +} + +static void +test_server_too_many_recipients(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_trans_free = + test_server_too_many_recipients_trans_free; + server_callbacks.conn_cmd_rcpt = + test_server_too_many_recipients_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_too_many_recipients_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_too_many_recipients(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.max_recipients = 10; + + test_begin("too many recipients"); + test_run_client_server(&smtp_server_set, + test_server_too_many_recipients, + test_client_too_many_recipients, 1); + test_end(); +} + +/* + * DATA without MAIL + */ + +/* client */ + +static void test_data_no_mail_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "EHLO frop\r\n" + "DATA\r\n" + ".\r\n" + "RSET\r\n"); +} + +static void test_client_data_no_mail(unsigned int index) +{ + test_client_connected = test_data_no_mail_connected; + test_client_run(index); +} + +/* server */ + +static int +test_server_data_no_mail_rcpt(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + /* not supposed to get here */ + i_assert(FALSE); + return 1; +} + +static int +test_server_data_no_mail_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + /* not supposed to get here */ + i_assert(FALSE); + return 1; +} + +static int +test_server_data_no_mail_rset(void *conn_ctx ATTR_UNUSED, + struct smtp_server_cmd_ctx *cmd ATTR_UNUSED) +{ + io_loop_stop(ioloop); + return 1; +} + +static void +test_server_data_no_mail(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_cmd_rcpt = + test_server_data_no_mail_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_data_no_mail_data_begin; + server_callbacks.conn_cmd_rset = + test_server_data_no_mail_rset; + test_server_run(server_set); +} + +/* test */ + +static void test_data_no_mail(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.capabilities = + SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING; + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.max_recipients = 10; + + test_begin("DATA without MAIL"); + test_run_client_server(&smtp_server_set, + test_server_data_no_mail, + test_client_data_no_mail, 1); + test_end(); +} + +/* + * DATA without RCPT + */ + +/* client */ + +static void test_data_no_rcpt_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "EHLO frop\r\n" + "MAIL FROM:\r\n" + "DATA\r\n" + ".\r\n" + "RSET\r\n"); +} + +static void test_client_data_no_rcpt(unsigned int index) +{ + test_client_connected = test_data_no_rcpt_connected; + test_client_run(index); +} + +/* server */ + +static void +test_server_data_no_rcpt_trans_free( + void *conn_ctx ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED) +{ + io_loop_stop(ioloop); +} + +static int +test_server_data_no_rcpt_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + /* not supposed to get here */ + i_assert(FALSE); + return 1; +} + +static int +test_server_data_no_rcpt_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + /* not supposed to get here */ + i_assert(FALSE); + return 1; +} + +static void +test_server_data_no_rcpt(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_trans_free = + test_server_data_no_rcpt_trans_free; + server_callbacks.conn_cmd_rcpt = + test_server_data_no_rcpt_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_data_no_rcpt_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_data_no_rcpt(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.capabilities = + SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING; + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.max_recipients = 10; + + test_begin("DATA without RCPT"); + test_run_client_server(&smtp_server_set, + test_server_data_no_rcpt, + test_client_data_no_rcpt, 1); + test_end(); +} + +/* + * Bad pipelined DATA + */ + +/* client */ + +static void test_bad_pipelined_data_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:<\r\n" + "DATA\r\n" + "FROP!\r\n" + "DATA\r\n" + "FROP!\r\n" + ".\r\n" + "QUIT\r\n"); +} + +static void test_client_bad_pipelined_data(unsigned int index) +{ + test_client_connected = test_bad_pipelined_data_connected; + test_client_run(index); +} + +/* server */ + +static void +test_server_bad_pipelined_data_trans_free( + void *conn_ctx ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED) +{ + io_loop_stop(ioloop); +} + +static int +test_server_bad_pipelined_data_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + /* not supposed to get here */ + i_assert(FALSE); + return 1; +} + +static int +test_server_bad_pipelined_data_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + /* not supposed to get here */ + i_assert(FALSE); + return 1; +} + +static void +test_server_bad_pipelined_data(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_trans_free = + test_server_bad_pipelined_data_trans_free; + server_callbacks.conn_cmd_rcpt = + test_server_bad_pipelined_data_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_bad_pipelined_data_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_pipelined_data(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.capabilities = + SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING; + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.max_recipients = 10; + smtp_server_set.max_pipelined_commands = 16; + + test_begin("Bad pipelined DATA"); + test_run_client_server(&smtp_server_set, + test_server_bad_pipelined_data, + test_client_bad_pipelined_data, 1); + test_end(); +} + +/* + * Bad pipelined DATA #2 + */ + +/* client */ + +static void test_bad_pipelined_data2_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "MAIL FROM:\r\n" + "DATA\r\n" + "DATA\r\n" + "RCPT TO:\r\n" + "BDAT 0\r\n"); +} + +static void test_client_bad_pipelined_data2(unsigned int index) +{ + test_client_connected = test_bad_pipelined_data2_connected; + test_client_run(index); +} + +/* server */ + +struct _bad_pipelined_data2 { + struct istream *payload_input; + struct io *io; +}; + +static void +test_server_bad_pipelined_data2_trans_free( + void *conn_ctx ATTR_UNUSED, struct smtp_server_transaction *trans) +{ + struct _bad_pipelined_data2 *ctx = trans->context; + + i_free(ctx); + io_loop_stop(ioloop); +} + +static int +test_server_bad_pipelined_data2_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt) +{ + if (debug) { + i_debug("RCPT TO:%s", + smtp_address_encode(rcpt->path)); + } + return 1; +} + +static int +test_server_bad_pipelined_data2_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans, struct istream *data_input) +{ + struct _bad_pipelined_data2 *ctx; + + if (debug) + i_debug("DATA"); + + ctx = i_new(struct _bad_pipelined_data2, 1); + trans->context = ctx; + + ctx->payload_input = data_input; + return 0; +} + +static int +test_server_bad_pipelined_data2_data_continue( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED) +{ + struct _bad_pipelined_data2 *ctx = trans->context; + size_t size; + ssize_t ret; + + while ((ret = i_stream_read(ctx->payload_input)) > 0 || ret == -2) { + size = i_stream_get_data_size(ctx->payload_input); + i_stream_skip(ctx->payload_input, size); + } + + if (ret == 0) + return 0; + if (ret < 0 && ctx->payload_input->stream_errno != 0) { + /* Client probably disconnected */ + return -1; + } + + smtp_server_reply_all(cmd, 250, "2.0.0", "Accepted"); + return 1; +} + +static void +test_server_bad_pipelined_data2(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_trans_free = + test_server_bad_pipelined_data2_trans_free; + server_callbacks.conn_cmd_rcpt = + test_server_bad_pipelined_data2_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_bad_pipelined_data2_data_begin; + server_callbacks.conn_cmd_data_continue = + test_server_bad_pipelined_data2_data_continue; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_pipelined_data2(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.capabilities = + SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING; + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.max_recipients = 10; + smtp_server_set.max_pipelined_commands = 16; + + test_begin("Bad pipelined DATA #2"); + test_run_client_server(&smtp_server_set, + test_server_bad_pipelined_data2, + test_client_bad_pipelined_data2, 1); + test_end(); +} + +/* + * DATA with BINARYMIME + */ + +/* client */ + +static void test_data_binarymime_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "EHLO frop\r\n" + "MAIL FROM: BODY=BINARYMIME\r\n" + "RCPT TO:\r\n" + "DATA\r\n" + ".\r\n" + "RSET\r\n"); +} + +static void test_client_data_binarymime(unsigned int index) +{ + test_client_connected = test_data_binarymime_connected; + test_client_run(index); +} + +/* server */ + +static void +test_server_data_binarymime_trans_free( + void *conn_ctx ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED) +{ + io_loop_stop(ioloop); +} + +static int +test_server_data_binarymime_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt) +{ + if (debug) { + i_debug("RCPT TO:%s", + smtp_address_encode(rcpt->path)); + } + return 1; +} + +static int +test_server_data_binarymime_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + /* not supposed to get here */ + i_assert(FALSE); + return 1; +} + +static void +test_server_data_binarymime(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_trans_free = + test_server_data_binarymime_trans_free; + server_callbacks.conn_cmd_rcpt = + test_server_data_binarymime_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_data_binarymime_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_data_binarymime(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.capabilities = + SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING; + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.max_recipients = 10; + + test_begin("DATA with BINARYMIME"); + test_run_client_server(&smtp_server_set, + test_server_data_binarymime, + test_client_data_binarymime, 1); + test_end(); +} + +/* + * MAIL broken path + */ + +/* client */ + +struct _mail_broken_path_client { + struct smtp_reply_parser *parser; + unsigned int reply; + + bool replied:1; +}; + +static void +test_mail_broken_path_client_input(struct client_connection *conn) +{ + struct _mail_broken_path_client *ctx = conn->context; + struct smtp_reply *reply; + const char *error; + int ret; + + while ((ret = smtp_reply_parse_next(ctx->parser, FALSE, + &reply, &error)) > 0) { + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (ctx->reply++) { + case 0: /* greeting */ + i_assert(reply->status == 220); + break; + case 1: /* bad command reply */ + switch (client_index) { + case 0: case 1: case 2: case 3: case 4: case 5: + case 6: case 7: case 8: case 11: case 14: case 16: + i_assert(reply->status == 501); + break; + case 9: case 10: case 12: case 13: case 15: case 17: + i_assert(reply->status == 250); + break; + default: + i_info("STATUS: %u", reply->status); + i_unreached(); + } + ctx->replied = TRUE; + io_loop_stop(ioloop); + connection_disconnect(&conn->conn); + return; + default: + i_unreached(); + } + } + + i_assert(ret >= 0); +} + +static void +test_mail_broken_path_client_connected(struct client_connection *conn) +{ + struct _mail_broken_path_client *ctx; + + ctx = p_new(conn->pool, struct _mail_broken_path_client, 1); + ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX); + conn->context = ctx; + + switch (client_index) { + case 0: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: \r\n"); + break; + case 1: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\t\r\n"); + break; + case 2: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\t \r\n"); + break; + case 3: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:hendrik@example.com\r\n"); + break; + case 4: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: hendrik@example.com\r\n"); + break; + case 5: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + case 6: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: \r\n"); + break; + case 7: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: BODY=7BIT\r\n"); + break; + case 8: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM: <>\r\n"); + break; + case 9: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + case 10: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:<>\r\n"); + break; + case 11: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:bla$die%bla@die&bla\r\n"); + break; + case 12: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + case 13: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + case 14: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:/@)$@)BLAARGH!@#$$\r\n"); + break; + case 15: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + case 16: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4\r\n"); + break; + case 17: + o_stream_nsend_str( + conn->conn.output, + "MAIL FROM:\r\n"); + break; + default: + i_unreached(); + } +} + +static void test_mail_broken_path_client_deinit(struct client_connection *conn) +{ + struct _mail_broken_path_client *ctx = conn->context; + + i_assert(ctx->replied); + smtp_reply_parser_deinit(&ctx->parser); +} + +static void test_client_mail_broken_path(unsigned int index) +{ + test_client_input = test_mail_broken_path_client_input; + test_client_connected = test_mail_broken_path_client_connected; + test_client_deinit = test_mail_broken_path_client_deinit; + test_client_run(index); +} + +/* server */ + +static void +test_server_mail_broken_path_disconnect(void *context ATTR_UNUSED, + const char *reason) +{ + if (debug) + i_debug("Disconnect: %s", reason); +} + +static int +test_server_mail_broken_path_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static int +test_server_mail_broken_path_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans ATTR_UNUSED, + struct istream *data_input ATTR_UNUSED) +{ + test_assert(FALSE); + return 1; +} + +static void +test_server_mail_broken_path(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_disconnect = + test_server_mail_broken_path_disconnect; + + server_callbacks.conn_cmd_rcpt = + test_server_mail_broken_path_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_mail_broken_path_data_begin; + test_server_run(server_set); +} + +/* test */ + +static void test_mail_broken_path(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.mail_path_allow_broken = TRUE; + smtp_server_set.max_client_idle_time_msecs = 1000; + + test_begin("MAIL broken path"); + test_run_client_server(&smtp_server_set, + test_server_mail_broken_path, + test_client_mail_broken_path, 18); + test_end(); +} + +/* + * Bad pipelined MAIL + */ + +/* client */ + +static void test_bad_pipelined_mail_connected(struct client_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "MAIL FROM:\r\n" + "RCPT TO:\r\n" + "RCPT TO:\r\n" + "MAIL FROM:\r\n" + "DATA\r\n" + "FROP!\r\n" + ".\r\n" + "QUIT\r\n"); +} + +static void test_client_bad_pipelined_mail(unsigned int index) +{ + test_client_connected = test_bad_pipelined_mail_connected; + test_client_run(index); +} + +/* server */ + +struct _bad_pipelined_mail { + struct istream *payload_input; + struct io *io; +}; + +static void +test_server_bad_pipelined_mail_trans_free( + void *conn_ctx ATTR_UNUSED, struct smtp_server_transaction *trans) +{ + struct _bad_pipelined_mail *ctx = trans->context; + + i_free(ctx); + io_loop_stop(ioloop); +} + +static int +test_server_bad_pipelined_mail_rcpt( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_recipient *rcpt) +{ + if (debug) { + i_debug("RCPT TO:%s", + smtp_address_encode(rcpt->path)); + } + return 1; +} + +static int +test_server_bad_pipelined_mail_data_begin( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, + struct smtp_server_transaction *trans, struct istream *data_input) +{ + struct _bad_pipelined_mail *ctx; + + if (debug) + i_debug("DATA"); + + ctx = i_new(struct _bad_pipelined_mail, 1); + trans->context = ctx; + + ctx->payload_input = data_input; + return 0; +} + +static int +test_server_bad_pipelined_mail_data_continue( + void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, + struct smtp_server_transaction *trans ATTR_UNUSED) +{ + struct _bad_pipelined_mail *ctx = trans->context; + size_t size; + ssize_t ret; + + while ((ret = i_stream_read(ctx->payload_input)) > 0 || ret == -2) { + size = i_stream_get_data_size(ctx->payload_input); + i_stream_skip(ctx->payload_input, size); + } + + if (ret == 0) + return 0; + if (ret < 0 && ctx->payload_input->stream_errno != 0) { + /* Client probably disconnected */ + return -1; + } + + smtp_server_reply_all(cmd, 250, "2.0.0", "Accepted"); + return 1; +} + +static void +test_server_bad_pipelined_mail(const struct smtp_server_settings *server_set) +{ + server_callbacks.conn_trans_free = + test_server_bad_pipelined_mail_trans_free; + server_callbacks.conn_cmd_rcpt = + test_server_bad_pipelined_mail_rcpt; + server_callbacks.conn_cmd_data_begin = + test_server_bad_pipelined_mail_data_begin; + server_callbacks.conn_cmd_data_continue = + test_server_bad_pipelined_mail_data_continue; + test_server_run(server_set); +} + +/* test */ + +static void test_bad_pipelined_mail(void) +{ + struct smtp_server_settings smtp_server_set; + + test_server_defaults(&smtp_server_set); + smtp_server_set.capabilities = + SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING; + smtp_server_set.max_client_idle_time_msecs = 1000; + smtp_server_set.max_recipients = 10; + smtp_server_set.max_pipelined_commands = 16; + + test_begin("Bad pipelined MAIL"); + test_run_client_server(&smtp_server_set, + test_server_bad_pipelined_mail, + test_client_bad_pipelined_mail, 1); + test_end(); +} + +/* + * All tests + */ + +static void (*const test_functions[])(void) = { + test_slow_server, + test_slow_client, + test_hanging_command_payload, + test_bad_command, + test_many_bad_commands, + test_long_command, + test_long_auth_line, + test_long_auth_line_small_buf, + test_big_data, + test_bad_helo, + test_bad_mail, + test_bad_rcpt, + test_bad_vrfy, + test_bad_noop, + test_mail_workarounds, + test_rcpt_workarounds, + test_too_many_recipients, + test_data_no_mail, + test_data_no_rcpt, + test_bad_pipelined_data, + test_bad_pipelined_data2, + test_data_binarymime, + test_mail_broken_path, + test_bad_pipelined_mail, + NULL +}; + +/* + * Test client + */ + +/* client connection */ + +static void client_connection_input(struct connection *_conn) +{ + struct client_connection *conn = (struct client_connection *)_conn; + + if (test_client_input != NULL) + test_client_input(conn); +} + +static void client_connection_connected(struct connection *_conn, bool success) +{ + struct client_connection *conn = (struct client_connection *)_conn; + + if (debug) + i_debug("Client connected"); + + if (success && test_client_connected != NULL) + test_client_connected(conn); +} + +static void client_connection_init(const struct ip_addr *ip, in_port_t port) +{ + struct client_connection *conn; + pool_t pool; + + pool = pool_alloconly_create("client connection", 256); + conn = p_new(pool, struct client_connection, 1); + conn->pool = pool; + + connection_init_client_ip(client_conn_list, &conn->conn, NULL, + ip, port); + (void)connection_client_connect(&conn->conn); +} + +static void client_connection_deinit(struct client_connection **_conn) +{ + struct client_connection *conn = *_conn; + + *_conn = NULL; + + if (test_client_deinit != NULL) + test_client_deinit(conn); + connection_deinit(&conn->conn); + pool_unref(&conn->pool); +} + +static void client_connection_destroy(struct connection *_conn) +{ + struct client_connection *conn = (struct client_connection *)_conn; + + client_connection_deinit(&conn); +} + +/* */ + +static struct connection_settings client_connection_set = { + .input_max_size = SIZE_MAX, + .output_max_size = SIZE_MAX, + .client = TRUE +}; + +static const struct connection_vfuncs client_connection_vfuncs = { + .destroy = client_connection_destroy, + .client_connected = client_connection_connected, + .input = client_connection_input +}; + +static void test_client_run(unsigned int index) +{ + client_index = index; + + if (debug) + i_debug("client connecting to %u", bind_port); + + client_conn_list = connection_list_init(&client_connection_set, + &client_connection_vfuncs); + + client_connection_init(&bind_ip, bind_port); + + io_loop_run(ioloop); + + /* close server socket */ + io_remove(&io_listen); + + connection_list_deinit(&client_conn_list); +} + +/* + * Test server + */ + +static void test_server_defaults(struct smtp_server_settings *smtp_set) +{ + /* server settings */ + i_zero(smtp_set); + smtp_set->max_client_idle_time_msecs = 5*1000; + smtp_set->max_pipelined_commands = 1; + smtp_set->auth_optional = TRUE; + smtp_set->debug = debug; +} + +/* client connection */ + +static void server_connection_free(void *context) +{ + struct server_connection *sconn = (struct server_connection *)context; + + if (debug) + i_debug("Connection freed"); + + if (--server_pending == 0) + io_loop_stop(ioloop); + i_free(sconn); +} + +static void server_connection_accept(void *context ATTR_UNUSED) +{ + struct smtp_server_connection *conn; + struct server_connection *sconn; + int fd; + + /* accept new client */ + fd = net_accept(fd_listen, NULL, NULL); + if (fd == -1) + return; + if (fd == -2) { + i_fatal("test server: accept() failed: %m"); + } + + if (debug) + i_debug("Accepted connection"); + + net_set_nonblock(fd, TRUE); + + sconn = i_new(struct server_connection, 1); + + server_callbacks.conn_free = server_connection_free; + + if (server_io_buffer_size == 0) { + conn = smtp_server_connection_create(smtp_server, fd, fd, + NULL, 0, FALSE, NULL, + &server_callbacks, sconn); + } else { + struct istream *input; + struct ostream *output; + + input = i_stream_create_fd(fd, server_io_buffer_size); + output = o_stream_create_fd(fd, server_io_buffer_size); + o_stream_set_no_error_handling(output, TRUE); + + conn = smtp_server_connection_create_from_streams( + smtp_server, input, output, NULL, 0, NULL, + &server_callbacks, sconn); + + i_stream_unref(&input); + o_stream_unref(&output); + } + smtp_server_connection_start(conn); +} + +/* */ + +static void test_server_timeout(void *context ATTR_UNUSED) +{ + i_fatal("Server timed out"); +} + +static void test_server_run(const struct smtp_server_settings *smtp_set) +{ + struct timeout *to; + + to = timeout_add(SERVER_MAX_TIMEOUT_MSECS, + test_server_timeout, NULL); + + /* open server socket */ + io_listen = io_add(fd_listen, IO_READ, server_connection_accept, NULL); + + smtp_server = smtp_server_init(smtp_set); + + io_loop_run(ioloop); + + if (debug) + i_debug("Server finished"); + + /* close server socket */ + io_remove(&io_listen); + timeout_remove(&to); + + smtp_server_deinit(&smtp_server); +} + +/* + * Tests + */ + +struct test_client_data { + unsigned int index; + test_client_init_t client_test; +}; + +static int test_open_server_fd(void) +{ + int fd = net_listen(&bind_ip, &bind_port, 128); + if (debug) + i_debug("server listening on %u", bind_port); + if (fd == -1) { + i_fatal("listen(%s:%u) failed: %m", + net_ip2addr(&bind_ip), bind_port); + } + return fd; +} + +static int test_run_client(struct test_client_data *data) +{ + i_set_failure_prefix("CLIENT[%u]: ", data->index + 1); + + if (debug) + i_debug("PID=%s", my_pid); + + /* wait a little for server setup */ + i_sleep_msecs(100); + + ioloop = io_loop_create(); + data->client_test(data->index); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); + + main_deinit(); + return 0; +} + +static void +test_run_server(const struct smtp_server_settings *server_set, + test_server_init_t server_test, + unsigned int client_tests_count) +{ + i_set_failure_prefix("SERVER: "); + + if (debug) + i_debug("PID=%s", my_pid); + + i_zero(&server_callbacks); + + server_pending = client_tests_count; + ioloop = io_loop_create(); + server_test(server_set); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); +} + +static void +test_run_client_server(const struct smtp_server_settings *server_set, + test_server_init_t server_test, + test_client_init_t client_test, + unsigned int client_tests_count) +{ + unsigned int i; + + server_io_buffer_size = 0; + + fd_listen = test_open_server_fd(); + + for (i = 0; i < client_tests_count; i++) { + struct test_client_data data; + + i_zero(&data); + data.index = i; + data.client_test = client_test; + + /* Fork client */ + test_subprocess_fork(test_run_client, &data, FALSE); + + } + + /* Run server */ + test_run_server(server_set, server_test, client_tests_count); + + i_unset_failure_prefix(); + i_close_fd(&fd_listen); + test_subprocess_kill_all(CLIENT_KILL_TIMEOUT_SECS); +} + +/* + * Main + */ + +static void main_init(void) +{ + /* nothing yet */ +} + +static void main_deinit(void) +{ + /* nothing yet; also called from sub-processes */ +} + +int main(int argc, char *argv[]) +{ + int c; + int ret; + + lib_init(); + main_init(); + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + + test_subprocesses_init(debug); + + /* listen on localhost */ + i_zero(&bind_ip); + bind_ip.family = AF_INET; + bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); + + ret = test_run(test_functions); + + test_subprocesses_deinit(); + main_deinit(); + lib_deinit(); + + return ret; +} diff --git a/src/lib-smtp/test-smtp-submit.c b/src/lib-smtp/test-smtp-submit.c new file mode 100644 index 0000000..f82dc4d --- /dev/null +++ b/src/lib-smtp/test-smtp-submit.c @@ -0,0 +1,2199 @@ +/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "hostpid.h" +#include "ioloop.h" +#include "istream.h" +#include "istream-chain.h" +#include "ostream.h" +#include "time-util.h" +#include "sleep.h" +#include "unlink-directory.h" +#include "write-full.h" +#include "connection.h" +#include "master-service.h" +#include "istream-dot.h" +#include "test-common.h" +#include "test-subprocess.h" + +#include "smtp-address.h" +#include "smtp-submit.h" + +#include +#include +#include + +#define SERVER_KILL_TIMEOUT_SECS 20 + +static void main_deinit(void); + +static const char *test_message1 = + "Subject: Test message\r\n" + "To: rcpt@example.com\r\n" + "From: sender@example.com\r\n" + "\r\n" + "Test message\r\n"; +static const char *test_message2 = + "Subject: Test message\r\n" + "To: rcpt@example.com\r\n" + "From: sender@example.com\r\n" + "\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n"; + +/* + * Types + */ + +struct server_connection { + struct connection conn; + + void *context; + + pool_t pool; +}; + +typedef void (*test_server_init_t)(unsigned int index); +typedef bool +(*test_client_init_t)(const struct smtp_submit_settings *submit_set); + +/* + * State + */ + +/* common */ +static struct ip_addr bind_ip; +static in_port_t *bind_ports = NULL; +static struct ioloop *ioloop; +static bool debug = FALSE; +static char *tmp_dir = NULL; + +/* server */ +static struct io *io_listen; +static int fd_listen = -1; +static in_port_t server_port = 0; +static struct connection_list *server_conn_list; +static unsigned int server_index; +static void (*test_server_input)(struct server_connection *conn); +static void (*test_server_init)(struct server_connection *conn); +static void (*test_server_deinit)(struct server_connection *conn); + +/* client */ + +/* + * Forward declarations + */ + +/* server */ +static void test_server_run(unsigned int index); +static void server_connection_deinit(struct server_connection **_conn); + +/* client */ +static void +test_client_defaults(struct smtp_submit_settings *smtp_set); +static void test_client_deinit(void); + +static int +test_client_smtp_send_simple(const struct smtp_submit_settings *smtp_set, + const char *message, const char *host, + const char **error_r); +static int +test_client_smtp_send_simple_port(const struct smtp_submit_settings *smtp_set, + const char *message, unsigned int port, + const char **error_r); + +/* test*/ +static const char *test_tmp_dir_get(void); + +static void test_message_delivery(const char *message, const char *file); + +static void +test_run_client_server(const struct smtp_submit_settings *submit_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count) ATTR_NULL(3); + +/* + * Host lookup failed + */ + +/* client */ + +static bool +test_client_host_lookup_failed(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple(submit_set, test_message1, + "host.invalid", &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_host_lookup_failed(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("host lookup failed"); + test_expect_errors(1); + test_run_client_server(&smtp_submit_set, + test_client_host_lookup_failed, NULL, 0); + test_end(); +} + +/* + * Connection refused + */ + +/* server */ + +static void test_server_connection_refused(unsigned int index ATTR_UNUSED) +{ + i_close_fd(&fd_listen); +} + +/* client */ + +static bool +test_client_connection_refused(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_connection_refused(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("connection refused"); + test_expect_errors(1); + test_run_client_server(&smtp_submit_set, + test_client_connection_refused, + test_server_connection_refused, 1); + test_end(); +} + +/* + * Connection timed out + */ + +/* server */ + +static void test_connection_timed_out_input(struct server_connection *conn) +{ + i_sleep_intr_secs(10); + server_connection_deinit(&conn); +} + +static void test_server_connection_timed_out(unsigned int index) +{ + test_server_input = test_connection_timed_out_input; + test_server_run(index); +} +/* client */ + +static bool +test_client_connection_timed_out(const struct smtp_submit_settings *submit_set) +{ + time_t time; + const char *error = NULL; + int ret; + + io_loop_time_refresh(); + time = ioloop_time; + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + io_loop_time_refresh(); + test_out("timeout", (ioloop_time - time) < 5); + + return FALSE; +} + +/* test */ + +static void test_connection_timed_out(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 1; + + test_begin("connection timed out"); + test_expect_errors(1); + test_run_client_server(&smtp_submit_set, + test_client_connection_timed_out, + test_server_connection_timed_out, 1); + test_end(); +} + +/* + * Bad greeting + */ + +/* server */ + +static void test_bad_greeting_input(struct server_connection *conn) +{ + server_connection_deinit(&conn); +} + +static void test_bad_greeting_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "554 No SMTP service here.\r\n"); +} + +static void test_server_bad_greeting(unsigned int index) +{ + test_server_init = test_bad_greeting_init; + test_server_input = test_bad_greeting_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_bad_greeting(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_bad_greeting(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("bad greeting"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_bad_greeting, + test_server_bad_greeting, 1); + test_end(); +} + +/* + * Denied HELO + */ + +/* server */ + +static void test_denied_helo_input(struct server_connection *conn) +{ + const char *line; + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + o_stream_nsend_str(conn->conn.output, + "550 Command rejected for testing reasons\r\n"); + server_connection_deinit(&conn); +} + +static void test_denied_helo_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_helo(unsigned int index) +{ + test_server_init = test_denied_helo_init; + test_server_input = test_denied_helo_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_denied_helo(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, + test_message1, bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_helo(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("denied helo"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_helo, + test_server_denied_helo, 1); + test_end(); +} + +/* + * Disconnect HELO + */ + +/* server */ + +static void test_disconnect_helo_input(struct server_connection *conn) +{ + const char *line; + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + server_connection_deinit(&conn); +} + +static void test_disconnect_helo_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_disconnect_helo(unsigned int index) +{ + test_server_init = test_disconnect_helo_init; + test_server_input = test_disconnect_helo_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_disconnect_helo(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_disconnect_helo(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("disconnect helo"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_disconnect_helo, + test_server_disconnect_helo, 1); + test_end(); +} + +/* + * Denied MAIL + */ + +/* server */ + +enum _denied_mail_state { + DENIED_MAIL_STATE_EHLO = 0, + DENIED_MAIL_STATE_MAIL_FROM +}; + +struct _denied_mail_server { + enum _denied_mail_state state; +}; + +static void test_denied_mail_input(struct server_connection *conn) +{ + struct _denied_mail_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _denied_mail_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _denied_mail_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_MAIL_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DENIED_MAIL_STATE_MAIL_FROM; + return; + case DENIED_MAIL_STATE_MAIL_FROM: + o_stream_nsend_str( + conn->conn.output,"453 4.3.2 " + "Incapable of accepting messages at this time.\r\n"); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_denied_mail_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_mail(unsigned int index) +{ + test_server_init = test_denied_mail_init; + test_server_input = test_denied_mail_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_denied_mail(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_mail(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("denied mail"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_mail, + test_server_denied_mail, 1); + test_end(); +} + +/* + * Denied RCPT + */ + +/* server */ + +enum _denied_rcpt_state { + DENIED_RCPT_STATE_EHLO = 0, + DENIED_RCPT_STATE_MAIL_FROM, + DENIED_RCPT_STATE_RCPT_TO +}; + +struct _denied_rcpt_server { + enum _denied_rcpt_state state; +}; + +static void test_denied_rcpt_input(struct server_connection *conn) +{ + struct _denied_rcpt_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _denied_rcpt_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _denied_rcpt_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_RCPT_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DENIED_RCPT_STATE_MAIL_FROM; + return; + case DENIED_RCPT_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DENIED_RCPT_STATE_RCPT_TO; + continue; + case DENIED_RCPT_STATE_RCPT_TO: + o_stream_nsend_str( + conn->conn.output, "550 5.4.3 " + "Directory server failure\r\n"); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_denied_rcpt_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_rcpt(unsigned int index) +{ + test_server_init = test_denied_rcpt_init; + test_server_input = test_denied_rcpt_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_denied_rcpt(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_rcpt(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("denied rcpt"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_rcpt, + test_server_denied_rcpt, 1); + test_end(); +} + +/* + * Denied second RCPT + */ + +/* server */ + +enum _denied_second_rcpt_state { + DENIED_SECOND_RCPT_STATE_EHLO = 0, + DENIED_SECOND_RCPT_STATE_MAIL_FROM, + DENIED_SECOND_RCPT_STATE_RCPT_TO, + DENIED_SECOND_RCPT_STATE_RCPT_TO2 +}; + +struct _denied_second_rcpt_server { + enum _denied_second_rcpt_state state; +}; + +static void test_denied_second_rcpt_input(struct server_connection *conn) +{ + struct _denied_second_rcpt_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _denied_second_rcpt_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _denied_second_rcpt_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_SECOND_RCPT_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DENIED_SECOND_RCPT_STATE_MAIL_FROM; + return; + case DENIED_SECOND_RCPT_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DENIED_SECOND_RCPT_STATE_RCPT_TO; + continue; + case DENIED_SECOND_RCPT_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DENIED_SECOND_RCPT_STATE_RCPT_TO2; + continue; + case DENIED_SECOND_RCPT_STATE_RCPT_TO2: + o_stream_nsend_str(conn->conn.output, "550 5.4.3 " + "Directory server failure\r\n"); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_denied_second_rcpt_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_second_rcpt(unsigned int index) +{ + test_server_init = test_denied_second_rcpt_init; + test_server_input = test_denied_second_rcpt_input; + test_server_run(index); +} + +/* client */ + +static void test_smtp_submit_input_init(struct smtp_submit_input *smtp_input_r) +{ + i_zero(smtp_input_r); + smtp_input_r->allow_root = TRUE; +} + +static bool +test_client_denied_second_rcpt(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit *smtp_submit; + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct ostream *output; + const char *error = NULL; + int ret; + + smtp_submit_set = *submit_set; + smtp_submit_set.submission_host = + t_strdup_printf("127.0.0.1:%u", bind_ports[0]); + smtp_submit_set.submission_timeout = 1000; + + test_smtp_submit_input_init(&smtp_input); + smtp_submit = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt(smtp_submit, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + smtp_submit_add_rcpt(smtp_submit, &((struct smtp_address){ + .localpart = "rcpt2", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit); + o_stream_nsend_str(output, test_message1); + + ret = smtp_submit_run(smtp_submit, &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + smtp_submit_deinit(&smtp_submit); + + return FALSE; +} + +/* test */ + +static void test_denied_second_rcpt(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("denied second rcpt"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_second_rcpt, + test_server_denied_second_rcpt, 1); + test_end(); +} + +/* + * Denied DATA + */ + +/* server */ + +enum _denied_data_state { + DENIED_DATA_STATE_EHLO = 0, + DENIED_DATA_STATE_MAIL_FROM, + DENIED_DATA_STATE_RCPT_TO, + DENIED_DATA_STATE_DATA +}; + +struct _denied_data_server { + enum _denied_data_state state; +}; + +static void test_denied_data_input(struct server_connection *conn) +{ + struct _denied_data_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _denied_data_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _denied_data_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_DATA_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DENIED_DATA_STATE_MAIL_FROM; + return; + case DENIED_DATA_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DENIED_DATA_STATE_RCPT_TO; + continue; + case DENIED_DATA_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DENIED_DATA_STATE_DATA; + continue; + case DENIED_DATA_STATE_DATA: + o_stream_nsend_str(conn->conn.output, "500 5.0.0 " + "Unacceptable recipients\r\n"); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_denied_data_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_data(unsigned int index) +{ + test_server_init = test_denied_data_init; + test_server_input = test_denied_data_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_denied_data(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_data(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("denied data"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_data, + test_server_denied_data, 1); + test_end(); +} + +/* + * Data failure + */ + +/* server */ + +enum _data_failure_state { + DATA_FAILURE_STATE_EHLO = 0, + DATA_FAILURE_STATE_MAIL_FROM, + DATA_FAILURE_STATE_RCPT_TO, + DATA_FAILURE_STATE_DATA, + DATA_FAILURE_STATE_FINISH +}; + +struct _data_failure_server { + enum _data_failure_state state; +}; + +static void test_data_failure_input(struct server_connection *conn) +{ + struct _data_failure_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _data_failure_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _data_failure_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DATA_FAILURE_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DATA_FAILURE_STATE_MAIL_FROM; + return; + case DATA_FAILURE_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DATA_FAILURE_STATE_RCPT_TO; + continue; + case DATA_FAILURE_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DATA_FAILURE_STATE_DATA; + continue; + case DATA_FAILURE_STATE_DATA: + o_stream_nsend_str( + conn->conn.output, + "354 End data with .\r\n"); + ctx->state = DATA_FAILURE_STATE_FINISH; + continue; + case DATA_FAILURE_STATE_FINISH: + if (strcmp(line, ".") == 0) { + o_stream_nsend_str( + conn->conn.output, "552 5.2.3 " + "Message length exceeds administrative limit\r\n"); + server_connection_deinit(&conn); + return; + } + continue; + } + i_unreached(); + } +} + +static void test_data_failure_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_data_failure(unsigned int index) +{ + test_server_init = test_data_failure_init; + test_server_input = test_data_failure_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_data_failure(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_data_failure(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("data failure"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_data_failure, + test_server_data_failure, 1); + test_end(); +} + +/* + * Data disconnect + */ + +/* server */ + +enum _data_disconnect_state { + DATA_DISCONNECT_STATE_EHLO = 0, + DATA_DISCONNECT_STATE_MAIL_FROM, + DATA_DISCONNECT_STATE_RCPT_TO, + DATA_DISCONNECT_STATE_DATA, + DATA_DISCONNECT_STATE_FINISH +}; + +struct _data_disconnect_server { + enum _data_disconnect_state state; +}; + +static void test_data_disconnect_input(struct server_connection *conn) +{ + struct _data_disconnect_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _data_disconnect_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _data_disconnect_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DATA_DISCONNECT_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DATA_DISCONNECT_STATE_MAIL_FROM; + return; + case DATA_DISCONNECT_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DATA_DISCONNECT_STATE_RCPT_TO; + continue; + case DATA_DISCONNECT_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DATA_DISCONNECT_STATE_DATA; + continue; + case DATA_DISCONNECT_STATE_DATA: + o_stream_nsend_str( + conn->conn.output, + "354 End data with .\r\n"); + ctx->state = DATA_DISCONNECT_STATE_FINISH; + continue; + case DATA_DISCONNECT_STATE_FINISH: + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_data_disconnect_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_data_disconnect(unsigned int index) +{ + test_server_init = test_data_disconnect_init; + test_server_input = test_data_disconnect_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_data_disconnect(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_data_disconnect(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("data disconnect"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_data_disconnect, + test_server_data_disconnect, 1); + test_end(); +} + +/* + * Data timout + */ + +/* server */ + +enum _data_timout_state { + DATA_TIMEOUT_STATE_EHLO = 0, + DATA_TIMEOUT_STATE_MAIL_FROM, + DATA_TIMEOUT_STATE_RCPT_TO, + DATA_TIMEOUT_STATE_DATA, + DATA_TIMEOUT_STATE_FINISH +}; + +struct _data_timout_server { + enum _data_timout_state state; +}; + +static void test_data_timout_input(struct server_connection *conn) +{ + struct _data_timout_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _data_timout_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _data_timout_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DATA_TIMEOUT_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DATA_TIMEOUT_STATE_MAIL_FROM; + return; + case DATA_TIMEOUT_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DATA_TIMEOUT_STATE_RCPT_TO; + continue; + case DATA_TIMEOUT_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DATA_TIMEOUT_STATE_DATA; + continue; + case DATA_TIMEOUT_STATE_DATA: + o_stream_nsend_str( + conn->conn.output, + "354 End data with .\r\n"); + ctx->state = DATA_TIMEOUT_STATE_FINISH; + continue; + case DATA_TIMEOUT_STATE_FINISH: + i_sleep_intr_secs(10); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_data_timout_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_data_timout(unsigned int index) +{ + test_server_init = test_data_timout_init; + test_server_input = test_data_timout_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_data_timout(const struct smtp_submit_settings *submit_set) +{ + time_t time; + const char *error = NULL; + int ret; + + io_loop_time_refresh(); + time = ioloop_time; + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + io_loop_time_refresh(); + test_out("timeout", (ioloop_time - time) < 5); + + return FALSE; +} + +/* test */ + +static void test_data_timeout(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 2; + + test_begin("data timeout"); + test_expect_errors(1); + test_run_client_server(&smtp_submit_set, + test_client_data_timout, + test_server_data_timout, 1); + test_end(); +} + +/* + * Successful delivery + */ + +/* server */ + +enum _successful_delivery_state { + SUCCESSFUL_DELIVERY_STATE_EHLO = 0, + SUCCESSFUL_DELIVERY_STATE_MAIL_FROM, + SUCCESSFUL_DELIVERY_STATE_RCPT_TO, + SUCCESSFUL_DELIVERY_STATE_DATA, + SUCCESSFUL_DELIVERY_STATE_FINISH +}; + +struct _successful_delivery_server { + enum _successful_delivery_state state; + + char *file_path; + struct istream *dot_input; + struct ostream *file; +}; + +static void test_successful_delivery_input(struct server_connection *conn) +{ + struct _successful_delivery_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _successful_delivery_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _successful_delivery_server *)conn->context; + } + + // FIXME: take structure from test-smtp-client-errors + + for (;;) { + if (ctx->state == SUCCESSFUL_DELIVERY_STATE_FINISH) { + enum ostream_send_istream_result res; + + if (ctx->dot_input == NULL) { + int fd; + + ctx->dot_input = + i_stream_create_dot(conn->conn.input, TRUE); + ctx->file_path = p_strdup_printf( + conn->pool, "%s/message-%u.eml", + test_tmp_dir_get(), server_port); + + if ((fd = open(ctx->file_path, O_WRONLY | O_CREAT, + 0600)) < 0) { + i_fatal("failed create tmp file for message: " + "open(%s) failed: %m", + ctx->file_path); + } + ctx->file = o_stream_create_fd_autoclose( + &fd, IO_BLOCK_SIZE); + } + + res = o_stream_send_istream(ctx->file, ctx->dot_input); + switch (res) { + case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: + break; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: + return; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: + i_error("test server: " + "Failed to read all message payload [%s]", + ctx->file_path); + server_connection_deinit(&conn); + return; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: + i_error("test server: " + "Failed to write all message payload [%s]", + ctx->file_path); + server_connection_deinit(&conn); + return; + } + + o_stream_nsend_str( + conn->conn.output, + "250 2.0.0 Ok: queued as 73BDE342129\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_MAIL_FROM; + continue; + } + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case SUCCESSFUL_DELIVERY_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_MAIL_FROM; + return; + case SUCCESSFUL_DELIVERY_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_RCPT_TO; + continue; + case SUCCESSFUL_DELIVERY_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_DATA; + continue; + case SUCCESSFUL_DELIVERY_STATE_DATA: + o_stream_nsend_str( + conn->conn.output, + "354 End data with .\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_FINISH; + continue; + case SUCCESSFUL_DELIVERY_STATE_FINISH: + break; + } + i_unreached(); + } +} + +static void test_successful_delivery_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_successful_delivery_deinit(struct server_connection *conn) +{ + struct _successful_delivery_server *ctx = + (struct _successful_delivery_server *)conn->context; + + i_stream_unref(&ctx->dot_input); + o_stream_unref(&ctx->file); +} + +static void test_server_successful_delivery(unsigned int index) +{ + test_server_init = test_successful_delivery_init; + test_server_input = test_successful_delivery_input; + test_server_deinit = test_successful_delivery_deinit; + test_server_run(index); +} + +/* client */ + +static bool +test_client_successful_delivery(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + /* send the message */ + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret > 0)", ret > 0, error); + + /* verify delivery */ + test_message_delivery(test_message1, + t_strdup_printf("%s/message-%u.eml", + test_tmp_dir_get(), + bind_ports[0])); + + return FALSE; +} + +struct _parallel_delivery_client { + unsigned int count; +}; + +static void +test_client_parallel_delivery_callback(const struct smtp_submit_result *result, + struct _parallel_delivery_client *ctx) +{ + if (result->status <= 0) + i_error("Submit failed: %s", result->error); + + if (--ctx->count == 0) + io_loop_stop(current_ioloop); +} + +static bool +test_client_parallel_delivery(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct _parallel_delivery_client *ctx; + struct smtp_submit *smtp_submit1, *smtp_submit2; + struct ostream *output; + struct ioloop *ioloop; + + ioloop = io_loop_create(); + + ctx = i_new(struct _parallel_delivery_client, 1); + ctx->count = 2; + + smtp_submit_set = *submit_set; + smtp_submit_set.submission_timeout = 5; + + /* submit 1 */ + test_smtp_submit_input_init(&smtp_input); + smtp_submit_set.submission_host = + t_strdup_printf("127.0.0.1:%u", bind_ports[0]); + smtp_submit1 = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit1, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit1); + o_stream_nsend_str(output, test_message1); + + smtp_submit_run_async( + smtp_submit1, test_client_parallel_delivery_callback, ctx); + + /* submit 2 */ + test_smtp_submit_input_init(&smtp_input); + smtp_submit_set.submission_host = + t_strdup_printf("127.0.0.1:%u", bind_ports[1]); + smtp_submit2 = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit2, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit2); + o_stream_nsend_str(output, test_message2); + + smtp_submit_run_async( + smtp_submit2, test_client_parallel_delivery_callback, ctx); + + io_loop_run(ioloop); + + smtp_submit_deinit(&smtp_submit1); + smtp_submit_deinit(&smtp_submit2); + io_loop_destroy(&ioloop); + + /* verify delivery */ + test_message_delivery(test_message1, + t_strdup_printf("%s/message-%u.eml", + test_tmp_dir_get(), + bind_ports[0])); + test_message_delivery(test_message2, + t_strdup_printf("%s/message-%u.eml", + test_tmp_dir_get(), + bind_ports[1])); + i_free(ctx); + + return FALSE; +} + +/* test */ + +static void test_successful_delivery(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("successful delivery"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_successful_delivery, + test_server_successful_delivery, 1); + test_end(); + + test_begin("parallel delivery"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_parallel_delivery, + test_server_successful_delivery, 2); + test_end(); +} + +/* + * Failed sendmail + */ + +/* client */ + +static bool +test_client_failed_sendmail(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit_settings smtp_submit_set; + struct smtp_submit_input smtp_input; + struct smtp_submit *smtp_submit; + struct ostream *output; + const char *sendmail_path, *error = NULL; + int ret; + + sendmail_path = TEST_BIN_DIR"/sendmail-exit-1.sh"; + + smtp_submit_set = *submit_set; + smtp_submit_set.sendmail_path = sendmail_path; + smtp_submit_set.submission_timeout = 5; + + test_smtp_submit_input_init(&smtp_input); + smtp_submit = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt(smtp_submit, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit); + o_stream_nsend_str(output, test_message1); + + ret = smtp_submit_run(smtp_submit, &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + smtp_submit_deinit(&smtp_submit); + + return FALSE; +} + +/* test */ + +static void test_failed_sendmail(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("failed sendmail"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_failed_sendmail, NULL, 0); + test_end(); +} + +/* + * Successful sendmail + */ + +/* client */ + +static bool +test_client_successful_sendmail(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct smtp_submit *smtp_submit; + struct ostream *output; + const char *sendmail_path, *msg_path, *error = NULL; + int ret; + + msg_path = t_strdup_printf("%s/message.eml", test_tmp_dir_get()); + + sendmail_path = t_strdup_printf( + TEST_BIN_DIR"/sendmail-success.sh %s", msg_path); + + smtp_submit_set = *submit_set; + smtp_submit_set.sendmail_path = sendmail_path; + smtp_submit_set.submission_timeout = 5; + + test_smtp_submit_input_init(&smtp_input); + smtp_submit = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt(smtp_submit, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit); + o_stream_nsend_str(output, test_message1); + + ret = smtp_submit_run(smtp_submit, &error); + test_out_reason("run (ret > 0)", ret > 0, error); + + smtp_submit_deinit(&smtp_submit); + + /* verify delivery */ + test_message_delivery(test_message1, msg_path); + + return FALSE; +} + +/* test */ + +static void test_successful_sendmail(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("successful sendmail"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_successful_sendmail, NULL, 0); + test_end(); +} + +/* + * Parallel sendmail + */ + +/* client */ + +struct _parallel_sendmail_client { + unsigned int count; +}; + +static void +test_client_parallel_sendmail_callback(const struct smtp_submit_result *result, + struct _parallel_sendmail_client *ctx) +{ + if (result->status <= 0) + i_error("Submit failed: %s", result->error); + + if (--ctx->count == 0) + io_loop_stop(current_ioloop); +} + +static bool +test_client_parallel_sendmail(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct _parallel_sendmail_client *ctx; + struct smtp_submit *smtp_submit1, *smtp_submit2; + struct ostream *output; + const char *sendmail_path1, *sendmail_path2; + const char *msg_path1, *msg_path2; + struct ioloop *ioloop; + + ctx = i_new(struct _parallel_sendmail_client, 1); + ctx->count = 2; + + ioloop = io_loop_create(); + + msg_path1 = t_strdup_printf("%s/message1.eml", test_tmp_dir_get()); + msg_path2 = t_strdup_printf("%s/message2.eml", test_tmp_dir_get()); + + sendmail_path1 = t_strdup_printf( + TEST_BIN_DIR"/sendmail-success.sh %s", msg_path1); + sendmail_path2 = t_strdup_printf( + TEST_BIN_DIR"/sendmail-success.sh %s", msg_path2); + + smtp_submit_set = *submit_set; + smtp_submit_set.submission_timeout = 5; + + /* submit 1 */ + test_smtp_submit_input_init(&smtp_input); + smtp_submit_set.sendmail_path = sendmail_path1; + smtp_submit1 = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit1, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit1); + o_stream_nsend_str(output, test_message1); + + smtp_submit_run_async( + smtp_submit1, test_client_parallel_sendmail_callback, ctx); + + /* submit 2 */ + test_smtp_submit_input_init(&smtp_input); + smtp_submit_set.sendmail_path = sendmail_path2; + smtp_submit2 = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit2, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit2); + o_stream_nsend_str(output, test_message2); + + smtp_submit_run_async( + smtp_submit2, test_client_parallel_sendmail_callback, ctx); + + io_loop_run(ioloop); + + smtp_submit_deinit(&smtp_submit1); + smtp_submit_deinit(&smtp_submit2); + io_loop_destroy(&ioloop); + + /* verify delivery */ + test_message_delivery(test_message1, msg_path1); + test_message_delivery(test_message2, msg_path2); + + i_free(ctx); + + return FALSE; +} + +/* test */ + +static void test_parallel_sendmail(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("parallel sendmail"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_parallel_sendmail, NULL, 0); + test_end(); +} + +/* + * All tests + */ + +static void (*const test_functions[])(void) = { + test_host_lookup_failed, + test_connection_refused, + test_connection_timed_out, + test_bad_greeting, + test_denied_helo, + test_disconnect_helo, + test_denied_mail, + test_denied_rcpt, + test_denied_second_rcpt, + test_denied_data, + test_data_failure, + test_data_disconnect, + test_data_timeout, + test_successful_delivery, + test_failed_sendmail, + test_successful_sendmail, + test_parallel_sendmail, + NULL +}; + +/* + * Test client + */ + +static void test_client_defaults(struct smtp_submit_settings *smtp_set) +{ + i_zero(smtp_set); + smtp_set->hostname = "test"; + smtp_set->submission_host = ""; + smtp_set->sendmail_path = "/bin/false"; + smtp_set->mail_debug = debug; +} + +static void test_client_deinit(void) +{ +} + +static int +test_client_smtp_send_simple(const struct smtp_submit_settings *smtp_set, + const char *message, const char *host, + const char **error_r) +{ + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct smtp_submit *smtp_submit; + struct ostream *output; + int ret; + + /* send the message */ + smtp_submit_set = *smtp_set; + smtp_submit_set.submission_host = host, + + i_zero(&smtp_input); + smtp_submit = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit); + o_stream_nsend_str(output, message); + + ret = smtp_submit_run(smtp_submit, error_r); + + smtp_submit_deinit(&smtp_submit); + + return ret; +} + +static int +test_client_smtp_send_simple_port(const struct smtp_submit_settings *smtp_set, + const char *message, unsigned int port, + const char **error_r) +{ + const char *host = t_strdup_printf("127.0.0.1:%u", port); + + return test_client_smtp_send_simple(smtp_set, message, host, error_r); +} + +/* + * Test server + */ + +/* client connection */ + +static void server_connection_input(struct connection *_conn) +{ + struct server_connection *conn = (struct server_connection *)_conn; + + test_server_input(conn); +} + +static void server_connection_init(int fd) +{ + struct server_connection *conn; + pool_t pool; + + net_set_nonblock(fd, TRUE); + + pool = pool_alloconly_create("server connection", 256); + conn = p_new(pool, struct server_connection, 1); + conn->pool = pool; + + connection_init_server(server_conn_list, &conn->conn, + "server connection", fd, fd); + + if (test_server_init != NULL) + test_server_init(conn); +} + +static void server_connection_deinit(struct server_connection **_conn) +{ + struct server_connection *conn = *_conn; + + *_conn = NULL; + + if (test_server_deinit != NULL) + test_server_deinit(conn); + + connection_deinit(&conn->conn); + pool_unref(&conn->pool); +} + +static void server_connection_destroy(struct connection *_conn) +{ + struct server_connection *conn = + (struct server_connection *)_conn; + + server_connection_deinit(&conn); +} + +static void server_connection_accept(void *context ATTR_UNUSED) +{ + int fd; + + /* accept new client */ + fd = net_accept(fd_listen, NULL, NULL); + if (fd == -1) + return; + if (fd == -2) { + i_fatal("test server: accept() failed: %m"); + } + + server_connection_init(fd); +} + +/* */ + +static struct connection_settings server_connection_set = { + .input_max_size = SIZE_MAX, + .output_max_size = SIZE_MAX, + .client = FALSE +}; + +static const struct connection_vfuncs server_connection_vfuncs = { + .destroy = server_connection_destroy, + .input = server_connection_input +}; + +static void test_server_run(unsigned int index) +{ + server_index = index; + + /* open server socket */ + io_listen = io_add(fd_listen, + IO_READ, server_connection_accept, NULL); + + server_conn_list = connection_list_init(&server_connection_set, + &server_connection_vfuncs); + + io_loop_run(ioloop); + + /* close server socket */ + io_remove(&io_listen); + + connection_list_deinit(&server_conn_list); +} + +/* + * Tests + */ + +struct test_server_data { + unsigned int index; + test_server_init_t server_test; +}; + +static int test_open_server_fd(in_port_t *bind_port) +{ + int fd = net_listen(&bind_ip, bind_port, 128); + if (debug) + i_debug("server listening on %u", *bind_port); + if (fd == -1) { + i_fatal("listen(%s:%u) failed: %m", + net_ip2addr(&bind_ip), *bind_port); + } + return fd; +} + +static void test_tmp_dir_init(void) +{ + tmp_dir = i_strdup_printf("/tmp/dovecot-test-smtp-client.%s.%s", + dec2str(time(NULL)), dec2str(getpid())); +} + +static const char *test_tmp_dir_get(void) +{ + if (mkdir(tmp_dir, 0700) < 0 && errno != EEXIST) { + i_fatal("failed to create temporary directory `%s': %m", + tmp_dir); + } + return tmp_dir; +} + +static void test_tmp_dir_deinit(void) +{ + const char *error; + + if (unlink_directory(tmp_dir, UNLINK_DIRECTORY_FLAG_RMDIR, + &error) < 0) { + i_warning("failed to remove temporary directory `%s': %s.", + tmp_dir, error); + } + + i_free(tmp_dir); +} + +static void test_message_delivery(const char *message, const char *file) +{ + struct istream *input; + const unsigned char *data; + size_t size, msize; + int ret; + + msize = strlen(message); + + input = i_stream_create_file(file, SIZE_MAX); + while ((ret = i_stream_read_more(input, &data, &size)) > 0) { + const unsigned char *mdata; + + test_assert(input->v_offset < (uoff_t)msize && + (input->v_offset + (uoff_t)size) <= (uoff_t)msize); + if (test_has_failed()) + break; + mdata = (const unsigned char *)message + input->v_offset; + test_assert(memcmp(data, mdata, size) == 0); + if (test_has_failed()) + break; + i_stream_skip(input, size); + } + + test_out_reason("delivery", ret < 0 && + input->stream_errno == 0 && + input->eof && + input->v_offset == (uoff_t)msize, + (input->stream_errno == 0 ? + NULL : i_stream_get_error(input))); + i_stream_unref(&input); +} + +static int test_run_server(struct test_server_data *data) +{ + server_port = bind_ports[data->index]; + + main_deinit(); + master_service_deinit_forked(&master_service); + + i_set_failure_prefix("SERVER[%u]: ", data->index + 1); + + if (debug) + i_debug("PID=%s", my_pid); + + ioloop = io_loop_create(); + data->server_test(data->index); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); + + i_close_fd(&fd_listen); + i_free(bind_ports); + test_tmp_dir_deinit(); + return 0; +} + +static void +test_run_client(const struct smtp_submit_settings *submit_set, + test_client_init_t client_test) +{ + i_set_failure_prefix("CLIENT: "); + + if (debug) + i_debug("PID=%s", my_pid); + + server_port = 0; + i_sleep_msecs(100); /* wait a little for server setup */ + + ioloop = io_loop_create(); + if (client_test(submit_set)) + io_loop_run(ioloop); + test_client_deinit(); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); +} + +static void +test_run_client_server(const struct smtp_submit_settings *submit_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count) +{ + unsigned int i; + + test_tmp_dir_init(); + + if (server_tests_count > 0) { + int fds[server_tests_count]; + + bind_ports = i_new(in_port_t, server_tests_count); + for (i = 0; i < server_tests_count; i++) + fds[i] = test_open_server_fd(&bind_ports[i]); + + for (i = 0; i < server_tests_count; i++) { + struct test_server_data data; + + i_zero(&data); + data.index = i; + data.server_test = server_test; + + /* Fork server */ + fd_listen = fds[i]; + test_subprocess_fork(test_run_server, &data, FALSE); + i_close_fd(&fd_listen); + } + } + + /* Run client */ + test_run_client(submit_set, client_test); + + i_unset_failure_prefix(); + test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS); + i_free(bind_ports); + test_tmp_dir_deinit(); +} + +/* + * Main + */ + +static void main_init(void) +{ + /* nothing yet */ +} + +static void main_deinit(void) +{ + /* nothing yet; also called from sub-processes */ +} + +int main(int argc, char *argv[]) +{ + const enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_DONT_SEND_STATS; + int c; + int ret; + + master_service = master_service_init("test-smtp-submit", service_flags, + &argc, &argv, "D"); + main_init(); + + while ((c = master_getopt(master_service)) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + + master_service_init_finish(master_service); + test_subprocesses_init(debug); + + /* listen on localhost */ + i_zero(&bind_ip); + bind_ip.family = AF_INET; + bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); + + ret = test_run(test_functions); + + test_subprocesses_deinit(); + main_deinit(); + master_service_deinit(&master_service); + + return ret; +} diff --git a/src/lib-smtp/test-smtp-syntax.c b/src/lib-smtp/test-smtp-syntax.c new file mode 100644 index 0000000..735cd01 --- /dev/null +++ b/src/lib-smtp/test-smtp-syntax.c @@ -0,0 +1,150 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "test-common.h" +#include "smtp-syntax.h" + +/* + * Valid string parse tests + */ + +struct valid_string_parse_test { + const char *input, *parsed, *output; +}; + +static const struct valid_string_parse_test +valid_string_parse_tests[] = { + { + .input = "", + .parsed = "", + }, + { + .input = "atom", + .parsed = "atom", + }, + { + .input = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789!#$%&'*+-/=?^_`{|}~", + .parsed = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789!#$%&'*+-/=?^_`{|}~", + }, + { + .input = "\"quoted-string\"", + .parsed = "quoted-string", + .output = "quoted-string", + }, + { + .input = "\"quoted \\\"string\\\"\"", + .parsed = "quoted \"string\"", + }, + { + .input = "\"quoted \\\\string\\\\\"", + .parsed = "quoted \\string\\", + }, +}; + +static const unsigned int valid_string_parse_test_count = + N_ELEMENTS(valid_string_parse_tests); + +static void test_smtp_string_parse_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_string_parse_test_count; i++) T_BEGIN { + const struct valid_string_parse_test *test = + &valid_string_parse_tests[i]; + const char *parsed, *error = NULL; + int ret; + + ret = smtp_string_parse(test->input, &parsed, &error); + + test_begin(t_strdup_printf("smtp string valid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret >= 0, error); + test_assert(ret != 0 || *test->input == '\0'); + + if (!test_has_failed()) { + string_t *encoded; + const char *output; + + test_out(t_strdup_printf("parsed = \"%s\"", parsed), + null_strcmp(parsed, test->parsed) == 0); + + encoded = t_str_new(255); + smtp_string_write(encoded, parsed); + output = (test->output == NULL ? + test->input : test->output); + test_out(t_strdup_printf("write() = \"%s\"", + str_c(encoded)), + strcmp(str_c(encoded), output) == 0); + } + test_end(); + } T_END; +} + +/* + * Invalid string parse tests + */ + +struct invalid_string_parse_test { + const char *input; +}; + +static const struct invalid_string_parse_test +invalid_string_parse_tests[] = { + { + .input = " ", + }, + { + .input = "\\", + }, + { + .input = "\"", + }, + { + .input = "\"aa", + }, + { + .input = "aa\"", + }, +}; + +static const unsigned int invalid_string_parse_test_count = + N_ELEMENTS(invalid_string_parse_tests); + +static void test_smtp_string_parse_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_string_parse_test_count; i++) T_BEGIN { + const struct invalid_string_parse_test *test = + &invalid_string_parse_tests[i]; + const char *parsed, *error; + int ret; + + ret = smtp_string_parse(test->input, &parsed, &error); + + test_begin(t_strdup_printf("smtp string invalid [%d]", i)); + test_out_reason(t_strdup_printf("parse(\"%s\")", test->input), + ret < 0, error); + test_end(); + } T_END; +} + +/* + * Tests + */ + +int main(void) +{ + static void (*test_functions[])(void) = { + test_smtp_string_parse_valid, + test_smtp_string_parse_invalid, + NULL + }; + return test_run(test_functions); +} -- cgit v1.2.3