summaryrefslogtreecommitdiffstats
path: root/src/smtpd
diff options
context:
space:
mode:
Diffstat (limited to '')
l---------src/smtpd/.indent.pro1
-rw-r--r--src/smtpd/.printfck25
-rw-r--r--src/smtpd/Makefile.in679
-rw-r--r--src/smtpd/smtpd.c6776
-rw-r--r--src/smtpd/smtpd.h446
-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.c352
-rw-r--r--src/smtpd/smtpd_chat.h45
-rw-r--r--src/smtpd/smtpd_check.c6445
-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.c658
-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.c396
-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
59 files changed, 22195 insertions, 0 deletions
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..8c4132a
--- /dev/null
+++ b/src/smtpd/Makefile.in
@@ -0,0 +1,679 @@
+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.3.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/hfrom_format.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/hfrom_format.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/match_list.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/sasl_mech_filter.h
+smtpd_sasl_glue.o: ../../include/sock_addr.h
+smtpd_sasl_glue.o: ../../include/string_list.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..4f4aedd
--- /dev/null
+++ b/src/smtpd/smtpd.c
@@ -0,0 +1,6776 @@
+/*++
+/* 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 - 3.6:
+/* .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.
+/* .PP
+/* Available in Postfix version 3.6 and later:
+/* .IP "\fBsmtpd_relay_before_recipient_restrictions (see 'postconf -d' output)\fR"
+/* Evaluate smtpd_relay_restrictions before smtpd_recipient_restrictions.
+/* .IP "\fBknown_tcp_ports (lmtp=24, smtp=25, smtps=submissions=465, submission=587)\fR"
+/* Optional setting that avoids lookups in the \fBservices\fR(5) database.
+/* .PP
+/* Available in Postfix version 3.7 and later:
+/* .IP "\fBsmtpd_per_request_deadline (normal: no, overload: yes)\fR"
+/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout
+/* time limits, from a time limit per plaintext or TLS read or write
+/* call, to a combined time limit for receiving a complete SMTP request
+/* and for sending a complete SMTP response.
+/* .IP "\fBsmtpd_min_data_rate (500)\fR"
+/* The minimum plaintext data transfer rate in bytes/second for
+/* DATA and BDAT requests, when deadlines are enabled with
+/* smtpd_per_request_deadline.
+/* 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.
+/* .PP
+/* Available in Postfix 3.6 and later:
+/* .IP "\fBsmtpd_sasl_mechanism_filter (!external, static:rest)\fR"
+/* If non-empty, a filter for the SASL mechanism names that the
+/* Postfix SMTP server will announce in the EHLO response.
+/* 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 (see 'postconf -d' output)\fR"
+/* 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 (see 'postconf -d' output)\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 (see postconf -d output)\fR"
+/* TLS protocols accepted by the Postfix SMTP server 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 nexthop destination,
+/* remote client or server name or network address matches a pattern
+/* given with the debug_peer_list parameter.
+/* .IP "\fBdebug_peer_list (empty)\fR"
+/* Optional list of nexthop destination, remote client or server
+/* name or network address patterns that, if matched, 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 (see 'postconf -d output')\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"
+/* When the Postfix SMTP server wants to send an SMTP server
+/* response, how long the Postfix SMTP server will wait for an underlying
+/* network write operation to complete; and when the Postfix SMTP
+/* server Postfix wants to receive an SMTP client request, how long
+/* the Postfix SMTP server will wait for an underlying network read
+/* operation to complete.
+/* .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 - 3.6:
+/* .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 version 3.7 and later:
+/* .IP "\fBsmtpd_per_request_deadline (normal: no, overload: yes)\fR"
+/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout
+/* time limits, from a time limit per plaintext or TLS read or write
+/* call, to a combined time limit for receiving a complete SMTP request
+/* and for sending a complete SMTP response.
+/* .IP "\fBsmtpd_min_data_rate (500)\fR"
+/* The minimum plaintext data transfer rate in bytes/second for
+/* DATA and BDAT requests, when deadlines are enabled with
+/* smtpd_per_request_deadline.
+/* .IP "\fBheader_from_format (standard)\fR"
+/* The format of the Postfix-generated \fBFrom:\fR header.
+/* .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 an email address
+/* localpart, user name, or a .forward file name from its extension.
+/* .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 regexp:{{/^[^A-Z]/ Bogus}})\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>
+#include <hfrom_format.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_smtpd_sasl_mech_filter;
+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;
+
+int smtpd_proxy_opts;
+
+#ifdef USE_TLSPROXY
+char *var_tlsproxy_service;
+
+#endif
+
+char *var_smtpd_uproxy_proto;
+int var_smtpd_uproxy_tmout;
+bool var_relay_before_rcpt_checks;
+bool var_smtpd_req_deadline;
+int var_smtpd_min_data_rate;
+char *var_hfrom_format;
+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;
+
+ /*
+ * Parsed header_from_format setting.
+ */
+int smtpd_hfrom_format;
+
+ /*
+ * 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;
+
+ /*
+ * If deadlines are enabled, increase the time budget as message content
+ * arrives.
+ */
+ smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline,
+ var_smtpd_min_data_rate);
+
+ /*
+ * 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);
+ }
+
+ /*
+ * If deadlines are enabled, increase the time budget as message content
+ * arrives.
+ */
+ smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline,
+ var_smtpd_min_data_rate);
+
+ /*
+ * 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.
+ *
+ * With deadlines enabled, do not increase the time budget while receiving a
+ * command, because that would give an attacker too much time.
+ */
+ vstream_control(state->client, VSTREAM_CTL_EXCEPT, VSTREAM_CTL_END);
+ while ((status = vstream_setjmp(state->client)) == SMTP_ERR_NONE)
+ /* void */ ;
+ smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline, 0);
+ 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;
+ smtp_stream_setup(state->client, var_smtpd_tmout,
+ var_smtpd_req_deadline, 0);
+ 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))) {
+ VSTRING *escape_buf = vstring_alloc(100);
+
+ msg_warn("non-SMTP command from %s: %.100s",
+ state->namaddr,
+ vstring_str(escape(escape_buf,
+ vstring_str(state->buffer),
+ VSTRING_LEN(state->buffer))));
+ smtpd_chat_reply(state, "221 2.7.0 Error: I can break rules, too. Goodbye.");
+ vstring_free(escape_buf);
+ 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, "500 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 denylist/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();
+
+ /*
+ * header_from_format support, for postmaster notifications.
+ */
+ smtpd_hfrom_format = hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format);
+}
+
+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,
+ VAR_SMTPD_MIN_DATA_RATE, DEF_SMTPD_MIN_DATA_RATE, &var_smtpd_min_data_rate, 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_RELAY_BEFORE_RCPT_CHECKS, DEF_RELAY_BEFORE_RCPT_CHECKS, &var_relay_before_rcpt_checks,
+ VAR_SMTPD_REQ_DEADLINE, DEF_SMTPD_REQ_DEADLINE, &var_smtpd_req_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_SASL_MECH_FILTER, DEF_SMTPD_SASL_MECH_FILTER, &var_smtpd_sasl_mech_filter, 0, 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_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 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..0bb5bbb
--- /dev/null
+++ b/src/smtpd/smtpd.h
@@ -0,0 +1,446 @@
+/*++
+/* 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;
+
+ /*
+ * header_from_format support.
+ */
+extern int smtpd_hfrom_format;
+
+/* 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..daed26c
--- /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.3.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..4b7f6e9
--- /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.3.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..10b5f01
--- /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.3.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..3bf610c
--- /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.3.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..278e536
--- /dev/null
+++ b/src/smtpd/smtpd_chat.c
@@ -0,0 +1,352 @@
+/*++
+/* 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>
+#include <hfrom_format.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;
+ }
+ if (smtpd_hfrom_format == HFROM_FORMAT_CODE_STD) {
+ post_mail_fprintf(notice, "From: Mail Delivery System <%s>",
+ mail_addr_mail_daemon());
+ post_mail_fprintf(notice, "To: Postmaster <%s>", var_error_rcpt);
+ } else {
+ 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..996a19e
--- /dev/null
+++ b/src/smtpd/smtpd_check.c
@@ -0,0 +1,6445 @@
+/*++
+/* 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 allow-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];
+
+ if (warn_compat_break_smtpd_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate "
+ "fingerprints");
+
+ 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(dns_get_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;
+ const 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(dns_get_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;
+ const 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(dns_get_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;
+ if (warn_compat_break_smtpd_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPD_TLS_FPT_DGST "=md5 to compute "
+ "certificate fingerprints");
+ break;
+ case SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT:
+ match_this = state->tls_context->peer_pkey_fprint;
+ if (warn_compat_break_smtpd_tls_fpt_dgst)
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPD_TLS_FPT_DGST "=md5 to compute "
+ "certificate fingerprints");
+ 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 DNS deny 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 allowlist 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 DNS deny 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 DNS deny 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 allowlist 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 DNS deny 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 int warned = 0;
+ 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);
+
+ /*
+ * XXX: Too noisy to warn for each policy lookup, especially because we
+ * don't even know whether the policy server will use the fingerprint. So
+ * warn at most once per process, though on only lightly loaded servers,
+ * it might come close to one warning per inbound message.
+ */
+ if (!warned
+ && warn_compat_break_smtpd_tls_fpt_dgst
+ && state->tls_context
+ && state->tls_context->peer_cert_fprint
+ && *state->tls_context->peer_cert_fprint) {
+ warned = 1;
+ msg_info("using backwards-compatible default setting "
+ VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate "
+ "fingerprints");
+ }
+#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_allowlist - disallow allowlisting */
+
+static void forbid_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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_allowlist(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;
+ int rcpt_index;
+ int relay_index;
+
+ /*
+ * 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_destination
+ * at the end would have blocked the request.
+ *
+ * If warn_compat_break_relay_restrictions is true, always evaluate
+ * smtpd_relay_restrictions last (rcpt_index == 0). The backwards
+ * compatibility warning says that it avoids blocking a recipient (with
+ * "Relay access denied"); that is not useful information when moments
+ * later, smtpd_recipient_restrictions blocks the recipient anyway (with
+ * 'Relay access denied' or some other cause).
+ */
+ SMTPD_CHECK_RESET();
+ rcpt_index = (var_relay_before_rcpt_checks
+ && !warn_compat_break_relay_restrictions);
+ relay_index = !rcpt_index;
+
+ restrctions[rcpt_index] = rcpt_restrctions;
+ restrctions[relay_index] = 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 == relay_index && 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;
+ }
+ if (status == SMTPD_CHECK_REJECT
+ && warn_compat_relay_before_rcpt_checks && n == 0)
+ msg_info("using backwards-compatible default setting "
+ VAR_RELAY_BEFORE_RCPT_CHECKS "=no to reject "
+ "recipient \"%s\" from client \"%s\"",
+ state->recipient, state->namaddr);
+
+ /*
+ * 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_smtpd_uproxy_proto = "";
+int var_smtpd_uproxy_tmout = 0;
+
+#ifdef USE_TLS
+char *var_relay_ccerts = "";
+
+#endif
+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_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_smtpd_null_key;
+char *var_smtpd_snd_auth_maps;
+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;
+
+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,
+ /* XXX No static initialization with "", because owned by a library. */
+ VAR_MYNETWORKS, "", &var_mynetworks,
+ VAR_RELAY_DOMAINS, "", &var_relay_domains,
+ 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);
+}
+
+ /*
+ * Boolean parameters.
+ */
+bool var_relay_before_rcpt_checks;
+
+ /*
+ * 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..efeba5b
--- /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.3.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.3.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.3.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.3.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.3.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.3.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.3.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..804fde1
--- /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.3.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.3.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.3.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.3.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.3.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.3.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..808f562
--- /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.3.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..8051f01
--- /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.3.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.3.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.3.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.3.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.3.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.3.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.3.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..e22f9e3
--- /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.3.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.3.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.3.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.3.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.3.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.3.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..7fb9242
--- /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.3.0/28
+#
+# MX backup
+#
+mydestination wzv.porcupine.org,localhost.porcupine.org
+inet_interfaces 168.100.3.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.3.5
+rcpt wietse@backup.porcupine.org
+permit_mx_backup_networks 168.100.3.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..8f4a0f2
--- /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.3.0/28
+OK
+>>> #
+>>> # MX backup
+>>> #
+>>> mydestination wzv.porcupine.org,localhost.porcupine.org
+OK
+>>> inet_interfaces 168.100.3.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.3.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.3.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..cf174e8
--- /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.3.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..168676d
--- /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.3.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..ce1710f
--- /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.3.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.3.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.3.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.3.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..db25474
--- /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.3.0/28
+mydestination porcupine.org
+relay_domains porcupine.org
+helo foobar
+
+#
+# DNSWL (by IP address)
+#
+
+# Allowlist overrides reject.
+client_restrictions permit_dnswl_client,wild.porcupine.org,reject
+client spike.porcupine.org 168.100.3.2
+
+# Allowlist does not fire - reject.
+client_restrictions permit_dnswl_client,porcupine.org,reject
+client spike.porcupine.org 168.100.3.2
+
+# Allowlist 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)
+#
+
+# Allowlist overrides reject.
+client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject
+# Non-allowlisted client name - reject.
+client spike.porcupine.org 168.100.3.2
+# Allowlisted client name - accept.
+client example.tld 168.100.3.2
+
+# Allowlist does not override reject_unauth_destination.
+client_restrictions permit
+recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination
+# Non-allowlisted client name.
+client spike.porcupine.org 168.100.3.2
+# Unauthorized destination - reject.
+rcpt rname@rdomain
+# Authorized destination - accept.
+rcpt wietse@porcupine.org
+# Allowlisted client name.
+client example.tld 168.100.3.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..dacda6c
--- /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.3.0/28
+OK
+>>> mydestination porcupine.org
+OK
+>>> relay_domains porcupine.org
+OK
+>>> helo foobar
+OK
+>>>
+>>> #
+>>> # DNSWL (by IP address)
+>>> #
+>>>
+>>> # Allowlist overrides reject.
+>>> client_restrictions permit_dnswl_client,wild.porcupine.org,reject
+OK
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>>
+>>> # Allowlist does not fire - reject.
+>>> client_restrictions permit_dnswl_client,porcupine.org,reject
+OK
+>>> client spike.porcupine.org 168.100.3.2
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <spike.porcupine.org[168.100.3.2]>: Client host rejected: Access denied; proto=SMTP helo=<foobar>
+554 5.7.1 <spike.porcupine.org[168.100.3.2]>: Client host rejected: Access denied
+>>>
+>>> # Allowlist 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.3.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)
+>>> #
+>>>
+>>> # Allowlist overrides reject.
+>>> client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject
+OK
+>>> # Non-allowlisted client name - reject.
+>>> client spike.porcupine.org 168.100.3.2
+./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 <spike.porcupine.org[168.100.3.2]>: Client host rejected: Access denied; proto=SMTP helo=<foobar>
+554 5.7.1 <spike.porcupine.org[168.100.3.2]>: Client host rejected: Access denied
+>>> # Allowlisted client name - accept.
+>>> client example.tld 168.100.3.2
+OK
+>>>
+>>> # Allowlist does not override reject_unauth_destination.
+>>> client_restrictions permit
+OK
+>>> recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination
+OK
+>>> # Non-allowlisted client name.
+>>> client spike.porcupine.org 168.100.3.2
+OK
+>>> # Unauthorized destination - reject.
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.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
+>>> # Allowlisted client name.
+>>> client example.tld 168.100.3.2
+OK
+>>> # Unauthorized destination - reject.
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.3.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.3.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.3.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..a7fb3e2
--- /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.3.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..d375522
--- /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.3.1/27
+OK
+>>> #
+>>> # Expect REJECT (server configuration error)
+>>> #
+>>> rcpt reject@dunno.domain
+./smtpd_check: warning: mynetworks: non-null host address bits in "168.100.3.1/27", perhaps you should use "168.100.3.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..8370404
--- /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.3.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.3.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.3.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.3.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.3.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..22c027e
--- /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.3.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.3.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.3.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.3.2
+OK
+>>> mail sname@example.tld
+OK
+>>> rcpt rname@rdomain
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.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.3.2] client_address=168.100.3.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.3.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.3.2]: 554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.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.3.2] client_address=168.100.3.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.3.2]: 554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.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.3.2] client_address=168.100.3.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..58eede3
--- /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.3.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.3.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.3.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.3.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.3.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..410c0c0
--- /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.3.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.3.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.3.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.3.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.3.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.3.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.3.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.3.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.3.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.3.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.3.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..3a5c1d4
--- /dev/null
+++ b/src/smtpd/smtpd_peer.c
@@ -0,0 +1,658 @@
+/*++
+/* 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 <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 const 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 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..2dc6aad
--- /dev/null
+++ b/src/smtpd/smtpd_sasl_glue.c
@@ -0,0 +1,396 @@
+/*++
+/* 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>
+#include <sasl_mech_filter.h>
+#include <string_list.h>
+
+/* XSASL library. */
+
+#include <xsasl.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_sasl_glue.h"
+#include "smtpd_chat.h"
+
+#ifdef USE_SASL_AUTH
+
+ /*
+ * SASL mechanism filter.
+ */
+static STRING_LIST *smtpd_sasl_mech_filter;
+
+/*
+ * 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");
+
+ /*
+ * Initialize the SASL mechanism filter.
+ */
+ smtpd_sasl_mech_filter = string_list_init(VAR_SMTPD_SASL_MECH_FILTER,
+ MATCH_FLAG_NONE,
+ var_smtpd_sasl_mech_filter);
+}
+
+/* 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;
+ const char *filtered_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");
+ filtered_mechanism_list =
+ sasl_mech_filter(smtpd_sasl_mech_filter, mechanism_list);
+ if (*filtered_mechanism_list == 0)
+ msg_fatal("%s discards all mechanisms in '%s'",
+ VAR_SMTPD_SASL_MECH_FILTER, mechanism_list);
+ state->sasl_mechanism_list = mystrdup(filtered_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..691b8c6
--- /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.3.2
+#
+# Check MX access
+#
+helo_restrictions check_helo_mx_access,inline:{168.100.3.2=reject}
+helo www.porcupine.org
+helo example.tld
+helo foo@postfix.org
+sender_restrictions check_sender_mx_access,inline:{168.100.3.2=reject}
+mail foo@www.porcupine.org
+mail example.tld
+mail foo@postfix.org
+recipient_restrictions check_recipient_mx_access,inline:{168.100.3.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.3.75=reject}
+helo www.porcupine.org
+helo example.tld
+helo foo@postfix.org
+sender_restrictions check_sender_ns_access,inline:{168.100.3.75=reject}
+mail foo@www.porcupine.org
+mail example.tld
+mail foo@postfix.org
+recipient_restrictions check_recipient_ns_access,inline:{168.100.3.75=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.3.2=reject}
+helo spike.porcupine.org
+helo www.porcupine.org
+client_restrictions check_client_a_access,inline:{168.100.3.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.3.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.3.2=reject}
+mail foo@spike.porcupine.org
+mail foo@www.porcupine.org
+recipient_restrictions check_recipient_a_access,inline:{168.100.3.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..b396a57
--- /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.3.2
+OK
+>>> #
+>>> # Check MX access
+>>> #
+>>> helo_restrictions check_helo_mx_access,inline:{168.100.3.2=reject}
+OK
+>>> helo www.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.3.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.3.2=reject}
+OK
+>>> mail foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.3.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.3.2=reject}
+OK
+>>> rcpt foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.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.3.75=reject}
+OK
+>>> helo www.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.3.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.3.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.3.75=reject}
+OK
+>>> mail foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.3.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.3.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.3.75=reject}
+OK
+>>> rcpt foo@www.porcupine.org
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.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.3.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.3.2=reject}
+OK
+>>> helo spike.porcupine.org
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.3.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.3.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.3.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.3.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.3.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);
+}