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.tar.xz postfix-upstream.zip |
Adding upstream version 3.4.23.upstream/3.4.23upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/smtp')
32 files changed, 12591 insertions, 0 deletions
diff --git a/src/smtp/.indent.pro b/src/smtp/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/smtp/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/smtp/.printfck b/src/smtp/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/smtp/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/smtp/Makefile.in b/src/smtp/Makefile.in new file mode 100644 index 0000000..7d45564 --- /dev/null +++ b/src/smtp/Makefile.in @@ -0,0 +1,831 @@ +SHELL = /bin/sh +SRCS = smtp.c smtp_connect.c smtp_proto.c smtp_chat.c smtp_session.c \ + smtp_addr.c smtp_trouble.c smtp_state.c smtp_rcpt.c smtp_tls_policy.c \ + smtp_sasl_proto.c smtp_sasl_glue.c smtp_reuse.c smtp_map11.c \ + smtp_sasl_auth_cache.c smtp_key.c +OBJS = smtp.o smtp_connect.o smtp_proto.o smtp_chat.o smtp_session.o \ + smtp_addr.o smtp_trouble.o smtp_state.o smtp_rcpt.o smtp_tls_policy.o \ + smtp_sasl_proto.o smtp_sasl_glue.o smtp_reuse.o smtp_map11.o \ + smtp_sasl_auth_cache.o smtp_key.o +HDRS = smtp.h smtp_sasl.h smtp_addr.h smtp_reuse.h smtp_sasl_auth_cache.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= smtp_unalias smtp_map11 +PROG = smtp +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/libxsasl.a \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: smtp_map11_test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +smtp.o: smtp.c smtp_params.c lmtp_params.c + +lmtp_params.c: smtp_params.c + egrep -v -f smtp-only smtp_params.c | \ + sed 's/SMTP/LMTP/g; s/smtp_\([a-z]*_table\)/lmtp_\1/' >$@ + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +smtp_unalias: smtp_unalias.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +smtp_map11: smtp_map11.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +smtp_map11_test: smtp_map11 smtp_map11.ref + $(SHLIB_ENV) $(VALGRIND) ./smtp_map11 <smtp_map11.in >smtp_map11.tmp 2>&1 + diff smtp_map11.ref smtp_map11.tmp + rm -f smtp_map11.tmp + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +lmtp_params.o: lmtp_params.c +smtp.o: ../../include/argv.h +smtp.o: ../../include/attr.h +smtp.o: ../../include/check_arg.h +smtp.o: ../../include/debug_peer.h +smtp.o: ../../include/deliver_request.h +smtp.o: ../../include/dict.h +smtp.o: ../../include/dns.h +smtp.o: ../../include/dsn.h +smtp.o: ../../include/dsn_buf.h +smtp.o: ../../include/ext_prop.h +smtp.o: ../../include/flush_clnt.h +smtp.o: ../../include/header_body_checks.h +smtp.o: ../../include/header_opts.h +smtp.o: ../../include/htable.h +smtp.o: ../../include/iostuff.h +smtp.o: ../../include/mail_conf.h +smtp.o: ../../include/mail_params.h +smtp.o: ../../include/mail_proto.h +smtp.o: ../../include/mail_server.h +smtp.o: ../../include/mail_version.h +smtp.o: ../../include/maps.h +smtp.o: ../../include/match_list.h +smtp.o: ../../include/mime_state.h +smtp.o: ../../include/msg.h +smtp.o: ../../include/msg_stats.h +smtp.o: ../../include/myaddrinfo.h +smtp.o: ../../include/myflock.h +smtp.o: ../../include/mymalloc.h +smtp.o: ../../include/name_code.h +smtp.o: ../../include/name_mask.h +smtp.o: ../../include/nvtable.h +smtp.o: ../../include/recipient_list.h +smtp.o: ../../include/resolve_clnt.h +smtp.o: ../../include/scache.h +smtp.o: ../../include/sock_addr.h +smtp.o: ../../include/string_list.h +smtp.o: ../../include/stringops.h +smtp.o: ../../include/sys_defs.h +smtp.o: ../../include/tls.h +smtp.o: ../../include/tls_proxy.h +smtp.o: ../../include/tok822.h +smtp.o: ../../include/vbuf.h +smtp.o: ../../include/vstream.h +smtp.o: ../../include/vstring.h +smtp.o: lmtp_params.c +smtp.o: smtp.c +smtp.o: smtp.h +smtp.o: smtp_params.c +smtp.o: smtp_sasl.h +smtp_addr.o: ../../include/argv.h +smtp_addr.o: ../../include/attr.h +smtp_addr.o: ../../include/check_arg.h +smtp_addr.o: ../../include/deliver_request.h +smtp_addr.o: ../../include/dict.h +smtp_addr.o: ../../include/dns.h +smtp_addr.o: ../../include/dsn.h +smtp_addr.o: ../../include/dsn_buf.h +smtp_addr.o: ../../include/header_body_checks.h +smtp_addr.o: ../../include/header_opts.h +smtp_addr.o: ../../include/htable.h +smtp_addr.o: ../../include/inet_addr_list.h +smtp_addr.o: ../../include/inet_proto.h +smtp_addr.o: ../../include/mail_params.h +smtp_addr.o: ../../include/maps.h +smtp_addr.o: ../../include/match_list.h +smtp_addr.o: ../../include/midna_domain.h +smtp_addr.o: ../../include/mime_state.h +smtp_addr.o: ../../include/msg.h +smtp_addr.o: ../../include/msg_stats.h +smtp_addr.o: ../../include/myaddrinfo.h +smtp_addr.o: ../../include/myflock.h +smtp_addr.o: ../../include/mymalloc.h +smtp_addr.o: ../../include/name_code.h +smtp_addr.o: ../../include/name_mask.h +smtp_addr.o: ../../include/nvtable.h +smtp_addr.o: ../../include/own_inet_addr.h +smtp_addr.o: ../../include/recipient_list.h +smtp_addr.o: ../../include/resolve_clnt.h +smtp_addr.o: ../../include/scache.h +smtp_addr.o: ../../include/sock_addr.h +smtp_addr.o: ../../include/string_list.h +smtp_addr.o: ../../include/stringops.h +smtp_addr.o: ../../include/sys_defs.h +smtp_addr.o: ../../include/tls.h +smtp_addr.o: ../../include/tls_proxy.h +smtp_addr.o: ../../include/tok822.h +smtp_addr.o: ../../include/vbuf.h +smtp_addr.o: ../../include/vstream.h +smtp_addr.o: ../../include/vstring.h +smtp_addr.o: smtp.h +smtp_addr.o: smtp_addr.c +smtp_addr.o: smtp_addr.h +smtp_chat.o: ../../include/argv.h +smtp_chat.o: ../../include/attr.h +smtp_chat.o: ../../include/check_arg.h +smtp_chat.o: ../../include/cleanup_user.h +smtp_chat.o: ../../include/deliver_request.h +smtp_chat.o: ../../include/dict.h +smtp_chat.o: ../../include/dns.h +smtp_chat.o: ../../include/dsn.h +smtp_chat.o: ../../include/dsn_buf.h +smtp_chat.o: ../../include/dsn_util.h +smtp_chat.o: ../../include/header_body_checks.h +smtp_chat.o: ../../include/header_opts.h +smtp_chat.o: ../../include/htable.h +smtp_chat.o: ../../include/int_filt.h +smtp_chat.o: ../../include/iostuff.h +smtp_chat.o: ../../include/line_wrap.h +smtp_chat.o: ../../include/mail_addr.h +smtp_chat.o: ../../include/mail_error.h +smtp_chat.o: ../../include/mail_params.h +smtp_chat.o: ../../include/mail_proto.h +smtp_chat.o: ../../include/maps.h +smtp_chat.o: ../../include/match_list.h +smtp_chat.o: ../../include/mime_state.h +smtp_chat.o: ../../include/msg.h +smtp_chat.o: ../../include/msg_stats.h +smtp_chat.o: ../../include/myaddrinfo.h +smtp_chat.o: ../../include/myflock.h +smtp_chat.o: ../../include/mymalloc.h +smtp_chat.o: ../../include/name_code.h +smtp_chat.o: ../../include/name_mask.h +smtp_chat.o: ../../include/nvtable.h +smtp_chat.o: ../../include/post_mail.h +smtp_chat.o: ../../include/recipient_list.h +smtp_chat.o: ../../include/resolve_clnt.h +smtp_chat.o: ../../include/scache.h +smtp_chat.o: ../../include/smtp_stream.h +smtp_chat.o: ../../include/smtputf8.h +smtp_chat.o: ../../include/sock_addr.h +smtp_chat.o: ../../include/string_list.h +smtp_chat.o: ../../include/stringops.h +smtp_chat.o: ../../include/sys_defs.h +smtp_chat.o: ../../include/tls.h +smtp_chat.o: ../../include/tls_proxy.h +smtp_chat.o: ../../include/tok822.h +smtp_chat.o: ../../include/vbuf.h +smtp_chat.o: ../../include/vstream.h +smtp_chat.o: ../../include/vstring.h +smtp_chat.o: smtp.h +smtp_chat.o: smtp_chat.c +smtp_connect.o: ../../include/argv.h +smtp_connect.o: ../../include/attr.h +smtp_connect.o: ../../include/check_arg.h +smtp_connect.o: ../../include/deliver_pass.h +smtp_connect.o: ../../include/deliver_request.h +smtp_connect.o: ../../include/dict.h +smtp_connect.o: ../../include/dns.h +smtp_connect.o: ../../include/dsn.h +smtp_connect.o: ../../include/dsn_buf.h +smtp_connect.o: ../../include/header_body_checks.h +smtp_connect.o: ../../include/header_opts.h +smtp_connect.o: ../../include/host_port.h +smtp_connect.o: ../../include/htable.h +smtp_connect.o: ../../include/inet_addr_list.h +smtp_connect.o: ../../include/inet_proto.h +smtp_connect.o: ../../include/iostuff.h +smtp_connect.o: ../../include/mail_addr.h +smtp_connect.o: ../../include/mail_error.h +smtp_connect.o: ../../include/mail_params.h +smtp_connect.o: ../../include/mail_proto.h +smtp_connect.o: ../../include/maps.h +smtp_connect.o: ../../include/match_list.h +smtp_connect.o: ../../include/mime_state.h +smtp_connect.o: ../../include/msg.h +smtp_connect.o: ../../include/msg_stats.h +smtp_connect.o: ../../include/myaddrinfo.h +smtp_connect.o: ../../include/myflock.h +smtp_connect.o: ../../include/mymalloc.h +smtp_connect.o: ../../include/name_code.h +smtp_connect.o: ../../include/name_mask.h +smtp_connect.o: ../../include/nvtable.h +smtp_connect.o: ../../include/own_inet_addr.h +smtp_connect.o: ../../include/recipient_list.h +smtp_connect.o: ../../include/resolve_clnt.h +smtp_connect.o: ../../include/sane_connect.h +smtp_connect.o: ../../include/scache.h +smtp_connect.o: ../../include/sock_addr.h +smtp_connect.o: ../../include/split_at.h +smtp_connect.o: ../../include/string_list.h +smtp_connect.o: ../../include/stringops.h +smtp_connect.o: ../../include/sys_defs.h +smtp_connect.o: ../../include/timed_connect.h +smtp_connect.o: ../../include/tls.h +smtp_connect.o: ../../include/tls_proxy.h +smtp_connect.o: ../../include/tok822.h +smtp_connect.o: ../../include/vbuf.h +smtp_connect.o: ../../include/vstream.h +smtp_connect.o: ../../include/vstring.h +smtp_connect.o: smtp.h +smtp_connect.o: smtp_addr.h +smtp_connect.o: smtp_connect.c +smtp_connect.o: smtp_reuse.h +smtp_key.o: ../../include/argv.h +smtp_key.o: ../../include/attr.h +smtp_key.o: ../../include/base64_code.h +smtp_key.o: ../../include/check_arg.h +smtp_key.o: ../../include/deliver_request.h +smtp_key.o: ../../include/dict.h +smtp_key.o: ../../include/dns.h +smtp_key.o: ../../include/dsn.h +smtp_key.o: ../../include/dsn_buf.h +smtp_key.o: ../../include/header_body_checks.h +smtp_key.o: ../../include/header_opts.h +smtp_key.o: ../../include/htable.h +smtp_key.o: ../../include/mail_params.h +smtp_key.o: ../../include/maps.h +smtp_key.o: ../../include/match_list.h +smtp_key.o: ../../include/mime_state.h +smtp_key.o: ../../include/msg.h +smtp_key.o: ../../include/msg_stats.h +smtp_key.o: ../../include/myaddrinfo.h +smtp_key.o: ../../include/myflock.h +smtp_key.o: ../../include/mymalloc.h +smtp_key.o: ../../include/name_code.h +smtp_key.o: ../../include/name_mask.h +smtp_key.o: ../../include/nvtable.h +smtp_key.o: ../../include/recipient_list.h +smtp_key.o: ../../include/resolve_clnt.h +smtp_key.o: ../../include/scache.h +smtp_key.o: ../../include/sock_addr.h +smtp_key.o: ../../include/string_list.h +smtp_key.o: ../../include/sys_defs.h +smtp_key.o: ../../include/tls.h +smtp_key.o: ../../include/tls_proxy.h +smtp_key.o: ../../include/tok822.h +smtp_key.o: ../../include/vbuf.h +smtp_key.o: ../../include/vstream.h +smtp_key.o: ../../include/vstring.h +smtp_key.o: smtp.h +smtp_key.o: smtp_key.c +smtp_map11.o: ../../include/argv.h +smtp_map11.o: ../../include/attr.h +smtp_map11.o: ../../include/check_arg.h +smtp_map11.o: ../../include/deliver_request.h +smtp_map11.o: ../../include/dict.h +smtp_map11.o: ../../include/dns.h +smtp_map11.o: ../../include/dsn.h +smtp_map11.o: ../../include/dsn_buf.h +smtp_map11.o: ../../include/header_body_checks.h +smtp_map11.o: ../../include/header_opts.h +smtp_map11.o: ../../include/htable.h +smtp_map11.o: ../../include/mail_addr_form.h +smtp_map11.o: ../../include/mail_addr_map.h +smtp_map11.o: ../../include/maps.h +smtp_map11.o: ../../include/match_list.h +smtp_map11.o: ../../include/mime_state.h +smtp_map11.o: ../../include/msg.h +smtp_map11.o: ../../include/msg_stats.h +smtp_map11.o: ../../include/myaddrinfo.h +smtp_map11.o: ../../include/myflock.h +smtp_map11.o: ../../include/mymalloc.h +smtp_map11.o: ../../include/name_code.h +smtp_map11.o: ../../include/name_mask.h +smtp_map11.o: ../../include/nvtable.h +smtp_map11.o: ../../include/quote_822_local.h +smtp_map11.o: ../../include/quote_flags.h +smtp_map11.o: ../../include/recipient_list.h +smtp_map11.o: ../../include/resolve_clnt.h +smtp_map11.o: ../../include/scache.h +smtp_map11.o: ../../include/sock_addr.h +smtp_map11.o: ../../include/string_list.h +smtp_map11.o: ../../include/sys_defs.h +smtp_map11.o: ../../include/tls.h +smtp_map11.o: ../../include/tls_proxy.h +smtp_map11.o: ../../include/tok822.h +smtp_map11.o: ../../include/vbuf.h +smtp_map11.o: ../../include/vstream.h +smtp_map11.o: ../../include/vstring.h +smtp_map11.o: smtp.h +smtp_map11.o: smtp_map11.c +smtp_params.o: smtp_params.c +smtp_proto.o: ../../include/argv.h +smtp_proto.o: ../../include/attr.h +smtp_proto.o: ../../include/bounce.h +smtp_proto.o: ../../include/check_arg.h +smtp_proto.o: ../../include/defer.h +smtp_proto.o: ../../include/deliver_request.h +smtp_proto.o: ../../include/dict.h +smtp_proto.o: ../../include/dns.h +smtp_proto.o: ../../include/dsn.h +smtp_proto.o: ../../include/dsn_buf.h +smtp_proto.o: ../../include/dsn_mask.h +smtp_proto.o: ../../include/ehlo_mask.h +smtp_proto.o: ../../include/ext_prop.h +smtp_proto.o: ../../include/header_body_checks.h +smtp_proto.o: ../../include/header_opts.h +smtp_proto.o: ../../include/htable.h +smtp_proto.o: ../../include/iostuff.h +smtp_proto.o: ../../include/lex_822.h +smtp_proto.o: ../../include/mail_addr_form.h +smtp_proto.o: ../../include/mail_addr_map.h +smtp_proto.o: ../../include/mail_params.h +smtp_proto.o: ../../include/mail_proto.h +smtp_proto.o: ../../include/mail_queue.h +smtp_proto.o: ../../include/maps.h +smtp_proto.o: ../../include/mark_corrupt.h +smtp_proto.o: ../../include/match_list.h +smtp_proto.o: ../../include/match_parent_style.h +smtp_proto.o: ../../include/mime_state.h +smtp_proto.o: ../../include/msg.h +smtp_proto.o: ../../include/msg_stats.h +smtp_proto.o: ../../include/myaddrinfo.h +smtp_proto.o: ../../include/myflock.h +smtp_proto.o: ../../include/mymalloc.h +smtp_proto.o: ../../include/namadr_list.h +smtp_proto.o: ../../include/name_code.h +smtp_proto.o: ../../include/name_mask.h +smtp_proto.o: ../../include/nvtable.h +smtp_proto.o: ../../include/off_cvt.h +smtp_proto.o: ../../include/quote_821_local.h +smtp_proto.o: ../../include/quote_822_local.h +smtp_proto.o: ../../include/quote_flags.h +smtp_proto.o: ../../include/rec_type.h +smtp_proto.o: ../../include/recipient_list.h +smtp_proto.o: ../../include/record.h +smtp_proto.o: ../../include/resolve_clnt.h +smtp_proto.o: ../../include/scache.h +smtp_proto.o: ../../include/smtp_stream.h +smtp_proto.o: ../../include/smtputf8.h +smtp_proto.o: ../../include/sock_addr.h +smtp_proto.o: ../../include/split_at.h +smtp_proto.o: ../../include/string_list.h +smtp_proto.o: ../../include/stringops.h +smtp_proto.o: ../../include/sys_defs.h +smtp_proto.o: ../../include/tls.h +smtp_proto.o: ../../include/tls_proxy.h +smtp_proto.o: ../../include/tok822.h +smtp_proto.o: ../../include/uxtext.h +smtp_proto.o: ../../include/vbuf.h +smtp_proto.o: ../../include/vstream.h +smtp_proto.o: ../../include/vstring.h +smtp_proto.o: ../../include/vstring_vstream.h +smtp_proto.o: ../../include/xtext.h +smtp_proto.o: smtp.h +smtp_proto.o: smtp_proto.c +smtp_proto.o: smtp_sasl.h +smtp_rcpt.o: ../../include/argv.h +smtp_rcpt.o: ../../include/attr.h +smtp_rcpt.o: ../../include/bounce.h +smtp_rcpt.o: ../../include/check_arg.h +smtp_rcpt.o: ../../include/deliver_completed.h +smtp_rcpt.o: ../../include/deliver_request.h +smtp_rcpt.o: ../../include/dict.h +smtp_rcpt.o: ../../include/dns.h +smtp_rcpt.o: ../../include/dsn.h +smtp_rcpt.o: ../../include/dsn_buf.h +smtp_rcpt.o: ../../include/dsn_mask.h +smtp_rcpt.o: ../../include/header_body_checks.h +smtp_rcpt.o: ../../include/header_opts.h +smtp_rcpt.o: ../../include/htable.h +smtp_rcpt.o: ../../include/mail_params.h +smtp_rcpt.o: ../../include/maps.h +smtp_rcpt.o: ../../include/match_list.h +smtp_rcpt.o: ../../include/mime_state.h +smtp_rcpt.o: ../../include/msg.h +smtp_rcpt.o: ../../include/msg_stats.h +smtp_rcpt.o: ../../include/myaddrinfo.h +smtp_rcpt.o: ../../include/myflock.h +smtp_rcpt.o: ../../include/mymalloc.h +smtp_rcpt.o: ../../include/name_code.h +smtp_rcpt.o: ../../include/name_mask.h +smtp_rcpt.o: ../../include/nvtable.h +smtp_rcpt.o: ../../include/recipient_list.h +smtp_rcpt.o: ../../include/resolve_clnt.h +smtp_rcpt.o: ../../include/scache.h +smtp_rcpt.o: ../../include/sent.h +smtp_rcpt.o: ../../include/sock_addr.h +smtp_rcpt.o: ../../include/string_list.h +smtp_rcpt.o: ../../include/stringops.h +smtp_rcpt.o: ../../include/sys_defs.h +smtp_rcpt.o: ../../include/tls.h +smtp_rcpt.o: ../../include/tls_proxy.h +smtp_rcpt.o: ../../include/tok822.h +smtp_rcpt.o: ../../include/vbuf.h +smtp_rcpt.o: ../../include/vstream.h +smtp_rcpt.o: ../../include/vstring.h +smtp_rcpt.o: smtp.h +smtp_rcpt.o: smtp_rcpt.c +smtp_reuse.o: ../../include/argv.h +smtp_reuse.o: ../../include/attr.h +smtp_reuse.o: ../../include/check_arg.h +smtp_reuse.o: ../../include/deliver_request.h +smtp_reuse.o: ../../include/dict.h +smtp_reuse.o: ../../include/dns.h +smtp_reuse.o: ../../include/dsn.h +smtp_reuse.o: ../../include/dsn_buf.h +smtp_reuse.o: ../../include/header_body_checks.h +smtp_reuse.o: ../../include/header_opts.h +smtp_reuse.o: ../../include/htable.h +smtp_reuse.o: ../../include/mail_params.h +smtp_reuse.o: ../../include/maps.h +smtp_reuse.o: ../../include/match_list.h +smtp_reuse.o: ../../include/mime_state.h +smtp_reuse.o: ../../include/msg.h +smtp_reuse.o: ../../include/msg_stats.h +smtp_reuse.o: ../../include/myaddrinfo.h +smtp_reuse.o: ../../include/myflock.h +smtp_reuse.o: ../../include/mymalloc.h +smtp_reuse.o: ../../include/name_code.h +smtp_reuse.o: ../../include/name_mask.h +smtp_reuse.o: ../../include/nvtable.h +smtp_reuse.o: ../../include/recipient_list.h +smtp_reuse.o: ../../include/resolve_clnt.h +smtp_reuse.o: ../../include/scache.h +smtp_reuse.o: ../../include/sock_addr.h +smtp_reuse.o: ../../include/string_list.h +smtp_reuse.o: ../../include/stringops.h +smtp_reuse.o: ../../include/sys_defs.h +smtp_reuse.o: ../../include/tls.h +smtp_reuse.o: ../../include/tls_proxy.h +smtp_reuse.o: ../../include/tok822.h +smtp_reuse.o: ../../include/vbuf.h +smtp_reuse.o: ../../include/vstream.h +smtp_reuse.o: ../../include/vstring.h +smtp_reuse.o: smtp.h +smtp_reuse.o: smtp_reuse.c +smtp_reuse.o: smtp_reuse.h +smtp_sasl_auth_cache.o: ../../include/argv.h +smtp_sasl_auth_cache.o: ../../include/attr.h +smtp_sasl_auth_cache.o: ../../include/base64_code.h +smtp_sasl_auth_cache.o: ../../include/check_arg.h +smtp_sasl_auth_cache.o: ../../include/deliver_request.h +smtp_sasl_auth_cache.o: ../../include/dict.h +smtp_sasl_auth_cache.o: ../../include/dict_proxy.h +smtp_sasl_auth_cache.o: ../../include/dns.h +smtp_sasl_auth_cache.o: ../../include/dsn.h +smtp_sasl_auth_cache.o: ../../include/dsn_buf.h +smtp_sasl_auth_cache.o: ../../include/dsn_util.h +smtp_sasl_auth_cache.o: ../../include/header_body_checks.h +smtp_sasl_auth_cache.o: ../../include/header_opts.h +smtp_sasl_auth_cache.o: ../../include/htable.h +smtp_sasl_auth_cache.o: ../../include/maps.h +smtp_sasl_auth_cache.o: ../../include/match_list.h +smtp_sasl_auth_cache.o: ../../include/mime_state.h +smtp_sasl_auth_cache.o: ../../include/msg.h +smtp_sasl_auth_cache.o: ../../include/msg_stats.h +smtp_sasl_auth_cache.o: ../../include/myaddrinfo.h +smtp_sasl_auth_cache.o: ../../include/myflock.h +smtp_sasl_auth_cache.o: ../../include/mymalloc.h +smtp_sasl_auth_cache.o: ../../include/name_code.h +smtp_sasl_auth_cache.o: ../../include/name_mask.h +smtp_sasl_auth_cache.o: ../../include/nvtable.h +smtp_sasl_auth_cache.o: ../../include/recipient_list.h +smtp_sasl_auth_cache.o: ../../include/resolve_clnt.h +smtp_sasl_auth_cache.o: ../../include/scache.h +smtp_sasl_auth_cache.o: ../../include/sock_addr.h +smtp_sasl_auth_cache.o: ../../include/string_list.h +smtp_sasl_auth_cache.o: ../../include/stringops.h +smtp_sasl_auth_cache.o: ../../include/sys_defs.h +smtp_sasl_auth_cache.o: ../../include/tls.h +smtp_sasl_auth_cache.o: ../../include/tls_proxy.h +smtp_sasl_auth_cache.o: ../../include/tok822.h +smtp_sasl_auth_cache.o: ../../include/vbuf.h +smtp_sasl_auth_cache.o: ../../include/vstream.h +smtp_sasl_auth_cache.o: ../../include/vstring.h +smtp_sasl_auth_cache.o: smtp.h +smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.c +smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.h +smtp_sasl_glue.o: ../../include/argv.h +smtp_sasl_glue.o: ../../include/attr.h +smtp_sasl_glue.o: ../../include/check_arg.h +smtp_sasl_glue.o: ../../include/deliver_request.h +smtp_sasl_glue.o: ../../include/dict.h +smtp_sasl_glue.o: ../../include/dns.h +smtp_sasl_glue.o: ../../include/dsn.h +smtp_sasl_glue.o: ../../include/dsn_buf.h +smtp_sasl_glue.o: ../../include/header_body_checks.h +smtp_sasl_glue.o: ../../include/header_opts.h +smtp_sasl_glue.o: ../../include/htable.h +smtp_sasl_glue.o: ../../include/mail_addr_find.h +smtp_sasl_glue.o: ../../include/mail_addr_form.h +smtp_sasl_glue.o: ../../include/mail_params.h +smtp_sasl_glue.o: ../../include/maps.h +smtp_sasl_glue.o: ../../include/match_list.h +smtp_sasl_glue.o: ../../include/mime_state.h +smtp_sasl_glue.o: ../../include/msg.h +smtp_sasl_glue.o: ../../include/msg_stats.h +smtp_sasl_glue.o: ../../include/myaddrinfo.h +smtp_sasl_glue.o: ../../include/myflock.h +smtp_sasl_glue.o: ../../include/mymalloc.h +smtp_sasl_glue.o: ../../include/name_code.h +smtp_sasl_glue.o: ../../include/name_mask.h +smtp_sasl_glue.o: ../../include/nvtable.h +smtp_sasl_glue.o: ../../include/recipient_list.h +smtp_sasl_glue.o: ../../include/resolve_clnt.h +smtp_sasl_glue.o: ../../include/scache.h +smtp_sasl_glue.o: ../../include/smtp_stream.h +smtp_sasl_glue.o: ../../include/sock_addr.h +smtp_sasl_glue.o: ../../include/split_at.h +smtp_sasl_glue.o: ../../include/string_list.h +smtp_sasl_glue.o: ../../include/stringops.h +smtp_sasl_glue.o: ../../include/sys_defs.h +smtp_sasl_glue.o: ../../include/tls.h +smtp_sasl_glue.o: ../../include/tls_proxy.h +smtp_sasl_glue.o: ../../include/tok822.h +smtp_sasl_glue.o: ../../include/vbuf.h +smtp_sasl_glue.o: ../../include/vstream.h +smtp_sasl_glue.o: ../../include/vstring.h +smtp_sasl_glue.o: ../../include/xsasl.h +smtp_sasl_glue.o: smtp.h +smtp_sasl_glue.o: smtp_sasl.h +smtp_sasl_glue.o: smtp_sasl_auth_cache.h +smtp_sasl_glue.o: smtp_sasl_glue.c +smtp_sasl_proto.o: ../../include/argv.h +smtp_sasl_proto.o: ../../include/attr.h +smtp_sasl_proto.o: ../../include/check_arg.h +smtp_sasl_proto.o: ../../include/deliver_request.h +smtp_sasl_proto.o: ../../include/dict.h +smtp_sasl_proto.o: ../../include/dns.h +smtp_sasl_proto.o: ../../include/dsn.h +smtp_sasl_proto.o: ../../include/dsn_buf.h +smtp_sasl_proto.o: ../../include/header_body_checks.h +smtp_sasl_proto.o: ../../include/header_opts.h +smtp_sasl_proto.o: ../../include/htable.h +smtp_sasl_proto.o: ../../include/mail_params.h +smtp_sasl_proto.o: ../../include/maps.h +smtp_sasl_proto.o: ../../include/match_list.h +smtp_sasl_proto.o: ../../include/mime_state.h +smtp_sasl_proto.o: ../../include/msg.h +smtp_sasl_proto.o: ../../include/msg_stats.h +smtp_sasl_proto.o: ../../include/myaddrinfo.h +smtp_sasl_proto.o: ../../include/myflock.h +smtp_sasl_proto.o: ../../include/mymalloc.h +smtp_sasl_proto.o: ../../include/name_code.h +smtp_sasl_proto.o: ../../include/name_mask.h +smtp_sasl_proto.o: ../../include/nvtable.h +smtp_sasl_proto.o: ../../include/recipient_list.h +smtp_sasl_proto.o: ../../include/resolve_clnt.h +smtp_sasl_proto.o: ../../include/scache.h +smtp_sasl_proto.o: ../../include/sock_addr.h +smtp_sasl_proto.o: ../../include/string_list.h +smtp_sasl_proto.o: ../../include/stringops.h +smtp_sasl_proto.o: ../../include/sys_defs.h +smtp_sasl_proto.o: ../../include/tls.h +smtp_sasl_proto.o: ../../include/tls_proxy.h +smtp_sasl_proto.o: ../../include/tok822.h +smtp_sasl_proto.o: ../../include/vbuf.h +smtp_sasl_proto.o: ../../include/vstream.h +smtp_sasl_proto.o: ../../include/vstring.h +smtp_sasl_proto.o: smtp.h +smtp_sasl_proto.o: smtp_sasl.h +smtp_sasl_proto.o: smtp_sasl_proto.c +smtp_session.o: ../../include/argv.h +smtp_session.o: ../../include/attr.h +smtp_session.o: ../../include/check_arg.h +smtp_session.o: ../../include/debug_peer.h +smtp_session.o: ../../include/deliver_request.h +smtp_session.o: ../../include/dict.h +smtp_session.o: ../../include/dns.h +smtp_session.o: ../../include/dsn.h +smtp_session.o: ../../include/dsn_buf.h +smtp_session.o: ../../include/header_body_checks.h +smtp_session.o: ../../include/header_opts.h +smtp_session.o: ../../include/htable.h +smtp_session.o: ../../include/mail_params.h +smtp_session.o: ../../include/maps.h +smtp_session.o: ../../include/match_list.h +smtp_session.o: ../../include/mime_state.h +smtp_session.o: ../../include/msg.h +smtp_session.o: ../../include/msg_stats.h +smtp_session.o: ../../include/myaddrinfo.h +smtp_session.o: ../../include/myflock.h +smtp_session.o: ../../include/mymalloc.h +smtp_session.o: ../../include/name_code.h +smtp_session.o: ../../include/name_mask.h +smtp_session.o: ../../include/nvtable.h +smtp_session.o: ../../include/recipient_list.h +smtp_session.o: ../../include/resolve_clnt.h +smtp_session.o: ../../include/scache.h +smtp_session.o: ../../include/sock_addr.h +smtp_session.o: ../../include/string_list.h +smtp_session.o: ../../include/stringops.h +smtp_session.o: ../../include/sys_defs.h +smtp_session.o: ../../include/tls.h +smtp_session.o: ../../include/tls_proxy.h +smtp_session.o: ../../include/tok822.h +smtp_session.o: ../../include/vbuf.h +smtp_session.o: ../../include/vstream.h +smtp_session.o: ../../include/vstring.h +smtp_session.o: smtp.h +smtp_session.o: smtp_sasl.h +smtp_session.o: smtp_session.c +smtp_state.o: ../../include/argv.h +smtp_state.o: ../../include/attr.h +smtp_state.o: ../../include/check_arg.h +smtp_state.o: ../../include/deliver_request.h +smtp_state.o: ../../include/dict.h +smtp_state.o: ../../include/dns.h +smtp_state.o: ../../include/dsn.h +smtp_state.o: ../../include/dsn_buf.h +smtp_state.o: ../../include/header_body_checks.h +smtp_state.o: ../../include/header_opts.h +smtp_state.o: ../../include/htable.h +smtp_state.o: ../../include/mail_params.h +smtp_state.o: ../../include/maps.h +smtp_state.o: ../../include/match_list.h +smtp_state.o: ../../include/mime_state.h +smtp_state.o: ../../include/msg.h +smtp_state.o: ../../include/msg_stats.h +smtp_state.o: ../../include/myaddrinfo.h +smtp_state.o: ../../include/myflock.h +smtp_state.o: ../../include/mymalloc.h +smtp_state.o: ../../include/name_code.h +smtp_state.o: ../../include/name_mask.h +smtp_state.o: ../../include/nvtable.h +smtp_state.o: ../../include/recipient_list.h +smtp_state.o: ../../include/resolve_clnt.h +smtp_state.o: ../../include/scache.h +smtp_state.o: ../../include/sock_addr.h +smtp_state.o: ../../include/string_list.h +smtp_state.o: ../../include/sys_defs.h +smtp_state.o: ../../include/tls.h +smtp_state.o: ../../include/tls_proxy.h +smtp_state.o: ../../include/tok822.h +smtp_state.o: ../../include/vbuf.h +smtp_state.o: ../../include/vstream.h +smtp_state.o: ../../include/vstring.h +smtp_state.o: smtp.h +smtp_state.o: smtp_sasl.h +smtp_state.o: smtp_state.c +smtp_tls_policy.o: ../../include/argv.h +smtp_tls_policy.o: ../../include/attr.h +smtp_tls_policy.o: ../../include/check_arg.h +smtp_tls_policy.o: ../../include/ctable.h +smtp_tls_policy.o: ../../include/deliver_request.h +smtp_tls_policy.o: ../../include/dict.h +smtp_tls_policy.o: ../../include/dns.h +smtp_tls_policy.o: ../../include/dsn.h +smtp_tls_policy.o: ../../include/dsn_buf.h +smtp_tls_policy.o: ../../include/header_body_checks.h +smtp_tls_policy.o: ../../include/header_opts.h +smtp_tls_policy.o: ../../include/htable.h +smtp_tls_policy.o: ../../include/mail_params.h +smtp_tls_policy.o: ../../include/maps.h +smtp_tls_policy.o: ../../include/match_list.h +smtp_tls_policy.o: ../../include/mime_state.h +smtp_tls_policy.o: ../../include/msg.h +smtp_tls_policy.o: ../../include/msg_stats.h +smtp_tls_policy.o: ../../include/myaddrinfo.h +smtp_tls_policy.o: ../../include/myflock.h +smtp_tls_policy.o: ../../include/mymalloc.h +smtp_tls_policy.o: ../../include/name_code.h +smtp_tls_policy.o: ../../include/name_mask.h +smtp_tls_policy.o: ../../include/nvtable.h +smtp_tls_policy.o: ../../include/recipient_list.h +smtp_tls_policy.o: ../../include/resolve_clnt.h +smtp_tls_policy.o: ../../include/scache.h +smtp_tls_policy.o: ../../include/sock_addr.h +smtp_tls_policy.o: ../../include/string_list.h +smtp_tls_policy.o: ../../include/stringops.h +smtp_tls_policy.o: ../../include/sys_defs.h +smtp_tls_policy.o: ../../include/tls.h +smtp_tls_policy.o: ../../include/tls_proxy.h +smtp_tls_policy.o: ../../include/tok822.h +smtp_tls_policy.o: ../../include/valid_hostname.h +smtp_tls_policy.o: ../../include/valid_utf8_hostname.h +smtp_tls_policy.o: ../../include/vbuf.h +smtp_tls_policy.o: ../../include/vstream.h +smtp_tls_policy.o: ../../include/vstring.h +smtp_tls_policy.o: smtp.h +smtp_tls_policy.o: smtp_tls_policy.c +smtp_trouble.o: ../../include/argv.h +smtp_trouble.o: ../../include/attr.h +smtp_trouble.o: ../../include/bounce.h +smtp_trouble.o: ../../include/check_arg.h +smtp_trouble.o: ../../include/defer.h +smtp_trouble.o: ../../include/deliver_completed.h +smtp_trouble.o: ../../include/deliver_request.h +smtp_trouble.o: ../../include/dict.h +smtp_trouble.o: ../../include/dns.h +smtp_trouble.o: ../../include/dsn.h +smtp_trouble.o: ../../include/dsn_buf.h +smtp_trouble.o: ../../include/header_body_checks.h +smtp_trouble.o: ../../include/header_opts.h +smtp_trouble.o: ../../include/htable.h +smtp_trouble.o: ../../include/mail_error.h +smtp_trouble.o: ../../include/mail_params.h +smtp_trouble.o: ../../include/maps.h +smtp_trouble.o: ../../include/match_list.h +smtp_trouble.o: ../../include/mime_state.h +smtp_trouble.o: ../../include/msg.h +smtp_trouble.o: ../../include/msg_stats.h +smtp_trouble.o: ../../include/myaddrinfo.h +smtp_trouble.o: ../../include/myflock.h +smtp_trouble.o: ../../include/mymalloc.h +smtp_trouble.o: ../../include/name_code.h +smtp_trouble.o: ../../include/name_mask.h +smtp_trouble.o: ../../include/nvtable.h +smtp_trouble.o: ../../include/recipient_list.h +smtp_trouble.o: ../../include/resolve_clnt.h +smtp_trouble.o: ../../include/scache.h +smtp_trouble.o: ../../include/smtp_stream.h +smtp_trouble.o: ../../include/sock_addr.h +smtp_trouble.o: ../../include/string_list.h +smtp_trouble.o: ../../include/stringops.h +smtp_trouble.o: ../../include/sys_defs.h +smtp_trouble.o: ../../include/tls.h +smtp_trouble.o: ../../include/tls_proxy.h +smtp_trouble.o: ../../include/tok822.h +smtp_trouble.o: ../../include/vbuf.h +smtp_trouble.o: ../../include/vstream.h +smtp_trouble.o: ../../include/vstring.h +smtp_trouble.o: smtp.h +smtp_trouble.o: smtp_sasl.h +smtp_trouble.o: smtp_trouble.c +smtp_unalias.o: ../../include/argv.h +smtp_unalias.o: ../../include/attr.h +smtp_unalias.o: ../../include/check_arg.h +smtp_unalias.o: ../../include/deliver_request.h +smtp_unalias.o: ../../include/dict.h +smtp_unalias.o: ../../include/dns.h +smtp_unalias.o: ../../include/dsn.h +smtp_unalias.o: ../../include/dsn_buf.h +smtp_unalias.o: ../../include/header_body_checks.h +smtp_unalias.o: ../../include/header_opts.h +smtp_unalias.o: ../../include/htable.h +smtp_unalias.o: ../../include/maps.h +smtp_unalias.o: ../../include/match_list.h +smtp_unalias.o: ../../include/mime_state.h +smtp_unalias.o: ../../include/msg.h +smtp_unalias.o: ../../include/msg_stats.h +smtp_unalias.o: ../../include/myaddrinfo.h +smtp_unalias.o: ../../include/myflock.h +smtp_unalias.o: ../../include/mymalloc.h +smtp_unalias.o: ../../include/name_code.h +smtp_unalias.o: ../../include/name_mask.h +smtp_unalias.o: ../../include/nvtable.h +smtp_unalias.o: ../../include/recipient_list.h +smtp_unalias.o: ../../include/resolve_clnt.h +smtp_unalias.o: ../../include/scache.h +smtp_unalias.o: ../../include/sock_addr.h +smtp_unalias.o: ../../include/string_list.h +smtp_unalias.o: ../../include/sys_defs.h +smtp_unalias.o: ../../include/tls.h +smtp_unalias.o: ../../include/tls_proxy.h +smtp_unalias.o: ../../include/tok822.h +smtp_unalias.o: ../../include/vbuf.h +smtp_unalias.o: ../../include/vstream.h +smtp_unalias.o: ../../include/vstring.h +smtp_unalias.o: smtp.h +smtp_unalias.o: smtp_unalias.c diff --git a/src/smtp/lmtp_params.c b/src/smtp/lmtp_params.c new file mode 100644 index 0000000..973cb5d --- /dev/null +++ b/src/smtp/lmtp_params.c @@ -0,0 +1,130 @@ + static const CONFIG_STR_TABLE lmtp_str_table[] = { + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_LMTP_FALLBACK, DEF_LMTP_FALLBACK, &var_fallback_relay, 0, 0, + VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, + VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, + VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, + VAR_LMTP_SASL_OPTS, DEF_LMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, + VAR_LMTP_SASL_PATH, DEF_LMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0, +#ifdef USE_TLS + VAR_LMTP_SASL_TLS_OPTS, DEF_LMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0, + VAR_LMTP_SASL_TLSV_OPTS, DEF_LMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0, + VAR_LMTP_TLS_CHAIN_FILES, DEF_LMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0, + VAR_LMTP_TLS_CERT_FILE, DEF_LMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_LMTP_TLS_KEY_FILE, DEF_LMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_LMTP_TLS_DCERT_FILE, DEF_LMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_LMTP_TLS_DKEY_FILE, DEF_LMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_LMTP_TLS_CA_FILE, DEF_LMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_LMTP_TLS_CA_PATH, DEF_LMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_LMTP_TLS_MAND_CIPH, DEF_LMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0, + VAR_LMTP_TLS_EXCL_CIPH, DEF_LMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0, + VAR_LMTP_TLS_MAND_EXCL, DEF_LMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0, + VAR_LMTP_TLS_MAND_PROTO, DEF_LMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0, + VAR_LMTP_TLS_VFY_CMATCH, DEF_LMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0, + VAR_LMTP_TLS_SEC_CMATCH, DEF_LMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0, + VAR_LMTP_TLS_FPT_CMATCH, DEF_LMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0, + VAR_LMTP_TLS_FPT_DGST, DEF_LMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_LMTP_TLS_TAFILE, DEF_LMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0, + VAR_LMTP_TLS_PROTO, DEF_LMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0, + VAR_LMTP_TLS_CIPH, DEF_LMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0, + VAR_LMTP_TLS_ECCERT_FILE, DEF_LMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, + VAR_LMTP_TLS_ECKEY_FILE, DEF_LMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0, + VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_LMTP_TLS_SNI, DEF_LMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0, +#endif + VAR_LMTP_SASL_MECHS, DEF_LMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0, + VAR_LMTP_SASL_TYPE, DEF_LMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0, + VAR_LMTP_BIND_ADDR, DEF_LMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0, + VAR_LMTP_BIND_ADDR6, DEF_LMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0, + VAR_LMTP_VRFY_TGT, DEF_LMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0, + VAR_LMTP_HELO_NAME, DEF_LMTP_HELO_NAME, &var_smtp_helo_name, 1, 0, + VAR_LMTP_HOST_LOOKUP, DEF_LMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0, + VAR_LMTP_DNS_SUPPORT, DEF_LMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0, + VAR_LMTP_CACHE_DEST, DEF_LMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0, + VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0, + VAR_LMTP_EHLO_DIS_WORDS, DEF_LMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0, + VAR_LMTP_EHLO_DIS_MAPS, DEF_LMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0, + VAR_LMTP_TLS_PER_SITE, DEF_LMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, + VAR_LMTP_TLS_LEVEL, DEF_LMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0, + VAR_LMTP_TLS_POLICY, DEF_LMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0, + VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, + VAR_LMTP_GENERIC_MAPS, DEF_LMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0, + VAR_LMTP_TCP_PORT, DEF_LMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0, + VAR_LMTP_PIX_BUG_WORDS, DEF_LMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0, + VAR_LMTP_PIX_BUG_MAPS, DEF_LMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0, + VAR_LMTP_SASL_AUTH_CACHE_NAME, DEF_LMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0, + VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0, + VAR_LMTP_HEAD_CHKS, DEF_LMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0, + VAR_LMTP_MIME_CHKS, DEF_LMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0, + VAR_LMTP_NEST_CHKS, DEF_LMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0, + VAR_LMTP_BODY_CHKS, DEF_LMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0, + VAR_LMTP_RESP_FILTER, DEF_LMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0, + VAR_LMTP_ADDR_PREF, DEF_LMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0, + VAR_LMTP_DNS_RES_OPT, DEF_LMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0, + VAR_LMTP_DSN_FILTER, DEF_LMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0, + VAR_LMTP_DNS_RE_FILTER, DEF_LMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0, + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, + 0, + }; + static const CONFIG_TIME_TABLE lmtp_time_table[] = { + VAR_LMTP_CONN_TMOUT, DEF_LMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0, + VAR_LMTP_HELO_TMOUT, DEF_LMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0, + VAR_LMTP_XFWD_TMOUT, DEF_LMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0, + VAR_LMTP_MAIL_TMOUT, DEF_LMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0, + VAR_LMTP_RCPT_TMOUT, DEF_LMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0, + VAR_LMTP_DATA0_TMOUT, DEF_LMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0, + VAR_LMTP_DATA1_TMOUT, DEF_LMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0, + VAR_LMTP_DATA2_TMOUT, DEF_LMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0, + VAR_LMTP_RSET_TMOUT, DEF_LMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0, + VAR_LMTP_QUIT_TMOUT, DEF_LMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0, + VAR_LMTP_PIX_THRESH, DEF_LMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0, + VAR_LMTP_PIX_DELAY, DEF_LMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0, + VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0, + VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0, + VAR_LMTP_CACHE_CONNT, DEF_LMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0, + VAR_LMTP_REUSE_TIME, DEF_LMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0, +#ifdef USE_TLS + VAR_LMTP_STARTTLS_TMOUT, DEF_LMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0, +#endif + VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0, + VAR_LMTP_SASL_AUTH_CACHE_TIME, DEF_LMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0, + 0, + }; + static const CONFIG_INT_TABLE lmtp_int_table[] = { + VAR_LMTP_LINE_LIMIT, DEF_LMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0, + VAR_LMTP_MXADDR_LIMIT, DEF_LMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0, + VAR_LMTP_MXSESS_LIMIT, DEF_LMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0, + VAR_LMTP_REUSE_COUNT, DEF_LMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0, +#ifdef USE_TLS + VAR_LMTP_TLS_SCERT_VD, DEF_LMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, +#endif + 0, + }; + static const CONFIG_BOOL_TABLE lmtp_bool_table[] = { + VAR_LMTP_SKIP_5XX, DEF_LMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting, + VAR_LMTP_SKIP_QUIT_RESP, DEF_LMTP_SKIP_QUIT_RESP, &var_skip_quit_resp, + VAR_LMTP_SASL_ENABLE, DEF_LMTP_SASL_ENABLE, &var_smtp_sasl_enable, + VAR_LMTP_RAND_ADDR, DEF_LMTP_RAND_ADDR, &var_smtp_rand_addr, + VAR_LMTP_QUOTE_821_ENV, DEF_LMTP_QUOTE_821_ENV, &var_smtp_quote_821_env, + VAR_LMTP_DEFER_MXADDR, DEF_LMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr, + VAR_LMTP_SEND_XFORWARD, DEF_LMTP_SEND_XFORWARD, &var_smtp_send_xforward, + VAR_LMTP_CACHE_DEMAND, DEF_LMTP_CACHE_DEMAND, &var_smtp_cache_demand, + VAR_LMTP_USE_TLS, DEF_LMTP_USE_TLS, &var_smtp_use_tls, + VAR_LMTP_ENFORCE_TLS, DEF_LMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_LMTP_TLS_CONN_REUSE, DEF_LMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse, +#ifdef USE_TLS + VAR_LMTP_TLS_ENFORCE_PN, DEF_LMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, + VAR_LMTP_TLS_NOTEOFFER, DEF_LMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, + VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, + VAR_LMTP_TLS_FORCE_TLSA, DEF_LMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, +#endif + VAR_LMTP_TLS_WRAPPER, DEF_LMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, + VAR_LMTP_SENDER_AUTH, DEF_LMTP_SENDER_AUTH, &var_smtp_sender_auth, + VAR_LMTP_CNAME_OVERR, DEF_LMTP_CNAME_OVERR, &var_smtp_cname_overr, + VAR_LMTP_SASL_AUTH_SOFT_BOUNCE, DEF_LMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce, + VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final, + VAR_LMTP_REC_DEADLINE, DEF_LMTP_REC_DEADLINE, &var_smtp_rec_deadline, + VAR_LMTP_DUMMY_MAIL_AUTH, DEF_LMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth, + VAR_LMTP_BALANCE_INET_PROTO, DEF_LMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto, + 0, + }; diff --git a/src/smtp/smtp-only b/src/smtp/smtp-only new file mode 100644 index 0000000..ee9288d --- /dev/null +++ b/src/smtp/smtp-only @@ -0,0 +1,4 @@ +_ALWAYS_EHLO +_NEVER_EHLO +_IGN_MX_LOOKUP_ERR +_INSECURE_MX_POLICY diff --git a/src/smtp/smtp.c b/src/smtp/smtp.c new file mode 100644 index 0000000..db17578 --- /dev/null +++ b/src/smtp/smtp.c @@ -0,0 +1,1430 @@ +/*++ +/* NAME +/* smtp 8 +/* SUMMARY +/* Postfix SMTP+LMTP client +/* SYNOPSIS +/* \fBsmtp\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The Postfix SMTP+LMTP client implements the SMTP and LMTP mail +/* delivery protocols. It processes message delivery requests from +/* the queue manager. Each request specifies a queue file, a sender +/* address, a domain or host to deliver to, and recipient information. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* The SMTP+LMTP client updates the queue file and marks recipients +/* as finished, or it informs the queue manager that delivery should +/* be tried again at a later time. Delivery status reports are sent +/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as +/* appropriate. +/* +/* The SMTP+LMTP client looks up a list of mail exchanger addresses for +/* the destination host, sorts the list by preference, and connects +/* to each listed address until it finds a server that responds. +/* +/* When a server is not reachable, or when mail delivery fails due +/* to a recoverable error condition, the SMTP+LMTP client will try to +/* deliver the mail to an alternate host. +/* +/* After a successful mail transaction, a connection may be saved +/* to the \fBscache\fR(8) connection cache server, so that it +/* may be used by any SMTP+LMTP client for a subsequent transaction. +/* +/* By default, connection caching is enabled temporarily for +/* destinations that have a high volume of mail in the active +/* queue. Connection caching can be enabled permanently for +/* specific destinations. +/* SMTP DESTINATION SYNTAX +/* .ad +/* .fi +/* SMTP destinations have the following form: +/* .IP \fIdomainname\fR +/* .IP \fIdomainname\fR:\fIport\fR +/* Look up the mail exchangers for the specified domain, and +/* connect to the specified port (default: \fBsmtp\fR). +/* .IP [\fIhostname\fR] +/* .IP [\fIhostname\fR]:\fIport\fR +/* Look up the address(es) of the specified host, and connect to +/* the specified port (default: \fBsmtp\fR). +/* .IP [\fIaddress\fR] +/* .IP [\fIaddress\fR]:\fIport\fR +/* Connect to the host at the specified address, and connect +/* to the specified port (default: \fBsmtp\fR). An IPv6 address +/* must be formatted as [\fBipv6\fR:\fIaddress\fR]. +/* LMTP DESTINATION SYNTAX +/* .ad +/* .fi +/* LMTP destinations have the following form: +/* .IP \fBunix\fR:\fIpathname\fR +/* Connect to the local UNIX-domain server that is bound to the specified +/* \fIpathname\fR. If the process runs chrooted, an absolute pathname +/* is interpreted relative to the Postfix queue directory. +/* .IP \fBinet\fR:\fIhostname\fR +/* .IP \fBinet\fR:\fIhostname\fR:\fIport\fR +/* .IP \fBinet\fR:[\fIaddress\fR] +/* .IP \fBinet\fR:[\fIaddress\fR]:\fIport\fR +/* Connect to the specified TCP port on the specified local or +/* remote host. If no port is specified, connect to the port defined as +/* \fBlmtp\fR in \fBservices\fR(4). +/* If no such service is found, the \fBlmtp_tcp_port\fR configuration +/* parameter (default value of 24) will be used. +/* An IPv6 address must be formatted as [\fBipv6\fR:\fIaddress\fR]. +/* .PP +/* SECURITY +/* .ad +/* .fi +/* The SMTP+LMTP client is moderately security-sensitive. It +/* talks to SMTP or LMTP servers and to DNS servers on the +/* network. The SMTP+LMTP client can be run chrooted at fixed +/* low privilege. +/* STANDARDS +/* RFC 821 (SMTP protocol) +/* RFC 822 (ARPA Internet Text Messages) +/* RFC 1651 (SMTP service extensions) +/* RFC 1652 (8bit-MIME transport) +/* RFC 1870 (Message Size Declaration) +/* RFC 2033 (LMTP protocol) +/* RFC 2034 (SMTP Enhanced Error Codes) +/* RFC 2045 (MIME: Format of Internet Message Bodies) +/* RFC 2046 (MIME: Media Types) +/* RFC 2554 (AUTH command) +/* RFC 2821 (SMTP protocol) +/* RFC 2920 (SMTP Pipelining) +/* RFC 3207 (STARTTLS command) +/* RFC 3461 (SMTP DSN Extension) +/* RFC 3463 (Enhanced Status Codes) +/* RFC 4954 (AUTH command) +/* RFC 5321 (SMTP protocol) +/* RFC 6531 (Internationalized SMTP) +/* RFC 6533 (Internationalized Delivery Status Notifications) +/* RFC 7672 (SMTP security via opportunistic DANE TLS) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* Corrupted message files are marked so that the queue manager can +/* move them to the \fBcorrupt\fR queue for further inspection. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces, protocol problems, and of +/* other trouble. +/* BUGS +/* SMTP and LMTP connection reuse for TLS (without closing the +/* SMTP or LMTP connection) is not supported before Postfix 3.4. +/* +/* SMTP and LMTP connection caching assumes that SASL credentials +/* are valid for all destinations that map onto the same IP +/* address and TCP port. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Before Postfix version 2.3, the LMTP client is a separate +/* program that implements only a subset of the functionality +/* available with SMTP: there is no support for TLS, and +/* connections are cached in-process, making it ineffective +/* when the client is used for multiple domains. +/* +/* Most smtp_\fIxxx\fR configuration parameters have an +/* lmtp_\fIxxx\fR "mirror" parameter for the equivalent LMTP +/* feature. This document describes only those LMTP-related +/* parameters that aren't simply "mirror" parameters. +/* +/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtp\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBignore_mx_lookup_error (no)\fR" +/* Ignore DNS MX lookups that produce no response. +/* .IP "\fBsmtp_always_send_ehlo (yes)\fR" +/* Always send EHLO at the start of an SMTP session. +/* .IP "\fBsmtp_never_send_ehlo (no)\fR" +/* Never send EHLO at the start of an SMTP session. +/* .IP "\fBsmtp_defer_if_no_mx_address_found (no)\fR" +/* Defer mail delivery when no MX record resolves to an IP address. +/* .IP "\fBsmtp_line_length_limit (998)\fR" +/* The maximal length of message header and body lines that Postfix +/* will send via SMTP. +/* .IP "\fBsmtp_pix_workaround_delay_time (10s)\fR" +/* How long the Postfix SMTP client pauses before sending +/* ".<CR><LF>" in order to work around the PIX firewall +/* "<CR><LF>.<CR><LF>" bug. +/* .IP "\fBsmtp_pix_workaround_threshold_time (500s)\fR" +/* How long a message must be queued before the Postfix SMTP client +/* turns on the PIX firewall "<CR><LF>.<CR><LF>" +/* bug workaround for delivery through firewalls with "smtp fixup" +/* mode turned on. +/* .IP "\fBsmtp_pix_workarounds (disable_esmtp, delay_dotcrlf)\fR" +/* A list that specifies zero or more workarounds for CISCO PIX +/* firewall bugs. +/* .IP "\fBsmtp_pix_workaround_maps (empty)\fR" +/* Lookup tables, indexed by the remote SMTP server address, with +/* per-destination workarounds for CISCO PIX firewall bugs. +/* .IP "\fBsmtp_quote_rfc821_envelope (yes)\fR" +/* Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO commands +/* as required +/* by RFC 5321. +/* .IP "\fBsmtp_reply_filter (empty)\fR" +/* A mechanism to transform replies from remote SMTP servers one +/* line at a time. +/* .IP "\fBsmtp_skip_5xx_greeting (yes)\fR" +/* Skip remote SMTP servers that greet with a 5XX status code. +/* .IP "\fBsmtp_skip_quit_response (yes)\fR" +/* Do not wait for the response to the SMTP QUIT command. +/* .PP +/* Available in Postfix version 2.0 and earlier: +/* .IP "\fBsmtp_skip_4xx_greeting (yes)\fR" +/* Skip SMTP servers that greet with a 4XX status code (go away, try +/* again later). +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtp_discard_ehlo_keyword_address_maps (empty)\fR" +/* Lookup tables, indexed by the remote SMTP server address, with +/* case insensitive lists of EHLO keywords (pipelining, starttls, auth, +/* etc.) that the Postfix SMTP client will ignore in the EHLO response from a +/* remote SMTP server. +/* .IP "\fBsmtp_discard_ehlo_keywords (empty)\fR" +/* A case insensitive list of EHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix SMTP client will ignore in the EHLO +/* response from a remote SMTP server. +/* .IP "\fBsmtp_generic_maps (empty)\fR" +/* Optional lookup tables that perform address rewriting in the +/* Postfix SMTP client, typically to transform a locally valid address into +/* a globally valid address when sending mail across the Internet. +/* .PP +/* Available in Postfix version 2.2.9 and later: +/* .IP "\fBsmtp_cname_overrides_servername (version dependent)\fR" +/* When the remote SMTP servername is a DNS CNAME, replace the +/* servername with the result from CNAME expansion for the purpose of +/* logging, SASL password lookup, TLS +/* policy decisions, or TLS certificate verification. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBlmtp_discard_lhlo_keyword_address_maps (empty)\fR" +/* Lookup tables, indexed by the remote LMTP server address, with +/* case insensitive lists of LHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO +/* response +/* from a remote LMTP server. +/* .IP "\fBlmtp_discard_lhlo_keywords (empty)\fR" +/* A case insensitive list of LHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO +/* response +/* from a remote LMTP server. +/* .PP +/* Available in Postfix version 2.4.4 and later: +/* .IP "\fBsend_cyrus_sasl_authzid (no)\fR" +/* When authenticating to a remote SMTP or LMTP server with the +/* default setting "no", send no SASL authoriZation ID (authzid); send +/* only the SASL authentiCation ID (authcid) plus the authcid's password. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtp_header_checks (empty)\fR" +/* Restricted \fBheader_checks\fR(5) tables for the Postfix SMTP client. +/* .IP "\fBsmtp_mime_header_checks (empty)\fR" +/* Restricted \fBmime_header_checks\fR(5) tables for the Postfix SMTP +/* client. +/* .IP "\fBsmtp_nested_header_checks (empty)\fR" +/* Restricted \fBnested_header_checks\fR(5) tables for the Postfix SMTP +/* client. +/* .IP "\fBsmtp_body_checks (empty)\fR" +/* Restricted \fBbody_checks\fR(5) tables for the Postfix SMTP client. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBtcp_windowsize (0)\fR" +/* An optional workaround for routers that break TCP window scaling. +/* .PP +/* Available in Postfix version 2.8 and later: +/* .IP "\fBsmtp_dns_resolver_options (empty)\fR" +/* DNS Resolver options for the Postfix SMTP client. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtp_per_record_deadline (no)\fR" +/* Change the behavior of the smtp_*_timeout time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR" +/* Whether or not to append the "AUTH=<>" option to the MAIL +/* FROM command in SASL-authenticated SMTP sessions. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_dns_support_level (empty)\fR" +/* Level of DNS support in the Postfix SMTP client. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtp_delivery_status_filter ($default_delivery_status_filter)\fR" +/* Optional filter for the \fBsmtp\fR(8) delivery agent to change the +/* delivery status code or explanatory text of successful or unsuccessful +/* deliveries. +/* .IP "\fBsmtp_dns_reply_filter (empty)\fR" +/* Optional filter for Postfix SMTP client DNS lookup results. +/* .PP +/* Available in Postfix version 3.3 and later: +/* .IP "\fBsmtp_balance_inet_protocols (yes)\fR" +/* When a remote destination resolves to a combination of IPv4 and +/* IPv6 addresses, ensure that the Postfix SMTP client can try both +/* address types before it runs into the smtp_mx_address_limit. +/* .PP +/* Available in Postfix 3.4.19 and later: +/* .IP "\fBdnssec_probe (ns:.)\fR" +/* The DNS query type (default: "ns") and DNS query name (default: +/* ".") that Postfix may use to determine whether DNSSEC validation +/* is available. +/* MIME PROCESSING CONTROLS +/* .ad +/* .fi +/* Available in Postfix version 2.0 and later: +/* .IP "\fBdisable_mime_output_conversion (no)\fR" +/* Disable the conversion of 8BITMIME format to 7BIT format. +/* .IP "\fBmime_boundary_length_limit (2048)\fR" +/* The maximal length of MIME multipart boundary strings. +/* .IP "\fBmime_nesting_limit (100)\fR" +/* The maximal recursion level that the MIME processor will handle. +/* EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtp_send_xforward_command (no)\fR" +/* Send the non-standard XFORWARD command when the Postfix SMTP server +/* EHLO response announces XFORWARD support. +/* SASL AUTHENTICATION CONTROLS +/* .ad +/* .fi +/* .IP "\fBsmtp_sasl_auth_enable (no)\fR" +/* Enable SASL authentication in the Postfix SMTP client. +/* .IP "\fBsmtp_sasl_password_maps (empty)\fR" +/* Optional Postfix SMTP client lookup tables with one username:password +/* entry per sender, remote hostname or next-hop domain. +/* .IP "\fBsmtp_sasl_security_options (noplaintext, noanonymous)\fR" +/* Postfix SMTP client SASL security options; as of Postfix 2.3 +/* the list of available +/* features depends on the SASL client implementation that is selected +/* with \fBsmtp_sasl_type\fR. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtp_sasl_mechanism_filter (empty)\fR" +/* If non-empty, a Postfix SMTP client filter for the remote SMTP +/* server's list of offered SASL mechanisms. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtp_sender_dependent_authentication (no)\fR" +/* Enable sender-dependent authentication in the Postfix SMTP client; this is +/* available only with SASL authentication, and disables SMTP connection +/* caching to ensure that mail from different senders will use the +/* appropriate credentials. +/* .IP "\fBsmtp_sasl_path (empty)\fR" +/* Implementation-specific information that the Postfix SMTP client +/* passes through to +/* the SASL plug-in implementation that is selected with +/* \fBsmtp_sasl_type\fR. +/* .IP "\fBsmtp_sasl_type (cyrus)\fR" +/* The SASL plug-in type that the Postfix SMTP client should use +/* for authentication. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtp_sasl_auth_cache_name (empty)\fR" +/* An optional table to prevent repeated SASL authentication +/* failures with the same remote SMTP server hostname, username and +/* password. +/* .IP "\fBsmtp_sasl_auth_cache_time (90d)\fR" +/* The maximal age of an smtp_sasl_auth_cache_name entry before it +/* is removed. +/* .IP "\fBsmtp_sasl_auth_soft_bounce (yes)\fR" +/* When a remote SMTP server rejects a SASL authentication request +/* with a 535 reply code, defer mail delivery instead of returning +/* mail as undeliverable. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR" +/* Whether or not to append the "AUTH=<>" option to the MAIL +/* FROM command in SASL-authenticated SMTP sessions. +/* STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* Detailed information about STARTTLS configuration may be found +/* in the TLS_README document. +/* .IP "\fBsmtp_tls_security_level (empty)\fR" +/* The default SMTP TLS security level for the Postfix SMTP client; +/* when a non-empty value is specified, this overrides the obsolete +/* parameters smtp_use_tls, smtp_enforce_tls, and smtp_tls_enforce_peername. +/* .IP "\fBsmtp_sasl_tls_security_options ($smtp_sasl_security_options)\fR" +/* The SASL authentication security options that the Postfix SMTP +/* client uses for TLS encrypted SMTP sessions. +/* .IP "\fBsmtp_starttls_timeout (300s)\fR" +/* Time limit for Postfix SMTP client write and read operations +/* during TLS startup and shutdown handshake procedures. +/* .IP "\fBsmtp_tls_CAfile (empty)\fR" +/* A file containing CA certificates of root CAs trusted to sign +/* either remote SMTP server certificates or intermediate CA certificates. +/* .IP "\fBsmtp_tls_CApath (empty)\fR" +/* Directory with PEM format Certification Authority certificates +/* that the Postfix SMTP client uses to verify a remote SMTP server +/* certificate. +/* .IP "\fBsmtp_tls_cert_file (empty)\fR" +/* File with the Postfix SMTP client RSA certificate in PEM format. +/* .IP "\fBsmtp_tls_mandatory_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP client will +/* use with +/* mandatory TLS encryption. +/* .IP "\fBsmtp_tls_exclude_ciphers (empty)\fR" +/* List of ciphers or cipher types to exclude from the Postfix +/* SMTP client cipher +/* list at all TLS security levels. +/* .IP "\fBsmtp_tls_mandatory_exclude_ciphers (empty)\fR" +/* Additional list of ciphers or cipher types to exclude from the +/* Postfix SMTP client cipher list at mandatory TLS security levels. +/* .IP "\fBsmtp_tls_dcert_file (empty)\fR" +/* File with the Postfix SMTP client DSA certificate in PEM format. +/* .IP "\fBsmtp_tls_dkey_file ($smtp_tls_dcert_file)\fR" +/* File with the Postfix SMTP client DSA private key in PEM format. +/* .IP "\fBsmtp_tls_key_file ($smtp_tls_cert_file)\fR" +/* File with the Postfix SMTP client RSA private key in PEM format. +/* .IP "\fBsmtp_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP client logging of TLS activity. +/* .IP "\fBsmtp_tls_note_starttls_offer (no)\fR" +/* Log the hostname of a remote SMTP server that offers STARTTLS, +/* when TLS is not already enabled for that server. +/* .IP "\fBsmtp_tls_policy_maps (empty)\fR" +/* Optional lookup tables with the Postfix SMTP client TLS security +/* policy by next-hop destination; when a non-empty value is specified, +/* this overrides the obsolete smtp_tls_per_site parameter. +/* .IP "\fBsmtp_tls_mandatory_protocols (!SSLv2, !SSLv3)\fR" +/* List of SSL/TLS protocols that the Postfix SMTP client will use with +/* mandatory TLS encryption. +/* .IP "\fBsmtp_tls_scert_verifydepth (9)\fR" +/* The verification depth for remote SMTP server certificates. +/* .IP "\fBsmtp_tls_secure_cert_match (nexthop, dot-nexthop)\fR" +/* How the Postfix SMTP client verifies the server certificate +/* peername for the "secure" TLS security level. +/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR" +/* Name of the file containing the optional Postfix SMTP client +/* TLS session cache. +/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR" +/* The expiration time of Postfix SMTP client TLS session cache +/* information. +/* .IP "\fBsmtp_tls_verify_cert_match (hostname)\fR" +/* How the Postfix SMTP client verifies the server certificate +/* peername for the +/* "verify" TLS security level. +/* .IP "\fBtls_daemon_random_bytes (32)\fR" +/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8) +/* process requests from the \fBtlsmgr\fR(8) server in order to seed its +/* internal pseudo random number generator (PRNG). +/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "high" grade ciphers. +/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "medium" or higher grade ciphers. +/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "low" or higher grade ciphers. +/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "export" or higher grade ciphers. +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" +/* The OpenSSL cipherlist for "NULL" grade ciphers that provide +/* authentication without encryption. +/* .PP +/* Available in Postfix version 2.4 and later: +/* .IP "\fBsmtp_sasl_tls_verified_security_options ($smtp_sasl_tls_security_options)\fR" +/* The SASL authentication security options that the Postfix SMTP +/* client uses for TLS encrypted SMTP sessions with a verified server +/* certificate. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtp_tls_fingerprint_cert_match (empty)\fR" +/* List of acceptable remote SMTP server certificate fingerprints for +/* the "fingerprint" TLS security level (\fBsmtp_tls_security_level\fR = +/* fingerprint). +/* .IP "\fBsmtp_tls_fingerprint_digest (md5)\fR" +/* The message digest algorithm used to construct remote SMTP server +/* certificate fingerprints. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBsmtp_tls_protocols (!SSLv2, !SSLv3)\fR" +/* List of TLS protocols that the Postfix SMTP client will exclude or +/* include with opportunistic TLS encryption. +/* .IP "\fBsmtp_tls_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP client +/* will use with opportunistic TLS encryption. +/* .IP "\fBsmtp_tls_eccert_file (empty)\fR" +/* File with the Postfix SMTP client ECDSA certificate in PEM format. +/* .IP "\fBsmtp_tls_eckey_file ($smtp_tls_eccert_file)\fR" +/* File with the Postfix SMTP client ECDSA private key in PEM format. +/* .PP +/* Available in Postfix version 2.7 and later: +/* .IP "\fBsmtp_tls_block_early_mail_reply (no)\fR" +/* Try to detect a mail hijacking attack based on a TLS protocol +/* vulnerability (CVE-2009-3555), where an attacker prepends malicious +/* HELO, MAIL, RCPT, DATA commands to a Postfix SMTP client TLS session. +/* .PP +/* Available in Postfix version 2.8 and later: +/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" +/* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .PP +/* Available in Postfix version 2.11-3.1: +/* .IP "\fBtls_dane_digest_agility (on)\fR" +/* Configure RFC7671 DANE TLSA digest algorithm agility. +/* .IP "\fBtls_dane_trust_anchor_digest_enable (yes)\fR" +/* Enable support for RFC 6698 (DANE TLSA) DNS records that contain +/* digests of trust-anchors with certificate usage "2". +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_tls_trust_anchor_file (empty)\fR" +/* Zero or more PEM-format files with trust-anchor certificates +/* and/or public keys. +/* .IP "\fBsmtp_tls_force_insecure_host_tlsa_lookup (no)\fR" +/* Lookup the associated DANE TLSA RRset even when a hostname is +/* not an alias and its address records lie in an unsigned zone. +/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" +/* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtp_tls_wrappermode (no)\fR" +/* Request that the Postfix SMTP client connects using the +/* legacy SMTPS protocol instead of using the STARTTLS command. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBsmtp_tls_dane_insecure_mx_policy (dane)\fR" +/* The TLS policy for MX hosts with "secure" TLSA records when the +/* nexthop destination security level is \fBdane\fR, but the MX +/* record was found via an "insecure" MX lookup. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtp_tls_connection_reuse (no)\fR" +/* Try to make multiple deliveries per TLS-encrypted connection. +/* .IP "\fBsmtp_tls_chain_files (empty)\fR" +/* List of one or more PEM files, each holding one or more private keys +/* directly followed by a corresponding certificate chain. +/* .IP "\fBsmtp_tls_servername (empty)\fR" +/* Optional name to send to the remote SMTP server in the TLS Server +/* Name Indication (SNI) extension. +/* .PP +/* Introduced with Postfix 3.4.6, 3.3.5, 3.2.10, and 3.1.13: +/* .IP "\fBtls_fast_shutdown_enable (yes)\fR" +/* A workaround for implementations that hang Postfix while shuting +/* down a TLS session, until Postfix times out. +/* OBSOLETE STARTTLS CONTROLS +/* .ad +/* .fi +/* The following configuration parameters exist for compatibility +/* with Postfix versions before 2.3. Support for these will +/* be removed in a future release. +/* .IP "\fBsmtp_use_tls (no)\fR" +/* Opportunistic mode: use TLS when a remote SMTP server announces +/* STARTTLS support, otherwise send the mail in the clear. +/* .IP "\fBsmtp_enforce_tls (no)\fR" +/* Enforcement mode: require that remote SMTP servers use TLS +/* encryption, and never send mail in the clear. +/* .IP "\fBsmtp_tls_enforce_peername (yes)\fR" +/* With mandatory TLS encryption, require that the remote SMTP +/* server hostname matches the information in the remote SMTP server +/* certificate. +/* .IP "\fBsmtp_tls_per_site (empty)\fR" +/* Optional lookup tables with the Postfix SMTP client TLS usage +/* policy by next-hop destination and by remote SMTP server hostname. +/* .IP "\fBsmtp_tls_cipherlist (empty)\fR" +/* Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS +/* cipher list. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBsmtp_connect_timeout (30s)\fR" +/* The Postfix SMTP client time limit for completing a TCP connection, or +/* zero (use the operating system built-in time limit). +/* .IP "\fBsmtp_helo_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the HELO or EHLO command, +/* and for receiving the initial remote SMTP server response. +/* .IP "\fBlmtp_lhlo_timeout (300s)\fR" +/* The Postfix LMTP client time limit for sending the LHLO command, +/* and for receiving the initial remote LMTP server response. +/* .IP "\fBsmtp_xforward_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the XFORWARD command, +/* and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_mail_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the MAIL FROM command, +/* and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_rcpt_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP RCPT TO +/* command, and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_data_init_timeout (120s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP DATA command, +/* and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_data_xfer_timeout (180s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP message content. +/* .IP "\fBsmtp_data_done_timeout (600s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP ".", and +/* for receiving the remote SMTP server response. +/* .IP "\fBsmtp_quit_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the QUIT command, +/* and for receiving the remote SMTP server response. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtp_mx_address_limit (5)\fR" +/* The maximal number of MX (mail exchanger) IP addresses that can +/* result from Postfix SMTP client mail exchanger lookups, or zero (no +/* limit). +/* .IP "\fBsmtp_mx_session_limit (2)\fR" +/* The maximal number of SMTP sessions per delivery request before +/* the Postfix SMTP client +/* gives up or delivers to a fall-back relay host, or zero (no +/* limit). +/* .IP "\fBsmtp_rset_timeout (20s)\fR" +/* The Postfix SMTP client time limit for sending the RSET command, +/* and for receiving the remote SMTP server response. +/* .PP +/* Available in Postfix version 2.2 and earlier: +/* .IP "\fBlmtp_cache_connection (yes)\fR" +/* Keep Postfix LMTP client connections open for up to $max_idle +/* seconds. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtp_connection_cache_destinations (empty)\fR" +/* Permanently enable SMTP connection caching for the specified +/* destinations. +/* .IP "\fBsmtp_connection_cache_on_demand (yes)\fR" +/* Temporarily enable SMTP connection caching while a destination +/* has a high volume of mail in the active queue. +/* .IP "\fBsmtp_connection_reuse_time_limit (300s)\fR" +/* The amount of time during which Postfix will use an SMTP +/* connection repeatedly. +/* .IP "\fBsmtp_connection_cache_time_limit (2s)\fR" +/* When SMTP connection caching is enabled, the amount of time that +/* an unused SMTP client socket is kept open before it is closed. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBconnection_cache_protocol_timeout (5s)\fR" +/* Time limit for connection cache connect, send or receive +/* operations. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtp_per_record_deadline (no)\fR" +/* Change the behavior of the smtp_*_timeout time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_connection_reuse_count_limit (0)\fR" +/* When SMTP connection caching is enabled, the number of times +/* that an SMTP session may be reused before it is closed, or zero (no +/* limit). +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtp_tls_connection_reuse (no)\fR" +/* Try to make multiple deliveries per TLS-encrypted connection. +/* .PP +/* Implemented in the qmgr(8) daemon: +/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR" +/* A transport-specific override for the +/* default_destination_recipient_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* SMTPUTF8 CONTROLS +/* .ad +/* .fi +/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. +/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR" +/* Detect that a message requires SMTPUTF8 support for the specified +/* mail origin classes. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBenable_idna2003_compatibility (no)\fR" +/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008, +/* when converting UTF-8 domain names to/from the ASCII form that is +/* used for DNS lookups. +/* TROUBLE SHOOTING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdebug_peer_level (2)\fR" +/* The increment in verbose logging level when a remote client or +/* server matches a pattern in the debug_peer_list parameter. +/* .IP "\fBdebug_peer_list (empty)\fR" +/* Optional list of remote client or server hostname or network +/* address patterns that cause the verbose logging level to increase +/* by the amount specified in $debug_peer_level. +/* .IP "\fBerror_notice_recipient (postmaster)\fR" +/* The recipient of postmaster notifications about mail delivery +/* problems that are caused by policy, resource, software or protocol +/* errors. +/* .IP "\fBinternal_mail_filter_classes (empty)\fR" +/* What categories of Postfix-generated mail are subject to +/* before-queue content inspection by non_smtpd_milters, header_checks +/* and body_checks. +/* .IP "\fBnotify_classes (resource, software)\fR" +/* The list of error classes that are reported to the postmaster. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBbest_mx_transport (empty)\fR" +/* Where the Postfix SMTP client should deliver mail when it detects +/* a "mail loops back to myself" error condition. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBdisable_dns_lookups (no)\fR" +/* Disable DNS lookups in the Postfix SMTP and LMTP clients. +/* .IP "\fBinet_interfaces (all)\fR" +/* The network interface addresses that this mail system receives +/* mail on. +/* .IP "\fBinet_protocols (all)\fR" +/* The Internet protocols Postfix will attempt to use when making +/* or accepting connections. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBlmtp_assume_final (no)\fR" +/* When a remote LMTP server announces no DSN support, assume that +/* the +/* server performs final delivery, and send "delivered" delivery status +/* notifications instead of "relayed". +/* .IP "\fBlmtp_tcp_port (24)\fR" +/* The default TCP port that the Postfix LMTP client connects to. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBproxy_interfaces (empty)\fR" +/* The network interface addresses that this mail system receives mail +/* on by way of a proxy or network address translation unit. +/* .IP "\fBsmtp_address_preference (any)\fR" +/* The address type ("ipv6", "ipv4" or "any") that the Postfix +/* SMTP client will try first, when a destination has IPv6 and IPv4 +/* addresses with equal MX preference. +/* .IP "\fBsmtp_bind_address (empty)\fR" +/* An optional numerical network address that the Postfix SMTP client +/* should bind to when making an IPv4 connection. +/* .IP "\fBsmtp_bind_address6 (empty)\fR" +/* An optional numerical network address that the Postfix SMTP client +/* should bind to when making an IPv6 connection. +/* .IP "\fBsmtp_helo_name ($myhostname)\fR" +/* The hostname to send in the SMTP HELO or EHLO command. +/* .IP "\fBlmtp_lhlo_name ($myhostname)\fR" +/* The hostname to send in the LMTP LHLO command. +/* .IP "\fBsmtp_host_lookup (dns)\fR" +/* What mechanisms the Postfix SMTP client uses to look up a host's +/* IP address. +/* .IP "\fBsmtp_randomize_addresses (yes)\fR" +/* Randomize the order of equal-preference MX host addresses. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available with Postfix 2.2 and earlier: +/* .IP "\fBfallback_relay (empty)\fR" +/* Optional list of relay hosts for SMTP destinations that can't be +/* found or that are unreachable. +/* .PP +/* Available with Postfix 2.3 and later: +/* .IP "\fBsmtp_fallback_relay ($fallback_relay)\fR" +/* Optional list of relay hosts for SMTP destinations that can't be +/* found or that are unreachable. +/* .PP +/* Available with Postfix 3.0 and later: +/* .IP "\fBsmtp_address_verify_target (rcpt)\fR" +/* In the context of email address verification, the SMTP protocol +/* stage that determines whether an email address is deliverable. +/* .PP +/* Available with Postfix 3.1 and later: +/* .IP "\fBlmtp_fallback_relay (empty)\fR" +/* Optional list of relay hosts for LMTP destinations that can't be +/* found or that are unreachable. +/* .PP +/* Available with Postfix 3.2 and later: +/* .IP "\fBsmtp_tcp_port (smtp)\fR" +/* The default TCP port that the Postfix SMTP client connects to. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* generic(5), output address rewriting +/* header_checks(5), message header content inspection +/* body_checks(5), body parts content inspection +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* scache(8), connection cache server +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* tlsmgr(8), TLS session and PRNG management +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* SASL_README, Postfix SASL howto +/* TLS_README, Postfix STARTTLS howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Command pipelining in cooperation with: +/* Jon Ribbens +/* Oaktree Internet Solutions Ltd., +/* Internet House, +/* Canal Basin, +/* Coventry, +/* CV1 4LY, United Kingdom. +/* +/* SASL support originally by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Revised TLS and SMTP connection cache support by: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <dict.h> +#include <stringops.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <name_mask.h> +#include <name_code.h> + +/* Global library. */ + +#include <deliver_request.h> +#include <mail_proto.h> +#include <mail_params.h> +#include <mail_version.h> +#include <mail_conf.h> +#include <debug_peer.h> +#include <flush_clnt.h> +#include <scache.h> +#include <string_list.h> +#include <maps.h> +#include <ext_prop.h> + +/* DNS library. */ + +#include <dns.h> + +/* Single server skeleton. */ + +#include <mail_server.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + + /* + * Tunable parameters. These have compiled-in defaults that can be overruled + * by settings in the global Postfix configuration file. + */ +int var_smtp_conn_tmout; +int var_smtp_helo_tmout; +int var_smtp_xfwd_tmout; +int var_smtp_mail_tmout; +int var_smtp_rcpt_tmout; +int var_smtp_data0_tmout; +int var_smtp_data1_tmout; +int var_smtp_data2_tmout; +int var_smtp_rset_tmout; +int var_smtp_quit_tmout; +char *var_inet_interfaces; +char *var_notify_classes; +int var_smtp_skip_5xx_greeting; +int var_ign_mx_lookup_err; +int var_skip_quit_resp; +char *var_fallback_relay; +char *var_bestmx_transp; +char *var_error_rcpt; +int var_smtp_always_ehlo; +int var_smtp_never_ehlo; +char *var_smtp_sasl_opts; +char *var_smtp_sasl_path; +char *var_smtp_sasl_passwd; +bool var_smtp_sasl_enable; +char *var_smtp_sasl_mechs; +char *var_smtp_sasl_type; +char *var_smtp_bind_addr; +char *var_smtp_bind_addr6; +char *var_smtp_vrfy_tgt; +bool var_smtp_rand_addr; +int var_smtp_pix_thresh; +int var_queue_run_delay; +int var_min_backoff_time; +int var_smtp_pix_delay; +int var_smtp_line_limit; +char *var_smtp_helo_name; +char *var_smtp_host_lookup; +bool var_smtp_quote_821_env; +bool var_smtp_defer_mxaddr; +bool var_smtp_send_xforward; +int var_smtp_mxaddr_limit; +int var_smtp_mxsess_limit; +int var_smtp_cache_conn; +int var_smtp_reuse_time; +int var_smtp_reuse_count; +char *var_smtp_cache_dest; +char *var_scache_service; /* You can now leave this here. */ +bool var_smtp_cache_demand; +char *var_smtp_ehlo_dis_words; +char *var_smtp_ehlo_dis_maps; +char *var_smtp_addr_pref; + +char *var_smtp_tls_level; +bool var_smtp_use_tls; +bool var_smtp_enforce_tls; +char *var_smtp_tls_per_site; +char *var_smtp_tls_policy; +bool var_smtp_tls_wrappermode; +bool var_smtp_tls_conn_reuse; +char *var_tlsproxy_service; + +#ifdef USE_TLS +char *var_smtp_sasl_tls_opts; +char *var_smtp_sasl_tlsv_opts; +int var_smtp_starttls_tmout; +char *var_smtp_tls_CAfile; +char *var_smtp_tls_CApath; +char *var_smtp_tls_chain_files; +char *var_smtp_tls_cert_file; +char *var_smtp_tls_mand_ciph; +char *var_smtp_tls_excl_ciph; +char *var_smtp_tls_mand_excl; +char *var_smtp_tls_dcert_file; +char *var_smtp_tls_dkey_file; +bool var_smtp_tls_enforce_peername; +char *var_smtp_tls_key_file; +char *var_smtp_tls_loglevel; +bool var_smtp_tls_note_starttls_offer; +char *var_smtp_tls_mand_proto; +char *var_smtp_tls_sec_cmatch; +int var_smtp_tls_scert_vd; +char *var_smtp_tls_vfy_cmatch; +char *var_smtp_tls_fpt_cmatch; +char *var_smtp_tls_fpt_dgst; +char *var_smtp_tls_tafile; +char *var_smtp_tls_proto; +char *var_smtp_tls_ciph; +char *var_smtp_tls_eccert_file; +char *var_smtp_tls_eckey_file; +char *var_smtp_tls_sni; +bool var_smtp_tls_blk_early_mail_reply; +bool var_smtp_tls_force_tlsa; +char *var_smtp_tls_insecure_mx_policy; + +#endif + +char *var_smtp_generic_maps; +char *var_prop_extension; +bool var_smtp_sender_auth; +char *var_smtp_tcp_port; +int var_scache_proto_tmout; +bool var_smtp_cname_overr; +char *var_smtp_pix_bug_words; +char *var_smtp_pix_bug_maps; +char *var_cyrus_conf_path; +char *var_smtp_head_chks; +char *var_smtp_mime_chks; +char *var_smtp_nest_chks; +char *var_smtp_body_chks; +char *var_smtp_resp_filter; +bool var_lmtp_assume_final; +char *var_smtp_dns_res_opt; +char *var_smtp_dns_support; +bool var_smtp_rec_deadline; +bool var_smtp_dummy_mail_auth; +char *var_smtp_dsn_filter; +char *var_smtp_dns_re_filter; +bool var_smtp_balance_inet_proto; + + /* Special handling of 535 AUTH errors. */ +char *var_smtp_sasl_auth_cache_name; +int var_smtp_sasl_auth_cache_time; +bool var_smtp_sasl_auth_soft_bounce; + + /* + * Global variables. + */ +int smtp_mode; +int smtp_host_lookup_mask; +int smtp_dns_support; +STRING_LIST *smtp_cache_dest; +SCACHE *smtp_scache; +MAPS *smtp_ehlo_dis_maps; +MAPS *smtp_generic_maps; +int smtp_ext_prop_mask; +unsigned smtp_dns_res_opt; +MAPS *smtp_pix_bug_maps; +HBC_CHECKS *smtp_header_checks; /* limited header checks */ +HBC_CHECKS *smtp_body_checks; /* limited body checks */ + +#ifdef USE_TLS + + /* + * OpenSSL client state (opaque handle) + */ +TLS_APPL_STATE *smtp_tls_ctx; +int smtp_tls_insecure_mx_policy; + +#endif + + /* + * IPv6 preference. + */ +static int smtp_addr_pref; + +/* deliver_message - deliver message with extreme prejudice */ + +static int deliver_message(const char *service, DELIVER_REQUEST *request) +{ + SMTP_STATE *state; + int result; + + if (msg_verbose) + msg_info("deliver_message: from %s", request->sender); + + /* + * Sanity checks. The smtp server is unprivileged and chrooted, so we can + * afford to distribute the data censoring code, instead of having it all + * in one place. + */ + if (request->nexthop[0] == 0) + msg_fatal("empty nexthop hostname"); + if (request->rcpt_list.len <= 0) + msg_fatal("recipient count: %d", request->rcpt_list.len); + + /* + * Initialize. Bundle all information about the delivery request, so that + * we can produce understandable diagnostics when something goes wrong + * many levels below. The alternative would be to make everything global. + */ + state = smtp_state_alloc(); + state->request = request; + state->src = request->fp; + state->service = service; + state->misc_flags |= smtp_addr_pref; + SMTP_RCPT_INIT(state); + + /* + * Establish an SMTP session and deliver this message to all requested + * recipients. At the end, notify the postmaster of any protocol errors. + * Optionally deliver mail locally when this machine is the best mail + * exchanger. + */ + result = smtp_connect(state); + + /* + * Clean up. + */ + smtp_state_free(state); + + return (result); +} + +/* smtp_service - perform service for client */ + +static void smtp_service(VSTREAM *client_stream, char *service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to remote SMTP delivery service. What we see below is a + * little protocol to (1) tell the queue manager that we are ready, (2) + * read a request from the queue manager, and (3) report the completion + * status of that request. All connection-management stuff is handled by + * the common code in single_server.c. + */ + if ((request = deliver_request_read(client_stream)) != 0) { + status = deliver_message(service, request); + deliver_request_done(client_stream, request, status); + } +} + +/* post_init - post-jail initialization */ + +static void post_init(char *unused_name, char **unused_argv) +{ + static const NAME_MASK lookup_masks[] = { + SMTP_HOST_LOOKUP_DNS, SMTP_HOST_FLAG_DNS, + SMTP_HOST_LOOKUP_NATIVE, SMTP_HOST_FLAG_NATIVE, + 0, + }; + static const NAME_MASK dns_res_opt_masks[] = { + SMTP_DNS_RES_OPT_DEFNAMES, RES_DEFNAMES, + SMTP_DNS_RES_OPT_DNSRCH, RES_DNSRCH, + 0, + }; + static const NAME_CODE dns_support[] = { + SMTP_DNS_SUPPORT_DISABLED, SMTP_DNS_DISABLED, + SMTP_DNS_SUPPORT_ENABLED, SMTP_DNS_ENABLED, +#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0) + SMTP_DNS_SUPPORT_DNSSEC, SMTP_DNS_DNSSEC, +#endif + 0, SMTP_DNS_INVALID, + }; + + if (*var_smtp_dns_support == 0) { + /* Backwards compatible empty setting */ + smtp_dns_support = + var_disable_dns ? SMTP_DNS_DISABLED : SMTP_DNS_ENABLED; + } else { + smtp_dns_support = + name_code(dns_support, NAME_CODE_FLAG_NONE, var_smtp_dns_support); + if (smtp_dns_support == SMTP_DNS_INVALID) + msg_fatal("invalid %s: \"%s\"", VAR_LMTP_SMTP(DNS_SUPPORT), + var_smtp_dns_support); + var_disable_dns = (smtp_dns_support == SMTP_DNS_DISABLED); + } + +#ifdef USE_TLS + if (smtp_mode) { + smtp_tls_insecure_mx_policy = + tls_level_lookup(var_smtp_tls_insecure_mx_policy); + switch (smtp_tls_insecure_mx_policy) { + case TLS_LEV_MAY: + case TLS_LEV_ENCRYPT: + case TLS_LEV_DANE: + break; + default: + msg_fatal("invalid %s: \"%s\"", VAR_SMTP_TLS_INSECURE_MX_POLICY, + var_smtp_tls_insecure_mx_policy); + } + } +#endif + + /* + * Select hostname lookup mechanisms. + */ + if (smtp_dns_support == SMTP_DNS_DISABLED) + smtp_host_lookup_mask = SMTP_HOST_FLAG_NATIVE; + else + smtp_host_lookup_mask = + name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks, + var_smtp_host_lookup); + if (msg_verbose) + msg_info("host name lookup methods: %s", + str_name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks, + smtp_host_lookup_mask)); + + /* + * Session cache instance. + */ + if (*var_smtp_cache_dest || var_smtp_cache_demand) +#if 0 + smtp_scache = scache_multi_create(); +#else + smtp_scache = scache_clnt_create(var_scache_service, + var_scache_proto_tmout, + var_ipc_idle_limit, + var_ipc_ttl_limit); +#endif + + /* + * Select DNS query flags. + */ + smtp_dns_res_opt = name_mask(VAR_LMTP_SMTP(DNS_RES_OPT), dns_res_opt_masks, + var_smtp_dns_res_opt); + + /* + * Address verification. + */ + smtp_vrfy_init(); +} + +/* pre_init - pre-jail initialization */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + int use_tls; + static const NAME_CODE addr_pref_map[] = { + INET_PROTO_NAME_IPV6, SMTP_MISC_FLAG_PREF_IPV6, + INET_PROTO_NAME_IPV4, SMTP_MISC_FLAG_PREF_IPV4, + INET_PROTO_NAME_ANY, 0, + 0, -1, + }; + + /* + * Turn on per-peer debugging. + */ + debug_peer_init(); + + /* + * SASL initialization. + */ + if (var_smtp_sasl_enable) +#ifdef USE_SASL_AUTH + smtp_sasl_initialize(); +#else + msg_warn("%s is true, but SASL support is not compiled in", + VAR_LMTP_SMTP(SASL_ENABLE)); +#endif + + if (*var_smtp_tls_level != 0) + switch (tls_level_lookup(var_smtp_tls_level)) { + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_DANE_ONLY: + case TLS_LEV_FPRINT: + case TLS_LEV_ENCRYPT: + var_smtp_use_tls = var_smtp_enforce_tls = 1; + break; + case TLS_LEV_DANE: + case TLS_LEV_MAY: + var_smtp_use_tls = 1; + var_smtp_enforce_tls = 0; + break; + case TLS_LEV_NONE: + var_smtp_use_tls = var_smtp_enforce_tls = 0; + break; + default: + /* tls_level_lookup() logs no warning. */ + /* session_tls_init() assumes that var_smtp_tls_level is sane. */ + msg_fatal("Invalid TLS level \"%s\"", var_smtp_tls_level); + } + use_tls = (var_smtp_use_tls || var_smtp_enforce_tls); + + /* + * Initialize the TLS data before entering the chroot jail + */ + if (use_tls || var_smtp_tls_per_site[0] || var_smtp_tls_policy[0]) { +#ifdef USE_TLS + TLS_CLIENT_INIT_PROPS props; + + tls_pre_jail_init(TLS_ROLE_CLIENT); + + /* + * We get stronger type safety and a cleaner interface by combining + * the various parameters into a single tls_client_props structure. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + * + * With tlsproxy(8) turned on, this is still needed for DANE-related + * initializations. + */ + smtp_tls_ctx = + TLS_CLIENT_INIT(&props, + log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL), + log_level = var_smtp_tls_loglevel, + verifydepth = var_smtp_tls_scert_vd, + cache_type = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE), + chain_files = var_smtp_tls_chain_files, + cert_file = var_smtp_tls_cert_file, + key_file = var_smtp_tls_key_file, + dcert_file = var_smtp_tls_dcert_file, + dkey_file = var_smtp_tls_dkey_file, + eccert_file = var_smtp_tls_eccert_file, + eckey_file = var_smtp_tls_eckey_file, + CAfile = var_smtp_tls_CAfile, + CApath = var_smtp_tls_CApath, + mdalg = var_smtp_tls_fpt_dgst); + smtp_tls_list_init(); +#else + msg_warn("TLS has been selected, but TLS support is not compiled in"); +#endif + } + + /* + * Flush client. + */ + flush_init(); + + /* + * Session cache domain list. + */ + if (*var_smtp_cache_dest) + smtp_cache_dest = string_list_init(VAR_SMTP_CACHE_DEST, + MATCH_FLAG_RETURN, + var_smtp_cache_dest); + + /* + * EHLO keyword filter. + */ + if (*var_smtp_ehlo_dis_maps) + smtp_ehlo_dis_maps = maps_create(VAR_LMTP_SMTP(EHLO_DIS_MAPS), + var_smtp_ehlo_dis_maps, + DICT_FLAG_LOCK); + + /* + * PIX bug workarounds. + */ + if (*var_smtp_pix_bug_maps) + smtp_pix_bug_maps = maps_create(VAR_LMTP_SMTP(PIX_BUG_MAPS), + var_smtp_pix_bug_maps, + DICT_FLAG_LOCK); + + /* + * Generic maps. + */ + if (*var_prop_extension) + smtp_ext_prop_mask = + ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension); + if (*var_smtp_generic_maps) + smtp_generic_maps = + maps_create(VAR_LMTP_SMTP(GENERIC_MAPS), var_smtp_generic_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + + /* + * Header/body checks. + */ + smtp_header_checks = hbc_header_checks_create( + VAR_LMTP_SMTP(HEAD_CHKS), var_smtp_head_chks, + VAR_LMTP_SMTP(MIME_CHKS), var_smtp_mime_chks, + VAR_LMTP_SMTP(NEST_CHKS), var_smtp_nest_chks, + smtp_hbc_callbacks); + smtp_body_checks = hbc_body_checks_create( + VAR_LMTP_SMTP(BODY_CHKS), var_smtp_body_chks, + smtp_hbc_callbacks); + + /* + * Server reply filter. + */ + if (*var_smtp_resp_filter) + smtp_chat_resp_filter = + dict_open(var_smtp_resp_filter, O_RDONLY, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + + /* + * Address family preference. + */ + if (*var_smtp_addr_pref) { + smtp_addr_pref = name_code(addr_pref_map, NAME_CODE_FLAG_NONE, + var_smtp_addr_pref); + if (smtp_addr_pref < 0) + msg_fatal("bad %s value: %s", VAR_LMTP_SMTP(ADDR_PREF), + var_smtp_addr_pref); + } + + /* + * DNS reply filter. + */ + if (*var_smtp_dns_re_filter) + dns_rr_filter_compile(VAR_LMTP_SMTP(DNS_RE_FILTER), + var_smtp_dns_re_filter); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + char *sane_procname; + +#include "smtp_params.c" +#include "lmtp_params.c" + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * XXX At this point, var_procname etc. are not initialized. + * + * The process name, "smtp" or "lmtp", determines the protocol, the DSN + * server reply type, SASL service information lookup, and more. Prepare + * for the possibility there may be another personality. + */ + sane_procname = sane_basename((VSTRING *) 0, argv[0]); + if (strcmp(sane_procname, "smtp") == 0) + smtp_mode = 1; + else if (strcmp(sane_procname, "lmtp") == 0) + smtp_mode = 0; + else + msg_fatal("unexpected process name \"%s\" - " + "specify \"smtp\" or \"lmtp\"", var_procname); + + /* + * Initialize with the LMTP or SMTP parameter name space. + */ + single_server_main(argc, argv, smtp_service, + CA_MAIL_SERVER_TIME_TABLE(smtp_mode ? + smtp_time_table : lmtp_time_table), + CA_MAIL_SERVER_INT_TABLE(smtp_mode ? + smtp_int_table : lmtp_int_table), + CA_MAIL_SERVER_STR_TABLE(smtp_mode ? + smtp_str_table : lmtp_str_table), + CA_MAIL_SERVER_BOOL_TABLE(smtp_mode ? + smtp_bool_table : lmtp_bool_table), + CA_MAIL_SERVER_PRE_INIT(pre_init), + CA_MAIL_SERVER_POST_INIT(post_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_BOUNCE_INIT(VAR_SMTP_DSN_FILTER, + &var_smtp_dsn_filter), + 0); +} diff --git a/src/smtp/smtp.h b/src/smtp/smtp.h new file mode 100644 index 0000000..ed3e6a5 --- /dev/null +++ b/src/smtp/smtp.h @@ -0,0 +1,727 @@ +/*++ +/* NAME +/* smtp 3h +/* SUMMARY +/* smtp client program +/* SYNOPSIS +/* #include "smtp.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <string.h> + + /* + * Utility library. + */ +#include <vstream.h> +#include <vstring.h> +#include <argv.h> +#include <htable.h> +#include <dict.h> + + /* + * Global library. + */ +#include <deliver_request.h> +#include <scache.h> +#include <string_list.h> +#include <maps.h> +#include <tok822.h> +#include <dsn_buf.h> +#include <header_body_checks.h> + + /* + * Postfix TLS library. + */ +#include <tls.h> + + /* + * tlsproxy client. + */ +#include <tls_proxy.h> + + /* + * Global iterator support. This is updated by the connection-management + * loop, and contains dynamic context that appears in lookup keys for SASL + * passwords, TLS policy, cached SMTP connections, and cached TLS session + * keys. + * + * For consistency and maintainability, context that is used in more than one + * lookup key is formatted with smtp_key_format(). + */ +typedef struct SMTP_ITERATOR { + /* Public members. */ + VSTRING *request_nexthop; /* delivery request nexhop or empty */ + VSTRING *dest; /* current nexthop */ + VSTRING *host; /* hostname or empty */ + VSTRING *addr; /* printable address or empty */ + unsigned port; /* network byte order or null */ + struct DNS_RR *rr; /* DNS resource record or null */ + struct DNS_RR *mx; /* DNS resource record or null */ + /* Private members. */ + VSTRING *saved_dest; /* saved current nexthop */ + struct SMTP_STATE *parent; /* parent linkage */ +} SMTP_ITERATOR; + +#define SMTP_ITER_INIT(iter, _dest, _host, _addr, _port, state) do { \ + vstring_strcpy((iter)->dest, (_dest)); \ + vstring_strcpy((iter)->host, (_host)); \ + vstring_strcpy((iter)->addr, (_addr)); \ + (iter)->port = (_port); \ + (iter)->mx = (iter)->rr = 0; \ + vstring_strcpy((iter)->saved_dest, ""); \ + (iter)->parent = (state); \ + } while (0) + +#define SMTP_ITER_SAVE_DEST(iter) do { \ + vstring_strcpy((iter)->saved_dest, STR((iter)->dest)); \ + } while (0) + +#define SMTP_ITER_RESTORE_DEST(iter) do { \ + vstring_strcpy((iter)->dest, STR((iter)->saved_dest)); \ + } while (0) + + /* + * TLS Policy support. + */ +#ifdef USE_TLS + +typedef struct SMTP_TLS_POLICY { + int level; /* TLS enforcement level */ + char *protocols; /* Acceptable SSL protocols */ + char *grade; /* Cipher grade: "export", ... */ + VSTRING *exclusions; /* Excluded SSL ciphers */ + ARGV *matchargv; /* Cert match patterns */ + DSN_BUF *why; /* Lookup error status */ + TLS_DANE *dane; /* DANE TLSA digests */ + char *sni; /* Optional SNI name when not DANE */ + int conn_reuse; /* enable connection reuse */ +} SMTP_TLS_POLICY; + + /* + * smtp_tls_policy.c + */ +extern void smtp_tls_list_init(void); +extern int smtp_tls_policy_cache_query(DSN_BUF *, SMTP_TLS_POLICY *, SMTP_ITERATOR *); +extern void smtp_tls_policy_cache_flush(void); + + /* + * Macros must use distinct names for local temporary variables, otherwise + * there will be bugs due to shadowing. This happened when an earlier + * version of smtp_tls_policy_dummy() invoked smtp_tls_policy_init(), but it + * could also happen without macro nesting. + * + * General principle: use all or part of the macro name in each temporary + * variable name. Then, append suffixes to the names if needed. + */ +#define smtp_tls_policy_dummy(t) do { \ + SMTP_TLS_POLICY *_tls_policy_dummy_tmp = (t); \ + smtp_tls_policy_init(_tls_policy_dummy_tmp, (DSN_BUF *) 0); \ + _tls_policy_dummy_tmp->level = TLS_LEV_NONE; \ + } while (0) + + /* This macro is not part of the module external interface. */ +#define smtp_tls_policy_init(t, w) do { \ + SMTP_TLS_POLICY *_tls_policy_init_tmp = (t); \ + _tls_policy_init_tmp->protocols = 0; \ + _tls_policy_init_tmp->grade = 0; \ + _tls_policy_init_tmp->exclusions = 0; \ + _tls_policy_init_tmp->matchargv = 0; \ + _tls_policy_init_tmp->why = (w); \ + _tls_policy_init_tmp->dane = 0; \ + _tls_policy_init_tmp->sni = 0; \ + _tls_policy_init_tmp->conn_reuse = 0; \ + } while (0) + +#endif + + /* + * State information associated with each SMTP delivery request. + * Session-specific state is stored separately. + */ +typedef struct SMTP_STATE { + int misc_flags; /* processing flags, see below */ + VSTREAM *src; /* queue file stream */ + const char *service; /* transport name */ + DELIVER_REQUEST *request; /* envelope info, offsets */ + struct SMTP_SESSION *session; /* network connection */ + int status; /* delivery status */ + ssize_t space_left; /* output length control */ + + /* + * Global iterator. + */ + SMTP_ITERATOR iterator[1]; /* Usage: state->iterator->member */ + + /* + * Global iterator. + */ +#ifdef USE_TLS + SMTP_TLS_POLICY tls[1]; /* Usage: state->tls->member */ +#endif + + /* + * Connection cache support. + */ + HTABLE *cache_used; /* cached addresses that were used */ + VSTRING *dest_label; /* cached logical/physical binding */ + VSTRING *dest_prop; /* binding properties, passivated */ + VSTRING *endp_label; /* cached session physical endpoint */ + VSTRING *endp_prop; /* endpoint properties, passivated */ + + /* + * Flags and counters to control the handling of mail delivery errors. + * There is some redundancy for sanity checking. At the end of an SMTP + * session all recipients should be marked one way or the other. + */ + int rcpt_left; /* recipients left over */ + int rcpt_drop; /* recipients marked as drop */ + int rcpt_keep; /* recipients marked as keep */ + + /* + * DSN Support introduced major bloat in error processing. + */ + DSN_BUF *why; /* on-the-fly formatting buffer */ +} SMTP_STATE; + + /* + * Primitives to enable/disable/test connection caching and reuse based on + * the delivery request next-hop destination (i.e. not smtp_fallback_relay). + * + * Connection cache lookup by the delivery request next-hop destination allows + * a reuse request to skip over bad hosts, and may result in a connection to + * a fall-back relay. Once we have found a 'good' host for a delivery + * request next-hop, clear the delivery request next-hop destination, to + * avoid caching less-preferred connections under that same delivery request + * next-hop. + */ +#define SET_SCACHE_REQUEST_NEXTHOP(state, nexthop) do { \ + vstring_strcpy((state)->iterator->request_nexthop, nexthop); \ + } while (0) + +#define CLEAR_SCACHE_REQUEST_NEXTHOP(state) do { \ + STR((state)->iterator->request_nexthop)[0] = 0; \ + } while (0) + +#define HAVE_SCACHE_REQUEST_NEXTHOP(state) \ + (STR((state)->iterator->request_nexthop)[0] != 0) + + + /* + * Server features. + */ +#define SMTP_FEATURE_ESMTP (1<<0) +#define SMTP_FEATURE_8BITMIME (1<<1) +#define SMTP_FEATURE_PIPELINING (1<<2) +#define SMTP_FEATURE_SIZE (1<<3) +#define SMTP_FEATURE_STARTTLS (1<<4) +#define SMTP_FEATURE_AUTH (1<<5) +#define SMTP_FEATURE_XFORWARD_NAME (1<<7) +#define SMTP_FEATURE_XFORWARD_ADDR (1<<8) +#define SMTP_FEATURE_XFORWARD_PROTO (1<<9) +#define SMTP_FEATURE_XFORWARD_HELO (1<<10) +#define SMTP_FEATURE_XFORWARD_DOMAIN (1<<11) +#define SMTP_FEATURE_BEST_MX (1<<12) /* for next-hop or fall-back */ +#define SMTP_FEATURE_RSET_REJECTED (1<<13) /* RSET probe rejected */ +#define SMTP_FEATURE_FROM_CACHE (1<<14) /* cached connection */ +#define SMTP_FEATURE_DSN (1<<15) /* DSN supported */ +#define SMTP_FEATURE_PIX_NO_ESMTP (1<<16) /* PIX smtp fixup mode */ +#define SMTP_FEATURE_PIX_DELAY_DOTCRLF (1<<17) /* PIX smtp fixup mode */ +#define SMTP_FEATURE_XFORWARD_PORT (1<<18) +#define SMTP_FEATURE_EARLY_TLS_MAIL_REPLY (1<<19) /* CVE-2009-3555 */ +#define SMTP_FEATURE_XFORWARD_IDENT (1<<20) +#define SMTP_FEATURE_SMTPUTF8 (1<<21) /* RFC 6531 */ +#define SMTP_FEATURE_FROM_PROXY (1<<22) /* proxied connection */ + + /* + * Features that passivate under the endpoint. + */ +#define SMTP_FEATURE_ENDPOINT_MASK \ + (~(SMTP_FEATURE_BEST_MX | SMTP_FEATURE_RSET_REJECTED \ + | SMTP_FEATURE_FROM_CACHE)) + + /* + * Features that passivate under the logical destination. + */ +#define SMTP_FEATURE_DESTINATION_MASK (SMTP_FEATURE_BEST_MX) + + /* + * Misc flags. + */ +#define SMTP_MISC_FLAG_LOOP_DETECT (1<<0) +#define SMTP_MISC_FLAG_IN_STARTTLS (1<<1) +#define SMTP_MISC_FLAG_FIRST_NEXTHOP (1<<2) +#define SMTP_MISC_FLAG_FINAL_NEXTHOP (1<<3) +#define SMTP_MISC_FLAG_FINAL_SERVER (1<<4) +#define SMTP_MISC_FLAG_CONN_LOAD (1<<5) +#define SMTP_MISC_FLAG_CONN_STORE (1<<6) +#define SMTP_MISC_FLAG_COMPLETE_SESSION (1<<7) +#define SMTP_MISC_FLAG_PREF_IPV6 (1<<8) +#define SMTP_MISC_FLAG_PREF_IPV4 (1<<9) + +#define SMTP_MISC_FLAG_CONN_CACHE_MASK \ + (SMTP_MISC_FLAG_CONN_LOAD | SMTP_MISC_FLAG_CONN_STORE) + + /* + * smtp.c + */ +#define SMTP_HAS_DSN(why) (STR((why)->status)[0] != 0) +#define SMTP_HAS_SOFT_DSN(why) (STR((why)->status)[0] == '4') +#define SMTP_HAS_HARD_DSN(why) (STR((why)->status)[0] == '5') +#define SMTP_HAS_LOOP_DSN(why) \ + (SMTP_HAS_DSN(why) && strcmp(STR((why)->status) + 1, ".4.6") == 0) + +#define SMTP_SET_SOFT_DSN(why) (STR((why)->status)[0] = '4') +#define SMTP_SET_HARD_DSN(why) (STR((why)->status)[0] = '5') + +extern int smtp_host_lookup_mask; /* host lookup methods to use */ + +#define SMTP_HOST_FLAG_DNS (1<<0) +#define SMTP_HOST_FLAG_NATIVE (1<<1) + +extern int smtp_dns_support; /* dns support level */ + +#define SMTP_DNS_INVALID (-1) /* smtp_dns_support_level = <bogus> */ +#define SMTP_DNS_DISABLED 0 /* smtp_dns_support_level = disabled */ +#define SMTP_DNS_ENABLED 1 /* smtp_dns_support_level = enabled */ +#define SMTP_DNS_DNSSEC 2 /* smtp_dns_support_level = dnssec */ + +extern SCACHE *smtp_scache; /* connection cache instance */ +extern STRING_LIST *smtp_cache_dest; /* cached destinations */ + +extern MAPS *smtp_ehlo_dis_maps; /* ehlo keyword filter */ + +extern MAPS *smtp_pix_bug_maps; /* PIX workarounds */ + +extern MAPS *smtp_generic_maps; /* make internal address valid */ +extern int smtp_ext_prop_mask; /* address externsion propagation */ +extern unsigned smtp_dns_res_opt; /* DNS query flags */ + +#ifdef USE_TLS + +extern TLS_APPL_STATE *smtp_tls_ctx; /* client-side TLS engine */ +extern int smtp_tls_insecure_mx_policy; /* DANE post insecure MX? */ + +#endif + +extern HBC_CHECKS *smtp_header_checks; /* limited header checks */ +extern HBC_CHECKS *smtp_body_checks; /* limited body checks */ + + /* + * smtp_session.c + */ + +typedef struct SMTP_SESSION { + VSTREAM *stream; /* network connection */ + SMTP_ITERATOR *iterator; /* dest, host, addr, port */ + char *namaddr; /* mail exchanger */ + char *helo; /* helo response */ + unsigned port; /* network byte order */ + char *namaddrport; /* mail exchanger, incl. port */ + + VSTRING *buffer; /* I/O buffer */ + VSTRING *scratch; /* scratch buffer */ + VSTRING *scratch2; /* scratch buffer */ + + int features; /* server features */ + off_t size_limit; /* server limit or unknown */ + + ARGV *history; /* transaction log */ + int error_mask; /* error classes */ + struct MIME_STATE *mime_state; /* mime state machine */ + + int send_proto_helo; /* XFORWARD support */ + + time_t expire_time; /* session reuse expiration time */ + int reuse_count; /* # of times reused (for logging) */ + int forbidden; /* No further I/O allowed */ + +#ifdef USE_SASL_AUTH + char *sasl_mechanism_list; /* server mechanism list */ + char *sasl_username; /* client username */ + char *sasl_passwd; /* client password */ + struct XSASL_CLIENT *sasl_client; /* SASL internal state */ + VSTRING *sasl_reply; /* client response */ +#endif + + /* + * TLS related state, don't forget to initialize in session_tls_init()! + */ +#ifdef USE_TLS + TLS_SESS_STATE *tls_context; /* TLS library session state */ + char *tls_nexthop; /* Nexthop domain for cert checks */ + int tls_retry_plain; /* Try plain when TLS handshake fails */ +#endif + + SMTP_STATE *state; /* back link */ +} SMTP_SESSION; + +extern SMTP_SESSION *smtp_session_alloc(VSTREAM *, SMTP_ITERATOR *, time_t, int); +extern void smtp_session_new_stream(SMTP_SESSION *, VSTREAM *, time_t, int); +extern int smtp_sess_plaintext_ok(SMTP_ITERATOR *, int); +extern void smtp_session_free(SMTP_SESSION *); +extern int smtp_session_passivate(SMTP_SESSION *, VSTRING *, VSTRING *); +extern SMTP_SESSION *smtp_session_activate(int, SMTP_ITERATOR *, VSTRING *, VSTRING *); + + /* + * What's in a name? + */ +#define SMTP_HNAME(rr) (var_smtp_cname_overr ? (rr)->rname : (rr)->qname) + + /* + * smtp_connect.c + */ +extern int smtp_connect(SMTP_STATE *); + + /* + * smtp_proto.c + */ +extern void smtp_vrfy_init(void); +extern int smtp_helo(SMTP_STATE *); +extern int smtp_xfer(SMTP_STATE *); +extern int smtp_rset(SMTP_STATE *); +extern int smtp_quit(SMTP_STATE *); + +extern HBC_CALL_BACKS smtp_hbc_callbacks[]; + + /* + * A connection is re-usable if session->expire_time is > 0 and the + * expiration time has not been reached. This is subtle because the timer + * can expire between sending a command and receiving the reply for that + * command. + * + * But wait, there is more! When SMTP command pipelining is enabled, there are + * two protocol loops that execute at very different times: one loop that + * generates commands, and one loop that receives replies to those commands. + * These will be called "sender loop" and "receiver loop", respectively. At + * well-defined protocol synchronization points, the sender loop pauses to + * let the receiver loop catch up. + * + * When we choose to reuse a connection, both the sender and receiver protocol + * loops end with "." (mail delivery) or "RSET" (address probe). When we + * choose not to reuse, both the sender and receiver protocol loops end with + * "QUIT". The problem is that we must make the same protocol choices in + * both the sender and receiver loops, even though those loops may execute + * at completely different times. + * + * We "freeze" the choice in the sender loop, just before we generate "." or + * "RSET". The reader loop leaves the connection cacheable even if the timer + * expires by the time the response arrives. The connection cleanup code + * will call smtp_quit() for connections with an expired cache expiration + * timer. + * + * We could have made the programmer's life a lot simpler by not making a + * choice at all, and always leaving it up to the connection cleanup code to + * call smtp_quit() for connections with an expired cache expiration timer. + * + * As a general principle, neither the sender loop nor the receiver loop must + * modify the connection caching state, if that can affect the receiver + * state machine for not-yet processed replies to already-generated + * commands. This restriction does not apply when we have to exit the + * protocol loops prematurely due to e.g., timeout or connection loss, so + * that those pending replies will never be received. + * + * But wait, there is even more! Only the first good connection for a specific + * destination may be cached under both the next-hop destination name and + * the server address; connections to alternate servers must be cached under + * the server address alone. This means we must distinguish between bad + * connections and other reasons why connections cannot be cached. + */ +#define THIS_SESSION_IS_CACHED \ + (!THIS_SESSION_IS_FORBIDDEN && session->expire_time > 0) + +#define THIS_SESSION_IS_EXPIRED \ + (THIS_SESSION_IS_CACHED \ + && (session->expire_time < vstream_ftime(session->stream) \ + || (var_smtp_reuse_count > 0 \ + && session->reuse_count >= var_smtp_reuse_count))) + +#define THIS_SESSION_IS_THROTTLED \ + (!THIS_SESSION_IS_FORBIDDEN && session->expire_time < 0) + +#define THIS_SESSION_IS_FORBIDDEN \ + (session->forbidden != 0) + + /* Bring the bad news. */ + +#define DONT_CACHE_THIS_SESSION \ + (session->expire_time = 0) + +#define DONT_CACHE_THROTTLED_SESSION \ + (session->expire_time = -1) + +#define DONT_USE_FORBIDDEN_SESSION \ + (session->forbidden = 1) + + /* Initialization. */ + +#define USE_NEWBORN_SESSION \ + (session->forbidden = 0) + +#define CACHE_THIS_SESSION_UNTIL(when) \ + (session->expire_time = (when)) + + /* + * Encapsulate the following so that we don't expose details of of + * connection management and error handling to the SMTP protocol engine. + */ +#ifdef USE_SASL_AUTH +#define HAVE_SASL_CREDENTIALS \ + (var_smtp_sasl_enable \ + && *var_smtp_sasl_passwd \ + && smtp_sasl_passwd_lookup(session)) +#else +#define HAVE_SASL_CREDENTIALS (0) +#endif + +#define PREACTIVE_DELAY \ + (session->state->request->msg_stats.active_arrival.tv_sec - \ + session->state->request->msg_stats.incoming_arrival.tv_sec) + +#define PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE \ + (session->tls_context == 0 \ + && state->tls->level == TLS_LEV_MAY \ + && PREACTIVE_DELAY >= var_min_backoff_time \ + && !HAVE_SASL_CREDENTIALS) + +#define PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE \ + (session->tls_context != 0 \ + && SMTP_RCPT_LEFT(state) > SMTP_RCPT_MARK_COUNT(state) \ + && state->tls->level == TLS_LEV_MAY \ + && PREACTIVE_DELAY >= var_min_backoff_time \ + && !HAVE_SASL_CREDENTIALS) + + /* + * XXX The following will not retry recipients that were deferred while the + * SMTP_MISC_FLAG_FINAL_SERVER flag was already set. This includes the case + * when TLS fails in the middle of a delivery. + */ +#define RETRY_AS_PLAINTEXT do { \ + session->tls_retry_plain = 1; \ + state->misc_flags &= ~SMTP_MISC_FLAG_FINAL_SERVER; \ + } while (0) + + /* + * smtp_chat.c + */ +typedef struct SMTP_RESP { /* server response */ + int code; /* SMTP code */ + const char *dsn; /* enhanced status */ + char *str; /* full reply */ + VSTRING *dsn_buf; /* status buffer */ + VSTRING *str_buf; /* reply buffer */ +} SMTP_RESP; + +extern void PRINTFLIKE(2, 3) smtp_chat_cmd(SMTP_SESSION *, const char *,...); +extern DICT *smtp_chat_resp_filter; +extern SMTP_RESP *smtp_chat_resp(SMTP_SESSION *); +extern void smtp_chat_init(SMTP_SESSION *); +extern void smtp_chat_reset(SMTP_SESSION *); +extern void smtp_chat_notify(SMTP_SESSION *); + +#define SMTP_RESP_FAKE(resp, _dsn) \ + ((resp)->code = 0, \ + (resp)->dsn = (_dsn), \ + (resp)->str = DSN_BY_LOCAL_MTA, \ + (resp)) + +#define DSN_BY_LOCAL_MTA ((char *) 0) /* DSN issued by local MTA */ + +#define SMTP_RESP_SET_DSN(resp, _dsn) do { \ + vstring_strcpy((resp)->dsn_buf, (_dsn)); \ + (resp)->dsn = STR((resp)->dsn_buf); \ + } while (0) + + /* + * These operations implement a redundant mark-and-sweep algorithm that + * explicitly accounts for the fate of every recipient. The interface is + * documented in smtp_rcpt.c, which also implements the sweeping. The + * smtp_trouble.c module does most of the marking after failure. + * + * When a delivery fails or succeeds, take one of the following actions: + * + * - Mark the recipient as KEEP (deliver to alternate MTA) and do not update + * the delivery request status. + * + * - Mark the recipient as DROP (remove from delivery request), log whether + * delivery succeeded or failed, delete the recipient from the queue file + * and/or update defer or bounce logfiles, and update the delivery request + * status. + * + * At the end of a delivery attempt, all recipients must be marked one way or + * the other. Failure to do so will trigger a panic. + */ +#define SMTP_RCPT_STATE_KEEP 1 /* send to backup host */ +#define SMTP_RCPT_STATE_DROP 2 /* remove from request */ +#define SMTP_RCPT_INIT(state) do { \ + (state)->rcpt_drop = (state)->rcpt_keep = 0; \ + (state)->rcpt_left = state->request->rcpt_list.len; \ + } while (0) + +#define SMTP_RCPT_DROP(state, rcpt) do { \ + (rcpt)->u.status = SMTP_RCPT_STATE_DROP; (state)->rcpt_drop++; \ + } while (0) + +#define SMTP_RCPT_KEEP(state, rcpt) do { \ + (rcpt)->u.status = SMTP_RCPT_STATE_KEEP; (state)->rcpt_keep++; \ + } while (0) + +#define SMTP_RCPT_ISMARKED(rcpt) ((rcpt)->u.status != 0) + +#define SMTP_RCPT_LEFT(state) (state)->rcpt_left + +#define SMTP_RCPT_MARK_COUNT(state) ((state)->rcpt_drop + (state)->rcpt_keep) + +extern void smtp_rcpt_cleanup(SMTP_STATE *); +extern void smtp_rcpt_done(SMTP_STATE *, SMTP_RESP *, RECIPIENT *); + + /* + * smtp_trouble.c + */ +#define SMTP_THROTTLE 1 +#define SMTP_NOTHROTTLE 0 +extern int smtp_sess_fail(SMTP_STATE *); +extern int PRINTFLIKE(5, 6) smtp_misc_fail(SMTP_STATE *, int, const char *, + SMTP_RESP *, const char *,...); +extern void PRINTFLIKE(5, 6) smtp_rcpt_fail(SMTP_STATE *, RECIPIENT *, + const char *, SMTP_RESP *, + const char *,...); +extern int smtp_stream_except(SMTP_STATE *, int, const char *); + +#define smtp_site_fail(state, mta, resp, ...) \ + smtp_misc_fail((state), SMTP_THROTTLE, (mta), (resp), __VA_ARGS__) +#define smtp_mesg_fail(state, mta, resp, ...) \ + smtp_misc_fail((state), SMTP_NOTHROTTLE, (mta), (resp), __VA_ARGS__) + + /* + * smtp_unalias.c + */ +extern const char *smtp_unalias_name(const char *); +extern VSTRING *smtp_unalias_addr(VSTRING *, const char *); + + /* + * smtp_state.c + */ +extern SMTP_STATE *smtp_state_alloc(void); +extern void smtp_state_free(SMTP_STATE *); + + /* + * smtp_map11.c + */ +extern int smtp_map11_external(VSTRING *, MAPS *, int); +extern int smtp_map11_tree(TOK822 *, MAPS *, int); +extern int smtp_map11_internal(VSTRING *, MAPS *, int); + + /* + * smtp_key.c + */ +char *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int); + +#define SMTP_KEY_FLAG_SERVICE (1<<0) /* service name */ +#define SMTP_KEY_FLAG_SENDER (1<<1) /* sender address */ +#define SMTP_KEY_FLAG_REQ_NEXTHOP (1<<2) /* delivery request nexthop */ +#define SMTP_KEY_FLAG_CUR_NEXTHOP (1<<3) /* current nexthop */ +#define SMTP_KEY_FLAG_HOSTNAME (1<<4) /* remote host name */ +#define SMTP_KEY_FLAG_ADDR (1<<5) /* remote address */ +#define SMTP_KEY_FLAG_PORT (1<<6) /* remote port */ +#define SMTP_KEY_FLAG_TLS_LEVEL (1<<7) /* requested TLS level */ + +#define SMTP_KEY_MASK_ALL \ + (SMTP_KEY_FLAG_SERVICE | SMTP_KEY_FLAG_SENDER | \ + SMTP_KEY_FLAG_REQ_NEXTHOP | \ + SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_HOSTNAME | \ + SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL) + + /* + * Conditional lookup-key flags for cached connections that may be + * SASL-authenticated with a per-{sender, nexthop, or hostname} credential. + * Each bit corresponds to one type of smtp_sasl_password_file lookup key, + * and is turned on only when the corresponding main.cf parameter is turned + * on. + */ +#define COND_SASL_SMTP_KEY_FLAG_SENDER \ + ((var_smtp_sender_auth && *var_smtp_sasl_passwd) ? \ + SMTP_KEY_FLAG_SENDER : 0) + +#define COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \ + (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0) + +#ifdef USE_TLS +#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \ + (TLS_MUST_MATCH(state->tls->level) ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0) +#else +#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \ + (0) +#endif + +#define COND_SASL_SMTP_KEY_FLAG_HOSTNAME \ + (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_HOSTNAME : 0) + + /* + * Connection-cache destination lookup key, based on the delivery request + * nexthop. The SENDER attribute is a proxy for sender-dependent SASL + * credentials (or absence thereof), and prevents false connection sharing + * when different SASL credentials may be required for different deliveries + * to the same domain and port. Likewise, the delivery request nexthop + * (REQ_NEXTHOP) prevents false sharing of TLS identities (the destination + * key links only to appropriate endpoint lookup keys). The SERVICE + * attribute is a proxy for all request-independent configuration details. + */ +#define SMTP_KEY_MASK_SCACHE_DEST_LABEL \ + (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \ + | SMTP_KEY_FLAG_REQ_NEXTHOP) + + /* + * Connection-cache endpoint lookup key. The SENDER, CUR_NEXTHOP, HOSTNAME, + * PORT and TLS_LEVEL attributes are proxies for SASL credentials and TLS + * authentication (or absence thereof), and prevent false connection sharing + * when different SASL credentials or TLS identities may be required for + * different deliveries to the same IP address and port. The SERVICE + * attribute is a proxy for all request-independent configuration details. + */ +#define SMTP_KEY_MASK_SCACHE_ENDP_LABEL \ + (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \ + | COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \ + | COND_SASL_SMTP_KEY_FLAG_HOSTNAME \ + | COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_ADDR | \ + SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL) + + /* + * Silly little macros. + */ +#define STR(s) vstring_str(s) +#define LEN(s) VSTRING_LEN(s) + +extern int smtp_mode; + +#define VAR_LMTP_SMTP(x) (smtp_mode ? VAR_SMTP_##x : VAR_LMTP_##x) +#define LMTP_SMTP_SUFFIX(x) (smtp_mode ? x##_SMTP : x##_LMTP) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ diff --git a/src/smtp/smtp_addr.c b/src/smtp/smtp_addr.c new file mode 100644 index 0000000..2210ff7 --- /dev/null +++ b/src/smtp/smtp_addr.c @@ -0,0 +1,693 @@ +/*++ +/* NAME +/* smtp_addr 3 +/* SUMMARY +/* SMTP server address lookup +/* SYNOPSIS +/* #include "smtp_addr.h" +/* +/* DNS_RR *smtp_domain_addr(name, mxrr, misc_flags, why, found_myself) +/* char *name; +/* DNS_RR **mxrr; +/* int misc_flags; +/* DSN_BUF *why; +/* int *found_myself; +/* +/* DNS_RR *smtp_host_addr(name, misc_flags, why) +/* char *name; +/* int misc_flags; +/* DSN_BUF *why; +/* DESCRIPTION +/* This module implements Internet address lookups. By default, +/* lookups are done via the Internet domain name service (DNS). +/* A reasonable number of CNAME indirections is permitted. When +/* DNS lookups are disabled, host address lookup is done with +/* getnameinfo() or gethostbyname(). +/* +/* smtp_domain_addr() looks up the network addresses for mail +/* exchanger hosts listed for the named domain. Addresses are +/* returned in most-preferred first order. The result is truncated +/* so that it contains only hosts that are more preferred than the +/* local mail server itself. The found_myself result parameter +/* is updated when the local MTA is MX host for the specified +/* destination. If MX records were found, the rname, qname, +/* and dnssec validation status of the MX RRset are returned +/* via mxrr, which the caller must free with dns_rr_free(). +/* +/* When no mail exchanger is listed in the DNS for \fIname\fR, the +/* request is passed to smtp_host_addr(). +/* +/* It is an error to call smtp_domain_addr() when DNS lookups are +/* disabled. +/* +/* smtp_host_addr() looks up all addresses listed for the named +/* host. The host can be specified as a numerical Internet network +/* address, or as a symbolic host name. +/* +/* Results from smtp_domain_addr() or smtp_host_addr() are +/* destroyed by dns_rr_free(), including null lists. +/* DIAGNOSTICS +/* Panics: interface violations. For example, calling smtp_domain_addr() +/* when DNS lookups are explicitly disabled. +/* +/* All routines either return a DNS_RR pointer, or return a null +/* pointer and update the \fIwhy\fR argument accordingly. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdlib.h> +#include <netdb.h> +#include <ctype.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <mymalloc.h> +#include <inet_addr_list.h> +#include <stringops.h> +#include <myaddrinfo.h> +#include <inet_proto.h> +#include <midna_domain.h> + +/* Global library. */ + +#include <mail_params.h> +#include <own_inet_addr.h> +#include <dsn_buf.h> + +/* DNS library. */ + +#include <dns.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_addr.h" + +/* smtp_print_addr - print address list */ + +static void smtp_print_addr(const char *what, DNS_RR *addr_list) +{ + DNS_RR *addr; + MAI_HOSTADDR_STR hostaddr; + + msg_info("begin %s address list", what); + for (addr = addr_list; addr; addr = addr->next) { + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("skipping record type %s: %m", dns_strtype(addr->type)); + } else { + msg_info("pref %4d host %s/%s", + addr->pref, SMTP_HNAME(addr), + hostaddr.buf); + } + } + msg_info("end %s address list", what); +} + +/* smtp_addr_one - address lookup for one host name */ + +static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt, + unsigned pref, DSN_BUF *why) +{ + const char *myname = "smtp_addr_one"; + DNS_RR *addr = 0; + DNS_RR *rr; + int aierr; + struct addrinfo *res0; + struct addrinfo *res; + INET_PROTO_INFO *proto_info = inet_proto_info(); + unsigned char *proto_family_list = proto_info->sa_family_list; + int found; + + if (msg_verbose) + msg_info("%s: host %s", myname, host); + + /* + * Interpret a numerical name as an address. + */ + if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0) { + if (strchr((char *) proto_family_list, res0->ai_family) != 0) { + if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0) + msg_fatal("host %s: conversion error for address family " + "%d: %m", host, res0->ai_addr->sa_family); + addr_list = dns_rr_append(addr_list, addr); + freeaddrinfo(res0); + return (addr_list); + } + freeaddrinfo(res0); + } + + /* + * Use DNS lookup, but keep the option open to use native name service. + * + * XXX A soft error dominates past and future hard errors. Therefore we + * should not clobber a soft error text and status code. + */ + if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) { + res_opt |= smtp_dns_res_opt; + switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0, + why->reason, DNS_REQ_FLAG_NONE, + proto_info->dns_atype_list)) { + case DNS_OK: + for (rr = addr; rr; rr = rr->next) + rr->pref = pref; + addr_list = dns_rr_append(addr_list, addr); + return (addr_list); + default: + dsb_status(why, "4.4.3"); + return (addr_list); + case DNS_FAIL: + dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3"); + return (addr_list); + case DNS_INVAL: + dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); + return (addr_list); + case DNS_POLICY: + dsb_status(why, "4.7.0"); + return (addr_list); + case DNS_NOTFOUND: + dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); + /* maybe native naming service will succeed */ + break; + } + } + + /* + * Use the native name service which also looks in /etc/hosts. + * + * XXX A soft error dominates past and future hard errors. Therefore we + * should not clobber a soft error text and status code. + */ +#define RETRY_AI_ERROR(e) \ + ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) +#ifdef EAI_NODATA +#define DSN_NOHOST(e) \ + ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME) +#else +#define DSN_NOHOST(e) \ + ((e) == EAI_AGAIN || (e) == EAI_NONAME) +#endif + + if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) { + if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) { + dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ? + (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") : + (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"), + "unable to look up host %s: %s", + host, MAI_STRERROR(aierr)); + } else { + for (found = 0, res = res0; res != 0; res = res->ai_next) { + if (strchr((char *) proto_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, host); + continue; + } + found++; + if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0) + msg_fatal("host %s: conversion error for address family " + "%d: %m", host, res0->ai_addr->sa_family); + addr_list = dns_rr_append(addr_list, addr); + } + freeaddrinfo(res0); + if (found == 0) { + dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4", + "%s: host not found", host); + } + return (addr_list); + } + } + + /* + * No further alternatives for host lookup. + */ + return (addr_list); +} + +/* smtp_addr_list - address lookup for a list of mail exchangers */ + +static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *why) +{ + DNS_RR *addr_list = 0; + DNS_RR *rr; + int res_opt = 0; + + if (mx_names->dnssec_valid) + res_opt = RES_USE_DNSSEC; +#ifdef USE_TLS + else if (smtp_tls_insecure_mx_policy > TLS_LEV_MAY) + res_opt = RES_USE_DNSSEC; +#endif + + /* + * As long as we are able to look up any host address, we ignore problems + * with DNS lookups (except if we're backup MX, and all the better MX + * hosts can't be found). + * + * XXX 2821: update the error status (0->FAIL upon unrecoverable lookup + * error, any->RETRY upon temporary lookup error) so that we can + * correctly handle the case of no resolvable MX host. Currently this is + * always treated as a soft error. RFC 2821 wants a more precise + * response. + * + * XXX dns_lookup() enables RES_DEFNAMES. This is wrong for names found in + * MX records - we should not append the local domain to dot-less names. + * + * XXX However, this is not the only problem. If we use the native name + * service for host lookup, then it will usually enable RES_DNSRCH which + * appends local domain information to all lookups. In particular, + * getaddrinfo() may invoke a resolver that runs in a different process + * (NIS server, nscd), so we can't even reliably turn this off by + * tweaking the in-process resolver flags. + */ + for (rr = mx_names; rr; rr = rr->next) { + if (rr->type != T_MX) + msg_panic("smtp_addr_list: bad resource type: %d", rr->type); + addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt, + rr->pref, why); + } + return (addr_list); +} + +/* smtp_find_self - spot myself in a crowd of mail exchangers */ + +static DNS_RR *smtp_find_self(DNS_RR *addr_list) +{ + const char *myname = "smtp_find_self"; + INET_ADDR_LIST *self; + INET_ADDR_LIST *proxy; + DNS_RR *addr; + int i; + + self = own_inet_addr_list(); + proxy = proxy_inet_addr_list(); + + for (addr = addr_list; addr; addr = addr->next) { + + /* + * Find out if this mail system is listening on this address. + */ + for (i = 0; i < self->used; i++) + if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) { + if (msg_verbose) + msg_info("%s: found self at pref %d", myname, addr->pref); + return (addr); + } + + /* + * Find out if this mail system has a proxy listening on this + * address. + */ + for (i = 0; i < proxy->used; i++) + if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) { + if (msg_verbose) + msg_info("%s: found proxy at pref %d", myname, addr->pref); + return (addr); + } + } + + /* + * Didn't find myself, or my proxy. + */ + if (msg_verbose) + msg_info("%s: not found", myname); + return (0); +} + +/* smtp_truncate_self - truncate address list at self and equivalents */ + +static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref) +{ + DNS_RR *addr; + DNS_RR *last; + + for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) { + if (pref == addr->pref) { + if (msg_verbose) + smtp_print_addr("truncated", addr); + dns_rr_free(addr); + if (last == 0) { + addr_list = 0; + } else { + last->next = 0; + } + break; + } + } + return (addr_list); +} + +/* smtp_balance_inet_proto - balance IPv4/6 protocols within address limit */ + +static DNS_RR *smtp_balance_inet_proto(DNS_RR *addr_list, int misc_flags, + int addr_limit) +{ + const char myname[] = "smtp_balance_inet_proto"; + DNS_RR *rr; + DNS_RR *stripped_list; + DNS_RR *next; + int v6_count; + int v4_count; + int v6_target, + v4_target; + int *p; + + /* + * Precondition: the input is sorted by MX preference (not necessarily IP + * address family preference), and addresses with the same or worse + * preference than 'myself' have been eliminated. Postcondition: the + * relative list order is unchanged, but some elements are removed. + */ + + /* + * Count the number of IPv6 and IPv4 addresses. + */ + for (v4_count = v6_count = 0, rr = addr_list; rr != 0; rr = rr->next) { + if (rr->type == T_A) { + v4_count++; + } else if (rr->type == T_AAAA) { + v6_count++; + } else { + msg_panic("%s: unexpected record type: %s", + myname, dns_strtype(rr->type)); + } + } + + /* + * Ensure that one address type will not out-crowd the other, while + * enforcing the address count limit. This works around a current problem + * where some destination announces primarily IPv6 MX addresses, the + * smtp_address_limit eliminates most or all IPv4 addresses, and the + * destination is not reachable over IPv6. + * + * Maybe: do all smtp_mx_address_limit enforcement here, and remove + * pre-existing enforcement elsewhere. That would obsolete the + * smtp_balance_inet_protocols configuration parameter. + */ + if (v4_count > 0 && v6_count > 0 && v4_count + v6_count > addr_limit) { + + /*- + * Decide how many IPv6 and IPv4 addresses to keep. The code below + * has three branches, corresponding to the regions R1, R2 and R3 + * in the figure. + * + * L = addr_limit + * X = excluded by condition (v4_count + v6_count > addr_limit) + * + * v4_count + * ^ + * | + * L \ R1 + * |X\ | + * |XXX\ | + * |XXXXX\ | R2 + * L/2 +-------\------- + * |XXXXXXX|X\ + * |XXXXXXX|XXX\ R3 + * |XXXXXXX|XXXXX\ + * 0 +-------+-------\--> v6_count + * 0 L/2 L + */ + if (v6_count <= addr_limit / 2) { /* Region R1 */ + v6_target = v6_count; + v4_target = addr_limit - v6_target; + } else if (v4_count <= addr_limit / 2) {/* Region R3 */ + v4_target = v4_count; + v6_target = addr_limit - v4_target; + } else { /* Region R2 */ + /* v4_count > addr_limit / 2 && v6_count > addr_limit / 2 */ + v4_target = (addr_limit + (addr_list->type == T_A)) / 2; + v6_target = addr_limit - v4_target; + } + if (msg_verbose) + msg_info("v6_target=%d, v4_target=%d", v6_target, v4_target); + + /* Enforce the address count targets. */ + stripped_list = 0; + for (rr = addr_list; rr != 0; rr = next) { + next = rr->next; + rr->next = 0; + if (rr->type == T_A) { + p = &v4_target; + } else if (rr->type == T_AAAA) { + p = &v6_target; + } else { + msg_panic("%s: unexpected record type: %s", + myname, dns_strtype(rr->type)); + } + if (*p > 0) { + stripped_list = dns_rr_append(stripped_list, rr); + *p -= 1; + } else { + dns_rr_free(rr); + } + } + if (v4_target > 0 || v6_target > 0) + msg_panic("%s: bad target count: v4_target=%d, v6_target=%d", + myname, v4_target, v6_target); + if (msg_verbose) + smtp_print_addr("smtp_balance_inet_proto result", stripped_list); + return (stripped_list); + } else { + return (addr_list); + } +} + +/* smtp_domain_addr - mail exchanger address lookup */ + +DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags, + DSN_BUF *why, int *found_myself) +{ + DNS_RR *mx_names; + DNS_RR *addr_list = 0; + DNS_RR *self = 0; + unsigned best_pref; + unsigned best_found; + int r = 0; /* Resolver flags */ + const char *aname; + + dsb_reset(why); /* Paranoia */ + + /* + * Preferences from DNS use 0..32767, fall-backs use 32768+. + */ +#define IMPOSSIBLE_PREFERENCE (~0) + + /* + * Sanity check. + */ + if (smtp_dns_support == SMTP_DNS_DISABLED) + msg_panic("smtp_domain_addr: DNS lookup is disabled"); + if (smtp_dns_support == SMTP_DNS_DNSSEC) + r |= RES_USE_DNSSEC; + + /* + * IDNA support. + */ +#ifndef NO_EAI + if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", name, aname); + } else +#endif + aname = name; + + /* + * Look up the mail exchanger hosts listed for this name. Sort the + * results by preference. Look up the corresponding host addresses, and + * truncate the list so that it contains only hosts that are more + * preferred than myself. When no MX resource records exist, look up the + * addresses listed for this name. + * + * According to RFC 974: "It is possible that the list of MXs in the + * response to the query will be empty. This is a special case. If the + * list is empty, mailers should treat it as if it contained one RR, an + * MX RR with a preference value of 0, and a host name of REMOTE. (I.e., + * REMOTE is its only MX). In addition, the mailer should do no further + * processing on the list, but should attempt to deliver the message to + * REMOTE." + * + * Normally it is OK if an MX host cannot be found in the DNS; we'll just + * use a backup one, and silently ignore the better MX host. However, if + * the best backup that we can find in the DNS is the local machine, then + * we must remember that the local machine is not the primary MX host, or + * else we will claim that mail loops back. + * + * XXX Optionally do A lookups even when the MX lookup didn't complete. + * Unfortunately with some DNS servers this is not a transient problem. + * + * XXX Ideally we would perform A lookups only as far as needed. But as long + * as we're looking up all the hosts, it would be better to look up the + * least preferred host first, so that DNS lookup error messages make + * more sense. + * + * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX + * hosts, whereas multiple A records per hostname must be used in the + * order as received. They make the bogus assumption that a hostname with + * multiple A records corresponds to one machine with multiple network + * interfaces. + * + * XXX 2821: Postfix recognizes the local machine by looking for its own IP + * address in the list of mail exchangers. RFC 2821 says one has to look + * at the mail exchanger hostname as well, making the bogus assumption + * that an IP address is listed only under one hostname. However, looking + * at hostnames provides a partial solution for MX hosts behind a NAT + * gateway. + */ + switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) { + default: + dsb_status(why, "4.4.3"); + if (var_ign_mx_lookup_err) + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + case DNS_INVAL: + dsb_status(why, "5.4.4"); + if (var_ign_mx_lookup_err) + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + case DNS_NULLMX: + dsb_status(why, "5.1.0"); + break; + case DNS_POLICY: + dsb_status(why, "4.7.0"); + break; + case DNS_FAIL: + dsb_status(why, "5.4.3"); + if (var_ign_mx_lookup_err) + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + case DNS_OK: + mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any); + best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE); + addr_list = smtp_addr_list(mx_names, why); + if (mxrr) + *mxrr = dns_rr_copy(mx_names); /* copies one record! */ + dns_rr_free(mx_names); + if (addr_list == 0) { + /* Text does not change. */ + if (var_smtp_defer_mxaddr) { + /* Don't clobber the null terminator. */ + if (SMTP_HAS_HARD_DSN(why)) + SMTP_SET_SOFT_DSN(why); /* XXX */ + /* Require some error status. */ + else if (!SMTP_HAS_SOFT_DSN(why)) + msg_panic("smtp_domain_addr: bad status"); + } + msg_warn("no MX host for %s has a valid address record", name); + break; + } + best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE); + if (msg_verbose) + smtp_print_addr(name, addr_list); + if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) + && (self = smtp_find_self(addr_list)) != 0) { + addr_list = smtp_truncate_self(addr_list, self->pref); + if (addr_list == 0) { + if (best_pref != best_found) { + dsb_simple(why, "4.4.4", + "unable to find primary relay for %s", name); + } else { + dsb_simple(why, "5.4.6", "mail for %s loops back to myself", + name); + } + } + } +#define SMTP_COMPARE_ADDR(flags) \ + (((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \ + ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \ + dns_rr_compare_pref_any) + + if (addr_list && addr_list->next) { + if (var_smtp_rand_addr) + addr_list = dns_rr_shuffle(addr_list); + addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); + if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto) + addr_list = smtp_balance_inet_proto(addr_list, misc_flags, + var_smtp_mxaddr_limit); + } + break; + case DNS_NOTFOUND: + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + } + + /* + * Clean up. + */ + *found_myself |= (self != 0); + return (addr_list); +} + +/* smtp_host_addr - direct host lookup */ + +DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why) +{ + DNS_RR *addr_list; + int res_opt = 0; + const char *ahost; + + dsb_reset(why); /* Paranoia */ + + if (smtp_dns_support == SMTP_DNS_DNSSEC) + res_opt |= RES_USE_DNSSEC; + + /* + * IDNA support. + */ +#ifndef NO_EAI + if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", host, ahost); + } else +#endif + ahost = host; + + /* + * If the host is specified by numerical address, just convert the + * address to internal form. Otherwise, the host is specified by name. + */ +#define PREF0 0 + addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, why); + if (addr_list + && (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) + && smtp_find_self(addr_list) != 0) { + dns_rr_free(addr_list); + dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host); + return (0); + } + if (addr_list && addr_list->next) { + if (var_smtp_rand_addr) + addr_list = dns_rr_shuffle(addr_list); + /* The following changes the order of equal-preference hosts. */ + if (inet_proto_info()->ai_family_list[1] != 0) + addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); + if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto) + addr_list = smtp_balance_inet_proto(addr_list, misc_flags, + var_smtp_mxaddr_limit); + } + if (msg_verbose) + smtp_print_addr(host, addr_list); + return (addr_list); +} diff --git a/src/smtp/smtp_addr.h b/src/smtp/smtp_addr.h new file mode 100644 index 0000000..8f20961 --- /dev/null +++ b/src/smtp/smtp_addr.h @@ -0,0 +1,31 @@ +/*++ +/* NAME +/* smtp_addr 3h +/* SUMMARY +/* SMTP server address lookup +/* SYNOPSIS +/* #include "smtp_addr.h" +/* DESCRIPTION +/* .nf + + /* + * DNS library. + */ +#include <dns.h> + + /* + * Internal interfaces. + */ +extern DNS_RR *smtp_host_addr(const char *, int, DSN_BUF *); +extern DNS_RR *smtp_domain_addr(const char *, DNS_RR **, int, DSN_BUF *, int *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtp/smtp_chat.c b/src/smtp/smtp_chat.c new file mode 100644 index 0000000..a020ef8 --- /dev/null +++ b/src/smtp/smtp_chat.c @@ -0,0 +1,486 @@ +/*++ +/* NAME +/* smtp_chat 3 +/* SUMMARY +/* SMTP client request/response support +/* SYNOPSIS +/* #include "smtp.h" +/* +/* typedef struct { +/* .in +4 +/* int code; /* SMTP code, not sanitized */ +/* char *dsn; /* enhanced status, sanitized */ +/* char *str; /* unmodified SMTP reply */ +/* VSTRING *dsn_buf; +/* VSTRING *str_buf; +/* .in -4 +/* } SMTP_RESP; +/* +/* void smtp_chat_cmd(session, format, ...) +/* SMTP_SESSION *session; +/* const char *format; +/* +/* DICT *smtp_chat_resp_filter; +/* +/* SMTP_RESP *smtp_chat_resp(session) +/* SMTP_SESSION *session; +/* +/* void smtp_chat_notify(session) +/* SMTP_SESSION *session; +/* +/* void smtp_chat_init(session) +/* SMTP_SESSION *session; +/* +/* void smtp_chat_reset(session) +/* SMTP_SESSION *session; +/* DESCRIPTION +/* This module implements SMTP client support for request/reply +/* conversations, and maintains a limited SMTP transaction log. +/* +/* smtp_chat_cmd() formats a command and sends it to an SMTP server. +/* Optionally, the command is logged. +/* +/* smtp_chat_resp() reads one SMTP server response. It extracts +/* the SMTP reply code and enhanced status code from the text, +/* and concatenates multi-line responses to one string, using +/* a newline as separator. Optionally, the server response +/* is logged. +/* .IP \(bu +/* Postfix never sanitizes the extracted SMTP reply code except +/* to ensure that it is a three-digit code. A malformed reply +/* results in a null extracted SMTP reply code value. +/* .IP \(bu +/* Postfix always sanitizes the extracted enhanced status code. +/* When the server's SMTP status code is 2xx, 4xx or 5xx, +/* Postfix requires that the first digit of the server's +/* enhanced status code matches the first digit of the server's +/* SMTP status code. In case of a mis-match, or when the +/* server specified no status code, the extracted enhanced +/* status code is set to 2.0.0, 4.0.0 or 5.0.0 instead. With +/* SMTP reply codes other than 2xx, 4xx or 5xx, the extracted +/* enhanced status code is set to a default value of 5.5.0 +/* (protocol error) for reasons outlined under the next bullet. +/* .IP \(bu +/* Since the SMTP reply code may violate the protocol even +/* when it is correctly formatted, Postfix uses the sanitized +/* extracted enhanced status code to decide whether an error +/* condition is permanent or transient. This means that the +/* caller may have to update the enhanced status code when it +/* discovers that a server reply violates the SMTP protocol, +/* even though it was correctly formatted. This happens when +/* the client and server get out of step due to a broken proxy +/* agent. +/* .PP +/* smtp_chat_resp_filter specifies an optional filter to +/* transform one server reply line before it is parsed. The +/* filter is invoked once for each line of a multi-line reply. +/* +/* smtp_chat_notify() sends a copy of the SMTP transaction log +/* to the postmaster for review. The postmaster notice is sent only +/* when delivery is possible immediately. It is an error to call +/* smtp_chat_notify() when no SMTP transaction log exists. +/* +/* smtp_chat_init() initializes the per-session transaction log. +/* This must be done at the beginning of a new SMTP session. +/* +/* smtp_chat_reset() resets the transaction log. This is +/* typically done at the beginning or end of an SMTP session, +/* or within a session to discard non-error information. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem, server response exceeds +/* configurable limit. +/* All other exceptions are handled by long jumps (see smtp_stream(3)). +/* SEE ALSO +/* smtp_stream(3) SMTP session I/O support +/* msg(3) generic logging interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <ctype.h> +#include <stdlib.h> +#include <setjmp.h> +#include <string.h> +#include <limits.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <argv.h> +#include <stringops.h> +#include <line_wrap.h> +#include <mymalloc.h> + +/* Global library. */ + +#include <recipient_list.h> +#include <deliver_request.h> +#include <smtp_stream.h> +#include <mail_params.h> +#include <mail_addr.h> +#include <post_mail.h> +#include <mail_error.h> +#include <dsn_util.h> + +/* Application-specific. */ + +#include "smtp.h" + + /* + * Server reply transformations. + */ +DICT *smtp_chat_resp_filter; + +/* smtp_chat_init - initialize SMTP transaction log */ + +void smtp_chat_init(SMTP_SESSION *session) +{ + session->history = 0; +} + +/* smtp_chat_reset - reset SMTP transaction log */ + +void smtp_chat_reset(SMTP_SESSION *session) +{ + if (session->history) { + argv_free(session->history); + session->history = 0; + } +} + +/* smtp_chat_append - append record to SMTP transaction log */ + +static void smtp_chat_append(SMTP_SESSION *session, const char *direction, + const char *data) +{ + char *line; + + if (session->history == 0) + session->history = argv_alloc(10); + line = concatenate(direction, data, (char *) 0); + argv_add(session->history, line, (char *) 0); + myfree(line); +} + +/* smtp_chat_cmd - send an SMTP command */ + +void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...) +{ + va_list ap; + + /* + * Format the command, and update the transaction log. + */ + va_start(ap, fmt); + vstring_vsprintf(session->buffer, fmt, ap); + va_end(ap); + smtp_chat_append(session, "Out: ", STR(session->buffer)); + + /* + * Optionally log the command first, so we can see in the log what the + * program is trying to do. + */ + if (msg_verbose) + msg_info("> %s: %s", session->namaddrport, STR(session->buffer)); + + /* + * Send the command to the SMTP server. + */ + smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream); + + /* + * Force flushing of output does not belong here. It is done in the + * smtp_loop() main protocol loop when reading the server response, and + * in smtp_helo() when reading the EHLO response after sending the EHLO + * command. + * + * If we do forced flush here, then we must longjmp() on error, and a + * matching "prepare for disaster" error handler must be set up before + * every smtp_chat_cmd() call. + */ +#if 0 + + /* + * Flush unsent data to avoid timeouts after slow DNS lookups. + */ + if (time((time_t *) 0) - vstream_ftime(session->stream) > 10) + vstream_fflush(session->stream); + + /* + * Abort immediately if the connection is broken. + */ + if (vstream_ftimeout(session->stream)) + vstream_longjmp(session->stream, SMTP_ERR_TIME); + if (vstream_ferror(session->stream)) + vstream_longjmp(session->stream, SMTP_ERR_EOF); +#endif +} + +/* smtp_chat_resp - read and process SMTP server response */ + +SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session) +{ + static SMTP_RESP rdata; + char *cp; + int last_char; + int three_digs = 0; + size_t len; + const char *new_reply; + int chat_append_flag; + int chat_append_skipped = 0; + + /* + * Initialize the response data buffer. + */ + if (rdata.str_buf == 0) { + rdata.dsn_buf = vstring_alloc(10); + rdata.str_buf = vstring_alloc(100); + } + + /* + * Censor out non-printable characters in server responses. Concatenate + * multi-line server responses. Separate the status code from the text. + * Leave further parsing up to the application. + * + * We can't parse or store input that exceeds var_line_limit, so we just + * skip over it to simplify the remainder of the code below. + */ + VSTRING_RESET(rdata.str_buf); + for (;;) { + last_char = smtp_get(session->buffer, session->stream, var_line_limit, + SMTP_GET_FLAG_SKIP); + /* XXX Update the per-line time limit. */ + printable(STR(session->buffer), '?'); + if (last_char != '\n') + msg_warn("%s: response longer than %d: %.30s...", + session->namaddrport, var_line_limit, STR(session->buffer)); + if (msg_verbose) + msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer)); + + /* + * Defend against a denial of service attack by limiting the amount + * of multi-line text that we are willing to store. + */ + chat_append_flag = (LEN(rdata.str_buf) < var_line_limit); + if (chat_append_flag) + smtp_chat_append(session, "In: ", STR(session->buffer)); + else { + if (chat_append_skipped == 0) + msg_warn("%s: multi-line response longer than %d %.30s...", + session->namaddrport, var_line_limit, STR(rdata.str_buf)); + if (chat_append_skipped < INT_MAX) + chat_append_skipped++; + } + + /* + * Server reply substitution, for fault-injection testing, or for + * working around broken systems. Use with care. + */ + if (smtp_chat_resp_filter != 0) { + new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer)); + if (new_reply != 0) { + msg_info("%s: replacing server reply \"%s\" with \"%s\"", + session->namaddrport, STR(session->buffer), new_reply); + vstring_strcpy(session->buffer, new_reply); + if (chat_append_flag) { + smtp_chat_append(session, "Replaced-by: ", ""); + smtp_chat_append(session, " ", new_reply); + } + } else if (smtp_chat_resp_filter->error != 0) { + msg_warn("%s: table %s:%s lookup error for %s", + session->state->request->queue_id, + smtp_chat_resp_filter->type, + smtp_chat_resp_filter->name, + printable(STR(session->buffer), '?')); + vstream_longjmp(session->stream, SMTP_ERR_DATA); + } + } + if (chat_append_flag) { + if (LEN(rdata.str_buf)) + VSTRING_ADDCH(rdata.str_buf, '\n'); + vstring_strcat(rdata.str_buf, STR(session->buffer)); + } + + /* + * Parse into code and text. Do not ignore garbage (see below). + */ + for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++) + /* void */ ; + if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) { + if (*cp == '-') + continue; + if (*cp == ' ' || *cp == 0) + break; + } + + /* + * XXX Do not simply ignore garbage in the server reply when ESMTP + * command pipelining is turned on. For example, after sending + * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a + * legitimate 2XX reply, Postfix recognizes the server's QUIT reply + * as the END-OF-DATA reply after garbage, causing mail to be lost. + * + * Without the ability to store per-domain status information in queue + * files, automatic workarounds are problematic: + * + * - Automatically deferring delivery creates a "repeated delivery" + * problem when garbage arrives after the DATA stage. Without the + * workaround, Postfix delivers only once. + * + * - Automatically deferring delivery creates a "no delivery" problem + * when the garbage arrives before the DATA stage. Without the + * workaround, mail might still get through. + * + * - Automatically turning off pipelining for delayed mail affects + * deliveries to correctly implemented servers, and may also affect + * delivery of large mailing lists. + * + * So we leave the decision with the administrator, but we don't force + * them to take action, like we would with automatic deferral. If + * loss of mail is not acceptable then they can turn off pipelining + * for specific sites, or they can turn off pipelining globally when + * they find that there are just too many broken sites. + */ + session->error_mask |= MAIL_ERROR_PROTOCOL; + if (session->features & SMTP_FEATURE_PIPELINING) { + msg_warn("%s: non-%s response from %s: %.100s", + session->state->request->queue_id, + smtp_mode ? "ESMTP" : "LMTP", + session->namaddrport, STR(session->buffer)); + if (var_helpful_warnings) + msg_warn("to prevent loss of mail, turn off command pipelining " + "for %s with the %s parameter", + STR(session->iterator->addr), + VAR_LMTP_SMTP(EHLO_DIS_MAPS)); + } + } + + /* + * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail + * code if none was given. + * + * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX + * replies, or codes whose initial digit is out of sync with the reply + * code. + * + * XXX Potential stability problem. In order to save memory, the queue + * manager stores DSNs in a compact manner: + * + * - empty strings are represented by null pointers, + * + * - the status and reason are required to be non-empty. + * + * Other Postfix daemons inherit this behavior, because they use the same + * DSN support code. This means that everything that receives DSNs must + * cope with null pointers for the optional DSN attributes, and that + * everything that provides DSN information must provide a non-empty + * status and reason, otherwise the DSN support code wil panic(). + * + * Thus, when the remote server sends a malformed reply (or 3XX out of + * context) we should not panic() in DSN_COPY() just because we don't + * have a status. Robustness suggests that we supply a status here, and + * that we leave it up to the down-stream code to override the + * server-supplied status in case of an error we can't detect here, such + * as an out-of-order server reply. + */ + VSTRING_TERMINATE(rdata.str_buf); + vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */ + if (three_digs != 0) { + rdata.code = atoi(STR(session->buffer)); + if (strchr("245", STR(session->buffer)[0]) != 0) { + for (cp = STR(session->buffer) + 4; *cp == ' '; cp++) + /* void */ ; + if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) { + vstring_strncpy(rdata.dsn_buf, cp, len); + } else { + vstring_strcpy(rdata.dsn_buf, "0.0.0"); + STR(rdata.dsn_buf)[0] = STR(session->buffer)[0]; + } + } + } else { + rdata.code = 0; + } + rdata.dsn = STR(rdata.dsn_buf); + rdata.str = STR(rdata.str_buf); + return (&rdata); +} + +/* print_line - line_wrap callback */ + +static void print_line(const char *str, int len, int indent, void *context) +{ + VSTREAM *notice = (VSTREAM *) context; + + post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str); +} + +/* smtp_chat_notify - notify postmaster */ + +void smtp_chat_notify(SMTP_SESSION *session) +{ + const char *myname = "smtp_chat_notify"; + VSTREAM *notice; + char **cpp; + + /* + * Sanity checks. + */ + if (session->history == 0) + msg_panic("%s: no conversation history", myname); + if (msg_verbose) + msg_info("%s: notify postmaster", myname); + + /* + * Construct a message for the postmaster, explaining what this is all + * about. This is junk mail: don't send it when the mail posting service + * is unavailable, and use the double bounce sender address, to prevent + * mail bounce wars. Always prepend one space to message content that we + * generate from untrusted data. + */ +#define NULL_TRACE_FLAGS 0 +#define NO_QUEUE_ID ((VSTRING *) 0) +#define LENGTH 78 +#define INDENT 4 + + notice = post_mail_fopen_nowait(mail_addr_double_bounce(), + var_error_rcpt, + MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS, + SMTPUTF8_FLAG_NONE, NO_QUEUE_ID); + if (notice == 0) { + msg_warn("postmaster notify: %m"); + return; + } + post_mail_fprintf(notice, "From: %s (Mail Delivery System)", + mail_addr_mail_daemon()); + post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); + post_mail_fprintf(notice, "Subject: %s %s client: errors from %s", + var_mail_name, smtp_mode ? "SMTP" : "LMTP", + session->namaddrport); + post_mail_fputs(notice, ""); + post_mail_fprintf(notice, "Unexpected response from %s.", + session->namaddrport); + post_mail_fputs(notice, ""); + post_mail_fputs(notice, "Transcript of session follows."); + post_mail_fputs(notice, ""); + argv_terminate(session->history); + for (cpp = session->history->argv; *cpp; cpp++) + line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, + (void *) notice); + post_mail_fputs(notice, ""); + post_mail_fprintf(notice, "For other details, see the local mail logfile"); + (void) post_mail_fclose(notice); +} diff --git a/src/smtp/smtp_connect.c b/src/smtp/smtp_connect.c new file mode 100644 index 0000000..aad48e4 --- /dev/null +++ b/src/smtp/smtp_connect.c @@ -0,0 +1,1206 @@ +/*++ +/* NAME +/* smtp_connect 3 +/* SUMMARY +/* connect to SMTP/LMTP server and deliver +/* SYNOPSIS +/* #include "smtp.h" +/* +/* int smtp_connect(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* This module implements SMTP/LMTP connection management and controls +/* mail delivery. +/* +/* smtp_connect() attempts to establish an SMTP/LMTP session with a host +/* that represents the destination domain, or with an optional fallback +/* relay when {the destination cannot be found, or when all the +/* destination servers are unavailable}. It skips over IP addresses +/* that fail to complete the SMTP/LMTP handshake and tries to find +/* an alternate server when an SMTP/LMTP session fails to deliver. +/* +/* This layer also controls what connections are retrieved from +/* the connection cache, and what connections are saved to the cache. +/* +/* The destination is either a host (or domain) name or a numeric +/* address. Symbolic or numeric service port information may be +/* appended, separated by a colon (":"). In the case of LMTP, +/* destinations may be specified as "unix:pathname", "inet:host" +/* or "inet:host:port". +/* +/* With SMTP, the Internet domain name service is queried for mail +/* exchanger hosts. Quote the domain name with `[' and `]' to +/* suppress mail exchanger lookups. +/* +/* Numerical address information should always be quoted with `[]'. +/* DIAGNOSTICS +/* The delivery status is the result value. +/* SEE ALSO +/* smtp_proto(3) SMTP client protocol +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Connection caching in cooperation with: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> + +#ifndef IPPORT_SMTP +#define IPPORT_SMTP 25 +#endif + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <split_at.h> +#include <mymalloc.h> +#include <inet_addr_list.h> +#include <iostuff.h> +#include <timed_connect.h> +#include <stringops.h> +#include <host_port.h> +#include <sane_connect.h> +#include <myaddrinfo.h> +#include <sock_addr.h> +#include <inet_proto.h> + +/* Global library. */ + +#include <mail_params.h> +#include <own_inet_addr.h> +#include <deliver_pass.h> +#include <mail_error.h> +#include <dsn_buf.h> +#include <mail_addr.h> + +/* DNS library. */ + +#include <dns.h> + +/* Application-specific. */ + +#include <smtp.h> +#include <smtp_addr.h> +#include <smtp_reuse.h> + + /* + * Forward declaration. + */ +static SMTP_SESSION *smtp_connect_sock(int, struct sockaddr *, int, + SMTP_ITERATOR *, DSN_BUF *, + int); + +/* smtp_connect_unix - connect to UNIX-domain address */ + +static SMTP_SESSION *smtp_connect_unix(SMTP_ITERATOR *iter, DSN_BUF *why, + int sess_flags) +{ + const char *myname = "smtp_connect_unix"; + struct sockaddr_un sock_un; + const char *addr = STR(iter->addr); + int len = strlen(addr); + int sock; + + dsb_reset(why); /* Paranoia */ + + /* + * Sanity checks. + */ + if (len >= (int) sizeof(sock_un.sun_path)) { + msg_warn("unix-domain name too long: %s", addr); + dsb_simple(why, "4.3.5", "Server configuration error"); + return (0); + } + + /* + * Initialize. + */ + memset((void *) &sock_un, 0, sizeof(sock_un)); + sock_un.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sock_un.sun_len = len + 1; +#endif + memcpy(sock_un.sun_path, addr, len + 1); + + /* + * Create a client socket. + */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + + /* + * Connect to the server. + */ + if (msg_verbose) + msg_info("%s: trying: %s...", myname, addr); + + return (smtp_connect_sock(sock, (struct sockaddr *) &sock_un, + sizeof(sock_un), iter, why, sess_flags)); +} + +/* smtp_connect_addr - connect to explicit address */ + +static SMTP_SESSION *smtp_connect_addr(SMTP_ITERATOR *iter, DSN_BUF *why, + int sess_flags) +{ + const char *myname = "smtp_connect_addr"; + struct sockaddr_storage ss; /* remote */ + struct sockaddr *sa = (struct sockaddr *) &ss; + SOCKADDR_SIZE salen = sizeof(ss); + MAI_HOSTADDR_STR hostaddr; + DNS_RR *addr = iter->rr; + unsigned port = iter->port; + int sock; + char *bind_addr; + char *bind_var; + + dsb_reset(why); /* Paranoia */ + + /* + * Sanity checks. + */ + if (dns_rr_to_sa(addr, port, sa, &salen) != 0) { + msg_warn("%s: skip address type %s: %m", + myname, dns_strtype(addr->type)); + dsb_simple(why, "4.4.0", "network address conversion failed: %m"); + return (0); + } + + /* + * Initialize. + */ + if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + + if (inet_windowsize > 0) + set_inet_windowsize(sock, inet_windowsize); + + /* + * Allow the sysadmin to specify the source address, for example, as "-o + * smtp_bind_address=x.x.x.x" in the master.cf file. + */ +#ifdef HAS_IPV6 + if (sa->sa_family == AF_INET6) { + bind_addr = var_smtp_bind_addr6; + bind_var = VAR_LMTP_SMTP(BIND_ADDR6); + } else +#endif + if (sa->sa_family == AF_INET) { + bind_addr = var_smtp_bind_addr; + bind_var = VAR_LMTP_SMTP(BIND_ADDR); + } else + bind_var = bind_addr = ""; + if (*bind_addr) { + int aierr; + struct addrinfo *res0; + + if ((aierr = hostaddr_to_sockaddr(bind_addr, (char *) 0, 0, &res0)) != 0) + msg_fatal("%s: bad %s parameter: %s: %s", + myname, bind_var, bind_addr, MAI_STRERROR(aierr)); + if (bind(sock, res0->ai_addr, res0->ai_addrlen) < 0) + msg_warn("%s: bind %s: %m", myname, bind_addr); + else if (msg_verbose) + msg_info("%s: bind %s", myname, bind_addr); + freeaddrinfo(res0); + } + + /* + * When running as a virtual host, bind to the virtual interface so that + * the mail appears to come from the "right" machine address. + * + * XXX The IPv6 patch expands the null host (as client endpoint) and uses + * the result as the loopback address list. + */ + else { + int count = 0; + struct sockaddr *own_addr = 0; + INET_ADDR_LIST *addr_list = own_inet_addr_list(); + struct sockaddr_storage *s; + + for (s = addr_list->addrs; s < addr_list->addrs + addr_list->used; s++) { + if (SOCK_ADDR_FAMILY(s) == sa->sa_family) { + if (count++ > 0) + break; + own_addr = SOCK_ADDR_PTR(s); + } + } + if (count == 1 && !sock_addr_in_loopback(own_addr)) { + if (bind(sock, own_addr, SOCK_ADDR_LEN(own_addr)) < 0) { + SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_warn("%s: bind %s: %m", myname, hostaddr.buf); + } else if (msg_verbose) { + SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("%s: bind %s", myname, hostaddr.buf); + } + } + } + + /* + * Connect to the server. + */ + if (msg_verbose) + msg_info("%s: trying: %s[%s] port %d...", + myname, STR(iter->host), STR(iter->addr), ntohs(port)); + + return (smtp_connect_sock(sock, sa, salen, iter, why, sess_flags)); +} + +/* smtp_connect_sock - connect a socket over some transport */ + +static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr *sa, + int salen, + SMTP_ITERATOR *iter, + DSN_BUF *why, + int sess_flags) +{ + int conn_stat; + int saved_errno; + VSTREAM *stream; + time_t start_time; + const char *name = STR(iter->host); + const char *addr = STR(iter->addr); + unsigned port = iter->port; + + start_time = time((time_t *) 0); + if (var_smtp_conn_tmout > 0) { + non_blocking(sock, NON_BLOCKING); + conn_stat = timed_connect(sock, sa, salen, var_smtp_conn_tmout); + saved_errno = errno; + non_blocking(sock, BLOCKING); + errno = saved_errno; + } else { + conn_stat = sane_connect(sock, sa, salen); + } + if (conn_stat < 0) { + if (port) + dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m", + name, addr, ntohs(port)); + else + dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr); + close(sock); + return (0); + } + stream = vstream_fdopen(sock, O_RDWR); + + /* + * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. + */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(stream); + + /* + * Bundle up what we have into a nice SMTP_SESSION object. + */ + return (smtp_session_alloc(stream, iter, start_time, sess_flags)); +} + +/* smtp_parse_destination - parse host/port destination */ + +static char *smtp_parse_destination(char *destination, char *def_service, + char **hostp, unsigned *portp) +{ + char *buf = mystrdup(destination); + char *service; + struct servent *sp; + char *protocol = "tcp"; /* XXX configurable? */ + unsigned port; + const char *err; + + if (msg_verbose) + msg_info("smtp_parse_destination: %s %s", destination, def_service); + + /* + * Parse the host/port information. We're working with a copy of the + * destination argument so the parsing can be destructive. + */ + if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0) + msg_fatal("%s in server description: %s", err, destination); + + /* + * Convert service to port number, network byte order. + */ + if (alldig(service)) { + if ((port = atoi(service)) >= 65536 || port == 0) + msg_fatal("bad network port in destination: %s", destination); + *portp = htons(port); + } else { + if ((sp = getservbyname(service, protocol)) == 0) + msg_fatal("unknown service: %s/%s", service, protocol); + *portp = sp->s_port; + } + return (buf); +} + +/* smtp_cleanup_session - clean up after using a session */ + +static void smtp_cleanup_session(SMTP_STATE *state) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + int throttled; + + /* + * Inform the postmaster of trouble. + * + * XXX Don't send notifications about errors while sending notifications. + */ +#define POSSIBLE_NOTIFICATION(sender) \ + (*sender == 0 || strcmp(sender, mail_addr_double_bounce()) == 0) + + if (session->history != 0 + && (session->error_mask & name_mask(VAR_NOTIFY_CLASSES, + mail_error_masks, + var_notify_classes)) != 0 + && POSSIBLE_NOTIFICATION(request->sender) == 0) + smtp_chat_notify(session); + + /* + * When session caching is enabled, cache the first good session for this + * delivery request under the next-hop destination, and cache all good + * sessions under their server network address (destroying the session in + * the process). + * + * Caching under the next-hop destination name (rather than the fall-back + * destination) allows us to skip over non-responding primary or backup + * hosts. In fact, this is the only benefit of caching logical to + * physical bindings; caching a session under its own hostname provides + * no performance benefit, given the way smtp_connect() works. + */ + throttled = THIS_SESSION_IS_THROTTLED; /* smtp_quit() may fail */ + if (THIS_SESSION_IS_EXPIRED) + smtp_quit(state); /* also disables caching */ + if (THIS_SESSION_IS_CACHED + /* Redundant tests for safety... */ + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) { + smtp_save_session(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL, + SMTP_KEY_MASK_SCACHE_ENDP_LABEL); + } else { + smtp_session_free(session); + } + state->session = 0; + + /* + * If this session was good, reset the scache next-hop destination, so + * that we won't cache connections to less-preferred servers under the + * same next-hop destination. Otherwise we could end up skipping over the + * available and more-preferred servers. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state) && !throttled) + CLEAR_SCACHE_REQUEST_NEXTHOP(state); + + /* + * Clean up the lists with todo and dropped recipients. + */ + smtp_rcpt_cleanup(state); + + /* + * Reset profiling info. + * + * XXX When one delivery request results in multiple sessions, the set-up + * and transmission latencies of the earlier sessions will count as + * connection set-up time for the later sessions. + * + * XXX On the other hand, when we first try to connect to one or more dead + * hosts before we reach a good host, then all that time must be counted + * as connection set-up time for the session with the good host. + * + * XXX So this set-up attribution problem exists only when we actually + * engage in a session, spend a lot of time delivering a message, find + * that it fails, and then connect to an alternate host. + */ + memset((void *) &request->msg_stats.conn_setup_done, 0, + sizeof(request->msg_stats.conn_setup_done)); + memset((void *) &request->msg_stats.deliver_done, 0, + sizeof(request->msg_stats.deliver_done)); + request->msg_stats.reuse_count = 0; +} + +static void smtp_cache_policy(SMTP_STATE *state, const char *dest) +{ + DELIVER_REQUEST *request = state->request; + + state->misc_flags &= ~SMTP_MISC_FLAG_CONN_CACHE_MASK; + + if (smtp_cache_dest && string_list_match(smtp_cache_dest, dest)) { + state->misc_flags |= SMTP_MISC_FLAG_CONN_CACHE_MASK; + } else if (var_smtp_cache_demand) { + if (request->flags & DEL_REQ_FLAG_CONN_LOAD) + state->misc_flags |= SMTP_MISC_FLAG_CONN_LOAD; + if (request->flags & DEL_REQ_FLAG_CONN_STORE) + state->misc_flags |= SMTP_MISC_FLAG_CONN_STORE; + } +} + +/* smtp_connect_local - connect to local server */ + +static void smtp_connect_local(SMTP_STATE *state, const char *path) +{ + const char *myname = "smtp_connect_local"; + SMTP_ITERATOR *iter = state->iterator; + SMTP_SESSION *session; + DSN_BUF *why = state->why; + + /* + * Do not silently ignore an unused setting. + */ + if (*var_fallback_relay) + msg_warn("ignoring \"%s = %s\" setting for non-TCP connections", + VAR_LMTP_FALLBACK, var_fallback_relay); + + /* + * It's too painful to weave this code into the SMTP connection + * management routine. + * + * Connection cache management is based on the UNIX-domain pathname, without + * the "unix:" prefix. + */ + smtp_cache_policy(state, path); + if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK) + SET_SCACHE_REQUEST_NEXTHOP(state, path); + + /* + * Here we ensure that the iter->addr member refers to a copy of the + * UNIX-domain pathname, so that smtp_save_session() will cache the + * connection using the pathname as the physical endpoint name. + * + * We set dest=path for backwards compatibility. + */ +#define NO_PORT 0 + + SMTP_ITER_INIT(iter, path, var_myhostname, path, NO_PORT, state); + + /* + * Opportunistic TLS for unix domain sockets does not make much sense, + * since the channel is private, mere encryption without authentication + * is just wasted cycles and opportunity for breakage. Since we are not + * willing to retry after TLS handshake failures here, we downgrade "may" + * no "none". Nothing is lost, and much waste is avoided. + * + * We don't know who is authenticating whom, so if a client cert is + * available, "encrypt" may be a sensible policy. Otherwise, we also + * downgrade "encrypt" to "none", this time just to avoid waste. + * + * We use smtp_reuse_nexthop() instead of smtp_reuse_addr(), so that we can + * reuse a SASL-authenticated connection (however unlikely this scenario + * may be). The smtp_reuse_addr() interface currently supports only reuse + * of SASL-unauthenticated connections. + */ +#ifdef USE_TLS + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup error for %s/%s: %s", + STR(iter->host), STR(iter->addr), STR(why->reason)); + return; + } +#endif + if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 + || (session = smtp_reuse_nexthop(state, + SMTP_KEY_MASK_SCACHE_DEST_LABEL)) == 0) + session = smtp_connect_unix(iter, why, state->misc_flags); + if ((state->session = session) != 0) { + session->state = state; +#ifdef USE_TLS + session->tls_nexthop = var_myhostname; /* for TLS_LEV_SECURE */ + if (state->tls->level == TLS_LEV_MAY) { + msg_warn("%s: opportunistic TLS encryption is not appropriate " + "for unix-domain destinations.", myname); + state->tls->level = TLS_LEV_NONE; + } +#endif + /* All delivery errors bounce or defer. */ + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + + /* + * When a TLS handshake fails, the stream is marked "dead" to avoid + * further I/O over a broken channel. + */ + if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 + && smtp_helo(state) != 0) { + if (!THIS_SESSION_IS_FORBIDDEN + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) + smtp_quit(state); + } else { + smtp_xfer(state); + } + + /* + * With opportunistic TLS disabled we don't expect to be asked to + * retry connections without TLS, and so we expect the final server + * flag to stay on. + */ + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) + msg_panic("%s: unix-domain destination not final!", myname); + smtp_cleanup_session(state); + } + + /* + * Cleanup. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + CLEAR_SCACHE_REQUEST_NEXTHOP(state); +} + +/* smtp_scrub_address_list - delete all cached addresses from list */ + +static void smtp_scrub_addr_list(HTABLE *cached_addr, DNS_RR **addr_list) +{ + MAI_HOSTADDR_STR hostaddr; + DNS_RR *addr; + DNS_RR *next; + + /* + * XXX Extend the DNS_RR structure with fields for the printable address + * and/or binary sockaddr representations, so that we can avoid repeated + * binary->string transformations for the same address. + */ + for (addr = *addr_list; addr; addr = next) { + next = addr->next; + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("cannot convert type %s record to printable address", + dns_strtype(addr->type)); + continue; + } + if (htable_locate(cached_addr, hostaddr.buf)) + *addr_list = dns_rr_remove(*addr_list, addr); + } +} + +/* smtp_update_addr_list - common address list update */ + +static void smtp_update_addr_list(DNS_RR **addr_list, const char *server_addr, + int session_count) +{ + DNS_RR *addr; + DNS_RR *next; + int aierr; + struct addrinfo *res0; + + if (*addr_list == 0) + return; + + /* + * Truncate the address list if we are not going to use it anyway. + */ + if (session_count == var_smtp_mxsess_limit + || session_count == var_smtp_mxaddr_limit) { + dns_rr_free(*addr_list); + *addr_list = 0; + return; + } + + /* + * Convert server address to internal form, and look it up in the address + * list. + * + * XXX smtp_reuse_session() breaks if we remove two or more adjacent list + * elements but do not truncate the list to zero length. + * + * XXX Extend the SMTP_SESSION structure with sockaddr information so that + * we can avoid repeated string->binary transformations for the same + * address. + */ + if ((aierr = hostaddr_to_sockaddr(server_addr, (char *) 0, 0, &res0)) != 0) { + msg_warn("hostaddr_to_sockaddr %s: %s", + server_addr, MAI_STRERROR(aierr)); + } else { + for (addr = *addr_list; addr; addr = next) { + next = addr->next; + if (DNS_RR_EQ_SA(addr, (struct sockaddr *) res0->ai_addr)) { + *addr_list = dns_rr_remove(*addr_list, addr); + break; + } + } + freeaddrinfo(res0); + } +} + +/* smtp_reuse_session - try to use existing connection, return session count */ + +static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list, + int domain_best_pref) +{ + int session_count = 0; + DNS_RR *addr; + DNS_RR *next; + MAI_HOSTADDR_STR hostaddr; + SMTP_SESSION *session; + SMTP_ITERATOR *iter = state->iterator; + DSN_BUF *why = state->why; + + /* + * First, search the cache by delivery request nexthop. We truncate the + * server address list when all the sessions for this destination are + * used up, to reduce the number of variables that need to be checked + * later. + * + * Note: connection reuse by delivery request nexthop restores the "best MX" + * bit. + * + * smtp_reuse_nexthop() clobbers the iterators's "dest" attribute. We save + * and restore it here, so that subsequent connections will use the + * proper nexthop information. + * + * We don't use TLS level info for nexthop-based connection cache storage + * keys. The combination of (service, nexthop, etc.) should be stable + * over the time range of interest, and the policy is still enforced on + * an individual connection to an MX host, before that connection is + * stored under a nexthop- or host-based storage key. + */ +#ifdef USE_TLS + smtp_tls_policy_dummy(state->tls); +#endif + SMTP_ITER_SAVE_DEST(state->iterator); + if (*addr_list && SMTP_RCPT_LEFT(state) > 0 + && HAVE_SCACHE_REQUEST_NEXTHOP(state) + && (session = smtp_reuse_nexthop(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL)) != 0) { + session_count = 1; + smtp_update_addr_list(addr_list, STR(iter->addr), session_count); + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && *addr_list == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + smtp_xfer(state); + smtp_cleanup_session(state); + } + SMTP_ITER_RESTORE_DEST(state->iterator); + + /* + * Second, search the cache by primary MX address. Again, we use address + * list truncation so that we have to check fewer variables later. + * + * XXX This loop is safe because smtp_update_addr_list() either truncates + * the list to zero length, or removes at most one list element. + * + * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated + * connections. Furthermore, we rely on smtp_reuse_addr() to look up an + * existing SASL-unauthenticated connection only when a new connection + * would be guaranteed not to require SASL authentication. + * + * In addition, we rely on smtp_reuse_addr() to look up an existing + * plaintext connection only when a new connection would be guaranteed + * not to use TLS. + * + * For more precise control over reuse, the iterator should look up SASL and + * TLS policy as it evaluates mail exchangers in order, instead of + * relying on duplicate lookup request code in smtp_reuse(3) and + * smtp_session(3). + */ + for (addr = *addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { + if (addr->pref != domain_best_pref) + break; + next = addr->next; + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("cannot convert type %s record to printable address", + dns_strtype(addr->type)); + /* XXX Assume there is no code at the end of this loop. */ + continue; + } + vstring_strcpy(iter->addr, hostaddr.buf); + vstring_strcpy(iter->host, SMTP_HNAME(addr)); + iter->rr = addr; +#ifdef USE_TLS + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup error for %s/%s: %s", + STR(iter->dest), STR(iter->host), STR(why->reason)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } +#endif + if ((session = smtp_reuse_addr(state, + SMTP_KEY_MASK_SCACHE_ENDP_LABEL)) != 0) { + session->features |= SMTP_FEATURE_BEST_MX; + session_count += 1; + smtp_update_addr_list(addr_list, STR(iter->addr), session_count); + if (*addr_list == 0) + next = 0; + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && next == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + smtp_xfer(state); + smtp_cleanup_session(state); + } + } + return (session_count); +} + +/* smtp_connect_inet - establish network connection */ + +static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop, + char *def_service) +{ + DELIVER_REQUEST *request = state->request; + SMTP_ITERATOR *iter = state->iterator; + ARGV *sites; + char *dest; + char **cpp; + int non_fallback_sites; + int retry_plain = 0; + DSN_BUF *why = state->why; + + /* + * For sanity, require that at least one of INET or INET6 is enabled. + * Otherwise, we can't look up interface information, and we can't + * convert names or addresses. + */ + if (inet_proto_info()->ai_family_list[0] == 0) { + dsb_simple(why, "4.4.4", "all network protocols are disabled"); + return; + } + + /* + * Future proofing: do a null destination sanity check in case we allow + * the primary destination to be a list (it could be just separators). + */ + sites = argv_alloc(1); + argv_add(sites, nexthop, (char *) 0); + if (sites->argc == 0) + msg_panic("null destination: \"%s\"", nexthop); + non_fallback_sites = sites->argc; + argv_split_append(sites, var_fallback_relay, CHARS_COMMA_SP); + + /* + * Don't give up after a hard host lookup error until we have tried the + * fallback relay servers. + * + * Don't bounce mail after a host lookup problem with a relayhost or with a + * fallback relay. + * + * Don't give up after a qualifying soft error until we have tried all + * qualifying backup mail servers. + * + * All this means that error handling and error reporting depends on whether + * the error qualifies for trying to deliver to a backup mail server, or + * whether we're looking up a relayhost or fallback relay. The challenge + * then is to build this into the pre-existing SMTP client without + * getting lost in the complexity. + */ +#define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \ + (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites)) + + for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP); + SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0; + cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) { + char *dest_buf; + char *domain; + unsigned port; + DNS_RR *addr_list; + DNS_RR *addr; + DNS_RR *next; + int addr_count; + int sess_count; + SMTP_SESSION *session; + int lookup_mx; + unsigned domain_best_pref; + MAI_HOSTADDR_STR hostaddr; + + if (cpp[1] == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; + + /* + * Parse the destination. If no TCP port is specified, use the port + * that is reserved for the protocol (SMTP or LMTP). + */ + dest_buf = smtp_parse_destination(dest, def_service, &domain, &port); + if (var_helpful_warnings && var_smtp_tls_wrappermode == 0 + && ntohs(port) == 465) { + msg_info("SMTPS wrappermode (TCP port 465) requires setting " + "\"%s = yes\", and \"%s = encrypt\" (or stronger)", + VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); + } +#define NO_HOST "" /* safety */ +#define NO_ADDR "" /* safety */ + + SMTP_ITER_INIT(iter, dest, NO_HOST, NO_ADDR, port, state); + + /* + * Resolve an SMTP or LMTP server. In the case of SMTP, skip mail + * exchanger lookups when a quoted host is specified or when DNS + * lookups are disabled. + */ + if (msg_verbose) + msg_info("connecting to %s port %d", domain, ntohs(port)); + if (smtp_mode) { + if (ntohs(port) == IPPORT_SMTP) + state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT; + else + state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT; + lookup_mx = (smtp_dns_support != SMTP_DNS_DISABLED && *dest != '['); + } else + lookup_mx = 0; + if (!lookup_mx) { + addr_list = smtp_host_addr(domain, state->misc_flags, why); + /* XXX We could be an MX host for this destination... */ + } else { + int i_am_mx = 0; + + addr_list = smtp_domain_addr(domain, &iter->mx, state->misc_flags, + why, &i_am_mx); + /* If we're MX host, don't connect to non-MX backups. */ + if (i_am_mx) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; + } + + /* + * Don't try fall-back hosts if mail loops to myself. That would just + * make the problem worse. + */ + if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why)) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; + + /* + * No early loop exit or we have a memory leak with dest_buf. + */ + if (addr_list) + domain_best_pref = addr_list->pref; + + /* + * When connection caching is enabled, store the first good + * connection for this delivery request under the delivery request + * next-hop name. Good connections will also be stored under their + * specific server IP address. + * + * XXX smtp_session_cache_destinations specifies domain names without + * :port, because : is already used for maptype:mapname. Because of + * this limitation we use the bare domain without the optional [] or + * non-default TCP port. + * + * Opportunistic (a.k.a. on-demand) session caching on request by the + * queue manager. This is turned temporarily when a destination has a + * high volume of mail in the active queue. When the surge reaches + * its end, the queue manager requests that connections be retrieved + * but not stored. + */ + if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) { + smtp_cache_policy(state, domain); + if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK) + SET_SCACHE_REQUEST_NEXTHOP(state, dest); + } + + /* + * Delete visited cached hosts from the address list. + * + * Optionally search the connection cache by domain name or by primary + * MX address before we try to create new connections. + * + * Enforce the MX session and MX address counts per next-hop or + * fall-back destination. smtp_reuse_session() will truncate the + * address list when either limit is reached. + */ + if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) { + if (state->cache_used->used > 0) + smtp_scrub_addr_list(state->cache_used, &addr_list); + sess_count = addr_count = + smtp_reuse_session(state, &addr_list, domain_best_pref); + } else + sess_count = addr_count = 0; + + /* + * Connect to an SMTP server: create primary MX connections, and + * reuse or create backup MX connections. + * + * At the start of an SMTP session, all recipients are unmarked. In the + * course of an SMTP session, recipients are marked as KEEP (deliver + * to alternate mail server) or DROP (remove from recipient list). At + * the end of an SMTP session, weed out the recipient list. Unmark + * any left-over recipients and try to deliver them to a backup mail + * server. + * + * Cache the first good session under the next-hop destination name. + * Cache all good sessions under their physical endpoint. + * + * Don't query the session cache for primary MX hosts. We already did + * that in smtp_reuse_session(), and if any were found in the cache, + * they were already deleted from the address list. + * + * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated + * connections. Furthermore, we rely on smtp_reuse_addr() to look up + * an existing SASL-unauthenticated connection only when a new + * connection would be guaranteed not to require SASL authentication. + * + * In addition, we rely on smtp_reuse_addr() to look up an existing + * plaintext connection only when a new connection would be + * guaranteed not to use TLS. + */ + for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { + next = addr->next; + if (++addr_count == var_smtp_mxaddr_limit) + next = 0; + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("cannot convert type %s record to printable address", + dns_strtype(addr->type)); + /* XXX Assume there is no code at the end of this loop. */ + continue; + } + vstring_strcpy(iter->addr, hostaddr.buf); + vstring_strcpy(iter->host, SMTP_HNAME(addr)); + iter->rr = addr; +#ifdef USE_TLS + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup for %s/%s: %s", + STR(iter->dest), STR(iter->host), STR(why->reason)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } + if (var_smtp_tls_wrappermode + && state->tls->level < TLS_LEV_ENCRYPT) { + msg_warn("%s requires \"%s = encrypt\" (or stronger)", + VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } + /* Disable TLS when retrying after a handshake failure */ + if (retry_plain) { + state->tls->level = TLS_LEV_NONE; + retry_plain = 0; + } +#endif + if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 + || addr->pref == domain_best_pref + || !(session = smtp_reuse_addr(state, + SMTP_KEY_MASK_SCACHE_ENDP_LABEL))) + session = smtp_connect_addr(iter, why, state->misc_flags); + if ((state->session = session) != 0) { + session->state = state; +#ifdef USE_TLS + session->tls_nexthop = domain; +#endif + if (addr->pref == domain_best_pref) + session->features |= SMTP_FEATURE_BEST_MX; + /* Don't count handshake errors towards the session limit. */ + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && next == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 + && smtp_helo(state) != 0) { +#ifdef USE_TLS + + /* + * When an opportunistic TLS handshake fails, try the + * same address again, with TLS disabled. See also the + * RETRY_AS_PLAINTEXT macro. + */ + if ((retry_plain = session->tls_retry_plain) != 0) { + --addr_count; + next = addr; + } +#endif + + /* + * When a TLS handshake fails, the stream is marked + * "dead" to avoid further I/O over a broken channel. + */ + if (!THIS_SESSION_IS_FORBIDDEN + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) + smtp_quit(state); + } else { + /* Do count delivery errors towards the session limit. */ + if (++sess_count == var_smtp_mxsess_limit) + next = 0; + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && next == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + smtp_xfer(state); +#ifdef USE_TLS + + /* + * When opportunistic TLS fails after the STARTTLS + * handshake, try the same address again, with TLS + * disabled. See also the RETRY_AS_PLAINTEXT macro. + */ + if ((retry_plain = session->tls_retry_plain) != 0) { + --sess_count; + --addr_count; + next = addr; + } +#endif + } + smtp_cleanup_session(state); + } else { + /* The reason already includes the IP address and TCP port. */ + msg_info("%s", STR(why->reason)); + } + /* XXX Code above assumes there is no code at this loop ending. */ + } + dns_rr_free(addr_list); + if (iter->mx) { + dns_rr_free(iter->mx); + iter->mx = 0; /* Just in case */ + } + myfree(dest_buf); + if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + break; + } + + /* + * We still need to deliver, bounce or defer some left-over recipients: + * either mail loops or some backup mail server was unavailable. + */ + if (SMTP_RCPT_LEFT(state) > 0) { + + /* + * In case of a "no error" indication we make up an excuse: we did + * find the host address, but we did not attempt to connect to it. + * This can happen when the fall-back relay was already tried via a + * cached connection, so that the address list scrubber left behind + * an empty list. + */ + if (!SMTP_HAS_DSN(why)) { + dsb_simple(why, "4.3.0", + "server unavailable or unable to receive mail"); + } + + /* + * Pay attention to what could be configuration problems, and pretend + * that these are recoverable rather than bouncing the mail. + */ + else if (!SMTP_HAS_SOFT_DSN(why)) { + + /* + * The fall-back destination did not resolve as expected, or it + * is refusing to talk to us, or mail for it loops back to us. + */ + if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) { + msg_warn("%s configuration problem", VAR_SMTP_FALLBACK); + vstring_strcpy(why->status, "4.3.5"); + /* XXX Keep the diagnostic code and MTA. */ + } + + /* + * The next-hop relayhost did not resolve as expected, or it is + * refusing to talk to us, or mail for it loops back to us. + * + * XXX There is no equivalent safety net for mis-configured + * sender-dependent relay hosts. The trivial-rewrite resolver + * would have to flag the result, and the queue manager would + * have to provide that information to delivery agents. + */ + else if (smtp_mode && strcmp(sites->argv[0], var_relayhost) == 0) { + msg_warn("%s configuration problem", VAR_RELAYHOST); + vstring_strcpy(why->status, "4.3.5"); + /* XXX Keep the diagnostic code and MTA. */ + } + + /* + * Mail for the next-hop destination loops back to myself. Pass + * the mail to the best_mx_transport or bounce it. + */ + else if (smtp_mode && SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) { + dsb_reset(why); /* XXX */ + state->status = deliver_pass_all(MAIL_CLASS_PRIVATE, + var_bestmx_transp, + request); + SMTP_RCPT_LEFT(state) = 0; /* XXX */ + } + } + } + + /* + * Cleanup. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + CLEAR_SCACHE_REQUEST_NEXTHOP(state); + argv_free(sites); +} + +/* smtp_connect - establish SMTP connection */ + +int smtp_connect(SMTP_STATE *state) +{ + DELIVER_REQUEST *request = state->request; + char *destination = request->nexthop; + + /* + * All deliveries proceed along the same lines, whether they are over TCP + * or UNIX-domain sockets, and whether they use SMTP or LMTP: get a + * connection from the cache or create a new connection; deliver mail; + * update the connection cache or disconnect. + * + * The major differences appear at a higher level: the expansion from + * destination to address list, and whether to stop before we reach the + * end of that list. + */ + + /* + * With LMTP we have direct-to-host delivery only. The destination may + * have multiple IP addresses. + */ + if (!smtp_mode) { + if (strncmp(destination, "unix:", 5) == 0) { + smtp_connect_local(state, destination + 5); + } else { + if (strncmp(destination, "inet:", 5) == 0) + destination += 5; + smtp_connect_inet(state, destination, var_smtp_tcp_port); + } + } + + /* + * XXX We don't add support for "unix:" or "inet:" prefixes in SMTP + * destinations, because that would break compatibility with existing + * Postfix configurations that have a host with such a name. + */ + else { + smtp_connect_inet(state, destination, var_smtp_tcp_port); + } + + /* + * We still need to bounce or defer some left-over recipients: either + * (SMTP) mail loops or some server was unavailable. + * + * We could avoid this (and the "final server" complexity) by keeping one + * DSN structure per recipient in memory, by updating those in-memory + * structures with each delivery attempt, and by always flushing all + * deferred recipients at the end. We'd probably still want to bounce + * recipients immediately, so we'd end up with another chunk of code for + * defer logging only. + */ + if (SMTP_RCPT_LEFT(state) > 0) { + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; /* XXX */ + smtp_sess_fail(state); + + /* + * Sanity check. Don't silently lose recipients. + */ + smtp_rcpt_cleanup(state); + if (SMTP_RCPT_LEFT(state) > 0) + msg_panic("smtp_connect: left-over recipients"); + } + return (state->status); +} diff --git a/src/smtp/smtp_key.c b/src/smtp/smtp_key.c new file mode 100644 index 0000000..643f4db --- /dev/null +++ b/src/smtp/smtp_key.c @@ -0,0 +1,215 @@ +/*++ +/* NAME +/* smtp_key 3 +/* SUMMARY +/* cache/table lookup key management +/* SYNOPSIS +/* #include "smtp.h" +/* +/* char *smtp_key_prefix(buffer, delim_na, iterator, context_flags) +/* VSTRING *buffer; +/* const char *delim_na; +/* SMTP_ITERATOR *iterator; +/* int context_flags; +/* DESCRIPTION +/* The Postfix SMTP server accesses caches and lookup tables, +/* using lookup keys that contain information from various +/* contexts: per-server configuration, per-request envelope, +/* and results from DNS queries. +/* +/* These lookup keys sometimes share the same context information. +/* The primary purpose of this API is to ensure that this +/* shared context is used consistently, and that its use is +/* made explicit (both are needed to verify that there is no +/* false cache sharing). +/* +/* smtp_key_prefix() constructs a lookup key prefix from context +/* that may be shared with other lookup keys. The user is free +/* to append additional application-specific context. The result +/* value is a pointer to the result text. +/* +/* Arguments: +/* .IP buffer +/* Storage for the result. +/* .IP delim_na +/* The field delimiter character, and the optional place holder +/* character for a) information that is unavailable, b) +/* information that is inapplicable, or c) that would result +/* in an empty field. Key fields that contain "delim_na" +/* characters will be base64-encoded. +/* Do not specify "delim_na" characters that are part of the +/* base64 character set. +/* .IP iterator +/* Information that will be selected by the specified flags. +/* .IP context_flags +/* Bit-wise OR of one or more of the following. +/* .RS +/* .IP SMTP_KEY_FLAG_SERVICE +/* The global service name. This is a proxy for +/* destination-independent and request-independent context. +/* .IP SMTP_KEY_FLAG_SENDER +/* The envelope sender address. This is a proxy for sender-dependent +/* context, such as per-sender SASL authentication. +/* .IP SMTP_KEY_FLAG_REQ_NEXTHOP +/* The delivery request nexthop destination, including optional +/* [] and :port (the same form that users specify in a SASL +/* password or TLS policy lookup table). This is a proxy for +/* destination-dependent, but host-independent context. +/* .IP SMTP_KEY_FLAG_CUR_NEXTHOP +/* The current iterator's nexthop destination (delivery request +/* nexthop or fallback nexthop, including optional [] and +/* :port). +/* .IP SMTP_KEY_FLAG_HOSTNAME +/* The current iterator's remote hostname. +/* .IP SMTP_KEY_FLAG_ADDR +/* The current iterator's remote address. +/* .IP SMTP_KEY_FLAG_PORT +/* The current iterator's remote port. +/* .RE +/* DIAGNOSTICS +/* Panic: undefined flag or zero flags. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <netinet/in.h> /* ntohs() for Solaris or BSD */ +#include <arpa/inet.h> /* ntohs() for Linux or BSD */ +#include <string.h> + + /* + * Utility library. + */ +#include <msg.h> +#include <vstring.h> +#include <base64_code.h> + + /* + * Global library. + */ +#include <mail_params.h> + + /* + * Application-specific. + */ +#include <smtp.h> + + /* + * We use a configurable field terminator and optional place holder for data + * that is unavailable or inapplicable. We base64-encode content that + * contains these characters, and content that needs obfuscation. + */ + +/* smtp_key_append_na - append place-holder key field */ + +static void smtp_key_append_na(VSTRING *buffer, const char *delim_na) +{ + if (delim_na[1] != 0) + VSTRING_ADDCH(buffer, delim_na[1]); + VSTRING_ADDCH(buffer, delim_na[0]); +} + +/* smtp_key_append_str - append string-valued key field */ + +static void smtp_key_append_str(VSTRING *buffer, const char *str, + const char *delim_na) +{ + if (str == 0 || str[0] == 0) { + smtp_key_append_na(buffer, delim_na); + } else if (str[strcspn(str, delim_na)] != 0) { + base64_encode_opt(buffer, str, strlen(str), BASE64_FLAG_APPEND); + VSTRING_ADDCH(buffer, delim_na[0]); + } else { + vstring_sprintf_append(buffer, "%s%c", str, delim_na[0]); + } +} + +/* smtp_key_append_uint - append unsigned-valued key field */ + +static void smtp_key_append_uint(VSTRING *buffer, unsigned num, + const char *delim_na) +{ + vstring_sprintf_append(buffer, "%u%c", num, delim_na[0]); +} + +/* smtp_key_prefix - format common elements in lookup key */ + +char *smtp_key_prefix(VSTRING *buffer, const char *delim_na, + SMTP_ITERATOR *iter, int flags) +{ + static const char myname[] = "smtp_key_prefix"; + SMTP_STATE *state = iter->parent; /* private member */ + + /* + * Sanity checks. + */ + if (state == 0) + msg_panic("%s: no parent state", myname); + if (flags & ~SMTP_KEY_MASK_ALL) + msg_panic("%s: unknown key flags 0x%x", + myname, flags & ~SMTP_KEY_MASK_ALL); + if (flags == 0) + msg_panic("%s: zero flags", myname); + + /* + * Initialize. + */ + VSTRING_RESET(buffer); + + /* + * Per-service and per-request context. + */ + if (flags & SMTP_KEY_FLAG_SERVICE) + smtp_key_append_str(buffer, state->service, delim_na); + if (flags & SMTP_KEY_FLAG_SENDER) + smtp_key_append_str(buffer, state->request->sender, delim_na); + + /* + * Per-destination context, non-canonicalized form. + */ + if (flags & SMTP_KEY_FLAG_REQ_NEXTHOP) + smtp_key_append_str(buffer, STR(iter->request_nexthop), delim_na); + if (flags & SMTP_KEY_FLAG_CUR_NEXTHOP) + smtp_key_append_str(buffer, STR(iter->dest), delim_na); + + /* + * Per-host context, canonicalized form. + */ + if (flags & SMTP_KEY_FLAG_HOSTNAME) + smtp_key_append_str(buffer, STR(iter->host), delim_na); + if (flags & SMTP_KEY_FLAG_ADDR) + smtp_key_append_str(buffer, STR(iter->addr), delim_na); + if (flags & SMTP_KEY_FLAG_PORT) + smtp_key_append_uint(buffer, ntohs(iter->port), delim_na); + + /* + * Requested TLS level, if applicable. TODO(tlsproxy) should the lookup + * engine also try the requested TLS level and 'stronger', in case a + * server hosts multiple domains with different TLS requirements? + */ + if (flags & SMTP_KEY_FLAG_TLS_LEVEL) +#ifdef USE_TLS + smtp_key_append_uint(buffer, state->tls->level, delim_na); +#else + smtp_key_append_na(buffer, delim_na); +#endif + + VSTRING_TERMINATE(buffer); + + return STR(buffer); +} diff --git a/src/smtp/smtp_map11.c b/src/smtp/smtp_map11.c new file mode 100644 index 0000000..bca8641 --- /dev/null +++ b/src/smtp/smtp_map11.c @@ -0,0 +1,325 @@ +/*++ +/* NAME +/* smtp_map11 3 +/* SUMMARY +/* one-to-one address mapping +/* SYNOPSIS +/* #include <smtp.h> +/* +/* int smtp_map11_internal(addr, maps, propagate) +/* VSTRING *addr; +/* MAPS *maps; +/* int propagate; +/* +/* int smtp_map11_external(addr, maps, propagate) +/* VSTRING *addr; +/* MAPS *maps; +/* int propagate; +/* +/* int smtp_map11_tree(tree, maps, propagate) +/* TOK822 *tree; +/* MAPS *maps; +/* int propagate; +/* DESCRIPTION +/* This module performs non-recursive one-to-one address mapping. +/* An unmatched address extension is propagated when +/* \fIpropagate\fR is non-zero. +/* +/* smtp_map11_internal() looks up the RFC 822 internal (unquoted) +/* string form of an address in the maps specified via the +/* \fImaps\fR argument. +/* +/* smtp_map11_external() is a wrapper around the smtp_map11_internal() +/* routine that transforms from external (quoted) string form +/* to internal form and back. +/* +/* smtp_map11_tree() is a wrapper around the smtp_map11_internal() +/* routine that transforms from internal parse tree form to +/* internal form and back. +/* DIAGNOSTICS +/* Table lookup errors are fatal. +/* SEE ALSO +/* mail_addr_map(3) address mappings +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <dict.h> +#include <argv.h> +#include <tok822.h> + +/* Global library. */ + +#include <mail_addr_map.h> +#include <quote_822_local.h> + +/* Application-specific. */ + +#include <smtp.h> + +/* smtp_map11_internal - one-to-one table lookups */ + +int smtp_map11_internal(VSTRING *addr, MAPS *maps, int propagate) +{ + const char *myname = "smtp_map11_internal"; + ARGV *new_addr; + const char *result; + + if ((new_addr = mail_addr_map_internal(maps, STR(addr), propagate)) != 0) { + if (new_addr->argc > 1) + msg_warn("multi-valued %s result for %s", maps->title, STR(addr)); + result = new_addr->argv[0]; + if (msg_verbose) + msg_info("%s: %s -> %s", myname, STR(addr), result); + vstring_strcpy(addr, result); + argv_free(new_addr); + return (1); + } else { + if (maps->error != 0) + msg_fatal("%s map lookup problem for %s", maps->title, STR(addr)); + if (msg_verbose) + msg_info("%s: %s not found", myname, STR(addr)); + return (0); + } +} + +/* smtp_map11_tree - rewrite address node */ + +int smtp_map11_tree(TOK822 *tree, MAPS *maps, int propagate) +{ + VSTRING *int_buf = vstring_alloc(100); + VSTRING *ext_buf = vstring_alloc(100); + int ret; + + tok822_internalize(int_buf, tree->head, TOK822_STR_DEFL); + ret = smtp_map11_internal(int_buf, maps, propagate); + tok822_free_tree(tree->head); + quote_822_local(ext_buf, STR(int_buf)); + tree->head = tok822_scan(STR(ext_buf), &tree->tail); + vstring_free(int_buf); + vstring_free(ext_buf); + return (ret); +} + +/* smtp_map11_external - rewrite address external form */ + +int smtp_map11_external(VSTRING *addr, MAPS *maps, int propagate) +{ + VSTRING *temp = vstring_alloc(100); + int ret; + + unquote_822_local(temp, STR(addr)); + ret = smtp_map11_internal(temp, maps, propagate); + quote_822_local(addr, STR(temp)); + vstring_free(temp); + return (ret); +} + +#ifdef TEST +#include <ctype.h> + +#include <msg_vstream.h> +#include <readlline.h> +#include <stringops.h> +#include <vstring_vstream.h> + +#include <canon_addr.h> +#include <mail_params.h> + +/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */ + +VSTRING *canon_addr_external(VSTRING *result, const char *addr) +{ + char *at; + + vstring_strcpy(result, addr); + if ((at = strrchr(addr, '@')) == 0 + || (at + 1)[strcspn(at + 1, "\"\\")] != 0) + vstring_sprintf_append(result, "@%s", var_myorigin); + return (result); +} + +static NORETURN usage(const char *progname) +{ + msg_fatal("usage: %s [-v]", progname); +} + +int main(int argc, char **argv) +{ + VSTRING *read_buf = vstring_alloc(100); + MAPS *maps = 0; + int lineno; + int first_line; + char *bp; + char *cmd; + VSTRING *addr_buf = vstring_alloc(100); + char *addr_field; + char *res_field; + int ch; + int errs = 0; + + /* + * Initialize. + */ + msg_vstream_init(basename(argv[0]), VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "v")) > 0) { + switch (ch) { + case 'v': + msg_verbose++; + break; + default: + usage(argv[0]); + } + } + if (argc != optind) + usage(argv[0]); + + util_utf8_enable = 1; + mail_params_init(); + + /* + * TODO: Data-driven parameter settings. + */ +#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0) + + UPDATE(var_myhostname, "localhost.localdomain"); + UPDATE(var_mydomain, "localdomain"); + UPDATE(var_myorigin, "localdomain"); + UPDATE(var_mydest, "localhost.localdomain"); + UPDATE(var_rcpt_delim, "+"); + + /* + * The read-eval-print loop. + */ + while (readllines(read_buf, VSTREAM_IN, &lineno, &first_line)) { + bp = STR(read_buf); + if (msg_verbose) + msg_info("> %s", bp); + if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0 || *cmd == '#') + continue; + while (ISSPACE(*bp)) + bp++; + if (*bp == 0) + msg_fatal("missing parameter for command %s", cmd); + + /* + * Open maps. + */ + if (strcmp(cmd, "maps") == 0) { + if (maps) + maps_free(maps); + maps = maps_create(bp, bp, DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + vstream_printf("%s\n", bp); + continue; + } + + /* + * Lookup and verify. + */ + else if (maps != 0 && (strcmp(cmd, "external") == 0 + || strcmp(cmd, "internal") == 0 + || strcmp(cmd, "tree") == 0)) { + int have_result = 0; + + + /* + * Parse the input and expectations. + */ + if ((addr_field = mystrtok(&bp, ":")) == 0) + msg_fatal("missing address field"); + res_field = mystrtok(&bp, ":"); + if (mystrtok(&bp, ":") != 0) + msg_fatal("garbage after result field"); + + /* + * Perform the mapping. + */ + if (strcmp(cmd, "external") == 0) { + vstring_strcpy(addr_buf, addr_field); + have_result = smtp_map11_external(addr_buf, maps, 1); + } else if (maps && strcmp(cmd, "internal") == 0) { + vstring_strcpy(addr_buf, addr_field); + have_result = smtp_map11_internal(addr_buf, maps, 1); + } else if (maps && strcmp(cmd, "tree") == 0) { + TOK822 *tree; + TOK822 **addr_list; + TOK822 **tpp; + + tree = tok822_parse(addr_field); + addr_list = tok822_grep(tree, TOK822_ADDR); + for (tpp = addr_list; *tpp; tpp++) + have_result |= smtp_map11_tree(tpp[0], maps, 1); + myfree((void *) addr_list); + if (have_result) + tok822_externalize(addr_buf, tree, TOK822_STR_DEFL); + tok822_free_tree(tree); + } + + /* + * Summarize. + */ + vstream_printf("%s:%s -> %s\n", + cmd, addr_field, have_result ? + STR(addr_buf) : maps->error ? + "(error)" : "(no match)"); + vstream_fflush(VSTREAM_OUT); + + /* + * Enforce expectations. + */ + if (res_field && have_result) { + if (strcmp(res_field, STR(addr_buf)) != 0) { + msg_warn("expect result '%s' but got '%s'", + res_field, STR(addr_buf)); + errs = 1; + } + } else if (res_field && !have_result) { + msg_warn("expect result '%s' but got none", res_field); + errs = 1; + } else if (!res_field && have_result) { + msg_warn("expected no result but got '%s'", STR(addr_buf)); + errs = 1; + } + vstream_fflush(VSTREAM_OUT); + } + + /* + * Unknown request. + */ + else { + msg_fatal("bad request: %s", cmd); + } + } + vstring_free(addr_buf); + vstring_free(read_buf); + maps_free(maps); + return (errs != 0); +} + +#endif diff --git a/src/smtp/smtp_map11.in b/src/smtp/smtp_map11.in new file mode 100644 index 0000000..242859f --- /dev/null +++ b/src/smtp/smtp_map11.in @@ -0,0 +1,33 @@ +# Table with external-form mapping. +maps inline:{ + { foo@example.com = bar@com.example } + { bar@example.com = bar } + { baz@example.com = @com.example } + { splitme@example.com = "split me"@com.example } } + +# Tests for external form. +external foo@example.com:bar@com.example +external bar@example.com:bar@localdomain +external baz@example.com:baz@com.example +external foo@example.net +external splitme@example.com:"split me"@com.example +external splitme+ext@example.com:"split me+ext"@com.example +external "baz+first last"@example.com:"baz+first last"@com.example + +# Same tests for tree form. +tree foo@example.com:bar@com.example +tree bar@example.com:bar@localdomain +tree baz@example.com:baz@com.example +tree foo@example.net +tree splitme@example.com:"split me"@com.example +tree splitme+ext@example.com:"split me+ext"@com.example +tree "baz+first last"@example.com:"baz+first last"@com.example + +# Same tests for internal form. +internal foo@example.com:bar@com.example +internal bar@example.com:bar@localdomain +internal baz@example.com:baz@com.example +internal foo@example.net +internal splitme@example.com:split me@com.example +internal splitme+ext@example.com:split me+ext@com.example +internal baz+first last@example.com:baz+first last@com.example diff --git a/src/smtp/smtp_map11.ref b/src/smtp/smtp_map11.ref new file mode 100644 index 0000000..7e6f2ed --- /dev/null +++ b/src/smtp/smtp_map11.ref @@ -0,0 +1,22 @@ +inline:{ { foo@example.com = bar@com.example } { bar@example.com = bar } { baz@example.com = @com.example } { splitme@example.com = "split me"@com.example } } +external:foo@example.com -> bar@com.example +external:bar@example.com -> bar@localdomain +external:baz@example.com -> baz@com.example +external:foo@example.net -> (no match) +external:splitme@example.com -> "split me"@com.example +external:splitme+ext@example.com -> "split me+ext"@com.example +external:"baz+first last"@example.com -> "baz+first last"@com.example +tree:foo@example.com -> bar@com.example +tree:bar@example.com -> bar@localdomain +tree:baz@example.com -> baz@com.example +tree:foo@example.net -> (no match) +tree:splitme@example.com -> "split me"@com.example +tree:splitme+ext@example.com -> "split me+ext"@com.example +tree:"baz+first last"@example.com -> "baz+first last"@com.example +internal:foo@example.com -> bar@com.example +internal:bar@example.com -> bar@localdomain +internal:baz@example.com -> baz@com.example +internal:foo@example.net -> (no match) +internal:splitme@example.com -> split me@com.example +internal:splitme+ext@example.com -> split me+ext@com.example +internal:baz+first last@example.com -> baz+first last@com.example diff --git a/src/smtp/smtp_params.c b/src/smtp/smtp_params.c new file mode 100644 index 0000000..561f6a0 --- /dev/null +++ b/src/smtp/smtp_params.c @@ -0,0 +1,134 @@ + static const CONFIG_STR_TABLE smtp_str_table[] = { + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_SMTP_FALLBACK, DEF_SMTP_FALLBACK, &var_fallback_relay, 0, 0, + VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, + VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, + VAR_SMTP_SASL_PASSWD, DEF_SMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, + VAR_SMTP_SASL_OPTS, DEF_SMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, + VAR_SMTP_SASL_PATH, DEF_SMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0, +#ifdef USE_TLS + VAR_SMTP_SASL_TLS_OPTS, DEF_SMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0, + VAR_SMTP_SASL_TLSV_OPTS, DEF_SMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0, + VAR_SMTP_TLS_CHAIN_FILES, DEF_SMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0, + VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_SMTP_TLS_MAND_CIPH, DEF_SMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0, + VAR_SMTP_TLS_EXCL_CIPH, DEF_SMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0, + VAR_SMTP_TLS_MAND_EXCL, DEF_SMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0, + VAR_SMTP_TLS_MAND_PROTO, DEF_SMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0, + VAR_SMTP_TLS_VFY_CMATCH, DEF_SMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0, + VAR_SMTP_TLS_SEC_CMATCH, DEF_SMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0, + VAR_SMTP_TLS_FPT_CMATCH, DEF_SMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0, + VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_SMTP_TLS_TAFILE, DEF_SMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0, + VAR_SMTP_TLS_PROTO, DEF_SMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0, + VAR_SMTP_TLS_CIPH, DEF_SMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0, + VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, + VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0, + VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_SMTP_TLS_SNI, DEF_SMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0, + VAR_SMTP_TLS_INSECURE_MX_POLICY, DEF_SMTP_TLS_INSECURE_MX_POLICY, &var_smtp_tls_insecure_mx_policy, 0, 0, +#endif + VAR_SMTP_SASL_MECHS, DEF_SMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0, + VAR_SMTP_SASL_TYPE, DEF_SMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0, + VAR_SMTP_BIND_ADDR, DEF_SMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0, + VAR_SMTP_BIND_ADDR6, DEF_SMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0, + VAR_SMTP_VRFY_TGT, DEF_SMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0, + VAR_SMTP_HELO_NAME, DEF_SMTP_HELO_NAME, &var_smtp_helo_name, 1, 0, + VAR_SMTP_HOST_LOOKUP, DEF_SMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0, + VAR_SMTP_DNS_SUPPORT, DEF_SMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0, + VAR_SMTP_CACHE_DEST, DEF_SMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0, + VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0, + VAR_SMTP_EHLO_DIS_WORDS, DEF_SMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0, + VAR_SMTP_EHLO_DIS_MAPS, DEF_SMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0, + VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, + VAR_SMTP_TLS_LEVEL, DEF_SMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0, + VAR_SMTP_TLS_POLICY, DEF_SMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0, + VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, + VAR_SMTP_GENERIC_MAPS, DEF_SMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0, + VAR_SMTP_TCP_PORT, DEF_SMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0, + VAR_SMTP_PIX_BUG_WORDS, DEF_SMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0, + VAR_SMTP_PIX_BUG_MAPS, DEF_SMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0, + VAR_SMTP_SASL_AUTH_CACHE_NAME, DEF_SMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0, + VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0, + VAR_SMTP_HEAD_CHKS, DEF_SMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0, + VAR_SMTP_MIME_CHKS, DEF_SMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0, + VAR_SMTP_NEST_CHKS, DEF_SMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0, + VAR_SMTP_BODY_CHKS, DEF_SMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0, + VAR_SMTP_RESP_FILTER, DEF_SMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0, + VAR_SMTP_ADDR_PREF, DEF_SMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0, + VAR_SMTP_DNS_RES_OPT, DEF_SMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0, + VAR_SMTP_DSN_FILTER, DEF_SMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0, + VAR_SMTP_DNS_RE_FILTER, DEF_SMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0, + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, + 0, + }; + static const CONFIG_TIME_TABLE smtp_time_table[] = { + VAR_SMTP_CONN_TMOUT, DEF_SMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0, + VAR_SMTP_HELO_TMOUT, DEF_SMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0, + VAR_SMTP_XFWD_TMOUT, DEF_SMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0, + VAR_SMTP_MAIL_TMOUT, DEF_SMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0, + VAR_SMTP_RCPT_TMOUT, DEF_SMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0, + VAR_SMTP_DATA0_TMOUT, DEF_SMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0, + VAR_SMTP_DATA1_TMOUT, DEF_SMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0, + VAR_SMTP_DATA2_TMOUT, DEF_SMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0, + VAR_SMTP_RSET_TMOUT, DEF_SMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0, + VAR_SMTP_QUIT_TMOUT, DEF_SMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0, + VAR_SMTP_PIX_THRESH, DEF_SMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0, + VAR_SMTP_PIX_DELAY, DEF_SMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0, + VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0, + VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0, + VAR_SMTP_CACHE_CONNT, DEF_SMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0, + VAR_SMTP_REUSE_TIME, DEF_SMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0, +#ifdef USE_TLS + VAR_SMTP_STARTTLS_TMOUT, DEF_SMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0, +#endif + VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0, + VAR_SMTP_SASL_AUTH_CACHE_TIME, DEF_SMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0, + 0, + }; + static const CONFIG_INT_TABLE smtp_int_table[] = { + VAR_SMTP_LINE_LIMIT, DEF_SMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0, + VAR_SMTP_MXADDR_LIMIT, DEF_SMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0, + VAR_SMTP_MXSESS_LIMIT, DEF_SMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0, + VAR_SMTP_REUSE_COUNT, DEF_SMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0, +#ifdef USE_TLS + VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, +#endif + 0, + }; + static const CONFIG_BOOL_TABLE smtp_bool_table[] = { + VAR_SMTP_SKIP_5XX, DEF_SMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting, + VAR_IGN_MX_LOOKUP_ERR, DEF_IGN_MX_LOOKUP_ERR, &var_ign_mx_lookup_err, + VAR_SMTP_SKIP_QUIT_RESP, DEF_SMTP_SKIP_QUIT_RESP, &var_skip_quit_resp, + VAR_SMTP_ALWAYS_EHLO, DEF_SMTP_ALWAYS_EHLO, &var_smtp_always_ehlo, + VAR_SMTP_NEVER_EHLO, DEF_SMTP_NEVER_EHLO, &var_smtp_never_ehlo, + VAR_SMTP_SASL_ENABLE, DEF_SMTP_SASL_ENABLE, &var_smtp_sasl_enable, + VAR_SMTP_RAND_ADDR, DEF_SMTP_RAND_ADDR, &var_smtp_rand_addr, + VAR_SMTP_QUOTE_821_ENV, DEF_SMTP_QUOTE_821_ENV, &var_smtp_quote_821_env, + VAR_SMTP_DEFER_MXADDR, DEF_SMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr, + VAR_SMTP_SEND_XFORWARD, DEF_SMTP_SEND_XFORWARD, &var_smtp_send_xforward, + VAR_SMTP_CACHE_DEMAND, DEF_SMTP_CACHE_DEMAND, &var_smtp_cache_demand, + VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls, + VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_SMTP_TLS_CONN_REUSE, DEF_SMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse, +#ifdef USE_TLS + VAR_SMTP_TLS_ENFORCE_PN, DEF_SMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, + VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, + VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, + VAR_SMTP_TLS_FORCE_TLSA, DEF_SMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, +#endif + VAR_SMTP_TLS_WRAPPER, DEF_SMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, + VAR_SMTP_SENDER_AUTH, DEF_SMTP_SENDER_AUTH, &var_smtp_sender_auth, + VAR_SMTP_CNAME_OVERR, DEF_SMTP_CNAME_OVERR, &var_smtp_cname_overr, + VAR_SMTP_SASL_AUTH_SOFT_BOUNCE, DEF_SMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce, + VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final, + VAR_SMTP_REC_DEADLINE, DEF_SMTP_REC_DEADLINE, &var_smtp_rec_deadline, + VAR_SMTP_DUMMY_MAIL_AUTH, DEF_SMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth, + VAR_SMTP_BALANCE_INET_PROTO, DEF_SMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto, + 0, + }; diff --git a/src/smtp/smtp_proto.c b/src/smtp/smtp_proto.c new file mode 100644 index 0000000..a43a326 --- /dev/null +++ b/src/smtp/smtp_proto.c @@ -0,0 +1,2469 @@ +/*++ +/* NAME +/* smtp_proto 3 +/* SUMMARY +/* client SMTP/LMTP protocol +/* SYNOPSIS +/* #include "smtp.h" +/* +/* int smtp_helo(state) +/* SMTP_STATE *state; +/* +/* int smtp_xfer(state) +/* SMTP_STATE *state; +/* +/* int smtp_rset(state) +/* SMTP_STATE *state; +/* +/* int smtp_quit(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* In the subsequent text, SMTP implies LMTP. +/* This module implements the client side of the SMTP protocol. +/* +/* smtp_helo() performs the initial handshake with the SMTP server. +/* When TLS is enabled, this includes STARTTLS negotiations. +/* +/* smtp_xfer() sends message envelope information followed by the +/* message data, and finishes the SMTP conversation. These operations +/* are combined in one function, in order to implement SMTP pipelining. +/* Recipients are marked as "done" in the mail queue file when +/* bounced or delivered. The message delivery status is updated +/* accordingly. +/* +/* smtp_rset() sends a single RSET command and waits for the +/* response. In case of a negative reply it sets the +/* CANT_RSET_THIS_SESSION flag. +/* +/* smtp_quit() sends a single QUIT command and waits for the +/* response if configured to do so. It always turns off connection +/* caching. +/* DIAGNOSTICS +/* smtp_helo(), smtp_xfer(), smtp_rset() and smtp_quit() return +/* 0 in case of success, -1 in case of failure. For smtp_xfer(), +/* smtp_rset() and smtp_quit(), success means the ability to +/* perform an SMTP conversation, not necessarily the ability +/* to deliver mail, or the achievement of server happiness. +/* +/* In case of a rejected or failed connection, a connection +/* is marked as "bad, do not cache". Otherwise, connection +/* caching may be turned off (without being marked "bad") at +/* the discretion of the code that implements the individual +/* protocol steps. +/* +/* Warnings: corrupt message file. A corrupt message is marked +/* as "corrupt" by changing its queue file permissions. +/* BUGS +/* Some SMTP servers will abort when the number of recipients +/* for one message exceeds their capacity. This behavior violates +/* the SMTP protocol. +/* The only way around this is to limit the number of recipients +/* per transaction to an artificially-low value. +/* SEE ALSO +/* smtp(3h) internal data structures +/* smtp_chat(3) query/reply SMTP support +/* smtp_trouble(3) error handlers +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Pipelining code in cooperation with: +/* Jon Ribbens +/* Oaktree Internet Solutions Ltd., +/* Internet House, +/* Canal Basin, +/* Coventry, +/* CV1 4LY, United Kingdom. +/* +/* Connection caching in cooperation with: +/* Victor Duchovni +/* Morgan Stanley +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <sys/socket.h> /* shutdown(2) */ +#include <netinet/in.h> /* ntohs() */ +#include <string.h> +#include <unistd.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <time.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <stringops.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <split_at.h> +#include <name_code.h> +#include <name_mask.h> + +/* Global library. */ + +#include <mail_params.h> +#include <smtp_stream.h> +#include <mail_queue.h> +#include <recipient_list.h> +#include <deliver_request.h> +#include <defer.h> +#include <bounce.h> +#include <record.h> +#include <rec_type.h> +#include <off_cvt.h> +#include <mark_corrupt.h> +#include <quote_821_local.h> +#include <quote_822_local.h> +#include <mail_proto.h> +#include <mime_state.h> +#include <ehlo_mask.h> +#include <maps.h> +#include <tok822.h> +#include <mail_addr_map.h> +#include <ext_prop.h> +#include <namadr_list.h> +#include <match_parent_style.h> +#include <lex_822.h> +#include <dsn_mask.h> +#include <xtext.h> +#include <uxtext.h> +#include <smtputf8.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + + /* + * Sender and receiver state. A session does not necessarily go through a + * linear progression, but states are guaranteed to not jump backwards. + * Normal sessions go from MAIL->RCPT->DATA->DOT->QUIT->LAST. The states + * MAIL, RCPT, and DATA may also be followed by ABORT->QUIT->LAST. + * + * When connection caching is enabled, the QUIT state is suppressed. Normal + * sessions proceed as MAIL->RCPT->DATA->DOT->LAST, while aborted sessions + * end with ABORT->LAST. The connection is left open for a limited time. An + * RSET probe should be sent before attempting to reuse an open connection + * for a new transaction. + * + * The code to send an RSET probe is a special case with its own initial state + * and with its own dedicated state transitions. The session proceeds as + * RSET->LAST. This code is kept inside the main protocol engine for + * consistent error handling and error reporting. It is not to be confused + * with the code that sends RSET to abort a mail transaction in progress. + * + * The code to send QUIT without message delivery transaction jumps into the + * main state machine. If this introduces complications, then we should + * introduce a second QUIT state with its own dedicated state transitions, + * just like we did for RSET probes. + * + * By default, the receiver skips the QUIT response. Some SMTP servers + * disconnect after responding to ".", and some SMTP servers wait before + * responding to QUIT. + * + * Client states that are associated with sending mail (up to and including + * SMTP_STATE_DOT) must have smaller numerical values than the non-sending + * states (SMTP_STATE_ABORT .. SMTP_STATE_LAST). + */ +#define SMTP_STATE_XFORWARD_NAME_ADDR 0 +#define SMTP_STATE_XFORWARD_PROTO_HELO 1 +#define SMTP_STATE_MAIL 2 +#define SMTP_STATE_RCPT 3 +#define SMTP_STATE_DATA 4 +#define SMTP_STATE_DOT 5 +#define SMTP_STATE_ABORT 6 +#define SMTP_STATE_RSET 7 +#define SMTP_STATE_QUIT 8 +#define SMTP_STATE_LAST 9 + +int *xfer_timeouts[SMTP_STATE_LAST] = { + &var_smtp_xfwd_tmout, /* name/addr */ + &var_smtp_xfwd_tmout, /* helo/proto */ + &var_smtp_mail_tmout, + &var_smtp_rcpt_tmout, + &var_smtp_data0_tmout, + &var_smtp_data2_tmout, + &var_smtp_rset_tmout, + &var_smtp_rset_tmout, + &var_smtp_quit_tmout, +}; + +char *xfer_states[SMTP_STATE_LAST] = { + "sending XFORWARD name/address", + "sending XFORWARD protocol/helo_name", + "sending MAIL FROM", + "sending RCPT TO", + "sending DATA command", + "sending end of data -- message may be sent more than once", + "sending final RSET", + "sending RSET probe", + "sending QUIT", +}; + +char *xfer_request[SMTP_STATE_LAST] = { + "XFORWARD name/address command", + "XFORWARD helo/protocol command", + "MAIL FROM command", + "RCPT TO command", + "DATA command", + "end of DATA command", + "final RSET command", + "RSET probe", + "QUIT command", +}; + + /* + * Note: MIME downgrade never happens for mail that must be delivered with + * SMTPUTF8 (the sender requested SMTPUTF8, AND the delivery request + * involves at least one UTF-8 envelope address or header value. + */ +#define SMTP_MIME_DOWNGRADE(session, request) \ + (var_disable_mime_oconv == 0 \ + && (session->features & SMTP_FEATURE_8BITMIME) == 0 \ + && strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0) + +#ifdef USE_TLS + +static int smtp_start_tls(SMTP_STATE *); + +#endif + + /* + * Call-back information for header/body checks. We don't provide call-backs + * for actions that change the message delivery time or destination. + */ +static void smtp_hbc_logger(void *, const char *, const char *, const char *, const char *); +static void smtp_text_out(void *, int, const char *, ssize_t, off_t); + +HBC_CALL_BACKS smtp_hbc_callbacks[1] = { + smtp_hbc_logger, + smtp_text_out, +}; + +static int smtp_vrfy_tgt; + +/* smtp_vrfy_init - initialize */ + +void smtp_vrfy_init(void) +{ + static const NAME_CODE vrfy_init_table[] = { + SMTP_VRFY_TGT_RCPT, SMTP_STATE_RCPT, + SMTP_VRFY_TGT_DATA, SMTP_STATE_DATA, + 0, + }; + + if ((smtp_vrfy_tgt = name_code(vrfy_init_table, NAME_CODE_FLAG_NONE, + var_smtp_vrfy_tgt)) == 0) + msg_fatal("bad protocol stage: \"%s = %s\"", + VAR_SMTP_VRFY_TGT, var_smtp_vrfy_tgt); +} + +/* smtp_helo - perform initial handshake with SMTP server */ + +int smtp_helo(SMTP_STATE *state) +{ + const char *myname = "smtp_helo"; + SMTP_SESSION *session = state->session; + DELIVER_REQUEST *request = state->request; + SMTP_ITERATOR *iter = state->iterator; + SMTP_RESP *resp; + SMTP_RESP fake; + int except; + char *lines; + char *words; + char *word; + int n; + static const NAME_CODE xforward_features[] = { + XFORWARD_NAME, SMTP_FEATURE_XFORWARD_NAME, + XFORWARD_ADDR, SMTP_FEATURE_XFORWARD_ADDR, + XFORWARD_PORT, SMTP_FEATURE_XFORWARD_PORT, + XFORWARD_PROTO, SMTP_FEATURE_XFORWARD_PROTO, + XFORWARD_HELO, SMTP_FEATURE_XFORWARD_HELO, + XFORWARD_IDENT, SMTP_FEATURE_XFORWARD_IDENT, + XFORWARD_DOMAIN, SMTP_FEATURE_XFORWARD_DOMAIN, + 0, 0, + }; + const char *ehlo_words; + int discard_mask; + static const NAME_MASK pix_bug_table[] = { + PIX_BUG_DISABLE_ESMTP, SMTP_FEATURE_PIX_NO_ESMTP, + PIX_BUG_DELAY_DOTCRLF, SMTP_FEATURE_PIX_DELAY_DOTCRLF, + 0, + }; + const char *pix_bug_words; + const char *pix_bug_source; + int pix_bug_mask; + +#ifdef USE_TLS + int saved_features = session->features; + int tls_helo_status; + +#endif + const char *NOCLOBBER where; + + /* + * Skip the plaintext SMTP handshake when connecting in SMTPS mode. + */ +#ifdef USE_TLS + if (var_smtp_tls_wrappermode + && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + /* XXX Mix-up of per-session and per-request flags. */ + state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; + smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout, + var_smtp_rec_deadline); + tls_helo_status = smtp_start_tls(state); + state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS; + return (tls_helo_status); + } +#endif + + /* + * Prepare for disaster. + */ + smtp_stream_setup(state->session->stream, var_smtp_helo_tmout, + var_smtp_rec_deadline); + if ((except = vstream_setjmp(state->session->stream)) != 0) + return (smtp_stream_except(state, except, where)); + + /* + * If not recursing after STARTTLS, examine the server greeting banner + * and decide if we are going to send EHLO as the next command. + */ + if (var_smtp_tls_wrappermode + || (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + + /* + * Read and parse the server's SMTP greeting banner. + */ + where = "receiving the initial server greeting"; + switch ((resp = smtp_chat_resp(session))->code / 100) { + case 2: + break; + case 5: + if (var_smtp_skip_5xx_greeting) + STR(resp->dsn_buf)[0] = '4'; + /* FALLTHROUGH */ + default: + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + } + + /* + * If the policy table specifies a bogus TLS security level, fail + * now. + */ +#ifdef USE_TLS + if (state->tls->level == TLS_LEV_INVALID) + /* Warning is already logged. */ + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.0"), + "client TLS configuration problem")); +#endif + + /* + * XXX Some PIX firewall versions require flush before ".<CR><LF>" so + * it does not span a packet boundary. This hurts performance so it + * is not on by default. + */ + if (resp->str[strspn(resp->str, "20 *\t\n")] == 0) { + /* Best effort only. Ignore errors. */ + if (smtp_pix_bug_maps != 0 + && (pix_bug_words = + maps_find(smtp_pix_bug_maps, + STR(iter->addr), 0)) != 0) { + pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_MAPS); + } else { + pix_bug_words = var_smtp_pix_bug_words; + pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_WORDS); + } + if (*pix_bug_words) { + pix_bug_mask = name_mask_opt(pix_bug_source, pix_bug_table, + pix_bug_words, + NAME_MASK_ANY_CASE | NAME_MASK_IGNORE); + if ((pix_bug_mask & SMTP_FEATURE_PIX_DELAY_DOTCRLF) + && request->msg_stats.incoming_arrival.tv_sec + > vstream_ftime(state->session->stream) - var_smtp_pix_thresh) + pix_bug_mask &= ~SMTP_FEATURE_PIX_DELAY_DOTCRLF; + msg_info("%s: enabling PIX workarounds: %s for %s", + request->queue_id, + str_name_mask("pix workaround bitmask", + pix_bug_table, pix_bug_mask), + session->namaddrport); + session->features |= pix_bug_mask; + } + } + + /* + * See if we are talking to ourself. This should not be possible with + * the way we implement DNS lookups. However, people are known to + * sometimes screw up the naming service. And, mailer loops are still + * possible when our own mailer routing tables are mis-configured. + */ + words = resp->str; + (void) mystrtok(&words, "- \t\n"); + for (n = 0; (word = mystrtok(&words, " \t\n")) != 0; n++) { + if (n == 0 && strcasecmp(word, var_myhostname) == 0) { + if (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) + msg_warn("host %s greeted me with my own hostname %s", + session->namaddrport, var_myhostname); + } else if (strcasecmp(word, "ESMTP") == 0) + session->features |= SMTP_FEATURE_ESMTP; + } + if (smtp_mode) { + if (var_smtp_always_ehlo + && (session->features & SMTP_FEATURE_PIX_NO_ESMTP) == 0) + session->features |= SMTP_FEATURE_ESMTP; + if (var_smtp_never_ehlo + || (session->features & SMTP_FEATURE_PIX_NO_ESMTP) != 0) + session->features &= ~SMTP_FEATURE_ESMTP; + } else { + session->features |= SMTP_FEATURE_ESMTP; + } + } + + /* + * If recursing after STARTTLS, there is no server greeting banner. + * Always send EHLO as the next command. + */ + else { + session->features |= SMTP_FEATURE_ESMTP; + } + + /* + * Return the compliment. Fall back to SMTP if our ESMTP recognition + * heuristic failed. + */ + if (smtp_mode) { + where = "performing the EHLO handshake"; + if (session->features & SMTP_FEATURE_ESMTP) { + smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name); + if ((resp = smtp_chat_resp(session))->code / 100 != 2) { + if (resp->code == 421) + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + else + session->features &= ~SMTP_FEATURE_ESMTP; + } + } + if ((session->features & SMTP_FEATURE_ESMTP) == 0) { + where = "performing the HELO handshake"; + smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name); + if ((resp = smtp_chat_resp(session))->code / 100 != 2) + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + } + } else { + where = "performing the LHLO handshake"; + smtp_chat_cmd(session, "LHLO %s", var_smtp_helo_name); + if ((resp = smtp_chat_resp(session))->code / 100 != 2) + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + } + + /* + * No early returns allowed, to ensure consistent handling of TLS and + * SASL policies. + */ + if (session->features & SMTP_FEATURE_ESMTP) { + + /* + * Determine what server EHLO keywords to ignore, typically to avoid + * inter-operability problems. + */ + if (smtp_ehlo_dis_maps == 0 + || (ehlo_words = maps_find(smtp_ehlo_dis_maps, + STR(iter->addr), 0)) == 0) + ehlo_words = var_smtp_ehlo_dis_words; + if (smtp_ehlo_dis_maps && smtp_ehlo_dis_maps->error) { + msg_warn("%s: %s map lookup error for %s", + session->state->request->queue_id, + smtp_ehlo_dis_maps->title, STR(iter->addr)); + vstream_longjmp(session->stream, SMTP_ERR_DATA); + } + discard_mask = ehlo_mask(ehlo_words); + if (discard_mask && !(discard_mask & EHLO_MASK_SILENT)) + msg_info("discarding EHLO keywords: %s", + str_ehlo_mask(discard_mask)); + + /* + * Pick up some useful features offered by the SMTP server. XXX Until + * we have a portable routine to convert from string to off_t with + * proper overflow detection, ignore the message size limit + * advertised by the SMTP server. Otherwise, we might do the wrong + * thing when the server advertises a really huge message size limit. + * + * XXX Allow for "code (SP|-) ehlo-keyword (SP|=) ehlo-param...", + * because MicroSoft implemented AUTH based on an old draft. + */ + lines = resp->str; + for (n = 0; (words = mystrtok(&lines, "\n")) != 0; /* see below */ ) { + if (mystrtok(&words, "- ") + && (word = mystrtok(&words, " \t=")) != 0) { + if (n == 0) { + if (session->helo != 0) + myfree(session->helo); + + /* + * XXX: Keep the original case: we don't expect a single + * SMTP server to randomly change the case of its helo + * response. If different capitalization is detected, we + * should assume disjoint TLS caches. + */ + session->helo = mystrdup(word); + if (strcasecmp(word, var_myhostname) == 0 + && (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) != 0) { + msg_warn("host %s replied to HELO/EHLO" + " with my own hostname %s", + session->namaddrport, var_myhostname); + if (session->features & SMTP_FEATURE_BEST_MX) + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.4.6"), + "mail for %s loops back to myself", + request->nexthop)); + else + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.4.6"), + "mail for %s loops back to myself", + request->nexthop)); + } + } else if (strcasecmp(word, "8BITMIME") == 0) { + if ((discard_mask & EHLO_MASK_8BITMIME) == 0) + session->features |= SMTP_FEATURE_8BITMIME; + } else if (strcasecmp(word, "PIPELINING") == 0) { + if ((discard_mask & EHLO_MASK_PIPELINING) == 0) + session->features |= SMTP_FEATURE_PIPELINING; + } else if (strcasecmp(word, "XFORWARD") == 0) { + if ((discard_mask & EHLO_MASK_XFORWARD) == 0) + while ((word = mystrtok(&words, " \t")) != 0) + session->features |= + name_code(xforward_features, + NAME_CODE_FLAG_NONE, word); + } else if (strcasecmp(word, "SIZE") == 0) { + if ((discard_mask & EHLO_MASK_SIZE) == 0) { + session->features |= SMTP_FEATURE_SIZE; + if ((word = mystrtok(&words, " \t")) != 0) { + if (!alldig(word)) + msg_warn("bad EHLO SIZE limit \"%s\" from %s", + word, session->namaddrport); + else + session->size_limit = off_cvt_string(word); + } + } +#ifdef USE_TLS + } else if (strcasecmp(word, "STARTTLS") == 0) { + /* Ignored later if we already sent STARTTLS. */ + if ((discard_mask & EHLO_MASK_STARTTLS) == 0) + session->features |= SMTP_FEATURE_STARTTLS; +#endif +#ifdef USE_SASL_AUTH + } else if (var_smtp_sasl_enable + && strcasecmp(word, "AUTH") == 0) { + if ((discard_mask & EHLO_MASK_AUTH) == 0) + smtp_sasl_helo_auth(session, words); +#endif + } else if (strcasecmp(word, "DSN") == 0) { + if ((discard_mask & EHLO_MASK_DSN) == 0) + session->features |= SMTP_FEATURE_DSN; + } else if (strcasecmp(word, "SMTPUTF8") == 0) { + if ((discard_mask & EHLO_MASK_SMTPUTF8) == 0) + session->features |= SMTP_FEATURE_SMTPUTF8; + } + n++; + } + } + } + if (msg_verbose) + msg_info("server features: 0x%x size %.0f", + session->features, (double) session->size_limit); + + /* + * Decide if this delivery requires SMTPUTF8 server support. + * + * For now, we require that the remote SMTP server supports SMTPUTF8 when + * the sender requested SMTPUTF8 support. + * + * XXX EAI Refine this to: the sender requested SMTPUTF8 support AND the + * delivery request involves at least one UTF-8 envelope address or + * header value. + * + * If the sender requested SMTPUTF8 support but the delivery request + * involves no UTF-8 envelope address or header value, then we could + * still deliver such mail to a non-SMTPUTF8 server, except that we must + * either uxtext-encode ORCPT parameters or not send them. We cannot + * encode the ORCPT in xtext, because legacy SMTP requires that the + * unencoded address consist entirely of printable (graphic and white + * space) characters from the US-ASCII repertoire (RFC 3461 section 4). A + * correct uxtext encoder will produce a result that an xtext decoder + * will pass through unchanged. + * + * XXX Should we try to encode headers with RFC 2047 when delivering to a + * non-SMTPUTF8 server? That could make life easier for mailing lists. + */ +#define DELIVERY_REQUIRES_SMTPUTF8 \ + ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) \ + && (request->smtputf8 & ~SMTPUTF8_FLAG_REQUESTED)) + + /* + * Require that the server supports SMTPUTF8 when delivery requires + * SMTPUTF8. + * + * Fix 20140706: moved this before negotiating TLS, AUTH, and so on. + */ + if ((session->features & SMTP_FEATURE_SMTPUTF8) == 0 + && DELIVERY_REQUIRES_SMTPUTF8) + return (smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.6.7"), + "SMTPUTF8 is required, " + "but was not offered by host %s", + session->namaddr)); + + /* + * Fix 20140706: don't do silly things when the remote server announces + * SMTPUTF8 but not 8BITMIME support. Our primary mission is to deliver + * mail, not to force people into compliance. + */ + if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0 + && (session->features & SMTP_FEATURE_8BITMIME) == 0) { + msg_info("host %s offers SMTPUTF8 support, but not 8BITMIME", + session->namaddr); + session->features |= SMTP_FEATURE_8BITMIME; + } + + /* + * We use SMTP command pipelining if the server said it supported it. + * Since we use blocking I/O, RFC 2197 says that we should inspect the + * TCP window size and not send more than this amount of information. + * Unfortunately this information is unavailable using the sockets + * interface. However, we *can* get the TCP send buffer size on the local + * TCP/IP stack. We should be able to fill this buffer without being + * blocked, and then the kernel will effectively do non-blocking I/O for + * us by automatically writing out the contents of its send buffer while + * we are reading in the responses. In addition to TCP buffering we have + * to be aware of application-level buffering by the vstream module, + * which is limited to a couple kbytes. + * + * XXX No need to do this before and after STARTTLS, but it's not a big deal + * if we do. + * + * XXX When TLS is turned on, the SMTP-level writes will be encapsulated as + * TLS messages. Thus, the TCP-level payload will be larger than the + * SMTP-level payload. This has implications for the PIPELINING engine. + * + * To avoid deadlock, the PIPELINING engine needs to request a TCP send + * buffer size that can hold the unacknowledged commands plus the TLS + * encapsulation overhead. + * + * The PIPELINING engine keeps the unacknowledged command size <= the + * default VSTREAM buffer size (to avoid small-write performance issues + * when the VSTREAM buffer size is at its default size). With a default + * VSTREAM buffer size of 4096 there is no reason to increase the + * unacknowledged command size as the TCP MSS increases. It's safer to + * spread the remote SMTP server's recipient processing load over time, + * than dumping a very large recipient list all at once. + * + * For TLS encapsulation overhead we make a conservative guess: take the + * current protocol overhead of ~40 bytes, double the number for future + * proofing (~80 bytes), then round up the result to the nearest power of + * 2 (128 bytes). Plus, be prepared for worst-case compression that + * expands data by 1 kbyte, so that the worst-case SMTP payload per TLS + * message becomes 15 kbytes. + */ +#define PIPELINING_BUFSIZE VSTREAM_BUFSIZE +#ifdef USE_TLS +#define TLS_WORST_PAYLOAD 16384 +#define TLS_WORST_COMP_OVERHD 1024 +#define TLS_WORST_PROTO_OVERHD 128 +#define TLS_WORST_SMTP_PAYLOAD (TLS_WORST_PAYLOAD - TLS_WORST_COMP_OVERHD) +#define TLS_WORST_TOTAL_OVERHD (TLS_WORST_COMP_OVERHD + TLS_WORST_PROTO_OVERHD) +#endif + + if (session->features & SMTP_FEATURE_PIPELINING) { + SOCKOPT_SIZE optlen; + int tcp_bufsize; + int enc_overhead = 0; + + optlen = sizeof(tcp_bufsize); + if (getsockopt(vstream_fileno(session->stream), SOL_SOCKET, + SO_SNDBUF, (char *) &tcp_bufsize, &optlen) < 0) + msg_fatal("%s: getsockopt: %m", myname); +#ifdef USE_TLS + if (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) + enc_overhead += + (1 + (PIPELINING_BUFSIZE - 1) + / TLS_WORST_SMTP_PAYLOAD) * TLS_WORST_TOTAL_OVERHD; +#endif + if (tcp_bufsize < PIPELINING_BUFSIZE + enc_overhead) { + tcp_bufsize = PIPELINING_BUFSIZE + enc_overhead; + if (setsockopt(vstream_fileno(session->stream), SOL_SOCKET, + SO_SNDBUF, (char *) &tcp_bufsize, optlen) < 0) + msg_fatal("%s: setsockopt: %m", myname); + } + if (msg_verbose) + msg_info("Using %s PIPELINING, TCP send buffer size is %d, " + "PIPELINING buffer size is %d", + smtp_mode ? "ESMTP" : "LMTP", + tcp_bufsize, PIPELINING_BUFSIZE); + } +#ifdef USE_TLS + + /* + * Skip this part if we already sent STARTTLS. + */ + if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + + /* + * Optionally log unused STARTTLS opportunities. + */ + if ((session->features & SMTP_FEATURE_STARTTLS) && + var_smtp_tls_note_starttls_offer && + state->tls->level <= TLS_LEV_NONE) + msg_info("Host offered STARTTLS: [%s]", STR(iter->host)); + + /* + * Decide whether or not to send STARTTLS. + */ + if ((session->features & SMTP_FEATURE_STARTTLS) != 0 + && smtp_tls_ctx != 0 && state->tls->level >= TLS_LEV_MAY) { + + /* + * Prepare for disaster. + */ + smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout, + var_smtp_rec_deadline); + if ((except = vstream_setjmp(state->session->stream)) != 0) + return (smtp_stream_except(state, except, + "receiving the STARTTLS response")); + + /* + * Send STARTTLS. Recurse when the server accepts STARTTLS, after + * resetting the SASL and EHLO features lists. + * + * Reset the SASL mechanism list to avoid spurious warnings. + * + * Use the smtp_sasl_tls_security_options feature to allow SASL + * mechanisms that may not be allowed with plain-text + * connections. + */ + smtp_chat_cmd(session, "STARTTLS"); + if ((resp = smtp_chat_resp(session))->code / 100 == 2) { +#ifdef USE_SASL_AUTH + if (session->features & SMTP_FEATURE_AUTH) + smtp_sasl_cleanup(session); +#endif + session->features = saved_features; + /* XXX Mix-up of per-session and per-request flags. */ + state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; + tls_helo_status = smtp_start_tls(state); + state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS; + return (tls_helo_status); + } + + /* + * Give up if we must use TLS but the server rejects STARTTLS + * although support for it was announced in the EHLO response. + */ + session->features &= ~SMTP_FEATURE_STARTTLS; + if (TLS_REQUIRED(state->tls->level)) + return (smtp_site_fail(state, STR(iter->host), resp, + "TLS is required, but host %s refused to start TLS: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + /* Else try to continue in plain-text mode. */ + } + + /* + * Give up if we must use TLS but can't for various reasons. + * + * 200412 Be sure to provide the default clause at the bottom of this + * block. When TLS is required we must never, ever, end up in + * plain-text mode. + */ + if (TLS_REQUIRED(state->tls->level)) { + if (!(session->features & SMTP_FEATURE_STARTTLS)) { + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.4"), + "TLS is required, but was not offered by host %s", + session->namaddr)); + } else if (smtp_tls_ctx == 0) { + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "TLS is required, but our TLS engine is unavailable")); + } else { + msg_warn("%s: TLS is required but unavailable, don't know why", + myname); + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.0"), + "TLS is required, but unavailable")); + } + } + } +#endif +#ifdef USE_SASL_AUTH + if (var_smtp_sasl_enable && (session->features & SMTP_FEATURE_AUTH)) + return (smtp_sasl_helo_login(state)); +#endif + + return (0); +} + +#ifdef USE_TLS + +/* smtp_start_tls - turn on TLS and recurse into the HELO dialog */ + +static int smtp_start_tls(SMTP_STATE *state) +{ + SMTP_SESSION *session = state->session; + SMTP_ITERATOR *iter = state->iterator; + TLS_CLIENT_START_PROPS start_props; + VSTRING *serverid; + SMTP_RESP fake; + TLS_CLIENT_INIT_PROPS init_props; + VSTREAM *tlsproxy; + VSTRING *port_buf; + + /* + * When the TLS handshake succeeds, we can reuse a connection only if TLS + * remains turned on for the lifetime of that connection. This requires + * that the TLS library state is maintained in some proxy process, for + * example, in tlsproxy(8). We then store the proxy file handle in the + * connection cache, and reuse that file handle. + * + * Otherwise, we must turn off connection caching. We can't turn off TLS in + * one SMTP client process, save the open connection to a cache which is + * shared with all SMTP clients, migrate the connection to another SMTP + * client, and resume TLS there. When the TLS handshake fails, we can't + * reuse the SMTP connection either, because the conversation is in an + * unknown state. + */ + if (state->tls->conn_reuse == 0) + DONT_CACHE_THIS_SESSION; + + /* + * The following assumes sites that use TLS in a perverse configuration: + * multiple hosts per hostname, or even multiple hosts per IP address. + * All this without a shared TLS session cache, and they still want to + * use TLS session caching??? + * + * The TLS session cache records the trust chain verification status of + * cached sessions. Different transports may have different CAfile or + * CApath settings, perhaps to allow authenticated connections to sites + * with private CA certs without trusting said private certs for other + * sites. So we cannot assume that a trust chain valid for one transport + * is valid for another. Therefore the client session id must include + * either the transport name or the values of CAfile and CApath. We use + * the transport name. + * + * XXX: We store only one session per lookup key. Ideally the the key maps + * 1-to-1 to a server TLS session cache. We use the IP address, port and + * ehlo response name to build a lookup key that works for split caches + * (that announce distinct names) behind a load balancer. + * + * XXX: The TLS library will salt the serverid with further details of the + * protocol and cipher requirements including the server ehlo response. + * Deferring the helo to the digested suffix results in more predictable + * SSL session lookup key lengths. + */ + serverid = vstring_alloc(10); + smtp_key_prefix(serverid, "&", state->iterator, SMTP_KEY_FLAG_SERVICE + | SMTP_KEY_FLAG_CUR_NEXTHOP /* With port */ + | SMTP_KEY_FLAG_HOSTNAME + | SMTP_KEY_FLAG_ADDR); + + if (state->tls->conn_reuse) { + TLS_CLIENT_PARAMS tls_params; + + /* + * Send all our wishes in one big request. + */ + TLS_PROXY_CLIENT_INIT_PROPS(&init_props, + log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL), + log_level = var_smtp_tls_loglevel, + verifydepth = var_smtp_tls_scert_vd, + cache_type + = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE), + chain_files = var_smtp_tls_chain_files, + cert_file = var_smtp_tls_cert_file, + key_file = var_smtp_tls_key_file, + dcert_file = var_smtp_tls_dcert_file, + dkey_file = var_smtp_tls_dkey_file, + eccert_file = var_smtp_tls_eccert_file, + eckey_file = var_smtp_tls_eckey_file, + CAfile = var_smtp_tls_CAfile, + CApath = var_smtp_tls_CApath, + mdalg = var_smtp_tls_fpt_dgst); + TLS_PROXY_CLIENT_START_PROPS(&start_props, + timeout = var_smtp_starttls_tmout, + tls_level = state->tls->level, + nexthop = session->tls_nexthop, + host = STR(iter->host), + namaddr = session->namaddrport, + sni = state->tls->sni, + serverid = vstring_str(serverid), + helo = session->helo, + protocols = state->tls->protocols, + cipher_grade = state->tls->grade, + cipher_exclusions + = vstring_str(state->tls->exclusions), + matchargv = state->tls->matchargv, + mdalg = var_smtp_tls_fpt_dgst, + dane = state->tls->dane); + + /* + * The tlsproxy(8) server enforces timeouts that are larger than + * those specified by the tlsproxy(8) client. These timeouts are a + * safety net for the case that the tlsproxy(8) client fails to + * enforce time limits. Normally, the tlsproxy(8) client would time + * out and trigger a plaintext event in the tlsproxy(8) server, and + * cause it to tear down the session. + * + * However, the tlsproxy(8) server has no insight into the SMTP + * protocol, and therefore it cannot by itself support different + * timeouts at different SMTP protocol stages. Instead, we specify + * the largest timeout (end-of-data) and rely on the SMTP client to + * time out first, which normally results in a plaintext event in the + * tlsproxy(8) server. Unfortunately, we cannot permit plaintext + * events during the TLS handshake, so we specify a separate timeout + * for that stage (the end-of-data timeout would be unreasonably + * large anyway). + */ +#define PROXY_OPEN_FLAGS \ + (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT) + + port_buf = vstring_alloc(100); /* minimize fragmentation */ + vstring_sprintf(port_buf, "%d", ntohs(iter->port)); + tlsproxy = + tls_proxy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS, + session->stream, STR(iter->addr), + STR(port_buf), var_smtp_starttls_tmout, + var_smtp_data2_tmout, state->service, + tls_proxy_client_param_from_config(&tls_params), + &init_props, &start_props); + vstring_free(port_buf); + + /* + * To insert tlsproxy(8) between this process and the remote SMTP + * server, we swap the file descriptors between the tlsproxy and + * session->stream VSTREAMS, so that we don't lose all the + * user-configurable session->stream attributes (such as longjump + * buffers or timeouts). + * + * TODO: the tlsproxy RPCs should return more error detail than a "NO" + * result. OTOH, the in-process TLS engine does not return such info + * either. + * + * If the tlsproxy request fails we do not fall back to the in-process + * TLS stack. Reason: the admin enabled connection reuse to respect + * receiver policy; silently violating such policy would not be + * useful. + * + * We also don't fall back to the in-process TLS stack under low-traffic + * conditions, to avoid frustrating attempts to debug a problem with + * using the tlsproxy(8) service. + */ + if (tlsproxy == 0) { + session->tls_context = 0; + } else { + vstream_control(tlsproxy, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_END); + vstream_control(session->stream, + CA_VSTREAM_CTL_SWAP_FD(tlsproxy), + CA_VSTREAM_CTL_END); + (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */ + + /* + * There must not be any pending data in the stream buffers + * before we read the TLS context attributes. + */ + vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); + + /* + * After plumbing the plaintext stream, receive the TLS context + * object. For this we use the same VSTREAM buffer that we also + * use to receive subsequent SMTP commands, therefore we must be + * prepared for the possibility that the remote SMTP server + * starts talking immediately. The tlsproxy implementation sends + * the TLS context before remote content. The attribute protocol + * is robust enough that an adversary cannot insert their own TLS + * context attributes. + */ + session->tls_context = tls_proxy_context_receive(session->stream); + if (session->tls_context) { + session->features |= SMTP_FEATURE_FROM_PROXY; + tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW, + session->tls_context); + } + } + } else { /* state->tls->conn_reuse */ + + /* + * As of Postfix 2.5, tls_client_start() tries hard to always + * complete the TLS handshake. It records the verification and match + * status in the resulting TLScontext. It is now up to the + * application to abort the TLS connection if it chooses. + * + * XXX When tls_client_start() fails then we don't know what state the + * SMTP connection is in, so we give up on this connection even if we + * are not required to use TLS. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + session->tls_context = + TLS_CLIENT_START(&start_props, + ctx = smtp_tls_ctx, + stream = session->stream, + fd = -1, + timeout = var_smtp_starttls_tmout, + tls_level = state->tls->level, + nexthop = session->tls_nexthop, + host = STR(iter->host), + namaddr = session->namaddrport, + sni = state->tls->sni, + serverid = vstring_str(serverid), + helo = session->helo, + protocols = state->tls->protocols, + cipher_grade = state->tls->grade, + cipher_exclusions + = vstring_str(state->tls->exclusions), + matchargv = state->tls->matchargv, + mdalg = var_smtp_tls_fpt_dgst, + dane = state->tls->dane); + + /* + * At this point there must not be any pending data in the stream + * buffers. + */ + vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); + } /* state->tls->conn_reuse */ + + vstring_free(serverid); + + if (session->tls_context == 0) { + + /* + * We must avoid further I/O, the peer is in an undefined state. + */ + DONT_USE_FORBIDDEN_SESSION; + + /* + * If TLS is optional, try delivery to the same server over a + * plaintext connection. Otherwise we would defer mail forever with + * destinations that have no alternate MX host. + * + * Don't fall back to plaintext if we were willing to use SASL-over-TLS + * authentication. If the server doesn't announce SASL support over + * plaintext connections, then we don't want delivery to fail with + * "relay access denied". + * + * If TLS is opportunistic, don't throttle the destination, otherwise if + * the mail is volume is high enough we may have difficulty ever + * draining even the deferred mail, as new mail provides a constant + * stream of negative feedback. + */ + if (PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE) + RETRY_AS_PLAINTEXT; + return (smtp_misc_fail(state, state->tls->level == TLS_LEV_MAY ? + SMTP_NOTHROTTLE : SMTP_THROTTLE, + DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "Cannot start TLS: handshake failure")); + } + + /* + * If we are verifying the server certificate and are not happy with the + * result, abort the delivery here. We have a usable TLS session with the + * server, so no need to disable I/O, ... we can even be polite and send + * "QUIT". + * + * See src/tls/tls_level.c and src/tls/tls.h. Levels above "encrypt" require + * matching. Levels >= "dane" require CA or DNSSEC trust. + * + * When DANE TLSA records specify an end-entity certificate, the trust and + * match bits always coincide, but it is fine to report the wrong + * end-entity certificate as untrusted rather than unmatched. + */ + if (TLS_MUST_TRUST(state->tls->level)) + if (!TLS_CERT_IS_TRUSTED(session->tls_context)) + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "Server certificate not trusted")); + if (TLS_MUST_MATCH(state->tls->level)) + if (!TLS_CERT_IS_MATCHED(session->tls_context)) + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "Server certificate not verified")); + + /* + * At this point we have to re-negotiate the "EHLO" to reget the + * feature-list. + */ + return (smtp_helo(state)); +} + +#endif + +/* smtp_hbc_logger - logging call-back for header/body checks */ + +static void smtp_hbc_logger(void *context, const char *action, + const char *where, const char *content, + const char *text) +{ + const SMTP_STATE *state = (SMTP_STATE *) context; + + if (*text) { + msg_info("%s: %s: %s %.60s: %s", + state->request->queue_id, action, where, content, text); + } else { + msg_info("%s: %s: %s %.60s", + state->request->queue_id, action, where, content); + } +} + +/* smtp_text_out - output one header/body record */ + +static void smtp_text_out(void *context, int rec_type, + const char *text, ssize_t len, + off_t unused_offset) +{ + SMTP_STATE *state = (SMTP_STATE *) context; + SMTP_SESSION *session = state->session; + ssize_t data_left; + const char *data_start; + + /* + * Deal with an impedance mismatch between Postfix queue files (record + * length <= $message_line_length_limit) and SMTP (DATA record length <= + * $smtp_line_length_limit). The code below does a little too much work + * when the SMTP line length limit is disabled, but it avoids code + * duplication, and thus, it avoids testing and maintenance problems. + */ + data_left = len; + data_start = text; + do { + if (state->space_left == var_smtp_line_limit + && data_left > 0 && *data_start == '.') + smtp_fputc('.', session->stream); + if (var_smtp_line_limit > 0 && data_left >= state->space_left) { + smtp_fputs(data_start, state->space_left, session->stream); + data_start += state->space_left; + data_left -= state->space_left; + state->space_left = var_smtp_line_limit; + if (data_left > 0 || rec_type == REC_TYPE_CONT) { + smtp_fputc(' ', session->stream); + state->space_left -= 1; + } + } else { + if (rec_type == REC_TYPE_CONT) { + smtp_fwrite(data_start, data_left, session->stream); + state->space_left -= data_left; + } else { + smtp_fputs(data_start, data_left, session->stream); + state->space_left = var_smtp_line_limit; + } + break; + } + } while (data_left > 0); +} + +/* smtp_format_out - output one header/body record */ + +static void PRINTFLIKE(3, 4) smtp_format_out(void *, int, const char *,...); + +static void smtp_format_out(void *context, int rec_type, const char *fmt,...) +{ + static VSTRING *vp; + va_list ap; + + if (vp == 0) + vp = vstring_alloc(100); + va_start(ap, fmt); + vstring_vsprintf(vp, fmt, ap); + va_end(ap); + smtp_text_out(context, rec_type, vstring_str(vp), VSTRING_LEN(vp), 0); +} + +/* smtp_header_out - output one message header */ + +static void smtp_header_out(void *context, int unused_header_class, + const HEADER_OPTS *unused_info, + VSTRING *buf, off_t offset) +{ + char *start = vstring_str(buf); + char *line; + char *next_line; + + /* + * This code destroys the header. We could try to avoid clobbering it, + * but we're not going to use the data any further. + */ + for (line = start; line; line = next_line) { + next_line = split_at(line, '\n'); + smtp_text_out(context, REC_TYPE_NORM, line, next_line ? + next_line - line - 1 : strlen(line), offset); + } +} + +/* smtp_header_rewrite - rewrite message header before output */ + +static void smtp_header_rewrite(void *context, int header_class, + const HEADER_OPTS *header_info, + VSTRING *buf, off_t offset) +{ + SMTP_STATE *state = (SMTP_STATE *) context; + int did_rewrite = 0; + char *line; + char *start; + char *next_line; + char *end_line; + char *result; + + /* + * Apply optional header filtering. + */ + if (smtp_header_checks) { + result = hbc_header_checks(context, smtp_header_checks, header_class, + header_info, buf, offset); + if (result == 0) + return; + if (result == HBC_CHECKS_STAT_ERROR) { + msg_warn("%s: smtp header checks lookup error", + state->request->queue_id); + vstream_longjmp(state->session->stream, SMTP_ERR_DATA); + } + if (result != STR(buf)) { + vstring_strcpy(buf, result); + myfree(result); + } + } + + /* + * Rewrite primary header addresses that match the smtp_generic_maps. The + * cleanup server already enforces that all headers have proper lengths + * and that all addresses are in proper form, so we don't have to repeat + * that. + */ + if (smtp_generic_maps && header_info && header_class == MIME_HDR_PRIMARY + && (header_info->flags & (HDR_OPT_SENDER | HDR_OPT_RECIP)) != 0) { + TOK822 *tree; + TOK822 **addr_list; + TOK822 **tpp; + + tree = tok822_parse(vstring_str(buf) + + strlen(header_info->name) + 1); + addr_list = tok822_grep(tree, TOK822_ADDR); + for (tpp = addr_list; *tpp; tpp++) + did_rewrite |= smtp_map11_tree(tpp[0], smtp_generic_maps, + smtp_ext_prop_mask & EXT_PROP_GENERIC); + if (did_rewrite) { + vstring_truncate(buf, strlen(header_info->name)); + vstring_strcat(buf, ": "); + tok822_externalize(buf, tree, TOK822_STR_HEAD); + } + myfree((void *) addr_list); + tok822_free_tree(tree); + } + + /* + * Pass through unmodified headers without reconstruction. + */ + if (did_rewrite == 0) { + smtp_header_out(context, header_class, header_info, buf, offset); + return; + } + + /* + * A rewritten address list contains one address per line. The code below + * replaces newlines by spaces, to fit as many addresses on a line as + * possible (without rearranging the order of addresses). Prepending + * white space to the beginning of lines is delegated to the output + * routine. + * + * Code derived from cleanup_fold_header(). + */ + for (line = start = vstring_str(buf); line != 0; line = next_line) { + end_line = line + strcspn(line, "\n"); + if (line > start) { + if (end_line - start < 70) { /* TAB counts as one */ + line[-1] = ' '; + } else { + start = line; + } + } + next_line = *end_line ? end_line + 1 : 0; + } + + /* + * Prepend a tab to continued header lines that went through the address + * rewriting machinery. Just like smtp_header_out(), this code destroys + * the header. We could try to avoid clobbering it, but we're not going + * to use the data any further. + * + * Code derived from cleanup_out_header(). + */ + for (line = start = vstring_str(buf); line != 0; line = next_line) { + next_line = split_at(line, '\n'); + if (line == start || IS_SPACE_TAB(*line)) { + smtp_text_out(state, REC_TYPE_NORM, line, next_line ? + next_line - line - 1 : strlen(line), offset); + } else { + smtp_format_out(state, REC_TYPE_NORM, "\t%s", line); + } + } +} + +/* smtp_body_rewrite - rewrite message body before output */ + +static void smtp_body_rewrite(void *context, int type, + const char *buf, ssize_t len, + off_t offset) +{ + SMTP_STATE *state = (SMTP_STATE *) context; + char *result; + + /* + * Apply optional body filtering. + */ + if (smtp_body_checks) { + result = hbc_body_checks(context, smtp_body_checks, buf, len, offset); + if (result == buf) { + smtp_text_out(state, type, buf, len, offset); + } else if (result == HBC_CHECKS_STAT_ERROR) { + msg_warn("%s: smtp body checks lookup error", + state->request->queue_id); + vstream_longjmp(state->session->stream, SMTP_ERR_DATA); + } else if (result != 0) { + smtp_text_out(state, type, result, strlen(result), offset); + myfree(result); + } + } +} + +/* smtp_mime_fail - MIME problem */ + +static void smtp_mime_fail(SMTP_STATE *state, int mime_errs) +{ + const MIME_STATE_DETAIL *detail; + SMTP_RESP fake; + + detail = mime_state_detail(mime_errs); + smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, detail->dsn), + "%s", detail->text); +} + +/* smtp_loop - exercise the SMTP protocol engine */ + +static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state, + NOCLOBBER int recv_state) +{ + const char *myname = "smtp_loop"; + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + SMTP_ITERATOR *iter = state->iterator; + SMTP_RESP *resp; + RECIPIENT *rcpt; + VSTRING *next_command = vstring_alloc(100); + int *NOCLOBBER survivors = 0; + NOCLOBBER int next_state; + NOCLOBBER int next_rcpt; + NOCLOBBER int send_rcpt; + NOCLOBBER int recv_rcpt; + NOCLOBBER int nrcpt; + NOCLOBBER int recv_done; + int except; + int rec_type; + NOCLOBBER int prev_type = 0; + NOCLOBBER int mail_from_rejected; + NOCLOBBER int downgrading; + int mime_errs; + SMTP_RESP fake; + int fail_status; + + /* + * Macros for readability. + */ +#define REWRITE_ADDRESS(dst, src) do { \ + vstring_strcpy(dst, src); \ + if (*(src) && smtp_generic_maps) \ + smtp_map11_internal(dst, smtp_generic_maps, \ + smtp_ext_prop_mask & EXT_PROP_GENERIC); \ + } while (0) + +#define QUOTE_ADDRESS(dst, src) do { \ + if (*(src) && var_smtp_quote_821_env) { \ + quote_821_local(dst, src); \ + } else { \ + vstring_strcpy(dst, src); \ + } \ + } while (0) + + /* Caution: changes to RETURN() also affect code outside the main loop. */ + +#define RETURN(x) do { \ + if (recv_state != SMTP_STATE_LAST) \ + DONT_CACHE_THIS_SESSION; \ + vstring_free(next_command); \ + if (survivors) \ + myfree((void *) survivors); \ + if (session->mime_state) \ + session->mime_state = mime_state_free(session->mime_state); \ + return (x); \ + } while (0) + +#define SENDER_IS_AHEAD \ + (recv_state < send_state || recv_rcpt != send_rcpt) + +#define SENDER_IN_WAIT_STATE \ + (send_state == SMTP_STATE_DOT || send_state == SMTP_STATE_LAST) + +#define SENDING_MAIL \ + (recv_state <= SMTP_STATE_DOT) + +#define CANT_RSET_THIS_SESSION \ + (session->features |= SMTP_FEATURE_RSET_REJECTED) + + /* + * Pipelining support requires two loops: one loop for sending and one + * for receiving. Each loop has its own independent state. Most of the + * time the sender can run ahead of the receiver by as much as the TCP + * send buffer permits. There are only two places where the sender must + * wait for status information from the receiver: once after sending DATA + * and once after sending QUIT. + * + * The sender state advances until the TCP send buffer would overflow, or + * until the sender needs status information from the receiver. At that + * point the receiver starts processing responses. Once the receiver has + * caught up with the sender, the sender resumes sending commands. If the + * receiver detects a serious problem (MAIL FROM rejected, all RCPT TO + * commands rejected, DATA rejected) it forces the sender to abort the + * SMTP dialog with RSET and QUIT. + */ + nrcpt = 0; + next_rcpt = send_rcpt = recv_rcpt = recv_done = 0; + mail_from_rejected = 0; + + /* + * Prepare for disaster. This should not be needed because the design + * guarantees that no output is flushed before smtp_chat_resp() is + * called. + * + * 1) Every SMTP command fits entirely in a VSTREAM output buffer. + * + * 2) smtp_loop() never invokes smtp_chat_cmd() without making sure that + * there is sufficient space for the command in the output buffer. + * + * 3) smtp_loop() flushes the output buffer to avoid server timeouts. + * + * Changing any of these would violate the design, and would likely break + * SMTP pipelining. + * + * We set up the error handler anyway (only upon entry to avoid wasting + * resources) because 1) there is code below that expects that VSTREAM + * timeouts are enabled, and 2) this allows us to detect if someone broke + * Postfix by introducing spurious flush before read operations. + */ + if (send_state < SMTP_STATE_XFORWARD_NAME_ADDR + || send_state > SMTP_STATE_QUIT) + msg_panic("%s: bad sender state %d (receiver state %d)", + myname, send_state, recv_state); + smtp_stream_setup(session->stream, *xfer_timeouts[send_state], + var_smtp_rec_deadline); + if ((except = vstream_setjmp(session->stream)) != 0) { + msg_warn("smtp_proto: spurious flush before read in send state %d", + send_state); + RETURN(SENDING_MAIL ? smtp_stream_except(state, except, + xfer_states[send_state]) : -1); + } + + /* + * The main protocol loop. + */ + do { + + /* + * Build the next command. + */ + switch (send_state) { + + /* + * Sanity check. + */ + default: + msg_panic("%s: bad sender state %d", myname, send_state); + + /* + * Build the XFORWARD command. With properly sanitized + * information, the command length stays within the 512 byte + * command line length limit. + * + * XXX smtpd_xforward_preset() initializes some fields as "unknown" + * and some as null; historically, pickup(8) does not send any of + * these, and the queue manager presets absent fields to "not + * available" except for the rewrite context which is preset to + * local by way of migration aid. These definitions need to be + * centralized for maintainability. + */ +#ifndef CAN_FORWARD_CLIENT_NAME +#define _ATTR_AVAIL_AND_KNOWN_(val) \ + (DEL_REQ_ATTR_AVAIL(val) && strcasecmp((val), "unknown")) +#define CAN_FORWARD_CLIENT_NAME _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_CLIENT_ADDR _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_CLIENT_PORT _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_PROTO_NAME _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_HELO_NAME DEL_REQ_ATTR_AVAIL +#define CAN_FORWARD_IDENT_NAME DEL_REQ_ATTR_AVAIL +#define CAN_FORWARD_RWR_CONTEXT DEL_REQ_ATTR_AVAIL +#endif + + case SMTP_STATE_XFORWARD_NAME_ADDR: + vstring_strcpy(next_command, XFORWARD_CMD); + if ((session->features & SMTP_FEATURE_XFORWARD_NAME) + && CAN_FORWARD_CLIENT_NAME(request->client_name)) { + vstring_strcat(next_command, " " XFORWARD_NAME "="); + xtext_quote_append(next_command, request->client_name, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_ADDR) + && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) { + vstring_strcat(next_command, " " XFORWARD_ADDR "="); + xtext_quote_append(next_command, request->client_addr, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_PORT) + && CAN_FORWARD_CLIENT_PORT(request->client_port)) { + vstring_strcat(next_command, " " XFORWARD_PORT "="); + xtext_quote_append(next_command, request->client_port, ""); + } + if (session->send_proto_helo) + next_state = SMTP_STATE_XFORWARD_PROTO_HELO; + else + next_state = SMTP_STATE_MAIL; + break; + + case SMTP_STATE_XFORWARD_PROTO_HELO: + vstring_strcpy(next_command, XFORWARD_CMD); + if ((session->features & SMTP_FEATURE_XFORWARD_PROTO) + && CAN_FORWARD_PROTO_NAME(request->client_proto)) { + vstring_strcat(next_command, " " XFORWARD_PROTO "="); + xtext_quote_append(next_command, request->client_proto, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_HELO) + && CAN_FORWARD_HELO_NAME(request->client_helo)) { + vstring_strcat(next_command, " " XFORWARD_HELO "="); + xtext_quote_append(next_command, request->client_helo, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_IDENT) + && CAN_FORWARD_IDENT_NAME(request->log_ident)) { + vstring_strcat(next_command, " " XFORWARD_IDENT "="); + xtext_quote_append(next_command, request->log_ident, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN) + && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)) { + vstring_strcat(next_command, " " XFORWARD_DOMAIN "="); + xtext_quote_append(next_command, + strcmp(request->rewrite_context, MAIL_ATTR_RWR_LOCAL) ? + XFORWARD_DOM_REMOTE : XFORWARD_DOM_LOCAL, ""); + } + next_state = SMTP_STATE_MAIL; + break; + + /* + * Build the MAIL FROM command. + */ + case SMTP_STATE_MAIL: + request->msg_stats.reuse_count = session->reuse_count; + GETTIMEOFDAY(&request->msg_stats.conn_setup_done); + REWRITE_ADDRESS(session->scratch2, request->sender); + QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2)); + vstring_sprintf(next_command, "MAIL FROM:<%s>", + vstring_str(session->scratch)); + /* XXX Don't announce SIZE if we're going to MIME downgrade. */ + if (session->features & SMTP_FEATURE_SIZE /* RFC 1870 */ + && !SMTP_MIME_DOWNGRADE(session, request)) + vstring_sprintf_append(next_command, " SIZE=%lu", + request->data_size); + if (session->features & SMTP_FEATURE_8BITMIME) { /* RFC 1652 */ + if (strcmp(request->encoding, MAIL_ATTR_ENC_8BIT) == 0) + vstring_strcat(next_command, " BODY=8BITMIME"); + else if (strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) == 0) + vstring_strcat(next_command, " BODY=7BIT"); + else if (strcmp(request->encoding, MAIL_ATTR_ENC_NONE) != 0) + msg_warn("%s: unknown content encoding: %s", + request->queue_id, request->encoding); + } + if (session->features & SMTP_FEATURE_DSN) { + if (request->dsn_envid[0]) { + vstring_sprintf_append(next_command, " ENVID="); + xtext_quote_append(next_command, request->dsn_envid, "+="); + } + if (request->dsn_ret) + vstring_sprintf_append(next_command, " RET=%s", + dsn_ret_str(request->dsn_ret)); + } + + /* + * Request SMTPUTF8 when the remote SMTP server supports SMTPUTF8 + * and the sender requested SMTPUTF8 support. + * + * If the sender requested SMTPUTF8 but the remote SMTP server does + * not support SMTPUTF8, then we have already determined earlier + * that delivering this message without SMTPUTF8 will not break + * the SMTPUTF8 promise that was made to the sender. + */ + if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0 + && (request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) != 0) + vstring_strcat(next_command, " SMTPUTF8"); + + /* + * We authenticate the local MTA only, but not the sender. + */ +#ifdef USE_SASL_AUTH + if (var_smtp_sasl_enable + && var_smtp_dummy_mail_auth + && (session->features & SMTP_FEATURE_AUTH)) + vstring_strcat(next_command, " AUTH=<>"); +#endif + + /* + * CVE-2009-3555 (TLS renegotiation). Try to detect a mail + * hijacking attack that prepends malicious EHLO/MAIL/RCPT/DATA + * commands to our TLS session. + * + * For the attack to succeed, the remote SMTP server must reply to + * the malicious EHLO/MAIL/RCPT/DATA commands after completing + * TLS (re)negotiation, so that the replies arrive in our TLS + * session (otherwise the Postfix SMTP client would time out + * waiting for an answer). With some luck we can detect this + * specific attack as a server MAIL reply that arrives before we + * send our own MAIL command. + * + * We don't apply this test to the HELO command because the result + * would be very timing sensitive, and we don't apply this test + * to RCPT and DATA replies because these may be pipelined for + * legitimate reasons. + */ +#ifdef USE_TLS + if (var_smtp_tls_blk_early_mail_reply + && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) != 0 + && (vstream_peek(session->stream) > 0 + || peekfd(vstream_fileno(session->stream)) > 0)) + session->features |= SMTP_FEATURE_EARLY_TLS_MAIL_REPLY; +#endif + + /* + * We now return to our regular broadcast. + */ + next_state = SMTP_STATE_RCPT; + break; + + /* + * Build one RCPT TO command before we have seen the MAIL FROM + * response. + */ + case SMTP_STATE_RCPT: + rcpt = request->rcpt_list.info + send_rcpt; + REWRITE_ADDRESS(session->scratch2, rcpt->address); + QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2)); + vstring_sprintf(next_command, "RCPT TO:<%s>", + vstring_str(session->scratch)); + if (session->features & SMTP_FEATURE_DSN) { + /* XXX DSN xtext encode address value not type. */ + const char *orcpt_type_addr = rcpt->dsn_orcpt; + + /* Fix 20140706: don't use empty rcpt->orig_addr. */ + if (orcpt_type_addr[0] == 0 && rcpt->orig_addr[0] != 0) { + quote_822_local(session->scratch, rcpt->orig_addr); + vstring_sprintf(session->scratch2, "%s;%s", + /* Fix 20140707: sender must request SMTPUTF8. */ + (request->smtputf8 != 0 + && !allascii(vstring_str(session->scratch))) ? + "utf-8" : "rfc822", + vstring_str(session->scratch)); + orcpt_type_addr = vstring_str(session->scratch2); + } + if (orcpt_type_addr[0] != 0) { + /* Fix 20140706: don't send unquoted ORCPT. */ + /* Fix 20140707: quoting method must match orcpt type. */ + /* Fix 20140707: handle uxtext encoder errors. */ + if (strncasecmp(orcpt_type_addr, "utf-8;", 6) == 0) { + if (uxtext_quote(session->scratch, + orcpt_type_addr, "+=") != 0) + vstring_sprintf_append(next_command, " ORCPT=%s", + vstring_str(session->scratch)); + } else { + xtext_quote(session->scratch, orcpt_type_addr, "="); + vstring_sprintf_append(next_command, " ORCPT=%s", + vstring_str(session->scratch)); + } + } + if (rcpt->dsn_notify) + vstring_sprintf_append(next_command, " NOTIFY=%s", + dsn_notify_str(rcpt->dsn_notify)); + } + if ((next_rcpt = send_rcpt + 1) == SMTP_RCPT_LEFT(state)) + next_state = (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_RCPT) ? + SMTP_STATE_ABORT : SMTP_STATE_DATA; + break; + + /* + * Build the DATA command before we have seen all the RCPT TO + * responses. + */ + case SMTP_STATE_DATA: + vstring_strcpy(next_command, "DATA"); + next_state = SMTP_STATE_DOT; + break; + + /* + * Build the "." command after we have seen the DATA response + * (DATA is a protocol synchronization point). + * + * Changing the connection caching state here is safe because it + * affects none of the not-yet processed replies to + * already-generated commands. + */ + case SMTP_STATE_DOT: + vstring_strcpy(next_command, "."); + if (THIS_SESSION_IS_EXPIRED) + DONT_CACHE_THIS_SESSION; + next_state = THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT; + break; + + /* + * The SMTP_STATE_ABORT sender state is entered by the sender + * when it has verified all recipients; or it is entered by the + * receiver when all recipients are verified or rejected, and is + * then left before the bottom of the main loop. + * + * Changing the connection caching state here is safe because there + * are no not-yet processed replies to already-generated + * commands. + */ + case SMTP_STATE_ABORT: + vstring_strcpy(next_command, "RSET"); + if (THIS_SESSION_IS_EXPIRED) + DONT_CACHE_THIS_SESSION; + next_state = THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT; + break; + + /* + * Build the RSET command. This is entered as initial state from + * smtp_rset() and has its own dedicated state transitions. It is + * used to find out the status of a cached session before + * attempting mail delivery. + */ + case SMTP_STATE_RSET: + vstring_strcpy(next_command, "RSET"); + next_state = SMTP_STATE_LAST; + break; + + /* + * Build the QUIT command before we have seen the "." or RSET + * response. This is entered as initial state from smtp_quit(), + * or is reached near the end of any non-cached session. + * + * Changing the connection caching state here is safe. If this + * command is pipelined together with a preceding command, then + * connection caching was already turned off. Do not clobber the + * "bad connection" flag. + */ + case SMTP_STATE_QUIT: + vstring_strcpy(next_command, "QUIT"); + next_state = SMTP_STATE_LAST; + if (THIS_SESSION_IS_CACHED) + DONT_CACHE_THIS_SESSION; + break; + + /* + * The final sender state has no action associated with it. + */ + case SMTP_STATE_LAST: + VSTRING_RESET(next_command); + break; + } + VSTRING_TERMINATE(next_command); + + /* + * Process responses until the receiver has caught up. Vstreams + * automatically flush buffered output when reading new data. + * + * Flush unsent output if command pipelining is off or if no I/O + * happened for a while. This limits the accumulation of client-side + * delays in pipelined sessions. + * + * The PIPELINING engine will flush the VSTREAM buffer if the sender + * could otherwise produce more output than fits the PIPELINING + * buffer. This generally works because we know exactly how much + * output we produced since the last time that the sender and + * receiver synchronized the SMTP state. However this logic is not + * applicable after the sender enters the DATA phase, where it does + * not synchronize with the receiver until the <CR><LF>.<CR><LF>. + * Thus, the PIPELINING engine no longer knows how much data is + * pending in the TCP send buffer. For this reason, if PIPELINING is + * enabled, we always pipeline QUIT after <CR><LF>.<CR><LF>. This is + * safe because once the receiver reads <CR><LF>.<CR><LF>, its TCP + * stack either has already received the QUIT<CR><LF>, or else it + * acknowledges all bytes up to and including <CR><LF>.<CR><LF>, + * making room in the sender's TCP stack for QUIT<CR><LF>. + */ +#define CHECK_PIPELINING_BUFSIZE \ + (recv_state != SMTP_STATE_DOT || send_state != SMTP_STATE_QUIT) + + if (SENDER_IN_WAIT_STATE + || (SENDER_IS_AHEAD + && ((session->features & SMTP_FEATURE_PIPELINING) == 0 + || (CHECK_PIPELINING_BUFSIZE + && (VSTRING_LEN(next_command) + 2 + + vstream_bufstat(session->stream, VSTREAM_BST_OUT_PEND) + > PIPELINING_BUFSIZE)) + || time((time_t *) 0) + - vstream_ftime(session->stream) > 10))) { + while (SENDER_IS_AHEAD) { + + /* + * Sanity check. + */ + if (recv_state < SMTP_STATE_XFORWARD_NAME_ADDR + || recv_state > SMTP_STATE_QUIT) + msg_panic("%s: bad receiver state %d (sender state %d)", + myname, recv_state, send_state); + + /* + * Receive the next server response. Use the proper timeout, + * and log the proper client state in case of trouble. + * + * XXX If we lose the connection before sending end-of-data, + * find out if the server sent a premature end-of-data reply. + * If this read attempt fails, report "lost connection while + * sending message body", not "lost connection while sending + * end-of-data". + * + * "except" becomes zero just above the protocol loop, and stays + * zero or triggers an early return from the loop. In just + * one case: loss of the connection when sending the message + * body, we record the exception, and keep processing in the + * hope of detecting a premature 5XX. We must be careful to + * not clobber this non-zero value once it is set. The + * variable need not survive longjmp() calls, since the only + * setjmp() which does not return early is the one sets this + * condition, subquent failures always return early. + */ +#define LOST_CONNECTION_INSIDE_DATA (except == SMTP_ERR_EOF) + + smtp_stream_setup(session->stream, *xfer_timeouts[recv_state], + var_smtp_rec_deadline); + if (LOST_CONNECTION_INSIDE_DATA) { + if (vstream_setjmp(session->stream) != 0) + RETURN(smtp_stream_except(state, SMTP_ERR_EOF, + "sending message body")); + } else { + if ((except = vstream_setjmp(session->stream)) != 0) + RETURN(SENDING_MAIL ? smtp_stream_except(state, except, + xfer_states[recv_state]) : -1); + } + resp = smtp_chat_resp(session); + + /* + * Process the response. + */ + switch (recv_state) { + + /* + * Process the XFORWARD response. + */ + case SMTP_STATE_XFORWARD_NAME_ADDR: + if (resp->code / 100 != 2) + msg_warn("host %s said: %s (in reply to %s)", + session->namaddrport, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_XFORWARD_NAME_ADDR]); + if (session->send_proto_helo) + recv_state = SMTP_STATE_XFORWARD_PROTO_HELO; + else + recv_state = SMTP_STATE_MAIL; + break; + + case SMTP_STATE_XFORWARD_PROTO_HELO: + if (resp->code / 100 != 2) + msg_warn("host %s said: %s (in reply to %s)", + session->namaddrport, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_XFORWARD_PROTO_HELO]); + recv_state = SMTP_STATE_MAIL; + break; + + /* + * Process the MAIL FROM response. When the server + * rejects the sender, set the mail_from_rejected flag so + * that the receiver may apply a course correction. + */ + case SMTP_STATE_MAIL: + if (resp->code / 100 != 2) { + smtp_mesg_fail(state, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_MAIL]); + mail_from_rejected = 1; + } + + /* + * CVE-2009-3555 (TLS renegotiation). Whatever it was + * that arrived before we sent our MAIL FROM command, it + * was not a fatal-level TLS alert message. It could be a + * warning-level TLS alert message, or a ChangeCipherSpec + * message, but such messages are not normally sent in + * the middle of a TLS session. We disconnect and try + * again later. + */ +#ifdef USE_TLS + if (var_smtp_tls_blk_early_mail_reply + && (session->features & SMTP_FEATURE_EARLY_TLS_MAIL_REPLY)) { + smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.0"), + "unexpected server message"); + msg_warn("server %s violates %s policy", + session->namaddr, + VAR_LMTP_SMTP(TLS_BLK_EARLY_MAIL_REPLY)); + mail_from_rejected = 1; + } +#endif + + /* + * We now return to our regular broadcast. + */ + recv_state = SMTP_STATE_RCPT; + break; + + /* + * Process one RCPT TO response. If MAIL FROM was + * rejected, ignore RCPT TO responses: all recipients are + * dead already. When all recipients are rejected the + * receiver may apply a course correction. + * + * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply + * must be treated as if the server replied with 452. + * However, this causes "too much mail data" to be + * treated as a recoverable error, which is wrong. I'll + * stick with RFC 821. + */ + case SMTP_STATE_RCPT: + if (!mail_from_rejected) { +#ifdef notdef + if (resp->code == 552) { + resp->code = 452; + resp->dsn[0] = '4'; + } +#endif + rcpt = request->rcpt_list.info + recv_rcpt; + if (resp->code / 100 == 2) { + if (!smtp_mode) { + if (survivors == 0) + survivors = (int *) + mymalloc(request->rcpt_list.len + * sizeof(int)); + survivors[nrcpt] = recv_rcpt; + } + ++nrcpt; + /* If trace-only, mark the recipient done. */ + if (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_RCPT) { + translit(resp->str, "\n", " "); + smtp_rcpt_done(state, resp, rcpt); + } + } else { + smtp_rcpt_fail(state, rcpt, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_RCPT]); + } + } + /* If trace-only, send RSET instead of DATA. */ + if (++recv_rcpt == SMTP_RCPT_LEFT(state)) + recv_state = (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_RCPT) ? + SMTP_STATE_ABORT : SMTP_STATE_DATA; + /* XXX Also: record if non-delivering session. */ + break; + + /* + * Process the DATA response. When the server rejects + * DATA, set nrcpt to a negative value so that the + * receiver can apply a course correction. + */ + case SMTP_STATE_DATA: + recv_state = SMTP_STATE_DOT; + if (resp->code / 100 != 3) { + if (nrcpt > 0) + smtp_mesg_fail(state, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_DATA]); + nrcpt = -1; + } + + /* + * In the case of a successful address probe with target + * equal to DATA, the remote server is now in the DATA + * state, and therefore we must not make any further + * attempt to send or receive on this connection. This + * means that we cannot not reuse the general-purpose + * course-correction logic below which sends RSET (and + * perhaps QUIT). Instead we "jump" straight to the exit + * and force an unceremonious disconnect. + */ + else if (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_DATA) { + for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (!SMTP_RCPT_ISMARKED(rcpt)) { + translit(resp->str, "\n", " "); + SMTP_RESP_SET_DSN(resp, "2.0.0"); + smtp_rcpt_done(state, resp, rcpt); + } + } + DONT_CACHE_THIS_SESSION; + send_state = recv_state = SMTP_STATE_LAST; + } + break; + + /* + * Process the end of message response. Ignore the + * response when no recipient was accepted: all + * recipients are dead already, and the next receiver + * state is SMTP_STATE_LAST/QUIT regardless. Otherwise, + * if the message transfer fails, bounce all remaining + * recipients, else cross off the recipients that were + * delivered. + */ + case SMTP_STATE_DOT: + GETTIMEOFDAY(&request->msg_stats.deliver_done); + if (smtp_mode) { + if (nrcpt > 0) { + if (resp->code / 100 != 2) { + smtp_mesg_fail(state, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_DOT]); + } else { + for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (!SMTP_RCPT_ISMARKED(rcpt)) { + translit(resp->str, "\n", " "); + smtp_rcpt_done(state, resp, rcpt); + } + } + } + } + } + + /* + * With LMTP we have one response per accepted RCPT TO + * command. Stay in the SMTP_STATE_DOT state until we + * have collected all responses. + */ + else { + if (nrcpt > 0) { + rcpt = request->rcpt_list.info + + survivors[recv_done++]; + if (resp->code / 100 != 2) { + smtp_rcpt_fail(state, rcpt, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_DOT]); + } else { + translit(resp->str, "\n", " "); + smtp_rcpt_done(state, resp, rcpt); + } + } + if (msg_verbose) + msg_info("%s: got %d of %d end-of-data replies", + myname, recv_done, nrcpt); + if (recv_done < nrcpt) + break; + } + + /* + * XXX Do not change the connection caching state here, + * even if the connection caching timer expired between + * generating the command and processing the reply, + * otherwise the sender and receiver loops get out of + * sync. The caller will call smtp_quit() if appropriate. + */ + if (var_skip_quit_resp || THIS_SESSION_IS_CACHED + || LOST_CONNECTION_INSIDE_DATA) + recv_state = SMTP_STATE_LAST; + else + recv_state = SMTP_STATE_QUIT; + break; + + /* + * Receive the RSET response. + * + * The SMTP_STATE_ABORT sender state is entered by the + * sender when it has verified all recipients; or it is + * entered by the receiver when all recipients are + * verified or rejected, and is then left before the + * bottom of the main loop. + * + * XXX Do not change the connection caching state here, even + * if the server rejected RSET or if the connection + * caching timer expired between generating the command + * and processing the reply, otherwise the sender and + * receiver loops get out of sync. The caller will call + * smtp_quit() if appropriate. + */ + case SMTP_STATE_ABORT: + recv_state = (var_skip_quit_resp || THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT); + break; + + /* + * This is the initial receiver state from smtp_rset(). + * It is used to find out the status of a cached session + * before attempting mail delivery. + */ + case SMTP_STATE_RSET: + if (resp->code / 100 != 2) + CANT_RSET_THIS_SESSION; + recv_state = SMTP_STATE_LAST; + break; + + /* + * Receive, but otherwise ignore, the QUIT response. + */ + case SMTP_STATE_QUIT: + recv_state = SMTP_STATE_LAST; + break; + } + } + + /* + * At this point, the sender and receiver are fully synchronized. + */ + + /* + * We know the server response to every command that was sent. + * Apply a course correction if necessary: the sender wants to + * send RCPT TO but MAIL FROM was rejected; the sender wants to + * send DATA but all recipients were rejected; the sender wants + * to deliver the message but DATA was rejected. + */ + if ((send_state == SMTP_STATE_RCPT && mail_from_rejected) + || (send_state == SMTP_STATE_DATA && nrcpt == 0) + || (send_state == SMTP_STATE_DOT && nrcpt < 0)) { + send_state = recv_state = SMTP_STATE_ABORT; + send_rcpt = recv_rcpt = 0; + vstring_strcpy(next_command, "RSET"); + if (THIS_SESSION_IS_EXPIRED) + DONT_CACHE_THIS_SESSION; + next_state = THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT; + /* XXX Also: record if non-delivering session. */ + next_rcpt = 0; + } + } + + /* + * Make the next sender state the current sender state. + */ + if (send_state == SMTP_STATE_LAST) + continue; + + /* + * Special case if the server accepted the DATA command. If the + * server accepted at least one recipient send the entire message. + * Otherwise, just send "." as per RFC 2197. + * + * XXX If there is a hard MIME error while downgrading to 7-bit mail, + * disconnect ungracefully, because there is no other way to cancel a + * transaction in progress. + */ + if (send_state == SMTP_STATE_DOT && nrcpt > 0) { + + smtp_stream_setup(session->stream, var_smtp_data1_tmout, + var_smtp_rec_deadline); + + if ((except = vstream_setjmp(session->stream)) == 0) { + + if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0) + msg_fatal("seek queue file: %m"); + + downgrading = SMTP_MIME_DOWNGRADE(session, request); + + /* + * XXX Don't downgrade just because generic_maps is turned + * on. + */ +#define SMTP_ANY_CHECKS (smtp_header_checks || smtp_body_checks) + + if (downgrading || smtp_generic_maps || SMTP_ANY_CHECKS) + session->mime_state = mime_state_alloc(downgrading ? + MIME_OPT_DOWNGRADE + | MIME_OPT_REPORT_NESTING : + SMTP_ANY_CHECKS == 0 ? + MIME_OPT_DISABLE_MIME : + 0, + smtp_generic_maps + || smtp_header_checks ? + smtp_header_rewrite : + smtp_header_out, + (MIME_STATE_ANY_END) 0, + smtp_body_checks ? + smtp_body_rewrite : + smtp_text_out, + (MIME_STATE_ANY_END) 0, + (MIME_STATE_ERR_PRINT) 0, + (void *) state); + state->space_left = var_smtp_line_limit; + + while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) { + if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT) + break; + if (session->mime_state == 0) { + smtp_text_out((void *) state, rec_type, + vstring_str(session->scratch), + VSTRING_LEN(session->scratch), + (off_t) 0); + } else { + mime_errs = + mime_state_update(session->mime_state, rec_type, + vstring_str(session->scratch), + VSTRING_LEN(session->scratch)); + if (mime_errs) { + smtp_mime_fail(state, mime_errs); + RETURN(0); + } + } + prev_type = rec_type; + } + + if (session->mime_state) { + + /* + * The cleanup server normally ends MIME content with a + * normal text record. The following code is needed to + * flush an internal buffer when someone submits 8-bit + * mail not ending in newline via /usr/sbin/sendmail + * while MIME input processing is turned off, and MIME + * 8bit->7bit conversion is requested upon delivery. + * + * Or some error while doing generic address mapping. + */ + mime_errs = + mime_state_update(session->mime_state, rec_type, "", 0); + if (mime_errs) { + smtp_mime_fail(state, mime_errs); + RETURN(0); + } + } else if (prev_type == REC_TYPE_CONT) /* missing newline */ + smtp_fputs("", 0, session->stream); + if (session->features & SMTP_FEATURE_PIX_DELAY_DOTCRLF) { + smtp_flush(session->stream);/* hurts performance */ + sleep(var_smtp_pix_delay); /* not to mention this */ + } + if (vstream_ferror(state->src)) + msg_fatal("queue file read error"); + if (rec_type != REC_TYPE_XTRA) { + msg_warn("%s: bad record type: %d in message content", + request->queue_id, rec_type); + fail_status = smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.3.0"), + "unreadable mail queue entry"); + /* Bailing out, abort stream with prejudice */ + (void) vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); + DONT_USE_FORBIDDEN_SESSION; + /* If bounce_append() succeeded, status is still 0 */ + if (state->status == 0) + (void) mark_corrupt(state->src); + /* Don't override smtp_mesg_fail() here. */ + RETURN(fail_status); + } + } else { + if (!LOST_CONNECTION_INSIDE_DATA) + RETURN(smtp_stream_except(state, except, + "sending message body")); + + /* + * We will clear the stream error flag to try and read a + * premature 5XX response, so it is important to flush any + * unwritten data. Otherwise, we will try to flush it again + * before reading, which may incur an unnecessary delay and + * will prevent the reading of any response that is not + * already buffered (bundled with the DATA 354 response). + * + * Not much point in sending QUIT at this point, skip right to + * SMTP_STATE_LAST. The read engine above will likewise avoid + * looking for a QUIT response. + */ + (void) vstream_fpurge(session->stream, VSTREAM_PURGE_WRITE); + next_state = SMTP_STATE_LAST; + } + } + + /* + * Copy the next command to the buffer and update the sender state. + */ + if (except == 0) { + smtp_chat_cmd(session, "%s", vstring_str(next_command)); + } else { + DONT_CACHE_THIS_SESSION; + } + send_state = next_state; + send_rcpt = next_rcpt; + } while (recv_state != SMTP_STATE_LAST); + RETURN(0); +} + +/* smtp_xfer - send a batch of envelope information and the message data */ + +int smtp_xfer(SMTP_STATE *state) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + SMTP_RESP fake; + int send_state; + int recv_state; + int send_name_addr; + int result; + + /* + * Sanity check. Recipients should be unmarked at this point. + */ + if (SMTP_RCPT_LEFT(state) <= 0) + msg_panic("smtp_xfer: bad recipient count: %d", + SMTP_RCPT_LEFT(state)); + if (SMTP_RCPT_ISMARKED(request->rcpt_list.info)) + msg_panic("smtp_xfer: bad recipient status: %d", + request->rcpt_list.info->u.status); + + /* + * See if we should even try to send this message at all. This code sits + * here rather than in the EHLO processing code, because of SMTP + * connection caching. + */ + if (session->size_limit > 0 && session->size_limit < request->data_size) { + smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.3.4"), + "message size %lu exceeds size limit %.0f of server %s", + request->data_size, (double) session->size_limit, + session->namaddr); + /* Redundant. We abort this delivery attempt. */ + state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION; + return (0); + } + + /* + * Use XFORWARD to forward the origin of this email message across an + * SMTP-based content filter. Send client attribute information only if + * it exists (i.e. remote submission). Local submissions have no client + * attributes; the mail will appear to originate from the content filter + * which is acceptable. + */ + send_name_addr = + var_smtp_send_xforward + && (((session->features & SMTP_FEATURE_XFORWARD_NAME) + && CAN_FORWARD_CLIENT_NAME(request->client_name)) + || ((session->features & SMTP_FEATURE_XFORWARD_ADDR) + && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) + || ((session->features & SMTP_FEATURE_XFORWARD_PORT) + && CAN_FORWARD_CLIENT_PORT(request->client_port))); + session->send_proto_helo = + var_smtp_send_xforward + && (((session->features & SMTP_FEATURE_XFORWARD_PROTO) + && CAN_FORWARD_PROTO_NAME(request->client_proto)) + || ((session->features & SMTP_FEATURE_XFORWARD_HELO) + && CAN_FORWARD_HELO_NAME(request->client_helo)) + || ((session->features & SMTP_FEATURE_XFORWARD_IDENT) + && CAN_FORWARD_IDENT_NAME(request->log_ident)) + || ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN) + && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context))); + if (send_name_addr) + recv_state = send_state = SMTP_STATE_XFORWARD_NAME_ADDR; + else if (session->send_proto_helo) + recv_state = send_state = SMTP_STATE_XFORWARD_PROTO_HELO; + else + recv_state = send_state = SMTP_STATE_MAIL; + + /* + * Remember this session's "normal completion", even if the server 4xx-ed + * some or all recipients. Connection or handshake errors with a later MX + * host should not cause this destination be marked as unreachable. + */ + result = smtp_loop(state, send_state, recv_state); + + if (result == 0 + /* Just in case */ + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) + state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION; + + return (result); +} + +/* smtp_rset - send a lone RSET command */ + +int smtp_rset(SMTP_STATE *state) +{ + + /* + * This works because SMTP_STATE_RSET is a dedicated sender/recipient + * entry state, with SMTP_STATE_LAST as next sender/recipient state. + */ + return (smtp_loop(state, SMTP_STATE_RSET, SMTP_STATE_RSET)); +} + +/* smtp_quit - send a lone QUIT command */ + +int smtp_quit(SMTP_STATE *state) +{ + + /* + * This works because SMTP_STATE_QUIT is the last state with a sender + * action, with SMTP_STATE_LAST as the next sender/recipient state. + */ + return (smtp_loop(state, SMTP_STATE_QUIT, var_skip_quit_resp ? + SMTP_STATE_LAST : SMTP_STATE_QUIT)); +} diff --git a/src/smtp/smtp_rcpt.c b/src/smtp/smtp_rcpt.c new file mode 100644 index 0000000..673f0b4 --- /dev/null +++ b/src/smtp/smtp_rcpt.c @@ -0,0 +1,221 @@ +/*++ +/* NAME +/* smtp_rcpt 3 +/* SUMMARY +/* application-specific recipient list operations +/* SYNOPSIS +/* #include <smtp.h> +/* +/* SMTP_RCPT_INIT(state) +/* SMTP_STATE *state; +/* +/* SMTP_RCPT_DROP(state, rcpt) +/* SMTP_STATE *state; +/* RECIPIENT *rcpt; +/* +/* SMTP_RCPT_KEEP(state, rcpt) +/* SMTP_STATE *state; +/* RECIPIENT *rcpt; +/* +/* SMTP_RCPT_ISMARKED(rcpt) +/* RECIPIENT *rcpt; +/* +/* void smtp_rcpt_cleanup(SMTP_STATE *state) +/* SMTP_STATE *state; +/* +/* int SMTP_RCPT_LEFT(state) +/* SMTP_STATE *state; +/* +/* int SMTP_RCPT_MARK_COUNT(state) +/* SMTP_STATE *state; +/* +/* void smtp_rcpt_done(state, resp, rcpt) +/* SMTP_STATE *state; +/* SMTP_RESP *resp; +/* RECIPIENT *rcpt; +/* DESCRIPTION +/* This module implements application-specific mark and sweep +/* operations on recipient lists. Operation is as follows: +/* .IP \(bu +/* In the course of a delivery attempt each recipient is +/* marked either as DROP (remove from recipient list) or KEEP +/* (deliver to alternate mail server). +/* .IP \(bu +/* After a delivery attempt any recipients marked DROP are deleted +/* from the request, and the left-over recipients are unmarked. +/* .PP +/* The mark/sweep algorithm is implemented in a redundant manner, +/* and ensures that all recipients are explicitly accounted for. +/* +/* Operations with upper case names are implemented by macros +/* whose arguments may be evaluated more than once. +/* +/* SMTP_RCPT_INIT() initializes application-specific recipient +/* information and must be called before the first delivery attempt. +/* +/* SMTP_RCPT_DROP() marks the specified recipient as DROP (remove +/* from recipient list). It is an error to mark an already marked +/* recipient. +/* +/* SMTP_RCPT_KEEP() marks the specified recipient as KEEP (deliver +/* to alternate mail server). It is an error to mark an already +/* marked recipient. +/* +/* SMTP_RCPT_ISMARKED() returns non-zero when the specified +/* recipient is marked. +/* +/* SMTP_RCPT_LEFT() returns the number of left_over recipients +/* (the total number of marked and non-marked recipients). +/* +/* SMTP_RCPT_MARK_COUNT() returns the number of left_over +/* recipients that are marked. +/* +/* smtp_rcpt_cleanup() cleans up the in-memory recipient list. +/* It removes the recipients marked DROP from the left-over +/* recipients, unmarks the left-over recipients, and enforces +/* the requirement that all recipients are marked upon entry. +/* +/* smtp_rcpt_done() logs that a recipient is completed and upon +/* success it marks the recipient as done in the queue file. +/* Finally, it marks the in-memory recipient as DROP. +/* +/* Note: smtp_rcpt_done() may change the order of the recipient +/* list. +/* DIAGNOSTICS +/* Panic: interface violation. +/* +/* When a recipient can't be logged as completed, the recipient is +/* logged as deferred instead. +/* BUGS +/* The single recipient list abstraction dates from the time +/* that the SMTP client would give up after one SMTP session, +/* so that each recipient was either bounced, delivered or +/* deferred. Implicitly, all recipients were marked as DROP. +/* +/* This abstraction is less convenient when an SMTP client +/* must be able to deliver left-over recipients to a backup +/* host. It might be more natural to have an input list with +/* recipients to deliver, and an output list with left-over +/* recipients. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* smtp_rcpt_cleanup */ +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <stringops.h> +#include <mymalloc.h> + +/* Global library. */ + +#include <mail_params.h> +#include <deliver_request.h> /* smtp_rcpt_done */ +#include <deliver_completed.h> /* smtp_rcpt_done */ +#include <sent.h> /* smtp_rcpt_done */ +#include <dsn_mask.h> /* smtp_rcpt_done */ + +/* Application-specific. */ + +#include <smtp.h> + +/* smtp_rcpt_done - mark recipient as done or else */ + +void smtp_rcpt_done(SMTP_STATE *state, SMTP_RESP *resp, RECIPIENT *rcpt) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + SMTP_ITERATOR *iter = state->iterator; + DSN_BUF *why = state->why; + const char *dsn_action = "relayed"; + int status; + + /* + * Assume this was intermediate delivery when the server announced DSN + * support, and don't send a DSN "SUCCESS" notification. + */ + if (session->features & SMTP_FEATURE_DSN) + rcpt->dsn_notify &= ~DSN_NOTIFY_SUCCESS; + + /* + * Assume this was final delivery when the LMTP server announced no DSN + * support. In backwards compatibility mode, send a "relayed" instead of + * a "delivered" DSN "SUCCESS" notification. Do not attempt to "simplify" + * the expression. The redundancy is for clarity. It is trivially + * eliminated by the compiler. There is no need to sacrifice clarity for + * the sake of "performance". + */ + if ((session->features & SMTP_FEATURE_DSN) == 0 + && !smtp_mode + && var_lmtp_assume_final != 0) + dsn_action = "delivered"; + + /* + * Report success and delete the recipient from the delivery request. + * Defer if the success can't be reported. + * + * Note: the DSN action is ignored in case of address probes. + */ + dsb_update(why, resp->dsn, dsn_action, DSB_MTYPE_DNS, STR(iter->host), + DSB_DTYPE_SMTP, resp->str, "%s", resp->str); + + status = sent(DEL_REQ_TRACE_FLAGS(request->flags), + request->queue_id, &request->msg_stats, rcpt, + session->namaddrport, DSN_FROM_DSN_BUF(why)); + if (status == 0) + if (request->flags & DEL_REQ_FLAG_SUCCESS) + deliver_completed(state->src, rcpt->offset); + SMTP_RCPT_DROP(state, rcpt); + state->status |= status; +} + +/* smtp_rcpt_cleanup_callback - qsort callback */ + +static int smtp_rcpt_cleanup_callback(const void *a, const void *b) +{ + return (((RECIPIENT *) a)->u.status - ((RECIPIENT *) b)->u.status); +} + +/* smtp_rcpt_cleanup - purge completed recipients from request */ + +void smtp_rcpt_cleanup(SMTP_STATE *state) +{ + RECIPIENT_LIST *rcpt_list = &state->request->rcpt_list; + RECIPIENT *rcpt; + + /* + * Sanity checks. + */ + if (state->rcpt_drop + state->rcpt_keep != state->rcpt_left) + msg_panic("smtp_rcpt_cleanup: recipient count mismatch: %d+%d!=%d", + state->rcpt_drop, state->rcpt_keep, state->rcpt_left); + + /* + * Recipients marked KEEP sort before recipients marked DROP. Skip the + * sorting in the common case that all recipients are marked the same. + */ + if (state->rcpt_drop > 0 && state->rcpt_keep > 0) + qsort((void *) rcpt_list->info, state->rcpt_left, + sizeof(rcpt_list->info[0]), smtp_rcpt_cleanup_callback); + + /* + * Truncate the recipient list and unmark the left-over recipients. + */ + state->rcpt_left = state->rcpt_keep; + for (rcpt = rcpt_list->info; rcpt < rcpt_list->info + state->rcpt_left; rcpt++) + rcpt->u.status = 0; + state->rcpt_drop = state->rcpt_keep = 0; +} diff --git a/src/smtp/smtp_reuse.c b/src/smtp/smtp_reuse.c new file mode 100644 index 0000000..f93ba29 --- /dev/null +++ b/src/smtp/smtp_reuse.c @@ -0,0 +1,269 @@ +/*++ +/* NAME +/* smtp_reuse 3 +/* SUMMARY +/* SMTP session cache glue +/* SYNOPSIS +/* #include <smtp.h> +/* #include <smtp_reuse.h> +/* +/* void smtp_save_session(state, name_key_flags, endp_key_flags) +/* SMTP_STATE *state; +/* int name_key_flags; +/* int endp_key_flags; +/* +/* SMTP_SESSION *smtp_reuse_nexthop(state, name_key_flags) +/* SMTP_STATE *state; +/* int name_key_flags; +/* +/* SMTP_SESSION *smtp_reuse_addr(state, endp_key_flags) +/* SMTP_STATE *state; +/* int endp_key_flags; +/* DESCRIPTION +/* This module implements the SMTP client specific interface to +/* the generic session cache infrastructure. +/* +/* The caller needs to include additional state in _key_flags +/* to avoid false sharing of SASL-authenticated or TLS-authenticated +/* sessions. +/* +/* smtp_save_session() stores the current session under the +/* delivery request next-hop logical destination (if applicable) +/* and under the remote server address. The SMTP_SESSION object +/* is destroyed. +/* +/* smtp_reuse_nexthop() looks up a cached session by its +/* delivery request next-hop destination, and verifies that +/* the session is still alive. The restored session information +/* includes the "best MX" bit and overrides the iterator dest, +/* host and addr fields. The result is null in case of failure. +/* +/* smtp_reuse_addr() looks up a cached session by its server +/* address, and verifies that the session is still alive. +/* The restored session information does not include the "best +/* MX" bit, and does not override the iterator dest, host and +/* addr fields. The result is null in case of failure. +/* +/* Arguments: +/* .IP state +/* SMTP client state, including the current session, the original +/* next-hop domain, etc. +/* .IP name_key_flags +/* Explicit declaration of context that should be used to look +/* up a cached connection by its logical destination. +/* See smtp_key(3) for details. +/* .IP endp_key_flags +/* Explicit declaration of context that should be used to look +/* up a cached connection by its server address. +/* See smtp_key(3) for details. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> +#include <htable.h> +#include <stringops.h> + +/* Global library. */ + +#include <scache.h> +#include <mail_params.h> + +/* Application-specific. */ + +#include <smtp.h> +#include <smtp_reuse.h> + + /* + * Key field delimiter, and place holder field value for + * unavailable/inapplicable information. + */ +#define SMTP_REUSE_KEY_DELIM_NA "\n*" + +/* smtp_save_session - save session under next-hop name and server address */ + +void smtp_save_session(SMTP_STATE *state, int name_key_flags, + int endp_key_flags) +{ + SMTP_SESSION *session = state->session; + int fd; + + /* + * Encode the delivery request next-hop destination, if applicable. Reuse + * storage that is also used for cache lookup queries. + * + * HAVE_SCACHE_REQUEST_NEXTHOP() controls whether or not to reuse or cache a + * connection by its delivery request next-hop destination. The idea is + * 1) to allow a reuse request to skip over bad hosts, and 2) to avoid + * caching a less-preferred connection when a more-preferred connection + * was possible. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, name_key_flags); + + /* + * Encode the physical endpoint name. Reuse storage that is also used for + * cache lookup queries. + */ + smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, endp_key_flags); + + /* + * Passivate the SMTP_SESSION object, destroying the object in the + * process. Reuse storage that is also used for cache lookup results. + */ + fd = smtp_session_passivate(session, state->dest_prop, state->endp_prop); + state->session = 0; + + /* + * Save the session under the delivery request next-hop name, if + * applicable. + * + * XXX The logical to physical binding can be kept for as long as the DNS + * allows us to (but that could result in the caching of lots of unused + * bindings). The session should be idle for no more than 30 seconds or + * so. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + scache_save_dest(smtp_scache, var_smtp_cache_conn, + STR(state->dest_label), STR(state->dest_prop), + STR(state->endp_label)); + + /* + * Save every good session under its physical endpoint address. + */ + scache_save_endp(smtp_scache, var_smtp_cache_conn, STR(state->endp_label), + STR(state->endp_prop), fd); +} + +/* smtp_reuse_common - common session reuse code */ + +static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd, + const char *label) +{ + const char *myname = "smtp_reuse_common"; + SMTP_ITERATOR *iter = state->iterator; + SMTP_SESSION *session; + + /* + * Re-activate the SMTP_SESSION object. + */ + session = smtp_session_activate(fd, state->iterator, state->dest_prop, + state->endp_prop); + if (session == 0) { + msg_warn("%s: bad cached session attribute for %s", myname, label); + (void) close(fd); + return (0); + } + state->session = session; + session->state = state; + + /* + * Send an RSET probe to verify that the session is still good. + */ + if (smtp_rset(state) < 0 + || (session->features & SMTP_FEATURE_RSET_REJECTED) != 0) { + smtp_session_free(session); + return (state->session = 0); + } + + /* + * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. + */ + vstream_tweak_sock(session->stream); + + /* + * Update the list of used cached addresses. + */ + htable_enter(state->cache_used, STR(iter->addr), (void *) 0); + + return (session); +} + +/* smtp_reuse_nexthop - reuse session cached under nexthop name */ + +SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *state, int name_key_flags) +{ + SMTP_SESSION *session; + int fd; + + /* + * Look up the session by its logical name. + */ + smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, name_key_flags); + if ((fd = scache_find_dest(smtp_scache, STR(state->dest_label), + state->dest_prop, state->endp_prop)) < 0) + return (0); + + /* + * Re-activate the SMTP_SESSION object, and verify that the session is + * still good. + */ + session = smtp_reuse_common(state, fd, STR(state->dest_label)); + return (session); +} + +/* smtp_reuse_addr - reuse session cached under numerical address */ + +SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *state, int endp_key_flags) +{ + SMTP_SESSION *session; + int fd; + + /* + * Address-based reuse is safe for security levels that require TLS + * certificate checks, as long as the current nexhop is included in the + * cache lookup key (COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP). This is + * sufficient to prevent the reuse of a TLS-authenticated connection to + * the same MX hostname, IP address, and port, but for a different + * current nexthop destination with a different TLS policy. + */ + + /* + * Look up the session by its IP address. This means that we have no + * destination-to-address binding properties. + */ + smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, endp_key_flags); + if ((fd = scache_find_endp(smtp_scache, STR(state->endp_label), + state->endp_prop)) < 0) + return (0); + VSTRING_RESET(state->dest_prop); + VSTRING_TERMINATE(state->dest_prop); + + /* + * Re-activate the SMTP_SESSION object, and verify that the session is + * still good. + */ + session = smtp_reuse_common(state, fd, STR(state->endp_label)); + + return (session); +} diff --git a/src/smtp/smtp_reuse.h b/src/smtp/smtp_reuse.h new file mode 100644 index 0000000..8c6cb48 --- /dev/null +++ b/src/smtp/smtp_reuse.h @@ -0,0 +1,27 @@ +/*++ +/* NAME +/* smtp_reuse 3h +/* SUMMARY +/* SMTP session cache glue +/* SYNOPSIS +/* #include <smtp_reuse.h> +/* DESCRIPTION +/* .nf + + /* + * Internal interfaces. + */ +extern void smtp_save_session(SMTP_STATE *, int, int); +extern SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *, int); +extern SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtp/smtp_sasl.h b/src/smtp/smtp_sasl.h new file mode 100644 index 0000000..756d13d --- /dev/null +++ b/src/smtp/smtp_sasl.h @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* smtp_sasl 3h +/* SUMMARY +/* Postfix SASL interface for SMTP client +/* SYNOPSIS +/* #include "smtp_sasl.h" +/* DESCRIPTION +/* .nf + + /* + * SASL protocol functions + */ +extern void smtp_sasl_initialize(void); +extern void smtp_sasl_connect(SMTP_SESSION *); +extern int smtp_sasl_passwd_lookup(SMTP_SESSION *); +extern void smtp_sasl_start(SMTP_SESSION *, const char *, const char *); +extern int smtp_sasl_authenticate(SMTP_SESSION *, DSN_BUF *); +extern void smtp_sasl_cleanup(SMTP_SESSION *); + +extern void smtp_sasl_helo_auth(SMTP_SESSION *, const char *); +extern int smtp_sasl_helo_login(SMTP_STATE *); + +extern void smtp_sasl_passivate(SMTP_SESSION *, VSTRING *); +extern int smtp_sasl_activate(SMTP_SESSION *, char *); +extern STRING_LIST *smtp_sasl_mechs; + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtp/smtp_sasl_auth_cache.c b/src/smtp/smtp_sasl_auth_cache.c new file mode 100644 index 0000000..f3104ca --- /dev/null +++ b/src/smtp/smtp_sasl_auth_cache.c @@ -0,0 +1,272 @@ +/*++ +/* NAME +/* smtp_sasl_auth_cache 3 +/* SUMMARY +/* Postfix SASL authentication reply cache +/* SYNOPSIS +/* #include "smtp.h" +/* #include "smtp_sasl_auth_cache.h" +/* +/* SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(map, ttl) +/* const char *map +/* int ttl; +/* +/* void smtp_sasl_auth_cache_store(auth_cache, session, resp) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* const SMTP_SESSION *session; +/* const SMTP_RESP *resp; +/* +/* int smtp_sasl_auth_cache_find(auth_cache, session) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* const SMTP_SESSION *session; +/* +/* char *smtp_sasl_auth_cache_dsn(auth_cache) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* +/* char *smtp_sasl_auth_cache_text(auth_cache) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* DESCRIPTION +/* This module maintains a cache of SASL authentication server replies. +/* This can be used to avoid repeated login failure errors. +/* +/* smtp_sasl_auth_cache_init() opens or creates the named cache. +/* +/* smtp_sasl_auth_cache_store() stores information about a +/* SASL login attempt together with the server status and +/* complete response. +/* +/* smtp_sasl_auth_cache_find() returns non-zero when a cache +/* entry exists for the given host, username and password. +/* +/* smtp_sasl_auth_cache_dsn() and smtp_sasl_auth_cache_text() +/* return the status and complete server response as found +/* with smtp_sasl_auth_cache_find(). +/* +/* Arguments: +/* .IP map +/* Lookup table name. The name must be singular and must start +/* with "proxy:". +/* .IP ttl +/* The time after which a cache entry is considered expired. +/* .IP session +/* Session context. +/* .IP resp +/* Remote SMTP server response, to be stored into the cache. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Keean Schupke +/* Fry-IT Ltd. +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library + */ +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <base64_code.h> +#include <dict.h> + + /* + * Global library + */ +#include <dsn_util.h> +#include <dict_proxy.h> + + /* + * Application-specific + */ +#include "smtp.h" +#include "smtp_sasl_auth_cache.h" + + /* + * XXX This feature stores passwords, so we must mask them with a strong + * cryptographic hash. This requires OpenSSL support. + * + * XXX It would be even better if the stored hash were salted. + */ +#ifdef HAVE_SASL_AUTH_CACHE + +/* smtp_sasl_auth_cache_init - per-process initialization (pre jail) */ + +SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *map, int ttl) +{ + const char *myname = "smtp_sasl_auth_cache_init"; + SMTP_SASL_AUTH_CACHE *auth_cache; + + /* + * Sanity checks. + */ +#define HAS_MULTIPLE_VALUES(s) ((s)[strcspn((s), CHARS_COMMA_SP)] != 0) + + if (*map == 0) + msg_panic("%s: empty SASL authentication cache name", myname); + if (ttl < 0) + msg_panic("%s: bad SASL authentication cache ttl: %d", myname, ttl); + if (HAS_MULTIPLE_VALUES(map)) + msg_fatal("SASL authentication cache name \"%s\" " + "contains multiple values", map); + + /* + * XXX To avoid multiple writers the map needs to be maintained by the + * proxywrite service. We would like to have a DICT_FLAG_REQ_PROXY flag + * so that the library can enforce this, but that requires moving the + * dict_proxy module one level down in the build dependency hierarchy. + */ +#define CACHE_DICT_OPEN_FLAGS \ + (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_UTF8_REQUEST) +#define PROXY_COLON DICT_TYPE_PROXY ":" +#define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1) + + if (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) != 0) + msg_fatal("SASL authentication cache name \"%s\" must start with \"" + PROXY_COLON, map); + + auth_cache = (SMTP_SASL_AUTH_CACHE *) mymalloc(sizeof(*auth_cache)); + auth_cache->dict = dict_open(map, O_CREAT | O_RDWR, CACHE_DICT_OPEN_FLAGS); + auth_cache->ttl = ttl; + auth_cache->dsn = mystrdup(""); + auth_cache->text = mystrdup(""); + return (auth_cache); +} + + /* + * Each cache lookup key contains a server host name and user name. Each + * cache value contains a time stamp, a hashed password, and the server + * response. With this organization, we don't have to worry about cache + * pollution, because we can detect if a cache entry has expired, or if the + * password has changed. + */ + +/* smtp_sasl_auth_cache_make_key - format auth failure cache lookup key */ + +static char *smtp_sasl_auth_cache_make_key(const char *host, const char *user) +{ + VSTRING *buf = vstring_alloc(100); + + vstring_sprintf(buf, "%s;%s", host, user); + return (vstring_export(buf)); +} + +/* smtp_sasl_auth_cache_make_pass - hash the auth failure cache password */ + +static char *smtp_sasl_auth_cache_make_pass(const char *password) +{ + VSTRING *buf = vstring_alloc(2 * SHA_DIGEST_LENGTH); + + base64_encode(buf, (const char *) SHA1((const unsigned char *) password, + strlen(password), 0), + SHA_DIGEST_LENGTH); + return (vstring_export(buf)); +} + +/* smtp_sasl_auth_cache_make_value - format auth failure cache value */ + +static char *smtp_sasl_auth_cache_make_value(const char *password, + const char *dsn, + const char *rep_str) +{ + VSTRING *val_buf = vstring_alloc(100); + char *pwd_hash; + unsigned long now = (unsigned long) time((time_t *) 0); + + pwd_hash = smtp_sasl_auth_cache_make_pass(password); + vstring_sprintf(val_buf, "%lu;%s;%s;%s", now, pwd_hash, dsn, rep_str); + myfree(pwd_hash); + return (vstring_export(val_buf)); +} + +/* smtp_sasl_auth_cache_valid_value - validate auth failure cache value */ + +static int smtp_sasl_auth_cache_valid_value(SMTP_SASL_AUTH_CACHE *auth_cache, + const char *entry, + const char *password) +{ + ssize_t len = strlen(entry); + char *cache_hash = mymalloc(len); + char *curr_hash; + unsigned long now = (unsigned long) time((time_t *) 0); + unsigned long time_stamp; + int valid; + + auth_cache->dsn = myrealloc(auth_cache->dsn, len); + auth_cache->text = myrealloc(auth_cache->text, len); + + if (sscanf(entry, "%lu;%[^;];%[^;];%[^\n]", &time_stamp, cache_hash, + auth_cache->dsn, auth_cache->text) != 4 + || !dsn_valid(auth_cache->dsn)) { + msg_warn("bad smtp_sasl_auth_cache entry: %.100s", entry); + valid = 0; + } else if (time_stamp + auth_cache->ttl < now) { + valid = 0; + } else { + curr_hash = smtp_sasl_auth_cache_make_pass(password); + valid = (strcmp(cache_hash, curr_hash) == 0); + myfree(curr_hash); + } + myfree(cache_hash); + return (valid); +} + +/* smtp_sasl_auth_cache_find - search auth failure cache */ + +int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *auth_cache, + const SMTP_SESSION *session) +{ + SMTP_ITERATOR *iter = session->iterator; + char *key; + const char *entry; + int valid = 0; + + key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username); + if ((entry = dict_get(auth_cache->dict, key)) != 0) + if ((valid = smtp_sasl_auth_cache_valid_value(auth_cache, entry, + session->sasl_passwd)) == 0) + /* Remove expired, password changed, or malformed cache entry. */ + if (dict_del(auth_cache->dict, key) != 0) + msg_warn("SASL auth failure map %s: entry not deleted: %s", + auth_cache->dict->name, key); + if (auth_cache->dict->error) + msg_warn("SASL auth failure map %s: lookup failed for %s", + auth_cache->dict->name, key); + myfree(key); + return (valid); +} + +/* smtp_sasl_auth_cache_store - update auth failure cache */ + +void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *auth_cache, + const SMTP_SESSION *session, + const SMTP_RESP *resp) +{ + SMTP_ITERATOR *iter = session->iterator; + char *key; + char *value; + + key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username); + value = smtp_sasl_auth_cache_make_value(session->sasl_passwd, + resp->dsn, resp->str); + dict_put(auth_cache->dict, key, value); + + myfree(value); + myfree(key); +} + +#endif diff --git a/src/smtp/smtp_sasl_auth_cache.h b/src/smtp/smtp_sasl_auth_cache.h new file mode 100644 index 0000000..cbbdb0d --- /dev/null +++ b/src/smtp/smtp_sasl_auth_cache.h @@ -0,0 +1,62 @@ +#ifndef _SMTP_SASL_AUTH_CACHE_H_INCLUDED_ +#define _SMTP_SASL_AUTH_CACHE_H_INCLUDED_ + +/*++ +/* NAME +/* smtp_sasl_auth_cache 3h +/* SUMMARY +/* Postfix SASL authentication failure cache +/* SYNOPSIS +/* #include "smtp.h" +/* #include "smtp_sasl_auth_cache.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * This code stores hashed passwords which requires OpenSSL. + */ +#if defined(USE_TLS) && defined(USE_SASL_AUTH) +#define HAVE_SASL_AUTH_CACHE + + /* + * External interface. + */ +typedef struct { + DICT *dict; + int ttl; + char *dsn; + char *text; +} SMTP_SASL_AUTH_CACHE; + +extern SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *, int); +extern void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *, const SMTP_RESP *); +extern int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *); + +#define smtp_sasl_auth_cache_dsn(cp) ((cp)->dsn) +#define smtp_sasl_auth_cache_text(cp) ((cp)->text) + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/smtp/smtp_sasl_glue.c b/src/smtp/smtp_sasl_glue.c new file mode 100644 index 0000000..ef8e8c4 --- /dev/null +++ b/src/smtp/smtp_sasl_glue.c @@ -0,0 +1,512 @@ +/*++ +/* NAME +/* smtp_sasl_glue 3 +/* SUMMARY +/* Postfix SASL interface for SMTP client +/* SYNOPSIS +/* #include smtp_sasl.h +/* +/* void smtp_sasl_initialize() +/* +/* void smtp_sasl_connect(session) +/* SMTP_SESSION *session; +/* +/* void smtp_sasl_start(session, sasl_opts_name, sasl_opts_val) +/* SMTP_SESSION *session; +/* +/* int smtp_sasl_passwd_lookup(session) +/* SMTP_SESSION *session; +/* +/* int smtp_sasl_authenticate(session, why) +/* SMTP_SESSION *session; +/* DSN_BUF *why; +/* +/* void smtp_sasl_cleanup(session) +/* SMTP_SESSION *session; +/* +/* void smtp_sasl_passivate(session, buf) +/* SMTP_SESSION *session; +/* VSTRING *buf; +/* +/* int smtp_sasl_activate(session, buf) +/* SMTP_SESSION *session; +/* char *buf; +/* DESCRIPTION +/* smtp_sasl_initialize() initializes the SASL library. This +/* routine must be called once at process startup, before any +/* chroot operations. +/* +/* smtp_sasl_connect() performs per-session initialization. This +/* routine must be called once at the start of each connection. +/* +/* smtp_sasl_start() performs per-session initialization. This +/* routine must be called once per session before doing any SASL +/* authentication. The sasl_opts_name and sasl_opts_val parameters are +/* the postfix configuration parameters setting the security +/* policy of the SASL authentication. +/* +/* smtp_sasl_passwd_lookup() looks up the username/password +/* for the current SMTP server. The result is zero in case +/* of failure, a long jump in case of error. +/* +/* smtp_sasl_authenticate() implements the SASL authentication +/* dialog. The result is < 0 in case of protocol failure, zero in +/* case of unsuccessful authentication, > 0 in case of success. +/* The why argument is updated with a reason for failure. +/* This routine must be called only when smtp_sasl_passwd_lookup() +/* succeeds. +/* +/* smtp_sasl_cleanup() cleans up. It must be called at the +/* end of every SMTP session that uses SASL authentication. +/* This routine is a noop for non-SASL sessions. +/* +/* smtp_sasl_passivate() appends flattened SASL attributes to the +/* specified buffer. The SASL attributes are not destroyed. +/* +/* smtp_sasl_activate() restores SASL attributes from the +/* specified buffer. The buffer is modified. A result < 0 +/* means there was an error. +/* +/* Arguments: +/* .IP session +/* Session context. +/* .IP mech_list +/* String of SASL mechanisms (separated by blanks) +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <stdlib.h> +#include <string.h> + + /* + * Utility library + */ +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <split_at.h> + + /* + * Global library + */ +#include <mail_params.h> +#include <string_list.h> +#include <maps.h> +#include <mail_addr_find.h> +#include <smtp_stream.h> + + /* + * XSASL library. + */ +#include <xsasl.h> + + /* + * Application-specific + */ +#include "smtp.h" +#include "smtp_sasl.h" +#include "smtp_sasl_auth_cache.h" + +#ifdef USE_SASL_AUTH + + /* + * Per-host login/password information. + */ +static MAPS *smtp_sasl_passwd_map; + + /* + * Supported SASL mechanisms. + */ +STRING_LIST *smtp_sasl_mechs; + + /* + * SASL implementation handle. + */ +static XSASL_CLIENT_IMPL *smtp_sasl_impl; + + /* + * The 535 SASL authentication failure cache. + */ +#ifdef HAVE_SASL_AUTH_CACHE +static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache; + +#endif + +/* smtp_sasl_passwd_lookup - password lookup routine */ + +int smtp_sasl_passwd_lookup(SMTP_SESSION *session) +{ + const char *myname = "smtp_sasl_passwd_lookup"; + SMTP_STATE *state = session->state; + SMTP_ITERATOR *iter = session->iterator; + const char *value; + char *passwd; + + /* + * Sanity check. + */ + if (smtp_sasl_passwd_map == 0) + msg_panic("%s: passwd map not initialized", myname); + + /* + * Look up the per-server password information. Try the hostname first, + * then try the destination. + * + * XXX Instead of using nexthop (the intended destination) we use dest + * (either the intended destination, or a fall-back destination). + * + * XXX SASL authentication currently depends on the host/domain but not on + * the TCP port. If the port is not :25, we should append it to the table + * lookup key. Code for this was briefly introduced into 2.2 snapshots, + * but didn't canonicalize the TCP port, and did not append the port to + * the MX hostname. + */ + smtp_sasl_passwd_map->error = 0; + if ((smtp_mode + && var_smtp_sender_auth && state->request->sender[0] + && (value = mail_addr_find(smtp_sasl_passwd_map, + state->request->sender, (char **) 0)) != 0) + || (smtp_sasl_passwd_map->error == 0 + && (value = maps_find(smtp_sasl_passwd_map, + STR(iter->host), 0)) != 0) + || (smtp_sasl_passwd_map->error == 0 + && (value = maps_find(smtp_sasl_passwd_map, + STR(iter->dest), 0)) != 0)) { + if (session->sasl_username) + myfree(session->sasl_username); + session->sasl_username = mystrdup(value); + passwd = split_at(session->sasl_username, ':'); + if (session->sasl_passwd) + myfree(session->sasl_passwd); + session->sasl_passwd = mystrdup(passwd ? passwd : ""); + if (msg_verbose) + msg_info("%s: host `%s' user `%s' pass `%s'", + myname, STR(iter->host), + session->sasl_username, session->sasl_passwd); + return (1); + } else if (smtp_sasl_passwd_map->error) { + msg_warn("%s: %s lookup error", + state->request->queue_id, smtp_sasl_passwd_map->title); + vstream_longjmp(session->stream, SMTP_ERR_DATA); + } else { + if (msg_verbose) + msg_info("%s: no auth info found (sender=`%s', host=`%s')", + myname, state->request->sender, STR(iter->host)); + return (0); + } +} + +/* smtp_sasl_initialize - per-process initialization (pre jail) */ + +void smtp_sasl_initialize(void) +{ + + /* + * Sanity check. + */ + if (smtp_sasl_passwd_map || smtp_sasl_impl) + msg_panic("smtp_sasl_initialize: repeated call"); + if (*var_smtp_sasl_passwd == 0) + msg_fatal("specify a password table via the `%s' configuration parameter", + VAR_LMTP_SMTP(SASL_PASSWD)); + + /* + * Open the per-host password table and initialize the SASL library. Use + * shared locks for reading, just in case someone updates the table. + */ + smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD), + var_smtp_sasl_passwd, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type, + var_smtp_sasl_path)) == 0) + msg_fatal("SASL library initialization"); + + /* + * Initialize optional supported mechanism matchlist + */ + if (*var_smtp_sasl_mechs) + smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS, + MATCH_FLAG_NONE, + var_smtp_sasl_mechs); + + /* + * Initialize the 535 SASL authentication failure cache. + */ + if (*var_smtp_sasl_auth_cache_name) { +#ifdef HAVE_SASL_AUTH_CACHE + smtp_sasl_auth_cache = + smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name, + var_smtp_sasl_auth_cache_time); +#else + msg_warn("not compiled with TLS support -- " + "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME)); +#endif + } +} + +/* smtp_sasl_connect - per-session client initialization */ + +void smtp_sasl_connect(SMTP_SESSION *session) +{ + + /* + * This initialization happens whenever we instantiate an SMTP session + * object. We don't instantiate a SASL client until we actually need one. + */ + session->sasl_mechanism_list = 0; + session->sasl_username = 0; + session->sasl_passwd = 0; + session->sasl_client = 0; + session->sasl_reply = 0; +} + +/* smtp_sasl_start - per-session SASL initialization */ + +void smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name, + const char *sasl_opts_val) +{ + XSASL_CLIENT_CREATE_ARGS create_args; + SMTP_ITERATOR *iter = session->iterator; + + if (msg_verbose) + msg_info("starting new SASL client"); + if ((session->sasl_client = + XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args, + stream = session->stream, + service = var_procname, + server_name = STR(iter->host), + security_options = sasl_opts_val)) == 0) + msg_fatal("SASL per-connection initialization failed"); + session->sasl_reply = vstring_alloc(20); +} + +/* smtp_sasl_authenticate - run authentication protocol */ + +int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why) +{ + const char *myname = "smtp_sasl_authenticate"; + SMTP_ITERATOR *iter = session->iterator; + SMTP_RESP *resp; + const char *mechanism; + int result; + char *line; + int steps = 0; + + /* + * Sanity check. + */ + if (session->sasl_mechanism_list == 0) + msg_panic("%s: no mechanism list", myname); + + if (msg_verbose) + msg_info("%s: %s: SASL mechanisms %s", + myname, session->namaddrport, session->sasl_mechanism_list); + + /* + * Avoid repeated login failures after a recent 535 error. + */ +#ifdef HAVE_SASL_AUTH_CACHE + if (smtp_sasl_auth_cache + && smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) { + char *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache); + char *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache); + + if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5') + resp_dsn[0] = '4'; + dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS, + STR(iter->host), var_procname, resp_str, + "SASL [CACHED] authentication failed; server %s said: %s", + STR(iter->host), resp_str); + return (0); + } +#endif + + /* + * Start the client side authentication protocol. + */ + result = xsasl_client_first(session->sasl_client, + session->sasl_mechanism_list, + session->sasl_username, + session->sasl_passwd, + &mechanism, session->sasl_reply); + if (result != XSASL_AUTH_OK) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA, + DSB_DTYPE_SASL, STR(session->sasl_reply), + "SASL authentication failed; " + "cannot authenticate to server %s: %s", + session->namaddr, STR(session->sasl_reply)); + return (-1); + } + /*- + * Send the AUTH command and the optional initial client response. + * + * https://tools.ietf.org/html/rfc4954#page-4 + * Note that the AUTH command is still subject to the line length + * limitations defined in [SMTP]. If use of the initial response argument + * would cause the AUTH command to exceed this length, the client MUST NOT + * use the initial response parameter... + * + * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4 + * The maximum total length of a command line including the command word + * and the <CRLF> is 512 octets. + * + * Defer the initial response if the resulting command exceeds the limit. + */ + if (LEN(session->sasl_reply) > 0 + && strlen(mechanism) + LEN(session->sasl_reply) + 8 <= 512) { + smtp_chat_cmd(session, "AUTH %s %s", mechanism, + STR(session->sasl_reply)); + VSTRING_RESET(session->sasl_reply); /* no deferred initial reply */ + } else { + smtp_chat_cmd(session, "AUTH %s", mechanism); + } + + /* + * Step through the authentication protocol until the server tells us + * that we are done. If session->sasl_reply is non-empty we have a + * deferred initial reply and expect an empty initial challenge from the + * server. If the server's initial challenge is non-empty we have a SASL + * protocol violation with both sides wanting to go first. + */ + while ((resp = smtp_chat_resp(session))->code / 100 == 3) { + + /* + * Sanity check. + */ + if (++steps > 100) { + dsb_simple(why, "4.3.0", "SASL authentication failed; " + "authentication protocol loop with server %s", + session->namaddr); + return (-1); + } + + /* + * Process a server challenge. + */ + line = resp->str; + (void) mystrtok(&line, "- \t\n"); /* skip over result code */ + + if (LEN(session->sasl_reply) > 0) { + + /* + * Deferred initial response, the server challenge must be empty. + * Cleared after actual transmission to the server. + */ + if (*line) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, + DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error", + "SASL authentication failed; non-empty initial " + "%s challenge from server %s: %s", mechanism, + session->namaddr, STR(session->sasl_reply)); + return (-1); + } + } else { + result = xsasl_client_next(session->sasl_client, line, + session->sasl_reply); + if (result != XSASL_AUTH_OK) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */ + DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply), + "SASL authentication failed; " + "cannot authenticate to server %s: %s", + session->namaddr, STR(session->sasl_reply)); + return (-1); /* Fix 200512 */ + } + } + + /* + * Send a client response. + */ + smtp_chat_cmd(session, "%s", STR(session->sasl_reply)); + VSTRING_RESET(session->sasl_reply); /* clear initial reply */ + } + + /* + * We completed the authentication protocol. + */ + if (resp->code / 100 != 2) { +#ifdef HAVE_SASL_AUTH_CACHE + /* Update the 535 authentication failure cache. */ + if (smtp_sasl_auth_cache && resp->code == 535) + smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp); +#endif + if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5) + STR(resp->dsn_buf)[0] = '4'; + dsb_update(why, resp->dsn, DSB_DEF_ACTION, + DSB_MTYPE_DNS, STR(iter->host), + var_procname, resp->str, + "SASL authentication failed; server %s said: %s", + session->namaddr, resp->str); + return (0); + } + return (1); +} + +/* smtp_sasl_cleanup - per-session cleanup */ + +void smtp_sasl_cleanup(SMTP_SESSION *session) +{ + if (session->sasl_username) { + myfree(session->sasl_username); + session->sasl_username = 0; + } + if (session->sasl_passwd) { + myfree(session->sasl_passwd); + session->sasl_passwd = 0; + } + if (session->sasl_mechanism_list) { + /* allocated in smtp_sasl_helo_auth */ + myfree(session->sasl_mechanism_list); + session->sasl_mechanism_list = 0; + } + if (session->sasl_client) { + if (msg_verbose) + msg_info("disposing SASL state information"); + xsasl_client_free(session->sasl_client); + session->sasl_client = 0; + } + if (session->sasl_reply) { + vstring_free(session->sasl_reply); + session->sasl_reply = 0; + } +} + +/* smtp_sasl_passivate - append serialized SASL attributes */ + +void smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf) +{ +} + +/* smtp_sasl_activate - de-serialize SASL attributes */ + +int smtp_sasl_activate(SMTP_SESSION *session, char *buf) +{ + return (0); +} + +#endif diff --git a/src/smtp/smtp_sasl_proto.c b/src/smtp/smtp_sasl_proto.c new file mode 100644 index 0000000..6a51696 --- /dev/null +++ b/src/smtp/smtp_sasl_proto.c @@ -0,0 +1,202 @@ +/*++ +/* NAME +/* smtp_sasl_proto 3 +/* SUMMARY +/* Postfix SASL interface for SMTP client +/* SYNOPSIS +/* #include smtp_sasl.h +/* +/* void smtp_sasl_helo_auth(state, words) +/* SMTP_STATE *state; +/* const char *words; +/* +/* int smtp_sasl_helo_login(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* This module contains random chunks of code that implement +/* the SMTP protocol interface for SASL negotiation. The goal +/* is to reduce clutter in the main SMTP client source code. +/* +/* smtp_sasl_helo_auth() processes the AUTH option in the +/* SMTP server's EHLO response. +/* +/* smtp_sasl_helo_login() authenticates the SMTP client to the +/* SMTP server, using the authentication mechanism information +/* given by the server. The result is a Postfix delivery status +/* code in case of trouble. +/* +/* Arguments: +/* .IP state +/* Session context. +/* .IP words +/* List of SASL authentication mechanisms (separated by blanks) +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + +#ifdef USE_SASL_AUTH + +/* smtp_sasl_compat_mechs - Trim server's mechanism list */ + +static const char *smtp_sasl_compat_mechs(const char *words) +{ + static VSTRING *buf; + char *mech_list; + char *save_mech; + char *mech; + + /* + * Use server's mechanisms if no filter specified + */ + if (smtp_sasl_mechs == 0 || *words == 0) + return (words); + + if (buf == 0) + buf = vstring_alloc(10); + + VSTRING_RESET(buf); + VSTRING_TERMINATE(buf); + + save_mech = mech_list = mystrdup(words); + + while ((mech = mystrtok(&mech_list, " \t")) != 0) { + if (string_list_match(smtp_sasl_mechs, mech)) { + if (VSTRING_LEN(buf) > 0) + VSTRING_ADDCH(buf, ' '); + vstring_strcat(buf, mech); + } else if (smtp_sasl_mechs->error) { + msg_fatal("SASL mechanism filter failed for: '%s'", mech); + } + } + myfree(save_mech); + + return (vstring_str(buf)); +} + +/* smtp_sasl_helo_auth - handle AUTH option in EHLO reply */ + +void smtp_sasl_helo_auth(SMTP_SESSION *session, const char *words) +{ + const char *mech_list = smtp_sasl_compat_mechs(words); + char *junk; + + /* + * XXX If the server offers no compatible authentication mechanisms, then + * pretend that the server doesn't support SASL authentication. + * + * XXX If the server offers multiple different lists, concatenate them. Let + * the SASL library worry about duplicates. + */ + if (session->sasl_mechanism_list) { + if (strcasecmp(session->sasl_mechanism_list, mech_list) != 0 + && strlen(mech_list) > 0 + && strlen(session->sasl_mechanism_list) < var_line_limit) { + junk = concatenate(session->sasl_mechanism_list, " ", mech_list, + (char *) 0); + myfree(session->sasl_mechanism_list); + session->sasl_mechanism_list = junk; + } + return; + } + if (strlen(mech_list) > 0) { + session->sasl_mechanism_list = mystrdup(mech_list); + } else { + msg_warn(*words ? "%s offered no supported AUTH mechanisms: '%s'" : + "%s offered null AUTH mechanism list%s", + session->namaddrport, words); + } + session->features |= SMTP_FEATURE_AUTH; +} + +/* smtp_sasl_helo_login - perform SASL login */ + +int smtp_sasl_helo_login(SMTP_STATE *state) +{ + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + int ret; + + /* + * Skip authentication when no authentication info exists for this + * server, so that we talk to each other like strangers. + */ + if (smtp_sasl_passwd_lookup(session) == 0) { + session->features &= ~SMTP_FEATURE_AUTH; + return 0; + } + + /* + * Otherwise, if authentication information exists, assume that + * authentication is required, and assume that an authentication error is + * recoverable from the message delivery point of view. An authentication + * error is unrecoverable from a session point of view - the session will + * not be reused. + */ + ret = 0; + if (session->sasl_mechanism_list == 0) { + dsb_simple(why, "4.7.0", "SASL authentication failed: " + "server %s offered no compatible authentication mechanisms for this type of connection security", + session->namaddr); + ret = smtp_sess_fail(state); + /* Session reuse is disabled. */ + } else { +#ifndef USE_TLS + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS), var_smtp_sasl_opts); +#else + if (session->tls_context == 0) + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS), + var_smtp_sasl_opts); + else if (TLS_CERT_IS_MATCHED(session->tls_context)) + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLSV_OPTS), + var_smtp_sasl_tlsv_opts); + else + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLS_OPTS), + var_smtp_sasl_tls_opts); +#endif + if (smtp_sasl_authenticate(session, why) <= 0) { + ret = smtp_sess_fail(state); + /* Session reuse is disabled. */ + } + } + return (ret); +} + +#endif diff --git a/src/smtp/smtp_session.c b/src/smtp/smtp_session.c new file mode 100644 index 0000000..1b3a20e --- /dev/null +++ b/src/smtp/smtp_session.c @@ -0,0 +1,446 @@ +/*++ +/* NAME +/* smtp_session 3 +/* SUMMARY +/* SMTP_SESSION structure management +/* SYNOPSIS +/* #include "smtp.h" +/* +/* SMTP_SESSION *smtp_session_alloc(stream, iter, start, flags) +/* VSTREAM *stream; +/* SMTP_ITERATOR *iter; +/* time_t start; +/* int flags; +/* +/* void smtp_session_free(session) +/* SMTP_SESSION *session; +/* +/* int smtp_session_passivate(session, dest_prop, endp_prop) +/* SMTP_SESSION *session; +/* VSTRING *dest_prop; +/* VSTRING *endp_prop; +/* +/* SMTP_SESSION *smtp_session_activate(fd, iter, dest_prop, endp_prop) +/* int fd; +/* SMTP_ITERATOR *iter; +/* VSTRING *dest_prop; +/* VSTRING *endp_prop; +/* DESCRIPTION +/* smtp_session_alloc() allocates memory for an SMTP_SESSION structure +/* and initializes it with the given stream and destination, host name +/* and address information. The host name and address strings are +/* copied. The port is in network byte order. +/* +/* smtp_session_free() destroys an SMTP_SESSION structure and its +/* members, making memory available for reuse. It will handle the +/* case of a null stream and will assume it was given a different +/* purpose. +/* +/* smtp_session_passivate() flattens an SMTP session (including +/* TLS context) so that it can be cached. The SMTP_SESSION +/* structure is destroyed. +/* +/* smtp_session_activate() inflates a flattened SMTP session +/* so that it can be used. The input property arguments are +/* modified. +/* +/* Arguments: +/* .IP stream +/* A full-duplex stream. +/* .IP iter +/* The literal next-hop or fall-back destination including +/* the optional [] and including the :port or :service; +/* the name of the remote host; +/* the printable address of the remote host; +/* the remote port in network byte order. +/* .IP start +/* The time when this connection was opened. +/* .IP flags +/* Zero or more of the following: +/* .RS +/* .IP SMTP_MISC_FLAG_CONN_LOAD +/* Enable re-use of cached SMTP or LMTP connections. +/* .IP SMTP_MISC_FLAG_CONN_STORE +/* Enable saving of cached SMTP or LMTP connections. +/* .RE +/* SMTP_MISC_FLAG_CONN_MASK corresponds with both _LOAD and _STORE. +/* .IP dest_prop +/* Destination specific session properties: the server is the +/* best MX host for the current logical destination, the dest, +/* host, and addr properties. When dest_prop is non-empty, it +/* overrides the iterator dest, host, and addr properties. It +/* is the caller's responsibility to save the current nexthop +/* with SMTP_ITER_SAVE_DEST() and to restore it afterwards +/* with SMTP_ITER_RESTORE_DEST() before trying alternatives. +/* .IP endp_prop +/* Endpoint specific session properties: all the features +/* advertised by the remote server. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Viktor Dukhovni +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <string.h> +#include <netinet/in.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <stringops.h> + +/* Global library. */ + +#include <mime_state.h> +#include <debug_peer.h> +#include <mail_params.h> + +/* TLS Library. */ + +#include <tls_proxy.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + + /* + * Local, because these are meaningful only for code in this file. + */ +#define SESS_ATTR_DEST "destination" +#define SESS_ATTR_HOST "host_name" +#define SESS_ATTR_ADDR "host_addr" +#define SESS_ATTR_DEST_FEATURES "destination_features" + +#define SESS_ATTR_TLS_LEVEL "tls_level" +#define SESS_ATTR_REUSE_COUNT "reuse_count" +#define SESS_ATTR_ENDP_FEATURES "endpoint_features" +#define SESS_ATTR_EXPIRE_TIME "expire_time" + +/* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */ + +SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, SMTP_ITERATOR *iter, + time_t start, int flags) +{ + SMTP_SESSION *session; + const char *host = STR(iter->host); + const char *addr = STR(iter->addr); + unsigned port = iter->port; + + session = (SMTP_SESSION *) mymalloc(sizeof(*session)); + session->stream = stream; + session->iterator = iter; + session->namaddr = concatenate(host, "[", addr, "]", (char *) 0); + session->helo = 0; + session->port = port; + session->features = 0; + + session->size_limit = 0; + session->error_mask = 0; + session->buffer = vstring_alloc(100); + session->scratch = vstring_alloc(100); + session->scratch2 = vstring_alloc(100); + smtp_chat_init(session); + session->mime_state = 0; + + if (session->port) { + vstring_sprintf(session->buffer, "%s:%d", + session->namaddr, ntohs(session->port)); + session->namaddrport = mystrdup(STR(session->buffer)); + } else + session->namaddrport = mystrdup(session->namaddr); + + session->send_proto_helo = 0; + + if (flags & SMTP_MISC_FLAG_CONN_STORE) + CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time); + else + DONT_CACHE_THIS_SESSION; + session->reuse_count = 0; + USE_NEWBORN_SESSION; /* He's not dead Jim! */ + +#ifdef USE_SASL_AUTH + smtp_sasl_connect(session); +#endif + +#ifdef USE_TLS + session->tls_context = 0; + session->tls_retry_plain = 0; + session->tls_nexthop = 0; +#endif + session->state = 0; + debug_peer_check(host, addr); + return (session); +} + +/* smtp_session_free - destroy SMTP_SESSION structure and contents */ + +void smtp_session_free(SMTP_SESSION *session) +{ +#ifdef USE_TLS + if (session->stream) { + vstream_fflush(session->stream); + } + if (session->tls_context) { + if (session->features & + (SMTP_FEATURE_FROM_CACHE | SMTP_FEATURE_FROM_PROXY)) + tls_proxy_context_free(session->tls_context); + else + tls_client_stop(smtp_tls_ctx, session->stream, + var_smtp_starttls_tmout, 0, session->tls_context); + } +#endif + if (session->stream) + vstream_fclose(session->stream); + myfree(session->namaddr); + myfree(session->namaddrport); + if (session->helo) + myfree(session->helo); + + vstring_free(session->buffer); + vstring_free(session->scratch); + vstring_free(session->scratch2); + + if (session->history) + smtp_chat_reset(session); + if (session->mime_state) + mime_state_free(session->mime_state); + +#ifdef USE_SASL_AUTH + smtp_sasl_cleanup(session); +#endif + + debug_peer_restore(); + myfree((void *) session); +} + +/* smtp_session_passivate - passivate an SMTP_SESSION object */ + +int smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop, + VSTRING *endp_prop) +{ + SMTP_ITERATOR *iter = session->iterator; + VSTREAM *mp; + int fd; + + /* + * Encode the delivery request next-hop to endpoint binding properties: + * whether or not this server is best MX host for the delivery request + * next-hop or fall-back logical destination (this information is needed + * for loop handling in smtp_proto()). + * + * TODO: save SASL username and password information so that we can + * correctly save a reused authenticated connection. + * + * These memory writes should never fail. + */ + if ((mp = vstream_memopen(dest_prop, O_WRONLY)) == 0 + || attr_print_plain(mp, ATTR_FLAG_NONE, + SEND_ATTR_STR(SESS_ATTR_DEST, STR(iter->dest)), + SEND_ATTR_STR(SESS_ATTR_HOST, STR(iter->host)), + SEND_ATTR_STR(SESS_ATTR_ADDR, STR(iter->addr)), + SEND_ATTR_INT(SESS_ATTR_DEST_FEATURES, + session->features & SMTP_FEATURE_DESTINATION_MASK), + ATTR_TYPE_END) != 0 + || vstream_fclose(mp) != 0) + msg_fatal("smtp_session_passivate: can't save dest properties: %m"); + + /* + * Encode the physical endpoint properties: all the session properties + * except for "session from cache", "best MX", or "RSET failure". Plus + * the TLS level, reuse count, and connection expiration time. + * + * XXX Should also record how many non-delivering mail transactions there + * were during this session, and perhaps other statistics, so that we + * don't reuse a session too much. + * + * TODO: passivate SASL username and password information so that we can + * correctly save a reused authenticated connection. + * + * These memory writes should never fail. + */ + if ((mp = vstream_memopen(endp_prop, O_WRONLY)) == 0 + || attr_print_plain(mp, ATTR_FLAG_NONE, +#ifdef USE_TLS + SEND_ATTR_INT(SESS_ATTR_TLS_LEVEL, + session->state->tls->level), +#endif + SEND_ATTR_INT(SESS_ATTR_REUSE_COUNT, + session->reuse_count), + SEND_ATTR_INT(SESS_ATTR_ENDP_FEATURES, + session->features & SMTP_FEATURE_ENDPOINT_MASK), + SEND_ATTR_LONG(SESS_ATTR_EXPIRE_TIME, + (long) session->expire_time), + ATTR_TYPE_END) != 0 + + /* + * Append the passivated TLS context. These memory writes should never + * fail. + */ +#ifdef USE_TLS + || (session->tls_context + && attr_print_plain(mp, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_context_print, + (void *) session->tls_context), + ATTR_TYPE_END) != 0) +#endif + || vstream_fclose(mp) != 0) + msg_fatal("smtp_session_passivate: cannot save TLS context: %m"); + + /* + * Salvage the underlying file descriptor, and destroy the session + * object. + */ + fd = vstream_fileno(session->stream); + vstream_fdclose(session->stream); + session->stream = 0; + smtp_session_free(session); + + return (fd); +} + +/* smtp_session_activate - re-activate a passivated SMTP_SESSION object */ + +SMTP_SESSION *smtp_session_activate(int fd, SMTP_ITERATOR *iter, + VSTRING *dest_prop, + VSTRING *endp_prop) +{ + const char *myname = "smtp_session_activate"; + VSTREAM *mp; + SMTP_SESSION *session; + int endp_features; /* server features */ + int dest_features; /* server features */ + long expire_time; /* session re-use expiration time */ + int reuse_count; /* # times reused */ + +#ifdef USE_TLS + TLS_SESS_STATE *tls_context = 0; + SMTP_TLS_POLICY *tls = iter->parent->tls; + +#define TLS_PROXY_CONTEXT_FREE() do { \ + if (tls_context) \ + tls_proxy_context_free(tls_context); \ + } while (0) +#else +#define TLS_PROXY_CONTEXT_FREE() /* nothing */ +#endif + +#define SMTP_SESSION_ACTIVATE_ERR_RETURN() do { \ + TLS_PROXY_CONTEXT_FREE(); \ + return (0); \ + } while (0) + + /* + * Sanity check: if TLS is required, the cached properties must contain a + * TLS context. + */ + if ((mp = vstream_memopen(endp_prop, O_RDONLY)) == 0 + || attr_scan_plain(mp, ATTR_FLAG_NONE, +#ifdef USE_TLS + RECV_ATTR_INT(SESS_ATTR_TLS_LEVEL, + &tls->level), +#endif + RECV_ATTR_INT(SESS_ATTR_REUSE_COUNT, + &reuse_count), + RECV_ATTR_INT(SESS_ATTR_ENDP_FEATURES, + &endp_features), + RECV_ATTR_LONG(SESS_ATTR_EXPIRE_TIME, + &expire_time), + ATTR_TYPE_END) != 4 +#ifdef USE_TLS + || ((tls->level > TLS_LEV_MAY + || (tls->level == TLS_LEV_MAY && vstream_peek(mp) > 0)) + && attr_scan_plain(mp, ATTR_FLAG_NONE, + RECV_ATTR_FUNC(tls_proxy_context_scan, + (void *) &tls_context), + ATTR_TYPE_END) != 1) +#endif + || vstream_fclose(mp) != 0) { + msg_warn("smtp_session_activate: bad cached endp properties"); + SMTP_SESSION_ACTIVATE_ERR_RETURN(); + } + + /* + * Clobber the iterator's current nexthop, host and address fields with + * cached-connection information. This is done when a session is looked + * up by delivery request nexthop instead of address and port. It is the + * caller's responsibility to save and restore the delivery request + * nexthop with SMTP_ITER_SAVE_DEST() and SMTP_ITER_RESTORE_DEST(). + * + * TODO: Eliminate the duplication between SMTP_ITERATOR and SMTP_SESSION. + * + * TODO: restore SASL username and password information so that we can + * correctly save a reused authenticated connection. + */ + if (dest_prop && VSTRING_LEN(dest_prop)) { + if ((mp = vstream_memopen(dest_prop, O_RDONLY)) == 0 + || attr_scan_plain(mp, ATTR_FLAG_NONE, + RECV_ATTR_STR(SESS_ATTR_DEST, iter->dest), + RECV_ATTR_STR(SESS_ATTR_HOST, iter->host), + RECV_ATTR_STR(SESS_ATTR_ADDR, iter->addr), + RECV_ATTR_INT(SESS_ATTR_DEST_FEATURES, + &dest_features), + ATTR_TYPE_END) != 4 + || vstream_fclose(mp) != 0) { + msg_warn("smtp_session_passivate: bad cached dest properties"); + SMTP_SESSION_ACTIVATE_ERR_RETURN(); + } + } else { + dest_features = 0; + } +#ifdef USE_TLS + if (msg_verbose) + msg_info("%s: tls_level=%d", myname, tls->level); +#endif + + /* + * Allright, bundle up what we have sofar. + */ +#define NO_FLAGS 0 + + session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), iter, + (time_t) 0, NO_FLAGS); + session->features = + (endp_features | dest_features | SMTP_FEATURE_FROM_CACHE); +#ifdef USE_TLS + session->tls_context = tls_context; +#endif + CACHE_THIS_SESSION_UNTIL(expire_time); + session->reuse_count = ++reuse_count; + + if (msg_verbose) + msg_info("%s: dest=%s host=%s addr=%s port=%u features=0x%x, " + "ttl=%ld, reuse=%d", + myname, STR(iter->dest), STR(iter->host), + STR(iter->addr), ntohs(iter->port), + endp_features | dest_features, + (long) (expire_time - time((time_t *) 0)), + reuse_count); + +#if USE_TLS + if (tls_context) + tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_USED, + session->tls_context); +#endif + + return (session); +} diff --git a/src/smtp/smtp_state.c b/src/smtp/smtp_state.c new file mode 100644 index 0000000..f91ab8c --- /dev/null +++ b/src/smtp/smtp_state.c @@ -0,0 +1,114 @@ +/*++ +/* NAME +/* smtp_state 3 +/* SUMMARY +/* initialize/cleanup shared state +/* SYNOPSIS +/* #include "smtp.h" +/* +/* SMTP_STATE *smtp_state_alloc() +/* +/* void smtp_state_free(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* smtp_state_init() initializes the shared state, and allocates +/* memory for buffers etc. +/* +/* smtp_cleanup() destroys memory allocated by smtp_state_init(). +/* STANDARDS +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <vstring.h> +#include <msg.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + +/* smtp_state_alloc - initialize */ + +SMTP_STATE *smtp_state_alloc(void) +{ + SMTP_STATE *state = (SMTP_STATE *) mymalloc(sizeof(*state)); + + state->misc_flags = 0; + state->src = 0; + state->service = 0; + state->request = 0; + state->session = 0; + state->status = 0; + state->space_left = 0; + state->iterator->request_nexthop = vstring_alloc(100); + state->iterator->dest = vstring_alloc(100); + state->iterator->host = vstring_alloc(100); + state->iterator->addr = vstring_alloc(100); + state->iterator->saved_dest = vstring_alloc(100); + if (var_smtp_cache_conn) { + state->dest_label = vstring_alloc(10); + state->dest_prop = vstring_alloc(10); + state->endp_label = vstring_alloc(10); + state->endp_prop = vstring_alloc(10); + state->cache_used = htable_create(1); + } else { + state->dest_label = 0; + state->dest_prop = 0; + state->endp_label = 0; + state->endp_prop = 0; + state->cache_used = 0; + } + state->why = dsb_create(); + return (state); +} + +/* smtp_state_free - destroy state */ + +void smtp_state_free(SMTP_STATE *state) +{ +#ifdef USE_TLS + /* The TLS policy cache lifetime is one delivery. */ + smtp_tls_policy_cache_flush(); +#endif + vstring_free(state->iterator->request_nexthop); + vstring_free(state->iterator->dest); + vstring_free(state->iterator->host); + vstring_free(state->iterator->addr); + vstring_free(state->iterator->saved_dest); + if (state->dest_label) + vstring_free(state->dest_label); + if (state->dest_prop) + vstring_free(state->dest_prop); + if (state->endp_label) + vstring_free(state->endp_label); + if (state->endp_prop) + vstring_free(state->endp_prop); + if (state->cache_used) + htable_free(state->cache_used, (void (*) (void *)) 0); + if (state->why) + dsb_free(state->why); + + myfree((void *) state); +} diff --git a/src/smtp/smtp_tls_policy.c b/src/smtp/smtp_tls_policy.c new file mode 100644 index 0000000..4b394a9 --- /dev/null +++ b/src/smtp/smtp_tls_policy.c @@ -0,0 +1,928 @@ +/*++ +/* NAME +/* smtp_tls_policy 3 +/* SUMMARY +/* SMTP_TLS_POLICY structure management +/* SYNOPSIS +/* #include "smtp.h" +/* +/* void smtp_tls_list_init() +/* +/* int smtp_tls_policy_cache_query(why, tls, iter) +/* DSN_BUF *why; +/* SMTP_TLS_POLICY *tls; +/* SMTP_ITERATOR *iter; +/* +/* void smtp_tls_policy_dummy(tls) +/* SMTP_TLS_POLICY *tls; +/* +/* void smtp_tls_policy_cache_flush() +/* DESCRIPTION +/* smtp_tls_list_init() initializes lookup tables used by the TLS +/* policy engine. +/* +/* smtp_tls_policy_cache_query() returns a shallow copy of the +/* cached SMTP_TLS_POLICY structure for the iterator's +/* destination, host, port and DNSSEC validation status. +/* This copy is guaranteed to be valid until the next +/* smtp_tls_policy_cache_query() or smtp_tls_policy_cache_flush() +/* call. The caller can override the TLS security level without +/* corrupting the policy cache. +/* When any required table or DNS lookups fail, the TLS level +/* is set to TLS_LEV_INVALID, the "why" argument is updated +/* with the error reason and the result value is zero (false). +/* +/* smtp_tls_policy_dummy() initializes a trivial, non-cached, +/* policy with TLS disabled. +/* +/* smtp_tls_policy_cache_flush() destroys the TLS policy cache +/* and contents. +/* +/* Arguments: +/* .IP why +/* A pointer to a DSN_BUF which holds error status information when +/* the TLS policy lookup fails. +/* .IP tls +/* Pointer to TLS policy storage. +/* .IP iter +/* The literal next-hop or fall-back destination including +/* the optional [] and including the :port or :service; +/* the name of the remote host after MX and CNAME expansions +/* (see smtp_cname_overrides_servername for the handling +/* of hostnames that resolve to a CNAME record); +/* the printable address of the remote host; +/* the remote port in network byte order; +/* the DNSSEC validation status of the host name lookup after +/* MX and CNAME expansions. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Viktor Dukhovni +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +#ifdef USE_TLS + +#include <netinet/in.h> /* ntohs() for Solaris or BSD */ +#include <arpa/inet.h> /* ntohs() for Linux or BSD */ +#include <stdlib.h> +#include <string.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <stringops.h> +#include <valid_hostname.h> +#include <valid_utf8_hostname.h> +#include <ctable.h> + +/* Global library. */ + +#include <mail_params.h> +#include <maps.h> +#include <dsn_buf.h> + +/* DNS library. */ + +#include <dns.h> + +/* Application-specific. */ + +#include "smtp.h" + +/* XXX Cache size should scale with [sl]mtp_mx_address_limit. */ +#define CACHE_SIZE 20 +static CTABLE *policy_cache; + +static int global_tls_level(void); +static void dane_init(SMTP_TLS_POLICY *, SMTP_ITERATOR *); + +static MAPS *tls_policy; /* lookup table(s) */ +static MAPS *tls_per_site; /* lookup table(s) */ + +/* smtp_tls_list_init - initialize per-site policy lists */ + +void smtp_tls_list_init(void) +{ + if (*var_smtp_tls_policy) { + tls_policy = maps_create(VAR_LMTP_SMTP(TLS_POLICY), + var_smtp_tls_policy, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if (*var_smtp_tls_per_site) + msg_warn("%s ignored when %s is not empty.", + VAR_LMTP_SMTP(TLS_PER_SITE), VAR_LMTP_SMTP(TLS_POLICY)); + return; + } + if (*var_smtp_tls_per_site) { + tls_per_site = maps_create(VAR_LMTP_SMTP(TLS_PER_SITE), + var_smtp_tls_per_site, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + } +} + +/* policy_name - printable tls policy level */ + +static const char *policy_name(int tls_level) +{ + const char *name = str_tls_level(tls_level); + + if (name == 0) + name = "unknown"; + return name; +} + +#define MARK_INVALID(why, levelp) do { \ + dsb_simple((why), "4.7.5", "client TLS configuration problem"); \ + *(levelp) = TLS_LEV_INVALID; } while (0) + +/* tls_site_lookup - look up per-site TLS security level */ + +static void tls_site_lookup(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, const char *site_class) +{ + const char *lookup; + + /* + * Look up a non-default policy. In case of multiple lookup results, the + * precedence order is a permutation of the TLS enforcement level order: + * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more + * specific policy including NONE, otherwise we choose the stronger + * enforcement level. + */ + if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) { + if (!strcasecmp(lookup, "NONE")) { + /* NONE overrides MAY or NOTFOUND. */ + if (*site_level <= TLS_LEV_MAY) + *site_level = TLS_LEV_NONE; + } else if (!strcasecmp(lookup, "MAY")) { + /* MAY overrides NOTFOUND but not NONE. */ + if (*site_level < TLS_LEV_NONE) + *site_level = TLS_LEV_MAY; + } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) { + if (*site_level < TLS_LEV_ENCRYPT) + *site_level = TLS_LEV_ENCRYPT; + } else if (!strcasecmp(lookup, "MUST")) { + if (*site_level < TLS_LEV_VERIFY) + *site_level = TLS_LEV_VERIFY; + } else { + msg_warn("%s: unknown TLS policy '%s' for %s %s", + tls_per_site->title, lookup, site_class, site_name); + MARK_INVALID(tls->why, site_level); + return; + } + } else if (tls_per_site->error) { + msg_warn("%s: %s \"%s\": per-site table lookup error", + tls_per_site->title, site_class, site_name); + dsb_simple(tls->why, "4.3.0", "Temporary lookup error"); + *site_level = TLS_LEV_INVALID; + return; + } + return; +} + +/* tls_policy_lookup_one - look up destination TLS policy */ + +static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, + const char *site_class) +{ + const char *lookup; + char *policy; + char *saved_policy; + char *tok; + const char *err; + char *name; + char *val; + static VSTRING *cbuf; + +#undef FREE_RETURN +#define FREE_RETURN do { myfree(saved_policy); return; } while (0) + +#define INVALID_RETURN(why, levelp) do { \ + MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0) + +#define WHERE \ + STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \ + tls_policy->title, site_class, site_name)) + + if (cbuf == 0) + cbuf = vstring_alloc(10); + + if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { + if (tls_policy->error) { + msg_warn("%s: policy table lookup error", WHERE); + MARK_INVALID(tls->why, site_level); + } + return; + } + saved_policy = policy = mystrdup(lookup); + + if ((tok = mystrtok(&policy, CHARS_COMMA_SP)) == 0) { + msg_warn("%s: invalid empty policy", WHERE); + INVALID_RETURN(tls->why, site_level); + } + *site_level = tls_level_lookup(tok); + if (*site_level == TLS_LEV_INVALID) { + /* tls_level_lookup() logs no warning. */ + msg_warn("%s: invalid security level \"%s\"", WHERE, tok); + INVALID_RETURN(tls->why, site_level); + } + + /* + * Warn about ignored attributes when TLS is disabled. + */ + if (*site_level < TLS_LEV_MAY) { + while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0) + msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", + WHERE, tok); + FREE_RETURN; + } + + /* + * Errors in attributes may have security consequences, don't ignore + * errors that can degrade security. + */ + while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0) { + if ((err = split_nameval(tok, &name, &val)) != 0) { + msg_warn("%s: malformed attribute/value pair \"%s\": %s", + WHERE, tok, err); + INVALID_RETURN(tls->why, site_level); + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "ciphers")) { + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (tls->grade) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + tls->grade = mystrdup(val); + continue; + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "protocols")) { + if (tls->protocols) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + tls->protocols = mystrdup(val); + continue; + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "servername")) { + if (tls->sni) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (valid_hostname(val, DONT_GRIPE)) + tls->sni = mystrdup(val); + else { + msg_warn("%s: \"%s=%s\" specifies an invalid hostname", + WHERE, name, val); + INVALID_RETURN(tls->why, site_level); + } + continue; + } + /* Multiple instances per policy. */ + if (!strcasecmp(name, "match")) { + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + switch (*site_level) { + default: + msg_warn("%s: attribute \"%s\" invalid at security level " + "\"%s\"", WHERE, name, policy_name(*site_level)); + INVALID_RETURN(tls->why, site_level); + break; + case TLS_LEV_FPRINT: + if (!tls->dane) + tls->dane = tls_dane_alloc(); + tls_dane_add_ee_digests(tls->dane, + var_smtp_tls_fpt_dgst, val, "|"); + break; + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->matchargv == 0) + tls->matchargv = argv_split(val, ":"); + else + argv_split_append(tls->matchargv, val, ":"); + break; + } + continue; + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "exclude")) { + if (tls->exclusions) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + tls->exclusions = vstring_strcpy(vstring_alloc(10), val); + continue; + } + /* Multiple instances per policy. */ + if (!strcasecmp(name, "tafile")) { + /* Only makes sense if we're using CA-based trust */ + if (!TLS_MUST_PKIX(*site_level)) { + msg_warn("%s: attribute \"%s\" invalid at security level" + " \"%s\"", WHERE, name, policy_name(*site_level)); + INVALID_RETURN(tls->why, site_level); + } + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (!tls->dane) + tls->dane = tls_dane_alloc(); + if (!tls_dane_load_trustfile(tls->dane, val)) { + INVALID_RETURN(tls->why, site_level); + } + continue; + } + /* Last one wins. */ + if (!strcasecmp(name, "connection_reuse")) { + if (strcasecmp(val, "yes") == 0) { + tls->conn_reuse = 1; + } else if (strcasecmp(val, "no") == 0) { + tls->conn_reuse = 0; + } else { + msg_warn("%s: attribute \"%s\" has bad value: \"%s\"", + WHERE, name, val); + INVALID_RETURN(tls->why, site_level); + } + continue; + } + msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + + FREE_RETURN; +} + +/* tls_policy_lookup - look up destination TLS policy */ + +static void tls_policy_lookup(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, + const char *site_class) +{ + + /* + * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These + * are never the domain part of localpart@domain, rather they are + * explicit nexthops from transport:nexthop, and match only the + * corresponding policy. Parent domain matching (below) applies only to + * sub-domains of the recipient domain. + * + * XXX UNIX-domain connections query with the pathname as destination. + */ + if (!valid_utf8_hostname(var_smtputf8_enable, site_name, DONT_GRIPE)) { + tls_policy_lookup_one(tls, site_level, site_name, site_class); + return; + } + do { + tls_policy_lookup_one(tls, site_level, site_name, site_class); + } while (*site_level == TLS_LEV_NOTFOUND + && (site_name = strchr(site_name + 1, '.')) != 0); +} + +/* load_tas - load one or more ta files */ + +static int load_tas(TLS_DANE *dane, const char *files) +{ + int ret = 0; + char *save = mystrdup(files); + char *buf = save; + char *file; + + do { + if ((file = mystrtok(&buf, CHARS_COMMA_SP)) != 0) + ret = tls_dane_load_trustfile(dane, file); + } while (file && ret); + + myfree(save); + return (ret); +} + +/* set_cipher_grade - Set cipher grade and exclusions */ + +static void set_cipher_grade(SMTP_TLS_POLICY *tls) +{ + const char *mand_exclude = ""; + const char *also_exclude = ""; + + /* + * Use main.cf cipher level if no per-destination value specified. With + * mandatory encryption at least encrypt, and with mandatory verification + * at least authenticate! + */ + switch (tls->level) { + case TLS_LEV_INVALID: + case TLS_LEV_NONE: + return; + + case TLS_LEV_MAY: + if (tls->grade == 0) + tls->grade = mystrdup(var_smtp_tls_ciph); + break; + + case TLS_LEV_ENCRYPT: + if (tls->grade == 0) + tls->grade = mystrdup(var_smtp_tls_mand_ciph); + mand_exclude = var_smtp_tls_mand_excl; + also_exclude = "eNULL"; + break; + + case TLS_LEV_HALF_DANE: + case TLS_LEV_DANE: + case TLS_LEV_DANE_ONLY: + case TLS_LEV_FPRINT: + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->grade == 0) + tls->grade = mystrdup(var_smtp_tls_mand_ciph); + mand_exclude = var_smtp_tls_mand_excl; + also_exclude = "aNULL"; + break; + } + +#define ADD_EXCLUDE(vstr, str) \ + do { \ + if (*(str)) \ + vstring_sprintf_append((vstr), "%s%s", \ + VSTRING_LEN(vstr) ? " " : "", (str)); \ + } while (0) + + /* + * The "exclude" policy table attribute overrides main.cf exclusion + * lists. + */ + if (tls->exclusions == 0) { + tls->exclusions = vstring_alloc(10); + ADD_EXCLUDE(tls->exclusions, var_smtp_tls_excl_ciph); + ADD_EXCLUDE(tls->exclusions, mand_exclude); + } + ADD_EXCLUDE(tls->exclusions, also_exclude); +} + +/* policy_create - create SMTP TLS policy cache object (ctable call-back) */ + +static void *policy_create(const char *unused_key, void *context) +{ + SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context; + int site_level; + const char *dest = STR(iter->dest); + const char *host = STR(iter->host); + + /* + * Prepare a pristine policy object. + */ + SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof(*tls)); + + smtp_tls_policy_init(tls, dsb_create()); + tls->conn_reuse = var_smtp_tls_conn_reuse; + + /* + * Compute the per-site TLS enforcement level. For compatibility with the + * original TLS patch, this algorithm is gives equal precedence to host + * and next-hop policies. + */ + tls->level = global_tls_level(); + site_level = TLS_LEV_NOTFOUND; + + if (tls_policy) { + tls_policy_lookup(tls, &site_level, dest, "next-hop destination"); + } else if (tls_per_site) { + tls_site_lookup(tls, &site_level, dest, "next-hop destination"); + if (site_level != TLS_LEV_INVALID + && strcasecmp_utf8(dest, host) != 0) + tls_site_lookup(tls, &site_level, host, "server hostname"); + + /* + * Override a wild-card per-site policy with a more specific global + * policy. + * + * With the original TLS patch, 1) a per-site ENCRYPT could not override + * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy + * produced inconsistent results: it changed a global VERIFY into + * NONE, while producing MAY with all weaker global policy settings. + * + * With the current implementation, a combined per-site (NONE+MAY) + * consistently overrides global policy with NONE, and global policy + * can override only a per-site MAY wildcard. That is, specific + * policies consistently override wildcard policies, and + * (non-wildcard) per-site policies consistently override global + * policies. + */ + if (site_level == TLS_LEV_MAY && tls->level > TLS_LEV_MAY) + site_level = tls->level; + } + switch (site_level) { + default: + tls->level = site_level; + /* FALLTHROUGH */ + case TLS_LEV_NOTFOUND: + break; + case TLS_LEV_INVALID: + tls->level = site_level; + return ((void *) tls); + } + + /* + * DANE initialization may change the security level to something else, + * so do this early, so that we use the right level below. Note that + * "dane-only" changes to "dane" once we obtain the requisite TLSA + * records. + */ + if (TLS_DANE_BASED(tls->level)) + dane_init(tls, iter); + if (tls->level == TLS_LEV_INVALID) + return ((void *) tls); + + /* + * Use main.cf protocols and SNI settings if not set in per-destination + * table. + */ + if (tls->level > TLS_LEV_NONE && tls->protocols == 0) + tls->protocols = + mystrdup((tls->level == TLS_LEV_MAY) ? + var_smtp_tls_proto : var_smtp_tls_mand_proto); + if (tls->level > TLS_LEV_NONE && tls->sni == 0) { + if (!*var_smtp_tls_sni || valid_hostname(var_smtp_tls_sni, DONT_GRIPE)) + tls->sni = mystrdup(var_smtp_tls_sni); + else { + msg_warn("\"%s = %s\" specifies an invalid hostname", + VAR_LMTP_SMTP(TLS_SNI), var_smtp_tls_sni); + MARK_INVALID(tls->why, &tls->level); + return ((void *) tls); + } + } + + /* + * Compute cipher grade (if set in per-destination table, else + * set_cipher() uses main.cf settings) and security level dependent + * cipher exclusion list. + */ + set_cipher_grade(tls); + + /* + * Use main.cf cert_match setting if not set in per-destination table. + */ + switch (tls->level) { + case TLS_LEV_INVALID: + case TLS_LEV_NONE: + case TLS_LEV_MAY: + case TLS_LEV_ENCRYPT: + case TLS_LEV_HALF_DANE: + case TLS_LEV_DANE: + case TLS_LEV_DANE_ONLY: + break; + case TLS_LEV_FPRINT: + if (tls->dane == 0) + tls->dane = tls_dane_alloc(); + if (!TLS_DANE_HASEE(tls->dane)) { + tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst, + var_smtp_tls_fpt_cmatch, CHARS_COMMA_SP); + if (!TLS_DANE_HASEE(tls->dane)) { + msg_warn("nexthop domain %s: configured at fingerprint " + "security level, but with no fingerprints to match.", + dest); + MARK_INVALID(tls->why, &tls->level); + return ((void *) tls); + } + } + break; + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->matchargv == 0) + tls->matchargv = + argv_split(tls->level == TLS_LEV_VERIFY ? + var_smtp_tls_vfy_cmatch : var_smtp_tls_sec_cmatch, + CHARS_COMMA_SP ":"); + if (*var_smtp_tls_tafile) { + if (tls->dane == 0) + tls->dane = tls_dane_alloc(); + if (!TLS_DANE_HASTA(tls->dane) + && !load_tas(tls->dane, var_smtp_tls_tafile)) { + MARK_INVALID(tls->why, &tls->level); + return ((void *) tls); + } + } + break; + default: + msg_panic("unexpected TLS security level: %d", tls->level); + } + + if (msg_verbose && tls->level != global_tls_level()) + msg_info("%s TLS level: %s", "effective", policy_name(tls->level)); + + return ((void *) tls); +} + +/* policy_delete - free no longer cached policy (ctable call-back) */ + +static void policy_delete(void *item, void *unused_context) +{ + SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) item; + + if (tls->protocols) + myfree(tls->protocols); + if (tls->sni) + myfree(tls->sni); + if (tls->grade) + myfree(tls->grade); + if (tls->exclusions) + vstring_free(tls->exclusions); + if (tls->matchargv) + argv_free(tls->matchargv); + if (tls->dane) + tls_dane_free(tls->dane); + dsb_free(tls->why); + + myfree((void *) tls); +} + +/* smtp_tls_policy_cache_query - cached lookup of TLS policy */ + +int smtp_tls_policy_cache_query(DSN_BUF *why, SMTP_TLS_POLICY *tls, + SMTP_ITERATOR *iter) +{ + VSTRING *key; + + /* + * Create an empty TLS Policy cache on the fly. + */ + if (policy_cache == 0) + policy_cache = + ctable_create(CACHE_SIZE, policy_create, policy_delete, (void *) 0); + + /* + * Query the TLS Policy cache, with a search key that reflects our shared + * values that also appear in other cache and table search keys. + */ + key = vstring_alloc(100); + smtp_key_prefix(key, ":", iter, SMTP_KEY_FLAG_CUR_NEXTHOP + | SMTP_KEY_FLAG_HOSTNAME + | SMTP_KEY_FLAG_PORT); + ctable_newcontext(policy_cache, (void *) iter); + *tls = *(SMTP_TLS_POLICY *) ctable_locate(policy_cache, STR(key)); + vstring_free(key); + + /* + * Report errors. Both error and non-error results are cached. We must + * therefore copy the cached DSN buffer content to the caller's buffer. + */ + if (tls->level == TLS_LEV_INVALID) { + /* XXX Simplify this by implementing a "copy" primitive. */ + dsb_update(why, + STR(tls->why->status), STR(tls->why->action), + STR(tls->why->mtype), STR(tls->why->mname), + STR(tls->why->dtype), STR(tls->why->dtext), + "%s", STR(tls->why->reason)); + return (0); + } else { + return (1); + } +} + +/* smtp_tls_policy_cache_flush - flush TLS policy cache */ + +void smtp_tls_policy_cache_flush(void) +{ + if (policy_cache != 0) { + ctable_free(policy_cache); + policy_cache = 0; + } +} + +/* global_tls_level - parse and cache var_smtp_tls_level */ + +static int global_tls_level(void) +{ + static int l = TLS_LEV_NOTFOUND; + + if (l != TLS_LEV_NOTFOUND) + return l; + + /* + * Compute the global TLS policy. This is the default policy level when + * no per-site policy exists. It also is used to override a wild-card + * per-site policy. + * + * We require that the global level is valid on startup. + */ + if (*var_smtp_tls_level) { + if ((l = tls_level_lookup(var_smtp_tls_level)) == TLS_LEV_INVALID) + msg_fatal("invalid tls security level: \"%s\"", var_smtp_tls_level); + } else if (var_smtp_enforce_tls) + l = var_smtp_tls_enforce_peername ? TLS_LEV_VERIFY : TLS_LEV_ENCRYPT; + else + l = var_smtp_use_tls ? TLS_LEV_MAY : TLS_LEV_NONE; + + if (msg_verbose) + msg_info("%s TLS level: %s", "global", policy_name(l)); + + return l; +} + +#define NONDANE_CONFIG 0 /* Administrator's fault */ +#define NONDANE_DEST 1 /* Remote server's fault */ +#define DANE_CANTAUTH 2 /* Remote server's fault */ + +static void PRINTFLIKE(4, 5) dane_incompat(SMTP_TLS_POLICY *tls, + SMTP_ITERATOR *iter, + int errtype, + const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + if (tls->level == TLS_LEV_DANE) { + tls->level = (errtype == DANE_CANTAUTH) ? TLS_LEV_ENCRYPT : TLS_LEV_MAY; + if (errtype == NONDANE_CONFIG) + vmsg_warn(fmt, ap); + else if (msg_verbose) + vmsg_info(fmt, ap); + } else { /* dane-only */ + if (errtype == NONDANE_CONFIG) { + vmsg_warn(fmt, ap); + MARK_INVALID(tls->why, &tls->level); + } else { + tls->level = TLS_LEV_INVALID; + vdsb_simple(tls->why, "4.7.5", fmt, ap); + } + } + va_end(ap); +} + +/* dane_init - special initialization for "dane" security level */ + +static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter) +{ + TLS_DANE *dane; + + if (!iter->port) { + msg_warn("%s: the \"dane\" security level is invalid for delivery via" + " unix-domain sockets", STR(iter->dest)); + MARK_INVALID(tls->why, &tls->level); + return; + } + if (!tls_dane_avail()) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: %s configured, but no requisite library support", + STR(iter->dest), policy_name(tls->level)); + return; + } + if (!(smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) + || smtp_dns_support != SMTP_DNS_DNSSEC) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: %s configured with dnssec lookups disabled", + STR(iter->dest), policy_name(tls->level)); + return; + } + + /* + * If we ignore MX lookup errors, we also ignore DNSSEC security problems + * and thus avoid any reasonable expectation that we get the right DANE + * key material. + */ + if (smtp_mode && var_ign_mx_lookup_err) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: %s configured with MX lookup errors ignored", + STR(iter->dest), policy_name(tls->level)); + return; + } + + /* + * This is not optional, code in tls_dane.c assumes that the nexthop + * qname is already an fqdn. If we're using these flags to go from qname + * to rname, the assumption is invalid. Likewise we cannot add the qname + * to certificate name checks, ... + */ + if (smtp_dns_res_opt & (RES_DEFNAMES | RES_DNSRCH)) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: dns resolver options incompatible with %s TLS", + STR(iter->dest), policy_name(tls->level)); + return; + } + + /* + * When the MX name is present and insecure, DANE may not apply, we then + * either fail if DANE is mandatory or use regular opportunistic TLS if + * the insecure MX level is "may". + */ + if (iter->mx && !iter->mx->dnssec_valid + && (tls->level == TLS_LEV_DANE_ONLY || + smtp_tls_insecure_mx_policy <= TLS_LEV_MAY)) { + dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination"); + return; + } + /* When TLSA lookups fail, we defer the message */ + if ((dane = tls_dane_resolve(iter->port, "tcp", iter->rr, + var_smtp_tls_force_tlsa)) == 0) { + tls->level = TLS_LEV_INVALID; + dsb_simple(tls->why, "4.7.5", "TLSA lookup error for %s:%u", + STR(iter->host), ntohs(iter->port)); + return; + } + if (tls_dane_notfound(dane)) { + dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found"); + tls_dane_free(dane); + return; + } + + /* + * Some TLSA records found, but none usable, per + * + * https://tools.ietf.org/html/draft-ietf-dane-srv-02#section-4 + * + * we MUST use TLS, and SHALL use full PKIX certificate checks. The latter + * would be unwise for SMTP: no human present to "click ok" and risk of + * non-delivery in most cases exceeds risk of interception. + * + * We also have a form of Goedel's incompleteness theorem in play: any list + * of public root CA certs is either incomplete or inconsistent (for any + * given verifier some of the CAs are surely not trustworthy). + */ + if (tls_dane_unusable(dane)) { + dane_incompat(tls, iter, DANE_CANTAUTH, "TLSA records unusable"); + tls_dane_free(dane); + return; + } + + /* + * Perhaps downgrade to "encrypt" if MX is insecure. + */ + if (iter->mx && !iter->mx->dnssec_valid) { + if (smtp_tls_insecure_mx_policy == TLS_LEV_ENCRYPT) { + dane_incompat(tls, iter, DANE_CANTAUTH, + "Verification not possible, MX RRset is insecure"); + tls_dane_free(dane); + return; + } + if (tls->level != TLS_LEV_DANE + || smtp_tls_insecure_mx_policy != TLS_LEV_DANE) + msg_panic("wrong state for insecure MX host DANE policy"); + + /* For correct logging in tls_client_start() */ + tls->level = TLS_LEV_HALF_DANE; + } + + /* + * With DANE trust anchors, peername matching is not configurable. + */ + if (TLS_DANE_HASTA(dane)) { + tls->matchargv = argv_alloc(2); + argv_add(tls->matchargv, dane->base_domain, ARGV_END); + if (iter->mx) { + if (strcmp(iter->mx->qname, iter->mx->rname) == 0) + argv_add(tls->matchargv, iter->mx->qname, ARGV_END); + else + argv_add(tls->matchargv, iter->mx->rname, + iter->mx->qname, ARGV_END); + } + } else if (!TLS_DANE_HASEE(dane)) + msg_panic("empty DANE match list"); + tls->dane = dane; + return; +} + +#endif diff --git a/src/smtp/smtp_trouble.c b/src/smtp/smtp_trouble.c new file mode 100644 index 0000000..1320c24 --- /dev/null +++ b/src/smtp/smtp_trouble.c @@ -0,0 +1,462 @@ +/*++ +/* NAME +/* smtp_trouble 3 +/* SUMMARY +/* error handler policies +/* SYNOPSIS +/* #include "smtp.h" +/* +/* int smtp_sess_fail(state) +/* SMTP_STATE *state; +/* +/* int smtp_site_fail(state, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* +/* int smtp_mesg_fail(state, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* +/* void smtp_rcpt_fail(state, recipient, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* RECIPIENT *recipient; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* +/* int smtp_stream_except(state, exception, description) +/* SMTP_STATE *state; +/* int exception; +/* const char *description; +/* AUXILIARY FUNCTIONS +/* int smtp_misc_fail(state, throttle, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* int throttle; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* DESCRIPTION +/* This module handles all non-fatal errors that can happen while +/* attempting to deliver mail via SMTP, and implements the policy +/* of how to deal with the error. Depending on the nature of +/* the problem, delivery of a single message is deferred, delivery +/* of all messages to the same domain is deferred, or one or more +/* recipients are given up as non-deliverable and a bounce log is +/* updated. In any case, the recipient is marked as either KEEP +/* (try again with a backup host) or DROP (delete recipient from +/* delivery request). +/* +/* In addition, when an unexpected response code is seen such +/* as 3xx where only 4xx or 5xx are expected, or any error code +/* that suggests a syntax error or something similar, the +/* protocol error flag is set so that the postmaster receives +/* a transcript of the session. No notification is generated for +/* what appear to be configuration errors - very likely, they +/* would suffer the same problem and just cause more trouble. +/* +/* In case of a soft error, action depends on whether the error +/* qualifies for trying the request with other mail servers (log +/* an informational record only and try a backup server) or +/* whether this is the final server (log recipient delivery status +/* records and delete the recipient from the request). +/* +/* smtp_sess_fail() takes a pre-formatted error report after +/* failure to complete some protocol handshake. The policy is +/* as with smtp_site_fail(). +/* +/* smtp_site_fail() handles the case where the program fails to +/* complete the initial handshake: the server is not reachable, +/* is not running, does not want talk to us, or we talk to ourselves. +/* The \fIcode\fR gives an error status code; the \fIformat\fR +/* argument gives a textual description. +/* The policy is: soft error, non-final server: log an informational +/* record why the host is being skipped; soft error, final server: +/* defer delivery of all remaining recipients and mark the destination +/* as problematic; hard error: bounce all remaining recipients. +/* The session is marked as "do not cache". +/* The result is non-zero. +/* +/* smtp_mesg_fail() handles the case where the smtp server +/* does not accept the sender address or the message data, +/* or when the local MTA is unable to convert the message data. +/* The policy is: soft error, non-final server: log an informational +/* record why the host is being skipped; soft error, final server: +/* defer delivery of all remaining recipients; hard error: bounce all +/* remaining recipients. +/* The result is non-zero. +/* +/* smtp_misc_fail() provides a more detailed interface than +/* smtp_site_fail() and smtp_mesg_fail(), which are convenience +/* wrappers around smtp_misc_fail(). The throttle argument +/* is either SMTP_THROTTLE or SMTP_NOTHROTTLE; it is used only +/* in the "soft error, final server" policy, and determines +/* whether a destination will be marked as problematic. +/* +/* smtp_rcpt_fail() handles the case where a recipient is not +/* accepted by the server for reasons other than that the server +/* recipient limit is reached. +/* The policy is: soft error, non-final server: log an informational +/* record why the recipient is being skipped; soft error, final server: +/* defer delivery of this recipient; hard error: bounce this +/* recipient. +/* +/* smtp_stream_except() handles the exceptions generated by +/* the smtp_stream(3) module (i.e. timeouts and I/O errors). +/* The \fIexception\fR argument specifies the type of problem. +/* The \fIdescription\fR argument describes at what stage of +/* the SMTP dialog the problem happened. +/* The policy is: non-final server: log an informational record +/* with the reason why the host is being skipped; final server: +/* defer delivery of all remaining recipients. +/* Retry plaintext delivery after TLS post-handshake session +/* failure, provided that at least one recipient was not +/* deferred or rejected during the TLS phase, and that global +/* preconditions for plaintext fallback are met. +/* The session is marked as "do not cache". +/* The result is non-zero. +/* +/* Arguments: +/* .IP state +/* SMTP client state per delivery request. +/* .IP resp +/* Server response including reply code and text. +/* .IP recipient +/* Undeliverable recipient address information. +/* .IP format +/* Human-readable description of why mail is not deliverable. +/* DIAGNOSTICS +/* Panic: unknown exception code. +/* SEE ALSO +/* smtp_proto(3) smtp high-level protocol +/* smtp_stream(3) smtp low-level protocol +/* defer(3) basic message defer interface +/* bounce(3) basic message bounce interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <stringops.h> + +/* Global library. */ + +#include <smtp_stream.h> +#include <deliver_request.h> +#include <deliver_completed.h> +#include <bounce.h> +#include <defer.h> +#include <mail_error.h> +#include <dsn_buf.h> +#include <dsn.h> +#include <mail_params.h> + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + +/* smtp_check_code - check response code */ + +static void smtp_check_code(SMTP_SESSION *session, int code) +{ + + /* + * The intention of this code is to alert the postmaster when the local + * Postfix SMTP client screws up, protocol wise. RFC 821 says that x0z + * replies "refer to syntax errors, syntactically correct commands that + * don't fit any functional category, and unimplemented or superfluous + * commands". Unfortunately, this also triggers postmaster notices when + * remote servers screw up, protocol wise. This is becoming a common + * problem now that response codes are configured manually as part of + * anti-UCE systems, by people who aren't aware of RFC details. + */ + if (code < 400 || code > 599 + || code == 555 /* RFC 1869, section 6.1. */ + || (code >= 500 && code < 510)) + session->error_mask |= MAIL_ERROR_PROTOCOL; +} + +/* smtp_bulk_fail - skip, defer or bounce recipients, maybe throttle queue */ + +static int smtp_bulk_fail(SMTP_STATE *state, int throttle_queue) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + RECIPIENT *rcpt; + int status; + int aggregate_status; + int soft_error = (STR(why->status)[0] == '4'); + int soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce); + int nrcpt; + + /* + * Don't defer the recipients just yet when this error qualifies them for + * delivery to a backup server. Just log something informative to show + * why we're skipping this host. + */ + if ((soft_error || soft_bounce_error) + && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) { + msg_info("%s: %s", request->queue_id, STR(why->reason)); + for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (SMTP_RCPT_ISMARKED(rcpt)) + continue; + SMTP_RCPT_KEEP(state, rcpt); + } + } + + /* + * Defer or bounce all the remaining recipients, and delete them from the + * delivery request. If a bounce fails, defer instead and do not qualify + * the recipient for delivery to a backup server. + */ + else { + + /* + * If we are still in the connection set-up phase, update the set-up + * completion time here, otherwise the time spent in set-up latency + * will be attributed as message transfer latency. + * + * All remaining recipients have failed at this point, so we update the + * delivery completion time stamp so that multiple recipient status + * records show the same delay values. + */ + if (request->msg_stats.conn_setup_done.tv_sec == 0) { + GETTIMEOFDAY(&request->msg_stats.conn_setup_done); + request->msg_stats.deliver_done = + request->msg_stats.conn_setup_done; + } else + GETTIMEOFDAY(&request->msg_stats.deliver_done); + + (void) DSN_FROM_DSN_BUF(why); + aggregate_status = 0; + for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (SMTP_RCPT_ISMARKED(rcpt)) + continue; + status = (soft_error ? defer_append : bounce_append) + (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, + &request->msg_stats, rcpt, + session ? session->namaddrport : "none", &why->dsn); + if (status == 0) + deliver_completed(state->src, rcpt->offset); + SMTP_RCPT_DROP(state, rcpt); + aggregate_status |= status; + } + state->status |= aggregate_status; + if ((state->misc_flags & SMTP_MISC_FLAG_COMPLETE_SESSION) == 0 + && throttle_queue && aggregate_status + && request->hop_status == 0) + request->hop_status = DSN_COPY(&why->dsn); + } + + /* + * Don't cache this session. We can't talk to this server. + */ + if (throttle_queue && session) + DONT_CACHE_THROTTLED_SESSION; + + return (-1); +} + +/* smtp_sess_fail - skip site, defer or bounce all recipients */ + +int smtp_sess_fail(SMTP_STATE *state) +{ + + /* + * We can't avoid copying copying lots of strings into VSTRING buffers, + * because this error information is collected by a routine that + * terminates BEFORE the error is reported. + */ + return (smtp_bulk_fail(state, SMTP_THROTTLE)); +} + +/* vsmtp_fill_dsn - fill in temporary DSN structure */ + +static void vsmtp_fill_dsn(SMTP_STATE *state, const char *mta_name, + const char *status, const char *reply, + const char *format, va_list ap) +{ + DSN_BUF *why = state->why; + + /* + * We could avoid copying lots of strings into VSTRING buffers, because + * this error information is given to us by a routine that terminates + * AFTER the error is reported. However, this results in ugly kludges + * when informal text needs to be formatted. So we maintain consistency + * with other error reporting in the SMTP client even if we waste a few + * cycles. + */ + VSTRING_RESET(why->reason); + if (mta_name && status && status[0] != '4' && status[0] != '5') { + vstring_strcpy(why->reason, "Protocol error: "); + status = "5.5.0"; + } + vstring_vsprintf_append(why->reason, format, ap); + dsb_formal(why, status, DSB_DEF_ACTION, + mta_name ? DSB_MTYPE_DNS : DSB_MTYPE_NONE, mta_name, + reply ? DSB_DTYPE_SMTP : DSB_DTYPE_NONE, reply); +} + +/* smtp_misc_fail - maybe throttle queue; skip/defer/bounce all recipients */ + +int smtp_misc_fail(SMTP_STATE *state, int throttle, const char *mta_name, + SMTP_RESP *resp, const char *format,...) +{ + va_list ap; + + /* + * Initialize. + */ + va_start(ap, format); + vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap); + va_end(ap); + + if (state->session && mta_name) + smtp_check_code(state->session, resp->code); + + /* + * Skip, defer or bounce recipients, and throttle this queue. + */ + return (smtp_bulk_fail(state, throttle)); +} + +/* smtp_rcpt_fail - skip, defer, or bounce recipient */ + +void smtp_rcpt_fail(SMTP_STATE *state, RECIPIENT *rcpt, const char *mta_name, + SMTP_RESP *resp, const char *format,...) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + int status; + int soft_error; + int soft_bounce_error; + va_list ap; + + /* + * Sanity check. + */ + if (SMTP_RCPT_ISMARKED(rcpt)) + msg_panic("smtp_rcpt_fail: recipient <%s> is marked", rcpt->address); + + /* + * Initialize. + */ + va_start(ap, format); + vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap); + va_end(ap); + soft_error = STR(why->status)[0] == '4'; + soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce); + + if (state->session && mta_name) + smtp_check_code(state->session, resp->code); + + /* + * Don't defer this recipient record just yet when this error qualifies + * for trying other mail servers. Just log something informative to show + * why we're skipping this recipient now. + */ + if ((soft_error || soft_bounce_error) + && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) { + msg_info("%s: %s", request->queue_id, STR(why->reason)); + SMTP_RCPT_KEEP(state, rcpt); + } + + /* + * Defer or bounce this recipient, and delete from the delivery request. + * If the bounce fails, defer instead and do not qualify the recipient + * for delivery to a backup server. + * + * Note: we may still make an SMTP connection to deliver other recipients + * that did qualify for delivery to a backup server. + */ + else { + (void) DSN_FROM_DSN_BUF(state->why); + status = (soft_error ? defer_append : bounce_append) + (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, + &request->msg_stats, rcpt, + session ? session->namaddrport : "none", &why->dsn); + if (status == 0) + deliver_completed(state->src, rcpt->offset); + SMTP_RCPT_DROP(state, rcpt); + state->status |= status; + } +} + +/* smtp_stream_except - defer domain after I/O problem */ + +int smtp_stream_except(SMTP_STATE *state, int code, const char *description) +{ + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + + /* + * Sanity check. + */ + if (session == 0) + msg_panic("smtp_stream_except: no session"); + + /* + * Initialize. + */ + switch (code) { + default: + msg_panic("smtp_stream_except: unknown exception %d", code); + case SMTP_ERR_EOF: + dsb_simple(why, "4.4.2", "lost connection with %s while %s", + session->namaddr, description); +#ifdef USE_TLS + if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE) + RETRY_AS_PLAINTEXT; +#endif + break; + case SMTP_ERR_TIME: + dsb_simple(why, "4.4.2", "conversation with %s timed out while %s", + session->namaddr, description); +#ifdef USE_TLS + if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE) + RETRY_AS_PLAINTEXT; +#endif + break; + case SMTP_ERR_DATA: + session->error_mask |= MAIL_ERROR_DATA; + dsb_simple(why, "4.3.0", "local data error while talking to %s", + session->namaddr); + } + + /* + * The smtp_bulk_fail() call below will not throttle the destination when + * falling back to plaintext, because RETRY_AS_PLAINTEXT clears the + * FINAL_SERVER flag. + */ + return (smtp_bulk_fail(state, SMTP_THROTTLE)); +} diff --git a/src/smtp/smtp_unalias.c b/src/smtp/smtp_unalias.c new file mode 100644 index 0000000..1c4d34d --- /dev/null +++ b/src/smtp/smtp_unalias.c @@ -0,0 +1,142 @@ +/*++ +/* NAME +/* smtp_unalias 3 +/* SUMMARY +/* replace host/domain alias by official name +/* SYNOPSIS +/* #include "smtp.h" +/* +/* const char *smtp_unalias_name(name) +/* const char *name; +/* +/* VSTRING *smtp_unalias_addr(result, addr) +/* VSTRING *result; +/* const char *addr; +/* DESCRIPTION +/* smtp_unalias_name() looks up A or MX records for the specified +/* name and returns the official name provided in the reply. When +/* no A or MX record is found, the result is a copy of the input. +/* In order to improve performance, smtp_unalias_name() remembers +/* results in a private cache, so a name is looked up only once. +/* +/* smtp_unalias_addr() returns the input address, which is in +/* RFC 821 quoted form, after replacing the domain part by the +/* official name as found by smtp_unalias_name(). When the input +/* contains no domain part, the result is a copy of the input. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdlib.h> +#include <netdb.h> +#include <string.h> + +/* Utility library. */ + +#include <htable.h> +#include <vstring.h> +#include <msg.h> + +/* DNS library. */ + +#include <dns.h> + +/* Application-specific. */ + +#include "smtp.h" + +static int smtp_unalias_flags; + +/* smtp_unalias_name - look up the official host or domain name. */ + +const char *smtp_unalias_name(const char *name) +{ + static HTABLE *cache; + VSTRING *fqdn; + char *result; + + if (*name == '[') + return (name); + + /* + * Initialize the cache on the fly. The smtp client is designed to exit + * after servicing a limited number of requests, so there is no need to + * prevent the cache from growing too large, or to expire old entries. + */ + if (cache == 0) + cache = htable_create(10); + + /* + * Look up the fqdn. If none is found use the query name instead, so that + * we won't lose time looking up the same bad name again. + */ + if ((result = htable_find(cache, name)) == 0) { + fqdn = vstring_alloc(10); + if (dns_lookup_l(name, smtp_unalias_flags, (DNS_RR **) 0, fqdn, + (VSTRING *) 0, DNS_REQ_FLAG_NONE, T_MX, T_A, +#ifdef HAS_IPV6 + T_AAAA, +#endif + 0) != DNS_OK) + vstring_strcpy(fqdn, name); + htable_enter(cache, name, result = vstring_export(fqdn)); + } + return (result); +} + +/* smtp_unalias_addr - rewrite aliases in domain part of address */ + +VSTRING *smtp_unalias_addr(VSTRING *result, const char *addr) +{ + char *at; + const char *fqdn; + + if ((at = strrchr(addr, '@')) == 0 || at[1] == 0) { + vstring_strcpy(result, addr); + } else { + fqdn = smtp_unalias_name(at + 1); + vstring_strncpy(result, addr, at - addr + 1); + vstring_strcat(result, fqdn); + } + return (result); +} + +#ifdef TEST + + /* + * Test program - read address from stdin, print result on stdout. + */ + +#include <vstring_vstream.h> + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *addr = vstring_alloc(10); + VSTRING *result = vstring_alloc(10); + + smtp_unalias_flags |= RES_DEBUG; + + while (vstring_fgets_nonl(addr, VSTREAM_IN)) { + smtp_unalias_addr(result, vstring_str(addr)); + vstream_printf("%s -> %s\n", vstring_str(addr), vstring_str(result)); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(addr); + vstring_free(result); + return (0); +} + +#endif diff --git a/src/smtp/tls_policy.in b/src/smtp/tls_policy.in new file mode 100644 index 0000000..95c295b --- /dev/null +++ b/src/smtp/tls_policy.in @@ -0,0 +1,64 @@ +- - - +- - may +- - must_nopeermatch +- - must +- none - +- none may +- none must_nopeermatch +- none must +- may - +- may may +- may must_nopeermatch +- may must +- must_nopeermatch - +- must_nopeermatch may +- must_nopeermatch must_nopeermatch +- must_nopeermatch must +- must - +- must may +- must must_nopeermatch +- must must + +none none - +none none may +none none must_nopeermatch +none none must +none may - +none may may +none may must_nopeermatch +none may must +none must_nopeermatch - +none must_nopeermatch may +none must_nopeermatch must_nopeermatch +none must_nopeermatch must +none must - +none must may +none must must_nopeermatch +none must must + +may may - +may may may +may may must_nopeermatch +may may must +may must_nopeermatch - +may must_nopeermatch may +may must_nopeermatch must_nopeermatch +may must_nopeermatch must +may must - +may must may +may must must_nopeermatch +may must must + +must_nopeermatch must_nopeermatch - +must_nopeermatch must_nopeermatch may +must_nopeermatch must_nopeermatch must_nopeermatch +must_nopeermatch must_nopeermatch must +must_nopeermatch must - +must_nopeermatch must may +must_nopeermatch must must_nopeermatch +must_nopeermatch must must + +must must - +must must may +must must must_nopeermatch +must must must diff --git a/src/smtp/tls_policy.ref b/src/smtp/tls_policy.ref new file mode 100644 index 0000000..5b9f187 --- /dev/null +++ b/src/smtp/tls_policy.ref @@ -0,0 +1,65 @@ +host dest global result +- - - none +- - may may +- - must_nopeermatch must_nopeermatch +- - must must +- none - none +- none may none +- none must_nopeermatch none +- none must none +- may - may +- may may may +- may must_nopeermatch must_nopeermatch +- may must must +- must_nopeermatch - must_nopeermatch +- must_nopeermatch may must_nopeermatch +- must_nopeermatch must_nopeermatch must_nopeermatch +- must_nopeermatch must must_nopeermatch +- must - must +- must may must +- must must_nopeermatch must +- must must must + +none none - none +none none may none +none none must_nopeermatch none +none none must none +none may - none +none may may none +none may must_nopeermatch none +none may must none +none must_nopeermatch - must_nopeermatch +none must_nopeermatch may must_nopeermatch +none must_nopeermatch must_nopeermatch must_nopeermatch +none must_nopeermatch must must_nopeermatch +none must - must +none must may must +none must must_nopeermatch must +none must must must + +may may - may +may may may may +may may must_nopeermatch must_nopeermatch +may may must must +may must_nopeermatch - must_nopeermatch +may must_nopeermatch may must_nopeermatch +may must_nopeermatch must_nopeermatch must_nopeermatch +may must_nopeermatch must must_nopeermatch +may must - must +may must may must +may must must_nopeermatch must +may must must must + +must_nopeermatch must_nopeermatch - must_nopeermatch +must_nopeermatch must_nopeermatch may must_nopeermatch +must_nopeermatch must_nopeermatch must_nopeermatch must_nopeermatch +must_nopeermatch must_nopeermatch must must_nopeermatch +must_nopeermatch must - must +must_nopeermatch must may must +must_nopeermatch must must_nopeermatch must +must_nopeermatch must must must + +must must - must +must must may must +must must must_nopeermatch must +must must must must |