diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:46:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:46:30 +0000 |
commit | b5896ba9f6047e7031e2bdee0622d543e11a6734 (patch) | |
tree | fd7b460593a2fee1be579bec5697e6d887ea3421 /src/smtpd | |
parent | Initial commit. (diff) | |
download | postfix-2f07919848e7bd4a084699d26e8821e3a00696d9.tar.xz postfix-2f07919848e7bd4a084699d26e8821e3a00696d9.zip |
Adding upstream version 3.4.23.upstream/3.4.23upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
59 files changed, 21757 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..14540d3 --- /dev/null +++ b/src/smtpd/Makefile.in @@ -0,0 +1,669 @@ +SHELL = /bin/sh +SRCS = smtpd.c smtpd_token.c smtpd_check.c smtpd_chat.c smtpd_state.c \ + smtpd_peer.c smtpd_sasl_proto.c smtpd_sasl_glue.c smtpd_proxy.c \ + smtpd_xforward.c smtpd_dsn_fix.c smtpd_milter.c smtpd_resolve.c \ + smtpd_expand.c smtpd_haproxy.c +OBJS = smtpd.o smtpd_token.o smtpd_check.o smtpd_chat.o smtpd_state.o \ + smtpd_peer.o smtpd_sasl_proto.o smtpd_sasl_glue.o smtpd_proxy.o \ + smtpd_xforward.o smtpd_dsn_fix.o smtpd_milter.o smtpd_resolve.o \ + smtpd_expand.o smtpd_haproxy.o +HDRS = smtpd_token.h smtpd_check.h smtpd_chat.h smtpd_sasl_proto.h \ + smtpd_sasl_glue.h smtpd_proxy.h smtpd_dsn_fix.h smtpd_milter.h \ + smtpd_resolve.h smtpd_expand.h +TESTSRC = smtpd_token_test.c +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= smtpd_token smtpd_check +PROG = smtpd +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/libxsasl.a \ + ../../lib/libmilter.a \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +SMTPD_CHECK_OBJ = smtpd_state.o smtpd_peer.o smtpd_xforward.o smtpd_dsn_fix.o \ + smtpd_resolve.o smtpd_expand.o smtpd_proxy.o smtpd_haproxy.o + +smtpd_token: smtpd_token.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +smtpd_check: smtpd_check.o smtpd_check.c $(SMTPD_CHECK_OBJ) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ smtpd_check.c $(SMTPD_CHECK_OBJ) \ + $(LIBS) $(SYSLIBS) + mv junk $@.o + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk *.db *.out *.tmp + rm -rf printfck + +tidy: clean + +broken-tests: smtpd_check_test smtpd_check_test2 + +tests: smtpd_acl_test smtpd_addr_valid_test smtpd_exp_test \ + smtpd_token_test smtpd_check_test4 smtpd_check_dsn_test \ + smtpd_check_backup_test smtpd_dnswl_test smtpd_error_test \ + smtpd_server_test smtpd_nullmx_test smtpd_dns_filter_test + +root_tests: + +# This requires that the DNS server can query porcupine.org. + +smtpd_check_test: smtpd_check smtpd_check.in smtpd_check.ref smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check.in >smtpd_check.tmp 2>&1 + diff smtpd_check.ref smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +# This requires that the DNS server can query porcupine.org. + +smtpd_check_test2: smtpd_check smtpd_check.in2 smtpd_check.ref2 smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check.in2 >smtpd_check.tmp 2>&1 + diff smtpd_check.ref2 smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +smtpd_check_test4: smtpd_check smtpd_check.in4 smtpd_check.ref4 smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check.in4 >smtpd_check.tmp 2>&1 + diff smtpd_check.ref4 smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +smtpd_acl_test: smtpd_check smtpd_acl.in smtpd_acl.ref smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_acl.in >smtpd_check.tmp 2>&1 + diff smtpd_acl.ref smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +smtpd_addr_valid_test: smtpd_check smtpd_addr_valid.in smtpd_addr_valid.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_addr_valid.in >smtpd_check.tmp 2>&1 + diff smtpd_addr_valid.ref smtpd_check.tmp + rm -f smtpd_check.tmp + +# This requires that the DNS server can query porcupine.org. + +ADDRINFO_FIX = sed 's/No address associated with hostname/hostname nor servname provided, or not known/' + +smtpd_exp_test: smtpd_check smtpd_exp.in smtpd_exp.ref + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_exp.in >smtpd_exp.tmp 2>&1 + diff smtpd_exp.ref smtpd_exp.tmp + rm -f smtpd_exp.tmp smtpd_check_access.* + +smtpd_server_test: smtpd_check smtpd_server.in smtpd_server.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_server.in >smtpd_server.tmp 2>&1 + $(ADDRINFO_FIX) smtpd_server.tmp | diff smtpd_server.ref - + rm -f smtpd_server.tmp smtpd_check_access.* + +smtpd_nullmx_test: smtpd_check smtpd_nullmx.in smtpd_nullmx.ref + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_nullmx.in >smtpd_nullmx.tmp 2>&1 + $(ADDRINFO_FIX) smtpd_nullmx.tmp | diff smtpd_nullmx.ref - + rm -f smtpd_nullmx.tmp smtpd_check_access.* + +smtpd_dns_filter_test: smtpd_check smtpd_dns_filter.in smtpd_dns_filter.ref \ + ../dns/no-mx.reg ../dns/no-a.reg ../dns/error.reg + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_dns_filter.in 2>&1 | \ + sed 's/\. [0-9]* IN/. TTL IN/' >smtpd_dns_filter.tmp + diff smtpd_dns_filter.ref smtpd_dns_filter.tmp + rm -f smtpd_dns_filter.tmp + +smtpd_check_dsn_test: smtpd_check smtpd_check_dsn.in smtpd_check_dsn.ref smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check_dsn.in >smtpd_check.tmp 2>&1 + diff smtpd_check_dsn.ref smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +# This requires that 168,100.189.7 is a local or virtual interface. + +smtpd_check_backup_test: smtpd_check smtpd_check_backup.in smtpd_check_backup.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_check_backup.in >smtpd_check.tmp 2>&1 + diff smtpd_check_backup.ref smtpd_check.tmp + rm -f smtpd_check.tmp + +smtpd_token_test: smtpd_token smtpd_token.in smtpd_token.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_token <smtpd_token.in >smtpd_token.tmp 2>&1 + diff smtpd_token.ref smtpd_token.tmp + rm -f smtpd_token.tmp + +# This requires that the DNS server can query porcupine.org and rfc-ignorant.org + +smtpd_dnswl_test: smtpd_check smtpd_dnswl.in smtpd_dnswl.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_dnswl.in >smtpd_dnswl.tmp 2>&1 + diff smtpd_dnswl.ref smtpd_dnswl.tmp + rm -f smtpd_dnswl.tmp + +smtpd_error_test: smtpd_check smtpd_error.in smtpd_error.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_error.in >smtpd_check.tmp 2>&1 + diff smtpd_error.ref smtpd_check.tmp + rm -f smtpd_check.tmp + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +smtpd.o: ../../include/anvil_clnt.h +smtpd.o: ../../include/argv.h +smtpd.o: ../../include/attr.h +smtpd.o: ../../include/attr_clnt.h +smtpd.o: ../../include/check_arg.h +smtpd.o: ../../include/cleanup_user.h +smtpd.o: ../../include/debug_peer.h +smtpd.o: ../../include/dict.h +smtpd.o: ../../include/dns.h +smtpd.o: ../../include/dsn_mask.h +smtpd.o: ../../include/ehlo_mask.h +smtpd.o: ../../include/events.h +smtpd.o: ../../include/flush_clnt.h +smtpd.o: ../../include/htable.h +smtpd.o: ../../include/inet_proto.h +smtpd.o: ../../include/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/nvtable.h +smtpd.o: ../../include/off_cvt.h +smtpd.o: ../../include/quote_822_local.h +smtpd.o: ../../include/quote_flags.h +smtpd.o: ../../include/rec_type.h +smtpd.o: ../../include/recipient_list.h +smtpd.o: ../../include/record.h +smtpd.o: ../../include/resolve_clnt.h +smtpd.o: ../../include/smtp_stream.h +smtpd.o: ../../include/smtputf8.h +smtpd.o: ../../include/sock_addr.h +smtpd.o: ../../include/split_at.h +smtpd.o: ../../include/string_list.h +smtpd.o: ../../include/stringops.h +smtpd.o: ../../include/sys_defs.h +smtpd.o: ../../include/tls.h +smtpd.o: ../../include/tls_proxy.h +smtpd.o: ../../include/tok822.h +smtpd.o: ../../include/uxtext.h +smtpd.o: ../../include/valid_hostname.h +smtpd.o: ../../include/valid_mailhost_addr.h +smtpd.o: ../../include/vbuf.h +smtpd.o: ../../include/verify_sender_addr.h +smtpd.o: ../../include/verp_sender.h +smtpd.o: ../../include/vstream.h +smtpd.o: ../../include/vstring.h +smtpd.o: ../../include/vstring_vstream.h +smtpd.o: ../../include/watchdog.h +smtpd.o: ../../include/xtext.h +smtpd.o: smtpd.c +smtpd.o: smtpd.h +smtpd.o: smtpd_chat.h +smtpd.o: smtpd_check.h +smtpd.o: smtpd_expand.h +smtpd.o: smtpd_milter.h +smtpd.o: smtpd_proxy.h +smtpd.o: smtpd_sasl_glue.h +smtpd.o: smtpd_sasl_proto.h +smtpd.o: smtpd_token.h +smtpd_chat.o: ../../include/argv.h +smtpd_chat.o: ../../include/attr.h +smtpd_chat.o: ../../include/check_arg.h +smtpd_chat.o: ../../include/cleanup_user.h +smtpd_chat.o: ../../include/dict.h +smtpd_chat.o: ../../include/dns.h +smtpd_chat.o: ../../include/htable.h +smtpd_chat.o: ../../include/int_filt.h +smtpd_chat.o: ../../include/iostuff.h +smtpd_chat.o: ../../include/line_wrap.h +smtpd_chat.o: ../../include/mac_expand.h +smtpd_chat.o: ../../include/mac_parse.h +smtpd_chat.o: ../../include/mail_addr.h +smtpd_chat.o: ../../include/mail_error.h +smtpd_chat.o: ../../include/mail_params.h +smtpd_chat.o: ../../include/mail_proto.h +smtpd_chat.o: ../../include/mail_stream.h +smtpd_chat.o: ../../include/maps.h +smtpd_chat.o: ../../include/milter.h +smtpd_chat.o: ../../include/msg.h +smtpd_chat.o: ../../include/myaddrinfo.h +smtpd_chat.o: ../../include/myflock.h +smtpd_chat.o: ../../include/mymalloc.h +smtpd_chat.o: ../../include/name_code.h +smtpd_chat.o: ../../include/name_mask.h +smtpd_chat.o: ../../include/nvtable.h +smtpd_chat.o: ../../include/post_mail.h +smtpd_chat.o: ../../include/rec_type.h +smtpd_chat.o: ../../include/record.h +smtpd_chat.o: ../../include/smtp_reply_footer.h +smtpd_chat.o: ../../include/smtp_stream.h +smtpd_chat.o: ../../include/smtputf8.h +smtpd_chat.o: ../../include/sock_addr.h +smtpd_chat.o: ../../include/stringops.h +smtpd_chat.o: ../../include/sys_defs.h +smtpd_chat.o: ../../include/tls.h +smtpd_chat.o: ../../include/vbuf.h +smtpd_chat.o: ../../include/vstream.h +smtpd_chat.o: ../../include/vstring.h +smtpd_chat.o: smtpd.h +smtpd_chat.o: smtpd_chat.c +smtpd_chat.o: smtpd_chat.h +smtpd_chat.o: smtpd_expand.h +smtpd_check.o: ../../include/argv.h +smtpd_check.o: ../../include/attr.h +smtpd_check.o: ../../include/attr_clnt.h +smtpd_check.o: ../../include/attr_override.h +smtpd_check.o: ../../include/check_arg.h +smtpd_check.o: ../../include/cleanup_user.h +smtpd_check.o: ../../include/conv_time.h +smtpd_check.o: ../../include/ctable.h +smtpd_check.o: ../../include/deliver_request.h +smtpd_check.o: ../../include/dict.h +smtpd_check.o: ../../include/dns.h +smtpd_check.o: ../../include/domain_list.h +smtpd_check.o: ../../include/dsn.h +smtpd_check.o: ../../include/dsn_util.h +smtpd_check.o: ../../include/fsspace.h +smtpd_check.o: ../../include/htable.h +smtpd_check.o: ../../include/inet_addr_list.h +smtpd_check.o: ../../include/inet_proto.h +smtpd_check.o: ../../include/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/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/mail_params.h +smtpd_haproxy.o: ../../include/mail_stream.h +smtpd_haproxy.o: ../../include/milter.h +smtpd_haproxy.o: ../../include/msg.h +smtpd_haproxy.o: ../../include/myaddrinfo.h +smtpd_haproxy.o: ../../include/mymalloc.h +smtpd_haproxy.o: ../../include/name_code.h +smtpd_haproxy.o: ../../include/name_mask.h +smtpd_haproxy.o: ../../include/nvtable.h +smtpd_haproxy.o: ../../include/smtp_stream.h +smtpd_haproxy.o: ../../include/sock_addr.h +smtpd_haproxy.o: ../../include/stringops.h +smtpd_haproxy.o: ../../include/sys_defs.h +smtpd_haproxy.o: ../../include/tls.h +smtpd_haproxy.o: ../../include/valid_hostname.h +smtpd_haproxy.o: ../../include/valid_mailhost_addr.h +smtpd_haproxy.o: ../../include/vbuf.h +smtpd_haproxy.o: ../../include/vstream.h +smtpd_haproxy.o: ../../include/vstring.h +smtpd_haproxy.o: smtpd.h +smtpd_haproxy.o: smtpd_haproxy.c +smtpd_milter.o: ../../include/argv.h +smtpd_milter.o: ../../include/attr.h +smtpd_milter.o: ../../include/check_arg.h +smtpd_milter.o: ../../include/dns.h +smtpd_milter.o: ../../include/htable.h +smtpd_milter.o: ../../include/mail_params.h +smtpd_milter.o: ../../include/mail_stream.h +smtpd_milter.o: ../../include/milter.h +smtpd_milter.o: ../../include/myaddrinfo.h +smtpd_milter.o: ../../include/mymalloc.h +smtpd_milter.o: ../../include/name_code.h +smtpd_milter.o: ../../include/name_mask.h +smtpd_milter.o: ../../include/nvtable.h +smtpd_milter.o: ../../include/quote_821_local.h +smtpd_milter.o: ../../include/quote_flags.h +smtpd_milter.o: ../../include/resolve_clnt.h +smtpd_milter.o: ../../include/sock_addr.h +smtpd_milter.o: ../../include/split_at.h +smtpd_milter.o: ../../include/stringops.h +smtpd_milter.o: ../../include/sys_defs.h +smtpd_milter.o: ../../include/tls.h +smtpd_milter.o: ../../include/vbuf.h +smtpd_milter.o: ../../include/vstream.h +smtpd_milter.o: ../../include/vstring.h +smtpd_milter.o: smtpd.h +smtpd_milter.o: smtpd_milter.c +smtpd_milter.o: smtpd_milter.h +smtpd_milter.o: smtpd_resolve.h +smtpd_milter.o: smtpd_sasl_glue.h +smtpd_peer.o: ../../include/argv.h +smtpd_peer.o: ../../include/attr.h +smtpd_peer.o: ../../include/check_arg.h +smtpd_peer.o: ../../include/dns.h +smtpd_peer.o: ../../include/haproxy_srvr.h +smtpd_peer.o: ../../include/htable.h +smtpd_peer.o: ../../include/inet_proto.h +smtpd_peer.o: ../../include/iostuff.h +smtpd_peer.o: ../../include/mail_params.h +smtpd_peer.o: ../../include/mail_proto.h +smtpd_peer.o: ../../include/mail_stream.h +smtpd_peer.o: ../../include/milter.h +smtpd_peer.o: ../../include/msg.h +smtpd_peer.o: ../../include/myaddrinfo.h +smtpd_peer.o: ../../include/mymalloc.h +smtpd_peer.o: ../../include/name_code.h +smtpd_peer.o: ../../include/name_mask.h +smtpd_peer.o: ../../include/nvtable.h +smtpd_peer.o: ../../include/sock_addr.h +smtpd_peer.o: ../../include/split_at.h +smtpd_peer.o: ../../include/stringops.h +smtpd_peer.o: ../../include/sys_defs.h +smtpd_peer.o: ../../include/tls.h +smtpd_peer.o: ../../include/valid_hostname.h +smtpd_peer.o: ../../include/valid_mailhost_addr.h +smtpd_peer.o: ../../include/vbuf.h +smtpd_peer.o: ../../include/vstream.h +smtpd_peer.o: ../../include/vstring.h +smtpd_peer.o: smtpd.h +smtpd_peer.o: smtpd_peer.c +smtpd_proxy.o: ../../include/argv.h +smtpd_proxy.o: ../../include/attr.h +smtpd_proxy.o: ../../include/check_arg.h +smtpd_proxy.o: ../../include/cleanup_user.h +smtpd_proxy.o: ../../include/connect.h +smtpd_proxy.o: ../../include/dns.h +smtpd_proxy.o: ../../include/htable.h +smtpd_proxy.o: ../../include/iostuff.h +smtpd_proxy.o: ../../include/mail_error.h +smtpd_proxy.o: ../../include/mail_params.h +smtpd_proxy.o: ../../include/mail_proto.h +smtpd_proxy.o: ../../include/mail_queue.h +smtpd_proxy.o: ../../include/mail_stream.h +smtpd_proxy.o: ../../include/milter.h +smtpd_proxy.o: ../../include/msg.h +smtpd_proxy.o: ../../include/myaddrinfo.h +smtpd_proxy.o: ../../include/mymalloc.h +smtpd_proxy.o: ../../include/name_code.h +smtpd_proxy.o: ../../include/name_mask.h +smtpd_proxy.o: ../../include/nvtable.h +smtpd_proxy.o: ../../include/rec_type.h +smtpd_proxy.o: ../../include/record.h +smtpd_proxy.o: ../../include/smtp_stream.h +smtpd_proxy.o: ../../include/sock_addr.h +smtpd_proxy.o: ../../include/stringops.h +smtpd_proxy.o: ../../include/sys_defs.h +smtpd_proxy.o: ../../include/tls.h +smtpd_proxy.o: ../../include/vbuf.h +smtpd_proxy.o: ../../include/vstream.h +smtpd_proxy.o: ../../include/vstring.h +smtpd_proxy.o: ../../include/xtext.h +smtpd_proxy.o: smtpd.h +smtpd_proxy.o: smtpd_proxy.c +smtpd_proxy.o: smtpd_proxy.h +smtpd_resolve.o: ../../include/attr.h +smtpd_resolve.o: ../../include/check_arg.h +smtpd_resolve.o: ../../include/ctable.h +smtpd_resolve.o: ../../include/htable.h +smtpd_resolve.o: ../../include/iostuff.h +smtpd_resolve.o: ../../include/mail_proto.h +smtpd_resolve.o: ../../include/msg.h +smtpd_resolve.o: ../../include/mymalloc.h +smtpd_resolve.o: ../../include/nvtable.h +smtpd_resolve.o: ../../include/resolve_clnt.h +smtpd_resolve.o: ../../include/rewrite_clnt.h +smtpd_resolve.o: ../../include/split_at.h +smtpd_resolve.o: ../../include/stringops.h +smtpd_resolve.o: ../../include/sys_defs.h +smtpd_resolve.o: ../../include/vbuf.h +smtpd_resolve.o: ../../include/vstream.h +smtpd_resolve.o: ../../include/vstring.h +smtpd_resolve.o: smtpd_resolve.c +smtpd_resolve.o: smtpd_resolve.h +smtpd_sasl_glue.o: ../../include/argv.h +smtpd_sasl_glue.o: ../../include/attr.h +smtpd_sasl_glue.o: ../../include/check_arg.h +smtpd_sasl_glue.o: ../../include/dns.h +smtpd_sasl_glue.o: ../../include/htable.h +smtpd_sasl_glue.o: ../../include/mail_params.h +smtpd_sasl_glue.o: ../../include/mail_stream.h +smtpd_sasl_glue.o: ../../include/milter.h +smtpd_sasl_glue.o: ../../include/msg.h +smtpd_sasl_glue.o: ../../include/myaddrinfo.h +smtpd_sasl_glue.o: ../../include/mymalloc.h +smtpd_sasl_glue.o: ../../include/name_code.h +smtpd_sasl_glue.o: ../../include/name_mask.h +smtpd_sasl_glue.o: ../../include/nvtable.h +smtpd_sasl_glue.o: ../../include/sock_addr.h +smtpd_sasl_glue.o: ../../include/stringops.h +smtpd_sasl_glue.o: ../../include/sys_defs.h +smtpd_sasl_glue.o: ../../include/tls.h +smtpd_sasl_glue.o: ../../include/vbuf.h +smtpd_sasl_glue.o: ../../include/vstream.h +smtpd_sasl_glue.o: ../../include/vstring.h +smtpd_sasl_glue.o: ../../include/xsasl.h +smtpd_sasl_glue.o: smtpd.h +smtpd_sasl_glue.o: smtpd_chat.h +smtpd_sasl_glue.o: smtpd_sasl_glue.c +smtpd_sasl_glue.o: smtpd_sasl_glue.h +smtpd_sasl_proto.o: ../../include/argv.h +smtpd_sasl_proto.o: ../../include/attr.h +smtpd_sasl_proto.o: ../../include/check_arg.h +smtpd_sasl_proto.o: ../../include/dns.h +smtpd_sasl_proto.o: ../../include/ehlo_mask.h +smtpd_sasl_proto.o: ../../include/htable.h +smtpd_sasl_proto.o: ../../include/iostuff.h +smtpd_sasl_proto.o: ../../include/mail_error.h +smtpd_sasl_proto.o: ../../include/mail_params.h +smtpd_sasl_proto.o: ../../include/mail_proto.h +smtpd_sasl_proto.o: ../../include/mail_stream.h +smtpd_sasl_proto.o: ../../include/milter.h +smtpd_sasl_proto.o: ../../include/msg.h +smtpd_sasl_proto.o: ../../include/myaddrinfo.h +smtpd_sasl_proto.o: ../../include/mymalloc.h +smtpd_sasl_proto.o: ../../include/name_code.h +smtpd_sasl_proto.o: ../../include/name_mask.h +smtpd_sasl_proto.o: ../../include/nvtable.h +smtpd_sasl_proto.o: ../../include/sock_addr.h +smtpd_sasl_proto.o: ../../include/stringops.h +smtpd_sasl_proto.o: ../../include/sys_defs.h +smtpd_sasl_proto.o: ../../include/tls.h +smtpd_sasl_proto.o: ../../include/vbuf.h +smtpd_sasl_proto.o: ../../include/vstream.h +smtpd_sasl_proto.o: ../../include/vstring.h +smtpd_sasl_proto.o: smtpd.h +smtpd_sasl_proto.o: smtpd_chat.h +smtpd_sasl_proto.o: smtpd_sasl_glue.h +smtpd_sasl_proto.o: smtpd_sasl_proto.c +smtpd_sasl_proto.o: smtpd_sasl_proto.h +smtpd_sasl_proto.o: smtpd_token.h +smtpd_state.o: ../../include/argv.h +smtpd_state.o: ../../include/attr.h +smtpd_state.o: ../../include/check_arg.h +smtpd_state.o: ../../include/cleanup_user.h +smtpd_state.o: ../../include/dns.h +smtpd_state.o: ../../include/events.h +smtpd_state.o: ../../include/htable.h +smtpd_state.o: ../../include/iostuff.h +smtpd_state.o: ../../include/mail_error.h +smtpd_state.o: ../../include/mail_params.h +smtpd_state.o: ../../include/mail_proto.h +smtpd_state.o: ../../include/mail_stream.h +smtpd_state.o: ../../include/milter.h +smtpd_state.o: ../../include/msg.h +smtpd_state.o: ../../include/myaddrinfo.h +smtpd_state.o: ../../include/mymalloc.h +smtpd_state.o: ../../include/name_code.h +smtpd_state.o: ../../include/name_mask.h +smtpd_state.o: ../../include/nvtable.h +smtpd_state.o: ../../include/sock_addr.h +smtpd_state.o: ../../include/sys_defs.h +smtpd_state.o: ../../include/tls.h +smtpd_state.o: ../../include/vbuf.h +smtpd_state.o: ../../include/vstream.h +smtpd_state.o: ../../include/vstring.h +smtpd_state.o: smtpd.h +smtpd_state.o: smtpd_chat.h +smtpd_state.o: smtpd_sasl_glue.h +smtpd_state.o: smtpd_state.c +smtpd_token.o: ../../include/check_arg.h +smtpd_token.o: ../../include/mvect.h +smtpd_token.o: ../../include/mymalloc.h +smtpd_token.o: ../../include/sys_defs.h +smtpd_token.o: ../../include/vbuf.h +smtpd_token.o: ../../include/vstring.h +smtpd_token.o: smtpd_token.c +smtpd_token.o: smtpd_token.h +smtpd_xforward.o: ../../include/argv.h +smtpd_xforward.o: ../../include/attr.h +smtpd_xforward.o: ../../include/check_arg.h +smtpd_xforward.o: ../../include/dns.h +smtpd_xforward.o: ../../include/htable.h +smtpd_xforward.o: ../../include/iostuff.h +smtpd_xforward.o: ../../include/mail_proto.h +smtpd_xforward.o: ../../include/mail_stream.h +smtpd_xforward.o: ../../include/milter.h +smtpd_xforward.o: ../../include/msg.h +smtpd_xforward.o: ../../include/myaddrinfo.h +smtpd_xforward.o: ../../include/mymalloc.h +smtpd_xforward.o: ../../include/name_code.h +smtpd_xforward.o: ../../include/name_mask.h +smtpd_xforward.o: ../../include/nvtable.h +smtpd_xforward.o: ../../include/sock_addr.h +smtpd_xforward.o: ../../include/sys_defs.h +smtpd_xforward.o: ../../include/tls.h +smtpd_xforward.o: ../../include/vbuf.h +smtpd_xforward.o: ../../include/vstream.h +smtpd_xforward.o: ../../include/vstring.h +smtpd_xforward.o: smtpd.h +smtpd_xforward.o: smtpd_xforward.c diff --git a/src/smtpd/smtpd.c b/src/smtpd/smtpd.c new file mode 100644 index 0000000..bb369d2 --- /dev/null +++ b/src/smtpd/smtpd.c @@ -0,0 +1,6517 @@ +/*++ +/* NAME +/* smtpd 8 +/* SUMMARY +/* Postfix SMTP server +/* SYNOPSIS +/* \fBsmtpd\fR [generic Postfix daemon options] +/* +/* \fBsendmail -bs\fR +/* DESCRIPTION +/* The SMTP server accepts network connection requests +/* and performs zero or more SMTP transactions per connection. +/* Each received message is piped through the \fBcleanup\fR(8) +/* daemon, and is placed into the \fBincoming\fR queue as one +/* single queue file. For this mode of operation, the program +/* expects to be run from the \fBmaster\fR(8) process manager. +/* +/* Alternatively, the SMTP server be can run in stand-alone +/* mode; this is traditionally obtained with "\fBsendmail +/* -bs\fR". When the SMTP server runs stand-alone with non +/* $\fBmail_owner\fR privileges, it receives mail even while +/* the mail system is not running, deposits messages directly +/* into the \fBmaildrop\fR queue, and disables the SMTP server's +/* access policies. As of Postfix version 2.3, the SMTP server +/* refuses to receive mail from the network when it runs with +/* non $\fBmail_owner\fR privileges. +/* +/* The SMTP server implements a variety of policies for connection +/* requests, and for parameters given to \fBHELO, ETRN, MAIL FROM, VRFY\fR +/* and \fBRCPT TO\fR commands. They are detailed below and in the +/* \fBmain.cf\fR configuration file. +/* SECURITY +/* .ad +/* .fi +/* The SMTP server is moderately security-sensitive. It talks to SMTP +/* clients and to DNS servers on the network. The SMTP server can be +/* run chrooted at fixed low privilege. +/* STANDARDS +/* RFC 821 (SMTP protocol) +/* RFC 1123 (Host requirements) +/* RFC 1652 (8bit-MIME transport) +/* RFC 1869 (SMTP service extensions) +/* RFC 1870 (Message size declaration) +/* RFC 1985 (ETRN command) +/* RFC 2034 (SMTP enhanced status codes) +/* RFC 2554 (AUTH command) +/* RFC 2821 (SMTP protocol) +/* RFC 2920 (SMTP pipelining) +/* RFC 3030 (CHUNKING without BINARYMIME) +/* RFC 3207 (STARTTLS command) +/* RFC 3461 (SMTP DSN extension) +/* RFC 3463 (Enhanced status codes) +/* RFC 3848 (ESMTP transmission types) +/* RFC 4409 (Message submission) +/* RFC 4954 (AUTH command) +/* RFC 5321 (SMTP protocol) +/* RFC 6531 (Internationalized SMTP) +/* RFC 6533 (Internationalized Delivery Status Notifications) +/* RFC 7505 ("Null MX" No Service Resource Record) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces, protocol problems, +/* policy violations, and of other trouble. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtpd\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* The following parameters work around implementation errors in other +/* software, and/or allow you to override standards in order to prevent +/* undesirable use. +/* .ad +/* .fi +/* .IP "\fBbroken_sasl_auth_clients (no)\fR" +/* Enable interoperability with remote SMTP clients that implement an obsolete +/* version of the AUTH command (RFC 4954). +/* .IP "\fBdisable_vrfy_command (no)\fR" +/* Disable the SMTP VRFY command. +/* .IP "\fBsmtpd_noop_commands (empty)\fR" +/* List of commands that the Postfix SMTP server replies to with "250 +/* Ok", without doing any syntax checks and without changing state. +/* .IP "\fBstrict_rfc821_envelopes (no)\fR" +/* Require that addresses received in SMTP MAIL FROM and RCPT TO +/* commands are enclosed with <>, and that those addresses do +/* not contain RFC 822 style comments or phrases. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR" +/* Request that the Postfix SMTP server rejects mail from unknown +/* sender addresses, even when no explicit reject_unlisted_sender +/* access restriction is specified. +/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR" +/* What remote SMTP clients the Postfix SMTP server will not offer +/* AUTH support to. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtpd_discard_ehlo_keyword_address_maps (empty)\fR" +/* Lookup tables, indexed by the remote SMTP client address, with +/* case insensitive lists of EHLO keywords (pipelining, starttls, auth, +/* etc.) that the Postfix SMTP server will not send in the EHLO response +/* to a +/* remote SMTP client. +/* .IP "\fBsmtpd_discard_ehlo_keywords (empty)\fR" +/* A case insensitive list of EHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix SMTP server will not send in the EHLO +/* response +/* to a remote SMTP client. +/* .IP "\fBsmtpd_delay_open_until_valid_rcpt (yes)\fR" +/* Postpone the start of an SMTP mail transaction until a valid +/* RCPT TO command is received. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR" +/* Force the Postfix SMTP server to issue a TLS session id, even +/* when TLS session caching is turned off (smtpd_tls_session_cache_database +/* is empty). +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBtcp_windowsize (0)\fR" +/* An optional workaround for routers that break TCP window scaling. +/* .PP +/* Available in Postfix version 2.7 and later: +/* .IP "\fBsmtpd_command_filter (empty)\fR" +/* A mechanism to transform commands from remote SMTP clients. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR" +/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout +/* time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtpd_dns_reply_filter (empty)\fR" +/* Optional filter for Postfix SMTP server DNS lookup results. +/* ADDRESS REWRITING CONTROLS +/* .ad +/* .fi +/* See the ADDRESS_REWRITING_README document for a detailed +/* discussion of Postfix address rewriting. +/* .IP "\fBreceive_override_options (empty)\fR" +/* Enable or disable recipient validation, built-in content +/* filtering, or address mapping. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBlocal_header_rewrite_clients (permit_inet_interfaces)\fR" +/* Rewrite message header addresses in mail from these clients and +/* update incomplete addresses with the domain name in $myorigin or +/* $mydomain; either don't rewrite message headers from other clients +/* at all, or rewrite message headers and update incomplete addresses +/* with the domain specified in the remote_header_rewrite_domain +/* parameter. +/* BEFORE-SMTPD PROXY AGENT +/* .ad +/* .fi +/* Available in Postfix version 2.10 and later: +/* .IP "\fBsmtpd_upstream_proxy_protocol (empty)\fR" +/* The name of the proxy protocol used by an optional before-smtpd +/* proxy agent. +/* .IP "\fBsmtpd_upstream_proxy_timeout (5s)\fR" +/* The time limit for the proxy protocol specified with the +/* smtpd_upstream_proxy_protocol parameter. +/* AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* As of version 1.0, Postfix can be configured to send new mail to +/* an external content filter AFTER the mail is queued. This content +/* filter is expected to inject mail back into a (Postfix or other) +/* MTA for further delivery. See the FILTER_README document for details. +/* .IP "\fBcontent_filter (empty)\fR" +/* After the message is queued, send the entire message to the +/* specified \fItransport:destination\fR. +/* BEFORE QUEUE EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* As of version 2.1, the Postfix SMTP server can be configured +/* to send incoming mail to a real-time SMTP-based content filter +/* BEFORE mail is queued. This content filter is expected to inject +/* mail back into Postfix. See the SMTPD_PROXY_README document for +/* details on how to configure and operate this feature. +/* .IP "\fBsmtpd_proxy_filter (empty)\fR" +/* The hostname and TCP port of the mail filtering proxy server. +/* .IP "\fBsmtpd_proxy_ehlo ($myhostname)\fR" +/* How the Postfix SMTP server announces itself to the proxy filter. +/* .IP "\fBsmtpd_proxy_options (empty)\fR" +/* List of options that control how the Postfix SMTP server +/* communicates with a before-queue content filter. +/* .IP "\fBsmtpd_proxy_timeout (100s)\fR" +/* The time limit for connecting to a proxy filter and for sending or +/* receiving information. +/* BEFORE QUEUE MILTER CONTROLS +/* .ad +/* .fi +/* As of version 2.3, Postfix supports the Sendmail version 8 +/* Milter (mail filter) protocol. These content filters run +/* outside Postfix. They can inspect the SMTP command stream +/* and the message content, and can request modifications before +/* mail is queued. For details see the MILTER_README document. +/* .IP "\fBsmtpd_milters (empty)\fR" +/* A list of Milter (mail filter) applications for new mail that +/* arrives via the Postfix \fBsmtpd\fR(8) server. +/* .IP "\fBmilter_protocol (6)\fR" +/* The mail filter protocol version and optional protocol extensions +/* for communication with a Milter application; prior to Postfix 2.6 +/* the default protocol is 2. +/* .IP "\fBmilter_default_action (tempfail)\fR" +/* The default action when a Milter (mail filter) application is +/* unavailable or mis-configured. +/* .IP "\fBmilter_macro_daemon_name ($myhostname)\fR" +/* The {daemon_name} macro value for Milter (mail filter) applications. +/* .IP "\fBmilter_macro_v ($mail_name $mail_version)\fR" +/* The {v} macro value for Milter (mail filter) applications. +/* .IP "\fBmilter_connect_timeout (30s)\fR" +/* The time limit for connecting to a Milter (mail filter) +/* application, and for negotiating protocol options. +/* .IP "\fBmilter_command_timeout (30s)\fR" +/* The time limit for sending an SMTP command to a Milter (mail +/* filter) application, and for receiving the response. +/* .IP "\fBmilter_content_timeout (300s)\fR" +/* The time limit for sending message content to a Milter (mail +/* filter) application, and for receiving the response. +/* .IP "\fBmilter_connect_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after completion of an SMTP connection. +/* .IP "\fBmilter_helo_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP HELO or EHLO command. +/* .IP "\fBmilter_mail_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP MAIL FROM command. +/* .IP "\fBmilter_rcpt_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP RCPT TO command. +/* .IP "\fBmilter_data_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to version 4 or higher Milter (mail +/* filter) applications after the SMTP DATA command. +/* .IP "\fBmilter_unknown_command_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to version 3 or higher Milter (mail +/* filter) applications after an unknown SMTP command. +/* .IP "\fBmilter_end_of_header_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the end of the message header. +/* .IP "\fBmilter_end_of_data_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the message end-of-data. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBmilter_macro_defaults (empty)\fR" +/* Optional list of \fIname=value\fR pairs that specify default +/* values for arbitrary macros that Postfix may send to Milter +/* applications. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBsmtpd_milter_maps (empty)\fR" +/* Lookup tables with Milter settings per remote SMTP client IP +/* address. +/* GENERAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* The following parameters are applicable for both built-in +/* and external content filters. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBreceive_override_options (empty)\fR" +/* Enable or disable recipient validation, built-in content +/* filtering, or address mapping. +/* EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* The following parameters are applicable for both before-queue +/* and after-queue content filtering. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_authorized_xforward_hosts (empty)\fR" +/* What remote SMTP clients are allowed to use the XFORWARD feature. +/* SASL AUTHENTICATION CONTROLS +/* .ad +/* .fi +/* Postfix SASL support (RFC 4954) can be used to authenticate remote +/* SMTP clients to the Postfix SMTP server, and to authenticate the +/* Postfix SMTP client to a remote SMTP server. +/* See the SASL_README document for details. +/* .IP "\fBbroken_sasl_auth_clients (no)\fR" +/* Enable interoperability with remote SMTP clients that implement an obsolete +/* version of the AUTH command (RFC 4954). +/* .IP "\fBsmtpd_sasl_auth_enable (no)\fR" +/* Enable SASL authentication in the Postfix SMTP server. +/* .IP "\fBsmtpd_sasl_local_domain (empty)\fR" +/* The name of the Postfix SMTP server's local SASL authentication +/* realm. +/* .IP "\fBsmtpd_sasl_security_options (noanonymous)\fR" +/* Postfix SMTP server SASL security options; as of Postfix 2.3 +/* the list of available +/* features depends on the SASL server implementation that is selected +/* with \fBsmtpd_sasl_type\fR. +/* .IP "\fBsmtpd_sender_login_maps (empty)\fR" +/* Optional lookup table with the SASL login names that own the sender +/* (MAIL FROM) addresses. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR" +/* What remote SMTP clients the Postfix SMTP server will not offer +/* AUTH support to. +/* .PP +/* Available in Postfix version 2.1 and 2.2: +/* .IP "\fBsmtpd_sasl_application_name (smtpd)\fR" +/* The application name that the Postfix SMTP server uses for SASL +/* server initialization. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_sasl_authenticated_header (no)\fR" +/* Report the SASL authenticated user name in the \fBsmtpd\fR(8) Received +/* message header. +/* .IP "\fBsmtpd_sasl_path (smtpd)\fR" +/* Implementation-specific information that the Postfix SMTP server +/* passes through to +/* the SASL plug-in implementation that is selected with +/* \fBsmtpd_sasl_type\fR. +/* .IP "\fBsmtpd_sasl_type (cyrus)\fR" +/* The SASL plug-in type that the Postfix SMTP server should use +/* for authentication. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBcyrus_sasl_config_path (empty)\fR" +/* Search path for Cyrus SASL application configuration files, +/* currently used only to locate the $smtpd_sasl_path.conf file. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtpd_sasl_service (smtp)\fR" +/* The service name that is passed to the SASL plug-in that is +/* selected with \fBsmtpd_sasl_type\fR and \fBsmtpd_sasl_path\fR. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtpd_sasl_response_limit (12288)\fR" +/* The maximum length of a SASL client's response to a server challenge. +/* STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* Detailed information about STARTTLS configuration may be +/* found in the TLS_README document. +/* .IP "\fBsmtpd_tls_security_level (empty)\fR" +/* The SMTP TLS security level for the Postfix SMTP server; when +/* a non-empty value is specified, this overrides the obsolete parameters +/* smtpd_use_tls and smtpd_enforce_tls. +/* .IP "\fBsmtpd_sasl_tls_security_options ($smtpd_sasl_security_options)\fR" +/* The SASL authentication security options that the Postfix SMTP +/* server uses for TLS encrypted SMTP sessions. +/* .IP "\fBsmtpd_starttls_timeout (see 'postconf -d' output)\fR" +/* The time limit for Postfix SMTP server write and read operations +/* during TLS startup and shutdown handshake procedures. +/* .IP "\fBsmtpd_tls_CAfile (empty)\fR" +/* A file containing (PEM format) CA certificates of root CAs trusted +/* to sign either remote SMTP client certificates or intermediate CA +/* certificates. +/* .IP "\fBsmtpd_tls_CApath (empty)\fR" +/* A directory containing (PEM format) CA certificates of root CAs +/* trusted to sign either remote SMTP client certificates or intermediate CA +/* certificates. +/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR" +/* Force the Postfix SMTP server to issue a TLS session id, even +/* when TLS session caching is turned off (smtpd_tls_session_cache_database +/* is empty). +/* .IP "\fBsmtpd_tls_ask_ccert (no)\fR" +/* Ask a remote SMTP client for a client certificate. +/* .IP "\fBsmtpd_tls_auth_only (no)\fR" +/* When TLS encryption is optional in the Postfix SMTP server, do +/* not announce or accept SASL authentication over unencrypted +/* connections. +/* .IP "\fBsmtpd_tls_ccert_verifydepth (9)\fR" +/* The verification depth for remote SMTP client certificates. +/* .IP "\fBsmtpd_tls_cert_file (empty)\fR" +/* File with the Postfix SMTP server RSA certificate in PEM format. +/* .IP "\fBsmtpd_tls_exclude_ciphers (empty)\fR" +/* List of ciphers or cipher types to exclude from the SMTP server +/* cipher list at all TLS security levels. +/* .IP "\fBsmtpd_tls_dcert_file (empty)\fR" +/* File with the Postfix SMTP server DSA certificate in PEM format. +/* .IP "\fBsmtpd_tls_dh1024_param_file (empty)\fR" +/* File with DH parameters that the Postfix SMTP server should +/* use with non-export EDH ciphers. +/* .IP "\fBsmtpd_tls_dh512_param_file (empty)\fR" +/* File with DH parameters that the Postfix SMTP server should +/* use with export-grade EDH ciphers. +/* .IP "\fBsmtpd_tls_dkey_file ($smtpd_tls_dcert_file)\fR" +/* File with the Postfix SMTP server DSA private key in PEM format. +/* .IP "\fBsmtpd_tls_key_file ($smtpd_tls_cert_file)\fR" +/* File with the Postfix SMTP server RSA private key in PEM format. +/* .IP "\fBsmtpd_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP server logging of TLS activity. +/* .IP "\fBsmtpd_tls_mandatory_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP server will +/* use with mandatory TLS encryption. +/* .IP "\fBsmtpd_tls_mandatory_exclude_ciphers (empty)\fR" +/* Additional list of ciphers or cipher types to exclude from the +/* Postfix SMTP server cipher list at mandatory TLS security levels. +/* .IP "\fBsmtpd_tls_mandatory_protocols (!SSLv2, !SSLv3)\fR" +/* The SSL/TLS protocols accepted by the Postfix SMTP server with +/* mandatory TLS encryption. +/* .IP "\fBsmtpd_tls_received_header (no)\fR" +/* Request that the Postfix SMTP server produces Received: message +/* headers that include information about the protocol and cipher used, +/* as well as the remote SMTP client CommonName and client certificate issuer +/* CommonName. +/* .IP "\fBsmtpd_tls_req_ccert (no)\fR" +/* With mandatory TLS encryption, require a trusted remote SMTP client +/* certificate in order to allow TLS connections to proceed. +/* .IP "\fBsmtpd_tls_wrappermode (no)\fR" +/* Run the Postfix SMTP server in the non-standard "wrapper" mode, +/* instead of using the STARTTLS command. +/* .IP "\fBtls_daemon_random_bytes (32)\fR" +/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8) +/* process requests from the \fBtlsmgr\fR(8) server in order to seed its +/* internal pseudo random number generator (PRNG). +/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "high" grade ciphers. +/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "medium" or higher grade ciphers. +/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "low" or higher grade ciphers. +/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "export" or higher grade ciphers. +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" +/* The OpenSSL cipherlist for "NULL" grade ciphers that provide +/* authentication without encryption. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtpd_tls_fingerprint_digest (md5)\fR" +/* The message digest algorithm to construct remote SMTP +/* client-certificate +/* fingerprints or public key fingerprints (Postfix 2.9 and later) +/* for \fBcheck_ccert_access\fR and \fBpermit_tls_clientcerts\fR. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBsmtpd_tls_protocols (!SSLv2, !SSLv3)\fR" +/* List of TLS protocols that the Postfix SMTP server will exclude +/* or include with opportunistic TLS encryption. +/* .IP "\fBsmtpd_tls_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP server +/* will use with opportunistic TLS encryption. +/* .IP "\fBsmtpd_tls_eccert_file (empty)\fR" +/* File with the Postfix SMTP server ECDSA certificate in PEM format. +/* .IP "\fBsmtpd_tls_eckey_file ($smtpd_tls_eccert_file)\fR" +/* File with the Postfix SMTP server ECDSA private key in PEM format. +/* .IP "\fBsmtpd_tls_eecdh_grade (see 'postconf -d' output)\fR" +/* The Postfix SMTP server security grade for ephemeral elliptic-curve +/* Diffie-Hellman (EECDH) key exchange. +/* .IP "\fBtls_eecdh_strong_curve (prime256v1)\fR" +/* The elliptic curve used by the Postfix SMTP server for sensibly +/* strong +/* ephemeral ECDH key exchange. +/* .IP "\fBtls_eecdh_ultra_curve (secp384r1)\fR" +/* The elliptic curve used by the Postfix SMTP server for maximally +/* strong +/* ephemeral ECDH key exchange. +/* .PP +/* Available in Postfix version 2.8 and later: +/* .IP "\fBtls_preempt_cipherlist (no)\fR" +/* With SSLv3 and later, use the Postfix SMTP server's cipher +/* preference order instead of the remote client's cipher preference +/* order. +/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" +/* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" +/* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBtls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc)\fR" +/* Algorithm used to encrypt RFC5077 TLS session tickets. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBtls_eecdh_auto_curves (see 'postconf -d' output)\fR" +/* The prioritized list of elliptic curves supported by the Postfix +/* SMTP client and server. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtpd_tls_chain_files (empty)\fR" +/* List of one or more PEM files, each holding one or more private keys +/* directly followed by a corresponding certificate chain. +/* .IP "\fBtls_server_sni_maps (empty)\fR" +/* Optional lookup tables that map names received from remote SMTP +/* clients via the TLS Server Name Indication (SNI) extension to the +/* appropriate keys and certificate chains. +/* .PP +/* Introduced with Postfix 3.4.6, 3.3.5, 3.2.10, and 3.1.13: +/* .IP "\fBtls_fast_shutdown_enable (yes)\fR" +/* A workaround for implementations that hang Postfix while shuting +/* down a TLS session, until Postfix times out. +/* OBSOLETE STARTTLS CONTROLS +/* .ad +/* .fi +/* The following configuration parameters exist for compatibility +/* with Postfix versions before 2.3. Support for these will +/* be removed in a future release. +/* .IP "\fBsmtpd_use_tls (no)\fR" +/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients, +/* but do not require that clients use TLS encryption. +/* .IP "\fBsmtpd_enforce_tls (no)\fR" +/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, +/* and require that clients use TLS encryption. +/* .IP "\fBsmtpd_tls_cipherlist (empty)\fR" +/* Obsolete Postfix < 2.3 control for the Postfix SMTP server TLS +/* cipher list. +/* SMTPUTF8 CONTROLS +/* .ad +/* .fi +/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. +/* .IP "\fBstrict_smtputf8 (no)\fR" +/* Enable stricter enforcement of the SMTPUTF8 protocol. +/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR" +/* Detect that a message requires SMTPUTF8 support for the specified +/* mail origin classes. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBenable_idna2003_compatibility (no)\fR" +/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008, +/* when converting UTF-8 domain names to/from the ASCII form that is +/* used for DNS lookups. +/* VERP SUPPORT CONTROLS +/* .ad +/* .fi +/* With VERP style delivery, each recipient of a message receives a +/* customized copy of the message with his/her own recipient address +/* encoded in the envelope sender address. The VERP_README file +/* describes configuration and operation details of Postfix support +/* for variable envelope return path addresses. VERP style delivery +/* is requested with the SMTP XVERP command or with the "sendmail +/* -V" command-line option and is available in Postfix version 1.1 +/* and later. +/* .IP "\fBdefault_verp_delimiters (+=)\fR" +/* The two default VERP delimiter characters. +/* .IP "\fBverp_delimiter_filter (-=+)\fR" +/* The characters Postfix accepts as VERP delimiter characters on the +/* Postfix \fBsendmail\fR(1) command line and in SMTP commands. +/* .PP +/* Available in Postfix version 1.1 and 2.0: +/* .IP "\fBauthorized_verp_clients ($mynetworks)\fR" +/* What remote SMTP clients are allowed to specify the XVERP command. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_authorized_verp_clients ($authorized_verp_clients)\fR" +/* What remote SMTP clients are allowed to specify the XVERP command. +/* TROUBLE SHOOTING CONTROLS +/* .ad +/* .fi +/* The DEBUG_README document describes how to debug parts of the +/* Postfix mail system. The methods vary from making the software log +/* a lot of detail, to running some daemon processes under control of +/* a call tracer or debugger. +/* .IP "\fBdebug_peer_level (2)\fR" +/* The increment in verbose logging level when a remote client or +/* server matches a pattern in the debug_peer_list parameter. +/* .IP "\fBdebug_peer_list (empty)\fR" +/* Optional list of remote client or server hostname or network +/* address patterns that cause the verbose logging level to increase +/* by the amount specified in $debug_peer_level. +/* .IP "\fBerror_notice_recipient (postmaster)\fR" +/* The recipient of postmaster notifications about mail delivery +/* problems that are caused by policy, resource, software or protocol +/* errors. +/* .IP "\fBinternal_mail_filter_classes (empty)\fR" +/* What categories of Postfix-generated mail are subject to +/* before-queue content inspection by non_smtpd_milters, header_checks +/* and body_checks. +/* .IP "\fBnotify_classes (resource, software)\fR" +/* The list of error classes that are reported to the postmaster. +/* .IP "\fBsmtpd_reject_footer (empty)\fR" +/* Optional information that is appended after each Postfix SMTP +/* server +/* 4XX or 5XX response. +/* .IP "\fBsoft_bounce (no)\fR" +/* Safety net to keep mail queued that would otherwise be returned to +/* the sender. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_authorized_xclient_hosts (empty)\fR" +/* What remote SMTP clients are allowed to use the XCLIENT feature. +/* .PP +/* Available in Postfix version 2.10 and later: +/* .IP "\fBsmtpd_log_access_permit_actions (empty)\fR" +/* Enable logging of the named "permit" actions in SMTP server +/* access lists (by default, the SMTP server logs "reject" actions but +/* not "permit" actions). +/* KNOWN VERSUS UNKNOWN RECIPIENT CONTROLS +/* .ad +/* .fi +/* As of Postfix version 2.0, the SMTP server rejects mail for +/* unknown recipients. This prevents the mail queue from clogging up +/* with undeliverable MAILER-DAEMON messages. Additional information +/* on this topic is in the LOCAL_RECIPIENT_README and ADDRESS_CLASS_README +/* documents. +/* .IP "\fBshow_user_unknown_table_name (yes)\fR" +/* Display the name of the recipient table in the "User unknown" +/* responses. +/* .IP "\fBcanonical_maps (empty)\fR" +/* Optional address mapping lookup tables for message headers and +/* envelopes. +/* .IP "\fBrecipient_canonical_maps (empty)\fR" +/* Optional address mapping lookup tables for envelope and header +/* recipient addresses. +/* .IP "\fBsender_canonical_maps (empty)\fR" +/* Optional address mapping lookup tables for envelope and header +/* sender addresses. +/* .PP +/* Parameters concerning known/unknown local recipients: +/* .IP "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR" +/* The list of domains that are delivered via the $local_transport +/* mail delivery transport. +/* .IP "\fBinet_interfaces (all)\fR" +/* The network interface addresses that this mail system receives +/* mail on. +/* .IP "\fBproxy_interfaces (empty)\fR" +/* The network interface addresses that this mail system receives mail +/* on by way of a proxy or network address translation unit. +/* .IP "\fBinet_protocols (all)\fR" +/* The Internet protocols Postfix will attempt to use when making +/* or accepting connections. +/* .IP "\fBlocal_recipient_maps (proxy:unix:passwd.byname $alias_maps)\fR" +/* Lookup tables with all names or addresses of local recipients: +/* a recipient address is local when its domain matches $mydestination, +/* $inet_interfaces or $proxy_interfaces. +/* .IP "\fBunknown_local_recipient_reject_code (550)\fR" +/* The numerical Postfix SMTP server response code when a recipient +/* address is local, and $local_recipient_maps specifies a list of +/* lookup tables that does not match the recipient. +/* .PP +/* Parameters concerning known/unknown recipients of relay destinations: +/* .IP "\fBrelay_domains (Postfix >= 3.0: empty, Postfix < 3.0: $mydestination)\fR" +/* What destination domains (and subdomains thereof) this system +/* will relay mail to. +/* .IP "\fBrelay_recipient_maps (empty)\fR" +/* Optional lookup tables with all valid addresses in the domains +/* that match $relay_domains. +/* .IP "\fBunknown_relay_recipient_reject_code (550)\fR" +/* The numerical Postfix SMTP server reply code when a recipient +/* address matches $relay_domains, and relay_recipient_maps specifies +/* a list of lookup tables that does not match the recipient address. +/* .PP +/* Parameters concerning known/unknown recipients in virtual alias +/* domains: +/* .IP "\fBvirtual_alias_domains ($virtual_alias_maps)\fR" +/* Postfix is final destination for the specified list of virtual +/* alias domains, that is, domains for which all addresses are aliased +/* to addresses in other local or remote domains. +/* .IP "\fBvirtual_alias_maps ($virtual_maps)\fR" +/* Optional lookup tables that alias specific mail addresses or domains +/* to other local or remote address. +/* .IP "\fBunknown_virtual_alias_reject_code (550)\fR" +/* The Postfix SMTP server reply code when a recipient address matches +/* $virtual_alias_domains, and $virtual_alias_maps specifies a list +/* of lookup tables that does not match the recipient address. +/* .PP +/* Parameters concerning known/unknown recipients in virtual mailbox +/* domains: +/* .IP "\fBvirtual_mailbox_domains ($virtual_mailbox_maps)\fR" +/* Postfix is final destination for the specified list of domains; +/* mail is delivered via the $virtual_transport mail delivery transport. +/* .IP "\fBvirtual_mailbox_maps (empty)\fR" +/* Optional lookup tables with all valid addresses in the domains that +/* match $virtual_mailbox_domains. +/* .IP "\fBunknown_virtual_mailbox_reject_code (550)\fR" +/* The Postfix SMTP server reply code when a recipient address matches +/* $virtual_mailbox_domains, and $virtual_mailbox_maps specifies a list +/* of lookup tables that does not match the recipient address. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* The following parameters limit resource usage by the SMTP +/* server and/or control client request rates. +/* .IP "\fBline_length_limit (2048)\fR" +/* Upon input, long lines are chopped up into pieces of at most +/* this length; upon delivery, long lines are reconstructed. +/* .IP "\fBqueue_minfree (0)\fR" +/* The minimal amount of free space in bytes in the queue file system +/* that is needed to receive mail. +/* .IP "\fBmessage_size_limit (10240000)\fR" +/* The maximal size in bytes of a message, including envelope information. +/* .IP "\fBsmtpd_recipient_limit (1000)\fR" +/* The maximal number of recipients that the Postfix SMTP server +/* accepts per message delivery request. +/* .IP "\fBsmtpd_timeout (normal: 300s, overload: 10s)\fR" +/* The time limit for sending a Postfix SMTP server response and for +/* receiving a remote SMTP client request. +/* .IP "\fBsmtpd_history_flush_threshold (100)\fR" +/* The maximal number of lines in the Postfix SMTP server command history +/* before it is flushed upon receipt of EHLO, RSET, or end of DATA. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_peername_lookup (yes)\fR" +/* Attempt to look up the remote SMTP client hostname, and verify that +/* the name matches the client IP address. +/* .PP +/* The per SMTP client connection count and request rate limits are +/* implemented in co-operation with the \fBanvil\fR(8) service, and +/* are available in Postfix version 2.2 and later. +/* .IP "\fBsmtpd_client_connection_count_limit (50)\fR" +/* How many simultaneous connections any client is allowed to +/* make to this service. +/* .IP "\fBsmtpd_client_connection_rate_limit (0)\fR" +/* The maximal number of connection attempts any client is allowed to +/* make to this service per time unit. +/* .IP "\fBsmtpd_client_message_rate_limit (0)\fR" +/* The maximal number of message delivery requests that any client is +/* allowed to make to this service per time unit, regardless of whether +/* or not Postfix actually accepts those messages. +/* .IP "\fBsmtpd_client_recipient_rate_limit (0)\fR" +/* The maximal number of recipient addresses that any client is allowed +/* to send to this service per time unit, regardless of whether or not +/* Postfix actually accepts those recipients. +/* .IP "\fBsmtpd_client_event_limit_exceptions ($mynetworks)\fR" +/* Clients that are excluded from smtpd_client_*_count/rate_limit +/* restrictions. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_client_new_tls_session_rate_limit (0)\fR" +/* The maximal number of new (i.e., uncached) TLS sessions that a +/* remote SMTP client is allowed to negotiate with this service per +/* time unit. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR" +/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout +/* time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBsmtpd_client_auth_rate_limit (0)\fR" +/* The maximal number of AUTH commands that any client is allowed to +/* send to this service per time unit, regardless of whether or not +/* Postfix actually accepts those commands. +/* TARPIT CONTROLS +/* .ad +/* .fi +/* When a remote SMTP client makes errors, the Postfix SMTP server +/* can insert delays before responding. This can help to slow down +/* run-away software. The behavior is controlled by an error counter +/* that counts the number of errors within an SMTP session that a +/* client makes without delivering mail. +/* .IP "\fBsmtpd_error_sleep_time (1s)\fR" +/* With Postfix version 2.1 and later: the SMTP server response delay after +/* a client has made more than $smtpd_soft_error_limit errors, and +/* fewer than $smtpd_hard_error_limit errors, without delivering mail. +/* .IP "\fBsmtpd_soft_error_limit (10)\fR" +/* The number of errors a remote SMTP client is allowed to make without +/* delivering mail before the Postfix SMTP server slows down all its +/* responses. +/* .IP "\fBsmtpd_hard_error_limit (normal: 20, overload: 1)\fR" +/* The maximal number of errors a remote SMTP client is allowed to +/* make without delivering mail. +/* .IP "\fBsmtpd_junk_command_limit (normal: 100, overload: 1)\fR" +/* The number of junk commands (NOOP, VRFY, ETRN or RSET) that a remote +/* SMTP client can send before the Postfix SMTP server starts to +/* increment the error counter with each junk command. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_recipient_overshoot_limit (1000)\fR" +/* The number of recipients that a remote SMTP client can send in +/* excess of the limit specified with $smtpd_recipient_limit, before +/* the Postfix SMTP server increments the per-session error count +/* for each excess recipient. +/* ACCESS POLICY DELEGATION CONTROLS +/* .ad +/* .fi +/* As of version 2.1, Postfix can be configured to delegate access +/* policy decisions to an external server that runs outside Postfix. +/* See the file SMTPD_POLICY_README for more information. +/* .IP "\fBsmtpd_policy_service_max_idle (300s)\fR" +/* The time after which an idle SMTPD policy service connection is +/* closed. +/* .IP "\fBsmtpd_policy_service_max_ttl (1000s)\fR" +/* The time after which an active SMTPD policy service connection is +/* closed. +/* .IP "\fBsmtpd_policy_service_timeout (100s)\fR" +/* The time limit for connecting to, writing to, or receiving from a +/* delegated SMTPD policy server. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtpd_policy_service_default_action (451 4.3.5 Server configuration problem)\fR" +/* The default action when an SMTPD policy service request fails. +/* .IP "\fBsmtpd_policy_service_request_limit (0)\fR" +/* The maximal number of requests per SMTPD policy service connection, +/* or zero (no limit). +/* .IP "\fBsmtpd_policy_service_try_limit (2)\fR" +/* The maximal number of attempts to send an SMTPD policy service +/* request before giving up. +/* .IP "\fBsmtpd_policy_service_retry_delay (1s)\fR" +/* The delay between attempts to resend a failed SMTPD policy +/* service request. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBsmtpd_policy_service_policy_context (empty)\fR" +/* Optional information that the Postfix SMTP server specifies in +/* the "policy_context" attribute of a policy service request (originally, +/* to share the same service endpoint among multiple check_policy_service +/* clients). +/* ACCESS CONTROLS +/* .ad +/* .fi +/* The SMTPD_ACCESS_README document gives an introduction to all the +/* SMTP server access control features. +/* .IP "\fBsmtpd_delay_reject (yes)\fR" +/* Wait until the RCPT TO command before evaluating +/* $smtpd_client_restrictions, $smtpd_helo_restrictions and +/* $smtpd_sender_restrictions, or wait until the ETRN command before +/* evaluating $smtpd_client_restrictions and $smtpd_helo_restrictions. +/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR" +/* A list of Postfix features where the pattern "example.com" also +/* matches subdomains of example.com, +/* instead of requiring an explicit ".example.com" pattern. +/* .IP "\fBsmtpd_client_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client connection request. +/* .IP "\fBsmtpd_helo_required (no)\fR" +/* Require that a remote SMTP client introduces itself with the HELO +/* or EHLO command before sending the MAIL command or other commands +/* that require EHLO negotiation. +/* .IP "\fBsmtpd_helo_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client HELO command. +/* .IP "\fBsmtpd_sender_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client MAIL FROM command. +/* .IP "\fBsmtpd_recipient_restrictions (see 'postconf -d' output)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client RCPT TO command, after smtpd_relay_restrictions. +/* .IP "\fBsmtpd_etrn_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client ETRN command. +/* .IP "\fBallow_untrusted_routing (no)\fR" +/* Forward mail with sender-specified routing (user[@%!]remote[@%!]site) +/* from untrusted clients to destinations matching $relay_domains. +/* .IP "\fBsmtpd_restriction_classes (empty)\fR" +/* User-defined aliases for groups of access restrictions. +/* .IP "\fBsmtpd_null_access_lookup_key (<>)\fR" +/* The lookup key to be used in SMTP \fBaccess\fR(5) tables instead of the +/* null sender address. +/* .IP "\fBpermit_mx_backup_networks (empty)\fR" +/* Restrict the use of the permit_mx_backup SMTP access feature to +/* only domains whose primary MX hosts match the listed networks. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBsmtpd_data_restrictions (empty)\fR" +/* Optional access restrictions that the Postfix SMTP server applies +/* in the context of the SMTP DATA command. +/* .IP "\fBsmtpd_expansion_filter (see 'postconf -d' output)\fR" +/* What characters are allowed in $name expansions of RBL reply +/* templates. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR" +/* Request that the Postfix SMTP server rejects mail from unknown +/* sender addresses, even when no explicit reject_unlisted_sender +/* access restriction is specified. +/* .IP "\fBsmtpd_reject_unlisted_recipient (yes)\fR" +/* Request that the Postfix SMTP server rejects mail for unknown +/* recipient addresses, even when no explicit reject_unlisted_recipient +/* access restriction is specified. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtpd_end_of_data_restrictions (empty)\fR" +/* Optional access restrictions that the Postfix SMTP server +/* applies in the context of the SMTP END-OF-DATA command. +/* .PP +/* Available in Postfix version 2.10 and later: +/* .IP "\fBsmtpd_relay_restrictions (permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination)\fR" +/* Access restrictions for mail relay control that the Postfix +/* SMTP server applies in the context of the RCPT TO command, before +/* smtpd_recipient_restrictions. +/* SENDER AND RECIPIENT ADDRESS VERIFICATION CONTROLS +/* .ad +/* .fi +/* Postfix version 2.1 introduces sender and recipient address verification. +/* This feature is implemented by sending probe email messages that +/* are not actually delivered. +/* This feature is requested via the reject_unverified_sender and +/* reject_unverified_recipient access restrictions. The status of +/* verification probes is maintained by the \fBverify\fR(8) server. +/* See the file ADDRESS_VERIFICATION_README for information +/* about how to configure and operate the Postfix sender/recipient +/* address verification service. +/* .IP "\fBaddress_verify_poll_count (normal: 3, overload: 1)\fR" +/* How many times to query the \fBverify\fR(8) service for the completion +/* of an address verification request in progress. +/* .IP "\fBaddress_verify_poll_delay (3s)\fR" +/* The delay between queries for the completion of an address +/* verification request in progress. +/* .IP "\fBaddress_verify_sender ($double_bounce_sender)\fR" +/* The sender address to use in address verification probes; prior +/* to Postfix 2.5 the default was "postmaster". +/* .IP "\fBunverified_sender_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when a recipient +/* address is rejected by the reject_unverified_sender restriction. +/* .IP "\fBunverified_recipient_reject_code (450)\fR" +/* The numerical Postfix SMTP server response when a recipient address +/* is rejected by the reject_unverified_recipient restriction. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBunverified_sender_defer_code (450)\fR" +/* The numerical Postfix SMTP server response code when a sender address +/* probe fails due to a temporary error condition. +/* .IP "\fBunverified_recipient_defer_code (450)\fR" +/* The numerical Postfix SMTP server response when a recipient address +/* probe fails due to a temporary error condition. +/* .IP "\fBunverified_sender_reject_reason (empty)\fR" +/* The Postfix SMTP server's reply when rejecting mail with +/* reject_unverified_sender. +/* .IP "\fBunverified_recipient_reject_reason (empty)\fR" +/* The Postfix SMTP server's reply when rejecting mail with +/* reject_unverified_recipient. +/* .IP "\fBunverified_sender_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unverified_sender +/* fails due to a temporary error condition. +/* .IP "\fBunverified_recipient_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unverified_recipient +/* fails due to a temporary error condition. +/* .PP +/* Available with Postfix 2.9 and later: +/* .IP "\fBaddress_verify_sender_ttl (0s)\fR" +/* The time between changes in the time-dependent portion of address +/* verification probe sender addresses. +/* ACCESS CONTROL RESPONSES +/* .ad +/* .fi +/* The following parameters control numerical SMTP reply codes +/* and/or text responses. +/* .IP "\fBaccess_map_reject_code (554)\fR" +/* The numerical Postfix SMTP server response code for +/* an \fBaccess\fR(5) map "reject" action. +/* .IP "\fBdefer_code (450)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is rejected by the "defer" restriction. +/* .IP "\fBinvalid_hostname_reject_code (501)\fR" +/* The numerical Postfix SMTP server response code when the client +/* HELO or EHLO command parameter is rejected by the reject_invalid_helo_hostname +/* restriction. +/* .IP "\fBmaps_rbl_reject_code (554)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is blocked by the reject_rbl_client, reject_rhsbl_client, +/* reject_rhsbl_reverse_client, reject_rhsbl_sender or +/* reject_rhsbl_recipient restriction. +/* .IP "\fBnon_fqdn_reject_code (504)\fR" +/* The numerical Postfix SMTP server reply code when a client request +/* is rejected by the reject_non_fqdn_helo_hostname, reject_non_fqdn_sender +/* or reject_non_fqdn_recipient restriction. +/* .IP "\fBplaintext_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when a request +/* is rejected by the \fBreject_plaintext_session\fR restriction. +/* .IP "\fBreject_code (554)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is rejected by the "reject" restriction. +/* .IP "\fBrelay_domains_reject_code (554)\fR" +/* The numerical Postfix SMTP server response code when a client +/* request is rejected by the reject_unauth_destination recipient +/* restriction. +/* .IP "\fBunknown_address_reject_code (450)\fR" +/* The numerical response code when the Postfix SMTP server rejects a +/* sender or recipient address because its domain is unknown. +/* .IP "\fBunknown_client_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when a client +/* without valid address <=> name mapping is rejected by the +/* reject_unknown_client_hostname restriction. +/* .IP "\fBunknown_hostname_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when the hostname +/* specified with the HELO or EHLO command is rejected by the +/* reject_unknown_helo_hostname restriction. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBdefault_rbl_reply (see 'postconf -d' output)\fR" +/* The default Postfix SMTP server response template for a request that is +/* rejected by an RBL-based restriction. +/* .IP "\fBmulti_recipient_bounce_reject_code (550)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is blocked by the reject_multi_recipient_bounce +/* restriction. +/* .IP "\fBrbl_reply_maps (empty)\fR" +/* Optional lookup tables with RBL response templates. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBaccess_map_defer_code (450)\fR" +/* The numerical Postfix SMTP server response code for +/* an \fBaccess\fR(5) map "defer" action, including "defer_if_permit" +/* or "defer_if_reject". +/* .IP "\fBreject_tempfail_action (defer_if_permit)\fR" +/* The Postfix SMTP server's action when a reject-type restriction +/* fails due to a temporary error condition. +/* .IP "\fBunknown_helo_hostname_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unknown_helo_hostname +/* fails due to a temporary error condition. +/* .IP "\fBunknown_address_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unknown_sender_domain +/* or reject_unknown_recipient_domain fail due to a temporary error +/* condition. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR" +/* The location of all postfix administrative commands. +/* .IP "\fBdouble_bounce_sender (double-bounce)\fR" +/* The sender address of postmaster notifications that are generated +/* by the mail system. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmail_name (Postfix)\fR" +/* The mail system name that is displayed in Received: headers, in +/* the SMTP greeting banner, and in bounced mail. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBmyhostname (see 'postconf -d' output)\fR" +/* The internet hostname of this mail system. +/* .IP "\fBmynetworks (see 'postconf -d' output)\fR" +/* The list of "trusted" remote SMTP clients that have more privileges than +/* "strangers". +/* .IP "\fBmyorigin ($myhostname)\fR" +/* The domain name that locally-posted mail appears to come +/* from, and that locally posted mail is delivered to. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBrecipient_delimiter (empty)\fR" +/* The set of characters that can separate a user name from its +/* extension (example: user+foo), or a .forward file name from its +/* extension (example: .forward+foo). +/* .IP "\fBsmtpd_banner ($myhostname ESMTP $mail_name)\fR" +/* The text that follows the 220 status code in the SMTP greeting +/* banner. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtpd_forbidden_commands (CONNECT, GET, POST)\fR" +/* List of commands that cause the Postfix SMTP server to immediately +/* terminate the session with a 221 code. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtpd_client_port_logging (no)\fR" +/* Enable logging of the remote SMTP client port in addition to +/* the hostname and IP address. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.4 and later: +/* .IP "\fBsmtpd_reject_footer_maps (empty)\fR" +/* Lookup tables, indexed by the complete Postfix SMTP server 4xx or +/* 5xx response, with reject footer templates. +/* SEE ALSO +/* anvil(8), connection/rate limiting +/* cleanup(8), message canonicalization +/* tlsmgr(8), TLS session and PRNG management +/* trivial-rewrite(8), address resolver +/* verify(8), address verification service +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* ADDRESS_CLASS_README, blocking unknown hosted or relay recipients +/* ADDRESS_REWRITING_README, Postfix address manipulation +/* BDAT_README, Postfix CHUNKING support +/* FILTER_README, external after-queue content filter +/* LOCAL_RECIPIENT_README, blocking unknown local recipients +/* MILTER_README, before-queue mail filter applications +/* SMTPD_ACCESS_README, built-in access policies +/* SMTPD_POLICY_README, external policy server +/* SMTPD_PROXY_README, external before-queue content filter +/* SASL_README, Postfix SASL howto +/* TLS_README, Postfix STARTTLS howto +/* VERP_README, Postfix XVERP extension +/* XCLIENT_README, Postfix XCLIENT extension +/* XFORWARD_README, Postfix XFORWARD extension +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* SASL support originally by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Revised TLS support by: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <string.h> +#include <stdio.h> /* remove() */ +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <ctype.h> +#include <signal.h> +#include <stddef.h> /* offsetof() */ + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <stringops.h> +#include <events.h> +#include <smtp_stream.h> +#include <valid_hostname.h> +#include <dict.h> +#include <watchdog.h> +#include <iostuff.h> +#include <split_at.h> +#include <name_code.h> +#include <inet_proto.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_version.h> /* milter_macro_v */ +#include <record.h> +#include <rec_type.h> +#include <mail_proto.h> +#include <cleanup_user.h> +#include <mail_date.h> +#include <mail_conf.h> +#include <off_cvt.h> +#include <debug_peer.h> +#include <mail_error.h> +#include <flush_clnt.h> +#include <mail_stream.h> +#include <mail_queue.h> +#include <tok822.h> +#include <verp_sender.h> +#include <string_list.h> +#include <quote_822_local.h> +#include <lex_822.h> +#include <namadr_list.h> +#include <input_transp.h> +#include <is_header.h> +#include <anvil_clnt.h> +#include <flush_clnt.h> +#include <ehlo_mask.h> /* ehlo filter */ +#include <maps.h> /* ehlo filter */ +#include <valid_mailhost_addr.h> +#include <dsn_mask.h> +#include <xtext.h> +#include <uxtext.h> +#include <tls_proxy.h> +#include <verify_sender_addr.h> +#include <smtputf8.h> +#include <match_parent_style.h> + +/* 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; +int var_queue_minfree; /* XXX use off_t */ +char *var_smtpd_banner; +char *var_notify_classes; +char *var_client_checks; +char *var_helo_checks; +char *var_mail_checks; +char *var_relay_checks; +char *var_rcpt_checks; +char *var_etrn_checks; +char *var_data_checks; +char *var_eod_checks; +int var_unk_client_code; +int var_bad_name_code; +int var_unk_name_code; +int var_unk_addr_code; +int var_relay_code; +int var_maps_rbl_code; +int var_map_reject_code; +int var_map_defer_code; +char *var_maps_rbl_domains; +char *var_rbl_reply_maps; +int var_helo_required; +int var_reject_code; +int var_defer_code; +int var_smtpd_err_sleep; +int var_non_fqdn_code; +char *var_bounce_rcpt; +char *var_error_rcpt; +int var_smtpd_delay_reject; +char *var_rest_classes; +int var_strict_rfc821_env; +bool var_disable_vrfy_cmd; +char *var_canonical_maps; +char *var_send_canon_maps; +char *var_rcpt_canon_maps; +char *var_virt_alias_maps; +char *var_virt_mailbox_maps; +char *var_alias_maps; +char *var_local_rcpt_maps; +bool var_allow_untrust_route; +int var_smtpd_junk_cmd_limit; +int var_smtpd_rcpt_overlim; +bool var_smtpd_sasl_enable; +bool var_smtpd_sasl_auth_hdr; +char *var_smtpd_sasl_opts; +char *var_smtpd_sasl_path; +char *var_smtpd_sasl_service; +char *var_cyrus_conf_path; +char *var_smtpd_sasl_realm; +int var_smtpd_sasl_resp_limit; +char *var_smtpd_sasl_exceptions_networks; +char *var_smtpd_sasl_type; +char *var_filter_xport; +bool var_broken_auth_clients; +char *var_perm_mx_networks; +char *var_smtpd_snd_auth_maps; +char *var_smtpd_noop_cmds; +char *var_smtpd_null_key; +int var_smtpd_hist_thrsh; +char *var_smtpd_exp_filter; +char *var_def_rbl_reply; +int var_unv_from_rcode; +int var_unv_rcpt_rcode; +int var_unv_from_dcode; +int var_unv_rcpt_dcode; +char *var_unv_from_why; +char *var_unv_rcpt_why; +int var_mul_rcpt_code; +char *var_relay_rcpt_maps; +int var_local_rcpt_code; +int var_virt_alias_code; +int var_virt_mailbox_code; +int var_relay_rcpt_code; +char *var_verp_clients; +int var_show_unk_rcpt_table; +int var_verify_poll_count; +int var_verify_poll_delay; +char *var_smtpd_proxy_filt; +int var_smtpd_proxy_tmout; +char *var_smtpd_proxy_ehlo; +char *var_smtpd_proxy_opts; +char *var_input_transp; +int var_smtpd_policy_tmout; +int var_smtpd_policy_req_limit; +int var_smtpd_policy_try_limit; +int var_smtpd_policy_try_delay; +char *var_smtpd_policy_def_action; +char *var_smtpd_policy_context; +int var_smtpd_policy_idle; +int var_smtpd_policy_ttl; +char *var_xclient_hosts; +char *var_xforward_hosts; +bool var_smtpd_rej_unl_from; +bool var_smtpd_rej_unl_rcpt; +char *var_smtpd_forbid_cmds; +int var_smtpd_crate_limit; +int var_smtpd_cconn_limit; +int var_smtpd_cmail_limit; +int var_smtpd_crcpt_limit; +int var_smtpd_cntls_limit; +int var_smtpd_cauth_limit; +char *var_smtpd_hoggers; +char *var_local_rwr_clients; +char *var_smtpd_ehlo_dis_words; +char *var_smtpd_ehlo_dis_maps; + +char *var_smtpd_tls_level; +bool var_smtpd_use_tls; +bool var_smtpd_enforce_tls; +bool var_smtpd_tls_wrappermode; +bool var_smtpd_tls_auth_only; +char *var_smtpd_cmd_filter; +char *var_smtpd_rej_footer; +char *var_smtpd_rej_ftr_maps; +char *var_smtpd_acl_perm_log; +char *var_smtpd_dns_re_filter; + +#ifdef USE_TLS +char *var_smtpd_relay_ccerts; +char *var_smtpd_sasl_tls_opts; +int var_smtpd_starttls_tmout; +char *var_smtpd_tls_CAfile; +char *var_smtpd_tls_CApath; +bool var_smtpd_tls_ask_ccert; +int var_smtpd_tls_ccert_vd; +char *var_smtpd_tls_cert_file; +char *var_smtpd_tls_mand_ciph; +char *var_smtpd_tls_excl_ciph; +char *var_smtpd_tls_mand_excl; +char *var_smtpd_tls_dcert_file; +char *var_smtpd_tls_dh1024_param_file; +char *var_smtpd_tls_dh512_param_file; +char *var_smtpd_tls_dkey_file; +char *var_smtpd_tls_key_file; +char *var_smtpd_tls_loglevel; +char *var_smtpd_tls_mand_proto; +bool var_smtpd_tls_received_header; +bool var_smtpd_tls_req_ccert; +bool var_smtpd_tls_set_sessid; +char *var_smtpd_tls_fpt_dgst; +char *var_smtpd_tls_ciph; +char *var_smtpd_tls_proto; +char *var_smtpd_tls_eecdh; +char *var_smtpd_tls_eccert_file; +char *var_smtpd_tls_eckey_file; +char *var_smtpd_tls_chain_files; + +#endif + +bool var_smtpd_peername_lookup; +int var_plaintext_code; +bool var_smtpd_delay_open; +char *var_smtpd_milters; +char *var_smtpd_milter_maps; +int var_milt_conn_time; +int var_milt_cmd_time; +int var_milt_msg_time; +char *var_milt_protocol; +char *var_milt_def_action; +char *var_milt_daemon_name; +char *var_milt_v; +char *var_milt_conn_macros; +char *var_milt_helo_macros; +char *var_milt_mail_macros; +char *var_milt_rcpt_macros; +char *var_milt_data_macros; +char *var_milt_eoh_macros; +char *var_milt_eod_macros; +char *var_milt_unk_macros; +char *var_milt_macro_deflts; +bool var_smtpd_client_port_log; +char *var_stress; + +char *var_reject_tmpf_act; +char *var_unk_name_tf_act; +char *var_unk_addr_tf_act; +char *var_unv_rcpt_tf_act; +char *var_unv_from_tf_act; +bool var_smtpd_rec_deadline; + +int smtpd_proxy_opts; + +#ifdef USE_TLSPROXY +char *var_tlsproxy_service; + +#endif + +char *var_smtpd_uproxy_proto; +int var_smtpd_uproxy_tmout; + + /* + * 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; + +#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>", state->sender); + if (state->recipient) + vstring_sprintf_append(buf, " to=<%s>", 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 (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; + + /* + * 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) { + 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; + 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 == '.' + && (proxy == 0 ? (++start, --len) == 0 : len == 1)) + break; + if (state->err == CLEANUP_STAT_OK) { + if (var_message_limit > 0 && 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 (why && IS_SMTP_REJECT(STR(why))) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", STR(why)); + } else if ((state->err & CLEANUP_STAT_DEFER) != 0) { + state->error_mask |= MAIL_ERROR_POLICY; + detail = cleanup_stat_detail(CLEANUP_STAT_DEFER); + if (why && LEN(why) > 0) { + /* Allow address-specific DSN status in header/body_checks. */ + smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why)); + } else { + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } + } else if ((state->err & CLEANUP_STAT_BAD) != 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + detail = cleanup_stat_detail(CLEANUP_STAT_BAD); + smtpd_chat_reply(state, "%d %s Error: internal error %d", + detail->smtp, detail->dsn, state->err); + } else if ((state->err & CLEANUP_STAT_SIZE) != 0) { + state->error_mask |= MAIL_ERROR_BOUNCE; + detail = cleanup_stat_detail(CLEANUP_STAT_SIZE); + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } else if ((state->err & CLEANUP_STAT_HOPS) != 0) { + state->error_mask |= MAIL_ERROR_BOUNCE; + detail = cleanup_stat_detail(CLEANUP_STAT_HOPS); + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } else if ((state->err & CLEANUP_STAT_CONT) != 0) { + state->error_mask |= MAIL_ERROR_POLICY; + detail = cleanup_stat_detail(CLEANUP_STAT_CONT); + if (proxy) { + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + } else if (why && LEN(why) > 0) { + /* Allow address-specific DSN status in header/body_checks. */ + smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why)); + } else { + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } + } else if ((state->err & CLEANUP_STAT_WRITE) != 0) { + state->error_mask |= MAIL_ERROR_RESOURCE; + detail = cleanup_stat_detail(CLEANUP_STAT_WRITE); + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } else if ((state->err & CLEANUP_STAT_PROXY) != 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + } else { + state->error_mask |= MAIL_ERROR_SOFTWARE; + detail = cleanup_stat_detail(CLEANUP_STAT_BAD); + smtpd_chat_reply(state, "%d %s Error: internal error %d", + detail->smtp, detail->dsn, state->err); + } + + /* + * By popular command: the proxy's end-of-data reply. + */ + if (proxy) + msg_info("proxy-%s: %s: %s;%s", + (state->err == CLEANUP_STAT_OK) ? "accept" : "reject", + state->where, STR(proxy->reply), smtpd_whatsup(state)); + + /* + * Cleanup. The client may send another MAIL command. + */ + saved_err = state->err; + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); + if (why) + vstring_free(why); + return (saved_err); +} + +/* skip_bdat - skip content and respond to BDAT error */ + +static int skip_bdat(SMTPD_STATE *state, off_t chunk_size, + bool final_chunk, const char *format,...) +{ + va_list ap; + off_t done; + off_t len; + + /* + * Read and discard content from the remote SMTP client. TODO: drop the + * connection in case of overload. + */ + for (done = 0; done < chunk_size; done += len) { + if ((len = chunk_size - done) > VSTREAM_BUFSIZE) + len = VSTREAM_BUFSIZE; + smtp_fread_buf(state->buffer, len, state->client); + } + + /* + * Send the response to the remote SMTP client. + */ + va_start(ap, format); + vsmtpd_chat_reply(state, format, ap); + va_end(ap); + + /* + * Reset state, or drop subsequent BDAT payloads until BDAT LAST or RSET. + */ + if (final_chunk) + mail_reset(state); + else + state->bdat_state = SMTPD_BDAT_STAT_ERROR; + return (-1); +} + +/* bdat_cmd - process BDAT command */ + +static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_PROXY *proxy; + const char *err; + off_t chunk_size; + bool final_chunk; + off_t done; + off_t read_len; + char *start; + int len; + int curr_rec_type; + int (*out_record) (VSTREAM *, int, const char *, ssize_t); + int (*out_fprintf) (VSTREAM *, int, const char *,...); + VSTREAM *out_stream; + int out_error; + + /* + * Hang up if the BDAT command is disabled. The next input would be raw + * message content and that would trigger lots of command errors. + */ + if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "521 5.5.1 Error: command not implemented"); + return (-1); + } + + /* + * Hang up if the BDAT command is malformed. The next input would be raw + * message content and that would trigger lots of command errors. + */ + if (argc < 2 || argc > 3 || !alldig(argv[1].strval) + || (chunk_size = off_cvt_string(argv[1].strval)) < 0 + || ((final_chunk = (argc == 3)) + && strcasecmp(argv[2].strval, "LAST") != 0)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + msg_warn("%s: malformed BDAT command syntax from %s: %.100s", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr, printable(vstring_str(state->buffer), '?')); + smtpd_chat_reply(state, "521 5.5.4 Syntax: BDAT count [LAST]"); + return (-1); + } + + /* + * Block abuse involving empty chunks (alternatively, we could count + * "BDAT 0" as a "NOOP", but then we would have to refactor the code that + * enforces the junk command limit). Clients that send a message as a + * sequence of "BDAT 1" should not be a problem: the Postfix BDAT + * implementation should be efficient enough to handle that. + */ + if (chunk_size == 0 && !final_chunk) { + msg_warn("%s: null BDAT request from %s", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr); + return skip_bdat(state, chunk_size, final_chunk, + "551 5.7.1 Null BDAT request"); + } + + /* + * BDAT commands may be pipelined within a MAIL transaction. After a BDAT + * request fails, keep accepting BDAT requests and skipping BDAT payloads + * to maintain synchronization with the remote SMTP client, until the + * client sends BDAT LAST or RSET. + */ + if (state->bdat_state == SMTPD_BDAT_STAT_ERROR) + return skip_bdat(state, chunk_size, final_chunk, + "551 5.0.0 Discarded %ld bytes after earlier error", + (long) chunk_size); + + /* + * Special handling for the first BDAT command in a MAIL transaction, + * treating it as a kind of "DATA" command for the purpose of policy + * evaluation. + */ + if (!SMTPD_PROCESSING_BDAT(state)) { + + /* + * With ESMTP command pipelining a client may send BDAT before the + * server has replied to all RCPT commands. For this reason we cannot + * treat BDAT without valid recipients as a protocol error. Worse, + * RFC 3030 does not discuss the role of BDAT commands in RFC 2920 + * command groups (batches of commands that may be sent without + * waiting for a response to each individual command). Therefore we + * have to allow for clients that pipeline the entire SMTP session + * after EHLO, including multiple MAIL transactions. + */ + if (state->rcpt_count == 0) { + if (!SMTPD_IN_MAIL_TRANSACTION(state)) { + /* TODO: maybe remove this from the DATA and BDAT handlers. */ + state->error_mask |= MAIL_ERROR_PROTOCOL; + return skip_bdat(state, chunk_size, final_chunk, + "503 5.5.1 Error: need RCPT command"); + } else { + return skip_bdat(state, chunk_size, final_chunk, + "554 5.5.1 Error: no valid recipients"); + } + } + if (SMTPD_STAND_ALONE(state) == 0 + && (err = smtpd_check_data(state)) != 0) { + return skip_bdat(state, chunk_size, final_chunk, "%s", err); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_data_event(state->milters)) != 0 + && (err = check_milter_reply(state, err)) != 0) { + return skip_bdat(state, chunk_size, final_chunk, "%s", err); + } + proxy = state->proxy; + if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE, + SMTPD_CMD_DATA) != 0) { + return skip_bdat(state, chunk_size, final_chunk, + "%s", STR(proxy->reply)); + } + } + /* Block too large chunks. */ + if (var_message_limit > 0 + && 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; + } + start = vstring_str(state->bdat_get_buffer); + len = VSTRING_LEN(state->bdat_get_buffer); + if (state->err == CLEANUP_STAT_OK) { + if (var_message_limit > 0 + && 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; + const char *bare_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) +#define UPDATE_STR(s, v) do { \ + const char *_v = (v); \ + if (s) myfree(s); \ + s = (_v) ? mystrdup(_v) : 0; \ + } while(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, '?'); + + /* + * 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; + bare_value = attr_value; + } else { + if ((bare_value = valid_mailhost_addr(attr_value, DONT_GRIPE)) == 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_STR(state->addr, bare_value); + UPDATE_STR(state->rfc_addr, attr_value); +#ifdef HAS_IPV6 + if (strncasecmp(attr_value, INET_PROTO_NAME_IPV6 ":", + sizeof(INET_PROTO_NAME_IPV6 ":") - 1) == 0) + state->addr_family = AF_INET6; + else +#endif + state->addr_family = AF_INET; + 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; + bare_value = attr_value; + } else { + if ((bare_value = valid_mailhost_addr(attr_value, DONT_GRIPE)) == 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); + } + } + UPDATE_STR(state->dest_addr, bare_value); + /* 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); + /* 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; + const char *bare_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; + bare_value = attr_value; + } else { + neuter(attr_value, NEUTER_CHARACTERS, '?'); + if ((bare_value = valid_mailhost_addr(attr_value, DONT_GRIPE)) == 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); + } + } + UPDATE_STR(state->xforward.addr, bare_value); + UPDATE_STR(state->xforward.rfc_addr, attr_value); + 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)) { + 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) { + + /* + * Fetch and reject the next command (should be EHLO), then + * disconnect (side-effect of returning "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"); + 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_proto - talk the SMTP protocol */ + +static void smtpd_proto(SMTPD_STATE *state) +{ + int argc; + SMTPD_TOKEN *argv; + SMTPD_CMD *cmdp; + const char *ehlo_words; + const char *err; + int status; + const char *cp; + +#ifdef USE_TLS + int tls_rate; + +#endif + + /* + * Print a greeting banner and run the state machine. Read SMTP commands + * one line at a time. According to the standard, a sender or recipient + * address could contain an escaped newline. I think this is perverse, + * and anyone depending on this is really asking for trouble. + * + * In case of mail protocol trouble, the program jumps back to this place, + * so that it can perform the necessary cleanup before talking to the + * next client. The setjmp/longjmp primitives are like a sharp tool: use + * with care. I would certainly recommend against the use of + * setjmp/longjmp in programs that change privilege levels. + * + * In case of file system trouble the program terminates after logging the + * error and after informing the client. In all other cases (out of + * memory, panic) the error is logged, and the msg_cleanup() exit handler + * cleans up, but no attempt is made to inform the client of the nature + * of the problem. + */ + smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_rec_deadline); + + while ((status = vstream_setjmp(state->client)) == SMTP_ERR_NONE) + /* void */ ; + switch (status) { + + default: + msg_panic("smtpd_proto: unknown error reading from %s", + state->namaddr); + break; + + case SMTP_ERR_TIME: + state->reason = REASON_TIMEOUT; + if (vstream_setjmp(state->client) == 0) + smtpd_chat_reply(state, "421 4.4.2 %s Error: timeout exceeded", + var_myhostname); + break; + + case SMTP_ERR_EOF: + state->reason = REASON_LOST_CONNECTION; + break; + + case SMTP_ERR_QUIET: + break; + + case SMTP_ERR_DATA: + msg_info("%s: reject: %s from %s: " + "421 4.3.0 %s Server local data error", + (state->queue_id ? state->queue_id : "NOQUEUE"), + state->where, state->namaddr, var_myhostname); + state->error_mask |= MAIL_ERROR_DATA; + if (vstream_setjmp(state->client) == 0) + smtpd_chat_reply(state, "421 4.3.0 %s Server local data error", + var_myhostname); + break; + + case 0: + + /* + * 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 + + /* + * XXX The client connection count/rate control must be consistent in + * its use of client address information in connect and disconnect + * events. For now we exclude xclient authorized hosts from + * connection count/rate control. + * + * XXX Must send connect/disconnect events to the anvil server even when + * this service is not connection count or rate limited, otherwise it + * will discard client message or recipient rate information too + * early or too late. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_connect(anvil_clnt, state->service, state->addr, + &state->conn_count, &state->conn_rate) + == ANVIL_STAT_OK) { + if (var_smtpd_cconn_limit > 0 + && state->conn_count > var_smtpd_cconn_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Connection concurrency limit exceeded: %d from %s for service %s", + state->conn_count, state->namaddr, state->service); + smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s", + var_myhostname, state->addr); + break; + } + if (var_smtpd_crate_limit > 0 + && state->conn_rate > var_smtpd_crate_limit) { + msg_warn("Connection rate limit exceeded: %d from %s for service %s", + state->conn_rate, state->namaddr, state->service); + smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s", + var_myhostname, state->addr); + break; + } + } + + /* + * Determine what server ESMTP features to suppress, typically to + * avoid inter-operability problems. Moved up so we don't send 421 + * immediately after sending the initial server response. + */ + if (ehlo_discard_maps == 0 + || (ehlo_words = maps_find(ehlo_discard_maps, state->addr, 0)) == 0) + ehlo_words = var_smtpd_ehlo_dis_words; + state->ehlo_discard_mask = ehlo_mask(ehlo_words); + + /* XXX We use the real client for connect access control. */ + if (SMTPD_STAND_ALONE(state) == 0 + && var_smtpd_delay_reject == 0 + && (err = smtpd_check_client(state)) != 0) { + state->error_mask |= MAIL_ERROR_POLICY; + state->access_denied = mystrdup(err); + smtpd_chat_reply(state, "%s", state->access_denied); + state->error_count++; + } + + /* + * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses + * other than the initial greeting and any response to HELO or EHLO + * are prefaced with a status code as defined in RFC 3463. + */ + + /* + * XXX If a Milter rejects CONNECT, reply with 220 except in case of + * hard reject or 421 (disconnect). The reply persists so it will + * apply to MAIL FROM and to other commands such as AUTH, STARTTLS, + * and VRFY. Note: after a Milter CONNECT reject, we must not reject + * HELO or EHLO, but we do change the feature list that is announced + * in the EHLO response. + */ + else { + err = 0; + if (state->milters != 0) { + milter_macro_callback(state->milters, smtpd_milter_eval, + (void *) state); + if ((err = milter_conn_event(state->milters, state->name, + state->addr, + strcmp(state->port, CLIENT_PORT_UNKNOWN) ? + state->port : "0", + state->addr_family)) != 0) + err = check_milter_reply(state, err); + } + if (err && err[0] == '5') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "554 %s ESMTP not accepting connections", + var_myhostname); + state->error_count++; + } else if (err && strncmp(err, "421", 3) == 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "421 %s Service unavailable - try again later", + var_myhostname); + /* Not: state->error_count++; */ + } else { + smtpd_chat_reply(state, "220 %s", var_smtpd_banner); + } + } + + /* + * SASL initialization for plaintext mode. + * + * XXX Backwards compatibility: allow AUTH commands when the AUTH + * announcement is suppressed via smtpd_sasl_exceptions_networks. + * + * XXX Safety: don't enable SASL with "smtpd_tls_auth_only = yes" and + * non-TLS build. + */ +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable && smtpd_sasl_is_active(state) == 0 +#ifdef USE_TLS + && state->tls_context == 0 && !var_smtpd_tls_auth_only +#else + && var_smtpd_tls_auth_only == 0 +#endif + ) + smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS, + var_smtpd_sasl_opts); +#endif + + /* + * The command read/execute loop. + */ + for (;;) { + if (state->flags & SMTPD_FLAG_HANGUP) + break; + if (state->error_count >= var_smtpd_hard_erlim) { + state->reason = REASON_ERROR_LIMIT; + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "421 4.7.0 %s Error: too many errors", + var_myhostname); + break; + } + watchdog_pat(); + smtpd_chat_query(state); + /* Safety: protect internal interfaces against malformed UTF-8. */ + if (var_smtputf8_enable && valid_utf8_string(STR(state->buffer), + LEN(state->buffer)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "500 5.5.2 Error: bad UTF-8 syntax"); + state->error_count++; + continue; + } + /* Move into smtpd_chat_query() and update session transcript. */ + if (smtpd_cmd_filter != 0) { + for (cp = STR(state->buffer); *cp && IS_SPACE_TAB(*cp); cp++) + /* void */ ; + if ((cp = dict_get(smtpd_cmd_filter, cp)) != 0) { + msg_info("%s: replacing command \"%.100s\" with \"%.100s\"", + state->namaddr, STR(state->buffer), cp); + vstring_strcpy(state->buffer, cp); + } else if (smtpd_cmd_filter->error != 0) { + msg_warn("%s:%s lookup error for \"%.100s\"", + smtpd_cmd_filter->type, smtpd_cmd_filter->name, + printable(STR(state->buffer), '?')); + vstream_longjmp(state->client, SMTP_ERR_DATA); + } + } + if ((argc = smtpd_token(vstring_str(state->buffer), &argv)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "500 5.5.2 Error: bad syntax"); + state->error_count++; + continue; + } + /* Ignore smtpd_noop_cmds lookup errors. Non-critical feature. */ + if (*var_smtpd_noop_cmds + && string_list_match(smtpd_noop_cmds, argv[0].strval)) { + smtpd_chat_reply(state, "250 2.0.0 Ok"); + if (state->junk_cmds++ > var_smtpd_junk_cmd_limit) + state->error_count++; + continue; + } + for (cmdp = smtpd_cmd_table; cmdp->name != 0; cmdp++) + if (strcasecmp(argv[0].strval, cmdp->name) == 0) + break; + cmdp->total_count += 1; + /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */ + if (cmdp->name == 0) { + state->where = SMTPD_CMD_UNKNOWN; + if (is_header(argv[0].strval) + || (*var_smtpd_forbid_cmds + && string_list_match(smtpd_forbid_cmds, argv[0].strval))) { + msg_warn("non-SMTP command from %s: %.100s", + state->namaddr, vstring_str(state->buffer)); + smtpd_chat_reply(state, "221 2.7.0 Error: I can break rules, too. Goodbye."); + break; + } + } + /* XXX We use the real client for connect access control. */ + if (state->access_denied && cmdp->action != quit_cmd) { + /* XXX Exception for Milter override. */ + if (strncmp(state->access_denied + 1, "21", 2) == 0) { + smtpd_chat_reply(state, "%s", state->access_denied); + continue; + } + smtpd_chat_reply(state, "503 5.7.0 Error: access denied for %s", + state->namaddr); /* RFC 2821 Sec 3.1 */ + state->error_count++; + continue; + } + /* state->access_denied == 0 || cmdp->action == quit_cmd */ + if (cmdp->name == 0) { + if (state->milters != 0 + && (err = milter_unknown_event(state->milters, + argv[0].strval)) != 0 + && (err = check_milter_reply(state, err)) != 0) { + smtpd_chat_reply(state, "%s", err); + } else + smtpd_chat_reply(state, "502 5.5.2 Error: command not recognized"); + state->error_mask |= MAIL_ERROR_PROTOCOL; + state->error_count++; + continue; + } +#ifdef USE_TLS + if (var_smtpd_enforce_tls && + !state->tls_context && + (cmdp->flags & SMTPD_CMD_FLAG_PRE_TLS) == 0) { + smtpd_chat_reply(state, + "530 5.7.0 Must issue a STARTTLS command first"); + state->error_count++; + continue; + } +#endif + state->where = cmdp->name; + if (SMTPD_STAND_ALONE(state) == 0 + && (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0 + || (cmdp->flags & SMTPD_CMD_FLAG_LAST)) + && (state->flags & SMTPD_FLAG_ILL_PIPELINING) == 0 + && (vstream_peek(state->client) > 0 + || peekfd(vstream_fileno(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", + cmdp->name, state->namaddr, STR(state->expand_buf)); + state->flags |= SMTPD_FLAG_ILL_PIPELINING; + } + 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. + */ + 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); + + /* + * 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. + */ + if ((state.flags & SMTPD_FLAG_HANGUP) == 0) + smtpd_proto(&state); + + /* + * After the client has gone away, clean up whatever we have set up at + * connection time. + */ + msg_info("disconnect from %s%s", state.namaddr, + smtpd_format_cmd_stats(state.buffer)); + teardown_milters(&state); /* duplicates xclient_cmd */ + smtpd_state_reset(&state); + debug_peer_restore(); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Initialize blacklist/etc. patterns before entering the chroot jail, in + * case they specify a filename pattern. + */ + smtpd_noop_cmds = string_list_init(VAR_SMTPD_NOOP_CMDS, MATCH_FLAG_RETURN, + var_smtpd_noop_cmds); + smtpd_forbid_cmds = string_list_init(VAR_SMTPD_FORBID_CMDS, + MATCH_FLAG_RETURN, + var_smtpd_forbid_cmds); + verp_clients = namadr_list_init(VAR_VERP_CLIENTS, MATCH_FLAG_RETURN, + var_verp_clients); + xclient_hosts = namadr_list_init(VAR_XCLIENT_HOSTS, MATCH_FLAG_RETURN, + var_xclient_hosts); + xforward_hosts = namadr_list_init(VAR_XFORWARD_HOSTS, MATCH_FLAG_RETURN, + var_xforward_hosts); + hogger_list = namadr_list_init(VAR_SMTPD_HOGGERS, MATCH_FLAG_RETURN + | match_parent_style(VAR_SMTPD_HOGGERS), + var_smtpd_hoggers); + + /* + * 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 (var_queue_minfree > 0 + && var_message_limit > 0 + && var_queue_minfree / 1.5 < var_message_limit) + msg_warn("%s(%lu) should be at least 1.5*%s(%lu)", + VAR_QUEUE_MINFREE, (unsigned long) var_queue_minfree, + VAR_MESSAGE_LIMIT, (unsigned long) var_message_limit); + + /* + * Connection rate management. + */ + if (var_smtpd_crate_limit || var_smtpd_cconn_limit + || var_smtpd_cmail_limit || var_smtpd_crcpt_limit + || var_smtpd_cntls_limit || var_smtpd_cauth_limit) + anvil_clnt = anvil_clnt_create(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static const CONFIG_NINT_TABLE nint_table[] = { + VAR_SMTPD_SOFT_ERLIM, DEF_SMTPD_SOFT_ERLIM, &var_smtpd_soft_erlim, 1, 0, + VAR_SMTPD_HARD_ERLIM, DEF_SMTPD_HARD_ERLIM, &var_smtpd_hard_erlim, 1, 0, + VAR_SMTPD_JUNK_CMD, DEF_SMTPD_JUNK_CMD, &var_smtpd_junk_cmd_limit, 1, 0, + VAR_VERIFY_POLL_COUNT, DEF_VERIFY_POLL_COUNT, &var_verify_poll_count, 1, 0, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_SMTPD_RCPT_LIMIT, DEF_SMTPD_RCPT_LIMIT, &var_smtpd_rcpt_limit, 1, 0, + VAR_QUEUE_MINFREE, DEF_QUEUE_MINFREE, &var_queue_minfree, 0, 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_CRATE_LIMIT, DEF_SMTPD_CRATE_LIMIT, &var_smtpd_crate_limit, 0, 0, + VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0, + VAR_SMTPD_CMAIL_LIMIT, DEF_SMTPD_CMAIL_LIMIT, &var_smtpd_cmail_limit, 0, 0, + VAR_SMTPD_CRCPT_LIMIT, DEF_SMTPD_CRCPT_LIMIT, &var_smtpd_crcpt_limit, 0, 0, + VAR_SMTPD_CNTLS_LIMIT, DEF_SMTPD_CNTLS_LIMIT, &var_smtpd_cntls_limit, 0, 0, + VAR_SMTPD_CAUTH_LIMIT, DEF_SMTPD_CAUTH_LIMIT, &var_smtpd_cauth_limit, 0, 0, +#ifdef USE_TLS + VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, +#endif + VAR_SMTPD_SASL_RESP_LIMIT, DEF_SMTPD_SASL_RESP_LIMIT, &var_smtpd_sasl_resp_limit, DEF_SMTPD_SASL_RESP_LIMIT, 0, + VAR_SMTPD_POLICY_REQ_LIMIT, DEF_SMTPD_POLICY_REQ_LIMIT, &var_smtpd_policy_req_limit, 0, 0, + VAR_SMTPD_POLICY_TRY_LIMIT, DEF_SMTPD_POLICY_TRY_LIMIT, &var_smtpd_policy_try_limit, 1, 0, + 0, + }; + static const CONFIG_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, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_SMTPD_REC_DEADLINE, DEF_SMTPD_REC_DEADLINE, &var_smtpd_rec_deadline, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0, + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_CLIENT_CHECKS, DEF_CLIENT_CHECKS, &var_client_checks, 0, 0, + VAR_HELO_CHECKS, DEF_HELO_CHECKS, &var_helo_checks, 0, 0, + VAR_MAIL_CHECKS, DEF_MAIL_CHECKS, &var_mail_checks, 0, 0, + VAR_RELAY_CHECKS, DEF_RELAY_CHECKS, &var_relay_checks, 0, 0, + VAR_RCPT_CHECKS, DEF_RCPT_CHECKS, &var_rcpt_checks, 0, 0, + VAR_ETRN_CHECKS, DEF_ETRN_CHECKS, &var_etrn_checks, 0, 0, + VAR_DATA_CHECKS, DEF_DATA_CHECKS, &var_data_checks, 0, 0, + VAR_EOD_CHECKS, DEF_EOD_CHECKS, &var_eod_checks, 0, 0, + VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains, 0, 0, + VAR_RBL_REPLY_MAPS, DEF_RBL_REPLY_MAPS, &var_rbl_reply_maps, 0, 0, + VAR_BOUNCE_RCPT, DEF_ERROR_RCPT, &var_bounce_rcpt, 1, 0, + VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, + VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0, + VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0, + VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0, + VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0, + VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0, + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, + VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0, + VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0, + VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts, 0, 0, + VAR_SMTPD_SASL_PATH, DEF_SMTPD_SASL_PATH, &var_smtpd_sasl_path, 1, 0, + VAR_SMTPD_SASL_SERVICE, DEF_SMTPD_SASL_SERVICE, &var_smtpd_sasl_service, 1, 0, + VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0, + VAR_SMTPD_SASL_REALM, DEF_SMTPD_SASL_REALM, &var_smtpd_sasl_realm, 0, 0, + VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS, DEF_SMTPD_SASL_EXCEPTIONS_NETWORKS, &var_smtpd_sasl_exceptions_networks, 0, 0, + VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0, + VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks, 0, 0, + VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0, + VAR_SMTPD_NOOP_CMDS, DEF_SMTPD_NOOP_CMDS, &var_smtpd_noop_cmds, 0, 0, + VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0, + VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key, 0, 0, + VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0, + VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender, 0, 0, + VAR_VERP_CLIENTS, DEF_VERP_CLIENTS, &var_verp_clients, 0, 0, + VAR_SMTPD_PROXY_FILT, DEF_SMTPD_PROXY_FILT, &var_smtpd_proxy_filt, 0, 0, + VAR_SMTPD_PROXY_EHLO, DEF_SMTPD_PROXY_EHLO, &var_smtpd_proxy_ehlo, 0, 0, + VAR_SMTPD_PROXY_OPTS, DEF_SMTPD_PROXY_OPTS, &var_smtpd_proxy_opts, 0, 0, + VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0, + VAR_XCLIENT_HOSTS, DEF_XCLIENT_HOSTS, &var_xclient_hosts, 0, 0, + VAR_XFORWARD_HOSTS, DEF_XFORWARD_HOSTS, &var_xforward_hosts, 0, 0, + VAR_SMTPD_HOGGERS, DEF_SMTPD_HOGGERS, &var_smtpd_hoggers, 0, 0, + VAR_LOC_RWR_CLIENTS, DEF_LOC_RWR_CLIENTS, &var_local_rwr_clients, 0, 0, + VAR_SMTPD_EHLO_DIS_WORDS, DEF_SMTPD_EHLO_DIS_WORDS, &var_smtpd_ehlo_dis_words, 0, 0, + VAR_SMTPD_EHLO_DIS_MAPS, DEF_SMTPD_EHLO_DIS_MAPS, &var_smtpd_ehlo_dis_maps, 0, 0, +#ifdef USE_TLS + VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts, 0, 0, + VAR_SMTPD_SASL_TLS_OPTS, DEF_SMTPD_SASL_TLS_OPTS, &var_smtpd_sasl_tls_opts, 0, 0, + VAR_SMTPD_TLS_CHAIN_FILES, DEF_SMTPD_TLS_CHAIN_FILES, &var_smtpd_tls_chain_files, 0, 0, + VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0, + VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0, + VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0, + VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0, + VAR_SMTPD_TLS_ECCERT_FILE, DEF_SMTPD_TLS_ECCERT_FILE, &var_smtpd_tls_eccert_file, 0, 0, + VAR_SMTPD_TLS_ECKEY_FILE, DEF_SMTPD_TLS_ECKEY_FILE, &var_smtpd_tls_eckey_file, 0, 0, + VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0, + VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0, + VAR_SMTPD_TLS_CIPH, DEF_SMTPD_TLS_CIPH, &var_smtpd_tls_ciph, 1, 0, + VAR_SMTPD_TLS_MAND_CIPH, DEF_SMTPD_TLS_MAND_CIPH, &var_smtpd_tls_mand_ciph, 1, 0, + VAR_SMTPD_TLS_EXCL_CIPH, DEF_SMTPD_TLS_EXCL_CIPH, &var_smtpd_tls_excl_ciph, 0, 0, + VAR_SMTPD_TLS_MAND_EXCL, DEF_SMTPD_TLS_MAND_EXCL, &var_smtpd_tls_mand_excl, 0, 0, + VAR_SMTPD_TLS_PROTO, DEF_SMTPD_TLS_PROTO, &var_smtpd_tls_proto, 0, 0, + VAR_SMTPD_TLS_MAND_PROTO, DEF_SMTPD_TLS_MAND_PROTO, &var_smtpd_tls_mand_proto, 0, 0, + VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0, + VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0, + VAR_SMTPD_TLS_EECDH, DEF_SMTPD_TLS_EECDH, &var_smtpd_tls_eecdh, 1, 0, + VAR_SMTPD_TLS_FPT_DGST, DEF_SMTPD_TLS_FPT_DGST, &var_smtpd_tls_fpt_dgst, 1, 0, + VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0, +#endif + VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0, + VAR_SMTPD_SASL_TYPE, DEF_SMTPD_SASL_TYPE, &var_smtpd_sasl_type, 1, 0, + VAR_SMTPD_MILTERS, DEF_SMTPD_MILTERS, &var_smtpd_milters, 0, 0, + VAR_MILT_CONN_MACROS, DEF_MILT_CONN_MACROS, &var_milt_conn_macros, 0, 0, + VAR_MILT_HELO_MACROS, DEF_MILT_HELO_MACROS, &var_milt_helo_macros, 0, 0, + VAR_MILT_MAIL_MACROS, DEF_MILT_MAIL_MACROS, &var_milt_mail_macros, 0, 0, + VAR_MILT_RCPT_MACROS, DEF_MILT_RCPT_MACROS, &var_milt_rcpt_macros, 0, 0, + VAR_MILT_DATA_MACROS, DEF_MILT_DATA_MACROS, &var_milt_data_macros, 0, 0, + VAR_MILT_EOH_MACROS, DEF_MILT_EOH_MACROS, &var_milt_eoh_macros, 0, 0, + VAR_MILT_EOD_MACROS, DEF_MILT_EOD_MACROS, &var_milt_eod_macros, 0, 0, + VAR_MILT_UNK_MACROS, DEF_MILT_UNK_MACROS, &var_milt_unk_macros, 0, 0, + VAR_MILT_PROTOCOL, DEF_MILT_PROTOCOL, &var_milt_protocol, 1, 0, + VAR_MILT_DEF_ACTION, DEF_MILT_DEF_ACTION, &var_milt_def_action, 1, 0, + VAR_MILT_DAEMON_NAME, DEF_MILT_DAEMON_NAME, &var_milt_daemon_name, 1, 0, + VAR_MILT_V, DEF_MILT_V, &var_milt_v, 1, 0, + VAR_MILT_MACRO_DEFLTS, DEF_MILT_MACRO_DEFLTS, &var_milt_macro_deflts, 0, 0, + VAR_SMTPD_MILTER_MAPS, DEF_SMTPD_MILTER_MAPS, &var_smtpd_milter_maps, 0, 0, + VAR_STRESS, DEF_STRESS, &var_stress, 0, 0, + VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why, 0, 0, + VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why, 0, 0, + VAR_REJECT_TMPF_ACT, DEF_REJECT_TMPF_ACT, &var_reject_tmpf_act, 1, 0, + VAR_UNK_NAME_TF_ACT, DEF_UNK_NAME_TF_ACT, &var_unk_name_tf_act, 1, 0, + VAR_UNK_ADDR_TF_ACT, DEF_UNK_ADDR_TF_ACT, &var_unk_addr_tf_act, 1, 0, + VAR_UNV_RCPT_TF_ACT, DEF_UNV_RCPT_TF_ACT, &var_unv_rcpt_tf_act, 1, 0, + VAR_UNV_FROM_TF_ACT, DEF_UNV_FROM_TF_ACT, &var_unv_from_tf_act, 1, 0, + VAR_SMTPD_CMD_FILTER, DEF_SMTPD_CMD_FILTER, &var_smtpd_cmd_filter, 0, 0, +#ifdef USE_TLSPROXY + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, +#endif + VAR_SMTPD_ACL_PERM_LOG, DEF_SMTPD_ACL_PERM_LOG, &var_smtpd_acl_perm_log, 0, 0, + VAR_SMTPD_UPROXY_PROTO, DEF_SMTPD_UPROXY_PROTO, &var_smtpd_uproxy_proto, 0, 0, + VAR_SMTPD_POLICY_DEF_ACTION, DEF_SMTPD_POLICY_DEF_ACTION, &var_smtpd_policy_def_action, 1, 0, + VAR_SMTPD_POLICY_CONTEXT, DEF_SMTPD_POLICY_CONTEXT, &var_smtpd_policy_context, 0, 0, + VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter, 0, 0, + VAR_SMTPD_REJ_FTR_MAPS, DEF_SMTPD_REJ_FTR_MAPS, &var_smtpd_rej_ftr_maps, 0, 0, + 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_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..0b1d87b --- /dev/null +++ b/src/smtpd/smtpd.h @@ -0,0 +1,440 @@ +/*++ +/* 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 int smtpd_peer_from_haproxy(SMTPD_STATE *state); + +#define SMTPD_PEER_CODE_OK 2 +#define SMTPD_PEER_CODE_TEMP 4 +#define SMTPD_PEER_CODE_PERM 5 +#define SMTPD_PEER_CODE_FORGED 6 + + /* + * Construct name[addr] or name[addr]:port as appropriate + */ +#define SMTPD_BUILD_NAMADDRPORT(name, addr, port) \ + concatenate((name), "[", (addr), "]", \ + var_smtpd_client_port_log ? ":" : (char *) 0, \ + (port), (char *) 0) + + /* + * Don't mix information from the current SMTP session with forwarded + * information from an up-stream session. + */ +#define HAVE_FORWARDED_CLIENT_ATTR(s) \ + ((s)->xforward.flags & SMTPD_STATE_XFORWARD_CLIENT_MASK) + +#define FORWARD_CLIENT_ATTR(s, a) \ + (HAVE_FORWARDED_CLIENT_ATTR(s) ? \ + (s)->xforward.a : (s)->a) + +#define FORWARD_ADDR(s) FORWARD_CLIENT_ATTR((s), rfc_addr) +#define FORWARD_NAME(s) FORWARD_CLIENT_ATTR((s), name) +#define FORWARD_NAMADDR(s) FORWARD_CLIENT_ATTR((s), namaddr) +#define FORWARD_PROTO(s) FORWARD_CLIENT_ATTR((s), protocol) +#define FORWARD_HELO(s) FORWARD_CLIENT_ATTR((s), helo_name) +#define FORWARD_PORT(s) FORWARD_CLIENT_ATTR((s), port) + + /* + * Mixing is not a problem with forwarded local message identifiers. + */ +#define HAVE_FORWARDED_IDENT(s) \ + ((s)->xforward.ident != 0) + +#define FORWARD_IDENT(s) \ + (HAVE_FORWARDED_IDENT(s) ? \ + (s)->xforward.ident : (s)->queue_id) + + /* + * Mixing is not a problem with forwarded address rewriting contexts. + */ +#define FORWARD_DOMAIN(s) \ + (((s)->xforward.flags & SMTPD_STATE_XFORWARD_DOMAIN) ? \ + (s)->xforward.domain : (s)->rewrite_context) + +extern void smtpd_xforward_init(SMTPD_STATE *); +extern void smtpd_xforward_preset(SMTPD_STATE *); +extern void smtpd_xforward_reset(SMTPD_STATE *); + + /* + * Transparency: before mail is queued, do we check for unknown recipients, + * do we allow address mapping, automatic bcc, header/body checks? + */ +extern int smtpd_input_transp_mask; + + /* + * More Milter support. + */ +extern MILTERS *smtpd_milters; + + /* + * Message size multiplication factor for free space check. + */ +extern double smtpd_space_multf; + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ diff --git a/src/smtpd/smtpd_acl.in b/src/smtpd/smtpd_acl.in new file mode 100644 index 0000000..45d1d4f --- /dev/null +++ b/src/smtpd/smtpd_acl.in @@ -0,0 +1,120 @@ +# +# Initialize +# +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +relay_domains porcupine.org +smtpd_null_access_lookup_key <> +# +# Test check_domain_access() +# +helo_restrictions hash:./smtpd_check_access +# Expect: REJECT +helo foo.dunno.com +# Expect: OK +helo bar.dunno.com +# Expect: OK +helo foo.duuno.com +# +# Test check_namadr_access(), domain part +# +client_restrictions hash:./smtpd_check_access +# Expect: REJECT +client foo.dunno.com 131.155.210.17 +# Expect: OK +client bar.dunno.com 131.155.210.17 +# Expect: OK +client bar.dunno.com 131.155.210.19 +# +# Test check_namadr_access(), address part +# +# Expect: OK +client bar.duno.com 131.155.210.17 +# Expect: REJECT +client bar.duno.com 131.155.210.19 +# Expect: REJECT +client bar.duno.com 44.33.22.11 +# Expect: OK +client bar.duno.com 44.33.22.55 +# Expect: REJECT +client bar.duno.com 44.33.44.33 +# +# Test check_mail_access() +# +sender_restrictions hash:./smtpd_check_access +# Expect: REJECT +mail reject@dunno.domain +# Expect: OK +mail ok@dunno.domain +# Expect: OK +mail anyone@dunno.domain +# Expect: OK +mail bad-sender@dunno.domain +# +# Again, with a domain that rejects by default +# +# Expect: REJECT +mail reject@reject.domain +# Expect: OK +mail ok@reject.domain +# Expect: REJECT +mail anyone@reject.domain +# Expect: REJECT +mail good-sender@reject.domain +# +# Again, with a domain that accepts by default +# +# Expect: REJECT +mail reject@ok.domain +# Expect: OK +mail ok@ok.domain +# Expect: OK +mail anyone@ok.domain +# Expect: OK +mail bad-sender@ok.domain +# +# Test check_mail_access() +# +recipient_restrictions hash:./smtpd_check_access +# Expect: REJECT +rcpt reject@dunno.domain +# Expect: REJECT +recipient_delimiter + +rcpt reject+ext@dunno.domain +recipient_delimiter | +# Expect: OK +rcpt ok@dunno.domain +# Expect: OK +recipient_delimiter + +rcpt ok+ext@dunno.domain +recipient_delimiter | +# Expect: OK +rcpt anyone@dunno.domain +# Expect: OK +rcpt bad-sender@dunno.domain +# +# Again, with a domain that rejects by default +# +# Expect: REJECT +rcpt reject@reject.domain +# Expect: OK +rcpt ok@reject.domain +# Expect: REJECT +rcpt anyone@reject.domain +# Expect: REJECT +rcpt good-sender@reject.domain +# +# Again, with a domain that accepts by default +# +# Expect: REJECT +rcpt reject@ok.domain +# Expect: OK +rcpt ok@ok.domain +# Expect: OK +rcpt anyone@ok.domain +# Expect: OK +rcpt bad-sender@ok.domain +# +# check_sender_access specific +# +mail <> diff --git a/src/smtpd/smtpd_acl.ref b/src/smtpd/smtpd_acl.ref new file mode 100644 index 0000000..110e7a5 --- /dev/null +++ b/src/smtpd/smtpd_acl.ref @@ -0,0 +1,187 @@ +>>> # +>>> # Initialize +>>> # +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.189.0/28 +OK +>>> relay_domains porcupine.org +OK +>>> smtpd_null_access_lookup_key <> +OK +>>> # +>>> # Test check_domain_access() +>>> # +>>> helo_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> helo foo.dunno.com +./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 554 5.7.1 <foo.dunno.com>: Helo command rejected: Access denied; proto=SMTP helo=<foo.dunno.com> +554 5.7.1 <foo.dunno.com>: Helo command rejected: Access denied +>>> # Expect: OK +>>> helo bar.dunno.com +OK +>>> # Expect: OK +>>> helo foo.duuno.com +OK +>>> # +>>> # Test check_namadr_access(), domain part +>>> # +>>> client_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> client foo.dunno.com 131.155.210.17 +./smtpd_check: <queue id>: reject: CONNECT from foo.dunno.com[131.155.210.17]: 554 5.7.1 <foo.dunno.com[131.155.210.17]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <foo.dunno.com[131.155.210.17]>: Client host rejected: Access denied +>>> # Expect: OK +>>> client bar.dunno.com 131.155.210.17 +OK +>>> # Expect: OK +>>> client bar.dunno.com 131.155.210.19 +OK +>>> # +>>> # Test check_namadr_access(), address part +>>> # +>>> # Expect: OK +>>> client bar.duno.com 131.155.210.17 +OK +>>> # Expect: REJECT +>>> client bar.duno.com 131.155.210.19 +./smtpd_check: <queue id>: reject: CONNECT from bar.duno.com[131.155.210.19]: 554 5.7.1 <bar.duno.com[131.155.210.19]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <bar.duno.com[131.155.210.19]>: Client host rejected: Access denied +>>> # Expect: REJECT +>>> client bar.duno.com 44.33.22.11 +./smtpd_check: <queue id>: reject: CONNECT from bar.duno.com[44.33.22.11]: 554 5.7.1 <bar.duno.com[44.33.22.11]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <bar.duno.com[44.33.22.11]>: Client host rejected: Access denied +>>> # Expect: OK +>>> client bar.duno.com 44.33.22.55 +OK +>>> # Expect: REJECT +>>> client bar.duno.com 44.33.44.33 +./smtpd_check: <queue id>: reject: CONNECT from bar.duno.com[44.33.44.33]: 554 5.7.1 <bar.duno.com[44.33.44.33]>: Client host rejected: Access denied; proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <bar.duno.com[44.33.44.33]>: Client host rejected: Access denied +>>> # +>>> # Test check_mail_access() +>>> # +>>> sender_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> mail reject@dunno.domain +./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@dunno.domain>: Sender address rejected: Access denied; from=<reject@dunno.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <reject@dunno.domain>: Sender address rejected: Access denied +>>> # Expect: OK +>>> mail ok@dunno.domain +OK +>>> # Expect: OK +>>> mail anyone@dunno.domain +OK +>>> # Expect: OK +>>> mail bad-sender@dunno.domain +OK +>>> # +>>> # Again, with a domain that rejects by default +>>> # +>>> # Expect: REJECT +>>> mail reject@reject.domain +./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@reject.domain>: Sender address rejected: Access denied; from=<reject@reject.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <reject@reject.domain>: Sender address rejected: Access denied +>>> # Expect: OK +>>> mail ok@reject.domain +OK +>>> # Expect: REJECT +>>> mail anyone@reject.domain +./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <anyone@reject.domain>: Sender address rejected: Access denied; from=<anyone@reject.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <anyone@reject.domain>: Sender address rejected: Access denied +>>> # Expect: REJECT +>>> mail good-sender@reject.domain +./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <good-sender@reject.domain>: Sender address rejected: Access denied; from=<good-sender@reject.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <good-sender@reject.domain>: Sender address rejected: Access denied +>>> # +>>> # Again, with a domain that accepts by default +>>> # +>>> # Expect: REJECT +>>> mail reject@ok.domain +./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@ok.domain>: Sender address rejected: Access denied; from=<reject@ok.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <reject@ok.domain>: Sender address rejected: Access denied +>>> # Expect: OK +>>> mail ok@ok.domain +OK +>>> # Expect: OK +>>> mail anyone@ok.domain +OK +>>> # Expect: OK +>>> mail bad-sender@ok.domain +OK +>>> # +>>> # Test check_mail_access() +>>> # +>>> recipient_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> rcpt reject@dunno.domain +./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@dunno.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject@dunno.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <reject@dunno.domain>: Recipient address rejected: Access denied +>>> # Expect: REJECT +>>> recipient_delimiter + +OK +>>> rcpt reject+ext@dunno.domain +./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject+ext@dunno.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject+ext@dunno.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <reject+ext@dunno.domain>: Recipient address rejected: Access denied +>>> recipient_delimiter | +OK +>>> # Expect: OK +>>> rcpt ok@dunno.domain +OK +>>> # Expect: OK +>>> recipient_delimiter + +OK +>>> rcpt ok+ext@dunno.domain +OK +>>> recipient_delimiter | +OK +>>> # Expect: OK +>>> rcpt anyone@dunno.domain +OK +>>> # Expect: OK +>>> rcpt bad-sender@dunno.domain +OK +>>> # +>>> # Again, with a domain that rejects by default +>>> # +>>> # Expect: REJECT +>>> rcpt reject@reject.domain +./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@reject.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject@reject.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <reject@reject.domain>: Recipient address rejected: Access denied +>>> # Expect: OK +>>> rcpt ok@reject.domain +OK +>>> # Expect: REJECT +>>> rcpt anyone@reject.domain +./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <anyone@reject.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<anyone@reject.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <anyone@reject.domain>: Recipient address rejected: Access denied +>>> # Expect: REJECT +>>> rcpt good-sender@reject.domain +./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <good-sender@reject.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<good-sender@reject.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <good-sender@reject.domain>: Recipient address rejected: Access denied +>>> # +>>> # Again, with a domain that accepts by default +>>> # +>>> # Expect: REJECT +>>> rcpt reject@ok.domain +./smtpd_check: <queue id>: reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 <reject@ok.domain>: Recipient address rejected: Access denied; from=<bad-sender@ok.domain> to=<reject@ok.domain> proto=SMTP helo=<foo.duuno.com> +554 5.7.1 <reject@ok.domain>: Recipient address rejected: Access denied +>>> # Expect: OK +>>> rcpt ok@ok.domain +OK +>>> # Expect: OK +>>> rcpt anyone@ok.domain +OK +>>> # Expect: OK +>>> rcpt bad-sender@ok.domain +OK +>>> # +>>> # check_sender_access specific +>>> # +>>> mail <> +./smtpd_check: <queue id>: reject: MAIL from bar.duno.com[44.33.44.33]: 550 5.7.1 <>: Sender address rejected: Go away postmaster; from=<> proto=SMTP helo=<foo.duuno.com> +550 5.7.1 <>: Sender address rejected: Go away postmaster diff --git a/src/smtpd/smtpd_addr_valid.in b/src/smtpd/smtpd_addr_valid.in new file mode 100644 index 0000000..e89537d --- /dev/null +++ b/src/smtpd/smtpd_addr_valid.in @@ -0,0 +1,35 @@ +# +# Initialize +# +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +local_recipient_maps inline:{foo_canon=whatever,bar_canon=whatever} +mydestination example.com +myorigin example.com + +sender_canonical_maps inline:{foo@example.com=foo_canon@example.com} +recipient_canonical_maps inline:{bar@example.com=bar_canon@example.com} + +sender_restrictions reject_unlisted_sender +# Expect accept +mail bar_canon@example.com +# Expect accept +mail bar@example.com +# Expect accept +mail foo_canon@example.com +# Expect accept +mail foo@example.com +# Expect reject +mail baz@example.com + +recipient_restrictions reject_unlisted_recipient +# Expect accept +rcpt bar_canon@example.com +# Expect accept +rcpt bar@example.com +# Expect accept +rcpt foo_canon@example.com +# Expect reject +rcpt foo@example.com +# Expect reject +mail baz@example.com diff --git a/src/smtpd/smtpd_addr_valid.ref b/src/smtpd/smtpd_addr_valid.ref new file mode 100644 index 0000000..768a4c4 --- /dev/null +++ b/src/smtpd/smtpd_addr_valid.ref @@ -0,0 +1,57 @@ +>>> # +>>> # Initialize +>>> # +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.189.0/28 +OK +>>> local_recipient_maps inline:{foo_canon=whatever,bar_canon=whatever} +OK +>>> mydestination example.com +OK +>>> myorigin example.com +OK +>>> +>>> sender_canonical_maps inline:{foo@example.com=foo_canon@example.com} +OK +>>> recipient_canonical_maps inline:{bar@example.com=bar_canon@example.com} +OK +>>> +>>> sender_restrictions reject_unlisted_sender +OK +>>> # Expect accept +>>> mail bar_canon@example.com +OK +>>> # Expect accept +>>> mail bar@example.com +OK +>>> # Expect accept +>>> mail foo_canon@example.com +OK +>>> # Expect accept +>>> mail foo@example.com +OK +>>> # Expect reject +>>> mail baz@example.com +./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table; from=<baz@example.com> proto=SMTP +550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table +>>> +>>> recipient_restrictions reject_unlisted_recipient +OK +>>> # Expect accept +>>> rcpt bar_canon@example.com +OK +>>> # Expect accept +>>> rcpt bar@example.com +OK +>>> # Expect accept +>>> rcpt foo_canon@example.com +OK +>>> # Expect reject +>>> rcpt foo@example.com +./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 550 5.1.1 <foo@example.com>: Recipient address rejected: User unknown in local recipient table; from=<baz@example.com> to=<foo@example.com> proto=SMTP +550 5.1.1 <foo@example.com>: Recipient address rejected: User unknown in local recipient table +>>> # Expect reject +>>> mail baz@example.com +./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table; from=<baz@example.com> proto=SMTP +550 5.1.0 <baz@example.com>: Sender address rejected: User unknown in local recipient table diff --git a/src/smtpd/smtpd_chat.c b/src/smtpd/smtpd_chat.c new file mode 100644 index 0000000..c172ab3 --- /dev/null +++ b/src/smtpd/smtpd_chat.c @@ -0,0 +1,345 @@ +/*++ +/* NAME +/* smtpd_chat 3 +/* SUMMARY +/* SMTP server request/response support +/* SYNOPSIS +/* #include <smtpd.h> +/* #include <smtpd_chat.h> +/* +/* void smtpd_chat_pre_jail_init(void) +/* +/* int smtpd_chat_query_limit(state, limit) +/* SMTPD_STATE *state; +/* int limit; +/* +/* void smtpd_chat_query(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_chat_reply(state, format, ...) +/* SMTPD_STATE *state; +/* char *format; +/* +/* void smtpd_chat_notify(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_chat_reset(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* This module implements SMTP server support for request/reply +/* conversations, and maintains a limited SMTP transaction log. +/* +/* smtpd_chat_pre_jail_init() performs one-time initialization. +/* +/* smtpd_chat_query_limit() reads a line from the client that is +/* at most "limit" bytes long. A copy is appended to the SMTP +/* transaction log. The return value is non-zero for a complete +/* line or else zero if the length limit was exceeded. +/* +/* smtpd_chat_query() receives a client request and appends a copy +/* to the SMTP transaction log. +/* +/* smtpd_chat_reply() formats a server reply, sends it to the +/* client, and appends a copy to the SMTP transaction log. +/* When soft_bounce is enabled, all 5xx (reject) responses are +/* replaced by 4xx (try again). In case of a 421 reply the +/* SMTPD_FLAG_HANGUP flag is set for orderly disconnect. +/* +/* smtpd_chat_notify() sends a copy of the SMTP transaction log +/* to the postmaster for review. The postmaster notice is sent only +/* when delivery is possible immediately. It is an error to call +/* smtpd_chat_notify() when no SMTP transaction log exists. +/* +/* smtpd_chat_reset() resets the transaction log. This is +/* typically done at the beginning of an SMTP session, or +/* within a session to discard non-error information. +/* DIAGNOSTICS +/* Panic: interface violations. Fatal errors: out of memory. +/* internal protocol errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <setjmp.h> +#include <unistd.h> +#include <time.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> + +/* Utility library. */ + +#include <msg.h> +#include <argv.h> +#include <vstring.h> +#include <vstream.h> +#include <stringops.h> +#include <line_wrap.h> +#include <mymalloc.h> + +/* Global library. */ + +#include <smtp_stream.h> +#include <record.h> +#include <rec_type.h> +#include <mail_proto.h> +#include <mail_params.h> +#include <mail_addr.h> +#include <maps.h> +#include <post_mail.h> +#include <mail_error.h> +#include <smtp_reply_footer.h> + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_expand.h" +#include "smtpd_chat.h" + + /* + * Reject footer. + */ +static MAPS *smtpd_rej_ftr_maps; + +#define STR vstring_str +#define LEN VSTRING_LEN + +/* smtpd_chat_pre_jail_init - initialize */ + +void smtpd_chat_pre_jail_init(void) +{ + static int init_count = 0; + + if (init_count++ != 0) + msg_panic("smtpd_chat_pre_jail_init: multiple calls"); + + /* + * SMTP server reject footer. + */ + if (*var_smtpd_rej_ftr_maps) + smtpd_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS, + var_smtpd_rej_ftr_maps, + DICT_FLAG_LOCK); +} + +/* smtp_chat_reset - reset SMTP transaction log */ + +void smtpd_chat_reset(SMTPD_STATE *state) +{ + if (state->history) { + argv_free(state->history); + state->history = 0; + } +} + +/* smtp_chat_append - append record to SMTP transaction log */ + +static void smtp_chat_append(SMTPD_STATE *state, char *direction, + const char *text) +{ + char *line; + + if (state->notify_mask == 0) + return; + + if (state->history == 0) + state->history = argv_alloc(10); + line = concatenate(direction, text, (char *) 0); + argv_add(state->history, line, (char *) 0); + myfree(line); +} + +/* smtpd_chat_query - receive and record an SMTP request */ + +int smtpd_chat_query_limit(SMTPD_STATE *state, int limit) +{ + int last_char; + + /* + * We can't parse or store input that exceeds var_line_limit, so we skip + * over it to avoid loss of synchronization. + */ + last_char = smtp_get(state->buffer, state->client, limit, + SMTP_GET_FLAG_SKIP); + smtp_chat_append(state, "In: ", STR(state->buffer)); + if (last_char != '\n') + msg_warn("%s: request longer than %d: %.30s...", + state->namaddr, limit, + printable(STR(state->buffer), '?')); + + if (msg_verbose) + msg_info("< %s: %s", state->namaddr, STR(state->buffer)); + return (last_char == '\n'); +} + +/* smtpd_chat_reply - format, send and record an SMTP response */ + +void smtpd_chat_reply(SMTPD_STATE *state, const char *format,...) +{ + va_list ap; + + va_start(ap, format); + vsmtpd_chat_reply(state, format, ap); + va_end(ap); +} + +/* vsmtpd_chat_reply - format, send and record an SMTP response */ + +void vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap) +{ + int delay = 0; + char *cp; + char *next; + char *end; + const char *footer; + + /* + * Slow down clients that make errors. Sleep-on-anything slows down + * clients that make an excessive number of errors within a session. + */ + if (state->error_count >= var_smtpd_soft_erlim) + sleep(delay = var_smtpd_err_sleep); + + vstring_vsprintf(state->buffer, format, ap); + + if ((*(cp = STR(state->buffer)) == '4' || *cp == '5') + && ((smtpd_rej_ftr_maps != 0 + && (footer = maps_find(smtpd_rej_ftr_maps, cp, 0)) != 0) + || *(footer = var_smtpd_rej_footer) != 0)) + smtp_reply_footer(state->buffer, 0, footer, STR(smtpd_expand_filter), + smtpd_expand_lookup, (void *) state); + + /* All 5xx replies must have a 5.xx.xx detail code. */ + for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) { + if (var_soft_bounce) { + if (cp[0] == '5') { + cp[0] = '4'; + if (cp[4] == '5') + cp[4] = '4'; + } + } + /* This is why we use strlen() above instead of VSTRING_LEN(). */ + if ((next = strstr(cp, "\r\n")) != 0) { + *next = 0; + if (next[2] != 0) + cp[3] = '-'; /* contact footer kludge */ + else + next = end; /* strip trailing \r\n */ + } else { + next = end; + } + smtp_chat_append(state, "Out: ", cp); + + if (msg_verbose) + msg_info("> %s: %s", state->namaddr, cp); + + smtp_fputs(cp, next - cp, state->client); + if (next < end) + cp = next + 2; + else + break; + } + + /* + * Flush unsent output if no I/O happened for a while. This avoids + * timeouts with pipelined SMTP sessions that have lots of server-side + * delays (tarpit delays or DNS lookups for UCE restrictions). + */ + if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10) + vstream_fflush(state->client); + + /* + * Abort immediately if the connection is broken. + */ + if (vstream_ftimeout(state->client)) + vstream_longjmp(state->client, SMTP_ERR_TIME); + if (vstream_ferror(state->client)) + vstream_longjmp(state->client, SMTP_ERR_EOF); + + /* + * Orderly disconnect in case of 421 or 521 reply. + */ + if (strncmp(STR(state->buffer), "421", 3) == 0 + || strncmp(STR(state->buffer), "521", 3) == 0) + state->flags |= SMTPD_FLAG_HANGUP; +} + +/* print_line - line_wrap callback */ + +static void print_line(const char *str, int len, int indent, void *context) +{ + VSTREAM *notice = (VSTREAM *) context; + + post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str); +} + +/* smtpd_chat_notify - notify postmaster */ + +void smtpd_chat_notify(SMTPD_STATE *state) +{ + const char *myname = "smtpd_chat_notify"; + VSTREAM *notice; + char **cpp; + + /* + * Sanity checks. + */ + if (state->history == 0) + msg_panic("%s: no conversation history", myname); + if (msg_verbose) + msg_info("%s: notify postmaster", myname); + + /* + * Construct a message for the postmaster, explaining what this is all + * about. This is junk mail: don't send it when the mail posting service + * is unavailable, and use the double bounce sender address to prevent + * mail bounce wars. Always prepend one space to message content that we + * generate from untrusted data. + */ +#define NULL_TRACE_FLAGS 0 +#define NO_QUEUE_ID ((VSTRING *) 0) +#define LENGTH 78 +#define INDENT 4 + + notice = post_mail_fopen_nowait(mail_addr_double_bounce(), + (state->error_mask & MAIL_ERROR_BOUNCE) ? + var_bounce_rcpt : var_error_rcpt, + MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS, + SMTPUTF8_FLAG_NONE, NO_QUEUE_ID); + if (notice == 0) { + msg_warn("postmaster notify: %m"); + return; + } + post_mail_fprintf(notice, "From: %s (Mail Delivery System)", + mail_addr_mail_daemon()); + post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); + post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s", + var_mail_name, state->namaddr); + post_mail_fputs(notice, ""); + post_mail_fputs(notice, "Transcript of session follows."); + post_mail_fputs(notice, ""); + argv_terminate(state->history); + for (cpp = state->history->argv; *cpp; cpp++) + line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, + (void *) notice); + post_mail_fputs(notice, ""); + if (state->reason) + post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason); + post_mail_fputs(notice, ""); + post_mail_fprintf(notice, "For other details, see the local mail logfile"); + (void) post_mail_fclose(notice); +} diff --git a/src/smtpd/smtpd_chat.h b/src/smtpd/smtpd_chat.h new file mode 100644 index 0000000..9fbe178 --- /dev/null +++ b/src/smtpd/smtpd_chat.h @@ -0,0 +1,45 @@ +/*++ +/* NAME +/* smtpd_chat 3h +/* SUMMARY +/* SMTP server request/response support +/* SYNOPSIS +/* #include <smtpd.h> +/* #include <smtpd_chat.h> +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include <mail_params.h> + + /* + * External interface. + */ +extern void smtpd_chat_pre_jail_init(void); +extern void smtpd_chat_reset(SMTPD_STATE *); +extern int smtpd_chat_query_limit(SMTPD_STATE *, int); +extern void smtpd_chat_query(SMTPD_STATE *); +extern void PRINTFLIKE(2, 3) smtpd_chat_reply(SMTPD_STATE *, const char *,...); +extern void vsmtpd_chat_reply(SMTPD_STATE *, const char *, va_list); +extern void smtpd_chat_notify(SMTPD_STATE *); + +#define smtpd_chat_query(state) \ + ((void) smtpd_chat_query_limit((state), var_line_limit)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/smtpd/smtpd_check.c b/src/smtpd/smtpd_check.c new file mode 100644 index 0000000..31ed00f --- /dev/null +++ b/src/smtpd/smtpd_check.c @@ -0,0 +1,6289 @@ +/*++ +/* 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; +/* 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). +/* 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> + +/* 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> + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_sasl_glue.h" +#include "smtpd_check.h" +#include "smtpd_dsn_fix.h" +#include "smtpd_resolve.h" +#include "smtpd_expand.h" + + /* + * Eject seat in case of parsing problems. + */ +static jmp_buf smtpd_check_buf; + + /* + * Results of restrictions. Errors are negative; see dict.h. + */ +#define SMTPD_CHECK_DUNNO 0 /* indifferent */ +#define SMTPD_CHECK_OK 1 /* explicitly permit */ +#define SMTPD_CHECK_REJECT 2 /* explicitly reject */ + + /* + * Intermediate results. These are static to avoid unnecessary stress on the + * memory manager routines. + */ +static VSTRING *error_text; +static CTABLE *smtpd_rbl_cache; +static CTABLE *smtpd_rbl_byte_cache; + + /* + * Pre-opened SMTP recipient maps so we can reject mail for unknown users. + * XXX This does not belong here and will eventually become part of the + * trivial-rewrite resolver. + */ +static MAPS *local_rcpt_maps; +static MAPS *send_canon_maps; +static MAPS *rcpt_canon_maps; +static MAPS *canonical_maps; +static MAPS *virt_alias_maps; +static MAPS *virt_mailbox_maps; +static MAPS *relay_rcpt_maps; + +#ifdef TEST + +static STRING_LIST *virt_alias_doms; +static STRING_LIST *virt_mailbox_doms; + +#endif + + /* + * Response templates for various rbl domains. + */ +static MAPS *rbl_reply_maps; + + /* + * Pre-opened sender to login name mapping. + */ +static MAPS *smtpd_sender_login_maps; + + /* + * Pre-opened access control lists. + */ +static DOMAIN_LIST *relay_domains; +static NAMADR_LIST *mynetworks_curr; +static NAMADR_LIST *mynetworks_new; +static NAMADR_LIST *perm_mx_networks; + +#ifdef USE_TLS +static MAPS *relay_ccerts; + +#endif + + /* + * How to do parent domain wildcard matching, if any. + */ +static int access_parent_style; + + /* + * Pre-parsed restriction lists. + */ +static ARGV *client_restrctions; +static ARGV *helo_restrctions; +static ARGV *mail_restrctions; +static ARGV *relay_restrctions; +static ARGV *fake_relay_restrctions; +static ARGV *rcpt_restrctions; +static ARGV *etrn_restrctions; +static ARGV *data_restrctions; +static ARGV *eod_restrictions; + +static HTABLE *smtpd_rest_classes; +static HTABLE *policy_clnt_table; +static HTABLE *map_command_table; + +static ARGV *local_rewrite_clients; + + /* + * The routine that recursively applies restrictions. + */ +static int generic_checks(SMTPD_STATE *, ARGV *, const char *, const char *, const char *); + + /* + * Recipient table check. + */ +static int check_sender_rcpt_maps(SMTPD_STATE *, const char *); +static int check_recipient_rcpt_maps(SMTPD_STATE *, const char *); +static int check_rcpt_maps(SMTPD_STATE *, const char *, const char *, + const char *); + + /* + * Tempfail actions; + */ +static int unk_name_tf_act; +static int unk_addr_tf_act; +static int unv_rcpt_tf_act; +static int unv_from_tf_act; + + /* + * Optional permit logging. + */ +static STRING_LIST *smtpd_acl_perm_log; + + /* + * YASLM. + */ +#define STR vstring_str +#define CONST_STR(x) ((const char *) vstring_str(x)) +#define UPDATE_STRING(ptr,val) { if (ptr) myfree(ptr); ptr = mystrdup(val); } + + /* + * If some decision can't be made due to a temporary error, then change + * other decisions into deferrals. + * + * XXX Deferrals can be postponed only with restrictions that are based on + * client-specified information: this restricts their use to parameters + * given in HELO, MAIL FROM, RCPT TO commands. + * + * XXX Deferrals must not be postponed after client hostname lookup failure. + * The reason is that the effect of access tables may depend on whether a + * client hostname is available or not. Thus, the reject_unknown_client + * restriction must defer immediately when lookup fails, otherwise incorrect + * results happen with: + * + * reject_unknown_client, hostname-based white-list, reject + * + * XXX With warn_if_reject, don't raise the defer_if_permit flag when a + * reject-style restriction fails. Instead, log the warning for the + * resulting defer message. + * + * XXX With warn_if_reject, do raise the defer_if_reject flag when a + * permit-style restriction fails. Otherwise, we could reject legitimate + * mail. + */ +static int PRINTFLIKE(5, 6) defer_if(SMTPD_DEFER *, int, int, const char *, const char *,...); +static int PRINTFLIKE(5, 6) smtpd_check_reject(SMTPD_STATE *, int, int, const char *, const char *,...); + +#define DEFER_IF_REJECT2(state, class, code, dsn, fmt, a1, a2) \ + defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2)) +#define DEFER_IF_REJECT3(state, class, code, dsn, fmt, a1, a2, a3) \ + defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2), (a3)) +#define DEFER_IF_REJECT4(state, class, code, dsn, fmt, a1, a2, a3, a4) \ + defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4)) + + /* + * The following choose between DEFER_IF_PERMIT (only if warn_if_reject is + * turned off) and plain DEFER. See tempfail_actions[] below for the mapping + * from names to numeric action code. + */ +#define DEFER_ALL_ACT 0 +#define DEFER_IF_PERMIT_ACT 1 + +#define DEFER_IF_PERMIT2(type, state, class, code, dsn, fmt, a1, a2) \ + (((state)->warn_if_reject == 0 && (type) != 0) ? \ + defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2)) \ + : \ + smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2))) +#define DEFER_IF_PERMIT3(type, state, class, code, dsn, fmt, a1, a2, a3) \ + (((state)->warn_if_reject == 0 && (type) != 0) ? \ + defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2), (a3)) \ + : \ + smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2), (a3))) +#define DEFER_IF_PERMIT4(type, state, class, code, dsn, fmt, a1, a2, a3, a4) \ + (((state)->warn_if_reject == 0 && (type) != 0) ? \ + defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4)) \ + : \ + smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4))) + + /* + * Cached RBL lookup state. + */ +typedef struct { + char *txt; /* TXT content or NULL */ + DNS_RR *a; /* A records */ +} SMTPD_RBL_STATE; + +static void *rbl_pagein(const char *, void *); +static void rbl_pageout(void *, void *); +static void *rbl_byte_pagein(const char *, void *); +static void rbl_byte_pageout(void *, void *); + + /* + * Context for RBL $name expansion. + */ +typedef struct { + SMTPD_STATE *state; /* general state */ + char *domain; /* query domain */ + const char *what; /* rejected value */ + const char *class; /* name of rejected value */ + const char *txt; /* randomly selected trimmed TXT rr */ +} SMTPD_RBL_EXPAND_CONTEXT; + + /* + * Multiplication factor for free space check. Free space must be at least + * smtpd_space_multf * message_size_limit. + */ +double smtpd_space_multf = 1.5; + + /* + * SMTPD policy client. Most attributes are ATTR_CLNT attributes. + */ +typedef struct { + ATTR_CLNT *client; /* client handle */ + char *def_action; /* default action */ + char *policy_context; /* context of policy request */ +} SMTPD_POLICY_CLNT; + + /* + * Table-driven parsing of main.cf parameter overrides for specific policy + * clients. We derive the override names from the corresponding main.cf + * parameter names by skipping the redundant "smtpd_policy_service_" prefix. + */ +static ATTR_OVER_TIME time_table[] = { + 21 + (const char *) VAR_SMTPD_POLICY_TMOUT, DEF_SMTPD_POLICY_TMOUT, 0, 1, 0, + 21 + (const char *) VAR_SMTPD_POLICY_IDLE, DEF_SMTPD_POLICY_IDLE, 0, 1, 0, + 21 + (const char *) VAR_SMTPD_POLICY_TTL, DEF_SMTPD_POLICY_TTL, 0, 1, 0, + 21 + (const char *) VAR_SMTPD_POLICY_TRY_DELAY, DEF_SMTPD_POLICY_TRY_DELAY, 0, 1, 0, + 0, +}; +static ATTR_OVER_INT int_table[] = { + 21 + (const char *) VAR_SMTPD_POLICY_REQ_LIMIT, 0, 0, 0, + 21 + (const char *) VAR_SMTPD_POLICY_TRY_LIMIT, 0, 1, 0, + 0, +}; +static ATTR_OVER_STR str_table[] = { + 21 + (const char *) VAR_SMTPD_POLICY_DEF_ACTION, 0, 1, 0, + 21 + (const char *) VAR_SMTPD_POLICY_CONTEXT, 0, 1, 0, + 0, +}; + +#define link_override_table_to_variable(table, var) \ + do { table[var##_offset].target = &var; } while (0) + +#define smtpd_policy_tmout_offset 0 +#define smtpd_policy_idle_offset 1 +#define smtpd_policy_ttl_offset 2 +#define smtpd_policy_try_delay_offset 3 + +#define smtpd_policy_req_limit_offset 0 +#define smtpd_policy_try_limit_offset 1 + +#define smtpd_policy_def_action_offset 0 +#define smtpd_policy_context_offset 1 + +/* 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; + + /* + * 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) && strchr(name, ':') != 0) { + command_map_register(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); + + /* + * 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 */ + +static 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>", state->sender); + if (state->recipient) + vstring_sprintf_append(buf, " to=<%s>", state->recipient); + if (state->protocol) + vstring_sprintf_append(buf, " proto=%s", state->protocol); + if (state->helo_name) + vstring_sprintf_append(buf, " helo=<%s>", state->helo_name); + msg_info("%s", STR(buf)); + vstring_free(buf); +} + +/* smtpd_acl_permit - permit request with optional logging */ + +static int PRINTFLIKE(5, 6) smtpd_acl_permit(SMTPD_STATE *state, + const char *action, + const char *reply_class, + const char *reply_name, + const char *format,...) +{ + const char myname[] = "smtpd_acl_permit"; + va_list ap; + const char *whatsup; + +#ifdef notdef +#define NO_PRINT_ARGS "" +#else +#define NO_PRINT_ARGS "%s", "" +#endif + + /* + * First, find out if (and how) this permit action should be logged. + */ + if (msg_verbose) + msg_info("%s: checking %s settings", myname, VAR_SMTPD_ACL_PERM_LOG); + + if (state->defer_if_permit.active) { + /* This action is overruled. Do not log. */ + whatsup = 0; + } else if (string_list_match(smtpd_acl_perm_log, action) != 0) { + /* This is not a test. Logging is enabled. */ + whatsup = "permit"; + } else { + /* This is not a test. Logging is disabled. */ + whatsup = 0; + } + if (whatsup != 0) { + vstring_sprintf(error_text, "action=%s for %s=%s", + action, reply_class, reply_name); + if (format && *format) { + vstring_strcat(error_text, " "); + va_start(ap, format); + vstring_vsprintf_append(error_text, format, ap); + va_end(ap); + } + log_whatsup(state, whatsup, STR(error_text)); + } else { + if (msg_verbose) + msg_info("%s: %s: no match", myname, VAR_SMTPD_ACL_PERM_LOG); + } + return (SMTPD_CHECK_OK); +} + +/* smtpd_check_reject - do the boring things that must be done */ + +static int smtpd_check_reject(SMTPD_STATE *state, int error_class, + int code, const char *dsn, + const char *format,...) +{ + va_list ap; + int warn_if_reject; + const char *whatsup; + + /* + * Do not reject mail if we were asked to warn only. However, + * configuration/software/data errors cannot be converted into warnings. + */ + if (state->warn_if_reject && error_class != MAIL_ERROR_SOFTWARE + && error_class != MAIL_ERROR_RESOURCE + && error_class != MAIL_ERROR_DATA) { + warn_if_reject = 1; + whatsup = "reject_warning"; + } else { + warn_if_reject = 0; + whatsup = "reject"; + } + + /* + * Update the error class mask, and format the response. XXX What about + * multi-line responses? For now we cheat and send whitespace. + * + * Format the response before complaining about configuration errors, so + * that we can show the error in context. + */ + state->error_mask |= error_class; + vstring_sprintf(error_text, "%d %s ", code, dsn); + va_start(ap, format); + vstring_vsprintf_append(error_text, format, ap); + va_end(ap); + + /* + * Validate the response, that is, the response must begin with a + * three-digit status code, and the first digit must be 4 or 5. If the + * response is bad, log a warning and send a generic response instead. + */ + if (code < 400 || code > 599) { + msg_warn("SMTP reply code configuration error: %s", STR(error_text)); + vstring_strcpy(error_text, "450 4.7.1 Service unavailable"); + } + if (!dsn_valid(STR(error_text) + 4)) { + msg_warn("DSN detail code configuration error: %s", STR(error_text)); + vstring_strcpy(error_text, "450 4.7.1 Service unavailable"); + } + + /* + * Ensure RFC compliance. We could do this inside smtpd_chat_reply() and + * switch to multi-line for long replies. + */ + vstring_truncate(error_text, 510); + printable(STR(error_text), ' '); + + /* + * Force this rejection into deferral because of some earlier temporary + * error that may have prevented us from accepting mail, and report the + * earlier problem instead. + */ + if (!warn_if_reject && state->defer_if_reject.active && STR(error_text)[0] == '5') { + state->warn_if_reject = state->defer_if_reject.active = 0; + return (smtpd_check_reject(state, state->defer_if_reject.class, + state->defer_if_reject.code, + STR(state->defer_if_reject.dsn), + "%s", STR(state->defer_if_reject.reason))); + } + + /* + * Soft bounce safety net. + * + * XXX The code below also appears in the Postfix SMTP server reply output + * routine. It is duplicated here in order to avoid discrepancies between + * the reply codes that are shown in "reject" logging and the reply codes + * that are actually sent to the SMTP client. + * + * Implementing the soft_bounce safety net in the SMTP server reply output + * routine has the advantage that it covers all 5xx replies, including + * SMTP protocol or syntax errors, which makes soft_bounce great for + * non-destructive tests (especially by people who are paranoid about + * losing mail). + * + * We could eliminate the code duplication and implement the soft_bounce + * safety net only in the code below. But then the safety net would cover + * the UCE restrictions only. This would be at odds with documentation + * which says soft_bounce changes all 5xx replies into 4xx ones. + */ + if (var_soft_bounce && STR(error_text)[0] == '5') + STR(error_text)[0] = '4'; + + /* + * In any case, enforce consistency between the SMTP code and DSN code. + * SMTP has the higher precedence since it came here first. + */ + STR(error_text)[4] = STR(error_text)[0]; + + /* + * Log what is happening. When the sysadmin discards policy violation + * postmaster notices, this may be the only trace left that service was + * rejected. Print the request, client name/address, and response. + */ + log_whatsup(state, whatsup, STR(error_text)); + + return (warn_if_reject ? 0 : SMTPD_CHECK_REJECT); +} + +/* defer_if - prepare to change our mind */ + +static int defer_if(SMTPD_DEFER *defer, int error_class, + int code, const char *dsn, + const char *fmt,...) +{ + va_list ap; + + /* + * Keep the first reason for this type of deferral, to minimize + * confusion. + */ + if (defer->active == 0) { + defer->active = 1; + defer->class = error_class; + defer->code = code; + if (defer->dsn == 0) + defer->dsn = vstring_alloc(10); + vstring_strcpy(defer->dsn, dsn); + if (defer->reason == 0) + defer->reason = vstring_alloc(10); + va_start(ap, fmt); + vstring_vsprintf(defer->reason, fmt, ap); + va_end(ap); + } + return (SMTPD_CHECK_DUNNO); +} + +/* reject_dict_retry - reject with temporary failure if dict lookup fails */ + +static NORETURN reject_dict_retry(SMTPD_STATE *state, const char *reply_name) +{ + longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_DATA, + 451, "4.3.0", + "<%s>: Temporary lookup failure", + reply_name)); +} + +/* reject_server_error - reject with temporary failure after non-dict error */ + +static NORETURN reject_server_error(SMTPD_STATE *state) +{ + longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE, + 451, "4.3.5", + "Server configuration error")); +} + +/* check_mail_addr_find - reject with temporary failure if dict lookup fails */ + +static const char *check_mail_addr_find(SMTPD_STATE *state, + const char *reply_name, + MAPS *maps, const char *key, + char **ext) +{ + const char *result; + + if ((result = mail_addr_find(maps, key, ext)) != 0 || maps->error == 0) + return (result); + if (maps->error == DICT_ERR_RETRY) + /* Warning is already logged. */ + reject_dict_retry(state, reply_name); + else + reject_server_error(state); +} + +/* reject_unknown_reverse_name - fail if reverse client hostname is unknown */ + +static int reject_unknown_reverse_name(SMTPD_STATE *state) +{ + const char *myname = "reject_unknown_reverse_name"; + + if (msg_verbose) + msg_info("%s: %s", myname, state->reverse_name); + + if (state->reverse_name_status != SMTPD_PEER_CODE_OK) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + state->reverse_name_status == SMTPD_PEER_CODE_PERM ? + var_unk_client_code : 450, "4.7.1", + "Client host rejected: cannot find your reverse hostname, [%s]", + state->addr)); + return (SMTPD_CHECK_DUNNO); +} + +/* reject_unknown_client - fail if client hostname is unknown */ + +static int reject_unknown_client(SMTPD_STATE *state) +{ + const char *myname = "reject_unknown_client"; + + if (msg_verbose) + msg_info("%s: %s %s", myname, state->name, state->addr); + + /* RFC 7372: Email Authentication Status Codes. */ + if (state->name_status != SMTPD_PEER_CODE_OK) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + state->name_status >= SMTPD_PEER_CODE_PERM ? + var_unk_client_code : 450, "4.7.25", + "Client host rejected: cannot find your hostname, [%s]", + state->addr)); + return (SMTPD_CHECK_DUNNO); +} + +/* reject_plaintext_session - fail if session is not encrypted */ + +static int reject_plaintext_session(SMTPD_STATE *state) +{ + const char *myname = "reject_plaintext_session"; + + if (msg_verbose) + msg_info("%s: %s %s", myname, state->name, state->addr); + +#ifdef USE_TLS + if (state->tls_context == 0) +#endif + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_plaintext_code, "4.7.1", + "Session encryption is required")); + return (SMTPD_CHECK_DUNNO); +} + +/* permit_inet_interfaces - succeed if client my own address */ + +static int permit_inet_interfaces(SMTPD_STATE *state) +{ + const char *myname = "permit_inet_interfaces"; + + if (msg_verbose) + msg_info("%s: %s %s", myname, state->name, state->addr); + + if (own_inet_addr((struct sockaddr *) &(state->sockaddr))) + /* Permit logging in generic_checks() only. */ + return (SMTPD_CHECK_OK); + return (SMTPD_CHECK_DUNNO); +} + +/* permit_mynetworks - succeed if client is in a trusted network */ + +static int permit_mynetworks(SMTPD_STATE *state) +{ + const char *myname = "permit_mynetworks"; + + if (msg_verbose) + msg_info("%s: %s %s", myname, state->name, state->addr); + + if (namadr_list_match(mynetworks_curr, state->name, state->addr)) { + if (warn_compat_break_mynetworks_style + && !namadr_list_match(mynetworks_new, state->name, state->addr)) + msg_info("using backwards-compatible default setting " + VAR_MYNETWORKS_STYLE "=%s to permit request from " + "client \"%s\"", var_mynetworks_style, state->namaddr); + /* Permit logging in generic_checks() only. */ + return (SMTPD_CHECK_OK); + } else if (mynetworks_curr->error == 0) + return (SMTPD_CHECK_DUNNO); + else + return (mynetworks_curr->error); +} + +/* dup_if_truncate - save hostname and truncate if it ends in dot */ + +static char *dup_if_truncate(char *name) +{ + ssize_t len; + char *result; + + /* + * Truncate hostnames ending in dot but not dot-dot. + * + * XXX This should not be distributed all over the code. Problem is, + * addresses can enter the system via multiple paths: networks, local + * forward/alias/include files, even as the result of address rewriting. + */ + if ((len = strlen(name)) > 1 + && name[len - 1] == '.' + && name[len - 2] != '.') { + result = mystrndup(name, len - 1); + } else + result = name; + return (result); +} + +/* reject_invalid_hostaddr - fail if host address is incorrect */ + +static int reject_invalid_hostaddr(SMTPD_STATE *state, char *addr, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_invalid_hostaddr"; + ssize_t len; + char *test_addr; + int stat; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + if (addr[0] == '[' && (len = strlen(addr)) > 2 && addr[len - 1] == ']') { + test_addr = mystrndup(addr + 1, len - 2); + } else + test_addr = addr; + + /* + * Validate the address. + */ + if (!valid_mailhost_addr(test_addr, DONT_GRIPE)) + stat = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_bad_name_code, "5.5.2", + "<%s>: %s rejected: invalid ip address", + reply_name, reply_class); + else + stat = SMTPD_CHECK_DUNNO; + + /* + * Cleanup. + */ + if (test_addr != addr) + myfree(test_addr); + + return (stat); +} + +/* reject_invalid_hostname - fail if host/domain syntax is incorrect */ + +static int reject_invalid_hostname(SMTPD_STATE *state, char *name, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_invalid_hostname"; + char *test_name; + int stat; + + if (msg_verbose) + msg_info("%s: %s", myname, name); + + /* + * Truncate hostnames ending in dot but not dot-dot. + */ + test_name = dup_if_truncate(name); + + /* + * Validate the HELO/EHLO hostname. Fix 20140706: EAI not allowed here. + */ + if (!valid_hostname(test_name, DONT_GRIPE) + && !valid_hostaddr(test_name, DONT_GRIPE)) /* XXX back compat */ + stat = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_bad_name_code, "5.5.2", + "<%s>: %s rejected: Invalid name", + reply_name, reply_class); + else + stat = SMTPD_CHECK_DUNNO; + + /* + * Cleanup. + */ + if (test_name != name) + myfree(test_name); + + return (stat); +} + +/* reject_non_fqdn_hostname - fail if host name is not in fqdn form */ + +static int reject_non_fqdn_hostname(SMTPD_STATE *state, char *name, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_non_fqdn_hostname"; + char *test_name; + int stat; + + if (msg_verbose) + msg_info("%s: %s", myname, name); + + /* + * Truncate hostnames ending in dot but not dot-dot. + */ + test_name = dup_if_truncate(name); + + /* + * Validate the hostname. For backwards compatibility, permit non-ASCII + * names only when the client requested SMTPUTF8 support. + */ + if (valid_utf8_hostname(state->flags & SMTPD_FLAG_SMTPUTF8, + test_name, DONT_GRIPE) == 0 || strchr(test_name, '.') == 0) + stat = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_non_fqdn_code, "5.5.2", + "<%s>: %s rejected: need fully-qualified hostname", + reply_name, reply_class); + else + stat = SMTPD_CHECK_DUNNO; + + /* + * Cleanup. + */ + if (test_name != name) + myfree(test_name); + + return (stat); +} + +/* reject_unknown_hostname - fail if name has no A, AAAA or MX record */ + +static int reject_unknown_hostname(SMTPD_STATE *state, char *name, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_unknown_hostname"; + int dns_status; + DNS_RR *dummy; + + if (msg_verbose) + msg_info("%s: %s", myname, name); + +#ifdef T_AAAA +#define RR_ADDR_TYPES T_A, T_AAAA +#else +#define RR_ADDR_TYPES T_A +#endif + + dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0, + (VSTRING *) 0, DNS_REQ_FLAG_STOP_OK, + RR_ADDR_TYPES, T_MX, 0); + if (dummy) + dns_rr_free(dummy); + /* Allow MTA names to have nullMX records. */ + if (dns_status != DNS_OK && dns_status != DNS_NULLMX) { + if (dns_status == DNS_POLICY) { + msg_warn("%s: address or MX lookup error: %s", + name, "DNS reply filter drops all results"); + return (SMTPD_CHECK_DUNNO); + } + if (dns_status != DNS_RETRY) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_unk_name_code, "4.7.1", + "<%s>: %s rejected: %s", + reply_name, reply_class, + dns_status == DNS_INVAL ? + "Malformed DNS server reply" : + "Host not found")); + else + return (DEFER_IF_PERMIT2(unk_name_tf_act, state, MAIL_ERROR_POLICY, + 450, "4.7.1", + "<%s>: %s rejected: Host not found", + reply_name, reply_class)); + } + return (SMTPD_CHECK_DUNNO); +} + +/* reject_unknown_mailhost - fail if name has no A, AAAA or MX record */ + +static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name, + const char *reply_name, const char *reply_class) +{ + const char *myname = "reject_unknown_mailhost"; + int dns_status; + DNS_RR *dummy; + const char *aname; + + if (msg_verbose) + msg_info("%s: %s", myname, name); + + /* + * Fix 20140924: convert domain to ASCII. + */ +#ifndef NO_EAI + if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", name, aname); + name = aname; + } +#endif + +#define MAILHOST_LOOKUP_FLAGS \ + (DNS_REQ_FLAG_STOP_OK | DNS_REQ_FLAG_STOP_INVAL | \ + DNS_REQ_FLAG_STOP_NULLMX | DNS_REQ_FLAG_STOP_MX_POLICY) + + dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0, + (VSTRING *) 0, MAILHOST_LOOKUP_FLAGS, + T_MX, RR_ADDR_TYPES, 0); + if (dummy) + dns_rr_free(dummy); + if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ + if (dns_status == DNS_POLICY) { + msg_warn("%s: MX or address lookup error: %s", + name, "DNS reply filter drops all results"); + return (SMTPD_CHECK_DUNNO); + } + if (dns_status == DNS_NULLMX) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + 550 : 556, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "4.7.27" : "4.1.10", + "<%s>: %s rejected: Domain %s " + "does not accept mail (nullMX)", + reply_name, reply_class, name)); + if (dns_status != DNS_RETRY) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_unk_addr_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "4.1.8" : "4.1.2", + "<%s>: %s rejected: %s", + reply_name, reply_class, + dns_status == DNS_INVAL ? + "Malformed DNS server reply" : + "Domain not found")); + else + return (DEFER_IF_PERMIT2(unk_addr_tf_act, state, MAIL_ERROR_POLICY, + 450, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "4.1.8" : "4.1.2", + "<%s>: %s rejected: Domain not found", + reply_name, reply_class)); + } + return (SMTPD_CHECK_DUNNO); +} + +static int permit_auth_destination(SMTPD_STATE *state, char *recipient); + +/* permit_tls_clientcerts - OK/DUNNO for message relaying, or set dict_errno */ + +static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs) +{ +#ifdef USE_TLS + const char *found = 0; + + if (!state->tls_context) + return SMTPD_CHECK_DUNNO; + + if (TLS_CERT_IS_TRUSTED(state->tls_context) && permit_all_certs) { + if (msg_verbose) + msg_info("Relaying allowed for all verified client certificates"); + /* Permit logging in generic_checks() only. */ + return (SMTPD_CHECK_OK); + } + + /* + * When directly checking the fingerprint, it is OK if the issuing CA is + * not trusted. + */ + if (TLS_CERT_IS_PRESENT(state->tls_context)) { + int i; + char *prints[2]; + + prints[0] = state->tls_context->peer_cert_fprint; + prints[1] = state->tls_context->peer_pkey_fprint; + + /* After lookup error, leave relay_ccerts->error at non-zero value. */ + for (i = 0; i < 2; ++i) { + found = maps_find(relay_ccerts, prints[i], DICT_FLAG_NONE); + if (found != 0) { + if (msg_verbose) + msg_info("Relaying allowed for certified client: %s", found); + /* Permit logging in generic_checks() only. */ + return (SMTPD_CHECK_OK); + } else if (relay_ccerts->error != 0) { + msg_warn("relay_clientcerts: lookup error for fingerprint '%s', " + "pkey fingerprint %s", prints[0], prints[1]); + return (relay_ccerts->error); + } + } + if (msg_verbose) + msg_info("relay_clientcerts: No match for fingerprint '%s', " + "pkey fingerprint %s", prints[0], prints[1]); + } +#endif + return (SMTPD_CHECK_DUNNO); +} + +/* check_relay_domains - OK/FAIL for message relaying */ + +static int check_relay_domains(SMTPD_STATE *state, char *recipient, + char *reply_name, char *reply_class) +{ + const char *myname = "check_relay_domains"; + +#if 1 + static int once; + + if (once == 0) { + once = 1; + msg_warn("support for restriction \"%s\" will be removed from %s; " + "use \"%s\" instead", + CHECK_RELAY_DOMAINS, var_mail_name, REJECT_UNAUTH_DEST); + } +#endif + + if (msg_verbose) + msg_info("%s: %s", myname, recipient); + + /* + * Permit if the client matches the relay_domains list. + */ + if (domain_list_match(relay_domains, state->name)) { + if (warn_compat_break_relay_domains) + msg_info("using backwards-compatible default setting " + VAR_RELAY_DOMAINS "=$mydestination to permit " + "request from client \"%s\"", state->name); + return (SMTPD_CHECK_OK); + } + + /* + * Permit authorized destinations. + */ + if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK) + return (SMTPD_CHECK_OK); + + /* + * Deny relaying between sites that both are not in relay_domains. + */ + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_relay_code, "5.7.1", + "<%s>: %s rejected: Relay access denied", + reply_name, reply_class)); +} + +/* permit_auth_destination - OK for message relaying */ + +static int permit_auth_destination(SMTPD_STATE *state, char *recipient) +{ + const char *myname = "permit_auth_destination"; + const RESOLVE_REPLY *reply; + const char *domain; + + if (msg_verbose) + msg_info("%s: %s", myname, recipient); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(state->sender, recipient); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, recipient); + + /* + * Handle special case that is not supposed to happen. + */ + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) + return (SMTPD_CHECK_OK); + domain += 1; + + /* + * Skip source-routed non-local or virtual mail (uncertain destination). + */ + if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED)) + return (SMTPD_CHECK_DUNNO); + + /* + * Permit final delivery: the destination matches mydestination, + * virtual_alias_domains, or virtual_mailbox_domains. + */ + if (reply->flags & RESOLVE_CLASS_FINAL) + return (SMTPD_CHECK_OK); + + /* + * Permit if the destination matches the relay_domains list. + */ + if (reply->flags & RESOLVE_CLASS_RELAY) { + if (warn_compat_break_relay_domains) + msg_info("using backwards-compatible default setting " + VAR_RELAY_DOMAINS "=$mydestination to accept mail " + "for domain \"%s\"", domain); + return (SMTPD_CHECK_OK); + } + + /* + * Skip when not matched + */ + return (SMTPD_CHECK_DUNNO); +} + +/* reject_unauth_destination - FAIL for message relaying */ + +static int reject_unauth_destination(SMTPD_STATE *state, char *recipient, + int reply_code, const char *reply_dsn) +{ + const char *myname = "reject_unauth_destination"; + + if (msg_verbose) + msg_info("%s: %s", myname, recipient); + + /* + * Skip authorized destination. + */ + if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK) + return (SMTPD_CHECK_DUNNO); + + /* + * Reject relaying to sites that are not listed in relay_domains. + */ + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + reply_code, reply_dsn, + "<%s>: Relay access denied", + recipient)); +} + +/* reject_unauth_pipelining - reject improper use of SMTP command pipelining */ + +static int reject_unauth_pipelining(SMTPD_STATE *state, + const char *reply_name, const char *reply_class) +{ + const char *myname = "reject_unauth_pipelining"; + + if (msg_verbose) + msg_info("%s: %s", myname, state->where); + + if (state->flags & SMTPD_FLAG_ILL_PIPELINING) + return (smtpd_check_reject(state, MAIL_ERROR_PROTOCOL, + 503, "5.5.0", + "<%s>: %s rejected: Improper use of SMTP command pipelining", + reply_name, reply_class)); + + return (SMTPD_CHECK_DUNNO); +} + +/* all_auth_mx_addr - match host addresses against permit_mx_backup_networks */ + +static int all_auth_mx_addr(SMTPD_STATE *state, char *host, + const char *reply_name, const char *reply_class) +{ + const char *myname = "all_auth_mx_addr"; + MAI_HOSTADDR_STR hostaddr; + DNS_RR *rr; + DNS_RR *addr_list; + int dns_status; + + if (msg_verbose) + msg_info("%s: host %s", myname, host); + + /* + * If we can't lookup the host, defer. + */ +#define NOPE 0 +#define YUP 1 + + /* + * Verify that all host addresses are within permit_mx_backup_networks. + */ + dns_status = dns_lookup_v(host, 0, &addr_list, (VSTRING *) 0, (VSTRING *) 0, + DNS_REQ_FLAG_NONE, inet_proto_info()->dns_atype_list); + /* DNS_NULLMX is not applicable here. */ + if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ + DEFER_IF_REJECT4(state, MAIL_ERROR_POLICY, + 450, "4.4.4", + "<%s>: %s rejected: Unable to look up host " + "%s as mail exchanger: %s", + reply_name, reply_class, host, + dns_status == DNS_POLICY ? + "DNS reply filter policy" : dns_strerror(h_errno)); + return (NOPE); + } + for (rr = addr_list; rr != 0; rr = rr->next) { + if (dns_rr_to_pa(rr, &hostaddr) == 0) { + msg_warn("%s: skipping record type %s for host %s: %m", + myname, dns_strtype(rr->type), host); + continue; + } + if (msg_verbose) + msg_info("%s: checking: %s", myname, hostaddr.buf); + + if (!namadr_list_match(perm_mx_networks, host, hostaddr.buf)) { + if (perm_mx_networks->error == 0) { + + /* + * Reject: at least one IP address is not listed in + * permit_mx_backup_networks. + */ + if (msg_verbose) + msg_info("%s: address %s for %s does not match %s", + myname, hostaddr.buf, host, VAR_PERM_MX_NETWORKS); + } else { + msg_warn("%s: %s lookup error for address %s for %s", + myname, VAR_PERM_MX_NETWORKS, hostaddr.buf, host); + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + 450, "4.4.4", + "<%s>: %s rejected: Unable to verify host %s as mail exchanger", + reply_name, reply_class, host); + } + dns_rr_free(addr_list); + return (NOPE); + } + } + dns_rr_free(addr_list); + return (YUP); +} + +/* has_my_addr - see if this host name lists one of my network addresses */ + +static int has_my_addr(SMTPD_STATE *state, const char *host, + const char *reply_name, const char *reply_class) +{ + const char *myname = "has_my_addr"; + struct addrinfo *res; + struct addrinfo *res0; + int aierr; + MAI_HOSTADDR_STR hostaddr; + INET_PROTO_INFO *proto_info = inet_proto_info(); + + if (msg_verbose) + msg_info("%s: host %s", myname, host); + + /* + * If we can't lookup the host, defer rather than reject. + */ +#define YUP 1 +#define NOPE 0 + + aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0); + if (aierr) { + DEFER_IF_REJECT4(state, MAIL_ERROR_POLICY, + 450, "4.4.4", + "<%s>: %s rejected: Unable to look up mail exchanger host %s: %s", + reply_name, reply_class, host, MAI_STRERROR(aierr)); + return (NOPE); + } +#define HAS_MY_ADDR_RETURN(x) { freeaddrinfo(res0); return (x); } + + for (res = res0; res != 0; res = res->ai_next) { + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + if (msg_verbose) + msg_info("skipping address family %d for host %s", + res->ai_family, host); + continue; + } + if (msg_verbose) { + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("%s: addr %s", myname, hostaddr.buf); + } + if (own_inet_addr(res->ai_addr)) + HAS_MY_ADDR_RETURN(YUP); + if (proxy_inet_addr(res->ai_addr)) + HAS_MY_ADDR_RETURN(YUP); + } + if (msg_verbose) + msg_info("%s: host %s: no match", myname, host); + + HAS_MY_ADDR_RETURN(NOPE); +} + +/* i_am_mx - is this machine listed as MX relay */ + +static int i_am_mx(SMTPD_STATE *state, DNS_RR *mx_list, + const char *reply_name, const char *reply_class) +{ + const char *myname = "i_am_mx"; + DNS_RR *mx; + + /* + * Compare hostnames first. Only if no name match is found, go through + * the trouble of host address lookups. + */ + for (mx = mx_list; mx != 0; mx = mx->next) { + if (msg_verbose) + msg_info("%s: resolve hostname: %s", myname, (char *) mx->data); + if (resolve_local((char *) mx->data) > 0) + return (YUP); + /* if no match or error, match interface addresses instead. */ + } + + /* + * Argh. Do further DNS lookups and match interface addresses. + */ + for (mx = mx_list; mx != 0; mx = mx->next) { + if (msg_verbose) + msg_info("%s: address lookup: %s", myname, (char *) mx->data); + if (has_my_addr(state, (char *) mx->data, reply_name, reply_class)) + return (YUP); + } + + /* + * This machine is not listed as MX relay. + */ + if (msg_verbose) + msg_info("%s: I am not listed as MX relay", myname); + return (NOPE); +} + +/* permit_mx_primary - authorize primary MX relays */ + +static int permit_mx_primary(SMTPD_STATE *state, DNS_RR *mx_list, + const char *reply_name, const char *reply_class) +{ + const char *myname = "permit_mx_primary"; + DNS_RR *mx; + + if (msg_verbose) + msg_info("%s", myname); + + /* + * See if each best MX host has all IP addresses in + * permit_mx_backup_networks. + */ + for (mx = mx_list; mx != 0; mx = mx->next) { + if (!all_auth_mx_addr(state, (char *) mx->data, reply_name, reply_class)) + return (NOPE); + } + + /* + * All IP addresses of the best MX hosts are within + * permit_mx_backup_networks. + */ + return (YUP); +} + +/* permit_mx_backup - permit use of me as MX backup for recipient domain */ + +static int permit_mx_backup(SMTPD_STATE *state, const char *recipient, + const char *reply_name, const char *reply_class) +{ + const char *myname = "permit_mx_backup"; + const RESOLVE_REPLY *reply; + const char *domain; + const char *adomain; + DNS_RR *mx_list; + DNS_RR *middle; + DNS_RR *rest; + int dns_status; + + if (msg_verbose) + msg_info("%s: %s", myname, recipient); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(state->sender, recipient); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, recipient); + + /* + * For backwards compatibility, emulate permit_auth_destination. However, + * old permit_mx_backup implementations allow source routing with local + * address class. + */ + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) + return (SMTPD_CHECK_OK); + domain += 1; +#if 0 + if (reply->flags & RESOLVE_CLASS_LOCAL) + return (SMTPD_CHECK_OK); +#endif + if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED)) + return (SMTPD_CHECK_DUNNO); + if (reply->flags & RESOLVE_CLASS_FINAL) + return (SMTPD_CHECK_OK); + if (reply->flags & RESOLVE_CLASS_RELAY) { + if (warn_compat_break_relay_domains) + msg_info("using backwards-compatible default setting " + VAR_RELAY_DOMAINS "=$mydestination to accept mail " + "for domain \"%s\"", domain); + return (SMTPD_CHECK_OK); + } + if (msg_verbose) + msg_info("%s: not local: %s", myname, recipient); + + /* + * Skip numerical forms that didn't match the local system. + */ + if (domain[0] == '[' && domain[strlen(domain) - 1] == ']') + return (SMTPD_CHECK_DUNNO); + + /* + * Fix 20140924: convert domain to ASCII. + */ +#ifndef NO_EAI + if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", domain, adomain); + domain = adomain; + } +#endif + + /* + * Look up the list of MX host names for this domain. If no MX host is + * found, perhaps it is a CNAME for the local machine. Clients aren't + * supposed to send CNAMEs in SMTP commands, but it happens anyway. If we + * can't look up the destination, play safe and turn reject into defer. + */ + dns_status = dns_lookup(domain, T_MX, 0, &mx_list, + (VSTRING *) 0, (VSTRING *) 0); +#if 0 + if (dns_status == DNS_NOTFOUND) + return (has_my_addr(state, domain, reply_name, reply_class) ? + SMTPD_CHECK_OK : SMTPD_CHECK_DUNNO); +#endif + if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ + /* We don't special-case DNS_NULLMX. */ + if (dns_status == DNS_RETRY || dns_status == DNS_POLICY) + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + 450, "4.4.4", + "<%s>: %s rejected: Unable to look up mail " + "exchanger information: %s", + reply_name, reply_class, dns_status == DNS_POLICY ? + "DNS reply filter policy" : dns_strerror(h_errno)); + return (SMTPD_CHECK_DUNNO); + } + + /* + * Separate MX list into primaries and backups. + */ + mx_list = dns_rr_sort(mx_list, dns_rr_compare_pref_any); + for (middle = mx_list; /* see below */ ; middle = rest) { + rest = middle->next; + if (rest == 0) + break; + if (rest->pref != mx_list->pref) { + middle->next = 0; + break; + } + } + /* postcondition: middle->next = 0, rest may be 0. */ + +#define PERMIT_MX_BACKUP_RETURN(x) do { \ + middle->next = rest; \ + dns_rr_free(mx_list); \ + return (x); \ + } while (0) + + /* + * First, see if we match any of the primary MX servers. + */ + if (i_am_mx(state, mx_list, reply_name, reply_class)) + PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO); + + /* + * Then, see if we match any of the backup MX servers. + */ + if (rest == 0 || !i_am_mx(state, rest, reply_name, reply_class)) + PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO); + + /* + * Optionally, see if the primary MX hosts are in a restricted list of + * networks. + */ + if (*var_perm_mx_networks + && !permit_mx_primary(state, mx_list, reply_name, reply_class)) + PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO); + + /* + * The destination passed all requirements. + */ + PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_OK); +} + +/* reject_non_fqdn_address - fail if address is not in fqdn form */ + +static int reject_non_fqdn_address(SMTPD_STATE *state, char *addr, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_non_fqdn_address"; + char *domain; + char *test_dom; + int stat; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* + * Locate the domain information. + */ + if ((domain = strrchr(addr, '@')) != 0) + domain++; + else + domain = ""; + + /* + * Skip forms that we can't handle yet. + */ + if (domain[0] == '[' && domain[strlen(domain) - 1] == ']') + return (SMTPD_CHECK_DUNNO); + + /* + * Truncate names ending in dot but not dot-dot. + */ + test_dom = dup_if_truncate(domain); + + /* + * Validate the domain. For backwards compatibility, permit non-ASCII + * names only when the client requested SMTPUTF8 support. + */ + if (!*test_dom || !valid_utf8_hostname(state->flags & SMTPD_FLAG_SMTPUTF8, + test_dom, DONT_GRIPE) || !strchr(test_dom, '.')) + stat = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_non_fqdn_code, "4.5.2", + "<%s>: %s rejected: need fully-qualified address", + reply_name, reply_class); + else + stat = SMTPD_CHECK_DUNNO; + + /* + * Cleanup. + */ + if (test_dom != domain) + myfree(test_dom); + + return (stat); +} + +/* reject_unknown_address - fail if address does not resolve */ + +static int reject_unknown_address(SMTPD_STATE *state, const char *addr, + const char *reply_name, const char *reply_class) +{ + const char *myname = "reject_unknown_address"; + const RESOLVE_REPLY *reply; + const char *domain; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + state->recipient : state->sender, addr); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, addr); + + /* + * Skip local destinations and non-DNS forms. + */ + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) + return (SMTPD_CHECK_DUNNO); + domain += 1; + if (reply->flags & RESOLVE_CLASS_FINAL) + return (SMTPD_CHECK_DUNNO); + if (domain[0] == '[' && domain[strlen(domain) - 1] == ']') + return (SMTPD_CHECK_DUNNO); + + /* + * Look up the name in the DNS. + */ + return (reject_unknown_mailhost(state, domain, reply_name, reply_class)); +} + +/* reject_unverified_address - fail if address bounces */ + +static int reject_unverified_address(SMTPD_STATE *state, const char *addr, + const char *reply_name, const char *reply_class, + int unv_addr_dcode, int unv_addr_rcode, + int unv_addr_tf_act, + const char *alt_reply) +{ + const char *myname = "reject_unverified_address"; + VSTRING *why = vstring_alloc(10); + int rqst_status = SMTPD_CHECK_DUNNO; + int rcpt_status; + int verify_status; + int count; + int reject_code = 0; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* + * Verify the address. Don't waste too much of their or our time. + */ + for (count = 0; /* see below */ ; /* see below */ ) { + verify_status = verify_clnt_query(addr, &rcpt_status, why); + if (verify_status != VRFY_STAT_OK || rcpt_status != DEL_RCPT_STAT_TODO) + break; + if (++count >= var_verify_poll_count) + break; + sleep(var_verify_poll_delay); + } + if (verify_status != VRFY_STAT_OK) { + msg_warn("%s service failure", var_verify_service); + rqst_status = + DEFER_IF_PERMIT2(unv_addr_tf_act, state, MAIL_ERROR_POLICY, + 450, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + SND_DSN : "4.1.1", + "<%s>: %s rejected: address verification problem", + reply_name, reply_class); + } else { + switch (rcpt_status) { + default: + msg_warn("unknown address verification status %d", rcpt_status); + break; + case DEL_RCPT_STAT_TODO: + case DEL_RCPT_STAT_DEFER: + reject_code = unv_addr_dcode; + break; + case DEL_RCPT_STAT_OK: + break; + case DEL_RCPT_STAT_BOUNCE: + reject_code = unv_addr_rcode; + break; + } + if (reject_code >= 400 && *alt_reply) + vstring_strcpy(why, alt_reply); + switch (reject_code / 100) { + case 2: + break; + case 4: + rqst_status = + DEFER_IF_PERMIT3(unv_addr_tf_act, state, MAIL_ERROR_POLICY, + reject_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + SND_DSN : "4.1.1", + "<%s>: %s rejected: unverified address: %.250s", + reply_name, reply_class, STR(why)); + break; + default: + if (reject_code != 0) + rqst_status = + smtpd_check_reject(state, MAIL_ERROR_POLICY, + reject_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + SND_DSN : "4.1.1", + "<%s>: %s rejected: undeliverable address: %s", + reply_name, reply_class, STR(why)); + break; + } + } + vstring_free(why); + return (rqst_status); +} + +/* can_delegate_action - can we delegate this to the cleanup server */ + +#ifndef TEST + +static int not_in_client_helo(SMTPD_STATE *, const char *, const char *, const char *); + +static int can_delegate_action(SMTPD_STATE *state, const char *table, + const char *action, const char *reply_class) +{ + + /* + * If we're not using the cleanup server, then there is no way that we + * can support actions such as FILTER or HOLD that are delegated to the + * cleanup server. + */ + if (USE_SMTPD_PROXY(state)) { + msg_warn("access table %s: with %s specified, action %s is unavailable", + table, VAR_SMTPD_PROXY_FILT, action); + return (0); + } + + /* + * ETRN does not receive mail so we can't store queue file records. + */ + if (strcmp(state->where, SMTPD_CMD_ETRN) == 0) { + msg_warn("access table %s: action %s is unavailable in %s", + table, action, VAR_ETRN_CHECKS); + return (0); + } + return (not_in_client_helo(state, table, action, reply_class)); +} + +/* not_in_client_helo - not in client or helo restriction context */ + +static int not_in_client_helo(SMTPD_STATE *state, const char *table, + const char *action, + const char *unused_reply_class) +{ + + /* + * If delay_reject=no, then client and helo restrictions take effect + * immediately, outside any particular mail transaction context. For + * example, rejecting HELO does not affect subsequent mail deliveries. + * Thus, if delay_reject=no, client and helo actions such as FILTER or + * HOLD also should not affect subsequent mail deliveries. Hmm... + * + * XXX If the MAIL FROM command is rejected then we have to reset access map + * side effects such as FILTER. + */ + if (state->sender == 0) { + msg_warn("access table %s: with %s=%s, " + "action %s is always skipped in %s or %s restrictions", + table, VAR_SMTPD_DELAY_REJECT, CONFIG_BOOL_NO, + action, SMTPD_NAME_CLIENT, SMTPD_NAME_HELO); + /* XXX What about ETRN? */ + return (0); + } + return (1); +} + +#endif + +/* check_table_result - translate table lookup result into pass/reject */ + +static int check_table_result(SMTPD_STATE *state, const char *table, + const char *value, const char *datum, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_table_result"; + int code; + ARGV *restrictions; + jmp_buf savebuf; + int status; + const char *cmd_text; + int cmd_len; + static char def_dsn[] = "5.7.1"; + DSN_SPLIT dp; + static VSTRING *buf; + +#ifdef DELAY_ACTION + int defer_delay; + +#endif + + if (buf == 0) + buf = vstring_alloc(10); + + /* + * Parse into command and text. Do not change the input. + */ + cmd_text = value + strcspn(value, " \t"); + cmd_len = cmd_text - value; + vstring_strncpy(buf, value, cmd_len); + while (*cmd_text && ISSPACE(*cmd_text)) + cmd_text++; + + if (msg_verbose) + msg_info("%s: %s %s %s", myname, table, value, datum); + +#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0) + + /* + * DUNNO means skip this table. Silently ignore optional text. + */ + if (STREQUAL(value, "DUNNO", cmd_len)) + return (SMTPD_CHECK_DUNNO); + + /* + * REJECT means NO. Use optional text or generate a generic error + * response. + */ + if (STREQUAL(value, "REJECT", cmd_len)) { + dsn_split(&dp, "5.7.1", cmd_text); + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_map_reject_code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Access denied")); + } + + /* + * DEFER means "try again". Use optional text or generate a generic error + * response. + */ + if (STREQUAL(value, "DEFER", cmd_len)) { + dsn_split(&dp, "4.7.1", cmd_text); + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_map_defer_code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Access denied")); + } +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif + + /* + * HANGUP. Text is optional. Drop the connection without sending any + * reply. + * + * Note: this is an unsupported test feature. No attempt is made to maintain + * compatibility between successive versions. + */ + if (STREQUAL(value, "HANGUP", cmd_len)) { + shutdown(vstream_fileno(state->client), SHUT_RDWR); + log_whatsup(state, "hangup", cmd_text); + vstream_longjmp(state->client, SMTP_ERR_QUIET); + } + + /* + * INFO. Text is optional. + */ + if (STREQUAL(value, "INFO", cmd_len)) { + log_whatsup(state, "info", cmd_text); + return (SMTPD_CHECK_DUNNO); + } + + /* + * WARN. Text is optional. + */ + if (STREQUAL(value, "WARN", cmd_len)) { + log_whatsup(state, "warn", cmd_text); + return (SMTPD_CHECK_DUNNO); + } + + /* + * FILTER means deliver to content filter. But we may still change our + * mind, and reject/discard the message for other reasons. + */ + if (STREQUAL(value, "FILTER", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "FILTER", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (*cmd_text == 0) { + msg_warn("access table %s entry \"%s\" has FILTER entry without value", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else if (strchr(cmd_text, ':') == 0) { + msg_warn("access table %s entry \"%s\" requires transport:destination", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else { + vstring_sprintf(error_text, "<%s>: %s triggers FILTER %s", + reply_name, reply_class, cmd_text); + log_whatsup(state, "filter", STR(error_text)); +#ifndef TEST + UPDATE_STRING(state->saved_filter, cmd_text); +#endif + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * HOLD means deliver later. But we may still change our mind, and + * reject/discard the message for other reasons. + */ + if (STREQUAL(value, "HOLD", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "HOLD", reply_class) == 0 + || (state->saved_flags & CLEANUP_FLAG_HOLD)) + return (SMTPD_CHECK_DUNNO); +#endif + vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class, + *cmd_text ? cmd_text : "triggers HOLD action"); + log_whatsup(state, "hold", STR(error_text)); +#ifndef TEST + state->saved_flags |= CLEANUP_FLAG_HOLD; +#endif + return (SMTPD_CHECK_DUNNO); + } + + /* + * DELAY means deliver later. But we may still change our mind, and + * reject/discard the message for other reasons. + * + * This feature is deleted because it has too many problems. 1) It does not + * work on some remote file systems; 2) mail will be delivered anyway + * with "sendmail -q" etc.; 3) while the mail is queued it bogs down the + * deferred queue scan with huge amounts of useless disk I/O operations. + */ +#ifdef DELAY_ACTION + if (STREQUAL(value, "DELAY", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "DELAY", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (*cmd_text == 0) { + msg_warn("access table %s entry \"%s\" has DELAY entry without value", + table, datum); + return (SMTPD_CHECK_DUNNO); + } + if (conv_time(cmd_text, &defer_delay, 's') == 0) { + msg_warn("access table %s entry \"%s\" has invalid DELAY argument \"%s\"", + table, datum, cmd_text); + return (SMTPD_CHECK_DUNNO); + } + vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class, + *cmd_text ? cmd_text : "triggers DELAY action"); + log_whatsup(state, "delay", STR(error_text)); +#ifndef TEST + state->saved_delay = defer_delay; +#endif + return (SMTPD_CHECK_DUNNO); + } +#endif + + /* + * DISCARD means silently discard and claim successful delivery. + */ + if (STREQUAL(value, "DISCARD", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "DISCARD", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class, + *cmd_text ? cmd_text : "triggers DISCARD action"); + log_whatsup(state, "discard", STR(error_text)); +#ifndef TEST + state->saved_flags |= CLEANUP_FLAG_DISCARD; + state->discard = 1; +#endif + return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name, + "from %s", table)); + } + + /* + * REDIRECT means deliver to designated recipient. But we may still + * change our mind, and reject/discard the message for other reasons. + */ + if (STREQUAL(value, "REDIRECT", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "REDIRECT", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (strchr(cmd_text, '@') == 0) { + msg_warn("access table %s entry \"%s\" requires user@domain target", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else { + vstring_sprintf(error_text, "<%s>: %s triggers REDIRECT %s", + reply_name, reply_class, cmd_text); + log_whatsup(state, "redirect", STR(error_text)); +#ifndef TEST + UPDATE_STRING(state->saved_redirect, cmd_text); +#endif + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * BCC means deliver to designated recipient. But we may still change our + * mind, and reject/discard the message for other reasons. + */ + if (STREQUAL(value, "BCC", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "BCC", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (strchr(cmd_text, '@') == 0) { + msg_warn("access table %s entry \"%s\" requires user@domain target", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else { + vstring_sprintf(error_text, "<%s>: %s triggers BCC %s", + reply_name, reply_class, cmd_text); + log_whatsup(state, "bcc", STR(error_text)); +#ifndef TEST + if (state->saved_bcc == 0) + state->saved_bcc = argv_alloc(1); + argv_add(state->saved_bcc, cmd_text, (char *) 0); +#endif + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * DEFER_IF_PERMIT changes "permit" into "maybe". Use optional text or + * generate a generic error response. + */ + if (STREQUAL(value, DEFER_IF_PERMIT, cmd_len)) { + dsn_split(&dp, "4.7.1", cmd_text); + return (DEFER_IF_PERMIT3(DEFER_IF_PERMIT_ACT, state, MAIL_ERROR_POLICY, + var_map_defer_code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Service unavailable")); + } + + /* + * DEFER_IF_REJECT changes "reject" into "maybe". Use optional text or + * generate a generic error response. + */ + if (STREQUAL(value, DEFER_IF_REJECT, cmd_len)) { + dsn_split(&dp, "4.7.1", cmd_text); + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + var_map_defer_code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Service unavailable"); + return (SMTPD_CHECK_DUNNO); + } + + /* + * PREPEND prepends the specified message header text. + */ + if (STREQUAL(value, "PREPEND", cmd_len)) { +#ifndef TEST + /* XXX what about ETRN. */ + if (not_in_client_helo(state, table, "PREPEND", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (strcmp(state->where, SMTPD_AFTER_EOM) == 0) { + msg_warn("access table %s: action PREPEND must be used before %s", + table, VAR_EOD_CHECKS); + return (SMTPD_CHECK_DUNNO); + } + if (*cmd_text == 0 || is_header(cmd_text) == 0) { + msg_warn("access table %s entry \"%s\" requires header: text", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else { + if (state->prepend == 0) + state->prepend = argv_alloc(1); + argv_add(state->prepend, cmd_text, (char *) 0); + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * All-numeric result probably means OK - some out-of-band authentication + * mechanism uses this as time stamp. + */ + if (alldig(value)) + return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name, + "from %s", table)); + + /* + * 4xx or 5xx means NO as well. smtpd_check_reject() will validate the + * response status code. + * + * If the caller specifies an RFC 3463 enhanced status code, put it + * immediately after the SMTP status code as described in RFC 2034. + */ + if (cmd_len == 3 && *cmd_text + && (value[0] == '4' || value[0] == '5') + && ISDIGIT(value[1]) && ISDIGIT(value[2])) { + code = atoi(value); + def_dsn[0] = value[0]; + dsn_split(&dp, def_dsn, cmd_text); + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Access denied")); + } + + /* + * OK or RELAY means YES. Ignore trailing text. + */ + if (STREQUAL(value, "OK", cmd_len) || STREQUAL(value, "RELAY", cmd_len)) + return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name, + "from %s", table)); + + /* + * Unfortunately, maps must be declared ahead of time so they can be + * opened before we go to jail. We could insist that the RHS can only + * contain a pre-defined restriction class name, but that would be too + * restrictive. Instead we warn if an access table references any map. + * + * XXX Don't use passwd files or address rewriting maps as access tables. + */ + if (strchr(value, ':') != 0) { + msg_warn("access table %s has entry with lookup table: %s", + table, value); + msg_warn("do not specify lookup tables inside SMTPD access maps"); + msg_warn("define a restriction class and specify its name instead."); + reject_server_error(state); + } + + /* + * Don't get carried away with recursion. + */ + if (state->recursion > 100) { + msg_warn("access table %s entry %s causes unreasonable recursion", + table, value); + reject_server_error(state); + } + + /* + * Recursively evaluate the restrictions given in the right-hand side. In + * the dark ages, an empty right-hand side meant OK. Make some + * discouraging comments. + * + * XXX Jump some hoops to avoid a minute memory leak in case of a file + * configuration error. + */ +#define ADDROF(x) ((char *) &(x)) + + restrictions = argv_splitq(value, CHARS_COMMA_SP, CHARS_BRACE); + memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf)); + status = setjmp(smtpd_check_buf); + if (status != 0) { + argv_free(restrictions); + memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), + sizeof(smtpd_check_buf)); + longjmp(smtpd_check_buf, status); + } + if (restrictions->argc == 0) { + msg_warn("access table %s entry %s has empty value", + table, value); + status = SMTPD_CHECK_OK; + } else { + status = generic_checks(state, restrictions, reply_name, + reply_class, def_acl); + } + argv_free(restrictions); + memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), sizeof(smtpd_check_buf)); + return (status); +} + +/* check_access - table lookup without substring magic */ + +static int check_access(SMTPD_STATE *state, const char *table, const char *name, + int flags, int *found, const char *reply_name, + const char *reply_class, const char *def_acl) +{ + const char *myname = "check_access"; + const char *value; + MAPS *maps; + +#define CHK_ACCESS_RETURN(x,y) \ + { *found = y; return(x); } +#define FULL 0 +#define PARTIAL DICT_FLAG_FIXED +#define FOUND 1 +#define MISSED 0 + + if (msg_verbose) + msg_info("%s: %s", myname, name); + + if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + CHK_ACCESS_RETURN(check_table_result(state, table, value, name, + reply_name, reply_class, + def_acl), FOUND); + } + if ((value = maps_find(maps, name, flags)) != 0) + CHK_ACCESS_RETURN(check_table_result(state, table, value, name, + reply_name, reply_class, + def_acl), FOUND); + if (maps->error != 0) { + /* Warning is already logged. */ + value = "451 4.3.5 Server configuration error"; + CHK_ACCESS_RETURN(check_table_result(state, table, value, name, + reply_name, reply_class, + def_acl), FOUND); + } + CHK_ACCESS_RETURN(SMTPD_CHECK_DUNNO, MISSED); +} + +/* check_domain_access - domainname-based table lookup */ + +static int check_domain_access(SMTPD_STATE *state, const char *table, + const char *domain, int flags, + int *found, const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_domain_access"; + const char *name; + const char *next; + const char *value; + MAPS *maps; + int maybe_numerical = 1; + + if (msg_verbose) + msg_info("%s: %s", myname, domain); + + /* + * Try the name and its parent domains. Including top-level domains. + * + * Helo names can end in ".". The test below avoids lookups of the empty + * key, because Berkeley DB cannot deal with it. [Victor Duchovni, Morgan + * Stanley]. + * + * TODO(wietse) move to mail_domain_find library module. + */ +#define CHK_DOMAIN_RETURN(x,y) { *found = y; return(x); } + + if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + CHK_DOMAIN_RETURN(check_table_result(state, table, value, + domain, reply_name, reply_class, + def_acl), FOUND); + } + for (name = domain; *name != 0; name = next) { + if ((value = maps_find(maps, name, flags)) != 0) + CHK_DOMAIN_RETURN(check_table_result(state, table, value, + domain, reply_name, reply_class, + def_acl), FOUND); + if (maps->error != 0) { + /* Warning is already logged. */ + value = "451 4.3.5 Server configuration error"; + CHK_DOMAIN_RETURN(check_table_result(state, table, value, + domain, reply_name, reply_class, + def_acl), FOUND); + } + /* Don't apply subdomain magic to numerical hostnames. */ + if (maybe_numerical + && (maybe_numerical = valid_hostaddr(domain, DONT_GRIPE)) != 0) + break; + if ((next = strchr(name + 1, '.')) == 0) + break; + if (access_parent_style == MATCH_FLAG_PARENT) + next += 1; + flags = PARTIAL; + } + CHK_DOMAIN_RETURN(SMTPD_CHECK_DUNNO, MISSED); +} + +/* check_addr_access - address-based table lookup */ + +static int check_addr_access(SMTPD_STATE *state, const char *table, + const char *address, int flags, + int *found, const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_addr_access"; + char *addr; + const char *value; + MAPS *maps; + int delim; + + if (msg_verbose) + msg_info("%s: %s", myname, address); + + /* + * Try the address and its parent networks. + * + * TODO(wietse) move to mail_ipaddr_find library module. + */ +#define CHK_ADDR_RETURN(x,y) { *found = y; return(x); } + + addr = STR(vstring_strcpy(error_text, address)); +#ifdef HAS_IPV6 + if (strchr(addr, ':') != 0) + delim = ':'; + else +#endif + delim = '.'; + + if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + CHK_ADDR_RETURN(check_table_result(state, table, value, address, + reply_name, reply_class, + def_acl), FOUND); + } + do { + if ((value = maps_find(maps, addr, flags)) != 0) + CHK_ADDR_RETURN(check_table_result(state, table, value, address, + reply_name, reply_class, + def_acl), FOUND); + if (maps->error != 0) { + /* Warning is already logged. */ + value = "451 4.3.5 Server configuration error"; + CHK_ADDR_RETURN(check_table_result(state, table, value, address, + reply_name, reply_class, + def_acl), FOUND); + } + flags = PARTIAL; + } while (split_at_right(addr, delim)); + + CHK_ADDR_RETURN(SMTPD_CHECK_DUNNO, MISSED); +} + +/* check_namadr_access - OK/FAIL based on host name/address lookup */ + +static int check_namadr_access(SMTPD_STATE *state, const char *table, + const char *name, const char *addr, + int flags, int *found, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_namadr_access"; + int status; + + if (msg_verbose) + msg_info("%s: name %s addr %s", myname, name, addr); + + /* + * Look up the host name, or parent domains thereof. XXX A domain + * wildcard may pre-empt a more specific address table entry. + */ + if ((status = check_domain_access(state, table, name, flags, + found, reply_name, reply_class, + def_acl)) != 0 || *found) + return (status); + + /* + * Look up the network address, or parent networks thereof. + */ + if ((status = check_addr_access(state, table, addr, flags, + found, reply_name, reply_class, + def_acl)) != 0 || *found) + return (status); + + /* + * Undecided when the host was not found. + */ + return (SMTPD_CHECK_DUNNO); +} + +/* check_server_access - access control by server host name or address */ + +static int check_server_access(SMTPD_STATE *state, const char *table, + const char *name, + int type, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_server_access"; + const char *domain; + const char *adomain; + int dns_status; + DNS_RR *server_list; + DNS_RR *server; + int found = 0; + MAI_HOSTADDR_STR addr_string; + int aierr; + struct addrinfo *res0; + struct addrinfo *res; + int status; + INET_PROTO_INFO *proto_info; + + /* + * Sanity check. + */ + if (type != T_MX && type != T_NS && type != T_A +#ifdef HAS_IPV6 + && type != T_AAAA +#endif + ) + msg_panic("%s: unexpected resource type \"%s\" in request", + myname, dns_strtype(type)); + + if (msg_verbose) + msg_info("%s: %s %s", myname, dns_strtype(type), name); + + /* + * Skip over local-part. + */ + if ((domain = strrchr(name, '@')) != 0) + domain += 1; + else + domain = name; + + /* + * Treat an address literal as its own MX server, just like we treat a + * name without MX record as its own MX server. There is, however, no + * applicable NS server equivalent. + */ + if (*domain == '[') { + char *saved_addr; + const char *bare_addr; + ssize_t len; + + if (type != T_A && type != T_MX) + return (SMTPD_CHECK_DUNNO); + len = strlen(domain); + if (domain[len - 1] != ']') + return (SMTPD_CHECK_DUNNO); + /* Memory leak alert: no early returns after this point. */ + saved_addr = mystrndup(domain + 1, len - 2); + if ((bare_addr = valid_mailhost_addr(saved_addr, DONT_GRIPE)) == 0) + status = SMTPD_CHECK_DUNNO; + else + status = check_addr_access(state, table, bare_addr, FULL, + &found, reply_name, reply_class, + def_acl); + myfree(saved_addr); + return (status); + } + + /* + * Fix 20140924: convert domain to ASCII. + */ +#ifndef NO_EAI + if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", domain, adomain); + domain = adomain; + } +#endif + + /* + * If the request is type A or AAAA, fabricate an MX record that points + * to the domain name itself, and skip name-based access control. + * + * If the domain name does not exist then we apply no restriction. + * + * If the domain name exists but no MX record exists, fabricate an MX record + * that points to the domain name itself. + * + * If the domain name exists but no NS record exists, look up parent domain + * NS records. + * + * XXX 20150707 Work around broken DNS servers that reply with NXDOMAIN + * instead of "no data". + */ + if (type == T_A +#ifdef HAS_IPV6 + || type == T_AAAA +#endif + ) { + server_list = dns_rr_create(domain, domain, T_MX, C_IN, 0, 0, + domain, strlen(domain) + 1); + } else { + dns_status = dns_lookup(domain, type, 0, &server_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status == DNS_NULLMX) + return (SMTPD_CHECK_DUNNO); + if (dns_status == DNS_NOTFOUND /* Not: h_errno == NO_DATA */ ) { + if (type == T_MX) { + server_list = dns_rr_create(domain, domain, type, C_IN, 0, 0, + domain, strlen(domain) + 1); + dns_status = DNS_OK; + } else if (type == T_NS /* && h_errno == NO_DATA */ ) { + while ((domain = strchr(domain, '.')) != 0 && domain[1]) { + domain += 1; + dns_status = dns_lookup(domain, type, 0, &server_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status != DNS_NOTFOUND /* || h_errno != NO_DATA */ ) + break; + } + } + } + if (dns_status != DNS_OK) { + msg_warn("Unable to look up %s host for %s: %s", dns_strtype(type), + domain && domain[1] ? domain : name, dns_status == DNS_POLICY ? + "DNS reply filter policy" : dns_strerror(h_errno)); + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * No bare returns after this point or we have a memory leak. + */ +#define CHECK_SERVER_RETURN(x) { dns_rr_free(server_list); return(x); } + + /* + * Check the hostnames first, then the addresses. + */ + proto_info = inet_proto_info(); + for (server = server_list; server != 0; server = server->next) { + if (msg_verbose) + msg_info("%s: %s hostname check: %s", + myname, dns_strtype(type), (char *) server->data); + if (valid_hostaddr((char *) server->data, DONT_GRIPE)) { + if ((status = check_addr_access(state, table, (char *) server->data, + FULL, &found, reply_name, reply_class, + def_acl)) != 0 || found) + CHECK_SERVER_RETURN(status); + continue; + } + if (type != T_A && type != T_AAAA + && ((status = check_domain_access(state, table, (char *) server->data, + FULL, &found, reply_name, reply_class, + def_acl)) != 0 || found)) + CHECK_SERVER_RETURN(status); + if ((aierr = hostname_to_sockaddr((char *) server->data, + (char *) 0, 0, &res0)) != 0) { + if (type != T_A && type != T_AAAA) + msg_warn("Unable to look up %s host %s for %s %s: %s", + dns_strtype(type), (char *) server->data, + reply_class, reply_name, MAI_STRERROR(aierr)); + continue; + } + /* Now we must also free the addrinfo result. */ + if (msg_verbose) + msg_info("%s: %s host address check: %s", + myname, dns_strtype(type), (char *) server->data); + for (res = res0; res != 0; res = res->ai_next) { + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + if (msg_verbose) + msg_info("skipping address family %d for host %s", + res->ai_family, server->data); + continue; + } + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &addr_string, (MAI_SERVPORT_STR *) 0, 0); + status = check_addr_access(state, table, addr_string.buf, FULL, + &found, reply_name, reply_class, + def_acl); + if (status != 0 || found) { + freeaddrinfo(res0); /* 200412 */ + CHECK_SERVER_RETURN(status); + } + } + freeaddrinfo(res0); /* 200412 */ + } + CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO); +} + +/* check_ccert_access - access for TLS clients by certificate fingerprint */ + + +static int check_ccert_access(SMTPD_STATE *state, const char *table, + const char *def_acl) +{ + int result = SMTPD_CHECK_DUNNO; + +#ifdef USE_TLS + const char *myname = "check_ccert_access"; + int found; + + /* + * When directly checking the fingerprint, it is OK if the issuing CA is + * not trusted. + */ + if (TLS_CERT_IS_PRESENT(state->tls_context)) { + int i; + char *prints[2]; + + prints[0] = state->tls_context->peer_cert_fprint; + prints[1] = state->tls_context->peer_pkey_fprint; + + for (i = 0; i < 2; ++i) { + if (msg_verbose) + msg_info("%s: %s", myname, prints[i]); + + /* + * Regexp tables don't make sense for certificate fingerprints. + * That may be so, but we can't ignore the entire + * check_ccert_access request without logging a warning. + * + * 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. + */ + result = check_access(state, table, prints[i], + DICT_FLAG_NONE, &found, + state->tls_context->peer_CN, + SMTPD_NAME_CCERT, def_acl); + if (result != SMTPD_CHECK_DUNNO) + break; + } + } +#endif + return (result); +} + +/* check_sasl_access - access by SASL user name */ + +#ifdef USE_SASL_AUTH + +static int check_sasl_access(SMTPD_STATE *state, const char *table, + const char *def_acl) +{ + int result; + int unused_found; + char *sane_username = printable(mystrdup(state->sasl_username), '_'); + + result = check_access(state, table, state->sasl_username, + DICT_FLAG_NONE, &unused_found, sane_username, + SMTPD_NAME_SASL_USER, def_acl); + myfree(sane_username); + return (result); +} + +#endif + +/* check_mail_access - OK/FAIL based on mail address lookup */ + +static int check_mail_access(SMTPD_STATE *state, const char *table, + const char *addr, int *found, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_mail_access"; + const RESOLVE_REPLY *reply; + const char *value; + int lookup_strategy; + int status; + MAPS *maps; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + state->recipient : state->sender, addr); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, addr); + + /* + * Garbage in, garbage out. Every address from rewrite_clnt_internal() + * and from resolve_clnt_query() must be fully qualified. + */ + if (strrchr(CONST_STR(reply->recipient), '@') == 0) { + msg_warn("%s: no @domain in address: %s", myname, + CONST_STR(reply->recipient)); + return (0); + } + + /* + * Source-routed (non-local or virtual) recipient addresses are too + * suspicious for returning an "OK" result. The complicated expression + * below was brought to you by the keyboard of Victor Duchovni, Morgan + * Stanley and hacked up a bit by Wietse. + */ +#define SUSPICIOUS(reply, reply_class) \ + (var_allow_untrust_route == 0 \ + && (reply->flags & RESOLVE_FLAG_ROUTED) \ + && strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0) + + /* + * Look up user+foo@domain if the address has an extension, user@domain + * otherwise. + */ + lookup_strategy = MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN + | MA_FIND_LOCALPART_AT + | (access_parent_style == MATCH_FLAG_PARENT ? + MA_FIND_PDMS : MA_FIND_PDDMDS); + + if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + return (check_table_result(state, table, value, + CONST_STR(reply->recipient), + reply_name, reply_class, + def_acl)); + } + if ((value = mail_addr_find_strategy(maps, CONST_STR(reply->recipient), + (char **) 0, lookup_strategy)) != 0) { + *found = 1; + status = check_table_result(state, table, value, + CONST_STR(reply->recipient), + reply_name, reply_class, def_acl); + return (status == SMTPD_CHECK_OK && SUSPICIOUS(reply, reply_class) ? + SMTPD_CHECK_DUNNO : status); + } else if (maps->error != 0) { + /* Warning is already logged. */ + value = "451 4.3.5 Server configuration error"; + return (check_table_result(state, table, value, + CONST_STR(reply->recipient), + reply_name, reply_class, + def_acl)); + } + + /* + * Undecided when no match found. + */ + return (SMTPD_CHECK_DUNNO); +} + +/* Support for different DNSXL lookup results. */ + +static SMTPD_RBL_STATE dnsxl_stat_soft[1]; + +#define SMTPD_DNSXL_STAT_SOFT(dnsxl_res) ((dnsxl_res) == dnsxl_stat_soft) +#define SMTPD_DNXSL_STAT_HARD(dnsxl_res) ((dnsxl_res) == 0) +#define SMTPD_DNSXL_STAT_OK(dnsxl_res) \ + !(SMTPD_DNXSL_STAT_HARD(dnsxl_res) || SMTPD_DNSXL_STAT_SOFT(dnsxl_res)) + +/* rbl_pagein - look up an RBL lookup result */ + +static void *rbl_pagein(const char *query, void *unused_context) +{ + DNS_RR *txt_list; + VSTRING *why; + int dns_status; + SMTPD_RBL_STATE *rbl = 0; + DNS_RR *addr_list; + DNS_RR *rr; + DNS_RR *next; + VSTRING *buf; + int space_left; + + /* + * Do the query. If the DNS lookup produces no definitive reply, give the + * requestor the benefit of the doubt. We can't block all email simply + * because an RBL server is unavailable. + * + * Don't do this for AAAA records. Yet. + */ + why = vstring_alloc(10); + dns_status = dns_lookup(query, T_A, 0, &addr_list, (VSTRING *) 0, why); + if (dns_status != DNS_OK && dns_status != DNS_NOTFOUND) { + msg_warn("%s: RBL lookup error: %s", query, STR(why)); + rbl = dnsxl_stat_soft; + } + vstring_free(why); + if (dns_status != DNS_OK) + return ((void *) rbl); + + /* + * Save the result. Yes, we cache negative results as well as positive + * results. Concatenate multiple TXT records, up to some limit. + */ +#define RBL_TXT_LIMIT 500 + + rbl = (SMTPD_RBL_STATE *) mymalloc(sizeof(*rbl)); + dns_status = dns_lookup(query, T_TXT, 0, &txt_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status == DNS_OK) { + buf = vstring_alloc(1); + space_left = RBL_TXT_LIMIT; + for (rr = txt_list; rr != 0 && space_left > 0; rr = next) { + vstring_strncat(buf, rr->data, (int) rr->data_len > space_left ? + space_left : rr->data_len); + space_left = RBL_TXT_LIMIT - VSTRING_LEN(buf); + next = rr->next; + if (next && space_left > 3) { + vstring_strcat(buf, " / "); + space_left -= 3; + } + } + rbl->txt = vstring_export(buf); + dns_rr_free(txt_list); + } else { + if (dns_status == DNS_POLICY) + msg_warn("%s: TXT lookup error: %s", + query, "DNS reply filter drops all results"); + rbl->txt = 0; + } + rbl->a = addr_list; + return ((void *) rbl); +} + +/* rbl_pageout - discard an RBL lookup result */ + +static void rbl_pageout(void *data, void *unused_context) +{ + SMTPD_RBL_STATE *rbl = (SMTPD_RBL_STATE *) data; + + if (SMTPD_DNSXL_STAT_OK(rbl)) { + if (rbl->txt) + myfree(rbl->txt); + if (rbl->a) + dns_rr_free(rbl->a); + myfree((void *) rbl); + } +} + +/* rbl_byte_pagein - parse RBL reply pattern, save byte codes */ + +static void *rbl_byte_pagein(const char *query, void *unused_context) +{ + VSTRING *byte_codes = vstring_alloc(100); + char *saved_query = mystrdup(query); + char *saved_byte_codes; + char *err; + + if ((err = ip_match_parse(byte_codes, saved_query)) != 0) + msg_fatal("RBL reply error: %s", err); + saved_byte_codes = ip_match_save(byte_codes); + myfree(saved_query); + vstring_free(byte_codes); + return (saved_byte_codes); +} + +/* rbl_byte_pageout - discard parsed RBL reply byte codes */ + +static void rbl_byte_pageout(void *data, void *unused_context) +{ + myfree(data); +} + +/* rbl_expand_lookup - RBL specific $name expansion */ + +static const char *rbl_expand_lookup(const char *name, int mode, + void *context) +{ + SMTPD_RBL_EXPAND_CONTEXT *rbl_exp = (SMTPD_RBL_EXPAND_CONTEXT *) context; + SMTPD_STATE *state = rbl_exp->state; + +#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0) + + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(10); + + if (msg_verbose > 1) + msg_info("rbl_expand_lookup: ${%s}", name); + + /* + * Be sure to return NULL only for non-existent names. + */ + if (STREQ(name, MAIL_ATTR_RBL_CODE)) { + vstring_sprintf(state->expand_buf, "%d", var_maps_rbl_code); + return (STR(state->expand_buf)); + } else if (STREQ(name, MAIL_ATTR_RBL_DOMAIN)) { + return (rbl_exp->domain); + } else if (STREQ(name, MAIL_ATTR_RBL_REASON)) { + return (rbl_exp->txt); + } else if (STREQ(name, MAIL_ATTR_RBL_TXT)) {/* LaMont compat */ + return (rbl_exp->txt); + } else if (STREQ(name, MAIL_ATTR_RBL_WHAT)) { + return (rbl_exp->what); + } else if (STREQ(name, MAIL_ATTR_RBL_CLASS)) { + return (rbl_exp->class); + } else { + return (smtpd_expand_lookup(name, mode, (void *) state)); + } +} + +/* rbl_reject_reply - format reply after RBL reject */ + +static int rbl_reject_reply(SMTPD_STATE *state, const SMTPD_RBL_STATE *rbl, + const char *rbl_domain, + const char *what, + const char *reply_class) +{ + const char *myname = "rbl_reject_reply"; + VSTRING *why = 0; + const char *template = 0; + SMTPD_RBL_EXPAND_CONTEXT rbl_exp; + int result; + DSN_SPLIT dp; + int code; + + /* + * Use the server-specific reply template or use the default one. + */ + if (*var_rbl_reply_maps) { + template = maps_find(rbl_reply_maps, rbl_domain, DICT_FLAG_NONE); + if (rbl_reply_maps->error) + reject_server_error(state); + } + why = vstring_alloc(100); + rbl_exp.state = state; + rbl_exp.domain = mystrdup(rbl_domain); + (void) split_at(rbl_exp.domain, '='); + rbl_exp.what = what; + rbl_exp.class = reply_class; + rbl_exp.txt = (rbl->txt == 0 ? "" : rbl->txt); + + for (;;) { + if (template == 0) + template = var_def_rbl_reply; + if (mac_expand(why, template, MAC_EXP_FLAG_NONE, + STR(smtpd_expand_filter), rbl_expand_lookup, + (void *) &rbl_exp) == 0) + break; + if (template == var_def_rbl_reply) + msg_fatal("%s: bad default rbl reply template: %s", + myname, var_def_rbl_reply); + msg_warn("%s: bad rbl reply template for domain %s: %s", + myname, rbl_domain, template); + template = 0; /* pretend not found */ + } + + /* + * XXX Impedance mis-match. + * + * Validate the response, that is, the response must begin with a + * three-digit status code, and the first digit must be 4 or 5. If the + * response is bad, log a warning and send a generic response instead. + */ + if ((STR(why)[0] != '4' && STR(why)[0] != '5') + || !ISDIGIT(STR(why)[1]) || !ISDIGIT(STR(why)[2]) + || STR(why)[3] != ' ') { + msg_warn("rbl response code configuration error: %s", STR(why)); + result = smtpd_check_reject(state, MAIL_ERROR_POLICY, + 450, "4.7.1", "Service unavailable"); + } else { + code = atoi(STR(why)); + dsn_split(&dp, "4.7.1", STR(why) + 4); + result = smtpd_check_reject(state, MAIL_ERROR_POLICY, + code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "%s", *dp.text ? + dp.text : "Service unavailable"); + } + + /* + * Clean up. + */ + myfree(rbl_exp.domain); + vstring_free(why); + + return (result); +} + +/* rbl_match_addr - match address list */ + +static int rbl_match_addr(SMTPD_RBL_STATE *rbl, const char *byte_codes) +{ + const char *myname = "rbl_match_addr"; + DNS_RR *rr; + + for (rr = rbl->a; rr != 0; rr = rr->next) { + if (rr->type == T_A) { + if (ip_match_execute(byte_codes, rr->data)) + return (1); + } else { + msg_warn("%s: skipping record type %s for query %s", + myname, dns_strtype(rr->type), rr->qname); + } + } + return (0); +} + +/* find_dnsxl_addr - look up address in DNSXL */ + +static const SMTPD_RBL_STATE *find_dnsxl_addr(SMTPD_STATE *state, + const char *rbl_domain, + const char *addr) +{ + const char *myname = "find_dnsxl_addr"; + ARGV *octets; + VSTRING *query; + int i; + SMTPD_RBL_STATE *rbl; + const char *reply_addr; + const char *byte_codes; + struct addrinfo *res; + unsigned char *ipv6_addr; + + query = vstring_alloc(100); + + /* + * Reverse the client IPV6 address, represented as 32 hexadecimal + * nibbles. We use the binary address to avoid tricky code. Asking for an + * AAAA record makes no sense here. Just like with IPv4 we use the lookup + * result as a bit mask, not as an IP address. + */ +#ifdef HAS_IPV6 + if (valid_ipv6_hostaddr(addr, DONT_GRIPE)) { + if (hostaddr_to_sockaddr(addr, (char *) 0, 0, &res) != 0 + || res->ai_family != PF_INET6) + msg_fatal("%s: unable to convert address %s", myname, addr); + ipv6_addr = (unsigned char *) &SOCK_ADDR_IN6_ADDR(res->ai_addr); + for (i = sizeof(SOCK_ADDR_IN6_ADDR(res->ai_addr)) - 1; i >= 0; i--) + vstring_sprintf_append(query, "%x.%x.", + ipv6_addr[i] & 0xf, ipv6_addr[i] >> 4); + freeaddrinfo(res); + } else +#endif + + /* + * Reverse the client IPV4 address, represented as four decimal octet + * values. We use the textual address for convenience. + */ + { + octets = argv_split(addr, "."); + for (i = octets->argc - 1; i >= 0; i--) { + vstring_strcat(query, octets->argv[i]); + vstring_strcat(query, "."); + } + argv_free(octets); + } + + /* + * Tack on the RBL domain name and query the DNS for an A record. + */ + vstring_strcat(query, rbl_domain); + reply_addr = split_at(STR(query), '='); + rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query)); + if (reply_addr != 0) + byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr); + + /* + * If the record exists, match the result address. + */ + if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0 + && !rbl_match_addr(rbl, byte_codes)) + rbl = 0; + vstring_free(query); + return (rbl); +} + +/* reject_rbl_addr - reject address in real-time blackhole list */ + +static int reject_rbl_addr(SMTPD_STATE *state, const char *rbl_domain, + const char *addr, const char *reply_class) +{ + const char *myname = "reject_rbl_addr"; + const SMTPD_RBL_STATE *rbl; + + if (msg_verbose) + msg_info("%s: %s %s", myname, reply_class, addr); + + rbl = find_dnsxl_addr(state, rbl_domain, addr); + if (!SMTPD_DNSXL_STAT_OK(rbl)) { + return (SMTPD_CHECK_DUNNO); + } else { + return (rbl_reject_reply(state, rbl, rbl_domain, addr, reply_class)); + } +} + +/* permit_dnswl_addr - permit address in DNSWL */ + +static int permit_dnswl_addr(SMTPD_STATE *state, const char *dnswl_domain, + const char *addr, const char *reply_class) +{ + const char *myname = "permit_dnswl_addr"; + const SMTPD_RBL_STATE *dnswl_result; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* Safety: don't whitelist unauthorized recipients. */ + if (strcmp(state->where, SMTPD_CMD_RCPT) == 0 && state->recipient != 0 + && permit_auth_destination(state, state->recipient) != SMTPD_CHECK_OK) + return (SMTPD_CHECK_DUNNO); + + dnswl_result = find_dnsxl_addr(state, dnswl_domain, addr); + if (SMTPD_DNXSL_STAT_HARD(dnswl_result)) { + return (SMTPD_CHECK_DUNNO); + } else if (SMTPD_DNSXL_STAT_SOFT(dnswl_result)) { + /* XXX: Make configurable as dnswl_tempfail_action. */ + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + 450, "4.7.1", + "<%s>: %s rejected: %s", + addr, reply_class, + "Service unavailable"); + return (SMTPD_CHECK_DUNNO); + } else if (SMTPD_DNSXL_STAT_OK(dnswl_result)) { + return (SMTPD_CHECK_OK); + } else { + /* Future proofing, in case find_dnsxl_addr() result is changed. */ + msg_panic("%s: find_dnsxl_addr API failure", myname); + } +} + +/* find_dnsxl_domain - reject if domain in real-time blackhole list */ + +static const SMTPD_RBL_STATE *find_dnsxl_domain(SMTPD_STATE *state, + const char *rbl_domain, const char *what) +{ + VSTRING *query; + SMTPD_RBL_STATE *rbl; + const char *domain; + const char *reply_addr; + const char *byte_codes; + const char *suffix; + const char *adomain; + + /* + * Extract the domain, tack on the RBL domain name and query the DNS for + * an A record. + */ + if ((domain = strrchr(what, '@')) != 0) { + domain += 1; + if (domain[0] == '[') + return (SMTPD_CHECK_DUNNO); + } else + domain = what; + + /* + * XXX Some Spamhaus RHSBL rejects lookups with "No IP queries" even if + * the name has an alphanumerical prefix. We play safe, and skip both + * RHSBL and RHSWL queries for names ending in a numerical suffix. + */ + if (domain[0] == 0) + return (SMTPD_CHECK_DUNNO); + suffix = strrchr(domain, '.'); + if (alldig(suffix == 0 ? domain : suffix + 1)) + return (SMTPD_CHECK_DUNNO); + + /* + * Fix 20140706: convert domain to ASCII. + */ +#ifndef NO_EAI + if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", domain, adomain); + domain = adomain; + } +#endif + if (domain[0] == 0 || valid_hostname(domain, DONT_GRIPE) == 0) + return (SMTPD_CHECK_DUNNO); + + query = vstring_alloc(100); + vstring_sprintf(query, "%s.%s", domain, rbl_domain); + reply_addr = split_at(STR(query), '='); + rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query)); + if (reply_addr != 0) + byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr); + + /* + * If the record exists, match the result address. + */ + if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0 + && !rbl_match_addr(rbl, byte_codes)) + rbl = 0; + vstring_free(query); + return (rbl); +} + +/* reject_rbl_domain - reject if domain in real-time blackhole list */ + +static int reject_rbl_domain(SMTPD_STATE *state, const char *rbl_domain, + const char *what, const char *reply_class) +{ + const char *myname = "reject_rbl_domain"; + const SMTPD_RBL_STATE *rbl; + + if (msg_verbose) + msg_info("%s: %s %s", myname, rbl_domain, what); + + rbl = find_dnsxl_domain(state, rbl_domain, what); + if (!SMTPD_DNSXL_STAT_OK(rbl)) { + return (SMTPD_CHECK_DUNNO); + } else { + return (rbl_reject_reply(state, rbl, rbl_domain, what, reply_class)); + } +} + +/* permit_dnswl_domain - permit domain in DNSWL */ + +static int permit_dnswl_domain(SMTPD_STATE *state, const char *dnswl_domain, + const char *what, const char *reply_class) +{ + const char *myname = "permit_dnswl_domain"; + const SMTPD_RBL_STATE *dnswl_result; + + if (msg_verbose) + msg_info("%s: %s", myname, what); + + /* Safety: don't whitelist unauthorized recipients. */ + if (strcmp(state->where, SMTPD_CMD_RCPT) == 0 && state->recipient != 0 + && permit_auth_destination(state, state->recipient) != SMTPD_CHECK_OK) + return (SMTPD_CHECK_DUNNO); + + dnswl_result = find_dnsxl_domain(state, dnswl_domain, what); + if (SMTPD_DNXSL_STAT_HARD(dnswl_result)) { + return (SMTPD_CHECK_DUNNO); + } else if (SMTPD_DNSXL_STAT_SOFT(dnswl_result)) { + /* XXX: Make configurable as rhswl_tempfail_action. */ + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + 450, "4.7.1", + "<%s>: %s rejected: %s", + what, reply_class, + "Service unavailable"); + return (SMTPD_CHECK_DUNNO); + } else if (SMTPD_DNSXL_STAT_OK(dnswl_result)) { + return (SMTPD_CHECK_OK); + } else { + /* Future proofing, in case find_dnsxl_addr() result is changed. */ + msg_panic("%s: find_dnsxl_addr API failure", myname); + } +} + +/* reject_maps_rbl - reject if client address in real-time blackhole list */ + +static int reject_maps_rbl(SMTPD_STATE *state) +{ + const char *myname = "reject_maps_rbl"; + char *saved_domains = mystrdup(var_maps_rbl_domains); + char *bp = saved_domains; + char *rbl_domain; + int result = SMTPD_CHECK_DUNNO; + static int warned; + + if (msg_verbose) + msg_info("%s: %s", myname, state->addr); + + if (warned == 0) { + warned++; + msg_warn("support for restriction \"%s\" will be removed from %s; " + "use \"%s domain-name\" instead", + REJECT_MAPS_RBL, var_mail_name, REJECT_RBL_CLIENT); + } + while ((rbl_domain = mystrtok(&bp, CHARS_COMMA_SP)) != 0) { + result = reject_rbl_addr(state, rbl_domain, state->addr, + SMTPD_NAME_CLIENT); + if (result != SMTPD_CHECK_DUNNO) + break; + } + + /* + * Clean up. + */ + myfree(saved_domains); + + return (result); +} + +#ifdef USE_SASL_AUTH + +/* reject_auth_sender_login_mismatch - logged in client must own sender address */ + +static int reject_auth_sender_login_mismatch(SMTPD_STATE *state, const char *sender, int allow_unknown_sender) +{ + const RESOLVE_REPLY *reply; + const char *owners; + char *saved_owners; + char *cp; + char *name; + int found = 0; + +#define ALLOW_UNKNOWN_SENDER 1 +#define FORBID_UNKNOWN_SENDER 0 + + /* + * Reject if the client is logged in and does not own the sender address. + */ + if (smtpd_sender_login_maps && state->sasl_username) { + reply = smtpd_resolve_addr(state->recipient, sender); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, sender); + if ((owners = check_mail_addr_find(state, sender, smtpd_sender_login_maps, + STR(reply->recipient), (char **) 0)) != 0) { + cp = saved_owners = mystrdup(owners); + while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) { + if (strcasecmp_utf8(state->sasl_username, name) == 0) { + found = 1; + break; + } + } + myfree(saved_owners); + } else if (allow_unknown_sender) + return (SMTPD_CHECK_DUNNO); + if (!found) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, 553, "5.7.1", + "<%s>: Sender address rejected: not owned by user %s", + sender, state->sasl_username)); + } + return (SMTPD_CHECK_DUNNO); +} + +/* reject_unauth_sender_login_mismatch - sender requires client is logged in */ + +static int reject_unauth_sender_login_mismatch(SMTPD_STATE *state, const char *sender) +{ + const RESOLVE_REPLY *reply; + + /* + * Reject if the client is not logged in and the sender address has an + * owner. + */ + if (smtpd_sender_login_maps && !state->sasl_username) { + reply = smtpd_resolve_addr(state->recipient, sender); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, sender); + if (check_mail_addr_find(state, sender, smtpd_sender_login_maps, + STR(reply->recipient), (char **) 0) != 0) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, 553, "5.7.1", + "<%s>: Sender address rejected: not logged in", sender)); + } + return (SMTPD_CHECK_DUNNO); +} + +#endif + +/* valid_utf8_action - validate UTF-8 policy server response */ + +static int valid_utf8_action(const char *server, const char *action) +{ + int retval; + + if ((retval = valid_utf8_string(action, strlen(action))) == 0) + msg_warn("malformed UTF-8 in policy server %s response: \"%s\"", + server, action); + return (retval); +} + +/* check_policy_service - check delegated policy service */ + +static int check_policy_service(SMTPD_STATE *state, const char *server, + const char *reply_name, const char *reply_class, + const char *def_acl) +{ + static VSTRING *action = 0; + SMTPD_POLICY_CLNT *policy_clnt; + +#ifdef USE_TLS + VSTRING *subject_buf; + VSTRING *issuer_buf; + const char *subject; + const char *issuer; + +#endif + int ret; + + /* + * Sanity check. + */ + if (!policy_clnt_table + || (policy_clnt = (SMTPD_POLICY_CLNT *) + htable_find(policy_clnt_table, server)) == 0) + msg_panic("check_policy_service: no client endpoint for server %s", + server); + + /* + * Initialize. + */ + if (action == 0) + action = vstring_alloc(10); + +#ifdef USE_TLS +#define ENCODE_CN(coded_CN, coded_CN_buf, CN) do { \ + if (!TLS_CERT_IS_TRUSTED(state->tls_context) || *(CN) == 0) { \ + coded_CN_buf = 0; \ + coded_CN = ""; \ + } else { \ + coded_CN_buf = vstring_alloc(strlen(CN) + 1); \ + xtext_quote(coded_CN_buf, CN, ""); \ + coded_CN = STR(coded_CN_buf); \ + } \ + } while (0); + + ENCODE_CN(subject, subject_buf, state->tls_context->peer_CN); + ENCODE_CN(issuer, issuer_buf, state->tls_context->issuer_CN); +#endif + + if (attr_clnt_request(policy_clnt->client, + ATTR_FLAG_NONE, /* Query attributes. */ + SEND_ATTR_STR(MAIL_ATTR_REQ, "smtpd_access_policy"), + SEND_ATTR_STR(MAIL_ATTR_PROTO_STATE, + STREQ(state->where, SMTPD_CMD_BDAT) ? + SMTPD_CMD_DATA : state->where), + SEND_ATTR_STR(MAIL_ATTR_ACT_PROTO_NAME, state->protocol), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->addr), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_NAME, state->name), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->port), + SEND_ATTR_STR(MAIL_ATTR_ACT_REVERSE_CLIENT_NAME, + state->reverse_name), + SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, + state->dest_addr), + SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, + state->dest_port), + SEND_ATTR_STR(MAIL_ATTR_ACT_HELO_NAME, + state->helo_name ? state->helo_name : ""), + SEND_ATTR_STR(MAIL_ATTR_SENDER, + state->sender ? state->sender : ""), + SEND_ATTR_STR(MAIL_ATTR_RECIP, + state->recipient ? state->recipient : ""), + SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT, + ((strcasecmp(state->where, SMTPD_CMD_DATA) == 0) || + (strcasecmp(state->where, SMTPD_CMD_BDAT) == 0) || + (strcasecmp(state->where, SMTPD_AFTER_EOM) == 0)) ? + state->rcpt_count : 0), + SEND_ATTR_STR(MAIL_ATTR_QUEUEID, + state->queue_id ? state->queue_id : ""), + SEND_ATTR_STR(MAIL_ATTR_INSTANCE, + STR(state->instance)), + SEND_ATTR_LONG(MAIL_ATTR_SIZE, + (unsigned long) (state->act_size > 0 ? + state->act_size : state->msg_size)), + SEND_ATTR_STR(MAIL_ATTR_ETRN_DOMAIN, + state->etrn_name ? state->etrn_name : ""), + SEND_ATTR_STR(MAIL_ATTR_STRESS, var_stress), +#ifdef USE_SASL_AUTH + SEND_ATTR_STR(MAIL_ATTR_SASL_METHOD, + state->sasl_method ? state->sasl_method : ""), + SEND_ATTR_STR(MAIL_ATTR_SASL_USERNAME, + state->sasl_username ? state->sasl_username : ""), + SEND_ATTR_STR(MAIL_ATTR_SASL_SENDER, + state->sasl_sender ? state->sasl_sender : ""), +#endif +#ifdef USE_TLS +#define IF_ENCRYPTED(x, y) ((state->tls_context && ((x) != 0)) ? (x) : (y)) + SEND_ATTR_STR(MAIL_ATTR_CCERT_SUBJECT, subject), + SEND_ATTR_STR(MAIL_ATTR_CCERT_ISSUER, issuer), + + /* + * When directly checking the fingerprint, it is OK if the issuing CA is + * not trusted. + */ + SEND_ATTR_STR(MAIL_ATTR_CCERT_CERT_FPRINT, + IF_ENCRYPTED(state->tls_context->peer_cert_fprint, "")), + SEND_ATTR_STR(MAIL_ATTR_CCERT_PKEY_FPRINT, + IF_ENCRYPTED(state->tls_context->peer_pkey_fprint, "")), + SEND_ATTR_STR(MAIL_ATTR_CRYPTO_PROTOCOL, + IF_ENCRYPTED(state->tls_context->protocol, "")), + SEND_ATTR_STR(MAIL_ATTR_CRYPTO_CIPHER, + IF_ENCRYPTED(state->tls_context->cipher_name, "")), + SEND_ATTR_INT(MAIL_ATTR_CRYPTO_KEYSIZE, + IF_ENCRYPTED(state->tls_context->cipher_usebits, 0)), +#endif + SEND_ATTR_STR(MAIL_ATTR_POL_CONTEXT, + policy_clnt->policy_context), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply attributes. */ + RECV_ATTR_STR(MAIL_ATTR_ACTION, action), + ATTR_TYPE_END) != 1 + || (var_smtputf8_enable && valid_utf8_action(server, STR(action)) == 0)) { + NOCLOBBER static int nesting_level = 0; + jmp_buf savebuf; + int status; + + /* + * Safety to prevent recursive execution of the default action. + */ + nesting_level += 1; + memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf)); + status = setjmp(smtpd_check_buf); + if (status != 0) { + nesting_level -= 1; + memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), + sizeof(smtpd_check_buf)); + longjmp(smtpd_check_buf, status); + } + ret = check_table_result(state, server, nesting_level == 1 ? + policy_clnt->def_action : + DEF_SMTPD_POLICY_DEF_ACTION, + "policy query", reply_name, + reply_class, def_acl); + nesting_level -= 1; + memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), + sizeof(smtpd_check_buf)); + } else { + + /* + * XXX This produces bogus error messages when the reply is + * malformed. + */ + ret = check_table_result(state, server, STR(action), + "policy query", reply_name, + reply_class, def_acl); + } +#ifdef USE_TLS + if (subject_buf) + vstring_free(subject_buf); + if (issuer_buf) + vstring_free(issuer_buf); +#endif + return (ret); +} + +/* is_map_command - restriction has form: check_xxx_access type:name */ + +static int is_map_command(SMTPD_STATE *state, const char *name, + const char *command, char ***argp) +{ + + /* + * This is a three-valued function: (a) this is not a check_xxx_access + * command, (b) this is a malformed check_xxx_access command, (c) this is + * a well-formed check_xxx_access command. That's too clumsy for function + * result values, so we use regular returns for (a) and (c), and use long + * jumps for the error case (b). + */ + if (strcasecmp(name, command) != 0) { + return (0); + } else if (*(*argp + 1) == 0 || strchr(*(*argp += 1), ':') == 0) { + msg_warn("restriction %s: bad argument \"%s\": need maptype:mapname", + command, **argp); + reject_server_error(state); + } else { + return (1); + } +} + +/* forbid_whitelist - disallow whitelisting */ + +static void forbid_whitelist(SMTPD_STATE *state, const char *name, + int status, const char *target) +{ + if (state->discard == 0 && status == SMTPD_CHECK_OK) { + msg_warn("restriction %s returns OK for %s", name, target); + msg_warn("this is not allowed for security reasons"); + msg_warn("use DUNNO instead of OK if you want to make an exception"); + reject_server_error(state); + } +} + +/* generic_checks - generic restrictions */ + +static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "generic_checks"; + char **cpp; + const char *name; + int status = 0; + ARGV *list; + int found; + int saved_recursion = state->recursion++; + + if (msg_verbose) + msg_info(">>> START %s RESTRICTIONS <<<", reply_class); + + for (cpp = restrictions->argv; (name = *cpp) != 0; cpp++) { + + if (state->discard != 0) + break; + + if (msg_verbose) + msg_info("%s: name=%s", myname, name); + + /* + * Pseudo restrictions. + */ + if (strcasecmp(name, WARN_IF_REJECT) == 0) { + if (state->warn_if_reject == 0) + state->warn_if_reject = state->recursion; + continue; + } + + /* + * Spoof the is_map_command() routine, so that we do not have to make + * special cases for the implicit short-hand access map notation. + */ +#define NO_DEF_ACL 0 + + if (strchr(name, ':') != 0) { + if (def_acl == NO_DEF_ACL) { + msg_warn("specify one of (%s, %s, %s, %s, %s, %s) before %s restriction \"%s\"", + CHECK_CLIENT_ACL, CHECK_REVERSE_CLIENT_ACL, CHECK_HELO_ACL, CHECK_SENDER_ACL, + CHECK_RECIP_ACL, CHECK_ETRN_ACL, reply_class, name); + reject_server_error(state); + } + name = def_acl; + cpp -= 1; + } + + /* + * Generic restrictions. + */ + if (strcasecmp(name, PERMIT_ALL) == 0) { + status = smtpd_acl_permit(state, name, reply_class, + reply_name, NO_PRINT_ARGS); + if (status == SMTPD_CHECK_OK && cpp[1] != 0) + msg_warn("restriction `%s' after `%s' is ignored", + cpp[1], PERMIT_ALL); + } else if (strcasecmp(name, DEFER_ALL) == 0) { + status = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_defer_code, "4.3.2", + "<%s>: %s rejected: Try again later", + reply_name, reply_class); + if (cpp[1] != 0 && state->warn_if_reject == 0) + msg_warn("restriction `%s' after `%s' is ignored", + cpp[1], DEFER_ALL); + } else if (strcasecmp(name, REJECT_ALL) == 0) { + status = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_reject_code, "5.7.1", + "<%s>: %s rejected: Access denied", + reply_name, reply_class); + if (cpp[1] != 0 && state->warn_if_reject == 0) + msg_warn("restriction `%s' after `%s' is ignored", + cpp[1], REJECT_ALL); + } else if (strcasecmp(name, REJECT_UNAUTH_PIPE) == 0) { + status = reject_unauth_pipelining(state, reply_name, reply_class); + } else if (strcasecmp(name, CHECK_POLICY_SERVICE) == 0) { + if (cpp[1] == 0 || strchr(cpp[1], ':') == 0) { + msg_warn("restriction %s must be followed by transport:server", + CHECK_POLICY_SERVICE); + reject_server_error(state); + } else + status = check_policy_service(state, *++cpp, reply_name, + reply_class, def_acl); + } else if (strcasecmp(name, DEFER_IF_PERMIT) == 0) { + status = DEFER_IF_PERMIT2(DEFER_IF_PERMIT_ACT, + state, MAIL_ERROR_POLICY, + 450, "4.7.0", + "<%s>: %s rejected: defer_if_permit requested", + reply_name, reply_class); + } else if (strcasecmp(name, DEFER_IF_REJECT) == 0) { + DEFER_IF_REJECT2(state, MAIL_ERROR_POLICY, + 450, "4.7.0", + "<%s>: %s rejected: defer_if_reject requested", + reply_name, reply_class); + } else if (strcasecmp(name, SLEEP) == 0) { + if (cpp[1] == 0 || alldig(cpp[1]) == 0) { + msg_warn("restriction %s must be followed by number", SLEEP); + reject_server_error(state); + } else + sleep(atoi(*++cpp)); + } else if (strcasecmp(name, REJECT_PLAINTEXT_SESSION) == 0) { + status = reject_plaintext_session(state); + } + + /* + * Client name/address restrictions. + */ + else if (strcasecmp(name, REJECT_UNKNOWN_CLIENT_HOSTNAME) == 0 + || strcasecmp(name, REJECT_UNKNOWN_CLIENT) == 0) { + status = reject_unknown_client(state); + } else if (strcasecmp(name, REJECT_UNKNOWN_REVERSE_HOSTNAME) == 0) { + status = reject_unknown_reverse_name(state); + } else if (strcasecmp(name, PERMIT_INET_INTERFACES) == 0) { + status = permit_inet_interfaces(state); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } else if (strcasecmp(name, PERMIT_MYNETWORKS) == 0) { + status = permit_mynetworks(state); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } else if (is_map_command(state, name, CHECK_CLIENT_ACL, &cpp)) { + status = check_namadr_access(state, *cpp, state->name, state->addr, + FULL, &found, state->namaddr, + SMTPD_NAME_CLIENT, def_acl); + } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_ACL, &cpp)) { + status = check_namadr_access(state, *cpp, state->reverse_name, state->addr, + FULL, &found, state->reverse_name, + SMTPD_NAME_REV_CLIENT, def_acl); + forbid_whitelist(state, name, status, state->reverse_name); + } else if (strcasecmp(name, REJECT_MAPS_RBL) == 0) { + status = reject_maps_rbl(state); + } else if (strcasecmp(name, REJECT_RBL_CLIENT) == 0 + || strcasecmp(name, REJECT_RBL) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", name); + else + status = reject_rbl_addr(state, *(cpp += 1), state->addr, + SMTPD_NAME_CLIENT); + } else if (strcasecmp(name, PERMIT_DNSWL_CLIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", name); + else { + status = permit_dnswl_addr(state, *(cpp += 1), state->addr, + SMTPD_NAME_CLIENT); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } + } else if (strcasecmp(name, REJECT_RHSBL_CLIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", + name); + else { + cpp += 1; + if (strcasecmp(state->name, "unknown") != 0) + status = reject_rbl_domain(state, *cpp, state->name, + SMTPD_NAME_CLIENT); + } + } else if (strcasecmp(name, PERMIT_RHSWL_CLIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", + name); + else { + cpp += 1; + if (strcasecmp(state->name, "unknown") != 0) { + status = permit_dnswl_domain(state, *cpp, state->name, + SMTPD_NAME_CLIENT); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, + SMTPD_NAME_CLIENT, state->namaddr, NO_PRINT_ARGS); + } + } + } else if (strcasecmp(name, REJECT_RHSBL_REVERSE_CLIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", + name); + else { + cpp += 1; + if (strcasecmp(state->reverse_name, "unknown") != 0) + status = reject_rbl_domain(state, *cpp, state->reverse_name, + SMTPD_NAME_REV_CLIENT); + } + } else if (is_map_command(state, name, CHECK_CCERT_ACL, &cpp)) { + status = check_ccert_access(state, *cpp, def_acl); + } else if (is_map_command(state, name, CHECK_SASL_ACL, &cpp)) { +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + if (state->sasl_username && state->sasl_username[0]) + status = check_sasl_access(state, *cpp, def_acl); + } else +#endif + msg_warn("restriction `%s' ignored: no SASL support", name); + } else if (is_map_command(state, name, CHECK_CLIENT_NS_ACL, &cpp)) { + if (strcasecmp(state->name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->name, + T_NS, state->namaddr, + SMTPD_NAME_CLIENT, def_acl); + forbid_whitelist(state, name, status, state->name); + } + } else if (is_map_command(state, name, CHECK_CLIENT_MX_ACL, &cpp)) { + if (strcasecmp(state->name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->name, + T_MX, state->namaddr, + SMTPD_NAME_CLIENT, def_acl); + forbid_whitelist(state, name, status, state->name); + } + } else if (is_map_command(state, name, CHECK_CLIENT_A_ACL, &cpp)) { + if (strcasecmp(state->name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->name, + T_A, state->namaddr, + SMTPD_NAME_CLIENT, def_acl); + forbid_whitelist(state, name, status, state->name); + } + } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_NS_ACL, &cpp)) { + if (strcasecmp(state->reverse_name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->reverse_name, + T_NS, state->reverse_name, + SMTPD_NAME_REV_CLIENT, def_acl); + forbid_whitelist(state, name, status, state->reverse_name); + } + } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_MX_ACL, &cpp)) { + if (strcasecmp(state->reverse_name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->reverse_name, + T_MX, state->reverse_name, + SMTPD_NAME_REV_CLIENT, def_acl); + forbid_whitelist(state, name, status, state->reverse_name); + } + } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_A_ACL, &cpp)) { + if (strcasecmp(state->reverse_name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->reverse_name, + T_A, state->reverse_name, + SMTPD_NAME_REV_CLIENT, def_acl); + forbid_whitelist(state, name, status, state->reverse_name); + } + } + + /* + * HELO/EHLO parameter restrictions. + */ + else if (is_map_command(state, name, CHECK_HELO_ACL, &cpp)) { + if (state->helo_name) + status = check_domain_access(state, *cpp, state->helo_name, + FULL, &found, state->helo_name, + SMTPD_NAME_HELO, def_acl); + } else if (strcasecmp(name, REJECT_INVALID_HELO_HOSTNAME) == 0 + || strcasecmp(name, REJECT_INVALID_HOSTNAME) == 0) { + if (state->helo_name) { + if (*state->helo_name != '[') + status = reject_invalid_hostname(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + else + status = reject_invalid_hostaddr(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + } + } else if (strcasecmp(name, REJECT_UNKNOWN_HELO_HOSTNAME) == 0 + || strcasecmp(name, REJECT_UNKNOWN_HOSTNAME) == 0) { + if (state->helo_name) { + if (*state->helo_name != '[') + status = reject_unknown_hostname(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + else + status = reject_invalid_hostaddr(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + } + } else if (strcasecmp(name, PERMIT_NAKED_IP_ADDR) == 0) { + msg_warn("restriction %s is deprecated. Use %s or %s instead", + PERMIT_NAKED_IP_ADDR, PERMIT_MYNETWORKS, PERMIT_SASL_AUTH); + if (state->helo_name) { + if (state->helo_name[strspn(state->helo_name, "0123456789.:")] == 0 + && (status = reject_invalid_hostaddr(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO)) == 0) + status = smtpd_acl_permit(state, name, SMTPD_NAME_HELO, + state->helo_name, NO_PRINT_ARGS); + } + } else if (is_map_command(state, name, CHECK_HELO_NS_ACL, &cpp)) { + if (state->helo_name) { + status = check_server_access(state, *cpp, state->helo_name, + T_NS, state->helo_name, + SMTPD_NAME_HELO, def_acl); + forbid_whitelist(state, name, status, state->helo_name); + } + } else if (is_map_command(state, name, CHECK_HELO_MX_ACL, &cpp)) { + if (state->helo_name) { + status = check_server_access(state, *cpp, state->helo_name, + T_MX, state->helo_name, + SMTPD_NAME_HELO, def_acl); + forbid_whitelist(state, name, status, state->helo_name); + } + } else if (is_map_command(state, name, CHECK_HELO_A_ACL, &cpp)) { + if (state->helo_name) { + status = check_server_access(state, *cpp, state->helo_name, + T_A, state->helo_name, + SMTPD_NAME_HELO, def_acl); + forbid_whitelist(state, name, status, state->helo_name); + } + } else if (strcasecmp(name, REJECT_NON_FQDN_HELO_HOSTNAME) == 0 + || strcasecmp(name, REJECT_NON_FQDN_HOSTNAME) == 0) { + if (state->helo_name) { + if (*state->helo_name != '[') + status = reject_non_fqdn_hostname(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + else + status = reject_invalid_hostaddr(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + } + } else if (strcasecmp(name, REJECT_RHSBL_HELO) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", + name); + else { + cpp += 1; + if (state->helo_name) + status = reject_rbl_domain(state, *cpp, state->helo_name, + SMTPD_NAME_HELO); + } + } + + /* + * Sender mail address restrictions. + */ + else if (is_map_command(state, name, CHECK_SENDER_ACL, &cpp)) { + if (state->sender && *state->sender) + status = check_mail_access(state, *cpp, state->sender, + &found, state->sender, + SMTPD_NAME_SENDER, def_acl); + if (state->sender && !*state->sender) + status = check_access(state, *cpp, var_smtpd_null_key, FULL, + &found, state->sender, + SMTPD_NAME_SENDER, def_acl); + } else if (strcasecmp(name, REJECT_UNKNOWN_ADDRESS) == 0) { + if (state->sender && *state->sender) + status = reject_unknown_address(state, state->sender, + state->sender, SMTPD_NAME_SENDER); + } else if (strcasecmp(name, REJECT_UNKNOWN_SENDDOM) == 0) { + if (state->sender && *state->sender) + status = reject_unknown_address(state, state->sender, + state->sender, SMTPD_NAME_SENDER); + } else if (strcasecmp(name, REJECT_UNVERIFIED_SENDER) == 0) { + if (state->sender && *state->sender) + status = reject_unverified_address(state, state->sender, + state->sender, SMTPD_NAME_SENDER, + var_unv_from_dcode, var_unv_from_rcode, + unv_from_tf_act, + var_unv_from_why); + } else if (strcasecmp(name, REJECT_NON_FQDN_SENDER) == 0) { + if (state->sender && *state->sender) + status = reject_non_fqdn_address(state, state->sender, + state->sender, SMTPD_NAME_SENDER); + } else if (strcasecmp(name, REJECT_AUTH_SENDER_LOGIN_MISMATCH) == 0) { +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + if (state->sender && *state->sender) + status = reject_auth_sender_login_mismatch(state, + state->sender, FORBID_UNKNOWN_SENDER); + } else +#endif + msg_warn("restriction `%s' ignored: no SASL support", name); + } else if (strcasecmp(name, REJECT_KNOWN_SENDER_LOGIN_MISMATCH) == 0) { +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + if (state->sender && *state->sender) { + if (state->sasl_username) + status = reject_auth_sender_login_mismatch(state, + state->sender, ALLOW_UNKNOWN_SENDER); + else + status = reject_unauth_sender_login_mismatch(state, state->sender); + } + } else +#endif + msg_warn("restriction `%s' ignored: no SASL support", name); + } else if (strcasecmp(name, REJECT_UNAUTH_SENDER_LOGIN_MISMATCH) == 0) { +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + if (state->sender && *state->sender) + status = reject_unauth_sender_login_mismatch(state, state->sender); + } else +#endif + msg_warn("restriction `%s' ignored: no SASL support", name); + } else if (is_map_command(state, name, CHECK_SENDER_NS_ACL, &cpp)) { + if (state->sender && *state->sender) { + status = check_server_access(state, *cpp, state->sender, + T_NS, state->sender, + SMTPD_NAME_SENDER, def_acl); + forbid_whitelist(state, name, status, state->sender); + } + } else if (is_map_command(state, name, CHECK_SENDER_MX_ACL, &cpp)) { + if (state->sender && *state->sender) { + status = check_server_access(state, *cpp, state->sender, + T_MX, state->sender, + SMTPD_NAME_SENDER, def_acl); + forbid_whitelist(state, name, status, state->sender); + } + } else if (is_map_command(state, name, CHECK_SENDER_A_ACL, &cpp)) { + if (state->sender && *state->sender) { + status = check_server_access(state, *cpp, state->sender, + T_A, state->sender, + SMTPD_NAME_SENDER, def_acl); + forbid_whitelist(state, name, status, state->sender); + } + } else if (strcasecmp(name, REJECT_RHSBL_SENDER) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", name); + else { + cpp += 1; + if (state->sender && *state->sender) + status = reject_rbl_domain(state, *cpp, state->sender, + SMTPD_NAME_SENDER); + } + } else if (strcasecmp(name, REJECT_UNLISTED_SENDER) == 0) { + if (state->sender && *state->sender) + status = check_sender_rcpt_maps(state, state->sender); + } + + /* + * Recipient mail address restrictions. + */ + else if (is_map_command(state, name, CHECK_RECIP_ACL, &cpp)) { + if (state->recipient) + status = check_mail_access(state, *cpp, state->recipient, + &found, state->recipient, + SMTPD_NAME_RECIPIENT, def_acl); + } else if (strcasecmp(name, PERMIT_MX_BACKUP) == 0) { + if (state->recipient) { + status = permit_mx_backup(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT, + state->recipient, NO_PRINT_ARGS); + } + } else if (strcasecmp(name, PERMIT_AUTH_DEST) == 0) { + if (state->recipient) { + status = permit_auth_destination(state, state->recipient); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT, + state->recipient, NO_PRINT_ARGS); + } + } else if (strcasecmp(name, REJECT_UNAUTH_DEST) == 0) { + if (state->recipient) + status = reject_unauth_destination(state, state->recipient, + var_relay_code, "5.7.1"); + } else if (strcasecmp(name, DEFER_UNAUTH_DEST) == 0) { + if (state->recipient) + status = reject_unauth_destination(state, state->recipient, + var_relay_code - 100, "4.7.1"); + } else if (strcasecmp(name, CHECK_RELAY_DOMAINS) == 0) { + if (state->recipient) + status = check_relay_domains(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT, + state->recipient, NO_PRINT_ARGS); + if (cpp[1] != 0 && state->warn_if_reject == 0) + msg_warn("restriction `%s' after `%s' is ignored", + cpp[1], CHECK_RELAY_DOMAINS); + } else if (strcasecmp(name, PERMIT_SASL_AUTH) == 0) { +#ifdef USE_SASL_AUTH + if (smtpd_sasl_is_active(state)) { + status = permit_sasl_auth(state, + SMTPD_CHECK_OK, SMTPD_CHECK_DUNNO); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } +#endif + } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 1); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 0); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } else if (strcasecmp(name, REJECT_UNKNOWN_RCPTDOM) == 0) { + if (state->recipient) + status = reject_unknown_address(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT); + } else if (strcasecmp(name, REJECT_NON_FQDN_RCPT) == 0) { + if (state->recipient) + status = reject_non_fqdn_address(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT); + } else if (is_map_command(state, name, CHECK_RECIP_NS_ACL, &cpp)) { + if (state->recipient && *state->recipient) { + status = check_server_access(state, *cpp, state->recipient, + T_NS, state->recipient, + SMTPD_NAME_RECIPIENT, def_acl); + forbid_whitelist(state, name, status, state->recipient); + } + } else if (is_map_command(state, name, CHECK_RECIP_MX_ACL, &cpp)) { + if (state->recipient && *state->recipient) { + status = check_server_access(state, *cpp, state->recipient, + T_MX, state->recipient, + SMTPD_NAME_RECIPIENT, def_acl); + forbid_whitelist(state, name, status, state->recipient); + } + } else if (is_map_command(state, name, CHECK_RECIP_A_ACL, &cpp)) { + if (state->recipient && *state->recipient) { + status = check_server_access(state, *cpp, state->recipient, + T_A, state->recipient, + SMTPD_NAME_RECIPIENT, def_acl); + forbid_whitelist(state, name, status, state->recipient); + } + } else if (strcasecmp(name, REJECT_RHSBL_RECIPIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", name); + else { + cpp += 1; + if (state->recipient) + status = reject_rbl_domain(state, *cpp, state->recipient, + SMTPD_NAME_RECIPIENT); + } + } else if (strcasecmp(name, CHECK_RCPT_MAPS) == 0 + || strcasecmp(name, REJECT_UNLISTED_RCPT) == 0) { + if (state->recipient && *state->recipient) + status = check_recipient_rcpt_maps(state, state->recipient); + } else if (strcasecmp(name, REJECT_MUL_RCPT_BOUNCE) == 0) { + if (state->sender && *state->sender == 0 && state->rcpt_count + > (strcmp(state->where, SMTPD_CMD_RCPT) != 0)) + status = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_mul_rcpt_code, "5.5.3", + "<%s>: %s rejected: Multi-recipient bounce", + reply_name, reply_class); + } else if (strcasecmp(name, REJECT_UNVERIFIED_RECIP) == 0) { + if (state->recipient && *state->recipient) + status = reject_unverified_address(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT, + var_unv_rcpt_dcode, var_unv_rcpt_rcode, + unv_rcpt_tf_act, + var_unv_rcpt_why); + } + + /* + * ETRN domain name restrictions. + */ + else if (is_map_command(state, name, CHECK_ETRN_ACL, &cpp)) { + if (state->etrn_name) + status = check_domain_access(state, *cpp, state->etrn_name, + FULL, &found, state->etrn_name, + SMTPD_NAME_ETRN, def_acl); + } + + /* + * User-defined restriction class. + */ + else if ((list = (ARGV *) htable_find(smtpd_rest_classes, name)) != 0) { + status = generic_checks(state, list, reply_name, + reply_class, def_acl); + } + + /* + * Error: undefined restriction name. + */ + else { + msg_warn("unknown smtpd restriction: \"%s\"", name); + reject_server_error(state); + } + if (msg_verbose) + msg_info("%s: name=%s status=%d", myname, name, status); + + if (status < 0) { + if (status == DICT_ERR_RETRY) + reject_dict_retry(state, reply_name); + else + reject_server_error(state); + } + if (state->warn_if_reject >= state->recursion) + state->warn_if_reject = 0; + + if (status != 0) + break; + + if (state->defer_if_permit.active && state->defer_if_reject.active) + break; + } + if (msg_verbose) + msg_info(">>> END %s RESTRICTIONS <<<", reply_class); + + state->recursion = saved_recursion; + + /* In case the list terminated with one or more warn_if_mumble. */ + if (state->warn_if_reject >= state->recursion) + state->warn_if_reject = 0; + + return (status); +} + +/* smtpd_check_addr - address sanity check */ + +int smtpd_check_addr(const char *sender, const char *addr, int smtputf8) +{ + const RESOLVE_REPLY *resolve_reply; + const char *myname = "smtpd_check_addr"; + const char *domain; + + if (msg_verbose) + msg_info("%s: addr=%s", myname, addr); + + /* + * Catch syntax errors early on if we can, but be prepared to re-compute + * the result later when the cache fills up with lots of recipients, at + * which time errors can still happen. + */ + if (addr == 0 || *addr == 0) + return (0); + resolve_reply = smtpd_resolve_addr(sender, addr); + if (resolve_reply->flags & RESOLVE_FLAG_ERROR) + return (-1); + + /* + * Backwards compatibility: if the client does not request SMTPUTF8 + * support, then behave like Postfix < 3.0 trivial-rewrite, and don't + * allow non-ASCII email domains. Historically, Postfix does not reject + * UTF8 etc. in the address localpart. + */ + if (smtputf8 == 0 + && (domain = strrchr(STR(resolve_reply->recipient), '@')) != 0 + && *(domain += 1) != 0 && !allascii(domain)) + return (-1); + + return (0); +} + +/* smtpd_check_rewrite - choose address qualification context */ + +char *smtpd_check_rewrite(SMTPD_STATE *state) +{ + const char *myname = "smtpd_check_rewrite"; + int status; + char **cpp; + MAPS *maps; + char *name; + + /* + * We don't use generic_checks() because it produces results that aren't + * applicable such as DEFER or REJECT. + */ + for (cpp = local_rewrite_clients->argv; *cpp != 0; cpp++) { + if (msg_verbose) + msg_info("%s: trying: %s", myname, *cpp); + status = SMTPD_CHECK_DUNNO; + if (strchr(name = *cpp, ':') != 0) { + name = CHECK_ADDR_MAP; + cpp -= 1; + } + if (strcasecmp(name, PERMIT_INET_INTERFACES) == 0) { + status = permit_inet_interfaces(state); + } else if (strcasecmp(name, PERMIT_MYNETWORKS) == 0) { + status = permit_mynetworks(state); + } else if (is_map_command(state, name, CHECK_ADDR_MAP, &cpp)) { + if ((maps = (MAPS *) htable_find(map_command_table, *cpp)) == 0) + msg_panic("%s: dictionary not found: %s", myname, *cpp); + if (maps_find(maps, state->addr, 0) != 0) + status = SMTPD_CHECK_OK; + else if (maps->error != 0) { + /* Warning is already logged. */ + status = maps->error; + } + } else if (strcasecmp(name, PERMIT_SASL_AUTH) == 0) { +#ifdef USE_SASL_AUTH + if (smtpd_sasl_is_active(state)) + status = permit_sasl_auth(state, SMTPD_CHECK_OK, + SMTPD_CHECK_DUNNO); +#endif + } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 1); + } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 0); + } else { + msg_warn("parameter %s: invalid request: %s", + VAR_LOC_RWR_CLIENTS, name); + continue; + } + if (status < 0) { + if (status == DICT_ERR_RETRY) { + state->error_mask |= MAIL_ERROR_RESOURCE; + log_whatsup(state, "reject", + "451 4.3.0 Temporary lookup error"); + return ("451 4.3.0 Temporary lookup error"); + } else { + state->error_mask |= MAIL_ERROR_SOFTWARE; + log_whatsup(state, "reject", + "451 4.3.5 Server configuration error"); + return ("451 4.3.5 Server configuration error"); + } + } + if (status == SMTPD_CHECK_OK) { + state->rewrite_context = MAIL_ATTR_RWR_LOCAL; + return (0); + } + } + state->rewrite_context = MAIL_ATTR_RWR_REMOTE; + return (0); +} + +/* smtpd_check_client - validate client name or address */ + +char *smtpd_check_client(SMTPD_STATE *state) +{ + int status; + + /* + * Initialize. + */ + if (state->name == 0 || state->addr == 0) + return (0); + +#define SMTPD_CHECK_RESET() { \ + state->recursion = 0; \ + state->warn_if_reject = 0; \ + state->defer_if_reject.active = 0; \ + } + + /* + * Reset the defer_if_permit flag. + */ + state->defer_if_permit.active = 0; + + /* + * Apply restrictions in the order as specified. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && client_restrctions->argc) + status = generic_checks(state, client_restrctions, state->namaddr, + SMTPD_NAME_CLIENT, CHECK_CLIENT_ACL); + state->defer_if_permit_client = state->defer_if_permit.active; + + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_helo - validate HELO hostname */ + +char *smtpd_check_helo(SMTPD_STATE *state, char *helohost) +{ + int status; + char *saved_helo; + + /* + * Initialize. + */ + if (helohost == 0) + return (0); + + /* + * Minor kluge so that we can delegate work to the generic routine and so + * that we can syslog the recipient with the reject messages. + */ +#define SMTPD_CHECK_PUSH(backup, current, new) { \ + backup = current; \ + current = (new ? mystrdup(new) : 0); \ + } + +#define SMTPD_CHECK_POP(current, backup) { \ + if (current) myfree(current); \ + current = backup; \ + } + + SMTPD_CHECK_PUSH(saved_helo, state->helo_name, helohost); + +#define SMTPD_CHECK_HELO_RETURN(x) { \ + SMTPD_CHECK_POP(state->helo_name, saved_helo); \ + return (x); \ + } + + /* + * Restore the defer_if_permit flag to its value before HELO/EHLO, and do + * not set the flag when it was already raised by a previous protocol + * stage. + */ + state->defer_if_permit.active = state->defer_if_permit_client; + + /* + * Apply restrictions in the order as specified. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && helo_restrctions->argc) + status = generic_checks(state, helo_restrctions, state->helo_name, + SMTPD_NAME_HELO, CHECK_HELO_ACL); + state->defer_if_permit_helo = state->defer_if_permit.active; + + SMTPD_CHECK_HELO_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_mail - validate sender address, driver */ + +char *smtpd_check_mail(SMTPD_STATE *state, char *sender) +{ + int status; + char *saved_sender; + + /* + * Initialize. + */ + if (sender == 0) + return (0); + + /* + * Minor kluge so that we can delegate work to the generic routine and so + * that we can syslog the recipient with the reject messages. + */ + SMTPD_CHECK_PUSH(saved_sender, state->sender, sender); + +#define SMTPD_CHECK_MAIL_RETURN(x) { \ + SMTPD_CHECK_POP(state->sender, saved_sender); \ + return (x); \ + } + + /* + * Restore the defer_if_permit flag to its value before MAIL FROM, and do + * not set the flag when it was already raised by a previous protocol + * stage. The client may skip the helo/ehlo. + */ + state->defer_if_permit.active = state->defer_if_permit_client + | state->defer_if_permit_helo; + state->sender_rcptmap_checked = 0; + + /* + * Apply restrictions in the order as specified. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && mail_restrctions->argc) + status = generic_checks(state, mail_restrctions, sender, + SMTPD_NAME_SENDER, CHECK_SENDER_ACL); + state->defer_if_permit_sender = state->defer_if_permit.active; + + /* + * If the "reject_unlisted_sender" restriction still needs to be applied, + * validate the sender here. + */ + if (var_smtpd_rej_unl_from + && status != SMTPD_CHECK_REJECT && state->sender_rcptmap_checked == 0 + && state->discard == 0 && *sender) + status = check_sender_rcpt_maps(state, sender); + + SMTPD_CHECK_MAIL_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_rcpt - validate recipient address, driver */ + +char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient) +{ + int status; + char *saved_recipient; + char *err; + ARGV *restrctions[2]; + int n; + + /* + * Initialize. + */ + if (recipient == 0) + return (0); + + /* + * XXX 2821: Section 3.6 requires that "postmaster" be accepted even when + * specified without a fully qualified domain name. + */ + if (strcasecmp(recipient, "postmaster") == 0) + return (0); + + /* + * Minor kluge so that we can delegate work to the generic routine and so + * that we can syslog the recipient with the reject messages. + */ + SMTPD_CHECK_PUSH(saved_recipient, state->recipient, recipient); + +#define SMTPD_CHECK_RCPT_RETURN(x) { \ + SMTPD_CHECK_POP(state->recipient, saved_recipient); \ + return (x); \ + } + + /* + * The "check_recipient_maps" restriction is relevant only when + * responding to RCPT TO or VRFY. + */ + state->recipient_rcptmap_checked = 0; + + /* + * Apply delayed restrictions. + */ + if (var_smtpd_delay_reject) + if ((err = smtpd_check_client(state)) != 0 + || (err = smtpd_check_helo(state, state->helo_name)) != 0 + || (err = smtpd_check_mail(state, state->sender)) != 0) + SMTPD_CHECK_RCPT_RETURN(err); + + /* + * Restore the defer_if_permit flag to its value before RCPT TO, and do + * not set the flag when it was already raised by a previous protocol + * stage. + */ + state->defer_if_permit.active = state->defer_if_permit_sender; + + /* + * Apply restrictions in the order as specified. We allow relay + * restrictions to be empty, for sites that require backwards + * compatibility. + * + * If compatibility_level < 1 and smtpd_relay_restrictions is left at its + * default value, find out if the new smtpd_relay_restrictions default + * value would block the request, without logging REJECT messages. + * Approach: evaluate fake relay restrictions (permit_mynetworks, + * permit_sasl_authenticated, permit_auth_destination) and log a warning + * if the result is DUNNO instead of OK, i.e. a reject_unauth_destinatin + * at the end would have blocked the request. + */ + SMTPD_CHECK_RESET(); + restrctions[0] = rcpt_restrctions; + restrctions[1] = warn_compat_break_relay_restrictions ? + fake_relay_restrctions : relay_restrctions; + for (n = 0; n < 2; n++) { + status = setjmp(smtpd_check_buf); + if (status == 0 && restrctions[n]->argc) + status = generic_checks(state, restrctions[n], + recipient, SMTPD_NAME_RECIPIENT, CHECK_RECIP_ACL); + if (n == 1 && warn_compat_break_relay_restrictions + && status == SMTPD_CHECK_DUNNO) { + msg_info("using backwards-compatible default setting \"" + VAR_RELAY_CHECKS " = (empty)\" to avoid \"Relay " + "access denied\" error for recipient \"%s\" from " + "client \"%s\"", state->recipient, state->namaddr); + } + if (status == SMTPD_CHECK_REJECT) + break; + } + + /* + * Force permission into deferral when some earlier temporary error may + * have prevented us from rejecting mail, and report the earlier problem. + */ + if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active) + status = smtpd_check_reject(state, state->defer_if_permit.class, + state->defer_if_permit.code, + STR(state->defer_if_permit.dsn), + "%s", STR(state->defer_if_permit.reason)); + + /* + * If the "reject_unlisted_recipient" restriction still needs to be + * applied, validate the recipient here. + */ + if (var_smtpd_rej_unl_rcpt + && status != SMTPD_CHECK_REJECT + && state->recipient_rcptmap_checked == 0 + && state->discard == 0) + status = check_recipient_rcpt_maps(state, recipient); + + SMTPD_CHECK_RCPT_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_etrn - validate ETRN request */ + +char *smtpd_check_etrn(SMTPD_STATE *state, char *domain) +{ + int status; + char *saved_etrn_name; + char *err; + + /* + * Initialize. + */ + if (domain == 0) + return (0); + + /* + * Minor kluge so that we can delegate work to the generic routine and so + * that we can syslog the recipient with the reject messages. + */ + SMTPD_CHECK_PUSH(saved_etrn_name, state->etrn_name, domain); + +#define SMTPD_CHECK_ETRN_RETURN(x) { \ + SMTPD_CHECK_POP(state->etrn_name, saved_etrn_name); \ + return (x); \ + } + + /* + * Apply delayed restrictions. + */ + if (var_smtpd_delay_reject) + if ((err = smtpd_check_client(state)) != 0 + || (err = smtpd_check_helo(state, state->helo_name)) != 0) + SMTPD_CHECK_ETRN_RETURN(err); + + /* + * Restore the defer_if_permit flag to its value before ETRN, and do not + * set the flag when it was already raised by a previous protocol stage. + * The client may skip the helo/ehlo. + */ + state->defer_if_permit.active = state->defer_if_permit_client + | state->defer_if_permit_helo; + + /* + * Apply restrictions in the order as specified. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && etrn_restrctions->argc) + status = generic_checks(state, etrn_restrctions, domain, + SMTPD_NAME_ETRN, CHECK_ETRN_ACL); + + /* + * Force permission into deferral when some earlier temporary error may + * have prevented us from rejecting mail, and report the earlier problem. + */ + if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active) + status = smtpd_check_reject(state, state->defer_if_permit.class, + state->defer_if_permit.code, + STR(state->defer_if_permit.dsn), + "%s", STR(state->defer_if_permit.reason)); + + SMTPD_CHECK_ETRN_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* check_recipient_rcpt_maps - generic_checks() recipient table check */ + +static int check_recipient_rcpt_maps(SMTPD_STATE *state, const char *recipient) +{ + + /* + * Duplicate suppression. There's an implicit check_recipient_maps + * restriction at the end of all recipient restrictions. + */ + if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT) + return (0); + if (state->recipient_rcptmap_checked == 1) + return (0); + if (state->warn_if_reject == 0) + /* We really validate the recipient address. */ + state->recipient_rcptmap_checked = 1; + return (check_rcpt_maps(state, state->sender, recipient, + SMTPD_NAME_RECIPIENT)); +} + +/* check_sender_rcpt_maps - generic_checks() sender table check */ + +static int check_sender_rcpt_maps(SMTPD_STATE *state, const char *sender) +{ + + /* + * Duplicate suppression. There's an implicit check_sender_maps + * restriction at the end of all sender restrictions. + */ + if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT) + return (0); + if (state->sender_rcptmap_checked == 1) + return (0); + if (state->warn_if_reject == 0) + /* We really validate the sender address. */ + state->sender_rcptmap_checked = 1; + return (check_rcpt_maps(state, state->recipient, sender, + SMTPD_NAME_SENDER)); +} + +/* check_rcpt_maps - generic_checks() interface for recipient table check */ + +static int check_rcpt_maps(SMTPD_STATE *state, const char *sender, + const char *recipient, + const char *reply_class) +{ + const RESOLVE_REPLY *reply; + DSN_SPLIT dp; + + if (msg_verbose) + msg_info(">>> CHECKING %s VALIDATION MAPS <<<", reply_class); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(sender, recipient); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, recipient); + + /* + * Make complex expressions more readable? + */ +#define MATCH(map, rcpt) \ + check_mail_addr_find(state, recipient, map, rcpt, (char **) 0) + +#define NOMATCH(map, rcpt) (MATCH(map, rcpt) == 0) + + /* + * XXX We assume the recipient address is OK if it matches a canonical + * map or virtual alias map. Eventually, the address resolver should give + * us the final resolved recipient address, and the SMTP server should + * write the final resolved recipient address to the output record + * stream. See also the next comment block on recipients in virtual alias + * domains. + */ + if (MATCH(rcpt_canon_maps, CONST_STR(reply->recipient)) + || (strcmp(reply_class, SMTPD_NAME_SENDER) == 0 + && MATCH(send_canon_maps, CONST_STR(reply->recipient))) + || MATCH(canonical_maps, CONST_STR(reply->recipient)) + || MATCH(virt_alias_maps, CONST_STR(reply->recipient))) + return (0); + + /* + * At this point, anything that resolves to the error mailer is known to + * be undeliverable. + * + * XXX Until the address resolver does final address resolution, known and + * unknown recipients in virtual alias domains will both resolve to + * "error:user unknown". + */ + if (strcmp(STR(reply->transport), MAIL_SERVICE_ERROR) == 0) { + dsn_split(&dp, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "5.1.0" : "5.1.1", STR(reply->nexthop)); + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, + (reply->flags & RESOLVE_CLASS_ALIAS) ? + var_virt_alias_code : 550, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + recipient, reply_class, + dp.text)); + } + if (strcmp(STR(reply->transport), MAIL_SERVICE_RETRY) == 0) { + dsn_split(&dp, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "4.1.0" : "4.1.1", STR(reply->nexthop)); + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, 450, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + recipient, reply_class, + dp.text)); + } + + /* + * Search the recipient lookup tables of the respective address class. + * + * XXX Use the less expensive maps_find() (built-in case folding) instead of + * the baroque mail_addr_find(). But then we have to strip the domain and + * deal with address extensions ourselves. + * + * XXX But that would break sites that use the virtual delivery agent for + * local delivery, because the virtual delivery agent requires + * user@domain style addresses in its user database. + */ +#define MATCH_LEFT(l, r, n) \ + (strncasecmp_utf8((l), (r), (n)) == 0 && (r)[n] == '@') + + switch (reply->flags & RESOLVE_CLASS_MASK) { + + /* + * Reject mail to unknown addresses in local domains (domains that + * match $mydestination or ${proxy,inet}_interfaces). + */ + case RESOLVE_CLASS_LOCAL: + if (*var_local_rcpt_maps + /* Generated by bounce, absorbed by qmgr. */ + && !MATCH_LEFT(var_double_bounce_sender, CONST_STR(reply->recipient), + strlen(var_double_bounce_sender)) + /* Absorbed by qmgr. */ + && !MATCH_LEFT(MAIL_ADDR_POSTMASTER, CONST_STR(reply->recipient), + strlen(MAIL_ADDR_POSTMASTER)) + /* Generated by bounce. */ + && !MATCH_LEFT(MAIL_ADDR_MAIL_DAEMON, CONST_STR(reply->recipient), + strlen(MAIL_ADDR_MAIL_DAEMON)) + && NOMATCH(local_rcpt_maps, CONST_STR(reply->recipient))) + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, + var_local_rcpt_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "5.1.0" : "5.1.1", + "<%s>: %s rejected: User unknown%s", + recipient, reply_class, + var_show_unk_rcpt_table ? + " in local recipient table" : "")); + break; + + /* + * Reject mail to unknown addresses in virtual mailbox domains. + */ + case RESOLVE_CLASS_VIRTUAL: + if (*var_virt_mailbox_maps + && NOMATCH(virt_mailbox_maps, CONST_STR(reply->recipient))) + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, + var_virt_mailbox_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "5.1.0" : "5.1.1", + "<%s>: %s rejected: User unknown%s", + recipient, reply_class, + var_show_unk_rcpt_table ? + " in virtual mailbox table" : "")); + break; + + /* + * Reject mail to unknown addresses in relay domains. + */ + case RESOLVE_CLASS_RELAY: + if (*var_relay_rcpt_maps + && NOMATCH(relay_rcpt_maps, CONST_STR(reply->recipient))) + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, + var_relay_rcpt_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "5.1.0" : "5.1.1", + "<%s>: %s rejected: User unknown%s", + recipient, reply_class, + var_show_unk_rcpt_table ? + " in relay recipient table" : "")); + if (warn_compat_break_relay_domains) + msg_info("using backwards-compatible default setting " + VAR_RELAY_DOMAINS "=$mydestination to accept mail " + "for address \"%s\"", recipient); + break; + } + + /* + * Accept all other addresses - including addresses that passed the above + * tests because of some table lookup problem. + */ + return (0); +} + +/* smtpd_check_size - check optional SIZE parameter value */ + +char *smtpd_check_size(SMTPD_STATE *state, off_t size) +{ + int status; + + /* + * Return here in case of serious trouble. + */ + SMTPD_CHECK_RESET(); + if ((status = setjmp(smtpd_check_buf)) != 0) + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); + + /* + * Check against file size limit. + */ + if (var_message_limit > 0 && size > var_message_limit) { + (void) smtpd_check_reject(state, MAIL_ERROR_POLICY, + 552, "5.3.4", + "Message size exceeds fixed limit"); + return (STR(error_text)); + } + return (0); +} + +/* smtpd_check_queue - check queue space */ + +char *smtpd_check_queue(SMTPD_STATE *state) +{ + const char *myname = "smtpd_check_queue"; + struct fsspace fsbuf; + int status; + + /* + * Return here in case of serious trouble. + */ + SMTPD_CHECK_RESET(); + if ((status = setjmp(smtpd_check_buf)) != 0) + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); + + /* + * Avoid overflow/underflow when comparing message size against available + * space. + */ +#define BLOCKS(x) ((x) / fsbuf.block_size) + + fsspace(".", &fsbuf); + if (msg_verbose) + msg_info("%s: blocks %lu avail %lu min_free %lu msg_size_limit %lu", + myname, + (unsigned long) fsbuf.block_size, + (unsigned long) fsbuf.block_free, + (unsigned long) var_queue_minfree, + (unsigned long) var_message_limit); + if (BLOCKS(var_queue_minfree) >= fsbuf.block_free + || BLOCKS(var_message_limit) >= fsbuf.block_free / smtpd_space_multf) { + (void) smtpd_check_reject(state, MAIL_ERROR_RESOURCE, + 452, "4.3.1", + "Insufficient system storage"); + msg_warn("not enough free space in mail queue: %lu bytes < " + "%g*message size limit", + (unsigned long) fsbuf.block_free * fsbuf.block_size, + smtpd_space_multf); + return (STR(error_text)); + } + return (0); +} + +/* smtpd_check_data - check DATA command */ + +char *smtpd_check_data(SMTPD_STATE *state) +{ + int status; + char *NOCLOBBER saved_recipient; + + /* + * Minor kluge so that we can delegate work to the generic routine. We + * provide no recipient information in the case of multiple recipients, + * This restriction applies to all recipients alike, and logging only one + * of them would be misleading. + */ + if (state->rcpt_count > 1) { + saved_recipient = state->recipient; + state->recipient = 0; + } + + /* + * Reset the defer_if_permit flag. This is necessary when some recipients + * were accepted but the last one was rejected. + */ + state->defer_if_permit.active = 0; + + /* + * Apply restrictions in the order as specified. + * + * XXX We cannot specify a default target for a bare access map. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && data_restrctions->argc) + status = generic_checks(state, data_restrctions, + SMTPD_CMD_DATA, SMTPD_NAME_DATA, NO_DEF_ACL); + + /* + * Force permission into deferral when some earlier temporary error may + * have prevented us from rejecting mail, and report the earlier problem. + */ + if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active) + status = smtpd_check_reject(state, state->defer_if_permit.class, + state->defer_if_permit.code, + STR(state->defer_if_permit.dsn), + "%s", STR(state->defer_if_permit.reason)); + + if (state->rcpt_count > 1) + state->recipient = saved_recipient; + + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_eod - check end-of-data command */ + +char *smtpd_check_eod(SMTPD_STATE *state) +{ + int status; + char *NOCLOBBER saved_recipient; + + /* + * Minor kluge so that we can delegate work to the generic routine. We + * provide no recipient information in the case of multiple recipients, + * This restriction applies to all recipients alike, and logging only one + * of them would be misleading. + */ + if (state->rcpt_count > 1) { + saved_recipient = state->recipient; + state->recipient = 0; + } + + /* + * Reset the defer_if_permit flag. This is necessary when some recipients + * were accepted but the last one was rejected. + */ + state->defer_if_permit.active = 0; + + /* + * Apply restrictions in the order as specified. + * + * XXX We cannot specify a default target for a bare access map. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && eod_restrictions->argc) + status = generic_checks(state, eod_restrictions, + SMTPD_CMD_EOD, SMTPD_NAME_EOD, NO_DEF_ACL); + + /* + * Force permission into deferral when some earlier temporary error may + * have prevented us from rejecting mail, and report the earlier problem. + */ + if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active) + status = smtpd_check_reject(state, state->defer_if_permit.class, + state->defer_if_permit.code, + STR(state->defer_if_permit.dsn), + "%s", STR(state->defer_if_permit.reason)); + + if (state->rcpt_count > 1) + state->recipient = saved_recipient; + + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +#ifdef TEST + + /* + * Test program to try out all these restrictions without having to go live. + * This is not entirely stand-alone, as it requires access to the Postfix + * rewrite/resolve service. This is just for testing code, not for debugging + * configuration files. + */ +#include <stdlib.h> + +#include <msg_vstream.h> +#include <vstring_vstream.h> + +#include <mail_conf.h> +#include <rewrite_clnt.h> +#include <dns.h> + +#include <smtpd_chat.h> + +int smtpd_input_transp_mask; + + /* + * Dummies. These are never set. + */ +char *var_client_checks = ""; +char *var_helo_checks = ""; +char *var_mail_checks = ""; +char *var_relay_checks = ""; +char *var_rcpt_checks = ""; +char *var_etrn_checks = ""; +char *var_data_checks = ""; +char *var_eod_checks = ""; +char *var_relay_domains = ""; +char *var_smtpd_uproxy_proto = ""; +int var_smtpd_uproxy_tmout = 0; + +#ifdef USE_TLS +char *var_relay_ccerts = ""; + +#endif +char *var_mynetworks = ""; +char *var_notify_classes = ""; +char *var_smtpd_policy_def_action = ""; +char *var_smtpd_policy_context = ""; + + /* + * String-valued configuration parameters. + */ +char *var_maps_rbl_domains; +char *var_myorigin; +char *var_mydest; +char *var_inet_interfaces; +char *var_proxy_interfaces; +char *var_rcpt_delim; +char *var_rest_classes; +char *var_alias_maps; +char *var_send_canon_maps; +char *var_rcpt_canon_maps; +char *var_canonical_maps; +char *var_virt_alias_maps; +char *var_virt_alias_doms; +char *var_virt_mailbox_maps; +char *var_virt_mailbox_doms; +char *var_local_rcpt_maps; +char *var_perm_mx_networks; +char *var_par_dom_match; +char *var_smtpd_null_key; +char *var_smtpd_snd_auth_maps; +char *var_double_bounce_sender; +char *var_rbl_reply_maps; +char *var_smtpd_exp_filter; +char *var_def_rbl_reply; +char *var_relay_rcpt_maps; +char *var_verify_sender; +char *var_smtpd_sasl_opts; +char *var_local_rwr_clients; +char *var_smtpd_relay_ccerts; +char *var_unv_from_why; +char *var_unv_rcpt_why; +char *var_stress; +char *var_unk_name_tf_act; +char *var_unk_addr_tf_act; +char *var_unv_rcpt_tf_act; +char *var_unv_from_tf_act; +char *var_smtpd_acl_perm_log; + +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, + 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. + */ +int 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; + +#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, + 0, +}; + +/* int_init - initialize int parameters */ + +static void int_init(void) +{ + const INT_TABLE *sp; + + for (sp = int_table; sp->name; sp++) + sp->target[0] = sp->defval; +} + +/* int_update - update int parameter */ + +static int int_update(char **argv) +{ + const INT_TABLE *ip; + + for (ip = int_table; ip->name; ip++) { + if (strcasecmp(argv[0], ip->name) == 0) { + if (!ISDIGIT(*argv[1])) + msg_fatal("bad number: %s %s", ip->name, argv[1]); + ip->target[0] = atoi(argv[1]); + return (1); + } + } + return (0); +} + + /* + * Restrictions. + */ +typedef struct { + char *name; + ARGV **target; +} REST_TABLE; + +static const REST_TABLE rest_table[] = { + "client_restrictions", &client_restrctions, + "helo_restrictions", &helo_restrctions, + "sender_restrictions", &mail_restrctions, + "relay_restrictions", &relay_restrctions, + "recipient_restrictions", &rcpt_restrctions, + "etrn_restrictions", &etrn_restrctions, + 0, +}; + +/* rest_update - update restriction */ + +static int rest_update(char **argv) +{ + const REST_TABLE *rp; + + for (rp = rest_table; rp->name; rp++) { + if (strcasecmp(rp->name, argv[0]) == 0) { + argv_free(rp->target[0]); + rp->target[0] = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, argv[1]); + return (1); + } + } + return (0); +} + +/* rest_class - (re)define a restriction class */ + +static void rest_class(char *class) +{ + char *cp = class; + char *name; + HTABLE_INFO *entry; + + if (smtpd_rest_classes == 0) + smtpd_rest_classes = htable_create(1); + + if ((name = mystrtok(&cp, CHARS_COMMA_SP)) == 0) + msg_panic("rest_class: null class name"); + if ((entry = htable_locate(smtpd_rest_classes, name)) != 0) + argv_free((ARGV *) entry->value); + else + entry = htable_enter(smtpd_rest_classes, name, (void *) 0); + entry->value = (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, cp); +} + +/* resolve_clnt_init - initialize reply */ + +void resolve_clnt_init(RESOLVE_REPLY *reply) +{ + reply->flags = 0; + reply->transport = vstring_alloc(100); + reply->nexthop = vstring_alloc(100); + reply->recipient = vstring_alloc(100); +} + +void resolve_clnt_free(RESOLVE_REPLY *reply) +{ + vstring_free(reply->transport); + vstring_free(reply->nexthop); + vstring_free(reply->recipient); +} + +bool var_smtpd_sasl_enable = 0; + +#ifdef USE_SASL_AUTH + +/* smtpd_sasl_activate - stub */ + +void smtpd_sasl_activate(SMTPD_STATE *state, const char *opts_name, + const char *opts_var) +{ + msg_panic("smtpd_sasl_activate was called"); +} + +/* smtpd_sasl_deactivate - stub */ + +void smtpd_sasl_deactivate(SMTPD_STATE *state) +{ + msg_panic("smtpd_sasl_deactivate was called"); +} + +/* permit_sasl_auth - stub */ + +int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot) +{ + return (ifnot); +} + +/* smtpd_sasl_state_init - the real deal */ + +void smtpd_sasl_state_init(SMTPD_STATE *state) +{ + state->sasl_username = 0; + state->sasl_method = 0; + state->sasl_sender = 0; +} + +#endif + +/* verify_clnt_query - stub */ + +int verify_clnt_query(const char *addr, int *addr_status, VSTRING *why) +{ + *addr_status = DEL_RCPT_STAT_OK; + return (VRFY_STAT_OK); +} + +/* rewrite_clnt_internal - stub */ + +VSTRING *rewrite_clnt_internal(const char *context, const char *addr, + VSTRING *result) +{ + if (addr == STR(result)) + msg_panic("rewrite_clnt_internal: result clobbers input"); + if (*addr && strchr(addr, '@') == 0) + msg_fatal("%s: address rewriting is disabled", addr); + vstring_strcpy(result, addr); + return (result); +} + +/* resolve_clnt_query - stub */ + +void resolve_clnt(const char *class, const char *unused_sender, const char *addr, + RESOLVE_REPLY *reply) +{ + const char *domain; + int rc; + + if (addr == CONST_STR(reply->recipient)) + msg_panic("resolve_clnt_query: result clobbers input"); + if (strchr(addr, '%')) + msg_fatal("%s: address rewriting is disabled", addr); + if ((domain = strrchr(addr, '@')) == 0) + msg_fatal("%s: unqualified address", addr); + domain += 1; + if ((rc = resolve_local(domain)) > 0) { + reply->flags = RESOLVE_CLASS_LOCAL; + vstring_strcpy(reply->transport, MAIL_SERVICE_LOCAL); + vstring_strcpy(reply->nexthop, domain); + } else if (rc < 0) { + reply->flags = RESOLVE_FLAG_FAIL; + } else if (string_list_match(virt_alias_doms, domain)) { + reply->flags = RESOLVE_CLASS_ALIAS; + vstring_strcpy(reply->transport, MAIL_SERVICE_ERROR); + vstring_strcpy(reply->nexthop, "user unknown"); + } else if (virt_alias_doms->error) { + reply->flags = RESOLVE_FLAG_FAIL; + } else if (string_list_match(virt_mailbox_doms, domain)) { + reply->flags = RESOLVE_CLASS_VIRTUAL; + vstring_strcpy(reply->transport, MAIL_SERVICE_VIRTUAL); + vstring_strcpy(reply->nexthop, domain); + } else if (virt_mailbox_doms->error) { + reply->flags = RESOLVE_FLAG_FAIL; + } else if (domain_list_match(relay_domains, domain)) { + reply->flags = RESOLVE_CLASS_RELAY; + vstring_strcpy(reply->transport, MAIL_SERVICE_RELAY); + vstring_strcpy(reply->nexthop, domain); + } else if (relay_domains->error) { + reply->flags = RESOLVE_FLAG_FAIL; + } else { + reply->flags = RESOLVE_CLASS_DEFAULT; + vstring_strcpy(reply->transport, MAIL_SERVICE_SMTP); + vstring_strcpy(reply->nexthop, domain); + } + vstring_strcpy(reply->recipient, addr); +} + +/* smtpd_chat_reset - stub */ + +void smtpd_chat_reset(SMTPD_STATE *unused_state) +{ +} + +/* usage - scream and terminate */ + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s", myname); +} + +int main(int argc, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + SMTPD_STATE state; + ARGV *args; + char *bp; + char *resp; + char *addr; + + /* + * Initialization. Use dummies for client information. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc != 1) + usage(argv[0]); + string_init(); + int_init(); + smtpd_check_init(); + smtpd_expand_init(); + (void) inet_proto_init(argv[0], INET_PROTO_NAME_IPV4); + smtpd_state_init(&state, VSTREAM_IN, "smtpd"); + state.queue_id = "<queue id>"; + + /* + * Main loop: update config parameters or test the client, helo, sender + * and recipient restrictions. + */ + while (vstring_fgets_nonl(buf, VSTREAM_IN) != 0) { + + /* + * Tokenize the command. Note, the comma is not a separator, so that + * restriction lists can be entered as comma-separated lists. + */ + bp = STR(buf); + if (!isatty(0)) { + vstream_printf(">>> %s\n", bp); + vstream_fflush(VSTREAM_OUT); + } + if (*bp == '#') + continue; + + if (*bp == '!') { + vstream_printf("exit %d\n", system(bp + 1)); + continue; + } + args = argv_splitq(bp, CHARS_SPACE, CHARS_BRACE); + + /* + * Recognize the command. + */ + resp = "bad command"; + switch (args->argc) { + + /* + * Emtpy line. + */ + case 0: + argv_free(args); + continue; + + /* + * Special case: rewrite context. + */ + case 1: + if (strcasecmp(args->argv[0], "rewrite") == 0) { + resp = smtpd_check_rewrite(&state); + break; + } + + /* + * Other parameter-less commands. + */ + if (strcasecmp(args->argv[0], "flush_dnsxl_cache") == 0) { + if (smtpd_rbl_cache) { + ctable_free(smtpd_rbl_cache); + ctable_free(smtpd_rbl_byte_cache); + } + smtpd_rbl_cache = ctable_create(100, rbl_pagein, + rbl_pageout, (void *) 0); + smtpd_rbl_byte_cache = ctable_create(1000, rbl_byte_pagein, + rbl_byte_pageout, (void *) 0); + resp = 0; + break; + } + + /* + * Special case: client identity. + */ + case 4: + case 3: + if (strcasecmp(args->argv[0], "client") == 0) { + state.where = SMTPD_AFTER_CONNECT; + UPDATE_STRING(state.name, args->argv[1]); + UPDATE_STRING(state.reverse_name, args->argv[1]); + UPDATE_STRING(state.addr, args->argv[2]); + if (args->argc == 4) + state.name_status = + state.reverse_name_status = + atoi(args->argv[3]); + else if (strcmp(state.name, "unknown") == 0) + state.name_status = + state.reverse_name_status = + SMTPD_PEER_CODE_TEMP; + else + state.name_status = + state.reverse_name_status = + SMTPD_PEER_CODE_OK; + if (state.namaddr) + myfree(state.namaddr); + state.namaddr = concatenate(state.name, "[", state.addr, + "]", (char *) 0); + resp = smtpd_check_client(&state); + } + break; + + /* + * Try config settings. + */ +#define UPDATE_MAPS(ptr, var, val, lock) \ + { if (ptr) maps_free(ptr); ptr = maps_create(var, val, lock); } + +#define UPDATE_LIST(ptr, var, val) \ + { if (ptr) string_list_free(ptr); \ + ptr = string_list_init(var, MATCH_FLAG_NONE, val); } + + case 2: + if (strcasecmp(args->argv[0], VAR_MYDEST) == 0) { + UPDATE_STRING(var_mydest, args->argv[1]); + resolve_local_init(); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_MAPS) == 0) { + UPDATE_STRING(var_virt_alias_maps, args->argv[1]); + UPDATE_MAPS(virt_alias_maps, VAR_VIRT_ALIAS_MAPS, + var_virt_alias_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_DOMS) == 0) { + UPDATE_STRING(var_virt_alias_doms, args->argv[1]); + UPDATE_LIST(virt_alias_doms, VAR_VIRT_ALIAS_DOMS, + var_virt_alias_doms); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_MAPS) == 0) { + UPDATE_STRING(var_virt_mailbox_maps, args->argv[1]); + UPDATE_MAPS(virt_mailbox_maps, VAR_VIRT_MAILBOX_MAPS, + var_virt_mailbox_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_DOMS) == 0) { + UPDATE_STRING(var_virt_mailbox_doms, args->argv[1]); + UPDATE_LIST(virt_mailbox_doms, VAR_VIRT_MAILBOX_DOMS, + var_virt_mailbox_doms); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_LOCAL_RCPT_MAPS) == 0) { + UPDATE_STRING(var_local_rcpt_maps, args->argv[1]); + UPDATE_MAPS(local_rcpt_maps, VAR_LOCAL_RCPT_MAPS, + var_local_rcpt_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_RELAY_RCPT_MAPS) == 0) { + UPDATE_STRING(var_relay_rcpt_maps, args->argv[1]); + UPDATE_MAPS(relay_rcpt_maps, VAR_RELAY_RCPT_MAPS, + var_relay_rcpt_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_CANONICAL_MAPS) == 0) { + UPDATE_STRING(var_canonical_maps, args->argv[1]); + UPDATE_MAPS(canonical_maps, VAR_CANONICAL_MAPS, + var_canonical_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_SEND_CANON_MAPS) == 0) { + UPDATE_STRING(var_send_canon_maps, args->argv[1]); + UPDATE_MAPS(send_canon_maps, VAR_SEND_CANON_MAPS, + var_send_canon_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_RCPT_CANON_MAPS) == 0) { + UPDATE_STRING(var_rcpt_canon_maps, args->argv[1]); + UPDATE_MAPS(rcpt_canon_maps, VAR_RCPT_CANON_MAPS, + var_rcpt_canon_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_RBL_REPLY_MAPS) == 0) { + UPDATE_STRING(var_rbl_reply_maps, args->argv[1]); + UPDATE_MAPS(rbl_reply_maps, VAR_RBL_REPLY_MAPS, + var_rbl_reply_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_MYNETWORKS) == 0) { + /* NOT: UPDATE_STRING */ + namadr_list_free(mynetworks_curr); + mynetworks_curr = + namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN + | match_parent_style(VAR_MYNETWORKS), + args->argv[1]); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_RELAY_DOMAINS) == 0) { + /* NOT: UPDATE_STRING */ + domain_list_free(relay_domains); + relay_domains = + domain_list_init(VAR_RELAY_DOMAINS, + match_parent_style(VAR_RELAY_DOMAINS), + args->argv[1]); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_PERM_MX_NETWORKS) == 0) { + UPDATE_STRING(var_perm_mx_networks, args->argv[1]); + domain_list_free(perm_mx_networks); + perm_mx_networks = + namadr_list_init(VAR_PERM_MX_NETWORKS, MATCH_FLAG_RETURN + | match_parent_style(VAR_PERM_MX_NETWORKS), + args->argv[1]); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_SMTPD_DNS_RE_FILTER) == 0) { + /* NOT: UPDATE_STRING */ + dns_rr_filter_compile(VAR_SMTPD_DNS_RE_FILTER, args->argv[1]); + resp = 0; + break; + } +#ifdef USE_TLS + if (strcasecmp(args->argv[0], VAR_RELAY_CCERTS) == 0) { + UPDATE_STRING(var_smtpd_relay_ccerts, args->argv[1]); + UPDATE_MAPS(relay_ccerts, VAR_RELAY_CCERTS, + var_smtpd_relay_ccerts, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX); + resp = 0; + } +#endif + if (strcasecmp(args->argv[0], "restriction_class") == 0) { + rest_class(args->argv[1]); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_LOC_RWR_CLIENTS) == 0) { + UPDATE_STRING(var_local_rwr_clients, args->argv[1]); + argv_free(local_rewrite_clients); + local_rewrite_clients = smtpd_check_parse(SMTPD_CHECK_PARSE_MAPS, + var_local_rwr_clients); + } + if (int_update(args->argv) + || string_update(args->argv) + || rest_update(args->argv)) { + resp = 0; + break; + } + + /* + * Try restrictions. + */ +#define TRIM_ADDR(src, res) { \ + if (*(res = src) == '<') { \ + res += strlen(res) - 1; \ + if (*res == '>') \ + *res = 0; \ + res = src + 1; \ + } \ + } + + if (strcasecmp(args->argv[0], "helo") == 0) { + state.where = "HELO"; + resp = smtpd_check_helo(&state, args->argv[1]); + UPDATE_STRING(state.helo_name, args->argv[1]); + } else if (strcasecmp(args->argv[0], "mail") == 0) { + state.where = "MAIL"; + TRIM_ADDR(args->argv[1], addr); + UPDATE_STRING(state.sender, addr); + resp = smtpd_check_mail(&state, addr); + } else if (strcasecmp(args->argv[0], "rcpt") == 0) { + state.where = "RCPT"; + TRIM_ADDR(args->argv[1], addr); + resp = smtpd_check_rcpt(&state, addr); +#ifdef USE_TLS + } else if (strcasecmp(args->argv[0], "fingerprint") == 0) { + if (state.tls_context == 0) { + state.tls_context = + (TLS_SESS_STATE *) mymalloc(sizeof(*state.tls_context)); + memset((void *) state.tls_context, 0, + sizeof(*state.tls_context)); + state.tls_context->peer_cert_fprint = + state.tls_context->peer_pkey_fprint = 0; + } + state.tls_context->peer_status |= TLS_CERT_FLAG_PRESENT; + UPDATE_STRING(state.tls_context->peer_cert_fprint, + args->argv[1]); + state.tls_context->peer_pkey_fprint = + state.tls_context->peer_cert_fprint; + resp = "OK"; + break; +#endif + } + break; + + /* + * Show commands. + */ + default: + if (strcasecmp(args->argv[0], "check_rewrite") == 0) { + smtpd_check_rewrite(&state); + resp = state.rewrite_context; + break; + } + resp = "Commands...\n\ + client <name> <address> [<code>]\n\ + helo <hostname>\n\ + sender <address>\n\ + recipient <address>\n\ + check_rewrite\n\ + msg_verbose <level>\n\ + client_restrictions <restrictions>\n\ + helo_restrictions <restrictions>\n\ + sender_restrictions <restrictions>\n\ + recipient_restrictions <restrictions>\n\ + restriction_class name,<restrictions>\n\ + flush_dnsxl_cache\n\ + \n\ + Note: no address rewriting \n"; + break; + } + vstream_printf("%s\n", resp ? resp : "OK"); + vstream_fflush(VSTREAM_OUT); + argv_free(args); + } + vstring_free(buf); + smtpd_state_reset(&state); +#define FREE_STRING(s) { if (s) myfree(s); } + FREE_STRING(state.helo_name); + FREE_STRING(state.sender); +#ifdef USE_TLS + if (state.tls_context) { + FREE_STRING(state.tls_context->peer_cert_fprint); + myfree((void *) state.tls_context); + } +#endif + exit(0); +} + +#endif diff --git a/src/smtpd/smtpd_check.h b/src/smtpd/smtpd_check.h new file mode 100644 index 0000000..ce24498 --- /dev/null +++ b/src/smtpd/smtpd_check.h @@ -0,0 +1,43 @@ +/*++ +/* 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 *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/smtpd/smtpd_check.in b/src/smtpd/smtpd_check.in new file mode 100644 index 0000000..980c7f8 --- /dev/null +++ b/src/smtpd/smtpd_check.in @@ -0,0 +1,182 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +relay_domains porcupine.org +maps_rbl_domains dnsbltest.porcupine.org +# +# Test the client restrictions. +# +client_restrictions permit_mynetworks,reject_unknown_client,hash:./smtpd_check_access +client unknown 131.155.210.17 +client unknown 168.100.189.13 +client random.bad.domain 123.123.123.123 +client friend.bad.domain 123.123.123.123 +client bad.domain 123.123.123.123 +client wzv.win.tue.nl 131.155.210.17 +client aa.win.tue.nl 131.155.210.18 +client_restrictions permit_mynetworks +# +# Test the helo restrictions +# +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,hash:./smtpd_check_access +client unknown 131.155.210.17 +helo foo. +client foo 123.123.123.123 +helo foo. +helo foo +helo spike.porcupine.org +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access +helo random.bad.domain +helo friend.bad.domain +helo_restrictions reject_invalid_hostname,reject_unknown_hostname +helo 123.123.123.123 +helo [123.123.123.123] +helo [::] +helo [ipv6:::] +helo [ipv6::::] +helo_restrictions permit_naked_ip_address,reject_invalid_hostname,reject_unknown_hostname +helo 123.123.123.123 +# +# Test the sender restrictions +# +sender_restrictions permit_mynetworks,reject_unknown_client +client unknown 131.155.210.17 +mail foo@ibm.com +client unknown 168.100.189.13 +mail foo@ibm.com +client foo 123.123.123.123 +mail foo@ibm.com +sender_restrictions reject_unknown_address +mail foo@ibm.com +mail foo@bad.domain +sender_restrictions hash:./smtpd_check_access +mail bad-sender@any.domain +mail bad-sender@good.domain +mail reject@this.address +mail Reject@this.address +mail foo@bad.domain +mail foo@Bad.domain +mail foo@random.bad.domain +mail foo@friend.bad.domain +# +# Test the recipient restrictions +# +recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains +client unknown 131.155.210.17 +rcpt foo@ibm.com +client unknown 168.100.189.13 +rcpt foo@ibm.com +client foo 123.123.123.123 +rcpt foo@ibm.com +rcpt foo@porcupine.org +recipient_restrictions check_relay_domains +client foo.porcupine.org 168.100.189.13 +rcpt foo@ibm.com +rcpt foo@porcupine.org +client foo 123.123.123.123 +rcpt foo@ibm.com +rcpt foo@porcupine.org +recipient_restrictions hash:./smtpd_check_access +mail bad-sender@any.domain +mail bad-sender@good.domain +mail reject@this.address +mail foo@bad.domain +mail foo@random.bad.domain +mail foo@friend.bad.domain +# +# RBL +# +client_restrictions reject_maps_rbl +client spike.porcupine.org 168.100.189.2 +client foo 127.0.0.2 +# +# Hybrids +# +recipient_restrictions check_relay_domains +client foo 131.155.210.17 +rcpt foo@ibm.com +recipient_restrictions check_client_access,hash:./smtpd_check_access,check_relay_domains +client foo 131.155.210.17 +rcpt foo@porcupine.org +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access +recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_relay_domains +helo bad.domain +rcpt foo@porcupine.org +helo 131.155.210.17 +rcpt foo@porcupine.org +recipient_restrictions check_sender_access,hash:./smtpd_check_access,check_relay_domains +mail foo@bad.domain +rcpt foo@porcupine.org +mail foo@friend.bad.domain +rcpt foo@porcupine.org +# +# MX backup +# +#mydestination spike.porcupine.org,localhost.porcupine.org +#inet_interfaces 168.100.189.2,127.0.0.1 +#recipient_restrictions permit_mx_backup,reject +#rcpt wietse@wzv.win.tue.nl +#rcpt wietse@trouble.org +#rcpt wietse@porcupine.org +# +# Deferred restrictions +# +client_restrictions permit +helo_restrictions permit +sender_restrictions permit +recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_sender_access,hash:./smtpd_check_access +helo bad.domain +mail foo@good.domain +rcpt foo@porcupine.org +helo good.domain +mail foo@bad.domain +rcpt foo@porcupine.org +# +# FQDN restrictions +# +helo_restrictions reject_non_fqdn_hostname +sender_restrictions reject_non_fqdn_sender +recipient_restrictions reject_non_fqdn_recipient +helo foo.bar. +helo foo.bar +helo foo +mail foo@foo.bar. +mail foo@foo.bar +mail foo@foo +mail foo +rcpt foo@foo.bar. +rcpt foo@foo.bar +rcpt foo@foo +rcpt foo +# +# Numerical HELO checks +# +helo_restrictions permit_naked_ip_address,reject_non_fqdn_hostname +helo [1.2.3.4] +helo [321.255.255.255] +helo [0.255.255.255] +helo [1.2.3.321] +helo [1.2.3] +helo [1.2.3.4.5] +helo [1..2.3.4] +helo [.1.2.3.4] +helo [1.2.3.4.5.] +helo 1.2.3.4 +helo 321.255.255.255 +helo 0.255.255.255 +helo 1.2.3.321 +helo 1.2.3 +helo 1.2.3.4.5 +helo 1..2.3.4 +helo .1.2.3.4 +helo 1.2.3.4.5. +# +# The defer restriction +# +defer_code 444 +helo_restrictions defer +helo foobar diff --git a/src/smtpd/smtpd_check.in2 b/src/smtpd/smtpd_check.in2 new file mode 100644 index 0000000..064cb41 --- /dev/null +++ b/src/smtpd/smtpd_check.in2 @@ -0,0 +1,116 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +relay_domains porcupine.org +maps_rbl_domains dnsbltest.porcupine.org +# +# Test the client restrictions. +# +client_restrictions permit_mynetworks,reject_unknown_client,check_client_access,hash:./smtpd_check_access +client unknown 131.155.210.17 +client unknown 168.100.189.13 +client random.bad.domain 123.123.123.123 +client friend.bad.domain 123.123.123.123 +client bad.domain 123.123.123.123 +client wzv.win.tue.nl 131.155.210.17 +client aa.win.tue.nl 131.155.210.18 +client_restrictions permit_mynetworks +# +# Test the helo restrictions +# +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,check_helo_access,hash:./smtpd_check_access +client unknown 131.155.210.17 +helo foo. +client foo 123.123.123.123 +helo foo. +helo foo +helo spike.porcupine.org +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,check_helo_access,hash:./smtpd_check_access +helo random.bad.domain +helo friend.bad.domain +# +# Test the sender restrictions +# +sender_restrictions permit_mynetworks,reject_unknown_client +client unknown 131.155.210.17 +mail foo@ibm.com +client unknown 168.100.189.13 +mail foo@ibm.com +client foo 123.123.123.123 +mail foo@ibm.com +sender_restrictions reject_unknown_address +mail foo@ibm.com +mail foo@bad.domain +sender_restrictions check_sender_access,hash:./smtpd_check_access +mail bad-sender@any.domain +mail bad-sender@good.domain +mail reject@this.address +mail Reject@this.address +mail foo@bad.domain +mail foo@Bad.domain +mail foo@random.bad.domain +mail foo@friend.bad.domain +# +# Test the recipient restrictions +# +recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains +client unknown 131.155.210.17 +rcpt foo@ibm.com +client unknown 168.100.189.13 +rcpt foo@ibm.com +client foo 123.123.123.123 +rcpt foo@ibm.com +rcpt foo@porcupine.org +recipient_restrictions check_relay_domains +client foo.porcupine.org 168.100.189.13 +rcpt foo@ibm.com +rcpt foo@porcupine.org +client foo 123.123.123.123 +rcpt foo@ibm.com +rcpt foo@porcupine.org +recipient_restrictions check_recipient_access,hash:./smtpd_check_access +mail bad-sender@any.domain +mail bad-sender@good.domain +mail reject@this.address +mail foo@bad.domain +mail foo@random.bad.domain +mail foo@friend.bad.domain +# +# RBL +# +client_restrictions reject_maps_rbl +client spike.porcupine.org 168.100.189.2 +client foo 127.0.0.2 +# +# unknown sender/recipient domain +# +unknown_address_reject_code 554 +recipient_restrictions reject_unknown_recipient_domain,reject_unknown_sender_domain +mail wietse@porcupine.org +rcpt wietse@porcupine.org +rcpt wietse@no.recipient.domain +mail wietse@no.sender.domain +rcpt wietse@porcupine.org +# +# {permit_auth,reject_unauth}_destination +# +relay_domains foo.com,bar.com +mail user@some.where +recipient_restrictions permit_auth_destination,reject +rcpt user@foo.org +rcpt user@foo.com +recipient_restrictions reject_unauth_destination,permit +rcpt user@foo.org +rcpt user@foo.com +# +# unknown client tests +# +unknown_client_reject_code 550 +client_restrictions reject_unknown_client +client spike.porcupine.org 160.100.189.2 2 +client unknown 1.1.1.1 4 +client unknown 1.1.1.1 5 diff --git a/src/smtpd/smtpd_check.in3 b/src/smtpd/smtpd_check.in3 new file mode 100644 index 0000000..52279f1 --- /dev/null +++ b/src/smtpd/smtpd_check.in3 @@ -0,0 +1,27 @@ +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +relay_domains porcupine.org +local_recipient_maps unix:passwd.byname +client unknown 131.155.210.17 +canonical_maps tcp:localhost:200 +# +recipient_restrictions permit +rcpt no.such.user@[127.0.0.1] +# +virtual_alias_maps tcp:localhost:100 +# +recipient_restrictions permit_mx_backup +rcpt wietse@nowhere1.com +# +recipient_restrictions check_relay_domains +rcpt wietse@nowhere2.com +# +recipient_restrictions reject_unknown_recipient_domain +rcpt wietse@nowhere3.com +# +recipient_restrictions permit_auth_destination +rcpt wietse@nowhere4.com +# +recipient_restrictions reject_unauth_destination +rcpt wietse@nowhere5.com diff --git a/src/smtpd/smtpd_check.in4 b/src/smtpd/smtpd_check.in4 new file mode 100644 index 0000000..d401de9 --- /dev/null +++ b/src/smtpd/smtpd_check.in4 @@ -0,0 +1,19 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +# +# Test the new access map features +# +sender_restrictions hash:./smtpd_check_access +mail rejecttext@bad.domain +mail filter@filter.domain +mail filtertext@filter.domain +mail filtertexttext@filter.domain +mail hold@hold.domain +mail holdtext@hold.domain +mail discard@hold.domain +mail discardtext@hold.domain +mail dunnotext@dunno.domain diff --git a/src/smtpd/smtpd_check.ref b/src/smtpd/smtpd_check.ref new file mode 100644 index 0000000..1a8090f --- /dev/null +++ b/src/smtpd/smtpd_check.ref @@ -0,0 +1,398 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.189.0/28 +OK +>>> relay_domains porcupine.org +OK +>>> maps_rbl_domains dnsbltest.porcupine.org +OK +>>> # +>>> # Test the client restrictions. +>>> # +>>> client_restrictions permit_mynetworks,reject_unknown_client,hash:./smtpd_check_access +OK +>>> client unknown 131.155.210.17 +./smtpd_check: <queue id>: reject: CONNECT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.189.13 +OK +>>> client random.bad.domain 123.123.123.123 +./smtpd_check: <queue id>: reject: CONNECT from random.bad.domain[123.123.123.123]: 554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP +554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain +>>> client friend.bad.domain 123.123.123.123 +OK +>>> client bad.domain 123.123.123.123 +./smtpd_check: <queue id>: reject: CONNECT from bad.domain[123.123.123.123]: 554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP +554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain +>>> client wzv.win.tue.nl 131.155.210.17 +OK +>>> client aa.win.tue.nl 131.155.210.18 +./smtpd_check: <queue id>: reject: CONNECT from aa.win.tue.nl[131.155.210.18]: 554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210; proto=SMTP +554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210 +>>> client_restrictions permit_mynetworks +OK +>>> # +>>> # Test the helo restrictions +>>> # +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,hash:./smtpd_check_access +OK +>>> client unknown 131.155.210.17 +OK +>>> helo foo. +./smtpd_check: <queue id>: reject: HELO from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP helo=<foo.> +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client foo 123.123.123.123 +OK +>>> helo foo. +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo.>: Helo command rejected: Host not found; proto=SMTP helo=<foo.> +450 4.7.1 <foo.>: Helo command rejected: Host not found +>>> helo foo +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo>: Helo command rejected: Host not found; proto=SMTP helo=<foo> +450 4.7.1 <foo>: Helo command rejected: Host not found +>>> helo spike.porcupine.org +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <spike.porcupine.org>: Helo command rejected: ns or mx server spike.porcupine.org; proto=SMTP helo=<spike.porcupine.org> +554 5.7.1 <spike.porcupine.org>: Helo command rejected: ns or mx server spike.porcupine.org +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access +OK +>>> helo random.bad.domain +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain; proto=SMTP helo=<random.bad.domain> +554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain +>>> helo friend.bad.domain +OK +>>> helo_restrictions reject_invalid_hostname,reject_unknown_hostname +OK +>>> helo 123.123.123.123 +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <123.123.123.123>: Helo command rejected: Host not found; proto=SMTP helo=<123.123.123.123> +450 4.7.1 <123.123.123.123>: Helo command rejected: Host not found +>>> helo [123.123.123.123] +OK +>>> helo [::] +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 501 5.5.2 <[::]>: Helo command rejected: invalid ip address; proto=SMTP helo=<[::]> +501 5.5.2 <[::]>: Helo command rejected: invalid ip address +>>> helo [ipv6:::] +OK +>>> helo [ipv6::::] +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 501 5.5.2 <[ipv6::::]>: Helo command rejected: invalid ip address; proto=SMTP helo=<[ipv6::::]> +501 5.5.2 <[ipv6::::]>: Helo command rejected: invalid ip address +>>> helo_restrictions permit_naked_ip_address,reject_invalid_hostname,reject_unknown_hostname +OK +>>> helo 123.123.123.123 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +OK +>>> # +>>> # Test the sender restrictions +>>> # +>>> sender_restrictions permit_mynetworks,reject_unknown_client +OK +>>> client unknown 131.155.210.17 +OK +>>> mail foo@ibm.com +./smtpd_check: <queue id>: reject: MAIL from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@ibm.com> proto=SMTP helo=<123.123.123.123> +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.189.13 +OK +>>> mail foo@ibm.com +OK +>>> client foo 123.123.123.123 +OK +>>> mail foo@ibm.com +OK +>>> sender_restrictions reject_unknown_address +OK +>>> mail foo@ibm.com +OK +>>> mail foo@bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found; from=<foo@bad.domain> proto=SMTP helo=<123.123.123.123> +450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found +>>> sender_restrictions hash:./smtpd_check_access +OK +>>> mail bad-sender@any.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@ +>>> mail bad-sender@good.domain +OK +>>> mail reject@this.address +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address +>>> mail Reject@this.address +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address; from=<Reject@this.address> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address +>>> mail foo@bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@Bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain; from=<foo@Bad.domain> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@random.bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> # +>>> # Test the recipient restrictions +>>> # +>>> recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains +OK +>>> client unknown 131.155.210.17 +OK +>>> rcpt foo@ibm.com +./smtpd_check: <queue id>: reject: RCPT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123> +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.189.13 +OK +>>> rcpt foo@ibm.com +OK +>>> client foo 123.123.123.123 +OK +>>> rcpt foo@ibm.com +./smtpd_check: warning: support for restriction "check_relay_domains" will be removed from Postfix; use "reject_unauth_destination" instead +./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions check_relay_domains +OK +>>> client foo.porcupine.org 168.100.189.13 +OK +>>> rcpt foo@ibm.com +OK +>>> rcpt foo@porcupine.org +OK +>>> client foo 123.123.123.123 +OK +>>> rcpt foo@ibm.com +./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions hash:./smtpd_check_access +OK +>>> mail bad-sender@any.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@ +>>> mail bad-sender@good.domain +OK +>>> mail reject@this.address +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address +>>> mail foo@bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@random.bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> # +>>> # RBL +>>> # +>>> client_restrictions reject_maps_rbl +OK +>>> client spike.porcupine.org 168.100.189.2 +./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead +OK +>>> client foo 127.0.0.2 +./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<foo@friend.bad.domain> proto=SMTP helo=<123.123.123.123> +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org +>>> # +>>> # Hybrids +>>> # +>>> recipient_restrictions check_relay_domains +OK +>>> client foo 131.155.210.17 +OK +>>> rcpt foo@ibm.com +./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<123.123.123.123> +554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied +>>> recipient_restrictions check_client_access,hash:./smtpd_check_access,check_relay_domains +OK +>>> client foo 131.155.210.17 +OK +>>> rcpt foo@porcupine.org +OK +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access +OK +>>> recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_relay_domains +OK +>>> helo bad.domain +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@friend.bad.domain> proto=SMTP helo=<bad.domain> +554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain +>>> rcpt foo@porcupine.org +./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@friend.bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<bad.domain> +554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain +>>> helo 131.155.210.17 +OK +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions check_sender_access,hash:./smtpd_check_access,check_relay_domains +OK +>>> mail foo@bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[131.155.210.17]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<131.155.210.17> +554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain +>>> rcpt foo@porcupine.org +./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<131.155.210.17> +554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> rcpt foo@porcupine.org +OK +>>> # +>>> # MX backup +>>> # +>>> #mydestination spike.porcupine.org,localhost.porcupine.org +>>> #inet_interfaces 168.100.189.2,127.0.0.1 +>>> #recipient_restrictions permit_mx_backup,reject +>>> #rcpt wietse@wzv.win.tue.nl +>>> #rcpt wietse@trouble.org +>>> #rcpt wietse@porcupine.org +>>> # +>>> # Deferred restrictions +>>> # +>>> client_restrictions permit +OK +>>> helo_restrictions permit +OK +>>> sender_restrictions permit +OK +>>> recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_sender_access,hash:./smtpd_check_access +OK +>>> helo bad.domain +OK +>>> mail foo@good.domain +OK +>>> rcpt foo@porcupine.org +./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@good.domain> to=<foo@porcupine.org> proto=SMTP helo=<bad.domain> +554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain +>>> helo good.domain +OK +>>> mail foo@bad.domain +OK +>>> rcpt foo@porcupine.org +./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<good.domain> +554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain +>>> # +>>> # FQDN restrictions +>>> # +>>> helo_restrictions reject_non_fqdn_hostname +OK +>>> sender_restrictions reject_non_fqdn_sender +OK +>>> recipient_restrictions reject_non_fqdn_recipient +OK +>>> helo foo.bar. +OK +>>> helo foo.bar +OK +>>> helo foo +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 504 5.5.2 <foo>: Helo command rejected: need fully-qualified hostname; from=<foo@bad.domain> proto=SMTP helo=<foo> +504 5.5.2 <foo>: Helo command rejected: need fully-qualified hostname +>>> mail foo@foo.bar. +OK +>>> mail foo@foo.bar +OK +>>> mail foo@foo +./smtpd_check: <queue id>: reject: MAIL from foo[131.155.210.17]: 504 5.5.2 <foo@foo>: Sender address rejected: need fully-qualified address; from=<foo@foo> proto=SMTP helo=<foo> +504 5.5.2 <foo@foo>: Sender address rejected: need fully-qualified address +>>> mail foo +./smtpd_check: <queue id>: reject: MAIL from foo[131.155.210.17]: 504 5.5.2 <foo>: Sender address rejected: need fully-qualified address; from=<foo> proto=SMTP helo=<foo> +504 5.5.2 <foo>: Sender address rejected: need fully-qualified address +>>> rcpt foo@foo.bar. +OK +>>> rcpt foo@foo.bar +OK +>>> rcpt foo@foo +./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 504 5.5.2 <foo@foo>: Recipient address rejected: need fully-qualified address; from=<foo> to=<foo@foo> proto=SMTP helo=<foo> +504 5.5.2 <foo@foo>: Recipient address rejected: need fully-qualified address +>>> rcpt foo +./smtpd_check: <queue id>: reject: RCPT from foo[131.155.210.17]: 504 5.5.2 <foo>: Recipient address rejected: need fully-qualified address; from=<foo> to=<foo> proto=SMTP helo=<foo> +504 5.5.2 <foo>: Recipient address rejected: need fully-qualified address +>>> # +>>> # Numerical HELO checks +>>> # +>>> helo_restrictions permit_naked_ip_address,reject_non_fqdn_hostname +OK +>>> helo [1.2.3.4] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +OK +>>> helo [321.255.255.255] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[321.255.255.255]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[321.255.255.255]> +501 5.5.2 <[321.255.255.255]>: Helo command rejected: invalid ip address +>>> helo [0.255.255.255] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[0.255.255.255]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[0.255.255.255]> +501 5.5.2 <[0.255.255.255]>: Helo command rejected: invalid ip address +>>> helo [1.2.3.321] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.321]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3.321]> +501 5.5.2 <[1.2.3.321]>: Helo command rejected: invalid ip address +>>> helo [1.2.3] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3]> +501 5.5.2 <[1.2.3]>: Helo command rejected: invalid ip address +>>> helo [1.2.3.4.5] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.4.5]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3.4.5]> +501 5.5.2 <[1.2.3.4.5]>: Helo command rejected: invalid ip address +>>> helo [1..2.3.4] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1..2.3.4]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1..2.3.4]> +501 5.5.2 <[1..2.3.4]>: Helo command rejected: invalid ip address +>>> helo [.1.2.3.4] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[.1.2.3.4]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[.1.2.3.4]> +501 5.5.2 <[.1.2.3.4]>: Helo command rejected: invalid ip address +>>> helo [1.2.3.4.5.] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.4.5.]>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<[1.2.3.4.5.]> +501 5.5.2 <[1.2.3.4.5.]>: Helo command rejected: invalid ip address +>>> helo 1.2.3.4 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +OK +>>> helo 321.255.255.255 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <321.255.255.255>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<321.255.255.255> +501 5.5.2 <321.255.255.255>: Helo command rejected: invalid ip address +>>> helo 0.255.255.255 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <0.255.255.255>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<0.255.255.255> +501 5.5.2 <0.255.255.255>: Helo command rejected: invalid ip address +>>> helo 1.2.3.321 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.321>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3.321> +501 5.5.2 <1.2.3.321>: Helo command rejected: invalid ip address +>>> helo 1.2.3 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3> +501 5.5.2 <1.2.3>: Helo command rejected: invalid ip address +>>> helo 1.2.3.4.5 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.4.5>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3.4.5> +501 5.5.2 <1.2.3.4.5>: Helo command rejected: invalid ip address +>>> helo 1..2.3.4 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1..2.3.4>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1..2.3.4> +501 5.5.2 <1..2.3.4>: Helo command rejected: invalid ip address +>>> helo .1.2.3.4 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <.1.2.3.4>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<.1.2.3.4> +501 5.5.2 <.1.2.3.4>: Helo command rejected: invalid ip address +>>> helo 1.2.3.4.5. +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.4.5.>: Helo command rejected: invalid ip address; from=<foo> proto=SMTP helo=<1.2.3.4.5.> +501 5.5.2 <1.2.3.4.5.>: Helo command rejected: invalid ip address +>>> # +>>> # The defer restriction +>>> # +>>> defer_code 444 +OK +>>> helo_restrictions defer +OK +>>> helo foobar +./smtpd_check: <queue id>: reject: HELO from foo[131.155.210.17]: 444 4.3.2 <foobar>: Helo command rejected: Try again later; from=<foo> proto=SMTP helo=<foobar> +444 4.3.2 <foobar>: Helo command rejected: Try again later diff --git a/src/smtpd/smtpd_check.ref2 b/src/smtpd/smtpd_check.ref2 new file mode 100644 index 0000000..9322457 --- /dev/null +++ b/src/smtpd/smtpd_check.ref2 @@ -0,0 +1,236 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.189.0/28 +OK +>>> relay_domains porcupine.org +OK +>>> maps_rbl_domains dnsbltest.porcupine.org +OK +>>> # +>>> # Test the client restrictions. +>>> # +>>> client_restrictions permit_mynetworks,reject_unknown_client,check_client_access,hash:./smtpd_check_access +OK +>>> client unknown 131.155.210.17 +./smtpd_check: <queue id>: reject: CONNECT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.189.13 +OK +>>> client random.bad.domain 123.123.123.123 +./smtpd_check: <queue id>: reject: CONNECT from random.bad.domain[123.123.123.123]: 554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP +554 5.7.1 <random.bad.domain[123.123.123.123]>: Client host rejected: match bad.domain +>>> client friend.bad.domain 123.123.123.123 +OK +>>> client bad.domain 123.123.123.123 +./smtpd_check: <queue id>: reject: CONNECT from bad.domain[123.123.123.123]: 554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain; proto=SMTP +554 5.7.1 <bad.domain[123.123.123.123]>: Client host rejected: match bad.domain +>>> client wzv.win.tue.nl 131.155.210.17 +OK +>>> client aa.win.tue.nl 131.155.210.18 +./smtpd_check: <queue id>: reject: CONNECT from aa.win.tue.nl[131.155.210.18]: 554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210; proto=SMTP +554 5.7.1 <aa.win.tue.nl[131.155.210.18]>: Client host rejected: match 131.155.210 +>>> client_restrictions permit_mynetworks +OK +>>> # +>>> # Test the helo restrictions +>>> # +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,check_helo_access,hash:./smtpd_check_access +OK +>>> client unknown 131.155.210.17 +OK +>>> helo foo. +./smtpd_check: <queue id>: reject: HELO from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP helo=<foo.> +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client foo 123.123.123.123 +OK +>>> helo foo. +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo.>: Helo command rejected: Host not found; proto=SMTP helo=<foo.> +450 4.7.1 <foo.>: Helo command rejected: Host not found +>>> helo foo +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 450 4.7.1 <foo>: Helo command rejected: Host not found; proto=SMTP helo=<foo> +450 4.7.1 <foo>: Helo command rejected: Host not found +>>> helo spike.porcupine.org +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <spike.porcupine.org>: Helo command rejected: name server spike.porcupine.org; proto=SMTP helo=<spike.porcupine.org> +554 5.7.1 <spike.porcupine.org>: Helo command rejected: name server spike.porcupine.org +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,check_helo_access,hash:./smtpd_check_access +OK +>>> helo random.bad.domain +./smtpd_check: <queue id>: reject: HELO from foo[123.123.123.123]: 554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain; proto=SMTP helo=<random.bad.domain> +554 5.7.1 <random.bad.domain>: Helo command rejected: match bad.domain +>>> helo friend.bad.domain +OK +>>> # +>>> # Test the sender restrictions +>>> # +>>> sender_restrictions permit_mynetworks,reject_unknown_client +OK +>>> client unknown 131.155.210.17 +OK +>>> mail foo@ibm.com +./smtpd_check: <queue id>: reject: MAIL from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain> +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.189.13 +OK +>>> mail foo@ibm.com +OK +>>> client foo 123.123.123.123 +OK +>>> mail foo@ibm.com +OK +>>> sender_restrictions reject_unknown_address +OK +>>> mail foo@ibm.com +OK +>>> mail foo@bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found; from=<foo@bad.domain> proto=SMTP helo=<friend.bad.domain> +450 4.1.8 <foo@bad.domain>: Sender address rejected: Domain not found +>>> sender_restrictions check_sender_access,hash:./smtpd_check_access +OK +>>> mail bad-sender@any.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@ +>>> mail bad-sender@good.domain +OK +>>> mail reject@this.address +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address +>>> mail Reject@this.address +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address; from=<Reject@this.address> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <Reject@this.address>: Sender address rejected: match reject@this.address +>>> mail foo@bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@Bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain; from=<foo@Bad.domain> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <foo@Bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@random.bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> # +>>> # Test the recipient restrictions +>>> # +>>> recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains +OK +>>> client unknown 131.155.210.17 +OK +>>> rcpt foo@ibm.com +./smtpd_check: <queue id>: reject: RCPT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain> +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.189.13 +OK +>>> rcpt foo@ibm.com +OK +>>> client foo 123.123.123.123 +OK +>>> rcpt foo@ibm.com +./smtpd_check: warning: support for restriction "check_relay_domains" will be removed from Postfix; use "reject_unauth_destination" instead +./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions check_relay_domains +OK +>>> client foo.porcupine.org 168.100.189.13 +OK +>>> rcpt foo@ibm.com +OK +>>> rcpt foo@porcupine.org +OK +>>> client foo 123.123.123.123 +OK +>>> rcpt foo@ibm.com +./smtpd_check: <queue id>: reject: RCPT from foo[123.123.123.123]: 554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied; from=<foo@friend.bad.domain> to=<foo@ibm.com> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <foo@ibm.com>: Recipient address rejected: Relay access denied +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions check_recipient_access,hash:./smtpd_check_access +OK +>>> mail bad-sender@any.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@; from=<bad-sender@any.domain> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <bad-sender@any.domain>: Sender address rejected: match bad-sender@ +>>> mail bad-sender@good.domain +OK +>>> mail reject@this.address +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address; from=<reject@this.address> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <reject@this.address>: Sender address rejected: match reject@this.address +>>> mail foo@bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@random.bad.domain +./smtpd_check: <queue id>: reject: MAIL from foo[123.123.123.123]: 554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain; from=<foo@random.bad.domain> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <foo@random.bad.domain>: Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> # +>>> # RBL +>>> # +>>> client_restrictions reject_maps_rbl +OK +>>> client spike.porcupine.org 168.100.189.2 +./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead +OK +>>> client foo 127.0.0.2 +./smtpd_check: <queue id>: reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<foo@friend.bad.domain> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org +>>> # +>>> # unknown sender/recipient domain +>>> # +>>> unknown_address_reject_code 554 +OK +>>> recipient_restrictions reject_unknown_recipient_domain,reject_unknown_sender_domain +OK +>>> mail wietse@porcupine.org +OK +>>> rcpt wietse@porcupine.org +OK +>>> rcpt wietse@no.recipient.domain +./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.1.2 <wietse@no.recipient.domain>: Recipient address rejected: Domain not found; from=<wietse@porcupine.org> to=<wietse@no.recipient.domain> proto=SMTP helo=<friend.bad.domain> +554 5.1.2 <wietse@no.recipient.domain>: Recipient address rejected: Domain not found +>>> mail wietse@no.sender.domain +OK +>>> rcpt wietse@porcupine.org +./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.1.8 <wietse@no.sender.domain>: Sender address rejected: Domain not found; from=<wietse@no.sender.domain> to=<wietse@porcupine.org> proto=SMTP helo=<friend.bad.domain> +554 5.1.8 <wietse@no.sender.domain>: Sender address rejected: Domain not found +>>> # +>>> # {permit_auth,reject_unauth}_destination +>>> # +>>> relay_domains foo.com,bar.com +OK +>>> mail user@some.where +OK +>>> recipient_restrictions permit_auth_destination,reject +OK +>>> rcpt user@foo.org +./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 <user@foo.org>: Recipient address rejected: Access denied; from=<user@some.where> to=<user@foo.org> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <user@foo.org>: Recipient address rejected: Access denied +>>> rcpt user@foo.com +OK +>>> recipient_restrictions reject_unauth_destination,permit +OK +>>> rcpt user@foo.org +./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 <user@foo.org>: Relay access denied; from=<user@some.where> to=<user@foo.org> proto=SMTP helo=<friend.bad.domain> +554 5.7.1 <user@foo.org>: Relay access denied +>>> rcpt user@foo.com +OK +>>> # +>>> # unknown client tests +>>> # +>>> unknown_client_reject_code 550 +OK +>>> client_restrictions reject_unknown_client +OK +>>> client spike.porcupine.org 160.100.189.2 2 +OK +>>> client unknown 1.1.1.1 4 +./smtpd_check: <queue id>: reject: CONNECT from unknown[1.1.1.1]: 450 4.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]; from=<user@some.where> proto=SMTP helo=<friend.bad.domain> +450 4.7.1 Client host rejected: cannot find your hostname, [1.1.1.1] +>>> client unknown 1.1.1.1 5 +./smtpd_check: <queue id>: reject: CONNECT from unknown[1.1.1.1]: 550 5.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]; from=<user@some.where> proto=SMTP helo=<friend.bad.domain> +550 5.7.1 Client host rejected: cannot find your hostname, [1.1.1.1] diff --git a/src/smtpd/smtpd_check.ref4 b/src/smtpd/smtpd_check.ref4 new file mode 100644 index 0000000..8e9a6df --- /dev/null +++ b/src/smtpd/smtpd_check.ref4 @@ -0,0 +1,38 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> # +>>> # Test the new access map features +>>> # +>>> sender_restrictions hash:./smtpd_check_access +OK +>>> mail rejecttext@bad.domain +./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 554 5.7.1 <rejecttext@bad.domain>: Sender address rejected: text; from=<rejecttext@bad.domain> proto=SMTP +554 5.7.1 <rejecttext@bad.domain>: Sender address rejected: text +>>> mail filter@filter.domain +./smtpd_check: warning: access table hash:./smtpd_check_access entry "filter@filter.domain" has FILTER entry without value +OK +>>> mail filtertext@filter.domain +./smtpd_check: warning: access table hash:./smtpd_check_access entry "filtertext@filter.domain" requires transport:destination +OK +>>> mail filtertexttext@filter.domain +./smtpd_check: <queue id>: filter: MAIL from localhost[127.0.0.1]: <filtertexttext@filter.domain>: Sender address triggers FILTER text:text; from=<filtertexttext@filter.domain> proto=SMTP +OK +>>> mail hold@hold.domain +./smtpd_check: <queue id>: hold: MAIL from localhost[127.0.0.1]: <hold@hold.domain>: Sender address triggers HOLD action; from=<hold@hold.domain> proto=SMTP +OK +>>> mail holdtext@hold.domain +./smtpd_check: <queue id>: hold: MAIL from localhost[127.0.0.1]: <holdtext@hold.domain>: Sender address text; from=<holdtext@hold.domain> proto=SMTP +OK +>>> mail discard@hold.domain +./smtpd_check: <queue id>: discard: MAIL from localhost[127.0.0.1]: <discard@hold.domain>: Sender address triggers DISCARD action; from=<discard@hold.domain> proto=SMTP +OK +>>> mail discardtext@hold.domain +./smtpd_check: <queue id>: discard: MAIL from localhost[127.0.0.1]: <discardtext@hold.domain>: Sender address text; from=<discardtext@hold.domain> proto=SMTP +OK +>>> mail dunnotext@dunno.domain +OK diff --git a/src/smtpd/smtpd_check_access b/src/smtpd/smtpd_check_access new file mode 100644 index 0000000..788276a --- /dev/null +++ b/src/smtpd/smtpd_check_access @@ -0,0 +1,91 @@ +bad.domain 554 match bad.domain +friend.bad.domain OK +bad-sender@ 554 match bad-sender@ +bad-sender@good.domain OK +good-sender@ OK +131.155.210 554 match 131.155.210 +131.155.210.17 OK +131.155.210.19 REJECT +reject@this.address 554 match reject@this.address +open_user@some.site open +strict_user@some.site strict +auth_client 123456 + +dunno.com dunno +foo.dunno.com reject + +44.33.22 dunno +44.33.22.11 REJECT +44.33 REJECT + +reject@dunno.domain REJECT +ok@dunno.domain OK +dunno.domain DUNNO + +reject@reject.domain REJECT +ok@reject.domain OK +reject.domain REJECT + +reject@ok.domain REJECT +ok@ok.domain OK +ok.domain OK +<> 550 Go away postmaster + +54.187.136.235 reject bizsat.net, gypsysoul.org spam + +blackholes.mail-abuse.org $rbl_code client=$client + client_address=$client_address + client_name=$client_name helo_name=$helo_name + sender=$sender sender_name=$sender_name sender_domain=$sender_domain + recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain + rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what + rbl_class=$rbl_class + +rhsbl.porcupine.org $rbl_code client=$client + client_address=$client_address + client_name=$client_name helo_name=$helo_name + sender=$sender sender_name=$sender_name sender_domain=$sender_domain + recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain + rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what + rbl_class=$rbl_class + +dnswl.porcupine.org $rbl_code client=$client + client_address=$client_address + client_name=$client_name helo_name=$helo_name + sender=$sender sender_name=$sender_name sender_domain=$sender_domain + recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain + rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what + rbl_class=$rbl_class + +rejecttext@bad.domain reject text +filter@filter.domain filter +filtertext@filter.domain filter text +filtertexttext@filter.domain filter text:text +hold@hold.domain hold +holdtext@hold.domain hold text +discard@hold.domain discard +discardtext@hold.domain discard text +dunnotext@dunno.domain dunno text +64.94.110.11 reject Verisign wild-card +topica.com reject +10.10.10.10 reject mail server 10.10.10.10 +spike.porcupine.org reject ns or mx server spike.porcupine.org +241 reject class E subnet +4.1.1_dsn reject 4.1.1 reject +4.1.2_dsn reject 4.1.2 reject +4.1.3_dsn reject 4.1.3 reject +4.1.4_dsn reject 4.1.4 reject +4.1.5_dsn reject 4.1.5 reject +4.1.6_dsn reject 4.1.6 reject +4.1.7_dsn reject 4.1.7 reject +4.1.8_dsn reject 4.1.8 reject +4.4.0_dsn reject 4.4.0 reject +user@4.1.1_dsn reject 4.1.1 reject +user@4.1.2_dsn reject 4.1.2 reject +user@4.1.3_dsn reject 4.1.3 reject +user@4.1.4_dsn reject 4.1.4 reject +user@4.1.5_dsn reject 4.1.5 reject +user@4.1.6_dsn reject 4.1.6 reject +user@4.1.7_dsn reject 4.1.7 reject +user@4.1.8_dsn reject 4.1.8 reject +user@4.4.0_dsn reject 4.4.0 reject diff --git a/src/smtpd/smtpd_check_backup.in b/src/smtpd/smtpd_check_backup.in new file mode 100644 index 0000000..c4f44bb --- /dev/null +++ b/src/smtpd/smtpd_check_backup.in @@ -0,0 +1,20 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +# +# MX backup +# +mydestination wzv.porcupine.org,localhost.porcupine.org +inet_interfaces 168.100.189.7,127.0.0.1 +recipient_restrictions permit_mx_backup,reject +rcpt wietse@wzv.porcupine.org +rcpt wietse@backup.porcupine.org +rcpt wietse@porcupine.org +permit_mx_backup_networks 168.100.189.5 +rcpt wietse@backup.porcupine.org +permit_mx_backup_networks 168.100.189.4 +rcpt wietse@backup.porcupine.org diff --git a/src/smtpd/smtpd_check_backup.ref b/src/smtpd/smtpd_check_backup.ref new file mode 100644 index 0000000..9f20b90 --- /dev/null +++ b/src/smtpd/smtpd_check_backup.ref @@ -0,0 +1,34 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.189.0/28 +OK +>>> # +>>> # MX backup +>>> # +>>> mydestination wzv.porcupine.org,localhost.porcupine.org +OK +>>> inet_interfaces 168.100.189.7,127.0.0.1 +OK +>>> recipient_restrictions permit_mx_backup,reject +OK +>>> rcpt wietse@wzv.porcupine.org +OK +>>> rcpt wietse@backup.porcupine.org +OK +>>> rcpt wietse@porcupine.org +./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 554 5.7.1 <wietse@porcupine.org>: Recipient address rejected: Access denied; to=<wietse@porcupine.org> proto=SMTP +554 5.7.1 <wietse@porcupine.org>: Recipient address rejected: Access denied +>>> permit_mx_backup_networks 168.100.189.5 +OK +>>> rcpt wietse@backup.porcupine.org +./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 554 5.7.1 <wietse@backup.porcupine.org>: Recipient address rejected: Access denied; to=<wietse@backup.porcupine.org> proto=SMTP +554 5.7.1 <wietse@backup.porcupine.org>: Recipient address rejected: Access denied +>>> permit_mx_backup_networks 168.100.189.4 +OK +>>> rcpt wietse@backup.porcupine.org +OK diff --git a/src/smtpd/smtpd_check_dsn.in b/src/smtpd/smtpd_check_dsn.in new file mode 100644 index 0000000..459bd5d --- /dev/null +++ b/src/smtpd/smtpd_check_dsn.in @@ -0,0 +1,60 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +# +# Test the client restrictions. +# +client_restrictions hash:./smtpd_check_access +client 4.1.1_dsn 1.2.3.4 +client 4.1.2_dsn 1.2.3.4 +client 4.1.3_dsn 1.2.3.4 +client 4.1.4_dsn 1.2.3.4 +client 4.1.5_dsn 1.2.3.4 +client 4.1.6_dsn 1.2.3.4 +client 4.1.7_dsn 1.2.3.4 +client 4.1.8_dsn 1.2.3.4 +client 4.4.0_dsn 1.2.3.4 +client dummy dummy +# +# Test the helo restrictions +# +helo_restrictions hash:./smtpd_check_access +helo 4.1.1_dsn +helo 4.1.2_dsn +helo 4.1.3_dsn +helo 4.1.4_dsn +helo 4.1.5_dsn +helo 4.1.6_dsn +helo 4.1.7_dsn +helo 4.1.8_dsn +helo 4.4.0_dsn +# +# Test the sender restrictions +# +sender_restrictions hash:./smtpd_check_access +mail user@4.1.1_dsn +mail user@4.1.2_dsn +mail user@4.1.3_dsn +mail user@4.1.4_dsn +mail user@4.1.5_dsn +mail user@4.1.6_dsn +mail user@4.1.7_dsn +mail user@4.1.8_dsn +mail user@4.4.0_dsn +# +# Test the recipient restrictions +# +recipient_restrictions hash:./smtpd_check_access +rcpt user@4.1.1_dsn +rcpt user@4.1.2_dsn +rcpt user@4.1.3_dsn +rcpt user@4.1.4_dsn +rcpt user@4.1.5_dsn +rcpt user@4.1.6_dsn +rcpt user@4.1.7_dsn +rcpt user@4.1.8_dsn +rcpt user@4.4.0_dsn diff --git a/src/smtpd/smtpd_check_dsn.ref b/src/smtpd/smtpd_check_dsn.ref new file mode 100644 index 0000000..87b1bb9 --- /dev/null +++ b/src/smtpd/smtpd_check_dsn.ref @@ -0,0 +1,163 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.189.0/28 +OK +>>> # +>>> # Test the client restrictions. +>>> # +>>> client_restrictions hash:./smtpd_check_access +OK +>>> client 4.1.1_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.1 into Client host status 4.0.0 +./smtpd_check: <queue id>: reject: CONNECT from 4.1.1_dsn[1.2.3.4]: 554 5.0.0 <4.1.1_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.1_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.2_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.2 into Client host status 4.0.0 +./smtpd_check: <queue id>: reject: CONNECT from 4.1.2_dsn[1.2.3.4]: 554 5.0.0 <4.1.2_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.2_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.3_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.3 into Client host status 4.0.0 +./smtpd_check: <queue id>: reject: CONNECT from 4.1.3_dsn[1.2.3.4]: 554 5.0.0 <4.1.3_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.3_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.4_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.4 into Client host status 4.0.0 +./smtpd_check: <queue id>: reject: CONNECT from 4.1.4_dsn[1.2.3.4]: 554 5.0.0 <4.1.4_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.4_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.5_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.5 into Client host status 4.0.0 +./smtpd_check: <queue id>: reject: CONNECT from 4.1.5_dsn[1.2.3.4]: 554 5.0.0 <4.1.5_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.5_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.6_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.6 into Client host status 4.0.0 +./smtpd_check: <queue id>: reject: CONNECT from 4.1.6_dsn[1.2.3.4]: 554 5.0.0 <4.1.6_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.6_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.7_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.7 into Client host status 4.0.0 +./smtpd_check: <queue id>: reject: CONNECT from 4.1.7_dsn[1.2.3.4]: 554 5.0.0 <4.1.7_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.7_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.8_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.8 into Client host status 4.0.0 +./smtpd_check: <queue id>: reject: CONNECT from 4.1.8_dsn[1.2.3.4]: 554 5.0.0 <4.1.8_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.8_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.4.0_dsn 1.2.3.4 +./smtpd_check: <queue id>: reject: CONNECT from 4.4.0_dsn[1.2.3.4]: 554 5.4.0 <4.4.0_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.4.0 <4.4.0_dsn[1.2.3.4]>: Client host rejected: reject +>>> client dummy dummy +OK +>>> # +>>> # Test the helo restrictions +>>> # +>>> helo_restrictions hash:./smtpd_check_access +OK +>>> helo 4.1.1_dsn +./smtpd_check: mapping DSN status 4.1.1 into Helo command status 4.0.0 +./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.1_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.1_dsn> +554 5.0.0 <4.1.1_dsn>: Helo command rejected: reject +>>> helo 4.1.2_dsn +./smtpd_check: mapping DSN status 4.1.2 into Helo command status 4.0.0 +./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.2_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.2_dsn> +554 5.0.0 <4.1.2_dsn>: Helo command rejected: reject +>>> helo 4.1.3_dsn +./smtpd_check: mapping DSN status 4.1.3 into Helo command status 4.0.0 +./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.3_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.3_dsn> +554 5.0.0 <4.1.3_dsn>: Helo command rejected: reject +>>> helo 4.1.4_dsn +./smtpd_check: mapping DSN status 4.1.4 into Helo command status 4.0.0 +./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.4_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.4_dsn> +554 5.0.0 <4.1.4_dsn>: Helo command rejected: reject +>>> helo 4.1.5_dsn +./smtpd_check: mapping DSN status 4.1.5 into Helo command status 4.0.0 +./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.5_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.5_dsn> +554 5.0.0 <4.1.5_dsn>: Helo command rejected: reject +>>> helo 4.1.6_dsn +./smtpd_check: mapping DSN status 4.1.6 into Helo command status 4.0.0 +./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.6_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.6_dsn> +554 5.0.0 <4.1.6_dsn>: Helo command rejected: reject +>>> helo 4.1.7_dsn +./smtpd_check: mapping DSN status 4.1.7 into Helo command status 4.0.0 +./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.7_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.7_dsn> +554 5.0.0 <4.1.7_dsn>: Helo command rejected: reject +>>> helo 4.1.8_dsn +./smtpd_check: mapping DSN status 4.1.8 into Helo command status 4.0.0 +./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.8_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.8_dsn> +554 5.0.0 <4.1.8_dsn>: Helo command rejected: reject +>>> helo 4.4.0_dsn +./smtpd_check: <queue id>: reject: HELO from dummy[dummy]: 554 5.4.0 <4.4.0_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.4.0_dsn> +554 5.4.0 <4.4.0_dsn>: Helo command rejected: reject +>>> # +>>> # Test the sender restrictions +>>> # +>>> sender_restrictions hash:./smtpd_check_access +OK +>>> mail user@4.1.1_dsn +./smtpd_check: mapping DSN status 4.1.1 into Sender address status 4.1.7 +./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.1_dsn>: Sender address rejected: reject; from=<user@4.1.1_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 <user@4.1.1_dsn>: Sender address rejected: reject +>>> mail user@4.1.2_dsn +./smtpd_check: mapping DSN status 4.1.2 into Sender address status 4.1.8 +./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.8 <user@4.1.2_dsn>: Sender address rejected: reject; from=<user@4.1.2_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.8 <user@4.1.2_dsn>: Sender address rejected: reject +>>> mail user@4.1.3_dsn +./smtpd_check: mapping DSN status 4.1.3 into Sender address status 4.1.7 +./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.3_dsn>: Sender address rejected: reject; from=<user@4.1.3_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 <user@4.1.3_dsn>: Sender address rejected: reject +>>> mail user@4.1.4_dsn +./smtpd_check: mapping DSN status 4.1.4 into Sender address status 4.1.7 +./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.4_dsn>: Sender address rejected: reject; from=<user@4.1.4_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 <user@4.1.4_dsn>: Sender address rejected: reject +>>> mail user@4.1.5_dsn +./smtpd_check: mapping DSN status 4.1.5 into Sender address status 4.1.0 +./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.0 <user@4.1.5_dsn>: Sender address rejected: reject; from=<user@4.1.5_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.0 <user@4.1.5_dsn>: Sender address rejected: reject +>>> mail user@4.1.6_dsn +./smtpd_check: mapping DSN status 4.1.6 into Sender address status 4.1.7 +./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.6_dsn>: Sender address rejected: reject; from=<user@4.1.6_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 <user@4.1.6_dsn>: Sender address rejected: reject +>>> mail user@4.1.7_dsn +./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.7 <user@4.1.7_dsn>: Sender address rejected: reject; from=<user@4.1.7_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 <user@4.1.7_dsn>: Sender address rejected: reject +>>> mail user@4.1.8_dsn +./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.1.8 <user@4.1.8_dsn>: Sender address rejected: reject; from=<user@4.1.8_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.8 <user@4.1.8_dsn>: Sender address rejected: reject +>>> mail user@4.4.0_dsn +./smtpd_check: <queue id>: reject: MAIL from dummy[dummy]: 554 5.4.0 <user@4.4.0_dsn>: Sender address rejected: reject; from=<user@4.4.0_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.4.0 <user@4.4.0_dsn>: Sender address rejected: reject +>>> # +>>> # Test the recipient restrictions +>>> # +>>> recipient_restrictions hash:./smtpd_check_access +OK +>>> rcpt user@4.1.1_dsn +./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.1 <user@4.1.1_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.1_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.1 <user@4.1.1_dsn>: Recipient address rejected: reject +>>> rcpt user@4.1.2_dsn +./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.2 <user@4.1.2_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.2_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.2 <user@4.1.2_dsn>: Recipient address rejected: reject +>>> rcpt user@4.1.3_dsn +./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.3 <user@4.1.3_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.3_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.3 <user@4.1.3_dsn>: Recipient address rejected: reject +>>> rcpt user@4.1.4_dsn +./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.4 <user@4.1.4_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.4_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.4 <user@4.1.4_dsn>: Recipient address rejected: reject +>>> rcpt user@4.1.5_dsn +./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.5 <user@4.1.5_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.5_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.5 <user@4.1.5_dsn>: Recipient address rejected: reject +>>> rcpt user@4.1.6_dsn +./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.6 <user@4.1.6_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.6_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.6 <user@4.1.6_dsn>: Recipient address rejected: reject +>>> rcpt user@4.1.7_dsn +./smtpd_check: mapping DSN status 4.1.7 into Recipient address status 4.1.3 +./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.3 <user@4.1.7_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.7_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.3 <user@4.1.7_dsn>: Recipient address rejected: reject +>>> rcpt user@4.1.8_dsn +./smtpd_check: mapping DSN status 4.1.8 into Recipient address status 4.1.2 +./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.1.2 <user@4.1.8_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.1.8_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.1.2 <user@4.1.8_dsn>: Recipient address rejected: reject +>>> rcpt user@4.4.0_dsn +./smtpd_check: <queue id>: reject: RCPT from dummy[dummy]: 554 5.4.0 <user@4.4.0_dsn>: Recipient address rejected: reject; from=<user@4.4.0_dsn> to=<user@4.4.0_dsn> proto=SMTP helo=<4.4.0_dsn> +554 5.4.0 <user@4.4.0_dsn>: Recipient address rejected: reject diff --git a/src/smtpd/smtpd_dns_filter.in b/src/smtpd/smtpd_dns_filter.in new file mode 100644 index 0000000..df1d8ef --- /dev/null +++ b/src/smtpd/smtpd_dns_filter.in @@ -0,0 +1,83 @@ +# +# Initialize +# +client localhost 127.0.0.1 +smtpd_delay_reject 0 +# +# Test reject_unknown_helo_hostname +# +smtpd_dns_reply_filter regexp:../dns/no-mx.reg +helo_restrictions reject_unknown_helo_hostname,permit +# EXPECT OK + "all MX records dropped" warning. +helo xn--1xa.porcupine.org +# EXPECT OK (nullmx has A record) +helo nullmx.porcupine.org +# EXPECT reject (nxdomain is not filtered). +helo nxdomain.porcupine.org +smtpd_dns_reply_filter regexp:../dns/no-a.reg +# EXPECT OK (host has AAAA record). +mail user@spike.porcupine.org +helo spike.porcupine.org +# EXPECT OK + "all A records dropped" warning + no delayed reject. +helo fist.porcupine.org +mail user@spike.porcupine.org +rcpt user@spike.porcupine.org +smtpd_dns_reply_filter regexp:../dns/error.reg +# EXPECT OK + "filter config error" warning + delayed reject. +helo spike.porcupine.org +mail user@spike.porcupine.org +rcpt user@spike.porcupine.org +# EXPECT OK + "filter config error" warning (nullmx has A record) + delayed reject. +helo nullmx.porcupine.org +mail user@spike.porcupine.org +rcpt user@spike.porcupine.org +# EXPECT reject (nxdomain is not filtered). +helo nxdomain.porcupine.org +# +# Test reject_unknown_sender_domain (same code as +# reject_unknown_recipient_domain). +# +smtpd_dns_reply_filter regexp:../dns/no-mx.reg +helo localhost +sender_restrictions reject_unknown_sender_domain +# EXPECT OK + "all MX records dropped" warning. +mail user@xn--1xa.porcupine.org +# EXPECT reject (nullmx is not filtered). +mail user@nullmx.porcupine.org +# EXPECT reject (nxdomain is not filtered). +mail user@nxdomain.porcupine.org +# EXPECT OK +mail user@localhost +smtpd_dns_reply_filter regexp:../dns/no-a.reg +# EXPECT OK (host has AAAA record). +mail user@spike.porcupine.org +# EXPECT OK + "all A records dropped" warning. +mail user@fist.porcupine.org +smtpd_dns_reply_filter regexp:../dns/error.reg +# EXPECT OK + "filter config error" warning + delayed reject. +mail user@xn--1xa.porcupine.org +rcpt user +# EXPECT reject (nullmx is not filtered). +mail user@nullmx.porcupine.org +# EXPECT reject (nxdomain is not filtered). +mail user@nxdomain.porcupine.org +# +# Test reject_rbl_client +# +client_restrictions reject_rbl_client,dnsbltest.porcupine.org +smtpd_dns_reply_filter regexp:../dns/no-mx.reg +flush_dnsxl_cache +# EXPECT reject + A and TXT record. +client localhost 127.0.0.2 +smtpd_dns_reply_filter regexp:../dns/no-a.reg +flush_dnsxl_cache +# EXPECT OK + "all A results dropped" warning. +client localhost 127.0.0.2 +smtpd_dns_reply_filter regexp:../dns/no-txt.reg +flush_dnsxl_cache +# EXPECT reject + A record, "all TXT results dropped" warning. +client localhost 127.0.0.2 +smtpd_dns_reply_filter regexp:../dns/error.reg +flush_dnsxl_cache +# EXPECT OK + "filter configuration error" +client localhost 127.0.0.2 diff --git a/src/smtpd/smtpd_dns_filter.ref b/src/smtpd/smtpd_dns_filter.ref new file mode 100644 index 0000000..92c9102 --- /dev/null +++ b/src/smtpd/smtpd_dns_filter.ref @@ -0,0 +1,163 @@ +>>> # +>>> # Initialize +>>> # +>>> client localhost 127.0.0.1 +OK +>>> smtpd_delay_reject 0 +OK +>>> # +>>> # Test reject_unknown_helo_hostname +>>> # +>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg +OK +>>> helo_restrictions reject_unknown_helo_hostname,permit +OK +>>> # EXPECT OK + "all MX records dropped" warning. +>>> helo xn--1xa.porcupine.org +./smtpd_check: ignoring DNS RR: xn--1xa.porcupine.org. TTL IN MX 10 spike.porcupine.org. +./smtpd_check: warning: xn--1xa.porcupine.org: address or MX lookup error: DNS reply filter drops all results +OK +>>> # EXPECT OK (nullmx has A record) +>>> helo nullmx.porcupine.org +OK +>>> # EXPECT reject (nxdomain is not filtered). +>>> helo nxdomain.porcupine.org +./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found; proto=SMTP helo=<nxdomain.porcupine.org> +450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found +>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg +OK +>>> # EXPECT OK (host has AAAA record). +>>> mail user@spike.porcupine.org +OK +>>> helo spike.porcupine.org +./smtpd_check: ignoring DNS RR: spike.porcupine.org. TTL IN A 168.100.189.2 +OK +>>> # EXPECT OK + "all A records dropped" warning + no delayed reject. +>>> helo fist.porcupine.org +./smtpd_check: ignoring DNS RR: fist.porcupine.org. TTL IN A 168.100.189.4 +./smtpd_check: warning: fist.porcupine.org: address or MX lookup error: DNS reply filter drops all results +OK +>>> mail user@spike.porcupine.org +OK +>>> rcpt user@spike.porcupine.org +OK +>>> smtpd_dns_reply_filter regexp:../dns/error.reg +OK +>>> # EXPECT OK + "filter config error" warning + delayed reject. +>>> helo spike.porcupine.org +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +OK +>>> mail user@spike.porcupine.org +OK +>>> rcpt user@spike.porcupine.org +./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 450 4.7.1 <spike.porcupine.org>: Helo command rejected: Host not found; from=<user@spike.porcupine.org> to=<user@spike.porcupine.org> proto=SMTP helo=<spike.porcupine.org> +450 4.7.1 <spike.porcupine.org>: Helo command rejected: Host not found +>>> # EXPECT OK + "filter config error" warning (nullmx has A record) + delayed reject. +>>> helo nullmx.porcupine.org +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +OK +>>> mail user@spike.porcupine.org +OK +>>> rcpt user@spike.porcupine.org +./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 450 4.7.1 <nullmx.porcupine.org>: Helo command rejected: Host not found; from=<user@spike.porcupine.org> to=<user@spike.porcupine.org> proto=SMTP helo=<nullmx.porcupine.org> +450 4.7.1 <nullmx.porcupine.org>: Helo command rejected: Host not found +>>> # EXPECT reject (nxdomain is not filtered). +>>> helo nxdomain.porcupine.org +./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found; from=<user@spike.porcupine.org> proto=SMTP helo=<nxdomain.porcupine.org> +450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found +>>> # +>>> # Test reject_unknown_sender_domain (same code as +>>> # reject_unknown_recipient_domain). +>>> # +>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg +OK +>>> helo localhost +OK +>>> sender_restrictions reject_unknown_sender_domain +OK +>>> # EXPECT OK + "all MX records dropped" warning. +>>> mail user@xn--1xa.porcupine.org +./smtpd_check: ignoring DNS RR: xn--1xa.porcupine.org. TTL IN MX 10 spike.porcupine.org. +./smtpd_check: warning: xn--1xa.porcupine.org: MX or address lookup error: DNS reply filter drops all results +OK +>>> # EXPECT reject (nullmx is not filtered). +>>> mail user@nullmx.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<user@nullmx.porcupine.org> proto=SMTP helo=<localhost> +550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX) +>>> # EXPECT reject (nxdomain is not filtered). +>>> mail user@nxdomain.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost> +450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found +>>> # EXPECT OK +>>> mail user@localhost +OK +>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg +OK +>>> # EXPECT OK (host has AAAA record). +>>> mail user@spike.porcupine.org +./smtpd_check: ignoring DNS RR: spike.porcupine.org. TTL IN A 168.100.189.2 +OK +>>> # EXPECT OK + "all A records dropped" warning. +>>> mail user@fist.porcupine.org +./smtpd_check: ignoring DNS RR: fist.porcupine.org. TTL IN A 168.100.189.4 +./smtpd_check: warning: fist.porcupine.org: MX or address lookup error: DNS reply filter drops all results +OK +>>> smtpd_dns_reply_filter regexp:../dns/error.reg +OK +>>> # EXPECT OK + "filter config error" warning + delayed reject. +>>> mail user@xn--1xa.porcupine.org +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +OK +>>> rcpt user +./smtpd_check: <queue id>: reject: RCPT from localhost[127.0.0.1]: 450 4.1.8 <user@xn--1xa.porcupine.org>: Sender address rejected: Domain not found; from=<user@xn--1xa.porcupine.org> to=<user> proto=SMTP helo=<localhost> +450 4.1.8 <user@xn--1xa.porcupine.org>: Sender address rejected: Domain not found +>>> # EXPECT reject (nullmx is not filtered). +>>> mail user@nullmx.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<user@nullmx.porcupine.org> proto=SMTP helo=<localhost> +550 5.7.27 <user@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX) +>>> # EXPECT reject (nxdomain is not filtered). +>>> mail user@nxdomain.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from localhost[127.0.0.1]: 450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost> +450 4.1.8 <user@nxdomain.porcupine.org>: Sender address rejected: Domain not found +>>> # +>>> # Test reject_rbl_client +>>> # +>>> client_restrictions reject_rbl_client,dnsbltest.porcupine.org +OK +>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg +OK +>>> flush_dnsxl_cache +OK +>>> # EXPECT reject + A and TXT record. +>>> client localhost 127.0.0.2 +./smtpd_check: <queue id>: reject: CONNECT from localhost[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost> +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test +>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg +OK +>>> flush_dnsxl_cache +OK +>>> # EXPECT OK + "all A results dropped" warning. +>>> client localhost 127.0.0.2 +./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN A 127.0.0.2 +./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: RBL lookup error: Error looking up name=2.0.0.127.dnsbltest.porcupine.org type=A: DNS reply filter drops all results +OK +>>> smtpd_dns_reply_filter regexp:../dns/no-txt.reg +OK +>>> flush_dnsxl_cache +OK +>>> # EXPECT reject + A record, "all TXT results dropped" warning. +>>> client localhost 127.0.0.2 +./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN TXT DNS blocklist test +./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: TXT lookup error: DNS reply filter drops all results +./smtpd_check: <queue id>: reject: CONNECT from localhost[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost> +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org +>>> smtpd_dns_reply_filter regexp:../dns/error.reg +OK +>>> flush_dnsxl_cache +OK +>>> # EXPECT OK + "filter configuration error" +>>> client localhost 127.0.0.2 +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: RBL lookup error: Error looking up name=2.0.0.127.dnsbltest.porcupine.org type=A: Invalid DNS reply filter syntax +OK diff --git a/src/smtpd/smtpd_dnswl.in b/src/smtpd/smtpd_dnswl.in new file mode 100644 index 0000000..6546e02 --- /dev/null +++ b/src/smtpd/smtpd_dnswl.in @@ -0,0 +1,60 @@ +# +# Initialize. +# +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +mydestination porcupine.org +relay_domains porcupine.org +helo foobar + +# +# DNSWL (by IP address) +# + +# Whitelist overrides reject. +client_restrictions permit_dnswl_client,wild.porcupine.org,reject +client spike.porcupine.org 168.100.189.2 + +# Whitelist does not fire - reject. +client_restrictions permit_dnswl_client,porcupine.org,reject +client spike.porcupine.org 168.100.189.2 + +# Whitelist does not override reject_unauth_destination. +client_restrictions permit +recipient_restrictions permit_dnswl_client,wild.porcupine.org,reject_unauth_destination +# Unauthorized destination - reject. +rcpt rname@rdomain +# Authorized destination - accept. +rcpt wietse@porcupine.org + +# +# RHSWL (by domain name) +# + +# Whitelist overrides reject. +client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject +# Non-whitelisted client name - reject. +client spike.porcupine.org 168.100.189.2 +# Whitelisted client name - accept. +client example.tld 168.100.189.2 + +# Whitelist does not override reject_unauth_destination. +client_restrictions permit +recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination +# Non-whitelisted client name. +client spike.porcupine.org 168.100.189.2 +# Unauthorized destination - reject. +rcpt rname@rdomain +# Authorized destination - accept. +rcpt wietse@porcupine.org +# Whitelisted client name. +client example.tld 168.100.189.2 +# Unauthorized destination - reject. +rcpt rname@rdomain +# Authorized destination - accept. +rcpt wietse@porcupine.org +# Numeric TLD - dunno. +rcpt wietse@12345 +rcpt wietse@12345.porcupine.org +rcpt wietse@porcupine.12345 diff --git a/src/smtpd/smtpd_dnswl.ref b/src/smtpd/smtpd_dnswl.ref new file mode 100644 index 0000000..7c23459 --- /dev/null +++ b/src/smtpd/smtpd_dnswl.ref @@ -0,0 +1,94 @@ +>>> # +>>> # Initialize. +>>> # +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.189.0/28 +OK +>>> mydestination porcupine.org +OK +>>> relay_domains porcupine.org +OK +>>> helo foobar +OK +>>> +>>> # +>>> # DNSWL (by IP address) +>>> # +>>> +>>> # Whitelist overrides reject. +>>> client_restrictions permit_dnswl_client,wild.porcupine.org,reject +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> +>>> # Whitelist does not fire - reject. +>>> client_restrictions permit_dnswl_client,porcupine.org,reject +OK +>>> client spike.porcupine.org 168.100.189.2 +./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <spike.porcupine.org[168.100.189.2]>: Client host rejected: Access denied; proto=SMTP helo=<foobar> +554 5.7.1 <spike.porcupine.org[168.100.189.2]>: Client host rejected: Access denied +>>> +>>> # Whitelist does not override reject_unauth_destination. +>>> client_restrictions permit +OK +>>> recipient_restrictions permit_dnswl_client,wild.porcupine.org,reject_unauth_destination +OK +>>> # Unauthorized destination - reject. +>>> rcpt rname@rdomain +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <rname@rdomain>: Relay access denied; to=<rname@rdomain> proto=SMTP helo=<foobar> +554 5.7.1 <rname@rdomain>: Relay access denied +>>> # Authorized destination - accept. +>>> rcpt wietse@porcupine.org +OK +>>> +>>> # +>>> # RHSWL (by domain name) +>>> # +>>> +>>> # Whitelist overrides reject. +>>> client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject +OK +>>> # Non-whitelisted client name - reject. +>>> client spike.porcupine.org 168.100.189.2 +./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <spike.porcupine.org[168.100.189.2]>: Client host rejected: Access denied; proto=SMTP helo=<foobar> +554 5.7.1 <spike.porcupine.org[168.100.189.2]>: Client host rejected: Access denied +>>> # Whitelisted client name - accept. +>>> client example.tld 168.100.189.2 +OK +>>> +>>> # Whitelist does not override reject_unauth_destination. +>>> client_restrictions permit +OK +>>> recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination +OK +>>> # Non-whitelisted client name. +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> # Unauthorized destination - reject. +>>> rcpt rname@rdomain +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <rname@rdomain>: Relay access denied; to=<rname@rdomain> proto=SMTP helo=<foobar> +554 5.7.1 <rname@rdomain>: Relay access denied +>>> # Authorized destination - accept. +>>> rcpt wietse@porcupine.org +OK +>>> # Whitelisted client name. +>>> client example.tld 168.100.189.2 +OK +>>> # Unauthorized destination - reject. +>>> rcpt rname@rdomain +./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.189.2]: 554 5.7.1 <rname@rdomain>: Relay access denied; to=<rname@rdomain> proto=SMTP helo=<foobar> +554 5.7.1 <rname@rdomain>: Relay access denied +>>> # Authorized destination - accept. +>>> rcpt wietse@porcupine.org +OK +>>> # Numeric TLD - dunno. +>>> rcpt wietse@12345 +./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.189.2]: 554 5.7.1 <wietse@12345>: Relay access denied; to=<wietse@12345> proto=SMTP helo=<foobar> +554 5.7.1 <wietse@12345>: Relay access denied +>>> rcpt wietse@12345.porcupine.org +OK +>>> rcpt wietse@porcupine.12345 +./smtpd_check: <queue id>: reject: RCPT from example.tld[168.100.189.2]: 554 5.7.1 <wietse@porcupine.12345>: Relay access denied; to=<wietse@porcupine.12345> proto=SMTP helo=<foobar> +554 5.7.1 <wietse@porcupine.12345>: Relay access denied diff --git a/src/smtpd/smtpd_dsn_fix.c b/src/smtpd/smtpd_dsn_fix.c new file mode 100644 index 0000000..d436967 --- /dev/null +++ b/src/smtpd/smtpd_dsn_fix.c @@ -0,0 +1,149 @@ +/*++ +/* NAME +/* smtpd_dsn_fix 3 +/* SUMMARY +/* fix DSN status +/* SYNOPSIS +/* #include <smtpd_dsn_fix.h> +/* +/* const char *smtpd_dsn_fix(status, reply_class) +/* const char *status; +/* const char *reply_class; +/* DESCRIPTION +/* smtpd_dsn_fix() transforms DSN status codes according to the +/* status information that is actually being reported. The +/* following transformations are implemented: +/* .IP \(bu +/* Transform a recipient address DSN into a sender address DSN +/* when reporting sender address status information, and vice +/* versa. This transformation may be needed because some Postfix +/* access control features don't know whether the address being +/* rejected is a sender or recipient. Examples are smtpd access +/* tables, rbl reply templates, and the error mailer. +/* .IP \(bu +/* Transform a sender or recipient address DSN into a non-address +/* DSN when reporting non-address status information. For +/* example, if something rejects HELO with DSN status 4.1.1 +/* (unknown recipient address), then we send the more neutral +/* 4.0.0 DSN instead. This transformation is needed when the +/* same smtpd access map entry or rbl reply template is used +/* for both address and non-address information. +/* .PP +/* A non-address DSN is not transformed +/* when reporting sender or recipient address status information, +/* as there are many legitimate instances of such usage. +/* +/* It is left up to the caller to update the initial DSN digit +/* appropriately; in Postfix this is done as late as possible, +/* because hard rejects may be changed into soft rejects for +/* all kinds of reasons. +/* +/* Arguments: +/* .IP status +/* A DSN status as per RFC 3463. +/* .IP reply_class +/* SMTPD_NAME_SENDER, SMTPD_NAME_RECIPIENT or some other +/* null-terminated string. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> + +/* Global library. */ + +/* Application-specific. */ + +#include <smtpd_dsn_fix.h> + +struct dsn_map { + const char *micro_code; /* Final digits in mailbox D.S.N. */ + const char *sender_dsn; /* Replacement sender D.S.N. */ + const char *rcpt_dsn; /* Replacement recipient D.S.N. */ +}; + +static struct dsn_map dsn_map[] = { + /* - Sender - Recipient */ + "1", SND_DSN, "4.1.1", /* 4.1.1: Bad dest mbox addr */ + "2", "4.1.8", "4.1.2", /* 4.1.2: Bad dest system addr */ + "3", "4.1.7", "4.1.3", /* 4.1.3: Bad dest mbox addr syntax */ + "4", SND_DSN, "4.1.4", /* 4.1.4: Dest mbox addr ambiguous */ + "5", "4.1.0", "4.1.5", /* 4.1.5: Dest mbox addr valid */ + "6", SND_DSN, "4.1.6", /* 4.1.6: Mailbox has moved */ + "7", "4.1.7", "4.1.3", /* 4.1.7: Bad sender mbox addr syntax */ + "8", "4.1.8", "4.1.2", /* 4.1.8: Bad sender system addr */ + 0, "4.1.0", "4.1.0", /* Default mapping */ +}; + +/* smtpd_dsn_fix - fix DSN status */ + +const char *smtpd_dsn_fix(const char *status, const char *reply_class) +{ + struct dsn_map *dp; + const char *result = status; + + /* + * Update an address-specific DSN according to what is being rejected. + */ + if (ISDIGIT(status[0]) && strncmp(status + 1, ".1.", 3) == 0) { + + /* + * Fix recipient address DSN while rejecting a sender address. Don't + * let future recipient-specific DSN codes slip past us. + */ + if (strcmp(reply_class, SMTPD_NAME_SENDER) == 0) { + for (dp = dsn_map; dp->micro_code != 0; dp++) + if (strcmp(status + 4, dp->micro_code) == 0) + break; + result = dp->sender_dsn; + } + + /* + * Fix sender address DSN while rejecting a recipient address. Don't + * let future sender-specific DSN codes slip past us. + */ + else if (strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0) { + for (dp = dsn_map; dp->micro_code != 0; dp++) + if (strcmp(status + 4, dp->micro_code) == 0) + break; + result = dp->rcpt_dsn; + } + + /* + * Fix address-specific DSN while rejecting a non-address. + */ + else { + result = "4.0.0"; + } + + /* + * Give them a clue of what is going on. + */ + if (strcmp(status + 2, result + 2) != 0) + msg_info("mapping DSN status %s into %s status %c%s", + status, reply_class, status[0], result + 1); + return (result); + } + + /* + * Don't update a non-address DSN. There are many legitimate uses for + * these while rejecting address or non-address information. + */ + else { + return (status); + } +} diff --git a/src/smtpd/smtpd_dsn_fix.h b/src/smtpd/smtpd_dsn_fix.h new file mode 100644 index 0000000..c608e34 --- /dev/null +++ b/src/smtpd/smtpd_dsn_fix.h @@ -0,0 +1,44 @@ +/*++ +/* NAME +/* smtpd_check 3h +/* SUMMARY +/* SMTP client request filtering +/* SYNOPSIS +/* #include "smtpd.h" +/* #include "smtpd_check_int.h" +/* DESCRIPTION +/* .nf + + /* + * Internal interface. + */ +#define SMTPD_NAME_CLIENT "Client host" +#define SMTPD_NAME_REV_CLIENT "Unverified Client host" +#define SMTPD_NAME_CCERT "Client certificate" +#define SMTPD_NAME_SASL_USER "SASL login name" +#define SMTPD_NAME_HELO "Helo command" +#define SMTPD_NAME_SENDER "Sender address" +#define SMTPD_NAME_RECIPIENT "Recipient address" +#define SMTPD_NAME_ETRN "Etrn command" +#define SMTPD_NAME_DATA "Data command" +#define SMTPD_NAME_EOD "End-of-data" + + /* + * Workaround for absence of "bad sender address" status code: use "bad + * sender address syntax" instead. If we were to use "4.1.0" then we would + * lose the critical distinction between sender and recipient problems. + */ +#define SND_DSN "4.1.7" + +extern const char *smtpd_dsn_fix(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtpd/smtpd_error.in b/src/smtpd/smtpd_error.in new file mode 100644 index 0000000..c894b87 --- /dev/null +++ b/src/smtpd/smtpd_error.in @@ -0,0 +1,81 @@ +# +# Initialize +# +smtpd_delay_reject 0 +# +# Test check_domain_access() +# +helo_restrictions fail:1_helo_access +# Expect: REJECT (temporary lookup failure) +helo foobar +# +# Test check_namadr_access() +# +client_restrictions fail:1_client_access +# Expect: REJECT (temporary lookup failure) +client foo.dunno.com 131.155.210.17 +# +# Test check_mail_access() +# +sender_restrictions fail:1_sender_access +# Expect: REJECT (temporary lookup failure) +mail reject@dunno.domain +# +# Test check_rcpt_access() +# +recipient_restrictions fail:1_rcpt_access +# Expect: REJECT (temporary lookup failure) +rcpt reject@dunno.domain +# Expect: OK +rcpt postmaster +# +# Test mynetworks in generic_checks(). +# +mynetworks fail:1_mynetworks +# +# Expect REJECT (temporary lookup failure) +# +recipient_restrictions permit_mynetworks +rcpt reject@dunno.domain +# +# Test mynetworks. +# +mynetworks 168.100.189.1/27 +# +# Expect REJECT (server configuration error) +# +rcpt reject@dunno.domain +# +# check_sender_access specific +# +smtpd_null_access_lookup_key <> +mail <> +# +# Test permit_tls_client_certs in generic_restrictions +# +relay_clientcerts fail:1_certs +fingerprint abcdef +recipient_restrictions permit_tls_clientcerts +rcpt reject@dunno.domain +# +# Test smtpd_check_rewrite(). +# +local_header_rewrite_clients fail:1_rewrite +# +# Expect: REJECT (temporary lookup failure) +# +rewrite +# +# Test resolve_local() +# +mydestination example.com +recipient_restrictions reject_unauth_destination +rcpt user@example.com +mydestination fail:1_mydestination +rcpt user@example.com +# +# Test virtual alias lookup. +# +mydestination example.com +virtual_alias_maps fail:1_virtual +rcpt user@example.com diff --git a/src/smtpd/smtpd_error.ref b/src/smtpd/smtpd_error.ref new file mode 100644 index 0000000..7df38e1 --- /dev/null +++ b/src/smtpd/smtpd_error.ref @@ -0,0 +1,135 @@ +>>> # +>>> # Initialize +>>> # +>>> smtpd_delay_reject 0 +OK +>>> # +>>> # Test check_domain_access() +>>> # +>>> helo_restrictions fail:1_helo_access +OK +>>> # Expect: REJECT (temporary lookup failure) +>>> helo foobar +./smtpd_check: warning: fail:1_helo_access lookup error for "foobar" +./smtpd_check: <queue id>: reject: HELO from localhost[127.0.0.1]: 451 4.3.5 <foobar>: Helo command rejected: Server configuration error; proto=SMTP helo=<foobar> +451 4.3.5 <foobar>: Helo command rejected: Server configuration error +>>> # +>>> # Test check_namadr_access() +>>> # +>>> client_restrictions fail:1_client_access +OK +>>> # Expect: REJECT (temporary lookup failure) +>>> client foo.dunno.com 131.155.210.17 +./smtpd_check: warning: fail:1_client_access lookup error for "foo.dunno.com" +./smtpd_check: <queue id>: reject: CONNECT from foo.dunno.com[131.155.210.17]: 451 4.3.5 <foo.dunno.com[131.155.210.17]>: Client host rejected: Server configuration error; proto=SMTP helo=<foobar> +451 4.3.5 <foo.dunno.com[131.155.210.17]>: Client host rejected: Server configuration error +>>> # +>>> # Test check_mail_access() +>>> # +>>> sender_restrictions fail:1_sender_access +OK +>>> # Expect: REJECT (temporary lookup failure) +>>> mail reject@dunno.domain +./smtpd_check: warning: fail:1_sender_access lookup error for "reject@dunno.domain" +./smtpd_check: <queue id>: reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 <reject@dunno.domain>: Sender address rejected: Server configuration error; from=<reject@dunno.domain> proto=SMTP helo=<foobar> +451 4.3.5 <reject@dunno.domain>: Sender address rejected: Server configuration error +>>> # +>>> # Test check_rcpt_access() +>>> # +>>> recipient_restrictions fail:1_rcpt_access +OK +>>> # Expect: REJECT (temporary lookup failure) +>>> rcpt reject@dunno.domain +./smtpd_check: warning: fail:1_rcpt_access lookup error for "reject@dunno.domain" +./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.5 <reject@dunno.domain>: Recipient address rejected: Server configuration error; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar> +451 4.3.5 <reject@dunno.domain>: Recipient address rejected: Server configuration error +>>> # Expect: OK +>>> rcpt postmaster +OK +>>> # +>>> # Test mynetworks in generic_checks(). +>>> # +>>> mynetworks fail:1_mynetworks +OK +>>> # +>>> # Expect REJECT (temporary lookup failure) +>>> # +>>> recipient_restrictions permit_mynetworks +OK +>>> rcpt reject@dunno.domain +./smtpd_check: warning: mynetworks: fail:1_mynetworks: table lookup problem +./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar> +451 4.3.0 <reject@dunno.domain>: Temporary lookup failure +>>> # +>>> # Test mynetworks. +>>> # +>>> mynetworks 168.100.189.1/27 +OK +>>> # +>>> # Expect REJECT (server configuration error) +>>> # +>>> rcpt reject@dunno.domain +./smtpd_check: warning: mynetworks: non-null host address bits in "168.100.189.1/27", perhaps you should use "168.100.189.0/27" instead +./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar> +451 4.3.0 <reject@dunno.domain>: Temporary lookup failure +>>> # +>>> # check_sender_access specific +>>> # +>>> smtpd_null_access_lookup_key <> +OK +>>> mail <> +./smtpd_check: warning: fail:1_sender_access lookup error for "<>" +./smtpd_check: <queue id>: reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 <>: Sender address rejected: Server configuration error; from=<> proto=SMTP helo=<foobar> +451 4.3.5 <>: Sender address rejected: Server configuration error +>>> # +>>> # Test permit_tls_client_certs in generic_restrictions +>>> # +>>> relay_clientcerts fail:1_certs +OK +>>> fingerprint abcdef +OK +>>> recipient_restrictions permit_tls_clientcerts +OK +>>> rcpt reject@dunno.domain +./smtpd_check: warning: fail:1_certs lookup error for "abcdef" +./smtpd_check: warning: relay_clientcerts: lookup error for fingerprint 'abcdef', pkey fingerprint abcdef +./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<> to=<reject@dunno.domain> proto=SMTP helo=<foobar> +451 4.3.0 <reject@dunno.domain>: Temporary lookup failure +>>> # +>>> # Test smtpd_check_rewrite(). +>>> # +>>> local_header_rewrite_clients fail:1_rewrite +OK +>>> # +>>> # Expect: REJECT (temporary lookup failure) +>>> # +>>> rewrite +./smtpd_check: warning: fail:1_rewrite lookup error for "131.155.210.17" +./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 Temporary lookup error; from=<> proto=SMTP helo=<foobar> +451 4.3.0 Temporary lookup error +>>> # +>>> # Test resolve_local() +>>> # +>>> mydestination example.com +OK +>>> recipient_restrictions reject_unauth_destination +OK +>>> rcpt user@example.com +OK +>>> mydestination fail:1_mydestination +OK +>>> rcpt user@example.com +./smtpd_check: warning: mydestination: fail:1_mydestination: table lookup problem +./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <user@example.com>: Temporary lookup failure; from=<> to=<user@example.com> proto=SMTP helo=<foobar> +451 4.3.0 <user@example.com>: Temporary lookup failure +>>> # +>>> # Test virtual alias lookup. +>>> # +>>> mydestination example.com +OK +>>> virtual_alias_maps fail:1_virtual +OK +>>> rcpt user@example.com +./smtpd_check: warning: fail:1_virtual lookup error for "user@example.com" +./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <user@example.com>: Temporary lookup failure; from=<> to=<user@example.com> proto=SMTP helo=<foobar> +451 4.3.0 <user@example.com>: Temporary lookup failure diff --git a/src/smtpd/smtpd_exp.in b/src/smtpd/smtpd_exp.in new file mode 100644 index 0000000..584acf3 --- /dev/null +++ b/src/smtpd/smtpd_exp.in @@ -0,0 +1,62 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.189.0/28 +relay_domains porcupine.org +maps_rbl_domains dnsbltest.porcupine.org +rbl_reply_maps hash:smtpd_check_access +helo foobar +# +# RBL +# +mail sname@sdomain +recipient_restrictions reject_maps_rbl +client spike.porcupine.org 168.100.189.2 +rcpt rname@rdomain +client foo 127.0.0.2 +rcpt rname@rdomain +# +recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org +client spike.porcupine.org 168.100.189.2 +rcpt rname@rdomain +client foo 127.0.0.2 +rcpt rname@rdomain +recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2 +client foo 127.0.0.2 +rcpt rname@rdomain +client foo 127.0.0.1 +rcpt rname@rdomain +# +# RHSBL sender domain name +# +recipient_restrictions reject_rhsbl_sender,rhsbl.porcupine.org +client spike.porcupine.org 168.100.189.2 +mail sname@example.tld +rcpt rname@rdomain +mail sname@sdomain +rcpt rname@rdomain +# +# RHSBL client domain name +# +recipient_restrictions reject_rhsbl_client,rhsbl.porcupine.org +client example.tld 1.2.3.4 +mail sname@sdomain +rcpt rname@rdomain +# +# RHSBL recipient domain name +# +recipient_restrictions reject_rhsbl_recipient,rhsbl.porcupine.org +client spike.porcupine.org 168.100.189.2 +mail sname@sdomain +rcpt rname@rdomain +rcpt rname@example.tld +# +# RHSBL helo domain name +# +recipient_restrictions reject_rhsbl_helo,rhsbl.porcupine.org +helo example.tld +mail sname@sdomain +rcpt rname@rdomain diff --git a/src/smtpd/smtpd_exp.ref b/src/smtpd/smtpd_exp.ref new file mode 100644 index 0000000..6d7e9fc --- /dev/null +++ b/src/smtpd/smtpd_exp.ref @@ -0,0 +1,111 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.189.0/28 +OK +>>> relay_domains porcupine.org +OK +>>> maps_rbl_domains dnsbltest.porcupine.org +OK +>>> rbl_reply_maps hash:smtpd_check_access +OK +>>> helo foobar +OK +>>> # +>>> # RBL +>>> # +>>> mail sname@sdomain +OK +>>> recipient_restrictions reject_maps_rbl +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> rcpt rname@rdomain +./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead +OK +>>> client foo 127.0.0.2 +OK +>>> rcpt rname@rdomain +./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar> +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test +>>> # +>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> rcpt rname@rdomain +OK +>>> client foo 127.0.0.2 +OK +>>> rcpt rname@rdomain +./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar> +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test +>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2 +OK +>>> client foo 127.0.0.2 +OK +>>> rcpt rname@rdomain +./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar> +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test +>>> client foo 127.0.0.1 +OK +>>> rcpt rname@rdomain +OK +>>> # +>>> # RHSBL sender domain name +>>> # +>>> recipient_restrictions reject_rhsbl_sender,rhsbl.porcupine.org +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> mail sname@example.tld +OK +>>> rcpt rname@rdomain +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@example.tld sender_name=sname sender_domain=example.tld recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=sname@example.tld rbl_class=Sender address; from=<sname@example.tld> to=<rname@rdomain> proto=SMTP helo=<foobar> +554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@example.tld sender_name=sname sender_domain=example.tld recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=sname@example.tld rbl_class=Sender address +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +OK +>>> # +>>> # RHSBL client domain name +>>> # +>>> recipient_restrictions reject_rhsbl_client,rhsbl.porcupine.org +OK +>>> client example.tld 1.2.3.4 +OK +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +./smtpd_check: <queue id>: reject: RCPT from example.tld[1.2.3.4]: 554 5.7.1 client=example.tld[1.2.3.4] client_address=1.2.3.4 client_name=example.tld helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Client host; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar> +554 5.7.1 client=example.tld[1.2.3.4] client_address=1.2.3.4 client_name=example.tld helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Client host +>>> # +>>> # RHSBL recipient domain name +>>> # +>>> recipient_restrictions reject_rhsbl_recipient,rhsbl.porcupine.org +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +OK +>>> rcpt rname@example.tld +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@example.tld recipient_name=rname recipient_domain=example.tld rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=rname@example.tld rbl_class=Recipient address; from=<sname@sdomain> to=<rname@example.tld> proto=SMTP helo=<foobar> +554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@example.tld recipient_name=rname recipient_domain=example.tld rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=rname@example.tld rbl_class=Recipient address +>>> # +>>> # RHSBL helo domain name +>>> # +>>> recipient_restrictions reject_rhsbl_helo,rhsbl.porcupine.org +OK +>>> helo example.tld +OK +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=example.tld sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Helo command; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<example.tld> +554 5.7.1 client=spike.porcupine.org[168.100.189.2] client_address=168.100.189.2 client_name=spike.porcupine.org helo_name=example.tld sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Helo command diff --git a/src/smtpd/smtpd_expand.c b/src/smtpd/smtpd_expand.c new file mode 100644 index 0000000..8362bd7 --- /dev/null +++ b/src/smtpd/smtpd_expand.c @@ -0,0 +1,247 @@ +/*++ +/* NAME +/* smtpd_expand 3 +/* SUMMARY +/* SMTP server macro expansion +/* SYNOPSIS +/* #include <smtpd.h> +/* #include <smtpd_expand.h> +/* +/* void smtpd_expand_init() +/* +/* int smtpd_expand(state, result, template, flags) +/* SMTPD_STATE *state; +/* VSTRING *result; +/* const char *template; +/* int flags; +/* LOW_LEVEL INTERFACE +/* VSTRING *smtpd_expand_filter; +/* +/* const char *smtpd_expand_lookup(name, unused_mode, context) +/* const char *name; +/* int unused_mode; +/* void *context; +/* const char *template; +/* DESCRIPTION +/* This module expands session-related macros. +/* +/* smtpd_expand_init() performs one-time initialization. +/* +/* smtpd_expand() expands macros in the template, using session +/* attributes in the state argument, and writes the result to +/* the result argument. The flags and result value are as with +/* mac_expand(). +/* +/* smtpd_expand_filter and smtpd_expand_lookup() provide access +/* to lower-level interfaces that are used by smtpd_expand(). +/* smtpd_expand_lookup() returns null when a string is not +/* found (or when it is a null pointer). +/* DIAGNOSTICS +/* Panic: interface violations. Fatal errors: out of memory. +/* internal protocol errors. smtpd_expand() returns the binary +/* OR of MAC_PARSE_ERROR (syntax error) and MAC_PARSE_UNDEF +/* (undefined macro name). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <mac_expand.h> +#include <stringops.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_proto.h> + +/* Application-specific. */ + +#include <smtpd.h> +#include <smtpd_expand.h> + + /* + * Pre-parsed expansion filter. + */ +VSTRING *smtpd_expand_filter; + + /* + * SLMs. + */ +#define STR vstring_str + +/* smtpd_expand_init - initialize once during process lifetime */ + +void smtpd_expand_init(void) +{ + + /* + * Expand the expansion filter :-) + */ + smtpd_expand_filter = vstring_alloc(10); + unescape(smtpd_expand_filter, var_smtpd_exp_filter); +} + +/* smtpd_expand_unknown - report unknown macro name */ + +static void smtpd_expand_unknown(const char *name) +{ + msg_warn("unknown macro name \"%s\" in expansion request", name); +} + +/* smtpd_expand_addr - return address or substring thereof */ + +static const char *smtpd_expand_addr(VSTRING *buf, const char *addr, + const char *name, int prefix_len) +{ + const char *p; + const char *suffix; + + /* + * Return NULL only for unknown names in expansion requests. + */ + if (addr == 0) + return (""); + + suffix = name + prefix_len; + + /* + * MAIL_ATTR_SENDER or MAIL_ATTR_RECIP. + */ + if (*suffix == 0) { + if (*addr) + return (addr); + else + return ("<>"); + } + + /* + * "sender_name" or "recipient_name". + */ +#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0) + + else if (STREQ(suffix, MAIL_ATTR_S_NAME)) { + if (*addr) { + if ((p = strrchr(addr, '@')) != 0) { + vstring_strncpy(buf, addr, p - addr); + return (STR(buf)); + } else { + return (addr); + } + } else + return ("<>"); + } + + /* + * "sender_domain" or "recipient_domain". + */ + else if (STREQ(suffix, MAIL_ATTR_S_DOMAIN)) { + if (*addr) { + if ((p = strrchr(addr, '@')) != 0) { + return (p + 1); + } else { + return (""); + } + } else + return (""); + } + + /* + * Unknown. Return NULL to indicate an "unknown name" error. + */ + else { + smtpd_expand_unknown(name); + return (0); + } +} + +/* smtpd_expand_lookup - generic SMTP attribute $name expansion */ + +const char *smtpd_expand_lookup(const char *name, int unused_mode, + void *context) +{ + SMTPD_STATE *state = (SMTPD_STATE *) context; + time_t now; + struct tm *lt; + + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(10); + + if (msg_verbose > 1) + msg_info("smtpd_expand_lookup: ${%s}", name); + +#define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0) +#define CONST_LEN(x) (sizeof(x) - 1) + + /* + * Don't query main.cf parameters, as the result of expansion could + * reveal system-internal information in server replies. + * + * XXX: This said, multiple servers may be behind a single client-visible + * name or IP address, and each may generate its own logs. Therefore, it + * may be useful to expose the replying MTA id (myhostname) in the + * contact footer, to identify the right logs. So while we don't expose + * the raw configuration dictionary, we do expose "$myhostname" as + * expanded in var_myhostname. + * + * Return NULL only for non-existent names. + */ + if (STREQ(name, MAIL_ATTR_SERVER_NAME)) { + return (var_myhostname); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT)) { + return (state->namaddr); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_PORT)) { + return (state->port); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_ADDR)) { + return (state->addr); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_NAME)) { + return (state->name); + } else if (STREQ(name, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME)) { + return (state->reverse_name); + } else if (STREQ(name, MAIL_ATTR_ACT_HELO_NAME)) { + return (state->helo_name ? state->helo_name : ""); + } else if (STREQN(name, MAIL_ATTR_SENDER, CONST_LEN(MAIL_ATTR_SENDER))) { + return (smtpd_expand_addr(state->expand_buf, state->sender, + name, CONST_LEN(MAIL_ATTR_SENDER))); + } else if (STREQN(name, MAIL_ATTR_RECIP, CONST_LEN(MAIL_ATTR_RECIP))) { + return (smtpd_expand_addr(state->expand_buf, state->recipient, + name, CONST_LEN(MAIL_ATTR_RECIP))); + } if (STREQ(name, MAIL_ATTR_LOCALTIME)) { + if (time(&now) == (time_t) -1) + msg_fatal("time lookup failed: %m"); + lt = localtime(&now); + VSTRING_RESET(state->expand_buf); + do { + VSTRING_SPACE(state->expand_buf, 100); + } while (strftime(STR(state->expand_buf), + vstring_avail(state->expand_buf), + "%b %d %H:%M:%S", lt) == 0); + return (STR(state->expand_buf)); + } else { + smtpd_expand_unknown(name); + return (0); + } +} + +/* smtpd_expand - expand session attributes in string */ + +int smtpd_expand(SMTPD_STATE *state, VSTRING *result, + const char *template, int flags) +{ + return (mac_expand(result, template, flags, STR(smtpd_expand_filter), + smtpd_expand_lookup, (void *) state)); +} diff --git a/src/smtpd/smtpd_expand.h b/src/smtpd/smtpd_expand.h new file mode 100644 index 0000000..71d705c --- /dev/null +++ b/src/smtpd/smtpd_expand.h @@ -0,0 +1,35 @@ +/*++ +/* 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. + */ +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 +/*--*/ diff --git a/src/smtpd/smtpd_haproxy.c b/src/smtpd/smtpd_haproxy.c new file mode 100644 index 0000000..3bcbffc --- /dev/null +++ b/src/smtpd/smtpd_haproxy.c @@ -0,0 +1,168 @@ +/*++ +/* 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 +/* 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> + +/* 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) +{ + const char *myname = "smtpd_peer_from_haproxy"; + MAI_HOSTADDR_STR smtp_client_addr; + MAI_SERVPORT_STR smtp_client_port; + MAI_HOSTADDR_STR smtp_server_addr; + MAI_SERVPORT_STR smtp_server_port; + const char *proxy_err; + int io_err; + VSTRING *escape_buf; + + /* + * While reading HAProxy handshake information, don't buffer input beyond + * the end-of-line. That would break the TLS wrappermode handshake. + */ + vstream_control(state->client, + VSTREAM_CTL_BUFSIZE, 1, + VSTREAM_CTL_END); + + /* + * Note: the haproxy_srvr_parse() routine performs address protocol + * checks, address and port syntax checks, and converts IPv4-in-IPv6 + * address string syntax (::ffff:1.2.3.4) to IPv4 syntax where permitted + * by the main.cf:inet_protocols setting, but logs no warnings. + */ +#define ENABLE_DEADLINE 1 + + smtp_stream_setup(state->client, var_smtpd_uproxy_tmout, ENABLE_DEADLINE); + switch (io_err = vstream_setjmp(state->client)) { + default: + msg_panic("%s: unhandled I/O error %d", myname, io_err); + case SMTP_ERR_EOF: + msg_warn("haproxy read: unexpected EOF"); + return (-1); + case SMTP_ERR_TIME: + msg_warn("haproxy read: timeout error"); + return (-1); + case 0: + if (smtp_get(state->buffer, state->client, HAPROXY_MAX_LEN, + SMTP_GET_FLAG_NONE) != '\n') { + msg_warn("haproxy read: line > %d characters", HAPROXY_MAX_LEN); + return (-1); + } + if ((proxy_err = haproxy_srvr_parse(STR(state->buffer), + &smtp_client_addr, &smtp_client_port, + &smtp_server_addr, &smtp_server_port)) != 0) { + escape_buf = vstring_alloc(HAPROXY_MAX_LEN + 2); + escape(escape_buf, STR(state->buffer), LEN(state->buffer)); + msg_warn("haproxy read: %s: %s", proxy_err, STR(escape_buf)); + vstring_free(escape_buf); + return (-1); + } + 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); + + /* + * Enable normal buffering. + */ + vstream_control(state->client, + VSTREAM_CTL_BUFSIZE, VSTREAM_BUFSIZE, + VSTREAM_CTL_END); + return (0); + } +} diff --git a/src/smtpd/smtpd_milter.c b/src/smtpd/smtpd_milter.c new file mode 100644 index 0000000..5deba67 --- /dev/null +++ b/src/smtpd/smtpd_milter.c @@ -0,0 +1,229 @@ +/*++ +/* NAME +/* smtpd_milter 3 +/* SUMMARY +/* SMTP server milter glue +/* SYNOPSIS +/* #include <smtpd.h> +/* #include <smtpd_milter.h> +/* +/* const char *smtpd_milter_eval(name, context) +/* const char *name; +/* void *context; +/* DESCRIPTION +/* smtpd_milter_eval() is a milter(3) call-back routine to +/* expand Sendmail macros before they are sent to filters. +/* DIAGNOSTICS +/* Panic: interface violations. Fatal errors: out of memory. +/* internal protocol errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <split_at.h> +#include <stringops.h> + +/* Global library. */ + +#include <mail_params.h> +#include <quote_821_local.h> + +/* Milter library. */ + +#include <milter.h> + +/* Application-specific. */ + +#include <smtpd.h> +#include <smtpd_sasl_glue.h> +#include <smtpd_resolve.h> +#include <smtpd_milter.h> + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* smtpd_milter_eval - evaluate milter macro */ + +const char *smtpd_milter_eval(const char *name, void *ptr) +{ + SMTPD_STATE *state = (SMTPD_STATE *) ptr; + const RESOLVE_REPLY *reply; + char *cp; + + /* + * On-the-fly initialization. + */ + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(10); + + /* + * System macros. + */ + if (strcmp(name, S8_MAC_DAEMON_NAME) == 0) + return (var_milt_daemon_name); + if (strcmp(name, S8_MAC_V) == 0) + return (var_milt_v); + + /* + * Connect macros. + */ + if (strcmp(name, S8_MAC__) == 0) { + vstring_sprintf(state->expand_buf, "%s [%s]", + state->reverse_name, state->addr); + if (strcasecmp_utf8(state->name, state->reverse_name) != 0) + vstring_strcat(state->expand_buf, " (may be forged)"); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_J) == 0) + return (var_myhostname); + if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0) + return (state->rfc_addr); + if (strcmp(name, S8_MAC_CLIENT_PORT) == 0) + return (strcmp(state->port, CLIENT_PORT_UNKNOWN) ? state->port : "0"); + if (strcmp(name, S8_MAC_CLIENT_CONN) == 0) { + vstring_sprintf(state->expand_buf, "%d", state->conn_count); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_CLIENT_NAME) == 0) + return (state->name); + if (strcmp(name, S8_MAC_CLIENT_PTR) == 0) + return (state->reverse_name); + if (strcmp(name, S8_MAC_CLIENT_RES) == 0) + return (state->name_status == SMTPD_PEER_CODE_OK ? "OK" : + state->name_status == SMTPD_PEER_CODE_FORGED ? "FORGED" : + state->name_status == SMTPD_PEER_CODE_TEMP ? "TEMP" : "FAIL"); + + if (strcmp(name, S8_MAC_DAEMON_ADDR) == 0) + return (state->dest_addr); + if (strcmp(name, S8_MAC_DAEMON_PORT) == 0) + return (state->dest_port); + + /* + * HELO macros. + */ +#ifdef USE_TLS +#define IF_ENCRYPTED(x) (state->tls_context ? (x) : 0) +#define IF_TRUSTED(x) (TLS_CERT_IS_TRUSTED(state->tls_context) ? (x) : 0) + + if (strcmp(name, S8_MAC_TLS_VERSION) == 0) + return (IF_ENCRYPTED(state->tls_context->protocol)); + if (strcmp(name, S8_MAC_CIPHER) == 0) + return (IF_ENCRYPTED(state->tls_context->cipher_name)); + if (strcmp(name, S8_MAC_CIPHER_BITS) == 0) { + if (state->tls_context == 0) + return (0); + vstring_sprintf(state->expand_buf, "%d", + IF_ENCRYPTED(state->tls_context->cipher_usebits)); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_CERT_SUBJECT) == 0) + return (IF_TRUSTED(state->tls_context->peer_CN)); + if (strcmp(name, S8_MAC_CERT_ISSUER) == 0) + return (IF_TRUSTED(state->tls_context->issuer_CN)); +#endif + + /* + * MAIL FROM macros. + */ +#define IF_SASL_ENABLED(s) ((s) ? (s) : 0) + + if (strcmp(name, S8_MAC_I) == 0) + return (state->queue_id); +#ifdef USE_SASL_AUTH + if (strcmp(name, S8_MAC_AUTH_TYPE) == 0) + return (IF_SASL_ENABLED(state->sasl_method)); + if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0) + return (IF_SASL_ENABLED(state->sasl_username)); + if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0) + return (IF_SASL_ENABLED(state->sasl_sender)); +#endif + if (strcmp(name, S8_MAC_MAIL_ADDR) == 0) { + if (state->sender == 0) + return (0); + if (state->sender[0] == 0) + return (""); + reply = smtpd_resolve_addr(state->recipient, state->sender); + /* Sendmail 8.13 does not externalize the null string. */ + if (STR(reply->recipient)[0]) + quote_821_local(state->expand_buf, STR(reply->recipient)); + else + vstring_strcpy(state->expand_buf, STR(reply->recipient)); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_MAIL_HOST) == 0) { + if (state->sender == 0) + return (0); + reply = smtpd_resolve_addr(state->recipient, state->sender); + return (STR(reply->nexthop)); + } + if (strcmp(name, S8_MAC_MAIL_MAILER) == 0) { + if (state->sender == 0) + return (0); + reply = smtpd_resolve_addr(state->recipient, state->sender); + return (STR(reply->transport)); + } + + /* + * RCPT TO macros. + */ + if (strcmp(name, S8_MAC_RCPT_ADDR) == 0) { + if (state->recipient == 0) + return (0); + if (state->recipient[0] == 0) + return (""); + if (state->milter_reject_text) { + /* 554 5.7.1 <user@example.com>: Relay access denied */ + vstring_strcpy(state->expand_buf, state->milter_reject_text + 4); + cp = split_at(STR(state->expand_buf), ' '); + return (cp ? split_at(cp, ' ') : cp); + } + reply = smtpd_resolve_addr(state->sender, state->recipient); + /* Sendmail 8.13 does not externalize the null string. */ + if (STR(reply->recipient)[0]) + quote_821_local(state->expand_buf, STR(reply->recipient)); + else + vstring_strcpy(state->expand_buf, STR(reply->recipient)); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_RCPT_HOST) == 0) { + if (state->recipient == 0) + return (0); + if (state->milter_reject_text) { + /* 554 5.7.1 <user@example.com>: Relay access denied */ + vstring_strcpy(state->expand_buf, state->milter_reject_text + 4); + (void) split_at(STR(state->expand_buf), ' '); + return (STR(state->expand_buf)); + } + reply = smtpd_resolve_addr(state->sender, state->recipient); + return (STR(reply->nexthop)); + } + if (strcmp(name, S8_MAC_RCPT_MAILER) == 0) { + if (state->recipient == 0) + return (0); + if (state->milter_reject_text) + return (S8_RCPT_MAILER_ERROR); + reply = smtpd_resolve_addr(state->sender, state->recipient); + return (STR(reply->transport)); + } + return (0); +} diff --git a/src/smtpd/smtpd_milter.h b/src/smtpd/smtpd_milter.h new file mode 100644 index 0000000..4006bde --- /dev/null +++ b/src/smtpd/smtpd_milter.h @@ -0,0 +1,27 @@ +/*++ +/* NAME +/* smtpd_milter 3h +/* SUMMARY +/* SMTP server milter glue +/* SYNOPSIS +/* #include <smtpd.h> +/* #include <smtpd_milter.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern const char *smtpd_milter_eval(const char *, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + diff --git a/src/smtpd/smtpd_nullmx.in b/src/smtpd/smtpd_nullmx.in new file mode 100644 index 0000000..9db8276 --- /dev/null +++ b/src/smtpd/smtpd_nullmx.in @@ -0,0 +1,58 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +#smtpd_delay_reject 0 +#mynetworks 127.0.0.0/8,168.100.189.0/28 +#relay_domains porcupine.org +#maps_rbl_domains dnsbltest.porcupine.org +#rbl_reply_maps hash:smtpd_check_access +#helo foobar +# +# reject_unknown_helo_hostname +# +smtpd_delay_reject 0 +helo_restrictions reject_unknown_helo_hostname +client spike.porcupine.org 168.100.189.2 +mail sname@sdomain +rcpt rname@rdomain +helo nxdomain.porcupine.org +helo nullmx.porcupine.org +helo spike.porcupine.org +# +# reject_unknown_sender_domain +# +smtpd_delay_reject 0 +sender_restrictions reject_unknown_sender_domain +client spike.porcupine.org 168.100.189.2 +helo spike.porcupine.org +rcpt rname@rdomain +mail sname@nxdomain.porcupine.org +mail sname@nullmx.porcupine.org +mail sname@spike.porcupine.org +# +# reject_unknown_recipient_domain +# +smtpd_delay_reject 0 +sender_restrictions permit +recipient_restrictions reject_unknown_recipient_domain +relay_restrictions reject_unauth_destination +client spike.porcupine.org 168.100.189.2 +helo spike.porcupine.org +mail sname@sdomain +relay_domains nxdomain.porcupine.org +rcpt rname@nxdomain.porcupine.org +relay_domains nullmx.porcupine.org +rcpt rname@nullmx.porcupine.org +relay_domains spike.porcupine.org +rcpt rname@spike.porcupine.org +# +# check_mx_access +# +smtpd_delay_reject 0 +sender_restrictions check_sender_mx_access,hash:smtpd_check_access +client spike.porcupine.org 168.100.189.2 +mail sname@nxdomain.porcupine.org +mail sname@nullmx.porcupine.org +mail sname@spike.porcupine.org diff --git a/src/smtpd/smtpd_nullmx.ref b/src/smtpd/smtpd_nullmx.ref new file mode 100644 index 0000000..4cc258e --- /dev/null +++ b/src/smtpd/smtpd_nullmx.ref @@ -0,0 +1,100 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> #smtpd_delay_reject 0 +>>> #mynetworks 127.0.0.0/8,168.100.189.0/28 +>>> #relay_domains porcupine.org +>>> #maps_rbl_domains dnsbltest.porcupine.org +>>> #rbl_reply_maps hash:smtpd_check_access +>>> #helo foobar +>>> # +>>> # reject_unknown_helo_hostname +>>> # +>>> smtpd_delay_reject 0 +OK +>>> helo_restrictions reject_unknown_helo_hostname +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +OK +>>> helo nxdomain.porcupine.org +./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found; from=<sname@sdomain> proto=SMTP helo=<nxdomain.porcupine.org> +450 4.7.1 <nxdomain.porcupine.org>: Helo command rejected: Host not found +>>> helo nullmx.porcupine.org +OK +>>> helo spike.porcupine.org +OK +>>> # +>>> # reject_unknown_sender_domain +>>> # +>>> smtpd_delay_reject 0 +OK +>>> sender_restrictions reject_unknown_sender_domain +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> helo spike.porcupine.org +OK +>>> rcpt rname@rdomain +OK +>>> mail sname@nxdomain.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 450 4.1.8 <sname@nxdomain.porcupine.org>: Sender address rejected: Domain not found; from=<sname@nxdomain.porcupine.org> proto=SMTP helo=<spike.porcupine.org> +450 4.1.8 <sname@nxdomain.porcupine.org>: Sender address rejected: Domain not found +>>> mail sname@nullmx.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 550 5.7.27 <sname@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<sname@nullmx.porcupine.org> proto=SMTP helo=<spike.porcupine.org> +550 5.7.27 <sname@nullmx.porcupine.org>: Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX) +>>> mail sname@spike.porcupine.org +OK +>>> # +>>> # reject_unknown_recipient_domain +>>> # +>>> smtpd_delay_reject 0 +OK +>>> sender_restrictions permit +OK +>>> recipient_restrictions reject_unknown_recipient_domain +OK +>>> relay_restrictions reject_unauth_destination +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> helo spike.porcupine.org +OK +>>> mail sname@sdomain +OK +>>> relay_domains nxdomain.porcupine.org +OK +>>> rcpt rname@nxdomain.porcupine.org +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 450 4.1.2 <rname@nxdomain.porcupine.org>: Recipient address rejected: Domain not found; from=<sname@sdomain> to=<rname@nxdomain.porcupine.org> proto=SMTP helo=<spike.porcupine.org> +450 4.1.2 <rname@nxdomain.porcupine.org>: Recipient address rejected: Domain not found +>>> relay_domains nullmx.porcupine.org +OK +>>> rcpt rname@nullmx.porcupine.org +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 556 5.1.10 <rname@nullmx.porcupine.org>: Recipient address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from=<sname@sdomain> to=<rname@nullmx.porcupine.org> proto=SMTP helo=<spike.porcupine.org> +556 5.1.10 <rname@nullmx.porcupine.org>: Recipient address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX) +>>> relay_domains spike.porcupine.org +OK +>>> rcpt rname@spike.porcupine.org +OK +>>> # +>>> # check_mx_access +>>> # +>>> smtpd_delay_reject 0 +OK +>>> sender_restrictions check_sender_mx_access,hash:smtpd_check_access +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> mail sname@nxdomain.porcupine.org +./smtpd_check: warning: Unable to look up MX host nxdomain.porcupine.org for Sender address sname@nxdomain.porcupine.org: hostname nor servname provided, or not known +OK +>>> mail sname@nullmx.porcupine.org +OK +>>> mail sname@spike.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <sname@spike.porcupine.org>: Sender address rejected: ns or mx server spike.porcupine.org; from=<sname@spike.porcupine.org> proto=SMTP helo=<spike.porcupine.org> +554 5.7.1 <sname@spike.porcupine.org>: Sender address rejected: ns or mx server spike.porcupine.org diff --git a/src/smtpd/smtpd_peer.c b/src/smtpd/smtpd_peer.c new file mode 100644 index 0000000..073310a --- /dev/null +++ b/src/smtpd/smtpd_peer.c @@ -0,0 +1,652 @@ +/*++ +/* 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; +/* 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(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> /* strerror() */ +#include <errno.h> +#include <netdb.h> +#include <string.h> +#include <htable.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <myaddrinfo.h> +#include <sock_addr.h> +#include <inet_proto.h> +#include <split_at.h> + +/* Global library. */ + +#include <mail_proto.h> +#include <valid_mailhost_addr.h> +#include <mail_params.h> +#include <haproxy_srvr.h> + +/* Application-specific. */ + +#include "smtpd.h" + +static INET_PROTO_INFO *proto_info; + + /* + * XXX If we make local port information available via logging, then we must + * also support these attributes with the XFORWARD command. + * + * XXX If support were to be added for Milter applications in down-stream MTAs, + * then consistency demands that we propagate a lot of Sendmail macro + * information via the XFORWARD command. Otherwise we could end up with a + * very confusing situation. + */ + +/* smtpd_peer_sockaddr_to_hostaddr - client address/port to printable form */ + +static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state) +{ + const char *myname = "smtpd_peer_sockaddr_to_hostaddr"; + struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr); + SOCKADDR_SIZE sa_length = state->sockaddr_len; + + /* + * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, + * while Postfix IPv6 (or IPv4) support is turned off, don't (skip to the + * final else clause, pretend the origin is localhost[127.0.0.1], and + * become an open relay). + */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) { + MAI_HOSTADDR_STR client_addr; + MAI_SERVPORT_STR client_port; + MAI_HOSTADDR_STR server_addr; + MAI_SERVPORT_STR server_port; + int aierr; + char *colonp; + + /* + * Sanity check: we can't use sockets that we're not configured for. + */ + if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0) + msg_fatal("cannot handle socket type %s with \"%s = %s\"", +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : + "other", VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * Sorry, but there are some things that we just cannot do while + * connected to the network. + */ + if (geteuid() != var_owner_uid || getuid() != var_owner_uid) { + msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu", + (unsigned long) getuid(), (unsigned long) geteuid()); + msg_fatal("the Postfix SMTP server must run with $%s privileges", + VAR_MAIL_OWNER); + } + + /* + * Convert the client address to printable form. + */ + if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr, + &client_port, 0)) != 0) + msg_fatal("%s: cannot convert client address/port to string: %s", + myname, MAI_STRERROR(aierr)); + state->port = mystrdup(client_port.buf); + + /* + * XXX Require that the infrastructure strips off the IPv6 datalink + * suffix to avoid false alarms with strict address syntax checks. + */ +#ifdef HAS_IPV6 + if (strchr(client_addr.buf, '%') != 0) + msg_panic("%s: address %s has datalink suffix", + myname, client_addr.buf); +#endif + + /* + * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on, + * but only if IPv4 support is enabled (why would anyone want to turn + * it off)? With IPv4 support enabled we have no need for the IPv6 + * form in logging, hostname verification and access checks. + */ +#ifdef HAS_IPV6 + if (sa->sa_family == AF_INET6) { + if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0 + && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa)) + && (colonp = strrchr(client_addr.buf, ':')) != 0) { + struct addrinfo *res0; + + if (msg_verbose > 1) + msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"", + myname, client_addr.buf, colonp + 1); + + state->addr = mystrdup(colonp + 1); + state->rfc_addr = mystrdup(colonp + 1); + state->addr_family = AF_INET; + aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0); + if (aierr) + msg_fatal("%s: cannot convert %s from string to binary: %s", + myname, state->addr, MAI_STRERROR(aierr)); + sa_length = res0->ai_addrlen; + if (sa_length > sizeof(state->sockaddr)) + sa_length = sizeof(state->sockaddr); + memcpy((void *) sa, res0->ai_addr, sa_length); + freeaddrinfo(res0); /* 200412 */ + } + + /* + * Following RFC 2821 section 4.1.3, an IPv6 address literal gets + * a prefix of 'IPv6:'. We do this consistently for all IPv6 + * addresses that that appear in headers or envelopes. The fact + * that valid_mailhost_addr() enforces the form helps of course. + * We use the form without IPV6: prefix when doing access + * control, or when accessing the connection cache. + */ + else { + state->addr = mystrdup(client_addr.buf); + state->rfc_addr = + concatenate(IPV6_COL, client_addr.buf, (char *) 0); + state->addr_family = sa->sa_family; + } + } + + /* + * An IPv4 address is in dotted quad decimal form. + */ + else +#endif + { + state->addr = mystrdup(client_addr.buf); + state->rfc_addr = mystrdup(client_addr.buf); + state->addr_family = sa->sa_family; + } + + /* + * Convert the server address/port to printable form. + */ + if ((aierr = sockaddr_to_hostaddr((struct sockaddr *) + &state->dest_sockaddr, + state->dest_sockaddr_len, + &server_addr, + &server_port, 0)) != 0) + msg_fatal("%s: cannot convert server address/port to string: %s", + myname, MAI_STRERROR(aierr)); + /* TODO: convert IPv4-in-IPv6 to IPv4 form. */ + state->dest_addr = mystrdup(server_addr.buf); + state->dest_port = mystrdup(server_port.buf); + + return (0); + } + + /* + * It's not Internet. + */ + else { + return (-1); + } +} + +/* smtpd_peer_sockaddr_to_hostname - client hostname lookup */ + +static void smtpd_peer_sockaddr_to_hostname(SMTPD_STATE *state) +{ + struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr); + SOCKADDR_SIZE sa_length = state->sockaddr_len; + MAI_HOSTNAME_STR client_name; + int aierr; + + /* + * Look up and sanity check the client hostname. + * + * It is unsafe to allow numeric hostnames, especially because there exists + * pressure to turn off the name->addr double check. In that case an + * attacker could trivally bypass access restrictions. + * + * sockaddr_to_hostname() already rejects malformed or numeric names. + */ +#define TEMP_AI_ERROR(e) \ + ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) + +#define REJECT_PEER_NAME(state, code) { \ + myfree(state->name); \ + state->name = mystrdup(CLIENT_NAME_UNKNOWN); \ + state->name_status = code; \ + } + + if (var_smtpd_peername_lookup == 0) { + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); + state->name_status = SMTPD_PEER_CODE_PERM; + state->reverse_name_status = SMTPD_PEER_CODE_PERM; + } else if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name, + (MAI_SERVNAME_STR *) 0, 0)) != 0) { + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); + state->name_status = (TEMP_AI_ERROR(aierr) ? + SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM); + state->reverse_name_status = (TEMP_AI_ERROR(aierr) ? + SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM); + } else { + struct addrinfo *res0; + struct addrinfo *res; + + state->name = mystrdup(client_name.buf); + state->reverse_name = mystrdup(client_name.buf); + state->name_status = SMTPD_PEER_CODE_OK; + state->reverse_name_status = SMTPD_PEER_CODE_OK; + + /* + * Reject the hostname if it does not list the peer address. Without + * further validation or qualification, such information must not be + * allowed to enter the audit trail, as people would draw false + * conclusions. + */ + aierr = hostname_to_sockaddr_pf(state->name, state->addr_family, + (char *) 0, 0, &res0); + if (aierr) { + msg_warn("hostname %s does not resolve to address %s: %s", + state->name, state->addr, MAI_STRERROR(aierr)); + REJECT_PEER_NAME(state, (TEMP_AI_ERROR(aierr) ? + SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_FORGED)); + } else { + for (res = res0; /* void */ ; res = res->ai_next) { + if (res == 0) { + msg_warn("hostname %s does not resolve to address %s", + state->name, state->addr); + REJECT_PEER_NAME(state, SMTPD_PEER_CODE_FORGED); + break; + } + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, state->name); + continue; + } + if (sock_addr_cmp_addr(res->ai_addr, sa) == 0) + break; /* keep peer name */ + } + freeaddrinfo(res0); + } + } +} + +/* smtpd_peer_hostaddr_to_sockaddr - convert numeric string to binary */ + +static void smtpd_peer_hostaddr_to_sockaddr(SMTPD_STATE *state) +{ + const char *myname = "smtpd_peer_hostaddr_to_sockaddr"; + struct addrinfo *res; + int aierr; + + if ((aierr = hostaddr_to_sockaddr(state->addr, state->port, + SOCK_STREAM, &res)) != 0) + msg_fatal("%s: cannot convert client address/port to string: %s", + myname, MAI_STRERROR(aierr)); + if (res->ai_addrlen > sizeof(state->sockaddr)) + msg_panic("%s: address length > struct sockaddr_storage", myname); + memcpy((void *) &(state->sockaddr), res->ai_addr, res->ai_addrlen); + state->sockaddr_len = res->ai_addrlen; + freeaddrinfo(res); +} + +/* smtpd_peer_not_inet - non-socket or non-Internet endpoint */ + +static void smtpd_peer_not_inet(SMTPD_STATE *state) +{ + + /* + * If it's not Internet, assume the client is local, and avoid using the + * naming service because that can hang when the machine is disconnected. + */ + state->name = mystrdup("localhost"); + state->reverse_name = mystrdup("localhost"); +#ifdef AF_INET6 + if (proto_info->sa_family_list[0] == PF_INET6) { + state->addr = mystrdup("::1"); /* XXX bogus. */ + state->rfc_addr = mystrdup(IPV6_COL "::1"); /* XXX bogus. */ + } else +#endif + { + state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */ + state->rfc_addr = mystrdup("127.0.0.1");/* XXX bogus. */ + } + state->addr_family = AF_UNSPEC; + state->name_status = SMTPD_PEER_CODE_OK; + state->reverse_name_status = SMTPD_PEER_CODE_OK; + state->port = mystrdup("0"); /* XXX bogus. */ + + state->dest_addr = mystrdup(state->addr); /* XXX bogus. */ + state->dest_port = mystrdup(state->port); /* XXX bogus. */ +} + +/* smtpd_peer_no_client - peer went away, or peer info unavailable */ + +static void smtpd_peer_no_client(SMTPD_STATE *state) +{ + smtpd_peer_reset(state); + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); + state->addr = mystrdup(CLIENT_ADDR_UNKNOWN); + state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN); + state->addr_family = AF_UNSPEC; + state->name_status = SMTPD_PEER_CODE_PERM; + state->reverse_name_status = SMTPD_PEER_CODE_PERM; + state->port = mystrdup(CLIENT_PORT_UNKNOWN); + + state->dest_addr = mystrdup(SERVER_ADDR_UNKNOWN); + state->dest_port = mystrdup(SERVER_PORT_UNKNOWN); +} + +/* smtpd_peer_from_pass_attr - initialize from attribute hash */ + +static void smtpd_peer_from_pass_attr(SMTPD_STATE *state) +{ + HTABLE *attr = (HTABLE *) vstream_context(state->client); + const char *cp; + + /* + * Extract the client endpoint information from the attribute hash. + */ + if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_ADDR)) == 0) + msg_fatal("missing client address from proxy"); + if (strrchr(cp, ':') != 0) { + if (valid_ipv6_hostaddr(cp, DO_GRIPE) == 0) + msg_fatal("bad IPv6 client address syntax from proxy: %s", cp); + state->addr = mystrdup(cp); + state->rfc_addr = concatenate(IPV6_COL, cp, (char *) 0); + state->addr_family = AF_INET6; + } else { + if (valid_ipv4_hostaddr(cp, DO_GRIPE) == 0) + msg_fatal("bad IPv4 client address syntax from proxy: %s", cp); + state->addr = mystrdup(cp); + state->rfc_addr = mystrdup(cp); + state->addr_family = AF_INET; + } + if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_PORT)) == 0) + msg_fatal("missing client port from proxy"); + if (valid_hostport(cp, DO_GRIPE) == 0) + msg_fatal("bad TCP client port number syntax from proxy: %s", cp); + state->port = mystrdup(cp); + + /* + * The Dovecot authentication server needs the server IP address. + */ + if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_ADDR)) == 0) + msg_fatal("missing server address from proxy"); + if (valid_hostaddr(cp, DO_GRIPE) == 0) + msg_fatal("bad IPv6 server address syntax from proxy: %s", cp); + state->dest_addr = mystrdup(cp); + + if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_PORT)) == 0) + msg_fatal("missing server port from proxy"); + if (valid_hostport(cp, DO_GRIPE) == 0) + msg_fatal("bad TCP server port number syntax from proxy: %s", cp); + state->dest_port = mystrdup(cp); + + /* + * Convert the client address from string to binary form. + */ + smtpd_peer_hostaddr_to_sockaddr(state); +} + +/* smtpd_peer_from_default - try to initialize peer information from socket */ + +static 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_no_client(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..b2e765b --- /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, "\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..020c830 --- /dev/null +++ b/src/smtpd/smtpd_sasl_glue.c @@ -0,0 +1,375 @@ +/*++ +/* NAME +/* smtpd_sasl_glue 3 +/* SUMMARY +/* Postfix SMTP server, SASL support interface +/* SYNOPSIS +/* #include "smtpd_sasl_glue.h" +/* +/* void smtpd_sasl_state_init(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_sasl_initialize() +/* +/* void smtpd_sasl_activate(state, sasl_opts_name, sasl_opts_val) +/* SMTPD_STATE *state; +/* const char *sasl_opts_name; +/* const char *sasl_opts_val; +/* +/* char *smtpd_sasl_authenticate(state, sasl_method, init_response) +/* SMTPD_STATE *state; +/* const char *sasl_method; +/* const char *init_response; +/* +/* void smtpd_sasl_logout(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_sasl_login(state, sasl_username, sasl_method) +/* SMTPD_STATE *state; +/* const char *sasl_username; +/* const char *sasl_method; +/* +/* void smtpd_sasl_deactivate(state) +/* SMTPD_STATE *state; +/* +/* int smtpd_sasl_is_active(state) +/* SMTPD_STATE *state; +/* +/* int smtpd_sasl_set_inactive(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* This module encapsulates most of the detail specific to SASL +/* authentication. +/* +/* smtpd_sasl_state_init() performs minimal server state +/* initialization to support external authentication (e.g., +/* XCLIENT) without having to enable SASL in main.cf. This +/* should always be called at process startup. +/* +/* smtpd_sasl_initialize() initializes the SASL library. This +/* routine should be called once at process start-up. It may +/* need access to the file system for run-time loading of +/* plug-in modules. There is no corresponding cleanup routine. +/* +/* smtpd_sasl_activate() performs per-connection initialization. +/* This routine should be called once at the start of every +/* connection. The sasl_opts_name and sasl_opts_val parameters +/* are the postfix configuration parameters setting the security +/* policy of the SASL authentication. +/* +/* smtpd_sasl_authenticate() implements the authentication +/* dialog. The result is zero in case of success, -1 in case +/* of failure. smtpd_sasl_authenticate() updates the following +/* state structure members: +/* .IP sasl_method +/* The authentication method that was successfully applied. +/* This member is a null pointer in the absence of successful +/* authentication. +/* .IP sasl_username +/* The username that was successfully authenticated. +/* This member is a null pointer in the absence of successful +/* authentication. +/* .PP +/* smtpd_sasl_login() records the result of successful external +/* authentication, i.e. without invoking smtpd_sasl_authenticate(), +/* but produces an otherwise equivalent result. +/* +/* smtpd_sasl_logout() cleans up after smtpd_sasl_authenticate(). +/* This routine exists for the sake of symmetry. +/* +/* smtpd_sasl_deactivate() performs per-connection cleanup. +/* This routine should be called at the end of every connection. +/* +/* smtpd_sasl_is_active() is a predicate that returns true +/* if the SMTP server session state is between smtpd_sasl_activate() +/* and smtpd_sasl_deactivate(). +/* +/* smtpd_sasl_set_inactive() initializes the SMTP session +/* state before the first smtpd_sasl_activate() call. +/* +/* Arguments: +/* .IP state +/* SMTP session context. +/* .IP sasl_opts_name +/* Security options parameter name. +/* .IP sasl_opts_val +/* Security options parameter value. +/* .IP sasl_method +/* A SASL mechanism name +/* .IP init_reply +/* An optional initial client response. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> + +/* Global library. */ + +#include <mail_params.h> + +/* XSASL library. */ + +#include <xsasl.h> + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_sasl_glue.h" +#include "smtpd_chat.h" + +#ifdef USE_SASL_AUTH + +/* + * Silly little macros. + */ +#define STR(s) vstring_str(s) + + /* + * SASL server implementation handle. + */ +static XSASL_SERVER_IMPL *smtpd_sasl_impl; + +/* smtpd_sasl_initialize - per-process initialization */ + +void smtpd_sasl_initialize(void) +{ + + /* + * Sanity check. + */ + if (smtpd_sasl_impl) + msg_panic("smtpd_sasl_initialize: repeated call"); + + /* + * Initialize the SASL library. + */ + if ((smtpd_sasl_impl = xsasl_server_init(var_smtpd_sasl_type, + var_smtpd_sasl_path)) == 0) + msg_fatal("SASL per-process initialization failed"); + +} + +/* smtpd_sasl_activate - per-connection initialization */ + +void smtpd_sasl_activate(SMTPD_STATE *state, const char *sasl_opts_name, + const char *sasl_opts_val) +{ + const char *mechanism_list; + XSASL_SERVER_CREATE_ARGS create_args; + int tls_flag; + + /* + * Sanity check. + */ + if (smtpd_sasl_is_active(state)) + msg_panic("smtpd_sasl_activate: already active"); + + /* + * Initialize SASL-specific state variables. Use long-lived storage for + * base 64 conversion results, rather than local variables, to avoid + * memory leaks when a read or write routine returns abnormally after + * timeout or I/O error. + */ + state->sasl_reply = vstring_alloc(20); + state->sasl_mechanism_list = 0; + + /* + * Set up a new server context for this connection. + */ +#ifdef USE_TLS + tls_flag = state->tls_context != 0; +#else + tls_flag = 0; +#endif +#define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "") +#define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0) + + if ((state->sasl_server = + XSASL_SERVER_CREATE(smtpd_sasl_impl, &create_args, + stream = state->client, + addr_family = state->addr_family, + server_addr = ADDR_OR_EMPTY(state->dest_addr, + SERVER_ADDR_UNKNOWN), + server_port = ADDR_OR_EMPTY(state->dest_port, + SERVER_PORT_UNKNOWN), + client_addr = ADDR_OR_EMPTY(state->addr, + CLIENT_ADDR_UNKNOWN), + client_port = ADDR_OR_EMPTY(state->port, + CLIENT_PORT_UNKNOWN), + service = var_smtpd_sasl_service, + user_realm = REALM_OR_NULL(var_smtpd_sasl_realm), + security_options = sasl_opts_val, + tls_flag = tls_flag)) == 0) + msg_fatal("SASL per-connection initialization failed"); + + /* + * Get the list of authentication mechanisms. + */ + if ((mechanism_list = + xsasl_server_get_mechanism_list(state->sasl_server)) == 0) + msg_fatal("no SASL authentication mechanisms"); + state->sasl_mechanism_list = mystrdup(mechanism_list); +} + +/* smtpd_sasl_state_init - initialize state to allow extern authentication. */ + +void smtpd_sasl_state_init(SMTPD_STATE *state) +{ + /* Initialization to support external authentication (e.g., XCLIENT). */ + state->sasl_username = 0; + state->sasl_method = 0; + state->sasl_sender = 0; +} + +/* smtpd_sasl_deactivate - per-connection cleanup */ + +void smtpd_sasl_deactivate(SMTPD_STATE *state) +{ + if (state->sasl_reply) { + vstring_free(state->sasl_reply); + state->sasl_reply = 0; + } + if (state->sasl_mechanism_list) { + myfree(state->sasl_mechanism_list); + state->sasl_mechanism_list = 0; + } + if (state->sasl_username) { + myfree(state->sasl_username); + state->sasl_username = 0; + } + if (state->sasl_method) { + myfree(state->sasl_method); + state->sasl_method = 0; + } + if (state->sasl_sender) { + myfree(state->sasl_sender); + state->sasl_sender = 0; + } + if (state->sasl_server) { + xsasl_server_free(state->sasl_server); + state->sasl_server = 0; + } +} + +/* smtpd_sasl_authenticate - per-session authentication */ + +int smtpd_sasl_authenticate(SMTPD_STATE *state, + const char *sasl_method, + const char *init_response) +{ + int status; + const char *sasl_username; + + /* + * SASL authentication protocol start-up. Process any initial client + * response that was sent along in the AUTH command. + */ + for (status = xsasl_server_first(state->sasl_server, sasl_method, + init_response, state->sasl_reply); + status == XSASL_AUTH_MORE; + status = xsasl_server_next(state->sasl_server, STR(state->buffer), + state->sasl_reply)) { + + /* + * Send a server challenge. + */ + smtpd_chat_reply(state, "334 %s", STR(state->sasl_reply)); + + /* + * Receive the client response. "*" means that the client gives up. + */ + if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) { + smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded"); + return (-1); + } + if (strcmp(STR(state->buffer), "*") == 0) { + msg_warn("%s: SASL %s authentication aborted", + state->namaddr, sasl_method); + smtpd_chat_reply(state, "501 5.7.0 Authentication aborted"); + return (-1); + } + } + if (status != XSASL_AUTH_DONE) { + msg_warn("%s: SASL %s authentication failed: %s", + state->namaddr, sasl_method, + STR(state->sasl_reply)); + /* RFC 4954 Section 6. */ + if (status == XSASL_AUTH_TEMP) + smtpd_chat_reply(state, "454 4.7.0 Temporary authentication failure: %s", + STR(state->sasl_reply)); + else + smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s", + STR(state->sasl_reply)); + return (-1); + } + /* RFC 4954 Section 6. */ + smtpd_chat_reply(state, "235 2.7.0 Authentication successful"); + if ((sasl_username = xsasl_server_get_username(state->sasl_server)) == 0) + msg_panic("cannot look up the authenticated SASL username"); + state->sasl_username = mystrdup(sasl_username); + printable(state->sasl_username, '?'); + state->sasl_method = mystrdup(sasl_method); + printable(state->sasl_method, '?'); + + return (0); +} + +/* smtpd_sasl_logout - clean up after smtpd_sasl_authenticate */ + +void smtpd_sasl_logout(SMTPD_STATE *state) +{ + if (state->sasl_username) { + myfree(state->sasl_username); + state->sasl_username = 0; + } + if (state->sasl_method) { + myfree(state->sasl_method); + state->sasl_method = 0; + } +} + +/* smtpd_sasl_login - set login information */ + +void smtpd_sasl_login(SMTPD_STATE *state, const char *sasl_username, + const char *sasl_method) +{ + if (state->sasl_username) + myfree(state->sasl_username); + state->sasl_username = mystrdup(sasl_username); + if (state->sasl_method) + myfree(state->sasl_method); + state->sasl_method = mystrdup(sasl_method); +} + +#endif diff --git a/src/smtpd/smtpd_sasl_glue.h b/src/smtpd/smtpd_sasl_glue.h new file mode 100644 index 0000000..d81eec1 --- /dev/null +++ b/src/smtpd/smtpd_sasl_glue.h @@ -0,0 +1,41 @@ +/*++ +/* NAME +/* smtpd_sasl_glue 3h +/* SUMMARY +/* Postfix SMTP server, SASL support interface +/* SYNOPSIS +/* #include "smtpd_sasl_glue.h" +/* DESCRIPTION +/* .nf + + /* + * SASL protocol interface + */ +extern void smtpd_sasl_state_init(SMTPD_STATE *); +extern void smtpd_sasl_initialize(void); +extern void smtpd_sasl_activate(SMTPD_STATE *, const char *, const char *); +extern void smtpd_sasl_deactivate(SMTPD_STATE *); +extern int smtpd_sasl_authenticate(SMTPD_STATE *, const char *, const char *); +extern void smtpd_sasl_login(SMTPD_STATE *, const char *, const char *); +extern void smtpd_sasl_logout(SMTPD_STATE *); +extern int permit_sasl_auth(SMTPD_STATE *, int, int); + +#define smtpd_sasl_is_active(s) ((s)->sasl_server != 0) +#define smtpd_sasl_set_inactive(s) ((void) ((s)->sasl_server = 0)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtpd/smtpd_sasl_proto.c b/src/smtpd/smtpd_sasl_proto.c new file mode 100644 index 0000000..476752d --- /dev/null +++ b/src/smtpd/smtpd_sasl_proto.c @@ -0,0 +1,274 @@ +/*++ +/* NAME +/* smtpd_sasl_proto 3 +/* SUMMARY +/* Postfix SMTP protocol support for SASL authentication +/* SYNOPSIS +/* #include "smtpd.h" +/* #include "smtpd_sasl_proto.h" +/* +/* int smtpd_sasl_auth_cmd(state, argc, argv) +/* SMTPD_STATE *state; +/* int argc; +/* SMTPD_TOKEN *argv; +/* +/* void smtpd_sasl_auth_extern(state, username, method) +/* SMTPD_STATE *state; +/* const char *username; +/* const char *method; +/* +/* void smtpd_sasl_auth_reset(state) +/* SMTPD_STATE *state; +/* +/* char *smtpd_sasl_mail_opt(state, sender) +/* SMTPD_STATE *state; +/* const char *sender; +/* +/* void smtpd_sasl_mail_reset(state) +/* SMTPD_STATE *state; +/* +/* static int permit_sasl_auth(state, authenticated, unauthenticated) +/* SMTPD_STATE *state; +/* int authenticated; +/* int unauthenticated; +/* DESCRIPTION +/* This module contains random chunks of code that implement +/* the SMTP protocol interface for SASL negotiation. The goal +/* is to reduce clutter of the main SMTP server source code. +/* +/* smtpd_sasl_auth_cmd() implements the AUTH command and updates +/* the following state structure members: +/* .IP sasl_method +/* The authentication method that was successfully applied. +/* This member is a null pointer in the absence of successful +/* authentication. +/* .IP sasl_username +/* The username that was successfully authenticated. +/* This member is a null pointer in the absence of successful +/* authentication. +/* .PP +/* smtpd_sasl_auth_reset() cleans up after the AUTH command. +/* This is required before smtpd_sasl_auth_cmd() can be used again. +/* This may be called even if SASL authentication is turned off +/* in main.cf. +/* +/* smtpd_sasl_auth_extern() records authentication information +/* that is received from an external source. +/* This may be called even if SASL authentication is turned off +/* in main.cf. +/* +/* smtpd_sasl_mail_opt() implements the SASL-specific AUTH=sender +/* option to the MAIL FROM command. The result is an error response +/* in case of problems. +/* +/* smtpd_sasl_mail_reset() performs cleanup for the SASL-specific +/* AUTH=sender option to the MAIL FROM command. +/* +/* permit_sasl_auth() permits access from an authenticated client. +/* This test fails for clients that use anonymous authentication. +/* +/* Arguments: +/* .IP state +/* SMTP session context. +/* .IP argc +/* Number of command line tokens. +/* .IP argv +/* The command line parsed into tokens. +/* .IP sender +/* Sender address from the AUTH=sender option in the MAIL FROM +/* command. +/* .IP authenticated +/* Result for authenticated client. +/* .IP unauthenticated +/* Result for unauthenticated client. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_proto.h> +#include <mail_error.h> +#include <ehlo_mask.h> + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_token.h" +#include "smtpd_chat.h" +#include "smtpd_sasl_proto.h" +#include "smtpd_sasl_glue.h" + +#ifdef USE_SASL_AUTH + +/* smtpd_sasl_auth_cmd - process AUTH command */ + +int smtpd_sasl_auth_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + char *auth_mechanism; + char *initial_response; + const char *err; + + if (var_helo_required && state->helo_name == 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "503 5.5.1 Error: send HELO/EHLO first"); + return (-1); + } + if (SMTPD_STAND_ALONE(state) || !smtpd_sasl_is_active(state) + || (state->ehlo_discard_mask & EHLO_MASK_AUTH)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: authentication not enabled"); + return (-1); + } + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress"); + return (-1); + } + if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0) { + if (err[0] == '5') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", err); + return (-1); + } + /* Sendmail compatibility: map 4xx into 454. */ + else if (err[0] == '4') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "454 4.3.0 Try again later"); + return (-1); + } + } +#ifdef USE_TLS + if (var_smtpd_tls_auth_only && !state->tls_context) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + /* RFC 4954, Section 4. */ + smtpd_chat_reply(state, "504 5.5.4 Encryption required for requested authentication mechanism"); + return (-1); + } +#endif + if (state->sasl_username) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: already authenticated"); + return (-1); + } + if (argc < 2 || argc > 3) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: AUTH mechanism"); + return (-1); + } + /* Don't reuse the SASL handle after authentication failure. */ +#ifndef XSASL_TYPE_CYRUS +#define XSASL_TYPE_CYRUS "cyrus" +#endif + if (state->flags & SMTPD_FLAG_AUTH_USED) { + smtpd_sasl_deactivate(state); +#ifdef USE_TLS + if (state->tls_context != 0) + smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS, + var_smtpd_sasl_tls_opts); + else +#endif + smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS, + var_smtpd_sasl_opts); + } else if (strcmp(var_smtpd_sasl_type, XSASL_TYPE_CYRUS) == 0) { + state->flags |= SMTPD_FLAG_AUTH_USED; + } + + /* + * All authentication failures shall be logged. The 5xx reply code from + * the SASL authentication routine triggers tar-pit delays, which help to + * slow down password guessing attacks. + */ + auth_mechanism = argv[1].strval; + initial_response = (argc == 3 ? argv[2].strval : 0); + return (smtpd_sasl_authenticate(state, auth_mechanism, initial_response)); +} + +/* smtpd_sasl_mail_opt - SASL-specific MAIL FROM option */ + +char *smtpd_sasl_mail_opt(SMTPD_STATE *state, const char *addr) +{ + + /* + * Do not store raw RFC2554 protocol data. + */ +#if 0 + if (state->sasl_username == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + return ("503 5.5.4 Error: send AUTH command first"); + } +#endif + if (state->sasl_sender != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + return ("503 5.5.4 Error: multiple AUTH= options"); + } + if (strcmp(addr, "<>") != 0) { + state->sasl_sender = mystrdup(addr); + printable(state->sasl_sender, '?'); + } + return (0); +} + +/* smtpd_sasl_mail_reset - SASL-specific MAIL FROM cleanup */ + +void smtpd_sasl_mail_reset(SMTPD_STATE *state) +{ + if (state->sasl_sender) { + myfree(state->sasl_sender); + state->sasl_sender = 0; + } +} + +/* permit_sasl_auth - OK for authenticated connection */ + +int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot) +{ + if (state->sasl_method && strcasecmp(state->sasl_method, "anonymous")) + return (ifyes); + return (ifnot); +} + +#endif diff --git a/src/smtpd/smtpd_sasl_proto.h b/src/smtpd/smtpd_sasl_proto.h new file mode 100644 index 0000000..d52bd4b --- /dev/null +++ b/src/smtpd/smtpd_sasl_proto.h @@ -0,0 +1,37 @@ +/*++ +/* NAME +/* smtpd_sasl_proto 3h +/* SUMMARY +/* Postfix SMTP protocol support for SASL authentication +/* SYNOPSIS +/* #include "smtpd_sasl_proto.h" +/* DESCRIPTION +/* .nf + + /* + * SMTP protocol interface. + */ +extern int smtpd_sasl_auth_cmd(SMTPD_STATE *, int, SMTPD_TOKEN *); +extern void smtpd_sasl_auth_reset(SMTPD_STATE *); +extern char *smtpd_sasl_mail_opt(SMTPD_STATE *, const char *); +extern void smtpd_sasl_mail_reset(SMTPD_STATE *); + +#define smtpd_sasl_auth_extern smtpd_sasl_login +#define smtpd_sasl_auth_reset smtpd_sasl_logout + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtpd/smtpd_server.in b/src/smtpd/smtpd_server.in new file mode 100644 index 0000000..325e9da --- /dev/null +++ b/src/smtpd/smtpd_server.in @@ -0,0 +1,56 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +relay_domains porcupine.org +client spike.porcupine.org 168.100.189.2 +# +# Check MX access +# +helo_restrictions check_helo_mx_access,inline:{168.100.189.2=reject} +helo www.porcupine.org +helo example.tld +helo foo@postfix.org +sender_restrictions check_sender_mx_access,inline:{168.100.189.2=reject} +mail foo@www.porcupine.org +mail example.tld +mail foo@postfix.org +recipient_restrictions check_recipient_mx_access,inline:{168.100.189.2=reject} +rcpt foo@www.porcupine.org +rcpt foo@example.tld +rcpt foo@postfix.org +# +# Check NS access +# +helo_restrictions check_helo_ns_access,inline:{168.100.1.2=reject} +helo www.porcupine.org +helo example.tld +helo foo@postfix.org +sender_restrictions check_sender_ns_access,inline:{168.100.1.2=reject} +mail foo@www.porcupine.org +mail example.tld +mail foo@postfix.org +recipient_restrictions check_recipient_ns_access,inline:{168.100.1.2=reject} +rcpt foo@www.porcupine.org +rcpt foo@example.tld +rcpt foo@postfix.org +# +# Check A access +# +helo_restrictions check_helo_a_access,inline:{168.100.189.2=reject} +helo spike.porcupine.org +helo www.porcupine.org +client_restrictions check_client_a_access,inline:{168.100.189.2=reject} +client spike.porcupine.org 1.2.3.4 +client www.porcupine.org 1.2.3.4 +reverse_client_restrictions check_reverse_client_a_access,inline:{168.100.189.2=reject} +client spike.porcupine.org 1.2.3.4 +client www.porcupine.org 1.2.3.4 +sender_restrictions check_sender_a_access,inline:{168.100.189.2=reject} +mail foo@spike.porcupine.org +mail foo@www.porcupine.org +recipient_restrictions check_recipient_a_access,inline:{168.100.189.2=reject} +rcpt foo@spike.porcupine.org +rcpt foo@www.porcupine.org diff --git a/src/smtpd/smtpd_server.ref b/src/smtpd/smtpd_server.ref new file mode 100644 index 0000000..563befe --- /dev/null +++ b/src/smtpd/smtpd_server.ref @@ -0,0 +1,118 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> relay_domains porcupine.org +OK +>>> client spike.porcupine.org 168.100.189.2 +OK +>>> # +>>> # Check MX access +>>> # +>>> helo_restrictions check_helo_mx_access,inline:{168.100.189.2=reject} +OK +>>> helo www.porcupine.org +./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied; proto=SMTP helo=<www.porcupine.org> +554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied +>>> helo example.tld +./smtpd_check: warning: Unable to look up MX host example.tld for Helo command example.tld: hostname nor servname provided, or not known +OK +>>> helo foo@postfix.org +OK +>>> sender_restrictions check_sender_mx_access,inline:{168.100.189.2=reject} +OK +>>> mail foo@www.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied; from=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org> +554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied +>>> mail example.tld +./smtpd_check: warning: Unable to look up MX host example.tld for Sender address example.tld: hostname nor servname provided, or not known +OK +>>> mail foo@postfix.org +OK +>>> recipient_restrictions check_recipient_mx_access,inline:{168.100.189.2=reject} +OK +>>> rcpt foo@www.porcupine.org +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org> +554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied +>>> rcpt foo@example.tld +./smtpd_check: warning: Unable to look up MX host example.tld for Recipient address foo@example.tld: hostname nor servname provided, or not known +OK +>>> rcpt foo@postfix.org +OK +>>> # +>>> # Check NS access +>>> # +>>> helo_restrictions check_helo_ns_access,inline:{168.100.1.2=reject} +OK +>>> helo www.porcupine.org +./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org> +554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied +>>> helo example.tld +./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found +OK +>>> helo foo@postfix.org +./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@postfix.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<foo@postfix.org> +554 5.7.1 <foo@postfix.org>: Helo command rejected: Access denied +>>> sender_restrictions check_sender_ns_access,inline:{168.100.1.2=reject} +OK +>>> mail foo@www.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied; from=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org> +554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied +>>> mail example.tld +./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found +OK +>>> mail foo@postfix.org +./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@postfix.org>: Sender address rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<foo@postfix.org> +554 5.7.1 <foo@postfix.org>: Sender address rejected: Access denied +>>> recipient_restrictions check_recipient_ns_access,inline:{168.100.1.2=reject} +OK +>>> rcpt foo@www.porcupine.org +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org> +554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied +>>> rcpt foo@example.tld +./smtpd_check: warning: Unable to look up NS host for foo@example.tld: Host not found +OK +>>> rcpt foo@postfix.org +./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@postfix.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@postfix.org> proto=SMTP helo=<foo@postfix.org> +554 5.7.1 <foo@postfix.org>: Recipient address rejected: Access denied +>>> # +>>> # Check A access +>>> # +>>> helo_restrictions check_helo_a_access,inline:{168.100.189.2=reject} +OK +>>> helo spike.porcupine.org +./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <spike.porcupine.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<spike.porcupine.org> +554 5.7.1 <spike.porcupine.org>: Helo command rejected: Access denied +>>> helo www.porcupine.org +OK +>>> client_restrictions check_client_a_access,inline:{168.100.189.2=reject} +OK +>>> client spike.porcupine.org 1.2.3.4 +./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[1.2.3.4]: 554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org> +554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied +>>> client www.porcupine.org 1.2.3.4 +OK +>>> reverse_client_restrictions check_reverse_client_a_access,inline:{168.100.189.2=reject} +bad command +>>> client spike.porcupine.org 1.2.3.4 +./smtpd_check: <queue id>: reject: CONNECT from spike.porcupine.org[1.2.3.4]: 554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org> +554 5.7.1 <spike.porcupine.org[1.2.3.4]>: Client host rejected: Access denied +>>> client www.porcupine.org 1.2.3.4 +OK +>>> sender_restrictions check_sender_a_access,inline:{168.100.189.2=reject} +OK +>>> mail foo@spike.porcupine.org +./smtpd_check: <queue id>: reject: MAIL from www.porcupine.org[1.2.3.4]: 554 5.7.1 <foo@spike.porcupine.org>: Sender address rejected: Access denied; from=<foo@spike.porcupine.org> proto=SMTP helo=<www.porcupine.org> +554 5.7.1 <foo@spike.porcupine.org>: Sender address rejected: Access denied +>>> mail foo@www.porcupine.org +OK +>>> recipient_restrictions check_recipient_a_access,inline:{168.100.189.2=reject} +OK +>>> rcpt foo@spike.porcupine.org +./smtpd_check: <queue id>: reject: RCPT from www.porcupine.org[1.2.3.4]: 554 5.7.1 <foo@spike.porcupine.org>: Recipient address rejected: Access denied; from=<foo@www.porcupine.org> to=<foo@spike.porcupine.org> proto=SMTP helo=<www.porcupine.org> +554 5.7.1 <foo@spike.porcupine.org>: Recipient address rejected: Access denied +>>> rcpt foo@www.porcupine.org +OK diff --git a/src/smtpd/smtpd_state.c b/src/smtpd/smtpd_state.c new file mode 100644 index 0000000..f2f5f89 --- /dev/null +++ b/src/smtpd/smtpd_state.c @@ -0,0 +1,248 @@ +/*++ +/* NAME +/* smtpd_state 3 +/* SUMMARY +/* Postfix SMTP server +/* SYNOPSIS +/* #include "smtpd.h" +/* +/* void smtpd_state_init(state, stream, service) +/* SMTPD_STATE *state; +/* VSTREAM *stream; +/* const char *service; +/* +/* void smtpd_state_reset(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* smtpd_state_init() initializes session context. +/* +/* smtpd_state_reset() cleans up session context. +/* +/* Arguments: +/* .IP state +/* Session context. +/* .IP stream +/* Stream connected to peer. The stream is not copied. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <events.h> +#include <mymalloc.h> +#include <vstream.h> +#include <name_mask.h> +#include <msg.h> + +/* Global library. */ + +#include <cleanup_user.h> +#include <mail_params.h> +#include <mail_error.h> +#include <mail_proto.h> + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_chat.h" +#include "smtpd_sasl_glue.h" + +/* smtpd_state_init - initialize after connection establishment */ + +void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream, + const char *service) +{ + + /* + * Initialize the state information for this connection, and fill in the + * connection-specific fields. + */ + state->flags = 0; + state->err = CLEANUP_STAT_OK; + state->client = stream; + state->service = mystrdup(service); + state->buffer = vstring_alloc(100); + state->addr_buf = vstring_alloc(100); + state->conn_count = state->conn_rate = 0; + state->error_count = 0; + state->error_mask = 0; + state->notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, + var_notify_classes); + state->helo_name = 0; + state->queue_id = 0; + state->cleanup = 0; + state->dest = 0; + state->rcpt_count = 0; + state->access_denied = 0; + state->history = 0; + state->reason = 0; + state->sender = 0; + state->verp_delims = 0; + state->recipient = 0; + state->etrn_name = 0; + state->protocol = mystrdup(MAIL_PROTO_SMTP); + state->where = SMTPD_AFTER_CONNECT; + state->recursion = 0; + state->msg_size = 0; + state->act_size = 0; + state->junk_cmds = 0; + state->rcpt_overshoot = 0; + state->defer_if_permit_client = 0; + state->defer_if_permit_helo = 0; + state->defer_if_permit_sender = 0; + state->defer_if_reject.dsn = 0; + state->defer_if_reject.reason = 0; + state->defer_if_permit.dsn = 0; + state->defer_if_permit.reason = 0; + state->discard = 0; + state->expand_buf = 0; + state->prepend = 0; + state->proxy = 0; + state->proxy_mail = 0; + state->saved_filter = 0; + state->saved_redirect = 0; + state->saved_bcc = 0; + state->saved_flags = 0; +#ifdef DELAY_ACTION + state->saved_delay = 0; +#endif + state->instance = vstring_alloc(10); + state->seqno = 0; + state->rewrite_context = 0; +#if 0 + state->ehlo_discard_mask = ~0; +#else + state->ehlo_discard_mask = 0; +#endif + state->dsn_envid = 0; + state->dsn_buf = vstring_alloc(100); + state->dsn_orcpt_buf = vstring_alloc(100); +#ifdef USE_TLS +#ifdef USE_TLSPROXY + state->tlsproxy = 0; +#endif + state->tls_context = 0; +#endif + + /* + * Minimal initialization to support external authentication (e.g., + * XCLIENT) without having to enable SASL in main.cf. + */ +#ifdef USE_SASL_AUTH + if (SMTPD_STAND_ALONE(state)) + var_smtpd_sasl_enable = 0; + smtpd_sasl_set_inactive(state); + smtpd_sasl_state_init(state); +#endif + + state->milter_argv = 0; + state->milter_argc = 0; + state->milters = 0; + + /* + * Initialize peer information. + */ + smtpd_peer_init(state); + + /* + * Initialize xforward information. + */ + smtpd_xforward_init(state); + + /* + * Initialize the conversation history. + */ + smtpd_chat_reset(state); + + state->ehlo_argv = 0; + state->ehlo_buf = 0; + + /* + * BDAT. + */ + state->bdat_state = SMTPD_BDAT_STAT_NONE; + state->bdat_get_stream = 0; + state->bdat_get_buffer = 0; +} + +/* smtpd_state_reset - cleanup after disconnect */ + +void smtpd_state_reset(SMTPD_STATE *state) +{ + + /* + * When cleaning up, touch only those fields that smtpd_state_init() + * filled in. The other fields are taken care of by their own + * "destructor" functions. + */ + if (state->service) + myfree(state->service); + if (state->buffer) + vstring_free(state->buffer); + if (state->addr_buf) + vstring_free(state->addr_buf); + if (state->access_denied) + myfree(state->access_denied); + if (state->protocol) + myfree(state->protocol); + smtpd_peer_reset(state); + + /* + * Buffers that are created on the fly and that may be shared among mail + * deliveries within the same SMTP session. + */ + if (state->defer_if_permit.dsn) + vstring_free(state->defer_if_permit.dsn); + if (state->defer_if_permit.reason) + vstring_free(state->defer_if_permit.reason); + if (state->defer_if_reject.dsn) + vstring_free(state->defer_if_reject.dsn); + if (state->defer_if_reject.reason) + vstring_free(state->defer_if_reject.reason); + if (state->expand_buf) + vstring_free(state->expand_buf); + if (state->instance) + vstring_free(state->instance); + if (state->dsn_buf) + vstring_free(state->dsn_buf); + if (state->dsn_orcpt_buf) + vstring_free(state->dsn_orcpt_buf); +#if (defined(USE_TLS) && defined(USE_TLSPROXY)) + if (state->tlsproxy) /* still open after longjmp */ + vstream_fclose(state->tlsproxy); +#endif + + /* + * BDAT. + */ + if (state->bdat_get_stream) + (void) vstream_fclose(state->bdat_get_stream); + if (state->bdat_get_buffer) + vstring_free(state->bdat_get_buffer); +} diff --git a/src/smtpd/smtpd_token.c b/src/smtpd/smtpd_token.c new file mode 100644 index 0000000..927088f --- /dev/null +++ b/src/smtpd/smtpd_token.c @@ -0,0 +1,233 @@ +/*++ +/* NAME +/* smtpd_token 3 +/* SUMMARY +/* tokenize SMTPD command +/* SYNOPSIS +/* #include <smtpd_token.h> +/* +/* typedef struct { +/* .in +4 +/* int tokval; +/* char *strval; +/* /* other stuff... */ +/* .in -4 +/* } SMTPD_TOKEN; +/* +/* int smtpd_token(str, argvp) +/* char *str; +/* SMTPD_TOKEN **argvp; +/* DESCRIPTION +/* smtpd_token() routine converts the string in \fIstr\fR to an +/* array of tokens in \fIargvp\fR. The number of tokens is returned +/* via the function return value. +/* +/* Token types: +/* .IP SMTPD_TOK_OTHER +/* The token is something else. +/* .IP SMTPD_TOK_ERROR +/* A malformed token. +/* BUGS +/* This tokenizer understands just enough to tokenize SMTPD commands. +/* It understands backslash escapes, white space, quoted strings, +/* and addresses (including quoted text) enclosed by < and >. +/* The input is broken up into tokens by whitespace, except for +/* whitespace that is protected by quotes etc. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> +#include <string.h> +#include <unistd.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <mymalloc.h> +#include <mvect.h> + +/* Application-specific. */ + +#include "smtpd_token.h" + +/* smtp_quoted - read until closing quote */ + +static char *smtp_quoted(char *cp, SMTPD_TOKEN *arg, int start, int last) +{ + static VSTRING *stack; + int wanted; + int c; + + /* + * Parser stack. `ch' is always the most-recently entered character. + */ +#define ENTER_CHAR(buf, ch) VSTRING_ADDCH(buf, ch); +#define LEAVE_CHAR(buf, ch) { \ + vstring_truncate(buf, VSTRING_LEN(buf) - 1); \ + ch = vstring_end(buf)[-1]; \ + } + + if (stack == 0) + stack = vstring_alloc(1); + VSTRING_RESET(stack); + ENTER_CHAR(stack, wanted = last); + + VSTRING_ADDCH(arg->vstrval, start); + for (;;) { + if ((c = *cp) == 0) + break; + cp++; + VSTRING_ADDCH(arg->vstrval, c); + if (c == '\\') { /* parse escape sequence */ + if ((c = *cp) == 0) + break; + cp++; + VSTRING_ADDCH(arg->vstrval, c); + } else if (c == wanted) { /* closing quote etc. */ + if (VSTRING_LEN(stack) == 1) + return (cp); + LEAVE_CHAR(stack, wanted); + } else if (c == '"') { + ENTER_CHAR(stack, wanted = '"'); /* highest precedence */ + } else if (c == '<' && wanted == '>') { + ENTER_CHAR(stack, wanted = '>'); /* lowest precedence */ + } + } + arg->tokval = SMTPD_TOK_ERROR; /* missing end */ + return (cp); +} + +/* smtp_next_token - extract next token from input, update cp */ + +static char *smtp_next_token(char *cp, SMTPD_TOKEN *arg) +{ + int c; + + VSTRING_RESET(arg->vstrval); + arg->tokval = SMTPD_TOK_OTHER; + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) +#define STREQ(x,y,l) (strncasecmp((x), (y), (l)) == 0) + + for (;;) { + if ((c = *cp) == 0) /* end of input */ + break; + cp++; + if (ISSPACE(c)) { /* whitespace, skip */ + while (*cp && ISSPACE(*cp)) + cp++; + if (LEN(arg->vstrval) > 0) /* end of token */ + break; + } else if (c == '<') { /* <stuff> */ + cp = smtp_quoted(cp, arg, c, '>'); + } else if (c == '"') { /* "stuff" */ + cp = smtp_quoted(cp, arg, c, c); + } else if (c == ':') { /* this is gross, but... */ + VSTRING_ADDCH(arg->vstrval, c); + if (STREQ(STR(arg->vstrval), "to:", LEN(arg->vstrval)) + || STREQ(STR(arg->vstrval), "from:", LEN(arg->vstrval))) + break; + } else { /* other */ + if (c == '\\') { + VSTRING_ADDCH(arg->vstrval, c); + if ((c = *cp) == 0) + break; + cp++; + } + VSTRING_ADDCH(arg->vstrval, c); + } + } + if (LEN(arg->vstrval) <= 0) /* no token found */ + return (0); + VSTRING_TERMINATE(arg->vstrval); + arg->strval = vstring_str(arg->vstrval); + return (cp); +} + +/* smtpd_token_init - initialize token structures */ + +static void smtpd_token_init(char *ptr, ssize_t count) +{ + SMTPD_TOKEN *arg; + int n; + + for (arg = (SMTPD_TOKEN *) ptr, n = 0; n < count; arg++, n++) + arg->vstrval = vstring_alloc(10); +} + +/* smtpd_token - tokenize SMTPD command */ + +int smtpd_token(char *cp, SMTPD_TOKEN **argvp) +{ + static SMTPD_TOKEN *smtp_argv; + static MVECT mvect; + int n; + + if (smtp_argv == 0) + smtp_argv = (SMTPD_TOKEN *) mvect_alloc(&mvect, sizeof(*smtp_argv), 1, + smtpd_token_init, (MVECT_FN) 0); + for (n = 0; /* void */ ; n++) { + smtp_argv = (SMTPD_TOKEN *) mvect_realloc(&mvect, n + 1); + if ((cp = smtp_next_token(cp, smtp_argv + n)) == 0) + break; + } + *argvp = smtp_argv; + return (n); +} + +#ifdef TEST + + /* + * Test program for the SMTPD command tokenizer. + */ + +#include <stdlib.h> +#include <vstream.h> +#include <vstring_vstream.h> + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *vp = vstring_alloc(10); + int tok_argc; + SMTPD_TOKEN *tok_argv; + int i; + + for (;;) { + if (isatty(STDIN_FILENO)) + vstream_printf("enter SMTPD command: "); + vstream_fflush(VSTREAM_OUT); + if (vstring_get_nonl(vp, VSTREAM_IN) == VSTREAM_EOF) + break; + if (*vstring_str(vp) == '#') + continue; + if (!isatty(STDIN_FILENO)) + vstream_printf("%s\n", vstring_str(vp)); + tok_argc = smtpd_token(vstring_str(vp), &tok_argv); + for (i = 0; i < tok_argc; i++) { + vstream_printf("Token type: %s\n", + tok_argv[i].tokval == SMTPD_TOK_OTHER ? "other" : + tok_argv[i].tokval == SMTPD_TOK_ERROR ? "error" : + "unknown"); + vstream_printf("Token value: %s\n", tok_argv[i].strval); + } + } + vstring_free(vp); + exit(0); +} + +#endif diff --git a/src/smtpd/smtpd_token.h b/src/smtpd/smtpd_token.h new file mode 100644 index 0000000..88489fa --- /dev/null +++ b/src/smtpd/smtpd_token.h @@ -0,0 +1,40 @@ +/*++ +/* NAME +/* smtpd_token 3h +/* SUMMARY +/* tokenize SMTPD command +/* SYNOPSIS +/* #include <smtpd_token.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +typedef struct SMTPD_TOKEN { + int tokval; + char *strval; + VSTRING *vstrval; +} SMTPD_TOKEN; + +#define SMTPD_TOK_OTHER 0 +#define SMTPD_TOK_ADDR 1 +#define SMTPD_TOK_ERROR 2 + +extern int smtpd_token(char *, SMTPD_TOKEN **); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtpd/smtpd_token.in b/src/smtpd/smtpd_token.in new file mode 100644 index 0000000..d67d5d0 --- /dev/null +++ b/src/smtpd/smtpd_token.in @@ -0,0 +1,12 @@ +mail from:<wietse@porcupine.org> +mail from:<"wietse venema"@porcupine.org> +mail from:wietse@porcupine.org +mail from:<wietse @ porcupine.org> +mail from:<"wietse venema"@porcupine.org ("wietse ) venema")> +mail from:<"wietse venema" <wietse@porcupine.org>> +mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )> +mail from:"wietse venema"@porcupine.org +mail from:wietse\ venema@porcupine.org +mail to:<"wietse venema> +mail to:<wietse@[stuff> +mail to:<wietse@["stuff]> diff --git a/src/smtpd/smtpd_token.ref b/src/smtpd/smtpd_token.ref new file mode 100644 index 0000000..21dc969 --- /dev/null +++ b/src/smtpd/smtpd_token.ref @@ -0,0 +1,84 @@ +mail from:<wietse@porcupine.org> +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <wietse@porcupine.org> +mail from:<"wietse venema"@porcupine.org> +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <"wietse venema"@porcupine.org> +mail from:wietse@porcupine.org +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: wietse@porcupine.org +mail from:<wietse @ porcupine.org> +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <wietse @ porcupine.org> +mail from:<"wietse venema"@porcupine.org ("wietse ) venema")> +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <"wietse venema"@porcupine.org ("wietse ) venema")> +mail from:<"wietse venema" <wietse@porcupine.org>> +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <"wietse venema" <wietse@porcupine.org>> +mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )> +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <"wietse venema"@porcupine.org ( ("wietse ) venema") )> +mail from:"wietse venema"@porcupine.org +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: "wietse venema"@porcupine.org +mail from:wietse\ venema@porcupine.org +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: wietse\ venema@porcupine.org +mail to:<"wietse venema> +Token type: other +Token value: mail +Token type: other +Token value: to: +Token type: error +Token value: <"wietse venema> +mail to:<wietse@[stuff> +Token type: other +Token value: mail +Token type: other +Token value: to: +Token type: other +Token value: <wietse@[stuff> +mail to:<wietse@["stuff]> +Token type: other +Token value: mail +Token type: other +Token value: to: +Token type: error +Token value: <wietse@["stuff]> diff --git a/src/smtpd/smtpd_xforward.c b/src/smtpd/smtpd_xforward.c new file mode 100644 index 0000000..053d377 --- /dev/null +++ b/src/smtpd/smtpd_xforward.c @@ -0,0 +1,114 @@ +/*++ +/* NAME +/* smtpd_xforward 3 +/* SUMMARY +/* maintain XCLIENT information +/* SYNOPSIS +/* #include "smtpd.h" +/* +/* void smtpd_xforward_init(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_xforward_preset(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_xforward_reset(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* smtpd_xforward_init() zeroes the attributes for storage of +/* XFORWARD command parameters. +/* +/* smtpd_xforward_preset() takes the result from smtpd_xforward_init() +/* and sets all fields to the same "unknown" value that regular +/* client attributes would have. +/* +/* smtpd_xforward_reset() restores the state from smtpd_xforward_init(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <msg.h> + +/* Global library. */ + +#include <mail_proto.h> + +/* Application-specific. */ + +#include <smtpd.h> + +/* smtpd_xforward_init - initialize xforward attributes */ + +void smtpd_xforward_init(SMTPD_STATE *state) +{ + state->xforward.flags = 0; + state->xforward.name = 0; + state->xforward.addr = 0; + state->xforward.port = 0; + state->xforward.namaddr = 0; + state->xforward.protocol = 0; + state->xforward.helo_name = 0; + state->xforward.ident = 0; + state->xforward.domain = 0; +} + +/* smtpd_xforward_preset - set xforward attributes to "unknown" */ + +void smtpd_xforward_preset(SMTPD_STATE *state) +{ + + /* + * Sanity checks. + */ + if (state->xforward.flags) + msg_panic("smtpd_xforward_preset: bad flags: 0x%x", + state->xforward.flags); + + /* + * This is a temporary solution. Unknown forwarded attributes get the + * same values as unknown normal attributes, so that we don't break + * assumptions in pre-existing code. + */ + state->xforward.flags = SMTPD_STATE_XFORWARD_INIT; + state->xforward.name = mystrdup(CLIENT_NAME_UNKNOWN); + state->xforward.addr = mystrdup(CLIENT_ADDR_UNKNOWN); + state->xforward.port = mystrdup(CLIENT_PORT_UNKNOWN); + state->xforward.namaddr = mystrdup(CLIENT_NAMADDR_UNKNOWN); + state->xforward.rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN); + /* Leave helo at zero. */ + state->xforward.protocol = mystrdup(CLIENT_PROTO_UNKNOWN); + /* Leave ident at zero. */ + /* Leave domain context at zero. */ +} + +/* smtpd_xforward_reset - reset xforward attributes */ + +void smtpd_xforward_reset(SMTPD_STATE *state) +{ +#define FREE_AND_WIPE(s) { if (s) myfree(s); s = 0; } + + state->xforward.flags = 0; + FREE_AND_WIPE(state->xforward.name); + FREE_AND_WIPE(state->xforward.addr); + FREE_AND_WIPE(state->xforward.port); + FREE_AND_WIPE(state->xforward.namaddr); + FREE_AND_WIPE(state->xforward.rfc_addr); + FREE_AND_WIPE(state->xforward.protocol); + FREE_AND_WIPE(state->xforward.helo_name); + FREE_AND_WIPE(state->xforward.ident); + FREE_AND_WIPE(state->xforward.domain); +} |