summaryrefslogtreecommitdiffstats
path: root/src/smtp
diff options
context:
space:
mode:
Diffstat (limited to 'src/smtp')
l---------src/smtp/.indent.pro1
-rw-r--r--src/smtp/.printfck25
-rw-r--r--src/smtp/Makefile.in831
-rw-r--r--src/smtp/lmtp_params.c130
-rw-r--r--src/smtp/smtp-only4
-rw-r--r--src/smtp/smtp.c1430
-rw-r--r--src/smtp/smtp.h727
-rw-r--r--src/smtp/smtp_addr.c693
-rw-r--r--src/smtp/smtp_addr.h31
-rw-r--r--src/smtp/smtp_chat.c486
-rw-r--r--src/smtp/smtp_connect.c1206
-rw-r--r--src/smtp/smtp_key.c215
-rw-r--r--src/smtp/smtp_map11.c325
-rw-r--r--src/smtp/smtp_map11.in33
-rw-r--r--src/smtp/smtp_map11.ref22
-rw-r--r--src/smtp/smtp_params.c134
-rw-r--r--src/smtp/smtp_proto.c2469
-rw-r--r--src/smtp/smtp_rcpt.c221
-rw-r--r--src/smtp/smtp_reuse.c269
-rw-r--r--src/smtp/smtp_reuse.h27
-rw-r--r--src/smtp/smtp_sasl.h43
-rw-r--r--src/smtp/smtp_sasl_auth_cache.c272
-rw-r--r--src/smtp/smtp_sasl_auth_cache.h62
-rw-r--r--src/smtp/smtp_sasl_glue.c512
-rw-r--r--src/smtp/smtp_sasl_proto.c202
-rw-r--r--src/smtp/smtp_session.c446
-rw-r--r--src/smtp/smtp_state.c114
-rw-r--r--src/smtp/smtp_tls_policy.c928
-rw-r--r--src/smtp/smtp_trouble.c462
-rw-r--r--src/smtp/smtp_unalias.c142
-rw-r--r--src/smtp/tls_policy.in64
-rw-r--r--src/smtp/tls_policy.ref65
32 files changed, 12591 insertions, 0 deletions
diff --git a/src/smtp/.indent.pro b/src/smtp/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/smtp/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/smtp/.printfck b/src/smtp/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/smtp/.printfck
@@ -0,0 +1,25 @@
+been_here_xt 2 0
+bounce_append 5 0
+cleanup_out_format 1 0
+defer_append 5 0
+mail_command 1 0
+mail_print 1 0
+msg_error 0 0
+msg_fatal 0 0
+msg_info 0 0
+msg_panic 0 0
+msg_warn 0 0
+opened 4 0
+post_mail_fprintf 1 0
+qmgr_message_bounce 2 0
+rec_fprintf 2 0
+sent 4 0
+smtp_cmd 1 0
+smtp_mesg_fail 2 0
+smtp_printf 1 0
+smtp_rcpt_fail 3 0
+smtp_site_fail 2 0
+udp_syslog 1 0
+vstream_fprintf 1 0
+vstream_printf 0 0
+vstring_sprintf 1 0
diff --git a/src/smtp/Makefile.in b/src/smtp/Makefile.in
new file mode 100644
index 0000000..7d45564
--- /dev/null
+++ b/src/smtp/Makefile.in
@@ -0,0 +1,831 @@
+SHELL = /bin/sh
+SRCS = smtp.c smtp_connect.c smtp_proto.c smtp_chat.c smtp_session.c \
+ smtp_addr.c smtp_trouble.c smtp_state.c smtp_rcpt.c smtp_tls_policy.c \
+ smtp_sasl_proto.c smtp_sasl_glue.c smtp_reuse.c smtp_map11.c \
+ smtp_sasl_auth_cache.c smtp_key.c
+OBJS = smtp.o smtp_connect.o smtp_proto.o smtp_chat.o smtp_session.o \
+ smtp_addr.o smtp_trouble.o smtp_state.o smtp_rcpt.o smtp_tls_policy.o \
+ smtp_sasl_proto.o smtp_sasl_glue.o smtp_reuse.o smtp_map11.o \
+ smtp_sasl_auth_cache.o smtp_key.o
+HDRS = smtp.h smtp_sasl.h smtp_addr.h smtp_reuse.h smtp_sasl_auth_cache.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG= smtp_unalias smtp_map11
+PROG = smtp
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../lib/libxsasl.a \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: smtp_map11_test
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+smtp.o: smtp.c smtp_params.c lmtp_params.c
+
+lmtp_params.c: smtp_params.c
+ egrep -v -f smtp-only smtp_params.c | \
+ sed 's/SMTP/LMTP/g; s/smtp_\([a-z]*_table\)/lmtp_\1/' >$@
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+smtp_unalias: smtp_unalias.c $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
+
+smtp_map11: smtp_map11.c $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
+
+smtp_map11_test: smtp_map11 smtp_map11.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtp_map11 <smtp_map11.in >smtp_map11.tmp 2>&1
+ diff smtp_map11.ref smtp_map11.tmp
+ rm -f smtp_map11.tmp
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+ -e 's/o: \.\//o: /' -e p -e '}' ; \
+ done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+lmtp_params.o: lmtp_params.c
+smtp.o: ../../include/argv.h
+smtp.o: ../../include/attr.h
+smtp.o: ../../include/check_arg.h
+smtp.o: ../../include/debug_peer.h
+smtp.o: ../../include/deliver_request.h
+smtp.o: ../../include/dict.h
+smtp.o: ../../include/dns.h
+smtp.o: ../../include/dsn.h
+smtp.o: ../../include/dsn_buf.h
+smtp.o: ../../include/ext_prop.h
+smtp.o: ../../include/flush_clnt.h
+smtp.o: ../../include/header_body_checks.h
+smtp.o: ../../include/header_opts.h
+smtp.o: ../../include/htable.h
+smtp.o: ../../include/iostuff.h
+smtp.o: ../../include/mail_conf.h
+smtp.o: ../../include/mail_params.h
+smtp.o: ../../include/mail_proto.h
+smtp.o: ../../include/mail_server.h
+smtp.o: ../../include/mail_version.h
+smtp.o: ../../include/maps.h
+smtp.o: ../../include/match_list.h
+smtp.o: ../../include/mime_state.h
+smtp.o: ../../include/msg.h
+smtp.o: ../../include/msg_stats.h
+smtp.o: ../../include/myaddrinfo.h
+smtp.o: ../../include/myflock.h
+smtp.o: ../../include/mymalloc.h
+smtp.o: ../../include/name_code.h
+smtp.o: ../../include/name_mask.h
+smtp.o: ../../include/nvtable.h
+smtp.o: ../../include/recipient_list.h
+smtp.o: ../../include/resolve_clnt.h
+smtp.o: ../../include/scache.h
+smtp.o: ../../include/sock_addr.h
+smtp.o: ../../include/string_list.h
+smtp.o: ../../include/stringops.h
+smtp.o: ../../include/sys_defs.h
+smtp.o: ../../include/tls.h
+smtp.o: ../../include/tls_proxy.h
+smtp.o: ../../include/tok822.h
+smtp.o: ../../include/vbuf.h
+smtp.o: ../../include/vstream.h
+smtp.o: ../../include/vstring.h
+smtp.o: lmtp_params.c
+smtp.o: smtp.c
+smtp.o: smtp.h
+smtp.o: smtp_params.c
+smtp.o: smtp_sasl.h
+smtp_addr.o: ../../include/argv.h
+smtp_addr.o: ../../include/attr.h
+smtp_addr.o: ../../include/check_arg.h
+smtp_addr.o: ../../include/deliver_request.h
+smtp_addr.o: ../../include/dict.h
+smtp_addr.o: ../../include/dns.h
+smtp_addr.o: ../../include/dsn.h
+smtp_addr.o: ../../include/dsn_buf.h
+smtp_addr.o: ../../include/header_body_checks.h
+smtp_addr.o: ../../include/header_opts.h
+smtp_addr.o: ../../include/htable.h
+smtp_addr.o: ../../include/inet_addr_list.h
+smtp_addr.o: ../../include/inet_proto.h
+smtp_addr.o: ../../include/mail_params.h
+smtp_addr.o: ../../include/maps.h
+smtp_addr.o: ../../include/match_list.h
+smtp_addr.o: ../../include/midna_domain.h
+smtp_addr.o: ../../include/mime_state.h
+smtp_addr.o: ../../include/msg.h
+smtp_addr.o: ../../include/msg_stats.h
+smtp_addr.o: ../../include/myaddrinfo.h
+smtp_addr.o: ../../include/myflock.h
+smtp_addr.o: ../../include/mymalloc.h
+smtp_addr.o: ../../include/name_code.h
+smtp_addr.o: ../../include/name_mask.h
+smtp_addr.o: ../../include/nvtable.h
+smtp_addr.o: ../../include/own_inet_addr.h
+smtp_addr.o: ../../include/recipient_list.h
+smtp_addr.o: ../../include/resolve_clnt.h
+smtp_addr.o: ../../include/scache.h
+smtp_addr.o: ../../include/sock_addr.h
+smtp_addr.o: ../../include/string_list.h
+smtp_addr.o: ../../include/stringops.h
+smtp_addr.o: ../../include/sys_defs.h
+smtp_addr.o: ../../include/tls.h
+smtp_addr.o: ../../include/tls_proxy.h
+smtp_addr.o: ../../include/tok822.h
+smtp_addr.o: ../../include/vbuf.h
+smtp_addr.o: ../../include/vstream.h
+smtp_addr.o: ../../include/vstring.h
+smtp_addr.o: smtp.h
+smtp_addr.o: smtp_addr.c
+smtp_addr.o: smtp_addr.h
+smtp_chat.o: ../../include/argv.h
+smtp_chat.o: ../../include/attr.h
+smtp_chat.o: ../../include/check_arg.h
+smtp_chat.o: ../../include/cleanup_user.h
+smtp_chat.o: ../../include/deliver_request.h
+smtp_chat.o: ../../include/dict.h
+smtp_chat.o: ../../include/dns.h
+smtp_chat.o: ../../include/dsn.h
+smtp_chat.o: ../../include/dsn_buf.h
+smtp_chat.o: ../../include/dsn_util.h
+smtp_chat.o: ../../include/header_body_checks.h
+smtp_chat.o: ../../include/header_opts.h
+smtp_chat.o: ../../include/htable.h
+smtp_chat.o: ../../include/int_filt.h
+smtp_chat.o: ../../include/iostuff.h
+smtp_chat.o: ../../include/line_wrap.h
+smtp_chat.o: ../../include/mail_addr.h
+smtp_chat.o: ../../include/mail_error.h
+smtp_chat.o: ../../include/mail_params.h
+smtp_chat.o: ../../include/mail_proto.h
+smtp_chat.o: ../../include/maps.h
+smtp_chat.o: ../../include/match_list.h
+smtp_chat.o: ../../include/mime_state.h
+smtp_chat.o: ../../include/msg.h
+smtp_chat.o: ../../include/msg_stats.h
+smtp_chat.o: ../../include/myaddrinfo.h
+smtp_chat.o: ../../include/myflock.h
+smtp_chat.o: ../../include/mymalloc.h
+smtp_chat.o: ../../include/name_code.h
+smtp_chat.o: ../../include/name_mask.h
+smtp_chat.o: ../../include/nvtable.h
+smtp_chat.o: ../../include/post_mail.h
+smtp_chat.o: ../../include/recipient_list.h
+smtp_chat.o: ../../include/resolve_clnt.h
+smtp_chat.o: ../../include/scache.h
+smtp_chat.o: ../../include/smtp_stream.h
+smtp_chat.o: ../../include/smtputf8.h
+smtp_chat.o: ../../include/sock_addr.h
+smtp_chat.o: ../../include/string_list.h
+smtp_chat.o: ../../include/stringops.h
+smtp_chat.o: ../../include/sys_defs.h
+smtp_chat.o: ../../include/tls.h
+smtp_chat.o: ../../include/tls_proxy.h
+smtp_chat.o: ../../include/tok822.h
+smtp_chat.o: ../../include/vbuf.h
+smtp_chat.o: ../../include/vstream.h
+smtp_chat.o: ../../include/vstring.h
+smtp_chat.o: smtp.h
+smtp_chat.o: smtp_chat.c
+smtp_connect.o: ../../include/argv.h
+smtp_connect.o: ../../include/attr.h
+smtp_connect.o: ../../include/check_arg.h
+smtp_connect.o: ../../include/deliver_pass.h
+smtp_connect.o: ../../include/deliver_request.h
+smtp_connect.o: ../../include/dict.h
+smtp_connect.o: ../../include/dns.h
+smtp_connect.o: ../../include/dsn.h
+smtp_connect.o: ../../include/dsn_buf.h
+smtp_connect.o: ../../include/header_body_checks.h
+smtp_connect.o: ../../include/header_opts.h
+smtp_connect.o: ../../include/host_port.h
+smtp_connect.o: ../../include/htable.h
+smtp_connect.o: ../../include/inet_addr_list.h
+smtp_connect.o: ../../include/inet_proto.h
+smtp_connect.o: ../../include/iostuff.h
+smtp_connect.o: ../../include/mail_addr.h
+smtp_connect.o: ../../include/mail_error.h
+smtp_connect.o: ../../include/mail_params.h
+smtp_connect.o: ../../include/mail_proto.h
+smtp_connect.o: ../../include/maps.h
+smtp_connect.o: ../../include/match_list.h
+smtp_connect.o: ../../include/mime_state.h
+smtp_connect.o: ../../include/msg.h
+smtp_connect.o: ../../include/msg_stats.h
+smtp_connect.o: ../../include/myaddrinfo.h
+smtp_connect.o: ../../include/myflock.h
+smtp_connect.o: ../../include/mymalloc.h
+smtp_connect.o: ../../include/name_code.h
+smtp_connect.o: ../../include/name_mask.h
+smtp_connect.o: ../../include/nvtable.h
+smtp_connect.o: ../../include/own_inet_addr.h
+smtp_connect.o: ../../include/recipient_list.h
+smtp_connect.o: ../../include/resolve_clnt.h
+smtp_connect.o: ../../include/sane_connect.h
+smtp_connect.o: ../../include/scache.h
+smtp_connect.o: ../../include/sock_addr.h
+smtp_connect.o: ../../include/split_at.h
+smtp_connect.o: ../../include/string_list.h
+smtp_connect.o: ../../include/stringops.h
+smtp_connect.o: ../../include/sys_defs.h
+smtp_connect.o: ../../include/timed_connect.h
+smtp_connect.o: ../../include/tls.h
+smtp_connect.o: ../../include/tls_proxy.h
+smtp_connect.o: ../../include/tok822.h
+smtp_connect.o: ../../include/vbuf.h
+smtp_connect.o: ../../include/vstream.h
+smtp_connect.o: ../../include/vstring.h
+smtp_connect.o: smtp.h
+smtp_connect.o: smtp_addr.h
+smtp_connect.o: smtp_connect.c
+smtp_connect.o: smtp_reuse.h
+smtp_key.o: ../../include/argv.h
+smtp_key.o: ../../include/attr.h
+smtp_key.o: ../../include/base64_code.h
+smtp_key.o: ../../include/check_arg.h
+smtp_key.o: ../../include/deliver_request.h
+smtp_key.o: ../../include/dict.h
+smtp_key.o: ../../include/dns.h
+smtp_key.o: ../../include/dsn.h
+smtp_key.o: ../../include/dsn_buf.h
+smtp_key.o: ../../include/header_body_checks.h
+smtp_key.o: ../../include/header_opts.h
+smtp_key.o: ../../include/htable.h
+smtp_key.o: ../../include/mail_params.h
+smtp_key.o: ../../include/maps.h
+smtp_key.o: ../../include/match_list.h
+smtp_key.o: ../../include/mime_state.h
+smtp_key.o: ../../include/msg.h
+smtp_key.o: ../../include/msg_stats.h
+smtp_key.o: ../../include/myaddrinfo.h
+smtp_key.o: ../../include/myflock.h
+smtp_key.o: ../../include/mymalloc.h
+smtp_key.o: ../../include/name_code.h
+smtp_key.o: ../../include/name_mask.h
+smtp_key.o: ../../include/nvtable.h
+smtp_key.o: ../../include/recipient_list.h
+smtp_key.o: ../../include/resolve_clnt.h
+smtp_key.o: ../../include/scache.h
+smtp_key.o: ../../include/sock_addr.h
+smtp_key.o: ../../include/string_list.h
+smtp_key.o: ../../include/sys_defs.h
+smtp_key.o: ../../include/tls.h
+smtp_key.o: ../../include/tls_proxy.h
+smtp_key.o: ../../include/tok822.h
+smtp_key.o: ../../include/vbuf.h
+smtp_key.o: ../../include/vstream.h
+smtp_key.o: ../../include/vstring.h
+smtp_key.o: smtp.h
+smtp_key.o: smtp_key.c
+smtp_map11.o: ../../include/argv.h
+smtp_map11.o: ../../include/attr.h
+smtp_map11.o: ../../include/check_arg.h
+smtp_map11.o: ../../include/deliver_request.h
+smtp_map11.o: ../../include/dict.h
+smtp_map11.o: ../../include/dns.h
+smtp_map11.o: ../../include/dsn.h
+smtp_map11.o: ../../include/dsn_buf.h
+smtp_map11.o: ../../include/header_body_checks.h
+smtp_map11.o: ../../include/header_opts.h
+smtp_map11.o: ../../include/htable.h
+smtp_map11.o: ../../include/mail_addr_form.h
+smtp_map11.o: ../../include/mail_addr_map.h
+smtp_map11.o: ../../include/maps.h
+smtp_map11.o: ../../include/match_list.h
+smtp_map11.o: ../../include/mime_state.h
+smtp_map11.o: ../../include/msg.h
+smtp_map11.o: ../../include/msg_stats.h
+smtp_map11.o: ../../include/myaddrinfo.h
+smtp_map11.o: ../../include/myflock.h
+smtp_map11.o: ../../include/mymalloc.h
+smtp_map11.o: ../../include/name_code.h
+smtp_map11.o: ../../include/name_mask.h
+smtp_map11.o: ../../include/nvtable.h
+smtp_map11.o: ../../include/quote_822_local.h
+smtp_map11.o: ../../include/quote_flags.h
+smtp_map11.o: ../../include/recipient_list.h
+smtp_map11.o: ../../include/resolve_clnt.h
+smtp_map11.o: ../../include/scache.h
+smtp_map11.o: ../../include/sock_addr.h
+smtp_map11.o: ../../include/string_list.h
+smtp_map11.o: ../../include/sys_defs.h
+smtp_map11.o: ../../include/tls.h
+smtp_map11.o: ../../include/tls_proxy.h
+smtp_map11.o: ../../include/tok822.h
+smtp_map11.o: ../../include/vbuf.h
+smtp_map11.o: ../../include/vstream.h
+smtp_map11.o: ../../include/vstring.h
+smtp_map11.o: smtp.h
+smtp_map11.o: smtp_map11.c
+smtp_params.o: smtp_params.c
+smtp_proto.o: ../../include/argv.h
+smtp_proto.o: ../../include/attr.h
+smtp_proto.o: ../../include/bounce.h
+smtp_proto.o: ../../include/check_arg.h
+smtp_proto.o: ../../include/defer.h
+smtp_proto.o: ../../include/deliver_request.h
+smtp_proto.o: ../../include/dict.h
+smtp_proto.o: ../../include/dns.h
+smtp_proto.o: ../../include/dsn.h
+smtp_proto.o: ../../include/dsn_buf.h
+smtp_proto.o: ../../include/dsn_mask.h
+smtp_proto.o: ../../include/ehlo_mask.h
+smtp_proto.o: ../../include/ext_prop.h
+smtp_proto.o: ../../include/header_body_checks.h
+smtp_proto.o: ../../include/header_opts.h
+smtp_proto.o: ../../include/htable.h
+smtp_proto.o: ../../include/iostuff.h
+smtp_proto.o: ../../include/lex_822.h
+smtp_proto.o: ../../include/mail_addr_form.h
+smtp_proto.o: ../../include/mail_addr_map.h
+smtp_proto.o: ../../include/mail_params.h
+smtp_proto.o: ../../include/mail_proto.h
+smtp_proto.o: ../../include/mail_queue.h
+smtp_proto.o: ../../include/maps.h
+smtp_proto.o: ../../include/mark_corrupt.h
+smtp_proto.o: ../../include/match_list.h
+smtp_proto.o: ../../include/match_parent_style.h
+smtp_proto.o: ../../include/mime_state.h
+smtp_proto.o: ../../include/msg.h
+smtp_proto.o: ../../include/msg_stats.h
+smtp_proto.o: ../../include/myaddrinfo.h
+smtp_proto.o: ../../include/myflock.h
+smtp_proto.o: ../../include/mymalloc.h
+smtp_proto.o: ../../include/namadr_list.h
+smtp_proto.o: ../../include/name_code.h
+smtp_proto.o: ../../include/name_mask.h
+smtp_proto.o: ../../include/nvtable.h
+smtp_proto.o: ../../include/off_cvt.h
+smtp_proto.o: ../../include/quote_821_local.h
+smtp_proto.o: ../../include/quote_822_local.h
+smtp_proto.o: ../../include/quote_flags.h
+smtp_proto.o: ../../include/rec_type.h
+smtp_proto.o: ../../include/recipient_list.h
+smtp_proto.o: ../../include/record.h
+smtp_proto.o: ../../include/resolve_clnt.h
+smtp_proto.o: ../../include/scache.h
+smtp_proto.o: ../../include/smtp_stream.h
+smtp_proto.o: ../../include/smtputf8.h
+smtp_proto.o: ../../include/sock_addr.h
+smtp_proto.o: ../../include/split_at.h
+smtp_proto.o: ../../include/string_list.h
+smtp_proto.o: ../../include/stringops.h
+smtp_proto.o: ../../include/sys_defs.h
+smtp_proto.o: ../../include/tls.h
+smtp_proto.o: ../../include/tls_proxy.h
+smtp_proto.o: ../../include/tok822.h
+smtp_proto.o: ../../include/uxtext.h
+smtp_proto.o: ../../include/vbuf.h
+smtp_proto.o: ../../include/vstream.h
+smtp_proto.o: ../../include/vstring.h
+smtp_proto.o: ../../include/vstring_vstream.h
+smtp_proto.o: ../../include/xtext.h
+smtp_proto.o: smtp.h
+smtp_proto.o: smtp_proto.c
+smtp_proto.o: smtp_sasl.h
+smtp_rcpt.o: ../../include/argv.h
+smtp_rcpt.o: ../../include/attr.h
+smtp_rcpt.o: ../../include/bounce.h
+smtp_rcpt.o: ../../include/check_arg.h
+smtp_rcpt.o: ../../include/deliver_completed.h
+smtp_rcpt.o: ../../include/deliver_request.h
+smtp_rcpt.o: ../../include/dict.h
+smtp_rcpt.o: ../../include/dns.h
+smtp_rcpt.o: ../../include/dsn.h
+smtp_rcpt.o: ../../include/dsn_buf.h
+smtp_rcpt.o: ../../include/dsn_mask.h
+smtp_rcpt.o: ../../include/header_body_checks.h
+smtp_rcpt.o: ../../include/header_opts.h
+smtp_rcpt.o: ../../include/htable.h
+smtp_rcpt.o: ../../include/mail_params.h
+smtp_rcpt.o: ../../include/maps.h
+smtp_rcpt.o: ../../include/match_list.h
+smtp_rcpt.o: ../../include/mime_state.h
+smtp_rcpt.o: ../../include/msg.h
+smtp_rcpt.o: ../../include/msg_stats.h
+smtp_rcpt.o: ../../include/myaddrinfo.h
+smtp_rcpt.o: ../../include/myflock.h
+smtp_rcpt.o: ../../include/mymalloc.h
+smtp_rcpt.o: ../../include/name_code.h
+smtp_rcpt.o: ../../include/name_mask.h
+smtp_rcpt.o: ../../include/nvtable.h
+smtp_rcpt.o: ../../include/recipient_list.h
+smtp_rcpt.o: ../../include/resolve_clnt.h
+smtp_rcpt.o: ../../include/scache.h
+smtp_rcpt.o: ../../include/sent.h
+smtp_rcpt.o: ../../include/sock_addr.h
+smtp_rcpt.o: ../../include/string_list.h
+smtp_rcpt.o: ../../include/stringops.h
+smtp_rcpt.o: ../../include/sys_defs.h
+smtp_rcpt.o: ../../include/tls.h
+smtp_rcpt.o: ../../include/tls_proxy.h
+smtp_rcpt.o: ../../include/tok822.h
+smtp_rcpt.o: ../../include/vbuf.h
+smtp_rcpt.o: ../../include/vstream.h
+smtp_rcpt.o: ../../include/vstring.h
+smtp_rcpt.o: smtp.h
+smtp_rcpt.o: smtp_rcpt.c
+smtp_reuse.o: ../../include/argv.h
+smtp_reuse.o: ../../include/attr.h
+smtp_reuse.o: ../../include/check_arg.h
+smtp_reuse.o: ../../include/deliver_request.h
+smtp_reuse.o: ../../include/dict.h
+smtp_reuse.o: ../../include/dns.h
+smtp_reuse.o: ../../include/dsn.h
+smtp_reuse.o: ../../include/dsn_buf.h
+smtp_reuse.o: ../../include/header_body_checks.h
+smtp_reuse.o: ../../include/header_opts.h
+smtp_reuse.o: ../../include/htable.h
+smtp_reuse.o: ../../include/mail_params.h
+smtp_reuse.o: ../../include/maps.h
+smtp_reuse.o: ../../include/match_list.h
+smtp_reuse.o: ../../include/mime_state.h
+smtp_reuse.o: ../../include/msg.h
+smtp_reuse.o: ../../include/msg_stats.h
+smtp_reuse.o: ../../include/myaddrinfo.h
+smtp_reuse.o: ../../include/myflock.h
+smtp_reuse.o: ../../include/mymalloc.h
+smtp_reuse.o: ../../include/name_code.h
+smtp_reuse.o: ../../include/name_mask.h
+smtp_reuse.o: ../../include/nvtable.h
+smtp_reuse.o: ../../include/recipient_list.h
+smtp_reuse.o: ../../include/resolve_clnt.h
+smtp_reuse.o: ../../include/scache.h
+smtp_reuse.o: ../../include/sock_addr.h
+smtp_reuse.o: ../../include/string_list.h
+smtp_reuse.o: ../../include/stringops.h
+smtp_reuse.o: ../../include/sys_defs.h
+smtp_reuse.o: ../../include/tls.h
+smtp_reuse.o: ../../include/tls_proxy.h
+smtp_reuse.o: ../../include/tok822.h
+smtp_reuse.o: ../../include/vbuf.h
+smtp_reuse.o: ../../include/vstream.h
+smtp_reuse.o: ../../include/vstring.h
+smtp_reuse.o: smtp.h
+smtp_reuse.o: smtp_reuse.c
+smtp_reuse.o: smtp_reuse.h
+smtp_sasl_auth_cache.o: ../../include/argv.h
+smtp_sasl_auth_cache.o: ../../include/attr.h
+smtp_sasl_auth_cache.o: ../../include/base64_code.h
+smtp_sasl_auth_cache.o: ../../include/check_arg.h
+smtp_sasl_auth_cache.o: ../../include/deliver_request.h
+smtp_sasl_auth_cache.o: ../../include/dict.h
+smtp_sasl_auth_cache.o: ../../include/dict_proxy.h
+smtp_sasl_auth_cache.o: ../../include/dns.h
+smtp_sasl_auth_cache.o: ../../include/dsn.h
+smtp_sasl_auth_cache.o: ../../include/dsn_buf.h
+smtp_sasl_auth_cache.o: ../../include/dsn_util.h
+smtp_sasl_auth_cache.o: ../../include/header_body_checks.h
+smtp_sasl_auth_cache.o: ../../include/header_opts.h
+smtp_sasl_auth_cache.o: ../../include/htable.h
+smtp_sasl_auth_cache.o: ../../include/maps.h
+smtp_sasl_auth_cache.o: ../../include/match_list.h
+smtp_sasl_auth_cache.o: ../../include/mime_state.h
+smtp_sasl_auth_cache.o: ../../include/msg.h
+smtp_sasl_auth_cache.o: ../../include/msg_stats.h
+smtp_sasl_auth_cache.o: ../../include/myaddrinfo.h
+smtp_sasl_auth_cache.o: ../../include/myflock.h
+smtp_sasl_auth_cache.o: ../../include/mymalloc.h
+smtp_sasl_auth_cache.o: ../../include/name_code.h
+smtp_sasl_auth_cache.o: ../../include/name_mask.h
+smtp_sasl_auth_cache.o: ../../include/nvtable.h
+smtp_sasl_auth_cache.o: ../../include/recipient_list.h
+smtp_sasl_auth_cache.o: ../../include/resolve_clnt.h
+smtp_sasl_auth_cache.o: ../../include/scache.h
+smtp_sasl_auth_cache.o: ../../include/sock_addr.h
+smtp_sasl_auth_cache.o: ../../include/string_list.h
+smtp_sasl_auth_cache.o: ../../include/stringops.h
+smtp_sasl_auth_cache.o: ../../include/sys_defs.h
+smtp_sasl_auth_cache.o: ../../include/tls.h
+smtp_sasl_auth_cache.o: ../../include/tls_proxy.h
+smtp_sasl_auth_cache.o: ../../include/tok822.h
+smtp_sasl_auth_cache.o: ../../include/vbuf.h
+smtp_sasl_auth_cache.o: ../../include/vstream.h
+smtp_sasl_auth_cache.o: ../../include/vstring.h
+smtp_sasl_auth_cache.o: smtp.h
+smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.c
+smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.h
+smtp_sasl_glue.o: ../../include/argv.h
+smtp_sasl_glue.o: ../../include/attr.h
+smtp_sasl_glue.o: ../../include/check_arg.h
+smtp_sasl_glue.o: ../../include/deliver_request.h
+smtp_sasl_glue.o: ../../include/dict.h
+smtp_sasl_glue.o: ../../include/dns.h
+smtp_sasl_glue.o: ../../include/dsn.h
+smtp_sasl_glue.o: ../../include/dsn_buf.h
+smtp_sasl_glue.o: ../../include/header_body_checks.h
+smtp_sasl_glue.o: ../../include/header_opts.h
+smtp_sasl_glue.o: ../../include/htable.h
+smtp_sasl_glue.o: ../../include/mail_addr_find.h
+smtp_sasl_glue.o: ../../include/mail_addr_form.h
+smtp_sasl_glue.o: ../../include/mail_params.h
+smtp_sasl_glue.o: ../../include/maps.h
+smtp_sasl_glue.o: ../../include/match_list.h
+smtp_sasl_glue.o: ../../include/mime_state.h
+smtp_sasl_glue.o: ../../include/msg.h
+smtp_sasl_glue.o: ../../include/msg_stats.h
+smtp_sasl_glue.o: ../../include/myaddrinfo.h
+smtp_sasl_glue.o: ../../include/myflock.h
+smtp_sasl_glue.o: ../../include/mymalloc.h
+smtp_sasl_glue.o: ../../include/name_code.h
+smtp_sasl_glue.o: ../../include/name_mask.h
+smtp_sasl_glue.o: ../../include/nvtable.h
+smtp_sasl_glue.o: ../../include/recipient_list.h
+smtp_sasl_glue.o: ../../include/resolve_clnt.h
+smtp_sasl_glue.o: ../../include/scache.h
+smtp_sasl_glue.o: ../../include/smtp_stream.h
+smtp_sasl_glue.o: ../../include/sock_addr.h
+smtp_sasl_glue.o: ../../include/split_at.h
+smtp_sasl_glue.o: ../../include/string_list.h
+smtp_sasl_glue.o: ../../include/stringops.h
+smtp_sasl_glue.o: ../../include/sys_defs.h
+smtp_sasl_glue.o: ../../include/tls.h
+smtp_sasl_glue.o: ../../include/tls_proxy.h
+smtp_sasl_glue.o: ../../include/tok822.h
+smtp_sasl_glue.o: ../../include/vbuf.h
+smtp_sasl_glue.o: ../../include/vstream.h
+smtp_sasl_glue.o: ../../include/vstring.h
+smtp_sasl_glue.o: ../../include/xsasl.h
+smtp_sasl_glue.o: smtp.h
+smtp_sasl_glue.o: smtp_sasl.h
+smtp_sasl_glue.o: smtp_sasl_auth_cache.h
+smtp_sasl_glue.o: smtp_sasl_glue.c
+smtp_sasl_proto.o: ../../include/argv.h
+smtp_sasl_proto.o: ../../include/attr.h
+smtp_sasl_proto.o: ../../include/check_arg.h
+smtp_sasl_proto.o: ../../include/deliver_request.h
+smtp_sasl_proto.o: ../../include/dict.h
+smtp_sasl_proto.o: ../../include/dns.h
+smtp_sasl_proto.o: ../../include/dsn.h
+smtp_sasl_proto.o: ../../include/dsn_buf.h
+smtp_sasl_proto.o: ../../include/header_body_checks.h
+smtp_sasl_proto.o: ../../include/header_opts.h
+smtp_sasl_proto.o: ../../include/htable.h
+smtp_sasl_proto.o: ../../include/mail_params.h
+smtp_sasl_proto.o: ../../include/maps.h
+smtp_sasl_proto.o: ../../include/match_list.h
+smtp_sasl_proto.o: ../../include/mime_state.h
+smtp_sasl_proto.o: ../../include/msg.h
+smtp_sasl_proto.o: ../../include/msg_stats.h
+smtp_sasl_proto.o: ../../include/myaddrinfo.h
+smtp_sasl_proto.o: ../../include/myflock.h
+smtp_sasl_proto.o: ../../include/mymalloc.h
+smtp_sasl_proto.o: ../../include/name_code.h
+smtp_sasl_proto.o: ../../include/name_mask.h
+smtp_sasl_proto.o: ../../include/nvtable.h
+smtp_sasl_proto.o: ../../include/recipient_list.h
+smtp_sasl_proto.o: ../../include/resolve_clnt.h
+smtp_sasl_proto.o: ../../include/scache.h
+smtp_sasl_proto.o: ../../include/sock_addr.h
+smtp_sasl_proto.o: ../../include/string_list.h
+smtp_sasl_proto.o: ../../include/stringops.h
+smtp_sasl_proto.o: ../../include/sys_defs.h
+smtp_sasl_proto.o: ../../include/tls.h
+smtp_sasl_proto.o: ../../include/tls_proxy.h
+smtp_sasl_proto.o: ../../include/tok822.h
+smtp_sasl_proto.o: ../../include/vbuf.h
+smtp_sasl_proto.o: ../../include/vstream.h
+smtp_sasl_proto.o: ../../include/vstring.h
+smtp_sasl_proto.o: smtp.h
+smtp_sasl_proto.o: smtp_sasl.h
+smtp_sasl_proto.o: smtp_sasl_proto.c
+smtp_session.o: ../../include/argv.h
+smtp_session.o: ../../include/attr.h
+smtp_session.o: ../../include/check_arg.h
+smtp_session.o: ../../include/debug_peer.h
+smtp_session.o: ../../include/deliver_request.h
+smtp_session.o: ../../include/dict.h
+smtp_session.o: ../../include/dns.h
+smtp_session.o: ../../include/dsn.h
+smtp_session.o: ../../include/dsn_buf.h
+smtp_session.o: ../../include/header_body_checks.h
+smtp_session.o: ../../include/header_opts.h
+smtp_session.o: ../../include/htable.h
+smtp_session.o: ../../include/mail_params.h
+smtp_session.o: ../../include/maps.h
+smtp_session.o: ../../include/match_list.h
+smtp_session.o: ../../include/mime_state.h
+smtp_session.o: ../../include/msg.h
+smtp_session.o: ../../include/msg_stats.h
+smtp_session.o: ../../include/myaddrinfo.h
+smtp_session.o: ../../include/myflock.h
+smtp_session.o: ../../include/mymalloc.h
+smtp_session.o: ../../include/name_code.h
+smtp_session.o: ../../include/name_mask.h
+smtp_session.o: ../../include/nvtable.h
+smtp_session.o: ../../include/recipient_list.h
+smtp_session.o: ../../include/resolve_clnt.h
+smtp_session.o: ../../include/scache.h
+smtp_session.o: ../../include/sock_addr.h
+smtp_session.o: ../../include/string_list.h
+smtp_session.o: ../../include/stringops.h
+smtp_session.o: ../../include/sys_defs.h
+smtp_session.o: ../../include/tls.h
+smtp_session.o: ../../include/tls_proxy.h
+smtp_session.o: ../../include/tok822.h
+smtp_session.o: ../../include/vbuf.h
+smtp_session.o: ../../include/vstream.h
+smtp_session.o: ../../include/vstring.h
+smtp_session.o: smtp.h
+smtp_session.o: smtp_sasl.h
+smtp_session.o: smtp_session.c
+smtp_state.o: ../../include/argv.h
+smtp_state.o: ../../include/attr.h
+smtp_state.o: ../../include/check_arg.h
+smtp_state.o: ../../include/deliver_request.h
+smtp_state.o: ../../include/dict.h
+smtp_state.o: ../../include/dns.h
+smtp_state.o: ../../include/dsn.h
+smtp_state.o: ../../include/dsn_buf.h
+smtp_state.o: ../../include/header_body_checks.h
+smtp_state.o: ../../include/header_opts.h
+smtp_state.o: ../../include/htable.h
+smtp_state.o: ../../include/mail_params.h
+smtp_state.o: ../../include/maps.h
+smtp_state.o: ../../include/match_list.h
+smtp_state.o: ../../include/mime_state.h
+smtp_state.o: ../../include/msg.h
+smtp_state.o: ../../include/msg_stats.h
+smtp_state.o: ../../include/myaddrinfo.h
+smtp_state.o: ../../include/myflock.h
+smtp_state.o: ../../include/mymalloc.h
+smtp_state.o: ../../include/name_code.h
+smtp_state.o: ../../include/name_mask.h
+smtp_state.o: ../../include/nvtable.h
+smtp_state.o: ../../include/recipient_list.h
+smtp_state.o: ../../include/resolve_clnt.h
+smtp_state.o: ../../include/scache.h
+smtp_state.o: ../../include/sock_addr.h
+smtp_state.o: ../../include/string_list.h
+smtp_state.o: ../../include/sys_defs.h
+smtp_state.o: ../../include/tls.h
+smtp_state.o: ../../include/tls_proxy.h
+smtp_state.o: ../../include/tok822.h
+smtp_state.o: ../../include/vbuf.h
+smtp_state.o: ../../include/vstream.h
+smtp_state.o: ../../include/vstring.h
+smtp_state.o: smtp.h
+smtp_state.o: smtp_sasl.h
+smtp_state.o: smtp_state.c
+smtp_tls_policy.o: ../../include/argv.h
+smtp_tls_policy.o: ../../include/attr.h
+smtp_tls_policy.o: ../../include/check_arg.h
+smtp_tls_policy.o: ../../include/ctable.h
+smtp_tls_policy.o: ../../include/deliver_request.h
+smtp_tls_policy.o: ../../include/dict.h
+smtp_tls_policy.o: ../../include/dns.h
+smtp_tls_policy.o: ../../include/dsn.h
+smtp_tls_policy.o: ../../include/dsn_buf.h
+smtp_tls_policy.o: ../../include/header_body_checks.h
+smtp_tls_policy.o: ../../include/header_opts.h
+smtp_tls_policy.o: ../../include/htable.h
+smtp_tls_policy.o: ../../include/mail_params.h
+smtp_tls_policy.o: ../../include/maps.h
+smtp_tls_policy.o: ../../include/match_list.h
+smtp_tls_policy.o: ../../include/mime_state.h
+smtp_tls_policy.o: ../../include/msg.h
+smtp_tls_policy.o: ../../include/msg_stats.h
+smtp_tls_policy.o: ../../include/myaddrinfo.h
+smtp_tls_policy.o: ../../include/myflock.h
+smtp_tls_policy.o: ../../include/mymalloc.h
+smtp_tls_policy.o: ../../include/name_code.h
+smtp_tls_policy.o: ../../include/name_mask.h
+smtp_tls_policy.o: ../../include/nvtable.h
+smtp_tls_policy.o: ../../include/recipient_list.h
+smtp_tls_policy.o: ../../include/resolve_clnt.h
+smtp_tls_policy.o: ../../include/scache.h
+smtp_tls_policy.o: ../../include/sock_addr.h
+smtp_tls_policy.o: ../../include/string_list.h
+smtp_tls_policy.o: ../../include/stringops.h
+smtp_tls_policy.o: ../../include/sys_defs.h
+smtp_tls_policy.o: ../../include/tls.h
+smtp_tls_policy.o: ../../include/tls_proxy.h
+smtp_tls_policy.o: ../../include/tok822.h
+smtp_tls_policy.o: ../../include/valid_hostname.h
+smtp_tls_policy.o: ../../include/valid_utf8_hostname.h
+smtp_tls_policy.o: ../../include/vbuf.h
+smtp_tls_policy.o: ../../include/vstream.h
+smtp_tls_policy.o: ../../include/vstring.h
+smtp_tls_policy.o: smtp.h
+smtp_tls_policy.o: smtp_tls_policy.c
+smtp_trouble.o: ../../include/argv.h
+smtp_trouble.o: ../../include/attr.h
+smtp_trouble.o: ../../include/bounce.h
+smtp_trouble.o: ../../include/check_arg.h
+smtp_trouble.o: ../../include/defer.h
+smtp_trouble.o: ../../include/deliver_completed.h
+smtp_trouble.o: ../../include/deliver_request.h
+smtp_trouble.o: ../../include/dict.h
+smtp_trouble.o: ../../include/dns.h
+smtp_trouble.o: ../../include/dsn.h
+smtp_trouble.o: ../../include/dsn_buf.h
+smtp_trouble.o: ../../include/header_body_checks.h
+smtp_trouble.o: ../../include/header_opts.h
+smtp_trouble.o: ../../include/htable.h
+smtp_trouble.o: ../../include/mail_error.h
+smtp_trouble.o: ../../include/mail_params.h
+smtp_trouble.o: ../../include/maps.h
+smtp_trouble.o: ../../include/match_list.h
+smtp_trouble.o: ../../include/mime_state.h
+smtp_trouble.o: ../../include/msg.h
+smtp_trouble.o: ../../include/msg_stats.h
+smtp_trouble.o: ../../include/myaddrinfo.h
+smtp_trouble.o: ../../include/myflock.h
+smtp_trouble.o: ../../include/mymalloc.h
+smtp_trouble.o: ../../include/name_code.h
+smtp_trouble.o: ../../include/name_mask.h
+smtp_trouble.o: ../../include/nvtable.h
+smtp_trouble.o: ../../include/recipient_list.h
+smtp_trouble.o: ../../include/resolve_clnt.h
+smtp_trouble.o: ../../include/scache.h
+smtp_trouble.o: ../../include/smtp_stream.h
+smtp_trouble.o: ../../include/sock_addr.h
+smtp_trouble.o: ../../include/string_list.h
+smtp_trouble.o: ../../include/stringops.h
+smtp_trouble.o: ../../include/sys_defs.h
+smtp_trouble.o: ../../include/tls.h
+smtp_trouble.o: ../../include/tls_proxy.h
+smtp_trouble.o: ../../include/tok822.h
+smtp_trouble.o: ../../include/vbuf.h
+smtp_trouble.o: ../../include/vstream.h
+smtp_trouble.o: ../../include/vstring.h
+smtp_trouble.o: smtp.h
+smtp_trouble.o: smtp_sasl.h
+smtp_trouble.o: smtp_trouble.c
+smtp_unalias.o: ../../include/argv.h
+smtp_unalias.o: ../../include/attr.h
+smtp_unalias.o: ../../include/check_arg.h
+smtp_unalias.o: ../../include/deliver_request.h
+smtp_unalias.o: ../../include/dict.h
+smtp_unalias.o: ../../include/dns.h
+smtp_unalias.o: ../../include/dsn.h
+smtp_unalias.o: ../../include/dsn_buf.h
+smtp_unalias.o: ../../include/header_body_checks.h
+smtp_unalias.o: ../../include/header_opts.h
+smtp_unalias.o: ../../include/htable.h
+smtp_unalias.o: ../../include/maps.h
+smtp_unalias.o: ../../include/match_list.h
+smtp_unalias.o: ../../include/mime_state.h
+smtp_unalias.o: ../../include/msg.h
+smtp_unalias.o: ../../include/msg_stats.h
+smtp_unalias.o: ../../include/myaddrinfo.h
+smtp_unalias.o: ../../include/myflock.h
+smtp_unalias.o: ../../include/mymalloc.h
+smtp_unalias.o: ../../include/name_code.h
+smtp_unalias.o: ../../include/name_mask.h
+smtp_unalias.o: ../../include/nvtable.h
+smtp_unalias.o: ../../include/recipient_list.h
+smtp_unalias.o: ../../include/resolve_clnt.h
+smtp_unalias.o: ../../include/scache.h
+smtp_unalias.o: ../../include/sock_addr.h
+smtp_unalias.o: ../../include/string_list.h
+smtp_unalias.o: ../../include/sys_defs.h
+smtp_unalias.o: ../../include/tls.h
+smtp_unalias.o: ../../include/tls_proxy.h
+smtp_unalias.o: ../../include/tok822.h
+smtp_unalias.o: ../../include/vbuf.h
+smtp_unalias.o: ../../include/vstream.h
+smtp_unalias.o: ../../include/vstring.h
+smtp_unalias.o: smtp.h
+smtp_unalias.o: smtp_unalias.c
diff --git a/src/smtp/lmtp_params.c b/src/smtp/lmtp_params.c
new file mode 100644
index 0000000..973cb5d
--- /dev/null
+++ b/src/smtp/lmtp_params.c
@@ -0,0 +1,130 @@
+ static const CONFIG_STR_TABLE lmtp_str_table[] = {
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_LMTP_FALLBACK, DEF_LMTP_FALLBACK, &var_fallback_relay, 0, 0,
+ VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0,
+ VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
+ VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0,
+ VAR_LMTP_SASL_OPTS, DEF_LMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0,
+ VAR_LMTP_SASL_PATH, DEF_LMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0,
+#ifdef USE_TLS
+ VAR_LMTP_SASL_TLS_OPTS, DEF_LMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0,
+ VAR_LMTP_SASL_TLSV_OPTS, DEF_LMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0,
+ VAR_LMTP_TLS_CHAIN_FILES, DEF_LMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0,
+ VAR_LMTP_TLS_CERT_FILE, DEF_LMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0,
+ VAR_LMTP_TLS_KEY_FILE, DEF_LMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0,
+ VAR_LMTP_TLS_DCERT_FILE, DEF_LMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0,
+ VAR_LMTP_TLS_DKEY_FILE, DEF_LMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0,
+ VAR_LMTP_TLS_CA_FILE, DEF_LMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0,
+ VAR_LMTP_TLS_CA_PATH, DEF_LMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0,
+ VAR_LMTP_TLS_MAND_CIPH, DEF_LMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0,
+ VAR_LMTP_TLS_EXCL_CIPH, DEF_LMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0,
+ VAR_LMTP_TLS_MAND_EXCL, DEF_LMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0,
+ VAR_LMTP_TLS_MAND_PROTO, DEF_LMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0,
+ VAR_LMTP_TLS_VFY_CMATCH, DEF_LMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0,
+ VAR_LMTP_TLS_SEC_CMATCH, DEF_LMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0,
+ VAR_LMTP_TLS_FPT_CMATCH, DEF_LMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0,
+ VAR_LMTP_TLS_FPT_DGST, DEF_LMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0,
+ VAR_LMTP_TLS_TAFILE, DEF_LMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0,
+ VAR_LMTP_TLS_PROTO, DEF_LMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0,
+ VAR_LMTP_TLS_CIPH, DEF_LMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0,
+ VAR_LMTP_TLS_ECCERT_FILE, DEF_LMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0,
+ VAR_LMTP_TLS_ECKEY_FILE, DEF_LMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0,
+ VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
+ VAR_LMTP_TLS_SNI, DEF_LMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0,
+#endif
+ VAR_LMTP_SASL_MECHS, DEF_LMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0,
+ VAR_LMTP_SASL_TYPE, DEF_LMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0,
+ VAR_LMTP_BIND_ADDR, DEF_LMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0,
+ VAR_LMTP_BIND_ADDR6, DEF_LMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0,
+ VAR_LMTP_VRFY_TGT, DEF_LMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0,
+ VAR_LMTP_HELO_NAME, DEF_LMTP_HELO_NAME, &var_smtp_helo_name, 1, 0,
+ VAR_LMTP_HOST_LOOKUP, DEF_LMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0,
+ VAR_LMTP_DNS_SUPPORT, DEF_LMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0,
+ VAR_LMTP_CACHE_DEST, DEF_LMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0,
+ VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0,
+ VAR_LMTP_EHLO_DIS_WORDS, DEF_LMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0,
+ VAR_LMTP_EHLO_DIS_MAPS, DEF_LMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0,
+ VAR_LMTP_TLS_PER_SITE, DEF_LMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0,
+ VAR_LMTP_TLS_LEVEL, DEF_LMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0,
+ VAR_LMTP_TLS_POLICY, DEF_LMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0,
+ VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0,
+ VAR_LMTP_GENERIC_MAPS, DEF_LMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0,
+ VAR_LMTP_TCP_PORT, DEF_LMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0,
+ VAR_LMTP_PIX_BUG_WORDS, DEF_LMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0,
+ VAR_LMTP_PIX_BUG_MAPS, DEF_LMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0,
+ VAR_LMTP_SASL_AUTH_CACHE_NAME, DEF_LMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0,
+ VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0,
+ VAR_LMTP_HEAD_CHKS, DEF_LMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0,
+ VAR_LMTP_MIME_CHKS, DEF_LMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0,
+ VAR_LMTP_NEST_CHKS, DEF_LMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0,
+ VAR_LMTP_BODY_CHKS, DEF_LMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0,
+ VAR_LMTP_RESP_FILTER, DEF_LMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0,
+ VAR_LMTP_ADDR_PREF, DEF_LMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0,
+ VAR_LMTP_DNS_RES_OPT, DEF_LMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0,
+ VAR_LMTP_DSN_FILTER, DEF_LMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0,
+ VAR_LMTP_DNS_RE_FILTER, DEF_LMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0,
+ VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE lmtp_time_table[] = {
+ VAR_LMTP_CONN_TMOUT, DEF_LMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0,
+ VAR_LMTP_HELO_TMOUT, DEF_LMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0,
+ VAR_LMTP_XFWD_TMOUT, DEF_LMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0,
+ VAR_LMTP_MAIL_TMOUT, DEF_LMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0,
+ VAR_LMTP_RCPT_TMOUT, DEF_LMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0,
+ VAR_LMTP_DATA0_TMOUT, DEF_LMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0,
+ VAR_LMTP_DATA1_TMOUT, DEF_LMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0,
+ VAR_LMTP_DATA2_TMOUT, DEF_LMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0,
+ VAR_LMTP_RSET_TMOUT, DEF_LMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0,
+ VAR_LMTP_QUIT_TMOUT, DEF_LMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0,
+ VAR_LMTP_PIX_THRESH, DEF_LMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0,
+ VAR_LMTP_PIX_DELAY, DEF_LMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0,
+ VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0,
+ VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0,
+ VAR_LMTP_CACHE_CONNT, DEF_LMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0,
+ VAR_LMTP_REUSE_TIME, DEF_LMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0,
+#ifdef USE_TLS
+ VAR_LMTP_STARTTLS_TMOUT, DEF_LMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0,
+#endif
+ VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0,
+ VAR_LMTP_SASL_AUTH_CACHE_TIME, DEF_LMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE lmtp_int_table[] = {
+ VAR_LMTP_LINE_LIMIT, DEF_LMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0,
+ VAR_LMTP_MXADDR_LIMIT, DEF_LMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0,
+ VAR_LMTP_MXSESS_LIMIT, DEF_LMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0,
+ VAR_LMTP_REUSE_COUNT, DEF_LMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0,
+#ifdef USE_TLS
+ VAR_LMTP_TLS_SCERT_VD, DEF_LMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0,
+#endif
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE lmtp_bool_table[] = {
+ VAR_LMTP_SKIP_5XX, DEF_LMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting,
+ VAR_LMTP_SKIP_QUIT_RESP, DEF_LMTP_SKIP_QUIT_RESP, &var_skip_quit_resp,
+ VAR_LMTP_SASL_ENABLE, DEF_LMTP_SASL_ENABLE, &var_smtp_sasl_enable,
+ VAR_LMTP_RAND_ADDR, DEF_LMTP_RAND_ADDR, &var_smtp_rand_addr,
+ VAR_LMTP_QUOTE_821_ENV, DEF_LMTP_QUOTE_821_ENV, &var_smtp_quote_821_env,
+ VAR_LMTP_DEFER_MXADDR, DEF_LMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr,
+ VAR_LMTP_SEND_XFORWARD, DEF_LMTP_SEND_XFORWARD, &var_smtp_send_xforward,
+ VAR_LMTP_CACHE_DEMAND, DEF_LMTP_CACHE_DEMAND, &var_smtp_cache_demand,
+ VAR_LMTP_USE_TLS, DEF_LMTP_USE_TLS, &var_smtp_use_tls,
+ VAR_LMTP_ENFORCE_TLS, DEF_LMTP_ENFORCE_TLS, &var_smtp_enforce_tls,
+ VAR_LMTP_TLS_CONN_REUSE, DEF_LMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse,
+#ifdef USE_TLS
+ VAR_LMTP_TLS_ENFORCE_PN, DEF_LMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername,
+ VAR_LMTP_TLS_NOTEOFFER, DEF_LMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer,
+ VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply,
+ VAR_LMTP_TLS_FORCE_TLSA, DEF_LMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa,
+#endif
+ VAR_LMTP_TLS_WRAPPER, DEF_LMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode,
+ VAR_LMTP_SENDER_AUTH, DEF_LMTP_SENDER_AUTH, &var_smtp_sender_auth,
+ VAR_LMTP_CNAME_OVERR, DEF_LMTP_CNAME_OVERR, &var_smtp_cname_overr,
+ VAR_LMTP_SASL_AUTH_SOFT_BOUNCE, DEF_LMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce,
+ VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final,
+ VAR_LMTP_REC_DEADLINE, DEF_LMTP_REC_DEADLINE, &var_smtp_rec_deadline,
+ VAR_LMTP_DUMMY_MAIL_AUTH, DEF_LMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth,
+ VAR_LMTP_BALANCE_INET_PROTO, DEF_LMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto,
+ 0,
+ };
diff --git a/src/smtp/smtp-only b/src/smtp/smtp-only
new file mode 100644
index 0000000..ee9288d
--- /dev/null
+++ b/src/smtp/smtp-only
@@ -0,0 +1,4 @@
+_ALWAYS_EHLO
+_NEVER_EHLO
+_IGN_MX_LOOKUP_ERR
+_INSECURE_MX_POLICY
diff --git a/src/smtp/smtp.c b/src/smtp/smtp.c
new file mode 100644
index 0000000..db17578
--- /dev/null
+++ b/src/smtp/smtp.c
@@ -0,0 +1,1430 @@
+/*++
+/* NAME
+/* smtp 8
+/* SUMMARY
+/* Postfix SMTP+LMTP client
+/* SYNOPSIS
+/* \fBsmtp\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The Postfix SMTP+LMTP client implements the SMTP and LMTP mail
+/* delivery protocols. It processes message delivery requests from
+/* the queue manager. Each request specifies a queue file, a sender
+/* address, a domain or host to deliver to, and recipient information.
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* The SMTP+LMTP client updates the queue file and marks recipients
+/* as finished, or it informs the queue manager that delivery should
+/* be tried again at a later time. Delivery status reports are sent
+/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as
+/* appropriate.
+/*
+/* The SMTP+LMTP client looks up a list of mail exchanger addresses for
+/* the destination host, sorts the list by preference, and connects
+/* to each listed address until it finds a server that responds.
+/*
+/* When a server is not reachable, or when mail delivery fails due
+/* to a recoverable error condition, the SMTP+LMTP client will try to
+/* deliver the mail to an alternate host.
+/*
+/* After a successful mail transaction, a connection may be saved
+/* to the \fBscache\fR(8) connection cache server, so that it
+/* may be used by any SMTP+LMTP client for a subsequent transaction.
+/*
+/* By default, connection caching is enabled temporarily for
+/* destinations that have a high volume of mail in the active
+/* queue. Connection caching can be enabled permanently for
+/* specific destinations.
+/* SMTP DESTINATION SYNTAX
+/* .ad
+/* .fi
+/* SMTP destinations have the following form:
+/* .IP \fIdomainname\fR
+/* .IP \fIdomainname\fR:\fIport\fR
+/* Look up the mail exchangers for the specified domain, and
+/* connect to the specified port (default: \fBsmtp\fR).
+/* .IP [\fIhostname\fR]
+/* .IP [\fIhostname\fR]:\fIport\fR
+/* Look up the address(es) of the specified host, and connect to
+/* the specified port (default: \fBsmtp\fR).
+/* .IP [\fIaddress\fR]
+/* .IP [\fIaddress\fR]:\fIport\fR
+/* Connect to the host at the specified address, and connect
+/* to the specified port (default: \fBsmtp\fR). An IPv6 address
+/* must be formatted as [\fBipv6\fR:\fIaddress\fR].
+/* LMTP DESTINATION SYNTAX
+/* .ad
+/* .fi
+/* LMTP destinations have the following form:
+/* .IP \fBunix\fR:\fIpathname\fR
+/* Connect to the local UNIX-domain server that is bound to the specified
+/* \fIpathname\fR. If the process runs chrooted, an absolute pathname
+/* is interpreted relative to the Postfix queue directory.
+/* .IP \fBinet\fR:\fIhostname\fR
+/* .IP \fBinet\fR:\fIhostname\fR:\fIport\fR
+/* .IP \fBinet\fR:[\fIaddress\fR]
+/* .IP \fBinet\fR:[\fIaddress\fR]:\fIport\fR
+/* Connect to the specified TCP port on the specified local or
+/* remote host. If no port is specified, connect to the port defined as
+/* \fBlmtp\fR in \fBservices\fR(4).
+/* If no such service is found, the \fBlmtp_tcp_port\fR configuration
+/* parameter (default value of 24) will be used.
+/* An IPv6 address must be formatted as [\fBipv6\fR:\fIaddress\fR].
+/* .PP
+/* SECURITY
+/* .ad
+/* .fi
+/* The SMTP+LMTP client is moderately security-sensitive. It
+/* talks to SMTP or LMTP servers and to DNS servers on the
+/* network. The SMTP+LMTP client can be run chrooted at fixed
+/* low privilege.
+/* STANDARDS
+/* RFC 821 (SMTP protocol)
+/* RFC 822 (ARPA Internet Text Messages)
+/* RFC 1651 (SMTP service extensions)
+/* RFC 1652 (8bit-MIME transport)
+/* RFC 1870 (Message Size Declaration)
+/* RFC 2033 (LMTP protocol)
+/* RFC 2034 (SMTP Enhanced Error Codes)
+/* RFC 2045 (MIME: Format of Internet Message Bodies)
+/* RFC 2046 (MIME: Media Types)
+/* RFC 2554 (AUTH command)
+/* RFC 2821 (SMTP protocol)
+/* RFC 2920 (SMTP Pipelining)
+/* RFC 3207 (STARTTLS command)
+/* RFC 3461 (SMTP DSN Extension)
+/* RFC 3463 (Enhanced Status Codes)
+/* RFC 4954 (AUTH command)
+/* RFC 5321 (SMTP protocol)
+/* RFC 6531 (Internationalized SMTP)
+/* RFC 6533 (Internationalized Delivery Status Notifications)
+/* RFC 7672 (SMTP security via opportunistic DANE TLS)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* Corrupted message files are marked so that the queue manager can
+/* move them to the \fBcorrupt\fR queue for further inspection.
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces, protocol problems, and of
+/* other trouble.
+/* BUGS
+/* SMTP and LMTP connection reuse for TLS (without closing the
+/* SMTP or LMTP connection) is not supported before Postfix 3.4.
+/*
+/* SMTP and LMTP connection caching assumes that SASL credentials
+/* are valid for all destinations that map onto the same IP
+/* address and TCP port.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Before Postfix version 2.3, the LMTP client is a separate
+/* program that implements only a subset of the functionality
+/* available with SMTP: there is no support for TLS, and
+/* connections are cached in-process, making it ineffective
+/* when the client is used for multiple domains.
+/*
+/* Most smtp_\fIxxx\fR configuration parameters have an
+/* lmtp_\fIxxx\fR "mirror" parameter for the equivalent LMTP
+/* feature. This document describes only those LMTP-related
+/* parameters that aren't simply "mirror" parameters.
+/*
+/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtp\fR(8)
+/* processes run for only a limited amount of time. Use the command
+/* "\fBpostfix reload\fR" to speed up a change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* COMPATIBILITY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBignore_mx_lookup_error (no)\fR"
+/* Ignore DNS MX lookups that produce no response.
+/* .IP "\fBsmtp_always_send_ehlo (yes)\fR"
+/* Always send EHLO at the start of an SMTP session.
+/* .IP "\fBsmtp_never_send_ehlo (no)\fR"
+/* Never send EHLO at the start of an SMTP session.
+/* .IP "\fBsmtp_defer_if_no_mx_address_found (no)\fR"
+/* Defer mail delivery when no MX record resolves to an IP address.
+/* .IP "\fBsmtp_line_length_limit (998)\fR"
+/* The maximal length of message header and body lines that Postfix
+/* will send via SMTP.
+/* .IP "\fBsmtp_pix_workaround_delay_time (10s)\fR"
+/* How long the Postfix SMTP client pauses before sending
+/* ".<CR><LF>" in order to work around the PIX firewall
+/* "<CR><LF>.<CR><LF>" bug.
+/* .IP "\fBsmtp_pix_workaround_threshold_time (500s)\fR"
+/* How long a message must be queued before the Postfix SMTP client
+/* turns on the PIX firewall "<CR><LF>.<CR><LF>"
+/* bug workaround for delivery through firewalls with "smtp fixup"
+/* mode turned on.
+/* .IP "\fBsmtp_pix_workarounds (disable_esmtp, delay_dotcrlf)\fR"
+/* A list that specifies zero or more workarounds for CISCO PIX
+/* firewall bugs.
+/* .IP "\fBsmtp_pix_workaround_maps (empty)\fR"
+/* Lookup tables, indexed by the remote SMTP server address, with
+/* per-destination workarounds for CISCO PIX firewall bugs.
+/* .IP "\fBsmtp_quote_rfc821_envelope (yes)\fR"
+/* Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO commands
+/* as required
+/* by RFC 5321.
+/* .IP "\fBsmtp_reply_filter (empty)\fR"
+/* A mechanism to transform replies from remote SMTP servers one
+/* line at a time.
+/* .IP "\fBsmtp_skip_5xx_greeting (yes)\fR"
+/* Skip remote SMTP servers that greet with a 5XX status code.
+/* .IP "\fBsmtp_skip_quit_response (yes)\fR"
+/* Do not wait for the response to the SMTP QUIT command.
+/* .PP
+/* Available in Postfix version 2.0 and earlier:
+/* .IP "\fBsmtp_skip_4xx_greeting (yes)\fR"
+/* Skip SMTP servers that greet with a 4XX status code (go away, try
+/* again later).
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtp_discard_ehlo_keyword_address_maps (empty)\fR"
+/* Lookup tables, indexed by the remote SMTP server address, with
+/* case insensitive lists of EHLO keywords (pipelining, starttls, auth,
+/* etc.) that the Postfix SMTP client will ignore in the EHLO response from a
+/* remote SMTP server.
+/* .IP "\fBsmtp_discard_ehlo_keywords (empty)\fR"
+/* A case insensitive list of EHLO keywords (pipelining, starttls,
+/* auth, etc.) that the Postfix SMTP client will ignore in the EHLO
+/* response from a remote SMTP server.
+/* .IP "\fBsmtp_generic_maps (empty)\fR"
+/* Optional lookup tables that perform address rewriting in the
+/* Postfix SMTP client, typically to transform a locally valid address into
+/* a globally valid address when sending mail across the Internet.
+/* .PP
+/* Available in Postfix version 2.2.9 and later:
+/* .IP "\fBsmtp_cname_overrides_servername (version dependent)\fR"
+/* When the remote SMTP servername is a DNS CNAME, replace the
+/* servername with the result from CNAME expansion for the purpose of
+/* logging, SASL password lookup, TLS
+/* policy decisions, or TLS certificate verification.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBlmtp_discard_lhlo_keyword_address_maps (empty)\fR"
+/* Lookup tables, indexed by the remote LMTP server address, with
+/* case insensitive lists of LHLO keywords (pipelining, starttls,
+/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO
+/* response
+/* from a remote LMTP server.
+/* .IP "\fBlmtp_discard_lhlo_keywords (empty)\fR"
+/* A case insensitive list of LHLO keywords (pipelining, starttls,
+/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO
+/* response
+/* from a remote LMTP server.
+/* .PP
+/* Available in Postfix version 2.4.4 and later:
+/* .IP "\fBsend_cyrus_sasl_authzid (no)\fR"
+/* When authenticating to a remote SMTP or LMTP server with the
+/* default setting "no", send no SASL authoriZation ID (authzid); send
+/* only the SASL authentiCation ID (authcid) plus the authcid's password.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBsmtp_header_checks (empty)\fR"
+/* Restricted \fBheader_checks\fR(5) tables for the Postfix SMTP client.
+/* .IP "\fBsmtp_mime_header_checks (empty)\fR"
+/* Restricted \fBmime_header_checks\fR(5) tables for the Postfix SMTP
+/* client.
+/* .IP "\fBsmtp_nested_header_checks (empty)\fR"
+/* Restricted \fBnested_header_checks\fR(5) tables for the Postfix SMTP
+/* client.
+/* .IP "\fBsmtp_body_checks (empty)\fR"
+/* Restricted \fBbody_checks\fR(5) tables for the Postfix SMTP client.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBtcp_windowsize (0)\fR"
+/* An optional workaround for routers that break TCP window scaling.
+/* .PP
+/* Available in Postfix version 2.8 and later:
+/* .IP "\fBsmtp_dns_resolver_options (empty)\fR"
+/* DNS Resolver options for the Postfix SMTP client.
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBsmtp_per_record_deadline (no)\fR"
+/* Change the behavior of the smtp_*_timeout time limits, from a
+/* time limit per read or write system call, to a time limit to send
+/* or receive a complete record (an SMTP command line, SMTP response
+/* line, SMTP message content line, or TLS protocol message).
+/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR"
+/* Whether or not to append the "AUTH=<>" option to the MAIL
+/* FROM command in SASL-authenticated SMTP sessions.
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBsmtp_dns_support_level (empty)\fR"
+/* Level of DNS support in the Postfix SMTP client.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBsmtp_delivery_status_filter ($default_delivery_status_filter)\fR"
+/* Optional filter for the \fBsmtp\fR(8) delivery agent to change the
+/* delivery status code or explanatory text of successful or unsuccessful
+/* deliveries.
+/* .IP "\fBsmtp_dns_reply_filter (empty)\fR"
+/* Optional filter for Postfix SMTP client DNS lookup results.
+/* .PP
+/* Available in Postfix version 3.3 and later:
+/* .IP "\fBsmtp_balance_inet_protocols (yes)\fR"
+/* When a remote destination resolves to a combination of IPv4 and
+/* IPv6 addresses, ensure that the Postfix SMTP client can try both
+/* address types before it runs into the smtp_mx_address_limit.
+/* .PP
+/* Available in Postfix 3.4.19 and later:
+/* .IP "\fBdnssec_probe (ns:.)\fR"
+/* The DNS query type (default: "ns") and DNS query name (default:
+/* ".") that Postfix may use to determine whether DNSSEC validation
+/* is available.
+/* MIME PROCESSING CONTROLS
+/* .ad
+/* .fi
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBdisable_mime_output_conversion (no)\fR"
+/* Disable the conversion of 8BITMIME format to 7BIT format.
+/* .IP "\fBmime_boundary_length_limit (2048)\fR"
+/* The maximal length of MIME multipart boundary strings.
+/* .IP "\fBmime_nesting_limit (100)\fR"
+/* The maximal recursion level that the MIME processor will handle.
+/* EXTERNAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtp_send_xforward_command (no)\fR"
+/* Send the non-standard XFORWARD command when the Postfix SMTP server
+/* EHLO response announces XFORWARD support.
+/* SASL AUTHENTICATION CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBsmtp_sasl_auth_enable (no)\fR"
+/* Enable SASL authentication in the Postfix SMTP client.
+/* .IP "\fBsmtp_sasl_password_maps (empty)\fR"
+/* Optional Postfix SMTP client lookup tables with one username:password
+/* entry per sender, remote hostname or next-hop domain.
+/* .IP "\fBsmtp_sasl_security_options (noplaintext, noanonymous)\fR"
+/* Postfix SMTP client SASL security options; as of Postfix 2.3
+/* the list of available
+/* features depends on the SASL client implementation that is selected
+/* with \fBsmtp_sasl_type\fR.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtp_sasl_mechanism_filter (empty)\fR"
+/* If non-empty, a Postfix SMTP client filter for the remote SMTP
+/* server's list of offered SASL mechanisms.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtp_sender_dependent_authentication (no)\fR"
+/* Enable sender-dependent authentication in the Postfix SMTP client; this is
+/* available only with SASL authentication, and disables SMTP connection
+/* caching to ensure that mail from different senders will use the
+/* appropriate credentials.
+/* .IP "\fBsmtp_sasl_path (empty)\fR"
+/* Implementation-specific information that the Postfix SMTP client
+/* passes through to
+/* the SASL plug-in implementation that is selected with
+/* \fBsmtp_sasl_type\fR.
+/* .IP "\fBsmtp_sasl_type (cyrus)\fR"
+/* The SASL plug-in type that the Postfix SMTP client should use
+/* for authentication.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBsmtp_sasl_auth_cache_name (empty)\fR"
+/* An optional table to prevent repeated SASL authentication
+/* failures with the same remote SMTP server hostname, username and
+/* password.
+/* .IP "\fBsmtp_sasl_auth_cache_time (90d)\fR"
+/* The maximal age of an smtp_sasl_auth_cache_name entry before it
+/* is removed.
+/* .IP "\fBsmtp_sasl_auth_soft_bounce (yes)\fR"
+/* When a remote SMTP server rejects a SASL authentication request
+/* with a 535 reply code, defer mail delivery instead of returning
+/* mail as undeliverable.
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR"
+/* Whether or not to append the "AUTH=<>" option to the MAIL
+/* FROM command in SASL-authenticated SMTP sessions.
+/* STARTTLS SUPPORT CONTROLS
+/* .ad
+/* .fi
+/* Detailed information about STARTTLS configuration may be found
+/* in the TLS_README document.
+/* .IP "\fBsmtp_tls_security_level (empty)\fR"
+/* The default SMTP TLS security level for the Postfix SMTP client;
+/* when a non-empty value is specified, this overrides the obsolete
+/* parameters smtp_use_tls, smtp_enforce_tls, and smtp_tls_enforce_peername.
+/* .IP "\fBsmtp_sasl_tls_security_options ($smtp_sasl_security_options)\fR"
+/* The SASL authentication security options that the Postfix SMTP
+/* client uses for TLS encrypted SMTP sessions.
+/* .IP "\fBsmtp_starttls_timeout (300s)\fR"
+/* Time limit for Postfix SMTP client write and read operations
+/* during TLS startup and shutdown handshake procedures.
+/* .IP "\fBsmtp_tls_CAfile (empty)\fR"
+/* A file containing CA certificates of root CAs trusted to sign
+/* either remote SMTP server certificates or intermediate CA certificates.
+/* .IP "\fBsmtp_tls_CApath (empty)\fR"
+/* Directory with PEM format Certification Authority certificates
+/* that the Postfix SMTP client uses to verify a remote SMTP server
+/* certificate.
+/* .IP "\fBsmtp_tls_cert_file (empty)\fR"
+/* File with the Postfix SMTP client RSA certificate in PEM format.
+/* .IP "\fBsmtp_tls_mandatory_ciphers (medium)\fR"
+/* The minimum TLS cipher grade that the Postfix SMTP client will
+/* use with
+/* mandatory TLS encryption.
+/* .IP "\fBsmtp_tls_exclude_ciphers (empty)\fR"
+/* List of ciphers or cipher types to exclude from the Postfix
+/* SMTP client cipher
+/* list at all TLS security levels.
+/* .IP "\fBsmtp_tls_mandatory_exclude_ciphers (empty)\fR"
+/* Additional list of ciphers or cipher types to exclude from the
+/* Postfix SMTP client cipher list at mandatory TLS security levels.
+/* .IP "\fBsmtp_tls_dcert_file (empty)\fR"
+/* File with the Postfix SMTP client DSA certificate in PEM format.
+/* .IP "\fBsmtp_tls_dkey_file ($smtp_tls_dcert_file)\fR"
+/* File with the Postfix SMTP client DSA private key in PEM format.
+/* .IP "\fBsmtp_tls_key_file ($smtp_tls_cert_file)\fR"
+/* File with the Postfix SMTP client RSA private key in PEM format.
+/* .IP "\fBsmtp_tls_loglevel (0)\fR"
+/* Enable additional Postfix SMTP client logging of TLS activity.
+/* .IP "\fBsmtp_tls_note_starttls_offer (no)\fR"
+/* Log the hostname of a remote SMTP server that offers STARTTLS,
+/* when TLS is not already enabled for that server.
+/* .IP "\fBsmtp_tls_policy_maps (empty)\fR"
+/* Optional lookup tables with the Postfix SMTP client TLS security
+/* policy by next-hop destination; when a non-empty value is specified,
+/* this overrides the obsolete smtp_tls_per_site parameter.
+/* .IP "\fBsmtp_tls_mandatory_protocols (!SSLv2, !SSLv3)\fR"
+/* List of SSL/TLS protocols that the Postfix SMTP client will use with
+/* mandatory TLS encryption.
+/* .IP "\fBsmtp_tls_scert_verifydepth (9)\fR"
+/* The verification depth for remote SMTP server certificates.
+/* .IP "\fBsmtp_tls_secure_cert_match (nexthop, dot-nexthop)\fR"
+/* How the Postfix SMTP client verifies the server certificate
+/* peername for the "secure" TLS security level.
+/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR"
+/* Name of the file containing the optional Postfix SMTP client
+/* TLS session cache.
+/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR"
+/* The expiration time of Postfix SMTP client TLS session cache
+/* information.
+/* .IP "\fBsmtp_tls_verify_cert_match (hostname)\fR"
+/* How the Postfix SMTP client verifies the server certificate
+/* peername for the
+/* "verify" TLS security level.
+/* .IP "\fBtls_daemon_random_bytes (32)\fR"
+/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8)
+/* process requests from the \fBtlsmgr\fR(8) server in order to seed its
+/* internal pseudo random number generator (PRNG).
+/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "high" grade ciphers.
+/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "medium" or higher grade ciphers.
+/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "low" or higher grade ciphers.
+/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR"
+/* The OpenSSL cipherlist for "export" or higher grade ciphers.
+/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR"
+/* The OpenSSL cipherlist for "NULL" grade ciphers that provide
+/* authentication without encryption.
+/* .PP
+/* Available in Postfix version 2.4 and later:
+/* .IP "\fBsmtp_sasl_tls_verified_security_options ($smtp_sasl_tls_security_options)\fR"
+/* The SASL authentication security options that the Postfix SMTP
+/* client uses for TLS encrypted SMTP sessions with a verified server
+/* certificate.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBsmtp_tls_fingerprint_cert_match (empty)\fR"
+/* List of acceptable remote SMTP server certificate fingerprints for
+/* the "fingerprint" TLS security level (\fBsmtp_tls_security_level\fR =
+/* fingerprint).
+/* .IP "\fBsmtp_tls_fingerprint_digest (md5)\fR"
+/* The message digest algorithm used to construct remote SMTP server
+/* certificate fingerprints.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBsmtp_tls_protocols (!SSLv2, !SSLv3)\fR"
+/* List of TLS protocols that the Postfix SMTP client will exclude or
+/* include with opportunistic TLS encryption.
+/* .IP "\fBsmtp_tls_ciphers (medium)\fR"
+/* The minimum TLS cipher grade that the Postfix SMTP client
+/* will use with opportunistic TLS encryption.
+/* .IP "\fBsmtp_tls_eccert_file (empty)\fR"
+/* File with the Postfix SMTP client ECDSA certificate in PEM format.
+/* .IP "\fBsmtp_tls_eckey_file ($smtp_tls_eccert_file)\fR"
+/* File with the Postfix SMTP client ECDSA private key in PEM format.
+/* .PP
+/* Available in Postfix version 2.7 and later:
+/* .IP "\fBsmtp_tls_block_early_mail_reply (no)\fR"
+/* Try to detect a mail hijacking attack based on a TLS protocol
+/* vulnerability (CVE-2009-3555), where an attacker prepends malicious
+/* HELO, MAIL, RCPT, DATA commands to a Postfix SMTP client TLS session.
+/* .PP
+/* Available in Postfix version 2.8 and later:
+/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR"
+/* List or bit-mask of OpenSSL bug work-arounds to disable.
+/* .PP
+/* Available in Postfix version 2.11-3.1:
+/* .IP "\fBtls_dane_digest_agility (on)\fR"
+/* Configure RFC7671 DANE TLSA digest algorithm agility.
+/* .IP "\fBtls_dane_trust_anchor_digest_enable (yes)\fR"
+/* Enable support for RFC 6698 (DANE TLSA) DNS records that contain
+/* digests of trust-anchors with certificate usage "2".
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBsmtp_tls_trust_anchor_file (empty)\fR"
+/* Zero or more PEM-format files with trust-anchor certificates
+/* and/or public keys.
+/* .IP "\fBsmtp_tls_force_insecure_host_tlsa_lookup (no)\fR"
+/* Lookup the associated DANE TLSA RRset even when a hostname is
+/* not an alias and its address records lie in an unsigned zone.
+/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR"
+/* The name of the \fBtlsmgr\fR(8) service entry in master.cf.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBsmtp_tls_wrappermode (no)\fR"
+/* Request that the Postfix SMTP client connects using the
+/* legacy SMTPS protocol instead of using the STARTTLS command.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBsmtp_tls_dane_insecure_mx_policy (dane)\fR"
+/* The TLS policy for MX hosts with "secure" TLSA records when the
+/* nexthop destination security level is \fBdane\fR, but the MX
+/* record was found via an "insecure" MX lookup.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBsmtp_tls_connection_reuse (no)\fR"
+/* Try to make multiple deliveries per TLS-encrypted connection.
+/* .IP "\fBsmtp_tls_chain_files (empty)\fR"
+/* List of one or more PEM files, each holding one or more private keys
+/* directly followed by a corresponding certificate chain.
+/* .IP "\fBsmtp_tls_servername (empty)\fR"
+/* Optional name to send to the remote SMTP server in the TLS Server
+/* Name Indication (SNI) extension.
+/* .PP
+/* Introduced with Postfix 3.4.6, 3.3.5, 3.2.10, and 3.1.13:
+/* .IP "\fBtls_fast_shutdown_enable (yes)\fR"
+/* A workaround for implementations that hang Postfix while shuting
+/* down a TLS session, until Postfix times out.
+/* OBSOLETE STARTTLS CONTROLS
+/* .ad
+/* .fi
+/* The following configuration parameters exist for compatibility
+/* with Postfix versions before 2.3. Support for these will
+/* be removed in a future release.
+/* .IP "\fBsmtp_use_tls (no)\fR"
+/* Opportunistic mode: use TLS when a remote SMTP server announces
+/* STARTTLS support, otherwise send the mail in the clear.
+/* .IP "\fBsmtp_enforce_tls (no)\fR"
+/* Enforcement mode: require that remote SMTP servers use TLS
+/* encryption, and never send mail in the clear.
+/* .IP "\fBsmtp_tls_enforce_peername (yes)\fR"
+/* With mandatory TLS encryption, require that the remote SMTP
+/* server hostname matches the information in the remote SMTP server
+/* certificate.
+/* .IP "\fBsmtp_tls_per_site (empty)\fR"
+/* Optional lookup tables with the Postfix SMTP client TLS usage
+/* policy by next-hop destination and by remote SMTP server hostname.
+/* .IP "\fBsmtp_tls_cipherlist (empty)\fR"
+/* Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS
+/* cipher list.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBsmtp_connect_timeout (30s)\fR"
+/* The Postfix SMTP client time limit for completing a TCP connection, or
+/* zero (use the operating system built-in time limit).
+/* .IP "\fBsmtp_helo_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the HELO or EHLO command,
+/* and for receiving the initial remote SMTP server response.
+/* .IP "\fBlmtp_lhlo_timeout (300s)\fR"
+/* The Postfix LMTP client time limit for sending the LHLO command,
+/* and for receiving the initial remote LMTP server response.
+/* .IP "\fBsmtp_xforward_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the XFORWARD command,
+/* and for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_mail_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the MAIL FROM command,
+/* and for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_rcpt_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the SMTP RCPT TO
+/* command, and for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_data_init_timeout (120s)\fR"
+/* The Postfix SMTP client time limit for sending the SMTP DATA command,
+/* and for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_data_xfer_timeout (180s)\fR"
+/* The Postfix SMTP client time limit for sending the SMTP message content.
+/* .IP "\fBsmtp_data_done_timeout (600s)\fR"
+/* The Postfix SMTP client time limit for sending the SMTP ".", and
+/* for receiving the remote SMTP server response.
+/* .IP "\fBsmtp_quit_timeout (300s)\fR"
+/* The Postfix SMTP client time limit for sending the QUIT command,
+/* and for receiving the remote SMTP server response.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtp_mx_address_limit (5)\fR"
+/* The maximal number of MX (mail exchanger) IP addresses that can
+/* result from Postfix SMTP client mail exchanger lookups, or zero (no
+/* limit).
+/* .IP "\fBsmtp_mx_session_limit (2)\fR"
+/* The maximal number of SMTP sessions per delivery request before
+/* the Postfix SMTP client
+/* gives up or delivers to a fall-back relay host, or zero (no
+/* limit).
+/* .IP "\fBsmtp_rset_timeout (20s)\fR"
+/* The Postfix SMTP client time limit for sending the RSET command,
+/* and for receiving the remote SMTP server response.
+/* .PP
+/* Available in Postfix version 2.2 and earlier:
+/* .IP "\fBlmtp_cache_connection (yes)\fR"
+/* Keep Postfix LMTP client connections open for up to $max_idle
+/* seconds.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtp_connection_cache_destinations (empty)\fR"
+/* Permanently enable SMTP connection caching for the specified
+/* destinations.
+/* .IP "\fBsmtp_connection_cache_on_demand (yes)\fR"
+/* Temporarily enable SMTP connection caching while a destination
+/* has a high volume of mail in the active queue.
+/* .IP "\fBsmtp_connection_reuse_time_limit (300s)\fR"
+/* The amount of time during which Postfix will use an SMTP
+/* connection repeatedly.
+/* .IP "\fBsmtp_connection_cache_time_limit (2s)\fR"
+/* When SMTP connection caching is enabled, the amount of time that
+/* an unused SMTP client socket is kept open before it is closed.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBconnection_cache_protocol_timeout (5s)\fR"
+/* Time limit for connection cache connect, send or receive
+/* operations.
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBsmtp_per_record_deadline (no)\fR"
+/* Change the behavior of the smtp_*_timeout time limits, from a
+/* time limit per read or write system call, to a time limit to send
+/* or receive a complete record (an SMTP command line, SMTP response
+/* line, SMTP message content line, or TLS protocol message).
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBsmtp_connection_reuse_count_limit (0)\fR"
+/* When SMTP connection caching is enabled, the number of times
+/* that an SMTP session may be reused before it is closed, or zero (no
+/* limit).
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBsmtp_tls_connection_reuse (no)\fR"
+/* Try to make multiple deliveries per TLS-encrypted connection.
+/* .PP
+/* Implemented in the qmgr(8) daemon:
+/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_concurrency_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR"
+/* A transport-specific override for the
+/* default_destination_recipient_limit parameter value, where
+/* \fItransport\fR is the master.cf name of the message delivery
+/* transport.
+/* SMTPUTF8 CONTROLS
+/* .ad
+/* .fi
+/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+/* .IP "\fBsmtputf8_enable (yes)\fR"
+/* Enable preliminary SMTPUTF8 support for the protocols described
+/* in RFC 6531..6533.
+/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR"
+/* Detect that a message requires SMTPUTF8 support for the specified
+/* mail origin classes.
+/* .PP
+/* Available in Postfix version 3.2 and later:
+/* .IP "\fBenable_idna2003_compatibility (no)\fR"
+/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008,
+/* when converting UTF-8 domain names to/from the ASCII form that is
+/* used for DNS lookups.
+/* TROUBLE SHOOTING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdebug_peer_level (2)\fR"
+/* The increment in verbose logging level when a remote client or
+/* server matches a pattern in the debug_peer_list parameter.
+/* .IP "\fBdebug_peer_list (empty)\fR"
+/* Optional list of remote client or server hostname or network
+/* address patterns that cause the verbose logging level to increase
+/* by the amount specified in $debug_peer_level.
+/* .IP "\fBerror_notice_recipient (postmaster)\fR"
+/* The recipient of postmaster notifications about mail delivery
+/* problems that are caused by policy, resource, software or protocol
+/* errors.
+/* .IP "\fBinternal_mail_filter_classes (empty)\fR"
+/* What categories of Postfix-generated mail are subject to
+/* before-queue content inspection by non_smtpd_milters, header_checks
+/* and body_checks.
+/* .IP "\fBnotify_classes (resource, software)\fR"
+/* The list of error classes that are reported to the postmaster.
+/* MISCELLANEOUS CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBbest_mx_transport (empty)\fR"
+/* Where the Postfix SMTP client should deliver mail when it detects
+/* a "mail loops back to myself" error condition.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdaemon_timeout (18000s)\fR"
+/* How much time a Postfix daemon process may take to handle a
+/* request before it is terminated by a built-in watchdog timer.
+/* .IP "\fBdelay_logging_resolution_limit (2)\fR"
+/* The maximal number of digits after the decimal point when logging
+/* sub-second delay values.
+/* .IP "\fBdisable_dns_lookups (no)\fR"
+/* Disable DNS lookups in the Postfix SMTP and LMTP clients.
+/* .IP "\fBinet_interfaces (all)\fR"
+/* The network interface addresses that this mail system receives
+/* mail on.
+/* .IP "\fBinet_protocols (all)\fR"
+/* The Internet protocols Postfix will attempt to use when making
+/* or accepting connections.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBlmtp_assume_final (no)\fR"
+/* When a remote LMTP server announces no DSN support, assume that
+/* the
+/* server performs final delivery, and send "delivered" delivery status
+/* notifications instead of "relayed".
+/* .IP "\fBlmtp_tcp_port (24)\fR"
+/* The default TCP port that the Postfix LMTP client connects to.
+/* .IP "\fBmax_idle (100s)\fR"
+/* The maximum amount of time that an idle Postfix daemon process waits
+/* for an incoming connection before terminating voluntarily.
+/* .IP "\fBmax_use (100)\fR"
+/* The maximal number of incoming connections that a Postfix daemon
+/* process will service before terminating voluntarily.
+/* .IP "\fBprocess_id (read-only)\fR"
+/* The process ID of a Postfix command or daemon process.
+/* .IP "\fBprocess_name (read-only)\fR"
+/* The process name of a Postfix command or daemon process.
+/* .IP "\fBproxy_interfaces (empty)\fR"
+/* The network interface addresses that this mail system receives mail
+/* on by way of a proxy or network address translation unit.
+/* .IP "\fBsmtp_address_preference (any)\fR"
+/* The address type ("ipv6", "ipv4" or "any") that the Postfix
+/* SMTP client will try first, when a destination has IPv6 and IPv4
+/* addresses with equal MX preference.
+/* .IP "\fBsmtp_bind_address (empty)\fR"
+/* An optional numerical network address that the Postfix SMTP client
+/* should bind to when making an IPv4 connection.
+/* .IP "\fBsmtp_bind_address6 (empty)\fR"
+/* An optional numerical network address that the Postfix SMTP client
+/* should bind to when making an IPv6 connection.
+/* .IP "\fBsmtp_helo_name ($myhostname)\fR"
+/* The hostname to send in the SMTP HELO or EHLO command.
+/* .IP "\fBlmtp_lhlo_name ($myhostname)\fR"
+/* The hostname to send in the LMTP LHLO command.
+/* .IP "\fBsmtp_host_lookup (dns)\fR"
+/* What mechanisms the Postfix SMTP client uses to look up a host's
+/* IP address.
+/* .IP "\fBsmtp_randomize_addresses (yes)\fR"
+/* Randomize the order of equal-preference MX host addresses.
+/* .IP "\fBsyslog_facility (mail)\fR"
+/* The syslog facility of Postfix logging.
+/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
+/* A prefix that is prepended to the process name in syslog
+/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available with Postfix 2.2 and earlier:
+/* .IP "\fBfallback_relay (empty)\fR"
+/* Optional list of relay hosts for SMTP destinations that can't be
+/* found or that are unreachable.
+/* .PP
+/* Available with Postfix 2.3 and later:
+/* .IP "\fBsmtp_fallback_relay ($fallback_relay)\fR"
+/* Optional list of relay hosts for SMTP destinations that can't be
+/* found or that are unreachable.
+/* .PP
+/* Available with Postfix 3.0 and later:
+/* .IP "\fBsmtp_address_verify_target (rcpt)\fR"
+/* In the context of email address verification, the SMTP protocol
+/* stage that determines whether an email address is deliverable.
+/* .PP
+/* Available with Postfix 3.1 and later:
+/* .IP "\fBlmtp_fallback_relay (empty)\fR"
+/* Optional list of relay hosts for LMTP destinations that can't be
+/* found or that are unreachable.
+/* .PP
+/* Available with Postfix 3.2 and later:
+/* .IP "\fBsmtp_tcp_port (smtp)\fR"
+/* The default TCP port that the Postfix SMTP client connects to.
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* generic(5), output address rewriting
+/* header_checks(5), message header content inspection
+/* body_checks(5), body parts content inspection
+/* qmgr(8), queue manager
+/* bounce(8), delivery status reports
+/* scache(8), connection cache server
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* tlsmgr(8), TLS session and PRNG management
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* SASL_README, Postfix SASL howto
+/* TLS_README, Postfix STARTTLS howto
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Command pipelining in cooperation with:
+/* Jon Ribbens
+/* Oaktree Internet Solutions Ltd.,
+/* Internet House,
+/* Canal Basin,
+/* Coventry,
+/* CV1 4LY, United Kingdom.
+/*
+/* SASL support originally by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Revised TLS and SMTP connection cache support by:
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <dict.h>
+#include <stringops.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <name_mask.h>
+#include <name_code.h>
+
+/* Global library. */
+
+#include <deliver_request.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <debug_peer.h>
+#include <flush_clnt.h>
+#include <scache.h>
+#include <string_list.h>
+#include <maps.h>
+#include <ext_prop.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+ /*
+ * Tunable parameters. These have compiled-in defaults that can be overruled
+ * by settings in the global Postfix configuration file.
+ */
+int var_smtp_conn_tmout;
+int var_smtp_helo_tmout;
+int var_smtp_xfwd_tmout;
+int var_smtp_mail_tmout;
+int var_smtp_rcpt_tmout;
+int var_smtp_data0_tmout;
+int var_smtp_data1_tmout;
+int var_smtp_data2_tmout;
+int var_smtp_rset_tmout;
+int var_smtp_quit_tmout;
+char *var_inet_interfaces;
+char *var_notify_classes;
+int var_smtp_skip_5xx_greeting;
+int var_ign_mx_lookup_err;
+int var_skip_quit_resp;
+char *var_fallback_relay;
+char *var_bestmx_transp;
+char *var_error_rcpt;
+int var_smtp_always_ehlo;
+int var_smtp_never_ehlo;
+char *var_smtp_sasl_opts;
+char *var_smtp_sasl_path;
+char *var_smtp_sasl_passwd;
+bool var_smtp_sasl_enable;
+char *var_smtp_sasl_mechs;
+char *var_smtp_sasl_type;
+char *var_smtp_bind_addr;
+char *var_smtp_bind_addr6;
+char *var_smtp_vrfy_tgt;
+bool var_smtp_rand_addr;
+int var_smtp_pix_thresh;
+int var_queue_run_delay;
+int var_min_backoff_time;
+int var_smtp_pix_delay;
+int var_smtp_line_limit;
+char *var_smtp_helo_name;
+char *var_smtp_host_lookup;
+bool var_smtp_quote_821_env;
+bool var_smtp_defer_mxaddr;
+bool var_smtp_send_xforward;
+int var_smtp_mxaddr_limit;
+int var_smtp_mxsess_limit;
+int var_smtp_cache_conn;
+int var_smtp_reuse_time;
+int var_smtp_reuse_count;
+char *var_smtp_cache_dest;
+char *var_scache_service; /* You can now leave this here. */
+bool var_smtp_cache_demand;
+char *var_smtp_ehlo_dis_words;
+char *var_smtp_ehlo_dis_maps;
+char *var_smtp_addr_pref;
+
+char *var_smtp_tls_level;
+bool var_smtp_use_tls;
+bool var_smtp_enforce_tls;
+char *var_smtp_tls_per_site;
+char *var_smtp_tls_policy;
+bool var_smtp_tls_wrappermode;
+bool var_smtp_tls_conn_reuse;
+char *var_tlsproxy_service;
+
+#ifdef USE_TLS
+char *var_smtp_sasl_tls_opts;
+char *var_smtp_sasl_tlsv_opts;
+int var_smtp_starttls_tmout;
+char *var_smtp_tls_CAfile;
+char *var_smtp_tls_CApath;
+char *var_smtp_tls_chain_files;
+char *var_smtp_tls_cert_file;
+char *var_smtp_tls_mand_ciph;
+char *var_smtp_tls_excl_ciph;
+char *var_smtp_tls_mand_excl;
+char *var_smtp_tls_dcert_file;
+char *var_smtp_tls_dkey_file;
+bool var_smtp_tls_enforce_peername;
+char *var_smtp_tls_key_file;
+char *var_smtp_tls_loglevel;
+bool var_smtp_tls_note_starttls_offer;
+char *var_smtp_tls_mand_proto;
+char *var_smtp_tls_sec_cmatch;
+int var_smtp_tls_scert_vd;
+char *var_smtp_tls_vfy_cmatch;
+char *var_smtp_tls_fpt_cmatch;
+char *var_smtp_tls_fpt_dgst;
+char *var_smtp_tls_tafile;
+char *var_smtp_tls_proto;
+char *var_smtp_tls_ciph;
+char *var_smtp_tls_eccert_file;
+char *var_smtp_tls_eckey_file;
+char *var_smtp_tls_sni;
+bool var_smtp_tls_blk_early_mail_reply;
+bool var_smtp_tls_force_tlsa;
+char *var_smtp_tls_insecure_mx_policy;
+
+#endif
+
+char *var_smtp_generic_maps;
+char *var_prop_extension;
+bool var_smtp_sender_auth;
+char *var_smtp_tcp_port;
+int var_scache_proto_tmout;
+bool var_smtp_cname_overr;
+char *var_smtp_pix_bug_words;
+char *var_smtp_pix_bug_maps;
+char *var_cyrus_conf_path;
+char *var_smtp_head_chks;
+char *var_smtp_mime_chks;
+char *var_smtp_nest_chks;
+char *var_smtp_body_chks;
+char *var_smtp_resp_filter;
+bool var_lmtp_assume_final;
+char *var_smtp_dns_res_opt;
+char *var_smtp_dns_support;
+bool var_smtp_rec_deadline;
+bool var_smtp_dummy_mail_auth;
+char *var_smtp_dsn_filter;
+char *var_smtp_dns_re_filter;
+bool var_smtp_balance_inet_proto;
+
+ /* Special handling of 535 AUTH errors. */
+char *var_smtp_sasl_auth_cache_name;
+int var_smtp_sasl_auth_cache_time;
+bool var_smtp_sasl_auth_soft_bounce;
+
+ /*
+ * Global variables.
+ */
+int smtp_mode;
+int smtp_host_lookup_mask;
+int smtp_dns_support;
+STRING_LIST *smtp_cache_dest;
+SCACHE *smtp_scache;
+MAPS *smtp_ehlo_dis_maps;
+MAPS *smtp_generic_maps;
+int smtp_ext_prop_mask;
+unsigned smtp_dns_res_opt;
+MAPS *smtp_pix_bug_maps;
+HBC_CHECKS *smtp_header_checks; /* limited header checks */
+HBC_CHECKS *smtp_body_checks; /* limited body checks */
+
+#ifdef USE_TLS
+
+ /*
+ * OpenSSL client state (opaque handle)
+ */
+TLS_APPL_STATE *smtp_tls_ctx;
+int smtp_tls_insecure_mx_policy;
+
+#endif
+
+ /*
+ * IPv6 preference.
+ */
+static int smtp_addr_pref;
+
+/* deliver_message - deliver message with extreme prejudice */
+
+static int deliver_message(const char *service, DELIVER_REQUEST *request)
+{
+ SMTP_STATE *state;
+ int result;
+
+ if (msg_verbose)
+ msg_info("deliver_message: from %s", request->sender);
+
+ /*
+ * Sanity checks. The smtp server is unprivileged and chrooted, so we can
+ * afford to distribute the data censoring code, instead of having it all
+ * in one place.
+ */
+ if (request->nexthop[0] == 0)
+ msg_fatal("empty nexthop hostname");
+ if (request->rcpt_list.len <= 0)
+ msg_fatal("recipient count: %d", request->rcpt_list.len);
+
+ /*
+ * Initialize. Bundle all information about the delivery request, so that
+ * we can produce understandable diagnostics when something goes wrong
+ * many levels below. The alternative would be to make everything global.
+ */
+ state = smtp_state_alloc();
+ state->request = request;
+ state->src = request->fp;
+ state->service = service;
+ state->misc_flags |= smtp_addr_pref;
+ SMTP_RCPT_INIT(state);
+
+ /*
+ * Establish an SMTP session and deliver this message to all requested
+ * recipients. At the end, notify the postmaster of any protocol errors.
+ * Optionally deliver mail locally when this machine is the best mail
+ * exchanger.
+ */
+ result = smtp_connect(state);
+
+ /*
+ * Clean up.
+ */
+ smtp_state_free(state);
+
+ return (result);
+}
+
+/* smtp_service - perform service for client */
+
+static void smtp_service(VSTREAM *client_stream, char *service, char **argv)
+{
+ DELIVER_REQUEST *request;
+ int status;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to remote SMTP delivery service. What we see below is a
+ * little protocol to (1) tell the queue manager that we are ready, (2)
+ * read a request from the queue manager, and (3) report the completion
+ * status of that request. All connection-management stuff is handled by
+ * the common code in single_server.c.
+ */
+ if ((request = deliver_request_read(client_stream)) != 0) {
+ status = deliver_message(service, request);
+ deliver_request_done(client_stream, request, status);
+ }
+}
+
+/* post_init - post-jail initialization */
+
+static void post_init(char *unused_name, char **unused_argv)
+{
+ static const NAME_MASK lookup_masks[] = {
+ SMTP_HOST_LOOKUP_DNS, SMTP_HOST_FLAG_DNS,
+ SMTP_HOST_LOOKUP_NATIVE, SMTP_HOST_FLAG_NATIVE,
+ 0,
+ };
+ static const NAME_MASK dns_res_opt_masks[] = {
+ SMTP_DNS_RES_OPT_DEFNAMES, RES_DEFNAMES,
+ SMTP_DNS_RES_OPT_DNSRCH, RES_DNSRCH,
+ 0,
+ };
+ static const NAME_CODE dns_support[] = {
+ SMTP_DNS_SUPPORT_DISABLED, SMTP_DNS_DISABLED,
+ SMTP_DNS_SUPPORT_ENABLED, SMTP_DNS_ENABLED,
+#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0)
+ SMTP_DNS_SUPPORT_DNSSEC, SMTP_DNS_DNSSEC,
+#endif
+ 0, SMTP_DNS_INVALID,
+ };
+
+ if (*var_smtp_dns_support == 0) {
+ /* Backwards compatible empty setting */
+ smtp_dns_support =
+ var_disable_dns ? SMTP_DNS_DISABLED : SMTP_DNS_ENABLED;
+ } else {
+ smtp_dns_support =
+ name_code(dns_support, NAME_CODE_FLAG_NONE, var_smtp_dns_support);
+ if (smtp_dns_support == SMTP_DNS_INVALID)
+ msg_fatal("invalid %s: \"%s\"", VAR_LMTP_SMTP(DNS_SUPPORT),
+ var_smtp_dns_support);
+ var_disable_dns = (smtp_dns_support == SMTP_DNS_DISABLED);
+ }
+
+#ifdef USE_TLS
+ if (smtp_mode) {
+ smtp_tls_insecure_mx_policy =
+ tls_level_lookup(var_smtp_tls_insecure_mx_policy);
+ switch (smtp_tls_insecure_mx_policy) {
+ case TLS_LEV_MAY:
+ case TLS_LEV_ENCRYPT:
+ case TLS_LEV_DANE:
+ break;
+ default:
+ msg_fatal("invalid %s: \"%s\"", VAR_SMTP_TLS_INSECURE_MX_POLICY,
+ var_smtp_tls_insecure_mx_policy);
+ }
+ }
+#endif
+
+ /*
+ * Select hostname lookup mechanisms.
+ */
+ if (smtp_dns_support == SMTP_DNS_DISABLED)
+ smtp_host_lookup_mask = SMTP_HOST_FLAG_NATIVE;
+ else
+ smtp_host_lookup_mask =
+ name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks,
+ var_smtp_host_lookup);
+ if (msg_verbose)
+ msg_info("host name lookup methods: %s",
+ str_name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks,
+ smtp_host_lookup_mask));
+
+ /*
+ * Session cache instance.
+ */
+ if (*var_smtp_cache_dest || var_smtp_cache_demand)
+#if 0
+ smtp_scache = scache_multi_create();
+#else
+ smtp_scache = scache_clnt_create(var_scache_service,
+ var_scache_proto_tmout,
+ var_ipc_idle_limit,
+ var_ipc_ttl_limit);
+#endif
+
+ /*
+ * Select DNS query flags.
+ */
+ smtp_dns_res_opt = name_mask(VAR_LMTP_SMTP(DNS_RES_OPT), dns_res_opt_masks,
+ var_smtp_dns_res_opt);
+
+ /*
+ * Address verification.
+ */
+ smtp_vrfy_init();
+}
+
+/* pre_init - pre-jail initialization */
+
+static void pre_init(char *unused_name, char **unused_argv)
+{
+ int use_tls;
+ static const NAME_CODE addr_pref_map[] = {
+ INET_PROTO_NAME_IPV6, SMTP_MISC_FLAG_PREF_IPV6,
+ INET_PROTO_NAME_IPV4, SMTP_MISC_FLAG_PREF_IPV4,
+ INET_PROTO_NAME_ANY, 0,
+ 0, -1,
+ };
+
+ /*
+ * Turn on per-peer debugging.
+ */
+ debug_peer_init();
+
+ /*
+ * SASL initialization.
+ */
+ if (var_smtp_sasl_enable)
+#ifdef USE_SASL_AUTH
+ smtp_sasl_initialize();
+#else
+ msg_warn("%s is true, but SASL support is not compiled in",
+ VAR_LMTP_SMTP(SASL_ENABLE));
+#endif
+
+ if (*var_smtp_tls_level != 0)
+ switch (tls_level_lookup(var_smtp_tls_level)) {
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_DANE_ONLY:
+ case TLS_LEV_FPRINT:
+ case TLS_LEV_ENCRYPT:
+ var_smtp_use_tls = var_smtp_enforce_tls = 1;
+ break;
+ case TLS_LEV_DANE:
+ case TLS_LEV_MAY:
+ var_smtp_use_tls = 1;
+ var_smtp_enforce_tls = 0;
+ break;
+ case TLS_LEV_NONE:
+ var_smtp_use_tls = var_smtp_enforce_tls = 0;
+ break;
+ default:
+ /* tls_level_lookup() logs no warning. */
+ /* session_tls_init() assumes that var_smtp_tls_level is sane. */
+ msg_fatal("Invalid TLS level \"%s\"", var_smtp_tls_level);
+ }
+ use_tls = (var_smtp_use_tls || var_smtp_enforce_tls);
+
+ /*
+ * Initialize the TLS data before entering the chroot jail
+ */
+ if (use_tls || var_smtp_tls_per_site[0] || var_smtp_tls_policy[0]) {
+#ifdef USE_TLS
+ TLS_CLIENT_INIT_PROPS props;
+
+ tls_pre_jail_init(TLS_ROLE_CLIENT);
+
+ /*
+ * We get stronger type safety and a cleaner interface by combining
+ * the various parameters into a single tls_client_props structure.
+ *
+ * Large parameter lists are error-prone, so we emulate a language
+ * feature that C does not have natively: named parameter lists.
+ *
+ * With tlsproxy(8) turned on, this is still needed for DANE-related
+ * initializations.
+ */
+ smtp_tls_ctx =
+ TLS_CLIENT_INIT(&props,
+ log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL),
+ log_level = var_smtp_tls_loglevel,
+ verifydepth = var_smtp_tls_scert_vd,
+ cache_type = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE),
+ chain_files = var_smtp_tls_chain_files,
+ cert_file = var_smtp_tls_cert_file,
+ key_file = var_smtp_tls_key_file,
+ dcert_file = var_smtp_tls_dcert_file,
+ dkey_file = var_smtp_tls_dkey_file,
+ eccert_file = var_smtp_tls_eccert_file,
+ eckey_file = var_smtp_tls_eckey_file,
+ CAfile = var_smtp_tls_CAfile,
+ CApath = var_smtp_tls_CApath,
+ mdalg = var_smtp_tls_fpt_dgst);
+ smtp_tls_list_init();
+#else
+ msg_warn("TLS has been selected, but TLS support is not compiled in");
+#endif
+ }
+
+ /*
+ * Flush client.
+ */
+ flush_init();
+
+ /*
+ * Session cache domain list.
+ */
+ if (*var_smtp_cache_dest)
+ smtp_cache_dest = string_list_init(VAR_SMTP_CACHE_DEST,
+ MATCH_FLAG_RETURN,
+ var_smtp_cache_dest);
+
+ /*
+ * EHLO keyword filter.
+ */
+ if (*var_smtp_ehlo_dis_maps)
+ smtp_ehlo_dis_maps = maps_create(VAR_LMTP_SMTP(EHLO_DIS_MAPS),
+ var_smtp_ehlo_dis_maps,
+ DICT_FLAG_LOCK);
+
+ /*
+ * PIX bug workarounds.
+ */
+ if (*var_smtp_pix_bug_maps)
+ smtp_pix_bug_maps = maps_create(VAR_LMTP_SMTP(PIX_BUG_MAPS),
+ var_smtp_pix_bug_maps,
+ DICT_FLAG_LOCK);
+
+ /*
+ * Generic maps.
+ */
+ if (*var_prop_extension)
+ smtp_ext_prop_mask =
+ ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension);
+ if (*var_smtp_generic_maps)
+ smtp_generic_maps =
+ maps_create(VAR_LMTP_SMTP(GENERIC_MAPS), var_smtp_generic_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+
+ /*
+ * Header/body checks.
+ */
+ smtp_header_checks = hbc_header_checks_create(
+ VAR_LMTP_SMTP(HEAD_CHKS), var_smtp_head_chks,
+ VAR_LMTP_SMTP(MIME_CHKS), var_smtp_mime_chks,
+ VAR_LMTP_SMTP(NEST_CHKS), var_smtp_nest_chks,
+ smtp_hbc_callbacks);
+ smtp_body_checks = hbc_body_checks_create(
+ VAR_LMTP_SMTP(BODY_CHKS), var_smtp_body_chks,
+ smtp_hbc_callbacks);
+
+ /*
+ * Server reply filter.
+ */
+ if (*var_smtp_resp_filter)
+ smtp_chat_resp_filter =
+ dict_open(var_smtp_resp_filter, O_RDONLY,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+
+ /*
+ * Address family preference.
+ */
+ if (*var_smtp_addr_pref) {
+ smtp_addr_pref = name_code(addr_pref_map, NAME_CODE_FLAG_NONE,
+ var_smtp_addr_pref);
+ if (smtp_addr_pref < 0)
+ msg_fatal("bad %s value: %s", VAR_LMTP_SMTP(ADDR_PREF),
+ var_smtp_addr_pref);
+ }
+
+ /*
+ * DNS reply filter.
+ */
+ if (*var_smtp_dns_re_filter)
+ dns_rr_filter_compile(VAR_LMTP_SMTP(DNS_RE_FILTER),
+ var_smtp_dns_re_filter);
+}
+
+/* pre_accept - see if tables have changed */
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ const char *table;
+
+ if ((table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ exit(0);
+ }
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ char *sane_procname;
+
+#include "smtp_params.c"
+#include "lmtp_params.c"
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * XXX At this point, var_procname etc. are not initialized.
+ *
+ * The process name, "smtp" or "lmtp", determines the protocol, the DSN
+ * server reply type, SASL service information lookup, and more. Prepare
+ * for the possibility there may be another personality.
+ */
+ sane_procname = sane_basename((VSTRING *) 0, argv[0]);
+ if (strcmp(sane_procname, "smtp") == 0)
+ smtp_mode = 1;
+ else if (strcmp(sane_procname, "lmtp") == 0)
+ smtp_mode = 0;
+ else
+ msg_fatal("unexpected process name \"%s\" - "
+ "specify \"smtp\" or \"lmtp\"", var_procname);
+
+ /*
+ * Initialize with the LMTP or SMTP parameter name space.
+ */
+ single_server_main(argc, argv, smtp_service,
+ CA_MAIL_SERVER_TIME_TABLE(smtp_mode ?
+ smtp_time_table : lmtp_time_table),
+ CA_MAIL_SERVER_INT_TABLE(smtp_mode ?
+ smtp_int_table : lmtp_int_table),
+ CA_MAIL_SERVER_STR_TABLE(smtp_mode ?
+ smtp_str_table : lmtp_str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(smtp_mode ?
+ smtp_bool_table : lmtp_bool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_init),
+ CA_MAIL_SERVER_POST_INIT(post_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_BOUNCE_INIT(VAR_SMTP_DSN_FILTER,
+ &var_smtp_dsn_filter),
+ 0);
+}
diff --git a/src/smtp/smtp.h b/src/smtp/smtp.h
new file mode 100644
index 0000000..ed3e6a5
--- /dev/null
+++ b/src/smtp/smtp.h
@@ -0,0 +1,727 @@
+/*++
+/* NAME
+/* smtp 3h
+/* SUMMARY
+/* smtp client program
+/* SYNOPSIS
+/* #include "smtp.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <argv.h>
+#include <htable.h>
+#include <dict.h>
+
+ /*
+ * Global library.
+ */
+#include <deliver_request.h>
+#include <scache.h>
+#include <string_list.h>
+#include <maps.h>
+#include <tok822.h>
+#include <dsn_buf.h>
+#include <header_body_checks.h>
+
+ /*
+ * Postfix TLS library.
+ */
+#include <tls.h>
+
+ /*
+ * tlsproxy client.
+ */
+#include <tls_proxy.h>
+
+ /*
+ * Global iterator support. This is updated by the connection-management
+ * loop, and contains dynamic context that appears in lookup keys for SASL
+ * passwords, TLS policy, cached SMTP connections, and cached TLS session
+ * keys.
+ *
+ * For consistency and maintainability, context that is used in more than one
+ * lookup key is formatted with smtp_key_format().
+ */
+typedef struct SMTP_ITERATOR {
+ /* Public members. */
+ VSTRING *request_nexthop; /* delivery request nexhop or empty */
+ VSTRING *dest; /* current nexthop */
+ VSTRING *host; /* hostname or empty */
+ VSTRING *addr; /* printable address or empty */
+ unsigned port; /* network byte order or null */
+ struct DNS_RR *rr; /* DNS resource record or null */
+ struct DNS_RR *mx; /* DNS resource record or null */
+ /* Private members. */
+ VSTRING *saved_dest; /* saved current nexthop */
+ struct SMTP_STATE *parent; /* parent linkage */
+} SMTP_ITERATOR;
+
+#define SMTP_ITER_INIT(iter, _dest, _host, _addr, _port, state) do { \
+ vstring_strcpy((iter)->dest, (_dest)); \
+ vstring_strcpy((iter)->host, (_host)); \
+ vstring_strcpy((iter)->addr, (_addr)); \
+ (iter)->port = (_port); \
+ (iter)->mx = (iter)->rr = 0; \
+ vstring_strcpy((iter)->saved_dest, ""); \
+ (iter)->parent = (state); \
+ } while (0)
+
+#define SMTP_ITER_SAVE_DEST(iter) do { \
+ vstring_strcpy((iter)->saved_dest, STR((iter)->dest)); \
+ } while (0)
+
+#define SMTP_ITER_RESTORE_DEST(iter) do { \
+ vstring_strcpy((iter)->dest, STR((iter)->saved_dest)); \
+ } while (0)
+
+ /*
+ * TLS Policy support.
+ */
+#ifdef USE_TLS
+
+typedef struct SMTP_TLS_POLICY {
+ int level; /* TLS enforcement level */
+ char *protocols; /* Acceptable SSL protocols */
+ char *grade; /* Cipher grade: "export", ... */
+ VSTRING *exclusions; /* Excluded SSL ciphers */
+ ARGV *matchargv; /* Cert match patterns */
+ DSN_BUF *why; /* Lookup error status */
+ TLS_DANE *dane; /* DANE TLSA digests */
+ char *sni; /* Optional SNI name when not DANE */
+ int conn_reuse; /* enable connection reuse */
+} SMTP_TLS_POLICY;
+
+ /*
+ * smtp_tls_policy.c
+ */
+extern void smtp_tls_list_init(void);
+extern int smtp_tls_policy_cache_query(DSN_BUF *, SMTP_TLS_POLICY *, SMTP_ITERATOR *);
+extern void smtp_tls_policy_cache_flush(void);
+
+ /*
+ * Macros must use distinct names for local temporary variables, otherwise
+ * there will be bugs due to shadowing. This happened when an earlier
+ * version of smtp_tls_policy_dummy() invoked smtp_tls_policy_init(), but it
+ * could also happen without macro nesting.
+ *
+ * General principle: use all or part of the macro name in each temporary
+ * variable name. Then, append suffixes to the names if needed.
+ */
+#define smtp_tls_policy_dummy(t) do { \
+ SMTP_TLS_POLICY *_tls_policy_dummy_tmp = (t); \
+ smtp_tls_policy_init(_tls_policy_dummy_tmp, (DSN_BUF *) 0); \
+ _tls_policy_dummy_tmp->level = TLS_LEV_NONE; \
+ } while (0)
+
+ /* This macro is not part of the module external interface. */
+#define smtp_tls_policy_init(t, w) do { \
+ SMTP_TLS_POLICY *_tls_policy_init_tmp = (t); \
+ _tls_policy_init_tmp->protocols = 0; \
+ _tls_policy_init_tmp->grade = 0; \
+ _tls_policy_init_tmp->exclusions = 0; \
+ _tls_policy_init_tmp->matchargv = 0; \
+ _tls_policy_init_tmp->why = (w); \
+ _tls_policy_init_tmp->dane = 0; \
+ _tls_policy_init_tmp->sni = 0; \
+ _tls_policy_init_tmp->conn_reuse = 0; \
+ } while (0)
+
+#endif
+
+ /*
+ * State information associated with each SMTP delivery request.
+ * Session-specific state is stored separately.
+ */
+typedef struct SMTP_STATE {
+ int misc_flags; /* processing flags, see below */
+ VSTREAM *src; /* queue file stream */
+ const char *service; /* transport name */
+ DELIVER_REQUEST *request; /* envelope info, offsets */
+ struct SMTP_SESSION *session; /* network connection */
+ int status; /* delivery status */
+ ssize_t space_left; /* output length control */
+
+ /*
+ * Global iterator.
+ */
+ SMTP_ITERATOR iterator[1]; /* Usage: state->iterator->member */
+
+ /*
+ * Global iterator.
+ */
+#ifdef USE_TLS
+ SMTP_TLS_POLICY tls[1]; /* Usage: state->tls->member */
+#endif
+
+ /*
+ * Connection cache support.
+ */
+ HTABLE *cache_used; /* cached addresses that were used */
+ VSTRING *dest_label; /* cached logical/physical binding */
+ VSTRING *dest_prop; /* binding properties, passivated */
+ VSTRING *endp_label; /* cached session physical endpoint */
+ VSTRING *endp_prop; /* endpoint properties, passivated */
+
+ /*
+ * Flags and counters to control the handling of mail delivery errors.
+ * There is some redundancy for sanity checking. At the end of an SMTP
+ * session all recipients should be marked one way or the other.
+ */
+ int rcpt_left; /* recipients left over */
+ int rcpt_drop; /* recipients marked as drop */
+ int rcpt_keep; /* recipients marked as keep */
+
+ /*
+ * DSN Support introduced major bloat in error processing.
+ */
+ DSN_BUF *why; /* on-the-fly formatting buffer */
+} SMTP_STATE;
+
+ /*
+ * Primitives to enable/disable/test connection caching and reuse based on
+ * the delivery request next-hop destination (i.e. not smtp_fallback_relay).
+ *
+ * Connection cache lookup by the delivery request next-hop destination allows
+ * a reuse request to skip over bad hosts, and may result in a connection to
+ * a fall-back relay. Once we have found a 'good' host for a delivery
+ * request next-hop, clear the delivery request next-hop destination, to
+ * avoid caching less-preferred connections under that same delivery request
+ * next-hop.
+ */
+#define SET_SCACHE_REQUEST_NEXTHOP(state, nexthop) do { \
+ vstring_strcpy((state)->iterator->request_nexthop, nexthop); \
+ } while (0)
+
+#define CLEAR_SCACHE_REQUEST_NEXTHOP(state) do { \
+ STR((state)->iterator->request_nexthop)[0] = 0; \
+ } while (0)
+
+#define HAVE_SCACHE_REQUEST_NEXTHOP(state) \
+ (STR((state)->iterator->request_nexthop)[0] != 0)
+
+
+ /*
+ * Server features.
+ */
+#define SMTP_FEATURE_ESMTP (1<<0)
+#define SMTP_FEATURE_8BITMIME (1<<1)
+#define SMTP_FEATURE_PIPELINING (1<<2)
+#define SMTP_FEATURE_SIZE (1<<3)
+#define SMTP_FEATURE_STARTTLS (1<<4)
+#define SMTP_FEATURE_AUTH (1<<5)
+#define SMTP_FEATURE_XFORWARD_NAME (1<<7)
+#define SMTP_FEATURE_XFORWARD_ADDR (1<<8)
+#define SMTP_FEATURE_XFORWARD_PROTO (1<<9)
+#define SMTP_FEATURE_XFORWARD_HELO (1<<10)
+#define SMTP_FEATURE_XFORWARD_DOMAIN (1<<11)
+#define SMTP_FEATURE_BEST_MX (1<<12) /* for next-hop or fall-back */
+#define SMTP_FEATURE_RSET_REJECTED (1<<13) /* RSET probe rejected */
+#define SMTP_FEATURE_FROM_CACHE (1<<14) /* cached connection */
+#define SMTP_FEATURE_DSN (1<<15) /* DSN supported */
+#define SMTP_FEATURE_PIX_NO_ESMTP (1<<16) /* PIX smtp fixup mode */
+#define SMTP_FEATURE_PIX_DELAY_DOTCRLF (1<<17) /* PIX smtp fixup mode */
+#define SMTP_FEATURE_XFORWARD_PORT (1<<18)
+#define SMTP_FEATURE_EARLY_TLS_MAIL_REPLY (1<<19) /* CVE-2009-3555 */
+#define SMTP_FEATURE_XFORWARD_IDENT (1<<20)
+#define SMTP_FEATURE_SMTPUTF8 (1<<21) /* RFC 6531 */
+#define SMTP_FEATURE_FROM_PROXY (1<<22) /* proxied connection */
+
+ /*
+ * Features that passivate under the endpoint.
+ */
+#define SMTP_FEATURE_ENDPOINT_MASK \
+ (~(SMTP_FEATURE_BEST_MX | SMTP_FEATURE_RSET_REJECTED \
+ | SMTP_FEATURE_FROM_CACHE))
+
+ /*
+ * Features that passivate under the logical destination.
+ */
+#define SMTP_FEATURE_DESTINATION_MASK (SMTP_FEATURE_BEST_MX)
+
+ /*
+ * Misc flags.
+ */
+#define SMTP_MISC_FLAG_LOOP_DETECT (1<<0)
+#define SMTP_MISC_FLAG_IN_STARTTLS (1<<1)
+#define SMTP_MISC_FLAG_FIRST_NEXTHOP (1<<2)
+#define SMTP_MISC_FLAG_FINAL_NEXTHOP (1<<3)
+#define SMTP_MISC_FLAG_FINAL_SERVER (1<<4)
+#define SMTP_MISC_FLAG_CONN_LOAD (1<<5)
+#define SMTP_MISC_FLAG_CONN_STORE (1<<6)
+#define SMTP_MISC_FLAG_COMPLETE_SESSION (1<<7)
+#define SMTP_MISC_FLAG_PREF_IPV6 (1<<8)
+#define SMTP_MISC_FLAG_PREF_IPV4 (1<<9)
+
+#define SMTP_MISC_FLAG_CONN_CACHE_MASK \
+ (SMTP_MISC_FLAG_CONN_LOAD | SMTP_MISC_FLAG_CONN_STORE)
+
+ /*
+ * smtp.c
+ */
+#define SMTP_HAS_DSN(why) (STR((why)->status)[0] != 0)
+#define SMTP_HAS_SOFT_DSN(why) (STR((why)->status)[0] == '4')
+#define SMTP_HAS_HARD_DSN(why) (STR((why)->status)[0] == '5')
+#define SMTP_HAS_LOOP_DSN(why) \
+ (SMTP_HAS_DSN(why) && strcmp(STR((why)->status) + 1, ".4.6") == 0)
+
+#define SMTP_SET_SOFT_DSN(why) (STR((why)->status)[0] = '4')
+#define SMTP_SET_HARD_DSN(why) (STR((why)->status)[0] = '5')
+
+extern int smtp_host_lookup_mask; /* host lookup methods to use */
+
+#define SMTP_HOST_FLAG_DNS (1<<0)
+#define SMTP_HOST_FLAG_NATIVE (1<<1)
+
+extern int smtp_dns_support; /* dns support level */
+
+#define SMTP_DNS_INVALID (-1) /* smtp_dns_support_level = <bogus> */
+#define SMTP_DNS_DISABLED 0 /* smtp_dns_support_level = disabled */
+#define SMTP_DNS_ENABLED 1 /* smtp_dns_support_level = enabled */
+#define SMTP_DNS_DNSSEC 2 /* smtp_dns_support_level = dnssec */
+
+extern SCACHE *smtp_scache; /* connection cache instance */
+extern STRING_LIST *smtp_cache_dest; /* cached destinations */
+
+extern MAPS *smtp_ehlo_dis_maps; /* ehlo keyword filter */
+
+extern MAPS *smtp_pix_bug_maps; /* PIX workarounds */
+
+extern MAPS *smtp_generic_maps; /* make internal address valid */
+extern int smtp_ext_prop_mask; /* address externsion propagation */
+extern unsigned smtp_dns_res_opt; /* DNS query flags */
+
+#ifdef USE_TLS
+
+extern TLS_APPL_STATE *smtp_tls_ctx; /* client-side TLS engine */
+extern int smtp_tls_insecure_mx_policy; /* DANE post insecure MX? */
+
+#endif
+
+extern HBC_CHECKS *smtp_header_checks; /* limited header checks */
+extern HBC_CHECKS *smtp_body_checks; /* limited body checks */
+
+ /*
+ * smtp_session.c
+ */
+
+typedef struct SMTP_SESSION {
+ VSTREAM *stream; /* network connection */
+ SMTP_ITERATOR *iterator; /* dest, host, addr, port */
+ char *namaddr; /* mail exchanger */
+ char *helo; /* helo response */
+ unsigned port; /* network byte order */
+ char *namaddrport; /* mail exchanger, incl. port */
+
+ VSTRING *buffer; /* I/O buffer */
+ VSTRING *scratch; /* scratch buffer */
+ VSTRING *scratch2; /* scratch buffer */
+
+ int features; /* server features */
+ off_t size_limit; /* server limit or unknown */
+
+ ARGV *history; /* transaction log */
+ int error_mask; /* error classes */
+ struct MIME_STATE *mime_state; /* mime state machine */
+
+ int send_proto_helo; /* XFORWARD support */
+
+ time_t expire_time; /* session reuse expiration time */
+ int reuse_count; /* # of times reused (for logging) */
+ int forbidden; /* No further I/O allowed */
+
+#ifdef USE_SASL_AUTH
+ char *sasl_mechanism_list; /* server mechanism list */
+ char *sasl_username; /* client username */
+ char *sasl_passwd; /* client password */
+ struct XSASL_CLIENT *sasl_client; /* SASL internal state */
+ VSTRING *sasl_reply; /* client response */
+#endif
+
+ /*
+ * TLS related state, don't forget to initialize in session_tls_init()!
+ */
+#ifdef USE_TLS
+ TLS_SESS_STATE *tls_context; /* TLS library session state */
+ char *tls_nexthop; /* Nexthop domain for cert checks */
+ int tls_retry_plain; /* Try plain when TLS handshake fails */
+#endif
+
+ SMTP_STATE *state; /* back link */
+} SMTP_SESSION;
+
+extern SMTP_SESSION *smtp_session_alloc(VSTREAM *, SMTP_ITERATOR *, time_t, int);
+extern void smtp_session_new_stream(SMTP_SESSION *, VSTREAM *, time_t, int);
+extern int smtp_sess_plaintext_ok(SMTP_ITERATOR *, int);
+extern void smtp_session_free(SMTP_SESSION *);
+extern int smtp_session_passivate(SMTP_SESSION *, VSTRING *, VSTRING *);
+extern SMTP_SESSION *smtp_session_activate(int, SMTP_ITERATOR *, VSTRING *, VSTRING *);
+
+ /*
+ * What's in a name?
+ */
+#define SMTP_HNAME(rr) (var_smtp_cname_overr ? (rr)->rname : (rr)->qname)
+
+ /*
+ * smtp_connect.c
+ */
+extern int smtp_connect(SMTP_STATE *);
+
+ /*
+ * smtp_proto.c
+ */
+extern void smtp_vrfy_init(void);
+extern int smtp_helo(SMTP_STATE *);
+extern int smtp_xfer(SMTP_STATE *);
+extern int smtp_rset(SMTP_STATE *);
+extern int smtp_quit(SMTP_STATE *);
+
+extern HBC_CALL_BACKS smtp_hbc_callbacks[];
+
+ /*
+ * A connection is re-usable if session->expire_time is > 0 and the
+ * expiration time has not been reached. This is subtle because the timer
+ * can expire between sending a command and receiving the reply for that
+ * command.
+ *
+ * But wait, there is more! When SMTP command pipelining is enabled, there are
+ * two protocol loops that execute at very different times: one loop that
+ * generates commands, and one loop that receives replies to those commands.
+ * These will be called "sender loop" and "receiver loop", respectively. At
+ * well-defined protocol synchronization points, the sender loop pauses to
+ * let the receiver loop catch up.
+ *
+ * When we choose to reuse a connection, both the sender and receiver protocol
+ * loops end with "." (mail delivery) or "RSET" (address probe). When we
+ * choose not to reuse, both the sender and receiver protocol loops end with
+ * "QUIT". The problem is that we must make the same protocol choices in
+ * both the sender and receiver loops, even though those loops may execute
+ * at completely different times.
+ *
+ * We "freeze" the choice in the sender loop, just before we generate "." or
+ * "RSET". The reader loop leaves the connection cacheable even if the timer
+ * expires by the time the response arrives. The connection cleanup code
+ * will call smtp_quit() for connections with an expired cache expiration
+ * timer.
+ *
+ * We could have made the programmer's life a lot simpler by not making a
+ * choice at all, and always leaving it up to the connection cleanup code to
+ * call smtp_quit() for connections with an expired cache expiration timer.
+ *
+ * As a general principle, neither the sender loop nor the receiver loop must
+ * modify the connection caching state, if that can affect the receiver
+ * state machine for not-yet processed replies to already-generated
+ * commands. This restriction does not apply when we have to exit the
+ * protocol loops prematurely due to e.g., timeout or connection loss, so
+ * that those pending replies will never be received.
+ *
+ * But wait, there is even more! Only the first good connection for a specific
+ * destination may be cached under both the next-hop destination name and
+ * the server address; connections to alternate servers must be cached under
+ * the server address alone. This means we must distinguish between bad
+ * connections and other reasons why connections cannot be cached.
+ */
+#define THIS_SESSION_IS_CACHED \
+ (!THIS_SESSION_IS_FORBIDDEN && session->expire_time > 0)
+
+#define THIS_SESSION_IS_EXPIRED \
+ (THIS_SESSION_IS_CACHED \
+ && (session->expire_time < vstream_ftime(session->stream) \
+ || (var_smtp_reuse_count > 0 \
+ && session->reuse_count >= var_smtp_reuse_count)))
+
+#define THIS_SESSION_IS_THROTTLED \
+ (!THIS_SESSION_IS_FORBIDDEN && session->expire_time < 0)
+
+#define THIS_SESSION_IS_FORBIDDEN \
+ (session->forbidden != 0)
+
+ /* Bring the bad news. */
+
+#define DONT_CACHE_THIS_SESSION \
+ (session->expire_time = 0)
+
+#define DONT_CACHE_THROTTLED_SESSION \
+ (session->expire_time = -1)
+
+#define DONT_USE_FORBIDDEN_SESSION \
+ (session->forbidden = 1)
+
+ /* Initialization. */
+
+#define USE_NEWBORN_SESSION \
+ (session->forbidden = 0)
+
+#define CACHE_THIS_SESSION_UNTIL(when) \
+ (session->expire_time = (when))
+
+ /*
+ * Encapsulate the following so that we don't expose details of of
+ * connection management and error handling to the SMTP protocol engine.
+ */
+#ifdef USE_SASL_AUTH
+#define HAVE_SASL_CREDENTIALS \
+ (var_smtp_sasl_enable \
+ && *var_smtp_sasl_passwd \
+ && smtp_sasl_passwd_lookup(session))
+#else
+#define HAVE_SASL_CREDENTIALS (0)
+#endif
+
+#define PREACTIVE_DELAY \
+ (session->state->request->msg_stats.active_arrival.tv_sec - \
+ session->state->request->msg_stats.incoming_arrival.tv_sec)
+
+#define PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE \
+ (session->tls_context == 0 \
+ && state->tls->level == TLS_LEV_MAY \
+ && PREACTIVE_DELAY >= var_min_backoff_time \
+ && !HAVE_SASL_CREDENTIALS)
+
+#define PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE \
+ (session->tls_context != 0 \
+ && SMTP_RCPT_LEFT(state) > SMTP_RCPT_MARK_COUNT(state) \
+ && state->tls->level == TLS_LEV_MAY \
+ && PREACTIVE_DELAY >= var_min_backoff_time \
+ && !HAVE_SASL_CREDENTIALS)
+
+ /*
+ * XXX The following will not retry recipients that were deferred while the
+ * SMTP_MISC_FLAG_FINAL_SERVER flag was already set. This includes the case
+ * when TLS fails in the middle of a delivery.
+ */
+#define RETRY_AS_PLAINTEXT do { \
+ session->tls_retry_plain = 1; \
+ state->misc_flags &= ~SMTP_MISC_FLAG_FINAL_SERVER; \
+ } while (0)
+
+ /*
+ * smtp_chat.c
+ */
+typedef struct SMTP_RESP { /* server response */
+ int code; /* SMTP code */
+ const char *dsn; /* enhanced status */
+ char *str; /* full reply */
+ VSTRING *dsn_buf; /* status buffer */
+ VSTRING *str_buf; /* reply buffer */
+} SMTP_RESP;
+
+extern void PRINTFLIKE(2, 3) smtp_chat_cmd(SMTP_SESSION *, const char *,...);
+extern DICT *smtp_chat_resp_filter;
+extern SMTP_RESP *smtp_chat_resp(SMTP_SESSION *);
+extern void smtp_chat_init(SMTP_SESSION *);
+extern void smtp_chat_reset(SMTP_SESSION *);
+extern void smtp_chat_notify(SMTP_SESSION *);
+
+#define SMTP_RESP_FAKE(resp, _dsn) \
+ ((resp)->code = 0, \
+ (resp)->dsn = (_dsn), \
+ (resp)->str = DSN_BY_LOCAL_MTA, \
+ (resp))
+
+#define DSN_BY_LOCAL_MTA ((char *) 0) /* DSN issued by local MTA */
+
+#define SMTP_RESP_SET_DSN(resp, _dsn) do { \
+ vstring_strcpy((resp)->dsn_buf, (_dsn)); \
+ (resp)->dsn = STR((resp)->dsn_buf); \
+ } while (0)
+
+ /*
+ * These operations implement a redundant mark-and-sweep algorithm that
+ * explicitly accounts for the fate of every recipient. The interface is
+ * documented in smtp_rcpt.c, which also implements the sweeping. The
+ * smtp_trouble.c module does most of the marking after failure.
+ *
+ * When a delivery fails or succeeds, take one of the following actions:
+ *
+ * - Mark the recipient as KEEP (deliver to alternate MTA) and do not update
+ * the delivery request status.
+ *
+ * - Mark the recipient as DROP (remove from delivery request), log whether
+ * delivery succeeded or failed, delete the recipient from the queue file
+ * and/or update defer or bounce logfiles, and update the delivery request
+ * status.
+ *
+ * At the end of a delivery attempt, all recipients must be marked one way or
+ * the other. Failure to do so will trigger a panic.
+ */
+#define SMTP_RCPT_STATE_KEEP 1 /* send to backup host */
+#define SMTP_RCPT_STATE_DROP 2 /* remove from request */
+#define SMTP_RCPT_INIT(state) do { \
+ (state)->rcpt_drop = (state)->rcpt_keep = 0; \
+ (state)->rcpt_left = state->request->rcpt_list.len; \
+ } while (0)
+
+#define SMTP_RCPT_DROP(state, rcpt) do { \
+ (rcpt)->u.status = SMTP_RCPT_STATE_DROP; (state)->rcpt_drop++; \
+ } while (0)
+
+#define SMTP_RCPT_KEEP(state, rcpt) do { \
+ (rcpt)->u.status = SMTP_RCPT_STATE_KEEP; (state)->rcpt_keep++; \
+ } while (0)
+
+#define SMTP_RCPT_ISMARKED(rcpt) ((rcpt)->u.status != 0)
+
+#define SMTP_RCPT_LEFT(state) (state)->rcpt_left
+
+#define SMTP_RCPT_MARK_COUNT(state) ((state)->rcpt_drop + (state)->rcpt_keep)
+
+extern void smtp_rcpt_cleanup(SMTP_STATE *);
+extern void smtp_rcpt_done(SMTP_STATE *, SMTP_RESP *, RECIPIENT *);
+
+ /*
+ * smtp_trouble.c
+ */
+#define SMTP_THROTTLE 1
+#define SMTP_NOTHROTTLE 0
+extern int smtp_sess_fail(SMTP_STATE *);
+extern int PRINTFLIKE(5, 6) smtp_misc_fail(SMTP_STATE *, int, const char *,
+ SMTP_RESP *, const char *,...);
+extern void PRINTFLIKE(5, 6) smtp_rcpt_fail(SMTP_STATE *, RECIPIENT *,
+ const char *, SMTP_RESP *,
+ const char *,...);
+extern int smtp_stream_except(SMTP_STATE *, int, const char *);
+
+#define smtp_site_fail(state, mta, resp, ...) \
+ smtp_misc_fail((state), SMTP_THROTTLE, (mta), (resp), __VA_ARGS__)
+#define smtp_mesg_fail(state, mta, resp, ...) \
+ smtp_misc_fail((state), SMTP_NOTHROTTLE, (mta), (resp), __VA_ARGS__)
+
+ /*
+ * smtp_unalias.c
+ */
+extern const char *smtp_unalias_name(const char *);
+extern VSTRING *smtp_unalias_addr(VSTRING *, const char *);
+
+ /*
+ * smtp_state.c
+ */
+extern SMTP_STATE *smtp_state_alloc(void);
+extern void smtp_state_free(SMTP_STATE *);
+
+ /*
+ * smtp_map11.c
+ */
+extern int smtp_map11_external(VSTRING *, MAPS *, int);
+extern int smtp_map11_tree(TOK822 *, MAPS *, int);
+extern int smtp_map11_internal(VSTRING *, MAPS *, int);
+
+ /*
+ * smtp_key.c
+ */
+char *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int);
+
+#define SMTP_KEY_FLAG_SERVICE (1<<0) /* service name */
+#define SMTP_KEY_FLAG_SENDER (1<<1) /* sender address */
+#define SMTP_KEY_FLAG_REQ_NEXTHOP (1<<2) /* delivery request nexthop */
+#define SMTP_KEY_FLAG_CUR_NEXTHOP (1<<3) /* current nexthop */
+#define SMTP_KEY_FLAG_HOSTNAME (1<<4) /* remote host name */
+#define SMTP_KEY_FLAG_ADDR (1<<5) /* remote address */
+#define SMTP_KEY_FLAG_PORT (1<<6) /* remote port */
+#define SMTP_KEY_FLAG_TLS_LEVEL (1<<7) /* requested TLS level */
+
+#define SMTP_KEY_MASK_ALL \
+ (SMTP_KEY_FLAG_SERVICE | SMTP_KEY_FLAG_SENDER | \
+ SMTP_KEY_FLAG_REQ_NEXTHOP | \
+ SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_HOSTNAME | \
+ SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL)
+
+ /*
+ * Conditional lookup-key flags for cached connections that may be
+ * SASL-authenticated with a per-{sender, nexthop, or hostname} credential.
+ * Each bit corresponds to one type of smtp_sasl_password_file lookup key,
+ * and is turned on only when the corresponding main.cf parameter is turned
+ * on.
+ */
+#define COND_SASL_SMTP_KEY_FLAG_SENDER \
+ ((var_smtp_sender_auth && *var_smtp_sasl_passwd) ? \
+ SMTP_KEY_FLAG_SENDER : 0)
+
+#define COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \
+ (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0)
+
+#ifdef USE_TLS
+#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \
+ (TLS_MUST_MATCH(state->tls->level) ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0)
+#else
+#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \
+ (0)
+#endif
+
+#define COND_SASL_SMTP_KEY_FLAG_HOSTNAME \
+ (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_HOSTNAME : 0)
+
+ /*
+ * Connection-cache destination lookup key, based on the delivery request
+ * nexthop. The SENDER attribute is a proxy for sender-dependent SASL
+ * credentials (or absence thereof), and prevents false connection sharing
+ * when different SASL credentials may be required for different deliveries
+ * to the same domain and port. Likewise, the delivery request nexthop
+ * (REQ_NEXTHOP) prevents false sharing of TLS identities (the destination
+ * key links only to appropriate endpoint lookup keys). The SERVICE
+ * attribute is a proxy for all request-independent configuration details.
+ */
+#define SMTP_KEY_MASK_SCACHE_DEST_LABEL \
+ (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \
+ | SMTP_KEY_FLAG_REQ_NEXTHOP)
+
+ /*
+ * Connection-cache endpoint lookup key. The SENDER, CUR_NEXTHOP, HOSTNAME,
+ * PORT and TLS_LEVEL attributes are proxies for SASL credentials and TLS
+ * authentication (or absence thereof), and prevent false connection sharing
+ * when different SASL credentials or TLS identities may be required for
+ * different deliveries to the same IP address and port. The SERVICE
+ * attribute is a proxy for all request-independent configuration details.
+ */
+#define SMTP_KEY_MASK_SCACHE_ENDP_LABEL \
+ (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \
+ | COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \
+ | COND_SASL_SMTP_KEY_FLAG_HOSTNAME \
+ | COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_ADDR | \
+ SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL)
+
+ /*
+ * Silly little macros.
+ */
+#define STR(s) vstring_str(s)
+#define LEN(s) VSTRING_LEN(s)
+
+extern int smtp_mode;
+
+#define VAR_LMTP_SMTP(x) (smtp_mode ? VAR_SMTP_##x : VAR_LMTP_##x)
+#define LMTP_SMTP_SUFFIX(x) (smtp_mode ? x##_SMTP : x##_LMTP)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
diff --git a/src/smtp/smtp_addr.c b/src/smtp/smtp_addr.c
new file mode 100644
index 0000000..2210ff7
--- /dev/null
+++ b/src/smtp/smtp_addr.c
@@ -0,0 +1,693 @@
+/*++
+/* NAME
+/* smtp_addr 3
+/* SUMMARY
+/* SMTP server address lookup
+/* SYNOPSIS
+/* #include "smtp_addr.h"
+/*
+/* DNS_RR *smtp_domain_addr(name, mxrr, misc_flags, why, found_myself)
+/* char *name;
+/* DNS_RR **mxrr;
+/* int misc_flags;
+/* DSN_BUF *why;
+/* int *found_myself;
+/*
+/* DNS_RR *smtp_host_addr(name, misc_flags, why)
+/* char *name;
+/* int misc_flags;
+/* DSN_BUF *why;
+/* DESCRIPTION
+/* This module implements Internet address lookups. By default,
+/* lookups are done via the Internet domain name service (DNS).
+/* A reasonable number of CNAME indirections is permitted. When
+/* DNS lookups are disabled, host address lookup is done with
+/* getnameinfo() or gethostbyname().
+/*
+/* smtp_domain_addr() looks up the network addresses for mail
+/* exchanger hosts listed for the named domain. Addresses are
+/* returned in most-preferred first order. The result is truncated
+/* so that it contains only hosts that are more preferred than the
+/* local mail server itself. The found_myself result parameter
+/* is updated when the local MTA is MX host for the specified
+/* destination. If MX records were found, the rname, qname,
+/* and dnssec validation status of the MX RRset are returned
+/* via mxrr, which the caller must free with dns_rr_free().
+/*
+/* When no mail exchanger is listed in the DNS for \fIname\fR, the
+/* request is passed to smtp_host_addr().
+/*
+/* It is an error to call smtp_domain_addr() when DNS lookups are
+/* disabled.
+/*
+/* smtp_host_addr() looks up all addresses listed for the named
+/* host. The host can be specified as a numerical Internet network
+/* address, or as a symbolic host name.
+/*
+/* Results from smtp_domain_addr() or smtp_host_addr() are
+/* destroyed by dns_rr_free(), including null lists.
+/* DIAGNOSTICS
+/* Panics: interface violations. For example, calling smtp_domain_addr()
+/* when DNS lookups are explicitly disabled.
+/*
+/* All routines either return a DNS_RR pointer, or return a null
+/* pointer and update the \fIwhy\fR argument accordingly.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <inet_addr_list.h>
+#include <stringops.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <midna_domain.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <own_inet_addr.h>
+#include <dsn_buf.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_addr.h"
+
+/* smtp_print_addr - print address list */
+
+static void smtp_print_addr(const char *what, DNS_RR *addr_list)
+{
+ DNS_RR *addr;
+ MAI_HOSTADDR_STR hostaddr;
+
+ msg_info("begin %s address list", what);
+ for (addr = addr_list; addr; addr = addr->next) {
+ if (dns_rr_to_pa(addr, &hostaddr) == 0) {
+ msg_warn("skipping record type %s: %m", dns_strtype(addr->type));
+ } else {
+ msg_info("pref %4d host %s/%s",
+ addr->pref, SMTP_HNAME(addr),
+ hostaddr.buf);
+ }
+ }
+ msg_info("end %s address list", what);
+}
+
+/* smtp_addr_one - address lookup for one host name */
+
+static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt,
+ unsigned pref, DSN_BUF *why)
+{
+ const char *myname = "smtp_addr_one";
+ DNS_RR *addr = 0;
+ DNS_RR *rr;
+ int aierr;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ INET_PROTO_INFO *proto_info = inet_proto_info();
+ unsigned char *proto_family_list = proto_info->sa_family_list;
+ int found;
+
+ if (msg_verbose)
+ msg_info("%s: host %s", myname, host);
+
+ /*
+ * Interpret a numerical name as an address.
+ */
+ if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0) {
+ if (strchr((char *) proto_family_list, res0->ai_family) != 0) {
+ if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0)
+ msg_fatal("host %s: conversion error for address family "
+ "%d: %m", host, res0->ai_addr->sa_family);
+ addr_list = dns_rr_append(addr_list, addr);
+ freeaddrinfo(res0);
+ return (addr_list);
+ }
+ freeaddrinfo(res0);
+ }
+
+ /*
+ * Use DNS lookup, but keep the option open to use native name service.
+ *
+ * XXX A soft error dominates past and future hard errors. Therefore we
+ * should not clobber a soft error text and status code.
+ */
+ if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) {
+ res_opt |= smtp_dns_res_opt;
+ switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0,
+ why->reason, DNS_REQ_FLAG_NONE,
+ proto_info->dns_atype_list)) {
+ case DNS_OK:
+ for (rr = addr; rr; rr = rr->next)
+ rr->pref = pref;
+ addr_list = dns_rr_append(addr_list, addr);
+ return (addr_list);
+ default:
+ dsb_status(why, "4.4.3");
+ return (addr_list);
+ case DNS_FAIL:
+ dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3");
+ return (addr_list);
+ case DNS_INVAL:
+ dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
+ return (addr_list);
+ case DNS_POLICY:
+ dsb_status(why, "4.7.0");
+ return (addr_list);
+ case DNS_NOTFOUND:
+ dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
+ /* maybe native naming service will succeed */
+ break;
+ }
+ }
+
+ /*
+ * Use the native name service which also looks in /etc/hosts.
+ *
+ * XXX A soft error dominates past and future hard errors. Therefore we
+ * should not clobber a soft error text and status code.
+ */
+#define RETRY_AI_ERROR(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
+#ifdef EAI_NODATA
+#define DSN_NOHOST(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME)
+#else
+#define DSN_NOHOST(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_NONAME)
+#endif
+
+ if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) {
+ if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) {
+ dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ?
+ (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") :
+ (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"),
+ "unable to look up host %s: %s",
+ host, MAI_STRERROR(aierr));
+ } else {
+ for (found = 0, res = res0; res != 0; res = res->ai_next) {
+ if (strchr((char *) proto_family_list, res->ai_family) == 0) {
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, host);
+ continue;
+ }
+ found++;
+ if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0)
+ msg_fatal("host %s: conversion error for address family "
+ "%d: %m", host, res0->ai_addr->sa_family);
+ addr_list = dns_rr_append(addr_list, addr);
+ }
+ freeaddrinfo(res0);
+ if (found == 0) {
+ dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4",
+ "%s: host not found", host);
+ }
+ return (addr_list);
+ }
+ }
+
+ /*
+ * No further alternatives for host lookup.
+ */
+ return (addr_list);
+}
+
+/* smtp_addr_list - address lookup for a list of mail exchangers */
+
+static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *why)
+{
+ DNS_RR *addr_list = 0;
+ DNS_RR *rr;
+ int res_opt = 0;
+
+ if (mx_names->dnssec_valid)
+ res_opt = RES_USE_DNSSEC;
+#ifdef USE_TLS
+ else if (smtp_tls_insecure_mx_policy > TLS_LEV_MAY)
+ res_opt = RES_USE_DNSSEC;
+#endif
+
+ /*
+ * As long as we are able to look up any host address, we ignore problems
+ * with DNS lookups (except if we're backup MX, and all the better MX
+ * hosts can't be found).
+ *
+ * XXX 2821: update the error status (0->FAIL upon unrecoverable lookup
+ * error, any->RETRY upon temporary lookup error) so that we can
+ * correctly handle the case of no resolvable MX host. Currently this is
+ * always treated as a soft error. RFC 2821 wants a more precise
+ * response.
+ *
+ * XXX dns_lookup() enables RES_DEFNAMES. This is wrong for names found in
+ * MX records - we should not append the local domain to dot-less names.
+ *
+ * XXX However, this is not the only problem. If we use the native name
+ * service for host lookup, then it will usually enable RES_DNSRCH which
+ * appends local domain information to all lookups. In particular,
+ * getaddrinfo() may invoke a resolver that runs in a different process
+ * (NIS server, nscd), so we can't even reliably turn this off by
+ * tweaking the in-process resolver flags.
+ */
+ for (rr = mx_names; rr; rr = rr->next) {
+ if (rr->type != T_MX)
+ msg_panic("smtp_addr_list: bad resource type: %d", rr->type);
+ addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt,
+ rr->pref, why);
+ }
+ return (addr_list);
+}
+
+/* smtp_find_self - spot myself in a crowd of mail exchangers */
+
+static DNS_RR *smtp_find_self(DNS_RR *addr_list)
+{
+ const char *myname = "smtp_find_self";
+ INET_ADDR_LIST *self;
+ INET_ADDR_LIST *proxy;
+ DNS_RR *addr;
+ int i;
+
+ self = own_inet_addr_list();
+ proxy = proxy_inet_addr_list();
+
+ for (addr = addr_list; addr; addr = addr->next) {
+
+ /*
+ * Find out if this mail system is listening on this address.
+ */
+ for (i = 0; i < self->used; i++)
+ if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) {
+ if (msg_verbose)
+ msg_info("%s: found self at pref %d", myname, addr->pref);
+ return (addr);
+ }
+
+ /*
+ * Find out if this mail system has a proxy listening on this
+ * address.
+ */
+ for (i = 0; i < proxy->used; i++)
+ if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) {
+ if (msg_verbose)
+ msg_info("%s: found proxy at pref %d", myname, addr->pref);
+ return (addr);
+ }
+ }
+
+ /*
+ * Didn't find myself, or my proxy.
+ */
+ if (msg_verbose)
+ msg_info("%s: not found", myname);
+ return (0);
+}
+
+/* smtp_truncate_self - truncate address list at self and equivalents */
+
+static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref)
+{
+ DNS_RR *addr;
+ DNS_RR *last;
+
+ for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) {
+ if (pref == addr->pref) {
+ if (msg_verbose)
+ smtp_print_addr("truncated", addr);
+ dns_rr_free(addr);
+ if (last == 0) {
+ addr_list = 0;
+ } else {
+ last->next = 0;
+ }
+ break;
+ }
+ }
+ return (addr_list);
+}
+
+/* smtp_balance_inet_proto - balance IPv4/6 protocols within address limit */
+
+static DNS_RR *smtp_balance_inet_proto(DNS_RR *addr_list, int misc_flags,
+ int addr_limit)
+{
+ const char myname[] = "smtp_balance_inet_proto";
+ DNS_RR *rr;
+ DNS_RR *stripped_list;
+ DNS_RR *next;
+ int v6_count;
+ int v4_count;
+ int v6_target,
+ v4_target;
+ int *p;
+
+ /*
+ * Precondition: the input is sorted by MX preference (not necessarily IP
+ * address family preference), and addresses with the same or worse
+ * preference than 'myself' have been eliminated. Postcondition: the
+ * relative list order is unchanged, but some elements are removed.
+ */
+
+ /*
+ * Count the number of IPv6 and IPv4 addresses.
+ */
+ for (v4_count = v6_count = 0, rr = addr_list; rr != 0; rr = rr->next) {
+ if (rr->type == T_A) {
+ v4_count++;
+ } else if (rr->type == T_AAAA) {
+ v6_count++;
+ } else {
+ msg_panic("%s: unexpected record type: %s",
+ myname, dns_strtype(rr->type));
+ }
+ }
+
+ /*
+ * Ensure that one address type will not out-crowd the other, while
+ * enforcing the address count limit. This works around a current problem
+ * where some destination announces primarily IPv6 MX addresses, the
+ * smtp_address_limit eliminates most or all IPv4 addresses, and the
+ * destination is not reachable over IPv6.
+ *
+ * Maybe: do all smtp_mx_address_limit enforcement here, and remove
+ * pre-existing enforcement elsewhere. That would obsolete the
+ * smtp_balance_inet_protocols configuration parameter.
+ */
+ if (v4_count > 0 && v6_count > 0 && v4_count + v6_count > addr_limit) {
+
+ /*-
+ * Decide how many IPv6 and IPv4 addresses to keep. The code below
+ * has three branches, corresponding to the regions R1, R2 and R3
+ * in the figure.
+ *
+ * L = addr_limit
+ * X = excluded by condition (v4_count + v6_count > addr_limit)
+ *
+ * v4_count
+ * ^
+ * |
+ * L \ R1
+ * |X\ |
+ * |XXX\ |
+ * |XXXXX\ | R2
+ * L/2 +-------\-------
+ * |XXXXXXX|X\
+ * |XXXXXXX|XXX\ R3
+ * |XXXXXXX|XXXXX\
+ * 0 +-------+-------\--> v6_count
+ * 0 L/2 L
+ */
+ if (v6_count <= addr_limit / 2) { /* Region R1 */
+ v6_target = v6_count;
+ v4_target = addr_limit - v6_target;
+ } else if (v4_count <= addr_limit / 2) {/* Region R3 */
+ v4_target = v4_count;
+ v6_target = addr_limit - v4_target;
+ } else { /* Region R2 */
+ /* v4_count > addr_limit / 2 && v6_count > addr_limit / 2 */
+ v4_target = (addr_limit + (addr_list->type == T_A)) / 2;
+ v6_target = addr_limit - v4_target;
+ }
+ if (msg_verbose)
+ msg_info("v6_target=%d, v4_target=%d", v6_target, v4_target);
+
+ /* Enforce the address count targets. */
+ stripped_list = 0;
+ for (rr = addr_list; rr != 0; rr = next) {
+ next = rr->next;
+ rr->next = 0;
+ if (rr->type == T_A) {
+ p = &v4_target;
+ } else if (rr->type == T_AAAA) {
+ p = &v6_target;
+ } else {
+ msg_panic("%s: unexpected record type: %s",
+ myname, dns_strtype(rr->type));
+ }
+ if (*p > 0) {
+ stripped_list = dns_rr_append(stripped_list, rr);
+ *p -= 1;
+ } else {
+ dns_rr_free(rr);
+ }
+ }
+ if (v4_target > 0 || v6_target > 0)
+ msg_panic("%s: bad target count: v4_target=%d, v6_target=%d",
+ myname, v4_target, v6_target);
+ if (msg_verbose)
+ smtp_print_addr("smtp_balance_inet_proto result", stripped_list);
+ return (stripped_list);
+ } else {
+ return (addr_list);
+ }
+}
+
+/* smtp_domain_addr - mail exchanger address lookup */
+
+DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags,
+ DSN_BUF *why, int *found_myself)
+{
+ DNS_RR *mx_names;
+ DNS_RR *addr_list = 0;
+ DNS_RR *self = 0;
+ unsigned best_pref;
+ unsigned best_found;
+ int r = 0; /* Resolver flags */
+ const char *aname;
+
+ dsb_reset(why); /* Paranoia */
+
+ /*
+ * Preferences from DNS use 0..32767, fall-backs use 32768+.
+ */
+#define IMPOSSIBLE_PREFERENCE (~0)
+
+ /*
+ * Sanity check.
+ */
+ if (smtp_dns_support == SMTP_DNS_DISABLED)
+ msg_panic("smtp_domain_addr: DNS lookup is disabled");
+ if (smtp_dns_support == SMTP_DNS_DNSSEC)
+ r |= RES_USE_DNSSEC;
+
+ /*
+ * IDNA support.
+ */
+#ifndef NO_EAI
+ if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", name, aname);
+ } else
+#endif
+ aname = name;
+
+ /*
+ * Look up the mail exchanger hosts listed for this name. Sort the
+ * results by preference. Look up the corresponding host addresses, and
+ * truncate the list so that it contains only hosts that are more
+ * preferred than myself. When no MX resource records exist, look up the
+ * addresses listed for this name.
+ *
+ * According to RFC 974: "It is possible that the list of MXs in the
+ * response to the query will be empty. This is a special case. If the
+ * list is empty, mailers should treat it as if it contained one RR, an
+ * MX RR with a preference value of 0, and a host name of REMOTE. (I.e.,
+ * REMOTE is its only MX). In addition, the mailer should do no further
+ * processing on the list, but should attempt to deliver the message to
+ * REMOTE."
+ *
+ * Normally it is OK if an MX host cannot be found in the DNS; we'll just
+ * use a backup one, and silently ignore the better MX host. However, if
+ * the best backup that we can find in the DNS is the local machine, then
+ * we must remember that the local machine is not the primary MX host, or
+ * else we will claim that mail loops back.
+ *
+ * XXX Optionally do A lookups even when the MX lookup didn't complete.
+ * Unfortunately with some DNS servers this is not a transient problem.
+ *
+ * XXX Ideally we would perform A lookups only as far as needed. But as long
+ * as we're looking up all the hosts, it would be better to look up the
+ * least preferred host first, so that DNS lookup error messages make
+ * more sense.
+ *
+ * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX
+ * hosts, whereas multiple A records per hostname must be used in the
+ * order as received. They make the bogus assumption that a hostname with
+ * multiple A records corresponds to one machine with multiple network
+ * interfaces.
+ *
+ * XXX 2821: Postfix recognizes the local machine by looking for its own IP
+ * address in the list of mail exchangers. RFC 2821 says one has to look
+ * at the mail exchanger hostname as well, making the bogus assumption
+ * that an IP address is listed only under one hostname. However, looking
+ * at hostnames provides a partial solution for MX hosts behind a NAT
+ * gateway.
+ */
+ switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) {
+ default:
+ dsb_status(why, "4.4.3");
+ if (var_ign_mx_lookup_err)
+ addr_list = smtp_host_addr(aname, misc_flags, why);
+ break;
+ case DNS_INVAL:
+ dsb_status(why, "5.4.4");
+ if (var_ign_mx_lookup_err)
+ addr_list = smtp_host_addr(aname, misc_flags, why);
+ break;
+ case DNS_NULLMX:
+ dsb_status(why, "5.1.0");
+ break;
+ case DNS_POLICY:
+ dsb_status(why, "4.7.0");
+ break;
+ case DNS_FAIL:
+ dsb_status(why, "5.4.3");
+ if (var_ign_mx_lookup_err)
+ addr_list = smtp_host_addr(aname, misc_flags, why);
+ break;
+ case DNS_OK:
+ mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any);
+ best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE);
+ addr_list = smtp_addr_list(mx_names, why);
+ if (mxrr)
+ *mxrr = dns_rr_copy(mx_names); /* copies one record! */
+ dns_rr_free(mx_names);
+ if (addr_list == 0) {
+ /* Text does not change. */
+ if (var_smtp_defer_mxaddr) {
+ /* Don't clobber the null terminator. */
+ if (SMTP_HAS_HARD_DSN(why))
+ SMTP_SET_SOFT_DSN(why); /* XXX */
+ /* Require some error status. */
+ else if (!SMTP_HAS_SOFT_DSN(why))
+ msg_panic("smtp_domain_addr: bad status");
+ }
+ msg_warn("no MX host for %s has a valid address record", name);
+ break;
+ }
+ best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
+ if (msg_verbose)
+ smtp_print_addr(name, addr_list);
+ if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
+ && (self = smtp_find_self(addr_list)) != 0) {
+ addr_list = smtp_truncate_self(addr_list, self->pref);
+ if (addr_list == 0) {
+ if (best_pref != best_found) {
+ dsb_simple(why, "4.4.4",
+ "unable to find primary relay for %s", name);
+ } else {
+ dsb_simple(why, "5.4.6", "mail for %s loops back to myself",
+ name);
+ }
+ }
+ }
+#define SMTP_COMPARE_ADDR(flags) \
+ (((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \
+ ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \
+ dns_rr_compare_pref_any)
+
+ if (addr_list && addr_list->next) {
+ if (var_smtp_rand_addr)
+ addr_list = dns_rr_shuffle(addr_list);
+ addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
+ if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
+ addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
+ var_smtp_mxaddr_limit);
+ }
+ break;
+ case DNS_NOTFOUND:
+ addr_list = smtp_host_addr(aname, misc_flags, why);
+ break;
+ }
+
+ /*
+ * Clean up.
+ */
+ *found_myself |= (self != 0);
+ return (addr_list);
+}
+
+/* smtp_host_addr - direct host lookup */
+
+DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why)
+{
+ DNS_RR *addr_list;
+ int res_opt = 0;
+ const char *ahost;
+
+ dsb_reset(why); /* Paranoia */
+
+ if (smtp_dns_support == SMTP_DNS_DNSSEC)
+ res_opt |= RES_USE_DNSSEC;
+
+ /*
+ * IDNA support.
+ */
+#ifndef NO_EAI
+ if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", host, ahost);
+ } else
+#endif
+ ahost = host;
+
+ /*
+ * If the host is specified by numerical address, just convert the
+ * address to internal form. Otherwise, the host is specified by name.
+ */
+#define PREF0 0
+ addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, why);
+ if (addr_list
+ && (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
+ && smtp_find_self(addr_list) != 0) {
+ dns_rr_free(addr_list);
+ dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host);
+ return (0);
+ }
+ if (addr_list && addr_list->next) {
+ if (var_smtp_rand_addr)
+ addr_list = dns_rr_shuffle(addr_list);
+ /* The following changes the order of equal-preference hosts. */
+ if (inet_proto_info()->ai_family_list[1] != 0)
+ addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
+ if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
+ addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
+ var_smtp_mxaddr_limit);
+ }
+ if (msg_verbose)
+ smtp_print_addr(host, addr_list);
+ return (addr_list);
+}
diff --git a/src/smtp/smtp_addr.h b/src/smtp/smtp_addr.h
new file mode 100644
index 0000000..8f20961
--- /dev/null
+++ b/src/smtp/smtp_addr.h
@@ -0,0 +1,31 @@
+/*++
+/* NAME
+/* smtp_addr 3h
+/* SUMMARY
+/* SMTP server address lookup
+/* SYNOPSIS
+/* #include "smtp_addr.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * DNS library.
+ */
+#include <dns.h>
+
+ /*
+ * Internal interfaces.
+ */
+extern DNS_RR *smtp_host_addr(const char *, int, DSN_BUF *);
+extern DNS_RR *smtp_domain_addr(const char *, DNS_RR **, int, DSN_BUF *, int *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
diff --git a/src/smtp/smtp_chat.c b/src/smtp/smtp_chat.c
new file mode 100644
index 0000000..a020ef8
--- /dev/null
+++ b/src/smtp/smtp_chat.c
@@ -0,0 +1,486 @@
+/*++
+/* NAME
+/* smtp_chat 3
+/* SUMMARY
+/* SMTP client request/response support
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* typedef struct {
+/* .in +4
+/* int code; /* SMTP code, not sanitized */
+/* char *dsn; /* enhanced status, sanitized */
+/* char *str; /* unmodified SMTP reply */
+/* VSTRING *dsn_buf;
+/* VSTRING *str_buf;
+/* .in -4
+/* } SMTP_RESP;
+/*
+/* void smtp_chat_cmd(session, format, ...)
+/* SMTP_SESSION *session;
+/* const char *format;
+/*
+/* DICT *smtp_chat_resp_filter;
+/*
+/* SMTP_RESP *smtp_chat_resp(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_chat_notify(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_chat_init(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_chat_reset(session)
+/* SMTP_SESSION *session;
+/* DESCRIPTION
+/* This module implements SMTP client support for request/reply
+/* conversations, and maintains a limited SMTP transaction log.
+/*
+/* smtp_chat_cmd() formats a command and sends it to an SMTP server.
+/* Optionally, the command is logged.
+/*
+/* smtp_chat_resp() reads one SMTP server response. It extracts
+/* the SMTP reply code and enhanced status code from the text,
+/* and concatenates multi-line responses to one string, using
+/* a newline as separator. Optionally, the server response
+/* is logged.
+/* .IP \(bu
+/* Postfix never sanitizes the extracted SMTP reply code except
+/* to ensure that it is a three-digit code. A malformed reply
+/* results in a null extracted SMTP reply code value.
+/* .IP \(bu
+/* Postfix always sanitizes the extracted enhanced status code.
+/* When the server's SMTP status code is 2xx, 4xx or 5xx,
+/* Postfix requires that the first digit of the server's
+/* enhanced status code matches the first digit of the server's
+/* SMTP status code. In case of a mis-match, or when the
+/* server specified no status code, the extracted enhanced
+/* status code is set to 2.0.0, 4.0.0 or 5.0.0 instead. With
+/* SMTP reply codes other than 2xx, 4xx or 5xx, the extracted
+/* enhanced status code is set to a default value of 5.5.0
+/* (protocol error) for reasons outlined under the next bullet.
+/* .IP \(bu
+/* Since the SMTP reply code may violate the protocol even
+/* when it is correctly formatted, Postfix uses the sanitized
+/* extracted enhanced status code to decide whether an error
+/* condition is permanent or transient. This means that the
+/* caller may have to update the enhanced status code when it
+/* discovers that a server reply violates the SMTP protocol,
+/* even though it was correctly formatted. This happens when
+/* the client and server get out of step due to a broken proxy
+/* agent.
+/* .PP
+/* smtp_chat_resp_filter specifies an optional filter to
+/* transform one server reply line before it is parsed. The
+/* filter is invoked once for each line of a multi-line reply.
+/*
+/* smtp_chat_notify() sends a copy of the SMTP transaction log
+/* to the postmaster for review. The postmaster notice is sent only
+/* when delivery is possible immediately. It is an error to call
+/* smtp_chat_notify() when no SMTP transaction log exists.
+/*
+/* smtp_chat_init() initializes the per-session transaction log.
+/* This must be done at the beginning of a new SMTP session.
+/*
+/* smtp_chat_reset() resets the transaction log. This is
+/* typically done at the beginning or end of an SMTP session,
+/* or within a session to discard non-error information.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem, server response exceeds
+/* configurable limit.
+/* All other exceptions are handled by long jumps (see smtp_stream(3)).
+/* SEE ALSO
+/* smtp_stream(3) SMTP session I/O support
+/* msg(3) generic logging interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <string.h>
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <argv.h>
+#include <stringops.h>
+#include <line_wrap.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <recipient_list.h>
+#include <deliver_request.h>
+#include <smtp_stream.h>
+#include <mail_params.h>
+#include <mail_addr.h>
+#include <post_mail.h>
+#include <mail_error.h>
+#include <dsn_util.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+
+ /*
+ * Server reply transformations.
+ */
+DICT *smtp_chat_resp_filter;
+
+/* smtp_chat_init - initialize SMTP transaction log */
+
+void smtp_chat_init(SMTP_SESSION *session)
+{
+ session->history = 0;
+}
+
+/* smtp_chat_reset - reset SMTP transaction log */
+
+void smtp_chat_reset(SMTP_SESSION *session)
+{
+ if (session->history) {
+ argv_free(session->history);
+ session->history = 0;
+ }
+}
+
+/* smtp_chat_append - append record to SMTP transaction log */
+
+static void smtp_chat_append(SMTP_SESSION *session, const char *direction,
+ const char *data)
+{
+ char *line;
+
+ if (session->history == 0)
+ session->history = argv_alloc(10);
+ line = concatenate(direction, data, (char *) 0);
+ argv_add(session->history, line, (char *) 0);
+ myfree(line);
+}
+
+/* smtp_chat_cmd - send an SMTP command */
+
+void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...)
+{
+ va_list ap;
+
+ /*
+ * Format the command, and update the transaction log.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(session->buffer, fmt, ap);
+ va_end(ap);
+ smtp_chat_append(session, "Out: ", STR(session->buffer));
+
+ /*
+ * Optionally log the command first, so we can see in the log what the
+ * program is trying to do.
+ */
+ if (msg_verbose)
+ msg_info("> %s: %s", session->namaddrport, STR(session->buffer));
+
+ /*
+ * Send the command to the SMTP server.
+ */
+ smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream);
+
+ /*
+ * Force flushing of output does not belong here. It is done in the
+ * smtp_loop() main protocol loop when reading the server response, and
+ * in smtp_helo() when reading the EHLO response after sending the EHLO
+ * command.
+ *
+ * If we do forced flush here, then we must longjmp() on error, and a
+ * matching "prepare for disaster" error handler must be set up before
+ * every smtp_chat_cmd() call.
+ */
+#if 0
+
+ /*
+ * Flush unsent data to avoid timeouts after slow DNS lookups.
+ */
+ if (time((time_t *) 0) - vstream_ftime(session->stream) > 10)
+ vstream_fflush(session->stream);
+
+ /*
+ * Abort immediately if the connection is broken.
+ */
+ if (vstream_ftimeout(session->stream))
+ vstream_longjmp(session->stream, SMTP_ERR_TIME);
+ if (vstream_ferror(session->stream))
+ vstream_longjmp(session->stream, SMTP_ERR_EOF);
+#endif
+}
+
+/* smtp_chat_resp - read and process SMTP server response */
+
+SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session)
+{
+ static SMTP_RESP rdata;
+ char *cp;
+ int last_char;
+ int three_digs = 0;
+ size_t len;
+ const char *new_reply;
+ int chat_append_flag;
+ int chat_append_skipped = 0;
+
+ /*
+ * Initialize the response data buffer.
+ */
+ if (rdata.str_buf == 0) {
+ rdata.dsn_buf = vstring_alloc(10);
+ rdata.str_buf = vstring_alloc(100);
+ }
+
+ /*
+ * Censor out non-printable characters in server responses. Concatenate
+ * multi-line server responses. Separate the status code from the text.
+ * Leave further parsing up to the application.
+ *
+ * We can't parse or store input that exceeds var_line_limit, so we just
+ * skip over it to simplify the remainder of the code below.
+ */
+ VSTRING_RESET(rdata.str_buf);
+ for (;;) {
+ last_char = smtp_get(session->buffer, session->stream, var_line_limit,
+ SMTP_GET_FLAG_SKIP);
+ /* XXX Update the per-line time limit. */
+ printable(STR(session->buffer), '?');
+ if (last_char != '\n')
+ msg_warn("%s: response longer than %d: %.30s...",
+ session->namaddrport, var_line_limit, STR(session->buffer));
+ if (msg_verbose)
+ msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer));
+
+ /*
+ * Defend against a denial of service attack by limiting the amount
+ * of multi-line text that we are willing to store.
+ */
+ chat_append_flag = (LEN(rdata.str_buf) < var_line_limit);
+ if (chat_append_flag)
+ smtp_chat_append(session, "In: ", STR(session->buffer));
+ else {
+ if (chat_append_skipped == 0)
+ msg_warn("%s: multi-line response longer than %d %.30s...",
+ session->namaddrport, var_line_limit, STR(rdata.str_buf));
+ if (chat_append_skipped < INT_MAX)
+ chat_append_skipped++;
+ }
+
+ /*
+ * Server reply substitution, for fault-injection testing, or for
+ * working around broken systems. Use with care.
+ */
+ if (smtp_chat_resp_filter != 0) {
+ new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer));
+ if (new_reply != 0) {
+ msg_info("%s: replacing server reply \"%s\" with \"%s\"",
+ session->namaddrport, STR(session->buffer), new_reply);
+ vstring_strcpy(session->buffer, new_reply);
+ if (chat_append_flag) {
+ smtp_chat_append(session, "Replaced-by: ", "");
+ smtp_chat_append(session, " ", new_reply);
+ }
+ } else if (smtp_chat_resp_filter->error != 0) {
+ msg_warn("%s: table %s:%s lookup error for %s",
+ session->state->request->queue_id,
+ smtp_chat_resp_filter->type,
+ smtp_chat_resp_filter->name,
+ printable(STR(session->buffer), '?'));
+ vstream_longjmp(session->stream, SMTP_ERR_DATA);
+ }
+ }
+ if (chat_append_flag) {
+ if (LEN(rdata.str_buf))
+ VSTRING_ADDCH(rdata.str_buf, '\n');
+ vstring_strcat(rdata.str_buf, STR(session->buffer));
+ }
+
+ /*
+ * Parse into code and text. Do not ignore garbage (see below).
+ */
+ for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++)
+ /* void */ ;
+ if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) {
+ if (*cp == '-')
+ continue;
+ if (*cp == ' ' || *cp == 0)
+ break;
+ }
+
+ /*
+ * XXX Do not simply ignore garbage in the server reply when ESMTP
+ * command pipelining is turned on. For example, after sending
+ * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a
+ * legitimate 2XX reply, Postfix recognizes the server's QUIT reply
+ * as the END-OF-DATA reply after garbage, causing mail to be lost.
+ *
+ * Without the ability to store per-domain status information in queue
+ * files, automatic workarounds are problematic:
+ *
+ * - Automatically deferring delivery creates a "repeated delivery"
+ * problem when garbage arrives after the DATA stage. Without the
+ * workaround, Postfix delivers only once.
+ *
+ * - Automatically deferring delivery creates a "no delivery" problem
+ * when the garbage arrives before the DATA stage. Without the
+ * workaround, mail might still get through.
+ *
+ * - Automatically turning off pipelining for delayed mail affects
+ * deliveries to correctly implemented servers, and may also affect
+ * delivery of large mailing lists.
+ *
+ * So we leave the decision with the administrator, but we don't force
+ * them to take action, like we would with automatic deferral. If
+ * loss of mail is not acceptable then they can turn off pipelining
+ * for specific sites, or they can turn off pipelining globally when
+ * they find that there are just too many broken sites.
+ */
+ session->error_mask |= MAIL_ERROR_PROTOCOL;
+ if (session->features & SMTP_FEATURE_PIPELINING) {
+ msg_warn("%s: non-%s response from %s: %.100s",
+ session->state->request->queue_id,
+ smtp_mode ? "ESMTP" : "LMTP",
+ session->namaddrport, STR(session->buffer));
+ if (var_helpful_warnings)
+ msg_warn("to prevent loss of mail, turn off command pipelining "
+ "for %s with the %s parameter",
+ STR(session->iterator->addr),
+ VAR_LMTP_SMTP(EHLO_DIS_MAPS));
+ }
+ }
+
+ /*
+ * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail
+ * code if none was given.
+ *
+ * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX
+ * replies, or codes whose initial digit is out of sync with the reply
+ * code.
+ *
+ * XXX Potential stability problem. In order to save memory, the queue
+ * manager stores DSNs in a compact manner:
+ *
+ * - empty strings are represented by null pointers,
+ *
+ * - the status and reason are required to be non-empty.
+ *
+ * Other Postfix daemons inherit this behavior, because they use the same
+ * DSN support code. This means that everything that receives DSNs must
+ * cope with null pointers for the optional DSN attributes, and that
+ * everything that provides DSN information must provide a non-empty
+ * status and reason, otherwise the DSN support code wil panic().
+ *
+ * Thus, when the remote server sends a malformed reply (or 3XX out of
+ * context) we should not panic() in DSN_COPY() just because we don't
+ * have a status. Robustness suggests that we supply a status here, and
+ * that we leave it up to the down-stream code to override the
+ * server-supplied status in case of an error we can't detect here, such
+ * as an out-of-order server reply.
+ */
+ VSTRING_TERMINATE(rdata.str_buf);
+ vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */
+ if (three_digs != 0) {
+ rdata.code = atoi(STR(session->buffer));
+ if (strchr("245", STR(session->buffer)[0]) != 0) {
+ for (cp = STR(session->buffer) + 4; *cp == ' '; cp++)
+ /* void */ ;
+ if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) {
+ vstring_strncpy(rdata.dsn_buf, cp, len);
+ } else {
+ vstring_strcpy(rdata.dsn_buf, "0.0.0");
+ STR(rdata.dsn_buf)[0] = STR(session->buffer)[0];
+ }
+ }
+ } else {
+ rdata.code = 0;
+ }
+ rdata.dsn = STR(rdata.dsn_buf);
+ rdata.str = STR(rdata.str_buf);
+ return (&rdata);
+}
+
+/* print_line - line_wrap callback */
+
+static void print_line(const char *str, int len, int indent, void *context)
+{
+ VSTREAM *notice = (VSTREAM *) context;
+
+ post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
+}
+
+/* smtp_chat_notify - notify postmaster */
+
+void smtp_chat_notify(SMTP_SESSION *session)
+{
+ const char *myname = "smtp_chat_notify";
+ VSTREAM *notice;
+ char **cpp;
+
+ /*
+ * Sanity checks.
+ */
+ if (session->history == 0)
+ msg_panic("%s: no conversation history", myname);
+ if (msg_verbose)
+ msg_info("%s: notify postmaster", myname);
+
+ /*
+ * Construct a message for the postmaster, explaining what this is all
+ * about. This is junk mail: don't send it when the mail posting service
+ * is unavailable, and use the double bounce sender address, to prevent
+ * mail bounce wars. Always prepend one space to message content that we
+ * generate from untrusted data.
+ */
+#define NULL_TRACE_FLAGS 0
+#define NO_QUEUE_ID ((VSTRING *) 0)
+#define LENGTH 78
+#define INDENT 4
+
+ notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ var_error_rcpt,
+ MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS,
+ SMTPUTF8_FLAG_NONE, NO_QUEUE_ID);
+ if (notice == 0) {
+ msg_warn("postmaster notify: %m");
+ return;
+ }
+ post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
+ mail_addr_mail_daemon());
+ post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
+ post_mail_fprintf(notice, "Subject: %s %s client: errors from %s",
+ var_mail_name, smtp_mode ? "SMTP" : "LMTP",
+ session->namaddrport);
+ post_mail_fputs(notice, "");
+ post_mail_fprintf(notice, "Unexpected response from %s.",
+ session->namaddrport);
+ post_mail_fputs(notice, "");
+ post_mail_fputs(notice, "Transcript of session follows.");
+ post_mail_fputs(notice, "");
+ argv_terminate(session->history);
+ for (cpp = session->history->argv; *cpp; cpp++)
+ line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
+ (void *) notice);
+ post_mail_fputs(notice, "");
+ post_mail_fprintf(notice, "For other details, see the local mail logfile");
+ (void) post_mail_fclose(notice);
+}
diff --git a/src/smtp/smtp_connect.c b/src/smtp/smtp_connect.c
new file mode 100644
index 0000000..aad48e4
--- /dev/null
+++ b/src/smtp/smtp_connect.c
@@ -0,0 +1,1206 @@
+/*++
+/* NAME
+/* smtp_connect 3
+/* SUMMARY
+/* connect to SMTP/LMTP server and deliver
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* int smtp_connect(state)
+/* SMTP_STATE *state;
+/* DESCRIPTION
+/* This module implements SMTP/LMTP connection management and controls
+/* mail delivery.
+/*
+/* smtp_connect() attempts to establish an SMTP/LMTP session with a host
+/* that represents the destination domain, or with an optional fallback
+/* relay when {the destination cannot be found, or when all the
+/* destination servers are unavailable}. It skips over IP addresses
+/* that fail to complete the SMTP/LMTP handshake and tries to find
+/* an alternate server when an SMTP/LMTP session fails to deliver.
+/*
+/* This layer also controls what connections are retrieved from
+/* the connection cache, and what connections are saved to the cache.
+/*
+/* The destination is either a host (or domain) name or a numeric
+/* address. Symbolic or numeric service port information may be
+/* appended, separated by a colon (":"). In the case of LMTP,
+/* destinations may be specified as "unix:pathname", "inet:host"
+/* or "inet:host:port".
+/*
+/* With SMTP, the Internet domain name service is queried for mail
+/* exchanger hosts. Quote the domain name with `[' and `]' to
+/* suppress mail exchanger lookups.
+/*
+/* Numerical address information should always be quoted with `[]'.
+/* DIAGNOSTICS
+/* The delivery status is the result value.
+/* SEE ALSO
+/* smtp_proto(3) SMTP client protocol
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Connection caching in cooperation with:
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#ifndef IPPORT_SMTP
+#define IPPORT_SMTP 25
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <split_at.h>
+#include <mymalloc.h>
+#include <inet_addr_list.h>
+#include <iostuff.h>
+#include <timed_connect.h>
+#include <stringops.h>
+#include <host_port.h>
+#include <sane_connect.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <own_inet_addr.h>
+#include <deliver_pass.h>
+#include <mail_error.h>
+#include <dsn_buf.h>
+#include <mail_addr.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific. */
+
+#include <smtp.h>
+#include <smtp_addr.h>
+#include <smtp_reuse.h>
+
+ /*
+ * Forward declaration.
+ */
+static SMTP_SESSION *smtp_connect_sock(int, struct sockaddr *, int,
+ SMTP_ITERATOR *, DSN_BUF *,
+ int);
+
+/* smtp_connect_unix - connect to UNIX-domain address */
+
+static SMTP_SESSION *smtp_connect_unix(SMTP_ITERATOR *iter, DSN_BUF *why,
+ int sess_flags)
+{
+ const char *myname = "smtp_connect_unix";
+ struct sockaddr_un sock_un;
+ const char *addr = STR(iter->addr);
+ int len = strlen(addr);
+ int sock;
+
+ dsb_reset(why); /* Paranoia */
+
+ /*
+ * Sanity checks.
+ */
+ if (len >= (int) sizeof(sock_un.sun_path)) {
+ msg_warn("unix-domain name too long: %s", addr);
+ dsb_simple(why, "4.3.5", "Server configuration error");
+ return (0);
+ }
+
+ /*
+ * Initialize.
+ */
+ memset((void *) &sock_un, 0, sizeof(sock_un));
+ sock_un.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sock_un.sun_len = len + 1;
+#endif
+ memcpy(sock_un.sun_path, addr, len + 1);
+
+ /*
+ * Create a client socket.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+
+ /*
+ * Connect to the server.
+ */
+ if (msg_verbose)
+ msg_info("%s: trying: %s...", myname, addr);
+
+ return (smtp_connect_sock(sock, (struct sockaddr *) &sock_un,
+ sizeof(sock_un), iter, why, sess_flags));
+}
+
+/* smtp_connect_addr - connect to explicit address */
+
+static SMTP_SESSION *smtp_connect_addr(SMTP_ITERATOR *iter, DSN_BUF *why,
+ int sess_flags)
+{
+ const char *myname = "smtp_connect_addr";
+ struct sockaddr_storage ss; /* remote */
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SOCKADDR_SIZE salen = sizeof(ss);
+ MAI_HOSTADDR_STR hostaddr;
+ DNS_RR *addr = iter->rr;
+ unsigned port = iter->port;
+ int sock;
+ char *bind_addr;
+ char *bind_var;
+
+ dsb_reset(why); /* Paranoia */
+
+ /*
+ * Sanity checks.
+ */
+ if (dns_rr_to_sa(addr, port, sa, &salen) != 0) {
+ msg_warn("%s: skip address type %s: %m",
+ myname, dns_strtype(addr->type));
+ dsb_simple(why, "4.4.0", "network address conversion failed: %m");
+ return (0);
+ }
+
+ /*
+ * Initialize.
+ */
+ if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+
+ if (inet_windowsize > 0)
+ set_inet_windowsize(sock, inet_windowsize);
+
+ /*
+ * Allow the sysadmin to specify the source address, for example, as "-o
+ * smtp_bind_address=x.x.x.x" in the master.cf file.
+ */
+#ifdef HAS_IPV6
+ if (sa->sa_family == AF_INET6) {
+ bind_addr = var_smtp_bind_addr6;
+ bind_var = VAR_LMTP_SMTP(BIND_ADDR6);
+ } else
+#endif
+ if (sa->sa_family == AF_INET) {
+ bind_addr = var_smtp_bind_addr;
+ bind_var = VAR_LMTP_SMTP(BIND_ADDR);
+ } else
+ bind_var = bind_addr = "";
+ if (*bind_addr) {
+ int aierr;
+ struct addrinfo *res0;
+
+ if ((aierr = hostaddr_to_sockaddr(bind_addr, (char *) 0, 0, &res0)) != 0)
+ msg_fatal("%s: bad %s parameter: %s: %s",
+ myname, bind_var, bind_addr, MAI_STRERROR(aierr));
+ if (bind(sock, res0->ai_addr, res0->ai_addrlen) < 0)
+ msg_warn("%s: bind %s: %m", myname, bind_addr);
+ else if (msg_verbose)
+ msg_info("%s: bind %s", myname, bind_addr);
+ freeaddrinfo(res0);
+ }
+
+ /*
+ * When running as a virtual host, bind to the virtual interface so that
+ * the mail appears to come from the "right" machine address.
+ *
+ * XXX The IPv6 patch expands the null host (as client endpoint) and uses
+ * the result as the loopback address list.
+ */
+ else {
+ int count = 0;
+ struct sockaddr *own_addr = 0;
+ INET_ADDR_LIST *addr_list = own_inet_addr_list();
+ struct sockaddr_storage *s;
+
+ for (s = addr_list->addrs; s < addr_list->addrs + addr_list->used; s++) {
+ if (SOCK_ADDR_FAMILY(s) == sa->sa_family) {
+ if (count++ > 0)
+ break;
+ own_addr = SOCK_ADDR_PTR(s);
+ }
+ }
+ if (count == 1 && !sock_addr_in_loopback(own_addr)) {
+ if (bind(sock, own_addr, SOCK_ADDR_LEN(own_addr)) < 0) {
+ SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_warn("%s: bind %s: %m", myname, hostaddr.buf);
+ } else if (msg_verbose) {
+ SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s: bind %s", myname, hostaddr.buf);
+ }
+ }
+ }
+
+ /*
+ * Connect to the server.
+ */
+ if (msg_verbose)
+ msg_info("%s: trying: %s[%s] port %d...",
+ myname, STR(iter->host), STR(iter->addr), ntohs(port));
+
+ return (smtp_connect_sock(sock, sa, salen, iter, why, sess_flags));
+}
+
+/* smtp_connect_sock - connect a socket over some transport */
+
+static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr *sa,
+ int salen,
+ SMTP_ITERATOR *iter,
+ DSN_BUF *why,
+ int sess_flags)
+{
+ int conn_stat;
+ int saved_errno;
+ VSTREAM *stream;
+ time_t start_time;
+ const char *name = STR(iter->host);
+ const char *addr = STR(iter->addr);
+ unsigned port = iter->port;
+
+ start_time = time((time_t *) 0);
+ if (var_smtp_conn_tmout > 0) {
+ non_blocking(sock, NON_BLOCKING);
+ conn_stat = timed_connect(sock, sa, salen, var_smtp_conn_tmout);
+ saved_errno = errno;
+ non_blocking(sock, BLOCKING);
+ errno = saved_errno;
+ } else {
+ conn_stat = sane_connect(sock, sa, salen);
+ }
+ if (conn_stat < 0) {
+ if (port)
+ dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m",
+ name, addr, ntohs(port));
+ else
+ dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr);
+ close(sock);
+ return (0);
+ }
+ stream = vstream_fdopen(sock, O_RDWR);
+
+ /*
+ * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE.
+ */
+ if (sa->sa_family == AF_INET
+#ifdef AF_INET6
+ || sa->sa_family == AF_INET6
+#endif
+ )
+ vstream_tweak_tcp(stream);
+
+ /*
+ * Bundle up what we have into a nice SMTP_SESSION object.
+ */
+ return (smtp_session_alloc(stream, iter, start_time, sess_flags));
+}
+
+/* smtp_parse_destination - parse host/port destination */
+
+static char *smtp_parse_destination(char *destination, char *def_service,
+ char **hostp, unsigned *portp)
+{
+ char *buf = mystrdup(destination);
+ char *service;
+ struct servent *sp;
+ char *protocol = "tcp"; /* XXX configurable? */
+ unsigned port;
+ const char *err;
+
+ if (msg_verbose)
+ msg_info("smtp_parse_destination: %s %s", destination, def_service);
+
+ /*
+ * Parse the host/port information. We're working with a copy of the
+ * destination argument so the parsing can be destructive.
+ */
+ if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0)
+ msg_fatal("%s in server description: %s", err, destination);
+
+ /*
+ * Convert service to port number, network byte order.
+ */
+ if (alldig(service)) {
+ if ((port = atoi(service)) >= 65536 || port == 0)
+ msg_fatal("bad network port in destination: %s", destination);
+ *portp = htons(port);
+ } else {
+ if ((sp = getservbyname(service, protocol)) == 0)
+ msg_fatal("unknown service: %s/%s", service, protocol);
+ *portp = sp->s_port;
+ }
+ return (buf);
+}
+
+/* smtp_cleanup_session - clean up after using a session */
+
+static void smtp_cleanup_session(SMTP_STATE *state)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ int throttled;
+
+ /*
+ * Inform the postmaster of trouble.
+ *
+ * XXX Don't send notifications about errors while sending notifications.
+ */
+#define POSSIBLE_NOTIFICATION(sender) \
+ (*sender == 0 || strcmp(sender, mail_addr_double_bounce()) == 0)
+
+ if (session->history != 0
+ && (session->error_mask & name_mask(VAR_NOTIFY_CLASSES,
+ mail_error_masks,
+ var_notify_classes)) != 0
+ && POSSIBLE_NOTIFICATION(request->sender) == 0)
+ smtp_chat_notify(session);
+
+ /*
+ * When session caching is enabled, cache the first good session for this
+ * delivery request under the next-hop destination, and cache all good
+ * sessions under their server network address (destroying the session in
+ * the process).
+ *
+ * Caching under the next-hop destination name (rather than the fall-back
+ * destination) allows us to skip over non-responding primary or backup
+ * hosts. In fact, this is the only benefit of caching logical to
+ * physical bindings; caching a session under its own hostname provides
+ * no performance benefit, given the way smtp_connect() works.
+ */
+ throttled = THIS_SESSION_IS_THROTTLED; /* smtp_quit() may fail */
+ if (THIS_SESSION_IS_EXPIRED)
+ smtp_quit(state); /* also disables caching */
+ if (THIS_SESSION_IS_CACHED
+ /* Redundant tests for safety... */
+ && vstream_ferror(session->stream) == 0
+ && vstream_feof(session->stream) == 0) {
+ smtp_save_session(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL,
+ SMTP_KEY_MASK_SCACHE_ENDP_LABEL);
+ } else {
+ smtp_session_free(session);
+ }
+ state->session = 0;
+
+ /*
+ * If this session was good, reset the scache next-hop destination, so
+ * that we won't cache connections to less-preferred servers under the
+ * same next-hop destination. Otherwise we could end up skipping over the
+ * available and more-preferred servers.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state) && !throttled)
+ CLEAR_SCACHE_REQUEST_NEXTHOP(state);
+
+ /*
+ * Clean up the lists with todo and dropped recipients.
+ */
+ smtp_rcpt_cleanup(state);
+
+ /*
+ * Reset profiling info.
+ *
+ * XXX When one delivery request results in multiple sessions, the set-up
+ * and transmission latencies of the earlier sessions will count as
+ * connection set-up time for the later sessions.
+ *
+ * XXX On the other hand, when we first try to connect to one or more dead
+ * hosts before we reach a good host, then all that time must be counted
+ * as connection set-up time for the session with the good host.
+ *
+ * XXX So this set-up attribution problem exists only when we actually
+ * engage in a session, spend a lot of time delivering a message, find
+ * that it fails, and then connect to an alternate host.
+ */
+ memset((void *) &request->msg_stats.conn_setup_done, 0,
+ sizeof(request->msg_stats.conn_setup_done));
+ memset((void *) &request->msg_stats.deliver_done, 0,
+ sizeof(request->msg_stats.deliver_done));
+ request->msg_stats.reuse_count = 0;
+}
+
+static void smtp_cache_policy(SMTP_STATE *state, const char *dest)
+{
+ DELIVER_REQUEST *request = state->request;
+
+ state->misc_flags &= ~SMTP_MISC_FLAG_CONN_CACHE_MASK;
+
+ if (smtp_cache_dest && string_list_match(smtp_cache_dest, dest)) {
+ state->misc_flags |= SMTP_MISC_FLAG_CONN_CACHE_MASK;
+ } else if (var_smtp_cache_demand) {
+ if (request->flags & DEL_REQ_FLAG_CONN_LOAD)
+ state->misc_flags |= SMTP_MISC_FLAG_CONN_LOAD;
+ if (request->flags & DEL_REQ_FLAG_CONN_STORE)
+ state->misc_flags |= SMTP_MISC_FLAG_CONN_STORE;
+ }
+}
+
+/* smtp_connect_local - connect to local server */
+
+static void smtp_connect_local(SMTP_STATE *state, const char *path)
+{
+ const char *myname = "smtp_connect_local";
+ SMTP_ITERATOR *iter = state->iterator;
+ SMTP_SESSION *session;
+ DSN_BUF *why = state->why;
+
+ /*
+ * Do not silently ignore an unused setting.
+ */
+ if (*var_fallback_relay)
+ msg_warn("ignoring \"%s = %s\" setting for non-TCP connections",
+ VAR_LMTP_FALLBACK, var_fallback_relay);
+
+ /*
+ * It's too painful to weave this code into the SMTP connection
+ * management routine.
+ *
+ * Connection cache management is based on the UNIX-domain pathname, without
+ * the "unix:" prefix.
+ */
+ smtp_cache_policy(state, path);
+ if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK)
+ SET_SCACHE_REQUEST_NEXTHOP(state, path);
+
+ /*
+ * Here we ensure that the iter->addr member refers to a copy of the
+ * UNIX-domain pathname, so that smtp_save_session() will cache the
+ * connection using the pathname as the physical endpoint name.
+ *
+ * We set dest=path for backwards compatibility.
+ */
+#define NO_PORT 0
+
+ SMTP_ITER_INIT(iter, path, var_myhostname, path, NO_PORT, state);
+
+ /*
+ * Opportunistic TLS for unix domain sockets does not make much sense,
+ * since the channel is private, mere encryption without authentication
+ * is just wasted cycles and opportunity for breakage. Since we are not
+ * willing to retry after TLS handshake failures here, we downgrade "may"
+ * no "none". Nothing is lost, and much waste is avoided.
+ *
+ * We don't know who is authenticating whom, so if a client cert is
+ * available, "encrypt" may be a sensible policy. Otherwise, we also
+ * downgrade "encrypt" to "none", this time just to avoid waste.
+ *
+ * We use smtp_reuse_nexthop() instead of smtp_reuse_addr(), so that we can
+ * reuse a SASL-authenticated connection (however unlikely this scenario
+ * may be). The smtp_reuse_addr() interface currently supports only reuse
+ * of SASL-unauthenticated connections.
+ */
+#ifdef USE_TLS
+ if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
+ msg_warn("TLS policy lookup error for %s/%s: %s",
+ STR(iter->host), STR(iter->addr), STR(why->reason));
+ return;
+ }
+#endif
+ if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
+ || (session = smtp_reuse_nexthop(state,
+ SMTP_KEY_MASK_SCACHE_DEST_LABEL)) == 0)
+ session = smtp_connect_unix(iter, why, state->misc_flags);
+ if ((state->session = session) != 0) {
+ session->state = state;
+#ifdef USE_TLS
+ session->tls_nexthop = var_myhostname; /* for TLS_LEV_SECURE */
+ if (state->tls->level == TLS_LEV_MAY) {
+ msg_warn("%s: opportunistic TLS encryption is not appropriate "
+ "for unix-domain destinations.", myname);
+ state->tls->level = TLS_LEV_NONE;
+ }
+#endif
+ /* All delivery errors bounce or defer. */
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+
+ /*
+ * When a TLS handshake fails, the stream is marked "dead" to avoid
+ * further I/O over a broken channel.
+ */
+ if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
+ && smtp_helo(state) != 0) {
+ if (!THIS_SESSION_IS_FORBIDDEN
+ && vstream_ferror(session->stream) == 0
+ && vstream_feof(session->stream) == 0)
+ smtp_quit(state);
+ } else {
+ smtp_xfer(state);
+ }
+
+ /*
+ * With opportunistic TLS disabled we don't expect to be asked to
+ * retry connections without TLS, and so we expect the final server
+ * flag to stay on.
+ */
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0)
+ msg_panic("%s: unix-domain destination not final!", myname);
+ smtp_cleanup_session(state);
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state))
+ CLEAR_SCACHE_REQUEST_NEXTHOP(state);
+}
+
+/* smtp_scrub_address_list - delete all cached addresses from list */
+
+static void smtp_scrub_addr_list(HTABLE *cached_addr, DNS_RR **addr_list)
+{
+ MAI_HOSTADDR_STR hostaddr;
+ DNS_RR *addr;
+ DNS_RR *next;
+
+ /*
+ * XXX Extend the DNS_RR structure with fields for the printable address
+ * and/or binary sockaddr representations, so that we can avoid repeated
+ * binary->string transformations for the same address.
+ */
+ for (addr = *addr_list; addr; addr = next) {
+ next = addr->next;
+ if (dns_rr_to_pa(addr, &hostaddr) == 0) {
+ msg_warn("cannot convert type %s record to printable address",
+ dns_strtype(addr->type));
+ continue;
+ }
+ if (htable_locate(cached_addr, hostaddr.buf))
+ *addr_list = dns_rr_remove(*addr_list, addr);
+ }
+}
+
+/* smtp_update_addr_list - common address list update */
+
+static void smtp_update_addr_list(DNS_RR **addr_list, const char *server_addr,
+ int session_count)
+{
+ DNS_RR *addr;
+ DNS_RR *next;
+ int aierr;
+ struct addrinfo *res0;
+
+ if (*addr_list == 0)
+ return;
+
+ /*
+ * Truncate the address list if we are not going to use it anyway.
+ */
+ if (session_count == var_smtp_mxsess_limit
+ || session_count == var_smtp_mxaddr_limit) {
+ dns_rr_free(*addr_list);
+ *addr_list = 0;
+ return;
+ }
+
+ /*
+ * Convert server address to internal form, and look it up in the address
+ * list.
+ *
+ * XXX smtp_reuse_session() breaks if we remove two or more adjacent list
+ * elements but do not truncate the list to zero length.
+ *
+ * XXX Extend the SMTP_SESSION structure with sockaddr information so that
+ * we can avoid repeated string->binary transformations for the same
+ * address.
+ */
+ if ((aierr = hostaddr_to_sockaddr(server_addr, (char *) 0, 0, &res0)) != 0) {
+ msg_warn("hostaddr_to_sockaddr %s: %s",
+ server_addr, MAI_STRERROR(aierr));
+ } else {
+ for (addr = *addr_list; addr; addr = next) {
+ next = addr->next;
+ if (DNS_RR_EQ_SA(addr, (struct sockaddr *) res0->ai_addr)) {
+ *addr_list = dns_rr_remove(*addr_list, addr);
+ break;
+ }
+ }
+ freeaddrinfo(res0);
+ }
+}
+
+/* smtp_reuse_session - try to use existing connection, return session count */
+
+static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
+ int domain_best_pref)
+{
+ int session_count = 0;
+ DNS_RR *addr;
+ DNS_RR *next;
+ MAI_HOSTADDR_STR hostaddr;
+ SMTP_SESSION *session;
+ SMTP_ITERATOR *iter = state->iterator;
+ DSN_BUF *why = state->why;
+
+ /*
+ * First, search the cache by delivery request nexthop. We truncate the
+ * server address list when all the sessions for this destination are
+ * used up, to reduce the number of variables that need to be checked
+ * later.
+ *
+ * Note: connection reuse by delivery request nexthop restores the "best MX"
+ * bit.
+ *
+ * smtp_reuse_nexthop() clobbers the iterators's "dest" attribute. We save
+ * and restore it here, so that subsequent connections will use the
+ * proper nexthop information.
+ *
+ * We don't use TLS level info for nexthop-based connection cache storage
+ * keys. The combination of (service, nexthop, etc.) should be stable
+ * over the time range of interest, and the policy is still enforced on
+ * an individual connection to an MX host, before that connection is
+ * stored under a nexthop- or host-based storage key.
+ */
+#ifdef USE_TLS
+ smtp_tls_policy_dummy(state->tls);
+#endif
+ SMTP_ITER_SAVE_DEST(state->iterator);
+ if (*addr_list && SMTP_RCPT_LEFT(state) > 0
+ && HAVE_SCACHE_REQUEST_NEXTHOP(state)
+ && (session = smtp_reuse_nexthop(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL)) != 0) {
+ session_count = 1;
+ smtp_update_addr_list(addr_list, STR(iter->addr), session_count);
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ && *addr_list == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+ smtp_xfer(state);
+ smtp_cleanup_session(state);
+ }
+ SMTP_ITER_RESTORE_DEST(state->iterator);
+
+ /*
+ * Second, search the cache by primary MX address. Again, we use address
+ * list truncation so that we have to check fewer variables later.
+ *
+ * XXX This loop is safe because smtp_update_addr_list() either truncates
+ * the list to zero length, or removes at most one list element.
+ *
+ * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated
+ * connections. Furthermore, we rely on smtp_reuse_addr() to look up an
+ * existing SASL-unauthenticated connection only when a new connection
+ * would be guaranteed not to require SASL authentication.
+ *
+ * In addition, we rely on smtp_reuse_addr() to look up an existing
+ * plaintext connection only when a new connection would be guaranteed
+ * not to use TLS.
+ *
+ * For more precise control over reuse, the iterator should look up SASL and
+ * TLS policy as it evaluates mail exchangers in order, instead of
+ * relying on duplicate lookup request code in smtp_reuse(3) and
+ * smtp_session(3).
+ */
+ for (addr = *addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) {
+ if (addr->pref != domain_best_pref)
+ break;
+ next = addr->next;
+ if (dns_rr_to_pa(addr, &hostaddr) == 0) {
+ msg_warn("cannot convert type %s record to printable address",
+ dns_strtype(addr->type));
+ /* XXX Assume there is no code at the end of this loop. */
+ continue;
+ }
+ vstring_strcpy(iter->addr, hostaddr.buf);
+ vstring_strcpy(iter->host, SMTP_HNAME(addr));
+ iter->rr = addr;
+#ifdef USE_TLS
+ if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
+ msg_warn("TLS policy lookup error for %s/%s: %s",
+ STR(iter->dest), STR(iter->host), STR(why->reason));
+ continue;
+ /* XXX Assume there is no code at the end of this loop. */
+ }
+#endif
+ if ((session = smtp_reuse_addr(state,
+ SMTP_KEY_MASK_SCACHE_ENDP_LABEL)) != 0) {
+ session->features |= SMTP_FEATURE_BEST_MX;
+ session_count += 1;
+ smtp_update_addr_list(addr_list, STR(iter->addr), session_count);
+ if (*addr_list == 0)
+ next = 0;
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ && next == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+ smtp_xfer(state);
+ smtp_cleanup_session(state);
+ }
+ }
+ return (session_count);
+}
+
+/* smtp_connect_inet - establish network connection */
+
+static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
+ char *def_service)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_ITERATOR *iter = state->iterator;
+ ARGV *sites;
+ char *dest;
+ char **cpp;
+ int non_fallback_sites;
+ int retry_plain = 0;
+ DSN_BUF *why = state->why;
+
+ /*
+ * For sanity, require that at least one of INET or INET6 is enabled.
+ * Otherwise, we can't look up interface information, and we can't
+ * convert names or addresses.
+ */
+ if (inet_proto_info()->ai_family_list[0] == 0) {
+ dsb_simple(why, "4.4.4", "all network protocols are disabled");
+ return;
+ }
+
+ /*
+ * Future proofing: do a null destination sanity check in case we allow
+ * the primary destination to be a list (it could be just separators).
+ */
+ sites = argv_alloc(1);
+ argv_add(sites, nexthop, (char *) 0);
+ if (sites->argc == 0)
+ msg_panic("null destination: \"%s\"", nexthop);
+ non_fallback_sites = sites->argc;
+ argv_split_append(sites, var_fallback_relay, CHARS_COMMA_SP);
+
+ /*
+ * Don't give up after a hard host lookup error until we have tried the
+ * fallback relay servers.
+ *
+ * Don't bounce mail after a host lookup problem with a relayhost or with a
+ * fallback relay.
+ *
+ * Don't give up after a qualifying soft error until we have tried all
+ * qualifying backup mail servers.
+ *
+ * All this means that error handling and error reporting depends on whether
+ * the error qualifies for trying to deliver to a backup mail server, or
+ * whether we're looking up a relayhost or fallback relay. The challenge
+ * then is to build this into the pre-existing SMTP client without
+ * getting lost in the complexity.
+ */
+#define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \
+ (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites))
+
+ for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP);
+ SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0;
+ cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
+ char *dest_buf;
+ char *domain;
+ unsigned port;
+ DNS_RR *addr_list;
+ DNS_RR *addr;
+ DNS_RR *next;
+ int addr_count;
+ int sess_count;
+ SMTP_SESSION *session;
+ int lookup_mx;
+ unsigned domain_best_pref;
+ MAI_HOSTADDR_STR hostaddr;
+
+ if (cpp[1] == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
+
+ /*
+ * Parse the destination. If no TCP port is specified, use the port
+ * that is reserved for the protocol (SMTP or LMTP).
+ */
+ dest_buf = smtp_parse_destination(dest, def_service, &domain, &port);
+ if (var_helpful_warnings && var_smtp_tls_wrappermode == 0
+ && ntohs(port) == 465) {
+ msg_info("SMTPS wrappermode (TCP port 465) requires setting "
+ "\"%s = yes\", and \"%s = encrypt\" (or stronger)",
+ VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL));
+ }
+#define NO_HOST "" /* safety */
+#define NO_ADDR "" /* safety */
+
+ SMTP_ITER_INIT(iter, dest, NO_HOST, NO_ADDR, port, state);
+
+ /*
+ * Resolve an SMTP or LMTP server. In the case of SMTP, skip mail
+ * exchanger lookups when a quoted host is specified or when DNS
+ * lookups are disabled.
+ */
+ if (msg_verbose)
+ msg_info("connecting to %s port %d", domain, ntohs(port));
+ if (smtp_mode) {
+ if (ntohs(port) == IPPORT_SMTP)
+ state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT;
+ else
+ state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT;
+ lookup_mx = (smtp_dns_support != SMTP_DNS_DISABLED && *dest != '[');
+ } else
+ lookup_mx = 0;
+ if (!lookup_mx) {
+ addr_list = smtp_host_addr(domain, state->misc_flags, why);
+ /* XXX We could be an MX host for this destination... */
+ } else {
+ int i_am_mx = 0;
+
+ addr_list = smtp_domain_addr(domain, &iter->mx, state->misc_flags,
+ why, &i_am_mx);
+ /* If we're MX host, don't connect to non-MX backups. */
+ if (i_am_mx)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
+ }
+
+ /*
+ * Don't try fall-back hosts if mail loops to myself. That would just
+ * make the problem worse.
+ */
+ if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why))
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
+
+ /*
+ * No early loop exit or we have a memory leak with dest_buf.
+ */
+ if (addr_list)
+ domain_best_pref = addr_list->pref;
+
+ /*
+ * When connection caching is enabled, store the first good
+ * connection for this delivery request under the delivery request
+ * next-hop name. Good connections will also be stored under their
+ * specific server IP address.
+ *
+ * XXX smtp_session_cache_destinations specifies domain names without
+ * :port, because : is already used for maptype:mapname. Because of
+ * this limitation we use the bare domain without the optional [] or
+ * non-default TCP port.
+ *
+ * Opportunistic (a.k.a. on-demand) session caching on request by the
+ * queue manager. This is turned temporarily when a destination has a
+ * high volume of mail in the active queue. When the surge reaches
+ * its end, the queue manager requests that connections be retrieved
+ * but not stored.
+ */
+ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
+ smtp_cache_policy(state, domain);
+ if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK)
+ SET_SCACHE_REQUEST_NEXTHOP(state, dest);
+ }
+
+ /*
+ * Delete visited cached hosts from the address list.
+ *
+ * Optionally search the connection cache by domain name or by primary
+ * MX address before we try to create new connections.
+ *
+ * Enforce the MX session and MX address counts per next-hop or
+ * fall-back destination. smtp_reuse_session() will truncate the
+ * address list when either limit is reached.
+ */
+ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) {
+ if (state->cache_used->used > 0)
+ smtp_scrub_addr_list(state->cache_used, &addr_list);
+ sess_count = addr_count =
+ smtp_reuse_session(state, &addr_list, domain_best_pref);
+ } else
+ sess_count = addr_count = 0;
+
+ /*
+ * Connect to an SMTP server: create primary MX connections, and
+ * reuse or create backup MX connections.
+ *
+ * At the start of an SMTP session, all recipients are unmarked. In the
+ * course of an SMTP session, recipients are marked as KEEP (deliver
+ * to alternate mail server) or DROP (remove from recipient list). At
+ * the end of an SMTP session, weed out the recipient list. Unmark
+ * any left-over recipients and try to deliver them to a backup mail
+ * server.
+ *
+ * Cache the first good session under the next-hop destination name.
+ * Cache all good sessions under their physical endpoint.
+ *
+ * Don't query the session cache for primary MX hosts. We already did
+ * that in smtp_reuse_session(), and if any were found in the cache,
+ * they were already deleted from the address list.
+ *
+ * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated
+ * connections. Furthermore, we rely on smtp_reuse_addr() to look up
+ * an existing SASL-unauthenticated connection only when a new
+ * connection would be guaranteed not to require SASL authentication.
+ *
+ * In addition, we rely on smtp_reuse_addr() to look up an existing
+ * plaintext connection only when a new connection would be
+ * guaranteed not to use TLS.
+ */
+ for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) {
+ next = addr->next;
+ if (++addr_count == var_smtp_mxaddr_limit)
+ next = 0;
+ if (dns_rr_to_pa(addr, &hostaddr) == 0) {
+ msg_warn("cannot convert type %s record to printable address",
+ dns_strtype(addr->type));
+ /* XXX Assume there is no code at the end of this loop. */
+ continue;
+ }
+ vstring_strcpy(iter->addr, hostaddr.buf);
+ vstring_strcpy(iter->host, SMTP_HNAME(addr));
+ iter->rr = addr;
+#ifdef USE_TLS
+ if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
+ msg_warn("TLS policy lookup for %s/%s: %s",
+ STR(iter->dest), STR(iter->host), STR(why->reason));
+ continue;
+ /* XXX Assume there is no code at the end of this loop. */
+ }
+ if (var_smtp_tls_wrappermode
+ && state->tls->level < TLS_LEV_ENCRYPT) {
+ msg_warn("%s requires \"%s = encrypt\" (or stronger)",
+ VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL));
+ continue;
+ /* XXX Assume there is no code at the end of this loop. */
+ }
+ /* Disable TLS when retrying after a handshake failure */
+ if (retry_plain) {
+ state->tls->level = TLS_LEV_NONE;
+ retry_plain = 0;
+ }
+#endif
+ if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
+ || addr->pref == domain_best_pref
+ || !(session = smtp_reuse_addr(state,
+ SMTP_KEY_MASK_SCACHE_ENDP_LABEL)))
+ session = smtp_connect_addr(iter, why, state->misc_flags);
+ if ((state->session = session) != 0) {
+ session->state = state;
+#ifdef USE_TLS
+ session->tls_nexthop = domain;
+#endif
+ if (addr->pref == domain_best_pref)
+ session->features |= SMTP_FEATURE_BEST_MX;
+ /* Don't count handshake errors towards the session limit. */
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ && next == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+ if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
+ && smtp_helo(state) != 0) {
+#ifdef USE_TLS
+
+ /*
+ * When an opportunistic TLS handshake fails, try the
+ * same address again, with TLS disabled. See also the
+ * RETRY_AS_PLAINTEXT macro.
+ */
+ if ((retry_plain = session->tls_retry_plain) != 0) {
+ --addr_count;
+ next = addr;
+ }
+#endif
+
+ /*
+ * When a TLS handshake fails, the stream is marked
+ * "dead" to avoid further I/O over a broken channel.
+ */
+ if (!THIS_SESSION_IS_FORBIDDEN
+ && vstream_ferror(session->stream) == 0
+ && vstream_feof(session->stream) == 0)
+ smtp_quit(state);
+ } else {
+ /* Do count delivery errors towards the session limit. */
+ if (++sess_count == var_smtp_mxsess_limit)
+ next = 0;
+ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ && next == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
+ smtp_xfer(state);
+#ifdef USE_TLS
+
+ /*
+ * When opportunistic TLS fails after the STARTTLS
+ * handshake, try the same address again, with TLS
+ * disabled. See also the RETRY_AS_PLAINTEXT macro.
+ */
+ if ((retry_plain = session->tls_retry_plain) != 0) {
+ --sess_count;
+ --addr_count;
+ next = addr;
+ }
+#endif
+ }
+ smtp_cleanup_session(state);
+ } else {
+ /* The reason already includes the IP address and TCP port. */
+ msg_info("%s", STR(why->reason));
+ }
+ /* XXX Code above assumes there is no code at this loop ending. */
+ }
+ dns_rr_free(addr_list);
+ if (iter->mx) {
+ dns_rr_free(iter->mx);
+ iter->mx = 0; /* Just in case */
+ }
+ myfree(dest_buf);
+ if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
+ break;
+ }
+
+ /*
+ * We still need to deliver, bounce or defer some left-over recipients:
+ * either mail loops or some backup mail server was unavailable.
+ */
+ if (SMTP_RCPT_LEFT(state) > 0) {
+
+ /*
+ * In case of a "no error" indication we make up an excuse: we did
+ * find the host address, but we did not attempt to connect to it.
+ * This can happen when the fall-back relay was already tried via a
+ * cached connection, so that the address list scrubber left behind
+ * an empty list.
+ */
+ if (!SMTP_HAS_DSN(why)) {
+ dsb_simple(why, "4.3.0",
+ "server unavailable or unable to receive mail");
+ }
+
+ /*
+ * Pay attention to what could be configuration problems, and pretend
+ * that these are recoverable rather than bouncing the mail.
+ */
+ else if (!SMTP_HAS_SOFT_DSN(why)) {
+
+ /*
+ * The fall-back destination did not resolve as expected, or it
+ * is refusing to talk to us, or mail for it loops back to us.
+ */
+ if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) {
+ msg_warn("%s configuration problem", VAR_SMTP_FALLBACK);
+ vstring_strcpy(why->status, "4.3.5");
+ /* XXX Keep the diagnostic code and MTA. */
+ }
+
+ /*
+ * The next-hop relayhost did not resolve as expected, or it is
+ * refusing to talk to us, or mail for it loops back to us.
+ *
+ * XXX There is no equivalent safety net for mis-configured
+ * sender-dependent relay hosts. The trivial-rewrite resolver
+ * would have to flag the result, and the queue manager would
+ * have to provide that information to delivery agents.
+ */
+ else if (smtp_mode && strcmp(sites->argv[0], var_relayhost) == 0) {
+ msg_warn("%s configuration problem", VAR_RELAYHOST);
+ vstring_strcpy(why->status, "4.3.5");
+ /* XXX Keep the diagnostic code and MTA. */
+ }
+
+ /*
+ * Mail for the next-hop destination loops back to myself. Pass
+ * the mail to the best_mx_transport or bounce it.
+ */
+ else if (smtp_mode && SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) {
+ dsb_reset(why); /* XXX */
+ state->status = deliver_pass_all(MAIL_CLASS_PRIVATE,
+ var_bestmx_transp,
+ request);
+ SMTP_RCPT_LEFT(state) = 0; /* XXX */
+ }
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state))
+ CLEAR_SCACHE_REQUEST_NEXTHOP(state);
+ argv_free(sites);
+}
+
+/* smtp_connect - establish SMTP connection */
+
+int smtp_connect(SMTP_STATE *state)
+{
+ DELIVER_REQUEST *request = state->request;
+ char *destination = request->nexthop;
+
+ /*
+ * All deliveries proceed along the same lines, whether they are over TCP
+ * or UNIX-domain sockets, and whether they use SMTP or LMTP: get a
+ * connection from the cache or create a new connection; deliver mail;
+ * update the connection cache or disconnect.
+ *
+ * The major differences appear at a higher level: the expansion from
+ * destination to address list, and whether to stop before we reach the
+ * end of that list.
+ */
+
+ /*
+ * With LMTP we have direct-to-host delivery only. The destination may
+ * have multiple IP addresses.
+ */
+ if (!smtp_mode) {
+ if (strncmp(destination, "unix:", 5) == 0) {
+ smtp_connect_local(state, destination + 5);
+ } else {
+ if (strncmp(destination, "inet:", 5) == 0)
+ destination += 5;
+ smtp_connect_inet(state, destination, var_smtp_tcp_port);
+ }
+ }
+
+ /*
+ * XXX We don't add support for "unix:" or "inet:" prefixes in SMTP
+ * destinations, because that would break compatibility with existing
+ * Postfix configurations that have a host with such a name.
+ */
+ else {
+ smtp_connect_inet(state, destination, var_smtp_tcp_port);
+ }
+
+ /*
+ * We still need to bounce or defer some left-over recipients: either
+ * (SMTP) mail loops or some server was unavailable.
+ *
+ * We could avoid this (and the "final server" complexity) by keeping one
+ * DSN structure per recipient in memory, by updating those in-memory
+ * structures with each delivery attempt, and by always flushing all
+ * deferred recipients at the end. We'd probably still want to bounce
+ * recipients immediately, so we'd end up with another chunk of code for
+ * defer logging only.
+ */
+ if (SMTP_RCPT_LEFT(state) > 0) {
+ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; /* XXX */
+ smtp_sess_fail(state);
+
+ /*
+ * Sanity check. Don't silently lose recipients.
+ */
+ smtp_rcpt_cleanup(state);
+ if (SMTP_RCPT_LEFT(state) > 0)
+ msg_panic("smtp_connect: left-over recipients");
+ }
+ return (state->status);
+}
diff --git a/src/smtp/smtp_key.c b/src/smtp/smtp_key.c
new file mode 100644
index 0000000..643f4db
--- /dev/null
+++ b/src/smtp/smtp_key.c
@@ -0,0 +1,215 @@
+/*++
+/* NAME
+/* smtp_key 3
+/* SUMMARY
+/* cache/table lookup key management
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* char *smtp_key_prefix(buffer, delim_na, iterator, context_flags)
+/* VSTRING *buffer;
+/* const char *delim_na;
+/* SMTP_ITERATOR *iterator;
+/* int context_flags;
+/* DESCRIPTION
+/* The Postfix SMTP server accesses caches and lookup tables,
+/* using lookup keys that contain information from various
+/* contexts: per-server configuration, per-request envelope,
+/* and results from DNS queries.
+/*
+/* These lookup keys sometimes share the same context information.
+/* The primary purpose of this API is to ensure that this
+/* shared context is used consistently, and that its use is
+/* made explicit (both are needed to verify that there is no
+/* false cache sharing).
+/*
+/* smtp_key_prefix() constructs a lookup key prefix from context
+/* that may be shared with other lookup keys. The user is free
+/* to append additional application-specific context. The result
+/* value is a pointer to the result text.
+/*
+/* Arguments:
+/* .IP buffer
+/* Storage for the result.
+/* .IP delim_na
+/* The field delimiter character, and the optional place holder
+/* character for a) information that is unavailable, b)
+/* information that is inapplicable, or c) that would result
+/* in an empty field. Key fields that contain "delim_na"
+/* characters will be base64-encoded.
+/* Do not specify "delim_na" characters that are part of the
+/* base64 character set.
+/* .IP iterator
+/* Information that will be selected by the specified flags.
+/* .IP context_flags
+/* Bit-wise OR of one or more of the following.
+/* .RS
+/* .IP SMTP_KEY_FLAG_SERVICE
+/* The global service name. This is a proxy for
+/* destination-independent and request-independent context.
+/* .IP SMTP_KEY_FLAG_SENDER
+/* The envelope sender address. This is a proxy for sender-dependent
+/* context, such as per-sender SASL authentication.
+/* .IP SMTP_KEY_FLAG_REQ_NEXTHOP
+/* The delivery request nexthop destination, including optional
+/* [] and :port (the same form that users specify in a SASL
+/* password or TLS policy lookup table). This is a proxy for
+/* destination-dependent, but host-independent context.
+/* .IP SMTP_KEY_FLAG_CUR_NEXTHOP
+/* The current iterator's nexthop destination (delivery request
+/* nexthop or fallback nexthop, including optional [] and
+/* :port).
+/* .IP SMTP_KEY_FLAG_HOSTNAME
+/* The current iterator's remote hostname.
+/* .IP SMTP_KEY_FLAG_ADDR
+/* The current iterator's remote address.
+/* .IP SMTP_KEY_FLAG_PORT
+/* The current iterator's remote port.
+/* .RE
+/* DIAGNOSTICS
+/* Panic: undefined flag or zero flags. Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <netinet/in.h> /* ntohs() for Solaris or BSD */
+#include <arpa/inet.h> /* ntohs() for Linux or BSD */
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <vstring.h>
+#include <base64_code.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+
+ /*
+ * Application-specific.
+ */
+#include <smtp.h>
+
+ /*
+ * We use a configurable field terminator and optional place holder for data
+ * that is unavailable or inapplicable. We base64-encode content that
+ * contains these characters, and content that needs obfuscation.
+ */
+
+/* smtp_key_append_na - append place-holder key field */
+
+static void smtp_key_append_na(VSTRING *buffer, const char *delim_na)
+{
+ if (delim_na[1] != 0)
+ VSTRING_ADDCH(buffer, delim_na[1]);
+ VSTRING_ADDCH(buffer, delim_na[0]);
+}
+
+/* smtp_key_append_str - append string-valued key field */
+
+static void smtp_key_append_str(VSTRING *buffer, const char *str,
+ const char *delim_na)
+{
+ if (str == 0 || str[0] == 0) {
+ smtp_key_append_na(buffer, delim_na);
+ } else if (str[strcspn(str, delim_na)] != 0) {
+ base64_encode_opt(buffer, str, strlen(str), BASE64_FLAG_APPEND);
+ VSTRING_ADDCH(buffer, delim_na[0]);
+ } else {
+ vstring_sprintf_append(buffer, "%s%c", str, delim_na[0]);
+ }
+}
+
+/* smtp_key_append_uint - append unsigned-valued key field */
+
+static void smtp_key_append_uint(VSTRING *buffer, unsigned num,
+ const char *delim_na)
+{
+ vstring_sprintf_append(buffer, "%u%c", num, delim_na[0]);
+}
+
+/* smtp_key_prefix - format common elements in lookup key */
+
+char *smtp_key_prefix(VSTRING *buffer, const char *delim_na,
+ SMTP_ITERATOR *iter, int flags)
+{
+ static const char myname[] = "smtp_key_prefix";
+ SMTP_STATE *state = iter->parent; /* private member */
+
+ /*
+ * Sanity checks.
+ */
+ if (state == 0)
+ msg_panic("%s: no parent state", myname);
+ if (flags & ~SMTP_KEY_MASK_ALL)
+ msg_panic("%s: unknown key flags 0x%x",
+ myname, flags & ~SMTP_KEY_MASK_ALL);
+ if (flags == 0)
+ msg_panic("%s: zero flags", myname);
+
+ /*
+ * Initialize.
+ */
+ VSTRING_RESET(buffer);
+
+ /*
+ * Per-service and per-request context.
+ */
+ if (flags & SMTP_KEY_FLAG_SERVICE)
+ smtp_key_append_str(buffer, state->service, delim_na);
+ if (flags & SMTP_KEY_FLAG_SENDER)
+ smtp_key_append_str(buffer, state->request->sender, delim_na);
+
+ /*
+ * Per-destination context, non-canonicalized form.
+ */
+ if (flags & SMTP_KEY_FLAG_REQ_NEXTHOP)
+ smtp_key_append_str(buffer, STR(iter->request_nexthop), delim_na);
+ if (flags & SMTP_KEY_FLAG_CUR_NEXTHOP)
+ smtp_key_append_str(buffer, STR(iter->dest), delim_na);
+
+ /*
+ * Per-host context, canonicalized form.
+ */
+ if (flags & SMTP_KEY_FLAG_HOSTNAME)
+ smtp_key_append_str(buffer, STR(iter->host), delim_na);
+ if (flags & SMTP_KEY_FLAG_ADDR)
+ smtp_key_append_str(buffer, STR(iter->addr), delim_na);
+ if (flags & SMTP_KEY_FLAG_PORT)
+ smtp_key_append_uint(buffer, ntohs(iter->port), delim_na);
+
+ /*
+ * Requested TLS level, if applicable. TODO(tlsproxy) should the lookup
+ * engine also try the requested TLS level and 'stronger', in case a
+ * server hosts multiple domains with different TLS requirements?
+ */
+ if (flags & SMTP_KEY_FLAG_TLS_LEVEL)
+#ifdef USE_TLS
+ smtp_key_append_uint(buffer, state->tls->level, delim_na);
+#else
+ smtp_key_append_na(buffer, delim_na);
+#endif
+
+ VSTRING_TERMINATE(buffer);
+
+ return STR(buffer);
+}
diff --git a/src/smtp/smtp_map11.c b/src/smtp/smtp_map11.c
new file mode 100644
index 0000000..bca8641
--- /dev/null
+++ b/src/smtp/smtp_map11.c
@@ -0,0 +1,325 @@
+/*++
+/* NAME
+/* smtp_map11 3
+/* SUMMARY
+/* one-to-one address mapping
+/* SYNOPSIS
+/* #include <smtp.h>
+/*
+/* int smtp_map11_internal(addr, maps, propagate)
+/* VSTRING *addr;
+/* MAPS *maps;
+/* int propagate;
+/*
+/* int smtp_map11_external(addr, maps, propagate)
+/* VSTRING *addr;
+/* MAPS *maps;
+/* int propagate;
+/*
+/* int smtp_map11_tree(tree, maps, propagate)
+/* TOK822 *tree;
+/* MAPS *maps;
+/* int propagate;
+/* DESCRIPTION
+/* This module performs non-recursive one-to-one address mapping.
+/* An unmatched address extension is propagated when
+/* \fIpropagate\fR is non-zero.
+/*
+/* smtp_map11_internal() looks up the RFC 822 internal (unquoted)
+/* string form of an address in the maps specified via the
+/* \fImaps\fR argument.
+/*
+/* smtp_map11_external() is a wrapper around the smtp_map11_internal()
+/* routine that transforms from external (quoted) string form
+/* to internal form and back.
+/*
+/* smtp_map11_tree() is a wrapper around the smtp_map11_internal()
+/* routine that transforms from internal parse tree form to
+/* internal form and back.
+/* DIAGNOSTICS
+/* Table lookup errors are fatal.
+/* SEE ALSO
+/* mail_addr_map(3) address mappings
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <dict.h>
+#include <argv.h>
+#include <tok822.h>
+
+/* Global library. */
+
+#include <mail_addr_map.h>
+#include <quote_822_local.h>
+
+/* Application-specific. */
+
+#include <smtp.h>
+
+/* smtp_map11_internal - one-to-one table lookups */
+
+int smtp_map11_internal(VSTRING *addr, MAPS *maps, int propagate)
+{
+ const char *myname = "smtp_map11_internal";
+ ARGV *new_addr;
+ const char *result;
+
+ if ((new_addr = mail_addr_map_internal(maps, STR(addr), propagate)) != 0) {
+ if (new_addr->argc > 1)
+ msg_warn("multi-valued %s result for %s", maps->title, STR(addr));
+ result = new_addr->argv[0];
+ if (msg_verbose)
+ msg_info("%s: %s -> %s", myname, STR(addr), result);
+ vstring_strcpy(addr, result);
+ argv_free(new_addr);
+ return (1);
+ } else {
+ if (maps->error != 0)
+ msg_fatal("%s map lookup problem for %s", maps->title, STR(addr));
+ if (msg_verbose)
+ msg_info("%s: %s not found", myname, STR(addr));
+ return (0);
+ }
+}
+
+/* smtp_map11_tree - rewrite address node */
+
+int smtp_map11_tree(TOK822 *tree, MAPS *maps, int propagate)
+{
+ VSTRING *int_buf = vstring_alloc(100);
+ VSTRING *ext_buf = vstring_alloc(100);
+ int ret;
+
+ tok822_internalize(int_buf, tree->head, TOK822_STR_DEFL);
+ ret = smtp_map11_internal(int_buf, maps, propagate);
+ tok822_free_tree(tree->head);
+ quote_822_local(ext_buf, STR(int_buf));
+ tree->head = tok822_scan(STR(ext_buf), &tree->tail);
+ vstring_free(int_buf);
+ vstring_free(ext_buf);
+ return (ret);
+}
+
+/* smtp_map11_external - rewrite address external form */
+
+int smtp_map11_external(VSTRING *addr, MAPS *maps, int propagate)
+{
+ VSTRING *temp = vstring_alloc(100);
+ int ret;
+
+ unquote_822_local(temp, STR(addr));
+ ret = smtp_map11_internal(temp, maps, propagate);
+ quote_822_local(addr, STR(temp));
+ vstring_free(temp);
+ return (ret);
+}
+
+#ifdef TEST
+#include <ctype.h>
+
+#include <msg_vstream.h>
+#include <readlline.h>
+#include <stringops.h>
+#include <vstring_vstream.h>
+
+#include <canon_addr.h>
+#include <mail_params.h>
+
+/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */
+
+VSTRING *canon_addr_external(VSTRING *result, const char *addr)
+{
+ char *at;
+
+ vstring_strcpy(result, addr);
+ if ((at = strrchr(addr, '@')) == 0
+ || (at + 1)[strcspn(at + 1, "\"\\")] != 0)
+ vstring_sprintf_append(result, "@%s", var_myorigin);
+ return (result);
+}
+
+static NORETURN usage(const char *progname)
+{
+ msg_fatal("usage: %s [-v]", progname);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *read_buf = vstring_alloc(100);
+ MAPS *maps = 0;
+ int lineno;
+ int first_line;
+ char *bp;
+ char *cmd;
+ VSTRING *addr_buf = vstring_alloc(100);
+ char *addr_field;
+ char *res_field;
+ int ch;
+ int errs = 0;
+
+ /*
+ * Initialize.
+ */
+ msg_vstream_init(basename(argv[0]), VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc != optind)
+ usage(argv[0]);
+
+ util_utf8_enable = 1;
+ mail_params_init();
+
+ /*
+ * TODO: Data-driven parameter settings.
+ */
+#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0)
+
+ UPDATE(var_myhostname, "localhost.localdomain");
+ UPDATE(var_mydomain, "localdomain");
+ UPDATE(var_myorigin, "localdomain");
+ UPDATE(var_mydest, "localhost.localdomain");
+ UPDATE(var_rcpt_delim, "+");
+
+ /*
+ * The read-eval-print loop.
+ */
+ while (readllines(read_buf, VSTREAM_IN, &lineno, &first_line)) {
+ bp = STR(read_buf);
+ if (msg_verbose)
+ msg_info("> %s", bp);
+ if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0 || *cmd == '#')
+ continue;
+ while (ISSPACE(*bp))
+ bp++;
+ if (*bp == 0)
+ msg_fatal("missing parameter for command %s", cmd);
+
+ /*
+ * Open maps.
+ */
+ if (strcmp(cmd, "maps") == 0) {
+ if (maps)
+ maps_free(maps);
+ maps = maps_create(bp, bp, DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ vstream_printf("%s\n", bp);
+ continue;
+ }
+
+ /*
+ * Lookup and verify.
+ */
+ else if (maps != 0 && (strcmp(cmd, "external") == 0
+ || strcmp(cmd, "internal") == 0
+ || strcmp(cmd, "tree") == 0)) {
+ int have_result = 0;
+
+
+ /*
+ * Parse the input and expectations.
+ */
+ if ((addr_field = mystrtok(&bp, ":")) == 0)
+ msg_fatal("missing address field");
+ res_field = mystrtok(&bp, ":");
+ if (mystrtok(&bp, ":") != 0)
+ msg_fatal("garbage after result field");
+
+ /*
+ * Perform the mapping.
+ */
+ if (strcmp(cmd, "external") == 0) {
+ vstring_strcpy(addr_buf, addr_field);
+ have_result = smtp_map11_external(addr_buf, maps, 1);
+ } else if (maps && strcmp(cmd, "internal") == 0) {
+ vstring_strcpy(addr_buf, addr_field);
+ have_result = smtp_map11_internal(addr_buf, maps, 1);
+ } else if (maps && strcmp(cmd, "tree") == 0) {
+ TOK822 *tree;
+ TOK822 **addr_list;
+ TOK822 **tpp;
+
+ tree = tok822_parse(addr_field);
+ addr_list = tok822_grep(tree, TOK822_ADDR);
+ for (tpp = addr_list; *tpp; tpp++)
+ have_result |= smtp_map11_tree(tpp[0], maps, 1);
+ myfree((void *) addr_list);
+ if (have_result)
+ tok822_externalize(addr_buf, tree, TOK822_STR_DEFL);
+ tok822_free_tree(tree);
+ }
+
+ /*
+ * Summarize.
+ */
+ vstream_printf("%s:%s -> %s\n",
+ cmd, addr_field, have_result ?
+ STR(addr_buf) : maps->error ?
+ "(error)" : "(no match)");
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Enforce expectations.
+ */
+ if (res_field && have_result) {
+ if (strcmp(res_field, STR(addr_buf)) != 0) {
+ msg_warn("expect result '%s' but got '%s'",
+ res_field, STR(addr_buf));
+ errs = 1;
+ }
+ } else if (res_field && !have_result) {
+ msg_warn("expect result '%s' but got none", res_field);
+ errs = 1;
+ } else if (!res_field && have_result) {
+ msg_warn("expected no result but got '%s'", STR(addr_buf));
+ errs = 1;
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+
+ /*
+ * Unknown request.
+ */
+ else {
+ msg_fatal("bad request: %s", cmd);
+ }
+ }
+ vstring_free(addr_buf);
+ vstring_free(read_buf);
+ maps_free(maps);
+ return (errs != 0);
+}
+
+#endif
diff --git a/src/smtp/smtp_map11.in b/src/smtp/smtp_map11.in
new file mode 100644
index 0000000..242859f
--- /dev/null
+++ b/src/smtp/smtp_map11.in
@@ -0,0 +1,33 @@
+# Table with external-form mapping.
+maps inline:{
+ { foo@example.com = bar@com.example }
+ { bar@example.com = bar }
+ { baz@example.com = @com.example }
+ { splitme@example.com = "split me"@com.example } }
+
+# Tests for external form.
+external foo@example.com:bar@com.example
+external bar@example.com:bar@localdomain
+external baz@example.com:baz@com.example
+external foo@example.net
+external splitme@example.com:"split me"@com.example
+external splitme+ext@example.com:"split me+ext"@com.example
+external "baz+first last"@example.com:"baz+first last"@com.example
+
+# Same tests for tree form.
+tree foo@example.com:bar@com.example
+tree bar@example.com:bar@localdomain
+tree baz@example.com:baz@com.example
+tree foo@example.net
+tree splitme@example.com:"split me"@com.example
+tree splitme+ext@example.com:"split me+ext"@com.example
+tree "baz+first last"@example.com:"baz+first last"@com.example
+
+# Same tests for internal form.
+internal foo@example.com:bar@com.example
+internal bar@example.com:bar@localdomain
+internal baz@example.com:baz@com.example
+internal foo@example.net
+internal splitme@example.com:split me@com.example
+internal splitme+ext@example.com:split me+ext@com.example
+internal baz+first last@example.com:baz+first last@com.example
diff --git a/src/smtp/smtp_map11.ref b/src/smtp/smtp_map11.ref
new file mode 100644
index 0000000..7e6f2ed
--- /dev/null
+++ b/src/smtp/smtp_map11.ref
@@ -0,0 +1,22 @@
+inline:{ { foo@example.com = bar@com.example } { bar@example.com = bar } { baz@example.com = @com.example } { splitme@example.com = "split me"@com.example } }
+external:foo@example.com -> bar@com.example
+external:bar@example.com -> bar@localdomain
+external:baz@example.com -> baz@com.example
+external:foo@example.net -> (no match)
+external:splitme@example.com -> "split me"@com.example
+external:splitme+ext@example.com -> "split me+ext"@com.example
+external:"baz+first last"@example.com -> "baz+first last"@com.example
+tree:foo@example.com -> bar@com.example
+tree:bar@example.com -> bar@localdomain
+tree:baz@example.com -> baz@com.example
+tree:foo@example.net -> (no match)
+tree:splitme@example.com -> "split me"@com.example
+tree:splitme+ext@example.com -> "split me+ext"@com.example
+tree:"baz+first last"@example.com -> "baz+first last"@com.example
+internal:foo@example.com -> bar@com.example
+internal:bar@example.com -> bar@localdomain
+internal:baz@example.com -> baz@com.example
+internal:foo@example.net -> (no match)
+internal:splitme@example.com -> split me@com.example
+internal:splitme+ext@example.com -> split me+ext@com.example
+internal:baz+first last@example.com -> baz+first last@com.example
diff --git a/src/smtp/smtp_params.c b/src/smtp/smtp_params.c
new file mode 100644
index 0000000..561f6a0
--- /dev/null
+++ b/src/smtp/smtp_params.c
@@ -0,0 +1,134 @@
+ static const CONFIG_STR_TABLE smtp_str_table[] = {
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_SMTP_FALLBACK, DEF_SMTP_FALLBACK, &var_fallback_relay, 0, 0,
+ VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0,
+ VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
+ VAR_SMTP_SASL_PASSWD, DEF_SMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0,
+ VAR_SMTP_SASL_OPTS, DEF_SMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0,
+ VAR_SMTP_SASL_PATH, DEF_SMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0,
+#ifdef USE_TLS
+ VAR_SMTP_SASL_TLS_OPTS, DEF_SMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0,
+ VAR_SMTP_SASL_TLSV_OPTS, DEF_SMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0,
+ VAR_SMTP_TLS_CHAIN_FILES, DEF_SMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0,
+ VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0,
+ VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0,
+ VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0,
+ VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0,
+ VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0,
+ VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0,
+ VAR_SMTP_TLS_MAND_CIPH, DEF_SMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0,
+ VAR_SMTP_TLS_EXCL_CIPH, DEF_SMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0,
+ VAR_SMTP_TLS_MAND_EXCL, DEF_SMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0,
+ VAR_SMTP_TLS_MAND_PROTO, DEF_SMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0,
+ VAR_SMTP_TLS_VFY_CMATCH, DEF_SMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0,
+ VAR_SMTP_TLS_SEC_CMATCH, DEF_SMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0,
+ VAR_SMTP_TLS_FPT_CMATCH, DEF_SMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0,
+ VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0,
+ VAR_SMTP_TLS_TAFILE, DEF_SMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0,
+ VAR_SMTP_TLS_PROTO, DEF_SMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0,
+ VAR_SMTP_TLS_CIPH, DEF_SMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0,
+ VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0,
+ VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0,
+ VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
+ VAR_SMTP_TLS_SNI, DEF_SMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0,
+ VAR_SMTP_TLS_INSECURE_MX_POLICY, DEF_SMTP_TLS_INSECURE_MX_POLICY, &var_smtp_tls_insecure_mx_policy, 0, 0,
+#endif
+ VAR_SMTP_SASL_MECHS, DEF_SMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0,
+ VAR_SMTP_SASL_TYPE, DEF_SMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0,
+ VAR_SMTP_BIND_ADDR, DEF_SMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0,
+ VAR_SMTP_BIND_ADDR6, DEF_SMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0,
+ VAR_SMTP_VRFY_TGT, DEF_SMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0,
+ VAR_SMTP_HELO_NAME, DEF_SMTP_HELO_NAME, &var_smtp_helo_name, 1, 0,
+ VAR_SMTP_HOST_LOOKUP, DEF_SMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0,
+ VAR_SMTP_DNS_SUPPORT, DEF_SMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0,
+ VAR_SMTP_CACHE_DEST, DEF_SMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0,
+ VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0,
+ VAR_SMTP_EHLO_DIS_WORDS, DEF_SMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0,
+ VAR_SMTP_EHLO_DIS_MAPS, DEF_SMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0,
+ VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0,
+ VAR_SMTP_TLS_LEVEL, DEF_SMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0,
+ VAR_SMTP_TLS_POLICY, DEF_SMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0,
+ VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0,
+ VAR_SMTP_GENERIC_MAPS, DEF_SMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0,
+ VAR_SMTP_TCP_PORT, DEF_SMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0,
+ VAR_SMTP_PIX_BUG_WORDS, DEF_SMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0,
+ VAR_SMTP_PIX_BUG_MAPS, DEF_SMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0,
+ VAR_SMTP_SASL_AUTH_CACHE_NAME, DEF_SMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0,
+ VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0,
+ VAR_SMTP_HEAD_CHKS, DEF_SMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0,
+ VAR_SMTP_MIME_CHKS, DEF_SMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0,
+ VAR_SMTP_NEST_CHKS, DEF_SMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0,
+ VAR_SMTP_BODY_CHKS, DEF_SMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0,
+ VAR_SMTP_RESP_FILTER, DEF_SMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0,
+ VAR_SMTP_ADDR_PREF, DEF_SMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0,
+ VAR_SMTP_DNS_RES_OPT, DEF_SMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0,
+ VAR_SMTP_DSN_FILTER, DEF_SMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0,
+ VAR_SMTP_DNS_RE_FILTER, DEF_SMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0,
+ VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE smtp_time_table[] = {
+ VAR_SMTP_CONN_TMOUT, DEF_SMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0,
+ VAR_SMTP_HELO_TMOUT, DEF_SMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0,
+ VAR_SMTP_XFWD_TMOUT, DEF_SMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0,
+ VAR_SMTP_MAIL_TMOUT, DEF_SMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0,
+ VAR_SMTP_RCPT_TMOUT, DEF_SMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0,
+ VAR_SMTP_DATA0_TMOUT, DEF_SMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0,
+ VAR_SMTP_DATA1_TMOUT, DEF_SMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0,
+ VAR_SMTP_DATA2_TMOUT, DEF_SMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0,
+ VAR_SMTP_RSET_TMOUT, DEF_SMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0,
+ VAR_SMTP_QUIT_TMOUT, DEF_SMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0,
+ VAR_SMTP_PIX_THRESH, DEF_SMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0,
+ VAR_SMTP_PIX_DELAY, DEF_SMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0,
+ VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0,
+ VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0,
+ VAR_SMTP_CACHE_CONNT, DEF_SMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0,
+ VAR_SMTP_REUSE_TIME, DEF_SMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0,
+#ifdef USE_TLS
+ VAR_SMTP_STARTTLS_TMOUT, DEF_SMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0,
+#endif
+ VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0,
+ VAR_SMTP_SASL_AUTH_CACHE_TIME, DEF_SMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE smtp_int_table[] = {
+ VAR_SMTP_LINE_LIMIT, DEF_SMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0,
+ VAR_SMTP_MXADDR_LIMIT, DEF_SMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0,
+ VAR_SMTP_MXSESS_LIMIT, DEF_SMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0,
+ VAR_SMTP_REUSE_COUNT, DEF_SMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0,
+#ifdef USE_TLS
+ VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0,
+#endif
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE smtp_bool_table[] = {
+ VAR_SMTP_SKIP_5XX, DEF_SMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting,
+ VAR_IGN_MX_LOOKUP_ERR, DEF_IGN_MX_LOOKUP_ERR, &var_ign_mx_lookup_err,
+ VAR_SMTP_SKIP_QUIT_RESP, DEF_SMTP_SKIP_QUIT_RESP, &var_skip_quit_resp,
+ VAR_SMTP_ALWAYS_EHLO, DEF_SMTP_ALWAYS_EHLO, &var_smtp_always_ehlo,
+ VAR_SMTP_NEVER_EHLO, DEF_SMTP_NEVER_EHLO, &var_smtp_never_ehlo,
+ VAR_SMTP_SASL_ENABLE, DEF_SMTP_SASL_ENABLE, &var_smtp_sasl_enable,
+ VAR_SMTP_RAND_ADDR, DEF_SMTP_RAND_ADDR, &var_smtp_rand_addr,
+ VAR_SMTP_QUOTE_821_ENV, DEF_SMTP_QUOTE_821_ENV, &var_smtp_quote_821_env,
+ VAR_SMTP_DEFER_MXADDR, DEF_SMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr,
+ VAR_SMTP_SEND_XFORWARD, DEF_SMTP_SEND_XFORWARD, &var_smtp_send_xforward,
+ VAR_SMTP_CACHE_DEMAND, DEF_SMTP_CACHE_DEMAND, &var_smtp_cache_demand,
+ VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls,
+ VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls,
+ VAR_SMTP_TLS_CONN_REUSE, DEF_SMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse,
+#ifdef USE_TLS
+ VAR_SMTP_TLS_ENFORCE_PN, DEF_SMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername,
+ VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer,
+ VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply,
+ VAR_SMTP_TLS_FORCE_TLSA, DEF_SMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa,
+#endif
+ VAR_SMTP_TLS_WRAPPER, DEF_SMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode,
+ VAR_SMTP_SENDER_AUTH, DEF_SMTP_SENDER_AUTH, &var_smtp_sender_auth,
+ VAR_SMTP_CNAME_OVERR, DEF_SMTP_CNAME_OVERR, &var_smtp_cname_overr,
+ VAR_SMTP_SASL_AUTH_SOFT_BOUNCE, DEF_SMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce,
+ VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final,
+ VAR_SMTP_REC_DEADLINE, DEF_SMTP_REC_DEADLINE, &var_smtp_rec_deadline,
+ VAR_SMTP_DUMMY_MAIL_AUTH, DEF_SMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth,
+ VAR_SMTP_BALANCE_INET_PROTO, DEF_SMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto,
+ 0,
+ };
diff --git a/src/smtp/smtp_proto.c b/src/smtp/smtp_proto.c
new file mode 100644
index 0000000..a43a326
--- /dev/null
+++ b/src/smtp/smtp_proto.c
@@ -0,0 +1,2469 @@
+/*++
+/* NAME
+/* smtp_proto 3
+/* SUMMARY
+/* client SMTP/LMTP protocol
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* int smtp_helo(state)
+/* SMTP_STATE *state;
+/*
+/* int smtp_xfer(state)
+/* SMTP_STATE *state;
+/*
+/* int smtp_rset(state)
+/* SMTP_STATE *state;
+/*
+/* int smtp_quit(state)
+/* SMTP_STATE *state;
+/* DESCRIPTION
+/* In the subsequent text, SMTP implies LMTP.
+/* This module implements the client side of the SMTP protocol.
+/*
+/* smtp_helo() performs the initial handshake with the SMTP server.
+/* When TLS is enabled, this includes STARTTLS negotiations.
+/*
+/* smtp_xfer() sends message envelope information followed by the
+/* message data, and finishes the SMTP conversation. These operations
+/* are combined in one function, in order to implement SMTP pipelining.
+/* Recipients are marked as "done" in the mail queue file when
+/* bounced or delivered. The message delivery status is updated
+/* accordingly.
+/*
+/* smtp_rset() sends a single RSET command and waits for the
+/* response. In case of a negative reply it sets the
+/* CANT_RSET_THIS_SESSION flag.
+/*
+/* smtp_quit() sends a single QUIT command and waits for the
+/* response if configured to do so. It always turns off connection
+/* caching.
+/* DIAGNOSTICS
+/* smtp_helo(), smtp_xfer(), smtp_rset() and smtp_quit() return
+/* 0 in case of success, -1 in case of failure. For smtp_xfer(),
+/* smtp_rset() and smtp_quit(), success means the ability to
+/* perform an SMTP conversation, not necessarily the ability
+/* to deliver mail, or the achievement of server happiness.
+/*
+/* In case of a rejected or failed connection, a connection
+/* is marked as "bad, do not cache". Otherwise, connection
+/* caching may be turned off (without being marked "bad") at
+/* the discretion of the code that implements the individual
+/* protocol steps.
+/*
+/* Warnings: corrupt message file. A corrupt message is marked
+/* as "corrupt" by changing its queue file permissions.
+/* BUGS
+/* Some SMTP servers will abort when the number of recipients
+/* for one message exceeds their capacity. This behavior violates
+/* the SMTP protocol.
+/* The only way around this is to limit the number of recipients
+/* per transaction to an artificially-low value.
+/* SEE ALSO
+/* smtp(3h) internal data structures
+/* smtp_chat(3) query/reply SMTP support
+/* smtp_trouble(3) error handlers
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Pipelining code in cooperation with:
+/* Jon Ribbens
+/* Oaktree Internet Solutions Ltd.,
+/* Internet House,
+/* Canal Basin,
+/* Coventry,
+/* CV1 4LY, United Kingdom.
+/*
+/* Connection caching in cooperation with:
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <sys/socket.h> /* shutdown(2) */
+#include <netinet/in.h> /* ntohs() */
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <time.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <split_at.h>
+#include <name_code.h>
+#include <name_mask.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <smtp_stream.h>
+#include <mail_queue.h>
+#include <recipient_list.h>
+#include <deliver_request.h>
+#include <defer.h>
+#include <bounce.h>
+#include <record.h>
+#include <rec_type.h>
+#include <off_cvt.h>
+#include <mark_corrupt.h>
+#include <quote_821_local.h>
+#include <quote_822_local.h>
+#include <mail_proto.h>
+#include <mime_state.h>
+#include <ehlo_mask.h>
+#include <maps.h>
+#include <tok822.h>
+#include <mail_addr_map.h>
+#include <ext_prop.h>
+#include <namadr_list.h>
+#include <match_parent_style.h>
+#include <lex_822.h>
+#include <dsn_mask.h>
+#include <xtext.h>
+#include <uxtext.h>
+#include <smtputf8.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+ /*
+ * Sender and receiver state. A session does not necessarily go through a
+ * linear progression, but states are guaranteed to not jump backwards.
+ * Normal sessions go from MAIL->RCPT->DATA->DOT->QUIT->LAST. The states
+ * MAIL, RCPT, and DATA may also be followed by ABORT->QUIT->LAST.
+ *
+ * When connection caching is enabled, the QUIT state is suppressed. Normal
+ * sessions proceed as MAIL->RCPT->DATA->DOT->LAST, while aborted sessions
+ * end with ABORT->LAST. The connection is left open for a limited time. An
+ * RSET probe should be sent before attempting to reuse an open connection
+ * for a new transaction.
+ *
+ * The code to send an RSET probe is a special case with its own initial state
+ * and with its own dedicated state transitions. The session proceeds as
+ * RSET->LAST. This code is kept inside the main protocol engine for
+ * consistent error handling and error reporting. It is not to be confused
+ * with the code that sends RSET to abort a mail transaction in progress.
+ *
+ * The code to send QUIT without message delivery transaction jumps into the
+ * main state machine. If this introduces complications, then we should
+ * introduce a second QUIT state with its own dedicated state transitions,
+ * just like we did for RSET probes.
+ *
+ * By default, the receiver skips the QUIT response. Some SMTP servers
+ * disconnect after responding to ".", and some SMTP servers wait before
+ * responding to QUIT.
+ *
+ * Client states that are associated with sending mail (up to and including
+ * SMTP_STATE_DOT) must have smaller numerical values than the non-sending
+ * states (SMTP_STATE_ABORT .. SMTP_STATE_LAST).
+ */
+#define SMTP_STATE_XFORWARD_NAME_ADDR 0
+#define SMTP_STATE_XFORWARD_PROTO_HELO 1
+#define SMTP_STATE_MAIL 2
+#define SMTP_STATE_RCPT 3
+#define SMTP_STATE_DATA 4
+#define SMTP_STATE_DOT 5
+#define SMTP_STATE_ABORT 6
+#define SMTP_STATE_RSET 7
+#define SMTP_STATE_QUIT 8
+#define SMTP_STATE_LAST 9
+
+int *xfer_timeouts[SMTP_STATE_LAST] = {
+ &var_smtp_xfwd_tmout, /* name/addr */
+ &var_smtp_xfwd_tmout, /* helo/proto */
+ &var_smtp_mail_tmout,
+ &var_smtp_rcpt_tmout,
+ &var_smtp_data0_tmout,
+ &var_smtp_data2_tmout,
+ &var_smtp_rset_tmout,
+ &var_smtp_rset_tmout,
+ &var_smtp_quit_tmout,
+};
+
+char *xfer_states[SMTP_STATE_LAST] = {
+ "sending XFORWARD name/address",
+ "sending XFORWARD protocol/helo_name",
+ "sending MAIL FROM",
+ "sending RCPT TO",
+ "sending DATA command",
+ "sending end of data -- message may be sent more than once",
+ "sending final RSET",
+ "sending RSET probe",
+ "sending QUIT",
+};
+
+char *xfer_request[SMTP_STATE_LAST] = {
+ "XFORWARD name/address command",
+ "XFORWARD helo/protocol command",
+ "MAIL FROM command",
+ "RCPT TO command",
+ "DATA command",
+ "end of DATA command",
+ "final RSET command",
+ "RSET probe",
+ "QUIT command",
+};
+
+ /*
+ * Note: MIME downgrade never happens for mail that must be delivered with
+ * SMTPUTF8 (the sender requested SMTPUTF8, AND the delivery request
+ * involves at least one UTF-8 envelope address or header value.
+ */
+#define SMTP_MIME_DOWNGRADE(session, request) \
+ (var_disable_mime_oconv == 0 \
+ && (session->features & SMTP_FEATURE_8BITMIME) == 0 \
+ && strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0)
+
+#ifdef USE_TLS
+
+static int smtp_start_tls(SMTP_STATE *);
+
+#endif
+
+ /*
+ * Call-back information for header/body checks. We don't provide call-backs
+ * for actions that change the message delivery time or destination.
+ */
+static void smtp_hbc_logger(void *, const char *, const char *, const char *, const char *);
+static void smtp_text_out(void *, int, const char *, ssize_t, off_t);
+
+HBC_CALL_BACKS smtp_hbc_callbacks[1] = {
+ smtp_hbc_logger,
+ smtp_text_out,
+};
+
+static int smtp_vrfy_tgt;
+
+/* smtp_vrfy_init - initialize */
+
+void smtp_vrfy_init(void)
+{
+ static const NAME_CODE vrfy_init_table[] = {
+ SMTP_VRFY_TGT_RCPT, SMTP_STATE_RCPT,
+ SMTP_VRFY_TGT_DATA, SMTP_STATE_DATA,
+ 0,
+ };
+
+ if ((smtp_vrfy_tgt = name_code(vrfy_init_table, NAME_CODE_FLAG_NONE,
+ var_smtp_vrfy_tgt)) == 0)
+ msg_fatal("bad protocol stage: \"%s = %s\"",
+ VAR_SMTP_VRFY_TGT, var_smtp_vrfy_tgt);
+}
+
+/* smtp_helo - perform initial handshake with SMTP server */
+
+int smtp_helo(SMTP_STATE *state)
+{
+ const char *myname = "smtp_helo";
+ SMTP_SESSION *session = state->session;
+ DELIVER_REQUEST *request = state->request;
+ SMTP_ITERATOR *iter = state->iterator;
+ SMTP_RESP *resp;
+ SMTP_RESP fake;
+ int except;
+ char *lines;
+ char *words;
+ char *word;
+ int n;
+ static const NAME_CODE xforward_features[] = {
+ XFORWARD_NAME, SMTP_FEATURE_XFORWARD_NAME,
+ XFORWARD_ADDR, SMTP_FEATURE_XFORWARD_ADDR,
+ XFORWARD_PORT, SMTP_FEATURE_XFORWARD_PORT,
+ XFORWARD_PROTO, SMTP_FEATURE_XFORWARD_PROTO,
+ XFORWARD_HELO, SMTP_FEATURE_XFORWARD_HELO,
+ XFORWARD_IDENT, SMTP_FEATURE_XFORWARD_IDENT,
+ XFORWARD_DOMAIN, SMTP_FEATURE_XFORWARD_DOMAIN,
+ 0, 0,
+ };
+ const char *ehlo_words;
+ int discard_mask;
+ static const NAME_MASK pix_bug_table[] = {
+ PIX_BUG_DISABLE_ESMTP, SMTP_FEATURE_PIX_NO_ESMTP,
+ PIX_BUG_DELAY_DOTCRLF, SMTP_FEATURE_PIX_DELAY_DOTCRLF,
+ 0,
+ };
+ const char *pix_bug_words;
+ const char *pix_bug_source;
+ int pix_bug_mask;
+
+#ifdef USE_TLS
+ int saved_features = session->features;
+ int tls_helo_status;
+
+#endif
+ const char *NOCLOBBER where;
+
+ /*
+ * Skip the plaintext SMTP handshake when connecting in SMTPS mode.
+ */
+#ifdef USE_TLS
+ if (var_smtp_tls_wrappermode
+ && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {
+ /* XXX Mix-up of per-session and per-request flags. */
+ state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS;
+ smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout,
+ var_smtp_rec_deadline);
+ tls_helo_status = smtp_start_tls(state);
+ state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS;
+ return (tls_helo_status);
+ }
+#endif
+
+ /*
+ * Prepare for disaster.
+ */
+ smtp_stream_setup(state->session->stream, var_smtp_helo_tmout,
+ var_smtp_rec_deadline);
+ if ((except = vstream_setjmp(state->session->stream)) != 0)
+ return (smtp_stream_except(state, except, where));
+
+ /*
+ * If not recursing after STARTTLS, examine the server greeting banner
+ * and decide if we are going to send EHLO as the next command.
+ */
+ if (var_smtp_tls_wrappermode
+ || (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {
+
+ /*
+ * Read and parse the server's SMTP greeting banner.
+ */
+ where = "receiving the initial server greeting";
+ switch ((resp = smtp_chat_resp(session))->code / 100) {
+ case 2:
+ break;
+ case 5:
+ if (var_smtp_skip_5xx_greeting)
+ STR(resp->dsn_buf)[0] = '4';
+ /* FALLTHROUGH */
+ default:
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "host %s refused to talk to me: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ }
+
+ /*
+ * If the policy table specifies a bogus TLS security level, fail
+ * now.
+ */
+#ifdef USE_TLS
+ if (state->tls->level == TLS_LEV_INVALID)
+ /* Warning is already logged. */
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.0"),
+ "client TLS configuration problem"));
+#endif
+
+ /*
+ * XXX Some PIX firewall versions require flush before ".<CR><LF>" so
+ * it does not span a packet boundary. This hurts performance so it
+ * is not on by default.
+ */
+ if (resp->str[strspn(resp->str, "20 *\t\n")] == 0) {
+ /* Best effort only. Ignore errors. */
+ if (smtp_pix_bug_maps != 0
+ && (pix_bug_words =
+ maps_find(smtp_pix_bug_maps,
+ STR(iter->addr), 0)) != 0) {
+ pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_MAPS);
+ } else {
+ pix_bug_words = var_smtp_pix_bug_words;
+ pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_WORDS);
+ }
+ if (*pix_bug_words) {
+ pix_bug_mask = name_mask_opt(pix_bug_source, pix_bug_table,
+ pix_bug_words,
+ NAME_MASK_ANY_CASE | NAME_MASK_IGNORE);
+ if ((pix_bug_mask & SMTP_FEATURE_PIX_DELAY_DOTCRLF)
+ && request->msg_stats.incoming_arrival.tv_sec
+ > vstream_ftime(state->session->stream) - var_smtp_pix_thresh)
+ pix_bug_mask &= ~SMTP_FEATURE_PIX_DELAY_DOTCRLF;
+ msg_info("%s: enabling PIX workarounds: %s for %s",
+ request->queue_id,
+ str_name_mask("pix workaround bitmask",
+ pix_bug_table, pix_bug_mask),
+ session->namaddrport);
+ session->features |= pix_bug_mask;
+ }
+ }
+
+ /*
+ * See if we are talking to ourself. This should not be possible with
+ * the way we implement DNS lookups. However, people are known to
+ * sometimes screw up the naming service. And, mailer loops are still
+ * possible when our own mailer routing tables are mis-configured.
+ */
+ words = resp->str;
+ (void) mystrtok(&words, "- \t\n");
+ for (n = 0; (word = mystrtok(&words, " \t\n")) != 0; n++) {
+ if (n == 0 && strcasecmp(word, var_myhostname) == 0) {
+ if (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
+ msg_warn("host %s greeted me with my own hostname %s",
+ session->namaddrport, var_myhostname);
+ } else if (strcasecmp(word, "ESMTP") == 0)
+ session->features |= SMTP_FEATURE_ESMTP;
+ }
+ if (smtp_mode) {
+ if (var_smtp_always_ehlo
+ && (session->features & SMTP_FEATURE_PIX_NO_ESMTP) == 0)
+ session->features |= SMTP_FEATURE_ESMTP;
+ if (var_smtp_never_ehlo
+ || (session->features & SMTP_FEATURE_PIX_NO_ESMTP) != 0)
+ session->features &= ~SMTP_FEATURE_ESMTP;
+ } else {
+ session->features |= SMTP_FEATURE_ESMTP;
+ }
+ }
+
+ /*
+ * If recursing after STARTTLS, there is no server greeting banner.
+ * Always send EHLO as the next command.
+ */
+ else {
+ session->features |= SMTP_FEATURE_ESMTP;
+ }
+
+ /*
+ * Return the compliment. Fall back to SMTP if our ESMTP recognition
+ * heuristic failed.
+ */
+ if (smtp_mode) {
+ where = "performing the EHLO handshake";
+ if (session->features & SMTP_FEATURE_ESMTP) {
+ smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name);
+ if ((resp = smtp_chat_resp(session))->code / 100 != 2) {
+ if (resp->code == 421)
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "host %s refused to talk to me: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ else
+ session->features &= ~SMTP_FEATURE_ESMTP;
+ }
+ }
+ if ((session->features & SMTP_FEATURE_ESMTP) == 0) {
+ where = "performing the HELO handshake";
+ smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name);
+ if ((resp = smtp_chat_resp(session))->code / 100 != 2)
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "host %s refused to talk to me: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ }
+ } else {
+ where = "performing the LHLO handshake";
+ smtp_chat_cmd(session, "LHLO %s", var_smtp_helo_name);
+ if ((resp = smtp_chat_resp(session))->code / 100 != 2)
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "host %s refused to talk to me: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ }
+
+ /*
+ * No early returns allowed, to ensure consistent handling of TLS and
+ * SASL policies.
+ */
+ if (session->features & SMTP_FEATURE_ESMTP) {
+
+ /*
+ * Determine what server EHLO keywords to ignore, typically to avoid
+ * inter-operability problems.
+ */
+ if (smtp_ehlo_dis_maps == 0
+ || (ehlo_words = maps_find(smtp_ehlo_dis_maps,
+ STR(iter->addr), 0)) == 0)
+ ehlo_words = var_smtp_ehlo_dis_words;
+ if (smtp_ehlo_dis_maps && smtp_ehlo_dis_maps->error) {
+ msg_warn("%s: %s map lookup error for %s",
+ session->state->request->queue_id,
+ smtp_ehlo_dis_maps->title, STR(iter->addr));
+ vstream_longjmp(session->stream, SMTP_ERR_DATA);
+ }
+ discard_mask = ehlo_mask(ehlo_words);
+ if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
+ msg_info("discarding EHLO keywords: %s",
+ str_ehlo_mask(discard_mask));
+
+ /*
+ * Pick up some useful features offered by the SMTP server. XXX Until
+ * we have a portable routine to convert from string to off_t with
+ * proper overflow detection, ignore the message size limit
+ * advertised by the SMTP server. Otherwise, we might do the wrong
+ * thing when the server advertises a really huge message size limit.
+ *
+ * XXX Allow for "code (SP|-) ehlo-keyword (SP|=) ehlo-param...",
+ * because MicroSoft implemented AUTH based on an old draft.
+ */
+ lines = resp->str;
+ for (n = 0; (words = mystrtok(&lines, "\n")) != 0; /* see below */ ) {
+ if (mystrtok(&words, "- ")
+ && (word = mystrtok(&words, " \t=")) != 0) {
+ if (n == 0) {
+ if (session->helo != 0)
+ myfree(session->helo);
+
+ /*
+ * XXX: Keep the original case: we don't expect a single
+ * SMTP server to randomly change the case of its helo
+ * response. If different capitalization is detected, we
+ * should assume disjoint TLS caches.
+ */
+ session->helo = mystrdup(word);
+ if (strcasecmp(word, var_myhostname) == 0
+ && (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) != 0) {
+ msg_warn("host %s replied to HELO/EHLO"
+ " with my own hostname %s",
+ session->namaddrport, var_myhostname);
+ if (session->features & SMTP_FEATURE_BEST_MX)
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "5.4.6"),
+ "mail for %s loops back to myself",
+ request->nexthop));
+ else
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.4.6"),
+ "mail for %s loops back to myself",
+ request->nexthop));
+ }
+ } else if (strcasecmp(word, "8BITMIME") == 0) {
+ if ((discard_mask & EHLO_MASK_8BITMIME) == 0)
+ session->features |= SMTP_FEATURE_8BITMIME;
+ } else if (strcasecmp(word, "PIPELINING") == 0) {
+ if ((discard_mask & EHLO_MASK_PIPELINING) == 0)
+ session->features |= SMTP_FEATURE_PIPELINING;
+ } else if (strcasecmp(word, "XFORWARD") == 0) {
+ if ((discard_mask & EHLO_MASK_XFORWARD) == 0)
+ while ((word = mystrtok(&words, " \t")) != 0)
+ session->features |=
+ name_code(xforward_features,
+ NAME_CODE_FLAG_NONE, word);
+ } else if (strcasecmp(word, "SIZE") == 0) {
+ if ((discard_mask & EHLO_MASK_SIZE) == 0) {
+ session->features |= SMTP_FEATURE_SIZE;
+ if ((word = mystrtok(&words, " \t")) != 0) {
+ if (!alldig(word))
+ msg_warn("bad EHLO SIZE limit \"%s\" from %s",
+ word, session->namaddrport);
+ else
+ session->size_limit = off_cvt_string(word);
+ }
+ }
+#ifdef USE_TLS
+ } else if (strcasecmp(word, "STARTTLS") == 0) {
+ /* Ignored later if we already sent STARTTLS. */
+ if ((discard_mask & EHLO_MASK_STARTTLS) == 0)
+ session->features |= SMTP_FEATURE_STARTTLS;
+#endif
+#ifdef USE_SASL_AUTH
+ } else if (var_smtp_sasl_enable
+ && strcasecmp(word, "AUTH") == 0) {
+ if ((discard_mask & EHLO_MASK_AUTH) == 0)
+ smtp_sasl_helo_auth(session, words);
+#endif
+ } else if (strcasecmp(word, "DSN") == 0) {
+ if ((discard_mask & EHLO_MASK_DSN) == 0)
+ session->features |= SMTP_FEATURE_DSN;
+ } else if (strcasecmp(word, "SMTPUTF8") == 0) {
+ if ((discard_mask & EHLO_MASK_SMTPUTF8) == 0)
+ session->features |= SMTP_FEATURE_SMTPUTF8;
+ }
+ n++;
+ }
+ }
+ }
+ if (msg_verbose)
+ msg_info("server features: 0x%x size %.0f",
+ session->features, (double) session->size_limit);
+
+ /*
+ * Decide if this delivery requires SMTPUTF8 server support.
+ *
+ * For now, we require that the remote SMTP server supports SMTPUTF8 when
+ * the sender requested SMTPUTF8 support.
+ *
+ * XXX EAI Refine this to: the sender requested SMTPUTF8 support AND the
+ * delivery request involves at least one UTF-8 envelope address or
+ * header value.
+ *
+ * If the sender requested SMTPUTF8 support but the delivery request
+ * involves no UTF-8 envelope address or header value, then we could
+ * still deliver such mail to a non-SMTPUTF8 server, except that we must
+ * either uxtext-encode ORCPT parameters or not send them. We cannot
+ * encode the ORCPT in xtext, because legacy SMTP requires that the
+ * unencoded address consist entirely of printable (graphic and white
+ * space) characters from the US-ASCII repertoire (RFC 3461 section 4). A
+ * correct uxtext encoder will produce a result that an xtext decoder
+ * will pass through unchanged.
+ *
+ * XXX Should we try to encode headers with RFC 2047 when delivering to a
+ * non-SMTPUTF8 server? That could make life easier for mailing lists.
+ */
+#define DELIVERY_REQUIRES_SMTPUTF8 \
+ ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) \
+ && (request->smtputf8 & ~SMTPUTF8_FLAG_REQUESTED))
+
+ /*
+ * Require that the server supports SMTPUTF8 when delivery requires
+ * SMTPUTF8.
+ *
+ * Fix 20140706: moved this before negotiating TLS, AUTH, and so on.
+ */
+ if ((session->features & SMTP_FEATURE_SMTPUTF8) == 0
+ && DELIVERY_REQUIRES_SMTPUTF8)
+ return (smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "5.6.7"),
+ "SMTPUTF8 is required, "
+ "but was not offered by host %s",
+ session->namaddr));
+
+ /*
+ * Fix 20140706: don't do silly things when the remote server announces
+ * SMTPUTF8 but not 8BITMIME support. Our primary mission is to deliver
+ * mail, not to force people into compliance.
+ */
+ if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0
+ && (session->features & SMTP_FEATURE_8BITMIME) == 0) {
+ msg_info("host %s offers SMTPUTF8 support, but not 8BITMIME",
+ session->namaddr);
+ session->features |= SMTP_FEATURE_8BITMIME;
+ }
+
+ /*
+ * We use SMTP command pipelining if the server said it supported it.
+ * Since we use blocking I/O, RFC 2197 says that we should inspect the
+ * TCP window size and not send more than this amount of information.
+ * Unfortunately this information is unavailable using the sockets
+ * interface. However, we *can* get the TCP send buffer size on the local
+ * TCP/IP stack. We should be able to fill this buffer without being
+ * blocked, and then the kernel will effectively do non-blocking I/O for
+ * us by automatically writing out the contents of its send buffer while
+ * we are reading in the responses. In addition to TCP buffering we have
+ * to be aware of application-level buffering by the vstream module,
+ * which is limited to a couple kbytes.
+ *
+ * XXX No need to do this before and after STARTTLS, but it's not a big deal
+ * if we do.
+ *
+ * XXX When TLS is turned on, the SMTP-level writes will be encapsulated as
+ * TLS messages. Thus, the TCP-level payload will be larger than the
+ * SMTP-level payload. This has implications for the PIPELINING engine.
+ *
+ * To avoid deadlock, the PIPELINING engine needs to request a TCP send
+ * buffer size that can hold the unacknowledged commands plus the TLS
+ * encapsulation overhead.
+ *
+ * The PIPELINING engine keeps the unacknowledged command size <= the
+ * default VSTREAM buffer size (to avoid small-write performance issues
+ * when the VSTREAM buffer size is at its default size). With a default
+ * VSTREAM buffer size of 4096 there is no reason to increase the
+ * unacknowledged command size as the TCP MSS increases. It's safer to
+ * spread the remote SMTP server's recipient processing load over time,
+ * than dumping a very large recipient list all at once.
+ *
+ * For TLS encapsulation overhead we make a conservative guess: take the
+ * current protocol overhead of ~40 bytes, double the number for future
+ * proofing (~80 bytes), then round up the result to the nearest power of
+ * 2 (128 bytes). Plus, be prepared for worst-case compression that
+ * expands data by 1 kbyte, so that the worst-case SMTP payload per TLS
+ * message becomes 15 kbytes.
+ */
+#define PIPELINING_BUFSIZE VSTREAM_BUFSIZE
+#ifdef USE_TLS
+#define TLS_WORST_PAYLOAD 16384
+#define TLS_WORST_COMP_OVERHD 1024
+#define TLS_WORST_PROTO_OVERHD 128
+#define TLS_WORST_SMTP_PAYLOAD (TLS_WORST_PAYLOAD - TLS_WORST_COMP_OVERHD)
+#define TLS_WORST_TOTAL_OVERHD (TLS_WORST_COMP_OVERHD + TLS_WORST_PROTO_OVERHD)
+#endif
+
+ if (session->features & SMTP_FEATURE_PIPELINING) {
+ SOCKOPT_SIZE optlen;
+ int tcp_bufsize;
+ int enc_overhead = 0;
+
+ optlen = sizeof(tcp_bufsize);
+ if (getsockopt(vstream_fileno(session->stream), SOL_SOCKET,
+ SO_SNDBUF, (char *) &tcp_bufsize, &optlen) < 0)
+ msg_fatal("%s: getsockopt: %m", myname);
+#ifdef USE_TLS
+ if (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS)
+ enc_overhead +=
+ (1 + (PIPELINING_BUFSIZE - 1)
+ / TLS_WORST_SMTP_PAYLOAD) * TLS_WORST_TOTAL_OVERHD;
+#endif
+ if (tcp_bufsize < PIPELINING_BUFSIZE + enc_overhead) {
+ tcp_bufsize = PIPELINING_BUFSIZE + enc_overhead;
+ if (setsockopt(vstream_fileno(session->stream), SOL_SOCKET,
+ SO_SNDBUF, (char *) &tcp_bufsize, optlen) < 0)
+ msg_fatal("%s: setsockopt: %m", myname);
+ }
+ if (msg_verbose)
+ msg_info("Using %s PIPELINING, TCP send buffer size is %d, "
+ "PIPELINING buffer size is %d",
+ smtp_mode ? "ESMTP" : "LMTP",
+ tcp_bufsize, PIPELINING_BUFSIZE);
+ }
+#ifdef USE_TLS
+
+ /*
+ * Skip this part if we already sent STARTTLS.
+ */
+ if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {
+
+ /*
+ * Optionally log unused STARTTLS opportunities.
+ */
+ if ((session->features & SMTP_FEATURE_STARTTLS) &&
+ var_smtp_tls_note_starttls_offer &&
+ state->tls->level <= TLS_LEV_NONE)
+ msg_info("Host offered STARTTLS: [%s]", STR(iter->host));
+
+ /*
+ * Decide whether or not to send STARTTLS.
+ */
+ if ((session->features & SMTP_FEATURE_STARTTLS) != 0
+ && smtp_tls_ctx != 0 && state->tls->level >= TLS_LEV_MAY) {
+
+ /*
+ * Prepare for disaster.
+ */
+ smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout,
+ var_smtp_rec_deadline);
+ if ((except = vstream_setjmp(state->session->stream)) != 0)
+ return (smtp_stream_except(state, except,
+ "receiving the STARTTLS response"));
+
+ /*
+ * Send STARTTLS. Recurse when the server accepts STARTTLS, after
+ * resetting the SASL and EHLO features lists.
+ *
+ * Reset the SASL mechanism list to avoid spurious warnings.
+ *
+ * Use the smtp_sasl_tls_security_options feature to allow SASL
+ * mechanisms that may not be allowed with plain-text
+ * connections.
+ */
+ smtp_chat_cmd(session, "STARTTLS");
+ if ((resp = smtp_chat_resp(session))->code / 100 == 2) {
+#ifdef USE_SASL_AUTH
+ if (session->features & SMTP_FEATURE_AUTH)
+ smtp_sasl_cleanup(session);
+#endif
+ session->features = saved_features;
+ /* XXX Mix-up of per-session and per-request flags. */
+ state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS;
+ tls_helo_status = smtp_start_tls(state);
+ state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS;
+ return (tls_helo_status);
+ }
+
+ /*
+ * Give up if we must use TLS but the server rejects STARTTLS
+ * although support for it was announced in the EHLO response.
+ */
+ session->features &= ~SMTP_FEATURE_STARTTLS;
+ if (TLS_REQUIRED(state->tls->level))
+ return (smtp_site_fail(state, STR(iter->host), resp,
+ "TLS is required, but host %s refused to start TLS: %s",
+ session->namaddr,
+ translit(resp->str, "\n", " ")));
+ /* Else try to continue in plain-text mode. */
+ }
+
+ /*
+ * Give up if we must use TLS but can't for various reasons.
+ *
+ * 200412 Be sure to provide the default clause at the bottom of this
+ * block. When TLS is required we must never, ever, end up in
+ * plain-text mode.
+ */
+ if (TLS_REQUIRED(state->tls->level)) {
+ if (!(session->features & SMTP_FEATURE_STARTTLS)) {
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.4"),
+ "TLS is required, but was not offered by host %s",
+ session->namaddr));
+ } else if (smtp_tls_ctx == 0) {
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.5"),
+ "TLS is required, but our TLS engine is unavailable"));
+ } else {
+ msg_warn("%s: TLS is required but unavailable, don't know why",
+ myname);
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.0"),
+ "TLS is required, but unavailable"));
+ }
+ }
+ }
+#endif
+#ifdef USE_SASL_AUTH
+ if (var_smtp_sasl_enable && (session->features & SMTP_FEATURE_AUTH))
+ return (smtp_sasl_helo_login(state));
+#endif
+
+ return (0);
+}
+
+#ifdef USE_TLS
+
+/* smtp_start_tls - turn on TLS and recurse into the HELO dialog */
+
+static int smtp_start_tls(SMTP_STATE *state)
+{
+ SMTP_SESSION *session = state->session;
+ SMTP_ITERATOR *iter = state->iterator;
+ TLS_CLIENT_START_PROPS start_props;
+ VSTRING *serverid;
+ SMTP_RESP fake;
+ TLS_CLIENT_INIT_PROPS init_props;
+ VSTREAM *tlsproxy;
+ VSTRING *port_buf;
+
+ /*
+ * When the TLS handshake succeeds, we can reuse a connection only if TLS
+ * remains turned on for the lifetime of that connection. This requires
+ * that the TLS library state is maintained in some proxy process, for
+ * example, in tlsproxy(8). We then store the proxy file handle in the
+ * connection cache, and reuse that file handle.
+ *
+ * Otherwise, we must turn off connection caching. We can't turn off TLS in
+ * one SMTP client process, save the open connection to a cache which is
+ * shared with all SMTP clients, migrate the connection to another SMTP
+ * client, and resume TLS there. When the TLS handshake fails, we can't
+ * reuse the SMTP connection either, because the conversation is in an
+ * unknown state.
+ */
+ if (state->tls->conn_reuse == 0)
+ DONT_CACHE_THIS_SESSION;
+
+ /*
+ * The following assumes sites that use TLS in a perverse configuration:
+ * multiple hosts per hostname, or even multiple hosts per IP address.
+ * All this without a shared TLS session cache, and they still want to
+ * use TLS session caching???
+ *
+ * The TLS session cache records the trust chain verification status of
+ * cached sessions. Different transports may have different CAfile or
+ * CApath settings, perhaps to allow authenticated connections to sites
+ * with private CA certs without trusting said private certs for other
+ * sites. So we cannot assume that a trust chain valid for one transport
+ * is valid for another. Therefore the client session id must include
+ * either the transport name or the values of CAfile and CApath. We use
+ * the transport name.
+ *
+ * XXX: We store only one session per lookup key. Ideally the the key maps
+ * 1-to-1 to a server TLS session cache. We use the IP address, port and
+ * ehlo response name to build a lookup key that works for split caches
+ * (that announce distinct names) behind a load balancer.
+ *
+ * XXX: The TLS library will salt the serverid with further details of the
+ * protocol and cipher requirements including the server ehlo response.
+ * Deferring the helo to the digested suffix results in more predictable
+ * SSL session lookup key lengths.
+ */
+ serverid = vstring_alloc(10);
+ smtp_key_prefix(serverid, "&", state->iterator, SMTP_KEY_FLAG_SERVICE
+ | SMTP_KEY_FLAG_CUR_NEXTHOP /* With port */
+ | SMTP_KEY_FLAG_HOSTNAME
+ | SMTP_KEY_FLAG_ADDR);
+
+ if (state->tls->conn_reuse) {
+ TLS_CLIENT_PARAMS tls_params;
+
+ /*
+ * Send all our wishes in one big request.
+ */
+ TLS_PROXY_CLIENT_INIT_PROPS(&init_props,
+ log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL),
+ log_level = var_smtp_tls_loglevel,
+ verifydepth = var_smtp_tls_scert_vd,
+ cache_type
+ = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE),
+ chain_files = var_smtp_tls_chain_files,
+ cert_file = var_smtp_tls_cert_file,
+ key_file = var_smtp_tls_key_file,
+ dcert_file = var_smtp_tls_dcert_file,
+ dkey_file = var_smtp_tls_dkey_file,
+ eccert_file = var_smtp_tls_eccert_file,
+ eckey_file = var_smtp_tls_eckey_file,
+ CAfile = var_smtp_tls_CAfile,
+ CApath = var_smtp_tls_CApath,
+ mdalg = var_smtp_tls_fpt_dgst);
+ TLS_PROXY_CLIENT_START_PROPS(&start_props,
+ timeout = var_smtp_starttls_tmout,
+ tls_level = state->tls->level,
+ nexthop = session->tls_nexthop,
+ host = STR(iter->host),
+ namaddr = session->namaddrport,
+ sni = state->tls->sni,
+ serverid = vstring_str(serverid),
+ helo = session->helo,
+ protocols = state->tls->protocols,
+ cipher_grade = state->tls->grade,
+ cipher_exclusions
+ = vstring_str(state->tls->exclusions),
+ matchargv = state->tls->matchargv,
+ mdalg = var_smtp_tls_fpt_dgst,
+ dane = state->tls->dane);
+
+ /*
+ * The tlsproxy(8) server enforces timeouts that are larger than
+ * those specified by the tlsproxy(8) client. These timeouts are a
+ * safety net for the case that the tlsproxy(8) client fails to
+ * enforce time limits. Normally, the tlsproxy(8) client would time
+ * out and trigger a plaintext event in the tlsproxy(8) server, and
+ * cause it to tear down the session.
+ *
+ * However, the tlsproxy(8) server has no insight into the SMTP
+ * protocol, and therefore it cannot by itself support different
+ * timeouts at different SMTP protocol stages. Instead, we specify
+ * the largest timeout (end-of-data) and rely on the SMTP client to
+ * time out first, which normally results in a plaintext event in the
+ * tlsproxy(8) server. Unfortunately, we cannot permit plaintext
+ * events during the TLS handshake, so we specify a separate timeout
+ * for that stage (the end-of-data timeout would be unreasonably
+ * large anyway).
+ */
+#define PROXY_OPEN_FLAGS \
+ (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT)
+
+ port_buf = vstring_alloc(100); /* minimize fragmentation */
+ vstring_sprintf(port_buf, "%d", ntohs(iter->port));
+ tlsproxy =
+ tls_proxy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS,
+ session->stream, STR(iter->addr),
+ STR(port_buf), var_smtp_starttls_tmout,
+ var_smtp_data2_tmout, state->service,
+ tls_proxy_client_param_from_config(&tls_params),
+ &init_props, &start_props);
+ vstring_free(port_buf);
+
+ /*
+ * To insert tlsproxy(8) between this process and the remote SMTP
+ * server, we swap the file descriptors between the tlsproxy and
+ * session->stream VSTREAMS, so that we don't lose all the
+ * user-configurable session->stream attributes (such as longjump
+ * buffers or timeouts).
+ *
+ * TODO: the tlsproxy RPCs should return more error detail than a "NO"
+ * result. OTOH, the in-process TLS engine does not return such info
+ * either.
+ *
+ * If the tlsproxy request fails we do not fall back to the in-process
+ * TLS stack. Reason: the admin enabled connection reuse to respect
+ * receiver policy; silently violating such policy would not be
+ * useful.
+ *
+ * We also don't fall back to the in-process TLS stack under low-traffic
+ * conditions, to avoid frustrating attempts to debug a problem with
+ * using the tlsproxy(8) service.
+ */
+ if (tlsproxy == 0) {
+ session->tls_context = 0;
+ } else {
+ vstream_control(tlsproxy,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_END);
+ vstream_control(session->stream,
+ CA_VSTREAM_CTL_SWAP_FD(tlsproxy),
+ CA_VSTREAM_CTL_END);
+ (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */
+
+ /*
+ * There must not be any pending data in the stream buffers
+ * before we read the TLS context attributes.
+ */
+ vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
+
+ /*
+ * After plumbing the plaintext stream, receive the TLS context
+ * object. For this we use the same VSTREAM buffer that we also
+ * use to receive subsequent SMTP commands, therefore we must be
+ * prepared for the possibility that the remote SMTP server
+ * starts talking immediately. The tlsproxy implementation sends
+ * the TLS context before remote content. The attribute protocol
+ * is robust enough that an adversary cannot insert their own TLS
+ * context attributes.
+ */
+ session->tls_context = tls_proxy_context_receive(session->stream);
+ if (session->tls_context) {
+ session->features |= SMTP_FEATURE_FROM_PROXY;
+ tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW,
+ session->tls_context);
+ }
+ }
+ } else { /* state->tls->conn_reuse */
+
+ /*
+ * As of Postfix 2.5, tls_client_start() tries hard to always
+ * complete the TLS handshake. It records the verification and match
+ * status in the resulting TLScontext. It is now up to the
+ * application to abort the TLS connection if it chooses.
+ *
+ * XXX When tls_client_start() fails then we don't know what state the
+ * SMTP connection is in, so we give up on this connection even if we
+ * are not required to use TLS.
+ *
+ * Large parameter lists are error-prone, so we emulate a language
+ * feature that C does not have natively: named parameter lists.
+ */
+ session->tls_context =
+ TLS_CLIENT_START(&start_props,
+ ctx = smtp_tls_ctx,
+ stream = session->stream,
+ fd = -1,
+ timeout = var_smtp_starttls_tmout,
+ tls_level = state->tls->level,
+ nexthop = session->tls_nexthop,
+ host = STR(iter->host),
+ namaddr = session->namaddrport,
+ sni = state->tls->sni,
+ serverid = vstring_str(serverid),
+ helo = session->helo,
+ protocols = state->tls->protocols,
+ cipher_grade = state->tls->grade,
+ cipher_exclusions
+ = vstring_str(state->tls->exclusions),
+ matchargv = state->tls->matchargv,
+ mdalg = var_smtp_tls_fpt_dgst,
+ dane = state->tls->dane);
+
+ /*
+ * At this point there must not be any pending data in the stream
+ * buffers.
+ */
+ vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
+ } /* state->tls->conn_reuse */
+
+ vstring_free(serverid);
+
+ if (session->tls_context == 0) {
+
+ /*
+ * We must avoid further I/O, the peer is in an undefined state.
+ */
+ DONT_USE_FORBIDDEN_SESSION;
+
+ /*
+ * If TLS is optional, try delivery to the same server over a
+ * plaintext connection. Otherwise we would defer mail forever with
+ * destinations that have no alternate MX host.
+ *
+ * Don't fall back to plaintext if we were willing to use SASL-over-TLS
+ * authentication. If the server doesn't announce SASL support over
+ * plaintext connections, then we don't want delivery to fail with
+ * "relay access denied".
+ *
+ * If TLS is opportunistic, don't throttle the destination, otherwise if
+ * the mail is volume is high enough we may have difficulty ever
+ * draining even the deferred mail, as new mail provides a constant
+ * stream of negative feedback.
+ */
+ if (PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE)
+ RETRY_AS_PLAINTEXT;
+ return (smtp_misc_fail(state, state->tls->level == TLS_LEV_MAY ?
+ SMTP_NOTHROTTLE : SMTP_THROTTLE,
+ DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.5"),
+ "Cannot start TLS: handshake failure"));
+ }
+
+ /*
+ * If we are verifying the server certificate and are not happy with the
+ * result, abort the delivery here. We have a usable TLS session with the
+ * server, so no need to disable I/O, ... we can even be polite and send
+ * "QUIT".
+ *
+ * See src/tls/tls_level.c and src/tls/tls.h. Levels above "encrypt" require
+ * matching. Levels >= "dane" require CA or DNSSEC trust.
+ *
+ * When DANE TLSA records specify an end-entity certificate, the trust and
+ * match bits always coincide, but it is fine to report the wrong
+ * end-entity certificate as untrusted rather than unmatched.
+ */
+ if (TLS_MUST_TRUST(state->tls->level))
+ if (!TLS_CERT_IS_TRUSTED(session->tls_context))
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.5"),
+ "Server certificate not trusted"));
+ if (TLS_MUST_MATCH(state->tls->level))
+ if (!TLS_CERT_IS_MATCHED(session->tls_context))
+ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.5"),
+ "Server certificate not verified"));
+
+ /*
+ * At this point we have to re-negotiate the "EHLO" to reget the
+ * feature-list.
+ */
+ return (smtp_helo(state));
+}
+
+#endif
+
+/* smtp_hbc_logger - logging call-back for header/body checks */
+
+static void smtp_hbc_logger(void *context, const char *action,
+ const char *where, const char *content,
+ const char *text)
+{
+ const SMTP_STATE *state = (SMTP_STATE *) context;
+
+ if (*text) {
+ msg_info("%s: %s: %s %.60s: %s",
+ state->request->queue_id, action, where, content, text);
+ } else {
+ msg_info("%s: %s: %s %.60s",
+ state->request->queue_id, action, where, content);
+ }
+}
+
+/* smtp_text_out - output one header/body record */
+
+static void smtp_text_out(void *context, int rec_type,
+ const char *text, ssize_t len,
+ off_t unused_offset)
+{
+ SMTP_STATE *state = (SMTP_STATE *) context;
+ SMTP_SESSION *session = state->session;
+ ssize_t data_left;
+ const char *data_start;
+
+ /*
+ * Deal with an impedance mismatch between Postfix queue files (record
+ * length <= $message_line_length_limit) and SMTP (DATA record length <=
+ * $smtp_line_length_limit). The code below does a little too much work
+ * when the SMTP line length limit is disabled, but it avoids code
+ * duplication, and thus, it avoids testing and maintenance problems.
+ */
+ data_left = len;
+ data_start = text;
+ do {
+ if (state->space_left == var_smtp_line_limit
+ && data_left > 0 && *data_start == '.')
+ smtp_fputc('.', session->stream);
+ if (var_smtp_line_limit > 0 && data_left >= state->space_left) {
+ smtp_fputs(data_start, state->space_left, session->stream);
+ data_start += state->space_left;
+ data_left -= state->space_left;
+ state->space_left = var_smtp_line_limit;
+ if (data_left > 0 || rec_type == REC_TYPE_CONT) {
+ smtp_fputc(' ', session->stream);
+ state->space_left -= 1;
+ }
+ } else {
+ if (rec_type == REC_TYPE_CONT) {
+ smtp_fwrite(data_start, data_left, session->stream);
+ state->space_left -= data_left;
+ } else {
+ smtp_fputs(data_start, data_left, session->stream);
+ state->space_left = var_smtp_line_limit;
+ }
+ break;
+ }
+ } while (data_left > 0);
+}
+
+/* smtp_format_out - output one header/body record */
+
+static void PRINTFLIKE(3, 4) smtp_format_out(void *, int, const char *,...);
+
+static void smtp_format_out(void *context, int rec_type, const char *fmt,...)
+{
+ static VSTRING *vp;
+ va_list ap;
+
+ if (vp == 0)
+ vp = vstring_alloc(100);
+ va_start(ap, fmt);
+ vstring_vsprintf(vp, fmt, ap);
+ va_end(ap);
+ smtp_text_out(context, rec_type, vstring_str(vp), VSTRING_LEN(vp), 0);
+}
+
+/* smtp_header_out - output one message header */
+
+static void smtp_header_out(void *context, int unused_header_class,
+ const HEADER_OPTS *unused_info,
+ VSTRING *buf, off_t offset)
+{
+ char *start = vstring_str(buf);
+ char *line;
+ char *next_line;
+
+ /*
+ * This code destroys the header. We could try to avoid clobbering it,
+ * but we're not going to use the data any further.
+ */
+ for (line = start; line; line = next_line) {
+ next_line = split_at(line, '\n');
+ smtp_text_out(context, REC_TYPE_NORM, line, next_line ?
+ next_line - line - 1 : strlen(line), offset);
+ }
+}
+
+/* smtp_header_rewrite - rewrite message header before output */
+
+static void smtp_header_rewrite(void *context, int header_class,
+ const HEADER_OPTS *header_info,
+ VSTRING *buf, off_t offset)
+{
+ SMTP_STATE *state = (SMTP_STATE *) context;
+ int did_rewrite = 0;
+ char *line;
+ char *start;
+ char *next_line;
+ char *end_line;
+ char *result;
+
+ /*
+ * Apply optional header filtering.
+ */
+ if (smtp_header_checks) {
+ result = hbc_header_checks(context, smtp_header_checks, header_class,
+ header_info, buf, offset);
+ if (result == 0)
+ return;
+ if (result == HBC_CHECKS_STAT_ERROR) {
+ msg_warn("%s: smtp header checks lookup error",
+ state->request->queue_id);
+ vstream_longjmp(state->session->stream, SMTP_ERR_DATA);
+ }
+ if (result != STR(buf)) {
+ vstring_strcpy(buf, result);
+ myfree(result);
+ }
+ }
+
+ /*
+ * Rewrite primary header addresses that match the smtp_generic_maps. The
+ * cleanup server already enforces that all headers have proper lengths
+ * and that all addresses are in proper form, so we don't have to repeat
+ * that.
+ */
+ if (smtp_generic_maps && header_info && header_class == MIME_HDR_PRIMARY
+ && (header_info->flags & (HDR_OPT_SENDER | HDR_OPT_RECIP)) != 0) {
+ TOK822 *tree;
+ TOK822 **addr_list;
+ TOK822 **tpp;
+
+ tree = tok822_parse(vstring_str(buf)
+ + strlen(header_info->name) + 1);
+ addr_list = tok822_grep(tree, TOK822_ADDR);
+ for (tpp = addr_list; *tpp; tpp++)
+ did_rewrite |= smtp_map11_tree(tpp[0], smtp_generic_maps,
+ smtp_ext_prop_mask & EXT_PROP_GENERIC);
+ if (did_rewrite) {
+ vstring_truncate(buf, strlen(header_info->name));
+ vstring_strcat(buf, ": ");
+ tok822_externalize(buf, tree, TOK822_STR_HEAD);
+ }
+ myfree((void *) addr_list);
+ tok822_free_tree(tree);
+ }
+
+ /*
+ * Pass through unmodified headers without reconstruction.
+ */
+ if (did_rewrite == 0) {
+ smtp_header_out(context, header_class, header_info, buf, offset);
+ return;
+ }
+
+ /*
+ * A rewritten address list contains one address per line. The code below
+ * replaces newlines by spaces, to fit as many addresses on a line as
+ * possible (without rearranging the order of addresses). Prepending
+ * white space to the beginning of lines is delegated to the output
+ * routine.
+ *
+ * Code derived from cleanup_fold_header().
+ */
+ for (line = start = vstring_str(buf); line != 0; line = next_line) {
+ end_line = line + strcspn(line, "\n");
+ if (line > start) {
+ if (end_line - start < 70) { /* TAB counts as one */
+ line[-1] = ' ';
+ } else {
+ start = line;
+ }
+ }
+ next_line = *end_line ? end_line + 1 : 0;
+ }
+
+ /*
+ * Prepend a tab to continued header lines that went through the address
+ * rewriting machinery. Just like smtp_header_out(), this code destroys
+ * the header. We could try to avoid clobbering it, but we're not going
+ * to use the data any further.
+ *
+ * Code derived from cleanup_out_header().
+ */
+ for (line = start = vstring_str(buf); line != 0; line = next_line) {
+ next_line = split_at(line, '\n');
+ if (line == start || IS_SPACE_TAB(*line)) {
+ smtp_text_out(state, REC_TYPE_NORM, line, next_line ?
+ next_line - line - 1 : strlen(line), offset);
+ } else {
+ smtp_format_out(state, REC_TYPE_NORM, "\t%s", line);
+ }
+ }
+}
+
+/* smtp_body_rewrite - rewrite message body before output */
+
+static void smtp_body_rewrite(void *context, int type,
+ const char *buf, ssize_t len,
+ off_t offset)
+{
+ SMTP_STATE *state = (SMTP_STATE *) context;
+ char *result;
+
+ /*
+ * Apply optional body filtering.
+ */
+ if (smtp_body_checks) {
+ result = hbc_body_checks(context, smtp_body_checks, buf, len, offset);
+ if (result == buf) {
+ smtp_text_out(state, type, buf, len, offset);
+ } else if (result == HBC_CHECKS_STAT_ERROR) {
+ msg_warn("%s: smtp body checks lookup error",
+ state->request->queue_id);
+ vstream_longjmp(state->session->stream, SMTP_ERR_DATA);
+ } else if (result != 0) {
+ smtp_text_out(state, type, result, strlen(result), offset);
+ myfree(result);
+ }
+ }
+}
+
+/* smtp_mime_fail - MIME problem */
+
+static void smtp_mime_fail(SMTP_STATE *state, int mime_errs)
+{
+ const MIME_STATE_DETAIL *detail;
+ SMTP_RESP fake;
+
+ detail = mime_state_detail(mime_errs);
+ smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, detail->dsn),
+ "%s", detail->text);
+}
+
+/* smtp_loop - exercise the SMTP protocol engine */
+
+static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
+ NOCLOBBER int recv_state)
+{
+ const char *myname = "smtp_loop";
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ SMTP_ITERATOR *iter = state->iterator;
+ SMTP_RESP *resp;
+ RECIPIENT *rcpt;
+ VSTRING *next_command = vstring_alloc(100);
+ int *NOCLOBBER survivors = 0;
+ NOCLOBBER int next_state;
+ NOCLOBBER int next_rcpt;
+ NOCLOBBER int send_rcpt;
+ NOCLOBBER int recv_rcpt;
+ NOCLOBBER int nrcpt;
+ NOCLOBBER int recv_done;
+ int except;
+ int rec_type;
+ NOCLOBBER int prev_type = 0;
+ NOCLOBBER int mail_from_rejected;
+ NOCLOBBER int downgrading;
+ int mime_errs;
+ SMTP_RESP fake;
+ int fail_status;
+
+ /*
+ * Macros for readability.
+ */
+#define REWRITE_ADDRESS(dst, src) do { \
+ vstring_strcpy(dst, src); \
+ if (*(src) && smtp_generic_maps) \
+ smtp_map11_internal(dst, smtp_generic_maps, \
+ smtp_ext_prop_mask & EXT_PROP_GENERIC); \
+ } while (0)
+
+#define QUOTE_ADDRESS(dst, src) do { \
+ if (*(src) && var_smtp_quote_821_env) { \
+ quote_821_local(dst, src); \
+ } else { \
+ vstring_strcpy(dst, src); \
+ } \
+ } while (0)
+
+ /* Caution: changes to RETURN() also affect code outside the main loop. */
+
+#define RETURN(x) do { \
+ if (recv_state != SMTP_STATE_LAST) \
+ DONT_CACHE_THIS_SESSION; \
+ vstring_free(next_command); \
+ if (survivors) \
+ myfree((void *) survivors); \
+ if (session->mime_state) \
+ session->mime_state = mime_state_free(session->mime_state); \
+ return (x); \
+ } while (0)
+
+#define SENDER_IS_AHEAD \
+ (recv_state < send_state || recv_rcpt != send_rcpt)
+
+#define SENDER_IN_WAIT_STATE \
+ (send_state == SMTP_STATE_DOT || send_state == SMTP_STATE_LAST)
+
+#define SENDING_MAIL \
+ (recv_state <= SMTP_STATE_DOT)
+
+#define CANT_RSET_THIS_SESSION \
+ (session->features |= SMTP_FEATURE_RSET_REJECTED)
+
+ /*
+ * Pipelining support requires two loops: one loop for sending and one
+ * for receiving. Each loop has its own independent state. Most of the
+ * time the sender can run ahead of the receiver by as much as the TCP
+ * send buffer permits. There are only two places where the sender must
+ * wait for status information from the receiver: once after sending DATA
+ * and once after sending QUIT.
+ *
+ * The sender state advances until the TCP send buffer would overflow, or
+ * until the sender needs status information from the receiver. At that
+ * point the receiver starts processing responses. Once the receiver has
+ * caught up with the sender, the sender resumes sending commands. If the
+ * receiver detects a serious problem (MAIL FROM rejected, all RCPT TO
+ * commands rejected, DATA rejected) it forces the sender to abort the
+ * SMTP dialog with RSET and QUIT.
+ */
+ nrcpt = 0;
+ next_rcpt = send_rcpt = recv_rcpt = recv_done = 0;
+ mail_from_rejected = 0;
+
+ /*
+ * Prepare for disaster. This should not be needed because the design
+ * guarantees that no output is flushed before smtp_chat_resp() is
+ * called.
+ *
+ * 1) Every SMTP command fits entirely in a VSTREAM output buffer.
+ *
+ * 2) smtp_loop() never invokes smtp_chat_cmd() without making sure that
+ * there is sufficient space for the command in the output buffer.
+ *
+ * 3) smtp_loop() flushes the output buffer to avoid server timeouts.
+ *
+ * Changing any of these would violate the design, and would likely break
+ * SMTP pipelining.
+ *
+ * We set up the error handler anyway (only upon entry to avoid wasting
+ * resources) because 1) there is code below that expects that VSTREAM
+ * timeouts are enabled, and 2) this allows us to detect if someone broke
+ * Postfix by introducing spurious flush before read operations.
+ */
+ if (send_state < SMTP_STATE_XFORWARD_NAME_ADDR
+ || send_state > SMTP_STATE_QUIT)
+ msg_panic("%s: bad sender state %d (receiver state %d)",
+ myname, send_state, recv_state);
+ smtp_stream_setup(session->stream, *xfer_timeouts[send_state],
+ var_smtp_rec_deadline);
+ if ((except = vstream_setjmp(session->stream)) != 0) {
+ msg_warn("smtp_proto: spurious flush before read in send state %d",
+ send_state);
+ RETURN(SENDING_MAIL ? smtp_stream_except(state, except,
+ xfer_states[send_state]) : -1);
+ }
+
+ /*
+ * The main protocol loop.
+ */
+ do {
+
+ /*
+ * Build the next command.
+ */
+ switch (send_state) {
+
+ /*
+ * Sanity check.
+ */
+ default:
+ msg_panic("%s: bad sender state %d", myname, send_state);
+
+ /*
+ * Build the XFORWARD command. With properly sanitized
+ * information, the command length stays within the 512 byte
+ * command line length limit.
+ *
+ * XXX smtpd_xforward_preset() initializes some fields as "unknown"
+ * and some as null; historically, pickup(8) does not send any of
+ * these, and the queue manager presets absent fields to "not
+ * available" except for the rewrite context which is preset to
+ * local by way of migration aid. These definitions need to be
+ * centralized for maintainability.
+ */
+#ifndef CAN_FORWARD_CLIENT_NAME
+#define _ATTR_AVAIL_AND_KNOWN_(val) \
+ (DEL_REQ_ATTR_AVAIL(val) && strcasecmp((val), "unknown"))
+#define CAN_FORWARD_CLIENT_NAME _ATTR_AVAIL_AND_KNOWN_
+#define CAN_FORWARD_CLIENT_ADDR _ATTR_AVAIL_AND_KNOWN_
+#define CAN_FORWARD_CLIENT_PORT _ATTR_AVAIL_AND_KNOWN_
+#define CAN_FORWARD_PROTO_NAME _ATTR_AVAIL_AND_KNOWN_
+#define CAN_FORWARD_HELO_NAME DEL_REQ_ATTR_AVAIL
+#define CAN_FORWARD_IDENT_NAME DEL_REQ_ATTR_AVAIL
+#define CAN_FORWARD_RWR_CONTEXT DEL_REQ_ATTR_AVAIL
+#endif
+
+ case SMTP_STATE_XFORWARD_NAME_ADDR:
+ vstring_strcpy(next_command, XFORWARD_CMD);
+ if ((session->features & SMTP_FEATURE_XFORWARD_NAME)
+ && CAN_FORWARD_CLIENT_NAME(request->client_name)) {
+ vstring_strcat(next_command, " " XFORWARD_NAME "=");
+ xtext_quote_append(next_command, request->client_name, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_ADDR)
+ && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) {
+ vstring_strcat(next_command, " " XFORWARD_ADDR "=");
+ xtext_quote_append(next_command, request->client_addr, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_PORT)
+ && CAN_FORWARD_CLIENT_PORT(request->client_port)) {
+ vstring_strcat(next_command, " " XFORWARD_PORT "=");
+ xtext_quote_append(next_command, request->client_port, "");
+ }
+ if (session->send_proto_helo)
+ next_state = SMTP_STATE_XFORWARD_PROTO_HELO;
+ else
+ next_state = SMTP_STATE_MAIL;
+ break;
+
+ case SMTP_STATE_XFORWARD_PROTO_HELO:
+ vstring_strcpy(next_command, XFORWARD_CMD);
+ if ((session->features & SMTP_FEATURE_XFORWARD_PROTO)
+ && CAN_FORWARD_PROTO_NAME(request->client_proto)) {
+ vstring_strcat(next_command, " " XFORWARD_PROTO "=");
+ xtext_quote_append(next_command, request->client_proto, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_HELO)
+ && CAN_FORWARD_HELO_NAME(request->client_helo)) {
+ vstring_strcat(next_command, " " XFORWARD_HELO "=");
+ xtext_quote_append(next_command, request->client_helo, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_IDENT)
+ && CAN_FORWARD_IDENT_NAME(request->log_ident)) {
+ vstring_strcat(next_command, " " XFORWARD_IDENT "=");
+ xtext_quote_append(next_command, request->log_ident, "");
+ }
+ if ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN)
+ && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)) {
+ vstring_strcat(next_command, " " XFORWARD_DOMAIN "=");
+ xtext_quote_append(next_command,
+ strcmp(request->rewrite_context, MAIL_ATTR_RWR_LOCAL) ?
+ XFORWARD_DOM_REMOTE : XFORWARD_DOM_LOCAL, "");
+ }
+ next_state = SMTP_STATE_MAIL;
+ break;
+
+ /*
+ * Build the MAIL FROM command.
+ */
+ case SMTP_STATE_MAIL:
+ request->msg_stats.reuse_count = session->reuse_count;
+ GETTIMEOFDAY(&request->msg_stats.conn_setup_done);
+ REWRITE_ADDRESS(session->scratch2, request->sender);
+ QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2));
+ vstring_sprintf(next_command, "MAIL FROM:<%s>",
+ vstring_str(session->scratch));
+ /* XXX Don't announce SIZE if we're going to MIME downgrade. */
+ if (session->features & SMTP_FEATURE_SIZE /* RFC 1870 */
+ && !SMTP_MIME_DOWNGRADE(session, request))
+ vstring_sprintf_append(next_command, " SIZE=%lu",
+ request->data_size);
+ if (session->features & SMTP_FEATURE_8BITMIME) { /* RFC 1652 */
+ if (strcmp(request->encoding, MAIL_ATTR_ENC_8BIT) == 0)
+ vstring_strcat(next_command, " BODY=8BITMIME");
+ else if (strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) == 0)
+ vstring_strcat(next_command, " BODY=7BIT");
+ else if (strcmp(request->encoding, MAIL_ATTR_ENC_NONE) != 0)
+ msg_warn("%s: unknown content encoding: %s",
+ request->queue_id, request->encoding);
+ }
+ if (session->features & SMTP_FEATURE_DSN) {
+ if (request->dsn_envid[0]) {
+ vstring_sprintf_append(next_command, " ENVID=");
+ xtext_quote_append(next_command, request->dsn_envid, "+=");
+ }
+ if (request->dsn_ret)
+ vstring_sprintf_append(next_command, " RET=%s",
+ dsn_ret_str(request->dsn_ret));
+ }
+
+ /*
+ * Request SMTPUTF8 when the remote SMTP server supports SMTPUTF8
+ * and the sender requested SMTPUTF8 support.
+ *
+ * If the sender requested SMTPUTF8 but the remote SMTP server does
+ * not support SMTPUTF8, then we have already determined earlier
+ * that delivering this message without SMTPUTF8 will not break
+ * the SMTPUTF8 promise that was made to the sender.
+ */
+ if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0
+ && (request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) != 0)
+ vstring_strcat(next_command, " SMTPUTF8");
+
+ /*
+ * We authenticate the local MTA only, but not the sender.
+ */
+#ifdef USE_SASL_AUTH
+ if (var_smtp_sasl_enable
+ && var_smtp_dummy_mail_auth
+ && (session->features & SMTP_FEATURE_AUTH))
+ vstring_strcat(next_command, " AUTH=<>");
+#endif
+
+ /*
+ * CVE-2009-3555 (TLS renegotiation). Try to detect a mail
+ * hijacking attack that prepends malicious EHLO/MAIL/RCPT/DATA
+ * commands to our TLS session.
+ *
+ * For the attack to succeed, the remote SMTP server must reply to
+ * the malicious EHLO/MAIL/RCPT/DATA commands after completing
+ * TLS (re)negotiation, so that the replies arrive in our TLS
+ * session (otherwise the Postfix SMTP client would time out
+ * waiting for an answer). With some luck we can detect this
+ * specific attack as a server MAIL reply that arrives before we
+ * send our own MAIL command.
+ *
+ * We don't apply this test to the HELO command because the result
+ * would be very timing sensitive, and we don't apply this test
+ * to RCPT and DATA replies because these may be pipelined for
+ * legitimate reasons.
+ */
+#ifdef USE_TLS
+ if (var_smtp_tls_blk_early_mail_reply
+ && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) != 0
+ && (vstream_peek(session->stream) > 0
+ || peekfd(vstream_fileno(session->stream)) > 0))
+ session->features |= SMTP_FEATURE_EARLY_TLS_MAIL_REPLY;
+#endif
+
+ /*
+ * We now return to our regular broadcast.
+ */
+ next_state = SMTP_STATE_RCPT;
+ break;
+
+ /*
+ * Build one RCPT TO command before we have seen the MAIL FROM
+ * response.
+ */
+ case SMTP_STATE_RCPT:
+ rcpt = request->rcpt_list.info + send_rcpt;
+ REWRITE_ADDRESS(session->scratch2, rcpt->address);
+ QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2));
+ vstring_sprintf(next_command, "RCPT TO:<%s>",
+ vstring_str(session->scratch));
+ if (session->features & SMTP_FEATURE_DSN) {
+ /* XXX DSN xtext encode address value not type. */
+ const char *orcpt_type_addr = rcpt->dsn_orcpt;
+
+ /* Fix 20140706: don't use empty rcpt->orig_addr. */
+ if (orcpt_type_addr[0] == 0 && rcpt->orig_addr[0] != 0) {
+ quote_822_local(session->scratch, rcpt->orig_addr);
+ vstring_sprintf(session->scratch2, "%s;%s",
+ /* Fix 20140707: sender must request SMTPUTF8. */
+ (request->smtputf8 != 0
+ && !allascii(vstring_str(session->scratch))) ?
+ "utf-8" : "rfc822",
+ vstring_str(session->scratch));
+ orcpt_type_addr = vstring_str(session->scratch2);
+ }
+ if (orcpt_type_addr[0] != 0) {
+ /* Fix 20140706: don't send unquoted ORCPT. */
+ /* Fix 20140707: quoting method must match orcpt type. */
+ /* Fix 20140707: handle uxtext encoder errors. */
+ if (strncasecmp(orcpt_type_addr, "utf-8;", 6) == 0) {
+ if (uxtext_quote(session->scratch,
+ orcpt_type_addr, "+=") != 0)
+ vstring_sprintf_append(next_command, " ORCPT=%s",
+ vstring_str(session->scratch));
+ } else {
+ xtext_quote(session->scratch, orcpt_type_addr, "=");
+ vstring_sprintf_append(next_command, " ORCPT=%s",
+ vstring_str(session->scratch));
+ }
+ }
+ if (rcpt->dsn_notify)
+ vstring_sprintf_append(next_command, " NOTIFY=%s",
+ dsn_notify_str(rcpt->dsn_notify));
+ }
+ if ((next_rcpt = send_rcpt + 1) == SMTP_RCPT_LEFT(state))
+ next_state = (DEL_REQ_TRACE_ONLY(request->flags)
+ && smtp_vrfy_tgt == SMTP_STATE_RCPT) ?
+ SMTP_STATE_ABORT : SMTP_STATE_DATA;
+ break;
+
+ /*
+ * Build the DATA command before we have seen all the RCPT TO
+ * responses.
+ */
+ case SMTP_STATE_DATA:
+ vstring_strcpy(next_command, "DATA");
+ next_state = SMTP_STATE_DOT;
+ break;
+
+ /*
+ * Build the "." command after we have seen the DATA response
+ * (DATA is a protocol synchronization point).
+ *
+ * Changing the connection caching state here is safe because it
+ * affects none of the not-yet processed replies to
+ * already-generated commands.
+ */
+ case SMTP_STATE_DOT:
+ vstring_strcpy(next_command, ".");
+ if (THIS_SESSION_IS_EXPIRED)
+ DONT_CACHE_THIS_SESSION;
+ next_state = THIS_SESSION_IS_CACHED ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT;
+ break;
+
+ /*
+ * The SMTP_STATE_ABORT sender state is entered by the sender
+ * when it has verified all recipients; or it is entered by the
+ * receiver when all recipients are verified or rejected, and is
+ * then left before the bottom of the main loop.
+ *
+ * Changing the connection caching state here is safe because there
+ * are no not-yet processed replies to already-generated
+ * commands.
+ */
+ case SMTP_STATE_ABORT:
+ vstring_strcpy(next_command, "RSET");
+ if (THIS_SESSION_IS_EXPIRED)
+ DONT_CACHE_THIS_SESSION;
+ next_state = THIS_SESSION_IS_CACHED ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT;
+ break;
+
+ /*
+ * Build the RSET command. This is entered as initial state from
+ * smtp_rset() and has its own dedicated state transitions. It is
+ * used to find out the status of a cached session before
+ * attempting mail delivery.
+ */
+ case SMTP_STATE_RSET:
+ vstring_strcpy(next_command, "RSET");
+ next_state = SMTP_STATE_LAST;
+ break;
+
+ /*
+ * Build the QUIT command before we have seen the "." or RSET
+ * response. This is entered as initial state from smtp_quit(),
+ * or is reached near the end of any non-cached session.
+ *
+ * Changing the connection caching state here is safe. If this
+ * command is pipelined together with a preceding command, then
+ * connection caching was already turned off. Do not clobber the
+ * "bad connection" flag.
+ */
+ case SMTP_STATE_QUIT:
+ vstring_strcpy(next_command, "QUIT");
+ next_state = SMTP_STATE_LAST;
+ if (THIS_SESSION_IS_CACHED)
+ DONT_CACHE_THIS_SESSION;
+ break;
+
+ /*
+ * The final sender state has no action associated with it.
+ */
+ case SMTP_STATE_LAST:
+ VSTRING_RESET(next_command);
+ break;
+ }
+ VSTRING_TERMINATE(next_command);
+
+ /*
+ * Process responses until the receiver has caught up. Vstreams
+ * automatically flush buffered output when reading new data.
+ *
+ * Flush unsent output if command pipelining is off or if no I/O
+ * happened for a while. This limits the accumulation of client-side
+ * delays in pipelined sessions.
+ *
+ * The PIPELINING engine will flush the VSTREAM buffer if the sender
+ * could otherwise produce more output than fits the PIPELINING
+ * buffer. This generally works because we know exactly how much
+ * output we produced since the last time that the sender and
+ * receiver synchronized the SMTP state. However this logic is not
+ * applicable after the sender enters the DATA phase, where it does
+ * not synchronize with the receiver until the <CR><LF>.<CR><LF>.
+ * Thus, the PIPELINING engine no longer knows how much data is
+ * pending in the TCP send buffer. For this reason, if PIPELINING is
+ * enabled, we always pipeline QUIT after <CR><LF>.<CR><LF>. This is
+ * safe because once the receiver reads <CR><LF>.<CR><LF>, its TCP
+ * stack either has already received the QUIT<CR><LF>, or else it
+ * acknowledges all bytes up to and including <CR><LF>.<CR><LF>,
+ * making room in the sender's TCP stack for QUIT<CR><LF>.
+ */
+#define CHECK_PIPELINING_BUFSIZE \
+ (recv_state != SMTP_STATE_DOT || send_state != SMTP_STATE_QUIT)
+
+ if (SENDER_IN_WAIT_STATE
+ || (SENDER_IS_AHEAD
+ && ((session->features & SMTP_FEATURE_PIPELINING) == 0
+ || (CHECK_PIPELINING_BUFSIZE
+ && (VSTRING_LEN(next_command) + 2
+ + vstream_bufstat(session->stream, VSTREAM_BST_OUT_PEND)
+ > PIPELINING_BUFSIZE))
+ || time((time_t *) 0)
+ - vstream_ftime(session->stream) > 10))) {
+ while (SENDER_IS_AHEAD) {
+
+ /*
+ * Sanity check.
+ */
+ if (recv_state < SMTP_STATE_XFORWARD_NAME_ADDR
+ || recv_state > SMTP_STATE_QUIT)
+ msg_panic("%s: bad receiver state %d (sender state %d)",
+ myname, recv_state, send_state);
+
+ /*
+ * Receive the next server response. Use the proper timeout,
+ * and log the proper client state in case of trouble.
+ *
+ * XXX If we lose the connection before sending end-of-data,
+ * find out if the server sent a premature end-of-data reply.
+ * If this read attempt fails, report "lost connection while
+ * sending message body", not "lost connection while sending
+ * end-of-data".
+ *
+ * "except" becomes zero just above the protocol loop, and stays
+ * zero or triggers an early return from the loop. In just
+ * one case: loss of the connection when sending the message
+ * body, we record the exception, and keep processing in the
+ * hope of detecting a premature 5XX. We must be careful to
+ * not clobber this non-zero value once it is set. The
+ * variable need not survive longjmp() calls, since the only
+ * setjmp() which does not return early is the one sets this
+ * condition, subquent failures always return early.
+ */
+#define LOST_CONNECTION_INSIDE_DATA (except == SMTP_ERR_EOF)
+
+ smtp_stream_setup(session->stream, *xfer_timeouts[recv_state],
+ var_smtp_rec_deadline);
+ if (LOST_CONNECTION_INSIDE_DATA) {
+ if (vstream_setjmp(session->stream) != 0)
+ RETURN(smtp_stream_except(state, SMTP_ERR_EOF,
+ "sending message body"));
+ } else {
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ RETURN(SENDING_MAIL ? smtp_stream_except(state, except,
+ xfer_states[recv_state]) : -1);
+ }
+ resp = smtp_chat_resp(session);
+
+ /*
+ * Process the response.
+ */
+ switch (recv_state) {
+
+ /*
+ * Process the XFORWARD response.
+ */
+ case SMTP_STATE_XFORWARD_NAME_ADDR:
+ if (resp->code / 100 != 2)
+ msg_warn("host %s said: %s (in reply to %s)",
+ session->namaddrport,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_XFORWARD_NAME_ADDR]);
+ if (session->send_proto_helo)
+ recv_state = SMTP_STATE_XFORWARD_PROTO_HELO;
+ else
+ recv_state = SMTP_STATE_MAIL;
+ break;
+
+ case SMTP_STATE_XFORWARD_PROTO_HELO:
+ if (resp->code / 100 != 2)
+ msg_warn("host %s said: %s (in reply to %s)",
+ session->namaddrport,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_XFORWARD_PROTO_HELO]);
+ recv_state = SMTP_STATE_MAIL;
+ break;
+
+ /*
+ * Process the MAIL FROM response. When the server
+ * rejects the sender, set the mail_from_rejected flag so
+ * that the receiver may apply a course correction.
+ */
+ case SMTP_STATE_MAIL:
+ if (resp->code / 100 != 2) {
+ smtp_mesg_fail(state, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_MAIL]);
+ mail_from_rejected = 1;
+ }
+
+ /*
+ * CVE-2009-3555 (TLS renegotiation). Whatever it was
+ * that arrived before we sent our MAIL FROM command, it
+ * was not a fatal-level TLS alert message. It could be a
+ * warning-level TLS alert message, or a ChangeCipherSpec
+ * message, but such messages are not normally sent in
+ * the middle of a TLS session. We disconnect and try
+ * again later.
+ */
+#ifdef USE_TLS
+ if (var_smtp_tls_blk_early_mail_reply
+ && (session->features & SMTP_FEATURE_EARLY_TLS_MAIL_REPLY)) {
+ smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "4.7.0"),
+ "unexpected server message");
+ msg_warn("server %s violates %s policy",
+ session->namaddr,
+ VAR_LMTP_SMTP(TLS_BLK_EARLY_MAIL_REPLY));
+ mail_from_rejected = 1;
+ }
+#endif
+
+ /*
+ * We now return to our regular broadcast.
+ */
+ recv_state = SMTP_STATE_RCPT;
+ break;
+
+ /*
+ * Process one RCPT TO response. If MAIL FROM was
+ * rejected, ignore RCPT TO responses: all recipients are
+ * dead already. When all recipients are rejected the
+ * receiver may apply a course correction.
+ *
+ * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply
+ * must be treated as if the server replied with 452.
+ * However, this causes "too much mail data" to be
+ * treated as a recoverable error, which is wrong. I'll
+ * stick with RFC 821.
+ */
+ case SMTP_STATE_RCPT:
+ if (!mail_from_rejected) {
+#ifdef notdef
+ if (resp->code == 552) {
+ resp->code = 452;
+ resp->dsn[0] = '4';
+ }
+#endif
+ rcpt = request->rcpt_list.info + recv_rcpt;
+ if (resp->code / 100 == 2) {
+ if (!smtp_mode) {
+ if (survivors == 0)
+ survivors = (int *)
+ mymalloc(request->rcpt_list.len
+ * sizeof(int));
+ survivors[nrcpt] = recv_rcpt;
+ }
+ ++nrcpt;
+ /* If trace-only, mark the recipient done. */
+ if (DEL_REQ_TRACE_ONLY(request->flags)
+ && smtp_vrfy_tgt == SMTP_STATE_RCPT) {
+ translit(resp->str, "\n", " ");
+ smtp_rcpt_done(state, resp, rcpt);
+ }
+ } else {
+ smtp_rcpt_fail(state, rcpt, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_RCPT]);
+ }
+ }
+ /* If trace-only, send RSET instead of DATA. */
+ if (++recv_rcpt == SMTP_RCPT_LEFT(state))
+ recv_state = (DEL_REQ_TRACE_ONLY(request->flags)
+ && smtp_vrfy_tgt == SMTP_STATE_RCPT) ?
+ SMTP_STATE_ABORT : SMTP_STATE_DATA;
+ /* XXX Also: record if non-delivering session. */
+ break;
+
+ /*
+ * Process the DATA response. When the server rejects
+ * DATA, set nrcpt to a negative value so that the
+ * receiver can apply a course correction.
+ */
+ case SMTP_STATE_DATA:
+ recv_state = SMTP_STATE_DOT;
+ if (resp->code / 100 != 3) {
+ if (nrcpt > 0)
+ smtp_mesg_fail(state, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_DATA]);
+ nrcpt = -1;
+ }
+
+ /*
+ * In the case of a successful address probe with target
+ * equal to DATA, the remote server is now in the DATA
+ * state, and therefore we must not make any further
+ * attempt to send or receive on this connection. This
+ * means that we cannot not reuse the general-purpose
+ * course-correction logic below which sends RSET (and
+ * perhaps QUIT). Instead we "jump" straight to the exit
+ * and force an unceremonious disconnect.
+ */
+ else if (DEL_REQ_TRACE_ONLY(request->flags)
+ && smtp_vrfy_tgt == SMTP_STATE_DATA) {
+ for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ if (!SMTP_RCPT_ISMARKED(rcpt)) {
+ translit(resp->str, "\n", " ");
+ SMTP_RESP_SET_DSN(resp, "2.0.0");
+ smtp_rcpt_done(state, resp, rcpt);
+ }
+ }
+ DONT_CACHE_THIS_SESSION;
+ send_state = recv_state = SMTP_STATE_LAST;
+ }
+ break;
+
+ /*
+ * Process the end of message response. Ignore the
+ * response when no recipient was accepted: all
+ * recipients are dead already, and the next receiver
+ * state is SMTP_STATE_LAST/QUIT regardless. Otherwise,
+ * if the message transfer fails, bounce all remaining
+ * recipients, else cross off the recipients that were
+ * delivered.
+ */
+ case SMTP_STATE_DOT:
+ GETTIMEOFDAY(&request->msg_stats.deliver_done);
+ if (smtp_mode) {
+ if (nrcpt > 0) {
+ if (resp->code / 100 != 2) {
+ smtp_mesg_fail(state, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_DOT]);
+ } else {
+ for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ if (!SMTP_RCPT_ISMARKED(rcpt)) {
+ translit(resp->str, "\n", " ");
+ smtp_rcpt_done(state, resp, rcpt);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * With LMTP we have one response per accepted RCPT TO
+ * command. Stay in the SMTP_STATE_DOT state until we
+ * have collected all responses.
+ */
+ else {
+ if (nrcpt > 0) {
+ rcpt = request->rcpt_list.info
+ + survivors[recv_done++];
+ if (resp->code / 100 != 2) {
+ smtp_rcpt_fail(state, rcpt, STR(iter->host), resp,
+ "host %s said: %s (in reply to %s)",
+ session->namaddr,
+ translit(resp->str, "\n", " "),
+ xfer_request[SMTP_STATE_DOT]);
+ } else {
+ translit(resp->str, "\n", " ");
+ smtp_rcpt_done(state, resp, rcpt);
+ }
+ }
+ if (msg_verbose)
+ msg_info("%s: got %d of %d end-of-data replies",
+ myname, recv_done, nrcpt);
+ if (recv_done < nrcpt)
+ break;
+ }
+
+ /*
+ * XXX Do not change the connection caching state here,
+ * even if the connection caching timer expired between
+ * generating the command and processing the reply,
+ * otherwise the sender and receiver loops get out of
+ * sync. The caller will call smtp_quit() if appropriate.
+ */
+ if (var_skip_quit_resp || THIS_SESSION_IS_CACHED
+ || LOST_CONNECTION_INSIDE_DATA)
+ recv_state = SMTP_STATE_LAST;
+ else
+ recv_state = SMTP_STATE_QUIT;
+ break;
+
+ /*
+ * Receive the RSET response.
+ *
+ * The SMTP_STATE_ABORT sender state is entered by the
+ * sender when it has verified all recipients; or it is
+ * entered by the receiver when all recipients are
+ * verified or rejected, and is then left before the
+ * bottom of the main loop.
+ *
+ * XXX Do not change the connection caching state here, even
+ * if the server rejected RSET or if the connection
+ * caching timer expired between generating the command
+ * and processing the reply, otherwise the sender and
+ * receiver loops get out of sync. The caller will call
+ * smtp_quit() if appropriate.
+ */
+ case SMTP_STATE_ABORT:
+ recv_state = (var_skip_quit_resp || THIS_SESSION_IS_CACHED ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT);
+ break;
+
+ /*
+ * This is the initial receiver state from smtp_rset().
+ * It is used to find out the status of a cached session
+ * before attempting mail delivery.
+ */
+ case SMTP_STATE_RSET:
+ if (resp->code / 100 != 2)
+ CANT_RSET_THIS_SESSION;
+ recv_state = SMTP_STATE_LAST;
+ break;
+
+ /*
+ * Receive, but otherwise ignore, the QUIT response.
+ */
+ case SMTP_STATE_QUIT:
+ recv_state = SMTP_STATE_LAST;
+ break;
+ }
+ }
+
+ /*
+ * At this point, the sender and receiver are fully synchronized.
+ */
+
+ /*
+ * We know the server response to every command that was sent.
+ * Apply a course correction if necessary: the sender wants to
+ * send RCPT TO but MAIL FROM was rejected; the sender wants to
+ * send DATA but all recipients were rejected; the sender wants
+ * to deliver the message but DATA was rejected.
+ */
+ if ((send_state == SMTP_STATE_RCPT && mail_from_rejected)
+ || (send_state == SMTP_STATE_DATA && nrcpt == 0)
+ || (send_state == SMTP_STATE_DOT && nrcpt < 0)) {
+ send_state = recv_state = SMTP_STATE_ABORT;
+ send_rcpt = recv_rcpt = 0;
+ vstring_strcpy(next_command, "RSET");
+ if (THIS_SESSION_IS_EXPIRED)
+ DONT_CACHE_THIS_SESSION;
+ next_state = THIS_SESSION_IS_CACHED ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT;
+ /* XXX Also: record if non-delivering session. */
+ next_rcpt = 0;
+ }
+ }
+
+ /*
+ * Make the next sender state the current sender state.
+ */
+ if (send_state == SMTP_STATE_LAST)
+ continue;
+
+ /*
+ * Special case if the server accepted the DATA command. If the
+ * server accepted at least one recipient send the entire message.
+ * Otherwise, just send "." as per RFC 2197.
+ *
+ * XXX If there is a hard MIME error while downgrading to 7-bit mail,
+ * disconnect ungracefully, because there is no other way to cancel a
+ * transaction in progress.
+ */
+ if (send_state == SMTP_STATE_DOT && nrcpt > 0) {
+
+ smtp_stream_setup(session->stream, var_smtp_data1_tmout,
+ var_smtp_rec_deadline);
+
+ if ((except = vstream_setjmp(session->stream)) == 0) {
+
+ if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0)
+ msg_fatal("seek queue file: %m");
+
+ downgrading = SMTP_MIME_DOWNGRADE(session, request);
+
+ /*
+ * XXX Don't downgrade just because generic_maps is turned
+ * on.
+ */
+#define SMTP_ANY_CHECKS (smtp_header_checks || smtp_body_checks)
+
+ if (downgrading || smtp_generic_maps || SMTP_ANY_CHECKS)
+ session->mime_state = mime_state_alloc(downgrading ?
+ MIME_OPT_DOWNGRADE
+ | MIME_OPT_REPORT_NESTING :
+ SMTP_ANY_CHECKS == 0 ?
+ MIME_OPT_DISABLE_MIME :
+ 0,
+ smtp_generic_maps
+ || smtp_header_checks ?
+ smtp_header_rewrite :
+ smtp_header_out,
+ (MIME_STATE_ANY_END) 0,
+ smtp_body_checks ?
+ smtp_body_rewrite :
+ smtp_text_out,
+ (MIME_STATE_ANY_END) 0,
+ (MIME_STATE_ERR_PRINT) 0,
+ (void *) state);
+ state->space_left = var_smtp_line_limit;
+
+ while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) {
+ if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
+ break;
+ if (session->mime_state == 0) {
+ smtp_text_out((void *) state, rec_type,
+ vstring_str(session->scratch),
+ VSTRING_LEN(session->scratch),
+ (off_t) 0);
+ } else {
+ mime_errs =
+ mime_state_update(session->mime_state, rec_type,
+ vstring_str(session->scratch),
+ VSTRING_LEN(session->scratch));
+ if (mime_errs) {
+ smtp_mime_fail(state, mime_errs);
+ RETURN(0);
+ }
+ }
+ prev_type = rec_type;
+ }
+
+ if (session->mime_state) {
+
+ /*
+ * The cleanup server normally ends MIME content with a
+ * normal text record. The following code is needed to
+ * flush an internal buffer when someone submits 8-bit
+ * mail not ending in newline via /usr/sbin/sendmail
+ * while MIME input processing is turned off, and MIME
+ * 8bit->7bit conversion is requested upon delivery.
+ *
+ * Or some error while doing generic address mapping.
+ */
+ mime_errs =
+ mime_state_update(session->mime_state, rec_type, "", 0);
+ if (mime_errs) {
+ smtp_mime_fail(state, mime_errs);
+ RETURN(0);
+ }
+ } else if (prev_type == REC_TYPE_CONT) /* missing newline */
+ smtp_fputs("", 0, session->stream);
+ if (session->features & SMTP_FEATURE_PIX_DELAY_DOTCRLF) {
+ smtp_flush(session->stream);/* hurts performance */
+ sleep(var_smtp_pix_delay); /* not to mention this */
+ }
+ if (vstream_ferror(state->src))
+ msg_fatal("queue file read error");
+ if (rec_type != REC_TYPE_XTRA) {
+ msg_warn("%s: bad record type: %d in message content",
+ request->queue_id, rec_type);
+ fail_status = smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "5.3.0"),
+ "unreadable mail queue entry");
+ /* Bailing out, abort stream with prejudice */
+ (void) vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
+ DONT_USE_FORBIDDEN_SESSION;
+ /* If bounce_append() succeeded, status is still 0 */
+ if (state->status == 0)
+ (void) mark_corrupt(state->src);
+ /* Don't override smtp_mesg_fail() here. */
+ RETURN(fail_status);
+ }
+ } else {
+ if (!LOST_CONNECTION_INSIDE_DATA)
+ RETURN(smtp_stream_except(state, except,
+ "sending message body"));
+
+ /*
+ * We will clear the stream error flag to try and read a
+ * premature 5XX response, so it is important to flush any
+ * unwritten data. Otherwise, we will try to flush it again
+ * before reading, which may incur an unnecessary delay and
+ * will prevent the reading of any response that is not
+ * already buffered (bundled with the DATA 354 response).
+ *
+ * Not much point in sending QUIT at this point, skip right to
+ * SMTP_STATE_LAST. The read engine above will likewise avoid
+ * looking for a QUIT response.
+ */
+ (void) vstream_fpurge(session->stream, VSTREAM_PURGE_WRITE);
+ next_state = SMTP_STATE_LAST;
+ }
+ }
+
+ /*
+ * Copy the next command to the buffer and update the sender state.
+ */
+ if (except == 0) {
+ smtp_chat_cmd(session, "%s", vstring_str(next_command));
+ } else {
+ DONT_CACHE_THIS_SESSION;
+ }
+ send_state = next_state;
+ send_rcpt = next_rcpt;
+ } while (recv_state != SMTP_STATE_LAST);
+ RETURN(0);
+}
+
+/* smtp_xfer - send a batch of envelope information and the message data */
+
+int smtp_xfer(SMTP_STATE *state)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ SMTP_RESP fake;
+ int send_state;
+ int recv_state;
+ int send_name_addr;
+ int result;
+
+ /*
+ * Sanity check. Recipients should be unmarked at this point.
+ */
+ if (SMTP_RCPT_LEFT(state) <= 0)
+ msg_panic("smtp_xfer: bad recipient count: %d",
+ SMTP_RCPT_LEFT(state));
+ if (SMTP_RCPT_ISMARKED(request->rcpt_list.info))
+ msg_panic("smtp_xfer: bad recipient status: %d",
+ request->rcpt_list.info->u.status);
+
+ /*
+ * See if we should even try to send this message at all. This code sits
+ * here rather than in the EHLO processing code, because of SMTP
+ * connection caching.
+ */
+ if (session->size_limit > 0 && session->size_limit < request->data_size) {
+ smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
+ SMTP_RESP_FAKE(&fake, "5.3.4"),
+ "message size %lu exceeds size limit %.0f of server %s",
+ request->data_size, (double) session->size_limit,
+ session->namaddr);
+ /* Redundant. We abort this delivery attempt. */
+ state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION;
+ return (0);
+ }
+
+ /*
+ * Use XFORWARD to forward the origin of this email message across an
+ * SMTP-based content filter. Send client attribute information only if
+ * it exists (i.e. remote submission). Local submissions have no client
+ * attributes; the mail will appear to originate from the content filter
+ * which is acceptable.
+ */
+ send_name_addr =
+ var_smtp_send_xforward
+ && (((session->features & SMTP_FEATURE_XFORWARD_NAME)
+ && CAN_FORWARD_CLIENT_NAME(request->client_name))
+ || ((session->features & SMTP_FEATURE_XFORWARD_ADDR)
+ && CAN_FORWARD_CLIENT_ADDR(request->client_addr))
+ || ((session->features & SMTP_FEATURE_XFORWARD_PORT)
+ && CAN_FORWARD_CLIENT_PORT(request->client_port)));
+ session->send_proto_helo =
+ var_smtp_send_xforward
+ && (((session->features & SMTP_FEATURE_XFORWARD_PROTO)
+ && CAN_FORWARD_PROTO_NAME(request->client_proto))
+ || ((session->features & SMTP_FEATURE_XFORWARD_HELO)
+ && CAN_FORWARD_HELO_NAME(request->client_helo))
+ || ((session->features & SMTP_FEATURE_XFORWARD_IDENT)
+ && CAN_FORWARD_IDENT_NAME(request->log_ident))
+ || ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN)
+ && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)));
+ if (send_name_addr)
+ recv_state = send_state = SMTP_STATE_XFORWARD_NAME_ADDR;
+ else if (session->send_proto_helo)
+ recv_state = send_state = SMTP_STATE_XFORWARD_PROTO_HELO;
+ else
+ recv_state = send_state = SMTP_STATE_MAIL;
+
+ /*
+ * Remember this session's "normal completion", even if the server 4xx-ed
+ * some or all recipients. Connection or handshake errors with a later MX
+ * host should not cause this destination be marked as unreachable.
+ */
+ result = smtp_loop(state, send_state, recv_state);
+
+ if (result == 0
+ /* Just in case */
+ && vstream_ferror(session->stream) == 0
+ && vstream_feof(session->stream) == 0)
+ state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION;
+
+ return (result);
+}
+
+/* smtp_rset - send a lone RSET command */
+
+int smtp_rset(SMTP_STATE *state)
+{
+
+ /*
+ * This works because SMTP_STATE_RSET is a dedicated sender/recipient
+ * entry state, with SMTP_STATE_LAST as next sender/recipient state.
+ */
+ return (smtp_loop(state, SMTP_STATE_RSET, SMTP_STATE_RSET));
+}
+
+/* smtp_quit - send a lone QUIT command */
+
+int smtp_quit(SMTP_STATE *state)
+{
+
+ /*
+ * This works because SMTP_STATE_QUIT is the last state with a sender
+ * action, with SMTP_STATE_LAST as the next sender/recipient state.
+ */
+ return (smtp_loop(state, SMTP_STATE_QUIT, var_skip_quit_resp ?
+ SMTP_STATE_LAST : SMTP_STATE_QUIT));
+}
diff --git a/src/smtp/smtp_rcpt.c b/src/smtp/smtp_rcpt.c
new file mode 100644
index 0000000..673f0b4
--- /dev/null
+++ b/src/smtp/smtp_rcpt.c
@@ -0,0 +1,221 @@
+/*++
+/* NAME
+/* smtp_rcpt 3
+/* SUMMARY
+/* application-specific recipient list operations
+/* SYNOPSIS
+/* #include <smtp.h>
+/*
+/* SMTP_RCPT_INIT(state)
+/* SMTP_STATE *state;
+/*
+/* SMTP_RCPT_DROP(state, rcpt)
+/* SMTP_STATE *state;
+/* RECIPIENT *rcpt;
+/*
+/* SMTP_RCPT_KEEP(state, rcpt)
+/* SMTP_STATE *state;
+/* RECIPIENT *rcpt;
+/*
+/* SMTP_RCPT_ISMARKED(rcpt)
+/* RECIPIENT *rcpt;
+/*
+/* void smtp_rcpt_cleanup(SMTP_STATE *state)
+/* SMTP_STATE *state;
+/*
+/* int SMTP_RCPT_LEFT(state)
+/* SMTP_STATE *state;
+/*
+/* int SMTP_RCPT_MARK_COUNT(state)
+/* SMTP_STATE *state;
+/*
+/* void smtp_rcpt_done(state, resp, rcpt)
+/* SMTP_STATE *state;
+/* SMTP_RESP *resp;
+/* RECIPIENT *rcpt;
+/* DESCRIPTION
+/* This module implements application-specific mark and sweep
+/* operations on recipient lists. Operation is as follows:
+/* .IP \(bu
+/* In the course of a delivery attempt each recipient is
+/* marked either as DROP (remove from recipient list) or KEEP
+/* (deliver to alternate mail server).
+/* .IP \(bu
+/* After a delivery attempt any recipients marked DROP are deleted
+/* from the request, and the left-over recipients are unmarked.
+/* .PP
+/* The mark/sweep algorithm is implemented in a redundant manner,
+/* and ensures that all recipients are explicitly accounted for.
+/*
+/* Operations with upper case names are implemented by macros
+/* whose arguments may be evaluated more than once.
+/*
+/* SMTP_RCPT_INIT() initializes application-specific recipient
+/* information and must be called before the first delivery attempt.
+/*
+/* SMTP_RCPT_DROP() marks the specified recipient as DROP (remove
+/* from recipient list). It is an error to mark an already marked
+/* recipient.
+/*
+/* SMTP_RCPT_KEEP() marks the specified recipient as KEEP (deliver
+/* to alternate mail server). It is an error to mark an already
+/* marked recipient.
+/*
+/* SMTP_RCPT_ISMARKED() returns non-zero when the specified
+/* recipient is marked.
+/*
+/* SMTP_RCPT_LEFT() returns the number of left_over recipients
+/* (the total number of marked and non-marked recipients).
+/*
+/* SMTP_RCPT_MARK_COUNT() returns the number of left_over
+/* recipients that are marked.
+/*
+/* smtp_rcpt_cleanup() cleans up the in-memory recipient list.
+/* It removes the recipients marked DROP from the left-over
+/* recipients, unmarks the left-over recipients, and enforces
+/* the requirement that all recipients are marked upon entry.
+/*
+/* smtp_rcpt_done() logs that a recipient is completed and upon
+/* success it marks the recipient as done in the queue file.
+/* Finally, it marks the in-memory recipient as DROP.
+/*
+/* Note: smtp_rcpt_done() may change the order of the recipient
+/* list.
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/*
+/* When a recipient can't be logged as completed, the recipient is
+/* logged as deferred instead.
+/* BUGS
+/* The single recipient list abstraction dates from the time
+/* that the SMTP client would give up after one SMTP session,
+/* so that each recipient was either bounced, delivered or
+/* deferred. Implicitly, all recipients were marked as DROP.
+/*
+/* This abstraction is less convenient when an SMTP client
+/* must be able to deliver left-over recipients to a backup
+/* host. It might be more natural to have an input list with
+/* recipients to deliver, and an output list with left-over
+/* recipients.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* smtp_rcpt_cleanup */
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <deliver_request.h> /* smtp_rcpt_done */
+#include <deliver_completed.h> /* smtp_rcpt_done */
+#include <sent.h> /* smtp_rcpt_done */
+#include <dsn_mask.h> /* smtp_rcpt_done */
+
+/* Application-specific. */
+
+#include <smtp.h>
+
+/* smtp_rcpt_done - mark recipient as done or else */
+
+void smtp_rcpt_done(SMTP_STATE *state, SMTP_RESP *resp, RECIPIENT *rcpt)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ SMTP_ITERATOR *iter = state->iterator;
+ DSN_BUF *why = state->why;
+ const char *dsn_action = "relayed";
+ int status;
+
+ /*
+ * Assume this was intermediate delivery when the server announced DSN
+ * support, and don't send a DSN "SUCCESS" notification.
+ */
+ if (session->features & SMTP_FEATURE_DSN)
+ rcpt->dsn_notify &= ~DSN_NOTIFY_SUCCESS;
+
+ /*
+ * Assume this was final delivery when the LMTP server announced no DSN
+ * support. In backwards compatibility mode, send a "relayed" instead of
+ * a "delivered" DSN "SUCCESS" notification. Do not attempt to "simplify"
+ * the expression. The redundancy is for clarity. It is trivially
+ * eliminated by the compiler. There is no need to sacrifice clarity for
+ * the sake of "performance".
+ */
+ if ((session->features & SMTP_FEATURE_DSN) == 0
+ && !smtp_mode
+ && var_lmtp_assume_final != 0)
+ dsn_action = "delivered";
+
+ /*
+ * Report success and delete the recipient from the delivery request.
+ * Defer if the success can't be reported.
+ *
+ * Note: the DSN action is ignored in case of address probes.
+ */
+ dsb_update(why, resp->dsn, dsn_action, DSB_MTYPE_DNS, STR(iter->host),
+ DSB_DTYPE_SMTP, resp->str, "%s", resp->str);
+
+ status = sent(DEL_REQ_TRACE_FLAGS(request->flags),
+ request->queue_id, &request->msg_stats, rcpt,
+ session->namaddrport, DSN_FROM_DSN_BUF(why));
+ if (status == 0)
+ if (request->flags & DEL_REQ_FLAG_SUCCESS)
+ deliver_completed(state->src, rcpt->offset);
+ SMTP_RCPT_DROP(state, rcpt);
+ state->status |= status;
+}
+
+/* smtp_rcpt_cleanup_callback - qsort callback */
+
+static int smtp_rcpt_cleanup_callback(const void *a, const void *b)
+{
+ return (((RECIPIENT *) a)->u.status - ((RECIPIENT *) b)->u.status);
+}
+
+/* smtp_rcpt_cleanup - purge completed recipients from request */
+
+void smtp_rcpt_cleanup(SMTP_STATE *state)
+{
+ RECIPIENT_LIST *rcpt_list = &state->request->rcpt_list;
+ RECIPIENT *rcpt;
+
+ /*
+ * Sanity checks.
+ */
+ if (state->rcpt_drop + state->rcpt_keep != state->rcpt_left)
+ msg_panic("smtp_rcpt_cleanup: recipient count mismatch: %d+%d!=%d",
+ state->rcpt_drop, state->rcpt_keep, state->rcpt_left);
+
+ /*
+ * Recipients marked KEEP sort before recipients marked DROP. Skip the
+ * sorting in the common case that all recipients are marked the same.
+ */
+ if (state->rcpt_drop > 0 && state->rcpt_keep > 0)
+ qsort((void *) rcpt_list->info, state->rcpt_left,
+ sizeof(rcpt_list->info[0]), smtp_rcpt_cleanup_callback);
+
+ /*
+ * Truncate the recipient list and unmark the left-over recipients.
+ */
+ state->rcpt_left = state->rcpt_keep;
+ for (rcpt = rcpt_list->info; rcpt < rcpt_list->info + state->rcpt_left; rcpt++)
+ rcpt->u.status = 0;
+ state->rcpt_drop = state->rcpt_keep = 0;
+}
diff --git a/src/smtp/smtp_reuse.c b/src/smtp/smtp_reuse.c
new file mode 100644
index 0000000..f93ba29
--- /dev/null
+++ b/src/smtp/smtp_reuse.c
@@ -0,0 +1,269 @@
+/*++
+/* NAME
+/* smtp_reuse 3
+/* SUMMARY
+/* SMTP session cache glue
+/* SYNOPSIS
+/* #include <smtp.h>
+/* #include <smtp_reuse.h>
+/*
+/* void smtp_save_session(state, name_key_flags, endp_key_flags)
+/* SMTP_STATE *state;
+/* int name_key_flags;
+/* int endp_key_flags;
+/*
+/* SMTP_SESSION *smtp_reuse_nexthop(state, name_key_flags)
+/* SMTP_STATE *state;
+/* int name_key_flags;
+/*
+/* SMTP_SESSION *smtp_reuse_addr(state, endp_key_flags)
+/* SMTP_STATE *state;
+/* int endp_key_flags;
+/* DESCRIPTION
+/* This module implements the SMTP client specific interface to
+/* the generic session cache infrastructure.
+/*
+/* The caller needs to include additional state in _key_flags
+/* to avoid false sharing of SASL-authenticated or TLS-authenticated
+/* sessions.
+/*
+/* smtp_save_session() stores the current session under the
+/* delivery request next-hop logical destination (if applicable)
+/* and under the remote server address. The SMTP_SESSION object
+/* is destroyed.
+/*
+/* smtp_reuse_nexthop() looks up a cached session by its
+/* delivery request next-hop destination, and verifies that
+/* the session is still alive. The restored session information
+/* includes the "best MX" bit and overrides the iterator dest,
+/* host and addr fields. The result is null in case of failure.
+/*
+/* smtp_reuse_addr() looks up a cached session by its server
+/* address, and verifies that the session is still alive.
+/* The restored session information does not include the "best
+/* MX" bit, and does not override the iterator dest, host and
+/* addr fields. The result is null in case of failure.
+/*
+/* Arguments:
+/* .IP state
+/* SMTP client state, including the current session, the original
+/* next-hop domain, etc.
+/* .IP name_key_flags
+/* Explicit declaration of context that should be used to look
+/* up a cached connection by its logical destination.
+/* See smtp_key(3) for details.
+/* .IP endp_key_flags
+/* Explicit declaration of context that should be used to look
+/* up a cached connection by its server address.
+/* See smtp_key(3) for details.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <htable.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <scache.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <smtp.h>
+#include <smtp_reuse.h>
+
+ /*
+ * Key field delimiter, and place holder field value for
+ * unavailable/inapplicable information.
+ */
+#define SMTP_REUSE_KEY_DELIM_NA "\n*"
+
+/* smtp_save_session - save session under next-hop name and server address */
+
+void smtp_save_session(SMTP_STATE *state, int name_key_flags,
+ int endp_key_flags)
+{
+ SMTP_SESSION *session = state->session;
+ int fd;
+
+ /*
+ * Encode the delivery request next-hop destination, if applicable. Reuse
+ * storage that is also used for cache lookup queries.
+ *
+ * HAVE_SCACHE_REQUEST_NEXTHOP() controls whether or not to reuse or cache a
+ * connection by its delivery request next-hop destination. The idea is
+ * 1) to allow a reuse request to skip over bad hosts, and 2) to avoid
+ * caching a less-preferred connection when a more-preferred connection
+ * was possible.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state))
+ smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA,
+ state->iterator, name_key_flags);
+
+ /*
+ * Encode the physical endpoint name. Reuse storage that is also used for
+ * cache lookup queries.
+ */
+ smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA,
+ state->iterator, endp_key_flags);
+
+ /*
+ * Passivate the SMTP_SESSION object, destroying the object in the
+ * process. Reuse storage that is also used for cache lookup results.
+ */
+ fd = smtp_session_passivate(session, state->dest_prop, state->endp_prop);
+ state->session = 0;
+
+ /*
+ * Save the session under the delivery request next-hop name, if
+ * applicable.
+ *
+ * XXX The logical to physical binding can be kept for as long as the DNS
+ * allows us to (but that could result in the caching of lots of unused
+ * bindings). The session should be idle for no more than 30 seconds or
+ * so.
+ */
+ if (HAVE_SCACHE_REQUEST_NEXTHOP(state))
+ scache_save_dest(smtp_scache, var_smtp_cache_conn,
+ STR(state->dest_label), STR(state->dest_prop),
+ STR(state->endp_label));
+
+ /*
+ * Save every good session under its physical endpoint address.
+ */
+ scache_save_endp(smtp_scache, var_smtp_cache_conn, STR(state->endp_label),
+ STR(state->endp_prop), fd);
+}
+
+/* smtp_reuse_common - common session reuse code */
+
+static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd,
+ const char *label)
+{
+ const char *myname = "smtp_reuse_common";
+ SMTP_ITERATOR *iter = state->iterator;
+ SMTP_SESSION *session;
+
+ /*
+ * Re-activate the SMTP_SESSION object.
+ */
+ session = smtp_session_activate(fd, state->iterator, state->dest_prop,
+ state->endp_prop);
+ if (session == 0) {
+ msg_warn("%s: bad cached session attribute for %s", myname, label);
+ (void) close(fd);
+ return (0);
+ }
+ state->session = session;
+ session->state = state;
+
+ /*
+ * Send an RSET probe to verify that the session is still good.
+ */
+ if (smtp_rset(state) < 0
+ || (session->features & SMTP_FEATURE_RSET_REJECTED) != 0) {
+ smtp_session_free(session);
+ return (state->session = 0);
+ }
+
+ /*
+ * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE.
+ */
+ vstream_tweak_sock(session->stream);
+
+ /*
+ * Update the list of used cached addresses.
+ */
+ htable_enter(state->cache_used, STR(iter->addr), (void *) 0);
+
+ return (session);
+}
+
+/* smtp_reuse_nexthop - reuse session cached under nexthop name */
+
+SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *state, int name_key_flags)
+{
+ SMTP_SESSION *session;
+ int fd;
+
+ /*
+ * Look up the session by its logical name.
+ */
+ smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA,
+ state->iterator, name_key_flags);
+ if ((fd = scache_find_dest(smtp_scache, STR(state->dest_label),
+ state->dest_prop, state->endp_prop)) < 0)
+ return (0);
+
+ /*
+ * Re-activate the SMTP_SESSION object, and verify that the session is
+ * still good.
+ */
+ session = smtp_reuse_common(state, fd, STR(state->dest_label));
+ return (session);
+}
+
+/* smtp_reuse_addr - reuse session cached under numerical address */
+
+SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *state, int endp_key_flags)
+{
+ SMTP_SESSION *session;
+ int fd;
+
+ /*
+ * Address-based reuse is safe for security levels that require TLS
+ * certificate checks, as long as the current nexhop is included in the
+ * cache lookup key (COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP). This is
+ * sufficient to prevent the reuse of a TLS-authenticated connection to
+ * the same MX hostname, IP address, and port, but for a different
+ * current nexthop destination with a different TLS policy.
+ */
+
+ /*
+ * Look up the session by its IP address. This means that we have no
+ * destination-to-address binding properties.
+ */
+ smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA,
+ state->iterator, endp_key_flags);
+ if ((fd = scache_find_endp(smtp_scache, STR(state->endp_label),
+ state->endp_prop)) < 0)
+ return (0);
+ VSTRING_RESET(state->dest_prop);
+ VSTRING_TERMINATE(state->dest_prop);
+
+ /*
+ * Re-activate the SMTP_SESSION object, and verify that the session is
+ * still good.
+ */
+ session = smtp_reuse_common(state, fd, STR(state->endp_label));
+
+ return (session);
+}
diff --git a/src/smtp/smtp_reuse.h b/src/smtp/smtp_reuse.h
new file mode 100644
index 0000000..8c6cb48
--- /dev/null
+++ b/src/smtp/smtp_reuse.h
@@ -0,0 +1,27 @@
+/*++
+/* NAME
+/* smtp_reuse 3h
+/* SUMMARY
+/* SMTP session cache glue
+/* SYNOPSIS
+/* #include <smtp_reuse.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Internal interfaces.
+ */
+extern void smtp_save_session(SMTP_STATE *, int, int);
+extern SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *, int);
+extern SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
diff --git a/src/smtp/smtp_sasl.h b/src/smtp/smtp_sasl.h
new file mode 100644
index 0000000..756d13d
--- /dev/null
+++ b/src/smtp/smtp_sasl.h
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* smtp_sasl 3h
+/* SUMMARY
+/* Postfix SASL interface for SMTP client
+/* SYNOPSIS
+/* #include "smtp_sasl.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * SASL protocol functions
+ */
+extern void smtp_sasl_initialize(void);
+extern void smtp_sasl_connect(SMTP_SESSION *);
+extern int smtp_sasl_passwd_lookup(SMTP_SESSION *);
+extern void smtp_sasl_start(SMTP_SESSION *, const char *, const char *);
+extern int smtp_sasl_authenticate(SMTP_SESSION *, DSN_BUF *);
+extern void smtp_sasl_cleanup(SMTP_SESSION *);
+
+extern void smtp_sasl_helo_auth(SMTP_SESSION *, const char *);
+extern int smtp_sasl_helo_login(SMTP_STATE *);
+
+extern void smtp_sasl_passivate(SMTP_SESSION *, VSTRING *);
+extern int smtp_sasl_activate(SMTP_SESSION *, char *);
+extern STRING_LIST *smtp_sasl_mechs;
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
diff --git a/src/smtp/smtp_sasl_auth_cache.c b/src/smtp/smtp_sasl_auth_cache.c
new file mode 100644
index 0000000..f3104ca
--- /dev/null
+++ b/src/smtp/smtp_sasl_auth_cache.c
@@ -0,0 +1,272 @@
+/*++
+/* NAME
+/* smtp_sasl_auth_cache 3
+/* SUMMARY
+/* Postfix SASL authentication reply cache
+/* SYNOPSIS
+/* #include "smtp.h"
+/* #include "smtp_sasl_auth_cache.h"
+/*
+/* SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(map, ttl)
+/* const char *map
+/* int ttl;
+/*
+/* void smtp_sasl_auth_cache_store(auth_cache, session, resp)
+/* SMTP_SASL_AUTH_CACHE *auth_cache;
+/* const SMTP_SESSION *session;
+/* const SMTP_RESP *resp;
+/*
+/* int smtp_sasl_auth_cache_find(auth_cache, session)
+/* SMTP_SASL_AUTH_CACHE *auth_cache;
+/* const SMTP_SESSION *session;
+/*
+/* char *smtp_sasl_auth_cache_dsn(auth_cache)
+/* SMTP_SASL_AUTH_CACHE *auth_cache;
+/*
+/* char *smtp_sasl_auth_cache_text(auth_cache)
+/* SMTP_SASL_AUTH_CACHE *auth_cache;
+/* DESCRIPTION
+/* This module maintains a cache of SASL authentication server replies.
+/* This can be used to avoid repeated login failure errors.
+/*
+/* smtp_sasl_auth_cache_init() opens or creates the named cache.
+/*
+/* smtp_sasl_auth_cache_store() stores information about a
+/* SASL login attempt together with the server status and
+/* complete response.
+/*
+/* smtp_sasl_auth_cache_find() returns non-zero when a cache
+/* entry exists for the given host, username and password.
+/*
+/* smtp_sasl_auth_cache_dsn() and smtp_sasl_auth_cache_text()
+/* return the status and complete server response as found
+/* with smtp_sasl_auth_cache_find().
+/*
+/* Arguments:
+/* .IP map
+/* Lookup table name. The name must be singular and must start
+/* with "proxy:".
+/* .IP ttl
+/* The time after which a cache entry is considered expired.
+/* .IP session
+/* Session context.
+/* .IP resp
+/* Remote SMTP server response, to be stored into the cache.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Original author:
+/* Keean Schupke
+/* Fry-IT Ltd.
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <base64_code.h>
+#include <dict.h>
+
+ /*
+ * Global library
+ */
+#include <dsn_util.h>
+#include <dict_proxy.h>
+
+ /*
+ * Application-specific
+ */
+#include "smtp.h"
+#include "smtp_sasl_auth_cache.h"
+
+ /*
+ * XXX This feature stores passwords, so we must mask them with a strong
+ * cryptographic hash. This requires OpenSSL support.
+ *
+ * XXX It would be even better if the stored hash were salted.
+ */
+#ifdef HAVE_SASL_AUTH_CACHE
+
+/* smtp_sasl_auth_cache_init - per-process initialization (pre jail) */
+
+SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *map, int ttl)
+{
+ const char *myname = "smtp_sasl_auth_cache_init";
+ SMTP_SASL_AUTH_CACHE *auth_cache;
+
+ /*
+ * Sanity checks.
+ */
+#define HAS_MULTIPLE_VALUES(s) ((s)[strcspn((s), CHARS_COMMA_SP)] != 0)
+
+ if (*map == 0)
+ msg_panic("%s: empty SASL authentication cache name", myname);
+ if (ttl < 0)
+ msg_panic("%s: bad SASL authentication cache ttl: %d", myname, ttl);
+ if (HAS_MULTIPLE_VALUES(map))
+ msg_fatal("SASL authentication cache name \"%s\" "
+ "contains multiple values", map);
+
+ /*
+ * XXX To avoid multiple writers the map needs to be maintained by the
+ * proxywrite service. We would like to have a DICT_FLAG_REQ_PROXY flag
+ * so that the library can enforce this, but that requires moving the
+ * dict_proxy module one level down in the build dependency hierarchy.
+ */
+#define CACHE_DICT_OPEN_FLAGS \
+ (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_UTF8_REQUEST)
+#define PROXY_COLON DICT_TYPE_PROXY ":"
+#define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1)
+
+ if (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) != 0)
+ msg_fatal("SASL authentication cache name \"%s\" must start with \""
+ PROXY_COLON, map);
+
+ auth_cache = (SMTP_SASL_AUTH_CACHE *) mymalloc(sizeof(*auth_cache));
+ auth_cache->dict = dict_open(map, O_CREAT | O_RDWR, CACHE_DICT_OPEN_FLAGS);
+ auth_cache->ttl = ttl;
+ auth_cache->dsn = mystrdup("");
+ auth_cache->text = mystrdup("");
+ return (auth_cache);
+}
+
+ /*
+ * Each cache lookup key contains a server host name and user name. Each
+ * cache value contains a time stamp, a hashed password, and the server
+ * response. With this organization, we don't have to worry about cache
+ * pollution, because we can detect if a cache entry has expired, or if the
+ * password has changed.
+ */
+
+/* smtp_sasl_auth_cache_make_key - format auth failure cache lookup key */
+
+static char *smtp_sasl_auth_cache_make_key(const char *host, const char *user)
+{
+ VSTRING *buf = vstring_alloc(100);
+
+ vstring_sprintf(buf, "%s;%s", host, user);
+ return (vstring_export(buf));
+}
+
+/* smtp_sasl_auth_cache_make_pass - hash the auth failure cache password */
+
+static char *smtp_sasl_auth_cache_make_pass(const char *password)
+{
+ VSTRING *buf = vstring_alloc(2 * SHA_DIGEST_LENGTH);
+
+ base64_encode(buf, (const char *) SHA1((const unsigned char *) password,
+ strlen(password), 0),
+ SHA_DIGEST_LENGTH);
+ return (vstring_export(buf));
+}
+
+/* smtp_sasl_auth_cache_make_value - format auth failure cache value */
+
+static char *smtp_sasl_auth_cache_make_value(const char *password,
+ const char *dsn,
+ const char *rep_str)
+{
+ VSTRING *val_buf = vstring_alloc(100);
+ char *pwd_hash;
+ unsigned long now = (unsigned long) time((time_t *) 0);
+
+ pwd_hash = smtp_sasl_auth_cache_make_pass(password);
+ vstring_sprintf(val_buf, "%lu;%s;%s;%s", now, pwd_hash, dsn, rep_str);
+ myfree(pwd_hash);
+ return (vstring_export(val_buf));
+}
+
+/* smtp_sasl_auth_cache_valid_value - validate auth failure cache value */
+
+static int smtp_sasl_auth_cache_valid_value(SMTP_SASL_AUTH_CACHE *auth_cache,
+ const char *entry,
+ const char *password)
+{
+ ssize_t len = strlen(entry);
+ char *cache_hash = mymalloc(len);
+ char *curr_hash;
+ unsigned long now = (unsigned long) time((time_t *) 0);
+ unsigned long time_stamp;
+ int valid;
+
+ auth_cache->dsn = myrealloc(auth_cache->dsn, len);
+ auth_cache->text = myrealloc(auth_cache->text, len);
+
+ if (sscanf(entry, "%lu;%[^;];%[^;];%[^\n]", &time_stamp, cache_hash,
+ auth_cache->dsn, auth_cache->text) != 4
+ || !dsn_valid(auth_cache->dsn)) {
+ msg_warn("bad smtp_sasl_auth_cache entry: %.100s", entry);
+ valid = 0;
+ } else if (time_stamp + auth_cache->ttl < now) {
+ valid = 0;
+ } else {
+ curr_hash = smtp_sasl_auth_cache_make_pass(password);
+ valid = (strcmp(cache_hash, curr_hash) == 0);
+ myfree(curr_hash);
+ }
+ myfree(cache_hash);
+ return (valid);
+}
+
+/* smtp_sasl_auth_cache_find - search auth failure cache */
+
+int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *auth_cache,
+ const SMTP_SESSION *session)
+{
+ SMTP_ITERATOR *iter = session->iterator;
+ char *key;
+ const char *entry;
+ int valid = 0;
+
+ key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username);
+ if ((entry = dict_get(auth_cache->dict, key)) != 0)
+ if ((valid = smtp_sasl_auth_cache_valid_value(auth_cache, entry,
+ session->sasl_passwd)) == 0)
+ /* Remove expired, password changed, or malformed cache entry. */
+ if (dict_del(auth_cache->dict, key) != 0)
+ msg_warn("SASL auth failure map %s: entry not deleted: %s",
+ auth_cache->dict->name, key);
+ if (auth_cache->dict->error)
+ msg_warn("SASL auth failure map %s: lookup failed for %s",
+ auth_cache->dict->name, key);
+ myfree(key);
+ return (valid);
+}
+
+/* smtp_sasl_auth_cache_store - update auth failure cache */
+
+void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *auth_cache,
+ const SMTP_SESSION *session,
+ const SMTP_RESP *resp)
+{
+ SMTP_ITERATOR *iter = session->iterator;
+ char *key;
+ char *value;
+
+ key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username);
+ value = smtp_sasl_auth_cache_make_value(session->sasl_passwd,
+ resp->dsn, resp->str);
+ dict_put(auth_cache->dict, key, value);
+
+ myfree(value);
+ myfree(key);
+}
+
+#endif
diff --git a/src/smtp/smtp_sasl_auth_cache.h b/src/smtp/smtp_sasl_auth_cache.h
new file mode 100644
index 0000000..cbbdb0d
--- /dev/null
+++ b/src/smtp/smtp_sasl_auth_cache.h
@@ -0,0 +1,62 @@
+#ifndef _SMTP_SASL_AUTH_CACHE_H_INCLUDED_
+#define _SMTP_SASL_AUTH_CACHE_H_INCLUDED_
+
+/*++
+/* NAME
+/* smtp_sasl_auth_cache 3h
+/* SUMMARY
+/* Postfix SASL authentication failure cache
+/* SYNOPSIS
+/* #include "smtp.h"
+/* #include "smtp_sasl_auth_cache.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * This code stores hashed passwords which requires OpenSSL.
+ */
+#if defined(USE_TLS) && defined(USE_SASL_AUTH)
+#define HAVE_SASL_AUTH_CACHE
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ DICT *dict;
+ int ttl;
+ char *dsn;
+ char *text;
+} SMTP_SASL_AUTH_CACHE;
+
+extern SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *, int);
+extern void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *, const SMTP_RESP *);
+extern int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *);
+
+#define smtp_sasl_auth_cache_dsn(cp) ((cp)->dsn)
+#define smtp_sasl_auth_cache_text(cp) ((cp)->text)
+
+#endif
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/smtp/smtp_sasl_glue.c b/src/smtp/smtp_sasl_glue.c
new file mode 100644
index 0000000..ef8e8c4
--- /dev/null
+++ b/src/smtp/smtp_sasl_glue.c
@@ -0,0 +1,512 @@
+/*++
+/* NAME
+/* smtp_sasl_glue 3
+/* SUMMARY
+/* Postfix SASL interface for SMTP client
+/* SYNOPSIS
+/* #include smtp_sasl.h
+/*
+/* void smtp_sasl_initialize()
+/*
+/* void smtp_sasl_connect(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_sasl_start(session, sasl_opts_name, sasl_opts_val)
+/* SMTP_SESSION *session;
+/*
+/* int smtp_sasl_passwd_lookup(session)
+/* SMTP_SESSION *session;
+/*
+/* int smtp_sasl_authenticate(session, why)
+/* SMTP_SESSION *session;
+/* DSN_BUF *why;
+/*
+/* void smtp_sasl_cleanup(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_sasl_passivate(session, buf)
+/* SMTP_SESSION *session;
+/* VSTRING *buf;
+/*
+/* int smtp_sasl_activate(session, buf)
+/* SMTP_SESSION *session;
+/* char *buf;
+/* DESCRIPTION
+/* smtp_sasl_initialize() initializes the SASL library. This
+/* routine must be called once at process startup, before any
+/* chroot operations.
+/*
+/* smtp_sasl_connect() performs per-session initialization. This
+/* routine must be called once at the start of each connection.
+/*
+/* smtp_sasl_start() performs per-session initialization. This
+/* routine must be called once per session before doing any SASL
+/* authentication. The sasl_opts_name and sasl_opts_val parameters are
+/* the postfix configuration parameters setting the security
+/* policy of the SASL authentication.
+/*
+/* smtp_sasl_passwd_lookup() looks up the username/password
+/* for the current SMTP server. The result is zero in case
+/* of failure, a long jump in case of error.
+/*
+/* smtp_sasl_authenticate() implements the SASL authentication
+/* dialog. The result is < 0 in case of protocol failure, zero in
+/* case of unsuccessful authentication, > 0 in case of success.
+/* The why argument is updated with a reason for failure.
+/* This routine must be called only when smtp_sasl_passwd_lookup()
+/* succeeds.
+/*
+/* smtp_sasl_cleanup() cleans up. It must be called at the
+/* end of every SMTP session that uses SASL authentication.
+/* This routine is a noop for non-SASL sessions.
+/*
+/* smtp_sasl_passivate() appends flattened SASL attributes to the
+/* specified buffer. The SASL attributes are not destroyed.
+/*
+/* smtp_sasl_activate() restores SASL attributes from the
+/* specified buffer. The buffer is modified. A result < 0
+/* means there was an error.
+/*
+/* Arguments:
+/* .IP session
+/* Session context.
+/* .IP mech_list
+/* String of SASL mechanisms (separated by blanks)
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Original author:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+ * Utility library
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <split_at.h>
+
+ /*
+ * Global library
+ */
+#include <mail_params.h>
+#include <string_list.h>
+#include <maps.h>
+#include <mail_addr_find.h>
+#include <smtp_stream.h>
+
+ /*
+ * XSASL library.
+ */
+#include <xsasl.h>
+
+ /*
+ * Application-specific
+ */
+#include "smtp.h"
+#include "smtp_sasl.h"
+#include "smtp_sasl_auth_cache.h"
+
+#ifdef USE_SASL_AUTH
+
+ /*
+ * Per-host login/password information.
+ */
+static MAPS *smtp_sasl_passwd_map;
+
+ /*
+ * Supported SASL mechanisms.
+ */
+STRING_LIST *smtp_sasl_mechs;
+
+ /*
+ * SASL implementation handle.
+ */
+static XSASL_CLIENT_IMPL *smtp_sasl_impl;
+
+ /*
+ * The 535 SASL authentication failure cache.
+ */
+#ifdef HAVE_SASL_AUTH_CACHE
+static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache;
+
+#endif
+
+/* smtp_sasl_passwd_lookup - password lookup routine */
+
+int smtp_sasl_passwd_lookup(SMTP_SESSION *session)
+{
+ const char *myname = "smtp_sasl_passwd_lookup";
+ SMTP_STATE *state = session->state;
+ SMTP_ITERATOR *iter = session->iterator;
+ const char *value;
+ char *passwd;
+
+ /*
+ * Sanity check.
+ */
+ if (smtp_sasl_passwd_map == 0)
+ msg_panic("%s: passwd map not initialized", myname);
+
+ /*
+ * Look up the per-server password information. Try the hostname first,
+ * then try the destination.
+ *
+ * XXX Instead of using nexthop (the intended destination) we use dest
+ * (either the intended destination, or a fall-back destination).
+ *
+ * XXX SASL authentication currently depends on the host/domain but not on
+ * the TCP port. If the port is not :25, we should append it to the table
+ * lookup key. Code for this was briefly introduced into 2.2 snapshots,
+ * but didn't canonicalize the TCP port, and did not append the port to
+ * the MX hostname.
+ */
+ smtp_sasl_passwd_map->error = 0;
+ if ((smtp_mode
+ && var_smtp_sender_auth && state->request->sender[0]
+ && (value = mail_addr_find(smtp_sasl_passwd_map,
+ state->request->sender, (char **) 0)) != 0)
+ || (smtp_sasl_passwd_map->error == 0
+ && (value = maps_find(smtp_sasl_passwd_map,
+ STR(iter->host), 0)) != 0)
+ || (smtp_sasl_passwd_map->error == 0
+ && (value = maps_find(smtp_sasl_passwd_map,
+ STR(iter->dest), 0)) != 0)) {
+ if (session->sasl_username)
+ myfree(session->sasl_username);
+ session->sasl_username = mystrdup(value);
+ passwd = split_at(session->sasl_username, ':');
+ if (session->sasl_passwd)
+ myfree(session->sasl_passwd);
+ session->sasl_passwd = mystrdup(passwd ? passwd : "");
+ if (msg_verbose)
+ msg_info("%s: host `%s' user `%s' pass `%s'",
+ myname, STR(iter->host),
+ session->sasl_username, session->sasl_passwd);
+ return (1);
+ } else if (smtp_sasl_passwd_map->error) {
+ msg_warn("%s: %s lookup error",
+ state->request->queue_id, smtp_sasl_passwd_map->title);
+ vstream_longjmp(session->stream, SMTP_ERR_DATA);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: no auth info found (sender=`%s', host=`%s')",
+ myname, state->request->sender, STR(iter->host));
+ return (0);
+ }
+}
+
+/* smtp_sasl_initialize - per-process initialization (pre jail) */
+
+void smtp_sasl_initialize(void)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (smtp_sasl_passwd_map || smtp_sasl_impl)
+ msg_panic("smtp_sasl_initialize: repeated call");
+ if (*var_smtp_sasl_passwd == 0)
+ msg_fatal("specify a password table via the `%s' configuration parameter",
+ VAR_LMTP_SMTP(SASL_PASSWD));
+
+ /*
+ * Open the per-host password table and initialize the SASL library. Use
+ * shared locks for reading, just in case someone updates the table.
+ */
+ smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD),
+ var_smtp_sasl_passwd,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type,
+ var_smtp_sasl_path)) == 0)
+ msg_fatal("SASL library initialization");
+
+ /*
+ * Initialize optional supported mechanism matchlist
+ */
+ if (*var_smtp_sasl_mechs)
+ smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS,
+ MATCH_FLAG_NONE,
+ var_smtp_sasl_mechs);
+
+ /*
+ * Initialize the 535 SASL authentication failure cache.
+ */
+ if (*var_smtp_sasl_auth_cache_name) {
+#ifdef HAVE_SASL_AUTH_CACHE
+ smtp_sasl_auth_cache =
+ smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name,
+ var_smtp_sasl_auth_cache_time);
+#else
+ msg_warn("not compiled with TLS support -- "
+ "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME));
+#endif
+ }
+}
+
+/* smtp_sasl_connect - per-session client initialization */
+
+void smtp_sasl_connect(SMTP_SESSION *session)
+{
+
+ /*
+ * This initialization happens whenever we instantiate an SMTP session
+ * object. We don't instantiate a SASL client until we actually need one.
+ */
+ session->sasl_mechanism_list = 0;
+ session->sasl_username = 0;
+ session->sasl_passwd = 0;
+ session->sasl_client = 0;
+ session->sasl_reply = 0;
+}
+
+/* smtp_sasl_start - per-session SASL initialization */
+
+void smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
+ const char *sasl_opts_val)
+{
+ XSASL_CLIENT_CREATE_ARGS create_args;
+ SMTP_ITERATOR *iter = session->iterator;
+
+ if (msg_verbose)
+ msg_info("starting new SASL client");
+ if ((session->sasl_client =
+ XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args,
+ stream = session->stream,
+ service = var_procname,
+ server_name = STR(iter->host),
+ security_options = sasl_opts_val)) == 0)
+ msg_fatal("SASL per-connection initialization failed");
+ session->sasl_reply = vstring_alloc(20);
+}
+
+/* smtp_sasl_authenticate - run authentication protocol */
+
+int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
+{
+ const char *myname = "smtp_sasl_authenticate";
+ SMTP_ITERATOR *iter = session->iterator;
+ SMTP_RESP *resp;
+ const char *mechanism;
+ int result;
+ char *line;
+ int steps = 0;
+
+ /*
+ * Sanity check.
+ */
+ if (session->sasl_mechanism_list == 0)
+ msg_panic("%s: no mechanism list", myname);
+
+ if (msg_verbose)
+ msg_info("%s: %s: SASL mechanisms %s",
+ myname, session->namaddrport, session->sasl_mechanism_list);
+
+ /*
+ * Avoid repeated login failures after a recent 535 error.
+ */
+#ifdef HAVE_SASL_AUTH_CACHE
+ if (smtp_sasl_auth_cache
+ && smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) {
+ char *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache);
+ char *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache);
+
+ if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5')
+ resp_dsn[0] = '4';
+ dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS,
+ STR(iter->host), var_procname, resp_str,
+ "SASL [CACHED] authentication failed; server %s said: %s",
+ STR(iter->host), resp_str);
+ return (0);
+ }
+#endif
+
+ /*
+ * Start the client side authentication protocol.
+ */
+ result = xsasl_client_first(session->sasl_client,
+ session->sasl_mechanism_list,
+ session->sasl_username,
+ session->sasl_passwd,
+ &mechanism, session->sasl_reply);
+ if (result != XSASL_AUTH_OK) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA,
+ DSB_DTYPE_SASL, STR(session->sasl_reply),
+ "SASL authentication failed; "
+ "cannot authenticate to server %s: %s",
+ session->namaddr, STR(session->sasl_reply));
+ return (-1);
+ }
+ /*-
+ * Send the AUTH command and the optional initial client response.
+ *
+ * https://tools.ietf.org/html/rfc4954#page-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...
+ *
+ * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4
+ * The maximum total length of a command line including the command word
+ * and the <CRLF> is 512 octets.
+ *
+ * Defer the initial response if the resulting command exceeds the limit.
+ */
+ if (LEN(session->sasl_reply) > 0
+ && strlen(mechanism) + LEN(session->sasl_reply) + 8 <= 512) {
+ smtp_chat_cmd(session, "AUTH %s %s", mechanism,
+ STR(session->sasl_reply));
+ VSTRING_RESET(session->sasl_reply); /* no deferred initial reply */
+ } else {
+ smtp_chat_cmd(session, "AUTH %s", mechanism);
+ }
+
+ /*
+ * Step through the authentication protocol until the server tells us
+ * that we are done. If session->sasl_reply is non-empty we have a
+ * deferred initial reply and expect an empty initial challenge from the
+ * server. If the server's initial challenge is non-empty we have a SASL
+ * protocol violation with both sides wanting to go first.
+ */
+ while ((resp = smtp_chat_resp(session))->code / 100 == 3) {
+
+ /*
+ * Sanity check.
+ */
+ if (++steps > 100) {
+ dsb_simple(why, "4.3.0", "SASL authentication failed; "
+ "authentication protocol loop with server %s",
+ session->namaddr);
+ return (-1);
+ }
+
+ /*
+ * Process a server challenge.
+ */
+ line = resp->str;
+ (void) mystrtok(&line, "- \t\n"); /* skip over result code */
+
+ if (LEN(session->sasl_reply) > 0) {
+
+ /*
+ * Deferred initial response, the server challenge must be empty.
+ * Cleared after actual transmission to the server.
+ */
+ if (*line) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION,
+ DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error",
+ "SASL authentication failed; non-empty initial "
+ "%s challenge from server %s: %s", mechanism,
+ session->namaddr, STR(session->sasl_reply));
+ return (-1);
+ }
+ } else {
+ result = xsasl_client_next(session->sasl_client, line,
+ session->sasl_reply);
+ if (result != XSASL_AUTH_OK) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */
+ DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply),
+ "SASL authentication failed; "
+ "cannot authenticate to server %s: %s",
+ session->namaddr, STR(session->sasl_reply));
+ return (-1); /* Fix 200512 */
+ }
+ }
+
+ /*
+ * Send a client response.
+ */
+ smtp_chat_cmd(session, "%s", STR(session->sasl_reply));
+ VSTRING_RESET(session->sasl_reply); /* clear initial reply */
+ }
+
+ /*
+ * We completed the authentication protocol.
+ */
+ if (resp->code / 100 != 2) {
+#ifdef HAVE_SASL_AUTH_CACHE
+ /* Update the 535 authentication failure cache. */
+ if (smtp_sasl_auth_cache && resp->code == 535)
+ smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp);
+#endif
+ if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5)
+ STR(resp->dsn_buf)[0] = '4';
+ dsb_update(why, resp->dsn, DSB_DEF_ACTION,
+ DSB_MTYPE_DNS, STR(iter->host),
+ var_procname, resp->str,
+ "SASL authentication failed; server %s said: %s",
+ session->namaddr, resp->str);
+ return (0);
+ }
+ return (1);
+}
+
+/* smtp_sasl_cleanup - per-session cleanup */
+
+void smtp_sasl_cleanup(SMTP_SESSION *session)
+{
+ if (session->sasl_username) {
+ myfree(session->sasl_username);
+ session->sasl_username = 0;
+ }
+ if (session->sasl_passwd) {
+ myfree(session->sasl_passwd);
+ session->sasl_passwd = 0;
+ }
+ if (session->sasl_mechanism_list) {
+ /* allocated in smtp_sasl_helo_auth */
+ myfree(session->sasl_mechanism_list);
+ session->sasl_mechanism_list = 0;
+ }
+ if (session->sasl_client) {
+ if (msg_verbose)
+ msg_info("disposing SASL state information");
+ xsasl_client_free(session->sasl_client);
+ session->sasl_client = 0;
+ }
+ if (session->sasl_reply) {
+ vstring_free(session->sasl_reply);
+ session->sasl_reply = 0;
+ }
+}
+
+/* smtp_sasl_passivate - append serialized SASL attributes */
+
+void smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf)
+{
+}
+
+/* smtp_sasl_activate - de-serialize SASL attributes */
+
+int smtp_sasl_activate(SMTP_SESSION *session, char *buf)
+{
+ return (0);
+}
+
+#endif
diff --git a/src/smtp/smtp_sasl_proto.c b/src/smtp/smtp_sasl_proto.c
new file mode 100644
index 0000000..6a51696
--- /dev/null
+++ b/src/smtp/smtp_sasl_proto.c
@@ -0,0 +1,202 @@
+/*++
+/* NAME
+/* smtp_sasl_proto 3
+/* SUMMARY
+/* Postfix SASL interface for SMTP client
+/* SYNOPSIS
+/* #include smtp_sasl.h
+/*
+/* void smtp_sasl_helo_auth(state, words)
+/* SMTP_STATE *state;
+/* const char *words;
+/*
+/* int smtp_sasl_helo_login(state)
+/* SMTP_STATE *state;
+/* DESCRIPTION
+/* This module contains random chunks of code that implement
+/* the SMTP protocol interface for SASL negotiation. The goal
+/* is to reduce clutter in the main SMTP client source code.
+/*
+/* smtp_sasl_helo_auth() processes the AUTH option in the
+/* SMTP server's EHLO response.
+/*
+/* smtp_sasl_helo_login() authenticates the SMTP client to the
+/* SMTP server, using the authentication mechanism information
+/* given by the server. The result is a Postfix delivery status
+/* code in case of trouble.
+/*
+/* Arguments:
+/* .IP state
+/* Session context.
+/* .IP words
+/* List of SASL authentication mechanisms (separated by blanks)
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Original author:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+#ifdef USE_SASL_AUTH
+
+/* smtp_sasl_compat_mechs - Trim server's mechanism list */
+
+static const char *smtp_sasl_compat_mechs(const char *words)
+{
+ static VSTRING *buf;
+ char *mech_list;
+ char *save_mech;
+ char *mech;
+
+ /*
+ * Use server's mechanisms if no filter specified
+ */
+ if (smtp_sasl_mechs == 0 || *words == 0)
+ return (words);
+
+ if (buf == 0)
+ buf = vstring_alloc(10);
+
+ VSTRING_RESET(buf);
+ VSTRING_TERMINATE(buf);
+
+ save_mech = mech_list = mystrdup(words);
+
+ while ((mech = mystrtok(&mech_list, " \t")) != 0) {
+ if (string_list_match(smtp_sasl_mechs, mech)) {
+ if (VSTRING_LEN(buf) > 0)
+ VSTRING_ADDCH(buf, ' ');
+ vstring_strcat(buf, mech);
+ } else if (smtp_sasl_mechs->error) {
+ msg_fatal("SASL mechanism filter failed for: '%s'", mech);
+ }
+ }
+ myfree(save_mech);
+
+ return (vstring_str(buf));
+}
+
+/* smtp_sasl_helo_auth - handle AUTH option in EHLO reply */
+
+void smtp_sasl_helo_auth(SMTP_SESSION *session, const char *words)
+{
+ const char *mech_list = smtp_sasl_compat_mechs(words);
+ char *junk;
+
+ /*
+ * XXX If the server offers no compatible authentication mechanisms, then
+ * pretend that the server doesn't support SASL authentication.
+ *
+ * XXX If the server offers multiple different lists, concatenate them. Let
+ * the SASL library worry about duplicates.
+ */
+ if (session->sasl_mechanism_list) {
+ if (strcasecmp(session->sasl_mechanism_list, mech_list) != 0
+ && strlen(mech_list) > 0
+ && strlen(session->sasl_mechanism_list) < var_line_limit) {
+ junk = concatenate(session->sasl_mechanism_list, " ", mech_list,
+ (char *) 0);
+ myfree(session->sasl_mechanism_list);
+ session->sasl_mechanism_list = junk;
+ }
+ return;
+ }
+ if (strlen(mech_list) > 0) {
+ session->sasl_mechanism_list = mystrdup(mech_list);
+ } else {
+ msg_warn(*words ? "%s offered no supported AUTH mechanisms: '%s'" :
+ "%s offered null AUTH mechanism list%s",
+ session->namaddrport, words);
+ }
+ session->features |= SMTP_FEATURE_AUTH;
+}
+
+/* smtp_sasl_helo_login - perform SASL login */
+
+int smtp_sasl_helo_login(SMTP_STATE *state)
+{
+ SMTP_SESSION *session = state->session;
+ DSN_BUF *why = state->why;
+ int ret;
+
+ /*
+ * Skip authentication when no authentication info exists for this
+ * server, so that we talk to each other like strangers.
+ */
+ if (smtp_sasl_passwd_lookup(session) == 0) {
+ session->features &= ~SMTP_FEATURE_AUTH;
+ return 0;
+ }
+
+ /*
+ * Otherwise, if authentication information exists, assume that
+ * authentication is required, and assume that an authentication error is
+ * recoverable from the message delivery point of view. An authentication
+ * error is unrecoverable from a session point of view - the session will
+ * not be reused.
+ */
+ ret = 0;
+ if (session->sasl_mechanism_list == 0) {
+ dsb_simple(why, "4.7.0", "SASL authentication failed: "
+ "server %s offered no compatible authentication mechanisms for this type of connection security",
+ session->namaddr);
+ ret = smtp_sess_fail(state);
+ /* Session reuse is disabled. */
+ } else {
+#ifndef USE_TLS
+ smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS), var_smtp_sasl_opts);
+#else
+ if (session->tls_context == 0)
+ smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS),
+ var_smtp_sasl_opts);
+ else if (TLS_CERT_IS_MATCHED(session->tls_context))
+ smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLSV_OPTS),
+ var_smtp_sasl_tlsv_opts);
+ else
+ smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLS_OPTS),
+ var_smtp_sasl_tls_opts);
+#endif
+ if (smtp_sasl_authenticate(session, why) <= 0) {
+ ret = smtp_sess_fail(state);
+ /* Session reuse is disabled. */
+ }
+ }
+ return (ret);
+}
+
+#endif
diff --git a/src/smtp/smtp_session.c b/src/smtp/smtp_session.c
new file mode 100644
index 0000000..1b3a20e
--- /dev/null
+++ b/src/smtp/smtp_session.c
@@ -0,0 +1,446 @@
+/*++
+/* NAME
+/* smtp_session 3
+/* SUMMARY
+/* SMTP_SESSION structure management
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* SMTP_SESSION *smtp_session_alloc(stream, iter, start, flags)
+/* VSTREAM *stream;
+/* SMTP_ITERATOR *iter;
+/* time_t start;
+/* int flags;
+/*
+/* void smtp_session_free(session)
+/* SMTP_SESSION *session;
+/*
+/* int smtp_session_passivate(session, dest_prop, endp_prop)
+/* SMTP_SESSION *session;
+/* VSTRING *dest_prop;
+/* VSTRING *endp_prop;
+/*
+/* SMTP_SESSION *smtp_session_activate(fd, iter, dest_prop, endp_prop)
+/* int fd;
+/* SMTP_ITERATOR *iter;
+/* VSTRING *dest_prop;
+/* VSTRING *endp_prop;
+/* DESCRIPTION
+/* smtp_session_alloc() allocates memory for an SMTP_SESSION structure
+/* and initializes it with the given stream and destination, host name
+/* and address information. The host name and address strings are
+/* copied. The port is in network byte order.
+/*
+/* smtp_session_free() destroys an SMTP_SESSION structure and its
+/* members, making memory available for reuse. It will handle the
+/* case of a null stream and will assume it was given a different
+/* purpose.
+/*
+/* smtp_session_passivate() flattens an SMTP session (including
+/* TLS context) so that it can be cached. The SMTP_SESSION
+/* structure is destroyed.
+/*
+/* smtp_session_activate() inflates a flattened SMTP session
+/* so that it can be used. The input property arguments are
+/* modified.
+/*
+/* Arguments:
+/* .IP stream
+/* A full-duplex stream.
+/* .IP iter
+/* The literal next-hop or fall-back destination including
+/* the optional [] and including the :port or :service;
+/* the name of the remote host;
+/* the printable address of the remote host;
+/* the remote port in network byte order.
+/* .IP start
+/* The time when this connection was opened.
+/* .IP flags
+/* Zero or more of the following:
+/* .RS
+/* .IP SMTP_MISC_FLAG_CONN_LOAD
+/* Enable re-use of cached SMTP or LMTP connections.
+/* .IP SMTP_MISC_FLAG_CONN_STORE
+/* Enable saving of cached SMTP or LMTP connections.
+/* .RE
+/* SMTP_MISC_FLAG_CONN_MASK corresponds with both _LOAD and _STORE.
+/* .IP dest_prop
+/* Destination specific session properties: the server is the
+/* best MX host for the current logical destination, the dest,
+/* host, and addr properties. When dest_prop is non-empty, it
+/* overrides the iterator dest, host, and addr properties. It
+/* is the caller's responsibility to save the current nexthop
+/* with SMTP_ITER_SAVE_DEST() and to restore it afterwards
+/* with SMTP_ITER_RESTORE_DEST() before trying alternatives.
+/* .IP endp_prop
+/* Endpoint specific session properties: all the features
+/* advertised by the remote server.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mime_state.h>
+#include <debug_peer.h>
+#include <mail_params.h>
+
+/* TLS Library. */
+
+#include <tls_proxy.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+ /*
+ * Local, because these are meaningful only for code in this file.
+ */
+#define SESS_ATTR_DEST "destination"
+#define SESS_ATTR_HOST "host_name"
+#define SESS_ATTR_ADDR "host_addr"
+#define SESS_ATTR_DEST_FEATURES "destination_features"
+
+#define SESS_ATTR_TLS_LEVEL "tls_level"
+#define SESS_ATTR_REUSE_COUNT "reuse_count"
+#define SESS_ATTR_ENDP_FEATURES "endpoint_features"
+#define SESS_ATTR_EXPIRE_TIME "expire_time"
+
+/* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */
+
+SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, SMTP_ITERATOR *iter,
+ time_t start, int flags)
+{
+ SMTP_SESSION *session;
+ const char *host = STR(iter->host);
+ const char *addr = STR(iter->addr);
+ unsigned port = iter->port;
+
+ session = (SMTP_SESSION *) mymalloc(sizeof(*session));
+ session->stream = stream;
+ session->iterator = iter;
+ session->namaddr = concatenate(host, "[", addr, "]", (char *) 0);
+ session->helo = 0;
+ session->port = port;
+ session->features = 0;
+
+ session->size_limit = 0;
+ session->error_mask = 0;
+ session->buffer = vstring_alloc(100);
+ session->scratch = vstring_alloc(100);
+ session->scratch2 = vstring_alloc(100);
+ smtp_chat_init(session);
+ session->mime_state = 0;
+
+ if (session->port) {
+ vstring_sprintf(session->buffer, "%s:%d",
+ session->namaddr, ntohs(session->port));
+ session->namaddrport = mystrdup(STR(session->buffer));
+ } else
+ session->namaddrport = mystrdup(session->namaddr);
+
+ session->send_proto_helo = 0;
+
+ if (flags & SMTP_MISC_FLAG_CONN_STORE)
+ CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time);
+ else
+ DONT_CACHE_THIS_SESSION;
+ session->reuse_count = 0;
+ USE_NEWBORN_SESSION; /* He's not dead Jim! */
+
+#ifdef USE_SASL_AUTH
+ smtp_sasl_connect(session);
+#endif
+
+#ifdef USE_TLS
+ session->tls_context = 0;
+ session->tls_retry_plain = 0;
+ session->tls_nexthop = 0;
+#endif
+ session->state = 0;
+ debug_peer_check(host, addr);
+ return (session);
+}
+
+/* smtp_session_free - destroy SMTP_SESSION structure and contents */
+
+void smtp_session_free(SMTP_SESSION *session)
+{
+#ifdef USE_TLS
+ if (session->stream) {
+ vstream_fflush(session->stream);
+ }
+ if (session->tls_context) {
+ if (session->features &
+ (SMTP_FEATURE_FROM_CACHE | SMTP_FEATURE_FROM_PROXY))
+ tls_proxy_context_free(session->tls_context);
+ else
+ tls_client_stop(smtp_tls_ctx, session->stream,
+ var_smtp_starttls_tmout, 0, session->tls_context);
+ }
+#endif
+ if (session->stream)
+ vstream_fclose(session->stream);
+ myfree(session->namaddr);
+ myfree(session->namaddrport);
+ if (session->helo)
+ myfree(session->helo);
+
+ vstring_free(session->buffer);
+ vstring_free(session->scratch);
+ vstring_free(session->scratch2);
+
+ if (session->history)
+ smtp_chat_reset(session);
+ if (session->mime_state)
+ mime_state_free(session->mime_state);
+
+#ifdef USE_SASL_AUTH
+ smtp_sasl_cleanup(session);
+#endif
+
+ debug_peer_restore();
+ myfree((void *) session);
+}
+
+/* smtp_session_passivate - passivate an SMTP_SESSION object */
+
+int smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop,
+ VSTRING *endp_prop)
+{
+ SMTP_ITERATOR *iter = session->iterator;
+ VSTREAM *mp;
+ int fd;
+
+ /*
+ * Encode the delivery request next-hop to endpoint binding properties:
+ * whether or not this server is best MX host for the delivery request
+ * next-hop or fall-back logical destination (this information is needed
+ * for loop handling in smtp_proto()).
+ *
+ * TODO: save SASL username and password information so that we can
+ * correctly save a reused authenticated connection.
+ *
+ * These memory writes should never fail.
+ */
+ if ((mp = vstream_memopen(dest_prop, O_WRONLY)) == 0
+ || attr_print_plain(mp, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(SESS_ATTR_DEST, STR(iter->dest)),
+ SEND_ATTR_STR(SESS_ATTR_HOST, STR(iter->host)),
+ SEND_ATTR_STR(SESS_ATTR_ADDR, STR(iter->addr)),
+ SEND_ATTR_INT(SESS_ATTR_DEST_FEATURES,
+ session->features & SMTP_FEATURE_DESTINATION_MASK),
+ ATTR_TYPE_END) != 0
+ || vstream_fclose(mp) != 0)
+ msg_fatal("smtp_session_passivate: can't save dest properties: %m");
+
+ /*
+ * Encode the physical endpoint properties: all the session properties
+ * except for "session from cache", "best MX", or "RSET failure". Plus
+ * the TLS level, reuse count, and connection expiration time.
+ *
+ * XXX Should also record how many non-delivering mail transactions there
+ * were during this session, and perhaps other statistics, so that we
+ * don't reuse a session too much.
+ *
+ * TODO: passivate SASL username and password information so that we can
+ * correctly save a reused authenticated connection.
+ *
+ * These memory writes should never fail.
+ */
+ if ((mp = vstream_memopen(endp_prop, O_WRONLY)) == 0
+ || attr_print_plain(mp, ATTR_FLAG_NONE,
+#ifdef USE_TLS
+ SEND_ATTR_INT(SESS_ATTR_TLS_LEVEL,
+ session->state->tls->level),
+#endif
+ SEND_ATTR_INT(SESS_ATTR_REUSE_COUNT,
+ session->reuse_count),
+ SEND_ATTR_INT(SESS_ATTR_ENDP_FEATURES,
+ session->features & SMTP_FEATURE_ENDPOINT_MASK),
+ SEND_ATTR_LONG(SESS_ATTR_EXPIRE_TIME,
+ (long) session->expire_time),
+ ATTR_TYPE_END) != 0
+
+ /*
+ * Append the passivated TLS context. These memory writes should never
+ * fail.
+ */
+#ifdef USE_TLS
+ || (session->tls_context
+ && attr_print_plain(mp, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_context_print,
+ (void *) session->tls_context),
+ ATTR_TYPE_END) != 0)
+#endif
+ || vstream_fclose(mp) != 0)
+ msg_fatal("smtp_session_passivate: cannot save TLS context: %m");
+
+ /*
+ * Salvage the underlying file descriptor, and destroy the session
+ * object.
+ */
+ fd = vstream_fileno(session->stream);
+ vstream_fdclose(session->stream);
+ session->stream = 0;
+ smtp_session_free(session);
+
+ return (fd);
+}
+
+/* smtp_session_activate - re-activate a passivated SMTP_SESSION object */
+
+SMTP_SESSION *smtp_session_activate(int fd, SMTP_ITERATOR *iter,
+ VSTRING *dest_prop,
+ VSTRING *endp_prop)
+{
+ const char *myname = "smtp_session_activate";
+ VSTREAM *mp;
+ SMTP_SESSION *session;
+ int endp_features; /* server features */
+ int dest_features; /* server features */
+ long expire_time; /* session re-use expiration time */
+ int reuse_count; /* # times reused */
+
+#ifdef USE_TLS
+ TLS_SESS_STATE *tls_context = 0;
+ SMTP_TLS_POLICY *tls = iter->parent->tls;
+
+#define TLS_PROXY_CONTEXT_FREE() do { \
+ if (tls_context) \
+ tls_proxy_context_free(tls_context); \
+ } while (0)
+#else
+#define TLS_PROXY_CONTEXT_FREE() /* nothing */
+#endif
+
+#define SMTP_SESSION_ACTIVATE_ERR_RETURN() do { \
+ TLS_PROXY_CONTEXT_FREE(); \
+ return (0); \
+ } while (0)
+
+ /*
+ * Sanity check: if TLS is required, the cached properties must contain a
+ * TLS context.
+ */
+ if ((mp = vstream_memopen(endp_prop, O_RDONLY)) == 0
+ || attr_scan_plain(mp, ATTR_FLAG_NONE,
+#ifdef USE_TLS
+ RECV_ATTR_INT(SESS_ATTR_TLS_LEVEL,
+ &tls->level),
+#endif
+ RECV_ATTR_INT(SESS_ATTR_REUSE_COUNT,
+ &reuse_count),
+ RECV_ATTR_INT(SESS_ATTR_ENDP_FEATURES,
+ &endp_features),
+ RECV_ATTR_LONG(SESS_ATTR_EXPIRE_TIME,
+ &expire_time),
+ ATTR_TYPE_END) != 4
+#ifdef USE_TLS
+ || ((tls->level > TLS_LEV_MAY
+ || (tls->level == TLS_LEV_MAY && vstream_peek(mp) > 0))
+ && attr_scan_plain(mp, ATTR_FLAG_NONE,
+ RECV_ATTR_FUNC(tls_proxy_context_scan,
+ (void *) &tls_context),
+ ATTR_TYPE_END) != 1)
+#endif
+ || vstream_fclose(mp) != 0) {
+ msg_warn("smtp_session_activate: bad cached endp properties");
+ SMTP_SESSION_ACTIVATE_ERR_RETURN();
+ }
+
+ /*
+ * Clobber the iterator's current nexthop, host and address fields with
+ * cached-connection information. This is done when a session is looked
+ * up by delivery request nexthop instead of address and port. It is the
+ * caller's responsibility to save and restore the delivery request
+ * nexthop with SMTP_ITER_SAVE_DEST() and SMTP_ITER_RESTORE_DEST().
+ *
+ * TODO: Eliminate the duplication between SMTP_ITERATOR and SMTP_SESSION.
+ *
+ * TODO: restore SASL username and password information so that we can
+ * correctly save a reused authenticated connection.
+ */
+ if (dest_prop && VSTRING_LEN(dest_prop)) {
+ if ((mp = vstream_memopen(dest_prop, O_RDONLY)) == 0
+ || attr_scan_plain(mp, ATTR_FLAG_NONE,
+ RECV_ATTR_STR(SESS_ATTR_DEST, iter->dest),
+ RECV_ATTR_STR(SESS_ATTR_HOST, iter->host),
+ RECV_ATTR_STR(SESS_ATTR_ADDR, iter->addr),
+ RECV_ATTR_INT(SESS_ATTR_DEST_FEATURES,
+ &dest_features),
+ ATTR_TYPE_END) != 4
+ || vstream_fclose(mp) != 0) {
+ msg_warn("smtp_session_passivate: bad cached dest properties");
+ SMTP_SESSION_ACTIVATE_ERR_RETURN();
+ }
+ } else {
+ dest_features = 0;
+ }
+#ifdef USE_TLS
+ if (msg_verbose)
+ msg_info("%s: tls_level=%d", myname, tls->level);
+#endif
+
+ /*
+ * Allright, bundle up what we have sofar.
+ */
+#define NO_FLAGS 0
+
+ session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), iter,
+ (time_t) 0, NO_FLAGS);
+ session->features =
+ (endp_features | dest_features | SMTP_FEATURE_FROM_CACHE);
+#ifdef USE_TLS
+ session->tls_context = tls_context;
+#endif
+ CACHE_THIS_SESSION_UNTIL(expire_time);
+ session->reuse_count = ++reuse_count;
+
+ if (msg_verbose)
+ msg_info("%s: dest=%s host=%s addr=%s port=%u features=0x%x, "
+ "ttl=%ld, reuse=%d",
+ myname, STR(iter->dest), STR(iter->host),
+ STR(iter->addr), ntohs(iter->port),
+ endp_features | dest_features,
+ (long) (expire_time - time((time_t *) 0)),
+ reuse_count);
+
+#if USE_TLS
+ if (tls_context)
+ tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_USED,
+ session->tls_context);
+#endif
+
+ return (session);
+}
diff --git a/src/smtp/smtp_state.c b/src/smtp/smtp_state.c
new file mode 100644
index 0000000..f91ab8c
--- /dev/null
+++ b/src/smtp/smtp_state.c
@@ -0,0 +1,114 @@
+/*++
+/* NAME
+/* smtp_state 3
+/* SUMMARY
+/* initialize/cleanup shared state
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* SMTP_STATE *smtp_state_alloc()
+/*
+/* void smtp_state_free(state)
+/* SMTP_STATE *state;
+/* DESCRIPTION
+/* smtp_state_init() initializes the shared state, and allocates
+/* memory for buffers etc.
+/*
+/* smtp_cleanup() destroys memory allocated by smtp_state_init().
+/* STANDARDS
+/* DIAGNOSTICS
+/* BUGS
+/* SEE ALSO
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstring.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+/* smtp_state_alloc - initialize */
+
+SMTP_STATE *smtp_state_alloc(void)
+{
+ SMTP_STATE *state = (SMTP_STATE *) mymalloc(sizeof(*state));
+
+ state->misc_flags = 0;
+ state->src = 0;
+ state->service = 0;
+ state->request = 0;
+ state->session = 0;
+ state->status = 0;
+ state->space_left = 0;
+ state->iterator->request_nexthop = vstring_alloc(100);
+ state->iterator->dest = vstring_alloc(100);
+ state->iterator->host = vstring_alloc(100);
+ state->iterator->addr = vstring_alloc(100);
+ state->iterator->saved_dest = vstring_alloc(100);
+ if (var_smtp_cache_conn) {
+ state->dest_label = vstring_alloc(10);
+ state->dest_prop = vstring_alloc(10);
+ state->endp_label = vstring_alloc(10);
+ state->endp_prop = vstring_alloc(10);
+ state->cache_used = htable_create(1);
+ } else {
+ state->dest_label = 0;
+ state->dest_prop = 0;
+ state->endp_label = 0;
+ state->endp_prop = 0;
+ state->cache_used = 0;
+ }
+ state->why = dsb_create();
+ return (state);
+}
+
+/* smtp_state_free - destroy state */
+
+void smtp_state_free(SMTP_STATE *state)
+{
+#ifdef USE_TLS
+ /* The TLS policy cache lifetime is one delivery. */
+ smtp_tls_policy_cache_flush();
+#endif
+ vstring_free(state->iterator->request_nexthop);
+ vstring_free(state->iterator->dest);
+ vstring_free(state->iterator->host);
+ vstring_free(state->iterator->addr);
+ vstring_free(state->iterator->saved_dest);
+ if (state->dest_label)
+ vstring_free(state->dest_label);
+ if (state->dest_prop)
+ vstring_free(state->dest_prop);
+ if (state->endp_label)
+ vstring_free(state->endp_label);
+ if (state->endp_prop)
+ vstring_free(state->endp_prop);
+ if (state->cache_used)
+ htable_free(state->cache_used, (void (*) (void *)) 0);
+ if (state->why)
+ dsb_free(state->why);
+
+ myfree((void *) state);
+}
diff --git a/src/smtp/smtp_tls_policy.c b/src/smtp/smtp_tls_policy.c
new file mode 100644
index 0000000..4b394a9
--- /dev/null
+++ b/src/smtp/smtp_tls_policy.c
@@ -0,0 +1,928 @@
+/*++
+/* NAME
+/* smtp_tls_policy 3
+/* SUMMARY
+/* SMTP_TLS_POLICY structure management
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* void smtp_tls_list_init()
+/*
+/* int smtp_tls_policy_cache_query(why, tls, iter)
+/* DSN_BUF *why;
+/* SMTP_TLS_POLICY *tls;
+/* SMTP_ITERATOR *iter;
+/*
+/* void smtp_tls_policy_dummy(tls)
+/* SMTP_TLS_POLICY *tls;
+/*
+/* void smtp_tls_policy_cache_flush()
+/* DESCRIPTION
+/* smtp_tls_list_init() initializes lookup tables used by the TLS
+/* policy engine.
+/*
+/* smtp_tls_policy_cache_query() returns a shallow copy of the
+/* cached SMTP_TLS_POLICY structure for the iterator's
+/* destination, host, port and DNSSEC validation status.
+/* This copy is guaranteed to be valid until the next
+/* smtp_tls_policy_cache_query() or smtp_tls_policy_cache_flush()
+/* call. The caller can override the TLS security level without
+/* corrupting the policy cache.
+/* When any required table or DNS lookups fail, the TLS level
+/* is set to TLS_LEV_INVALID, the "why" argument is updated
+/* with the error reason and the result value is zero (false).
+/*
+/* smtp_tls_policy_dummy() initializes a trivial, non-cached,
+/* policy with TLS disabled.
+/*
+/* smtp_tls_policy_cache_flush() destroys the TLS policy cache
+/* and contents.
+/*
+/* Arguments:
+/* .IP why
+/* A pointer to a DSN_BUF which holds error status information when
+/* the TLS policy lookup fails.
+/* .IP tls
+/* Pointer to TLS policy storage.
+/* .IP iter
+/* The literal next-hop or fall-back destination including
+/* the optional [] and including the :port or :service;
+/* the name of the remote host after MX and CNAME expansions
+/* (see smtp_cname_overrides_servername for the handling
+/* of hostnames that resolve to a CNAME record);
+/* the printable address of the remote host;
+/* the remote port in network byte order;
+/* the DNSSEC validation status of the host name lookup after
+/* MX and CNAME expansions.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+#include <netinet/in.h> /* ntohs() for Solaris or BSD */
+#include <arpa/inet.h> /* ntohs() for Linux or BSD */
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+#include <valid_utf8_hostname.h>
+#include <ctable.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <maps.h>
+#include <dsn_buf.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+
+/* XXX Cache size should scale with [sl]mtp_mx_address_limit. */
+#define CACHE_SIZE 20
+static CTABLE *policy_cache;
+
+static int global_tls_level(void);
+static void dane_init(SMTP_TLS_POLICY *, SMTP_ITERATOR *);
+
+static MAPS *tls_policy; /* lookup table(s) */
+static MAPS *tls_per_site; /* lookup table(s) */
+
+/* smtp_tls_list_init - initialize per-site policy lists */
+
+void smtp_tls_list_init(void)
+{
+ if (*var_smtp_tls_policy) {
+ tls_policy = maps_create(VAR_LMTP_SMTP(TLS_POLICY),
+ var_smtp_tls_policy,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if (*var_smtp_tls_per_site)
+ msg_warn("%s ignored when %s is not empty.",
+ VAR_LMTP_SMTP(TLS_PER_SITE), VAR_LMTP_SMTP(TLS_POLICY));
+ return;
+ }
+ if (*var_smtp_tls_per_site) {
+ tls_per_site = maps_create(VAR_LMTP_SMTP(TLS_PER_SITE),
+ var_smtp_tls_per_site,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ }
+}
+
+/* policy_name - printable tls policy level */
+
+static const char *policy_name(int tls_level)
+{
+ const char *name = str_tls_level(tls_level);
+
+ if (name == 0)
+ name = "unknown";
+ return name;
+}
+
+#define MARK_INVALID(why, levelp) do { \
+ dsb_simple((why), "4.7.5", "client TLS configuration problem"); \
+ *(levelp) = TLS_LEV_INVALID; } while (0)
+
+/* tls_site_lookup - look up per-site TLS security level */
+
+static void tls_site_lookup(SMTP_TLS_POLICY *tls, int *site_level,
+ const char *site_name, const char *site_class)
+{
+ const char *lookup;
+
+ /*
+ * Look up a non-default policy. In case of multiple lookup results, the
+ * precedence order is a permutation of the TLS enforcement level order:
+ * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more
+ * specific policy including NONE, otherwise we choose the stronger
+ * enforcement level.
+ */
+ if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) {
+ if (!strcasecmp(lookup, "NONE")) {
+ /* NONE overrides MAY or NOTFOUND. */
+ if (*site_level <= TLS_LEV_MAY)
+ *site_level = TLS_LEV_NONE;
+ } else if (!strcasecmp(lookup, "MAY")) {
+ /* MAY overrides NOTFOUND but not NONE. */
+ if (*site_level < TLS_LEV_NONE)
+ *site_level = TLS_LEV_MAY;
+ } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) {
+ if (*site_level < TLS_LEV_ENCRYPT)
+ *site_level = TLS_LEV_ENCRYPT;
+ } else if (!strcasecmp(lookup, "MUST")) {
+ if (*site_level < TLS_LEV_VERIFY)
+ *site_level = TLS_LEV_VERIFY;
+ } else {
+ msg_warn("%s: unknown TLS policy '%s' for %s %s",
+ tls_per_site->title, lookup, site_class, site_name);
+ MARK_INVALID(tls->why, site_level);
+ return;
+ }
+ } else if (tls_per_site->error) {
+ msg_warn("%s: %s \"%s\": per-site table lookup error",
+ tls_per_site->title, site_class, site_name);
+ dsb_simple(tls->why, "4.3.0", "Temporary lookup error");
+ *site_level = TLS_LEV_INVALID;
+ return;
+ }
+ return;
+}
+
+/* tls_policy_lookup_one - look up destination TLS policy */
+
+static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level,
+ const char *site_name,
+ const char *site_class)
+{
+ const char *lookup;
+ char *policy;
+ char *saved_policy;
+ char *tok;
+ const char *err;
+ char *name;
+ char *val;
+ static VSTRING *cbuf;
+
+#undef FREE_RETURN
+#define FREE_RETURN do { myfree(saved_policy); return; } while (0)
+
+#define INVALID_RETURN(why, levelp) do { \
+ MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0)
+
+#define WHERE \
+ STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \
+ tls_policy->title, site_class, site_name))
+
+ if (cbuf == 0)
+ cbuf = vstring_alloc(10);
+
+ if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
+ if (tls_policy->error) {
+ msg_warn("%s: policy table lookup error", WHERE);
+ MARK_INVALID(tls->why, site_level);
+ }
+ return;
+ }
+ saved_policy = policy = mystrdup(lookup);
+
+ if ((tok = mystrtok(&policy, CHARS_COMMA_SP)) == 0) {
+ msg_warn("%s: invalid empty policy", WHERE);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ *site_level = tls_level_lookup(tok);
+ if (*site_level == TLS_LEV_INVALID) {
+ /* tls_level_lookup() logs no warning. */
+ msg_warn("%s: invalid security level \"%s\"", WHERE, tok);
+ INVALID_RETURN(tls->why, site_level);
+ }
+
+ /*
+ * Warn about ignored attributes when TLS is disabled.
+ */
+ if (*site_level < TLS_LEV_MAY) {
+ while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0)
+ msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
+ WHERE, tok);
+ FREE_RETURN;
+ }
+
+ /*
+ * Errors in attributes may have security consequences, don't ignore
+ * errors that can degrade security.
+ */
+ while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0) {
+ if ((err = split_nameval(tok, &name, &val)) != 0) {
+ msg_warn("%s: malformed attribute/value pair \"%s\": %s",
+ WHERE, tok, err);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "ciphers")) {
+ if (*val == 0) {
+ msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ if (tls->grade) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ tls->grade = mystrdup(val);
+ continue;
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "protocols")) {
+ if (tls->protocols) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ tls->protocols = mystrdup(val);
+ continue;
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "servername")) {
+ if (tls->sni) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ if (valid_hostname(val, DONT_GRIPE))
+ tls->sni = mystrdup(val);
+ else {
+ msg_warn("%s: \"%s=%s\" specifies an invalid hostname",
+ WHERE, name, val);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ continue;
+ }
+ /* Multiple instances per policy. */
+ if (!strcasecmp(name, "match")) {
+ if (*val == 0) {
+ msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ switch (*site_level) {
+ default:
+ msg_warn("%s: attribute \"%s\" invalid at security level "
+ "\"%s\"", WHERE, name, policy_name(*site_level));
+ INVALID_RETURN(tls->why, site_level);
+ break;
+ case TLS_LEV_FPRINT:
+ if (!tls->dane)
+ tls->dane = tls_dane_alloc();
+ tls_dane_add_ee_digests(tls->dane,
+ var_smtp_tls_fpt_dgst, val, "|");
+ break;
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_SECURE:
+ if (tls->matchargv == 0)
+ tls->matchargv = argv_split(val, ":");
+ else
+ argv_split_append(tls->matchargv, val, ":");
+ break;
+ }
+ continue;
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "exclude")) {
+ if (tls->exclusions) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ tls->exclusions = vstring_strcpy(vstring_alloc(10), val);
+ continue;
+ }
+ /* Multiple instances per policy. */
+ if (!strcasecmp(name, "tafile")) {
+ /* Only makes sense if we're using CA-based trust */
+ if (!TLS_MUST_PKIX(*site_level)) {
+ msg_warn("%s: attribute \"%s\" invalid at security level"
+ " \"%s\"", WHERE, name, policy_name(*site_level));
+ INVALID_RETURN(tls->why, site_level);
+ }
+ if (*val == 0) {
+ msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ if (!tls->dane)
+ tls->dane = tls_dane_alloc();
+ if (!tls_dane_load_trustfile(tls->dane, val)) {
+ INVALID_RETURN(tls->why, site_level);
+ }
+ continue;
+ }
+ /* Last one wins. */
+ if (!strcasecmp(name, "connection_reuse")) {
+ if (strcasecmp(val, "yes") == 0) {
+ tls->conn_reuse = 1;
+ } else if (strcasecmp(val, "no") == 0) {
+ tls->conn_reuse = 0;
+ } else {
+ msg_warn("%s: attribute \"%s\" has bad value: \"%s\"",
+ WHERE, name, val);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ continue;
+ }
+ msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
+ INVALID_RETURN(tls->why, site_level);
+ }
+
+ FREE_RETURN;
+}
+
+/* tls_policy_lookup - look up destination TLS policy */
+
+static void tls_policy_lookup(SMTP_TLS_POLICY *tls, int *site_level,
+ const char *site_name,
+ const char *site_class)
+{
+
+ /*
+ * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These
+ * are never the domain part of localpart@domain, rather they are
+ * explicit nexthops from transport:nexthop, and match only the
+ * corresponding policy. Parent domain matching (below) applies only to
+ * sub-domains of the recipient domain.
+ *
+ * XXX UNIX-domain connections query with the pathname as destination.
+ */
+ if (!valid_utf8_hostname(var_smtputf8_enable, site_name, DONT_GRIPE)) {
+ tls_policy_lookup_one(tls, site_level, site_name, site_class);
+ return;
+ }
+ do {
+ tls_policy_lookup_one(tls, site_level, site_name, site_class);
+ } while (*site_level == TLS_LEV_NOTFOUND
+ && (site_name = strchr(site_name + 1, '.')) != 0);
+}
+
+/* load_tas - load one or more ta files */
+
+static int load_tas(TLS_DANE *dane, const char *files)
+{
+ int ret = 0;
+ char *save = mystrdup(files);
+ char *buf = save;
+ char *file;
+
+ do {
+ if ((file = mystrtok(&buf, CHARS_COMMA_SP)) != 0)
+ ret = tls_dane_load_trustfile(dane, file);
+ } while (file && ret);
+
+ myfree(save);
+ return (ret);
+}
+
+/* set_cipher_grade - Set cipher grade and exclusions */
+
+static void set_cipher_grade(SMTP_TLS_POLICY *tls)
+{
+ const char *mand_exclude = "";
+ const char *also_exclude = "";
+
+ /*
+ * Use main.cf cipher level if no per-destination value specified. With
+ * mandatory encryption at least encrypt, and with mandatory verification
+ * at least authenticate!
+ */
+ switch (tls->level) {
+ case TLS_LEV_INVALID:
+ case TLS_LEV_NONE:
+ return;
+
+ case TLS_LEV_MAY:
+ if (tls->grade == 0)
+ tls->grade = mystrdup(var_smtp_tls_ciph);
+ break;
+
+ case TLS_LEV_ENCRYPT:
+ if (tls->grade == 0)
+ tls->grade = mystrdup(var_smtp_tls_mand_ciph);
+ mand_exclude = var_smtp_tls_mand_excl;
+ also_exclude = "eNULL";
+ break;
+
+ case TLS_LEV_HALF_DANE:
+ case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
+ case TLS_LEV_FPRINT:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_SECURE:
+ if (tls->grade == 0)
+ tls->grade = mystrdup(var_smtp_tls_mand_ciph);
+ mand_exclude = var_smtp_tls_mand_excl;
+ also_exclude = "aNULL";
+ break;
+ }
+
+#define ADD_EXCLUDE(vstr, str) \
+ do { \
+ if (*(str)) \
+ vstring_sprintf_append((vstr), "%s%s", \
+ VSTRING_LEN(vstr) ? " " : "", (str)); \
+ } while (0)
+
+ /*
+ * The "exclude" policy table attribute overrides main.cf exclusion
+ * lists.
+ */
+ if (tls->exclusions == 0) {
+ tls->exclusions = vstring_alloc(10);
+ ADD_EXCLUDE(tls->exclusions, var_smtp_tls_excl_ciph);
+ ADD_EXCLUDE(tls->exclusions, mand_exclude);
+ }
+ ADD_EXCLUDE(tls->exclusions, also_exclude);
+}
+
+/* policy_create - create SMTP TLS policy cache object (ctable call-back) */
+
+static void *policy_create(const char *unused_key, void *context)
+{
+ SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context;
+ int site_level;
+ const char *dest = STR(iter->dest);
+ const char *host = STR(iter->host);
+
+ /*
+ * Prepare a pristine policy object.
+ */
+ SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof(*tls));
+
+ smtp_tls_policy_init(tls, dsb_create());
+ tls->conn_reuse = var_smtp_tls_conn_reuse;
+
+ /*
+ * Compute the per-site TLS enforcement level. For compatibility with the
+ * original TLS patch, this algorithm is gives equal precedence to host
+ * and next-hop policies.
+ */
+ tls->level = global_tls_level();
+ site_level = TLS_LEV_NOTFOUND;
+
+ if (tls_policy) {
+ tls_policy_lookup(tls, &site_level, dest, "next-hop destination");
+ } else if (tls_per_site) {
+ tls_site_lookup(tls, &site_level, dest, "next-hop destination");
+ if (site_level != TLS_LEV_INVALID
+ && strcasecmp_utf8(dest, host) != 0)
+ tls_site_lookup(tls, &site_level, host, "server hostname");
+
+ /*
+ * Override a wild-card per-site policy with a more specific global
+ * policy.
+ *
+ * With the original TLS patch, 1) a per-site ENCRYPT could not override
+ * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy
+ * produced inconsistent results: it changed a global VERIFY into
+ * NONE, while producing MAY with all weaker global policy settings.
+ *
+ * With the current implementation, a combined per-site (NONE+MAY)
+ * consistently overrides global policy with NONE, and global policy
+ * can override only a per-site MAY wildcard. That is, specific
+ * policies consistently override wildcard policies, and
+ * (non-wildcard) per-site policies consistently override global
+ * policies.
+ */
+ if (site_level == TLS_LEV_MAY && tls->level > TLS_LEV_MAY)
+ site_level = tls->level;
+ }
+ switch (site_level) {
+ default:
+ tls->level = site_level;
+ /* FALLTHROUGH */
+ case TLS_LEV_NOTFOUND:
+ break;
+ case TLS_LEV_INVALID:
+ tls->level = site_level;
+ return ((void *) tls);
+ }
+
+ /*
+ * DANE initialization may change the security level to something else,
+ * so do this early, so that we use the right level below. Note that
+ * "dane-only" changes to "dane" once we obtain the requisite TLSA
+ * records.
+ */
+ if (TLS_DANE_BASED(tls->level))
+ dane_init(tls, iter);
+ if (tls->level == TLS_LEV_INVALID)
+ return ((void *) tls);
+
+ /*
+ * Use main.cf protocols and SNI settings if not set in per-destination
+ * table.
+ */
+ if (tls->level > TLS_LEV_NONE && tls->protocols == 0)
+ tls->protocols =
+ mystrdup((tls->level == TLS_LEV_MAY) ?
+ var_smtp_tls_proto : var_smtp_tls_mand_proto);
+ if (tls->level > TLS_LEV_NONE && tls->sni == 0) {
+ if (!*var_smtp_tls_sni || valid_hostname(var_smtp_tls_sni, DONT_GRIPE))
+ tls->sni = mystrdup(var_smtp_tls_sni);
+ else {
+ msg_warn("\"%s = %s\" specifies an invalid hostname",
+ VAR_LMTP_SMTP(TLS_SNI), var_smtp_tls_sni);
+ MARK_INVALID(tls->why, &tls->level);
+ return ((void *) tls);
+ }
+ }
+
+ /*
+ * Compute cipher grade (if set in per-destination table, else
+ * set_cipher() uses main.cf settings) and security level dependent
+ * cipher exclusion list.
+ */
+ set_cipher_grade(tls);
+
+ /*
+ * Use main.cf cert_match setting if not set in per-destination table.
+ */
+ switch (tls->level) {
+ case TLS_LEV_INVALID:
+ case TLS_LEV_NONE:
+ case TLS_LEV_MAY:
+ case TLS_LEV_ENCRYPT:
+ case TLS_LEV_HALF_DANE:
+ case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
+ break;
+ case TLS_LEV_FPRINT:
+ if (tls->dane == 0)
+ tls->dane = tls_dane_alloc();
+ if (!TLS_DANE_HASEE(tls->dane)) {
+ tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst,
+ var_smtp_tls_fpt_cmatch, CHARS_COMMA_SP);
+ if (!TLS_DANE_HASEE(tls->dane)) {
+ msg_warn("nexthop domain %s: configured at fingerprint "
+ "security level, but with no fingerprints to match.",
+ dest);
+ MARK_INVALID(tls->why, &tls->level);
+ return ((void *) tls);
+ }
+ }
+ break;
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_SECURE:
+ if (tls->matchargv == 0)
+ tls->matchargv =
+ argv_split(tls->level == TLS_LEV_VERIFY ?
+ var_smtp_tls_vfy_cmatch : var_smtp_tls_sec_cmatch,
+ CHARS_COMMA_SP ":");
+ if (*var_smtp_tls_tafile) {
+ if (tls->dane == 0)
+ tls->dane = tls_dane_alloc();
+ if (!TLS_DANE_HASTA(tls->dane)
+ && !load_tas(tls->dane, var_smtp_tls_tafile)) {
+ MARK_INVALID(tls->why, &tls->level);
+ return ((void *) tls);
+ }
+ }
+ break;
+ default:
+ msg_panic("unexpected TLS security level: %d", tls->level);
+ }
+
+ if (msg_verbose && tls->level != global_tls_level())
+ msg_info("%s TLS level: %s", "effective", policy_name(tls->level));
+
+ return ((void *) tls);
+}
+
+/* policy_delete - free no longer cached policy (ctable call-back) */
+
+static void policy_delete(void *item, void *unused_context)
+{
+ SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) item;
+
+ if (tls->protocols)
+ myfree(tls->protocols);
+ if (tls->sni)
+ myfree(tls->sni);
+ if (tls->grade)
+ myfree(tls->grade);
+ if (tls->exclusions)
+ vstring_free(tls->exclusions);
+ if (tls->matchargv)
+ argv_free(tls->matchargv);
+ if (tls->dane)
+ tls_dane_free(tls->dane);
+ dsb_free(tls->why);
+
+ myfree((void *) tls);
+}
+
+/* smtp_tls_policy_cache_query - cached lookup of TLS policy */
+
+int smtp_tls_policy_cache_query(DSN_BUF *why, SMTP_TLS_POLICY *tls,
+ SMTP_ITERATOR *iter)
+{
+ VSTRING *key;
+
+ /*
+ * Create an empty TLS Policy cache on the fly.
+ */
+ if (policy_cache == 0)
+ policy_cache =
+ ctable_create(CACHE_SIZE, policy_create, policy_delete, (void *) 0);
+
+ /*
+ * Query the TLS Policy cache, with a search key that reflects our shared
+ * values that also appear in other cache and table search keys.
+ */
+ key = vstring_alloc(100);
+ smtp_key_prefix(key, ":", iter, SMTP_KEY_FLAG_CUR_NEXTHOP
+ | SMTP_KEY_FLAG_HOSTNAME
+ | SMTP_KEY_FLAG_PORT);
+ ctable_newcontext(policy_cache, (void *) iter);
+ *tls = *(SMTP_TLS_POLICY *) ctable_locate(policy_cache, STR(key));
+ vstring_free(key);
+
+ /*
+ * Report errors. Both error and non-error results are cached. We must
+ * therefore copy the cached DSN buffer content to the caller's buffer.
+ */
+ if (tls->level == TLS_LEV_INVALID) {
+ /* XXX Simplify this by implementing a "copy" primitive. */
+ dsb_update(why,
+ STR(tls->why->status), STR(tls->why->action),
+ STR(tls->why->mtype), STR(tls->why->mname),
+ STR(tls->why->dtype), STR(tls->why->dtext),
+ "%s", STR(tls->why->reason));
+ return (0);
+ } else {
+ return (1);
+ }
+}
+
+/* smtp_tls_policy_cache_flush - flush TLS policy cache */
+
+void smtp_tls_policy_cache_flush(void)
+{
+ if (policy_cache != 0) {
+ ctable_free(policy_cache);
+ policy_cache = 0;
+ }
+}
+
+/* global_tls_level - parse and cache var_smtp_tls_level */
+
+static int global_tls_level(void)
+{
+ static int l = TLS_LEV_NOTFOUND;
+
+ if (l != TLS_LEV_NOTFOUND)
+ return l;
+
+ /*
+ * Compute the global TLS policy. This is the default policy level when
+ * no per-site policy exists. It also is used to override a wild-card
+ * per-site policy.
+ *
+ * We require that the global level is valid on startup.
+ */
+ if (*var_smtp_tls_level) {
+ if ((l = tls_level_lookup(var_smtp_tls_level)) == TLS_LEV_INVALID)
+ msg_fatal("invalid tls security level: \"%s\"", var_smtp_tls_level);
+ } else if (var_smtp_enforce_tls)
+ l = var_smtp_tls_enforce_peername ? TLS_LEV_VERIFY : TLS_LEV_ENCRYPT;
+ else
+ l = var_smtp_use_tls ? TLS_LEV_MAY : TLS_LEV_NONE;
+
+ if (msg_verbose)
+ msg_info("%s TLS level: %s", "global", policy_name(l));
+
+ return l;
+}
+
+#define NONDANE_CONFIG 0 /* Administrator's fault */
+#define NONDANE_DEST 1 /* Remote server's fault */
+#define DANE_CANTAUTH 2 /* Remote server's fault */
+
+static void PRINTFLIKE(4, 5) dane_incompat(SMTP_TLS_POLICY *tls,
+ SMTP_ITERATOR *iter,
+ int errtype,
+ const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (tls->level == TLS_LEV_DANE) {
+ tls->level = (errtype == DANE_CANTAUTH) ? TLS_LEV_ENCRYPT : TLS_LEV_MAY;
+ if (errtype == NONDANE_CONFIG)
+ vmsg_warn(fmt, ap);
+ else if (msg_verbose)
+ vmsg_info(fmt, ap);
+ } else { /* dane-only */
+ if (errtype == NONDANE_CONFIG) {
+ vmsg_warn(fmt, ap);
+ MARK_INVALID(tls->why, &tls->level);
+ } else {
+ tls->level = TLS_LEV_INVALID;
+ vdsb_simple(tls->why, "4.7.5", fmt, ap);
+ }
+ }
+ va_end(ap);
+}
+
+/* dane_init - special initialization for "dane" security level */
+
+static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
+{
+ TLS_DANE *dane;
+
+ if (!iter->port) {
+ msg_warn("%s: the \"dane\" security level is invalid for delivery via"
+ " unix-domain sockets", STR(iter->dest));
+ MARK_INVALID(tls->why, &tls->level);
+ return;
+ }
+ if (!tls_dane_avail()) {
+ dane_incompat(tls, iter, NONDANE_CONFIG,
+ "%s: %s configured, but no requisite library support",
+ STR(iter->dest), policy_name(tls->level));
+ return;
+ }
+ if (!(smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS)
+ || smtp_dns_support != SMTP_DNS_DNSSEC) {
+ dane_incompat(tls, iter, NONDANE_CONFIG,
+ "%s: %s configured with dnssec lookups disabled",
+ STR(iter->dest), policy_name(tls->level));
+ return;
+ }
+
+ /*
+ * If we ignore MX lookup errors, we also ignore DNSSEC security problems
+ * and thus avoid any reasonable expectation that we get the right DANE
+ * key material.
+ */
+ if (smtp_mode && var_ign_mx_lookup_err) {
+ dane_incompat(tls, iter, NONDANE_CONFIG,
+ "%s: %s configured with MX lookup errors ignored",
+ STR(iter->dest), policy_name(tls->level));
+ return;
+ }
+
+ /*
+ * This is not optional, code in tls_dane.c assumes that the nexthop
+ * qname is already an fqdn. If we're using these flags to go from qname
+ * to rname, the assumption is invalid. Likewise we cannot add the qname
+ * to certificate name checks, ...
+ */
+ if (smtp_dns_res_opt & (RES_DEFNAMES | RES_DNSRCH)) {
+ dane_incompat(tls, iter, NONDANE_CONFIG,
+ "%s: dns resolver options incompatible with %s TLS",
+ STR(iter->dest), policy_name(tls->level));
+ return;
+ }
+
+ /*
+ * When the MX name is present and insecure, DANE may not apply, we then
+ * either fail if DANE is mandatory or use regular opportunistic TLS if
+ * the insecure MX level is "may".
+ */
+ if (iter->mx && !iter->mx->dnssec_valid
+ && (tls->level == TLS_LEV_DANE_ONLY ||
+ smtp_tls_insecure_mx_policy <= TLS_LEV_MAY)) {
+ dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination");
+ return;
+ }
+ /* When TLSA lookups fail, we defer the message */
+ if ((dane = tls_dane_resolve(iter->port, "tcp", iter->rr,
+ var_smtp_tls_force_tlsa)) == 0) {
+ tls->level = TLS_LEV_INVALID;
+ dsb_simple(tls->why, "4.7.5", "TLSA lookup error for %s:%u",
+ STR(iter->host), ntohs(iter->port));
+ return;
+ }
+ if (tls_dane_notfound(dane)) {
+ dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found");
+ tls_dane_free(dane);
+ return;
+ }
+
+ /*
+ * Some TLSA records found, but none usable, per
+ *
+ * https://tools.ietf.org/html/draft-ietf-dane-srv-02#section-4
+ *
+ * we MUST use TLS, and SHALL use full PKIX certificate checks. The latter
+ * would be unwise for SMTP: no human present to "click ok" and risk of
+ * non-delivery in most cases exceeds risk of interception.
+ *
+ * We also have a form of Goedel's incompleteness theorem in play: any list
+ * of public root CA certs is either incomplete or inconsistent (for any
+ * given verifier some of the CAs are surely not trustworthy).
+ */
+ if (tls_dane_unusable(dane)) {
+ dane_incompat(tls, iter, DANE_CANTAUTH, "TLSA records unusable");
+ tls_dane_free(dane);
+ return;
+ }
+
+ /*
+ * Perhaps downgrade to "encrypt" if MX is insecure.
+ */
+ if (iter->mx && !iter->mx->dnssec_valid) {
+ if (smtp_tls_insecure_mx_policy == TLS_LEV_ENCRYPT) {
+ dane_incompat(tls, iter, DANE_CANTAUTH,
+ "Verification not possible, MX RRset is insecure");
+ tls_dane_free(dane);
+ return;
+ }
+ if (tls->level != TLS_LEV_DANE
+ || smtp_tls_insecure_mx_policy != TLS_LEV_DANE)
+ msg_panic("wrong state for insecure MX host DANE policy");
+
+ /* For correct logging in tls_client_start() */
+ tls->level = TLS_LEV_HALF_DANE;
+ }
+
+ /*
+ * With DANE trust anchors, peername matching is not configurable.
+ */
+ if (TLS_DANE_HASTA(dane)) {
+ tls->matchargv = argv_alloc(2);
+ argv_add(tls->matchargv, dane->base_domain, ARGV_END);
+ if (iter->mx) {
+ if (strcmp(iter->mx->qname, iter->mx->rname) == 0)
+ argv_add(tls->matchargv, iter->mx->qname, ARGV_END);
+ else
+ argv_add(tls->matchargv, iter->mx->rname,
+ iter->mx->qname, ARGV_END);
+ }
+ } else if (!TLS_DANE_HASEE(dane))
+ msg_panic("empty DANE match list");
+ tls->dane = dane;
+ return;
+}
+
+#endif
diff --git a/src/smtp/smtp_trouble.c b/src/smtp/smtp_trouble.c
new file mode 100644
index 0000000..1320c24
--- /dev/null
+++ b/src/smtp/smtp_trouble.c
@@ -0,0 +1,462 @@
+/*++
+/* NAME
+/* smtp_trouble 3
+/* SUMMARY
+/* error handler policies
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* int smtp_sess_fail(state)
+/* SMTP_STATE *state;
+/*
+/* int smtp_site_fail(state, mta_name, resp, format, ...)
+/* SMTP_STATE *state;
+/* const char *mta_name;
+/* SMTP_RESP *resp;
+/* const char *format;
+/*
+/* int smtp_mesg_fail(state, mta_name, resp, format, ...)
+/* SMTP_STATE *state;
+/* const char *mta_name;
+/* SMTP_RESP *resp;
+/* const char *format;
+/*
+/* void smtp_rcpt_fail(state, recipient, mta_name, resp, format, ...)
+/* SMTP_STATE *state;
+/* RECIPIENT *recipient;
+/* const char *mta_name;
+/* SMTP_RESP *resp;
+/* const char *format;
+/*
+/* int smtp_stream_except(state, exception, description)
+/* SMTP_STATE *state;
+/* int exception;
+/* const char *description;
+/* AUXILIARY FUNCTIONS
+/* int smtp_misc_fail(state, throttle, mta_name, resp, format, ...)
+/* SMTP_STATE *state;
+/* int throttle;
+/* const char *mta_name;
+/* SMTP_RESP *resp;
+/* const char *format;
+/* DESCRIPTION
+/* This module handles all non-fatal errors that can happen while
+/* attempting to deliver mail via SMTP, and implements the policy
+/* of how to deal with the error. Depending on the nature of
+/* the problem, delivery of a single message is deferred, delivery
+/* of all messages to the same domain is deferred, or one or more
+/* recipients are given up as non-deliverable and a bounce log is
+/* updated. In any case, the recipient is marked as either KEEP
+/* (try again with a backup host) or DROP (delete recipient from
+/* delivery request).
+/*
+/* In addition, when an unexpected response code is seen such
+/* as 3xx where only 4xx or 5xx are expected, or any error code
+/* that suggests a syntax error or something similar, the
+/* protocol error flag is set so that the postmaster receives
+/* a transcript of the session. No notification is generated for
+/* what appear to be configuration errors - very likely, they
+/* would suffer the same problem and just cause more trouble.
+/*
+/* In case of a soft error, action depends on whether the error
+/* qualifies for trying the request with other mail servers (log
+/* an informational record only and try a backup server) or
+/* whether this is the final server (log recipient delivery status
+/* records and delete the recipient from the request).
+/*
+/* smtp_sess_fail() takes a pre-formatted error report after
+/* failure to complete some protocol handshake. The policy is
+/* as with smtp_site_fail().
+/*
+/* smtp_site_fail() handles the case where the program fails to
+/* complete the initial handshake: the server is not reachable,
+/* is not running, does not want talk to us, or we talk to ourselves.
+/* The \fIcode\fR gives an error status code; the \fIformat\fR
+/* argument gives a textual description.
+/* The policy is: soft error, non-final server: log an informational
+/* record why the host is being skipped; soft error, final server:
+/* defer delivery of all remaining recipients and mark the destination
+/* as problematic; hard error: bounce all remaining recipients.
+/* The session is marked as "do not cache".
+/* The result is non-zero.
+/*
+/* smtp_mesg_fail() handles the case where the smtp server
+/* does not accept the sender address or the message data,
+/* or when the local MTA is unable to convert the message data.
+/* The policy is: soft error, non-final server: log an informational
+/* record why the host is being skipped; soft error, final server:
+/* defer delivery of all remaining recipients; hard error: bounce all
+/* remaining recipients.
+/* The result is non-zero.
+/*
+/* smtp_misc_fail() provides a more detailed interface than
+/* smtp_site_fail() and smtp_mesg_fail(), which are convenience
+/* wrappers around smtp_misc_fail(). The throttle argument
+/* is either SMTP_THROTTLE or SMTP_NOTHROTTLE; it is used only
+/* in the "soft error, final server" policy, and determines
+/* whether a destination will be marked as problematic.
+/*
+/* smtp_rcpt_fail() handles the case where a recipient is not
+/* accepted by the server for reasons other than that the server
+/* recipient limit is reached.
+/* The policy is: soft error, non-final server: log an informational
+/* record why the recipient is being skipped; soft error, final server:
+/* defer delivery of this recipient; hard error: bounce this
+/* recipient.
+/*
+/* smtp_stream_except() handles the exceptions generated by
+/* the smtp_stream(3) module (i.e. timeouts and I/O errors).
+/* The \fIexception\fR argument specifies the type of problem.
+/* The \fIdescription\fR argument describes at what stage of
+/* the SMTP dialog the problem happened.
+/* The policy is: non-final server: log an informational record
+/* with the reason why the host is being skipped; final server:
+/* defer delivery of all remaining recipients.
+/* Retry plaintext delivery after TLS post-handshake session
+/* failure, provided that at least one recipient was not
+/* deferred or rejected during the TLS phase, and that global
+/* preconditions for plaintext fallback are met.
+/* The session is marked as "do not cache".
+/* The result is non-zero.
+/*
+/* Arguments:
+/* .IP state
+/* SMTP client state per delivery request.
+/* .IP resp
+/* Server response including reply code and text.
+/* .IP recipient
+/* Undeliverable recipient address information.
+/* .IP format
+/* Human-readable description of why mail is not deliverable.
+/* DIAGNOSTICS
+/* Panic: unknown exception code.
+/* SEE ALSO
+/* smtp_proto(3) smtp high-level protocol
+/* smtp_stream(3) smtp low-level protocol
+/* defer(3) basic message defer interface
+/* bounce(3) basic message bounce interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <deliver_request.h>
+#include <deliver_completed.h>
+#include <bounce.h>
+#include <defer.h>
+#include <mail_error.h>
+#include <dsn_buf.h>
+#include <dsn.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+#include "smtp_sasl.h"
+
+/* smtp_check_code - check response code */
+
+static void smtp_check_code(SMTP_SESSION *session, int code)
+{
+
+ /*
+ * The intention of this code is to alert the postmaster when the local
+ * Postfix SMTP client screws up, protocol wise. RFC 821 says that x0z
+ * replies "refer to syntax errors, syntactically correct commands that
+ * don't fit any functional category, and unimplemented or superfluous
+ * commands". Unfortunately, this also triggers postmaster notices when
+ * remote servers screw up, protocol wise. This is becoming a common
+ * problem now that response codes are configured manually as part of
+ * anti-UCE systems, by people who aren't aware of RFC details.
+ */
+ if (code < 400 || code > 599
+ || code == 555 /* RFC 1869, section 6.1. */
+ || (code >= 500 && code < 510))
+ session->error_mask |= MAIL_ERROR_PROTOCOL;
+}
+
+/* smtp_bulk_fail - skip, defer or bounce recipients, maybe throttle queue */
+
+static int smtp_bulk_fail(SMTP_STATE *state, int throttle_queue)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ DSN_BUF *why = state->why;
+ RECIPIENT *rcpt;
+ int status;
+ int aggregate_status;
+ int soft_error = (STR(why->status)[0] == '4');
+ int soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce);
+ int nrcpt;
+
+ /*
+ * Don't defer the recipients just yet when this error qualifies them for
+ * delivery to a backup server. Just log something informative to show
+ * why we're skipping this host.
+ */
+ if ((soft_error || soft_bounce_error)
+ && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) {
+ msg_info("%s: %s", request->queue_id, STR(why->reason));
+ for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ if (SMTP_RCPT_ISMARKED(rcpt))
+ continue;
+ SMTP_RCPT_KEEP(state, rcpt);
+ }
+ }
+
+ /*
+ * Defer or bounce all the remaining recipients, and delete them from the
+ * delivery request. If a bounce fails, defer instead and do not qualify
+ * the recipient for delivery to a backup server.
+ */
+ else {
+
+ /*
+ * If we are still in the connection set-up phase, update the set-up
+ * completion time here, otherwise the time spent in set-up latency
+ * will be attributed as message transfer latency.
+ *
+ * All remaining recipients have failed at this point, so we update the
+ * delivery completion time stamp so that multiple recipient status
+ * records show the same delay values.
+ */
+ if (request->msg_stats.conn_setup_done.tv_sec == 0) {
+ GETTIMEOFDAY(&request->msg_stats.conn_setup_done);
+ request->msg_stats.deliver_done =
+ request->msg_stats.conn_setup_done;
+ } else
+ GETTIMEOFDAY(&request->msg_stats.deliver_done);
+
+ (void) DSN_FROM_DSN_BUF(why);
+ aggregate_status = 0;
+ for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) {
+ rcpt = request->rcpt_list.info + nrcpt;
+ if (SMTP_RCPT_ISMARKED(rcpt))
+ continue;
+ status = (soft_error ? defer_append : bounce_append)
+ (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id,
+ &request->msg_stats, rcpt,
+ session ? session->namaddrport : "none", &why->dsn);
+ if (status == 0)
+ deliver_completed(state->src, rcpt->offset);
+ SMTP_RCPT_DROP(state, rcpt);
+ aggregate_status |= status;
+ }
+ state->status |= aggregate_status;
+ if ((state->misc_flags & SMTP_MISC_FLAG_COMPLETE_SESSION) == 0
+ && throttle_queue && aggregate_status
+ && request->hop_status == 0)
+ request->hop_status = DSN_COPY(&why->dsn);
+ }
+
+ /*
+ * Don't cache this session. We can't talk to this server.
+ */
+ if (throttle_queue && session)
+ DONT_CACHE_THROTTLED_SESSION;
+
+ return (-1);
+}
+
+/* smtp_sess_fail - skip site, defer or bounce all recipients */
+
+int smtp_sess_fail(SMTP_STATE *state)
+{
+
+ /*
+ * We can't avoid copying copying lots of strings into VSTRING buffers,
+ * because this error information is collected by a routine that
+ * terminates BEFORE the error is reported.
+ */
+ return (smtp_bulk_fail(state, SMTP_THROTTLE));
+}
+
+/* vsmtp_fill_dsn - fill in temporary DSN structure */
+
+static void vsmtp_fill_dsn(SMTP_STATE *state, const char *mta_name,
+ const char *status, const char *reply,
+ const char *format, va_list ap)
+{
+ DSN_BUF *why = state->why;
+
+ /*
+ * We could avoid copying lots of strings into VSTRING buffers, because
+ * this error information is given to us by a routine that terminates
+ * AFTER the error is reported. However, this results in ugly kludges
+ * when informal text needs to be formatted. So we maintain consistency
+ * with other error reporting in the SMTP client even if we waste a few
+ * cycles.
+ */
+ VSTRING_RESET(why->reason);
+ if (mta_name && status && status[0] != '4' && status[0] != '5') {
+ vstring_strcpy(why->reason, "Protocol error: ");
+ status = "5.5.0";
+ }
+ vstring_vsprintf_append(why->reason, format, ap);
+ dsb_formal(why, status, DSB_DEF_ACTION,
+ mta_name ? DSB_MTYPE_DNS : DSB_MTYPE_NONE, mta_name,
+ reply ? DSB_DTYPE_SMTP : DSB_DTYPE_NONE, reply);
+}
+
+/* smtp_misc_fail - maybe throttle queue; skip/defer/bounce all recipients */
+
+int smtp_misc_fail(SMTP_STATE *state, int throttle, const char *mta_name,
+ SMTP_RESP *resp, const char *format,...)
+{
+ va_list ap;
+
+ /*
+ * Initialize.
+ */
+ va_start(ap, format);
+ vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap);
+ va_end(ap);
+
+ if (state->session && mta_name)
+ smtp_check_code(state->session, resp->code);
+
+ /*
+ * Skip, defer or bounce recipients, and throttle this queue.
+ */
+ return (smtp_bulk_fail(state, throttle));
+}
+
+/* smtp_rcpt_fail - skip, defer, or bounce recipient */
+
+void smtp_rcpt_fail(SMTP_STATE *state, RECIPIENT *rcpt, const char *mta_name,
+ SMTP_RESP *resp, const char *format,...)
+{
+ DELIVER_REQUEST *request = state->request;
+ SMTP_SESSION *session = state->session;
+ DSN_BUF *why = state->why;
+ int status;
+ int soft_error;
+ int soft_bounce_error;
+ va_list ap;
+
+ /*
+ * Sanity check.
+ */
+ if (SMTP_RCPT_ISMARKED(rcpt))
+ msg_panic("smtp_rcpt_fail: recipient <%s> is marked", rcpt->address);
+
+ /*
+ * Initialize.
+ */
+ va_start(ap, format);
+ vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap);
+ va_end(ap);
+ soft_error = STR(why->status)[0] == '4';
+ soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce);
+
+ if (state->session && mta_name)
+ smtp_check_code(state->session, resp->code);
+
+ /*
+ * Don't defer this recipient record just yet when this error qualifies
+ * for trying other mail servers. Just log something informative to show
+ * why we're skipping this recipient now.
+ */
+ if ((soft_error || soft_bounce_error)
+ && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) {
+ msg_info("%s: %s", request->queue_id, STR(why->reason));
+ SMTP_RCPT_KEEP(state, rcpt);
+ }
+
+ /*
+ * Defer or bounce this recipient, and delete from the delivery request.
+ * If the bounce fails, defer instead and do not qualify the recipient
+ * for delivery to a backup server.
+ *
+ * Note: we may still make an SMTP connection to deliver other recipients
+ * that did qualify for delivery to a backup server.
+ */
+ else {
+ (void) DSN_FROM_DSN_BUF(state->why);
+ status = (soft_error ? defer_append : bounce_append)
+ (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id,
+ &request->msg_stats, rcpt,
+ session ? session->namaddrport : "none", &why->dsn);
+ if (status == 0)
+ deliver_completed(state->src, rcpt->offset);
+ SMTP_RCPT_DROP(state, rcpt);
+ state->status |= status;
+ }
+}
+
+/* smtp_stream_except - defer domain after I/O problem */
+
+int smtp_stream_except(SMTP_STATE *state, int code, const char *description)
+{
+ SMTP_SESSION *session = state->session;
+ DSN_BUF *why = state->why;
+
+ /*
+ * Sanity check.
+ */
+ if (session == 0)
+ msg_panic("smtp_stream_except: no session");
+
+ /*
+ * Initialize.
+ */
+ switch (code) {
+ default:
+ msg_panic("smtp_stream_except: unknown exception %d", code);
+ case SMTP_ERR_EOF:
+ dsb_simple(why, "4.4.2", "lost connection with %s while %s",
+ session->namaddr, description);
+#ifdef USE_TLS
+ if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE)
+ RETRY_AS_PLAINTEXT;
+#endif
+ break;
+ case SMTP_ERR_TIME:
+ dsb_simple(why, "4.4.2", "conversation with %s timed out while %s",
+ session->namaddr, description);
+#ifdef USE_TLS
+ if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE)
+ RETRY_AS_PLAINTEXT;
+#endif
+ break;
+ case SMTP_ERR_DATA:
+ session->error_mask |= MAIL_ERROR_DATA;
+ dsb_simple(why, "4.3.0", "local data error while talking to %s",
+ session->namaddr);
+ }
+
+ /*
+ * The smtp_bulk_fail() call below will not throttle the destination when
+ * falling back to plaintext, because RETRY_AS_PLAINTEXT clears the
+ * FINAL_SERVER flag.
+ */
+ return (smtp_bulk_fail(state, SMTP_THROTTLE));
+}
diff --git a/src/smtp/smtp_unalias.c b/src/smtp/smtp_unalias.c
new file mode 100644
index 0000000..1c4d34d
--- /dev/null
+++ b/src/smtp/smtp_unalias.c
@@ -0,0 +1,142 @@
+/*++
+/* NAME
+/* smtp_unalias 3
+/* SUMMARY
+/* replace host/domain alias by official name
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* const char *smtp_unalias_name(name)
+/* const char *name;
+/*
+/* VSTRING *smtp_unalias_addr(result, addr)
+/* VSTRING *result;
+/* const char *addr;
+/* DESCRIPTION
+/* smtp_unalias_name() looks up A or MX records for the specified
+/* name and returns the official name provided in the reply. When
+/* no A or MX record is found, the result is a copy of the input.
+/* In order to improve performance, smtp_unalias_name() remembers
+/* results in a private cache, so a name is looked up only once.
+/*
+/* smtp_unalias_addr() returns the input address, which is in
+/* RFC 821 quoted form, after replacing the domain part by the
+/* official name as found by smtp_unalias_name(). When the input
+/* contains no domain part, the result is a copy of the input.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <htable.h>
+#include <vstring.h>
+#include <msg.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+
+static int smtp_unalias_flags;
+
+/* smtp_unalias_name - look up the official host or domain name. */
+
+const char *smtp_unalias_name(const char *name)
+{
+ static HTABLE *cache;
+ VSTRING *fqdn;
+ char *result;
+
+ if (*name == '[')
+ return (name);
+
+ /*
+ * Initialize the cache on the fly. The smtp client is designed to exit
+ * after servicing a limited number of requests, so there is no need to
+ * prevent the cache from growing too large, or to expire old entries.
+ */
+ if (cache == 0)
+ cache = htable_create(10);
+
+ /*
+ * Look up the fqdn. If none is found use the query name instead, so that
+ * we won't lose time looking up the same bad name again.
+ */
+ if ((result = htable_find(cache, name)) == 0) {
+ fqdn = vstring_alloc(10);
+ if (dns_lookup_l(name, smtp_unalias_flags, (DNS_RR **) 0, fqdn,
+ (VSTRING *) 0, DNS_REQ_FLAG_NONE, T_MX, T_A,
+#ifdef HAS_IPV6
+ T_AAAA,
+#endif
+ 0) != DNS_OK)
+ vstring_strcpy(fqdn, name);
+ htable_enter(cache, name, result = vstring_export(fqdn));
+ }
+ return (result);
+}
+
+/* smtp_unalias_addr - rewrite aliases in domain part of address */
+
+VSTRING *smtp_unalias_addr(VSTRING *result, const char *addr)
+{
+ char *at;
+ const char *fqdn;
+
+ if ((at = strrchr(addr, '@')) == 0 || at[1] == 0) {
+ vstring_strcpy(result, addr);
+ } else {
+ fqdn = smtp_unalias_name(at + 1);
+ vstring_strncpy(result, addr, at - addr + 1);
+ vstring_strcat(result, fqdn);
+ }
+ return (result);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - read address from stdin, print result on stdout.
+ */
+
+#include <vstring_vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *addr = vstring_alloc(10);
+ VSTRING *result = vstring_alloc(10);
+
+ smtp_unalias_flags |= RES_DEBUG;
+
+ while (vstring_fgets_nonl(addr, VSTREAM_IN)) {
+ smtp_unalias_addr(result, vstring_str(addr));
+ vstream_printf("%s -> %s\n", vstring_str(addr), vstring_str(result));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(addr);
+ vstring_free(result);
+ return (0);
+}
+
+#endif
diff --git a/src/smtp/tls_policy.in b/src/smtp/tls_policy.in
new file mode 100644
index 0000000..95c295b
--- /dev/null
+++ b/src/smtp/tls_policy.in
@@ -0,0 +1,64 @@
+- - -
+- - may
+- - must_nopeermatch
+- - must
+- none -
+- none may
+- none must_nopeermatch
+- none must
+- may -
+- may may
+- may must_nopeermatch
+- may must
+- must_nopeermatch -
+- must_nopeermatch may
+- must_nopeermatch must_nopeermatch
+- must_nopeermatch must
+- must -
+- must may
+- must must_nopeermatch
+- must must
+
+none none -
+none none may
+none none must_nopeermatch
+none none must
+none may -
+none may may
+none may must_nopeermatch
+none may must
+none must_nopeermatch -
+none must_nopeermatch may
+none must_nopeermatch must_nopeermatch
+none must_nopeermatch must
+none must -
+none must may
+none must must_nopeermatch
+none must must
+
+may may -
+may may may
+may may must_nopeermatch
+may may must
+may must_nopeermatch -
+may must_nopeermatch may
+may must_nopeermatch must_nopeermatch
+may must_nopeermatch must
+may must -
+may must may
+may must must_nopeermatch
+may must must
+
+must_nopeermatch must_nopeermatch -
+must_nopeermatch must_nopeermatch may
+must_nopeermatch must_nopeermatch must_nopeermatch
+must_nopeermatch must_nopeermatch must
+must_nopeermatch must -
+must_nopeermatch must may
+must_nopeermatch must must_nopeermatch
+must_nopeermatch must must
+
+must must -
+must must may
+must must must_nopeermatch
+must must must
diff --git a/src/smtp/tls_policy.ref b/src/smtp/tls_policy.ref
new file mode 100644
index 0000000..5b9f187
--- /dev/null
+++ b/src/smtp/tls_policy.ref
@@ -0,0 +1,65 @@
+host dest global result
+- - - none
+- - may may
+- - must_nopeermatch must_nopeermatch
+- - must must
+- none - none
+- none may none
+- none must_nopeermatch none
+- none must none
+- may - may
+- may may may
+- may must_nopeermatch must_nopeermatch
+- may must must
+- must_nopeermatch - must_nopeermatch
+- must_nopeermatch may must_nopeermatch
+- must_nopeermatch must_nopeermatch must_nopeermatch
+- must_nopeermatch must must_nopeermatch
+- must - must
+- must may must
+- must must_nopeermatch must
+- must must must
+
+none none - none
+none none may none
+none none must_nopeermatch none
+none none must none
+none may - none
+none may may none
+none may must_nopeermatch none
+none may must none
+none must_nopeermatch - must_nopeermatch
+none must_nopeermatch may must_nopeermatch
+none must_nopeermatch must_nopeermatch must_nopeermatch
+none must_nopeermatch must must_nopeermatch
+none must - must
+none must may must
+none must must_nopeermatch must
+none must must must
+
+may may - may
+may may may may
+may may must_nopeermatch must_nopeermatch
+may may must must
+may must_nopeermatch - must_nopeermatch
+may must_nopeermatch may must_nopeermatch
+may must_nopeermatch must_nopeermatch must_nopeermatch
+may must_nopeermatch must must_nopeermatch
+may must - must
+may must may must
+may must must_nopeermatch must
+may must must must
+
+must_nopeermatch must_nopeermatch - must_nopeermatch
+must_nopeermatch must_nopeermatch may must_nopeermatch
+must_nopeermatch must_nopeermatch must_nopeermatch must_nopeermatch
+must_nopeermatch must_nopeermatch must must_nopeermatch
+must_nopeermatch must - must
+must_nopeermatch must may must
+must_nopeermatch must must_nopeermatch must
+must_nopeermatch must must must
+
+must must - must
+must must may must
+must must must_nopeermatch must
+must must must must