summaryrefslogtreecommitdiffstats
path: root/src/postscreen
diff options
context:
space:
mode:
Diffstat (limited to 'src/postscreen')
l---------src/postscreen/.indent.pro1
-rw-r--r--src/postscreen/Makefile.in428
-rw-r--r--src/postscreen/postscreen.c1252
-rw-r--r--src/postscreen/postscreen.h599
-rw-r--r--src/postscreen/postscreen_dict.c182
-rw-r--r--src/postscreen/postscreen_dnsbl.c624
-rw-r--r--src/postscreen/postscreen_early.c377
-rw-r--r--src/postscreen/postscreen_endpt.c232
-rw-r--r--src/postscreen/postscreen_expand.c141
-rw-r--r--src/postscreen/postscreen_haproxy.c137
-rw-r--r--src/postscreen/postscreen_haproxy.h30
-rw-r--r--src/postscreen/postscreen_misc.c164
-rw-r--r--src/postscreen/postscreen_send.c293
-rw-r--r--src/postscreen/postscreen_smtpd.c1339
-rw-r--r--src/postscreen/postscreen_starttls.c317
-rw-r--r--src/postscreen/postscreen_state.c317
-rw-r--r--src/postscreen/postscreen_tests.c341
17 files changed, 6774 insertions, 0 deletions
diff --git a/src/postscreen/.indent.pro b/src/postscreen/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/postscreen/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/postscreen/Makefile.in b/src/postscreen/Makefile.in
new file mode 100644
index 0000000..8ed8692
--- /dev/null
+++ b/src/postscreen/Makefile.in
@@ -0,0 +1,428 @@
+SHELL = /bin/sh
+SRCS = postscreen.c postscreen_dict.c postscreen_dnsbl.c \
+ postscreen_early.c postscreen_smtpd.c postscreen_misc.c \
+ postscreen_state.c postscreen_tests.c postscreen_send.c \
+ postscreen_starttls.c postscreen_expand.c postscreen_endpt.c \
+ postscreen_haproxy.c
+OBJS = postscreen.o postscreen_dict.o postscreen_dnsbl.o \
+ postscreen_early.o postscreen_smtpd.o postscreen_misc.o \
+ postscreen_state.o postscreen_tests.o postscreen_send.o \
+ postscreen_starttls.o postscreen_expand.o postscreen_endpt.o \
+ postscreen_haproxy.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = postscreen
+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/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: test
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+ -e 's/o: \.\//o: /' -e p -e '}' ; \
+ done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+postscreen.o: ../../include/addr_match_list.h
+postscreen.o: ../../include/argv.h
+postscreen.o: ../../include/attr.h
+postscreen.o: ../../include/check_arg.h
+postscreen.o: ../../include/data_redirect.h
+postscreen.o: ../../include/dict.h
+postscreen.o: ../../include/dict_cache.h
+postscreen.o: ../../include/events.h
+postscreen.o: ../../include/htable.h
+postscreen.o: ../../include/inet_proto.h
+postscreen.o: ../../include/iostuff.h
+postscreen.o: ../../include/mail_conf.h
+postscreen.o: ../../include/mail_params.h
+postscreen.o: ../../include/mail_proto.h
+postscreen.o: ../../include/mail_server.h
+postscreen.o: ../../include/mail_version.h
+postscreen.o: ../../include/maps.h
+postscreen.o: ../../include/match_list.h
+postscreen.o: ../../include/msg.h
+postscreen.o: ../../include/myaddrinfo.h
+postscreen.o: ../../include/myflock.h
+postscreen.o: ../../include/mymalloc.h
+postscreen.o: ../../include/name_code.h
+postscreen.o: ../../include/nvtable.h
+postscreen.o: ../../include/server_acl.h
+postscreen.o: ../../include/set_eugid.h
+postscreen.o: ../../include/string_list.h
+postscreen.o: ../../include/sys_defs.h
+postscreen.o: ../../include/vbuf.h
+postscreen.o: ../../include/vstream.h
+postscreen.o: ../../include/vstring.h
+postscreen.o: postscreen.c
+postscreen.o: postscreen.h
+postscreen_dict.o: ../../include/addr_match_list.h
+postscreen_dict.o: ../../include/argv.h
+postscreen_dict.o: ../../include/check_arg.h
+postscreen_dict.o: ../../include/dict.h
+postscreen_dict.o: ../../include/dict_cache.h
+postscreen_dict.o: ../../include/events.h
+postscreen_dict.o: ../../include/htable.h
+postscreen_dict.o: ../../include/maps.h
+postscreen_dict.o: ../../include/match_list.h
+postscreen_dict.o: ../../include/msg.h
+postscreen_dict.o: ../../include/myaddrinfo.h
+postscreen_dict.o: ../../include/myflock.h
+postscreen_dict.o: ../../include/server_acl.h
+postscreen_dict.o: ../../include/string_list.h
+postscreen_dict.o: ../../include/sys_defs.h
+postscreen_dict.o: ../../include/vbuf.h
+postscreen_dict.o: ../../include/vstream.h
+postscreen_dict.o: ../../include/vstring.h
+postscreen_dict.o: postscreen.h
+postscreen_dict.o: postscreen_dict.c
+postscreen_dnsbl.o: ../../include/addr_match_list.h
+postscreen_dnsbl.o: ../../include/argv.h
+postscreen_dnsbl.o: ../../include/attr.h
+postscreen_dnsbl.o: ../../include/check_arg.h
+postscreen_dnsbl.o: ../../include/connect.h
+postscreen_dnsbl.o: ../../include/dict.h
+postscreen_dnsbl.o: ../../include/dict_cache.h
+postscreen_dnsbl.o: ../../include/events.h
+postscreen_dnsbl.o: ../../include/htable.h
+postscreen_dnsbl.o: ../../include/iostuff.h
+postscreen_dnsbl.o: ../../include/ip_match.h
+postscreen_dnsbl.o: ../../include/mail_params.h
+postscreen_dnsbl.o: ../../include/mail_proto.h
+postscreen_dnsbl.o: ../../include/maps.h
+postscreen_dnsbl.o: ../../include/match_list.h
+postscreen_dnsbl.o: ../../include/msg.h
+postscreen_dnsbl.o: ../../include/myaddrinfo.h
+postscreen_dnsbl.o: ../../include/myflock.h
+postscreen_dnsbl.o: ../../include/mymalloc.h
+postscreen_dnsbl.o: ../../include/nvtable.h
+postscreen_dnsbl.o: ../../include/server_acl.h
+postscreen_dnsbl.o: ../../include/split_at.h
+postscreen_dnsbl.o: ../../include/string_list.h
+postscreen_dnsbl.o: ../../include/stringops.h
+postscreen_dnsbl.o: ../../include/sys_defs.h
+postscreen_dnsbl.o: ../../include/valid_hostname.h
+postscreen_dnsbl.o: ../../include/vbuf.h
+postscreen_dnsbl.o: ../../include/vstream.h
+postscreen_dnsbl.o: ../../include/vstring.h
+postscreen_dnsbl.o: postscreen.h
+postscreen_dnsbl.o: postscreen_dnsbl.c
+postscreen_early.o: ../../include/addr_match_list.h
+postscreen_early.o: ../../include/argv.h
+postscreen_early.o: ../../include/check_arg.h
+postscreen_early.o: ../../include/dict.h
+postscreen_early.o: ../../include/dict_cache.h
+postscreen_early.o: ../../include/events.h
+postscreen_early.o: ../../include/htable.h
+postscreen_early.o: ../../include/mail_params.h
+postscreen_early.o: ../../include/maps.h
+postscreen_early.o: ../../include/match_list.h
+postscreen_early.o: ../../include/msg.h
+postscreen_early.o: ../../include/myaddrinfo.h
+postscreen_early.o: ../../include/myflock.h
+postscreen_early.o: ../../include/mymalloc.h
+postscreen_early.o: ../../include/server_acl.h
+postscreen_early.o: ../../include/string_list.h
+postscreen_early.o: ../../include/stringops.h
+postscreen_early.o: ../../include/sys_defs.h
+postscreen_early.o: ../../include/vbuf.h
+postscreen_early.o: ../../include/vstream.h
+postscreen_early.o: ../../include/vstring.h
+postscreen_early.o: postscreen.h
+postscreen_early.o: postscreen_early.c
+postscreen_endpt.o: ../../include/addr_match_list.h
+postscreen_endpt.o: ../../include/argv.h
+postscreen_endpt.o: ../../include/check_arg.h
+postscreen_endpt.o: ../../include/dict.h
+postscreen_endpt.o: ../../include/dict_cache.h
+postscreen_endpt.o: ../../include/events.h
+postscreen_endpt.o: ../../include/haproxy_srvr.h
+postscreen_endpt.o: ../../include/htable.h
+postscreen_endpt.o: ../../include/inet_proto.h
+postscreen_endpt.o: ../../include/mail_params.h
+postscreen_endpt.o: ../../include/maps.h
+postscreen_endpt.o: ../../include/match_list.h
+postscreen_endpt.o: ../../include/msg.h
+postscreen_endpt.o: ../../include/myaddrinfo.h
+postscreen_endpt.o: ../../include/myflock.h
+postscreen_endpt.o: ../../include/server_acl.h
+postscreen_endpt.o: ../../include/string_list.h
+postscreen_endpt.o: ../../include/sys_defs.h
+postscreen_endpt.o: ../../include/vbuf.h
+postscreen_endpt.o: ../../include/vstream.h
+postscreen_endpt.o: ../../include/vstring.h
+postscreen_endpt.o: postscreen.h
+postscreen_endpt.o: postscreen_endpt.c
+postscreen_endpt.o: postscreen_haproxy.h
+postscreen_expand.o: ../../include/addr_match_list.h
+postscreen_expand.o: ../../include/argv.h
+postscreen_expand.o: ../../include/attr.h
+postscreen_expand.o: ../../include/check_arg.h
+postscreen_expand.o: ../../include/dict.h
+postscreen_expand.o: ../../include/dict_cache.h
+postscreen_expand.o: ../../include/events.h
+postscreen_expand.o: ../../include/htable.h
+postscreen_expand.o: ../../include/iostuff.h
+postscreen_expand.o: ../../include/mail_params.h
+postscreen_expand.o: ../../include/mail_proto.h
+postscreen_expand.o: ../../include/maps.h
+postscreen_expand.o: ../../include/match_list.h
+postscreen_expand.o: ../../include/msg.h
+postscreen_expand.o: ../../include/myaddrinfo.h
+postscreen_expand.o: ../../include/myflock.h
+postscreen_expand.o: ../../include/mymalloc.h
+postscreen_expand.o: ../../include/nvtable.h
+postscreen_expand.o: ../../include/server_acl.h
+postscreen_expand.o: ../../include/string_list.h
+postscreen_expand.o: ../../include/stringops.h
+postscreen_expand.o: ../../include/sys_defs.h
+postscreen_expand.o: ../../include/vbuf.h
+postscreen_expand.o: ../../include/vstream.h
+postscreen_expand.o: ../../include/vstring.h
+postscreen_expand.o: postscreen.h
+postscreen_expand.o: postscreen_expand.c
+postscreen_haproxy.o: ../../include/addr_match_list.h
+postscreen_haproxy.o: ../../include/argv.h
+postscreen_haproxy.o: ../../include/check_arg.h
+postscreen_haproxy.o: ../../include/dict.h
+postscreen_haproxy.o: ../../include/dict_cache.h
+postscreen_haproxy.o: ../../include/events.h
+postscreen_haproxy.o: ../../include/haproxy_srvr.h
+postscreen_haproxy.o: ../../include/htable.h
+postscreen_haproxy.o: ../../include/mail_params.h
+postscreen_haproxy.o: ../../include/maps.h
+postscreen_haproxy.o: ../../include/match_list.h
+postscreen_haproxy.o: ../../include/msg.h
+postscreen_haproxy.o: ../../include/myaddrinfo.h
+postscreen_haproxy.o: ../../include/myflock.h
+postscreen_haproxy.o: ../../include/mymalloc.h
+postscreen_haproxy.o: ../../include/server_acl.h
+postscreen_haproxy.o: ../../include/string_list.h
+postscreen_haproxy.o: ../../include/stringops.h
+postscreen_haproxy.o: ../../include/sys_defs.h
+postscreen_haproxy.o: ../../include/vbuf.h
+postscreen_haproxy.o: ../../include/vstream.h
+postscreen_haproxy.o: ../../include/vstring.h
+postscreen_haproxy.o: postscreen.h
+postscreen_haproxy.o: postscreen_haproxy.c
+postscreen_haproxy.o: postscreen_haproxy.h
+postscreen_misc.o: ../../include/addr_match_list.h
+postscreen_misc.o: ../../include/argv.h
+postscreen_misc.o: ../../include/check_arg.h
+postscreen_misc.o: ../../include/dict.h
+postscreen_misc.o: ../../include/dict_cache.h
+postscreen_misc.o: ../../include/events.h
+postscreen_misc.o: ../../include/format_tv.h
+postscreen_misc.o: ../../include/htable.h
+postscreen_misc.o: ../../include/iostuff.h
+postscreen_misc.o: ../../include/mail_params.h
+postscreen_misc.o: ../../include/maps.h
+postscreen_misc.o: ../../include/match_list.h
+postscreen_misc.o: ../../include/msg.h
+postscreen_misc.o: ../../include/myaddrinfo.h
+postscreen_misc.o: ../../include/myflock.h
+postscreen_misc.o: ../../include/server_acl.h
+postscreen_misc.o: ../../include/string_list.h
+postscreen_misc.o: ../../include/sys_defs.h
+postscreen_misc.o: ../../include/vbuf.h
+postscreen_misc.o: ../../include/vstream.h
+postscreen_misc.o: ../../include/vstring.h
+postscreen_misc.o: postscreen.h
+postscreen_misc.o: postscreen_misc.c
+postscreen_send.o: ../../include/addr_match_list.h
+postscreen_send.o: ../../include/argv.h
+postscreen_send.o: ../../include/attr.h
+postscreen_send.o: ../../include/check_arg.h
+postscreen_send.o: ../../include/connect.h
+postscreen_send.o: ../../include/dict.h
+postscreen_send.o: ../../include/dict_cache.h
+postscreen_send.o: ../../include/events.h
+postscreen_send.o: ../../include/htable.h
+postscreen_send.o: ../../include/iostuff.h
+postscreen_send.o: ../../include/mac_expand.h
+postscreen_send.o: ../../include/mac_parse.h
+postscreen_send.o: ../../include/mail_params.h
+postscreen_send.o: ../../include/mail_proto.h
+postscreen_send.o: ../../include/maps.h
+postscreen_send.o: ../../include/match_list.h
+postscreen_send.o: ../../include/msg.h
+postscreen_send.o: ../../include/myaddrinfo.h
+postscreen_send.o: ../../include/myflock.h
+postscreen_send.o: ../../include/mymalloc.h
+postscreen_send.o: ../../include/nvtable.h
+postscreen_send.o: ../../include/server_acl.h
+postscreen_send.o: ../../include/smtp_reply_footer.h
+postscreen_send.o: ../../include/string_list.h
+postscreen_send.o: ../../include/sys_defs.h
+postscreen_send.o: ../../include/vbuf.h
+postscreen_send.o: ../../include/vstream.h
+postscreen_send.o: ../../include/vstring.h
+postscreen_send.o: postscreen.h
+postscreen_send.o: postscreen_send.c
+postscreen_smtpd.o: ../../include/addr_match_list.h
+postscreen_smtpd.o: ../../include/argv.h
+postscreen_smtpd.o: ../../include/attr.h
+postscreen_smtpd.o: ../../include/check_arg.h
+postscreen_smtpd.o: ../../include/dict.h
+postscreen_smtpd.o: ../../include/dict_cache.h
+postscreen_smtpd.o: ../../include/dns.h
+postscreen_smtpd.o: ../../include/ehlo_mask.h
+postscreen_smtpd.o: ../../include/events.h
+postscreen_smtpd.o: ../../include/htable.h
+postscreen_smtpd.o: ../../include/info_log_addr_form.h
+postscreen_smtpd.o: ../../include/iostuff.h
+postscreen_smtpd.o: ../../include/is_header.h
+postscreen_smtpd.o: ../../include/lex_822.h
+postscreen_smtpd.o: ../../include/mail_params.h
+postscreen_smtpd.o: ../../include/mail_proto.h
+postscreen_smtpd.o: ../../include/maps.h
+postscreen_smtpd.o: ../../include/match_list.h
+postscreen_smtpd.o: ../../include/msg.h
+postscreen_smtpd.o: ../../include/myaddrinfo.h
+postscreen_smtpd.o: ../../include/myflock.h
+postscreen_smtpd.o: ../../include/mymalloc.h
+postscreen_smtpd.o: ../../include/name_code.h
+postscreen_smtpd.o: ../../include/name_mask.h
+postscreen_smtpd.o: ../../include/nvtable.h
+postscreen_smtpd.o: ../../include/server_acl.h
+postscreen_smtpd.o: ../../include/sock_addr.h
+postscreen_smtpd.o: ../../include/string_list.h
+postscreen_smtpd.o: ../../include/stringops.h
+postscreen_smtpd.o: ../../include/sys_defs.h
+postscreen_smtpd.o: ../../include/tls.h
+postscreen_smtpd.o: ../../include/vbuf.h
+postscreen_smtpd.o: ../../include/vstream.h
+postscreen_smtpd.o: ../../include/vstring.h
+postscreen_smtpd.o: postscreen.h
+postscreen_smtpd.o: postscreen_smtpd.c
+postscreen_starttls.o: ../../include/addr_match_list.h
+postscreen_starttls.o: ../../include/argv.h
+postscreen_starttls.o: ../../include/attr.h
+postscreen_starttls.o: ../../include/check_arg.h
+postscreen_starttls.o: ../../include/connect.h
+postscreen_starttls.o: ../../include/dict.h
+postscreen_starttls.o: ../../include/dict_cache.h
+postscreen_starttls.o: ../../include/dns.h
+postscreen_starttls.o: ../../include/events.h
+postscreen_starttls.o: ../../include/htable.h
+postscreen_starttls.o: ../../include/iostuff.h
+postscreen_starttls.o: ../../include/mail_params.h
+postscreen_starttls.o: ../../include/mail_proto.h
+postscreen_starttls.o: ../../include/maps.h
+postscreen_starttls.o: ../../include/match_list.h
+postscreen_starttls.o: ../../include/msg.h
+postscreen_starttls.o: ../../include/myaddrinfo.h
+postscreen_starttls.o: ../../include/myflock.h
+postscreen_starttls.o: ../../include/mymalloc.h
+postscreen_starttls.o: ../../include/name_code.h
+postscreen_starttls.o: ../../include/name_mask.h
+postscreen_starttls.o: ../../include/nvtable.h
+postscreen_starttls.o: ../../include/server_acl.h
+postscreen_starttls.o: ../../include/sock_addr.h
+postscreen_starttls.o: ../../include/string_list.h
+postscreen_starttls.o: ../../include/stringops.h
+postscreen_starttls.o: ../../include/sys_defs.h
+postscreen_starttls.o: ../../include/tls.h
+postscreen_starttls.o: ../../include/tls_proxy.h
+postscreen_starttls.o: ../../include/vbuf.h
+postscreen_starttls.o: ../../include/vstream.h
+postscreen_starttls.o: ../../include/vstring.h
+postscreen_starttls.o: postscreen.h
+postscreen_starttls.o: postscreen_starttls.c
+postscreen_state.o: ../../include/addr_match_list.h
+postscreen_state.o: ../../include/argv.h
+postscreen_state.o: ../../include/attr.h
+postscreen_state.o: ../../include/check_arg.h
+postscreen_state.o: ../../include/dict.h
+postscreen_state.o: ../../include/dict_cache.h
+postscreen_state.o: ../../include/events.h
+postscreen_state.o: ../../include/htable.h
+postscreen_state.o: ../../include/iostuff.h
+postscreen_state.o: ../../include/mail_conf.h
+postscreen_state.o: ../../include/mail_proto.h
+postscreen_state.o: ../../include/mail_server.h
+postscreen_state.o: ../../include/maps.h
+postscreen_state.o: ../../include/match_list.h
+postscreen_state.o: ../../include/msg.h
+postscreen_state.o: ../../include/myaddrinfo.h
+postscreen_state.o: ../../include/myflock.h
+postscreen_state.o: ../../include/mymalloc.h
+postscreen_state.o: ../../include/name_mask.h
+postscreen_state.o: ../../include/nvtable.h
+postscreen_state.o: ../../include/server_acl.h
+postscreen_state.o: ../../include/string_list.h
+postscreen_state.o: ../../include/sys_defs.h
+postscreen_state.o: ../../include/vbuf.h
+postscreen_state.o: ../../include/vstream.h
+postscreen_state.o: ../../include/vstring.h
+postscreen_state.o: postscreen.h
+postscreen_state.o: postscreen_state.c
+postscreen_tests.o: ../../include/addr_match_list.h
+postscreen_tests.o: ../../include/argv.h
+postscreen_tests.o: ../../include/check_arg.h
+postscreen_tests.o: ../../include/dict.h
+postscreen_tests.o: ../../include/dict_cache.h
+postscreen_tests.o: ../../include/events.h
+postscreen_tests.o: ../../include/htable.h
+postscreen_tests.o: ../../include/mail_params.h
+postscreen_tests.o: ../../include/maps.h
+postscreen_tests.o: ../../include/match_list.h
+postscreen_tests.o: ../../include/msg.h
+postscreen_tests.o: ../../include/myaddrinfo.h
+postscreen_tests.o: ../../include/myflock.h
+postscreen_tests.o: ../../include/name_code.h
+postscreen_tests.o: ../../include/sane_strtol.h
+postscreen_tests.o: ../../include/server_acl.h
+postscreen_tests.o: ../../include/string_list.h
+postscreen_tests.o: ../../include/sys_defs.h
+postscreen_tests.o: ../../include/vbuf.h
+postscreen_tests.o: ../../include/vstream.h
+postscreen_tests.o: ../../include/vstring.h
+postscreen_tests.o: postscreen.h
+postscreen_tests.o: postscreen_tests.c
diff --git a/src/postscreen/postscreen.c b/src/postscreen/postscreen.c
new file mode 100644
index 0000000..67716cb
--- /dev/null
+++ b/src/postscreen/postscreen.c
@@ -0,0 +1,1252 @@
+/*++
+/* NAME
+/* postscreen 8
+/* SUMMARY
+/* Postfix zombie blocker
+/* SYNOPSIS
+/* \fBpostscreen\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The Postfix \fBpostscreen\fR(8) server provides additional
+/* protection against mail server overload. One \fBpostscreen\fR(8)
+/* process handles multiple inbound SMTP connections, and decides
+/* which clients may talk to a Postfix SMTP server process.
+/* By keeping spambots away, \fBpostscreen\fR(8) leaves more
+/* SMTP server processes available for legitimate clients, and
+/* delays the onset of server overload conditions.
+/*
+/* This program should not be used on SMTP ports that receive
+/* mail from end-user clients (MUAs). In a typical deployment,
+/* \fBpostscreen\fR(8) handles the MX service on TCP port 25, and
+/* \fBsmtpd\fR(8) receives mail from MUAs on the \fBsubmission\fR
+/* service (TCP port 587) which requires client authentication.
+/* Alternatively, a site could set up a dedicated, non-postscreen,
+/* "port 25" server that provides \fBsubmission\fR service and
+/* client authentication, but no MX service.
+/*
+/* \fBpostscreen\fR(8) maintains a temporary allowlist for
+/* clients that have passed a number of tests. When an SMTP
+/* client IP address is allowlisted, \fBpostscreen\fR(8) hands
+/* off the connection immediately to a Postfix SMTP server
+/* process. This minimizes the overhead for legitimate mail.
+/*
+/* By default, \fBpostscreen\fR(8) logs statistics and hands
+/* off each connection to a Postfix SMTP server process, while
+/* excluding clients in mynetworks from all tests (primarily,
+/* to avoid problems with non-standard SMTP implementations
+/* in network appliances). This default mode blocks no clients,
+/* and is useful for non-destructive testing.
+/*
+/* In a typical production setting, \fBpostscreen\fR(8) is
+/* configured to reject mail from clients that fail one or
+/* more tests. \fBpostscreen\fR(8) logs rejected mail with the
+/* client address, helo, sender and recipient information.
+/*
+/* \fBpostscreen\fR(8) is not an SMTP proxy; this is intentional.
+/* The purpose is to keep spambots away from Postfix SMTP
+/* server processes, while minimizing overhead for legitimate
+/* traffic.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBpostscreen\fR(8) server is moderately security-sensitive.
+/* It talks to untrusted clients on the network. The process
+/* can be run chrooted at fixed low privilege.
+/* STANDARDS
+/* RFC 821 (SMTP protocol)
+/* RFC 1123 (Host requirements)
+/* RFC 1652 (8bit-MIME transport)
+/* RFC 1869 (SMTP service extensions)
+/* RFC 1870 (Message Size Declaration)
+/* RFC 1985 (ETRN command)
+/* RFC 2034 (SMTP Enhanced Status Codes)
+/* RFC 2821 (SMTP protocol)
+/* Not: RFC 2920 (SMTP Pipelining)
+/* RFC 3030 (CHUNKING without BINARYMIME)
+/* RFC 3207 (STARTTLS command)
+/* RFC 3461 (SMTP DSN Extension)
+/* RFC 3463 (Enhanced Status Codes)
+/* RFC 5321 (SMTP protocol, including multi-line 220 banners)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* The \fBpostscreen\fR(8) built-in SMTP protocol engine
+/* currently does not announce support for AUTH, XCLIENT or
+/* XFORWARD.
+/* If you need to make these services available
+/* on port 25, then do not enable the optional "after 220
+/* server greeting" tests.
+/*
+/* The optional "after 220 server greeting" tests may result in
+/* unexpected delivery delays from senders that retry email delivery
+/* from a different IP address. Reason: after passing these tests a
+/* new client must disconnect, and reconnect from the same IP
+/* address before it can deliver mail. See POSTSCREEN_README, section
+/* "Tests after the 220 SMTP server greeting", for a discussion.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to main.cf are not picked up automatically, as
+/* \fBpostscreen\fR(8) processes may run for several hours.
+/* Use the command "postfix reload" after a configuration
+/* change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/*
+/* NOTE: Some \fBpostscreen\fR(8) parameters implement
+/* stress-dependent behavior. This is supported only when the
+/* default parameter value is stress-dependent (that is, it
+/* looks like ${stress?{X}:{Y}}, or it is the $\fIname\fR
+/* of an smtpd parameter with a stress-dependent default).
+/* Other parameters always evaluate as if the \fBstress\fR
+/* parameter value is the empty string.
+/* COMPATIBILITY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBpostscreen_command_filter ($smtpd_command_filter)\fR"
+/* A mechanism to transform commands from remote SMTP clients.
+/* .IP "\fBpostscreen_discard_ehlo_keyword_address_maps ($smtpd_discard_ehlo_keyword_address_maps)\fR"
+/* Lookup tables, indexed by the remote SMTP client address, with
+/* case insensitive lists of EHLO keywords (pipelining, starttls, auth,
+/* etc.) that the \fBpostscreen\fR(8) server will not send in the EHLO response
+/* to a remote SMTP client.
+/* .IP "\fBpostscreen_discard_ehlo_keywords ($smtpd_discard_ehlo_keywords)\fR"
+/* A case insensitive list of EHLO keywords (pipelining, starttls,
+/* auth, etc.) that the \fBpostscreen\fR(8) server will not send in the EHLO
+/* response to a remote SMTP client.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBdns_ncache_ttl_fix_enable (no)\fR"
+/* Enable a workaround for future libc incompatibility.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBpostscreen_reject_footer_maps ($smtpd_reject_footer_maps)\fR"
+/* Optional lookup table for information that is appended after a 4XX
+/* or 5XX \fBpostscreen\fR(8) server response.
+/* .PP
+/* Available in Postfix 3.6 and later:
+/* .IP "\fBrespectful_logging (see 'postconf -d' output)\fR"
+/* Avoid logging that implies white is better than black.
+/* TROUBLE SHOOTING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBpostscreen_expansion_filter (see 'postconf -d' output)\fR"
+/* List of characters that are permitted in postscreen_reject_footer
+/* attribute expansions.
+/* .IP "\fBpostscreen_reject_footer ($smtpd_reject_footer)\fR"
+/* Optional information that is appended after a 4XX or 5XX
+/* \fBpostscreen\fR(8) server
+/* response.
+/* .IP "\fBsoft_bounce (no)\fR"
+/* Safety net to keep mail queued that would otherwise be returned to
+/* the sender.
+/* BEFORE-POSTSCREEN PROXY AGENT
+/* .ad
+/* .fi
+/* Available in Postfix version 2.10 and later:
+/* .IP "\fBpostscreen_upstream_proxy_protocol (empty)\fR"
+/* The name of the proxy protocol used by an optional before-postscreen
+/* proxy agent.
+/* .IP "\fBpostscreen_upstream_proxy_timeout (5s)\fR"
+/* The time limit for the proxy protocol specified with the
+/* postscreen_upstream_proxy_protocol parameter.
+/* PERMANENT ALLOW/DENYLIST TEST
+/* .ad
+/* .fi
+/* This test is executed immediately after a remote SMTP client
+/* connects. If a client is permanently allowlisted, the client
+/* will be handed off immediately to a Postfix SMTP server
+/* process.
+/* .IP "\fBpostscreen_access_list (permit_mynetworks)\fR"
+/* Permanent allow/denylist for remote SMTP client IP addresses.
+/* .IP "\fBpostscreen_blacklist_action (ignore)\fR"
+/* Renamed to postscreen_denylist_action in Postfix 3.6.
+/* MAIL EXCHANGER POLICY TESTS
+/* .ad
+/* .fi
+/* When \fBpostscreen\fR(8) is configured to monitor all primary
+/* and backup MX addresses, it can refuse to allowlist clients
+/* that connect to a backup MX address only. For small sites,
+/* this requires configuring primary and backup MX addresses
+/* on the same MTA. Larger sites would have to share the
+/* \fBpostscreen\fR(8) cache between primary and backup MTAs,
+/* which would introduce a common point of failure.
+/* .IP "\fBpostscreen_whitelist_interfaces (static:all)\fR"
+/* Renamed to postscreen_allowlist_interfaces in Postfix 3.6.
+/* BEFORE 220 GREETING TESTS
+/* .ad
+/* .fi
+/* These tests are executed before the remote SMTP client
+/* receives the "220 servername" greeting. If no tests remain
+/* after the successful completion of this phase, the client
+/* will be handed off immediately to a Postfix SMTP server
+/* process.
+/* .IP "\fBdnsblog_service_name (dnsblog)\fR"
+/* The name of the \fBdnsblog\fR(8) service entry in master.cf.
+/* .IP "\fBpostscreen_dnsbl_action (ignore)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client's combined
+/* DNSBL score is equal to or greater than a threshold (as defined
+/* with the postscreen_dnsbl_sites and postscreen_dnsbl_threshold
+/* parameters).
+/* .IP "\fBpostscreen_dnsbl_reply_map (empty)\fR"
+/* A mapping from actual DNSBL domain name which includes a secret
+/* password, to the DNSBL domain name that postscreen will reply with
+/* when it rejects mail.
+/* .IP "\fBpostscreen_dnsbl_sites (empty)\fR"
+/* Optional list of DNS allow/denylist domains, filters and weight
+/* factors.
+/* .IP "\fBpostscreen_dnsbl_threshold (1)\fR"
+/* The inclusive lower bound for blocking a remote SMTP client, based on
+/* its combined DNSBL score as defined with the postscreen_dnsbl_sites
+/* parameter.
+/* .IP "\fBpostscreen_greet_action (ignore)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client speaks
+/* before its turn within the time specified with the postscreen_greet_wait
+/* parameter.
+/* .IP "\fBpostscreen_greet_banner ($smtpd_banner)\fR"
+/* The \fItext\fR in the optional "220-\fItext\fR..." server
+/* response that
+/* \fBpostscreen\fR(8) sends ahead of the real Postfix SMTP server's "220
+/* text..." response, in an attempt to confuse bad SMTP clients so
+/* that they speak before their turn (pre-greet).
+/* .IP "\fBpostscreen_greet_wait (normal: 6s, overload: 2s)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will wait for an SMTP
+/* client to send a command before its turn, and for DNS blocklist
+/* lookup results to arrive (default: up to 2 seconds under stress,
+/* up to 6 seconds otherwise).
+/* .IP "\fBsmtpd_service_name (smtpd)\fR"
+/* The internal service that \fBpostscreen\fR(8) hands off allowed
+/* connections to.
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBpostscreen_dnsbl_whitelist_threshold (0)\fR"
+/* Renamed to postscreen_dnsbl_allowlist_threshold in Postfix 3.6.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBpostscreen_dnsbl_timeout (10s)\fR"
+/* The time limit for DNSBL or DNSWL lookups.
+/* .PP
+/* Available in Postfix version 3.6 and later:
+/* .IP "\fBpostscreen_denylist_action (ignore)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client is
+/* permanently denylisted with the postscreen_access_list parameter.
+/* .IP "\fBpostscreen_allowlist_interfaces (static:all)\fR"
+/* A list of local \fBpostscreen\fR(8) server IP addresses where a
+/* non-allowlisted remote SMTP client can obtain \fBpostscreen\fR(8)'s temporary
+/* allowlist status.
+/* .IP "\fBpostscreen_dnsbl_allowlist_threshold (0)\fR"
+/* Allow a remote SMTP client to skip "before" and "after 220
+/* greeting" protocol tests, based on its combined DNSBL score as
+/* defined with the postscreen_dnsbl_sites parameter.
+/* AFTER 220 GREETING TESTS
+/* .ad
+/* .fi
+/* These tests are executed after the remote SMTP client
+/* receives the "220 servername" greeting. If a client passes
+/* all tests during this phase, it will receive a 4XX response
+/* to all RCPT TO commands. After the client reconnects, it
+/* will be allowed to talk directly to a Postfix SMTP server
+/* process.
+/* .IP "\fBpostscreen_bare_newline_action (ignore)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client sends
+/* a bare newline character, that is, a newline not preceded by carriage
+/* return.
+/* .IP "\fBpostscreen_bare_newline_enable (no)\fR"
+/* Enable "bare newline" SMTP protocol tests in the \fBpostscreen\fR(8)
+/* server.
+/* .IP "\fBpostscreen_disable_vrfy_command ($disable_vrfy_command)\fR"
+/* Disable the SMTP VRFY command in the \fBpostscreen\fR(8) daemon.
+/* .IP "\fBpostscreen_forbidden_commands ($smtpd_forbidden_commands)\fR"
+/* List of commands that the \fBpostscreen\fR(8) server considers in
+/* violation of the SMTP protocol.
+/* .IP "\fBpostscreen_helo_required ($smtpd_helo_required)\fR"
+/* Require that a remote SMTP client sends HELO or EHLO before
+/* commencing a MAIL transaction.
+/* .IP "\fBpostscreen_non_smtp_command_action (drop)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client sends
+/* non-SMTP commands as specified with the postscreen_forbidden_commands
+/* parameter.
+/* .IP "\fBpostscreen_non_smtp_command_enable (no)\fR"
+/* Enable "non-SMTP command" tests in the \fBpostscreen\fR(8) server.
+/* .IP "\fBpostscreen_pipelining_action (enforce)\fR"
+/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client
+/* sends
+/* multiple commands instead of sending one command and waiting for
+/* the server to respond.
+/* .IP "\fBpostscreen_pipelining_enable (no)\fR"
+/* Enable "pipelining" SMTP protocol tests in the \fBpostscreen\fR(8)
+/* server.
+/* CACHE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBpostscreen_cache_cleanup_interval (12h)\fR"
+/* The amount of time between \fBpostscreen\fR(8) cache cleanup runs.
+/* .IP "\fBpostscreen_cache_map (btree:$data_directory/postscreen_cache)\fR"
+/* Persistent storage for the \fBpostscreen\fR(8) server decisions.
+/* .IP "\fBpostscreen_cache_retention_time (7d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will cache an expired
+/* temporary allowlist entry before it is removed.
+/* .IP "\fBpostscreen_bare_newline_ttl (30d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will use the result from
+/* a successful "bare newline" SMTP protocol test.
+/* .IP "\fBpostscreen_dnsbl_max_ttl (${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h)\fR"
+/* The maximum amount of time that \fBpostscreen\fR(8) will use the
+/* result from a successful DNS-based reputation test before a
+/* client IP address is required to pass that test again.
+/* .IP "\fBpostscreen_dnsbl_min_ttl (60s)\fR"
+/* The minimum amount of time that \fBpostscreen\fR(8) will use the
+/* result from a successful DNS-based reputation test before a
+/* client IP address is required to pass that test again.
+/* .IP "\fBpostscreen_greet_ttl (1d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will use the result from
+/* a successful PREGREET test.
+/* .IP "\fBpostscreen_non_smtp_command_ttl (30d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will use the result from
+/* a successful "non_smtp_command" SMTP protocol test.
+/* .IP "\fBpostscreen_pipelining_ttl (30d)\fR"
+/* The amount of time that \fBpostscreen\fR(8) will use the result from
+/* a successful "pipelining" SMTP protocol test.
+/* RESOURCE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBline_length_limit (2048)\fR"
+/* Upon input, long lines are chopped up into pieces of at most
+/* this length; upon delivery, long lines are reconstructed.
+/* .IP "\fBpostscreen_client_connection_count_limit ($smtpd_client_connection_count_limit)\fR"
+/* How many simultaneous connections any remote SMTP client is
+/* allowed to have
+/* with the \fBpostscreen\fR(8) daemon.
+/* .IP "\fBpostscreen_command_count_limit (20)\fR"
+/* The limit on the total number of commands per SMTP session for
+/* \fBpostscreen\fR(8)'s built-in SMTP protocol engine.
+/* .IP "\fBpostscreen_command_time_limit (normal: 300s, overload: 10s)\fR"
+/* The time limit to read an entire command line with \fBpostscreen\fR(8)'s
+/* built-in SMTP protocol engine.
+/* .IP "\fBpostscreen_post_queue_limit ($default_process_limit)\fR"
+/* The number of clients that can be waiting for service from a
+/* real Postfix SMTP server process.
+/* .IP "\fBpostscreen_pre_queue_limit ($default_process_limit)\fR"
+/* The number of non-allowlisted clients that can be waiting for
+/* a decision whether they will receive service from a real Postfix
+/* SMTP server
+/* process.
+/* .IP "\fBpostscreen_watchdog_timeout (10s)\fR"
+/* How much time a \fBpostscreen\fR(8) process may take to respond to
+/* a remote SMTP client command or to perform a cache operation before it
+/* is terminated by a built-in watchdog timer.
+/* STARTTLS CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBpostscreen_tls_security_level ($smtpd_tls_security_level)\fR"
+/* The SMTP TLS security level for the \fBpostscreen\fR(8) server; when
+/* a non-empty value is specified, this overrides the obsolete parameters
+/* postscreen_use_tls and postscreen_enforce_tls.
+/* .IP "\fBtlsproxy_service_name (tlsproxy)\fR"
+/* The name of the \fBtlsproxy\fR(8) service entry in master.cf.
+/* OBSOLETE STARTTLS SUPPORT CONTROLS
+/* .ad
+/* .fi
+/* These parameters are supported for compatibility with
+/* \fBsmtpd\fR(8) legacy parameters.
+/* .IP "\fBpostscreen_use_tls ($smtpd_use_tls)\fR"
+/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+/* but do not require that clients use TLS encryption.
+/* .IP "\fBpostscreen_enforce_tls ($smtpd_enforce_tls)\fR"
+/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
+/* require that clients use TLS encryption.
+/* MISCELLANEOUS CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdelay_logging_resolution_limit (2)\fR"
+/* The maximal number of digits after the decimal point when logging
+/* sub-second delay values.
+/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR"
+/* The location of all postfix administrative commands.
+/* .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 "\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 "\fBsyslog_facility (mail)\fR"
+/* The syslog facility of Postfix logging.
+/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
+/* A prefix that is prepended to the process name in syslog
+/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* .PP
+/* Available in Postfix 3.5 and later:
+/* .IP "\fBinfo_log_address_format (external)\fR"
+/* The email address form that will be used in non-debug logging
+/* (info, warning, etc.).
+/* SEE ALSO
+/* smtpd(8), Postfix SMTP server
+/* tlsproxy(8), Postfix TLS proxy server
+/* dnsblog(8), DNS allow/denylist logger
+/* 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.
+/* .nf
+/* .na
+/* POSTSCREEN_README, Postfix Postscreen Howto
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* This service was introduced with Postfix version 2.8.
+/*
+/* Many ideas in \fBpostscreen\fR(8) were explored in earlier
+/* work by Michael Tokarev, in OpenBSD spamd, and in MailChannels
+/* Traffic Control.
+/* 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/stat.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <myaddrinfo.h>
+#include <dict_cache.h>
+#include <set_eugid.h>
+#include <vstream.h>
+#include <name_code.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <data_redirect.h>
+#include <string_list.h>
+
+/* Master server protocols. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Configuration parameters.
+ */
+char *var_smtpd_service;
+char *var_smtpd_banner;
+bool var_disable_vrfy_cmd;
+bool var_helo_required;
+
+char *var_smtpd_cmd_filter;
+char *var_psc_cmd_filter;
+
+char *var_smtpd_forbid_cmds;
+char *var_psc_forbid_cmds;
+
+char *var_smtpd_ehlo_dis_words;
+char *var_smtpd_ehlo_dis_maps;
+char *var_psc_ehlo_dis_words;
+char *var_psc_ehlo_dis_maps;
+
+char *var_smtpd_tls_level;
+bool var_smtpd_use_tls;
+bool var_smtpd_enforce_tls;
+char *var_psc_tls_level;
+bool var_psc_use_tls;
+bool var_psc_enforce_tls;
+
+bool var_psc_disable_vrfy;
+bool var_psc_helo_required;
+
+char *var_psc_cache_map;
+int var_psc_cache_scan;
+int var_psc_cache_ret;
+int var_psc_post_queue_limit;
+int var_psc_pre_queue_limit;
+int var_psc_watchdog;
+
+char *var_psc_acl;
+char *var_psc_dnlist_action;
+
+char *var_psc_greet_ttl;
+int var_psc_greet_wait;
+
+char *var_psc_pregr_banner;
+char *var_psc_pregr_action;
+int var_psc_pregr_ttl;
+
+char *var_psc_dnsbl_sites;
+char *var_psc_dnsbl_reply;
+int var_psc_dnsbl_thresh;
+int var_psc_dnsbl_althresh;
+char *var_psc_dnsbl_action;
+int var_psc_dnsbl_min_ttl;
+int var_psc_dnsbl_max_ttl;
+int var_psc_dnsbl_tmout;
+
+bool var_psc_pipel_enable;
+char *var_psc_pipel_action;
+int var_psc_pipel_ttl;
+
+bool var_psc_nsmtp_enable;
+char *var_psc_nsmtp_action;
+int var_psc_nsmtp_ttl;
+
+bool var_psc_barlf_enable;
+char *var_psc_barlf_action;
+int var_psc_barlf_ttl;
+
+int var_psc_cmd_count;
+int var_psc_cmd_time;
+
+char *var_dnsblog_service;
+char *var_tlsproxy_service;
+
+char *var_smtpd_rej_footer;
+char *var_psc_rej_footer;
+char *var_psc_rej_ftr_maps;
+
+int var_smtpd_cconn_limit;
+int var_psc_cconn_limit;
+
+char *var_smtpd_exp_filter;
+char *var_psc_exp_filter;
+
+char *var_psc_allist_if;
+char *var_psc_uproxy_proto;
+int var_psc_uproxy_tmout;
+
+ /*
+ * Global variables.
+ */
+int psc_check_queue_length; /* connections being checked */
+int psc_post_queue_length; /* being sent to real SMTPD */
+DICT_CACHE *psc_cache_map; /* cache table handle */
+VSTRING *psc_temp; /* scratchpad */
+char *psc_smtpd_service_name; /* path to real SMTPD */
+int psc_pregr_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_dnsbl_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_pipel_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_nsmtp_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_barlf_action; /* PSC_ACT_DROP/ENFORCE/etc */
+int psc_min_ttl; /* Update with new tests! */
+STRING_LIST *psc_forbid_cmds; /* CONNECT GET POST */
+int psc_stress_greet_wait; /* stressed greet wait */
+int psc_normal_greet_wait; /* stressed greet wait */
+int psc_stress_cmd_time_limit; /* stressed command limit */
+int psc_normal_cmd_time_limit; /* normal command time limit */
+int psc_stress; /* stress level */
+int psc_lowat_check_queue_length; /* stress low-water mark */
+int psc_hiwat_check_queue_length; /* stress high-water mark */
+DICT *psc_dnsbl_reply; /* DNSBL name mapper */
+HTABLE *psc_client_concurrency; /* per-client concurrency */
+
+ /*
+ * Local variables and functions.
+ */
+static ARGV *psc_acl; /* permanent allow/denylist */
+static int psc_dnlist_action; /* PSC_ACT_DROP/ENFORCE/etc */
+static ADDR_MATCH_LIST *psc_allist_if; /* allowlist interfaces */
+
+static void psc_endpt_lookup_done(int, VSTREAM *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *);
+
+/* psc_dump - dump some statistics before exit */
+
+static void psc_dump(char *unused_service, char **unused_argv)
+{
+
+ /*
+ * Dump preliminary cache cleanup statistics when the process commits
+ * suicide while a cache cleanup run is in progress. We can't currently
+ * distinguish between "postfix reload" (we should restart) or "maximal
+ * idle time reached" (we could finish the cache cleanup first).
+ */
+ if (psc_cache_map) {
+ dict_cache_close(psc_cache_map);
+ psc_cache_map = 0;
+ }
+}
+
+/* psc_drain - delayed exit after "postfix reload" */
+
+static void psc_drain(char *unused_service, char **unused_argv)
+{
+ int count;
+
+ /*
+ * After "postfix reload", complete work-in-progress in the background,
+ * instead of dropping already-accepted connections on the floor.
+ *
+ * Unfortunately we must close all writable tables, so we can't store or
+ * look up reputation information. The reason is that we don't have any
+ * multi-writer safety guarantees. We also can't use the single-writer
+ * proxywrite service, because its latency guarantees are too weak.
+ *
+ * All error retry counts shall be limited. Instead of blocking here, we
+ * could retry failed fork() operations in the event call-back routines,
+ * but we don't need perfection. The host system is severely overloaded
+ * and service levels are already way down.
+ *
+ * XXX Some Berkeley DB versions break with close-after-fork. Every new
+ * version is an improvement over its predecessor.
+ *
+ * XXX Don't assume that it is OK to share the same LMDB lockfile descriptor
+ * between different processes.
+ */
+ if (psc_cache_map != 0 /* XXX && psc_cache_map
+ requires locking */ ) {
+ dict_cache_close(psc_cache_map);
+ psc_cache_map = 0;
+ }
+ for (count = 0; /* see below */ ; count++) {
+ if (count >= 5) {
+ msg_fatal("fork: %m");
+ } else if (event_server_drain() != 0) {
+ msg_warn("fork: %m");
+ sleep(1);
+ continue;
+ } else {
+ return;
+ }
+ }
+}
+
+/* psc_service - handle new client connection */
+
+static void psc_service(VSTREAM *smtp_client_stream,
+ char *unused_service,
+ char **unused_argv)
+{
+
+ /*
+ * 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)
+ msg_fatal("all network protocols are disabled (%s = %s)",
+ VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * This program handles all incoming connections, so it must not block.
+ * We use event-driven code for all operations that introduce latency.
+ *
+ * Note: instead of using VSTREAM-level timeouts, we enforce limits on the
+ * total amount of time to receive a complete SMTP command line.
+ */
+ non_blocking(vstream_fileno(smtp_client_stream), NON_BLOCKING);
+
+ /*
+ * Look up the remote SMTP client address and port.
+ */
+ psc_endpt_lookup(smtp_client_stream, psc_endpt_lookup_done);
+}
+
+/* psc_warn_compat_respectful_logging - compatibility warning */
+
+static void psc_warn_compat_respectful_logging(PSC_STATE *state)
+{
+ msg_info("using backwards-compatible default setting "
+ VAR_RESPECTFUL_LOGGING "=no for client [%s]:%s",
+ PSC_CLIENT_ADDR_PORT(state));
+ warn_compat_respectful_logging = 0;
+}
+
+/* psc_endpt_lookup_done - endpoint lookup completed */
+
+static void psc_endpt_lookup_done(int endpt_status,
+ VSTREAM *smtp_client_stream,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port)
+{
+ const char *myname = "psc_endpt_lookup_done";
+ PSC_STATE *state;
+ const char *stamp_str;
+ int saved_flags;
+
+ /*
+ * Best effort - if this non-blocking write(2) fails, so be it.
+ */
+ if (endpt_status < 0) {
+ (void) write(vstream_fileno(smtp_client_stream),
+ "421 4.3.2 No system resources\r\n",
+ sizeof("421 4.3.2 No system resources\r\n") - 1);
+ event_server_disconnect(smtp_client_stream);
+ return;
+ }
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d connect from [%s]:%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ smtp_client_addr->buf, smtp_client_port->buf);
+
+ msg_info("CONNECT from [%s]:%s to [%s]:%s",
+ smtp_client_addr->buf, smtp_client_port->buf,
+ smtp_server_addr->buf, smtp_server_port->buf);
+
+ /*
+ * Bundle up all the loose session pieces. This zeroes all flags and time
+ * stamps.
+ */
+ state = psc_new_session_state(smtp_client_stream, smtp_client_addr->buf,
+ smtp_client_port->buf,
+ smtp_server_addr->buf,
+ smtp_server_port->buf);
+
+ /*
+ * Reply with 421 when the client has too many open connections.
+ */
+ if (var_psc_cconn_limit > 0
+ && state->client_info->concurrency > var_psc_cconn_limit) {
+ msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: too many connections",
+ state->smtp_client_addr, state->smtp_client_port);
+ PSC_DROP_SESSION_STATE(state,
+ "421 4.7.0 Error: too many connections\r\n");
+ return;
+ }
+
+ /*
+ * Reply with 421 when we can't forward more connections.
+ */
+ if (var_psc_post_queue_limit > 0
+ && psc_post_queue_length >= var_psc_post_queue_limit) {
+ msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: all server ports busy",
+ state->smtp_client_addr, state->smtp_client_port);
+ PSC_DROP_SESSION_STATE(state,
+ "421 4.3.2 All server ports are busy\r\n");
+ return;
+ }
+
+ /*
+ * The permanent allow/denylist has highest precedence.
+ */
+ if (psc_acl != 0) {
+ switch (psc_acl_eval(state, psc_acl, VAR_PSC_ACL)) {
+
+ /*
+ * Permanently denylisted.
+ */
+ case PSC_ACL_ACT_DENYLIST:
+ msg_info("%sLISTED [%s]:%s",
+ var_respectful_logging ? "DENY" : "BLACK",
+ PSC_CLIENT_ADDR_PORT(state));
+ if (warn_compat_respectful_logging)
+ psc_warn_compat_respectful_logging(state);
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNLIST_FAIL);
+ switch (psc_dnlist_action) {
+ case PSC_ACT_DROP:
+ PSC_DROP_SESSION_STATE(state,
+ "521 5.3.2 Service currently unavailable\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ PSC_ENFORCE_SESSION_STATE(state,
+ "550 5.3.2 Service currently unavailable\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNLIST_FAIL);
+
+ /*
+ * Not: PSC_PASS_SESSION_STATE. Repeat this test the next
+ * time.
+ */
+ break;
+ default:
+ msg_panic("%s: unknown denylist action value %d",
+ myname, psc_dnlist_action);
+ }
+ break;
+
+ /*
+ * Permanently allowlisted.
+ */
+ case PSC_ACL_ACT_ALLOWLIST:
+ msg_info("%sLISTED [%s]:%s",
+ var_respectful_logging ? "ALLOW" : "WHITE",
+ PSC_CLIENT_ADDR_PORT(state));
+ if (warn_compat_respectful_logging)
+ psc_warn_compat_respectful_logging(state);
+ psc_conclude(state);
+ return;
+
+ /*
+ * Other: dunno (don't know) or error.
+ */
+ default:
+ break;
+ }
+ }
+
+ /*
+ * The temporary allowlist (i.e. the postscreen cache) has the lowest
+ * precedence. This cache contains information about the results of prior
+ * tests. Allowlist the client when all enabled test results are still
+ * valid.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
+ && state->client_info->concurrency == 1
+ && psc_cache_map != 0
+ && (stamp_str = psc_cache_lookup(psc_cache_map, state->smtp_client_addr)) != 0) {
+ saved_flags = state->flags;
+ psc_parse_tests(state, stamp_str, event_time());
+ state->flags |= saved_flags;
+ if (msg_verbose)
+ msg_info("%s: cached + recent flags: %s",
+ myname, psc_print_state_flags(state->flags, myname));
+ if ((state->flags & PSC_STATE_MASK_ANY_TODO_FAIL) == 0) {
+ msg_info("PASS OLD [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
+ psc_conclude(state);
+ return;
+ }
+ } else if (state->client_info->concurrency > 1) {
+ saved_flags = state->flags;
+ psc_todo_tests(state, event_time());
+ state->flags |= saved_flags;
+ if (msg_verbose)
+ msg_info("%s: new + recent flags: %s",
+ myname, psc_print_state_flags(state->flags, myname));
+ } else {
+ saved_flags = state->flags;
+ psc_new_tests(state);
+ state->flags |= saved_flags;
+ if (msg_verbose)
+ msg_info("%s: new + recent flags: %s",
+ myname, psc_print_state_flags(state->flags, myname));
+ }
+
+ /*
+ * Don't allowlist clients that connect to backup MX addresses. Fail
+ * "closed" on error.
+ */
+ if (addr_match_list_match(psc_allist_if, smtp_server_addr->buf) == 0) {
+ state->flags |= (PSC_STATE_FLAG_ALLIST_FAIL | PSC_STATE_FLAG_NOFORWARD);
+ msg_info("%sLIST VETO [%s]:%s", var_respectful_logging ?
+ "ALLOW" : "WHITE", PSC_CLIENT_ADDR_PORT(state));
+ if (warn_compat_respectful_logging)
+ psc_warn_compat_respectful_logging(state);
+ }
+
+ /*
+ * Reply with 421 when we can't analyze more connections. That also means
+ * no deep protocol tests when the noforward flag is raised.
+ */
+ if (var_psc_pre_queue_limit > 0
+ && psc_check_queue_length - psc_post_queue_length
+ >= var_psc_pre_queue_limit) {
+ msg_info("reject: connect from [%s]:%s: all screening ports busy",
+ state->smtp_client_addr, state->smtp_client_port);
+ PSC_DROP_SESSION_STATE(state,
+ "421 4.3.2 All screening ports are busy\r\n");
+ return;
+ }
+
+ /*
+ * If the client has no up-to-date results for some tests, do those tests
+ * first. Otherwise, skip the tests and hand off the connection.
+ */
+ if (state->flags & PSC_STATE_MASK_EARLY_TODO)
+ psc_early_tests(state);
+ else if (state->flags & (PSC_STATE_MASK_SMTPD_TODO | PSC_STATE_FLAG_NOFORWARD))
+ psc_smtpd_tests(state);
+ else
+ psc_conclude(state);
+}
+
+/* psc_cache_validator - validate one cache entry */
+
+static int psc_cache_validator(const char *client_addr,
+ const char *stamp_str,
+ void *unused_context)
+{
+ PSC_STATE dummy_state;
+ PSC_CLIENT_INFO dummy_client_info;
+
+ /*
+ * This function is called by the cache cleanup pseudo thread.
+ *
+ * When an entry is removed from the cache, the client will be reported as
+ * "NEW" in the next session where it passes all tests again. To avoid
+ * silly logging we remove the cache entry only after all tests have
+ * expired longer ago than the cache retention time.
+ */
+ dummy_state.client_info = &dummy_client_info;
+ psc_parse_tests(&dummy_state, stamp_str, event_time() - var_psc_cache_ret);
+ return ((dummy_state.flags & PSC_STATE_MASK_ANY_TODO) == 0);
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+ VSTRING *redirect;
+
+ /*
+ * Open read-only maps before dropping privilege, for consistency with
+ * other Postfix daemons.
+ */
+ psc_acl_pre_jail_init(var_mynetworks, VAR_PSC_ACL);
+ if (*var_psc_acl)
+ psc_acl = psc_acl_parse(var_psc_acl, VAR_PSC_ACL);
+ /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */
+ if (*var_psc_forbid_cmds)
+ psc_forbid_cmds = string_list_init(VAR_PSC_FORBID_CMDS,
+ MATCH_FLAG_RETURN,
+ var_psc_forbid_cmds);
+ if (*var_psc_dnsbl_reply)
+ psc_dnsbl_reply = dict_open(var_psc_dnsbl_reply, O_RDONLY,
+ DICT_FLAG_DUP_WARN);
+
+ /*
+ * Never, ever, get killed by a master signal, as that would corrupt the
+ * database when we're in the middle of an update.
+ */
+ if (setsid() < 0)
+ msg_warn("setsid: %m");
+
+ /*
+ * Security: don't create root-owned files that contain untrusted data.
+ * And don't create Postfix-owned files in root-owned directories,
+ * either. We want a correct relationship between (file or directory)
+ * ownership and (file or directory) content. To open files before going
+ * to jail, temporarily drop root privileges.
+ */
+ SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid);
+ redirect = vstring_alloc(100);
+
+ /*
+ * Keep state in persistent external map. As a safety measure we sync the
+ * database on each update. This hurts on LINUX file systems that sync
+ * all dirty disk blocks whenever any application invokes fsync().
+ *
+ * Start the cache maintenance pseudo thread after dropping privileges.
+ */
+#define PSC_DICT_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \
+ DICT_FLAG_OPEN_LOCK)
+
+ if (*var_psc_cache_map)
+ psc_cache_map =
+ dict_cache_open(data_redirect_map(redirect, var_psc_cache_map),
+ O_CREAT | O_RDWR, PSC_DICT_OPEN_FLAGS);
+
+ /*
+ * Clean up and restore privilege.
+ */
+ vstring_free(redirect);
+ RESTORE_SAVED_EUGID();
+
+ /*
+ * Initialize the dummy SMTP engine.
+ */
+ psc_smtpd_pre_jail_init();
+}
+
+/* pre_accept - see if tables have changed */
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ static time_t last_event_time;
+ time_t new_event_time;
+ const char *name;
+
+ /*
+ * If some table has changed then stop accepting new connections. Don't
+ * check the tables more than once a second.
+ */
+ new_event_time = event_time();
+ if (new_event_time >= last_event_time + 1
+ && (name = dict_changed_name()) != 0) {
+ msg_info("table %s has changed - finishing in the background", name);
+ event_server_drain();
+ } else {
+ last_event_time = new_event_time;
+ }
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+ const NAME_CODE actions[] = {
+ PSC_NAME_ACT_DROP, PSC_ACT_DROP,
+ PSC_NAME_ACT_ENFORCE, PSC_ACT_ENFORCE,
+ PSC_NAME_ACT_IGNORE, PSC_ACT_IGNORE,
+ PSC_NAME_ACT_CONT, PSC_ACT_IGNORE, /* compatibility */
+ 0, -1,
+ };
+ int cache_flags;
+ const char *tmp;
+
+ /*
+ * This routine runs after the skeleton code has entered the chroot jail.
+ * Prevent automatic process suicide after a limited number of client
+ * requests. It is OK to terminate after a limited amount of idle time.
+ */
+ var_use_limit = 0;
+
+ /*
+ * Workaround for parameters whose values may contain "$", and that have
+ * a default of "$parametername". Not sure if it would be a good idea to
+ * always to this in the mail_conf_raw(3) module.
+ */
+ if (*var_psc_rej_footer == '$'
+ && mail_conf_lookup(var_psc_rej_footer + 1)) {
+ tmp = mail_conf_eval_once(var_psc_rej_footer);
+ myfree(var_psc_rej_footer);
+ var_psc_rej_footer = mystrdup(tmp);
+ }
+ if (*var_psc_exp_filter == '$'
+ && mail_conf_lookup(var_psc_exp_filter + 1)) {
+ tmp = mail_conf_eval_once(var_psc_exp_filter);
+ myfree(var_psc_exp_filter);
+ var_psc_exp_filter = mystrdup(tmp);
+ }
+
+ /*
+ * Other one-time initialization.
+ */
+ psc_temp = vstring_alloc(10);
+ vstring_sprintf(psc_temp, "%s/%s", MAIL_CLASS_PRIVATE, var_smtpd_service);
+ psc_smtpd_service_name = mystrdup(STR(psc_temp));
+ psc_dnsbl_init();
+ psc_early_init();
+ psc_smtpd_init();
+
+ if ((psc_dnlist_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_dnlist_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_DNLIST_ACTION,
+ var_psc_dnlist_action);
+ if ((psc_dnsbl_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_dnsbl_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_DNSBL_ACTION,
+ var_psc_dnsbl_action);
+ if ((psc_pregr_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_pregr_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_PREGR_ACTION,
+ var_psc_pregr_action);
+ if ((psc_pipel_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_pipel_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_PIPEL_ACTION,
+ var_psc_pipel_action);
+ if ((psc_nsmtp_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_nsmtp_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_NSMTP_ACTION,
+ var_psc_nsmtp_action);
+ if ((psc_barlf_action = name_code(actions, NAME_CODE_FLAG_NONE,
+ var_psc_barlf_action)) < 0)
+ msg_fatal("bad %s value: %s", VAR_PSC_BARLF_ACTION,
+ var_psc_barlf_action);
+ /* Fail "closed" on error. */
+ psc_allist_if = addr_match_list_init(VAR_PSC_ALLIST_IF, MATCH_FLAG_RETURN,
+ var_psc_allist_if);
+
+ /*
+ * Start the cache maintenance pseudo thread last. Early cleanup makes
+ * verbose logging more informative (we get positive confirmation that
+ * the cleanup thread runs).
+ */
+ cache_flags = DICT_CACHE_FLAG_STATISTICS;
+ if (msg_verbose > 1)
+ cache_flags |= DICT_CACHE_FLAG_VERBOSE;
+ if (psc_cache_map != 0 && var_psc_cache_scan > 0)
+ dict_cache_control(psc_cache_map,
+ CA_DICT_CACHE_CTL_FLAGS(cache_flags),
+ CA_DICT_CACHE_CTL_INTERVAL(var_psc_cache_scan),
+ CA_DICT_CACHE_CTL_VALIDATOR(psc_cache_validator),
+ CA_DICT_CACHE_CTL_CONTEXT((void *) 0),
+ CA_DICT_CACHE_CTL_END);
+
+ /*
+ * Pre-compute the minimal and maximal TTL.
+ */
+ psc_min_ttl =
+ PSC_MIN(PSC_MIN(var_psc_pregr_ttl, var_psc_dnsbl_min_ttl),
+ PSC_MIN(PSC_MIN(var_psc_pipel_ttl, var_psc_nsmtp_ttl),
+ var_psc_barlf_ttl));
+
+ /*
+ * Pre-compute the stress and normal command time limits.
+ */
+ mail_conf_update(VAR_STRESS, "yes");
+ psc_stress_cmd_time_limit =
+ get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0);
+ psc_stress_greet_wait =
+ get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0);
+
+ mail_conf_update(VAR_STRESS, "");
+ psc_normal_cmd_time_limit =
+ get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0);
+ psc_normal_greet_wait =
+ get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0);
+
+ psc_lowat_check_queue_length = .7 * var_psc_pre_queue_limit;
+ psc_hiwat_check_queue_length = .9 * var_psc_pre_queue_limit;
+ if (msg_verbose)
+ msg_info(VAR_PSC_CMD_TIME ": stress=%d normal=%d lowat=%d hiwat=%d",
+ psc_stress_cmd_time_limit, psc_normal_cmd_time_limit,
+ psc_lowat_check_queue_length, psc_hiwat_check_queue_length);
+
+ if (psc_lowat_check_queue_length == 0)
+ msg_panic("compiler error: 0.7 * %d = %d", var_psc_pre_queue_limit,
+ psc_lowat_check_queue_length);
+ if (psc_hiwat_check_queue_length == 0)
+ msg_panic("compiler error: 0.9 * %d = %d", var_psc_pre_queue_limit,
+ psc_hiwat_check_queue_length);
+
+ /*
+ * Per-client concurrency.
+ */
+ psc_client_concurrency = htable_create(var_psc_pre_queue_limit);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+
+ /*
+ * List smtpd(8) parameters before any postscreen(8) parameters that have
+ * defaults dependencies on them.
+ */
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_SMTPD_SERVICE, DEF_SMTPD_SERVICE, &var_smtpd_service, 1, 0,
+ VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0,
+ VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0,
+ VAR_SMTPD_EHLO_DIS_WORDS, DEF_SMTPD_EHLO_DIS_WORDS, &var_smtpd_ehlo_dis_words, 0, 0,
+ VAR_SMTPD_EHLO_DIS_MAPS, DEF_SMTPD_EHLO_DIS_MAPS, &var_smtpd_ehlo_dis_maps, 0, 0,
+ VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0,
+ VAR_SMTPD_CMD_FILTER, DEF_SMTPD_CMD_FILTER, &var_smtpd_cmd_filter, 0, 0,
+ VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0,
+ VAR_PSC_PREGR_BANNER, DEF_PSC_PREGR_BANNER, &var_psc_pregr_banner, 0, 0,
+ VAR_PSC_PREGR_ACTION, DEF_PSC_PREGR_ACTION, &var_psc_pregr_action, 1, 0,
+ VAR_PSC_DNSBL_SITES, DEF_PSC_DNSBL_SITES, &var_psc_dnsbl_sites, 0, 0,
+ VAR_PSC_DNSBL_ACTION, DEF_PSC_DNSBL_ACTION, &var_psc_dnsbl_action, 1, 0,
+ VAR_PSC_PIPEL_ACTION, DEF_PSC_PIPEL_ACTION, &var_psc_pipel_action, 1, 0,
+ VAR_PSC_NSMTP_ACTION, DEF_PSC_NSMTP_ACTION, &var_psc_nsmtp_action, 1, 0,
+ VAR_PSC_BARLF_ACTION, DEF_PSC_BARLF_ACTION, &var_psc_barlf_action, 1, 0,
+ VAR_PSC_ACL, DEF_PSC_ACL, &var_psc_acl, 0, 0,
+ VAR_PSC_DNLIST_ACTION, DEF_PSC_DNLIST_ACTION, &var_psc_dnlist_action, 1, 0,
+ VAR_PSC_FORBID_CMDS, DEF_PSC_FORBID_CMDS, &var_psc_forbid_cmds, 0, 0,
+ VAR_PSC_EHLO_DIS_WORDS, DEF_PSC_EHLO_DIS_WORDS, &var_psc_ehlo_dis_words, 0, 0,
+ VAR_PSC_EHLO_DIS_MAPS, DEF_PSC_EHLO_DIS_MAPS, &var_psc_ehlo_dis_maps, 0, 0,
+ VAR_PSC_DNSBL_REPLY, DEF_PSC_DNSBL_REPLY, &var_psc_dnsbl_reply, 0, 0,
+ VAR_PSC_TLS_LEVEL, DEF_PSC_TLS_LEVEL, &var_psc_tls_level, 0, 0,
+ VAR_PSC_CMD_FILTER, DEF_PSC_CMD_FILTER, &var_psc_cmd_filter, 0, 0,
+ VAR_DNSBLOG_SERVICE, DEF_DNSBLOG_SERVICE, &var_dnsblog_service, 1, 0,
+ VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0,
+ VAR_PSC_ALLIST_IF, DEF_PSC_ALLIST_IF, &var_psc_allist_if, 0, 0,
+ VAR_PSC_UPROXY_PROTO, DEF_PSC_UPROXY_PROTO, &var_psc_uproxy_proto, 0, 0,
+ VAR_PSC_REJ_FTR_MAPS, DEF_PSC_REJ_FTR_MAPS, &var_psc_rej_ftr_maps, 0, 0,
+ 0,
+ };
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_PSC_DNSBL_THRESH, DEF_PSC_DNSBL_THRESH, &var_psc_dnsbl_thresh, 1, 0,
+ VAR_PSC_CMD_COUNT, DEF_PSC_CMD_COUNT, &var_psc_cmd_count, 1, 0,
+ VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0,
+ 0,
+ };
+ static const CONFIG_NINT_TABLE nint_table[] = {
+ VAR_PSC_POST_QLIMIT, DEF_PSC_POST_QLIMIT, &var_psc_post_queue_limit, 5, 0,
+ VAR_PSC_PRE_QLIMIT, DEF_PSC_PRE_QLIMIT, &var_psc_pre_queue_limit, 10, 0,
+ VAR_PSC_CCONN_LIMIT, DEF_PSC_CCONN_LIMIT, &var_psc_cconn_limit, 0, 0,
+ VAR_PSC_DNSBL_ALTHRESH, DEF_PSC_DNSBL_ALTHRESH, &var_psc_dnsbl_althresh, 0, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, &var_psc_cmd_time, 1, 0,
+ VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, &var_psc_greet_wait, 1, 0,
+ VAR_PSC_PREGR_TTL, DEF_PSC_PREGR_TTL, &var_psc_pregr_ttl, 1, 0,
+ VAR_PSC_DNSBL_MIN_TTL, DEF_PSC_DNSBL_MIN_TTL, &var_psc_dnsbl_min_ttl, 1, 0,
+ VAR_PSC_DNSBL_MAX_TTL, DEF_PSC_DNSBL_MAX_TTL, &var_psc_dnsbl_max_ttl, 1, 0,
+ VAR_PSC_PIPEL_TTL, DEF_PSC_PIPEL_TTL, &var_psc_pipel_ttl, 1, 0,
+ VAR_PSC_NSMTP_TTL, DEF_PSC_NSMTP_TTL, &var_psc_nsmtp_ttl, 1, 0,
+ VAR_PSC_BARLF_TTL, DEF_PSC_BARLF_TTL, &var_psc_barlf_ttl, 1, 0,
+ VAR_PSC_CACHE_RET, DEF_PSC_CACHE_RET, &var_psc_cache_ret, 1, 0,
+ VAR_PSC_CACHE_SCAN, DEF_PSC_CACHE_SCAN, &var_psc_cache_scan, 0, 0,
+ VAR_PSC_WATCHDOG, DEF_PSC_WATCHDOG, &var_psc_watchdog, 10, 0,
+ VAR_PSC_UPROXY_TMOUT, DEF_PSC_UPROXY_TMOUT, &var_psc_uproxy_tmout, 1, 0,
+ VAR_PSC_DNSBL_TMOUT, DEF_PSC_DNSBL_TMOUT, &var_psc_dnsbl_tmout, 1, 0,
+
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_HELO_REQUIRED, DEF_HELO_REQUIRED, &var_helo_required,
+ VAR_DISABLE_VRFY_CMD, DEF_DISABLE_VRFY_CMD, &var_disable_vrfy_cmd,
+ VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls,
+ VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls,
+ VAR_PSC_PIPEL_ENABLE, DEF_PSC_PIPEL_ENABLE, &var_psc_pipel_enable,
+ VAR_PSC_NSMTP_ENABLE, DEF_PSC_NSMTP_ENABLE, &var_psc_nsmtp_enable,
+ VAR_PSC_BARLF_ENABLE, DEF_PSC_BARLF_ENABLE, &var_psc_barlf_enable,
+ 0,
+ };
+ static const CONFIG_RAW_TABLE raw_table[] = {
+ VAR_SMTPD_REJ_FOOTER, DEF_SMTPD_REJ_FOOTER, &var_smtpd_rej_footer, 0, 0,
+ VAR_PSC_REJ_FOOTER, DEF_PSC_REJ_FOOTER, &var_psc_rej_footer, 0, 0,
+ VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter, 1, 0,
+ VAR_PSC_EXP_FILTER, DEF_PSC_EXP_FILTER, &var_psc_exp_filter, 1, 0,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_PSC_HELO_REQUIRED, DEF_PSC_HELO_REQUIRED, &var_psc_helo_required,
+ VAR_PSC_DISABLE_VRFY, DEF_PSC_DISABLE_VRFY, &var_psc_disable_vrfy,
+ VAR_PSC_USE_TLS, DEF_PSC_USE_TLS, &var_psc_use_tls,
+ VAR_PSC_ENFORCE_TLS, DEF_PSC_ENFORCE_TLS, &var_psc_enforce_tls,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ event_server_main(argc, argv, psc_service,
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_NINT_TABLE(nint_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_RAW_TABLE(raw_table),
+ CA_MAIL_SERVER_NBOOL_TABLE(nbool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_SOLITARY,
+ CA_MAIL_SERVER_SLOW_EXIT(psc_drain),
+ CA_MAIL_SERVER_EXIT(psc_dump),
+ CA_MAIL_SERVER_WATCHDOG(&var_psc_watchdog),
+ 0);
+}
diff --git a/src/postscreen/postscreen.h b/src/postscreen/postscreen.h
new file mode 100644
index 0000000..69a5e17
--- /dev/null
+++ b/src/postscreen/postscreen.h
@@ -0,0 +1,599 @@
+/*++
+/* NAME
+/* postscreen 3h
+/* SUMMARY
+/* postscreen internal interfaces
+/* SYNOPSIS
+/* #include <postscreen.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+
+ /*
+ * Utility library.
+ */
+#include <dict_cache.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <events.h>
+#include <htable.h>
+#include <myaddrinfo.h>
+
+ /*
+ * Global library.
+ */
+#include <addr_match_list.h>
+#include <string_list.h>
+#include <maps.h>
+#include <server_acl.h>
+
+ /*
+ * Preliminary stuff, to be fixed.
+ */
+#define PSC_READ_BUF_SIZE 1024
+
+ /*
+ * Numeric indices and symbolic names for tests whose time stamps and status
+ * flags can be accessed by numeric index.
+ */
+#define PSC_TINDX_PREGR 0 /* pregreet */
+#define PSC_TINDX_DNSBL 1 /* dnsbl */
+#define PSC_TINDX_PIPEL 2 /* pipelining */
+#define PSC_TINDX_NSMTP 3 /* non-smtp command */
+#define PSC_TINDX_BARLF 4 /* bare newline */
+#define PSC_TINDX_COUNT 5 /* number of tests */
+
+#define PSC_TNAME_PREGR "pregreet"
+#define PSC_TNAME_DNSBL "dnsbl"
+#define PSC_TNAME_PIPEL "pipelining"
+#define PSC_TNAME_NSMTP "non-smtp command"
+#define PSC_TNAME_BARLF "bare newline"
+
+#define PSC_TINDX_BYTNAME(tname) (PSC_TINDX_ ## tname)
+
+ /*
+ * Per-client shared state.
+ */
+typedef struct {
+ int concurrency; /* per-client */
+ int pass_new_count; /* per-client */
+ time_t expire_time[PSC_TINDX_COUNT]; /* per-test expiration */
+} PSC_CLIENT_INFO;
+
+ /*
+ * Per-session state.
+ */
+typedef struct {
+ int flags; /* see below */
+ /* Socket state. */
+ VSTREAM *smtp_client_stream; /* remote SMTP client */
+ int smtp_server_fd; /* real SMTP server */
+ char *smtp_client_addr; /* client address */
+ char *smtp_client_port; /* client port */
+ char *smtp_server_addr; /* server address */
+ char *smtp_server_port; /* server port */
+ const char *final_reply; /* cause for hanging up */
+ VSTRING *send_buf; /* pending output */
+ /* Test context. */
+ struct timeval start_time; /* start of current test */
+ const char *test_name; /* name of current test */
+ PSC_CLIENT_INFO *client_info; /* shared client state */
+ VSTRING *dnsbl_reply; /* dnsbl reject text */
+ int dnsbl_score; /* saved DNSBL score */
+ int dnsbl_ttl; /* saved DNSBL TTL */
+ const char *dnsbl_name; /* DNSBL name with largest weight */
+ int dnsbl_index; /* dnsbl request index */
+ const char *rcpt_reply; /* how to reject recipients */
+ int command_count; /* error + junk command count */
+ const char *protocol; /* SMTP or ESMTP */
+ char *helo_name; /* SMTP helo/ehlo */
+ char *sender; /* MAIL FROM */
+ VSTRING *cmd_buffer; /* command read buffer */
+ int read_state; /* command read state machine */
+ /* smtpd(8) compatibility */
+ int ehlo_discard_mask; /* EHLO filter */
+ VSTRING *expand_buf; /* macro expansion */
+ const char *where; /* SMTP protocol state */
+} PSC_STATE;
+
+ /*
+ * Special expiration time values.
+ */
+#define PSC_TIME_STAMP_NEW (0) /* test was never passed */
+#define PSC_TIME_STAMP_DISABLED (1) /* never passed but disabled */
+#define PSC_TIME_STAMP_INVALID (-1) /* must not be cached */
+
+ /*
+ * Status flags.
+ */
+#define PSC_STATE_FLAG_NOFORWARD (1<<0) /* don't forward this session */
+#define PSC_STATE_FLAG_USING_TLS (1<<1) /* using the TLS proxy */
+#define PSC_STATE_FLAG_UNUSED2 (1<<2) /* use me! */
+#define PSC_STATE_FLAG_NEW (1<<3) /* some test was never passed */
+#define PSC_STATE_FLAG_DNLIST_FAIL (1<<4) /* denylisted */
+#define PSC_STATE_FLAG_HANGUP (1<<5) /* NOT a test failure */
+#define PSC_STATE_FLAG_SMTPD_X21 (1<<6) /* hang up after command */
+#define PSC_STATE_FLAG_ALLIST_FAIL (1<<7) /* do not allowlist */
+#define PSC_STATE_FLAG_TEST_BASE (8) /* start of indexable flags */
+
+ /*
+ * Tests whose flags and expiration time can be accessed by numerical index.
+ *
+ * Important: every MUMBLE_TODO flag must have a MUMBLE_PASS flag, such that
+ * MUMBLE_PASS == PSC_STATE_FLAGS_TODO_TO_PASS(MUMBLE_TODO).
+ *
+ * MUMBLE_TODO flags must not be cleared once raised. The _TODO_TO_PASS and
+ * _TODO_TO_DONE macros depend on this to decide that a group of tests is
+ * passed or completed.
+ *
+ * MUMBLE_DONE flags are used for "early" tests that have final results.
+ *
+ * MUMBLE_SKIP flags are used for "deep" tests where the client messed up.
+ * These flags look like MUMBLE_DONE but they are different. Deep tests can
+ * tentatively pass, but can still fail later in a session. The "ignore"
+ * action introduces an additional complication. MUMBLE_PASS indicates
+ * either that a deep test passed tentatively, or that the test failed but
+ * the result was ignored. MUMBLE_FAIL, on the other hand, is always final.
+ * We use MUMBLE_SKIP to indicate that a decision was either "fail" or
+ * forced "pass".
+ *
+ * The difference between DONE and SKIP is in the beholder's eye. These flags
+ * share the same bit.
+ */
+#define PSC_STATE_FLAGS_TODO_TO_PASS(todo_flags) ((todo_flags) >> 1)
+#define PSC_STATE_FLAGS_TODO_TO_DONE(todo_flags) ((todo_flags) << 1)
+
+#define PSC_STATE_FLAG_SHIFT_FAIL (0) /* failed test */
+#define PSC_STATE_FLAG_SHIFT_PASS (1) /* passed test */
+#define PSC_STATE_FLAG_SHIFT_TODO (2) /* expired test */
+#define PSC_STATE_FLAG_SHIFT_DONE (3) /* decision is final */
+#define PSC_STATE_FLAG_SHIFT_SKIP (3) /* action is already logged */
+#define PSC_STATE_FLAG_SHIFT_STRIDE (4) /* nr of flags per test */
+
+#define PSC_STATE_FLAG_SHIFT_BYFNAME(fname) (PSC_STATE_FLAG_SHIFT_ ## fname)
+
+ /*
+ * Indexable per-test flags. These are used for DNS allowlisting multiple
+ * tests, without needing per-test ad-hoc code.
+ */
+#define PSC_STATE_FLAG_BYTINDX_FNAME(tindx, fname) \
+ (1U << (PSC_STATE_FLAG_TEST_BASE \
+ + PSC_STATE_FLAG_SHIFT_STRIDE * (tindx) \
+ + PSC_STATE_FLAG_SHIFT_BYFNAME(fname)))
+
+#define PSC_STATE_FLAG_BYTINDX_FAIL(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), FAIL)
+#define PSC_STATE_FLAG_BYTINDX_PASS(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), PASS)
+#define PSC_STATE_FLAG_BYTINDX_TODO(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), TODO)
+#define PSC_STATE_FLAG_BYTINDX_DONE(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), DONE)
+#define PSC_STATE_FLAG_BYTINDX_SKIP(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), SKIP)
+
+ /*
+ * Flags with distinct names. These are used in the per-test ad-hoc code.
+ */
+#define PSC_STATE_FLAG_BYTNAME_FNAME(tname, fname) \
+ (1U << (PSC_STATE_FLAG_TEST_BASE \
+ + PSC_STATE_FLAG_SHIFT_STRIDE * PSC_TINDX_BYTNAME(tname) \
+ + PSC_STATE_FLAG_SHIFT_BYFNAME(fname)))
+
+#define PSC_STATE_FLAG_PREGR_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, FAIL)
+#define PSC_STATE_FLAG_PREGR_PASS PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, PASS)
+#define PSC_STATE_FLAG_PREGR_TODO PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, TODO)
+#define PSC_STATE_FLAG_PREGR_DONE PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, DONE)
+
+#define PSC_STATE_FLAG_DNSBL_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, FAIL)
+#define PSC_STATE_FLAG_DNSBL_PASS PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, PASS)
+#define PSC_STATE_FLAG_DNSBL_TODO PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, TODO)
+#define PSC_STATE_FLAG_DNSBL_DONE PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, DONE)
+
+#define PSC_STATE_FLAG_PIPEL_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, FAIL)
+#define PSC_STATE_FLAG_PIPEL_PASS PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, PASS)
+#define PSC_STATE_FLAG_PIPEL_TODO PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, TODO)
+#define PSC_STATE_FLAG_PIPEL_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, SKIP)
+
+#define PSC_STATE_FLAG_NSMTP_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, FAIL)
+#define PSC_STATE_FLAG_NSMTP_PASS PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, PASS)
+#define PSC_STATE_FLAG_NSMTP_TODO PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, TODO)
+#define PSC_STATE_FLAG_NSMTP_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, SKIP)
+
+#define PSC_STATE_FLAG_BARLF_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, FAIL)
+#define PSC_STATE_FLAG_BARLF_PASS PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, PASS)
+#define PSC_STATE_FLAG_BARLF_TODO PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, TODO)
+#define PSC_STATE_FLAG_BARLF_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, SKIP)
+
+ /*
+ * Aggregates for individual tests.
+ */
+#define PSC_STATE_MASK_PREGR_TODO_FAIL \
+ (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_PREGR_FAIL)
+#define PSC_STATE_MASK_DNSBL_TODO_FAIL \
+ (PSC_STATE_FLAG_DNSBL_TODO | PSC_STATE_FLAG_DNSBL_FAIL)
+#define PSC_STATE_MASK_PIPEL_TODO_FAIL \
+ (PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_PIPEL_FAIL)
+#define PSC_STATE_MASK_NSMTP_TODO_FAIL \
+ (PSC_STATE_FLAG_NSMTP_TODO | PSC_STATE_FLAG_NSMTP_FAIL)
+#define PSC_STATE_MASK_BARLF_TODO_FAIL \
+ (PSC_STATE_FLAG_BARLF_TODO | PSC_STATE_FLAG_BARLF_FAIL)
+
+#define PSC_STATE_MASK_PREGR_TODO_DONE \
+ (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_PREGR_DONE)
+#define PSC_STATE_MASK_PIPEL_TODO_SKIP \
+ (PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_PIPEL_SKIP)
+#define PSC_STATE_MASK_NSMTP_TODO_SKIP \
+ (PSC_STATE_FLAG_NSMTP_TODO | PSC_STATE_FLAG_NSMTP_SKIP)
+#define PSC_STATE_MASK_BARLF_TODO_SKIP \
+ (PSC_STATE_FLAG_BARLF_TODO | PSC_STATE_FLAG_BARLF_SKIP)
+
+#define PSC_STATE_MASK_PREGR_FAIL_DONE \
+ (PSC_STATE_FLAG_PREGR_FAIL | PSC_STATE_FLAG_PREGR_DONE)
+
+#define PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL \
+ (PSC_STATE_MASK_PIPEL_TODO_FAIL | PSC_STATE_FLAG_PIPEL_PASS)
+#define PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL \
+ (PSC_STATE_MASK_NSMTP_TODO_FAIL | PSC_STATE_FLAG_NSMTP_PASS)
+#define PSC_STATE_MASK_BARLF_TODO_PASS_FAIL \
+ (PSC_STATE_MASK_BARLF_TODO_FAIL | PSC_STATE_FLAG_BARLF_PASS)
+
+ /*
+ * Separate aggregates for early tests and deep tests.
+ */
+#define PSC_STATE_MASK_EARLY_DONE \
+ (PSC_STATE_FLAG_PREGR_DONE | PSC_STATE_FLAG_DNSBL_DONE)
+#define PSC_STATE_MASK_EARLY_TODO \
+ (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_DNSBL_TODO)
+#define PSC_STATE_MASK_EARLY_PASS \
+ (PSC_STATE_FLAG_PREGR_PASS | PSC_STATE_FLAG_DNSBL_PASS)
+#define PSC_STATE_MASK_EARLY_FAIL \
+ (PSC_STATE_FLAG_PREGR_FAIL | PSC_STATE_FLAG_DNSBL_FAIL)
+
+#define PSC_STATE_MASK_SMTPD_TODO \
+ (PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_NSMTP_TODO | \
+ PSC_STATE_FLAG_BARLF_TODO)
+#define PSC_STATE_MASK_SMTPD_PASS \
+ (PSC_STATE_FLAG_PIPEL_PASS | PSC_STATE_FLAG_NSMTP_PASS | \
+ PSC_STATE_FLAG_BARLF_PASS)
+#define PSC_STATE_MASK_SMTPD_FAIL \
+ (PSC_STATE_FLAG_PIPEL_FAIL | PSC_STATE_FLAG_NSMTP_FAIL | \
+ PSC_STATE_FLAG_BARLF_FAIL)
+
+ /*
+ * Super-aggregates for all tests combined.
+ */
+#define PSC_STATE_MASK_ANY_FAIL \
+ (PSC_STATE_FLAG_DNLIST_FAIL | \
+ PSC_STATE_MASK_EARLY_FAIL | PSC_STATE_MASK_SMTPD_FAIL | \
+ PSC_STATE_FLAG_ALLIST_FAIL)
+
+#define PSC_STATE_MASK_ANY_PASS \
+ (PSC_STATE_MASK_EARLY_PASS | PSC_STATE_MASK_SMTPD_PASS)
+
+#define PSC_STATE_MASK_ANY_TODO \
+ (PSC_STATE_MASK_EARLY_TODO | PSC_STATE_MASK_SMTPD_TODO)
+
+#define PSC_STATE_MASK_ANY_TODO_FAIL \
+ (PSC_STATE_MASK_ANY_TODO | PSC_STATE_MASK_ANY_FAIL)
+
+#define PSC_STATE_MASK_ANY_UPDATE \
+ (PSC_STATE_MASK_ANY_PASS)
+
+ /*
+ * Meta-commands for state->where that reflect the initial command processor
+ * state and commands that aren't implemented.
+ */
+#define PSC_SMTPD_CMD_CONNECT "CONNECT"
+#define PSC_SMTPD_CMD_UNIMPL "UNIMPLEMENTED"
+
+ /*
+ * See log_adhoc.c for discussion.
+ */
+typedef struct {
+ int dt_sec; /* make sure it's signed */
+ int dt_usec; /* make sure it's signed */
+} DELTA_TIME;
+
+#define PSC_CALC_DELTA(x, y, z) \
+ do { \
+ (x).dt_sec = (y).tv_sec - (z).tv_sec; \
+ (x).dt_usec = (y).tv_usec - (z).tv_usec; \
+ while ((x).dt_usec < 0) { \
+ (x).dt_usec += 1000000; \
+ (x).dt_sec -= 1; \
+ } \
+ while ((x).dt_usec >= 1000000) { \
+ (x).dt_usec -= 1000000; \
+ (x).dt_sec += 1; \
+ } \
+ if ((x).dt_sec < 0) \
+ (x).dt_sec = (x).dt_usec = 0; \
+ } while (0)
+
+#define SIG_DIGS 2
+
+ /*
+ * Event management.
+ */
+
+/* PSC_READ_EVENT_REQUEST - prepare for transition to next state */
+
+#define PSC_READ_EVENT_REQUEST(fd, action, context, timeout) do { \
+ if (msg_verbose > 1) \
+ msg_info("%s: read-request fd=%d", myname, (fd)); \
+ event_enable_read((fd), (action), (context)); \
+ event_request_timer((action), (context), (timeout)); \
+ } while (0)
+
+#define PSC_READ_EVENT_REQUEST2(fd, read_act, time_act, context, timeout) do { \
+ if (msg_verbose > 1) \
+ msg_info("%s: read-request fd=%d", myname, (fd)); \
+ event_enable_read((fd), (read_act), (context)); \
+ event_request_timer((time_act), (context), (timeout)); \
+ } while (0)
+
+/* PSC_CLEAR_EVENT_REQUEST - complete state transition */
+
+#define PSC_CLEAR_EVENT_REQUEST(fd, time_act, context) do { \
+ if (msg_verbose > 1) \
+ msg_info("%s: clear-request fd=%d", myname, (fd)); \
+ event_disable_readwrite(fd); \
+ event_cancel_timer((time_act), (context)); \
+ } while (0)
+
+ /*
+ * Failure enforcement policies.
+ */
+#define PSC_NAME_ACT_DROP "drop"
+#define PSC_NAME_ACT_ENFORCE "enforce"
+#define PSC_NAME_ACT_IGNORE "ignore"
+#define PSC_NAME_ACT_CONT "continue"
+
+#define PSC_ACT_DROP 1
+#define PSC_ACT_ENFORCE 2
+#define PSC_ACT_IGNORE 3
+
+ /*
+ * Global variables.
+ */
+extern int psc_check_queue_length; /* connections being checked */
+extern int psc_post_queue_length; /* being sent to real SMTPD */
+extern DICT_CACHE *psc_cache_map; /* cache table handle */
+extern VSTRING *psc_temp; /* scratchpad */
+extern char *psc_smtpd_service_name; /* path to real SMTPD */
+extern int psc_pregr_action; /* PSC_ACT_DROP etc. */
+extern int psc_dnsbl_action; /* PSC_ACT_DROP etc. */
+extern int psc_pipel_action; /* PSC_ACT_DROP etc. */
+extern int psc_nsmtp_action; /* PSC_ACT_DROP etc. */
+extern int psc_barlf_action; /* PSC_ACT_DROP etc. */
+extern int psc_min_ttl; /* Update with new tests! */
+extern STRING_LIST *psc_forbid_cmds; /* CONNECT GET POST */
+extern int psc_stress_greet_wait; /* stressed greet wait */
+extern int psc_normal_greet_wait; /* stressed greet wait */
+extern int psc_stress_cmd_time_limit; /* stressed command limit */
+extern int psc_normal_cmd_time_limit; /* normal command time limit */
+extern int psc_stress; /* stress level */
+extern int psc_lowat_check_queue_length;/* stress low-water mark */
+extern int psc_hiwat_check_queue_length;/* stress high-water mark */
+extern DICT *psc_dnsbl_reply; /* DNSBL name mapper */
+extern HTABLE *psc_client_concurrency; /* per-client concurrency */
+
+#define PSC_EFF_GREET_WAIT \
+ (psc_stress ? psc_stress_greet_wait : psc_normal_greet_wait)
+#define PSC_EFF_CMD_TIME_LIMIT \
+ (psc_stress ? psc_stress_cmd_time_limit : psc_normal_cmd_time_limit)
+
+ /*
+ * String plumbing macros.
+ */
+#define PSC_STRING_UPDATE(str, text) do { \
+ if (str) myfree(str); \
+ (str) = ((text) ? mystrdup(text) : 0); \
+ } while (0)
+
+#define PSC_STRING_RESET(str) do { \
+ if (str) { \
+ myfree(str); \
+ (str) = 0; \
+ } \
+ } while (0)
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+ /*
+ * postscreen_state.c
+ */
+#define PSC_CLIENT_ADDR_PORT(state) \
+ (state)->smtp_client_addr, (state)->smtp_client_port
+
+#define PSC_PASS_SESSION_STATE(state, what, bits) do { \
+ if (msg_verbose) \
+ msg_info("PASS %s [%s]:%s", (what), PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags |= (bits); \
+ } while (0)
+#define PSC_FAIL_SESSION_STATE(state, bits) do { \
+ if (msg_verbose) \
+ msg_info("FAIL [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags |= (bits); \
+ } while (0)
+#define PSC_SKIP_SESSION_STATE(state, what, bits) do { \
+ if (msg_verbose) \
+ msg_info("SKIP %s [%s]:%s", (what), PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags |= (bits); \
+ } while (0)
+#define PSC_DROP_SESSION_STATE(state, reply) do { \
+ if (msg_verbose) \
+ msg_info("DROP [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags |= PSC_STATE_FLAG_NOFORWARD; \
+ (state)->final_reply = (reply); \
+ psc_conclude(state); \
+ } while (0)
+#define PSC_ENFORCE_SESSION_STATE(state, reply) do { \
+ if (msg_verbose) \
+ msg_info("ENFORCE [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->rcpt_reply = (reply); \
+ (state)->flags |= PSC_STATE_FLAG_NOFORWARD; \
+ } while (0)
+#define PSC_UNPASS_SESSION_STATE(state, bits) do { \
+ if (msg_verbose) \
+ msg_info("UNPASS [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags &= ~(bits); \
+ } while (0)
+#define PSC_UNFAIL_SESSION_STATE(state, bits) do { \
+ if (msg_verbose) \
+ msg_info("UNFAIL [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \
+ (state)->flags &= ~(bits); \
+ } while (0)
+#define PSC_ADD_SERVER_STATE(state, fd) do { \
+ (state)->smtp_server_fd = (fd); \
+ psc_post_queue_length++; \
+ } while (0)
+#define PSC_DEL_SERVER_STATE(state) do { \
+ close((state)->smtp_server_fd); \
+ (state)->smtp_server_fd = (-1); \
+ psc_post_queue_length--; \
+ } while (0)
+#define PSC_DEL_CLIENT_STATE(state) do { \
+ event_server_disconnect((state)->smtp_client_stream); \
+ (state)->smtp_client_stream = 0; \
+ psc_check_queue_length--; \
+ } while (0)
+extern PSC_STATE *psc_new_session_state(VSTREAM *, const char *, const char *, const char *, const char *);
+extern void psc_free_session_state(PSC_STATE *);
+extern const char *psc_print_state_flags(int, const char *);
+
+ /*
+ * postscreen_dict.c
+ */
+extern int psc_addr_match_list_match(ADDR_MATCH_LIST *, const char *);
+extern const char *psc_cache_lookup(DICT_CACHE *, const char *);
+extern void psc_cache_update(DICT_CACHE *, const char *, const char *);
+const char *psc_dict_get(DICT *, const char *);
+const char *psc_maps_find(MAPS *, const char *, int);
+
+ /*
+ * postscreen_dnsbl.c
+ */
+extern void psc_dnsbl_init(void);
+extern int psc_dnsbl_retrieve(const char *, const char **, int, int *);
+extern int psc_dnsbl_request(const char *, void (*) (int, void *), void *);
+
+ /*
+ * postscreen_tests.c
+ */
+#define PSC_INIT_TESTS(dst) do { \
+ time_t *_it_stamp_p; \
+ (dst)->flags = 0; \
+ for (_it_stamp_p = (dst)->client_info->expire_time; \
+ _it_stamp_p < (dst)->client_info->expire_time + PSC_TINDX_COUNT; \
+ _it_stamp_p++) \
+ *_it_stamp_p = PSC_TIME_STAMP_INVALID; \
+ } while (0)
+#define PSC_INIT_TEST_FLAGS_ONLY(dst) do { \
+ (dst)->flags = 0; \
+ } while (0)
+#define PSC_BEGIN_TESTS(state, name) do { \
+ (state)->test_name = (name); \
+ GETTIMEOFDAY(&(state)->start_time); \
+ } while (0)
+extern void psc_new_tests(PSC_STATE *);
+extern void psc_parse_tests(PSC_STATE *, const char *, time_t);
+extern void psc_todo_tests(PSC_STATE *, time_t);
+extern char *psc_print_tests(VSTRING *, PSC_STATE *);
+extern char *psc_print_grey_key(VSTRING *, const char *, const char *,
+ const char *, const char *);
+extern const char *psc_test_name(int);
+
+#define PSC_MIN(x, y) ((x) < (y) ? (x) : (y))
+#define PSC_MAX(x, y) ((x) > (y) ? (x) : (y))
+
+ /*
+ * postscreen_early.c
+ */
+extern void psc_early_tests(PSC_STATE *);
+extern void psc_early_init(void);
+
+ /*
+ * postscreen_smtpd.c
+ */
+extern void psc_smtpd_tests(PSC_STATE *);
+extern void psc_smtpd_init(void);
+extern void psc_smtpd_pre_jail_init(void);
+
+#define PSC_SMTPD_X21(state, reply) do { \
+ (state)->flags |= PSC_STATE_FLAG_SMTPD_X21; \
+ (state)->final_reply = (reply); \
+ psc_smtpd_tests(state); \
+ } while (0)
+
+ /*
+ * postscreen_misc.c
+ */
+extern char *psc_format_delta_time(VSTRING *, struct timeval, DELTA_TIME *);
+extern void psc_conclude(PSC_STATE *);
+extern void psc_hangup_event(PSC_STATE *);
+
+ /*
+ * postscreen_send.c
+ */
+#define PSC_SEND_REPLY psc_send_reply /* legacy macro */
+extern void pcs_send_pre_jail_init(void);
+extern int psc_send_reply(PSC_STATE *, const char *);
+extern void psc_send_socket(PSC_STATE *);
+
+ /*
+ * postscreen_starttls.c
+ */
+extern void psc_starttls_open(PSC_STATE *, EVENT_NOTIFY_FN);
+
+ /*
+ * postscreen_expand.c
+ */
+extern VSTRING *psc_expand_filter;
+extern void psc_expand_init(void);
+extern const char *psc_expand_lookup(const char *, int, void *);
+
+ /*
+ * postscreen_endpt.c
+ */
+typedef void (*PSC_ENDPT_LOOKUP_FN) (int, VSTREAM *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *);
+extern void psc_endpt_lookup(VSTREAM *, PSC_ENDPT_LOOKUP_FN);
+extern void psc_endpt_local_lookup(VSTREAM *, PSC_ENDPT_LOOKUP_FN);
+
+ /*
+ * postscreen_access emulation.
+ */
+#define PSC_ACL_ACT_ALLOWLIST SERVER_ACL_ACT_PERMIT
+#define PSC_ACL_ACT_DUNNO SERVER_ACL_ACT_DUNNO
+#define PSC_ACL_ACT_DENYLIST SERVER_ACL_ACT_REJECT
+#define PSC_ACL_ACT_ERROR SERVER_ACL_ACT_ERROR
+
+#define psc_acl_pre_jail_init server_acl_pre_jail_init
+#define psc_acl_parse server_acl_parse
+#define psc_acl_eval(s,a,p) server_acl_eval((s)->smtp_client_addr, (a), (p))
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
diff --git a/src/postscreen/postscreen_dict.c b/src/postscreen/postscreen_dict.c
new file mode 100644
index 0000000..131ff81
--- /dev/null
+++ b/src/postscreen/postscreen_dict.c
@@ -0,0 +1,182 @@
+/*++
+/* NAME
+/* postscreen_dict 3
+/* SUMMARY
+/* postscreen table access wrappers
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* int psc_addr_match_list_match(match_list, client_addr)
+/* ADDR_MATCH_LIST *match_list;
+/* const char *client_addr;
+/*
+/* const char *psc_cache_lookup(DICT_CACHE *cache, const char *key)
+/* DICT_CACHE *cache;
+/* const char *key;
+/*
+/* void psc_cache_update(cache, key, value)
+/* DICT_CACHE *cache;
+/* const char *key;
+/* const char *value;
+/*
+/* void psc_dict_get(dict, key)
+/* DICT *dict;
+/* const char *key;
+/*
+/* void psc_maps_find(maps, key, flags)
+/* MAPS *maps;
+/* const char *key;
+/* int flags;
+/* DESCRIPTION
+/* This module implements wrappers around time-critical table
+/* access functions. The functions log a warning when table
+/* access takes a non-trivial amount of time.
+/*
+/* psc_addr_match_list_match() is a wrapper around
+/* addr_match_list_match().
+/*
+/* psc_cache_lookup() and psc_cache_update() are wrappers around
+/* the corresponding dict_cache() methods.
+/*
+/* psc_dict_get() and psc_maps_find() are wrappers around
+/* dict_get() and maps_find(), respectively.
+/* 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 <msg.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <maps.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Monitor time-critical operations.
+ *
+ * XXX Averaging support was added during a stable release candidate, so it
+ * provides only the absolute minimum necessary. A complete implementation
+ * should maintain separate statistics for each table, and it should not
+ * complain when the access latency is less than the time between accesses.
+ */
+#define PSC_GET_TIME_BEFORE_LOOKUP { \
+ struct timeval _before, _after; \
+ DELTA_TIME _delta; \
+ double _new_delta_ms; \
+ GETTIMEOFDAY(&_before);
+
+#define PSC_DELTA_MS(d) ((d).dt_sec * 1000.0 + (d).dt_usec / 1000.0)
+
+#define PSC_AVERAGE(new, old) (0.1 * (new) + 0.9 * (old))
+
+#ifndef PSC_THRESHOLD_MS
+#define PSC_THRESHOLD_MS 100 /* nag if latency > 100ms */
+#endif
+
+#ifndef PSC_WARN_LOCKOUT_S
+#define PSC_WARN_LOCKOUT_S 60 /* don't nag for 60s */
+#endif
+
+ /*
+ * Shared warning lock, so that we don't spam the logfile when the system
+ * becomes slow.
+ */
+static time_t psc_last_warn = 0;
+
+#define PSC_CHECK_TIME_AFTER_LOOKUP(table, action, average) \
+ GETTIMEOFDAY(&_after); \
+ PSC_CALC_DELTA(_delta, _after, _before); \
+ _new_delta_ms = PSC_DELTA_MS(_delta); \
+ if ((average = PSC_AVERAGE(_new_delta_ms, average)) > PSC_THRESHOLD_MS \
+ && psc_last_warn < _after.tv_sec - PSC_WARN_LOCKOUT_S) { \
+ msg_warn("%s: %s %s average delay is %.0f ms", \
+ myname, (table), (action), average); \
+ psc_last_warn = _after.tv_sec; \
+ } \
+}
+
+/* psc_addr_match_list_match - time-critical address list lookup */
+
+int psc_addr_match_list_match(ADDR_MATCH_LIST *addr_list,
+ const char *addr_str)
+{
+ const char *myname = "psc_addr_match_list_match";
+ int result;
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ result = addr_match_list_match(addr_list, addr_str);
+ PSC_CHECK_TIME_AFTER_LOOKUP("address list", "lookup", latency_ms);
+ return (result);
+}
+
+/* psc_cache_lookup - time-critical cache lookup */
+
+const char *psc_cache_lookup(DICT_CACHE *cache, const char *key)
+{
+ const char *myname = "psc_cache_lookup";
+ const char *result;
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ result = dict_cache_lookup(cache, key);
+ PSC_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "lookup", latency_ms);
+ return (result);
+}
+
+/* psc_cache_update - time-critical cache update */
+
+void psc_cache_update(DICT_CACHE *cache, const char *key, const char *value)
+{
+ const char *myname = "psc_cache_update";
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ dict_cache_update(cache, key, value);
+ PSC_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "update", latency_ms);
+}
+
+/* psc_dict_get - time-critical table lookup */
+
+const char *psc_dict_get(DICT *dict, const char *key)
+{
+ const char *myname = "psc_dict_get";
+ const char *result;
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ result = dict_get(dict, key);
+ PSC_CHECK_TIME_AFTER_LOOKUP(dict->name, "lookup", latency_ms);
+ return (result);
+}
+
+/* psc_maps_find - time-critical table lookup */
+
+const char *psc_maps_find(MAPS *maps, const char *key, int flags)
+{
+ const char *myname = "psc_maps_find";
+ const char *result;
+ static double latency_ms;
+
+ PSC_GET_TIME_BEFORE_LOOKUP;
+ result = maps_find(maps, key, flags);
+ PSC_CHECK_TIME_AFTER_LOOKUP(maps->title, "lookup", latency_ms);
+ return (result);
+}
diff --git a/src/postscreen/postscreen_dnsbl.c b/src/postscreen/postscreen_dnsbl.c
new file mode 100644
index 0000000..7d9a5e9
--- /dev/null
+++ b/src/postscreen/postscreen_dnsbl.c
@@ -0,0 +1,624 @@
+/*++
+/* NAME
+/* postscreen_dnsbl 3
+/* SUMMARY
+/* postscreen DNSBL support
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_dnsbl_init(void)
+/*
+/* int psc_dnsbl_request(client_addr, callback, context)
+/* char *client_addr;
+/* void (*callback)(int, char *);
+/* char *context;
+/*
+/* int psc_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index,
+/* dnsbl_ttl)
+/* char *client_addr;
+/* const char **dnsbl_name;
+/* int dnsbl_index;
+/* int *dnsbl_ttl;
+/* DESCRIPTION
+/* This module implements preliminary support for DNSBL lookups.
+/* Multiple requests for the same information are handled with
+/* reference counts.
+/*
+/* psc_dnsbl_init() initializes this module, and must be called
+/* once before any of the other functions in this module.
+/*
+/* psc_dnsbl_request() requests a blocklist score for the
+/* specified client IP address and increments the reference
+/* count. The request completes in the background. The client
+/* IP address must be in inet_ntop(3) output format. The
+/* callback argument specifies a function that is called when
+/* the requested result is available. The context is passed
+/* on to the callback function. The callback should ignore its
+/* first argument (it exists for compatibility with Postfix
+/* generic event infrastructure).
+/* The result value is the index for the psc_dnsbl_retrieve()
+/* call.
+/*
+/* psc_dnsbl_retrieve() retrieves the result score and reply
+/* TTL requested with psc_dnsbl_request(), and decrements the
+/* reference count. The reply TTL value is clamped to
+/* postscreen_dnsbl_min_ttl and postscreen_dnsbl_max_ttl. It
+/* is an error to retrieve a score without requesting it first.
+/* 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> /* AF_INET */
+#include <netinet/in.h> /* inet_pton() */
+#include <arpa/inet.h> /* inet_pton() */
+#include <stdio.h> /* sscanf */
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <argv.h>
+#include <htable.h>
+#include <events.h>
+#include <vstream.h>
+#include <connect.h>
+#include <split_at.h>
+#include <valid_hostname.h>
+#include <ip_match.h>
+#include <myaddrinfo.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Talking to the DNSBLOG service.
+ */
+static char *psc_dnsbl_service;
+
+ /*
+ * Per-DNSBL filters and weights.
+ *
+ * The postscreen_dnsbl_sites parameter specifies zero or more DNSBL domains.
+ * We provide multiple access methods, one for quick iteration when sending
+ * queries to all DNSBL servers, and one for quick location when receiving a
+ * reply from one DNSBL server.
+ *
+ * Each DNSBL domain can be specified more than once, each time with a
+ * different (filter, weight) pair. We group (filter, weight) pairs in a
+ * linked list under their DNSBL domain name. The list head has a reference
+ * to a "safe name" for the DNSBL, in case the name includes a password.
+ */
+static HTABLE *dnsbl_site_cache; /* indexed by DNSBNL domain */
+static HTABLE_INFO **dnsbl_site_list; /* flattened cache */
+
+typedef struct {
+ const char *safe_dnsbl; /* from postscreen_dnsbl_reply_map */
+ struct PSC_DNSBL_SITE *first; /* list of (filter, weight) tuples */
+} PSC_DNSBL_HEAD;
+
+typedef struct PSC_DNSBL_SITE {
+ char *filter; /* printable filter (default: null) */
+ char *byte_codes; /* encoded filter (default: null) */
+ int weight; /* reply weight (default: 1) */
+ struct PSC_DNSBL_SITE *next; /* linked list */
+} PSC_DNSBL_SITE;
+
+ /*
+ * Per-client DNSBL scores.
+ *
+ * Some SMTP clients make parallel connections. This can trigger parallel
+ * blocklist score requests when the pre-handshake delays of the connections
+ * overlap.
+ *
+ * We combine requests for the same score under the client IP address in a
+ * single reference-counted entry. The reference count goes up with each
+ * request for a score, and it goes down with each score retrieval. Each
+ * score has one or more requestors that need to be notified when the result
+ * is ready, so that postscreen can terminate a pre-handshake delay when all
+ * pre-handshake tests are completed.
+ */
+static HTABLE *dnsbl_score_cache; /* indexed by client address */
+
+typedef struct {
+ void (*callback) (int, void *); /* generic call-back routine */
+ void *context; /* generic call-back argument */
+} PSC_CALL_BACK_ENTRY;
+
+typedef struct {
+ const char *dnsbl_name; /* DNSBL with largest contribution */
+ int dnsbl_weight; /* weight of largest contribution */
+ int total; /* combined allow+denylist score */
+ int fail_ttl; /* combined reply TTL */
+ int pass_ttl; /* combined reply TTL */
+ int refcount; /* score reference count */
+ int pending_lookups; /* nr of DNS requests in flight */
+ int request_id; /* duplicate suppression */
+ /* Call-back table support. */
+ int index; /* next table index */
+ int limit; /* last valid index */
+ PSC_CALL_BACK_ENTRY table[1]; /* actually a bunch */
+} PSC_DNSBL_SCORE;
+
+#define PSC_CALL_BACK_INIT(sp) do { \
+ (sp)->limit = 0; \
+ (sp)->index = 0; \
+ } while (0)
+
+#define PSC_CALL_BACK_INDEX_OF_LAST(sp) ((sp)->index - 1)
+
+#define PSC_CALL_BACK_CANCEL(sp, idx) do { \
+ PSC_CALL_BACK_ENTRY *_cb_; \
+ if ((idx) < 0 || (idx) >= (sp)->index) \
+ msg_panic("%s: index %d must be >= 0 and < %d", \
+ myname, (idx), (sp)->index); \
+ _cb_ = (sp)->table + (idx); \
+ event_cancel_timer(_cb_->callback, _cb_->context); \
+ _cb_->callback = 0; \
+ _cb_->context = 0; \
+ } while (0)
+
+#define PSC_CALL_BACK_EXTEND(hp, sp) do { \
+ if ((sp)->index >= (sp)->limit) { \
+ int _count_ = ((sp)->limit ? (sp)->limit * 2 : 5); \
+ (hp)->value = myrealloc((void *) (sp), sizeof(*(sp)) + \
+ _count_ * sizeof((sp)->table)); \
+ (sp) = (PSC_DNSBL_SCORE *) (hp)->value; \
+ (sp)->limit = _count_; \
+ } \
+ } while (0)
+
+#define PSC_CALL_BACK_ENTER(sp, fn, ctx) do { \
+ PSC_CALL_BACK_ENTRY *_cb_ = (sp)->table + (sp)->index++; \
+ _cb_->callback = (fn); \
+ _cb_->context = (ctx); \
+ } while (0)
+
+#define PSC_CALL_BACK_NOTIFY(sp, ev) do { \
+ PSC_CALL_BACK_ENTRY *_cb_; \
+ for (_cb_ = (sp)->table; _cb_ < (sp)->table + (sp)->index; _cb_++) \
+ if (_cb_->callback != 0) \
+ _cb_->callback((ev), _cb_->context); \
+ } while (0)
+
+#define PSC_NULL_EVENT (0)
+
+ /*
+ * Per-request state.
+ *
+ * This implementation stores the client IP address and DNSBL domain in the
+ * DNSBLOG query/reply stream. This simplifies code, and allows the DNSBLOG
+ * server to produce more informative logging.
+ */
+static VSTRING *reply_client; /* client address in DNSBLOG reply */
+static VSTRING *reply_dnsbl; /* domain in DNSBLOG reply */
+static VSTRING *reply_addr; /* address list in DNSBLOG reply */
+
+/* psc_dnsbl_add_site - add DNSBL site information */
+
+static void psc_dnsbl_add_site(const char *site)
+{
+ const char *myname = "psc_dnsbl_add_site";
+ char *saved_site = mystrdup(site);
+ VSTRING *byte_codes = 0;
+ PSC_DNSBL_HEAD *head;
+ PSC_DNSBL_SITE *new_site;
+ char junk;
+ const char *weight_text;
+ char *pattern_text;
+ int weight;
+ HTABLE_INFO *ht;
+ char *parse_err;
+ const char *safe_dnsbl;
+
+ /*
+ * Parse the required DNSBL domain name, the optional reply filter and
+ * the optional reply weight factor.
+ */
+#define DO_GRIPE 1
+
+ /* Negative weight means allowlist. */
+ if ((weight_text = split_at(saved_site, '*')) != 0) {
+ if (sscanf(weight_text, "%d%c", &weight, &junk) != 1)
+ msg_fatal("bad DNSBL weight factor \"%s\" in \"%s\"",
+ weight_text, site);
+ } else {
+ weight = 1;
+ }
+ /* Reply filter. */
+ if ((pattern_text = split_at(saved_site, '=')) != 0) {
+ byte_codes = vstring_alloc(100);
+ if ((parse_err = ip_match_parse(byte_codes, pattern_text)) != 0)
+ msg_fatal("bad DNSBL filter syntax: %s", parse_err);
+ }
+ if (valid_hostname(saved_site, DO_GRIPE) == 0)
+ msg_fatal("bad DNSBL domain name \"%s\" in \"%s\"",
+ saved_site, site);
+
+ if (msg_verbose > 1)
+ msg_info("%s: \"%s\" -> domain=\"%s\" pattern=\"%s\" weight=%d",
+ myname, site, saved_site, pattern_text ? pattern_text :
+ "null", weight);
+
+ /*
+ * Look up or create the (filter, weight) list head for this DNSBL domain
+ * name.
+ */
+ if ((head = (PSC_DNSBL_HEAD *)
+ htable_find(dnsbl_site_cache, saved_site)) == 0) {
+ head = (PSC_DNSBL_HEAD *) mymalloc(sizeof(*head));
+ ht = htable_enter(dnsbl_site_cache, saved_site, (void *) head);
+ /* Translate the DNSBL name into a safe name if available. */
+ if (psc_dnsbl_reply == 0
+ || (safe_dnsbl = dict_get(psc_dnsbl_reply, saved_site)) == 0)
+ safe_dnsbl = ht->key;
+ head->safe_dnsbl = mystrdup(safe_dnsbl);
+ if (psc_dnsbl_reply && psc_dnsbl_reply->error)
+ msg_fatal("%s:%s lookup error", psc_dnsbl_reply->type,
+ psc_dnsbl_reply->name);
+ head->first = 0;
+ }
+
+ /*
+ * Append the new (filter, weight) node to the list for this DNSBL domain
+ * name.
+ */
+ new_site = (PSC_DNSBL_SITE *) mymalloc(sizeof(*new_site));
+ new_site->filter = (pattern_text ? mystrdup(pattern_text) : 0);
+ new_site->byte_codes = (byte_codes ? ip_match_save(byte_codes) : 0);
+ new_site->weight = weight;
+ new_site->next = head->first;
+ head->first = new_site;
+
+ myfree(saved_site);
+ if (byte_codes)
+ vstring_free(byte_codes);
+}
+
+/* psc_dnsbl_match - match DNSBL reply filter */
+
+static int psc_dnsbl_match(const char *filter, ARGV *reply)
+{
+ char addr_buf[MAI_HOSTADDR_STRSIZE];
+ char **cpp;
+
+ /*
+ * Run the replies through the pattern-matching engine.
+ */
+ for (cpp = reply->argv; *cpp != 0; cpp++) {
+ if (inet_pton(AF_INET, *cpp, addr_buf) != 1)
+ msg_warn("address conversion error for %s -- ignoring this reply",
+ *cpp);
+ if (ip_match_execute(filter, addr_buf))
+ return (1);
+ }
+ return (0);
+}
+
+/* psc_dnsbl_retrieve - retrieve blocklist score, decrement reference count */
+
+int psc_dnsbl_retrieve(const char *client_addr, const char **dnsbl_name,
+ int dnsbl_index, int *dnsbl_ttl)
+{
+ const char *myname = "psc_dnsbl_retrieve";
+ PSC_DNSBL_SCORE *score;
+ int result_score;
+ int result_ttl;
+
+ /*
+ * Sanity check.
+ */
+ if ((score = (PSC_DNSBL_SCORE *)
+ htable_find(dnsbl_score_cache, client_addr)) == 0)
+ msg_panic("%s: no blocklist score for %s", myname, client_addr);
+
+ /*
+ * Disable callbacks.
+ */
+ PSC_CALL_BACK_CANCEL(score, dnsbl_index);
+
+ /*
+ * Reads are destructive.
+ */
+ result_score = score->total;
+ *dnsbl_name = score->dnsbl_name;
+ result_ttl = (result_score > 0) ? score->fail_ttl : score->pass_ttl;
+ /* As with dnsblog(8), a value < 0 means no reply TTL. */
+ if (result_ttl < var_psc_dnsbl_min_ttl)
+ result_ttl = var_psc_dnsbl_min_ttl;
+ if (result_ttl > var_psc_dnsbl_max_ttl)
+ result_ttl = var_psc_dnsbl_max_ttl;
+ *dnsbl_ttl = result_ttl;
+ if (msg_verbose)
+ msg_info("%s: addr=%s score=%d ttl=%d",
+ myname, client_addr, result_score, result_ttl);
+ score->refcount -= 1;
+ if (score->refcount < 1) {
+ if (msg_verbose > 1)
+ msg_info("%s: delete blocklist score for %s", myname, client_addr);
+ htable_delete(dnsbl_score_cache, client_addr, myfree);
+ }
+ return (result_score);
+}
+
+/* psc_dnsbl_receive - receive DNSBL reply, update blocklist score */
+
+static void psc_dnsbl_receive(int event, void *context)
+{
+ const char *myname = "psc_dnsbl_receive";
+ VSTREAM *stream = (VSTREAM *) context;
+ PSC_DNSBL_SCORE *score;
+ PSC_DNSBL_HEAD *head;
+ PSC_DNSBL_SITE *site;
+ ARGV *reply_argv;
+ int request_id;
+ int dnsbl_ttl;
+
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, context);
+
+ /*
+ * Receive the DNSBL lookup result.
+ *
+ * This is preliminary code to explore the field. Later, DNSBL lookup will
+ * be handled by an UDP-based DNS client that is built directly into some
+ * Postfix daemon.
+ *
+ * Don't bother looking up the blocklist score when the client IP address is
+ * not listed at the DNSBL.
+ *
+ * Don't panic when the blocklist score no longer exists. It may be deleted
+ * when the client triggers a "drop" action after pregreet, when the
+ * client does not pregreet and the DNSBL reply arrives late, or when the
+ * client triggers a "drop" action after hanging up.
+ */
+ if (event == EVENT_READ
+ && attr_scan(stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, reply_dnsbl),
+ RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, reply_client),
+ RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id),
+ RECV_ATTR_STR(MAIL_ATTR_RBL_ADDR, reply_addr),
+ RECV_ATTR_INT(MAIL_ATTR_TTL, &dnsbl_ttl),
+ ATTR_TYPE_END) == 5
+ && (score = (PSC_DNSBL_SCORE *)
+ htable_find(dnsbl_score_cache, STR(reply_client))) != 0
+ && score->request_id == request_id) {
+
+ /*
+ * Run this response past all applicable DNSBL filters and update the
+ * blocklist score for this client IP address.
+ *
+ * Don't panic when the DNSBL domain name is not found. The DNSBLOG
+ * server may be messed up.
+ */
+ if (msg_verbose > 1)
+ msg_info("%s: client=\"%s\" score=%d domain=\"%s\" reply=\"%d %s\"",
+ myname, STR(reply_client), score->total,
+ STR(reply_dnsbl), dnsbl_ttl, STR(reply_addr));
+ head = (PSC_DNSBL_HEAD *)
+ htable_find(dnsbl_site_cache, STR(reply_dnsbl));
+ if (head == 0) {
+ /* Bogus domain. Do nothing. */
+ } else if (*STR(reply_addr) != 0) {
+ /* DNS reputation record(s) found. */
+ reply_argv = 0;
+ for (site = head->first; site != 0; site = site->next) {
+ if (site->byte_codes == 0
+ || psc_dnsbl_match(site->byte_codes, reply_argv ? reply_argv :
+ (reply_argv = argv_split(STR(reply_addr), " ")))) {
+ if (score->dnsbl_name == 0
+ || score->dnsbl_weight < site->weight) {
+ score->dnsbl_name = head->safe_dnsbl;
+ score->dnsbl_weight = site->weight;
+ }
+ score->total += site->weight;
+ if (msg_verbose > 1)
+ msg_info("%s: filter=\"%s\" weight=%d score=%d",
+ myname, site->filter ? site->filter : "null",
+ site->weight, score->total);
+ }
+ /* As with dnsblog(8), a value < 0 means no reply TTL. */
+ if (site->weight > 0) {
+ if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
+ score->fail_ttl = dnsbl_ttl;
+ } else {
+ if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
+ score->pass_ttl = dnsbl_ttl;
+ }
+ }
+ if (reply_argv != 0)
+ argv_free(reply_argv);
+ } else {
+ /* No DNS reputation record found. */
+ for (site = head->first; site != 0; site = site->next) {
+ /* As with dnsblog(8), a value < 0 means no reply TTL. */
+ if (site->weight > 0) {
+ if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
+ score->pass_ttl = dnsbl_ttl;
+ } else {
+ if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
+ score->fail_ttl = dnsbl_ttl;
+ }
+ }
+ }
+
+ /*
+ * Notify the requestor(s) that the result is ready to be picked up.
+ * If this call isn't made, clients have to sit out the entire
+ * pre-handshake delay.
+ */
+ score->pending_lookups -= 1;
+ if (score->pending_lookups == 0)
+ PSC_CALL_BACK_NOTIFY(score, PSC_NULL_EVENT);
+ } else if (event == EVENT_TIME) {
+ msg_warn("dnsblog reply timeout %ds for %s",
+ var_psc_dnsbl_tmout, (char *) vstream_context(stream));
+ }
+ /* Here, score may be a null pointer. */
+ vstream_fclose(stream);
+}
+
+/* psc_dnsbl_request - send dnsbl query, increment reference count */
+
+int psc_dnsbl_request(const char *client_addr,
+ void (*callback) (int, void *),
+ void *context)
+{
+ const char *myname = "psc_dnsbl_request";
+ int fd;
+ VSTREAM *stream;
+ HTABLE_INFO **ht;
+ PSC_DNSBL_SCORE *score;
+ HTABLE_INFO *hash_node;
+ static int request_count;
+
+ /*
+ * Some spambots make several connections at nearly the same time,
+ * causing their pregreet delays to overlap. Such connections can share
+ * the efforts of DNSBL lookup.
+ *
+ * We store a reference-counted DNSBL score under its client IP address. We
+ * increment the reference count with each score request, and decrement
+ * the reference count with each score retrieval.
+ *
+ * Do not notify the requestor NOW when the DNS replies are already in.
+ * Reason: we must not make a backwards call while we are still in the
+ * middle of executing the corresponding forward call. Instead we create
+ * a zero-delay timer request and call the notification function from
+ * there.
+ *
+ * psc_dnsbl_request() could instead return a result value to indicate that
+ * the DNSBL score is already available, but that would complicate the
+ * caller with two different notification code paths: one asynchronous
+ * code path via the callback invocation, and one synchronous code path
+ * via the psc_dnsbl_request() result value. That would be a source of
+ * future bugs.
+ */
+ if ((hash_node = htable_locate(dnsbl_score_cache, client_addr)) != 0) {
+ score = (PSC_DNSBL_SCORE *) hash_node->value;
+ score->refcount += 1;
+ PSC_CALL_BACK_EXTEND(hash_node, score);
+ PSC_CALL_BACK_ENTER(score, callback, context);
+ if (msg_verbose > 1)
+ msg_info("%s: reuse blocklist score for %s refcount=%d pending=%d",
+ myname, client_addr, score->refcount,
+ score->pending_lookups);
+ if (score->pending_lookups == 0)
+ event_request_timer(callback, context, EVENT_NULL_DELAY);
+ return (PSC_CALL_BACK_INDEX_OF_LAST(score));
+ }
+ if (msg_verbose > 1)
+ msg_info("%s: create blocklist score for %s", myname, client_addr);
+ score = (PSC_DNSBL_SCORE *) mymalloc(sizeof(*score));
+ score->request_id = request_count++;
+ score->dnsbl_name = 0;
+ score->dnsbl_weight = 0;
+ /* As with dnsblog(8), a value < 0 means no reply TTL. */
+ score->pass_ttl = -1;
+ score->fail_ttl = -1;
+ score->total = 0;
+ score->refcount = 1;
+ score->pending_lookups = 0;
+ PSC_CALL_BACK_INIT(score);
+ PSC_CALL_BACK_ENTER(score, callback, context);
+ (void) htable_enter(dnsbl_score_cache, client_addr, (void *) score);
+
+ /*
+ * Send a query to all DNSBL servers. Later, DNSBL lookup will be done
+ * with an UDP-based DNS client that is built directly into Postfix code.
+ * We therefore do not optimize the maximum out of this temporary
+ * implementation.
+ */
+ for (ht = dnsbl_site_list; *ht; ht++) {
+ if ((fd = LOCAL_CONNECT(psc_dnsbl_service, NON_BLOCKING, 1)) < 0) {
+ msg_warn("%s: connect to %s service: %m",
+ myname, psc_dnsbl_service);
+ continue;
+ }
+ stream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(stream,
+ CA_VSTREAM_CTL_CONTEXT(ht[0]->key),
+ CA_VSTREAM_CTL_END);
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, ht[0]->key),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, client_addr),
+ SEND_ATTR_INT(MAIL_ATTR_LABEL, score->request_id),
+ ATTR_TYPE_END);
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("%s: error sending to %s service: %m",
+ myname, psc_dnsbl_service);
+ vstream_fclose(stream);
+ continue;
+ }
+ PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive,
+ (void *) stream, var_psc_dnsbl_tmout);
+ score->pending_lookups += 1;
+ }
+ return (PSC_CALL_BACK_INDEX_OF_LAST(score));
+}
+
+/* psc_dnsbl_init - initialize */
+
+void psc_dnsbl_init(void)
+{
+ const char *myname = "psc_dnsbl_init";
+ ARGV *dnsbl_site = argv_split(var_psc_dnsbl_sites, CHARS_COMMA_SP);
+ char **cpp;
+
+ /*
+ * Sanity check.
+ */
+ if (dnsbl_site_cache != 0)
+ msg_panic("%s: called more than once", myname);
+
+ /*
+ * pre-compute the DNSBLOG socket name.
+ */
+ psc_dnsbl_service = concatenate(MAIL_CLASS_PRIVATE, "/",
+ var_dnsblog_service, (char *) 0);
+
+ /*
+ * Prepare for quick iteration when sending out queries to all DNSBL
+ * servers, and for quick lookup when a reply arrives from a specific
+ * DNSBL server.
+ */
+ dnsbl_site_cache = htable_create(13);
+ for (cpp = dnsbl_site->argv; *cpp; cpp++)
+ psc_dnsbl_add_site(*cpp);
+ argv_free(dnsbl_site);
+ dnsbl_site_list = htable_list(dnsbl_site_cache);
+
+ /*
+ * The per-client blocklist score.
+ */
+ dnsbl_score_cache = htable_create(13);
+
+ /*
+ * Space for ad-hoc DNSBLOG server request/reply parameters.
+ */
+ reply_client = vstring_alloc(100);
+ reply_dnsbl = vstring_alloc(100);
+ reply_addr = vstring_alloc(100);
+}
diff --git a/src/postscreen/postscreen_early.c b/src/postscreen/postscreen_early.c
new file mode 100644
index 0000000..c9d8faf
--- /dev/null
+++ b/src/postscreen/postscreen_early.c
@@ -0,0 +1,377 @@
+/*++
+/* NAME
+/* postscreen_early 3
+/* SUMMARY
+/* postscreen pre-handshake tests
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_early_init(void)
+/*
+/* void psc_early_tests(state)
+/* PSC_STATE *state;
+/* DESCRIPTION
+/* psc_early_tests() performs protocol tests before the SMTP
+/* handshake: the pregreet test and the DNSBL test. Control
+/* is passed to the psc_smtpd_tests() routine as appropriate.
+/*
+/* psc_early_init() performs one-time initialization.
+/* 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 <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+static char *psc_teaser_greeting;
+static VSTRING *psc_escape_buf;
+
+/* psc_allowlist_non_dnsbl - allowlist pending non-dnsbl tests */
+
+static void psc_allowlist_non_dnsbl(PSC_STATE *state)
+{
+ time_t now;
+ int tindx;
+
+ /*
+ * If no tests failed (we can't undo those), and if the allowlist
+ * threshold is met, flag non-dnsbl tests that are pending or disabled as
+ * successfully completed, and set their expiration times equal to the
+ * DNSBL expiration time, except for tests that would expire later.
+ *
+ * Why flag disabled tests as passed? When a disabled test is turned on,
+ * postscreen should not apply that test to clients that are already
+ * allowlisted based on their combined DNSBL score.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
+ && state->dnsbl_score < var_psc_dnsbl_thresh
+ && var_psc_dnsbl_althresh < 0
+ && state->dnsbl_score <= var_psc_dnsbl_althresh) {
+ now = event_time();
+ for (tindx = 0; tindx < PSC_TINDX_COUNT; tindx++) {
+ if (tindx == PSC_TINDX_DNSBL)
+ continue;
+ if ((state->flags & PSC_STATE_FLAG_BYTINDX_TODO(tindx))
+ && !(state->flags & PSC_STATE_FLAG_BYTINDX_PASS(tindx))) {
+ if (msg_verbose)
+ msg_info("skip %s test for [%s]:%s",
+ psc_test_name(tindx), PSC_CLIENT_ADDR_PORT(state));
+ /* Wrong for deep protocol tests, but we disable those. */
+ state->flags |= PSC_STATE_FLAG_BYTINDX_DONE(tindx);
+ /* This also disables pending deep protocol tests. */
+ state->flags |= PSC_STATE_FLAG_BYTINDX_PASS(tindx);
+ }
+ /* Update expiration even if the test was completed or disabled. */
+ if (state->client_info->expire_time[tindx] < now + state->dnsbl_ttl)
+ state->client_info->expire_time[tindx] = now + state->dnsbl_ttl;
+ }
+ }
+}
+
+/* psc_early_event - handle pre-greet, EOF, and DNSBL results. */
+
+static void psc_early_event(int event, void *context)
+{
+ const char *myname = "psc_early_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+ time_t *expire_time = state->client_info->expire_time;
+ char read_buf[PSC_READ_BUF_SIZE];
+ int read_count;
+ DELTA_TIME elapsed;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ event, vstream_fileno(state->smtp_client_stream),
+ state->smtp_client_addr, state->smtp_client_port,
+ psc_print_state_flags(state->flags, myname));
+
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
+ psc_early_event, context);
+
+ /*
+ * XXX Be sure to empty the DNSBL lookup buffer otherwise we have a
+ * memory leak.
+ *
+ * XXX We can avoid "forgetting" to do this by keeping a pointer to the
+ * DNSBL lookup buffer in the PSC_STATE structure. This also allows us to
+ * shave off a hash table lookup when retrieving the DNSBL result.
+ *
+ * A direct pointer increases the odds of dangling pointers. Hash-table
+ * lookup is safer, and that is why it's done that way.
+ */
+ switch (event) {
+
+ /*
+ * We either reached the end of the early tests time limit, or all
+ * early tests completed before the pregreet timer would go off.
+ */
+ case EVENT_TIME:
+
+ /*
+ * Check if the SMTP client spoke before its turn.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
+ && (state->flags & PSC_STATE_MASK_PREGR_FAIL_DONE) == 0) {
+ expire_time[PSC_TINDX_PREGR] = event_time() + var_psc_pregr_ttl;
+ PSC_PASS_SESSION_STATE(state, "pregreet test",
+ PSC_STATE_FLAG_PREGR_PASS);
+ }
+ if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL)
+ && psc_pregr_action == PSC_ACT_IGNORE) {
+ PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
+ /* Not: PSC_PASS_SESSION_STATE. Repeat this test the next time. */
+ }
+
+ /*
+ * Collect the DNSBL score, and allowlist other tests if applicable.
+ * Note: this score will be partial when some DNS lookup did not
+ * complete before the pregreet timer expired.
+ *
+ * If the client is DNS blocklisted, drop the connection, send the
+ * client to a dummy protocol engine, or continue to the next test.
+ */
+#define PSC_DNSBL_FORMAT \
+ "%s 5.7.1 Service unavailable; client [%s] blocked using %s\r\n"
+#define NO_DNSBL_SCORE INT_MAX
+
+ if (state->flags & PSC_STATE_FLAG_DNSBL_TODO) {
+ if (state->dnsbl_score == NO_DNSBL_SCORE) {
+ state->dnsbl_score =
+ psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index,
+ &state->dnsbl_ttl);
+ if (var_psc_dnsbl_althresh < 0)
+ psc_allowlist_non_dnsbl(state);
+ }
+ if (state->dnsbl_score < var_psc_dnsbl_thresh) {
+ expire_time[PSC_TINDX_DNSBL] = event_time() + state->dnsbl_ttl;
+ PSC_PASS_SESSION_STATE(state, "dnsbl test",
+ PSC_STATE_FLAG_DNSBL_PASS);
+ } else {
+ msg_info("DNSBL rank %d for [%s]:%s",
+ state->dnsbl_score, PSC_CLIENT_ADDR_PORT(state));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
+ switch (psc_dnsbl_action) {
+ case PSC_ACT_DROP:
+ state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
+ PSC_DNSBL_FORMAT, "521",
+ state->smtp_client_addr,
+ state->dnsbl_name);
+ PSC_DROP_SESSION_STATE(state, STR(state->dnsbl_reply));
+ return;
+ case PSC_ACT_ENFORCE:
+ state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
+ PSC_DNSBL_FORMAT, "550",
+ state->smtp_client_addr,
+ state->dnsbl_name);
+ PSC_ENFORCE_SESSION_STATE(state, STR(state->dnsbl_reply));
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
+ /* Not: PSC_PASS_SESSION_STATE. Repeat this test. */
+ break;
+ default:
+ msg_panic("%s: unknown dnsbl action value %d",
+ myname, psc_dnsbl_action);
+
+ }
+ }
+ }
+
+ /*
+ * Pass the connection to a real SMTP server, or enter the dummy
+ * engine for deep tests.
+ */
+ if ((state->flags & PSC_STATE_FLAG_NOFORWARD) != 0
+ || ((state->flags & PSC_STATE_MASK_SMTPD_PASS)
+ != PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_SMTPD_TODO)))
+ psc_smtpd_tests(state);
+ else
+ psc_conclude(state);
+ return;
+
+ /*
+ * EOF, or the client spoke before its turn. We simply drop the
+ * connection, or we continue waiting and allow DNS replies to
+ * trickle in.
+ */
+ default:
+ if ((read_count = recv(vstream_fileno(state->smtp_client_stream),
+ read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
+ /* Avoid memory leak. */
+ if (state->dnsbl_score == NO_DNSBL_SCORE
+ && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
+ (void) psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index,
+ &state->dnsbl_ttl);
+ /* XXX Wait for DNS replies to come in. */
+ psc_hangup_event(state);
+ return;
+ }
+ read_buf[read_count] = 0;
+ escape(psc_escape_buf, read_buf, read_count);
+ msg_info("PREGREET %d after %s from [%s]:%s: %.100s", read_count,
+ psc_format_delta_time(psc_temp, state->start_time, &elapsed),
+ PSC_CLIENT_ADDR_PORT(state), STR(psc_escape_buf));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
+ switch (psc_pregr_action) {
+ case PSC_ACT_DROP:
+ /* Avoid memory leak. */
+ if (state->dnsbl_score == NO_DNSBL_SCORE
+ && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
+ (void) psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index,
+ &state->dnsbl_ttl);
+ PSC_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ /* We call psc_dnsbl_retrieve() when the timer expires. */
+ PSC_ENFORCE_SESSION_STATE(state, "550 5.5.1 Protocol error\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ /* We call psc_dnsbl_retrieve() when the timer expires. */
+ /* We must handle this case after the timer expires. */
+ break;
+ default:
+ msg_panic("%s: unknown pregreet action value %d",
+ myname, psc_pregr_action);
+ }
+
+ /*
+ * Terminate the greet delay if we're just waiting for the pregreet
+ * test to complete. It is safe to call psc_early_event directly,
+ * since we are already in that function.
+ *
+ * XXX After this code passes all tests, swap around the two blocks in
+ * this switch statement and fall through from EVENT_READ into
+ * EVENT_TIME, instead of calling psc_early_event recursively.
+ */
+ state->flags |= PSC_STATE_FLAG_PREGR_DONE;
+ if (elapsed.dt_sec >= PSC_EFF_GREET_WAIT
+ || ((state->flags & PSC_STATE_MASK_EARLY_DONE)
+ == PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO)))
+ psc_early_event(EVENT_TIME, context);
+ else
+ event_request_timer(psc_early_event, context,
+ PSC_EFF_GREET_WAIT - elapsed.dt_sec);
+ return;
+ }
+}
+
+/* psc_early_dnsbl_event - cancel pregreet timer if waiting for DNS only */
+
+static void psc_early_dnsbl_event(int unused_event, void *context)
+{
+ const char *myname = "psc_early_dnsbl_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+
+ if (msg_verbose)
+ msg_info("%s: notify [%s]:%s", myname, PSC_CLIENT_ADDR_PORT(state));
+
+ /*
+ * Collect the DNSBL score, and allowlist other tests if applicable.
+ */
+ state->dnsbl_score =
+ psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name,
+ state->dnsbl_index, &state->dnsbl_ttl);
+ if (var_psc_dnsbl_althresh < 0)
+ psc_allowlist_non_dnsbl(state);
+
+ /*
+ * Terminate the greet delay if we're just waiting for DNSBL lookup to
+ * complete. Don't call psc_early_event directly, that would result in a
+ * dangling pointer.
+ */
+ state->flags |= PSC_STATE_FLAG_DNSBL_DONE;
+ if ((state->flags & PSC_STATE_MASK_EARLY_DONE)
+ == PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO))
+ event_request_timer(psc_early_event, context, EVENT_NULL_DELAY);
+}
+
+/* psc_early_tests - start the early (before protocol) tests */
+
+void psc_early_tests(PSC_STATE *state)
+{
+ const char *myname = "psc_early_tests";
+
+ /*
+ * Report errors and progress in the context of this test.
+ */
+ PSC_BEGIN_TESTS(state, "tests before SMTP handshake");
+
+ /*
+ * Run a PREGREET test. Send half the greeting banner, by way of teaser,
+ * then wait briefly to see if the client speaks before its turn.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
+ && psc_teaser_greeting != 0
+ && PSC_SEND_REPLY(state, psc_teaser_greeting) != 0) {
+ psc_hangup_event(state);
+ return;
+ }
+
+ /*
+ * Run a DNS blocklist query.
+ */
+ if ((state->flags & PSC_STATE_FLAG_DNSBL_TODO) != 0)
+ state->dnsbl_index =
+ psc_dnsbl_request(state->smtp_client_addr, psc_early_dnsbl_event,
+ (void *) state);
+ else
+ state->dnsbl_index = -1;
+ state->dnsbl_score = NO_DNSBL_SCORE;
+
+ /*
+ * Wait for the client to respond or for DNS lookup to complete.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0)
+ PSC_READ_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
+ psc_early_event, (void *) state, PSC_EFF_GREET_WAIT);
+ else
+ event_request_timer(psc_early_event, (void *) state, PSC_EFF_GREET_WAIT);
+}
+
+/* psc_early_init - initialize early tests */
+
+void psc_early_init(void)
+{
+ if (*var_psc_pregr_banner) {
+ vstring_sprintf(psc_temp, "220-%s\r\n", var_psc_pregr_banner);
+ psc_teaser_greeting = mystrdup(STR(psc_temp));
+ psc_escape_buf = vstring_alloc(100);
+ }
+}
diff --git a/src/postscreen/postscreen_endpt.c b/src/postscreen/postscreen_endpt.c
new file mode 100644
index 0000000..36949e3
--- /dev/null
+++ b/src/postscreen/postscreen_endpt.c
@@ -0,0 +1,232 @@
+/*++
+/* NAME
+/* postscreen_endpt 3
+/* SUMMARY
+/* look up connection endpoint information
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_endpt_lookup(smtp_client_stream, lookup_done)
+/* VSTREAM *smtp_client_stream;
+/* void (*lookup_done)(status, smtp_client_stream,
+/* smtp_client_addr, smtp_client_port,
+/* smtp_server_addr, smtp_server_port)
+/* int status;
+/* MAI_HOSTADDR_STR *smtp_client_addr;
+/* MAI_SERVPORT_STR *smtp_client_port;
+/* MAI_HOSTADDR_STR *smtp_server_addr;
+/* MAI_SERVPORT_STR *smtp_server_port;
+/* AUXILIARY METHODS
+/* void psc_endpt_local_lookup(smtp_client_stream, lookup_done)
+/* VSTREAM *smtp_client_stream;
+/* void (*lookup_done)(status, smtp_client_stream,
+/* smtp_client_addr, smtp_client_port,
+/* smtp_server_addr, smtp_server_port)
+/* int status;
+/* MAI_HOSTADDR_STR *smtp_client_addr;
+/* MAI_SERVPORT_STR *smtp_client_port;
+/* MAI_HOSTADDR_STR *smtp_server_addr;
+/* MAI_SERVPORT_STR *smtp_server_port;
+/* DESCRIPTION
+/* psc_endpt_lookup() looks up remote and local connection
+/* endpoint information, either through local system calls,
+/* or through an adapter for an up-stream proxy protocol.
+/*
+/* The following summarizes what the postscreen(8) server
+/* expects from a proxy protocol adapter routine.
+/* .IP \(bu
+/* Accept the same arguments as psc_endpt_lookup().
+/* .IP \(bu
+/* Call psc_endpt_local_lookup() to look up connection info
+/* when the upstream proxy indicates that the connection is
+/* not proxied (e.g., health check probe).
+/* .IP \(bu
+/* Validate protocol, address and port syntax. Permit only
+/* protocols that are configured with the main.cf:inet_protocols
+/* setting.
+/* .IP \(bu
+/* Convert IPv4-in-IPv6 address syntax to IPv4 syntax when
+/* both IPv6 and IPv4 support are enabled with main.cf:inet_protocols.
+/* .IP \(bu
+/* Log a clear warning message that explains why a request
+/* fails.
+/* .IP \(bu
+/* Never talk to the remote SMTP client.
+/* .PP
+/* Arguments:
+/* .IP client_stream
+/* A brand-new stream that is connected to the remote client.
+/* This argument MUST be passed to psc_endpt_local_lookup()
+/* if the up-stream proxy indicates that a connection is not
+/* proxied.
+/* .IP lookup
+/* Call-back routine that reports the result status, address
+/* and port information. The result status is -1 in case of
+/* error, 0 in case of success. This MUST NOT be called directly
+/* if the up-stream proxy indicates that a connection is not
+/* proxied; instead this MUST be called indirectly by
+/* psc_endpt_local_lookup().
+/* 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>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <vstream.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <haproxy_srvr.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+#include <postscreen_haproxy.h>
+
+static const INET_PROTO_INFO *proto_info;
+
+/* psc_sockaddr_to_hostaddr - transform endpoint address and port to string */
+
+static int psc_sockaddr_to_hostaddr(struct sockaddr *addr_storage,
+ SOCKADDR_SIZE addr_storage_len,
+ MAI_HOSTADDR_STR *addr_buf,
+ MAI_SERVPORT_STR *port_buf,
+ int socktype)
+{
+ int aierr;
+
+ if ((aierr = sockaddr_to_hostaddr(addr_storage, addr_storage_len,
+ addr_buf, port_buf, socktype)) == 0
+ && strncasecmp("::ffff:", addr_buf->buf, 7) == 0
+ && strchr((char *) proto_info->sa_family_list, AF_INET) != 0)
+ memmove(addr_buf->buf, addr_buf->buf + 7,
+ sizeof(addr_buf->buf) - 7);
+ return (aierr);
+}
+
+/* psc_endpt_local_lookup - look up local system connection information */
+
+void psc_endpt_local_lookup(VSTREAM *smtp_client_stream,
+ PSC_ENDPT_LOOKUP_FN lookup_done)
+{
+ struct sockaddr_storage addr_storage;
+ SOCKADDR_SIZE addr_storage_len = sizeof(addr_storage);
+ int status;
+ MAI_HOSTADDR_STR smtp_client_addr;
+ MAI_SERVPORT_STR smtp_client_port;
+ MAI_HOSTADDR_STR smtp_server_addr;
+ MAI_SERVPORT_STR smtp_server_port;
+ int aierr;
+
+ /*
+ * Look up the remote SMTP client address and port.
+ */
+ if (getpeername(vstream_fileno(smtp_client_stream), (struct sockaddr *)
+ &addr_storage, &addr_storage_len) < 0) {
+ msg_warn("getpeername: %m -- dropping this connection");
+ status = -1;
+ }
+
+ /*
+ * Convert the remote SMTP client address and port to printable form for
+ * logging and access control.
+ */
+ else if ((aierr = psc_sockaddr_to_hostaddr(
+ (struct sockaddr *) &addr_storage,
+ addr_storage_len, &smtp_client_addr,
+ &smtp_client_port, SOCK_STREAM)) != 0) {
+ msg_warn("cannot convert client address/port to string: %s"
+ " -- dropping this connection",
+ MAI_STRERROR(aierr));
+ status = -1;
+ }
+
+ /*
+ * Look up the local SMTP server address and port.
+ */
+ else if (getsockname(vstream_fileno(smtp_client_stream),
+ (struct sockaddr *) &addr_storage,
+ &addr_storage_len) < 0) {
+ msg_warn("getsockname: %m -- dropping this connection");
+ status = -1;
+ }
+
+ /*
+ * Convert the local SMTP server address and port to printable form for
+ * logging.
+ */
+ else if ((aierr = psc_sockaddr_to_hostaddr(
+ (struct sockaddr *) &addr_storage,
+ addr_storage_len, &smtp_server_addr,
+ &smtp_server_port, SOCK_STREAM)) != 0) {
+ msg_warn("cannot convert server address/port to string: %s"
+ " -- dropping this connection",
+ MAI_STRERROR(aierr));
+ status = -1;
+ } else {
+ status = 0;
+ }
+ lookup_done(status, smtp_client_stream,
+ &smtp_client_addr, &smtp_client_port,
+ &smtp_server_addr, &smtp_server_port);
+}
+
+ /*
+ * Lookup table for available proxy protocols.
+ */
+typedef struct {
+ const char *name;
+ void (*endpt_lookup) (VSTREAM *, PSC_ENDPT_LOOKUP_FN);
+} PSC_ENDPT_LOOKUP_INFO;
+
+static const PSC_ENDPT_LOOKUP_INFO psc_endpt_lookup_info[] = {
+ NOPROXY_PROTO_NAME, psc_endpt_local_lookup,
+ HAPROXY_PROTO_NAME, psc_endpt_haproxy_lookup,
+ 0,
+};
+
+/* psc_endpt_lookup - look up connection endpoint information */
+
+void psc_endpt_lookup(VSTREAM *smtp_client_stream,
+ PSC_ENDPT_LOOKUP_FN notify)
+{
+ const PSC_ENDPT_LOOKUP_INFO *pp;
+
+ if (proto_info == 0)
+ proto_info = inet_proto_info();
+
+ for (pp = psc_endpt_lookup_info; /* see below */ ; pp++) {
+ if (pp->name == 0)
+ msg_fatal("unsupported %s value: %s",
+ VAR_PSC_UPROXY_PROTO, var_psc_uproxy_proto);
+ if (strcmp(var_psc_uproxy_proto, pp->name) == 0) {
+ pp->endpt_lookup(smtp_client_stream, notify);
+ return;
+ }
+ }
+}
diff --git a/src/postscreen/postscreen_expand.c b/src/postscreen/postscreen_expand.c
new file mode 100644
index 0000000..ecda543
--- /dev/null
+++ b/src/postscreen/postscreen_expand.c
@@ -0,0 +1,141 @@
+/*++
+/* NAME
+/* postscreen_expand 3
+/* SUMMARY
+/* SMTP server macro expansion
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_expand_init()
+/*
+/* VSTRING *psc_expand_filter;
+/*
+/* const char *psc_expand_lookup(name, unused_mode, context)
+/* const char *name;
+/* int unused_mode;
+/* char *context;
+/* DESCRIPTION
+/* This module expands session-related macros.
+/*
+/* psc_expand_init() performs one-time initialization
+/* of the psc_expand_filter buffer.
+/*
+/* The psc_expand_filter buffer contains the characters
+/* that are allowed in macro expansion, as specified with the
+/* psc_expand_filter configuration parameter.
+/*
+/* psc_expand_lookup() returns the value of the named
+/* macro or a null pointer.
+/*
+/* Arguments:
+/* .IP name
+/* Macro name.
+/* .IP context
+/* Call-back context (a PSC_STATE pointer).
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal errors: out of memory.
+/* internal protocol errors. postscreen_expand() returns the
+/* binary OR of MAC_PARSE_ERROR (syntax error) and MAC_PARSE_UNDEF
+/* (undefined macro name).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Pre-parsed expansion filter.
+ */
+VSTRING *psc_expand_filter;
+
+/* psc_expand_init - initialize once during process lifetime */
+
+void psc_expand_init(void)
+{
+
+ /*
+ * Expand the expansion filter :-)
+ */
+ psc_expand_filter = vstring_alloc(10);
+ unescape(psc_expand_filter, var_psc_exp_filter);
+}
+
+/* psc_expand_lookup - generic SMTP attribute $name expansion */
+
+const char *psc_expand_lookup(const char *name, int unused_mode,
+ void *context)
+{
+ PSC_STATE *state = (PSC_STATE *) context;
+ time_t now;
+ struct tm *lt;
+
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(10);
+
+ if (msg_verbose > 1)
+ msg_info("psc_expand_lookup: ${%s}", name);
+
+#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)
+#define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0)
+#define CONST_LEN(x) (sizeof(x) - 1)
+
+ /*
+ * Don't query main.cf parameters, as the result of expansion could
+ * reveal system-internal information in server replies.
+ *
+ * XXX: This said, multiple servers may be behind a single client-visible
+ * name or IP address, and each may generate its own logs. Therefore, it
+ * may be useful to expose the replying MTA id (myhostname) in the
+ * contact footer, to identify the right logs. So while we don't expose
+ * the raw configuration dictionary, we do expose "$myhostname" as
+ * expanded in var_myhostname.
+ *
+ * Return NULL only for non-existent names.
+ */
+ if (STREQ(name, MAIL_ATTR_SERVER_NAME)) {
+ return (var_myhostname);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_ADDR)) {
+ return (state->smtp_client_addr);
+ } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_PORT)) {
+ return (state->smtp_client_port);
+ } if (STREQ(name, MAIL_ATTR_LOCALTIME)) {
+ if (time(&now) == (time_t) -1)
+ msg_fatal("time lookup failed: %m");
+ lt = localtime(&now);
+ VSTRING_RESET(state->expand_buf);
+ do {
+ VSTRING_SPACE(state->expand_buf, 100);
+ } while (strftime(STR(state->expand_buf),
+ vstring_avail(state->expand_buf),
+ "%b %d %H:%M:%S", lt) == 0);
+ return (STR(state->expand_buf));
+ } else {
+ msg_warn("unknown macro name \"%s\" in expansion request", name);
+ return (0);
+ }
+}
diff --git a/src/postscreen/postscreen_haproxy.c b/src/postscreen/postscreen_haproxy.c
new file mode 100644
index 0000000..45c6b9a
--- /dev/null
+++ b/src/postscreen/postscreen_haproxy.c
@@ -0,0 +1,137 @@
+/*++
+/* NAME
+/* postscreen_haproxy 3
+/* SUMMARY
+/* haproxy protocol adapter
+/* SYNOPSIS
+/* #include <postscreen_haproxy.h>
+/*
+/* void psc_endpt_haproxy_lookup(smtp_client_stream, lookup_done)
+/* VSTRING *smtp_client_stream;
+/* void (*lookup_done)(status, smtp_client_stream,
+/* smtp_client_addr, smtp_client_port,
+/* smtp_server_addr, smtp_server_port)
+/* int status;
+/* MAI_HOSTADDR_STR *smtp_client_addr;
+/* MAI_SERVPORT_STR *smtp_client_port;
+/* MAI_HOSTADDR_STR *smtp_server_addr;
+/* MAI_SERVPORT_STR *smtp_server_port;
+/* DESCRIPTION
+/* psc_endpt_haproxy_lookup() looks up connection endpoint
+/* information via the haproxy protocol, or looks up local
+/* information if the haproxy handshake indicates that a
+/* connection is not proxied. Arguments and results conform
+/* to the postscreen_endpt(3) API.
+/* 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 <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <myaddrinfo.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <haproxy_srvr.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+#include <postscreen_haproxy.h>
+
+ /*
+ * Per-session state.
+ */
+typedef struct {
+ VSTREAM *stream;
+ PSC_ENDPT_LOOKUP_FN notify;
+} PSC_HAPROXY_STATE;
+
+/* psc_endpt_haproxy_event - read or time event */
+
+static void psc_endpt_haproxy_event(int event, void *context)
+{
+ const char *myname = "psc_endpt_haproxy_event";
+ PSC_HAPROXY_STATE *state = (PSC_HAPROXY_STATE *) context;
+ int status = 0;
+ MAI_HOSTADDR_STR smtp_client_addr;
+ MAI_SERVPORT_STR smtp_client_port;
+ MAI_HOSTADDR_STR smtp_server_addr;
+ MAI_SERVPORT_STR smtp_server_port;
+ int non_proxy = 0;
+
+ switch (event) {
+ case EVENT_TIME:
+ msg_warn("haproxy read: time limit exceeded");
+ status = -1;
+ break;
+ case EVENT_READ:
+ status = haproxy_srvr_receive(vstream_fileno(state->stream), &non_proxy,
+ &smtp_client_addr, &smtp_client_port,
+ &smtp_server_addr, &smtp_server_port);
+ }
+
+ /*
+ * Terminate this pseudo thread, and notify the caller.
+ */
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->stream),
+ psc_endpt_haproxy_event, context);
+ if (status == 0 && non_proxy)
+ psc_endpt_local_lookup(state->stream, state->notify);
+ else
+ state->notify(status, state->stream,
+ &smtp_client_addr, &smtp_client_port,
+ &smtp_server_addr, &smtp_server_port);
+ /* Note: the stream may be closed at this point. */
+ myfree((void *) state);
+}
+
+/* psc_endpt_haproxy_lookup - event-driven haproxy client */
+
+void psc_endpt_haproxy_lookup(VSTREAM *stream,
+ PSC_ENDPT_LOOKUP_FN notify)
+{
+ const char *myname = "psc_endpt_haproxy_lookup";
+ PSC_HAPROXY_STATE *state;
+
+ /*
+ * Prepare the per-session state. XXX To improve overload behavior,
+ * maintain a pool of these so that we can reduce memory allocator
+ * activity.
+ */
+ state = (PSC_HAPROXY_STATE *) mymalloc(sizeof(*state));
+ state->stream = stream;
+ state->notify = notify;
+
+ /*
+ * Read the haproxy line.
+ */
+ PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_endpt_haproxy_event,
+ (void *) state, var_psc_uproxy_tmout);
+}
diff --git a/src/postscreen/postscreen_haproxy.h b/src/postscreen/postscreen_haproxy.h
new file mode 100644
index 0000000..7557fb3
--- /dev/null
+++ b/src/postscreen/postscreen_haproxy.h
@@ -0,0 +1,30 @@
+/*++
+/* NAME
+/* postscreen_haproxy 3h
+/* SUMMARY
+/* postscreen haproxy protocol support
+/* SYNOPSIS
+/* #include <postscreen_haproxy.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * haproxy protocol interface.
+ */
+extern void psc_endpt_haproxy_lookup(VSTREAM *, PSC_ENDPT_LOOKUP_FN);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
diff --git a/src/postscreen/postscreen_misc.c b/src/postscreen/postscreen_misc.c
new file mode 100644
index 0000000..f07c9ac
--- /dev/null
+++ b/src/postscreen/postscreen_misc.c
@@ -0,0 +1,164 @@
+/*++
+/* NAME
+/* postscreen_misc 3
+/* SUMMARY
+/* postscreen misc routines
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* char *psc_format_delta_time(buf, tv, delta)
+/* VSTRING *buf;
+/* struct timeval tv;
+/* DELTA_TIME *delta;
+/*
+/* void psc_conclude(state)
+/* PSC_STATE *state;
+/*
+/* void psc_hangup_event(state)
+/* PSC_STATE *state;
+/* DESCRIPTION
+/* psc_format_delta_time() computes the time difference between
+/* tv (past) and the present, formats the time difference with
+/* sub-second resolution in a human-readable way, and returns
+/* the integer time difference in seconds through the delta
+/* argument.
+/*
+/* psc_conclude() logs when a client passes all necessary tests,
+/* updates the postscreen cache for any testes that were passed,
+/* and either forwards the connection to a real SMTP server or
+/* replies with the text in state->error_reply and hangs up the
+/* connection (by default, state->error_reply is set to a
+/* default 421 reply).
+/*
+/* psc_hangup_event() cleans up after a client connection breaks
+/* unexpectedly. If logs the test where the break happened,
+/* and how much time as spent in that test before the connection
+/* broke.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <iostuff.h>
+#include <format_tv.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+/* psc_format_delta_time - pretty-formatted delta time */
+
+char *psc_format_delta_time(VSTRING *buf, struct timeval tv,
+ DELTA_TIME *delta)
+{
+ DELTA_TIME pdelay;
+ struct timeval now;
+
+ GETTIMEOFDAY(&now);
+ PSC_CALC_DELTA(pdelay, now, tv);
+ VSTRING_RESET(buf);
+ format_tv(buf, pdelay.dt_sec, pdelay.dt_usec, SIG_DIGS, var_delay_max_res);
+ *delta = pdelay;
+ return (STR(buf));
+}
+
+/* psc_conclude - bring this session to a conclusion */
+
+void psc_conclude(PSC_STATE *state)
+{
+ const char *myname = "psc_conclude";
+
+ if (msg_verbose)
+ msg_info("flags for %s: %s",
+ myname, psc_print_state_flags(state->flags, myname));
+
+ /*
+ * Handle clients that passed at least one test other than permanent
+ * allowlisting, and that didn't fail any test including permanent
+ * denylisting. There may still be unfinished tests; those tests will
+ * need to be completed when the client returns in a later session.
+ */
+ if (state->flags & PSC_STATE_MASK_ANY_FAIL)
+ state->flags &= ~PSC_STATE_MASK_ANY_PASS;
+
+ /*
+ * Log our final blessing when all unfinished tests were completed.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_PASS) != 0
+ && (state->flags & PSC_STATE_MASK_ANY_PASS) ==
+ PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_ANY_TODO))
+ msg_info("PASS %s [%s]:%s", (state->flags & PSC_STATE_FLAG_NEW) == 0
+ || state->client_info->pass_new_count++ > 0 ?
+ "OLD" : "NEW", PSC_CLIENT_ADDR_PORT(state));
+
+ /*
+ * Update the postscreen cache. This still supports a scenario where a
+ * client gets allowlisted in the course of multiple sessions, as long as
+ * that client does not "fail" any test. Don't try to optimize away cache
+ * updates; we want cached information to be up-to-date even if a test
+ * result is renewed during overlapping SMTP sessions, and even if
+ * 'postfix reload' happens in the middle of that.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) != 0
+ && psc_cache_map != 0) {
+ psc_print_tests(psc_temp, state);
+ psc_cache_update(psc_cache_map, state->smtp_client_addr, STR(psc_temp));
+ }
+
+ /*
+ * Either hand off the socket to a real SMTP engine, or say bye-bye.
+ */
+ if ((state->flags & PSC_STATE_FLAG_NOFORWARD) == 0) {
+ psc_send_socket(state);
+ } else {
+ if ((state->flags & PSC_STATE_FLAG_HANGUP) == 0)
+ (void) PSC_SEND_REPLY(state, state->final_reply);
+ msg_info("DISCONNECT [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
+ psc_free_session_state(state);
+ }
+}
+
+/* psc_hangup_event - handle unexpected disconnect */
+
+void psc_hangup_event(PSC_STATE *state)
+{
+ DELTA_TIME elapsed;
+
+ /*
+ * Sessions can break at any time, even after the client passes all tests
+ * (some MTAs including Postfix don't send QUIT when connection reuse is
+ * enabled). This must not be treated as a protocol test failure.
+ *
+ * Log the current test phase, and the elapsed time after the start of that
+ * phase.
+ */
+ state->flags |= PSC_STATE_FLAG_HANGUP;
+ msg_info("HANGUP after %s from [%s]:%s in %s",
+ psc_format_delta_time(psc_temp, state->start_time, &elapsed),
+ PSC_CLIENT_ADDR_PORT(state), state->test_name);
+ state->flags |= PSC_STATE_FLAG_NOFORWARD;
+ psc_conclude(state);
+}
diff --git a/src/postscreen/postscreen_send.c b/src/postscreen/postscreen_send.c
new file mode 100644
index 0000000..53714b1
--- /dev/null
+++ b/src/postscreen/postscreen_send.c
@@ -0,0 +1,293 @@
+/*++
+/* NAME
+/* postscreen_send 3
+/* SUMMARY
+/* postscreen low-level output
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void pcs_send_pre_jail_init(void)
+/*
+/* int psc_send_reply(state, text)
+/* PSC_STATE *state;
+/* const char *text;
+/*
+/* int PSC_SEND_REPLY(state, text)
+/* PSC_STATE *state;
+/* const char *text;
+/*
+/* void psc_send_socket(state)
+/* PSC_STATE *state;
+/* DESCRIPTION
+/* pcs_send_pre_jail_init() performs one-time initialization.
+/*
+/* psc_send_reply() sends the specified text to the specified
+/* remote SMTP client. In case of an immediate error, it logs
+/* a warning (except EPIPE) with the client address and port,
+/* and returns a non-zero result (all errors including EPIPE).
+/*
+/* psc_send_reply() does a best effort to send the reply, but
+/* it won't block when the output is throttled by a hostile
+/* peer.
+/*
+/* PSC_SEND_REPLY() is a legacy wrapper for psc_send_reply().
+/* It will eventually be replaced by its expansion.
+/*
+/* psc_send_socket() sends the specified socket to the real
+/* Postfix SMTP server. The socket is delivered in the background.
+/* This function must be called after all other session-related
+/* work is finished including postscreen cache updates.
+/*
+/* In case of an immediate error, psc_send_socket() sends a 421
+/* reply to the remote SMTP client and closes the connection.
+/* If the 220- greeting was sent, sending 421 would be invalid;
+/* instead, the client is redirected to the dummy SMTP engine
+/* which sends the 421 reply at the first legitimate opportunity.
+/* 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>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+#include <connect.h>
+#include <attr.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <smtp_reply_footer.h>
+#include <mail_proto.h>
+#include <maps.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+static MAPS *psc_rej_ftr_maps;
+
+ /*
+ * This program screens all inbound SMTP connections, so it better not waste
+ * time.
+ */
+#define PSC_SEND_SOCK_CONNECT_TIMEOUT 1
+#define PSC_SEND_SOCK_NOTIFY_TIMEOUT 100
+
+/* pcs_send_pre_jail_init - initialize */
+
+void pcs_send_pre_jail_init(void)
+{
+ static int init_count = 0;
+
+ if (init_count++ != 0)
+ msg_panic("pcs_send_pre_jail_init: multiple calls");
+
+ /*
+ * SMTP server reject footer.
+ */
+ if (*var_psc_rej_ftr_maps)
+ psc_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS,
+ var_psc_rej_ftr_maps,
+ DICT_FLAG_LOCK);
+}
+
+/* psc_get_footer - find that footer */
+
+static const char *psc_get_footer(const char *text, ssize_t text_len)
+{
+ static VSTRING *footer_buf = 0;
+
+ if (footer_buf == 0)
+ footer_buf = vstring_alloc(100);
+ /* Strip the \r\n for consistency with smtpd. */
+ vstring_strncpy(footer_buf, text, text_len);
+ return (psc_maps_find(psc_rej_ftr_maps, STR(footer_buf), 0));
+}
+
+/* psc_send_reply - send reply to remote SMTP client */
+
+int psc_send_reply(PSC_STATE *state, const char *text)
+{
+ ssize_t start;
+ int ret;
+ const char *footer;
+ ssize_t text_len = strlen(text) - 2;
+
+ if (msg_verbose)
+ msg_info("> [%s]:%s: %.*s", state->smtp_client_addr,
+ state->smtp_client_port, (int) text_len, text);
+
+ /*
+ * Append the new text to earlier text that could not be sent because the
+ * output was throttled.
+ */
+ start = VSTRING_LEN(state->send_buf);
+ vstring_strcat(state->send_buf, text);
+
+ /*
+ * For soft_bounce support, we also fix the REJECT logging before the
+ * dummy SMTP engine calls the psc_send_reply() output routine. We do
+ * some double work, but it is for debugging only.
+ */
+ if (var_soft_bounce) {
+ if (text[0] == '5')
+ STR(state->send_buf)[start + 0] = '4';
+ if (text[4] == '5')
+ STR(state->send_buf)[start + 4] = '4';
+ }
+
+ /*
+ * Append the optional reply footer.
+ */
+ if ((*text == '4' || *text == '5')
+ && ((psc_rej_ftr_maps != 0
+ && (footer = psc_get_footer(text, text_len)) != 0)
+ || *(footer = var_psc_rej_footer) != 0))
+ smtp_reply_footer(state->send_buf, start, footer,
+ STR(psc_expand_filter), psc_expand_lookup,
+ (void *) state);
+
+ /*
+ * Do a best effort sending text, but don't block when the output is
+ * throttled by a hostile peer.
+ */
+ ret = write(vstream_fileno(state->smtp_client_stream),
+ STR(state->send_buf), LEN(state->send_buf));
+ if (ret > 0)
+ vstring_truncate(state->send_buf, ret - LEN(state->send_buf));
+ if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET)
+ msg_warn("write [%s]:%s: %m", state->smtp_client_addr,
+ state->smtp_client_port);
+ return (ret < 0 && errno != EAGAIN);
+}
+
+/* psc_send_socket_close_event - file descriptor has arrived or timeout */
+
+static void psc_send_socket_close_event(int event, void *context)
+{
+ const char *myname = "psc_send_socket_close_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d event %d on send socket %d from [%s]:%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ event, state->smtp_server_fd, state->smtp_client_addr,
+ state->smtp_client_port);
+
+ /*
+ * The real SMTP server has closed the local IPC channel, or we have
+ * reached the limit of our patience. In the latter case it is still
+ * possible that the real SMTP server will receive the socket so we
+ * should not interfere.
+ */
+ PSC_CLEAR_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
+ context);
+ if (event == EVENT_TIME)
+ msg_warn("timeout sending connection to service %s",
+ psc_smtpd_service_name);
+ psc_free_session_state(state);
+}
+
+/* psc_send_socket - send socket to real SMTP server process */
+
+void psc_send_socket(PSC_STATE *state)
+{
+ const char *myname = "psc_send_socket";
+ int server_fd;
+ int pass_err;
+ VSTREAM *fp;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ vstream_fileno(state->smtp_client_stream),
+ state->smtp_client_addr, state->smtp_client_port);
+
+ /*
+ * Connect to the real SMTP service over a local IPC channel, send the
+ * file descriptor, and close the file descriptor to save resources.
+ * Experience has shown that some systems will discard information when
+ * we close a channel immediately after writing. Thus, we waste resources
+ * waiting for the remote side to close the local IPC channel first. The
+ * good side of waiting is that we learn when the real SMTP server is
+ * falling behind.
+ *
+ * This is where we would forward the connection to an SMTP server that
+ * provides an appropriate level of service for this client class. For
+ * example, a server that is more forgiving, or one that is more
+ * suspicious. Alternatively, we could send attributes along with the
+ * socket with client reputation information, making everything even more
+ * Postfix-specific.
+ */
+ if ((server_fd =
+ LOCAL_CONNECT(psc_smtpd_service_name, NON_BLOCKING,
+ PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) {
+ msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name);
+ if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
+ PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
+ } else {
+ PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n");
+ psc_free_session_state(state);
+ }
+ return;
+ }
+ /* XXX Note: no dummy read between LOCAL_SEND_FD() and attr_print(). */
+ fp = vstream_fdopen(server_fd, O_RDWR);
+ pass_err =
+ (LOCAL_SEND_FD(server_fd,
+ vstream_fileno(state->smtp_client_stream)) < 0
+ || (attr_print(fp, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->smtp_client_addr),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->smtp_client_port),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, state->smtp_server_addr),
+ SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, state->smtp_server_port),
+ ATTR_TYPE_END) || vstream_fflush(fp)));
+ /* XXX Note: no read between attr_print() and vstream_fdclose(). */
+ (void) vstream_fdclose(fp);
+ if (pass_err != 0) {
+ msg_warn("cannot pass connection to service %s: %m",
+ psc_smtpd_service_name);
+ (void) close(server_fd);
+ if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
+ PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
+ } else {
+ PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n");
+ psc_free_session_state(state);
+ }
+ return;
+ } else {
+
+ /*
+ * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug
+ * where smtp-source sometimes sees the connection being closed after
+ * it has already received the real SMTP server's 220 greeting!
+ */
+#if 0
+ PSC_DEL_CLIENT_STATE(state);
+#endif
+ PSC_ADD_SERVER_STATE(state, server_fd);
+ PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
+ (void *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT);
+ return;
+ }
+}
diff --git a/src/postscreen/postscreen_smtpd.c b/src/postscreen/postscreen_smtpd.c
new file mode 100644
index 0000000..edd5d71
--- /dev/null
+++ b/src/postscreen/postscreen_smtpd.c
@@ -0,0 +1,1339 @@
+/*++
+/* NAME
+/* postscreen_smtpd 3
+/* SUMMARY
+/* postscreen built-in SMTP server engine
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_smtpd_pre_jail_init(void)
+/*
+/* void psc_smtpd_init(void)
+/*
+/* void psc_smtpd_tests(state)
+/* PSC_STATE *state;
+/*
+/* void PSC_SMTPD_X21(state, final_reply)
+/* PSC_STATE *state;
+/* const char *final_reply;
+/* DESCRIPTION
+/* psc_smtpd_pre_jail_init() performs one-time per-process
+/* initialization during the "before chroot" execution phase.
+/*
+/* psc_smtpd_init() performs one-time per-process initialization.
+/*
+/* psc_smtpd_tests() starts up an SMTP server engine for deep
+/* protocol tests and for collecting helo/sender/recipient
+/* information.
+/*
+/* PSC_SMTPD_X21() redirects the SMTP client to an SMTP server
+/* engine, which sends the specified final reply at the first
+/* legitimate opportunity without doing any protocol tests.
+/*
+/* Unlike the Postfix SMTP server, this engine does not announce
+/* PIPELINING support. This exposes spambots that pipeline
+/* their commands anyway. Like the Postfix SMTP server, this
+/* engine will accept input with bare newline characters. To
+/* pass the "pipelining" and "bare newline" test, the client
+/* has to properly speak SMTP all the way to the RCPT TO
+/* command. These tests fail if the client violates the protocol
+/* at any stage.
+/*
+/* No support is announced for AUTH, XCLIENT or XFORWARD.
+/* Clients that need this should be allowlisted or should talk
+/* directly to the submission service.
+/*
+/* The engine rejects RCPT TO and VRFY commands with the
+/* state->rcpt_reply response which depends on program history,
+/* rejects ETRN with a generic response, and closes the
+/* connection after QUIT.
+/*
+/* Since this engine defers or rejects all non-junk commands,
+/* there is no point maintaining separate counters for "error"
+/* commands and "junk" commands. Instead, the engine maintains
+/* a per-session command counter, and terminates the session
+/* with a 421 reply when the command count exceeds the limit.
+/*
+/* We limit the command count, as well as the total time to
+/* receive a command. This limits the time per client more
+/* effectively than would be possible with read() timeouts.
+/*
+/* There is no concern about getting blocked on output. The
+/* psc_send() routine uses non-blocking output, and discards
+/* output that the client is not willing to receive.
+/* PROTOCOL INSPECTION VERSUS CONTENT INSPECTION
+/* The goal of postscreen is to keep spambots away from Postfix.
+/* To recognize spambots, postscreen measures properties of
+/* the client IP address and of the client SMTP protocol
+/* implementation. These client properties don't change with
+/* each delivery attempt. Therefore it is possible to make a
+/* long-term decision after a single measurement. For example,
+/* allow a good client to skip the DNSBL test for 24 hours,
+/* or to skip the pipelining test for one week.
+/*
+/* If postscreen were to measure properties of message content
+/* (MIME compliance, etc.) then it would measure properties
+/* that may change with each delivery attempt. Here, it would
+/* be wrong to make a long-term decision after a single
+/* measurement. Instead, postscreen would need to develop a
+/* ranking based on the content of multiple messages from the
+/* same client.
+/*
+/* Many spambots avoid spamming the same site repeatedly.
+/* Thus, postscreen must make decisions after a single
+/* measurement. Message content is not a good indicator for
+/* making long-term decisions after single measurements, and
+/* that is why postscreen does not inspect message content.
+/* REJECTING RCPT TO VERSUS SENDING LIVE SOCKETS TO SMTPD(8)
+/* When post-handshake protocol tests are enabled, postscreen
+/* rejects the RCPT TO command from a good client, and forces
+/* it to deliver mail in a later session. This is why
+/* post-handshake protocol tests have a longer expiration time
+/* than pre-handshake tests.
+/*
+/* Instead, postscreen could send the network socket to smtpd(8)
+/* and ship the session history (including TLS and other SMTP
+/* or non-SMTP attributes) as auxiliary data. The Postfix SMTP
+/* server would then use new code to replay the session history,
+/* and would use existing code to validate the client, helo,
+/* sender and recipient address.
+/*
+/* Such an approach would increase the implementation and
+/* maintenance effort, because:
+/*
+/* 1) New replay code would be needed in smtpd(8), such that
+/* the HELO, EHLO, and MAIL command handlers can delay their
+/* error responses until the RCPT TO reply.
+/*
+/* 2) postscreen(8) would have to implement more of smtpd(8)'s
+/* syntax checks, to avoid confusing delayed "syntax error"
+/* and other error responses syntax error responses while
+/* replaying history.
+/*
+/* 3) New code would be needed in postscreen(8) and smtpd(8)
+/* to send and receive the session history (including TLS and
+/* other SMTP or non-SMTP attributes) as auxiliary data while
+/* sending the network socket from postscreen(8) to smtpd(8).
+/* REJECTING RCPT TO VERSUS PROXYING LIVE SESSIONS TO SMTPD(8)
+/* An alternative would be to proxy the session history to a
+/* real Postfix SMTP process, presumably passing TLS and other
+/* attributes via an extended XCLIENT implementation. That
+/* would require all the work described in 2) above, plus
+/* duplication of all the features of the smtpd(8) TLS engine,
+/* plus additional XCLIENT support for a lot more attributes.
+/* 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>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <is_header.h>
+#include <string_list.h>
+#include <maps.h>
+#include <ehlo_mask.h>
+#include <lex_822.h>
+#include <info_log_addr_form.h>
+
+/* TLS library. */
+
+#include <tls.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Plan for future body processing. See smtp-sink.c. For now, we have no
+ * per-session push-back except for the single-character push-back that
+ * VSTREAM guarantees after we read one character.
+ */
+#define PSC_SMTPD_HAVE_PUSH_BACK(state) (0)
+#define PSC_SMTPD_PUSH_BACK_CHAR(state, ch) \
+ vstream_ungetc((state)->smtp_client_stream, (ch))
+#define PSC_SMTPD_NEXT_CHAR(state) \
+ VSTREAM_GETC((state)->smtp_client_stream)
+
+#define PSC_SMTPD_BUFFER_EMPTY(state) \
+ (!PSC_SMTPD_HAVE_PUSH_BACK(state) \
+ && vstream_peek((state)->smtp_client_stream) <= 0)
+
+#define PSC_SMTPD_PEEK_DATA(state) \
+ vstream_peek_data((state)->smtp_client_stream)
+#define PSC_SMTPD_PEEK_LEN(state) \
+ vstream_peek((state)->smtp_client_stream)
+
+ /*
+ * Dynamic reply strings. To minimize overhead we format these once.
+ */
+static char *psc_smtpd_greeting; /* smtp banner */
+static char *psc_smtpd_helo_reply; /* helo reply */
+static char *psc_smtpd_ehlo_reply_plain;/* multi-line ehlo reply, non-TLS */
+static char *psc_smtpd_ehlo_reply_tls; /* multi-line ehlo reply, with TLS */
+static char *psc_smtpd_timeout_reply; /* timeout reply */
+static char *psc_smtpd_421_reply; /* generic final_reply value */
+
+ /*
+ * Forward declaration, needed by PSC_CLEAR_EVENT_REQUEST.
+ */
+static void psc_smtpd_time_event(int, void *);
+static void psc_smtpd_read_event(int, void *);
+
+ /*
+ * Encapsulation. The STARTTLS, EHLO and AUTH command handlers temporarily
+ * suspend SMTP command events, send an asynchronous proxy request, and
+ * resume SMTP command events after receiving the asynchronous proxy
+ * response (the EHLO handler must asynchronously talk to the auth server
+ * before it can announce the SASL mechanism list; the list can depend on
+ * the client IP address and on the presence on TLS encryption).
+ */
+#define PSC_RESUME_SMTP_CMD_EVENTS(state) do { \
+ PSC_READ_EVENT_REQUEST2(vstream_fileno((state)->smtp_client_stream), \
+ psc_smtpd_read_event, psc_smtpd_time_event, \
+ (void *) (state), PSC_EFF_CMD_TIME_LIMIT); \
+ if (!PSC_SMTPD_BUFFER_EMPTY(state)) \
+ psc_smtpd_read_event(EVENT_READ, (void *) state); \
+ } while (0)
+
+#define PSC_SUSPEND_SMTP_CMD_EVENTS(state) \
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
+ psc_smtpd_time_event, (void *) (state));
+
+ /*
+ * Make control characters and other non-text visible.
+ */
+#define PSC_SMTPD_ESCAPE_TEXT(dest, src, src_len, max_len) do { \
+ ssize_t _s_len = (src_len); \
+ ssize_t _m_len = (max_len); \
+ (void) escape((dest), (src), _s_len < _m_len ? _s_len : _m_len); \
+ } while (0)
+
+ /*
+ * Command parser support.
+ */
+#define PSC_SMTPD_NEXT_TOKEN(ptr) mystrtok(&(ptr), " ")
+
+ /*
+ * EHLO keyword filter
+ */
+static MAPS *psc_ehlo_discard_maps;
+static int psc_ehlo_discard_mask;
+
+ /*
+ * Command editing filter.
+ */
+static DICT *psc_cmd_filter;
+
+ /*
+ * Encapsulation. We must not forget turn off input/timer events when we
+ * terminate the SMTP protocol engine.
+ *
+ * It would be safer to turn off input/timer events after each event, and to
+ * turn on input/timer events again when we want more input. But experience
+ * with the Postfix smtp-source and smtp-sink tools shows that this would
+ * noticeably increase the run-time cost.
+ */
+#define PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, event, reply) do { \
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
+ (event), (void *) (state)); \
+ PSC_DROP_SESSION_STATE((state), (reply)); \
+ } while (0)
+
+#define PSC_CLEAR_EVENT_HANGUP(state, event) do { \
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
+ (event), (void *) (state)); \
+ psc_hangup_event(state); \
+ } while (0)
+
+/* psc_helo_cmd - record HELO and respond */
+
+static int psc_helo_cmd(PSC_STATE *state, char *args)
+{
+ char *helo_name = PSC_SMTPD_NEXT_TOKEN(args);
+
+ /*
+ * smtpd(8) incompatibility: we ignore extra words; smtpd(8) saves them.
+ */
+ if (helo_name == 0)
+ return (PSC_SEND_REPLY(state, "501 Syntax: HELO hostname\r\n"));
+
+ PSC_STRING_UPDATE(state->helo_name, helo_name);
+ PSC_STRING_RESET(state->sender);
+ /* Don't downgrade state->protocol, in case some test depends on this. */
+ return (PSC_SEND_REPLY(state, psc_smtpd_helo_reply));
+}
+
+/* psc_smtpd_format_ehlo_reply - format EHLO response */
+
+static void psc_smtpd_format_ehlo_reply(VSTRING *buf, int discard_mask
+ /* , const char *sasl_mechanism_list */ )
+{
+ const char *myname = "psc_smtpd_format_ehlo_reply";
+ int saved_len = 0;
+
+ if (msg_verbose)
+ msg_info("%s: discard_mask %s", myname, str_ehlo_mask(discard_mask));
+
+#define PSC_EHLO_APPEND(save, buf, fmt) do { \
+ (save) = LEN(buf); \
+ vstring_sprintf_append((buf), (fmt)); \
+ } while (0)
+
+#define PSC_EHLO_APPEND1(save, buf, fmt, arg1) do { \
+ (save) = LEN(buf); \
+ vstring_sprintf_append((buf), (fmt), (arg1)); \
+ } while (0)
+
+ vstring_sprintf(psc_temp, "250-%s\r\n", var_myhostname);
+ if ((discard_mask & EHLO_MASK_SIZE) == 0) {
+ if (ENFORCING_SIZE_LIMIT(var_message_limit))
+ PSC_EHLO_APPEND1(saved_len, psc_temp, "250-SIZE %lu\r\n",
+ (unsigned long) var_message_limit);
+ else
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-SIZE\r\n");
+ }
+ if ((discard_mask & EHLO_MASK_VRFY) == 0 && var_disable_vrfy_cmd == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-VRFY\r\n");
+ if ((discard_mask & EHLO_MASK_ETRN) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-ETRN\r\n");
+ if ((discard_mask & EHLO_MASK_STARTTLS) == 0 && var_psc_use_tls)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-STARTTLS\r\n");
+#ifdef TODO_SASL_AUTH
+ if ((discard_mask & EHLO_MASK_AUTH) == 0 && sasl_mechanism_list
+ && (!var_psc_tls_auth_only || (discard_mask & EHLO_MASK_STARTTLS))) {
+ PSC_EHLO_APPEND1(saved_len, psc_temp, "AUTH %s", sasl_mechanism_list);
+ if (var_broken_auth_clients)
+ PSC_EHLO_APPEND1(saved_len, psc_temp, "AUTH=%s", sasl_mechanism_list);
+ }
+#endif
+ if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-ENHANCEDSTATUSCODES\r\n");
+ if ((discard_mask & EHLO_MASK_8BITMIME) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-8BITMIME\r\n");
+ if ((discard_mask & EHLO_MASK_DSN) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-DSN\r\n");
+ /* Fix 20140708: announce SMTPUTF8. */
+ if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-SMTPUTF8\r\n");
+ if ((discard_mask & EHLO_MASK_CHUNKING) == 0)
+ PSC_EHLO_APPEND(saved_len, psc_temp, "250-CHUNKING\r\n");
+ STR(psc_temp)[saved_len + 3] = ' ';
+}
+
+/* psc_ehlo_cmd - record EHLO and respond */
+
+static int psc_ehlo_cmd(PSC_STATE *state, char *args)
+{
+ char *helo_name = PSC_SMTPD_NEXT_TOKEN(args);
+ const char *ehlo_words;
+ int discard_mask;
+ char *reply;
+
+ /*
+ * smtpd(8) incompatibility: we ignore extra words; smtpd(8) saves them.
+ */
+ if (helo_name == 0)
+ return (PSC_SEND_REPLY(state, "501 Syntax: EHLO hostname\r\n"));
+
+ PSC_STRING_UPDATE(state->helo_name, helo_name);
+ PSC_STRING_RESET(state->sender);
+ state->protocol = MAIL_PROTO_ESMTP;
+
+ /*
+ * smtpd(8) compatibility: dynamic reply filtering.
+ */
+ if (psc_ehlo_discard_maps != 0
+ && (ehlo_words = psc_maps_find(psc_ehlo_discard_maps,
+ state->smtp_client_addr, 0)) != 0
+ && (discard_mask = ehlo_mask(ehlo_words)) != psc_ehlo_discard_mask) {
+ if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
+ msg_info("[%s]%s: discarding EHLO keywords: %s",
+ PSC_CLIENT_ADDR_PORT(state), str_ehlo_mask(discard_mask));
+ if (state->flags & PSC_STATE_FLAG_USING_TLS)
+ discard_mask |= EHLO_MASK_STARTTLS;
+ psc_smtpd_format_ehlo_reply(psc_temp, discard_mask);
+ reply = STR(psc_temp);
+ state->ehlo_discard_mask = discard_mask;
+ } else if (psc_ehlo_discard_maps && psc_ehlo_discard_maps->error) {
+ msg_fatal("%s lookup error for %s",
+ psc_ehlo_discard_maps->title, state->smtp_client_addr);
+ } else if (state->flags & PSC_STATE_FLAG_USING_TLS) {
+ reply = psc_smtpd_ehlo_reply_tls;
+ state->ehlo_discard_mask = psc_ehlo_discard_mask | EHLO_MASK_STARTTLS;
+ } else {
+ reply = psc_smtpd_ehlo_reply_plain;
+ state->ehlo_discard_mask = psc_ehlo_discard_mask;
+ }
+ return (PSC_SEND_REPLY(state, reply));
+}
+
+/* psc_starttls_resume - resume the SMTP protocol after tlsproxy activation */
+
+static void psc_starttls_resume(int unused_event, void *context)
+{
+ const char *myname = "psc_starttls_resume";
+ PSC_STATE *state = (PSC_STATE *) context;
+
+ /*
+ * Reset SMTP server state if STARTTLS was successful.
+ */
+ if (state->flags & PSC_STATE_FLAG_USING_TLS) {
+ /* Purge the push-back buffer, when implemented. */
+ PSC_STRING_RESET(state->helo_name);
+ PSC_STRING_RESET(state->sender);
+#ifdef TODO_SASL_AUTH
+ /* Reset SASL AUTH state. Dovecot responses may change. */
+#endif
+ }
+
+ /*
+ * Resume read/timeout events. If we still have unread input, resume the
+ * command processor immediately.
+ */
+ PSC_RESUME_SMTP_CMD_EVENTS(state);
+}
+
+/* psc_starttls_cmd - activate the tlsproxy server */
+
+static int psc_starttls_cmd(PSC_STATE *state, char *args)
+{
+ const char *myname = "psc_starttls_cmd";
+
+ /*
+ * smtpd(8) incompatibility: we can't send a 4XX reply that TLS is
+ * unavailable when tlsproxy(8) detects the problem too late.
+ */
+ if (PSC_SMTPD_NEXT_TOKEN(args) != 0)
+ return (PSC_SEND_REPLY(state, "501 Syntax: EHLO hostname\r\n"));
+ if (state->flags & PSC_STATE_FLAG_USING_TLS)
+ return (PSC_SEND_REPLY(state,
+ "554 5.5.1 Error: TLS already active\r\n"));
+ if (var_psc_use_tls == 0 || (state->ehlo_discard_mask & EHLO_MASK_STARTTLS))
+ return (PSC_SEND_REPLY(state,
+ "502 5.5.1 Error: command not implemented\r\n"));
+
+ /*
+ * Suspend the SMTP protocol until psc_starttls_resume() is called.
+ */
+ PSC_SUSPEND_SMTP_CMD_EVENTS(state);
+ psc_starttls_open(state, psc_starttls_resume);
+ return (0);
+}
+
+/* psc_extract_addr - extract MAIL/RCPT address, unquoted form */
+
+static char *psc_extract_addr(VSTRING *result, const char *string)
+{
+ const unsigned char *cp = (const unsigned char *) string;
+ char *addr;
+ char *colon;
+ int stop_at;
+ int inquote = 0;
+
+ /*
+ * smtpd(8) incompatibility: we allow more invalid address forms, and we
+ * don't validate recipients. We are not going to deliver them so we
+ * won't have to worry about deliverability. This may have to change when
+ * we pass the socket to a real SMTP server and replay message envelope
+ * commands.
+ */
+
+ /* Skip SP characters. */
+ while (*cp && *cp == ' ')
+ cp++;
+
+ /* Choose the terminator for <addr> or bare addr. */
+ if (*cp == '<') {
+ cp++;
+ stop_at = '>';
+ } else {
+ stop_at = ' ';
+ }
+
+ /* Skip to terminator or end. */
+ VSTRING_RESET(result);
+ for ( /* void */ ; *cp; cp++) {
+ if (!inquote && *cp == stop_at)
+ break;
+ if (*cp == '"') {
+ inquote = !inquote;
+ } else {
+ if (*cp == '\\' && *++cp == 0)
+ break;
+ VSTRING_ADDCH(result, *cp);
+ }
+ }
+ VSTRING_TERMINATE(result);
+
+ /*
+ * smtpd(8) compatibility: truncate deprecated route address form. This
+ * is primarily to simplify logfile analysis.
+ */
+ addr = STR(result);
+ if (*addr == '@' && (colon = strchr(addr, ':')) != 0)
+ addr = colon + 1;
+ return (addr);
+}
+
+/* psc_mail_cmd - record MAIL and respond */
+
+static int psc_mail_cmd(PSC_STATE *state, char *args)
+{
+ char *colon;
+ char *addr;
+
+ /*
+ * smtpd(8) incompatibility: we never reject the sender, and we ignore
+ * additional arguments.
+ */
+ if (var_psc_helo_required && state->helo_name == 0)
+ return (PSC_SEND_REPLY(state,
+ "503 5.5.1 Error: send HELO/EHLO first\r\n"));
+ if (state->sender != 0)
+ return (PSC_SEND_REPLY(state,
+ "503 5.5.1 Error: nested MAIL command\r\n"));
+ if (args == 0 || (colon = strchr(args, ':')) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.5.4 Syntax: MAIL FROM:<address>\r\n"));
+ if ((addr = psc_extract_addr(psc_temp, colon + 1)) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.1.7 Bad sender address syntax\r\n"));
+ PSC_STRING_UPDATE(state->sender, addr);
+ return (PSC_SEND_REPLY(state, "250 2.1.0 Ok\r\n"));
+}
+
+/* psc_soften_reply - copy and soft-bounce a reply */
+
+static char *psc_soften_reply(const char *reply)
+{
+ static VSTRING *buf = 0;
+
+ if (buf == 0)
+ buf = vstring_alloc(100);
+ vstring_strcpy(buf, reply);
+ if (reply[0] == '5')
+ STR(buf)[0] = '4';
+ if (reply[4] == '5')
+ STR(buf)[4] = '4';
+ return (STR(buf));
+}
+
+/* psc_rcpt_cmd record RCPT and respond */
+
+static int psc_rcpt_cmd(PSC_STATE *state, char *args)
+{
+ char *colon;
+ char *addr;
+
+ /*
+ * smtpd(8) incompatibility: we reject all recipients, and ignore
+ * additional arguments.
+ */
+ if (state->sender == 0)
+ return (PSC_SEND_REPLY(state,
+ "503 5.5.1 Error: need MAIL command\r\n"));
+ if (args == 0 || (colon = strchr(args, ':')) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.5.4 Syntax: RCPT TO:<address>\r\n"));
+ if ((addr = psc_extract_addr(psc_temp, colon + 1)) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.1.3 Bad recipient address syntax\r\n"));
+ msg_info("NOQUEUE: reject: RCPT from [%s]:%s: %.*s; "
+ "from=<%s>, to=<%s>, proto=%s, helo=<%s>",
+ PSC_CLIENT_ADDR_PORT(state),
+ (int) strlen(state->rcpt_reply) - 2,
+ var_soft_bounce == 0 ? state->rcpt_reply :
+ psc_soften_reply(state->rcpt_reply),
+ info_log_addr_form_sender(state->sender),
+ info_log_addr_form_recipient(addr), state->protocol,
+ state->helo_name ? state->helo_name : "");
+ return (PSC_SEND_REPLY(state, state->rcpt_reply));
+}
+
+/* psc_data_cmd - respond to DATA and disconnect */
+
+static int psc_data_cmd(PSC_STATE *state, char *args)
+{
+ const char myname[] = "psc_data_cmd";
+
+ /*
+ * smtpd(8) incompatibility: postscreen(8) drops the connection, instead
+ * of waiting for the next command. Justification: postscreen(8) should
+ * never see DATA from a legitimate client, because 1) the server rejects
+ * every recipient, and 2) the server does not announce PIPELINING.
+ */
+ msg_info("DATA without valid RCPT from [%s]:%s",
+ PSC_CLIENT_ADDR_PORT(state));
+ if (PSC_SMTPD_NEXT_TOKEN(args) != 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "501 5.5.4 Syntax: DATA\r\n");
+ else if (state->sender == 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "503 5.5.1 Error: need RCPT command\r\n");
+ else
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "554 5.5.1 Error: no valid recipients\r\n");
+ /* Caution: state is now a dangling pointer. */
+ return (0);
+}
+
+/* psc_bdat_cmd - respond to BDAT and disconnect */
+
+static int psc_bdat_cmd(PSC_STATE *state, char *args)
+{
+ const char *myname = "psc_bdat_cmd";
+
+ /*
+ * smtpd(8) incompatibility: postscreen(8) drops the connection, instead
+ * of reading the entire BDAT chunk and staying in sync with the client.
+ * Justification: postscreen(8) should never see BDAT from a legitimate
+ * client, because 1) the server rejects every recipient, and 2) the
+ * server does not announce PIPELINING.
+ */
+ msg_info("BDAT without valid RCPT from [%s]:%s",
+ PSC_CLIENT_ADDR_PORT(state));
+ if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "502 5.5.1 Error: command not implemented\r\n");
+ else if (PSC_SMTPD_NEXT_TOKEN(args) == 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "501 5.5.4 Syntax: BDAT count [LAST]\r\n");
+ else if (state->sender == 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "554 5.5.1 Error: need MAIL command\r\n");
+ else
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "554 5.5.1 Error: no valid recipients\r\n");
+ /* Caution: state is now a dangling pointer. */
+ return (0);
+}
+
+/* psc_rset_cmd - reset, send 250 OK */
+
+static int psc_rset_cmd(PSC_STATE *state, char *unused_args)
+{
+ PSC_STRING_RESET(state->sender);
+ return (PSC_SEND_REPLY(state, "250 2.0.0 Ok\r\n"));
+}
+
+/* psc_noop_cmd - respond to something */
+
+static int psc_noop_cmd(PSC_STATE *state, char *unused_args)
+{
+ return (PSC_SEND_REPLY(state, "250 2.0.0 Ok\r\n"));
+}
+
+/* psc_vrfy_cmd - respond to VRFY */
+
+static int psc_vrfy_cmd(PSC_STATE *state, char *args)
+{
+
+ /*
+ * smtpd(8) incompatibility: we reject all requests, and ignore
+ * additional arguments.
+ */
+ if (PSC_SMTPD_NEXT_TOKEN(args) == 0)
+ return (PSC_SEND_REPLY(state,
+ "501 5.5.4 Syntax: VRFY address\r\n"));
+ if (var_psc_disable_vrfy)
+ return (PSC_SEND_REPLY(state,
+ "502 5.5.1 VRFY command is disabled\r\n"));
+ return (PSC_SEND_REPLY(state, state->rcpt_reply));
+}
+
+/* psc_etrn_cmd - reset, send 250 OK */
+
+static int psc_etrn_cmd(PSC_STATE *state, char *args)
+{
+
+ /*
+ * smtpd(8) incompatibility: we reject all requests, and ignore
+ * additional arguments.
+ */
+ if (var_psc_helo_required && state->helo_name == 0)
+ return (PSC_SEND_REPLY(state,
+ "503 5.5.1 Error: send HELO/EHLO first\r\n"));
+ if (PSC_SMTPD_NEXT_TOKEN(args) == 0)
+ return (PSC_SEND_REPLY(state,
+ "500 Syntax: ETRN domain\r\n"));
+ return (PSC_SEND_REPLY(state, "458 Unable to queue messages\r\n"));
+}
+
+/* psc_quit_cmd - respond to QUIT and disconnect */
+
+static int psc_quit_cmd(PSC_STATE *state, char *unused_args)
+{
+ const char *myname = "psc_quit_cmd";
+
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ "221 2.0.0 Bye\r\n");
+ /* Caution: state is now a dangling pointer. */
+ return (0);
+}
+
+/* psc_smtpd_time_event - handle per-session time limit */
+
+static void psc_smtpd_time_event(int event, void *context)
+{
+ const char *myname = "psc_smtpd_time_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ event, vstream_fileno(state->smtp_client_stream),
+ state->smtp_client_addr, state->smtp_client_port,
+ psc_print_state_flags(state->flags, myname));
+
+ msg_info("COMMAND TIME LIMIT from [%s]:%s after %s",
+ PSC_CLIENT_ADDR_PORT(state), state->where);
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ psc_smtpd_timeout_reply);
+}
+
+ /*
+ * The table of all SMTP commands that we know.
+ */
+typedef struct {
+ const char *name;
+ int (*action) (PSC_STATE *, char *);
+ int flags; /* see below */
+} PSC_SMTPD_COMMAND;
+
+#define PSC_SMTPD_CMD_FLAG_NONE (0) /* no flags (i.e. disabled) */
+#define PSC_SMTPD_CMD_FLAG_ENABLE (1<<0) /* command is enabled */
+#define PSC_SMTPD_CMD_FLAG_DESTROY (1<<1) /* dangling pointer alert */
+#define PSC_SMTPD_CMD_FLAG_PRE_TLS (1<<2) /* allowed with mandatory TLS */
+#define PSC_SMTPD_CMD_FLAG_SUSPEND (1<<3) /* suspend command engine */
+#define PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD (1<<4) /* command has payload */
+
+static const PSC_SMTPD_COMMAND command_table[] = {
+ "HELO", psc_helo_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
+ "EHLO", psc_ehlo_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
+ "STARTTLS", psc_starttls_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS | PSC_SMTPD_CMD_FLAG_SUSPEND,
+ "XCLIENT", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
+ "XFORWARD", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
+ "AUTH", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
+ "MAIL", psc_mail_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "RCPT", psc_rcpt_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "DATA", psc_data_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY,
+ /* ".", psc_dot_cmd, PSC_SMTPD_CMD_FLAG_NONE, */
+ "BDAT", psc_bdat_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY | PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD,
+ "RSET", psc_rset_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "NOOP", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
+ "VRFY", psc_vrfy_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "ETRN", psc_etrn_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "QUIT", psc_quit_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY | PSC_SMTPD_CMD_FLAG_PRE_TLS,
+ 0,
+};
+
+/* psc_smtpd_read_event - pseudo responder */
+
+static void psc_smtpd_read_event(int event, void *context)
+{
+ const char *myname = "psc_smtpd_read_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+ time_t *expire_time = state->client_info->expire_time;
+ int ch;
+ struct cmd_trans {
+ int state;
+ int want;
+ int next_state;
+ };
+ const char *saved_where;
+
+#define PSC_SMTPD_CMD_ST_ANY 0
+#define PSC_SMTPD_CMD_ST_CR 1
+#define PSC_SMTPD_CMD_ST_CR_LF 2
+
+ static const struct cmd_trans cmd_trans[] = {
+ PSC_SMTPD_CMD_ST_ANY, '\r', PSC_SMTPD_CMD_ST_CR,
+ PSC_SMTPD_CMD_ST_CR, '\n', PSC_SMTPD_CMD_ST_CR_LF,
+ 0, 0, 0,
+ };
+ const struct cmd_trans *transp;
+ char *cmd_buffer_ptr;
+ char *command;
+ const PSC_SMTPD_COMMAND *cmdp;
+ int write_stat;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ event, vstream_fileno(state->smtp_client_stream),
+ state->smtp_client_addr, state->smtp_client_port,
+ psc_print_state_flags(state->flags, myname));
+
+ /*
+ * Basic liveness requirements.
+ *
+ * Drain all input in the VSTREAM buffer, otherwise this socket will not
+ * receive further read event notification until the client disconnects!
+ *
+ * To suspend this loop temporarily before the buffer is drained, use the
+ * PSC_SUSPEND_SMTP_CMD_EVENTS() and PSC_RESUME_SMTP_CMD_EVENTS() macros,
+ * and set the PSC_SMTPD_CMD_FLAG_SUSPEND flag in the command table.
+ *
+ * Don't try to read input before it has arrived, otherwise we would starve
+ * the pseudo threads of other sessions. Get out of here as soon as the
+ * VSTREAM read buffer dries up. Do not look for more input in kernel
+ * buffers. That input wasn't likely there when psc_smtpd_read_event()
+ * was called. Also, yielding the pseudo thread will improve fairness for
+ * other pseudo threads.
+ */
+
+ /*
+ * Note: on entry into this function the VSTREAM buffer may or may not be
+ * empty, so we test the "no more input" condition at the bottom of the
+ * loops.
+ */
+ for (;;) {
+
+ /*
+ * Read one command line, possibly one fragment at a time.
+ */
+ for (;;) {
+
+ if ((ch = PSC_SMTPD_NEXT_CHAR(state)) == VSTREAM_EOF) {
+ PSC_CLEAR_EVENT_HANGUP(state, psc_smtpd_time_event);
+ return;
+ }
+
+ /*
+ * Sanity check. We don't want to store infinitely long commands.
+ */
+ if (state->read_state == PSC_SMTPD_CMD_ST_ANY
+ && VSTRING_LEN(state->cmd_buffer) >= var_line_limit) {
+ msg_info("COMMAND LENGTH LIMIT from [%s]:%s after %s",
+ PSC_CLIENT_ADDR_PORT(state), state->where);
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ psc_smtpd_421_reply);
+ return;
+ }
+ VSTRING_ADDCH(state->cmd_buffer, ch);
+
+ /*
+ * Try to match the current character desired by the state
+ * machine. If that fails, try to restart the machine with a
+ * match for its first state. Like smtpd(8), we understand lines
+ * ending in <CR><LF> and bare <LF>. Unlike smtpd(8), we may
+ * treat lines ending in bare <LF> as an offense.
+ */
+ for (transp = cmd_trans; transp->state != state->read_state; transp++)
+ if (transp->want == 0)
+ msg_panic("%s: command_read: unknown state: %d",
+ myname, state->read_state);
+ if (ch == transp->want)
+ state->read_state = transp->next_state;
+ else if (ch == cmd_trans[0].want)
+ state->read_state = cmd_trans[0].next_state;
+ else
+ state->read_state = PSC_SMTPD_CMD_ST_ANY;
+ if (state->read_state == PSC_SMTPD_CMD_ST_CR_LF) {
+ vstring_truncate(state->cmd_buffer,
+ VSTRING_LEN(state->cmd_buffer) - 2);
+ break;
+ }
+
+ /*
+ * Bare newline test.
+ */
+ if (ch == '\n') {
+ if ((state->flags & PSC_STATE_MASK_BARLF_TODO_SKIP)
+ == PSC_STATE_FLAG_BARLF_TODO) {
+ PSC_SMTPD_ESCAPE_TEXT(psc_temp, STR(state->cmd_buffer),
+ VSTRING_LEN(state->cmd_buffer) - 1, 100);
+ msg_info("BARE NEWLINE from [%s]:%s after %s",
+ PSC_CLIENT_ADDR_PORT(state), STR(psc_temp));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_BARLF_FAIL);
+ PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_BARLF_PASS);
+ expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED; /* XXX */
+ /* Skip this test for the remainder of this session. */
+ PSC_SKIP_SESSION_STATE(state, "bare newline test",
+ PSC_STATE_FLAG_BARLF_SKIP);
+ switch (psc_barlf_action) {
+ case PSC_ACT_DROP:
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "521 5.5.1 Protocol error\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ PSC_ENFORCE_SESSION_STATE(state,
+ "550 5.5.1 Protocol error\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state,
+ PSC_STATE_FLAG_BARLF_FAIL);
+ /* Temporarily allowlist until something expires. */
+ PSC_PASS_SESSION_STATE(state, "bare newline test",
+ PSC_STATE_FLAG_BARLF_PASS);
+ expire_time[PSC_TINDX_BARLF] = event_time() + psc_min_ttl;
+ break;
+ default:
+ msg_panic("%s: unknown bare_newline action value %d",
+ myname, psc_barlf_action);
+ }
+ }
+ vstring_truncate(state->cmd_buffer,
+ VSTRING_LEN(state->cmd_buffer) - 1);
+ break;
+ }
+
+ /*
+ * Yield this pseudo thread when the VSTREAM buffer is empty in
+ * the middle of a command.
+ *
+ * XXX Do not reset the read timeout. The entire command must be
+ * received within the time limit.
+ */
+ if (PSC_SMTPD_BUFFER_EMPTY(state))
+ return;
+ }
+
+ /*
+ * Avoid complaints from Postfix maps about malformed content.
+ */
+#define PSC_BAD_UTF8(str, len) \
+ (var_smtputf8_enable && !valid_utf8_string((str), (len)))
+
+ /*
+ * Terminate the command buffer, and apply the last-resort command
+ * editing workaround.
+ */
+ VSTRING_TERMINATE(state->cmd_buffer);
+ if (psc_cmd_filter != 0 && !PSC_BAD_UTF8(STR(state->cmd_buffer),
+ LEN(state->cmd_buffer))) {
+ const char *cp;
+
+ for (cp = STR(state->cmd_buffer); *cp && IS_SPACE_TAB(*cp); cp++)
+ /* void */ ;
+ if ((cp = psc_dict_get(psc_cmd_filter, cp)) != 0) {
+ msg_info("[%s]:%s: replacing command \"%.100s\" with \"%.100s\"",
+ state->smtp_client_addr, state->smtp_client_port,
+ STR(state->cmd_buffer), cp);
+ vstring_strcpy(state->cmd_buffer, cp);
+ } else if (psc_cmd_filter->error != 0) {
+ msg_fatal("%s:%s lookup error for \"%.100s\"",
+ psc_cmd_filter->type, psc_cmd_filter->name,
+ STR(state->cmd_buffer));
+ }
+ }
+
+ /*
+ * Reset the command buffer write pointer and state machine in
+ * preparation for the next command. For this to work as expected,
+ * VSTRING_RESET() must be non-destructive. We just can't ask for the
+ * VSTRING_LEN() and vstring_end() results.
+ */
+ state->read_state = PSC_SMTPD_CMD_ST_ANY;
+ VSTRING_RESET(state->cmd_buffer);
+
+ /*
+ * Process the command line.
+ *
+ * Caution: some command handlers terminate the session and destroy the
+ * session state structure. When this happens we must leave the SMTP
+ * engine to avoid a dangling pointer problem.
+ */
+ cmd_buffer_ptr = STR(state->cmd_buffer);
+ if (msg_verbose)
+ msg_info("< [%s]:%s: %s", state->smtp_client_addr,
+ state->smtp_client_port, cmd_buffer_ptr);
+
+ /* Parse the command name. */
+ if ((command = PSC_SMTPD_NEXT_TOKEN(cmd_buffer_ptr)) == 0)
+ command = "";
+
+ /*
+ * The non-SMTP, PIPELINING and command COUNT tests depend on the
+ * client command handler.
+ *
+ * Caution: cmdp->name and cmdp->action may be null on loop exit.
+ */
+ saved_where = state->where;
+ state->where = PSC_SMTPD_CMD_UNIMPL;
+ for (cmdp = command_table; cmdp->name != 0; cmdp++) {
+ if (strcasecmp(command, cmdp->name) == 0) {
+ state->where = cmdp->name;
+ break;
+ }
+ }
+
+ if ((state->flags & PSC_STATE_FLAG_SMTPD_X21)
+ && cmdp->action != psc_quit_cmd) {
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ state->final_reply);
+ return;
+ }
+ /* Non-SMTP command test. */
+ if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_SKIP)
+ == PSC_STATE_FLAG_NSMTP_TODO && cmdp->name == 0
+ && (is_header(command)
+ || PSC_BAD_UTF8(command, strlen(command))
+ /* Ignore forbid_cmds lookup errors. Non-critical feature. */
+ || (*var_psc_forbid_cmds
+ && string_list_match(psc_forbid_cmds, command)))) {
+ printable(command, '?');
+ PSC_SMTPD_ESCAPE_TEXT(psc_temp, cmd_buffer_ptr,
+ strlen(cmd_buffer_ptr), 100);
+ msg_info("NON-SMTP COMMAND from [%s]:%s after %s: %.100s %s",
+ PSC_CLIENT_ADDR_PORT(state), saved_where,
+ command, STR(psc_temp));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_FAIL);
+ PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_PASS);
+ expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED; /* XXX */
+ /* Skip this test for the remainder of this SMTP session. */
+ PSC_SKIP_SESSION_STATE(state, "non-smtp test",
+ PSC_STATE_FLAG_NSMTP_SKIP);
+ switch (psc_nsmtp_action) {
+ case PSC_ACT_DROP:
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "521 5.7.0 Error: I can break rules, too. Goodbye.\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ PSC_ENFORCE_SESSION_STATE(state,
+ "550 5.5.1 Protocol error\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state,
+ PSC_STATE_FLAG_NSMTP_FAIL);
+ /* Temporarily allowlist until something else expires. */
+ PSC_PASS_SESSION_STATE(state, "non-smtp test",
+ PSC_STATE_FLAG_NSMTP_PASS);
+ expire_time[PSC_TINDX_NSMTP] = event_time() + psc_min_ttl;
+ break;
+ default:
+ msg_panic("%s: unknown non_smtp_command action value %d",
+ myname, psc_nsmtp_action);
+ }
+ }
+ /* Command PIPELINING test. */
+ if ((cmdp->flags & PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD) == 0
+ && (state->flags & PSC_STATE_MASK_PIPEL_TODO_SKIP)
+ == PSC_STATE_FLAG_PIPEL_TODO && !PSC_SMTPD_BUFFER_EMPTY(state)) {
+ printable(command, '?');
+ PSC_SMTPD_ESCAPE_TEXT(psc_temp, PSC_SMTPD_PEEK_DATA(state),
+ PSC_SMTPD_PEEK_LEN(state), 100);
+ msg_info("COMMAND PIPELINING from [%s]:%s after %.100s: %s",
+ PSC_CLIENT_ADDR_PORT(state), command, STR(psc_temp));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_FAIL);
+ PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_PASS);
+ expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED; /* XXX */
+ /* Skip this test for the remainder of this SMTP session. */
+ PSC_SKIP_SESSION_STATE(state, "pipelining test",
+ PSC_STATE_FLAG_PIPEL_SKIP);
+ switch (psc_pipel_action) {
+ case PSC_ACT_DROP:
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "521 5.5.1 Protocol error\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ PSC_ENFORCE_SESSION_STATE(state,
+ "550 5.5.1 Protocol error\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state,
+ PSC_STATE_FLAG_PIPEL_FAIL);
+ /* Temporarily allowlist until something else expires. */
+ PSC_PASS_SESSION_STATE(state, "pipelining test",
+ PSC_STATE_FLAG_PIPEL_PASS);
+ expire_time[PSC_TINDX_PIPEL] = event_time() + psc_min_ttl;
+ break;
+ default:
+ msg_panic("%s: unknown pipelining action value %d",
+ myname, psc_pipel_action);
+ }
+ }
+
+ /*
+ * The following tests don't pass until the client gets all the way
+ * to the RCPT TO command. However, the client can still fail these
+ * tests with some later command.
+ */
+ if (cmdp->action == psc_rcpt_cmd) {
+ if ((state->flags & PSC_STATE_MASK_BARLF_TODO_PASS_FAIL)
+ == PSC_STATE_FLAG_BARLF_TODO) {
+ PSC_PASS_SESSION_STATE(state, "bare newline test",
+ PSC_STATE_FLAG_BARLF_PASS);
+ /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */
+ expire_time[PSC_TINDX_BARLF] = event_time() + var_psc_barlf_ttl;
+ }
+ if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL)
+ == PSC_STATE_FLAG_NSMTP_TODO) {
+ PSC_PASS_SESSION_STATE(state, "non-smtp test",
+ PSC_STATE_FLAG_NSMTP_PASS);
+ /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */
+ expire_time[PSC_TINDX_NSMTP] = event_time() + var_psc_nsmtp_ttl;
+ }
+ if ((state->flags & PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL)
+ == PSC_STATE_FLAG_PIPEL_TODO) {
+ PSC_PASS_SESSION_STATE(state, "pipelining test",
+ PSC_STATE_FLAG_PIPEL_PASS);
+ /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */
+ expire_time[PSC_TINDX_PIPEL] = event_time() + var_psc_pipel_ttl;
+ }
+ }
+ /* Command COUNT limit test. */
+ if (++state->command_count > var_psc_cmd_count
+ && cmdp->action != psc_quit_cmd) {
+ msg_info("COMMAND COUNT LIMIT from [%s]:%s after %s",
+ PSC_CLIENT_ADDR_PORT(state), saved_where);
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
+ psc_smtpd_421_reply);
+ return;
+ }
+ /* Finally, execute the command. */
+ if (cmdp->name == 0 || (cmdp->flags & PSC_SMTPD_CMD_FLAG_ENABLE) == 0) {
+ write_stat = PSC_SEND_REPLY(state,
+ "502 5.5.2 Error: command not recognized\r\n");
+ } else if (var_psc_enforce_tls
+ && (state->flags & PSC_STATE_FLAG_USING_TLS) == 0
+ && (cmdp->flags & PSC_SMTPD_CMD_FLAG_PRE_TLS) == 0) {
+ write_stat = PSC_SEND_REPLY(state,
+ "530 5.7.0 Must issue a STARTTLS command first\r\n");
+ } else {
+ write_stat = cmdp->action(state, cmd_buffer_ptr);
+ if (cmdp->flags & PSC_SMTPD_CMD_FLAG_DESTROY)
+ return;
+ }
+
+ /*
+ * Terminate the session after a write error.
+ */
+ if (write_stat < 0) {
+ PSC_CLEAR_EVENT_HANGUP(state, psc_smtpd_time_event);
+ return;
+ }
+
+ /*
+ * We're suspended, waiting for some external event to happen.
+ * Hopefully, someone will call us back to process the remainder of
+ * the pending input, otherwise we could hang.
+ */
+ if (cmdp->flags & PSC_SMTPD_CMD_FLAG_SUSPEND)
+ return;
+
+ /*
+ * Reset the command read timeout before reading the next command.
+ */
+ event_request_timer(psc_smtpd_time_event, (void *) state,
+ PSC_EFF_CMD_TIME_LIMIT);
+
+ /*
+ * Yield this pseudo thread when the VSTREAM buffer is empty.
+ */
+ if (PSC_SMTPD_BUFFER_EMPTY(state))
+ return;
+ }
+}
+
+/* psc_smtpd_tests - per-session deep protocol test initialization */
+
+void psc_smtpd_tests(PSC_STATE *state)
+{
+ static char *myname = "psc_smtpd_tests";
+
+ /*
+ * Report errors and progress in the context of this test.
+ */
+ PSC_BEGIN_TESTS(state, "tests after SMTP handshake");
+
+ /*
+ * Initialize per-session state that is used only by the dummy engine:
+ * the command read buffer and the command read state machine.
+ */
+ state->cmd_buffer = vstring_alloc(100);
+ state->read_state = PSC_SMTPD_CMD_ST_ANY;
+
+ /*
+ * Disable all after-220 tests when we need to reply with 421 and hang up
+ * after reading the next SMTP client command.
+ *
+ * Opportunistically make postscreen more useful, by turning on all
+ * after-220 tests when a bad client failed a before-220 test.
+ *
+ * Otherwise, only apply the explicitly-configured after-220 tests.
+ */
+ if (state->flags & PSC_STATE_FLAG_SMTPD_X21) {
+ state->flags &= ~PSC_STATE_MASK_SMTPD_TODO;
+ } else if (state->flags & PSC_STATE_MASK_ANY_FAIL) {
+ state->flags |= PSC_STATE_MASK_SMTPD_TODO;
+ }
+
+ /*
+ * Send no SMTP banner to pregreeting clients. This eliminates a lot of
+ * "NON-SMTP COMMAND" events, and improves sender/recipient logging.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL) == 0
+ && PSC_SEND_REPLY(state, psc_smtpd_greeting) != 0) {
+ psc_hangup_event(state);
+ return;
+ }
+
+ /*
+ * Wait for the client to respond.
+ */
+ PSC_READ_EVENT_REQUEST2(vstream_fileno(state->smtp_client_stream),
+ psc_smtpd_read_event, psc_smtpd_time_event,
+ (void *) state, PSC_EFF_CMD_TIME_LIMIT);
+}
+
+/* psc_smtpd_init - per-process deep protocol test initialization */
+
+void psc_smtpd_init(void)
+{
+
+ /*
+ * Initialize the server banner.
+ */
+ vstring_sprintf(psc_temp, "220 %s\r\n", var_smtpd_banner);
+ psc_smtpd_greeting = mystrdup(STR(psc_temp));
+
+ /*
+ * Initialize the HELO reply.
+ */
+ vstring_sprintf(psc_temp, "250 %s\r\n", var_myhostname);
+ psc_smtpd_helo_reply = mystrdup(STR(psc_temp));
+
+ /*
+ * STARTTLS support. Note the complete absence of #ifdef USE_TLS
+ * throughout the postscreen(8) source code. If Postfix is built without
+ * TLS support, then the TLS proxy will simply report that TLS is not
+ * available, and conventional error handling will take care of the
+ * issue.
+ *
+ * Legacy code copied from smtpd(8). The pre-fabricated EHLO reply depends
+ * on this.
+ */
+ if (*var_psc_tls_level) {
+ switch (tls_level_lookup(var_psc_tls_level)) {
+ default:
+ msg_fatal("Invalid TLS level \"%s\"", var_psc_tls_level);
+ /* NOTREACHED */
+ break;
+ case TLS_LEV_SECURE:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_FPRINT:
+ msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"",
+ VAR_PSC_TLS_LEVEL, var_psc_tls_level);
+ /* FALLTHROUGH */
+ case TLS_LEV_ENCRYPT:
+ var_psc_enforce_tls = var_psc_use_tls = 1;
+ break;
+ case TLS_LEV_MAY:
+ var_psc_enforce_tls = 0;
+ var_psc_use_tls = 1;
+ break;
+ case TLS_LEV_NONE:
+ var_psc_enforce_tls = var_psc_use_tls = 0;
+ break;
+ }
+ }
+ var_psc_use_tls = var_psc_use_tls || var_psc_enforce_tls;
+#ifdef TODO_SASL_AUTH
+ var_psc_tls_auth_only = var_psc_tls_auth_only || var_psc_enforce_tls;
+#endif
+
+ /*
+ * Initialize the EHLO reply. Once for plaintext sessions, and once for
+ * TLS sessions.
+ */
+ psc_smtpd_format_ehlo_reply(psc_temp, psc_ehlo_discard_mask);
+ psc_smtpd_ehlo_reply_plain = mystrdup(STR(psc_temp));
+
+ psc_smtpd_format_ehlo_reply(psc_temp,
+ psc_ehlo_discard_mask | EHLO_MASK_STARTTLS);
+ psc_smtpd_ehlo_reply_tls = mystrdup(STR(psc_temp));
+
+ /*
+ * Initialize the 421 timeout reply.
+ */
+ vstring_sprintf(psc_temp, "421 4.4.2 %s Error: timeout exceeded\r\n",
+ var_myhostname);
+ psc_smtpd_timeout_reply = mystrdup(STR(psc_temp));
+
+ /*
+ * Initialize the generic 421 reply.
+ */
+ vstring_sprintf(psc_temp, "421 %s Service unavailable - try again later\r\n",
+ var_myhostname);
+ psc_smtpd_421_reply = mystrdup(STR(psc_temp));
+
+ /*
+ * Initialize the reply footer.
+ */
+ if (*var_psc_rej_footer || *var_psc_rej_ftr_maps)
+ psc_expand_init();
+}
+
+/* psc_smtpd_pre_jail_init - per-process deep protocol test initialization */
+
+void psc_smtpd_pre_jail_init(void)
+{
+
+ /*
+ * Determine what server ESMTP features to suppress, typically to avoid
+ * inter-operability problems. We do the default filter here, and
+ * determine client-dependent filtering on the fly.
+ *
+ * XXX Bugger. This means we have to restart when the table changes!
+ */
+ if (*var_psc_ehlo_dis_maps)
+ psc_ehlo_discard_maps = maps_create(VAR_PSC_EHLO_DIS_MAPS,
+ var_psc_ehlo_dis_maps,
+ DICT_FLAG_LOCK);
+ psc_ehlo_discard_mask = ehlo_mask(var_psc_ehlo_dis_words);
+
+ /*
+ * Last-resort command editing support.
+ */
+ if (*var_psc_cmd_filter)
+ psc_cmd_filter = dict_open(var_psc_cmd_filter, O_RDONLY,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+
+ /*
+ * SMTP server reply footer.
+ */
+ if (*var_psc_rej_ftr_maps)
+ pcs_send_pre_jail_init();
+}
diff --git a/src/postscreen/postscreen_starttls.c b/src/postscreen/postscreen_starttls.c
new file mode 100644
index 0000000..4036a3d
--- /dev/null
+++ b/src/postscreen/postscreen_starttls.c
@@ -0,0 +1,317 @@
+/*++
+/* NAME
+/* postscreen_starttls 3
+/* SUMMARY
+/* postscreen TLS proxy support
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* int psc_starttls_open(state, resume_event)
+/* PSC_STATE *state;
+/* void (*resume_event)(int unused_event, char *context);
+/* DESCRIPTION
+/* This module inserts the tlsproxy(8) proxy between the
+/* postscreen(8) server and the remote SMTP client. The entire
+/* process happens in the background, including notification
+/* of completion to the remote SMTP client and to the calling
+/* application.
+/*
+/* Before calling psc_starttls_open() the caller must turn off
+/* all pending timer and I/O event requests on the SMTP client
+/* stream.
+/*
+/* psc_starttls_open() starts the first transaction in the
+/* tlsproxy(8) hand-off protocol, and sets up event handlers
+/* for the successive protocol stages.
+/*
+/* Upon completion, the event handlers call resume_event()
+/* which must reset the SMTP helo/sender/etc. state when the
+/* PSC_STATE_FLAG_USING_TLS is set, and set up timer and read
+/* event requests to receive the next SMTP command.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <stringops.h> /* concatenate() */
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* TLS library. */
+
+#include <tls_proxy.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * For now, this code is built into the postscreen(8) daemon. In the future
+ * it may be abstracted into a reusable library module for use by other
+ * event-driven programs (perhaps smtp-source and smtp-sink).
+ */
+
+ /*
+ * Transient state for the postscreen(8)-to-tlsproxy(8) hand-off protocol.
+ */
+typedef struct {
+ VSTREAM *tlsproxy_stream; /* hand-off negotiation */
+ EVENT_NOTIFY_FN resume_event; /* call-back handler */
+ PSC_STATE *smtp_state; /* SMTP session state */
+} PSC_STARTTLS;
+
+#define TLSPROXY_INIT_TIMEOUT 10
+
+static char *psc_tlsp_service = 0;
+
+/* Resume the dummy SMTP engine after an event handling error */
+
+#define PSC_STARTTLS_EVENT_ERR_RESUME_RETURN() do { \
+ event_disable_readwrite(vstream_fileno(tlsproxy_stream)); \
+ PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state); \
+ } while (0);
+
+/* Resume the dummy SMTP engine, possibly after swapping streams */
+
+#define PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state) do { \
+ vstream_fclose(tlsproxy_stream); \
+ starttls_state->resume_event(event, (void *) smtp_state); \
+ myfree((void *) starttls_state); \
+ return; \
+ } while (0)
+
+/* psc_starttls_finish - complete negotiation with TLS proxy */
+
+static void psc_starttls_finish(int event, void *context)
+{
+ const char *myname = "psc_starttls_finish";
+ PSC_STARTTLS *starttls_state = (PSC_STARTTLS *) context;
+ PSC_STATE *smtp_state = starttls_state->smtp_state;
+ VSTREAM *tlsproxy_stream = starttls_state->tlsproxy_stream;
+ int status;
+
+ if (msg_verbose)
+ msg_info("%s: send client handle on proxy socket %d"
+ " for smtp socket %d from [%s]:%s flags=%s",
+ myname, vstream_fileno(tlsproxy_stream),
+ vstream_fileno(smtp_state->smtp_client_stream),
+ smtp_state->smtp_client_addr, smtp_state->smtp_client_port,
+ psc_print_state_flags(smtp_state->flags, myname));
+
+ /*
+ * We leave read-event notification enabled on the postscreen to TLS
+ * proxy stream, to avoid two kqueue/epoll/etc. system calls: one here,
+ * and one when resuming the dummy SMTP engine.
+ */
+ if (event != EVENT_TIME)
+ event_cancel_timer(psc_starttls_finish, (void *) starttls_state);
+
+ /*
+ * Receive the "TLS is available" indication.
+ *
+ * This may seem out of order, but we must have a read transaction between
+ * sending the request attributes and sending the SMTP client file
+ * descriptor. We can't assume UNIX-domain socket semantics here.
+ */
+ if (event != EVENT_READ
+ || attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1 || status == 0) {
+
+ /*
+ * The TLS proxy reports that the TLS engine is not available (due to
+ * configuration error, or other causes).
+ */
+ msg_warn("%s receiving status from %s service",
+ event == EVENT_TIME ? "timeout" : "problem", psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ PSC_STARTTLS_EVENT_ERR_RESUME_RETURN();
+ }
+
+ /*
+ * Send the remote SMTP client file descriptor.
+ */
+ else if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream),
+ vstream_fileno(smtp_state->smtp_client_stream)) < 0) {
+
+ /*
+ * Some error: drop the TLS proxy stream.
+ */
+ msg_warn("problem sending file handle to %s service", psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ PSC_STARTTLS_EVENT_ERR_RESUME_RETURN();
+ }
+
+ /*
+ * After we send the plaintext 220 greeting, the client-side TLS engine
+ * is supposed to talk first, then the server-side TLS engine. However,
+ * postscreen(8) will not participate in that conversation.
+ */
+ else {
+ PSC_SEND_REPLY(smtp_state, "220 2.0.0 Ready to start TLS\r\n");
+
+ /*
+ * Swap the SMTP client stream and the TLS proxy stream, and close
+ * the direct connection to the SMTP client. The TLS proxy will talk
+ * directly to the SMTP client, and once the TLS handshake is
+ * completed, the TLS proxy will talk plaintext to postscreen(8).
+ *
+ * Swap the file descriptors from under the VSTREAM so that we don't
+ * have to worry about loss of user-configurable VSTREAM attributes.
+ */
+ vstream_fpurge(smtp_state->smtp_client_stream, VSTREAM_PURGE_BOTH);
+ vstream_control(smtp_state->smtp_client_stream,
+ CA_VSTREAM_CTL_SWAP_FD(tlsproxy_stream),
+ CA_VSTREAM_CTL_END);
+ smtp_state->flags |= PSC_STATE_FLAG_USING_TLS;
+ PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state);
+ }
+}
+
+/* psc_starttls_first - start negotiation with TLS proxy */
+
+static void psc_starttls_first(int event, void *context)
+{
+ const char *myname = "psc_starttls_first";
+ PSC_STARTTLS *starttls_state = (PSC_STARTTLS *) context;
+ PSC_STATE *smtp_state = starttls_state->smtp_state;
+ VSTREAM *tlsproxy_stream = starttls_state->tlsproxy_stream;
+ static VSTRING *remote_endpt = 0;
+
+ if (msg_verbose)
+ msg_info("%s: receive server protocol on proxy socket %d"
+ " for smtp socket %d from [%s]:%s flags=%s",
+ myname, vstream_fileno(tlsproxy_stream),
+ vstream_fileno(smtp_state->smtp_client_stream),
+ smtp_state->smtp_client_addr, smtp_state->smtp_client_port,
+ psc_print_state_flags(smtp_state->flags, myname));
+
+ /*
+ * We leave read-event notification enabled on the postscreen to TLS
+ * proxy stream, to avoid two kqueue/epoll/etc. system calls: one here,
+ * and one when resuming the dummy SMTP engine.
+ */
+ if (event != EVENT_TIME)
+ event_cancel_timer(psc_starttls_first, (void *) starttls_state);
+
+ /*
+ * Receive and verify the server protocol.
+ */
+ if (event != EVENT_READ
+ || attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY),
+ ATTR_TYPE_END) != 0) {
+ msg_warn("%s receiving %s attribute from %s service: %m",
+ event == EVENT_TIME ? "timeout" : "problem",
+ MAIL_ATTR_PROTO, psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ PSC_STARTTLS_EVENT_ERR_RESUME_RETURN();
+ }
+
+ /*
+ * Send the data attributes now, and send the client file descriptor in a
+ * later transaction. We report all errors asynchronously, to avoid
+ * having to maintain multiple error delivery paths.
+ *
+ * XXX The formatted endpoint should be a state member. Then, we can
+ * simplify all the format strings throughout the program.
+ */
+ if (remote_endpt == 0)
+ remote_endpt = vstring_alloc(20);
+ vstring_sprintf(remote_endpt, "[%s]:%s", smtp_state->smtp_client_addr,
+ smtp_state->smtp_client_port);
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, STR(remote_endpt)),
+ SEND_ATTR_INT(TLS_ATTR_FLAGS, TLS_PROXY_FLAG_ROLE_SERVER),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, psc_normal_cmd_time_limit),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, psc_normal_cmd_time_limit),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID, MAIL_SERVICE_SMTPD), /* XXX */
+ ATTR_TYPE_END);
+ if (vstream_fflush(tlsproxy_stream) != 0) {
+ msg_warn("error sending request to %s service: %m", psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ PSC_STARTTLS_EVENT_ERR_RESUME_RETURN();
+ }
+
+ /*
+ * Set up a read event for the next phase of the TLS proxy handshake.
+ */
+ PSC_READ_EVENT_REQUEST(vstream_fileno(tlsproxy_stream), psc_starttls_finish,
+ (void *) starttls_state, TLSPROXY_INIT_TIMEOUT);
+}
+
+/* psc_starttls_open - open negotiations with TLS proxy */
+
+void psc_starttls_open(PSC_STATE *smtp_state, EVENT_NOTIFY_FN resume_event)
+{
+ const char *myname = "psc_starttls_open";
+ PSC_STARTTLS *starttls_state;
+ VSTREAM *tlsproxy_stream;
+ int fd;
+
+ if (psc_tlsp_service == 0) {
+ psc_tlsp_service = concatenate(MAIL_CLASS_PRIVATE "/",
+ var_tlsproxy_service, (char *) 0);
+ }
+
+ /*
+ * Connect to the tlsproxy(8) daemon. We report all errors
+ * asynchronously, to avoid having to maintain multiple delivery paths.
+ */
+ if ((fd = LOCAL_CONNECT(psc_tlsp_service, NON_BLOCKING, 1)) < 0) {
+ msg_warn("connect to %s service: %m", psc_tlsp_service);
+ PSC_SEND_REPLY(smtp_state,
+ "454 4.7.0 TLS not available due to local problem\r\n");
+ event_request_timer(resume_event, (void *) smtp_state, 0);
+ return;
+ }
+ if (msg_verbose)
+ msg_info("%s: connecting to proxy socket %d"
+ " for smtp socket %d from [%s]:%s flags=%s",
+ myname, fd, vstream_fileno(smtp_state->smtp_client_stream),
+ smtp_state->smtp_client_addr, smtp_state->smtp_client_port,
+ psc_print_state_flags(smtp_state->flags, myname));
+
+ tlsproxy_stream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(tlsproxy_stream,
+ VSTREAM_CTL_PATH, psc_tlsp_service,
+ VSTREAM_CTL_END);
+
+ /*
+ * Set up a read event for the next phase of the TLS proxy handshake.
+ */
+ starttls_state = (PSC_STARTTLS *) mymalloc(sizeof(*starttls_state));
+ starttls_state->tlsproxy_stream = tlsproxy_stream;
+ starttls_state->resume_event = resume_event;
+ starttls_state->smtp_state = smtp_state;
+ PSC_READ_EVENT_REQUEST(vstream_fileno(tlsproxy_stream), psc_starttls_first,
+ (void *) starttls_state, TLSPROXY_INIT_TIMEOUT);
+}
diff --git a/src/postscreen/postscreen_state.c b/src/postscreen/postscreen_state.c
new file mode 100644
index 0000000..2b5db3c
--- /dev/null
+++ b/src/postscreen/postscreen_state.c
@@ -0,0 +1,317 @@
+/*++
+/* NAME
+/* postscreen_state 3
+/* SUMMARY
+/* postscreen session state and queue length management
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* PSC_STATE *psc_new_session_state(stream, client_addr, client_port,
+/* server_addr, server_port)
+/* VSTREAM *stream;
+/* const char *client_addr;
+/* const char *client_port;
+/* const char *server_addr;
+/* const char *server_port;
+/*
+/* void psc_free_session_state(state)
+/* PSC_STATE *state;
+/*
+/* char *psc_print_state_flags(flags, context)
+/* int flags;
+/* const char *context;
+/*
+/* void PSC_ADD_SERVER_STATE(state, server_fd)
+/* PSC_STATE *state;
+/* int server_fd;
+/*
+/* void PSC_DEL_SERVER_STATE(state)
+/* PSC_STATE *state;
+/*
+/* void PSC_DEL_CLIENT_STATE(state)
+/* PSC_STATE *state;
+/*
+/* void PSC_DROP_SESSION_STATE(state, final_reply)
+/* PSC_STATE *state;
+/* const char *final_reply;
+/*
+/* void PSC_ENFORCE_SESSION_STATE(state, rcpt_reply)
+/* PSC_STATE *state;
+/* const char *rcpt_reply;
+/*
+/* void PSC_PASS_SESSION_STATE(state, testname, pass_flag)
+/* PSC_STATE *state;
+/* const char *testname;
+/* int pass_flag;
+/*
+/* void PSC_FAIL_SESSION_STATE(state, fail_flag)
+/* PSC_STATE *state;
+/* int fail_flag;
+/*
+/* void PSC_UNFAIL_SESSION_STATE(state, fail_flag)
+/* PSC_STATE *state;
+/* int fail_flag;
+/* DESCRIPTION
+/* This module maintains per-client session state, and two
+/* global file descriptor counters:
+/* .IP psc_check_queue_length
+/* The total number of remote SMTP client sockets.
+/* .IP psc_post_queue_length
+/* The total number of server file descriptors that are currently
+/* in use for client file descriptor passing. This number
+/* equals the number of client file descriptors in transit.
+/* .PP
+/* psc_new_session_state() creates a new session state object
+/* for the specified client stream, and increments the
+/* psc_check_queue_length counter. The flags and per-test time
+/* stamps are initialized with PSC_INIT_TESTS(), or for concurrent
+/* sessions, with PSC_INIT_TEST_FLAGS_ONLY(). The addr and
+/* port arguments are null-terminated strings with the remote
+/* SMTP client endpoint. The _reply members are set to
+/* polite "try again" SMTP replies. The protocol member is set
+/* to "SMTP".
+/*
+/* The psc_stress variable is set to non-zero when
+/* psc_check_queue_length passes over a high-water mark.
+/*
+/* psc_free_session_state() destroys the specified session state
+/* object, closes the applicable I/O channels, and decrements
+/* the applicable file descriptor counters: psc_check_queue_length
+/* and psc_post_queue_length.
+/*
+/* The psc_stress variable is reset to zero when psc_check_queue_length
+/* passes under a low-water mark.
+/*
+/* psc_print_state_flags() converts per-session flags into
+/* human-readable form. The context is for error reporting.
+/* The result is overwritten upon each call.
+/*
+/* PSC_ADD_SERVER_STATE() updates the specified session state
+/* object with the specified server file descriptor, and
+/* increments the global psc_post_queue_length file descriptor
+/* counter.
+/*
+/* PSC_DEL_SERVER_STATE() closes the specified session state
+/* object's server file descriptor, and decrements the global
+/* psc_post_queue_length file descriptor counter.
+/*
+/* PSC_DEL_CLIENT_STATE() updates the specified session state
+/* object, closes the client stream, and decrements the global
+/* psc_check_queue_length file descriptor counter.
+/*
+/* PSC_DROP_SESSION_STATE() updates the specified session state
+/* object and closes the client stream after sending the
+/* specified SMTP reply.
+/*
+/* PSC_ENFORCE_SESSION_STATE() updates the specified session
+/* state object. It arranges that the built-in SMTP engine
+/* logs sender/recipient information and rejects all RCPT TO
+/* commands with the specified SMTP reply.
+/*
+/* PSC_PASS_SESSION_STATE() sets the specified "pass" flag.
+/* The testname is used for debug logging.
+/*
+/* PSC_FAIL_SESSION_STATE() sets the specified "fail" flag.
+/*
+/* PSC_UNFAIL_SESSION_STATE() unsets the specified "fail" flag.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <name_mask.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+
+/* Master server protocols. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+/* psc_new_session_state - fill in connection state for event processing */
+
+PSC_STATE *psc_new_session_state(VSTREAM *stream,
+ const char *client_addr,
+ const char *client_port,
+ const char *server_addr,
+ const char *server_port)
+{
+ PSC_STATE *state;
+
+ state = (PSC_STATE *) mymalloc(sizeof(*state));
+ if ((state->smtp_client_stream = stream) != 0)
+ psc_check_queue_length++;
+ state->smtp_server_fd = (-1);
+ state->smtp_client_addr = mystrdup(client_addr);
+ state->smtp_client_port = mystrdup(client_port);
+ state->smtp_server_addr = mystrdup(server_addr);
+ state->smtp_server_port = mystrdup(server_port);
+ state->send_buf = vstring_alloc(100);
+ state->test_name = "TEST NAME HERE";
+ state->dnsbl_reply = 0;
+ state->final_reply = "421 4.3.2 Service currently unavailable\r\n";
+ state->rcpt_reply = "450 4.3.2 Service currently unavailable\r\n";
+ state->command_count = 0;
+ state->protocol = MAIL_PROTO_SMTP;
+ state->helo_name = 0;
+ state->sender = 0;
+ state->cmd_buffer = 0;
+ state->read_state = 0;
+ state->ehlo_discard_mask = 0; /* XXX Should be ~0 */
+ state->expand_buf = 0;
+ state->where = PSC_SMTPD_CMD_CONNECT;
+
+ /*
+ * Update the stress level.
+ */
+ if (psc_stress == 0
+ && psc_check_queue_length >= psc_hiwat_check_queue_length) {
+ psc_stress = 1;
+ msg_info("entering STRESS mode with %d connections",
+ psc_check_queue_length);
+ }
+
+ /*
+ * Update the per-client session count.
+ */
+ if ((state->client_info = (PSC_CLIENT_INFO *)
+ htable_find(psc_client_concurrency, client_addr)) == 0) {
+ state->client_info = (PSC_CLIENT_INFO *)
+ mymalloc(sizeof(state->client_info[0]));
+ (void) htable_enter(psc_client_concurrency, client_addr,
+ (void *) state->client_info);
+ PSC_INIT_TESTS(state);
+ state->client_info->concurrency = 1;
+ state->client_info->pass_new_count = 0;
+ } else {
+ PSC_INIT_TEST_FLAGS_ONLY(state);
+ state->client_info->concurrency += 1;
+ }
+
+ return (state);
+}
+
+/* psc_free_session_state - destroy connection state including connections */
+
+void psc_free_session_state(PSC_STATE *state)
+{
+ const char *myname = "psc_free_session_state";
+ HTABLE_INFO *ht;
+
+ /*
+ * Update the per-client session count.
+ */
+ if ((ht = htable_locate(psc_client_concurrency,
+ state->smtp_client_addr)) == 0)
+ msg_panic("%s: unknown client address: %s",
+ myname, state->smtp_client_addr);
+ if (--(state->client_info->concurrency) == 0)
+ htable_delete(psc_client_concurrency, state->smtp_client_addr, myfree);
+
+ if (state->smtp_client_stream != 0) {
+ PSC_DEL_CLIENT_STATE(state);
+ }
+ if (state->smtp_server_fd >= 0) {
+ PSC_DEL_SERVER_STATE(state);
+ }
+ if (state->send_buf != 0)
+ state->send_buf = vstring_free(state->send_buf);
+ myfree(state->smtp_client_addr);
+ myfree(state->smtp_client_port);
+ myfree(state->smtp_server_addr);
+ myfree(state->smtp_server_port);
+ if (state->dnsbl_reply)
+ vstring_free(state->dnsbl_reply);
+ if (state->helo_name)
+ myfree(state->helo_name);
+ if (state->sender)
+ myfree(state->sender);
+ if (state->cmd_buffer)
+ vstring_free(state->cmd_buffer);
+ if (state->expand_buf)
+ vstring_free(state->expand_buf);
+ myfree((void *) state);
+
+ if (psc_check_queue_length < 0 || psc_post_queue_length < 0)
+ msg_panic("bad queue length: check_queue=%d, post_queue=%d",
+ psc_check_queue_length, psc_post_queue_length);
+
+ /*
+ * Update the stress level.
+ */
+ if (psc_stress != 0
+ && psc_check_queue_length <= psc_lowat_check_queue_length) {
+ psc_stress = 0;
+ msg_info("leaving STRESS mode with %d connections",
+ psc_check_queue_length);
+ }
+}
+
+/* psc_print_state_flags - format state flags */
+
+const char *psc_print_state_flags(int flags, const char *context)
+{
+ static const NAME_MASK flags_mask[] = {
+ "NOFORWARD", PSC_STATE_FLAG_NOFORWARD,
+ "USING_TLS", PSC_STATE_FLAG_USING_TLS,
+ "NEW", PSC_STATE_FLAG_NEW,
+ "DNLIST_FAIL", PSC_STATE_FLAG_DNLIST_FAIL,
+ "HANGUP", PSC_STATE_FLAG_HANGUP,
+ /* unused */
+ "ALLIST_FAIL", PSC_STATE_FLAG_ALLIST_FAIL,
+
+ "PREGR_FAIL", PSC_STATE_FLAG_PREGR_FAIL,
+ "PREGR_PASS", PSC_STATE_FLAG_PREGR_PASS,
+ "PREGR_TODO", PSC_STATE_FLAG_PREGR_TODO,
+ "PREGR_DONE", PSC_STATE_FLAG_PREGR_DONE,
+
+ "DNSBL_FAIL", PSC_STATE_FLAG_DNSBL_FAIL,
+ "DNSBL_PASS", PSC_STATE_FLAG_DNSBL_PASS,
+ "DNSBL_TODO", PSC_STATE_FLAG_DNSBL_TODO,
+ "DNSBL_DONE", PSC_STATE_FLAG_DNSBL_DONE,
+
+ "PIPEL_FAIL", PSC_STATE_FLAG_PIPEL_FAIL,
+ "PIPEL_PASS", PSC_STATE_FLAG_PIPEL_PASS,
+ "PIPEL_TODO", PSC_STATE_FLAG_PIPEL_TODO,
+ "PIPEL_SKIP", PSC_STATE_FLAG_PIPEL_SKIP,
+
+ "NSMTP_FAIL", PSC_STATE_FLAG_NSMTP_FAIL,
+ "NSMTP_PASS", PSC_STATE_FLAG_NSMTP_PASS,
+ "NSMTP_TODO", PSC_STATE_FLAG_NSMTP_TODO,
+ "NSMTP_SKIP", PSC_STATE_FLAG_NSMTP_SKIP,
+
+ "BARLF_FAIL", PSC_STATE_FLAG_BARLF_FAIL,
+ "BARLF_PASS", PSC_STATE_FLAG_BARLF_PASS,
+ "BARLF_TODO", PSC_STATE_FLAG_BARLF_TODO,
+ "BARLF_SKIP", PSC_STATE_FLAG_BARLF_SKIP,
+ 0,
+ };
+
+ return (str_name_mask_opt((VSTRING *) 0, context, flags_mask, flags,
+ NAME_MASK_PIPE | NAME_MASK_NUMBER));
+}
diff --git a/src/postscreen/postscreen_tests.c b/src/postscreen/postscreen_tests.c
new file mode 100644
index 0000000..5e18622
--- /dev/null
+++ b/src/postscreen/postscreen_tests.c
@@ -0,0 +1,341 @@
+/*++
+/* NAME
+/* postscreen_tests 3
+/* SUMMARY
+/* postscreen tests timestamp/flag bulk support
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void PSC_INIT_TESTS(state)
+/* PSC_STATE *state;
+/*
+/* void psc_new_tests(state)
+/* PSC_STATE *state;
+/*
+/* void psc_parse_tests(state, stamp_text, time_value)
+/* PSC_STATE *state;
+/* const char *stamp_text;
+/* time_t time_value;
+/*
+/* void psc_todo_tests(state, time_value)
+/* PSC_STATE *state;
+/* const char *stamp_text;
+/* time_t time_value;
+/*
+/* char *psc_print_tests(buffer, state)
+/* VSTRING *buffer;
+/* PSC_STATE *state;
+/*
+/* char *psc_print_grey_key(buffer, client, helo, sender, rcpt)
+/* VSTRING *buffer;
+/* const char *client;
+/* const char *helo;
+/* const char *sender;
+/* const char *rcpt;
+/*
+/* const char *psc_test_name(tindx)
+/* int tindx;
+/* DESCRIPTION
+/* The functions in this module overwrite the per-test expiration
+/* time stamps and all flags bits. Some functions are implemented
+/* as unsafe macros, meaning they evaluate one or more arguments
+/* multiple times.
+/*
+/* PSC_INIT_TESTS() is an unsafe macro that sets the per-test
+/* expiration time stamps to PSC_TIME_STAMP_INVALID, and that
+/* zeroes all the flags bits. These values are not meant to
+/* be stored into the postscreen(8) cache.
+/*
+/* PSC_INIT_TEST_FLAGS_ONLY() zeroes all the flag bits. It
+/* should be used when the time stamps are already initialized.
+/*
+/* psc_new_tests() sets all test expiration time stamps to
+/* PSC_TIME_STAMP_NEW, and invokes psc_todo_tests().
+/*
+/* psc_parse_tests() parses a cache file record and invokes
+/* psc_todo_tests().
+/*
+/* psc_todo_tests() overwrites all per-session flag bits, and
+/* populates the flags based on test expiration time stamp
+/* information. Tests are considered "expired" when they
+/* would be expired at the specified time value. Only enabled
+/* tests are flagged as "expired"; the object is flagged as
+/* "new" if some enabled tests have "new" time stamps.
+/*
+/* psc_print_tests() creates a cache file record for the
+/* specified flags and per-test expiration time stamps.
+/* This may modify the time stamps for disabled tests.
+/*
+/* psc_print_grey_key() prints a greylist lookup key.
+/*
+/* psc_test_name() returns the name for the specified text
+/* index.
+/* 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 <stdio.h> /* sscanf */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <name_code.h>
+#include <sane_strtol.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Kludge to detect if some test is enabled.
+ */
+#define PSC_PREGR_TEST_ENABLE() (*var_psc_pregr_banner != 0)
+#define PSC_DNSBL_TEST_ENABLE() (*var_psc_dnsbl_sites != 0)
+
+ /*
+ * Format of a persistent cache entry (which is almost but not quite the
+ * same as the in-memory representation).
+ *
+ * Each cache entry has one time stamp for each test.
+ *
+ * - A time stamp of PSC_TIME_STAMP_INVALID must never appear in the cache. It
+ * is reserved for in-memory objects that are still being initialized.
+ *
+ * - A time stamp of PSC_TIME_STAMP_NEW indicates that the test never passed.
+ * Postscreen will log the client with "pass new" when it passes the final
+ * test.
+ *
+ * - A time stamp of PSC_TIME_STAMP_DISABLED indicates that the test never
+ * passed, and that the test was disabled when the cache entry was written.
+ *
+ * - Otherwise, the test was passed, and the time stamp indicates when that
+ * test result expires.
+ *
+ * A cache entry is expired when the time stamps of all passed tests are
+ * expired.
+ */
+
+/* psc_new_tests - initialize new test results from scratch */
+
+void psc_new_tests(PSC_STATE *state)
+{
+ time_t *expire_time = state->client_info->expire_time;
+
+ /*
+ * Give all tests a PSC_TIME_STAMP_NEW time stamp, so that we can later
+ * recognize cache entries that haven't passed all enabled tests. When we
+ * write a cache entry to the database, any new-but-disabled tests will
+ * get a PSC_TIME_STAMP_DISABLED time stamp.
+ */
+ expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_NEW;
+
+ /*
+ * Determine what tests need to be completed.
+ */
+ psc_todo_tests(state, PSC_TIME_STAMP_NEW + 1);
+}
+
+/* psc_parse_tests - parse test results from cache */
+
+void psc_parse_tests(PSC_STATE *state,
+ const char *stamp_str,
+ time_t time_value)
+{
+ const char *start = stamp_str;
+ char *cp;
+ time_t *time_stamps = state->client_info->expire_time;
+ time_t *sp;
+
+ /*
+ * Parse the cache entry, and allow for older postscreen versions that
+ * implemented fewer tests. We pretend that the newer tests were disabled
+ * at the time that the cache entry was written.
+ */
+ for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
+ *sp = sane_strtoul(start, &cp, 10);
+ if (*start == 0 || (*cp != '\0' && *cp != ';') || errno == ERANGE)
+ *sp = PSC_TIME_STAMP_DISABLED;
+ if (msg_verbose)
+ msg_info("%s -> %lu", start, (unsigned long) *sp);
+ if (*cp == ';')
+ start = cp + 1;
+ else
+ start = cp;
+ }
+
+ /*
+ * Determine what tests need to be completed.
+ */
+ psc_todo_tests(state, time_value);
+}
+
+/* psc_todo_tests - determine what tests to perform */
+
+void psc_todo_tests(PSC_STATE *state, time_t time_value)
+{
+ time_t *expire_time = state->client_info->expire_time;
+ time_t *sp;
+
+ /*
+ * Reset all per-session flags.
+ */
+ state->flags = 0;
+
+ /*
+ * Flag the tests as "new" when the cache entry has fields for all
+ * enabled tests, but the remote SMTP client has not yet passed all those
+ * tests.
+ */
+ for (sp = expire_time; sp < expire_time + PSC_TINDX_COUNT; sp++) {
+ if (*sp == PSC_TIME_STAMP_NEW)
+ state->flags |= PSC_STATE_FLAG_NEW;
+ }
+
+ /*
+ * Don't flag disabled tests as "todo", because there would be no way to
+ * make those bits go away.
+ */
+ if (PSC_PREGR_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_PREGR])
+ state->flags |= PSC_STATE_FLAG_PREGR_TODO;
+ if (PSC_DNSBL_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_DNSBL])
+ state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
+ if (var_psc_pipel_enable && time_value > expire_time[PSC_TINDX_PIPEL])
+ state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
+ if (var_psc_nsmtp_enable && time_value > expire_time[PSC_TINDX_NSMTP])
+ state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
+ if (var_psc_barlf_enable && time_value > expire_time[PSC_TINDX_BARLF])
+ state->flags |= PSC_STATE_FLAG_BARLF_TODO;
+
+ /*
+ * If any test has expired, proactively refresh tests that will expire
+ * soon. This can increase the occurrence of client-visible delays, but
+ * avoids questions about why a client can pass some test and then fail
+ * within seconds. The proactive refresh time is really a surrogate for
+ * the user's curiosity level, and therefore hard to choose optimally.
+ */
+#ifdef VAR_PSC_REFRESH_TIME
+ if ((state->flags & PSC_STATE_MASK_ANY_TODO) != 0
+ && var_psc_refresh_time > 0) {
+ time_t refresh_time = time_value + var_psc_refresh_time;
+
+ if (PSC_PREGR_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_PREGR])
+ state->flags |= PSC_STATE_FLAG_PREGR_TODO;
+ if (PSC_DNSBL_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_DNSBL])
+ state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
+ if (var_psc_pipel_enable && refresh_time > expire_time[PSC_TINDX_PIPEL])
+ state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
+ if (var_psc_nsmtp_enable && refresh_time > expire_time[PSC_TINDX_NSMTP])
+ state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
+ if (var_psc_barlf_enable && refresh_time > expire_time[PSC_TINDX_BARLF])
+ state->flags |= PSC_STATE_FLAG_BARLF_TODO;
+ }
+#endif
+
+ /*
+ * Gratuitously make postscreen logging more useful by turning on all
+ * enabled pre-handshake tests when any pre-handshake test is turned on.
+ *
+ * XXX Don't enable PREGREET gratuitously before the test expires. With a
+ * short TTL for DNSBL allowlisting, turning on PREGREET would force a
+ * full postscreen_greet_wait too frequently.
+ */
+#if 0
+ if (state->flags & PSC_STATE_MASK_EARLY_TODO) {
+ if (PSC_PREGR_TEST_ENABLE())
+ state->flags |= PSC_STATE_FLAG_PREGR_TODO;
+ if (PSC_DNSBL_TEST_ENABLE())
+ state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
+ }
+#endif
+}
+
+/* psc_print_tests - print postscreen cache record */
+
+char *psc_print_tests(VSTRING *buf, PSC_STATE *state)
+{
+ const char *myname = "psc_print_tests";
+ time_t *expire_time = state->client_info->expire_time;
+
+ /*
+ * Sanity check.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) == 0)
+ msg_panic("%s: attempt to save a no-update record", myname);
+
+ /*
+ * Give disabled tests a dummy time stamp so that we don't log a client
+ * with "pass new" when some disabled test becomes enabled at some later
+ * time.
+ */
+ if (PSC_PREGR_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_PREGR] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_DISABLED;
+ if (PSC_DNSBL_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_DNSBL] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_DISABLED;
+ if (var_psc_pipel_enable == 0 && expire_time[PSC_TINDX_PIPEL] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED;
+ if (var_psc_nsmtp_enable == 0 && expire_time[PSC_TINDX_NSMTP] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED;
+ if (var_psc_barlf_enable == 0 && expire_time[PSC_TINDX_BARLF] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED;
+
+ vstring_sprintf(buf, "%lu;%lu;%lu;%lu;%lu",
+ (unsigned long) expire_time[PSC_TINDX_PREGR],
+ (unsigned long) expire_time[PSC_TINDX_DNSBL],
+ (unsigned long) expire_time[PSC_TINDX_PIPEL],
+ (unsigned long) expire_time[PSC_TINDX_NSMTP],
+ (unsigned long) expire_time[PSC_TINDX_BARLF]);
+ return (STR(buf));
+}
+
+/* psc_print_grey_key - print postscreen cache record */
+
+char *psc_print_grey_key(VSTRING *buf, const char *client,
+ const char *helo, const char *sender,
+ const char *rcpt)
+{
+ return (STR(vstring_sprintf(buf, "%s/%s/%s/%s",
+ client, helo, sender, rcpt)));
+}
+
+/* psc_test_name - map test index to symbolic name */
+
+const char *psc_test_name(int tindx)
+{
+ const char *myname = "psc_test_name";
+ const NAME_CODE test_name_map[] = {
+ PSC_TNAME_PREGR, PSC_TINDX_PREGR,
+ PSC_TNAME_DNSBL, PSC_TINDX_DNSBL,
+ PSC_TNAME_PIPEL, PSC_TINDX_PIPEL,
+ PSC_TNAME_NSMTP, PSC_TINDX_NSMTP,
+ PSC_TNAME_BARLF, PSC_TINDX_BARLF,
+ 0, -1,
+ };
+ const char *result;
+
+ if ((result = str_name_code(test_name_map, tindx)) == 0)
+ msg_panic("%s: bad index %d", myname, tindx);
+ return (result);
+}