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/smtp | |
parent | Initial commit. (diff) | |
download | postfix-upstream/3.4.23.tar.xz postfix-upstream/3.4.23.zip |
Adding upstream version 3.4.23.upstream/3.4.23upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
110 files changed, 38749 insertions, 0 deletions
diff --git a/src/smtp/.indent.pro b/src/smtp/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/smtp/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/smtp/.printfck b/src/smtp/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/smtp/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/smtp/Makefile.in b/src/smtp/Makefile.in new file mode 100644 index 0000000..7d45564 --- /dev/null +++ b/src/smtp/Makefile.in @@ -0,0 +1,831 @@ +SHELL = /bin/sh +SRCS = smtp.c smtp_connect.c smtp_proto.c smtp_chat.c smtp_session.c \ + smtp_addr.c smtp_trouble.c smtp_state.c smtp_rcpt.c smtp_tls_policy.c \ + smtp_sasl_proto.c smtp_sasl_glue.c smtp_reuse.c smtp_map11.c \ + smtp_sasl_auth_cache.c smtp_key.c +OBJS = smtp.o smtp_connect.o smtp_proto.o smtp_chat.o smtp_session.o \ + smtp_addr.o smtp_trouble.o smtp_state.o smtp_rcpt.o smtp_tls_policy.o \ + smtp_sasl_proto.o smtp_sasl_glue.o smtp_reuse.o smtp_map11.o \ + smtp_sasl_auth_cache.o smtp_key.o +HDRS = smtp.h smtp_sasl.h smtp_addr.h smtp_reuse.h smtp_sasl_auth_cache.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= smtp_unalias smtp_map11 +PROG = smtp +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/libxsasl.a \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: smtp_map11_test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +smtp.o: smtp.c smtp_params.c lmtp_params.c + +lmtp_params.c: smtp_params.c + egrep -v -f smtp-only smtp_params.c | \ + sed 's/SMTP/LMTP/g; s/smtp_\([a-z]*_table\)/lmtp_\1/' >$@ + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +smtp_unalias: smtp_unalias.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +smtp_map11: smtp_map11.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +smtp_map11_test: smtp_map11 smtp_map11.ref + $(SHLIB_ENV) $(VALGRIND) ./smtp_map11 <smtp_map11.in >smtp_map11.tmp 2>&1 + diff smtp_map11.ref smtp_map11.tmp + rm -f smtp_map11.tmp + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +lmtp_params.o: lmtp_params.c +smtp.o: ../../include/argv.h +smtp.o: ../../include/attr.h +smtp.o: ../../include/check_arg.h +smtp.o: ../../include/debug_peer.h +smtp.o: ../../include/deliver_request.h +smtp.o: ../../include/dict.h +smtp.o: ../../include/dns.h +smtp.o: ../../include/dsn.h +smtp.o: ../../include/dsn_buf.h +smtp.o: ../../include/ext_prop.h +smtp.o: ../../include/flush_clnt.h +smtp.o: ../../include/header_body_checks.h +smtp.o: ../../include/header_opts.h +smtp.o: ../../include/htable.h +smtp.o: ../../include/iostuff.h +smtp.o: ../../include/mail_conf.h +smtp.o: ../../include/mail_params.h +smtp.o: ../../include/mail_proto.h +smtp.o: ../../include/mail_server.h +smtp.o: ../../include/mail_version.h +smtp.o: ../../include/maps.h +smtp.o: ../../include/match_list.h +smtp.o: ../../include/mime_state.h +smtp.o: ../../include/msg.h +smtp.o: ../../include/msg_stats.h +smtp.o: ../../include/myaddrinfo.h +smtp.o: ../../include/myflock.h +smtp.o: ../../include/mymalloc.h +smtp.o: ../../include/name_code.h +smtp.o: ../../include/name_mask.h +smtp.o: ../../include/nvtable.h +smtp.o: ../../include/recipient_list.h +smtp.o: ../../include/resolve_clnt.h +smtp.o: ../../include/scache.h +smtp.o: ../../include/sock_addr.h +smtp.o: ../../include/string_list.h +smtp.o: ../../include/stringops.h +smtp.o: ../../include/sys_defs.h +smtp.o: ../../include/tls.h +smtp.o: ../../include/tls_proxy.h +smtp.o: ../../include/tok822.h +smtp.o: ../../include/vbuf.h +smtp.o: ../../include/vstream.h +smtp.o: ../../include/vstring.h +smtp.o: lmtp_params.c +smtp.o: smtp.c +smtp.o: smtp.h +smtp.o: smtp_params.c +smtp.o: smtp_sasl.h +smtp_addr.o: ../../include/argv.h +smtp_addr.o: ../../include/attr.h +smtp_addr.o: ../../include/check_arg.h +smtp_addr.o: ../../include/deliver_request.h +smtp_addr.o: ../../include/dict.h +smtp_addr.o: ../../include/dns.h +smtp_addr.o: ../../include/dsn.h +smtp_addr.o: ../../include/dsn_buf.h +smtp_addr.o: ../../include/header_body_checks.h +smtp_addr.o: ../../include/header_opts.h +smtp_addr.o: ../../include/htable.h +smtp_addr.o: ../../include/inet_addr_list.h +smtp_addr.o: ../../include/inet_proto.h +smtp_addr.o: ../../include/mail_params.h +smtp_addr.o: ../../include/maps.h +smtp_addr.o: ../../include/match_list.h +smtp_addr.o: ../../include/midna_domain.h +smtp_addr.o: ../../include/mime_state.h +smtp_addr.o: ../../include/msg.h +smtp_addr.o: ../../include/msg_stats.h +smtp_addr.o: ../../include/myaddrinfo.h +smtp_addr.o: ../../include/myflock.h +smtp_addr.o: ../../include/mymalloc.h +smtp_addr.o: ../../include/name_code.h +smtp_addr.o: ../../include/name_mask.h +smtp_addr.o: ../../include/nvtable.h +smtp_addr.o: ../../include/own_inet_addr.h +smtp_addr.o: ../../include/recipient_list.h +smtp_addr.o: ../../include/resolve_clnt.h +smtp_addr.o: ../../include/scache.h +smtp_addr.o: ../../include/sock_addr.h +smtp_addr.o: ../../include/string_list.h +smtp_addr.o: ../../include/stringops.h +smtp_addr.o: ../../include/sys_defs.h +smtp_addr.o: ../../include/tls.h +smtp_addr.o: ../../include/tls_proxy.h +smtp_addr.o: ../../include/tok822.h +smtp_addr.o: ../../include/vbuf.h +smtp_addr.o: ../../include/vstream.h +smtp_addr.o: ../../include/vstring.h +smtp_addr.o: smtp.h +smtp_addr.o: smtp_addr.c +smtp_addr.o: smtp_addr.h +smtp_chat.o: ../../include/argv.h +smtp_chat.o: ../../include/attr.h +smtp_chat.o: ../../include/check_arg.h +smtp_chat.o: ../../include/cleanup_user.h +smtp_chat.o: ../../include/deliver_request.h +smtp_chat.o: ../../include/dict.h +smtp_chat.o: ../../include/dns.h +smtp_chat.o: ../../include/dsn.h +smtp_chat.o: ../../include/dsn_buf.h +smtp_chat.o: ../../include/dsn_util.h +smtp_chat.o: ../../include/header_body_checks.h +smtp_chat.o: ../../include/header_opts.h +smtp_chat.o: ../../include/htable.h +smtp_chat.o: ../../include/int_filt.h +smtp_chat.o: ../../include/iostuff.h +smtp_chat.o: ../../include/line_wrap.h +smtp_chat.o: ../../include/mail_addr.h +smtp_chat.o: ../../include/mail_error.h +smtp_chat.o: ../../include/mail_params.h +smtp_chat.o: ../../include/mail_proto.h +smtp_chat.o: ../../include/maps.h +smtp_chat.o: ../../include/match_list.h +smtp_chat.o: ../../include/mime_state.h +smtp_chat.o: ../../include/msg.h +smtp_chat.o: ../../include/msg_stats.h +smtp_chat.o: ../../include/myaddrinfo.h +smtp_chat.o: ../../include/myflock.h +smtp_chat.o: ../../include/mymalloc.h +smtp_chat.o: ../../include/name_code.h +smtp_chat.o: ../../include/name_mask.h +smtp_chat.o: ../../include/nvtable.h +smtp_chat.o: ../../include/post_mail.h +smtp_chat.o: ../../include/recipient_list.h +smtp_chat.o: ../../include/resolve_clnt.h +smtp_chat.o: ../../include/scache.h +smtp_chat.o: ../../include/smtp_stream.h +smtp_chat.o: ../../include/smtputf8.h +smtp_chat.o: ../../include/sock_addr.h +smtp_chat.o: ../../include/string_list.h +smtp_chat.o: ../../include/stringops.h +smtp_chat.o: ../../include/sys_defs.h +smtp_chat.o: ../../include/tls.h +smtp_chat.o: ../../include/tls_proxy.h +smtp_chat.o: ../../include/tok822.h +smtp_chat.o: ../../include/vbuf.h +smtp_chat.o: ../../include/vstream.h +smtp_chat.o: ../../include/vstring.h +smtp_chat.o: smtp.h +smtp_chat.o: smtp_chat.c +smtp_connect.o: ../../include/argv.h +smtp_connect.o: ../../include/attr.h +smtp_connect.o: ../../include/check_arg.h +smtp_connect.o: ../../include/deliver_pass.h +smtp_connect.o: ../../include/deliver_request.h +smtp_connect.o: ../../include/dict.h +smtp_connect.o: ../../include/dns.h +smtp_connect.o: ../../include/dsn.h +smtp_connect.o: ../../include/dsn_buf.h +smtp_connect.o: ../../include/header_body_checks.h +smtp_connect.o: ../../include/header_opts.h +smtp_connect.o: ../../include/host_port.h +smtp_connect.o: ../../include/htable.h +smtp_connect.o: ../../include/inet_addr_list.h +smtp_connect.o: ../../include/inet_proto.h +smtp_connect.o: ../../include/iostuff.h +smtp_connect.o: ../../include/mail_addr.h +smtp_connect.o: ../../include/mail_error.h +smtp_connect.o: ../../include/mail_params.h +smtp_connect.o: ../../include/mail_proto.h +smtp_connect.o: ../../include/maps.h +smtp_connect.o: ../../include/match_list.h +smtp_connect.o: ../../include/mime_state.h +smtp_connect.o: ../../include/msg.h +smtp_connect.o: ../../include/msg_stats.h +smtp_connect.o: ../../include/myaddrinfo.h +smtp_connect.o: ../../include/myflock.h +smtp_connect.o: ../../include/mymalloc.h +smtp_connect.o: ../../include/name_code.h +smtp_connect.o: ../../include/name_mask.h +smtp_connect.o: ../../include/nvtable.h +smtp_connect.o: ../../include/own_inet_addr.h +smtp_connect.o: ../../include/recipient_list.h +smtp_connect.o: ../../include/resolve_clnt.h +smtp_connect.o: ../../include/sane_connect.h +smtp_connect.o: ../../include/scache.h +smtp_connect.o: ../../include/sock_addr.h +smtp_connect.o: ../../include/split_at.h +smtp_connect.o: ../../include/string_list.h +smtp_connect.o: ../../include/stringops.h +smtp_connect.o: ../../include/sys_defs.h +smtp_connect.o: ../../include/timed_connect.h +smtp_connect.o: ../../include/tls.h +smtp_connect.o: ../../include/tls_proxy.h +smtp_connect.o: ../../include/tok822.h +smtp_connect.o: ../../include/vbuf.h +smtp_connect.o: ../../include/vstream.h +smtp_connect.o: ../../include/vstring.h +smtp_connect.o: smtp.h +smtp_connect.o: smtp_addr.h +smtp_connect.o: smtp_connect.c +smtp_connect.o: smtp_reuse.h +smtp_key.o: ../../include/argv.h +smtp_key.o: ../../include/attr.h +smtp_key.o: ../../include/base64_code.h +smtp_key.o: ../../include/check_arg.h +smtp_key.o: ../../include/deliver_request.h +smtp_key.o: ../../include/dict.h +smtp_key.o: ../../include/dns.h +smtp_key.o: ../../include/dsn.h +smtp_key.o: ../../include/dsn_buf.h +smtp_key.o: ../../include/header_body_checks.h +smtp_key.o: ../../include/header_opts.h +smtp_key.o: ../../include/htable.h +smtp_key.o: ../../include/mail_params.h +smtp_key.o: ../../include/maps.h +smtp_key.o: ../../include/match_list.h +smtp_key.o: ../../include/mime_state.h +smtp_key.o: ../../include/msg.h +smtp_key.o: ../../include/msg_stats.h +smtp_key.o: ../../include/myaddrinfo.h +smtp_key.o: ../../include/myflock.h +smtp_key.o: ../../include/mymalloc.h +smtp_key.o: ../../include/name_code.h +smtp_key.o: ../../include/name_mask.h +smtp_key.o: ../../include/nvtable.h +smtp_key.o: ../../include/recipient_list.h +smtp_key.o: ../../include/resolve_clnt.h +smtp_key.o: ../../include/scache.h +smtp_key.o: ../../include/sock_addr.h +smtp_key.o: ../../include/string_list.h +smtp_key.o: ../../include/sys_defs.h +smtp_key.o: ../../include/tls.h +smtp_key.o: ../../include/tls_proxy.h +smtp_key.o: ../../include/tok822.h +smtp_key.o: ../../include/vbuf.h +smtp_key.o: ../../include/vstream.h +smtp_key.o: ../../include/vstring.h +smtp_key.o: smtp.h +smtp_key.o: smtp_key.c +smtp_map11.o: ../../include/argv.h +smtp_map11.o: ../../include/attr.h +smtp_map11.o: ../../include/check_arg.h +smtp_map11.o: ../../include/deliver_request.h +smtp_map11.o: ../../include/dict.h +smtp_map11.o: ../../include/dns.h +smtp_map11.o: ../../include/dsn.h +smtp_map11.o: ../../include/dsn_buf.h +smtp_map11.o: ../../include/header_body_checks.h +smtp_map11.o: ../../include/header_opts.h +smtp_map11.o: ../../include/htable.h +smtp_map11.o: ../../include/mail_addr_form.h +smtp_map11.o: ../../include/mail_addr_map.h +smtp_map11.o: ../../include/maps.h +smtp_map11.o: ../../include/match_list.h +smtp_map11.o: ../../include/mime_state.h +smtp_map11.o: ../../include/msg.h +smtp_map11.o: ../../include/msg_stats.h +smtp_map11.o: ../../include/myaddrinfo.h +smtp_map11.o: ../../include/myflock.h +smtp_map11.o: ../../include/mymalloc.h +smtp_map11.o: ../../include/name_code.h +smtp_map11.o: ../../include/name_mask.h +smtp_map11.o: ../../include/nvtable.h +smtp_map11.o: ../../include/quote_822_local.h +smtp_map11.o: ../../include/quote_flags.h +smtp_map11.o: ../../include/recipient_list.h +smtp_map11.o: ../../include/resolve_clnt.h +smtp_map11.o: ../../include/scache.h +smtp_map11.o: ../../include/sock_addr.h +smtp_map11.o: ../../include/string_list.h +smtp_map11.o: ../../include/sys_defs.h +smtp_map11.o: ../../include/tls.h +smtp_map11.o: ../../include/tls_proxy.h +smtp_map11.o: ../../include/tok822.h +smtp_map11.o: ../../include/vbuf.h +smtp_map11.o: ../../include/vstream.h +smtp_map11.o: ../../include/vstring.h +smtp_map11.o: smtp.h +smtp_map11.o: smtp_map11.c +smtp_params.o: smtp_params.c +smtp_proto.o: ../../include/argv.h +smtp_proto.o: ../../include/attr.h +smtp_proto.o: ../../include/bounce.h +smtp_proto.o: ../../include/check_arg.h +smtp_proto.o: ../../include/defer.h +smtp_proto.o: ../../include/deliver_request.h +smtp_proto.o: ../../include/dict.h +smtp_proto.o: ../../include/dns.h +smtp_proto.o: ../../include/dsn.h +smtp_proto.o: ../../include/dsn_buf.h +smtp_proto.o: ../../include/dsn_mask.h +smtp_proto.o: ../../include/ehlo_mask.h +smtp_proto.o: ../../include/ext_prop.h +smtp_proto.o: ../../include/header_body_checks.h +smtp_proto.o: ../../include/header_opts.h +smtp_proto.o: ../../include/htable.h +smtp_proto.o: ../../include/iostuff.h +smtp_proto.o: ../../include/lex_822.h +smtp_proto.o: ../../include/mail_addr_form.h +smtp_proto.o: ../../include/mail_addr_map.h +smtp_proto.o: ../../include/mail_params.h +smtp_proto.o: ../../include/mail_proto.h +smtp_proto.o: ../../include/mail_queue.h +smtp_proto.o: ../../include/maps.h +smtp_proto.o: ../../include/mark_corrupt.h +smtp_proto.o: ../../include/match_list.h +smtp_proto.o: ../../include/match_parent_style.h +smtp_proto.o: ../../include/mime_state.h +smtp_proto.o: ../../include/msg.h +smtp_proto.o: ../../include/msg_stats.h +smtp_proto.o: ../../include/myaddrinfo.h +smtp_proto.o: ../../include/myflock.h +smtp_proto.o: ../../include/mymalloc.h +smtp_proto.o: ../../include/namadr_list.h +smtp_proto.o: ../../include/name_code.h +smtp_proto.o: ../../include/name_mask.h +smtp_proto.o: ../../include/nvtable.h +smtp_proto.o: ../../include/off_cvt.h +smtp_proto.o: ../../include/quote_821_local.h +smtp_proto.o: ../../include/quote_822_local.h +smtp_proto.o: ../../include/quote_flags.h +smtp_proto.o: ../../include/rec_type.h +smtp_proto.o: ../../include/recipient_list.h +smtp_proto.o: ../../include/record.h +smtp_proto.o: ../../include/resolve_clnt.h +smtp_proto.o: ../../include/scache.h +smtp_proto.o: ../../include/smtp_stream.h +smtp_proto.o: ../../include/smtputf8.h +smtp_proto.o: ../../include/sock_addr.h +smtp_proto.o: ../../include/split_at.h +smtp_proto.o: ../../include/string_list.h +smtp_proto.o: ../../include/stringops.h +smtp_proto.o: ../../include/sys_defs.h +smtp_proto.o: ../../include/tls.h +smtp_proto.o: ../../include/tls_proxy.h +smtp_proto.o: ../../include/tok822.h +smtp_proto.o: ../../include/uxtext.h +smtp_proto.o: ../../include/vbuf.h +smtp_proto.o: ../../include/vstream.h +smtp_proto.o: ../../include/vstring.h +smtp_proto.o: ../../include/vstring_vstream.h +smtp_proto.o: ../../include/xtext.h +smtp_proto.o: smtp.h +smtp_proto.o: smtp_proto.c +smtp_proto.o: smtp_sasl.h +smtp_rcpt.o: ../../include/argv.h +smtp_rcpt.o: ../../include/attr.h +smtp_rcpt.o: ../../include/bounce.h +smtp_rcpt.o: ../../include/check_arg.h +smtp_rcpt.o: ../../include/deliver_completed.h +smtp_rcpt.o: ../../include/deliver_request.h +smtp_rcpt.o: ../../include/dict.h +smtp_rcpt.o: ../../include/dns.h +smtp_rcpt.o: ../../include/dsn.h +smtp_rcpt.o: ../../include/dsn_buf.h +smtp_rcpt.o: ../../include/dsn_mask.h +smtp_rcpt.o: ../../include/header_body_checks.h +smtp_rcpt.o: ../../include/header_opts.h +smtp_rcpt.o: ../../include/htable.h +smtp_rcpt.o: ../../include/mail_params.h +smtp_rcpt.o: ../../include/maps.h +smtp_rcpt.o: ../../include/match_list.h +smtp_rcpt.o: ../../include/mime_state.h +smtp_rcpt.o: ../../include/msg.h +smtp_rcpt.o: ../../include/msg_stats.h +smtp_rcpt.o: ../../include/myaddrinfo.h +smtp_rcpt.o: ../../include/myflock.h +smtp_rcpt.o: ../../include/mymalloc.h +smtp_rcpt.o: ../../include/name_code.h +smtp_rcpt.o: ../../include/name_mask.h +smtp_rcpt.o: ../../include/nvtable.h +smtp_rcpt.o: ../../include/recipient_list.h +smtp_rcpt.o: ../../include/resolve_clnt.h +smtp_rcpt.o: ../../include/scache.h +smtp_rcpt.o: ../../include/sent.h +smtp_rcpt.o: ../../include/sock_addr.h +smtp_rcpt.o: ../../include/string_list.h +smtp_rcpt.o: ../../include/stringops.h +smtp_rcpt.o: ../../include/sys_defs.h +smtp_rcpt.o: ../../include/tls.h +smtp_rcpt.o: ../../include/tls_proxy.h +smtp_rcpt.o: ../../include/tok822.h +smtp_rcpt.o: ../../include/vbuf.h +smtp_rcpt.o: ../../include/vstream.h +smtp_rcpt.o: ../../include/vstring.h +smtp_rcpt.o: smtp.h +smtp_rcpt.o: smtp_rcpt.c +smtp_reuse.o: ../../include/argv.h +smtp_reuse.o: ../../include/attr.h +smtp_reuse.o: ../../include/check_arg.h +smtp_reuse.o: ../../include/deliver_request.h +smtp_reuse.o: ../../include/dict.h +smtp_reuse.o: ../../include/dns.h +smtp_reuse.o: ../../include/dsn.h +smtp_reuse.o: ../../include/dsn_buf.h +smtp_reuse.o: ../../include/header_body_checks.h +smtp_reuse.o: ../../include/header_opts.h +smtp_reuse.o: ../../include/htable.h +smtp_reuse.o: ../../include/mail_params.h +smtp_reuse.o: ../../include/maps.h +smtp_reuse.o: ../../include/match_list.h +smtp_reuse.o: ../../include/mime_state.h +smtp_reuse.o: ../../include/msg.h +smtp_reuse.o: ../../include/msg_stats.h +smtp_reuse.o: ../../include/myaddrinfo.h +smtp_reuse.o: ../../include/myflock.h +smtp_reuse.o: ../../include/mymalloc.h +smtp_reuse.o: ../../include/name_code.h +smtp_reuse.o: ../../include/name_mask.h +smtp_reuse.o: ../../include/nvtable.h +smtp_reuse.o: ../../include/recipient_list.h +smtp_reuse.o: ../../include/resolve_clnt.h +smtp_reuse.o: ../../include/scache.h +smtp_reuse.o: ../../include/sock_addr.h +smtp_reuse.o: ../../include/string_list.h +smtp_reuse.o: ../../include/stringops.h +smtp_reuse.o: ../../include/sys_defs.h +smtp_reuse.o: ../../include/tls.h +smtp_reuse.o: ../../include/tls_proxy.h +smtp_reuse.o: ../../include/tok822.h +smtp_reuse.o: ../../include/vbuf.h +smtp_reuse.o: ../../include/vstream.h +smtp_reuse.o: ../../include/vstring.h +smtp_reuse.o: smtp.h +smtp_reuse.o: smtp_reuse.c +smtp_reuse.o: smtp_reuse.h +smtp_sasl_auth_cache.o: ../../include/argv.h +smtp_sasl_auth_cache.o: ../../include/attr.h +smtp_sasl_auth_cache.o: ../../include/base64_code.h +smtp_sasl_auth_cache.o: ../../include/check_arg.h +smtp_sasl_auth_cache.o: ../../include/deliver_request.h +smtp_sasl_auth_cache.o: ../../include/dict.h +smtp_sasl_auth_cache.o: ../../include/dict_proxy.h +smtp_sasl_auth_cache.o: ../../include/dns.h +smtp_sasl_auth_cache.o: ../../include/dsn.h +smtp_sasl_auth_cache.o: ../../include/dsn_buf.h +smtp_sasl_auth_cache.o: ../../include/dsn_util.h +smtp_sasl_auth_cache.o: ../../include/header_body_checks.h +smtp_sasl_auth_cache.o: ../../include/header_opts.h +smtp_sasl_auth_cache.o: ../../include/htable.h +smtp_sasl_auth_cache.o: ../../include/maps.h +smtp_sasl_auth_cache.o: ../../include/match_list.h +smtp_sasl_auth_cache.o: ../../include/mime_state.h +smtp_sasl_auth_cache.o: ../../include/msg.h +smtp_sasl_auth_cache.o: ../../include/msg_stats.h +smtp_sasl_auth_cache.o: ../../include/myaddrinfo.h +smtp_sasl_auth_cache.o: ../../include/myflock.h +smtp_sasl_auth_cache.o: ../../include/mymalloc.h +smtp_sasl_auth_cache.o: ../../include/name_code.h +smtp_sasl_auth_cache.o: ../../include/name_mask.h +smtp_sasl_auth_cache.o: ../../include/nvtable.h +smtp_sasl_auth_cache.o: ../../include/recipient_list.h +smtp_sasl_auth_cache.o: ../../include/resolve_clnt.h +smtp_sasl_auth_cache.o: ../../include/scache.h +smtp_sasl_auth_cache.o: ../../include/sock_addr.h +smtp_sasl_auth_cache.o: ../../include/string_list.h +smtp_sasl_auth_cache.o: ../../include/stringops.h +smtp_sasl_auth_cache.o: ../../include/sys_defs.h +smtp_sasl_auth_cache.o: ../../include/tls.h +smtp_sasl_auth_cache.o: ../../include/tls_proxy.h +smtp_sasl_auth_cache.o: ../../include/tok822.h +smtp_sasl_auth_cache.o: ../../include/vbuf.h +smtp_sasl_auth_cache.o: ../../include/vstream.h +smtp_sasl_auth_cache.o: ../../include/vstring.h +smtp_sasl_auth_cache.o: smtp.h +smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.c +smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.h +smtp_sasl_glue.o: ../../include/argv.h +smtp_sasl_glue.o: ../../include/attr.h +smtp_sasl_glue.o: ../../include/check_arg.h +smtp_sasl_glue.o: ../../include/deliver_request.h +smtp_sasl_glue.o: ../../include/dict.h +smtp_sasl_glue.o: ../../include/dns.h +smtp_sasl_glue.o: ../../include/dsn.h +smtp_sasl_glue.o: ../../include/dsn_buf.h +smtp_sasl_glue.o: ../../include/header_body_checks.h +smtp_sasl_glue.o: ../../include/header_opts.h +smtp_sasl_glue.o: ../../include/htable.h +smtp_sasl_glue.o: ../../include/mail_addr_find.h +smtp_sasl_glue.o: ../../include/mail_addr_form.h +smtp_sasl_glue.o: ../../include/mail_params.h +smtp_sasl_glue.o: ../../include/maps.h +smtp_sasl_glue.o: ../../include/match_list.h +smtp_sasl_glue.o: ../../include/mime_state.h +smtp_sasl_glue.o: ../../include/msg.h +smtp_sasl_glue.o: ../../include/msg_stats.h +smtp_sasl_glue.o: ../../include/myaddrinfo.h +smtp_sasl_glue.o: ../../include/myflock.h +smtp_sasl_glue.o: ../../include/mymalloc.h +smtp_sasl_glue.o: ../../include/name_code.h +smtp_sasl_glue.o: ../../include/name_mask.h +smtp_sasl_glue.o: ../../include/nvtable.h +smtp_sasl_glue.o: ../../include/recipient_list.h +smtp_sasl_glue.o: ../../include/resolve_clnt.h +smtp_sasl_glue.o: ../../include/scache.h +smtp_sasl_glue.o: ../../include/smtp_stream.h +smtp_sasl_glue.o: ../../include/sock_addr.h +smtp_sasl_glue.o: ../../include/split_at.h +smtp_sasl_glue.o: ../../include/string_list.h +smtp_sasl_glue.o: ../../include/stringops.h +smtp_sasl_glue.o: ../../include/sys_defs.h +smtp_sasl_glue.o: ../../include/tls.h +smtp_sasl_glue.o: ../../include/tls_proxy.h +smtp_sasl_glue.o: ../../include/tok822.h +smtp_sasl_glue.o: ../../include/vbuf.h +smtp_sasl_glue.o: ../../include/vstream.h +smtp_sasl_glue.o: ../../include/vstring.h +smtp_sasl_glue.o: ../../include/xsasl.h +smtp_sasl_glue.o: smtp.h +smtp_sasl_glue.o: smtp_sasl.h +smtp_sasl_glue.o: smtp_sasl_auth_cache.h +smtp_sasl_glue.o: smtp_sasl_glue.c +smtp_sasl_proto.o: ../../include/argv.h +smtp_sasl_proto.o: ../../include/attr.h +smtp_sasl_proto.o: ../../include/check_arg.h +smtp_sasl_proto.o: ../../include/deliver_request.h +smtp_sasl_proto.o: ../../include/dict.h +smtp_sasl_proto.o: ../../include/dns.h +smtp_sasl_proto.o: ../../include/dsn.h +smtp_sasl_proto.o: ../../include/dsn_buf.h +smtp_sasl_proto.o: ../../include/header_body_checks.h +smtp_sasl_proto.o: ../../include/header_opts.h +smtp_sasl_proto.o: ../../include/htable.h +smtp_sasl_proto.o: ../../include/mail_params.h +smtp_sasl_proto.o: ../../include/maps.h +smtp_sasl_proto.o: ../../include/match_list.h +smtp_sasl_proto.o: ../../include/mime_state.h +smtp_sasl_proto.o: ../../include/msg.h +smtp_sasl_proto.o: ../../include/msg_stats.h +smtp_sasl_proto.o: ../../include/myaddrinfo.h +smtp_sasl_proto.o: ../../include/myflock.h +smtp_sasl_proto.o: ../../include/mymalloc.h +smtp_sasl_proto.o: ../../include/name_code.h +smtp_sasl_proto.o: ../../include/name_mask.h +smtp_sasl_proto.o: ../../include/nvtable.h +smtp_sasl_proto.o: ../../include/recipient_list.h +smtp_sasl_proto.o: ../../include/resolve_clnt.h +smtp_sasl_proto.o: ../../include/scache.h +smtp_sasl_proto.o: ../../include/sock_addr.h +smtp_sasl_proto.o: ../../include/string_list.h +smtp_sasl_proto.o: ../../include/stringops.h +smtp_sasl_proto.o: ../../include/sys_defs.h +smtp_sasl_proto.o: ../../include/tls.h +smtp_sasl_proto.o: ../../include/tls_proxy.h +smtp_sasl_proto.o: ../../include/tok822.h +smtp_sasl_proto.o: ../../include/vbuf.h +smtp_sasl_proto.o: ../../include/vstream.h +smtp_sasl_proto.o: ../../include/vstring.h +smtp_sasl_proto.o: smtp.h +smtp_sasl_proto.o: smtp_sasl.h +smtp_sasl_proto.o: smtp_sasl_proto.c +smtp_session.o: ../../include/argv.h +smtp_session.o: ../../include/attr.h +smtp_session.o: ../../include/check_arg.h +smtp_session.o: ../../include/debug_peer.h +smtp_session.o: ../../include/deliver_request.h +smtp_session.o: ../../include/dict.h +smtp_session.o: ../../include/dns.h +smtp_session.o: ../../include/dsn.h +smtp_session.o: ../../include/dsn_buf.h +smtp_session.o: ../../include/header_body_checks.h +smtp_session.o: ../../include/header_opts.h +smtp_session.o: ../../include/htable.h +smtp_session.o: ../../include/mail_params.h +smtp_session.o: ../../include/maps.h +smtp_session.o: ../../include/match_list.h +smtp_session.o: ../../include/mime_state.h +smtp_session.o: ../../include/msg.h +smtp_session.o: ../../include/msg_stats.h +smtp_session.o: ../../include/myaddrinfo.h +smtp_session.o: ../../include/myflock.h +smtp_session.o: ../../include/mymalloc.h +smtp_session.o: ../../include/name_code.h +smtp_session.o: ../../include/name_mask.h +smtp_session.o: ../../include/nvtable.h +smtp_session.o: ../../include/recipient_list.h +smtp_session.o: ../../include/resolve_clnt.h +smtp_session.o: ../../include/scache.h +smtp_session.o: ../../include/sock_addr.h +smtp_session.o: ../../include/string_list.h +smtp_session.o: ../../include/stringops.h +smtp_session.o: ../../include/sys_defs.h +smtp_session.o: ../../include/tls.h +smtp_session.o: ../../include/tls_proxy.h +smtp_session.o: ../../include/tok822.h +smtp_session.o: ../../include/vbuf.h +smtp_session.o: ../../include/vstream.h +smtp_session.o: ../../include/vstring.h +smtp_session.o: smtp.h +smtp_session.o: smtp_sasl.h +smtp_session.o: smtp_session.c +smtp_state.o: ../../include/argv.h +smtp_state.o: ../../include/attr.h +smtp_state.o: ../../include/check_arg.h +smtp_state.o: ../../include/deliver_request.h +smtp_state.o: ../../include/dict.h +smtp_state.o: ../../include/dns.h +smtp_state.o: ../../include/dsn.h +smtp_state.o: ../../include/dsn_buf.h +smtp_state.o: ../../include/header_body_checks.h +smtp_state.o: ../../include/header_opts.h +smtp_state.o: ../../include/htable.h +smtp_state.o: ../../include/mail_params.h +smtp_state.o: ../../include/maps.h +smtp_state.o: ../../include/match_list.h +smtp_state.o: ../../include/mime_state.h +smtp_state.o: ../../include/msg.h +smtp_state.o: ../../include/msg_stats.h +smtp_state.o: ../../include/myaddrinfo.h +smtp_state.o: ../../include/myflock.h +smtp_state.o: ../../include/mymalloc.h +smtp_state.o: ../../include/name_code.h +smtp_state.o: ../../include/name_mask.h +smtp_state.o: ../../include/nvtable.h +smtp_state.o: ../../include/recipient_list.h +smtp_state.o: ../../include/resolve_clnt.h +smtp_state.o: ../../include/scache.h +smtp_state.o: ../../include/sock_addr.h +smtp_state.o: ../../include/string_list.h +smtp_state.o: ../../include/sys_defs.h +smtp_state.o: ../../include/tls.h +smtp_state.o: ../../include/tls_proxy.h +smtp_state.o: ../../include/tok822.h +smtp_state.o: ../../include/vbuf.h +smtp_state.o: ../../include/vstream.h +smtp_state.o: ../../include/vstring.h +smtp_state.o: smtp.h +smtp_state.o: smtp_sasl.h +smtp_state.o: smtp_state.c +smtp_tls_policy.o: ../../include/argv.h +smtp_tls_policy.o: ../../include/attr.h +smtp_tls_policy.o: ../../include/check_arg.h +smtp_tls_policy.o: ../../include/ctable.h +smtp_tls_policy.o: ../../include/deliver_request.h +smtp_tls_policy.o: ../../include/dict.h +smtp_tls_policy.o: ../../include/dns.h +smtp_tls_policy.o: ../../include/dsn.h +smtp_tls_policy.o: ../../include/dsn_buf.h +smtp_tls_policy.o: ../../include/header_body_checks.h +smtp_tls_policy.o: ../../include/header_opts.h +smtp_tls_policy.o: ../../include/htable.h +smtp_tls_policy.o: ../../include/mail_params.h +smtp_tls_policy.o: ../../include/maps.h +smtp_tls_policy.o: ../../include/match_list.h +smtp_tls_policy.o: ../../include/mime_state.h +smtp_tls_policy.o: ../../include/msg.h +smtp_tls_policy.o: ../../include/msg_stats.h +smtp_tls_policy.o: ../../include/myaddrinfo.h +smtp_tls_policy.o: ../../include/myflock.h +smtp_tls_policy.o: ../../include/mymalloc.h +smtp_tls_policy.o: ../../include/name_code.h +smtp_tls_policy.o: ../../include/name_mask.h +smtp_tls_policy.o: ../../include/nvtable.h +smtp_tls_policy.o: ../../include/recipient_list.h +smtp_tls_policy.o: ../../include/resolve_clnt.h +smtp_tls_policy.o: ../../include/scache.h +smtp_tls_policy.o: ../../include/sock_addr.h +smtp_tls_policy.o: ../../include/string_list.h +smtp_tls_policy.o: ../../include/stringops.h +smtp_tls_policy.o: ../../include/sys_defs.h +smtp_tls_policy.o: ../../include/tls.h +smtp_tls_policy.o: ../../include/tls_proxy.h +smtp_tls_policy.o: ../../include/tok822.h +smtp_tls_policy.o: ../../include/valid_hostname.h +smtp_tls_policy.o: ../../include/valid_utf8_hostname.h +smtp_tls_policy.o: ../../include/vbuf.h +smtp_tls_policy.o: ../../include/vstream.h +smtp_tls_policy.o: ../../include/vstring.h +smtp_tls_policy.o: smtp.h +smtp_tls_policy.o: smtp_tls_policy.c +smtp_trouble.o: ../../include/argv.h +smtp_trouble.o: ../../include/attr.h +smtp_trouble.o: ../../include/bounce.h +smtp_trouble.o: ../../include/check_arg.h +smtp_trouble.o: ../../include/defer.h +smtp_trouble.o: ../../include/deliver_completed.h +smtp_trouble.o: ../../include/deliver_request.h +smtp_trouble.o: ../../include/dict.h +smtp_trouble.o: ../../include/dns.h +smtp_trouble.o: ../../include/dsn.h +smtp_trouble.o: ../../include/dsn_buf.h +smtp_trouble.o: ../../include/header_body_checks.h +smtp_trouble.o: ../../include/header_opts.h +smtp_trouble.o: ../../include/htable.h +smtp_trouble.o: ../../include/mail_error.h +smtp_trouble.o: ../../include/mail_params.h +smtp_trouble.o: ../../include/maps.h +smtp_trouble.o: ../../include/match_list.h +smtp_trouble.o: ../../include/mime_state.h +smtp_trouble.o: ../../include/msg.h +smtp_trouble.o: ../../include/msg_stats.h +smtp_trouble.o: ../../include/myaddrinfo.h +smtp_trouble.o: ../../include/myflock.h +smtp_trouble.o: ../../include/mymalloc.h +smtp_trouble.o: ../../include/name_code.h +smtp_trouble.o: ../../include/name_mask.h +smtp_trouble.o: ../../include/nvtable.h +smtp_trouble.o: ../../include/recipient_list.h +smtp_trouble.o: ../../include/resolve_clnt.h +smtp_trouble.o: ../../include/scache.h +smtp_trouble.o: ../../include/smtp_stream.h +smtp_trouble.o: ../../include/sock_addr.h +smtp_trouble.o: ../../include/string_list.h +smtp_trouble.o: ../../include/stringops.h +smtp_trouble.o: ../../include/sys_defs.h +smtp_trouble.o: ../../include/tls.h +smtp_trouble.o: ../../include/tls_proxy.h +smtp_trouble.o: ../../include/tok822.h +smtp_trouble.o: ../../include/vbuf.h +smtp_trouble.o: ../../include/vstream.h +smtp_trouble.o: ../../include/vstring.h +smtp_trouble.o: smtp.h +smtp_trouble.o: smtp_sasl.h +smtp_trouble.o: smtp_trouble.c +smtp_unalias.o: ../../include/argv.h +smtp_unalias.o: ../../include/attr.h +smtp_unalias.o: ../../include/check_arg.h +smtp_unalias.o: ../../include/deliver_request.h +smtp_unalias.o: ../../include/dict.h +smtp_unalias.o: ../../include/dns.h +smtp_unalias.o: ../../include/dsn.h +smtp_unalias.o: ../../include/dsn_buf.h +smtp_unalias.o: ../../include/header_body_checks.h +smtp_unalias.o: ../../include/header_opts.h +smtp_unalias.o: ../../include/htable.h +smtp_unalias.o: ../../include/maps.h +smtp_unalias.o: ../../include/match_list.h +smtp_unalias.o: ../../include/mime_state.h +smtp_unalias.o: ../../include/msg.h +smtp_unalias.o: ../../include/msg_stats.h +smtp_unalias.o: ../../include/myaddrinfo.h +smtp_unalias.o: ../../include/myflock.h +smtp_unalias.o: ../../include/mymalloc.h +smtp_unalias.o: ../../include/name_code.h +smtp_unalias.o: ../../include/name_mask.h +smtp_unalias.o: ../../include/nvtable.h +smtp_unalias.o: ../../include/recipient_list.h +smtp_unalias.o: ../../include/resolve_clnt.h +smtp_unalias.o: ../../include/scache.h +smtp_unalias.o: ../../include/sock_addr.h +smtp_unalias.o: ../../include/string_list.h +smtp_unalias.o: ../../include/sys_defs.h +smtp_unalias.o: ../../include/tls.h +smtp_unalias.o: ../../include/tls_proxy.h +smtp_unalias.o: ../../include/tok822.h +smtp_unalias.o: ../../include/vbuf.h +smtp_unalias.o: ../../include/vstream.h +smtp_unalias.o: ../../include/vstring.h +smtp_unalias.o: smtp.h +smtp_unalias.o: smtp_unalias.c diff --git a/src/smtp/lmtp_params.c b/src/smtp/lmtp_params.c new file mode 100644 index 0000000..973cb5d --- /dev/null +++ b/src/smtp/lmtp_params.c @@ -0,0 +1,130 @@ + static const CONFIG_STR_TABLE lmtp_str_table[] = { + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_LMTP_FALLBACK, DEF_LMTP_FALLBACK, &var_fallback_relay, 0, 0, + VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, + VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, + VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, + VAR_LMTP_SASL_OPTS, DEF_LMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, + VAR_LMTP_SASL_PATH, DEF_LMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0, +#ifdef USE_TLS + VAR_LMTP_SASL_TLS_OPTS, DEF_LMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0, + VAR_LMTP_SASL_TLSV_OPTS, DEF_LMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0, + VAR_LMTP_TLS_CHAIN_FILES, DEF_LMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0, + VAR_LMTP_TLS_CERT_FILE, DEF_LMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_LMTP_TLS_KEY_FILE, DEF_LMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_LMTP_TLS_DCERT_FILE, DEF_LMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_LMTP_TLS_DKEY_FILE, DEF_LMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_LMTP_TLS_CA_FILE, DEF_LMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_LMTP_TLS_CA_PATH, DEF_LMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_LMTP_TLS_MAND_CIPH, DEF_LMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0, + VAR_LMTP_TLS_EXCL_CIPH, DEF_LMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0, + VAR_LMTP_TLS_MAND_EXCL, DEF_LMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0, + VAR_LMTP_TLS_MAND_PROTO, DEF_LMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0, + VAR_LMTP_TLS_VFY_CMATCH, DEF_LMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0, + VAR_LMTP_TLS_SEC_CMATCH, DEF_LMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0, + VAR_LMTP_TLS_FPT_CMATCH, DEF_LMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0, + VAR_LMTP_TLS_FPT_DGST, DEF_LMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_LMTP_TLS_TAFILE, DEF_LMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0, + VAR_LMTP_TLS_PROTO, DEF_LMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0, + VAR_LMTP_TLS_CIPH, DEF_LMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0, + VAR_LMTP_TLS_ECCERT_FILE, DEF_LMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, + VAR_LMTP_TLS_ECKEY_FILE, DEF_LMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0, + VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_LMTP_TLS_SNI, DEF_LMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0, +#endif + VAR_LMTP_SASL_MECHS, DEF_LMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0, + VAR_LMTP_SASL_TYPE, DEF_LMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0, + VAR_LMTP_BIND_ADDR, DEF_LMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0, + VAR_LMTP_BIND_ADDR6, DEF_LMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0, + VAR_LMTP_VRFY_TGT, DEF_LMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0, + VAR_LMTP_HELO_NAME, DEF_LMTP_HELO_NAME, &var_smtp_helo_name, 1, 0, + VAR_LMTP_HOST_LOOKUP, DEF_LMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0, + VAR_LMTP_DNS_SUPPORT, DEF_LMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0, + VAR_LMTP_CACHE_DEST, DEF_LMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0, + VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0, + VAR_LMTP_EHLO_DIS_WORDS, DEF_LMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0, + VAR_LMTP_EHLO_DIS_MAPS, DEF_LMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0, + VAR_LMTP_TLS_PER_SITE, DEF_LMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, + VAR_LMTP_TLS_LEVEL, DEF_LMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0, + VAR_LMTP_TLS_POLICY, DEF_LMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0, + VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, + VAR_LMTP_GENERIC_MAPS, DEF_LMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0, + VAR_LMTP_TCP_PORT, DEF_LMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0, + VAR_LMTP_PIX_BUG_WORDS, DEF_LMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0, + VAR_LMTP_PIX_BUG_MAPS, DEF_LMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0, + VAR_LMTP_SASL_AUTH_CACHE_NAME, DEF_LMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0, + VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0, + VAR_LMTP_HEAD_CHKS, DEF_LMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0, + VAR_LMTP_MIME_CHKS, DEF_LMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0, + VAR_LMTP_NEST_CHKS, DEF_LMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0, + VAR_LMTP_BODY_CHKS, DEF_LMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0, + VAR_LMTP_RESP_FILTER, DEF_LMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0, + VAR_LMTP_ADDR_PREF, DEF_LMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0, + VAR_LMTP_DNS_RES_OPT, DEF_LMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0, + VAR_LMTP_DSN_FILTER, DEF_LMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0, + VAR_LMTP_DNS_RE_FILTER, DEF_LMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0, + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, + 0, + }; + static const CONFIG_TIME_TABLE lmtp_time_table[] = { + VAR_LMTP_CONN_TMOUT, DEF_LMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0, + VAR_LMTP_HELO_TMOUT, DEF_LMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0, + VAR_LMTP_XFWD_TMOUT, DEF_LMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0, + VAR_LMTP_MAIL_TMOUT, DEF_LMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0, + VAR_LMTP_RCPT_TMOUT, DEF_LMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0, + VAR_LMTP_DATA0_TMOUT, DEF_LMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0, + VAR_LMTP_DATA1_TMOUT, DEF_LMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0, + VAR_LMTP_DATA2_TMOUT, DEF_LMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0, + VAR_LMTP_RSET_TMOUT, DEF_LMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0, + VAR_LMTP_QUIT_TMOUT, DEF_LMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0, + VAR_LMTP_PIX_THRESH, DEF_LMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0, + VAR_LMTP_PIX_DELAY, DEF_LMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0, + VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0, + VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0, + VAR_LMTP_CACHE_CONNT, DEF_LMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0, + VAR_LMTP_REUSE_TIME, DEF_LMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0, +#ifdef USE_TLS + VAR_LMTP_STARTTLS_TMOUT, DEF_LMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0, +#endif + VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0, + VAR_LMTP_SASL_AUTH_CACHE_TIME, DEF_LMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0, + 0, + }; + static const CONFIG_INT_TABLE lmtp_int_table[] = { + VAR_LMTP_LINE_LIMIT, DEF_LMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0, + VAR_LMTP_MXADDR_LIMIT, DEF_LMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0, + VAR_LMTP_MXSESS_LIMIT, DEF_LMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0, + VAR_LMTP_REUSE_COUNT, DEF_LMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0, +#ifdef USE_TLS + VAR_LMTP_TLS_SCERT_VD, DEF_LMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, +#endif + 0, + }; + static const CONFIG_BOOL_TABLE lmtp_bool_table[] = { + VAR_LMTP_SKIP_5XX, DEF_LMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting, + VAR_LMTP_SKIP_QUIT_RESP, DEF_LMTP_SKIP_QUIT_RESP, &var_skip_quit_resp, + VAR_LMTP_SASL_ENABLE, DEF_LMTP_SASL_ENABLE, &var_smtp_sasl_enable, + VAR_LMTP_RAND_ADDR, DEF_LMTP_RAND_ADDR, &var_smtp_rand_addr, + VAR_LMTP_QUOTE_821_ENV, DEF_LMTP_QUOTE_821_ENV, &var_smtp_quote_821_env, + VAR_LMTP_DEFER_MXADDR, DEF_LMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr, + VAR_LMTP_SEND_XFORWARD, DEF_LMTP_SEND_XFORWARD, &var_smtp_send_xforward, + VAR_LMTP_CACHE_DEMAND, DEF_LMTP_CACHE_DEMAND, &var_smtp_cache_demand, + VAR_LMTP_USE_TLS, DEF_LMTP_USE_TLS, &var_smtp_use_tls, + VAR_LMTP_ENFORCE_TLS, DEF_LMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_LMTP_TLS_CONN_REUSE, DEF_LMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse, +#ifdef USE_TLS + VAR_LMTP_TLS_ENFORCE_PN, DEF_LMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, + VAR_LMTP_TLS_NOTEOFFER, DEF_LMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, + VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, + VAR_LMTP_TLS_FORCE_TLSA, DEF_LMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, +#endif + VAR_LMTP_TLS_WRAPPER, DEF_LMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, + VAR_LMTP_SENDER_AUTH, DEF_LMTP_SENDER_AUTH, &var_smtp_sender_auth, + VAR_LMTP_CNAME_OVERR, DEF_LMTP_CNAME_OVERR, &var_smtp_cname_overr, + VAR_LMTP_SASL_AUTH_SOFT_BOUNCE, DEF_LMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce, + VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final, + VAR_LMTP_REC_DEADLINE, DEF_LMTP_REC_DEADLINE, &var_smtp_rec_deadline, + VAR_LMTP_DUMMY_MAIL_AUTH, DEF_LMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth, + VAR_LMTP_BALANCE_INET_PROTO, DEF_LMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto, + 0, + }; diff --git a/src/smtp/smtp-only b/src/smtp/smtp-only new file mode 100644 index 0000000..ee9288d --- /dev/null +++ b/src/smtp/smtp-only @@ -0,0 +1,4 @@ +_ALWAYS_EHLO +_NEVER_EHLO +_IGN_MX_LOOKUP_ERR +_INSECURE_MX_POLICY diff --git a/src/smtp/smtp.c b/src/smtp/smtp.c new file mode 100644 index 0000000..db17578 --- /dev/null +++ b/src/smtp/smtp.c @@ -0,0 +1,1430 @@ +/*++ +/* NAME +/* smtp 8 +/* SUMMARY +/* Postfix SMTP+LMTP client +/* SYNOPSIS +/* \fBsmtp\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The Postfix SMTP+LMTP client implements the SMTP and LMTP mail +/* delivery protocols. It processes message delivery requests from +/* the queue manager. Each request specifies a queue file, a sender +/* address, a domain or host to deliver to, and recipient information. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* The SMTP+LMTP client updates the queue file and marks recipients +/* as finished, or it informs the queue manager that delivery should +/* be tried again at a later time. Delivery status reports are sent +/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as +/* appropriate. +/* +/* The SMTP+LMTP client looks up a list of mail exchanger addresses for +/* the destination host, sorts the list by preference, and connects +/* to each listed address until it finds a server that responds. +/* +/* When a server is not reachable, or when mail delivery fails due +/* to a recoverable error condition, the SMTP+LMTP client will try to +/* deliver the mail to an alternate host. +/* +/* After a successful mail transaction, a connection may be saved +/* to the \fBscache\fR(8) connection cache server, so that it +/* may be used by any SMTP+LMTP client for a subsequent transaction. +/* +/* By default, connection caching is enabled temporarily for +/* destinations that have a high volume of mail in the active +/* queue. Connection caching can be enabled permanently for +/* specific destinations. +/* SMTP DESTINATION SYNTAX +/* .ad +/* .fi +/* SMTP destinations have the following form: +/* .IP \fIdomainname\fR +/* .IP \fIdomainname\fR:\fIport\fR +/* Look up the mail exchangers for the specified domain, and +/* connect to the specified port (default: \fBsmtp\fR). +/* .IP [\fIhostname\fR] +/* .IP [\fIhostname\fR]:\fIport\fR +/* Look up the address(es) of the specified host, and connect to +/* the specified port (default: \fBsmtp\fR). +/* .IP [\fIaddress\fR] +/* .IP [\fIaddress\fR]:\fIport\fR +/* Connect to the host at the specified address, and connect +/* to the specified port (default: \fBsmtp\fR). An IPv6 address +/* must be formatted as [\fBipv6\fR:\fIaddress\fR]. +/* LMTP DESTINATION SYNTAX +/* .ad +/* .fi +/* LMTP destinations have the following form: +/* .IP \fBunix\fR:\fIpathname\fR +/* Connect to the local UNIX-domain server that is bound to the specified +/* \fIpathname\fR. If the process runs chrooted, an absolute pathname +/* is interpreted relative to the Postfix queue directory. +/* .IP \fBinet\fR:\fIhostname\fR +/* .IP \fBinet\fR:\fIhostname\fR:\fIport\fR +/* .IP \fBinet\fR:[\fIaddress\fR] +/* .IP \fBinet\fR:[\fIaddress\fR]:\fIport\fR +/* Connect to the specified TCP port on the specified local or +/* remote host. If no port is specified, connect to the port defined as +/* \fBlmtp\fR in \fBservices\fR(4). +/* If no such service is found, the \fBlmtp_tcp_port\fR configuration +/* parameter (default value of 24) will be used. +/* An IPv6 address must be formatted as [\fBipv6\fR:\fIaddress\fR]. +/* .PP +/* SECURITY +/* .ad +/* .fi +/* The SMTP+LMTP client is moderately security-sensitive. It +/* talks to SMTP or LMTP servers and to DNS servers on the +/* network. The SMTP+LMTP client can be run chrooted at fixed +/* low privilege. +/* STANDARDS +/* RFC 821 (SMTP protocol) +/* RFC 822 (ARPA Internet Text Messages) +/* RFC 1651 (SMTP service extensions) +/* RFC 1652 (8bit-MIME transport) +/* RFC 1870 (Message Size Declaration) +/* RFC 2033 (LMTP protocol) +/* RFC 2034 (SMTP Enhanced Error Codes) +/* RFC 2045 (MIME: Format of Internet Message Bodies) +/* RFC 2046 (MIME: Media Types) +/* RFC 2554 (AUTH command) +/* RFC 2821 (SMTP protocol) +/* RFC 2920 (SMTP Pipelining) +/* RFC 3207 (STARTTLS command) +/* RFC 3461 (SMTP DSN Extension) +/* RFC 3463 (Enhanced Status Codes) +/* RFC 4954 (AUTH command) +/* RFC 5321 (SMTP protocol) +/* RFC 6531 (Internationalized SMTP) +/* RFC 6533 (Internationalized Delivery Status Notifications) +/* RFC 7672 (SMTP security via opportunistic DANE TLS) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* Corrupted message files are marked so that the queue manager can +/* move them to the \fBcorrupt\fR queue for further inspection. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces, protocol problems, and of +/* other trouble. +/* BUGS +/* SMTP and LMTP connection reuse for TLS (without closing the +/* SMTP or LMTP connection) is not supported before Postfix 3.4. +/* +/* SMTP and LMTP connection caching assumes that SASL credentials +/* are valid for all destinations that map onto the same IP +/* address and TCP port. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Before Postfix version 2.3, the LMTP client is a separate +/* program that implements only a subset of the functionality +/* available with SMTP: there is no support for TLS, and +/* connections are cached in-process, making it ineffective +/* when the client is used for multiple domains. +/* +/* Most smtp_\fIxxx\fR configuration parameters have an +/* lmtp_\fIxxx\fR "mirror" parameter for the equivalent LMTP +/* feature. This document describes only those LMTP-related +/* parameters that aren't simply "mirror" parameters. +/* +/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtp\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBignore_mx_lookup_error (no)\fR" +/* Ignore DNS MX lookups that produce no response. +/* .IP "\fBsmtp_always_send_ehlo (yes)\fR" +/* Always send EHLO at the start of an SMTP session. +/* .IP "\fBsmtp_never_send_ehlo (no)\fR" +/* Never send EHLO at the start of an SMTP session. +/* .IP "\fBsmtp_defer_if_no_mx_address_found (no)\fR" +/* Defer mail delivery when no MX record resolves to an IP address. +/* .IP "\fBsmtp_line_length_limit (998)\fR" +/* The maximal length of message header and body lines that Postfix +/* will send via SMTP. +/* .IP "\fBsmtp_pix_workaround_delay_time (10s)\fR" +/* How long the Postfix SMTP client pauses before sending +/* ".<CR><LF>" in order to work around the PIX firewall +/* "<CR><LF>.<CR><LF>" bug. +/* .IP "\fBsmtp_pix_workaround_threshold_time (500s)\fR" +/* How long a message must be queued before the Postfix SMTP client +/* turns on the PIX firewall "<CR><LF>.<CR><LF>" +/* bug workaround for delivery through firewalls with "smtp fixup" +/* mode turned on. +/* .IP "\fBsmtp_pix_workarounds (disable_esmtp, delay_dotcrlf)\fR" +/* A list that specifies zero or more workarounds for CISCO PIX +/* firewall bugs. +/* .IP "\fBsmtp_pix_workaround_maps (empty)\fR" +/* Lookup tables, indexed by the remote SMTP server address, with +/* per-destination workarounds for CISCO PIX firewall bugs. +/* .IP "\fBsmtp_quote_rfc821_envelope (yes)\fR" +/* Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO commands +/* as required +/* by RFC 5321. +/* .IP "\fBsmtp_reply_filter (empty)\fR" +/* A mechanism to transform replies from remote SMTP servers one +/* line at a time. +/* .IP "\fBsmtp_skip_5xx_greeting (yes)\fR" +/* Skip remote SMTP servers that greet with a 5XX status code. +/* .IP "\fBsmtp_skip_quit_response (yes)\fR" +/* Do not wait for the response to the SMTP QUIT command. +/* .PP +/* Available in Postfix version 2.0 and earlier: +/* .IP "\fBsmtp_skip_4xx_greeting (yes)\fR" +/* Skip SMTP servers that greet with a 4XX status code (go away, try +/* again later). +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtp_discard_ehlo_keyword_address_maps (empty)\fR" +/* Lookup tables, indexed by the remote SMTP server address, with +/* case insensitive lists of EHLO keywords (pipelining, starttls, auth, +/* etc.) that the Postfix SMTP client will ignore in the EHLO response from a +/* remote SMTP server. +/* .IP "\fBsmtp_discard_ehlo_keywords (empty)\fR" +/* A case insensitive list of EHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix SMTP client will ignore in the EHLO +/* response from a remote SMTP server. +/* .IP "\fBsmtp_generic_maps (empty)\fR" +/* Optional lookup tables that perform address rewriting in the +/* Postfix SMTP client, typically to transform a locally valid address into +/* a globally valid address when sending mail across the Internet. +/* .PP +/* Available in Postfix version 2.2.9 and later: +/* .IP "\fBsmtp_cname_overrides_servername (version dependent)\fR" +/* When the remote SMTP servername is a DNS CNAME, replace the +/* servername with the result from CNAME expansion for the purpose of +/* logging, SASL password lookup, TLS +/* policy decisions, or TLS certificate verification. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBlmtp_discard_lhlo_keyword_address_maps (empty)\fR" +/* Lookup tables, indexed by the remote LMTP server address, with +/* case insensitive lists of LHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO +/* response +/* from a remote LMTP server. +/* .IP "\fBlmtp_discard_lhlo_keywords (empty)\fR" +/* A case insensitive list of LHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO +/* response +/* from a remote LMTP server. +/* .PP +/* Available in Postfix version 2.4.4 and later: +/* .IP "\fBsend_cyrus_sasl_authzid (no)\fR" +/* When authenticating to a remote SMTP or LMTP server with the +/* default setting "no", send no SASL authoriZation ID (authzid); send +/* only the SASL authentiCation ID (authcid) plus the authcid's password. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtp_header_checks (empty)\fR" +/* Restricted \fBheader_checks\fR(5) tables for the Postfix SMTP client. +/* .IP "\fBsmtp_mime_header_checks (empty)\fR" +/* Restricted \fBmime_header_checks\fR(5) tables for the Postfix SMTP +/* client. +/* .IP "\fBsmtp_nested_header_checks (empty)\fR" +/* Restricted \fBnested_header_checks\fR(5) tables for the Postfix SMTP +/* client. +/* .IP "\fBsmtp_body_checks (empty)\fR" +/* Restricted \fBbody_checks\fR(5) tables for the Postfix SMTP client. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBtcp_windowsize (0)\fR" +/* An optional workaround for routers that break TCP window scaling. +/* .PP +/* Available in Postfix version 2.8 and later: +/* .IP "\fBsmtp_dns_resolver_options (empty)\fR" +/* DNS Resolver options for the Postfix SMTP client. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtp_per_record_deadline (no)\fR" +/* Change the behavior of the smtp_*_timeout time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR" +/* Whether or not to append the "AUTH=<>" option to the MAIL +/* FROM command in SASL-authenticated SMTP sessions. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_dns_support_level (empty)\fR" +/* Level of DNS support in the Postfix SMTP client. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtp_delivery_status_filter ($default_delivery_status_filter)\fR" +/* Optional filter for the \fBsmtp\fR(8) delivery agent to change the +/* delivery status code or explanatory text of successful or unsuccessful +/* deliveries. +/* .IP "\fBsmtp_dns_reply_filter (empty)\fR" +/* Optional filter for Postfix SMTP client DNS lookup results. +/* .PP +/* Available in Postfix version 3.3 and later: +/* .IP "\fBsmtp_balance_inet_protocols (yes)\fR" +/* When a remote destination resolves to a combination of IPv4 and +/* IPv6 addresses, ensure that the Postfix SMTP client can try both +/* address types before it runs into the smtp_mx_address_limit. +/* .PP +/* Available in Postfix 3.4.19 and later: +/* .IP "\fBdnssec_probe (ns:.)\fR" +/* The DNS query type (default: "ns") and DNS query name (default: +/* ".") that Postfix may use to determine whether DNSSEC validation +/* is available. +/* MIME PROCESSING CONTROLS +/* .ad +/* .fi +/* Available in Postfix version 2.0 and later: +/* .IP "\fBdisable_mime_output_conversion (no)\fR" +/* Disable the conversion of 8BITMIME format to 7BIT format. +/* .IP "\fBmime_boundary_length_limit (2048)\fR" +/* The maximal length of MIME multipart boundary strings. +/* .IP "\fBmime_nesting_limit (100)\fR" +/* The maximal recursion level that the MIME processor will handle. +/* EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtp_send_xforward_command (no)\fR" +/* Send the non-standard XFORWARD command when the Postfix SMTP server +/* EHLO response announces XFORWARD support. +/* SASL AUTHENTICATION CONTROLS +/* .ad +/* .fi +/* .IP "\fBsmtp_sasl_auth_enable (no)\fR" +/* Enable SASL authentication in the Postfix SMTP client. +/* .IP "\fBsmtp_sasl_password_maps (empty)\fR" +/* Optional Postfix SMTP client lookup tables with one username:password +/* entry per sender, remote hostname or next-hop domain. +/* .IP "\fBsmtp_sasl_security_options (noplaintext, noanonymous)\fR" +/* Postfix SMTP client SASL security options; as of Postfix 2.3 +/* the list of available +/* features depends on the SASL client implementation that is selected +/* with \fBsmtp_sasl_type\fR. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtp_sasl_mechanism_filter (empty)\fR" +/* If non-empty, a Postfix SMTP client filter for the remote SMTP +/* server's list of offered SASL mechanisms. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtp_sender_dependent_authentication (no)\fR" +/* Enable sender-dependent authentication in the Postfix SMTP client; this is +/* available only with SASL authentication, and disables SMTP connection +/* caching to ensure that mail from different senders will use the +/* appropriate credentials. +/* .IP "\fBsmtp_sasl_path (empty)\fR" +/* Implementation-specific information that the Postfix SMTP client +/* passes through to +/* the SASL plug-in implementation that is selected with +/* \fBsmtp_sasl_type\fR. +/* .IP "\fBsmtp_sasl_type (cyrus)\fR" +/* The SASL plug-in type that the Postfix SMTP client should use +/* for authentication. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtp_sasl_auth_cache_name (empty)\fR" +/* An optional table to prevent repeated SASL authentication +/* failures with the same remote SMTP server hostname, username and +/* password. +/* .IP "\fBsmtp_sasl_auth_cache_time (90d)\fR" +/* The maximal age of an smtp_sasl_auth_cache_name entry before it +/* is removed. +/* .IP "\fBsmtp_sasl_auth_soft_bounce (yes)\fR" +/* When a remote SMTP server rejects a SASL authentication request +/* with a 535 reply code, defer mail delivery instead of returning +/* mail as undeliverable. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR" +/* Whether or not to append the "AUTH=<>" option to the MAIL +/* FROM command in SASL-authenticated SMTP sessions. +/* STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* Detailed information about STARTTLS configuration may be found +/* in the TLS_README document. +/* .IP "\fBsmtp_tls_security_level (empty)\fR" +/* The default SMTP TLS security level for the Postfix SMTP client; +/* when a non-empty value is specified, this overrides the obsolete +/* parameters smtp_use_tls, smtp_enforce_tls, and smtp_tls_enforce_peername. +/* .IP "\fBsmtp_sasl_tls_security_options ($smtp_sasl_security_options)\fR" +/* The SASL authentication security options that the Postfix SMTP +/* client uses for TLS encrypted SMTP sessions. +/* .IP "\fBsmtp_starttls_timeout (300s)\fR" +/* Time limit for Postfix SMTP client write and read operations +/* during TLS startup and shutdown handshake procedures. +/* .IP "\fBsmtp_tls_CAfile (empty)\fR" +/* A file containing CA certificates of root CAs trusted to sign +/* either remote SMTP server certificates or intermediate CA certificates. +/* .IP "\fBsmtp_tls_CApath (empty)\fR" +/* Directory with PEM format Certification Authority certificates +/* that the Postfix SMTP client uses to verify a remote SMTP server +/* certificate. +/* .IP "\fBsmtp_tls_cert_file (empty)\fR" +/* File with the Postfix SMTP client RSA certificate in PEM format. +/* .IP "\fBsmtp_tls_mandatory_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP client will +/* use with +/* mandatory TLS encryption. +/* .IP "\fBsmtp_tls_exclude_ciphers (empty)\fR" +/* List of ciphers or cipher types to exclude from the Postfix +/* SMTP client cipher +/* list at all TLS security levels. +/* .IP "\fBsmtp_tls_mandatory_exclude_ciphers (empty)\fR" +/* Additional list of ciphers or cipher types to exclude from the +/* Postfix SMTP client cipher list at mandatory TLS security levels. +/* .IP "\fBsmtp_tls_dcert_file (empty)\fR" +/* File with the Postfix SMTP client DSA certificate in PEM format. +/* .IP "\fBsmtp_tls_dkey_file ($smtp_tls_dcert_file)\fR" +/* File with the Postfix SMTP client DSA private key in PEM format. +/* .IP "\fBsmtp_tls_key_file ($smtp_tls_cert_file)\fR" +/* File with the Postfix SMTP client RSA private key in PEM format. +/* .IP "\fBsmtp_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP client logging of TLS activity. +/* .IP "\fBsmtp_tls_note_starttls_offer (no)\fR" +/* Log the hostname of a remote SMTP server that offers STARTTLS, +/* when TLS is not already enabled for that server. +/* .IP "\fBsmtp_tls_policy_maps (empty)\fR" +/* Optional lookup tables with the Postfix SMTP client TLS security +/* policy by next-hop destination; when a non-empty value is specified, +/* this overrides the obsolete smtp_tls_per_site parameter. +/* .IP "\fBsmtp_tls_mandatory_protocols (!SSLv2, !SSLv3)\fR" +/* List of SSL/TLS protocols that the Postfix SMTP client will use with +/* mandatory TLS encryption. +/* .IP "\fBsmtp_tls_scert_verifydepth (9)\fR" +/* The verification depth for remote SMTP server certificates. +/* .IP "\fBsmtp_tls_secure_cert_match (nexthop, dot-nexthop)\fR" +/* How the Postfix SMTP client verifies the server certificate +/* peername for the "secure" TLS security level. +/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR" +/* Name of the file containing the optional Postfix SMTP client +/* TLS session cache. +/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR" +/* The expiration time of Postfix SMTP client TLS session cache +/* information. +/* .IP "\fBsmtp_tls_verify_cert_match (hostname)\fR" +/* How the Postfix SMTP client verifies the server certificate +/* peername for the +/* "verify" TLS security level. +/* .IP "\fBtls_daemon_random_bytes (32)\fR" +/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8) +/* process requests from the \fBtlsmgr\fR(8) server in order to seed its +/* internal pseudo random number generator (PRNG). +/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "high" grade ciphers. +/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "medium" or higher grade ciphers. +/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "low" or higher grade ciphers. +/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "export" or higher grade ciphers. +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" +/* The OpenSSL cipherlist for "NULL" grade ciphers that provide +/* authentication without encryption. +/* .PP +/* Available in Postfix version 2.4 and later: +/* .IP "\fBsmtp_sasl_tls_verified_security_options ($smtp_sasl_tls_security_options)\fR" +/* The SASL authentication security options that the Postfix SMTP +/* client uses for TLS encrypted SMTP sessions with a verified server +/* certificate. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtp_tls_fingerprint_cert_match (empty)\fR" +/* List of acceptable remote SMTP server certificate fingerprints for +/* the "fingerprint" TLS security level (\fBsmtp_tls_security_level\fR = +/* fingerprint). +/* .IP "\fBsmtp_tls_fingerprint_digest (md5)\fR" +/* The message digest algorithm used to construct remote SMTP server +/* certificate fingerprints. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBsmtp_tls_protocols (!SSLv2, !SSLv3)\fR" +/* List of TLS protocols that the Postfix SMTP client will exclude or +/* include with opportunistic TLS encryption. +/* .IP "\fBsmtp_tls_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP client +/* will use with opportunistic TLS encryption. +/* .IP "\fBsmtp_tls_eccert_file (empty)\fR" +/* File with the Postfix SMTP client ECDSA certificate in PEM format. +/* .IP "\fBsmtp_tls_eckey_file ($smtp_tls_eccert_file)\fR" +/* File with the Postfix SMTP client ECDSA private key in PEM format. +/* .PP +/* Available in Postfix version 2.7 and later: +/* .IP "\fBsmtp_tls_block_early_mail_reply (no)\fR" +/* Try to detect a mail hijacking attack based on a TLS protocol +/* vulnerability (CVE-2009-3555), where an attacker prepends malicious +/* HELO, MAIL, RCPT, DATA commands to a Postfix SMTP client TLS session. +/* .PP +/* Available in Postfix version 2.8 and later: +/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" +/* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .PP +/* Available in Postfix version 2.11-3.1: +/* .IP "\fBtls_dane_digest_agility (on)\fR" +/* Configure RFC7671 DANE TLSA digest algorithm agility. +/* .IP "\fBtls_dane_trust_anchor_digest_enable (yes)\fR" +/* Enable support for RFC 6698 (DANE TLSA) DNS records that contain +/* digests of trust-anchors with certificate usage "2". +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_tls_trust_anchor_file (empty)\fR" +/* Zero or more PEM-format files with trust-anchor certificates +/* and/or public keys. +/* .IP "\fBsmtp_tls_force_insecure_host_tlsa_lookup (no)\fR" +/* Lookup the associated DANE TLSA RRset even when a hostname is +/* not an alias and its address records lie in an unsigned zone. +/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" +/* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtp_tls_wrappermode (no)\fR" +/* Request that the Postfix SMTP client connects using the +/* legacy SMTPS protocol instead of using the STARTTLS command. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBsmtp_tls_dane_insecure_mx_policy (dane)\fR" +/* The TLS policy for MX hosts with "secure" TLSA records when the +/* nexthop destination security level is \fBdane\fR, but the MX +/* record was found via an "insecure" MX lookup. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtp_tls_connection_reuse (no)\fR" +/* Try to make multiple deliveries per TLS-encrypted connection. +/* .IP "\fBsmtp_tls_chain_files (empty)\fR" +/* List of one or more PEM files, each holding one or more private keys +/* directly followed by a corresponding certificate chain. +/* .IP "\fBsmtp_tls_servername (empty)\fR" +/* Optional name to send to the remote SMTP server in the TLS Server +/* Name Indication (SNI) extension. +/* .PP +/* Introduced with Postfix 3.4.6, 3.3.5, 3.2.10, and 3.1.13: +/* .IP "\fBtls_fast_shutdown_enable (yes)\fR" +/* A workaround for implementations that hang Postfix while shuting +/* down a TLS session, until Postfix times out. +/* OBSOLETE STARTTLS CONTROLS +/* .ad +/* .fi +/* The following configuration parameters exist for compatibility +/* with Postfix versions before 2.3. Support for these will +/* be removed in a future release. +/* .IP "\fBsmtp_use_tls (no)\fR" +/* Opportunistic mode: use TLS when a remote SMTP server announces +/* STARTTLS support, otherwise send the mail in the clear. +/* .IP "\fBsmtp_enforce_tls (no)\fR" +/* Enforcement mode: require that remote SMTP servers use TLS +/* encryption, and never send mail in the clear. +/* .IP "\fBsmtp_tls_enforce_peername (yes)\fR" +/* With mandatory TLS encryption, require that the remote SMTP +/* server hostname matches the information in the remote SMTP server +/* certificate. +/* .IP "\fBsmtp_tls_per_site (empty)\fR" +/* Optional lookup tables with the Postfix SMTP client TLS usage +/* policy by next-hop destination and by remote SMTP server hostname. +/* .IP "\fBsmtp_tls_cipherlist (empty)\fR" +/* Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS +/* cipher list. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBsmtp_connect_timeout (30s)\fR" +/* The Postfix SMTP client time limit for completing a TCP connection, or +/* zero (use the operating system built-in time limit). +/* .IP "\fBsmtp_helo_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the HELO or EHLO command, +/* and for receiving the initial remote SMTP server response. +/* .IP "\fBlmtp_lhlo_timeout (300s)\fR" +/* The Postfix LMTP client time limit for sending the LHLO command, +/* and for receiving the initial remote LMTP server response. +/* .IP "\fBsmtp_xforward_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the XFORWARD command, +/* and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_mail_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the MAIL FROM command, +/* and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_rcpt_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP RCPT TO +/* command, and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_data_init_timeout (120s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP DATA command, +/* and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_data_xfer_timeout (180s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP message content. +/* .IP "\fBsmtp_data_done_timeout (600s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP ".", and +/* for receiving the remote SMTP server response. +/* .IP "\fBsmtp_quit_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the QUIT command, +/* and for receiving the remote SMTP server response. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtp_mx_address_limit (5)\fR" +/* The maximal number of MX (mail exchanger) IP addresses that can +/* result from Postfix SMTP client mail exchanger lookups, or zero (no +/* limit). +/* .IP "\fBsmtp_mx_session_limit (2)\fR" +/* The maximal number of SMTP sessions per delivery request before +/* the Postfix SMTP client +/* gives up or delivers to a fall-back relay host, or zero (no +/* limit). +/* .IP "\fBsmtp_rset_timeout (20s)\fR" +/* The Postfix SMTP client time limit for sending the RSET command, +/* and for receiving the remote SMTP server response. +/* .PP +/* Available in Postfix version 2.2 and earlier: +/* .IP "\fBlmtp_cache_connection (yes)\fR" +/* Keep Postfix LMTP client connections open for up to $max_idle +/* seconds. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtp_connection_cache_destinations (empty)\fR" +/* Permanently enable SMTP connection caching for the specified +/* destinations. +/* .IP "\fBsmtp_connection_cache_on_demand (yes)\fR" +/* Temporarily enable SMTP connection caching while a destination +/* has a high volume of mail in the active queue. +/* .IP "\fBsmtp_connection_reuse_time_limit (300s)\fR" +/* The amount of time during which Postfix will use an SMTP +/* connection repeatedly. +/* .IP "\fBsmtp_connection_cache_time_limit (2s)\fR" +/* When SMTP connection caching is enabled, the amount of time that +/* an unused SMTP client socket is kept open before it is closed. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBconnection_cache_protocol_timeout (5s)\fR" +/* Time limit for connection cache connect, send or receive +/* operations. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtp_per_record_deadline (no)\fR" +/* Change the behavior of the smtp_*_timeout time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_connection_reuse_count_limit (0)\fR" +/* When SMTP connection caching is enabled, the number of times +/* that an SMTP session may be reused before it is closed, or zero (no +/* limit). +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtp_tls_connection_reuse (no)\fR" +/* Try to make multiple deliveries per TLS-encrypted connection. +/* .PP +/* Implemented in the qmgr(8) daemon: +/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR" +/* A transport-specific override for the +/* default_destination_recipient_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* SMTPUTF8 CONTROLS +/* .ad +/* .fi +/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. +/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR" +/* Detect that a message requires SMTPUTF8 support for the specified +/* mail origin classes. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBenable_idna2003_compatibility (no)\fR" +/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008, +/* when converting UTF-8 domain names to/from the ASCII form that is +/* used for DNS lookups. +/* TROUBLE SHOOTING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdebug_peer_level (2)\fR" +/* The increment in verbose logging level when a remote client or +/* server matches a pattern in the debug_peer_list parameter. +/* .IP "\fBdebug_peer_list (empty)\fR" +/* Optional list of remote client or server hostname or network +/* address patterns that cause the verbose logging level to increase +/* by the amount specified in $debug_peer_level. +/* .IP "\fBerror_notice_recipient (postmaster)\fR" +/* The recipient of postmaster notifications about mail delivery +/* problems that are caused by policy, resource, software or protocol +/* errors. +/* .IP "\fBinternal_mail_filter_classes (empty)\fR" +/* What categories of Postfix-generated mail are subject to +/* before-queue content inspection by non_smtpd_milters, header_checks +/* and body_checks. +/* .IP "\fBnotify_classes (resource, software)\fR" +/* The list of error classes that are reported to the postmaster. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBbest_mx_transport (empty)\fR" +/* Where the Postfix SMTP client should deliver mail when it detects +/* a "mail loops back to myself" error condition. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBdisable_dns_lookups (no)\fR" +/* Disable DNS lookups in the Postfix SMTP and LMTP clients. +/* .IP "\fBinet_interfaces (all)\fR" +/* The network interface addresses that this mail system receives +/* mail on. +/* .IP "\fBinet_protocols (all)\fR" +/* The Internet protocols Postfix will attempt to use when making +/* or accepting connections. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBlmtp_assume_final (no)\fR" +/* When a remote LMTP server announces no DSN support, assume that +/* the +/* server performs final delivery, and send "delivered" delivery status +/* notifications instead of "relayed". +/* .IP "\fBlmtp_tcp_port (24)\fR" +/* The default TCP port that the Postfix LMTP client connects to. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBproxy_interfaces (empty)\fR" +/* The network interface addresses that this mail system receives mail +/* on by way of a proxy or network address translation unit. +/* .IP "\fBsmtp_address_preference (any)\fR" +/* The address type ("ipv6", "ipv4" or "any") that the Postfix +/* SMTP client will try first, when a destination has IPv6 and IPv4 +/* addresses with equal MX preference. +/* .IP "\fBsmtp_bind_address (empty)\fR" +/* An optional numerical network address that the Postfix SMTP client +/* should bind to when making an IPv4 connection. +/* .IP "\fBsmtp_bind_address6 (empty)\fR" +/* An optional numerical network address that the Postfix SMTP client +/* should bind to when making an IPv6 connection. +/* .IP "\fBsmtp_helo_name ($myhostname)\fR" +/* The hostname to send in the SMTP HELO or EHLO command. +/* .IP "\fBlmtp_lhlo_name ($myhostname)\fR" +/* The hostname to send in the LMTP LHLO command. +/* .IP "\fBsmtp_host_lookup (dns)\fR" +/* What mechanisms the Postfix SMTP client uses to look up a host's +/* IP address. +/* .IP "\fBsmtp_randomize_addresses (yes)\fR" +/* Randomize the order of equal-preference MX host addresses. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available with Postfix 2.2 and earlier: +/* .IP "\fBfallback_relay (empty)\fR" +/* Optional list of relay hosts for SMTP destinations that can't be +/* found or that are unreachable. +/* .PP +/* Available with Postfix 2.3 and later: +/* .IP "\fBsmtp_fallback_relay ($fallback_relay)\fR" +/* Optional list of relay hosts for SMTP destinations that can't be +/* found or that are unreachable. +/* .PP +/* Available with Postfix 3.0 and later: +/* .IP "\fBsmtp_address_verify_target (rcpt)\fR" +/* In the context of email address verification, the SMTP protocol +/* stage that determines whether an email address is deliverable. +/* .PP +/* Available with Postfix 3.1 and later: +/* .IP "\fBlmtp_fallback_relay (empty)\fR" +/* Optional list of relay hosts for LMTP destinations that can't be +/* found or that are unreachable. +/* .PP +/* Available with Postfix 3.2 and later: +/* .IP "\fBsmtp_tcp_port (smtp)\fR" +/* The default TCP port that the Postfix SMTP client connects to. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* generic(5), output address rewriting +/* header_checks(5), message header content inspection +/* body_checks(5), body parts content inspection +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* scache(8), connection cache server +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* tlsmgr(8), TLS session and PRNG management +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* SASL_README, Postfix SASL howto +/* TLS_README, Postfix STARTTLS howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Command pipelining in cooperation with: +/* Jon Ribbens +/* Oaktree Internet Solutions Ltd., +/* Internet House, +/* Canal Basin, +/* Coventry, +/* CV1 4LY, United Kingdom. +/* +/* SASL support originally by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Revised TLS and SMTP connection cache support by: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <dict.h> +#include <stringops.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <name_mask.h> +#include <name_code.h> + +/* Global library. */ + +#include <deliver_request.h> +#include <mail_proto.h> +#include <mail_params.h> +#include <mail_version.h> +#include <mail_conf.h> +#include <debug_peer.h> +#include <flush_clnt.h> +#include <scache.h> +#include <string_list.h> +#include <maps.h> +#include <ext_prop.h> + +/* DNS library. */ + +#include <dns.h> + +/* Single server skeleton. */ + +#include <mail_server.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + + /* + * Tunable parameters. These have compiled-in defaults that can be overruled + * by settings in the global Postfix configuration file. + */ +int var_smtp_conn_tmout; +int var_smtp_helo_tmout; +int var_smtp_xfwd_tmout; +int var_smtp_mail_tmout; +int var_smtp_rcpt_tmout; +int var_smtp_data0_tmout; +int var_smtp_data1_tmout; +int var_smtp_data2_tmout; +int var_smtp_rset_tmout; +int var_smtp_quit_tmout; +char *var_inet_interfaces; +char *var_notify_classes; +int var_smtp_skip_5xx_greeting; +int var_ign_mx_lookup_err; +int var_skip_quit_resp; +char *var_fallback_relay; +char *var_bestmx_transp; +char *var_error_rcpt; +int var_smtp_always_ehlo; +int var_smtp_never_ehlo; +char *var_smtp_sasl_opts; +char *var_smtp_sasl_path; +char *var_smtp_sasl_passwd; +bool var_smtp_sasl_enable; +char *var_smtp_sasl_mechs; +char *var_smtp_sasl_type; +char *var_smtp_bind_addr; +char *var_smtp_bind_addr6; +char *var_smtp_vrfy_tgt; +bool var_smtp_rand_addr; +int var_smtp_pix_thresh; +int var_queue_run_delay; +int var_min_backoff_time; +int var_smtp_pix_delay; +int var_smtp_line_limit; +char *var_smtp_helo_name; +char *var_smtp_host_lookup; +bool var_smtp_quote_821_env; +bool var_smtp_defer_mxaddr; +bool var_smtp_send_xforward; +int var_smtp_mxaddr_limit; +int var_smtp_mxsess_limit; +int var_smtp_cache_conn; +int var_smtp_reuse_time; +int var_smtp_reuse_count; +char *var_smtp_cache_dest; +char *var_scache_service; /* You can now leave this here. */ +bool var_smtp_cache_demand; +char *var_smtp_ehlo_dis_words; +char *var_smtp_ehlo_dis_maps; +char *var_smtp_addr_pref; + +char *var_smtp_tls_level; +bool var_smtp_use_tls; +bool var_smtp_enforce_tls; +char *var_smtp_tls_per_site; +char *var_smtp_tls_policy; +bool var_smtp_tls_wrappermode; +bool var_smtp_tls_conn_reuse; +char *var_tlsproxy_service; + +#ifdef USE_TLS +char *var_smtp_sasl_tls_opts; +char *var_smtp_sasl_tlsv_opts; +int var_smtp_starttls_tmout; +char *var_smtp_tls_CAfile; +char *var_smtp_tls_CApath; +char *var_smtp_tls_chain_files; +char *var_smtp_tls_cert_file; +char *var_smtp_tls_mand_ciph; +char *var_smtp_tls_excl_ciph; +char *var_smtp_tls_mand_excl; +char *var_smtp_tls_dcert_file; +char *var_smtp_tls_dkey_file; +bool var_smtp_tls_enforce_peername; +char *var_smtp_tls_key_file; +char *var_smtp_tls_loglevel; +bool var_smtp_tls_note_starttls_offer; +char *var_smtp_tls_mand_proto; +char *var_smtp_tls_sec_cmatch; +int var_smtp_tls_scert_vd; +char *var_smtp_tls_vfy_cmatch; +char *var_smtp_tls_fpt_cmatch; +char *var_smtp_tls_fpt_dgst; +char *var_smtp_tls_tafile; +char *var_smtp_tls_proto; +char *var_smtp_tls_ciph; +char *var_smtp_tls_eccert_file; +char *var_smtp_tls_eckey_file; +char *var_smtp_tls_sni; +bool var_smtp_tls_blk_early_mail_reply; +bool var_smtp_tls_force_tlsa; +char *var_smtp_tls_insecure_mx_policy; + +#endif + +char *var_smtp_generic_maps; +char *var_prop_extension; +bool var_smtp_sender_auth; +char *var_smtp_tcp_port; +int var_scache_proto_tmout; +bool var_smtp_cname_overr; +char *var_smtp_pix_bug_words; +char *var_smtp_pix_bug_maps; +char *var_cyrus_conf_path; +char *var_smtp_head_chks; +char *var_smtp_mime_chks; +char *var_smtp_nest_chks; +char *var_smtp_body_chks; +char *var_smtp_resp_filter; +bool var_lmtp_assume_final; +char *var_smtp_dns_res_opt; +char *var_smtp_dns_support; +bool var_smtp_rec_deadline; +bool var_smtp_dummy_mail_auth; +char *var_smtp_dsn_filter; +char *var_smtp_dns_re_filter; +bool var_smtp_balance_inet_proto; + + /* Special handling of 535 AUTH errors. */ +char *var_smtp_sasl_auth_cache_name; +int var_smtp_sasl_auth_cache_time; +bool var_smtp_sasl_auth_soft_bounce; + + /* + * Global variables. + */ +int smtp_mode; +int smtp_host_lookup_mask; +int smtp_dns_support; +STRING_LIST *smtp_cache_dest; +SCACHE *smtp_scache; +MAPS *smtp_ehlo_dis_maps; +MAPS *smtp_generic_maps; +int smtp_ext_prop_mask; +unsigned smtp_dns_res_opt; +MAPS *smtp_pix_bug_maps; +HBC_CHECKS *smtp_header_checks; /* limited header checks */ +HBC_CHECKS *smtp_body_checks; /* limited body checks */ + +#ifdef USE_TLS + + /* + * OpenSSL client state (opaque handle) + */ +TLS_APPL_STATE *smtp_tls_ctx; +int smtp_tls_insecure_mx_policy; + +#endif + + /* + * IPv6 preference. + */ +static int smtp_addr_pref; + +/* deliver_message - deliver message with extreme prejudice */ + +static int deliver_message(const char *service, DELIVER_REQUEST *request) +{ + SMTP_STATE *state; + int result; + + if (msg_verbose) + msg_info("deliver_message: from %s", request->sender); + + /* + * Sanity checks. The smtp server is unprivileged and chrooted, so we can + * afford to distribute the data censoring code, instead of having it all + * in one place. + */ + if (request->nexthop[0] == 0) + msg_fatal("empty nexthop hostname"); + if (request->rcpt_list.len <= 0) + msg_fatal("recipient count: %d", request->rcpt_list.len); + + /* + * Initialize. Bundle all information about the delivery request, so that + * we can produce understandable diagnostics when something goes wrong + * many levels below. The alternative would be to make everything global. + */ + state = smtp_state_alloc(); + state->request = request; + state->src = request->fp; + state->service = service; + state->misc_flags |= smtp_addr_pref; + SMTP_RCPT_INIT(state); + + /* + * Establish an SMTP session and deliver this message to all requested + * recipients. At the end, notify the postmaster of any protocol errors. + * Optionally deliver mail locally when this machine is the best mail + * exchanger. + */ + result = smtp_connect(state); + + /* + * Clean up. + */ + smtp_state_free(state); + + return (result); +} + +/* smtp_service - perform service for client */ + +static void smtp_service(VSTREAM *client_stream, char *service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to remote SMTP delivery service. What we see below is a + * little protocol to (1) tell the queue manager that we are ready, (2) + * read a request from the queue manager, and (3) report the completion + * status of that request. All connection-management stuff is handled by + * the common code in single_server.c. + */ + if ((request = deliver_request_read(client_stream)) != 0) { + status = deliver_message(service, request); + deliver_request_done(client_stream, request, status); + } +} + +/* post_init - post-jail initialization */ + +static void post_init(char *unused_name, char **unused_argv) +{ + static const NAME_MASK lookup_masks[] = { + SMTP_HOST_LOOKUP_DNS, SMTP_HOST_FLAG_DNS, + SMTP_HOST_LOOKUP_NATIVE, SMTP_HOST_FLAG_NATIVE, + 0, + }; + static const NAME_MASK dns_res_opt_masks[] = { + SMTP_DNS_RES_OPT_DEFNAMES, RES_DEFNAMES, + SMTP_DNS_RES_OPT_DNSRCH, RES_DNSRCH, + 0, + }; + static const NAME_CODE dns_support[] = { + SMTP_DNS_SUPPORT_DISABLED, SMTP_DNS_DISABLED, + SMTP_DNS_SUPPORT_ENABLED, SMTP_DNS_ENABLED, +#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0) + SMTP_DNS_SUPPORT_DNSSEC, SMTP_DNS_DNSSEC, +#endif + 0, SMTP_DNS_INVALID, + }; + + if (*var_smtp_dns_support == 0) { + /* Backwards compatible empty setting */ + smtp_dns_support = + var_disable_dns ? SMTP_DNS_DISABLED : SMTP_DNS_ENABLED; + } else { + smtp_dns_support = + name_code(dns_support, NAME_CODE_FLAG_NONE, var_smtp_dns_support); + if (smtp_dns_support == SMTP_DNS_INVALID) + msg_fatal("invalid %s: \"%s\"", VAR_LMTP_SMTP(DNS_SUPPORT), + var_smtp_dns_support); + var_disable_dns = (smtp_dns_support == SMTP_DNS_DISABLED); + } + +#ifdef USE_TLS + if (smtp_mode) { + smtp_tls_insecure_mx_policy = + tls_level_lookup(var_smtp_tls_insecure_mx_policy); + switch (smtp_tls_insecure_mx_policy) { + case TLS_LEV_MAY: + case TLS_LEV_ENCRYPT: + case TLS_LEV_DANE: + break; + default: + msg_fatal("invalid %s: \"%s\"", VAR_SMTP_TLS_INSECURE_MX_POLICY, + var_smtp_tls_insecure_mx_policy); + } + } +#endif + + /* + * Select hostname lookup mechanisms. + */ + if (smtp_dns_support == SMTP_DNS_DISABLED) + smtp_host_lookup_mask = SMTP_HOST_FLAG_NATIVE; + else + smtp_host_lookup_mask = + name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks, + var_smtp_host_lookup); + if (msg_verbose) + msg_info("host name lookup methods: %s", + str_name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks, + smtp_host_lookup_mask)); + + /* + * Session cache instance. + */ + if (*var_smtp_cache_dest || var_smtp_cache_demand) +#if 0 + smtp_scache = scache_multi_create(); +#else + smtp_scache = scache_clnt_create(var_scache_service, + var_scache_proto_tmout, + var_ipc_idle_limit, + var_ipc_ttl_limit); +#endif + + /* + * Select DNS query flags. + */ + smtp_dns_res_opt = name_mask(VAR_LMTP_SMTP(DNS_RES_OPT), dns_res_opt_masks, + var_smtp_dns_res_opt); + + /* + * Address verification. + */ + smtp_vrfy_init(); +} + +/* pre_init - pre-jail initialization */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + int use_tls; + static const NAME_CODE addr_pref_map[] = { + INET_PROTO_NAME_IPV6, SMTP_MISC_FLAG_PREF_IPV6, + INET_PROTO_NAME_IPV4, SMTP_MISC_FLAG_PREF_IPV4, + INET_PROTO_NAME_ANY, 0, + 0, -1, + }; + + /* + * Turn on per-peer debugging. + */ + debug_peer_init(); + + /* + * SASL initialization. + */ + if (var_smtp_sasl_enable) +#ifdef USE_SASL_AUTH + smtp_sasl_initialize(); +#else + msg_warn("%s is true, but SASL support is not compiled in", + VAR_LMTP_SMTP(SASL_ENABLE)); +#endif + + if (*var_smtp_tls_level != 0) + switch (tls_level_lookup(var_smtp_tls_level)) { + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_DANE_ONLY: + case TLS_LEV_FPRINT: + case TLS_LEV_ENCRYPT: + var_smtp_use_tls = var_smtp_enforce_tls = 1; + break; + case TLS_LEV_DANE: + case TLS_LEV_MAY: + var_smtp_use_tls = 1; + var_smtp_enforce_tls = 0; + break; + case TLS_LEV_NONE: + var_smtp_use_tls = var_smtp_enforce_tls = 0; + break; + default: + /* tls_level_lookup() logs no warning. */ + /* session_tls_init() assumes that var_smtp_tls_level is sane. */ + msg_fatal("Invalid TLS level \"%s\"", var_smtp_tls_level); + } + use_tls = (var_smtp_use_tls || var_smtp_enforce_tls); + + /* + * Initialize the TLS data before entering the chroot jail + */ + if (use_tls || var_smtp_tls_per_site[0] || var_smtp_tls_policy[0]) { +#ifdef USE_TLS + TLS_CLIENT_INIT_PROPS props; + + tls_pre_jail_init(TLS_ROLE_CLIENT); + + /* + * We get stronger type safety and a cleaner interface by combining + * the various parameters into a single tls_client_props structure. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + * + * With tlsproxy(8) turned on, this is still needed for DANE-related + * initializations. + */ + smtp_tls_ctx = + TLS_CLIENT_INIT(&props, + log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL), + log_level = var_smtp_tls_loglevel, + verifydepth = var_smtp_tls_scert_vd, + cache_type = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE), + chain_files = var_smtp_tls_chain_files, + cert_file = var_smtp_tls_cert_file, + key_file = var_smtp_tls_key_file, + dcert_file = var_smtp_tls_dcert_file, + dkey_file = var_smtp_tls_dkey_file, + eccert_file = var_smtp_tls_eccert_file, + eckey_file = var_smtp_tls_eckey_file, + CAfile = var_smtp_tls_CAfile, + CApath = var_smtp_tls_CApath, + mdalg = var_smtp_tls_fpt_dgst); + smtp_tls_list_init(); +#else + msg_warn("TLS has been selected, but TLS support is not compiled in"); +#endif + } + + /* + * Flush client. + */ + flush_init(); + + /* + * Session cache domain list. + */ + if (*var_smtp_cache_dest) + smtp_cache_dest = string_list_init(VAR_SMTP_CACHE_DEST, + MATCH_FLAG_RETURN, + var_smtp_cache_dest); + + /* + * EHLO keyword filter. + */ + if (*var_smtp_ehlo_dis_maps) + smtp_ehlo_dis_maps = maps_create(VAR_LMTP_SMTP(EHLO_DIS_MAPS), + var_smtp_ehlo_dis_maps, + DICT_FLAG_LOCK); + + /* + * PIX bug workarounds. + */ + if (*var_smtp_pix_bug_maps) + smtp_pix_bug_maps = maps_create(VAR_LMTP_SMTP(PIX_BUG_MAPS), + var_smtp_pix_bug_maps, + DICT_FLAG_LOCK); + + /* + * Generic maps. + */ + if (*var_prop_extension) + smtp_ext_prop_mask = + ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension); + if (*var_smtp_generic_maps) + smtp_generic_maps = + maps_create(VAR_LMTP_SMTP(GENERIC_MAPS), var_smtp_generic_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + + /* + * Header/body checks. + */ + smtp_header_checks = hbc_header_checks_create( + VAR_LMTP_SMTP(HEAD_CHKS), var_smtp_head_chks, + VAR_LMTP_SMTP(MIME_CHKS), var_smtp_mime_chks, + VAR_LMTP_SMTP(NEST_CHKS), var_smtp_nest_chks, + smtp_hbc_callbacks); + smtp_body_checks = hbc_body_checks_create( + VAR_LMTP_SMTP(BODY_CHKS), var_smtp_body_chks, + smtp_hbc_callbacks); + + /* + * Server reply filter. + */ + if (*var_smtp_resp_filter) + smtp_chat_resp_filter = + dict_open(var_smtp_resp_filter, O_RDONLY, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + + /* + * Address family preference. + */ + if (*var_smtp_addr_pref) { + smtp_addr_pref = name_code(addr_pref_map, NAME_CODE_FLAG_NONE, + var_smtp_addr_pref); + if (smtp_addr_pref < 0) + msg_fatal("bad %s value: %s", VAR_LMTP_SMTP(ADDR_PREF), + var_smtp_addr_pref); + } + + /* + * DNS reply filter. + */ + if (*var_smtp_dns_re_filter) + dns_rr_filter_compile(VAR_LMTP_SMTP(DNS_RE_FILTER), + var_smtp_dns_re_filter); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + char *sane_procname; + +#include "smtp_params.c" +#include "lmtp_params.c" + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * XXX At this point, var_procname etc. are not initialized. + * + * The process name, "smtp" or "lmtp", determines the protocol, the DSN + * server reply type, SASL service information lookup, and more. Prepare + * for the possibility there may be another personality. + */ + sane_procname = sane_basename((VSTRING *) 0, argv[0]); + if (strcmp(sane_procname, "smtp") == 0) + smtp_mode = 1; + else if (strcmp(sane_procname, "lmtp") == 0) + smtp_mode = 0; + else + msg_fatal("unexpected process name \"%s\" - " + "specify \"smtp\" or \"lmtp\"", var_procname); + + /* + * Initialize with the LMTP or SMTP parameter name space. + */ + single_server_main(argc, argv, smtp_service, + CA_MAIL_SERVER_TIME_TABLE(smtp_mode ? + smtp_time_table : lmtp_time_table), + CA_MAIL_SERVER_INT_TABLE(smtp_mode ? + smtp_int_table : lmtp_int_table), + CA_MAIL_SERVER_STR_TABLE(smtp_mode ? + smtp_str_table : lmtp_str_table), + CA_MAIL_SERVER_BOOL_TABLE(smtp_mode ? + smtp_bool_table : lmtp_bool_table), + CA_MAIL_SERVER_PRE_INIT(pre_init), + CA_MAIL_SERVER_POST_INIT(post_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_BOUNCE_INIT(VAR_SMTP_DSN_FILTER, + &var_smtp_dsn_filter), + 0); +} diff --git a/src/smtp/smtp.h b/src/smtp/smtp.h new file mode 100644 index 0000000..ed3e6a5 --- /dev/null +++ b/src/smtp/smtp.h @@ -0,0 +1,727 @@ +/*++ +/* NAME +/* smtp 3h +/* SUMMARY +/* smtp client program +/* SYNOPSIS +/* #include "smtp.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <string.h> + + /* + * Utility library. + */ +#include <vstream.h> +#include <vstring.h> +#include <argv.h> +#include <htable.h> +#include <dict.h> + + /* + * Global library. + */ +#include <deliver_request.h> +#include <scache.h> +#include <string_list.h> +#include <maps.h> +#include <tok822.h> +#include <dsn_buf.h> +#include <header_body_checks.h> + + /* + * Postfix TLS library. + */ +#include <tls.h> + + /* + * tlsproxy client. + */ +#include <tls_proxy.h> + + /* + * Global iterator support. This is updated by the connection-management + * loop, and contains dynamic context that appears in lookup keys for SASL + * passwords, TLS policy, cached SMTP connections, and cached TLS session + * keys. + * + * For consistency and maintainability, context that is used in more than one + * lookup key is formatted with smtp_key_format(). + */ +typedef struct SMTP_ITERATOR { + /* Public members. */ + VSTRING *request_nexthop; /* delivery request nexhop or empty */ + VSTRING *dest; /* current nexthop */ + VSTRING *host; /* hostname or empty */ + VSTRING *addr; /* printable address or empty */ + unsigned port; /* network byte order or null */ + struct DNS_RR *rr; /* DNS resource record or null */ + struct DNS_RR *mx; /* DNS resource record or null */ + /* Private members. */ + VSTRING *saved_dest; /* saved current nexthop */ + struct SMTP_STATE *parent; /* parent linkage */ +} SMTP_ITERATOR; + +#define SMTP_ITER_INIT(iter, _dest, _host, _addr, _port, state) do { \ + vstring_strcpy((iter)->dest, (_dest)); \ + vstring_strcpy((iter)->host, (_host)); \ + vstring_strcpy((iter)->addr, (_addr)); \ + (iter)->port = (_port); \ + (iter)->mx = (iter)->rr = 0; \ + vstring_strcpy((iter)->saved_dest, ""); \ + (iter)->parent = (state); \ + } while (0) + +#define SMTP_ITER_SAVE_DEST(iter) do { \ + vstring_strcpy((iter)->saved_dest, STR((iter)->dest)); \ + } while (0) + +#define SMTP_ITER_RESTORE_DEST(iter) do { \ + vstring_strcpy((iter)->dest, STR((iter)->saved_dest)); \ + } while (0) + + /* + * TLS Policy support. + */ +#ifdef USE_TLS + +typedef struct SMTP_TLS_POLICY { + int level; /* TLS enforcement level */ + char *protocols; /* Acceptable SSL protocols */ + char *grade; /* Cipher grade: "export", ... */ + VSTRING *exclusions; /* Excluded SSL ciphers */ + ARGV *matchargv; /* Cert match patterns */ + DSN_BUF *why; /* Lookup error status */ + TLS_DANE *dane; /* DANE TLSA digests */ + char *sni; /* Optional SNI name when not DANE */ + int conn_reuse; /* enable connection reuse */ +} SMTP_TLS_POLICY; + + /* + * smtp_tls_policy.c + */ +extern void smtp_tls_list_init(void); +extern int smtp_tls_policy_cache_query(DSN_BUF *, SMTP_TLS_POLICY *, SMTP_ITERATOR *); +extern void smtp_tls_policy_cache_flush(void); + + /* + * Macros must use distinct names for local temporary variables, otherwise + * there will be bugs due to shadowing. This happened when an earlier + * version of smtp_tls_policy_dummy() invoked smtp_tls_policy_init(), but it + * could also happen without macro nesting. + * + * General principle: use all or part of the macro name in each temporary + * variable name. Then, append suffixes to the names if needed. + */ +#define smtp_tls_policy_dummy(t) do { \ + SMTP_TLS_POLICY *_tls_policy_dummy_tmp = (t); \ + smtp_tls_policy_init(_tls_policy_dummy_tmp, (DSN_BUF *) 0); \ + _tls_policy_dummy_tmp->level = TLS_LEV_NONE; \ + } while (0) + + /* This macro is not part of the module external interface. */ +#define smtp_tls_policy_init(t, w) do { \ + SMTP_TLS_POLICY *_tls_policy_init_tmp = (t); \ + _tls_policy_init_tmp->protocols = 0; \ + _tls_policy_init_tmp->grade = 0; \ + _tls_policy_init_tmp->exclusions = 0; \ + _tls_policy_init_tmp->matchargv = 0; \ + _tls_policy_init_tmp->why = (w); \ + _tls_policy_init_tmp->dane = 0; \ + _tls_policy_init_tmp->sni = 0; \ + _tls_policy_init_tmp->conn_reuse = 0; \ + } while (0) + +#endif + + /* + * State information associated with each SMTP delivery request. + * Session-specific state is stored separately. + */ +typedef struct SMTP_STATE { + int misc_flags; /* processing flags, see below */ + VSTREAM *src; /* queue file stream */ + const char *service; /* transport name */ + DELIVER_REQUEST *request; /* envelope info, offsets */ + struct SMTP_SESSION *session; /* network connection */ + int status; /* delivery status */ + ssize_t space_left; /* output length control */ + + /* + * Global iterator. + */ + SMTP_ITERATOR iterator[1]; /* Usage: state->iterator->member */ + + /* + * Global iterator. + */ +#ifdef USE_TLS + SMTP_TLS_POLICY tls[1]; /* Usage: state->tls->member */ +#endif + + /* + * Connection cache support. + */ + HTABLE *cache_used; /* cached addresses that were used */ + VSTRING *dest_label; /* cached logical/physical binding */ + VSTRING *dest_prop; /* binding properties, passivated */ + VSTRING *endp_label; /* cached session physical endpoint */ + VSTRING *endp_prop; /* endpoint properties, passivated */ + + /* + * Flags and counters to control the handling of mail delivery errors. + * There is some redundancy for sanity checking. At the end of an SMTP + * session all recipients should be marked one way or the other. + */ + int rcpt_left; /* recipients left over */ + int rcpt_drop; /* recipients marked as drop */ + int rcpt_keep; /* recipients marked as keep */ + + /* + * DSN Support introduced major bloat in error processing. + */ + DSN_BUF *why; /* on-the-fly formatting buffer */ +} SMTP_STATE; + + /* + * Primitives to enable/disable/test connection caching and reuse based on + * the delivery request next-hop destination (i.e. not smtp_fallback_relay). + * + * Connection cache lookup by the delivery request next-hop destination allows + * a reuse request to skip over bad hosts, and may result in a connection to + * a fall-back relay. Once we have found a 'good' host for a delivery + * request next-hop, clear the delivery request next-hop destination, to + * avoid caching less-preferred connections under that same delivery request + * next-hop. + */ +#define SET_SCACHE_REQUEST_NEXTHOP(state, nexthop) do { \ + vstring_strcpy((state)->iterator->request_nexthop, nexthop); \ + } while (0) + +#define CLEAR_SCACHE_REQUEST_NEXTHOP(state) do { \ + STR((state)->iterator->request_nexthop)[0] = 0; \ + } while (0) + +#define HAVE_SCACHE_REQUEST_NEXTHOP(state) \ + (STR((state)->iterator->request_nexthop)[0] != 0) + + + /* + * Server features. + */ +#define SMTP_FEATURE_ESMTP (1<<0) +#define SMTP_FEATURE_8BITMIME (1<<1) +#define SMTP_FEATURE_PIPELINING (1<<2) +#define SMTP_FEATURE_SIZE (1<<3) +#define SMTP_FEATURE_STARTTLS (1<<4) +#define SMTP_FEATURE_AUTH (1<<5) +#define SMTP_FEATURE_XFORWARD_NAME (1<<7) +#define SMTP_FEATURE_XFORWARD_ADDR (1<<8) +#define SMTP_FEATURE_XFORWARD_PROTO (1<<9) +#define SMTP_FEATURE_XFORWARD_HELO (1<<10) +#define SMTP_FEATURE_XFORWARD_DOMAIN (1<<11) +#define SMTP_FEATURE_BEST_MX (1<<12) /* for next-hop or fall-back */ +#define SMTP_FEATURE_RSET_REJECTED (1<<13) /* RSET probe rejected */ +#define SMTP_FEATURE_FROM_CACHE (1<<14) /* cached connection */ +#define SMTP_FEATURE_DSN (1<<15) /* DSN supported */ +#define SMTP_FEATURE_PIX_NO_ESMTP (1<<16) /* PIX smtp fixup mode */ +#define SMTP_FEATURE_PIX_DELAY_DOTCRLF (1<<17) /* PIX smtp fixup mode */ +#define SMTP_FEATURE_XFORWARD_PORT (1<<18) +#define SMTP_FEATURE_EARLY_TLS_MAIL_REPLY (1<<19) /* CVE-2009-3555 */ +#define SMTP_FEATURE_XFORWARD_IDENT (1<<20) +#define SMTP_FEATURE_SMTPUTF8 (1<<21) /* RFC 6531 */ +#define SMTP_FEATURE_FROM_PROXY (1<<22) /* proxied connection */ + + /* + * Features that passivate under the endpoint. + */ +#define SMTP_FEATURE_ENDPOINT_MASK \ + (~(SMTP_FEATURE_BEST_MX | SMTP_FEATURE_RSET_REJECTED \ + | SMTP_FEATURE_FROM_CACHE)) + + /* + * Features that passivate under the logical destination. + */ +#define SMTP_FEATURE_DESTINATION_MASK (SMTP_FEATURE_BEST_MX) + + /* + * Misc flags. + */ +#define SMTP_MISC_FLAG_LOOP_DETECT (1<<0) +#define SMTP_MISC_FLAG_IN_STARTTLS (1<<1) +#define SMTP_MISC_FLAG_FIRST_NEXTHOP (1<<2) +#define SMTP_MISC_FLAG_FINAL_NEXTHOP (1<<3) +#define SMTP_MISC_FLAG_FINAL_SERVER (1<<4) +#define SMTP_MISC_FLAG_CONN_LOAD (1<<5) +#define SMTP_MISC_FLAG_CONN_STORE (1<<6) +#define SMTP_MISC_FLAG_COMPLETE_SESSION (1<<7) +#define SMTP_MISC_FLAG_PREF_IPV6 (1<<8) +#define SMTP_MISC_FLAG_PREF_IPV4 (1<<9) + +#define SMTP_MISC_FLAG_CONN_CACHE_MASK \ + (SMTP_MISC_FLAG_CONN_LOAD | SMTP_MISC_FLAG_CONN_STORE) + + /* + * smtp.c + */ +#define SMTP_HAS_DSN(why) (STR((why)->status)[0] != 0) +#define SMTP_HAS_SOFT_DSN(why) (STR((why)->status)[0] == '4') +#define SMTP_HAS_HARD_DSN(why) (STR((why)->status)[0] == '5') +#define SMTP_HAS_LOOP_DSN(why) \ + (SMTP_HAS_DSN(why) && strcmp(STR((why)->status) + 1, ".4.6") == 0) + +#define SMTP_SET_SOFT_DSN(why) (STR((why)->status)[0] = '4') +#define SMTP_SET_HARD_DSN(why) (STR((why)->status)[0] = '5') + +extern int smtp_host_lookup_mask; /* host lookup methods to use */ + +#define SMTP_HOST_FLAG_DNS (1<<0) +#define SMTP_HOST_FLAG_NATIVE (1<<1) + +extern int smtp_dns_support; /* dns support level */ + +#define SMTP_DNS_INVALID (-1) /* smtp_dns_support_level = <bogus> */ +#define SMTP_DNS_DISABLED 0 /* smtp_dns_support_level = disabled */ +#define SMTP_DNS_ENABLED 1 /* smtp_dns_support_level = enabled */ +#define SMTP_DNS_DNSSEC 2 /* smtp_dns_support_level = dnssec */ + +extern SCACHE *smtp_scache; /* connection cache instance */ +extern STRING_LIST *smtp_cache_dest; /* cached destinations */ + +extern MAPS *smtp_ehlo_dis_maps; /* ehlo keyword filter */ + +extern MAPS *smtp_pix_bug_maps; /* PIX workarounds */ + +extern MAPS *smtp_generic_maps; /* make internal address valid */ +extern int smtp_ext_prop_mask; /* address externsion propagation */ +extern unsigned smtp_dns_res_opt; /* DNS query flags */ + +#ifdef USE_TLS + +extern TLS_APPL_STATE *smtp_tls_ctx; /* client-side TLS engine */ +extern int smtp_tls_insecure_mx_policy; /* DANE post insecure MX? */ + +#endif + +extern HBC_CHECKS *smtp_header_checks; /* limited header checks */ +extern HBC_CHECKS *smtp_body_checks; /* limited body checks */ + + /* + * smtp_session.c + */ + +typedef struct SMTP_SESSION { + VSTREAM *stream; /* network connection */ + SMTP_ITERATOR *iterator; /* dest, host, addr, port */ + char *namaddr; /* mail exchanger */ + char *helo; /* helo response */ + unsigned port; /* network byte order */ + char *namaddrport; /* mail exchanger, incl. port */ + + VSTRING *buffer; /* I/O buffer */ + VSTRING *scratch; /* scratch buffer */ + VSTRING *scratch2; /* scratch buffer */ + + int features; /* server features */ + off_t size_limit; /* server limit or unknown */ + + ARGV *history; /* transaction log */ + int error_mask; /* error classes */ + struct MIME_STATE *mime_state; /* mime state machine */ + + int send_proto_helo; /* XFORWARD support */ + + time_t expire_time; /* session reuse expiration time */ + int reuse_count; /* # of times reused (for logging) */ + int forbidden; /* No further I/O allowed */ + +#ifdef USE_SASL_AUTH + char *sasl_mechanism_list; /* server mechanism list */ + char *sasl_username; /* client username */ + char *sasl_passwd; /* client password */ + struct XSASL_CLIENT *sasl_client; /* SASL internal state */ + VSTRING *sasl_reply; /* client response */ +#endif + + /* + * TLS related state, don't forget to initialize in session_tls_init()! + */ +#ifdef USE_TLS + TLS_SESS_STATE *tls_context; /* TLS library session state */ + char *tls_nexthop; /* Nexthop domain for cert checks */ + int tls_retry_plain; /* Try plain when TLS handshake fails */ +#endif + + SMTP_STATE *state; /* back link */ +} SMTP_SESSION; + +extern SMTP_SESSION *smtp_session_alloc(VSTREAM *, SMTP_ITERATOR *, time_t, int); +extern void smtp_session_new_stream(SMTP_SESSION *, VSTREAM *, time_t, int); +extern int smtp_sess_plaintext_ok(SMTP_ITERATOR *, int); +extern void smtp_session_free(SMTP_SESSION *); +extern int smtp_session_passivate(SMTP_SESSION *, VSTRING *, VSTRING *); +extern SMTP_SESSION *smtp_session_activate(int, SMTP_ITERATOR *, VSTRING *, VSTRING *); + + /* + * What's in a name? + */ +#define SMTP_HNAME(rr) (var_smtp_cname_overr ? (rr)->rname : (rr)->qname) + + /* + * smtp_connect.c + */ +extern int smtp_connect(SMTP_STATE *); + + /* + * smtp_proto.c + */ +extern void smtp_vrfy_init(void); +extern int smtp_helo(SMTP_STATE *); +extern int smtp_xfer(SMTP_STATE *); +extern int smtp_rset(SMTP_STATE *); +extern int smtp_quit(SMTP_STATE *); + +extern HBC_CALL_BACKS smtp_hbc_callbacks[]; + + /* + * A connection is re-usable if session->expire_time is > 0 and the + * expiration time has not been reached. This is subtle because the timer + * can expire between sending a command and receiving the reply for that + * command. + * + * But wait, there is more! When SMTP command pipelining is enabled, there are + * two protocol loops that execute at very different times: one loop that + * generates commands, and one loop that receives replies to those commands. + * These will be called "sender loop" and "receiver loop", respectively. At + * well-defined protocol synchronization points, the sender loop pauses to + * let the receiver loop catch up. + * + * When we choose to reuse a connection, both the sender and receiver protocol + * loops end with "." (mail delivery) or "RSET" (address probe). When we + * choose not to reuse, both the sender and receiver protocol loops end with + * "QUIT". The problem is that we must make the same protocol choices in + * both the sender and receiver loops, even though those loops may execute + * at completely different times. + * + * We "freeze" the choice in the sender loop, just before we generate "." or + * "RSET". The reader loop leaves the connection cacheable even if the timer + * expires by the time the response arrives. The connection cleanup code + * will call smtp_quit() for connections with an expired cache expiration + * timer. + * + * We could have made the programmer's life a lot simpler by not making a + * choice at all, and always leaving it up to the connection cleanup code to + * call smtp_quit() for connections with an expired cache expiration timer. + * + * As a general principle, neither the sender loop nor the receiver loop must + * modify the connection caching state, if that can affect the receiver + * state machine for not-yet processed replies to already-generated + * commands. This restriction does not apply when we have to exit the + * protocol loops prematurely due to e.g., timeout or connection loss, so + * that those pending replies will never be received. + * + * But wait, there is even more! Only the first good connection for a specific + * destination may be cached under both the next-hop destination name and + * the server address; connections to alternate servers must be cached under + * the server address alone. This means we must distinguish between bad + * connections and other reasons why connections cannot be cached. + */ +#define THIS_SESSION_IS_CACHED \ + (!THIS_SESSION_IS_FORBIDDEN && session->expire_time > 0) + +#define THIS_SESSION_IS_EXPIRED \ + (THIS_SESSION_IS_CACHED \ + && (session->expire_time < vstream_ftime(session->stream) \ + || (var_smtp_reuse_count > 0 \ + && session->reuse_count >= var_smtp_reuse_count))) + +#define THIS_SESSION_IS_THROTTLED \ + (!THIS_SESSION_IS_FORBIDDEN && session->expire_time < 0) + +#define THIS_SESSION_IS_FORBIDDEN \ + (session->forbidden != 0) + + /* Bring the bad news. */ + +#define DONT_CACHE_THIS_SESSION \ + (session->expire_time = 0) + +#define DONT_CACHE_THROTTLED_SESSION \ + (session->expire_time = -1) + +#define DONT_USE_FORBIDDEN_SESSION \ + (session->forbidden = 1) + + /* Initialization. */ + +#define USE_NEWBORN_SESSION \ + (session->forbidden = 0) + +#define CACHE_THIS_SESSION_UNTIL(when) \ + (session->expire_time = (when)) + + /* + * Encapsulate the following so that we don't expose details of of + * connection management and error handling to the SMTP protocol engine. + */ +#ifdef USE_SASL_AUTH +#define HAVE_SASL_CREDENTIALS \ + (var_smtp_sasl_enable \ + && *var_smtp_sasl_passwd \ + && smtp_sasl_passwd_lookup(session)) +#else +#define HAVE_SASL_CREDENTIALS (0) +#endif + +#define PREACTIVE_DELAY \ + (session->state->request->msg_stats.active_arrival.tv_sec - \ + session->state->request->msg_stats.incoming_arrival.tv_sec) + +#define PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE \ + (session->tls_context == 0 \ + && state->tls->level == TLS_LEV_MAY \ + && PREACTIVE_DELAY >= var_min_backoff_time \ + && !HAVE_SASL_CREDENTIALS) + +#define PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE \ + (session->tls_context != 0 \ + && SMTP_RCPT_LEFT(state) > SMTP_RCPT_MARK_COUNT(state) \ + && state->tls->level == TLS_LEV_MAY \ + && PREACTIVE_DELAY >= var_min_backoff_time \ + && !HAVE_SASL_CREDENTIALS) + + /* + * XXX The following will not retry recipients that were deferred while the + * SMTP_MISC_FLAG_FINAL_SERVER flag was already set. This includes the case + * when TLS fails in the middle of a delivery. + */ +#define RETRY_AS_PLAINTEXT do { \ + session->tls_retry_plain = 1; \ + state->misc_flags &= ~SMTP_MISC_FLAG_FINAL_SERVER; \ + } while (0) + + /* + * smtp_chat.c + */ +typedef struct SMTP_RESP { /* server response */ + int code; /* SMTP code */ + const char *dsn; /* enhanced status */ + char *str; /* full reply */ + VSTRING *dsn_buf; /* status buffer */ + VSTRING *str_buf; /* reply buffer */ +} SMTP_RESP; + +extern void PRINTFLIKE(2, 3) smtp_chat_cmd(SMTP_SESSION *, const char *,...); +extern DICT *smtp_chat_resp_filter; +extern SMTP_RESP *smtp_chat_resp(SMTP_SESSION *); +extern void smtp_chat_init(SMTP_SESSION *); +extern void smtp_chat_reset(SMTP_SESSION *); +extern void smtp_chat_notify(SMTP_SESSION *); + +#define SMTP_RESP_FAKE(resp, _dsn) \ + ((resp)->code = 0, \ + (resp)->dsn = (_dsn), \ + (resp)->str = DSN_BY_LOCAL_MTA, \ + (resp)) + +#define DSN_BY_LOCAL_MTA ((char *) 0) /* DSN issued by local MTA */ + +#define SMTP_RESP_SET_DSN(resp, _dsn) do { \ + vstring_strcpy((resp)->dsn_buf, (_dsn)); \ + (resp)->dsn = STR((resp)->dsn_buf); \ + } while (0) + + /* + * These operations implement a redundant mark-and-sweep algorithm that + * explicitly accounts for the fate of every recipient. The interface is + * documented in smtp_rcpt.c, which also implements the sweeping. The + * smtp_trouble.c module does most of the marking after failure. + * + * When a delivery fails or succeeds, take one of the following actions: + * + * - Mark the recipient as KEEP (deliver to alternate MTA) and do not update + * the delivery request status. + * + * - Mark the recipient as DROP (remove from delivery request), log whether + * delivery succeeded or failed, delete the recipient from the queue file + * and/or update defer or bounce logfiles, and update the delivery request + * status. + * + * At the end of a delivery attempt, all recipients must be marked one way or + * the other. Failure to do so will trigger a panic. + */ +#define SMTP_RCPT_STATE_KEEP 1 /* send to backup host */ +#define SMTP_RCPT_STATE_DROP 2 /* remove from request */ +#define SMTP_RCPT_INIT(state) do { \ + (state)->rcpt_drop = (state)->rcpt_keep = 0; \ + (state)->rcpt_left = state->request->rcpt_list.len; \ + } while (0) + +#define SMTP_RCPT_DROP(state, rcpt) do { \ + (rcpt)->u.status = SMTP_RCPT_STATE_DROP; (state)->rcpt_drop++; \ + } while (0) + +#define SMTP_RCPT_KEEP(state, rcpt) do { \ + (rcpt)->u.status = SMTP_RCPT_STATE_KEEP; (state)->rcpt_keep++; \ + } while (0) + +#define SMTP_RCPT_ISMARKED(rcpt) ((rcpt)->u.status != 0) + +#define SMTP_RCPT_LEFT(state) (state)->rcpt_left + +#define SMTP_RCPT_MARK_COUNT(state) ((state)->rcpt_drop + (state)->rcpt_keep) + +extern void smtp_rcpt_cleanup(SMTP_STATE *); +extern void smtp_rcpt_done(SMTP_STATE *, SMTP_RESP *, RECIPIENT *); + + /* + * smtp_trouble.c + */ +#define SMTP_THROTTLE 1 +#define SMTP_NOTHROTTLE 0 +extern int smtp_sess_fail(SMTP_STATE *); +extern int PRINTFLIKE(5, 6) smtp_misc_fail(SMTP_STATE *, int, const char *, + SMTP_RESP *, const char *,...); +extern void PRINTFLIKE(5, 6) smtp_rcpt_fail(SMTP_STATE *, RECIPIENT *, + const char *, SMTP_RESP *, + const char *,...); +extern int smtp_stream_except(SMTP_STATE *, int, const char *); + +#define smtp_site_fail(state, mta, resp, ...) \ + smtp_misc_fail((state), SMTP_THROTTLE, (mta), (resp), __VA_ARGS__) +#define smtp_mesg_fail(state, mta, resp, ...) \ + smtp_misc_fail((state), SMTP_NOTHROTTLE, (mta), (resp), __VA_ARGS__) + + /* + * smtp_unalias.c + */ +extern const char *smtp_unalias_name(const char *); +extern VSTRING *smtp_unalias_addr(VSTRING *, const char *); + + /* + * smtp_state.c + */ +extern SMTP_STATE *smtp_state_alloc(void); +extern void smtp_state_free(SMTP_STATE *); + + /* + * smtp_map11.c + */ +extern int smtp_map11_external(VSTRING *, MAPS *, int); +extern int smtp_map11_tree(TOK822 *, MAPS *, int); +extern int smtp_map11_internal(VSTRING *, MAPS *, int); + + /* + * smtp_key.c + */ +char *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int); + +#define SMTP_KEY_FLAG_SERVICE (1<<0) /* service name */ +#define SMTP_KEY_FLAG_SENDER (1<<1) /* sender address */ +#define SMTP_KEY_FLAG_REQ_NEXTHOP (1<<2) /* delivery request nexthop */ +#define SMTP_KEY_FLAG_CUR_NEXTHOP (1<<3) /* current nexthop */ +#define SMTP_KEY_FLAG_HOSTNAME (1<<4) /* remote host name */ +#define SMTP_KEY_FLAG_ADDR (1<<5) /* remote address */ +#define SMTP_KEY_FLAG_PORT (1<<6) /* remote port */ +#define SMTP_KEY_FLAG_TLS_LEVEL (1<<7) /* requested TLS level */ + +#define SMTP_KEY_MASK_ALL \ + (SMTP_KEY_FLAG_SERVICE | SMTP_KEY_FLAG_SENDER | \ + SMTP_KEY_FLAG_REQ_NEXTHOP | \ + SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_HOSTNAME | \ + SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL) + + /* + * Conditional lookup-key flags for cached connections that may be + * SASL-authenticated with a per-{sender, nexthop, or hostname} credential. + * Each bit corresponds to one type of smtp_sasl_password_file lookup key, + * and is turned on only when the corresponding main.cf parameter is turned + * on. + */ +#define COND_SASL_SMTP_KEY_FLAG_SENDER \ + ((var_smtp_sender_auth && *var_smtp_sasl_passwd) ? \ + SMTP_KEY_FLAG_SENDER : 0) + +#define COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \ + (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0) + +#ifdef USE_TLS +#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \ + (TLS_MUST_MATCH(state->tls->level) ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0) +#else +#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \ + (0) +#endif + +#define COND_SASL_SMTP_KEY_FLAG_HOSTNAME \ + (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_HOSTNAME : 0) + + /* + * Connection-cache destination lookup key, based on the delivery request + * nexthop. The SENDER attribute is a proxy for sender-dependent SASL + * credentials (or absence thereof), and prevents false connection sharing + * when different SASL credentials may be required for different deliveries + * to the same domain and port. Likewise, the delivery request nexthop + * (REQ_NEXTHOP) prevents false sharing of TLS identities (the destination + * key links only to appropriate endpoint lookup keys). The SERVICE + * attribute is a proxy for all request-independent configuration details. + */ +#define SMTP_KEY_MASK_SCACHE_DEST_LABEL \ + (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \ + | SMTP_KEY_FLAG_REQ_NEXTHOP) + + /* + * Connection-cache endpoint lookup key. The SENDER, CUR_NEXTHOP, HOSTNAME, + * PORT and TLS_LEVEL attributes are proxies for SASL credentials and TLS + * authentication (or absence thereof), and prevent false connection sharing + * when different SASL credentials or TLS identities may be required for + * different deliveries to the same IP address and port. The SERVICE + * attribute is a proxy for all request-independent configuration details. + */ +#define SMTP_KEY_MASK_SCACHE_ENDP_LABEL \ + (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \ + | COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \ + | COND_SASL_SMTP_KEY_FLAG_HOSTNAME \ + | COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_ADDR | \ + SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL) + + /* + * Silly little macros. + */ +#define STR(s) vstring_str(s) +#define LEN(s) VSTRING_LEN(s) + +extern int smtp_mode; + +#define VAR_LMTP_SMTP(x) (smtp_mode ? VAR_SMTP_##x : VAR_LMTP_##x) +#define LMTP_SMTP_SUFFIX(x) (smtp_mode ? x##_SMTP : x##_LMTP) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ diff --git a/src/smtp/smtp_addr.c b/src/smtp/smtp_addr.c new file mode 100644 index 0000000..2210ff7 --- /dev/null +++ b/src/smtp/smtp_addr.c @@ -0,0 +1,693 @@ +/*++ +/* NAME +/* smtp_addr 3 +/* SUMMARY +/* SMTP server address lookup +/* SYNOPSIS +/* #include "smtp_addr.h" +/* +/* DNS_RR *smtp_domain_addr(name, mxrr, misc_flags, why, found_myself) +/* char *name; +/* DNS_RR **mxrr; +/* int misc_flags; +/* DSN_BUF *why; +/* int *found_myself; +/* +/* DNS_RR *smtp_host_addr(name, misc_flags, why) +/* char *name; +/* int misc_flags; +/* DSN_BUF *why; +/* DESCRIPTION +/* This module implements Internet address lookups. By default, +/* lookups are done via the Internet domain name service (DNS). +/* A reasonable number of CNAME indirections is permitted. When +/* DNS lookups are disabled, host address lookup is done with +/* getnameinfo() or gethostbyname(). +/* +/* smtp_domain_addr() looks up the network addresses for mail +/* exchanger hosts listed for the named domain. Addresses are +/* returned in most-preferred first order. The result is truncated +/* so that it contains only hosts that are more preferred than the +/* local mail server itself. The found_myself result parameter +/* is updated when the local MTA is MX host for the specified +/* destination. If MX records were found, the rname, qname, +/* and dnssec validation status of the MX RRset are returned +/* via mxrr, which the caller must free with dns_rr_free(). +/* +/* When no mail exchanger is listed in the DNS for \fIname\fR, the +/* request is passed to smtp_host_addr(). +/* +/* It is an error to call smtp_domain_addr() when DNS lookups are +/* disabled. +/* +/* smtp_host_addr() looks up all addresses listed for the named +/* host. The host can be specified as a numerical Internet network +/* address, or as a symbolic host name. +/* +/* Results from smtp_domain_addr() or smtp_host_addr() are +/* destroyed by dns_rr_free(), including null lists. +/* DIAGNOSTICS +/* Panics: interface violations. For example, calling smtp_domain_addr() +/* when DNS lookups are explicitly disabled. +/* +/* All routines either return a DNS_RR pointer, or return a null +/* pointer and update the \fIwhy\fR argument accordingly. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdlib.h> +#include <netdb.h> +#include <ctype.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <mymalloc.h> +#include <inet_addr_list.h> +#include <stringops.h> +#include <myaddrinfo.h> +#include <inet_proto.h> +#include <midna_domain.h> + +/* Global library. */ + +#include <mail_params.h> +#include <own_inet_addr.h> +#include <dsn_buf.h> + +/* DNS library. */ + +#include <dns.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_addr.h" + +/* smtp_print_addr - print address list */ + +static void smtp_print_addr(const char *what, DNS_RR *addr_list) +{ + DNS_RR *addr; + MAI_HOSTADDR_STR hostaddr; + + msg_info("begin %s address list", what); + for (addr = addr_list; addr; addr = addr->next) { + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("skipping record type %s: %m", dns_strtype(addr->type)); + } else { + msg_info("pref %4d host %s/%s", + addr->pref, SMTP_HNAME(addr), + hostaddr.buf); + } + } + msg_info("end %s address list", what); +} + +/* smtp_addr_one - address lookup for one host name */ + +static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt, + unsigned pref, DSN_BUF *why) +{ + const char *myname = "smtp_addr_one"; + DNS_RR *addr = 0; + DNS_RR *rr; + int aierr; + struct addrinfo *res0; + struct addrinfo *res; + INET_PROTO_INFO *proto_info = inet_proto_info(); + unsigned char *proto_family_list = proto_info->sa_family_list; + int found; + + if (msg_verbose) + msg_info("%s: host %s", myname, host); + + /* + * Interpret a numerical name as an address. + */ + if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0) { + if (strchr((char *) proto_family_list, res0->ai_family) != 0) { + if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0) + msg_fatal("host %s: conversion error for address family " + "%d: %m", host, res0->ai_addr->sa_family); + addr_list = dns_rr_append(addr_list, addr); + freeaddrinfo(res0); + return (addr_list); + } + freeaddrinfo(res0); + } + + /* + * Use DNS lookup, but keep the option open to use native name service. + * + * XXX A soft error dominates past and future hard errors. Therefore we + * should not clobber a soft error text and status code. + */ + if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) { + res_opt |= smtp_dns_res_opt; + switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0, + why->reason, DNS_REQ_FLAG_NONE, + proto_info->dns_atype_list)) { + case DNS_OK: + for (rr = addr; rr; rr = rr->next) + rr->pref = pref; + addr_list = dns_rr_append(addr_list, addr); + return (addr_list); + default: + dsb_status(why, "4.4.3"); + return (addr_list); + case DNS_FAIL: + dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3"); + return (addr_list); + case DNS_INVAL: + dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); + return (addr_list); + case DNS_POLICY: + dsb_status(why, "4.7.0"); + return (addr_list); + case DNS_NOTFOUND: + dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); + /* maybe native naming service will succeed */ + break; + } + } + + /* + * Use the native name service which also looks in /etc/hosts. + * + * XXX A soft error dominates past and future hard errors. Therefore we + * should not clobber a soft error text and status code. + */ +#define RETRY_AI_ERROR(e) \ + ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) +#ifdef EAI_NODATA +#define DSN_NOHOST(e) \ + ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME) +#else +#define DSN_NOHOST(e) \ + ((e) == EAI_AGAIN || (e) == EAI_NONAME) +#endif + + if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) { + if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) { + dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ? + (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") : + (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"), + "unable to look up host %s: %s", + host, MAI_STRERROR(aierr)); + } else { + for (found = 0, res = res0; res != 0; res = res->ai_next) { + if (strchr((char *) proto_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, host); + continue; + } + found++; + if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0) + msg_fatal("host %s: conversion error for address family " + "%d: %m", host, res0->ai_addr->sa_family); + addr_list = dns_rr_append(addr_list, addr); + } + freeaddrinfo(res0); + if (found == 0) { + dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4", + "%s: host not found", host); + } + return (addr_list); + } + } + + /* + * No further alternatives for host lookup. + */ + return (addr_list); +} + +/* smtp_addr_list - address lookup for a list of mail exchangers */ + +static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *why) +{ + DNS_RR *addr_list = 0; + DNS_RR *rr; + int res_opt = 0; + + if (mx_names->dnssec_valid) + res_opt = RES_USE_DNSSEC; +#ifdef USE_TLS + else if (smtp_tls_insecure_mx_policy > TLS_LEV_MAY) + res_opt = RES_USE_DNSSEC; +#endif + + /* + * As long as we are able to look up any host address, we ignore problems + * with DNS lookups (except if we're backup MX, and all the better MX + * hosts can't be found). + * + * XXX 2821: update the error status (0->FAIL upon unrecoverable lookup + * error, any->RETRY upon temporary lookup error) so that we can + * correctly handle the case of no resolvable MX host. Currently this is + * always treated as a soft error. RFC 2821 wants a more precise + * response. + * + * XXX dns_lookup() enables RES_DEFNAMES. This is wrong for names found in + * MX records - we should not append the local domain to dot-less names. + * + * XXX However, this is not the only problem. If we use the native name + * service for host lookup, then it will usually enable RES_DNSRCH which + * appends local domain information to all lookups. In particular, + * getaddrinfo() may invoke a resolver that runs in a different process + * (NIS server, nscd), so we can't even reliably turn this off by + * tweaking the in-process resolver flags. + */ + for (rr = mx_names; rr; rr = rr->next) { + if (rr->type != T_MX) + msg_panic("smtp_addr_list: bad resource type: %d", rr->type); + addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt, + rr->pref, why); + } + return (addr_list); +} + +/* smtp_find_self - spot myself in a crowd of mail exchangers */ + +static DNS_RR *smtp_find_self(DNS_RR *addr_list) +{ + const char *myname = "smtp_find_self"; + INET_ADDR_LIST *self; + INET_ADDR_LIST *proxy; + DNS_RR *addr; + int i; + + self = own_inet_addr_list(); + proxy = proxy_inet_addr_list(); + + for (addr = addr_list; addr; addr = addr->next) { + + /* + * Find out if this mail system is listening on this address. + */ + for (i = 0; i < self->used; i++) + if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) { + if (msg_verbose) + msg_info("%s: found self at pref %d", myname, addr->pref); + return (addr); + } + + /* + * Find out if this mail system has a proxy listening on this + * address. + */ + for (i = 0; i < proxy->used; i++) + if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) { + if (msg_verbose) + msg_info("%s: found proxy at pref %d", myname, addr->pref); + return (addr); + } + } + + /* + * Didn't find myself, or my proxy. + */ + if (msg_verbose) + msg_info("%s: not found", myname); + return (0); +} + +/* smtp_truncate_self - truncate address list at self and equivalents */ + +static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref) +{ + DNS_RR *addr; + DNS_RR *last; + + for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) { + if (pref == addr->pref) { + if (msg_verbose) + smtp_print_addr("truncated", addr); + dns_rr_free(addr); + if (last == 0) { + addr_list = 0; + } else { + last->next = 0; + } + break; + } + } + return (addr_list); +} + +/* smtp_balance_inet_proto - balance IPv4/6 protocols within address limit */ + +static DNS_RR *smtp_balance_inet_proto(DNS_RR *addr_list, int misc_flags, + int addr_limit) +{ + const char myname[] = "smtp_balance_inet_proto"; + DNS_RR *rr; + DNS_RR *stripped_list; + DNS_RR *next; + int v6_count; + int v4_count; + int v6_target, + v4_target; + int *p; + + /* + * Precondition: the input is sorted by MX preference (not necessarily IP + * address family preference), and addresses with the same or worse + * preference than 'myself' have been eliminated. Postcondition: the + * relative list order is unchanged, but some elements are removed. + */ + + /* + * Count the number of IPv6 and IPv4 addresses. + */ + for (v4_count = v6_count = 0, rr = addr_list; rr != 0; rr = rr->next) { + if (rr->type == T_A) { + v4_count++; + } else if (rr->type == T_AAAA) { + v6_count++; + } else { + msg_panic("%s: unexpected record type: %s", + myname, dns_strtype(rr->type)); + } + } + + /* + * Ensure that one address type will not out-crowd the other, while + * enforcing the address count limit. This works around a current problem + * where some destination announces primarily IPv6 MX addresses, the + * smtp_address_limit eliminates most or all IPv4 addresses, and the + * destination is not reachable over IPv6. + * + * Maybe: do all smtp_mx_address_limit enforcement here, and remove + * pre-existing enforcement elsewhere. That would obsolete the + * smtp_balance_inet_protocols configuration parameter. + */ + if (v4_count > 0 && v6_count > 0 && v4_count + v6_count > addr_limit) { + + /*- + * Decide how many IPv6 and IPv4 addresses to keep. The code below + * has three branches, corresponding to the regions R1, R2 and R3 + * in the figure. + * + * L = addr_limit + * X = excluded by condition (v4_count + v6_count > addr_limit) + * + * v4_count + * ^ + * | + * L \ R1 + * |X\ | + * |XXX\ | + * |XXXXX\ | R2 + * L/2 +-------\------- + * |XXXXXXX|X\ + * |XXXXXXX|XXX\ R3 + * |XXXXXXX|XXXXX\ + * 0 +-------+-------\--> v6_count + * 0 L/2 L + */ + if (v6_count <= addr_limit / 2) { /* Region R1 */ + v6_target = v6_count; + v4_target = addr_limit - v6_target; + } else if (v4_count <= addr_limit / 2) {/* Region R3 */ + v4_target = v4_count; + v6_target = addr_limit - v4_target; + } else { /* Region R2 */ + /* v4_count > addr_limit / 2 && v6_count > addr_limit / 2 */ + v4_target = (addr_limit + (addr_list->type == T_A)) / 2; + v6_target = addr_limit - v4_target; + } + if (msg_verbose) + msg_info("v6_target=%d, v4_target=%d", v6_target, v4_target); + + /* Enforce the address count targets. */ + stripped_list = 0; + for (rr = addr_list; rr != 0; rr = next) { + next = rr->next; + rr->next = 0; + if (rr->type == T_A) { + p = &v4_target; + } else if (rr->type == T_AAAA) { + p = &v6_target; + } else { + msg_panic("%s: unexpected record type: %s", + myname, dns_strtype(rr->type)); + } + if (*p > 0) { + stripped_list = dns_rr_append(stripped_list, rr); + *p -= 1; + } else { + dns_rr_free(rr); + } + } + if (v4_target > 0 || v6_target > 0) + msg_panic("%s: bad target count: v4_target=%d, v6_target=%d", + myname, v4_target, v6_target); + if (msg_verbose) + smtp_print_addr("smtp_balance_inet_proto result", stripped_list); + return (stripped_list); + } else { + return (addr_list); + } +} + +/* smtp_domain_addr - mail exchanger address lookup */ + +DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags, + DSN_BUF *why, int *found_myself) +{ + DNS_RR *mx_names; + DNS_RR *addr_list = 0; + DNS_RR *self = 0; + unsigned best_pref; + unsigned best_found; + int r = 0; /* Resolver flags */ + const char *aname; + + dsb_reset(why); /* Paranoia */ + + /* + * Preferences from DNS use 0..32767, fall-backs use 32768+. + */ +#define IMPOSSIBLE_PREFERENCE (~0) + + /* + * Sanity check. + */ + if (smtp_dns_support == SMTP_DNS_DISABLED) + msg_panic("smtp_domain_addr: DNS lookup is disabled"); + if (smtp_dns_support == SMTP_DNS_DNSSEC) + r |= RES_USE_DNSSEC; + + /* + * IDNA support. + */ +#ifndef NO_EAI + if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", name, aname); + } else +#endif + aname = name; + + /* + * Look up the mail exchanger hosts listed for this name. Sort the + * results by preference. Look up the corresponding host addresses, and + * truncate the list so that it contains only hosts that are more + * preferred than myself. When no MX resource records exist, look up the + * addresses listed for this name. + * + * According to RFC 974: "It is possible that the list of MXs in the + * response to the query will be empty. This is a special case. If the + * list is empty, mailers should treat it as if it contained one RR, an + * MX RR with a preference value of 0, and a host name of REMOTE. (I.e., + * REMOTE is its only MX). In addition, the mailer should do no further + * processing on the list, but should attempt to deliver the message to + * REMOTE." + * + * Normally it is OK if an MX host cannot be found in the DNS; we'll just + * use a backup one, and silently ignore the better MX host. However, if + * the best backup that we can find in the DNS is the local machine, then + * we must remember that the local machine is not the primary MX host, or + * else we will claim that mail loops back. + * + * XXX Optionally do A lookups even when the MX lookup didn't complete. + * Unfortunately with some DNS servers this is not a transient problem. + * + * XXX Ideally we would perform A lookups only as far as needed. But as long + * as we're looking up all the hosts, it would be better to look up the + * least preferred host first, so that DNS lookup error messages make + * more sense. + * + * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX + * hosts, whereas multiple A records per hostname must be used in the + * order as received. They make the bogus assumption that a hostname with + * multiple A records corresponds to one machine with multiple network + * interfaces. + * + * XXX 2821: Postfix recognizes the local machine by looking for its own IP + * address in the list of mail exchangers. RFC 2821 says one has to look + * at the mail exchanger hostname as well, making the bogus assumption + * that an IP address is listed only under one hostname. However, looking + * at hostnames provides a partial solution for MX hosts behind a NAT + * gateway. + */ + switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) { + default: + dsb_status(why, "4.4.3"); + if (var_ign_mx_lookup_err) + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + case DNS_INVAL: + dsb_status(why, "5.4.4"); + if (var_ign_mx_lookup_err) + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + case DNS_NULLMX: + dsb_status(why, "5.1.0"); + break; + case DNS_POLICY: + dsb_status(why, "4.7.0"); + break; + case DNS_FAIL: + dsb_status(why, "5.4.3"); + if (var_ign_mx_lookup_err) + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + case DNS_OK: + mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any); + best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE); + addr_list = smtp_addr_list(mx_names, why); + if (mxrr) + *mxrr = dns_rr_copy(mx_names); /* copies one record! */ + dns_rr_free(mx_names); + if (addr_list == 0) { + /* Text does not change. */ + if (var_smtp_defer_mxaddr) { + /* Don't clobber the null terminator. */ + if (SMTP_HAS_HARD_DSN(why)) + SMTP_SET_SOFT_DSN(why); /* XXX */ + /* Require some error status. */ + else if (!SMTP_HAS_SOFT_DSN(why)) + msg_panic("smtp_domain_addr: bad status"); + } + msg_warn("no MX host for %s has a valid address record", name); + break; + } + best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE); + if (msg_verbose) + smtp_print_addr(name, addr_list); + if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) + && (self = smtp_find_self(addr_list)) != 0) { + addr_list = smtp_truncate_self(addr_list, self->pref); + if (addr_list == 0) { + if (best_pref != best_found) { + dsb_simple(why, "4.4.4", + "unable to find primary relay for %s", name); + } else { + dsb_simple(why, "5.4.6", "mail for %s loops back to myself", + name); + } + } + } +#define SMTP_COMPARE_ADDR(flags) \ + (((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \ + ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \ + dns_rr_compare_pref_any) + + if (addr_list && addr_list->next) { + if (var_smtp_rand_addr) + addr_list = dns_rr_shuffle(addr_list); + addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); + if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto) + addr_list = smtp_balance_inet_proto(addr_list, misc_flags, + var_smtp_mxaddr_limit); + } + break; + case DNS_NOTFOUND: + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + } + + /* + * Clean up. + */ + *found_myself |= (self != 0); + return (addr_list); +} + +/* smtp_host_addr - direct host lookup */ + +DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why) +{ + DNS_RR *addr_list; + int res_opt = 0; + const char *ahost; + + dsb_reset(why); /* Paranoia */ + + if (smtp_dns_support == SMTP_DNS_DNSSEC) + res_opt |= RES_USE_DNSSEC; + + /* + * IDNA support. + */ +#ifndef NO_EAI + if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", host, ahost); + } else +#endif + ahost = host; + + /* + * If the host is specified by numerical address, just convert the + * address to internal form. Otherwise, the host is specified by name. + */ +#define PREF0 0 + addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, why); + if (addr_list + && (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) + && smtp_find_self(addr_list) != 0) { + dns_rr_free(addr_list); + dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host); + return (0); + } + if (addr_list && addr_list->next) { + if (var_smtp_rand_addr) + addr_list = dns_rr_shuffle(addr_list); + /* The following changes the order of equal-preference hosts. */ + if (inet_proto_info()->ai_family_list[1] != 0) + addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); + if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto) + addr_list = smtp_balance_inet_proto(addr_list, misc_flags, + var_smtp_mxaddr_limit); + } + if (msg_verbose) + smtp_print_addr(host, addr_list); + return (addr_list); +} diff --git a/src/smtp/smtp_addr.h b/src/smtp/smtp_addr.h new file mode 100644 index 0000000..8f20961 --- /dev/null +++ b/src/smtp/smtp_addr.h @@ -0,0 +1,31 @@ +/*++ +/* NAME +/* smtp_addr 3h +/* SUMMARY +/* SMTP server address lookup +/* SYNOPSIS +/* #include "smtp_addr.h" +/* DESCRIPTION +/* .nf + + /* + * DNS library. + */ +#include <dns.h> + + /* + * Internal interfaces. + */ +extern DNS_RR *smtp_host_addr(const char *, int, DSN_BUF *); +extern DNS_RR *smtp_domain_addr(const char *, DNS_RR **, int, DSN_BUF *, int *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtp/smtp_chat.c b/src/smtp/smtp_chat.c new file mode 100644 index 0000000..a020ef8 --- /dev/null +++ b/src/smtp/smtp_chat.c @@ -0,0 +1,486 @@ +/*++ +/* NAME +/* smtp_chat 3 +/* SUMMARY +/* SMTP client request/response support +/* SYNOPSIS +/* #include "smtp.h" +/* +/* typedef struct { +/* .in +4 +/* int code; /* SMTP code, not sanitized */ +/* char *dsn; /* enhanced status, sanitized */ +/* char *str; /* unmodified SMTP reply */ +/* VSTRING *dsn_buf; +/* VSTRING *str_buf; +/* .in -4 +/* } SMTP_RESP; +/* +/* void smtp_chat_cmd(session, format, ...) +/* SMTP_SESSION *session; +/* const char *format; +/* +/* DICT *smtp_chat_resp_filter; +/* +/* SMTP_RESP *smtp_chat_resp(session) +/* SMTP_SESSION *session; +/* +/* void smtp_chat_notify(session) +/* SMTP_SESSION *session; +/* +/* void smtp_chat_init(session) +/* SMTP_SESSION *session; +/* +/* void smtp_chat_reset(session) +/* SMTP_SESSION *session; +/* DESCRIPTION +/* This module implements SMTP client support for request/reply +/* conversations, and maintains a limited SMTP transaction log. +/* +/* smtp_chat_cmd() formats a command and sends it to an SMTP server. +/* Optionally, the command is logged. +/* +/* smtp_chat_resp() reads one SMTP server response. It extracts +/* the SMTP reply code and enhanced status code from the text, +/* and concatenates multi-line responses to one string, using +/* a newline as separator. Optionally, the server response +/* is logged. +/* .IP \(bu +/* Postfix never sanitizes the extracted SMTP reply code except +/* to ensure that it is a three-digit code. A malformed reply +/* results in a null extracted SMTP reply code value. +/* .IP \(bu +/* Postfix always sanitizes the extracted enhanced status code. +/* When the server's SMTP status code is 2xx, 4xx or 5xx, +/* Postfix requires that the first digit of the server's +/* enhanced status code matches the first digit of the server's +/* SMTP status code. In case of a mis-match, or when the +/* server specified no status code, the extracted enhanced +/* status code is set to 2.0.0, 4.0.0 or 5.0.0 instead. With +/* SMTP reply codes other than 2xx, 4xx or 5xx, the extracted +/* enhanced status code is set to a default value of 5.5.0 +/* (protocol error) for reasons outlined under the next bullet. +/* .IP \(bu +/* Since the SMTP reply code may violate the protocol even +/* when it is correctly formatted, Postfix uses the sanitized +/* extracted enhanced status code to decide whether an error +/* condition is permanent or transient. This means that the +/* caller may have to update the enhanced status code when it +/* discovers that a server reply violates the SMTP protocol, +/* even though it was correctly formatted. This happens when +/* the client and server get out of step due to a broken proxy +/* agent. +/* .PP +/* smtp_chat_resp_filter specifies an optional filter to +/* transform one server reply line before it is parsed. The +/* filter is invoked once for each line of a multi-line reply. +/* +/* smtp_chat_notify() sends a copy of the SMTP transaction log +/* to the postmaster for review. The postmaster notice is sent only +/* when delivery is possible immediately. It is an error to call +/* smtp_chat_notify() when no SMTP transaction log exists. +/* +/* smtp_chat_init() initializes the per-session transaction log. +/* This must be done at the beginning of a new SMTP session. +/* +/* smtp_chat_reset() resets the transaction log. This is +/* typically done at the beginning or end of an SMTP session, +/* or within a session to discard non-error information. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem, server response exceeds +/* configurable limit. +/* All other exceptions are handled by long jumps (see smtp_stream(3)). +/* SEE ALSO +/* smtp_stream(3) SMTP session I/O support +/* msg(3) generic logging interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <ctype.h> +#include <stdlib.h> +#include <setjmp.h> +#include <string.h> +#include <limits.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <argv.h> +#include <stringops.h> +#include <line_wrap.h> +#include <mymalloc.h> + +/* Global library. */ + +#include <recipient_list.h> +#include <deliver_request.h> +#include <smtp_stream.h> +#include <mail_params.h> +#include <mail_addr.h> +#include <post_mail.h> +#include <mail_error.h> +#include <dsn_util.h> + +/* Application-specific. */ + +#include "smtp.h" + + /* + * Server reply transformations. + */ +DICT *smtp_chat_resp_filter; + +/* smtp_chat_init - initialize SMTP transaction log */ + +void smtp_chat_init(SMTP_SESSION *session) +{ + session->history = 0; +} + +/* smtp_chat_reset - reset SMTP transaction log */ + +void smtp_chat_reset(SMTP_SESSION *session) +{ + if (session->history) { + argv_free(session->history); + session->history = 0; + } +} + +/* smtp_chat_append - append record to SMTP transaction log */ + +static void smtp_chat_append(SMTP_SESSION *session, const char *direction, + const char *data) +{ + char *line; + + if (session->history == 0) + session->history = argv_alloc(10); + line = concatenate(direction, data, (char *) 0); + argv_add(session->history, line, (char *) 0); + myfree(line); +} + +/* smtp_chat_cmd - send an SMTP command */ + +void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...) +{ + va_list ap; + + /* + * Format the command, and update the transaction log. + */ + va_start(ap, fmt); + vstring_vsprintf(session->buffer, fmt, ap); + va_end(ap); + smtp_chat_append(session, "Out: ", STR(session->buffer)); + + /* + * Optionally log the command first, so we can see in the log what the + * program is trying to do. + */ + if (msg_verbose) + msg_info("> %s: %s", session->namaddrport, STR(session->buffer)); + + /* + * Send the command to the SMTP server. + */ + smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream); + + /* + * Force flushing of output does not belong here. It is done in the + * smtp_loop() main protocol loop when reading the server response, and + * in smtp_helo() when reading the EHLO response after sending the EHLO + * command. + * + * If we do forced flush here, then we must longjmp() on error, and a + * matching "prepare for disaster" error handler must be set up before + * every smtp_chat_cmd() call. + */ +#if 0 + + /* + * Flush unsent data to avoid timeouts after slow DNS lookups. + */ + if (time((time_t *) 0) - vstream_ftime(session->stream) > 10) + vstream_fflush(session->stream); + + /* + * Abort immediately if the connection is broken. + */ + if (vstream_ftimeout(session->stream)) + vstream_longjmp(session->stream, SMTP_ERR_TIME); + if (vstream_ferror(session->stream)) + vstream_longjmp(session->stream, SMTP_ERR_EOF); +#endif +} + +/* smtp_chat_resp - read and process SMTP server response */ + +SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session) +{ + static SMTP_RESP rdata; + char *cp; + int last_char; + int three_digs = 0; + size_t len; + const char *new_reply; + int chat_append_flag; + int chat_append_skipped = 0; + + /* + * Initialize the response data buffer. + */ + if (rdata.str_buf == 0) { + rdata.dsn_buf = vstring_alloc(10); + rdata.str_buf = vstring_alloc(100); + } + + /* + * Censor out non-printable characters in server responses. Concatenate + * multi-line server responses. Separate the status code from the text. + * Leave further parsing up to the application. + * + * We can't parse or store input that exceeds var_line_limit, so we just + * skip over it to simplify the remainder of the code below. + */ + VSTRING_RESET(rdata.str_buf); + for (;;) { + last_char = smtp_get(session->buffer, session->stream, var_line_limit, + SMTP_GET_FLAG_SKIP); + /* XXX Update the per-line time limit. */ + printable(STR(session->buffer), '?'); + if (last_char != '\n') + msg_warn("%s: response longer than %d: %.30s...", + session->namaddrport, var_line_limit, STR(session->buffer)); + if (msg_verbose) + msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer)); + + /* + * Defend against a denial of service attack by limiting the amount + * of multi-line text that we are willing to store. + */ + chat_append_flag = (LEN(rdata.str_buf) < var_line_limit); + if (chat_append_flag) + smtp_chat_append(session, "In: ", STR(session->buffer)); + else { + if (chat_append_skipped == 0) + msg_warn("%s: multi-line response longer than %d %.30s...", + session->namaddrport, var_line_limit, STR(rdata.str_buf)); + if (chat_append_skipped < INT_MAX) + chat_append_skipped++; + } + + /* + * Server reply substitution, for fault-injection testing, or for + * working around broken systems. Use with care. + */ + if (smtp_chat_resp_filter != 0) { + new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer)); + if (new_reply != 0) { + msg_info("%s: replacing server reply \"%s\" with \"%s\"", + session->namaddrport, STR(session->buffer), new_reply); + vstring_strcpy(session->buffer, new_reply); + if (chat_append_flag) { + smtp_chat_append(session, "Replaced-by: ", ""); + smtp_chat_append(session, " ", new_reply); + } + } else if (smtp_chat_resp_filter->error != 0) { + msg_warn("%s: table %s:%s lookup error for %s", + session->state->request->queue_id, + smtp_chat_resp_filter->type, + smtp_chat_resp_filter->name, + printable(STR(session->buffer), '?')); + vstream_longjmp(session->stream, SMTP_ERR_DATA); + } + } + if (chat_append_flag) { + if (LEN(rdata.str_buf)) + VSTRING_ADDCH(rdata.str_buf, '\n'); + vstring_strcat(rdata.str_buf, STR(session->buffer)); + } + + /* + * Parse into code and text. Do not ignore garbage (see below). + */ + for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++) + /* void */ ; + if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) { + if (*cp == '-') + continue; + if (*cp == ' ' || *cp == 0) + break; + } + + /* + * XXX Do not simply ignore garbage in the server reply when ESMTP + * command pipelining is turned on. For example, after sending + * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a + * legitimate 2XX reply, Postfix recognizes the server's QUIT reply + * as the END-OF-DATA reply after garbage, causing mail to be lost. + * + * Without the ability to store per-domain status information in queue + * files, automatic workarounds are problematic: + * + * - Automatically deferring delivery creates a "repeated delivery" + * problem when garbage arrives after the DATA stage. Without the + * workaround, Postfix delivers only once. + * + * - Automatically deferring delivery creates a "no delivery" problem + * when the garbage arrives before the DATA stage. Without the + * workaround, mail might still get through. + * + * - Automatically turning off pipelining for delayed mail affects + * deliveries to correctly implemented servers, and may also affect + * delivery of large mailing lists. + * + * So we leave the decision with the administrator, but we don't force + * them to take action, like we would with automatic deferral. If + * loss of mail is not acceptable then they can turn off pipelining + * for specific sites, or they can turn off pipelining globally when + * they find that there are just too many broken sites. + */ + session->error_mask |= MAIL_ERROR_PROTOCOL; + if (session->features & SMTP_FEATURE_PIPELINING) { + msg_warn("%s: non-%s response from %s: %.100s", + session->state->request->queue_id, + smtp_mode ? "ESMTP" : "LMTP", + session->namaddrport, STR(session->buffer)); + if (var_helpful_warnings) + msg_warn("to prevent loss of mail, turn off command pipelining " + "for %s with the %s parameter", + STR(session->iterator->addr), + VAR_LMTP_SMTP(EHLO_DIS_MAPS)); + } + } + + /* + * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail + * code if none was given. + * + * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX + * replies, or codes whose initial digit is out of sync with the reply + * code. + * + * XXX Potential stability problem. In order to save memory, the queue + * manager stores DSNs in a compact manner: + * + * - empty strings are represented by null pointers, + * + * - the status and reason are required to be non-empty. + * + * Other Postfix daemons inherit this behavior, because they use the same + * DSN support code. This means that everything that receives DSNs must + * cope with null pointers for the optional DSN attributes, and that + * everything that provides DSN information must provide a non-empty + * status and reason, otherwise the DSN support code wil panic(). + * + * Thus, when the remote server sends a malformed reply (or 3XX out of + * context) we should not panic() in DSN_COPY() just because we don't + * have a status. Robustness suggests that we supply a status here, and + * that we leave it up to the down-stream code to override the + * server-supplied status in case of an error we can't detect here, such + * as an out-of-order server reply. + */ + VSTRING_TERMINATE(rdata.str_buf); + vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */ + if (three_digs != 0) { + rdata.code = atoi(STR(session->buffer)); + if (strchr("245", STR(session->buffer)[0]) != 0) { + for (cp = STR(session->buffer) + 4; *cp == ' '; cp++) + /* void */ ; + if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) { + vstring_strncpy(rdata.dsn_buf, cp, len); + } else { + vstring_strcpy(rdata.dsn_buf, "0.0.0"); + STR(rdata.dsn_buf)[0] = STR(session->buffer)[0]; + } + } + } else { + rdata.code = 0; + } + rdata.dsn = STR(rdata.dsn_buf); + rdata.str = STR(rdata.str_buf); + return (&rdata); +} + +/* print_line - line_wrap callback */ + +static void print_line(const char *str, int len, int indent, void *context) +{ + VSTREAM *notice = (VSTREAM *) context; + + post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str); +} + +/* smtp_chat_notify - notify postmaster */ + +void smtp_chat_notify(SMTP_SESSION *session) +{ + const char *myname = "smtp_chat_notify"; + VSTREAM *notice; + char **cpp; + + /* + * Sanity checks. + */ + if (session->history == 0) + msg_panic("%s: no conversation history", myname); + if (msg_verbose) + msg_info("%s: notify postmaster", myname); + + /* + * Construct a message for the postmaster, explaining what this is all + * about. This is junk mail: don't send it when the mail posting service + * is unavailable, and use the double bounce sender address, to prevent + * mail bounce wars. Always prepend one space to message content that we + * generate from untrusted data. + */ +#define NULL_TRACE_FLAGS 0 +#define NO_QUEUE_ID ((VSTRING *) 0) +#define LENGTH 78 +#define INDENT 4 + + notice = post_mail_fopen_nowait(mail_addr_double_bounce(), + var_error_rcpt, + MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS, + SMTPUTF8_FLAG_NONE, NO_QUEUE_ID); + if (notice == 0) { + msg_warn("postmaster notify: %m"); + return; + } + post_mail_fprintf(notice, "From: %s (Mail Delivery System)", + mail_addr_mail_daemon()); + post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); + post_mail_fprintf(notice, "Subject: %s %s client: errors from %s", + var_mail_name, smtp_mode ? "SMTP" : "LMTP", + session->namaddrport); + post_mail_fputs(notice, ""); + post_mail_fprintf(notice, "Unexpected response from %s.", + session->namaddrport); + post_mail_fputs(notice, ""); + post_mail_fputs(notice, "Transcript of session follows."); + post_mail_fputs(notice, ""); + argv_terminate(session->history); + for (cpp = session->history->argv; *cpp; cpp++) + line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, + (void *) notice); + post_mail_fputs(notice, ""); + post_mail_fprintf(notice, "For other details, see the local mail logfile"); + (void) post_mail_fclose(notice); +} diff --git a/src/smtp/smtp_connect.c b/src/smtp/smtp_connect.c new file mode 100644 index 0000000..aad48e4 --- /dev/null +++ b/src/smtp/smtp_connect.c @@ -0,0 +1,1206 @@ +/*++ +/* NAME +/* smtp_connect 3 +/* SUMMARY +/* connect to SMTP/LMTP server and deliver +/* SYNOPSIS +/* #include "smtp.h" +/* +/* int smtp_connect(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* This module implements SMTP/LMTP connection management and controls +/* mail delivery. +/* +/* smtp_connect() attempts to establish an SMTP/LMTP session with a host +/* that represents the destination domain, or with an optional fallback +/* relay when {the destination cannot be found, or when all the +/* destination servers are unavailable}. It skips over IP addresses +/* that fail to complete the SMTP/LMTP handshake and tries to find +/* an alternate server when an SMTP/LMTP session fails to deliver. +/* +/* This layer also controls what connections are retrieved from +/* the connection cache, and what connections are saved to the cache. +/* +/* The destination is either a host (or domain) name or a numeric +/* address. Symbolic or numeric service port information may be +/* appended, separated by a colon (":"). In the case of LMTP, +/* destinations may be specified as "unix:pathname", "inet:host" +/* or "inet:host:port". +/* +/* With SMTP, the Internet domain name service is queried for mail +/* exchanger hosts. Quote the domain name with `[' and `]' to +/* suppress mail exchanger lookups. +/* +/* Numerical address information should always be quoted with `[]'. +/* DIAGNOSTICS +/* The delivery status is the result value. +/* SEE ALSO +/* smtp_proto(3) SMTP client protocol +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Connection caching in cooperation with: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> + +#ifndef IPPORT_SMTP +#define IPPORT_SMTP 25 +#endif + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <split_at.h> +#include <mymalloc.h> +#include <inet_addr_list.h> +#include <iostuff.h> +#include <timed_connect.h> +#include <stringops.h> +#include <host_port.h> +#include <sane_connect.h> +#include <myaddrinfo.h> +#include <sock_addr.h> +#include <inet_proto.h> + +/* Global library. */ + +#include <mail_params.h> +#include <own_inet_addr.h> +#include <deliver_pass.h> +#include <mail_error.h> +#include <dsn_buf.h> +#include <mail_addr.h> + +/* DNS library. */ + +#include <dns.h> + +/* Application-specific. */ + +#include <smtp.h> +#include <smtp_addr.h> +#include <smtp_reuse.h> + + /* + * Forward declaration. + */ +static SMTP_SESSION *smtp_connect_sock(int, struct sockaddr *, int, + SMTP_ITERATOR *, DSN_BUF *, + int); + +/* smtp_connect_unix - connect to UNIX-domain address */ + +static SMTP_SESSION *smtp_connect_unix(SMTP_ITERATOR *iter, DSN_BUF *why, + int sess_flags) +{ + const char *myname = "smtp_connect_unix"; + struct sockaddr_un sock_un; + const char *addr = STR(iter->addr); + int len = strlen(addr); + int sock; + + dsb_reset(why); /* Paranoia */ + + /* + * Sanity checks. + */ + if (len >= (int) sizeof(sock_un.sun_path)) { + msg_warn("unix-domain name too long: %s", addr); + dsb_simple(why, "4.3.5", "Server configuration error"); + return (0); + } + + /* + * Initialize. + */ + memset((void *) &sock_un, 0, sizeof(sock_un)); + sock_un.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sock_un.sun_len = len + 1; +#endif + memcpy(sock_un.sun_path, addr, len + 1); + + /* + * Create a client socket. + */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + + /* + * Connect to the server. + */ + if (msg_verbose) + msg_info("%s: trying: %s...", myname, addr); + + return (smtp_connect_sock(sock, (struct sockaddr *) &sock_un, + sizeof(sock_un), iter, why, sess_flags)); +} + +/* smtp_connect_addr - connect to explicit address */ + +static SMTP_SESSION *smtp_connect_addr(SMTP_ITERATOR *iter, DSN_BUF *why, + int sess_flags) +{ + const char *myname = "smtp_connect_addr"; + struct sockaddr_storage ss; /* remote */ + struct sockaddr *sa = (struct sockaddr *) &ss; + SOCKADDR_SIZE salen = sizeof(ss); + MAI_HOSTADDR_STR hostaddr; + DNS_RR *addr = iter->rr; + unsigned port = iter->port; + int sock; + char *bind_addr; + char *bind_var; + + dsb_reset(why); /* Paranoia */ + + /* + * Sanity checks. + */ + if (dns_rr_to_sa(addr, port, sa, &salen) != 0) { + msg_warn("%s: skip address type %s: %m", + myname, dns_strtype(addr->type)); + dsb_simple(why, "4.4.0", "network address conversion failed: %m"); + return (0); + } + + /* + * Initialize. + */ + if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + + if (inet_windowsize > 0) + set_inet_windowsize(sock, inet_windowsize); + + /* + * Allow the sysadmin to specify the source address, for example, as "-o + * smtp_bind_address=x.x.x.x" in the master.cf file. + */ +#ifdef HAS_IPV6 + if (sa->sa_family == AF_INET6) { + bind_addr = var_smtp_bind_addr6; + bind_var = VAR_LMTP_SMTP(BIND_ADDR6); + } else +#endif + if (sa->sa_family == AF_INET) { + bind_addr = var_smtp_bind_addr; + bind_var = VAR_LMTP_SMTP(BIND_ADDR); + } else + bind_var = bind_addr = ""; + if (*bind_addr) { + int aierr; + struct addrinfo *res0; + + if ((aierr = hostaddr_to_sockaddr(bind_addr, (char *) 0, 0, &res0)) != 0) + msg_fatal("%s: bad %s parameter: %s: %s", + myname, bind_var, bind_addr, MAI_STRERROR(aierr)); + if (bind(sock, res0->ai_addr, res0->ai_addrlen) < 0) + msg_warn("%s: bind %s: %m", myname, bind_addr); + else if (msg_verbose) + msg_info("%s: bind %s", myname, bind_addr); + freeaddrinfo(res0); + } + + /* + * When running as a virtual host, bind to the virtual interface so that + * the mail appears to come from the "right" machine address. + * + * XXX The IPv6 patch expands the null host (as client endpoint) and uses + * the result as the loopback address list. + */ + else { + int count = 0; + struct sockaddr *own_addr = 0; + INET_ADDR_LIST *addr_list = own_inet_addr_list(); + struct sockaddr_storage *s; + + for (s = addr_list->addrs; s < addr_list->addrs + addr_list->used; s++) { + if (SOCK_ADDR_FAMILY(s) == sa->sa_family) { + if (count++ > 0) + break; + own_addr = SOCK_ADDR_PTR(s); + } + } + if (count == 1 && !sock_addr_in_loopback(own_addr)) { + if (bind(sock, own_addr, SOCK_ADDR_LEN(own_addr)) < 0) { + SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_warn("%s: bind %s: %m", myname, hostaddr.buf); + } else if (msg_verbose) { + SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("%s: bind %s", myname, hostaddr.buf); + } + } + } + + /* + * Connect to the server. + */ + if (msg_verbose) + msg_info("%s: trying: %s[%s] port %d...", + myname, STR(iter->host), STR(iter->addr), ntohs(port)); + + return (smtp_connect_sock(sock, sa, salen, iter, why, sess_flags)); +} + +/* smtp_connect_sock - connect a socket over some transport */ + +static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr *sa, + int salen, + SMTP_ITERATOR *iter, + DSN_BUF *why, + int sess_flags) +{ + int conn_stat; + int saved_errno; + VSTREAM *stream; + time_t start_time; + const char *name = STR(iter->host); + const char *addr = STR(iter->addr); + unsigned port = iter->port; + + start_time = time((time_t *) 0); + if (var_smtp_conn_tmout > 0) { + non_blocking(sock, NON_BLOCKING); + conn_stat = timed_connect(sock, sa, salen, var_smtp_conn_tmout); + saved_errno = errno; + non_blocking(sock, BLOCKING); + errno = saved_errno; + } else { + conn_stat = sane_connect(sock, sa, salen); + } + if (conn_stat < 0) { + if (port) + dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m", + name, addr, ntohs(port)); + else + dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr); + close(sock); + return (0); + } + stream = vstream_fdopen(sock, O_RDWR); + + /* + * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. + */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(stream); + + /* + * Bundle up what we have into a nice SMTP_SESSION object. + */ + return (smtp_session_alloc(stream, iter, start_time, sess_flags)); +} + +/* smtp_parse_destination - parse host/port destination */ + +static char *smtp_parse_destination(char *destination, char *def_service, + char **hostp, unsigned *portp) +{ + char *buf = mystrdup(destination); + char *service; + struct servent *sp; + char *protocol = "tcp"; /* XXX configurable? */ + unsigned port; + const char *err; + + if (msg_verbose) + msg_info("smtp_parse_destination: %s %s", destination, def_service); + + /* + * Parse the host/port information. We're working with a copy of the + * destination argument so the parsing can be destructive. + */ + if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0) + msg_fatal("%s in server description: %s", err, destination); + + /* + * Convert service to port number, network byte order. + */ + if (alldig(service)) { + if ((port = atoi(service)) >= 65536 || port == 0) + msg_fatal("bad network port in destination: %s", destination); + *portp = htons(port); + } else { + if ((sp = getservbyname(service, protocol)) == 0) + msg_fatal("unknown service: %s/%s", service, protocol); + *portp = sp->s_port; + } + return (buf); +} + +/* smtp_cleanup_session - clean up after using a session */ + +static void smtp_cleanup_session(SMTP_STATE *state) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + int throttled; + + /* + * Inform the postmaster of trouble. + * + * XXX Don't send notifications about errors while sending notifications. + */ +#define POSSIBLE_NOTIFICATION(sender) \ + (*sender == 0 || strcmp(sender, mail_addr_double_bounce()) == 0) + + if (session->history != 0 + && (session->error_mask & name_mask(VAR_NOTIFY_CLASSES, + mail_error_masks, + var_notify_classes)) != 0 + && POSSIBLE_NOTIFICATION(request->sender) == 0) + smtp_chat_notify(session); + + /* + * When session caching is enabled, cache the first good session for this + * delivery request under the next-hop destination, and cache all good + * sessions under their server network address (destroying the session in + * the process). + * + * Caching under the next-hop destination name (rather than the fall-back + * destination) allows us to skip over non-responding primary or backup + * hosts. In fact, this is the only benefit of caching logical to + * physical bindings; caching a session under its own hostname provides + * no performance benefit, given the way smtp_connect() works. + */ + throttled = THIS_SESSION_IS_THROTTLED; /* smtp_quit() may fail */ + if (THIS_SESSION_IS_EXPIRED) + smtp_quit(state); /* also disables caching */ + if (THIS_SESSION_IS_CACHED + /* Redundant tests for safety... */ + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) { + smtp_save_session(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL, + SMTP_KEY_MASK_SCACHE_ENDP_LABEL); + } else { + smtp_session_free(session); + } + state->session = 0; + + /* + * If this session was good, reset the scache next-hop destination, so + * that we won't cache connections to less-preferred servers under the + * same next-hop destination. Otherwise we could end up skipping over the + * available and more-preferred servers. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state) && !throttled) + CLEAR_SCACHE_REQUEST_NEXTHOP(state); + + /* + * Clean up the lists with todo and dropped recipients. + */ + smtp_rcpt_cleanup(state); + + /* + * Reset profiling info. + * + * XXX When one delivery request results in multiple sessions, the set-up + * and transmission latencies of the earlier sessions will count as + * connection set-up time for the later sessions. + * + * XXX On the other hand, when we first try to connect to one or more dead + * hosts before we reach a good host, then all that time must be counted + * as connection set-up time for the session with the good host. + * + * XXX So this set-up attribution problem exists only when we actually + * engage in a session, spend a lot of time delivering a message, find + * that it fails, and then connect to an alternate host. + */ + memset((void *) &request->msg_stats.conn_setup_done, 0, + sizeof(request->msg_stats.conn_setup_done)); + memset((void *) &request->msg_stats.deliver_done, 0, + sizeof(request->msg_stats.deliver_done)); + request->msg_stats.reuse_count = 0; +} + +static void smtp_cache_policy(SMTP_STATE *state, const char *dest) +{ + DELIVER_REQUEST *request = state->request; + + state->misc_flags &= ~SMTP_MISC_FLAG_CONN_CACHE_MASK; + + if (smtp_cache_dest && string_list_match(smtp_cache_dest, dest)) { + state->misc_flags |= SMTP_MISC_FLAG_CONN_CACHE_MASK; + } else if (var_smtp_cache_demand) { + if (request->flags & DEL_REQ_FLAG_CONN_LOAD) + state->misc_flags |= SMTP_MISC_FLAG_CONN_LOAD; + if (request->flags & DEL_REQ_FLAG_CONN_STORE) + state->misc_flags |= SMTP_MISC_FLAG_CONN_STORE; + } +} + +/* smtp_connect_local - connect to local server */ + +static void smtp_connect_local(SMTP_STATE *state, const char *path) +{ + const char *myname = "smtp_connect_local"; + SMTP_ITERATOR *iter = state->iterator; + SMTP_SESSION *session; + DSN_BUF *why = state->why; + + /* + * Do not silently ignore an unused setting. + */ + if (*var_fallback_relay) + msg_warn("ignoring \"%s = %s\" setting for non-TCP connections", + VAR_LMTP_FALLBACK, var_fallback_relay); + + /* + * It's too painful to weave this code into the SMTP connection + * management routine. + * + * Connection cache management is based on the UNIX-domain pathname, without + * the "unix:" prefix. + */ + smtp_cache_policy(state, path); + if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK) + SET_SCACHE_REQUEST_NEXTHOP(state, path); + + /* + * Here we ensure that the iter->addr member refers to a copy of the + * UNIX-domain pathname, so that smtp_save_session() will cache the + * connection using the pathname as the physical endpoint name. + * + * We set dest=path for backwards compatibility. + */ +#define NO_PORT 0 + + SMTP_ITER_INIT(iter, path, var_myhostname, path, NO_PORT, state); + + /* + * Opportunistic TLS for unix domain sockets does not make much sense, + * since the channel is private, mere encryption without authentication + * is just wasted cycles and opportunity for breakage. Since we are not + * willing to retry after TLS handshake failures here, we downgrade "may" + * no "none". Nothing is lost, and much waste is avoided. + * + * We don't know who is authenticating whom, so if a client cert is + * available, "encrypt" may be a sensible policy. Otherwise, we also + * downgrade "encrypt" to "none", this time just to avoid waste. + * + * We use smtp_reuse_nexthop() instead of smtp_reuse_addr(), so that we can + * reuse a SASL-authenticated connection (however unlikely this scenario + * may be). The smtp_reuse_addr() interface currently supports only reuse + * of SASL-unauthenticated connections. + */ +#ifdef USE_TLS + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup error for %s/%s: %s", + STR(iter->host), STR(iter->addr), STR(why->reason)); + return; + } +#endif + if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 + || (session = smtp_reuse_nexthop(state, + SMTP_KEY_MASK_SCACHE_DEST_LABEL)) == 0) + session = smtp_connect_unix(iter, why, state->misc_flags); + if ((state->session = session) != 0) { + session->state = state; +#ifdef USE_TLS + session->tls_nexthop = var_myhostname; /* for TLS_LEV_SECURE */ + if (state->tls->level == TLS_LEV_MAY) { + msg_warn("%s: opportunistic TLS encryption is not appropriate " + "for unix-domain destinations.", myname); + state->tls->level = TLS_LEV_NONE; + } +#endif + /* All delivery errors bounce or defer. */ + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + + /* + * When a TLS handshake fails, the stream is marked "dead" to avoid + * further I/O over a broken channel. + */ + if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 + && smtp_helo(state) != 0) { + if (!THIS_SESSION_IS_FORBIDDEN + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) + smtp_quit(state); + } else { + smtp_xfer(state); + } + + /* + * With opportunistic TLS disabled we don't expect to be asked to + * retry connections without TLS, and so we expect the final server + * flag to stay on. + */ + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) + msg_panic("%s: unix-domain destination not final!", myname); + smtp_cleanup_session(state); + } + + /* + * Cleanup. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + CLEAR_SCACHE_REQUEST_NEXTHOP(state); +} + +/* smtp_scrub_address_list - delete all cached addresses from list */ + +static void smtp_scrub_addr_list(HTABLE *cached_addr, DNS_RR **addr_list) +{ + MAI_HOSTADDR_STR hostaddr; + DNS_RR *addr; + DNS_RR *next; + + /* + * XXX Extend the DNS_RR structure with fields for the printable address + * and/or binary sockaddr representations, so that we can avoid repeated + * binary->string transformations for the same address. + */ + for (addr = *addr_list; addr; addr = next) { + next = addr->next; + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("cannot convert type %s record to printable address", + dns_strtype(addr->type)); + continue; + } + if (htable_locate(cached_addr, hostaddr.buf)) + *addr_list = dns_rr_remove(*addr_list, addr); + } +} + +/* smtp_update_addr_list - common address list update */ + +static void smtp_update_addr_list(DNS_RR **addr_list, const char *server_addr, + int session_count) +{ + DNS_RR *addr; + DNS_RR *next; + int aierr; + struct addrinfo *res0; + + if (*addr_list == 0) + return; + + /* + * Truncate the address list if we are not going to use it anyway. + */ + if (session_count == var_smtp_mxsess_limit + || session_count == var_smtp_mxaddr_limit) { + dns_rr_free(*addr_list); + *addr_list = 0; + return; + } + + /* + * Convert server address to internal form, and look it up in the address + * list. + * + * XXX smtp_reuse_session() breaks if we remove two or more adjacent list + * elements but do not truncate the list to zero length. + * + * XXX Extend the SMTP_SESSION structure with sockaddr information so that + * we can avoid repeated string->binary transformations for the same + * address. + */ + if ((aierr = hostaddr_to_sockaddr(server_addr, (char *) 0, 0, &res0)) != 0) { + msg_warn("hostaddr_to_sockaddr %s: %s", + server_addr, MAI_STRERROR(aierr)); + } else { + for (addr = *addr_list; addr; addr = next) { + next = addr->next; + if (DNS_RR_EQ_SA(addr, (struct sockaddr *) res0->ai_addr)) { + *addr_list = dns_rr_remove(*addr_list, addr); + break; + } + } + freeaddrinfo(res0); + } +} + +/* smtp_reuse_session - try to use existing connection, return session count */ + +static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list, + int domain_best_pref) +{ + int session_count = 0; + DNS_RR *addr; + DNS_RR *next; + MAI_HOSTADDR_STR hostaddr; + SMTP_SESSION *session; + SMTP_ITERATOR *iter = state->iterator; + DSN_BUF *why = state->why; + + /* + * First, search the cache by delivery request nexthop. We truncate the + * server address list when all the sessions for this destination are + * used up, to reduce the number of variables that need to be checked + * later. + * + * Note: connection reuse by delivery request nexthop restores the "best MX" + * bit. + * + * smtp_reuse_nexthop() clobbers the iterators's "dest" attribute. We save + * and restore it here, so that subsequent connections will use the + * proper nexthop information. + * + * We don't use TLS level info for nexthop-based connection cache storage + * keys. The combination of (service, nexthop, etc.) should be stable + * over the time range of interest, and the policy is still enforced on + * an individual connection to an MX host, before that connection is + * stored under a nexthop- or host-based storage key. + */ +#ifdef USE_TLS + smtp_tls_policy_dummy(state->tls); +#endif + SMTP_ITER_SAVE_DEST(state->iterator); + if (*addr_list && SMTP_RCPT_LEFT(state) > 0 + && HAVE_SCACHE_REQUEST_NEXTHOP(state) + && (session = smtp_reuse_nexthop(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL)) != 0) { + session_count = 1; + smtp_update_addr_list(addr_list, STR(iter->addr), session_count); + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && *addr_list == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + smtp_xfer(state); + smtp_cleanup_session(state); + } + SMTP_ITER_RESTORE_DEST(state->iterator); + + /* + * Second, search the cache by primary MX address. Again, we use address + * list truncation so that we have to check fewer variables later. + * + * XXX This loop is safe because smtp_update_addr_list() either truncates + * the list to zero length, or removes at most one list element. + * + * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated + * connections. Furthermore, we rely on smtp_reuse_addr() to look up an + * existing SASL-unauthenticated connection only when a new connection + * would be guaranteed not to require SASL authentication. + * + * In addition, we rely on smtp_reuse_addr() to look up an existing + * plaintext connection only when a new connection would be guaranteed + * not to use TLS. + * + * For more precise control over reuse, the iterator should look up SASL and + * TLS policy as it evaluates mail exchangers in order, instead of + * relying on duplicate lookup request code in smtp_reuse(3) and + * smtp_session(3). + */ + for (addr = *addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { + if (addr->pref != domain_best_pref) + break; + next = addr->next; + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("cannot convert type %s record to printable address", + dns_strtype(addr->type)); + /* XXX Assume there is no code at the end of this loop. */ + continue; + } + vstring_strcpy(iter->addr, hostaddr.buf); + vstring_strcpy(iter->host, SMTP_HNAME(addr)); + iter->rr = addr; +#ifdef USE_TLS + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup error for %s/%s: %s", + STR(iter->dest), STR(iter->host), STR(why->reason)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } +#endif + if ((session = smtp_reuse_addr(state, + SMTP_KEY_MASK_SCACHE_ENDP_LABEL)) != 0) { + session->features |= SMTP_FEATURE_BEST_MX; + session_count += 1; + smtp_update_addr_list(addr_list, STR(iter->addr), session_count); + if (*addr_list == 0) + next = 0; + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && next == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + smtp_xfer(state); + smtp_cleanup_session(state); + } + } + return (session_count); +} + +/* smtp_connect_inet - establish network connection */ + +static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop, + char *def_service) +{ + DELIVER_REQUEST *request = state->request; + SMTP_ITERATOR *iter = state->iterator; + ARGV *sites; + char *dest; + char **cpp; + int non_fallback_sites; + int retry_plain = 0; + DSN_BUF *why = state->why; + + /* + * For sanity, require that at least one of INET or INET6 is enabled. + * Otherwise, we can't look up interface information, and we can't + * convert names or addresses. + */ + if (inet_proto_info()->ai_family_list[0] == 0) { + dsb_simple(why, "4.4.4", "all network protocols are disabled"); + return; + } + + /* + * Future proofing: do a null destination sanity check in case we allow + * the primary destination to be a list (it could be just separators). + */ + sites = argv_alloc(1); + argv_add(sites, nexthop, (char *) 0); + if (sites->argc == 0) + msg_panic("null destination: \"%s\"", nexthop); + non_fallback_sites = sites->argc; + argv_split_append(sites, var_fallback_relay, CHARS_COMMA_SP); + + /* + * Don't give up after a hard host lookup error until we have tried the + * fallback relay servers. + * + * Don't bounce mail after a host lookup problem with a relayhost or with a + * fallback relay. + * + * Don't give up after a qualifying soft error until we have tried all + * qualifying backup mail servers. + * + * All this means that error handling and error reporting depends on whether + * the error qualifies for trying to deliver to a backup mail server, or + * whether we're looking up a relayhost or fallback relay. The challenge + * then is to build this into the pre-existing SMTP client without + * getting lost in the complexity. + */ +#define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \ + (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites)) + + for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP); + SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0; + cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) { + char *dest_buf; + char *domain; + unsigned port; + DNS_RR *addr_list; + DNS_RR *addr; + DNS_RR *next; + int addr_count; + int sess_count; + SMTP_SESSION *session; + int lookup_mx; + unsigned domain_best_pref; + MAI_HOSTADDR_STR hostaddr; + + if (cpp[1] == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; + + /* + * Parse the destination. If no TCP port is specified, use the port + * that is reserved for the protocol (SMTP or LMTP). + */ + dest_buf = smtp_parse_destination(dest, def_service, &domain, &port); + if (var_helpful_warnings && var_smtp_tls_wrappermode == 0 + && ntohs(port) == 465) { + msg_info("SMTPS wrappermode (TCP port 465) requires setting " + "\"%s = yes\", and \"%s = encrypt\" (or stronger)", + VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); + } +#define NO_HOST "" /* safety */ +#define NO_ADDR "" /* safety */ + + SMTP_ITER_INIT(iter, dest, NO_HOST, NO_ADDR, port, state); + + /* + * Resolve an SMTP or LMTP server. In the case of SMTP, skip mail + * exchanger lookups when a quoted host is specified or when DNS + * lookups are disabled. + */ + if (msg_verbose) + msg_info("connecting to %s port %d", domain, ntohs(port)); + if (smtp_mode) { + if (ntohs(port) == IPPORT_SMTP) + state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT; + else + state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT; + lookup_mx = (smtp_dns_support != SMTP_DNS_DISABLED && *dest != '['); + } else + lookup_mx = 0; + if (!lookup_mx) { + addr_list = smtp_host_addr(domain, state->misc_flags, why); + /* XXX We could be an MX host for this destination... */ + } else { + int i_am_mx = 0; + + addr_list = smtp_domain_addr(domain, &iter->mx, state->misc_flags, + why, &i_am_mx); + /* If we're MX host, don't connect to non-MX backups. */ + if (i_am_mx) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; + } + + /* + * Don't try fall-back hosts if mail loops to myself. That would just + * make the problem worse. + */ + if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why)) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; + + /* + * No early loop exit or we have a memory leak with dest_buf. + */ + if (addr_list) + domain_best_pref = addr_list->pref; + + /* + * When connection caching is enabled, store the first good + * connection for this delivery request under the delivery request + * next-hop name. Good connections will also be stored under their + * specific server IP address. + * + * XXX smtp_session_cache_destinations specifies domain names without + * :port, because : is already used for maptype:mapname. Because of + * this limitation we use the bare domain without the optional [] or + * non-default TCP port. + * + * Opportunistic (a.k.a. on-demand) session caching on request by the + * queue manager. This is turned temporarily when a destination has a + * high volume of mail in the active queue. When the surge reaches + * its end, the queue manager requests that connections be retrieved + * but not stored. + */ + if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) { + smtp_cache_policy(state, domain); + if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK) + SET_SCACHE_REQUEST_NEXTHOP(state, dest); + } + + /* + * Delete visited cached hosts from the address list. + * + * Optionally search the connection cache by domain name or by primary + * MX address before we try to create new connections. + * + * Enforce the MX session and MX address counts per next-hop or + * fall-back destination. smtp_reuse_session() will truncate the + * address list when either limit is reached. + */ + if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) { + if (state->cache_used->used > 0) + smtp_scrub_addr_list(state->cache_used, &addr_list); + sess_count = addr_count = + smtp_reuse_session(state, &addr_list, domain_best_pref); + } else + sess_count = addr_count = 0; + + /* + * Connect to an SMTP server: create primary MX connections, and + * reuse or create backup MX connections. + * + * At the start of an SMTP session, all recipients are unmarked. In the + * course of an SMTP session, recipients are marked as KEEP (deliver + * to alternate mail server) or DROP (remove from recipient list). At + * the end of an SMTP session, weed out the recipient list. Unmark + * any left-over recipients and try to deliver them to a backup mail + * server. + * + * Cache the first good session under the next-hop destination name. + * Cache all good sessions under their physical endpoint. + * + * Don't query the session cache for primary MX hosts. We already did + * that in smtp_reuse_session(), and if any were found in the cache, + * they were already deleted from the address list. + * + * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated + * connections. Furthermore, we rely on smtp_reuse_addr() to look up + * an existing SASL-unauthenticated connection only when a new + * connection would be guaranteed not to require SASL authentication. + * + * In addition, we rely on smtp_reuse_addr() to look up an existing + * plaintext connection only when a new connection would be + * guaranteed not to use TLS. + */ + for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { + next = addr->next; + if (++addr_count == var_smtp_mxaddr_limit) + next = 0; + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("cannot convert type %s record to printable address", + dns_strtype(addr->type)); + /* XXX Assume there is no code at the end of this loop. */ + continue; + } + vstring_strcpy(iter->addr, hostaddr.buf); + vstring_strcpy(iter->host, SMTP_HNAME(addr)); + iter->rr = addr; +#ifdef USE_TLS + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup for %s/%s: %s", + STR(iter->dest), STR(iter->host), STR(why->reason)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } + if (var_smtp_tls_wrappermode + && state->tls->level < TLS_LEV_ENCRYPT) { + msg_warn("%s requires \"%s = encrypt\" (or stronger)", + VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } + /* Disable TLS when retrying after a handshake failure */ + if (retry_plain) { + state->tls->level = TLS_LEV_NONE; + retry_plain = 0; + } +#endif + if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 + || addr->pref == domain_best_pref + || !(session = smtp_reuse_addr(state, + SMTP_KEY_MASK_SCACHE_ENDP_LABEL))) + session = smtp_connect_addr(iter, why, state->misc_flags); + if ((state->session = session) != 0) { + session->state = state; +#ifdef USE_TLS + session->tls_nexthop = domain; +#endif + if (addr->pref == domain_best_pref) + session->features |= SMTP_FEATURE_BEST_MX; + /* Don't count handshake errors towards the session limit. */ + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && next == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 + && smtp_helo(state) != 0) { +#ifdef USE_TLS + + /* + * When an opportunistic TLS handshake fails, try the + * same address again, with TLS disabled. See also the + * RETRY_AS_PLAINTEXT macro. + */ + if ((retry_plain = session->tls_retry_plain) != 0) { + --addr_count; + next = addr; + } +#endif + + /* + * When a TLS handshake fails, the stream is marked + * "dead" to avoid further I/O over a broken channel. + */ + if (!THIS_SESSION_IS_FORBIDDEN + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) + smtp_quit(state); + } else { + /* Do count delivery errors towards the session limit. */ + if (++sess_count == var_smtp_mxsess_limit) + next = 0; + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && next == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + smtp_xfer(state); +#ifdef USE_TLS + + /* + * When opportunistic TLS fails after the STARTTLS + * handshake, try the same address again, with TLS + * disabled. See also the RETRY_AS_PLAINTEXT macro. + */ + if ((retry_plain = session->tls_retry_plain) != 0) { + --sess_count; + --addr_count; + next = addr; + } +#endif + } + smtp_cleanup_session(state); + } else { + /* The reason already includes the IP address and TCP port. */ + msg_info("%s", STR(why->reason)); + } + /* XXX Code above assumes there is no code at this loop ending. */ + } + dns_rr_free(addr_list); + if (iter->mx) { + dns_rr_free(iter->mx); + iter->mx = 0; /* Just in case */ + } + myfree(dest_buf); + if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + break; + } + + /* + * We still need to deliver, bounce or defer some left-over recipients: + * either mail loops or some backup mail server was unavailable. + */ + if (SMTP_RCPT_LEFT(state) > 0) { + + /* + * In case of a "no error" indication we make up an excuse: we did + * find the host address, but we did not attempt to connect to it. + * This can happen when the fall-back relay was already tried via a + * cached connection, so that the address list scrubber left behind + * an empty list. + */ + if (!SMTP_HAS_DSN(why)) { + dsb_simple(why, "4.3.0", + "server unavailable or unable to receive mail"); + } + + /* + * Pay attention to what could be configuration problems, and pretend + * that these are recoverable rather than bouncing the mail. + */ + else if (!SMTP_HAS_SOFT_DSN(why)) { + + /* + * The fall-back destination did not resolve as expected, or it + * is refusing to talk to us, or mail for it loops back to us. + */ + if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) { + msg_warn("%s configuration problem", VAR_SMTP_FALLBACK); + vstring_strcpy(why->status, "4.3.5"); + /* XXX Keep the diagnostic code and MTA. */ + } + + /* + * The next-hop relayhost did not resolve as expected, or it is + * refusing to talk to us, or mail for it loops back to us. + * + * XXX There is no equivalent safety net for mis-configured + * sender-dependent relay hosts. The trivial-rewrite resolver + * would have to flag the result, and the queue manager would + * have to provide that information to delivery agents. + */ + else if (smtp_mode && strcmp(sites->argv[0], var_relayhost) == 0) { + msg_warn("%s configuration problem", VAR_RELAYHOST); + vstring_strcpy(why->status, "4.3.5"); + /* XXX Keep the diagnostic code and MTA. */ + } + + /* + * Mail for the next-hop destination loops back to myself. Pass + * the mail to the best_mx_transport or bounce it. + */ + else if (smtp_mode && SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) { + dsb_reset(why); /* XXX */ + state->status = deliver_pass_all(MAIL_CLASS_PRIVATE, + var_bestmx_transp, + request); + SMTP_RCPT_LEFT(state) = 0; /* XXX */ + } + } + } + + /* + * Cleanup. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + CLEAR_SCACHE_REQUEST_NEXTHOP(state); + argv_free(sites); +} + +/* smtp_connect - establish SMTP connection */ + +int smtp_connect(SMTP_STATE *state) +{ + DELIVER_REQUEST *request = state->request; + char *destination = request->nexthop; + + /* + * All deliveries proceed along the same lines, whether they are over TCP + * or UNIX-domain sockets, and whether they use SMTP or LMTP: get a + * connection from the cache or create a new connection; deliver mail; + * update the connection cache or disconnect. + * + * The major differences appear at a higher level: the expansion from + * destination to address list, and whether to stop before we reach the + * end of that list. + */ + + /* + * With LMTP we have direct-to-host delivery only. The destination may + * have multiple IP addresses. + */ + if (!smtp_mode) { + if (strncmp(destination, "unix:", 5) == 0) { + smtp_connect_local(state, destination + 5); + } else { + if (strncmp(destination, "inet:", 5) == 0) + destination += 5; + smtp_connect_inet(state, destination, var_smtp_tcp_port); + } + } + + /* + * XXX We don't add support for "unix:" or "inet:" prefixes in SMTP + * destinations, because that would break compatibility with existing + * Postfix configurations that have a host with such a name. + */ + else { + smtp_connect_inet(state, destination, var_smtp_tcp_port); + } + + /* + * We still need to bounce or defer some left-over recipients: either + * (SMTP) mail loops or some server was unavailable. + * + * We could avoid this (and the "final server" complexity) by keeping one + * DSN structure per recipient in memory, by updating those in-memory + * structures with each delivery attempt, and by always flushing all + * deferred recipients at the end. We'd probably still want to bounce + * recipients immediately, so we'd end up with another chunk of code for + * defer logging only. + */ + if (SMTP_RCPT_LEFT(state) > 0) { + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; /* XXX */ + smtp_sess_fail(state); + + /* + * Sanity check. Don't silently lose recipients. + */ + smtp_rcpt_cleanup(state); + if (SMTP_RCPT_LEFT(state) > 0) + msg_panic("smtp_connect: left-over recipients"); + } + return (state->status); +} diff --git a/src/smtp/smtp_key.c b/src/smtp/smtp_key.c new file mode 100644 index 0000000..643f4db --- /dev/null +++ b/src/smtp/smtp_key.c @@ -0,0 +1,215 @@ +/*++ +/* NAME +/* smtp_key 3 +/* SUMMARY +/* cache/table lookup key management +/* SYNOPSIS +/* #include "smtp.h" +/* +/* char *smtp_key_prefix(buffer, delim_na, iterator, context_flags) +/* VSTRING *buffer; +/* const char *delim_na; +/* SMTP_ITERATOR *iterator; +/* int context_flags; +/* DESCRIPTION +/* The Postfix SMTP server accesses caches and lookup tables, +/* using lookup keys that contain information from various +/* contexts: per-server configuration, per-request envelope, +/* and results from DNS queries. +/* +/* These lookup keys sometimes share the same context information. +/* The primary purpose of this API is to ensure that this +/* shared context is used consistently, and that its use is +/* made explicit (both are needed to verify that there is no +/* false cache sharing). +/* +/* smtp_key_prefix() constructs a lookup key prefix from context +/* that may be shared with other lookup keys. The user is free +/* to append additional application-specific context. The result +/* value is a pointer to the result text. +/* +/* Arguments: +/* .IP buffer +/* Storage for the result. +/* .IP delim_na +/* The field delimiter character, and the optional place holder +/* character for a) information that is unavailable, b) +/* information that is inapplicable, or c) that would result +/* in an empty field. Key fields that contain "delim_na" +/* characters will be base64-encoded. +/* Do not specify "delim_na" characters that are part of the +/* base64 character set. +/* .IP iterator +/* Information that will be selected by the specified flags. +/* .IP context_flags +/* Bit-wise OR of one or more of the following. +/* .RS +/* .IP SMTP_KEY_FLAG_SERVICE +/* The global service name. This is a proxy for +/* destination-independent and request-independent context. +/* .IP SMTP_KEY_FLAG_SENDER +/* The envelope sender address. This is a proxy for sender-dependent +/* context, such as per-sender SASL authentication. +/* .IP SMTP_KEY_FLAG_REQ_NEXTHOP +/* The delivery request nexthop destination, including optional +/* [] and :port (the same form that users specify in a SASL +/* password or TLS policy lookup table). This is a proxy for +/* destination-dependent, but host-independent context. +/* .IP SMTP_KEY_FLAG_CUR_NEXTHOP +/* The current iterator's nexthop destination (delivery request +/* nexthop or fallback nexthop, including optional [] and +/* :port). +/* .IP SMTP_KEY_FLAG_HOSTNAME +/* The current iterator's remote hostname. +/* .IP SMTP_KEY_FLAG_ADDR +/* The current iterator's remote address. +/* .IP SMTP_KEY_FLAG_PORT +/* The current iterator's remote port. +/* .RE +/* DIAGNOSTICS +/* Panic: undefined flag or zero flags. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <netinet/in.h> /* ntohs() for Solaris or BSD */ +#include <arpa/inet.h> /* ntohs() for Linux or BSD */ +#include <string.h> + + /* + * Utility library. + */ +#include <msg.h> +#include <vstring.h> +#include <base64_code.h> + + /* + * Global library. + */ +#include <mail_params.h> + + /* + * Application-specific. + */ +#include <smtp.h> + + /* + * We use a configurable field terminator and optional place holder for data + * that is unavailable or inapplicable. We base64-encode content that + * contains these characters, and content that needs obfuscation. + */ + +/* smtp_key_append_na - append place-holder key field */ + +static void smtp_key_append_na(VSTRING *buffer, const char *delim_na) +{ + if (delim_na[1] != 0) + VSTRING_ADDCH(buffer, delim_na[1]); + VSTRING_ADDCH(buffer, delim_na[0]); +} + +/* smtp_key_append_str - append string-valued key field */ + +static void smtp_key_append_str(VSTRING *buffer, const char *str, + const char *delim_na) +{ + if (str == 0 || str[0] == 0) { + smtp_key_append_na(buffer, delim_na); + } else if (str[strcspn(str, delim_na)] != 0) { + base64_encode_opt(buffer, str, strlen(str), BASE64_FLAG_APPEND); + VSTRING_ADDCH(buffer, delim_na[0]); + } else { + vstring_sprintf_append(buffer, "%s%c", str, delim_na[0]); + } +} + +/* smtp_key_append_uint - append unsigned-valued key field */ + +static void smtp_key_append_uint(VSTRING *buffer, unsigned num, + const char *delim_na) +{ + vstring_sprintf_append(buffer, "%u%c", num, delim_na[0]); +} + +/* smtp_key_prefix - format common elements in lookup key */ + +char *smtp_key_prefix(VSTRING *buffer, const char *delim_na, + SMTP_ITERATOR *iter, int flags) +{ + static const char myname[] = "smtp_key_prefix"; + SMTP_STATE *state = iter->parent; /* private member */ + + /* + * Sanity checks. + */ + if (state == 0) + msg_panic("%s: no parent state", myname); + if (flags & ~SMTP_KEY_MASK_ALL) + msg_panic("%s: unknown key flags 0x%x", + myname, flags & ~SMTP_KEY_MASK_ALL); + if (flags == 0) + msg_panic("%s: zero flags", myname); + + /* + * Initialize. + */ + VSTRING_RESET(buffer); + + /* + * Per-service and per-request context. + */ + if (flags & SMTP_KEY_FLAG_SERVICE) + smtp_key_append_str(buffer, state->service, delim_na); + if (flags & SMTP_KEY_FLAG_SENDER) + smtp_key_append_str(buffer, state->request->sender, delim_na); + + /* + * Per-destination context, non-canonicalized form. + */ + if (flags & SMTP_KEY_FLAG_REQ_NEXTHOP) + smtp_key_append_str(buffer, STR(iter->request_nexthop), delim_na); + if (flags & SMTP_KEY_FLAG_CUR_NEXTHOP) + smtp_key_append_str(buffer, STR(iter->dest), delim_na); + + /* + * Per-host context, canonicalized form. + */ + if (flags & SMTP_KEY_FLAG_HOSTNAME) + smtp_key_append_str(buffer, STR(iter->host), delim_na); + if (flags & SMTP_KEY_FLAG_ADDR) + smtp_key_append_str(buffer, STR(iter->addr), delim_na); + if (flags & SMTP_KEY_FLAG_PORT) + smtp_key_append_uint(buffer, ntohs(iter->port), delim_na); + + /* + * Requested TLS level, if applicable. TODO(tlsproxy) should the lookup + * engine also try the requested TLS level and 'stronger', in case a + * server hosts multiple domains with different TLS requirements? + */ + if (flags & SMTP_KEY_FLAG_TLS_LEVEL) +#ifdef USE_TLS + smtp_key_append_uint(buffer, state->tls->level, delim_na); +#else + smtp_key_append_na(buffer, delim_na); +#endif + + VSTRING_TERMINATE(buffer); + + return STR(buffer); +} diff --git a/src/smtp/smtp_map11.c b/src/smtp/smtp_map11.c new file mode 100644 index 0000000..bca8641 --- /dev/null +++ b/src/smtp/smtp_map11.c @@ -0,0 +1,325 @@ +/*++ +/* NAME +/* smtp_map11 3 +/* SUMMARY +/* one-to-one address mapping +/* SYNOPSIS +/* #include <smtp.h> +/* +/* int smtp_map11_internal(addr, maps, propagate) +/* VSTRING *addr; +/* MAPS *maps; +/* int propagate; +/* +/* int smtp_map11_external(addr, maps, propagate) +/* VSTRING *addr; +/* MAPS *maps; +/* int propagate; +/* +/* int smtp_map11_tree(tree, maps, propagate) +/* TOK822 *tree; +/* MAPS *maps; +/* int propagate; +/* DESCRIPTION +/* This module performs non-recursive one-to-one address mapping. +/* An unmatched address extension is propagated when +/* \fIpropagate\fR is non-zero. +/* +/* smtp_map11_internal() looks up the RFC 822 internal (unquoted) +/* string form of an address in the maps specified via the +/* \fImaps\fR argument. +/* +/* smtp_map11_external() is a wrapper around the smtp_map11_internal() +/* routine that transforms from external (quoted) string form +/* to internal form and back. +/* +/* smtp_map11_tree() is a wrapper around the smtp_map11_internal() +/* routine that transforms from internal parse tree form to +/* internal form and back. +/* DIAGNOSTICS +/* Table lookup errors are fatal. +/* SEE ALSO +/* mail_addr_map(3) address mappings +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <dict.h> +#include <argv.h> +#include <tok822.h> + +/* Global library. */ + +#include <mail_addr_map.h> +#include <quote_822_local.h> + +/* Application-specific. */ + +#include <smtp.h> + +/* smtp_map11_internal - one-to-one table lookups */ + +int smtp_map11_internal(VSTRING *addr, MAPS *maps, int propagate) +{ + const char *myname = "smtp_map11_internal"; + ARGV *new_addr; + const char *result; + + if ((new_addr = mail_addr_map_internal(maps, STR(addr), propagate)) != 0) { + if (new_addr->argc > 1) + msg_warn("multi-valued %s result for %s", maps->title, STR(addr)); + result = new_addr->argv[0]; + if (msg_verbose) + msg_info("%s: %s -> %s", myname, STR(addr), result); + vstring_strcpy(addr, result); + argv_free(new_addr); + return (1); + } else { + if (maps->error != 0) + msg_fatal("%s map lookup problem for %s", maps->title, STR(addr)); + if (msg_verbose) + msg_info("%s: %s not found", myname, STR(addr)); + return (0); + } +} + +/* smtp_map11_tree - rewrite address node */ + +int smtp_map11_tree(TOK822 *tree, MAPS *maps, int propagate) +{ + VSTRING *int_buf = vstring_alloc(100); + VSTRING *ext_buf = vstring_alloc(100); + int ret; + + tok822_internalize(int_buf, tree->head, TOK822_STR_DEFL); + ret = smtp_map11_internal(int_buf, maps, propagate); + tok822_free_tree(tree->head); + quote_822_local(ext_buf, STR(int_buf)); + tree->head = tok822_scan(STR(ext_buf), &tree->tail); + vstring_free(int_buf); + vstring_free(ext_buf); + return (ret); +} + +/* smtp_map11_external - rewrite address external form */ + +int smtp_map11_external(VSTRING *addr, MAPS *maps, int propagate) +{ + VSTRING *temp = vstring_alloc(100); + int ret; + + unquote_822_local(temp, STR(addr)); + ret = smtp_map11_internal(temp, maps, propagate); + quote_822_local(addr, STR(temp)); + vstring_free(temp); + return (ret); +} + +#ifdef TEST +#include <ctype.h> + +#include <msg_vstream.h> +#include <readlline.h> +#include <stringops.h> +#include <vstring_vstream.h> + +#include <canon_addr.h> +#include <mail_params.h> + +/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */ + +VSTRING *canon_addr_external(VSTRING *result, const char *addr) +{ + char *at; + + vstring_strcpy(result, addr); + if ((at = strrchr(addr, '@')) == 0 + || (at + 1)[strcspn(at + 1, "\"\\")] != 0) + vstring_sprintf_append(result, "@%s", var_myorigin); + return (result); +} + +static NORETURN usage(const char *progname) +{ + msg_fatal("usage: %s [-v]", progname); +} + +int main(int argc, char **argv) +{ + VSTRING *read_buf = vstring_alloc(100); + MAPS *maps = 0; + int lineno; + int first_line; + char *bp; + char *cmd; + VSTRING *addr_buf = vstring_alloc(100); + char *addr_field; + char *res_field; + int ch; + int errs = 0; + + /* + * Initialize. + */ + msg_vstream_init(basename(argv[0]), VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "v")) > 0) { + switch (ch) { + case 'v': + msg_verbose++; + break; + default: + usage(argv[0]); + } + } + if (argc != optind) + usage(argv[0]); + + util_utf8_enable = 1; + mail_params_init(); + + /* + * TODO: Data-driven parameter settings. + */ +#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0) + + UPDATE(var_myhostname, "localhost.localdomain"); + UPDATE(var_mydomain, "localdomain"); + UPDATE(var_myorigin, "localdomain"); + UPDATE(var_mydest, "localhost.localdomain"); + UPDATE(var_rcpt_delim, "+"); + + /* + * The read-eval-print loop. + */ + while (readllines(read_buf, VSTREAM_IN, &lineno, &first_line)) { + bp = STR(read_buf); + if (msg_verbose) + msg_info("> %s", bp); + if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0 || *cmd == '#') + continue; + while (ISSPACE(*bp)) + bp++; + if (*bp == 0) + msg_fatal("missing parameter for command %s", cmd); + + /* + * Open maps. + */ + if (strcmp(cmd, "maps") == 0) { + if (maps) + maps_free(maps); + maps = maps_create(bp, bp, DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + vstream_printf("%s\n", bp); + continue; + } + + /* + * Lookup and verify. + */ + else if (maps != 0 && (strcmp(cmd, "external") == 0 + || strcmp(cmd, "internal") == 0 + || strcmp(cmd, "tree") == 0)) { + int have_result = 0; + + + /* + * Parse the input and expectations. + */ + if ((addr_field = mystrtok(&bp, ":")) == 0) + msg_fatal("missing address field"); + res_field = mystrtok(&bp, ":"); + if (mystrtok(&bp, ":") != 0) + msg_fatal("garbage after result field"); + + /* + * Perform the mapping. + */ + if (strcmp(cmd, "external") == 0) { + vstring_strcpy(addr_buf, addr_field); + have_result = smtp_map11_external(addr_buf, maps, 1); + } else if (maps && strcmp(cmd, "internal") == 0) { + vstring_strcpy(addr_buf, addr_field); + have_result = smtp_map11_internal(addr_buf, maps, 1); + } else if (maps && strcmp(cmd, "tree") == 0) { + TOK822 *tree; + TOK822 **addr_list; + TOK822 **tpp; + + tree = tok822_parse(addr_field); + addr_list = tok822_grep(tree, TOK822_ADDR); + for (tpp = addr_list; *tpp; tpp++) + have_result |= smtp_map11_tree(tpp[0], maps, 1); + myfree((void *) addr_list); + if (have_result) + tok822_externalize(addr_buf, tree, TOK822_STR_DEFL); + tok822_free_tree(tree); + } + + /* + * Summarize. + */ + vstream_printf("%s:%s -> %s\n", + cmd, addr_field, have_result ? + STR(addr_buf) : maps->error ? + "(error)" : "(no match)"); + vstream_fflush(VSTREAM_OUT); + + /* + * Enforce expectations. + */ + if (res_field && have_result) { + if (strcmp(res_field, STR(addr_buf)) != 0) { + msg_warn("expect result '%s' but got '%s'", + res_field, STR(addr_buf)); + errs = 1; + } + } else if (res_field && !have_result) { + msg_warn("expect result '%s' but got none", res_field); + errs = 1; + } else if (!res_field && have_result) { + msg_warn("expected no result but got '%s'", STR(addr_buf)); + errs = 1; + } + vstream_fflush(VSTREAM_OUT); + } + + /* + * Unknown request. + */ + else { + msg_fatal("bad request: %s", cmd); + } + } + vstring_free(addr_buf); + vstring_free(read_buf); + maps_free(maps); + return (errs != 0); +} + +#endif diff --git a/src/smtp/smtp_map11.in b/src/smtp/smtp_map11.in new file mode 100644 index 0000000..242859f --- /dev/null +++ b/src/smtp/smtp_map11.in @@ -0,0 +1,33 @@ +# Table with external-form mapping. +maps inline:{ + { foo@example.com = bar@com.example } + { bar@example.com = bar } + { baz@example.com = @com.example } + { splitme@example.com = "split me"@com.example } } + +# Tests for external form. +external foo@example.com:bar@com.example +external bar@example.com:bar@localdomain +external baz@example.com:baz@com.example +external foo@example.net +external splitme@example.com:"split me"@com.example +external splitme+ext@example.com:"split me+ext"@com.example +external "baz+first last"@example.com:"baz+first last"@com.example + +# Same tests for tree form. +tree foo@example.com:bar@com.example +tree bar@example.com:bar@localdomain +tree baz@example.com:baz@com.example +tree foo@example.net +tree splitme@example.com:"split me"@com.example +tree splitme+ext@example.com:"split me+ext"@com.example +tree "baz+first last"@example.com:"baz+first last"@com.example + +# Same tests for internal form. +internal foo@example.com:bar@com.example +internal bar@example.com:bar@localdomain +internal baz@example.com:baz@com.example +internal foo@example.net +internal splitme@example.com:split me@com.example +internal splitme+ext@example.com:split me+ext@com.example +internal baz+first last@example.com:baz+first last@com.example diff --git a/src/smtp/smtp_map11.ref b/src/smtp/smtp_map11.ref new file mode 100644 index 0000000..7e6f2ed --- /dev/null +++ b/src/smtp/smtp_map11.ref @@ -0,0 +1,22 @@ +inline:{ { foo@example.com = bar@com.example } { bar@example.com = bar } { baz@example.com = @com.example } { splitme@example.com = "split me"@com.example } } +external:foo@example.com -> bar@com.example +external:bar@example.com -> bar@localdomain +external:baz@example.com -> baz@com.example +external:foo@example.net -> (no match) +external:splitme@example.com -> "split me"@com.example +external:splitme+ext@example.com -> "split me+ext"@com.example +external:"baz+first last"@example.com -> "baz+first last"@com.example +tree:foo@example.com -> bar@com.example +tree:bar@example.com -> bar@localdomain +tree:baz@example.com -> baz@com.example +tree:foo@example.net -> (no match) +tree:splitme@example.com -> "split me"@com.example +tree:splitme+ext@example.com -> "split me+ext"@com.example +tree:"baz+first last"@example.com -> "baz+first last"@com.example +internal:foo@example.com -> bar@com.example +internal:bar@example.com -> bar@localdomain +internal:baz@example.com -> baz@com.example +internal:foo@example.net -> (no match) +internal:splitme@example.com -> split me@com.example +internal:splitme+ext@example.com -> split me+ext@com.example +internal:baz+first last@example.com -> baz+first last@com.example diff --git a/src/smtp/smtp_params.c b/src/smtp/smtp_params.c new file mode 100644 index 0000000..561f6a0 --- /dev/null +++ b/src/smtp/smtp_params.c @@ -0,0 +1,134 @@ + static const CONFIG_STR_TABLE smtp_str_table[] = { + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_SMTP_FALLBACK, DEF_SMTP_FALLBACK, &var_fallback_relay, 0, 0, + VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, + VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, + VAR_SMTP_SASL_PASSWD, DEF_SMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, + VAR_SMTP_SASL_OPTS, DEF_SMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, + VAR_SMTP_SASL_PATH, DEF_SMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0, +#ifdef USE_TLS + VAR_SMTP_SASL_TLS_OPTS, DEF_SMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0, + VAR_SMTP_SASL_TLSV_OPTS, DEF_SMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0, + VAR_SMTP_TLS_CHAIN_FILES, DEF_SMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0, + VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_SMTP_TLS_MAND_CIPH, DEF_SMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0, + VAR_SMTP_TLS_EXCL_CIPH, DEF_SMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0, + VAR_SMTP_TLS_MAND_EXCL, DEF_SMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0, + VAR_SMTP_TLS_MAND_PROTO, DEF_SMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0, + VAR_SMTP_TLS_VFY_CMATCH, DEF_SMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0, + VAR_SMTP_TLS_SEC_CMATCH, DEF_SMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0, + VAR_SMTP_TLS_FPT_CMATCH, DEF_SMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0, + VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_SMTP_TLS_TAFILE, DEF_SMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0, + VAR_SMTP_TLS_PROTO, DEF_SMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0, + VAR_SMTP_TLS_CIPH, DEF_SMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0, + VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, + VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0, + VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_SMTP_TLS_SNI, DEF_SMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0, + VAR_SMTP_TLS_INSECURE_MX_POLICY, DEF_SMTP_TLS_INSECURE_MX_POLICY, &var_smtp_tls_insecure_mx_policy, 0, 0, +#endif + VAR_SMTP_SASL_MECHS, DEF_SMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0, + VAR_SMTP_SASL_TYPE, DEF_SMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0, + VAR_SMTP_BIND_ADDR, DEF_SMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0, + VAR_SMTP_BIND_ADDR6, DEF_SMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0, + VAR_SMTP_VRFY_TGT, DEF_SMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0, + VAR_SMTP_HELO_NAME, DEF_SMTP_HELO_NAME, &var_smtp_helo_name, 1, 0, + VAR_SMTP_HOST_LOOKUP, DEF_SMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0, + VAR_SMTP_DNS_SUPPORT, DEF_SMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0, + VAR_SMTP_CACHE_DEST, DEF_SMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0, + VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0, + VAR_SMTP_EHLO_DIS_WORDS, DEF_SMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0, + VAR_SMTP_EHLO_DIS_MAPS, DEF_SMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0, + VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, + VAR_SMTP_TLS_LEVEL, DEF_SMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0, + VAR_SMTP_TLS_POLICY, DEF_SMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0, + VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, + VAR_SMTP_GENERIC_MAPS, DEF_SMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0, + VAR_SMTP_TCP_PORT, DEF_SMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0, + VAR_SMTP_PIX_BUG_WORDS, DEF_SMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0, + VAR_SMTP_PIX_BUG_MAPS, DEF_SMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0, + VAR_SMTP_SASL_AUTH_CACHE_NAME, DEF_SMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0, + VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0, + VAR_SMTP_HEAD_CHKS, DEF_SMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0, + VAR_SMTP_MIME_CHKS, DEF_SMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0, + VAR_SMTP_NEST_CHKS, DEF_SMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0, + VAR_SMTP_BODY_CHKS, DEF_SMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0, + VAR_SMTP_RESP_FILTER, DEF_SMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0, + VAR_SMTP_ADDR_PREF, DEF_SMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0, + VAR_SMTP_DNS_RES_OPT, DEF_SMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0, + VAR_SMTP_DSN_FILTER, DEF_SMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0, + VAR_SMTP_DNS_RE_FILTER, DEF_SMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0, + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, + 0, + }; + static const CONFIG_TIME_TABLE smtp_time_table[] = { + VAR_SMTP_CONN_TMOUT, DEF_SMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0, + VAR_SMTP_HELO_TMOUT, DEF_SMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0, + VAR_SMTP_XFWD_TMOUT, DEF_SMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0, + VAR_SMTP_MAIL_TMOUT, DEF_SMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0, + VAR_SMTP_RCPT_TMOUT, DEF_SMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0, + VAR_SMTP_DATA0_TMOUT, DEF_SMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0, + VAR_SMTP_DATA1_TMOUT, DEF_SMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0, + VAR_SMTP_DATA2_TMOUT, DEF_SMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0, + VAR_SMTP_RSET_TMOUT, DEF_SMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0, + VAR_SMTP_QUIT_TMOUT, DEF_SMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0, + VAR_SMTP_PIX_THRESH, DEF_SMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0, + VAR_SMTP_PIX_DELAY, DEF_SMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0, + VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0, + VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0, + VAR_SMTP_CACHE_CONNT, DEF_SMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0, + VAR_SMTP_REUSE_TIME, DEF_SMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0, +#ifdef USE_TLS + VAR_SMTP_STARTTLS_TMOUT, DEF_SMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0, +#endif + VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0, + VAR_SMTP_SASL_AUTH_CACHE_TIME, DEF_SMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0, + 0, + }; + static const CONFIG_INT_TABLE smtp_int_table[] = { + VAR_SMTP_LINE_LIMIT, DEF_SMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0, + VAR_SMTP_MXADDR_LIMIT, DEF_SMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0, + VAR_SMTP_MXSESS_LIMIT, DEF_SMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0, + VAR_SMTP_REUSE_COUNT, DEF_SMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0, +#ifdef USE_TLS + VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, +#endif + 0, + }; + static const CONFIG_BOOL_TABLE smtp_bool_table[] = { + VAR_SMTP_SKIP_5XX, DEF_SMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting, + VAR_IGN_MX_LOOKUP_ERR, DEF_IGN_MX_LOOKUP_ERR, &var_ign_mx_lookup_err, + VAR_SMTP_SKIP_QUIT_RESP, DEF_SMTP_SKIP_QUIT_RESP, &var_skip_quit_resp, + VAR_SMTP_ALWAYS_EHLO, DEF_SMTP_ALWAYS_EHLO, &var_smtp_always_ehlo, + VAR_SMTP_NEVER_EHLO, DEF_SMTP_NEVER_EHLO, &var_smtp_never_ehlo, + VAR_SMTP_SASL_ENABLE, DEF_SMTP_SASL_ENABLE, &var_smtp_sasl_enable, + VAR_SMTP_RAND_ADDR, DEF_SMTP_RAND_ADDR, &var_smtp_rand_addr, + VAR_SMTP_QUOTE_821_ENV, DEF_SMTP_QUOTE_821_ENV, &var_smtp_quote_821_env, + VAR_SMTP_DEFER_MXADDR, DEF_SMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr, + VAR_SMTP_SEND_XFORWARD, DEF_SMTP_SEND_XFORWARD, &var_smtp_send_xforward, + VAR_SMTP_CACHE_DEMAND, DEF_SMTP_CACHE_DEMAND, &var_smtp_cache_demand, + VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls, + VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_SMTP_TLS_CONN_REUSE, DEF_SMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse, +#ifdef USE_TLS + VAR_SMTP_TLS_ENFORCE_PN, DEF_SMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, + VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, + VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, + VAR_SMTP_TLS_FORCE_TLSA, DEF_SMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, +#endif + VAR_SMTP_TLS_WRAPPER, DEF_SMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, + VAR_SMTP_SENDER_AUTH, DEF_SMTP_SENDER_AUTH, &var_smtp_sender_auth, + VAR_SMTP_CNAME_OVERR, DEF_SMTP_CNAME_OVERR, &var_smtp_cname_overr, + VAR_SMTP_SASL_AUTH_SOFT_BOUNCE, DEF_SMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce, + VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final, + VAR_SMTP_REC_DEADLINE, DEF_SMTP_REC_DEADLINE, &var_smtp_rec_deadline, + VAR_SMTP_DUMMY_MAIL_AUTH, DEF_SMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth, + VAR_SMTP_BALANCE_INET_PROTO, DEF_SMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto, + 0, + }; diff --git a/src/smtp/smtp_proto.c b/src/smtp/smtp_proto.c new file mode 100644 index 0000000..a43a326 --- /dev/null +++ b/src/smtp/smtp_proto.c @@ -0,0 +1,2469 @@ +/*++ +/* NAME +/* smtp_proto 3 +/* SUMMARY +/* client SMTP/LMTP protocol +/* SYNOPSIS +/* #include "smtp.h" +/* +/* int smtp_helo(state) +/* SMTP_STATE *state; +/* +/* int smtp_xfer(state) +/* SMTP_STATE *state; +/* +/* int smtp_rset(state) +/* SMTP_STATE *state; +/* +/* int smtp_quit(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* In the subsequent text, SMTP implies LMTP. +/* This module implements the client side of the SMTP protocol. +/* +/* smtp_helo() performs the initial handshake with the SMTP server. +/* When TLS is enabled, this includes STARTTLS negotiations. +/* +/* smtp_xfer() sends message envelope information followed by the +/* message data, and finishes the SMTP conversation. These operations +/* are combined in one function, in order to implement SMTP pipelining. +/* Recipients are marked as "done" in the mail queue file when +/* bounced or delivered. The message delivery status is updated +/* accordingly. +/* +/* smtp_rset() sends a single RSET command and waits for the +/* response. In case of a negative reply it sets the +/* CANT_RSET_THIS_SESSION flag. +/* +/* smtp_quit() sends a single QUIT command and waits for the +/* response if configured to do so. It always turns off connection +/* caching. +/* DIAGNOSTICS +/* smtp_helo(), smtp_xfer(), smtp_rset() and smtp_quit() return +/* 0 in case of success, -1 in case of failure. For smtp_xfer(), +/* smtp_rset() and smtp_quit(), success means the ability to +/* perform an SMTP conversation, not necessarily the ability +/* to deliver mail, or the achievement of server happiness. +/* +/* In case of a rejected or failed connection, a connection +/* is marked as "bad, do not cache". Otherwise, connection +/* caching may be turned off (without being marked "bad") at +/* the discretion of the code that implements the individual +/* protocol steps. +/* +/* Warnings: corrupt message file. A corrupt message is marked +/* as "corrupt" by changing its queue file permissions. +/* BUGS +/* Some SMTP servers will abort when the number of recipients +/* for one message exceeds their capacity. This behavior violates +/* the SMTP protocol. +/* The only way around this is to limit the number of recipients +/* per transaction to an artificially-low value. +/* SEE ALSO +/* smtp(3h) internal data structures +/* smtp_chat(3) query/reply SMTP support +/* smtp_trouble(3) error handlers +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Pipelining code in cooperation with: +/* Jon Ribbens +/* Oaktree Internet Solutions Ltd., +/* Internet House, +/* Canal Basin, +/* Coventry, +/* CV1 4LY, United Kingdom. +/* +/* Connection caching in cooperation with: +/* Victor Duchovni +/* Morgan Stanley +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <sys/socket.h> /* shutdown(2) */ +#include <netinet/in.h> /* ntohs() */ +#include <string.h> +#include <unistd.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <time.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <stringops.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <split_at.h> +#include <name_code.h> +#include <name_mask.h> + +/* Global library. */ + +#include <mail_params.h> +#include <smtp_stream.h> +#include <mail_queue.h> +#include <recipient_list.h> +#include <deliver_request.h> +#include <defer.h> +#include <bounce.h> +#include <record.h> +#include <rec_type.h> +#include <off_cvt.h> +#include <mark_corrupt.h> +#include <quote_821_local.h> +#include <quote_822_local.h> +#include <mail_proto.h> +#include <mime_state.h> +#include <ehlo_mask.h> +#include <maps.h> +#include <tok822.h> +#include <mail_addr_map.h> +#include <ext_prop.h> +#include <namadr_list.h> +#include <match_parent_style.h> +#include <lex_822.h> +#include <dsn_mask.h> +#include <xtext.h> +#include <uxtext.h> +#include <smtputf8.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + + /* + * Sender and receiver state. A session does not necessarily go through a + * linear progression, but states are guaranteed to not jump backwards. + * Normal sessions go from MAIL->RCPT->DATA->DOT->QUIT->LAST. The states + * MAIL, RCPT, and DATA may also be followed by ABORT->QUIT->LAST. + * + * When connection caching is enabled, the QUIT state is suppressed. Normal + * sessions proceed as MAIL->RCPT->DATA->DOT->LAST, while aborted sessions + * end with ABORT->LAST. The connection is left open for a limited time. An + * RSET probe should be sent before attempting to reuse an open connection + * for a new transaction. + * + * The code to send an RSET probe is a special case with its own initial state + * and with its own dedicated state transitions. The session proceeds as + * RSET->LAST. This code is kept inside the main protocol engine for + * consistent error handling and error reporting. It is not to be confused + * with the code that sends RSET to abort a mail transaction in progress. + * + * The code to send QUIT without message delivery transaction jumps into the + * main state machine. If this introduces complications, then we should + * introduce a second QUIT state with its own dedicated state transitions, + * just like we did for RSET probes. + * + * By default, the receiver skips the QUIT response. Some SMTP servers + * disconnect after responding to ".", and some SMTP servers wait before + * responding to QUIT. + * + * Client states that are associated with sending mail (up to and including + * SMTP_STATE_DOT) must have smaller numerical values than the non-sending + * states (SMTP_STATE_ABORT .. SMTP_STATE_LAST). + */ +#define SMTP_STATE_XFORWARD_NAME_ADDR 0 +#define SMTP_STATE_XFORWARD_PROTO_HELO 1 +#define SMTP_STATE_MAIL 2 +#define SMTP_STATE_RCPT 3 +#define SMTP_STATE_DATA 4 +#define SMTP_STATE_DOT 5 +#define SMTP_STATE_ABORT 6 +#define SMTP_STATE_RSET 7 +#define SMTP_STATE_QUIT 8 +#define SMTP_STATE_LAST 9 + +int *xfer_timeouts[SMTP_STATE_LAST] = { + &var_smtp_xfwd_tmout, /* name/addr */ + &var_smtp_xfwd_tmout, /* helo/proto */ + &var_smtp_mail_tmout, + &var_smtp_rcpt_tmout, + &var_smtp_data0_tmout, + &var_smtp_data2_tmout, + &var_smtp_rset_tmout, + &var_smtp_rset_tmout, + &var_smtp_quit_tmout, +}; + +char *xfer_states[SMTP_STATE_LAST] = { + "sending XFORWARD name/address", + "sending XFORWARD protocol/helo_name", + "sending MAIL FROM", + "sending RCPT TO", + "sending DATA command", + "sending end of data -- message may be sent more than once", + "sending final RSET", + "sending RSET probe", + "sending QUIT", +}; + +char *xfer_request[SMTP_STATE_LAST] = { + "XFORWARD name/address command", + "XFORWARD helo/protocol command", + "MAIL FROM command", + "RCPT TO command", + "DATA command", + "end of DATA command", + "final RSET command", + "RSET probe", + "QUIT command", +}; + + /* + * Note: MIME downgrade never happens for mail that must be delivered with + * SMTPUTF8 (the sender requested SMTPUTF8, AND the delivery request + * involves at least one UTF-8 envelope address or header value. + */ +#define SMTP_MIME_DOWNGRADE(session, request) \ + (var_disable_mime_oconv == 0 \ + && (session->features & SMTP_FEATURE_8BITMIME) == 0 \ + && strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0) + +#ifdef USE_TLS + +static int smtp_start_tls(SMTP_STATE *); + +#endif + + /* + * Call-back information for header/body checks. We don't provide call-backs + * for actions that change the message delivery time or destination. + */ +static void smtp_hbc_logger(void *, const char *, const char *, const char *, const char *); +static void smtp_text_out(void *, int, const char *, ssize_t, off_t); + +HBC_CALL_BACKS smtp_hbc_callbacks[1] = { + smtp_hbc_logger, + smtp_text_out, +}; + +static int smtp_vrfy_tgt; + +/* smtp_vrfy_init - initialize */ + +void smtp_vrfy_init(void) +{ + static const NAME_CODE vrfy_init_table[] = { + SMTP_VRFY_TGT_RCPT, SMTP_STATE_RCPT, + SMTP_VRFY_TGT_DATA, SMTP_STATE_DATA, + 0, + }; + + if ((smtp_vrfy_tgt = name_code(vrfy_init_table, NAME_CODE_FLAG_NONE, + var_smtp_vrfy_tgt)) == 0) + msg_fatal("bad protocol stage: \"%s = %s\"", + VAR_SMTP_VRFY_TGT, var_smtp_vrfy_tgt); +} + +/* smtp_helo - perform initial handshake with SMTP server */ + +int smtp_helo(SMTP_STATE *state) +{ + const char *myname = "smtp_helo"; + SMTP_SESSION *session = state->session; + DELIVER_REQUEST *request = state->request; + SMTP_ITERATOR *iter = state->iterator; + SMTP_RESP *resp; + SMTP_RESP fake; + int except; + char *lines; + char *words; + char *word; + int n; + static const NAME_CODE xforward_features[] = { + XFORWARD_NAME, SMTP_FEATURE_XFORWARD_NAME, + XFORWARD_ADDR, SMTP_FEATURE_XFORWARD_ADDR, + XFORWARD_PORT, SMTP_FEATURE_XFORWARD_PORT, + XFORWARD_PROTO, SMTP_FEATURE_XFORWARD_PROTO, + XFORWARD_HELO, SMTP_FEATURE_XFORWARD_HELO, + XFORWARD_IDENT, SMTP_FEATURE_XFORWARD_IDENT, + XFORWARD_DOMAIN, SMTP_FEATURE_XFORWARD_DOMAIN, + 0, 0, + }; + const char *ehlo_words; + int discard_mask; + static const NAME_MASK pix_bug_table[] = { + PIX_BUG_DISABLE_ESMTP, SMTP_FEATURE_PIX_NO_ESMTP, + PIX_BUG_DELAY_DOTCRLF, SMTP_FEATURE_PIX_DELAY_DOTCRLF, + 0, + }; + const char *pix_bug_words; + const char *pix_bug_source; + int pix_bug_mask; + +#ifdef USE_TLS + int saved_features = session->features; + int tls_helo_status; + +#endif + const char *NOCLOBBER where; + + /* + * Skip the plaintext SMTP handshake when connecting in SMTPS mode. + */ +#ifdef USE_TLS + if (var_smtp_tls_wrappermode + && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + /* XXX Mix-up of per-session and per-request flags. */ + state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; + smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout, + var_smtp_rec_deadline); + tls_helo_status = smtp_start_tls(state); + state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS; + return (tls_helo_status); + } +#endif + + /* + * Prepare for disaster. + */ + smtp_stream_setup(state->session->stream, var_smtp_helo_tmout, + var_smtp_rec_deadline); + if ((except = vstream_setjmp(state->session->stream)) != 0) + return (smtp_stream_except(state, except, where)); + + /* + * If not recursing after STARTTLS, examine the server greeting banner + * and decide if we are going to send EHLO as the next command. + */ + if (var_smtp_tls_wrappermode + || (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + + /* + * Read and parse the server's SMTP greeting banner. + */ + where = "receiving the initial server greeting"; + switch ((resp = smtp_chat_resp(session))->code / 100) { + case 2: + break; + case 5: + if (var_smtp_skip_5xx_greeting) + STR(resp->dsn_buf)[0] = '4'; + /* FALLTHROUGH */ + default: + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + } + + /* + * If the policy table specifies a bogus TLS security level, fail + * now. + */ +#ifdef USE_TLS + if (state->tls->level == TLS_LEV_INVALID) + /* Warning is already logged. */ + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.0"), + "client TLS configuration problem")); +#endif + + /* + * XXX Some PIX firewall versions require flush before ".<CR><LF>" so + * it does not span a packet boundary. This hurts performance so it + * is not on by default. + */ + if (resp->str[strspn(resp->str, "20 *\t\n")] == 0) { + /* Best effort only. Ignore errors. */ + if (smtp_pix_bug_maps != 0 + && (pix_bug_words = + maps_find(smtp_pix_bug_maps, + STR(iter->addr), 0)) != 0) { + pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_MAPS); + } else { + pix_bug_words = var_smtp_pix_bug_words; + pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_WORDS); + } + if (*pix_bug_words) { + pix_bug_mask = name_mask_opt(pix_bug_source, pix_bug_table, + pix_bug_words, + NAME_MASK_ANY_CASE | NAME_MASK_IGNORE); + if ((pix_bug_mask & SMTP_FEATURE_PIX_DELAY_DOTCRLF) + && request->msg_stats.incoming_arrival.tv_sec + > vstream_ftime(state->session->stream) - var_smtp_pix_thresh) + pix_bug_mask &= ~SMTP_FEATURE_PIX_DELAY_DOTCRLF; + msg_info("%s: enabling PIX workarounds: %s for %s", + request->queue_id, + str_name_mask("pix workaround bitmask", + pix_bug_table, pix_bug_mask), + session->namaddrport); + session->features |= pix_bug_mask; + } + } + + /* + * See if we are talking to ourself. This should not be possible with + * the way we implement DNS lookups. However, people are known to + * sometimes screw up the naming service. And, mailer loops are still + * possible when our own mailer routing tables are mis-configured. + */ + words = resp->str; + (void) mystrtok(&words, "- \t\n"); + for (n = 0; (word = mystrtok(&words, " \t\n")) != 0; n++) { + if (n == 0 && strcasecmp(word, var_myhostname) == 0) { + if (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) + msg_warn("host %s greeted me with my own hostname %s", + session->namaddrport, var_myhostname); + } else if (strcasecmp(word, "ESMTP") == 0) + session->features |= SMTP_FEATURE_ESMTP; + } + if (smtp_mode) { + if (var_smtp_always_ehlo + && (session->features & SMTP_FEATURE_PIX_NO_ESMTP) == 0) + session->features |= SMTP_FEATURE_ESMTP; + if (var_smtp_never_ehlo + || (session->features & SMTP_FEATURE_PIX_NO_ESMTP) != 0) + session->features &= ~SMTP_FEATURE_ESMTP; + } else { + session->features |= SMTP_FEATURE_ESMTP; + } + } + + /* + * If recursing after STARTTLS, there is no server greeting banner. + * Always send EHLO as the next command. + */ + else { + session->features |= SMTP_FEATURE_ESMTP; + } + + /* + * Return the compliment. Fall back to SMTP if our ESMTP recognition + * heuristic failed. + */ + if (smtp_mode) { + where = "performing the EHLO handshake"; + if (session->features & SMTP_FEATURE_ESMTP) { + smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name); + if ((resp = smtp_chat_resp(session))->code / 100 != 2) { + if (resp->code == 421) + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + else + session->features &= ~SMTP_FEATURE_ESMTP; + } + } + if ((session->features & SMTP_FEATURE_ESMTP) == 0) { + where = "performing the HELO handshake"; + smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name); + if ((resp = smtp_chat_resp(session))->code / 100 != 2) + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + } + } else { + where = "performing the LHLO handshake"; + smtp_chat_cmd(session, "LHLO %s", var_smtp_helo_name); + if ((resp = smtp_chat_resp(session))->code / 100 != 2) + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + } + + /* + * No early returns allowed, to ensure consistent handling of TLS and + * SASL policies. + */ + if (session->features & SMTP_FEATURE_ESMTP) { + + /* + * Determine what server EHLO keywords to ignore, typically to avoid + * inter-operability problems. + */ + if (smtp_ehlo_dis_maps == 0 + || (ehlo_words = maps_find(smtp_ehlo_dis_maps, + STR(iter->addr), 0)) == 0) + ehlo_words = var_smtp_ehlo_dis_words; + if (smtp_ehlo_dis_maps && smtp_ehlo_dis_maps->error) { + msg_warn("%s: %s map lookup error for %s", + session->state->request->queue_id, + smtp_ehlo_dis_maps->title, STR(iter->addr)); + vstream_longjmp(session->stream, SMTP_ERR_DATA); + } + discard_mask = ehlo_mask(ehlo_words); + if (discard_mask && !(discard_mask & EHLO_MASK_SILENT)) + msg_info("discarding EHLO keywords: %s", + str_ehlo_mask(discard_mask)); + + /* + * Pick up some useful features offered by the SMTP server. XXX Until + * we have a portable routine to convert from string to off_t with + * proper overflow detection, ignore the message size limit + * advertised by the SMTP server. Otherwise, we might do the wrong + * thing when the server advertises a really huge message size limit. + * + * XXX Allow for "code (SP|-) ehlo-keyword (SP|=) ehlo-param...", + * because MicroSoft implemented AUTH based on an old draft. + */ + lines = resp->str; + for (n = 0; (words = mystrtok(&lines, "\n")) != 0; /* see below */ ) { + if (mystrtok(&words, "- ") + && (word = mystrtok(&words, " \t=")) != 0) { + if (n == 0) { + if (session->helo != 0) + myfree(session->helo); + + /* + * XXX: Keep the original case: we don't expect a single + * SMTP server to randomly change the case of its helo + * response. If different capitalization is detected, we + * should assume disjoint TLS caches. + */ + session->helo = mystrdup(word); + if (strcasecmp(word, var_myhostname) == 0 + && (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) != 0) { + msg_warn("host %s replied to HELO/EHLO" + " with my own hostname %s", + session->namaddrport, var_myhostname); + if (session->features & SMTP_FEATURE_BEST_MX) + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.4.6"), + "mail for %s loops back to myself", + request->nexthop)); + else + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.4.6"), + "mail for %s loops back to myself", + request->nexthop)); + } + } else if (strcasecmp(word, "8BITMIME") == 0) { + if ((discard_mask & EHLO_MASK_8BITMIME) == 0) + session->features |= SMTP_FEATURE_8BITMIME; + } else if (strcasecmp(word, "PIPELINING") == 0) { + if ((discard_mask & EHLO_MASK_PIPELINING) == 0) + session->features |= SMTP_FEATURE_PIPELINING; + } else if (strcasecmp(word, "XFORWARD") == 0) { + if ((discard_mask & EHLO_MASK_XFORWARD) == 0) + while ((word = mystrtok(&words, " \t")) != 0) + session->features |= + name_code(xforward_features, + NAME_CODE_FLAG_NONE, word); + } else if (strcasecmp(word, "SIZE") == 0) { + if ((discard_mask & EHLO_MASK_SIZE) == 0) { + session->features |= SMTP_FEATURE_SIZE; + if ((word = mystrtok(&words, " \t")) != 0) { + if (!alldig(word)) + msg_warn("bad EHLO SIZE limit \"%s\" from %s", + word, session->namaddrport); + else + session->size_limit = off_cvt_string(word); + } + } +#ifdef USE_TLS + } else if (strcasecmp(word, "STARTTLS") == 0) { + /* Ignored later if we already sent STARTTLS. */ + if ((discard_mask & EHLO_MASK_STARTTLS) == 0) + session->features |= SMTP_FEATURE_STARTTLS; +#endif +#ifdef USE_SASL_AUTH + } else if (var_smtp_sasl_enable + && strcasecmp(word, "AUTH") == 0) { + if ((discard_mask & EHLO_MASK_AUTH) == 0) + smtp_sasl_helo_auth(session, words); +#endif + } else if (strcasecmp(word, "DSN") == 0) { + if ((discard_mask & EHLO_MASK_DSN) == 0) + session->features |= SMTP_FEATURE_DSN; + } else if (strcasecmp(word, "SMTPUTF8") == 0) { + if ((discard_mask & EHLO_MASK_SMTPUTF8) == 0) + session->features |= SMTP_FEATURE_SMTPUTF8; + } + n++; + } + } + } + if (msg_verbose) + msg_info("server features: 0x%x size %.0f", + session->features, (double) session->size_limit); + + /* + * Decide if this delivery requires SMTPUTF8 server support. + * + * For now, we require that the remote SMTP server supports SMTPUTF8 when + * the sender requested SMTPUTF8 support. + * + * XXX EAI Refine this to: the sender requested SMTPUTF8 support AND the + * delivery request involves at least one UTF-8 envelope address or + * header value. + * + * If the sender requested SMTPUTF8 support but the delivery request + * involves no UTF-8 envelope address or header value, then we could + * still deliver such mail to a non-SMTPUTF8 server, except that we must + * either uxtext-encode ORCPT parameters or not send them. We cannot + * encode the ORCPT in xtext, because legacy SMTP requires that the + * unencoded address consist entirely of printable (graphic and white + * space) characters from the US-ASCII repertoire (RFC 3461 section 4). A + * correct uxtext encoder will produce a result that an xtext decoder + * will pass through unchanged. + * + * XXX Should we try to encode headers with RFC 2047 when delivering to a + * non-SMTPUTF8 server? That could make life easier for mailing lists. + */ +#define DELIVERY_REQUIRES_SMTPUTF8 \ + ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) \ + && (request->smtputf8 & ~SMTPUTF8_FLAG_REQUESTED)) + + /* + * Require that the server supports SMTPUTF8 when delivery requires + * SMTPUTF8. + * + * Fix 20140706: moved this before negotiating TLS, AUTH, and so on. + */ + if ((session->features & SMTP_FEATURE_SMTPUTF8) == 0 + && DELIVERY_REQUIRES_SMTPUTF8) + return (smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.6.7"), + "SMTPUTF8 is required, " + "but was not offered by host %s", + session->namaddr)); + + /* + * Fix 20140706: don't do silly things when the remote server announces + * SMTPUTF8 but not 8BITMIME support. Our primary mission is to deliver + * mail, not to force people into compliance. + */ + if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0 + && (session->features & SMTP_FEATURE_8BITMIME) == 0) { + msg_info("host %s offers SMTPUTF8 support, but not 8BITMIME", + session->namaddr); + session->features |= SMTP_FEATURE_8BITMIME; + } + + /* + * We use SMTP command pipelining if the server said it supported it. + * Since we use blocking I/O, RFC 2197 says that we should inspect the + * TCP window size and not send more than this amount of information. + * Unfortunately this information is unavailable using the sockets + * interface. However, we *can* get the TCP send buffer size on the local + * TCP/IP stack. We should be able to fill this buffer without being + * blocked, and then the kernel will effectively do non-blocking I/O for + * us by automatically writing out the contents of its send buffer while + * we are reading in the responses. In addition to TCP buffering we have + * to be aware of application-level buffering by the vstream module, + * which is limited to a couple kbytes. + * + * XXX No need to do this before and after STARTTLS, but it's not a big deal + * if we do. + * + * XXX When TLS is turned on, the SMTP-level writes will be encapsulated as + * TLS messages. Thus, the TCP-level payload will be larger than the + * SMTP-level payload. This has implications for the PIPELINING engine. + * + * To avoid deadlock, the PIPELINING engine needs to request a TCP send + * buffer size that can hold the unacknowledged commands plus the TLS + * encapsulation overhead. + * + * The PIPELINING engine keeps the unacknowledged command size <= the + * default VSTREAM buffer size (to avoid small-write performance issues + * when the VSTREAM buffer size is at its default size). With a default + * VSTREAM buffer size of 4096 there is no reason to increase the + * unacknowledged command size as the TCP MSS increases. It's safer to + * spread the remote SMTP server's recipient processing load over time, + * than dumping a very large recipient list all at once. + * + * For TLS encapsulation overhead we make a conservative guess: take the + * current protocol overhead of ~40 bytes, double the number for future + * proofing (~80 bytes), then round up the result to the nearest power of + * 2 (128 bytes). Plus, be prepared for worst-case compression that + * expands data by 1 kbyte, so that the worst-case SMTP payload per TLS + * message becomes 15 kbytes. + */ +#define PIPELINING_BUFSIZE VSTREAM_BUFSIZE +#ifdef USE_TLS +#define TLS_WORST_PAYLOAD 16384 +#define TLS_WORST_COMP_OVERHD 1024 +#define TLS_WORST_PROTO_OVERHD 128 +#define TLS_WORST_SMTP_PAYLOAD (TLS_WORST_PAYLOAD - TLS_WORST_COMP_OVERHD) +#define TLS_WORST_TOTAL_OVERHD (TLS_WORST_COMP_OVERHD + TLS_WORST_PROTO_OVERHD) +#endif + + if (session->features & SMTP_FEATURE_PIPELINING) { + SOCKOPT_SIZE optlen; + int tcp_bufsize; + int enc_overhead = 0; + + optlen = sizeof(tcp_bufsize); + if (getsockopt(vstream_fileno(session->stream), SOL_SOCKET, + SO_SNDBUF, (char *) &tcp_bufsize, &optlen) < 0) + msg_fatal("%s: getsockopt: %m", myname); +#ifdef USE_TLS + if (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) + enc_overhead += + (1 + (PIPELINING_BUFSIZE - 1) + / TLS_WORST_SMTP_PAYLOAD) * TLS_WORST_TOTAL_OVERHD; +#endif + if (tcp_bufsize < PIPELINING_BUFSIZE + enc_overhead) { + tcp_bufsize = PIPELINING_BUFSIZE + enc_overhead; + if (setsockopt(vstream_fileno(session->stream), SOL_SOCKET, + SO_SNDBUF, (char *) &tcp_bufsize, optlen) < 0) + msg_fatal("%s: setsockopt: %m", myname); + } + if (msg_verbose) + msg_info("Using %s PIPELINING, TCP send buffer size is %d, " + "PIPELINING buffer size is %d", + smtp_mode ? "ESMTP" : "LMTP", + tcp_bufsize, PIPELINING_BUFSIZE); + } +#ifdef USE_TLS + + /* + * Skip this part if we already sent STARTTLS. + */ + if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + + /* + * Optionally log unused STARTTLS opportunities. + */ + if ((session->features & SMTP_FEATURE_STARTTLS) && + var_smtp_tls_note_starttls_offer && + state->tls->level <= TLS_LEV_NONE) + msg_info("Host offered STARTTLS: [%s]", STR(iter->host)); + + /* + * Decide whether or not to send STARTTLS. + */ + if ((session->features & SMTP_FEATURE_STARTTLS) != 0 + && smtp_tls_ctx != 0 && state->tls->level >= TLS_LEV_MAY) { + + /* + * Prepare for disaster. + */ + smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout, + var_smtp_rec_deadline); + if ((except = vstream_setjmp(state->session->stream)) != 0) + return (smtp_stream_except(state, except, + "receiving the STARTTLS response")); + + /* + * Send STARTTLS. Recurse when the server accepts STARTTLS, after + * resetting the SASL and EHLO features lists. + * + * Reset the SASL mechanism list to avoid spurious warnings. + * + * Use the smtp_sasl_tls_security_options feature to allow SASL + * mechanisms that may not be allowed with plain-text + * connections. + */ + smtp_chat_cmd(session, "STARTTLS"); + if ((resp = smtp_chat_resp(session))->code / 100 == 2) { +#ifdef USE_SASL_AUTH + if (session->features & SMTP_FEATURE_AUTH) + smtp_sasl_cleanup(session); +#endif + session->features = saved_features; + /* XXX Mix-up of per-session and per-request flags. */ + state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; + tls_helo_status = smtp_start_tls(state); + state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS; + return (tls_helo_status); + } + + /* + * Give up if we must use TLS but the server rejects STARTTLS + * although support for it was announced in the EHLO response. + */ + session->features &= ~SMTP_FEATURE_STARTTLS; + if (TLS_REQUIRED(state->tls->level)) + return (smtp_site_fail(state, STR(iter->host), resp, + "TLS is required, but host %s refused to start TLS: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + /* Else try to continue in plain-text mode. */ + } + + /* + * Give up if we must use TLS but can't for various reasons. + * + * 200412 Be sure to provide the default clause at the bottom of this + * block. When TLS is required we must never, ever, end up in + * plain-text mode. + */ + if (TLS_REQUIRED(state->tls->level)) { + if (!(session->features & SMTP_FEATURE_STARTTLS)) { + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.4"), + "TLS is required, but was not offered by host %s", + session->namaddr)); + } else if (smtp_tls_ctx == 0) { + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "TLS is required, but our TLS engine is unavailable")); + } else { + msg_warn("%s: TLS is required but unavailable, don't know why", + myname); + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.0"), + "TLS is required, but unavailable")); + } + } + } +#endif +#ifdef USE_SASL_AUTH + if (var_smtp_sasl_enable && (session->features & SMTP_FEATURE_AUTH)) + return (smtp_sasl_helo_login(state)); +#endif + + return (0); +} + +#ifdef USE_TLS + +/* smtp_start_tls - turn on TLS and recurse into the HELO dialog */ + +static int smtp_start_tls(SMTP_STATE *state) +{ + SMTP_SESSION *session = state->session; + SMTP_ITERATOR *iter = state->iterator; + TLS_CLIENT_START_PROPS start_props; + VSTRING *serverid; + SMTP_RESP fake; + TLS_CLIENT_INIT_PROPS init_props; + VSTREAM *tlsproxy; + VSTRING *port_buf; + + /* + * When the TLS handshake succeeds, we can reuse a connection only if TLS + * remains turned on for the lifetime of that connection. This requires + * that the TLS library state is maintained in some proxy process, for + * example, in tlsproxy(8). We then store the proxy file handle in the + * connection cache, and reuse that file handle. + * + * Otherwise, we must turn off connection caching. We can't turn off TLS in + * one SMTP client process, save the open connection to a cache which is + * shared with all SMTP clients, migrate the connection to another SMTP + * client, and resume TLS there. When the TLS handshake fails, we can't + * reuse the SMTP connection either, because the conversation is in an + * unknown state. + */ + if (state->tls->conn_reuse == 0) + DONT_CACHE_THIS_SESSION; + + /* + * The following assumes sites that use TLS in a perverse configuration: + * multiple hosts per hostname, or even multiple hosts per IP address. + * All this without a shared TLS session cache, and they still want to + * use TLS session caching??? + * + * The TLS session cache records the trust chain verification status of + * cached sessions. Different transports may have different CAfile or + * CApath settings, perhaps to allow authenticated connections to sites + * with private CA certs without trusting said private certs for other + * sites. So we cannot assume that a trust chain valid for one transport + * is valid for another. Therefore the client session id must include + * either the transport name or the values of CAfile and CApath. We use + * the transport name. + * + * XXX: We store only one session per lookup key. Ideally the the key maps + * 1-to-1 to a server TLS session cache. We use the IP address, port and + * ehlo response name to build a lookup key that works for split caches + * (that announce distinct names) behind a load balancer. + * + * XXX: The TLS library will salt the serverid with further details of the + * protocol and cipher requirements including the server ehlo response. + * Deferring the helo to the digested suffix results in more predictable + * SSL session lookup key lengths. + */ + serverid = vstring_alloc(10); + smtp_key_prefix(serverid, "&", state->iterator, SMTP_KEY_FLAG_SERVICE + | SMTP_KEY_FLAG_CUR_NEXTHOP /* With port */ + | SMTP_KEY_FLAG_HOSTNAME + | SMTP_KEY_FLAG_ADDR); + + if (state->tls->conn_reuse) { + TLS_CLIENT_PARAMS tls_params; + + /* + * Send all our wishes in one big request. + */ + TLS_PROXY_CLIENT_INIT_PROPS(&init_props, + log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL), + log_level = var_smtp_tls_loglevel, + verifydepth = var_smtp_tls_scert_vd, + cache_type + = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE), + chain_files = var_smtp_tls_chain_files, + cert_file = var_smtp_tls_cert_file, + key_file = var_smtp_tls_key_file, + dcert_file = var_smtp_tls_dcert_file, + dkey_file = var_smtp_tls_dkey_file, + eccert_file = var_smtp_tls_eccert_file, + eckey_file = var_smtp_tls_eckey_file, + CAfile = var_smtp_tls_CAfile, + CApath = var_smtp_tls_CApath, + mdalg = var_smtp_tls_fpt_dgst); + TLS_PROXY_CLIENT_START_PROPS(&start_props, + timeout = var_smtp_starttls_tmout, + tls_level = state->tls->level, + nexthop = session->tls_nexthop, + host = STR(iter->host), + namaddr = session->namaddrport, + sni = state->tls->sni, + serverid = vstring_str(serverid), + helo = session->helo, + protocols = state->tls->protocols, + cipher_grade = state->tls->grade, + cipher_exclusions + = vstring_str(state->tls->exclusions), + matchargv = state->tls->matchargv, + mdalg = var_smtp_tls_fpt_dgst, + dane = state->tls->dane); + + /* + * The tlsproxy(8) server enforces timeouts that are larger than + * those specified by the tlsproxy(8) client. These timeouts are a + * safety net for the case that the tlsproxy(8) client fails to + * enforce time limits. Normally, the tlsproxy(8) client would time + * out and trigger a plaintext event in the tlsproxy(8) server, and + * cause it to tear down the session. + * + * However, the tlsproxy(8) server has no insight into the SMTP + * protocol, and therefore it cannot by itself support different + * timeouts at different SMTP protocol stages. Instead, we specify + * the largest timeout (end-of-data) and rely on the SMTP client to + * time out first, which normally results in a plaintext event in the + * tlsproxy(8) server. Unfortunately, we cannot permit plaintext + * events during the TLS handshake, so we specify a separate timeout + * for that stage (the end-of-data timeout would be unreasonably + * large anyway). + */ +#define PROXY_OPEN_FLAGS \ + (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT) + + port_buf = vstring_alloc(100); /* minimize fragmentation */ + vstring_sprintf(port_buf, "%d", ntohs(iter->port)); + tlsproxy = + tls_proxy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS, + session->stream, STR(iter->addr), + STR(port_buf), var_smtp_starttls_tmout, + var_smtp_data2_tmout, state->service, + tls_proxy_client_param_from_config(&tls_params), + &init_props, &start_props); + vstring_free(port_buf); + + /* + * To insert tlsproxy(8) between this process and the remote SMTP + * server, we swap the file descriptors between the tlsproxy and + * session->stream VSTREAMS, so that we don't lose all the + * user-configurable session->stream attributes (such as longjump + * buffers or timeouts). + * + * TODO: the tlsproxy RPCs should return more error detail than a "NO" + * result. OTOH, the in-process TLS engine does not return such info + * either. + * + * If the tlsproxy request fails we do not fall back to the in-process + * TLS stack. Reason: the admin enabled connection reuse to respect + * receiver policy; silently violating such policy would not be + * useful. + * + * We also don't fall back to the in-process TLS stack under low-traffic + * conditions, to avoid frustrating attempts to debug a problem with + * using the tlsproxy(8) service. + */ + if (tlsproxy == 0) { + session->tls_context = 0; + } else { + vstream_control(tlsproxy, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_END); + vstream_control(session->stream, + CA_VSTREAM_CTL_SWAP_FD(tlsproxy), + CA_VSTREAM_CTL_END); + (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */ + + /* + * There must not be any pending data in the stream buffers + * before we read the TLS context attributes. + */ + vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); + + /* + * After plumbing the plaintext stream, receive the TLS context + * object. For this we use the same VSTREAM buffer that we also + * use to receive subsequent SMTP commands, therefore we must be + * prepared for the possibility that the remote SMTP server + * starts talking immediately. The tlsproxy implementation sends + * the TLS context before remote content. The attribute protocol + * is robust enough that an adversary cannot insert their own TLS + * context attributes. + */ + session->tls_context = tls_proxy_context_receive(session->stream); + if (session->tls_context) { + session->features |= SMTP_FEATURE_FROM_PROXY; + tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW, + session->tls_context); + } + } + } else { /* state->tls->conn_reuse */ + + /* + * As of Postfix 2.5, tls_client_start() tries hard to always + * complete the TLS handshake. It records the verification and match + * status in the resulting TLScontext. It is now up to the + * application to abort the TLS connection if it chooses. + * + * XXX When tls_client_start() fails then we don't know what state the + * SMTP connection is in, so we give up on this connection even if we + * are not required to use TLS. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + session->tls_context = + TLS_CLIENT_START(&start_props, + ctx = smtp_tls_ctx, + stream = session->stream, + fd = -1, + timeout = var_smtp_starttls_tmout, + tls_level = state->tls->level, + nexthop = session->tls_nexthop, + host = STR(iter->host), + namaddr = session->namaddrport, + sni = state->tls->sni, + serverid = vstring_str(serverid), + helo = session->helo, + protocols = state->tls->protocols, + cipher_grade = state->tls->grade, + cipher_exclusions + = vstring_str(state->tls->exclusions), + matchargv = state->tls->matchargv, + mdalg = var_smtp_tls_fpt_dgst, + dane = state->tls->dane); + + /* + * At this point there must not be any pending data in the stream + * buffers. + */ + vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); + } /* state->tls->conn_reuse */ + + vstring_free(serverid); + + if (session->tls_context == 0) { + + /* + * We must avoid further I/O, the peer is in an undefined state. + */ + DONT_USE_FORBIDDEN_SESSION; + + /* + * If TLS is optional, try delivery to the same server over a + * plaintext connection. Otherwise we would defer mail forever with + * destinations that have no alternate MX host. + * + * Don't fall back to plaintext if we were willing to use SASL-over-TLS + * authentication. If the server doesn't announce SASL support over + * plaintext connections, then we don't want delivery to fail with + * "relay access denied". + * + * If TLS is opportunistic, don't throttle the destination, otherwise if + * the mail is volume is high enough we may have difficulty ever + * draining even the deferred mail, as new mail provides a constant + * stream of negative feedback. + */ + if (PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE) + RETRY_AS_PLAINTEXT; + return (smtp_misc_fail(state, state->tls->level == TLS_LEV_MAY ? + SMTP_NOTHROTTLE : SMTP_THROTTLE, + DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "Cannot start TLS: handshake failure")); + } + + /* + * If we are verifying the server certificate and are not happy with the + * result, abort the delivery here. We have a usable TLS session with the + * server, so no need to disable I/O, ... we can even be polite and send + * "QUIT". + * + * See src/tls/tls_level.c and src/tls/tls.h. Levels above "encrypt" require + * matching. Levels >= "dane" require CA or DNSSEC trust. + * + * When DANE TLSA records specify an end-entity certificate, the trust and + * match bits always coincide, but it is fine to report the wrong + * end-entity certificate as untrusted rather than unmatched. + */ + if (TLS_MUST_TRUST(state->tls->level)) + if (!TLS_CERT_IS_TRUSTED(session->tls_context)) + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "Server certificate not trusted")); + if (TLS_MUST_MATCH(state->tls->level)) + if (!TLS_CERT_IS_MATCHED(session->tls_context)) + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "Server certificate not verified")); + + /* + * At this point we have to re-negotiate the "EHLO" to reget the + * feature-list. + */ + return (smtp_helo(state)); +} + +#endif + +/* smtp_hbc_logger - logging call-back for header/body checks */ + +static void smtp_hbc_logger(void *context, const char *action, + const char *where, const char *content, + const char *text) +{ + const SMTP_STATE *state = (SMTP_STATE *) context; + + if (*text) { + msg_info("%s: %s: %s %.60s: %s", + state->request->queue_id, action, where, content, text); + } else { + msg_info("%s: %s: %s %.60s", + state->request->queue_id, action, where, content); + } +} + +/* smtp_text_out - output one header/body record */ + +static void smtp_text_out(void *context, int rec_type, + const char *text, ssize_t len, + off_t unused_offset) +{ + SMTP_STATE *state = (SMTP_STATE *) context; + SMTP_SESSION *session = state->session; + ssize_t data_left; + const char *data_start; + + /* + * Deal with an impedance mismatch between Postfix queue files (record + * length <= $message_line_length_limit) and SMTP (DATA record length <= + * $smtp_line_length_limit). The code below does a little too much work + * when the SMTP line length limit is disabled, but it avoids code + * duplication, and thus, it avoids testing and maintenance problems. + */ + data_left = len; + data_start = text; + do { + if (state->space_left == var_smtp_line_limit + && data_left > 0 && *data_start == '.') + smtp_fputc('.', session->stream); + if (var_smtp_line_limit > 0 && data_left >= state->space_left) { + smtp_fputs(data_start, state->space_left, session->stream); + data_start += state->space_left; + data_left -= state->space_left; + state->space_left = var_smtp_line_limit; + if (data_left > 0 || rec_type == REC_TYPE_CONT) { + smtp_fputc(' ', session->stream); + state->space_left -= 1; + } + } else { + if (rec_type == REC_TYPE_CONT) { + smtp_fwrite(data_start, data_left, session->stream); + state->space_left -= data_left; + } else { + smtp_fputs(data_start, data_left, session->stream); + state->space_left = var_smtp_line_limit; + } + break; + } + } while (data_left > 0); +} + +/* smtp_format_out - output one header/body record */ + +static void PRINTFLIKE(3, 4) smtp_format_out(void *, int, const char *,...); + +static void smtp_format_out(void *context, int rec_type, const char *fmt,...) +{ + static VSTRING *vp; + va_list ap; + + if (vp == 0) + vp = vstring_alloc(100); + va_start(ap, fmt); + vstring_vsprintf(vp, fmt, ap); + va_end(ap); + smtp_text_out(context, rec_type, vstring_str(vp), VSTRING_LEN(vp), 0); +} + +/* smtp_header_out - output one message header */ + +static void smtp_header_out(void *context, int unused_header_class, + const HEADER_OPTS *unused_info, + VSTRING *buf, off_t offset) +{ + char *start = vstring_str(buf); + char *line; + char *next_line; + + /* + * This code destroys the header. We could try to avoid clobbering it, + * but we're not going to use the data any further. + */ + for (line = start; line; line = next_line) { + next_line = split_at(line, '\n'); + smtp_text_out(context, REC_TYPE_NORM, line, next_line ? + next_line - line - 1 : strlen(line), offset); + } +} + +/* smtp_header_rewrite - rewrite message header before output */ + +static void smtp_header_rewrite(void *context, int header_class, + const HEADER_OPTS *header_info, + VSTRING *buf, off_t offset) +{ + SMTP_STATE *state = (SMTP_STATE *) context; + int did_rewrite = 0; + char *line; + char *start; + char *next_line; + char *end_line; + char *result; + + /* + * Apply optional header filtering. + */ + if (smtp_header_checks) { + result = hbc_header_checks(context, smtp_header_checks, header_class, + header_info, buf, offset); + if (result == 0) + return; + if (result == HBC_CHECKS_STAT_ERROR) { + msg_warn("%s: smtp header checks lookup error", + state->request->queue_id); + vstream_longjmp(state->session->stream, SMTP_ERR_DATA); + } + if (result != STR(buf)) { + vstring_strcpy(buf, result); + myfree(result); + } + } + + /* + * Rewrite primary header addresses that match the smtp_generic_maps. The + * cleanup server already enforces that all headers have proper lengths + * and that all addresses are in proper form, so we don't have to repeat + * that. + */ + if (smtp_generic_maps && header_info && header_class == MIME_HDR_PRIMARY + && (header_info->flags & (HDR_OPT_SENDER | HDR_OPT_RECIP)) != 0) { + TOK822 *tree; + TOK822 **addr_list; + TOK822 **tpp; + + tree = tok822_parse(vstring_str(buf) + + strlen(header_info->name) + 1); + addr_list = tok822_grep(tree, TOK822_ADDR); + for (tpp = addr_list; *tpp; tpp++) + did_rewrite |= smtp_map11_tree(tpp[0], smtp_generic_maps, + smtp_ext_prop_mask & EXT_PROP_GENERIC); + if (did_rewrite) { + vstring_truncate(buf, strlen(header_info->name)); + vstring_strcat(buf, ": "); + tok822_externalize(buf, tree, TOK822_STR_HEAD); + } + myfree((void *) addr_list); + tok822_free_tree(tree); + } + + /* + * Pass through unmodified headers without reconstruction. + */ + if (did_rewrite == 0) { + smtp_header_out(context, header_class, header_info, buf, offset); + return; + } + + /* + * A rewritten address list contains one address per line. The code below + * replaces newlines by spaces, to fit as many addresses on a line as + * possible (without rearranging the order of addresses). Prepending + * white space to the beginning of lines is delegated to the output + * routine. + * + * Code derived from cleanup_fold_header(). + */ + for (line = start = vstring_str(buf); line != 0; line = next_line) { + end_line = line + strcspn(line, "\n"); + if (line > start) { + if (end_line - start < 70) { /* TAB counts as one */ + line[-1] = ' '; + } else { + start = line; + } + } + next_line = *end_line ? end_line + 1 : 0; + } + + /* + * Prepend a tab to continued header lines that went through the address + * rewriting machinery. Just like smtp_header_out(), this code destroys + * the header. We could try to avoid clobbering it, but we're not going + * to use the data any further. + * + * Code derived from cleanup_out_header(). + */ + for (line = start = vstring_str(buf); line != 0; line = next_line) { + next_line = split_at(line, '\n'); + if (line == start || IS_SPACE_TAB(*line)) { + smtp_text_out(state, REC_TYPE_NORM, line, next_line ? + next_line - line - 1 : strlen(line), offset); + } else { + smtp_format_out(state, REC_TYPE_NORM, "\t%s", line); + } + } +} + +/* smtp_body_rewrite - rewrite message body before output */ + +static void smtp_body_rewrite(void *context, int type, + const char *buf, ssize_t len, + off_t offset) +{ + SMTP_STATE *state = (SMTP_STATE *) context; + char *result; + + /* + * Apply optional body filtering. + */ + if (smtp_body_checks) { + result = hbc_body_checks(context, smtp_body_checks, buf, len, offset); + if (result == buf) { + smtp_text_out(state, type, buf, len, offset); + } else if (result == HBC_CHECKS_STAT_ERROR) { + msg_warn("%s: smtp body checks lookup error", + state->request->queue_id); + vstream_longjmp(state->session->stream, SMTP_ERR_DATA); + } else if (result != 0) { + smtp_text_out(state, type, result, strlen(result), offset); + myfree(result); + } + } +} + +/* smtp_mime_fail - MIME problem */ + +static void smtp_mime_fail(SMTP_STATE *state, int mime_errs) +{ + const MIME_STATE_DETAIL *detail; + SMTP_RESP fake; + + detail = mime_state_detail(mime_errs); + smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, detail->dsn), + "%s", detail->text); +} + +/* smtp_loop - exercise the SMTP protocol engine */ + +static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state, + NOCLOBBER int recv_state) +{ + const char *myname = "smtp_loop"; + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + SMTP_ITERATOR *iter = state->iterator; + SMTP_RESP *resp; + RECIPIENT *rcpt; + VSTRING *next_command = vstring_alloc(100); + int *NOCLOBBER survivors = 0; + NOCLOBBER int next_state; + NOCLOBBER int next_rcpt; + NOCLOBBER int send_rcpt; + NOCLOBBER int recv_rcpt; + NOCLOBBER int nrcpt; + NOCLOBBER int recv_done; + int except; + int rec_type; + NOCLOBBER int prev_type = 0; + NOCLOBBER int mail_from_rejected; + NOCLOBBER int downgrading; + int mime_errs; + SMTP_RESP fake; + int fail_status; + + /* + * Macros for readability. + */ +#define REWRITE_ADDRESS(dst, src) do { \ + vstring_strcpy(dst, src); \ + if (*(src) && smtp_generic_maps) \ + smtp_map11_internal(dst, smtp_generic_maps, \ + smtp_ext_prop_mask & EXT_PROP_GENERIC); \ + } while (0) + +#define QUOTE_ADDRESS(dst, src) do { \ + if (*(src) && var_smtp_quote_821_env) { \ + quote_821_local(dst, src); \ + } else { \ + vstring_strcpy(dst, src); \ + } \ + } while (0) + + /* Caution: changes to RETURN() also affect code outside the main loop. */ + +#define RETURN(x) do { \ + if (recv_state != SMTP_STATE_LAST) \ + DONT_CACHE_THIS_SESSION; \ + vstring_free(next_command); \ + if (survivors) \ + myfree((void *) survivors); \ + if (session->mime_state) \ + session->mime_state = mime_state_free(session->mime_state); \ + return (x); \ + } while (0) + +#define SENDER_IS_AHEAD \ + (recv_state < send_state || recv_rcpt != send_rcpt) + +#define SENDER_IN_WAIT_STATE \ + (send_state == SMTP_STATE_DOT || send_state == SMTP_STATE_LAST) + +#define SENDING_MAIL \ + (recv_state <= SMTP_STATE_DOT) + +#define CANT_RSET_THIS_SESSION \ + (session->features |= SMTP_FEATURE_RSET_REJECTED) + + /* + * Pipelining support requires two loops: one loop for sending and one + * for receiving. Each loop has its own independent state. Most of the + * time the sender can run ahead of the receiver by as much as the TCP + * send buffer permits. There are only two places where the sender must + * wait for status information from the receiver: once after sending DATA + * and once after sending QUIT. + * + * The sender state advances until the TCP send buffer would overflow, or + * until the sender needs status information from the receiver. At that + * point the receiver starts processing responses. Once the receiver has + * caught up with the sender, the sender resumes sending commands. If the + * receiver detects a serious problem (MAIL FROM rejected, all RCPT TO + * commands rejected, DATA rejected) it forces the sender to abort the + * SMTP dialog with RSET and QUIT. + */ + nrcpt = 0; + next_rcpt = send_rcpt = recv_rcpt = recv_done = 0; + mail_from_rejected = 0; + + /* + * Prepare for disaster. This should not be needed because the design + * guarantees that no output is flushed before smtp_chat_resp() is + * called. + * + * 1) Every SMTP command fits entirely in a VSTREAM output buffer. + * + * 2) smtp_loop() never invokes smtp_chat_cmd() without making sure that + * there is sufficient space for the command in the output buffer. + * + * 3) smtp_loop() flushes the output buffer to avoid server timeouts. + * + * Changing any of these would violate the design, and would likely break + * SMTP pipelining. + * + * We set up the error handler anyway (only upon entry to avoid wasting + * resources) because 1) there is code below that expects that VSTREAM + * timeouts are enabled, and 2) this allows us to detect if someone broke + * Postfix by introducing spurious flush before read operations. + */ + if (send_state < SMTP_STATE_XFORWARD_NAME_ADDR + || send_state > SMTP_STATE_QUIT) + msg_panic("%s: bad sender state %d (receiver state %d)", + myname, send_state, recv_state); + smtp_stream_setup(session->stream, *xfer_timeouts[send_state], + var_smtp_rec_deadline); + if ((except = vstream_setjmp(session->stream)) != 0) { + msg_warn("smtp_proto: spurious flush before read in send state %d", + send_state); + RETURN(SENDING_MAIL ? smtp_stream_except(state, except, + xfer_states[send_state]) : -1); + } + + /* + * The main protocol loop. + */ + do { + + /* + * Build the next command. + */ + switch (send_state) { + + /* + * Sanity check. + */ + default: + msg_panic("%s: bad sender state %d", myname, send_state); + + /* + * Build the XFORWARD command. With properly sanitized + * information, the command length stays within the 512 byte + * command line length limit. + * + * XXX smtpd_xforward_preset() initializes some fields as "unknown" + * and some as null; historically, pickup(8) does not send any of + * these, and the queue manager presets absent fields to "not + * available" except for the rewrite context which is preset to + * local by way of migration aid. These definitions need to be + * centralized for maintainability. + */ +#ifndef CAN_FORWARD_CLIENT_NAME +#define _ATTR_AVAIL_AND_KNOWN_(val) \ + (DEL_REQ_ATTR_AVAIL(val) && strcasecmp((val), "unknown")) +#define CAN_FORWARD_CLIENT_NAME _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_CLIENT_ADDR _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_CLIENT_PORT _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_PROTO_NAME _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_HELO_NAME DEL_REQ_ATTR_AVAIL +#define CAN_FORWARD_IDENT_NAME DEL_REQ_ATTR_AVAIL +#define CAN_FORWARD_RWR_CONTEXT DEL_REQ_ATTR_AVAIL +#endif + + case SMTP_STATE_XFORWARD_NAME_ADDR: + vstring_strcpy(next_command, XFORWARD_CMD); + if ((session->features & SMTP_FEATURE_XFORWARD_NAME) + && CAN_FORWARD_CLIENT_NAME(request->client_name)) { + vstring_strcat(next_command, " " XFORWARD_NAME "="); + xtext_quote_append(next_command, request->client_name, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_ADDR) + && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) { + vstring_strcat(next_command, " " XFORWARD_ADDR "="); + xtext_quote_append(next_command, request->client_addr, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_PORT) + && CAN_FORWARD_CLIENT_PORT(request->client_port)) { + vstring_strcat(next_command, " " XFORWARD_PORT "="); + xtext_quote_append(next_command, request->client_port, ""); + } + if (session->send_proto_helo) + next_state = SMTP_STATE_XFORWARD_PROTO_HELO; + else + next_state = SMTP_STATE_MAIL; + break; + + case SMTP_STATE_XFORWARD_PROTO_HELO: + vstring_strcpy(next_command, XFORWARD_CMD); + if ((session->features & SMTP_FEATURE_XFORWARD_PROTO) + && CAN_FORWARD_PROTO_NAME(request->client_proto)) { + vstring_strcat(next_command, " " XFORWARD_PROTO "="); + xtext_quote_append(next_command, request->client_proto, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_HELO) + && CAN_FORWARD_HELO_NAME(request->client_helo)) { + vstring_strcat(next_command, " " XFORWARD_HELO "="); + xtext_quote_append(next_command, request->client_helo, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_IDENT) + && CAN_FORWARD_IDENT_NAME(request->log_ident)) { + vstring_strcat(next_command, " " XFORWARD_IDENT "="); + xtext_quote_append(next_command, request->log_ident, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN) + && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)) { + vstring_strcat(next_command, " " XFORWARD_DOMAIN "="); + xtext_quote_append(next_command, + strcmp(request->rewrite_context, MAIL_ATTR_RWR_LOCAL) ? + XFORWARD_DOM_REMOTE : XFORWARD_DOM_LOCAL, ""); + } + next_state = SMTP_STATE_MAIL; + break; + + /* + * Build the MAIL FROM command. + */ + case SMTP_STATE_MAIL: + request->msg_stats.reuse_count = session->reuse_count; + GETTIMEOFDAY(&request->msg_stats.conn_setup_done); + REWRITE_ADDRESS(session->scratch2, request->sender); + QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2)); + vstring_sprintf(next_command, "MAIL FROM:<%s>", + vstring_str(session->scratch)); + /* XXX Don't announce SIZE if we're going to MIME downgrade. */ + if (session->features & SMTP_FEATURE_SIZE /* RFC 1870 */ + && !SMTP_MIME_DOWNGRADE(session, request)) + vstring_sprintf_append(next_command, " SIZE=%lu", + request->data_size); + if (session->features & SMTP_FEATURE_8BITMIME) { /* RFC 1652 */ + if (strcmp(request->encoding, MAIL_ATTR_ENC_8BIT) == 0) + vstring_strcat(next_command, " BODY=8BITMIME"); + else if (strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) == 0) + vstring_strcat(next_command, " BODY=7BIT"); + else if (strcmp(request->encoding, MAIL_ATTR_ENC_NONE) != 0) + msg_warn("%s: unknown content encoding: %s", + request->queue_id, request->encoding); + } + if (session->features & SMTP_FEATURE_DSN) { + if (request->dsn_envid[0]) { + vstring_sprintf_append(next_command, " ENVID="); + xtext_quote_append(next_command, request->dsn_envid, "+="); + } + if (request->dsn_ret) + vstring_sprintf_append(next_command, " RET=%s", + dsn_ret_str(request->dsn_ret)); + } + + /* + * Request SMTPUTF8 when the remote SMTP server supports SMTPUTF8 + * and the sender requested SMTPUTF8 support. + * + * If the sender requested SMTPUTF8 but the remote SMTP server does + * not support SMTPUTF8, then we have already determined earlier + * that delivering this message without SMTPUTF8 will not break + * the SMTPUTF8 promise that was made to the sender. + */ + if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0 + && (request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) != 0) + vstring_strcat(next_command, " SMTPUTF8"); + + /* + * We authenticate the local MTA only, but not the sender. + */ +#ifdef USE_SASL_AUTH + if (var_smtp_sasl_enable + && var_smtp_dummy_mail_auth + && (session->features & SMTP_FEATURE_AUTH)) + vstring_strcat(next_command, " AUTH=<>"); +#endif + + /* + * CVE-2009-3555 (TLS renegotiation). Try to detect a mail + * hijacking attack that prepends malicious EHLO/MAIL/RCPT/DATA + * commands to our TLS session. + * + * For the attack to succeed, the remote SMTP server must reply to + * the malicious EHLO/MAIL/RCPT/DATA commands after completing + * TLS (re)negotiation, so that the replies arrive in our TLS + * session (otherwise the Postfix SMTP client would time out + * waiting for an answer). With some luck we can detect this + * specific attack as a server MAIL reply that arrives before we + * send our own MAIL command. + * + * We don't apply this test to the HELO command because the result + * would be very timing sensitive, and we don't apply this test + * to RCPT and DATA replies because these may be pipelined for + * legitimate reasons. + */ +#ifdef USE_TLS + if (var_smtp_tls_blk_early_mail_reply + && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) != 0 + && (vstream_peek(session->stream) > 0 + || peekfd(vstream_fileno(session->stream)) > 0)) + session->features |= SMTP_FEATURE_EARLY_TLS_MAIL_REPLY; +#endif + + /* + * We now return to our regular broadcast. + */ + next_state = SMTP_STATE_RCPT; + break; + + /* + * Build one RCPT TO command before we have seen the MAIL FROM + * response. + */ + case SMTP_STATE_RCPT: + rcpt = request->rcpt_list.info + send_rcpt; + REWRITE_ADDRESS(session->scratch2, rcpt->address); + QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2)); + vstring_sprintf(next_command, "RCPT TO:<%s>", + vstring_str(session->scratch)); + if (session->features & SMTP_FEATURE_DSN) { + /* XXX DSN xtext encode address value not type. */ + const char *orcpt_type_addr = rcpt->dsn_orcpt; + + /* Fix 20140706: don't use empty rcpt->orig_addr. */ + if (orcpt_type_addr[0] == 0 && rcpt->orig_addr[0] != 0) { + quote_822_local(session->scratch, rcpt->orig_addr); + vstring_sprintf(session->scratch2, "%s;%s", + /* Fix 20140707: sender must request SMTPUTF8. */ + (request->smtputf8 != 0 + && !allascii(vstring_str(session->scratch))) ? + "utf-8" : "rfc822", + vstring_str(session->scratch)); + orcpt_type_addr = vstring_str(session->scratch2); + } + if (orcpt_type_addr[0] != 0) { + /* Fix 20140706: don't send unquoted ORCPT. */ + /* Fix 20140707: quoting method must match orcpt type. */ + /* Fix 20140707: handle uxtext encoder errors. */ + if (strncasecmp(orcpt_type_addr, "utf-8;", 6) == 0) { + if (uxtext_quote(session->scratch, + orcpt_type_addr, "+=") != 0) + vstring_sprintf_append(next_command, " ORCPT=%s", + vstring_str(session->scratch)); + } else { + xtext_quote(session->scratch, orcpt_type_addr, "="); + vstring_sprintf_append(next_command, " ORCPT=%s", + vstring_str(session->scratch)); + } + } + if (rcpt->dsn_notify) + vstring_sprintf_append(next_command, " NOTIFY=%s", + dsn_notify_str(rcpt->dsn_notify)); + } + if ((next_rcpt = send_rcpt + 1) == SMTP_RCPT_LEFT(state)) + next_state = (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_RCPT) ? + SMTP_STATE_ABORT : SMTP_STATE_DATA; + break; + + /* + * Build the DATA command before we have seen all the RCPT TO + * responses. + */ + case SMTP_STATE_DATA: + vstring_strcpy(next_command, "DATA"); + next_state = SMTP_STATE_DOT; + break; + + /* + * Build the "." command after we have seen the DATA response + * (DATA is a protocol synchronization point). + * + * Changing the connection caching state here is safe because it + * affects none of the not-yet processed replies to + * already-generated commands. + */ + case SMTP_STATE_DOT: + vstring_strcpy(next_command, "."); + if (THIS_SESSION_IS_EXPIRED) + DONT_CACHE_THIS_SESSION; + next_state = THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT; + break; + + /* + * The SMTP_STATE_ABORT sender state is entered by the sender + * when it has verified all recipients; or it is entered by the + * receiver when all recipients are verified or rejected, and is + * then left before the bottom of the main loop. + * + * Changing the connection caching state here is safe because there + * are no not-yet processed replies to already-generated + * commands. + */ + case SMTP_STATE_ABORT: + vstring_strcpy(next_command, "RSET"); + if (THIS_SESSION_IS_EXPIRED) + DONT_CACHE_THIS_SESSION; + next_state = THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT; + break; + + /* + * Build the RSET command. This is entered as initial state from + * smtp_rset() and has its own dedicated state transitions. It is + * used to find out the status of a cached session before + * attempting mail delivery. + */ + case SMTP_STATE_RSET: + vstring_strcpy(next_command, "RSET"); + next_state = SMTP_STATE_LAST; + break; + + /* + * Build the QUIT command before we have seen the "." or RSET + * response. This is entered as initial state from smtp_quit(), + * or is reached near the end of any non-cached session. + * + * Changing the connection caching state here is safe. If this + * command is pipelined together with a preceding command, then + * connection caching was already turned off. Do not clobber the + * "bad connection" flag. + */ + case SMTP_STATE_QUIT: + vstring_strcpy(next_command, "QUIT"); + next_state = SMTP_STATE_LAST; + if (THIS_SESSION_IS_CACHED) + DONT_CACHE_THIS_SESSION; + break; + + /* + * The final sender state has no action associated with it. + */ + case SMTP_STATE_LAST: + VSTRING_RESET(next_command); + break; + } + VSTRING_TERMINATE(next_command); + + /* + * Process responses until the receiver has caught up. Vstreams + * automatically flush buffered output when reading new data. + * + * Flush unsent output if command pipelining is off or if no I/O + * happened for a while. This limits the accumulation of client-side + * delays in pipelined sessions. + * + * The PIPELINING engine will flush the VSTREAM buffer if the sender + * could otherwise produce more output than fits the PIPELINING + * buffer. This generally works because we know exactly how much + * output we produced since the last time that the sender and + * receiver synchronized the SMTP state. However this logic is not + * applicable after the sender enters the DATA phase, where it does + * not synchronize with the receiver until the <CR><LF>.<CR><LF>. + * Thus, the PIPELINING engine no longer knows how much data is + * pending in the TCP send buffer. For this reason, if PIPELINING is + * enabled, we always pipeline QUIT after <CR><LF>.<CR><LF>. This is + * safe because once the receiver reads <CR><LF>.<CR><LF>, its TCP + * stack either has already received the QUIT<CR><LF>, or else it + * acknowledges all bytes up to and including <CR><LF>.<CR><LF>, + * making room in the sender's TCP stack for QUIT<CR><LF>. + */ +#define CHECK_PIPELINING_BUFSIZE \ + (recv_state != SMTP_STATE_DOT || send_state != SMTP_STATE_QUIT) + + if (SENDER_IN_WAIT_STATE + || (SENDER_IS_AHEAD + && ((session->features & SMTP_FEATURE_PIPELINING) == 0 + || (CHECK_PIPELINING_BUFSIZE + && (VSTRING_LEN(next_command) + 2 + + vstream_bufstat(session->stream, VSTREAM_BST_OUT_PEND) + > PIPELINING_BUFSIZE)) + || time((time_t *) 0) + - vstream_ftime(session->stream) > 10))) { + while (SENDER_IS_AHEAD) { + + /* + * Sanity check. + */ + if (recv_state < SMTP_STATE_XFORWARD_NAME_ADDR + || recv_state > SMTP_STATE_QUIT) + msg_panic("%s: bad receiver state %d (sender state %d)", + myname, recv_state, send_state); + + /* + * Receive the next server response. Use the proper timeout, + * and log the proper client state in case of trouble. + * + * XXX If we lose the connection before sending end-of-data, + * find out if the server sent a premature end-of-data reply. + * If this read attempt fails, report "lost connection while + * sending message body", not "lost connection while sending + * end-of-data". + * + * "except" becomes zero just above the protocol loop, and stays + * zero or triggers an early return from the loop. In just + * one case: loss of the connection when sending the message + * body, we record the exception, and keep processing in the + * hope of detecting a premature 5XX. We must be careful to + * not clobber this non-zero value once it is set. The + * variable need not survive longjmp() calls, since the only + * setjmp() which does not return early is the one sets this + * condition, subquent failures always return early. + */ +#define LOST_CONNECTION_INSIDE_DATA (except == SMTP_ERR_EOF) + + smtp_stream_setup(session->stream, *xfer_timeouts[recv_state], + var_smtp_rec_deadline); + if (LOST_CONNECTION_INSIDE_DATA) { + if (vstream_setjmp(session->stream) != 0) + RETURN(smtp_stream_except(state, SMTP_ERR_EOF, + "sending message body")); + } else { + if ((except = vstream_setjmp(session->stream)) != 0) + RETURN(SENDING_MAIL ? smtp_stream_except(state, except, + xfer_states[recv_state]) : -1); + } + resp = smtp_chat_resp(session); + + /* + * Process the response. + */ + switch (recv_state) { + + /* + * Process the XFORWARD response. + */ + case SMTP_STATE_XFORWARD_NAME_ADDR: + if (resp->code / 100 != 2) + msg_warn("host %s said: %s (in reply to %s)", + session->namaddrport, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_XFORWARD_NAME_ADDR]); + if (session->send_proto_helo) + recv_state = SMTP_STATE_XFORWARD_PROTO_HELO; + else + recv_state = SMTP_STATE_MAIL; + break; + + case SMTP_STATE_XFORWARD_PROTO_HELO: + if (resp->code / 100 != 2) + msg_warn("host %s said: %s (in reply to %s)", + session->namaddrport, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_XFORWARD_PROTO_HELO]); + recv_state = SMTP_STATE_MAIL; + break; + + /* + * Process the MAIL FROM response. When the server + * rejects the sender, set the mail_from_rejected flag so + * that the receiver may apply a course correction. + */ + case SMTP_STATE_MAIL: + if (resp->code / 100 != 2) { + smtp_mesg_fail(state, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_MAIL]); + mail_from_rejected = 1; + } + + /* + * CVE-2009-3555 (TLS renegotiation). Whatever it was + * that arrived before we sent our MAIL FROM command, it + * was not a fatal-level TLS alert message. It could be a + * warning-level TLS alert message, or a ChangeCipherSpec + * message, but such messages are not normally sent in + * the middle of a TLS session. We disconnect and try + * again later. + */ +#ifdef USE_TLS + if (var_smtp_tls_blk_early_mail_reply + && (session->features & SMTP_FEATURE_EARLY_TLS_MAIL_REPLY)) { + smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.0"), + "unexpected server message"); + msg_warn("server %s violates %s policy", + session->namaddr, + VAR_LMTP_SMTP(TLS_BLK_EARLY_MAIL_REPLY)); + mail_from_rejected = 1; + } +#endif + + /* + * We now return to our regular broadcast. + */ + recv_state = SMTP_STATE_RCPT; + break; + + /* + * Process one RCPT TO response. If MAIL FROM was + * rejected, ignore RCPT TO responses: all recipients are + * dead already. When all recipients are rejected the + * receiver may apply a course correction. + * + * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply + * must be treated as if the server replied with 452. + * However, this causes "too much mail data" to be + * treated as a recoverable error, which is wrong. I'll + * stick with RFC 821. + */ + case SMTP_STATE_RCPT: + if (!mail_from_rejected) { +#ifdef notdef + if (resp->code == 552) { + resp->code = 452; + resp->dsn[0] = '4'; + } +#endif + rcpt = request->rcpt_list.info + recv_rcpt; + if (resp->code / 100 == 2) { + if (!smtp_mode) { + if (survivors == 0) + survivors = (int *) + mymalloc(request->rcpt_list.len + * sizeof(int)); + survivors[nrcpt] = recv_rcpt; + } + ++nrcpt; + /* If trace-only, mark the recipient done. */ + if (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_RCPT) { + translit(resp->str, "\n", " "); + smtp_rcpt_done(state, resp, rcpt); + } + } else { + smtp_rcpt_fail(state, rcpt, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_RCPT]); + } + } + /* If trace-only, send RSET instead of DATA. */ + if (++recv_rcpt == SMTP_RCPT_LEFT(state)) + recv_state = (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_RCPT) ? + SMTP_STATE_ABORT : SMTP_STATE_DATA; + /* XXX Also: record if non-delivering session. */ + break; + + /* + * Process the DATA response. When the server rejects + * DATA, set nrcpt to a negative value so that the + * receiver can apply a course correction. + */ + case SMTP_STATE_DATA: + recv_state = SMTP_STATE_DOT; + if (resp->code / 100 != 3) { + if (nrcpt > 0) + smtp_mesg_fail(state, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_DATA]); + nrcpt = -1; + } + + /* + * In the case of a successful address probe with target + * equal to DATA, the remote server is now in the DATA + * state, and therefore we must not make any further + * attempt to send or receive on this connection. This + * means that we cannot not reuse the general-purpose + * course-correction logic below which sends RSET (and + * perhaps QUIT). Instead we "jump" straight to the exit + * and force an unceremonious disconnect. + */ + else if (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_DATA) { + for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (!SMTP_RCPT_ISMARKED(rcpt)) { + translit(resp->str, "\n", " "); + SMTP_RESP_SET_DSN(resp, "2.0.0"); + smtp_rcpt_done(state, resp, rcpt); + } + } + DONT_CACHE_THIS_SESSION; + send_state = recv_state = SMTP_STATE_LAST; + } + break; + + /* + * Process the end of message response. Ignore the + * response when no recipient was accepted: all + * recipients are dead already, and the next receiver + * state is SMTP_STATE_LAST/QUIT regardless. Otherwise, + * if the message transfer fails, bounce all remaining + * recipients, else cross off the recipients that were + * delivered. + */ + case SMTP_STATE_DOT: + GETTIMEOFDAY(&request->msg_stats.deliver_done); + if (smtp_mode) { + if (nrcpt > 0) { + if (resp->code / 100 != 2) { + smtp_mesg_fail(state, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_DOT]); + } else { + for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (!SMTP_RCPT_ISMARKED(rcpt)) { + translit(resp->str, "\n", " "); + smtp_rcpt_done(state, resp, rcpt); + } + } + } + } + } + + /* + * With LMTP we have one response per accepted RCPT TO + * command. Stay in the SMTP_STATE_DOT state until we + * have collected all responses. + */ + else { + if (nrcpt > 0) { + rcpt = request->rcpt_list.info + + survivors[recv_done++]; + if (resp->code / 100 != 2) { + smtp_rcpt_fail(state, rcpt, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_DOT]); + } else { + translit(resp->str, "\n", " "); + smtp_rcpt_done(state, resp, rcpt); + } + } + if (msg_verbose) + msg_info("%s: got %d of %d end-of-data replies", + myname, recv_done, nrcpt); + if (recv_done < nrcpt) + break; + } + + /* + * XXX Do not change the connection caching state here, + * even if the connection caching timer expired between + * generating the command and processing the reply, + * otherwise the sender and receiver loops get out of + * sync. The caller will call smtp_quit() if appropriate. + */ + if (var_skip_quit_resp || THIS_SESSION_IS_CACHED + || LOST_CONNECTION_INSIDE_DATA) + recv_state = SMTP_STATE_LAST; + else + recv_state = SMTP_STATE_QUIT; + break; + + /* + * Receive the RSET response. + * + * The SMTP_STATE_ABORT sender state is entered by the + * sender when it has verified all recipients; or it is + * entered by the receiver when all recipients are + * verified or rejected, and is then left before the + * bottom of the main loop. + * + * XXX Do not change the connection caching state here, even + * if the server rejected RSET or if the connection + * caching timer expired between generating the command + * and processing the reply, otherwise the sender and + * receiver loops get out of sync. The caller will call + * smtp_quit() if appropriate. + */ + case SMTP_STATE_ABORT: + recv_state = (var_skip_quit_resp || THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT); + break; + + /* + * This is the initial receiver state from smtp_rset(). + * It is used to find out the status of a cached session + * before attempting mail delivery. + */ + case SMTP_STATE_RSET: + if (resp->code / 100 != 2) + CANT_RSET_THIS_SESSION; + recv_state = SMTP_STATE_LAST; + break; + + /* + * Receive, but otherwise ignore, the QUIT response. + */ + case SMTP_STATE_QUIT: + recv_state = SMTP_STATE_LAST; + break; + } + } + + /* + * At this point, the sender and receiver are fully synchronized. + */ + + /* + * We know the server response to every command that was sent. + * Apply a course correction if necessary: the sender wants to + * send RCPT TO but MAIL FROM was rejected; the sender wants to + * send DATA but all recipients were rejected; the sender wants + * to deliver the message but DATA was rejected. + */ + if ((send_state == SMTP_STATE_RCPT && mail_from_rejected) + || (send_state == SMTP_STATE_DATA && nrcpt == 0) + || (send_state == SMTP_STATE_DOT && nrcpt < 0)) { + send_state = recv_state = SMTP_STATE_ABORT; + send_rcpt = recv_rcpt = 0; + vstring_strcpy(next_command, "RSET"); + if (THIS_SESSION_IS_EXPIRED) + DONT_CACHE_THIS_SESSION; + next_state = THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT; + /* XXX Also: record if non-delivering session. */ + next_rcpt = 0; + } + } + + /* + * Make the next sender state the current sender state. + */ + if (send_state == SMTP_STATE_LAST) + continue; + + /* + * Special case if the server accepted the DATA command. If the + * server accepted at least one recipient send the entire message. + * Otherwise, just send "." as per RFC 2197. + * + * XXX If there is a hard MIME error while downgrading to 7-bit mail, + * disconnect ungracefully, because there is no other way to cancel a + * transaction in progress. + */ + if (send_state == SMTP_STATE_DOT && nrcpt > 0) { + + smtp_stream_setup(session->stream, var_smtp_data1_tmout, + var_smtp_rec_deadline); + + if ((except = vstream_setjmp(session->stream)) == 0) { + + if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0) + msg_fatal("seek queue file: %m"); + + downgrading = SMTP_MIME_DOWNGRADE(session, request); + + /* + * XXX Don't downgrade just because generic_maps is turned + * on. + */ +#define SMTP_ANY_CHECKS (smtp_header_checks || smtp_body_checks) + + if (downgrading || smtp_generic_maps || SMTP_ANY_CHECKS) + session->mime_state = mime_state_alloc(downgrading ? + MIME_OPT_DOWNGRADE + | MIME_OPT_REPORT_NESTING : + SMTP_ANY_CHECKS == 0 ? + MIME_OPT_DISABLE_MIME : + 0, + smtp_generic_maps + || smtp_header_checks ? + smtp_header_rewrite : + smtp_header_out, + (MIME_STATE_ANY_END) 0, + smtp_body_checks ? + smtp_body_rewrite : + smtp_text_out, + (MIME_STATE_ANY_END) 0, + (MIME_STATE_ERR_PRINT) 0, + (void *) state); + state->space_left = var_smtp_line_limit; + + while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) { + if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT) + break; + if (session->mime_state == 0) { + smtp_text_out((void *) state, rec_type, + vstring_str(session->scratch), + VSTRING_LEN(session->scratch), + (off_t) 0); + } else { + mime_errs = + mime_state_update(session->mime_state, rec_type, + vstring_str(session->scratch), + VSTRING_LEN(session->scratch)); + if (mime_errs) { + smtp_mime_fail(state, mime_errs); + RETURN(0); + } + } + prev_type = rec_type; + } + + if (session->mime_state) { + + /* + * The cleanup server normally ends MIME content with a + * normal text record. The following code is needed to + * flush an internal buffer when someone submits 8-bit + * mail not ending in newline via /usr/sbin/sendmail + * while MIME input processing is turned off, and MIME + * 8bit->7bit conversion is requested upon delivery. + * + * Or some error while doing generic address mapping. + */ + mime_errs = + mime_state_update(session->mime_state, rec_type, "", 0); + if (mime_errs) { + smtp_mime_fail(state, mime_errs); + RETURN(0); + } + } else if (prev_type == REC_TYPE_CONT) /* missing newline */ + smtp_fputs("", 0, session->stream); + if (session->features & SMTP_FEATURE_PIX_DELAY_DOTCRLF) { + smtp_flush(session->stream);/* hurts performance */ + sleep(var_smtp_pix_delay); /* not to mention this */ + } + if (vstream_ferror(state->src)) + msg_fatal("queue file read error"); + if (rec_type != REC_TYPE_XTRA) { + msg_warn("%s: bad record type: %d in message content", + request->queue_id, rec_type); + fail_status = smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.3.0"), + "unreadable mail queue entry"); + /* Bailing out, abort stream with prejudice */ + (void) vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); + DONT_USE_FORBIDDEN_SESSION; + /* If bounce_append() succeeded, status is still 0 */ + if (state->status == 0) + (void) mark_corrupt(state->src); + /* Don't override smtp_mesg_fail() here. */ + RETURN(fail_status); + } + } else { + if (!LOST_CONNECTION_INSIDE_DATA) + RETURN(smtp_stream_except(state, except, + "sending message body")); + + /* + * We will clear the stream error flag to try and read a + * premature 5XX response, so it is important to flush any + * unwritten data. Otherwise, we will try to flush it again + * before reading, which may incur an unnecessary delay and + * will prevent the reading of any response that is not + * already buffered (bundled with the DATA 354 response). + * + * Not much point in sending QUIT at this point, skip right to + * SMTP_STATE_LAST. The read engine above will likewise avoid + * looking for a QUIT response. + */ + (void) vstream_fpurge(session->stream, VSTREAM_PURGE_WRITE); + next_state = SMTP_STATE_LAST; + } + } + + /* + * Copy the next command to the buffer and update the sender state. + */ + if (except == 0) { + smtp_chat_cmd(session, "%s", vstring_str(next_command)); + } else { + DONT_CACHE_THIS_SESSION; + } + send_state = next_state; + send_rcpt = next_rcpt; + } while (recv_state != SMTP_STATE_LAST); + RETURN(0); +} + +/* smtp_xfer - send a batch of envelope information and the message data */ + +int smtp_xfer(SMTP_STATE *state) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + SMTP_RESP fake; + int send_state; + int recv_state; + int send_name_addr; + int result; + + /* + * Sanity check. Recipients should be unmarked at this point. + */ + if (SMTP_RCPT_LEFT(state) <= 0) + msg_panic("smtp_xfer: bad recipient count: %d", + SMTP_RCPT_LEFT(state)); + if (SMTP_RCPT_ISMARKED(request->rcpt_list.info)) + msg_panic("smtp_xfer: bad recipient status: %d", + request->rcpt_list.info->u.status); + + /* + * See if we should even try to send this message at all. This code sits + * here rather than in the EHLO processing code, because of SMTP + * connection caching. + */ + if (session->size_limit > 0 && session->size_limit < request->data_size) { + smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.3.4"), + "message size %lu exceeds size limit %.0f of server %s", + request->data_size, (double) session->size_limit, + session->namaddr); + /* Redundant. We abort this delivery attempt. */ + state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION; + return (0); + } + + /* + * Use XFORWARD to forward the origin of this email message across an + * SMTP-based content filter. Send client attribute information only if + * it exists (i.e. remote submission). Local submissions have no client + * attributes; the mail will appear to originate from the content filter + * which is acceptable. + */ + send_name_addr = + var_smtp_send_xforward + && (((session->features & SMTP_FEATURE_XFORWARD_NAME) + && CAN_FORWARD_CLIENT_NAME(request->client_name)) + || ((session->features & SMTP_FEATURE_XFORWARD_ADDR) + && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) + || ((session->features & SMTP_FEATURE_XFORWARD_PORT) + && CAN_FORWARD_CLIENT_PORT(request->client_port))); + session->send_proto_helo = + var_smtp_send_xforward + && (((session->features & SMTP_FEATURE_XFORWARD_PROTO) + && CAN_FORWARD_PROTO_NAME(request->client_proto)) + || ((session->features & SMTP_FEATURE_XFORWARD_HELO) + && CAN_FORWARD_HELO_NAME(request->client_helo)) + || ((session->features & SMTP_FEATURE_XFORWARD_IDENT) + && CAN_FORWARD_IDENT_NAME(request->log_ident)) + || ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN) + && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context))); + if (send_name_addr) + recv_state = send_state = SMTP_STATE_XFORWARD_NAME_ADDR; + else if (session->send_proto_helo) + recv_state = send_state = SMTP_STATE_XFORWARD_PROTO_HELO; + else + recv_state = send_state = SMTP_STATE_MAIL; + + /* + * Remember this session's "normal completion", even if the server 4xx-ed + * some or all recipients. Connection or handshake errors with a later MX + * host should not cause this destination be marked as unreachable. + */ + result = smtp_loop(state, send_state, recv_state); + + if (result == 0 + /* Just in case */ + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) + state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION; + + return (result); +} + +/* smtp_rset - send a lone RSET command */ + +int smtp_rset(SMTP_STATE *state) +{ + + /* + * This works because SMTP_STATE_RSET is a dedicated sender/recipient + * entry state, with SMTP_STATE_LAST as next sender/recipient state. + */ + return (smtp_loop(state, SMTP_STATE_RSET, SMTP_STATE_RSET)); +} + +/* smtp_quit - send a lone QUIT command */ + +int smtp_quit(SMTP_STATE *state) +{ + + /* + * This works because SMTP_STATE_QUIT is the last state with a sender + * action, with SMTP_STATE_LAST as the next sender/recipient state. + */ + return (smtp_loop(state, SMTP_STATE_QUIT, var_skip_quit_resp ? + SMTP_STATE_LAST : SMTP_STATE_QUIT)); +} diff --git a/src/smtp/smtp_rcpt.c b/src/smtp/smtp_rcpt.c new file mode 100644 index 0000000..673f0b4 --- /dev/null +++ b/src/smtp/smtp_rcpt.c @@ -0,0 +1,221 @@ +/*++ +/* NAME +/* smtp_rcpt 3 +/* SUMMARY +/* application-specific recipient list operations +/* SYNOPSIS +/* #include <smtp.h> +/* +/* SMTP_RCPT_INIT(state) +/* SMTP_STATE *state; +/* +/* SMTP_RCPT_DROP(state, rcpt) +/* SMTP_STATE *state; +/* RECIPIENT *rcpt; +/* +/* SMTP_RCPT_KEEP(state, rcpt) +/* SMTP_STATE *state; +/* RECIPIENT *rcpt; +/* +/* SMTP_RCPT_ISMARKED(rcpt) +/* RECIPIENT *rcpt; +/* +/* void smtp_rcpt_cleanup(SMTP_STATE *state) +/* SMTP_STATE *state; +/* +/* int SMTP_RCPT_LEFT(state) +/* SMTP_STATE *state; +/* +/* int SMTP_RCPT_MARK_COUNT(state) +/* SMTP_STATE *state; +/* +/* void smtp_rcpt_done(state, resp, rcpt) +/* SMTP_STATE *state; +/* SMTP_RESP *resp; +/* RECIPIENT *rcpt; +/* DESCRIPTION +/* This module implements application-specific mark and sweep +/* operations on recipient lists. Operation is as follows: +/* .IP \(bu +/* In the course of a delivery attempt each recipient is +/* marked either as DROP (remove from recipient list) or KEEP +/* (deliver to alternate mail server). +/* .IP \(bu +/* After a delivery attempt any recipients marked DROP are deleted +/* from the request, and the left-over recipients are unmarked. +/* .PP +/* The mark/sweep algorithm is implemented in a redundant manner, +/* and ensures that all recipients are explicitly accounted for. +/* +/* Operations with upper case names are implemented by macros +/* whose arguments may be evaluated more than once. +/* +/* SMTP_RCPT_INIT() initializes application-specific recipient +/* information and must be called before the first delivery attempt. +/* +/* SMTP_RCPT_DROP() marks the specified recipient as DROP (remove +/* from recipient list). It is an error to mark an already marked +/* recipient. +/* +/* SMTP_RCPT_KEEP() marks the specified recipient as KEEP (deliver +/* to alternate mail server). It is an error to mark an already +/* marked recipient. +/* +/* SMTP_RCPT_ISMARKED() returns non-zero when the specified +/* recipient is marked. +/* +/* SMTP_RCPT_LEFT() returns the number of left_over recipients +/* (the total number of marked and non-marked recipients). +/* +/* SMTP_RCPT_MARK_COUNT() returns the number of left_over +/* recipients that are marked. +/* +/* smtp_rcpt_cleanup() cleans up the in-memory recipient list. +/* It removes the recipients marked DROP from the left-over +/* recipients, unmarks the left-over recipients, and enforces +/* the requirement that all recipients are marked upon entry. +/* +/* smtp_rcpt_done() logs that a recipient is completed and upon +/* success it marks the recipient as done in the queue file. +/* Finally, it marks the in-memory recipient as DROP. +/* +/* Note: smtp_rcpt_done() may change the order of the recipient +/* list. +/* DIAGNOSTICS +/* Panic: interface violation. +/* +/* When a recipient can't be logged as completed, the recipient is +/* logged as deferred instead. +/* BUGS +/* The single recipient list abstraction dates from the time +/* that the SMTP client would give up after one SMTP session, +/* so that each recipient was either bounced, delivered or +/* deferred. Implicitly, all recipients were marked as DROP. +/* +/* This abstraction is less convenient when an SMTP client +/* must be able to deliver left-over recipients to a backup +/* host. It might be more natural to have an input list with +/* recipients to deliver, and an output list with left-over +/* recipients. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* smtp_rcpt_cleanup */ +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <stringops.h> +#include <mymalloc.h> + +/* Global library. */ + +#include <mail_params.h> +#include <deliver_request.h> /* smtp_rcpt_done */ +#include <deliver_completed.h> /* smtp_rcpt_done */ +#include <sent.h> /* smtp_rcpt_done */ +#include <dsn_mask.h> /* smtp_rcpt_done */ + +/* Application-specific. */ + +#include <smtp.h> + +/* smtp_rcpt_done - mark recipient as done or else */ + +void smtp_rcpt_done(SMTP_STATE *state, SMTP_RESP *resp, RECIPIENT *rcpt) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + SMTP_ITERATOR *iter = state->iterator; + DSN_BUF *why = state->why; + const char *dsn_action = "relayed"; + int status; + + /* + * Assume this was intermediate delivery when the server announced DSN + * support, and don't send a DSN "SUCCESS" notification. + */ + if (session->features & SMTP_FEATURE_DSN) + rcpt->dsn_notify &= ~DSN_NOTIFY_SUCCESS; + + /* + * Assume this was final delivery when the LMTP server announced no DSN + * support. In backwards compatibility mode, send a "relayed" instead of + * a "delivered" DSN "SUCCESS" notification. Do not attempt to "simplify" + * the expression. The redundancy is for clarity. It is trivially + * eliminated by the compiler. There is no need to sacrifice clarity for + * the sake of "performance". + */ + if ((session->features & SMTP_FEATURE_DSN) == 0 + && !smtp_mode + && var_lmtp_assume_final != 0) + dsn_action = "delivered"; + + /* + * Report success and delete the recipient from the delivery request. + * Defer if the success can't be reported. + * + * Note: the DSN action is ignored in case of address probes. + */ + dsb_update(why, resp->dsn, dsn_action, DSB_MTYPE_DNS, STR(iter->host), + DSB_DTYPE_SMTP, resp->str, "%s", resp->str); + + status = sent(DEL_REQ_TRACE_FLAGS(request->flags), + request->queue_id, &request->msg_stats, rcpt, + session->namaddrport, DSN_FROM_DSN_BUF(why)); + if (status == 0) + if (request->flags & DEL_REQ_FLAG_SUCCESS) + deliver_completed(state->src, rcpt->offset); + SMTP_RCPT_DROP(state, rcpt); + state->status |= status; +} + +/* smtp_rcpt_cleanup_callback - qsort callback */ + +static int smtp_rcpt_cleanup_callback(const void *a, const void *b) +{ + return (((RECIPIENT *) a)->u.status - ((RECIPIENT *) b)->u.status); +} + +/* smtp_rcpt_cleanup - purge completed recipients from request */ + +void smtp_rcpt_cleanup(SMTP_STATE *state) +{ + RECIPIENT_LIST *rcpt_list = &state->request->rcpt_list; + RECIPIENT *rcpt; + + /* + * Sanity checks. + */ + if (state->rcpt_drop + state->rcpt_keep != state->rcpt_left) + msg_panic("smtp_rcpt_cleanup: recipient count mismatch: %d+%d!=%d", + state->rcpt_drop, state->rcpt_keep, state->rcpt_left); + + /* + * Recipients marked KEEP sort before recipients marked DROP. Skip the + * sorting in the common case that all recipients are marked the same. + */ + if (state->rcpt_drop > 0 && state->rcpt_keep > 0) + qsort((void *) rcpt_list->info, state->rcpt_left, + sizeof(rcpt_list->info[0]), smtp_rcpt_cleanup_callback); + + /* + * Truncate the recipient list and unmark the left-over recipients. + */ + state->rcpt_left = state->rcpt_keep; + for (rcpt = rcpt_list->info; rcpt < rcpt_list->info + state->rcpt_left; rcpt++) + rcpt->u.status = 0; + state->rcpt_drop = state->rcpt_keep = 0; +} diff --git a/src/smtp/smtp_reuse.c b/src/smtp/smtp_reuse.c new file mode 100644 index 0000000..f93ba29 --- /dev/null +++ b/src/smtp/smtp_reuse.c @@ -0,0 +1,269 @@ +/*++ +/* NAME +/* smtp_reuse 3 +/* SUMMARY +/* SMTP session cache glue +/* SYNOPSIS +/* #include <smtp.h> +/* #include <smtp_reuse.h> +/* +/* void smtp_save_session(state, name_key_flags, endp_key_flags) +/* SMTP_STATE *state; +/* int name_key_flags; +/* int endp_key_flags; +/* +/* SMTP_SESSION *smtp_reuse_nexthop(state, name_key_flags) +/* SMTP_STATE *state; +/* int name_key_flags; +/* +/* SMTP_SESSION *smtp_reuse_addr(state, endp_key_flags) +/* SMTP_STATE *state; +/* int endp_key_flags; +/* DESCRIPTION +/* This module implements the SMTP client specific interface to +/* the generic session cache infrastructure. +/* +/* The caller needs to include additional state in _key_flags +/* to avoid false sharing of SASL-authenticated or TLS-authenticated +/* sessions. +/* +/* smtp_save_session() stores the current session under the +/* delivery request next-hop logical destination (if applicable) +/* and under the remote server address. The SMTP_SESSION object +/* is destroyed. +/* +/* smtp_reuse_nexthop() looks up a cached session by its +/* delivery request next-hop destination, and verifies that +/* the session is still alive. The restored session information +/* includes the "best MX" bit and overrides the iterator dest, +/* host and addr fields. The result is null in case of failure. +/* +/* smtp_reuse_addr() looks up a cached session by its server +/* address, and verifies that the session is still alive. +/* The restored session information does not include the "best +/* MX" bit, and does not override the iterator dest, host and +/* addr fields. The result is null in case of failure. +/* +/* Arguments: +/* .IP state +/* SMTP client state, including the current session, the original +/* next-hop domain, etc. +/* .IP name_key_flags +/* Explicit declaration of context that should be used to look +/* up a cached connection by its logical destination. +/* See smtp_key(3) for details. +/* .IP endp_key_flags +/* Explicit declaration of context that should be used to look +/* up a cached connection by its server address. +/* See smtp_key(3) for details. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> +#include <htable.h> +#include <stringops.h> + +/* Global library. */ + +#include <scache.h> +#include <mail_params.h> + +/* Application-specific. */ + +#include <smtp.h> +#include <smtp_reuse.h> + + /* + * Key field delimiter, and place holder field value for + * unavailable/inapplicable information. + */ +#define SMTP_REUSE_KEY_DELIM_NA "\n*" + +/* smtp_save_session - save session under next-hop name and server address */ + +void smtp_save_session(SMTP_STATE *state, int name_key_flags, + int endp_key_flags) +{ + SMTP_SESSION *session = state->session; + int fd; + + /* + * Encode the delivery request next-hop destination, if applicable. Reuse + * storage that is also used for cache lookup queries. + * + * HAVE_SCACHE_REQUEST_NEXTHOP() controls whether or not to reuse or cache a + * connection by its delivery request next-hop destination. The idea is + * 1) to allow a reuse request to skip over bad hosts, and 2) to avoid + * caching a less-preferred connection when a more-preferred connection + * was possible. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, name_key_flags); + + /* + * Encode the physical endpoint name. Reuse storage that is also used for + * cache lookup queries. + */ + smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, endp_key_flags); + + /* + * Passivate the SMTP_SESSION object, destroying the object in the + * process. Reuse storage that is also used for cache lookup results. + */ + fd = smtp_session_passivate(session, state->dest_prop, state->endp_prop); + state->session = 0; + + /* + * Save the session under the delivery request next-hop name, if + * applicable. + * + * XXX The logical to physical binding can be kept for as long as the DNS + * allows us to (but that could result in the caching of lots of unused + * bindings). The session should be idle for no more than 30 seconds or + * so. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + scache_save_dest(smtp_scache, var_smtp_cache_conn, + STR(state->dest_label), STR(state->dest_prop), + STR(state->endp_label)); + + /* + * Save every good session under its physical endpoint address. + */ + scache_save_endp(smtp_scache, var_smtp_cache_conn, STR(state->endp_label), + STR(state->endp_prop), fd); +} + +/* smtp_reuse_common - common session reuse code */ + +static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd, + const char *label) +{ + const char *myname = "smtp_reuse_common"; + SMTP_ITERATOR *iter = state->iterator; + SMTP_SESSION *session; + + /* + * Re-activate the SMTP_SESSION object. + */ + session = smtp_session_activate(fd, state->iterator, state->dest_prop, + state->endp_prop); + if (session == 0) { + msg_warn("%s: bad cached session attribute for %s", myname, label); + (void) close(fd); + return (0); + } + state->session = session; + session->state = state; + + /* + * Send an RSET probe to verify that the session is still good. + */ + if (smtp_rset(state) < 0 + || (session->features & SMTP_FEATURE_RSET_REJECTED) != 0) { + smtp_session_free(session); + return (state->session = 0); + } + + /* + * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. + */ + vstream_tweak_sock(session->stream); + + /* + * Update the list of used cached addresses. + */ + htable_enter(state->cache_used, STR(iter->addr), (void *) 0); + + return (session); +} + +/* smtp_reuse_nexthop - reuse session cached under nexthop name */ + +SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *state, int name_key_flags) +{ + SMTP_SESSION *session; + int fd; + + /* + * Look up the session by its logical name. + */ + smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, name_key_flags); + if ((fd = scache_find_dest(smtp_scache, STR(state->dest_label), + state->dest_prop, state->endp_prop)) < 0) + return (0); + + /* + * Re-activate the SMTP_SESSION object, and verify that the session is + * still good. + */ + session = smtp_reuse_common(state, fd, STR(state->dest_label)); + return (session); +} + +/* smtp_reuse_addr - reuse session cached under numerical address */ + +SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *state, int endp_key_flags) +{ + SMTP_SESSION *session; + int fd; + + /* + * Address-based reuse is safe for security levels that require TLS + * certificate checks, as long as the current nexhop is included in the + * cache lookup key (COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP). This is + * sufficient to prevent the reuse of a TLS-authenticated connection to + * the same MX hostname, IP address, and port, but for a different + * current nexthop destination with a different TLS policy. + */ + + /* + * Look up the session by its IP address. This means that we have no + * destination-to-address binding properties. + */ + smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, endp_key_flags); + if ((fd = scache_find_endp(smtp_scache, STR(state->endp_label), + state->endp_prop)) < 0) + return (0); + VSTRING_RESET(state->dest_prop); + VSTRING_TERMINATE(state->dest_prop); + + /* + * Re-activate the SMTP_SESSION object, and verify that the session is + * still good. + */ + session = smtp_reuse_common(state, fd, STR(state->endp_label)); + + return (session); +} diff --git a/src/smtp/smtp_reuse.h b/src/smtp/smtp_reuse.h new file mode 100644 index 0000000..8c6cb48 --- /dev/null +++ b/src/smtp/smtp_reuse.h @@ -0,0 +1,27 @@ +/*++ +/* NAME +/* smtp_reuse 3h +/* SUMMARY +/* SMTP session cache glue +/* SYNOPSIS +/* #include <smtp_reuse.h> +/* DESCRIPTION +/* .nf + + /* + * Internal interfaces. + */ +extern void smtp_save_session(SMTP_STATE *, int, int); +extern SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *, int); +extern SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtp/smtp_sasl.h b/src/smtp/smtp_sasl.h new file mode 100644 index 0000000..756d13d --- /dev/null +++ b/src/smtp/smtp_sasl.h @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* smtp_sasl 3h +/* SUMMARY +/* Postfix SASL interface for SMTP client +/* SYNOPSIS +/* #include "smtp_sasl.h" +/* DESCRIPTION +/* .nf + + /* + * SASL protocol functions + */ +extern void smtp_sasl_initialize(void); +extern void smtp_sasl_connect(SMTP_SESSION *); +extern int smtp_sasl_passwd_lookup(SMTP_SESSION *); +extern void smtp_sasl_start(SMTP_SESSION *, const char *, const char *); +extern int smtp_sasl_authenticate(SMTP_SESSION *, DSN_BUF *); +extern void smtp_sasl_cleanup(SMTP_SESSION *); + +extern void smtp_sasl_helo_auth(SMTP_SESSION *, const char *); +extern int smtp_sasl_helo_login(SMTP_STATE *); + +extern void smtp_sasl_passivate(SMTP_SESSION *, VSTRING *); +extern int smtp_sasl_activate(SMTP_SESSION *, char *); +extern STRING_LIST *smtp_sasl_mechs; + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtp/smtp_sasl_auth_cache.c b/src/smtp/smtp_sasl_auth_cache.c new file mode 100644 index 0000000..f3104ca --- /dev/null +++ b/src/smtp/smtp_sasl_auth_cache.c @@ -0,0 +1,272 @@ +/*++ +/* NAME +/* smtp_sasl_auth_cache 3 +/* SUMMARY +/* Postfix SASL authentication reply cache +/* SYNOPSIS +/* #include "smtp.h" +/* #include "smtp_sasl_auth_cache.h" +/* +/* SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(map, ttl) +/* const char *map +/* int ttl; +/* +/* void smtp_sasl_auth_cache_store(auth_cache, session, resp) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* const SMTP_SESSION *session; +/* const SMTP_RESP *resp; +/* +/* int smtp_sasl_auth_cache_find(auth_cache, session) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* const SMTP_SESSION *session; +/* +/* char *smtp_sasl_auth_cache_dsn(auth_cache) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* +/* char *smtp_sasl_auth_cache_text(auth_cache) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* DESCRIPTION +/* This module maintains a cache of SASL authentication server replies. +/* This can be used to avoid repeated login failure errors. +/* +/* smtp_sasl_auth_cache_init() opens or creates the named cache. +/* +/* smtp_sasl_auth_cache_store() stores information about a +/* SASL login attempt together with the server status and +/* complete response. +/* +/* smtp_sasl_auth_cache_find() returns non-zero when a cache +/* entry exists for the given host, username and password. +/* +/* smtp_sasl_auth_cache_dsn() and smtp_sasl_auth_cache_text() +/* return the status and complete server response as found +/* with smtp_sasl_auth_cache_find(). +/* +/* Arguments: +/* .IP map +/* Lookup table name. The name must be singular and must start +/* with "proxy:". +/* .IP ttl +/* The time after which a cache entry is considered expired. +/* .IP session +/* Session context. +/* .IP resp +/* Remote SMTP server response, to be stored into the cache. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Keean Schupke +/* Fry-IT Ltd. +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library + */ +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <base64_code.h> +#include <dict.h> + + /* + * Global library + */ +#include <dsn_util.h> +#include <dict_proxy.h> + + /* + * Application-specific + */ +#include "smtp.h" +#include "smtp_sasl_auth_cache.h" + + /* + * XXX This feature stores passwords, so we must mask them with a strong + * cryptographic hash. This requires OpenSSL support. + * + * XXX It would be even better if the stored hash were salted. + */ +#ifdef HAVE_SASL_AUTH_CACHE + +/* smtp_sasl_auth_cache_init - per-process initialization (pre jail) */ + +SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *map, int ttl) +{ + const char *myname = "smtp_sasl_auth_cache_init"; + SMTP_SASL_AUTH_CACHE *auth_cache; + + /* + * Sanity checks. + */ +#define HAS_MULTIPLE_VALUES(s) ((s)[strcspn((s), CHARS_COMMA_SP)] != 0) + + if (*map == 0) + msg_panic("%s: empty SASL authentication cache name", myname); + if (ttl < 0) + msg_panic("%s: bad SASL authentication cache ttl: %d", myname, ttl); + if (HAS_MULTIPLE_VALUES(map)) + msg_fatal("SASL authentication cache name \"%s\" " + "contains multiple values", map); + + /* + * XXX To avoid multiple writers the map needs to be maintained by the + * proxywrite service. We would like to have a DICT_FLAG_REQ_PROXY flag + * so that the library can enforce this, but that requires moving the + * dict_proxy module one level down in the build dependency hierarchy. + */ +#define CACHE_DICT_OPEN_FLAGS \ + (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_UTF8_REQUEST) +#define PROXY_COLON DICT_TYPE_PROXY ":" +#define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1) + + if (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) != 0) + msg_fatal("SASL authentication cache name \"%s\" must start with \"" + PROXY_COLON, map); + + auth_cache = (SMTP_SASL_AUTH_CACHE *) mymalloc(sizeof(*auth_cache)); + auth_cache->dict = dict_open(map, O_CREAT | O_RDWR, CACHE_DICT_OPEN_FLAGS); + auth_cache->ttl = ttl; + auth_cache->dsn = mystrdup(""); + auth_cache->text = mystrdup(""); + return (auth_cache); +} + + /* + * Each cache lookup key contains a server host name and user name. Each + * cache value contains a time stamp, a hashed password, and the server + * response. With this organization, we don't have to worry about cache + * pollution, because we can detect if a cache entry has expired, or if the + * password has changed. + */ + +/* smtp_sasl_auth_cache_make_key - format auth failure cache lookup key */ + +static char *smtp_sasl_auth_cache_make_key(const char *host, const char *user) +{ + VSTRING *buf = vstring_alloc(100); + + vstring_sprintf(buf, "%s;%s", host, user); + return (vstring_export(buf)); +} + +/* smtp_sasl_auth_cache_make_pass - hash the auth failure cache password */ + +static char *smtp_sasl_auth_cache_make_pass(const char *password) +{ + VSTRING *buf = vstring_alloc(2 * SHA_DIGEST_LENGTH); + + base64_encode(buf, (const char *) SHA1((const unsigned char *) password, + strlen(password), 0), + SHA_DIGEST_LENGTH); + return (vstring_export(buf)); +} + +/* smtp_sasl_auth_cache_make_value - format auth failure cache value */ + +static char *smtp_sasl_auth_cache_make_value(const char *password, + const char *dsn, + const char *rep_str) +{ + VSTRING *val_buf = vstring_alloc(100); + char *pwd_hash; + unsigned long now = (unsigned long) time((time_t *) 0); + + pwd_hash = smtp_sasl_auth_cache_make_pass(password); + vstring_sprintf(val_buf, "%lu;%s;%s;%s", now, pwd_hash, dsn, rep_str); + myfree(pwd_hash); + return (vstring_export(val_buf)); +} + +/* smtp_sasl_auth_cache_valid_value - validate auth failure cache value */ + +static int smtp_sasl_auth_cache_valid_value(SMTP_SASL_AUTH_CACHE *auth_cache, + const char *entry, + const char *password) +{ + ssize_t len = strlen(entry); + char *cache_hash = mymalloc(len); + char *curr_hash; + unsigned long now = (unsigned long) time((time_t *) 0); + unsigned long time_stamp; + int valid; + + auth_cache->dsn = myrealloc(auth_cache->dsn, len); + auth_cache->text = myrealloc(auth_cache->text, len); + + if (sscanf(entry, "%lu;%[^;];%[^;];%[^\n]", &time_stamp, cache_hash, + auth_cache->dsn, auth_cache->text) != 4 + || !dsn_valid(auth_cache->dsn)) { + msg_warn("bad smtp_sasl_auth_cache entry: %.100s", entry); + valid = 0; + } else if (time_stamp + auth_cache->ttl < now) { + valid = 0; + } else { + curr_hash = smtp_sasl_auth_cache_make_pass(password); + valid = (strcmp(cache_hash, curr_hash) == 0); + myfree(curr_hash); + } + myfree(cache_hash); + return (valid); +} + +/* smtp_sasl_auth_cache_find - search auth failure cache */ + +int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *auth_cache, + const SMTP_SESSION *session) +{ + SMTP_ITERATOR *iter = session->iterator; + char *key; + const char *entry; + int valid = 0; + + key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username); + if ((entry = dict_get(auth_cache->dict, key)) != 0) + if ((valid = smtp_sasl_auth_cache_valid_value(auth_cache, entry, + session->sasl_passwd)) == 0) + /* Remove expired, password changed, or malformed cache entry. */ + if (dict_del(auth_cache->dict, key) != 0) + msg_warn("SASL auth failure map %s: entry not deleted: %s", + auth_cache->dict->name, key); + if (auth_cache->dict->error) + msg_warn("SASL auth failure map %s: lookup failed for %s", + auth_cache->dict->name, key); + myfree(key); + return (valid); +} + +/* smtp_sasl_auth_cache_store - update auth failure cache */ + +void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *auth_cache, + const SMTP_SESSION *session, + const SMTP_RESP *resp) +{ + SMTP_ITERATOR *iter = session->iterator; + char *key; + char *value; + + key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username); + value = smtp_sasl_auth_cache_make_value(session->sasl_passwd, + resp->dsn, resp->str); + dict_put(auth_cache->dict, key, value); + + myfree(value); + myfree(key); +} + +#endif diff --git a/src/smtp/smtp_sasl_auth_cache.h b/src/smtp/smtp_sasl_auth_cache.h new file mode 100644 index 0000000..cbbdb0d --- /dev/null +++ b/src/smtp/smtp_sasl_auth_cache.h @@ -0,0 +1,62 @@ +#ifndef _SMTP_SASL_AUTH_CACHE_H_INCLUDED_ +#define _SMTP_SASL_AUTH_CACHE_H_INCLUDED_ + +/*++ +/* NAME +/* smtp_sasl_auth_cache 3h +/* SUMMARY +/* Postfix SASL authentication failure cache +/* SYNOPSIS +/* #include "smtp.h" +/* #include "smtp_sasl_auth_cache.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * This code stores hashed passwords which requires OpenSSL. + */ +#if defined(USE_TLS) && defined(USE_SASL_AUTH) +#define HAVE_SASL_AUTH_CACHE + + /* + * External interface. + */ +typedef struct { + DICT *dict; + int ttl; + char *dsn; + char *text; +} SMTP_SASL_AUTH_CACHE; + +extern SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *, int); +extern void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *, const SMTP_RESP *); +extern int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *); + +#define smtp_sasl_auth_cache_dsn(cp) ((cp)->dsn) +#define smtp_sasl_auth_cache_text(cp) ((cp)->text) + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/smtp/smtp_sasl_glue.c b/src/smtp/smtp_sasl_glue.c new file mode 100644 index 0000000..ef8e8c4 --- /dev/null +++ b/src/smtp/smtp_sasl_glue.c @@ -0,0 +1,512 @@ +/*++ +/* NAME +/* smtp_sasl_glue 3 +/* SUMMARY +/* Postfix SASL interface for SMTP client +/* SYNOPSIS +/* #include smtp_sasl.h +/* +/* void smtp_sasl_initialize() +/* +/* void smtp_sasl_connect(session) +/* SMTP_SESSION *session; +/* +/* void smtp_sasl_start(session, sasl_opts_name, sasl_opts_val) +/* SMTP_SESSION *session; +/* +/* int smtp_sasl_passwd_lookup(session) +/* SMTP_SESSION *session; +/* +/* int smtp_sasl_authenticate(session, why) +/* SMTP_SESSION *session; +/* DSN_BUF *why; +/* +/* void smtp_sasl_cleanup(session) +/* SMTP_SESSION *session; +/* +/* void smtp_sasl_passivate(session, buf) +/* SMTP_SESSION *session; +/* VSTRING *buf; +/* +/* int smtp_sasl_activate(session, buf) +/* SMTP_SESSION *session; +/* char *buf; +/* DESCRIPTION +/* smtp_sasl_initialize() initializes the SASL library. This +/* routine must be called once at process startup, before any +/* chroot operations. +/* +/* smtp_sasl_connect() performs per-session initialization. This +/* routine must be called once at the start of each connection. +/* +/* smtp_sasl_start() performs per-session initialization. This +/* routine must be called once per session before doing any SASL +/* authentication. The sasl_opts_name and sasl_opts_val parameters are +/* the postfix configuration parameters setting the security +/* policy of the SASL authentication. +/* +/* smtp_sasl_passwd_lookup() looks up the username/password +/* for the current SMTP server. The result is zero in case +/* of failure, a long jump in case of error. +/* +/* smtp_sasl_authenticate() implements the SASL authentication +/* dialog. The result is < 0 in case of protocol failure, zero in +/* case of unsuccessful authentication, > 0 in case of success. +/* The why argument is updated with a reason for failure. +/* This routine must be called only when smtp_sasl_passwd_lookup() +/* succeeds. +/* +/* smtp_sasl_cleanup() cleans up. It must be called at the +/* end of every SMTP session that uses SASL authentication. +/* This routine is a noop for non-SASL sessions. +/* +/* smtp_sasl_passivate() appends flattened SASL attributes to the +/* specified buffer. The SASL attributes are not destroyed. +/* +/* smtp_sasl_activate() restores SASL attributes from the +/* specified buffer. The buffer is modified. A result < 0 +/* means there was an error. +/* +/* Arguments: +/* .IP session +/* Session context. +/* .IP mech_list +/* String of SASL mechanisms (separated by blanks) +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <stdlib.h> +#include <string.h> + + /* + * Utility library + */ +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <split_at.h> + + /* + * Global library + */ +#include <mail_params.h> +#include <string_list.h> +#include <maps.h> +#include <mail_addr_find.h> +#include <smtp_stream.h> + + /* + * XSASL library. + */ +#include <xsasl.h> + + /* + * Application-specific + */ +#include "smtp.h" +#include "smtp_sasl.h" +#include "smtp_sasl_auth_cache.h" + +#ifdef USE_SASL_AUTH + + /* + * Per-host login/password information. + */ +static MAPS *smtp_sasl_passwd_map; + + /* + * Supported SASL mechanisms. + */ +STRING_LIST *smtp_sasl_mechs; + + /* + * SASL implementation handle. + */ +static XSASL_CLIENT_IMPL *smtp_sasl_impl; + + /* + * The 535 SASL authentication failure cache. + */ +#ifdef HAVE_SASL_AUTH_CACHE +static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache; + +#endif + +/* smtp_sasl_passwd_lookup - password lookup routine */ + +int smtp_sasl_passwd_lookup(SMTP_SESSION *session) +{ + const char *myname = "smtp_sasl_passwd_lookup"; + SMTP_STATE *state = session->state; + SMTP_ITERATOR *iter = session->iterator; + const char *value; + char *passwd; + + /* + * Sanity check. + */ + if (smtp_sasl_passwd_map == 0) + msg_panic("%s: passwd map not initialized", myname); + + /* + * Look up the per-server password information. Try the hostname first, + * then try the destination. + * + * XXX Instead of using nexthop (the intended destination) we use dest + * (either the intended destination, or a fall-back destination). + * + * XXX SASL authentication currently depends on the host/domain but not on + * the TCP port. If the port is not :25, we should append it to the table + * lookup key. Code for this was briefly introduced into 2.2 snapshots, + * but didn't canonicalize the TCP port, and did not append the port to + * the MX hostname. + */ + smtp_sasl_passwd_map->error = 0; + if ((smtp_mode + && var_smtp_sender_auth && state->request->sender[0] + && (value = mail_addr_find(smtp_sasl_passwd_map, + state->request->sender, (char **) 0)) != 0) + || (smtp_sasl_passwd_map->error == 0 + && (value = maps_find(smtp_sasl_passwd_map, + STR(iter->host), 0)) != 0) + || (smtp_sasl_passwd_map->error == 0 + && (value = maps_find(smtp_sasl_passwd_map, + STR(iter->dest), 0)) != 0)) { + if (session->sasl_username) + myfree(session->sasl_username); + session->sasl_username = mystrdup(value); + passwd = split_at(session->sasl_username, ':'); + if (session->sasl_passwd) + myfree(session->sasl_passwd); + session->sasl_passwd = mystrdup(passwd ? passwd : ""); + if (msg_verbose) + msg_info("%s: host `%s' user `%s' pass `%s'", + myname, STR(iter->host), + session->sasl_username, session->sasl_passwd); + return (1); + } else if (smtp_sasl_passwd_map->error) { + msg_warn("%s: %s lookup error", + state->request->queue_id, smtp_sasl_passwd_map->title); + vstream_longjmp(session->stream, SMTP_ERR_DATA); + } else { + if (msg_verbose) + msg_info("%s: no auth info found (sender=`%s', host=`%s')", + myname, state->request->sender, STR(iter->host)); + return (0); + } +} + +/* smtp_sasl_initialize - per-process initialization (pre jail) */ + +void smtp_sasl_initialize(void) +{ + + /* + * Sanity check. + */ + if (smtp_sasl_passwd_map || smtp_sasl_impl) + msg_panic("smtp_sasl_initialize: repeated call"); + if (*var_smtp_sasl_passwd == 0) + msg_fatal("specify a password table via the `%s' configuration parameter", + VAR_LMTP_SMTP(SASL_PASSWD)); + + /* + * Open the per-host password table and initialize the SASL library. Use + * shared locks for reading, just in case someone updates the table. + */ + smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD), + var_smtp_sasl_passwd, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type, + var_smtp_sasl_path)) == 0) + msg_fatal("SASL library initialization"); + + /* + * Initialize optional supported mechanism matchlist + */ + if (*var_smtp_sasl_mechs) + smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS, + MATCH_FLAG_NONE, + var_smtp_sasl_mechs); + + /* + * Initialize the 535 SASL authentication failure cache. + */ + if (*var_smtp_sasl_auth_cache_name) { +#ifdef HAVE_SASL_AUTH_CACHE + smtp_sasl_auth_cache = + smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name, + var_smtp_sasl_auth_cache_time); +#else + msg_warn("not compiled with TLS support -- " + "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME)); +#endif + } +} + +/* smtp_sasl_connect - per-session client initialization */ + +void smtp_sasl_connect(SMTP_SESSION *session) +{ + + /* + * This initialization happens whenever we instantiate an SMTP session + * object. We don't instantiate a SASL client until we actually need one. + */ + session->sasl_mechanism_list = 0; + session->sasl_username = 0; + session->sasl_passwd = 0; + session->sasl_client = 0; + session->sasl_reply = 0; +} + +/* smtp_sasl_start - per-session SASL initialization */ + +void smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name, + const char *sasl_opts_val) +{ + XSASL_CLIENT_CREATE_ARGS create_args; + SMTP_ITERATOR *iter = session->iterator; + + if (msg_verbose) + msg_info("starting new SASL client"); + if ((session->sasl_client = + XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args, + stream = session->stream, + service = var_procname, + server_name = STR(iter->host), + security_options = sasl_opts_val)) == 0) + msg_fatal("SASL per-connection initialization failed"); + session->sasl_reply = vstring_alloc(20); +} + +/* smtp_sasl_authenticate - run authentication protocol */ + +int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why) +{ + const char *myname = "smtp_sasl_authenticate"; + SMTP_ITERATOR *iter = session->iterator; + SMTP_RESP *resp; + const char *mechanism; + int result; + char *line; + int steps = 0; + + /* + * Sanity check. + */ + if (session->sasl_mechanism_list == 0) + msg_panic("%s: no mechanism list", myname); + + if (msg_verbose) + msg_info("%s: %s: SASL mechanisms %s", + myname, session->namaddrport, session->sasl_mechanism_list); + + /* + * Avoid repeated login failures after a recent 535 error. + */ +#ifdef HAVE_SASL_AUTH_CACHE + if (smtp_sasl_auth_cache + && smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) { + char *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache); + char *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache); + + if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5') + resp_dsn[0] = '4'; + dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS, + STR(iter->host), var_procname, resp_str, + "SASL [CACHED] authentication failed; server %s said: %s", + STR(iter->host), resp_str); + return (0); + } +#endif + + /* + * Start the client side authentication protocol. + */ + result = xsasl_client_first(session->sasl_client, + session->sasl_mechanism_list, + session->sasl_username, + session->sasl_passwd, + &mechanism, session->sasl_reply); + if (result != XSASL_AUTH_OK) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA, + DSB_DTYPE_SASL, STR(session->sasl_reply), + "SASL authentication failed; " + "cannot authenticate to server %s: %s", + session->namaddr, STR(session->sasl_reply)); + return (-1); + } + /*- + * Send the AUTH command and the optional initial client response. + * + * https://tools.ietf.org/html/rfc4954#page-4 + * Note that the AUTH command is still subject to the line length + * limitations defined in [SMTP]. If use of the initial response argument + * would cause the AUTH command to exceed this length, the client MUST NOT + * use the initial response parameter... + * + * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4 + * The maximum total length of a command line including the command word + * and the <CRLF> is 512 octets. + * + * Defer the initial response if the resulting command exceeds the limit. + */ + if (LEN(session->sasl_reply) > 0 + && strlen(mechanism) + LEN(session->sasl_reply) + 8 <= 512) { + smtp_chat_cmd(session, "AUTH %s %s", mechanism, + STR(session->sasl_reply)); + VSTRING_RESET(session->sasl_reply); /* no deferred initial reply */ + } else { + smtp_chat_cmd(session, "AUTH %s", mechanism); + } + + /* + * Step through the authentication protocol until the server tells us + * that we are done. If session->sasl_reply is non-empty we have a + * deferred initial reply and expect an empty initial challenge from the + * server. If the server's initial challenge is non-empty we have a SASL + * protocol violation with both sides wanting to go first. + */ + while ((resp = smtp_chat_resp(session))->code / 100 == 3) { + + /* + * Sanity check. + */ + if (++steps > 100) { + dsb_simple(why, "4.3.0", "SASL authentication failed; " + "authentication protocol loop with server %s", + session->namaddr); + return (-1); + } + + /* + * Process a server challenge. + */ + line = resp->str; + (void) mystrtok(&line, "- \t\n"); /* skip over result code */ + + if (LEN(session->sasl_reply) > 0) { + + /* + * Deferred initial response, the server challenge must be empty. + * Cleared after actual transmission to the server. + */ + if (*line) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, + DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error", + "SASL authentication failed; non-empty initial " + "%s challenge from server %s: %s", mechanism, + session->namaddr, STR(session->sasl_reply)); + return (-1); + } + } else { + result = xsasl_client_next(session->sasl_client, line, + session->sasl_reply); + if (result != XSASL_AUTH_OK) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */ + DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply), + "SASL authentication failed; " + "cannot authenticate to server %s: %s", + session->namaddr, STR(session->sasl_reply)); + return (-1); /* Fix 200512 */ + } + } + + /* + * Send a client response. + */ + smtp_chat_cmd(session, "%s", STR(session->sasl_reply)); + VSTRING_RESET(session->sasl_reply); /* clear initial reply */ + } + + /* + * We completed the authentication protocol. + */ + if (resp->code / 100 != 2) { +#ifdef HAVE_SASL_AUTH_CACHE + /* Update the 535 authentication failure cache. */ + if (smtp_sasl_auth_cache && resp->code == 535) + smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp); +#endif + if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5) + STR(resp->dsn_buf)[0] = '4'; + dsb_update(why, resp->dsn, DSB_DEF_ACTION, + DSB_MTYPE_DNS, STR(iter->host), + var_procname, resp->str, + "SASL authentication failed; server %s said: %s", + session->namaddr, resp->str); + return (0); + } + return (1); +} + +/* smtp_sasl_cleanup - per-session cleanup */ + +void smtp_sasl_cleanup(SMTP_SESSION *session) +{ + if (session->sasl_username) { + myfree(session->sasl_username); + session->sasl_username = 0; + } + if (session->sasl_passwd) { + myfree(session->sasl_passwd); + session->sasl_passwd = 0; + } + if (session->sasl_mechanism_list) { + /* allocated in smtp_sasl_helo_auth */ + myfree(session->sasl_mechanism_list); + session->sasl_mechanism_list = 0; + } + if (session->sasl_client) { + if (msg_verbose) + msg_info("disposing SASL state information"); + xsasl_client_free(session->sasl_client); + session->sasl_client = 0; + } + if (session->sasl_reply) { + vstring_free(session->sasl_reply); + session->sasl_reply = 0; + } +} + +/* smtp_sasl_passivate - append serialized SASL attributes */ + +void smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf) +{ +} + +/* smtp_sasl_activate - de-serialize SASL attributes */ + +int smtp_sasl_activate(SMTP_SESSION *session, char *buf) +{ + return (0); +} + +#endif diff --git a/src/smtp/smtp_sasl_proto.c b/src/smtp/smtp_sasl_proto.c new file mode 100644 index 0000000..6a51696 --- /dev/null +++ b/src/smtp/smtp_sasl_proto.c @@ -0,0 +1,202 @@ +/*++ +/* NAME +/* smtp_sasl_proto 3 +/* SUMMARY +/* Postfix SASL interface for SMTP client +/* SYNOPSIS +/* #include smtp_sasl.h +/* +/* void smtp_sasl_helo_auth(state, words) +/* SMTP_STATE *state; +/* const char *words; +/* +/* int smtp_sasl_helo_login(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* This module contains random chunks of code that implement +/* the SMTP protocol interface for SASL negotiation. The goal +/* is to reduce clutter in the main SMTP client source code. +/* +/* smtp_sasl_helo_auth() processes the AUTH option in the +/* SMTP server's EHLO response. +/* +/* smtp_sasl_helo_login() authenticates the SMTP client to the +/* SMTP server, using the authentication mechanism information +/* given by the server. The result is a Postfix delivery status +/* code in case of trouble. +/* +/* Arguments: +/* .IP state +/* Session context. +/* .IP words +/* List of SASL authentication mechanisms (separated by blanks) +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + +#ifdef USE_SASL_AUTH + +/* smtp_sasl_compat_mechs - Trim server's mechanism list */ + +static const char *smtp_sasl_compat_mechs(const char *words) +{ + static VSTRING *buf; + char *mech_list; + char *save_mech; + char *mech; + + /* + * Use server's mechanisms if no filter specified + */ + if (smtp_sasl_mechs == 0 || *words == 0) + return (words); + + if (buf == 0) + buf = vstring_alloc(10); + + VSTRING_RESET(buf); + VSTRING_TERMINATE(buf); + + save_mech = mech_list = mystrdup(words); + + while ((mech = mystrtok(&mech_list, " \t")) != 0) { + if (string_list_match(smtp_sasl_mechs, mech)) { + if (VSTRING_LEN(buf) > 0) + VSTRING_ADDCH(buf, ' '); + vstring_strcat(buf, mech); + } else if (smtp_sasl_mechs->error) { + msg_fatal("SASL mechanism filter failed for: '%s'", mech); + } + } + myfree(save_mech); + + return (vstring_str(buf)); +} + +/* smtp_sasl_helo_auth - handle AUTH option in EHLO reply */ + +void smtp_sasl_helo_auth(SMTP_SESSION *session, const char *words) +{ + const char *mech_list = smtp_sasl_compat_mechs(words); + char *junk; + + /* + * XXX If the server offers no compatible authentication mechanisms, then + * pretend that the server doesn't support SASL authentication. + * + * XXX If the server offers multiple different lists, concatenate them. Let + * the SASL library worry about duplicates. + */ + if (session->sasl_mechanism_list) { + if (strcasecmp(session->sasl_mechanism_list, mech_list) != 0 + && strlen(mech_list) > 0 + && strlen(session->sasl_mechanism_list) < var_line_limit) { + junk = concatenate(session->sasl_mechanism_list, " ", mech_list, + (char *) 0); + myfree(session->sasl_mechanism_list); + session->sasl_mechanism_list = junk; + } + return; + } + if (strlen(mech_list) > 0) { + session->sasl_mechanism_list = mystrdup(mech_list); + } else { + msg_warn(*words ? "%s offered no supported AUTH mechanisms: '%s'" : + "%s offered null AUTH mechanism list%s", + session->namaddrport, words); + } + session->features |= SMTP_FEATURE_AUTH; +} + +/* smtp_sasl_helo_login - perform SASL login */ + +int smtp_sasl_helo_login(SMTP_STATE *state) +{ + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + int ret; + + /* + * Skip authentication when no authentication info exists for this + * server, so that we talk to each other like strangers. + */ + if (smtp_sasl_passwd_lookup(session) == 0) { + session->features &= ~SMTP_FEATURE_AUTH; + return 0; + } + + /* + * Otherwise, if authentication information exists, assume that + * authentication is required, and assume that an authentication error is + * recoverable from the message delivery point of view. An authentication + * error is unrecoverable from a session point of view - the session will + * not be reused. + */ + ret = 0; + if (session->sasl_mechanism_list == 0) { + dsb_simple(why, "4.7.0", "SASL authentication failed: " + "server %s offered no compatible authentication mechanisms for this type of connection security", + session->namaddr); + ret = smtp_sess_fail(state); + /* Session reuse is disabled. */ + } else { +#ifndef USE_TLS + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS), var_smtp_sasl_opts); +#else + if (session->tls_context == 0) + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS), + var_smtp_sasl_opts); + else if (TLS_CERT_IS_MATCHED(session->tls_context)) + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLSV_OPTS), + var_smtp_sasl_tlsv_opts); + else + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLS_OPTS), + var_smtp_sasl_tls_opts); +#endif + if (smtp_sasl_authenticate(session, why) <= 0) { + ret = smtp_sess_fail(state); + /* Session reuse is disabled. */ + } + } + return (ret); +} + +#endif diff --git a/src/smtp/smtp_session.c b/src/smtp/smtp_session.c new file mode 100644 index 0000000..1b3a20e --- /dev/null +++ b/src/smtp/smtp_session.c @@ -0,0 +1,446 @@ +/*++ +/* NAME +/* smtp_session 3 +/* SUMMARY +/* SMTP_SESSION structure management +/* SYNOPSIS +/* #include "smtp.h" +/* +/* SMTP_SESSION *smtp_session_alloc(stream, iter, start, flags) +/* VSTREAM *stream; +/* SMTP_ITERATOR *iter; +/* time_t start; +/* int flags; +/* +/* void smtp_session_free(session) +/* SMTP_SESSION *session; +/* +/* int smtp_session_passivate(session, dest_prop, endp_prop) +/* SMTP_SESSION *session; +/* VSTRING *dest_prop; +/* VSTRING *endp_prop; +/* +/* SMTP_SESSION *smtp_session_activate(fd, iter, dest_prop, endp_prop) +/* int fd; +/* SMTP_ITERATOR *iter; +/* VSTRING *dest_prop; +/* VSTRING *endp_prop; +/* DESCRIPTION +/* smtp_session_alloc() allocates memory for an SMTP_SESSION structure +/* and initializes it with the given stream and destination, host name +/* and address information. The host name and address strings are +/* copied. The port is in network byte order. +/* +/* smtp_session_free() destroys an SMTP_SESSION structure and its +/* members, making memory available for reuse. It will handle the +/* case of a null stream and will assume it was given a different +/* purpose. +/* +/* smtp_session_passivate() flattens an SMTP session (including +/* TLS context) so that it can be cached. The SMTP_SESSION +/* structure is destroyed. +/* +/* smtp_session_activate() inflates a flattened SMTP session +/* so that it can be used. The input property arguments are +/* modified. +/* +/* Arguments: +/* .IP stream +/* A full-duplex stream. +/* .IP iter +/* The literal next-hop or fall-back destination including +/* the optional [] and including the :port or :service; +/* the name of the remote host; +/* the printable address of the remote host; +/* the remote port in network byte order. +/* .IP start +/* The time when this connection was opened. +/* .IP flags +/* Zero or more of the following: +/* .RS +/* .IP SMTP_MISC_FLAG_CONN_LOAD +/* Enable re-use of cached SMTP or LMTP connections. +/* .IP SMTP_MISC_FLAG_CONN_STORE +/* Enable saving of cached SMTP or LMTP connections. +/* .RE +/* SMTP_MISC_FLAG_CONN_MASK corresponds with both _LOAD and _STORE. +/* .IP dest_prop +/* Destination specific session properties: the server is the +/* best MX host for the current logical destination, the dest, +/* host, and addr properties. When dest_prop is non-empty, it +/* overrides the iterator dest, host, and addr properties. It +/* is the caller's responsibility to save the current nexthop +/* with SMTP_ITER_SAVE_DEST() and to restore it afterwards +/* with SMTP_ITER_RESTORE_DEST() before trying alternatives. +/* .IP endp_prop +/* Endpoint specific session properties: all the features +/* advertised by the remote server. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Viktor Dukhovni +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <string.h> +#include <netinet/in.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <stringops.h> + +/* Global library. */ + +#include <mime_state.h> +#include <debug_peer.h> +#include <mail_params.h> + +/* TLS Library. */ + +#include <tls_proxy.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + + /* + * Local, because these are meaningful only for code in this file. + */ +#define SESS_ATTR_DEST "destination" +#define SESS_ATTR_HOST "host_name" +#define SESS_ATTR_ADDR "host_addr" +#define SESS_ATTR_DEST_FEATURES "destination_features" + +#define SESS_ATTR_TLS_LEVEL "tls_level" +#define SESS_ATTR_REUSE_COUNT "reuse_count" +#define SESS_ATTR_ENDP_FEATURES "endpoint_features" +#define SESS_ATTR_EXPIRE_TIME "expire_time" + +/* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */ + +SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, SMTP_ITERATOR *iter, + time_t start, int flags) +{ + SMTP_SESSION *session; + const char *host = STR(iter->host); + const char *addr = STR(iter->addr); + unsigned port = iter->port; + + session = (SMTP_SESSION *) mymalloc(sizeof(*session)); + session->stream = stream; + session->iterator = iter; + session->namaddr = concatenate(host, "[", addr, "]", (char *) 0); + session->helo = 0; + session->port = port; + session->features = 0; + + session->size_limit = 0; + session->error_mask = 0; + session->buffer = vstring_alloc(100); + session->scratch = vstring_alloc(100); + session->scratch2 = vstring_alloc(100); + smtp_chat_init(session); + session->mime_state = 0; + + if (session->port) { + vstring_sprintf(session->buffer, "%s:%d", + session->namaddr, ntohs(session->port)); + session->namaddrport = mystrdup(STR(session->buffer)); + } else + session->namaddrport = mystrdup(session->namaddr); + + session->send_proto_helo = 0; + + if (flags & SMTP_MISC_FLAG_CONN_STORE) + CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time); + else + DONT_CACHE_THIS_SESSION; + session->reuse_count = 0; + USE_NEWBORN_SESSION; /* He's not dead Jim! */ + +#ifdef USE_SASL_AUTH + smtp_sasl_connect(session); +#endif + +#ifdef USE_TLS + session->tls_context = 0; + session->tls_retry_plain = 0; + session->tls_nexthop = 0; +#endif + session->state = 0; + debug_peer_check(host, addr); + return (session); +} + +/* smtp_session_free - destroy SMTP_SESSION structure and contents */ + +void smtp_session_free(SMTP_SESSION *session) +{ +#ifdef USE_TLS + if (session->stream) { + vstream_fflush(session->stream); + } + if (session->tls_context) { + if (session->features & + (SMTP_FEATURE_FROM_CACHE | SMTP_FEATURE_FROM_PROXY)) + tls_proxy_context_free(session->tls_context); + else + tls_client_stop(smtp_tls_ctx, session->stream, + var_smtp_starttls_tmout, 0, session->tls_context); + } +#endif + if (session->stream) + vstream_fclose(session->stream); + myfree(session->namaddr); + myfree(session->namaddrport); + if (session->helo) + myfree(session->helo); + + vstring_free(session->buffer); + vstring_free(session->scratch); + vstring_free(session->scratch2); + + if (session->history) + smtp_chat_reset(session); + if (session->mime_state) + mime_state_free(session->mime_state); + +#ifdef USE_SASL_AUTH + smtp_sasl_cleanup(session); +#endif + + debug_peer_restore(); + myfree((void *) session); +} + +/* smtp_session_passivate - passivate an SMTP_SESSION object */ + +int smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop, + VSTRING *endp_prop) +{ + SMTP_ITERATOR *iter = session->iterator; + VSTREAM *mp; + int fd; + + /* + * Encode the delivery request next-hop to endpoint binding properties: + * whether or not this server is best MX host for the delivery request + * next-hop or fall-back logical destination (this information is needed + * for loop handling in smtp_proto()). + * + * TODO: save SASL username and password information so that we can + * correctly save a reused authenticated connection. + * + * These memory writes should never fail. + */ + if ((mp = vstream_memopen(dest_prop, O_WRONLY)) == 0 + || attr_print_plain(mp, ATTR_FLAG_NONE, + SEND_ATTR_STR(SESS_ATTR_DEST, STR(iter->dest)), + SEND_ATTR_STR(SESS_ATTR_HOST, STR(iter->host)), + SEND_ATTR_STR(SESS_ATTR_ADDR, STR(iter->addr)), + SEND_ATTR_INT(SESS_ATTR_DEST_FEATURES, + session->features & SMTP_FEATURE_DESTINATION_MASK), + ATTR_TYPE_END) != 0 + || vstream_fclose(mp) != 0) + msg_fatal("smtp_session_passivate: can't save dest properties: %m"); + + /* + * Encode the physical endpoint properties: all the session properties + * except for "session from cache", "best MX", or "RSET failure". Plus + * the TLS level, reuse count, and connection expiration time. + * + * XXX Should also record how many non-delivering mail transactions there + * were during this session, and perhaps other statistics, so that we + * don't reuse a session too much. + * + * TODO: passivate SASL username and password information so that we can + * correctly save a reused authenticated connection. + * + * These memory writes should never fail. + */ + if ((mp = vstream_memopen(endp_prop, O_WRONLY)) == 0 + || attr_print_plain(mp, ATTR_FLAG_NONE, +#ifdef USE_TLS + SEND_ATTR_INT(SESS_ATTR_TLS_LEVEL, + session->state->tls->level), +#endif + SEND_ATTR_INT(SESS_ATTR_REUSE_COUNT, + session->reuse_count), + SEND_ATTR_INT(SESS_ATTR_ENDP_FEATURES, + session->features & SMTP_FEATURE_ENDPOINT_MASK), + SEND_ATTR_LONG(SESS_ATTR_EXPIRE_TIME, + (long) session->expire_time), + ATTR_TYPE_END) != 0 + + /* + * Append the passivated TLS context. These memory writes should never + * fail. + */ +#ifdef USE_TLS + || (session->tls_context + && attr_print_plain(mp, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_context_print, + (void *) session->tls_context), + ATTR_TYPE_END) != 0) +#endif + || vstream_fclose(mp) != 0) + msg_fatal("smtp_session_passivate: cannot save TLS context: %m"); + + /* + * Salvage the underlying file descriptor, and destroy the session + * object. + */ + fd = vstream_fileno(session->stream); + vstream_fdclose(session->stream); + session->stream = 0; + smtp_session_free(session); + + return (fd); +} + +/* smtp_session_activate - re-activate a passivated SMTP_SESSION object */ + +SMTP_SESSION *smtp_session_activate(int fd, SMTP_ITERATOR *iter, + VSTRING *dest_prop, + VSTRING *endp_prop) +{ + const char *myname = "smtp_session_activate"; + VSTREAM *mp; + SMTP_SESSION *session; + int endp_features; /* server features */ + int dest_features; /* server features */ + long expire_time; /* session re-use expiration time */ + int reuse_count; /* # times reused */ + +#ifdef USE_TLS + TLS_SESS_STATE *tls_context = 0; + SMTP_TLS_POLICY *tls = iter->parent->tls; + +#define TLS_PROXY_CONTEXT_FREE() do { \ + if (tls_context) \ + tls_proxy_context_free(tls_context); \ + } while (0) +#else +#define TLS_PROXY_CONTEXT_FREE() /* nothing */ +#endif + +#define SMTP_SESSION_ACTIVATE_ERR_RETURN() do { \ + TLS_PROXY_CONTEXT_FREE(); \ + return (0); \ + } while (0) + + /* + * Sanity check: if TLS is required, the cached properties must contain a + * TLS context. + */ + if ((mp = vstream_memopen(endp_prop, O_RDONLY)) == 0 + || attr_scan_plain(mp, ATTR_FLAG_NONE, +#ifdef USE_TLS + RECV_ATTR_INT(SESS_ATTR_TLS_LEVEL, + &tls->level), +#endif + RECV_ATTR_INT(SESS_ATTR_REUSE_COUNT, + &reuse_count), + RECV_ATTR_INT(SESS_ATTR_ENDP_FEATURES, + &endp_features), + RECV_ATTR_LONG(SESS_ATTR_EXPIRE_TIME, + &expire_time), + ATTR_TYPE_END) != 4 +#ifdef USE_TLS + || ((tls->level > TLS_LEV_MAY + || (tls->level == TLS_LEV_MAY && vstream_peek(mp) > 0)) + && attr_scan_plain(mp, ATTR_FLAG_NONE, + RECV_ATTR_FUNC(tls_proxy_context_scan, + (void *) &tls_context), + ATTR_TYPE_END) != 1) +#endif + || vstream_fclose(mp) != 0) { + msg_warn("smtp_session_activate: bad cached endp properties"); + SMTP_SESSION_ACTIVATE_ERR_RETURN(); + } + + /* + * Clobber the iterator's current nexthop, host and address fields with + * cached-connection information. This is done when a session is looked + * up by delivery request nexthop instead of address and port. It is the + * caller's responsibility to save and restore the delivery request + * nexthop with SMTP_ITER_SAVE_DEST() and SMTP_ITER_RESTORE_DEST(). + * + * TODO: Eliminate the duplication between SMTP_ITERATOR and SMTP_SESSION. + * + * TODO: restore SASL username and password information so that we can + * correctly save a reused authenticated connection. + */ + if (dest_prop && VSTRING_LEN(dest_prop)) { + if ((mp = vstream_memopen(dest_prop, O_RDONLY)) == 0 + || attr_scan_plain(mp, ATTR_FLAG_NONE, + RECV_ATTR_STR(SESS_ATTR_DEST, iter->dest), + RECV_ATTR_STR(SESS_ATTR_HOST, iter->host), + RECV_ATTR_STR(SESS_ATTR_ADDR, iter->addr), + RECV_ATTR_INT(SESS_ATTR_DEST_FEATURES, + &dest_features), + ATTR_TYPE_END) != 4 + || vstream_fclose(mp) != 0) { + msg_warn("smtp_session_passivate: bad cached dest properties"); + SMTP_SESSION_ACTIVATE_ERR_RETURN(); + } + } else { + dest_features = 0; + } +#ifdef USE_TLS + if (msg_verbose) + msg_info("%s: tls_level=%d", myname, tls->level); +#endif + + /* + * Allright, bundle up what we have sofar. + */ +#define NO_FLAGS 0 + + session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), iter, + (time_t) 0, NO_FLAGS); + session->features = + (endp_features | dest_features | SMTP_FEATURE_FROM_CACHE); +#ifdef USE_TLS + session->tls_context = tls_context; +#endif + CACHE_THIS_SESSION_UNTIL(expire_time); + session->reuse_count = ++reuse_count; + + if (msg_verbose) + msg_info("%s: dest=%s host=%s addr=%s port=%u features=0x%x, " + "ttl=%ld, reuse=%d", + myname, STR(iter->dest), STR(iter->host), + STR(iter->addr), ntohs(iter->port), + endp_features | dest_features, + (long) (expire_time - time((time_t *) 0)), + reuse_count); + +#if USE_TLS + if (tls_context) + tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_USED, + session->tls_context); +#endif + + return (session); +} diff --git a/src/smtp/smtp_state.c b/src/smtp/smtp_state.c new file mode 100644 index 0000000..f91ab8c --- /dev/null +++ b/src/smtp/smtp_state.c @@ -0,0 +1,114 @@ +/*++ +/* NAME +/* smtp_state 3 +/* SUMMARY +/* initialize/cleanup shared state +/* SYNOPSIS +/* #include "smtp.h" +/* +/* SMTP_STATE *smtp_state_alloc() +/* +/* void smtp_state_free(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* smtp_state_init() initializes the shared state, and allocates +/* memory for buffers etc. +/* +/* smtp_cleanup() destroys memory allocated by smtp_state_init(). +/* STANDARDS +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <vstring.h> +#include <msg.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + +/* smtp_state_alloc - initialize */ + +SMTP_STATE *smtp_state_alloc(void) +{ + SMTP_STATE *state = (SMTP_STATE *) mymalloc(sizeof(*state)); + + state->misc_flags = 0; + state->src = 0; + state->service = 0; + state->request = 0; + state->session = 0; + state->status = 0; + state->space_left = 0; + state->iterator->request_nexthop = vstring_alloc(100); + state->iterator->dest = vstring_alloc(100); + state->iterator->host = vstring_alloc(100); + state->iterator->addr = vstring_alloc(100); + state->iterator->saved_dest = vstring_alloc(100); + if (var_smtp_cache_conn) { + state->dest_label = vstring_alloc(10); + state->dest_prop = vstring_alloc(10); + state->endp_label = vstring_alloc(10); + state->endp_prop = vstring_alloc(10); + state->cache_used = htable_create(1); + } else { + state->dest_label = 0; + state->dest_prop = 0; + state->endp_label = 0; + state->endp_prop = 0; + state->cache_used = 0; + } + state->why = dsb_create(); + return (state); +} + +/* smtp_state_free - destroy state */ + +void smtp_state_free(SMTP_STATE *state) +{ +#ifdef USE_TLS + /* The TLS policy cache lifetime is one delivery. */ + smtp_tls_policy_cache_flush(); +#endif + vstring_free(state->iterator->request_nexthop); + vstring_free(state->iterator->dest); + vstring_free(state->iterator->host); + vstring_free(state->iterator->addr); + vstring_free(state->iterator->saved_dest); + if (state->dest_label) + vstring_free(state->dest_label); + if (state->dest_prop) + vstring_free(state->dest_prop); + if (state->endp_label) + vstring_free(state->endp_label); + if (state->endp_prop) + vstring_free(state->endp_prop); + if (state->cache_used) + htable_free(state->cache_used, (void (*) (void *)) 0); + if (state->why) + dsb_free(state->why); + + myfree((void *) state); +} diff --git a/src/smtp/smtp_tls_policy.c b/src/smtp/smtp_tls_policy.c new file mode 100644 index 0000000..4b394a9 --- /dev/null +++ b/src/smtp/smtp_tls_policy.c @@ -0,0 +1,928 @@ +/*++ +/* NAME +/* smtp_tls_policy 3 +/* SUMMARY +/* SMTP_TLS_POLICY structure management +/* SYNOPSIS +/* #include "smtp.h" +/* +/* void smtp_tls_list_init() +/* +/* int smtp_tls_policy_cache_query(why, tls, iter) +/* DSN_BUF *why; +/* SMTP_TLS_POLICY *tls; +/* SMTP_ITERATOR *iter; +/* +/* void smtp_tls_policy_dummy(tls) +/* SMTP_TLS_POLICY *tls; +/* +/* void smtp_tls_policy_cache_flush() +/* DESCRIPTION +/* smtp_tls_list_init() initializes lookup tables used by the TLS +/* policy engine. +/* +/* smtp_tls_policy_cache_query() returns a shallow copy of the +/* cached SMTP_TLS_POLICY structure for the iterator's +/* destination, host, port and DNSSEC validation status. +/* This copy is guaranteed to be valid until the next +/* smtp_tls_policy_cache_query() or smtp_tls_policy_cache_flush() +/* call. The caller can override the TLS security level without +/* corrupting the policy cache. +/* When any required table or DNS lookups fail, the TLS level +/* is set to TLS_LEV_INVALID, the "why" argument is updated +/* with the error reason and the result value is zero (false). +/* +/* smtp_tls_policy_dummy() initializes a trivial, non-cached, +/* policy with TLS disabled. +/* +/* smtp_tls_policy_cache_flush() destroys the TLS policy cache +/* and contents. +/* +/* Arguments: +/* .IP why +/* A pointer to a DSN_BUF which holds error status information when +/* the TLS policy lookup fails. +/* .IP tls +/* Pointer to TLS policy storage. +/* .IP iter +/* The literal next-hop or fall-back destination including +/* the optional [] and including the :port or :service; +/* the name of the remote host after MX and CNAME expansions +/* (see smtp_cname_overrides_servername for the handling +/* of hostnames that resolve to a CNAME record); +/* the printable address of the remote host; +/* the remote port in network byte order; +/* the DNSSEC validation status of the host name lookup after +/* MX and CNAME expansions. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Viktor Dukhovni +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +#ifdef USE_TLS + +#include <netinet/in.h> /* ntohs() for Solaris or BSD */ +#include <arpa/inet.h> /* ntohs() for Linux or BSD */ +#include <stdlib.h> +#include <string.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <stringops.h> +#include <valid_hostname.h> +#include <valid_utf8_hostname.h> +#include <ctable.h> + +/* Global library. */ + +#include <mail_params.h> +#include <maps.h> +#include <dsn_buf.h> + +/* DNS library. */ + +#include <dns.h> + +/* Application-specific. */ + +#include "smtp.h" + +/* XXX Cache size should scale with [sl]mtp_mx_address_limit. */ +#define CACHE_SIZE 20 +static CTABLE *policy_cache; + +static int global_tls_level(void); +static void dane_init(SMTP_TLS_POLICY *, SMTP_ITERATOR *); + +static MAPS *tls_policy; /* lookup table(s) */ +static MAPS *tls_per_site; /* lookup table(s) */ + +/* smtp_tls_list_init - initialize per-site policy lists */ + +void smtp_tls_list_init(void) +{ + if (*var_smtp_tls_policy) { + tls_policy = maps_create(VAR_LMTP_SMTP(TLS_POLICY), + var_smtp_tls_policy, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if (*var_smtp_tls_per_site) + msg_warn("%s ignored when %s is not empty.", + VAR_LMTP_SMTP(TLS_PER_SITE), VAR_LMTP_SMTP(TLS_POLICY)); + return; + } + if (*var_smtp_tls_per_site) { + tls_per_site = maps_create(VAR_LMTP_SMTP(TLS_PER_SITE), + var_smtp_tls_per_site, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + } +} + +/* policy_name - printable tls policy level */ + +static const char *policy_name(int tls_level) +{ + const char *name = str_tls_level(tls_level); + + if (name == 0) + name = "unknown"; + return name; +} + +#define MARK_INVALID(why, levelp) do { \ + dsb_simple((why), "4.7.5", "client TLS configuration problem"); \ + *(levelp) = TLS_LEV_INVALID; } while (0) + +/* tls_site_lookup - look up per-site TLS security level */ + +static void tls_site_lookup(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, const char *site_class) +{ + const char *lookup; + + /* + * Look up a non-default policy. In case of multiple lookup results, the + * precedence order is a permutation of the TLS enforcement level order: + * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more + * specific policy including NONE, otherwise we choose the stronger + * enforcement level. + */ + if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) { + if (!strcasecmp(lookup, "NONE")) { + /* NONE overrides MAY or NOTFOUND. */ + if (*site_level <= TLS_LEV_MAY) + *site_level = TLS_LEV_NONE; + } else if (!strcasecmp(lookup, "MAY")) { + /* MAY overrides NOTFOUND but not NONE. */ + if (*site_level < TLS_LEV_NONE) + *site_level = TLS_LEV_MAY; + } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) { + if (*site_level < TLS_LEV_ENCRYPT) + *site_level = TLS_LEV_ENCRYPT; + } else if (!strcasecmp(lookup, "MUST")) { + if (*site_level < TLS_LEV_VERIFY) + *site_level = TLS_LEV_VERIFY; + } else { + msg_warn("%s: unknown TLS policy '%s' for %s %s", + tls_per_site->title, lookup, site_class, site_name); + MARK_INVALID(tls->why, site_level); + return; + } + } else if (tls_per_site->error) { + msg_warn("%s: %s \"%s\": per-site table lookup error", + tls_per_site->title, site_class, site_name); + dsb_simple(tls->why, "4.3.0", "Temporary lookup error"); + *site_level = TLS_LEV_INVALID; + return; + } + return; +} + +/* tls_policy_lookup_one - look up destination TLS policy */ + +static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, + const char *site_class) +{ + const char *lookup; + char *policy; + char *saved_policy; + char *tok; + const char *err; + char *name; + char *val; + static VSTRING *cbuf; + +#undef FREE_RETURN +#define FREE_RETURN do { myfree(saved_policy); return; } while (0) + +#define INVALID_RETURN(why, levelp) do { \ + MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0) + +#define WHERE \ + STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \ + tls_policy->title, site_class, site_name)) + + if (cbuf == 0) + cbuf = vstring_alloc(10); + + if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { + if (tls_policy->error) { + msg_warn("%s: policy table lookup error", WHERE); + MARK_INVALID(tls->why, site_level); + } + return; + } + saved_policy = policy = mystrdup(lookup); + + if ((tok = mystrtok(&policy, CHARS_COMMA_SP)) == 0) { + msg_warn("%s: invalid empty policy", WHERE); + INVALID_RETURN(tls->why, site_level); + } + *site_level = tls_level_lookup(tok); + if (*site_level == TLS_LEV_INVALID) { + /* tls_level_lookup() logs no warning. */ + msg_warn("%s: invalid security level \"%s\"", WHERE, tok); + INVALID_RETURN(tls->why, site_level); + } + + /* + * Warn about ignored attributes when TLS is disabled. + */ + if (*site_level < TLS_LEV_MAY) { + while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0) + msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", + WHERE, tok); + FREE_RETURN; + } + + /* + * Errors in attributes may have security consequences, don't ignore + * errors that can degrade security. + */ + while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0) { + if ((err = split_nameval(tok, &name, &val)) != 0) { + msg_warn("%s: malformed attribute/value pair \"%s\": %s", + WHERE, tok, err); + INVALID_RETURN(tls->why, site_level); + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "ciphers")) { + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (tls->grade) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + tls->grade = mystrdup(val); + continue; + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "protocols")) { + if (tls->protocols) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + tls->protocols = mystrdup(val); + continue; + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "servername")) { + if (tls->sni) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (valid_hostname(val, DONT_GRIPE)) + tls->sni = mystrdup(val); + else { + msg_warn("%s: \"%s=%s\" specifies an invalid hostname", + WHERE, name, val); + INVALID_RETURN(tls->why, site_level); + } + continue; + } + /* Multiple instances per policy. */ + if (!strcasecmp(name, "match")) { + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + switch (*site_level) { + default: + msg_warn("%s: attribute \"%s\" invalid at security level " + "\"%s\"", WHERE, name, policy_name(*site_level)); + INVALID_RETURN(tls->why, site_level); + break; + case TLS_LEV_FPRINT: + if (!tls->dane) + tls->dane = tls_dane_alloc(); + tls_dane_add_ee_digests(tls->dane, + var_smtp_tls_fpt_dgst, val, "|"); + break; + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->matchargv == 0) + tls->matchargv = argv_split(val, ":"); + else + argv_split_append(tls->matchargv, val, ":"); + break; + } + continue; + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "exclude")) { + if (tls->exclusions) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + tls->exclusions = vstring_strcpy(vstring_alloc(10), val); + continue; + } + /* Multiple instances per policy. */ + if (!strcasecmp(name, "tafile")) { + /* Only makes sense if we're using CA-based trust */ + if (!TLS_MUST_PKIX(*site_level)) { + msg_warn("%s: attribute \"%s\" invalid at security level" + " \"%s\"", WHERE, name, policy_name(*site_level)); + INVALID_RETURN(tls->why, site_level); + } + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (!tls->dane) + tls->dane = tls_dane_alloc(); + if (!tls_dane_load_trustfile(tls->dane, val)) { + INVALID_RETURN(tls->why, site_level); + } + continue; + } + /* Last one wins. */ + if (!strcasecmp(name, "connection_reuse")) { + if (strcasecmp(val, "yes") == 0) { + tls->conn_reuse = 1; + } else if (strcasecmp(val, "no") == 0) { + tls->conn_reuse = 0; + } else { + msg_warn("%s: attribute \"%s\" has bad value: \"%s\"", + WHERE, name, val); + INVALID_RETURN(tls->why, site_level); + } + continue; + } + msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + + FREE_RETURN; +} + +/* tls_policy_lookup - look up destination TLS policy */ + +static void tls_policy_lookup(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, + const char *site_class) +{ + + /* + * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These + * are never the domain part of localpart@domain, rather they are + * explicit nexthops from transport:nexthop, and match only the + * corresponding policy. Parent domain matching (below) applies only to + * sub-domains of the recipient domain. + * + * XXX UNIX-domain connections query with the pathname as destination. + */ + if (!valid_utf8_hostname(var_smtputf8_enable, site_name, DONT_GRIPE)) { + tls_policy_lookup_one(tls, site_level, site_name, site_class); + return; + } + do { + tls_policy_lookup_one(tls, site_level, site_name, site_class); + } while (*site_level == TLS_LEV_NOTFOUND + && (site_name = strchr(site_name + 1, '.')) != 0); +} + +/* load_tas - load one or more ta files */ + +static int load_tas(TLS_DANE *dane, const char *files) +{ + int ret = 0; + char *save = mystrdup(files); + char *buf = save; + char *file; + + do { + if ((file = mystrtok(&buf, CHARS_COMMA_SP)) != 0) + ret = tls_dane_load_trustfile(dane, file); + } while (file && ret); + + myfree(save); + return (ret); +} + +/* set_cipher_grade - Set cipher grade and exclusions */ + +static void set_cipher_grade(SMTP_TLS_POLICY *tls) +{ + const char *mand_exclude = ""; + const char *also_exclude = ""; + + /* + * Use main.cf cipher level if no per-destination value specified. With + * mandatory encryption at least encrypt, and with mandatory verification + * at least authenticate! + */ + switch (tls->level) { + case TLS_LEV_INVALID: + case TLS_LEV_NONE: + return; + + case TLS_LEV_MAY: + if (tls->grade == 0) + tls->grade = mystrdup(var_smtp_tls_ciph); + break; + + case TLS_LEV_ENCRYPT: + if (tls->grade == 0) + tls->grade = mystrdup(var_smtp_tls_mand_ciph); + mand_exclude = var_smtp_tls_mand_excl; + also_exclude = "eNULL"; + break; + + case TLS_LEV_HALF_DANE: + case TLS_LEV_DANE: + case TLS_LEV_DANE_ONLY: + case TLS_LEV_FPRINT: + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->grade == 0) + tls->grade = mystrdup(var_smtp_tls_mand_ciph); + mand_exclude = var_smtp_tls_mand_excl; + also_exclude = "aNULL"; + break; + } + +#define ADD_EXCLUDE(vstr, str) \ + do { \ + if (*(str)) \ + vstring_sprintf_append((vstr), "%s%s", \ + VSTRING_LEN(vstr) ? " " : "", (str)); \ + } while (0) + + /* + * The "exclude" policy table attribute overrides main.cf exclusion + * lists. + */ + if (tls->exclusions == 0) { + tls->exclusions = vstring_alloc(10); + ADD_EXCLUDE(tls->exclusions, var_smtp_tls_excl_ciph); + ADD_EXCLUDE(tls->exclusions, mand_exclude); + } + ADD_EXCLUDE(tls->exclusions, also_exclude); +} + +/* policy_create - create SMTP TLS policy cache object (ctable call-back) */ + +static void *policy_create(const char *unused_key, void *context) +{ + SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context; + int site_level; + const char *dest = STR(iter->dest); + const char *host = STR(iter->host); + + /* + * Prepare a pristine policy object. + */ + SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof(*tls)); + + smtp_tls_policy_init(tls, dsb_create()); + tls->conn_reuse = var_smtp_tls_conn_reuse; + + /* + * Compute the per-site TLS enforcement level. For compatibility with the + * original TLS patch, this algorithm is gives equal precedence to host + * and next-hop policies. + */ + tls->level = global_tls_level(); + site_level = TLS_LEV_NOTFOUND; + + if (tls_policy) { + tls_policy_lookup(tls, &site_level, dest, "next-hop destination"); + } else if (tls_per_site) { + tls_site_lookup(tls, &site_level, dest, "next-hop destination"); + if (site_level != TLS_LEV_INVALID + && strcasecmp_utf8(dest, host) != 0) + tls_site_lookup(tls, &site_level, host, "server hostname"); + + /* + * Override a wild-card per-site policy with a more specific global + * policy. + * + * With the original TLS patch, 1) a per-site ENCRYPT could not override + * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy + * produced inconsistent results: it changed a global VERIFY into + * NONE, while producing MAY with all weaker global policy settings. + * + * With the current implementation, a combined per-site (NONE+MAY) + * consistently overrides global policy with NONE, and global policy + * can override only a per-site MAY wildcard. That is, specific + * policies consistently override wildcard policies, and + * (non-wildcard) per-site policies consistently override global + * policies. + */ + if (site_level == TLS_LEV_MAY && tls->level > TLS_LEV_MAY) + site_level = tls->level; + } + switch (site_level) { + default: + tls->level = site_level; + /* FALLTHROUGH */ + case TLS_LEV_NOTFOUND: + break; + case TLS_LEV_INVALID: + tls->level = site_level; + return ((void *) tls); + } + + /* + * DANE initialization may change the security level to something else, + * so do this early, so that we use the right level below. Note that + * "dane-only" changes to "dane" once we obtain the requisite TLSA + * records. + */ + if (TLS_DANE_BASED(tls->level)) + dane_init(tls, iter); + if (tls->level == TLS_LEV_INVALID) + return ((void *) tls); + + /* + * Use main.cf protocols and SNI settings if not set in per-destination + * table. + */ + if (tls->level > TLS_LEV_NONE && tls->protocols == 0) + tls->protocols = + mystrdup((tls->level == TLS_LEV_MAY) ? + var_smtp_tls_proto : var_smtp_tls_mand_proto); + if (tls->level > TLS_LEV_NONE && tls->sni == 0) { + if (!*var_smtp_tls_sni || valid_hostname(var_smtp_tls_sni, DONT_GRIPE)) + tls->sni = mystrdup(var_smtp_tls_sni); + else { + msg_warn("\"%s = %s\" specifies an invalid hostname", + VAR_LMTP_SMTP(TLS_SNI), var_smtp_tls_sni); + MARK_INVALID(tls->why, &tls->level); + return ((void *) tls); + } + } + + /* + * Compute cipher grade (if set in per-destination table, else + * set_cipher() uses main.cf settings) and security level dependent + * cipher exclusion list. + */ + set_cipher_grade(tls); + + /* + * Use main.cf cert_match setting if not set in per-destination table. + */ + switch (tls->level) { + case TLS_LEV_INVALID: + case TLS_LEV_NONE: + case TLS_LEV_MAY: + case TLS_LEV_ENCRYPT: + case TLS_LEV_HALF_DANE: + case TLS_LEV_DANE: + case TLS_LEV_DANE_ONLY: + break; + case TLS_LEV_FPRINT: + if (tls->dane == 0) + tls->dane = tls_dane_alloc(); + if (!TLS_DANE_HASEE(tls->dane)) { + tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst, + var_smtp_tls_fpt_cmatch, CHARS_COMMA_SP); + if (!TLS_DANE_HASEE(tls->dane)) { + msg_warn("nexthop domain %s: configured at fingerprint " + "security level, but with no fingerprints to match.", + dest); + MARK_INVALID(tls->why, &tls->level); + return ((void *) tls); + } + } + break; + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->matchargv == 0) + tls->matchargv = + argv_split(tls->level == TLS_LEV_VERIFY ? + var_smtp_tls_vfy_cmatch : var_smtp_tls_sec_cmatch, + CHARS_COMMA_SP ":"); + if (*var_smtp_tls_tafile) { + if (tls->dane == 0) + tls->dane = tls_dane_alloc(); + if (!TLS_DANE_HASTA(tls->dane) + && !load_tas(tls->dane, var_smtp_tls_tafile)) { + MARK_INVALID(tls->why, &tls->level); + return ((void *) tls); + } + } + break; + default: + msg_panic("unexpected TLS security level: %d", tls->level); + } + + if (msg_verbose && tls->level != global_tls_level()) + msg_info("%s TLS level: %s", "effective", policy_name(tls->level)); + + return ((void *) tls); +} + +/* policy_delete - free no longer cached policy (ctable call-back) */ + +static void policy_delete(void *item, void *unused_context) +{ + SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) item; + + if (tls->protocols) + myfree(tls->protocols); + if (tls->sni) + myfree(tls->sni); + if (tls->grade) + myfree(tls->grade); + if (tls->exclusions) + vstring_free(tls->exclusions); + if (tls->matchargv) + argv_free(tls->matchargv); + if (tls->dane) + tls_dane_free(tls->dane); + dsb_free(tls->why); + + myfree((void *) tls); +} + +/* smtp_tls_policy_cache_query - cached lookup of TLS policy */ + +int smtp_tls_policy_cache_query(DSN_BUF *why, SMTP_TLS_POLICY *tls, + SMTP_ITERATOR *iter) +{ + VSTRING *key; + + /* + * Create an empty TLS Policy cache on the fly. + */ + if (policy_cache == 0) + policy_cache = + ctable_create(CACHE_SIZE, policy_create, policy_delete, (void *) 0); + + /* + * Query the TLS Policy cache, with a search key that reflects our shared + * values that also appear in other cache and table search keys. + */ + key = vstring_alloc(100); + smtp_key_prefix(key, ":", iter, SMTP_KEY_FLAG_CUR_NEXTHOP + | SMTP_KEY_FLAG_HOSTNAME + | SMTP_KEY_FLAG_PORT); + ctable_newcontext(policy_cache, (void *) iter); + *tls = *(SMTP_TLS_POLICY *) ctable_locate(policy_cache, STR(key)); + vstring_free(key); + + /* + * Report errors. Both error and non-error results are cached. We must + * therefore copy the cached DSN buffer content to the caller's buffer. + */ + if (tls->level == TLS_LEV_INVALID) { + /* XXX Simplify this by implementing a "copy" primitive. */ + dsb_update(why, + STR(tls->why->status), STR(tls->why->action), + STR(tls->why->mtype), STR(tls->why->mname), + STR(tls->why->dtype), STR(tls->why->dtext), + "%s", STR(tls->why->reason)); + return (0); + } else { + return (1); + } +} + +/* smtp_tls_policy_cache_flush - flush TLS policy cache */ + +void smtp_tls_policy_cache_flush(void) +{ + if (policy_cache != 0) { + ctable_free(policy_cache); + policy_cache = 0; + } +} + +/* global_tls_level - parse and cache var_smtp_tls_level */ + +static int global_tls_level(void) +{ + static int l = TLS_LEV_NOTFOUND; + + if (l != TLS_LEV_NOTFOUND) + return l; + + /* + * Compute the global TLS policy. This is the default policy level when + * no per-site policy exists. It also is used to override a wild-card + * per-site policy. + * + * We require that the global level is valid on startup. + */ + if (*var_smtp_tls_level) { + if ((l = tls_level_lookup(var_smtp_tls_level)) == TLS_LEV_INVALID) + msg_fatal("invalid tls security level: \"%s\"", var_smtp_tls_level); + } else if (var_smtp_enforce_tls) + l = var_smtp_tls_enforce_peername ? TLS_LEV_VERIFY : TLS_LEV_ENCRYPT; + else + l = var_smtp_use_tls ? TLS_LEV_MAY : TLS_LEV_NONE; + + if (msg_verbose) + msg_info("%s TLS level: %s", "global", policy_name(l)); + + return l; +} + +#define NONDANE_CONFIG 0 /* Administrator's fault */ +#define NONDANE_DEST 1 /* Remote server's fault */ +#define DANE_CANTAUTH 2 /* Remote server's fault */ + +static void PRINTFLIKE(4, 5) dane_incompat(SMTP_TLS_POLICY *tls, + SMTP_ITERATOR *iter, + int errtype, + const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + if (tls->level == TLS_LEV_DANE) { + tls->level = (errtype == DANE_CANTAUTH) ? TLS_LEV_ENCRYPT : TLS_LEV_MAY; + if (errtype == NONDANE_CONFIG) + vmsg_warn(fmt, ap); + else if (msg_verbose) + vmsg_info(fmt, ap); + } else { /* dane-only */ + if (errtype == NONDANE_CONFIG) { + vmsg_warn(fmt, ap); + MARK_INVALID(tls->why, &tls->level); + } else { + tls->level = TLS_LEV_INVALID; + vdsb_simple(tls->why, "4.7.5", fmt, ap); + } + } + va_end(ap); +} + +/* dane_init - special initialization for "dane" security level */ + +static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter) +{ + TLS_DANE *dane; + + if (!iter->port) { + msg_warn("%s: the \"dane\" security level is invalid for delivery via" + " unix-domain sockets", STR(iter->dest)); + MARK_INVALID(tls->why, &tls->level); + return; + } + if (!tls_dane_avail()) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: %s configured, but no requisite library support", + STR(iter->dest), policy_name(tls->level)); + return; + } + if (!(smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) + || smtp_dns_support != SMTP_DNS_DNSSEC) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: %s configured with dnssec lookups disabled", + STR(iter->dest), policy_name(tls->level)); + return; + } + + /* + * If we ignore MX lookup errors, we also ignore DNSSEC security problems + * and thus avoid any reasonable expectation that we get the right DANE + * key material. + */ + if (smtp_mode && var_ign_mx_lookup_err) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: %s configured with MX lookup errors ignored", + STR(iter->dest), policy_name(tls->level)); + return; + } + + /* + * This is not optional, code in tls_dane.c assumes that the nexthop + * qname is already an fqdn. If we're using these flags to go from qname + * to rname, the assumption is invalid. Likewise we cannot add the qname + * to certificate name checks, ... + */ + if (smtp_dns_res_opt & (RES_DEFNAMES | RES_DNSRCH)) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: dns resolver options incompatible with %s TLS", + STR(iter->dest), policy_name(tls->level)); + return; + } + + /* + * When the MX name is present and insecure, DANE may not apply, we then + * either fail if DANE is mandatory or use regular opportunistic TLS if + * the insecure MX level is "may". + */ + if (iter->mx && !iter->mx->dnssec_valid + && (tls->level == TLS_LEV_DANE_ONLY || + smtp_tls_insecure_mx_policy <= TLS_LEV_MAY)) { + dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination"); + return; + } + /* When TLSA lookups fail, we defer the message */ + if ((dane = tls_dane_resolve(iter->port, "tcp", iter->rr, + var_smtp_tls_force_tlsa)) == 0) { + tls->level = TLS_LEV_INVALID; + dsb_simple(tls->why, "4.7.5", "TLSA lookup error for %s:%u", + STR(iter->host), ntohs(iter->port)); + return; + } + if (tls_dane_notfound(dane)) { + dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found"); + tls_dane_free(dane); + return; + } + + /* + * Some TLSA records found, but none usable, per + * + * https://tools.ietf.org/html/draft-ietf-dane-srv-02#section-4 + * + * we MUST use TLS, and SHALL use full PKIX certificate checks. The latter + * would be unwise for SMTP: no human present to "click ok" and risk of + * non-delivery in most cases exceeds risk of interception. + * + * We also have a form of Goedel's incompleteness theorem in play: any list + * of public root CA certs is either incomplete or inconsistent (for any + * given verifier some of the CAs are surely not trustworthy). + */ + if (tls_dane_unusable(dane)) { + dane_incompat(tls, iter, DANE_CANTAUTH, "TLSA records unusable"); + tls_dane_free(dane); + return; + } + + /* + * Perhaps downgrade to "encrypt" if MX is insecure. + */ + if (iter->mx && !iter->mx->dnssec_valid) { + if (smtp_tls_insecure_mx_policy == TLS_LEV_ENCRYPT) { + dane_incompat(tls, iter, DANE_CANTAUTH, + "Verification not possible, MX RRset is insecure"); + tls_dane_free(dane); + return; + } + if (tls->level != TLS_LEV_DANE + || smtp_tls_insecure_mx_policy != TLS_LEV_DANE) + msg_panic("wrong state for insecure MX host DANE policy"); + + /* For correct logging in tls_client_start() */ + tls->level = TLS_LEV_HALF_DANE; + } + + /* + * With DANE trust anchors, peername matching is not configurable. + */ + if (TLS_DANE_HASTA(dane)) { + tls->matchargv = argv_alloc(2); + argv_add(tls->matchargv, dane->base_domain, ARGV_END); + if (iter->mx) { + if (strcmp(iter->mx->qname, iter->mx->rname) == 0) + argv_add(tls->matchargv, iter->mx->qname, ARGV_END); + else + argv_add(tls->matchargv, iter->mx->rname, + iter->mx->qname, ARGV_END); + } + } else if (!TLS_DANE_HASEE(dane)) + msg_panic("empty DANE match list"); + tls->dane = dane; + return; +} + +#endif diff --git a/src/smtp/smtp_trouble.c b/src/smtp/smtp_trouble.c new file mode 100644 index 0000000..1320c24 --- /dev/null +++ b/src/smtp/smtp_trouble.c @@ -0,0 +1,462 @@ +/*++ +/* NAME +/* smtp_trouble 3 +/* SUMMARY +/* error handler policies +/* SYNOPSIS +/* #include "smtp.h" +/* +/* int smtp_sess_fail(state) +/* SMTP_STATE *state; +/* +/* int smtp_site_fail(state, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* +/* int smtp_mesg_fail(state, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* +/* void smtp_rcpt_fail(state, recipient, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* RECIPIENT *recipient; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* +/* int smtp_stream_except(state, exception, description) +/* SMTP_STATE *state; +/* int exception; +/* const char *description; +/* AUXILIARY FUNCTIONS +/* int smtp_misc_fail(state, throttle, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* int throttle; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* DESCRIPTION +/* This module handles all non-fatal errors that can happen while +/* attempting to deliver mail via SMTP, and implements the policy +/* of how to deal with the error. Depending on the nature of +/* the problem, delivery of a single message is deferred, delivery +/* of all messages to the same domain is deferred, or one or more +/* recipients are given up as non-deliverable and a bounce log is +/* updated. In any case, the recipient is marked as either KEEP +/* (try again with a backup host) or DROP (delete recipient from +/* delivery request). +/* +/* In addition, when an unexpected response code is seen such +/* as 3xx where only 4xx or 5xx are expected, or any error code +/* that suggests a syntax error or something similar, the +/* protocol error flag is set so that the postmaster receives +/* a transcript of the session. No notification is generated for +/* what appear to be configuration errors - very likely, they +/* would suffer the same problem and just cause more trouble. +/* +/* In case of a soft error, action depends on whether the error +/* qualifies for trying the request with other mail servers (log +/* an informational record only and try a backup server) or +/* whether this is the final server (log recipient delivery status +/* records and delete the recipient from the request). +/* +/* smtp_sess_fail() takes a pre-formatted error report after +/* failure to complete some protocol handshake. The policy is +/* as with smtp_site_fail(). +/* +/* smtp_site_fail() handles the case where the program fails to +/* complete the initial handshake: the server is not reachable, +/* is not running, does not want talk to us, or we talk to ourselves. +/* The \fIcode\fR gives an error status code; the \fIformat\fR +/* argument gives a textual description. +/* The policy is: soft error, non-final server: log an informational +/* record why the host is being skipped; soft error, final server: +/* defer delivery of all remaining recipients and mark the destination +/* as problematic; hard error: bounce all remaining recipients. +/* The session is marked as "do not cache". +/* The result is non-zero. +/* +/* smtp_mesg_fail() handles the case where the smtp server +/* does not accept the sender address or the message data, +/* or when the local MTA is unable to convert the message data. +/* The policy is: soft error, non-final server: log an informational +/* record why the host is being skipped; soft error, final server: +/* defer delivery of all remaining recipients; hard error: bounce all +/* remaining recipients. +/* The result is non-zero. +/* +/* smtp_misc_fail() provides a more detailed interface than +/* smtp_site_fail() and smtp_mesg_fail(), which are convenience +/* wrappers around smtp_misc_fail(). The throttle argument +/* is either SMTP_THROTTLE or SMTP_NOTHROTTLE; it is used only +/* in the "soft error, final server" policy, and determines +/* whether a destination will be marked as problematic. +/* +/* smtp_rcpt_fail() handles the case where a recipient is not +/* accepted by the server for reasons other than that the server +/* recipient limit is reached. +/* The policy is: soft error, non-final server: log an informational +/* record why the recipient is being skipped; soft error, final server: +/* defer delivery of this recipient; hard error: bounce this +/* recipient. +/* +/* smtp_stream_except() handles the exceptions generated by +/* the smtp_stream(3) module (i.e. timeouts and I/O errors). +/* The \fIexception\fR argument specifies the type of problem. +/* The \fIdescription\fR argument describes at what stage of +/* the SMTP dialog the problem happened. +/* The policy is: non-final server: log an informational record +/* with the reason why the host is being skipped; final server: +/* defer delivery of all remaining recipients. +/* Retry plaintext delivery after TLS post-handshake session +/* failure, provided that at least one recipient was not +/* deferred or rejected during the TLS phase, and that global +/* preconditions for plaintext fallback are met. +/* The session is marked as "do not cache". +/* The result is non-zero. +/* +/* Arguments: +/* .IP state +/* SMTP client state per delivery request. +/* .IP resp +/* Server response including reply code and text. +/* .IP recipient +/* Undeliverable recipient address information. +/* .IP format +/* Human-readable description of why mail is not deliverable. +/* DIAGNOSTICS +/* Panic: unknown exception code. +/* SEE ALSO +/* smtp_proto(3) smtp high-level protocol +/* smtp_stream(3) smtp low-level protocol +/* defer(3) basic message defer interface +/* bounce(3) basic message bounce interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <stringops.h> + +/* Global library. */ + +#include <smtp_stream.h> +#include <deliver_request.h> +#include <deliver_completed.h> +#include <bounce.h> +#include <defer.h> +#include <mail_error.h> +#include <dsn_buf.h> +#include <dsn.h> +#include <mail_params.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + +/* smtp_check_code - check response code */ + +static void smtp_check_code(SMTP_SESSION *session, int code) +{ + + /* + * The intention of this code is to alert the postmaster when the local + * Postfix SMTP client screws up, protocol wise. RFC 821 says that x0z + * replies "refer to syntax errors, syntactically correct commands that + * don't fit any functional category, and unimplemented or superfluous + * commands". Unfortunately, this also triggers postmaster notices when + * remote servers screw up, protocol wise. This is becoming a common + * problem now that response codes are configured manually as part of + * anti-UCE systems, by people who aren't aware of RFC details. + */ + if (code < 400 || code > 599 + || code == 555 /* RFC 1869, section 6.1. */ + || (code >= 500 && code < 510)) + session->error_mask |= MAIL_ERROR_PROTOCOL; +} + +/* smtp_bulk_fail - skip, defer or bounce recipients, maybe throttle queue */ + +static int smtp_bulk_fail(SMTP_STATE *state, int throttle_queue) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + RECIPIENT *rcpt; + int status; + int aggregate_status; + int soft_error = (STR(why->status)[0] == '4'); + int soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce); + int nrcpt; + + /* + * Don't defer the recipients just yet when this error qualifies them for + * delivery to a backup server. Just log something informative to show + * why we're skipping this host. + */ + if ((soft_error || soft_bounce_error) + && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) { + msg_info("%s: %s", request->queue_id, STR(why->reason)); + for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (SMTP_RCPT_ISMARKED(rcpt)) + continue; + SMTP_RCPT_KEEP(state, rcpt); + } + } + + /* + * Defer or bounce all the remaining recipients, and delete them from the + * delivery request. If a bounce fails, defer instead and do not qualify + * the recipient for delivery to a backup server. + */ + else { + + /* + * If we are still in the connection set-up phase, update the set-up + * completion time here, otherwise the time spent in set-up latency + * will be attributed as message transfer latency. + * + * All remaining recipients have failed at this point, so we update the + * delivery completion time stamp so that multiple recipient status + * records show the same delay values. + */ + if (request->msg_stats.conn_setup_done.tv_sec == 0) { + GETTIMEOFDAY(&request->msg_stats.conn_setup_done); + request->msg_stats.deliver_done = + request->msg_stats.conn_setup_done; + } else + GETTIMEOFDAY(&request->msg_stats.deliver_done); + + (void) DSN_FROM_DSN_BUF(why); + aggregate_status = 0; + for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (SMTP_RCPT_ISMARKED(rcpt)) + continue; + status = (soft_error ? defer_append : bounce_append) + (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, + &request->msg_stats, rcpt, + session ? session->namaddrport : "none", &why->dsn); + if (status == 0) + deliver_completed(state->src, rcpt->offset); + SMTP_RCPT_DROP(state, rcpt); + aggregate_status |= status; + } + state->status |= aggregate_status; + if ((state->misc_flags & SMTP_MISC_FLAG_COMPLETE_SESSION) == 0 + && throttle_queue && aggregate_status + && request->hop_status == 0) + request->hop_status = DSN_COPY(&why->dsn); + } + + /* + * Don't cache this session. We can't talk to this server. + */ + if (throttle_queue && session) + DONT_CACHE_THROTTLED_SESSION; + + return (-1); +} + +/* smtp_sess_fail - skip site, defer or bounce all recipients */ + +int smtp_sess_fail(SMTP_STATE *state) +{ + + /* + * We can't avoid copying copying lots of strings into VSTRING buffers, + * because this error information is collected by a routine that + * terminates BEFORE the error is reported. + */ + return (smtp_bulk_fail(state, SMTP_THROTTLE)); +} + +/* vsmtp_fill_dsn - fill in temporary DSN structure */ + +static void vsmtp_fill_dsn(SMTP_STATE *state, const char *mta_name, + const char *status, const char *reply, + const char *format, va_list ap) +{ + DSN_BUF *why = state->why; + + /* + * We could avoid copying lots of strings into VSTRING buffers, because + * this error information is given to us by a routine that terminates + * AFTER the error is reported. However, this results in ugly kludges + * when informal text needs to be formatted. So we maintain consistency + * with other error reporting in the SMTP client even if we waste a few + * cycles. + */ + VSTRING_RESET(why->reason); + if (mta_name && status && status[0] != '4' && status[0] != '5') { + vstring_strcpy(why->reason, "Protocol error: "); + status = "5.5.0"; + } + vstring_vsprintf_append(why->reason, format, ap); + dsb_formal(why, status, DSB_DEF_ACTION, + mta_name ? DSB_MTYPE_DNS : DSB_MTYPE_NONE, mta_name, + reply ? DSB_DTYPE_SMTP : DSB_DTYPE_NONE, reply); +} + +/* smtp_misc_fail - maybe throttle queue; skip/defer/bounce all recipients */ + +int smtp_misc_fail(SMTP_STATE *state, int throttle, const char *mta_name, + SMTP_RESP *resp, const char *format,...) +{ + va_list ap; + + /* + * Initialize. + */ + va_start(ap, format); + vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap); + va_end(ap); + + if (state->session && mta_name) + smtp_check_code(state->session, resp->code); + + /* + * Skip, defer or bounce recipients, and throttle this queue. + */ + return (smtp_bulk_fail(state, throttle)); +} + +/* smtp_rcpt_fail - skip, defer, or bounce recipient */ + +void smtp_rcpt_fail(SMTP_STATE *state, RECIPIENT *rcpt, const char *mta_name, + SMTP_RESP *resp, const char *format,...) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + int status; + int soft_error; + int soft_bounce_error; + va_list ap; + + /* + * Sanity check. + */ + if (SMTP_RCPT_ISMARKED(rcpt)) + msg_panic("smtp_rcpt_fail: recipient <%s> is marked", rcpt->address); + + /* + * Initialize. + */ + va_start(ap, format); + vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap); + va_end(ap); + soft_error = STR(why->status)[0] == '4'; + soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce); + + if (state->session && mta_name) + smtp_check_code(state->session, resp->code); + + /* + * Don't defer this recipient record just yet when this error qualifies + * for trying other mail servers. Just log something informative to show + * why we're skipping this recipient now. + */ + if ((soft_error || soft_bounce_error) + && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) { + msg_info("%s: %s", request->queue_id, STR(why->reason)); + SMTP_RCPT_KEEP(state, rcpt); + } + + /* + * Defer or bounce this recipient, and delete from the delivery request. + * If the bounce fails, defer instead and do not qualify the recipient + * for delivery to a backup server. + * + * Note: we may still make an SMTP connection to deliver other recipients + * that did qualify for delivery to a backup server. + */ + else { + (void) DSN_FROM_DSN_BUF(state->why); + status = (soft_error ? defer_append : bounce_append) + (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, + &request->msg_stats, rcpt, + session ? session->namaddrport : "none", &why->dsn); + if (status == 0) + deliver_completed(state->src, rcpt->offset); + SMTP_RCPT_DROP(state, rcpt); + state->status |= status; + } +} + +/* smtp_stream_except - defer domain after I/O problem */ + +int smtp_stream_except(SMTP_STATE *state, int code, const char *description) +{ + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + + /* + * Sanity check. + */ + if (session == 0) + msg_panic("smtp_stream_except: no session"); + + /* + * Initialize. + */ + switch (code) { + default: + msg_panic("smtp_stream_except: unknown exception %d", code); + case SMTP_ERR_EOF: + dsb_simple(why, "4.4.2", "lost connection with %s while %s", + session->namaddr, description); +#ifdef USE_TLS + if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE) + RETRY_AS_PLAINTEXT; +#endif + break; + case SMTP_ERR_TIME: + dsb_simple(why, "4.4.2", "conversation with %s timed out while %s", + session->namaddr, description); +#ifdef USE_TLS + if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE) + RETRY_AS_PLAINTEXT; +#endif + break; + case SMTP_ERR_DATA: + session->error_mask |= MAIL_ERROR_DATA; + dsb_simple(why, "4.3.0", "local data error while talking to %s", + session->namaddr); + } + + /* + * The smtp_bulk_fail() call below will not throttle the destination when + * falling back to plaintext, because RETRY_AS_PLAINTEXT clears the + * FINAL_SERVER flag. + */ + return (smtp_bulk_fail(state, SMTP_THROTTLE)); +} diff --git a/src/smtp/smtp_unalias.c b/src/smtp/smtp_unalias.c new file mode 100644 index 0000000..1c4d34d --- /dev/null +++ b/src/smtp/smtp_unalias.c @@ -0,0 +1,142 @@ +/*++ +/* NAME +/* smtp_unalias 3 +/* SUMMARY +/* replace host/domain alias by official name +/* SYNOPSIS +/* #include "smtp.h" +/* +/* const char *smtp_unalias_name(name) +/* const char *name; +/* +/* VSTRING *smtp_unalias_addr(result, addr) +/* VSTRING *result; +/* const char *addr; +/* DESCRIPTION +/* smtp_unalias_name() looks up A or MX records for the specified +/* name and returns the official name provided in the reply. When +/* no A or MX record is found, the result is a copy of the input. +/* In order to improve performance, smtp_unalias_name() remembers +/* results in a private cache, so a name is looked up only once. +/* +/* smtp_unalias_addr() returns the input address, which is in +/* RFC 821 quoted form, after replacing the domain part by the +/* official name as found by smtp_unalias_name(). When the input +/* contains no domain part, the result is a copy of the input. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdlib.h> +#include <netdb.h> +#include <string.h> + +/* Utility library. */ + +#include <htable.h> +#include <vstring.h> +#include <msg.h> + +/* DNS library. */ + +#include <dns.h> + +/* Application-specific. */ + +#include "smtp.h" + +static int smtp_unalias_flags; + +/* smtp_unalias_name - look up the official host or domain name. */ + +const char *smtp_unalias_name(const char *name) +{ + static HTABLE *cache; + VSTRING *fqdn; + char *result; + + if (*name == '[') + return (name); + + /* + * Initialize the cache on the fly. The smtp client is designed to exit + * after servicing a limited number of requests, so there is no need to + * prevent the cache from growing too large, or to expire old entries. + */ + if (cache == 0) + cache = htable_create(10); + + /* + * Look up the fqdn. If none is found use the query name instead, so that + * we won't lose time looking up the same bad name again. + */ + if ((result = htable_find(cache, name)) == 0) { + fqdn = vstring_alloc(10); + if (dns_lookup_l(name, smtp_unalias_flags, (DNS_RR **) 0, fqdn, + (VSTRING *) 0, DNS_REQ_FLAG_NONE, T_MX, T_A, +#ifdef HAS_IPV6 + T_AAAA, +#endif + 0) != DNS_OK) + vstring_strcpy(fqdn, name); + htable_enter(cache, name, result = vstring_export(fqdn)); + } + return (result); +} + +/* smtp_unalias_addr - rewrite aliases in domain part of address */ + +VSTRING *smtp_unalias_addr(VSTRING *result, const char *addr) +{ + char *at; + const char *fqdn; + + if ((at = strrchr(addr, '@')) == 0 || at[1] == 0) { + vstring_strcpy(result, addr); + } else { + fqdn = smtp_unalias_name(at + 1); + vstring_strncpy(result, addr, at - addr + 1); + vstring_strcat(result, fqdn); + } + return (result); +} + +#ifdef TEST + + /* + * Test program - read address from stdin, print result on stdout. + */ + +#include <vstring_vstream.h> + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *addr = vstring_alloc(10); + VSTRING *result = vstring_alloc(10); + + smtp_unalias_flags |= RES_DEBUG; + + while (vstring_fgets_nonl(addr, VSTREAM_IN)) { + smtp_unalias_addr(result, vstring_str(addr)); + vstream_printf("%s -> %s\n", vstring_str(addr), vstring_str(result)); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(addr); + vstring_free(result); + return (0); +} + +#endif diff --git a/src/smtp/tls_policy.in b/src/smtp/tls_policy.in new file mode 100644 index 0000000..95c295b --- /dev/null +++ b/src/smtp/tls_policy.in @@ -0,0 +1,64 @@ +- - - +- - may +- - must_nopeermatch +- - must +- none - +- none may +- none must_nopeermatch +- none must +- may - +- may may +- may must_nopeermatch +- may must +- must_nopeermatch - +- must_nopeermatch may +- must_nopeermatch must_nopeermatch +- must_nopeermatch must +- must - +- must may +- must must_nopeermatch +- must must + +none none - +none none may +none none must_nopeermatch +none none must +none may - +none may may +none may must_nopeermatch +none may must +none must_nopeermatch - +none must_nopeermatch may +none must_nopeermatch must_nopeermatch +none must_nopeermatch must +none must - +none must may +none must must_nopeermatch +none must must + +may may - +may may may +may may must_nopeermatch +may may must +may must_nopeermatch - +may must_nopeermatch may +may must_nopeermatch must_nopeermatch +may must_nopeermatch must +may must - +may must may +may must must_nopeermatch +may must must + +must_nopeermatch must_nopeermatch - +must_nopeermatch must_nopeermatch may +must_nopeermatch must_nopeermatch must_nopeermatch +must_nopeermatch must_nopeermatch must +must_nopeermatch must - +must_nopeermatch must may +must_nopeermatch must must_nopeermatch +must_nopeermatch must must + +must must - +must must may +must must must_nopeermatch +must must must diff --git a/src/smtp/tls_policy.ref b/src/smtp/tls_policy.ref new file mode 100644 index 0000000..5b9f187 --- /dev/null +++ b/src/smtp/tls_policy.ref @@ -0,0 +1,65 @@ +host dest global result +- - - none +- - may may +- - must_nopeermatch must_nopeermatch +- - must must +- none - none +- none may none +- none must_nopeermatch none +- none must none +- may - may +- may may may +- may must_nopeermatch must_nopeermatch +- may must must +- must_nopeermatch - must_nopeermatch +- must_nopeermatch may must_nopeermatch +- must_nopeermatch must_nopeermatch must_nopeermatch +- must_nopeermatch must must_nopeermatch +- must - must +- must may must +- must must_nopeermatch must +- must must must + +none none - none +none none may none +none none must_nopeermatch none +none none must none +none may - none +none may may none +none may must_nopeermatch none +none may must none +none must_nopeermatch - must_nopeermatch +none must_nopeermatch may must_nopeermatch +none must_nopeermatch must_nopeermatch must_nopeermatch +none must_nopeermatch must must_nopeermatch +none must - must +none must may must +none must must_nopeermatch must +none must must must + +may may - may +may may may may +may may must_nopeermatch must_nopeermatch +may may must must +may must_nopeermatch - must_nopeermatch +may must_nopeermatch may must_nopeermatch +may must_nopeermatch must_nopeermatch must_nopeermatch +may must_nopeermatch must must_nopeermatch +may must - must +may must may must +may must must_nopeermatch must +may must must must + +must_nopeermatch must_nopeermatch - must_nopeermatch +must_nopeermatch must_nopeermatch may must_nopeermatch +must_nopeermatch must_nopeermatch must_nopeermatch must_nopeermatch +must_nopeermatch must_nopeermatch must must_nopeermatch +must_nopeermatch must - must +must_nopeermatch must may must +must_nopeermatch must must_nopeermatch must +must_nopeermatch must must must + +must must - must +must must may must +must must must_nopeermatch must +must must must must 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); +} diff --git a/src/smtpstone/.indent.pro b/src/smtpstone/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/smtpstone/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/smtpstone/.printfck b/src/smtpstone/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/smtpstone/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/smtpstone/Makefile.in b/src/smtpstone/Makefile.in new file mode 100644 index 0000000..f86e019 --- /dev/null +++ b/src/smtpstone/Makefile.in @@ -0,0 +1,172 @@ +SHELL = /bin/sh +SRCS = smtp-source.c smtp-sink.c qmqp-source.c qmqp-sink.c +OBJS = smtp-source.o smtp-sink.o qmqp-source.o qmqp-sink.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +INC_DIR = ../../include +PROG = smtp-source smtp-sink qmqp-source qmqp-sink +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(PROG) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +smtp-sink: smtp-sink.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-sink.o $(LIBS) $(SYSLIBS) + +smtp-source: smtp-source.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-source.o $(LIBS) $(SYSLIBS) + +qmqp-sink: qmqp-sink.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-sink.o $(LIBS) $(SYSLIBS) + +qmqp-source: qmqp-source.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-source.o $(LIBS) $(SYSLIBS) + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/smtp-source ../../bin/smtp-sink ../../bin/qmqp-source \ + ../../bin/qmqp-sink + +../../bin/smtp-source: smtp-source + cp $? $@ + +../../bin/smtp-sink: smtp-sink + cp $? $@ + +../../bin/qmqp-source: qmqp-source + cp $? $@ + +../../bin/qmqp-sink: qmqp-sink + cp $? $@ + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +qmqp-sink.o: ../../include/check_arg.h +qmqp-sink.o: ../../include/events.h +qmqp-sink.o: ../../include/htable.h +qmqp-sink.o: ../../include/inet_proto.h +qmqp-sink.o: ../../include/iostuff.h +qmqp-sink.o: ../../include/listen.h +qmqp-sink.o: ../../include/mail_version.h +qmqp-sink.o: ../../include/msg.h +qmqp-sink.o: ../../include/msg_vstream.h +qmqp-sink.o: ../../include/mymalloc.h +qmqp-sink.o: ../../include/netstring.h +qmqp-sink.o: ../../include/qmqp_proto.h +qmqp-sink.o: ../../include/sys_defs.h +qmqp-sink.o: ../../include/vbuf.h +qmqp-sink.o: ../../include/vstream.h +qmqp-sink.o: ../../include/vstring.h +qmqp-sink.o: qmqp-sink.c +qmqp-source.o: ../../include/check_arg.h +qmqp-source.o: ../../include/connect.h +qmqp-source.o: ../../include/events.h +qmqp-source.o: ../../include/get_hostname.h +qmqp-source.o: ../../include/host_port.h +qmqp-source.o: ../../include/inet_proto.h +qmqp-source.o: ../../include/iostuff.h +qmqp-source.o: ../../include/mail_date.h +qmqp-source.o: ../../include/mail_version.h +qmqp-source.o: ../../include/msg.h +qmqp-source.o: ../../include/msg_vstream.h +qmqp-source.o: ../../include/myaddrinfo.h +qmqp-source.o: ../../include/mymalloc.h +qmqp-source.o: ../../include/netstring.h +qmqp-source.o: ../../include/qmqp_proto.h +qmqp-source.o: ../../include/sane_connect.h +qmqp-source.o: ../../include/split_at.h +qmqp-source.o: ../../include/sys_defs.h +qmqp-source.o: ../../include/valid_hostname.h +qmqp-source.o: ../../include/valid_mailhost_addr.h +qmqp-source.o: ../../include/vbuf.h +qmqp-source.o: ../../include/vstream.h +qmqp-source.o: ../../include/vstring.h +qmqp-source.o: qmqp-source.c +smtp-sink.o: ../../include/check_arg.h +smtp-sink.o: ../../include/chroot_uid.h +smtp-sink.o: ../../include/events.h +smtp-sink.o: ../../include/get_hostname.h +smtp-sink.o: ../../include/htable.h +smtp-sink.o: ../../include/inet_proto.h +smtp-sink.o: ../../include/iostuff.h +smtp-sink.o: ../../include/listen.h +smtp-sink.o: ../../include/mail_date.h +smtp-sink.o: ../../include/mail_version.h +smtp-sink.o: ../../include/make_dirs.h +smtp-sink.o: ../../include/msg.h +smtp-sink.o: ../../include/msg_vstream.h +smtp-sink.o: ../../include/myaddrinfo.h +smtp-sink.o: ../../include/mymalloc.h +smtp-sink.o: ../../include/myrand.h +smtp-sink.o: ../../include/sane_accept.h +smtp-sink.o: ../../include/smtp_stream.h +smtp-sink.o: ../../include/stringops.h +smtp-sink.o: ../../include/sys_defs.h +smtp-sink.o: ../../include/vbuf.h +smtp-sink.o: ../../include/vstream.h +smtp-sink.o: ../../include/vstring.h +smtp-sink.o: ../../include/vstring_vstream.h +smtp-sink.o: smtp-sink.c +smtp-source.o: ../../include/check_arg.h +smtp-source.o: ../../include/compat_va_copy.h +smtp-source.o: ../../include/connect.h +smtp-source.o: ../../include/events.h +smtp-source.o: ../../include/get_hostname.h +smtp-source.o: ../../include/host_port.h +smtp-source.o: ../../include/inet_proto.h +smtp-source.o: ../../include/iostuff.h +smtp-source.o: ../../include/mail_date.h +smtp-source.o: ../../include/mail_version.h +smtp-source.o: ../../include/msg.h +smtp-source.o: ../../include/msg_vstream.h +smtp-source.o: ../../include/myaddrinfo.h +smtp-source.o: ../../include/mymalloc.h +smtp-source.o: ../../include/sane_connect.h +smtp-source.o: ../../include/smtp_stream.h +smtp-source.o: ../../include/split_at.h +smtp-source.o: ../../include/sys_defs.h +smtp-source.o: ../../include/valid_hostname.h +smtp-source.o: ../../include/valid_mailhost_addr.h +smtp-source.o: ../../include/vbuf.h +smtp-source.o: ../../include/vstream.h +smtp-source.o: ../../include/vstring.h +smtp-source.o: ../../include/vstring_vstream.h +smtp-source.o: smtp-source.c diff --git a/src/smtpstone/hashed-deferred b/src/smtpstone/hashed-deferred new file mode 100644 index 0000000..93f0e12 --- /dev/null +++ b/src/smtpstone/hashed-deferred @@ -0,0 +1,37 @@ +Delivering 1000 deferred messages over the loopback transport, +outbound concurrency 10. smtp-sink pipelining disabled. Machine is +P230, BSD/OS 3.1, 64MB memory. + +hashing is 16 directories per level + +flat deferred queue + + start: Sun Feb 21 16:42:37 EST 1999 + done: Feb 21 16:44:35 + time: 1:58 = 118 seconds + + start: Sun Feb 21 16:48:01 EST 1999 + done: Feb 21 16:49:51 + time: 1:50 = 110 seconds + +hashed deferred queue, depth=1 (16 directories) + + start: Sun Feb 21 17:29:36 EST 1999 + done: Feb 21 17:31:32 + time: 1:56 = 116 seconds + + start: Sun Feb 21 17:33:36 EST 1999 + done: Feb 21 17:35:24 + time: 1:48 = 108 seconds + + start: Sun Feb 21 17:37:08 EST 1999 + done: Feb 21 17:39:02 + time: 1:52 = 112 seconds + +Hashing does not slow down deliveries. + +However the problem is scanning an empty deferred queue. On an idle +machine, it takes some 5 seconds to scan an empty depth=2 deferred +queue unless the blocks happen to be cached. During those 5 seconds +the queue manager will not pay attention to I/O from delivery +agents, which is bad. diff --git a/src/smtpstone/hashed-incoming b/src/smtpstone/hashed-incoming new file mode 100644 index 0000000..7bb1993 --- /dev/null +++ b/src/smtpstone/hashed-incoming @@ -0,0 +1,48 @@ +Sending 1000 messages through postfix over the loopback transport, +inbound concurrency 10. smtp-sink pipelining disabled. Machine is +P230, BSD/OS 3.1 + +accepted = time for postfix to accept all messages +moved = number of messages delivered by postfix when all mail accepted +done = time for postfix to deliver last message + +hashing is 16 directories per level + +flat incoming queue + + start: Sun Feb 21 15:12:44 EST 1999 + accepted: 66.10 real 0.50 user 1.56 sys + moved: 122 + done: Feb 21 15:15:19 + + total time: 2:35 = 155 seconds + + start: Sun Feb 21 15:30:44 EST 1999 + accepted: 67.47 real 0.48 user 1.60 sys + moved: + done: Feb 21 15:33:10 + + total time: 2:26 = 146 seconds + +hashed incoming queue, depth=1 (16 subdirs) + + start: Sun Feb 21 15:39:43 EST 1999 + accepted: 84.42 real 0.49 user 1.66 sys + moved: 144 + done: Feb 21 15:42:27 + + total time: 2:44 = 164 seconds + + start: Sun Feb 21 15:42:43 EST 1999 + accepted: 84.57 real 0.46 user 1.67 sys + moved: + done: Feb 21 15:45:34 + + total time: 2:51 = 171 seconds + + start: Sun Feb 21 15:47:11 EST 1999 + accepted: 84.11 real 0.34 user 1.72 sys + moved: + done: Feb 21 15:49:54 + + total time: 2:43 = 163 seconds diff --git a/src/smtpstone/mx-deliver b/src/smtpstone/mx-deliver new file mode 100644 index 0000000..a4f44a0 --- /dev/null +++ b/src/smtpstone/mx-deliver @@ -0,0 +1,20 @@ +MX needs 24 seconds to deliver 100 SMTP messages to one local user. +smtpd/local-delivery process limit = 100 + +/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@fist.porcupine.org fist + 10.47 real 0.12 user 0.16 sys +Jun 8 14:45:25 fist mx:smtpd[19432]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:45:49 fist mx:local[19444]: 085788: to=<wietse@porcupine.org>, relay=l +Total time: 24 seconds + +/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse@fist.porcupine.org fist + 9.10 real 0.06 user 0.26 sys +Jun 8 14:46:42 fist mx:smtpd[19443]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:47:06 fist mx:local[19446]: 085792: to=<wietse@porcupine.org>, relay=l +Total time: 24 seconds + +/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@fist.porcupine.org fist + 9.84 real 0.09 user 0.28 sys +Jun 8 14:48:03 fist mx:smtpd[19458]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:48:28 fist mx:local[19479]: 085795: to=<wietse@porcupine.org>, relay=l +Total time: 25 seconds diff --git a/src/smtpstone/mx-explode b/src/smtpstone/mx-explode new file mode 100644 index 0000000..e997147 --- /dev/null +++ b/src/smtpstone/mx-explode @@ -0,0 +1,33 @@ +MX needs 12 seconds per 1000 different remote destinations. +smtp process limit = 100, bundle_recipients = 0. + +/usr/bin/time ./smtp-source -r 1000 fist + 1.13 real 0.07 user 0.27 sys +Jun 8 13:32:18 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1 +Jun 8 13:32:31 fist mx:smtp[18209]: 085688: to=<544foo@spike.porcupine.org>, re +Total time: 13 seconds + +/usr/bin/time ./smtp-source -r 2000 fist + 2.55 real 0.21 user 0.48 sys +Jun 8 13:33:23 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1 +Jun 8 13:33:48 fist mx:smtp[18184]: 085693: to=<1041foo@spike.porcupine.org>, r +Total time: 25 seconds + +/usr/bin/time ./smtp-source -r 5000 fist +[test generating machine ran out of resources while receiving mail] + +/usr/bin/time ./smtp-source -r 1000 fist + 1.38 real 0.17 user 0.16 sys +Jun 8 15:20:33 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1 +Jun 8 15:20:46 fist mx:smtp[27724]: 085687: to=<493foo@spike.porcupine.org>, re +Total time: 13 seconds + +/usr/bin/time ./smtp-source -r 2000 fist + 2.64 real 0.23 user 0.46 sys +Jun 8 15:20:52 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1 +Jun 8 15:21:16 fist mx:smtp[27743]: 085687: to=<1086foo@spike.porcupine.org>, r +Total time: 24 seconds + +/usr/bin/time ./smtp-source -r 5000 fist +[test generating machine ran out of resources while receiving mail] + diff --git a/src/smtpstone/mx-relay b/src/smtpstone/mx-relay new file mode 100644 index 0000000..a5930cb --- /dev/null +++ b/src/smtpstone/mx-relay @@ -0,0 +1,20 @@ +MX needs 19 seconds to relay 100 messages with one recipient. +smtp/smtpd process limit = 100, bundle_recipients = 0. + +/usr/bin/time ./smtp-source -s 5 -m 100 fist + 9.56 real 0.07 user 0.20 sys +Jun 8 14:33:19 fist mx:smtpd[19366]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:33:36 fist mx:smtp[19382]: 085781: to=<foo@spike.porcupine.org>, relay +Total time: 17 seconds + +/usr/bin/time ./smtp-source -s 10 -m 100 fist + 8.95 real 0.12 user 0.19 sys +Jun 8 14:34:22 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:34:41 fist mx:smtp[19378]: 085792: to=<foo@spike.porcupine.org>, relay +Total time: 19 seconds + +/usr/bin/time ./smtp-source -s 20 -m 100 fist + 9.79 real 0.11 user 0.27 sys +Jun 8 14:35:18 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:35:38 fist mx:smtp[19382]: 085794: to=<foo@spike.porcupine.org>, relay +Total time: 20 seconds diff --git a/src/smtpstone/performance b/src/smtpstone/performance new file mode 100644 index 0000000..805676f --- /dev/null +++ b/src/smtpstone/performance @@ -0,0 +1,28 @@ +List performance: time to forward one SMTP message with 1000, 2000 +and 5000 different remote destinations. Outbound SMTP concurrency += 100. + +dests 1000 2000 5000 time per 1000 +============================================= +qmail 15 32 80 16 +mx 13 25 (*) 13 + +(*) message sink host saturated under the load + +Local delivery performance: time to deliver 100 SMTP messages to +one recipient. Outbound SMTP concurrency = 100, inbound SMTP +concurrency = 5, 10, 20. + +concur 5 10 20 average time +============================================ +qmail 62 59 58 60 +mx 24 24 25 24 + +Relay performance: time to forward 100 SMTP messages with one +recipient. Outbound SMTP concurrency = 100, inbound SMTP concurrency += 5, 10, 20. + +concur 5 10 20 average time +============================================ +qmail 56 54 54 55 +mx 17 19 20 19 diff --git a/src/smtpstone/qmail-deliver b/src/smtpstone/qmail-deliver new file mode 100644 index 0000000..e00ab39 --- /dev/null +++ b/src/smtpstone/qmail-deliver @@ -0,0 +1,20 @@ +Qmail needs 59 seconds to deliver 100 SMTP messages to one local recipient. +Default configuration, concurrencyremote = 100. + +/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse fist + 41.45 real 0.07 user 0.21 sys +Jun 8 12:17:20 fist qmail: 865786640.494072 new msg 39901 +Jun 8 12:18:22 fist qmail: 865786702.301087 end msg 39982 +Total time: 62 sec + +/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse fist + 26.50 real 0.10 user 0.22 sys +Jun 8 12:14:49 fist qmail: 865786489.089492 new msg 39901 +Jun 8 12:15:48 fist qmail: 865786548.316898 end msg 39928 +Total time: 59 sec + +/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse fist + 21.15 real 0.08 user 0.30 sys +Jun 8 12:19:18 fist qmail: 865786758.939755 new msg 39903 +Jun 8 12:20:16 fist qmail: 865786816.739912 end msg 40031 +Total time: 58 sec diff --git a/src/smtpstone/qmail-explode b/src/smtpstone/qmail-explode new file mode 100644 index 0000000..ed40e2c --- /dev/null +++ b/src/smtpstone/qmail-explode @@ -0,0 +1,20 @@ +Qmail needs 16 seconds per 1000 destinations. +Default configuration, concurrencyremote = 100. + +/usr/bin/time ./smtp-source -r 1000 fist + 1.16 real 0.09 user 0.24 sys +Jun 8 14:57:17 fist qmail: 865796237.334359 new msg 39906 +Jun 8 14:57:32 fist qmail: 865796252.632756 delivery 2154: success: 168.100.189 +Total time: 15 seconds + +/usr/bin/time ./smtp-source -r 2000 fist + 1.99 real 0.23 user 0.45 sys +Jun 8 14:58:11 fist qmail: 865796291.817523 new msg 39907 +Jun 8 14:58:43 fist qmail: 865796323.174117 delivery 4116: success: 168.100.189 +Total time: 32 seconds + +/usr/bin/time ./smtp-source -r 5000 fist + 4.63 real 0.58 user 1.10 sys +Jun 8 14:59:23 fist qmail: 865796363.346735 new msg 39908 +Jun 8 15:00:43 fist qmail: 865796443.209168 delivery 9153: success: 168.100.189 +Total time: 80 seconds diff --git a/src/smtpstone/qmail-relay b/src/smtpstone/qmail-relay new file mode 100644 index 0000000..8718e5e --- /dev/null +++ b/src/smtpstone/qmail-relay @@ -0,0 +1,20 @@ +Qmail needs 54 seconds to relay 100 messages with one recipient. +Default configuration, concurrencyremote = 100. + +spike_4% /usr/bin/time ./smtp-source -s 5 -m 100 fist + 43.77 real 0.05 user 0.23 sys +Jun 8 12:08:36 fist qmail: 865786116.744366 new msg 39901 +Jun 8 12:09:32 fist qmail: 865786172.791473 end msg 39921 +Total time: 56 sec + +/usr/bin/time ./smtp-source -s 10 -m 100 fist + 26.66 real 0.06 user 0.26 sys +Jun 8 12:06:20 fist qmail: 865785980.185885 new msg 39901 +Jun 8 12:07:14 fist qmail: 865786034.306429 end msg 39920 +Total time: 54 sec + +spike_8% /usr/bin/time ./smtp-source -s 20 -m 100 fist + 20.94 real 0.11 user 0.27 sys +Jun 8 12:10:52 fist qmail: 865786252.412648 new msg 39901 +Jun 8 12:11:46 fist qmail: 865786306.080605 end msg 39962 +Total time: 54 sec diff --git a/src/smtpstone/qmqp-sink.c b/src/smtpstone/qmqp-sink.c new file mode 100644 index 0000000..2fcbdd1 --- /dev/null +++ b/src/smtpstone/qmqp-sink.c @@ -0,0 +1,325 @@ +/*++ +/* NAME +/* qmqp-sink 1 +/* SUMMARY +/* parallelized QMQP test server +/* SYNOPSIS +/* .fi +/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR] +/* [\fBinet:\fR][\fIhost\fR]:\fIport\fR \fIbacklog\fR +/* +/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR] +/* \fBunix:\fR\fIpathname\fR \fIbacklog\fR +/* DESCRIPTION +/* \fBqmqp-sink\fR listens on the named host (or address) and port. +/* It receives messages from the network and throws them away. +/* The purpose is to measure QMQP client performance, not protocol +/* compliance. +/* Connections can be accepted on IPv4 or IPv6 endpoints, or on +/* UNIX-domain sockets. +/* IPv4 and IPv6 are the default. +/* This program is the complement of the \fBqmqp-source\fR(1) program. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Support IPv4 only. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Support IPv6 only. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP \fB-c\fR +/* Display a running counter that is updated whenever a delivery +/* is completed. +/* .IP \fB-v\fR +/* Increase verbosity. Specify \fB-v -v\fR to see some of the QMQP +/* conversation. +/* .IP "\fB-x \fItime\fR" +/* Terminate after \fItime\fR seconds. This is to facilitate memory +/* leak testing. +/* SEE ALSO +/* qmqp-source(1), QMQP message generator +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <signal.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <listen.h> +#include <events.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <msg_vstream.h> +#include <netstring.h> +#include <inet_proto.h> + +/* Global library. */ + +#include <qmqp_proto.h> +#include <mail_version.h> + +/* Application-specific. */ + +typedef struct { + VSTREAM *stream; /* client connection */ + int count; /* bytes to go */ +} SINK_STATE; + +static int var_tmout; +static VSTRING *buffer; +static void disconnect(SINK_STATE *); +static int count_deliveries; +static int counter; + +/* send_reply - finish conversation */ + +static void send_reply(SINK_STATE *state) +{ + vstring_sprintf(buffer, "%cOk", QMQP_STAT_OK); + NETSTRING_PUT_BUF(state->stream, buffer); + netstring_fflush(state->stream); + if (count_deliveries) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + disconnect(state); +} + +/* read_data - read over-all netstring data */ + +static void read_data(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + int fd = vstream_fileno(state->stream); + int count; + + /* + * Refill the VSTREAM buffer, if necessary. + */ + if (VSTREAM_GETC(state->stream) == VSTREAM_EOF) + netstring_except(state->stream, vstream_ftimeout(state->stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + state->count--; + + /* + * Flush the VSTREAM buffer. As documented, vstream_fseek() discards + * unread input. + */ + if ((count = vstream_peek(state->stream)) > 0) { + state->count -= count; + if (state->count <= 0) { + send_reply(state); + return; + } + vstream_fpurge(state->stream, VSTREAM_PURGE_BOTH); + } + + /* + * Do not block while waiting for the arrival of more data. + */ + event_disable_readwrite(fd); + event_enable_read(fd, read_data, context); +} + +/* read_length - read over-all netstring length */ + +static void read_length(int event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown error reading input"); + + case NETSTRING_ERR_TIME: + msg_panic("attempt to read non-readable socket"); + /* NOTREACHED */ + + case NETSTRING_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case NETSTRING_ERR_FORMAT: + msg_warn("netstring format error"); + disconnect(state); + return; + + case NETSTRING_ERR_SIZE: + msg_warn("netstring size error"); + disconnect(state); + return; + + /* + * Include the netstring terminator in the read byte count. This + * violates abstractions. + */ + case 0: + state->count = netstring_get_length(state->stream) + 1; + read_data(event, context); + return; + } +} + +/* disconnect - handle disconnection events */ + +static void disconnect(SINK_STATE *state) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + vstream_fclose(state->stream); + myfree((void *) state); +} + +/* connect_event - handle connection events */ + +static void connect_event(int unused_event, void *context) +{ + int sock = CAST_ANY_PTR_TO_INT(context); + struct sockaddr_storage ss; + SOCKADDR_SIZE len = sizeof(ss); + struct sockaddr *sa = (struct sockaddr *) &ss; + SINK_STATE *state; + int fd; + + if ((fd = accept(sock, sa, &len)) >= 0) { + if (msg_verbose) + msg_info("connect (%s)", +#ifdef AF_LOCAL + sa->sa_family == AF_LOCAL ? "AF_LOCAL" : +#else + sa->sa_family == AF_UNIX ? "AF_UNIX" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + "unknown protocol family"); + non_blocking(fd, NON_BLOCKING); + state = (SINK_STATE *) mymalloc(sizeof(*state)); + state->stream = vstream_fdopen(fd, O_RDWR); + vstream_tweak_sock(state->stream); + netstring_setup(state->stream, var_tmout); + event_enable_read(fd, read_length, (void *) state); + } +} + +/* terminate - voluntary exit */ + +static void terminate(int unused_event, void *unused_context) +{ + exit(0); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s [-cv] [-x time] [host]:port backlog", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + int sock; + int backlog; + int ch; + int ttl; + const char *protocols = INET_PROTO_NAME_ALL; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Fix 20051207. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Initialize diagnostics. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46cvx:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'c': + count_deliveries++; + break; + case 'v': + msg_verbose++; + break; + case 'x': + if ((ttl = atoi(optarg)) <= 0) + usage(argv[0]); + event_request_timer(terminate, (void *) 0, ttl); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 2) + usage(argv[0]); + if ((backlog = atoi(argv[optind + 1])) <= 0) + usage(argv[0]); + + /* + * Initialize. + */ + (void) inet_proto_init("protocols", protocols); + buffer = vstring_alloc(1024); + if (strncmp(argv[optind], "unix:", 5) == 0) { + sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + sock = inet_listen(argv[optind], backlog, BLOCKING); + } + + /* + * Start the event handler. + */ + event_enable_read(sock, connect_event, CAST_INT_TO_VOID_PTR(sock)); + for (;;) + event_loop(-1); +} diff --git a/src/smtpstone/qmqp-source.c b/src/smtpstone/qmqp-source.c new file mode 100644 index 0000000..9231aec --- /dev/null +++ b/src/smtpstone/qmqp-source.c @@ -0,0 +1,673 @@ +/*++ +/* NAME +/* qmqp-source 1 +/* SUMMARY +/* parallelized QMQP test generator +/* SYNOPSIS +/* .fi +/* \fBqmqp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* +/* \fBqmqp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR +/* DESCRIPTION +/* \fBqmqp-source\fR connects to the named host and TCP port (default 628) +/* and sends one or more messages to it, either sequentially +/* or in parallel. The program speaks the QMQP protocol. +/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers. +/* IPv4 and IPv6 are the default. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Connect to the server with IPv4. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Connect to the server with IPv6. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP \fB-c\fR +/* Display a running counter that is incremented each time +/* a delivery completes. +/* .IP "\fB-C \fIcount\fR" +/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times +/* before giving up. The default count is 1. Specify a larger count in +/* order to work around a problem with TCP/IP stacks that send RESET +/* when the listen queue is full. +/* .IP "\fB-f \fIfrom\fR" +/* Use the specified sender address (default: <foo@myhostname>). +/* .IP "\fB-l \fIlength\fR" +/* Send \fIlength\fR bytes as message payload. The length +/* includes the message headers. +/* .IP "\fB-m \fImessage_count\fR" +/* Send the specified number of messages (default: 1). +/* .IP "\fB-M \fImyhostname\fR" +/* Use the specified hostname or [address] in the default +/* sender and recipient addresses, instead of the machine +/* hostname. +/* .IP "\fB-r \fIrecipient_count\fR" +/* Send the specified number of recipients per transaction (default: 1). +/* Recipient names are generated by prepending a number to the +/* recipient address. +/* .IP "\fB-s \fIsession_count\fR" +/* Run the specified number of QMQP sessions in parallel (default: 1). +/* .IP "\fB-t \fIto\fR" +/* Use the specified recipient address (default: <foo@myhostname>). +/* .IP "\fB-R \fIinterval\fR" +/* Wait for a random period of time 0 <= n <= interval between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP \fB-v\fR +/* Make the program more verbose, for debugging purposes. +/* .IP "\fB-w \fIinterval\fR" +/* Wait a fixed time between messages. +/* Suspending one thread does not affect other delivery threads. +/* SEE ALSO +/* qmqp-sink(1), QMQP message dump +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <msg_vstream.h> +#include <vstring.h> +#include <vstream.h> +#include <get_hostname.h> +#include <split_at.h> +#include <connect.h> +#include <mymalloc.h> +#include <events.h> +#include <iostuff.h> +#include <netstring.h> +#include <sane_connect.h> +#include <host_port.h> +#include <myaddrinfo.h> +#include <inet_proto.h> +#include <valid_hostname.h> +#include <valid_mailhost_addr.h> + +/* Global library. */ + +#include <mail_date.h> +#include <qmqp_proto.h> +#include <mail_version.h> + +/* Application-specific. */ + + /* + * Per-session data structure with state. + * + * This software can maintain multiple parallel connections to the same QMQP + * server. However, it makes no more than one connection request at a time + * to avoid overwhelming the server with SYN packets and having to back off. + * Back-off would screw up the benchmark. Pending connection requests are + * kept in a linear list. + */ +typedef struct SESSION { + int xfer_count; /* # of xfers in session */ + int rcpt_done; /* # of recipients done */ + int rcpt_count; /* # of recipients to go */ + VSTREAM *stream; /* open connection */ + int connect_count; /* # of connect()s to retry */ + struct SESSION *next; /* connect() queue linkage */ +} SESSION; + +static SESSION *last_session; /* connect() queue tail */ + +static VSTRING *buffer; +static int var_line_limit = 10240; +static int var_timeout = 300; +static const char *var_myhostname; +static int session_count; +static int message_count = 1; +static struct sockaddr_storage ss; + +#undef sun +static struct sockaddr_un sun; +static struct sockaddr *sa; +static int sa_length; +static int recipients = 1; +static char *defaddr; +static char *recipient; +static char *sender; +static int message_length = 1024; +static int count = 0; +static int counter = 0; +static int connect_count = 1; +static int random_delay = 0; +static int fixed_delay = 0; +static const char *mydate; +static int mypid; + +static void enqueue_connect(SESSION *); +static void start_connect(SESSION *); +static void connect_done(int, void *); + +static void send_data(SESSION *); +static void receive_reply(int, void *); + +static VSTRING *message_buffer; +static VSTRING *sender_buffer; +static VSTRING *recipient_buffer; + +/* Silly little macros. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* random_interval - generate a random value in 0 .. (small) interval */ + +static int random_interval(int interval) +{ + return (rand() % (interval + 1)); +} + +/* socket_error - look up and reset the last socket error */ + +static int socket_error(int sock) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Some Solaris 2 versions have getsockopt() itself return the error, + * instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} + +/* exception_text - translate exceptions from the netstring module */ + +static char *exception_text(int except) +{ + ; + + switch (except) { + case NETSTRING_ERR_EOF: + return ("lost connection"); + case NETSTRING_ERR_TIME: + return ("timeout"); + case NETSTRING_ERR_FORMAT: + return ("netstring format error"); + case NETSTRING_ERR_SIZE: + return ("netstring size exceeds limit"); + default: + msg_panic("exception_text: unknown exception %d", except); + } + /* NOTREACHED */ +} + +/* startup - connect to server but do not wait */ + +static void startup(SESSION *session) +{ + if (message_count-- <= 0) { + myfree((void *) session); + session_count--; + return; + } + enqueue_connect(session); +} + +/* start_event - invoke startup from timer context */ + +static void start_event(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + startup(session); +} + +/* start_another - start another session */ + +static void start_another(SESSION *session) +{ + if (random_delay > 0) { + event_request_timer(start_event, (void *) session, + random_interval(random_delay)); + } else if (fixed_delay > 0) { + event_request_timer(start_event, (void *) session, fixed_delay); + } else { + startup(session); + } +} + +/* enqueue_connect - queue a connection request */ + +static void enqueue_connect(SESSION *session) +{ + session->next = 0; + if (last_session == 0) { + last_session = session; + start_connect(session); + } else { + last_session->next = session; + last_session = session; + } +} + +/* dequeue_connect - connection request completed */ + +static void dequeue_connect(SESSION *session) +{ + if (session == last_session) { + if (session->next != 0) + msg_panic("dequeue_connect: queue ends after last"); + last_session = 0; + } else { + if (session->next == 0) + msg_panic("dequeue_connect: queue ends before last"); + start_connect(session->next); + } +} + +/* fail_connect - handle failed startup */ + +static void fail_connect(SESSION *session) +{ + if (session->connect_count-- == 1) + msg_fatal("connect: %m"); + msg_warn("connect: %m"); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; +#ifdef MISSING_USLEEP + doze(10); +#else + usleep(10); +#endif + start_connect(session); +} + +/* start_connect - start TCP handshake */ + +static void start_connect(SESSION *session) +{ + int fd; + struct linger linger; + + /* + * Some systems don't set the socket error when connect() fails early + * (loopback) so we must deal with the error immediately, rather than + * retrieving it later with getsockopt(). We can't use MSG_PEEK to + * distinguish between server disconnect and connection refused. + */ + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + (void) non_blocking(fd, NON_BLOCKING); + linger.l_onoff = 1; + linger.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger, + sizeof(linger)) < 0) + msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); + session->stream = vstream_fdopen(fd, O_RDWR); + event_enable_write(fd, connect_done, (void *) session); + netstring_setup(session->stream, var_timeout); + if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS) + fail_connect(session); +} + +/* connect_done - send message sender info */ + +static void connect_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int fd = vstream_fileno(session->stream); + + /* + * Try again after some delay when the connection failed, in case they + * run a Mickey Mouse protocol stack. + */ + if (socket_error(fd) < 0) { + fail_connect(session); + } else { + dequeue_connect(session); + non_blocking(fd, BLOCKING); + event_disable_readwrite(fd); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(session->stream); + send_data(session); + } +} + +/* send_data - send message+sender+recipients */ + +static void send_data(SESSION *session) +{ + int fd = vstream_fileno(session->stream); + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + + /* + * Send the message content, by wrapping three netstrings into an + * over-all netstring. + * + * XXX This should be done more carefully to avoid blocking when sending + * large messages over slow networks. + */ + netstring_put_multi(session->stream, + STR(message_buffer), LEN(message_buffer), + STR(sender_buffer), LEN(sender_buffer), + STR(recipient_buffer), LEN(recipient_buffer), + (char *) 0); + netstring_fflush(session->stream); + + /* + * Wake me up when the server replies or when something bad happens. + */ + event_enable_read(fd, receive_reply, (void *) session); +} + +/* receive_reply - read server reply */ + +static void receive_reply(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while receiving server reply", exception_text(except)); + + /* + * Receive and process the server reply. + */ + netstring_get(session->stream, buffer, var_line_limit); + if (msg_verbose) + vstream_printf("<< %.*s\n", (int) LEN(buffer), STR(buffer)); + if (STR(buffer)[0] != QMQP_STAT_OK) + msg_fatal("%s error: %.*s", + STR(buffer)[0] == QMQP_STAT_RETRY ? "recoverable" : + STR(buffer)[0] == QMQP_STAT_HARD ? "unrecoverable" : + "unknown", (int) LEN(buffer) - 1, STR(buffer) + 1); + + /* + * Update the optional running counter. + */ + if (count) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + + /* + * Finish this session. QMQP sends only one message per session. + */ + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s -cv -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -R delay -w delay host[:port]", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - parse JCL and start the machine */ + +int main(int argc, char **argv) +{ + SESSION *session; + char *host; + char *port; + char *path; + int path_len; + int sessions = 1; + int ch; + ssize_t len; + int n; + int i; + char *buf; + const char *parse_err; + struct addrinfo *res; + int aierr; + const char *protocols = INET_PROTO_NAME_ALL; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + signal(SIGPIPE, SIG_IGN); + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46cC:f:l:m:M:r:R:s:t:vw:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'c': + count++; + break; + case 'C': + if ((connect_count = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'f': + sender = optarg; + break; + case 'l': + if ((message_length = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'm': + if ((message_count = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'M': + if (*optarg == '[') { + if (!valid_mailhost_literal(optarg, DO_GRIPE)) + msg_fatal("bad address literal: %s", optarg); + } else { + if (!valid_hostname(optarg, DO_GRIPE)) + msg_fatal("bad hostname: %s", optarg); + } + var_myhostname = optarg; + break; + case 'r': + if ((recipients = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'R': + if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 's': + if ((sessions = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 't': + recipient = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'w': + if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0) + usage(argv[0]); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 1) + usage(argv[0]); + + if (random_delay > 0) + srand(getpid()); + + /* + * Translate endpoint address to internal form. + */ + (void) inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + path = argv[optind] + 5; + path_len = strlen(path); + if (path_len >= (int) sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len); + sa = (struct sockaddr *) &sun; + sa_length = sizeof(sun); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + buf = mystrdup(argv[optind]); + if ((parse_err = host_port(buf, &host, (char *) 0, &port, "628")) != 0) + msg_fatal("%s: %s", argv[optind], parse_err); + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) + msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); + myfree(buf); + sa = (struct sockaddr *) &ss; + if (res->ai_addrlen > sizeof(ss)) + msg_fatal("address length %d > buffer length %d", + (int) res->ai_addrlen, (int) sizeof(ss)); + memcpy((void *) sa, res->ai_addr, res->ai_addrlen); + sa_length = res->ai_addrlen; +#ifdef HAS_SA_LEN + sa->sa_len = sa_length; +#endif + freeaddrinfo(res); + } + + /* + * Allocate space for temporary buffer. + */ + buffer = vstring_alloc(100); + + /* + * Make sure we have sender and recipient addresses. + */ + if (var_myhostname == 0) + var_myhostname = get_hostname(); + if (sender == 0 || recipient == 0) { + vstring_sprintf(buffer, "foo@%s", var_myhostname); + defaddr = mystrdup(vstring_str(buffer)); + if (sender == 0) + sender = defaddr; + if (recipient == 0) + recipient = defaddr; + } + + /* + * Prepare some results that may be used multiple times: the message + * content netstring, the sender netstring, and the recipient netstrings. + */ + mydate = mail_date(time((time_t *) 0)); + mypid = getpid(); + + message_buffer = vstring_alloc(message_length + 200); + vstring_sprintf(buffer, + "From: <%s>\nTo: <%s>\nDate: %s\nMessage-Id: <%d@%s>\n\n", + sender, recipient, mydate, mypid, var_myhostname); + for (n = 1; LEN(buffer) < message_length; n++) { + for (i = 0; i < n && i < 79; i++) + VSTRING_ADDCH(buffer, 'X'); + VSTRING_ADDCH(buffer, '\n'); + } + STR(buffer)[message_length - 1] = '\n'; + netstring_memcpy(message_buffer, STR(buffer), message_length); + + len = strlen(sender); + sender_buffer = vstring_alloc(len); + netstring_memcpy(sender_buffer, sender, len); + + if (recipients == 1) { + len = strlen(recipient); + recipient_buffer = vstring_alloc(len); + netstring_memcpy(recipient_buffer, recipient, len); + } else { + recipient_buffer = vstring_alloc(100); + for (n = 0; n < recipients; n++) { + vstring_sprintf(buffer, "%d%s", n, recipient); + netstring_memcat(recipient_buffer, STR(buffer), LEN(buffer)); + } + } + + /* + * Start sessions. + */ + while (sessions-- > 0) { + session = (SESSION *) mymalloc(sizeof(*session)); + session->stream = 0; + session->xfer_count = 0; + session->connect_count = connect_count; + session->next = 0; + session_count++; + startup(session); + } + for (;;) { + event_loop(-1); + if (session_count <= 0 && message_count <= 0) { + if (count) { + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } + exit(0); + } + } +} diff --git a/src/smtpstone/smtp-sink.c b/src/smtpstone/smtp-sink.c new file mode 100644 index 0000000..4378d5a --- /dev/null +++ b/src/smtpstone/smtp-sink.c @@ -0,0 +1,1643 @@ +/*++ +/* NAME +/* smtp-sink 1 +/* SUMMARY +/* parallelized SMTP/LMTP test server +/* SYNOPSIS +/* .fi +/* \fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR +/* \fIbacklog\fR +/* +/* \fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR +/* DESCRIPTION +/* \fBsmtp-sink\fR listens on the named host (or address) and port. +/* It takes SMTP messages from the network and throws them away. +/* The purpose is to measure client performance, not protocol +/* compliance. +/* +/* \fBsmtp-sink\fR may also be configured to capture each mail +/* delivery transaction to file. Since disk latencies are large +/* compared to network delays, this mode of operation can +/* reduce the maximal performance by several orders of magnitude. +/* +/* Connections can be accepted on IPv4 or IPv6 endpoints, or on +/* UNIX-domain sockets. +/* IPv4 and IPv6 are the default. +/* This program is the complement of the \fBsmtp-source\fR(1) program. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Support IPv4 only. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Support IPv6 only. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP \fB-8\fR +/* Do not announce 8BITMIME support. +/* .IP \fB-a\fR +/* Do not announce SASL authentication support. +/* .IP "\fB-A \fIdelay\fR" +/* Wait \fIdelay\fR seconds after responding to DATA, then +/* abort prematurely with a 550 reply status. Do not read +/* further input from the client; this is an attempt to block +/* the client before it sends ".". Specify a zero delay value +/* to abort immediately. +/* .IP "\fB-b \fIsoft-bounce-reply\fR" +/* Use \fIsoft-bounce-reply\fR for soft reject responses. The +/* default reply is "450 4.3.0 Error: command failed". +/* .IP "\fB-B \fIhard-bounce-reply\fR" +/* Use \fIhard-bounce-reply\fR for hard reject responses. The +/* default reply is "500 5.3.0 Error: command failed". +/* .IP \fB-c\fR +/* Display running counters that are updated whenever an SMTP +/* session ends, a QUIT command is executed, or when "." is +/* received. +/* .IP \fB-C\fR +/* Disable XCLIENT support. +/* .IP "\fB-d \fIdump-template\fR" +/* Dump each mail transaction to a single-message file whose +/* name is created by expanding the \fIdump-template\fR via +/* strftime(3) and appending a pseudo-random hexadecimal number +/* (example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3"). +/* If the template contains "/" characters, missing directories +/* are created automatically. The message dump format is +/* described below. +/* .sp +/* Note: this option keeps one capture file open for every +/* mail transaction in progress. +/* .IP "\fB-D \fIdump-template\fR" +/* Append mail transactions to a multi-message dump file whose +/* name is created by expanding the \fIdump-template\fR via +/* strftime(3). +/* If the template contains "/" characters, missing directories +/* are created automatically. The message dump format is +/* described below. +/* .sp +/* Note: this option keeps one capture file open for every +/* mail transaction in progress. +/* .IP \fB-e\fR +/* Do not announce ESMTP support. +/* .IP \fB-E\fR +/* Do not announce ENHANCEDSTATUSCODES support. +/* .IP "\fB-f \fIcommand,command,...\fR" +/* Reject the specified commands with a hard (5xx) error code. +/* This option implies \fB-p\fR. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP \fB-F\fR +/* Disable XFORWARD support. +/* .IP "\fB-h\fI hostname\fR" +/* Use \fIhostname\fR in the SMTP greeting, in the HELO response, +/* and in the EHLO response. The default hostname is "smtp-sink". +/* .IP "\fB-H\fI delay\fR" +/* Delay the first read operation after receiving DATA (time +/* in seconds). Combine with a large test message and a small +/* TCP window size (see the \fB-T\fR option) to test the Postfix +/* client write_wait() implementation. +/* .IP \fB-L\fR +/* Enable LMTP instead of SMTP. +/* .IP "\fB-m \fIcount\fR (default: 256)" +/* An upper bound on the maximal number of simultaneous +/* connections that \fBsmtp-sink\fR will handle. This prevents +/* the process from running out of file descriptors. Excess +/* connections will stay queued in the TCP/IP stack. +/* .IP "\fB-M \fIcount\fR" +/* Terminate after receiving \fIcount\fR messages. +/* .IP "\fB-n \fIcount\fR" +/* Terminate after \fIcount\fR sessions. +/* .IP \fB-N\fR +/* Do not announce support for DSN. +/* .IP \fB-p\fR +/* Do not announce support for ESMTP command pipelining. +/* .IP \fB-P\fR +/* Change the server greeting so that it appears to come through +/* a CISCO PIX system. Implies \fB-e\fR. +/* .IP "\fB-q \fIcommand,command,...\fR" +/* Disconnect (without replying) after receiving one of the +/* specified commands. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-Q \fIcommand,command,...\fR" +/* Send a 421 reply and disconnect after receiving one +/* of the specified commands. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-r \fIcommand,command,...\fR" +/* Reject the specified commands with a soft (4xx) error code. +/* This option implies \fB-p\fR. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-R \fIroot-directory\fR" +/* Change the process root directory to the specified location. +/* This option requires super-user privileges. See also the +/* \fB-u\fR option. +/* .IP "\fB-s \fIcommand,command,...\fR" +/* Log the named commands to syslogd. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-S start-string\fR" +/* An optional string that is prepended to each message that is +/* written to a dump file (see the dump file format description +/* below). The following C escape sequences are supported: \ea +/* (bell), \eb (backspace), \ef (formfeed), \en (newline), \er +/* (carriage return), \et (horizontal tab), \ev (vertical tab), +/* \e\fIddd\fR (up to three octal digits) and \e\e (the backslash +/* character). +/* .IP "\fB-t \fItimeout\fR (default: 100)" +/* Limit the time for receiving a command or sending a response. +/* The time limit is specified in seconds. +/* .IP "\fB-T \fIwindowsize\fR" +/* Override the default TCP window size. To work around +/* broken TCP window scaling implementations, specify a +/* value > 0 and < 65536. +/* .IP "\fB-u \fIusername\fR" +/* Switch to the specified user privileges after opening the +/* network socket and optionally changing the process root +/* directory. This option is required when the process runs +/* with super-user privileges. See also the \fB-R\fR option. +/* .IP \fB-v\fR +/* Show the SMTP conversations. +/* .IP "\fB-w \fIdelay\fR" +/* Wait \fIdelay\fR seconds before responding to a DATA command. +/* .IP "\fB-W \fIcommand:delay[:odds]\fR" +/* Wait \fIdelay\fR seconds before responding to \fIcommand\fR. +/* If \fIodds\fR is also specified (a number between 1-99 +/* inclusive), wait for a random multiple of \fIdelay\fR. The +/* random multiplier is equal to the number of times the program +/* needs to roll a dice with a range of 0..99 inclusive, before +/* the dice produces a result greater than or equal to \fIodds\fR. +/* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR +/* Listen on network interface \fIhost\fR (default: any interface) +/* TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be +/* specified in numeric or symbolic form. +/* .IP \fBunix:\fR\fIpathname\fR +/* Listen on the UNIX-domain socket at \fIpathname\fR. +/* .IP \fIbacklog\fR +/* The maximum length the queue of pending connections, +/* as defined by the \fBlisten\fR(2) system call. +/* DUMP FILE FORMAT +/* .ad +/* .fi +/* Each dumped message contains a sequence of text lines, +/* terminated with the newline character. The sequence of +/* information is as follows: +/* .IP \(bu +/* The optional string specified with the \fB-S\fR option. +/* .IP \(bu +/* The \fBsmtp-sink\fR generated headers as documented below. +/* .IP \(bu +/* The message header and body as received from the SMTP client. +/* .IP \(bu +/* An empty line. +/* .PP +/* The format of the \fBsmtp-sink\fR generated headers is as +/* follows: +/* .IP "\fBX-Client-Addr: \fItext\fR" +/* The client IP address without enclosing []. An IPv6 address +/* is prefixed with "ipv6:". This record is always present. +/* .IP "\fBX-Client-Proto: \fItext\fR" +/* The client protocol: SMTP, ESMTP or LMTP. This record is +/* always present. +/* .IP "\fBX-Helo-Args: \fItext\fR" +/* The arguments of the last HELO or EHLO command before this +/* mail delivery transaction. This record is present only if +/* the client sent a recognizable HELO or EHLO command before +/* the DATA command. +/* .IP "\fBX-Mail-Args: \fItext\fR" +/* The arguments of the MAIL command that started this mail +/* delivery transaction. This record is present exactly once. +/* .IP "\fBX-Rcpt-Args: \fItext\fR" +/* The arguments of an RCPT command within this mail delivery +/* transaction. There is one record for each RCPT command, and +/* they are in the order as sent by the client. +/* .IP "\fBReceived: \fItext\fR" +/* A message header for compatibility with mail processing +/* software. This three-line header marks the end of the headers +/* provided by \fBsmtp-sink\fR, and is formatted as follows: +/* .RS +/* .IP "\fBfrom \fIhelo\fR ([\fIaddr\fR])" +/* The HELO or EHLO command argument and client IP address. +/* If the client did not send HELO or EHLO, the client IP +/* address is used instead. +/* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR" +/* The hostname specified with the \fB-h\fR option, the client +/* protocol (see \fBX-Client-Proto\fR above), and the pseudo-random +/* portion of the per-message capture file name. +/* .IP \fItime-stamp\fR +/* A time stamp as defined in RFC 2822. +/* .RE +/* SEE ALSO +/* smtp-source(1), SMTP/LMTP message generator +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <syslog.h> +#include <signal.h> +#include <time.h> +#include <ctype.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <get_hostname.h> +#include <listen.h> +#include <events.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <msg_vstream.h> +#include <stringops.h> +#include <sane_accept.h> +#include <inet_proto.h> +#include <myaddrinfo.h> +#include <make_dirs.h> +#include <myrand.h> +#include <chroot_uid.h> + +/* Global library. */ + +#include <smtp_stream.h> +#include <mail_date.h> +#include <mail_version.h> + +/* Application-specific. */ + +typedef struct SINK_STATE { + VSTREAM *stream; + VSTRING *buffer; + int data_state; + int (*read_fn) (struct SINK_STATE *); + int in_mail; + int rcpts; + char *push_back_ptr; + /* Capture file information for fake Received: header */ + MAI_HOSTADDR_STR client_addr; /* IP address */ + char *addr_prefix; /* ipv6: or empty */ + char *helo_args; /* text after HELO or EHLO */ + const char *client_proto; /* SMTP, ESMTP, LMTP */ + time_t start_time; /* MAIL command time */ + int id; /* pseudo-random */ + VSTREAM *dump_file; /* dump file or null */ + void (*delayed_response) (struct SINK_STATE *state, const char *); + char *delayed_args; +} SINK_STATE; + +#define ST_ANY 0 +#define ST_CR 1 +#define ST_CR_LF 2 +#define ST_CR_LF_DOT 3 +#define ST_CR_LF_DOT_CR 4 +#define ST_CR_LF_DOT_CR_LF 5 + +#define PUSH_BACK_PEEK(state) (*(state)->push_back_ptr != 0) +#define PUSH_BACK_GET(state) (*(state)->push_back_ptr++) +#define PUSH_BACK_SET(state, text) ((state)->push_back_ptr = (text)) + +#ifndef DEF_MAX_CLIENT_COUNT +#define DEF_MAX_CLIENT_COUNT 256 +#endif + +#define SOFT_ERROR_RESP "450 4.3.0 Error: command failed" +#define HARD_ERROR_RESP "500 5.3.0 Error: command failed" + + /* + * We can't rely on vstream auto-flushing, so we have to prepare for the + * next read request. + */ +#define SMTP_FLUSH(fp) do { \ + if (vstream_peek(fp) <= 0 && readable(vstream_fileno(fp)) <= 0) \ + smtp_flush(fp); \ + } while (0) + +static int var_tmout = 100; +static int var_max_line_length = 2048; +static char *var_myhostname; +static char *soft_error_resp = SOFT_ERROR_RESP; +static char *hard_error_resp = HARD_ERROR_RESP; +static int command_read(SINK_STATE *); +static int data_read(SINK_STATE *); +static void disconnect(SINK_STATE *); +static void read_timeout(int, void *); +static void read_event(int, void *); +static int show_count; +static int sess_count; +static int quit_count; +static int mesg_count; +static int max_quit_count; +static int max_msg_quit_count; +static int disable_pipelining; +static int disable_8bitmime; +static int disable_esmtp; +static int enable_lmtp; +static int pretend_pix; +static int disable_saslauth; +static int disable_xclient; +static int disable_xforward; +static int disable_enh_status; +static int disable_dsn; +static int max_client_count = DEF_MAX_CLIENT_COUNT; +static int client_count; +static int sock; +static int abort_delay = -1; +static int data_read_delay = 0; + +static char *single_template; /* individual template */ +static char *shared_template; /* shared template */ +static VSTRING *start_string; /* dump content prefix */ + +static INET_PROTO_INFO *proto_info; + +#define STR(x) vstring_str(x) + +/* do_stats - show counters */ + +static void do_stats(void) +{ + vstream_printf("sess=%d quit=%d mesg=%d\r", + sess_count, quit_count, mesg_count); + vstream_fflush(VSTREAM_OUT); +} + +/* hard_err_resp - generic hard error response */ + +static void hard_err_resp(SINK_STATE *state) +{ + smtp_printf(state->stream, "%s", hard_error_resp); + SMTP_FLUSH(state->stream); +} + +/* soft_err_resp - generic soft error response */ + +static void soft_err_resp(SINK_STATE *state) +{ + smtp_printf(state->stream, "%s", soft_error_resp); + SMTP_FLUSH(state->stream); +} + +/* exp_path_template - expand template pathname, static result */ + +static VSTRING *exp_path_template(const char *template, time_t start_time) +{ + static VSTRING *path_buf = 0; + struct tm *lt; + + if (path_buf == 0) + path_buf = vstring_alloc(100); + else + VSTRING_RESET(path_buf); + lt = localtime(&start_time); + while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0) + VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100); + VSTRING_SKIP(path_buf); + return (path_buf); +} + +/* make_parent_dir - create parent directory or bust */ + +static void make_parent_dir(const char *path, mode_t mode) +{ + const char *parent; + + parent = sane_dirname((VSTRING *) 0, path); + if (make_dirs(parent, mode) < 0) + msg_fatal("mkdir %s: %m", parent); +} + +/* mail_file_open - open mail capture file */ + +static void mail_file_open(SINK_STATE *state) +{ + const char *myname = "mail_file_open"; + VSTRING *path_buf; + ssize_t len; + int tries = 0; + + /* + * Save the start time for later. + */ + time(&(state->start_time)); + + /* + * Expand the per-message dumpfile pathname template. + */ + path_buf = exp_path_template(single_template, state->start_time); + + /* + * Append a random hexadecimal string to the pathname and create a new + * file. Retry with a different path if the file already exists. Create + * intermediate directories on the fly when the template specifies + * multiple pathname segments. + */ +#define ID_FORMAT "%08x" + + for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) { + if (++tries > 100) + msg_fatal("%s: something is looping", myname); + state->id = myrand(); + vstring_sprintf_append(path_buf, ID_FORMAT, state->id); + if ((state->dump_file = vstream_fopen(STR(path_buf), + O_RDWR | O_CREAT | O_EXCL, + 0644)) != 0) { + break; + } else if (errno == EEXIST) { + continue; + } else if (errno == ENOENT) { + make_parent_dir(STR(path_buf), 0755); + continue; + } else { + msg_fatal("open %s: %m", STR(path_buf)); + } + } + + /* + * Don't leave temporary files behind. + */ + if (shared_template != 0 && unlink(STR(path_buf)) < 0) + msg_fatal("unlink %s: %m", STR(path_buf)); + + /* + * Do initial header records. + */ + if (start_string) + vstream_fprintf(state->dump_file, "%s", STR(start_string)); + vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n", + state->addr_prefix, state->client_addr.buf); + vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto); + if (state->helo_args) + vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args); + /* Note: there may be more than one recipient. */ +} + +/* mail_file_finish_header - do final smtp-sink generated header records */ + +static void mail_file_finish_header(SINK_STATE *state) +{ + if (state->helo_args) + vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n", + state->helo_args, state->addr_prefix, + state->client_addr.buf); + else + vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n", + state->addr_prefix, state->client_addr.buf, + state->addr_prefix, state->client_addr.buf); + vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)" + " with %s id " ID_FORMAT ";\n", + var_myhostname, state->client_proto, state->id); + vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time)); +} + +/* mail_file_cleanup - common cleanup for capture file */ + +static void mail_file_cleanup(SINK_STATE *state) +{ + (void) vstream_fclose(state->dump_file); + state->dump_file = 0; +} + +/* mail_file_finish - handle message completion for capture file */ + +static void mail_file_finish(SINK_STATE *state) +{ + + /* + * Optionally append the captured message to a shared dumpfile. + */ + if (shared_template) { + const char *out_path; + VSTREAM *out_fp; + ssize_t count; + + /* + * Expand the shared dumpfile pathname template. + */ + out_path = STR(exp_path_template(shared_template, state->start_time)); + + /* + * Open the shared dump file. + */ +#define OUT_OPEN_FLAGS (O_WRONLY | O_CREAT | O_APPEND) +#define OUT_OPEN_MODE 0644 + + if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE)) + == 0 && errno == ENOENT) { + make_parent_dir(out_path, 0755); + out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE); + } + if (out_fp == 0) + msg_fatal("open %s: %m", out_path); + + /* + * Append message content from single-message dump file. + */ + if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file)); + VSTRING_RESET(state->buffer); + for (;;) { + count = vstream_fread(state->dump_file, STR(state->buffer), + vstring_avail(state->buffer)); + if (count <= 0) + break; + if (vstream_fwrite(out_fp, STR(state->buffer), count) != count) + msg_fatal("append file %s: %m", out_path); + } + if (vstream_ferror(state->dump_file)) + msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file)); + if (vstream_fclose(out_fp)) + msg_fatal("append file %s: %m", out_path); + } + mail_file_cleanup(state); +} + +/* mail_file_reset - abort mail to capture file */ + +static void mail_file_reset(SINK_STATE *state) +{ + if (shared_template == 0 + && unlink(VSTREAM_PATH(state->dump_file)) < 0 + && errno != ENOENT) + msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file)); + mail_file_cleanup(state); +} + +/* mail_cmd_reset - reset mail transaction information */ + +static void mail_cmd_reset(SINK_STATE *state) +{ + state->in_mail = 0; + /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */ + if (state->dump_file) + mail_file_reset(state); +} + +/* ehlo_response - respond to EHLO command */ + +static void ehlo_response(SINK_STATE *state, const char *args) +{ +#define SKIP(cp, cond) do { \ + for (/* void */; *cp && (cond); cp++) \ + /* void */; \ + } while (0) + + /* EHLO aborts a mail transaction in progress. */ + mail_cmd_reset(state); + if (enable_lmtp == 0) + state->client_proto = "ESMTP"; + smtp_printf(state->stream, "250-%s", var_myhostname); + if (!disable_pipelining) + smtp_printf(state->stream, "250-PIPELINING"); + if (!disable_8bitmime) + smtp_printf(state->stream, "250-8BITMIME"); + if (!disable_saslauth) + smtp_printf(state->stream, "250-AUTH PLAIN LOGIN"); + if (!disable_xclient) + smtp_printf(state->stream, "250-XCLIENT NAME HELO"); + if (!disable_xforward) + smtp_printf(state->stream, "250-XFORWARD NAME ADDR PROTO HELO"); + if (!disable_enh_status) + smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES"); + if (!disable_dsn) + smtp_printf(state->stream, "250-DSN"); + /* RFC 821/2821/5321: Format is replycode<SPACE>optional-text<CRLF> */ + smtp_printf(state->stream, "250 "); + SMTP_FLUSH(state->stream); + if (single_template) { + if (state->helo_args) + myfree(state->helo_args); + SKIP(args, ISSPACE(*args)); + state->helo_args = mystrdup(args); + } +} + +/* helo_response - respond to HELO command */ + +static void helo_response(SINK_STATE *state, const char *args) +{ + /* HELO aborts a mail transaction in progress. */ + mail_cmd_reset(state); + state->client_proto = "SMTP"; + smtp_printf(state->stream, "250 %s", var_myhostname); + SMTP_FLUSH(state->stream); + if (single_template) { + if (state->helo_args) + myfree(state->helo_args); + SKIP(args, ISSPACE(*args)); + state->helo_args = mystrdup(args); + } +} + +/* ok_response - send 250 OK */ + +static void ok_response(SINK_STATE *state, const char *unused_args) +{ + smtp_printf(state->stream, "250 2.0.0 Ok"); + SMTP_FLUSH(state->stream); +} + +/* rset_response - reset, send 250 OK */ + +static void rset_response(SINK_STATE *state, const char *unused_args) +{ + mail_cmd_reset(state); + smtp_printf(state->stream, "250 2.1.0 Ok"); + SMTP_FLUSH(state->stream); +} + +/* mail_response - reset recipient count, send 250 OK */ + +static void mail_response(SINK_STATE *state, const char *args) +{ + if (state->in_mail) { + smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command"); + SMTP_FLUSH(state->stream); + return; + } + state->in_mail++; + state->rcpts = 0; + smtp_printf(state->stream, "250 2.1.0 Ok"); + SMTP_FLUSH(state->stream); + if (single_template) { + mail_file_open(state); + SKIP(args, *args != ':'); + SKIP(args, *args == ':'); + SKIP(args, ISSPACE(*args)); + vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args); + } +} + +/* rcpt_response - bump recipient count, send 250 OK */ + +static void rcpt_response(SINK_STATE *state, const char *args) +{ + if (state->in_mail == 0) { + smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command"); + SMTP_FLUSH(state->stream); + return; + } + state->rcpts++; + smtp_printf(state->stream, "250 2.1.5 Ok"); + SMTP_FLUSH(state->stream); + /* Note: there may be more than one recipient per mail transaction. */ + if (state->dump_file) { + SKIP(args, *args != ':'); + SKIP(args, *args == ':'); + SKIP(args, ISSPACE(*args)); + vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args); + } +} + +/* abort_event - delayed abort after DATA command */ + +static void abort_event(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + smtp_printf(state->stream, "550 This violates SMTP"); + SMTP_FLUSH(state->stream); + disconnect(state); +} + +/* delay_read_event - resume input event handling */ + +static void delay_read_event(int event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + if (event != EVENT_TIME) + msg_panic("delay_read_event: non-timer event %d", event); + + event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); + event_request_timer(read_timeout, (void *) state, var_tmout); +} + +/* delay_read - temporarily suspend input event handling */ + +static void delay_read(SINK_STATE *state, int delay) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_timeout, (void *) state); + event_request_timer(delay_read_event, (void *) state, delay); +} + +/* data_response - respond to DATA command */ + +static void data_response(SINK_STATE *state, const char *unused_args) +{ + if (state->in_mail == 0 || state->rcpts == 0) { + smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command"); + SMTP_FLUSH(state->stream); + return; + } + /* Not: ST_ANY. */ + state->data_state = ST_CR_LF; + smtp_printf(state->stream, "354 End data with <CR><LF>.<CR><LF>"); + SMTP_FLUSH(state->stream); + if (abort_delay < 0) { + state->read_fn = data_read; + /* Todo: move into code that invokes the command response function. */ + if (data_read_delay > 0) + delay_read(state, data_read_delay); + } else { + /* Stop reading, send premature 550, and disconnect. */ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_event, (void *) state); + event_request_timer(abort_event, (void *) state, abort_delay); + } + if (state->dump_file) + mail_file_finish_header(state); +} + +/* dot_resp_hard - hard error response to . command */ + +static void dot_resp_hard(SINK_STATE *state) +{ + if (enable_lmtp) { + while (state->rcpts-- > 0) /* XXX this could block */ + smtp_printf(state->stream, "%s", hard_error_resp); + } else { + smtp_printf(state->stream, "%s", hard_error_resp); + } + SMTP_FLUSH(state->stream); +} + +/* dot_resp_soft - soft error response to . command */ + +static void dot_resp_soft(SINK_STATE *state) +{ + if (enable_lmtp) { + while (state->rcpts-- > 0) /* XXX this could block */ + smtp_printf(state->stream, "%s", soft_error_resp); + } else { + smtp_printf(state->stream, "%s", soft_error_resp); + } + SMTP_FLUSH(state->stream); +} + +/* dot_response - response to . command */ + +static void dot_response(SINK_STATE *state, const char *unused_args) +{ + if (enable_lmtp) { + while (state->rcpts-- > 0) /* XXX this could block */ + smtp_printf(state->stream, "250 2.2.0 Ok"); + } else { + smtp_printf(state->stream, "250 2.0.0 Ok"); + } + SMTP_FLUSH(state->stream); +} + +/* quit_response - respond to QUIT command */ + +static void quit_response(SINK_STATE *state, const char *unused_args) +{ + smtp_printf(state->stream, "221 Bye"); + smtp_flush(state->stream); /* not: SMTP_FLUSH */ + if (show_count) + quit_count++; +} + +/* conn_response - respond to connect command */ + +static void conn_response(SINK_STATE *state, const char *unused_args) +{ + if (pretend_pix) + smtp_printf(state->stream, "220 ********"); + else if (disable_esmtp) + smtp_printf(state->stream, "220 %s", var_myhostname); + else + smtp_printf(state->stream, "220 %s ESMTP", var_myhostname); + SMTP_FLUSH(state->stream); +} + +/* delay_event - delayed command response */ + +static void delay_event(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown read/write error"); + /* NOTREACHED */ + + case SMTP_ERR_TIME: + msg_warn("write timeout"); + disconnect(state); + return; + + case SMTP_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case 0: + state->delayed_response(state, state->delayed_args); + myfree(state->delayed_args); + state->delayed_args = 0; + break; + } + + if (state->delayed_response == quit_response) { + disconnect(state); + return; + } + state->delayed_response = 0; + + /* Resume input event handling after the delayed response. */ + event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); + event_request_timer(read_timeout, (void *) state, var_tmout); +} + +/* data_read - read data from socket */ + +static int data_read(SINK_STATE *state) +{ + int ch; + struct data_trans { + int state; + int want; + int next_state; + }; + static struct data_trans data_trans[] = { + ST_ANY, '\r', ST_CR, + ST_CR, '\n', ST_CR_LF, + ST_CR_LF, '.', ST_CR_LF_DOT, + ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR, + ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF, + }; + struct data_trans *dp; + + /* + * A read may result in EOF, but is never supposed to time out - a time + * out means that we were trying to read when no data was available. + */ + for (;;) { + if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF) + return (-1); + for (dp = data_trans; dp->state != state->data_state; dp++) + /* void */ ; + + /* + * Try to match the current character desired by the state machine. + * If that fails, try to restart the machine with a match for its + * first state. This covers the case of a CR/LF/CR/LF sequence + * (empty line) right before the end of the message data. + */ + if (ch == dp->want) + state->data_state = dp->next_state; + else if (ch == data_trans[0].want) + state->data_state = data_trans[0].next_state; + else + state->data_state = ST_ANY; + if (state->dump_file) { + if (ch != '\r' && state->data_state != ST_CR_LF_DOT) + VSTREAM_PUTC(ch, state->dump_file); + if (vstream_ferror(state->dump_file)) + msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file)); + } + if (state->data_state == ST_CR_LF_DOT_CR_LF) { + PUSH_BACK_SET(state, ".\r\n"); + state->read_fn = command_read; + state->data_state = ST_ANY; + if (state->dump_file) + mail_file_finish(state); + mail_cmd_reset(state); + if (show_count || max_msg_quit_count > 0) { + mesg_count++; + if (show_count) + do_stats(); + if (max_msg_quit_count > 0 && mesg_count >= max_msg_quit_count) + exit(0); + } + break; + } + + /* + * We must avoid blocking I/O, so get out of here as soon as both the + * VSTREAM and kernel read buffers dry up. + */ + if (vstream_peek(state->stream) <= 0 + && readable(vstream_fileno(state->stream)) <= 0) + return (0); + } + return (0); +} + + /* + * The table of all SMTP commands that we can handle. + */ +typedef struct SINK_COMMAND { + const char *name; + void (*response) (SINK_STATE *, const char *); + void (*hard_response) (SINK_STATE *); + void (*soft_response) (SINK_STATE *); + int flags; + int delay; + int delay_odds; +} SINK_COMMAND; + +#define FLAG_ENABLE (1<<0) /* command is enabled */ +#define FLAG_SYSLOG (1<<1) /* log the command */ +#define FLAG_HARD_ERR (1<<2) /* report hard error */ +#define FLAG_SOFT_ERR (1<<3) /* report soft error */ +#define FLAG_DISCONNECT (1<<4) /* disconnect */ +#define FLAG_CLOSE (1<<5) /* say goodbye and disconnect */ + +static SINK_COMMAND command_table[] = { + "connect", conn_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "helo", helo_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "ehlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "lhlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "xclient", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "xforward", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "auth", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "mail", mail_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + ".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE, 0, 0, + "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + 0, +}; + +/* reset_cmd_flags - reset per-command command flags */ + +static void reset_cmd_flags(const char *cmd, int flags) +{ + SINK_COMMAND *cmdp; + + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(cmd, cmdp->name) == 0) + break; + if (cmdp->name == 0) + msg_fatal("unknown command: %s", cmd); + cmdp->flags &= ~flags; +} + +/* set_cmd_flags - set per-command command flags */ + +static void set_cmd_flags(const char *cmd, int flags) +{ + SINK_COMMAND *cmdp; + + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(cmd, cmdp->name) == 0) + break; + if (cmdp->name == 0) + msg_fatal("unknown command: %s", cmd); + cmdp->flags |= flags; +} + +/* set_cmds_flags - set per-command flags for multiple commands */ + +static void set_cmds_flags(const char *cmds, int flags) +{ + char *saved_cmds; + char *cp; + char *cmd; + + saved_cmds = cp = mystrdup(cmds); + while ((cmd = mystrtok(&cp, CHARS_COMMA_SP)) != 0) + set_cmd_flags(cmd, flags); + myfree(saved_cmds); +} + +/* set_cmd_delay - set per-command delay */ + +static void set_cmd_delay(const char *cmd, int delay, int odds) +{ + SINK_COMMAND *cmdp; + + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(cmd, cmdp->name) == 0) + break; + if (cmdp->name == 0) + msg_fatal("unknown command: %s", cmd); + + if (delay <= 0) + msg_fatal("non-positive '%s' delay", cmd); + if (odds < 0 || odds > 99) + msg_fatal("delay odds for '%s' out of range", cmd); + + cmdp->delay = delay; + cmdp->delay_odds = odds; +} + +/* set_cmd_delay_arg - set per-command delay from option argument */ + +static void set_cmd_delay_arg(char *arg) +{ + char *cp; + char *saved_arg; + char *cmd; + char *delay; + char *odds; + + saved_arg = cp = mystrdup(arg); + cmd = mystrtok(&cp, ":"); + delay = mystrtok(&cp, ":"); + if (cmd == 0 || delay == 0) + msg_fatal("invalid command delay argument: %s", arg); + odds = mystrtok(&cp, ""); + set_cmd_delay(cmd, atoi(delay), odds ? atoi(odds) : 0); + myfree(saved_arg); +} + +/* command_resp - respond to command */ + +static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp, + const char *command, const char *args) +{ + /* We use raw syslog. Sanitize data content and length. */ + if (cmdp->flags & FLAG_SYSLOG) + syslog(LOG_INFO, "%s %.100s", command, args); + if (cmdp->flags & FLAG_DISCONNECT) + return (-1); + if (cmdp->flags & FLAG_CLOSE) { + smtp_printf(state->stream, "421 4.0.0 Server closing connection"); + return (-1); + } + if (cmdp->flags & FLAG_HARD_ERR) { + cmdp->hard_response(state); + return (0); + } + if (cmdp->flags & FLAG_SOFT_ERR) { + cmdp->soft_response(state); + return (0); + } + if (cmdp->delay > 0) { + int delay = cmdp->delay; + + if (cmdp->delay_odds > 0) + for (delay = 0; + ((int) (100.0 * rand() / (RAND_MAX + 1.0))) < cmdp->delay_odds; + delay += cmdp->delay) + /* NOP */ ; + /* Suspend input event handling while delaying the command response. */ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_timeout, (void *) state); + event_request_timer(delay_event, (void *) state, delay); + state->delayed_response = cmdp->response; + state->delayed_args = mystrdup(args); + } else { + cmdp->response(state, args); + if (cmdp->response == quit_response) + return (-1); + } + return (0); +} + +/* command_read - talk the SMTP protocol, server side */ + +static int command_read(SINK_STATE *state) +{ + char *command; + SINK_COMMAND *cmdp; + int ch; + struct cmd_trans { + int state; + int want; + int next_state; + }; + static struct cmd_trans cmd_trans[] = { + ST_ANY, '\r', ST_CR, + ST_CR, '\n', ST_CR_LF, + 0, 0, 0, + }; + struct cmd_trans *cp; + char *ptr; + + /* + * A read may result in EOF, but is never supposed to time out - a time + * out means that we were trying to read when no data was available. + */ +#define NEXT_CHAR(state) \ + (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream)) + + if (state->data_state == ST_CR_LF) + state->data_state = ST_ANY; /* XXX */ + for (;;) { + if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF) + return (-1); + + /* + * Sanity check. We don't want to store infinitely long commands. + */ + if (VSTRING_LEN(state->buffer) >= var_max_line_length) { + msg_warn("command line too long"); + return (-1); + } + VSTRING_ADDCH(state->buffer, ch); + + /* + * Try to match the current character desired by the state machine. + * If that fails, try to restart the machine with a match for its + * first state. + */ + for (cp = cmd_trans; cp->state != state->data_state; cp++) + if (cp->want == 0) + msg_panic("command_read: unknown state: %d", state->data_state); + if (ch == cp->want) + state->data_state = cp->next_state; + else if (ch == cmd_trans[0].want) + state->data_state = cmd_trans[0].next_state; + else + state->data_state = ST_ANY; + if (state->data_state == ST_CR_LF) + break; + + /* + * We must avoid blocking I/O, so get out of here as soon as both the + * VSTREAM and kernel read buffers dry up. + * + * XXX Solaris non-blocking read() may fail on a socket when ioctl + * FIONREAD reports there is unread data. Diagnosis by Max Pashkov. + * As a workaround we use readable() (which uses poll or select()) + * instead of peek_fd() (which uses ioctl FIONREAD). Workaround added + * 20020604. + */ + if (PUSH_BACK_PEEK(state) == 0 && vstream_peek(state->stream) <= 0 + && readable(vstream_fileno(state->stream)) <= 0) + return (0); + } + + /* + * Properly terminate the result, and reset the buffer write pointer for + * reading the next command. This is ugly, but not as ugly as trying to + * deal with all the early returns below. + */ + vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2); + VSTRING_TERMINATE(state->buffer); + state->data_state = ST_CR_LF; + VSTRING_RESET(state->buffer); + + /* + * Got a complete command line. Parse it. + */ + ptr = vstring_str(state->buffer); + if (msg_verbose) + msg_info("%s", ptr); + if ((command = mystrtok(&ptr, " \t")) == 0) { + smtp_printf(state->stream, "500 5.5.2 Error: unknown command"); + SMTP_FLUSH(state->stream); + return (0); + } + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(command, cmdp->name) == 0) + break; + if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) { + smtp_printf(state->stream, "500 5.5.1 Error: unknown command"); + SMTP_FLUSH(state->stream); + return (0); + } + return (command_resp(state, cmdp, command, printable(ptr, '?'))); +} + +/* read_timeout - handle timer event */ + +static void read_timeout(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + /* + * We don't send anything to the client, because we would have to set up + * an smtp_stream exception handler first. And that is just too much + * trouble. + */ + msg_warn("read timeout"); + disconnect(state); +} + +/* read_event - handle command or data read events */ + +static void read_event(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + /* + * The input reading routine not only reads input (with vstream calls) + * but also produces output (with smtp_stream calls). Because the output + * routines can raise timeout or EOF exceptions with vstream_longjmp(), + * the input reading routine needs to set up corresponding exception + * handlers with vstream_setjmp(). Guarding the input operations in the + * same manner is not useful: we must read input in non-blocking mode, so + * we never get called when the socket stays unreadable too long. And EOF + * is already trivial to detect with the vstream calls. + */ + do { + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown read/write error"); + /* NOTREACHED */ + + case SMTP_ERR_TIME: + msg_warn("write timeout"); + disconnect(state); + return; + + case SMTP_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case 0: + if (state->read_fn(state) < 0) { + if (msg_verbose) + msg_info("disconnect"); + disconnect(state); + return; + } + } + } while (PUSH_BACK_PEEK(state) != 0 || vstream_peek(state->stream) > 0); + + /* + * Reset the idle timer. Wait until the next input event, or until the + * idle timer goes off. + */ + event_request_timer(read_timeout, (void *) state, var_tmout); +} + +static void connect_event(int, void *); + +/* disconnect - handle disconnection events */ + +static void disconnect(SINK_STATE *state) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_timeout, (void *) state); + if (show_count) { + sess_count++; + do_stats(); + } + vstream_fclose(state->stream); + vstring_free(state->buffer); + /* Clean up file capture attributes. */ + if (state->helo_args) + myfree(state->helo_args); + /* Delete incomplete mail transaction. */ + mail_cmd_reset(state); + if (state->delayed_args) + myfree(state->delayed_args); + myfree((void *) state); + if (max_quit_count > 0 && quit_count >= max_quit_count) + exit(0); + if (client_count-- == max_client_count) + event_enable_read(sock, connect_event, (void *) 0); +} + +/* connect_event - handle connection events */ + +static void connect_event(int unused_event, void *unused_context) +{ + struct sockaddr_storage ss; + SOCKADDR_SIZE len = sizeof(ss); + struct sockaddr *sa = (struct sockaddr *) &ss; + SINK_STATE *state; + int fd; + + if ((fd = sane_accept(sock, sa, &len)) >= 0) { + /* Safety: limit the number of open sockets and capture files. */ + if (++client_count == max_client_count) + event_disable_readwrite(sock); + state = (SINK_STATE *) mymalloc(sizeof(*state)); + if (strchr((char *) proto_info->sa_family_list, sa->sa_family)) + SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr, + (MAI_SERVPORT_STR *) 0, sa->sa_family); + else + strncpy(state->client_addr.buf, "local", sizeof("local")); + if (msg_verbose) + msg_info("connect (%s %s)", +#ifdef AF_LOCAL + sa->sa_family == AF_LOCAL ? "AF_LOCAL" : +#else + sa->sa_family == AF_UNIX ? "AF_UNIX" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + "unknown protocol family", + state->client_addr.buf); + non_blocking(fd, NON_BLOCKING); + state->stream = vstream_fdopen(fd, O_RDWR); + vstream_tweak_sock(state->stream); + state->buffer = vstring_alloc(1024); + state->read_fn = command_read; + state->data_state = ST_ANY; + PUSH_BACK_SET(state, ""); + smtp_timeout_setup(state->stream, var_tmout); + state->in_mail = 0; + state->rcpts = 0; + state->delayed_response = 0; + state->delayed_args = 0; + /* Initialize file capture attributes. */ +#ifdef AF_INET6 + if (sa->sa_family == AF_INET6) + state->addr_prefix = "ipv6:"; + else +#endif + state->addr_prefix = ""; + + state->helo_args = 0; + state->client_proto = enable_lmtp ? "LMTP" : "SMTP"; + state->start_time = 0; + state->id = 0; + state->dump_file = 0; + + /* + * We use the smtp_stream module to produce output. That module + * throws an exception via vstream_longjmp() in case of a timeout or + * lost connection error. Therefore we must prepare to handle these + * exceptions with vstream_setjmp(). + */ + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown read/write error"); + /* NOTREACHED */ + + case SMTP_ERR_TIME: + msg_warn("write timeout"); + disconnect(state); + return; + + case SMTP_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case 0: + if (command_resp(state, command_table, "connect", "") < 0) + disconnect(state); + else if (command_table->delay == 0) { + event_enable_read(fd, read_event, (void *) state); + event_request_timer(read_timeout, (void *) state, var_tmout); + } + } + } +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s [-468acCeEFLpPv] [-A abort_delay] [-b soft_bounce_reply] [-B hard_bounce_reply] [-d dump-template] [-D dump-template] [-f commands] [-h hostname] [-m max_concurrency] [-M message_quit_count] [-n quit_count] [-q commands] [-r commands] [-R root-dir] [-s commands] [-S start-string] [-u user_privs] [-w delay] [host]:port backlog", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + int backlog; + int ch; + int delay; + const char *protocols = INET_PROTO_NAME_ALL; + const char *root_dir = 0; + const char *user_privs = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Fix 20051207. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Initialize diagnostics. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "468aA:b:B:cCd:D:eEf:Fh:H:Ln:m:M:NpPq:Q:r:R:s:S:t:T:u:vw:W:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case '8': + disable_8bitmime = 1; + break; + case 'a': + disable_saslauth = 1; + break; + case 'A': + if (!alldig(optarg) || (abort_delay = atoi(optarg)) < 0) + usage(argv[0]); + break; + case 'b': + if (optarg[0] != '4' || strspn(optarg, "0123456789") != 3) { + msg_error("bad soft error reply: %s", optarg); + usage(argv[0]); + } else + soft_error_resp = optarg; + break; + case 'B': + if (optarg[0] != '5' || strspn(optarg, "0123456789") != 3) { + msg_error("bad hard error reply: %s", optarg); + usage(argv[0]); + } else + hard_error_resp = optarg; + break; + case 'c': + show_count++; + break; + case 'C': + disable_xclient = 1; + reset_cmd_flags("xclient", FLAG_ENABLE); + break; + case 'd': + single_template = optarg; + break; + case 'D': + shared_template = optarg; + break; + case 'e': + disable_esmtp = 1; + break; + case 'E': + disable_enh_status = 1; + break; + case 'f': + set_cmds_flags(optarg, FLAG_HARD_ERR); + disable_pipelining = 1; + break; + case 'F': + disable_xforward = 1; + reset_cmd_flags("xforward", FLAG_ENABLE); + break; + case 'h': + var_myhostname = optarg; + break; + case 'H': + if ((data_read_delay = atoi(optarg)) <= 0) + msg_fatal("bad data read delay: %s", optarg); + break; + case 'L': + enable_lmtp = 1; + break; + case 'm': + if ((max_client_count = atoi(optarg)) <= 0) + msg_fatal("bad concurrency limit: %s", optarg); + break; + case 'M': + if ((max_msg_quit_count = atoi(optarg)) <= 0) + msg_fatal("bad message quit count: %s", optarg); + break; + case 'n': + if ((max_quit_count = atoi(optarg)) <= 0) + msg_fatal("bad quit count: %s", optarg); + break; + case 'N': + disable_dsn = 1; + break; + case 'p': + disable_pipelining = 1; + break; + case 'P': + pretend_pix = 1; + disable_esmtp = 1; + break; + case 'q': + set_cmds_flags(optarg, FLAG_DISCONNECT); + break; + case 'Q': + set_cmds_flags(optarg, FLAG_CLOSE); + break; + case 'r': + set_cmds_flags(optarg, FLAG_SOFT_ERR); + disable_pipelining = 1; + break; + case 'R': + root_dir = optarg; + break; + case 's': + openlog(basename(argv[0]), LOG_PID, LOG_MAIL); + set_cmds_flags(optarg, FLAG_SYSLOG); + break; + case 'S': + start_string = vstring_alloc(10); + unescape(start_string, optarg); + break; + case 't': + if ((var_tmout = atoi(optarg)) <= 0) + msg_fatal("bad timeout: %s", optarg); + break; + case 'T': + if ((inet_windowsize = atoi(optarg)) <= 0) + msg_fatal("bad TCP window size: %s", optarg); + break; + case 'u': + user_privs = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'w': + if ((delay = atoi(optarg)) <= 0) + usage(argv[0]); + set_cmd_delay("data", delay, 0); + break; + case 'W': + set_cmd_delay_arg(optarg); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 2) + usage(argv[0]); + if ((backlog = atoi(argv[optind + 1])) <= 0) + usage(argv[0]); + if (single_template && shared_template) + msg_fatal("use only one of -d or -D, but not both"); + if (geteuid() == 0 && user_privs == 0) + msg_fatal("-u option is required if running as root"); + + /* + * Initialize. + */ + if (var_myhostname == 0) + var_myhostname = "smtp-sink"; + set_cmds_flags(enable_lmtp ? "lhlo" : + disable_esmtp ? "helo" : + "helo, ehlo", FLAG_ENABLE); + proto_info = inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + sock = inet_listen(argv[optind], backlog, BLOCKING); + } + if (user_privs) + chroot_uid(root_dir, user_privs); + + if (single_template) + mysrand((int) time((time_t *) 0)); + else if (shared_template) + single_template = shared_template; + + /* + * Start the event handler. + */ + event_enable_read(sock, connect_event, (void *) 0); + for (;;) + event_loop(-1); +} diff --git a/src/smtpstone/smtp-source.c b/src/smtpstone/smtp-source.c new file mode 100644 index 0000000..be388d6 --- /dev/null +++ b/src/smtpstone/smtp-source.c @@ -0,0 +1,1188 @@ +/*++ +/* NAME +/* smtp-source 1 +/* SUMMARY +/* parallelized SMTP/LMTP test generator +/* SYNOPSIS +/* .fi +/* \fBsmtp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* +/* \fBsmtp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR +/* DESCRIPTION +/* \fBsmtp-source\fR connects to the named \fIhost\fR and TCP \fIport\fR +/* (default: port 25) +/* and sends one or more messages to it, either sequentially +/* or in parallel. The program speaks either SMTP (default) or +/* LMTP. +/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers. +/* IPv4 and IPv6 are the default. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Connect to the server with IPv4. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Connect to the server with IPv6. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP "\fB-A\fR" +/* Don't abort when the server sends something other than the +/* expected positive reply code. +/* .IP \fB-c\fR +/* Display a running counter that is incremented each time +/* an SMTP DATA command completes. +/* .IP "\fB-C \fIcount\fR" +/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times +/* before giving up. The default count is 1. Specify a larger count in +/* order to work around a problem with TCP/IP stacks that send RESET +/* when the listen queue is full. +/* .IP \fB-d\fR +/* Don't disconnect after sending a message; send the next +/* message over the same connection. +/* .IP "\fB-f \fIfrom\fR" +/* Use the specified sender address (default: <foo@myhostname>). +/* .IP "\fB-F \fIfile\fR" +/* Send the pre-formatted message header and body in the +/* specified \fIfile\fR, while prepending '.' before lines that +/* begin with '.', and while appending CRLF after each line. +/* .IP "\fB-l \fIlength\fR" +/* Send \fIlength\fR bytes as message payload. The length does not +/* include message headers. +/* .IP \fB-L\fR +/* Speak LMTP rather than SMTP. +/* .IP "\fB-m \fImessage_count\fR" +/* Send the specified number of messages (default: 1). +/* .IP "\fB-M \fImyhostname\fR" +/* Use the specified hostname or [address] in the HELO command +/* and in the default sender and recipient addresses, instead +/* of the machine hostname. +/* .IP "\fB-N\fR" +/* Prepend a non-repeating sequence number to each recipient +/* address. This avoids the artificial 100% hit rate in the +/* resolve and rewrite client caches and exercises the +/* trivial-rewrite daemon, better approximating Postfix +/* performance under real-life work-loads. +/* .IP \fB-o\fR +/* Old mode: don't send HELO, and don't send message headers. +/* .IP "\fB-r \fIrecipient_count\fR" +/* Send the specified number of recipients per transaction (default: 1). +/* Recipient names are generated by prepending a number to the +/* recipient address. +/* .IP "\fB-R \fIinterval\fR" +/* Wait for a random period of time 0 <= n <= interval between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP "\fB-s \fIsession_count\fR" +/* Run the specified number of SMTP sessions in parallel (default: 1). +/* .IP "\fB-S \fIsubject\fR" +/* Send mail with the named subject line (default: none). +/* .IP "\fB-t \fIto\fR" +/* Use the specified recipient address (default: <foo@myhostname>). +/* .IP "\fB-T \fIwindowsize\fR" +/* Override the default TCP window size. To work around +/* broken TCP window scaling implementations, specify a +/* value > 0 and < 65536. +/* .IP \fB-v\fR +/* Make the program more verbose, for debugging purposes. +/* .IP "\fB-w \fIinterval\fR" +/* Wait a fixed time between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* Connect via TCP to host \fIhost\fR, port \fIport\fR. The default +/* port is \fBsmtp\fR. +/* .IP \fBunix:\fIpathname\fR +/* Connect to the UNIX-domain socket at \fIpathname\fR. +/* BUGS +/* No SMTP command pipelining support. +/* SEE ALSO +/* smtp-sink(1), SMTP/LMTP message dump +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <msg_vstream.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <get_hostname.h> +#include <split_at.h> +#include <connect.h> +#include <mymalloc.h> +#include <events.h> +#include <iostuff.h> +#include <sane_connect.h> +#include <host_port.h> +#include <myaddrinfo.h> +#include <inet_proto.h> +#include <valid_hostname.h> +#include <valid_mailhost_addr.h> +#include <compat_va_copy.h> + +/* Global library. */ + +#include <smtp_stream.h> +#include <mail_date.h> +#include <mail_version.h> + +/* Application-specific. */ + + /* + * Per-session data structure with state. + * + * This software can maintain multiple parallel connections to the same SMTP + * server. However, it makes no more than one connection request at a time + * to avoid overwhelming the server with SYN packets and having to back off. + * Back-off would screw up the benchmark. Pending connection requests are + * kept in a linear list. + */ +typedef struct SESSION { + int xfer_count; /* # of xfers in session */ + int rcpt_done; /* # of recipients done */ + int rcpt_count; /* # of recipients to go */ + int rcpt_accepted; /* # of recipients accepted */ + VSTREAM *stream; /* open connection */ + int connect_count; /* # of connect()s to retry */ + struct SESSION *next; /* connect() queue linkage */ +} SESSION; + +static SESSION *last_session; /* connect() queue tail */ + + /* + * Structure with broken-up SMTP server response. + */ +typedef struct { /* server response */ + int code; /* status */ + char *str; /* text */ + VSTRING *buf; /* origin of text */ +} RESPONSE; + +static VSTRING *buffer; +static int var_line_limit = 10240; +static int var_timeout = 300; +static const char *var_myhostname; +static int session_count; +static int message_count = 1; +static struct sockaddr_storage ss; + +#undef sun +static struct sockaddr_un sun; +static struct sockaddr *sa; +static int sa_length; +static int recipients = 1; +static char *defaddr; +static char *recipient; +static char *sender; +static char *message_data; +static int message_length; +static int disconnect = 1; +static int count = 0; +static int counter = 0; +static int send_helo_first = 1; +static int send_headers = 1; +static int connect_count = 1; +static int random_delay = 0; +static int fixed_delay = 0; +static int talk_lmtp = 0; +static char *subject = 0; +static int number_rcpts = 0; +static int allow_reject = 0; + +static void enqueue_connect(SESSION *); +static void start_connect(SESSION *); +static void connect_done(int, void *); +static void read_banner(int, void *); +static void send_helo(SESSION *); +static void helo_done(int, void *); +static void send_mail(SESSION *); +static void mail_done(int, void *); +static void send_rcpt(int, void *); +static void rcpt_done(int, void *); +static void send_data(int, void *); +static void data_done(int, void *); +static void dot_done(int, void *); +static void send_rset(int, void *); +static void rset_done(int, void *); +static void send_quit(SESSION *); +static void quit_done(int, void *); +static void close_session(SESSION *); + +/* random_interval - generate a random value in 0 .. (small) interval */ + +static int random_interval(int interval) +{ + return (rand() % (interval + 1)); +} + +/* command - send an SMTP command */ + +static void command(VSTREAM *stream, char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + + /* + * Optionally, log the command before actually sending, so we can see + * what the program is trying to do. + */ + if (msg_verbose) { + va_list ap2; + + VA_COPY(ap2, ap); + vmsg_info(fmt, ap2); + va_end(ap2); + } + smtp_vprintf(stream, fmt, ap); + va_end(ap); + smtp_flush(stream); +} + +/* socket_error - look up and reset the last socket error */ + +static int socket_error(int sock) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Some Solaris 2 versions have getsockopt() itself return the error, + * instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} + +/* response - read and process SMTP server response */ + +static RESPONSE *response(VSTREAM *stream, VSTRING *buf) +{ + static RESPONSE rdata; + int more; + char *cp; + + /* + * Initialize the response data buffer. smtp_get() defends against a + * denial of service attack by limiting the amount of single-line text, + * and the loop below limits the amount of multi-line text that we are + * willing to store. + */ + if (rdata.buf == 0) + rdata.buf = vstring_alloc(100); + + /* + * Censor out non-printable characters in server responses. Concatenate + * multi-line server responses. Separate the status code from the text. + * Leave further parsing up to the application. + */ +#define BUF ((char *) vstring_str(buf)) + VSTRING_RESET(rdata.buf); + for (;;) { + smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP); + for (cp = BUF; *cp != 0; cp++) + if (!ISPRINT(*cp) && !ISSPACE(*cp)) + *cp = '?'; + cp = BUF; + if (msg_verbose) + msg_info("<<< %s", cp); + while (ISDIGIT(*cp)) + cp++; + rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0); + if ((more = (*cp == '-')) != 0) + cp++; + while (ISSPACE(*cp)) + cp++; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + vstring_strcat(rdata.buf, cp); + if (more == 0) + break; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + VSTRING_ADDCH(rdata.buf, '\n'); + } + VSTRING_TERMINATE(rdata.buf); + rdata.str = vstring_str(rdata.buf); + return (&rdata); +} + +/* exception_text - translate exceptions from the smtp_stream module */ + +static char *exception_text(int except) +{ + switch (except) { + case SMTP_ERR_EOF: + return ("lost connection"); + case SMTP_ERR_TIME: + return ("timeout"); + default: + msg_panic("exception_text: unknown exception %d", except); + } + /* NOTREACHED */ +} + +/* startup - connect to server but do not wait */ + +static void startup(SESSION *session) +{ + if (message_count-- <= 0) { + myfree((void *) session); + session_count--; + return; + } + if (session->stream == 0) { + enqueue_connect(session); + } else { + send_mail(session); + } +} + +/* start_event - invoke startup from timer context */ + +static void start_event(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + startup(session); +} + +/* start_another - start another session */ + +static void start_another(SESSION *session) +{ + if (random_delay > 0) { + event_request_timer(start_event, (void *) session, + random_interval(random_delay)); + } else if (fixed_delay > 0) { + event_request_timer(start_event, (void *) session, fixed_delay); + } else { + startup(session); + } +} + +/* enqueue_connect - queue a connection request */ + +static void enqueue_connect(SESSION *session) +{ + session->next = 0; + if (last_session == 0) { + last_session = session; + start_connect(session); + } else { + last_session->next = session; + last_session = session; + } +} + +/* dequeue_connect - connection request completed */ + +static void dequeue_connect(SESSION *session) +{ + if (session == last_session) { + if (session->next != 0) + msg_panic("dequeue_connect: queue ends after last"); + last_session = 0; + } else { + if (session->next == 0) + msg_panic("dequeue_connect: queue ends before last"); + start_connect(session->next); + } +} + +/* fail_connect - handle failed startup */ + +static void fail_connect(SESSION *session) +{ + if (session->connect_count-- == 1) + msg_fatal("connect: %m"); + msg_warn("connect: %m"); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; +#ifdef MISSING_USLEEP + doze(10); +#else + usleep(10); +#endif + start_connect(session); +} + +/* start_connect - start TCP handshake */ + +static void start_connect(SESSION *session) +{ + int fd; + struct linger linger; + + /* + * Some systems don't set the socket error when connect() fails early + * (loopback) so we must deal with the error immediately, rather than + * retrieving it later with getsockopt(). We can't use MSG_PEEK to + * distinguish between server disconnect and connection refused. + */ + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + (void) non_blocking(fd, NON_BLOCKING); + linger.l_onoff = 1; + linger.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger, + sizeof(linger)) < 0) + msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); + session->stream = vstream_fdopen(fd, O_RDWR); + event_enable_write(fd, connect_done, (void *) session); + smtp_timeout_setup(session->stream, var_timeout); + if (inet_windowsize > 0) + set_inet_windowsize(fd, inet_windowsize); + if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS) + fail_connect(session); +} + +/* connect_done - send message sender info */ + +static void connect_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int fd = vstream_fileno(session->stream); + + /* + * Try again after some delay when the connection failed, in case they + * run a Mickey Mouse protocol stack. + */ + if (socket_error(fd) < 0) { + fail_connect(session); + } else { + non_blocking(fd, BLOCKING); + /* Disable write events. */ + event_disable_readwrite(fd); + event_enable_read(fd, read_banner, (void *) session); + dequeue_connect(session); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(session->stream); + } +} + +/* read_banner - receive SMTP server greeting */ + +static void read_banner(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while reading server greeting", exception_text(except)); + + /* + * Read and parse the server's SMTP greeting banner. + */ + if (((resp = response(session->stream, buffer))->code / 100) == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("rejected at server banner: %d %s", resp->code, resp->str); + } else { + msg_fatal("rejected at server banner: %d %s", resp->code, resp->str); + } + + /* + * Send helo or send the envelope sender address. + */ + if (send_helo_first) + send_helo(session); + else + send_mail(session); +} + +/* send_helo - send hostname */ + +static void send_helo(SESSION *session) +{ + int except; + const char *NOCLOBBER protocol = (talk_lmtp ? "LHLO" : "HELO"); + + /* + * Send the standard greeting with our hostname + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending %s", exception_text(except), protocol); + + command(session->stream, "%s %s", protocol, var_myhostname); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), helo_done, (void *) session); +} + +/* helo_done - handle HELO response */ + +static void helo_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + const char *protocol = (talk_lmtp ? "LHLO" : "HELO"); + + /* + * Get response to HELO command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending %s", exception_text(except), protocol); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("%s rejected: %d %s", protocol, resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("%s rejected: %d %s", protocol, resp->code, resp->str); + } + + send_mail(session); +} + +/* send_mail - send envelope sender */ + +static void send_mail(SESSION *session) +{ + int except; + + /* + * Send the envelope sender address. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending sender", exception_text(except)); + + command(session->stream, "MAIL FROM:<%s>", sender); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), mail_done, (void *) session); +} + +/* mail_done - handle MAIL response */ + +static void mail_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to MAIL command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending sender", exception_text(except)); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + session->rcpt_count = recipients; + session->rcpt_done = 0; + session->rcpt_accepted = 0; + send_rcpt(unused, context); + } else if (allow_reject) { + msg_warn("sender rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + send_rset(unused, context); + } else { + msg_fatal("sender rejected: %d %s", resp->code, resp->str); + } +} + +/* send_rcpt - send recipient address */ + +static void send_rcpt(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Send envelope recipient address. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending recipient", exception_text(except)); + + if (session->rcpt_count > 1 || number_rcpts > 0) + command(session->stream, "RCPT TO:<%d%s>", + number_rcpts ? number_rcpts++ : session->rcpt_count, + recipient); + else + command(session->stream, "RCPT TO:<%s>", recipient); + session->rcpt_count--; + session->rcpt_done++; + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), rcpt_done, (void *) session); +} + +/* rcpt_done - handle RCPT completion */ + +static void rcpt_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to RCPT command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending recipient", exception_text(except)); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + session->rcpt_accepted++; + } else if (allow_reject) { + msg_warn("recipient rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("recipient rejected: %d %s", resp->code, resp->str); + } + + /* + * Send another RCPT command or send DATA. + */ + if (session->rcpt_count > 0) + send_rcpt(unused, context); + else if (session->rcpt_accepted > 0) + send_data(unused, context); + else + send_rset(unused, context); +} + +/* send_data - send DATA command */ + +static void send_data(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Request data transmission. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending DATA command", exception_text(except)); + command(session->stream, "DATA"); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), data_done, (void *) session); +} + +/* data_done - send message content */ + +static void data_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + static const char *mydate; + static int mypid; + + /* + * Get response to DATA command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending DATA command", exception_text(except)); + if ((resp = response(session->stream, buffer))->code == 354) { + /* see below */ ; + } else if (allow_reject) { + msg_warn("data rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + send_rset(unused, context); + return; + } else { + msg_fatal("data rejected: %d %s", resp->code, resp->str); + } + + /* + * Send basic header to keep mailers that bother to examine them happy. + */ + if (send_headers) { + if (mydate == 0) { + mydate = mail_date(time((time_t *) 0)); + mypid = getpid(); + } + smtp_printf(session->stream, "From: <%s>", sender); + smtp_printf(session->stream, "To: <%s>", recipient); + smtp_printf(session->stream, "Date: %s", mydate); + smtp_printf(session->stream, "Message-Id: <%04x.%04x.%04x@%s>", + mypid, vstream_fileno(session->stream), message_count, var_myhostname); + if (subject) + smtp_printf(session->stream, "Subject: %s", subject); + smtp_fputs("", 0, session->stream); + } + + /* + * Send some garbage. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + if (message_length == 0) { + smtp_fputs("La de da de da 1.", 17, session->stream); + smtp_fputs("La de da de da 2.", 17, session->stream); + smtp_fputs("La de da de da 3.", 17, session->stream); + smtp_fputs("La de da de da 4.", 17, session->stream); + } else { + + /* + * XXX This may cause the process to block with message content + * larger than VSTREAM_BUFIZ bytes. + */ + smtp_fputs(message_data, message_length, session->stream); + } + + /* + * Send end of message and process the server response. + */ + command(session->stream, "."); + + /* + * Update the running counter. + */ + if (count) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), dot_done, (void *) session); +} + +/* dot_done - send QUIT or start another transaction */ + +static void dot_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to "." command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + do { /* XXX this could block */ + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("end of data rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("end of data rejected: %d %s", resp->code, resp->str); + } + } while (talk_lmtp && --session->rcpt_done > 0); + session->xfer_count++; + + /* + * Say goodbye or send the next message. + */ + if (disconnect || message_count < 1) { + send_quit(session); + } else { + event_disable_readwrite(vstream_fileno(session->stream)); + start_another(session); + } +} + +/* send_rset - send RSET command */ + +static void send_rset(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + command(session->stream, "RSET"); + event_enable_read(vstream_fileno(session->stream), rset_done, (void *) session); +} + +/* rset_done - handle RSET reply */ + +static void rset_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to RSET command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ + } else if (allow_reject) { + msg_warn("rset rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("rset rejected: %d %s", resp->code, resp->str); + } + + /* + * Say goodbye or send the next message. + */ + if (disconnect || message_count < 1) { + send_quit(session); + } else { + event_disable_readwrite(vstream_fileno(session->stream)); + start_another(session); + } +} + +/* send_quit - send QUIT command */ + +static void send_quit(SESSION *session) +{ + command(session->stream, "QUIT"); + event_enable_read(vstream_fileno(session->stream), quit_done, (void *) session); +} + +/* quit_done - disconnect */ + +static void quit_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + (void) response(session->stream, buffer); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* close_session - disconnect, for example after 421 or 521 reply */ + +static void close_session(SESSION *session) +{ + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s -cdLNov -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -r rcptcount -R delay -w delay host[:port]", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - parse JCL and start the machine */ + +int main(int argc, char **argv) +{ + SESSION *session; + char *host; + char *port; + char *path; + int path_len; + int sessions = 1; + int ch; + int i; + char *buf; + const char *parse_err; + struct addrinfo *res; + int aierr; + const char *protocols = INET_PROTO_NAME_ALL; + char *message_file = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + signal(SIGPIPE, SIG_IGN); + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'A': + allow_reject = 1; + break; + case 'c': + count++; + break; + case 'C': + if ((connect_count = atoi(optarg)) <= 0) + msg_fatal("bad connection count: %s", optarg); + break; + case 'd': + disconnect = 0; + break; + case 'f': + sender = optarg; + break; + case 'F': + if (message_file == 0 && message_length > 0) + msg_fatal("-l option cannot be used with -F"); + message_file = optarg; + break; + case 'l': + if (message_file != 0) + msg_fatal("-l option cannot be used with -F"); + if ((message_length = atoi(optarg)) <= 0) + msg_fatal("bad message length: %s", optarg); + break; + case 'L': + talk_lmtp = 1; + break; + case 'm': + if ((message_count = atoi(optarg)) <= 0) + msg_fatal("bad message count: %s", optarg); + break; + case 'M': + if (*optarg == '[') { + if (!valid_mailhost_literal(optarg, DO_GRIPE)) + msg_fatal("bad address literal: %s", optarg); + } else { + if (!valid_hostname(optarg, DO_GRIPE)) + msg_fatal("bad hostname: %s", optarg); + } + var_myhostname = optarg; + break; + case 'N': + number_rcpts = 1; + break; + case 'o': + send_helo_first = 0; + send_headers = 0; + break; + case 'r': + if ((recipients = atoi(optarg)) <= 0) + msg_fatal("bad recipient count: %s", optarg); + break; + case 'R': + if (fixed_delay > 0) + msg_fatal("do not use -w and -R options at the same time"); + if ((random_delay = atoi(optarg)) <= 0) + msg_fatal("bad random delay: %s", optarg); + break; + case 's': + if ((sessions = atoi(optarg)) <= 0) + msg_fatal("bad session count: %s", optarg); + break; + case 'S': + subject = optarg; + break; + case 't': + recipient = optarg; + break; + case 'T': + if ((inet_windowsize = atoi(optarg)) <= 0) + msg_fatal("bad TCP window size: %s", optarg); + break; + case 'v': + msg_verbose++; + break; + case 'w': + if (random_delay > 0) + msg_fatal("do not use -w and -R options at the same time"); + if ((fixed_delay = atoi(optarg)) <= 0) + msg_fatal("bad fixed delay: %s", optarg); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 1) + usage(argv[0]); + + if (random_delay > 0) + srand(getpid()); + + /* + * Initialize the message content, SMTP encoded. smtp_fputs() will append + * another \r\n but we don't care. + */ + if (message_file != 0) { + VSTREAM *fp; + VSTRING *buf = vstring_alloc(100); + VSTRING *msg = vstring_alloc(100); + + if ((fp = vstream_fopen(message_file, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", message_file); + while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) { + if (*vstring_str(buf) == '.') + VSTRING_ADDCH(msg, '.'); + vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf)); + vstring_memcat(msg, "\r\n", 2); + } + if (vstream_ferror(fp)) + msg_fatal("read %s: %m", message_file); + vstream_fclose(fp); + vstring_free(buf); + message_length = VSTRING_LEN(msg); + message_data = vstring_export(msg); + send_headers = 0; + } else if (message_length > 0) { + message_data = mymalloc(message_length); + memset(message_data, 'X', message_length); + for (i = 80; i < message_length; i += 80) { + message_data[i - 80] = "0123456789"[(i / 80) % 10]; + message_data[i - 2] = '\r'; + message_data[i - 1] = '\n'; + } + } + + /* + * Translate endpoint address to internal form. + */ + (void) inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + path = argv[optind] + 5; + path_len = strlen(path); + if (path_len >= (int) sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len); + sa = (struct sockaddr *) &sun; + sa_length = sizeof(sun); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + buf = mystrdup(argv[optind]); + if ((parse_err = host_port(buf, &host, (char *) 0, &port, "smtp")) != 0) + msg_fatal("%s: %s", argv[optind], parse_err); + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) + msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); + myfree(buf); + sa = (struct sockaddr *) &ss; + if (res->ai_addrlen > sizeof(ss)) + msg_fatal("address length %d > buffer length %d", + (int) res->ai_addrlen, (int) sizeof(ss)); + memcpy((void *) sa, res->ai_addr, res->ai_addrlen); + sa_length = res->ai_addrlen; +#ifdef HAS_SA_LEN + sa->sa_len = sa_length; +#endif + freeaddrinfo(res); + } + + /* + * smtp_get() makes sure the SMTP server cannot run us out of memory by + * sending never-ending lines of text. + */ + if (buffer == 0) + buffer = vstring_alloc(100); + + /* + * Make sure we have sender and recipient addresses. + */ + if (var_myhostname == 0) + var_myhostname = get_hostname(); + if (sender == 0 || recipient == 0) { + vstring_sprintf(buffer, "foo@%s", var_myhostname); + defaddr = mystrdup(vstring_str(buffer)); + if (sender == 0) + sender = defaddr; + if (recipient == 0) + recipient = defaddr; + } + + /* + * Start sessions. + */ + while (sessions-- > 0) { + session = (SESSION *) mymalloc(sizeof(*session)); + session->stream = 0; + session->xfer_count = 0; + session->connect_count = connect_count; + session->next = 0; + session_count++; + startup(session); + } + for (;;) { + event_loop(-1); + if (session_count <= 0 && message_count <= 0) { + if (count) { + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } + exit(0); + } + } +} diff --git a/src/smtpstone/throughput b/src/smtpstone/throughput new file mode 100644 index 0000000..4853d75 --- /dev/null +++ b/src/smtpstone/throughput @@ -0,0 +1,28 @@ +Host: P233 BSD/OS 3.1 smtp-source and smtp-sink on the same host, +100 msgs in 10 sessions. + +send = time to send 100 msgs into postfix +rest = time for Postfix to finish +total = total elapsed time + +19990627 + +send rest total +14 10 25 +10 8 18 + 9 10 19 + 9 17 26 + 8 11 19 + 8 9 17 + +19990906 + +send rest total +10 15 25 + 9 10 19 + 8 9 17 + 9 8 17 + 8 9 17 + 9 8 17 + 8 9 17 + 8 8 16 diff --git a/src/smtpstone/vmail-local b/src/smtpstone/vmail-local new file mode 100644 index 0000000..84269b2 --- /dev/null +++ b/src/smtpstone/vmail-local @@ -0,0 +1,43 @@ +fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple +Sat May 9 22:08:27 EDT 1998 + 6.29 real 0.08 user 0.17 sys +May 9 22:08:27 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu +May 9 22:08:58 nipple postfix/local[2330]: 5082D53AD0: to=<wietse@porcupine.org +Elapsed 31 + +fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple +Sat May 9 22:09:13 EDT 1998 + 5.94 real 0.09 user 0.16 sys +May 9 22:09:13 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu +May 9 22:09:40 nipple postfix/local[2260]: 5082FBE00B: to=<wietse@porcupine.org +Elapsed 27 + +fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple +Sat May 9 22:09:43 EDT 1998 + 5.97 real 0.06 user 0.17 sys +May 9 22:09:43 nipple postfix/local[2321]: 5082C7E2F3: to=<wietse@porcupine.org +May 9 22:10:13 nipple postfix/local[2517]: 5082C8772C: to=<wietse@porcupine.org +Elapsed 30 + +fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple +Sat May 9 22:10:30 EDT 1998 + 6.26 real 0.08 user 0.20 sys +May 9 22:10:30 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu +May 9 22:10:58 nipple postfix/local[2330]: 5082C7A2C9: to=<wietse@porcupine.org +Elapsed 28 + +fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple +Sat May 9 22:11:06 EDT 1998 + 6.04 real 0.05 user 0.25 sys +May 9 22:11:06 nipple wietse[100]: postfix/smtpd[2675]: connect from fist.porcu +May 9 22:11:33 nipple postfix/local[2685]: 5082D23842: to=<wietse@porcupine.org +Elapsed 27 + +fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple +Sat May 9 22:11:38 EDT 1998 + 6.11 real 0.06 user 0.24 sys +May 9 22:11:38 nipple postfix/local[2686]: 5082E318AD: to=<wietse@porcupine.org +May 9 22:12:07 nipple postfix/local[2687]: 5082CBF5EC: to=<wietse@porcupine.org +Elapsed 31 + +Elapsed: 29, throughput: 3.4 msg/s (local_delivery_concurrency not set) diff --git a/src/smtpstone/vmail-relay b/src/smtpstone/vmail-relay new file mode 100644 index 0000000..e9055b5 --- /dev/null +++ b/src/smtpstone/vmail-relay @@ -0,0 +1,57 @@ +fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple +Sat May 9 21:54:48 EDT 1998 + 7.09 real 0.03 user 0.18 sys +May 9 21:54:48 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu +May 9 21:55:05 nipple wietse[100]: postfix/smtp[2215]: 508672B24D: to=<foo@fist +Elapsed 17 + +fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple +Sat May 9 21:55:09 EDT 1998 + 6.02 real 0.09 user 0.13 sys +May 9 21:55:09 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu +May 9 21:55:23 nipple wietse[100]: postfix/smtp[2217]: 50864BCA59: to=<foo@fist +Elapsed 14 + +fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple +Sat May 9 21:55:26 EDT 1998 + 6.06 real 0.07 user 0.17 sys +May 9 21:55:27 nipple wietse[100]: postfix/smtpd[2205]: connect from fist.porcu +May 9 21:55:41 nipple wietse[100]: postfix/smtp[2218]: 508753CB5D: to=<foo@fist +Elapsed 14 + +fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple +Sat May 9 21:55:53 EDT 1998 + 6.01 real 0.13 user 0.14 sys +May 9 21:55:53 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu +May 9 21:56:08 nipple wietse[100]: postfix/smtp[2216]: 50868D8D44: to=<foo@fist +Elapsed 15 + +fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple +Sat May 9 21:56:10 EDT 1998 + 6.01 real 0.14 user 0.13 sys +May 9 21:56:10 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu +May 9 21:56:24 nipple wietse[100]: postfix/smtp[2229]: 50875825BA: to=<foo@fist +Elapsed 14 + +fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple +Sat May 9 21:56:28 EDT 1998 + 6.10 real 0.03 user 0.23 sys +May 9 21:56:28 nipple wietse[100]: postfix/smtpd[2205]: connect from fist.porcu +May 9 21:56:43 nipple wietse[100]: postfix/smtp[2229]: 508651D724: to=<foo@fist +Elapsed 15 + +fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 nipple +Sat May 9 21:56:58 EDT 1998 + 6.02 real 0.10 user 0.21 sys +May 9 21:56:58 nipple wietse[100]: postfix/smtpd[2222]: connect from fist.porcu +May 9 21:57:13 nipple wietse[100]: postfix/smtp[2231]: 5085D279A7: to=<foo@fist +Elapsed 15 + +fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 nipple +Sat May 9 21:57:18 EDT 1998 + 6.04 real 0.09 user 0.19 sys +May 9 21:57:18 nipple wietse[100]: postfix/smtpd[2222]: connect from fist.porcu +May 9 21:57:32 nipple wietse[100]: postfix/smtp[2230]: 508774A28A: to=<foo@fist +Elapsed 14 + +Elapsed: 14.4 s, throughput: 6.9 msg/s |