summaryrefslogtreecommitdiffstats
path: root/src/lib-smtp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-smtp')
-rw-r--r--src/lib-smtp/Makefile.am192
-rw-r--r--src/lib-smtp/Makefile.in1356
-rw-r--r--src/lib-smtp/fuzz-smtp-server.c102
-rw-r--r--src/lib-smtp/smtp-address.c958
-rw-r--r--src/lib-smtp/smtp-address.h214
-rw-r--r--src/lib-smtp/smtp-client-command.c1580
-rw-r--r--src/lib-smtp/smtp-client-command.h274
-rw-r--r--src/lib-smtp/smtp-client-connection.c2522
-rw-r--r--src/lib-smtp/smtp-client-connection.h93
-rw-r--r--src/lib-smtp/smtp-client-private.h340
-rw-r--r--src/lib-smtp/smtp-client-transaction.c1656
-rw-r--r--src/lib-smtp/smtp-client-transaction.h259
-rw-r--r--src/lib-smtp/smtp-client.c142
-rw-r--r--src/lib-smtp/smtp-client.h128
-rw-r--r--src/lib-smtp/smtp-command-parser.c613
-rw-r--r--src/lib-smtp/smtp-command-parser.h51
-rw-r--r--src/lib-smtp/smtp-command.h38
-rw-r--r--src/lib-smtp/smtp-common.c91
-rw-r--r--src/lib-smtp/smtp-common.h119
-rw-r--r--src/lib-smtp/smtp-params.c1375
-rw-r--r--src/lib-smtp/smtp-params.h233
-rw-r--r--src/lib-smtp/smtp-parser.c587
-rw-r--r--src/lib-smtp/smtp-parser.h89
-rw-r--r--src/lib-smtp/smtp-reply-parser.c657
-rw-r--r--src/lib-smtp/smtp-reply-parser.h25
-rw-r--r--src/lib-smtp/smtp-reply.c186
-rw-r--r--src/lib-smtp/smtp-reply.h82
-rw-r--r--src/lib-smtp/smtp-server-cmd-auth.c242
-rw-r--r--src/lib-smtp/smtp-server-cmd-data.c679
-rw-r--r--src/lib-smtp/smtp-server-cmd-helo.c196
-rw-r--r--src/lib-smtp/smtp-server-cmd-mail.c216
-rw-r--r--src/lib-smtp/smtp-server-cmd-noop.c52
-rw-r--r--src/lib-smtp/smtp-server-cmd-quit.c42
-rw-r--r--src/lib-smtp/smtp-server-cmd-rcpt.c243
-rw-r--r--src/lib-smtp/smtp-server-cmd-rset.c70
-rw-r--r--src/lib-smtp/smtp-server-cmd-starttls.c179
-rw-r--r--src/lib-smtp/smtp-server-cmd-vrfy.c73
-rw-r--r--src/lib-smtp/smtp-server-cmd-xclient.c234
-rw-r--r--src/lib-smtp/smtp-server-command.c901
-rw-r--r--src/lib-smtp/smtp-server-connection.c1648
-rw-r--r--src/lib-smtp/smtp-server-private.h404
-rw-r--r--src/lib-smtp/smtp-server-recipient.c361
-rw-r--r--src/lib-smtp/smtp-server-reply.c876
-rw-r--r--src/lib-smtp/smtp-server-transaction.c361
-rw-r--r--src/lib-smtp/smtp-server.c153
-rw-r--r--src/lib-smtp/smtp-server.h802
-rw-r--r--src/lib-smtp/smtp-submit-settings.c69
-rw-r--r--src/lib-smtp/smtp-submit-settings.h17
-rw-r--r--src/lib-smtp/smtp-submit.c505
-rw-r--r--src/lib-smtp/smtp-submit.h73
-rw-r--r--src/lib-smtp/smtp-syntax.c327
-rw-r--r--src/lib-smtp/smtp-syntax.h49
-rwxr-xr-xsrc/lib-smtp/test-bin/sendmail-exit-1.sh4
-rwxr-xr-xsrc/lib-smtp/test-bin/sendmail-success.sh4
-rw-r--r--src/lib-smtp/test-smtp-address.c1382
-rw-r--r--src/lib-smtp/test-smtp-client-errors.c4374
-rw-r--r--src/lib-smtp/test-smtp-command-parser.c643
-rw-r--r--src/lib-smtp/test-smtp-params.c894
-rw-r--r--src/lib-smtp/test-smtp-payload.c1148
-rw-r--r--src/lib-smtp/test-smtp-reply.c279
-rw-r--r--src/lib-smtp/test-smtp-server-errors.c3883
-rw-r--r--src/lib-smtp/test-smtp-submit.c2199
-rw-r--r--src/lib-smtp/test-smtp-syntax.c150
63 files changed, 37694 insertions, 0 deletions
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..c6c7c66
--- /dev/null
+++ b/src/lib-smtp/Makefile.in
@@ -0,0 +1,1356 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 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 '<CRLF>.<CRLF>'-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 <param> */
+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:<address> <params...> */
+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:<address> 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 <ctype.h>
+
+#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, &params,
+ &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 <ctype.h>
+
+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 <ctype.h>
+
+#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 "<LF>";
+ if (c == 0x0d)
+ return "<CR>";
+ 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 <CRLF>.
+
+ 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 <CRLF>. The
+ commands themselves are alphabetic characters terminated by <SP> if
+ parameters follow and <CRLF> otherwise. (In the interest of improved
+ interoperability, SMTP receivers SHOULD tolerate trailing white space
+ before the terminating <CRLF>.)
+ */
+ 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 <ctype.h>
+
+/*
+ * 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, &param_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(&param);
+ param.keyword = p_strdup(pool, keyword);
+ param.value = p_strdup(pool, value);
+ array_push_back(params, &param);
+}
+
+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=<type>: 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=<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=<keyword>: 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=<size-value>: 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, &params->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,
+ &param, &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(&params->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(&params->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(&params->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=<type>: 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=<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=<keyword>: 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=<size-value>: 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, &params->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(&params->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(&params->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=<type>: 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=<address>: 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,
+ &params->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,
+ &param, &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(&params->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(&params->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(&params->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=<type>: 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=<address>: 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, &params->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(&params->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(&params->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(&params1->extra_params,
+ &params2->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 <ctype.h>
+
+/* 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 <ctype.h>
+
+/* 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 <SP> 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 <SP> 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
+ ;; <sasl-mech> 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,
+ &params);
+ 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, &param, &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:" ( "<Postmaster@" Domain ">" /
+ "<Postmaster>" / Forward-path ) [SP Rcpt-parameters] CRLF
+ Forward-path = Path
+ */
+
+ /* Check transaction state as far as possible */
+ if (!cmd_rcpt_check_state(cmd, FALSE))
+ return;
+
+ /* ( "<Postmaster@" Domain ">" / "<Postmaster>" / 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,
+ &params) < 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, &param, &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,
+ &param, &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, &param.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, &param, &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, &param);
+ } else {
+ unsigned int count = array_count(&conn->mail_param_extensions);
+
+ i_assert(count > 0);
+ array_idx_set(&conn->mail_param_extensions,
+ count - 1, &param);
+ }
+ 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, &param);
+ } else {
+ unsigned int count = array_count(&conn->rcpt_param_extensions);
+
+ i_assert(count > 0);
+ array_idx_set(&conn->rcpt_param_extensions,
+ count - 1, &param);
+ }
+ 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 <stddef.h>
+
+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 <unistd.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <signal.h>
+
+#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 <ctype.h>
+
+/*
+ * 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 <SP> 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(&params, 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(&params, &param);
+ 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(&params, &param);
+ array_append_zero(&params);
+ *params_r = array_front(&params);
+ 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 = "<user>",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
+ .address = { .localpart = "user", .domain = NULL }
+ },
+ {
+ .input = "<user@domain.tld>",
+ .address = { .localpart = "user", .domain = "domain.tld" }
+ },
+ {
+ .input = "<@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld>",
+ .address = { .localpart = "user", .domain = "domain.tld" },
+ .output = "<user@domain.tld>"
+ },
+ {
+ .input = "user@domain.tld",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
+ .address = { .localpart = "user", .domain = "domain.tld" },
+ .output = "<user@domain.tld>"
+ },
+ /* Raw */
+ {
+ .input = "<>",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY |
+ SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW,
+ .address = { .localpart = NULL, .domain = NULL, .raw = NULL }
+ },
+ {
+ .input = "<user>",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
+ SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW,
+ .address = { .localpart = "user", .domain = NULL,
+ .raw = "user" }
+ },
+ {
+ .input = "<user@domain.tld>",
+ .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 = "<user@domain.tld>"
+ },
+ {
+ .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 = "<user@domain.tld>"
+ },
+ /* 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 = "<user>",
+ .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 = "<user@domain.tld>",
+ .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 = "<user@domain.tld>"
+ },
+ {
+ .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 = "<user@domain.tld>"
+ },
+ {
+ .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 = "<u\"ser>",
+ .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 = "<user\"@domain.tld>",
+ .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 = "</@)$@)BLAARGH!@#$$>",
+ .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 = "</@)$@)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 = "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 = "<f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4>",
+ .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 <email@example.com>",
+ },
+ {
+ .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 <email@example.com>",
+ .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 = "<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 <email@example.com>",
+ },
+ {
+ .input = "<email.example.com>",
+ },
+ {
+ .input = "<email@example@example.com>",
+ },
+ {
+ .input = "<あいうえお@example.com>",
+ },
+ {
+ .input = "<email@example.com> (Eric Mail)",
+ },
+ {
+ .input = "<email@example..com>",
+ },
+ {
+ .input = "<email@example.com",
+ },
+ {
+ .input = "email@example.com>",
+ },
+ {
+ .input = "email@example.com>",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ },
+ {
+ .input = "<",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY,
+ },
+ {
+ .input = "<user",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
+ },
+ {
+ .input = "<@otherdomain.tld,@yetanotherdomain.tld.user@domain.tld>",
+ },
+ {
+ .input = "<@###domain.tld,@yetanotherdomain.tld.user@domain.tld>",
+ },
+ {
+ .input = "f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
+ }
+};
+
+unsigned int invalid_path_parse_test_count =
+ N_ELEMENTS(invalid_path_parse_tests);
+
+static void test_smtp_path_parse_invalid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < invalid_path_parse_test_count; i++) T_BEGIN {
+ const struct invalid_path_parse_test *test;
+ struct smtp_address *address;
+ const char *error = NULL;
+ int ret;
+
+ test = &invalid_path_parse_tests[i];
+ ret = smtp_address_parse_path(pool_datastack_create(),
+ test->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 = "<frop@example.com>",
+ .address = "frop@example.com",
+ .pos = 18,
+ .ret = 0,
+ },
+ {
+ .input = "<frop@example.com> ",
+ .address = "frop@example.com",
+ .pos = 18,
+ .ret = 0,
+ },
+ {
+ .input = "<frop@example.com",
+ .pos = 0,
+ .ret = -1,
+ },
+ {
+ .input = "<frop@example.com ",
+ .pos = 0,
+ .ret = -1,
+ },
+ {
+ .input = "fr\"op@example.com",
+ .address = "fr\"op@example.com",
+ .pos = 17,
+ .ret = 0,
+ },
+ {
+ .input = "fr\"op@example.com ",
+ .address = "fr\"op@example.com",
+ .pos = 17,
+ .ret = 0,
+ },
+ {
+ .input = "fr<op@example.com",
+ .address = "fr<op@example.com",
+ .pos = 17,
+ .ret = 0,
+ },
+ {
+ .input = "fr<op@example.com ",
+ .address = "fr<op@example.com",
+ .pos = 17,
+ .ret = 0,
+ },
+ {
+ .input = "\"frop\"@example.com",
+ .address = "\"frop\"@example.com",
+ .pos = 18,
+ .ret = 0,
+ },
+ {
+ .input = "\"frop\"@example.com ",
+ .address = "\"frop\"@example.com",
+ .pos = 18,
+ .ret = 0,
+ },
+ {
+ .input = "\"frop\\\"@example.com",
+ .pos = 0,
+ .ret = -1,
+ },
+ {
+ .input = "\"frop\\\"@example.com ",
+ .pos = 0,
+ .ret = -1,
+ },
+ {
+ .input = "<\"fr>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 <unistd.h>
+
+#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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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 <time.h>
+
+/*
+ * 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:<sender@example.com> ENVID=frop\r\n",
+ .cmd_name = "MAIL",
+ .cmd_params = "FROM:<sender@example.com> ENVID=frop",
+ },
+ {
+ .command = "VRFY \"Sherlock Holmes\"\r\n",
+ .cmd_name = "VRFY",
+ .cmd_params = "\"Sherlock Holmes\"",
+ },
+ {
+ .command = "RCPT TO:<recipient@example.com> NOTIFY=NEVER\r\n",
+ .limits = { .max_parameters_size = 39 },
+ .cmd_name = "RCPT",
+ .cmd_params = "TO:<recipient@example.com> NOTIFY=NEVER",
+ },
+ {
+ .command = "MAIL FROM:<f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4>\r\n",
+ .cmd_name = "MAIL",
+ .cmd_params = "FROM:<f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4>",
+ },
+};
+
+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:<recipient@example.com> NOTIFY=NEVER\r\n",
+ .limits = { .max_parameters_size = 38 },
+ .error_code = SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG,
+ },
+ {
+ .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\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
+ }
+ },
+ /* <extensions> */
+ {
+ .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 = "<UNSPECIFIED>";
+ 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 = "<UNSPECIFIED>";
+ 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, &params, &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, &params);
+ /* BODY */
+ if ((test->caps & SMTP_CAPABILITY_8BITMIME) != 0 ||
+ (test->caps & SMTP_CAPABILITY_BINARYMIME) != 0)
+ test_smtp_mail_params_body(&test->params, &params);
+ /* ENVID */
+ if ((test->caps & SMTP_CAPABILITY_DSN) != 0)
+ test_smtp_mail_params_envid(&test->params, &params);
+ /* RET */
+ if ((test->caps & SMTP_CAPABILITY_DSN) != 0)
+ test_smtp_mail_params_ret(&test->params, &params);
+ /* SIZE */
+ if ((test->caps & SMTP_CAPABILITY_SIZE) != 0)
+ test_smtp_mail_params_size(&test->params, &params);
+ /* <extensions> */
+ if (test->extensions != NULL)
+ test_smtp_mail_params_extensions(&test->params, &params);
+
+ encoded = t_str_new(256);
+ smtp_params_mail_write(encoded, test->caps,
+ test->extensions, &params);
+
+ 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,
+ &params, &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;<e+3Dmc2@example.com>",
+ .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;<user+2Bdetail>",
+ .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,
+ }
+ },
+ /* <extensions> */
+ {
+ .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, "<UNSPECIFIED>");
+ } 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,
+ &params, &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, &params);
+ /* NOTIFY */
+ if ((test->caps & SMTP_CAPABILITY_DSN) != 0)
+ test_smtp_rcpt_params_notify(&test->params, &params);
+ /* <extensions> */
+ if (test->extensions != NULL)
+ test_smtp_rcpt_params_extensions(&test->params, &params);
+
+ encoded = t_str_new(256);
+ smtp_params_rcpt_write(encoded, test->caps,
+ test->extensions, &params);
+
+ 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,
+ &params, &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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#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 <time.h>
+
+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 <frop@example.com> ok\r\n",
+ .status = 250,
+ .enhanced_code = { 2, 1, 0 },
+ .text_lines = (const char *[]){
+ "Originator <frop@example.com> 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 <unistd.h>
+
+#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:<hangman@example.com>\r\n"
+ "RCPT TO:<jerry@example.com>\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:<user@example.com>\r\n"
+ "RCPT TO:<user@example.com>\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:<sender@example.com>\r\n"
+ "RCPT TO:<recipient@example.com>\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: <hendrik@example.com>\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:<hendrik@example.com>\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:<hendrik@example.com>\r\n"
+ "RCPT TO: <harrie@example.com>\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:harrie@example.com\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: harrie@example.com\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:\r\n");
+ break;
+ case 4:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: \r\n");
+ break;
+ case 5:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: NOTIFY=NEVER\r\n");
+ break;
+ case 6:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:<harrie@example.com>\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: <hendrik@example.com>\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\t<hendrik@example.com>\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\t <hendrik@example.com>\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:<hendrik@example.com>\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:<hendrik@example.com>\r\n"
+ "RCPT TO: <harrie@example.com>\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:\t<harrie@example.com>\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:\t <harrie@example.com>\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:harrie@example.com\r\n");
+ break;
+ case 4:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: harrie@example.com\r\n");
+ break;
+ case 5:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:\r\n");
+ break;
+ case 6:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: \r\n");
+ break;
+ case 7:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: NOTIFY=NEVER\r\n");
+ break;
+ case 8:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:<harrie@example.com>\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:<sender@example.com>\r\n"
+ "RCPT TO:<recipient1@example.com>\r\n"
+ "RCPT TO:<recipient2@example.com>\r\n"
+ "RCPT TO:<recipient3@example.com>\r\n"
+ "RCPT TO:<recipient4@example.com>\r\n"
+ "RCPT TO:<recipient5@example.com>\r\n"
+ "RCPT TO:<recipient6@example.com>\r\n"
+ "RCPT TO:<recipient7@example.com>\r\n"
+ "RCPT TO:<recipient8@example.com>\r\n"
+ "RCPT TO:<recipient9@example.com>\r\n"
+ "RCPT TO:<recipient10@example.com>\r\n"
+ "RCPT TO:<recipient11@example.com>\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:<sender@example.com>\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:<senderp@example.com>\r\n"
+ "RCPT TO:<<recipient1@example.com>\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:<frop@example.com>\r\n"
+ "DATA\r\n"
+ "DATA\r\n"
+ "RCPT TO:<frop@example.com>\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:<sender@example.com> BODY=BINARYMIME\r\n"
+ "RCPT TO:<recipient1@example.com>\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: <hendrik@example.com>\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\t<hendrik@example.com>\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\t <hendrik@example.com>\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:<hendrik@example.com>\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:<u\"ser>\r\n");
+ break;
+ case 13:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<u\"ser@domain.tld>\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:</@)$@)BLAARGH!@#$$>\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:<f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4>\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:<user1@example.com>\r\n"
+ "RCPT TO:<user2@example.com>\r\n"
+ "RCPT TO:<user3@example.com>\r\n"
+ "MAIL FROM:<user4@example.com>\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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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);
+}