summaryrefslogtreecommitdiffstats
path: root/src/smtp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:06:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:06:34 +0000
commit5e61585d76ae77fd5e9e96ebabb57afa4d74880d (patch)
tree2b467823aaeebc7ef8bc9e3cabe8074eaef1666d /src/smtp
parentInitial commit. (diff)
downloadpostfix-5e61585d76ae77fd5e9e96ebabb57afa4d74880d.tar.xz
postfix-5e61585d76ae77fd5e9e96ebabb57afa4d74880d.zip
Adding upstream version 3.5.24.upstream/3.5.24upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
l---------src/smtp/.indent.pro1
-rw-r--r--src/smtp/.printfck25
-rw-r--r--src/smtp/Makefile.in871
-rw-r--r--src/smtp/lmtp_params.c130
-rw-r--r--src/smtp/smtp-only4
-rw-r--r--src/smtp/smtp.c1605
-rw-r--r--src/smtp/smtp.h754
-rw-r--r--src/smtp/smtp_addr.c693
-rw-r--r--src/smtp/smtp_addr.h31
-rw-r--r--src/smtp/smtp_chat.c496
-rw-r--r--src/smtp/smtp_connect.c1205
-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_misc.c100
-rw-r--r--src/smtp/smtp_params.c134
-rw-r--r--src/smtp/smtp_proto.c2502
-rw-r--r--src/smtp/smtp_rcpt.c226
-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.c476
-rw-r--r--src/smtp/smtp_unalias.c142
-rw-r--r--src/smtp/tls_policy.in64
-rw-r--r--src/smtp/tls_policy.ref65
l---------src/smtpd/.indent.pro1
-rw-r--r--src/smtpd/.printfck25
-rw-r--r--src/smtpd/Makefile.in674
-rw-r--r--src/smtpd/smtpd.c6695
-rw-r--r--src/smtpd/smtpd.h441
-rw-r--r--src/smtpd/smtpd_acl.in120
-rw-r--r--src/smtpd/smtpd_acl.ref187
-rw-r--r--src/smtpd/smtpd_addr_valid.in35
-rw-r--r--src/smtpd/smtpd_addr_valid.ref57
-rw-r--r--src/smtpd/smtpd_chat.c345
-rw-r--r--src/smtpd/smtpd_chat.h45
-rw-r--r--src/smtpd/smtpd_check.c6392
-rw-r--r--src/smtpd/smtpd_check.h44
-rw-r--r--src/smtpd/smtpd_check.in182
-rw-r--r--src/smtpd/smtpd_check.in2116
-rw-r--r--src/smtpd/smtpd_check.in327
-rw-r--r--src/smtpd/smtpd_check.in419
-rw-r--r--src/smtpd/smtpd_check.ref398
-rw-r--r--src/smtpd/smtpd_check.ref2236
-rw-r--r--src/smtpd/smtpd_check.ref438
-rw-r--r--src/smtpd/smtpd_check_access91
-rw-r--r--src/smtpd/smtpd_check_backup.in20
-rw-r--r--src/smtpd/smtpd_check_backup.ref34
-rw-r--r--src/smtpd/smtpd_check_dsn.in60
-rw-r--r--src/smtpd/smtpd_check_dsn.ref163
-rw-r--r--src/smtpd/smtpd_dns_filter.in83
-rw-r--r--src/smtpd/smtpd_dns_filter.ref163
-rw-r--r--src/smtpd/smtpd_dnswl.in60
-rw-r--r--src/smtpd/smtpd_dnswl.ref94
-rw-r--r--src/smtpd/smtpd_dsn_fix.c149
-rw-r--r--src/smtpd/smtpd_dsn_fix.h44
-rw-r--r--src/smtpd/smtpd_error.in81
-rw-r--r--src/smtpd/smtpd_error.ref135
-rw-r--r--src/smtpd/smtpd_exp.in62
-rw-r--r--src/smtpd/smtpd_exp.ref111
-rw-r--r--src/smtpd/smtpd_expand.c247
-rw-r--r--src/smtpd/smtpd_expand.h40
-rw-r--r--src/smtpd/smtpd_haproxy.c135
-rw-r--r--src/smtpd/smtpd_milter.c229
-rw-r--r--src/smtpd/smtpd_milter.h27
-rw-r--r--src/smtpd/smtpd_nullmx.in58
-rw-r--r--src/smtpd/smtpd_nullmx.ref100
-rw-r--r--src/smtpd/smtpd_peer.c659
-rw-r--r--src/smtpd/smtpd_proxy.c1171
-rw-r--r--src/smtpd/smtpd_proxy.h66
-rw-r--r--src/smtpd/smtpd_resolve.c190
-rw-r--r--src/smtpd/smtpd_resolve.h43
-rw-r--r--src/smtpd/smtpd_sasl_glue.c377
-rw-r--r--src/smtpd/smtpd_sasl_glue.h41
-rw-r--r--src/smtpd/smtpd_sasl_proto.c274
-rw-r--r--src/smtpd/smtpd_sasl_proto.h37
-rw-r--r--src/smtpd/smtpd_server.in56
-rw-r--r--src/smtpd/smtpd_server.ref118
-rw-r--r--src/smtpd/smtpd_state.c248
-rw-r--r--src/smtpd/smtpd_token.c233
-rw-r--r--src/smtpd/smtpd_token.h40
-rw-r--r--src/smtpd/smtpd_token.in12
-rw-r--r--src/smtpd/smtpd_token.ref84
-rw-r--r--src/smtpd/smtpd_xforward.c114
l---------src/smtpstone/.indent.pro1
-rw-r--r--src/smtpstone/.printfck25
-rw-r--r--src/smtpstone/Makefile.in172
-rw-r--r--src/smtpstone/hashed-deferred37
-rw-r--r--src/smtpstone/hashed-incoming48
-rw-r--r--src/smtpstone/mx-deliver20
-rw-r--r--src/smtpstone/mx-explode33
-rw-r--r--src/smtpstone/mx-relay20
-rw-r--r--src/smtpstone/performance28
-rw-r--r--src/smtpstone/qmail-deliver20
-rw-r--r--src/smtpstone/qmail-explode20
-rw-r--r--src/smtpstone/qmail-relay20
-rw-r--r--src/smtpstone/qmqp-sink.c325
-rw-r--r--src/smtpstone/qmqp-source.c673
-rw-r--r--src/smtpstone/smtp-sink.c1643
-rw-r--r--src/smtpstone/smtp-source.c1188
-rw-r--r--src/smtpstone/throughput28
-rw-r--r--src/smtpstone/vmail-local43
-rw-r--r--src/smtpstone/vmail-relay57
111 files changed, 39421 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..140192f
--- /dev/null
+++ b/src/smtp/Makefile.in
@@ -0,0 +1,871 @@
+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 smtp_misc.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 smtp_misc.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/byte_mask.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_misc.o: ../../include/argv.h
+smtp_misc.o: ../../include/attr.h
+smtp_misc.o: ../../include/check_arg.h
+smtp_misc.o: ../../include/deliver_request.h
+smtp_misc.o: ../../include/dict.h
+smtp_misc.o: ../../include/dns.h
+smtp_misc.o: ../../include/dsn.h
+smtp_misc.o: ../../include/dsn_buf.h
+smtp_misc.o: ../../include/ext_prop.h
+smtp_misc.o: ../../include/header_body_checks.h
+smtp_misc.o: ../../include/header_opts.h
+smtp_misc.o: ../../include/htable.h
+smtp_misc.o: ../../include/mail_params.h
+smtp_misc.o: ../../include/maps.h
+smtp_misc.o: ../../include/match_list.h
+smtp_misc.o: ../../include/mime_state.h
+smtp_misc.o: ../../include/msg_stats.h
+smtp_misc.o: ../../include/myaddrinfo.h
+smtp_misc.o: ../../include/myflock.h
+smtp_misc.o: ../../include/mymalloc.h
+smtp_misc.o: ../../include/name_code.h
+smtp_misc.o: ../../include/name_mask.h
+smtp_misc.o: ../../include/nvtable.h
+smtp_misc.o: ../../include/quote_821_local.h
+smtp_misc.o: ../../include/quote_822_local.h
+smtp_misc.o: ../../include/quote_flags.h
+smtp_misc.o: ../../include/recipient_list.h
+smtp_misc.o: ../../include/resolve_clnt.h
+smtp_misc.o: ../../include/scache.h
+smtp_misc.o: ../../include/sock_addr.h
+smtp_misc.o: ../../include/string_list.h
+smtp_misc.o: ../../include/sys_defs.h
+smtp_misc.o: ../../include/tls.h
+smtp_misc.o: ../../include/tls_proxy.h
+smtp_misc.o: ../../include/tok822.h
+smtp_misc.o: ../../include/vbuf.h
+smtp_misc.o: ../../include/vstream.h
+smtp_misc.o: ../../include/vstring.h
+smtp_misc.o: smtp.h
+smtp_misc.o: smtp_misc.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_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..9a28cc2
--- /dev/null
+++ b/src/smtp/smtp.c
@@ -0,0 +1,1605 @@
+/*++
+/* NAME
+/* smtp 8
+/* SUMMARY
+/* Postfix SMTP+LMTP client
+/* SYNOPSIS
+/* \fBsmtp\fR [generic Postfix daemon options] [flags=DORX]
+/* 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
+/* The Postfix SMTP+LMTP client supports multiple destinations
+/* separated by comma or whitespace (Postfix 3.5 and later).
+/* 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
+/* The Postfix SMTP+LMTP client supports multiple destinations
+/* separated by comma or whitespace (Postfix 3.5 and later).
+/* 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].
+/* SINGLE-RECIPIENT DELIVERY
+/* .ad
+/* .fi
+/* By default, the Postfix SMTP+LMTP client delivers mail to
+/* multiple recipients per delivery request. This is undesirable
+/* when prepending a \fBDelivered-to:\fR or \fBX-Original-To:\fR
+/* message header. To prevent Postfix from sending multiple
+/* recipients per delivery request, specify
+/* .sp
+/* .nf
+/* \fItransport\fB_destination_recipient_limit = 1\fR
+/* .fi
+/*
+/* in the Postfix \fBmain.cf\fR file, where \fItransport\fR
+/* is the name in the first column of the Postfix \fBmaster.cf\fR
+/* entry for this mail delivery service.
+/* COMMAND ATTRIBUTE SYNTAX
+/* .ad
+/* .fi
+/* .IP "\fBflags=DORX\fR (optional)"
+/* Optional message processing flags.
+/* .RS
+/* .IP \fBD\fR
+/* Prepend a "\fBDelivered-To: \fIrecipient\fR" message header
+/* with the envelope recipient address. Note: for this to work,
+/* the \fItransport\fB_destination_recipient_limit\fR must be
+/* 1 (see SINGLE-RECIPIENT DELIVERY above for details).
+/* .sp
+/* The \fBD\fR flag also enforces loop detection: if a message
+/* already contains a \fBDelivered-To:\fR header with the same
+/* recipient address, then the message is returned as
+/* undeliverable. The address comparison is case insensitive.
+/* .sp
+/* This feature is available as of Postfix 3.5.
+/* .IP \fBO\fR
+/* Prepend an "\fBX-Original-To: \fIrecipient\fR" message
+/* header with the recipient address as given to Postfix. Note:
+/* for this to work, the
+/* \fItransport\fB_destination_recipient_limit\fR must be 1
+/* (see SINGLE-RECIPIENT DELIVERY above for details).
+/* .sp
+/* This feature is available as of Postfix 3.5.
+/* .IP \fBR\fR
+/* Prepend a "\fBReturn-Path: <\fIsender\fB>\fR" message header
+/* with the envelope sender address.
+/* .sp
+/* This feature is available as of Postfix 3.5.
+/* .IP \fBX\fR
+/* Indicates that the delivery is final. This flag affects
+/* the status reported in "success" DSN (delivery status
+/* notification) messages, and changes it from "relayed" into
+/* "delivered".
+/* .sp
+/* This feature is available as of Postfix 3.5.
+/* .RE
+/* SECURITY
+/* 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 reuse 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.5 and later:
+/* .IP "\fBinfo_log_address_format (external)\fR"
+/* The email address form that will be used in non-debug logging
+/* (info, warning, etc.).
+/* .PP
+/* Available in Postfix 3.5.9 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
+/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+/* .IP "\fBtls_fast_shutdown_enable (yes)\fR"
+/* A workaround for implementations that hang Postfix while shutting
+/* down a TLS session, until Postfix times out.
+/* .PP
+/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+/* .IP "\fBtls_config_file (default)\fR"
+/* Optional configuration file with baseline OpenSSL settings.
+/* .IP "\fBtls_config_name (empty)\fR"
+/* The application name passed by Postfix to OpenSSL library
+/* initialization functions.
+/* 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>
+#include <byte_mask.h>
+
+/* Global library. */
+
+#include <deliver_request.h>
+#include <delivered_hdr.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 */
+SMTP_CLI_ATTR smtp_cli_attr; /* parsed command-line */
+
+#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;
+
+/* get_cli_attr - get command-line attributes */
+
+static void get_cli_attr(SMTP_CLI_ATTR *attr, char **argv)
+{
+ const char myname[] = "get_cli_attr";
+ const char *last_flags = "flags="; /* i.e. empty */
+ static const BYTE_MASK flags_map[] = {
+ 'D', SMTP_CLI_FLAG_DELIVERED_TO,
+ 'O', SMTP_CLI_FLAG_ORIG_RCPT,
+ 'R', SMTP_CLI_FLAG_RETURN_PATH,
+ 'X', SMTP_CLI_FLAG_FINAL_DELIVERY,
+ 0,
+ };
+
+ /*
+ * Initialize.
+ */
+ attr->flags = 0;
+
+ /*
+ * Iterate over the command-line attribute list. Errors are fatal.
+ */
+ for ( /* void */ ; *argv != 0; argv++) {
+
+ /*
+ * flags=stuff. Errors are fatal.
+ */
+ if (strncasecmp("flags=", *argv, sizeof("flags=") - 1) == 0) {
+ last_flags = *argv;
+ if (msg_verbose)
+ msg_info("%s: %s", myname, last_flags);
+ attr->flags = byte_mask(*argv, flags_map,
+ *argv + sizeof("flags=") - 1);
+ }
+
+ /*
+ * Bad.
+ */
+ else
+ msg_fatal("unknown attribute name: %s", *argv);
+ }
+
+ /*
+ * Backwards compatibility, redundancy, and obsolescence.
+ */
+ if (!smtp_mode && var_lmtp_assume_final
+ && (attr->flags & SMTP_CLI_FLAG_FINAL_DELIVERY) == 0) {
+ attr->flags |= SMTP_CLI_FLAG_FINAL_DELIVERY;
+ msg_warn("%s is obsolete; instead, specify \"%sX\" in %s",
+ VAR_LMTP_ASSUME_FINAL, last_flags, MASTER_CONF_FILE);
+ }
+}
+
+/* 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);
+
+ /*
+ * D flag checks.
+ */
+ if (smtp_cli_attr.flags & SMTP_CLI_FLAG_DELIVERED_TO) {
+
+ /*
+ * The D flag cannot be specified for multi-recipient deliveries.
+ */
+ if (request->rcpt_list.len > 1) {
+ msg_warn("flag `D' requires %s_destination_recipient_limit = 1",
+ service);
+ return (reject_deliver_request(service, request, "4.3.5",
+ "mail system configuration error"));
+ }
+
+ /*
+ * The recipient cannot appear in a Delivered-To: header.
+ */
+ else {
+ DELIVERED_HDR_INFO *delivered_info = delivered_hdr_init(
+ request->fp, request->data_offset, FOLD_ADDR_ALL);
+ VSTRING *generic_rcpt = vstring_alloc(100);
+ int have_delivered_loop;
+
+ smtp_rewrite_generic_internal(generic_rcpt,
+ request->rcpt_list.info->address);
+ have_delivered_loop = delivered_hdr_find(
+ delivered_info, STR(generic_rcpt));
+ vstring_free(generic_rcpt);
+ delivered_hdr_free(delivered_info);
+ if (have_delivered_loop) {
+ return (reject_deliver_request(service, request, "5.4.6",
+ "mail forwarding loop for %s",
+ request->rcpt_list.info->address));
+ }
+ }
+ }
+
+ /*
+ * The O flag cannot be specified for multi-recipient deliveries.
+ */
+ if ((smtp_cli_attr.flags & SMTP_CLI_FLAG_ORIG_RCPT)
+ && request->rcpt_list.len > 1) {
+ msg_warn("flag `O' requires %s_destination_recipient_limit = 1",
+ service);
+ return (reject_deliver_request(service, request, "4.3.5",
+ "mail system configuration error"));
+ }
+
+ /*
+ * 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 **unused_argv)
+{
+ DELIVER_REQUEST *request;
+ int status;
+
+ /*
+ * 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 **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();
+
+ /*
+ * Look up service command-line attributes; these do not change during
+ * the process lifetime.
+ */
+ get_cli_attr(&smtp_cli_attr, argv);
+}
+
+/* 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
+ /* TODO: logging is not initialized. */
+ 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..4fea37a
--- /dev/null
+++ b/src/smtp/smtp.h
@@ -0,0 +1,754 @@
+/*++
+/* 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 TRACE_REQ_ONLY (DEL_REQ_TRACE_ONLY(state->request->flags))
+
+#define PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE \
+ (session->tls_context == 0 \
+ && state->tls->level == TLS_LEV_MAY \
+ && (TRACE_REQ_ONLY || 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 \
+ && (TRACE_REQ_ONLY || 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)
+
+ /*
+ * Parsed command-line attributes. These do not change during the process
+ * lifetime.
+ */
+typedef struct {
+ int flags; /* from flags=, see below */
+} SMTP_CLI_ATTR;
+
+#define SMTP_CLI_FLAG_DELIVERED_TO (1<<0) /* prepend Delivered-To: */
+#define SMTP_CLI_FLAG_ORIG_RCPT (1<<1) /* prepend X-Original-To: */
+#define SMTP_CLI_FLAG_RETURN_PATH (1<<2) /* prepend Return-Path: */
+#define SMTP_CLI_FLAG_FINAL_DELIVERY (1<<3) /* final, not relay */
+
+#define SMTP_CLI_MASK_ADD_HEADERS (SMTP_CLI_FLAG_DELIVERED_TO | \
+ SMTP_CLI_FLAG_ORIG_RCPT | SMTP_CLI_FLAG_RETURN_PATH)
+
+extern SMTP_CLI_ATTR smtp_cli_attr;
+
+ /*
+ * smtp_misc.c.
+ */
+extern void smtp_rewrite_generic_internal(VSTRING *, const char *);
+extern void smtp_quote_822_address_flags(VSTRING *, const char *, int);
+extern void smtp_quote_821_address(VSTRING *, const char *);
+
+/* 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..bff5986
--- /dev/null
+++ b/src/smtp/smtp_chat.c
@@ -0,0 +1,496 @@
+/*++
+/* 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
+/*
+/* 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 <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.
+ *
+ * Fix 20190621: don't cache an SMTP session after an SMTP protocol
+ * error. The protocol may be in a bad state. Disable caching here so
+ * that the protocol engine will send QUIT.
+ */
+ session->error_mask |= MAIL_ERROR_PROTOCOL;
+ DONT_CACHE_THIS_SESSION;
+ 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..4d48883
--- /dev/null
+++ b/src/smtp/smtp_connect.c
@@ -0,0 +1,1205 @@
+/*++
+/* 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;
+ }
+
+ /*
+ * Do a null destination sanity check in case the primary destination is
+ * a list that consists of only separators.
+ */
+ sites = argv_split(nexthop, CHARS_COMMA_SP);
+ 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_misc.c b/src/smtp/smtp_misc.c
new file mode 100644
index 0000000..43a176f
--- /dev/null
+++ b/src/smtp/smtp_misc.c
@@ -0,0 +1,100 @@
+/*++
+/* NAME
+/* smtp_misc 3
+/* SUMMARY
+/* SMTP client address rewriting
+/* SYNOPSIS
+/* #include <smtp_addr.h>
+/*
+/* void smtp_rewrite_generic_internal(
+/* VSTRING *dst,
+/* const char *src);
+/*
+/* void smtp_quote_822_address_flags(
+/* VSTRING *dst,
+/* const char *src,
+/* int flags);
+/*
+/* void smtp_quote_821_address(
+/* VSTRING *dst,
+/* const char *src);
+/* DESCRIPTION
+/* smtp_rewrite_generic_internal() rewrites a non-empty address
+/* if generic mapping is enabled, otherwise copies it literally.
+/*
+/* smtp_quote_822_address_flags() is a wrapper around
+/* quote_822_local_flags(), except for the empty address which
+/* is copied literally.
+/*
+/* smtp_quote_821_address() is a wrapper around quote_821_local(),
+/* except for the empty address or with "smtp_quote_rfc821_envelope
+/* = no"; in those cases the address is copied literally.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <ext_prop.h>
+#include <mail_params.h>
+#include <quote_821_local.h>
+#include <quote_822_local.h>
+
+ /*
+ * Application-specific.
+ */
+#include <smtp.h>
+
+/* smtp_rewrite_generic_internal - generic non-empty address rewriting */
+
+void smtp_rewrite_generic_internal(VSTRING *dst, const char *src)
+{
+ vstring_strcpy(dst, src);
+ if (*src && smtp_generic_maps)
+ smtp_map11_internal(dst, smtp_generic_maps,
+ smtp_ext_prop_mask & EXT_PROP_GENERIC);
+}
+
+/* smtp_quote_822_address_flags - quote non-empty header address */
+
+void smtp_quote_822_address_flags(VSTRING *dst, const char *src, int flags)
+{
+ if (*src) {
+ quote_822_local_flags(dst, src, flags);
+ } else if (flags & QUOTE_FLAG_APPEND) {
+ vstring_strcat(dst, src);
+ } else {
+ vstring_strcpy(dst, src);
+ }
+}
+
+/* smtp_quote_821_address - quote non-empty envelope address */
+
+void smtp_quote_821_address(VSTRING *dst, const char *src)
+{
+ if (*src && var_smtp_quote_821_env) {
+ quote_821_local(dst, src);
+ } else {
+ vstring_strcpy(dst, src);
+ }
+}
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..a47861e
--- /dev/null
+++ b/src/smtp/smtp_proto.c
@@ -0,0 +1,2502 @@
+/*++
+/* 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_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_out_raw_or_mime - output buffer, raw output or MIME-aware */
+
+static int smtp_out_raw_or_mime(SMTP_STATE *state, int rec_type, VSTRING *buf)
+{
+ SMTP_SESSION *session = state->session;
+ int mime_errs;
+
+ if (session->mime_state == 0) {
+ smtp_text_out((void *) state, rec_type, vstring_str(buf),
+ VSTRING_LEN(buf), (off_t) 0);
+ } else {
+ mime_errs =
+ mime_state_update(session->mime_state, rec_type,
+ vstring_str(buf), VSTRING_LEN(buf));
+ if (mime_errs) {
+ smtp_mime_fail(state, mime_errs);
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+/* smtp_out_add_header - format address header, uses session->scratch* */
+
+static int smtp_out_add_header(SMTP_STATE *state, const char *label,
+ const char *lt, const char *addr,
+ const char *gt)
+{
+ SMTP_SESSION *session = state->session;
+
+ smtp_rewrite_generic_internal(session->scratch2, addr);
+ vstring_sprintf(session->scratch, "%s: %s", label, lt);
+ smtp_quote_822_address_flags(session->scratch,
+ vstring_str(session->scratch2),
+ QUOTE_FLAG_DEFAULT | QUOTE_FLAG_APPEND);
+ vstring_strcat(session->scratch, gt);
+ return (smtp_out_raw_or_mime(state, REC_TYPE_NORM, session->scratch));
+}
+
+/* smtp_out_add_headers - output additional headers, uses session->scratch* */
+
+static int smtp_out_add_headers(SMTP_STATE *state)
+{
+ if (smtp_cli_attr.flags & SMTP_CLI_FLAG_DELIVERED_TO)
+ if (smtp_out_add_header(state, "Delivered-To", "",
+ state->request->rcpt_list.info->address, "") < 0)
+ return (-1);
+ if (smtp_cli_attr.flags & SMTP_CLI_FLAG_ORIG_RCPT)
+ if (smtp_out_add_header(state, "X-Original-To", "",
+ state->request->rcpt_list.info->orig_addr, "") < 0)
+ return (-1);
+ if (smtp_cli_attr.flags & SMTP_CLI_FLAG_RETURN_PATH)
+ if (smtp_out_add_header(state, "Return-Path", "<",
+ state->request->sender, ">") < 0)
+ return (-1);
+ return (0);
+}
+
+/* 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;
+
+ /* 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);
+ smtp_rewrite_generic_internal(session->scratch2, request->sender);
+ smtp_quote_821_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;
+ smtp_rewrite_generic_internal(session->scratch2, rcpt->address);
+ smtp_quote_821_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;
+
+ if ((smtp_cli_attr.flags & SMTP_CLI_MASK_ADD_HEADERS) != 0
+ && smtp_out_add_headers(state) < 0)
+ RETURN(0);
+
+ while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) {
+ if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
+ break;
+ if (smtp_out_raw_or_mime(state, rec_type,
+ session->scratch) < 0)
+ 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..6608ea8
--- /dev/null
+++ b/src/smtp/smtp_rcpt.c
@@ -0,0 +1,226 @@
+/*++
+/* 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, 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
+ && (smtp_cli_attr.flags & SMTP_CLI_FLAG_FINAL_DELIVERY) != 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..60880df
--- /dev/null
+++ b/src/smtp/smtp_trouble.c
@@ -0,0 +1,476 @@
+/*++
+/* 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.
+ *
+ * Fix 20190621: don't cache an SMTP session after an SMTP protocol error.
+ * The protocol may be in a bad state. Disable caching here so that the
+ * protocol engine will send QUIT.
+ */
+ if (code < 400 || code > 599
+ || code == 555 /* RFC 1869, section 6.1. */
+ || (code >= 500 && code < 510)) {
+ session->error_mask |= MAIL_ERROR_PROTOCOL;
+ DONT_CACHE_THIS_SESSION;
+ }
+}
+
+/* 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.
+ *
+ * Fix 20190621: don't cache an SMTP session after an SMTP protocol error.
+ * The protocol may be in a bad state. Disable caching here so that the
+ * protocol engine will send QUIT.
+ */
+ VSTRING_RESET(why->reason);
+ if (mta_name && status && status[0] != '4' && status[0] != '5') {
+ SMTP_SESSION *session = state->session;
+
+ session->error_mask |= MAIL_ERROR_PROTOCOL;
+ DONT_CACHE_THIS_SESSION;
+ 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
diff --git a/src/smtpd/.indent.pro b/src/smtpd/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/smtpd/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/smtpd/.printfck b/src/smtpd/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/smtpd/.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/smtpd/Makefile.in b/src/smtpd/Makefile.in
new file mode 100644
index 0000000..009f5bd
--- /dev/null
+++ b/src/smtpd/Makefile.in
@@ -0,0 +1,674 @@
+SHELL = /bin/sh
+SRCS = smtpd.c smtpd_token.c smtpd_check.c smtpd_chat.c smtpd_state.c \
+ smtpd_peer.c smtpd_sasl_proto.c smtpd_sasl_glue.c smtpd_proxy.c \
+ smtpd_xforward.c smtpd_dsn_fix.c smtpd_milter.c smtpd_resolve.c \
+ smtpd_expand.c smtpd_haproxy.c
+OBJS = smtpd.o smtpd_token.o smtpd_check.o smtpd_chat.o smtpd_state.o \
+ smtpd_peer.o smtpd_sasl_proto.o smtpd_sasl_glue.o smtpd_proxy.o \
+ smtpd_xforward.o smtpd_dsn_fix.o smtpd_milter.o smtpd_resolve.o \
+ smtpd_expand.o smtpd_haproxy.o
+HDRS = smtpd_token.h smtpd_check.h smtpd_chat.h smtpd_sasl_proto.h \
+ smtpd_sasl_glue.h smtpd_proxy.h smtpd_dsn_fix.h smtpd_milter.h \
+ smtpd_resolve.h smtpd_expand.h
+TESTSRC = smtpd_token_test.c
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG= smtpd_token smtpd_check
+PROG = smtpd
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
+ ../../lib/libxsasl.a \
+ ../../lib/libmilter.a \
+ ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../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)
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+SMTPD_CHECK_OBJ = smtpd_state.o smtpd_peer.o smtpd_xforward.o smtpd_dsn_fix.o \
+ smtpd_resolve.o smtpd_expand.o smtpd_proxy.o smtpd_haproxy.o
+
+smtpd_token: smtpd_token.c $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
+
+smtpd_check: smtpd_check.o smtpd_check.c $(SMTPD_CHECK_OBJ) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ smtpd_check.c $(SMTPD_CHECK_OBJ) \
+ $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+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 *.db *.out *.tmp
+ rm -rf printfck
+
+tidy: clean
+
+broken-tests: smtpd_check_test smtpd_check_test2
+
+tests: smtpd_acl_test smtpd_addr_valid_test smtpd_exp_test \
+ smtpd_token_test smtpd_check_test4 smtpd_check_dsn_test \
+ smtpd_check_backup_test smtpd_dnswl_test smtpd_error_test \
+ smtpd_server_test smtpd_nullmx_test smtpd_dns_filter_test
+
+root_tests:
+
+# This requires that the DNS server can query porcupine.org.
+
+smtpd_check_test: smtpd_check smtpd_check.in smtpd_check.ref smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check.in >smtpd_check.tmp 2>&1
+ diff smtpd_check.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+# This requires that the DNS server can query porcupine.org.
+
+smtpd_check_test2: smtpd_check smtpd_check.in2 smtpd_check.ref2 smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check.in2 >smtpd_check.tmp 2>&1
+ diff smtpd_check.ref2 smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+smtpd_check_test4: smtpd_check smtpd_check.in4 smtpd_check.ref4 smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check.in4 >smtpd_check.tmp 2>&1
+ diff smtpd_check.ref4 smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+smtpd_acl_test: smtpd_check smtpd_acl.in smtpd_acl.ref smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_acl.in >smtpd_check.tmp 2>&1
+ diff smtpd_acl.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+smtpd_addr_valid_test: smtpd_check smtpd_addr_valid.in smtpd_addr_valid.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_addr_valid.in >smtpd_check.tmp 2>&1
+ diff smtpd_addr_valid.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp
+
+# This requires that the DNS server can query porcupine.org.
+
+ADDRINFO_FIX = sed 's/No address associated with hostname/hostname nor servname provided, or not known/'
+
+smtpd_exp_test: smtpd_check smtpd_exp.in smtpd_exp.ref
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_exp.in >smtpd_exp.tmp 2>&1
+ diff smtpd_exp.ref smtpd_exp.tmp
+ rm -f smtpd_exp.tmp smtpd_check_access.*
+
+smtpd_server_test: smtpd_check smtpd_server.in smtpd_server.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_server.in >smtpd_server.tmp 2>&1
+ $(ADDRINFO_FIX) smtpd_server.tmp | diff smtpd_server.ref -
+ rm -f smtpd_server.tmp smtpd_check_access.*
+
+smtpd_nullmx_test: smtpd_check smtpd_nullmx.in smtpd_nullmx.ref
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_nullmx.in >smtpd_nullmx.tmp 2>&1
+ $(ADDRINFO_FIX) smtpd_nullmx.tmp | diff smtpd_nullmx.ref -
+ rm -f smtpd_nullmx.tmp smtpd_check_access.*
+
+smtpd_dns_filter_test: smtpd_check smtpd_dns_filter.in smtpd_dns_filter.ref \
+ ../dns/no-mx.reg ../dns/no-a.reg ../dns/error.reg
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_dns_filter.in 2>&1 | \
+ sed 's/\. [0-9]* IN/. TTL IN/' >smtpd_dns_filter.tmp
+ diff smtpd_dns_filter.ref smtpd_dns_filter.tmp
+ rm -f smtpd_dns_filter.tmp
+
+smtpd_check_dsn_test: smtpd_check smtpd_check_dsn.in smtpd_check_dsn.ref smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check_dsn.in >smtpd_check.tmp 2>&1
+ diff smtpd_check_dsn.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp smtpd_check_access.*
+
+# This requires that 168,100.189.7 is a local or virtual interface.
+
+smtpd_check_backup_test: smtpd_check smtpd_check_backup.in smtpd_check_backup.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check_backup.in >smtpd_check.tmp 2>&1
+ diff smtpd_check_backup.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp
+
+smtpd_token_test: smtpd_token smtpd_token.in smtpd_token.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_token <smtpd_token.in >smtpd_token.tmp 2>&1
+ diff smtpd_token.ref smtpd_token.tmp
+ rm -f smtpd_token.tmp
+
+# This requires that the DNS server can query porcupine.org and rfc-ignorant.org
+
+smtpd_dnswl_test: smtpd_check smtpd_dnswl.in smtpd_dnswl.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_dnswl.in >smtpd_dnswl.tmp 2>&1
+ diff smtpd_dnswl.ref smtpd_dnswl.tmp
+ rm -f smtpd_dnswl.tmp
+
+smtpd_error_test: smtpd_check smtpd_error.in smtpd_error.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_error.in >smtpd_check.tmp 2>&1
+ diff smtpd_error.ref smtpd_check.tmp
+ rm -f smtpd_check.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'
+smtpd.o: ../../include/anvil_clnt.h
+smtpd.o: ../../include/argv.h
+smtpd.o: ../../include/attr.h
+smtpd.o: ../../include/attr_clnt.h
+smtpd.o: ../../include/check_arg.h
+smtpd.o: ../../include/cleanup_user.h
+smtpd.o: ../../include/debug_peer.h
+smtpd.o: ../../include/dict.h
+smtpd.o: ../../include/dns.h
+smtpd.o: ../../include/dsn_mask.h
+smtpd.o: ../../include/ehlo_mask.h
+smtpd.o: ../../include/events.h
+smtpd.o: ../../include/flush_clnt.h
+smtpd.o: ../../include/htable.h
+smtpd.o: ../../include/inet_proto.h
+smtpd.o: ../../include/info_log_addr_form.h
+smtpd.o: ../../include/input_transp.h
+smtpd.o: ../../include/iostuff.h
+smtpd.o: ../../include/is_header.h
+smtpd.o: ../../include/lex_822.h
+smtpd.o: ../../include/mac_expand.h
+smtpd.o: ../../include/mac_parse.h
+smtpd.o: ../../include/mail_conf.h
+smtpd.o: ../../include/mail_date.h
+smtpd.o: ../../include/mail_error.h
+smtpd.o: ../../include/mail_params.h
+smtpd.o: ../../include/mail_proto.h
+smtpd.o: ../../include/mail_queue.h
+smtpd.o: ../../include/mail_server.h
+smtpd.o: ../../include/mail_stream.h
+smtpd.o: ../../include/mail_version.h
+smtpd.o: ../../include/maps.h
+smtpd.o: ../../include/match_list.h
+smtpd.o: ../../include/match_parent_style.h
+smtpd.o: ../../include/milter.h
+smtpd.o: ../../include/msg.h
+smtpd.o: ../../include/myaddrinfo.h
+smtpd.o: ../../include/myflock.h
+smtpd.o: ../../include/mymalloc.h
+smtpd.o: ../../include/namadr_list.h
+smtpd.o: ../../include/name_code.h
+smtpd.o: ../../include/name_mask.h
+smtpd.o: ../../include/normalize_mailhost_addr.h
+smtpd.o: ../../include/nvtable.h
+smtpd.o: ../../include/off_cvt.h
+smtpd.o: ../../include/quote_822_local.h
+smtpd.o: ../../include/quote_flags.h
+smtpd.o: ../../include/rec_type.h
+smtpd.o: ../../include/recipient_list.h
+smtpd.o: ../../include/record.h
+smtpd.o: ../../include/resolve_clnt.h
+smtpd.o: ../../include/smtp_stream.h
+smtpd.o: ../../include/smtputf8.h
+smtpd.o: ../../include/sock_addr.h
+smtpd.o: ../../include/split_at.h
+smtpd.o: ../../include/string_list.h
+smtpd.o: ../../include/stringops.h
+smtpd.o: ../../include/sys_defs.h
+smtpd.o: ../../include/tls.h
+smtpd.o: ../../include/tls_proxy.h
+smtpd.o: ../../include/tok822.h
+smtpd.o: ../../include/uxtext.h
+smtpd.o: ../../include/valid_hostname.h
+smtpd.o: ../../include/valid_mailhost_addr.h
+smtpd.o: ../../include/vbuf.h
+smtpd.o: ../../include/verify_sender_addr.h
+smtpd.o: ../../include/verp_sender.h
+smtpd.o: ../../include/vstream.h
+smtpd.o: ../../include/vstring.h
+smtpd.o: ../../include/vstring_vstream.h
+smtpd.o: ../../include/watchdog.h
+smtpd.o: ../../include/xtext.h
+smtpd.o: smtpd.c
+smtpd.o: smtpd.h
+smtpd.o: smtpd_chat.h
+smtpd.o: smtpd_check.h
+smtpd.o: smtpd_expand.h
+smtpd.o: smtpd_milter.h
+smtpd.o: smtpd_proxy.h
+smtpd.o: smtpd_sasl_glue.h
+smtpd.o: smtpd_sasl_proto.h
+smtpd.o: smtpd_token.h
+smtpd_chat.o: ../../include/argv.h
+smtpd_chat.o: ../../include/attr.h
+smtpd_chat.o: ../../include/check_arg.h
+smtpd_chat.o: ../../include/cleanup_user.h
+smtpd_chat.o: ../../include/dict.h
+smtpd_chat.o: ../../include/dns.h
+smtpd_chat.o: ../../include/htable.h
+smtpd_chat.o: ../../include/int_filt.h
+smtpd_chat.o: ../../include/iostuff.h
+smtpd_chat.o: ../../include/line_wrap.h
+smtpd_chat.o: ../../include/mac_expand.h
+smtpd_chat.o: ../../include/mac_parse.h
+smtpd_chat.o: ../../include/mail_addr.h
+smtpd_chat.o: ../../include/mail_error.h
+smtpd_chat.o: ../../include/mail_params.h
+smtpd_chat.o: ../../include/mail_proto.h
+smtpd_chat.o: ../../include/mail_stream.h
+smtpd_chat.o: ../../include/maps.h
+smtpd_chat.o: ../../include/milter.h
+smtpd_chat.o: ../../include/msg.h
+smtpd_chat.o: ../../include/myaddrinfo.h
+smtpd_chat.o: ../../include/myflock.h
+smtpd_chat.o: ../../include/mymalloc.h
+smtpd_chat.o: ../../include/name_code.h
+smtpd_chat.o: ../../include/name_mask.h
+smtpd_chat.o: ../../include/nvtable.h
+smtpd_chat.o: ../../include/post_mail.h
+smtpd_chat.o: ../../include/rec_type.h
+smtpd_chat.o: ../../include/record.h
+smtpd_chat.o: ../../include/smtp_reply_footer.h
+smtpd_chat.o: ../../include/smtp_stream.h
+smtpd_chat.o: ../../include/smtputf8.h
+smtpd_chat.o: ../../include/sock_addr.h
+smtpd_chat.o: ../../include/stringops.h
+smtpd_chat.o: ../../include/sys_defs.h
+smtpd_chat.o: ../../include/tls.h
+smtpd_chat.o: ../../include/vbuf.h
+smtpd_chat.o: ../../include/vstream.h
+smtpd_chat.o: ../../include/vstring.h
+smtpd_chat.o: smtpd.h
+smtpd_chat.o: smtpd_chat.c
+smtpd_chat.o: smtpd_chat.h
+smtpd_chat.o: smtpd_expand.h
+smtpd_check.o: ../../include/argv.h
+smtpd_check.o: ../../include/attr.h
+smtpd_check.o: ../../include/attr_clnt.h
+smtpd_check.o: ../../include/attr_override.h
+smtpd_check.o: ../../include/check_arg.h
+smtpd_check.o: ../../include/cleanup_user.h
+smtpd_check.o: ../../include/conv_time.h
+smtpd_check.o: ../../include/ctable.h
+smtpd_check.o: ../../include/deliver_request.h
+smtpd_check.o: ../../include/dict.h
+smtpd_check.o: ../../include/dns.h
+smtpd_check.o: ../../include/domain_list.h
+smtpd_check.o: ../../include/dsn.h
+smtpd_check.o: ../../include/dsn_util.h
+smtpd_check.o: ../../include/fsspace.h
+smtpd_check.o: ../../include/htable.h
+smtpd_check.o: ../../include/inet_addr_list.h
+smtpd_check.o: ../../include/inet_proto.h
+smtpd_check.o: ../../include/info_log_addr_form.h
+smtpd_check.o: ../../include/input_transp.h
+smtpd_check.o: ../../include/iostuff.h
+smtpd_check.o: ../../include/ip_match.h
+smtpd_check.o: ../../include/is_header.h
+smtpd_check.o: ../../include/mac_expand.h
+smtpd_check.o: ../../include/mac_parse.h
+smtpd_check.o: ../../include/mail_addr.h
+smtpd_check.o: ../../include/mail_addr_find.h
+smtpd_check.o: ../../include/mail_addr_form.h
+smtpd_check.o: ../../include/mail_conf.h
+smtpd_check.o: ../../include/mail_error.h
+smtpd_check.o: ../../include/mail_params.h
+smtpd_check.o: ../../include/mail_proto.h
+smtpd_check.o: ../../include/mail_stream.h
+smtpd_check.o: ../../include/map_search.h
+smtpd_check.o: ../../include/maps.h
+smtpd_check.o: ../../include/match_list.h
+smtpd_check.o: ../../include/match_parent_style.h
+smtpd_check.o: ../../include/midna_domain.h
+smtpd_check.o: ../../include/milter.h
+smtpd_check.o: ../../include/msg.h
+smtpd_check.o: ../../include/msg_stats.h
+smtpd_check.o: ../../include/myaddrinfo.h
+smtpd_check.o: ../../include/myflock.h
+smtpd_check.o: ../../include/mymalloc.h
+smtpd_check.o: ../../include/mynetworks.h
+smtpd_check.o: ../../include/namadr_list.h
+smtpd_check.o: ../../include/name_code.h
+smtpd_check.o: ../../include/name_mask.h
+smtpd_check.o: ../../include/nvtable.h
+smtpd_check.o: ../../include/own_inet_addr.h
+smtpd_check.o: ../../include/rec_type.h
+smtpd_check.o: ../../include/recipient_list.h
+smtpd_check.o: ../../include/record.h
+smtpd_check.o: ../../include/resolve_clnt.h
+smtpd_check.o: ../../include/resolve_local.h
+smtpd_check.o: ../../include/smtp_stream.h
+smtpd_check.o: ../../include/sock_addr.h
+smtpd_check.o: ../../include/split_at.h
+smtpd_check.o: ../../include/string_list.h
+smtpd_check.o: ../../include/stringops.h
+smtpd_check.o: ../../include/strip_addr.h
+smtpd_check.o: ../../include/sys_defs.h
+smtpd_check.o: ../../include/tls.h
+smtpd_check.o: ../../include/valid_hostname.h
+smtpd_check.o: ../../include/valid_mailhost_addr.h
+smtpd_check.o: ../../include/valid_utf8_hostname.h
+smtpd_check.o: ../../include/vbuf.h
+smtpd_check.o: ../../include/verify_clnt.h
+smtpd_check.o: ../../include/vstream.h
+smtpd_check.o: ../../include/vstring.h
+smtpd_check.o: ../../include/xtext.h
+smtpd_check.o: smtpd.h
+smtpd_check.o: smtpd_check.c
+smtpd_check.o: smtpd_check.h
+smtpd_check.o: smtpd_dsn_fix.h
+smtpd_check.o: smtpd_expand.h
+smtpd_check.o: smtpd_resolve.h
+smtpd_check.o: smtpd_sasl_glue.h
+smtpd_dsn_fix.o: ../../include/msg.h
+smtpd_dsn_fix.o: ../../include/sys_defs.h
+smtpd_dsn_fix.o: smtpd_dsn_fix.c
+smtpd_dsn_fix.o: smtpd_dsn_fix.h
+smtpd_expand.o: ../../include/argv.h
+smtpd_expand.o: ../../include/attr.h
+smtpd_expand.o: ../../include/check_arg.h
+smtpd_expand.o: ../../include/dns.h
+smtpd_expand.o: ../../include/htable.h
+smtpd_expand.o: ../../include/iostuff.h
+smtpd_expand.o: ../../include/mac_expand.h
+smtpd_expand.o: ../../include/mac_parse.h
+smtpd_expand.o: ../../include/mail_params.h
+smtpd_expand.o: ../../include/mail_proto.h
+smtpd_expand.o: ../../include/mail_stream.h
+smtpd_expand.o: ../../include/milter.h
+smtpd_expand.o: ../../include/msg.h
+smtpd_expand.o: ../../include/myaddrinfo.h
+smtpd_expand.o: ../../include/mymalloc.h
+smtpd_expand.o: ../../include/name_code.h
+smtpd_expand.o: ../../include/name_mask.h
+smtpd_expand.o: ../../include/nvtable.h
+smtpd_expand.o: ../../include/sock_addr.h
+smtpd_expand.o: ../../include/stringops.h
+smtpd_expand.o: ../../include/sys_defs.h
+smtpd_expand.o: ../../include/tls.h
+smtpd_expand.o: ../../include/vbuf.h
+smtpd_expand.o: ../../include/vstream.h
+smtpd_expand.o: ../../include/vstring.h
+smtpd_expand.o: smtpd.h
+smtpd_expand.o: smtpd_expand.c
+smtpd_expand.o: smtpd_expand.h
+smtpd_haproxy.o: ../../include/argv.h
+smtpd_haproxy.o: ../../include/attr.h
+smtpd_haproxy.o: ../../include/check_arg.h
+smtpd_haproxy.o: ../../include/dns.h
+smtpd_haproxy.o: ../../include/haproxy_srvr.h
+smtpd_haproxy.o: ../../include/htable.h
+smtpd_haproxy.o: ../../include/iostuff.h
+smtpd_haproxy.o: ../../include/mail_params.h
+smtpd_haproxy.o: ../../include/mail_stream.h
+smtpd_haproxy.o: ../../include/milter.h
+smtpd_haproxy.o: ../../include/msg.h
+smtpd_haproxy.o: ../../include/myaddrinfo.h
+smtpd_haproxy.o: ../../include/mymalloc.h
+smtpd_haproxy.o: ../../include/name_code.h
+smtpd_haproxy.o: ../../include/name_mask.h
+smtpd_haproxy.o: ../../include/nvtable.h
+smtpd_haproxy.o: ../../include/smtp_stream.h
+smtpd_haproxy.o: ../../include/sock_addr.h
+smtpd_haproxy.o: ../../include/stringops.h
+smtpd_haproxy.o: ../../include/sys_defs.h
+smtpd_haproxy.o: ../../include/tls.h
+smtpd_haproxy.o: ../../include/valid_hostname.h
+smtpd_haproxy.o: ../../include/valid_mailhost_addr.h
+smtpd_haproxy.o: ../../include/vbuf.h
+smtpd_haproxy.o: ../../include/vstream.h
+smtpd_haproxy.o: ../../include/vstring.h
+smtpd_haproxy.o: smtpd.h
+smtpd_haproxy.o: smtpd_haproxy.c
+smtpd_milter.o: ../../include/argv.h
+smtpd_milter.o: ../../include/attr.h
+smtpd_milter.o: ../../include/check_arg.h
+smtpd_milter.o: ../../include/dns.h
+smtpd_milter.o: ../../include/htable.h
+smtpd_milter.o: ../../include/mail_params.h
+smtpd_milter.o: ../../include/mail_stream.h
+smtpd_milter.o: ../../include/milter.h
+smtpd_milter.o: ../../include/myaddrinfo.h
+smtpd_milter.o: ../../include/mymalloc.h
+smtpd_milter.o: ../../include/name_code.h
+smtpd_milter.o: ../../include/name_mask.h
+smtpd_milter.o: ../../include/nvtable.h
+smtpd_milter.o: ../../include/quote_821_local.h
+smtpd_milter.o: ../../include/quote_flags.h
+smtpd_milter.o: ../../include/resolve_clnt.h
+smtpd_milter.o: ../../include/sock_addr.h
+smtpd_milter.o: ../../include/split_at.h
+smtpd_milter.o: ../../include/stringops.h
+smtpd_milter.o: ../../include/sys_defs.h
+smtpd_milter.o: ../../include/tls.h
+smtpd_milter.o: ../../include/vbuf.h
+smtpd_milter.o: ../../include/vstream.h
+smtpd_milter.o: ../../include/vstring.h
+smtpd_milter.o: smtpd.h
+smtpd_milter.o: smtpd_milter.c
+smtpd_milter.o: smtpd_milter.h
+smtpd_milter.o: smtpd_resolve.h
+smtpd_milter.o: smtpd_sasl_glue.h
+smtpd_peer.o: ../../include/argv.h
+smtpd_peer.o: ../../include/attr.h
+smtpd_peer.o: ../../include/check_arg.h
+smtpd_peer.o: ../../include/dns.h
+smtpd_peer.o: ../../include/haproxy_srvr.h
+smtpd_peer.o: ../../include/htable.h
+smtpd_peer.o: ../../include/inet_proto.h
+smtpd_peer.o: ../../include/iostuff.h
+smtpd_peer.o: ../../include/mail_params.h
+smtpd_peer.o: ../../include/mail_proto.h
+smtpd_peer.o: ../../include/mail_stream.h
+smtpd_peer.o: ../../include/milter.h
+smtpd_peer.o: ../../include/msg.h
+smtpd_peer.o: ../../include/myaddrinfo.h
+smtpd_peer.o: ../../include/mymalloc.h
+smtpd_peer.o: ../../include/name_code.h
+smtpd_peer.o: ../../include/name_mask.h
+smtpd_peer.o: ../../include/nvtable.h
+smtpd_peer.o: ../../include/sock_addr.h
+smtpd_peer.o: ../../include/split_at.h
+smtpd_peer.o: ../../include/stringops.h
+smtpd_peer.o: ../../include/sys_defs.h
+smtpd_peer.o: ../../include/tls.h
+smtpd_peer.o: ../../include/valid_hostname.h
+smtpd_peer.o: ../../include/valid_mailhost_addr.h
+smtpd_peer.o: ../../include/vbuf.h
+smtpd_peer.o: ../../include/vstream.h
+smtpd_peer.o: ../../include/vstring.h
+smtpd_peer.o: smtpd.h
+smtpd_peer.o: smtpd_peer.c
+smtpd_proxy.o: ../../include/argv.h
+smtpd_proxy.o: ../../include/attr.h
+smtpd_proxy.o: ../../include/check_arg.h
+smtpd_proxy.o: ../../include/cleanup_user.h
+smtpd_proxy.o: ../../include/connect.h
+smtpd_proxy.o: ../../include/dns.h
+smtpd_proxy.o: ../../include/htable.h
+smtpd_proxy.o: ../../include/iostuff.h
+smtpd_proxy.o: ../../include/mail_error.h
+smtpd_proxy.o: ../../include/mail_params.h
+smtpd_proxy.o: ../../include/mail_proto.h
+smtpd_proxy.o: ../../include/mail_queue.h
+smtpd_proxy.o: ../../include/mail_stream.h
+smtpd_proxy.o: ../../include/milter.h
+smtpd_proxy.o: ../../include/msg.h
+smtpd_proxy.o: ../../include/myaddrinfo.h
+smtpd_proxy.o: ../../include/mymalloc.h
+smtpd_proxy.o: ../../include/name_code.h
+smtpd_proxy.o: ../../include/name_mask.h
+smtpd_proxy.o: ../../include/nvtable.h
+smtpd_proxy.o: ../../include/rec_type.h
+smtpd_proxy.o: ../../include/record.h
+smtpd_proxy.o: ../../include/smtp_stream.h
+smtpd_proxy.o: ../../include/sock_addr.h
+smtpd_proxy.o: ../../include/stringops.h
+smtpd_proxy.o: ../../include/sys_defs.h
+smtpd_proxy.o: ../../include/tls.h
+smtpd_proxy.o: ../../include/vbuf.h
+smtpd_proxy.o: ../../include/vstream.h
+smtpd_proxy.o: ../../include/vstring.h
+smtpd_proxy.o: ../../include/xtext.h
+smtpd_proxy.o: smtpd.h
+smtpd_proxy.o: smtpd_proxy.c
+smtpd_proxy.o: smtpd_proxy.h
+smtpd_resolve.o: ../../include/attr.h
+smtpd_resolve.o: ../../include/check_arg.h
+smtpd_resolve.o: ../../include/ctable.h
+smtpd_resolve.o: ../../include/htable.h
+smtpd_resolve.o: ../../include/iostuff.h
+smtpd_resolve.o: ../../include/mail_proto.h
+smtpd_resolve.o: ../../include/msg.h
+smtpd_resolve.o: ../../include/mymalloc.h
+smtpd_resolve.o: ../../include/nvtable.h
+smtpd_resolve.o: ../../include/resolve_clnt.h
+smtpd_resolve.o: ../../include/rewrite_clnt.h
+smtpd_resolve.o: ../../include/split_at.h
+smtpd_resolve.o: ../../include/stringops.h
+smtpd_resolve.o: ../../include/sys_defs.h
+smtpd_resolve.o: ../../include/vbuf.h
+smtpd_resolve.o: ../../include/vstream.h
+smtpd_resolve.o: ../../include/vstring.h
+smtpd_resolve.o: smtpd_resolve.c
+smtpd_resolve.o: smtpd_resolve.h
+smtpd_sasl_glue.o: ../../include/argv.h
+smtpd_sasl_glue.o: ../../include/attr.h
+smtpd_sasl_glue.o: ../../include/check_arg.h
+smtpd_sasl_glue.o: ../../include/dns.h
+smtpd_sasl_glue.o: ../../include/htable.h
+smtpd_sasl_glue.o: ../../include/mail_params.h
+smtpd_sasl_glue.o: ../../include/mail_stream.h
+smtpd_sasl_glue.o: ../../include/milter.h
+smtpd_sasl_glue.o: ../../include/msg.h
+smtpd_sasl_glue.o: ../../include/myaddrinfo.h
+smtpd_sasl_glue.o: ../../include/mymalloc.h
+smtpd_sasl_glue.o: ../../include/name_code.h
+smtpd_sasl_glue.o: ../../include/name_mask.h
+smtpd_sasl_glue.o: ../../include/nvtable.h
+smtpd_sasl_glue.o: ../../include/sock_addr.h
+smtpd_sasl_glue.o: ../../include/stringops.h
+smtpd_sasl_glue.o: ../../include/sys_defs.h
+smtpd_sasl_glue.o: ../../include/tls.h
+smtpd_sasl_glue.o: ../../include/vbuf.h
+smtpd_sasl_glue.o: ../../include/vstream.h
+smtpd_sasl_glue.o: ../../include/vstring.h
+smtpd_sasl_glue.o: ../../include/xsasl.h
+smtpd_sasl_glue.o: smtpd.h
+smtpd_sasl_glue.o: smtpd_chat.h
+smtpd_sasl_glue.o: smtpd_sasl_glue.c
+smtpd_sasl_glue.o: smtpd_sasl_glue.h
+smtpd_sasl_proto.o: ../../include/argv.h
+smtpd_sasl_proto.o: ../../include/attr.h
+smtpd_sasl_proto.o: ../../include/check_arg.h
+smtpd_sasl_proto.o: ../../include/dns.h
+smtpd_sasl_proto.o: ../../include/ehlo_mask.h
+smtpd_sasl_proto.o: ../../include/htable.h
+smtpd_sasl_proto.o: ../../include/iostuff.h
+smtpd_sasl_proto.o: ../../include/mail_error.h
+smtpd_sasl_proto.o: ../../include/mail_params.h
+smtpd_sasl_proto.o: ../../include/mail_proto.h
+smtpd_sasl_proto.o: ../../include/mail_stream.h
+smtpd_sasl_proto.o: ../../include/milter.h
+smtpd_sasl_proto.o: ../../include/msg.h
+smtpd_sasl_proto.o: ../../include/myaddrinfo.h
+smtpd_sasl_proto.o: ../../include/mymalloc.h
+smtpd_sasl_proto.o: ../../include/name_code.h
+smtpd_sasl_proto.o: ../../include/name_mask.h
+smtpd_sasl_proto.o: ../../include/nvtable.h
+smtpd_sasl_proto.o: ../../include/sock_addr.h
+smtpd_sasl_proto.o: ../../include/stringops.h
+smtpd_sasl_proto.o: ../../include/sys_defs.h
+smtpd_sasl_proto.o: ../../include/tls.h
+smtpd_sasl_proto.o: ../../include/vbuf.h
+smtpd_sasl_proto.o: ../../include/vstream.h
+smtpd_sasl_proto.o: ../../include/vstring.h
+smtpd_sasl_proto.o: smtpd.h
+smtpd_sasl_proto.o: smtpd_chat.h
+smtpd_sasl_proto.o: smtpd_sasl_glue.h
+smtpd_sasl_proto.o: smtpd_sasl_proto.c
+smtpd_sasl_proto.o: smtpd_sasl_proto.h
+smtpd_sasl_proto.o: smtpd_token.h
+smtpd_state.o: ../../include/argv.h
+smtpd_state.o: ../../include/attr.h
+smtpd_state.o: ../../include/check_arg.h
+smtpd_state.o: ../../include/cleanup_user.h
+smtpd_state.o: ../../include/dns.h
+smtpd_state.o: ../../include/events.h
+smtpd_state.o: ../../include/htable.h
+smtpd_state.o: ../../include/iostuff.h
+smtpd_state.o: ../../include/mail_error.h
+smtpd_state.o: ../../include/mail_params.h
+smtpd_state.o: ../../include/mail_proto.h
+smtpd_state.o: ../../include/mail_stream.h
+smtpd_state.o: ../../include/milter.h
+smtpd_state.o: ../../include/msg.h
+smtpd_state.o: ../../include/myaddrinfo.h
+smtpd_state.o: ../../include/mymalloc.h
+smtpd_state.o: ../../include/name_code.h
+smtpd_state.o: ../../include/name_mask.h
+smtpd_state.o: ../../include/nvtable.h
+smtpd_state.o: ../../include/sock_addr.h
+smtpd_state.o: ../../include/sys_defs.h
+smtpd_state.o: ../../include/tls.h
+smtpd_state.o: ../../include/vbuf.h
+smtpd_state.o: ../../include/vstream.h
+smtpd_state.o: ../../include/vstring.h
+smtpd_state.o: smtpd.h
+smtpd_state.o: smtpd_chat.h
+smtpd_state.o: smtpd_sasl_glue.h
+smtpd_state.o: smtpd_state.c
+smtpd_token.o: ../../include/check_arg.h
+smtpd_token.o: ../../include/mvect.h
+smtpd_token.o: ../../include/mymalloc.h
+smtpd_token.o: ../../include/sys_defs.h
+smtpd_token.o: ../../include/vbuf.h
+smtpd_token.o: ../../include/vstring.h
+smtpd_token.o: smtpd_token.c
+smtpd_token.o: smtpd_token.h
+smtpd_xforward.o: ../../include/argv.h
+smtpd_xforward.o: ../../include/attr.h
+smtpd_xforward.o: ../../include/check_arg.h
+smtpd_xforward.o: ../../include/dns.h
+smtpd_xforward.o: ../../include/htable.h
+smtpd_xforward.o: ../../include/iostuff.h
+smtpd_xforward.o: ../../include/mail_proto.h
+smtpd_xforward.o: ../../include/mail_stream.h
+smtpd_xforward.o: ../../include/milter.h
+smtpd_xforward.o: ../../include/msg.h
+smtpd_xforward.o: ../../include/myaddrinfo.h
+smtpd_xforward.o: ../../include/mymalloc.h
+smtpd_xforward.o: ../../include/name_code.h
+smtpd_xforward.o: ../../include/name_mask.h
+smtpd_xforward.o: ../../include/nvtable.h
+smtpd_xforward.o: ../../include/sock_addr.h
+smtpd_xforward.o: ../../include/sys_defs.h
+smtpd_xforward.o: ../../include/tls.h
+smtpd_xforward.o: ../../include/vbuf.h
+smtpd_xforward.o: ../../include/vstream.h
+smtpd_xforward.o: ../../include/vstring.h
+smtpd_xforward.o: smtpd.h
+smtpd_xforward.o: smtpd_xforward.c
diff --git a/src/smtpd/smtpd.c b/src/smtpd/smtpd.c
new file mode 100644
index 0000000..c46bdf8
--- /dev/null
+++ b/src/smtpd/smtpd.c
@@ -0,0 +1,6695 @@
+/*++
+/* NAME
+/* smtpd 8
+/* SUMMARY
+/* Postfix SMTP server
+/* SYNOPSIS
+/* \fBsmtpd\fR [generic Postfix daemon options]
+/*
+/* \fBsendmail -bs\fR
+/* DESCRIPTION
+/* The SMTP server accepts network connection requests
+/* and performs zero or more SMTP transactions per connection.
+/* Each received message is piped through the \fBcleanup\fR(8)
+/* daemon, and is placed into the \fBincoming\fR queue as one
+/* single queue file. For this mode of operation, the program
+/* expects to be run from the \fBmaster\fR(8) process manager.
+/*
+/* Alternatively, the SMTP server be can run in stand-alone
+/* mode; this is traditionally obtained with "\fBsendmail
+/* -bs\fR". When the SMTP server runs stand-alone with non
+/* $\fBmail_owner\fR privileges, it receives mail even while
+/* the mail system is not running, deposits messages directly
+/* into the \fBmaildrop\fR queue, and disables the SMTP server's
+/* access policies. As of Postfix version 2.3, the SMTP server
+/* refuses to receive mail from the network when it runs with
+/* non $\fBmail_owner\fR privileges.
+/*
+/* The SMTP server implements a variety of policies for connection
+/* requests, and for parameters given to \fBHELO, ETRN, MAIL FROM, VRFY\fR
+/* and \fBRCPT TO\fR commands. They are detailed below and in the
+/* \fBmain.cf\fR configuration file.
+/* SECURITY
+/* .ad
+/* .fi
+/* The SMTP server is moderately security-sensitive. It talks to SMTP
+/* clients and to DNS servers on the network. The SMTP server can be
+/* run chrooted at fixed low privilege.
+/* STANDARDS
+/* RFC 821 (SMTP protocol)
+/* RFC 1123 (Host requirements)
+/* RFC 1652 (8bit-MIME transport)
+/* RFC 1869 (SMTP service extensions)
+/* RFC 1870 (Message size declaration)
+/* RFC 1985 (ETRN command)
+/* RFC 2034 (SMTP enhanced status codes)
+/* RFC 2554 (AUTH command)
+/* RFC 2821 (SMTP protocol)
+/* RFC 2920 (SMTP pipelining)
+/* RFC 3030 (CHUNKING without BINARYMIME)
+/* RFC 3207 (STARTTLS command)
+/* RFC 3461 (SMTP DSN extension)
+/* RFC 3463 (Enhanced status codes)
+/* RFC 3848 (ESMTP transmission types)
+/* RFC 4409 (Message submission)
+/* RFC 4954 (AUTH command)
+/* RFC 5321 (SMTP protocol)
+/* RFC 6531 (Internationalized SMTP)
+/* RFC 6533 (Internationalized Delivery Status Notifications)
+/* RFC 7505 ("Null MX" No Service Resource Record)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces, protocol problems,
+/* policy violations, and of other trouble.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtpd\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
+/* The following parameters work around implementation errors in other
+/* software, and/or allow you to override standards in order to prevent
+/* undesirable use.
+/* .ad
+/* .fi
+/* .IP "\fBbroken_sasl_auth_clients (no)\fR"
+/* Enable interoperability with remote SMTP clients that implement an obsolete
+/* version of the AUTH command (RFC 4954).
+/* .IP "\fBdisable_vrfy_command (no)\fR"
+/* Disable the SMTP VRFY command.
+/* .IP "\fBsmtpd_noop_commands (empty)\fR"
+/* List of commands that the Postfix SMTP server replies to with "250
+/* Ok", without doing any syntax checks and without changing state.
+/* .IP "\fBstrict_rfc821_envelopes (no)\fR"
+/* Require that addresses received in SMTP MAIL FROM and RCPT TO
+/* commands are enclosed with <>, and that those addresses do
+/* not contain RFC 822 style comments or phrases.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR"
+/* Request that the Postfix SMTP server rejects mail from unknown
+/* sender addresses, even when no explicit reject_unlisted_sender
+/* access restriction is specified.
+/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR"
+/* What remote SMTP clients the Postfix SMTP server will not offer
+/* AUTH support to.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtpd_discard_ehlo_keyword_address_maps (empty)\fR"
+/* Lookup tables, indexed by the remote SMTP client address, with
+/* case insensitive lists of EHLO keywords (pipelining, starttls, auth,
+/* etc.) that the Postfix SMTP server will not send in the EHLO response
+/* to a
+/* remote SMTP client.
+/* .IP "\fBsmtpd_discard_ehlo_keywords (empty)\fR"
+/* A case insensitive list of EHLO keywords (pipelining, starttls,
+/* auth, etc.) that the Postfix SMTP server will not send in the EHLO
+/* response
+/* to a remote SMTP client.
+/* .IP "\fBsmtpd_delay_open_until_valid_rcpt (yes)\fR"
+/* Postpone the start of an SMTP mail transaction until a valid
+/* RCPT TO command is received.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR"
+/* Force the Postfix SMTP server to issue a TLS session id, even
+/* when TLS session caching is turned off (smtpd_tls_session_cache_database
+/* is empty).
+/* .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.7 and later:
+/* .IP "\fBsmtpd_command_filter (empty)\fR"
+/* A mechanism to transform commands from remote SMTP clients.
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR"
+/* Change the behavior of the smtpd_timeout and smtpd_starttls_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 3.0 and later:
+/* .IP "\fBsmtpd_dns_reply_filter (empty)\fR"
+/* Optional filter for Postfix SMTP server DNS lookup results.
+/* ADDRESS REWRITING CONTROLS
+/* .ad
+/* .fi
+/* See the ADDRESS_REWRITING_README document for a detailed
+/* discussion of Postfix address rewriting.
+/* .IP "\fBreceive_override_options (empty)\fR"
+/* Enable or disable recipient validation, built-in content
+/* filtering, or address mapping.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBlocal_header_rewrite_clients (permit_inet_interfaces)\fR"
+/* Rewrite message header addresses in mail from these clients and
+/* update incomplete addresses with the domain name in $myorigin or
+/* $mydomain; either don't rewrite message headers from other clients
+/* at all, or rewrite message headers and update incomplete addresses
+/* with the domain specified in the remote_header_rewrite_domain
+/* parameter.
+/* BEFORE-SMTPD PROXY AGENT
+/* .ad
+/* .fi
+/* Available in Postfix version 2.10 and later:
+/* .IP "\fBsmtpd_upstream_proxy_protocol (empty)\fR"
+/* The name of the proxy protocol used by an optional before-smtpd
+/* proxy agent.
+/* .IP "\fBsmtpd_upstream_proxy_timeout (5s)\fR"
+/* The time limit for the proxy protocol specified with the
+/* smtpd_upstream_proxy_protocol parameter.
+/* AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* As of version 1.0, Postfix can be configured to send new mail to
+/* an external content filter AFTER the mail is queued. This content
+/* filter is expected to inject mail back into a (Postfix or other)
+/* MTA for further delivery. See the FILTER_README document for details.
+/* .IP "\fBcontent_filter (empty)\fR"
+/* After the message is queued, send the entire message to the
+/* specified \fItransport:destination\fR.
+/* BEFORE QUEUE EXTERNAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* As of version 2.1, the Postfix SMTP server can be configured
+/* to send incoming mail to a real-time SMTP-based content filter
+/* BEFORE mail is queued. This content filter is expected to inject
+/* mail back into Postfix. See the SMTPD_PROXY_README document for
+/* details on how to configure and operate this feature.
+/* .IP "\fBsmtpd_proxy_filter (empty)\fR"
+/* The hostname and TCP port of the mail filtering proxy server.
+/* .IP "\fBsmtpd_proxy_ehlo ($myhostname)\fR"
+/* How the Postfix SMTP server announces itself to the proxy filter.
+/* .IP "\fBsmtpd_proxy_options (empty)\fR"
+/* List of options that control how the Postfix SMTP server
+/* communicates with a before-queue content filter.
+/* .IP "\fBsmtpd_proxy_timeout (100s)\fR"
+/* The time limit for connecting to a proxy filter and for sending or
+/* receiving information.
+/* BEFORE QUEUE MILTER CONTROLS
+/* .ad
+/* .fi
+/* As of version 2.3, Postfix supports the Sendmail version 8
+/* Milter (mail filter) protocol. These content filters run
+/* outside Postfix. They can inspect the SMTP command stream
+/* and the message content, and can request modifications before
+/* mail is queued. For details see the MILTER_README document.
+/* .IP "\fBsmtpd_milters (empty)\fR"
+/* A list of Milter (mail filter) applications for new mail that
+/* arrives via the Postfix \fBsmtpd\fR(8) server.
+/* .IP "\fBmilter_protocol (6)\fR"
+/* The mail filter protocol version and optional protocol extensions
+/* for communication with a Milter application; prior to Postfix 2.6
+/* the default protocol is 2.
+/* .IP "\fBmilter_default_action (tempfail)\fR"
+/* The default action when a Milter (mail filter) response is
+/* unavailable (for example, bad Postfix configuration or Milter
+/* failure).
+/* .IP "\fBmilter_macro_daemon_name ($myhostname)\fR"
+/* The {daemon_name} macro value for Milter (mail filter) applications.
+/* .IP "\fBmilter_macro_v ($mail_name $mail_version)\fR"
+/* The {v} macro value for Milter (mail filter) applications.
+/* .IP "\fBmilter_connect_timeout (30s)\fR"
+/* The time limit for connecting to a Milter (mail filter)
+/* application, and for negotiating protocol options.
+/* .IP "\fBmilter_command_timeout (30s)\fR"
+/* The time limit for sending an SMTP command to a Milter (mail
+/* filter) application, and for receiving the response.
+/* .IP "\fBmilter_content_timeout (300s)\fR"
+/* The time limit for sending message content to a Milter (mail
+/* filter) application, and for receiving the response.
+/* .IP "\fBmilter_connect_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after completion of an SMTP connection.
+/* .IP "\fBmilter_helo_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the SMTP HELO or EHLO command.
+/* .IP "\fBmilter_mail_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the SMTP MAIL FROM command.
+/* .IP "\fBmilter_rcpt_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the SMTP RCPT TO command.
+/* .IP "\fBmilter_data_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to version 4 or higher Milter (mail
+/* filter) applications after the SMTP DATA command.
+/* .IP "\fBmilter_unknown_command_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to version 3 or higher Milter (mail
+/* filter) applications after an unknown SMTP command.
+/* .IP "\fBmilter_end_of_header_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the end of the message header.
+/* .IP "\fBmilter_end_of_data_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the message end-of-data.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBmilter_macro_defaults (empty)\fR"
+/* Optional list of \fIname=value\fR pairs that specify default
+/* values for arbitrary macros that Postfix may send to Milter
+/* applications.
+/* .PP
+/* Available in Postfix version 3.2 and later:
+/* .IP "\fBsmtpd_milter_maps (empty)\fR"
+/* Lookup tables with Milter settings per remote SMTP client IP
+/* address.
+/* GENERAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* The following parameters are applicable for both built-in
+/* and external content filters.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBreceive_override_options (empty)\fR"
+/* Enable or disable recipient validation, built-in content
+/* filtering, or address mapping.
+/* EXTERNAL CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* The following parameters are applicable for both before-queue
+/* and after-queue content filtering.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_authorized_xforward_hosts (empty)\fR"
+/* What remote SMTP clients are allowed to use the XFORWARD feature.
+/* SASL AUTHENTICATION CONTROLS
+/* .ad
+/* .fi
+/* Postfix SASL support (RFC 4954) can be used to authenticate remote
+/* SMTP clients to the Postfix SMTP server, and to authenticate the
+/* Postfix SMTP client to a remote SMTP server.
+/* See the SASL_README document for details.
+/* .IP "\fBbroken_sasl_auth_clients (no)\fR"
+/* Enable interoperability with remote SMTP clients that implement an obsolete
+/* version of the AUTH command (RFC 4954).
+/* .IP "\fBsmtpd_sasl_auth_enable (no)\fR"
+/* Enable SASL authentication in the Postfix SMTP server.
+/* .IP "\fBsmtpd_sasl_local_domain (empty)\fR"
+/* The name of the Postfix SMTP server's local SASL authentication
+/* realm.
+/* .IP "\fBsmtpd_sasl_security_options (noanonymous)\fR"
+/* Postfix SMTP server SASL security options; as of Postfix 2.3
+/* the list of available
+/* features depends on the SASL server implementation that is selected
+/* with \fBsmtpd_sasl_type\fR.
+/* .IP "\fBsmtpd_sender_login_maps (empty)\fR"
+/* Optional lookup table with the SASL login names that own the sender
+/* (MAIL FROM) addresses.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR"
+/* What remote SMTP clients the Postfix SMTP server will not offer
+/* AUTH support to.
+/* .PP
+/* Available in Postfix version 2.1 and 2.2:
+/* .IP "\fBsmtpd_sasl_application_name (smtpd)\fR"
+/* The application name that the Postfix SMTP server uses for SASL
+/* server initialization.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtpd_sasl_authenticated_header (no)\fR"
+/* Report the SASL authenticated user name in the \fBsmtpd\fR(8) Received
+/* message header.
+/* .IP "\fBsmtpd_sasl_path (smtpd)\fR"
+/* Implementation-specific information that the Postfix SMTP server
+/* passes through to
+/* the SASL plug-in implementation that is selected with
+/* \fBsmtpd_sasl_type\fR.
+/* .IP "\fBsmtpd_sasl_type (cyrus)\fR"
+/* The SASL plug-in type that the Postfix SMTP server should use
+/* for authentication.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBcyrus_sasl_config_path (empty)\fR"
+/* Search path for Cyrus SASL application configuration files,
+/* currently used only to locate the $smtpd_sasl_path.conf file.
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBsmtpd_sasl_service (smtp)\fR"
+/* The service name that is passed to the SASL plug-in that is
+/* selected with \fBsmtpd_sasl_type\fR and \fBsmtpd_sasl_path\fR.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBsmtpd_sasl_response_limit (12288)\fR"
+/* The maximum length of a SASL client's response to a server challenge.
+/* STARTTLS SUPPORT CONTROLS
+/* .ad
+/* .fi
+/* Detailed information about STARTTLS configuration may be
+/* found in the TLS_README document.
+/* .IP "\fBsmtpd_tls_security_level (empty)\fR"
+/* The SMTP TLS security level for the Postfix SMTP server; when
+/* a non-empty value is specified, this overrides the obsolete parameters
+/* smtpd_use_tls and smtpd_enforce_tls.
+/* .IP "\fBsmtpd_sasl_tls_security_options ($smtpd_sasl_security_options)\fR"
+/* The SASL authentication security options that the Postfix SMTP
+/* server uses for TLS encrypted SMTP sessions.
+/* .IP "\fBsmtpd_starttls_timeout (see 'postconf -d' output)\fR"
+/* The time limit for Postfix SMTP server write and read operations
+/* during TLS startup and shutdown handshake procedures.
+/* .IP "\fBsmtpd_tls_CAfile (empty)\fR"
+/* A file containing (PEM format) CA certificates of root CAs trusted
+/* to sign either remote SMTP client certificates or intermediate CA
+/* certificates.
+/* .IP "\fBsmtpd_tls_CApath (empty)\fR"
+/* A directory containing (PEM format) CA certificates of root CAs
+/* trusted to sign either remote SMTP client certificates or intermediate CA
+/* certificates.
+/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR"
+/* Force the Postfix SMTP server to issue a TLS session id, even
+/* when TLS session caching is turned off (smtpd_tls_session_cache_database
+/* is empty).
+/* .IP "\fBsmtpd_tls_ask_ccert (no)\fR"
+/* Ask a remote SMTP client for a client certificate.
+/* .IP "\fBsmtpd_tls_auth_only (no)\fR"
+/* When TLS encryption is optional in the Postfix SMTP server, do
+/* not announce or accept SASL authentication over unencrypted
+/* connections.
+/* .IP "\fBsmtpd_tls_ccert_verifydepth (9)\fR"
+/* The verification depth for remote SMTP client certificates.
+/* .IP "\fBsmtpd_tls_cert_file (empty)\fR"
+/* File with the Postfix SMTP server RSA certificate in PEM format.
+/* .IP "\fBsmtpd_tls_exclude_ciphers (empty)\fR"
+/* List of ciphers or cipher types to exclude from the SMTP server
+/* cipher list at all TLS security levels.
+/* .IP "\fBsmtpd_tls_dcert_file (empty)\fR"
+/* File with the Postfix SMTP server DSA certificate in PEM format.
+/* .IP "\fBsmtpd_tls_dh1024_param_file (empty)\fR"
+/* File with DH parameters that the Postfix SMTP server should
+/* use with non-export EDH ciphers.
+/* .IP "\fBsmtpd_tls_dh512_param_file (empty)\fR"
+/* File with DH parameters that the Postfix SMTP server should
+/* use with export-grade EDH ciphers.
+/* .IP "\fBsmtpd_tls_dkey_file ($smtpd_tls_dcert_file)\fR"
+/* File with the Postfix SMTP server DSA private key in PEM format.
+/* .IP "\fBsmtpd_tls_key_file ($smtpd_tls_cert_file)\fR"
+/* File with the Postfix SMTP server RSA private key in PEM format.
+/* .IP "\fBsmtpd_tls_loglevel (0)\fR"
+/* Enable additional Postfix SMTP server logging of TLS activity.
+/* .IP "\fBsmtpd_tls_mandatory_ciphers (medium)\fR"
+/* The minimum TLS cipher grade that the Postfix SMTP server will
+/* use with mandatory TLS encryption.
+/* .IP "\fBsmtpd_tls_mandatory_exclude_ciphers (empty)\fR"
+/* Additional list of ciphers or cipher types to exclude from the
+/* Postfix SMTP server cipher list at mandatory TLS security levels.
+/* .IP "\fBsmtpd_tls_mandatory_protocols (!SSLv2, !SSLv3)\fR"
+/* The SSL/TLS protocols accepted by the Postfix SMTP server with
+/* mandatory TLS encryption.
+/* .IP "\fBsmtpd_tls_received_header (no)\fR"
+/* Request that the Postfix SMTP server produces Received: message
+/* headers that include information about the protocol and cipher used,
+/* as well as the remote SMTP client CommonName and client certificate issuer
+/* CommonName.
+/* .IP "\fBsmtpd_tls_req_ccert (no)\fR"
+/* With mandatory TLS encryption, require a trusted remote SMTP client
+/* certificate in order to allow TLS connections to proceed.
+/* .IP "\fBsmtpd_tls_wrappermode (no)\fR"
+/* Run the Postfix SMTP server in the non-standard "wrapper" mode,
+/* instead of using the STARTTLS command.
+/* .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.5 and later:
+/* .IP "\fBsmtpd_tls_fingerprint_digest (md5)\fR"
+/* The message digest algorithm to construct remote SMTP
+/* client-certificate
+/* fingerprints or public key fingerprints (Postfix 2.9 and later)
+/* for \fBcheck_ccert_access\fR and \fBpermit_tls_clientcerts\fR.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBsmtpd_tls_protocols (!SSLv2, !SSLv3)\fR"
+/* List of TLS protocols that the Postfix SMTP server will exclude
+/* or include with opportunistic TLS encryption.
+/* .IP "\fBsmtpd_tls_ciphers (medium)\fR"
+/* The minimum TLS cipher grade that the Postfix SMTP server
+/* will use with opportunistic TLS encryption.
+/* .IP "\fBsmtpd_tls_eccert_file (empty)\fR"
+/* File with the Postfix SMTP server ECDSA certificate in PEM format.
+/* .IP "\fBsmtpd_tls_eckey_file ($smtpd_tls_eccert_file)\fR"
+/* File with the Postfix SMTP server ECDSA private key in PEM format.
+/* .IP "\fBsmtpd_tls_eecdh_grade (see 'postconf -d' output)\fR"
+/* The Postfix SMTP server security grade for ephemeral elliptic-curve
+/* Diffie-Hellman (EECDH) key exchange.
+/* .IP "\fBtls_eecdh_strong_curve (prime256v1)\fR"
+/* The elliptic curve used by the Postfix SMTP server for sensibly
+/* strong
+/* ephemeral ECDH key exchange.
+/* .IP "\fBtls_eecdh_ultra_curve (secp384r1)\fR"
+/* The elliptic curve used by the Postfix SMTP server for maximally
+/* strong
+/* ephemeral ECDH key exchange.
+/* .PP
+/* Available in Postfix version 2.8 and later:
+/* .IP "\fBtls_preempt_cipherlist (no)\fR"
+/* With SSLv3 and later, use the Postfix SMTP server's cipher
+/* preference order instead of the remote client's cipher preference
+/* order.
+/* .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 and later:
+/* .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 "\fBtls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc)\fR"
+/* Algorithm used to encrypt RFC5077 TLS session tickets.
+/* .PP
+/* Available in Postfix version 3.2 and later:
+/* .IP "\fBtls_eecdh_auto_curves (see 'postconf -d' output)\fR"
+/* The prioritized list of elliptic curves supported by the Postfix
+/* SMTP client and server.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBsmtpd_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 "\fBtls_server_sni_maps (empty)\fR"
+/* Optional lookup tables that map names received from remote SMTP
+/* clients via the TLS Server Name Indication (SNI) extension to the
+/* appropriate keys and certificate chains.
+/* .PP
+/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+/* .IP "\fBtls_fast_shutdown_enable (yes)\fR"
+/* A workaround for implementations that hang Postfix while shutting
+/* down a TLS session, until Postfix times out.
+/* .PP
+/* Available in Postfix 3.5 and later:
+/* .IP "\fBinfo_log_address_format (external)\fR"
+/* The email address form that will be used in non-debug logging
+/* (info, warning, etc.).
+/* .PP
+/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+/* .IP "\fBtls_config_file (default)\fR"
+/* Optional configuration file with baseline OpenSSL settings.
+/* .IP "\fBtls_config_name (empty)\fR"
+/* The application name passed by Postfix to OpenSSL library
+/* initialization functions.
+/* 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 "\fBsmtpd_use_tls (no)\fR"
+/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+/* but do not require that clients use TLS encryption.
+/* .IP "\fBsmtpd_enforce_tls (no)\fR"
+/* Mandatory TLS: announce STARTTLS support to remote SMTP clients,
+/* and require that clients use TLS encryption.
+/* .IP "\fBsmtpd_tls_cipherlist (empty)\fR"
+/* Obsolete Postfix < 2.3 control for the Postfix SMTP server TLS
+/* cipher list.
+/* 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 "\fBstrict_smtputf8 (no)\fR"
+/* Enable stricter enforcement of the SMTPUTF8 protocol.
+/* .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.
+/* VERP SUPPORT CONTROLS
+/* .ad
+/* .fi
+/* With VERP style delivery, each recipient of a message receives a
+/* customized copy of the message with his/her own recipient address
+/* encoded in the envelope sender address. The VERP_README file
+/* describes configuration and operation details of Postfix support
+/* for variable envelope return path addresses. VERP style delivery
+/* is requested with the SMTP XVERP command or with the "sendmail
+/* -V" command-line option and is available in Postfix version 1.1
+/* and later.
+/* .IP "\fBdefault_verp_delimiters (+=)\fR"
+/* The two default VERP delimiter characters.
+/* .IP "\fBverp_delimiter_filter (-=+)\fR"
+/* The characters Postfix accepts as VERP delimiter characters on the
+/* Postfix \fBsendmail\fR(1) command line and in SMTP commands.
+/* .PP
+/* Available in Postfix version 1.1 and 2.0:
+/* .IP "\fBauthorized_verp_clients ($mynetworks)\fR"
+/* What remote SMTP clients are allowed to specify the XVERP command.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_authorized_verp_clients ($authorized_verp_clients)\fR"
+/* What remote SMTP clients are allowed to specify the XVERP command.
+/* TROUBLE SHOOTING CONTROLS
+/* .ad
+/* .fi
+/* The DEBUG_README document describes how to debug parts of the
+/* Postfix mail system. The methods vary from making the software log
+/* a lot of detail, to running some daemon processes under control of
+/* a call tracer or debugger.
+/* .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.
+/* .IP "\fBsmtpd_reject_footer (empty)\fR"
+/* Optional information that is appended after each Postfix SMTP
+/* server
+/* 4XX or 5XX response.
+/* .IP "\fBsoft_bounce (no)\fR"
+/* Safety net to keep mail queued that would otherwise be returned to
+/* the sender.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_authorized_xclient_hosts (empty)\fR"
+/* What remote SMTP clients are allowed to use the XCLIENT feature.
+/* .PP
+/* Available in Postfix version 2.10 and later:
+/* .IP "\fBsmtpd_log_access_permit_actions (empty)\fR"
+/* Enable logging of the named "permit" actions in SMTP server
+/* access lists (by default, the SMTP server logs "reject" actions but
+/* not "permit" actions).
+/* KNOWN VERSUS UNKNOWN RECIPIENT CONTROLS
+/* .ad
+/* .fi
+/* As of Postfix version 2.0, the SMTP server rejects mail for
+/* unknown recipients. This prevents the mail queue from clogging up
+/* with undeliverable MAILER-DAEMON messages. Additional information
+/* on this topic is in the LOCAL_RECIPIENT_README and ADDRESS_CLASS_README
+/* documents.
+/* .IP "\fBshow_user_unknown_table_name (yes)\fR"
+/* Display the name of the recipient table in the "User unknown"
+/* responses.
+/* .IP "\fBcanonical_maps (empty)\fR"
+/* Optional address mapping lookup tables for message headers and
+/* envelopes.
+/* .IP "\fBrecipient_canonical_maps (empty)\fR"
+/* Optional address mapping lookup tables for envelope and header
+/* recipient addresses.
+/* .IP "\fBsender_canonical_maps (empty)\fR"
+/* Optional address mapping lookup tables for envelope and header
+/* sender addresses.
+/* .PP
+/* Parameters concerning known/unknown local recipients:
+/* .IP "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+/* The list of domains that are delivered via the $local_transport
+/* mail delivery transport.
+/* .IP "\fBinet_interfaces (all)\fR"
+/* The network interface addresses that this mail system receives
+/* mail on.
+/* .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 "\fBinet_protocols (all)\fR"
+/* The Internet protocols Postfix will attempt to use when making
+/* or accepting connections.
+/* .IP "\fBlocal_recipient_maps (proxy:unix:passwd.byname $alias_maps)\fR"
+/* Lookup tables with all names or addresses of local recipients:
+/* a recipient address is local when its domain matches $mydestination,
+/* $inet_interfaces or $proxy_interfaces.
+/* .IP "\fBunknown_local_recipient_reject_code (550)\fR"
+/* The numerical Postfix SMTP server response code when a recipient
+/* address is local, and $local_recipient_maps specifies a list of
+/* lookup tables that does not match the recipient.
+/* .PP
+/* Parameters concerning known/unknown recipients of relay destinations:
+/* .IP "\fBrelay_domains (Postfix >= 3.0: empty, Postfix < 3.0: $mydestination)\fR"
+/* What destination domains (and subdomains thereof) this system
+/* will relay mail to.
+/* .IP "\fBrelay_recipient_maps (empty)\fR"
+/* Optional lookup tables with all valid addresses in the domains
+/* that match $relay_domains.
+/* .IP "\fBunknown_relay_recipient_reject_code (550)\fR"
+/* The numerical Postfix SMTP server reply code when a recipient
+/* address matches $relay_domains, and relay_recipient_maps specifies
+/* a list of lookup tables that does not match the recipient address.
+/* .PP
+/* Parameters concerning known/unknown recipients in virtual alias
+/* domains:
+/* .IP "\fBvirtual_alias_domains ($virtual_alias_maps)\fR"
+/* Postfix is final destination for the specified list of virtual
+/* alias domains, that is, domains for which all addresses are aliased
+/* to addresses in other local or remote domains.
+/* .IP "\fBvirtual_alias_maps ($virtual_maps)\fR"
+/* Optional lookup tables that alias specific mail addresses or domains
+/* to other local or remote address.
+/* .IP "\fBunknown_virtual_alias_reject_code (550)\fR"
+/* The Postfix SMTP server reply code when a recipient address matches
+/* $virtual_alias_domains, and $virtual_alias_maps specifies a list
+/* of lookup tables that does not match the recipient address.
+/* .PP
+/* Parameters concerning known/unknown recipients in virtual mailbox
+/* domains:
+/* .IP "\fBvirtual_mailbox_domains ($virtual_mailbox_maps)\fR"
+/* Postfix is final destination for the specified list of domains;
+/* mail is delivered via the $virtual_transport mail delivery transport.
+/* .IP "\fBvirtual_mailbox_maps (empty)\fR"
+/* Optional lookup tables with all valid addresses in the domains that
+/* match $virtual_mailbox_domains.
+/* .IP "\fBunknown_virtual_mailbox_reject_code (550)\fR"
+/* The Postfix SMTP server reply code when a recipient address matches
+/* $virtual_mailbox_domains, and $virtual_mailbox_maps specifies a list
+/* of lookup tables that does not match the recipient address.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* The following parameters limit resource usage by the SMTP
+/* server and/or control client request rates.
+/* .IP "\fBline_length_limit (2048)\fR"
+/* Upon input, long lines are chopped up into pieces of at most
+/* this length; upon delivery, long lines are reconstructed.
+/* .IP "\fBqueue_minfree (0)\fR"
+/* The minimal amount of free space in bytes in the queue file system
+/* that is needed to receive mail.
+/* .IP "\fBmessage_size_limit (10240000)\fR"
+/* The maximal size in bytes of a message, including envelope information.
+/* .IP "\fBsmtpd_recipient_limit (1000)\fR"
+/* The maximal number of recipients that the Postfix SMTP server
+/* accepts per message delivery request.
+/* .IP "\fBsmtpd_timeout (normal: 300s, overload: 10s)\fR"
+/* The time limit for sending a Postfix SMTP server response and for
+/* receiving a remote SMTP client request.
+/* .IP "\fBsmtpd_history_flush_threshold (100)\fR"
+/* The maximal number of lines in the Postfix SMTP server command history
+/* before it is flushed upon receipt of EHLO, RSET, or end of DATA.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtpd_peername_lookup (yes)\fR"
+/* Attempt to look up the remote SMTP client hostname, and verify that
+/* the name matches the client IP address.
+/* .PP
+/* The per SMTP client connection count and request rate limits are
+/* implemented in co-operation with the \fBanvil\fR(8) service, and
+/* are available in Postfix version 2.2 and later.
+/* .IP "\fBsmtpd_client_connection_count_limit (50)\fR"
+/* How many simultaneous connections any client is allowed to
+/* make to this service.
+/* .IP "\fBsmtpd_client_connection_rate_limit (0)\fR"
+/* The maximal number of connection attempts any client is allowed to
+/* make to this service per time unit.
+/* .IP "\fBsmtpd_client_message_rate_limit (0)\fR"
+/* The maximal number of message delivery requests that any client is
+/* allowed to make to this service per time unit, regardless of whether
+/* or not Postfix actually accepts those messages.
+/* .IP "\fBsmtpd_client_recipient_rate_limit (0)\fR"
+/* The maximal number of recipient addresses that any client is allowed
+/* to send to this service per time unit, regardless of whether or not
+/* Postfix actually accepts those recipients.
+/* .IP "\fBsmtpd_client_event_limit_exceptions ($mynetworks)\fR"
+/* Clients that are excluded from smtpd_client_*_count/rate_limit
+/* restrictions.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsmtpd_client_new_tls_session_rate_limit (0)\fR"
+/* The maximal number of new (i.e., uncached) TLS sessions that a
+/* remote SMTP client is allowed to negotiate with this service per
+/* time unit.
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR"
+/* Change the behavior of the smtpd_timeout and smtpd_starttls_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 3.1 and later:
+/* .IP "\fBsmtpd_client_auth_rate_limit (0)\fR"
+/* The maximal number of AUTH commands that any client is allowed to
+/* send to this service per time unit, regardless of whether or not
+/* Postfix actually accepts those commands.
+/* .PP
+/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+/* .IP "\fBsmtpd_forbid_unauth_pipelining (Postfix >= 3.9: yes)\fR"
+/* Disconnect remote SMTP clients that violate RFC 2920 (or 5321)
+/* command pipelining constraints.
+/* .PP
+/* Available in Postfix 3.9, 3.8.4, 3.7.9, 3.6.13, 3.5.23 and later:
+/* .IP "\fBsmtpd_forbid_bare_newline (Postfix < 3.9: no)\fR"
+/* Reject or restrict input lines from an SMTP client that end in
+/* <LF> instead of the standard <CR><LF>.
+/* .IP "\fBsmtpd_forbid_bare_newline_exclusions ($mynetworks)\fR"
+/* Exclude the specified clients from smtpd_forbid_bare_newline
+/* enforcement.
+/* .PP
+/* Available in Postfix 3.9, 3.8.5, 3.7.10, 3.6.14, 3.5.24 and
+/* later:
+/* .IP "\fBsmtpd_forbid_bare_newline_reject_code (550)\fR"
+/* The numerical Postfix SMTP server response code when rejecting a
+/* request with "smtpd_forbid_bare_newline = reject".
+/* TARPIT CONTROLS
+/* .ad
+/* .fi
+/* When a remote SMTP client makes errors, the Postfix SMTP server
+/* can insert delays before responding. This can help to slow down
+/* run-away software. The behavior is controlled by an error counter
+/* that counts the number of errors within an SMTP session that a
+/* client makes without delivering mail.
+/* .IP "\fBsmtpd_error_sleep_time (1s)\fR"
+/* With Postfix version 2.1 and later: the SMTP server response delay after
+/* a client has made more than $smtpd_soft_error_limit errors, and
+/* fewer than $smtpd_hard_error_limit errors, without delivering mail.
+/* .IP "\fBsmtpd_soft_error_limit (10)\fR"
+/* The number of errors a remote SMTP client is allowed to make without
+/* delivering mail before the Postfix SMTP server slows down all its
+/* responses.
+/* .IP "\fBsmtpd_hard_error_limit (normal: 20, overload: 1)\fR"
+/* The maximal number of errors a remote SMTP client is allowed to
+/* make without delivering mail.
+/* .IP "\fBsmtpd_junk_command_limit (normal: 100, overload: 1)\fR"
+/* The number of junk commands (NOOP, VRFY, ETRN or RSET) that a remote
+/* SMTP client can send before the Postfix SMTP server starts to
+/* increment the error counter with each junk command.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_recipient_overshoot_limit (1000)\fR"
+/* The number of recipients that a remote SMTP client can send in
+/* excess of the limit specified with $smtpd_recipient_limit, before
+/* the Postfix SMTP server increments the per-session error count
+/* for each excess recipient.
+/* ACCESS POLICY DELEGATION CONTROLS
+/* .ad
+/* .fi
+/* As of version 2.1, Postfix can be configured to delegate access
+/* policy decisions to an external server that runs outside Postfix.
+/* See the file SMTPD_POLICY_README for more information.
+/* .IP "\fBsmtpd_policy_service_max_idle (300s)\fR"
+/* The time after which an idle SMTPD policy service connection is
+/* closed.
+/* .IP "\fBsmtpd_policy_service_max_ttl (1000s)\fR"
+/* The time after which an active SMTPD policy service connection is
+/* closed.
+/* .IP "\fBsmtpd_policy_service_timeout (100s)\fR"
+/* The time limit for connecting to, writing to, or receiving from a
+/* delegated SMTPD policy server.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBsmtpd_policy_service_default_action (451 4.3.5 Server configuration problem)\fR"
+/* The default action when an SMTPD policy service request fails.
+/* .IP "\fBsmtpd_policy_service_request_limit (0)\fR"
+/* The maximal number of requests per SMTPD policy service connection,
+/* or zero (no limit).
+/* .IP "\fBsmtpd_policy_service_try_limit (2)\fR"
+/* The maximal number of attempts to send an SMTPD policy service
+/* request before giving up.
+/* .IP "\fBsmtpd_policy_service_retry_delay (1s)\fR"
+/* The delay between attempts to resend a failed SMTPD policy
+/* service request.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBsmtpd_policy_service_policy_context (empty)\fR"
+/* Optional information that the Postfix SMTP server specifies in
+/* the "policy_context" attribute of a policy service request (originally,
+/* to share the same service endpoint among multiple check_policy_service
+/* clients).
+/* ACCESS CONTROLS
+/* .ad
+/* .fi
+/* The SMTPD_ACCESS_README document gives an introduction to all the
+/* SMTP server access control features.
+/* .IP "\fBsmtpd_delay_reject (yes)\fR"
+/* Wait until the RCPT TO command before evaluating
+/* $smtpd_client_restrictions, $smtpd_helo_restrictions and
+/* $smtpd_sender_restrictions, or wait until the ETRN command before
+/* evaluating $smtpd_client_restrictions and $smtpd_helo_restrictions.
+/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR"
+/* A list of Postfix features where the pattern "example.com" also
+/* matches subdomains of example.com,
+/* instead of requiring an explicit ".example.com" pattern.
+/* .IP "\fBsmtpd_client_restrictions (empty)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client connection request.
+/* .IP "\fBsmtpd_helo_required (no)\fR"
+/* Require that a remote SMTP client introduces itself with the HELO
+/* or EHLO command before sending the MAIL command or other commands
+/* that require EHLO negotiation.
+/* .IP "\fBsmtpd_helo_restrictions (empty)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client HELO command.
+/* .IP "\fBsmtpd_sender_restrictions (empty)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client MAIL FROM command.
+/* .IP "\fBsmtpd_recipient_restrictions (see 'postconf -d' output)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client RCPT TO command, after smtpd_relay_restrictions.
+/* .IP "\fBsmtpd_etrn_restrictions (empty)\fR"
+/* Optional restrictions that the Postfix SMTP server applies in the
+/* context of a client ETRN command.
+/* .IP "\fBallow_untrusted_routing (no)\fR"
+/* Forward mail with sender-specified routing (user[@%!]remote[@%!]site)
+/* from untrusted clients to destinations matching $relay_domains.
+/* .IP "\fBsmtpd_restriction_classes (empty)\fR"
+/* User-defined aliases for groups of access restrictions.
+/* .IP "\fBsmtpd_null_access_lookup_key (<>)\fR"
+/* The lookup key to be used in SMTP \fBaccess\fR(5) tables instead of the
+/* null sender address.
+/* .IP "\fBpermit_mx_backup_networks (empty)\fR"
+/* Restrict the use of the permit_mx_backup SMTP access feature to
+/* only domains whose primary MX hosts match the listed networks.
+/* .PP
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBsmtpd_data_restrictions (empty)\fR"
+/* Optional access restrictions that the Postfix SMTP server applies
+/* in the context of the SMTP DATA command.
+/* .IP "\fBsmtpd_expansion_filter (see 'postconf -d' output)\fR"
+/* What characters are allowed in $name expansions of RBL reply
+/* templates.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR"
+/* Request that the Postfix SMTP server rejects mail from unknown
+/* sender addresses, even when no explicit reject_unlisted_sender
+/* access restriction is specified.
+/* .IP "\fBsmtpd_reject_unlisted_recipient (yes)\fR"
+/* Request that the Postfix SMTP server rejects mail for unknown
+/* recipient addresses, even when no explicit reject_unlisted_recipient
+/* access restriction is specified.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBsmtpd_end_of_data_restrictions (empty)\fR"
+/* Optional access restrictions that the Postfix SMTP server
+/* applies in the context of the SMTP END-OF-DATA command.
+/* .PP
+/* Available in Postfix version 2.10 and later:
+/* .IP "\fBsmtpd_relay_restrictions (permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination)\fR"
+/* Access restrictions for mail relay control that the Postfix
+/* SMTP server applies in the context of the RCPT TO command, before
+/* smtpd_recipient_restrictions.
+/* SENDER AND RECIPIENT ADDRESS VERIFICATION CONTROLS
+/* .ad
+/* .fi
+/* Postfix version 2.1 introduces sender and recipient address verification.
+/* This feature is implemented by sending probe email messages that
+/* are not actually delivered.
+/* This feature is requested via the reject_unverified_sender and
+/* reject_unverified_recipient access restrictions. The status of
+/* verification probes is maintained by the \fBverify\fR(8) server.
+/* See the file ADDRESS_VERIFICATION_README for information
+/* about how to configure and operate the Postfix sender/recipient
+/* address verification service.
+/* .IP "\fBaddress_verify_poll_count (normal: 3, overload: 1)\fR"
+/* How many times to query the \fBverify\fR(8) service for the completion
+/* of an address verification request in progress.
+/* .IP "\fBaddress_verify_poll_delay (3s)\fR"
+/* The delay between queries for the completion of an address
+/* verification request in progress.
+/* .IP "\fBaddress_verify_sender ($double_bounce_sender)\fR"
+/* The sender address to use in address verification probes; prior
+/* to Postfix 2.5 the default was "postmaster".
+/* .IP "\fBunverified_sender_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a recipient
+/* address is rejected by the reject_unverified_sender restriction.
+/* .IP "\fBunverified_recipient_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response when a recipient address
+/* is rejected by the reject_unverified_recipient restriction.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBunverified_sender_defer_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a sender address
+/* probe fails due to a temporary error condition.
+/* .IP "\fBunverified_recipient_defer_code (450)\fR"
+/* The numerical Postfix SMTP server response when a recipient address
+/* probe fails due to a temporary error condition.
+/* .IP "\fBunverified_sender_reject_reason (empty)\fR"
+/* The Postfix SMTP server's reply when rejecting mail with
+/* reject_unverified_sender.
+/* .IP "\fBunverified_recipient_reject_reason (empty)\fR"
+/* The Postfix SMTP server's reply when rejecting mail with
+/* reject_unverified_recipient.
+/* .IP "\fBunverified_sender_tempfail_action ($reject_tempfail_action)\fR"
+/* The Postfix SMTP server's action when reject_unverified_sender
+/* fails due to a temporary error condition.
+/* .IP "\fBunverified_recipient_tempfail_action ($reject_tempfail_action)\fR"
+/* The Postfix SMTP server's action when reject_unverified_recipient
+/* fails due to a temporary error condition.
+/* .PP
+/* Available with Postfix 2.9 and later:
+/* .IP "\fBaddress_verify_sender_ttl (0s)\fR"
+/* The time between changes in the time-dependent portion of address
+/* verification probe sender addresses.
+/* ACCESS CONTROL RESPONSES
+/* .ad
+/* .fi
+/* The following parameters control numerical SMTP reply codes
+/* and/or text responses.
+/* .IP "\fBaccess_map_reject_code (554)\fR"
+/* The numerical Postfix SMTP server response code for
+/* an \fBaccess\fR(5) map "reject" action.
+/* .IP "\fBdefer_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a remote SMTP
+/* client request is rejected by the "defer" restriction.
+/* .IP "\fBinvalid_hostname_reject_code (501)\fR"
+/* The numerical Postfix SMTP server response code when the client
+/* HELO or EHLO command parameter is rejected by the reject_invalid_helo_hostname
+/* restriction.
+/* .IP "\fBmaps_rbl_reject_code (554)\fR"
+/* The numerical Postfix SMTP server response code when a remote SMTP
+/* client request is blocked by the reject_rbl_client, reject_rhsbl_client,
+/* reject_rhsbl_reverse_client, reject_rhsbl_sender or
+/* reject_rhsbl_recipient restriction.
+/* .IP "\fBnon_fqdn_reject_code (504)\fR"
+/* The numerical Postfix SMTP server reply code when a client request
+/* is rejected by the reject_non_fqdn_helo_hostname, reject_non_fqdn_sender
+/* or reject_non_fqdn_recipient restriction.
+/* .IP "\fBplaintext_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a request
+/* is rejected by the \fBreject_plaintext_session\fR restriction.
+/* .IP "\fBreject_code (554)\fR"
+/* The numerical Postfix SMTP server response code when a remote SMTP
+/* client request is rejected by the "reject" restriction.
+/* .IP "\fBrelay_domains_reject_code (554)\fR"
+/* The numerical Postfix SMTP server response code when a client
+/* request is rejected by the reject_unauth_destination recipient
+/* restriction.
+/* .IP "\fBunknown_address_reject_code (450)\fR"
+/* The numerical response code when the Postfix SMTP server rejects a
+/* sender or recipient address because its domain is unknown.
+/* .IP "\fBunknown_client_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response code when a client
+/* without valid address <=> name mapping is rejected by the
+/* reject_unknown_client_hostname restriction.
+/* .IP "\fBunknown_hostname_reject_code (450)\fR"
+/* The numerical Postfix SMTP server response code when the hostname
+/* specified with the HELO or EHLO command is rejected by the
+/* reject_unknown_helo_hostname restriction.
+/* .PP
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBdefault_rbl_reply (see 'postconf -d' output)\fR"
+/* The default Postfix SMTP server response template for a request that is
+/* rejected by an RBL-based restriction.
+/* .IP "\fBmulti_recipient_bounce_reject_code (550)\fR"
+/* The numerical Postfix SMTP server response code when a remote SMTP
+/* client request is blocked by the reject_multi_recipient_bounce
+/* restriction.
+/* .IP "\fBrbl_reply_maps (empty)\fR"
+/* Optional lookup tables with RBL response templates.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBaccess_map_defer_code (450)\fR"
+/* The numerical Postfix SMTP server response code for
+/* an \fBaccess\fR(5) map "defer" action, including "defer_if_permit"
+/* or "defer_if_reject".
+/* .IP "\fBreject_tempfail_action (defer_if_permit)\fR"
+/* The Postfix SMTP server's action when a reject-type restriction
+/* fails due to a temporary error condition.
+/* .IP "\fBunknown_helo_hostname_tempfail_action ($reject_tempfail_action)\fR"
+/* The Postfix SMTP server's action when reject_unknown_helo_hostname
+/* fails due to a temporary error condition.
+/* .IP "\fBunknown_address_tempfail_action ($reject_tempfail_action)\fR"
+/* The Postfix SMTP server's action when reject_unknown_sender_domain
+/* or reject_unknown_recipient_domain fail due to a temporary error
+/* condition.
+/* MISCELLANEOUS CONTROLS
+/* .ad
+/* .fi
+/* .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 "\fBcommand_directory (see 'postconf -d' output)\fR"
+/* The location of all postfix administrative commands.
+/* .IP "\fBdouble_bounce_sender (double-bounce)\fR"
+/* The sender address of postmaster notifications that are generated
+/* by the mail system.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBmail_name (Postfix)\fR"
+/* The mail system name that is displayed in Received: headers, in
+/* the SMTP greeting banner, and in bounced mail.
+/* .IP "\fBmail_owner (postfix)\fR"
+/* The UNIX system account that owns the Postfix queue and most Postfix
+/* daemon processes.
+/* .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 "\fBmyhostname (see 'postconf -d' output)\fR"
+/* The internet hostname of this mail system.
+/* .IP "\fBmynetworks (see 'postconf -d' output)\fR"
+/* The list of "trusted" remote SMTP clients that have more privileges than
+/* "strangers".
+/* .IP "\fBmyorigin ($myhostname)\fR"
+/* The domain name that locally-posted mail appears to come
+/* from, and that locally posted mail is delivered to.
+/* .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 "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .IP "\fBrecipient_delimiter (empty)\fR"
+/* The set of characters that can separate a user name from its
+/* extension (example: user+foo), or a .forward file name from its
+/* extension (example: .forward+foo).
+/* .IP "\fBsmtpd_banner ($myhostname ESMTP $mail_name)\fR"
+/* The text that follows the 220 status code in the SMTP greeting
+/* banner.
+/* .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 in Postfix version 2.2 and later:
+/* .IP "\fBsmtpd_forbidden_commands (CONNECT, GET, POST)\fR"
+/* List of commands that cause the Postfix SMTP server to immediately
+/* terminate the session with a 221 code.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBsmtpd_client_port_logging (no)\fR"
+/* Enable logging of the remote SMTP client port in addition to
+/* the hostname and IP address.
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* .PP
+/* Available in Postfix 3.4 and later:
+/* .IP "\fBsmtpd_reject_footer_maps (empty)\fR"
+/* Lookup tables, indexed by the complete Postfix SMTP server 4xx or
+/* 5xx response, with reject footer templates.
+/* SEE ALSO
+/* anvil(8), connection/rate limiting
+/* cleanup(8), message canonicalization
+/* tlsmgr(8), TLS session and PRNG management
+/* trivial-rewrite(8), address resolver
+/* verify(8), address verification service
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* 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
+/* ADDRESS_CLASS_README, blocking unknown hosted or relay recipients
+/* ADDRESS_REWRITING_README, Postfix address manipulation
+/* BDAT_README, Postfix CHUNKING support
+/* FILTER_README, external after-queue content filter
+/* LOCAL_RECIPIENT_README, blocking unknown local recipients
+/* MILTER_README, before-queue mail filter applications
+/* SMTPD_ACCESS_README, built-in access policies
+/* SMTPD_POLICY_README, external policy server
+/* SMTPD_PROXY_README, external before-queue content filter
+/* SASL_README, Postfix SASL howto
+/* TLS_README, Postfix STARTTLS howto
+/* VERP_README, Postfix XVERP extension
+/* XCLIENT_README, Postfix XCLIENT extension
+/* XFORWARD_README, Postfix XFORWARD extension
+/* 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
+/*
+/* 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 support by:
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdio.h> /* remove() */
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <signal.h>
+#include <stddef.h> /* offsetof() */
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <events.h>
+#include <smtp_stream.h>
+#include <valid_hostname.h>
+#include <dict.h>
+#include <watchdog.h>
+#include <iostuff.h>
+#include <split_at.h>
+#include <name_code.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h> /* milter_macro_v */
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <mail_date.h>
+#include <mail_conf.h>
+#include <off_cvt.h>
+#include <debug_peer.h>
+#include <mail_error.h>
+#include <flush_clnt.h>
+#include <mail_stream.h>
+#include <mail_queue.h>
+#include <tok822.h>
+#include <verp_sender.h>
+#include <string_list.h>
+#include <quote_822_local.h>
+#include <lex_822.h>
+#include <namadr_list.h>
+#include <input_transp.h>
+#include <is_header.h>
+#include <anvil_clnt.h>
+#include <flush_clnt.h>
+#include <ehlo_mask.h> /* ehlo filter */
+#include <maps.h> /* ehlo filter */
+#include <valid_mailhost_addr.h>
+#include <dsn_mask.h>
+#include <xtext.h>
+#include <uxtext.h>
+#include <tls_proxy.h>
+#include <verify_sender_addr.h>
+#include <smtputf8.h>
+#include <match_parent_style.h>
+#include <normalize_mailhost_addr.h>
+#include <info_log_addr_form.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Mail filter library. */
+
+#include <milter.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Application-specific */
+
+#include <smtpd_token.h>
+#include <smtpd.h>
+#include <smtpd_check.h>
+#include <smtpd_chat.h>
+#include <smtpd_sasl_proto.h>
+#include <smtpd_sasl_glue.h>
+#include <smtpd_proxy.h>
+#include <smtpd_milter.h>
+#include <smtpd_expand.h>
+
+ /*
+ * Tunable parameters. Make sure that there is some bound on the length of
+ * an SMTP command, so that the mail system stays in control even when a
+ * malicious client sends commands of unreasonable length (qmail-dos-1).
+ * Make sure there is some bound on the number of recipients, so that the
+ * mail system stays in control even when a malicious client sends an
+ * unreasonable number of recipients (qmail-dos-2).
+ */
+int var_smtpd_rcpt_limit;
+int var_smtpd_tmout;
+int var_smtpd_soft_erlim;
+int var_smtpd_hard_erlim;
+long var_queue_minfree; /* XXX use off_t */
+char *var_smtpd_banner;
+char *var_notify_classes;
+char *var_client_checks;
+char *var_helo_checks;
+char *var_mail_checks;
+char *var_relay_checks;
+char *var_rcpt_checks;
+char *var_etrn_checks;
+char *var_data_checks;
+char *var_eod_checks;
+int var_unk_client_code;
+int var_bad_name_code;
+int var_unk_name_code;
+int var_unk_addr_code;
+int var_relay_code;
+int var_maps_rbl_code;
+int var_map_reject_code;
+int var_map_defer_code;
+char *var_maps_rbl_domains;
+char *var_rbl_reply_maps;
+int var_helo_required;
+int var_reject_code;
+int var_defer_code;
+int var_smtpd_err_sleep;
+int var_non_fqdn_code;
+char *var_bounce_rcpt;
+char *var_error_rcpt;
+int var_smtpd_delay_reject;
+char *var_rest_classes;
+int var_strict_rfc821_env;
+bool var_disable_vrfy_cmd;
+char *var_canonical_maps;
+char *var_send_canon_maps;
+char *var_rcpt_canon_maps;
+char *var_virt_alias_maps;
+char *var_virt_mailbox_maps;
+char *var_alias_maps;
+char *var_local_rcpt_maps;
+bool var_allow_untrust_route;
+int var_smtpd_junk_cmd_limit;
+int var_smtpd_rcpt_overlim;
+bool var_smtpd_sasl_enable;
+bool var_smtpd_sasl_auth_hdr;
+char *var_smtpd_sasl_opts;
+char *var_smtpd_sasl_path;
+char *var_smtpd_sasl_service;
+char *var_cyrus_conf_path;
+char *var_smtpd_sasl_realm;
+int var_smtpd_sasl_resp_limit;
+char *var_smtpd_sasl_exceptions_networks;
+char *var_smtpd_sasl_type;
+char *var_filter_xport;
+bool var_broken_auth_clients;
+char *var_perm_mx_networks;
+char *var_smtpd_snd_auth_maps;
+char *var_smtpd_noop_cmds;
+char *var_smtpd_null_key;
+int var_smtpd_hist_thrsh;
+char *var_smtpd_exp_filter;
+char *var_def_rbl_reply;
+int var_unv_from_rcode;
+int var_unv_rcpt_rcode;
+int var_unv_from_dcode;
+int var_unv_rcpt_dcode;
+char *var_unv_from_why;
+char *var_unv_rcpt_why;
+int var_mul_rcpt_code;
+char *var_relay_rcpt_maps;
+int var_local_rcpt_code;
+int var_virt_alias_code;
+int var_virt_mailbox_code;
+int var_relay_rcpt_code;
+char *var_verp_clients;
+int var_show_unk_rcpt_table;
+int var_verify_poll_count;
+int var_verify_poll_delay;
+char *var_smtpd_proxy_filt;
+int var_smtpd_proxy_tmout;
+char *var_smtpd_proxy_ehlo;
+char *var_smtpd_proxy_opts;
+char *var_input_transp;
+int var_smtpd_policy_tmout;
+int var_smtpd_policy_req_limit;
+int var_smtpd_policy_try_limit;
+int var_smtpd_policy_try_delay;
+char *var_smtpd_policy_def_action;
+char *var_smtpd_policy_context;
+int var_smtpd_policy_idle;
+int var_smtpd_policy_ttl;
+char *var_xclient_hosts;
+char *var_xforward_hosts;
+bool var_smtpd_rej_unl_from;
+bool var_smtpd_rej_unl_rcpt;
+char *var_smtpd_forbid_cmds;
+int var_smtpd_crate_limit;
+int var_smtpd_cconn_limit;
+int var_smtpd_cmail_limit;
+int var_smtpd_crcpt_limit;
+int var_smtpd_cntls_limit;
+int var_smtpd_cauth_limit;
+char *var_smtpd_hoggers;
+char *var_local_rwr_clients;
+char *var_smtpd_ehlo_dis_words;
+char *var_smtpd_ehlo_dis_maps;
+
+char *var_smtpd_tls_level;
+bool var_smtpd_use_tls;
+bool var_smtpd_enforce_tls;
+bool var_smtpd_tls_wrappermode;
+bool var_smtpd_tls_auth_only;
+char *var_smtpd_cmd_filter;
+char *var_smtpd_rej_footer;
+char *var_smtpd_rej_ftr_maps;
+char *var_smtpd_acl_perm_log;
+char *var_smtpd_dns_re_filter;
+
+#ifdef USE_TLS
+char *var_smtpd_relay_ccerts;
+char *var_smtpd_sasl_tls_opts;
+int var_smtpd_starttls_tmout;
+char *var_smtpd_tls_CAfile;
+char *var_smtpd_tls_CApath;
+bool var_smtpd_tls_ask_ccert;
+int var_smtpd_tls_ccert_vd;
+char *var_smtpd_tls_cert_file;
+char *var_smtpd_tls_mand_ciph;
+char *var_smtpd_tls_excl_ciph;
+char *var_smtpd_tls_mand_excl;
+char *var_smtpd_tls_dcert_file;
+char *var_smtpd_tls_dh1024_param_file;
+char *var_smtpd_tls_dh512_param_file;
+char *var_smtpd_tls_dkey_file;
+char *var_smtpd_tls_key_file;
+char *var_smtpd_tls_loglevel;
+char *var_smtpd_tls_mand_proto;
+bool var_smtpd_tls_received_header;
+bool var_smtpd_tls_req_ccert;
+bool var_smtpd_tls_set_sessid;
+char *var_smtpd_tls_fpt_dgst;
+char *var_smtpd_tls_ciph;
+char *var_smtpd_tls_proto;
+char *var_smtpd_tls_eecdh;
+char *var_smtpd_tls_eccert_file;
+char *var_smtpd_tls_eckey_file;
+char *var_smtpd_tls_chain_files;
+
+#endif
+
+bool var_smtpd_peername_lookup;
+int var_plaintext_code;
+bool var_smtpd_delay_open;
+char *var_smtpd_milters;
+char *var_smtpd_milter_maps;
+int var_milt_conn_time;
+int var_milt_cmd_time;
+int var_milt_msg_time;
+char *var_milt_protocol;
+char *var_milt_def_action;
+char *var_milt_daemon_name;
+char *var_milt_v;
+char *var_milt_conn_macros;
+char *var_milt_helo_macros;
+char *var_milt_mail_macros;
+char *var_milt_rcpt_macros;
+char *var_milt_data_macros;
+char *var_milt_eoh_macros;
+char *var_milt_eod_macros;
+char *var_milt_unk_macros;
+char *var_milt_macro_deflts;
+bool var_smtpd_client_port_log;
+bool var_smtpd_forbid_unauth_pipe;
+char *var_stress;
+
+char *var_reject_tmpf_act;
+char *var_unk_name_tf_act;
+char *var_unk_addr_tf_act;
+char *var_unv_rcpt_tf_act;
+char *var_unv_from_tf_act;
+bool var_smtpd_rec_deadline;
+
+int smtpd_proxy_opts;
+
+#ifdef USE_TLSPROXY
+char *var_tlsproxy_service;
+
+#endif
+
+char *var_smtpd_uproxy_proto;
+int var_smtpd_uproxy_tmout;
+
+char *var_smtpd_forbid_bare_lf;
+char *var_smtpd_forbid_bare_lf_excl;
+int var_smtpd_forbid_bare_lf_code;
+static int bare_lf_mask;
+static NAMADR_LIST *bare_lf_excl;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+ /*
+ * EHLO keyword filter
+ */
+static MAPS *ehlo_discard_maps;
+
+ /*
+ * Per-client Milter support.
+ */
+static MAPS *smtpd_milter_maps;
+static void setup_milters(SMTPD_STATE *);
+static void teardown_milters(SMTPD_STATE *);
+
+ /*
+ * VERP command name.
+ */
+#define VERP_CMD "XVERP"
+#define VERP_CMD_LEN 5
+
+static NAMADR_LIST *verp_clients;
+
+ /*
+ * XCLIENT command. Access control is cached, so that XCLIENT can't override
+ * its own access control.
+ */
+static NAMADR_LIST *xclient_hosts;
+static int xclient_allowed; /* XXX should be SMTPD_STATE member */
+
+ /*
+ * XFORWARD command. Access control is cached.
+ */
+static NAMADR_LIST *xforward_hosts;
+static int xforward_allowed; /* XXX should be SMTPD_STATE member */
+
+ /*
+ * Client connection and rate limiting.
+ */
+ANVIL_CLNT *anvil_clnt;
+static NAMADR_LIST *hogger_list;
+
+ /*
+ * Other application-specific globals.
+ */
+int smtpd_input_transp_mask;
+
+ /*
+ * Forward declarations.
+ */
+static void helo_reset(SMTPD_STATE *);
+static void mail_reset(SMTPD_STATE *);
+static void rcpt_reset(SMTPD_STATE *);
+static void chat_reset(SMTPD_STATE *, int);
+
+#ifdef USE_TLS
+static void tls_reset(SMTPD_STATE *);
+
+#endif
+
+ /*
+ * This filter is applied after printable().
+ */
+#define NEUTER_CHARACTERS " <>()\\\";@"
+
+ /*
+ * Reasons for losing the client.
+ */
+#define REASON_TIMEOUT "timeout"
+#define REASON_LOST_CONNECTION "lost connection"
+#define REASON_ERROR_LIMIT "too many errors"
+
+#ifdef USE_TLS
+
+ /*
+ * TLS initialization status.
+ */
+#ifndef USE_TLSPROXY
+static TLS_APPL_STATE *smtpd_tls_ctx;
+static int ask_client_cert;
+
+#endif /* USE_TLSPROXY */
+#endif
+
+ /*
+ * SMTP command mapping for broken clients.
+ */
+static DICT *smtpd_cmd_filter;
+
+ /*
+ * Bare LF and End-of-DATA controls (bare CR is handled elsewhere).
+ *
+ * At the smtp_get*() line reader level, setting any of these flags in the
+ * smtp_detect_bare_lf variable enables the detection of bare newlines. The
+ * line reader will set the same flags in the smtp_got_bare_lf variable
+ * after it detects a bare newline, otherwise it clears smtp_got_bare_lf.
+ *
+ * At the SMTP command level, the flags in smtp_got_bare_lf control whether
+ * commands ending in a bare newline are rejected.
+ *
+ * At the DATA and BDAT content level, the flags in smtp_got_bare_lf control
+ * whether the standard End-of-DATA sequence CRLF.CRLF is required, and
+ * whether lines ending in bare newlines are rejected.
+ *
+ * Postfix implements "delayed reject" after detecting a bare newline in BDAT
+ * or DATA content. The SMTP server delays a REJECT response until the
+ * command is finished, instead of replying and hanging up immediately. The
+ * End-of-DATA detection is secured with BARE_LF_FLAG_WANT_STD_EOD.
+ */
+#define BARE_LF_FLAG_WANT_STD_EOD (1<<0) /* Require CRLF.CRLF */
+#define BARE_LF_FLAG_REPLY_REJECT (1<<1) /* Reject bare newline */
+
+#define IS_BARE_LF_WANT_STD_EOD(m) ((m) & BARE_LF_FLAG_WANT_STD_EOD)
+#define IS_BARE_LF_REPLY_REJECT(m) ((m) & BARE_LF_FLAG_REPLY_REJECT)
+
+static const NAME_CODE bare_lf_mask_table[] = {
+ "normalize", BARE_LF_FLAG_WANT_STD_EOD, /* Default */
+ "yes", BARE_LF_FLAG_WANT_STD_EOD, /* Migration aid */
+ "reject", BARE_LF_FLAG_WANT_STD_EOD | BARE_LF_FLAG_REPLY_REJECT,
+ "no", 0,
+ 0, -1, /* error */
+};
+
+#ifdef USE_SASL_AUTH
+
+ /*
+ * SASL exceptions.
+ */
+static NAMADR_LIST *sasl_exceptions_networks;
+
+/* sasl_client_exception - can we offer AUTH for this client */
+
+static int sasl_client_exception(SMTPD_STATE *state)
+{
+ int match;
+
+ /*
+ * This is to work around a Netscape mail client bug where it tries to
+ * use AUTH if available, even if user has not configured it. Returns
+ * TRUE if AUTH should be offered in the EHLO.
+ */
+ if (sasl_exceptions_networks == 0)
+ return (0);
+
+ if ((match = namadr_list_match(sasl_exceptions_networks,
+ state->name, state->addr)) == 0)
+ match = sasl_exceptions_networks->error;
+
+ if (msg_verbose)
+ msg_info("sasl_exceptions: %s, match=%d",
+ state->namaddr, match);
+
+ return (match);
+}
+
+#endif
+
+/* smtpd_whatsup - gather available evidence for logging */
+
+static const char *smtpd_whatsup(SMTPD_STATE *state)
+{
+ static VSTRING *buf = 0;
+
+ if (buf == 0)
+ buf = vstring_alloc(100);
+ else
+ VSTRING_RESET(buf);
+ if (state->sender)
+ vstring_sprintf_append(buf, " from=<%s>",
+ info_log_addr_form_sender(state->sender));
+ if (state->recipient)
+ vstring_sprintf_append(buf, " to=<%s>",
+ info_log_addr_form_recipient(state->recipient));
+ if (state->protocol)
+ vstring_sprintf_append(buf, " proto=%s", state->protocol);
+ if (state->helo_name)
+ vstring_sprintf_append(buf, " helo=<%s>", state->helo_name);
+#ifdef USE_SASL_AUTH
+ if (state->sasl_username)
+ vstring_sprintf_append(buf, " sasl_username=<%s>",
+ state->sasl_username);
+#endif
+ return (STR(buf));
+}
+
+/* collapse_args - put arguments together again */
+
+static void collapse_args(int argc, SMTPD_TOKEN *argv)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ vstring_strcat(argv[0].vstrval, " ");
+ vstring_strcat(argv[0].vstrval, argv[i].strval);
+ }
+ argv[0].strval = STR(argv[0].vstrval);
+}
+
+/* check_milter_reply - process reply from Milter */
+
+static const char *check_milter_reply(SMTPD_STATE *state, const char *reply)
+{
+ const char *queue_id = state->queue_id ? state->queue_id : "NOQUEUE";
+ const char *action;
+ const char *text;
+
+ /*
+ * The syntax of user-specified SMTP replies is checked by the Milter
+ * module, because the replies are also used in the cleanup server.
+ * Automatically disconnect after 421 (shutdown) reply. The Sendmail 8
+ * Milter quarantine action is not final, so it is not included in
+ * MILTER_SKIP_FLAGS.
+ */
+#define MILTER_SKIP_FLAGS (CLEANUP_FLAG_DISCARD)
+
+ switch (reply[0]) {
+ case 'H':
+ state->saved_flags |= CLEANUP_FLAG_HOLD;
+ action = "milter-hold";
+ reply = 0;
+ text = "milter triggers HOLD action";
+ break;
+ case 'D':
+ state->saved_flags |= CLEANUP_FLAG_DISCARD;
+ action = "milter-discard";
+ reply = 0;
+ text = "milter triggers DISCARD action";
+ break;
+ case 'S':
+ state->error_mask |= MAIL_ERROR_POLICY;
+ action = "milter-reject";
+ reply = "421 4.7.0 Server closing connection";
+ text = 0;
+ break;
+ case '4':
+ case '5':
+ state->error_mask |= MAIL_ERROR_POLICY;
+ action = "milter-reject";
+ text = 0;
+ break;
+ default:
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ action = "reject";
+ reply = "421 4.3.5 Server configuration error";
+ text = 0;
+ break;
+ }
+ msg_info("%s: %s: %s from %s: %s;%s", queue_id, action, state->where,
+ state->namaddr, reply ? reply : text, smtpd_whatsup(state));
+ return (reply);
+}
+
+/* helo_cmd - process HELO command */
+
+static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err;
+
+ /*
+ * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses other
+ * than the initial greeting and any response to HELO or EHLO are
+ * prefaced with a status code as defined in RFC 3463.
+ */
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 Syntax: HELO hostname");
+ return (-1);
+ }
+ if (argc > 2)
+ collapse_args(argc - 1, argv + 1);
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+
+ /*
+ * XXX Sendmail compatibility: if a Milter rejects CONNECT, EHLO, or
+ * HELO, reply with 250 except in case of 421 (disconnect). The reply
+ * persists so it will apply to MAIL FROM and to other commands such as
+ * AUTH, STARTTLS, and VRFY.
+ */
+#define PUSH_STRING(old, curr, new) { char *old = (curr); (curr) = (new);
+#define POP_STRING(old, curr) (curr) = old; }
+
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0
+ && (err = milter_helo_event(state->milters, argv[1].strval, 0)) != 0) {
+ /* Log reject etc. with correct HELO information. */
+ PUSH_STRING(saved_helo, state->helo_name, argv[1].strval);
+ err = check_milter_reply(state, err);
+ POP_STRING(saved_helo, state->helo_name);
+ if (err != 0 && strncmp(err, "421", 3) == 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+ if (state->helo_name != 0)
+ helo_reset(state);
+ chat_reset(state, var_smtpd_hist_thrsh);
+ mail_reset(state);
+ rcpt_reset(state);
+ state->helo_name = mystrdup(printable(argv[1].strval, '?'));
+ neuter(state->helo_name, NEUTER_CHARACTERS, '?');
+ /* Downgrading the protocol name breaks the unauthorized pipelining test. */
+ if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0
+ && strcasecmp(state->protocol, MAIL_PROTO_SMTP) != 0) {
+ myfree(state->protocol);
+ state->protocol = mystrdup(MAIL_PROTO_SMTP);
+ }
+ smtpd_chat_reply(state, "250 %s", var_myhostname);
+ return (0);
+}
+
+/* cant_announce_feature - explain and terminate this session */
+
+static NORETURN cant_announce_feature(SMTPD_STATE *state, const char *feature)
+{
+ msg_warn("don't know if EHLO feature %s should be announced to %s",
+ feature, state->namaddr);
+ vstream_longjmp(state->client, SMTP_ERR_DATA);
+}
+
+/* cant_permit_command - explain and terminate this session */
+
+static NORETURN cant_permit_command(SMTPD_STATE *state, const char *command)
+{
+ msg_warn("don't know if command %s should be allowed from %s",
+ command, state->namaddr);
+ vstream_longjmp(state->client, SMTP_ERR_DATA);
+}
+
+/* ehlo_cmd - process EHLO command */
+
+static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err;
+ int discard_mask;
+ char **cpp;
+
+ /*
+ * XXX 2821 new feature: Section 4.1.4 specifies that a server must clear
+ * all buffers and reset the state exactly as if a RSET command had been
+ * issued.
+ *
+ * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses other
+ * than the initial greeting and any response to HELO or EHLO are
+ * prefaced with a status code as defined in RFC 3463.
+ */
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 Syntax: EHLO hostname");
+ return (-1);
+ }
+ if (argc > 2)
+ collapse_args(argc - 1, argv + 1);
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+
+ /*
+ * XXX Sendmail compatibility: if a Milter 5xx rejects CONNECT, EHLO, or
+ * HELO, reply with ENHANCEDSTATUSCODES except in case of immediate
+ * disconnect. The reply persists so it will apply to MAIL FROM and to
+ * other commands such as AUTH, STARTTLS, and VRFY.
+ */
+ err = 0;
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0
+ && (err = milter_helo_event(state->milters, argv[1].strval, 1)) != 0) {
+ /* Log reject etc. with correct HELO information. */
+ PUSH_STRING(saved_helo, state->helo_name, argv[1].strval);
+ err = check_milter_reply(state, err);
+ POP_STRING(saved_helo, state->helo_name);
+ if (err != 0 && strncmp(err, "421", 3) == 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+ if (state->helo_name != 0)
+ helo_reset(state);
+ chat_reset(state, var_smtpd_hist_thrsh);
+ mail_reset(state);
+ rcpt_reset(state);
+ state->helo_name = mystrdup(printable(argv[1].strval, '?'));
+ neuter(state->helo_name, NEUTER_CHARACTERS, '?');
+
+ /*
+ * XXX reject_unauth_pipelining depends on the following. If the user
+ * sends EHLO then we announce PIPELINING and we can't accuse them of
+ * using pipelining in places where it is allowed.
+ *
+ * XXX The reject_unauth_pipelining test needs to change and also account
+ * for mechanisms that disable PIPELINING selectively.
+ */
+ if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0) {
+ myfree(state->protocol);
+ state->protocol = mystrdup(MAIL_PROTO_ESMTP);
+ }
+
+ /*
+ * Build the EHLO response, producing no output until we know what to
+ * send - this simplifies exception handling. The CRLF record boundaries
+ * don't exist at this level in the code, so we represent multi-line
+ * output as an array of single-line responses.
+ */
+#define EHLO_APPEND(state, cmd) \
+ do { \
+ vstring_sprintf((state)->ehlo_buf, (cmd)); \
+ argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \
+ } while (0)
+
+#define EHLO_APPEND1(state, cmd, arg) \
+ do { \
+ vstring_sprintf((state)->ehlo_buf, (cmd), (arg)); \
+ argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \
+ } while (0)
+
+ /*
+ * XXX Sendmail compatibility: if a Milter 5XX rejects CONNECT, EHLO, or
+ * HELO, reply with ENHANCEDSTATUSCODES only. The reply persists so it
+ * will apply to MAIL FROM, but we currently don't have a proper
+ * mechanism to apply Milter rejects to AUTH, STARTTLS, VRFY, and other
+ * commands while still allowing HELO/EHLO.
+ */
+ discard_mask = state->ehlo_discard_mask;
+ if (err != 0 && err[0] == '5')
+ discard_mask |= ~EHLO_MASK_ENHANCEDSTATUSCODES;
+ if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0)
+ if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
+ msg_info("discarding EHLO keywords: %s", str_ehlo_mask(discard_mask));
+ if (ehlo_discard_maps && ehlo_discard_maps->error) {
+ msg_warn("don't know what EHLO features to announce to %s",
+ state->namaddr);
+ vstream_longjmp(state->client, SMTP_ERR_DATA);
+ }
+
+ /*
+ * These may still exist after a prior exception.
+ */
+ if (state->ehlo_argv == 0) {
+ state->ehlo_argv = argv_alloc(10);
+ state->ehlo_buf = vstring_alloc(10);
+ } else
+ argv_truncate(state->ehlo_argv, 0);
+
+ EHLO_APPEND1(state, "%s", var_myhostname);
+ if ((discard_mask & EHLO_MASK_PIPELINING) == 0)
+ EHLO_APPEND(state, "PIPELINING");
+ if ((discard_mask & EHLO_MASK_SIZE) == 0) {
+ if (ENFORCING_SIZE_LIMIT(var_message_limit))
+ EHLO_APPEND1(state, "SIZE %lu",
+ (unsigned long) var_message_limit); /* XXX */
+ else
+ EHLO_APPEND(state, "SIZE");
+ }
+ if ((discard_mask & EHLO_MASK_VRFY) == 0)
+ if (var_disable_vrfy_cmd == 0)
+ EHLO_APPEND(state, SMTPD_CMD_VRFY);
+ if ((discard_mask & EHLO_MASK_ETRN) == 0)
+ EHLO_APPEND(state, SMTPD_CMD_ETRN);
+#ifdef USE_TLS
+ if ((discard_mask & EHLO_MASK_STARTTLS) == 0)
+ if (var_smtpd_use_tls && (!state->tls_context))
+ EHLO_APPEND(state, SMTPD_CMD_STARTTLS);
+#endif
+#ifdef USE_SASL_AUTH
+#ifndef AUTH_CMD
+#define AUTH_CMD "AUTH"
+#endif
+ if ((discard_mask & EHLO_MASK_AUTH) == 0) {
+ if (smtpd_sasl_is_active(state) && !sasl_client_exception(state)) {
+ EHLO_APPEND1(state, "AUTH %s", state->sasl_mechanism_list);
+ if (var_broken_auth_clients)
+ EHLO_APPEND1(state, "AUTH=%s", state->sasl_mechanism_list);
+ } else if (sasl_exceptions_networks && sasl_exceptions_networks->error)
+ cant_announce_feature(state, AUTH_CMD);
+ }
+#define XCLIENT_LOGIN_KLUDGE " " XCLIENT_LOGIN
+#else
+#define XCLIENT_LOGIN_KLUDGE ""
+#endif
+ if ((discard_mask & EHLO_MASK_VERP) == 0) {
+ if (namadr_list_match(verp_clients, state->name, state->addr))
+ EHLO_APPEND(state, VERP_CMD);
+ else if (verp_clients && verp_clients->error)
+ cant_announce_feature(state, VERP_CMD);
+ }
+ /* XCLIENT must not override its own access control. */
+ if ((discard_mask & EHLO_MASK_XCLIENT) == 0) {
+ if (xclient_allowed)
+ EHLO_APPEND(state, XCLIENT_CMD
+ " " XCLIENT_NAME " " XCLIENT_ADDR
+ " " XCLIENT_PROTO " " XCLIENT_HELO
+ " " XCLIENT_REVERSE_NAME " " XCLIENT_PORT
+ XCLIENT_LOGIN_KLUDGE
+ " " XCLIENT_DESTADDR
+ " " XCLIENT_DESTPORT);
+ else if (xclient_hosts && xclient_hosts->error)
+ cant_announce_feature(state, XCLIENT_CMD);
+ }
+ if ((discard_mask & EHLO_MASK_XFORWARD) == 0) {
+ if (xforward_allowed)
+ EHLO_APPEND(state, XFORWARD_CMD
+ " " XFORWARD_NAME " " XFORWARD_ADDR
+ " " XFORWARD_PROTO " " XFORWARD_HELO
+ " " XFORWARD_DOMAIN " " XFORWARD_PORT
+ " " XFORWARD_IDENT);
+ else if (xforward_hosts && xforward_hosts->error)
+ cant_announce_feature(state, XFORWARD_CMD);
+ }
+ if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0)
+ EHLO_APPEND(state, "ENHANCEDSTATUSCODES");
+ if ((discard_mask & EHLO_MASK_8BITMIME) == 0)
+ EHLO_APPEND(state, "8BITMIME");
+ if ((discard_mask & EHLO_MASK_DSN) == 0)
+ EHLO_APPEND(state, "DSN");
+ if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0)
+ EHLO_APPEND(state, "SMTPUTF8");
+ if ((discard_mask & EHLO_MASK_CHUNKING) == 0)
+ EHLO_APPEND(state, "CHUNKING");
+
+ /*
+ * Send the reply.
+ */
+ for (cpp = state->ehlo_argv->argv; *cpp; cpp++)
+ smtpd_chat_reply(state, "250%c%s", cpp[1] ? '-' : ' ', *cpp);
+
+ /*
+ * Clean up.
+ */
+ argv_free(state->ehlo_argv);
+ state->ehlo_argv = 0;
+ vstring_free(state->ehlo_buf);
+ state->ehlo_buf = 0;
+
+ return (0);
+}
+
+/* helo_reset - reset HELO/EHLO command stuff */
+
+static void helo_reset(SMTPD_STATE *state)
+{
+ if (state->helo_name) {
+ myfree(state->helo_name);
+ state->helo_name = 0;
+ if (state->milters != 0)
+ milter_abort(state->milters);
+ }
+ if (state->ehlo_argv) {
+ argv_free(state->ehlo_argv);
+ state->ehlo_argv = 0;
+ }
+ if (state->ehlo_buf) {
+ vstring_free(state->ehlo_buf);
+ state->ehlo_buf = 0;
+ }
+}
+
+#ifdef USE_SASL_AUTH
+
+/* smtpd_sasl_auth_cmd_wrapper - smtpd_sasl_auth_cmd front-end */
+
+static int smtpd_sasl_auth_cmd_wrapper(SMTPD_STATE *state, int argc,
+ SMTPD_TOKEN *argv)
+{
+ int rate;
+
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && var_smtpd_cauth_limit > 0
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_auth(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_cauth_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("AUTH command rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ smtpd_chat_reply(state,
+ "450 4.7.1 Error: too many AUTH commands from %s",
+ state->addr);
+ return (-1);
+ }
+ return (smtpd_sasl_auth_cmd(state, argc, argv));
+}
+
+#endif
+
+/* mail_open_stream - open mail queue file or IPC stream */
+
+static int mail_open_stream(SMTPD_STATE *state)
+{
+
+ /*
+ * Connect to the before-queue filter when one is configured. The MAIL
+ * FROM and RCPT TO commands are forwarded as received (including DSN
+ * attributes), with the exception that the before-filter smtpd process
+ * handles all authentication, encryption, access control and relay
+ * control, and that the before-filter smtpd process does not forward
+ * blocked commands. If the after-filter smtp server does not support
+ * some of Postfix's ESMTP features, then they must be turned off in the
+ * before-filter smtpd process with the smtpd_discard_ehlo_keywords
+ * feature.
+ */
+ if (state->proxy_mail) {
+ if (smtpd_proxy_create(state, smtpd_proxy_opts, var_smtpd_proxy_filt,
+ var_smtpd_proxy_tmout, var_smtpd_proxy_ehlo,
+ state->proxy_mail) != 0) {
+ smtpd_chat_reply(state, "%s", STR(state->proxy->reply));
+ smtpd_proxy_free(state);
+ return (-1);
+ }
+ }
+
+ /*
+ * If running from the master or from inetd, connect to the cleanup
+ * service.
+ *
+ * XXX 2821: An SMTP server is not allowed to "clean up" mail except in the
+ * case of original submissions.
+ *
+ * We implement this by distinguishing between mail that we are willing to
+ * rewrite (the local rewrite context) and mail from elsewhere.
+ */
+ else if (SMTPD_STAND_ALONE(state) == 0) {
+ int cleanup_flags;
+
+ cleanup_flags = input_transp_cleanup(CLEANUP_FLAG_MASK_EXTERNAL,
+ smtpd_input_transp_mask)
+ | CLEANUP_FLAG_SMTP_REPLY;
+ if (state->flags & SMTPD_FLAG_SMTPUTF8)
+ cleanup_flags |= CLEANUP_FLAG_SMTPUTF8;
+ else
+ cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_SMTPD);
+ state->dest = mail_stream_service(MAIL_CLASS_PUBLIC,
+ var_cleanup_service);
+ if (state->dest == 0
+ || attr_print(state->dest->stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
+ ATTR_TYPE_END) != 0)
+ msg_fatal("unable to connect to the %s %s service",
+ MAIL_CLASS_PUBLIC, var_cleanup_service);
+ }
+
+ /*
+ * Otherwise, pipe the message through the privileged postdrop helper.
+ * XXX Make postdrop a manifest constant.
+ */
+ else {
+ char *postdrop_command;
+
+ postdrop_command = concatenate(var_command_dir, "/postdrop",
+ msg_verbose ? " -v" : (char *) 0, (char *) 0);
+ state->dest = mail_stream_command(postdrop_command);
+ if (state->dest == 0)
+ msg_fatal("unable to execute %s", postdrop_command);
+ myfree(postdrop_command);
+ }
+
+ /*
+ * Record the time of arrival, the SASL-related stuff if applicable, the
+ * sender envelope address, some session information, and some additional
+ * attributes.
+ *
+ * XXX Send Milter information first, because this will hang when cleanup
+ * goes into "throw away" mode. Also, cleanup needs to know early on
+ * whether or not it has to do its own SMTP event emulation.
+ *
+ * XXX At this point we send only dummy information to keep the cleanup
+ * server from using its non_smtpd_milters settings. We have to send
+ * up-to-date Milter information after DATA so that the cleanup server
+ * knows the actual Milter state.
+ */
+ if (state->dest) {
+ state->cleanup = state->dest->stream;
+ state->queue_id = mystrdup(state->dest->id);
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0)
+ /* Send place-holder smtpd_milters list. */
+ (void) milter_dummy(state->milters, state->cleanup);
+ rec_fprintf(state->cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
+ REC_TYPE_TIME_ARG(state->arrival_time));
+ if (*var_filter_xport)
+ rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport);
+ if (FORWARD_IDENT(state))
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_IDENT, FORWARD_IDENT(state));
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_RWR_CONTEXT, FORWARD_DOMAIN(state));
+#ifdef USE_SASL_AUTH
+ /* Make external authentication painless (e.g., XCLIENT). */
+ if (state->sasl_method)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_SASL_METHOD, state->sasl_method);
+ if (state->sasl_username)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_SASL_USERNAME, state->sasl_username);
+ if (state->sasl_sender)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_SASL_SENDER, state->sasl_sender);
+#endif
+
+ /*
+ * Record DSN related information that was received with the MAIL
+ * FROM command.
+ *
+ * RFC 3461 Section 5.2.1. If no ENVID parameter was included in the
+ * MAIL command when the message was received, the ENVID
+ * parameter MUST NOT be supplied when the message is relayed.
+ * Ditto for the RET parameter.
+ *
+ * In other words, we can't simply make up our default ENVID or RET
+ * values. We have to remember whether the client sent any.
+ *
+ * We store DSN information as named attribute records so that we
+ * don't have to pollute the queue file with records that are
+ * incompatible with past Postfix versions. Preferably, people
+ * should be able to back out from an upgrade without losing
+ * mail.
+ */
+ if (state->dsn_envid)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ENVID, state->dsn_envid);
+ if (state->dsn_ret)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_RET, state->dsn_ret);
+ }
+ rec_fputs(state->cleanup, REC_TYPE_FROM, state->sender);
+ if (state->encoding != 0)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ENCODING, state->encoding);
+
+ /*
+ * Store client attributes.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0) {
+
+ /*
+ * Attributes for logging, also used for XFORWARD.
+ *
+ * We store all client attributes, including ones with unknown
+ * values. Otherwise, an unknown client hostname would be treated
+ * as a non-existent hostname (i.e. local submission).
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_NAME, FORWARD_NAME(state));
+ /* XXX Note: state->rfc_addr, not state->addr. */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_ADDR, FORWARD_ADDR(state));
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_PORT, FORWARD_PORT(state));
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_ORIGIN, FORWARD_NAMADDR(state));
+ if (FORWARD_HELO(state))
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_HELO_NAME, FORWARD_HELO(state));
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_PROTO_NAME, FORWARD_PROTO(state));
+
+ /*
+ * Attributes with actual client information. These are used by
+ * the smtpd Milter client for policy decisions. Mail that is
+ * requeued with "postsuper -r" is not subject to processing by
+ * the cleanup Milter client, because a) it has already been
+ * filtered, and b) we don't have sufficient information to
+ * reproduce the exact same SMTP events and Sendmail macros that
+ * the smtpd Milter client received when the message originally
+ * arrived in Postfix.
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_NAME, state->name);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_REVERSE_CLIENT_NAME, state->reverse_name);
+ /* XXX Note: state->addr, not state->rfc_addr. */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_ADDR, state->addr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_PORT, state->port);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_SERVER_ADDR, state->dest_addr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_SERVER_PORT, state->dest_port);
+ if (state->helo_name)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_HELO_NAME, state->helo_name);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_PROTO_NAME, state->protocol);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%u",
+ MAIL_ATTR_ACT_CLIENT_AF, state->addr_family);
+
+ /*
+ * Don't send client certificate down the pipeline unless it is
+ * a) verified or b) just a fingerprint.
+ */
+ }
+ if (state->verp_delims)
+ rec_fputs(state->cleanup, REC_TYPE_VERP, state->verp_delims);
+ }
+
+ /*
+ * Log the queue ID with the message origin.
+ */
+#define PRINT_OR_NULL(cond, str) \
+ ((cond) ? (str) : "")
+#define PRINT2_OR_NULL(cond, name, value) \
+ PRINT_OR_NULL((cond), (name)), PRINT_OR_NULL((cond), (value))
+
+ msg_info("%s: client=%s%s%s%s%s%s%s%s%s%s%s",
+ (state->queue_id ? state->queue_id : "NOQUEUE"),
+ state->namaddr,
+#ifdef USE_SASL_AUTH
+ PRINT2_OR_NULL(state->sasl_method,
+ ", sasl_method=", state->sasl_method),
+ PRINT2_OR_NULL(state->sasl_username,
+ ", sasl_username=", state->sasl_username),
+ PRINT2_OR_NULL(state->sasl_sender,
+ ", sasl_sender=", state->sasl_sender),
+#else
+ "", "", "", "", "", "",
+#endif
+ /* Insert transaction TLS status here. */
+ PRINT2_OR_NULL(HAVE_FORWARDED_IDENT(state),
+ ", orig_queue_id=", FORWARD_IDENT(state)),
+ PRINT2_OR_NULL(HAVE_FORWARDED_CLIENT_ATTR(state),
+ ", orig_client=", FORWARD_NAMADDR(state)));
+ return (0);
+}
+
+/* extract_addr - extract address from rubble */
+
+static int extract_addr(SMTPD_STATE *state, SMTPD_TOKEN *arg,
+ int allow_empty_addr, int strict_rfc821,
+ int smtputf8)
+{
+ const char *myname = "extract_addr";
+ TOK822 *tree;
+ TOK822 *tp;
+ TOK822 *addr = 0;
+ int naddr;
+ int non_addr;
+ int err = 0;
+ char *junk = 0;
+ char *text;
+ char *colon;
+
+ /*
+ * Special case.
+ */
+#define PERMIT_EMPTY_ADDR 1
+#define REJECT_EMPTY_ADDR 0
+
+ /*
+ * Some mailers send RFC822-style address forms (with comments and such)
+ * in SMTP envelopes. We cannot blame users for this: the blame is with
+ * programmers violating the RFC, and with sendmail for being permissive.
+ *
+ * XXX The SMTP command tokenizer must leave the address in externalized
+ * (quoted) form, so that the address parser can correctly extract the
+ * address from surrounding junk.
+ *
+ * XXX We have only one address parser, written according to the rules of
+ * RFC 822. That standard differs subtly from RFC 821.
+ */
+ if (msg_verbose)
+ msg_info("%s: input: %s", myname, STR(arg->vstrval));
+ if (STR(arg->vstrval)[0] == '<'
+ && STR(arg->vstrval)[LEN(arg->vstrval) - 1] == '>') {
+ junk = text = mystrndup(STR(arg->vstrval) + 1, LEN(arg->vstrval) - 2);
+ } else
+ text = STR(arg->vstrval);
+
+ /*
+ * Truncate deprecated route address form.
+ */
+ if (*text == '@' && (colon = strchr(text, ':')) != 0)
+ text = colon + 1;
+ tree = tok822_parse(text);
+
+ if (junk)
+ myfree(junk);
+
+ /*
+ * Find trouble.
+ */
+ for (naddr = non_addr = 0, tp = tree; tp != 0; tp = tp->next) {
+ if (tp->type == TOK822_ADDR) {
+ addr = tp;
+ naddr += 1; /* count address forms */
+ } else if (tp->type == '<' || tp->type == '>') {
+ /* void */ ; /* ignore brackets */
+ } else {
+ non_addr += 1; /* count non-address forms */
+ }
+ }
+
+ /*
+ * Report trouble. XXX Should log a warning only if we are going to
+ * sleep+reject so that attackers can't flood our logfiles.
+ *
+ * XXX Unfortunately, the sleep-before-reject feature had to be abandoned
+ * (at least for small error counts) because servers were DOS-ing
+ * themselves when flooded by backscatter traffic.
+ */
+ if (naddr > 1
+ || (strict_rfc821 && (non_addr || *STR(arg->vstrval) != '<'))) {
+ msg_warn("Illegal address syntax from %s in %s command: %s",
+ state->namaddr, state->where,
+ printable(STR(arg->vstrval), '?'));
+ err = 1;
+ }
+
+ /*
+ * Don't overwrite the input with the extracted address. We need the
+ * original (external) form in case the client does not send ORCPT
+ * information; and error messages are more accurate if we log the
+ * unmodified form. We need the internal form for all other purposes.
+ */
+ if (addr)
+ tok822_internalize(state->addr_buf, addr->head, TOK822_STR_DEFL);
+ else
+ vstring_strcpy(state->addr_buf, "");
+
+ /*
+ * Report trouble. XXX Should log a warning only if we are going to
+ * sleep+reject so that attackers can't flood our logfiles. Log the
+ * original address.
+ */
+ if (err == 0)
+ if ((STR(state->addr_buf)[0] == 0 && !allow_empty_addr)
+ || (strict_rfc821 && STR(state->addr_buf)[0] == '@')
+ || (SMTPD_STAND_ALONE(state) == 0
+ && smtpd_check_addr(strcmp(state->where, SMTPD_CMD_MAIL) == 0 ?
+ state->recipient : state->sender,
+ STR(state->addr_buf), smtputf8) != 0)) {
+ msg_warn("Illegal address syntax from %s in %s command: %s",
+ state->namaddr, state->where,
+ printable(STR(arg->vstrval), '?'));
+ err = 1;
+ }
+
+ /*
+ * Cleanup.
+ */
+ tok822_free_tree(tree);
+ if (msg_verbose)
+ msg_info("%s: in: %s, result: %s",
+ myname, STR(arg->vstrval), STR(state->addr_buf));
+ return (err);
+}
+
+/* milter_argv - impedance adapter */
+
+static const char **milter_argv(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ int n;
+ ssize_t len = argc + 1;
+
+ if (state->milter_argc < len) {
+ if (state->milter_argc > 0)
+ state->milter_argv = (const char **)
+ myrealloc((void *) state->milter_argv,
+ sizeof(const char *) * len);
+ else
+ state->milter_argv = (const char **)
+ mymalloc(sizeof(const char *) * len);
+ state->milter_argc = len;
+ }
+ for (n = 0; n < argc; n++)
+ state->milter_argv[n] = argv[n].strval;
+ state->milter_argv[n] = 0;
+ return (state->milter_argv);
+}
+
+/* mail_cmd - process MAIL command */
+
+static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err;
+ int narg;
+ char *arg;
+ char *verp_delims = 0;
+ int rate;
+ int dsn_envid = 0;
+
+ state->flags &= ~SMTPD_FLAG_SMTPUTF8;
+ state->encoding = 0;
+ state->dsn_ret = 0;
+
+ /*
+ * Sanity checks.
+ *
+ * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a
+ * command in which invalid character codes have been employed, and for
+ * which there are no other reasons for rejection, MUST reject that
+ * command with a 501 response. Postfix attempts to be 8-bit clean.
+ */
+ if (var_helo_required && state->helo_name == 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "503 5.5.1 Error: send HELO/EHLO first");
+ return (-1);
+ }
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: nested MAIL command");
+ return (-1);
+ }
+ /* Don't accept MAIL after out-of-order BDAT. */
+ if (SMTPD_PROCESSING_BDAT(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: MAIL after BDAT");
+ return (-1);
+ }
+ if (argc < 3
+ || strcasecmp(argv[1].strval, "from:") != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: MAIL FROM:<address>");
+ return (-1);
+ }
+
+ /*
+ * XXX The client event count/rate control must be consistent in its use
+ * of client address information in connect and disconnect events. For
+ * now we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && var_smtpd_cmail_limit > 0
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_mail(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_cmail_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "450 4.7.1 Error: too much mail from %s",
+ state->addr);
+ msg_warn("Message delivery request rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ return (-1);
+ }
+ if (argv[2].tokval == SMTPD_TOK_ERROR) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.7 Bad sender address syntax");
+ return (-1);
+ }
+
+ /*
+ * XXX The sender address comes first, but the optional SMTPUTF8
+ * parameter determines what address syntax is permitted. We must process
+ * this parameter early.
+ */
+ if (var_smtputf8_enable
+ && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0) {
+ for (narg = 3; narg < argc; narg++) {
+ arg = argv[narg].strval;
+ if (strcasecmp(arg, "SMTPUTF8") == 0) { /* RFC 6531 */
+ /* Fix 20161206: allow UTF8 in smtpd_sender_restrictions. */
+ state->flags |= SMTPD_FLAG_SMTPUTF8;
+ break;
+ }
+ }
+ }
+ if (extract_addr(state, argv + 2, PERMIT_EMPTY_ADDR,
+ var_strict_rfc821_env,
+ state->flags & SMTPD_FLAG_SMTPUTF8) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.7 Bad sender address syntax");
+ return (-1);
+ }
+ for (narg = 3; narg < argc; narg++) {
+ arg = argv[narg].strval;
+ if (strcasecmp(arg, "BODY=8BITMIME") == 0) { /* RFC 1652 */
+ state->encoding = MAIL_ATTR_ENC_8BIT;
+ } else if (strcasecmp(arg, "BODY=7BIT") == 0) { /* RFC 1652 */
+ state->encoding = MAIL_ATTR_ENC_7BIT;
+ } else if (strncasecmp(arg, "SIZE=", 5) == 0) { /* RFC 1870 */
+ /* Reject non-numeric size. */
+ if (!alldig(arg + 5)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad message size syntax");
+ return (-1);
+ }
+ /* Reject size overflow. */
+ if ((state->msg_size = off_cvt_string(arg + 5)) < 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "552 5.3.4 Message size exceeds file system imposed limit");
+ return (-1);
+ }
+ } else if (var_smtputf8_enable
+ && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0
+ && strcasecmp(arg, "SMTPUTF8") == 0) { /* RFC 6531 */
+ /* Already processed early. */ ;
+#ifdef USE_SASL_AUTH
+ } else if (strncasecmp(arg, "AUTH=", 5) == 0) {
+ if ((err = smtpd_sasl_mail_opt(state, arg + 5)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+#endif
+ } else if (namadr_list_match(verp_clients, state->name, state->addr)
+ && strncasecmp(arg, VERP_CMD, VERP_CMD_LEN) == 0
+ && (arg[VERP_CMD_LEN] == '=' || arg[VERP_CMD_LEN] == 0)) {
+ if (arg[VERP_CMD_LEN] == 0) {
+ verp_delims = var_verp_delims;
+ } else {
+ verp_delims = arg + VERP_CMD_LEN + 1;
+ if (verp_delims_verify(verp_delims) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state,
+ "501 5.5.4 Error: %s needs two characters from %s",
+ VERP_CMD, var_verp_filter);
+ return (-1);
+ }
+ }
+ } else if (strncasecmp(arg, "RET=", 4) == 0) { /* RFC 3461 */
+ /* Sanitized on input. */
+ if (state->ehlo_discard_mask & EHLO_MASK_DSN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled");
+ return (-1);
+ }
+ if (state->dsn_ret
+ || (state->dsn_ret = dsn_ret_code(arg + 4)) == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state,
+ "501 5.5.4 Bad RET parameter syntax");
+ return (-1);
+ }
+ } else if (strncasecmp(arg, "ENVID=", 6) == 0) { /* RFC 3461 */
+ /* Sanitized by bounce server. */
+ if (state->ehlo_discard_mask & EHLO_MASK_DSN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled");
+ return (-1);
+ }
+ if (dsn_envid
+ || xtext_unquote(state->dsn_buf, arg + 6) == 0
+ || !allprint(STR(state->dsn_buf))) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad ENVID parameter syntax");
+ return (-1);
+ }
+ dsn_envid = 1;
+ } else {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "555 5.5.4 Unsupported option: %s", arg);
+ return (-1);
+ }
+ }
+ /* Fix 20161205: show the envelope sender in reject logging. */
+ PUSH_STRING(saved_sender, state->sender, STR(state->addr_buf));
+ err = smtpd_check_size(state, state->msg_size);
+ POP_STRING(saved_sender, state->sender);
+ if (err != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (verp_delims && STR(state->addr_buf)[0] == 0) {
+ smtpd_chat_reply(state, "503 5.5.4 Error: %s requires non-null sender",
+ VERP_CMD);
+ return (-1);
+ }
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ const char *verify_sender;
+
+ /*
+ * XXX Don't reject the address when we're probed with our own
+ * address verification sender address. Otherwise, some timeout or
+ * some UCE block may result in mutual negative caching, making it
+ * painful to get the mail through. Unfortunately we still have to
+ * send the address to the Milters otherwise they may bail out with a
+ * "missing recipient" protocol error.
+ */
+ verify_sender = valid_verify_sender_addr(STR(state->addr_buf));
+ if (verify_sender != 0)
+ vstring_strcpy(state->addr_buf, verify_sender);
+ }
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_mail(state, STR(state->addr_buf))) != 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) {
+ state->flags |= SMTPD_FLAG_NEED_MILTER_ABORT;
+ PUSH_STRING(saved_sender, state->sender, STR(state->addr_buf));
+ err = milter_mail_event(state->milters,
+ milter_argv(state, argc - 2, argv + 2));
+ if (err != 0) {
+ /* Log reject etc. with correct sender information. */
+ err = check_milter_reply(state, err);
+ }
+ POP_STRING(saved_sender, state->sender);
+ if (err != 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ err = smtpd_check_rewrite(state);
+ if (err != 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+
+ /*
+ * Historically, Postfix does not forbid 8-bit envelope localparts.
+ * Changing this would be a compatibility break. That can't happen in the
+ * foreseeable future.
+ */
+ if ((var_strict_smtputf8 || warn_compat_break_smtputf8_enable)
+ && (state->flags & SMTPD_FLAG_SMTPUTF8) == 0
+ && *STR(state->addr_buf) && !allascii(STR(state->addr_buf))) {
+ if (var_strict_smtputf8) {
+ smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to "
+ "send unicode address");
+ return (-1);
+ }
+
+ /*
+ * Not: #ifndef NO_EAI. They must configure SMTPUTF8_ENABLE=no if a
+ * warning message is logged, so that they don't suddenly start to
+ * lose mail after Postfix is built with EAI support.
+ */
+ if (warn_compat_break_smtputf8_enable)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPUTF8_ENABLE "=no to accept non-ASCII sender "
+ "address \"%s\" from %s", STR(state->addr_buf),
+ state->namaddr);
+ }
+
+ /*
+ * Check the queue file space, if applicable. The optional before-filter
+ * speed-adjust buffers use disk space. However, we don't know if they
+ * compete for storage space with the after-filter queue, so we can't
+ * simply bump up the free space requirement to 2.5 * message_size_limit.
+ */
+ if (!USE_SMTPD_PROXY(state)
+ || (smtpd_proxy_opts & SMTPD_PROXY_FLAG_SPEED_ADJUST)) {
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_queue(state)) != 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+
+ /*
+ * No more early returns. The mail transaction is in progress.
+ */
+ GETTIMEOFDAY(&state->arrival_time);
+ state->sender = mystrdup(STR(state->addr_buf));
+ vstring_sprintf(state->instance, "%x.%lx.%lx.%x",
+ var_pid, (unsigned long) state->arrival_time.tv_sec,
+ (unsigned long) state->arrival_time.tv_usec, state->seqno++);
+ if (verp_delims)
+ state->verp_delims = mystrdup(verp_delims);
+ if (dsn_envid)
+ state->dsn_envid = mystrdup(STR(state->dsn_buf));
+ if (USE_SMTPD_PROXY(state))
+ state->proxy_mail = mystrdup(STR(state->buffer));
+ if (var_smtpd_delay_open == 0 && mail_open_stream(state) < 0) {
+ /* XXX Reset access map side effects. */
+ mail_reset(state);
+ return (-1);
+ }
+ smtpd_chat_reply(state, "250 2.1.0 Ok");
+ return (0);
+}
+
+/* mail_reset - reset MAIL command stuff */
+
+static void mail_reset(SMTPD_STATE *state)
+{
+ state->msg_size = 0;
+ state->act_size = 0;
+ state->flags &= SMTPD_MASK_MAIL_KEEP;
+
+ /*
+ * Unceremoniously close the pipe to the cleanup service. The cleanup
+ * service will delete the queue file when it detects a premature
+ * end-of-file condition on input.
+ */
+ if (state->cleanup != 0) {
+ mail_stream_cleanup(state->dest);
+ state->dest = 0;
+ state->cleanup = 0;
+ }
+ state->err = 0;
+ if (state->queue_id != 0) {
+ myfree(state->queue_id);
+ state->queue_id = 0;
+ }
+ if (state->sender) {
+ myfree(state->sender);
+ state->sender = 0;
+ }
+ /* WeiYu Wu: need to undo milter_mail_event() state change. */
+ if (state->flags & SMTPD_FLAG_NEED_MILTER_ABORT) {
+ milter_abort(state->milters);
+ state->flags &= ~SMTPD_FLAG_NEED_MILTER_ABORT;
+ }
+ if (state->verp_delims) {
+ myfree(state->verp_delims);
+ state->verp_delims = 0;
+ }
+ if (state->proxy_mail) {
+ myfree(state->proxy_mail);
+ state->proxy_mail = 0;
+ }
+ if (state->saved_filter) {
+ myfree(state->saved_filter);
+ state->saved_filter = 0;
+ }
+ if (state->saved_redirect) {
+ myfree(state->saved_redirect);
+ state->saved_redirect = 0;
+ }
+ if (state->saved_bcc) {
+ argv_free(state->saved_bcc);
+ state->saved_bcc = 0;
+ }
+ state->saved_flags = 0;
+#ifdef DELAY_ACTION
+ state->saved_delay = 0;
+#endif
+#ifdef USE_SASL_AUTH
+ if (state->sasl_sender)
+ smtpd_sasl_mail_reset(state);
+#endif
+ state->discard = 0;
+ VSTRING_RESET(state->instance);
+ VSTRING_TERMINATE(state->instance);
+
+ if (state->proxy)
+ smtpd_proxy_free(state);
+ if (state->xforward.flags)
+ smtpd_xforward_reset(state);
+ if (state->prepend)
+ state->prepend = argv_free(state->prepend);
+ if (state->dsn_envid) {
+ myfree(state->dsn_envid);
+ state->dsn_envid = 0;
+ }
+ if (state->milter_argv) {
+ myfree((void *) state->milter_argv);
+ state->milter_argv = 0;
+ state->milter_argc = 0;
+ }
+
+ /*
+ * BDAT.
+ */
+ state->bdat_state = SMTPD_BDAT_STAT_NONE;
+ if (state->bdat_get_stream) {
+ (void) vstream_fclose(state->bdat_get_stream);
+ state->bdat_get_stream = 0;
+ }
+ if (state->bdat_get_buffer)
+ VSTRING_RESET(state->bdat_get_buffer);
+}
+
+/* rcpt_cmd - process RCPT TO command */
+
+static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ SMTPD_PROXY *proxy;
+ const char *err;
+ int narg;
+ char *arg;
+ int rate;
+ const char *dsn_orcpt_addr = 0;
+ ssize_t dsn_orcpt_addr_len = 0;
+ const char *dsn_orcpt_type = 0;
+ int dsn_notify = 0;
+ const char *coded_addr;
+ const char *milter_err;
+
+ /*
+ * Sanity checks.
+ *
+ * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a
+ * command in which invalid character codes have been employed, and for
+ * which there are no other reasons for rejection, MUST reject that
+ * command with a 501 response. So much for the principle of "be liberal
+ * in what you accept, be strict in what you send".
+ */
+ if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: need MAIL command");
+ return (-1);
+ }
+ /* Don't accept RCPT after BDAT. */
+ if (SMTPD_PROCESSING_BDAT(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: RCPT after BDAT");
+ return (-1);
+ }
+ if (argc < 3
+ || strcasecmp(argv[1].strval, "to:") != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: RCPT TO:<address>");
+ return (-1);
+ }
+
+ /*
+ * XXX The client event count/rate control must be consistent in its use
+ * of client address information in connect and disconnect events. For
+ * now we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && var_smtpd_crcpt_limit > 0
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_rcpt(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_crcpt_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Recipient address rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ smtpd_chat_reply(state, "450 4.7.1 Error: too many recipients from %s",
+ state->addr);
+ return (-1);
+ }
+ if (argv[2].tokval == SMTPD_TOK_ERROR) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax");
+ return (-1);
+ }
+ if (extract_addr(state, argv + 2, REJECT_EMPTY_ADDR, var_strict_rfc821_env,
+ state->flags & SMTPD_FLAG_SMTPUTF8) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax");
+ return (-1);
+ }
+ for (narg = 3; narg < argc; narg++) {
+ arg = argv[narg].strval;
+ if (strncasecmp(arg, "NOTIFY=", 7) == 0) { /* RFC 3461 */
+ /* Sanitized on input. */
+ if (state->ehlo_discard_mask & EHLO_MASK_DSN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled");
+ return (-1);
+ }
+ if (dsn_notify || (dsn_notify = dsn_notify_mask(arg + 7)) == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state,
+ "501 5.5.4 Error: Bad NOTIFY parameter syntax");
+ return (-1);
+ }
+ } else if (strncasecmp(arg, "ORCPT=", 6) == 0) { /* RFC 3461 */
+ /* Sanitized by bounce server. */
+ if (state->ehlo_discard_mask & EHLO_MASK_DSN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled");
+ return (-1);
+ }
+ vstring_strcpy(state->dsn_orcpt_buf, arg + 6);
+ if (dsn_orcpt_addr
+ || (coded_addr = split_at(STR(state->dsn_orcpt_buf), ';')) == 0
+ || *(dsn_orcpt_type = STR(state->dsn_orcpt_buf)) == 0
+ || (strcasecmp(dsn_orcpt_type, "utf-8") == 0 ?
+ uxtext_unquote(state->dsn_buf, coded_addr) == 0 :
+ xtext_unquote(state->dsn_buf, coded_addr) == 0)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state,
+ "501 5.5.4 Error: Bad ORCPT parameter syntax");
+ return (-1);
+ }
+ dsn_orcpt_addr = STR(state->dsn_buf);
+ dsn_orcpt_addr_len = LEN(state->dsn_buf);
+ } else {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "555 5.5.4 Unsupported option: %s", arg);
+ return (-1);
+ }
+ }
+ if (var_smtpd_rcpt_limit && state->rcpt_count >= var_smtpd_rcpt_limit) {
+ smtpd_chat_reply(state, "452 4.5.3 Error: too many recipients");
+ if (state->rcpt_overshoot++ < var_smtpd_rcpt_overlim)
+ return (0);
+ state->error_mask |= MAIL_ERROR_POLICY;
+ return (-1);
+ }
+
+ /*
+ * Historically, Postfix does not forbid 8-bit envelope localparts.
+ * Changing this would be a compatibility break. That can't happen in the
+ * foreseeable future.
+ */
+ if ((var_strict_smtputf8 || warn_compat_break_smtputf8_enable)
+ && (state->flags & SMTPD_FLAG_SMTPUTF8) == 0
+ && *STR(state->addr_buf) && !allascii(STR(state->addr_buf))) {
+ if (var_strict_smtputf8) {
+ smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to "
+ "send unicode address");
+ return (-1);
+ }
+
+ /*
+ * Not: #ifndef NO_EAI. They must configure SMTPUTF8_ENABLE=no if a
+ * warning message is logged, so that they don't suddenly start to
+ * lose mail after Postfix is built with EAI support.
+ */
+ if (warn_compat_break_smtputf8_enable)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPUTF8_ENABLE "=no to accept non-ASCII recipient "
+ "address \"%s\" from %s", STR(state->addr_buf),
+ state->namaddr);
+ }
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ const char *verify_sender;
+
+ /*
+ * XXX Don't reject the address when we're probed with our own
+ * address verification sender address. Otherwise, some timeout or
+ * some UCE block may result in mutual negative caching, making it
+ * painful to get the mail through. Unfortunately we still have to
+ * send the address to the Milters otherwise they may bail out with a
+ * "missing recipient" protocol error.
+ */
+ verify_sender = valid_verify_sender_addr(STR(state->addr_buf));
+ if (verify_sender != 0) {
+ vstring_strcpy(state->addr_buf, verify_sender);
+ err = 0;
+ } else {
+ err = smtpd_check_rcpt(state, STR(state->addr_buf));
+ }
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) {
+ PUSH_STRING(saved_rcpt, state->recipient, STR(state->addr_buf));
+ state->milter_reject_text = err;
+ milter_err = milter_rcpt_event(state->milters,
+ err == 0 ? MILTER_FLAG_NONE :
+ MILTER_FLAG_WANT_RCPT_REJ,
+ milter_argv(state, argc - 2, argv + 2));
+ if (err == 0 && milter_err != 0) {
+ /* Log reject etc. with correct recipient information. */
+ err = check_milter_reply(state, milter_err);
+ }
+ POP_STRING(saved_rcpt, state->recipient);
+ }
+ if (err != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+
+ /*
+ * Don't access the proxy, queue file, or queue file writer process until
+ * we have a valid recipient address.
+ */
+ if (state->proxy == 0 && state->cleanup == 0 && mail_open_stream(state) < 0)
+ return (-1);
+
+ /*
+ * Proxy the recipient. OK, so we lied. If the real-time proxy rejects
+ * the recipient then we can have a proxy connection without having
+ * accepted a recipient.
+ */
+ proxy = state->proxy;
+ if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_OK,
+ "%s", STR(state->buffer)) != 0) {
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ return (-1);
+ }
+
+ /*
+ * Store the recipient. Remember the first one.
+ *
+ * Flush recipients to maintain a stiffer coupling with the next stage and
+ * to better utilize parallelism.
+ *
+ * RFC 3461 Section 5.2.1: If the NOTIFY parameter was not supplied for a
+ * recipient when the message was received, the NOTIFY parameter MUST NOT
+ * be supplied for that recipient when the message is relayed.
+ *
+ * In other words, we can't simply make up our default NOTIFY value. We have
+ * to remember whether the client sent any.
+ *
+ * RFC 3461 Section 5.2.1: If no ORCPT parameter was present when the
+ * message was received, an ORCPT parameter MAY be added to the RCPT
+ * command when the message is relayed. If an ORCPT parameter is added
+ * by the relaying MTA, it MUST contain the recipient address from the
+ * RCPT command used when the message was received by that MTA.
+ *
+ * In other words, it is OK to make up our own DSN original recipient when
+ * the client didn't send one. Although the RFC mentions mail relaying
+ * only, we also make up our own original recipient for the purpose of
+ * final delivery. For now, we do this here, rather than on the fly.
+ *
+ * XXX We use REC_TYPE_ATTR for DSN-related recipient attributes even though
+ * 1) REC_TYPE_ATTR is not meant for multiple instances of the same named
+ * attribute, and 2) mixing REC_TYPE_ATTR with REC_TYPE_(not attr)
+ * requires that we map attributes with rec_attr_map() in order to
+ * simplify the recipient record processing loops in the cleanup and qmgr
+ * servers.
+ *
+ * Another possibility, yet to be explored, is to leave the additional
+ * recipient information in the queue file and just pass queue file
+ * offsets along with the delivery request. This is a trade off between
+ * memory allocation versus numeric conversion overhead.
+ *
+ * Since we have no record grouping mechanism, all recipient-specific
+ * parameters must be sent to the cleanup server before the actual
+ * recipient address.
+ */
+ state->rcpt_count++;
+ if (state->recipient == 0)
+ state->recipient = mystrdup(STR(state->addr_buf));
+ if (state->cleanup) {
+ /* Note: RFC(2)821 externalized address! */
+ if (dsn_orcpt_addr == 0) {
+ dsn_orcpt_type = "rfc822";
+ dsn_orcpt_addr = argv[2].strval;
+ dsn_orcpt_addr_len = strlen(argv[2].strval);
+ if (dsn_orcpt_addr[0] == '<'
+ && dsn_orcpt_addr[dsn_orcpt_addr_len - 1] == '>') {
+ dsn_orcpt_addr += 1;
+ dsn_orcpt_addr_len -= 2;
+ }
+ }
+ if (dsn_notify)
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, dsn_notify);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s;%.*s",
+ MAIL_ATTR_DSN_ORCPT, dsn_orcpt_type,
+ (int) dsn_orcpt_addr_len, dsn_orcpt_addr);
+ rec_fputs(state->cleanup, REC_TYPE_RCPT, STR(state->addr_buf));
+ vstream_fflush(state->cleanup);
+ }
+ smtpd_chat_reply(state, "250 2.1.5 Ok");
+ return (0);
+}
+
+/* rcpt_reset - reset RCPT stuff */
+
+static void rcpt_reset(SMTPD_STATE *state)
+{
+ if (state->recipient) {
+ myfree(state->recipient);
+ state->recipient = 0;
+ }
+ state->rcpt_count = 0;
+ /* XXX Must flush the command history. */
+ state->rcpt_overshoot = 0;
+}
+
+#if 0
+
+/* rfc2047_comment_encode - encode comment string */
+
+static VSTRING *rfc2047_comment_encode(const char *str, const char *charset)
+{
+ VSTRING *buf = vstring_alloc(30);
+ const unsigned char *cp;
+ int ch;
+
+ /*
+ * XXX This is problematic code.
+ *
+ * XXX Most of the RFC 2047 "especials" are not special in RFC*822 comments,
+ * but we encode them anyway to avoid complaints.
+ *
+ * XXX In Received: header comments we enclose peer and issuer common names
+ * with "" quotes (inherited from the Lutz Jaenicke patch). This is the
+ * cause of several quirks.
+ *
+ * 1) We encode text that contains the " character, even though that
+ * character is not special for RFC*822 comments.
+ *
+ * 2) We ignore the recommended limit of 75 characters per encoded word,
+ * because long comments look ugly when folded in-between quotes.
+ *
+ * 3) We encode the enclosing quotes, to avoid producing invalid encoded
+ * words. Microsoft abuses RFC 2047 encoding with attachment names, but
+ * we have no information on what decoders do with malformed encoding in
+ * comments. This means the comments are Jaenicke-compatible only after
+ * decoding.
+ */
+#define ESPECIALS "()<>@,;:\"/[]?.=" /* Special in RFC 2047 */
+#define QSPECIALS "_" ESPECIALS /* Special in RFC 2047 'Q' */
+#define CSPECIALS "\\\"()" /* Special in our comments */
+
+ /* Don't encode if not needed. */
+ for (cp = (unsigned char *) str; /* see below */ ; ++cp) {
+ if ((ch = *cp) == 0) {
+ vstring_sprintf(buf, "\"%s\"", str);
+ return (buf);
+ }
+ if (!ISPRINT(ch) || strchr(CSPECIALS, ch))
+ break;
+ }
+
+ /*
+ * Use quoted-printable (like) encoding with spaces mapped to underscore.
+ */
+ vstring_sprintf(buf, "=?%s?Q?=%02X", charset, '"');
+ for (cp = (unsigned char *) str; (ch = *cp) != 0; ++cp) {
+ if (!ISPRINT(ch) || strchr(QSPECIALS CSPECIALS, ch)) {
+ vstring_sprintf_append(buf, "=%02X", ch);
+ } else if (ch == ' ') {
+ VSTRING_ADDCH(buf, '_');
+ } else {
+ VSTRING_ADDCH(buf, ch);
+ }
+ }
+ vstring_sprintf_append(buf, "=%02X?=", '"');
+ return (buf);
+}
+
+#endif
+
+/* comment_sanitize - clean up comment string */
+
+static void comment_sanitize(VSTRING *comment_string)
+{
+ unsigned char *cp;
+ int ch;
+ int pc;
+
+ /*
+ * Postfix Received: headers can be configured to include a comment with
+ * the CN (CommonName) of the peer and its issuer, or the login name of a
+ * SASL authenticated user. To avoid problems with RFC 822 etc. syntax,
+ * we limit this information to printable ASCII text, and neutralize
+ * characters that affect comment parsing: the backslash and unbalanced
+ * parentheses.
+ */
+ for (pc = 0, cp = (unsigned char *) STR(comment_string); (ch = *cp) != 0; cp++) {
+ if (!ISASCII(ch) || !ISPRINT(ch) || ch == '\\') {
+ *cp = '?';
+ } else if (ch == '(') {
+ pc++;
+ } else if (ch == ')') {
+ if (pc > 0)
+ pc--;
+ else
+ *cp = '?';
+ }
+ }
+ while (pc-- > 0)
+ VSTRING_ADDCH(comment_string, ')');
+ VSTRING_TERMINATE(comment_string);
+}
+
+static void common_pre_message_handling(SMTPD_STATE *state,
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+ int (*out_fprintf) (VSTREAM *, int, const char *,...),
+ VSTREAM *out_stream, int out_error);
+static void receive_data_message(SMTPD_STATE *state,
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+ int (*out_fprintf) (VSTREAM *, int, const char *,...),
+ VSTREAM *out_stream, int out_error);
+static int common_post_message_handling(SMTPD_STATE *state);
+
+/* data_cmd - process DATA command */
+
+static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+ SMTPD_PROXY *proxy;
+ const char *err;
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t);
+ int (*out_fprintf) (VSTREAM *, int, const char *,...);
+ VSTREAM *out_stream;
+ int out_error;
+
+ /*
+ * Sanity checks. With ESMTP command pipelining the client can send DATA
+ * before all recipients are rejected, so don't report that as a protocol
+ * error.
+ */
+ if (SMTPD_PROCESSING_BDAT(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: DATA after BDAT");
+ return (-1);
+ }
+ if (state->rcpt_count == 0) {
+ if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: need RCPT command");
+ } else {
+ smtpd_chat_reply(state, "554 5.5.1 Error: no valid recipients");
+ }
+ return (-1);
+ }
+ if (argc != 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: DATA");
+ return (-1);
+ }
+ if (SMTPD_STAND_ALONE(state) == 0 && (err = smtpd_check_data(state)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0
+ && (err = milter_data_event(state->milters)) != 0
+ && (err = check_milter_reply(state, err)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ proxy = state->proxy;
+ if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE,
+ "%s", STR(state->buffer)) != 0) {
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ return (-1);
+ }
+
+ /*
+ * One level of indirection to choose between normal or proxied
+ * operation. We want to avoid massive code duplication within tons of
+ * if-else clauses.
+ */
+ if (proxy) {
+ out_stream = proxy->stream;
+ out_record = proxy->rec_put;
+ out_fprintf = proxy->rec_fprintf;
+ out_error = CLEANUP_STAT_PROXY;
+ } else {
+ out_stream = state->cleanup;
+ out_record = rec_put;
+ out_fprintf = rec_fprintf;
+ out_error = CLEANUP_STAT_WRITE;
+ }
+ common_pre_message_handling(state, out_record, out_fprintf,
+ out_stream, out_error);
+ smtpd_chat_reply(state, "354 End data with <CR><LF>.<CR><LF>");
+ state->where = SMTPD_AFTER_DATA;
+ receive_data_message(state, out_record, out_fprintf, out_stream, out_error);
+ return common_post_message_handling(state);
+}
+
+/* common_pre_message_handling - finish envelope and open message segment */
+
+static void common_pre_message_handling(SMTPD_STATE *state,
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+ int (*out_fprintf) (VSTREAM *, int, const char *,...),
+ VSTREAM *out_stream,
+ int out_error)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ char **cpp;
+ const char *rfc3848_sess;
+ const char *rfc3848_auth;
+ const char *with_protocol = (state->flags & SMTPD_FLAG_SMTPUTF8) ?
+ "UTF8SMTP" : state->protocol;
+
+#ifdef USE_TLS
+ VSTRING *peer_CN;
+ VSTRING *issuer_CN;
+
+#endif
+#ifdef USE_SASL_AUTH
+ VSTRING *username;
+
+#endif
+
+ /*
+ * Flush out a first batch of access table actions that are delegated to
+ * the cleanup server, and that may trigger before we accept the first
+ * valid recipient. There will be more after end-of-data.
+ *
+ * Terminate the message envelope segment. Start the message content
+ * segment, and prepend our own Received: header. If there is only one
+ * recipient, list the recipient address.
+ */
+ if (state->cleanup) {
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0)
+ /* Send actual smtpd_milters list. */
+ (void) milter_send(state->milters, state->cleanup);
+ if (state->saved_flags)
+ rec_fprintf(state->cleanup, REC_TYPE_FLGS, "%d",
+ state->saved_flags);
+ }
+ rec_fputs(state->cleanup, REC_TYPE_MESG, "");
+ }
+
+ /*
+ * PREPEND message headers above our own Received: header.
+ */
+ if (state->prepend)
+ for (cpp = state->prepend->argv; *cpp; cpp++)
+ out_fprintf(out_stream, REC_TYPE_NORM, "%s", *cpp);
+
+ /*
+ * Suppress our own Received: header in the unlikely case that we are an
+ * intermediate proxy.
+ */
+ if (!proxy || state->xforward.flags == 0) {
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "Received: from %s (%s [%s])",
+ state->helo_name ? state->helo_name : state->name,
+ state->name, state->rfc_addr);
+
+#define VSTRING_STRDUP(s) vstring_strcpy(vstring_alloc(strlen(s) + 1), (s))
+
+#ifdef USE_TLS
+ if (var_smtpd_tls_received_header && state->tls_context) {
+ int cont = 0;
+
+ vstring_sprintf(state->buffer,
+ "\t(using %s with cipher %s (%d/%d bits)",
+ state->tls_context->protocol,
+ state->tls_context->cipher_name,
+ state->tls_context->cipher_usebits,
+ state->tls_context->cipher_algbits);
+ if (state->tls_context->kex_name && *state->tls_context->kex_name) {
+ out_record(out_stream, REC_TYPE_NORM, STR(state->buffer),
+ LEN(state->buffer));
+ vstring_sprintf(state->buffer, "\t key-exchange %s",
+ state->tls_context->kex_name);
+ if (state->tls_context->kex_curve
+ && *state->tls_context->kex_curve)
+ vstring_sprintf_append(state->buffer, " (%s)",
+ state->tls_context->kex_curve);
+ else if (state->tls_context->kex_bits > 0)
+ vstring_sprintf_append(state->buffer, " (%d bits)",
+ state->tls_context->kex_bits);
+ cont = 1;
+ }
+ if (state->tls_context->srvr_sig_name
+ && *state->tls_context->srvr_sig_name) {
+ if (cont) {
+ vstring_sprintf_append(state->buffer, " server-signature %s",
+ state->tls_context->srvr_sig_name);
+ } else {
+ out_record(out_stream, REC_TYPE_NORM, STR(state->buffer),
+ LEN(state->buffer));
+ vstring_sprintf(state->buffer, "\t server-signature %s",
+ state->tls_context->srvr_sig_name);
+ }
+ if (state->tls_context->srvr_sig_curve
+ && *state->tls_context->srvr_sig_curve)
+ vstring_sprintf_append(state->buffer, " (%s)",
+ state->tls_context->srvr_sig_curve);
+ else if (state->tls_context->srvr_sig_bits > 0)
+ vstring_sprintf_append(state->buffer, " (%d bits)",
+ state->tls_context->srvr_sig_bits);
+ if (state->tls_context->srvr_sig_dgst
+ && *state->tls_context->srvr_sig_dgst)
+ vstring_sprintf_append(state->buffer, " server-digest %s",
+ state->tls_context->srvr_sig_dgst);
+ }
+ if (state->tls_context->clnt_sig_name
+ && *state->tls_context->clnt_sig_name) {
+ out_record(out_stream, REC_TYPE_NORM, STR(state->buffer),
+ LEN(state->buffer));
+ vstring_sprintf(state->buffer, "\t client-signature %s",
+ state->tls_context->clnt_sig_name);
+ if (state->tls_context->clnt_sig_curve
+ && *state->tls_context->clnt_sig_curve)
+ vstring_sprintf_append(state->buffer, " (%s)",
+ state->tls_context->clnt_sig_curve);
+ else if (state->tls_context->clnt_sig_bits > 0)
+ vstring_sprintf_append(state->buffer, " (%d bits)",
+ state->tls_context->clnt_sig_bits);
+ if (state->tls_context->clnt_sig_dgst
+ && *state->tls_context->clnt_sig_dgst)
+ vstring_sprintf_append(state->buffer, " client-digest %s",
+ state->tls_context->clnt_sig_dgst);
+ }
+ out_fprintf(out_stream, REC_TYPE_NORM, "%s)", STR(state->buffer));
+ if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ peer_CN = VSTRING_STRDUP(state->tls_context->peer_CN);
+ comment_sanitize(peer_CN);
+ issuer_CN = VSTRING_STRDUP(state->tls_context->issuer_CN ?
+ state->tls_context->issuer_CN : "");
+ comment_sanitize(issuer_CN);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(Client CN \"%s\", Issuer \"%s\" (%s))",
+ STR(peer_CN), STR(issuer_CN),
+ TLS_CERT_IS_TRUSTED(state->tls_context) ?
+ "verified OK" : "not verified");
+ vstring_free(issuer_CN);
+ vstring_free(peer_CN);
+ } else if (var_smtpd_tls_ask_ccert)
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(Client did not present a certificate)");
+ else
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(No client certificate requested)");
+ }
+ /* RFC 3848 is defined for ESMTP only. */
+ if (state->tls_context != 0
+ && strcmp(state->protocol, MAIL_PROTO_ESMTP) == 0)
+ rfc3848_sess = "S";
+ else
+#endif
+ rfc3848_sess = "";
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_auth_hdr && state->sasl_username) {
+ username = VSTRING_STRDUP(state->sasl_username);
+ comment_sanitize(username);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(Authenticated sender: %s)", STR(username));
+ vstring_free(username);
+ }
+ /* RFC 3848 is defined for ESMTP only. */
+ if (state->sasl_username
+ && strcmp(state->protocol, MAIL_PROTO_ESMTP) == 0)
+ rfc3848_auth = "A";
+ else
+#endif
+ rfc3848_auth = "";
+ if (state->rcpt_count == 1 && state->recipient) {
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ state->cleanup ? "\tby %s (%s) with %s%s%s id %s" :
+ "\tby %s (%s) with %s%s%s",
+ var_myhostname, var_mail_name,
+ with_protocol, rfc3848_sess,
+ rfc3848_auth, state->queue_id);
+ quote_822_local(state->buffer, state->recipient);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\tfor <%s>; %s", STR(state->buffer),
+ mail_date(state->arrival_time.tv_sec));
+ } else {
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ state->cleanup ? "\tby %s (%s) with %s%s%s id %s;" :
+ "\tby %s (%s) with %s%s%s;",
+ var_myhostname, var_mail_name,
+ with_protocol, rfc3848_sess,
+ rfc3848_auth, state->queue_id);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t%s", mail_date(state->arrival_time.tv_sec));
+ }
+#ifdef RECEIVED_ENVELOPE_FROM
+ quote_822_local(state->buffer, state->sender);
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(envelope-from %s)", STR(state->buffer));
+#endif
+ }
+}
+
+/* receive_data_message - finish envelope and open message segment */
+
+static void receive_data_message(SMTPD_STATE *state,
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+ int (*out_fprintf) (VSTREAM *, int, const char *,...),
+ VSTREAM *out_stream,
+ int out_error)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ char *start;
+ int len;
+ int curr_rec_type;
+ int prev_rec_type;
+ int first = 1;
+ int prev_got_bare_lf = 0;
+
+ /*
+ * Copy the message content. If the cleanup process has a problem, keep
+ * reading until the remote stops sending, then complain. Produce typed
+ * records from the SMTP stream so we can handle data that spans buffers.
+ *
+ * XXX Force an empty record when the queue file content begins with
+ * whitespace, so that it won't be considered as being part of our own
+ * Received: header. What an ugly Kluge.
+ *
+ * XXX Deal with UNIX-style From_ lines at the start of message content
+ * because sendmail permits it.
+ */
+ for (prev_rec_type = 0; /* void */ ; prev_rec_type = curr_rec_type,
+ prev_got_bare_lf = smtp_got_bare_lf) {
+ if (smtp_get(state->buffer, state->client, var_line_limit,
+ SMTP_GET_FLAG_NONE) == '\n')
+ curr_rec_type = REC_TYPE_NORM;
+ else
+ curr_rec_type = REC_TYPE_CONT;
+ if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf))
+ state->err |= CLEANUP_STAT_BARE_LF;
+ start = vstring_str(state->buffer);
+ len = VSTRING_LEN(state->buffer);
+ if (first) {
+ if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) {
+ out_fprintf(out_stream, curr_rec_type,
+ "X-Mailbox-Line: %s", start);
+ continue;
+ }
+ first = 0;
+ if (len > 0 && IS_SPACE_TAB(start[0]))
+ out_record(out_stream, REC_TYPE_NORM, "", 0);
+ }
+ if (prev_rec_type != REC_TYPE_CONT && *start == '.') {
+ if (len == 1 && IS_BARE_LF_WANT_STD_EOD(smtp_detect_bare_lf)
+ && (smtp_got_bare_lf || prev_got_bare_lf))
+ /* Do not store or send to proxy filter. */
+ continue;
+ if (proxy == 0 ? (++start, --len) == 0 : len == 1)
+ break;
+ }
+ if (state->err == CLEANUP_STAT_OK) {
+ if (ENFORCING_SIZE_LIMIT(var_message_limit)
+ && var_message_limit - state->act_size < len + 2) {
+ state->err = CLEANUP_STAT_SIZE;
+ msg_warn("%s: queue file size limit exceeded",
+ state->queue_id ? state->queue_id : "NOQUEUE");
+ } else {
+ state->act_size += len + 2;
+ if (out_record(out_stream, curr_rec_type, start, len) < 0)
+ state->err = out_error;
+ }
+ }
+ }
+ state->where = SMTPD_AFTER_EOM;
+}
+
+/* common_post_message_handling - commit message or report error */
+
+static int common_post_message_handling(SMTPD_STATE *state)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ const char *err;
+ VSTRING *why = 0;
+ int saved_err;
+ const CLEANUP_STAT_DETAIL *detail;
+
+#define IS_SMTP_REJECT(s) \
+ (((s)[0] == '4' || (s)[0] == '5') \
+ && ISDIGIT((s)[1]) && ISDIGIT((s)[2]) \
+ && ((s)[3] == '\0' || (s)[3] == ' ' || (s)[3] == '-'))
+
+ if (state->err == CLEANUP_STAT_OK
+ && SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_eod(state)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ if (proxy) {
+ smtpd_proxy_close(state);
+ } else {
+ mail_stream_cleanup(state->dest);
+ state->dest = 0;
+ state->cleanup = 0;
+ }
+ return (-1);
+ }
+
+ /*
+ * Send the end of DATA and finish the proxy connection. Set the
+ * CLEANUP_STAT_PROXY error flag in case of trouble.
+ */
+ if (proxy) {
+ if (state->err == CLEANUP_STAT_OK) {
+ (void) proxy->cmd(state, SMTPD_PROX_WANT_ANY, ".");
+ if (state->err == CLEANUP_STAT_OK &&
+ *STR(proxy->reply) != '2')
+ state->err = CLEANUP_STAT_CONT;
+ }
+ }
+
+ /*
+ * Flush out access table actions that are delegated to the cleanup
+ * server. There is similar code at the beginning of the DATA command.
+ *
+ * Send the end-of-segment markers and finish the queue file record stream.
+ */
+ else {
+ if (state->err == CLEANUP_STAT_OK) {
+ rec_fputs(state->cleanup, REC_TYPE_XTRA, "");
+ if (state->saved_filter)
+ rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s",
+ state->saved_filter);
+ if (state->saved_redirect)
+ rec_fprintf(state->cleanup, REC_TYPE_RDR, "%s",
+ state->saved_redirect);
+ if (state->saved_bcc) {
+ char **cpp;
+
+ for (cpp = state->saved_bcc->argv; *cpp; cpp++) {
+ rec_fprintf(state->cleanup, REC_TYPE_RCPT, "%s",
+ *cpp);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, DSN_NOTIFY_NEVER);
+ }
+ }
+ if (state->saved_flags)
+ rec_fprintf(state->cleanup, REC_TYPE_FLGS, "%d",
+ state->saved_flags);
+#ifdef DELAY_ACTION
+ if (state->saved_delay)
+ rec_fprintf(state->cleanup, REC_TYPE_DELAY, "%d",
+ state->saved_delay);
+#endif
+ if (vstream_ferror(state->cleanup))
+ state->err = CLEANUP_STAT_WRITE;
+ }
+ if (state->err == CLEANUP_STAT_OK)
+ if (rec_fputs(state->cleanup, REC_TYPE_END, "") < 0
+ || vstream_fflush(state->cleanup))
+ state->err = CLEANUP_STAT_WRITE;
+ if (state->err == 0) {
+ why = vstring_alloc(10);
+ state->err = mail_stream_finish(state->dest, why);
+ if (IS_SMTP_REJECT(STR(why)))
+ printable_except(STR(why), ' ', "\r\n");
+ else
+ printable(STR(why), ' ');
+ } else
+ mail_stream_cleanup(state->dest);
+ state->dest = 0;
+ state->cleanup = 0;
+ }
+
+ /*
+ * XXX If we lose the cleanup server while it is editing a queue file,
+ * the Postfix SMTP server will be out of sync with Milter applications.
+ * Sending an ABORT to the Milters is not sufficient to restore
+ * synchronization, because there may be any number of Milter replies
+ * already in flight. Destroying and recreating the Milters (and faking
+ * the connect and ehlo events) is too much trouble for testing and
+ * maintenance. Workaround: force the Postfix SMTP server to hang up with
+ * a 421 response in the rare case that the cleanup server breaks AND
+ * that the remote SMTP client continues the session after end-of-data.
+ *
+ * XXX Should use something other than CLEANUP_STAT_WRITE when we lose
+ * contact with the cleanup server. This requires changes to the
+ * mail_stream module and its users (smtpd, qmqpd, perhaps sendmail).
+ *
+ * XXX See exception below in code that overrides state->access_denied for
+ * compliance with RFC 2821 Sec 3.1.
+ */
+ if (state->milters != 0 && (state->err & CLEANUP_STAT_WRITE) != 0)
+ state->access_denied = mystrdup("421 4.3.0 Mail system error");
+
+ /*
+ * Handle any errors. One message may suffer from multiple errors, so
+ * complain only about the most severe error. Forgive any previous client
+ * errors when a message was received successfully.
+ *
+ * See also: qmqpd.c
+ */
+ if (state->err == CLEANUP_STAT_OK) {
+ state->error_count = 0;
+ state->error_mask = 0;
+ state->junk_cmds = 0;
+ if (proxy)
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ else if (SMTPD_PROCESSING_BDAT(state))
+ smtpd_chat_reply(state,
+ "250 2.0.0 Ok: %ld bytes queued as %s",
+ (long) state->act_size, state->queue_id);
+ else
+ smtpd_chat_reply(state,
+ "250 2.0.0 Ok: queued as %s", state->queue_id);
+ } else if ((state->err & CLEANUP_STAT_BARE_LF) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ log_whatsup(state, "reject", "bare <LF> received");
+ smtpd_chat_reply(state, "%d 5.5.2 %s Error: bare <LF> received",
+ var_smtpd_forbid_bare_lf_code, var_myhostname);
+ } else if (why && IS_SMTP_REJECT(STR(why))) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", STR(why));
+ } else if ((state->err & CLEANUP_STAT_DEFER) != 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ detail = cleanup_stat_detail(CLEANUP_STAT_DEFER);
+ if (why && LEN(why) > 0) {
+ /* Allow address-specific DSN status in header/body_checks. */
+ smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why));
+ } else {
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ }
+ } else if ((state->err & CLEANUP_STAT_BAD) != 0) {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_BAD);
+ smtpd_chat_reply(state, "%d %s Error: internal error %d",
+ detail->smtp, detail->dsn, state->err);
+ } else if ((state->err & CLEANUP_STAT_SIZE) != 0) {
+ state->error_mask |= MAIL_ERROR_BOUNCE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_SIZE);
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ } else if ((state->err & CLEANUP_STAT_HOPS) != 0) {
+ state->error_mask |= MAIL_ERROR_BOUNCE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_HOPS);
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ } else if ((state->err & CLEANUP_STAT_CONT) != 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
+ if (proxy) {
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ } else if (why && LEN(why) > 0) {
+ /* Allow address-specific DSN status in header/body_checks. */
+ smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why));
+ } else {
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ }
+ } else if ((state->err & CLEANUP_STAT_WRITE) != 0) {
+ state->error_mask |= MAIL_ERROR_RESOURCE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_WRITE);
+ smtpd_chat_reply(state, "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+ } else if ((state->err & CLEANUP_STAT_PROXY) != 0) {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ } else {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ detail = cleanup_stat_detail(CLEANUP_STAT_BAD);
+ smtpd_chat_reply(state, "%d %s Error: internal error %d",
+ detail->smtp, detail->dsn, state->err);
+ }
+
+ /*
+ * By popular command: the proxy's end-of-data reply.
+ */
+ if (proxy)
+ msg_info("proxy-%s: %s: %s;%s",
+ (state->err == CLEANUP_STAT_OK) ? "accept" : "reject",
+ state->where, STR(proxy->reply), smtpd_whatsup(state));
+
+ /*
+ * Cleanup. The client may send another MAIL command.
+ */
+ saved_err = state->err;
+ chat_reset(state, var_smtpd_hist_thrsh);
+ mail_reset(state);
+ rcpt_reset(state);
+ if (why)
+ vstring_free(why);
+ return (saved_err);
+}
+
+/* skip_bdat - skip content and respond to BDAT error */
+
+static int skip_bdat(SMTPD_STATE *state, off_t chunk_size,
+ bool final_chunk, const char *format,...)
+{
+ va_list ap;
+ off_t done;
+ off_t len;
+
+ /*
+ * Read and discard content from the remote SMTP client. TODO: drop the
+ * connection in case of overload.
+ */
+ for (done = 0; done < chunk_size; done += len) {
+ if ((len = chunk_size - done) > VSTREAM_BUFSIZE)
+ len = VSTREAM_BUFSIZE;
+ smtp_fread_buf(state->buffer, len, state->client);
+ }
+
+ /*
+ * Send the response to the remote SMTP client.
+ */
+ va_start(ap, format);
+ vsmtpd_chat_reply(state, format, ap);
+ va_end(ap);
+
+ /*
+ * Reset state, or drop subsequent BDAT payloads until BDAT LAST or RSET.
+ */
+ if (final_chunk)
+ mail_reset(state);
+ else
+ state->bdat_state = SMTPD_BDAT_STAT_ERROR;
+ return (-1);
+}
+
+/* bdat_cmd - process BDAT command */
+
+static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ SMTPD_PROXY *proxy;
+ const char *err;
+ off_t chunk_size;
+ bool final_chunk;
+ off_t done;
+ off_t read_len;
+ char *start;
+ int len;
+ int curr_rec_type;
+ int (*out_record) (VSTREAM *, int, const char *, ssize_t);
+ int (*out_fprintf) (VSTREAM *, int, const char *,...);
+ VSTREAM *out_stream;
+ int out_error;
+
+ /*
+ * Hang up if the BDAT command is disabled. The next input would be raw
+ * message content and that would trigger lots of command errors.
+ */
+ if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "521 5.5.1 Error: command not implemented");
+ return (-1);
+ }
+
+ /*
+ * Hang up if the BDAT command is malformed. The next input would be raw
+ * message content and that would trigger lots of command errors.
+ */
+ if (argc < 2 || argc > 3 || !alldig(argv[1].strval)
+ || (chunk_size = off_cvt_string(argv[1].strval)) < 0
+ || ((final_chunk = (argc == 3))
+ && strcasecmp(argv[2].strval, "LAST") != 0)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ msg_warn("%s: malformed BDAT command syntax from %s: %.100s",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ state->namaddr, printable(vstring_str(state->buffer), '?'));
+ smtpd_chat_reply(state, "521 5.5.4 Syntax: BDAT count [LAST]");
+ return (-1);
+ }
+
+ /*
+ * Block abuse involving empty chunks (alternatively, we could count
+ * "BDAT 0" as a "NOOP", but then we would have to refactor the code that
+ * enforces the junk command limit). Clients that send a message as a
+ * sequence of "BDAT 1" should not be a problem: the Postfix BDAT
+ * implementation should be efficient enough to handle that.
+ */
+ if (chunk_size == 0 && !final_chunk) {
+ msg_warn("%s: null BDAT request from %s",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ state->namaddr);
+ return skip_bdat(state, chunk_size, final_chunk,
+ "551 5.7.1 Null BDAT request");
+ }
+
+ /*
+ * BDAT commands may be pipelined within a MAIL transaction. After a BDAT
+ * request fails, keep accepting BDAT requests and skipping BDAT payloads
+ * to maintain synchronization with the remote SMTP client, until the
+ * client sends BDAT LAST or RSET.
+ */
+ if (state->bdat_state == SMTPD_BDAT_STAT_ERROR)
+ return skip_bdat(state, chunk_size, final_chunk,
+ "551 5.0.0 Discarded %ld bytes after earlier error",
+ (long) chunk_size);
+
+ /*
+ * Special handling for the first BDAT command in a MAIL transaction,
+ * treating it as a kind of "DATA" command for the purpose of policy
+ * evaluation.
+ */
+ if (!SMTPD_PROCESSING_BDAT(state)) {
+
+ /*
+ * With ESMTP command pipelining a client may send BDAT before the
+ * server has replied to all RCPT commands. For this reason we cannot
+ * treat BDAT without valid recipients as a protocol error. Worse,
+ * RFC 3030 does not discuss the role of BDAT commands in RFC 2920
+ * command groups (batches of commands that may be sent without
+ * waiting for a response to each individual command). Therefore we
+ * have to allow for clients that pipeline the entire SMTP session
+ * after EHLO, including multiple MAIL transactions.
+ */
+ if (state->rcpt_count == 0) {
+ if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
+ /* TODO: maybe remove this from the DATA and BDAT handlers. */
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ return skip_bdat(state, chunk_size, final_chunk,
+ "503 5.5.1 Error: need RCPT command");
+ } else {
+ return skip_bdat(state, chunk_size, final_chunk,
+ "554 5.5.1 Error: no valid recipients");
+ }
+ }
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_data(state)) != 0) {
+ return skip_bdat(state, chunk_size, final_chunk, "%s", err);
+ }
+ if (state->milters != 0
+ && (state->saved_flags & MILTER_SKIP_FLAGS) == 0
+ && (err = milter_data_event(state->milters)) != 0
+ && (err = check_milter_reply(state, err)) != 0) {
+ return skip_bdat(state, chunk_size, final_chunk, "%s", err);
+ }
+ proxy = state->proxy;
+ if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE,
+ SMTPD_CMD_DATA) != 0) {
+ return skip_bdat(state, chunk_size, final_chunk,
+ "%s", STR(proxy->reply));
+ }
+ }
+ /* Block too large chunks. */
+ if (ENFORCING_SIZE_LIMIT(var_message_limit)
+ && state->act_size > var_message_limit - chunk_size) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("%s: BDAT request from %s exceeds message size limit",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ state->namaddr);
+ return skip_bdat(state, chunk_size, final_chunk,
+ "552 5.3.4 Chunk exceeds message size limit");
+ }
+
+ /*
+ * One level of indirection to choose between normal or proxied
+ * operation. We want to avoid massive code duplication within tons of
+ * if-else clauses. TODO: store this in its own data structure, or in
+ * SMTPD_STATE.
+ */
+ proxy = state->proxy;
+ if (proxy) {
+ out_stream = proxy->stream;
+ out_record = proxy->rec_put;
+ out_fprintf = proxy->rec_fprintf;
+ out_error = CLEANUP_STAT_PROXY;
+ } else {
+ out_stream = state->cleanup;
+ out_record = rec_put;
+ out_fprintf = rec_fprintf;
+ out_error = CLEANUP_STAT_WRITE;
+ }
+ if (!SMTPD_PROCESSING_BDAT(state)) {
+ common_pre_message_handling(state, out_record, out_fprintf,
+ out_stream, out_error);
+ if (state->bdat_get_buffer == 0)
+ state->bdat_get_buffer = vstring_alloc(VSTREAM_BUFSIZE);
+ else
+ VSTRING_RESET(state->bdat_get_buffer);
+ state->bdat_prev_rec_type = 0;
+ }
+ state->bdat_state = SMTPD_BDAT_STAT_OK;
+ state->where = SMTPD_AFTER_BDAT;
+
+ /*
+ * Copy the message content. If the cleanup process has a problem, keep
+ * reading until the remote stops sending, then complain. Produce typed
+ * records from the SMTP stream so we can handle data that spans buffers.
+ */
+
+ /*
+ * Instead of reading the entire BDAT chunk into memory, read the chunk
+ * one fragment at a time. The loops below always make one iteration, to
+ * avoid code duplication for the "BDAT 0 LAST" case (empty chunk).
+ */
+ done = 0;
+ do {
+
+ /*
+ * Do not skip the smtp_fread_buf() call if read_len == 0. We still
+ * need the side effects which include resetting the buffer write
+ * position. Skipping the call would invalidate the buffer state.
+ *
+ * Caution: smtp_fread_buf() will long jump after EOF or timeout.
+ */
+ if ((read_len = chunk_size - done) > VSTREAM_BUFSIZE)
+ read_len = VSTREAM_BUFSIZE;
+ smtp_fread_buf(state->buffer, read_len, state->client);
+ state->bdat_get_stream = vstream_memreopen(
+ state->bdat_get_stream, state->buffer, O_RDONLY);
+
+ /*
+ * Read lines from the fragment. The last line may continue in the
+ * next fragment, or in the next chunk.
+ */
+ do {
+ if (smtp_get_noexcept(state->bdat_get_buffer,
+ state->bdat_get_stream,
+ var_line_limit,
+ SMTP_GET_FLAG_APPEND) == '\n') {
+ /* Stopped at end-of-line. */
+ curr_rec_type = REC_TYPE_NORM;
+ } else if (!vstream_feof(state->bdat_get_stream)) {
+ /* Stopped at var_line_limit. */
+ curr_rec_type = REC_TYPE_CONT;
+ } else if (VSTRING_LEN(state->bdat_get_buffer) > 0
+ && final_chunk && read_len == chunk_size - done) {
+ /* Stopped at final chunk end; handle missing end-of-line. */
+ curr_rec_type = REC_TYPE_NORM;
+ } else {
+ /* Stopped at fragment end; empty buffer or not at chunk end. */
+ /* Skip the out_record() and VSTRING_RESET() calls below. */
+ break;
+ }
+ if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf))
+ state->err |= CLEANUP_STAT_BARE_LF;
+ start = vstring_str(state->bdat_get_buffer);
+ len = VSTRING_LEN(state->bdat_get_buffer);
+ if (state->err == CLEANUP_STAT_OK) {
+ if (ENFORCING_SIZE_LIMIT(var_message_limit)
+ && var_message_limit - state->act_size < len + 2) {
+ state->err = CLEANUP_STAT_SIZE;
+ msg_warn("%s: queue file size limit exceeded",
+ state->queue_id ? state->queue_id : "NOQUEUE");
+ } else {
+ state->act_size += len + 2;
+ if (*start == '.' && proxy != 0
+ && state->bdat_prev_rec_type != REC_TYPE_CONT)
+ if (out_record(out_stream, REC_TYPE_CONT, ".", 1) < 0)
+ state->err = out_error;
+ if (state->err == CLEANUP_STAT_OK
+ && out_record(out_stream, curr_rec_type,
+ vstring_str(state->bdat_get_buffer),
+ VSTRING_LEN(state->bdat_get_buffer)) < 0)
+ state->err = out_error;
+ }
+ }
+ VSTRING_RESET(state->bdat_get_buffer);
+ state->bdat_prev_rec_type = curr_rec_type;
+ } while (!vstream_feof(state->bdat_get_stream));
+ done += read_len;
+ } while (done < chunk_size);
+
+ /*
+ * Special handling for BDAT LAST (successful or unsuccessful).
+ */
+ if (final_chunk) {
+ state->where = SMTPD_AFTER_EOM;
+ return common_post_message_handling(state);
+ }
+
+ /*
+ * Unsuccessful non-final BDAT command. common_post_message_handling()
+ * resets all MAIL transaction state including BDAT state. To avoid
+ * useless error messages due to pipelined BDAT commands, enter the
+ * SMTPD_BDAT_STAT_ERROR state to accept BDAT commands and skip BDAT
+ * payloads.
+ */
+ else if (state->err != CLEANUP_STAT_OK) {
+ /* NOT: state->where = SMTPD_AFTER_EOM; */
+ (void) common_post_message_handling(state);
+ state->bdat_state = SMTPD_BDAT_STAT_ERROR;
+ return (-1);
+ }
+
+ /*
+ * Successful non-final BDAT command.
+ */
+ else {
+ smtpd_chat_reply(state, "250 2.0.0 Ok: %ld bytes", (long) chunk_size);
+ return (0);
+ }
+}
+
+/* rset_cmd - process RSET */
+
+static int rset_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+
+ /*
+ * Sanity checks.
+ */
+ if (argc != 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: RSET");
+ return (-1);
+ }
+
+ /*
+ * Restore state to right after HELO/EHLO command.
+ */
+ chat_reset(state, var_smtpd_hist_thrsh);
+ mail_reset(state);
+ rcpt_reset(state);
+ smtpd_chat_reply(state, "250 2.0.0 Ok");
+ return (0);
+}
+
+/* noop_cmd - process NOOP */
+
+static int noop_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+
+ /*
+ * XXX 2821 incompatibility: Section 4.1.1.9 says that NOOP can have a
+ * parameter string which is to be ignored. NOOP instructions with
+ * parameters? Go figure.
+ *
+ * RFC 2821 violates RFC 821, which says that NOOP takes no parameters.
+ */
+#ifdef RFC821_SYNTAX
+
+ /*
+ * Sanity checks.
+ */
+ if (argc != 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: NOOP");
+ return (-1);
+ }
+#endif
+ smtpd_chat_reply(state, "250 2.0.0 Ok");
+ return (0);
+}
+
+/* vrfy_cmd - process VRFY */
+
+static int vrfy_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err = 0;
+ int rate;
+ int smtputf8 = 0;
+ int saved_flags;
+
+ /*
+ * The SMTP standard (RFC 821) disallows unquoted special characters in
+ * the VRFY argument. Common practice violates the standard, however.
+ * Postfix accommodates common practice where it violates the standard.
+ *
+ * XXX Impedance mismatch! The SMTP command tokenizer preserves quoting,
+ * whereas the recipient restrictions checks expect unquoted (internal)
+ * address forms. Therefore we must parse out the address, or we must
+ * stop doing recipient restriction checks and lose the opportunity to
+ * say "user unknown" at the SMTP port.
+ *
+ * XXX 2821 incompatibility and brain damage: Section 4.5.1 requires that
+ * VRFY is implemented. RFC 821 specifies that VRFY is optional. It gets
+ * even worse: section 3.5.3 says that a 502 (command recognized but not
+ * implemented) reply is not fully compliant.
+ *
+ * Thus, an RFC 2821 compliant implementation cannot refuse to supply
+ * information in reply to VRFY queries. That is simply bogus. The only
+ * reply we could supply is a generic 252 reply. This causes spammers to
+ * add tons of bogus addresses to their mailing lists (spam harvesting by
+ * trying out large lists of potential recipient names with VRFY).
+ */
+#define SLOPPY 0
+
+ if (var_disable_vrfy_cmd) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "502 5.5.1 VRFY command is disabled");
+ return (-1);
+ }
+ /* Fix 20140707: handle missing address. */
+ if (var_smtputf8_enable
+ && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0
+ && argc > 1 && strcasecmp(argv[argc - 1].strval, "SMTPUTF8") == 0) {
+ argc--; /* RFC 6531 */
+ smtputf8 = 1;
+ }
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: VRFY address%s",
+ var_smtputf8_enable ? " [SMTPUTF8]" : "");
+ return (-1);
+ }
+
+ /*
+ * XXX The client event count/rate control must be consistent in its use
+ * of client address information in connect and disconnect events. For
+ * now we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && var_smtpd_crcpt_limit > 0
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_rcpt(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_crcpt_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Recipient address rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ smtpd_chat_reply(state, "450 4.7.1 Error: too many recipients from %s",
+ state->addr);
+ return (-1);
+ }
+ if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0
+ && (err[0] == '5' || err[0] == '4')) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (argc > 2)
+ collapse_args(argc - 1, argv + 1);
+ if (extract_addr(state, argv + 1, REJECT_EMPTY_ADDR, SLOPPY, smtputf8) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax");
+ return (-1);
+ }
+ /* Fix 20140707: Check the VRFY command. */
+ if (smtputf8 == 0 && var_strict_smtputf8) {
+ if (*STR(state->addr_buf) && !allascii(STR(state->addr_buf))) {
+ mail_reset(state);
+ smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to send unicode address");
+ return (-1);
+ }
+ }
+ /* Use state->addr_buf, with the unquoted result from extract_addr() */
+ if (SMTPD_STAND_ALONE(state) == 0) {
+ /* Fix 20161206: allow UTF8 in smtpd_recipient_restrictions. */
+ saved_flags = state->flags;
+ if (smtputf8)
+ state->flags |= SMTPD_FLAG_SMTPUTF8;
+ err = smtpd_check_rcpt(state, STR(state->addr_buf));
+ state->flags = saved_flags;
+ if (err != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ }
+
+ /*
+ * XXX 2821 new feature: Section 3.5.1 requires that the VRFY response is
+ * either "full name <user@domain>" or "user@domain". Postfix replies
+ * with the string that was provided by the client, whether or not it is
+ * in fully qualified domain form and the address is in <>.
+ *
+ * Reply code 250 is reserved for the case where the address is verified;
+ * reply code 252 should be used when no definitive certainty exists.
+ */
+ smtpd_chat_reply(state, "252 2.0.0 %s", argv[1].strval);
+ return (0);
+}
+
+/* etrn_cmd - process ETRN command */
+
+static int etrn_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ const char *err;
+
+ /*
+ * Sanity checks.
+ */
+ if (var_helo_required && state->helo_name == 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "503 Error: send HELO/EHLO first");
+ return (-1);
+ }
+ if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0
+ && (err[0] == '5' || err[0] == '4')) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 Error: MAIL transaction in progress");
+ return (-1);
+ }
+ if (argc != 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "500 Syntax: ETRN domain");
+ return (-1);
+ }
+ if (argv[1].strval[0] == '@' || argv[1].strval[0] == '#')
+ argv[1].strval++;
+
+ /*
+ * As an extension to RFC 1985 we also allow an RFC 2821 address literal
+ * enclosed in [].
+ *
+ * XXX There does not appear to be an ETRN parameter to indicate that the
+ * domain name is UTF-8.
+ */
+ if (!valid_hostname(argv[1].strval, DONT_GRIPE)
+ && !valid_mailhost_literal(argv[1].strval, DONT_GRIPE)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 Error: invalid parameter syntax");
+ return (-1);
+ }
+
+ /*
+ * XXX The implementation borrows heavily from the code that implements
+ * UCE restrictions. These typically return 450 or 550 when a request is
+ * rejected. RFC 1985 requires that 459 be sent when the server refuses
+ * to perform the request.
+ */
+ if (SMTPD_STAND_ALONE(state)) {
+ msg_warn("do not use ETRN in \"sendmail -bs\" mode");
+ smtpd_chat_reply(state, "458 Unable to queue messages");
+ return (-1);
+ }
+ if ((err = smtpd_check_etrn(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ switch (flush_send_site(argv[1].strval)) {
+ case FLUSH_STAT_OK:
+ smtpd_chat_reply(state, "250 Queuing started");
+ return (0);
+ case FLUSH_STAT_DENY:
+ msg_warn("reject: ETRN %.100s... from %s",
+ argv[1].strval, state->namaddr);
+ smtpd_chat_reply(state, "459 <%s>: service unavailable",
+ argv[1].strval);
+ return (-1);
+ case FLUSH_STAT_BAD:
+ msg_warn("bad ETRN %.100s... from %s", argv[1].strval, state->namaddr);
+ smtpd_chat_reply(state, "458 Unable to queue messages");
+ return (-1);
+ default:
+ msg_warn("unable to talk to fast flush service");
+ smtpd_chat_reply(state, "458 Unable to queue messages");
+ return (-1);
+ }
+}
+
+/* quit_cmd - process QUIT command */
+
+static int quit_cmd(SMTPD_STATE *state, int unused_argc, SMTPD_TOKEN *unused_argv)
+{
+ int out_pending = vstream_bufstat(state->client, VSTREAM_BST_OUT_PEND);
+
+ /*
+ * Don't bother checking the syntax.
+ */
+ smtpd_chat_reply(state, "221 2.0.0 Bye");
+
+ /*
+ * When the "." and quit replies are pipelined, make sure they are
+ * flushed now, to avoid repeated mail deliveries in case of a crash in
+ * the "clean up before disconnect" code.
+ *
+ * XXX When this was added in Postfix 2.1 we used vstream_fflush(). As of
+ * Postfix 2.3 we use smtp_flush() for better error reporting.
+ */
+ if (out_pending > 0)
+ smtp_flush(state->client);
+ return (0);
+}
+
+/* xclient_cmd - override SMTP client attributes */
+
+static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ SMTPD_TOKEN *argp;
+ char *raw_value;
+ char *attr_value;
+ char *attr_name;
+ int update_namaddr = 0;
+ int name_status;
+ static const NAME_CODE peer_codes[] = {
+ XCLIENT_UNAVAILABLE, SMTPD_PEER_CODE_PERM,
+ XCLIENT_TEMPORARY, SMTPD_PEER_CODE_TEMP,
+ 0, SMTPD_PEER_CODE_OK,
+ };
+ static const NAME_CODE proto_names[] = {
+ MAIL_PROTO_SMTP, 1,
+ MAIL_PROTO_ESMTP, 2,
+ 0, -1,
+ };
+ int got_helo = 0;
+ int got_proto = 0;
+
+#ifdef USE_SASL_AUTH
+ int got_login = 0;
+ char *saved_username;
+
+#endif
+
+ /*
+ * Sanity checks.
+ *
+ * XXX The XCLIENT command will override its own access control, so that
+ * connection count/rate restrictions can be correctly simulated.
+ */
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress");
+ return (-1);
+ }
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: %s attribute=value...",
+ XCLIENT_CMD);
+ return (-1);
+ }
+ if (xclient_hosts && xclient_hosts->error)
+ cant_permit_command(state, XCLIENT_CMD);
+ if (!xclient_allowed) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization");
+ return (-1);
+ }
+#define STREQ(x,y) (strcasecmp((x), (y)) == 0)
+
+ /*
+ * Initialize.
+ */
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(100);
+
+ /*
+ * Iterate over all attribute=value elements.
+ */
+ for (argp = argv + 1; argp < argv + argc; argp++) {
+ attr_name = argp->strval;
+
+ if ((raw_value = split_at(attr_name, '=')) == 0 || *raw_value == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Error: attribute=value expected");
+ return (-1);
+ }
+ if (strlen(raw_value) > 255) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Error: attribute value too long");
+ return (-1);
+ }
+
+ /*
+ * Backwards compatibility: Postfix prior to version 2.3 does not
+ * xtext encode attribute values.
+ */
+ attr_value = xtext_unquote(state->expand_buf, raw_value) ?
+ STR(state->expand_buf) : raw_value;
+
+ /*
+ * For safety's sake mask non-printable characters. We'll do more
+ * specific censoring later.
+ */
+ printable(attr_value, '?');
+
+#define UPDATE_STR(s, v) do { \
+ const char *_v = (v); \
+ if (s) myfree(s); \
+ (s) = (_v) ? mystrdup(_v) : 0; \
+ } while(0)
+
+ /*
+ * NAME=substitute SMTP client hostname (and reverse/forward name, in
+ * case of success). Also updates the client hostname lookup status
+ * code.
+ */
+ if (STREQ(attr_name, XCLIENT_NAME)) {
+ name_status = name_code(peer_codes, NAME_CODE_FLAG_NONE, attr_value);
+ if (name_status != SMTPD_PEER_CODE_OK) {
+ attr_value = CLIENT_NAME_UNKNOWN;
+ } else {
+ /* XXX EAI */
+ if (!valid_hostname(attr_value, DONT_GRIPE)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_NAME, attr_value);
+ return (-1);
+ }
+ }
+ state->name_status = name_status;
+ UPDATE_STR(state->name, attr_value);
+ update_namaddr = 1;
+ if (name_status == SMTPD_PEER_CODE_OK) {
+ UPDATE_STR(state->reverse_name, attr_value);
+ state->reverse_name_status = name_status;
+ }
+ }
+
+ /*
+ * REVERSE_NAME=substitute SMTP client reverse hostname. Also updates
+ * the client reverse hostname lookup status code.
+ */
+ else if (STREQ(attr_name, XCLIENT_REVERSE_NAME)) {
+ name_status = name_code(peer_codes, NAME_CODE_FLAG_NONE, attr_value);
+ if (name_status != SMTPD_PEER_CODE_OK) {
+ attr_value = CLIENT_NAME_UNKNOWN;
+ } else {
+ /* XXX EAI */
+ if (!valid_hostname(attr_value, DONT_GRIPE)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_REVERSE_NAME, attr_value);
+ return (-1);
+ }
+ }
+ state->reverse_name_status = name_status;
+ UPDATE_STR(state->reverse_name, attr_value);
+ }
+
+ /*
+ * ADDR=substitute SMTP client network address.
+ */
+ else if (STREQ(attr_name, XCLIENT_ADDR)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = CLIENT_ADDR_UNKNOWN;
+ UPDATE_STR(state->addr, attr_value);
+ UPDATE_STR(state->rfc_addr, attr_value);
+ } else {
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ if (normalize_mailhost_addr(attr_value, &state->rfc_addr,
+ &state->addr,
+ &state->addr_family) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_ADDR, attr_value);
+ return (-1);
+ }
+ }
+ update_namaddr = 1;
+ }
+
+ /*
+ * PORT=substitute SMTP client port number.
+ */
+ else if (STREQ(attr_name, XCLIENT_PORT)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = CLIENT_PORT_UNKNOWN;
+ } else {
+ if (!alldig(attr_value)
+ || strlen(attr_value) > sizeof("65535") - 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_PORT, attr_value);
+ return (-1);
+ }
+ }
+ UPDATE_STR(state->port, attr_value);
+ update_namaddr = 1;
+ }
+
+ /*
+ * HELO=substitute SMTP client HELO parameter. Censor special
+ * characters that could mess up message headers.
+ */
+ else if (STREQ(attr_name, XCLIENT_HELO)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = CLIENT_HELO_UNKNOWN;
+ } else {
+ if (strlen(attr_value) > VALID_HOSTNAME_LEN) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_HELO, attr_value);
+ return (-1);
+ }
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ }
+ UPDATE_STR(state->helo_name, attr_value);
+ got_helo = 1;
+ }
+
+ /*
+ * PROTO=SMTP protocol name.
+ */
+ else if (STREQ(attr_name, XCLIENT_PROTO)) {
+ if (name_code(proto_names, NAME_CODE_FLAG_NONE, attr_value) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_PROTO, attr_value);
+ return (-1);
+ }
+ UPDATE_STR(state->protocol, uppercase(attr_value));
+ got_proto = 1;
+ }
+
+ /*
+ * LOGIN=sasl_username. Sets the authentication method as XCLIENT.
+ * This can be used even if SASL authentication is turned off in
+ * main.cf. We can't make it easier than that.
+ */
+#ifdef USE_SASL_AUTH
+ else if (STREQ(attr_name, XCLIENT_LOGIN)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE) == 0) {
+ smtpd_sasl_auth_extern(state, attr_value, XCLIENT_CMD);
+ got_login = 1;
+ }
+ }
+#endif
+
+ /*
+ * DESTADDR=substitute SMTP server network address.
+ */
+ else if (STREQ(attr_name, XCLIENT_DESTADDR)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = SERVER_ADDR_UNKNOWN;
+ UPDATE_STR(state->dest_addr, attr_value);
+ } else {
+#define NO_NORM_RFC_ADDR ((char **) 0)
+#define NO_NORM_ADDR_FAMILY ((int *) 0)
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ if (normalize_mailhost_addr(attr_value, NO_NORM_RFC_ADDR,
+ &state->dest_addr,
+ NO_NORM_ADDR_FAMILY) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_DESTADDR, attr_value);
+ return (-1);
+ }
+ }
+ /* XXX Require same address family as client address. */
+ }
+
+ /*
+ * DESTPORT=substitute SMTP server port number.
+ */
+ else if (STREQ(attr_name, XCLIENT_DESTPORT)) {
+ if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) {
+ attr_value = SERVER_PORT_UNKNOWN;
+ } else {
+ if (!alldig(attr_value)
+ || strlen(attr_value) > sizeof("65535") - 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XCLIENT_DESTPORT, attr_value);
+ return (-1);
+ }
+ }
+ UPDATE_STR(state->dest_port, attr_value);
+ }
+
+ /*
+ * Unknown attribute name. Complain.
+ */
+ else {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s attribute name: %s",
+ XCLIENT_CMD, attr_name);
+ return (-1);
+ }
+ }
+
+ /*
+ * Update the combined name and address when either has changed.
+ */
+ if (update_namaddr) {
+ if (state->namaddr)
+ myfree(state->namaddr);
+ state->namaddr =
+ SMTPD_BUILD_NAMADDRPORT(state->name, state->addr, state->port);
+ }
+
+ /*
+ * XXX Compatibility: when the client issues XCLIENT then we have to go
+ * back to initial server greeting stage, otherwise we can't correctly
+ * simulate smtpd_client_restrictions (with smtpd_delay_reject=0) and
+ * Milter connect restrictions.
+ *
+ * XXX Compatibility: for accurate simulation we must also reset the HELO
+ * information. We keep the information if it was specified in the
+ * XCLIENT command.
+ *
+ * XXX The client connection count/rate control must be consistent in its
+ * use of client address information in connect and disconnect events. We
+ * re-evaluate xclient so that we correctly simulate connection
+ * concurrency and connection rate restrictions.
+ *
+ * XXX Duplicated from smtpd_proto().
+ */
+ xclient_allowed =
+ namadr_list_match(xclient_hosts, state->name, state->addr);
+ smtp_detect_bare_lf = (SMTPD_STAND_ALONE((state)) == 0 && bare_lf_mask
+ && !namadr_list_match(bare_lf_excl, state->name, state->addr)) ?
+ bare_lf_mask : 0;
+ /* NOT: tls_reset() */
+ if (got_helo == 0)
+ helo_reset(state);
+ if (got_proto == 0 && strcasecmp(state->protocol, MAIL_PROTO_SMTP) != 0) {
+ myfree(state->protocol);
+ state->protocol = mystrdup(MAIL_PROTO_SMTP);
+ }
+#ifdef USE_SASL_AUTH
+ /* XXX What if they send the parameters via multiple commands? */
+ if (got_login == 0)
+ smtpd_sasl_auth_reset(state);
+ if (smtpd_sasl_is_active(state)) {
+ if (got_login)
+ saved_username = mystrdup(state->sasl_username);
+ smtpd_sasl_deactivate(state);
+#ifdef USE_TLS
+ if (state->tls_context != 0) /* TLS from XCLIENT proxy? */
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS,
+ var_smtpd_sasl_tls_opts);
+ else
+#endif
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS,
+ var_smtpd_sasl_opts);
+ if (got_login) {
+ smtpd_sasl_auth_extern(state, saved_username, XCLIENT_CMD);
+ myfree(saved_username);
+ }
+ }
+#endif
+ chat_reset(state, 0);
+ mail_reset(state);
+ rcpt_reset(state);
+ if (state->milters)
+ milter_disc_event(state->milters);
+ /* Following duplicates the top-level connect/disconnect handler. */
+ teardown_milters(state);
+ setup_milters(state);
+ vstream_longjmp(state->client, SMTP_ERR_NONE);
+ return (0);
+}
+
+/* xforward_cmd - forward logging attributes */
+
+static int xforward_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ SMTPD_TOKEN *argp;
+ char *raw_value;
+ char *attr_value;
+ char *attr_name;
+ int updated = 0;
+ static const NAME_CODE xforward_flags[] = {
+ XFORWARD_NAME, SMTPD_STATE_XFORWARD_NAME,
+ XFORWARD_ADDR, SMTPD_STATE_XFORWARD_ADDR,
+ XFORWARD_PORT, SMTPD_STATE_XFORWARD_PORT,
+ XFORWARD_PROTO, SMTPD_STATE_XFORWARD_PROTO,
+ XFORWARD_HELO, SMTPD_STATE_XFORWARD_HELO,
+ XFORWARD_IDENT, SMTPD_STATE_XFORWARD_IDENT,
+ XFORWARD_DOMAIN, SMTPD_STATE_XFORWARD_DOMAIN,
+ 0, 0,
+ };
+ static const char *context_name[] = {
+ MAIL_ATTR_RWR_LOCAL, /* Postfix internal form */
+ MAIL_ATTR_RWR_REMOTE, /* Postfix internal form */
+ };
+ static const NAME_CODE xforward_to_context[] = {
+ XFORWARD_DOM_LOCAL, 0, /* XFORWARD representation */
+ XFORWARD_DOM_REMOTE, 1, /* XFORWARD representation */
+ 0, -1,
+ };
+ int flag;
+ int context_code;
+
+ /*
+ * Sanity checks.
+ */
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress");
+ return (-1);
+ }
+ if (argc < 2) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: %s attribute=value...",
+ XFORWARD_CMD);
+ return (-1);
+ }
+ if (xforward_hosts && xforward_hosts->error)
+ cant_permit_command(state, XFORWARD_CMD);
+ if (!xforward_allowed) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization");
+ return (-1);
+ }
+
+ /*
+ * Initialize.
+ */
+ if (state->xforward.flags == 0)
+ smtpd_xforward_preset(state);
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(100);
+
+ /*
+ * Iterate over all attribute=value elements.
+ */
+ for (argp = argv + 1; argp < argv + argc; argp++) {
+ attr_name = argp->strval;
+
+ if ((raw_value = split_at(attr_name, '=')) == 0 || *raw_value == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Error: attribute=value expected");
+ return (-1);
+ }
+ if (strlen(raw_value) > 255) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Error: attribute value too long");
+ return (-1);
+ }
+
+ /*
+ * Backwards compatibility: Postfix prior to version 2.3 does not
+ * xtext encode attribute values.
+ */
+ attr_value = xtext_unquote(state->expand_buf, raw_value) ?
+ STR(state->expand_buf) : raw_value;
+
+ /*
+ * For safety's sake mask non-printable characters. We'll do more
+ * specific censoring later.
+ */
+ printable(attr_value, '?');
+
+ flag = name_code(xforward_flags, NAME_CODE_FLAG_NONE, attr_name);
+ switch (flag) {
+
+ /*
+ * NAME=up-stream host name, not necessarily in the DNS. Censor
+ * special characters that could mess up message headers.
+ */
+ case SMTPD_STATE_XFORWARD_NAME:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_NAME_UNKNOWN;
+ } else {
+ /* XXX EAI */
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ if (!valid_hostname(attr_value, DONT_GRIPE)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_NAME, attr_value);
+ return (-1);
+ }
+ }
+ UPDATE_STR(state->xforward.name, attr_value);
+ break;
+
+ /*
+ * ADDR=up-stream host network address, not necessarily on the
+ * Internet. Censor special characters that could mess up message
+ * headers.
+ */
+ case SMTPD_STATE_XFORWARD_ADDR:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_ADDR_UNKNOWN;
+ UPDATE_STR(state->xforward.addr, attr_value);
+ } else {
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ if (normalize_mailhost_addr(attr_value,
+ &state->xforward.rfc_addr,
+ &state->xforward.addr,
+ NO_NORM_ADDR_FAMILY) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_ADDR, attr_value);
+ return (-1);
+ }
+ }
+ break;
+
+ /*
+ * PORT=up-stream port number.
+ */
+ case SMTPD_STATE_XFORWARD_PORT:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_PORT_UNKNOWN;
+ } else {
+ if (!alldig(attr_value)
+ || strlen(attr_value) > sizeof("65535") - 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_PORT, attr_value);
+ return (-1);
+ }
+ }
+ UPDATE_STR(state->xforward.port, attr_value);
+ break;
+
+ /*
+ * HELO=hostname that the up-stream MTA introduced itself with
+ * (not necessarily SMTP HELO). Censor special characters that
+ * could mess up message headers.
+ */
+ case SMTPD_STATE_XFORWARD_HELO:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_HELO_UNKNOWN;
+ } else {
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ }
+ UPDATE_STR(state->xforward.helo_name, attr_value);
+ break;
+
+ /*
+ * PROTO=up-stream protocol, not necessarily SMTP or ESMTP.
+ * Censor special characters that could mess up message headers.
+ */
+ case SMTPD_STATE_XFORWARD_PROTO:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_PROTO_UNKNOWN;
+ } else {
+ if (strlen(attr_value) > 64) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_PROTO, attr_value);
+ return (-1);
+ }
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ }
+ UPDATE_STR(state->xforward.protocol, attr_value);
+ break;
+
+ /*
+ * IDENT=local message identifier on the up-stream MTA. Censor
+ * special characters that could mess up logging or macro
+ * expansions.
+ */
+ case SMTPD_STATE_XFORWARD_IDENT:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) {
+ attr_value = CLIENT_IDENT_UNKNOWN;
+ } else {
+ neuter(attr_value, NEUTER_CHARACTERS, '?');
+ }
+ UPDATE_STR(state->xforward.ident, attr_value);
+ break;
+
+ /*
+ * DOMAIN=local or remote.
+ */
+ case SMTPD_STATE_XFORWARD_DOMAIN:
+ if (STREQ(attr_value, XFORWARD_UNAVAILABLE))
+ attr_value = XFORWARD_DOM_LOCAL;
+ if ((context_code = name_code(xforward_to_context,
+ NAME_CODE_FLAG_NONE,
+ attr_value)) < 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s",
+ XFORWARD_DOMAIN, attr_value);
+ return (-1);
+ }
+ UPDATE_STR(state->xforward.domain, context_name[context_code]);
+ break;
+
+ /*
+ * Unknown attribute name. Complain.
+ */
+ default:
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Bad %s attribute name: %s",
+ XFORWARD_CMD, attr_name);
+ return (-1);
+ }
+ updated |= flag;
+ }
+ state->xforward.flags |= updated;
+
+ /*
+ * Update the combined name and address when either has changed. Use only
+ * the name when no address is available.
+ */
+ if (updated & (SMTPD_STATE_XFORWARD_NAME | SMTPD_STATE_XFORWARD_ADDR
+ | SMTPD_STATE_XFORWARD_PORT)) {
+ if (state->xforward.namaddr)
+ myfree(state->xforward.namaddr);
+ state->xforward.namaddr =
+ IS_AVAIL_CLIENT_ADDR(state->xforward.addr) ?
+ SMTPD_BUILD_NAMADDRPORT(state->xforward.name,
+ state->xforward.addr,
+ state->xforward.port) :
+ mystrdup(state->xforward.name);
+ }
+ smtpd_chat_reply(state, "250 2.0.0 Ok");
+ return (0);
+}
+
+/* chat_reset - notify postmaster and reset conversation log */
+
+static void chat_reset(SMTPD_STATE *state, int threshold)
+{
+
+ /*
+ * Notify the postmaster if there were errors. This usually indicates a
+ * client configuration problem, or that someone is trying nasty things.
+ * Either is significant enough to bother the postmaster. XXX Can't
+ * report problems when running in stand-alone mode: postmaster notices
+ * require availability of the cleanup service.
+ */
+ if (state->history != 0 && state->history->argc > threshold) {
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (state->error_mask & state->notify_mask))
+ smtpd_chat_notify(state);
+ state->error_mask = 0;
+ smtpd_chat_reset(state);
+ }
+}
+
+#ifdef USE_TLS
+
+/* smtpd_start_tls - turn on TLS or force disconnect */
+
+static void smtpd_start_tls(SMTPD_STATE *state)
+{
+ int rate;
+ int cert_present;
+ int requirecert;
+
+#ifdef USE_TLSPROXY
+
+ /*
+ * This is non-production code, for tlsproxy(8) load testing only. It
+ * implements enough to enable some Postfix features that depend on TLS
+ * encryption.
+ *
+ * To insert tlsproxy(8) between this process and the SMTP client, we swap
+ * the file descriptors between the state->tlsproxy and state->client
+ * VSTREAMS, so that we don't lose all the user-configurable
+ * state->client attributes (such as longjump buffers or timeouts).
+ *
+ * As we implement tlsproxy support in the Postfix SMTP client we should
+ * develop a usable abstraction that encapsulates this stream plumbing in
+ * a library module.
+ */
+ vstream_control(state->tlsproxy, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END);
+ vstream_control(state->client, CA_VSTREAM_CTL_SWAP_FD(state->tlsproxy),
+ CA_VSTREAM_CTL_END);
+ (void) vstream_fclose(state->tlsproxy); /* direct-to-client stream! */
+ state->tlsproxy = 0;
+
+ /*
+ * After plumbing the plaintext stream, receive the TLS context object.
+ * For this we must use the same VSTREAM buffer that we also use to
+ * receive subsequent SMTP commands. The attribute protocol is robust
+ * enough that an adversary cannot inject their own bogus TLS context
+ * attributes into the stream.
+ */
+ state->tls_context = tls_proxy_context_receive(state->client);
+
+ /*
+ * XXX Maybe it is better to send this information to tlsproxy(8) when
+ * requesting service, effectively making a remote tls_server_start()
+ * call.
+ */
+ requirecert = (var_smtpd_tls_req_ccert && var_smtpd_enforce_tls);
+
+#else /* USE_TLSPROXY */
+ TLS_SERVER_START_PROPS props;
+ static char *cipher_grade;
+ static VSTRING *cipher_exclusions;
+
+ /*
+ * Wrapper mode uses a dedicated port and always requires TLS.
+ *
+ * XXX In non-wrapper mode, it is possible to require client certificate
+ * verification without requiring TLS. Since certificates can be verified
+ * only while TLS is turned on, this means that Postfix will happily
+ * perform SMTP transactions when the client does not use the STARTTLS
+ * command. For this reason, Postfix does not require client certificate
+ * verification unless TLS is required.
+ *
+ * The cipher grade and exclusions don't change between sessions. Compute
+ * just once and cache.
+ */
+#define ADD_EXCLUDE(vstr, str) \
+ do { \
+ if (*(str)) \
+ vstring_sprintf_append((vstr), "%s%s", \
+ VSTRING_LEN(vstr) ? " " : "", (str)); \
+ } while (0)
+
+ if (cipher_grade == 0) {
+ cipher_grade = var_smtpd_enforce_tls ?
+ var_smtpd_tls_mand_ciph : var_smtpd_tls_ciph;
+ cipher_exclusions = vstring_alloc(10);
+ ADD_EXCLUDE(cipher_exclusions, var_smtpd_tls_excl_ciph);
+ if (var_smtpd_enforce_tls)
+ ADD_EXCLUDE(cipher_exclusions, var_smtpd_tls_mand_excl);
+ if (ask_client_cert)
+ ADD_EXCLUDE(cipher_exclusions, "aNULL");
+ }
+
+ /*
+ * Perform the TLS handshake now. Check the client certificate
+ * requirements later, if necessary.
+ */
+ requirecert = (var_smtpd_tls_req_ccert && var_smtpd_enforce_tls);
+
+ state->tls_context =
+ TLS_SERVER_START(&props,
+ ctx = smtpd_tls_ctx,
+ stream = state->client,
+ fd = -1,
+ timeout = var_smtpd_starttls_tmout,
+ requirecert = requirecert,
+ serverid = state->service,
+ namaddr = state->namaddr,
+ cipher_grade = cipher_grade,
+ cipher_exclusions = STR(cipher_exclusions),
+ mdalg = var_smtpd_tls_fpt_dgst);
+
+#endif /* USE_TLSPROXY */
+
+ /*
+ * For new (i.e. not re-used) TLS sessions, increment the client's new
+ * TLS session rate counter. We enforce the limit here only for human
+ * factors reasons (reduce the WTF factor), even though it is too late to
+ * save the CPU that was already burnt on PKI ops. The real safety
+ * mechanism applies with future STARTTLS commands (or wrappermode
+ * connections), prior to the SSL handshake.
+ *
+ * XXX The client event count/rate control must be consistent in its use of
+ * client address information in connect and disconnect events. For now
+ * we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (var_smtpd_cntls_limit > 0
+ && (state->tls_context == 0 || state->tls_context->session_reused == 0)
+ && SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_newtls(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_cntls_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("New TLS session rate limit exceeded: %d from %s for service %s",
+ rate, state->namaddr, state->service);
+ if (state->tls_context)
+ smtpd_chat_reply(state,
+ "421 4.7.0 %s Error: too many new TLS sessions from %s",
+ var_myhostname, state->namaddr);
+ /* XXX Use regular return to signal end of session. */
+ vstream_longjmp(state->client, SMTP_ERR_QUIET);
+ }
+
+ /*
+ * When the TLS handshake fails, the conversation is in an unknown state.
+ * There is nothing we can do except to disconnect from the client.
+ */
+ if (state->tls_context == 0)
+ vstream_longjmp(state->client, SMTP_ERR_EOF);
+
+ /*
+ * If we are requiring verified client certs, enforce the constraint
+ * here. We have a usable TLS session with the client, so no need to
+ * disable I/O, ... we can even be polite and send "421 ...".
+ */
+ if (requirecert && TLS_CERT_IS_TRUSTED(state->tls_context) == 0) {
+
+ /*
+ * In non-wrappermode, fetch the next command (should be EHLO). Reply
+ * with 421, then disconnect (as a side-effect of replying with 421).
+ */
+ cert_present = TLS_CERT_IS_PRESENT(state->tls_context);
+ msg_info("NOQUEUE: abort: TLS from %s: %s",
+ state->namaddr, cert_present ?
+ "Client certificate not trusted" :
+ "No client certificate presented");
+ if (var_smtpd_tls_wrappermode == 0)
+ smtpd_chat_query(state);
+ smtpd_chat_reply(state, "421 4.7.1 %s Error: %s",
+ var_myhostname, cert_present ?
+ "Client certificate not trusted" :
+ "No client certificate presented");
+ state->error_mask |= MAIL_ERROR_POLICY;
+ return;
+ }
+
+ /*
+ * When TLS is turned on, we may offer AUTH methods that would not be
+ * offered within a plain-text session.
+ *
+ * XXX Always refresh SASL the mechanism list after STARTTLS. Dovecot
+ * responses may depend on whether the SMTP connection is encrypted.
+ */
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ /* Non-wrappermode, presumably. */
+ if (smtpd_sasl_is_active(state)) {
+ smtpd_sasl_auth_reset(state);
+ smtpd_sasl_deactivate(state);
+ }
+ /* Wrappermode and non-wrappermode. */
+ if (smtpd_sasl_is_active(state) == 0)
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS,
+ var_smtpd_sasl_tls_opts);
+ }
+#endif
+}
+
+/* starttls_cmd - respond to STARTTLS */
+
+static int starttls_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+ const char *err;
+ int rate;
+
+ if (argc != 1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: STARTTLS");
+ return (-1);
+ }
+ if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0) {
+ if (err[0] == '5') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ /* Sendmail compatibility: map 4xx into 454. */
+ else if (err[0] == '4') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "454 4.3.0 Try again later");
+ return (-1);
+ }
+ }
+ if (state->tls_context != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "554 5.5.1 Error: TLS already active");
+ return (-1);
+ }
+ if (var_smtpd_use_tls == 0
+ || (state->ehlo_discard_mask & EHLO_MASK_STARTTLS)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "502 5.5.1 Error: command not implemented");
+ return (-1);
+ }
+#ifdef USE_TLSPROXY
+
+ /*
+ * Note: state->tlsproxy is left open when smtp_flush() calls longjmp(),
+ * so we garbage-collect the VSTREAM in smtpd_state_reset().
+ */
+#define PROXY_OPEN_FLAGS \
+ (TLS_PROXY_FLAG_ROLE_SERVER | TLS_PROXY_FLAG_SEND_CONTEXT)
+
+ state->tlsproxy =
+ tls_proxy_legacy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS,
+ state->client, state->addr,
+ state->port, var_smtpd_tmout,
+ state->service);
+ if (state->tlsproxy == 0) {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ /* RFC 3207 Section 4. */
+ smtpd_chat_reply(state, "454 4.7.0 TLS not available due to local problem");
+ return (-1);
+ }
+#else /* USE_TLSPROXY */
+ if (smtpd_tls_ctx == 0) {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ /* RFC 3207 Section 4. */
+ smtpd_chat_reply(state, "454 4.7.0 TLS not available due to local problem");
+ return (-1);
+ }
+#endif /* USE_TLSPROXY */
+
+ /*
+ * Enforce TLS handshake rate limit when this client negotiated too many
+ * new TLS sessions in the recent past.
+ *
+ * XXX The client event count/rate control must be consistent in its use of
+ * client address information in connect and disconnect events. For now
+ * we exclude xclient authorized hosts from event count/rate control.
+ */
+ if (var_smtpd_cntls_limit > 0
+ && SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_newtls_stat(anvil_clnt, state->service, state->addr,
+ &rate) == ANVIL_STAT_OK
+ && rate > var_smtpd_cntls_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Refusing STARTTLS request from %s for service %s",
+ state->namaddr, state->service);
+ smtpd_chat_reply(state,
+ "454 4.7.0 Error: too many new TLS sessions from %s",
+ state->namaddr);
+#ifdef USE_TLSPROXY
+ (void) vstream_fclose(state->tlsproxy);
+ state->tlsproxy = 0;
+#endif
+ return (-1);
+ }
+ smtpd_chat_reply(state, "220 2.0.0 Ready to start TLS");
+ /* Flush before we switch read/write routines or file descriptors. */
+ smtp_flush(state->client);
+ /* At this point there must not be any pending plaintext. */
+ vstream_fpurge(state->client, VSTREAM_PURGE_BOTH);
+
+ /*
+ * Reset all inputs to the initial state.
+ *
+ * XXX RFC 2487 does not forbid the use of STARTTLS while mail transfer is
+ * in progress, so we have to allow it even when it makes no sense.
+ */
+ helo_reset(state);
+ mail_reset(state);
+ rcpt_reset(state);
+
+ /*
+ * Turn on TLS, using code that is shared with TLS wrapper mode. This
+ * code does not return when the handshake fails.
+ */
+ smtpd_start_tls(state);
+ return (0);
+}
+
+/* tls_reset - undo STARTTLS */
+
+static void tls_reset(SMTPD_STATE *state)
+{
+ int failure = 0;
+
+ /*
+ * Don't waste time when we lost contact.
+ */
+ if (state->tls_context) {
+ if (vstream_feof(state->client) || vstream_ferror(state->client))
+ failure = 1;
+ vstream_fflush(state->client); /* NOT: smtp_flush() */
+#ifdef USE_TLSPROXY
+ tls_proxy_context_free(state->tls_context);
+#else
+ tls_server_stop(smtpd_tls_ctx, state->client, var_smtpd_starttls_tmout,
+ failure, state->tls_context);
+#endif
+ state->tls_context = 0;
+ }
+}
+
+#endif
+
+#if !defined(USE_TLS) || !defined(USE_SASL_AUTH)
+
+/* unimpl_cmd - dummy for functionality that is not compiled in */
+
+static int unimpl_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
+{
+
+ /*
+ * When a connection is closed we want to log the request counts for
+ * unimplemented STARTTLS or AUTH commands separately, instead of logging
+ * those commands as "unknown". By handling unimplemented commands with
+ * this dummy function, we avoid messing up the command processing loop.
+ */
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "502 5.5.1 Error: command not implemented");
+ return (-1);
+}
+
+#endif
+
+ /*
+ * The table of all SMTP commands that we know. Set the junk limit flag on
+ * any command that can be repeated an arbitrary number of times without
+ * triggering a tarpit delay of some sort.
+ */
+typedef struct SMTPD_CMD {
+ char *name;
+ int (*action) (SMTPD_STATE *, int, SMTPD_TOKEN *);
+ int flags;
+ int success_count;
+ int total_count;
+} SMTPD_CMD;
+
+ /*
+ * Per RFC 2920: "In particular, the commands RSET, MAIL FROM, SEND FROM,
+ * SOML FROM, SAML FROM, and RCPT TO can all appear anywhere in a pipelined
+ * command group. The EHLO, DATA, VRFY, EXPN, TURN, QUIT, and NOOP commands
+ * can only appear as the last command in a group". RFC 3030 allows BDAT
+ * commands to be pipelined as well.
+ */
+#define SMTPD_CMD_FLAG_LIMIT (1<<0) /* limit usage */
+#define SMTPD_CMD_FLAG_PRE_TLS (1<<1) /* allow before STARTTLS */
+#define SMTPD_CMD_FLAG_LAST (1<<2) /* last in PIPELINING command group */
+
+static SMTPD_CMD smtpd_cmd_table[] = {
+ {SMTPD_CMD_HELO, helo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_EHLO, ehlo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_XCLIENT, xclient_cmd, SMTPD_CMD_FLAG_PRE_TLS},
+ {SMTPD_CMD_XFORWARD, xforward_cmd,},
+#ifdef USE_TLS
+ {SMTPD_CMD_STARTTLS, starttls_cmd, SMTPD_CMD_FLAG_PRE_TLS,},
+#else
+ {SMTPD_CMD_STARTTLS, unimpl_cmd, SMTPD_CMD_FLAG_PRE_TLS,},
+#endif
+#ifdef USE_SASL_AUTH
+ {SMTPD_CMD_AUTH, smtpd_sasl_auth_cmd_wrapper,},
+#else
+ {SMTPD_CMD_AUTH, unimpl_cmd,},
+#endif
+ {SMTPD_CMD_MAIL, mail_cmd,},
+ {SMTPD_CMD_RCPT, rcpt_cmd,},
+ {SMTPD_CMD_DATA, data_cmd, SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_BDAT, bdat_cmd,},
+ {SMTPD_CMD_RSET, rset_cmd, SMTPD_CMD_FLAG_LIMIT,},
+ {SMTPD_CMD_NOOP, noop_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_VRFY, vrfy_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_LAST,},
+ {SMTPD_CMD_ETRN, etrn_cmd, SMTPD_CMD_FLAG_LIMIT,},
+ {SMTPD_CMD_QUIT, quit_cmd, SMTPD_CMD_FLAG_PRE_TLS,},
+ {0,},
+};
+
+static STRING_LIST *smtpd_noop_cmds;
+static STRING_LIST *smtpd_forbid_cmds;
+
+/* smtpd_flag_ill_pipelining - flag pipelining protocol violation */
+
+static int smtpd_flag_ill_pipelining(SMTPD_STATE *state)
+{
+
+ /*
+ * This code will not return after I/O error, timeout, or EOF. VSTREAM
+ * exceptions must be enabled in advance with smtp_stream_setup().
+ */
+ if (vstream_peek(state->client) == 0
+ && peekfd(vstream_fileno(state->client)) > 0)
+ (void) vstream_ungetc(state->client, smtp_fgetc(state->client));
+ if (vstream_peek(state->client) > 0) {
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(100);
+ escape(state->expand_buf, vstream_peek_data(state->client),
+ vstream_peek(state->client) < 100 ?
+ vstream_peek(state->client) : 100);
+ msg_info("improper command pipelining after %s from %s: %s",
+ state->where, state->namaddr, STR(state->expand_buf));
+ state->flags |= SMTPD_FLAG_ILL_PIPELINING;
+ return (1);
+ }
+ return (0);
+}
+
+/* smtpd_proto - talk the SMTP protocol */
+
+static void smtpd_proto(SMTPD_STATE *state)
+{
+ int argc;
+ SMTPD_TOKEN *argv;
+ SMTPD_CMD *cmdp;
+ const char *ehlo_words;
+ const char *err;
+ int status;
+ const char *cp;
+
+#ifdef USE_TLS
+ int tls_rate;
+
+#endif
+
+ /*
+ * Print a greeting banner and run the state machine. Read SMTP commands
+ * one line at a time. According to the standard, a sender or recipient
+ * address could contain an escaped newline. I think this is perverse,
+ * and anyone depending on this is really asking for trouble.
+ *
+ * In case of mail protocol trouble, the program jumps back to this place,
+ * so that it can perform the necessary cleanup before talking to the
+ * next client. The setjmp/longjmp primitives are like a sharp tool: use
+ * with care. I would certainly recommend against the use of
+ * setjmp/longjmp in programs that change privilege levels.
+ *
+ * In case of file system trouble the program terminates after logging the
+ * error and after informing the client. In all other cases (out of
+ * memory, panic) the error is logged, and the msg_cleanup() exit handler
+ * cleans up, but no attempt is made to inform the client of the nature
+ * of the problem.
+ */
+ smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_rec_deadline);
+
+ while ((status = vstream_setjmp(state->client)) == SMTP_ERR_NONE)
+ /* void */ ;
+ switch (status) {
+
+ default:
+ msg_panic("smtpd_proto: unknown error reading from %s",
+ state->namaddr);
+ break;
+
+ case SMTP_ERR_TIME:
+ state->reason = REASON_TIMEOUT;
+ if (vstream_setjmp(state->client) == 0)
+ smtpd_chat_reply(state, "421 4.4.2 %s Error: timeout exceeded",
+ var_myhostname);
+ break;
+
+ case SMTP_ERR_EOF:
+ state->reason = REASON_LOST_CONNECTION;
+ break;
+
+ case SMTP_ERR_QUIET:
+ break;
+
+ case SMTP_ERR_DATA:
+ msg_info("%s: reject: %s from %s: "
+ "421 4.3.0 %s Server local data error",
+ (state->queue_id ? state->queue_id : "NOQUEUE"),
+ state->where, state->namaddr, var_myhostname);
+ state->error_mask |= MAIL_ERROR_DATA;
+ if (vstream_setjmp(state->client) == 0)
+ smtpd_chat_reply(state, "421 4.3.0 %s Server local data error",
+ var_myhostname);
+ break;
+
+ case 0:
+
+ /*
+ * Don't bother doing anything if some pre-SMTP handshake (haproxy)
+ * did not work out.
+ */
+ if (state->flags & SMTPD_FLAG_HANGUP) {
+ smtpd_chat_reply(state, "421 4.3.0 %s Server local error",
+ var_myhostname);
+ break;
+ }
+
+ /*
+ * In TLS wrapper mode, turn on TLS using code that is shared with
+ * the STARTTLS command. This code does not return when the handshake
+ * fails.
+ *
+ * Enforce TLS handshake rate limit when this client negotiated too many
+ * new TLS sessions in the recent past.
+ *
+ * XXX This means we don't complete a TLS handshake just to tell the
+ * client that we don't provide service. TLS wrapper mode is
+ * obsolete, so we don't have to provide perfect support.
+ */
+#ifdef USE_TLS
+ if (SMTPD_STAND_ALONE(state) == 0 && var_smtpd_tls_wrappermode
+ && state->tls_context == 0) {
+#ifdef USE_TLSPROXY
+ /* We garbage-collect the VSTREAM in smtpd_state_reset() */
+ state->tlsproxy =
+ tls_proxy_legacy_open(var_tlsproxy_service,
+ PROXY_OPEN_FLAGS,
+ state->client, state->addr,
+ state->port, var_smtpd_tmout,
+ state->service);
+ if (state->tlsproxy == 0) {
+ msg_warn("Wrapper-mode request dropped from %s for service %s."
+ " TLS context initialization failed. For details see"
+ " earlier warnings in your logs.",
+ state->namaddr, state->service);
+ break;
+ }
+#else /* USE_TLSPROXY */
+ if (smtpd_tls_ctx == 0) {
+ msg_warn("Wrapper-mode request dropped from %s for service %s."
+ " TLS context initialization failed. For details see"
+ " earlier warnings in your logs.",
+ state->namaddr, state->service);
+ break;
+ }
+#endif /* USE_TLSPROXY */
+ if (var_smtpd_cntls_limit > 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_newtls_stat(anvil_clnt, state->service,
+ state->addr, &tls_rate) == ANVIL_STAT_OK
+ && tls_rate > var_smtpd_cntls_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Refusing TLS service request from %s for service %s",
+ state->namaddr, state->service);
+ break;
+ }
+ smtpd_start_tls(state);
+ }
+#endif
+
+ /*
+ * If the client spoke before the server sends the initial greeting,
+ * raise a flag and log the content of the protocol violation. This
+ * check MUST NOT apply to TLS wrappermode connections.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && vstream_context(state->client) == 0 /* not postscreen */
+ && (state->flags & SMTPD_FLAG_ILL_PIPELINING) == 0
+ && smtpd_flag_ill_pipelining(state)
+ && var_smtpd_forbid_unauth_pipe) {
+ smtpd_chat_reply(state,
+ "554 5.5.0 Error: SMTP protocol synchronization");
+ break;
+ }
+
+ /*
+ * XXX The client connection count/rate control must be consistent in
+ * its use of client address information in connect and disconnect
+ * events. For now we exclude xclient authorized hosts from
+ * connection count/rate control.
+ *
+ * XXX Must send connect/disconnect events to the anvil server even when
+ * this service is not connection count or rate limited, otherwise it
+ * will discard client message or recipient rate information too
+ * early or too late.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr)
+ && anvil_clnt_connect(anvil_clnt, state->service, state->addr,
+ &state->conn_count, &state->conn_rate)
+ == ANVIL_STAT_OK) {
+ if (var_smtpd_cconn_limit > 0
+ && state->conn_count > var_smtpd_cconn_limit) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ msg_warn("Connection concurrency limit exceeded: %d from %s for service %s",
+ state->conn_count, state->namaddr, state->service);
+ smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s",
+ var_myhostname, state->addr);
+ break;
+ }
+ if (var_smtpd_crate_limit > 0
+ && state->conn_rate > var_smtpd_crate_limit) {
+ msg_warn("Connection rate limit exceeded: %d from %s for service %s",
+ state->conn_rate, state->namaddr, state->service);
+ smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s",
+ var_myhostname, state->addr);
+ break;
+ }
+ }
+
+ /*
+ * Determine what server ESMTP features to suppress, typically to
+ * avoid inter-operability problems. Moved up so we don't send 421
+ * immediately after sending the initial server response.
+ */
+ if (ehlo_discard_maps == 0
+ || (ehlo_words = maps_find(ehlo_discard_maps, state->addr, 0)) == 0)
+ ehlo_words = var_smtpd_ehlo_dis_words;
+ state->ehlo_discard_mask = ehlo_mask(ehlo_words);
+
+ /* XXX We use the real client for connect access control. */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_client(state)) != 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ state->access_denied = mystrdup(err);
+ smtpd_chat_reply(state, "%s", state->access_denied);
+ state->error_count++;
+ }
+
+ /*
+ * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses
+ * other than the initial greeting and any response to HELO or EHLO
+ * are prefaced with a status code as defined in RFC 3463.
+ */
+
+ /*
+ * XXX If a Milter rejects CONNECT, reply with 220 except in case of
+ * hard reject or 421 (disconnect). The reply persists so it will
+ * apply to MAIL FROM and to other commands such as AUTH, STARTTLS,
+ * and VRFY. Note: after a Milter CONNECT reject, we must not reject
+ * HELO or EHLO, but we do change the feature list that is announced
+ * in the EHLO response.
+ */
+ else {
+ err = 0;
+ if (state->milters != 0) {
+ milter_macro_callback(state->milters, smtpd_milter_eval,
+ (void *) state);
+ if ((err = milter_conn_event(state->milters, state->name,
+ state->addr,
+ strcmp(state->port, CLIENT_PORT_UNKNOWN) ?
+ state->port : "0",
+ state->addr_family)) != 0)
+ err = check_milter_reply(state, err);
+ }
+ if (err && err[0] == '5') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "554 %s ESMTP not accepting connections",
+ var_myhostname);
+ state->error_count++;
+ } else if (err && strncmp(err, "421", 3) == 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "421 %s Service unavailable - try again later",
+ var_myhostname);
+ /* Not: state->error_count++; */
+ } else {
+ smtpd_chat_reply(state, "220 %s", var_smtpd_banner);
+ }
+ }
+
+ /*
+ * SASL initialization for plaintext mode.
+ *
+ * XXX Backwards compatibility: allow AUTH commands when the AUTH
+ * announcement is suppressed via smtpd_sasl_exceptions_networks.
+ *
+ * XXX Safety: don't enable SASL with "smtpd_tls_auth_only = yes" and
+ * non-TLS build.
+ */
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable && smtpd_sasl_is_active(state) == 0
+#ifdef USE_TLS
+ && state->tls_context == 0 && !var_smtpd_tls_auth_only
+#else
+ && var_smtpd_tls_auth_only == 0
+#endif
+ )
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS,
+ var_smtpd_sasl_opts);
+#endif
+
+ /*
+ * The command read/execute loop.
+ */
+ for (;;) {
+ if (state->flags & SMTPD_FLAG_HANGUP)
+ break;
+ if (state->error_count >= var_smtpd_hard_erlim) {
+ state->reason = REASON_ERROR_LIMIT;
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "421 4.7.0 %s Error: too many errors",
+ var_myhostname);
+ break;
+ }
+ watchdog_pat();
+ smtpd_chat_query(state);
+ if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf)) {
+ log_whatsup(state, "reject", "bare <LF> received");
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "%d 5.5.2 %s Error: bare <LF> received",
+ var_smtpd_forbid_bare_lf_code, var_myhostname);
+ break;
+ }
+ /* Safety: protect internal interfaces against malformed UTF-8. */
+ if (var_smtputf8_enable && valid_utf8_string(STR(state->buffer),
+ LEN(state->buffer)) == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "500 5.5.2 Error: bad UTF-8 syntax");
+ state->error_count++;
+ continue;
+ }
+ /* Move into smtpd_chat_query() and update session transcript. */
+ if (smtpd_cmd_filter != 0) {
+ for (cp = STR(state->buffer); *cp && IS_SPACE_TAB(*cp); cp++)
+ /* void */ ;
+ if ((cp = dict_get(smtpd_cmd_filter, cp)) != 0) {
+ msg_info("%s: replacing command \"%.100s\" with \"%.100s\"",
+ state->namaddr, STR(state->buffer), cp);
+ vstring_strcpy(state->buffer, cp);
+ } else if (smtpd_cmd_filter->error != 0) {
+ msg_warn("%s:%s lookup error for \"%.100s\"",
+ smtpd_cmd_filter->type, smtpd_cmd_filter->name,
+ printable(STR(state->buffer), '?'));
+ vstream_longjmp(state->client, SMTP_ERR_DATA);
+ }
+ }
+ if ((argc = smtpd_token(vstring_str(state->buffer), &argv)) == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "500 5.5.2 Error: bad syntax");
+ state->error_count++;
+ continue;
+ }
+ /* Ignore smtpd_noop_cmds lookup errors. Non-critical feature. */
+ if (*var_smtpd_noop_cmds
+ && string_list_match(smtpd_noop_cmds, argv[0].strval)) {
+ smtpd_chat_reply(state, "250 2.0.0 Ok");
+ if (state->junk_cmds++ > var_smtpd_junk_cmd_limit)
+ state->error_count++;
+ continue;
+ }
+ for (cmdp = smtpd_cmd_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(argv[0].strval, cmdp->name) == 0)
+ break;
+ cmdp->total_count += 1;
+ /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */
+ if (cmdp->name == 0) {
+ state->where = SMTPD_CMD_UNKNOWN;
+ if (is_header(argv[0].strval)
+ || (*var_smtpd_forbid_cmds
+ && string_list_match(smtpd_forbid_cmds, argv[0].strval))) {
+ msg_warn("non-SMTP command from %s: %.100s",
+ state->namaddr, vstring_str(state->buffer));
+ smtpd_chat_reply(state, "221 2.7.0 Error: I can break rules, too. Goodbye.");
+ break;
+ }
+ }
+ /* XXX We use the real client for connect access control. */
+ if (state->access_denied && cmdp->action != quit_cmd) {
+ /* XXX Exception for Milter override. */
+ if (strncmp(state->access_denied + 1, "21", 2) == 0) {
+ smtpd_chat_reply(state, "%s", state->access_denied);
+ continue;
+ }
+ smtpd_chat_reply(state, "503 5.7.0 Error: access denied for %s",
+ state->namaddr); /* RFC 2821 Sec 3.1 */
+ state->error_count++;
+ continue;
+ }
+ /* state->access_denied == 0 || cmdp->action == quit_cmd */
+ if (cmdp->name == 0) {
+ if (state->milters != 0
+ && (err = milter_unknown_event(state->milters,
+ argv[0].strval)) != 0
+ && (err = check_milter_reply(state, err)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ } else
+ smtpd_chat_reply(state, "502 5.5.2 Error: command not recognized");
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ state->error_count++;
+ continue;
+ }
+#ifdef USE_TLS
+ if (var_smtpd_enforce_tls &&
+ !state->tls_context &&
+ (cmdp->flags & SMTPD_CMD_FLAG_PRE_TLS) == 0) {
+ smtpd_chat_reply(state,
+ "530 5.7.0 Must issue a STARTTLS command first");
+ state->error_count++;
+ continue;
+ }
+#endif
+ state->where = cmdp->name;
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0
+ || (cmdp->flags & SMTPD_CMD_FLAG_LAST))
+ && (state->flags & SMTPD_FLAG_ILL_PIPELINING) == 0
+ && smtpd_flag_ill_pipelining(state)
+ && var_smtpd_forbid_unauth_pipe) {
+ smtpd_chat_reply(state,
+ "554 5.5.0 Error: SMTP protocol synchronization");
+ break;
+ }
+ if (cmdp->action(state, argc, argv) != 0)
+ state->error_count++;
+ else
+ cmdp->success_count += 1;
+ if ((cmdp->flags & SMTPD_CMD_FLAG_LIMIT)
+ && state->junk_cmds++ > var_smtpd_junk_cmd_limit)
+ state->error_count++;
+ if (cmdp->action == quit_cmd)
+ break;
+ }
+ break;
+ }
+
+ /*
+ * XXX The client connection count/rate control must be consistent in its
+ * use of client address information in connect and disconnect events.
+ * For now we exclude xclient authorized hosts from connection count/rate
+ * control.
+ *
+ * XXX Must send connect/disconnect events to the anvil server even when
+ * this service is not connection count or rate limited, otherwise it
+ * will discard client message or recipient rate information too early or
+ * too late.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && !xclient_allowed
+ && anvil_clnt
+ && !namadr_list_match(hogger_list, state->name, state->addr))
+ anvil_clnt_disconnect(anvil_clnt, state->service, state->addr);
+
+ /*
+ * Log abnormal session termination, in case postmaster notification has
+ * been turned off. In the log, indicate the last recognized state before
+ * things went wrong. Don't complain about clients that go away without
+ * sending QUIT. Log the byte count after DATA to help diagnose MTU
+ * troubles.
+ */
+ if (state->reason && state->where) {
+ if (strcmp(state->where, SMTPD_AFTER_DATA) == 0) {
+ msg_info("%s after %s (%lu bytes) from %s", /* 2.5 compat */
+ state->reason, SMTPD_CMD_DATA, /* 2.5 compat */
+ (long) (state->act_size + vstream_peek(state->client)),
+ state->namaddr);
+ } else if (strcmp(state->where, SMTPD_AFTER_BDAT) == 0) {
+ msg_info("%s after %s (%lu bytes) from %s",
+ state->reason, SMTPD_CMD_BDAT,
+ (long) (state->act_size + VSTRING_LEN(state->buffer)
+ + VSTRING_LEN(state->bdat_get_buffer)),
+ state->namaddr);
+ } else if (strcmp(state->where, SMTPD_AFTER_EOM)
+ || strcmp(state->reason, REASON_LOST_CONNECTION)) {
+ msg_info("%s after %s from %s",
+ state->reason, state->where, state->namaddr);
+ }
+ }
+
+ /*
+ * Cleanup whatever information the client gave us during the SMTP
+ * dialog.
+ *
+ * XXX Duplicated in xclient_cmd().
+ */
+#ifdef USE_TLS
+ tls_reset(state);
+#endif
+ helo_reset(state);
+#ifdef USE_SASL_AUTH
+ smtpd_sasl_auth_reset(state);
+ if (smtpd_sasl_is_active(state)) {
+ smtpd_sasl_deactivate(state);
+ }
+#endif
+ chat_reset(state, 0);
+ mail_reset(state);
+ rcpt_reset(state);
+ if (state->milters)
+ milter_disc_event(state->milters);
+}
+
+/* smtpd_format_cmd_stats - format per-command statistics */
+
+static char *smtpd_format_cmd_stats(VSTRING *buf)
+{
+ SMTPD_CMD *cmdp;
+ int all_success = 0;
+ int all_total = 0;
+
+ /*
+ * Log the statistics. Note that this loop produces no output when no
+ * command was received. We address that after the loop.
+ */
+ VSTRING_RESET(buf);
+ for (cmdp = smtpd_cmd_table; /* see below */ ; cmdp++) {
+ if (cmdp->total_count > 0) {
+ vstring_sprintf_append(buf, " %s=%d",
+ cmdp->name ? cmdp->name : "unknown",
+ cmdp->success_count);
+ if (cmdp->success_count != cmdp->total_count)
+ vstring_sprintf_append(buf, "/%d", cmdp->total_count);
+ all_success += cmdp->success_count;
+ all_total += cmdp->total_count;
+ }
+ if (cmdp->name == 0)
+ break;
+ }
+
+ /*
+ * Reset the per-command counters.
+ *
+ * Fix 20190621: the command counter resetting code was moved from the SMTP
+ * protocol handler to this place, because the protocol handler was never
+ * called after HaProxy handshake error, causing stale numbers to be
+ * logged.
+ */
+ for (cmdp = smtpd_cmd_table; /* see below */ ; cmdp++) {
+ cmdp->success_count = cmdp->total_count = 0;
+ if (cmdp->name == 0)
+ break;
+ }
+
+ /*
+ * Log total numbers, so that logfile analyzers will see something even
+ * if the above loop produced no output. When no commands were received
+ * log "0/0" to simplify the identification of abnormal sessions: any
+ * statistics with [0-9]/ indicate that there was a problem.
+ */
+ vstring_sprintf_append(buf, " commands=%d", all_success);
+ if (all_success != all_total || all_total == 0)
+ vstring_sprintf_append(buf, "/%d", all_total);
+ return (lowercase(STR(buf)));
+}
+
+/* setup_milters - set up Milters after a connection is established */
+
+static void setup_milters(SMTPD_STATE *state)
+{
+ const char *milter_string;
+
+ /*
+ * Postcondition: either state->milters is set, or the
+ * INPUT_TRANSP_MILTER flag is passed down-stream.
+ */
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (smtpd_input_transp_mask & INPUT_TRANSP_MILTER) == 0
+ && ((smtpd_milter_maps
+ && (milter_string =
+ maps_find(smtpd_milter_maps, state->addr, 0)) != 0)
+ || *(milter_string = var_smtpd_milters) != 0)
+ && strcasecmp(milter_string, SMTPD_MILTERS_DISABLE) != 0) {
+ state->milters = milter_create(milter_string,
+ var_milt_conn_time,
+ var_milt_cmd_time,
+ var_milt_msg_time,
+ var_milt_protocol,
+ var_milt_def_action,
+ var_milt_conn_macros,
+ var_milt_helo_macros,
+ var_milt_mail_macros,
+ var_milt_rcpt_macros,
+ var_milt_data_macros,
+ var_milt_eoh_macros,
+ var_milt_eod_macros,
+ var_milt_unk_macros,
+ var_milt_macro_deflts);
+ }
+
+ /*
+ * Safety: disable non_smtpd_milters when not sending our own mail filter
+ * list. Otherwise the next stage could handle this message as a local
+ * submission.
+ */
+ if (state->milters == 0)
+ smtpd_input_transp_mask |= INPUT_TRANSP_MILTER;
+}
+
+/* teardown_milters - release resources */
+
+static void teardown_milters(SMTPD_STATE *state)
+{
+ if (state->milters) {
+ milter_free(state->milters);
+ state->milters = 0;
+ }
+ smtpd_input_transp_mask =
+ input_transp_mask(VAR_INPUT_TRANSP, var_input_transp);
+}
+
+
+/* smtpd_service - service one client */
+
+static void smtpd_service(VSTREAM *stream, char *service, char **argv)
+{
+ SMTPD_STATE state;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * 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 (SMTPD_STAND_ALONE_STREAM(stream) == 0
+ && inet_proto_info()->ai_family_list[0] == 0)
+ msg_fatal("all network protocols are disabled (%s = %s)",
+ VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * This routine runs when a client has connected to our network port, or
+ * when the smtp server is run in stand-alone mode (input from pipe).
+ *
+ * Look up and sanitize the peer name, then initialize some connection-
+ * specific state. When the name service is hosed, hostname lookup will
+ * take a while. This is why I always run a local name server on critical
+ * machines.
+ */
+ smtpd_state_init(&state, stream, service);
+ msg_info("connect from %s", state.namaddr);
+
+ /*
+ * Disable TLS when running in stand-alone mode via "sendmail -bs".
+ */
+ if (SMTPD_STAND_ALONE((&state))) {
+ var_smtpd_use_tls = 0;
+ var_smtpd_enforce_tls = 0;
+ var_smtpd_tls_auth_only = 0;
+ }
+
+ /*
+ * XCLIENT must not override its own access control.
+ */
+ xclient_allowed = SMTPD_STAND_ALONE((&state)) == 0 &&
+ namadr_list_match(xclient_hosts, state.name, state.addr);
+
+ /*
+ * Overriding XFORWARD access control makes no sense, either.
+ */
+ xforward_allowed = SMTPD_STAND_ALONE((&state)) == 0 &&
+ namadr_list_match(xforward_hosts, state.name, state.addr);
+
+ /*
+ * Reject or normalize bare LF, with compatibility exclusions.
+ */
+ smtp_detect_bare_lf = (SMTPD_STAND_ALONE((&state)) == 0 && bare_lf_mask
+ && !namadr_list_match(bare_lf_excl, state.name, state.addr)) ?
+ bare_lf_mask : 0;
+
+ /*
+ * See if we need to turn on verbose logging for this client.
+ */
+ debug_peer_check(state.name, state.addr);
+
+ /*
+ * Set up Milters, or disable Milters down-stream.
+ */
+ setup_milters(&state); /* duplicates xclient_cmd */
+
+ /*
+ * Provide the SMTP service.
+ */
+ smtpd_proto(&state);
+
+ /*
+ * After the client has gone away, clean up whatever we have set up at
+ * connection time.
+ */
+ msg_info("disconnect from %s%s", state.namaddr,
+ smtpd_format_cmd_stats(state.buffer));
+ teardown_milters(&state); /* duplicates xclient_cmd */
+ smtpd_state_reset(&state);
+ debug_peer_restore();
+}
+
+/* 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);
+ }
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Initialize blacklist/etc. patterns before entering the chroot jail, in
+ * case they specify a filename pattern.
+ */
+ smtpd_noop_cmds = string_list_init(VAR_SMTPD_NOOP_CMDS, MATCH_FLAG_RETURN,
+ var_smtpd_noop_cmds);
+ smtpd_forbid_cmds = string_list_init(VAR_SMTPD_FORBID_CMDS,
+ MATCH_FLAG_RETURN,
+ var_smtpd_forbid_cmds);
+ verp_clients = namadr_list_init(VAR_VERP_CLIENTS, MATCH_FLAG_RETURN,
+ var_verp_clients);
+ xclient_hosts = namadr_list_init(VAR_XCLIENT_HOSTS, MATCH_FLAG_RETURN,
+ var_xclient_hosts);
+ xforward_hosts = namadr_list_init(VAR_XFORWARD_HOSTS, MATCH_FLAG_RETURN,
+ var_xforward_hosts);
+ hogger_list = namadr_list_init(VAR_SMTPD_HOGGERS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_SMTPD_HOGGERS),
+ var_smtpd_hoggers);
+ bare_lf_excl = namadr_list_init(VAR_SMTPD_FORBID_BARE_LF_EXCL,
+ MATCH_FLAG_RETURN
+ | match_parent_style(VAR_MYNETWORKS),
+ var_smtpd_forbid_bare_lf_excl);
+ if ((bare_lf_mask = name_code(bare_lf_mask_table, NAME_CODE_FLAG_NONE,
+ var_smtpd_forbid_bare_lf)) < 0)
+ msg_fatal("bad parameter value: '%s = %s'",
+ VAR_SMTPD_FORBID_BARE_LF, var_smtpd_forbid_bare_lf);
+
+ /*
+ * Open maps before dropping privileges so we can read passwords etc.
+ *
+ * XXX We should not do this in stand-alone (sendmail -bs) mode, but we
+ * can't use SMTPD_STAND_ALONE(state) here. This means "sendmail -bs"
+ * will try to connect to proxymap when invoked by root for mail
+ * submission. To fix, we would have to pass stand-alone mode information
+ * via different means. For now we have to tell people not to run mail
+ * clients as root.
+ */
+ if (getuid() == 0 || getuid() == var_owner_uid)
+ smtpd_check_init();
+ smtpd_expand_init();
+ debug_peer_init();
+
+ if (var_smtpd_sasl_enable)
+#ifdef USE_SASL_AUTH
+ smtpd_sasl_initialize();
+
+ if (*var_smtpd_sasl_exceptions_networks)
+ sasl_exceptions_networks =
+ namadr_list_init(VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS,
+ MATCH_FLAG_RETURN,
+ var_smtpd_sasl_exceptions_networks);
+#else
+ msg_warn("%s is true, but SASL support is not compiled in",
+ VAR_SMTPD_SASL_ENABLE);
+#endif
+
+ if (*var_smtpd_cmd_filter)
+ smtpd_cmd_filter = dict_open(var_smtpd_cmd_filter, O_RDONLY,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+
+ /*
+ * XXX Temporary fix to pretend that we consistently implement TLS
+ * security levels. We implement only a subset for now. If we implement
+ * more levels, wrappermode should override only weaker TLS security
+ * levels.
+ *
+ * Note: tls_level_lookup() logs no warning.
+ */
+ if (!var_smtpd_tls_wrappermode && *var_smtpd_tls_level) {
+ switch (tls_level_lookup(var_smtpd_tls_level)) {
+ default:
+ msg_fatal("Invalid TLS level \"%s\"", var_smtpd_tls_level);
+ /* NOTREACHED */
+ break;
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_FPRINT:
+ msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"",
+ VAR_SMTPD_TLS_LEVEL, var_smtpd_tls_level);
+ /* FALLTHROUGH */
+ case TLS_LEV_ENCRYPT:
+ var_smtpd_enforce_tls = var_smtpd_use_tls = 1;
+ break;
+ case TLS_LEV_MAY:
+ var_smtpd_enforce_tls = 0;
+ var_smtpd_use_tls = 1;
+ break;
+ case TLS_LEV_NONE:
+ var_smtpd_enforce_tls = var_smtpd_use_tls = 0;
+ break;
+ }
+ }
+
+ /*
+ * With TLS wrapper mode, we run on a dedicated port and turn on TLS
+ * before actually speaking the SMTP protocol. This implies TLS enforce
+ * mode.
+ *
+ * With non-wrapper mode, TLS enforce mode implies that we don't advertise
+ * AUTH before the client issues STARTTLS.
+ */
+ var_smtpd_enforce_tls = var_smtpd_tls_wrappermode || var_smtpd_enforce_tls;
+ var_smtpd_tls_auth_only = var_smtpd_tls_auth_only || var_smtpd_enforce_tls;
+ var_smtpd_use_tls = var_smtpd_use_tls || var_smtpd_enforce_tls;
+
+ /*
+ * Keys can only be loaded when running with suitable permissions. When
+ * called from "sendmail -bs" this is not the case, so we must not
+ * announce STARTTLS support.
+ */
+ if (getuid() == 0 || getuid() == var_owner_uid) {
+ if (var_smtpd_use_tls) {
+#ifdef USE_TLS
+#ifndef USE_TLSPROXY
+ TLS_SERVER_INIT_PROPS props;
+ const char *cert_file;
+ int have_server_cert;
+ int no_server_cert_ok;
+ int require_server_cert;
+
+ /*
+ * Can't use anonymous ciphers if we want client certificates.
+ * Must use anonymous ciphers if we have no certificates.
+ *
+ * XXX: Ugh! Too many booleans!
+ */
+ ask_client_cert = require_server_cert =
+ (var_smtpd_tls_ask_ccert
+ || (var_smtpd_enforce_tls && var_smtpd_tls_req_ccert));
+ if (strcasecmp(var_smtpd_tls_cert_file, "none") == 0) {
+ no_server_cert_ok = 1;
+ cert_file = "";
+ } else {
+ no_server_cert_ok = 0;
+ cert_file = var_smtpd_tls_cert_file;
+ }
+
+ have_server_cert = *cert_file != 0;
+ have_server_cert |= *var_smtpd_tls_eccert_file != 0;
+ have_server_cert |= *var_smtpd_tls_dcert_file != 0;
+
+ if (*var_smtpd_tls_chain_files != 0) {
+ if (!have_server_cert)
+ have_server_cert = 1;
+ else
+ msg_warn("Both %s and one or more of the legacy "
+ " %s, %s or %s are non-empty; the legacy "
+ " parameters will be ignored",
+ VAR_SMTPD_TLS_CHAIN_FILES,
+ VAR_SMTPD_TLS_CERT_FILE,
+ VAR_SMTPD_TLS_ECCERT_FILE,
+ VAR_SMTPD_TLS_DCERT_FILE);
+ }
+ /* Some TLS configuration errors are not show stoppers. */
+ if (!have_server_cert && require_server_cert)
+ msg_warn("Need a server cert to request client certs");
+ if (!var_smtpd_enforce_tls && var_smtpd_tls_req_ccert)
+ msg_warn("Can't require client certs unless TLS is required");
+ /* After a show-stopper error, reply with 454 to STARTTLS. */
+ if (have_server_cert
+ || (no_server_cert_ok && !require_server_cert)) {
+
+ tls_pre_jail_init(TLS_ROLE_SERVER);
+
+ /*
+ * Large parameter lists are error-prone, so we emulate a
+ * language feature that C does not have natively: named
+ * parameter lists.
+ */
+ smtpd_tls_ctx =
+ TLS_SERVER_INIT(&props,
+ log_param = VAR_SMTPD_TLS_LOGLEVEL,
+ log_level = var_smtpd_tls_loglevel,
+ verifydepth = var_smtpd_tls_ccert_vd,
+ cache_type = TLS_MGR_SCACHE_SMTPD,
+ set_sessid = var_smtpd_tls_set_sessid,
+ chain_files = var_smtpd_tls_chain_files,
+ cert_file = cert_file,
+ key_file = var_smtpd_tls_key_file,
+ dcert_file = var_smtpd_tls_dcert_file,
+ dkey_file = var_smtpd_tls_dkey_file,
+ eccert_file = var_smtpd_tls_eccert_file,
+ eckey_file = var_smtpd_tls_eckey_file,
+ CAfile = var_smtpd_tls_CAfile,
+ CApath = var_smtpd_tls_CApath,
+ dh1024_param_file
+ = var_smtpd_tls_dh1024_param_file,
+ dh512_param_file
+ = var_smtpd_tls_dh512_param_file,
+ eecdh_grade = var_smtpd_tls_eecdh,
+ protocols = var_smtpd_enforce_tls ?
+ var_smtpd_tls_mand_proto :
+ var_smtpd_tls_proto,
+ ask_ccert = ask_client_cert,
+ mdalg = var_smtpd_tls_fpt_dgst);
+ } else {
+ msg_warn("No server certs available. TLS won't be enabled");
+ }
+#endif /* USE_TLSPROXY */
+#else
+ msg_warn("TLS has been selected, but TLS support is not compiled in");
+#endif
+ }
+ }
+
+ /*
+ * flush client.
+ */
+ flush_init();
+
+ /*
+ * EHLO keyword filter.
+ */
+ if (*var_smtpd_ehlo_dis_maps)
+ ehlo_discard_maps = maps_create(VAR_SMTPD_EHLO_DIS_MAPS,
+ var_smtpd_ehlo_dis_maps,
+ DICT_FLAG_LOCK);
+
+ /*
+ * Per-client Milter support.
+ */
+ if (*var_smtpd_milter_maps)
+ smtpd_milter_maps = maps_create(VAR_SMTPD_MILTER_MAPS,
+ var_smtpd_milter_maps,
+ DICT_FLAG_LOCK);
+
+ /*
+ * DNS reply filter.
+ */
+ if (*var_smtpd_dns_re_filter)
+ dns_rr_filter_compile(VAR_SMTPD_DNS_RE_FILTER,
+ var_smtpd_dns_re_filter);
+
+ /*
+ * Reject footer.
+ */
+ if (*var_smtpd_rej_ftr_maps)
+ smtpd_chat_pre_jail_init();
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Initialize the receive transparency options: do we want unknown
+ * recipient checks, address mapping, header_body_checks?.
+ */
+ smtpd_input_transp_mask =
+ input_transp_mask(VAR_INPUT_TRANSP, var_input_transp);
+
+ /*
+ * Initialize before-queue filter options: do we want speed-matching
+ * support so that the entire message is received before we contact a
+ * before-queue content filter?
+ */
+ if (*var_smtpd_proxy_filt)
+ smtpd_proxy_opts =
+ smtpd_proxy_parse_opts(VAR_SMTPD_PROXY_OPTS, var_smtpd_proxy_opts);
+
+ /*
+ * Sanity checks. The queue_minfree value should be at least as large as
+ * (process_limit * message_size_limit) but that is unpractical, so we
+ * arbitrarily pick a small multiple of the per-message size limit. This
+ * helps to avoid many unneeded (re)transmissions.
+ */
+ if (ENFORCING_SIZE_LIMIT(var_queue_minfree)
+ && ENFORCING_SIZE_LIMIT(var_message_limit)
+ && var_queue_minfree / 1.5 < var_message_limit)
+ msg_warn("%s(%lu) should be at least 1.5*%s(%lu)",
+ VAR_QUEUE_MINFREE, (unsigned long) var_queue_minfree,
+ VAR_MESSAGE_LIMIT, (unsigned long) var_message_limit);
+
+ /*
+ * Connection rate management.
+ */
+ if (var_smtpd_crate_limit || var_smtpd_cconn_limit
+ || var_smtpd_cmail_limit || var_smtpd_crcpt_limit
+ || var_smtpd_cntls_limit || var_smtpd_cauth_limit)
+ anvil_clnt = anvil_clnt_create();
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_NINT_TABLE nint_table[] = {
+ VAR_SMTPD_SOFT_ERLIM, DEF_SMTPD_SOFT_ERLIM, &var_smtpd_soft_erlim, 1, 0,
+ VAR_SMTPD_HARD_ERLIM, DEF_SMTPD_HARD_ERLIM, &var_smtpd_hard_erlim, 1, 0,
+ VAR_SMTPD_JUNK_CMD, DEF_SMTPD_JUNK_CMD, &var_smtpd_junk_cmd_limit, 1, 0,
+ VAR_VERIFY_POLL_COUNT, DEF_VERIFY_POLL_COUNT, &var_verify_poll_count, 1, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_SMTPD_RCPT_LIMIT, DEF_SMTPD_RCPT_LIMIT, &var_smtpd_rcpt_limit, 1, 0,
+ VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code, 0, 0,
+ VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code, 0, 0,
+ VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code, 0, 0,
+ VAR_UNK_ADDR_CODE, DEF_UNK_ADDR_CODE, &var_unk_addr_code, 0, 0,
+ VAR_RELAY_CODE, DEF_RELAY_CODE, &var_relay_code, 0, 0,
+ VAR_MAPS_RBL_CODE, DEF_MAPS_RBL_CODE, &var_maps_rbl_code, 0, 0,
+ VAR_MAP_REJECT_CODE, DEF_MAP_REJECT_CODE, &var_map_reject_code, 0, 0,
+ VAR_MAP_DEFER_CODE, DEF_MAP_DEFER_CODE, &var_map_defer_code, 0, 0,
+ VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code, 0, 0,
+ VAR_DEFER_CODE, DEF_DEFER_CODE, &var_defer_code, 0, 0,
+ VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code, 0, 0,
+ VAR_SMTPD_RCPT_OVERLIM, DEF_SMTPD_RCPT_OVERLIM, &var_smtpd_rcpt_overlim, 1, 0,
+ VAR_SMTPD_HIST_THRSH, DEF_SMTPD_HIST_THRSH, &var_smtpd_hist_thrsh, 1, 0,
+ VAR_UNV_FROM_RCODE, DEF_UNV_FROM_RCODE, &var_unv_from_rcode, 200, 599,
+ VAR_UNV_RCPT_RCODE, DEF_UNV_RCPT_RCODE, &var_unv_rcpt_rcode, 200, 599,
+ VAR_UNV_FROM_DCODE, DEF_UNV_FROM_DCODE, &var_unv_from_dcode, 200, 499,
+ VAR_UNV_RCPT_DCODE, DEF_UNV_RCPT_DCODE, &var_unv_rcpt_dcode, 200, 499,
+ VAR_MUL_RCPT_CODE, DEF_MUL_RCPT_CODE, &var_mul_rcpt_code, 0, 0,
+ VAR_LOCAL_RCPT_CODE, DEF_LOCAL_RCPT_CODE, &var_local_rcpt_code, 0, 0,
+ VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code, 0, 0,
+ VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code, 0, 0,
+ VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code, 0, 0,
+ VAR_PLAINTEXT_CODE, DEF_PLAINTEXT_CODE, &var_plaintext_code, 0, 0,
+ VAR_SMTPD_FORBID_BARE_LF_CODE, DEF_SMTPD_FORBID_BARE_LF_CODE, &var_smtpd_forbid_bare_lf_code, 500, 599,
+ VAR_SMTPD_CRATE_LIMIT, DEF_SMTPD_CRATE_LIMIT, &var_smtpd_crate_limit, 0, 0,
+ VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0,
+ VAR_SMTPD_CMAIL_LIMIT, DEF_SMTPD_CMAIL_LIMIT, &var_smtpd_cmail_limit, 0, 0,
+ VAR_SMTPD_CRCPT_LIMIT, DEF_SMTPD_CRCPT_LIMIT, &var_smtpd_crcpt_limit, 0, 0,
+ VAR_SMTPD_CNTLS_LIMIT, DEF_SMTPD_CNTLS_LIMIT, &var_smtpd_cntls_limit, 0, 0,
+ VAR_SMTPD_CAUTH_LIMIT, DEF_SMTPD_CAUTH_LIMIT, &var_smtpd_cauth_limit, 0, 0,
+#ifdef USE_TLS
+ VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0,
+#endif
+ VAR_SMTPD_SASL_RESP_LIMIT, DEF_SMTPD_SASL_RESP_LIMIT, &var_smtpd_sasl_resp_limit, DEF_SMTPD_SASL_RESP_LIMIT, 0,
+ VAR_SMTPD_POLICY_REQ_LIMIT, DEF_SMTPD_POLICY_REQ_LIMIT, &var_smtpd_policy_req_limit, 0, 0,
+ VAR_SMTPD_POLICY_TRY_LIMIT, DEF_SMTPD_POLICY_TRY_LIMIT, &var_smtpd_policy_try_limit, 1, 0,
+ 0,
+ };
+ static const CONFIG_LONG_TABLE long_table[] = {
+ VAR_QUEUE_MINFREE, DEF_QUEUE_MINFREE, &var_queue_minfree, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_SMTPD_TMOUT, DEF_SMTPD_TMOUT, &var_smtpd_tmout, 1, 0,
+ VAR_SMTPD_ERR_SLEEP, DEF_SMTPD_ERR_SLEEP, &var_smtpd_err_sleep, 0, 0,
+ VAR_SMTPD_PROXY_TMOUT, DEF_SMTPD_PROXY_TMOUT, &var_smtpd_proxy_tmout, 1, 0,
+ VAR_VERIFY_POLL_DELAY, DEF_VERIFY_POLL_DELAY, &var_verify_poll_delay, 1, 0,
+ VAR_SMTPD_POLICY_TMOUT, DEF_SMTPD_POLICY_TMOUT, &var_smtpd_policy_tmout, 1, 0,
+ VAR_SMTPD_POLICY_IDLE, DEF_SMTPD_POLICY_IDLE, &var_smtpd_policy_idle, 1, 0,
+ VAR_SMTPD_POLICY_TTL, DEF_SMTPD_POLICY_TTL, &var_smtpd_policy_ttl, 1, 0,
+#ifdef USE_TLS
+ VAR_SMTPD_STARTTLS_TMOUT, DEF_SMTPD_STARTTLS_TMOUT, &var_smtpd_starttls_tmout, 1, 0,
+#endif
+ VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, &var_milt_conn_time, 1, 0,
+ VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, &var_milt_cmd_time, 1, 0,
+ VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, &var_milt_msg_time, 1, 0,
+ VAR_VERIFY_SENDER_TTL, DEF_VERIFY_SENDER_TTL, &var_verify_sender_ttl, 0, 0,
+ VAR_SMTPD_UPROXY_TMOUT, DEF_SMTPD_UPROXY_TMOUT, &var_smtpd_uproxy_tmout, 1, 0,
+ VAR_SMTPD_POLICY_TRY_DELAY, DEF_SMTPD_POLICY_TRY_DELAY, &var_smtpd_policy_try_delay, 1, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_HELO_REQUIRED, DEF_HELO_REQUIRED, &var_helo_required,
+ VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject,
+ VAR_STRICT_RFC821_ENV, DEF_STRICT_RFC821_ENV, &var_strict_rfc821_env,
+ VAR_DISABLE_VRFY_CMD, DEF_DISABLE_VRFY_CMD, &var_disable_vrfy_cmd,
+ VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route,
+ VAR_SMTPD_SASL_ENABLE, DEF_SMTPD_SASL_ENABLE, &var_smtpd_sasl_enable,
+ VAR_SMTPD_SASL_AUTH_HDR, DEF_SMTPD_SASL_AUTH_HDR, &var_smtpd_sasl_auth_hdr,
+ VAR_BROKEN_AUTH_CLNTS, DEF_BROKEN_AUTH_CLNTS, &var_broken_auth_clients,
+ VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table,
+ VAR_SMTPD_REJ_UNL_FROM, DEF_SMTPD_REJ_UNL_FROM, &var_smtpd_rej_unl_from,
+ VAR_SMTPD_REJ_UNL_RCPT, DEF_SMTPD_REJ_UNL_RCPT, &var_smtpd_rej_unl_rcpt,
+ VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls,
+ VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls,
+ VAR_SMTPD_TLS_WRAPPER, DEF_SMTPD_TLS_WRAPPER, &var_smtpd_tls_wrappermode,
+ VAR_SMTPD_TLS_AUTH_ONLY, DEF_SMTPD_TLS_AUTH_ONLY, &var_smtpd_tls_auth_only,
+#ifdef USE_TLS
+ VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
+ VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert,
+ VAR_SMTPD_TLS_RECHEAD, DEF_SMTPD_TLS_RECHEAD, &var_smtpd_tls_received_header,
+ VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid,
+#endif
+ VAR_SMTPD_PEERNAME_LOOKUP, DEF_SMTPD_PEERNAME_LOOKUP, &var_smtpd_peername_lookup,
+ VAR_SMTPD_DELAY_OPEN, DEF_SMTPD_DELAY_OPEN, &var_smtpd_delay_open,
+ VAR_SMTPD_CLIENT_PORT_LOG, DEF_SMTPD_CLIENT_PORT_LOG, &var_smtpd_client_port_log,
+ VAR_SMTPD_FORBID_UNAUTH_PIPE, DEF_SMTPD_FORBID_UNAUTH_PIPE, &var_smtpd_forbid_unauth_pipe,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_SMTPD_REC_DEADLINE, DEF_SMTPD_REC_DEADLINE, &var_smtpd_rec_deadline,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0,
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_CLIENT_CHECKS, DEF_CLIENT_CHECKS, &var_client_checks, 0, 0,
+ VAR_HELO_CHECKS, DEF_HELO_CHECKS, &var_helo_checks, 0, 0,
+ VAR_MAIL_CHECKS, DEF_MAIL_CHECKS, &var_mail_checks, 0, 0,
+ VAR_RELAY_CHECKS, DEF_RELAY_CHECKS, &var_relay_checks, 0, 0,
+ VAR_RCPT_CHECKS, DEF_RCPT_CHECKS, &var_rcpt_checks, 0, 0,
+ VAR_ETRN_CHECKS, DEF_ETRN_CHECKS, &var_etrn_checks, 0, 0,
+ VAR_DATA_CHECKS, DEF_DATA_CHECKS, &var_data_checks, 0, 0,
+ VAR_EOD_CHECKS, DEF_EOD_CHECKS, &var_eod_checks, 0, 0,
+ VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains, 0, 0,
+ VAR_RBL_REPLY_MAPS, DEF_RBL_REPLY_MAPS, &var_rbl_reply_maps, 0, 0,
+ VAR_BOUNCE_RCPT, DEF_BOUNCE_RCPT, &var_bounce_rcpt, 1, 0,
+ VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
+ VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0,
+ VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0,
+ VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0,
+ VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
+ VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
+ VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
+ VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
+ VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts, 0, 0,
+ VAR_SMTPD_SASL_PATH, DEF_SMTPD_SASL_PATH, &var_smtpd_sasl_path, 1, 0,
+ VAR_SMTPD_SASL_SERVICE, DEF_SMTPD_SASL_SERVICE, &var_smtpd_sasl_service, 1, 0,
+ VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0,
+ VAR_SMTPD_SASL_REALM, DEF_SMTPD_SASL_REALM, &var_smtpd_sasl_realm, 0, 0,
+ VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS, DEF_SMTPD_SASL_EXCEPTIONS_NETWORKS, &var_smtpd_sasl_exceptions_networks, 0, 0,
+ VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0,
+ VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks, 0, 0,
+ VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0,
+ VAR_SMTPD_NOOP_CMDS, DEF_SMTPD_NOOP_CMDS, &var_smtpd_noop_cmds, 0, 0,
+ VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0,
+ VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key, 0, 0,
+ VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0,
+ VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender, 0, 0,
+ VAR_VERP_CLIENTS, DEF_VERP_CLIENTS, &var_verp_clients, 0, 0,
+ VAR_SMTPD_PROXY_FILT, DEF_SMTPD_PROXY_FILT, &var_smtpd_proxy_filt, 0, 0,
+ VAR_SMTPD_PROXY_EHLO, DEF_SMTPD_PROXY_EHLO, &var_smtpd_proxy_ehlo, 0, 0,
+ VAR_SMTPD_PROXY_OPTS, DEF_SMTPD_PROXY_OPTS, &var_smtpd_proxy_opts, 0, 0,
+ VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0,
+ VAR_XCLIENT_HOSTS, DEF_XCLIENT_HOSTS, &var_xclient_hosts, 0, 0,
+ VAR_XFORWARD_HOSTS, DEF_XFORWARD_HOSTS, &var_xforward_hosts, 0, 0,
+ VAR_SMTPD_HOGGERS, DEF_SMTPD_HOGGERS, &var_smtpd_hoggers, 0, 0,
+ VAR_LOC_RWR_CLIENTS, DEF_LOC_RWR_CLIENTS, &var_local_rwr_clients, 0, 0,
+ VAR_SMTPD_EHLO_DIS_WORDS, DEF_SMTPD_EHLO_DIS_WORDS, &var_smtpd_ehlo_dis_words, 0, 0,
+ VAR_SMTPD_EHLO_DIS_MAPS, DEF_SMTPD_EHLO_DIS_MAPS, &var_smtpd_ehlo_dis_maps, 0, 0,
+#ifdef USE_TLS
+ VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts, 0, 0,
+ VAR_SMTPD_SASL_TLS_OPTS, DEF_SMTPD_SASL_TLS_OPTS, &var_smtpd_sasl_tls_opts, 0, 0,
+ VAR_SMTPD_TLS_CHAIN_FILES, DEF_SMTPD_TLS_CHAIN_FILES, &var_smtpd_tls_chain_files, 0, 0,
+ VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0,
+ VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0,
+ VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0,
+ VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0,
+ VAR_SMTPD_TLS_ECCERT_FILE, DEF_SMTPD_TLS_ECCERT_FILE, &var_smtpd_tls_eccert_file, 0, 0,
+ VAR_SMTPD_TLS_ECKEY_FILE, DEF_SMTPD_TLS_ECKEY_FILE, &var_smtpd_tls_eckey_file, 0, 0,
+ VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0,
+ VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0,
+ VAR_SMTPD_TLS_CIPH, DEF_SMTPD_TLS_CIPH, &var_smtpd_tls_ciph, 1, 0,
+ VAR_SMTPD_TLS_MAND_CIPH, DEF_SMTPD_TLS_MAND_CIPH, &var_smtpd_tls_mand_ciph, 1, 0,
+ VAR_SMTPD_TLS_EXCL_CIPH, DEF_SMTPD_TLS_EXCL_CIPH, &var_smtpd_tls_excl_ciph, 0, 0,
+ VAR_SMTPD_TLS_MAND_EXCL, DEF_SMTPD_TLS_MAND_EXCL, &var_smtpd_tls_mand_excl, 0, 0,
+ VAR_SMTPD_TLS_PROTO, DEF_SMTPD_TLS_PROTO, &var_smtpd_tls_proto, 0, 0,
+ VAR_SMTPD_TLS_MAND_PROTO, DEF_SMTPD_TLS_MAND_PROTO, &var_smtpd_tls_mand_proto, 0, 0,
+ VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0,
+ VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0,
+ VAR_SMTPD_TLS_EECDH, DEF_SMTPD_TLS_EECDH, &var_smtpd_tls_eecdh, 1, 0,
+ VAR_SMTPD_TLS_FPT_DGST, DEF_SMTPD_TLS_FPT_DGST, &var_smtpd_tls_fpt_dgst, 1, 0,
+ VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0,
+#endif
+ VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0,
+ VAR_SMTPD_SASL_TYPE, DEF_SMTPD_SASL_TYPE, &var_smtpd_sasl_type, 1, 0,
+ VAR_SMTPD_MILTERS, DEF_SMTPD_MILTERS, &var_smtpd_milters, 0, 0,
+ VAR_MILT_CONN_MACROS, DEF_MILT_CONN_MACROS, &var_milt_conn_macros, 0, 0,
+ VAR_MILT_HELO_MACROS, DEF_MILT_HELO_MACROS, &var_milt_helo_macros, 0, 0,
+ VAR_MILT_MAIL_MACROS, DEF_MILT_MAIL_MACROS, &var_milt_mail_macros, 0, 0,
+ VAR_MILT_RCPT_MACROS, DEF_MILT_RCPT_MACROS, &var_milt_rcpt_macros, 0, 0,
+ VAR_MILT_DATA_MACROS, DEF_MILT_DATA_MACROS, &var_milt_data_macros, 0, 0,
+ VAR_MILT_EOH_MACROS, DEF_MILT_EOH_MACROS, &var_milt_eoh_macros, 0, 0,
+ VAR_MILT_EOD_MACROS, DEF_MILT_EOD_MACROS, &var_milt_eod_macros, 0, 0,
+ VAR_MILT_UNK_MACROS, DEF_MILT_UNK_MACROS, &var_milt_unk_macros, 0, 0,
+ VAR_MILT_PROTOCOL, DEF_MILT_PROTOCOL, &var_milt_protocol, 1, 0,
+ VAR_MILT_DEF_ACTION, DEF_MILT_DEF_ACTION, &var_milt_def_action, 1, 0,
+ VAR_MILT_DAEMON_NAME, DEF_MILT_DAEMON_NAME, &var_milt_daemon_name, 1, 0,
+ VAR_MILT_V, DEF_MILT_V, &var_milt_v, 1, 0,
+ VAR_MILT_MACRO_DEFLTS, DEF_MILT_MACRO_DEFLTS, &var_milt_macro_deflts, 0, 0,
+ VAR_SMTPD_MILTER_MAPS, DEF_SMTPD_MILTER_MAPS, &var_smtpd_milter_maps, 0, 0,
+ VAR_STRESS, DEF_STRESS, &var_stress, 0, 0,
+ VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why, 0, 0,
+ VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why, 0, 0,
+ VAR_REJECT_TMPF_ACT, DEF_REJECT_TMPF_ACT, &var_reject_tmpf_act, 1, 0,
+ VAR_UNK_NAME_TF_ACT, DEF_UNK_NAME_TF_ACT, &var_unk_name_tf_act, 1, 0,
+ VAR_UNK_ADDR_TF_ACT, DEF_UNK_ADDR_TF_ACT, &var_unk_addr_tf_act, 1, 0,
+ VAR_UNV_RCPT_TF_ACT, DEF_UNV_RCPT_TF_ACT, &var_unv_rcpt_tf_act, 1, 0,
+ VAR_UNV_FROM_TF_ACT, DEF_UNV_FROM_TF_ACT, &var_unv_from_tf_act, 1, 0,
+ VAR_SMTPD_CMD_FILTER, DEF_SMTPD_CMD_FILTER, &var_smtpd_cmd_filter, 0, 0,
+#ifdef USE_TLSPROXY
+ VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0,
+#endif
+ VAR_SMTPD_ACL_PERM_LOG, DEF_SMTPD_ACL_PERM_LOG, &var_smtpd_acl_perm_log, 0, 0,
+ VAR_SMTPD_UPROXY_PROTO, DEF_SMTPD_UPROXY_PROTO, &var_smtpd_uproxy_proto, 0, 0,
+ VAR_SMTPD_POLICY_DEF_ACTION, DEF_SMTPD_POLICY_DEF_ACTION, &var_smtpd_policy_def_action, 1, 0,
+ VAR_SMTPD_POLICY_CONTEXT, DEF_SMTPD_POLICY_CONTEXT, &var_smtpd_policy_context, 0, 0,
+ VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter, 0, 0,
+ VAR_SMTPD_REJ_FTR_MAPS, DEF_SMTPD_REJ_FTR_MAPS, &var_smtpd_rej_ftr_maps, 0, 0,
+ VAR_SMTPD_FORBID_BARE_LF_EXCL, DEF_SMTPD_FORBID_BARE_LF_EXCL, &var_smtpd_forbid_bare_lf_excl, 0, 0,
+ VAR_SMTPD_FORBID_BARE_LF, DEF_SMTPD_FORBID_BARE_LF, &var_smtpd_forbid_bare_lf, 1, 0,
+ 0,
+ };
+ static const CONFIG_RAW_TABLE raw_table[] = {
+ VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter, 1, 0,
+ VAR_DEF_RBL_REPLY, DEF_DEF_RBL_REPLY, &var_def_rbl_reply, 1, 0,
+ VAR_SMTPD_REJ_FOOTER, DEF_SMTPD_REJ_FOOTER, &var_smtpd_rej_footer, 0, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Pass control to the single-threaded service skeleton.
+ */
+ single_server_main(argc, argv, smtpd_service,
+ CA_MAIL_SERVER_NINT_TABLE(nint_table),
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_LONG_TABLE(long_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_RAW_TABLE(raw_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_NBOOL_TABLE(nbool_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ 0);
+}
diff --git a/src/smtpd/smtpd.h b/src/smtpd/smtpd.h
new file mode 100644
index 0000000..490cda2
--- /dev/null
+++ b/src/smtpd/smtpd.h
@@ -0,0 +1,441 @@
+/*++
+/* NAME
+/* smtpd 3h
+/* SUMMARY
+/* smtp server
+/* SYNOPSIS
+/* include "smtpd.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <argv.h>
+#include <myaddrinfo.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_stream.h>
+
+ /*
+ * Postfix TLS library.
+ */
+#include <tls.h>
+
+ /*
+ * Milter library.
+ */
+#include <milter.h>
+
+ /*
+ * Variables that keep track of conversation state. There is only one SMTP
+ * conversation at a time, so the state variables can be made global. And
+ * some of this has to be global anyway, so that the run-time error handler
+ * can clean up in case of a fatal error deep down in some library routine.
+ */
+typedef struct SMTPD_DEFER {
+ int active; /* is this active */
+ VSTRING *reason; /* reason for deferral */
+ VSTRING *dsn; /* DSN detail */
+ int code; /* SMTP reply code */
+ int class; /* error notification class */
+} SMTPD_DEFER;
+
+typedef struct {
+ int flags; /* XFORWARD server state */
+ char *name; /* name for access control */
+ char *addr; /* address for access control */
+ char *port; /* port for logging */
+ char *namaddr; /* name[address]:port */
+ char *rfc_addr; /* address for RFC 2821 */
+ char *protocol; /* email protocol */
+ char *helo_name; /* helo/ehlo parameter */
+ char *ident; /* local message identifier */
+ char *domain; /* rewrite context */
+} SMTPD_XFORWARD_ATTR;
+
+typedef struct {
+ int flags; /* see below */
+ int err; /* cleanup server/queue file errors */
+ VSTREAM *client; /* SMTP client handle */
+ VSTRING *buffer; /* SMTP client buffer */
+ VSTRING *addr_buf; /* internalized address buffer */
+ char *service; /* for event rate control */
+ struct timeval arrival_time; /* start of MAIL FROM transaction */
+ char *name; /* verified client hostname */
+ char *reverse_name; /* unverified client hostname */
+ char *addr; /* client host address string */
+ char *port; /* port for logging */
+ char *namaddr; /* name[address]:port */
+ char *rfc_addr; /* address for RFC 2821 */
+ int addr_family; /* address family */
+ char *dest_addr; /* Dovecot AUTH, Milter {daemon_addr} */
+ char *dest_port; /* Milter {daemon_port} */
+ struct sockaddr_storage sockaddr; /* binary client endpoint */
+ SOCKADDR_SIZE sockaddr_len; /* binary client endpoint */
+ struct sockaddr_storage dest_sockaddr; /* binary local endpoint */
+ SOCKADDR_SIZE dest_sockaddr_len; /* binary local endpoint */
+ int name_status; /* 2=ok 4=soft 5=hard 6=forged */
+ int reverse_name_status; /* 2=ok 4=soft 5=hard */
+ int conn_count; /* connections from this client */
+ int conn_rate; /* connection rate for this client */
+ int error_count; /* reset after DOT */
+ int error_mask; /* client errors */
+ int notify_mask; /* what to report to postmaster */
+ char *helo_name; /* client HELO/EHLO argument */
+ char *queue_id; /* from cleanup server/queue file */
+ VSTREAM *cleanup; /* cleanup server/queue file handle */
+ MAIL_STREAM *dest; /* another server/file handle */
+ int rcpt_count; /* number of accepted recipients */
+ char *access_denied; /* fixme */
+ ARGV *history; /* protocol transcript */
+ char *reason; /* cause of connection loss */
+ char *sender; /* sender address */
+ char *encoding; /* owned by mail_cmd() */
+ char *verp_delims; /* owned by mail_cmd() */
+ char *recipient; /* recipient address */
+ char *etrn_name; /* client ETRN argument */
+ char *protocol; /* SMTP or ESMTP */
+ char *where; /* protocol stage */
+ int recursion; /* Kellerspeicherpegelanzeiger */
+ off_t msg_size; /* MAIL FROM message size */
+ off_t act_size; /* END-OF-DATA message size */
+ int junk_cmds; /* counter */
+ int rcpt_overshoot; /* counter */
+ char *rewrite_context; /* address rewriting context */
+
+ /*
+ * SASL specific.
+ */
+#ifdef USE_SASL_AUTH
+ struct XSASL_SERVER *sasl_server;
+ VSTRING *sasl_reply;
+ char *sasl_mechanism_list;
+ char *sasl_method;
+ char *sasl_username;
+ char *sasl_sender;
+#endif
+
+ /*
+ * Specific to smtpd access checks.
+ */
+ int sender_rcptmap_checked; /* sender validated against maps */
+ int recipient_rcptmap_checked; /* recipient validated against maps */
+ int warn_if_reject; /* force reject into warning */
+ SMTPD_DEFER defer_if_reject; /* force reject into deferral */
+ SMTPD_DEFER defer_if_permit; /* force permit into deferral */
+ int defer_if_permit_client; /* force permit into warning */
+ int defer_if_permit_helo; /* force permit into warning */
+ int defer_if_permit_sender; /* force permit into warning */
+ int discard; /* discard message */
+ char *saved_filter; /* postponed filter action */
+ char *saved_redirect; /* postponed redirect action */
+ ARGV *saved_bcc; /* postponed bcc action */
+ int saved_flags; /* postponed hold/discard */
+#ifdef DELAY_ACTION
+ int saved_delay; /* postponed deferred delay */
+#endif
+ VSTRING *expand_buf; /* scratch space for $name expansion */
+ ARGV *prepend; /* prepended headers */
+ VSTRING *instance; /* policy query correlation */
+ int seqno; /* policy query correlation */
+ int ehlo_discard_mask; /* suppressed EHLO features */
+ char *dsn_envid; /* temporary MAIL FROM state */
+ int dsn_ret; /* temporary MAIL FROM state */
+ VSTRING *dsn_buf; /* scratch space for xtext expansion */
+ VSTRING *dsn_orcpt_buf; /* scratch space for ORCPT parsing */
+
+ /*
+ * Pass-through proxy client.
+ */
+ struct SMTPD_PROXY *proxy;
+ char *proxy_mail; /* owned by mail_cmd() */
+
+ /*
+ * XFORWARD server state.
+ */
+ SMTPD_XFORWARD_ATTR xforward; /* up-stream logging info */
+
+ /*
+ * TLS related state.
+ */
+#ifdef USE_TLS
+#ifdef USE_TLSPROXY
+ VSTREAM *tlsproxy; /* tlsproxy(8) temp. handle */
+#endif
+ TLS_SESS_STATE *tls_context; /* TLS session state */
+#endif
+
+ /*
+ * Milter support.
+ */
+ const char **milter_argv; /* SMTP command vector */
+ ssize_t milter_argc; /* SMTP command vector */
+ const char *milter_reject_text; /* input to call-back from Milter */
+ MILTERS *milters; /* Milter initialization status. */
+
+ /*
+ * EHLO temporary space.
+ */
+ VSTRING *ehlo_buf;
+ ARGV *ehlo_argv;
+
+ /*
+ * BDAT processing state.
+ */
+#define SMTPD_BDAT_STAT_NONE 0 /* not processing BDAT */
+#define SMTPD_BDAT_STAT_OK 1 /* accepting BDAT chunks */
+#define SMTPD_BDAT_STAT_ERROR 2 /* skipping BDAT chunks */
+ int bdat_state; /* see above */
+ VSTREAM *bdat_get_stream; /* memory stream from BDAT chunk */
+ VSTRING *bdat_get_buffer; /* read from memory stream */
+ int bdat_prev_rec_type;
+} SMTPD_STATE;
+
+#define SMTPD_FLAG_HANGUP (1<<0) /* 421/521 disconnect */
+#define SMTPD_FLAG_ILL_PIPELINING (1<<1) /* inappropriate pipelining */
+#define SMTPD_FLAG_AUTH_USED (1<<2) /* don't reuse SASL state */
+#define SMTPD_FLAG_SMTPUTF8 (1<<3) /* RFC 6531/2 transaction */
+#define SMTPD_FLAG_NEED_MILTER_ABORT (1<<4) /* undo milter_mail_event() */
+
+ /* Security: don't reset SMTPD_FLAG_AUTH_USED. */
+#define SMTPD_MASK_MAIL_KEEP \
+ ~(SMTPD_FLAG_SMTPUTF8) /* Fix 20140706 */
+
+#define SMTPD_STATE_XFORWARD_INIT (1<<0) /* xforward preset done */
+#define SMTPD_STATE_XFORWARD_NAME (1<<1) /* client name received */
+#define SMTPD_STATE_XFORWARD_ADDR (1<<2) /* client address received */
+#define SMTPD_STATE_XFORWARD_PROTO (1<<3) /* protocol received */
+#define SMTPD_STATE_XFORWARD_HELO (1<<4) /* client helo received */
+#define SMTPD_STATE_XFORWARD_IDENT (1<<5) /* message identifier */
+#define SMTPD_STATE_XFORWARD_DOMAIN (1<<6) /* address context */
+#define SMTPD_STATE_XFORWARD_PORT (1<<7) /* client port received */
+
+#define SMTPD_STATE_XFORWARD_CLIENT_MASK \
+ (SMTPD_STATE_XFORWARD_NAME | SMTPD_STATE_XFORWARD_ADDR \
+ | SMTPD_STATE_XFORWARD_PROTO | SMTPD_STATE_XFORWARD_HELO \
+ | SMTPD_STATE_XFORWARD_PORT)
+
+extern void smtpd_state_init(SMTPD_STATE *, VSTREAM *, const char *);
+extern void smtpd_state_reset(SMTPD_STATE *);
+
+ /*
+ * Conversation stages. This is used for "lost connection after XXX"
+ * diagnostics.
+ */
+#define SMTPD_AFTER_CONNECT "CONNECT"
+#define SMTPD_AFTER_DATA "DATA content"
+#define SMTPD_AFTER_BDAT "BDAT content"
+#define SMTPD_AFTER_EOM "END-OF-MESSAGE"
+
+ /*
+ * Other stages. These are sometimes used to change the way information is
+ * logged or what information will be available for access control.
+ */
+#define SMTPD_CMD_HELO "HELO"
+#define SMTPD_CMD_EHLO "EHLO"
+#define SMTPD_CMD_STARTTLS "STARTTLS"
+#define SMTPD_CMD_AUTH "AUTH"
+#define SMTPD_CMD_MAIL "MAIL"
+#define SMTPD_CMD_RCPT "RCPT"
+#define SMTPD_CMD_DATA "DATA"
+#define SMTPD_CMD_BDAT "BDAT"
+#define SMTPD_CMD_EOD SMTPD_AFTER_EOM /* XXX Was: END-OF-DATA */
+#define SMTPD_CMD_RSET "RSET"
+#define SMTPD_CMD_NOOP "NOOP"
+#define SMTPD_CMD_VRFY "VRFY"
+#define SMTPD_CMD_ETRN "ETRN"
+#define SMTPD_CMD_QUIT "QUIT"
+#define SMTPD_CMD_XCLIENT "XCLIENT"
+#define SMTPD_CMD_XFORWARD "XFORWARD"
+#define SMTPD_CMD_UNKNOWN "UNKNOWN"
+
+ /*
+ * Representation of unknown and non-existent client information. Throughout
+ * Postfix, we use the "unknown" string value for unknown client information
+ * (e.g., unknown remote client hostname), and we use the empty string, null
+ * pointer or "no queue file record" for non-existent client information
+ * (e.g., no HELO command, or local submission).
+ *
+ * Inside the SMTP server, unknown real client attributes are represented by
+ * the string "unknown", and non-existent HELO is represented as a null
+ * pointer. The SMTP server uses this same representation internally for
+ * forwarded client attributes; the XFORWARD syntax makes no distinction
+ * between unknown (remote submission) and non-existent (local submission).
+ *
+ * The SMTP client sends forwarded client attributes only when upstream client
+ * attributes exist (i.e. remote submission). Thus, local submissions will
+ * appear to come from an SMTP-based content filter, which is acceptable.
+ *
+ * Known/unknown client attribute values use the SMTP server's internal
+ * representation in queue files, in queue manager delivery requests, and in
+ * delivery agent $name expansions.
+ *
+ * Non-existent attribute values are never present in queue files. Non-existent
+ * information is represented as empty strings in queue manager delivery
+ * requests and in delivery agent $name expansions.
+ */
+#define CLIENT_ATTR_UNKNOWN "unknown"
+
+#define CLIENT_NAME_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_ADDR_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_PORT_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_NAMADDR_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_HELO_UNKNOWN 0
+#define CLIENT_PROTO_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_IDENT_UNKNOWN 0
+#define CLIENT_DOMAIN_UNKNOWN 0
+#define CLIENT_LOGIN_UNKNOWN 0
+
+#define SERVER_ATTR_UNKNOWN "unknown"
+
+#define SERVER_ADDR_UNKNOWN SERVER_ATTR_UNKNOWN
+#define SERVER_PORT_UNKNOWN SERVER_ATTR_UNKNOWN
+
+#define IS_AVAIL_CLIENT_ATTR(v) ((v) && strcmp((v), CLIENT_ATTR_UNKNOWN))
+
+#define IS_AVAIL_CLIENT_NAME(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_ADDR(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_PORT(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_NAMADDR(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_HELO(v) ((v) != 0)
+#define IS_AVAIL_CLIENT_PROTO(v) IS_AVAIL_CLIENT_ATTR(v)
+#define IS_AVAIL_CLIENT_IDENT(v) ((v) != 0)
+#define IS_AVAIL_CLIENT_DOMAIN(v) ((v) != 0)
+
+ /*
+ * If running in stand-alone mode, do not try to talk to Postfix daemons but
+ * write to queue file instead.
+ */
+#define SMTPD_STAND_ALONE_STREAM(stream) \
+ (stream == VSTREAM_IN && getuid() != var_owner_uid)
+
+#define SMTPD_STAND_ALONE(state) \
+ (state->client == VSTREAM_IN && getuid() != var_owner_uid)
+
+ /*
+ * If running as proxy front-end, disable actions that require communication
+ * with the cleanup server.
+ */
+#define USE_SMTPD_PROXY(state) \
+ (SMTPD_STAND_ALONE(state) == 0 && *var_smtpd_proxy_filt)
+
+ /*
+ * Are we in a MAIL transaction?
+ */
+#define SMTPD_IN_MAIL_TRANSACTION(state) ((state)->sender != 0)
+
+ /*
+ * Are we processing BDAT requests?
+ */
+#define SMTPD_PROCESSING_BDAT(state) \
+ ((state)->bdat_state != SMTPD_BDAT_STAT_NONE)
+
+ /*
+ * SMTPD peer information lookup.
+ */
+extern void smtpd_peer_init(SMTPD_STATE *state);
+extern void smtpd_peer_reset(SMTPD_STATE *state);
+extern void smtpd_peer_from_default(SMTPD_STATE *);
+extern int smtpd_peer_from_haproxy(SMTPD_STATE *);
+
+#define SMTPD_PEER_CODE_OK 2
+#define SMTPD_PEER_CODE_TEMP 4
+#define SMTPD_PEER_CODE_PERM 5
+#define SMTPD_PEER_CODE_FORGED 6
+
+ /*
+ * Construct name[addr] or name[addr]:port as appropriate
+ */
+#define SMTPD_BUILD_NAMADDRPORT(name, addr, port) \
+ concatenate((name), "[", (addr), "]", \
+ var_smtpd_client_port_log ? ":" : (char *) 0, \
+ (port), (char *) 0)
+
+ /*
+ * Don't mix information from the current SMTP session with forwarded
+ * information from an up-stream session.
+ */
+#define HAVE_FORWARDED_CLIENT_ATTR(s) \
+ ((s)->xforward.flags & SMTPD_STATE_XFORWARD_CLIENT_MASK)
+
+#define FORWARD_CLIENT_ATTR(s, a) \
+ (HAVE_FORWARDED_CLIENT_ATTR(s) ? \
+ (s)->xforward.a : (s)->a)
+
+#define FORWARD_ADDR(s) FORWARD_CLIENT_ATTR((s), rfc_addr)
+#define FORWARD_NAME(s) FORWARD_CLIENT_ATTR((s), name)
+#define FORWARD_NAMADDR(s) FORWARD_CLIENT_ATTR((s), namaddr)
+#define FORWARD_PROTO(s) FORWARD_CLIENT_ATTR((s), protocol)
+#define FORWARD_HELO(s) FORWARD_CLIENT_ATTR((s), helo_name)
+#define FORWARD_PORT(s) FORWARD_CLIENT_ATTR((s), port)
+
+ /*
+ * Mixing is not a problem with forwarded local message identifiers.
+ */
+#define HAVE_FORWARDED_IDENT(s) \
+ ((s)->xforward.ident != 0)
+
+#define FORWARD_IDENT(s) \
+ (HAVE_FORWARDED_IDENT(s) ? \
+ (s)->xforward.ident : (s)->queue_id)
+
+ /*
+ * Mixing is not a problem with forwarded address rewriting contexts.
+ */
+#define FORWARD_DOMAIN(s) \
+ (((s)->xforward.flags & SMTPD_STATE_XFORWARD_DOMAIN) ? \
+ (s)->xforward.domain : (s)->rewrite_context)
+
+extern void smtpd_xforward_init(SMTPD_STATE *);
+extern void smtpd_xforward_preset(SMTPD_STATE *);
+extern void smtpd_xforward_reset(SMTPD_STATE *);
+
+ /*
+ * Transparency: before mail is queued, do we check for unknown recipients,
+ * do we allow address mapping, automatic bcc, header/body checks?
+ */
+extern int smtpd_input_transp_mask;
+
+ /*
+ * More Milter support.
+ */
+extern MILTERS *smtpd_milters;
+
+ /*
+ * Message size multiplication factor for free space check.
+ */
+extern double smtpd_space_multf;
+
+/* 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
+/*--*/
diff --git a/src/smtpd/smtpd_acl.in b/src/smtpd/smtpd_acl.in
new file mode 100644
index 0000000..45d1d4f
--- /dev/null
+++ b/src/smtpd/smtpd_acl.in
@@ -0,0 +1,120 @@
+#
+# Initialize
+#
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.189.0/28
+relay_domains porcupine.org
+smtpd_null_access_lookup_key <>
+#
+# Test check_domain_access()
+#
+helo_restrictions hash:./smtpd_check_access
+# Expect: REJECT
+helo foo.dunno.com
+# Expect: OK
+helo bar.dunno.com
+# Expect: OK
+helo foo.duuno.com
+#
+# Test check_namadr_access(), domain part
+#
+client_restrictions hash:./smtpd_check_access
+# Expect: REJECT
+client foo.dunno.com 131.155.210.17
+# Expect: OK
+client bar.dunno.com 131.155.210.17
+# Expect: OK
+client bar.dunno.com 131.155.210.19
+#
+# Test check_namadr_access(), address part
+#
+# Expect: OK
+client bar.duno.com 131.155.210.17
+# Expect: REJECT
+client bar.duno.com 131.155.210.19
+# Expect: REJECT
+client bar.duno.com 44.33.22.11
+# Expect: OK
+client bar.duno.com 44.33.22.55
+# Expect: REJECT
+client bar.duno.com 44.33.44.33
+#
+# Test check_mail_access()
+#
+sender_restrictions hash:./smtpd_check_access
+# Expect: REJECT
+mail reject@dunno.domain
+# Expect: OK
+mail ok@dunno.domain
+# Expect: OK
+mail anyone@dunno.domain
+# Expect: OK
+mail bad-sender@dunno.domain
+#
+# Again, with a domain that rejects by default
+#
+# Expect: REJECT
+mail reject@reject.domain
+# Expect: OK
+mail ok@reject.domain
+# Expect: REJECT
+mail anyone@reject.domain
+# Expect: REJECT
+mail good-sender@reject.domain
+#
+# Again, with a domain that accepts by default
+#
+# Expect: REJECT
+mail reject@ok.domain
+# Expect: OK
+mail ok@ok.domain
+# Expect: OK
+mail anyone@ok.domain
+# Expect: OK
+mail bad-sender@ok.domain
+#
+# Test check_mail_access()
+#
+recipient_restrictions hash:./smtpd_check_access
+# Expect: REJECT
+rcpt reject@dunno.domain
+# Expect: REJECT
+recipient_delimiter +
+rcpt reject+ext@dunno.domain
+recipient_delimiter |
+# Expect: OK
+rcpt ok@dunno.domain
+# Expect: OK
+recipient_delimiter +
+rcpt ok+ext@dunno.domain
+recipient_delimiter |
+# Expect: OK
+rcpt anyone@dunno.domain
+# Expect: OK
+rcpt bad-sender@dunno.domain
+#
+# Again, with a domain that rejects by default
+#
+# Expect: REJECT
+rcpt reject@reject.domain
+# Expect: OK
+rcpt ok@reject.domain
+# Expect: REJECT
+rcpt anyone@reject.domain
+# Expect: REJECT
+rcpt good-sender@reject.domain
+#
+# Again, with a domain that accepts by default
+#
+# Expect: REJECT
+rcpt reject@ok.domain
+# Expect: OK
+rcpt ok@ok.domain
+# Expect: OK
+rcpt anyone@ok.domain
+# Expect: OK
+rcpt bad-sender@ok.domain
+#
+# check_sender_access specific
+#
+mail <>
diff --git a/src/smtpd/smtpd_acl.ref b/src/smtpd/smtpd_acl.ref
new file mode 100644
index 0000000..110e7a5
--- /dev/null
+++ b/src/smtpd/smtpd_acl.ref
@@ -0,0 +1,187 @@
+>>> #
+>>> # Initialize
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.189.0/28
+OK
+>>> relay_domains porcupine.org
+OK
+>>> smtpd_null_access_lookup_key <>
+OK
+>>> #
+>>> # Test check_domain_access()
+>>> #
+>>> helo_restrictions hash:./smtpd_check_access
+OK
+>>> # Expect: REJECT
+>>> helo foo.dunno.com
+./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 554 5.7.1 <foo.dunno.com>: Helo command rejected: Access denied; proto=SMTP helo=<foo.dunno.com>
+554 5.7.1 <foo.dunno.com>: Helo command rejected: Access denied
+>>> # Expect: OK
+>>> helo bar.dunno.com
+OK
+>>> # Expect: OK
+>>> helo foo.duuno.com
+OK
+>>> #
+>>> # Test check_namadr_access(), domain part
+>>> #
+>>> client_restrictions hash:./smtpd_check_access
+OK
+>>> # Expect: REJECT
+>>> client foo.dunno.com 131.155.210.17
+./smtpd_check: <queue id>: reject: CONNECT from foo.dunno.com[131.155.210.17]: 554 5.7.1 <foo.dunno.com[131.155.210.17]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <foo.dunno.com[131.155.210.17]>: Client host rejected: Access denied
+>>> # Expect: OK
+>>> client bar.dunno.com 131.155.210.17
+OK
+>>> # Expect: OK
+>>> client bar.dunno.com 131.155.210.19
+OK
+>>> #
+>>> # Test check_namadr_access(), address part
+>>> #
+>>> # Expect: OK
+>>> client bar.duno.com 131.155.210.17
+OK
+>>> # Expect: REJECT
+>>> client bar.duno.com 131.155.210.19
+./smtpd_check: <queue id>: reject: CONNECT from bar.duno.com[131.155.210.19]: 554 5.7.1 <bar.duno.com[131.155.210.19]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <bar.duno.com[131.155.210.19]>: Client host rejected: Access denied
+>>> # Expect: REJECT
+>>> client bar.duno.com 44.33.22.11
+./smtpd_check: <queue id>: reject: CONNECT from bar.duno.com[44.33.22.11]: 554 5.7.1 <bar.duno.com[44.33.22.11]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <bar.duno.com[44.33.22.11]>: Client host rejected: Access denied
+>>> # Expect: OK
+>>> client bar.duno.com 44.33.22.55
+OK
+>>> # Expect: REJECT
+>>> client bar.duno.com 44.33.44.33
+./smtpd_check: <queue id>: reject: CONNECT from bar.duno.com[44.33.44.33]: 554 5.7.1 <bar.duno.com[44.33.44.33]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <bar.duno.com[44.33.44.33]>: Client host rejected: Access denied
+>>> #
+>>> # Test check_mail_access()
+>>> #
+>>> sender_restrictions hash:./smtpd_check_access
+OK
+>>> # Expect: REJECT
+>>> mail reject@dunno.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@dunno.domain>: Sender address rejected: Access denied; from=<reject@dunno.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@dunno.domain>: Sender address rejected: Access denied
+>>> # Expect: OK
+>>> mail ok@dunno.domain
+OK
+>>> # Expect: OK
+>>> mail anyone@dunno.domain
+OK
+>>> # Expect: OK
+>>> mail bad-sender@dunno.domain
+OK
+>>> #
+>>> # Again, with a domain that rejects by default
+>>> #
+>>> # Expect: REJECT
+>>> mail reject@reject.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@reject.domain>: Sender address rejected: Access denied; from=<reject@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@reject.domain>: Sender address rejected: Access denied
+>>> # Expect: OK
+>>> mail ok@reject.domain
+OK
+>>> # Expect: REJECT
+>>> mail anyone@reject.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <anyone@reject.domain>: Sender address rejected: Access denied; from=<anyone@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <anyone@reject.domain>: Sender address rejected: Access denied
+>>> # Expect: REJECT
+>>> mail good-sender@reject.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <good-sender@reject.domain>: Sender address rejected: Access denied; from=<good-sender@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <good-sender@reject.domain>: Sender address rejected: Access denied
+>>> #
+>>> # Again, with a domain that accepts by default
+>>> #
+>>> # Expect: REJECT
+>>> mail reject@ok.domain
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@ok.domain>: Sender address rejected: Access denied; from=<reject@ok.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@ok.domain>: Sender address rejected: Access denied
+>>> # Expect: OK
+>>> mail ok@ok.domain
+OK
+>>> # Expect: OK
+>>> mail anyone@ok.domain
+OK
+>>> # Expect: OK
+>>> mail bad-sender@ok.domain
+OK
+>>> #
+>>> # Test check_mail_access()
+>>> #
+>>> recipient_restrictions hash:./smtpd_check_access
+OK
+>>> # Expect: REJECT
+>>> rcpt reject@dunno.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@dunno.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject@dunno.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@dunno.domain>: Recipient address rejected: Access denied
+>>> # Expect: REJECT
+>>> recipient_delimiter +
+OK
+>>> rcpt reject+ext@dunno.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject+ext@dunno.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject+ext@dunno.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject+ext@dunno.domain>: Recipient address rejected: Access denied
+>>> recipient_delimiter |
+OK
+>>> # Expect: OK
+>>> rcpt ok@dunno.domain
+OK
+>>> # Expect: OK
+>>> recipient_delimiter +
+OK
+>>> rcpt ok+ext@dunno.domain
+OK
+>>> recipient_delimiter |
+OK
+>>> # Expect: OK
+>>> rcpt anyone@dunno.domain
+OK
+>>> # Expect: OK
+>>> rcpt bad-sender@dunno.domain
+OK
+>>> #
+>>> # Again, with a domain that rejects by default
+>>> #
+>>> # Expect: REJECT
+>>> rcpt reject@reject.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@reject.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@reject.domain>: Recipient address rejected: Access denied
+>>> # Expect: OK
+>>> rcpt ok@reject.domain
+OK
+>>> # Expect: REJECT
+>>> rcpt anyone@reject.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <anyone@reject.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<anyone@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <anyone@reject.domain>: Recipient address rejected: Access denied
+>>> # Expect: REJECT
+>>> rcpt good-sender@reject.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <good-sender@reject.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<good-sender@reject.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <good-sender@reject.domain>: Recipient address rejected: Access denied
+>>> #
+>>> # Again, with a domain that accepts by default
+>>> #
+>>> # Expect: REJECT
+>>> rcpt reject@ok.domain
+./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@ok.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject@ok.domain> proto=SMTP helo=<foo.duuno.com>
+554 5.7.1 <reject@ok.domain>: Recipient address rejected: Access denied
+>>> # Expect: OK
+>>> rcpt ok@ok.domain
+OK
+>>> # Expect: OK
+>>> rcpt anyone@ok.domain
+OK
+>>> # Expect: OK
+>>> rcpt bad-sender@ok.domain
+OK
+>>> #
+>>> # check_sender_access specific
+>>> #
+>>> mail <>
+./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 550 5.7.1 <>: Sender address rejected: Go away postmaster; from=<> proto=SMTP helo=<foo.duuno.com>
+550 5.7.1 <>: Sender address rejected: Go away postmaster
diff --git a/src/smtpd/smtpd_addr_valid.in b/src/smtpd/smtpd_addr_valid.in
new file mode 100644
index 0000000..e89537d
--- /dev/null
+++ b/src/smtpd/smtpd_addr_valid.in
@@ -0,0 +1,35 @@
+#
+# Initialize
+#
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.189.0/28
+local_recipient_maps inline:{foo_canon=whatever,bar_canon=whatever}
+mydestination example.com
+myorigin example.com
+
+sender_canonical_maps inline:{foo@example.com=foo_canon@example.com}
+recipient_canonical_maps inline:{bar@example.com=bar_canon@example.com}
+
+sender_restrictions reject_unlisted_sender
+# Expect accept
+mail bar_canon@example.com
+# Expect accept
+mail bar@example.com
+# Expect accept
+mail foo_canon@example.com
+# Expect accept
+mail foo@example.com
+# Expect reject
+mail baz@example.com
+
+recipient_restrictions reject_unlisted_recipient
+# Expect accept
+rcpt bar_canon@example.com
+# Expect accept
+rcpt bar@example.com
+# Expect accept
+rcpt foo_canon@example.com
+# Expect reject
+rcpt foo@example.com
+# Expect reject
+mail baz@example.com
diff --git a/src/smtpd/smtpd_addr_valid.ref b/src/smtpd/smtpd_addr_valid.ref
new file mode 100644
index 0000000..768a4c4
--- /dev/null
+++ b/src/smtpd/smtpd_addr_valid.ref
@@ -0,0 +1,57 @@
+>>> #
+>>> # Initialize
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.189.0/28
+OK
+>>> local_recipient_maps inline:{foo_canon=whatever,bar_canon=whatever}
+OK
+>>> mydestination example.com
+OK
+>>> myorigin example.com
+OK
+>>>
+>>> sender_canonical_maps inline:{foo@example.com=foo_canon@example.com}
+OK
+>>> recipient_canonical_maps inline:{bar@example.com=bar_canon@example.com}
+OK
+>>>
+>>> sender_restrictions reject_unlisted_sender
+OK
+>>> # Expect accept
+>>> mail bar_canon@example.com
+OK
+>>> # Expect accept
+>>> mail bar@example.com
+OK
+>>> # Expect accept
+>>> mail foo_canon@example.com
+OK
+>>> # Expect accept
+>>> mail foo@example.com
+OK
+>>> # Expect reject
+>>> mail baz@example.com
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table; from=<baz@example.com> proto=SMTP
+550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table
+>>>
+>>> recipient_restrictions reject_unlisted_recipient
+OK
+>>> # Expect accept
+>>> rcpt bar_canon@example.com
+OK
+>>> # Expect accept
+>>> rcpt bar@example.com
+OK
+>>> # Expect accept
+>>> rcpt foo_canon@example.com
+OK
+>>> # Expect reject
+>>> rcpt foo@example.com
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 550 5.1.1 <foo@example.com>: Recipient address rejected: User unknown in local recipient table; from=<baz@example.com> to=<foo@example.com> proto=SMTP
+550 5.1.1 <foo@example.com>: Recipient address rejected: User unknown in local recipient table
+>>> # Expect reject
+>>> mail baz@example.com
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table; from=<baz@example.com> proto=SMTP
+550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table
diff --git a/src/smtpd/smtpd_chat.c b/src/smtpd/smtpd_chat.c
new file mode 100644
index 0000000..c172ab3
--- /dev/null
+++ b/src/smtpd/smtpd_chat.c
@@ -0,0 +1,345 @@
+/*++
+/* NAME
+/* smtpd_chat 3
+/* SUMMARY
+/* SMTP server request/response support
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_chat.h>
+/*
+/* void smtpd_chat_pre_jail_init(void)
+/*
+/* int smtpd_chat_query_limit(state, limit)
+/* SMTPD_STATE *state;
+/* int limit;
+/*
+/* void smtpd_chat_query(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_chat_reply(state, format, ...)
+/* SMTPD_STATE *state;
+/* char *format;
+/*
+/* void smtpd_chat_notify(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_chat_reset(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* This module implements SMTP server support for request/reply
+/* conversations, and maintains a limited SMTP transaction log.
+/*
+/* smtpd_chat_pre_jail_init() performs one-time initialization.
+/*
+/* smtpd_chat_query_limit() reads a line from the client that is
+/* at most "limit" bytes long. A copy is appended to the SMTP
+/* transaction log. The return value is non-zero for a complete
+/* line or else zero if the length limit was exceeded.
+/*
+/* smtpd_chat_query() receives a client request and appends a copy
+/* to the SMTP transaction log.
+/*
+/* smtpd_chat_reply() formats a server reply, sends it to the
+/* client, and appends a copy to the SMTP transaction log.
+/* When soft_bounce is enabled, all 5xx (reject) responses are
+/* replaced by 4xx (try again). In case of a 421 reply the
+/* SMTPD_FLAG_HANGUP flag is set for orderly disconnect.
+/*
+/* smtpd_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
+/* smtpd_chat_notify() when no SMTP transaction log exists.
+/*
+/* smtpd_chat_reset() resets the transaction log. This is
+/* typically done at the beginning of an SMTP session, or
+/* within a session to discard non-error information.
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal errors: out of memory.
+/* internal protocol errors.
+/* 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 <setjmp.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <argv.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <line_wrap.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <mail_addr.h>
+#include <maps.h>
+#include <post_mail.h>
+#include <mail_error.h>
+#include <smtp_reply_footer.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_expand.h"
+#include "smtpd_chat.h"
+
+ /*
+ * Reject footer.
+ */
+static MAPS *smtpd_rej_ftr_maps;
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* smtpd_chat_pre_jail_init - initialize */
+
+void smtpd_chat_pre_jail_init(void)
+{
+ static int init_count = 0;
+
+ if (init_count++ != 0)
+ msg_panic("smtpd_chat_pre_jail_init: multiple calls");
+
+ /*
+ * SMTP server reject footer.
+ */
+ if (*var_smtpd_rej_ftr_maps)
+ smtpd_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS,
+ var_smtpd_rej_ftr_maps,
+ DICT_FLAG_LOCK);
+}
+
+/* smtp_chat_reset - reset SMTP transaction log */
+
+void smtpd_chat_reset(SMTPD_STATE *state)
+{
+ if (state->history) {
+ argv_free(state->history);
+ state->history = 0;
+ }
+}
+
+/* smtp_chat_append - append record to SMTP transaction log */
+
+static void smtp_chat_append(SMTPD_STATE *state, char *direction,
+ const char *text)
+{
+ char *line;
+
+ if (state->notify_mask == 0)
+ return;
+
+ if (state->history == 0)
+ state->history = argv_alloc(10);
+ line = concatenate(direction, text, (char *) 0);
+ argv_add(state->history, line, (char *) 0);
+ myfree(line);
+}
+
+/* smtpd_chat_query - receive and record an SMTP request */
+
+int smtpd_chat_query_limit(SMTPD_STATE *state, int limit)
+{
+ int last_char;
+
+ /*
+ * We can't parse or store input that exceeds var_line_limit, so we skip
+ * over it to avoid loss of synchronization.
+ */
+ last_char = smtp_get(state->buffer, state->client, limit,
+ SMTP_GET_FLAG_SKIP);
+ smtp_chat_append(state, "In: ", STR(state->buffer));
+ if (last_char != '\n')
+ msg_warn("%s: request longer than %d: %.30s...",
+ state->namaddr, limit,
+ printable(STR(state->buffer), '?'));
+
+ if (msg_verbose)
+ msg_info("< %s: %s", state->namaddr, STR(state->buffer));
+ return (last_char == '\n');
+}
+
+/* smtpd_chat_reply - format, send and record an SMTP response */
+
+void smtpd_chat_reply(SMTPD_STATE *state, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vsmtpd_chat_reply(state, format, ap);
+ va_end(ap);
+}
+
+/* vsmtpd_chat_reply - format, send and record an SMTP response */
+
+void vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap)
+{
+ int delay = 0;
+ char *cp;
+ char *next;
+ char *end;
+ const char *footer;
+
+ /*
+ * Slow down clients that make errors. Sleep-on-anything slows down
+ * clients that make an excessive number of errors within a session.
+ */
+ if (state->error_count >= var_smtpd_soft_erlim)
+ sleep(delay = var_smtpd_err_sleep);
+
+ vstring_vsprintf(state->buffer, format, ap);
+
+ if ((*(cp = STR(state->buffer)) == '4' || *cp == '5')
+ && ((smtpd_rej_ftr_maps != 0
+ && (footer = maps_find(smtpd_rej_ftr_maps, cp, 0)) != 0)
+ || *(footer = var_smtpd_rej_footer) != 0))
+ smtp_reply_footer(state->buffer, 0, footer, STR(smtpd_expand_filter),
+ smtpd_expand_lookup, (void *) state);
+
+ /* All 5xx replies must have a 5.xx.xx detail code. */
+ for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) {
+ if (var_soft_bounce) {
+ if (cp[0] == '5') {
+ cp[0] = '4';
+ if (cp[4] == '5')
+ cp[4] = '4';
+ }
+ }
+ /* This is why we use strlen() above instead of VSTRING_LEN(). */
+ if ((next = strstr(cp, "\r\n")) != 0) {
+ *next = 0;
+ if (next[2] != 0)
+ cp[3] = '-'; /* contact footer kludge */
+ else
+ next = end; /* strip trailing \r\n */
+ } else {
+ next = end;
+ }
+ smtp_chat_append(state, "Out: ", cp);
+
+ if (msg_verbose)
+ msg_info("> %s: %s", state->namaddr, cp);
+
+ smtp_fputs(cp, next - cp, state->client);
+ if (next < end)
+ cp = next + 2;
+ else
+ break;
+ }
+
+ /*
+ * Flush unsent output if no I/O happened for a while. This avoids
+ * timeouts with pipelined SMTP sessions that have lots of server-side
+ * delays (tarpit delays or DNS lookups for UCE restrictions).
+ */
+ if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10)
+ vstream_fflush(state->client);
+
+ /*
+ * Abort immediately if the connection is broken.
+ */
+ if (vstream_ftimeout(state->client))
+ vstream_longjmp(state->client, SMTP_ERR_TIME);
+ if (vstream_ferror(state->client))
+ vstream_longjmp(state->client, SMTP_ERR_EOF);
+
+ /*
+ * Orderly disconnect in case of 421 or 521 reply.
+ */
+ if (strncmp(STR(state->buffer), "421", 3) == 0
+ || strncmp(STR(state->buffer), "521", 3) == 0)
+ state->flags |= SMTPD_FLAG_HANGUP;
+}
+
+/* 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);
+}
+
+/* smtpd_chat_notify - notify postmaster */
+
+void smtpd_chat_notify(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_chat_notify";
+ VSTREAM *notice;
+ char **cpp;
+
+ /*
+ * Sanity checks.
+ */
+ if (state->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(),
+ (state->error_mask & MAIL_ERROR_BOUNCE) ?
+ var_bounce_rcpt : 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 SMTP server: errors from %s",
+ var_mail_name, state->namaddr);
+ post_mail_fputs(notice, "");
+ post_mail_fputs(notice, "Transcript of session follows.");
+ post_mail_fputs(notice, "");
+ argv_terminate(state->history);
+ for (cpp = state->history->argv; *cpp; cpp++)
+ line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
+ (void *) notice);
+ post_mail_fputs(notice, "");
+ if (state->reason)
+ post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason);
+ 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/smtpd/smtpd_chat.h b/src/smtpd/smtpd_chat.h
new file mode 100644
index 0000000..9fbe178
--- /dev/null
+++ b/src/smtpd/smtpd_chat.h
@@ -0,0 +1,45 @@
+/*++
+/* NAME
+/* smtpd_chat 3h
+/* SUMMARY
+/* SMTP server request/response support
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_chat.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+
+ /*
+ * External interface.
+ */
+extern void smtpd_chat_pre_jail_init(void);
+extern void smtpd_chat_reset(SMTPD_STATE *);
+extern int smtpd_chat_query_limit(SMTPD_STATE *, int);
+extern void smtpd_chat_query(SMTPD_STATE *);
+extern void PRINTFLIKE(2, 3) smtpd_chat_reply(SMTPD_STATE *, const char *,...);
+extern void vsmtpd_chat_reply(SMTPD_STATE *, const char *, va_list);
+extern void smtpd_chat_notify(SMTPD_STATE *);
+
+#define smtpd_chat_query(state) \
+ ((void) smtpd_chat_query_limit((state), var_line_limit))
+
+/* 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
+/*--*/
diff --git a/src/smtpd/smtpd_check.c b/src/smtpd/smtpd_check.c
new file mode 100644
index 0000000..ed0e3ff
--- /dev/null
+++ b/src/smtpd/smtpd_check.c
@@ -0,0 +1,6392 @@
+/*++
+/* NAME
+/* smtpd_check 3
+/* SUMMARY
+/* SMTP client request filtering
+/* SYNOPSIS
+/* #include "smtpd.h"
+/* #include "smtpd_check.h"
+/*
+/* void smtpd_check_init()
+/*
+/* int smtpd_check_addr(sender, address, smtputf8)
+/* const char *sender;
+/* const char *address;
+/* int smtputf8;
+/*
+/* char *smtpd_check_rewrite(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_check_client(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_check_helo(state, helohost)
+/* SMTPD_STATE *state;
+/* char *helohost;
+/*
+/* char *smtpd_check_mail(state, sender)
+/* SMTPD_STATE *state;
+/* char *sender;
+/*
+/* char *smtpd_check_rcpt(state, recipient)
+/* SMTPD_STATE *state;
+/* char *recipient;
+/*
+/* char *smtpd_check_etrn(state, destination)
+/* SMTPD_STATE *state;
+/* char *destination;
+/*
+/* char *smtpd_check_data(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_check_eod(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_check_size(state, size)
+/* SMTPD_STATE *state;
+/* off_t size;
+/*
+/* char *smtpd_check_queue(state)
+/* SMTPD_STATE *state;
+/* AUXILIARY FUNCTIONS
+/* void log_whatsup(state, action, text)
+/* SMTPD_STATE *state;
+/* const char *action;
+/* const char *text;
+/* DESCRIPTION
+/* This module implements additional checks on SMTP client requests.
+/* A client request is validated in the context of the session state.
+/* The result is either an error response (including the numerical
+/* code) or the result is a null pointer in case of success.
+/*
+/* smtpd_check_init() initializes. This function should be called
+/* once during the process life time.
+/*
+/* smtpd_check_addr() sanity checks an email address and returns
+/* non-zero in case of badness. The sender argument provides sender
+/* context for address resolution and caching, or a null pointer
+/* if information is unavailable.
+/*
+/* smtpd_check_rewrite() should be called before opening a queue
+/* file or proxy connection, in order to establish the proper
+/* header address rewriting context.
+/*
+/* Each of the following routines scrutinizes the argument passed to
+/* an SMTP command such as HELO, MAIL FROM, RCPT TO, or scrutinizes
+/* the initial client connection request. The administrator can
+/* specify what restrictions apply.
+/*
+/* Restrictions are specified via configuration parameters named
+/* \fIsmtpd_{client,helo,sender,recipient}_restrictions.\fR Each
+/* configuration parameter specifies a list of zero or more
+/* restrictions that are applied in the order as specified.
+/* .PP
+/* smtpd_check_client() validates the client host name or address.
+/* Relevant configuration parameters:
+/* .IP smtpd_client_restrictions
+/* Restrictions on the names or addresses of clients that may connect
+/* to this SMTP server.
+/* .PP
+/* smtpd_check_helo() validates the hostname provided with the
+/* HELO/EHLO commands. Relevant configuration parameters:
+/* .IP smtpd_helo_restrictions
+/* Restrictions on the hostname that is sent with the HELO/EHLO
+/* command.
+/* .PP
+/* smtpd_check_mail() validates the sender address provided with
+/* a MAIL FROM request. Relevant configuration parameters:
+/* .IP smtpd_sender_restrictions
+/* Restrictions on the sender address that is sent with the MAIL FROM
+/* command.
+/* .PP
+/* smtpd_check_rcpt() validates the recipient address provided
+/* with an RCPT TO request. Relevant configuration parameters:
+/* .IP smtpd_recipient_restrictions
+/* Restrictions on the recipient address that is sent with the RCPT
+/* TO command.
+/* .IP local_recipient_maps
+/* Tables of user names (not addresses) that exist in $mydestination.
+/* Mail for local users not in these tables is rejected.
+/* .PP
+/* smtpd_check_etrn() validates the domain name provided with the
+/* ETRN command, and other client-provided information. Relevant
+/* configuration parameters:
+/* .IP smtpd_etrn_restrictions
+/* Restrictions on the hostname that is sent with the HELO/EHLO
+/* command.
+/* .PP
+/* smtpd_check_size() checks if a message with the given size can
+/* be received (zero means that the message size is unknown). The
+/* message is rejected when
+/* the message size exceeds the non-zero bound specified with the
+/* \fImessage_size_limit\fR configuration parameter. This is a
+/* permanent error.
+/*
+/* smtpd_check_queue() checks the available queue file system
+/* space. The message is rejected when:
+/* .IP \(bu
+/* The available queue file system space is less than the amount
+/* specified with the \fImin_queue_free\fR configuration parameter.
+/* This is a temporary error.
+/* .IP \(bu
+/* The available queue file system space is less than twice the
+/* message size limit. This is a temporary error.
+/* .PP
+/* smtpd_check_data() enforces generic restrictions after the
+/* client has sent the DATA command.
+/*
+/* smtpd_check_eod() enforces generic restrictions after the
+/* client has sent the END-OF-DATA command.
+/*
+/* Arguments:
+/* .IP name
+/* The client hostname, or \fIunknown\fR.
+/* .IP addr
+/* The client address.
+/* .IP helohost
+/* The hostname given with the HELO command.
+/* .IP sender
+/* The sender address given with the MAIL FROM command.
+/* .IP recipient
+/* The recipient address given with the RCPT TO or VRFY command.
+/* .IP size
+/* The message size given with the MAIL FROM command (zero if unknown).
+/* .PP
+/* log_whatsup() logs "<queueid>: <action>: <protocol state>
+/* from: <client-name[client-addr]>: <text>" plus the protocol
+/* (SMTP or ESMTP), and if available, EHLO, MAIL FROM, or RCPT
+/* TO.
+/* BUGS
+/* Policies like these should not be hard-coded in C, but should
+/* be user-programmable instead.
+/* SEE ALSO
+/* namadr_list(3) host access control
+/* domain_list(3) domain access control
+/* fsspace(3) free file system space
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <netdb.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <split_at.h>
+#include <fsspace.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+#include <argv.h>
+#include <mymalloc.h>
+#include <dict.h>
+#include <htable.h>
+#include <ctable.h>
+#include <mac_expand.h>
+#include <attr_clnt.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <ip_match.h>
+#include <valid_utf8_hostname.h>
+#include <midna_domain.h>
+#include <mynetworks.h>
+#include <name_code.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* Global library. */
+
+#include <string_list.h>
+#include <namadr_list.h>
+#include <domain_list.h>
+#include <mail_params.h>
+#include <resolve_clnt.h>
+#include <mail_error.h>
+#include <resolve_local.h>
+#include <own_inet_addr.h>
+#include <mail_conf.h>
+#include <maps.h>
+#include <mail_addr_find.h>
+#include <match_parent_style.h>
+#include <strip_addr.h>
+#include <cleanup_user.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <mail_addr.h>
+#include <verify_clnt.h>
+#include <input_transp.h>
+#include <is_header.h>
+#include <valid_mailhost_addr.h>
+#include <dsn_util.h>
+#include <conv_time.h>
+#include <xtext.h>
+#include <smtp_stream.h>
+#include <attr_override.h>
+#include <map_search.h>
+#include <info_log_addr_form.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_sasl_glue.h"
+#include "smtpd_check.h"
+#include "smtpd_dsn_fix.h"
+#include "smtpd_resolve.h"
+#include "smtpd_expand.h"
+
+ /*
+ * Eject seat in case of parsing problems.
+ */
+static jmp_buf smtpd_check_buf;
+
+ /*
+ * Results of restrictions. Errors are negative; see dict.h.
+ */
+#define SMTPD_CHECK_DUNNO 0 /* indifferent */
+#define SMTPD_CHECK_OK 1 /* explicitly permit */
+#define SMTPD_CHECK_REJECT 2 /* explicitly reject */
+
+ /*
+ * Intermediate results. These are static to avoid unnecessary stress on the
+ * memory manager routines.
+ */
+static VSTRING *error_text;
+static CTABLE *smtpd_rbl_cache;
+static CTABLE *smtpd_rbl_byte_cache;
+
+ /*
+ * Pre-opened SMTP recipient maps so we can reject mail for unknown users.
+ * XXX This does not belong here and will eventually become part of the
+ * trivial-rewrite resolver.
+ */
+static MAPS *local_rcpt_maps;
+static MAPS *send_canon_maps;
+static MAPS *rcpt_canon_maps;
+static MAPS *canonical_maps;
+static MAPS *virt_alias_maps;
+static MAPS *virt_mailbox_maps;
+static MAPS *relay_rcpt_maps;
+
+#ifdef TEST
+
+static STRING_LIST *virt_alias_doms;
+static STRING_LIST *virt_mailbox_doms;
+
+#endif
+
+ /*
+ * Response templates for various rbl domains.
+ */
+static MAPS *rbl_reply_maps;
+
+ /*
+ * Pre-opened sender to login name mapping.
+ */
+static MAPS *smtpd_sender_login_maps;
+
+ /*
+ * Pre-opened access control lists.
+ */
+static DOMAIN_LIST *relay_domains;
+static NAMADR_LIST *mynetworks_curr;
+static NAMADR_LIST *mynetworks_new;
+static NAMADR_LIST *perm_mx_networks;
+
+#ifdef USE_TLS
+static MAPS *relay_ccerts;
+
+#endif
+
+ /*
+ * How to do parent domain wildcard matching, if any.
+ */
+static int access_parent_style;
+
+ /*
+ * Pre-parsed restriction lists.
+ */
+static ARGV *client_restrctions;
+static ARGV *helo_restrctions;
+static ARGV *mail_restrctions;
+static ARGV *relay_restrctions;
+static ARGV *fake_relay_restrctions;
+static ARGV *rcpt_restrctions;
+static ARGV *etrn_restrctions;
+static ARGV *data_restrctions;
+static ARGV *eod_restrictions;
+
+static HTABLE *smtpd_rest_classes;
+static HTABLE *policy_clnt_table;
+static HTABLE *map_command_table;
+
+static ARGV *local_rewrite_clients;
+
+ /*
+ * The routine that recursively applies restrictions.
+ */
+static int generic_checks(SMTPD_STATE *, ARGV *, const char *, const char *, const char *);
+
+ /*
+ * Recipient table check.
+ */
+static int check_sender_rcpt_maps(SMTPD_STATE *, const char *);
+static int check_recipient_rcpt_maps(SMTPD_STATE *, const char *);
+static int check_rcpt_maps(SMTPD_STATE *, const char *, const char *,
+ const char *);
+
+ /*
+ * Tempfail actions;
+ */
+static int unk_name_tf_act;
+static int unk_addr_tf_act;
+static int unv_rcpt_tf_act;
+static int unv_from_tf_act;
+
+ /*
+ * Optional permit logging.
+ */
+static STRING_LIST *smtpd_acl_perm_log;
+
+ /*
+ * YASLM.
+ */
+#define STR vstring_str
+#define CONST_STR(x) ((const char *) vstring_str(x))
+#define UPDATE_STRING(ptr,val) { if (ptr) myfree(ptr); ptr = mystrdup(val); }
+
+ /*
+ * If some decision can't be made due to a temporary error, then change
+ * other decisions into deferrals.
+ *
+ * XXX Deferrals can be postponed only with restrictions that are based on
+ * client-specified information: this restricts their use to parameters
+ * given in HELO, MAIL FROM, RCPT TO commands.
+ *
+ * XXX Deferrals must not be postponed after client hostname lookup failure.
+ * The reason is that the effect of access tables may depend on whether a
+ * client hostname is available or not. Thus, the reject_unknown_client
+ * restriction must defer immediately when lookup fails, otherwise incorrect
+ * results happen with:
+ *
+ * reject_unknown_client, hostname-based white-list, reject
+ *
+ * XXX With warn_if_reject, don't raise the defer_if_permit flag when a
+ * reject-style restriction fails. Instead, log the warning for the
+ * resulting defer message.
+ *
+ * XXX With warn_if_reject, do raise the defer_if_reject flag when a
+ * permit-style restriction fails. Otherwise, we could reject legitimate
+ * mail.
+ */
+static int PRINTFLIKE(5, 6) defer_if(SMTPD_DEFER *, int, int, const char *, const char *,...);
+static int PRINTFLIKE(5, 6) smtpd_check_reject(SMTPD_STATE *, int, int, const char *, const char *,...);
+
+#define DEFER_IF_REJECT2(state, class, code, dsn, fmt, a1, a2) \
+ defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2))
+#define DEFER_IF_REJECT3(state, class, code, dsn, fmt, a1, a2, a3) \
+ defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2), (a3))
+#define DEFER_IF_REJECT4(state, class, code, dsn, fmt, a1, a2, a3, a4) \
+ defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4))
+
+ /*
+ * The following choose between DEFER_IF_PERMIT (only if warn_if_reject is
+ * turned off) and plain DEFER. See tempfail_actions[] below for the mapping
+ * from names to numeric action code.
+ */
+#define DEFER_ALL_ACT 0
+#define DEFER_IF_PERMIT_ACT 1
+
+#define DEFER_IF_PERMIT2(type, state, class, code, dsn, fmt, a1, a2) \
+ (((state)->warn_if_reject == 0 && (type) != 0) ? \
+ defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2)) \
+ : \
+ smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2)))
+#define DEFER_IF_PERMIT3(type, state, class, code, dsn, fmt, a1, a2, a3) \
+ (((state)->warn_if_reject == 0 && (type) != 0) ? \
+ defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2), (a3)) \
+ : \
+ smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2), (a3)))
+#define DEFER_IF_PERMIT4(type, state, class, code, dsn, fmt, a1, a2, a3, a4) \
+ (((state)->warn_if_reject == 0 && (type) != 0) ? \
+ defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4)) \
+ : \
+ smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4)))
+
+ /*
+ * Cached RBL lookup state.
+ */
+typedef struct {
+ char *txt; /* TXT content or NULL */
+ DNS_RR *a; /* A records */
+} SMTPD_RBL_STATE;
+
+static void *rbl_pagein(const char *, void *);
+static void rbl_pageout(void *, void *);
+static void *rbl_byte_pagein(const char *, void *);
+static void rbl_byte_pageout(void *, void *);
+
+ /*
+ * Context for RBL $name expansion.
+ */
+typedef struct {
+ SMTPD_STATE *state; /* general state */
+ char *domain; /* query domain */
+ const char *what; /* rejected value */
+ const char *class; /* name of rejected value */
+ const char *txt; /* randomly selected trimmed TXT rr */
+} SMTPD_RBL_EXPAND_CONTEXT;
+
+ /*
+ * Multiplication factor for free space check. Free space must be at least
+ * smtpd_space_multf * message_size_limit.
+ */
+double smtpd_space_multf = 1.5;
+
+ /*
+ * SMTPD policy client. Most attributes are ATTR_CLNT attributes.
+ */
+typedef struct {
+ ATTR_CLNT *client; /* client handle */
+ char *def_action; /* default action */
+ char *policy_context; /* context of policy request */
+} SMTPD_POLICY_CLNT;
+
+ /*
+ * Table-driven parsing of main.cf parameter overrides for specific policy
+ * clients. We derive the override names from the corresponding main.cf
+ * parameter names by skipping the redundant "smtpd_policy_service_" prefix.
+ */
+static ATTR_OVER_TIME time_table[] = {
+ 21 + (const char *) VAR_SMTPD_POLICY_TMOUT, DEF_SMTPD_POLICY_TMOUT, 0, 1, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_IDLE, DEF_SMTPD_POLICY_IDLE, 0, 1, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_TTL, DEF_SMTPD_POLICY_TTL, 0, 1, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_TRY_DELAY, DEF_SMTPD_POLICY_TRY_DELAY, 0, 1, 0,
+ 0,
+};
+static ATTR_OVER_INT int_table[] = {
+ 21 + (const char *) VAR_SMTPD_POLICY_REQ_LIMIT, 0, 0, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_TRY_LIMIT, 0, 1, 0,
+ 0,
+};
+static ATTR_OVER_STR str_table[] = {
+ 21 + (const char *) VAR_SMTPD_POLICY_DEF_ACTION, 0, 1, 0,
+ 21 + (const char *) VAR_SMTPD_POLICY_CONTEXT, 0, 1, 0,
+ 0,
+};
+
+#define link_override_table_to_variable(table, var) \
+ do { table[var##_offset].target = &var; } while (0)
+
+#define smtpd_policy_tmout_offset 0
+#define smtpd_policy_idle_offset 1
+#define smtpd_policy_ttl_offset 2
+#define smtpd_policy_try_delay_offset 3
+
+#define smtpd_policy_req_limit_offset 0
+#define smtpd_policy_try_limit_offset 1
+
+#define smtpd_policy_def_action_offset 0
+#define smtpd_policy_context_offset 1
+
+ /*
+ * Search order names must be distinct, non-empty, and non-null.
+ */
+#define SMTPD_ACL_SEARCH_NAME_CERT_FPRINT "cert_fingerprint"
+#define SMTPD_ACL_SEARCH_NAME_PKEY_FPRINT "pubkey_fingerprint"
+#define SMTPD_ACL_SEARCH_NAME_CERT_ISSUER_CN "issuer_cn"
+#define SMTPD_ACL_SEARCH_NAME_CERT_SUBJECT_CN "subject_cn"
+
+ /*
+ * Search order tokens must be distinct, and 1..126 inclusive, so that they
+ * can be stored in a character string without concerns about signed versus
+ * unsigned. Code 127 is reserved by map_search(3).
+ */
+#define SMTPD_ACL_SEARCH_CODE_CERT_FPRINT 1
+#define SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT 2
+#define SMTPD_ACL_SEARCH_CODE_CERT_ISSUER_CN 3
+#define SMTPD_ACL_SEARCH_CODE_CERT_SUBJECT_CN 4
+
+ /*
+ * Mapping from search-list names and to search-list codes.
+ */
+static const NAME_CODE search_actions[] = {
+ SMTPD_ACL_SEARCH_NAME_CERT_FPRINT, SMTPD_ACL_SEARCH_CODE_CERT_FPRINT,
+ SMTPD_ACL_SEARCH_NAME_PKEY_FPRINT, SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT,
+ SMTPD_ACL_SEARCH_NAME_CERT_ISSUER_CN, SMTPD_ACL_SEARCH_CODE_CERT_ISSUER_CN,
+ SMTPD_ACL_SEARCH_NAME_CERT_SUBJECT_CN, SMTPD_ACL_SEARCH_CODE_CERT_SUBJECT_CN,
+ 0, MAP_SEARCH_CODE_UNKNOWN,
+};
+
+/* policy_client_register - register policy service endpoint */
+
+static void policy_client_register(const char *name)
+{
+ static const char myname[] = "policy_client_register";
+ SMTPD_POLICY_CLNT *policy_client;
+ char *saved_name = 0;
+ const char *policy_name = 0;
+ char *cp;
+ const char *sep = CHARS_COMMA_SP;
+ const char *parens = CHARS_BRACE;
+ char *err;
+
+ if (policy_clnt_table == 0)
+ policy_clnt_table = htable_create(1);
+
+ if (htable_find(policy_clnt_table, name) == 0) {
+
+ /*
+ * Allow per-service overrides for main.cf global settings.
+ */
+ int smtpd_policy_tmout = var_smtpd_policy_tmout;
+ int smtpd_policy_idle = var_smtpd_policy_idle;
+ int smtpd_policy_ttl = var_smtpd_policy_ttl;
+ int smtpd_policy_try_delay = var_smtpd_policy_try_delay;
+ int smtpd_policy_req_limit = var_smtpd_policy_req_limit;
+ int smtpd_policy_try_limit = var_smtpd_policy_try_limit;
+ const char *smtpd_policy_def_action = var_smtpd_policy_def_action;
+ const char *smtpd_policy_context = var_smtpd_policy_context;
+
+ link_override_table_to_variable(time_table, smtpd_policy_tmout);
+ link_override_table_to_variable(time_table, smtpd_policy_idle);
+ link_override_table_to_variable(time_table, smtpd_policy_ttl);
+ link_override_table_to_variable(time_table, smtpd_policy_try_delay);
+ link_override_table_to_variable(int_table, smtpd_policy_req_limit);
+ link_override_table_to_variable(int_table, smtpd_policy_try_limit);
+ link_override_table_to_variable(str_table, smtpd_policy_def_action);
+ link_override_table_to_variable(str_table, smtpd_policy_context);
+
+ if (*name == parens[0]) {
+ cp = saved_name = mystrdup(name);
+ if ((err = extpar(&cp, parens, EXTPAR_FLAG_NONE)) != 0)
+ msg_fatal("policy service syntax error: %s", cp);
+ if ((policy_name = mystrtok(&cp, sep)) == 0)
+ msg_fatal("empty policy service: \"%s\"", name);
+ attr_override(cp, sep, parens,
+ CA_ATTR_OVER_TIME_TABLE(time_table),
+ CA_ATTR_OVER_INT_TABLE(int_table),
+ CA_ATTR_OVER_STR_TABLE(str_table),
+ CA_ATTR_OVER_END);
+ } else {
+ policy_name = name;
+ }
+ if (msg_verbose)
+ msg_info("%s: name=\"%s\" default_action=\"%s\" max_idle=%d "
+ "max_ttl=%d request_limit=%d retry_delay=%d "
+ "timeout=%d try_limit=%d policy_context=\"%s\"",
+ myname, policy_name, smtpd_policy_def_action,
+ smtpd_policy_idle, smtpd_policy_ttl,
+ smtpd_policy_req_limit, smtpd_policy_try_delay,
+ smtpd_policy_tmout, smtpd_policy_try_limit,
+ smtpd_policy_context);
+
+ /*
+ * Create the client.
+ */
+ policy_client = (SMTPD_POLICY_CLNT *) mymalloc(sizeof(*policy_client));
+ policy_client->client = attr_clnt_create(policy_name,
+ smtpd_policy_tmout,
+ smtpd_policy_idle,
+ smtpd_policy_ttl);
+
+ attr_clnt_control(policy_client->client,
+ ATTR_CLNT_CTL_REQ_LIMIT, smtpd_policy_req_limit,
+ ATTR_CLNT_CTL_TRY_LIMIT, smtpd_policy_try_limit,
+ ATTR_CLNT_CTL_TRY_DELAY, smtpd_policy_try_delay,
+ ATTR_CLNT_CTL_END);
+ policy_client->def_action = mystrdup(smtpd_policy_def_action);
+ policy_client->policy_context = mystrdup(smtpd_policy_context);
+ htable_enter(policy_clnt_table, name, (void *) policy_client);
+ if (saved_name)
+ myfree(saved_name);
+ }
+}
+
+/* command_map_register - register access table for maps lookup */
+
+static void command_map_register(const char *name)
+{
+ MAPS *maps;
+
+ if (map_command_table == 0)
+ map_command_table = htable_create(1);
+
+ if (htable_find(map_command_table, name) == 0) {
+ maps = maps_create(name, name, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ (void) htable_enter(map_command_table, name, (void *) maps);
+ }
+}
+
+/* smtpd_check_parse - pre-parse restrictions */
+
+static ARGV *smtpd_check_parse(int flags, const char *checks)
+{
+ char *saved_checks = mystrdup(checks);
+ ARGV *argv = argv_alloc(1);
+ char *bp = saved_checks;
+ char *name;
+ char *last = 0;
+ const MAP_SEARCH *map_search;
+
+ /*
+ * Pre-parse the restriction list, and open any dictionaries that we
+ * encounter. Dictionaries must be opened before entering the chroot
+ * jail.
+ */
+#define SMTPD_CHECK_PARSE_POLICY (1<<0)
+#define SMTPD_CHECK_PARSE_MAPS (1<<1)
+#define SMTPD_CHECK_PARSE_ALL (~0)
+
+ while ((name = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ argv_add(argv, name, (char *) 0);
+ if ((flags & SMTPD_CHECK_PARSE_POLICY)
+ && last && strcasecmp(last, CHECK_POLICY_SERVICE) == 0) {
+ policy_client_register(name);
+ } else if ((flags & SMTPD_CHECK_PARSE_MAPS)
+ && (*name == *CHARS_BRACE || strchr(name, ':') != 0)) {
+ if ((map_search = map_search_create(name)) != 0)
+ command_map_register(map_search->map_type_name);
+ }
+ last = name;
+ }
+ argv_terminate(argv);
+
+ /*
+ * Cleanup.
+ */
+ myfree(saved_checks);
+ return (argv);
+}
+
+#ifndef TEST
+
+/* has_required - make sure required restriction is present */
+
+static int has_required(ARGV *restrictions, const char **required)
+{
+ char **rest;
+ const char **reqd;
+ ARGV *expansion;
+
+ /*
+ * Recursively check list membership.
+ */
+ for (rest = restrictions->argv; *rest; rest++) {
+ if (strcasecmp(*rest, WARN_IF_REJECT) == 0 && rest[1] != 0) {
+ rest += 1;
+ continue;
+ }
+ if (strcasecmp(*rest, PERMIT_ALL) == 0) {
+ if (rest[1] != 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ rest[1], rest[0]);
+ return (0);
+ }
+ for (reqd = required; *reqd; reqd++)
+ if (strcasecmp(*rest, *reqd) == 0)
+ return (1);
+ /* XXX This lookup operation should not be case-sensitive. */
+ if ((expansion = (ARGV *) htable_find(smtpd_rest_classes, *rest)) != 0)
+ if (has_required(expansion, required))
+ return (1);
+ }
+ return (0);
+}
+
+/* fail_required - handle failure to use required restriction */
+
+static void fail_required(const char *name, const char **required)
+{
+ const char *myname = "fail_required";
+ const char **reqd;
+ VSTRING *example;
+
+ /*
+ * Sanity check.
+ */
+ if (required[0] == 0)
+ msg_panic("%s: null required list", myname);
+
+ /*
+ * Go bust.
+ */
+ example = vstring_alloc(10);
+ for (reqd = required; *reqd; reqd++)
+ vstring_sprintf_append(example, "%s%s", *reqd,
+ reqd[1] == 0 ? "" : reqd[2] == 0 ? " or " : ", ");
+ msg_fatal("in parameter %s, specify at least one working instance of: %s",
+ name, STR(example));
+}
+
+#endif
+
+/* smtpd_check_init - initialize once during process lifetime */
+
+void smtpd_check_init(void)
+{
+ char *saved_classes;
+ const char *name;
+ const char *value;
+ char *cp;
+
+#ifndef TEST
+ static const char *rcpt_required[] = {
+ REJECT_UNAUTH_DEST,
+ DEFER_UNAUTH_DEST,
+ REJECT_ALL,
+ DEFER_ALL,
+ DEFER_IF_PERMIT,
+ CHECK_RELAY_DOMAINS,
+ 0,
+ };
+
+#endif
+ static NAME_CODE tempfail_actions[] = {
+ DEFER_ALL, DEFER_ALL_ACT,
+ DEFER_IF_PERMIT, DEFER_IF_PERMIT_ACT,
+ 0, -1,
+ };
+
+ /*
+ * Pre-open access control lists before going to jail.
+ */
+ mynetworks_curr =
+ namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_MYNETWORKS), var_mynetworks);
+ mynetworks_new =
+ namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_MYNETWORKS), mynetworks_host());
+ relay_domains =
+ domain_list_init(VAR_RELAY_DOMAINS,
+ match_parent_style(VAR_RELAY_DOMAINS),
+ var_relay_domains);
+ perm_mx_networks =
+ namadr_list_init(VAR_PERM_MX_NETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_PERM_MX_NETWORKS),
+ var_perm_mx_networks);
+#ifdef USE_TLS
+ relay_ccerts = maps_create(VAR_RELAY_CCERTS, var_smtpd_relay_ccerts,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+#endif
+
+ /*
+ * Pre-parse and pre-open the recipient maps.
+ */
+ local_rcpt_maps = maps_create(VAR_LOCAL_RCPT_MAPS, var_local_rcpt_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ send_canon_maps = maps_create(VAR_SEND_CANON_MAPS, var_send_canon_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ rcpt_canon_maps = maps_create(VAR_RCPT_CANON_MAPS, var_rcpt_canon_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ canonical_maps = maps_create(VAR_CANONICAL_MAPS, var_canonical_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ virt_alias_maps = maps_create(VAR_VIRT_ALIAS_MAPS, var_virt_alias_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ virt_mailbox_maps = maps_create(VAR_VIRT_MAILBOX_MAPS,
+ var_virt_mailbox_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ relay_rcpt_maps = maps_create(VAR_RELAY_RCPT_MAPS, var_relay_rcpt_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+
+#ifdef TEST
+ virt_alias_doms = string_list_init(VAR_VIRT_ALIAS_DOMS, MATCH_FLAG_NONE,
+ var_virt_alias_doms);
+ virt_mailbox_doms = string_list_init(VAR_VIRT_MAILBOX_DOMS, MATCH_FLAG_NONE,
+ var_virt_mailbox_doms);
+#endif
+
+ access_parent_style = match_parent_style(SMTPD_ACCESS_MAPS);
+
+ /*
+ * Templates for RBL rejection replies.
+ */
+ rbl_reply_maps = maps_create(VAR_RBL_REPLY_MAPS, var_rbl_reply_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+
+ /*
+ * Sender to login name mapping.
+ */
+ smtpd_sender_login_maps = maps_create(VAR_SMTPD_SND_AUTH_MAPS,
+ var_smtpd_snd_auth_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+
+ /*
+ * error_text is used for returning error responses.
+ */
+ error_text = vstring_alloc(10);
+
+ /*
+ * Initialize the resolved address cache. Note: the cache persists across
+ * SMTP sessions so we cannot make it dependent on session state.
+ */
+ smtpd_resolve_init(100);
+
+ /*
+ * Initialize the RBL lookup cache. Note: the cache persists across SMTP
+ * sessions so we cannot make it dependent on session state.
+ */
+ smtpd_rbl_cache = ctable_create(100, rbl_pagein, rbl_pageout, (void *) 0);
+ smtpd_rbl_byte_cache = ctable_create(1000, rbl_byte_pagein,
+ rbl_byte_pageout, (void *) 0);
+
+ /*
+ * Initialize access map search list support before parsing restriction
+ * lists.
+ */
+ map_search_init(search_actions);
+
+ /*
+ * Pre-parse the restriction lists. At the same time, pre-open tables
+ * before going to jail.
+ */
+ client_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_client_checks);
+ helo_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_helo_checks);
+ mail_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_mail_checks);
+ relay_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_relay_checks);
+ if (warn_compat_break_relay_restrictions)
+ fake_relay_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ FAKE_RELAY_CHECKS);
+ rcpt_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_rcpt_checks);
+ etrn_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_etrn_checks);
+ data_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_data_checks);
+ eod_restrictions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ var_eod_checks);
+
+ /*
+ * Parse the pre-defined restriction classes.
+ */
+ smtpd_rest_classes = htable_create(1);
+ if (*var_rest_classes) {
+ cp = saved_classes = mystrdup(var_rest_classes);
+ while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ if ((value = mail_conf_lookup_eval(name)) == 0 || *value == 0)
+ msg_fatal("restriction class `%s' needs a definition", name);
+ /* XXX This store operation should not be case-sensitive. */
+ htable_enter(smtpd_rest_classes, name,
+ (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ value));
+ }
+ myfree(saved_classes);
+ }
+
+ /*
+ * This is the place to specify definitions for complex restrictions such
+ * as check_relay_domains in terms of more elementary restrictions.
+ */
+#if 0
+ htable_enter(smtpd_rest_classes, "check_relay_domains",
+ smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ "permit_mydomain reject_unauth_destination"));
+#endif
+ htable_enter(smtpd_rest_classes, REJECT_SENDER_LOGIN_MISMATCH,
+ (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL,
+ REJECT_AUTH_SENDER_LOGIN_MISMATCH
+ " " REJECT_UNAUTH_SENDER_LOGIN_MISMATCH));
+
+ /*
+ * People screw up the relay restrictions too often. Require that they
+ * list at least one restriction that rejects mail by default. We allow
+ * relay restrictions to be empty for sites that require backwards
+ * compatibility.
+ */
+#ifndef TEST
+ if (!has_required(rcpt_restrctions, rcpt_required)
+ && !has_required(relay_restrctions, rcpt_required))
+ fail_required(VAR_RELAY_CHECKS " or " VAR_RCPT_CHECKS, rcpt_required);
+#endif
+
+ /*
+ * Local rewrite policy.
+ */
+ local_rewrite_clients = smtpd_check_parse(SMTPD_CHECK_PARSE_MAPS,
+ var_local_rwr_clients);
+
+ /*
+ * Tempfail_actions.
+ *
+ * XXX This name-to-number mapping should be encapsulated in a separate
+ * mail_conf_name_code.c module.
+ */
+ if ((unk_name_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE,
+ var_unk_name_tf_act)) < 0)
+ msg_fatal("bad configuration: %s = %s",
+ VAR_UNK_NAME_TF_ACT, var_unk_name_tf_act);
+ if ((unk_addr_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE,
+ var_unk_addr_tf_act)) < 0)
+ msg_fatal("bad configuration: %s = %s",
+ VAR_UNK_ADDR_TF_ACT, var_unk_addr_tf_act);
+ if ((unv_rcpt_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE,
+ var_unv_rcpt_tf_act)) < 0)
+ msg_fatal("bad configuration: %s = %s",
+ VAR_UNV_RCPT_TF_ACT, var_unv_rcpt_tf_act);
+ if ((unv_from_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE,
+ var_unv_from_tf_act)) < 0)
+ msg_fatal("bad configuration: %s = %s",
+ VAR_UNV_FROM_TF_ACT, var_unv_from_tf_act);
+ if (msg_verbose) {
+ msg_info("%s = %s", VAR_UNK_NAME_TF_ACT, tempfail_actions[unk_name_tf_act].name);
+ msg_info("%s = %s", VAR_UNK_ADDR_TF_ACT, tempfail_actions[unk_addr_tf_act].name);
+ msg_info("%s = %s", VAR_UNV_RCPT_TF_ACT, tempfail_actions[unv_rcpt_tf_act].name);
+ msg_info("%s = %s", VAR_UNV_FROM_TF_ACT, tempfail_actions[unv_from_tf_act].name);
+ }
+
+ /*
+ * Optional permit logging.
+ */
+ smtpd_acl_perm_log = string_list_init(VAR_SMTPD_ACL_PERM_LOG,
+ MATCH_FLAG_RETURN,
+ var_smtpd_acl_perm_log);
+}
+
+/* log_whatsup - log as much context as we have */
+
+void log_whatsup(SMTPD_STATE *state, const char *whatsup,
+ const char *text)
+{
+ VSTRING *buf = vstring_alloc(100);
+
+ vstring_sprintf(buf, "%s: %s: %s from %s: %s;",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ whatsup, state->where, state->namaddr, text);
+ if (state->sender)
+ vstring_sprintf_append(buf, " from=<%s>",
+ info_log_addr_form_sender(state->sender));
+ if (state->recipient)
+ vstring_sprintf_append(buf, " to=<%s>",
+ info_log_addr_form_recipient(state->recipient));
+ if (state->protocol)
+ vstring_sprintf_append(buf, " proto=%s", state->protocol);
+ if (state->helo_name)
+ vstring_sprintf_append(buf, " helo=<%s>", state->helo_name);
+ msg_info("%s", STR(buf));
+ vstring_free(buf);
+}
+
+/* smtpd_acl_permit - permit request with optional logging */
+
+static int PRINTFLIKE(5, 6) smtpd_acl_permit(SMTPD_STATE *state,
+ const char *action,
+ const char *reply_class,
+ const char *reply_name,
+ const char *format,...)
+{
+ const char myname[] = "smtpd_acl_permit";
+ va_list ap;
+ const char *whatsup;
+
+#ifdef notdef
+#define NO_PRINT_ARGS ""
+#else
+#define NO_PRINT_ARGS "%s", ""
+#endif
+
+ /*
+ * First, find out if (and how) this permit action should be logged.
+ */
+ if (msg_verbose)
+ msg_info("%s: checking %s settings", myname, VAR_SMTPD_ACL_PERM_LOG);
+
+ if (state->defer_if_permit.active) {
+ /* This action is overruled. Do not log. */
+ whatsup = 0;
+ } else if (string_list_match(smtpd_acl_perm_log, action) != 0) {
+ /* This is not a test. Logging is enabled. */
+ whatsup = "permit";
+ } else {
+ /* This is not a test. Logging is disabled. */
+ whatsup = 0;
+ }
+ if (whatsup != 0) {
+ vstring_sprintf(error_text, "action=%s for %s=%s",
+ action, reply_class, reply_name);
+ if (format && *format) {
+ vstring_strcat(error_text, " ");
+ va_start(ap, format);
+ vstring_vsprintf_append(error_text, format, ap);
+ va_end(ap);
+ }
+ log_whatsup(state, whatsup, STR(error_text));
+ } else {
+ if (msg_verbose)
+ msg_info("%s: %s: no match", myname, VAR_SMTPD_ACL_PERM_LOG);
+ }
+ return (SMTPD_CHECK_OK);
+}
+
+/* smtpd_check_reject - do the boring things that must be done */
+
+static int smtpd_check_reject(SMTPD_STATE *state, int error_class,
+ int code, const char *dsn,
+ const char *format,...)
+{
+ va_list ap;
+ int warn_if_reject;
+ const char *whatsup;
+
+ /*
+ * Do not reject mail if we were asked to warn only. However,
+ * configuration/software/data errors cannot be converted into warnings.
+ */
+ if (state->warn_if_reject && error_class != MAIL_ERROR_SOFTWARE
+ && error_class != MAIL_ERROR_RESOURCE
+ && error_class != MAIL_ERROR_DATA) {
+ warn_if_reject = 1;
+ whatsup = "reject_warning";
+ } else {
+ warn_if_reject = 0;
+ whatsup = "reject";
+ }
+
+ /*
+ * Update the error class mask, and format the response. XXX What about
+ * multi-line responses? For now we cheat and send whitespace.
+ *
+ * Format the response before complaining about configuration errors, so
+ * that we can show the error in context.
+ */
+ state->error_mask |= error_class;
+ vstring_sprintf(error_text, "%d %s ", code, dsn);
+ va_start(ap, format);
+ vstring_vsprintf_append(error_text, format, ap);
+ va_end(ap);
+
+ /*
+ * Validate the response, that is, the response must begin with a
+ * three-digit status code, and the first digit must be 4 or 5. If the
+ * response is bad, log a warning and send a generic response instead.
+ */
+ if (code < 400 || code > 599) {
+ msg_warn("SMTP reply code configuration error: %s", STR(error_text));
+ vstring_strcpy(error_text, "450 4.7.1 Service unavailable");
+ }
+ if (!dsn_valid(STR(error_text) + 4)) {
+ msg_warn("DSN detail code configuration error: %s", STR(error_text));
+ vstring_strcpy(error_text, "450 4.7.1 Service unavailable");
+ }
+
+ /*
+ * Ensure RFC compliance. We could do this inside smtpd_chat_reply() and
+ * switch to multi-line for long replies.
+ */
+ vstring_truncate(error_text, 510);
+ printable(STR(error_text), ' ');
+
+ /*
+ * Force this rejection into deferral because of some earlier temporary
+ * error that may have prevented us from accepting mail, and report the
+ * earlier problem instead.
+ */
+ if (!warn_if_reject && state->defer_if_reject.active && STR(error_text)[0] == '5') {
+ state->warn_if_reject = state->defer_if_reject.active = 0;
+ return (smtpd_check_reject(state, state->defer_if_reject.class,
+ state->defer_if_reject.code,
+ STR(state->defer_if_reject.dsn),
+ "%s", STR(state->defer_if_reject.reason)));
+ }
+
+ /*
+ * Soft bounce safety net.
+ *
+ * XXX The code below also appears in the Postfix SMTP server reply output
+ * routine. It is duplicated here in order to avoid discrepancies between
+ * the reply codes that are shown in "reject" logging and the reply codes
+ * that are actually sent to the SMTP client.
+ *
+ * Implementing the soft_bounce safety net in the SMTP server reply output
+ * routine has the advantage that it covers all 5xx replies, including
+ * SMTP protocol or syntax errors, which makes soft_bounce great for
+ * non-destructive tests (especially by people who are paranoid about
+ * losing mail).
+ *
+ * We could eliminate the code duplication and implement the soft_bounce
+ * safety net only in the code below. But then the safety net would cover
+ * the UCE restrictions only. This would be at odds with documentation
+ * which says soft_bounce changes all 5xx replies into 4xx ones.
+ */
+ if (var_soft_bounce && STR(error_text)[0] == '5')
+ STR(error_text)[0] = '4';
+
+ /*
+ * In any case, enforce consistency between the SMTP code and DSN code.
+ * SMTP has the higher precedence since it came here first.
+ */
+ STR(error_text)[4] = STR(error_text)[0];
+
+ /*
+ * Log what is happening. When the sysadmin discards policy violation
+ * postmaster notices, this may be the only trace left that service was
+ * rejected. Print the request, client name/address, and response.
+ */
+ log_whatsup(state, whatsup, STR(error_text));
+
+ return (warn_if_reject ? 0 : SMTPD_CHECK_REJECT);
+}
+
+/* defer_if - prepare to change our mind */
+
+static int defer_if(SMTPD_DEFER *defer, int error_class,
+ int code, const char *dsn,
+ const char *fmt,...)
+{
+ va_list ap;
+
+ /*
+ * Keep the first reason for this type of deferral, to minimize
+ * confusion.
+ */
+ if (defer->active == 0) {
+ defer->active = 1;
+ defer->class = error_class;
+ defer->code = code;
+ if (defer->dsn == 0)
+ defer->dsn = vstring_alloc(10);
+ vstring_strcpy(defer->dsn, dsn);
+ if (defer->reason == 0)
+ defer->reason = vstring_alloc(10);
+ va_start(ap, fmt);
+ vstring_vsprintf(defer->reason, fmt, ap);
+ va_end(ap);
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_dict_retry - reject with temporary failure if dict lookup fails */
+
+static NORETURN reject_dict_retry(SMTPD_STATE *state, const char *reply_name)
+{
+ longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_DATA,
+ 451, "4.3.0",
+ "<%s>: Temporary lookup failure",
+ reply_name));
+}
+
+/* reject_server_error - reject with temporary failure after non-dict error */
+
+static NORETURN reject_server_error(SMTPD_STATE *state)
+{
+ longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
+ 451, "4.3.5",
+ "Server configuration error"));
+}
+
+/* check_mail_addr_find - reject with temporary failure if dict lookup fails */
+
+static const char *check_mail_addr_find(SMTPD_STATE *state,
+ const char *reply_name,
+ MAPS *maps, const char *key,
+ char **ext)
+{
+ const char *result;
+
+ if ((result = mail_addr_find(maps, key, ext)) != 0 || maps->error == 0)
+ return (result);
+ if (maps->error == DICT_ERR_RETRY)
+ /* Warning is already logged. */
+ reject_dict_retry(state, reply_name);
+ else
+ reject_server_error(state);
+}
+
+/* reject_unknown_reverse_name - fail if reverse client hostname is unknown */
+
+static int reject_unknown_reverse_name(SMTPD_STATE *state)
+{
+ const char *myname = "reject_unknown_reverse_name";
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, state->reverse_name);
+
+ if (state->reverse_name_status != SMTPD_PEER_CODE_OK)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ state->reverse_name_status == SMTPD_PEER_CODE_PERM ?
+ var_unk_client_code : 450, "4.7.1",
+ "Client host rejected: cannot find your reverse hostname, [%s]",
+ state->addr));
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_unknown_client - fail if client hostname is unknown */
+
+static int reject_unknown_client(SMTPD_STATE *state)
+{
+ const char *myname = "reject_unknown_client";
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, state->name, state->addr);
+
+ /* RFC 7372: Email Authentication Status Codes. */
+ if (state->name_status != SMTPD_PEER_CODE_OK)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ state->name_status >= SMTPD_PEER_CODE_PERM ?
+ var_unk_client_code : 450, "4.7.25",
+ "Client host rejected: cannot find your hostname, [%s]",
+ state->addr));
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_plaintext_session - fail if session is not encrypted */
+
+static int reject_plaintext_session(SMTPD_STATE *state)
+{
+ const char *myname = "reject_plaintext_session";
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, state->name, state->addr);
+
+#ifdef USE_TLS
+ if (state->tls_context == 0)
+#endif
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_plaintext_code, "4.7.1",
+ "Session encryption is required"));
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* permit_inet_interfaces - succeed if client my own address */
+
+static int permit_inet_interfaces(SMTPD_STATE *state)
+{
+ const char *myname = "permit_inet_interfaces";
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, state->name, state->addr);
+
+ if (own_inet_addr((struct sockaddr *) &(state->sockaddr)))
+ /* Permit logging in generic_checks() only. */
+ return (SMTPD_CHECK_OK);
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* permit_mynetworks - succeed if client is in a trusted network */
+
+static int permit_mynetworks(SMTPD_STATE *state)
+{
+ const char *myname = "permit_mynetworks";
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, state->name, state->addr);
+
+ if (namadr_list_match(mynetworks_curr, state->name, state->addr)) {
+ if (warn_compat_break_mynetworks_style
+ && !namadr_list_match(mynetworks_new, state->name, state->addr))
+ msg_info("using backwards-compatible default setting "
+ VAR_MYNETWORKS_STYLE "=%s to permit request from "
+ "client \"%s\"", var_mynetworks_style, state->namaddr);
+ /* Permit logging in generic_checks() only. */
+ return (SMTPD_CHECK_OK);
+ } else if (mynetworks_curr->error == 0)
+ return (SMTPD_CHECK_DUNNO);
+ else
+ return (mynetworks_curr->error);
+}
+
+/* dup_if_truncate - save hostname and truncate if it ends in dot */
+
+static char *dup_if_truncate(char *name)
+{
+ ssize_t len;
+ char *result;
+
+ /*
+ * Truncate hostnames ending in dot but not dot-dot.
+ *
+ * XXX This should not be distributed all over the code. Problem is,
+ * addresses can enter the system via multiple paths: networks, local
+ * forward/alias/include files, even as the result of address rewriting.
+ */
+ if ((len = strlen(name)) > 1
+ && name[len - 1] == '.'
+ && name[len - 2] != '.') {
+ result = mystrndup(name, len - 1);
+ } else
+ result = name;
+ return (result);
+}
+
+/* reject_invalid_hostaddr - fail if host address is incorrect */
+
+static int reject_invalid_hostaddr(SMTPD_STATE *state, char *addr,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_invalid_hostaddr";
+ ssize_t len;
+ char *test_addr;
+ int stat;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ if (addr[0] == '[' && (len = strlen(addr)) > 2 && addr[len - 1] == ']') {
+ test_addr = mystrndup(addr + 1, len - 2);
+ } else
+ test_addr = addr;
+
+ /*
+ * Validate the address.
+ */
+ if (!valid_mailhost_addr(test_addr, DONT_GRIPE))
+ stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_bad_name_code, "5.5.2",
+ "<%s>: %s rejected: invalid ip address",
+ reply_name, reply_class);
+ else
+ stat = SMTPD_CHECK_DUNNO;
+
+ /*
+ * Cleanup.
+ */
+ if (test_addr != addr)
+ myfree(test_addr);
+
+ return (stat);
+}
+
+/* reject_invalid_hostname - fail if host/domain syntax is incorrect */
+
+static int reject_invalid_hostname(SMTPD_STATE *state, char *name,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_invalid_hostname";
+ char *test_name;
+ int stat;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+ /*
+ * Truncate hostnames ending in dot but not dot-dot.
+ */
+ test_name = dup_if_truncate(name);
+
+ /*
+ * Validate the HELO/EHLO hostname. Fix 20140706: EAI not allowed here.
+ */
+ if (!valid_hostname(test_name, DONT_GRIPE)
+ && !valid_hostaddr(test_name, DONT_GRIPE)) /* XXX back compat */
+ stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_bad_name_code, "5.5.2",
+ "<%s>: %s rejected: Invalid name",
+ reply_name, reply_class);
+ else
+ stat = SMTPD_CHECK_DUNNO;
+
+ /*
+ * Cleanup.
+ */
+ if (test_name != name)
+ myfree(test_name);
+
+ return (stat);
+}
+
+/* reject_non_fqdn_hostname - fail if host name is not in fqdn form */
+
+static int reject_non_fqdn_hostname(SMTPD_STATE *state, char *name,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_non_fqdn_hostname";
+ char *test_name;
+ int stat;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+ /*
+ * Truncate hostnames ending in dot but not dot-dot.
+ */
+ test_name = dup_if_truncate(name);
+
+ /*
+ * Validate the hostname. For backwards compatibility, permit non-ASCII
+ * names only when the client requested SMTPUTF8 support.
+ */
+ if (valid_utf8_hostname(state->flags & SMTPD_FLAG_SMTPUTF8,
+ test_name, DONT_GRIPE) == 0 || strchr(test_name, '.') == 0)
+ stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_non_fqdn_code, "5.5.2",
+ "<%s>: %s rejected: need fully-qualified hostname",
+ reply_name, reply_class);
+ else
+ stat = SMTPD_CHECK_DUNNO;
+
+ /*
+ * Cleanup.
+ */
+ if (test_name != name)
+ myfree(test_name);
+
+ return (stat);
+}
+
+/* reject_unknown_hostname - fail if name has no A, AAAA or MX record */
+
+static int reject_unknown_hostname(SMTPD_STATE *state, char *name,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_unknown_hostname";
+ int dns_status;
+ DNS_RR *dummy;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+#ifdef T_AAAA
+#define RR_ADDR_TYPES T_A, T_AAAA
+#else
+#define RR_ADDR_TYPES T_A
+#endif
+
+ dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0,
+ (VSTRING *) 0, DNS_REQ_FLAG_STOP_OK,
+ RR_ADDR_TYPES, T_MX, 0);
+ if (dummy)
+ dns_rr_free(dummy);
+ /* Allow MTA names to have nullMX records. */
+ if (dns_status != DNS_OK && dns_status != DNS_NULLMX) {
+ if (dns_status == DNS_POLICY) {
+ msg_warn("%s: address or MX lookup error: %s",
+ name, "DNS reply filter drops all results");
+ return (SMTPD_CHECK_DUNNO);
+ }
+ if (dns_status != DNS_RETRY)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_unk_name_code, "4.7.1",
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ dns_status == DNS_INVAL ?
+ "Malformed DNS server reply" :
+ "Host not found"));
+ else
+ return (DEFER_IF_PERMIT2(unk_name_tf_act, state, MAIL_ERROR_POLICY,
+ 450, "4.7.1",
+ "<%s>: %s rejected: Host not found",
+ reply_name, reply_class));
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_unknown_mailhost - fail if name has no A, AAAA or MX record */
+
+static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "reject_unknown_mailhost";
+ int dns_status;
+ DNS_RR *dummy;
+ const char *aname;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+ /*
+ * Fix 20140924: convert domain to ASCII.
+ */
+#ifndef NO_EAI
+ if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", name, aname);
+ name = aname;
+ }
+#endif
+
+#define MAILHOST_LOOKUP_FLAGS \
+ (DNS_REQ_FLAG_STOP_OK | DNS_REQ_FLAG_STOP_INVAL | \
+ DNS_REQ_FLAG_STOP_NULLMX | DNS_REQ_FLAG_STOP_MX_POLICY)
+
+ dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0,
+ (VSTRING *) 0, MAILHOST_LOOKUP_FLAGS,
+ T_MX, RR_ADDR_TYPES, 0);
+ if (dummy)
+ dns_rr_free(dummy);
+ if (dns_status != DNS_OK) { /* incl. DNS_INVAL */
+ if (dns_status == DNS_POLICY) {
+ msg_warn("%s: MX or address lookup error: %s",
+ name, "DNS reply filter drops all results");
+ return (SMTPD_CHECK_DUNNO);
+ }
+ if (dns_status == DNS_NULLMX)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ 550 : 556,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "4.7.27" : "4.1.10",
+ "<%s>: %s rejected: Domain %s "
+ "does not accept mail (nullMX)",
+ reply_name, reply_class, name));
+ if (dns_status != DNS_RETRY)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_unk_addr_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "4.1.8" : "4.1.2",
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ dns_status == DNS_INVAL ?
+ "Malformed DNS server reply" :
+ "Domain not found"));
+ else
+ return (DEFER_IF_PERMIT2(unk_addr_tf_act, state, MAIL_ERROR_POLICY,
+ 450, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "4.1.8" : "4.1.2",
+ "<%s>: %s rejected: Domain not found",
+ reply_name, reply_class));
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+static int permit_auth_destination(SMTPD_STATE *state, char *recipient);
+
+/* permit_tls_clientcerts - OK/DUNNO for message relaying, or set dict_errno */
+
+static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs)
+{
+#ifdef USE_TLS
+ const char *found = 0;
+
+ if (!state->tls_context)
+ return SMTPD_CHECK_DUNNO;
+
+ if (TLS_CERT_IS_TRUSTED(state->tls_context) && permit_all_certs) {
+ if (msg_verbose)
+ msg_info("Relaying allowed for all verified client certificates");
+ /* Permit logging in generic_checks() only. */
+ return (SMTPD_CHECK_OK);
+ }
+
+ /*
+ * When directly checking the fingerprint, it is OK if the issuing CA is
+ * not trusted.
+ */
+ if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ int i;
+ char *prints[2];
+
+ prints[0] = state->tls_context->peer_cert_fprint;
+ prints[1] = state->tls_context->peer_pkey_fprint;
+
+ /* After lookup error, leave relay_ccerts->error at non-zero value. */
+ for (i = 0; i < 2; ++i) {
+ found = maps_find(relay_ccerts, prints[i], DICT_FLAG_NONE);
+ if (found != 0) {
+ if (msg_verbose)
+ msg_info("Relaying allowed for certified client: %s", found);
+ /* Permit logging in generic_checks() only. */
+ return (SMTPD_CHECK_OK);
+ } else if (relay_ccerts->error != 0) {
+ msg_warn("relay_clientcerts: lookup error for fingerprint '%s', "
+ "pkey fingerprint %s", prints[0], prints[1]);
+ return (relay_ccerts->error);
+ }
+ }
+ if (msg_verbose)
+ msg_info("relay_clientcerts: No match for fingerprint '%s', "
+ "pkey fingerprint %s", prints[0], prints[1]);
+ } else if (!var_smtpd_tls_ask_ccert) {
+ msg_warn("%s is requested, but \"%s = no\"", permit_all_certs ?
+ PERMIT_TLS_ALL_CLIENTCERTS : PERMIT_TLS_CLIENTCERTS,
+ VAR_SMTPD_TLS_ACERT);
+ }
+#endif
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* check_relay_domains - OK/FAIL for message relaying */
+
+static int check_relay_domains(SMTPD_STATE *state, char *recipient,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "check_relay_domains";
+
+#if 1
+ static int once;
+
+ if (once == 0) {
+ once = 1;
+ msg_warn("support for restriction \"%s\" will be removed from %s; "
+ "use \"%s\" instead",
+ CHECK_RELAY_DOMAINS, var_mail_name, REJECT_UNAUTH_DEST);
+ }
+#endif
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Permit if the client matches the relay_domains list.
+ */
+ if (domain_list_match(relay_domains, state->name)) {
+ if (warn_compat_break_relay_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to permit "
+ "request from client \"%s\"", state->name);
+ return (SMTPD_CHECK_OK);
+ }
+
+ /*
+ * Permit authorized destinations.
+ */
+ if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK)
+ return (SMTPD_CHECK_OK);
+
+ /*
+ * Deny relaying between sites that both are not in relay_domains.
+ */
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_relay_code, "5.7.1",
+ "<%s>: %s rejected: Relay access denied",
+ reply_name, reply_class));
+}
+
+/* permit_auth_destination - OK for message relaying */
+
+static int permit_auth_destination(SMTPD_STATE *state, char *recipient)
+{
+ const char *myname = "permit_auth_destination";
+ const RESOLVE_REPLY *reply;
+ const char *domain;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(state->sender, recipient);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, recipient);
+
+ /*
+ * Handle special case that is not supposed to happen.
+ */
+ if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0)
+ return (SMTPD_CHECK_OK);
+ domain += 1;
+
+ /*
+ * Skip source-routed non-local or virtual mail (uncertain destination).
+ */
+ if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED))
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Permit final delivery: the destination matches mydestination,
+ * virtual_alias_domains, or virtual_mailbox_domains.
+ */
+ if (reply->flags & RESOLVE_CLASS_FINAL)
+ return (SMTPD_CHECK_OK);
+
+ /*
+ * Permit if the destination matches the relay_domains list.
+ */
+ if (reply->flags & RESOLVE_CLASS_RELAY) {
+ if (warn_compat_break_relay_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to accept mail "
+ "for domain \"%s\"", domain);
+ return (SMTPD_CHECK_OK);
+ }
+
+ /*
+ * Skip when not matched
+ */
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_unauth_destination - FAIL for message relaying */
+
+static int reject_unauth_destination(SMTPD_STATE *state, char *recipient,
+ int reply_code, const char *reply_dsn)
+{
+ const char *myname = "reject_unauth_destination";
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Skip authorized destination.
+ */
+ if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK)
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Reject relaying to sites that are not listed in relay_domains.
+ */
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ reply_code, reply_dsn,
+ "<%s>: Relay access denied",
+ recipient));
+}
+
+/* reject_unauth_pipelining - reject improper use of SMTP command pipelining */
+
+static int reject_unauth_pipelining(SMTPD_STATE *state,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "reject_unauth_pipelining";
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, state->where);
+
+ if (state->flags & SMTPD_FLAG_ILL_PIPELINING)
+ return (smtpd_check_reject(state, MAIL_ERROR_PROTOCOL,
+ 503, "5.5.0",
+ "<%s>: %s rejected: Improper use of SMTP command pipelining",
+ reply_name, reply_class));
+
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* all_auth_mx_addr - match host addresses against permit_mx_backup_networks */
+
+static int all_auth_mx_addr(SMTPD_STATE *state, char *host,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "all_auth_mx_addr";
+ MAI_HOSTADDR_STR hostaddr;
+ DNS_RR *rr;
+ DNS_RR *addr_list;
+ int dns_status;
+
+ if (msg_verbose)
+ msg_info("%s: host %s", myname, host);
+
+ /*
+ * If we can't lookup the host, defer.
+ */
+#define NOPE 0
+#define YUP 1
+
+ /*
+ * Verify that all host addresses are within permit_mx_backup_networks.
+ */
+ dns_status = dns_lookup_v(host, 0, &addr_list, (VSTRING *) 0, (VSTRING *) 0,
+ DNS_REQ_FLAG_NONE, inet_proto_info()->dns_atype_list);
+ /* DNS_NULLMX is not applicable here. */
+ if (dns_status != DNS_OK) { /* incl. DNS_INVAL */
+ DEFER_IF_REJECT4(state, MAIL_ERROR_POLICY,
+ 450, "4.4.4",
+ "<%s>: %s rejected: Unable to look up host "
+ "%s as mail exchanger: %s",
+ reply_name, reply_class, host,
+ dns_status == DNS_POLICY ?
+ "DNS reply filter policy" : dns_strerror(h_errno));
+ return (NOPE);
+ }
+ for (rr = addr_list; rr != 0; rr = rr->next) {
+ if (dns_rr_to_pa(rr, &hostaddr) == 0) {
+ msg_warn("%s: skipping record type %s for host %s: %m",
+ myname, dns_strtype(rr->type), host);
+ continue;
+ }
+ if (msg_verbose)
+ msg_info("%s: checking: %s", myname, hostaddr.buf);
+
+ if (!namadr_list_match(perm_mx_networks, host, hostaddr.buf)) {
+ if (perm_mx_networks->error == 0) {
+
+ /*
+ * Reject: at least one IP address is not listed in
+ * permit_mx_backup_networks.
+ */
+ if (msg_verbose)
+ msg_info("%s: address %s for %s does not match %s",
+ myname, hostaddr.buf, host, VAR_PERM_MX_NETWORKS);
+ } else {
+ msg_warn("%s: %s lookup error for address %s for %s",
+ myname, VAR_PERM_MX_NETWORKS, hostaddr.buf, host);
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ 450, "4.4.4",
+ "<%s>: %s rejected: Unable to verify host %s as mail exchanger",
+ reply_name, reply_class, host);
+ }
+ dns_rr_free(addr_list);
+ return (NOPE);
+ }
+ }
+ dns_rr_free(addr_list);
+ return (YUP);
+}
+
+/* has_my_addr - see if this host name lists one of my network addresses */
+
+static int has_my_addr(SMTPD_STATE *state, const char *host,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "has_my_addr";
+ struct addrinfo *res;
+ struct addrinfo *res0;
+ int aierr;
+ MAI_HOSTADDR_STR hostaddr;
+ INET_PROTO_INFO *proto_info = inet_proto_info();
+
+ if (msg_verbose)
+ msg_info("%s: host %s", myname, host);
+
+ /*
+ * If we can't lookup the host, defer rather than reject.
+ */
+#define YUP 1
+#define NOPE 0
+
+ aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0);
+ if (aierr) {
+ DEFER_IF_REJECT4(state, MAIL_ERROR_POLICY,
+ 450, "4.4.4",
+ "<%s>: %s rejected: Unable to look up mail exchanger host %s: %s",
+ reply_name, reply_class, host, MAI_STRERROR(aierr));
+ return (NOPE);
+ }
+#define HAS_MY_ADDR_RETURN(x) { freeaddrinfo(res0); return (x); }
+
+ for (res = res0; res != 0; res = res->ai_next) {
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ if (msg_verbose)
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, host);
+ continue;
+ }
+ if (msg_verbose) {
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s: addr %s", myname, hostaddr.buf);
+ }
+ if (own_inet_addr(res->ai_addr))
+ HAS_MY_ADDR_RETURN(YUP);
+ if (proxy_inet_addr(res->ai_addr))
+ HAS_MY_ADDR_RETURN(YUP);
+ }
+ if (msg_verbose)
+ msg_info("%s: host %s: no match", myname, host);
+
+ HAS_MY_ADDR_RETURN(NOPE);
+}
+
+/* i_am_mx - is this machine listed as MX relay */
+
+static int i_am_mx(SMTPD_STATE *state, DNS_RR *mx_list,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "i_am_mx";
+ DNS_RR *mx;
+
+ /*
+ * Compare hostnames first. Only if no name match is found, go through
+ * the trouble of host address lookups.
+ */
+ for (mx = mx_list; mx != 0; mx = mx->next) {
+ if (msg_verbose)
+ msg_info("%s: resolve hostname: %s", myname, (char *) mx->data);
+ if (resolve_local((char *) mx->data) > 0)
+ return (YUP);
+ /* if no match or error, match interface addresses instead. */
+ }
+
+ /*
+ * Argh. Do further DNS lookups and match interface addresses.
+ */
+ for (mx = mx_list; mx != 0; mx = mx->next) {
+ if (msg_verbose)
+ msg_info("%s: address lookup: %s", myname, (char *) mx->data);
+ if (has_my_addr(state, (char *) mx->data, reply_name, reply_class))
+ return (YUP);
+ }
+
+ /*
+ * This machine is not listed as MX relay.
+ */
+ if (msg_verbose)
+ msg_info("%s: I am not listed as MX relay", myname);
+ return (NOPE);
+}
+
+/* permit_mx_primary - authorize primary MX relays */
+
+static int permit_mx_primary(SMTPD_STATE *state, DNS_RR *mx_list,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "permit_mx_primary";
+ DNS_RR *mx;
+
+ if (msg_verbose)
+ msg_info("%s", myname);
+
+ /*
+ * See if each best MX host has all IP addresses in
+ * permit_mx_backup_networks.
+ */
+ for (mx = mx_list; mx != 0; mx = mx->next) {
+ if (!all_auth_mx_addr(state, (char *) mx->data, reply_name, reply_class))
+ return (NOPE);
+ }
+
+ /*
+ * All IP addresses of the best MX hosts are within
+ * permit_mx_backup_networks.
+ */
+ return (YUP);
+}
+
+/* permit_mx_backup - permit use of me as MX backup for recipient domain */
+
+static int permit_mx_backup(SMTPD_STATE *state, const char *recipient,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "permit_mx_backup";
+ const RESOLVE_REPLY *reply;
+ const char *domain;
+ const char *adomain;
+ DNS_RR *mx_list;
+ DNS_RR *middle;
+ DNS_RR *rest;
+ int dns_status;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(state->sender, recipient);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, recipient);
+
+ /*
+ * For backwards compatibility, emulate permit_auth_destination. However,
+ * old permit_mx_backup implementations allow source routing with local
+ * address class.
+ */
+ if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0)
+ return (SMTPD_CHECK_OK);
+ domain += 1;
+#if 0
+ if (reply->flags & RESOLVE_CLASS_LOCAL)
+ return (SMTPD_CHECK_OK);
+#endif
+ if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED))
+ return (SMTPD_CHECK_DUNNO);
+ if (reply->flags & RESOLVE_CLASS_FINAL)
+ return (SMTPD_CHECK_OK);
+ if (reply->flags & RESOLVE_CLASS_RELAY) {
+ if (warn_compat_break_relay_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to accept mail "
+ "for domain \"%s\"", domain);
+ return (SMTPD_CHECK_OK);
+ }
+ if (msg_verbose)
+ msg_info("%s: not local: %s", myname, recipient);
+
+ /*
+ * Skip numerical forms that didn't match the local system.
+ */
+ if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Fix 20140924: convert domain to ASCII.
+ */
+#ifndef NO_EAI
+ if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", domain, adomain);
+ domain = adomain;
+ }
+#endif
+
+ /*
+ * Look up the list of MX host names for this domain. If no MX host is
+ * found, perhaps it is a CNAME for the local machine. Clients aren't
+ * supposed to send CNAMEs in SMTP commands, but it happens anyway. If we
+ * can't look up the destination, play safe and turn reject into defer.
+ */
+ dns_status = dns_lookup(domain, T_MX, 0, &mx_list,
+ (VSTRING *) 0, (VSTRING *) 0);
+#if 0
+ if (dns_status == DNS_NOTFOUND)
+ return (has_my_addr(state, domain, reply_name, reply_class) ?
+ SMTPD_CHECK_OK : SMTPD_CHECK_DUNNO);
+#endif
+ if (dns_status != DNS_OK) { /* incl. DNS_INVAL */
+ /* We don't special-case DNS_NULLMX. */
+ if (dns_status == DNS_RETRY || dns_status == DNS_POLICY)
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ 450, "4.4.4",
+ "<%s>: %s rejected: Unable to look up mail "
+ "exchanger information: %s",
+ reply_name, reply_class, dns_status == DNS_POLICY ?
+ "DNS reply filter policy" : dns_strerror(h_errno));
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * Separate MX list into primaries and backups.
+ */
+ mx_list = dns_rr_sort(mx_list, dns_rr_compare_pref_any);
+ for (middle = mx_list; /* see below */ ; middle = rest) {
+ rest = middle->next;
+ if (rest == 0)
+ break;
+ if (rest->pref != mx_list->pref) {
+ middle->next = 0;
+ break;
+ }
+ }
+ /* postcondition: middle->next = 0, rest may be 0. */
+
+#define PERMIT_MX_BACKUP_RETURN(x) do { \
+ middle->next = rest; \
+ dns_rr_free(mx_list); \
+ return (x); \
+ } while (0)
+
+ /*
+ * First, see if we match any of the primary MX servers.
+ */
+ if (i_am_mx(state, mx_list, reply_name, reply_class))
+ PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO);
+
+ /*
+ * Then, see if we match any of the backup MX servers.
+ */
+ if (rest == 0 || !i_am_mx(state, rest, reply_name, reply_class))
+ PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO);
+
+ /*
+ * Optionally, see if the primary MX hosts are in a restricted list of
+ * networks.
+ */
+ if (*var_perm_mx_networks
+ && !permit_mx_primary(state, mx_list, reply_name, reply_class))
+ PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO);
+
+ /*
+ * The destination passed all requirements.
+ */
+ PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_OK);
+}
+
+/* reject_non_fqdn_address - fail if address is not in fqdn form */
+
+static int reject_non_fqdn_address(SMTPD_STATE *state, char *addr,
+ char *reply_name, char *reply_class)
+{
+ const char *myname = "reject_non_fqdn_address";
+ char *domain;
+ char *test_dom;
+ int stat;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /*
+ * Locate the domain information.
+ */
+ if ((domain = strrchr(addr, '@')) != 0)
+ domain++;
+ else
+ domain = "";
+
+ /*
+ * Skip forms that we can't handle yet.
+ */
+ if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Truncate names ending in dot but not dot-dot.
+ */
+ test_dom = dup_if_truncate(domain);
+
+ /*
+ * Validate the domain. For backwards compatibility, permit non-ASCII
+ * names only when the client requested SMTPUTF8 support.
+ */
+ if (!*test_dom || !valid_utf8_hostname(state->flags & SMTPD_FLAG_SMTPUTF8,
+ test_dom, DONT_GRIPE) || !strchr(test_dom, '.'))
+ stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_non_fqdn_code, "4.5.2",
+ "<%s>: %s rejected: need fully-qualified address",
+ reply_name, reply_class);
+ else
+ stat = SMTPD_CHECK_DUNNO;
+
+ /*
+ * Cleanup.
+ */
+ if (test_dom != domain)
+ myfree(test_dom);
+
+ return (stat);
+}
+
+/* reject_unknown_address - fail if address does not resolve */
+
+static int reject_unknown_address(SMTPD_STATE *state, const char *addr,
+ const char *reply_name, const char *reply_class)
+{
+ const char *myname = "reject_unknown_address";
+ const RESOLVE_REPLY *reply;
+ const char *domain;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ state->recipient : state->sender, addr);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, addr);
+
+ /*
+ * Skip local destinations and non-DNS forms.
+ */
+ if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0)
+ return (SMTPD_CHECK_DUNNO);
+ domain += 1;
+ if (reply->flags & RESOLVE_CLASS_FINAL)
+ return (SMTPD_CHECK_DUNNO);
+ if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Look up the name in the DNS.
+ */
+ return (reject_unknown_mailhost(state, domain, reply_name, reply_class));
+}
+
+/* reject_unverified_address - fail if address bounces */
+
+static int reject_unverified_address(SMTPD_STATE *state, const char *addr,
+ const char *reply_name, const char *reply_class,
+ int unv_addr_dcode, int unv_addr_rcode,
+ int unv_addr_tf_act,
+ const char *alt_reply)
+{
+ const char *myname = "reject_unverified_address";
+ VSTRING *why = vstring_alloc(10);
+ int rqst_status = SMTPD_CHECK_DUNNO;
+ int rcpt_status;
+ int verify_status;
+ int count;
+ int reject_code = 0;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /*
+ * Verify the address. Don't waste too much of their or our time.
+ */
+ for (count = 0; /* see below */ ; /* see below */ ) {
+ verify_status = verify_clnt_query(addr, &rcpt_status, why);
+ if (verify_status != VRFY_STAT_OK || rcpt_status != DEL_RCPT_STAT_TODO)
+ break;
+ if (++count >= var_verify_poll_count)
+ break;
+ sleep(var_verify_poll_delay);
+ }
+ if (verify_status != VRFY_STAT_OK) {
+ msg_warn("%s service failure", var_verify_service);
+ rqst_status =
+ DEFER_IF_PERMIT2(unv_addr_tf_act, state, MAIL_ERROR_POLICY,
+ 450, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ SND_DSN : "4.1.1",
+ "<%s>: %s rejected: address verification problem",
+ reply_name, reply_class);
+ } else {
+ switch (rcpt_status) {
+ default:
+ msg_warn("unknown address verification status %d", rcpt_status);
+ break;
+ case DEL_RCPT_STAT_TODO:
+ case DEL_RCPT_STAT_DEFER:
+ reject_code = unv_addr_dcode;
+ break;
+ case DEL_RCPT_STAT_OK:
+ break;
+ case DEL_RCPT_STAT_BOUNCE:
+ reject_code = unv_addr_rcode;
+ break;
+ }
+ if (reject_code >= 400 && *alt_reply)
+ vstring_strcpy(why, alt_reply);
+ switch (reject_code / 100) {
+ case 2:
+ break;
+ case 4:
+ rqst_status =
+ DEFER_IF_PERMIT3(unv_addr_tf_act, state, MAIL_ERROR_POLICY,
+ reject_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ SND_DSN : "4.1.1",
+ "<%s>: %s rejected: unverified address: %.250s",
+ reply_name, reply_class, STR(why));
+ break;
+ default:
+ if (reject_code != 0)
+ rqst_status =
+ smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ reject_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ SND_DSN : "4.1.1",
+ "<%s>: %s rejected: undeliverable address: %s",
+ reply_name, reply_class, STR(why));
+ break;
+ }
+ }
+ vstring_free(why);
+ return (rqst_status);
+}
+
+/* can_delegate_action - can we delegate this to the cleanup server */
+
+#ifndef TEST
+
+static int not_in_client_helo(SMTPD_STATE *, const char *, const char *, const char *);
+
+static int can_delegate_action(SMTPD_STATE *state, const char *table,
+ const char *action, const char *reply_class)
+{
+
+ /*
+ * If we're not using the cleanup server, then there is no way that we
+ * can support actions such as FILTER or HOLD that are delegated to the
+ * cleanup server.
+ */
+ if (USE_SMTPD_PROXY(state)) {
+ msg_warn("access table %s: with %s specified, action %s is unavailable",
+ table, VAR_SMTPD_PROXY_FILT, action);
+ return (0);
+ }
+
+ /*
+ * ETRN does not receive mail so we can't store queue file records.
+ */
+ if (strcmp(state->where, SMTPD_CMD_ETRN) == 0) {
+ msg_warn("access table %s: action %s is unavailable in %s",
+ table, action, VAR_ETRN_CHECKS);
+ return (0);
+ }
+ return (not_in_client_helo(state, table, action, reply_class));
+}
+
+/* not_in_client_helo - not in client or helo restriction context */
+
+static int not_in_client_helo(SMTPD_STATE *state, const char *table,
+ const char *action,
+ const char *unused_reply_class)
+{
+
+ /*
+ * If delay_reject=no, then client and helo restrictions take effect
+ * immediately, outside any particular mail transaction context. For
+ * example, rejecting HELO does not affect subsequent mail deliveries.
+ * Thus, if delay_reject=no, client and helo actions such as FILTER or
+ * HOLD also should not affect subsequent mail deliveries. Hmm...
+ *
+ * XXX If the MAIL FROM command is rejected then we have to reset access map
+ * side effects such as FILTER.
+ */
+ if (state->sender == 0) {
+ msg_warn("access table %s: with %s=%s, "
+ "action %s is always skipped in %s or %s restrictions",
+ table, VAR_SMTPD_DELAY_REJECT, CONFIG_BOOL_NO,
+ action, SMTPD_NAME_CLIENT, SMTPD_NAME_HELO);
+ /* XXX What about ETRN? */
+ return (0);
+ }
+ return (1);
+}
+
+#endif
+
+/* check_table_result - translate table lookup result into pass/reject */
+
+static int check_table_result(SMTPD_STATE *state, const char *table,
+ const char *value, const char *datum,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_table_result";
+ int code;
+ ARGV *restrictions;
+ jmp_buf savebuf;
+ int status;
+ const char *cmd_text;
+ int cmd_len;
+ static char def_dsn[] = "5.7.1";
+ DSN_SPLIT dp;
+ static VSTRING *buf;
+
+#ifdef DELAY_ACTION
+ int defer_delay;
+
+#endif
+
+ if (buf == 0)
+ buf = vstring_alloc(10);
+
+ /*
+ * Parse into command and text. Do not change the input.
+ */
+ cmd_text = value + strcspn(value, " \t");
+ cmd_len = cmd_text - value;
+ vstring_strncpy(buf, value, cmd_len);
+ while (*cmd_text && ISSPACE(*cmd_text))
+ cmd_text++;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s %s", myname, table, value, datum);
+
+#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+
+ /*
+ * DUNNO means skip this table. Silently ignore optional text.
+ */
+ if (STREQUAL(value, "DUNNO", cmd_len))
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * REJECT means NO. Use optional text or generate a generic error
+ * response.
+ */
+ if (STREQUAL(value, "REJECT", cmd_len)) {
+ dsn_split(&dp, "5.7.1", cmd_text);
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_map_reject_code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Access denied"));
+ }
+
+ /*
+ * DEFER means "try again". Use optional text or generate a generic error
+ * response.
+ */
+ if (STREQUAL(value, "DEFER", cmd_len)) {
+ dsn_split(&dp, "4.7.1", cmd_text);
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_map_defer_code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Access denied"));
+ }
+#ifndef SHUT_RDWR
+#define SHUT_RDWR 2
+#endif
+
+ /*
+ * HANGUP. Text is optional. Drop the connection without sending any
+ * reply.
+ *
+ * Note: this is an unsupported test feature. No attempt is made to maintain
+ * compatibility between successive versions.
+ */
+ if (STREQUAL(value, "HANGUP", cmd_len)) {
+ shutdown(vstream_fileno(state->client), SHUT_RDWR);
+ log_whatsup(state, "hangup", cmd_text);
+ vstream_longjmp(state->client, SMTP_ERR_QUIET);
+ }
+
+ /*
+ * INFO. Text is optional.
+ */
+ if (STREQUAL(value, "INFO", cmd_len)) {
+ log_whatsup(state, "info", cmd_text);
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * WARN. Text is optional.
+ */
+ if (STREQUAL(value, "WARN", cmd_len)) {
+ log_whatsup(state, "warn", cmd_text);
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * FILTER means deliver to content filter. But we may still change our
+ * mind, and reject/discard the message for other reasons.
+ */
+ if (STREQUAL(value, "FILTER", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "FILTER", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (*cmd_text == 0) {
+ msg_warn("access table %s entry \"%s\" has FILTER entry without value",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else if (strchr(cmd_text, ':') == 0) {
+ msg_warn("access table %s entry \"%s\" requires transport:destination",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ vstring_sprintf(error_text, "<%s>: %s triggers FILTER %s",
+ reply_name, reply_class, cmd_text);
+ log_whatsup(state, "filter", STR(error_text));
+#ifndef TEST
+ UPDATE_STRING(state->saved_filter, cmd_text);
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * HOLD means deliver later. But we may still change our mind, and
+ * reject/discard the message for other reasons.
+ */
+ if (STREQUAL(value, "HOLD", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "HOLD", reply_class) == 0
+ || (state->saved_flags & CLEANUP_FLAG_HOLD))
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
+ *cmd_text ? cmd_text : "triggers HOLD action");
+ log_whatsup(state, "hold", STR(error_text));
+#ifndef TEST
+ state->saved_flags |= CLEANUP_FLAG_HOLD;
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * DELAY means deliver later. But we may still change our mind, and
+ * reject/discard the message for other reasons.
+ *
+ * This feature is deleted because it has too many problems. 1) It does not
+ * work on some remote file systems; 2) mail will be delivered anyway
+ * with "sendmail -q" etc.; 3) while the mail is queued it bogs down the
+ * deferred queue scan with huge amounts of useless disk I/O operations.
+ */
+#ifdef DELAY_ACTION
+ if (STREQUAL(value, "DELAY", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "DELAY", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (*cmd_text == 0) {
+ msg_warn("access table %s entry \"%s\" has DELAY entry without value",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ }
+ if (conv_time(cmd_text, &defer_delay, 's') == 0) {
+ msg_warn("access table %s entry \"%s\" has invalid DELAY argument \"%s\"",
+ table, datum, cmd_text);
+ return (SMTPD_CHECK_DUNNO);
+ }
+ vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
+ *cmd_text ? cmd_text : "triggers DELAY action");
+ log_whatsup(state, "delay", STR(error_text));
+#ifndef TEST
+ state->saved_delay = defer_delay;
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+#endif
+
+ /*
+ * DISCARD means silently discard and claim successful delivery.
+ */
+ if (STREQUAL(value, "DISCARD", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "DISCARD", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
+ *cmd_text ? cmd_text : "triggers DISCARD action");
+ log_whatsup(state, "discard", STR(error_text));
+#ifndef TEST
+ state->saved_flags |= CLEANUP_FLAG_DISCARD;
+ state->discard = 1;
+#endif
+ return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name,
+ "from %s", table));
+ }
+
+ /*
+ * REDIRECT means deliver to designated recipient. But we may still
+ * change our mind, and reject/discard the message for other reasons.
+ */
+ if (STREQUAL(value, "REDIRECT", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "REDIRECT", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (strchr(cmd_text, '@') == 0) {
+ msg_warn("access table %s entry \"%s\" requires user@domain target",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ vstring_sprintf(error_text, "<%s>: %s triggers REDIRECT %s",
+ reply_name, reply_class, cmd_text);
+ log_whatsup(state, "redirect", STR(error_text));
+#ifndef TEST
+ UPDATE_STRING(state->saved_redirect, cmd_text);
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * BCC means deliver to designated recipient. But we may still change our
+ * mind, and reject/discard the message for other reasons.
+ */
+ if (STREQUAL(value, "BCC", cmd_len)) {
+#ifndef TEST
+ if (can_delegate_action(state, table, "BCC", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (strchr(cmd_text, '@') == 0) {
+ msg_warn("access table %s entry \"%s\" requires user@domain target",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ vstring_sprintf(error_text, "<%s>: %s triggers BCC %s",
+ reply_name, reply_class, cmd_text);
+ log_whatsup(state, "bcc", STR(error_text));
+#ifndef TEST
+ if (state->saved_bcc == 0)
+ state->saved_bcc = argv_alloc(1);
+ argv_add(state->saved_bcc, cmd_text, (char *) 0);
+#endif
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * DEFER_IF_PERMIT changes "permit" into "maybe". Use optional text or
+ * generate a generic error response.
+ */
+ if (STREQUAL(value, DEFER_IF_PERMIT, cmd_len)) {
+ dsn_split(&dp, "4.7.1", cmd_text);
+ return (DEFER_IF_PERMIT3(DEFER_IF_PERMIT_ACT, state, MAIL_ERROR_POLICY,
+ var_map_defer_code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn), reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Service unavailable"));
+ }
+
+ /*
+ * DEFER_IF_REJECT changes "reject" into "maybe". Use optional text or
+ * generate a generic error response.
+ */
+ if (STREQUAL(value, DEFER_IF_REJECT, cmd_len)) {
+ dsn_split(&dp, "4.7.1", cmd_text);
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ var_map_defer_code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn), reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Service unavailable");
+ return (SMTPD_CHECK_DUNNO);
+ }
+
+ /*
+ * PREPEND prepends the specified message header text.
+ */
+ if (STREQUAL(value, "PREPEND", cmd_len)) {
+#ifndef TEST
+ /* XXX what about ETRN. */
+ if (not_in_client_helo(state, table, "PREPEND", reply_class) == 0)
+ return (SMTPD_CHECK_DUNNO);
+#endif
+ if (strcmp(state->where, SMTPD_AFTER_EOM) == 0) {
+ msg_warn("access table %s: action PREPEND must be used before %s",
+ table, VAR_EOD_CHECKS);
+ return (SMTPD_CHECK_DUNNO);
+ }
+ if (*cmd_text == 0 || is_header(cmd_text) == 0) {
+ msg_warn("access table %s entry \"%s\" requires header: text",
+ table, datum);
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ if (state->prepend == 0)
+ state->prepend = argv_alloc(1);
+ argv_add(state->prepend, cmd_text, (char *) 0);
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * All-numeric result probably means OK - some out-of-band authentication
+ * mechanism uses this as time stamp.
+ */
+ if (alldig(value))
+ return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name,
+ "from %s", table));
+
+ /*
+ * 4xx or 5xx means NO as well. smtpd_check_reject() will validate the
+ * response status code.
+ *
+ * If the caller specifies an RFC 3463 enhanced status code, put it
+ * immediately after the SMTP status code as described in RFC 2034.
+ */
+ if (cmd_len == 3 && *cmd_text
+ && (value[0] == '4' || value[0] == '5')
+ && ISDIGIT(value[1]) && ISDIGIT(value[2])) {
+ code = atoi(value);
+ def_dsn[0] = value[0];
+ dsn_split(&dp, def_dsn, cmd_text);
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Access denied"));
+ }
+
+ /*
+ * OK or RELAY means YES. Ignore trailing text.
+ */
+ if (STREQUAL(value, "OK", cmd_len) || STREQUAL(value, "RELAY", cmd_len))
+ return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name,
+ "from %s", table));
+
+ /*
+ * Unfortunately, maps must be declared ahead of time so they can be
+ * opened before we go to jail. We could insist that the RHS can only
+ * contain a pre-defined restriction class name, but that would be too
+ * restrictive. Instead we warn if an access table references any map.
+ *
+ * XXX Don't use passwd files or address rewriting maps as access tables.
+ */
+ if (strchr(value, ':') != 0) {
+ msg_warn("access table %s has entry with lookup table: %s",
+ table, value);
+ msg_warn("do not specify lookup tables inside SMTPD access maps");
+ msg_warn("define a restriction class and specify its name instead.");
+ reject_server_error(state);
+ }
+
+ /*
+ * Don't get carried away with recursion.
+ */
+ if (state->recursion > 100) {
+ msg_warn("access table %s entry %s causes unreasonable recursion",
+ table, value);
+ reject_server_error(state);
+ }
+
+ /*
+ * Recursively evaluate the restrictions given in the right-hand side. In
+ * the dark ages, an empty right-hand side meant OK. Make some
+ * discouraging comments.
+ *
+ * XXX Jump some hoops to avoid a minute memory leak in case of a file
+ * configuration error.
+ */
+#define ADDROF(x) ((char *) &(x))
+
+ restrictions = argv_splitq(value, CHARS_COMMA_SP, CHARS_BRACE);
+ memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf));
+ status = setjmp(smtpd_check_buf);
+ if (status != 0) {
+ argv_free(restrictions);
+ memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf),
+ sizeof(smtpd_check_buf));
+ longjmp(smtpd_check_buf, status);
+ }
+ if (restrictions->argc == 0) {
+ msg_warn("access table %s entry %s has empty value",
+ table, value);
+ status = SMTPD_CHECK_OK;
+ } else {
+ status = generic_checks(state, restrictions, reply_name,
+ reply_class, def_acl);
+ }
+ argv_free(restrictions);
+ memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), sizeof(smtpd_check_buf));
+ return (status);
+}
+
+/* check_access - table lookup without substring magic */
+
+static int check_access(SMTPD_STATE *state, const char *table, const char *name,
+ int flags, int *found, const char *reply_name,
+ const char *reply_class, const char *def_acl)
+{
+ const char *myname = "check_access";
+ const char *value;
+ MAPS *maps;
+
+#define CHK_ACCESS_RETURN(x,y) \
+ { *found = y; return(x); }
+#define FULL 0
+#define PARTIAL DICT_FLAG_FIXED
+#define FOUND 1
+#define MISSED 0
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+
+ if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) {
+ msg_warn("%s: unexpected dictionary: %s", myname, table);
+ value = "451 4.3.5 Server configuration error";
+ CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ if ((value = maps_find(maps, name, flags)) != 0)
+ CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ if (maps->error != 0) {
+ /* Warning is already logged. */
+ value = "451 4.3.5 Server configuration error";
+ CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ CHK_ACCESS_RETURN(SMTPD_CHECK_DUNNO, MISSED);
+}
+
+/* check_domain_access - domainname-based table lookup */
+
+static int check_domain_access(SMTPD_STATE *state, const char *table,
+ const char *domain, int flags,
+ int *found, const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_domain_access";
+ const char *name;
+ const char *next;
+ const char *value;
+ MAPS *maps;
+ int maybe_numerical = 1;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, domain);
+
+ /*
+ * Try the name and its parent domains. Including top-level domains.
+ *
+ * Helo names can end in ".". The test below avoids lookups of the empty
+ * key, because Berkeley DB cannot deal with it. [Victor Duchovni, Morgan
+ * Stanley].
+ *
+ * TODO(wietse) move to mail_domain_find library module.
+ */
+#define CHK_DOMAIN_RETURN(x,y) { *found = y; return(x); }
+
+ if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) {
+ msg_warn("%s: unexpected dictionary: %s", myname, table);
+ value = "451 4.3.5 Server configuration error";
+ CHK_DOMAIN_RETURN(check_table_result(state, table, value,
+ domain, reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ for (name = domain; *name != 0; name = next) {
+ if ((value = maps_find(maps, name, flags)) != 0)
+ CHK_DOMAIN_RETURN(check_table_result(state, table, value,
+ domain, reply_name, reply_class,
+ def_acl), FOUND);
+ if (maps->error != 0) {
+ /* Warning is already logged. */
+ value = "451 4.3.5 Server configuration error";
+ CHK_DOMAIN_RETURN(check_table_result(state, table, value,
+ domain, reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ /* Don't apply subdomain magic to numerical hostnames. */
+ if (maybe_numerical
+ && (maybe_numerical = valid_hostaddr(domain, DONT_GRIPE)) != 0)
+ break;
+ if ((next = strchr(name + 1, '.')) == 0)
+ break;
+ if (access_parent_style == MATCH_FLAG_PARENT)
+ next += 1;
+ flags = PARTIAL;
+ }
+ CHK_DOMAIN_RETURN(SMTPD_CHECK_DUNNO, MISSED);
+}
+
+/* check_addr_access - address-based table lookup */
+
+static int check_addr_access(SMTPD_STATE *state, const char *table,
+ const char *address, int flags,
+ int *found, const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_addr_access";
+ char *addr;
+ const char *value;
+ MAPS *maps;
+ int delim;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, address);
+
+ /*
+ * Try the address and its parent networks.
+ *
+ * TODO(wietse) move to mail_ipaddr_find library module.
+ */
+#define CHK_ADDR_RETURN(x,y) { *found = y; return(x); }
+
+ addr = STR(vstring_strcpy(error_text, address));
+#ifdef HAS_IPV6
+ if (strchr(addr, ':') != 0)
+ delim = ':';
+ else
+#endif
+ delim = '.';
+
+ if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) {
+ msg_warn("%s: unexpected dictionary: %s", myname, table);
+ value = "451 4.3.5 Server configuration error";
+ CHK_ADDR_RETURN(check_table_result(state, table, value, address,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ do {
+ if ((value = maps_find(maps, addr, flags)) != 0)
+ CHK_ADDR_RETURN(check_table_result(state, table, value, address,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ if (maps->error != 0) {
+ /* Warning is already logged. */
+ value = "451 4.3.5 Server configuration error";
+ CHK_ADDR_RETURN(check_table_result(state, table, value, address,
+ reply_name, reply_class,
+ def_acl), FOUND);
+ }
+ flags = PARTIAL;
+ } while (split_at_right(addr, delim));
+
+ CHK_ADDR_RETURN(SMTPD_CHECK_DUNNO, MISSED);
+}
+
+/* check_namadr_access - OK/FAIL based on host name/address lookup */
+
+static int check_namadr_access(SMTPD_STATE *state, const char *table,
+ const char *name, const char *addr,
+ int flags, int *found,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_namadr_access";
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s: name %s addr %s", myname, name, addr);
+
+ /*
+ * Look up the host name, or parent domains thereof. XXX A domain
+ * wildcard may pre-empt a more specific address table entry.
+ */
+ if ((status = check_domain_access(state, table, name, flags,
+ found, reply_name, reply_class,
+ def_acl)) != 0 || *found)
+ return (status);
+
+ /*
+ * Look up the network address, or parent networks thereof.
+ */
+ if ((status = check_addr_access(state, table, addr, flags,
+ found, reply_name, reply_class,
+ def_acl)) != 0 || *found)
+ return (status);
+
+ /*
+ * Undecided when the host was not found.
+ */
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* check_server_access - access control by server host name or address */
+
+static int check_server_access(SMTPD_STATE *state, const char *table,
+ const char *name,
+ int type,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_server_access";
+ const char *domain;
+ const char *adomain;
+ int dns_status;
+ DNS_RR *server_list;
+ DNS_RR *server;
+ int found = 0;
+ MAI_HOSTADDR_STR addr_string;
+ int aierr;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ int status;
+ INET_PROTO_INFO *proto_info;
+
+ /*
+ * Sanity check.
+ */
+ if (type != T_MX && type != T_NS && type != T_A
+#ifdef HAS_IPV6
+ && type != T_AAAA
+#endif
+ )
+ msg_panic("%s: unexpected resource type \"%s\" in request",
+ myname, dns_strtype(type));
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, dns_strtype(type), name);
+
+ /*
+ * Skip over local-part.
+ */
+ if ((domain = strrchr(name, '@')) != 0)
+ domain += 1;
+ else
+ domain = name;
+
+ /*
+ * Treat an address literal as its own MX server, just like we treat a
+ * name without MX record as its own MX server. There is, however, no
+ * applicable NS server equivalent.
+ */
+ if (*domain == '[') {
+ char *saved_addr;
+ const char *bare_addr;
+ ssize_t len;
+
+ if (type != T_A && type != T_MX)
+ return (SMTPD_CHECK_DUNNO);
+ len = strlen(domain);
+ if (domain[len - 1] != ']')
+ return (SMTPD_CHECK_DUNNO);
+ /* Memory leak alert: no early returns after this point. */
+ saved_addr = mystrndup(domain + 1, len - 2);
+ if ((bare_addr = valid_mailhost_addr(saved_addr, DONT_GRIPE)) == 0)
+ status = SMTPD_CHECK_DUNNO;
+ else
+ status = check_addr_access(state, table, bare_addr, FULL,
+ &found, reply_name, reply_class,
+ def_acl);
+ myfree(saved_addr);
+ return (status);
+ }
+
+ /*
+ * Fix 20140924: convert domain to ASCII.
+ */
+#ifndef NO_EAI
+ if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", domain, adomain);
+ domain = adomain;
+ }
+#endif
+
+ /*
+ * If the request is type A or AAAA, fabricate an MX record that points
+ * to the domain name itself, and skip name-based access control.
+ *
+ * If the domain name does not exist then we apply no restriction.
+ *
+ * If the domain name exists but no MX record exists, fabricate an MX record
+ * that points to the domain name itself.
+ *
+ * If the domain name exists but no NS record exists, look up parent domain
+ * NS records.
+ *
+ * XXX 20150707 Work around broken DNS servers that reply with NXDOMAIN
+ * instead of "no data".
+ */
+ if (type == T_A
+#ifdef HAS_IPV6
+ || type == T_AAAA
+#endif
+ ) {
+ server_list = dns_rr_create(domain, domain, T_MX, C_IN, 0, 0,
+ domain, strlen(domain) + 1);
+ } else {
+ dns_status = dns_lookup(domain, type, 0, &server_list,
+ (VSTRING *) 0, (VSTRING *) 0);
+ if (dns_status == DNS_NULLMX)
+ return (SMTPD_CHECK_DUNNO);
+ if (dns_status == DNS_NOTFOUND /* Not: h_errno == NO_DATA */ ) {
+ if (type == T_MX) {
+ server_list = dns_rr_create(domain, domain, type, C_IN, 0, 0,
+ domain, strlen(domain) + 1);
+ dns_status = DNS_OK;
+ } else if (type == T_NS /* && h_errno == NO_DATA */ ) {
+ while ((domain = strchr(domain, '.')) != 0 && domain[1]) {
+ domain += 1;
+ dns_status = dns_lookup(domain, type, 0, &server_list,
+ (VSTRING *) 0, (VSTRING *) 0);
+ if (dns_status != DNS_NOTFOUND /* || h_errno != NO_DATA */ )
+ break;
+ }
+ }
+ }
+ if (dns_status != DNS_OK) {
+ msg_warn("Unable to look up %s host for %s: %s", dns_strtype(type),
+ domain && domain[1] ? domain : name, dns_status == DNS_POLICY ?
+ "DNS reply filter policy" : dns_strerror(h_errno));
+ return (SMTPD_CHECK_DUNNO);
+ }
+ }
+
+ /*
+ * No bare returns after this point or we have a memory leak.
+ */
+#define CHECK_SERVER_RETURN(x) { dns_rr_free(server_list); return(x); }
+
+ /*
+ * Check the hostnames first, then the addresses.
+ */
+ proto_info = inet_proto_info();
+ for (server = server_list; server != 0; server = server->next) {
+ if (msg_verbose)
+ msg_info("%s: %s hostname check: %s",
+ myname, dns_strtype(type), (char *) server->data);
+ if (valid_hostaddr((char *) server->data, DONT_GRIPE)) {
+ if ((status = check_addr_access(state, table, (char *) server->data,
+ FULL, &found, reply_name, reply_class,
+ def_acl)) != 0 || found)
+ CHECK_SERVER_RETURN(status);
+ continue;
+ }
+ if (type != T_A && type != T_AAAA
+ && ((status = check_domain_access(state, table, (char *) server->data,
+ FULL, &found, reply_name, reply_class,
+ def_acl)) != 0 || found))
+ CHECK_SERVER_RETURN(status);
+ if ((aierr = hostname_to_sockaddr((char *) server->data,
+ (char *) 0, 0, &res0)) != 0) {
+ if (type != T_A && type != T_AAAA)
+ msg_warn("Unable to look up %s host %s for %s %s: %s",
+ dns_strtype(type), (char *) server->data,
+ reply_class, reply_name, MAI_STRERROR(aierr));
+ continue;
+ }
+ /* Now we must also free the addrinfo result. */
+ if (msg_verbose)
+ msg_info("%s: %s host address check: %s",
+ myname, dns_strtype(type), (char *) server->data);
+ for (res = res0; res != 0; res = res->ai_next) {
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ if (msg_verbose)
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, server->data);
+ continue;
+ }
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &addr_string, (MAI_SERVPORT_STR *) 0, 0);
+ status = check_addr_access(state, table, addr_string.buf, FULL,
+ &found, reply_name, reply_class,
+ def_acl);
+ if (status != 0 || found) {
+ freeaddrinfo(res0); /* 200412 */
+ CHECK_SERVER_RETURN(status);
+ }
+ }
+ freeaddrinfo(res0); /* 200412 */
+ }
+ CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO);
+}
+
+/* check_ccert_access - access for TLS clients by certificate fingerprint */
+
+static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
+ const char *def_acl)
+{
+ int result = SMTPD_CHECK_DUNNO;
+
+#ifdef USE_TLS
+ const char *myname = "check_ccert_access";
+ int found;
+ const MAP_SEARCH *acl;
+ const char default_search[] = {
+ SMTPD_ACL_SEARCH_CODE_CERT_FPRINT,
+ SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT,
+ 0,
+ };
+ const char *search_order;
+
+ /*
+ * Look up the acl search list. If there is no ACL then we don't have a
+ * table to check.
+ */
+ if ((acl = map_search_lookup(acl_spec)) == 0) {
+ msg_warn("See earlier parsing error messages for '%s", acl_spec);
+ return (smtpd_check_reject(state, MAIL_ERROR_SOFTWARE, 451, "4.3.5",
+ "Server configuration error"));
+ }
+ if ((search_order = acl->search_order) == 0)
+ search_order = default_search;
+ if (msg_verbose)
+ msg_info("%s: search_order length=%ld",
+ myname, (long) strlen(search_order));
+
+ /*
+ * When directly checking the fingerprint, it is OK if the issuing CA is
+ * not trusted.
+ */
+ if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ const char *action;
+ const char *match_this;
+ const char *known_action;
+
+ for (action = search_order; *action; action++) {
+ switch (*action) {
+ case SMTPD_ACL_SEARCH_CODE_CERT_FPRINT:
+ match_this = state->tls_context->peer_cert_fprint;
+ break;
+ case SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT:
+ match_this = state->tls_context->peer_pkey_fprint;
+ break;
+ default:
+ known_action = str_name_code(search_actions, *action);
+ if (known_action == 0)
+ msg_panic("%s: unknown action #%d in '%s'",
+ myname, *action, acl_spec);
+ msg_warn("%s: unexpected action '%s' in '%s'",
+ myname, known_action, acl_spec);
+ return (smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
+ 451, "4.3.5",
+ "Server configuration error"));
+ }
+ if (msg_verbose)
+ msg_info("%s: look up %s %s",
+ myname, str_name_code(search_actions, *action),
+ match_this);
+
+ /*
+ * Log the peer CommonName when access is denied. Non-printable
+ * characters will be neutered by smtpd_check_reject(). The SMTP
+ * client name and address are always syslogged as part of a
+ * "reject" event. XXX Should log the thing that is rejected
+ * (fingerprint etc.) or would that give away too much?
+ */
+ result = check_access(state, acl->map_type_name, match_this,
+ DICT_FLAG_NONE, &found,
+ state->tls_context->peer_CN,
+ SMTPD_NAME_CCERT, def_acl);
+ if (result != SMTPD_CHECK_DUNNO)
+ break;
+ }
+ } else if (!var_smtpd_tls_ask_ccert) {
+ msg_warn("%s is requested, but \"%s = no\"",
+ CHECK_CCERT_ACL, VAR_SMTPD_TLS_ACERT);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: no client certificate", myname);
+ }
+#endif
+ return (result);
+}
+
+/* check_sasl_access - access by SASL user name */
+
+#ifdef USE_SASL_AUTH
+
+static int check_sasl_access(SMTPD_STATE *state, const char *table,
+ const char *def_acl)
+{
+ int result;
+ int unused_found;
+ char *sane_username = printable(mystrdup(state->sasl_username), '_');
+
+ result = check_access(state, table, state->sasl_username,
+ DICT_FLAG_NONE, &unused_found, sane_username,
+ SMTPD_NAME_SASL_USER, def_acl);
+ myfree(sane_username);
+ return (result);
+}
+
+#endif
+
+/* check_mail_access - OK/FAIL based on mail address lookup */
+
+static int check_mail_access(SMTPD_STATE *state, const char *table,
+ const char *addr, int *found,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "check_mail_access";
+ const RESOLVE_REPLY *reply;
+ const char *value;
+ int lookup_strategy;
+ int status;
+ MAPS *maps;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ state->recipient : state->sender, addr);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, addr);
+
+ /*
+ * Garbage in, garbage out. Every address from rewrite_clnt_internal()
+ * and from resolve_clnt_query() must be fully qualified.
+ */
+ if (strrchr(CONST_STR(reply->recipient), '@') == 0) {
+ msg_warn("%s: no @domain in address: %s", myname,
+ CONST_STR(reply->recipient));
+ return (0);
+ }
+
+ /*
+ * Source-routed (non-local or virtual) recipient addresses are too
+ * suspicious for returning an "OK" result. The complicated expression
+ * below was brought to you by the keyboard of Victor Duchovni, Morgan
+ * Stanley and hacked up a bit by Wietse.
+ */
+#define SUSPICIOUS(reply, reply_class) \
+ (var_allow_untrust_route == 0 \
+ && (reply->flags & RESOLVE_FLAG_ROUTED) \
+ && strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0)
+
+ /*
+ * Look up user+foo@domain if the address has an extension, user@domain
+ * otherwise.
+ */
+ lookup_strategy = MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN
+ | MA_FIND_LOCALPART_AT
+ | (access_parent_style == MATCH_FLAG_PARENT ?
+ MA_FIND_PDMS : MA_FIND_PDDMDS);
+
+ if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) {
+ msg_warn("%s: unexpected dictionary: %s", myname, table);
+ value = "451 4.3.5 Server configuration error";
+ return (check_table_result(state, table, value,
+ CONST_STR(reply->recipient),
+ reply_name, reply_class,
+ def_acl));
+ }
+ if ((value = mail_addr_find_strategy(maps, CONST_STR(reply->recipient),
+ (char **) 0, lookup_strategy)) != 0) {
+ *found = 1;
+ status = check_table_result(state, table, value,
+ CONST_STR(reply->recipient),
+ reply_name, reply_class, def_acl);
+ return (status == SMTPD_CHECK_OK && SUSPICIOUS(reply, reply_class) ?
+ SMTPD_CHECK_DUNNO : status);
+ } else if (maps->error != 0) {
+ /* Warning is already logged. */
+ value = "451 4.3.5 Server configuration error";
+ return (check_table_result(state, table, value,
+ CONST_STR(reply->recipient),
+ reply_name, reply_class,
+ def_acl));
+ }
+
+ /*
+ * Undecided when no match found.
+ */
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* Support for different DNSXL lookup results. */
+
+static SMTPD_RBL_STATE dnsxl_stat_soft[1];
+
+#define SMTPD_DNSXL_STAT_SOFT(dnsxl_res) ((dnsxl_res) == dnsxl_stat_soft)
+#define SMTPD_DNXSL_STAT_HARD(dnsxl_res) ((dnsxl_res) == 0)
+#define SMTPD_DNSXL_STAT_OK(dnsxl_res) \
+ !(SMTPD_DNXSL_STAT_HARD(dnsxl_res) || SMTPD_DNSXL_STAT_SOFT(dnsxl_res))
+
+/* rbl_pagein - look up an RBL lookup result */
+
+static void *rbl_pagein(const char *query, void *unused_context)
+{
+ DNS_RR *txt_list;
+ VSTRING *why;
+ int dns_status;
+ SMTPD_RBL_STATE *rbl = 0;
+ DNS_RR *addr_list;
+ DNS_RR *rr;
+ DNS_RR *next;
+ VSTRING *buf;
+ int space_left;
+
+ /*
+ * Do the query. If the DNS lookup produces no definitive reply, give the
+ * requestor the benefit of the doubt. We can't block all email simply
+ * because an RBL server is unavailable.
+ *
+ * Don't do this for AAAA records. Yet.
+ */
+ why = vstring_alloc(10);
+ dns_status = dns_lookup(query, T_A, 0, &addr_list, (VSTRING *) 0, why);
+ if (dns_status != DNS_OK && dns_status != DNS_NOTFOUND) {
+ msg_warn("%s: RBL lookup error: %s", query, STR(why));
+ rbl = dnsxl_stat_soft;
+ }
+ vstring_free(why);
+ if (dns_status != DNS_OK)
+ return ((void *) rbl);
+
+ /*
+ * Save the result. Yes, we cache negative results as well as positive
+ * results. Concatenate multiple TXT records, up to some limit.
+ */
+#define RBL_TXT_LIMIT 500
+
+ rbl = (SMTPD_RBL_STATE *) mymalloc(sizeof(*rbl));
+ dns_status = dns_lookup(query, T_TXT, 0, &txt_list,
+ (VSTRING *) 0, (VSTRING *) 0);
+ if (dns_status == DNS_OK) {
+ buf = vstring_alloc(1);
+ space_left = RBL_TXT_LIMIT;
+ for (rr = txt_list; rr != 0 && space_left > 0; rr = next) {
+ vstring_strncat(buf, rr->data, (int) rr->data_len > space_left ?
+ space_left : rr->data_len);
+ space_left = RBL_TXT_LIMIT - VSTRING_LEN(buf);
+ next = rr->next;
+ if (next && space_left > 3) {
+ vstring_strcat(buf, " / ");
+ space_left -= 3;
+ }
+ }
+ rbl->txt = vstring_export(buf);
+ dns_rr_free(txt_list);
+ } else {
+ if (dns_status == DNS_POLICY)
+ msg_warn("%s: TXT lookup error: %s",
+ query, "DNS reply filter drops all results");
+ rbl->txt = 0;
+ }
+ rbl->a = addr_list;
+ return ((void *) rbl);
+}
+
+/* rbl_pageout - discard an RBL lookup result */
+
+static void rbl_pageout(void *data, void *unused_context)
+{
+ SMTPD_RBL_STATE *rbl = (SMTPD_RBL_STATE *) data;
+
+ if (SMTPD_DNSXL_STAT_OK(rbl)) {
+ if (rbl->txt)
+ myfree(rbl->txt);
+ if (rbl->a)
+ dns_rr_free(rbl->a);
+ myfree((void *) rbl);
+ }
+}
+
+/* rbl_byte_pagein - parse RBL reply pattern, save byte codes */
+
+static void *rbl_byte_pagein(const char *query, void *unused_context)
+{
+ VSTRING *byte_codes = vstring_alloc(100);
+ char *saved_query = mystrdup(query);
+ char *saved_byte_codes;
+ char *err;
+
+ if ((err = ip_match_parse(byte_codes, saved_query)) != 0)
+ msg_fatal("RBL reply error: %s", err);
+ saved_byte_codes = ip_match_save(byte_codes);
+ myfree(saved_query);
+ vstring_free(byte_codes);
+ return (saved_byte_codes);
+}
+
+/* rbl_byte_pageout - discard parsed RBL reply byte codes */
+
+static void rbl_byte_pageout(void *data, void *unused_context)
+{
+ myfree(data);
+}
+
+/* rbl_expand_lookup - RBL specific $name expansion */
+
+static const char *rbl_expand_lookup(const char *name, int mode,
+ void *context)
+{
+ SMTPD_RBL_EXPAND_CONTEXT *rbl_exp = (SMTPD_RBL_EXPAND_CONTEXT *) context;
+ SMTPD_STATE *state = rbl_exp->state;
+
+#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)
+
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(10);
+
+ if (msg_verbose > 1)
+ msg_info("rbl_expand_lookup: ${%s}", name);
+
+ /*
+ * Be sure to return NULL only for non-existent names.
+ */
+ if (STREQ(name, MAIL_ATTR_RBL_CODE)) {
+ vstring_sprintf(state->expand_buf, "%d", var_maps_rbl_code);
+ return (STR(state->expand_buf));
+ } else if (STREQ(name, MAIL_ATTR_RBL_DOMAIN)) {
+ return (rbl_exp->domain);
+ } else if (STREQ(name, MAIL_ATTR_RBL_REASON)) {
+ return (rbl_exp->txt);
+ } else if (STREQ(name, MAIL_ATTR_RBL_TXT)) {/* LaMont compat */
+ return (rbl_exp->txt);
+ } else if (STREQ(name, MAIL_ATTR_RBL_WHAT)) {
+ return (rbl_exp->what);
+ } else if (STREQ(name, MAIL_ATTR_RBL_CLASS)) {
+ return (rbl_exp->class);
+ } else {
+ return (smtpd_expand_lookup(name, mode, (void *) state));
+ }
+}
+
+/* rbl_reject_reply - format reply after RBL reject */
+
+static int rbl_reject_reply(SMTPD_STATE *state, const SMTPD_RBL_STATE *rbl,
+ const char *rbl_domain,
+ const char *what,
+ const char *reply_class)
+{
+ const char *myname = "rbl_reject_reply";
+ VSTRING *why = 0;
+ const char *template = 0;
+ SMTPD_RBL_EXPAND_CONTEXT rbl_exp;
+ int result;
+ DSN_SPLIT dp;
+ int code;
+
+ /*
+ * Use the server-specific reply template or use the default one.
+ */
+ if (*var_rbl_reply_maps) {
+ template = maps_find(rbl_reply_maps, rbl_domain, DICT_FLAG_NONE);
+ if (rbl_reply_maps->error)
+ reject_server_error(state);
+ }
+ why = vstring_alloc(100);
+ rbl_exp.state = state;
+ rbl_exp.domain = mystrdup(rbl_domain);
+ (void) split_at(rbl_exp.domain, '=');
+ rbl_exp.what = what;
+ rbl_exp.class = reply_class;
+ rbl_exp.txt = (rbl->txt == 0 ? "" : rbl->txt);
+
+ for (;;) {
+ if (template == 0)
+ template = var_def_rbl_reply;
+ if (mac_expand(why, template, MAC_EXP_FLAG_NONE,
+ STR(smtpd_expand_filter), rbl_expand_lookup,
+ (void *) &rbl_exp) == 0)
+ break;
+ if (template == var_def_rbl_reply)
+ msg_fatal("%s: bad default rbl reply template: %s",
+ myname, var_def_rbl_reply);
+ msg_warn("%s: bad rbl reply template for domain %s: %s",
+ myname, rbl_domain, template);
+ template = 0; /* pretend not found */
+ }
+
+ /*
+ * XXX Impedance mis-match.
+ *
+ * Validate the response, that is, the response must begin with a
+ * three-digit status code, and the first digit must be 4 or 5. If the
+ * response is bad, log a warning and send a generic response instead.
+ */
+ if ((STR(why)[0] != '4' && STR(why)[0] != '5')
+ || !ISDIGIT(STR(why)[1]) || !ISDIGIT(STR(why)[2])
+ || STR(why)[3] != ' ') {
+ msg_warn("rbl response code configuration error: %s", STR(why));
+ result = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ 450, "4.7.1", "Service unavailable");
+ } else {
+ code = atoi(STR(why));
+ dsn_split(&dp, "4.7.1", STR(why) + 4);
+ result = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ code,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "%s", *dp.text ?
+ dp.text : "Service unavailable");
+ }
+
+ /*
+ * Clean up.
+ */
+ myfree(rbl_exp.domain);
+ vstring_free(why);
+
+ return (result);
+}
+
+/* rbl_match_addr - match address list */
+
+static int rbl_match_addr(SMTPD_RBL_STATE *rbl, const char *byte_codes)
+{
+ const char *myname = "rbl_match_addr";
+ DNS_RR *rr;
+
+ for (rr = rbl->a; rr != 0; rr = rr->next) {
+ if (rr->type == T_A) {
+ if (ip_match_execute(byte_codes, rr->data))
+ return (1);
+ } else {
+ msg_warn("%s: skipping record type %s for query %s",
+ myname, dns_strtype(rr->type), rr->qname);
+ }
+ }
+ return (0);
+}
+
+/* find_dnsxl_addr - look up address in DNSXL */
+
+static const SMTPD_RBL_STATE *find_dnsxl_addr(SMTPD_STATE *state,
+ const char *rbl_domain,
+ const char *addr)
+{
+ const char *myname = "find_dnsxl_addr";
+ ARGV *octets;
+ VSTRING *query;
+ int i;
+ SMTPD_RBL_STATE *rbl;
+ const char *reply_addr;
+ const char *byte_codes;
+ struct addrinfo *res;
+ unsigned char *ipv6_addr;
+
+ query = vstring_alloc(100);
+
+ /*
+ * Reverse the client IPV6 address, represented as 32 hexadecimal
+ * nibbles. We use the binary address to avoid tricky code. Asking for an
+ * AAAA record makes no sense here. Just like with IPv4 we use the lookup
+ * result as a bit mask, not as an IP address.
+ */
+#ifdef HAS_IPV6
+ if (valid_ipv6_hostaddr(addr, DONT_GRIPE)) {
+ if (hostaddr_to_sockaddr(addr, (char *) 0, 0, &res) != 0
+ || res->ai_family != PF_INET6)
+ msg_fatal("%s: unable to convert address %s", myname, addr);
+ ipv6_addr = (unsigned char *) &SOCK_ADDR_IN6_ADDR(res->ai_addr);
+ for (i = sizeof(SOCK_ADDR_IN6_ADDR(res->ai_addr)) - 1; i >= 0; i--)
+ vstring_sprintf_append(query, "%x.%x.",
+ ipv6_addr[i] & 0xf, ipv6_addr[i] >> 4);
+ freeaddrinfo(res);
+ } else
+#endif
+
+ /*
+ * Reverse the client IPV4 address, represented as four decimal octet
+ * values. We use the textual address for convenience.
+ */
+ {
+ octets = argv_split(addr, ".");
+ for (i = octets->argc - 1; i >= 0; i--) {
+ vstring_strcat(query, octets->argv[i]);
+ vstring_strcat(query, ".");
+ }
+ argv_free(octets);
+ }
+
+ /*
+ * Tack on the RBL domain name and query the DNS for an A record.
+ */
+ vstring_strcat(query, rbl_domain);
+ reply_addr = split_at(STR(query), '=');
+ rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query));
+ if (reply_addr != 0)
+ byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr);
+
+ /*
+ * If the record exists, match the result address.
+ */
+ if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0
+ && !rbl_match_addr(rbl, byte_codes))
+ rbl = 0;
+ vstring_free(query);
+ return (rbl);
+}
+
+/* reject_rbl_addr - reject address in real-time blackhole list */
+
+static int reject_rbl_addr(SMTPD_STATE *state, const char *rbl_domain,
+ const char *addr, const char *reply_class)
+{
+ const char *myname = "reject_rbl_addr";
+ const SMTPD_RBL_STATE *rbl;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, reply_class, addr);
+
+ rbl = find_dnsxl_addr(state, rbl_domain, addr);
+ if (!SMTPD_DNSXL_STAT_OK(rbl)) {
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ return (rbl_reject_reply(state, rbl, rbl_domain, addr, reply_class));
+ }
+}
+
+/* permit_dnswl_addr - permit address in DNSWL */
+
+static int permit_dnswl_addr(SMTPD_STATE *state, const char *dnswl_domain,
+ const char *addr, const char *reply_class)
+{
+ const char *myname = "permit_dnswl_addr";
+ const SMTPD_RBL_STATE *dnswl_result;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, addr);
+
+ /* Safety: don't whitelist unauthorized recipients. */
+ if (strcmp(state->where, SMTPD_CMD_RCPT) == 0 && state->recipient != 0
+ && permit_auth_destination(state, state->recipient) != SMTPD_CHECK_OK)
+ return (SMTPD_CHECK_DUNNO);
+
+ dnswl_result = find_dnsxl_addr(state, dnswl_domain, addr);
+ if (SMTPD_DNXSL_STAT_HARD(dnswl_result)) {
+ return (SMTPD_CHECK_DUNNO);
+ } else if (SMTPD_DNSXL_STAT_SOFT(dnswl_result)) {
+ /* XXX: Make configurable as dnswl_tempfail_action. */
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ 450, "4.7.1",
+ "<%s>: %s rejected: %s",
+ addr, reply_class,
+ "Service unavailable");
+ return (SMTPD_CHECK_DUNNO);
+ } else if (SMTPD_DNSXL_STAT_OK(dnswl_result)) {
+ return (SMTPD_CHECK_OK);
+ } else {
+ /* Future proofing, in case find_dnsxl_addr() result is changed. */
+ msg_panic("%s: find_dnsxl_addr API failure", myname);
+ }
+}
+
+/* find_dnsxl_domain - reject if domain in real-time blackhole list */
+
+static const SMTPD_RBL_STATE *find_dnsxl_domain(SMTPD_STATE *state,
+ const char *rbl_domain, const char *what)
+{
+ VSTRING *query;
+ SMTPD_RBL_STATE *rbl;
+ const char *domain;
+ const char *reply_addr;
+ const char *byte_codes;
+ const char *suffix;
+ const char *adomain;
+
+ /*
+ * Extract the domain, tack on the RBL domain name and query the DNS for
+ * an A record.
+ */
+ if ((domain = strrchr(what, '@')) != 0) {
+ domain += 1;
+ if (domain[0] == '[')
+ return (SMTPD_CHECK_DUNNO);
+ } else
+ domain = what;
+
+ /*
+ * XXX Some Spamhaus RHSBL rejects lookups with "No IP queries" even if
+ * the name has an alphanumerical prefix. We play safe, and skip both
+ * RHSBL and RHSWL queries for names ending in a numerical suffix.
+ */
+ if (domain[0] == 0)
+ return (SMTPD_CHECK_DUNNO);
+ suffix = strrchr(domain, '.');
+ if (alldig(suffix == 0 ? domain : suffix + 1))
+ return (SMTPD_CHECK_DUNNO);
+
+ /*
+ * Fix 20140706: convert domain to ASCII.
+ */
+#ifndef NO_EAI
+ if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", domain, adomain);
+ domain = adomain;
+ }
+#endif
+ if (domain[0] == 0 || valid_hostname(domain, DONT_GRIPE) == 0)
+ return (SMTPD_CHECK_DUNNO);
+
+ query = vstring_alloc(100);
+ vstring_sprintf(query, "%s.%s", domain, rbl_domain);
+ reply_addr = split_at(STR(query), '=');
+ rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query));
+ if (reply_addr != 0)
+ byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr);
+
+ /*
+ * If the record exists, match the result address.
+ */
+ if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0
+ && !rbl_match_addr(rbl, byte_codes))
+ rbl = 0;
+ vstring_free(query);
+ return (rbl);
+}
+
+/* reject_rbl_domain - reject if domain in real-time blackhole list */
+
+static int reject_rbl_domain(SMTPD_STATE *state, const char *rbl_domain,
+ const char *what, const char *reply_class)
+{
+ const char *myname = "reject_rbl_domain";
+ const SMTPD_RBL_STATE *rbl;
+
+ if (msg_verbose)
+ msg_info("%s: %s %s", myname, rbl_domain, what);
+
+ rbl = find_dnsxl_domain(state, rbl_domain, what);
+ if (!SMTPD_DNSXL_STAT_OK(rbl)) {
+ return (SMTPD_CHECK_DUNNO);
+ } else {
+ return (rbl_reject_reply(state, rbl, rbl_domain, what, reply_class));
+ }
+}
+
+/* permit_dnswl_domain - permit domain in DNSWL */
+
+static int permit_dnswl_domain(SMTPD_STATE *state, const char *dnswl_domain,
+ const char *what, const char *reply_class)
+{
+ const char *myname = "permit_dnswl_domain";
+ const SMTPD_RBL_STATE *dnswl_result;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, what);
+
+ /* Safety: don't whitelist unauthorized recipients. */
+ if (strcmp(state->where, SMTPD_CMD_RCPT) == 0 && state->recipient != 0
+ && permit_auth_destination(state, state->recipient) != SMTPD_CHECK_OK)
+ return (SMTPD_CHECK_DUNNO);
+
+ dnswl_result = find_dnsxl_domain(state, dnswl_domain, what);
+ if (SMTPD_DNXSL_STAT_HARD(dnswl_result)) {
+ return (SMTPD_CHECK_DUNNO);
+ } else if (SMTPD_DNSXL_STAT_SOFT(dnswl_result)) {
+ /* XXX: Make configurable as rhswl_tempfail_action. */
+ DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
+ 450, "4.7.1",
+ "<%s>: %s rejected: %s",
+ what, reply_class,
+ "Service unavailable");
+ return (SMTPD_CHECK_DUNNO);
+ } else if (SMTPD_DNSXL_STAT_OK(dnswl_result)) {
+ return (SMTPD_CHECK_OK);
+ } else {
+ /* Future proofing, in case find_dnsxl_addr() result is changed. */
+ msg_panic("%s: find_dnsxl_addr API failure", myname);
+ }
+}
+
+/* reject_maps_rbl - reject if client address in real-time blackhole list */
+
+static int reject_maps_rbl(SMTPD_STATE *state)
+{
+ const char *myname = "reject_maps_rbl";
+ char *saved_domains = mystrdup(var_maps_rbl_domains);
+ char *bp = saved_domains;
+ char *rbl_domain;
+ int result = SMTPD_CHECK_DUNNO;
+ static int warned;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, state->addr);
+
+ if (warned == 0) {
+ warned++;
+ msg_warn("support for restriction \"%s\" will be removed from %s; "
+ "use \"%s domain-name\" instead",
+ REJECT_MAPS_RBL, var_mail_name, REJECT_RBL_CLIENT);
+ }
+ while ((rbl_domain = mystrtok(&bp, CHARS_COMMA_SP)) != 0) {
+ result = reject_rbl_addr(state, rbl_domain, state->addr,
+ SMTPD_NAME_CLIENT);
+ if (result != SMTPD_CHECK_DUNNO)
+ break;
+ }
+
+ /*
+ * Clean up.
+ */
+ myfree(saved_domains);
+
+ return (result);
+}
+
+#ifdef USE_SASL_AUTH
+
+/* reject_auth_sender_login_mismatch - logged in client must own sender address */
+
+static int reject_auth_sender_login_mismatch(SMTPD_STATE *state, const char *sender, int allow_unknown_sender)
+{
+ const RESOLVE_REPLY *reply;
+ const char *owners;
+ char *saved_owners;
+ char *cp;
+ char *name;
+ int found = 0;
+
+#define ALLOW_UNKNOWN_SENDER 1
+#define FORBID_UNKNOWN_SENDER 0
+
+ /*
+ * Reject if the client is logged in and does not own the sender address.
+ */
+ if (smtpd_sender_login_maps && state->sasl_username) {
+ reply = smtpd_resolve_addr(state->recipient, sender);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, sender);
+ if ((owners = check_mail_addr_find(state, sender, smtpd_sender_login_maps,
+ STR(reply->recipient), (char **) 0)) != 0) {
+ cp = saved_owners = mystrdup(owners);
+ while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ if (strcasecmp_utf8(state->sasl_username, name) == 0) {
+ found = 1;
+ break;
+ }
+ }
+ myfree(saved_owners);
+ } else if (allow_unknown_sender)
+ return (SMTPD_CHECK_DUNNO);
+ if (!found)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY, 553, "5.7.1",
+ "<%s>: Sender address rejected: not owned by user %s",
+ sender, state->sasl_username));
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+/* reject_unauth_sender_login_mismatch - sender requires client is logged in */
+
+static int reject_unauth_sender_login_mismatch(SMTPD_STATE *state, const char *sender)
+{
+ const RESOLVE_REPLY *reply;
+
+ /*
+ * Reject if the client is not logged in and the sender address has an
+ * owner.
+ */
+ if (smtpd_sender_login_maps && !state->sasl_username) {
+ reply = smtpd_resolve_addr(state->recipient, sender);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, sender);
+ if (check_mail_addr_find(state, sender, smtpd_sender_login_maps,
+ STR(reply->recipient), (char **) 0) != 0)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY, 553, "5.7.1",
+ "<%s>: Sender address rejected: not logged in", sender));
+ }
+ return (SMTPD_CHECK_DUNNO);
+}
+
+#endif
+
+/* valid_utf8_action - validate UTF-8 policy server response */
+
+static int valid_utf8_action(const char *server, const char *action)
+{
+ int retval;
+
+ if ((retval = valid_utf8_string(action, strlen(action))) == 0)
+ msg_warn("malformed UTF-8 in policy server %s response: \"%s\"",
+ server, action);
+ return (retval);
+}
+
+/* check_policy_service - check delegated policy service */
+
+static int check_policy_service(SMTPD_STATE *state, const char *server,
+ const char *reply_name, const char *reply_class,
+ const char *def_acl)
+{
+ static VSTRING *action = 0;
+ SMTPD_POLICY_CLNT *policy_clnt;
+
+#ifdef USE_TLS
+ VSTRING *subject_buf;
+ VSTRING *issuer_buf;
+ const char *subject;
+ const char *issuer;
+
+#endif
+ int ret;
+
+ /*
+ * Sanity check.
+ */
+ if (!policy_clnt_table
+ || (policy_clnt = (SMTPD_POLICY_CLNT *)
+ htable_find(policy_clnt_table, server)) == 0)
+ msg_panic("check_policy_service: no client endpoint for server %s",
+ server);
+
+ /*
+ * Initialize.
+ */
+ if (action == 0)
+ action = vstring_alloc(10);
+
+#ifdef USE_TLS
+#define ENCODE_CN(coded_CN, coded_CN_buf, CN) do { \
+ if (!TLS_CERT_IS_TRUSTED(state->tls_context) || *(CN) == 0) { \
+ coded_CN_buf = 0; \
+ coded_CN = ""; \
+ } else { \
+ coded_CN_buf = vstring_alloc(strlen(CN) + 1); \
+ xtext_quote(coded_CN_buf, CN, ""); \
+ coded_CN = STR(coded_CN_buf); \
+ } \
+ } while (0);
+
+ ENCODE_CN(subject, subject_buf, state->tls_context->peer_CN);
+ ENCODE_CN(issuer, issuer_buf, state->tls_context->issuer_CN);
+#endif
+
+ if (attr_clnt_request(policy_clnt->client,
+ ATTR_FLAG_NONE, /* Query attributes. */
+ SEND_ATTR_STR(MAIL_ATTR_REQ, "smtpd_access_policy"),
+ SEND_ATTR_STR(MAIL_ATTR_PROTO_STATE,
+ STREQ(state->where, SMTPD_CMD_BDAT) ?
+ SMTPD_CMD_DATA : state->where),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_PROTO_NAME, state->protocol),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->addr),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_NAME, state->name),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->port),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_REVERSE_CLIENT_NAME,
+ state->reverse_name),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR,
+ state->dest_addr),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT,
+ state->dest_port),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_HELO_NAME,
+ state->helo_name ? state->helo_name : ""),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER,
+ state->sender ? state->sender : ""),
+ SEND_ATTR_STR(MAIL_ATTR_RECIP,
+ state->recipient ? state->recipient : ""),
+ SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT,
+ ((strcasecmp(state->where, SMTPD_CMD_DATA) == 0) ||
+ (strcasecmp(state->where, SMTPD_CMD_BDAT) == 0) ||
+ (strcasecmp(state->where, SMTPD_AFTER_EOM) == 0)) ?
+ state->rcpt_count : 0),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID,
+ state->queue_id ? state->queue_id : ""),
+ SEND_ATTR_STR(MAIL_ATTR_INSTANCE,
+ STR(state->instance)),
+ SEND_ATTR_LONG(MAIL_ATTR_SIZE,
+ (unsigned long) (state->act_size > 0 ?
+ state->act_size : state->msg_size)),
+ SEND_ATTR_STR(MAIL_ATTR_ETRN_DOMAIN,
+ state->etrn_name ? state->etrn_name : ""),
+ SEND_ATTR_STR(MAIL_ATTR_STRESS, var_stress),
+#ifdef USE_SASL_AUTH
+ SEND_ATTR_STR(MAIL_ATTR_SASL_METHOD,
+ state->sasl_method ? state->sasl_method : ""),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_USERNAME,
+ state->sasl_username ? state->sasl_username : ""),
+ SEND_ATTR_STR(MAIL_ATTR_SASL_SENDER,
+ state->sasl_sender ? state->sasl_sender : ""),
+#endif
+#ifdef USE_TLS
+#define IF_ENCRYPTED(x, y) ((state->tls_context && ((x) != 0)) ? (x) : (y))
+ SEND_ATTR_STR(MAIL_ATTR_CCERT_SUBJECT, subject),
+ SEND_ATTR_STR(MAIL_ATTR_CCERT_ISSUER, issuer),
+
+ /*
+ * When directly checking the fingerprint, it is OK if the issuing CA is
+ * not trusted.
+ */
+ SEND_ATTR_STR(MAIL_ATTR_CCERT_CERT_FPRINT,
+ IF_ENCRYPTED(state->tls_context->peer_cert_fprint, "")),
+ SEND_ATTR_STR(MAIL_ATTR_CCERT_PKEY_FPRINT,
+ IF_ENCRYPTED(state->tls_context->peer_pkey_fprint, "")),
+ SEND_ATTR_STR(MAIL_ATTR_CRYPTO_PROTOCOL,
+ IF_ENCRYPTED(state->tls_context->protocol, "")),
+ SEND_ATTR_STR(MAIL_ATTR_CRYPTO_CIPHER,
+ IF_ENCRYPTED(state->tls_context->cipher_name, "")),
+ SEND_ATTR_INT(MAIL_ATTR_CRYPTO_KEYSIZE,
+ IF_ENCRYPTED(state->tls_context->cipher_usebits, 0)),
+#endif
+ SEND_ATTR_STR(MAIL_ATTR_POL_CONTEXT,
+ policy_clnt->policy_context),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes. */
+ RECV_ATTR_STR(MAIL_ATTR_ACTION, action),
+ ATTR_TYPE_END) != 1
+ || (var_smtputf8_enable && valid_utf8_action(server, STR(action)) == 0)) {
+ NOCLOBBER static int nesting_level = 0;
+ jmp_buf savebuf;
+ int status;
+
+ /*
+ * Safety to prevent recursive execution of the default action.
+ */
+ nesting_level += 1;
+ memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf));
+ status = setjmp(smtpd_check_buf);
+ if (status != 0) {
+ nesting_level -= 1;
+ memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf),
+ sizeof(smtpd_check_buf));
+ longjmp(smtpd_check_buf, status);
+ }
+ ret = check_table_result(state, server, nesting_level == 1 ?
+ policy_clnt->def_action :
+ DEF_SMTPD_POLICY_DEF_ACTION,
+ "policy query", reply_name,
+ reply_class, def_acl);
+ nesting_level -= 1;
+ memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf),
+ sizeof(smtpd_check_buf));
+ } else {
+
+ /*
+ * XXX This produces bogus error messages when the reply is
+ * malformed.
+ */
+ ret = check_table_result(state, server, STR(action),
+ "policy query", reply_name,
+ reply_class, def_acl);
+ }
+#ifdef USE_TLS
+ if (subject_buf)
+ vstring_free(subject_buf);
+ if (issuer_buf)
+ vstring_free(issuer_buf);
+#endif
+ return (ret);
+}
+
+/* is_map_command - restriction has form: check_xxx_access type:name */
+
+static int is_map_command(SMTPD_STATE *state, const char *name,
+ const char *command, char ***argp)
+{
+
+ /*
+ * This is a three-valued function: (a) this is not a check_xxx_access
+ * command, (b) this is a malformed check_xxx_access command, (c) this is
+ * a well-formed check_xxx_access command. That's too clumsy for function
+ * result values, so we use regular returns for (a) and (c), and use long
+ * jumps for the error case (b).
+ */
+ if (strcasecmp(name, command) != 0) {
+ return (0);
+ } else if (*(*argp + 1) == 0 || strchr(*(*argp += 1), ':') == 0) {
+ msg_warn("restriction %s: bad argument \"%s\": need maptype:mapname",
+ command, **argp);
+ reject_server_error(state);
+ } else {
+ return (1);
+ }
+}
+
+/* forbid_whitelist - disallow whitelisting */
+
+static void forbid_whitelist(SMTPD_STATE *state, const char *name,
+ int status, const char *target)
+{
+ if (state->discard == 0 && status == SMTPD_CHECK_OK) {
+ msg_warn("restriction %s returns OK for %s", name, target);
+ msg_warn("this is not allowed for security reasons");
+ msg_warn("use DUNNO instead of OK if you want to make an exception");
+ reject_server_error(state);
+ }
+}
+
+/* generic_checks - generic restrictions */
+
+static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
+ const char *reply_name,
+ const char *reply_class,
+ const char *def_acl)
+{
+ const char *myname = "generic_checks";
+ char **cpp;
+ const char *name;
+ int status = 0;
+ ARGV *list;
+ int found;
+ int saved_recursion = state->recursion++;
+
+ if (msg_verbose)
+ msg_info(">>> START %s RESTRICTIONS <<<", reply_class);
+
+ for (cpp = restrictions->argv; (name = *cpp) != 0; cpp++) {
+
+ if (state->discard != 0)
+ break;
+
+ if (msg_verbose)
+ msg_info("%s: name=%s", myname, name);
+
+ /*
+ * Pseudo restrictions.
+ */
+ if (strcasecmp(name, WARN_IF_REJECT) == 0) {
+ if (state->warn_if_reject == 0)
+ state->warn_if_reject = state->recursion;
+ continue;
+ }
+
+ /*
+ * Spoof the is_map_command() routine, so that we do not have to make
+ * special cases for the implicit short-hand access map notation.
+ */
+#define NO_DEF_ACL 0
+
+ if (strchr(name, ':') != 0) {
+ if (def_acl == NO_DEF_ACL) {
+ msg_warn("specify one of (%s, %s, %s, %s, %s, %s) before %s restriction \"%s\"",
+ CHECK_CLIENT_ACL, CHECK_REVERSE_CLIENT_ACL, CHECK_HELO_ACL, CHECK_SENDER_ACL,
+ CHECK_RECIP_ACL, CHECK_ETRN_ACL, reply_class, name);
+ reject_server_error(state);
+ }
+ name = def_acl;
+ cpp -= 1;
+ }
+
+ /*
+ * Generic restrictions.
+ */
+ if (strcasecmp(name, PERMIT_ALL) == 0) {
+ status = smtpd_acl_permit(state, name, reply_class,
+ reply_name, NO_PRINT_ARGS);
+ if (status == SMTPD_CHECK_OK && cpp[1] != 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ cpp[1], PERMIT_ALL);
+ } else if (strcasecmp(name, DEFER_ALL) == 0) {
+ status = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_defer_code, "4.3.2",
+ "<%s>: %s rejected: Try again later",
+ reply_name, reply_class);
+ if (cpp[1] != 0 && state->warn_if_reject == 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ cpp[1], DEFER_ALL);
+ } else if (strcasecmp(name, REJECT_ALL) == 0) {
+ status = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_reject_code, "5.7.1",
+ "<%s>: %s rejected: Access denied",
+ reply_name, reply_class);
+ if (cpp[1] != 0 && state->warn_if_reject == 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ cpp[1], REJECT_ALL);
+ } else if (strcasecmp(name, REJECT_UNAUTH_PIPE) == 0) {
+ status = reject_unauth_pipelining(state, reply_name, reply_class);
+ } else if (strcasecmp(name, CHECK_POLICY_SERVICE) == 0) {
+ if (cpp[1] == 0 || strchr(cpp[1], ':') == 0) {
+ msg_warn("restriction %s must be followed by transport:server",
+ CHECK_POLICY_SERVICE);
+ reject_server_error(state);
+ } else
+ status = check_policy_service(state, *++cpp, reply_name,
+ reply_class, def_acl);
+ } else if (strcasecmp(name, DEFER_IF_PERMIT) == 0) {
+ status = DEFER_IF_PERMIT2(DEFER_IF_PERMIT_ACT,
+ state, MAIL_ERROR_POLICY,
+ 450, "4.7.0",
+ "<%s>: %s rejected: defer_if_permit requested",
+ reply_name, reply_class);
+ } else if (strcasecmp(name, DEFER_IF_REJECT) == 0) {
+ DEFER_IF_REJECT2(state, MAIL_ERROR_POLICY,
+ 450, "4.7.0",
+ "<%s>: %s rejected: defer_if_reject requested",
+ reply_name, reply_class);
+ } else if (strcasecmp(name, SLEEP) == 0) {
+ if (cpp[1] == 0 || alldig(cpp[1]) == 0) {
+ msg_warn("restriction %s must be followed by number", SLEEP);
+ reject_server_error(state);
+ } else
+ sleep(atoi(*++cpp));
+ } else if (strcasecmp(name, REJECT_PLAINTEXT_SESSION) == 0) {
+ status = reject_plaintext_session(state);
+ }
+
+ /*
+ * Client name/address restrictions.
+ */
+ else if (strcasecmp(name, REJECT_UNKNOWN_CLIENT_HOSTNAME) == 0
+ || strcasecmp(name, REJECT_UNKNOWN_CLIENT) == 0) {
+ status = reject_unknown_client(state);
+ } else if (strcasecmp(name, REJECT_UNKNOWN_REVERSE_HOSTNAME) == 0) {
+ status = reject_unknown_reverse_name(state);
+ } else if (strcasecmp(name, PERMIT_INET_INTERFACES) == 0) {
+ status = permit_inet_interfaces(state);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ } else if (strcasecmp(name, PERMIT_MYNETWORKS) == 0) {
+ status = permit_mynetworks(state);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ } else if (is_map_command(state, name, CHECK_CLIENT_ACL, &cpp)) {
+ status = check_namadr_access(state, *cpp, state->name, state->addr,
+ FULL, &found, state->namaddr,
+ SMTPD_NAME_CLIENT, def_acl);
+ } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_ACL, &cpp)) {
+ status = check_namadr_access(state, *cpp, state->reverse_name, state->addr,
+ FULL, &found, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT, def_acl);
+ forbid_whitelist(state, name, status, state->reverse_name);
+ } else if (strcasecmp(name, REJECT_MAPS_RBL) == 0) {
+ status = reject_maps_rbl(state);
+ } else if (strcasecmp(name, REJECT_RBL_CLIENT) == 0
+ || strcasecmp(name, REJECT_RBL) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);
+ else
+ status = reject_rbl_addr(state, *(cpp += 1), state->addr,
+ SMTPD_NAME_CLIENT);
+ } else if (strcasecmp(name, PERMIT_DNSWL_CLIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);
+ else {
+ status = permit_dnswl_addr(state, *(cpp += 1), state->addr,
+ SMTPD_NAME_CLIENT);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_CLIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument",
+ name);
+ else {
+ cpp += 1;
+ if (strcasecmp(state->name, "unknown") != 0)
+ status = reject_rbl_domain(state, *cpp, state->name,
+ SMTPD_NAME_CLIENT);
+ }
+ } else if (strcasecmp(name, PERMIT_RHSWL_CLIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument",
+ name);
+ else {
+ cpp += 1;
+ if (strcasecmp(state->name, "unknown") != 0) {
+ status = permit_dnswl_domain(state, *cpp, state->name,
+ SMTPD_NAME_CLIENT);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name,
+ SMTPD_NAME_CLIENT, state->namaddr, NO_PRINT_ARGS);
+ }
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_REVERSE_CLIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument",
+ name);
+ else {
+ cpp += 1;
+ if (strcasecmp(state->reverse_name, "unknown") != 0)
+ status = reject_rbl_domain(state, *cpp, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT);
+ }
+ } else if (is_map_command(state, name, CHECK_CCERT_ACL, &cpp)) {
+ status = check_ccert_access(state, *cpp, def_acl);
+ } else if (is_map_command(state, name, CHECK_SASL_ACL, &cpp)) {
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ if (state->sasl_username && state->sasl_username[0])
+ status = check_sasl_access(state, *cpp, def_acl);
+ } else
+#endif
+ msg_warn("restriction `%s' ignored: no SASL support", name);
+ } else if (is_map_command(state, name, CHECK_CLIENT_NS_ACL, &cpp)) {
+ if (strcasecmp(state->name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->name,
+ T_NS, state->namaddr,
+ SMTPD_NAME_CLIENT, def_acl);
+ forbid_whitelist(state, name, status, state->name);
+ }
+ } else if (is_map_command(state, name, CHECK_CLIENT_MX_ACL, &cpp)) {
+ if (strcasecmp(state->name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->name,
+ T_MX, state->namaddr,
+ SMTPD_NAME_CLIENT, def_acl);
+ forbid_whitelist(state, name, status, state->name);
+ }
+ } else if (is_map_command(state, name, CHECK_CLIENT_A_ACL, &cpp)) {
+ if (strcasecmp(state->name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->name,
+ T_A, state->namaddr,
+ SMTPD_NAME_CLIENT, def_acl);
+ forbid_whitelist(state, name, status, state->name);
+ }
+ } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_NS_ACL, &cpp)) {
+ if (strcasecmp(state->reverse_name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->reverse_name,
+ T_NS, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT, def_acl);
+ forbid_whitelist(state, name, status, state->reverse_name);
+ }
+ } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_MX_ACL, &cpp)) {
+ if (strcasecmp(state->reverse_name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->reverse_name,
+ T_MX, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT, def_acl);
+ forbid_whitelist(state, name, status, state->reverse_name);
+ }
+ } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_A_ACL, &cpp)) {
+ if (strcasecmp(state->reverse_name, "unknown") != 0) {
+ status = check_server_access(state, *cpp, state->reverse_name,
+ T_A, state->reverse_name,
+ SMTPD_NAME_REV_CLIENT, def_acl);
+ forbid_whitelist(state, name, status, state->reverse_name);
+ }
+ }
+
+ /*
+ * HELO/EHLO parameter restrictions.
+ */
+ else if (is_map_command(state, name, CHECK_HELO_ACL, &cpp)) {
+ if (state->helo_name)
+ status = check_domain_access(state, *cpp, state->helo_name,
+ FULL, &found, state->helo_name,
+ SMTPD_NAME_HELO, def_acl);
+ } else if (strcasecmp(name, REJECT_INVALID_HELO_HOSTNAME) == 0
+ || strcasecmp(name, REJECT_INVALID_HOSTNAME) == 0) {
+ if (state->helo_name) {
+ if (*state->helo_name != '[')
+ status = reject_invalid_hostname(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ else
+ status = reject_invalid_hostaddr(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ }
+ } else if (strcasecmp(name, REJECT_UNKNOWN_HELO_HOSTNAME) == 0
+ || strcasecmp(name, REJECT_UNKNOWN_HOSTNAME) == 0) {
+ if (state->helo_name) {
+ if (*state->helo_name != '[')
+ status = reject_unknown_hostname(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ else
+ status = reject_invalid_hostaddr(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ }
+ } else if (strcasecmp(name, PERMIT_NAKED_IP_ADDR) == 0) {
+ msg_warn("restriction %s is deprecated. Use %s or %s instead",
+ PERMIT_NAKED_IP_ADDR, PERMIT_MYNETWORKS, PERMIT_SASL_AUTH);
+ if (state->helo_name) {
+ if (state->helo_name[strspn(state->helo_name, "0123456789.:")] == 0
+ && (status = reject_invalid_hostaddr(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO)) == 0)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_HELO,
+ state->helo_name, NO_PRINT_ARGS);
+ }
+ } else if (is_map_command(state, name, CHECK_HELO_NS_ACL, &cpp)) {
+ if (state->helo_name) {
+ status = check_server_access(state, *cpp, state->helo_name,
+ T_NS, state->helo_name,
+ SMTPD_NAME_HELO, def_acl);
+ forbid_whitelist(state, name, status, state->helo_name);
+ }
+ } else if (is_map_command(state, name, CHECK_HELO_MX_ACL, &cpp)) {
+ if (state->helo_name) {
+ status = check_server_access(state, *cpp, state->helo_name,
+ T_MX, state->helo_name,
+ SMTPD_NAME_HELO, def_acl);
+ forbid_whitelist(state, name, status, state->helo_name);
+ }
+ } else if (is_map_command(state, name, CHECK_HELO_A_ACL, &cpp)) {
+ if (state->helo_name) {
+ status = check_server_access(state, *cpp, state->helo_name,
+ T_A, state->helo_name,
+ SMTPD_NAME_HELO, def_acl);
+ forbid_whitelist(state, name, status, state->helo_name);
+ }
+ } else if (strcasecmp(name, REJECT_NON_FQDN_HELO_HOSTNAME) == 0
+ || strcasecmp(name, REJECT_NON_FQDN_HOSTNAME) == 0) {
+ if (state->helo_name) {
+ if (*state->helo_name != '[')
+ status = reject_non_fqdn_hostname(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ else
+ status = reject_invalid_hostaddr(state, state->helo_name,
+ state->helo_name, SMTPD_NAME_HELO);
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_HELO) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument",
+ name);
+ else {
+ cpp += 1;
+ if (state->helo_name)
+ status = reject_rbl_domain(state, *cpp, state->helo_name,
+ SMTPD_NAME_HELO);
+ }
+ }
+
+ /*
+ * Sender mail address restrictions.
+ */
+ else if (is_map_command(state, name, CHECK_SENDER_ACL, &cpp)) {
+ if (state->sender && *state->sender)
+ status = check_mail_access(state, *cpp, state->sender,
+ &found, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ if (state->sender && !*state->sender)
+ status = check_access(state, *cpp, var_smtpd_null_key, FULL,
+ &found, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ } else if (strcasecmp(name, REJECT_UNKNOWN_ADDRESS) == 0) {
+ if (state->sender && *state->sender)
+ status = reject_unknown_address(state, state->sender,
+ state->sender, SMTPD_NAME_SENDER);
+ } else if (strcasecmp(name, REJECT_UNKNOWN_SENDDOM) == 0) {
+ if (state->sender && *state->sender)
+ status = reject_unknown_address(state, state->sender,
+ state->sender, SMTPD_NAME_SENDER);
+ } else if (strcasecmp(name, REJECT_UNVERIFIED_SENDER) == 0) {
+ if (state->sender && *state->sender)
+ status = reject_unverified_address(state, state->sender,
+ state->sender, SMTPD_NAME_SENDER,
+ var_unv_from_dcode, var_unv_from_rcode,
+ unv_from_tf_act,
+ var_unv_from_why);
+ } else if (strcasecmp(name, REJECT_NON_FQDN_SENDER) == 0) {
+ if (state->sender && *state->sender)
+ status = reject_non_fqdn_address(state, state->sender,
+ state->sender, SMTPD_NAME_SENDER);
+ } else if (strcasecmp(name, REJECT_AUTH_SENDER_LOGIN_MISMATCH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ if (state->sender && *state->sender)
+ status = reject_auth_sender_login_mismatch(state,
+ state->sender, FORBID_UNKNOWN_SENDER);
+ } else
+#endif
+ msg_warn("restriction `%s' ignored: no SASL support", name);
+ } else if (strcasecmp(name, REJECT_KNOWN_SENDER_LOGIN_MISMATCH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ if (state->sender && *state->sender) {
+ if (state->sasl_username)
+ status = reject_auth_sender_login_mismatch(state,
+ state->sender, ALLOW_UNKNOWN_SENDER);
+ else
+ status = reject_unauth_sender_login_mismatch(state, state->sender);
+ }
+ } else
+#endif
+ msg_warn("restriction `%s' ignored: no SASL support", name);
+ } else if (strcasecmp(name, REJECT_UNAUTH_SENDER_LOGIN_MISMATCH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (var_smtpd_sasl_enable) {
+ if (state->sender && *state->sender)
+ status = reject_unauth_sender_login_mismatch(state, state->sender);
+ } else
+#endif
+ msg_warn("restriction `%s' ignored: no SASL support", name);
+ } else if (is_map_command(state, name, CHECK_SENDER_NS_ACL, &cpp)) {
+ if (state->sender && *state->sender) {
+ status = check_server_access(state, *cpp, state->sender,
+ T_NS, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ forbid_whitelist(state, name, status, state->sender);
+ }
+ } else if (is_map_command(state, name, CHECK_SENDER_MX_ACL, &cpp)) {
+ if (state->sender && *state->sender) {
+ status = check_server_access(state, *cpp, state->sender,
+ T_MX, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ forbid_whitelist(state, name, status, state->sender);
+ }
+ } else if (is_map_command(state, name, CHECK_SENDER_A_ACL, &cpp)) {
+ if (state->sender && *state->sender) {
+ status = check_server_access(state, *cpp, state->sender,
+ T_A, state->sender,
+ SMTPD_NAME_SENDER, def_acl);
+ forbid_whitelist(state, name, status, state->sender);
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_SENDER) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);
+ else {
+ cpp += 1;
+ if (state->sender && *state->sender)
+ status = reject_rbl_domain(state, *cpp, state->sender,
+ SMTPD_NAME_SENDER);
+ }
+ } else if (strcasecmp(name, REJECT_UNLISTED_SENDER) == 0) {
+ if (state->sender && *state->sender)
+ status = check_sender_rcpt_maps(state, state->sender);
+ }
+
+ /*
+ * Recipient mail address restrictions.
+ */
+ else if (is_map_command(state, name, CHECK_RECIP_ACL, &cpp)) {
+ if (state->recipient)
+ status = check_mail_access(state, *cpp, state->recipient,
+ &found, state->recipient,
+ SMTPD_NAME_RECIPIENT, def_acl);
+ } else if (strcasecmp(name, PERMIT_MX_BACKUP) == 0) {
+ if (state->recipient) {
+ status = permit_mx_backup(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT,
+ state->recipient, NO_PRINT_ARGS);
+ }
+ } else if (strcasecmp(name, PERMIT_AUTH_DEST) == 0) {
+ if (state->recipient) {
+ status = permit_auth_destination(state, state->recipient);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT,
+ state->recipient, NO_PRINT_ARGS);
+ }
+ } else if (strcasecmp(name, REJECT_UNAUTH_DEST) == 0) {
+ if (state->recipient)
+ status = reject_unauth_destination(state, state->recipient,
+ var_relay_code, "5.7.1");
+ } else if (strcasecmp(name, DEFER_UNAUTH_DEST) == 0) {
+ if (state->recipient)
+ status = reject_unauth_destination(state, state->recipient,
+ var_relay_code - 100, "4.7.1");
+ } else if (strcasecmp(name, CHECK_RELAY_DOMAINS) == 0) {
+ if (state->recipient)
+ status = check_relay_domains(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT,
+ state->recipient, NO_PRINT_ARGS);
+ if (cpp[1] != 0 && state->warn_if_reject == 0)
+ msg_warn("restriction `%s' after `%s' is ignored",
+ cpp[1], CHECK_RELAY_DOMAINS);
+ } else if (strcasecmp(name, PERMIT_SASL_AUTH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (smtpd_sasl_is_active(state)) {
+ status = permit_sasl_auth(state,
+ SMTPD_CHECK_OK, SMTPD_CHECK_DUNNO);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ }
+#endif
+ } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) {
+ status = permit_tls_clientcerts(state, 1);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) {
+ status = permit_tls_clientcerts(state, 0);
+ if (status == SMTPD_CHECK_OK)
+ status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT,
+ state->namaddr, NO_PRINT_ARGS);
+ } else if (strcasecmp(name, REJECT_UNKNOWN_RCPTDOM) == 0) {
+ if (state->recipient)
+ status = reject_unknown_address(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT);
+ } else if (strcasecmp(name, REJECT_NON_FQDN_RCPT) == 0) {
+ if (state->recipient)
+ status = reject_non_fqdn_address(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT);
+ } else if (is_map_command(state, name, CHECK_RECIP_NS_ACL, &cpp)) {
+ if (state->recipient && *state->recipient) {
+ status = check_server_access(state, *cpp, state->recipient,
+ T_NS, state->recipient,
+ SMTPD_NAME_RECIPIENT, def_acl);
+ forbid_whitelist(state, name, status, state->recipient);
+ }
+ } else if (is_map_command(state, name, CHECK_RECIP_MX_ACL, &cpp)) {
+ if (state->recipient && *state->recipient) {
+ status = check_server_access(state, *cpp, state->recipient,
+ T_MX, state->recipient,
+ SMTPD_NAME_RECIPIENT, def_acl);
+ forbid_whitelist(state, name, status, state->recipient);
+ }
+ } else if (is_map_command(state, name, CHECK_RECIP_A_ACL, &cpp)) {
+ if (state->recipient && *state->recipient) {
+ status = check_server_access(state, *cpp, state->recipient,
+ T_A, state->recipient,
+ SMTPD_NAME_RECIPIENT, def_acl);
+ forbid_whitelist(state, name, status, state->recipient);
+ }
+ } else if (strcasecmp(name, REJECT_RHSBL_RECIPIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);
+ else {
+ cpp += 1;
+ if (state->recipient)
+ status = reject_rbl_domain(state, *cpp, state->recipient,
+ SMTPD_NAME_RECIPIENT);
+ }
+ } else if (strcasecmp(name, CHECK_RCPT_MAPS) == 0
+ || strcasecmp(name, REJECT_UNLISTED_RCPT) == 0) {
+ if (state->recipient && *state->recipient)
+ status = check_recipient_rcpt_maps(state, state->recipient);
+ } else if (strcasecmp(name, REJECT_MUL_RCPT_BOUNCE) == 0) {
+ if (state->sender && *state->sender == 0 && state->rcpt_count
+ > (strcmp(state->where, SMTPD_CMD_RCPT) != 0))
+ status = smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ var_mul_rcpt_code, "5.5.3",
+ "<%s>: %s rejected: Multi-recipient bounce",
+ reply_name, reply_class);
+ } else if (strcasecmp(name, REJECT_UNVERIFIED_RECIP) == 0) {
+ if (state->recipient && *state->recipient)
+ status = reject_unverified_address(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT,
+ var_unv_rcpt_dcode, var_unv_rcpt_rcode,
+ unv_rcpt_tf_act,
+ var_unv_rcpt_why);
+ }
+
+ /*
+ * ETRN domain name restrictions.
+ */
+ else if (is_map_command(state, name, CHECK_ETRN_ACL, &cpp)) {
+ if (state->etrn_name)
+ status = check_domain_access(state, *cpp, state->etrn_name,
+ FULL, &found, state->etrn_name,
+ SMTPD_NAME_ETRN, def_acl);
+ }
+
+ /*
+ * User-defined restriction class.
+ */
+ else if ((list = (ARGV *) htable_find(smtpd_rest_classes, name)) != 0) {
+ status = generic_checks(state, list, reply_name,
+ reply_class, def_acl);
+ }
+
+ /*
+ * Error: undefined restriction name.
+ */
+ else {
+ msg_warn("unknown smtpd restriction: \"%s\"", name);
+ reject_server_error(state);
+ }
+ if (msg_verbose)
+ msg_info("%s: name=%s status=%d", myname, name, status);
+
+ if (status < 0) {
+ if (status == DICT_ERR_RETRY)
+ reject_dict_retry(state, reply_name);
+ else
+ reject_server_error(state);
+ }
+ if (state->warn_if_reject >= state->recursion)
+ state->warn_if_reject = 0;
+
+ if (status != 0)
+ break;
+
+ if (state->defer_if_permit.active && state->defer_if_reject.active)
+ break;
+ }
+ if (msg_verbose)
+ msg_info(">>> END %s RESTRICTIONS <<<", reply_class);
+
+ state->recursion = saved_recursion;
+
+ /* In case the list terminated with one or more warn_if_mumble. */
+ if (state->warn_if_reject >= state->recursion)
+ state->warn_if_reject = 0;
+
+ return (status);
+}
+
+/* smtpd_check_addr - address sanity check */
+
+int smtpd_check_addr(const char *sender, const char *addr, int smtputf8)
+{
+ const RESOLVE_REPLY *resolve_reply;
+ const char *myname = "smtpd_check_addr";
+ const char *domain;
+
+ if (msg_verbose)
+ msg_info("%s: addr=%s", myname, addr);
+
+ /*
+ * Catch syntax errors early on if we can, but be prepared to re-compute
+ * the result later when the cache fills up with lots of recipients, at
+ * which time errors can still happen.
+ */
+ if (addr == 0 || *addr == 0)
+ return (0);
+ resolve_reply = smtpd_resolve_addr(sender, addr);
+ if (resolve_reply->flags & RESOLVE_FLAG_ERROR)
+ return (-1);
+
+ /*
+ * Backwards compatibility: if the client does not request SMTPUTF8
+ * support, then behave like Postfix < 3.0 trivial-rewrite, and don't
+ * allow non-ASCII email domains. Historically, Postfix does not reject
+ * UTF8 etc. in the address localpart.
+ */
+ if (smtputf8 == 0
+ && (domain = strrchr(STR(resolve_reply->recipient), '@')) != 0
+ && *(domain += 1) != 0 && !allascii(domain))
+ return (-1);
+
+ return (0);
+}
+
+/* smtpd_check_rewrite - choose address qualification context */
+
+char *smtpd_check_rewrite(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_check_rewrite";
+ int status;
+ char **cpp;
+ MAPS *maps;
+ char *name;
+
+ /*
+ * We don't use generic_checks() because it produces results that aren't
+ * applicable such as DEFER or REJECT.
+ */
+ for (cpp = local_rewrite_clients->argv; *cpp != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("%s: trying: %s", myname, *cpp);
+ status = SMTPD_CHECK_DUNNO;
+ if (strchr(name = *cpp, ':') != 0) {
+ name = CHECK_ADDR_MAP;
+ cpp -= 1;
+ }
+ if (strcasecmp(name, PERMIT_INET_INTERFACES) == 0) {
+ status = permit_inet_interfaces(state);
+ } else if (strcasecmp(name, PERMIT_MYNETWORKS) == 0) {
+ status = permit_mynetworks(state);
+ } else if (is_map_command(state, name, CHECK_ADDR_MAP, &cpp)) {
+ if ((maps = (MAPS *) htable_find(map_command_table, *cpp)) == 0)
+ msg_panic("%s: dictionary not found: %s", myname, *cpp);
+ if (maps_find(maps, state->addr, 0) != 0)
+ status = SMTPD_CHECK_OK;
+ else if (maps->error != 0) {
+ /* Warning is already logged. */
+ status = maps->error;
+ }
+ } else if (strcasecmp(name, PERMIT_SASL_AUTH) == 0) {
+#ifdef USE_SASL_AUTH
+ if (smtpd_sasl_is_active(state))
+ status = permit_sasl_auth(state, SMTPD_CHECK_OK,
+ SMTPD_CHECK_DUNNO);
+#endif
+ } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) {
+ status = permit_tls_clientcerts(state, 1);
+ } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) {
+ status = permit_tls_clientcerts(state, 0);
+ } else {
+ msg_warn("parameter %s: invalid request: %s",
+ VAR_LOC_RWR_CLIENTS, name);
+ continue;
+ }
+ if (status < 0) {
+ if (status == DICT_ERR_RETRY) {
+ state->error_mask |= MAIL_ERROR_RESOURCE;
+ log_whatsup(state, "reject",
+ "451 4.3.0 Temporary lookup error");
+ return ("451 4.3.0 Temporary lookup error");
+ } else {
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ log_whatsup(state, "reject",
+ "451 4.3.5 Server configuration error");
+ return ("451 4.3.5 Server configuration error");
+ }
+ }
+ if (status == SMTPD_CHECK_OK) {
+ state->rewrite_context = MAIL_ATTR_RWR_LOCAL;
+ return (0);
+ }
+ }
+ state->rewrite_context = MAIL_ATTR_RWR_REMOTE;
+ return (0);
+}
+
+/* smtpd_check_client - validate client name or address */
+
+char *smtpd_check_client(SMTPD_STATE *state)
+{
+ int status;
+
+ /*
+ * Initialize.
+ */
+ if (state->name == 0 || state->addr == 0)
+ return (0);
+
+#define SMTPD_CHECK_RESET() { \
+ state->recursion = 0; \
+ state->warn_if_reject = 0; \
+ state->defer_if_reject.active = 0; \
+ }
+
+ /*
+ * Reset the defer_if_permit flag.
+ */
+ state->defer_if_permit.active = 0;
+
+ /*
+ * Apply restrictions in the order as specified.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && client_restrctions->argc)
+ status = generic_checks(state, client_restrctions, state->namaddr,
+ SMTPD_NAME_CLIENT, CHECK_CLIENT_ACL);
+ state->defer_if_permit_client = state->defer_if_permit.active;
+
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_helo - validate HELO hostname */
+
+char *smtpd_check_helo(SMTPD_STATE *state, char *helohost)
+{
+ int status;
+ char *saved_helo;
+
+ /*
+ * Initialize.
+ */
+ if (helohost == 0)
+ return (0);
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine and so
+ * that we can syslog the recipient with the reject messages.
+ */
+#define SMTPD_CHECK_PUSH(backup, current, new) { \
+ backup = current; \
+ current = (new ? mystrdup(new) : 0); \
+ }
+
+#define SMTPD_CHECK_POP(current, backup) { \
+ if (current) myfree(current); \
+ current = backup; \
+ }
+
+ SMTPD_CHECK_PUSH(saved_helo, state->helo_name, helohost);
+
+#define SMTPD_CHECK_HELO_RETURN(x) { \
+ SMTPD_CHECK_POP(state->helo_name, saved_helo); \
+ return (x); \
+ }
+
+ /*
+ * Restore the defer_if_permit flag to its value before HELO/EHLO, and do
+ * not set the flag when it was already raised by a previous protocol
+ * stage.
+ */
+ state->defer_if_permit.active = state->defer_if_permit_client;
+
+ /*
+ * Apply restrictions in the order as specified.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && helo_restrctions->argc)
+ status = generic_checks(state, helo_restrctions, state->helo_name,
+ SMTPD_NAME_HELO, CHECK_HELO_ACL);
+ state->defer_if_permit_helo = state->defer_if_permit.active;
+
+ SMTPD_CHECK_HELO_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_mail - validate sender address, driver */
+
+char *smtpd_check_mail(SMTPD_STATE *state, char *sender)
+{
+ int status;
+ char *saved_sender;
+
+ /*
+ * Initialize.
+ */
+ if (sender == 0)
+ return (0);
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine and so
+ * that we can syslog the recipient with the reject messages.
+ */
+ SMTPD_CHECK_PUSH(saved_sender, state->sender, sender);
+
+#define SMTPD_CHECK_MAIL_RETURN(x) { \
+ SMTPD_CHECK_POP(state->sender, saved_sender); \
+ return (x); \
+ }
+
+ /*
+ * Restore the defer_if_permit flag to its value before MAIL FROM, and do
+ * not set the flag when it was already raised by a previous protocol
+ * stage. The client may skip the helo/ehlo.
+ */
+ state->defer_if_permit.active = state->defer_if_permit_client
+ | state->defer_if_permit_helo;
+ state->sender_rcptmap_checked = 0;
+
+ /*
+ * Apply restrictions in the order as specified.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && mail_restrctions->argc)
+ status = generic_checks(state, mail_restrctions, sender,
+ SMTPD_NAME_SENDER, CHECK_SENDER_ACL);
+ state->defer_if_permit_sender = state->defer_if_permit.active;
+
+ /*
+ * If the "reject_unlisted_sender" restriction still needs to be applied,
+ * validate the sender here.
+ */
+ if (var_smtpd_rej_unl_from
+ && status != SMTPD_CHECK_REJECT && state->sender_rcptmap_checked == 0
+ && state->discard == 0 && *sender)
+ status = check_sender_rcpt_maps(state, sender);
+
+ SMTPD_CHECK_MAIL_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_rcpt - validate recipient address, driver */
+
+char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient)
+{
+ int status;
+ char *saved_recipient;
+ char *err;
+ ARGV *restrctions[2];
+ int n;
+
+ /*
+ * Initialize.
+ */
+ if (recipient == 0)
+ return (0);
+
+ /*
+ * XXX 2821: Section 3.6 requires that "postmaster" be accepted even when
+ * specified without a fully qualified domain name.
+ */
+ if (strcasecmp(recipient, "postmaster") == 0)
+ return (0);
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine and so
+ * that we can syslog the recipient with the reject messages.
+ */
+ SMTPD_CHECK_PUSH(saved_recipient, state->recipient, recipient);
+
+#define SMTPD_CHECK_RCPT_RETURN(x) { \
+ SMTPD_CHECK_POP(state->recipient, saved_recipient); \
+ return (x); \
+ }
+
+ /*
+ * The "check_recipient_maps" restriction is relevant only when
+ * responding to RCPT TO or VRFY.
+ */
+ state->recipient_rcptmap_checked = 0;
+
+ /*
+ * Apply delayed restrictions.
+ */
+ if (var_smtpd_delay_reject)
+ if ((err = smtpd_check_client(state)) != 0
+ || (err = smtpd_check_helo(state, state->helo_name)) != 0
+ || (err = smtpd_check_mail(state, state->sender)) != 0)
+ SMTPD_CHECK_RCPT_RETURN(err);
+
+ /*
+ * Restore the defer_if_permit flag to its value before RCPT TO, and do
+ * not set the flag when it was already raised by a previous protocol
+ * stage.
+ */
+ state->defer_if_permit.active = state->defer_if_permit_sender;
+
+ /*
+ * Apply restrictions in the order as specified. We allow relay
+ * restrictions to be empty, for sites that require backwards
+ * compatibility.
+ *
+ * If compatibility_level < 1 and smtpd_relay_restrictions is left at its
+ * default value, find out if the new smtpd_relay_restrictions default
+ * value would block the request, without logging REJECT messages.
+ * Approach: evaluate fake relay restrictions (permit_mynetworks,
+ * permit_sasl_authenticated, permit_auth_destination) and log a warning
+ * if the result is DUNNO instead of OK, i.e. a reject_unauth_destinatin
+ * at the end would have blocked the request.
+ */
+ SMTPD_CHECK_RESET();
+ restrctions[0] = rcpt_restrctions;
+ restrctions[1] = warn_compat_break_relay_restrictions ?
+ fake_relay_restrctions : relay_restrctions;
+ for (n = 0; n < 2; n++) {
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && restrctions[n]->argc)
+ status = generic_checks(state, restrctions[n],
+ recipient, SMTPD_NAME_RECIPIENT, CHECK_RECIP_ACL);
+ if (n == 1 && warn_compat_break_relay_restrictions
+ && status == SMTPD_CHECK_DUNNO) {
+ msg_info("using backwards-compatible default setting \""
+ VAR_RELAY_CHECKS " = (empty)\" to avoid \"Relay "
+ "access denied\" error for recipient \"%s\" from "
+ "client \"%s\"", state->recipient, state->namaddr);
+ }
+ if (status == SMTPD_CHECK_REJECT)
+ break;
+ }
+
+ /*
+ * Force permission into deferral when some earlier temporary error may
+ * have prevented us from rejecting mail, and report the earlier problem.
+ */
+ if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
+ status = smtpd_check_reject(state, state->defer_if_permit.class,
+ state->defer_if_permit.code,
+ STR(state->defer_if_permit.dsn),
+ "%s", STR(state->defer_if_permit.reason));
+
+ /*
+ * If the "reject_unlisted_recipient" restriction still needs to be
+ * applied, validate the recipient here.
+ */
+ if (var_smtpd_rej_unl_rcpt
+ && status != SMTPD_CHECK_REJECT
+ && state->recipient_rcptmap_checked == 0
+ && state->discard == 0)
+ status = check_recipient_rcpt_maps(state, recipient);
+
+ SMTPD_CHECK_RCPT_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_etrn - validate ETRN request */
+
+char *smtpd_check_etrn(SMTPD_STATE *state, char *domain)
+{
+ int status;
+ char *saved_etrn_name;
+ char *err;
+
+ /*
+ * Initialize.
+ */
+ if (domain == 0)
+ return (0);
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine and so
+ * that we can syslog the recipient with the reject messages.
+ */
+ SMTPD_CHECK_PUSH(saved_etrn_name, state->etrn_name, domain);
+
+#define SMTPD_CHECK_ETRN_RETURN(x) { \
+ SMTPD_CHECK_POP(state->etrn_name, saved_etrn_name); \
+ return (x); \
+ }
+
+ /*
+ * Apply delayed restrictions.
+ */
+ if (var_smtpd_delay_reject)
+ if ((err = smtpd_check_client(state)) != 0
+ || (err = smtpd_check_helo(state, state->helo_name)) != 0)
+ SMTPD_CHECK_ETRN_RETURN(err);
+
+ /*
+ * Restore the defer_if_permit flag to its value before ETRN, and do not
+ * set the flag when it was already raised by a previous protocol stage.
+ * The client may skip the helo/ehlo.
+ */
+ state->defer_if_permit.active = state->defer_if_permit_client
+ | state->defer_if_permit_helo;
+
+ /*
+ * Apply restrictions in the order as specified.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && etrn_restrctions->argc)
+ status = generic_checks(state, etrn_restrctions, domain,
+ SMTPD_NAME_ETRN, CHECK_ETRN_ACL);
+
+ /*
+ * Force permission into deferral when some earlier temporary error may
+ * have prevented us from rejecting mail, and report the earlier problem.
+ */
+ if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
+ status = smtpd_check_reject(state, state->defer_if_permit.class,
+ state->defer_if_permit.code,
+ STR(state->defer_if_permit.dsn),
+ "%s", STR(state->defer_if_permit.reason));
+
+ SMTPD_CHECK_ETRN_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* check_recipient_rcpt_maps - generic_checks() recipient table check */
+
+static int check_recipient_rcpt_maps(SMTPD_STATE *state, const char *recipient)
+{
+
+ /*
+ * Duplicate suppression. There's an implicit check_recipient_maps
+ * restriction at the end of all recipient restrictions.
+ */
+ if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT)
+ return (0);
+ if (state->recipient_rcptmap_checked == 1)
+ return (0);
+ if (state->warn_if_reject == 0)
+ /* We really validate the recipient address. */
+ state->recipient_rcptmap_checked = 1;
+ return (check_rcpt_maps(state, state->sender, recipient,
+ SMTPD_NAME_RECIPIENT));
+}
+
+/* check_sender_rcpt_maps - generic_checks() sender table check */
+
+static int check_sender_rcpt_maps(SMTPD_STATE *state, const char *sender)
+{
+
+ /*
+ * Duplicate suppression. There's an implicit check_sender_maps
+ * restriction at the end of all sender restrictions.
+ */
+ if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT)
+ return (0);
+ if (state->sender_rcptmap_checked == 1)
+ return (0);
+ if (state->warn_if_reject == 0)
+ /* We really validate the sender address. */
+ state->sender_rcptmap_checked = 1;
+ return (check_rcpt_maps(state, state->recipient, sender,
+ SMTPD_NAME_SENDER));
+}
+
+/* check_rcpt_maps - generic_checks() interface for recipient table check */
+
+static int check_rcpt_maps(SMTPD_STATE *state, const char *sender,
+ const char *recipient,
+ const char *reply_class)
+{
+ const RESOLVE_REPLY *reply;
+ DSN_SPLIT dp;
+
+ if (msg_verbose)
+ msg_info(">>> CHECKING %s VALIDATION MAPS <<<", reply_class);
+
+ /*
+ * Resolve the address.
+ */
+ reply = smtpd_resolve_addr(sender, recipient);
+ if (reply->flags & RESOLVE_FLAG_FAIL)
+ reject_dict_retry(state, recipient);
+
+ /*
+ * Make complex expressions more readable?
+ */
+#define MATCH(map, rcpt) \
+ check_mail_addr_find(state, recipient, map, rcpt, (char **) 0)
+
+#define NOMATCH(map, rcpt) (MATCH(map, rcpt) == 0)
+
+ /*
+ * XXX We assume the recipient address is OK if it matches a canonical
+ * map or virtual alias map. Eventually, the address resolver should give
+ * us the final resolved recipient address, and the SMTP server should
+ * write the final resolved recipient address to the output record
+ * stream. See also the next comment block on recipients in virtual alias
+ * domains.
+ */
+ if (MATCH(rcpt_canon_maps, CONST_STR(reply->recipient))
+ || (strcmp(reply_class, SMTPD_NAME_SENDER) == 0
+ && MATCH(send_canon_maps, CONST_STR(reply->recipient)))
+ || MATCH(canonical_maps, CONST_STR(reply->recipient))
+ || MATCH(virt_alias_maps, CONST_STR(reply->recipient)))
+ return (0);
+
+ /*
+ * At this point, anything that resolves to the error mailer is known to
+ * be undeliverable.
+ *
+ * XXX Until the address resolver does final address resolution, known and
+ * unknown recipients in virtual alias domains will both resolve to
+ * "error:user unknown".
+ */
+ if (strcmp(STR(reply->transport), MAIL_SERVICE_ERROR) == 0) {
+ dsn_split(&dp, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "5.1.0" : "5.1.1", STR(reply->nexthop));
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ (reply->flags & RESOLVE_CLASS_ALIAS) ?
+ var_virt_alias_code : 550,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ recipient, reply_class,
+ dp.text));
+ }
+ if (strcmp(STR(reply->transport), MAIL_SERVICE_RETRY) == 0) {
+ dsn_split(&dp, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "4.1.0" : "4.1.1", STR(reply->nexthop));
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, 450,
+ smtpd_dsn_fix(DSN_STATUS(dp.dsn),
+ reply_class),
+ "<%s>: %s rejected: %s",
+ recipient, reply_class,
+ dp.text));
+ }
+
+ /*
+ * Search the recipient lookup tables of the respective address class.
+ *
+ * XXX Use the less expensive maps_find() (built-in case folding) instead of
+ * the baroque mail_addr_find(). But then we have to strip the domain and
+ * deal with address extensions ourselves.
+ *
+ * XXX But that would break sites that use the virtual delivery agent for
+ * local delivery, because the virtual delivery agent requires
+ * user@domain style addresses in its user database.
+ */
+#define MATCH_LEFT(l, r, n) \
+ (strncasecmp_utf8((l), (r), (n)) == 0 && (r)[n] == '@')
+
+ switch (reply->flags & RESOLVE_CLASS_MASK) {
+
+ /*
+ * Reject mail to unknown addresses in local domains (domains that
+ * match $mydestination or ${proxy,inet}_interfaces).
+ */
+ case RESOLVE_CLASS_LOCAL:
+ if (*var_local_rcpt_maps
+ /* Generated by bounce, absorbed by qmgr. */
+ && !MATCH_LEFT(var_double_bounce_sender, CONST_STR(reply->recipient),
+ strlen(var_double_bounce_sender))
+ /* Absorbed by qmgr. */
+ && !MATCH_LEFT(MAIL_ADDR_POSTMASTER, CONST_STR(reply->recipient),
+ strlen(MAIL_ADDR_POSTMASTER))
+ /* Generated by bounce. */
+ && !MATCH_LEFT(MAIL_ADDR_MAIL_DAEMON, CONST_STR(reply->recipient),
+ strlen(MAIL_ADDR_MAIL_DAEMON))
+ && NOMATCH(local_rcpt_maps, CONST_STR(reply->recipient)))
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ var_local_rcpt_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "5.1.0" : "5.1.1",
+ "<%s>: %s rejected: User unknown%s",
+ recipient, reply_class,
+ var_show_unk_rcpt_table ?
+ " in local recipient table" : ""));
+ break;
+
+ /*
+ * Reject mail to unknown addresses in virtual mailbox domains.
+ */
+ case RESOLVE_CLASS_VIRTUAL:
+ if (*var_virt_mailbox_maps
+ && NOMATCH(virt_mailbox_maps, CONST_STR(reply->recipient)))
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ var_virt_mailbox_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "5.1.0" : "5.1.1",
+ "<%s>: %s rejected: User unknown%s",
+ recipient, reply_class,
+ var_show_unk_rcpt_table ?
+ " in virtual mailbox table" : ""));
+ break;
+
+ /*
+ * Reject mail to unknown addresses in relay domains.
+ */
+ case RESOLVE_CLASS_RELAY:
+ if (*var_relay_rcpt_maps
+ && NOMATCH(relay_rcpt_maps, CONST_STR(reply->recipient)))
+ return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ var_relay_rcpt_code,
+ strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ?
+ "5.1.0" : "5.1.1",
+ "<%s>: %s rejected: User unknown%s",
+ recipient, reply_class,
+ var_show_unk_rcpt_table ?
+ " in relay recipient table" : ""));
+ if (warn_compat_break_relay_domains)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_DOMAINS "=$mydestination to accept mail "
+ "for address \"%s\"", recipient);
+ break;
+ }
+
+ /*
+ * Accept all other addresses - including addresses that passed the above
+ * tests because of some table lookup problem.
+ */
+ return (0);
+}
+
+/* smtpd_check_size - check optional SIZE parameter value */
+
+char *smtpd_check_size(SMTPD_STATE *state, off_t size)
+{
+ int status;
+
+ /*
+ * Return here in case of serious trouble.
+ */
+ SMTPD_CHECK_RESET();
+ if ((status = setjmp(smtpd_check_buf)) != 0)
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+
+ /*
+ * Check against file size limit.
+ */
+ if (ENFORCING_SIZE_LIMIT(var_message_limit) && size > var_message_limit) {
+ (void) smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ 552, "5.3.4",
+ "Message size exceeds fixed limit");
+ return (STR(error_text));
+ }
+ return (0);
+}
+
+/* smtpd_check_queue - check queue space */
+
+char *smtpd_check_queue(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_check_queue";
+ struct fsspace fsbuf;
+ int status;
+
+ /*
+ * Return here in case of serious trouble.
+ */
+ SMTPD_CHECK_RESET();
+ if ((status = setjmp(smtpd_check_buf)) != 0)
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+
+ /*
+ * Avoid overflow/underflow when comparing message size against available
+ * space.
+ */
+#define BLOCKS(x) ((x) / fsbuf.block_size)
+
+ fsspace(".", &fsbuf);
+ if (msg_verbose)
+ msg_info("%s: blocks %lu avail %lu min_free %lu msg_size_limit %lu",
+ myname,
+ (unsigned long) fsbuf.block_size,
+ (unsigned long) fsbuf.block_free,
+ (unsigned long) var_queue_minfree,
+ (unsigned long) var_message_limit);
+ if (BLOCKS(var_queue_minfree) >= fsbuf.block_free
+ || BLOCKS(var_message_limit) >= fsbuf.block_free / smtpd_space_multf) {
+ (void) smtpd_check_reject(state, MAIL_ERROR_RESOURCE,
+ 452, "4.3.1",
+ "Insufficient system storage");
+ msg_warn("not enough free space in mail queue: %lu bytes < "
+ "%g*message size limit",
+ (unsigned long) fsbuf.block_free * fsbuf.block_size,
+ smtpd_space_multf);
+ return (STR(error_text));
+ }
+ return (0);
+}
+
+/* smtpd_check_data - check DATA command */
+
+char *smtpd_check_data(SMTPD_STATE *state)
+{
+ int status;
+ char *NOCLOBBER saved_recipient;
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine. We
+ * provide no recipient information in the case of multiple recipients,
+ * This restriction applies to all recipients alike, and logging only one
+ * of them would be misleading.
+ */
+ if (state->rcpt_count > 1) {
+ saved_recipient = state->recipient;
+ state->recipient = 0;
+ }
+
+ /*
+ * Reset the defer_if_permit flag. This is necessary when some recipients
+ * were accepted but the last one was rejected.
+ */
+ state->defer_if_permit.active = 0;
+
+ /*
+ * Apply restrictions in the order as specified.
+ *
+ * XXX We cannot specify a default target for a bare access map.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && data_restrctions->argc)
+ status = generic_checks(state, data_restrctions,
+ SMTPD_CMD_DATA, SMTPD_NAME_DATA, NO_DEF_ACL);
+
+ /*
+ * Force permission into deferral when some earlier temporary error may
+ * have prevented us from rejecting mail, and report the earlier problem.
+ */
+ if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
+ status = smtpd_check_reject(state, state->defer_if_permit.class,
+ state->defer_if_permit.code,
+ STR(state->defer_if_permit.dsn),
+ "%s", STR(state->defer_if_permit.reason));
+
+ if (state->rcpt_count > 1)
+ state->recipient = saved_recipient;
+
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+/* smtpd_check_eod - check end-of-data command */
+
+char *smtpd_check_eod(SMTPD_STATE *state)
+{
+ int status;
+ char *NOCLOBBER saved_recipient;
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine. We
+ * provide no recipient information in the case of multiple recipients,
+ * This restriction applies to all recipients alike, and logging only one
+ * of them would be misleading.
+ */
+ if (state->rcpt_count > 1) {
+ saved_recipient = state->recipient;
+ state->recipient = 0;
+ }
+
+ /*
+ * Reset the defer_if_permit flag. This is necessary when some recipients
+ * were accepted but the last one was rejected.
+ */
+ state->defer_if_permit.active = 0;
+
+ /*
+ * Apply restrictions in the order as specified.
+ *
+ * XXX We cannot specify a default target for a bare access map.
+ */
+ SMTPD_CHECK_RESET();
+ status = setjmp(smtpd_check_buf);
+ if (status == 0 && eod_restrictions->argc)
+ status = generic_checks(state, eod_restrictions,
+ SMTPD_CMD_EOD, SMTPD_NAME_EOD, NO_DEF_ACL);
+
+ /*
+ * Force permission into deferral when some earlier temporary error may
+ * have prevented us from rejecting mail, and report the earlier problem.
+ */
+ if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
+ status = smtpd_check_reject(state, state->defer_if_permit.class,
+ state->defer_if_permit.code,
+ STR(state->defer_if_permit.dsn),
+ "%s", STR(state->defer_if_permit.reason));
+
+ if (state->rcpt_count > 1)
+ state->recipient = saved_recipient;
+
+ return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program to try out all these restrictions without having to go live.
+ * This is not entirely stand-alone, as it requires access to the Postfix
+ * rewrite/resolve service. This is just for testing code, not for debugging
+ * configuration files.
+ */
+#include <stdlib.h>
+
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+
+#include <mail_conf.h>
+#include <rewrite_clnt.h>
+#include <dns.h>
+
+#include <smtpd_chat.h>
+
+int smtpd_input_transp_mask;
+
+ /*
+ * Dummies. These are never set.
+ */
+char *var_client_checks = "";
+char *var_helo_checks = "";
+char *var_mail_checks = "";
+char *var_relay_checks = "";
+char *var_rcpt_checks = "";
+char *var_etrn_checks = "";
+char *var_data_checks = "";
+char *var_eod_checks = "";
+char *var_relay_domains = "";
+char *var_smtpd_uproxy_proto = "";
+int var_smtpd_uproxy_tmout = 0;
+
+#ifdef USE_TLS
+char *var_relay_ccerts = "";
+
+#endif
+char *var_mynetworks = "";
+char *var_notify_classes = "";
+char *var_smtpd_policy_def_action = "";
+char *var_smtpd_policy_context = "";
+
+ /*
+ * String-valued configuration parameters.
+ */
+char *var_maps_rbl_domains;
+char *var_myorigin;
+char *var_mydest;
+char *var_inet_interfaces;
+char *var_proxy_interfaces;
+char *var_rcpt_delim;
+char *var_rest_classes;
+char *var_alias_maps;
+char *var_send_canon_maps;
+char *var_rcpt_canon_maps;
+char *var_canonical_maps;
+char *var_virt_alias_maps;
+char *var_virt_alias_doms;
+char *var_virt_mailbox_maps;
+char *var_virt_mailbox_doms;
+char *var_local_rcpt_maps;
+char *var_perm_mx_networks;
+char *var_par_dom_match;
+char *var_smtpd_null_key;
+char *var_smtpd_snd_auth_maps;
+char *var_double_bounce_sender;
+char *var_rbl_reply_maps;
+char *var_smtpd_exp_filter;
+char *var_def_rbl_reply;
+char *var_relay_rcpt_maps;
+char *var_verify_sender;
+char *var_smtpd_sasl_opts;
+char *var_local_rwr_clients;
+char *var_smtpd_relay_ccerts;
+char *var_unv_from_why;
+char *var_unv_rcpt_why;
+char *var_stress;
+char *var_unk_name_tf_act;
+char *var_unk_addr_tf_act;
+char *var_unv_rcpt_tf_act;
+char *var_unv_from_tf_act;
+char *var_smtpd_acl_perm_log;
+char *var_info_log_addr_form;
+
+typedef struct {
+ char *name;
+ char *defval;
+ char **target;
+} STRING_TABLE;
+
+#undef DEF_VIRT_ALIAS_MAPS
+#define DEF_VIRT_ALIAS_MAPS ""
+
+#undef DEF_LOCAL_RCPT_MAPS
+#define DEF_LOCAL_RCPT_MAPS ""
+
+static const STRING_TABLE string_table[] = {
+ VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains,
+ VAR_MYORIGIN, DEF_MYORIGIN, &var_myorigin,
+ VAR_MYDEST, DEF_MYDEST, &var_mydest,
+ VAR_INET_INTERFACES, DEF_INET_INTERFACES, &var_inet_interfaces,
+ VAR_PROXY_INTERFACES, DEF_PROXY_INTERFACES, &var_proxy_interfaces,
+ VAR_RCPT_DELIM, DEF_RCPT_DELIM, &var_rcpt_delim,
+ VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes,
+ VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps,
+ VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps,
+ VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps,
+ VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps,
+ VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps,
+ VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps,
+ VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms,
+ VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps,
+ VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks,
+ VAR_PAR_DOM_MATCH, DEF_PAR_DOM_MATCH, &var_par_dom_match,
+ VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps,
+ VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key,
+ VAR_DOUBLE_BOUNCE, DEF_DOUBLE_BOUNCE, &var_double_bounce_sender,
+ VAR_RBL_REPLY_MAPS, DEF_RBL_REPLY_MAPS, &var_rbl_reply_maps,
+ VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter,
+ VAR_DEF_RBL_REPLY, DEF_DEF_RBL_REPLY, &var_def_rbl_reply,
+ VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps,
+ VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender,
+ VAR_MAIL_NAME, DEF_MAIL_NAME, &var_mail_name,
+ VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts,
+ VAR_LOC_RWR_CLIENTS, DEF_LOC_RWR_CLIENTS, &var_local_rwr_clients,
+ VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts,
+ VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why,
+ VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why,
+ VAR_STRESS, DEF_STRESS, &var_stress,
+ /* XXX Can't use ``$name'' type default values below. */
+ VAR_UNK_NAME_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unk_name_tf_act,
+ VAR_UNK_ADDR_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unk_addr_tf_act,
+ VAR_UNV_RCPT_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unv_rcpt_tf_act,
+ VAR_UNV_FROM_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unv_from_tf_act,
+ /* XXX Can't use ``$name'' type default values above. */
+ VAR_SMTPD_ACL_PERM_LOG, DEF_SMTPD_ACL_PERM_LOG, &var_smtpd_acl_perm_log,
+ VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter,
+ VAR_INFO_LOG_ADDR_FORM, DEF_INFO_LOG_ADDR_FORM, &var_info_log_addr_form,
+ 0,
+};
+
+/* string_init - initialize string parameters */
+
+static void string_init(void)
+{
+ const STRING_TABLE *sp;
+
+ for (sp = string_table; sp->name; sp++)
+ sp->target[0] = mystrdup(sp->defval);
+}
+
+/* string_update - update string parameter */
+
+static int string_update(char **argv)
+{
+ const STRING_TABLE *sp;
+
+ for (sp = string_table; sp->name; sp++) {
+ if (strcasecmp(argv[0], sp->name) == 0) {
+ myfree(sp->target[0]);
+ sp->target[0] = mystrdup(argv[1]);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+ /*
+ * Integer parameters.
+ */
+long var_queue_minfree; /* XXX use off_t */
+typedef struct {
+ char *name;
+ int defval;
+ int *target;
+} INT_TABLE;
+
+int var_unk_client_code;
+int var_bad_name_code;
+int var_unk_name_code;
+int var_unk_addr_code;
+int var_relay_code;
+int var_maps_rbl_code;
+int var_map_reject_code;
+int var_map_defer_code;
+int var_reject_code;
+int var_defer_code;
+int var_non_fqdn_code;
+int var_smtpd_delay_reject;
+int var_allow_untrust_route;
+int var_mul_rcpt_code;
+int var_unv_from_rcode;
+int var_unv_from_dcode;
+int var_unv_rcpt_rcode;
+int var_unv_rcpt_dcode;
+int var_local_rcpt_code;
+int var_relay_rcpt_code;
+int var_virt_mailbox_code;
+int var_virt_alias_code;
+int var_show_unk_rcpt_table;
+int var_verify_poll_count;
+int var_verify_poll_delay;
+int var_smtpd_policy_tmout;
+int var_smtpd_policy_idle;
+int var_smtpd_policy_ttl;
+int var_smtpd_policy_req_limit;
+int var_smtpd_policy_try_limit;
+int var_smtpd_policy_try_delay;
+int var_smtpd_rej_unl_from;
+int var_smtpd_rej_unl_rcpt;
+int var_plaintext_code;
+bool var_smtpd_peername_lookup;
+bool var_smtpd_client_port_log;
+char *var_smtpd_dns_re_filter;
+bool var_smtpd_tls_ask_ccert;
+
+#define int_table test_int_table
+
+static const INT_TABLE int_table[] = {
+ "msg_verbose", 0, &msg_verbose,
+ VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code,
+ VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code,
+ VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code,
+ VAR_UNK_ADDR_CODE, DEF_UNK_ADDR_CODE, &var_unk_addr_code,
+ VAR_RELAY_CODE, DEF_RELAY_CODE, &var_relay_code,
+ VAR_MAPS_RBL_CODE, DEF_MAPS_RBL_CODE, &var_maps_rbl_code,
+ VAR_MAP_REJECT_CODE, DEF_MAP_REJECT_CODE, &var_map_reject_code,
+ VAR_MAP_DEFER_CODE, DEF_MAP_DEFER_CODE, &var_map_defer_code,
+ VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code,
+ VAR_DEFER_CODE, DEF_DEFER_CODE, &var_defer_code,
+ VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code,
+ VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject,
+ VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route,
+ VAR_MUL_RCPT_CODE, DEF_MUL_RCPT_CODE, &var_mul_rcpt_code,
+ VAR_UNV_FROM_RCODE, DEF_UNV_FROM_RCODE, &var_unv_from_rcode,
+ VAR_UNV_FROM_DCODE, DEF_UNV_FROM_DCODE, &var_unv_from_dcode,
+ VAR_UNV_RCPT_RCODE, DEF_UNV_RCPT_RCODE, &var_unv_rcpt_rcode,
+ VAR_UNV_RCPT_DCODE, DEF_UNV_RCPT_DCODE, &var_unv_rcpt_dcode,
+ VAR_LOCAL_RCPT_CODE, DEF_LOCAL_RCPT_CODE, &var_local_rcpt_code,
+ VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code,
+ VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code,
+ VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code,
+ VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table,
+ VAR_VERIFY_POLL_COUNT, 3, &var_verify_poll_count,
+ VAR_SMTPD_REJ_UNL_FROM, DEF_SMTPD_REJ_UNL_FROM, &var_smtpd_rej_unl_from,
+ VAR_SMTPD_REJ_UNL_RCPT, DEF_SMTPD_REJ_UNL_RCPT, &var_smtpd_rej_unl_rcpt,
+ VAR_PLAINTEXT_CODE, DEF_PLAINTEXT_CODE, &var_plaintext_code,
+ VAR_SMTPD_PEERNAME_LOOKUP, DEF_SMTPD_PEERNAME_LOOKUP, &var_smtpd_peername_lookup,
+ VAR_SMTPD_CLIENT_PORT_LOG, DEF_SMTPD_CLIENT_PORT_LOG, &var_smtpd_client_port_log,
+ VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
+ 0,
+};
+
+/* int_init - initialize int parameters */
+
+static void int_init(void)
+{
+ const INT_TABLE *sp;
+
+ for (sp = int_table; sp->name; sp++)
+ sp->target[0] = sp->defval;
+}
+
+/* int_update - update int parameter */
+
+static int int_update(char **argv)
+{
+ const INT_TABLE *ip;
+
+ for (ip = int_table; ip->name; ip++) {
+ if (strcasecmp(argv[0], ip->name) == 0) {
+ if (!ISDIGIT(*argv[1]))
+ msg_fatal("bad number: %s %s", ip->name, argv[1]);
+ ip->target[0] = atoi(argv[1]);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+ /*
+ * Restrictions.
+ */
+typedef struct {
+ char *name;
+ ARGV **target;
+} REST_TABLE;
+
+static const REST_TABLE rest_table[] = {
+ "client_restrictions", &client_restrctions,
+ "helo_restrictions", &helo_restrctions,
+ "sender_restrictions", &mail_restrctions,
+ "relay_restrictions", &relay_restrctions,
+ "recipient_restrictions", &rcpt_restrctions,
+ "etrn_restrictions", &etrn_restrctions,
+ 0,
+};
+
+/* rest_update - update restriction */
+
+static int rest_update(char **argv)
+{
+ const REST_TABLE *rp;
+
+ for (rp = rest_table; rp->name; rp++) {
+ if (strcasecmp(rp->name, argv[0]) == 0) {
+ argv_free(rp->target[0]);
+ rp->target[0] = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, argv[1]);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/* rest_class - (re)define a restriction class */
+
+static void rest_class(char *class)
+{
+ char *cp = class;
+ char *name;
+ HTABLE_INFO *entry;
+
+ if (smtpd_rest_classes == 0)
+ smtpd_rest_classes = htable_create(1);
+
+ if ((name = mystrtok(&cp, CHARS_COMMA_SP)) == 0)
+ msg_panic("rest_class: null class name");
+ if ((entry = htable_locate(smtpd_rest_classes, name)) != 0)
+ argv_free((ARGV *) entry->value);
+ else
+ entry = htable_enter(smtpd_rest_classes, name, (void *) 0);
+ entry->value = (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, cp);
+}
+
+/* resolve_clnt_init - initialize reply */
+
+void resolve_clnt_init(RESOLVE_REPLY *reply)
+{
+ reply->flags = 0;
+ reply->transport = vstring_alloc(100);
+ reply->nexthop = vstring_alloc(100);
+ reply->recipient = vstring_alloc(100);
+}
+
+void resolve_clnt_free(RESOLVE_REPLY *reply)
+{
+ vstring_free(reply->transport);
+ vstring_free(reply->nexthop);
+ vstring_free(reply->recipient);
+}
+
+bool var_smtpd_sasl_enable = 0;
+
+#ifdef USE_SASL_AUTH
+
+/* smtpd_sasl_activate - stub */
+
+void smtpd_sasl_activate(SMTPD_STATE *state, const char *opts_name,
+ const char *opts_var)
+{
+ msg_panic("smtpd_sasl_activate was called");
+}
+
+/* smtpd_sasl_deactivate - stub */
+
+void smtpd_sasl_deactivate(SMTPD_STATE *state)
+{
+ msg_panic("smtpd_sasl_deactivate was called");
+}
+
+/* permit_sasl_auth - stub */
+
+int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot)
+{
+ return (ifnot);
+}
+
+/* smtpd_sasl_state_init - the real deal */
+
+void smtpd_sasl_state_init(SMTPD_STATE *state)
+{
+ state->sasl_username = 0;
+ state->sasl_method = 0;
+ state->sasl_sender = 0;
+}
+
+#endif
+
+/* verify_clnt_query - stub */
+
+int verify_clnt_query(const char *addr, int *addr_status, VSTRING *why)
+{
+ *addr_status = DEL_RCPT_STAT_OK;
+ return (VRFY_STAT_OK);
+}
+
+/* rewrite_clnt_internal - stub */
+
+VSTRING *rewrite_clnt_internal(const char *context, const char *addr,
+ VSTRING *result)
+{
+ if (addr == STR(result))
+ msg_panic("rewrite_clnt_internal: result clobbers input");
+ if (*addr && strchr(addr, '@') == 0)
+ msg_fatal("%s: address rewriting is disabled", addr);
+ vstring_strcpy(result, addr);
+ return (result);
+}
+
+/* resolve_clnt_query - stub */
+
+void resolve_clnt(const char *class, const char *unused_sender, const char *addr,
+ RESOLVE_REPLY *reply)
+{
+ const char *domain;
+ int rc;
+
+ if (addr == CONST_STR(reply->recipient))
+ msg_panic("resolve_clnt_query: result clobbers input");
+ if (strchr(addr, '%'))
+ msg_fatal("%s: address rewriting is disabled", addr);
+ if ((domain = strrchr(addr, '@')) == 0)
+ msg_fatal("%s: unqualified address", addr);
+ domain += 1;
+ if ((rc = resolve_local(domain)) > 0) {
+ reply->flags = RESOLVE_CLASS_LOCAL;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_LOCAL);
+ vstring_strcpy(reply->nexthop, domain);
+ } else if (rc < 0) {
+ reply->flags = RESOLVE_FLAG_FAIL;
+ } else if (string_list_match(virt_alias_doms, domain)) {
+ reply->flags = RESOLVE_CLASS_ALIAS;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_ERROR);
+ vstring_strcpy(reply->nexthop, "user unknown");
+ } else if (virt_alias_doms->error) {
+ reply->flags = RESOLVE_FLAG_FAIL;
+ } else if (string_list_match(virt_mailbox_doms, domain)) {
+ reply->flags = RESOLVE_CLASS_VIRTUAL;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_VIRTUAL);
+ vstring_strcpy(reply->nexthop, domain);
+ } else if (virt_mailbox_doms->error) {
+ reply->flags = RESOLVE_FLAG_FAIL;
+ } else if (domain_list_match(relay_domains, domain)) {
+ reply->flags = RESOLVE_CLASS_RELAY;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_RELAY);
+ vstring_strcpy(reply->nexthop, domain);
+ } else if (relay_domains->error) {
+ reply->flags = RESOLVE_FLAG_FAIL;
+ } else {
+ reply->flags = RESOLVE_CLASS_DEFAULT;
+ vstring_strcpy(reply->transport, MAIL_SERVICE_SMTP);
+ vstring_strcpy(reply->nexthop, domain);
+ }
+ vstring_strcpy(reply->recipient, addr);
+}
+
+/* smtpd_chat_reset - stub */
+
+void smtpd_chat_reset(SMTPD_STATE *unused_state)
+{
+}
+
+/* usage - scream and terminate */
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s", myname);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ SMTPD_STATE state;
+ ARGV *args;
+ char *bp;
+ char *resp;
+ char *addr;
+
+ /*
+ * Initialization. Use dummies for client information.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc != 1)
+ usage(argv[0]);
+ string_init();
+ int_init();
+ smtpd_check_init();
+ smtpd_expand_init();
+ (void) inet_proto_init(argv[0], INET_PROTO_NAME_IPV4);
+ smtpd_state_init(&state, VSTREAM_IN, "smtpd");
+ state.queue_id = "<queue id>";
+
+ /*
+ * Main loop: update config parameters or test the client, helo, sender
+ * and recipient restrictions.
+ */
+ while (vstring_fgets_nonl(buf, VSTREAM_IN) != 0) {
+
+ /*
+ * Tokenize the command. Note, the comma is not a separator, so that
+ * restriction lists can be entered as comma-separated lists.
+ */
+ bp = STR(buf);
+ if (!isatty(0)) {
+ vstream_printf(">>> %s\n", bp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bp == '#')
+ continue;
+
+ if (*bp == '!') {
+ vstream_printf("exit %d\n", system(bp + 1));
+ continue;
+ }
+ args = argv_splitq(bp, CHARS_SPACE, CHARS_BRACE);
+
+ /*
+ * Recognize the command.
+ */
+ resp = "bad command";
+ switch (args->argc) {
+
+ /*
+ * Emtpy line.
+ */
+ case 0:
+ argv_free(args);
+ continue;
+
+ /*
+ * Special case: rewrite context.
+ */
+ case 1:
+ if (strcasecmp(args->argv[0], "rewrite") == 0) {
+ resp = smtpd_check_rewrite(&state);
+ break;
+ }
+
+ /*
+ * Other parameter-less commands.
+ */
+ if (strcasecmp(args->argv[0], "flush_dnsxl_cache") == 0) {
+ if (smtpd_rbl_cache) {
+ ctable_free(smtpd_rbl_cache);
+ ctable_free(smtpd_rbl_byte_cache);
+ }
+ smtpd_rbl_cache = ctable_create(100, rbl_pagein,
+ rbl_pageout, (void *) 0);
+ smtpd_rbl_byte_cache = ctable_create(1000, rbl_byte_pagein,
+ rbl_byte_pageout, (void *) 0);
+ resp = 0;
+ break;
+ }
+
+ /*
+ * Special case: client identity.
+ */
+ case 4:
+ case 3:
+ if (strcasecmp(args->argv[0], "client") == 0) {
+ state.where = SMTPD_AFTER_CONNECT;
+ UPDATE_STRING(state.name, args->argv[1]);
+ UPDATE_STRING(state.reverse_name, args->argv[1]);
+ UPDATE_STRING(state.addr, args->argv[2]);
+ if (args->argc == 4)
+ state.name_status =
+ state.reverse_name_status =
+ atoi(args->argv[3]);
+ else if (strcmp(state.name, "unknown") == 0)
+ state.name_status =
+ state.reverse_name_status =
+ SMTPD_PEER_CODE_TEMP;
+ else
+ state.name_status =
+ state.reverse_name_status =
+ SMTPD_PEER_CODE_OK;
+ if (state.namaddr)
+ myfree(state.namaddr);
+ state.namaddr = concatenate(state.name, "[", state.addr,
+ "]", (char *) 0);
+ resp = smtpd_check_client(&state);
+ }
+ break;
+
+ /*
+ * Try config settings.
+ */
+#define UPDATE_MAPS(ptr, var, val, lock) \
+ { if (ptr) maps_free(ptr); ptr = maps_create(var, val, lock); }
+
+#define UPDATE_LIST(ptr, var, val) \
+ { if (ptr) string_list_free(ptr); \
+ ptr = string_list_init(var, MATCH_FLAG_NONE, val); }
+
+ case 2:
+ if (strcasecmp(args->argv[0], VAR_MYDEST) == 0) {
+ UPDATE_STRING(var_mydest, args->argv[1]);
+ resolve_local_init();
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_MAPS) == 0) {
+ UPDATE_STRING(var_virt_alias_maps, args->argv[1]);
+ UPDATE_MAPS(virt_alias_maps, VAR_VIRT_ALIAS_MAPS,
+ var_virt_alias_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_DOMS) == 0) {
+ UPDATE_STRING(var_virt_alias_doms, args->argv[1]);
+ UPDATE_LIST(virt_alias_doms, VAR_VIRT_ALIAS_DOMS,
+ var_virt_alias_doms);
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_MAPS) == 0) {
+ UPDATE_STRING(var_virt_mailbox_maps, args->argv[1]);
+ UPDATE_MAPS(virt_mailbox_maps, VAR_VIRT_MAILBOX_MAPS,
+ var_virt_mailbox_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_DOMS) == 0) {
+ UPDATE_STRING(var_virt_mailbox_doms, args->argv[1]);
+ UPDATE_LIST(virt_mailbox_doms, VAR_VIRT_MAILBOX_DOMS,
+ var_virt_mailbox_doms);
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_LOCAL_RCPT_MAPS) == 0) {
+ UPDATE_STRING(var_local_rcpt_maps, args->argv[1]);
+ UPDATE_MAPS(local_rcpt_maps, VAR_LOCAL_RCPT_MAPS,
+ var_local_rcpt_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_RELAY_RCPT_MAPS) == 0) {
+ UPDATE_STRING(var_relay_rcpt_maps, args->argv[1]);
+ UPDATE_MAPS(relay_rcpt_maps, VAR_RELAY_RCPT_MAPS,
+ var_relay_rcpt_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_CANONICAL_MAPS) == 0) {
+ UPDATE_STRING(var_canonical_maps, args->argv[1]);
+ UPDATE_MAPS(canonical_maps, VAR_CANONICAL_MAPS,
+ var_canonical_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_SEND_CANON_MAPS) == 0) {
+ UPDATE_STRING(var_send_canon_maps, args->argv[1]);
+ UPDATE_MAPS(send_canon_maps, VAR_SEND_CANON_MAPS,
+ var_send_canon_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_RCPT_CANON_MAPS) == 0) {
+ UPDATE_STRING(var_rcpt_canon_maps, args->argv[1]);
+ UPDATE_MAPS(rcpt_canon_maps, VAR_RCPT_CANON_MAPS,
+ var_rcpt_canon_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_RBL_REPLY_MAPS) == 0) {
+ UPDATE_STRING(var_rbl_reply_maps, args->argv[1]);
+ UPDATE_MAPS(rbl_reply_maps, VAR_RBL_REPLY_MAPS,
+ var_rbl_reply_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_MYNETWORKS) == 0) {
+ /* NOT: UPDATE_STRING */
+ namadr_list_free(mynetworks_curr);
+ mynetworks_curr =
+ namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_MYNETWORKS),
+ args->argv[1]);
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_RELAY_DOMAINS) == 0) {
+ /* NOT: UPDATE_STRING */
+ domain_list_free(relay_domains);
+ relay_domains =
+ domain_list_init(VAR_RELAY_DOMAINS,
+ match_parent_style(VAR_RELAY_DOMAINS),
+ args->argv[1]);
+ smtpd_resolve_init(100);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_PERM_MX_NETWORKS) == 0) {
+ UPDATE_STRING(var_perm_mx_networks, args->argv[1]);
+ domain_list_free(perm_mx_networks);
+ perm_mx_networks =
+ namadr_list_init(VAR_PERM_MX_NETWORKS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_PERM_MX_NETWORKS),
+ args->argv[1]);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_SMTPD_DNS_RE_FILTER) == 0) {
+ /* NOT: UPDATE_STRING */
+ dns_rr_filter_compile(VAR_SMTPD_DNS_RE_FILTER, args->argv[1]);
+ resp = 0;
+ break;
+ }
+#ifdef USE_TLS
+ if (strcasecmp(args->argv[0], VAR_RELAY_CCERTS) == 0) {
+ UPDATE_STRING(var_smtpd_relay_ccerts, args->argv[1]);
+ UPDATE_MAPS(relay_ccerts, VAR_RELAY_CCERTS,
+ var_smtpd_relay_ccerts, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX);
+ resp = 0;
+ }
+#endif
+ if (strcasecmp(args->argv[0], "restriction_class") == 0) {
+ rest_class(args->argv[1]);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_LOC_RWR_CLIENTS) == 0) {
+ UPDATE_STRING(var_local_rwr_clients, args->argv[1]);
+ argv_free(local_rewrite_clients);
+ local_rewrite_clients = smtpd_check_parse(SMTPD_CHECK_PARSE_MAPS,
+ var_local_rwr_clients);
+ }
+ if (int_update(args->argv)
+ || string_update(args->argv)
+ || rest_update(args->argv)) {
+ resp = 0;
+ break;
+ }
+
+ /*
+ * Try restrictions.
+ */
+#define TRIM_ADDR(src, res) { \
+ if (*(res = src) == '<') { \
+ res += strlen(res) - 1; \
+ if (*res == '>') \
+ *res = 0; \
+ res = src + 1; \
+ } \
+ }
+
+ if (strcasecmp(args->argv[0], "helo") == 0) {
+ state.where = "HELO";
+ resp = smtpd_check_helo(&state, args->argv[1]);
+ UPDATE_STRING(state.helo_name, args->argv[1]);
+ } else if (strcasecmp(args->argv[0], "mail") == 0) {
+ state.where = "MAIL";
+ TRIM_ADDR(args->argv[1], addr);
+ UPDATE_STRING(state.sender, addr);
+ resp = smtpd_check_mail(&state, addr);
+ } else if (strcasecmp(args->argv[0], "rcpt") == 0) {
+ state.where = "RCPT";
+ TRIM_ADDR(args->argv[1], addr);
+ resp = smtpd_check_rcpt(&state, addr);
+#ifdef USE_TLS
+ } else if (strcasecmp(args->argv[0], "fingerprint") == 0) {
+ if (state.tls_context == 0) {
+ state.tls_context =
+ (TLS_SESS_STATE *) mymalloc(sizeof(*state.tls_context));
+ memset((void *) state.tls_context, 0,
+ sizeof(*state.tls_context));
+ state.tls_context->peer_cert_fprint =
+ state.tls_context->peer_pkey_fprint = 0;
+ }
+ state.tls_context->peer_status |= TLS_CERT_FLAG_PRESENT;
+ UPDATE_STRING(state.tls_context->peer_cert_fprint,
+ args->argv[1]);
+ state.tls_context->peer_pkey_fprint =
+ state.tls_context->peer_cert_fprint;
+ resp = "OK";
+ break;
+#endif
+ }
+ break;
+
+ /*
+ * Show commands.
+ */
+ default:
+ if (strcasecmp(args->argv[0], "check_rewrite") == 0) {
+ smtpd_check_rewrite(&state);
+ resp = state.rewrite_context;
+ break;
+ }
+ resp = "Commands...\n\
+ client <name> <address> [<code>]\n\
+ helo <hostname>\n\
+ sender <address>\n\
+ recipient <address>\n\
+ check_rewrite\n\
+ msg_verbose <level>\n\
+ client_restrictions <restrictions>\n\
+ helo_restrictions <restrictions>\n\
+ sender_restrictions <restrictions>\n\
+ recipient_restrictions <restrictions>\n\
+ restriction_class name,<restrictions>\n\
+ flush_dnsxl_cache\n\
+ \n\
+ Note: no address rewriting \n";
+ break;
+ }
+ vstream_printf("%s\n", resp ? resp : "OK");
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(args);
+ }
+ vstring_free(buf);
+ smtpd_state_reset(&state);
+#define FREE_STRING(s) { if (s) myfree(s); }
+ FREE_STRING(state.helo_name);
+ FREE_STRING(state.sender);
+#ifdef USE_TLS
+ if (state.tls_context) {
+ FREE_STRING(state.tls_context->peer_cert_fprint);
+ myfree((void *) state.tls_context);
+ }
+#endif
+ exit(0);
+}
+
+#endif
diff --git a/src/smtpd/smtpd_check.h b/src/smtpd/smtpd_check.h
new file mode 100644
index 0000000..bf0fe00
--- /dev/null
+++ b/src/smtpd/smtpd_check.h
@@ -0,0 +1,44 @@
+/*++
+/* NAME
+/* smtpd_check 3h
+/* SUMMARY
+/* SMTP client request filtering
+/* SYNOPSIS
+/* #include "smtpd.h"
+/* #include "smtpd_check.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void smtpd_check_init(void);
+extern int smtpd_check_addr(const char *, const char *, int);
+extern char *smtpd_check_rewrite(SMTPD_STATE *);
+extern char *smtpd_check_client(SMTPD_STATE *);
+extern char *smtpd_check_helo(SMTPD_STATE *, char *);
+extern char *smtpd_check_mail(SMTPD_STATE *, char *);
+extern char *smtpd_check_size(SMTPD_STATE *, off_t);
+extern char *smtpd_check_queue(SMTPD_STATE *);
+extern char *smtpd_check_rcpt(SMTPD_STATE *, char *);
+extern char *smtpd_check_etrn(SMTPD_STATE *, char *);
+extern char *smtpd_check_data(SMTPD_STATE *);
+extern char *smtpd_check_eod(SMTPD_STATE *);
+extern char *smtpd_check_policy(SMTPD_STATE *, char *);
+extern void log_whatsup(SMTPD_STATE *, const char *, const char *);
+
+/* 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
+/*--*/
diff --git a/src/smtpd/smtpd_check.in b/src/smtpd/smtpd_check.in
new file mode 100644
index 0000000..980c7f8
--- /dev/null
+++ b/src/smtpd/smtpd_check.in
@@ -0,0 +1,182 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.189.0/28
+relay_domains porcupine.org
+maps_rbl_domains dnsbltest.porcupine.org
+#
+# Test the client restrictions.
+#
+client_restrictions permit_mynetworks,reject_unknown_client,hash:./smtpd_check_access
+client unknown 131.155.210.17
+client unknown 168.100.189.13
+client random.bad.domain 123.123.123.123
+client friend.bad.domain 123.123.123.123
+client bad.domain 123.123.123.123
+client wzv.win.tue.nl 131.155.210.17
+client aa.win.tue.nl 131.155.210.18
+client_restrictions permit_mynetworks
+#
+# Test the helo restrictions
+#
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,hash:./smtpd_check_access
+client unknown 131.155.210.17
+helo foo.
+client foo 123.123.123.123
+helo foo.
+helo foo
+helo spike.porcupine.org
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access
+helo random.bad.domain
+helo friend.bad.domain
+helo_restrictions reject_invalid_hostname,reject_unknown_hostname
+helo 123.123.123.123
+helo [123.123.123.123]
+helo [::]
+helo [ipv6:::]
+helo [ipv6::::]
+helo_restrictions permit_naked_ip_address,reject_invalid_hostname,reject_unknown_hostname
+helo 123.123.123.123
+#
+# Test the sender restrictions
+#
+sender_restrictions permit_mynetworks,reject_unknown_client
+client unknown 131.155.210.17
+mail foo@ibm.com
+client unknown 168.100.189.13
+mail foo@ibm.com
+client foo 123.123.123.123
+mail foo@ibm.com
+sender_restrictions reject_unknown_address
+mail foo@ibm.com
+mail foo@bad.domain
+sender_restrictions hash:./smtpd_check_access
+mail bad-sender@any.domain
+mail bad-sender@good.domain
+mail reject@this.address
+mail Reject@this.address
+mail foo@bad.domain
+mail foo@Bad.domain
+mail foo@random.bad.domain
+mail foo@friend.bad.domain
+#
+# Test the recipient restrictions
+#
+recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains
+client unknown 131.155.210.17
+rcpt foo@ibm.com
+client unknown 168.100.189.13
+rcpt foo@ibm.com
+client foo 123.123.123.123
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+recipient_restrictions check_relay_domains
+client foo.porcupine.org 168.100.189.13
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+client foo 123.123.123.123
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+recipient_restrictions hash:./smtpd_check_access
+mail bad-sender@any.domain
+mail bad-sender@good.domain
+mail reject@this.address
+mail foo@bad.domain
+mail foo@random.bad.domain
+mail foo@friend.bad.domain
+#
+# RBL
+#
+client_restrictions reject_maps_rbl
+client spike.porcupine.org 168.100.189.2
+client foo 127.0.0.2
+#
+# Hybrids
+#
+recipient_restrictions check_relay_domains
+client foo 131.155.210.17
+rcpt foo@ibm.com
+recipient_restrictions check_client_access,hash:./smtpd_check_access,check_relay_domains
+client foo 131.155.210.17
+rcpt foo@porcupine.org
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access
+recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_relay_domains
+helo bad.domain
+rcpt foo@porcupine.org
+helo 131.155.210.17
+rcpt foo@porcupine.org
+recipient_restrictions check_sender_access,hash:./smtpd_check_access,check_relay_domains
+mail foo@bad.domain
+rcpt foo@porcupine.org
+mail foo@friend.bad.domain
+rcpt foo@porcupine.org
+#
+# MX backup
+#
+#mydestination spike.porcupine.org,localhost.porcupine.org
+#inet_interfaces 168.100.189.2,127.0.0.1
+#recipient_restrictions permit_mx_backup,reject
+#rcpt wietse@wzv.win.tue.nl
+#rcpt wietse@trouble.org
+#rcpt wietse@porcupine.org
+#
+# Deferred restrictions
+#
+client_restrictions permit
+helo_restrictions permit
+sender_restrictions permit
+recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_sender_access,hash:./smtpd_check_access
+helo bad.domain
+mail foo@good.domain
+rcpt foo@porcupine.org
+helo good.domain
+mail foo@bad.domain
+rcpt foo@porcupine.org
+#
+# FQDN restrictions
+#
+helo_restrictions reject_non_fqdn_hostname
+sender_restrictions reject_non_fqdn_sender
+recipient_restrictions reject_non_fqdn_recipient
+helo foo.bar.
+helo foo.bar
+helo foo
+mail foo@foo.bar.
+mail foo@foo.bar
+mail foo@foo
+mail foo
+rcpt foo@foo.bar.
+rcpt foo@foo.bar
+rcpt foo@foo
+rcpt foo
+#
+# Numerical HELO checks
+#
+helo_restrictions permit_naked_ip_address,reject_non_fqdn_hostname
+helo [1.2.3.4]
+helo [321.255.255.255]
+helo [0.255.255.255]
+helo [1.2.3.321]
+helo [1.2.3]
+helo [1.2.3.4.5]
+helo [1..2.3.4]
+helo [.1.2.3.4]
+helo [1.2.3.4.5.]
+helo 1.2.3.4
+helo 321.255.255.255
+helo 0.255.255.255
+helo 1.2.3.321
+helo 1.2.3
+helo 1.2.3.4.5
+helo 1..2.3.4
+helo .1.2.3.4
+helo 1.2.3.4.5.
+#
+# The defer restriction
+#
+defer_code 444
+helo_restrictions defer
+helo foobar
diff --git a/src/smtpd/smtpd_check.in2 b/src/smtpd/smtpd_check.in2
new file mode 100644
index 0000000..064cb41
--- /dev/null
+++ b/src/smtpd/smtpd_check.in2
@@ -0,0 +1,116 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.189.0/28
+relay_domains porcupine.org
+maps_rbl_domains dnsbltest.porcupine.org
+#
+# Test the client restrictions.
+#
+client_restrictions permit_mynetworks,reject_unknown_client,check_client_access,hash:./smtpd_check_access
+client unknown 131.155.210.17
+client unknown 168.100.189.13
+client random.bad.domain 123.123.123.123
+client friend.bad.domain 123.123.123.123
+client bad.domain 123.123.123.123
+client wzv.win.tue.nl 131.155.210.17
+client aa.win.tue.nl 131.155.210.18
+client_restrictions permit_mynetworks
+#
+# Test the helo restrictions
+#
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,check_helo_access,hash:./smtpd_check_access
+client unknown 131.155.210.17
+helo foo.
+client foo 123.123.123.123
+helo foo.
+helo foo
+helo spike.porcupine.org
+helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,check_helo_access,hash:./smtpd_check_access
+helo random.bad.domain
+helo friend.bad.domain
+#
+# Test the sender restrictions
+#
+sender_restrictions permit_mynetworks,reject_unknown_client
+client unknown 131.155.210.17
+mail foo@ibm.com
+client unknown 168.100.189.13
+mail foo@ibm.com
+client foo 123.123.123.123
+mail foo@ibm.com
+sender_restrictions reject_unknown_address
+mail foo@ibm.com
+mail foo@bad.domain
+sender_restrictions check_sender_access,hash:./smtpd_check_access
+mail bad-sender@any.domain
+mail bad-sender@good.domain
+mail reject@this.address
+mail Reject@this.address
+mail foo@bad.domain
+mail foo@Bad.domain
+mail foo@random.bad.domain
+mail foo@friend.bad.domain
+#
+# Test the recipient restrictions
+#
+recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains
+client unknown 131.155.210.17
+rcpt foo@ibm.com
+client unknown 168.100.189.13
+rcpt foo@ibm.com
+client foo 123.123.123.123
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+recipient_restrictions check_relay_domains
+client foo.porcupine.org 168.100.189.13
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+client foo 123.123.123.123
+rcpt foo@ibm.com
+rcpt foo@porcupine.org
+recipient_restrictions check_recipient_access,hash:./smtpd_check_access
+mail bad-sender@any.domain
+mail bad-sender@good.domain
+mail reject@this.address
+mail foo@bad.domain
+mail foo@random.bad.domain
+mail foo@friend.bad.domain
+#
+# RBL
+#
+client_restrictions reject_maps_rbl
+client spike.porcupine.org 168.100.189.2
+client foo 127.0.0.2
+#
+# unknown sender/recipient domain
+#
+unknown_address_reject_code 554
+recipient_restrictions reject_unknown_recipient_domain,reject_unknown_sender_domain
+mail wietse@porcupine.org
+rcpt wietse@porcupine.org
+rcpt wietse@no.recipient.domain
+mail wietse@no.sender.domain
+rcpt wietse@porcupine.org
+#
+# {permit_auth,reject_unauth}_destination
+#
+relay_domains foo.com,bar.com
+mail user@some.where
+recipient_restrictions permit_auth_destination,reject
+rcpt user@foo.org
+rcpt user@foo.com
+recipient_restrictions reject_unauth_destination,permit
+rcpt user@foo.org
+rcpt user@foo.com
+#
+# unknown client tests
+#
+unknown_client_reject_code 550
+client_restrictions reject_unknown_client
+client spike.porcupine.org 160.100.189.2 2
+client unknown 1.1.1.1 4
+client unknown 1.1.1.1 5
diff --git a/src/smtpd/smtpd_check.in3 b/src/smtpd/smtpd_check.in3
new file mode 100644
index 0000000..52279f1
--- /dev/null
+++ b/src/smtpd/smtpd_check.in3
@@ -0,0 +1,27 @@
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.189.0/28
+relay_domains porcupine.org
+local_recipient_maps unix:passwd.byname
+client unknown 131.155.210.17
+canonical_maps tcp:localhost:200
+#
+recipient_restrictions permit
+rcpt no.such.user@[127.0.0.1]
+#
+virtual_alias_maps tcp:localhost:100
+#
+recipient_restrictions permit_mx_backup
+rcpt wietse@nowhere1.com
+#
+recipient_restrictions check_relay_domains
+rcpt wietse@nowhere2.com
+#
+recipient_restrictions reject_unknown_recipient_domain
+rcpt wietse@nowhere3.com
+#
+recipient_restrictions permit_auth_destination
+rcpt wietse@nowhere4.com
+#
+recipient_restrictions reject_unauth_destination
+rcpt wietse@nowhere5.com
diff --git a/src/smtpd/smtpd_check.in4 b/src/smtpd/smtpd_check.in4
new file mode 100644
index 0000000..d401de9
--- /dev/null
+++ b/src/smtpd/smtpd_check.in4
@@ -0,0 +1,19 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+#
+# Test the new access map features
+#
+sender_restrictions hash:./smtpd_check_access
+mail rejecttext@bad.domain
+mail filter@filter.domain
+mail filtertext@filter.domain
+mail filtertexttext@filter.domain
+mail hold@hold.domain
+mail holdtext@hold.domain
+mail discard@hold.domain
+mail discardtext@hold.domain
+mail dunnotext@dunno.domain
diff --git a/src/smtpd/smtpd_check.ref b/src/smtpd/smtpd_check.ref
new file mode 100644
index 0000000..1a8090f
--- /dev/null
+++ b/src/smtpd/smtpd_check.ref
@@ -0,0 +1,398 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.189.0/28
+OK
+>>> relay_domains porcupine.org
+OK
+>>> maps_rbl_domains dnsbltest.porcupine.org
+OK
+>>> #
+>>> # Test the client restrictions.
+>>> #
+>>> client_restrictions permit_mynetworks,reject_unknown_client,hash:./smtpd_check_access
+OK
+>>> client unknown 131.155.210.17
+./smtpd_check: <queue id>: reject: CONNECT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.189.13
+OK
+>>> client random.bad.domain 123.123.123.123
+./smtpd_check: <queue id>: reject: CONNECT from random.bad.domain[123.123.123.123]: 554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP
+554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain
+>>> client friend.bad.domain 123.123.123.123
+OK
+>>> client bad.domain 123.123.123.123
+./smtpd_check: <queue id>: reject: CONNECT from bad.domain[123.123.123.123]: 554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP
+554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain
+>>> client wzv.win.tue.nl 131.155.210.17
+OK
+>>> client aa.win.tue.nl 131.155.210.18
+./smtpd_check: <queue id>: reject: CONNECT from aa.win.tue.nl[131.155.210.18]: 554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210; proto=SMTP
+554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210
+>>> client_restrictions permit_mynetworks
+OK
+>>> #
+>>> # Test the helo restrictions
+>>> #
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,hash:./smtpd_check_access
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> helo foo.
+./smtpd_check: <queue id>: reject: HELO from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP helo=<foo.>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client foo 123.123.123.123
+OK
+>>> helo foo.
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo.>: Helo command rejected: Host not found; proto=SMTP helo=<foo.>
+450 4.7.1 <foo.>: Helo command rejected: Host not found
+>>> helo foo
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo>: Helo command rejected: Host not found; proto=SMTP helo=<foo>
+450 4.7.1 <foo>: Helo command rejected: Host not found
+>>> helo spike.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <spike.porcupine.org>: Helo command rejected: ns or mx server spike.porcupine.org; proto=SMTP helo=<spike.porcupine.org>
+554 5.7.1 <spike.porcupine.org>: Helo command rejected: ns or mx server spike.porcupine.org
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access
+OK
+>>> helo random.bad.domain
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain; proto=SMTP helo=<random.bad.domain>
+554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain
+>>> helo friend.bad.domain
+OK
+>>> helo_restrictions reject_invalid_hostname,reject_unknown_hostname
+OK
+>>> helo 123.123.123.123
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <123.123.123.123>: Helo command rejected: Host not found; proto=SMTP helo=<123.123.123.123>
+450 4.7.1 <123.123.123.123>: Helo command rejected: Host not found
+>>> helo [123.123.123.123]
+OK
+>>> helo [::]
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 501 5.5.2 <[::]>: Helo command rejected: invalid ip address; proto=SMTP helo=<[::]>
+501 5.5.2 <[::]>: Helo command rejected: invalid ip address
+>>> helo [ipv6:::]
+OK
+>>> helo [ipv6::::]
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 501 5.5.2 <[ipv6::::]>: Helo command rejected: invalid ip address; proto=SMTP helo=<[ipv6::::]>
+501 5.5.2 <[ipv6::::]>: Helo command rejected: invalid ip address
+>>> helo_restrictions permit_naked_ip_address,reject_invalid_hostname,reject_unknown_hostname
+OK
+>>> helo 123.123.123.123
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+OK
+>>> #
+>>> # Test the sender restrictions
+>>> #
+>>> sender_restrictions permit_mynetworks,reject_unknown_client
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> mail foo@ibm.com
+./smtpd_check: <queue id>: reject: MAIL from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.189.13
+OK
+>>> mail foo@ibm.com
+OK
+>>> client foo 123.123.123.123
+OK
+>>> mail foo@ibm.com
+OK
+>>> sender_restrictions reject_unknown_address
+OK
+>>> mail foo@ibm.com
+OK
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found; from=<foo@bad.domain> proto=SMTP helo=<123.123.123.123>
+450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found
+>>> sender_restrictions hash:./smtpd_check_access
+OK
+>>> mail bad-sender@any.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@
+>>> mail bad-sender@good.domain
+OK
+>>> mail reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail Reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address; from=<Reject@this.address> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@Bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain; from=<foo@Bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@random.bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> #
+>>> # Test the recipient restrictions
+>>> #
+>>> recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.189.13
+OK
+>>> rcpt foo@ibm.com
+OK
+>>> client foo 123.123.123.123
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: warning: support for restriction "check_relay_domains" will be removed from Postfix; use "reject_unauth_destination" instead
+./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions check_relay_domains
+OK
+>>> client foo.porcupine.org 168.100.189.13
+OK
+>>> rcpt foo@ibm.com
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> client foo 123.123.123.123
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions hash:./smtpd_check_access
+OK
+>>> mail bad-sender@any.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@
+>>> mail bad-sender@good.domain
+OK
+>>> mail reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@random.bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> #
+>>> # RBL
+>>> #
+>>> client_restrictions reject_maps_rbl
+OK
+>>> client spike.porcupine.org 168.100.189.2
+./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead
+OK
+>>> client foo 127.0.0.2
+./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<foo@friend.bad.domain> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
+>>> #
+>>> # Hybrids
+>>> #
+>>> recipient_restrictions check_relay_domains
+OK
+>>> client foo 131.155.210.17
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> recipient_restrictions check_client_access,hash:./smtpd_check_access,check_relay_domains
+OK
+>>> client foo 131.155.210.17
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access
+OK
+>>> recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_relay_domains
+OK
+>>> helo bad.domain
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@friend.bad.domain> proto=SMTP helo=<bad.domain>
+554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain
+>>> rcpt foo@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@friend.bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<bad.domain>
+554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain
+>>> helo 131.155.210.17
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions check_sender_access,hash:./smtpd_check_access,check_relay_domains
+OK
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[131.155.210.17]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<131.155.210.17>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> rcpt foo@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<131.155.210.17>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> #
+>>> # MX backup
+>>> #
+>>> #mydestination spike.porcupine.org,localhost.porcupine.org
+>>> #inet_interfaces 168.100.189.2,127.0.0.1
+>>> #recipient_restrictions permit_mx_backup,reject
+>>> #rcpt wietse@wzv.win.tue.nl
+>>> #rcpt wietse@trouble.org
+>>> #rcpt wietse@porcupine.org
+>>> #
+>>> # Deferred restrictions
+>>> #
+>>> client_restrictions permit
+OK
+>>> helo_restrictions permit
+OK
+>>> sender_restrictions permit
+OK
+>>> recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_sender_access,hash:./smtpd_check_access
+OK
+>>> helo bad.domain
+OK
+>>> mail foo@good.domain
+OK
+>>> rcpt foo@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@good.domain> to=<foo@porcupine.org> proto=SMTP helo=<bad.domain>
+554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain
+>>> helo good.domain
+OK
+>>> mail foo@bad.domain
+OK
+>>> rcpt foo@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<good.domain>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> #
+>>> # FQDN restrictions
+>>> #
+>>> helo_restrictions reject_non_fqdn_hostname
+OK
+>>> sender_restrictions reject_non_fqdn_sender
+OK
+>>> recipient_restrictions reject_non_fqdn_recipient
+OK
+>>> helo foo.bar.
+OK
+>>> helo foo.bar
+OK
+>>> helo foo
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 504 5.5.2 <foo>: Helo command rejected: need fully-qualified hostname; from=<foo@bad.domain> proto=SMTP helo=<foo>
+504 5.5.2 <foo>: Helo command rejected: need fully-qualified hostname
+>>> mail foo@foo.bar.
+OK
+>>> mail foo@foo.bar
+OK
+>>> mail foo@foo
+./smtpd_check: <queue id>: reject: MAIL from foo[131.155.210.17]: 504 5.5.2 <foo@foo>: Sender address rejected: need fully-qualified address; from=<foo@foo> proto=SMTP helo=<foo>
+504 5.5.2 <foo@foo>: Sender address rejected: need fully-qualified address
+>>> mail foo
+./smtpd_check: <queue id>: reject: MAIL from foo[131.155.210.17]: 504 5.5.2 <foo>: Sender address rejected: need fully-qualified address; from=<foo> proto=SMTP helo=<foo>
+504 5.5.2 <foo>: Sender address rejected: need fully-qualified address
+>>> rcpt foo@foo.bar.
+OK
+>>> rcpt foo@foo.bar
+OK
+>>> rcpt foo@foo
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 504 5.5.2 <foo@foo>: Recipient address rejected: need fully-qualified address; from=<foo> to=<foo@foo> proto=SMTP helo=<foo>
+504 5.5.2 <foo@foo>: Recipient address rejected: need fully-qualified address
+>>> rcpt foo
+./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 504 5.5.2 <foo>: Recipient address rejected: need fully-qualified address; from=<foo> to=<foo> proto=SMTP helo=<foo>
+504 5.5.2 <foo>: Recipient address rejected: need fully-qualified address
+>>> #
+>>> # Numerical HELO checks
+>>> #
+>>> helo_restrictions permit_naked_ip_address,reject_non_fqdn_hostname
+OK
+>>> helo [1.2.3.4]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+OK
+>>> helo [321.255.255.255]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[321.255.255.255]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[321.255.255.255]>
+501 5.5.2 <[321.255.255.255]>: Helo command rejected: invalid ip address
+>>> helo [0.255.255.255]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[0.255.255.255]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[0.255.255.255]>
+501 5.5.2 <[0.255.255.255]>: Helo command rejected: invalid ip address
+>>> helo [1.2.3.321]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.321]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3.321]>
+501 5.5.2 <[1.2.3.321]>: Helo command rejected: invalid ip address
+>>> helo [1.2.3]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3]>
+501 5.5.2 <[1.2.3]>: Helo command rejected: invalid ip address
+>>> helo [1.2.3.4.5]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.4.5]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3.4.5]>
+501 5.5.2 <[1.2.3.4.5]>: Helo command rejected: invalid ip address
+>>> helo [1..2.3.4]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1..2.3.4]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1..2.3.4]>
+501 5.5.2 <[1..2.3.4]>: Helo command rejected: invalid ip address
+>>> helo [.1.2.3.4]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[.1.2.3.4]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[.1.2.3.4]>
+501 5.5.2 <[.1.2.3.4]>: Helo command rejected: invalid ip address
+>>> helo [1.2.3.4.5.]
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.4.5.]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3.4.5.]>
+501 5.5.2 <[1.2.3.4.5.]>: Helo command rejected: invalid ip address
+>>> helo 1.2.3.4
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+OK
+>>> helo 321.255.255.255
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <321.255.255.255>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<321.255.255.255>
+501 5.5.2 <321.255.255.255>: Helo command rejected: invalid ip address
+>>> helo 0.255.255.255
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <0.255.255.255>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<0.255.255.255>
+501 5.5.2 <0.255.255.255>: Helo command rejected: invalid ip address
+>>> helo 1.2.3.321
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.321>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3.321>
+501 5.5.2 <1.2.3.321>: Helo command rejected: invalid ip address
+>>> helo 1.2.3
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3>
+501 5.5.2 <1.2.3>: Helo command rejected: invalid ip address
+>>> helo 1.2.3.4.5
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.4.5>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3.4.5>
+501 5.5.2 <1.2.3.4.5>: Helo command rejected: invalid ip address
+>>> helo 1..2.3.4
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1..2.3.4>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1..2.3.4>
+501 5.5.2 <1..2.3.4>: Helo command rejected: invalid ip address
+>>> helo .1.2.3.4
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <.1.2.3.4>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<.1.2.3.4>
+501 5.5.2 <.1.2.3.4>: Helo command rejected: invalid ip address
+>>> helo 1.2.3.4.5.
+./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.4.5.>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3.4.5.>
+501 5.5.2 <1.2.3.4.5.>: Helo command rejected: invalid ip address
+>>> #
+>>> # The defer restriction
+>>> #
+>>> defer_code 444
+OK
+>>> helo_restrictions defer
+OK
+>>> helo foobar
+./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 444 4.3.2 <foobar>: Helo command rejected: Try again later; from=<foo> proto=SMTP helo=<foobar>
+444 4.3.2 <foobar>: Helo command rejected: Try again later
diff --git a/src/smtpd/smtpd_check.ref2 b/src/smtpd/smtpd_check.ref2
new file mode 100644
index 0000000..9322457
--- /dev/null
+++ b/src/smtpd/smtpd_check.ref2
@@ -0,0 +1,236 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.189.0/28
+OK
+>>> relay_domains porcupine.org
+OK
+>>> maps_rbl_domains dnsbltest.porcupine.org
+OK
+>>> #
+>>> # Test the client restrictions.
+>>> #
+>>> client_restrictions permit_mynetworks,reject_unknown_client,check_client_access,hash:./smtpd_check_access
+OK
+>>> client unknown 131.155.210.17
+./smtpd_check: <queue id>: reject: CONNECT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.189.13
+OK
+>>> client random.bad.domain 123.123.123.123
+./smtpd_check: <queue id>: reject: CONNECT from random.bad.domain[123.123.123.123]: 554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP
+554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain
+>>> client friend.bad.domain 123.123.123.123
+OK
+>>> client bad.domain 123.123.123.123
+./smtpd_check: <queue id>: reject: CONNECT from bad.domain[123.123.123.123]: 554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP
+554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain
+>>> client wzv.win.tue.nl 131.155.210.17
+OK
+>>> client aa.win.tue.nl 131.155.210.18
+./smtpd_check: <queue id>: reject: CONNECT from aa.win.tue.nl[131.155.210.18]: 554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210; proto=SMTP
+554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210
+>>> client_restrictions permit_mynetworks
+OK
+>>> #
+>>> # Test the helo restrictions
+>>> #
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,check_helo_access,hash:./smtpd_check_access
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> helo foo.
+./smtpd_check: <queue id>: reject: HELO from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP helo=<foo.>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client foo 123.123.123.123
+OK
+>>> helo foo.
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo.>: Helo command rejected: Host not found; proto=SMTP helo=<foo.>
+450 4.7.1 <foo.>: Helo command rejected: Host not found
+>>> helo foo
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo>: Helo command rejected: Host not found; proto=SMTP helo=<foo>
+450 4.7.1 <foo>: Helo command rejected: Host not found
+>>> helo spike.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <spike.porcupine.org>: Helo command rejected: name server spike.porcupine.org; proto=SMTP helo=<spike.porcupine.org>
+554 5.7.1 <spike.porcupine.org>: Helo command rejected: name server spike.porcupine.org
+>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,check_helo_access,hash:./smtpd_check_access
+OK
+>>> helo random.bad.domain
+./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain; proto=SMTP helo=<random.bad.domain>
+554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain
+>>> helo friend.bad.domain
+OK
+>>> #
+>>> # Test the sender restrictions
+>>> #
+>>> sender_restrictions permit_mynetworks,reject_unknown_client
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> mail foo@ibm.com
+./smtpd_check: <queue id>: reject: MAIL from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.189.13
+OK
+>>> mail foo@ibm.com
+OK
+>>> client foo 123.123.123.123
+OK
+>>> mail foo@ibm.com
+OK
+>>> sender_restrictions reject_unknown_address
+OK
+>>> mail foo@ibm.com
+OK
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found; from=<foo@bad.domain> proto=SMTP helo=<friend.bad.domain>
+450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found
+>>> sender_restrictions check_sender_access,hash:./smtpd_check_access
+OK
+>>> mail bad-sender@any.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@
+>>> mail bad-sender@good.domain
+OK
+>>> mail reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail Reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address; from=<Reject@this.address> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@Bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain; from=<foo@Bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@random.bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> #
+>>> # Test the recipient restrictions
+>>> #
+>>> recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains
+OK
+>>> client unknown 131.155.210.17
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain>
+450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]
+>>> client unknown 168.100.189.13
+OK
+>>> rcpt foo@ibm.com
+OK
+>>> client foo 123.123.123.123
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: warning: support for restriction "check_relay_domains" will be removed from Postfix; use "reject_unauth_destination" instead
+./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions check_relay_domains
+OK
+>>> client foo.porcupine.org 168.100.189.13
+OK
+>>> rcpt foo@ibm.com
+OK
+>>> rcpt foo@porcupine.org
+OK
+>>> client foo 123.123.123.123
+OK
+>>> rcpt foo@ibm.com
+./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied
+>>> rcpt foo@porcupine.org
+OK
+>>> recipient_restrictions check_recipient_access,hash:./smtpd_check_access
+OK
+>>> mail bad-sender@any.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@
+>>> mail bad-sender@good.domain
+OK
+>>> mail reject@this.address
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address
+>>> mail foo@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@random.bad.domain
+./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain
+>>> mail foo@friend.bad.domain
+OK
+>>> #
+>>> # RBL
+>>> #
+>>> client_restrictions reject_maps_rbl
+OK
+>>> client spike.porcupine.org 168.100.189.2
+./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead
+OK
+>>> client foo 127.0.0.2
+./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<foo@friend.bad.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
+>>> #
+>>> # unknown sender/recipient domain
+>>> #
+>>> unknown_address_reject_code 554
+OK
+>>> recipient_restrictions reject_unknown_recipient_domain,reject_unknown_sender_domain
+OK
+>>> mail wietse@porcupine.org
+OK
+>>> rcpt wietse@porcupine.org
+OK
+>>> rcpt wietse@no.recipient.domain
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.1.2 <wietse@no.recipient.domain>: Recipient address rejected: Domain not found; from=<wietse@porcupine.org> to=<wietse@no.recipient.domain> proto=SMTP helo=<friend.bad.domain>
+554 5.1.2 <wietse@no.recipient.domain>: Recipient address rejected: Domain not found
+>>> mail wietse@no.sender.domain
+OK
+>>> rcpt wietse@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.1.8 <wietse@no.sender.domain>: Sender address rejected: Domain not found; from=<wietse@no.sender.domain> to=<wietse@porcupine.org> proto=SMTP helo=<friend.bad.domain>
+554 5.1.8 <wietse@no.sender.domain>: Sender address rejected: Domain not found
+>>> #
+>>> # {permit_auth,reject_unauth}_destination
+>>> #
+>>> relay_domains foo.com,bar.com
+OK
+>>> mail user@some.where
+OK
+>>> recipient_restrictions permit_auth_destination,reject
+OK
+>>> rcpt user@foo.org
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 <user@foo.org>: Recipient address rejected: Access denied; from=<user@some.where> to=<user@foo.org> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <user@foo.org>: Recipient address rejected: Access denied
+>>> rcpt user@foo.com
+OK
+>>> recipient_restrictions reject_unauth_destination,permit
+OK
+>>> rcpt user@foo.org
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 <user@foo.org>: Relay access denied; from=<user@some.where> to=<user@foo.org> proto=SMTP helo=<friend.bad.domain>
+554 5.7.1 <user@foo.org>: Relay access denied
+>>> rcpt user@foo.com
+OK
+>>> #
+>>> # unknown client tests
+>>> #
+>>> unknown_client_reject_code 550
+OK
+>>> client_restrictions reject_unknown_client
+OK
+>>> client spike.porcupine.org 160.100.189.2 2
+OK
+>>> client unknown 1.1.1.1 4
+./smtpd_check: <queue id>: reject: CONNECT from unknown[1.1.1.1]: 450 4.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]; from=<user@some.where> proto=SMTP helo=<friend.bad.domain>
+450 4.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]
+>>> client unknown 1.1.1.1 5
+./smtpd_check: <queue id>: reject: CONNECT from unknown[1.1.1.1]: 550 5.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]; from=<user@some.where> proto=SMTP helo=<friend.bad.domain>
+550 5.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]
diff --git a/src/smtpd/smtpd_check.ref4 b/src/smtpd/smtpd_check.ref4
new file mode 100644
index 0000000..8e9a6df
--- /dev/null
+++ b/src/smtpd/smtpd_check.ref4
@@ -0,0 +1,38 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> #
+>>> # Test the new access map features
+>>> #
+>>> sender_restrictions hash:./smtpd_check_access
+OK
+>>> mail rejecttext@bad.domain
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 554 5.7.1 <rejecttext@bad.domain>: Sender address rejected: text; from=<rejecttext@bad.domain> proto=SMTP
+554 5.7.1 <rejecttext@bad.domain>: Sender address rejected: text
+>>> mail filter@filter.domain
+./smtpd_check: warning: access table hash:./smtpd_check_access entry "filter@filter.domain" has FILTER entry without value
+OK
+>>> mail filtertext@filter.domain
+./smtpd_check: warning: access table hash:./smtpd_check_access entry "filtertext@filter.domain" requires transport:destination
+OK
+>>> mail filtertexttext@filter.domain
+./smtpd_check: <queue id>: filter: MAIL from localhost[127.0.0.1]: <filtertexttext@filter.domain>: Sender address triggers FILTER text:text; from=<filtertexttext@filter.domain> proto=SMTP
+OK
+>>> mail hold@hold.domain
+./smtpd_check: <queue id>: hold: MAIL from localhost[127.0.0.1]: <hold@hold.domain>: Sender address triggers HOLD action; from=<hold@hold.domain> proto=SMTP
+OK
+>>> mail holdtext@hold.domain
+./smtpd_check: <queue id>: hold: MAIL from localhost[127.0.0.1]: <holdtext@hold.domain>: Sender address text; from=<holdtext@hold.domain> proto=SMTP
+OK
+>>> mail discard@hold.domain
+./smtpd_check: <queue id>: discard: MAIL from localhost[127.0.0.1]: <discard@hold.domain>: Sender address triggers DISCARD action; from=<discard@hold.domain> proto=SMTP
+OK
+>>> mail discardtext@hold.domain
+./smtpd_check: <queue id>: discard: MAIL from localhost[127.0.0.1]: <discardtext@hold.domain>: Sender address text; from=<discardtext@hold.domain> proto=SMTP
+OK
+>>> mail dunnotext@dunno.domain
+OK
diff --git a/src/smtpd/smtpd_check_access b/src/smtpd/smtpd_check_access
new file mode 100644
index 0000000..788276a
--- /dev/null
+++ b/src/smtpd/smtpd_check_access
@@ -0,0 +1,91 @@
+bad.domain 554 match bad.domain
+friend.bad.domain OK
+bad-sender@ 554 match bad-sender@
+bad-sender@good.domain OK
+good-sender@ OK
+131.155.210 554 match 131.155.210
+131.155.210.17 OK
+131.155.210.19 REJECT
+reject@this.address 554 match reject@this.address
+open_user@some.site open
+strict_user@some.site strict
+auth_client 123456
+
+dunno.com dunno
+foo.dunno.com reject
+
+44.33.22 dunno
+44.33.22.11 REJECT
+44.33 REJECT
+
+reject@dunno.domain REJECT
+ok@dunno.domain OK
+dunno.domain DUNNO
+
+reject@reject.domain REJECT
+ok@reject.domain OK
+reject.domain REJECT
+
+reject@ok.domain REJECT
+ok@ok.domain OK
+ok.domain OK
+<> 550 Go away postmaster
+
+54.187.136.235 reject bizsat.net, gypsysoul.org spam
+
+blackholes.mail-abuse.org $rbl_code client=$client
+ client_address=$client_address
+ client_name=$client_name helo_name=$helo_name
+ sender=$sender sender_name=$sender_name sender_domain=$sender_domain
+ recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain
+ rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what
+ rbl_class=$rbl_class
+
+rhsbl.porcupine.org $rbl_code client=$client
+ client_address=$client_address
+ client_name=$client_name helo_name=$helo_name
+ sender=$sender sender_name=$sender_name sender_domain=$sender_domain
+ recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain
+ rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what
+ rbl_class=$rbl_class
+
+dnswl.porcupine.org $rbl_code client=$client
+ client_address=$client_address
+ client_name=$client_name helo_name=$helo_name
+ sender=$sender sender_name=$sender_name sender_domain=$sender_domain
+ recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain
+ rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what
+ rbl_class=$rbl_class
+
+rejecttext@bad.domain reject text
+filter@filter.domain filter
+filtertext@filter.domain filter text
+filtertexttext@filter.domain filter text:text
+hold@hold.domain hold
+holdtext@hold.domain hold text
+discard@hold.domain discard
+discardtext@hold.domain discard text
+dunnotext@dunno.domain dunno text
+64.94.110.11 reject Verisign wild-card
+topica.com reject
+10.10.10.10 reject mail server 10.10.10.10
+spike.porcupine.org reject ns or mx server spike.porcupine.org
+241 reject class E subnet
+4.1.1_dsn reject 4.1.1 reject
+4.1.2_dsn reject 4.1.2 reject
+4.1.3_dsn reject 4.1.3 reject
+4.1.4_dsn reject 4.1.4 reject
+4.1.5_dsn reject 4.1.5 reject
+4.1.6_dsn reject 4.1.6 reject
+4.1.7_dsn reject 4.1.7 reject
+4.1.8_dsn reject 4.1.8 reject
+4.4.0_dsn reject 4.4.0 reject
+user@4.1.1_dsn reject 4.1.1 reject
+user@4.1.2_dsn reject 4.1.2 reject
+user@4.1.3_dsn reject 4.1.3 reject
+user@4.1.4_dsn reject 4.1.4 reject
+user@4.1.5_dsn reject 4.1.5 reject
+user@4.1.6_dsn reject 4.1.6 reject
+user@4.1.7_dsn reject 4.1.7 reject
+user@4.1.8_dsn reject 4.1.8 reject
+user@4.4.0_dsn reject 4.4.0 reject
diff --git a/src/smtpd/smtpd_check_backup.in b/src/smtpd/smtpd_check_backup.in
new file mode 100644
index 0000000..c4f44bb
--- /dev/null
+++ b/src/smtpd/smtpd_check_backup.in
@@ -0,0 +1,20 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.189.0/28
+#
+# MX backup
+#
+mydestination wzv.porcupine.org,localhost.porcupine.org
+inet_interfaces 168.100.189.7,127.0.0.1
+recipient_restrictions permit_mx_backup,reject
+rcpt wietse@wzv.porcupine.org
+rcpt wietse@backup.porcupine.org
+rcpt wietse@porcupine.org
+permit_mx_backup_networks 168.100.189.5
+rcpt wietse@backup.porcupine.org
+permit_mx_backup_networks 168.100.189.4
+rcpt wietse@backup.porcupine.org
diff --git a/src/smtpd/smtpd_check_backup.ref b/src/smtpd/smtpd_check_backup.ref
new file mode 100644
index 0000000..9f20b90
--- /dev/null
+++ b/src/smtpd/smtpd_check_backup.ref
@@ -0,0 +1,34 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.189.0/28
+OK
+>>> #
+>>> # MX backup
+>>> #
+>>> mydestination wzv.porcupine.org,localhost.porcupine.org
+OK
+>>> inet_interfaces 168.100.189.7,127.0.0.1
+OK
+>>> recipient_restrictions permit_mx_backup,reject
+OK
+>>> rcpt wietse@wzv.porcupine.org
+OK
+>>> rcpt wietse@backup.porcupine.org
+OK
+>>> rcpt wietse@porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 554 5.7.1 <wietse@porcupine.org>: Recipient address rejected: Access denied; to=<wietse@porcupine.org> proto=SMTP
+554 5.7.1 <wietse@porcupine.org>: Recipient address rejected: Access denied
+>>> permit_mx_backup_networks 168.100.189.5
+OK
+>>> rcpt wietse@backup.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 554 5.7.1 <wietse@backup.porcupine.org>: Recipient address rejected: Access denied; to=<wietse@backup.porcupine.org> proto=SMTP
+554 5.7.1 <wietse@backup.porcupine.org>: Recipient address rejected: Access denied
+>>> permit_mx_backup_networks 168.100.189.4
+OK
+>>> rcpt wietse@backup.porcupine.org
+OK
diff --git a/src/smtpd/smtpd_check_dsn.in b/src/smtpd/smtpd_check_dsn.in
new file mode 100644
index 0000000..459bd5d
--- /dev/null
+++ b/src/smtpd/smtpd_check_dsn.in
@@ -0,0 +1,60 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.189.0/28
+#
+# Test the client restrictions.
+#
+client_restrictions hash:./smtpd_check_access
+client 4.1.1_dsn 1.2.3.4
+client 4.1.2_dsn 1.2.3.4
+client 4.1.3_dsn 1.2.3.4
+client 4.1.4_dsn 1.2.3.4
+client 4.1.5_dsn 1.2.3.4
+client 4.1.6_dsn 1.2.3.4
+client 4.1.7_dsn 1.2.3.4
+client 4.1.8_dsn 1.2.3.4
+client 4.4.0_dsn 1.2.3.4
+client dummy dummy
+#
+# Test the helo restrictions
+#
+helo_restrictions hash:./smtpd_check_access
+helo 4.1.1_dsn
+helo 4.1.2_dsn
+helo 4.1.3_dsn
+helo 4.1.4_dsn
+helo 4.1.5_dsn
+helo 4.1.6_dsn
+helo 4.1.7_dsn
+helo 4.1.8_dsn
+helo 4.4.0_dsn
+#
+# Test the sender restrictions
+#
+sender_restrictions hash:./smtpd_check_access
+mail user@4.1.1_dsn
+mail user@4.1.2_dsn
+mail user@4.1.3_dsn
+mail user@4.1.4_dsn
+mail user@4.1.5_dsn
+mail user@4.1.6_dsn
+mail user@4.1.7_dsn
+mail user@4.1.8_dsn
+mail user@4.4.0_dsn
+#
+# Test the recipient restrictions
+#
+recipient_restrictions hash:./smtpd_check_access
+rcpt user@4.1.1_dsn
+rcpt user@4.1.2_dsn
+rcpt user@4.1.3_dsn
+rcpt user@4.1.4_dsn
+rcpt user@4.1.5_dsn
+rcpt user@4.1.6_dsn
+rcpt user@4.1.7_dsn
+rcpt user@4.1.8_dsn
+rcpt user@4.4.0_dsn
diff --git a/src/smtpd/smtpd_check_dsn.ref b/src/smtpd/smtpd_check_dsn.ref
new file mode 100644
index 0000000..87b1bb9
--- /dev/null
+++ b/src/smtpd/smtpd_check_dsn.ref
@@ -0,0 +1,163 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.189.0/28
+OK
+>>> #
+>>> # Test the client restrictions.
+>>> #
+>>> client_restrictions hash:./smtpd_check_access
+OK
+>>> client 4.1.1_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.1 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.1_dsn[1.2.3.4]: 554 5.0.0 <4.1.1_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.1_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.2_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.2 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.2_dsn[1.2.3.4]: 554 5.0.0 <4.1.2_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.2_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.3_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.3 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.3_dsn[1.2.3.4]: 554 5.0.0 <4.1.3_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.3_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.4_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.4 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.4_dsn[1.2.3.4]: 554 5.0.0 <4.1.4_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.4_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.5_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.5 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.5_dsn[1.2.3.4]: 554 5.0.0 <4.1.5_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.5_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.6_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.6 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.6_dsn[1.2.3.4]: 554 5.0.0 <4.1.6_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.6_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.7_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.7 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.7_dsn[1.2.3.4]: 554 5.0.0 <4.1.7_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.7_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.1.8_dsn 1.2.3.4
+./smtpd_check: mapping DSN status 4.1.8 into Client host status 4.0.0
+./smtpd_check: <queue id>: reject: CONNECT from 4.1.8_dsn[1.2.3.4]: 554 5.0.0 <4.1.8_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.0.0 <4.1.8_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client 4.4.0_dsn 1.2.3.4
+./smtpd_check: <queue id>: reject: CONNECT from 4.4.0_dsn[1.2.3.4]: 554 5.4.0 <4.4.0_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP
+554 5.4.0 <4.4.0_dsn[1.2.3.4]>: Client host rejected: reject
+>>> client dummy dummy
+OK
+>>> #
+>>> # Test the helo restrictions
+>>> #
+>>> helo_restrictions hash:./smtpd_check_access
+OK
+>>> helo 4.1.1_dsn
+./smtpd_check: mapping DSN status 4.1.1 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.1_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.1_dsn>
+554 5.0.0 <4.1.1_dsn>: Helo command rejected: reject
+>>> helo 4.1.2_dsn
+./smtpd_check: mapping DSN status 4.1.2 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.2_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.2_dsn>
+554 5.0.0 <4.1.2_dsn>: Helo command rejected: reject
+>>> helo 4.1.3_dsn
+./smtpd_check: mapping DSN status 4.1.3 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.3_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.3_dsn>
+554 5.0.0 <4.1.3_dsn>: Helo command rejected: reject
+>>> helo 4.1.4_dsn
+./smtpd_check: mapping DSN status 4.1.4 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.4_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.4_dsn>
+554 5.0.0 <4.1.4_dsn>: Helo command rejected: reject
+>>> helo 4.1.5_dsn
+./smtpd_check: mapping DSN status 4.1.5 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.5_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.5_dsn>
+554 5.0.0 <4.1.5_dsn>: Helo command rejected: reject
+>>> helo 4.1.6_dsn
+./smtpd_check: mapping DSN status 4.1.6 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.6_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.6_dsn>
+554 5.0.0 <4.1.6_dsn>: Helo command rejected: reject
+>>> helo 4.1.7_dsn
+./smtpd_check: mapping DSN status 4.1.7 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.7_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.7_dsn>
+554 5.0.0 <4.1.7_dsn>: Helo command rejected: reject
+>>> helo 4.1.8_dsn
+./smtpd_check: mapping DSN status 4.1.8 into Helo command status 4.0.0
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.8_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.8_dsn>
+554 5.0.0 <4.1.8_dsn>: Helo command rejected: reject
+>>> helo 4.4.0_dsn
+./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.4.0 <4.4.0_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.4.0_dsn>
+554 5.4.0 <4.4.0_dsn>: Helo command rejected: reject
+>>> #
+>>> # Test the sender restrictions
+>>> #
+>>> sender_restrictions hash:./smtpd_check_access
+OK
+>>> mail user@4.1.1_dsn
+./smtpd_check: mapping DSN status 4.1.1 into Sender address status 4.1.7
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.1_dsn>: Sender address rejected: reject; from=<user@4.1.1_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.1_dsn>: Sender address rejected: reject
+>>> mail user@4.1.2_dsn
+./smtpd_check: mapping DSN status 4.1.2 into Sender address status 4.1.8
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.8 <user@4.1.2_dsn>: Sender address rejected: reject; from=<user@4.1.2_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.8 <user@4.1.2_dsn>: Sender address rejected: reject
+>>> mail user@4.1.3_dsn
+./smtpd_check: mapping DSN status 4.1.3 into Sender address status 4.1.7
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.3_dsn>: Sender address rejected: reject; from=<user@4.1.3_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.3_dsn>: Sender address rejected: reject
+>>> mail user@4.1.4_dsn
+./smtpd_check: mapping DSN status 4.1.4 into Sender address status 4.1.7
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.4_dsn>: Sender address rejected: reject; from=<user@4.1.4_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.4_dsn>: Sender address rejected: reject
+>>> mail user@4.1.5_dsn
+./smtpd_check: mapping DSN status 4.1.5 into Sender address status 4.1.0
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.0 <user@4.1.5_dsn>: Sender address rejected: reject; from=<user@4.1.5_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.0 <user@4.1.5_dsn>: Sender address rejected: reject
+>>> mail user@4.1.6_dsn
+./smtpd_check: mapping DSN status 4.1.6 into Sender address status 4.1.7
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.6_dsn>: Sender address rejected: reject; from=<user@4.1.6_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.6_dsn>: Sender address rejected: reject
+>>> mail user@4.1.7_dsn
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.7_dsn>: Sender address rejected: reject; from=<user@4.1.7_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.7 <user@4.1.7_dsn>: Sender address rejected: reject
+>>> mail user@4.1.8_dsn
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.8 <user@4.1.8_dsn>: Sender address rejected: reject; from=<user@4.1.8_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.8 <user@4.1.8_dsn>: Sender address rejected: reject
+>>> mail user@4.4.0_dsn
+./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.4.0 <user@4.4.0_dsn>: Sender address rejected: reject; from=<user@4.4.0_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.4.0 <user@4.4.0_dsn>: Sender address rejected: reject
+>>> #
+>>> # Test the recipient restrictions
+>>> #
+>>> recipient_restrictions hash:./smtpd_check_access
+OK
+>>> rcpt user@4.1.1_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.1 <user@4.1.1_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.1_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.1 <user@4.1.1_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.2_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.2 <user@4.1.2_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.2_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.2 <user@4.1.2_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.3_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.3 <user@4.1.3_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.3_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.3 <user@4.1.3_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.4_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.4 <user@4.1.4_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.4_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.4 <user@4.1.4_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.5_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.5 <user@4.1.5_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.5_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.5 <user@4.1.5_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.6_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.6 <user@4.1.6_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.6_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.6 <user@4.1.6_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.7_dsn
+./smtpd_check: mapping DSN status 4.1.7 into Recipient address status 4.1.3
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.3 <user@4.1.7_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.7_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.3 <user@4.1.7_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.1.8_dsn
+./smtpd_check: mapping DSN status 4.1.8 into Recipient address status 4.1.2
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.2 <user@4.1.8_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.8_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.1.2 <user@4.1.8_dsn>: Recipient address rejected: reject
+>>> rcpt user@4.4.0_dsn
+./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.4.0 <user@4.4.0_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.4.0_dsn> proto=SMTP helo=<4.4.0_dsn>
+554 5.4.0 <user@4.4.0_dsn>: Recipient address rejected: reject
diff --git a/src/smtpd/smtpd_dns_filter.in b/src/smtpd/smtpd_dns_filter.in
new file mode 100644
index 0000000..df1d8ef
--- /dev/null
+++ b/src/smtpd/smtpd_dns_filter.in
@@ -0,0 +1,83 @@
+#
+# Initialize
+#
+client localhost 127.0.0.1
+smtpd_delay_reject 0
+#
+# Test reject_unknown_helo_hostname
+#
+smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+helo_restrictions reject_unknown_helo_hostname,permit
+# EXPECT OK + "all MX records dropped" warning.
+helo xn--1xa.porcupine.org
+# EXPECT OK (nullmx has A record)
+helo nullmx.porcupine.org
+# EXPECT reject (nxdomain is not filtered).
+helo nxdomain.porcupine.org
+smtpd_dns_reply_filter regexp:../dns/no-a.reg
+# EXPECT OK (host has AAAA record).
+mail user@spike.porcupine.org
+helo spike.porcupine.org
+# EXPECT OK + "all A records dropped" warning + no delayed reject.
+helo fist.porcupine.org
+mail user@spike.porcupine.org
+rcpt user@spike.porcupine.org
+smtpd_dns_reply_filter regexp:../dns/error.reg
+# EXPECT OK + "filter config error" warning + delayed reject.
+helo spike.porcupine.org
+mail user@spike.porcupine.org
+rcpt user@spike.porcupine.org
+# EXPECT OK + "filter config error" warning (nullmx has A record) + delayed reject.
+helo nullmx.porcupine.org
+mail user@spike.porcupine.org
+rcpt user@spike.porcupine.org
+# EXPECT reject (nxdomain is not filtered).
+helo nxdomain.porcupine.org
+#
+# Test reject_unknown_sender_domain (same code as
+# reject_unknown_recipient_domain).
+#
+smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+helo localhost
+sender_restrictions reject_unknown_sender_domain
+# EXPECT OK + "all MX records dropped" warning.
+mail user@xn--1xa.porcupine.org
+# EXPECT reject (nullmx is not filtered).
+mail user@nullmx.porcupine.org
+# EXPECT reject (nxdomain is not filtered).
+mail user@nxdomain.porcupine.org
+# EXPECT OK
+mail user@localhost
+smtpd_dns_reply_filter regexp:../dns/no-a.reg
+# EXPECT OK (host has AAAA record).
+mail user@spike.porcupine.org
+# EXPECT OK + "all A records dropped" warning.
+mail user@fist.porcupine.org
+smtpd_dns_reply_filter regexp:../dns/error.reg
+# EXPECT OK + "filter config error" warning + delayed reject.
+mail user@xn--1xa.porcupine.org
+rcpt user
+# EXPECT reject (nullmx is not filtered).
+mail user@nullmx.porcupine.org
+# EXPECT reject (nxdomain is not filtered).
+mail user@nxdomain.porcupine.org
+#
+# Test reject_rbl_client
+#
+client_restrictions reject_rbl_client,dnsbltest.porcupine.org
+smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+flush_dnsxl_cache
+# EXPECT reject + A and TXT record.
+client localhost 127.0.0.2
+smtpd_dns_reply_filter regexp:../dns/no-a.reg
+flush_dnsxl_cache
+# EXPECT OK + "all A results dropped" warning.
+client localhost 127.0.0.2
+smtpd_dns_reply_filter regexp:../dns/no-txt.reg
+flush_dnsxl_cache
+# EXPECT reject + A record, "all TXT results dropped" warning.
+client localhost 127.0.0.2
+smtpd_dns_reply_filter regexp:../dns/error.reg
+flush_dnsxl_cache
+# EXPECT OK + "filter configuration error"
+client localhost 127.0.0.2
diff --git a/src/smtpd/smtpd_dns_filter.ref b/src/smtpd/smtpd_dns_filter.ref
new file mode 100644
index 0000000..92c9102
--- /dev/null
+++ b/src/smtpd/smtpd_dns_filter.ref
@@ -0,0 +1,163 @@
+>>> #
+>>> # Initialize
+>>> #
+>>> client localhost 127.0.0.1
+OK
+>>> smtpd_delay_reject 0
+OK
+>>> #
+>>> # Test reject_unknown_helo_hostname
+>>> #
+>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+OK
+>>> helo_restrictions reject_unknown_helo_hostname,permit
+OK
+>>> # EXPECT OK + "all MX records dropped" warning.
+>>> helo xn--1xa.porcupine.org
+./smtpd_check: ignoring DNS RR: xn--1xa.porcupine.org. TTL IN MX 10 spike.porcupine.org.
+./smtpd_check: warning: xn--1xa.porcupine.org: address or MX lookup error: DNS reply filter drops all results
+OK
+>>> # EXPECT OK (nullmx has A record)
+>>> helo nullmx.porcupine.org
+OK
+>>> # EXPECT reject (nxdomain is not filtered).
+>>> helo nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found; proto=SMTP helo=<nxdomain.porcupine.org>
+450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found
+>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg
+OK
+>>> # EXPECT OK (host has AAAA record).
+>>> mail user@spike.porcupine.org
+OK
+>>> helo spike.porcupine.org
+./smtpd_check: ignoring DNS RR: spike.porcupine.org. TTL IN A 168.100.189.2
+OK
+>>> # EXPECT OK + "all A records dropped" warning + no delayed reject.
+>>> helo fist.porcupine.org
+./smtpd_check: ignoring DNS RR: fist.porcupine.org. TTL IN A 168.100.189.4
+./smtpd_check: warning: fist.porcupine.org: address or MX lookup error: DNS reply filter drops all results
+OK
+>>> mail user@spike.porcupine.org
+OK
+>>> rcpt user@spike.porcupine.org
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/error.reg
+OK
+>>> # EXPECT OK + "filter config error" warning + delayed reject.
+>>> helo spike.porcupine.org
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+OK
+>>> mail user@spike.porcupine.org
+OK
+>>> rcpt user@spike.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 450 4.7.1 <spike.porcupine.org>: Helo command rejected: Host not found; from=<user@spike.porcupine.org> to=<user@spike.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+450 4.7.1 <spike.porcupine.org>: Helo command rejected: Host not found
+>>> # EXPECT OK + "filter config error" warning (nullmx has A record) + delayed reject.
+>>> helo nullmx.porcupine.org
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+OK
+>>> mail user@spike.porcupine.org
+OK
+>>> rcpt user@spike.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 450 4.7.1 <nullmx.porcupine.org>: Helo command rejected: Host not found; from=<user@spike.porcupine.org> to=<user@spike.porcupine.org> proto=SMTP helo=<nullmx.porcupine.org>
+450 4.7.1 <nullmx.porcupine.org>: Helo command rejected: Host not found
+>>> # EXPECT reject (nxdomain is not filtered).
+>>> helo nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found; from=<user@spike.porcupine.org> proto=SMTP helo=<nxdomain.porcupine.org>
+450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found
+>>> #
+>>> # Test reject_unknown_sender_domain (same code as
+>>> # reject_unknown_recipient_domain).
+>>> #
+>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+OK
+>>> helo localhost
+OK
+>>> sender_restrictions reject_unknown_sender_domain
+OK
+>>> # EXPECT OK + "all MX records dropped" warning.
+>>> mail user@xn--1xa.porcupine.org
+./smtpd_check: ignoring DNS RR: xn--1xa.porcupine.org. TTL IN MX 10 spike.porcupine.org.
+./smtpd_check: warning: xn--1xa.porcupine.org: MX or address lookup error: DNS reply filter drops all results
+OK
+>>> # EXPECT reject (nullmx is not filtered).
+>>> mail user@nullmx.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<user@nullmx.porcupine.org> proto=SMTP helo=<localhost>
+550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX)
+>>> # EXPECT reject (nxdomain is not filtered).
+>>> mail user@nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost>
+450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found
+>>> # EXPECT OK
+>>> mail user@localhost
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg
+OK
+>>> # EXPECT OK (host has AAAA record).
+>>> mail user@spike.porcupine.org
+./smtpd_check: ignoring DNS RR: spike.porcupine.org. TTL IN A 168.100.189.2
+OK
+>>> # EXPECT OK + "all A records dropped" warning.
+>>> mail user@fist.porcupine.org
+./smtpd_check: ignoring DNS RR: fist.porcupine.org. TTL IN A 168.100.189.4
+./smtpd_check: warning: fist.porcupine.org: MX or address lookup error: DNS reply filter drops all results
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/error.reg
+OK
+>>> # EXPECT OK + "filter config error" warning + delayed reject.
+>>> mail user@xn--1xa.porcupine.org
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+OK
+>>> rcpt user
+./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 450 4.1.8 <user@xn--1xa.porcupine.org>: Sender address rejected: Domain not found; from=<user@xn--1xa.porcupine.org> to=<user> proto=SMTP helo=<localhost>
+450 4.1.8 <user@xn--1xa.porcupine.org>: Sender address rejected: Domain not found
+>>> # EXPECT reject (nullmx is not filtered).
+>>> mail user@nullmx.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<user@nullmx.porcupine.org> proto=SMTP helo=<localhost>
+550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX)
+>>> # EXPECT reject (nxdomain is not filtered).
+>>> mail user@nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost>
+450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found
+>>> #
+>>> # Test reject_rbl_client
+>>> #
+>>> client_restrictions reject_rbl_client,dnsbltest.porcupine.org
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg
+OK
+>>> flush_dnsxl_cache
+OK
+>>> # EXPECT reject + A and TXT record.
+>>> client localhost 127.0.0.2
+./smtpd_check: <queue id>: reject: CONNECT from localhost[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test
+>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg
+OK
+>>> flush_dnsxl_cache
+OK
+>>> # EXPECT OK + "all A results dropped" warning.
+>>> client localhost 127.0.0.2
+./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN A 127.0.0.2
+./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: RBL lookup error: Error looking up name=2.0.0.127.dnsbltest.porcupine.org type=A: DNS reply filter drops all results
+OK
+>>> smtpd_dns_reply_filter regexp:../dns/no-txt.reg
+OK
+>>> flush_dnsxl_cache
+OK
+>>> # EXPECT reject + A record, "all TXT results dropped" warning.
+>>> client localhost 127.0.0.2
+./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN TXT DNS blocklist test
+./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: TXT lookup error: DNS reply filter drops all results
+./smtpd_check: <queue id>: reject: CONNECT from localhost[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
+>>> smtpd_dns_reply_filter regexp:../dns/error.reg
+OK
+>>> flush_dnsxl_cache
+OK
+>>> # EXPECT OK + "filter configuration error"
+>>> client localhost 127.0.0.2
+./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops"
+./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: RBL lookup error: Error looking up name=2.0.0.127.dnsbltest.porcupine.org type=A: Invalid DNS reply filter syntax
+OK
diff --git a/src/smtpd/smtpd_dnswl.in b/src/smtpd/smtpd_dnswl.in
new file mode 100644
index 0000000..6546e02
--- /dev/null
+++ b/src/smtpd/smtpd_dnswl.in
@@ -0,0 +1,60 @@
+#
+# Initialize.
+#
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.189.0/28
+mydestination porcupine.org
+relay_domains porcupine.org
+helo foobar
+
+#
+# DNSWL (by IP address)
+#
+
+# Whitelist overrides reject.
+client_restrictions permit_dnswl_client,wild.porcupine.org,reject
+client spike.porcupine.org 168.100.189.2
+
+# Whitelist does not fire - reject.
+client_restrictions permit_dnswl_client,porcupine.org,reject
+client spike.porcupine.org 168.100.189.2
+
+# Whitelist does not override reject_unauth_destination.
+client_restrictions permit
+recipient_restrictions permit_dnswl_client,wild.porcupine.org,reject_unauth_destination
+# Unauthorized destination - reject.
+rcpt rname@rdomain
+# Authorized destination - accept.
+rcpt wietse@porcupine.org
+
+#
+# RHSWL (by domain name)
+#
+
+# Whitelist overrides reject.
+client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject
+# Non-whitelisted client name - reject.
+client spike.porcupine.org 168.100.189.2
+# Whitelisted client name - accept.
+client example.tld 168.100.189.2
+
+# Whitelist does not override reject_unauth_destination.
+client_restrictions permit
+recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination
+# Non-whitelisted client name.
+client spike.porcupine.org 168.100.189.2
+# Unauthorized destination - reject.
+rcpt rname@rdomain
+# Authorized destination - accept.
+rcpt wietse@porcupine.org
+# Whitelisted client name.
+client example.tld 168.100.189.2
+# Unauthorized destination - reject.
+rcpt rname@rdomain
+# Authorized destination - accept.
+rcpt wietse@porcupine.org
+# Numeric TLD - dunno.
+rcpt wietse@12345
+rcpt wietse@12345.porcupine.org
+rcpt wietse@porcupine.12345
diff --git a/src/smtpd/smtpd_dnswl.ref b/src/smtpd/smtpd_dnswl.ref
new file mode 100644
index 0000000..7c23459
--- /dev/null
+++ b/src/smtpd/smtpd_dnswl.ref
@@ -0,0 +1,94 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.189.0/28
+OK
+>>> mydestination porcupine.org
+OK
+>>> relay_domains porcupine.org
+OK
+>>> helo foobar
+OK
+>>>
+>>> #
+>>> # DNSWL (by IP address)
+>>> #
+>>>
+>>> # Whitelist overrides reject.
+>>> client_restrictions permit_dnswl_client,wild.porcupine.org,reject
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>>
+>>> # Whitelist does not fire - reject.
+>>> client_restrictions permit_dnswl_client,porcupine.org,reject
+OK
+>>> client spike.porcupine.org 168.100.189.2
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <spike.porcupine.org[168.100.189.2]>: Client host rejected: Access denied; proto=SMTP helo=<foobar>
+554 5.7.1 <spike.porcupine.org[168.100.189.2]>: Client host rejected: Access denied
+>>>
+>>> # Whitelist does not override reject_unauth_destination.
+>>> client_restrictions permit
+OK
+>>> recipient_restrictions permit_dnswl_client,wild.porcupine.org,reject_unauth_destination
+OK
+>>> # Unauthorized destination - reject.
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <rname@rdomain>: Relay access denied; to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 <rname@rdomain>: Relay access denied
+>>> # Authorized destination - accept.
+>>> rcpt wietse@porcupine.org
+OK
+>>>
+>>> #
+>>> # RHSWL (by domain name)
+>>> #
+>>>
+>>> # Whitelist overrides reject.
+>>> client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject
+OK
+>>> # Non-whitelisted client name - reject.
+>>> client spike.porcupine.org 168.100.189.2
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <spike.porcupine.org[168.100.189.2]>: Client host rejected: Access denied; proto=SMTP helo=<foobar>
+554 5.7.1 <spike.porcupine.org[168.100.189.2]>: Client host rejected: Access denied
+>>> # Whitelisted client name - accept.
+>>> client example.tld 168.100.189.2
+OK
+>>>
+>>> # Whitelist does not override reject_unauth_destination.
+>>> client_restrictions permit
+OK
+>>> recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination
+OK
+>>> # Non-whitelisted client name.
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> # Unauthorized destination - reject.
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <rname@rdomain>: Relay access denied; to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 <rname@rdomain>: Relay access denied
+>>> # Authorized destination - accept.
+>>> rcpt wietse@porcupine.org
+OK
+>>> # Whitelisted client name.
+>>> client example.tld 168.100.189.2
+OK
+>>> # Unauthorized destination - reject.
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.189.2]: 554 5.7.1 <rname@rdomain>: Relay access denied; to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 <rname@rdomain>: Relay access denied
+>>> # Authorized destination - accept.
+>>> rcpt wietse@porcupine.org
+OK
+>>> # Numeric TLD - dunno.
+>>> rcpt wietse@12345
+./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.189.2]: 554 5.7.1 <wietse@12345>: Relay access denied; to=<wietse@12345> proto=SMTP helo=<foobar>
+554 5.7.1 <wietse@12345>: Relay access denied
+>>> rcpt wietse@12345.porcupine.org
+OK
+>>> rcpt wietse@porcupine.12345
+./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.189.2]: 554 5.7.1 <wietse@porcupine.12345>: Relay access denied; to=<wietse@porcupine.12345> proto=SMTP helo=<foobar>
+554 5.7.1 <wietse@porcupine.12345>: Relay access denied
diff --git a/src/smtpd/smtpd_dsn_fix.c b/src/smtpd/smtpd_dsn_fix.c
new file mode 100644
index 0000000..d436967
--- /dev/null
+++ b/src/smtpd/smtpd_dsn_fix.c
@@ -0,0 +1,149 @@
+/*++
+/* NAME
+/* smtpd_dsn_fix 3
+/* SUMMARY
+/* fix DSN status
+/* SYNOPSIS
+/* #include <smtpd_dsn_fix.h>
+/*
+/* const char *smtpd_dsn_fix(status, reply_class)
+/* const char *status;
+/* const char *reply_class;
+/* DESCRIPTION
+/* smtpd_dsn_fix() transforms DSN status codes according to the
+/* status information that is actually being reported. The
+/* following transformations are implemented:
+/* .IP \(bu
+/* Transform a recipient address DSN into a sender address DSN
+/* when reporting sender address status information, and vice
+/* versa. This transformation may be needed because some Postfix
+/* access control features don't know whether the address being
+/* rejected is a sender or recipient. Examples are smtpd access
+/* tables, rbl reply templates, and the error mailer.
+/* .IP \(bu
+/* Transform a sender or recipient address DSN into a non-address
+/* DSN when reporting non-address status information. For
+/* example, if something rejects HELO with DSN status 4.1.1
+/* (unknown recipient address), then we send the more neutral
+/* 4.0.0 DSN instead. This transformation is needed when the
+/* same smtpd access map entry or rbl reply template is used
+/* for both address and non-address information.
+/* .PP
+/* A non-address DSN is not transformed
+/* when reporting sender or recipient address status information,
+/* as there are many legitimate instances of such usage.
+/*
+/* It is left up to the caller to update the initial DSN digit
+/* appropriately; in Postfix this is done as late as possible,
+/* because hard rejects may be changed into soft rejects for
+/* all kinds of reasons.
+/*
+/* Arguments:
+/* .IP status
+/* A DSN status as per RFC 3463.
+/* .IP reply_class
+/* SMTPD_NAME_SENDER, SMTPD_NAME_RECIPIENT or some other
+/* null-terminated string.
+/* 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 <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+/* Application-specific. */
+
+#include <smtpd_dsn_fix.h>
+
+struct dsn_map {
+ const char *micro_code; /* Final digits in mailbox D.S.N. */
+ const char *sender_dsn; /* Replacement sender D.S.N. */
+ const char *rcpt_dsn; /* Replacement recipient D.S.N. */
+};
+
+static struct dsn_map dsn_map[] = {
+ /* - Sender - Recipient */
+ "1", SND_DSN, "4.1.1", /* 4.1.1: Bad dest mbox addr */
+ "2", "4.1.8", "4.1.2", /* 4.1.2: Bad dest system addr */
+ "3", "4.1.7", "4.1.3", /* 4.1.3: Bad dest mbox addr syntax */
+ "4", SND_DSN, "4.1.4", /* 4.1.4: Dest mbox addr ambiguous */
+ "5", "4.1.0", "4.1.5", /* 4.1.5: Dest mbox addr valid */
+ "6", SND_DSN, "4.1.6", /* 4.1.6: Mailbox has moved */
+ "7", "4.1.7", "4.1.3", /* 4.1.7: Bad sender mbox addr syntax */
+ "8", "4.1.8", "4.1.2", /* 4.1.8: Bad sender system addr */
+ 0, "4.1.0", "4.1.0", /* Default mapping */
+};
+
+/* smtpd_dsn_fix - fix DSN status */
+
+const char *smtpd_dsn_fix(const char *status, const char *reply_class)
+{
+ struct dsn_map *dp;
+ const char *result = status;
+
+ /*
+ * Update an address-specific DSN according to what is being rejected.
+ */
+ if (ISDIGIT(status[0]) && strncmp(status + 1, ".1.", 3) == 0) {
+
+ /*
+ * Fix recipient address DSN while rejecting a sender address. Don't
+ * let future recipient-specific DSN codes slip past us.
+ */
+ if (strcmp(reply_class, SMTPD_NAME_SENDER) == 0) {
+ for (dp = dsn_map; dp->micro_code != 0; dp++)
+ if (strcmp(status + 4, dp->micro_code) == 0)
+ break;
+ result = dp->sender_dsn;
+ }
+
+ /*
+ * Fix sender address DSN while rejecting a recipient address. Don't
+ * let future sender-specific DSN codes slip past us.
+ */
+ else if (strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0) {
+ for (dp = dsn_map; dp->micro_code != 0; dp++)
+ if (strcmp(status + 4, dp->micro_code) == 0)
+ break;
+ result = dp->rcpt_dsn;
+ }
+
+ /*
+ * Fix address-specific DSN while rejecting a non-address.
+ */
+ else {
+ result = "4.0.0";
+ }
+
+ /*
+ * Give them a clue of what is going on.
+ */
+ if (strcmp(status + 2, result + 2) != 0)
+ msg_info("mapping DSN status %s into %s status %c%s",
+ status, reply_class, status[0], result + 1);
+ return (result);
+ }
+
+ /*
+ * Don't update a non-address DSN. There are many legitimate uses for
+ * these while rejecting address or non-address information.
+ */
+ else {
+ return (status);
+ }
+}
diff --git a/src/smtpd/smtpd_dsn_fix.h b/src/smtpd/smtpd_dsn_fix.h
new file mode 100644
index 0000000..c608e34
--- /dev/null
+++ b/src/smtpd/smtpd_dsn_fix.h
@@ -0,0 +1,44 @@
+/*++
+/* NAME
+/* smtpd_check 3h
+/* SUMMARY
+/* SMTP client request filtering
+/* SYNOPSIS
+/* #include "smtpd.h"
+/* #include "smtpd_check_int.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Internal interface.
+ */
+#define SMTPD_NAME_CLIENT "Client host"
+#define SMTPD_NAME_REV_CLIENT "Unverified Client host"
+#define SMTPD_NAME_CCERT "Client certificate"
+#define SMTPD_NAME_SASL_USER "SASL login name"
+#define SMTPD_NAME_HELO "Helo command"
+#define SMTPD_NAME_SENDER "Sender address"
+#define SMTPD_NAME_RECIPIENT "Recipient address"
+#define SMTPD_NAME_ETRN "Etrn command"
+#define SMTPD_NAME_DATA "Data command"
+#define SMTPD_NAME_EOD "End-of-data"
+
+ /*
+ * Workaround for absence of "bad sender address" status code: use "bad
+ * sender address syntax" instead. If we were to use "4.1.0" then we would
+ * lose the critical distinction between sender and recipient problems.
+ */
+#define SND_DSN "4.1.7"
+
+extern const char *smtpd_dsn_fix(const char *, const char *);
+
+/* 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/smtpd/smtpd_error.in b/src/smtpd/smtpd_error.in
new file mode 100644
index 0000000..c894b87
--- /dev/null
+++ b/src/smtpd/smtpd_error.in
@@ -0,0 +1,81 @@
+#
+# Initialize
+#
+smtpd_delay_reject 0
+#
+# Test check_domain_access()
+#
+helo_restrictions fail:1_helo_access
+# Expect: REJECT (temporary lookup failure)
+helo foobar
+#
+# Test check_namadr_access()
+#
+client_restrictions fail:1_client_access
+# Expect: REJECT (temporary lookup failure)
+client foo.dunno.com 131.155.210.17
+#
+# Test check_mail_access()
+#
+sender_restrictions fail:1_sender_access
+# Expect: REJECT (temporary lookup failure)
+mail reject@dunno.domain
+#
+# Test check_rcpt_access()
+#
+recipient_restrictions fail:1_rcpt_access
+# Expect: REJECT (temporary lookup failure)
+rcpt reject@dunno.domain
+# Expect: OK
+rcpt postmaster
+#
+# Test mynetworks in generic_checks().
+#
+mynetworks fail:1_mynetworks
+#
+# Expect REJECT (temporary lookup failure)
+#
+recipient_restrictions permit_mynetworks
+rcpt reject@dunno.domain
+#
+# Test mynetworks.
+#
+mynetworks 168.100.189.1/27
+#
+# Expect REJECT (server configuration error)
+#
+rcpt reject@dunno.domain
+#
+# check_sender_access specific
+#
+smtpd_null_access_lookup_key <>
+mail <>
+#
+# Test permit_tls_client_certs in generic_restrictions
+#
+relay_clientcerts fail:1_certs
+fingerprint abcdef
+recipient_restrictions permit_tls_clientcerts
+rcpt reject@dunno.domain
+#
+# Test smtpd_check_rewrite().
+#
+local_header_rewrite_clients fail:1_rewrite
+#
+# Expect: REJECT (temporary lookup failure)
+#
+rewrite
+#
+# Test resolve_local()
+#
+mydestination example.com
+recipient_restrictions reject_unauth_destination
+rcpt user@example.com
+mydestination fail:1_mydestination
+rcpt user@example.com
+#
+# Test virtual alias lookup.
+#
+mydestination example.com
+virtual_alias_maps fail:1_virtual
+rcpt user@example.com
diff --git a/src/smtpd/smtpd_error.ref b/src/smtpd/smtpd_error.ref
new file mode 100644
index 0000000..7df38e1
--- /dev/null
+++ b/src/smtpd/smtpd_error.ref
@@ -0,0 +1,135 @@
+>>> #
+>>> # Initialize
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> #
+>>> # Test check_domain_access()
+>>> #
+>>> helo_restrictions fail:1_helo_access
+OK
+>>> # Expect: REJECT (temporary lookup failure)
+>>> helo foobar
+./smtpd_check: warning: fail:1_helo_access lookup error for "foobar"
+./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 451 4.3.5 <foobar>: Helo command rejected: Server configuration error; proto=SMTP helo=<foobar>
+451 4.3.5 <foobar>: Helo command rejected: Server configuration error
+>>> #
+>>> # Test check_namadr_access()
+>>> #
+>>> client_restrictions fail:1_client_access
+OK
+>>> # Expect: REJECT (temporary lookup failure)
+>>> client foo.dunno.com 131.155.210.17
+./smtpd_check: warning: fail:1_client_access lookup error for "foo.dunno.com"
+./smtpd_check: <queue id>: reject: CONNECT from foo.dunno.com[131.155.210.17]: 451 4.3.5 <foo.dunno.com[131.155.210.17]>: Client host rejected: Server configuration error; proto=SMTP helo=<foobar>
+451 4.3.5 <foo.dunno.com[131.155.210.17]>: Client host rejected: Server configuration error
+>>> #
+>>> # Test check_mail_access()
+>>> #
+>>> sender_restrictions fail:1_sender_access
+OK
+>>> # Expect: REJECT (temporary lookup failure)
+>>> mail reject@dunno.domain
+./smtpd_check: warning: fail:1_sender_access lookup error for "reject@dunno.domain"
+./smtpd_check: <queue id>: reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 <reject@dunno.domain>: Sender address rejected: Server configuration error; from=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.5 <reject@dunno.domain>: Sender address rejected: Server configuration error
+>>> #
+>>> # Test check_rcpt_access()
+>>> #
+>>> recipient_restrictions fail:1_rcpt_access
+OK
+>>> # Expect: REJECT (temporary lookup failure)
+>>> rcpt reject@dunno.domain
+./smtpd_check: warning: fail:1_rcpt_access lookup error for "reject@dunno.domain"
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.5 <reject@dunno.domain>: Recipient address rejected: Server configuration error; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.5 <reject@dunno.domain>: Recipient address rejected: Server configuration error
+>>> # Expect: OK
+>>> rcpt postmaster
+OK
+>>> #
+>>> # Test mynetworks in generic_checks().
+>>> #
+>>> mynetworks fail:1_mynetworks
+OK
+>>> #
+>>> # Expect REJECT (temporary lookup failure)
+>>> #
+>>> recipient_restrictions permit_mynetworks
+OK
+>>> rcpt reject@dunno.domain
+./smtpd_check: warning: mynetworks: fail:1_mynetworks: table lookup problem
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.0 <reject@dunno.domain>: Temporary lookup failure
+>>> #
+>>> # Test mynetworks.
+>>> #
+>>> mynetworks 168.100.189.1/27
+OK
+>>> #
+>>> # Expect REJECT (server configuration error)
+>>> #
+>>> rcpt reject@dunno.domain
+./smtpd_check: warning: mynetworks: non-null host address bits in "168.100.189.1/27", perhaps you should use "168.100.189.0/27" instead
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.0 <reject@dunno.domain>: Temporary lookup failure
+>>> #
+>>> # check_sender_access specific
+>>> #
+>>> smtpd_null_access_lookup_key <>
+OK
+>>> mail <>
+./smtpd_check: warning: fail:1_sender_access lookup error for "<>"
+./smtpd_check: <queue id>: reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 <>: Sender address rejected: Server configuration error; from=<> proto=SMTP helo=<foobar>
+451 4.3.5 <>: Sender address rejected: Server configuration error
+>>> #
+>>> # Test permit_tls_client_certs in generic_restrictions
+>>> #
+>>> relay_clientcerts fail:1_certs
+OK
+>>> fingerprint abcdef
+OK
+>>> recipient_restrictions permit_tls_clientcerts
+OK
+>>> rcpt reject@dunno.domain
+./smtpd_check: warning: fail:1_certs lookup error for "abcdef"
+./smtpd_check: warning: relay_clientcerts: lookup error for fingerprint 'abcdef', pkey fingerprint abcdef
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.0 <reject@dunno.domain>: Temporary lookup failure
+>>> #
+>>> # Test smtpd_check_rewrite().
+>>> #
+>>> local_header_rewrite_clients fail:1_rewrite
+OK
+>>> #
+>>> # Expect: REJECT (temporary lookup failure)
+>>> #
+>>> rewrite
+./smtpd_check: warning: fail:1_rewrite lookup error for "131.155.210.17"
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 Temporary lookup error; from=<> proto=SMTP helo=<foobar>
+451 4.3.0 Temporary lookup error
+>>> #
+>>> # Test resolve_local()
+>>> #
+>>> mydestination example.com
+OK
+>>> recipient_restrictions reject_unauth_destination
+OK
+>>> rcpt user@example.com
+OK
+>>> mydestination fail:1_mydestination
+OK
+>>> rcpt user@example.com
+./smtpd_check: warning: mydestination: fail:1_mydestination: table lookup problem
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <user@example.com>: Temporary lookup failure; from=<> to=<user@example.com> proto=SMTP helo=<foobar>
+451 4.3.0 <user@example.com>: Temporary lookup failure
+>>> #
+>>> # Test virtual alias lookup.
+>>> #
+>>> mydestination example.com
+OK
+>>> virtual_alias_maps fail:1_virtual
+OK
+>>> rcpt user@example.com
+./smtpd_check: warning: fail:1_virtual lookup error for "user@example.com"
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <user@example.com>: Temporary lookup failure; from=<> to=<user@example.com> proto=SMTP helo=<foobar>
+451 4.3.0 <user@example.com>: Temporary lookup failure
diff --git a/src/smtpd/smtpd_exp.in b/src/smtpd/smtpd_exp.in
new file mode 100644
index 0000000..584acf3
--- /dev/null
+++ b/src/smtpd/smtpd_exp.in
@@ -0,0 +1,62 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+mynetworks 127.0.0.0/8,168.100.189.0/28
+relay_domains porcupine.org
+maps_rbl_domains dnsbltest.porcupine.org
+rbl_reply_maps hash:smtpd_check_access
+helo foobar
+#
+# RBL
+#
+mail sname@sdomain
+recipient_restrictions reject_maps_rbl
+client spike.porcupine.org 168.100.189.2
+rcpt rname@rdomain
+client foo 127.0.0.2
+rcpt rname@rdomain
+#
+recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org
+client spike.porcupine.org 168.100.189.2
+rcpt rname@rdomain
+client foo 127.0.0.2
+rcpt rname@rdomain
+recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2
+client foo 127.0.0.2
+rcpt rname@rdomain
+client foo 127.0.0.1
+rcpt rname@rdomain
+#
+# RHSBL sender domain name
+#
+recipient_restrictions reject_rhsbl_sender,rhsbl.porcupine.org
+client spike.porcupine.org 168.100.189.2
+mail sname@example.tld
+rcpt rname@rdomain
+mail sname@sdomain
+rcpt rname@rdomain
+#
+# RHSBL client domain name
+#
+recipient_restrictions reject_rhsbl_client,rhsbl.porcupine.org
+client example.tld 1.2.3.4
+mail sname@sdomain
+rcpt rname@rdomain
+#
+# RHSBL recipient domain name
+#
+recipient_restrictions reject_rhsbl_recipient,rhsbl.porcupine.org
+client spike.porcupine.org 168.100.189.2
+mail sname@sdomain
+rcpt rname@rdomain
+rcpt rname@example.tld
+#
+# RHSBL helo domain name
+#
+recipient_restrictions reject_rhsbl_helo,rhsbl.porcupine.org
+helo example.tld
+mail sname@sdomain
+rcpt rname@rdomain
diff --git a/src/smtpd/smtpd_exp.ref b/src/smtpd/smtpd_exp.ref
new file mode 100644
index 0000000..6d7e9fc
--- /dev/null
+++ b/src/smtpd/smtpd_exp.ref
@@ -0,0 +1,111 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> mynetworks 127.0.0.0/8,168.100.189.0/28
+OK
+>>> relay_domains porcupine.org
+OK
+>>> maps_rbl_domains dnsbltest.porcupine.org
+OK
+>>> rbl_reply_maps hash:smtpd_check_access
+OK
+>>> helo foobar
+OK
+>>> #
+>>> # RBL
+>>> #
+>>> mail sname@sdomain
+OK
+>>> recipient_restrictions reject_maps_rbl
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead
+OK
+>>> client foo 127.0.0.2
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test
+>>> #
+>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> rcpt rname@rdomain
+OK
+>>> client foo 127.0.0.2
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test
+>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2
+OK
+>>> client foo 127.0.0.2
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test
+>>> client foo 127.0.0.1
+OK
+>>> rcpt rname@rdomain
+OK
+>>> #
+>>> # RHSBL sender domain name
+>>> #
+>>> recipient_restrictions reject_rhsbl_sender,rhsbl.porcupine.org
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> mail sname@example.tld
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@example.tld sender_name=sname sender_domain=example.tld recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=sname@example.tld rbl_class=Sender address; from=<sname@example.tld> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@example.tld sender_name=sname sender_domain=example.tld recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=sname@example.tld rbl_class=Sender address
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+OK
+>>> #
+>>> # RHSBL client domain name
+>>> #
+>>> recipient_restrictions reject_rhsbl_client,rhsbl.porcupine.org
+OK
+>>> client example.tld 1.2.3.4
+OK
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from example.tld[1.2.3.4]: 554 5.7.1 client=example.tld[1.2.3.4] client_address=1.2.3.4 client_name=example.tld helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Client host; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+554 5.7.1 client=example.tld[1.2.3.4] client_address=1.2.3.4 client_name=example.tld helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Client host
+>>> #
+>>> # RHSBL recipient domain name
+>>> #
+>>> recipient_restrictions reject_rhsbl_recipient,rhsbl.porcupine.org
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+OK
+>>> rcpt rname@example.tld
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@example.tld recipient_name=rname recipient_domain=example.tld rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=rname@example.tld rbl_class=Recipient address; from=<sname@sdomain> to=<rname@example.tld> proto=SMTP helo=<foobar>
+554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@example.tld recipient_name=rname recipient_domain=example.tld rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=rname@example.tld rbl_class=Recipient address
+>>> #
+>>> # RHSBL helo domain name
+>>> #
+>>> recipient_restrictions reject_rhsbl_helo,rhsbl.porcupine.org
+OK
+>>> helo example.tld
+OK
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=example.tld sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Helo command; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<example.tld>
+554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=example.tld sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Helo command
diff --git a/src/smtpd/smtpd_expand.c b/src/smtpd/smtpd_expand.c
new file mode 100644
index 0000000..8362bd7
--- /dev/null
+++ b/src/smtpd/smtpd_expand.c
@@ -0,0 +1,247 @@
+/*++
+/* NAME
+/* smtpd_expand 3
+/* SUMMARY
+/* SMTP server macro expansion
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_expand.h>
+/*
+/* void smtpd_expand_init()
+/*
+/* int smtpd_expand(state, result, template, flags)
+/* SMTPD_STATE *state;
+/* VSTRING *result;
+/* const char *template;
+/* int flags;
+/* LOW_LEVEL INTERFACE
+/* VSTRING *smtpd_expand_filter;
+/*
+/* const char *smtpd_expand_lookup(name, unused_mode, context)
+/* const char *name;
+/* int unused_mode;
+/* void *context;
+/* const char *template;
+/* DESCRIPTION
+/* This module expands session-related macros.
+/*
+/* smtpd_expand_init() performs one-time initialization.
+/*
+/* smtpd_expand() expands macros in the template, using session
+/* attributes in the state argument, and writes the result to
+/* the result argument. The flags and result value are as with
+/* mac_expand().
+/*
+/* smtpd_expand_filter and smtpd_expand_lookup() provide access
+/* to lower-level interfaces that are used by smtpd_expand().
+/* smtpd_expand_lookup() returns null when a string is not
+/* found (or when it is a null pointer).
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal errors: out of memory.
+/* internal protocol errors. smtpd_expand() returns the binary
+/* OR of MAC_PARSE_ERROR (syntax error) and MAC_PARSE_UNDEF
+/* (undefined macro name).
+/* 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 <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <mac_expand.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+#include <smtpd_expand.h>
+
+ /*
+ * Pre-parsed expansion filter.
+ */
+VSTRING *smtpd_expand_filter;
+
+ /*
+ * SLMs.
+ */
+#define STR vstring_str
+
+/* smtpd_expand_init - initialize once during process lifetime */
+
+void smtpd_expand_init(void)
+{
+
+ /*
+ * Expand the expansion filter :-)
+ */
+ smtpd_expand_filter = vstring_alloc(10);
+ unescape(smtpd_expand_filter, var_smtpd_exp_filter);
+}
+
+/* smtpd_expand_unknown - report unknown macro name */
+
+static void smtpd_expand_unknown(const char *name)
+{
+ msg_warn("unknown macro name \"%s\" in expansion request", name);
+}
+
+/* smtpd_expand_addr - return address or substring thereof */
+
+static const char *smtpd_expand_addr(VSTRING *buf, const char *addr,
+ const char *name, int prefix_len)
+{
+ const char *p;
+ const char *suffix;
+
+ /*
+ * Return NULL only for unknown names in expansion requests.
+ */
+ if (addr == 0)
+ return ("");
+
+ suffix = name + prefix_len;
+
+ /*
+ * MAIL_ATTR_SENDER or MAIL_ATTR_RECIP.
+ */
+ if (*suffix == 0) {
+ if (*addr)
+ return (addr);
+ else
+ return ("<>");
+ }
+
+ /*
+ * "sender_name" or "recipient_name".
+ */
+#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)
+
+ else if (STREQ(suffix, MAIL_ATTR_S_NAME)) {
+ if (*addr) {
+ if ((p = strrchr(addr, '@')) != 0) {
+ vstring_strncpy(buf, addr, p - addr);
+ return (STR(buf));
+ } else {
+ return (addr);
+ }
+ } else
+ return ("<>");
+ }
+
+ /*
+ * "sender_domain" or "recipient_domain".
+ */
+ else if (STREQ(suffix, MAIL_ATTR_S_DOMAIN)) {
+ if (*addr) {
+ if ((p = strrchr(addr, '@')) != 0) {
+ return (p + 1);
+ } else {
+ return ("");
+ }
+ } else
+ return ("");
+ }
+
+ /*
+ * Unknown. Return NULL to indicate an "unknown name" error.
+ */
+ else {
+ smtpd_expand_unknown(name);
+ return (0);
+ }
+}
+
+/* smtpd_expand_lookup - generic SMTP attribute $name expansion */
+
+const char *smtpd_expand_lookup(const char *name, int unused_mode,
+ void *context)
+{
+ SMTPD_STATE *state = (SMTPD_STATE *) context;
+ time_t now;
+ struct tm *lt;
+
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(10);
+
+ if (msg_verbose > 1)
+ msg_info("smtpd_expand_lookup: ${%s}", name);
+
+#define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0)
+#define CONST_LEN(x) (sizeof(x) - 1)
+
+ /*
+ * Don't query main.cf parameters, as the result of expansion could
+ * reveal system-internal information in server replies.
+ *
+ * XXX: This said, multiple servers may be behind a single client-visible
+ * name or IP address, and each may generate its own logs. Therefore, it
+ * may be useful to expose the replying MTA id (myhostname) in the
+ * contact footer, to identify the right logs. So while we don't expose
+ * the raw configuration dictionary, we do expose "$myhostname" as
+ * expanded in var_myhostname.
+ *
+ * Return NULL only for non-existent names.
+ */
+ if (STREQ(name, MAIL_ATTR_SERVER_NAME)) {
+ return (var_myhostname);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT)) {
+ return (state->namaddr);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_PORT)) {
+ return (state->port);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_ADDR)) {
+ return (state->addr);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_NAME)) {
+ return (state->name);
+ } else if (STREQ(name, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME)) {
+ return (state->reverse_name);
+ } else if (STREQ(name, MAIL_ATTR_ACT_HELO_NAME)) {
+ return (state->helo_name ? state->helo_name : "");
+ } else if (STREQN(name, MAIL_ATTR_SENDER, CONST_LEN(MAIL_ATTR_SENDER))) {
+ return (smtpd_expand_addr(state->expand_buf, state->sender,
+ name, CONST_LEN(MAIL_ATTR_SENDER)));
+ } else if (STREQN(name, MAIL_ATTR_RECIP, CONST_LEN(MAIL_ATTR_RECIP))) {
+ return (smtpd_expand_addr(state->expand_buf, state->recipient,
+ name, CONST_LEN(MAIL_ATTR_RECIP)));
+ } if (STREQ(name, MAIL_ATTR_LOCALTIME)) {
+ if (time(&now) == (time_t) -1)
+ msg_fatal("time lookup failed: %m");
+ lt = localtime(&now);
+ VSTRING_RESET(state->expand_buf);
+ do {
+ VSTRING_SPACE(state->expand_buf, 100);
+ } while (strftime(STR(state->expand_buf),
+ vstring_avail(state->expand_buf),
+ "%b %d %H:%M:%S", lt) == 0);
+ return (STR(state->expand_buf));
+ } else {
+ smtpd_expand_unknown(name);
+ return (0);
+ }
+}
+
+/* smtpd_expand - expand session attributes in string */
+
+int smtpd_expand(SMTPD_STATE *state, VSTRING *result,
+ const char *template, int flags)
+{
+ return (mac_expand(result, template, flags, STR(smtpd_expand_filter),
+ smtpd_expand_lookup, (void *) state));
+}
diff --git a/src/smtpd/smtpd_expand.h b/src/smtpd/smtpd_expand.h
new file mode 100644
index 0000000..eb95983
--- /dev/null
+++ b/src/smtpd/smtpd_expand.h
@@ -0,0 +1,40 @@
+/*++
+/* NAME
+/* smtpd_expand 3h
+/* SUMMARY
+/* SMTP server macro expansion
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_expand.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <mac_expand.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *smtpd_expand_filter;
+void smtpd_expand_init(void);
+const char *smtpd_expand_lookup(const char *, int, void *);
+int smtpd_expand(SMTPD_STATE *, VSTRING *, const char *, 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
diff --git a/src/smtpd/smtpd_haproxy.c b/src/smtpd/smtpd_haproxy.c
new file mode 100644
index 0000000..542c3fe
--- /dev/null
+++ b/src/smtpd/smtpd_haproxy.c
@@ -0,0 +1,135 @@
+/*++
+/* NAME
+/* smtpd_haproxy 3
+/* SUMMARY
+/* Postfix SMTP server haproxy adapter
+/* SYNOPSIS
+/* #include "smtpd.h"
+/*
+/* int smtpd_peer_from_haproxy(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* smtpd_peer_from_haproxy() receives endpoint address and
+/* port information via the haproxy protocol.
+/*
+/* The following summarizes what the Postfix SMTP server expects
+/* from an up-stream proxy adapter.
+/* .IP \(bu
+/* Call smtpd_peer_from_default() if the up-stream proxy
+/* indicates that the connection is not proxied. In that case,
+/* a proxy adapter MUST NOT update any STATE fields: the
+/* smtpd_peer_from_default() function will do that instead.
+/* .IP \(bu
+/* Validate protocol, address and port syntax. Permit only
+/* protocols that are configured with the main.cf:inet_protocols
+/* setting.
+/* .IP \(bu
+/* Convert IPv4-in-IPv6 address syntax to IPv4 syntax when
+/* both IPv6 and IPv4 support are enabled with main.cf:inet_protocols.
+/* .IP \(bu
+/* Update the following session context fields: addr, port,
+/* rfc_addr, addr_family, dest_addr, dest_port. The addr_family
+/* field applies to the client address.
+/* .IP \(bu
+/* Dynamically allocate storage for string information with
+/* mystrdup(). In case of error, leave unassigned string fields
+/* at their initial zero value.
+/* .IP \(bu
+/* Log a clear warning message that explains why a request
+/* fails.
+/* .IP \(bu
+/* Never talk to the remote SMTP client.
+/* .PP
+/* Arguments:
+/* .IP state
+/* Session context.
+/* DIAGNOSTICS
+/* Warnings: I/O errors, malformed haproxy line.
+/*
+/* The result value is 0 in case of success, -1 in case of
+/* error.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <iostuff.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <mail_params.h>
+#include <valid_mailhost_addr.h>
+#include <haproxy_srvr.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+
+/* SLMs. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* smtpd_peer_from_haproxy - initialize peer information from haproxy */
+
+int smtpd_peer_from_haproxy(SMTPD_STATE *state)
+{
+ MAI_HOSTADDR_STR smtp_client_addr;
+ MAI_SERVPORT_STR smtp_client_port;
+ MAI_HOSTADDR_STR smtp_server_addr;
+ MAI_SERVPORT_STR smtp_server_port;
+ int non_proxy = 0;
+
+ if (read_wait(vstream_fileno(state->client), var_smtpd_uproxy_tmout) < 0) {
+ msg_warn("haproxy read: timeout error");
+ return (-1);
+ }
+ if (haproxy_srvr_receive(vstream_fileno(state->client), &non_proxy,
+ &smtp_client_addr, &smtp_client_port,
+ &smtp_server_addr, &smtp_server_port) < 0) {
+ return (-1);
+ }
+ if (non_proxy) {
+ smtpd_peer_from_default(state);
+ return (0);
+ }
+ state->addr = mystrdup(smtp_client_addr.buf);
+ if (strrchr(state->addr, ':') != 0) {
+ state->rfc_addr = concatenate(IPV6_COL, state->addr, (char *) 0);
+ state->addr_family = AF_INET6;
+ } else {
+ state->rfc_addr = mystrdup(state->addr);
+ state->addr_family = AF_INET;
+ }
+ state->port = mystrdup(smtp_client_port.buf);
+
+ /*
+ * The Dovecot authentication server needs the server IP address.
+ */
+ state->dest_addr = mystrdup(smtp_server_addr.buf);
+ state->dest_port = mystrdup(smtp_server_port.buf);
+ return (0);
+}
diff --git a/src/smtpd/smtpd_milter.c b/src/smtpd/smtpd_milter.c
new file mode 100644
index 0000000..5deba67
--- /dev/null
+++ b/src/smtpd/smtpd_milter.c
@@ -0,0 +1,229 @@
+/*++
+/* NAME
+/* smtpd_milter 3
+/* SUMMARY
+/* SMTP server milter glue
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_milter.h>
+/*
+/* const char *smtpd_milter_eval(name, context)
+/* const char *name;
+/* void *context;
+/* DESCRIPTION
+/* smtpd_milter_eval() is a milter(3) call-back routine to
+/* expand Sendmail macros before they are sent to filters.
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal errors: out of memory.
+/* internal protocol errors.
+/* 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>
+
+/* Utility library. */
+
+#include <split_at.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <quote_821_local.h>
+
+/* Milter library. */
+
+#include <milter.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+#include <smtpd_sasl_glue.h>
+#include <smtpd_resolve.h>
+#include <smtpd_milter.h>
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* smtpd_milter_eval - evaluate milter macro */
+
+const char *smtpd_milter_eval(const char *name, void *ptr)
+{
+ SMTPD_STATE *state = (SMTPD_STATE *) ptr;
+ const RESOLVE_REPLY *reply;
+ char *cp;
+
+ /*
+ * On-the-fly initialization.
+ */
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(10);
+
+ /*
+ * System macros.
+ */
+ if (strcmp(name, S8_MAC_DAEMON_NAME) == 0)
+ return (var_milt_daemon_name);
+ if (strcmp(name, S8_MAC_V) == 0)
+ return (var_milt_v);
+
+ /*
+ * Connect macros.
+ */
+ if (strcmp(name, S8_MAC__) == 0) {
+ vstring_sprintf(state->expand_buf, "%s [%s]",
+ state->reverse_name, state->addr);
+ if (strcasecmp_utf8(state->name, state->reverse_name) != 0)
+ vstring_strcat(state->expand_buf, " (may be forged)");
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_J) == 0)
+ return (var_myhostname);
+ if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0)
+ return (state->rfc_addr);
+ if (strcmp(name, S8_MAC_CLIENT_PORT) == 0)
+ return (strcmp(state->port, CLIENT_PORT_UNKNOWN) ? state->port : "0");
+ if (strcmp(name, S8_MAC_CLIENT_CONN) == 0) {
+ vstring_sprintf(state->expand_buf, "%d", state->conn_count);
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_CLIENT_NAME) == 0)
+ return (state->name);
+ if (strcmp(name, S8_MAC_CLIENT_PTR) == 0)
+ return (state->reverse_name);
+ if (strcmp(name, S8_MAC_CLIENT_RES) == 0)
+ return (state->name_status == SMTPD_PEER_CODE_OK ? "OK" :
+ state->name_status == SMTPD_PEER_CODE_FORGED ? "FORGED" :
+ state->name_status == SMTPD_PEER_CODE_TEMP ? "TEMP" : "FAIL");
+
+ if (strcmp(name, S8_MAC_DAEMON_ADDR) == 0)
+ return (state->dest_addr);
+ if (strcmp(name, S8_MAC_DAEMON_PORT) == 0)
+ return (state->dest_port);
+
+ /*
+ * HELO macros.
+ */
+#ifdef USE_TLS
+#define IF_ENCRYPTED(x) (state->tls_context ? (x) : 0)
+#define IF_TRUSTED(x) (TLS_CERT_IS_TRUSTED(state->tls_context) ? (x) : 0)
+
+ if (strcmp(name, S8_MAC_TLS_VERSION) == 0)
+ return (IF_ENCRYPTED(state->tls_context->protocol));
+ if (strcmp(name, S8_MAC_CIPHER) == 0)
+ return (IF_ENCRYPTED(state->tls_context->cipher_name));
+ if (strcmp(name, S8_MAC_CIPHER_BITS) == 0) {
+ if (state->tls_context == 0)
+ return (0);
+ vstring_sprintf(state->expand_buf, "%d",
+ IF_ENCRYPTED(state->tls_context->cipher_usebits));
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_CERT_SUBJECT) == 0)
+ return (IF_TRUSTED(state->tls_context->peer_CN));
+ if (strcmp(name, S8_MAC_CERT_ISSUER) == 0)
+ return (IF_TRUSTED(state->tls_context->issuer_CN));
+#endif
+
+ /*
+ * MAIL FROM macros.
+ */
+#define IF_SASL_ENABLED(s) ((s) ? (s) : 0)
+
+ if (strcmp(name, S8_MAC_I) == 0)
+ return (state->queue_id);
+#ifdef USE_SASL_AUTH
+ if (strcmp(name, S8_MAC_AUTH_TYPE) == 0)
+ return (IF_SASL_ENABLED(state->sasl_method));
+ if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0)
+ return (IF_SASL_ENABLED(state->sasl_username));
+ if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0)
+ return (IF_SASL_ENABLED(state->sasl_sender));
+#endif
+ if (strcmp(name, S8_MAC_MAIL_ADDR) == 0) {
+ if (state->sender == 0)
+ return (0);
+ if (state->sender[0] == 0)
+ return ("");
+ reply = smtpd_resolve_addr(state->recipient, state->sender);
+ /* Sendmail 8.13 does not externalize the null string. */
+ if (STR(reply->recipient)[0])
+ quote_821_local(state->expand_buf, STR(reply->recipient));
+ else
+ vstring_strcpy(state->expand_buf, STR(reply->recipient));
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_MAIL_HOST) == 0) {
+ if (state->sender == 0)
+ return (0);
+ reply = smtpd_resolve_addr(state->recipient, state->sender);
+ return (STR(reply->nexthop));
+ }
+ if (strcmp(name, S8_MAC_MAIL_MAILER) == 0) {
+ if (state->sender == 0)
+ return (0);
+ reply = smtpd_resolve_addr(state->recipient, state->sender);
+ return (STR(reply->transport));
+ }
+
+ /*
+ * RCPT TO macros.
+ */
+ if (strcmp(name, S8_MAC_RCPT_ADDR) == 0) {
+ if (state->recipient == 0)
+ return (0);
+ if (state->recipient[0] == 0)
+ return ("");
+ if (state->milter_reject_text) {
+ /* 554 5.7.1 <user@example.com>: Relay access denied */
+ vstring_strcpy(state->expand_buf, state->milter_reject_text + 4);
+ cp = split_at(STR(state->expand_buf), ' ');
+ return (cp ? split_at(cp, ' ') : cp);
+ }
+ reply = smtpd_resolve_addr(state->sender, state->recipient);
+ /* Sendmail 8.13 does not externalize the null string. */
+ if (STR(reply->recipient)[0])
+ quote_821_local(state->expand_buf, STR(reply->recipient));
+ else
+ vstring_strcpy(state->expand_buf, STR(reply->recipient));
+ return (STR(state->expand_buf));
+ }
+ if (strcmp(name, S8_MAC_RCPT_HOST) == 0) {
+ if (state->recipient == 0)
+ return (0);
+ if (state->milter_reject_text) {
+ /* 554 5.7.1 <user@example.com>: Relay access denied */
+ vstring_strcpy(state->expand_buf, state->milter_reject_text + 4);
+ (void) split_at(STR(state->expand_buf), ' ');
+ return (STR(state->expand_buf));
+ }
+ reply = smtpd_resolve_addr(state->sender, state->recipient);
+ return (STR(reply->nexthop));
+ }
+ if (strcmp(name, S8_MAC_RCPT_MAILER) == 0) {
+ if (state->recipient == 0)
+ return (0);
+ if (state->milter_reject_text)
+ return (S8_RCPT_MAILER_ERROR);
+ reply = smtpd_resolve_addr(state->sender, state->recipient);
+ return (STR(reply->transport));
+ }
+ return (0);
+}
diff --git a/src/smtpd/smtpd_milter.h b/src/smtpd/smtpd_milter.h
new file mode 100644
index 0000000..4006bde
--- /dev/null
+++ b/src/smtpd/smtpd_milter.h
@@ -0,0 +1,27 @@
+/*++
+/* NAME
+/* smtpd_milter 3h
+/* SUMMARY
+/* SMTP server milter glue
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_milter.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern const char *smtpd_milter_eval(const char *, void *);
+
+/* 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/smtpd/smtpd_nullmx.in b/src/smtpd/smtpd_nullmx.in
new file mode 100644
index 0000000..9db8276
--- /dev/null
+++ b/src/smtpd/smtpd_nullmx.in
@@ -0,0 +1,58 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+#smtpd_delay_reject 0
+#mynetworks 127.0.0.0/8,168.100.189.0/28
+#relay_domains porcupine.org
+#maps_rbl_domains dnsbltest.porcupine.org
+#rbl_reply_maps hash:smtpd_check_access
+#helo foobar
+#
+# reject_unknown_helo_hostname
+#
+smtpd_delay_reject 0
+helo_restrictions reject_unknown_helo_hostname
+client spike.porcupine.org 168.100.189.2
+mail sname@sdomain
+rcpt rname@rdomain
+helo nxdomain.porcupine.org
+helo nullmx.porcupine.org
+helo spike.porcupine.org
+#
+# reject_unknown_sender_domain
+#
+smtpd_delay_reject 0
+sender_restrictions reject_unknown_sender_domain
+client spike.porcupine.org 168.100.189.2
+helo spike.porcupine.org
+rcpt rname@rdomain
+mail sname@nxdomain.porcupine.org
+mail sname@nullmx.porcupine.org
+mail sname@spike.porcupine.org
+#
+# reject_unknown_recipient_domain
+#
+smtpd_delay_reject 0
+sender_restrictions permit
+recipient_restrictions reject_unknown_recipient_domain
+relay_restrictions reject_unauth_destination
+client spike.porcupine.org 168.100.189.2
+helo spike.porcupine.org
+mail sname@sdomain
+relay_domains nxdomain.porcupine.org
+rcpt rname@nxdomain.porcupine.org
+relay_domains nullmx.porcupine.org
+rcpt rname@nullmx.porcupine.org
+relay_domains spike.porcupine.org
+rcpt rname@spike.porcupine.org
+#
+# check_mx_access
+#
+smtpd_delay_reject 0
+sender_restrictions check_sender_mx_access,hash:smtpd_check_access
+client spike.porcupine.org 168.100.189.2
+mail sname@nxdomain.porcupine.org
+mail sname@nullmx.porcupine.org
+mail sname@spike.porcupine.org
diff --git a/src/smtpd/smtpd_nullmx.ref b/src/smtpd/smtpd_nullmx.ref
new file mode 100644
index 0000000..4cc258e
--- /dev/null
+++ b/src/smtpd/smtpd_nullmx.ref
@@ -0,0 +1,100 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> #smtpd_delay_reject 0
+>>> #mynetworks 127.0.0.0/8,168.100.189.0/28
+>>> #relay_domains porcupine.org
+>>> #maps_rbl_domains dnsbltest.porcupine.org
+>>> #rbl_reply_maps hash:smtpd_check_access
+>>> #helo foobar
+>>> #
+>>> # reject_unknown_helo_hostname
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> helo_restrictions reject_unknown_helo_hostname
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> mail sname@sdomain
+OK
+>>> rcpt rname@rdomain
+OK
+>>> helo nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found; from=<sname@sdomain> proto=SMTP helo=<nxdomain.porcupine.org>
+450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found
+>>> helo nullmx.porcupine.org
+OK
+>>> helo spike.porcupine.org
+OK
+>>> #
+>>> # reject_unknown_sender_domain
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> sender_restrictions reject_unknown_sender_domain
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> helo spike.porcupine.org
+OK
+>>> rcpt rname@rdomain
+OK
+>>> mail sname@nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 450 4.1.8 <sname@nxdomain.porcupine.org>: Sender address rejected: Domain not found; from=<sname@nxdomain.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+450 4.1.8 <sname@nxdomain.porcupine.org>: Sender address rejected: Domain not found
+>>> mail sname@nullmx.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 550 5.7.27 <sname@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<sname@nullmx.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+550 5.7.27 <sname@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX)
+>>> mail sname@spike.porcupine.org
+OK
+>>> #
+>>> # reject_unknown_recipient_domain
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> sender_restrictions permit
+OK
+>>> recipient_restrictions reject_unknown_recipient_domain
+OK
+>>> relay_restrictions reject_unauth_destination
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> helo spike.porcupine.org
+OK
+>>> mail sname@sdomain
+OK
+>>> relay_domains nxdomain.porcupine.org
+OK
+>>> rcpt rname@nxdomain.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 450 4.1.2 <rname@nxdomain.porcupine.org>: Recipient address rejected: Domain not found; from=<sname@sdomain> to=<rname@nxdomain.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+450 4.1.2 <rname@nxdomain.porcupine.org>: Recipient address rejected: Domain not found
+>>> relay_domains nullmx.porcupine.org
+OK
+>>> rcpt rname@nullmx.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 556 5.1.10 <rname@nullmx.porcupine.org>: Recipient address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<sname@sdomain> to=<rname@nullmx.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+556 5.1.10 <rname@nullmx.porcupine.org>: Recipient address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX)
+>>> relay_domains spike.porcupine.org
+OK
+>>> rcpt rname@spike.porcupine.org
+OK
+>>> #
+>>> # check_mx_access
+>>> #
+>>> smtpd_delay_reject 0
+OK
+>>> sender_restrictions check_sender_mx_access,hash:smtpd_check_access
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> mail sname@nxdomain.porcupine.org
+./smtpd_check: warning: Unable to look up MX host nxdomain.porcupine.org for Sender address sname@nxdomain.porcupine.org: hostname nor servname provided, or not known
+OK
+>>> mail sname@nullmx.porcupine.org
+OK
+>>> mail sname@spike.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <sname@spike.porcupine.org>: Sender address rejected: ns or mx server spike.porcupine.org; from=<sname@spike.porcupine.org> proto=SMTP helo=<spike.porcupine.org>
+554 5.7.1 <sname@spike.porcupine.org>: Sender address rejected: ns or mx server spike.porcupine.org
diff --git a/src/smtpd/smtpd_peer.c b/src/smtpd/smtpd_peer.c
new file mode 100644
index 0000000..7a48f85
--- /dev/null
+++ b/src/smtpd/smtpd_peer.c
@@ -0,0 +1,659 @@
+/*++
+/* NAME
+/* smtpd_peer 3
+/* SUMMARY
+/* look up peer name/address information
+/* SYNOPSIS
+/* #include "smtpd.h"
+/*
+/* void smtpd_peer_init(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_peer_reset(state)
+/* SMTPD_STATE *state;
+/* AUXILIARY METHODS
+/* void smtpd_peer_from_default(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* The smtpd_peer_init() routine attempts to produce a printable
+/* version of the peer name and address of the specified socket.
+/* Where information is unavailable, the name and/or address
+/* are set to "unknown".
+/*
+/* Alternatively, the peer address and port may be obtained
+/* from a proxy server.
+/*
+/* This module uses the local name service via getaddrinfo()
+/* and getnameinfo(). It does not query the DNS directly.
+/*
+/* smtpd_peer_init() updates the following fields:
+/* .IP name
+/* The verified client hostname. This name is represented by
+/* the string "unknown" when 1) the address->name lookup failed,
+/* 2) the name->address mapping fails, or 3) the name->address
+/* mapping does not produce the client IP address.
+/* .IP reverse_name
+/* The unverified client hostname as found with address->name
+/* lookup; it is not verified for consistency with the client
+/* IP address result from name->address lookup.
+/* .IP forward_name
+/* The unverified client hostname as found with address->name
+/* lookup followed by name->address lookup; it is not verified
+/* for consistency with the result from address->name lookup.
+/* For example, when the address->name lookup produces as
+/* hostname an alias, the name->address lookup will produce
+/* as hostname the expansion of that alias, so that the two
+/* lookups produce different names.
+/* .IP addr
+/* Printable representation of the client address.
+/* .IP namaddr
+/* String of the form: "name[addr]:port".
+/* .IP rfc_addr
+/* String of the form "ipv4addr" or "ipv6:ipv6addr" for use
+/* in Received: message headers.
+/* .IP dest_addr
+/* Server address, used by the Dovecot authentication server,
+/* available as Milter {daemon_addr} macro, and as server_address
+/* policy delegation attribute.
+/* .IP dest_port
+/* Server port, available as Milter {daemon_port} macro, and
+/* as server_port policy delegation attribute.
+/* .IP name_status
+/* The name_status result field specifies how the name
+/* information should be interpreted:
+/* .RS
+/* .IP 2
+/* The address->name lookup and name->address lookup produced
+/* the client IP address.
+/* .IP 4
+/* The address->name lookup or name->address lookup failed
+/* with a recoverable error.
+/* .IP 5
+/* The address->name lookup or name->address lookup failed
+/* with an unrecoverable error, or the result did not match
+/* the client IP address.
+/* .RE
+/* .IP reverse_name_status
+/* The reverse_name_status result field specifies how the
+/* reverse_name information should be interpreted:
+/* .RS
+/* .IP 2
+/* The address->name lookup succeeded.
+/* .IP 4
+/* The address->name lookup failed with a recoverable error.
+/* .IP 5
+/* The address->name lookup failed with an unrecoverable error.
+/* .RE
+/* .IP forward_name_status
+/* The forward_name_status result field specifies how the
+/* forward_name information should be interpreted:
+/* .RS
+/* .IP 2
+/* The address->name and name->address lookup succeeded.
+/* .IP 4
+/* The address->name lookup or name->address failed with a
+/* recoverable error.
+/* .IP 5
+/* The address->name lookup or name->address failed with an
+/* unrecoverable error.
+/* .RE
+/* .PP
+/* smtpd_peer_reset() releases memory allocated by smtpd_peer_init().
+/*
+/* smtpd_peer_from_default() looks up connection information
+/* when an up-stream proxy indicates that a connection is not
+/* proxied.
+/* 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 <stdio.h> /* strerror() */
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+#include <htable.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <valid_mailhost_addr.h>
+#include <mail_params.h>
+#include <haproxy_srvr.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+
+static INET_PROTO_INFO *proto_info;
+
+ /*
+ * XXX If we make local port information available via logging, then we must
+ * also support these attributes with the XFORWARD command.
+ *
+ * XXX If support were to be added for Milter applications in down-stream MTAs,
+ * then consistency demands that we propagate a lot of Sendmail macro
+ * information via the XFORWARD command. Otherwise we could end up with a
+ * very confusing situation.
+ */
+
+/* smtpd_peer_sockaddr_to_hostaddr - client address/port to printable form */
+
+static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_peer_sockaddr_to_hostaddr";
+ struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr);
+ SOCKADDR_SIZE sa_length = state->sockaddr_len;
+
+ /*
+ * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd,
+ * while Postfix IPv6 (or IPv4) support is turned off, don't (skip to the
+ * final else clause, pretend the origin is localhost[127.0.0.1], and
+ * become an open relay).
+ */
+ if (sa->sa_family == AF_INET
+#ifdef AF_INET6
+ || sa->sa_family == AF_INET6
+#endif
+ ) {
+ MAI_HOSTADDR_STR client_addr;
+ MAI_SERVPORT_STR client_port;
+ MAI_HOSTADDR_STR server_addr;
+ MAI_SERVPORT_STR server_port;
+ int aierr;
+ char *colonp;
+
+ /*
+ * Sanity check: we can't use sockets that we're not configured for.
+ */
+ if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0)
+ msg_fatal("cannot handle socket type %s with \"%s = %s\"",
+#ifdef AF_INET6
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
+#endif
+ sa->sa_family == AF_INET ? "AF_INET" :
+ "other", VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * Sorry, but there are some things that we just cannot do while
+ * connected to the network.
+ */
+ if (geteuid() != var_owner_uid || getuid() != var_owner_uid) {
+ msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu",
+ (unsigned long) getuid(), (unsigned long) geteuid());
+ msg_fatal("the Postfix SMTP server must run with $%s privileges",
+ VAR_MAIL_OWNER);
+ }
+
+ /*
+ * Convert the client address to printable form.
+ */
+ if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr,
+ &client_port, 0)) != 0)
+ msg_fatal("%s: cannot convert client address/port to string: %s",
+ myname, MAI_STRERROR(aierr));
+ state->port = mystrdup(client_port.buf);
+
+ /*
+ * XXX Require that the infrastructure strips off the IPv6 datalink
+ * suffix to avoid false alarms with strict address syntax checks.
+ */
+#ifdef HAS_IPV6
+ if (strchr(client_addr.buf, '%') != 0)
+ msg_panic("%s: address %s has datalink suffix",
+ myname, client_addr.buf);
+#endif
+
+ /*
+ * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on,
+ * but only if IPv4 support is enabled (why would anyone want to turn
+ * it off)? With IPv4 support enabled we have no need for the IPv6
+ * form in logging, hostname verification and access checks.
+ */
+#ifdef HAS_IPV6
+ if (sa->sa_family == AF_INET6) {
+ if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0
+ && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa))
+ && (colonp = strrchr(client_addr.buf, ':')) != 0) {
+ struct addrinfo *res0;
+
+ if (msg_verbose > 1)
+ msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"",
+ myname, client_addr.buf, colonp + 1);
+
+ state->addr = mystrdup(colonp + 1);
+ state->rfc_addr = mystrdup(colonp + 1);
+ state->addr_family = AF_INET;
+ aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0);
+ if (aierr)
+ msg_fatal("%s: cannot convert %s from string to binary: %s",
+ myname, state->addr, MAI_STRERROR(aierr));
+ sa_length = res0->ai_addrlen;
+ if (sa_length > sizeof(state->sockaddr))
+ sa_length = sizeof(state->sockaddr);
+ memcpy((void *) sa, res0->ai_addr, sa_length);
+ freeaddrinfo(res0); /* 200412 */
+ }
+
+ /*
+ * Following RFC 2821 section 4.1.3, an IPv6 address literal gets
+ * a prefix of 'IPv6:'. We do this consistently for all IPv6
+ * addresses that that appear in headers or envelopes. The fact
+ * that valid_mailhost_addr() enforces the form helps of course.
+ * We use the form without IPV6: prefix when doing access
+ * control, or when accessing the connection cache.
+ */
+ else {
+ state->addr = mystrdup(client_addr.buf);
+ state->rfc_addr =
+ concatenate(IPV6_COL, client_addr.buf, (char *) 0);
+ state->addr_family = sa->sa_family;
+ }
+ }
+
+ /*
+ * An IPv4 address is in dotted quad decimal form.
+ */
+ else
+#endif
+ {
+ state->addr = mystrdup(client_addr.buf);
+ state->rfc_addr = mystrdup(client_addr.buf);
+ state->addr_family = sa->sa_family;
+ }
+
+ /*
+ * Convert the server address/port to printable form.
+ */
+ if ((aierr = sockaddr_to_hostaddr((struct sockaddr *)
+ &state->dest_sockaddr,
+ state->dest_sockaddr_len,
+ &server_addr,
+ &server_port, 0)) != 0)
+ msg_fatal("%s: cannot convert server address/port to string: %s",
+ myname, MAI_STRERROR(aierr));
+ /* TODO: convert IPv4-in-IPv6 to IPv4 form. */
+ state->dest_addr = mystrdup(server_addr.buf);
+ state->dest_port = mystrdup(server_port.buf);
+
+ return (0);
+ }
+
+ /*
+ * It's not Internet.
+ */
+ else {
+ return (-1);
+ }
+}
+
+/* smtpd_peer_sockaddr_to_hostname - client hostname lookup */
+
+static void smtpd_peer_sockaddr_to_hostname(SMTPD_STATE *state)
+{
+ struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr);
+ SOCKADDR_SIZE sa_length = state->sockaddr_len;
+ MAI_HOSTNAME_STR client_name;
+ int aierr;
+
+ /*
+ * Look up and sanity check the client hostname.
+ *
+ * It is unsafe to allow numeric hostnames, especially because there exists
+ * pressure to turn off the name->addr double check. In that case an
+ * attacker could trivally bypass access restrictions.
+ *
+ * sockaddr_to_hostname() already rejects malformed or numeric names.
+ */
+#define TEMP_AI_ERROR(e) \
+ ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
+
+#define REJECT_PEER_NAME(state, code) { \
+ myfree(state->name); \
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN); \
+ state->name_status = code; \
+ }
+
+ if (var_smtpd_peername_lookup == 0) {
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->name_status = SMTPD_PEER_CODE_PERM;
+ state->reverse_name_status = SMTPD_PEER_CODE_PERM;
+ } else if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name,
+ (MAI_SERVNAME_STR *) 0, 0)) != 0) {
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->name_status = (TEMP_AI_ERROR(aierr) ?
+ SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM);
+ state->reverse_name_status = (TEMP_AI_ERROR(aierr) ?
+ SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM);
+ } else {
+ struct addrinfo *res0;
+ struct addrinfo *res;
+
+ state->name = mystrdup(client_name.buf);
+ state->reverse_name = mystrdup(client_name.buf);
+ state->name_status = SMTPD_PEER_CODE_OK;
+ state->reverse_name_status = SMTPD_PEER_CODE_OK;
+
+ /*
+ * Reject the hostname if it does not list the peer address. Without
+ * further validation or qualification, such information must not be
+ * allowed to enter the audit trail, as people would draw false
+ * conclusions.
+ */
+ aierr = hostname_to_sockaddr_pf(state->name, state->addr_family,
+ (char *) 0, 0, &res0);
+ if (aierr) {
+ msg_warn("hostname %s does not resolve to address %s: %s",
+ state->name, state->addr, MAI_STRERROR(aierr));
+ REJECT_PEER_NAME(state, (TEMP_AI_ERROR(aierr) ?
+ SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_FORGED));
+ } else {
+ for (res = res0; /* void */ ; res = res->ai_next) {
+ if (res == 0) {
+ msg_warn("hostname %s does not resolve to address %s",
+ state->name, state->addr);
+ REJECT_PEER_NAME(state, SMTPD_PEER_CODE_FORGED);
+ break;
+ }
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, state->name);
+ continue;
+ }
+ if (sock_addr_cmp_addr(res->ai_addr, sa) == 0)
+ break; /* keep peer name */
+ }
+ freeaddrinfo(res0);
+ }
+ }
+}
+
+/* smtpd_peer_hostaddr_to_sockaddr - convert numeric string to binary */
+
+static void smtpd_peer_hostaddr_to_sockaddr(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_peer_hostaddr_to_sockaddr";
+ struct addrinfo *res;
+ int aierr;
+
+ if ((aierr = hostaddr_to_sockaddr(state->addr, state->port,
+ SOCK_STREAM, &res)) != 0)
+ msg_fatal("%s: cannot convert client address/port to string: %s",
+ myname, MAI_STRERROR(aierr));
+ if (res->ai_addrlen > sizeof(state->sockaddr))
+ msg_panic("%s: address length > struct sockaddr_storage", myname);
+ memcpy((void *) &(state->sockaddr), res->ai_addr, res->ai_addrlen);
+ state->sockaddr_len = res->ai_addrlen;
+ freeaddrinfo(res);
+}
+
+/* smtpd_peer_not_inet - non-socket or non-Internet endpoint */
+
+static void smtpd_peer_not_inet(SMTPD_STATE *state)
+{
+
+ /*
+ * If it's not Internet, assume the client is local, and avoid using the
+ * naming service because that can hang when the machine is disconnected.
+ */
+ state->name = mystrdup("localhost");
+ state->reverse_name = mystrdup("localhost");
+#ifdef AF_INET6
+ if (proto_info->sa_family_list[0] == PF_INET6) {
+ state->addr = mystrdup("::1"); /* XXX bogus. */
+ state->rfc_addr = mystrdup(IPV6_COL "::1"); /* XXX bogus. */
+ } else
+#endif
+ {
+ state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */
+ state->rfc_addr = mystrdup("127.0.0.1");/* XXX bogus. */
+ }
+ state->addr_family = AF_UNSPEC;
+ state->name_status = SMTPD_PEER_CODE_OK;
+ state->reverse_name_status = SMTPD_PEER_CODE_OK;
+ state->port = mystrdup("0"); /* XXX bogus. */
+
+ state->dest_addr = mystrdup(state->addr); /* XXX bogus. */
+ state->dest_port = mystrdup(state->port); /* XXX bogus. */
+}
+
+/* smtpd_peer_no_client - peer went away, or peer info unavailable */
+
+static void smtpd_peer_no_client(SMTPD_STATE *state)
+{
+ smtpd_peer_reset(state);
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->addr_family = AF_UNSPEC;
+ state->name_status = SMTPD_PEER_CODE_PERM;
+ state->reverse_name_status = SMTPD_PEER_CODE_PERM;
+ state->port = mystrdup(CLIENT_PORT_UNKNOWN);
+
+ state->dest_addr = mystrdup(SERVER_ADDR_UNKNOWN);
+ state->dest_port = mystrdup(SERVER_PORT_UNKNOWN);
+}
+
+/* smtpd_peer_from_pass_attr - initialize from attribute hash */
+
+static void smtpd_peer_from_pass_attr(SMTPD_STATE *state)
+{
+ HTABLE *attr = (HTABLE *) vstream_context(state->client);
+ const char *cp;
+
+ /*
+ * Extract the client endpoint information from the attribute hash.
+ */
+ if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_ADDR)) == 0)
+ msg_fatal("missing client address from proxy");
+ if (strrchr(cp, ':') != 0) {
+ if (valid_ipv6_hostaddr(cp, DO_GRIPE) == 0)
+ msg_fatal("bad IPv6 client address syntax from proxy: %s", cp);
+ state->addr = mystrdup(cp);
+ state->rfc_addr = concatenate(IPV6_COL, cp, (char *) 0);
+ state->addr_family = AF_INET6;
+ } else {
+ if (valid_ipv4_hostaddr(cp, DO_GRIPE) == 0)
+ msg_fatal("bad IPv4 client address syntax from proxy: %s", cp);
+ state->addr = mystrdup(cp);
+ state->rfc_addr = mystrdup(cp);
+ state->addr_family = AF_INET;
+ }
+ if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_PORT)) == 0)
+ msg_fatal("missing client port from proxy");
+ if (valid_hostport(cp, DO_GRIPE) == 0)
+ msg_fatal("bad TCP client port number syntax from proxy: %s", cp);
+ state->port = mystrdup(cp);
+
+ /*
+ * The Dovecot authentication server needs the server IP address.
+ */
+ if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_ADDR)) == 0)
+ msg_fatal("missing server address from proxy");
+ if (valid_hostaddr(cp, DO_GRIPE) == 0)
+ msg_fatal("bad IPv6 server address syntax from proxy: %s", cp);
+ state->dest_addr = mystrdup(cp);
+
+ if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_PORT)) == 0)
+ msg_fatal("missing server port from proxy");
+ if (valid_hostport(cp, DO_GRIPE) == 0)
+ msg_fatal("bad TCP server port number syntax from proxy: %s", cp);
+ state->dest_port = mystrdup(cp);
+
+ /*
+ * Convert the client address from string to binary form.
+ */
+ smtpd_peer_hostaddr_to_sockaddr(state);
+}
+
+/* smtpd_peer_from_default - try to initialize peer information from socket */
+
+void smtpd_peer_from_default(SMTPD_STATE *state)
+{
+
+ /*
+ * The "no client" routine provides surrogate information so that the
+ * application can produce sensible logging when a client disconnects
+ * before the server wakes up. The "not inet" routine provides surrogate
+ * state for (presumably) local IPC channels.
+ */
+ state->sockaddr_len = sizeof(state->sockaddr);
+ state->dest_sockaddr_len = sizeof(state->dest_sockaddr);
+ if (getpeername(vstream_fileno(state->client),
+ (struct sockaddr *) &state->sockaddr,
+ &state->sockaddr_len) <0
+ || getsockname(vstream_fileno(state->client),
+ (struct sockaddr *) &state->dest_sockaddr,
+ &state->dest_sockaddr_len) < 0) {
+ if (errno == ENOTSOCK)
+ smtpd_peer_not_inet(state);
+ else
+ smtpd_peer_no_client(state);
+ } else {
+ if (smtpd_peer_sockaddr_to_hostaddr(state) < 0)
+ smtpd_peer_not_inet(state);
+ }
+}
+
+/* smtpd_peer_from_proxy - get endpoint info from proxy agent */
+
+static void smtpd_peer_from_proxy(SMTPD_STATE *state)
+{
+ typedef struct {
+ const char *name;
+ int (*endpt_lookup) (SMTPD_STATE *);
+ } SMTPD_ENDPT_LOOKUP_INFO;
+ static const SMTPD_ENDPT_LOOKUP_INFO smtpd_endpt_lookup_info[] = {
+ HAPROXY_PROTO_NAME, smtpd_peer_from_haproxy,
+ 0,
+ };
+ const SMTPD_ENDPT_LOOKUP_INFO *pp;
+
+ /*
+ * When the proxy information is unavailable, we can't maintain an audit
+ * trail or enforce access control, therefore we forcibly hang up.
+ */
+ for (pp = smtpd_endpt_lookup_info; /* see below */ ; pp++) {
+ if (pp->name == 0)
+ msg_fatal("unsupported %s value: %s",
+ VAR_SMTPD_UPROXY_PROTO, var_smtpd_uproxy_proto);
+ if (strcmp(var_smtpd_uproxy_proto, pp->name) == 0)
+ break;
+ }
+ if (pp->endpt_lookup(state) < 0) {
+ smtpd_peer_from_default(state);
+ state->flags |= SMTPD_FLAG_HANGUP;
+ } else {
+ smtpd_peer_hostaddr_to_sockaddr(state);
+ }
+}
+
+/* smtpd_peer_init - initialize peer information */
+
+void smtpd_peer_init(SMTPD_STATE *state)
+{
+
+ /*
+ * Initialize.
+ */
+ if (proto_info == 0)
+ proto_info = inet_proto_info();
+
+ /*
+ * Prepare for partial initialization after error.
+ */
+ memset((void *) &(state->sockaddr), 0, sizeof(state->sockaddr));
+ state->sockaddr_len = 0;
+ state->name = 0;
+ state->reverse_name = 0;
+ state->addr = 0;
+ state->namaddr = 0;
+ state->rfc_addr = 0;
+ state->port = 0;
+ state->dest_addr = 0;
+ state->dest_port = 0;
+
+ /*
+ * Determine the remote SMTP client address and port.
+ *
+ * XXX In stand-alone mode, don't assume that the peer will be a local
+ * process. That could introduce a gaping hole when the SMTP daemon is
+ * hooked up to the network via inetd or some other super-server.
+ */
+ if (vstream_context(state->client) != 0) {
+ smtpd_peer_from_pass_attr(state);
+ if (*var_smtpd_uproxy_proto != 0)
+ msg_warn("ignoring non-empty %s setting behind postscreen",
+ VAR_SMTPD_UPROXY_PROTO);
+ } else if (SMTPD_STAND_ALONE(state) || *var_smtpd_uproxy_proto == 0) {
+ smtpd_peer_from_default(state);
+ } else {
+ smtpd_peer_from_proxy(state);
+ }
+
+ /*
+ * Determine the remote SMTP client hostname. Note: some of the handlers
+ * above provide surrogate endpoint information in case of error. In that
+ * case, leave the surrogate information alone.
+ */
+ if (state->name == 0)
+ smtpd_peer_sockaddr_to_hostname(state);
+
+ /*
+ * Do the name[addr]:port formatting for pretty reports.
+ */
+ state->namaddr = SMTPD_BUILD_NAMADDRPORT(state->name, state->addr,
+ state->port);
+}
+
+/* smtpd_peer_reset - destroy peer information */
+
+void smtpd_peer_reset(SMTPD_STATE *state)
+{
+ if (state->name)
+ myfree(state->name);
+ if (state->reverse_name)
+ myfree(state->reverse_name);
+ if (state->addr)
+ myfree(state->addr);
+ if (state->namaddr)
+ myfree(state->namaddr);
+ if (state->rfc_addr)
+ myfree(state->rfc_addr);
+ if (state->port)
+ myfree(state->port);
+ if (state->dest_addr)
+ myfree(state->dest_addr);
+ if (state->dest_port)
+ myfree(state->dest_port);
+}
diff --git a/src/smtpd/smtpd_proxy.c b/src/smtpd/smtpd_proxy.c
new file mode 100644
index 0000000..1c1f9fc
--- /dev/null
+++ b/src/smtpd/smtpd_proxy.c
@@ -0,0 +1,1171 @@
+/*++
+/* NAME
+/* smtpd_proxy 3
+/* SUMMARY
+/* SMTP server pass-through proxy client
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_proxy.h>
+/*
+/* typedef struct {
+/* .in +4
+/* VSTREAM *stream; /* SMTP proxy or replay log */
+/* VSTRING *buffer; /* last SMTP proxy response */
+/* /* other fields... */
+/* .in -4
+/* } SMTPD_PROXY;
+/*
+/* int smtpd_proxy_create(state, flags, service, timeout,
+/* ehlo_name, mail_from)
+/* SMTPD_STATE *state;
+/* int flags;
+/* const char *service;
+/* int timeout;
+/* const char *ehlo_name;
+/* const char *mail_from;
+/*
+/* int proxy->cmd(state, expect, format, ...)
+/* SMTPD_PROXY *proxy;
+/* SMTPD_STATE *state;
+/* int expect;
+/* const char *format;
+/*
+/* void smtpd_proxy_free(state)
+/* SMTPD_STATE *state;
+/*
+/* int smtpd_proxy_parse_opts(param_name, param_val)
+/* const char *param_name;
+/* const char *param_val;
+/* RECORD-LEVEL ROUTINES
+/* int proxy->rec_put(proxy->stream, rec_type, data, len)
+/* SMTPD_PROXY *proxy;
+/* int rec_type;
+/* const char *data;
+/* ssize_t len;
+/*
+/* int proxy->rec_fprintf(proxy->stream, rec_type, format, ...)
+/* SMTPD_PROXY *proxy;
+/* int rec_type;
+/* cont char *format;
+/* DESCRIPTION
+/* The functions in this module implement a pass-through proxy
+/* client.
+/*
+/* In order to minimize the intrusiveness of pass-through
+/* proxying, 1) the proxy server must support the same MAIL
+/* FROM/RCPT syntax that Postfix supports, 2) the record-level
+/* routines for message content proxying have the same interface
+/* as the routines that are used for non-proxied mail.
+/*
+/* smtpd_proxy_create() takes a description of a before-queue
+/* filter. Depending on flags, it either arranges to buffer
+/* up commands and message content until the entire message
+/* is received, or it immediately connects to the proxy service,
+/* sends EHLO, sends client information with the XFORWARD
+/* command if possible, sends the MAIL FROM command, and
+/* receives the reply.
+/* A non-zero result value means trouble: either the proxy is
+/* unavailable, or it did not send the expected reply.
+/* All results are reported via the proxy->buffer field in a
+/* form that can be sent to the SMTP client. An unexpected
+/* 2xx or 3xx proxy server response is replaced by a generic
+/* error response to avoid support problems.
+/* In case of error, smtpd_proxy_create() updates the
+/* state->error_mask and state->err fields, and leaves the
+/* SMTPD_PROXY handle in an unconnected state. Destroy the
+/* handle after reporting the error reply in the proxy->buffer
+/* field.
+/*
+/* proxy->cmd() formats and either buffers up the command and
+/* expected response until the entire message is received, or
+/* it immediately sends the specified command to the proxy
+/* server, and receives the proxy server reply.
+/* A non-zero result value means trouble: either the proxy is
+/* unavailable, or it did not send the expected reply.
+/* All results are reported via the proxy->buffer field in a
+/* form that can be sent to the SMTP client. An unexpected
+/* 2xx or 3xx proxy server response is replaced by a generic
+/* error response to avoid support problems.
+/* In case of error, proxy->cmd() updates the state->error_mask
+/* and state->err fields.
+/*
+/* smtpd_proxy_free() destroys a proxy server handle and resets
+/* the state->proxy field.
+/*
+/* smtpd_proxy_parse_opts() parses main.cf processing options.
+/*
+/* proxy->rec_put() is a rec_put() clone that either buffers
+/* up arbitrary message content records until the entire message
+/* is received, or that immediately sends it to the proxy
+/* server.
+/* All data is expected to be in SMTP dot-escaped form.
+/* All errors are reported as a REC_TYPE_ERROR result value,
+/* with the state->error_mask, state->err and proxy-buffer
+/* fields given appropriate values.
+/*
+/* proxy->rec_fprintf() is a rec_fprintf() clone that formats
+/* message content and either buffers up the record until the
+/* entire message is received, or that immediately sends it
+/* to the proxy server.
+/* All data is expected to be in SMTP dot-escaped form.
+/* All errors are reported as a REC_TYPE_ERROR result value,
+/* with the state->error_mask, state->err and proxy-buffer
+/* fields given appropriate values.
+/*
+/* Arguments:
+/* .IP flags
+/* Zero, or SMTPD_PROXY_FLAG_SPEED_ADJUST to buffer up the entire
+/* message before contacting a before-queue content filter.
+/* Note: when this feature is requested, the before-queue
+/* filter MUST use the same 2xx, 4xx or 5xx reply code for all
+/* recipients of a multi-recipient message.
+/* .IP server
+/* The SMTP proxy server host:port. The host or host: part is optional.
+/* This argument is not duplicated.
+/* .IP timeout
+/* Time limit for connecting to the proxy server and for
+/* sending and receiving proxy server commands and replies.
+/* .IP ehlo_name
+/* The EHLO Hostname that will be sent to the proxy server.
+/* This argument is not duplicated.
+/* .IP mail_from
+/* The MAIL FROM command. This argument is not duplicated.
+/* .IP state
+/* SMTP server state.
+/* .IP expect
+/* Expected proxy server reply status code range. A warning is logged
+/* when an unexpected reply is received. Specify one of the following:
+/* .RS
+/* .IP SMTPD_PROX_WANT_OK
+/* The caller expects a reply in the 200 range.
+/* .IP SMTPD_PROX_WANT_MORE
+/* The caller expects a reply in the 300 range.
+/* .IP SMTPD_PROX_WANT_ANY
+/* The caller has no expectation. Do not warn for unexpected replies.
+/* .IP SMTPD_PROX_WANT_NONE
+/* Do not bother waiting for a reply.
+/* .RE
+/* .IP format
+/* A format string.
+/* .IP stream
+/* Connection to proxy server.
+/* .IP data
+/* Pointer to the content of one message content record.
+/* .IP len
+/* The length of a message content record.
+/* SEE ALSO
+/* smtpd(8) Postfix smtp server
+/* DIAGNOSTICS
+/* Panic: internal API violations.
+/*
+/* Fatal errors: memory allocation problem.
+/*
+/* Warnings: unexpected response from proxy server, unable
+/* to connect to proxy server, proxy server read/write error,
+/* proxy speed-adjust buffer read/write error.
+/* 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 <ctype.h>
+#include <unistd.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <connect.h>
+#include <name_code.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_error.h>
+#include <smtp_stream.h>
+#include <cleanup_user.h>
+#include <mail_params.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <xtext.h>
+#include <record.h>
+#include <mail_queue.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+#include <smtpd_proxy.h>
+
+ /*
+ * XFORWARD server features, recognized by the pass-through proxy client.
+ */
+#define SMTPD_PROXY_XFORWARD_NAME (1<<0) /* client name */
+#define SMTPD_PROXY_XFORWARD_ADDR (1<<1) /* client address */
+#define SMTPD_PROXY_XFORWARD_PROTO (1<<2) /* protocol */
+#define SMTPD_PROXY_XFORWARD_HELO (1<<3) /* client helo */
+#define SMTPD_PROXY_XFORWARD_IDENT (1<<4) /* message identifier */
+#define SMTPD_PROXY_XFORWARD_DOMAIN (1<<5) /* origin type */
+#define SMTPD_PROXY_XFORWARD_PORT (1<<6) /* client port */
+
+ /*
+ * Spead-matching: we use an unlinked file for transient storage.
+ */
+static VSTREAM *smtpd_proxy_replay_stream;
+
+ /*
+ * Forward declarations.
+ */
+static void smtpd_proxy_fake_server_reply(SMTPD_STATE *, int);
+static int smtpd_proxy_rdwr_error(SMTPD_STATE *, int);
+static int PRINTFLIKE(3, 4) smtpd_proxy_cmd(SMTPD_STATE *, int, const char *,...);
+static int smtpd_proxy_rec_put(VSTREAM *, int, const char *, ssize_t);
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define STREQ(x, y) (strcmp((x), (y)) == 0)
+
+/* smtpd_proxy_xforward_flush - flush forwarding information */
+
+static int smtpd_proxy_xforward_flush(SMTPD_STATE *state, VSTRING *buf)
+{
+ int ret;
+
+ if (VSTRING_LEN(buf) > 0) {
+ ret = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK,
+ XFORWARD_CMD "%s", STR(buf));
+ VSTRING_RESET(buf);
+ return (ret);
+ }
+ return (0);
+}
+
+/* smtpd_proxy_xforward_send - send forwarding information */
+
+static int smtpd_proxy_xforward_send(SMTPD_STATE *state, VSTRING *buf,
+ const char *name,
+ int value_available,
+ const char *value)
+{
+ size_t new_len;
+ int ret;
+
+#define CONSTR_LEN(s) (sizeof(s) - 1)
+#define PAYLOAD_LIMIT (512 - CONSTR_LEN("250 " XFORWARD_CMD "\r\n"))
+
+ if (!value_available)
+ value = XFORWARD_UNAVAILABLE;
+
+ /*
+ * Encode the attribute value.
+ */
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(100);
+ xtext_quote(state->expand_buf, value, "");
+
+ /*
+ * How much space does this attribute need? SPACE name = value.
+ */
+ new_len = strlen(name) + strlen(STR(state->expand_buf)) + 2;
+ if (new_len > PAYLOAD_LIMIT)
+ msg_warn("%s command payload %s=%.10s... exceeds SMTP protocol limit",
+ XFORWARD_CMD, name, value);
+
+ /*
+ * Flush the buffer if we need to, and store the attribute.
+ */
+ if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT)
+ if ((ret = smtpd_proxy_xforward_flush(state, buf)) < 0)
+ return (ret);
+ vstring_sprintf_append(buf, " %s=%s", name, STR(state->expand_buf));
+
+ return (0);
+}
+
+/* smtpd_proxy_connect - open proxy connection */
+
+static int smtpd_proxy_connect(SMTPD_STATE *state)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ int fd;
+ char *lines;
+ char *words;
+ VSTRING *buf;
+ int bad;
+ char *word;
+ static const NAME_CODE known_xforward_features[] = {
+ XFORWARD_NAME, SMTPD_PROXY_XFORWARD_NAME,
+ XFORWARD_ADDR, SMTPD_PROXY_XFORWARD_ADDR,
+ XFORWARD_PORT, SMTPD_PROXY_XFORWARD_PORT,
+ XFORWARD_PROTO, SMTPD_PROXY_XFORWARD_PROTO,
+ XFORWARD_HELO, SMTPD_PROXY_XFORWARD_HELO,
+ XFORWARD_IDENT, SMTPD_PROXY_XFORWARD_IDENT,
+ XFORWARD_DOMAIN, SMTPD_PROXY_XFORWARD_DOMAIN,
+ 0, 0,
+ };
+ int server_xforward_features;
+ int (*connect_fn) (const char *, int, int);
+ const char *endpoint;
+
+ /*
+ * Find connection method (default inet)
+ */
+ if (strncasecmp("unix:", proxy->service_name, 5) == 0) {
+ endpoint = proxy->service_name + 5;
+ connect_fn = unix_connect;
+ } else {
+ if (strncasecmp("inet:", proxy->service_name, 5) == 0)
+ endpoint = proxy->service_name + 5;
+ else
+ endpoint = proxy->service_name;
+ connect_fn = inet_connect;
+ }
+
+ /*
+ * Connect to proxy.
+ */
+ if ((fd = connect_fn(endpoint, BLOCKING, proxy->timeout)) < 0) {
+ msg_warn("connect to proxy filter %s: %m", proxy->service_name);
+ return (smtpd_proxy_rdwr_error(state, 0));
+ }
+ proxy->service_stream = vstream_fdopen(fd, O_RDWR);
+ /* Needed by our DATA-phase record emulation routines. */
+ vstream_control(proxy->service_stream,
+ CA_VSTREAM_CTL_CONTEXT((void *) state),
+ CA_VSTREAM_CTL_END);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ if (connect_fn == inet_connect)
+ vstream_tweak_tcp(proxy->service_stream);
+ smtp_timeout_setup(proxy->service_stream, proxy->timeout);
+
+ /*
+ * Get server greeting banner.
+ *
+ * If this fails then we have a problem because the proxy should always
+ * accept our connection. Make up our own response instead of passing
+ * back a negative greeting banner: the proxy open is delayed to the
+ * point that the client expects a MAIL FROM or RCPT TO reply.
+ */
+ if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", "")) {
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ smtpd_proxy_close(state);
+ return (-1);
+ }
+
+ /*
+ * Send our own EHLO command. If this fails then we have a problem
+ * because the proxy should always accept our EHLO command. Make up our
+ * own response instead of passing back a negative EHLO reply: the proxy
+ * open is delayed to the point that the remote SMTP client expects a
+ * MAIL FROM or RCPT TO reply.
+ */
+ if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s",
+ proxy->ehlo_name)) {
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ smtpd_proxy_close(state);
+ return (-1);
+ }
+
+ /*
+ * Parse the EHLO reply and see if we can forward logging information.
+ */
+ server_xforward_features = 0;
+ lines = STR(proxy->reply);
+ while ((words = mystrtok(&lines, "\r\n")) != 0) {
+ if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) {
+ if (strcasecmp(word, XFORWARD_CMD) == 0)
+ while ((word = mystrtok(&words, " \t")) != 0)
+ server_xforward_features |=
+ name_code(known_xforward_features,
+ NAME_CODE_FLAG_NONE, word);
+ }
+ }
+
+ /*
+ * Send XFORWARD attributes. For robustness, explicitly specify what SMTP
+ * session attributes are known and unknown. Make up our own response
+ * instead of passing back a negative XFORWARD reply: the proxy open is
+ * delayed to the point that the remote SMTP client expects a MAIL FROM
+ * or RCPT TO reply.
+ */
+ if (server_xforward_features) {
+ buf = vstring_alloc(100);
+ bad =
+ (((server_xforward_features & SMTPD_PROXY_XFORWARD_NAME)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_NAME,
+ IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)),
+ FORWARD_NAME(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_ADDR)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_ADDR,
+ IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)),
+ FORWARD_ADDR(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PORT)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_PORT,
+ IS_AVAIL_CLIENT_PORT(FORWARD_PORT(state)),
+ FORWARD_PORT(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_HELO)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_HELO,
+ IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)),
+ FORWARD_HELO(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_IDENT)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_IDENT,
+ IS_AVAIL_CLIENT_IDENT(FORWARD_IDENT(state)),
+ FORWARD_IDENT(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PROTO)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_PROTO,
+ IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)),
+ FORWARD_PROTO(state)))
+ || ((server_xforward_features & SMTPD_PROXY_XFORWARD_DOMAIN)
+ && smtpd_proxy_xforward_send(state, buf, XFORWARD_DOMAIN, 1,
+ STREQ(FORWARD_DOMAIN(state), MAIL_ATTR_RWR_LOCAL) ?
+ XFORWARD_DOM_LOCAL : XFORWARD_DOM_REMOTE))
+ || smtpd_proxy_xforward_flush(state, buf));
+ vstring_free(buf);
+ if (bad) {
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ smtpd_proxy_close(state);
+ return (-1);
+ }
+ }
+
+ /*
+ * Pass-through the remote SMTP client's MAIL FROM command. If this
+ * fails, then we have a problem because the proxy should always accept
+ * any MAIL FROM command that was accepted by us.
+ */
+ if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s",
+ proxy->mail_from) != 0) {
+ /* NOT: smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); */
+ smtpd_proxy_close(state);
+ return (-1);
+ }
+ return (0);
+}
+
+/* smtpd_proxy_fake_server_reply - produce generic error response */
+
+static void smtpd_proxy_fake_server_reply(SMTPD_STATE *state, int status)
+{
+ const CLEANUP_STAT_DETAIL *detail;
+
+ /*
+ * Either we have no server reply (connection refused), or we have an
+ * out-of-protocol server reply, so we make up a generic server error
+ * response instead.
+ */
+ detail = cleanup_stat_detail(status);
+ vstring_sprintf(state->proxy->reply,
+ "%d %s Error: %s",
+ detail->smtp, detail->dsn, detail->text);
+}
+
+/* smtpd_proxy_replay_rdwr_error - report replay log I/O error */
+
+static int smtpd_proxy_replay_rdwr_error(SMTPD_STATE *state)
+{
+
+ /*
+ * Log an appropriate warning message.
+ */
+ msg_warn("proxy speed-adjust log I/O error: %m");
+
+ /*
+ * Set the appropriate flags and server reply.
+ */
+ state->error_mask |= MAIL_ERROR_RESOURCE;
+ /* Update state->err in case we are past the client's DATA command. */
+ state->err |= CLEANUP_STAT_PROXY;
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ return (-1);
+}
+
+/* smtpd_proxy_rdwr_error - report proxy communication error */
+
+static int smtpd_proxy_rdwr_error(SMTPD_STATE *state, int err)
+{
+ const char *myname = "smtpd_proxy_rdwr_error";
+ SMTPD_PROXY *proxy = state->proxy;
+
+ /*
+ * Sanity check.
+ */
+ if (err != 0 && err != SMTP_ERR_NONE && proxy == 0)
+ msg_panic("%s: proxy error %d without proxy handle", myname, err);
+
+ /*
+ * Log an appropriate warning message.
+ */
+ switch (err) {
+ case 0:
+ case SMTP_ERR_NONE:
+ break;
+ case SMTP_ERR_EOF:
+ msg_warn("lost connection with proxy %s", proxy->service_name);
+ break;
+ case SMTP_ERR_TIME:
+ msg_warn("timeout talking to proxy %s", proxy->service_name);
+ break;
+ default:
+ msg_panic("%s: unknown proxy %s error %d",
+ myname, proxy->service_name, err);
+ }
+
+ /*
+ * Set the appropriate flags and server reply.
+ */
+ state->error_mask |= MAIL_ERROR_SOFTWARE;
+ /* Update state->err in case we are past the client's DATA command. */
+ state->err |= CLEANUP_STAT_PROXY;
+ smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
+ return (-1);
+}
+
+/* smtpd_proxy_replay_send - replay saved SMTP session from speed-match log */
+
+static int smtpd_proxy_replay_send(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_proxy_replay_send";
+ static VSTRING *replay_buf = 0;
+ SMTPD_PROXY *proxy = state->proxy;
+ int rec_type;
+ int expect = SMTPD_PROX_WANT_BAD;
+
+ /*
+ * Sanity check.
+ */
+ if (smtpd_proxy_replay_stream == 0)
+ msg_panic("%s: no before-queue filter speed-adjust log", myname);
+
+ /*
+ * Errors first.
+ */
+ if (vstream_ferror(smtpd_proxy_replay_stream)
+ || vstream_feof(smtpd_proxy_replay_stream)
+ || rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END
+ || vstream_fflush(smtpd_proxy_replay_stream))
+ /* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */
+ return (smtpd_proxy_replay_rdwr_error(state));
+
+ /*
+ * Delayed connection to the before-queue filter.
+ */
+ if (smtpd_proxy_connect(state) < 0)
+ return (-1);
+
+ /*
+ * Replay the speed-match log. We do sanity check record content, but we
+ * don't implement a protocol state engine here, since we are reading
+ * from a file that we just wrote ourselves.
+ *
+ * This is different than the MailChannels patented solution that
+ * multiplexes a large number of slowed-down inbound connections over a
+ * small number of fast connections to a local MTA.
+ *
+ * - MailChannels receives mail directly from the Internet. It uses one
+ * connection to the local MTA to reject invalid recipients before
+ * receiving the entire email message at reduced bit rates, and then uses
+ * a different connection to quickly deliver the message to the local
+ * MTA.
+ *
+ * - Postfix receives mail directly from the Internet. The Postfix SMTP
+ * server rejects invalid recipients before receiving the entire message
+ * over the Internet, and then delivers the message quickly to a local
+ * SMTP-based content filter.
+ */
+ if (replay_buf == 0)
+ replay_buf = vstring_alloc(100);
+ if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0)
+ return (smtpd_proxy_replay_rdwr_error(state));
+
+ for (;;) {
+ switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf,
+ REC_FLAG_NONE)) {
+
+ /*
+ * Message content.
+ */
+ case REC_TYPE_NORM:
+ case REC_TYPE_CONT:
+ if (smtpd_proxy_rec_put(proxy->service_stream, rec_type,
+ STR(replay_buf), LEN(replay_buf)) < 0)
+ return (-1);
+ break;
+
+ /*
+ * Expected server reply type.
+ */
+ case REC_TYPE_RCPT:
+ if (!alldig(STR(replay_buf))
+ || (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD)
+ msg_panic("%s: malformed server reply type: %s",
+ myname, STR(replay_buf));
+ break;
+
+ /*
+ * Client command, or void. Bail out on the first negative proxy
+ * response. This is OK, because the filter must use the same
+ * reply code for all recipients of a multi-recipient message.
+ */
+ case REC_TYPE_FROM:
+ if (expect == SMTPD_PROX_WANT_BAD)
+ msg_panic("%s: missing server reply type", myname);
+ if (smtpd_proxy_cmd(state, expect, "%s", STR(replay_buf)) < 0)
+ return (-1);
+ expect = SMTPD_PROX_WANT_BAD;
+ break;
+
+ /*
+ * Explicit end marker, instead of implicit EOF.
+ */
+ case REC_TYPE_END:
+ return (0);
+
+ /*
+ * Errors.
+ */
+ case REC_TYPE_ERROR:
+ return (smtpd_proxy_replay_rdwr_error(state));
+ default:
+ msg_panic("%s: unexpected record type; %d", myname, rec_type);
+ }
+ }
+}
+
+/* smtpd_proxy_save_cmd - save SMTP command + expected response to replay log */
+
+static int PRINTFLIKE(3, 4) smtpd_proxy_save_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
+{
+ va_list ap;
+
+ /*
+ * Errors first.
+ */
+ if (vstream_ferror(smtpd_proxy_replay_stream)
+ || vstream_feof(smtpd_proxy_replay_stream))
+ return (smtpd_proxy_replay_rdwr_error(state));
+
+ /*
+ * Save the expected reply first, so that the replayer can safely
+ * overwrite the input buffer with the command.
+ */
+ rec_fprintf(smtpd_proxy_replay_stream, REC_TYPE_RCPT, "%d", expect);
+
+ /*
+ * The command can be omitted at the start of an SMTP session. This is
+ * not documented as part of the official interface because it is used
+ * only internally to this module.
+ */
+
+ /*
+ * Save the command to the replay log, and send it to the before-queue
+ * filter after we have received the entire message.
+ */
+ va_start(ap, fmt);
+ rec_vfprintf(smtpd_proxy_replay_stream, REC_TYPE_FROM, fmt, ap);
+ va_end(ap);
+
+ /*
+ * If we just saved the "." command, replay the log.
+ */
+ return (strcmp(fmt, ".") ? 0 : smtpd_proxy_replay_send(state));
+}
+
+/* smtpd_proxy_cmd - send command to proxy, receive reply */
+
+static int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+ va_list ap;
+ char *cp;
+ int last_char;
+ int err = 0;
+ static VSTRING *buffer = 0;
+
+ /*
+ * Errors first. Be prepared for delayed errors from the DATA phase.
+ */
+ if (vstream_ferror(proxy->service_stream)
+ || vstream_feof(proxy->service_stream)
+ || (err = vstream_setjmp(proxy->service_stream)) != 0) {
+ return (smtpd_proxy_rdwr_error(state, err));
+ }
+
+ /*
+ * Format the command.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(proxy->request, fmt, ap);
+ va_end(ap);
+
+ /*
+ * The command can be omitted at the start of an SMTP session. This is
+ * not documented as part of the official interface because it is used
+ * only internally to this module.
+ */
+ if (LEN(proxy->request) > 0) {
+
+ /*
+ * Optionally log the command first, so that we can see in the log
+ * what the program is trying to do.
+ */
+ if (msg_verbose)
+ msg_info("> %s: %s", proxy->service_name, STR(proxy->request));
+
+ /*
+ * Send the command to the proxy server. Since we're going to read a
+ * reply immediately, there is no need to flush buffers.
+ */
+ smtp_fputs(STR(proxy->request), LEN(proxy->request),
+ proxy->service_stream);
+ }
+
+ /*
+ * Early return if we don't want to wait for a server reply (such as
+ * after sending QUIT).
+ */
+ if (expect == SMTPD_PROX_WANT_NONE)
+ return (0);
+
+ /*
+ * Censor out non-printable characters in server responses and save
+ * complete multi-line responses if possible.
+ *
+ * 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(proxy->reply);
+ if (buffer == 0)
+ buffer = vstring_alloc(10);
+ for (;;) {
+ last_char = smtp_get(buffer, proxy->service_stream, var_line_limit,
+ SMTP_GET_FLAG_SKIP);
+ printable(STR(buffer), '?');
+ if (last_char != '\n')
+ msg_warn("%s: response longer than %d: %.30s...",
+ proxy->service_name, var_line_limit,
+ STR(buffer));
+ if (msg_verbose)
+ msg_info("< %s: %.100s", proxy->service_name, STR(buffer));
+
+ /*
+ * Defend against a denial of service attack by limiting the amount
+ * of multi-line text that we are willing to store.
+ */
+ if (LEN(proxy->reply) < var_line_limit) {
+ if (VSTRING_LEN(proxy->reply))
+ vstring_strcat(proxy->reply, "\r\n");
+ vstring_strcat(proxy->reply, STR(buffer));
+ }
+
+ /*
+ * Parse the response into code and text. Ignore unrecognized
+ * garbage. This means that any character except space (or end of
+ * line) will have the same effect as the '-' line continuation
+ * character.
+ */
+ for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++)
+ /* void */ ;
+ if (cp - STR(buffer) == 3) {
+ if (*cp == '-')
+ continue;
+ if (*cp == ' ' || *cp == 0)
+ break;
+ }
+ msg_warn("received garbage from proxy %s: %.100s",
+ proxy->service_name, STR(buffer));
+ }
+
+ /*
+ * Log a warning in case the proxy does not send the expected response.
+ * Silently accept any response when the client expressed no expectation.
+ *
+ * Starting with Postfix 2.6 we don't pass through unexpected 2xx or 3xx
+ * proxy replies. They are a source of support problems, so we replace
+ * them by generic server error replies.
+ */
+ if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(proxy->reply)) {
+ msg_warn("proxy %s rejected \"%s\": \"%s\"",
+ proxy->service_name, LEN(proxy->request) == 0 ?
+ "connection request" : STR(proxy->request),
+ STR(proxy->reply));
+ if (*STR(proxy->reply) == SMTPD_PROX_WANT_OK
+ || *STR(proxy->reply) == SMTPD_PROX_WANT_MORE) {
+ smtpd_proxy_rdwr_error(state, 0);
+ }
+ return (-1);
+ } else {
+ return (0);
+ }
+}
+
+/* smtpd_proxy_save_rec_put - save message content to replay log */
+
+static int smtpd_proxy_save_rec_put(VSTREAM *stream, int rec_type,
+ const char *data, ssize_t len)
+{
+ const char *myname = "smtpd_proxy_save_rec_put";
+ int ret;
+
+#define VSTREAM_TO_SMTPD_STATE(s) ((SMTPD_STATE *) vstream_context(s))
+
+ /*
+ * Sanity check.
+ */
+ if (stream == 0)
+ msg_panic("%s: attempt to use closed stream", myname);
+
+ /*
+ * Send one content record. Errors and results must be as with rec_put().
+ */
+ if (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT)
+ ret = rec_put(stream, rec_type, data, len);
+ else
+ msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname);
+
+ /*
+ * Errors last.
+ */
+ if (ret != rec_type) {
+ (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream));
+ return (REC_TYPE_ERROR);
+ }
+ return (rec_type);
+}
+
+/* smtpd_proxy_rec_put - send message content, rec_put() clone */
+
+static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type,
+ const char *data, ssize_t len)
+{
+ const char *myname = "smtpd_proxy_rec_put";
+ int err = 0;
+
+ /*
+ * Errors first.
+ */
+ if (vstream_ferror(stream) || vstream_feof(stream)
+ || (err = vstream_setjmp(stream)) != 0) {
+ (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err);
+ return (REC_TYPE_ERROR);
+ }
+
+ /*
+ * Send one content record. Errors and results must be as with rec_put().
+ */
+ if (rec_type == REC_TYPE_NORM)
+ smtp_fputs(data, len, stream);
+ else if (rec_type == REC_TYPE_CONT)
+ smtp_fwrite(data, len, stream);
+ else
+ msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname);
+ return (rec_type);
+}
+
+/* smtpd_proxy_save_rec_fprintf - save message content to replay log */
+
+static int smtpd_proxy_save_rec_fprintf(VSTREAM *stream, int rec_type,
+ const char *fmt,...)
+{
+ const char *myname = "smtpd_proxy_save_rec_fprintf";
+ va_list ap;
+ int ret;
+
+ /*
+ * Sanity check.
+ */
+ if (stream == 0)
+ msg_panic("%s: attempt to use closed stream", myname);
+
+ /*
+ * Save one content record. Errors and results must be as with
+ * rec_fprintf().
+ */
+ va_start(ap, fmt);
+ if (rec_type == REC_TYPE_NORM)
+ ret = rec_vfprintf(stream, rec_type, fmt, ap);
+ else
+ msg_panic("%s: need REC_TYPE_NORM", myname);
+ va_end(ap);
+
+ /*
+ * Errors last.
+ */
+ if (ret != rec_type) {
+ (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream));
+ return (REC_TYPE_ERROR);
+ }
+ return (rec_type);
+}
+
+/* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */
+
+static int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type,
+ const char *fmt,...)
+{
+ const char *myname = "smtpd_proxy_rec_fprintf";
+ va_list ap;
+ int err = 0;
+
+ /*
+ * Errors first.
+ */
+ if (vstream_ferror(stream) || vstream_feof(stream)
+ || (err = vstream_setjmp(stream)) != 0) {
+ (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err);
+ return (REC_TYPE_ERROR);
+ }
+
+ /*
+ * Send one content record. Errors and results must be as with
+ * rec_fprintf().
+ */
+ va_start(ap, fmt);
+ if (rec_type == REC_TYPE_NORM)
+ smtp_vprintf(stream, fmt, ap);
+ else
+ msg_panic("%s: need REC_TYPE_NORM", myname);
+ va_end(ap);
+ return (rec_type);
+}
+
+#ifndef NO_TRUNCATE
+
+/* smtpd_proxy_replay_setup - prepare the replay logfile */
+
+static int smtpd_proxy_replay_setup(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_proxy_replay_setup";
+ off_t file_offs;
+
+ /*
+ * Where possible reuse an existing replay logfile, because creating a
+ * file is expensive compared to reading or writing. For security reasons
+ * we must truncate the file before reuse. For performance reasons we
+ * should truncate the file immediately after the end of a mail
+ * transaction. We enforce the security guarantee upon reuse, by
+ * requiring that no I/O happened since the file was truncated. This is
+ * less expensive than truncating the file redundantly.
+ */
+ if (smtpd_proxy_replay_stream != 0) {
+ /* vstream_ftell() won't invoke the kernel, so all errors are mine. */
+ if ((file_offs = vstream_ftell(smtpd_proxy_replay_stream)) != 0)
+ msg_panic("%s: bad before-queue filter speed-adjust log offset %lu",
+ myname, (unsigned long) file_offs);
+ vstream_clearerr(smtpd_proxy_replay_stream);
+ if (msg_verbose)
+ msg_info("%s: reuse speed-adjust stream fd=%d", myname,
+ vstream_fileno(smtpd_proxy_replay_stream));
+ /* Here, smtpd_proxy_replay_stream != 0 */
+ }
+
+ /*
+ * Create a new replay logfile.
+ */
+ if (smtpd_proxy_replay_stream == 0) {
+ smtpd_proxy_replay_stream = mail_queue_enter(MAIL_QUEUE_INCOMING, 0,
+ (struct timeval *) 0);
+ if (smtpd_proxy_replay_stream == 0)
+ return (smtpd_proxy_replay_rdwr_error(state));
+ if (unlink(VSTREAM_PATH(smtpd_proxy_replay_stream)) < 0)
+ msg_warn("remove before-queue filter speed-adjust log %s: %m",
+ VSTREAM_PATH(smtpd_proxy_replay_stream));
+ if (msg_verbose)
+ msg_info("%s: new speed-adjust stream fd=%d", myname,
+ vstream_fileno(smtpd_proxy_replay_stream));
+ }
+
+ /*
+ * Needed by our DATA-phase record emulation routines.
+ */
+ vstream_control(smtpd_proxy_replay_stream,
+ CA_VSTREAM_CTL_CONTEXT((void *) state),
+ CA_VSTREAM_CTL_END);
+ return (0);
+}
+
+#endif
+
+/* smtpd_proxy_create - set up smtpd proxy handle */
+
+int smtpd_proxy_create(SMTPD_STATE *state, int flags, const char *service,
+ int timeout, const char *ehlo_name,
+ const char *mail_from)
+{
+ SMTPD_PROXY *proxy;
+
+ /*
+ * When an operation has many arguments it is safer to use named
+ * parameters, and have the compiler enforce the argument count.
+ */
+#define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) \
+ ((p) = (SMTPD_PROXY *) mymalloc(sizeof(*(p))), (p)->a1, (p)->a2, \
+ (p)->a3, (p)->a4, (p)->a5, (p)->a6, (p)->a7, (p)->a8, (p)->a9, \
+ (p)->a10, (p)->a11, (p)->a12, (p))
+
+ /*
+ * Sanity check.
+ */
+ if (state->proxy != 0)
+ msg_panic("smtpd_proxy_create: handle still exists");
+
+ /*
+ * Connect to the before-queue filter immediately.
+ */
+ if ((flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) == 0) {
+ state->proxy =
+ SMTPD_PROXY_ALLOC(proxy, stream = 0, request = vstring_alloc(10),
+ reply = vstring_alloc(10),
+ cmd = smtpd_proxy_cmd,
+ rec_fprintf = smtpd_proxy_rec_fprintf,
+ rec_put = smtpd_proxy_rec_put,
+ flags = flags, service_stream = 0,
+ service_name = service, timeout = timeout,
+ ehlo_name = ehlo_name, mail_from = mail_from);
+ if (smtpd_proxy_connect(state) < 0) {
+ /* NOT: smtpd_proxy_free(state); we still need proxy->reply. */
+ return (-1);
+ }
+ proxy->stream = proxy->service_stream;
+ return (0);
+ }
+
+ /*
+ * Connect to the before-queue filter after we receive the entire
+ * message. Open the replay logfile early to simplify code. The file is
+ * reused for multiple mail transactions, so there is no need to minimize
+ * its life time.
+ */
+ else {
+#ifdef NO_TRUNCATE
+ msg_panic("smtpd_proxy_create: speed-adjust support is not available");
+#else
+ if (smtpd_proxy_replay_setup(state) < 0)
+ return (-1);
+ state->proxy =
+ SMTPD_PROXY_ALLOC(proxy, stream = smtpd_proxy_replay_stream,
+ request = vstring_alloc(10),
+ reply = vstring_alloc(10),
+ cmd = smtpd_proxy_save_cmd,
+ rec_fprintf = smtpd_proxy_save_rec_fprintf,
+ rec_put = smtpd_proxy_save_rec_put,
+ flags = flags, service_stream = 0,
+ service_name = service, timeout = timeout,
+ ehlo_name = ehlo_name, mail_from = mail_from);
+ return (0);
+#endif
+ }
+}
+
+/* smtpd_proxy_close - close proxy connection without destroying handle */
+
+void smtpd_proxy_close(SMTPD_STATE *state)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+
+ /*
+ * Specify SMTPD_PROX_WANT_NONE so that the server reply will not clobber
+ * the END-OF-DATA reply.
+ */
+ if (proxy->service_stream != 0) {
+ if (vstream_feof(proxy->service_stream) == 0
+ && vstream_ferror(proxy->service_stream) == 0)
+ (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE,
+ SMTPD_CMD_QUIT);
+ (void) vstream_fclose(proxy->service_stream);
+ if (proxy->stream == proxy->service_stream)
+ proxy->stream = 0;
+ proxy->service_stream = 0;
+ }
+}
+
+/* smtpd_proxy_free - destroy smtpd proxy handle */
+
+void smtpd_proxy_free(SMTPD_STATE *state)
+{
+ SMTPD_PROXY *proxy = state->proxy;
+
+ /*
+ * Clean up.
+ */
+ if (proxy->service_stream != 0)
+ (void) smtpd_proxy_close(state);
+ if (proxy->request != 0)
+ vstring_free(proxy->request);
+ if (proxy->reply != 0)
+ vstring_free(proxy->reply);
+ myfree((void *) proxy);
+ state->proxy = 0;
+
+ /*
+ * Reuse the replay logfile if possible. For security reasons we must
+ * truncate the replay logfile before reuse. For performance reasons we
+ * should truncate the replay logfile immediately after the end of a mail
+ * transaction. We truncate the file here, and enforce the security
+ * guarantee by requiring that no I/O happens before the file is reused.
+ */
+ if (smtpd_proxy_replay_stream == 0)
+ return;
+ if (vstream_ferror(smtpd_proxy_replay_stream)) {
+ /* Errors are already reported. */
+ (void) vstream_fclose(smtpd_proxy_replay_stream);
+ smtpd_proxy_replay_stream = 0;
+ return;
+ }
+ /* Flush output from aborted transaction before truncating the file!! */
+ if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) {
+ msg_warn("seek before-queue filter speed-adjust log: %m");
+ (void) vstream_fclose(smtpd_proxy_replay_stream);
+ smtpd_proxy_replay_stream = 0;
+ return;
+ }
+ if (ftruncate(vstream_fileno(smtpd_proxy_replay_stream), (off_t) 0) < 0) {
+ msg_warn("truncate before-queue filter speed-adjust log: %m");
+ (void) vstream_fclose(smtpd_proxy_replay_stream);
+ smtpd_proxy_replay_stream = 0;
+ return;
+ }
+}
+
+/* smtpd_proxy_parse_opts - parse main.cf options */
+
+int smtpd_proxy_parse_opts(const char *param_name, const char *param_val)
+{
+ static const NAME_MASK proxy_opts_table[] = {
+ SMTPD_PROXY_NAME_SPEED_ADJUST, SMTPD_PROXY_FLAG_SPEED_ADJUST,
+ 0, 0,
+ };
+ int flags;
+
+ /*
+ * The optional before-filter speed-adjust buffers use disk space.
+ * However, we don't know if they compete for storage space with the
+ * after-filter queue, so we can't simply bump up the free space
+ * requirement to 2.5 * message_size_limit.
+ */
+ flags = name_mask(param_name, proxy_opts_table, param_val);
+ if (flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) {
+#ifdef NO_TRUNCATE
+ msg_warn("smtpd_proxy %s support is not available",
+ SMTPD_PROXY_NAME_SPEED_ADJUST);
+ flags &= ~SMTPD_PROXY_FLAG_SPEED_ADJUST;
+#endif
+ }
+ return (flags);
+}
diff --git a/src/smtpd/smtpd_proxy.h b/src/smtpd/smtpd_proxy.h
new file mode 100644
index 0000000..3d35d07
--- /dev/null
+++ b/src/smtpd/smtpd_proxy.h
@@ -0,0 +1,66 @@
+/*++
+/* NAME
+/* smtpd_proxy 3h
+/* SUMMARY
+/* SMTP server pass-through proxy client
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_proxy.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Application-specific.
+ */
+typedef int PRINTFPTRLIKE(3, 4) (*SMTPD_PROXY_CMD_FN) (SMTPD_STATE *, int, const char *,...);
+typedef int PRINTFPTRLIKE(3, 4) (*SMTPD_PROXY_REC_FPRINTF_FN) (VSTREAM *, int, const char *,...);
+typedef int (*SMTPD_PROXY_REC_PUT_FN) (VSTREAM *, int, const char *, ssize_t);
+
+typedef struct SMTPD_PROXY {
+ /* Public. */
+ VSTREAM *stream;
+ VSTRING *request; /* proxy request buffer */
+ VSTRING *reply; /* proxy reply buffer */
+ SMTPD_PROXY_CMD_FN cmd;
+ SMTPD_PROXY_REC_FPRINTF_FN rec_fprintf;
+ SMTPD_PROXY_REC_PUT_FN rec_put;
+ /* Private. */
+ int flags;
+ VSTREAM *service_stream;
+ const char *service_name;
+ int timeout;
+ const char *ehlo_name;
+ const char *mail_from;
+} SMTPD_PROXY;
+
+#define SMTPD_PROXY_FLAG_SPEED_ADJUST (1<<0)
+
+#define SMTPD_PROXY_NAME_SPEED_ADJUST "speed_adjust"
+
+#define SMTPD_PROX_WANT_BAD 0xff /* Do not use */
+#define SMTPD_PROX_WANT_NONE '\0' /* Do not receive reply */
+#define SMTPD_PROX_WANT_ANY '0' /* Expect any reply */
+#define SMTPD_PROX_WANT_OK '2' /* Expect 2XX reply */
+#define SMTPD_PROX_WANT_MORE '3' /* Expect 3XX reply */
+
+extern int smtpd_proxy_create(SMTPD_STATE *, int, const char *, int, const char *, const char *);
+extern void smtpd_proxy_close(SMTPD_STATE *);
+extern void smtpd_proxy_free(SMTPD_STATE *);
+extern int smtpd_proxy_parse_opts(const char *, const char *);
+
+/* 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/smtpd/smtpd_resolve.c b/src/smtpd/smtpd_resolve.c
new file mode 100644
index 0000000..1dd6914
--- /dev/null
+++ b/src/smtpd/smtpd_resolve.c
@@ -0,0 +1,190 @@
+/*++
+/* NAME
+/* smtpd_resolve 3
+/* SUMMARY
+/* caching resolve client
+/* SYNOPSIS
+/* #include <smtpd_resolve.h>
+/*
+/* void smtpd_resolve_init(cache_size)
+/* int cache_size;
+/*
+/* const RESOLVE_REPLY *smtpd_resolve_addr(sender, addr)
+/* const char *sender;
+/* const char *addr;
+/* DESCRIPTION
+/* This module maintains a resolve client cache that persists
+/* across SMTP sessions (not process life times). Addresses
+/* are always resolved in local rewriting context.
+/*
+/* smtpd_resolve_init() initializes the cache and must be
+/* called before the cache can be used. This function may also
+/* be called to flush the cache after an address class update.
+/*
+/* smtpd_resolve_addr() resolves one address or returns
+/* a known result from cache.
+/*
+/* Arguments:
+/* .IP cache_size
+/* The requested cache size.
+/* .IP sender
+/* The message sender, or null pointer.
+/* .IP addr
+/* The address to resolve.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* BUGS
+/* The recipient address is always case folded to lowercase.
+/* Changing this requires great care, since the address is used
+/* for policy lookups.
+/* 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>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <ctable.h>
+#include <stringops.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <rewrite_clnt.h>
+#include <resolve_clnt.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <smtpd_resolve.h>
+
+static CTABLE *smtpd_resolve_cache;
+
+#define STR(x) vstring_str(x)
+#define SENDER_ADDR_JOIN_CHAR '\n'
+
+/* resolve_pagein - page in an address resolver result */
+
+static void *resolve_pagein(const char *sender_plus_addr, void *unused_context)
+{
+ const char myname[] = "resolve_pagein";
+ static VSTRING *query;
+ static VSTRING *junk;
+ static VSTRING *sender_buf;
+ RESOLVE_REPLY *reply;
+ const char *sender;
+ const char *addr;
+
+ /*
+ * Initialize on the fly.
+ */
+ if (query == 0) {
+ query = vstring_alloc(10);
+ junk = vstring_alloc(10);
+ sender_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Initialize.
+ */
+ reply = (RESOLVE_REPLY *) mymalloc(sizeof(*reply));
+ resolve_clnt_init(reply);
+
+ /*
+ * Split the sender and address.
+ */
+ vstring_strcpy(junk, sender_plus_addr);
+ sender = STR(junk);
+ if ((addr = split_at(STR(junk), SENDER_ADDR_JOIN_CHAR)) == 0)
+ msg_panic("%s: bad search key: \"%s\"", myname, sender_plus_addr);
+
+ /*
+ * Resolve the address.
+ */
+ rewrite_clnt_internal(MAIL_ATTR_RWR_LOCAL, sender, sender_buf);
+ rewrite_clnt_internal(MAIL_ATTR_RWR_LOCAL, addr, query);
+ resolve_clnt_query_from(STR(sender_buf), STR(query), reply);
+ vstring_strcpy(junk, STR(reply->recipient));
+ casefold(reply->recipient, STR(junk)); /* XXX */
+
+ /*
+ * Save the result.
+ */
+ return ((void *) reply);
+}
+
+/* resolve_pageout - page out an address resolver result */
+
+static void resolve_pageout(void *data, void *unused_context)
+{
+ RESOLVE_REPLY *reply = (RESOLVE_REPLY *) data;
+
+ resolve_clnt_free(reply);
+ myfree((void *) reply);
+}
+
+/* smtpd_resolve_init - set up global cache */
+
+void smtpd_resolve_init(int cache_size)
+{
+
+ /*
+ * Flush a pre-existing cache. The smtpd_check test program requires this
+ * after an address class change.
+ */
+ if (smtpd_resolve_cache)
+ ctable_free(smtpd_resolve_cache);
+
+ /*
+ * Initialize the resolved address cache. Note: the cache persists across
+ * SMTP sessions so we cannot make it dependent on session state.
+ */
+ smtpd_resolve_cache = ctable_create(cache_size, resolve_pagein,
+ resolve_pageout, (void *) 0);
+}
+
+/* smtpd_resolve_addr - resolve cached address */
+
+const RESOLVE_REPLY *smtpd_resolve_addr(const char *sender, const char *addr)
+{
+ static VSTRING *sender_plus_addr_buf;
+
+ /*
+ * Initialize on the fly.
+ */
+ if (sender_plus_addr_buf == 0)
+ sender_plus_addr_buf = vstring_alloc(10);
+
+ /*
+ * Sanity check.
+ */
+ if (smtpd_resolve_cache == 0)
+ msg_panic("smtpd_resolve_addr: missing initialization");
+
+ /*
+ * Reply from the read-through cache.
+ */
+ vstring_sprintf(sender_plus_addr_buf, "%s%c%s",
+ sender ? sender : RESOLVE_NULL_FROM,
+ SENDER_ADDR_JOIN_CHAR, addr);
+ return (const RESOLVE_REPLY *)
+ ctable_locate(smtpd_resolve_cache, STR(sender_plus_addr_buf));
+}
diff --git a/src/smtpd/smtpd_resolve.h b/src/smtpd/smtpd_resolve.h
new file mode 100644
index 0000000..cd0257a
--- /dev/null
+++ b/src/smtpd/smtpd_resolve.h
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* smtpd_resolve 3h
+/* SUMMARY
+/* caching resolve client
+/* SYNOPSIS
+/* include <smtpd_resolve.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Global library.
+ */
+#include <resolve_clnt.h>
+
+ /*
+ * External interface.
+ */
+extern void smtpd_resolve_init(int);
+extern const RESOLVE_REPLY *smtpd_resolve_addr(const char*, const char *);
+
+/* 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
+/*--*/
diff --git a/src/smtpd/smtpd_sasl_glue.c b/src/smtpd/smtpd_sasl_glue.c
new file mode 100644
index 0000000..ee5c041
--- /dev/null
+++ b/src/smtpd/smtpd_sasl_glue.c
@@ -0,0 +1,377 @@
+/*++
+/* NAME
+/* smtpd_sasl_glue 3
+/* SUMMARY
+/* Postfix SMTP server, SASL support interface
+/* SYNOPSIS
+/* #include "smtpd_sasl_glue.h"
+/*
+/* void smtpd_sasl_state_init(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_sasl_initialize()
+/*
+/* void smtpd_sasl_activate(state, sasl_opts_name, sasl_opts_val)
+/* SMTPD_STATE *state;
+/* const char *sasl_opts_name;
+/* const char *sasl_opts_val;
+/*
+/* char *smtpd_sasl_authenticate(state, sasl_method, init_response)
+/* SMTPD_STATE *state;
+/* const char *sasl_method;
+/* const char *init_response;
+/*
+/* void smtpd_sasl_logout(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_sasl_login(state, sasl_username, sasl_method)
+/* SMTPD_STATE *state;
+/* const char *sasl_username;
+/* const char *sasl_method;
+/*
+/* void smtpd_sasl_deactivate(state)
+/* SMTPD_STATE *state;
+/*
+/* int smtpd_sasl_is_active(state)
+/* SMTPD_STATE *state;
+/*
+/* int smtpd_sasl_set_inactive(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* This module encapsulates most of the detail specific to SASL
+/* authentication.
+/*
+/* smtpd_sasl_state_init() performs minimal server state
+/* initialization to support external authentication (e.g.,
+/* XCLIENT) without having to enable SASL in main.cf. This
+/* should always be called at process startup.
+/*
+/* smtpd_sasl_initialize() initializes the SASL library. This
+/* routine should be called once at process start-up. It may
+/* need access to the file system for run-time loading of
+/* plug-in modules. There is no corresponding cleanup routine.
+/*
+/* smtpd_sasl_activate() performs per-connection initialization.
+/* This routine should be called once at the start of every
+/* connection. The sasl_opts_name and sasl_opts_val parameters
+/* are the postfix configuration parameters setting the security
+/* policy of the SASL authentication.
+/*
+/* smtpd_sasl_authenticate() implements the authentication
+/* dialog. The result is zero in case of success, -1 in case
+/* of failure. smtpd_sasl_authenticate() updates the following
+/* state structure members:
+/* .IP sasl_method
+/* The authentication method that was successfully applied.
+/* This member is a null pointer in the absence of successful
+/* authentication.
+/* .IP sasl_username
+/* The username that was successfully authenticated.
+/* This member is a null pointer in the absence of successful
+/* authentication.
+/* .PP
+/* smtpd_sasl_login() records the result of successful external
+/* authentication, i.e. without invoking smtpd_sasl_authenticate(),
+/* but produces an otherwise equivalent result.
+/*
+/* smtpd_sasl_logout() cleans up after smtpd_sasl_authenticate().
+/* This routine exists for the sake of symmetry.
+/*
+/* smtpd_sasl_deactivate() performs per-connection cleanup.
+/* This routine should be called at the end of every connection.
+/*
+/* smtpd_sasl_is_active() is a predicate that returns true
+/* if the SMTP server session state is between smtpd_sasl_activate()
+/* and smtpd_sasl_deactivate().
+/*
+/* smtpd_sasl_set_inactive() initializes the SMTP session
+/* state before the first smtpd_sasl_activate() call.
+/*
+/* Arguments:
+/* .IP state
+/* SMTP session context.
+/* .IP sasl_opts_name
+/* Security options parameter name.
+/* .IP sasl_opts_val
+/* Security options parameter value.
+/* .IP sasl_method
+/* A SASL mechanism name
+/* .IP init_reply
+/* An optional initial client response.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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
+/*
+/* 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>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* XSASL library. */
+
+#include <xsasl.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_sasl_glue.h"
+#include "smtpd_chat.h"
+
+#ifdef USE_SASL_AUTH
+
+/*
+ * Silly little macros.
+ */
+#define STR(s) vstring_str(s)
+
+ /*
+ * SASL server implementation handle.
+ */
+static XSASL_SERVER_IMPL *smtpd_sasl_impl;
+
+/* smtpd_sasl_initialize - per-process initialization */
+
+void smtpd_sasl_initialize(void)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (smtpd_sasl_impl)
+ msg_panic("smtpd_sasl_initialize: repeated call");
+
+ /*
+ * Initialize the SASL library.
+ */
+ if ((smtpd_sasl_impl = xsasl_server_init(var_smtpd_sasl_type,
+ var_smtpd_sasl_path)) == 0)
+ msg_fatal("SASL per-process initialization failed");
+
+}
+
+/* smtpd_sasl_activate - per-connection initialization */
+
+void smtpd_sasl_activate(SMTPD_STATE *state, const char *sasl_opts_name,
+ const char *sasl_opts_val)
+{
+ const char *mechanism_list;
+ XSASL_SERVER_CREATE_ARGS create_args;
+ int tls_flag;
+
+ /*
+ * Sanity check.
+ */
+ if (smtpd_sasl_is_active(state))
+ msg_panic("smtpd_sasl_activate: already active");
+
+ /*
+ * Initialize SASL-specific state variables. Use long-lived storage for
+ * base 64 conversion results, rather than local variables, to avoid
+ * memory leaks when a read or write routine returns abnormally after
+ * timeout or I/O error.
+ */
+ state->sasl_reply = vstring_alloc(20);
+ state->sasl_mechanism_list = 0;
+
+ /*
+ * Set up a new server context for this connection.
+ */
+#ifdef USE_TLS
+ tls_flag = state->tls_context != 0;
+#else
+ tls_flag = 0;
+#endif
+#define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "")
+#define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0)
+
+ if ((state->sasl_server =
+ XSASL_SERVER_CREATE(smtpd_sasl_impl, &create_args,
+ stream = state->client,
+ addr_family = state->addr_family,
+ server_addr = ADDR_OR_EMPTY(state->dest_addr,
+ SERVER_ADDR_UNKNOWN),
+ server_port = ADDR_OR_EMPTY(state->dest_port,
+ SERVER_PORT_UNKNOWN),
+ client_addr = ADDR_OR_EMPTY(state->addr,
+ CLIENT_ADDR_UNKNOWN),
+ client_port = ADDR_OR_EMPTY(state->port,
+ CLIENT_PORT_UNKNOWN),
+ service = var_smtpd_sasl_service,
+ user_realm = REALM_OR_NULL(var_smtpd_sasl_realm),
+ security_options = sasl_opts_val,
+ tls_flag = tls_flag)) == 0)
+ msg_fatal("SASL per-connection initialization failed");
+
+ /*
+ * Get the list of authentication mechanisms.
+ */
+ if ((mechanism_list =
+ xsasl_server_get_mechanism_list(state->sasl_server)) == 0)
+ msg_fatal("no SASL authentication mechanisms");
+ state->sasl_mechanism_list = mystrdup(mechanism_list);
+}
+
+/* smtpd_sasl_state_init - initialize state to allow extern authentication. */
+
+void smtpd_sasl_state_init(SMTPD_STATE *state)
+{
+ /* Initialization to support external authentication (e.g., XCLIENT). */
+ state->sasl_username = 0;
+ state->sasl_method = 0;
+ state->sasl_sender = 0;
+}
+
+/* smtpd_sasl_deactivate - per-connection cleanup */
+
+void smtpd_sasl_deactivate(SMTPD_STATE *state)
+{
+ if (state->sasl_reply) {
+ vstring_free(state->sasl_reply);
+ state->sasl_reply = 0;
+ }
+ if (state->sasl_mechanism_list) {
+ myfree(state->sasl_mechanism_list);
+ state->sasl_mechanism_list = 0;
+ }
+ if (state->sasl_username) {
+ myfree(state->sasl_username);
+ state->sasl_username = 0;
+ }
+ if (state->sasl_method) {
+ myfree(state->sasl_method);
+ state->sasl_method = 0;
+ }
+ if (state->sasl_sender) {
+ myfree(state->sasl_sender);
+ state->sasl_sender = 0;
+ }
+ if (state->sasl_server) {
+ xsasl_server_free(state->sasl_server);
+ state->sasl_server = 0;
+ }
+}
+
+/* smtpd_sasl_authenticate - per-session authentication */
+
+int smtpd_sasl_authenticate(SMTPD_STATE *state,
+ const char *sasl_method,
+ const char *init_response)
+{
+ int status;
+ const char *sasl_username;
+
+ /*
+ * SASL authentication protocol start-up. Process any initial client
+ * response that was sent along in the AUTH command.
+ */
+ for (status = xsasl_server_first(state->sasl_server, sasl_method,
+ init_response, state->sasl_reply);
+ status == XSASL_AUTH_MORE;
+ status = xsasl_server_next(state->sasl_server, STR(state->buffer),
+ state->sasl_reply)) {
+
+ /*
+ * Send a server challenge.
+ */
+ smtpd_chat_reply(state, "334 %s", STR(state->sasl_reply));
+
+ /*
+ * Receive the client response. "*" means that the client gives up.
+ */
+ if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) {
+ smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded");
+ return (-1);
+ }
+ if (strcmp(STR(state->buffer), "*") == 0) {
+ msg_warn("%s: SASL %s authentication aborted",
+ state->namaddr, sasl_method);
+ smtpd_chat_reply(state, "501 5.7.0 Authentication aborted");
+ return (-1);
+ }
+ }
+ if (status != XSASL_AUTH_DONE) {
+ sasl_username = xsasl_server_get_username(state->sasl_server);
+ msg_warn("%s: SASL %.100s authentication failed: %s, sasl_username=%.100s",
+ state->namaddr, sasl_method, *STR(state->sasl_reply) ?
+ STR(state->sasl_reply) : "(reason unavailable)",
+ sasl_username ? sasl_username : "(unavailable)");
+ /* RFC 4954 Section 6. */
+ if (status == XSASL_AUTH_TEMP)
+ smtpd_chat_reply(state, "454 4.7.0 Temporary authentication failure: %s",
+ STR(state->sasl_reply));
+ else
+ smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s",
+ STR(state->sasl_reply));
+ return (-1);
+ }
+ /* RFC 4954 Section 6. */
+ smtpd_chat_reply(state, "235 2.7.0 Authentication successful");
+ if ((sasl_username = xsasl_server_get_username(state->sasl_server)) == 0)
+ msg_panic("cannot look up the authenticated SASL username");
+ state->sasl_username = mystrdup(sasl_username);
+ printable(state->sasl_username, '?');
+ state->sasl_method = mystrdup(sasl_method);
+ printable(state->sasl_method, '?');
+
+ return (0);
+}
+
+/* smtpd_sasl_logout - clean up after smtpd_sasl_authenticate */
+
+void smtpd_sasl_logout(SMTPD_STATE *state)
+{
+ if (state->sasl_username) {
+ myfree(state->sasl_username);
+ state->sasl_username = 0;
+ }
+ if (state->sasl_method) {
+ myfree(state->sasl_method);
+ state->sasl_method = 0;
+ }
+}
+
+/* smtpd_sasl_login - set login information */
+
+void smtpd_sasl_login(SMTPD_STATE *state, const char *sasl_username,
+ const char *sasl_method)
+{
+ if (state->sasl_username)
+ myfree(state->sasl_username);
+ state->sasl_username = mystrdup(sasl_username);
+ if (state->sasl_method)
+ myfree(state->sasl_method);
+ state->sasl_method = mystrdup(sasl_method);
+}
+
+#endif
diff --git a/src/smtpd/smtpd_sasl_glue.h b/src/smtpd/smtpd_sasl_glue.h
new file mode 100644
index 0000000..d81eec1
--- /dev/null
+++ b/src/smtpd/smtpd_sasl_glue.h
@@ -0,0 +1,41 @@
+/*++
+/* NAME
+/* smtpd_sasl_glue 3h
+/* SUMMARY
+/* Postfix SMTP server, SASL support interface
+/* SYNOPSIS
+/* #include "smtpd_sasl_glue.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * SASL protocol interface
+ */
+extern void smtpd_sasl_state_init(SMTPD_STATE *);
+extern void smtpd_sasl_initialize(void);
+extern void smtpd_sasl_activate(SMTPD_STATE *, const char *, const char *);
+extern void smtpd_sasl_deactivate(SMTPD_STATE *);
+extern int smtpd_sasl_authenticate(SMTPD_STATE *, const char *, const char *);
+extern void smtpd_sasl_login(SMTPD_STATE *, const char *, const char *);
+extern void smtpd_sasl_logout(SMTPD_STATE *);
+extern int permit_sasl_auth(SMTPD_STATE *, int, int);
+
+#define smtpd_sasl_is_active(s) ((s)->sasl_server != 0)
+#define smtpd_sasl_set_inactive(s) ((void) ((s)->sasl_server = 0))
+
+/* 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/smtpd/smtpd_sasl_proto.c b/src/smtpd/smtpd_sasl_proto.c
new file mode 100644
index 0000000..476752d
--- /dev/null
+++ b/src/smtpd/smtpd_sasl_proto.c
@@ -0,0 +1,274 @@
+/*++
+/* NAME
+/* smtpd_sasl_proto 3
+/* SUMMARY
+/* Postfix SMTP protocol support for SASL authentication
+/* SYNOPSIS
+/* #include "smtpd.h"
+/* #include "smtpd_sasl_proto.h"
+/*
+/* int smtpd_sasl_auth_cmd(state, argc, argv)
+/* SMTPD_STATE *state;
+/* int argc;
+/* SMTPD_TOKEN *argv;
+/*
+/* void smtpd_sasl_auth_extern(state, username, method)
+/* SMTPD_STATE *state;
+/* const char *username;
+/* const char *method;
+/*
+/* void smtpd_sasl_auth_reset(state)
+/* SMTPD_STATE *state;
+/*
+/* char *smtpd_sasl_mail_opt(state, sender)
+/* SMTPD_STATE *state;
+/* const char *sender;
+/*
+/* void smtpd_sasl_mail_reset(state)
+/* SMTPD_STATE *state;
+/*
+/* static int permit_sasl_auth(state, authenticated, unauthenticated)
+/* SMTPD_STATE *state;
+/* int authenticated;
+/* int unauthenticated;
+/* DESCRIPTION
+/* This module contains random chunks of code that implement
+/* the SMTP protocol interface for SASL negotiation. The goal
+/* is to reduce clutter of the main SMTP server source code.
+/*
+/* smtpd_sasl_auth_cmd() implements the AUTH command and updates
+/* the following state structure members:
+/* .IP sasl_method
+/* The authentication method that was successfully applied.
+/* This member is a null pointer in the absence of successful
+/* authentication.
+/* .IP sasl_username
+/* The username that was successfully authenticated.
+/* This member is a null pointer in the absence of successful
+/* authentication.
+/* .PP
+/* smtpd_sasl_auth_reset() cleans up after the AUTH command.
+/* This is required before smtpd_sasl_auth_cmd() can be used again.
+/* This may be called even if SASL authentication is turned off
+/* in main.cf.
+/*
+/* smtpd_sasl_auth_extern() records authentication information
+/* that is received from an external source.
+/* This may be called even if SASL authentication is turned off
+/* in main.cf.
+/*
+/* smtpd_sasl_mail_opt() implements the SASL-specific AUTH=sender
+/* option to the MAIL FROM command. The result is an error response
+/* in case of problems.
+/*
+/* smtpd_sasl_mail_reset() performs cleanup for the SASL-specific
+/* AUTH=sender option to the MAIL FROM command.
+/*
+/* permit_sasl_auth() permits access from an authenticated client.
+/* This test fails for clients that use anonymous authentication.
+/*
+/* Arguments:
+/* .IP state
+/* SMTP session context.
+/* .IP argc
+/* Number of command line tokens.
+/* .IP argv
+/* The command line parsed into tokens.
+/* .IP sender
+/* Sender address from the AUTH=sender option in the MAIL FROM
+/* command.
+/* .IP authenticated
+/* Result for authenticated client.
+/* .IP unauthenticated
+/* Result for unauthenticated client.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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
+/*
+/* 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
+/*--*/
+
+/* 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>
+#include <mail_proto.h>
+#include <mail_error.h>
+#include <ehlo_mask.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_token.h"
+#include "smtpd_chat.h"
+#include "smtpd_sasl_proto.h"
+#include "smtpd_sasl_glue.h"
+
+#ifdef USE_SASL_AUTH
+
+/* smtpd_sasl_auth_cmd - process AUTH command */
+
+int smtpd_sasl_auth_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+ char *auth_mechanism;
+ char *initial_response;
+ const char *err;
+
+ if (var_helo_required && state->helo_name == 0) {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "503 5.5.1 Error: send HELO/EHLO first");
+ return (-1);
+ }
+ if (SMTPD_STAND_ALONE(state) || !smtpd_sasl_is_active(state)
+ || (state->ehlo_discard_mask & EHLO_MASK_AUTH)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: authentication not enabled");
+ return (-1);
+ }
+ if (SMTPD_IN_MAIL_TRANSACTION(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress");
+ return (-1);
+ }
+ if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0) {
+ if (err[0] == '5') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ /* Sendmail compatibility: map 4xx into 454. */
+ else if (err[0] == '4') {
+ state->error_mask |= MAIL_ERROR_POLICY;
+ smtpd_chat_reply(state, "454 4.3.0 Try again later");
+ return (-1);
+ }
+ }
+#ifdef USE_TLS
+ if (var_smtpd_tls_auth_only && !state->tls_context) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ /* RFC 4954, Section 4. */
+ smtpd_chat_reply(state, "504 5.5.4 Encryption required for requested authentication mechanism");
+ return (-1);
+ }
+#endif
+ if (state->sasl_username) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: already authenticated");
+ return (-1);
+ }
+ if (argc < 2 || argc > 3) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 5.5.4 Syntax: AUTH mechanism");
+ return (-1);
+ }
+ /* Don't reuse the SASL handle after authentication failure. */
+#ifndef XSASL_TYPE_CYRUS
+#define XSASL_TYPE_CYRUS "cyrus"
+#endif
+ if (state->flags & SMTPD_FLAG_AUTH_USED) {
+ smtpd_sasl_deactivate(state);
+#ifdef USE_TLS
+ if (state->tls_context != 0)
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS,
+ var_smtpd_sasl_tls_opts);
+ else
+#endif
+ smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS,
+ var_smtpd_sasl_opts);
+ } else if (strcmp(var_smtpd_sasl_type, XSASL_TYPE_CYRUS) == 0) {
+ state->flags |= SMTPD_FLAG_AUTH_USED;
+ }
+
+ /*
+ * All authentication failures shall be logged. The 5xx reply code from
+ * the SASL authentication routine triggers tar-pit delays, which help to
+ * slow down password guessing attacks.
+ */
+ auth_mechanism = argv[1].strval;
+ initial_response = (argc == 3 ? argv[2].strval : 0);
+ return (smtpd_sasl_authenticate(state, auth_mechanism, initial_response));
+}
+
+/* smtpd_sasl_mail_opt - SASL-specific MAIL FROM option */
+
+char *smtpd_sasl_mail_opt(SMTPD_STATE *state, const char *addr)
+{
+
+ /*
+ * Do not store raw RFC2554 protocol data.
+ */
+#if 0
+ if (state->sasl_username == 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ return ("503 5.5.4 Error: send AUTH command first");
+ }
+#endif
+ if (state->sasl_sender != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ return ("503 5.5.4 Error: multiple AUTH= options");
+ }
+ if (strcmp(addr, "<>") != 0) {
+ state->sasl_sender = mystrdup(addr);
+ printable(state->sasl_sender, '?');
+ }
+ return (0);
+}
+
+/* smtpd_sasl_mail_reset - SASL-specific MAIL FROM cleanup */
+
+void smtpd_sasl_mail_reset(SMTPD_STATE *state)
+{
+ if (state->sasl_sender) {
+ myfree(state->sasl_sender);
+ state->sasl_sender = 0;
+ }
+}
+
+/* permit_sasl_auth - OK for authenticated connection */
+
+int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot)
+{
+ if (state->sasl_method && strcasecmp(state->sasl_method, "anonymous"))
+ return (ifyes);
+ return (ifnot);
+}
+
+#endif
diff --git a/src/smtpd/smtpd_sasl_proto.h b/src/smtpd/smtpd_sasl_proto.h
new file mode 100644
index 0000000..d52bd4b
--- /dev/null
+++ b/src/smtpd/smtpd_sasl_proto.h
@@ -0,0 +1,37 @@
+/*++
+/* NAME
+/* smtpd_sasl_proto 3h
+/* SUMMARY
+/* Postfix SMTP protocol support for SASL authentication
+/* SYNOPSIS
+/* #include "smtpd_sasl_proto.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * SMTP protocol interface.
+ */
+extern int smtpd_sasl_auth_cmd(SMTPD_STATE *, int, SMTPD_TOKEN *);
+extern void smtpd_sasl_auth_reset(SMTPD_STATE *);
+extern char *smtpd_sasl_mail_opt(SMTPD_STATE *, const char *);
+extern void smtpd_sasl_mail_reset(SMTPD_STATE *);
+
+#define smtpd_sasl_auth_extern smtpd_sasl_login
+#define smtpd_sasl_auth_reset smtpd_sasl_logout
+
+/* 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/smtpd/smtpd_server.in b/src/smtpd/smtpd_server.in
new file mode 100644
index 0000000..325e9da
--- /dev/null
+++ b/src/smtpd/smtpd_server.in
@@ -0,0 +1,56 @@
+#
+# Initialize.
+#
+#! ../bin/postmap smtpd_check_access
+#msg_verbose 1
+smtpd_delay_reject 0
+relay_domains porcupine.org
+client spike.porcupine.org 168.100.189.2
+#
+# Check MX access
+#
+helo_restrictions check_helo_mx_access,inline:{168.100.189.2=reject}
+helo www.porcupine.org
+helo example.tld
+helo foo@postfix.org
+sender_restrictions check_sender_mx_access,inline:{168.100.189.2=reject}
+mail foo@www.porcupine.org
+mail example.tld
+mail foo@postfix.org
+recipient_restrictions check_recipient_mx_access,inline:{168.100.189.2=reject}
+rcpt foo@www.porcupine.org
+rcpt foo@example.tld
+rcpt foo@postfix.org
+#
+# Check NS access
+#
+helo_restrictions check_helo_ns_access,inline:{168.100.1.2=reject}
+helo www.porcupine.org
+helo example.tld
+helo foo@postfix.org
+sender_restrictions check_sender_ns_access,inline:{168.100.1.2=reject}
+mail foo@www.porcupine.org
+mail example.tld
+mail foo@postfix.org
+recipient_restrictions check_recipient_ns_access,inline:{168.100.1.2=reject}
+rcpt foo@www.porcupine.org
+rcpt foo@example.tld
+rcpt foo@postfix.org
+#
+# Check A access
+#
+helo_restrictions check_helo_a_access,inline:{168.100.189.2=reject}
+helo spike.porcupine.org
+helo www.porcupine.org
+client_restrictions check_client_a_access,inline:{168.100.189.2=reject}
+client spike.porcupine.org 1.2.3.4
+client www.porcupine.org 1.2.3.4
+reverse_client_restrictions check_reverse_client_a_access,inline:{168.100.189.2=reject}
+client spike.porcupine.org 1.2.3.4
+client www.porcupine.org 1.2.3.4
+sender_restrictions check_sender_a_access,inline:{168.100.189.2=reject}
+mail foo@spike.porcupine.org
+mail foo@www.porcupine.org
+recipient_restrictions check_recipient_a_access,inline:{168.100.189.2=reject}
+rcpt foo@spike.porcupine.org
+rcpt foo@www.porcupine.org
diff --git a/src/smtpd/smtpd_server.ref b/src/smtpd/smtpd_server.ref
new file mode 100644
index 0000000..563befe
--- /dev/null
+++ b/src/smtpd/smtpd_server.ref
@@ -0,0 +1,118 @@
+>>> #
+>>> # Initialize.
+>>> #
+>>> #! ../bin/postmap smtpd_check_access
+>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
+>>> relay_domains porcupine.org
+OK
+>>> client spike.porcupine.org 168.100.189.2
+OK
+>>> #
+>>> # Check MX access
+>>> #
+>>> helo_restrictions check_helo_mx_access,inline:{168.100.189.2=reject}
+OK
+>>> helo www.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied; proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied
+>>> helo example.tld
+./smtpd_check: warning: Unable to look up MX host example.tld for Helo command example.tld: hostname nor servname provided, or not known
+OK
+>>> helo foo@postfix.org
+OK
+>>> sender_restrictions check_sender_mx_access,inline:{168.100.189.2=reject}
+OK
+>>> mail foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied; from=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied
+>>> mail example.tld
+./smtpd_check: warning: Unable to look up MX host example.tld for Sender address example.tld: hostname nor servname provided, or not known
+OK
+>>> mail foo@postfix.org
+OK
+>>> recipient_restrictions check_recipient_mx_access,inline:{168.100.189.2=reject}
+OK
+>>> rcpt foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied
+>>> rcpt foo@example.tld
+./smtpd_check: warning: Unable to look up MX host example.tld for Recipient address foo@example.tld: hostname nor servname provided, or not known
+OK
+>>> rcpt foo@postfix.org
+OK
+>>> #
+>>> # Check NS access
+>>> #
+>>> helo_restrictions check_helo_ns_access,inline:{168.100.1.2=reject}
+OK
+>>> helo www.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied
+>>> helo example.tld
+./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found
+OK
+>>> helo foo@postfix.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@postfix.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@postfix.org>: Helo command rejected: Access denied
+>>> sender_restrictions check_sender_ns_access,inline:{168.100.1.2=reject}
+OK
+>>> mail foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied; from=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied
+>>> mail example.tld
+./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found
+OK
+>>> mail foo@postfix.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@postfix.org>: Sender address rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@postfix.org>: Sender address rejected: Access denied
+>>> recipient_restrictions check_recipient_ns_access,inline:{168.100.1.2=reject}
+OK
+>>> rcpt foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied
+>>> rcpt foo@example.tld
+./smtpd_check: warning: Unable to look up NS host for foo@example.tld: Host not found
+OK
+>>> rcpt foo@postfix.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@postfix.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@postfix.org> proto=SMTP helo=<foo@postfix.org>
+554 5.7.1 <foo@postfix.org>: Recipient address rejected: Access denied
+>>> #
+>>> # Check A access
+>>> #
+>>> helo_restrictions check_helo_a_access,inline:{168.100.189.2=reject}
+OK
+>>> helo spike.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <spike.porcupine.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<spike.porcupine.org>
+554 5.7.1 <spike.porcupine.org>: Helo command rejected: Access denied
+>>> helo www.porcupine.org
+OK
+>>> client_restrictions check_client_a_access,inline:{168.100.189.2=reject}
+OK
+>>> client spike.porcupine.org 1.2.3.4
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[1.2.3.4]: 554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied
+>>> client www.porcupine.org 1.2.3.4
+OK
+>>> reverse_client_restrictions check_reverse_client_a_access,inline:{168.100.189.2=reject}
+bad command
+>>> client spike.porcupine.org 1.2.3.4
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[1.2.3.4]: 554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied
+>>> client www.porcupine.org 1.2.3.4
+OK
+>>> sender_restrictions check_sender_a_access,inline:{168.100.189.2=reject}
+OK
+>>> mail foo@spike.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from www.porcupine.org[1.2.3.4]: 554 5.7.1 <foo@spike.porcupine.org>: Sender address rejected: Access denied; from=<foo@spike.porcupine.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <foo@spike.porcupine.org>: Sender address rejected: Access denied
+>>> mail foo@www.porcupine.org
+OK
+>>> recipient_restrictions check_recipient_a_access,inline:{168.100.189.2=reject}
+OK
+>>> rcpt foo@spike.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from www.porcupine.org[1.2.3.4]: 554 5.7.1 <foo@spike.porcupine.org>: Recipient address rejected: Access denied; from=<foo@www.porcupine.org> to=<foo@spike.porcupine.org> proto=SMTP helo=<www.porcupine.org>
+554 5.7.1 <foo@spike.porcupine.org>: Recipient address rejected: Access denied
+>>> rcpt foo@www.porcupine.org
+OK
diff --git a/src/smtpd/smtpd_state.c b/src/smtpd/smtpd_state.c
new file mode 100644
index 0000000..f2f5f89
--- /dev/null
+++ b/src/smtpd/smtpd_state.c
@@ -0,0 +1,248 @@
+/*++
+/* NAME
+/* smtpd_state 3
+/* SUMMARY
+/* Postfix SMTP server
+/* SYNOPSIS
+/* #include "smtpd.h"
+/*
+/* void smtpd_state_init(state, stream, service)
+/* SMTPD_STATE *state;
+/* VSTREAM *stream;
+/* const char *service;
+/*
+/* void smtpd_state_reset(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* smtpd_state_init() initializes session context.
+/*
+/* smtpd_state_reset() cleans up session context.
+/*
+/* Arguments:
+/* .IP state
+/* Session context.
+/* .IP stream
+/* Stream connected to peer. The stream is not copied.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <events.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <cleanup_user.h>
+#include <mail_params.h>
+#include <mail_error.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_chat.h"
+#include "smtpd_sasl_glue.h"
+
+/* smtpd_state_init - initialize after connection establishment */
+
+void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream,
+ const char *service)
+{
+
+ /*
+ * Initialize the state information for this connection, and fill in the
+ * connection-specific fields.
+ */
+ state->flags = 0;
+ state->err = CLEANUP_STAT_OK;
+ state->client = stream;
+ state->service = mystrdup(service);
+ state->buffer = vstring_alloc(100);
+ state->addr_buf = vstring_alloc(100);
+ state->conn_count = state->conn_rate = 0;
+ state->error_count = 0;
+ state->error_mask = 0;
+ state->notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ state->helo_name = 0;
+ state->queue_id = 0;
+ state->cleanup = 0;
+ state->dest = 0;
+ state->rcpt_count = 0;
+ state->access_denied = 0;
+ state->history = 0;
+ state->reason = 0;
+ state->sender = 0;
+ state->verp_delims = 0;
+ state->recipient = 0;
+ state->etrn_name = 0;
+ state->protocol = mystrdup(MAIL_PROTO_SMTP);
+ state->where = SMTPD_AFTER_CONNECT;
+ state->recursion = 0;
+ state->msg_size = 0;
+ state->act_size = 0;
+ state->junk_cmds = 0;
+ state->rcpt_overshoot = 0;
+ state->defer_if_permit_client = 0;
+ state->defer_if_permit_helo = 0;
+ state->defer_if_permit_sender = 0;
+ state->defer_if_reject.dsn = 0;
+ state->defer_if_reject.reason = 0;
+ state->defer_if_permit.dsn = 0;
+ state->defer_if_permit.reason = 0;
+ state->discard = 0;
+ state->expand_buf = 0;
+ state->prepend = 0;
+ state->proxy = 0;
+ state->proxy_mail = 0;
+ state->saved_filter = 0;
+ state->saved_redirect = 0;
+ state->saved_bcc = 0;
+ state->saved_flags = 0;
+#ifdef DELAY_ACTION
+ state->saved_delay = 0;
+#endif
+ state->instance = vstring_alloc(10);
+ state->seqno = 0;
+ state->rewrite_context = 0;
+#if 0
+ state->ehlo_discard_mask = ~0;
+#else
+ state->ehlo_discard_mask = 0;
+#endif
+ state->dsn_envid = 0;
+ state->dsn_buf = vstring_alloc(100);
+ state->dsn_orcpt_buf = vstring_alloc(100);
+#ifdef USE_TLS
+#ifdef USE_TLSPROXY
+ state->tlsproxy = 0;
+#endif
+ state->tls_context = 0;
+#endif
+
+ /*
+ * Minimal initialization to support external authentication (e.g.,
+ * XCLIENT) without having to enable SASL in main.cf.
+ */
+#ifdef USE_SASL_AUTH
+ if (SMTPD_STAND_ALONE(state))
+ var_smtpd_sasl_enable = 0;
+ smtpd_sasl_set_inactive(state);
+ smtpd_sasl_state_init(state);
+#endif
+
+ state->milter_argv = 0;
+ state->milter_argc = 0;
+ state->milters = 0;
+
+ /*
+ * Initialize peer information.
+ */
+ smtpd_peer_init(state);
+
+ /*
+ * Initialize xforward information.
+ */
+ smtpd_xforward_init(state);
+
+ /*
+ * Initialize the conversation history.
+ */
+ smtpd_chat_reset(state);
+
+ state->ehlo_argv = 0;
+ state->ehlo_buf = 0;
+
+ /*
+ * BDAT.
+ */
+ state->bdat_state = SMTPD_BDAT_STAT_NONE;
+ state->bdat_get_stream = 0;
+ state->bdat_get_buffer = 0;
+}
+
+/* smtpd_state_reset - cleanup after disconnect */
+
+void smtpd_state_reset(SMTPD_STATE *state)
+{
+
+ /*
+ * When cleaning up, touch only those fields that smtpd_state_init()
+ * filled in. The other fields are taken care of by their own
+ * "destructor" functions.
+ */
+ if (state->service)
+ myfree(state->service);
+ if (state->buffer)
+ vstring_free(state->buffer);
+ if (state->addr_buf)
+ vstring_free(state->addr_buf);
+ if (state->access_denied)
+ myfree(state->access_denied);
+ if (state->protocol)
+ myfree(state->protocol);
+ smtpd_peer_reset(state);
+
+ /*
+ * Buffers that are created on the fly and that may be shared among mail
+ * deliveries within the same SMTP session.
+ */
+ if (state->defer_if_permit.dsn)
+ vstring_free(state->defer_if_permit.dsn);
+ if (state->defer_if_permit.reason)
+ vstring_free(state->defer_if_permit.reason);
+ if (state->defer_if_reject.dsn)
+ vstring_free(state->defer_if_reject.dsn);
+ if (state->defer_if_reject.reason)
+ vstring_free(state->defer_if_reject.reason);
+ if (state->expand_buf)
+ vstring_free(state->expand_buf);
+ if (state->instance)
+ vstring_free(state->instance);
+ if (state->dsn_buf)
+ vstring_free(state->dsn_buf);
+ if (state->dsn_orcpt_buf)
+ vstring_free(state->dsn_orcpt_buf);
+#if (defined(USE_TLS) && defined(USE_TLSPROXY))
+ if (state->tlsproxy) /* still open after longjmp */
+ vstream_fclose(state->tlsproxy);
+#endif
+
+ /*
+ * BDAT.
+ */
+ if (state->bdat_get_stream)
+ (void) vstream_fclose(state->bdat_get_stream);
+ if (state->bdat_get_buffer)
+ vstring_free(state->bdat_get_buffer);
+}
diff --git a/src/smtpd/smtpd_token.c b/src/smtpd/smtpd_token.c
new file mode 100644
index 0000000..927088f
--- /dev/null
+++ b/src/smtpd/smtpd_token.c
@@ -0,0 +1,233 @@
+/*++
+/* NAME
+/* smtpd_token 3
+/* SUMMARY
+/* tokenize SMTPD command
+/* SYNOPSIS
+/* #include <smtpd_token.h>
+/*
+/* typedef struct {
+/* .in +4
+/* int tokval;
+/* char *strval;
+/* /* other stuff... */
+/* .in -4
+/* } SMTPD_TOKEN;
+/*
+/* int smtpd_token(str, argvp)
+/* char *str;
+/* SMTPD_TOKEN **argvp;
+/* DESCRIPTION
+/* smtpd_token() routine converts the string in \fIstr\fR to an
+/* array of tokens in \fIargvp\fR. The number of tokens is returned
+/* via the function return value.
+/*
+/* Token types:
+/* .IP SMTPD_TOK_OTHER
+/* The token is something else.
+/* .IP SMTPD_TOK_ERROR
+/* A malformed token.
+/* BUGS
+/* This tokenizer understands just enough to tokenize SMTPD commands.
+/* It understands backslash escapes, white space, quoted strings,
+/* and addresses (including quoted text) enclosed by < and >.
+/* The input is broken up into tokens by whitespace, except for
+/* whitespace that is protected by quotes etc.
+/* 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 <ctype.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <mvect.h>
+
+/* Application-specific. */
+
+#include "smtpd_token.h"
+
+/* smtp_quoted - read until closing quote */
+
+static char *smtp_quoted(char *cp, SMTPD_TOKEN *arg, int start, int last)
+{
+ static VSTRING *stack;
+ int wanted;
+ int c;
+
+ /*
+ * Parser stack. `ch' is always the most-recently entered character.
+ */
+#define ENTER_CHAR(buf, ch) VSTRING_ADDCH(buf, ch);
+#define LEAVE_CHAR(buf, ch) { \
+ vstring_truncate(buf, VSTRING_LEN(buf) - 1); \
+ ch = vstring_end(buf)[-1]; \
+ }
+
+ if (stack == 0)
+ stack = vstring_alloc(1);
+ VSTRING_RESET(stack);
+ ENTER_CHAR(stack, wanted = last);
+
+ VSTRING_ADDCH(arg->vstrval, start);
+ for (;;) {
+ if ((c = *cp) == 0)
+ break;
+ cp++;
+ VSTRING_ADDCH(arg->vstrval, c);
+ if (c == '\\') { /* parse escape sequence */
+ if ((c = *cp) == 0)
+ break;
+ cp++;
+ VSTRING_ADDCH(arg->vstrval, c);
+ } else if (c == wanted) { /* closing quote etc. */
+ if (VSTRING_LEN(stack) == 1)
+ return (cp);
+ LEAVE_CHAR(stack, wanted);
+ } else if (c == '"') {
+ ENTER_CHAR(stack, wanted = '"'); /* highest precedence */
+ } else if (c == '<' && wanted == '>') {
+ ENTER_CHAR(stack, wanted = '>'); /* lowest precedence */
+ }
+ }
+ arg->tokval = SMTPD_TOK_ERROR; /* missing end */
+ return (cp);
+}
+
+/* smtp_next_token - extract next token from input, update cp */
+
+static char *smtp_next_token(char *cp, SMTPD_TOKEN *arg)
+{
+ int c;
+
+ VSTRING_RESET(arg->vstrval);
+ arg->tokval = SMTPD_TOK_OTHER;
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define STREQ(x,y,l) (strncasecmp((x), (y), (l)) == 0)
+
+ for (;;) {
+ if ((c = *cp) == 0) /* end of input */
+ break;
+ cp++;
+ if (ISSPACE(c)) { /* whitespace, skip */
+ while (*cp && ISSPACE(*cp))
+ cp++;
+ if (LEN(arg->vstrval) > 0) /* end of token */
+ break;
+ } else if (c == '<') { /* <stuff> */
+ cp = smtp_quoted(cp, arg, c, '>');
+ } else if (c == '"') { /* "stuff" */
+ cp = smtp_quoted(cp, arg, c, c);
+ } else if (c == ':') { /* this is gross, but... */
+ VSTRING_ADDCH(arg->vstrval, c);
+ if (STREQ(STR(arg->vstrval), "to:", LEN(arg->vstrval))
+ || STREQ(STR(arg->vstrval), "from:", LEN(arg->vstrval)))
+ break;
+ } else { /* other */
+ if (c == '\\') {
+ VSTRING_ADDCH(arg->vstrval, c);
+ if ((c = *cp) == 0)
+ break;
+ cp++;
+ }
+ VSTRING_ADDCH(arg->vstrval, c);
+ }
+ }
+ if (LEN(arg->vstrval) <= 0) /* no token found */
+ return (0);
+ VSTRING_TERMINATE(arg->vstrval);
+ arg->strval = vstring_str(arg->vstrval);
+ return (cp);
+}
+
+/* smtpd_token_init - initialize token structures */
+
+static void smtpd_token_init(char *ptr, ssize_t count)
+{
+ SMTPD_TOKEN *arg;
+ int n;
+
+ for (arg = (SMTPD_TOKEN *) ptr, n = 0; n < count; arg++, n++)
+ arg->vstrval = vstring_alloc(10);
+}
+
+/* smtpd_token - tokenize SMTPD command */
+
+int smtpd_token(char *cp, SMTPD_TOKEN **argvp)
+{
+ static SMTPD_TOKEN *smtp_argv;
+ static MVECT mvect;
+ int n;
+
+ if (smtp_argv == 0)
+ smtp_argv = (SMTPD_TOKEN *) mvect_alloc(&mvect, sizeof(*smtp_argv), 1,
+ smtpd_token_init, (MVECT_FN) 0);
+ for (n = 0; /* void */ ; n++) {
+ smtp_argv = (SMTPD_TOKEN *) mvect_realloc(&mvect, n + 1);
+ if ((cp = smtp_next_token(cp, smtp_argv + n)) == 0)
+ break;
+ }
+ *argvp = smtp_argv;
+ return (n);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program for the SMTPD command tokenizer.
+ */
+
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *vp = vstring_alloc(10);
+ int tok_argc;
+ SMTPD_TOKEN *tok_argv;
+ int i;
+
+ for (;;) {
+ if (isatty(STDIN_FILENO))
+ vstream_printf("enter SMTPD command: ");
+ vstream_fflush(VSTREAM_OUT);
+ if (vstring_get_nonl(vp, VSTREAM_IN) == VSTREAM_EOF)
+ break;
+ if (*vstring_str(vp) == '#')
+ continue;
+ if (!isatty(STDIN_FILENO))
+ vstream_printf("%s\n", vstring_str(vp));
+ tok_argc = smtpd_token(vstring_str(vp), &tok_argv);
+ for (i = 0; i < tok_argc; i++) {
+ vstream_printf("Token type: %s\n",
+ tok_argv[i].tokval == SMTPD_TOK_OTHER ? "other" :
+ tok_argv[i].tokval == SMTPD_TOK_ERROR ? "error" :
+ "unknown");
+ vstream_printf("Token value: %s\n", tok_argv[i].strval);
+ }
+ }
+ vstring_free(vp);
+ exit(0);
+}
+
+#endif
diff --git a/src/smtpd/smtpd_token.h b/src/smtpd/smtpd_token.h
new file mode 100644
index 0000000..88489fa
--- /dev/null
+++ b/src/smtpd/smtpd_token.h
@@ -0,0 +1,40 @@
+/*++
+/* NAME
+/* smtpd_token 3h
+/* SUMMARY
+/* tokenize SMTPD command
+/* SYNOPSIS
+/* #include <smtpd_token.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct SMTPD_TOKEN {
+ int tokval;
+ char *strval;
+ VSTRING *vstrval;
+} SMTPD_TOKEN;
+
+#define SMTPD_TOK_OTHER 0
+#define SMTPD_TOK_ADDR 1
+#define SMTPD_TOK_ERROR 2
+
+extern int smtpd_token(char *, SMTPD_TOKEN **);
+
+/* 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/smtpd/smtpd_token.in b/src/smtpd/smtpd_token.in
new file mode 100644
index 0000000..d67d5d0
--- /dev/null
+++ b/src/smtpd/smtpd_token.in
@@ -0,0 +1,12 @@
+mail from:<wietse@porcupine.org>
+mail from:<"wietse venema"@porcupine.org>
+mail from:wietse@porcupine.org
+mail from:<wietse @ porcupine.org>
+mail from:<"wietse venema"@porcupine.org ("wietse ) venema")>
+mail from:<"wietse venema" <wietse@porcupine.org>>
+mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )>
+mail from:"wietse venema"@porcupine.org
+mail from:wietse\ venema@porcupine.org
+mail to:<"wietse venema>
+mail to:<wietse@[stuff>
+mail to:<wietse@["stuff]>
diff --git a/src/smtpd/smtpd_token.ref b/src/smtpd/smtpd_token.ref
new file mode 100644
index 0000000..21dc969
--- /dev/null
+++ b/src/smtpd/smtpd_token.ref
@@ -0,0 +1,84 @@
+mail from:<wietse@porcupine.org>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <wietse@porcupine.org>
+mail from:<"wietse venema"@porcupine.org>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <"wietse venema"@porcupine.org>
+mail from:wietse@porcupine.org
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: wietse@porcupine.org
+mail from:<wietse @ porcupine.org>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <wietse @ porcupine.org>
+mail from:<"wietse venema"@porcupine.org ("wietse ) venema")>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <"wietse venema"@porcupine.org ("wietse ) venema")>
+mail from:<"wietse venema" <wietse@porcupine.org>>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <"wietse venema" <wietse@porcupine.org>>
+mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )>
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: <"wietse venema"@porcupine.org ( ("wietse ) venema") )>
+mail from:"wietse venema"@porcupine.org
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: "wietse venema"@porcupine.org
+mail from:wietse\ venema@porcupine.org
+Token type: other
+Token value: mail
+Token type: other
+Token value: from:
+Token type: other
+Token value: wietse\ venema@porcupine.org
+mail to:<"wietse venema>
+Token type: other
+Token value: mail
+Token type: other
+Token value: to:
+Token type: error
+Token value: <"wietse venema>
+mail to:<wietse@[stuff>
+Token type: other
+Token value: mail
+Token type: other
+Token value: to:
+Token type: other
+Token value: <wietse@[stuff>
+mail to:<wietse@["stuff]>
+Token type: other
+Token value: mail
+Token type: other
+Token value: to:
+Token type: error
+Token value: <wietse@["stuff]>
diff --git a/src/smtpd/smtpd_xforward.c b/src/smtpd/smtpd_xforward.c
new file mode 100644
index 0000000..053d377
--- /dev/null
+++ b/src/smtpd/smtpd_xforward.c
@@ -0,0 +1,114 @@
+/*++
+/* NAME
+/* smtpd_xforward 3
+/* SUMMARY
+/* maintain XCLIENT information
+/* SYNOPSIS
+/* #include "smtpd.h"
+/*
+/* void smtpd_xforward_init(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_xforward_preset(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_xforward_reset(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* smtpd_xforward_init() zeroes the attributes for storage of
+/* XFORWARD command parameters.
+/*
+/* smtpd_xforward_preset() takes the result from smtpd_xforward_init()
+/* and sets all fields to the same "unknown" value that regular
+/* client attributes would have.
+/*
+/* smtpd_xforward_reset() restores the state from smtpd_xforward_init().
+/* 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 <msg.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <smtpd.h>
+
+/* smtpd_xforward_init - initialize xforward attributes */
+
+void smtpd_xforward_init(SMTPD_STATE *state)
+{
+ state->xforward.flags = 0;
+ state->xforward.name = 0;
+ state->xforward.addr = 0;
+ state->xforward.port = 0;
+ state->xforward.namaddr = 0;
+ state->xforward.protocol = 0;
+ state->xforward.helo_name = 0;
+ state->xforward.ident = 0;
+ state->xforward.domain = 0;
+}
+
+/* smtpd_xforward_preset - set xforward attributes to "unknown" */
+
+void smtpd_xforward_preset(SMTPD_STATE *state)
+{
+
+ /*
+ * Sanity checks.
+ */
+ if (state->xforward.flags)
+ msg_panic("smtpd_xforward_preset: bad flags: 0x%x",
+ state->xforward.flags);
+
+ /*
+ * This is a temporary solution. Unknown forwarded attributes get the
+ * same values as unknown normal attributes, so that we don't break
+ * assumptions in pre-existing code.
+ */
+ state->xforward.flags = SMTPD_STATE_XFORWARD_INIT;
+ state->xforward.name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->xforward.addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->xforward.port = mystrdup(CLIENT_PORT_UNKNOWN);
+ state->xforward.namaddr = mystrdup(CLIENT_NAMADDR_UNKNOWN);
+ state->xforward.rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ /* Leave helo at zero. */
+ state->xforward.protocol = mystrdup(CLIENT_PROTO_UNKNOWN);
+ /* Leave ident at zero. */
+ /* Leave domain context at zero. */
+}
+
+/* smtpd_xforward_reset - reset xforward attributes */
+
+void smtpd_xforward_reset(SMTPD_STATE *state)
+{
+#define FREE_AND_WIPE(s) { if (s) myfree(s); s = 0; }
+
+ state->xforward.flags = 0;
+ FREE_AND_WIPE(state->xforward.name);
+ FREE_AND_WIPE(state->xforward.addr);
+ FREE_AND_WIPE(state->xforward.port);
+ FREE_AND_WIPE(state->xforward.namaddr);
+ FREE_AND_WIPE(state->xforward.rfc_addr);
+ FREE_AND_WIPE(state->xforward.protocol);
+ FREE_AND_WIPE(state->xforward.helo_name);
+ FREE_AND_WIPE(state->xforward.ident);
+ FREE_AND_WIPE(state->xforward.domain);
+}
diff --git a/src/smtpstone/.indent.pro b/src/smtpstone/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/smtpstone/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/smtpstone/.printfck b/src/smtpstone/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/smtpstone/.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/smtpstone/Makefile.in b/src/smtpstone/Makefile.in
new file mode 100644
index 0000000..f86e019
--- /dev/null
+++ b/src/smtpstone/Makefile.in
@@ -0,0 +1,172 @@
+SHELL = /bin/sh
+SRCS = smtp-source.c smtp-sink.c qmqp-source.c qmqp-sink.c
+OBJS = smtp-source.o smtp-sink.o qmqp-source.o qmqp-sink.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+INC_DIR = ../../include
+PROG = smtp-source smtp-sink qmqp-source qmqp-sink
+LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(PROG)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+smtp-sink: smtp-sink.o $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-sink.o $(LIBS) $(SYSLIBS)
+
+smtp-source: smtp-source.o $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-source.o $(LIBS) $(SYSLIBS)
+
+qmqp-sink: qmqp-sink.o $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-sink.o $(LIBS) $(SYSLIBS)
+
+qmqp-source: qmqp-source.o $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-source.o $(LIBS) $(SYSLIBS)
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+update: ../../bin/smtp-source ../../bin/smtp-sink ../../bin/qmqp-source \
+ ../../bin/qmqp-sink
+
+../../bin/smtp-source: smtp-source
+ cp $? $@
+
+../../bin/smtp-sink: smtp-sink
+ cp $? $@
+
+../../bin/qmqp-source: qmqp-source
+ cp $? $@
+
+../../bin/qmqp-sink: qmqp-sink
+ cp $? $@
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir 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
+
+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'
+qmqp-sink.o: ../../include/check_arg.h
+qmqp-sink.o: ../../include/events.h
+qmqp-sink.o: ../../include/htable.h
+qmqp-sink.o: ../../include/inet_proto.h
+qmqp-sink.o: ../../include/iostuff.h
+qmqp-sink.o: ../../include/listen.h
+qmqp-sink.o: ../../include/mail_version.h
+qmqp-sink.o: ../../include/msg.h
+qmqp-sink.o: ../../include/msg_vstream.h
+qmqp-sink.o: ../../include/mymalloc.h
+qmqp-sink.o: ../../include/netstring.h
+qmqp-sink.o: ../../include/qmqp_proto.h
+qmqp-sink.o: ../../include/sys_defs.h
+qmqp-sink.o: ../../include/vbuf.h
+qmqp-sink.o: ../../include/vstream.h
+qmqp-sink.o: ../../include/vstring.h
+qmqp-sink.o: qmqp-sink.c
+qmqp-source.o: ../../include/check_arg.h
+qmqp-source.o: ../../include/connect.h
+qmqp-source.o: ../../include/events.h
+qmqp-source.o: ../../include/get_hostname.h
+qmqp-source.o: ../../include/host_port.h
+qmqp-source.o: ../../include/inet_proto.h
+qmqp-source.o: ../../include/iostuff.h
+qmqp-source.o: ../../include/mail_date.h
+qmqp-source.o: ../../include/mail_version.h
+qmqp-source.o: ../../include/msg.h
+qmqp-source.o: ../../include/msg_vstream.h
+qmqp-source.o: ../../include/myaddrinfo.h
+qmqp-source.o: ../../include/mymalloc.h
+qmqp-source.o: ../../include/netstring.h
+qmqp-source.o: ../../include/qmqp_proto.h
+qmqp-source.o: ../../include/sane_connect.h
+qmqp-source.o: ../../include/split_at.h
+qmqp-source.o: ../../include/sys_defs.h
+qmqp-source.o: ../../include/valid_hostname.h
+qmqp-source.o: ../../include/valid_mailhost_addr.h
+qmqp-source.o: ../../include/vbuf.h
+qmqp-source.o: ../../include/vstream.h
+qmqp-source.o: ../../include/vstring.h
+qmqp-source.o: qmqp-source.c
+smtp-sink.o: ../../include/check_arg.h
+smtp-sink.o: ../../include/chroot_uid.h
+smtp-sink.o: ../../include/events.h
+smtp-sink.o: ../../include/get_hostname.h
+smtp-sink.o: ../../include/htable.h
+smtp-sink.o: ../../include/inet_proto.h
+smtp-sink.o: ../../include/iostuff.h
+smtp-sink.o: ../../include/listen.h
+smtp-sink.o: ../../include/mail_date.h
+smtp-sink.o: ../../include/mail_version.h
+smtp-sink.o: ../../include/make_dirs.h
+smtp-sink.o: ../../include/msg.h
+smtp-sink.o: ../../include/msg_vstream.h
+smtp-sink.o: ../../include/myaddrinfo.h
+smtp-sink.o: ../../include/mymalloc.h
+smtp-sink.o: ../../include/myrand.h
+smtp-sink.o: ../../include/sane_accept.h
+smtp-sink.o: ../../include/smtp_stream.h
+smtp-sink.o: ../../include/stringops.h
+smtp-sink.o: ../../include/sys_defs.h
+smtp-sink.o: ../../include/vbuf.h
+smtp-sink.o: ../../include/vstream.h
+smtp-sink.o: ../../include/vstring.h
+smtp-sink.o: ../../include/vstring_vstream.h
+smtp-sink.o: smtp-sink.c
+smtp-source.o: ../../include/check_arg.h
+smtp-source.o: ../../include/compat_va_copy.h
+smtp-source.o: ../../include/connect.h
+smtp-source.o: ../../include/events.h
+smtp-source.o: ../../include/get_hostname.h
+smtp-source.o: ../../include/host_port.h
+smtp-source.o: ../../include/inet_proto.h
+smtp-source.o: ../../include/iostuff.h
+smtp-source.o: ../../include/mail_date.h
+smtp-source.o: ../../include/mail_version.h
+smtp-source.o: ../../include/msg.h
+smtp-source.o: ../../include/msg_vstream.h
+smtp-source.o: ../../include/myaddrinfo.h
+smtp-source.o: ../../include/mymalloc.h
+smtp-source.o: ../../include/sane_connect.h
+smtp-source.o: ../../include/smtp_stream.h
+smtp-source.o: ../../include/split_at.h
+smtp-source.o: ../../include/sys_defs.h
+smtp-source.o: ../../include/valid_hostname.h
+smtp-source.o: ../../include/valid_mailhost_addr.h
+smtp-source.o: ../../include/vbuf.h
+smtp-source.o: ../../include/vstream.h
+smtp-source.o: ../../include/vstring.h
+smtp-source.o: ../../include/vstring_vstream.h
+smtp-source.o: smtp-source.c
diff --git a/src/smtpstone/hashed-deferred b/src/smtpstone/hashed-deferred
new file mode 100644
index 0000000..93f0e12
--- /dev/null
+++ b/src/smtpstone/hashed-deferred
@@ -0,0 +1,37 @@
+Delivering 1000 deferred messages over the loopback transport,
+outbound concurrency 10. smtp-sink pipelining disabled. Machine is
+P230, BSD/OS 3.1, 64MB memory.
+
+hashing is 16 directories per level
+
+flat deferred queue
+
+ start: Sun Feb 21 16:42:37 EST 1999
+ done: Feb 21 16:44:35
+ time: 1:58 = 118 seconds
+
+ start: Sun Feb 21 16:48:01 EST 1999
+ done: Feb 21 16:49:51
+ time: 1:50 = 110 seconds
+
+hashed deferred queue, depth=1 (16 directories)
+
+ start: Sun Feb 21 17:29:36 EST 1999
+ done: Feb 21 17:31:32
+ time: 1:56 = 116 seconds
+
+ start: Sun Feb 21 17:33:36 EST 1999
+ done: Feb 21 17:35:24
+ time: 1:48 = 108 seconds
+
+ start: Sun Feb 21 17:37:08 EST 1999
+ done: Feb 21 17:39:02
+ time: 1:52 = 112 seconds
+
+Hashing does not slow down deliveries.
+
+However the problem is scanning an empty deferred queue. On an idle
+machine, it takes some 5 seconds to scan an empty depth=2 deferred
+queue unless the blocks happen to be cached. During those 5 seconds
+the queue manager will not pay attention to I/O from delivery
+agents, which is bad.
diff --git a/src/smtpstone/hashed-incoming b/src/smtpstone/hashed-incoming
new file mode 100644
index 0000000..7bb1993
--- /dev/null
+++ b/src/smtpstone/hashed-incoming
@@ -0,0 +1,48 @@
+Sending 1000 messages through postfix over the loopback transport,
+inbound concurrency 10. smtp-sink pipelining disabled. Machine is
+P230, BSD/OS 3.1
+
+accepted = time for postfix to accept all messages
+moved = number of messages delivered by postfix when all mail accepted
+done = time for postfix to deliver last message
+
+hashing is 16 directories per level
+
+flat incoming queue
+
+ start: Sun Feb 21 15:12:44 EST 1999
+ accepted: 66.10 real 0.50 user 1.56 sys
+ moved: 122
+ done: Feb 21 15:15:19
+
+ total time: 2:35 = 155 seconds
+
+ start: Sun Feb 21 15:30:44 EST 1999
+ accepted: 67.47 real 0.48 user 1.60 sys
+ moved:
+ done: Feb 21 15:33:10
+
+ total time: 2:26 = 146 seconds
+
+hashed incoming queue, depth=1 (16 subdirs)
+
+ start: Sun Feb 21 15:39:43 EST 1999
+ accepted: 84.42 real 0.49 user 1.66 sys
+ moved: 144
+ done: Feb 21 15:42:27
+
+ total time: 2:44 = 164 seconds
+
+ start: Sun Feb 21 15:42:43 EST 1999
+ accepted: 84.57 real 0.46 user 1.67 sys
+ moved:
+ done: Feb 21 15:45:34
+
+ total time: 2:51 = 171 seconds
+
+ start: Sun Feb 21 15:47:11 EST 1999
+ accepted: 84.11 real 0.34 user 1.72 sys
+ moved:
+ done: Feb 21 15:49:54
+
+ total time: 2:43 = 163 seconds
diff --git a/src/smtpstone/mx-deliver b/src/smtpstone/mx-deliver
new file mode 100644
index 0000000..a4f44a0
--- /dev/null
+++ b/src/smtpstone/mx-deliver
@@ -0,0 +1,20 @@
+MX needs 24 seconds to deliver 100 SMTP messages to one local user.
+smtpd/local-delivery process limit = 100
+
+/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@fist.porcupine.org fist
+ 10.47 real 0.12 user 0.16 sys
+Jun 8 14:45:25 fist mx:smtpd[19432]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:45:49 fist mx:local[19444]: 085788: to=<wietse@porcupine.org>, relay=l
+Total time: 24 seconds
+
+/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse@fist.porcupine.org fist
+ 9.10 real 0.06 user 0.26 sys
+Jun 8 14:46:42 fist mx:smtpd[19443]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:47:06 fist mx:local[19446]: 085792: to=<wietse@porcupine.org>, relay=l
+Total time: 24 seconds
+
+/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@fist.porcupine.org fist
+ 9.84 real 0.09 user 0.28 sys
+Jun 8 14:48:03 fist mx:smtpd[19458]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:48:28 fist mx:local[19479]: 085795: to=<wietse@porcupine.org>, relay=l
+Total time: 25 seconds
diff --git a/src/smtpstone/mx-explode b/src/smtpstone/mx-explode
new file mode 100644
index 0000000..e997147
--- /dev/null
+++ b/src/smtpstone/mx-explode
@@ -0,0 +1,33 @@
+MX needs 12 seconds per 1000 different remote destinations.
+smtp process limit = 100, bundle_recipients = 0.
+
+/usr/bin/time ./smtp-source -r 1000 fist
+ 1.13 real 0.07 user 0.27 sys
+Jun 8 13:32:18 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1
+Jun 8 13:32:31 fist mx:smtp[18209]: 085688: to=<544foo@spike.porcupine.org>, re
+Total time: 13 seconds
+
+/usr/bin/time ./smtp-source -r 2000 fist
+ 2.55 real 0.21 user 0.48 sys
+Jun 8 13:33:23 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1
+Jun 8 13:33:48 fist mx:smtp[18184]: 085693: to=<1041foo@spike.porcupine.org>, r
+Total time: 25 seconds
+
+/usr/bin/time ./smtp-source -r 5000 fist
+[test generating machine ran out of resources while receiving mail]
+
+/usr/bin/time ./smtp-source -r 1000 fist
+ 1.38 real 0.17 user 0.16 sys
+Jun 8 15:20:33 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1
+Jun 8 15:20:46 fist mx:smtp[27724]: 085687: to=<493foo@spike.porcupine.org>, re
+Total time: 13 seconds
+
+/usr/bin/time ./smtp-source -r 2000 fist
+ 2.64 real 0.23 user 0.46 sys
+Jun 8 15:20:52 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1
+Jun 8 15:21:16 fist mx:smtp[27743]: 085687: to=<1086foo@spike.porcupine.org>, r
+Total time: 24 seconds
+
+/usr/bin/time ./smtp-source -r 5000 fist
+[test generating machine ran out of resources while receiving mail]
+
diff --git a/src/smtpstone/mx-relay b/src/smtpstone/mx-relay
new file mode 100644
index 0000000..a5930cb
--- /dev/null
+++ b/src/smtpstone/mx-relay
@@ -0,0 +1,20 @@
+MX needs 19 seconds to relay 100 messages with one recipient.
+smtp/smtpd process limit = 100, bundle_recipients = 0.
+
+/usr/bin/time ./smtp-source -s 5 -m 100 fist
+ 9.56 real 0.07 user 0.20 sys
+Jun 8 14:33:19 fist mx:smtpd[19366]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:33:36 fist mx:smtp[19382]: 085781: to=<foo@spike.porcupine.org>, relay
+Total time: 17 seconds
+
+/usr/bin/time ./smtp-source -s 10 -m 100 fist
+ 8.95 real 0.12 user 0.19 sys
+Jun 8 14:34:22 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:34:41 fist mx:smtp[19378]: 085792: to=<foo@spike.porcupine.org>, relay
+Total time: 19 seconds
+
+/usr/bin/time ./smtp-source -s 20 -m 100 fist
+ 9.79 real 0.11 user 0.27 sys
+Jun 8 14:35:18 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1
+Jun 8 14:35:38 fist mx:smtp[19382]: 085794: to=<foo@spike.porcupine.org>, relay
+Total time: 20 seconds
diff --git a/src/smtpstone/performance b/src/smtpstone/performance
new file mode 100644
index 0000000..805676f
--- /dev/null
+++ b/src/smtpstone/performance
@@ -0,0 +1,28 @@
+List performance: time to forward one SMTP message with 1000, 2000
+and 5000 different remote destinations. Outbound SMTP concurrency
+= 100.
+
+dests 1000 2000 5000 time per 1000
+=============================================
+qmail 15 32 80 16
+mx 13 25 (*) 13
+
+(*) message sink host saturated under the load
+
+Local delivery performance: time to deliver 100 SMTP messages to
+one recipient. Outbound SMTP concurrency = 100, inbound SMTP
+concurrency = 5, 10, 20.
+
+concur 5 10 20 average time
+============================================
+qmail 62 59 58 60
+mx 24 24 25 24
+
+Relay performance: time to forward 100 SMTP messages with one
+recipient. Outbound SMTP concurrency = 100, inbound SMTP concurrency
+= 5, 10, 20.
+
+concur 5 10 20 average time
+============================================
+qmail 56 54 54 55
+mx 17 19 20 19
diff --git a/src/smtpstone/qmail-deliver b/src/smtpstone/qmail-deliver
new file mode 100644
index 0000000..e00ab39
--- /dev/null
+++ b/src/smtpstone/qmail-deliver
@@ -0,0 +1,20 @@
+Qmail needs 59 seconds to deliver 100 SMTP messages to one local recipient.
+Default configuration, concurrencyremote = 100.
+
+/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse fist
+ 41.45 real 0.07 user 0.21 sys
+Jun 8 12:17:20 fist qmail: 865786640.494072 new msg 39901
+Jun 8 12:18:22 fist qmail: 865786702.301087 end msg 39982
+Total time: 62 sec
+
+/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse fist
+ 26.50 real 0.10 user 0.22 sys
+Jun 8 12:14:49 fist qmail: 865786489.089492 new msg 39901
+Jun 8 12:15:48 fist qmail: 865786548.316898 end msg 39928
+Total time: 59 sec
+
+/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse fist
+ 21.15 real 0.08 user 0.30 sys
+Jun 8 12:19:18 fist qmail: 865786758.939755 new msg 39903
+Jun 8 12:20:16 fist qmail: 865786816.739912 end msg 40031
+Total time: 58 sec
diff --git a/src/smtpstone/qmail-explode b/src/smtpstone/qmail-explode
new file mode 100644
index 0000000..ed40e2c
--- /dev/null
+++ b/src/smtpstone/qmail-explode
@@ -0,0 +1,20 @@
+Qmail needs 16 seconds per 1000 destinations.
+Default configuration, concurrencyremote = 100.
+
+/usr/bin/time ./smtp-source -r 1000 fist
+ 1.16 real 0.09 user 0.24 sys
+Jun 8 14:57:17 fist qmail: 865796237.334359 new msg 39906
+Jun 8 14:57:32 fist qmail: 865796252.632756 delivery 2154: success: 168.100.189
+Total time: 15 seconds
+
+/usr/bin/time ./smtp-source -r 2000 fist
+ 1.99 real 0.23 user 0.45 sys
+Jun 8 14:58:11 fist qmail: 865796291.817523 new msg 39907
+Jun 8 14:58:43 fist qmail: 865796323.174117 delivery 4116: success: 168.100.189
+Total time: 32 seconds
+
+/usr/bin/time ./smtp-source -r 5000 fist
+ 4.63 real 0.58 user 1.10 sys
+Jun 8 14:59:23 fist qmail: 865796363.346735 new msg 39908
+Jun 8 15:00:43 fist qmail: 865796443.209168 delivery 9153: success: 168.100.189
+Total time: 80 seconds
diff --git a/src/smtpstone/qmail-relay b/src/smtpstone/qmail-relay
new file mode 100644
index 0000000..8718e5e
--- /dev/null
+++ b/src/smtpstone/qmail-relay
@@ -0,0 +1,20 @@
+Qmail needs 54 seconds to relay 100 messages with one recipient.
+Default configuration, concurrencyremote = 100.
+
+spike_4% /usr/bin/time ./smtp-source -s 5 -m 100 fist
+ 43.77 real 0.05 user 0.23 sys
+Jun 8 12:08:36 fist qmail: 865786116.744366 new msg 39901
+Jun 8 12:09:32 fist qmail: 865786172.791473 end msg 39921
+Total time: 56 sec
+
+/usr/bin/time ./smtp-source -s 10 -m 100 fist
+ 26.66 real 0.06 user 0.26 sys
+Jun 8 12:06:20 fist qmail: 865785980.185885 new msg 39901
+Jun 8 12:07:14 fist qmail: 865786034.306429 end msg 39920
+Total time: 54 sec
+
+spike_8% /usr/bin/time ./smtp-source -s 20 -m 100 fist
+ 20.94 real 0.11 user 0.27 sys
+Jun 8 12:10:52 fist qmail: 865786252.412648 new msg 39901
+Jun 8 12:11:46 fist qmail: 865786306.080605 end msg 39962
+Total time: 54 sec
diff --git a/src/smtpstone/qmqp-sink.c b/src/smtpstone/qmqp-sink.c
new file mode 100644
index 0000000..2fcbdd1
--- /dev/null
+++ b/src/smtpstone/qmqp-sink.c
@@ -0,0 +1,325 @@
+/*++
+/* NAME
+/* qmqp-sink 1
+/* SUMMARY
+/* parallelized QMQP test server
+/* SYNOPSIS
+/* .fi
+/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR]
+/* [\fBinet:\fR][\fIhost\fR]:\fIport\fR \fIbacklog\fR
+/*
+/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR]
+/* \fBunix:\fR\fIpathname\fR \fIbacklog\fR
+/* DESCRIPTION
+/* \fBqmqp-sink\fR listens on the named host (or address) and port.
+/* It receives messages from the network and throws them away.
+/* The purpose is to measure QMQP client performance, not protocol
+/* compliance.
+/* Connections can be accepted on IPv4 or IPv6 endpoints, or on
+/* UNIX-domain sockets.
+/* IPv4 and IPv6 are the default.
+/* This program is the complement of the \fBqmqp-source\fR(1) program.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
+/* .IP \fB-4\fR
+/* Support IPv4 only. This option has no effect when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-6\fR
+/* Support IPv6 only. This option is not available when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-c\fR
+/* Display a running counter that is updated whenever a delivery
+/* is completed.
+/* .IP \fB-v\fR
+/* Increase verbosity. Specify \fB-v -v\fR to see some of the QMQP
+/* conversation.
+/* .IP "\fB-x \fItime\fR"
+/* Terminate after \fItime\fR seconds. This is to facilitate memory
+/* leak testing.
+/* SEE ALSO
+/* qmqp-source(1), QMQP message generator
+/* 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 <sys/wait.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <signal.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <listen.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <msg_vstream.h>
+#include <netstring.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <qmqp_proto.h>
+#include <mail_version.h>
+
+/* Application-specific. */
+
+typedef struct {
+ VSTREAM *stream; /* client connection */
+ int count; /* bytes to go */
+} SINK_STATE;
+
+static int var_tmout;
+static VSTRING *buffer;
+static void disconnect(SINK_STATE *);
+static int count_deliveries;
+static int counter;
+
+/* send_reply - finish conversation */
+
+static void send_reply(SINK_STATE *state)
+{
+ vstring_sprintf(buffer, "%cOk", QMQP_STAT_OK);
+ NETSTRING_PUT_BUF(state->stream, buffer);
+ netstring_fflush(state->stream);
+ if (count_deliveries) {
+ counter++;
+ vstream_printf("%d\r", counter);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ disconnect(state);
+}
+
+/* read_data - read over-all netstring data */
+
+static void read_data(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+ int fd = vstream_fileno(state->stream);
+ int count;
+
+ /*
+ * Refill the VSTREAM buffer, if necessary.
+ */
+ if (VSTREAM_GETC(state->stream) == VSTREAM_EOF)
+ netstring_except(state->stream, vstream_ftimeout(state->stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ state->count--;
+
+ /*
+ * Flush the VSTREAM buffer. As documented, vstream_fseek() discards
+ * unread input.
+ */
+ if ((count = vstream_peek(state->stream)) > 0) {
+ state->count -= count;
+ if (state->count <= 0) {
+ send_reply(state);
+ return;
+ }
+ vstream_fpurge(state->stream, VSTREAM_PURGE_BOTH);
+ }
+
+ /*
+ * Do not block while waiting for the arrival of more data.
+ */
+ event_disable_readwrite(fd);
+ event_enable_read(fd, read_data, context);
+}
+
+/* read_length - read over-all netstring length */
+
+static void read_length(int event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ switch (vstream_setjmp(state->stream)) {
+
+ default:
+ msg_panic("unknown error reading input");
+
+ case NETSTRING_ERR_TIME:
+ msg_panic("attempt to read non-readable socket");
+ /* NOTREACHED */
+
+ case NETSTRING_ERR_EOF:
+ msg_warn("lost connection");
+ disconnect(state);
+ return;
+
+ case NETSTRING_ERR_FORMAT:
+ msg_warn("netstring format error");
+ disconnect(state);
+ return;
+
+ case NETSTRING_ERR_SIZE:
+ msg_warn("netstring size error");
+ disconnect(state);
+ return;
+
+ /*
+ * Include the netstring terminator in the read byte count. This
+ * violates abstractions.
+ */
+ case 0:
+ state->count = netstring_get_length(state->stream) + 1;
+ read_data(event, context);
+ return;
+ }
+}
+
+/* disconnect - handle disconnection events */
+
+static void disconnect(SINK_STATE *state)
+{
+ event_disable_readwrite(vstream_fileno(state->stream));
+ vstream_fclose(state->stream);
+ myfree((void *) state);
+}
+
+/* connect_event - handle connection events */
+
+static void connect_event(int unused_event, void *context)
+{
+ int sock = CAST_ANY_PTR_TO_INT(context);
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE len = sizeof(ss);
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SINK_STATE *state;
+ int fd;
+
+ if ((fd = accept(sock, sa, &len)) >= 0) {
+ if (msg_verbose)
+ msg_info("connect (%s)",
+#ifdef AF_LOCAL
+ sa->sa_family == AF_LOCAL ? "AF_LOCAL" :
+#else
+ sa->sa_family == AF_UNIX ? "AF_UNIX" :
+#endif
+ sa->sa_family == AF_INET ? "AF_INET" :
+#ifdef AF_INET6
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
+#endif
+ "unknown protocol family");
+ non_blocking(fd, NON_BLOCKING);
+ state = (SINK_STATE *) mymalloc(sizeof(*state));
+ state->stream = vstream_fdopen(fd, O_RDWR);
+ vstream_tweak_sock(state->stream);
+ netstring_setup(state->stream, var_tmout);
+ event_enable_read(fd, read_length, (void *) state);
+ }
+}
+
+/* terminate - voluntary exit */
+
+static void terminate(int unused_event, void *unused_context)
+{
+ exit(0);
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s [-cv] [-x time] [host]:port backlog", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ int sock;
+ int backlog;
+ int ch;
+ int ttl;
+ const char *protocols = INET_PROTO_NAME_ALL;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Fix 20051207.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Initialize diagnostics.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "46cvx:")) > 0) {
+ switch (ch) {
+ case '4':
+ protocols = INET_PROTO_NAME_IPV4;
+ break;
+ case '6':
+ protocols = INET_PROTO_NAME_IPV6;
+ break;
+ case 'c':
+ count_deliveries++;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'x':
+ if ((ttl = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ event_request_timer(terminate, (void *) 0, ttl);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 2)
+ usage(argv[0]);
+ if ((backlog = atoi(argv[optind + 1])) <= 0)
+ usage(argv[0]);
+
+ /*
+ * Initialize.
+ */
+ (void) inet_proto_init("protocols", protocols);
+ buffer = vstring_alloc(1024);
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ sock = unix_listen(argv[optind] + 5, backlog, BLOCKING);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ sock = inet_listen(argv[optind], backlog, BLOCKING);
+ }
+
+ /*
+ * Start the event handler.
+ */
+ event_enable_read(sock, connect_event, CAST_INT_TO_VOID_PTR(sock));
+ for (;;)
+ event_loop(-1);
+}
diff --git a/src/smtpstone/qmqp-source.c b/src/smtpstone/qmqp-source.c
new file mode 100644
index 0000000..9231aec
--- /dev/null
+++ b/src/smtpstone/qmqp-source.c
@@ -0,0 +1,673 @@
+/*++
+/* NAME
+/* qmqp-source 1
+/* SUMMARY
+/* parallelized QMQP test generator
+/* SYNOPSIS
+/* .fi
+/* \fBqmqp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
+/*
+/* \fBqmqp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR
+/* DESCRIPTION
+/* \fBqmqp-source\fR connects to the named host and TCP port (default 628)
+/* and sends one or more messages to it, either sequentially
+/* or in parallel. The program speaks the QMQP protocol.
+/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers.
+/* IPv4 and IPv6 are the default.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
+/* .IP \fB-4\fR
+/* Connect to the server with IPv4. This option has no effect when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-6\fR
+/* Connect to the server with IPv6. This option is not available when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-c\fR
+/* Display a running counter that is incremented each time
+/* a delivery completes.
+/* .IP "\fB-C \fIcount\fR"
+/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times
+/* before giving up. The default count is 1. Specify a larger count in
+/* order to work around a problem with TCP/IP stacks that send RESET
+/* when the listen queue is full.
+/* .IP "\fB-f \fIfrom\fR"
+/* Use the specified sender address (default: <foo@myhostname>).
+/* .IP "\fB-l \fIlength\fR"
+/* Send \fIlength\fR bytes as message payload. The length
+/* includes the message headers.
+/* .IP "\fB-m \fImessage_count\fR"
+/* Send the specified number of messages (default: 1).
+/* .IP "\fB-M \fImyhostname\fR"
+/* Use the specified hostname or [address] in the default
+/* sender and recipient addresses, instead of the machine
+/* hostname.
+/* .IP "\fB-r \fIrecipient_count\fR"
+/* Send the specified number of recipients per transaction (default: 1).
+/* Recipient names are generated by prepending a number to the
+/* recipient address.
+/* .IP "\fB-s \fIsession_count\fR"
+/* Run the specified number of QMQP sessions in parallel (default: 1).
+/* .IP "\fB-t \fIto\fR"
+/* Use the specified recipient address (default: <foo@myhostname>).
+/* .IP "\fB-R \fIinterval\fR"
+/* Wait for a random period of time 0 <= n <= interval between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* .IP \fB-v\fR
+/* Make the program more verbose, for debugging purposes.
+/* .IP "\fB-w \fIinterval\fR"
+/* Wait a fixed time between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* SEE ALSO
+/* qmqp-sink(1), QMQP message dump
+/* 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 <sys/wait.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <get_hostname.h>
+#include <split_at.h>
+#include <connect.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <iostuff.h>
+#include <netstring.h>
+#include <sane_connect.h>
+#include <host_port.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <valid_hostname.h>
+#include <valid_mailhost_addr.h>
+
+/* Global library. */
+
+#include <mail_date.h>
+#include <qmqp_proto.h>
+#include <mail_version.h>
+
+/* Application-specific. */
+
+ /*
+ * Per-session data structure with state.
+ *
+ * This software can maintain multiple parallel connections to the same QMQP
+ * server. However, it makes no more than one connection request at a time
+ * to avoid overwhelming the server with SYN packets and having to back off.
+ * Back-off would screw up the benchmark. Pending connection requests are
+ * kept in a linear list.
+ */
+typedef struct SESSION {
+ int xfer_count; /* # of xfers in session */
+ int rcpt_done; /* # of recipients done */
+ int rcpt_count; /* # of recipients to go */
+ VSTREAM *stream; /* open connection */
+ int connect_count; /* # of connect()s to retry */
+ struct SESSION *next; /* connect() queue linkage */
+} SESSION;
+
+static SESSION *last_session; /* connect() queue tail */
+
+static VSTRING *buffer;
+static int var_line_limit = 10240;
+static int var_timeout = 300;
+static const char *var_myhostname;
+static int session_count;
+static int message_count = 1;
+static struct sockaddr_storage ss;
+
+#undef sun
+static struct sockaddr_un sun;
+static struct sockaddr *sa;
+static int sa_length;
+static int recipients = 1;
+static char *defaddr;
+static char *recipient;
+static char *sender;
+static int message_length = 1024;
+static int count = 0;
+static int counter = 0;
+static int connect_count = 1;
+static int random_delay = 0;
+static int fixed_delay = 0;
+static const char *mydate;
+static int mypid;
+
+static void enqueue_connect(SESSION *);
+static void start_connect(SESSION *);
+static void connect_done(int, void *);
+
+static void send_data(SESSION *);
+static void receive_reply(int, void *);
+
+static VSTRING *message_buffer;
+static VSTRING *sender_buffer;
+static VSTRING *recipient_buffer;
+
+/* Silly little macros. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* random_interval - generate a random value in 0 .. (small) interval */
+
+static int random_interval(int interval)
+{
+ return (rand() % (interval + 1));
+}
+
+/* socket_error - look up and reset the last socket error */
+
+static int socket_error(int sock)
+{
+ int error;
+ SOCKOPT_SIZE error_len;
+
+ /*
+ * Some Solaris 2 versions have getsockopt() itself return the error,
+ * instead of returning it via the parameter list.
+ */
+ error = 0;
+ error_len = sizeof(error);
+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0)
+ return (-1);
+ if (error) {
+ errno = error;
+ return (-1);
+ }
+
+ /*
+ * No problems.
+ */
+ return (0);
+}
+
+/* exception_text - translate exceptions from the netstring module */
+
+static char *exception_text(int except)
+{
+ ;
+
+ switch (except) {
+ case NETSTRING_ERR_EOF:
+ return ("lost connection");
+ case NETSTRING_ERR_TIME:
+ return ("timeout");
+ case NETSTRING_ERR_FORMAT:
+ return ("netstring format error");
+ case NETSTRING_ERR_SIZE:
+ return ("netstring size exceeds limit");
+ default:
+ msg_panic("exception_text: unknown exception %d", except);
+ }
+ /* NOTREACHED */
+}
+
+/* startup - connect to server but do not wait */
+
+static void startup(SESSION *session)
+{
+ if (message_count-- <= 0) {
+ myfree((void *) session);
+ session_count--;
+ return;
+ }
+ enqueue_connect(session);
+}
+
+/* start_event - invoke startup from timer context */
+
+static void start_event(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+
+ startup(session);
+}
+
+/* start_another - start another session */
+
+static void start_another(SESSION *session)
+{
+ if (random_delay > 0) {
+ event_request_timer(start_event, (void *) session,
+ random_interval(random_delay));
+ } else if (fixed_delay > 0) {
+ event_request_timer(start_event, (void *) session, fixed_delay);
+ } else {
+ startup(session);
+ }
+}
+
+/* enqueue_connect - queue a connection request */
+
+static void enqueue_connect(SESSION *session)
+{
+ session->next = 0;
+ if (last_session == 0) {
+ last_session = session;
+ start_connect(session);
+ } else {
+ last_session->next = session;
+ last_session = session;
+ }
+}
+
+/* dequeue_connect - connection request completed */
+
+static void dequeue_connect(SESSION *session)
+{
+ if (session == last_session) {
+ if (session->next != 0)
+ msg_panic("dequeue_connect: queue ends after last");
+ last_session = 0;
+ } else {
+ if (session->next == 0)
+ msg_panic("dequeue_connect: queue ends before last");
+ start_connect(session->next);
+ }
+}
+
+/* fail_connect - handle failed startup */
+
+static void fail_connect(SESSION *session)
+{
+ if (session->connect_count-- == 1)
+ msg_fatal("connect: %m");
+ msg_warn("connect: %m");
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+#ifdef MISSING_USLEEP
+ doze(10);
+#else
+ usleep(10);
+#endif
+ start_connect(session);
+}
+
+/* start_connect - start TCP handshake */
+
+static void start_connect(SESSION *session)
+{
+ int fd;
+ struct linger linger;
+
+ /*
+ * Some systems don't set the socket error when connect() fails early
+ * (loopback) so we must deal with the error immediately, rather than
+ * retrieving it later with getsockopt(). We can't use MSG_PEEK to
+ * distinguish between server disconnect and connection refused.
+ */
+ if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+ msg_fatal("socket: %m");
+ (void) non_blocking(fd, NON_BLOCKING);
+ linger.l_onoff = 1;
+ linger.l_linger = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger,
+ sizeof(linger)) < 0)
+ msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger);
+ session->stream = vstream_fdopen(fd, O_RDWR);
+ event_enable_write(fd, connect_done, (void *) session);
+ netstring_setup(session->stream, var_timeout);
+ if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS)
+ fail_connect(session);
+}
+
+/* connect_done - send message sender info */
+
+static void connect_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int fd = vstream_fileno(session->stream);
+
+ /*
+ * Try again after some delay when the connection failed, in case they
+ * run a Mickey Mouse protocol stack.
+ */
+ if (socket_error(fd) < 0) {
+ fail_connect(session);
+ } else {
+ dequeue_connect(session);
+ non_blocking(fd, BLOCKING);
+ event_disable_readwrite(fd);
+ /* 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(session->stream);
+ send_data(session);
+ }
+}
+
+/* send_data - send message+sender+recipients */
+
+static void send_data(SESSION *session)
+{
+ int fd = vstream_fileno(session->stream);
+ int except;
+
+ /*
+ * Prepare for disaster.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending message", exception_text(except));
+
+ /*
+ * Send the message content, by wrapping three netstrings into an
+ * over-all netstring.
+ *
+ * XXX This should be done more carefully to avoid blocking when sending
+ * large messages over slow networks.
+ */
+ netstring_put_multi(session->stream,
+ STR(message_buffer), LEN(message_buffer),
+ STR(sender_buffer), LEN(sender_buffer),
+ STR(recipient_buffer), LEN(recipient_buffer),
+ (char *) 0);
+ netstring_fflush(session->stream);
+
+ /*
+ * Wake me up when the server replies or when something bad happens.
+ */
+ event_enable_read(fd, receive_reply, (void *) session);
+}
+
+/* receive_reply - read server reply */
+
+static void receive_reply(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int except;
+
+ /*
+ * Prepare for disaster.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while receiving server reply", exception_text(except));
+
+ /*
+ * Receive and process the server reply.
+ */
+ netstring_get(session->stream, buffer, var_line_limit);
+ if (msg_verbose)
+ vstream_printf("<< %.*s\n", (int) LEN(buffer), STR(buffer));
+ if (STR(buffer)[0] != QMQP_STAT_OK)
+ msg_fatal("%s error: %.*s",
+ STR(buffer)[0] == QMQP_STAT_RETRY ? "recoverable" :
+ STR(buffer)[0] == QMQP_STAT_HARD ? "unrecoverable" :
+ "unknown", (int) LEN(buffer) - 1, STR(buffer) + 1);
+
+ /*
+ * Update the optional running counter.
+ */
+ if (count) {
+ counter++;
+ vstream_printf("%d\r", counter);
+ vstream_fflush(VSTREAM_OUT);
+ }
+
+ /*
+ * Finish this session. QMQP sends only one message per session.
+ */
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+ start_another(session);
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s -cv -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -R delay -w delay host[:port]", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - parse JCL and start the machine */
+
+int main(int argc, char **argv)
+{
+ SESSION *session;
+ char *host;
+ char *port;
+ char *path;
+ int path_len;
+ int sessions = 1;
+ int ch;
+ ssize_t len;
+ int n;
+ int i;
+ char *buf;
+ const char *parse_err;
+ struct addrinfo *res;
+ int aierr;
+ const char *protocols = INET_PROTO_NAME_ALL;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ signal(SIGPIPE, SIG_IGN);
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "46cC:f:l:m:M:r:R:s:t:vw:")) > 0) {
+ switch (ch) {
+ case '4':
+ protocols = INET_PROTO_NAME_IPV4;
+ break;
+ case '6':
+ protocols = INET_PROTO_NAME_IPV6;
+ break;
+ case 'c':
+ count++;
+ break;
+ case 'C':
+ if ((connect_count = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'f':
+ sender = optarg;
+ break;
+ case 'l':
+ if ((message_length = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'm':
+ if ((message_count = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'M':
+ if (*optarg == '[') {
+ if (!valid_mailhost_literal(optarg, DO_GRIPE))
+ msg_fatal("bad address literal: %s", optarg);
+ } else {
+ if (!valid_hostname(optarg, DO_GRIPE))
+ msg_fatal("bad hostname: %s", optarg);
+ }
+ var_myhostname = optarg;
+ break;
+ case 'r':
+ if ((recipients = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 'R':
+ if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 's':
+ if ((sessions = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ case 't':
+ recipient = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 1)
+ usage(argv[0]);
+
+ if (random_delay > 0)
+ srand(getpid());
+
+ /*
+ * Translate endpoint address to internal form.
+ */
+ (void) inet_proto_init("protocols", protocols);
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ path = argv[optind] + 5;
+ path_len = strlen(path);
+ if (path_len >= (int) sizeof(sun.sun_path))
+ msg_fatal("unix-domain name too long: %s", path);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = path_len + 1;
+#endif
+ memcpy(sun.sun_path, path, path_len);
+ sa = (struct sockaddr *) &sun;
+ sa_length = sizeof(sun);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ buf = mystrdup(argv[optind]);
+ if ((parse_err = host_port(buf, &host, (char *) 0, &port, "628")) != 0)
+ msg_fatal("%s: %s", argv[optind], parse_err);
+ if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
+ msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr));
+ myfree(buf);
+ sa = (struct sockaddr *) &ss;
+ if (res->ai_addrlen > sizeof(ss))
+ msg_fatal("address length %d > buffer length %d",
+ (int) res->ai_addrlen, (int) sizeof(ss));
+ memcpy((void *) sa, res->ai_addr, res->ai_addrlen);
+ sa_length = res->ai_addrlen;
+#ifdef HAS_SA_LEN
+ sa->sa_len = sa_length;
+#endif
+ freeaddrinfo(res);
+ }
+
+ /*
+ * Allocate space for temporary buffer.
+ */
+ buffer = vstring_alloc(100);
+
+ /*
+ * Make sure we have sender and recipient addresses.
+ */
+ if (var_myhostname == 0)
+ var_myhostname = get_hostname();
+ if (sender == 0 || recipient == 0) {
+ vstring_sprintf(buffer, "foo@%s", var_myhostname);
+ defaddr = mystrdup(vstring_str(buffer));
+ if (sender == 0)
+ sender = defaddr;
+ if (recipient == 0)
+ recipient = defaddr;
+ }
+
+ /*
+ * Prepare some results that may be used multiple times: the message
+ * content netstring, the sender netstring, and the recipient netstrings.
+ */
+ mydate = mail_date(time((time_t *) 0));
+ mypid = getpid();
+
+ message_buffer = vstring_alloc(message_length + 200);
+ vstring_sprintf(buffer,
+ "From: <%s>\nTo: <%s>\nDate: %s\nMessage-Id: <%d@%s>\n\n",
+ sender, recipient, mydate, mypid, var_myhostname);
+ for (n = 1; LEN(buffer) < message_length; n++) {
+ for (i = 0; i < n && i < 79; i++)
+ VSTRING_ADDCH(buffer, 'X');
+ VSTRING_ADDCH(buffer, '\n');
+ }
+ STR(buffer)[message_length - 1] = '\n';
+ netstring_memcpy(message_buffer, STR(buffer), message_length);
+
+ len = strlen(sender);
+ sender_buffer = vstring_alloc(len);
+ netstring_memcpy(sender_buffer, sender, len);
+
+ if (recipients == 1) {
+ len = strlen(recipient);
+ recipient_buffer = vstring_alloc(len);
+ netstring_memcpy(recipient_buffer, recipient, len);
+ } else {
+ recipient_buffer = vstring_alloc(100);
+ for (n = 0; n < recipients; n++) {
+ vstring_sprintf(buffer, "%d%s", n, recipient);
+ netstring_memcat(recipient_buffer, STR(buffer), LEN(buffer));
+ }
+ }
+
+ /*
+ * Start sessions.
+ */
+ while (sessions-- > 0) {
+ session = (SESSION *) mymalloc(sizeof(*session));
+ session->stream = 0;
+ session->xfer_count = 0;
+ session->connect_count = connect_count;
+ session->next = 0;
+ session_count++;
+ startup(session);
+ }
+ for (;;) {
+ event_loop(-1);
+ if (session_count <= 0 && message_count <= 0) {
+ if (count) {
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ exit(0);
+ }
+ }
+}
diff --git a/src/smtpstone/smtp-sink.c b/src/smtpstone/smtp-sink.c
new file mode 100644
index 0000000..4378d5a
--- /dev/null
+++ b/src/smtpstone/smtp-sink.c
@@ -0,0 +1,1643 @@
+/*++
+/* NAME
+/* smtp-sink 1
+/* SUMMARY
+/* parallelized SMTP/LMTP test server
+/* SYNOPSIS
+/* .fi
+/* \fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR
+/* \fIbacklog\fR
+/*
+/* \fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR
+/* DESCRIPTION
+/* \fBsmtp-sink\fR listens on the named host (or address) and port.
+/* It takes SMTP messages from the network and throws them away.
+/* The purpose is to measure client performance, not protocol
+/* compliance.
+/*
+/* \fBsmtp-sink\fR may also be configured to capture each mail
+/* delivery transaction to file. Since disk latencies are large
+/* compared to network delays, this mode of operation can
+/* reduce the maximal performance by several orders of magnitude.
+/*
+/* Connections can be accepted on IPv4 or IPv6 endpoints, or on
+/* UNIX-domain sockets.
+/* IPv4 and IPv6 are the default.
+/* This program is the complement of the \fBsmtp-source\fR(1) program.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
+/* .IP \fB-4\fR
+/* Support IPv4 only. This option has no effect when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-6\fR
+/* Support IPv6 only. This option is not available when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-8\fR
+/* Do not announce 8BITMIME support.
+/* .IP \fB-a\fR
+/* Do not announce SASL authentication support.
+/* .IP "\fB-A \fIdelay\fR"
+/* Wait \fIdelay\fR seconds after responding to DATA, then
+/* abort prematurely with a 550 reply status. Do not read
+/* further input from the client; this is an attempt to block
+/* the client before it sends ".". Specify a zero delay value
+/* to abort immediately.
+/* .IP "\fB-b \fIsoft-bounce-reply\fR"
+/* Use \fIsoft-bounce-reply\fR for soft reject responses. The
+/* default reply is "450 4.3.0 Error: command failed".
+/* .IP "\fB-B \fIhard-bounce-reply\fR"
+/* Use \fIhard-bounce-reply\fR for hard reject responses. The
+/* default reply is "500 5.3.0 Error: command failed".
+/* .IP \fB-c\fR
+/* Display running counters that are updated whenever an SMTP
+/* session ends, a QUIT command is executed, or when "." is
+/* received.
+/* .IP \fB-C\fR
+/* Disable XCLIENT support.
+/* .IP "\fB-d \fIdump-template\fR"
+/* Dump each mail transaction to a single-message file whose
+/* name is created by expanding the \fIdump-template\fR via
+/* strftime(3) and appending a pseudo-random hexadecimal number
+/* (example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3").
+/* If the template contains "/" characters, missing directories
+/* are created automatically. The message dump format is
+/* described below.
+/* .sp
+/* Note: this option keeps one capture file open for every
+/* mail transaction in progress.
+/* .IP "\fB-D \fIdump-template\fR"
+/* Append mail transactions to a multi-message dump file whose
+/* name is created by expanding the \fIdump-template\fR via
+/* strftime(3).
+/* If the template contains "/" characters, missing directories
+/* are created automatically. The message dump format is
+/* described below.
+/* .sp
+/* Note: this option keeps one capture file open for every
+/* mail transaction in progress.
+/* .IP \fB-e\fR
+/* Do not announce ESMTP support.
+/* .IP \fB-E\fR
+/* Do not announce ENHANCEDSTATUSCODES support.
+/* .IP "\fB-f \fIcommand,command,...\fR"
+/* Reject the specified commands with a hard (5xx) error code.
+/* This option implies \fB-p\fR.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP \fB-F\fR
+/* Disable XFORWARD support.
+/* .IP "\fB-h\fI hostname\fR"
+/* Use \fIhostname\fR in the SMTP greeting, in the HELO response,
+/* and in the EHLO response. The default hostname is "smtp-sink".
+/* .IP "\fB-H\fI delay\fR"
+/* Delay the first read operation after receiving DATA (time
+/* in seconds). Combine with a large test message and a small
+/* TCP window size (see the \fB-T\fR option) to test the Postfix
+/* client write_wait() implementation.
+/* .IP \fB-L\fR
+/* Enable LMTP instead of SMTP.
+/* .IP "\fB-m \fIcount\fR (default: 256)"
+/* An upper bound on the maximal number of simultaneous
+/* connections that \fBsmtp-sink\fR will handle. This prevents
+/* the process from running out of file descriptors. Excess
+/* connections will stay queued in the TCP/IP stack.
+/* .IP "\fB-M \fIcount\fR"
+/* Terminate after receiving \fIcount\fR messages.
+/* .IP "\fB-n \fIcount\fR"
+/* Terminate after \fIcount\fR sessions.
+/* .IP \fB-N\fR
+/* Do not announce support for DSN.
+/* .IP \fB-p\fR
+/* Do not announce support for ESMTP command pipelining.
+/* .IP \fB-P\fR
+/* Change the server greeting so that it appears to come through
+/* a CISCO PIX system. Implies \fB-e\fR.
+/* .IP "\fB-q \fIcommand,command,...\fR"
+/* Disconnect (without replying) after receiving one of the
+/* specified commands.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP "\fB-Q \fIcommand,command,...\fR"
+/* Send a 421 reply and disconnect after receiving one
+/* of the specified commands.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP "\fB-r \fIcommand,command,...\fR"
+/* Reject the specified commands with a soft (4xx) error code.
+/* This option implies \fB-p\fR.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP "\fB-R \fIroot-directory\fR"
+/* Change the process root directory to the specified location.
+/* This option requires super-user privileges. See also the
+/* \fB-u\fR option.
+/* .IP "\fB-s \fIcommand,command,...\fR"
+/* Log the named commands to syslogd.
+/* .sp
+/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
+/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
+/* white space or commas, and use quotes to protect white space
+/* from the shell. Command names are case-insensitive.
+/* .IP "\fB-S start-string\fR"
+/* An optional string that is prepended to each message that is
+/* written to a dump file (see the dump file format description
+/* below). The following C escape sequences are supported: \ea
+/* (bell), \eb (backspace), \ef (formfeed), \en (newline), \er
+/* (carriage return), \et (horizontal tab), \ev (vertical tab),
+/* \e\fIddd\fR (up to three octal digits) and \e\e (the backslash
+/* character).
+/* .IP "\fB-t \fItimeout\fR (default: 100)"
+/* Limit the time for receiving a command or sending a response.
+/* The time limit is specified in seconds.
+/* .IP "\fB-T \fIwindowsize\fR"
+/* Override the default TCP window size. To work around
+/* broken TCP window scaling implementations, specify a
+/* value > 0 and < 65536.
+/* .IP "\fB-u \fIusername\fR"
+/* Switch to the specified user privileges after opening the
+/* network socket and optionally changing the process root
+/* directory. This option is required when the process runs
+/* with super-user privileges. See also the \fB-R\fR option.
+/* .IP \fB-v\fR
+/* Show the SMTP conversations.
+/* .IP "\fB-w \fIdelay\fR"
+/* Wait \fIdelay\fR seconds before responding to a DATA command.
+/* .IP "\fB-W \fIcommand:delay[:odds]\fR"
+/* Wait \fIdelay\fR seconds before responding to \fIcommand\fR.
+/* If \fIodds\fR is also specified (a number between 1-99
+/* inclusive), wait for a random multiple of \fIdelay\fR. The
+/* random multiplier is equal to the number of times the program
+/* needs to roll a dice with a range of 0..99 inclusive, before
+/* the dice produces a result greater than or equal to \fIodds\fR.
+/* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR
+/* Listen on network interface \fIhost\fR (default: any interface)
+/* TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be
+/* specified in numeric or symbolic form.
+/* .IP \fBunix:\fR\fIpathname\fR
+/* Listen on the UNIX-domain socket at \fIpathname\fR.
+/* .IP \fIbacklog\fR
+/* The maximum length the queue of pending connections,
+/* as defined by the \fBlisten\fR(2) system call.
+/* DUMP FILE FORMAT
+/* .ad
+/* .fi
+/* Each dumped message contains a sequence of text lines,
+/* terminated with the newline character. The sequence of
+/* information is as follows:
+/* .IP \(bu
+/* The optional string specified with the \fB-S\fR option.
+/* .IP \(bu
+/* The \fBsmtp-sink\fR generated headers as documented below.
+/* .IP \(bu
+/* The message header and body as received from the SMTP client.
+/* .IP \(bu
+/* An empty line.
+/* .PP
+/* The format of the \fBsmtp-sink\fR generated headers is as
+/* follows:
+/* .IP "\fBX-Client-Addr: \fItext\fR"
+/* The client IP address without enclosing []. An IPv6 address
+/* is prefixed with "ipv6:". This record is always present.
+/* .IP "\fBX-Client-Proto: \fItext\fR"
+/* The client protocol: SMTP, ESMTP or LMTP. This record is
+/* always present.
+/* .IP "\fBX-Helo-Args: \fItext\fR"
+/* The arguments of the last HELO or EHLO command before this
+/* mail delivery transaction. This record is present only if
+/* the client sent a recognizable HELO or EHLO command before
+/* the DATA command.
+/* .IP "\fBX-Mail-Args: \fItext\fR"
+/* The arguments of the MAIL command that started this mail
+/* delivery transaction. This record is present exactly once.
+/* .IP "\fBX-Rcpt-Args: \fItext\fR"
+/* The arguments of an RCPT command within this mail delivery
+/* transaction. There is one record for each RCPT command, and
+/* they are in the order as sent by the client.
+/* .IP "\fBReceived: \fItext\fR"
+/* A message header for compatibility with mail processing
+/* software. This three-line header marks the end of the headers
+/* provided by \fBsmtp-sink\fR, and is formatted as follows:
+/* .RS
+/* .IP "\fBfrom \fIhelo\fR ([\fIaddr\fR])"
+/* The HELO or EHLO command argument and client IP address.
+/* If the client did not send HELO or EHLO, the client IP
+/* address is used instead.
+/* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR"
+/* The hostname specified with the \fB-h\fR option, the client
+/* protocol (see \fBX-Client-Proto\fR above), and the pseudo-random
+/* portion of the per-message capture file name.
+/* .IP \fItime-stamp\fR
+/* A time stamp as defined in RFC 2822.
+/* .RE
+/* SEE ALSO
+/* smtp-source(1), SMTP/LMTP message generator
+/* 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 <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <signal.h>
+#include <time.h>
+#include <ctype.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 <get_hostname.h>
+#include <listen.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <sane_accept.h>
+#include <inet_proto.h>
+#include <myaddrinfo.h>
+#include <make_dirs.h>
+#include <myrand.h>
+#include <chroot_uid.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <mail_date.h>
+#include <mail_version.h>
+
+/* Application-specific. */
+
+typedef struct SINK_STATE {
+ VSTREAM *stream;
+ VSTRING *buffer;
+ int data_state;
+ int (*read_fn) (struct SINK_STATE *);
+ int in_mail;
+ int rcpts;
+ char *push_back_ptr;
+ /* Capture file information for fake Received: header */
+ MAI_HOSTADDR_STR client_addr; /* IP address */
+ char *addr_prefix; /* ipv6: or empty */
+ char *helo_args; /* text after HELO or EHLO */
+ const char *client_proto; /* SMTP, ESMTP, LMTP */
+ time_t start_time; /* MAIL command time */
+ int id; /* pseudo-random */
+ VSTREAM *dump_file; /* dump file or null */
+ void (*delayed_response) (struct SINK_STATE *state, const char *);
+ char *delayed_args;
+} SINK_STATE;
+
+#define ST_ANY 0
+#define ST_CR 1
+#define ST_CR_LF 2
+#define ST_CR_LF_DOT 3
+#define ST_CR_LF_DOT_CR 4
+#define ST_CR_LF_DOT_CR_LF 5
+
+#define PUSH_BACK_PEEK(state) (*(state)->push_back_ptr != 0)
+#define PUSH_BACK_GET(state) (*(state)->push_back_ptr++)
+#define PUSH_BACK_SET(state, text) ((state)->push_back_ptr = (text))
+
+#ifndef DEF_MAX_CLIENT_COUNT
+#define DEF_MAX_CLIENT_COUNT 256
+#endif
+
+#define SOFT_ERROR_RESP "450 4.3.0 Error: command failed"
+#define HARD_ERROR_RESP "500 5.3.0 Error: command failed"
+
+ /*
+ * We can't rely on vstream auto-flushing, so we have to prepare for the
+ * next read request.
+ */
+#define SMTP_FLUSH(fp) do { \
+ if (vstream_peek(fp) <= 0 && readable(vstream_fileno(fp)) <= 0) \
+ smtp_flush(fp); \
+ } while (0)
+
+static int var_tmout = 100;
+static int var_max_line_length = 2048;
+static char *var_myhostname;
+static char *soft_error_resp = SOFT_ERROR_RESP;
+static char *hard_error_resp = HARD_ERROR_RESP;
+static int command_read(SINK_STATE *);
+static int data_read(SINK_STATE *);
+static void disconnect(SINK_STATE *);
+static void read_timeout(int, void *);
+static void read_event(int, void *);
+static int show_count;
+static int sess_count;
+static int quit_count;
+static int mesg_count;
+static int max_quit_count;
+static int max_msg_quit_count;
+static int disable_pipelining;
+static int disable_8bitmime;
+static int disable_esmtp;
+static int enable_lmtp;
+static int pretend_pix;
+static int disable_saslauth;
+static int disable_xclient;
+static int disable_xforward;
+static int disable_enh_status;
+static int disable_dsn;
+static int max_client_count = DEF_MAX_CLIENT_COUNT;
+static int client_count;
+static int sock;
+static int abort_delay = -1;
+static int data_read_delay = 0;
+
+static char *single_template; /* individual template */
+static char *shared_template; /* shared template */
+static VSTRING *start_string; /* dump content prefix */
+
+static INET_PROTO_INFO *proto_info;
+
+#define STR(x) vstring_str(x)
+
+/* do_stats - show counters */
+
+static void do_stats(void)
+{
+ vstream_printf("sess=%d quit=%d mesg=%d\r",
+ sess_count, quit_count, mesg_count);
+ vstream_fflush(VSTREAM_OUT);
+}
+
+/* hard_err_resp - generic hard error response */
+
+static void hard_err_resp(SINK_STATE *state)
+{
+ smtp_printf(state->stream, "%s", hard_error_resp);
+ SMTP_FLUSH(state->stream);
+}
+
+/* soft_err_resp - generic soft error response */
+
+static void soft_err_resp(SINK_STATE *state)
+{
+ smtp_printf(state->stream, "%s", soft_error_resp);
+ SMTP_FLUSH(state->stream);
+}
+
+/* exp_path_template - expand template pathname, static result */
+
+static VSTRING *exp_path_template(const char *template, time_t start_time)
+{
+ static VSTRING *path_buf = 0;
+ struct tm *lt;
+
+ if (path_buf == 0)
+ path_buf = vstring_alloc(100);
+ else
+ VSTRING_RESET(path_buf);
+ lt = localtime(&start_time);
+ while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0)
+ VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100);
+ VSTRING_SKIP(path_buf);
+ return (path_buf);
+}
+
+/* make_parent_dir - create parent directory or bust */
+
+static void make_parent_dir(const char *path, mode_t mode)
+{
+ const char *parent;
+
+ parent = sane_dirname((VSTRING *) 0, path);
+ if (make_dirs(parent, mode) < 0)
+ msg_fatal("mkdir %s: %m", parent);
+}
+
+/* mail_file_open - open mail capture file */
+
+static void mail_file_open(SINK_STATE *state)
+{
+ const char *myname = "mail_file_open";
+ VSTRING *path_buf;
+ ssize_t len;
+ int tries = 0;
+
+ /*
+ * Save the start time for later.
+ */
+ time(&(state->start_time));
+
+ /*
+ * Expand the per-message dumpfile pathname template.
+ */
+ path_buf = exp_path_template(single_template, state->start_time);
+
+ /*
+ * Append a random hexadecimal string to the pathname and create a new
+ * file. Retry with a different path if the file already exists. Create
+ * intermediate directories on the fly when the template specifies
+ * multiple pathname segments.
+ */
+#define ID_FORMAT "%08x"
+
+ for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) {
+ if (++tries > 100)
+ msg_fatal("%s: something is looping", myname);
+ state->id = myrand();
+ vstring_sprintf_append(path_buf, ID_FORMAT, state->id);
+ if ((state->dump_file = vstream_fopen(STR(path_buf),
+ O_RDWR | O_CREAT | O_EXCL,
+ 0644)) != 0) {
+ break;
+ } else if (errno == EEXIST) {
+ continue;
+ } else if (errno == ENOENT) {
+ make_parent_dir(STR(path_buf), 0755);
+ continue;
+ } else {
+ msg_fatal("open %s: %m", STR(path_buf));
+ }
+ }
+
+ /*
+ * Don't leave temporary files behind.
+ */
+ if (shared_template != 0 && unlink(STR(path_buf)) < 0)
+ msg_fatal("unlink %s: %m", STR(path_buf));
+
+ /*
+ * Do initial header records.
+ */
+ if (start_string)
+ vstream_fprintf(state->dump_file, "%s", STR(start_string));
+ vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n",
+ state->addr_prefix, state->client_addr.buf);
+ vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto);
+ if (state->helo_args)
+ vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args);
+ /* Note: there may be more than one recipient. */
+}
+
+/* mail_file_finish_header - do final smtp-sink generated header records */
+
+static void mail_file_finish_header(SINK_STATE *state)
+{
+ if (state->helo_args)
+ vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n",
+ state->helo_args, state->addr_prefix,
+ state->client_addr.buf);
+ else
+ vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n",
+ state->addr_prefix, state->client_addr.buf,
+ state->addr_prefix, state->client_addr.buf);
+ vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)"
+ " with %s id " ID_FORMAT ";\n",
+ var_myhostname, state->client_proto, state->id);
+ vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time));
+}
+
+/* mail_file_cleanup - common cleanup for capture file */
+
+static void mail_file_cleanup(SINK_STATE *state)
+{
+ (void) vstream_fclose(state->dump_file);
+ state->dump_file = 0;
+}
+
+/* mail_file_finish - handle message completion for capture file */
+
+static void mail_file_finish(SINK_STATE *state)
+{
+
+ /*
+ * Optionally append the captured message to a shared dumpfile.
+ */
+ if (shared_template) {
+ const char *out_path;
+ VSTREAM *out_fp;
+ ssize_t count;
+
+ /*
+ * Expand the shared dumpfile pathname template.
+ */
+ out_path = STR(exp_path_template(shared_template, state->start_time));
+
+ /*
+ * Open the shared dump file.
+ */
+#define OUT_OPEN_FLAGS (O_WRONLY | O_CREAT | O_APPEND)
+#define OUT_OPEN_MODE 0644
+
+ if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE))
+ == 0 && errno == ENOENT) {
+ make_parent_dir(out_path, 0755);
+ out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE);
+ }
+ if (out_fp == 0)
+ msg_fatal("open %s: %m", out_path);
+
+ /*
+ * Append message content from single-message dump file.
+ */
+ if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file));
+ VSTRING_RESET(state->buffer);
+ for (;;) {
+ count = vstream_fread(state->dump_file, STR(state->buffer),
+ vstring_avail(state->buffer));
+ if (count <= 0)
+ break;
+ if (vstream_fwrite(out_fp, STR(state->buffer), count) != count)
+ msg_fatal("append file %s: %m", out_path);
+ }
+ if (vstream_ferror(state->dump_file))
+ msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file));
+ if (vstream_fclose(out_fp))
+ msg_fatal("append file %s: %m", out_path);
+ }
+ mail_file_cleanup(state);
+}
+
+/* mail_file_reset - abort mail to capture file */
+
+static void mail_file_reset(SINK_STATE *state)
+{
+ if (shared_template == 0
+ && unlink(VSTREAM_PATH(state->dump_file)) < 0
+ && errno != ENOENT)
+ msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file));
+ mail_file_cleanup(state);
+}
+
+/* mail_cmd_reset - reset mail transaction information */
+
+static void mail_cmd_reset(SINK_STATE *state)
+{
+ state->in_mail = 0;
+ /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */
+ if (state->dump_file)
+ mail_file_reset(state);
+}
+
+/* ehlo_response - respond to EHLO command */
+
+static void ehlo_response(SINK_STATE *state, const char *args)
+{
+#define SKIP(cp, cond) do { \
+ for (/* void */; *cp && (cond); cp++) \
+ /* void */; \
+ } while (0)
+
+ /* EHLO aborts a mail transaction in progress. */
+ mail_cmd_reset(state);
+ if (enable_lmtp == 0)
+ state->client_proto = "ESMTP";
+ smtp_printf(state->stream, "250-%s", var_myhostname);
+ if (!disable_pipelining)
+ smtp_printf(state->stream, "250-PIPELINING");
+ if (!disable_8bitmime)
+ smtp_printf(state->stream, "250-8BITMIME");
+ if (!disable_saslauth)
+ smtp_printf(state->stream, "250-AUTH PLAIN LOGIN");
+ if (!disable_xclient)
+ smtp_printf(state->stream, "250-XCLIENT NAME HELO");
+ if (!disable_xforward)
+ smtp_printf(state->stream, "250-XFORWARD NAME ADDR PROTO HELO");
+ if (!disable_enh_status)
+ smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES");
+ if (!disable_dsn)
+ smtp_printf(state->stream, "250-DSN");
+ /* RFC 821/2821/5321: Format is replycode<SPACE>optional-text<CRLF> */
+ smtp_printf(state->stream, "250 ");
+ SMTP_FLUSH(state->stream);
+ if (single_template) {
+ if (state->helo_args)
+ myfree(state->helo_args);
+ SKIP(args, ISSPACE(*args));
+ state->helo_args = mystrdup(args);
+ }
+}
+
+/* helo_response - respond to HELO command */
+
+static void helo_response(SINK_STATE *state, const char *args)
+{
+ /* HELO aborts a mail transaction in progress. */
+ mail_cmd_reset(state);
+ state->client_proto = "SMTP";
+ smtp_printf(state->stream, "250 %s", var_myhostname);
+ SMTP_FLUSH(state->stream);
+ if (single_template) {
+ if (state->helo_args)
+ myfree(state->helo_args);
+ SKIP(args, ISSPACE(*args));
+ state->helo_args = mystrdup(args);
+ }
+}
+
+/* ok_response - send 250 OK */
+
+static void ok_response(SINK_STATE *state, const char *unused_args)
+{
+ smtp_printf(state->stream, "250 2.0.0 Ok");
+ SMTP_FLUSH(state->stream);
+}
+
+/* rset_response - reset, send 250 OK */
+
+static void rset_response(SINK_STATE *state, const char *unused_args)
+{
+ mail_cmd_reset(state);
+ smtp_printf(state->stream, "250 2.1.0 Ok");
+ SMTP_FLUSH(state->stream);
+}
+
+/* mail_response - reset recipient count, send 250 OK */
+
+static void mail_response(SINK_STATE *state, const char *args)
+{
+ if (state->in_mail) {
+ smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command");
+ SMTP_FLUSH(state->stream);
+ return;
+ }
+ state->in_mail++;
+ state->rcpts = 0;
+ smtp_printf(state->stream, "250 2.1.0 Ok");
+ SMTP_FLUSH(state->stream);
+ if (single_template) {
+ mail_file_open(state);
+ SKIP(args, *args != ':');
+ SKIP(args, *args == ':');
+ SKIP(args, ISSPACE(*args));
+ vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args);
+ }
+}
+
+/* rcpt_response - bump recipient count, send 250 OK */
+
+static void rcpt_response(SINK_STATE *state, const char *args)
+{
+ if (state->in_mail == 0) {
+ smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command");
+ SMTP_FLUSH(state->stream);
+ return;
+ }
+ state->rcpts++;
+ smtp_printf(state->stream, "250 2.1.5 Ok");
+ SMTP_FLUSH(state->stream);
+ /* Note: there may be more than one recipient per mail transaction. */
+ if (state->dump_file) {
+ SKIP(args, *args != ':');
+ SKIP(args, *args == ':');
+ SKIP(args, ISSPACE(*args));
+ vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args);
+ }
+}
+
+/* abort_event - delayed abort after DATA command */
+
+static void abort_event(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ smtp_printf(state->stream, "550 This violates SMTP");
+ SMTP_FLUSH(state->stream);
+ disconnect(state);
+}
+
+/* delay_read_event - resume input event handling */
+
+static void delay_read_event(int event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ if (event != EVENT_TIME)
+ msg_panic("delay_read_event: non-timer event %d", event);
+
+ event_enable_read(vstream_fileno(state->stream), read_event, (void *) state);
+ event_request_timer(read_timeout, (void *) state, var_tmout);
+}
+
+/* delay_read - temporarily suspend input event handling */
+
+static void delay_read(SINK_STATE *state, int delay)
+{
+ event_disable_readwrite(vstream_fileno(state->stream));
+ event_cancel_timer(read_timeout, (void *) state);
+ event_request_timer(delay_read_event, (void *) state, delay);
+}
+
+/* data_response - respond to DATA command */
+
+static void data_response(SINK_STATE *state, const char *unused_args)
+{
+ if (state->in_mail == 0 || state->rcpts == 0) {
+ smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command");
+ SMTP_FLUSH(state->stream);
+ return;
+ }
+ /* Not: ST_ANY. */
+ state->data_state = ST_CR_LF;
+ smtp_printf(state->stream, "354 End data with <CR><LF>.<CR><LF>");
+ SMTP_FLUSH(state->stream);
+ if (abort_delay < 0) {
+ state->read_fn = data_read;
+ /* Todo: move into code that invokes the command response function. */
+ if (data_read_delay > 0)
+ delay_read(state, data_read_delay);
+ } else {
+ /* Stop reading, send premature 550, and disconnect. */
+ event_disable_readwrite(vstream_fileno(state->stream));
+ event_cancel_timer(read_event, (void *) state);
+ event_request_timer(abort_event, (void *) state, abort_delay);
+ }
+ if (state->dump_file)
+ mail_file_finish_header(state);
+}
+
+/* dot_resp_hard - hard error response to . command */
+
+static void dot_resp_hard(SINK_STATE *state)
+{
+ if (enable_lmtp) {
+ while (state->rcpts-- > 0) /* XXX this could block */
+ smtp_printf(state->stream, "%s", hard_error_resp);
+ } else {
+ smtp_printf(state->stream, "%s", hard_error_resp);
+ }
+ SMTP_FLUSH(state->stream);
+}
+
+/* dot_resp_soft - soft error response to . command */
+
+static void dot_resp_soft(SINK_STATE *state)
+{
+ if (enable_lmtp) {
+ while (state->rcpts-- > 0) /* XXX this could block */
+ smtp_printf(state->stream, "%s", soft_error_resp);
+ } else {
+ smtp_printf(state->stream, "%s", soft_error_resp);
+ }
+ SMTP_FLUSH(state->stream);
+}
+
+/* dot_response - response to . command */
+
+static void dot_response(SINK_STATE *state, const char *unused_args)
+{
+ if (enable_lmtp) {
+ while (state->rcpts-- > 0) /* XXX this could block */
+ smtp_printf(state->stream, "250 2.2.0 Ok");
+ } else {
+ smtp_printf(state->stream, "250 2.0.0 Ok");
+ }
+ SMTP_FLUSH(state->stream);
+}
+
+/* quit_response - respond to QUIT command */
+
+static void quit_response(SINK_STATE *state, const char *unused_args)
+{
+ smtp_printf(state->stream, "221 Bye");
+ smtp_flush(state->stream); /* not: SMTP_FLUSH */
+ if (show_count)
+ quit_count++;
+}
+
+/* conn_response - respond to connect command */
+
+static void conn_response(SINK_STATE *state, const char *unused_args)
+{
+ if (pretend_pix)
+ smtp_printf(state->stream, "220 ********");
+ else if (disable_esmtp)
+ smtp_printf(state->stream, "220 %s", var_myhostname);
+ else
+ smtp_printf(state->stream, "220 %s ESMTP", var_myhostname);
+ SMTP_FLUSH(state->stream);
+}
+
+/* delay_event - delayed command response */
+
+static void delay_event(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ switch (vstream_setjmp(state->stream)) {
+
+ default:
+ msg_panic("unknown read/write error");
+ /* NOTREACHED */
+
+ case SMTP_ERR_TIME:
+ msg_warn("write timeout");
+ disconnect(state);
+ return;
+
+ case SMTP_ERR_EOF:
+ msg_warn("lost connection");
+ disconnect(state);
+ return;
+
+ case 0:
+ state->delayed_response(state, state->delayed_args);
+ myfree(state->delayed_args);
+ state->delayed_args = 0;
+ break;
+ }
+
+ if (state->delayed_response == quit_response) {
+ disconnect(state);
+ return;
+ }
+ state->delayed_response = 0;
+
+ /* Resume input event handling after the delayed response. */
+ event_enable_read(vstream_fileno(state->stream), read_event, (void *) state);
+ event_request_timer(read_timeout, (void *) state, var_tmout);
+}
+
+/* data_read - read data from socket */
+
+static int data_read(SINK_STATE *state)
+{
+ int ch;
+ struct data_trans {
+ int state;
+ int want;
+ int next_state;
+ };
+ static struct data_trans data_trans[] = {
+ ST_ANY, '\r', ST_CR,
+ ST_CR, '\n', ST_CR_LF,
+ ST_CR_LF, '.', ST_CR_LF_DOT,
+ ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR,
+ ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF,
+ };
+ struct data_trans *dp;
+
+ /*
+ * A read may result in EOF, but is never supposed to time out - a time
+ * out means that we were trying to read when no data was available.
+ */
+ for (;;) {
+ if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF)
+ return (-1);
+ for (dp = data_trans; dp->state != state->data_state; dp++)
+ /* void */ ;
+
+ /*
+ * Try to match the current character desired by the state machine.
+ * If that fails, try to restart the machine with a match for its
+ * first state. This covers the case of a CR/LF/CR/LF sequence
+ * (empty line) right before the end of the message data.
+ */
+ if (ch == dp->want)
+ state->data_state = dp->next_state;
+ else if (ch == data_trans[0].want)
+ state->data_state = data_trans[0].next_state;
+ else
+ state->data_state = ST_ANY;
+ if (state->dump_file) {
+ if (ch != '\r' && state->data_state != ST_CR_LF_DOT)
+ VSTREAM_PUTC(ch, state->dump_file);
+ if (vstream_ferror(state->dump_file))
+ msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file));
+ }
+ if (state->data_state == ST_CR_LF_DOT_CR_LF) {
+ PUSH_BACK_SET(state, ".\r\n");
+ state->read_fn = command_read;
+ state->data_state = ST_ANY;
+ if (state->dump_file)
+ mail_file_finish(state);
+ mail_cmd_reset(state);
+ if (show_count || max_msg_quit_count > 0) {
+ mesg_count++;
+ if (show_count)
+ do_stats();
+ if (max_msg_quit_count > 0 && mesg_count >= max_msg_quit_count)
+ exit(0);
+ }
+ break;
+ }
+
+ /*
+ * We must avoid blocking I/O, so get out of here as soon as both the
+ * VSTREAM and kernel read buffers dry up.
+ */
+ if (vstream_peek(state->stream) <= 0
+ && readable(vstream_fileno(state->stream)) <= 0)
+ return (0);
+ }
+ return (0);
+}
+
+ /*
+ * The table of all SMTP commands that we can handle.
+ */
+typedef struct SINK_COMMAND {
+ const char *name;
+ void (*response) (SINK_STATE *, const char *);
+ void (*hard_response) (SINK_STATE *);
+ void (*soft_response) (SINK_STATE *);
+ int flags;
+ int delay;
+ int delay_odds;
+} SINK_COMMAND;
+
+#define FLAG_ENABLE (1<<0) /* command is enabled */
+#define FLAG_SYSLOG (1<<1) /* log the command */
+#define FLAG_HARD_ERR (1<<2) /* report hard error */
+#define FLAG_SOFT_ERR (1<<3) /* report soft error */
+#define FLAG_DISCONNECT (1<<4) /* disconnect */
+#define FLAG_CLOSE (1<<5) /* say goodbye and disconnect */
+
+static SINK_COMMAND command_table[] = {
+ "connect", conn_response, hard_err_resp, soft_err_resp, 0, 0, 0,
+ "helo", helo_response, hard_err_resp, soft_err_resp, 0, 0, 0,
+ "ehlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0,
+ "lhlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0,
+ "xclient", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "xforward", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "auth", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "mail", mail_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ ".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE, 0, 0,
+ "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ "quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
+ 0,
+};
+
+/* reset_cmd_flags - reset per-command command flags */
+
+static void reset_cmd_flags(const char *cmd, int flags)
+{
+ SINK_COMMAND *cmdp;
+
+ for (cmdp = command_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(cmd, cmdp->name) == 0)
+ break;
+ if (cmdp->name == 0)
+ msg_fatal("unknown command: %s", cmd);
+ cmdp->flags &= ~flags;
+}
+
+/* set_cmd_flags - set per-command command flags */
+
+static void set_cmd_flags(const char *cmd, int flags)
+{
+ SINK_COMMAND *cmdp;
+
+ for (cmdp = command_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(cmd, cmdp->name) == 0)
+ break;
+ if (cmdp->name == 0)
+ msg_fatal("unknown command: %s", cmd);
+ cmdp->flags |= flags;
+}
+
+/* set_cmds_flags - set per-command flags for multiple commands */
+
+static void set_cmds_flags(const char *cmds, int flags)
+{
+ char *saved_cmds;
+ char *cp;
+ char *cmd;
+
+ saved_cmds = cp = mystrdup(cmds);
+ while ((cmd = mystrtok(&cp, CHARS_COMMA_SP)) != 0)
+ set_cmd_flags(cmd, flags);
+ myfree(saved_cmds);
+}
+
+/* set_cmd_delay - set per-command delay */
+
+static void set_cmd_delay(const char *cmd, int delay, int odds)
+{
+ SINK_COMMAND *cmdp;
+
+ for (cmdp = command_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(cmd, cmdp->name) == 0)
+ break;
+ if (cmdp->name == 0)
+ msg_fatal("unknown command: %s", cmd);
+
+ if (delay <= 0)
+ msg_fatal("non-positive '%s' delay", cmd);
+ if (odds < 0 || odds > 99)
+ msg_fatal("delay odds for '%s' out of range", cmd);
+
+ cmdp->delay = delay;
+ cmdp->delay_odds = odds;
+}
+
+/* set_cmd_delay_arg - set per-command delay from option argument */
+
+static void set_cmd_delay_arg(char *arg)
+{
+ char *cp;
+ char *saved_arg;
+ char *cmd;
+ char *delay;
+ char *odds;
+
+ saved_arg = cp = mystrdup(arg);
+ cmd = mystrtok(&cp, ":");
+ delay = mystrtok(&cp, ":");
+ if (cmd == 0 || delay == 0)
+ msg_fatal("invalid command delay argument: %s", arg);
+ odds = mystrtok(&cp, "");
+ set_cmd_delay(cmd, atoi(delay), odds ? atoi(odds) : 0);
+ myfree(saved_arg);
+}
+
+/* command_resp - respond to command */
+
+static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp,
+ const char *command, const char *args)
+{
+ /* We use raw syslog. Sanitize data content and length. */
+ if (cmdp->flags & FLAG_SYSLOG)
+ syslog(LOG_INFO, "%s %.100s", command, args);
+ if (cmdp->flags & FLAG_DISCONNECT)
+ return (-1);
+ if (cmdp->flags & FLAG_CLOSE) {
+ smtp_printf(state->stream, "421 4.0.0 Server closing connection");
+ return (-1);
+ }
+ if (cmdp->flags & FLAG_HARD_ERR) {
+ cmdp->hard_response(state);
+ return (0);
+ }
+ if (cmdp->flags & FLAG_SOFT_ERR) {
+ cmdp->soft_response(state);
+ return (0);
+ }
+ if (cmdp->delay > 0) {
+ int delay = cmdp->delay;
+
+ if (cmdp->delay_odds > 0)
+ for (delay = 0;
+ ((int) (100.0 * rand() / (RAND_MAX + 1.0))) < cmdp->delay_odds;
+ delay += cmdp->delay)
+ /* NOP */ ;
+ /* Suspend input event handling while delaying the command response. */
+ event_disable_readwrite(vstream_fileno(state->stream));
+ event_cancel_timer(read_timeout, (void *) state);
+ event_request_timer(delay_event, (void *) state, delay);
+ state->delayed_response = cmdp->response;
+ state->delayed_args = mystrdup(args);
+ } else {
+ cmdp->response(state, args);
+ if (cmdp->response == quit_response)
+ return (-1);
+ }
+ return (0);
+}
+
+/* command_read - talk the SMTP protocol, server side */
+
+static int command_read(SINK_STATE *state)
+{
+ char *command;
+ SINK_COMMAND *cmdp;
+ int ch;
+ struct cmd_trans {
+ int state;
+ int want;
+ int next_state;
+ };
+ static struct cmd_trans cmd_trans[] = {
+ ST_ANY, '\r', ST_CR,
+ ST_CR, '\n', ST_CR_LF,
+ 0, 0, 0,
+ };
+ struct cmd_trans *cp;
+ char *ptr;
+
+ /*
+ * A read may result in EOF, but is never supposed to time out - a time
+ * out means that we were trying to read when no data was available.
+ */
+#define NEXT_CHAR(state) \
+ (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream))
+
+ if (state->data_state == ST_CR_LF)
+ state->data_state = ST_ANY; /* XXX */
+ for (;;) {
+ if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF)
+ return (-1);
+
+ /*
+ * Sanity check. We don't want to store infinitely long commands.
+ */
+ if (VSTRING_LEN(state->buffer) >= var_max_line_length) {
+ msg_warn("command line too long");
+ return (-1);
+ }
+ VSTRING_ADDCH(state->buffer, ch);
+
+ /*
+ * Try to match the current character desired by the state machine.
+ * If that fails, try to restart the machine with a match for its
+ * first state.
+ */
+ for (cp = cmd_trans; cp->state != state->data_state; cp++)
+ if (cp->want == 0)
+ msg_panic("command_read: unknown state: %d", state->data_state);
+ if (ch == cp->want)
+ state->data_state = cp->next_state;
+ else if (ch == cmd_trans[0].want)
+ state->data_state = cmd_trans[0].next_state;
+ else
+ state->data_state = ST_ANY;
+ if (state->data_state == ST_CR_LF)
+ break;
+
+ /*
+ * We must avoid blocking I/O, so get out of here as soon as both the
+ * VSTREAM and kernel read buffers dry up.
+ *
+ * XXX Solaris non-blocking read() may fail on a socket when ioctl
+ * FIONREAD reports there is unread data. Diagnosis by Max Pashkov.
+ * As a workaround we use readable() (which uses poll or select())
+ * instead of peek_fd() (which uses ioctl FIONREAD). Workaround added
+ * 20020604.
+ */
+ if (PUSH_BACK_PEEK(state) == 0 && vstream_peek(state->stream) <= 0
+ && readable(vstream_fileno(state->stream)) <= 0)
+ return (0);
+ }
+
+ /*
+ * Properly terminate the result, and reset the buffer write pointer for
+ * reading the next command. This is ugly, but not as ugly as trying to
+ * deal with all the early returns below.
+ */
+ vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2);
+ VSTRING_TERMINATE(state->buffer);
+ state->data_state = ST_CR_LF;
+ VSTRING_RESET(state->buffer);
+
+ /*
+ * Got a complete command line. Parse it.
+ */
+ ptr = vstring_str(state->buffer);
+ if (msg_verbose)
+ msg_info("%s", ptr);
+ if ((command = mystrtok(&ptr, " \t")) == 0) {
+ smtp_printf(state->stream, "500 5.5.2 Error: unknown command");
+ SMTP_FLUSH(state->stream);
+ return (0);
+ }
+ for (cmdp = command_table; cmdp->name != 0; cmdp++)
+ if (strcasecmp(command, cmdp->name) == 0)
+ break;
+ if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) {
+ smtp_printf(state->stream, "500 5.5.1 Error: unknown command");
+ SMTP_FLUSH(state->stream);
+ return (0);
+ }
+ return (command_resp(state, cmdp, command, printable(ptr, '?')));
+}
+
+/* read_timeout - handle timer event */
+
+static void read_timeout(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ /*
+ * We don't send anything to the client, because we would have to set up
+ * an smtp_stream exception handler first. And that is just too much
+ * trouble.
+ */
+ msg_warn("read timeout");
+ disconnect(state);
+}
+
+/* read_event - handle command or data read events */
+
+static void read_event(int unused_event, void *context)
+{
+ SINK_STATE *state = (SINK_STATE *) context;
+
+ /*
+ * The input reading routine not only reads input (with vstream calls)
+ * but also produces output (with smtp_stream calls). Because the output
+ * routines can raise timeout or EOF exceptions with vstream_longjmp(),
+ * the input reading routine needs to set up corresponding exception
+ * handlers with vstream_setjmp(). Guarding the input operations in the
+ * same manner is not useful: we must read input in non-blocking mode, so
+ * we never get called when the socket stays unreadable too long. And EOF
+ * is already trivial to detect with the vstream calls.
+ */
+ do {
+ switch (vstream_setjmp(state->stream)) {
+
+ default:
+ msg_panic("unknown read/write error");
+ /* NOTREACHED */
+
+ case SMTP_ERR_TIME:
+ msg_warn("write timeout");
+ disconnect(state);
+ return;
+
+ case SMTP_ERR_EOF:
+ msg_warn("lost connection");
+ disconnect(state);
+ return;
+
+ case 0:
+ if (state->read_fn(state) < 0) {
+ if (msg_verbose)
+ msg_info("disconnect");
+ disconnect(state);
+ return;
+ }
+ }
+ } while (PUSH_BACK_PEEK(state) != 0 || vstream_peek(state->stream) > 0);
+
+ /*
+ * Reset the idle timer. Wait until the next input event, or until the
+ * idle timer goes off.
+ */
+ event_request_timer(read_timeout, (void *) state, var_tmout);
+}
+
+static void connect_event(int, void *);
+
+/* disconnect - handle disconnection events */
+
+static void disconnect(SINK_STATE *state)
+{
+ event_disable_readwrite(vstream_fileno(state->stream));
+ event_cancel_timer(read_timeout, (void *) state);
+ if (show_count) {
+ sess_count++;
+ do_stats();
+ }
+ vstream_fclose(state->stream);
+ vstring_free(state->buffer);
+ /* Clean up file capture attributes. */
+ if (state->helo_args)
+ myfree(state->helo_args);
+ /* Delete incomplete mail transaction. */
+ mail_cmd_reset(state);
+ if (state->delayed_args)
+ myfree(state->delayed_args);
+ myfree((void *) state);
+ if (max_quit_count > 0 && quit_count >= max_quit_count)
+ exit(0);
+ if (client_count-- == max_client_count)
+ event_enable_read(sock, connect_event, (void *) 0);
+}
+
+/* connect_event - handle connection events */
+
+static void connect_event(int unused_event, void *unused_context)
+{
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE len = sizeof(ss);
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SINK_STATE *state;
+ int fd;
+
+ if ((fd = sane_accept(sock, sa, &len)) >= 0) {
+ /* Safety: limit the number of open sockets and capture files. */
+ if (++client_count == max_client_count)
+ event_disable_readwrite(sock);
+ state = (SINK_STATE *) mymalloc(sizeof(*state));
+ if (strchr((char *) proto_info->sa_family_list, sa->sa_family))
+ SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr,
+ (MAI_SERVPORT_STR *) 0, sa->sa_family);
+ else
+ strncpy(state->client_addr.buf, "local", sizeof("local"));
+ if (msg_verbose)
+ msg_info("connect (%s %s)",
+#ifdef AF_LOCAL
+ sa->sa_family == AF_LOCAL ? "AF_LOCAL" :
+#else
+ sa->sa_family == AF_UNIX ? "AF_UNIX" :
+#endif
+ sa->sa_family == AF_INET ? "AF_INET" :
+#ifdef AF_INET6
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
+#endif
+ "unknown protocol family",
+ state->client_addr.buf);
+ non_blocking(fd, NON_BLOCKING);
+ state->stream = vstream_fdopen(fd, O_RDWR);
+ vstream_tweak_sock(state->stream);
+ state->buffer = vstring_alloc(1024);
+ state->read_fn = command_read;
+ state->data_state = ST_ANY;
+ PUSH_BACK_SET(state, "");
+ smtp_timeout_setup(state->stream, var_tmout);
+ state->in_mail = 0;
+ state->rcpts = 0;
+ state->delayed_response = 0;
+ state->delayed_args = 0;
+ /* Initialize file capture attributes. */
+#ifdef AF_INET6
+ if (sa->sa_family == AF_INET6)
+ state->addr_prefix = "ipv6:";
+ else
+#endif
+ state->addr_prefix = "";
+
+ state->helo_args = 0;
+ state->client_proto = enable_lmtp ? "LMTP" : "SMTP";
+ state->start_time = 0;
+ state->id = 0;
+ state->dump_file = 0;
+
+ /*
+ * We use the smtp_stream module to produce output. That module
+ * throws an exception via vstream_longjmp() in case of a timeout or
+ * lost connection error. Therefore we must prepare to handle these
+ * exceptions with vstream_setjmp().
+ */
+ switch (vstream_setjmp(state->stream)) {
+
+ default:
+ msg_panic("unknown read/write error");
+ /* NOTREACHED */
+
+ case SMTP_ERR_TIME:
+ msg_warn("write timeout");
+ disconnect(state);
+ return;
+
+ case SMTP_ERR_EOF:
+ msg_warn("lost connection");
+ disconnect(state);
+ return;
+
+ case 0:
+ if (command_resp(state, command_table, "connect", "") < 0)
+ disconnect(state);
+ else if (command_table->delay == 0) {
+ event_enable_read(fd, read_event, (void *) state);
+ event_request_timer(read_timeout, (void *) state, var_tmout);
+ }
+ }
+ }
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s [-468acCeEFLpPv] [-A abort_delay] [-b soft_bounce_reply] [-B hard_bounce_reply] [-d dump-template] [-D dump-template] [-f commands] [-h hostname] [-m max_concurrency] [-M message_quit_count] [-n quit_count] [-q commands] [-r commands] [-R root-dir] [-s commands] [-S start-string] [-u user_privs] [-w delay] [host]:port backlog", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ int backlog;
+ int ch;
+ int delay;
+ const char *protocols = INET_PROTO_NAME_ALL;
+ const char *root_dir = 0;
+ const char *user_privs = 0;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Fix 20051207.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Initialize diagnostics.
+ */
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "468aA:b:B:cCd:D:eEf:Fh:H:Ln:m:M:NpPq:Q:r:R:s:S:t:T:u:vw:W:")) > 0) {
+ switch (ch) {
+ case '4':
+ protocols = INET_PROTO_NAME_IPV4;
+ break;
+ case '6':
+ protocols = INET_PROTO_NAME_IPV6;
+ break;
+ case '8':
+ disable_8bitmime = 1;
+ break;
+ case 'a':
+ disable_saslauth = 1;
+ break;
+ case 'A':
+ if (!alldig(optarg) || (abort_delay = atoi(optarg)) < 0)
+ usage(argv[0]);
+ break;
+ case 'b':
+ if (optarg[0] != '4' || strspn(optarg, "0123456789") != 3) {
+ msg_error("bad soft error reply: %s", optarg);
+ usage(argv[0]);
+ } else
+ soft_error_resp = optarg;
+ break;
+ case 'B':
+ if (optarg[0] != '5' || strspn(optarg, "0123456789") != 3) {
+ msg_error("bad hard error reply: %s", optarg);
+ usage(argv[0]);
+ } else
+ hard_error_resp = optarg;
+ break;
+ case 'c':
+ show_count++;
+ break;
+ case 'C':
+ disable_xclient = 1;
+ reset_cmd_flags("xclient", FLAG_ENABLE);
+ break;
+ case 'd':
+ single_template = optarg;
+ break;
+ case 'D':
+ shared_template = optarg;
+ break;
+ case 'e':
+ disable_esmtp = 1;
+ break;
+ case 'E':
+ disable_enh_status = 1;
+ break;
+ case 'f':
+ set_cmds_flags(optarg, FLAG_HARD_ERR);
+ disable_pipelining = 1;
+ break;
+ case 'F':
+ disable_xforward = 1;
+ reset_cmd_flags("xforward", FLAG_ENABLE);
+ break;
+ case 'h':
+ var_myhostname = optarg;
+ break;
+ case 'H':
+ if ((data_read_delay = atoi(optarg)) <= 0)
+ msg_fatal("bad data read delay: %s", optarg);
+ break;
+ case 'L':
+ enable_lmtp = 1;
+ break;
+ case 'm':
+ if ((max_client_count = atoi(optarg)) <= 0)
+ msg_fatal("bad concurrency limit: %s", optarg);
+ break;
+ case 'M':
+ if ((max_msg_quit_count = atoi(optarg)) <= 0)
+ msg_fatal("bad message quit count: %s", optarg);
+ break;
+ case 'n':
+ if ((max_quit_count = atoi(optarg)) <= 0)
+ msg_fatal("bad quit count: %s", optarg);
+ break;
+ case 'N':
+ disable_dsn = 1;
+ break;
+ case 'p':
+ disable_pipelining = 1;
+ break;
+ case 'P':
+ pretend_pix = 1;
+ disable_esmtp = 1;
+ break;
+ case 'q':
+ set_cmds_flags(optarg, FLAG_DISCONNECT);
+ break;
+ case 'Q':
+ set_cmds_flags(optarg, FLAG_CLOSE);
+ break;
+ case 'r':
+ set_cmds_flags(optarg, FLAG_SOFT_ERR);
+ disable_pipelining = 1;
+ break;
+ case 'R':
+ root_dir = optarg;
+ break;
+ case 's':
+ openlog(basename(argv[0]), LOG_PID, LOG_MAIL);
+ set_cmds_flags(optarg, FLAG_SYSLOG);
+ break;
+ case 'S':
+ start_string = vstring_alloc(10);
+ unescape(start_string, optarg);
+ break;
+ case 't':
+ if ((var_tmout = atoi(optarg)) <= 0)
+ msg_fatal("bad timeout: %s", optarg);
+ break;
+ case 'T':
+ if ((inet_windowsize = atoi(optarg)) <= 0)
+ msg_fatal("bad TCP window size: %s", optarg);
+ break;
+ case 'u':
+ user_privs = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ if ((delay = atoi(optarg)) <= 0)
+ usage(argv[0]);
+ set_cmd_delay("data", delay, 0);
+ break;
+ case 'W':
+ set_cmd_delay_arg(optarg);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 2)
+ usage(argv[0]);
+ if ((backlog = atoi(argv[optind + 1])) <= 0)
+ usage(argv[0]);
+ if (single_template && shared_template)
+ msg_fatal("use only one of -d or -D, but not both");
+ if (geteuid() == 0 && user_privs == 0)
+ msg_fatal("-u option is required if running as root");
+
+ /*
+ * Initialize.
+ */
+ if (var_myhostname == 0)
+ var_myhostname = "smtp-sink";
+ set_cmds_flags(enable_lmtp ? "lhlo" :
+ disable_esmtp ? "helo" :
+ "helo, ehlo", FLAG_ENABLE);
+ proto_info = inet_proto_init("protocols", protocols);
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ sock = unix_listen(argv[optind] + 5, backlog, BLOCKING);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ sock = inet_listen(argv[optind], backlog, BLOCKING);
+ }
+ if (user_privs)
+ chroot_uid(root_dir, user_privs);
+
+ if (single_template)
+ mysrand((int) time((time_t *) 0));
+ else if (shared_template)
+ single_template = shared_template;
+
+ /*
+ * Start the event handler.
+ */
+ event_enable_read(sock, connect_event, (void *) 0);
+ for (;;)
+ event_loop(-1);
+}
diff --git a/src/smtpstone/smtp-source.c b/src/smtpstone/smtp-source.c
new file mode 100644
index 0000000..be388d6
--- /dev/null
+++ b/src/smtpstone/smtp-source.c
@@ -0,0 +1,1188 @@
+/*++
+/* NAME
+/* smtp-source 1
+/* SUMMARY
+/* parallelized SMTP/LMTP test generator
+/* SYNOPSIS
+/* .fi
+/* \fBsmtp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
+/*
+/* \fBsmtp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR
+/* DESCRIPTION
+/* \fBsmtp-source\fR connects to the named \fIhost\fR and TCP \fIport\fR
+/* (default: port 25)
+/* and sends one or more messages to it, either sequentially
+/* or in parallel. The program speaks either SMTP (default) or
+/* LMTP.
+/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers.
+/* IPv4 and IPv6 are the default.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
+/* .IP \fB-4\fR
+/* Connect to the server with IPv4. This option has no effect when
+/* Postfix is built without IPv6 support.
+/* .IP \fB-6\fR
+/* Connect to the server with IPv6. This option is not available when
+/* Postfix is built without IPv6 support.
+/* .IP "\fB-A\fR"
+/* Don't abort when the server sends something other than the
+/* expected positive reply code.
+/* .IP \fB-c\fR
+/* Display a running counter that is incremented each time
+/* an SMTP DATA command completes.
+/* .IP "\fB-C \fIcount\fR"
+/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times
+/* before giving up. The default count is 1. Specify a larger count in
+/* order to work around a problem with TCP/IP stacks that send RESET
+/* when the listen queue is full.
+/* .IP \fB-d\fR
+/* Don't disconnect after sending a message; send the next
+/* message over the same connection.
+/* .IP "\fB-f \fIfrom\fR"
+/* Use the specified sender address (default: <foo@myhostname>).
+/* .IP "\fB-F \fIfile\fR"
+/* Send the pre-formatted message header and body in the
+/* specified \fIfile\fR, while prepending '.' before lines that
+/* begin with '.', and while appending CRLF after each line.
+/* .IP "\fB-l \fIlength\fR"
+/* Send \fIlength\fR bytes as message payload. The length does not
+/* include message headers.
+/* .IP \fB-L\fR
+/* Speak LMTP rather than SMTP.
+/* .IP "\fB-m \fImessage_count\fR"
+/* Send the specified number of messages (default: 1).
+/* .IP "\fB-M \fImyhostname\fR"
+/* Use the specified hostname or [address] in the HELO command
+/* and in the default sender and recipient addresses, instead
+/* of the machine hostname.
+/* .IP "\fB-N\fR"
+/* Prepend a non-repeating sequence number to each recipient
+/* address. This avoids the artificial 100% hit rate in the
+/* resolve and rewrite client caches and exercises the
+/* trivial-rewrite daemon, better approximating Postfix
+/* performance under real-life work-loads.
+/* .IP \fB-o\fR
+/* Old mode: don't send HELO, and don't send message headers.
+/* .IP "\fB-r \fIrecipient_count\fR"
+/* Send the specified number of recipients per transaction (default: 1).
+/* Recipient names are generated by prepending a number to the
+/* recipient address.
+/* .IP "\fB-R \fIinterval\fR"
+/* Wait for a random period of time 0 <= n <= interval between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* .IP "\fB-s \fIsession_count\fR"
+/* Run the specified number of SMTP sessions in parallel (default: 1).
+/* .IP "\fB-S \fIsubject\fR"
+/* Send mail with the named subject line (default: none).
+/* .IP "\fB-t \fIto\fR"
+/* Use the specified recipient address (default: <foo@myhostname>).
+/* .IP "\fB-T \fIwindowsize\fR"
+/* Override the default TCP window size. To work around
+/* broken TCP window scaling implementations, specify a
+/* value > 0 and < 65536.
+/* .IP \fB-v\fR
+/* Make the program more verbose, for debugging purposes.
+/* .IP "\fB-w \fIinterval\fR"
+/* Wait a fixed time between messages.
+/* Suspending one thread does not affect other delivery threads.
+/* .IP [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
+/* Connect via TCP to host \fIhost\fR, port \fIport\fR. The default
+/* port is \fBsmtp\fR.
+/* .IP \fBunix:\fIpathname\fR
+/* Connect to the UNIX-domain socket at \fIpathname\fR.
+/* BUGS
+/* No SMTP command pipelining support.
+/* SEE ALSO
+/* smtp-sink(1), SMTP/LMTP message dump
+/* 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 <sys/wait.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <get_hostname.h>
+#include <split_at.h>
+#include <connect.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <iostuff.h>
+#include <sane_connect.h>
+#include <host_port.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <valid_hostname.h>
+#include <valid_mailhost_addr.h>
+#include <compat_va_copy.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <mail_date.h>
+#include <mail_version.h>
+
+/* Application-specific. */
+
+ /*
+ * Per-session data structure with state.
+ *
+ * This software can maintain multiple parallel connections to the same SMTP
+ * server. However, it makes no more than one connection request at a time
+ * to avoid overwhelming the server with SYN packets and having to back off.
+ * Back-off would screw up the benchmark. Pending connection requests are
+ * kept in a linear list.
+ */
+typedef struct SESSION {
+ int xfer_count; /* # of xfers in session */
+ int rcpt_done; /* # of recipients done */
+ int rcpt_count; /* # of recipients to go */
+ int rcpt_accepted; /* # of recipients accepted */
+ VSTREAM *stream; /* open connection */
+ int connect_count; /* # of connect()s to retry */
+ struct SESSION *next; /* connect() queue linkage */
+} SESSION;
+
+static SESSION *last_session; /* connect() queue tail */
+
+ /*
+ * Structure with broken-up SMTP server response.
+ */
+typedef struct { /* server response */
+ int code; /* status */
+ char *str; /* text */
+ VSTRING *buf; /* origin of text */
+} RESPONSE;
+
+static VSTRING *buffer;
+static int var_line_limit = 10240;
+static int var_timeout = 300;
+static const char *var_myhostname;
+static int session_count;
+static int message_count = 1;
+static struct sockaddr_storage ss;
+
+#undef sun
+static struct sockaddr_un sun;
+static struct sockaddr *sa;
+static int sa_length;
+static int recipients = 1;
+static char *defaddr;
+static char *recipient;
+static char *sender;
+static char *message_data;
+static int message_length;
+static int disconnect = 1;
+static int count = 0;
+static int counter = 0;
+static int send_helo_first = 1;
+static int send_headers = 1;
+static int connect_count = 1;
+static int random_delay = 0;
+static int fixed_delay = 0;
+static int talk_lmtp = 0;
+static char *subject = 0;
+static int number_rcpts = 0;
+static int allow_reject = 0;
+
+static void enqueue_connect(SESSION *);
+static void start_connect(SESSION *);
+static void connect_done(int, void *);
+static void read_banner(int, void *);
+static void send_helo(SESSION *);
+static void helo_done(int, void *);
+static void send_mail(SESSION *);
+static void mail_done(int, void *);
+static void send_rcpt(int, void *);
+static void rcpt_done(int, void *);
+static void send_data(int, void *);
+static void data_done(int, void *);
+static void dot_done(int, void *);
+static void send_rset(int, void *);
+static void rset_done(int, void *);
+static void send_quit(SESSION *);
+static void quit_done(int, void *);
+static void close_session(SESSION *);
+
+/* random_interval - generate a random value in 0 .. (small) interval */
+
+static int random_interval(int interval)
+{
+ return (rand() % (interval + 1));
+}
+
+/* command - send an SMTP command */
+
+static void command(VSTREAM *stream, char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ /*
+ * Optionally, log the command before actually sending, so we can see
+ * what the program is trying to do.
+ */
+ if (msg_verbose) {
+ va_list ap2;
+
+ VA_COPY(ap2, ap);
+ vmsg_info(fmt, ap2);
+ va_end(ap2);
+ }
+ smtp_vprintf(stream, fmt, ap);
+ va_end(ap);
+ smtp_flush(stream);
+}
+
+/* socket_error - look up and reset the last socket error */
+
+static int socket_error(int sock)
+{
+ int error;
+ SOCKOPT_SIZE error_len;
+
+ /*
+ * Some Solaris 2 versions have getsockopt() itself return the error,
+ * instead of returning it via the parameter list.
+ */
+ error = 0;
+ error_len = sizeof(error);
+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0)
+ return (-1);
+ if (error) {
+ errno = error;
+ return (-1);
+ }
+
+ /*
+ * No problems.
+ */
+ return (0);
+}
+
+/* response - read and process SMTP server response */
+
+static RESPONSE *response(VSTREAM *stream, VSTRING *buf)
+{
+ static RESPONSE rdata;
+ int more;
+ char *cp;
+
+ /*
+ * Initialize the response data buffer. smtp_get() defends against a
+ * denial of service attack by limiting the amount of single-line text,
+ * and the loop below limits the amount of multi-line text that we are
+ * willing to store.
+ */
+ if (rdata.buf == 0)
+ rdata.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.
+ */
+#define BUF ((char *) vstring_str(buf))
+ VSTRING_RESET(rdata.buf);
+ for (;;) {
+ smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP);
+ for (cp = BUF; *cp != 0; cp++)
+ if (!ISPRINT(*cp) && !ISSPACE(*cp))
+ *cp = '?';
+ cp = BUF;
+ if (msg_verbose)
+ msg_info("<<< %s", cp);
+ while (ISDIGIT(*cp))
+ cp++;
+ rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0);
+ if ((more = (*cp == '-')) != 0)
+ cp++;
+ while (ISSPACE(*cp))
+ cp++;
+ if (VSTRING_LEN(rdata.buf) < var_line_limit)
+ vstring_strcat(rdata.buf, cp);
+ if (more == 0)
+ break;
+ if (VSTRING_LEN(rdata.buf) < var_line_limit)
+ VSTRING_ADDCH(rdata.buf, '\n');
+ }
+ VSTRING_TERMINATE(rdata.buf);
+ rdata.str = vstring_str(rdata.buf);
+ return (&rdata);
+}
+
+/* exception_text - translate exceptions from the smtp_stream module */
+
+static char *exception_text(int except)
+{
+ switch (except) {
+ case SMTP_ERR_EOF:
+ return ("lost connection");
+ case SMTP_ERR_TIME:
+ return ("timeout");
+ default:
+ msg_panic("exception_text: unknown exception %d", except);
+ }
+ /* NOTREACHED */
+}
+
+/* startup - connect to server but do not wait */
+
+static void startup(SESSION *session)
+{
+ if (message_count-- <= 0) {
+ myfree((void *) session);
+ session_count--;
+ return;
+ }
+ if (session->stream == 0) {
+ enqueue_connect(session);
+ } else {
+ send_mail(session);
+ }
+}
+
+/* start_event - invoke startup from timer context */
+
+static void start_event(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+
+ startup(session);
+}
+
+/* start_another - start another session */
+
+static void start_another(SESSION *session)
+{
+ if (random_delay > 0) {
+ event_request_timer(start_event, (void *) session,
+ random_interval(random_delay));
+ } else if (fixed_delay > 0) {
+ event_request_timer(start_event, (void *) session, fixed_delay);
+ } else {
+ startup(session);
+ }
+}
+
+/* enqueue_connect - queue a connection request */
+
+static void enqueue_connect(SESSION *session)
+{
+ session->next = 0;
+ if (last_session == 0) {
+ last_session = session;
+ start_connect(session);
+ } else {
+ last_session->next = session;
+ last_session = session;
+ }
+}
+
+/* dequeue_connect - connection request completed */
+
+static void dequeue_connect(SESSION *session)
+{
+ if (session == last_session) {
+ if (session->next != 0)
+ msg_panic("dequeue_connect: queue ends after last");
+ last_session = 0;
+ } else {
+ if (session->next == 0)
+ msg_panic("dequeue_connect: queue ends before last");
+ start_connect(session->next);
+ }
+}
+
+/* fail_connect - handle failed startup */
+
+static void fail_connect(SESSION *session)
+{
+ if (session->connect_count-- == 1)
+ msg_fatal("connect: %m");
+ msg_warn("connect: %m");
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+#ifdef MISSING_USLEEP
+ doze(10);
+#else
+ usleep(10);
+#endif
+ start_connect(session);
+}
+
+/* start_connect - start TCP handshake */
+
+static void start_connect(SESSION *session)
+{
+ int fd;
+ struct linger linger;
+
+ /*
+ * Some systems don't set the socket error when connect() fails early
+ * (loopback) so we must deal with the error immediately, rather than
+ * retrieving it later with getsockopt(). We can't use MSG_PEEK to
+ * distinguish between server disconnect and connection refused.
+ */
+ if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+ msg_fatal("socket: %m");
+ (void) non_blocking(fd, NON_BLOCKING);
+ linger.l_onoff = 1;
+ linger.l_linger = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger,
+ sizeof(linger)) < 0)
+ msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger);
+ session->stream = vstream_fdopen(fd, O_RDWR);
+ event_enable_write(fd, connect_done, (void *) session);
+ smtp_timeout_setup(session->stream, var_timeout);
+ if (inet_windowsize > 0)
+ set_inet_windowsize(fd, inet_windowsize);
+ if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS)
+ fail_connect(session);
+}
+
+/* connect_done - send message sender info */
+
+static void connect_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int fd = vstream_fileno(session->stream);
+
+ /*
+ * Try again after some delay when the connection failed, in case they
+ * run a Mickey Mouse protocol stack.
+ */
+ if (socket_error(fd) < 0) {
+ fail_connect(session);
+ } else {
+ non_blocking(fd, BLOCKING);
+ /* Disable write events. */
+ event_disable_readwrite(fd);
+ event_enable_read(fd, read_banner, (void *) session);
+ dequeue_connect(session);
+ /* 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(session->stream);
+ }
+}
+
+/* read_banner - receive SMTP server greeting */
+
+static void read_banner(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Prepare for disaster.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while reading server greeting", exception_text(except));
+
+ /*
+ * Read and parse the server's SMTP greeting banner.
+ */
+ if (((resp = response(session->stream, buffer))->code / 100) == 2) {
+ /* void */ ;
+ } else if (allow_reject) {
+ msg_warn("rejected at server banner: %d %s", resp->code, resp->str);
+ } else {
+ msg_fatal("rejected at server banner: %d %s", resp->code, resp->str);
+ }
+
+ /*
+ * Send helo or send the envelope sender address.
+ */
+ if (send_helo_first)
+ send_helo(session);
+ else
+ send_mail(session);
+}
+
+/* send_helo - send hostname */
+
+static void send_helo(SESSION *session)
+{
+ int except;
+ const char *NOCLOBBER protocol = (talk_lmtp ? "LHLO" : "HELO");
+
+ /*
+ * Send the standard greeting with our hostname
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending %s", exception_text(except), protocol);
+
+ command(session->stream, "%s %s", protocol, var_myhostname);
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), helo_done, (void *) session);
+}
+
+/* helo_done - handle HELO response */
+
+static void helo_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+ const char *protocol = (talk_lmtp ? "LHLO" : "HELO");
+
+ /*
+ * Get response to HELO command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending %s", exception_text(except), protocol);
+
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ /* void */ ;
+ } else if (allow_reject) {
+ msg_warn("%s rejected: %d %s", protocol, resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ } else {
+ msg_fatal("%s rejected: %d %s", protocol, resp->code, resp->str);
+ }
+
+ send_mail(session);
+}
+
+/* send_mail - send envelope sender */
+
+static void send_mail(SESSION *session)
+{
+ int except;
+
+ /*
+ * Send the envelope sender address.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending sender", exception_text(except));
+
+ command(session->stream, "MAIL FROM:<%s>", sender);
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), mail_done, (void *) session);
+}
+
+/* mail_done - handle MAIL response */
+
+static void mail_done(int unused, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Get response to MAIL command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending sender", exception_text(except));
+
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ session->rcpt_count = recipients;
+ session->rcpt_done = 0;
+ session->rcpt_accepted = 0;
+ send_rcpt(unused, context);
+ } else if (allow_reject) {
+ msg_warn("sender rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ send_rset(unused, context);
+ } else {
+ msg_fatal("sender rejected: %d %s", resp->code, resp->str);
+ }
+}
+
+/* send_rcpt - send recipient address */
+
+static void send_rcpt(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int except;
+
+ /*
+ * Send envelope recipient address.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending recipient", exception_text(except));
+
+ if (session->rcpt_count > 1 || number_rcpts > 0)
+ command(session->stream, "RCPT TO:<%d%s>",
+ number_rcpts ? number_rcpts++ : session->rcpt_count,
+ recipient);
+ else
+ command(session->stream, "RCPT TO:<%s>", recipient);
+ session->rcpt_count--;
+ session->rcpt_done++;
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), rcpt_done, (void *) session);
+}
+
+/* rcpt_done - handle RCPT completion */
+
+static void rcpt_done(int unused, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Get response to RCPT command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending recipient", exception_text(except));
+
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ session->rcpt_accepted++;
+ } else if (allow_reject) {
+ msg_warn("recipient rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ } else {
+ msg_fatal("recipient rejected: %d %s", resp->code, resp->str);
+ }
+
+ /*
+ * Send another RCPT command or send DATA.
+ */
+ if (session->rcpt_count > 0)
+ send_rcpt(unused, context);
+ else if (session->rcpt_accepted > 0)
+ send_data(unused, context);
+ else
+ send_rset(unused, context);
+}
+
+/* send_data - send DATA command */
+
+static void send_data(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ int except;
+
+ /*
+ * Request data transmission.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending DATA command", exception_text(except));
+ command(session->stream, "DATA");
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), data_done, (void *) session);
+}
+
+/* data_done - send message content */
+
+static void data_done(int unused, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+ static const char *mydate;
+ static int mypid;
+
+ /*
+ * Get response to DATA command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending DATA command", exception_text(except));
+ if ((resp = response(session->stream, buffer))->code == 354) {
+ /* see below */ ;
+ } else if (allow_reject) {
+ msg_warn("data rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ send_rset(unused, context);
+ return;
+ } else {
+ msg_fatal("data rejected: %d %s", resp->code, resp->str);
+ }
+
+ /*
+ * Send basic header to keep mailers that bother to examine them happy.
+ */
+ if (send_headers) {
+ if (mydate == 0) {
+ mydate = mail_date(time((time_t *) 0));
+ mypid = getpid();
+ }
+ smtp_printf(session->stream, "From: <%s>", sender);
+ smtp_printf(session->stream, "To: <%s>", recipient);
+ smtp_printf(session->stream, "Date: %s", mydate);
+ smtp_printf(session->stream, "Message-Id: <%04x.%04x.%04x@%s>",
+ mypid, vstream_fileno(session->stream), message_count, var_myhostname);
+ if (subject)
+ smtp_printf(session->stream, "Subject: %s", subject);
+ smtp_fputs("", 0, session->stream);
+ }
+
+ /*
+ * Send some garbage.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending message", exception_text(except));
+ if (message_length == 0) {
+ smtp_fputs("La de da de da 1.", 17, session->stream);
+ smtp_fputs("La de da de da 2.", 17, session->stream);
+ smtp_fputs("La de da de da 3.", 17, session->stream);
+ smtp_fputs("La de da de da 4.", 17, session->stream);
+ } else {
+
+ /*
+ * XXX This may cause the process to block with message content
+ * larger than VSTREAM_BUFIZ bytes.
+ */
+ smtp_fputs(message_data, message_length, session->stream);
+ }
+
+ /*
+ * Send end of message and process the server response.
+ */
+ command(session->stream, ".");
+
+ /*
+ * Update the running counter.
+ */
+ if (count) {
+ counter++;
+ vstream_printf("%d\r", counter);
+ vstream_fflush(VSTREAM_OUT);
+ }
+
+ /*
+ * Prepare for the next event.
+ */
+ event_enable_read(vstream_fileno(session->stream), dot_done, (void *) session);
+}
+
+/* dot_done - send QUIT or start another transaction */
+
+static void dot_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Get response to "." command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending message", exception_text(except));
+ do { /* XXX this could block */
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ /* void */ ;
+ } else if (allow_reject) {
+ msg_warn("end of data rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ } else {
+ msg_fatal("end of data rejected: %d %s", resp->code, resp->str);
+ }
+ } while (talk_lmtp && --session->rcpt_done > 0);
+ session->xfer_count++;
+
+ /*
+ * Say goodbye or send the next message.
+ */
+ if (disconnect || message_count < 1) {
+ send_quit(session);
+ } else {
+ event_disable_readwrite(vstream_fileno(session->stream));
+ start_another(session);
+ }
+}
+
+/* send_rset - send RSET command */
+
+static void send_rset(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+
+ command(session->stream, "RSET");
+ event_enable_read(vstream_fileno(session->stream), rset_done, (void *) session);
+}
+
+/* rset_done - handle RSET reply */
+
+static void rset_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+ RESPONSE *resp;
+ int except;
+
+ /*
+ * Get response to RSET command.
+ */
+ if ((except = vstream_setjmp(session->stream)) != 0)
+ msg_fatal("%s while sending message", exception_text(except));
+ if ((resp = response(session->stream, buffer))->code / 100 == 2) {
+ /* void */
+ } else if (allow_reject) {
+ msg_warn("rset rejected: %d %s", resp->code, resp->str);
+ if (resp->code == 421 || resp->code == 521) {
+ close_session(session);
+ return;
+ }
+ } else {
+ msg_fatal("rset rejected: %d %s", resp->code, resp->str);
+ }
+
+ /*
+ * Say goodbye or send the next message.
+ */
+ if (disconnect || message_count < 1) {
+ send_quit(session);
+ } else {
+ event_disable_readwrite(vstream_fileno(session->stream));
+ start_another(session);
+ }
+}
+
+/* send_quit - send QUIT command */
+
+static void send_quit(SESSION *session)
+{
+ command(session->stream, "QUIT");
+ event_enable_read(vstream_fileno(session->stream), quit_done, (void *) session);
+}
+
+/* quit_done - disconnect */
+
+static void quit_done(int unused_event, void *context)
+{
+ SESSION *session = (SESSION *) context;
+
+ (void) response(session->stream, buffer);
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+ start_another(session);
+}
+
+/* close_session - disconnect, for example after 421 or 521 reply */
+
+static void close_session(SESSION *session)
+{
+ event_disable_readwrite(vstream_fileno(session->stream));
+ vstream_fclose(session->stream);
+ session->stream = 0;
+ start_another(session);
+}
+
+/* usage - explain */
+
+static void usage(char *myname)
+{
+ msg_fatal("usage: %s -cdLNov -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -r rcptcount -R delay -w delay host[:port]", myname);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - parse JCL and start the machine */
+
+int main(int argc, char **argv)
+{
+ SESSION *session;
+ char *host;
+ char *port;
+ char *path;
+ int path_len;
+ int sessions = 1;
+ int ch;
+ int i;
+ char *buf;
+ const char *parse_err;
+ struct addrinfo *res;
+ int aierr;
+ const char *protocols = INET_PROTO_NAME_ALL;
+ char *message_file = 0;
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ signal(SIGPIPE, SIG_IGN);
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:")) > 0) {
+ switch (ch) {
+ case '4':
+ protocols = INET_PROTO_NAME_IPV4;
+ break;
+ case '6':
+ protocols = INET_PROTO_NAME_IPV6;
+ break;
+ case 'A':
+ allow_reject = 1;
+ break;
+ case 'c':
+ count++;
+ break;
+ case 'C':
+ if ((connect_count = atoi(optarg)) <= 0)
+ msg_fatal("bad connection count: %s", optarg);
+ break;
+ case 'd':
+ disconnect = 0;
+ break;
+ case 'f':
+ sender = optarg;
+ break;
+ case 'F':
+ if (message_file == 0 && message_length > 0)
+ msg_fatal("-l option cannot be used with -F");
+ message_file = optarg;
+ break;
+ case 'l':
+ if (message_file != 0)
+ msg_fatal("-l option cannot be used with -F");
+ if ((message_length = atoi(optarg)) <= 0)
+ msg_fatal("bad message length: %s", optarg);
+ break;
+ case 'L':
+ talk_lmtp = 1;
+ break;
+ case 'm':
+ if ((message_count = atoi(optarg)) <= 0)
+ msg_fatal("bad message count: %s", optarg);
+ break;
+ case 'M':
+ if (*optarg == '[') {
+ if (!valid_mailhost_literal(optarg, DO_GRIPE))
+ msg_fatal("bad address literal: %s", optarg);
+ } else {
+ if (!valid_hostname(optarg, DO_GRIPE))
+ msg_fatal("bad hostname: %s", optarg);
+ }
+ var_myhostname = optarg;
+ break;
+ case 'N':
+ number_rcpts = 1;
+ break;
+ case 'o':
+ send_helo_first = 0;
+ send_headers = 0;
+ break;
+ case 'r':
+ if ((recipients = atoi(optarg)) <= 0)
+ msg_fatal("bad recipient count: %s", optarg);
+ break;
+ case 'R':
+ if (fixed_delay > 0)
+ msg_fatal("do not use -w and -R options at the same time");
+ if ((random_delay = atoi(optarg)) <= 0)
+ msg_fatal("bad random delay: %s", optarg);
+ break;
+ case 's':
+ if ((sessions = atoi(optarg)) <= 0)
+ msg_fatal("bad session count: %s", optarg);
+ break;
+ case 'S':
+ subject = optarg;
+ break;
+ case 't':
+ recipient = optarg;
+ break;
+ case 'T':
+ if ((inet_windowsize = atoi(optarg)) <= 0)
+ msg_fatal("bad TCP window size: %s", optarg);
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'w':
+ if (random_delay > 0)
+ msg_fatal("do not use -w and -R options at the same time");
+ if ((fixed_delay = atoi(optarg)) <= 0)
+ msg_fatal("bad fixed delay: %s", optarg);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc - optind != 1)
+ usage(argv[0]);
+
+ if (random_delay > 0)
+ srand(getpid());
+
+ /*
+ * Initialize the message content, SMTP encoded. smtp_fputs() will append
+ * another \r\n but we don't care.
+ */
+ if (message_file != 0) {
+ VSTREAM *fp;
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *msg = vstring_alloc(100);
+
+ if ((fp = vstream_fopen(message_file, O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", message_file);
+ while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) {
+ if (*vstring_str(buf) == '.')
+ VSTRING_ADDCH(msg, '.');
+ vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf));
+ vstring_memcat(msg, "\r\n", 2);
+ }
+ if (vstream_ferror(fp))
+ msg_fatal("read %s: %m", message_file);
+ vstream_fclose(fp);
+ vstring_free(buf);
+ message_length = VSTRING_LEN(msg);
+ message_data = vstring_export(msg);
+ send_headers = 0;
+ } else if (message_length > 0) {
+ message_data = mymalloc(message_length);
+ memset(message_data, 'X', message_length);
+ for (i = 80; i < message_length; i += 80) {
+ message_data[i - 80] = "0123456789"[(i / 80) % 10];
+ message_data[i - 2] = '\r';
+ message_data[i - 1] = '\n';
+ }
+ }
+
+ /*
+ * Translate endpoint address to internal form.
+ */
+ (void) inet_proto_init("protocols", protocols);
+ if (strncmp(argv[optind], "unix:", 5) == 0) {
+ path = argv[optind] + 5;
+ path_len = strlen(path);
+ if (path_len >= (int) sizeof(sun.sun_path))
+ msg_fatal("unix-domain name too long: %s", path);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = path_len + 1;
+#endif
+ memcpy(sun.sun_path, path, path_len);
+ sa = (struct sockaddr *) &sun;
+ sa_length = sizeof(sun);
+ } else {
+ if (strncmp(argv[optind], "inet:", 5) == 0)
+ argv[optind] += 5;
+ buf = mystrdup(argv[optind]);
+ if ((parse_err = host_port(buf, &host, (char *) 0, &port, "smtp")) != 0)
+ msg_fatal("%s: %s", argv[optind], parse_err);
+ if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
+ msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr));
+ myfree(buf);
+ sa = (struct sockaddr *) &ss;
+ if (res->ai_addrlen > sizeof(ss))
+ msg_fatal("address length %d > buffer length %d",
+ (int) res->ai_addrlen, (int) sizeof(ss));
+ memcpy((void *) sa, res->ai_addr, res->ai_addrlen);
+ sa_length = res->ai_addrlen;
+#ifdef HAS_SA_LEN
+ sa->sa_len = sa_length;
+#endif
+ freeaddrinfo(res);
+ }
+
+ /*
+ * smtp_get() makes sure the SMTP server cannot run us out of memory by
+ * sending never-ending lines of text.
+ */
+ if (buffer == 0)
+ buffer = vstring_alloc(100);
+
+ /*
+ * Make sure we have sender and recipient addresses.
+ */
+ if (var_myhostname == 0)
+ var_myhostname = get_hostname();
+ if (sender == 0 || recipient == 0) {
+ vstring_sprintf(buffer, "foo@%s", var_myhostname);
+ defaddr = mystrdup(vstring_str(buffer));
+ if (sender == 0)
+ sender = defaddr;
+ if (recipient == 0)
+ recipient = defaddr;
+ }
+
+ /*
+ * Start sessions.
+ */
+ while (sessions-- > 0) {
+ session = (SESSION *) mymalloc(sizeof(*session));
+ session->stream = 0;
+ session->xfer_count = 0;
+ session->connect_count = connect_count;
+ session->next = 0;
+ session_count++;
+ startup(session);
+ }
+ for (;;) {
+ event_loop(-1);
+ if (session_count <= 0 && message_count <= 0) {
+ if (count) {
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ exit(0);
+ }
+ }
+}
diff --git a/src/smtpstone/throughput b/src/smtpstone/throughput
new file mode 100644
index 0000000..4853d75
--- /dev/null
+++ b/src/smtpstone/throughput
@@ -0,0 +1,28 @@
+Host: P233 BSD/OS 3.1 smtp-source and smtp-sink on the same host,
+100 msgs in 10 sessions.
+
+send = time to send 100 msgs into postfix
+rest = time for Postfix to finish
+total = total elapsed time
+
+19990627
+
+send rest total
+14 10 25
+10 8 18
+ 9 10 19
+ 9 17 26
+ 8 11 19
+ 8 9 17
+
+19990906
+
+send rest total
+10 15 25
+ 9 10 19
+ 8 9 17
+ 9 8 17
+ 8 9 17
+ 9 8 17
+ 8 9 17
+ 8 8 16
diff --git a/src/smtpstone/vmail-local b/src/smtpstone/vmail-local
new file mode 100644
index 0000000..84269b2
--- /dev/null
+++ b/src/smtpstone/vmail-local
@@ -0,0 +1,43 @@
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple
+Sat May 9 22:08:27 EDT 1998
+ 6.29 real 0.08 user 0.17 sys
+May 9 22:08:27 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu
+May 9 22:08:58 nipple postfix/local[2330]: 5082D53AD0: to=<wietse@porcupine.org
+Elapsed 31
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple
+Sat May 9 22:09:13 EDT 1998
+ 5.94 real 0.09 user 0.16 sys
+May 9 22:09:13 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu
+May 9 22:09:40 nipple postfix/local[2260]: 5082FBE00B: to=<wietse@porcupine.org
+Elapsed 27
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple
+Sat May 9 22:09:43 EDT 1998
+ 5.97 real 0.06 user 0.17 sys
+May 9 22:09:43 nipple postfix/local[2321]: 5082C7E2F3: to=<wietse@porcupine.org
+May 9 22:10:13 nipple postfix/local[2517]: 5082C8772C: to=<wietse@porcupine.org
+Elapsed 30
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple
+Sat May 9 22:10:30 EDT 1998
+ 6.26 real 0.08 user 0.20 sys
+May 9 22:10:30 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu
+May 9 22:10:58 nipple postfix/local[2330]: 5082C7A2C9: to=<wietse@porcupine.org
+Elapsed 28
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple
+Sat May 9 22:11:06 EDT 1998
+ 6.04 real 0.05 user 0.25 sys
+May 9 22:11:06 nipple wietse[100]: postfix/smtpd[2675]: connect from fist.porcu
+May 9 22:11:33 nipple postfix/local[2685]: 5082D23842: to=<wietse@porcupine.org
+Elapsed 27
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple
+Sat May 9 22:11:38 EDT 1998
+ 6.11 real 0.06 user 0.24 sys
+May 9 22:11:38 nipple postfix/local[2686]: 5082E318AD: to=<wietse@porcupine.org
+May 9 22:12:07 nipple postfix/local[2687]: 5082CBF5EC: to=<wietse@porcupine.org
+Elapsed 31
+
+Elapsed: 29, throughput: 3.4 msg/s (local_delivery_concurrency not set)
diff --git a/src/smtpstone/vmail-relay b/src/smtpstone/vmail-relay
new file mode 100644
index 0000000..e9055b5
--- /dev/null
+++ b/src/smtpstone/vmail-relay
@@ -0,0 +1,57 @@
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple
+Sat May 9 21:54:48 EDT 1998
+ 7.09 real 0.03 user 0.18 sys
+May 9 21:54:48 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:55:05 nipple wietse[100]: postfix/smtp[2215]: 508672B24D: to=<foo@fist
+Elapsed 17
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple
+Sat May 9 21:55:09 EDT 1998
+ 6.02 real 0.09 user 0.13 sys
+May 9 21:55:09 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:55:23 nipple wietse[100]: postfix/smtp[2217]: 50864BCA59: to=<foo@fist
+Elapsed 14
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple
+Sat May 9 21:55:26 EDT 1998
+ 6.06 real 0.07 user 0.17 sys
+May 9 21:55:27 nipple wietse[100]: postfix/smtpd[2205]: connect from fist.porcu
+May 9 21:55:41 nipple wietse[100]: postfix/smtp[2218]: 508753CB5D: to=<foo@fist
+Elapsed 14
+
+fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple
+Sat May 9 21:55:53 EDT 1998
+ 6.01 real 0.13 user 0.14 sys
+May 9 21:55:53 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:56:08 nipple wietse[100]: postfix/smtp[2216]: 50868D8D44: to=<foo@fist
+Elapsed 15
+
+fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple
+Sat May 9 21:56:10 EDT 1998
+ 6.01 real 0.14 user 0.13 sys
+May 9 21:56:10 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:56:24 nipple wietse[100]: postfix/smtp[2229]: 50875825BA: to=<foo@fist
+Elapsed 14
+
+fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple
+Sat May 9 21:56:28 EDT 1998
+ 6.10 real 0.03 user 0.23 sys
+May 9 21:56:28 nipple wietse[100]: postfix/smtpd[2205]: connect from fist.porcu
+May 9 21:56:43 nipple wietse[100]: postfix/smtp[2229]: 508651D724: to=<foo@fist
+Elapsed 15
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 nipple
+Sat May 9 21:56:58 EDT 1998
+ 6.02 real 0.10 user 0.21 sys
+May 9 21:56:58 nipple wietse[100]: postfix/smtpd[2222]: connect from fist.porcu
+May 9 21:57:13 nipple wietse[100]: postfix/smtp[2231]: 5085D279A7: to=<foo@fist
+Elapsed 15
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 nipple
+Sat May 9 21:57:18 EDT 1998
+ 6.04 real 0.09 user 0.19 sys
+May 9 21:57:18 nipple wietse[100]: postfix/smtpd[2222]: connect from fist.porcu
+May 9 21:57:32 nipple wietse[100]: postfix/smtp[2230]: 508774A28A: to=<foo@fist
+Elapsed 14
+
+Elapsed: 14.4 s, throughput: 6.9 msg/s