summaryrefslogtreecommitdiffstats
path: root/src/bounce
diff options
context:
space:
mode:
Diffstat (limited to '')
l---------src/bounce/.indent.pro1
-rw-r--r--src/bounce/.printfck25
-rw-r--r--src/bounce/2template_test.in136
-rw-r--r--src/bounce/Makefile.in695
-rwxr-xr-xsrc/bounce/annotate.sh120
-rw-r--r--src/bounce/bounce.c713
-rw-r--r--src/bounce/bounce_append_service.c168
-rw-r--r--src/bounce/bounce_cleanup.c177
-rw-r--r--src/bounce/bounce_notify_service.c327
-rw-r--r--src/bounce/bounce_notify_util.c1010
-rw-r--r--src/bounce/bounce_notify_util_tester.c167
-rw-r--r--src/bounce/bounce_notify_verp.c267
-rw-r--r--src/bounce/bounce_one_service.c266
-rw-r--r--src/bounce/bounce_service.h121
-rw-r--r--src/bounce/bounce_template.c548
-rw-r--r--src/bounce/bounce_template.h99
-rw-r--r--src/bounce/bounce_templates.c399
-rw-r--r--src/bounce/bounce_trace_service.c220
-rw-r--r--src/bounce/bounce_warn_service.c295
-rw-r--r--src/bounce/logfile-no-msgid-no-eoh-event13
-rw-r--r--src/bounce/logfile-no-msgid-with-eoh-event13
-rw-r--r--src/bounce/logfile-with-msgid-no-eoh-event13
-rw-r--r--src/bounce/logfile-with-msgid-with-eoh-event13
-rw-r--r--src/bounce/logfile-with-msgid-with-filter13
-rw-r--r--src/bounce/logfile-with-msgid-with-long-line13
-rwxr-xr-xsrc/bounce/msgfile-no-msgid-no-eoh-eventbin0 -> 522 bytes
-rwxr-xr-xsrc/bounce/msgfile-no-msgid-with-eoh-eventbin0 -> 536 bytes
-rwxr-xr-xsrc/bounce/msgfile-with-msgid-no-eoh-eventbin0 -> 606 bytes
-rwxr-xr-xsrc/bounce/msgfile-with-msgid-with-eoh-eventbin0 -> 621 bytes
-rwxr-xr-xsrc/bounce/msgfile-with-msgid-with-filterbin0 -> 581 bytes
-rwxr-xr-xsrc/bounce/msgfile-with-msgid-with-long-linebin0 -> 4624 bytes
-rw-r--r--src/bounce/no-msgid-no-eoh-event-no-thread.ref47
-rw-r--r--src/bounce/no-msgid-no-eoh-event-with-thread.ref47
-rw-r--r--src/bounce/no-msgid-with-eoh-event-no-thread.ref49
-rw-r--r--src/bounce/no-msgid-with-eoh-event-with-thread.ref49
-rw-r--r--src/bounce/obs_template_test.ref68
-rw-r--r--src/bounce/template_test.ref68
-rw-r--r--src/bounce/with-msgid-no-eoh-event-no-thread.ref49
-rw-r--r--src/bounce/with-msgid-no-eoh-event-with-thread.ref51
-rw-r--r--src/bounce/with-msgid-with-eoh-event-no-thread.ref51
-rw-r--r--src/bounce/with-msgid-with-eoh-event-with-thread.ref53
-rw-r--r--src/bounce/with-msgid-with-filter-no-thread.ref48
-rw-r--r--src/bounce/with-msgid-with-filter-with-thread.ref50
-rw-r--r--src/bounce/with-msgid-with-long-line-no-thread.ref50
-rw-r--r--src/bounce/with-msgid-with-long-line-with-thread.ref52
45 files changed, 6564 insertions, 0 deletions
diff --git a/src/bounce/.indent.pro b/src/bounce/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/bounce/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/bounce/.printfck b/src/bounce/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/bounce/.printfck
@@ -0,0 +1,25 @@
+been_here_xt 2 0
+bounce_append 5 0
+cleanup_out_format 1 0
+defer_append 5 0
+mail_command 1 0
+mail_print 1 0
+msg_error 0 0
+msg_fatal 0 0
+msg_info 0 0
+msg_panic 0 0
+msg_warn 0 0
+opened 4 0
+post_mail_fprintf 1 0
+qmgr_message_bounce 2 0
+rec_fprintf 2 0
+sent 4 0
+smtp_cmd 1 0
+smtp_mesg_fail 2 0
+smtp_printf 1 0
+smtp_rcpt_fail 3 0
+smtp_site_fail 2 0
+udp_syslog 1 0
+vstream_fprintf 1 0
+vstream_printf 0 0
+vstring_sprintf 1 0
diff --git a/src/bounce/2template_test.in b/src/bounce/2template_test.in
new file mode 100644
index 0000000..e45fd32
--- /dev/null
+++ b/src/bounce/2template_test.in
@@ -0,0 +1,136 @@
+failure_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+Postmaster-Subject: Postmaster Copy: Undelivered Mail
+
+This is the mail system at host $myhostname.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+delay_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Delayed Mail (still being retried)
+Postmaster-Subject: Postmaster Warning: Delayed Mail
+
+This is the mail system at host $myhostname.
+
+####################################################################
+# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #
+####################################################################
+
+Your message could not be delivered for more than $delay_warning_time_hours hour(s).
+It will be retried until it is $maximal_queue_lifetime_days day(s) old.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+success_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Successful Mail Delivery Report
+
+This is the mail system at host $myhostname.
+
+Your message was successfully delivered to the destination(s)
+listed below. If the message was delivered to mailbox you will
+receive no further notifications. Otherwise you may still receive
+notifications of mail delivery errors from other systems.
+
+ The mail system
+EOF
+
+verify_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Mail Delivery Status Report
+
+This is the mail system at host $myhostname.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+EOF
+failure_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+Postmaster-Subject: Postmaster Copy: Undelivered Mail
+
+This is the mail system at host $myhostname.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+delay_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Delayed Mail (still being retried)
+Postmaster-Subject: Postmaster Warning: Delayed Mail
+
+This is the mail system at host $myhostname.
+
+####################################################################
+# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #
+####################################################################
+
+Your message could not be delivered for more than $delay_warning_time_hours hour(s).
+It will be retried until it is $maximal_queue_lifetime_days day(s) old.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+success_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Successful Mail Delivery Report
+
+This is the mail system at host $myhostname.
+
+Your message was successfully delivered to the destination(s)
+listed below. If the message was delivered to mailbox you will
+receive no further notifications. Otherwise you may still receive
+notifications of mail delivery errors from other systems.
+
+ The mail system
+EOF
+
+verify_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Mail Delivery Status Report
+
+This is the mail system at host $myhostname.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+EOF
diff --git a/src/bounce/Makefile.in b/src/bounce/Makefile.in
new file mode 100644
index 0000000..969413a
--- /dev/null
+++ b/src/bounce/Makefile.in
@@ -0,0 +1,695 @@
+SHELL = /bin/sh
+SRCS = bounce.c bounce_append_service.c bounce_notify_service.c \
+ bounce_cleanup.c bounce_notify_util.c bounce_notify_verp.c \
+ bounce_one_service.c bounce_warn_service.c bounce_trace_service.c \
+ bounce_template.c bounce_templates.c
+OBJS = bounce.o bounce_append_service.o bounce_notify_service.o \
+ bounce_cleanup.o bounce_notify_util.o bounce_notify_verp.o \
+ bounce_one_service.o bounce_warn_service.o bounce_trace_service.o \
+ bounce_template.o bounce_templates.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG= bounce_notify_util_tester
+PROG = bounce
+SAMPLES = ../../conf/bounce.cf.default
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(PROG) ../../conf/bounce.cf.default
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+# Eliminate dependency on installed Postfix.
+../../conf/bounce.cf.default: template_test.ref annotate.sh
+ rm -f $@
+ ./annotate.sh <template_test.ref >$@
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: update template_test obs_template_test 2template_test \
+ with-msgid-with-long-line_test \
+ with-msgid-with-eoh-event_test \
+ with-msgid-no-eoh-event_test \
+ no-msgid-with-eoh-event_test \
+ no-msgid-no-eoh-event_test \
+ with-msgid-with-filter_test
+
+root_tests:
+
+update: ../../libexec/$(PROG) $(SAMPLES)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h printfck
+ sed '1,/^# do not edit/!d' Makefile > printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk main.cf
+ rm -rf printfck
+
+tidy: clean
+
+BOUNCE_NOTIFY_UTIL_TESTER_OBJS = bounce_notify_util_tester.o \
+ bounce_notify_util.o bounce_template.o bounce_templates.o
+
+bounce_notify_util_tester: $(BOUNCE_NOTIFY_UTIL_TESTER_OBJS) $(LIBS)
+ $(CC) -DTEST $(CFLAGS) -o $@ $(BOUNCE_NOTIFY_UTIL_TESTER_OBJS) \
+ $(LIBS) $(SYSLIBS)
+
+# Avoid dependency on installed Postfix.
+# XXX This still requires that default_privs, mail_owner etc. accounts exist.
+template_test: $(PROG) template_test.ref
+ echo queue_directory=. >main.cf
+ echo myhostname=example.com >>main.cf
+ echo header_from_format=standard >>main.cf
+ touch -t 197101010000 main.cf
+ MAIL_CONFIG=. ./$(PROG) -SVzndump_templates >template_test.tmp
+ diff template_test.ref template_test.tmp
+ MAIL_CONFIG=. ./$(PROG) -SVzndump_templates \
+ -o bounce_template_file=template_test.ref > template_test.tmp
+ diff template_test.ref template_test.tmp
+ rm -f template_test.tmp main.cf
+
+obs_template_test: $(PROG) obs_template_test.ref
+ echo queue_directory=. >main.cf
+ echo myhostname=example.com >>main.cf
+ echo header_from_format=obsolete >>main.cf
+ touch -t 197101010000 main.cf
+ MAIL_CONFIG=. ./$(PROG) -SVzndump_templates >template_test.tmp
+ diff obs_template_test.ref template_test.tmp
+ rm -f template_test.tmp main.cf
+
+2template_test: $(PROG) template_test.ref 2template_test.in
+ echo queue_directory=. >main.cf
+ echo myhostname=example.com >>main.cf
+ touch -t 197101010000 main.cf
+ MAIL_CONFIG=. ./$(PROG) -SVzndump_templates \
+ -o bounce_template_file=2template_test.in > template_test.tmp
+ diff template_test.ref template_test.tmp
+ rm -f template_test.tmp main.cf
+
+with-msgid-with-long-line_test: bounce_notify_util_tester \
+ msgfile-with-msgid-with-long-line logfile-with-msgid-with-long-line \
+ with-msgid-with-long-line-no-thread.ref \
+ with-msgid-with-long-line-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-long-line queue/bounce/msgid
+ cp msgfile-with-msgid-with-long-line queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-long-line-no-thread.tmp
+ diff with-msgid-with-long-line-no-thread.ref with-msgid-with-long-line-no-thread.tmp
+ rm -f with-msgid-with-long-line-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-long-line queue/bounce/msgid
+ cp msgfile-with-msgid-with-long-line queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-long-line-with-thread.tmp
+ diff with-msgid-with-long-line-with-thread.ref with-msgid-with-long-line-with-thread.tmp
+ rm -f with-msgid-with-long-line-with-thread.tmp
+ rm -rf queue main.cf
+
+with-msgid-with-eoh-event_test: bounce_notify_util_tester \
+ msgfile-with-msgid-with-eoh-event logfile-with-msgid-with-eoh-event \
+ with-msgid-with-eoh-event-no-thread.ref \
+ with-msgid-with-eoh-event-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-eoh-event queue/bounce/msgid
+ cp msgfile-with-msgid-with-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-eoh-event-no-thread.tmp
+ diff with-msgid-with-eoh-event-no-thread.ref with-msgid-with-eoh-event-no-thread.tmp
+ rm -f with-msgid-with-eoh-event-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-eoh-event queue/bounce/msgid
+ cp msgfile-with-msgid-with-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-eoh-event-with-thread.tmp
+ diff with-msgid-with-eoh-event-with-thread.ref with-msgid-with-eoh-event-with-thread.tmp
+ rm -f with-msgid-with-eoh-event-with-thread.tmp
+ rm -rf queue main.cf
+
+with-msgid-no-eoh-event_test: bounce_notify_util_tester \
+ msgfile-with-msgid-no-eoh-event logfile-with-msgid-no-eoh-event \
+ with-msgid-no-eoh-event-no-thread.ref \
+ with-msgid-no-eoh-event-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-no-eoh-event queue/bounce/msgid
+ cp msgfile-with-msgid-no-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-no-eoh-event-no-thread.tmp
+ diff with-msgid-no-eoh-event-no-thread.ref with-msgid-no-eoh-event-no-thread.tmp
+ rm -f with-msgid-no-eoh-event-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-no-eoh-event queue/bounce/msgid
+ cp msgfile-with-msgid-no-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-no-eoh-event-with-thread.tmp
+ diff with-msgid-no-eoh-event-with-thread.ref with-msgid-no-eoh-event-with-thread.tmp
+ rm -f with-msgid-no-eoh-event-with-thread.tmp
+ rm -rf queue main.cf
+
+no-msgid-with-eoh-event_test: bounce_notify_util_tester \
+ msgfile-no-msgid-with-eoh-event logfile-no-msgid-with-eoh-event \
+ no-msgid-with-eoh-event-no-thread.ref \
+ no-msgid-with-eoh-event-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-no-msgid-with-eoh-event queue/bounce/msgid
+ cp msgfile-no-msgid-with-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > no-msgid-with-eoh-event-no-thread.tmp
+ diff no-msgid-with-eoh-event-no-thread.ref no-msgid-with-eoh-event-no-thread.tmp
+ rm -f no-msgid-with-eoh-event-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-no-msgid-with-eoh-event queue/bounce/msgid
+ cp msgfile-no-msgid-with-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > no-msgid-with-eoh-event-with-thread.tmp
+ diff no-msgid-with-eoh-event-with-thread.ref no-msgid-with-eoh-event-with-thread.tmp
+ rm -f no-msgid-with-eoh-event-with-thread.tmp
+ rm -rf queue main.cf
+
+no-msgid-no-eoh-event_test: bounce_notify_util_tester \
+ msgfile-no-msgid-no-eoh-event logfile-no-msgid-no-eoh-event \
+ no-msgid-no-eoh-event-no-thread.ref \
+ no-msgid-no-eoh-event-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-no-msgid-no-eoh-event queue/bounce/msgid
+ cp msgfile-no-msgid-no-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > no-msgid-no-eoh-event-no-thread.tmp
+ diff no-msgid-no-eoh-event-no-thread.ref no-msgid-no-eoh-event-no-thread.tmp
+ rm -f no-msgid-no-eoh-event-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-no-msgid-no-eoh-event queue/bounce/msgid
+ cp msgfile-no-msgid-no-eoh-event queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > no-msgid-no-eoh-event-with-thread.tmp
+ diff no-msgid-no-eoh-event-with-thread.ref no-msgid-no-eoh-event-with-thread.tmp
+ rm -f no-msgid-no-eoh-event-with-thread.tmp
+ rm -rf queue main.cf
+
+with-msgid-with-filter_test: bounce_notify_util_tester \
+ msgfile-with-msgid-with-filter logfile-with-msgid-with-filter \
+ with-msgid-with-filter-no-thread.ref \
+ with-msgid-with-filter-with-thread.ref
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = no' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-filter queue/bounce/msgid
+ cp msgfile-with-msgid-with-filter queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-filter-no-thread.tmp
+ diff with-msgid-with-filter-no-thread.ref with-msgid-with-filter-no-thread.tmp
+ rm -f with-msgid-with-filter-no-thread.tmp
+ :
+ rm -rf queue main.cf
+ echo 'enable_threaded_bounces = yes' >main.cf
+ echo 'queue_directory = queue' >>main.cf
+ echo 'myhostname = mail.example' >>main.cf
+ touch -t 197101010000 main.cf
+ mkdir -p queue/active queue/bounce
+ cp logfile-with-msgid-with-filter queue/bounce/msgid
+ cp msgfile-with-msgid-with-filter queue/active/msgid
+ $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \
+ -c. bounce active msgid 2>&1 | \
+ sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \
+ > with-msgid-with-filter-with-thread.tmp
+ diff with-msgid-with-filter-with-thread.ref with-msgid-with-filter-with-thread.tmp
+ rm -f with-msgid-with-filter-with-thread.tmp
+ rm -rf queue main.cf
+
+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'
+bounce.o: ../../include/attr.h
+bounce.o: ../../include/bounce.h
+bounce.o: ../../include/bounce_log.h
+bounce.o: ../../include/check_arg.h
+bounce.o: ../../include/deliver_request.h
+bounce.o: ../../include/dsb_scan.h
+bounce.o: ../../include/dsn.h
+bounce.o: ../../include/dsn_buf.h
+bounce.o: ../../include/hfrom_format.h
+bounce.o: ../../include/htable.h
+bounce.o: ../../include/iostuff.h
+bounce.o: ../../include/load_file.h
+bounce.o: ../../include/mail_addr.h
+bounce.o: ../../include/mail_conf.h
+bounce.o: ../../include/mail_params.h
+bounce.o: ../../include/mail_proto.h
+bounce.o: ../../include/mail_queue.h
+bounce.o: ../../include/mail_server.h
+bounce.o: ../../include/mail_version.h
+bounce.o: ../../include/msg.h
+bounce.o: ../../include/msg_stats.h
+bounce.o: ../../include/mymalloc.h
+bounce.o: ../../include/nvtable.h
+bounce.o: ../../include/rcpt_buf.h
+bounce.o: ../../include/recipient_list.h
+bounce.o: ../../include/stringops.h
+bounce.o: ../../include/sys_defs.h
+bounce.o: ../../include/vbuf.h
+bounce.o: ../../include/vstream.h
+bounce.o: ../../include/vstring.h
+bounce.o: bounce.c
+bounce.o: bounce_service.h
+bounce.o: bounce_template.h
+bounce_append_service.o: ../../include/attr.h
+bounce_append_service.o: ../../include/bounce_log.h
+bounce_append_service.o: ../../include/check_arg.h
+bounce_append_service.o: ../../include/deliver_flock.h
+bounce_append_service.o: ../../include/dsn.h
+bounce_append_service.o: ../../include/dsn_buf.h
+bounce_append_service.o: ../../include/htable.h
+bounce_append_service.o: ../../include/iostuff.h
+bounce_append_service.o: ../../include/mail_params.h
+bounce_append_service.o: ../../include/mail_proto.h
+bounce_append_service.o: ../../include/mail_queue.h
+bounce_append_service.o: ../../include/msg.h
+bounce_append_service.o: ../../include/myflock.h
+bounce_append_service.o: ../../include/mymalloc.h
+bounce_append_service.o: ../../include/nvtable.h
+bounce_append_service.o: ../../include/quote_822_local.h
+bounce_append_service.o: ../../include/quote_flags.h
+bounce_append_service.o: ../../include/rcpt_buf.h
+bounce_append_service.o: ../../include/recipient_list.h
+bounce_append_service.o: ../../include/stringops.h
+bounce_append_service.o: ../../include/sys_defs.h
+bounce_append_service.o: ../../include/vbuf.h
+bounce_append_service.o: ../../include/vstream.h
+bounce_append_service.o: ../../include/vstring.h
+bounce_append_service.o: bounce_append_service.c
+bounce_append_service.o: bounce_service.h
+bounce_append_service.o: bounce_template.h
+bounce_cleanup.o: ../../include/attr.h
+bounce_cleanup.o: ../../include/bounce_log.h
+bounce_cleanup.o: ../../include/check_arg.h
+bounce_cleanup.o: ../../include/dsn.h
+bounce_cleanup.o: ../../include/dsn_buf.h
+bounce_cleanup.o: ../../include/htable.h
+bounce_cleanup.o: ../../include/mail_queue.h
+bounce_cleanup.o: ../../include/msg.h
+bounce_cleanup.o: ../../include/mymalloc.h
+bounce_cleanup.o: ../../include/nvtable.h
+bounce_cleanup.o: ../../include/rcpt_buf.h
+bounce_cleanup.o: ../../include/recipient_list.h
+bounce_cleanup.o: ../../include/sys_defs.h
+bounce_cleanup.o: ../../include/vbuf.h
+bounce_cleanup.o: ../../include/vstream.h
+bounce_cleanup.o: ../../include/vstring.h
+bounce_cleanup.o: bounce_cleanup.c
+bounce_cleanup.o: bounce_service.h
+bounce_cleanup.o: bounce_template.h
+bounce_notify_service.o: ../../include/attr.h
+bounce_notify_service.o: ../../include/bounce.h
+bounce_notify_service.o: ../../include/bounce_log.h
+bounce_notify_service.o: ../../include/check_arg.h
+bounce_notify_service.o: ../../include/cleanup_user.h
+bounce_notify_service.o: ../../include/deliver_request.h
+bounce_notify_service.o: ../../include/dsn.h
+bounce_notify_service.o: ../../include/dsn_buf.h
+bounce_notify_service.o: ../../include/dsn_mask.h
+bounce_notify_service.o: ../../include/htable.h
+bounce_notify_service.o: ../../include/int_filt.h
+bounce_notify_service.o: ../../include/iostuff.h
+bounce_notify_service.o: ../../include/mail_addr.h
+bounce_notify_service.o: ../../include/mail_error.h
+bounce_notify_service.o: ../../include/mail_params.h
+bounce_notify_service.o: ../../include/mail_proto.h
+bounce_notify_service.o: ../../include/mail_queue.h
+bounce_notify_service.o: ../../include/msg.h
+bounce_notify_service.o: ../../include/msg_stats.h
+bounce_notify_service.o: ../../include/mymalloc.h
+bounce_notify_service.o: ../../include/name_mask.h
+bounce_notify_service.o: ../../include/nvtable.h
+bounce_notify_service.o: ../../include/post_mail.h
+bounce_notify_service.o: ../../include/rcpt_buf.h
+bounce_notify_service.o: ../../include/rec_type.h
+bounce_notify_service.o: ../../include/recipient_list.h
+bounce_notify_service.o: ../../include/smtputf8.h
+bounce_notify_service.o: ../../include/stringops.h
+bounce_notify_service.o: ../../include/sys_defs.h
+bounce_notify_service.o: ../../include/vbuf.h
+bounce_notify_service.o: ../../include/vstream.h
+bounce_notify_service.o: ../../include/vstring.h
+bounce_notify_service.o: bounce_notify_service.c
+bounce_notify_service.o: bounce_service.h
+bounce_notify_service.o: bounce_template.h
+bounce_notify_util.o: ../../include/attr.h
+bounce_notify_util.o: ../../include/bounce_log.h
+bounce_notify_util.o: ../../include/check_arg.h
+bounce_notify_util.o: ../../include/cleanup_user.h
+bounce_notify_util.o: ../../include/deliver_completed.h
+bounce_notify_util.o: ../../include/dsn.h
+bounce_notify_util.o: ../../include/dsn_buf.h
+bounce_notify_util.o: ../../include/dsn_mask.h
+bounce_notify_util.o: ../../include/events.h
+bounce_notify_util.o: ../../include/header_opts.h
+bounce_notify_util.o: ../../include/htable.h
+bounce_notify_util.o: ../../include/int_filt.h
+bounce_notify_util.o: ../../include/iostuff.h
+bounce_notify_util.o: ../../include/is_header.h
+bounce_notify_util.o: ../../include/lex_822.h
+bounce_notify_util.o: ../../include/line_wrap.h
+bounce_notify_util.o: ../../include/mail_addr.h
+bounce_notify_util.o: ../../include/mail_date.h
+bounce_notify_util.o: ../../include/mail_error.h
+bounce_notify_util.o: ../../include/mail_params.h
+bounce_notify_util.o: ../../include/mail_proto.h
+bounce_notify_util.o: ../../include/mail_queue.h
+bounce_notify_util.o: ../../include/msg.h
+bounce_notify_util.o: ../../include/myflock.h
+bounce_notify_util.o: ../../include/mymalloc.h
+bounce_notify_util.o: ../../include/name_mask.h
+bounce_notify_util.o: ../../include/nvtable.h
+bounce_notify_util.o: ../../include/post_mail.h
+bounce_notify_util.o: ../../include/quote_822_local.h
+bounce_notify_util.o: ../../include/quote_flags.h
+bounce_notify_util.o: ../../include/rcpt_buf.h
+bounce_notify_util.o: ../../include/rec_type.h
+bounce_notify_util.o: ../../include/recipient_list.h
+bounce_notify_util.o: ../../include/record.h
+bounce_notify_util.o: ../../include/smtputf8.h
+bounce_notify_util.o: ../../include/stringops.h
+bounce_notify_util.o: ../../include/sys_defs.h
+bounce_notify_util.o: ../../include/vbuf.h
+bounce_notify_util.o: ../../include/vstream.h
+bounce_notify_util.o: ../../include/vstring.h
+bounce_notify_util.o: bounce_notify_util.c
+bounce_notify_util.o: bounce_service.h
+bounce_notify_util.o: bounce_template.h
+bounce_notify_util_tester.o: ../../include/attr.h
+bounce_notify_util_tester.o: ../../include/bounce_log.h
+bounce_notify_util_tester.o: ../../include/check_arg.h
+bounce_notify_util_tester.o: ../../include/dsn.h
+bounce_notify_util_tester.o: ../../include/dsn_buf.h
+bounce_notify_util_tester.o: ../../include/dsn_mask.h
+bounce_notify_util_tester.o: ../../include/hfrom_format.h
+bounce_notify_util_tester.o: ../../include/htable.h
+bounce_notify_util_tester.o: ../../include/mail_conf.h
+bounce_notify_util_tester.o: ../../include/mail_params.h
+bounce_notify_util_tester.o: ../../include/msg.h
+bounce_notify_util_tester.o: ../../include/mymalloc.h
+bounce_notify_util_tester.o: ../../include/nvtable.h
+bounce_notify_util_tester.o: ../../include/rcpt_buf.h
+bounce_notify_util_tester.o: ../../include/rec_type.h
+bounce_notify_util_tester.o: ../../include/recipient_list.h
+bounce_notify_util_tester.o: ../../include/record.h
+bounce_notify_util_tester.o: ../../include/sys_defs.h
+bounce_notify_util_tester.o: ../../include/test_main.h
+bounce_notify_util_tester.o: ../../include/vbuf.h
+bounce_notify_util_tester.o: ../../include/vstream.h
+bounce_notify_util_tester.o: ../../include/vstring.h
+bounce_notify_util_tester.o: bounce_notify_util_tester.c
+bounce_notify_util_tester.o: bounce_service.h
+bounce_notify_util_tester.o: bounce_template.h
+bounce_notify_verp.o: ../../include/attr.h
+bounce_notify_verp.o: ../../include/bounce.h
+bounce_notify_verp.o: ../../include/bounce_log.h
+bounce_notify_verp.o: ../../include/check_arg.h
+bounce_notify_verp.o: ../../include/cleanup_user.h
+bounce_notify_verp.o: ../../include/deliver_request.h
+bounce_notify_verp.o: ../../include/dsn.h
+bounce_notify_verp.o: ../../include/dsn_buf.h
+bounce_notify_verp.o: ../../include/dsn_mask.h
+bounce_notify_verp.o: ../../include/htable.h
+bounce_notify_verp.o: ../../include/int_filt.h
+bounce_notify_verp.o: ../../include/iostuff.h
+bounce_notify_verp.o: ../../include/mail_addr.h
+bounce_notify_verp.o: ../../include/mail_error.h
+bounce_notify_verp.o: ../../include/mail_params.h
+bounce_notify_verp.o: ../../include/mail_proto.h
+bounce_notify_verp.o: ../../include/mail_queue.h
+bounce_notify_verp.o: ../../include/msg.h
+bounce_notify_verp.o: ../../include/msg_stats.h
+bounce_notify_verp.o: ../../include/mymalloc.h
+bounce_notify_verp.o: ../../include/name_mask.h
+bounce_notify_verp.o: ../../include/nvtable.h
+bounce_notify_verp.o: ../../include/post_mail.h
+bounce_notify_verp.o: ../../include/rcpt_buf.h
+bounce_notify_verp.o: ../../include/rec_type.h
+bounce_notify_verp.o: ../../include/recipient_list.h
+bounce_notify_verp.o: ../../include/smtputf8.h
+bounce_notify_verp.o: ../../include/stringops.h
+bounce_notify_verp.o: ../../include/sys_defs.h
+bounce_notify_verp.o: ../../include/vbuf.h
+bounce_notify_verp.o: ../../include/verp_sender.h
+bounce_notify_verp.o: ../../include/vstream.h
+bounce_notify_verp.o: ../../include/vstring.h
+bounce_notify_verp.o: bounce_notify_verp.c
+bounce_notify_verp.o: bounce_service.h
+bounce_notify_verp.o: bounce_template.h
+bounce_one_service.o: ../../include/attr.h
+bounce_one_service.o: ../../include/bounce.h
+bounce_one_service.o: ../../include/bounce_log.h
+bounce_one_service.o: ../../include/check_arg.h
+bounce_one_service.o: ../../include/cleanup_user.h
+bounce_one_service.o: ../../include/deliver_request.h
+bounce_one_service.o: ../../include/dsn.h
+bounce_one_service.o: ../../include/dsn_buf.h
+bounce_one_service.o: ../../include/dsn_mask.h
+bounce_one_service.o: ../../include/htable.h
+bounce_one_service.o: ../../include/int_filt.h
+bounce_one_service.o: ../../include/iostuff.h
+bounce_one_service.o: ../../include/mail_addr.h
+bounce_one_service.o: ../../include/mail_error.h
+bounce_one_service.o: ../../include/mail_params.h
+bounce_one_service.o: ../../include/mail_proto.h
+bounce_one_service.o: ../../include/msg.h
+bounce_one_service.o: ../../include/msg_stats.h
+bounce_one_service.o: ../../include/mymalloc.h
+bounce_one_service.o: ../../include/name_mask.h
+bounce_one_service.o: ../../include/nvtable.h
+bounce_one_service.o: ../../include/post_mail.h
+bounce_one_service.o: ../../include/rcpt_buf.h
+bounce_one_service.o: ../../include/rec_type.h
+bounce_one_service.o: ../../include/recipient_list.h
+bounce_one_service.o: ../../include/smtputf8.h
+bounce_one_service.o: ../../include/stringops.h
+bounce_one_service.o: ../../include/sys_defs.h
+bounce_one_service.o: ../../include/vbuf.h
+bounce_one_service.o: ../../include/vstream.h
+bounce_one_service.o: ../../include/vstring.h
+bounce_one_service.o: bounce_one_service.c
+bounce_one_service.o: bounce_service.h
+bounce_one_service.o: bounce_template.h
+bounce_template.o: ../../include/attr.h
+bounce_template.o: ../../include/bounce_log.h
+bounce_template.o: ../../include/check_arg.h
+bounce_template.o: ../../include/dsn.h
+bounce_template.o: ../../include/dsn_buf.h
+bounce_template.o: ../../include/hfrom_format.h
+bounce_template.o: ../../include/htable.h
+bounce_template.o: ../../include/iostuff.h
+bounce_template.o: ../../include/is_header.h
+bounce_template.o: ../../include/mac_expand.h
+bounce_template.o: ../../include/mac_parse.h
+bounce_template.o: ../../include/mail_conf.h
+bounce_template.o: ../../include/mail_params.h
+bounce_template.o: ../../include/mail_proto.h
+bounce_template.o: ../../include/midna_domain.h
+bounce_template.o: ../../include/msg.h
+bounce_template.o: ../../include/mymalloc.h
+bounce_template.o: ../../include/nvtable.h
+bounce_template.o: ../../include/rcpt_buf.h
+bounce_template.o: ../../include/recipient_list.h
+bounce_template.o: ../../include/split_at.h
+bounce_template.o: ../../include/stringops.h
+bounce_template.o: ../../include/sys_defs.h
+bounce_template.o: ../../include/vbuf.h
+bounce_template.o: ../../include/vstream.h
+bounce_template.o: ../../include/vstring.h
+bounce_template.o: bounce_service.h
+bounce_template.o: bounce_template.c
+bounce_template.o: bounce_template.h
+bounce_templates.o: ../../include/attr.h
+bounce_templates.o: ../../include/check_arg.h
+bounce_templates.o: ../../include/htable.h
+bounce_templates.o: ../../include/iostuff.h
+bounce_templates.o: ../../include/mail_addr.h
+bounce_templates.o: ../../include/mail_proto.h
+bounce_templates.o: ../../include/msg.h
+bounce_templates.o: ../../include/mymalloc.h
+bounce_templates.o: ../../include/nvtable.h
+bounce_templates.o: ../../include/stringops.h
+bounce_templates.o: ../../include/sys_defs.h
+bounce_templates.o: ../../include/vbuf.h
+bounce_templates.o: ../../include/vstream.h
+bounce_templates.o: ../../include/vstring.h
+bounce_templates.o: ../../include/vstring_vstream.h
+bounce_templates.o: bounce_template.h
+bounce_templates.o: bounce_templates.c
+bounce_trace_service.o: ../../include/attr.h
+bounce_trace_service.o: ../../include/bounce_log.h
+bounce_trace_service.o: ../../include/check_arg.h
+bounce_trace_service.o: ../../include/cleanup_user.h
+bounce_trace_service.o: ../../include/deliver_request.h
+bounce_trace_service.o: ../../include/dsn.h
+bounce_trace_service.o: ../../include/dsn_buf.h
+bounce_trace_service.o: ../../include/dsn_mask.h
+bounce_trace_service.o: ../../include/htable.h
+bounce_trace_service.o: ../../include/int_filt.h
+bounce_trace_service.o: ../../include/iostuff.h
+bounce_trace_service.o: ../../include/mail_addr.h
+bounce_trace_service.o: ../../include/mail_error.h
+bounce_trace_service.o: ../../include/mail_params.h
+bounce_trace_service.o: ../../include/mail_proto.h
+bounce_trace_service.o: ../../include/mail_queue.h
+bounce_trace_service.o: ../../include/msg.h
+bounce_trace_service.o: ../../include/msg_stats.h
+bounce_trace_service.o: ../../include/mymalloc.h
+bounce_trace_service.o: ../../include/name_mask.h
+bounce_trace_service.o: ../../include/nvtable.h
+bounce_trace_service.o: ../../include/post_mail.h
+bounce_trace_service.o: ../../include/rcpt_buf.h
+bounce_trace_service.o: ../../include/rec_type.h
+bounce_trace_service.o: ../../include/recipient_list.h
+bounce_trace_service.o: ../../include/smtputf8.h
+bounce_trace_service.o: ../../include/stringops.h
+bounce_trace_service.o: ../../include/sys_defs.h
+bounce_trace_service.o: ../../include/vbuf.h
+bounce_trace_service.o: ../../include/vstream.h
+bounce_trace_service.o: ../../include/vstring.h
+bounce_trace_service.o: bounce_service.h
+bounce_trace_service.o: bounce_template.h
+bounce_trace_service.o: bounce_trace_service.c
+bounce_warn_service.o: ../../include/attr.h
+bounce_warn_service.o: ../../include/bounce_log.h
+bounce_warn_service.o: ../../include/check_arg.h
+bounce_warn_service.o: ../../include/cleanup_user.h
+bounce_warn_service.o: ../../include/dsn.h
+bounce_warn_service.o: ../../include/dsn_buf.h
+bounce_warn_service.o: ../../include/dsn_mask.h
+bounce_warn_service.o: ../../include/htable.h
+bounce_warn_service.o: ../../include/int_filt.h
+bounce_warn_service.o: ../../include/iostuff.h
+bounce_warn_service.o: ../../include/mail_addr.h
+bounce_warn_service.o: ../../include/mail_error.h
+bounce_warn_service.o: ../../include/mail_params.h
+bounce_warn_service.o: ../../include/mail_proto.h
+bounce_warn_service.o: ../../include/mail_queue.h
+bounce_warn_service.o: ../../include/msg.h
+bounce_warn_service.o: ../../include/mymalloc.h
+bounce_warn_service.o: ../../include/name_mask.h
+bounce_warn_service.o: ../../include/nvtable.h
+bounce_warn_service.o: ../../include/post_mail.h
+bounce_warn_service.o: ../../include/rcpt_buf.h
+bounce_warn_service.o: ../../include/rec_type.h
+bounce_warn_service.o: ../../include/recipient_list.h
+bounce_warn_service.o: ../../include/smtputf8.h
+bounce_warn_service.o: ../../include/stringops.h
+bounce_warn_service.o: ../../include/sys_defs.h
+bounce_warn_service.o: ../../include/vbuf.h
+bounce_warn_service.o: ../../include/vstream.h
+bounce_warn_service.o: ../../include/vstring.h
+bounce_warn_service.o: bounce_service.h
+bounce_warn_service.o: bounce_template.h
+bounce_warn_service.o: bounce_warn_service.c
diff --git a/src/bounce/annotate.sh b/src/bounce/annotate.sh
new file mode 100755
index 0000000..c2acaa8
--- /dev/null
+++ b/src/bounce/annotate.sh
@@ -0,0 +1,120 @@
+#!/bin/sh
+
+cat <<'EOF'
+#
+# Do not edit this file. This file shows the default delivery status
+# notification (DSN) messages that are built into Postfix.
+#
+# To change Postfix DSN messages, perhaps to add non-English text,
+# follow instructions in the bounce(5) manual page.
+#
+EOF
+
+# QUICK INSTRUCTIONS:
+#
+#-Edit a temporary copy of this file, and preview the result of $name
+# expansions with "postconf -b temporary_file". If there are any
+# problems, Postfix will log "warning" or "fatal" messages to the
+# maillog file.
+#
+#-The template file can specify bounce message templates for
+# failed mail, for delayed mail, for successful delivery, or for
+# verbose delivery. You don't have to specify all templates.
+#
+#-Each template starts with "template_name = <<EOF" and ends
+# with a line that contains the word "EOF" only. You can change the
+# word EOF if you like, but you can't do shell/perl/etc like things
+# such as enclosing it in quotes (template_name = <<'EOF').
+#
+#-Each template consists of a few headers and message text. The
+# headers control what the recipient sees as From: and Subject:, and
+# what MIME information Postfix will generate.
+#
+#-Template message headers must not span multiple lines.
+#
+#-Template message headers must not contain main.cf $parameters.
+#
+#-Template message headers must contain ASCII characters only.
+#
+#-The template message text is not sent in Postmaster copies of
+# delivery status notifications.
+#
+#-Template message text may contain main.cf $parameters. Some
+# parameters have additional features as described below with the
+# delayed mail message template.
+#
+#-Template message text may contain non-ASCII text. In that case you
+# MUST change the character set value in the CHARSET: template header,
+# otherwise Postfix will not use your template. You must specify a
+# character set that is a superset of US-ASCII, because Postfix
+# appends ASCII text after the message template when it sends a
+# delivery status notification.
+#
+#-When previewing the result with "postconf -b temporary_file", be
+# sure to pay particular attention to the time values that appear
+# in the delayed mail notification text.
+#
+#-Once you're satisfied with the result, and once Postfix stops
+# logging warning messages, copy the template to the Postfix
+# configuration directory and specify in main.cf something like:
+#
+# /etc/postfix/main.cf:
+# bounce_template_file = $config_directory/bounce.cf
+#
+#EOF
+
+IFS=
+while read line; do
+ case "$line" in
+ failure_template*) cat <<'EOF'
+
+#
+# The failure template is used when mail is returned to the sender;
+# either the destination rejected the message, or the destination
+# could not be reached before the message expired in the queue.
+#
+
+EOF
+ ;;
+ delay_template*) cat <<'EOF'
+
+#
+# The delay template is used when mail is delayed. Note a neat trick:
+# the default template displays the delay_warning_time value as hours
+# by appending the _hours suffix to the parameter name; it displays
+# the maximal_queue_lifetime value as days by appending the _days
+# suffix.
+#
+# Other suffixes are: _seconds, _minutes, _weeks. There are no other
+# main.cf parameters that have this special behavior.
+#
+# You need to adjust these suffixes (and the surrounding text) if
+# you have very different settings for these time parameters.
+#
+
+EOF
+ ;;
+ success_template*) cat <<'EOF'
+
+#
+# The success template is used when mail is delivered to mailbox,
+# when an alias or list is expanded, or when mail is delivered to a
+# system that does not announce DSN support. It is an error to specify
+# a Postmaster-Subject: here.
+#
+
+EOF
+ ;;
+ verify_template*) cat <<'EOF'
+
+#
+# The verify template is used for address verification (sendmail -bv
+# address...) or for verbose mail delivery (sendmail -v address...).
+# It is an error to specify a Postmaster-Subject: here.
+#
+
+EOF
+ ;;
+ esac
+ echo "$line";
+done
diff --git a/src/bounce/bounce.c b/src/bounce/bounce.c
new file mode 100644
index 0000000..2393929
--- /dev/null
+++ b/src/bounce/bounce.c
@@ -0,0 +1,713 @@
+/*++
+/* NAME
+/* bounce 8
+/* SUMMARY
+/* Postfix delivery status reports
+/* SYNOPSIS
+/* \fBbounce\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBbounce\fR(8) daemon maintains per-message log files with
+/* delivery status information. Each log file is named after the
+/* queue file that it corresponds to, and is kept in a queue subdirectory
+/* named after the service name in the \fBmaster.cf\fR file (either
+/* \fBbounce\fR, \fBdefer\fR or \fBtrace\fR).
+/* This program expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/*
+/* The \fBbounce\fR(8) daemon processes two types of service requests:
+/* .IP \(bu
+/* Append a recipient (non-)delivery status record to a per-message
+/* log file.
+/* .IP \(bu
+/* Enqueue a delivery status notification message, with a copy
+/* of a per-message log file and of the corresponding message.
+/* When the delivery status notification message is
+/* enqueued successfully, the per-message log file is deleted.
+/* .PP
+/* The software does a best notification effort. A non-delivery
+/* notification is sent even when the log file or the original
+/* message cannot be read.
+/*
+/* Optionally, a bounce (defer, trace) client can request that the
+/* per-message log file be deleted when the requested operation fails.
+/* This is used by clients that cannot retry transactions by
+/* themselves, and that depend on retry logic in their own client.
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* RFC 2045 (Format of Internet Message Bodies)
+/* RFC 2822 (Internet Message Format)
+/* RFC 3462 (Delivery Status Notifications)
+/* RFC 3464 (Delivery Status Notifications)
+/* RFC 3834 (Auto-Submitted: message header)
+/* RFC 5322 (Internet Message Format)
+/* RFC 6531 (Internationalized SMTP)
+/* RFC 6532 (Internationalized Message Format)
+/* RFC 6533 (Internationalized Delivery Status Notifications)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as \fBbounce\fR(8)
+/* processes run for only a limited amount of time. Use the command
+/* "\fBpostfix reload\fR" to speed up a change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fB2bounce_notice_recipient (postmaster)\fR"
+/* The recipient of undeliverable mail that cannot be returned to
+/* the sender.
+/* .IP "\fBbackwards_bounce_logfile_compatibility (yes)\fR"
+/* Produce additional \fBbounce\fR(8) logfile records that can be read by
+/* Postfix versions before 2.0.
+/* .IP "\fBbounce_notice_recipient (postmaster)\fR"
+/* The recipient of postmaster notifications with the message headers
+/* of mail that Postfix did not deliver and of SMTP conversation
+/* transcripts of mail that Postfix did not receive.
+/* .IP "\fBbounce_size_limit (50000)\fR"
+/* The maximal amount of original message text that is sent in a
+/* non-delivery notification.
+/* .IP "\fBbounce_template_file (empty)\fR"
+/* Pathname of a configuration file with bounce message templates.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdaemon_timeout (18000s)\fR"
+/* How much time a Postfix daemon process may take to handle a
+/* request before it is terminated by a built-in watchdog timer.
+/* .IP "\fBdelay_notice_recipient (postmaster)\fR"
+/* The recipient of postmaster notifications with the message headers
+/* of mail that cannot be delivered within $delay_warning_time time
+/* units.
+/* .IP "\fBdeliver_lock_attempts (20)\fR"
+/* The maximal number of attempts to acquire an exclusive lock on a
+/* mailbox file or \fBbounce\fR(8) logfile.
+/* .IP "\fBdeliver_lock_delay (1s)\fR"
+/* The time between attempts to acquire an exclusive lock on a mailbox
+/* file or \fBbounce\fR(8) logfile.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBinternal_mail_filter_classes (empty)\fR"
+/* What categories of Postfix-generated mail are subject to
+/* before-queue content inspection by non_smtpd_milters, header_checks
+/* and body_checks.
+/* .IP "\fBmail_name (Postfix)\fR"
+/* The mail system name that is displayed in Received: headers, in
+/* the SMTP greeting banner, and in bounced mail.
+/* .IP "\fBmax_idle (100s)\fR"
+/* The maximum amount of time that an idle Postfix daemon process waits
+/* for an incoming connection before terminating voluntarily.
+/* .IP "\fBmax_use (100)\fR"
+/* The maximal number of incoming connections that a Postfix daemon
+/* process will service before terminating voluntarily.
+/* .IP "\fBnotify_classes (resource, software)\fR"
+/* The list of error classes that are reported to the postmaster.
+/* .IP "\fBprocess_id (read-only)\fR"
+/* The process ID of a Postfix command or daemon process.
+/* .IP "\fBprocess_name (read-only)\fR"
+/* The process name of a Postfix command or daemon process.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .IP "\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.0 and later:
+/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR"
+/* Detect that a message requires SMTPUTF8 support for the specified
+/* mail origin classes.
+/* .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.6 and later:
+/* .IP "\fBenable_threaded_bounces (no)\fR"
+/* Enable non-delivery, success, and delay notifications that link
+/* to the original message by including a References: and In-Reply-To:
+/* header with the original Message-ID value.
+/* .PP
+/* Available in Postfix 3.7 and later:
+/* .IP "\fBheader_from_format (standard)\fR"
+/* The format of the Postfix-generated \fBFrom:\fR header.
+/* FILES
+/* /var/spool/postfix/bounce/* non-delivery records
+/* /var/spool/postfix/defer/* non-delivery records
+/* /var/spool/postfix/trace/* delivery status records
+/* SEE ALSO
+/* bounce(5), bounce message template format
+/* qmgr(8), queue manager
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* 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 <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <load_file.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_queue.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_conf.h>
+#include <bounce.h>
+#include <mail_addr.h>
+#include <rcpt_buf.h>
+#include <dsb_scan.h>
+#include <hfrom_format.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include <bounce_service.h>
+
+ /*
+ * Tunables.
+ */
+int var_bounce_limit;
+int var_max_queue_time;
+int var_delay_warn_time;
+char *var_notify_classes;
+char *var_bounce_rcpt;
+char *var_2bounce_rcpt;
+char *var_delay_rcpt;
+char *var_bounce_tmpl;
+bool var_threaded_bounce;
+char *var_hfrom_format; /* header_from_format */
+
+ /*
+ * We're single threaded, so we can avoid some memory allocation overhead.
+ */
+static VSTRING *queue_id;
+static VSTRING *queue_name;
+static RCPT_BUF *rcpt_buf;
+static VSTRING *encoding;
+static VSTRING *sender;
+static VSTRING *dsn_envid;
+static VSTRING *verp_delims;
+static DSN_BUF *dsn_buf;
+
+ /*
+ * Templates.
+ */
+BOUNCE_TEMPLATES *bounce_templates;
+
+ /*
+ * From: header format.
+ */
+int bounce_hfrom_format;
+
+#define STR vstring_str
+
+#define VS_NEUTER(s) printable(vstring_str(s), '?')
+
+/* bounce_append_proto - bounce_append server protocol */
+
+static int bounce_append_proto(char *service_name, VSTREAM *client)
+{
+ const char *myname = "bounce_append_proto";
+ int flags;
+
+ /*
+ * Read and validate the client request.
+ */
+ if (mail_command_server(client,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_FUNC(rcpb_scan, (void *) rcpt_buf),
+ RECV_ATTR_FUNC(dsb_scan, (void *) dsn_buf),
+ ATTR_TYPE_END) != 4) {
+ msg_warn("malformed request");
+ return (-1);
+ }
+
+ /*
+ * Sanitize input.
+ */
+ if (mail_queue_id_ok(STR(queue_id)) == 0) {
+ msg_warn("malformed queue id: %s", printable(STR(queue_id), '?'));
+ return (-1);
+ }
+ VS_NEUTER(rcpt_buf->address);
+ VS_NEUTER(rcpt_buf->orig_addr);
+ VS_NEUTER(rcpt_buf->dsn_orcpt);
+ VS_NEUTER(dsn_buf->status);
+ VS_NEUTER(dsn_buf->action);
+ VS_NEUTER(dsn_buf->reason);
+ VS_NEUTER(dsn_buf->dtype);
+ VS_NEUTER(dsn_buf->dtext);
+ VS_NEUTER(dsn_buf->mtype);
+ VS_NEUTER(dsn_buf->mname);
+ (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf);
+ (void) DSN_FROM_DSN_BUF(dsn_buf);
+
+ /*
+ * Beware: some DSN or RECIPIENT fields may be null; access dsn_buf and
+ * rcpt_buf buffers instead. See DSN_FROM_DSN_BUF() and
+ * RECIPIENT_FROM_RCPT_BUF().
+ */
+ if (msg_verbose)
+ msg_info("%s: flags=0x%x service=%s id=%s org_to=%s to=%s off=%ld dsn_org=%s, notif=0x%x stat=%s act=%s why=%s",
+ myname, flags, service_name, STR(queue_id),
+ STR(rcpt_buf->orig_addr), STR(rcpt_buf->address),
+ rcpt_buf->offset, STR(rcpt_buf->dsn_orcpt),
+ rcpt_buf->dsn_notify, STR(dsn_buf->status),
+ STR(dsn_buf->action), STR(dsn_buf->reason));
+
+ /*
+ * On request by the client, set up a trap to delete the log file in case
+ * of errors.
+ */
+ if (flags & BOUNCE_FLAG_CLEAN)
+ bounce_cleanup_register(service_name, STR(queue_id));
+
+ /*
+ * Execute the request.
+ */
+ return (bounce_append_service(flags, service_name, STR(queue_id),
+ &rcpt_buf->rcpt, &dsn_buf->dsn));
+}
+
+/* bounce_notify_proto - bounce_notify server protocol */
+
+static int bounce_notify_proto(char *service_name, VSTREAM *client,
+ int (*service) (int, char *, char *, char *,
+ char *, int, char *, char *, int,
+ BOUNCE_TEMPLATES *))
+{
+ const char *myname = "bounce_notify_proto";
+ int flags;
+ int smtputf8;
+ int dsn_ret;
+
+ /*
+ * Read and validate the client request.
+ */
+ if (mail_command_server(client,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret),
+ ATTR_TYPE_END) != 8) {
+ msg_warn("malformed request");
+ return (-1);
+ }
+
+ /*
+ * Sanitize input.
+ */
+ if (mail_queue_name_ok(STR(queue_name)) == 0) {
+ msg_warn("malformed queue name: %s", printable(STR(queue_name), '?'));
+ return (-1);
+ }
+ if (mail_queue_id_ok(STR(queue_id)) == 0) {
+ msg_warn("malformed queue id: %s", printable(STR(queue_id), '?'));
+ return (-1);
+ }
+ VS_NEUTER(encoding);
+ VS_NEUTER(sender);
+ VS_NEUTER(dsn_envid);
+ if (msg_verbose)
+ msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s ret=0x%x",
+ myname, flags, service_name, STR(queue_name), STR(queue_id),
+ STR(encoding), smtputf8, STR(sender), STR(dsn_envid),
+ dsn_ret);
+
+ /*
+ * On request by the client, set up a trap to delete the log file in case
+ * of errors.
+ */
+ if (flags & BOUNCE_FLAG_CLEAN)
+ bounce_cleanup_register(service_name, STR(queue_id));
+
+ /*
+ * Execute the request.
+ */
+ return (service(flags, service_name, STR(queue_name),
+ STR(queue_id), STR(encoding), smtputf8,
+ STR(sender), STR(dsn_envid), dsn_ret,
+ bounce_templates));
+}
+
+/* bounce_verp_proto - bounce_notify server protocol, VERP style */
+
+static int bounce_verp_proto(char *service_name, VSTREAM *client)
+{
+ const char *myname = "bounce_verp_proto";
+ int flags;
+ int smtputf8;
+ int dsn_ret;
+
+ /*
+ * Read and validate the client request.
+ */
+ if (mail_command_server(client,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret),
+ RECV_ATTR_STR(MAIL_ATTR_VERPDL, verp_delims),
+ ATTR_TYPE_END) != 9) {
+ msg_warn("malformed request");
+ return (-1);
+ }
+
+ /*
+ * Sanitize input.
+ */
+ if (mail_queue_name_ok(STR(queue_name)) == 0) {
+ msg_warn("malformed queue name: %s", printable(STR(queue_name), '?'));
+ return (-1);
+ }
+ if (mail_queue_id_ok(STR(queue_id)) == 0) {
+ msg_warn("malformed queue id: %s", printable(STR(queue_id), '?'));
+ return (-1);
+ }
+ VS_NEUTER(encoding);
+ VS_NEUTER(sender);
+ VS_NEUTER(dsn_envid);
+ VS_NEUTER(verp_delims);
+ if (strlen(STR(verp_delims)) != 2) {
+ msg_warn("malformed verp delimiter string: %s", STR(verp_delims));
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s ret=0x%x delim=%s",
+ myname, flags, service_name, STR(queue_name),
+ STR(queue_id), STR(encoding), smtputf8, STR(sender),
+ STR(dsn_envid), dsn_ret, STR(verp_delims));
+
+ /*
+ * On request by the client, set up a trap to delete the log file in case
+ * of errors.
+ */
+ if (flags & BOUNCE_FLAG_CLEAN)
+ bounce_cleanup_register(service_name, STR(queue_id));
+
+ /*
+ * Execute the request. Fall back to traditional notification if a bounce
+ * was returned as undeliverable, because we don't want to VERPify those.
+ */
+ if (!*STR(sender) || !strcasecmp_utf8(STR(sender),
+ mail_addr_double_bounce())) {
+ msg_warn("request to send VERP-style notification of bounced mail");
+ return (bounce_notify_service(flags, service_name, STR(queue_name),
+ STR(queue_id), STR(encoding), smtputf8,
+ STR(sender), STR(dsn_envid), dsn_ret,
+ bounce_templates));
+ } else
+ return (bounce_notify_verp(flags, service_name, STR(queue_name),
+ STR(queue_id), STR(encoding), smtputf8,
+ STR(sender), STR(dsn_envid), dsn_ret,
+ STR(verp_delims), bounce_templates));
+}
+
+/* bounce_one_proto - bounce_one server protocol */
+
+static int bounce_one_proto(char *service_name, VSTREAM *client)
+{
+ const char *myname = "bounce_one_proto";
+ int flags;
+ int smtputf8;
+ int dsn_ret;
+
+ /*
+ * Read and validate the client request.
+ */
+ if (mail_command_server(client,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
+ RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
+ RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8),
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
+ RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret),
+ RECV_ATTR_FUNC(rcpb_scan, (void *) rcpt_buf),
+ RECV_ATTR_FUNC(dsb_scan, (void *) dsn_buf),
+ ATTR_TYPE_END) != 10) {
+ msg_warn("malformed request");
+ return (-1);
+ }
+
+ /*
+ * Sanitize input.
+ */
+ if (strcmp(service_name, MAIL_SERVICE_BOUNCE) != 0) {
+ msg_warn("wrong service name \"%s\" for one-recipient bouncing",
+ service_name);
+ return (-1);
+ }
+ if (mail_queue_name_ok(STR(queue_name)) == 0) {
+ msg_warn("malformed queue name: %s", printable(STR(queue_name), '?'));
+ return (-1);
+ }
+ if (mail_queue_id_ok(STR(queue_id)) == 0) {
+ msg_warn("malformed queue id: %s", printable(STR(queue_id), '?'));
+ return (-1);
+ }
+ VS_NEUTER(encoding);
+ VS_NEUTER(sender);
+ VS_NEUTER(dsn_envid);
+ VS_NEUTER(rcpt_buf->address);
+ VS_NEUTER(rcpt_buf->orig_addr);
+ VS_NEUTER(rcpt_buf->dsn_orcpt);
+ VS_NEUTER(dsn_buf->status);
+ VS_NEUTER(dsn_buf->action);
+ VS_NEUTER(dsn_buf->reason);
+ VS_NEUTER(dsn_buf->dtype);
+ VS_NEUTER(dsn_buf->dtext);
+ VS_NEUTER(dsn_buf->mtype);
+ VS_NEUTER(dsn_buf->mname);
+ (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf);
+ (void) DSN_FROM_DSN_BUF(dsn_buf);
+
+ /*
+ * Beware: some DSN or RECIPIENT fields may be null; access dsn_buf and
+ * rcpt_buf buffers instead. See DSN_FROM_DSN_BUF() and
+ * RECIPIENT_FROM_RCPT_BUF().
+ */
+ if (msg_verbose)
+ msg_info("%s: flags=0x%x queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s dsn_ret=0x%x orig_to=%s to=%s off=%ld dsn_orig=%s notif=0x%x stat=%s act=%s why=%s",
+ myname, flags, STR(queue_name), STR(queue_id),
+ STR(encoding), smtputf8, STR(sender), STR(dsn_envid),
+ dsn_ret, STR(rcpt_buf->orig_addr), STR(rcpt_buf->address),
+ rcpt_buf->offset, STR(rcpt_buf->dsn_orcpt),
+ rcpt_buf->dsn_notify, STR(dsn_buf->status),
+ STR(dsn_buf->action), STR(dsn_buf->reason));
+
+ /*
+ * Execute the request.
+ */
+ return (bounce_one_service(flags, STR(queue_name), STR(queue_id),
+ STR(encoding), smtputf8, STR(sender),
+ STR(dsn_envid), dsn_ret, rcpt_buf,
+ dsn_buf, bounce_templates));
+}
+
+/* bounce_service - parse bounce command type and delegate */
+
+static void bounce_service(VSTREAM *client, char *service_name, char **argv)
+{
+ int command;
+ int status;
+
+ /*
+ * Sanity check. This service takes no command-line arguments. The
+ * service name should be usable as a subdirectory name.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+ if (mail_queue_name_ok(service_name) == 0)
+ msg_fatal("malformed service name: %s", service_name);
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(client, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_BOUNCE),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(client);
+
+ /*
+ * Read and validate the first parameter of the client request. Let the
+ * request-specific protocol routines take care of the remainder.
+ */
+ if (attr_scan(client, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(MAIL_ATTR_NREQ, &command), 0) != 1) {
+ msg_warn("malformed request");
+ status = -1;
+ } else if (command == BOUNCE_CMD_VERP) {
+ status = bounce_verp_proto(service_name, client);
+ } else if (command == BOUNCE_CMD_FLUSH) {
+ status = bounce_notify_proto(service_name, client,
+ bounce_notify_service);
+ } else if (command == BOUNCE_CMD_WARN) {
+ status = bounce_notify_proto(service_name, client,
+ bounce_warn_service);
+ } else if (command == BOUNCE_CMD_TRACE) {
+ status = bounce_notify_proto(service_name, client,
+ bounce_trace_service);
+ } else if (command == BOUNCE_CMD_APPEND) {
+ status = bounce_append_proto(service_name, client);
+ } else if (command == BOUNCE_CMD_ONE) {
+ status = bounce_one_proto(service_name, client);
+ } else {
+ msg_warn("unknown command: %d", command);
+ status = -1;
+ }
+
+ /*
+ * When the request has completed, send the completion status to the
+ * client.
+ */
+ attr_print(client, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ ATTR_TYPE_END);
+ vstream_fflush(client);
+
+ /*
+ * When a cleanup trap was set, delete the log file in case of error.
+ * This includes errors while sending the completion status to the
+ * client.
+ */
+ if (bounce_cleanup_path) {
+ if (status || vstream_ferror(client))
+ bounce_cleanup_log();
+ bounce_cleanup_unregister();
+ }
+}
+
+static void load_helper(VSTREAM *stream, void *context)
+{
+ BOUNCE_TEMPLATES *templates = (BOUNCE_TEMPLATES *) context;
+
+ bounce_templates_load(stream, templates);
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Bundle up a bunch of bounce template information.
+ */
+ bounce_templates = bounce_templates_create();
+
+ /*
+ * Load the alternate message files (if specified) before entering the
+ * chroot jail.
+ */
+ if (*var_bounce_tmpl)
+ load_file(var_bounce_tmpl, load_helper, (void *) bounce_templates);
+}
+
+/* post_jail_init - initialize after entering chroot jail */
+
+static void post_jail_init(char *service_name, char **unused_argv)
+{
+ bounce_hfrom_format = hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format);
+
+ /*
+ * Special case: dump bounce templates. This is not part of the master(5)
+ * public interface. This internal interface is used by the postconf
+ * command. It was implemented before bounce templates were isolated into
+ * modules that could have been called directly.
+ */
+ if (strcmp(service_name, "dump_templates") == 0) {
+ bounce_templates_dump(VSTREAM_OUT, bounce_templates);
+ vstream_fflush(VSTREAM_OUT);
+ exit(0);
+ }
+ if (strcmp(service_name, "expand_templates") == 0) {
+ bounce_templates_expand(VSTREAM_OUT, bounce_templates);
+ vstream_fflush(VSTREAM_OUT);
+ exit(0);
+ }
+
+ /*
+ * Initialize. We're single threaded so we can reuse some memory upon
+ * successive requests.
+ */
+ queue_id = vstring_alloc(10);
+ queue_name = vstring_alloc(10);
+ rcpt_buf = rcpb_create();
+ encoding = vstring_alloc(10);
+ sender = vstring_alloc(10);
+ dsn_envid = vstring_alloc(10);
+ verp_delims = vstring_alloc(10);
+ dsn_buf = dsb_create();
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_BOUNCE_LIMIT, DEF_BOUNCE_LIMIT, &var_bounce_limit, 1, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000,
+ VAR_DELAY_WARN_TIME, DEF_DELAY_WARN_TIME, &var_delay_warn_time, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_BOUNCE_RCPT, DEF_BOUNCE_RCPT, &var_bounce_rcpt, 1, 0,
+ VAR_2BOUNCE_RCPT, DEF_2BOUNCE_RCPT, &var_2bounce_rcpt, 1, 0,
+ VAR_DELAY_RCPT, DEF_DELAY_RCPT, &var_delay_rcpt, 1, 0,
+ VAR_BOUNCE_TMPL, DEF_BOUNCE_TMPL, &var_bounce_tmpl, 0, 0,
+ VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_THREADED_BOUNCE, DEF_THREADED_BOUNCE, &var_threaded_bounce,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Pass control to the single-threaded service skeleton.
+ */
+ single_server_main(argc, argv, bounce_service,
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_TIME_TABLE(time_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_UNLIMITED,
+ 0);
+}
diff --git a/src/bounce/bounce_append_service.c b/src/bounce/bounce_append_service.c
new file mode 100644
index 0000000..c3cea0b
--- /dev/null
+++ b/src/bounce/bounce_append_service.c
@@ -0,0 +1,168 @@
+/*++
+/* NAME
+/* bounce_append_service 3
+/* SUMMARY
+/* append record to bounce log, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_append_service(flags, service, queue_id, rcpt, dsn),
+/* int flags;
+/* char *service;
+/* char *queue_id;
+/* RECIPIENT *rcpt;
+/* DSN *dsn;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_append()
+/* (append bounce log) request. This routine either succeeds or
+/* it raises a fatal error.
+/* DIAGNOSTICS
+/* Fatal errors: all file access errors; memory allocation errors.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <quote_822_local.h>
+#include <deliver_flock.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+/* bounce_append_service - append bounce log */
+
+int bounce_append_service(int unused_flags, char *service, char *queue_id,
+ RECIPIENT *rcpt, DSN *dsn)
+{
+ VSTRING *in_buf = vstring_alloc(100);
+ VSTREAM *log;
+ long orig_length;
+
+ /*
+ * This code is paranoid for a good reason. Once the bounce service takes
+ * responsibility, the mail system will make no further attempts to
+ * deliver this recipient. Whenever file access fails, assume that the
+ * system is under stress or that something has been mis-configured, and
+ * force a backoff by raising a fatal run-time error.
+ */
+ log = mail_queue_open(service, queue_id,
+ O_WRONLY | O_APPEND | O_CREAT, 0600);
+ if (log == 0)
+ msg_fatal("open file %s %s: %m", service, queue_id);
+
+ /*
+ * Lock out other processes to avoid truncating someone else's data in
+ * case of trouble.
+ */
+ if (deliver_flock(vstream_fileno(log), INTERNAL_LOCK, (VSTRING *) 0) < 0)
+ msg_fatal("lock file %s %s: %m", service, queue_id);
+
+ /*
+ * Now, go for it. Append a record. Truncate the log to the original
+ * length when the append operation fails. We use the plain stream-lf
+ * file format because we do not need anything more complicated. As a
+ * benefit, we can still recover some data when the file is a little
+ * garbled.
+ *
+ * XXX addresses in defer logfiles are in printable quoted form, while
+ * addresses in message envelope records are in raw unquoted form. This
+ * may change once we replace the present ad-hoc bounce/defer logfile
+ * format by one that is transparent for control etc. characters. See
+ * also: showq/showq.c.
+ *
+ * While migrating from old format to new format, allow backwards
+ * compatibility by writing an old-style record before the new-style
+ * records.
+ */
+ if ((orig_length = vstream_fseek(log, 0L, SEEK_END)) < 0)
+ msg_fatal("seek file %s %s: %m", service, queue_id);
+
+#define NOT_NULL_EMPTY(s) ((s) != 0 && *(s) != 0)
+#define STR(x) vstring_str(x)
+
+ vstream_fputs("\n", log);
+ if (var_oldlog_compat) {
+ vstream_fprintf(log, "<%s>: %s\n", *rcpt->address == 0 ? "" :
+ STR(quote_822_local(in_buf, rcpt->address)),
+ dsn->reason);
+ }
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_RECIP, *rcpt->address ?
+ STR(quote_822_local(in_buf, rcpt->address)) : "<>");
+ if (NOT_NULL_EMPTY(rcpt->orig_addr)
+ && strcasecmp_utf8(rcpt->address, rcpt->orig_addr) != 0)
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_ORCPT,
+ STR(quote_822_local(in_buf, rcpt->orig_addr)));
+ if (rcpt->offset > 0)
+ vstream_fprintf(log, "%s=%ld\n", MAIL_ATTR_OFFSET, rcpt->offset);
+ if (NOT_NULL_EMPTY(rcpt->dsn_orcpt))
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ORCPT, rcpt->dsn_orcpt);
+ if (rcpt->dsn_notify != 0)
+ vstream_fprintf(log, "%s=%d\n", MAIL_ATTR_DSN_NOTIFY, rcpt->dsn_notify);
+
+ if (NOT_NULL_EMPTY(dsn->status))
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_STATUS, dsn->status);
+ if (NOT_NULL_EMPTY(dsn->action))
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ACTION, dsn->action);
+ if (NOT_NULL_EMPTY(dsn->dtype) && NOT_NULL_EMPTY(dsn->dtext)) {
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTYPE, dsn->dtype);
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTEXT, dsn->dtext);
+ }
+ if (NOT_NULL_EMPTY(dsn->mtype) && NOT_NULL_EMPTY(dsn->mname)) {
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MTYPE, dsn->mtype);
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MNAME, dsn->mname);
+ }
+ if (NOT_NULL_EMPTY(dsn->reason))
+ vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_WHY, dsn->reason);
+ vstream_fputs("\n", log);
+
+ if (vstream_fflush(log) != 0 || fsync(vstream_fileno(log)) < 0) {
+#ifndef NO_TRUNCATE
+ if (ftruncate(vstream_fileno(log), (off_t) orig_length) < 0)
+ msg_fatal("truncate file %s %s: %m", service, queue_id);
+#endif
+ msg_fatal("append file %s %s: %m", service, queue_id);
+ }
+
+ /*
+ * Darn. If closing the log detects a problem, the only way to undo the
+ * damage is to open the log once more, and to truncate the log to the
+ * original length. But, this could happen only when the log is kept on a
+ * remote file system, and that is not recommended practice anyway.
+ */
+ if (vstream_fclose(log) != 0)
+ msg_warn("append file %s %s: %m", service, queue_id);
+
+ vstring_free(in_buf);
+ return (0);
+}
diff --git a/src/bounce/bounce_cleanup.c b/src/bounce/bounce_cleanup.c
new file mode 100644
index 0000000..9fb900b
--- /dev/null
+++ b/src/bounce/bounce_cleanup.c
@@ -0,0 +1,177 @@
+/*++
+/* NAME
+/* bounce_cleanup 3
+/* SUMMARY
+/* cleanup logfile upon error
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_cleanup_registered()
+/*
+/* void bounce_cleanup_register(queue_id)
+/* char *queue_id;
+/*
+/* void bounce_cleanup_log(void)
+/*
+/* void bounce_cleanup_unregister(void)
+/* DESCRIPTION
+/* This module implements support for deleting the current
+/* bounce logfile in case of errors, and upon the arrival
+/* of a SIGTERM signal (shutdown).
+/*
+/* bounce_cleanup_register() registers a callback routine with the
+/* run-time error handler, for automatic logfile removal in case
+/* of a fatal run-time error.
+/*
+/* bounce_cleanup_unregister() cleans up storage used by
+/* bounce_cleanup_register().
+/*
+/* In-between bounce_cleanup_register() and bounce_cleanup_unregister()
+/* calls, a call of bounce_cleanup_log() will delete the registered
+/* bounce logfile.
+/*
+/* bounce_cleanup_registered() returns non-zero when a cleanup
+/* trap has been set.
+/* DIAGNOSTICS
+/* Fatal error: all file access errors. Panic: nested calls of
+/* bounce_cleanup_register(); any calls of bounce_cleanup_unregister()
+/* or bounce_cleanup_log() without preceding bounce_cleanup_register()
+/* call.
+/* BUGS
+/* SEE ALSO
+/* master(8) process manager
+/* 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 <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+ /*
+ * Support for removing a logfile when an update fails. In order to do this,
+ * we save a copy of the currently-open logfile name, and register a
+ * callback function pointer with the run-time error handler. The saved
+ * pathname is made global so that the application can see whether or not a
+ * trap was set up.
+ */
+static MSG_CLEANUP_FN bounce_cleanup_func; /* saved callback */
+VSTRING *bounce_cleanup_path; /* saved path name */
+
+/* bounce_cleanup_callback - run-time callback to cleanup logfile */
+
+static void bounce_cleanup_callback(void)
+{
+
+ /*
+ * Remove the logfile.
+ */
+ if (bounce_cleanup_path)
+ bounce_cleanup_log();
+
+ /*
+ * Execute the saved cleanup action.
+ */
+ if (bounce_cleanup_func)
+ bounce_cleanup_func();
+}
+
+/* bounce_cleanup_log - clean up the logfile */
+
+void bounce_cleanup_log(void)
+{
+ const char *myname = "bounce_cleanup_log";
+
+ /*
+ * Sanity checks.
+ */
+ if (bounce_cleanup_path == 0)
+ msg_panic("%s: no cleanup context", myname);
+
+ /*
+ * This function may be called before a logfile is created or after it
+ * has been deleted, so do not complain.
+ */
+ (void) unlink(vstring_str(bounce_cleanup_path));
+}
+
+/* bounce_cleanup_sig - signal handler */
+
+static void bounce_cleanup_sig(int sig)
+{
+
+ /*
+ * Running as a signal handler - don't do complicated stuff.
+ */
+ if (bounce_cleanup_path)
+ (void) unlink(vstring_str(bounce_cleanup_path));
+ _exit(sig);
+}
+
+/* bounce_cleanup_register - register logfile to clean up */
+
+void bounce_cleanup_register(char *service, char *queue_id)
+{
+ const char *myname = "bounce_cleanup_register";
+
+ /*
+ * Sanity checks.
+ */
+ if (bounce_cleanup_path)
+ msg_panic("%s: nested call", myname);
+
+ /*
+ * Save a copy of the logfile path, and of the last callback function
+ * pointer registered with the run-time error handler.
+ */
+ bounce_cleanup_path = vstring_alloc(10);
+ (void) mail_queue_path(bounce_cleanup_path, service, queue_id);
+ bounce_cleanup_func = msg_cleanup(bounce_cleanup_callback);
+ signal(SIGTERM, bounce_cleanup_sig);
+}
+
+/* bounce_cleanup_unregister - unregister logfile to clean up */
+
+void bounce_cleanup_unregister(void)
+{
+ const char *myname = "bounce_cleanup_unregister";
+
+ /*
+ * Sanity checks.
+ */
+ if (bounce_cleanup_path == 0)
+ msg_panic("%s: no cleanup context", myname);
+
+ /*
+ * Restore the saved callback function pointer, and release storage for
+ * the saved logfile pathname.
+ */
+ signal(SIGTERM, SIG_DFL);
+ (void) msg_cleanup(bounce_cleanup_func);
+ vstring_free(bounce_cleanup_path);
+ bounce_cleanup_path = 0;
+}
diff --git a/src/bounce/bounce_notify_service.c b/src/bounce/bounce_notify_service.c
new file mode 100644
index 0000000..d6c751f
--- /dev/null
+++ b/src/bounce/bounce_notify_service.c
@@ -0,0 +1,327 @@
+/*++
+/* NAME
+/* bounce_notify_service 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_notify_service(flags, service, queue_name, queue_id,
+/* encoding, smtputf8, sender, dsn_envid,
+/* dsn_ret, templates)
+/* int flags;
+/* char *service;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *sender;
+/* char *dsn_envid;
+/* int dsn_ret;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_flush()
+/* (send bounce message) request.
+/*
+/* When a message bounces, a full copy is sent to the originator,
+/* and an optional copy of the diagnostics with message headers is
+/* sent to the postmaster. The result is non-zero when the operation
+/* should be tried again. Otherwise, the logfile is removed.
+/*
+/* When a bounce is sent, the sender address is the empty
+/* address. When a bounce bounces, an optional double bounce
+/* with the entire undeliverable mail is sent to the postmaster,
+/* with as sender address the double bounce address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <bounce.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_notify_service - send a bounce */
+
+int bounce_notify_service(int flags, char *service, char *queue_name,
+ char *queue_id, char *encoding,
+ int smtputf8, char *recipient,
+ char *dsn_envid, int dsn_ret,
+ BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 1;
+ int postmaster_status = 1;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ VSTRING *new_id = vstring_alloc(10);
+ char *postmaster;
+ int count;
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ *
+ * XXX DSN The bounce service produces RFC 3464-style "failed mail" reports
+ * from information in two following types of logfile:
+ *
+ * 1 - bounce: this file is used for RFC 3464-style reports of permanent
+ * delivery errors by the bounce(8) service. This reports to the sender
+ * all recipients that have no DSN NOTIFY information (compatibility) and
+ * all recipients that have DSN NOTIFY=FAILURE; this reports to
+ * postmaster all recipients, if postmaster notification is enabled.
+ *
+ * 2 - defer: this file is used for three types of report:
+ *
+ * 2a) RFC 3464-style "mail is too old" reports by the bounce(8) service.
+ * This reports to the sender all recipients that have no DSN NOTIFY
+ * information (compatibility) and all recipients that have DSN
+ * NOTIFY=FAILURE; this reports to postmaster all recipients, if
+ * postmaster notification is enabled.
+ *
+ * Other reports that other servers produce from the defer logfile:
+ *
+ * 2b) On-demand reports of all delayed deliveries by the showq(8) service
+ * and mailq(1) command. This reports all recipients that have a
+ * transient delivery error.
+ *
+ * 2c) RFC 3464-style "delayed mail" notifications by the defer(8) service.
+ * This reports to the sender all recipients that have no DSN NOTIFY
+ * information (compatibility) and all recipients that have DSN
+ * NOTIFY=DELAY; this reports to postmaster all recipients, if postmaster
+ * notification is enabled.
+ */
+ bounce_info = bounce_mail_init(service, queue_name, queue_id,
+ encoding, smtputf8, dsn_envid,
+ ts->failure);
+
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * The choice of sender address depends on the recipient address. For a
+ * single bounce (a non-delivery notification to the message originator),
+ * the sender address is the empty string. For a double bounce (typically
+ * a failed single bounce, or a postmaster notification that was produced
+ * by any of the mail processes) the sender address is defined by the
+ * var_double_bounce_sender configuration variable. When a double bounce
+ * cannot be delivered, the queue manager blackholes the resulting triple
+ * bounce message.
+ */
+
+ /*
+ * Double bounce failed. Never send a triple bounce.
+ *
+ * However, this does not prevent double bounces from bouncing on other
+ * systems. In order to cope with this, either the queue manager must
+ * recognize the double-bounce recipient address and discard mail, or
+ * every delivery agent must recognize the double-bounce sender address
+ * and substitute something else so mail does not come back at us.
+ */
+ if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) {
+ msg_warn("%s: undeliverable postmaster notification discarded",
+ queue_id);
+ bounce_status = 0;
+ }
+
+ /*
+ * Single bounce failed. Optionally send a double bounce to postmaster,
+ * subject to notify_classes restrictions.
+ */
+#define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE)
+#define SEND_POSTMASTER_ANY_BOUNCE_NOTICE (notify_mask & ANY_BOUNCE)
+
+ else if (*recipient == 0) {
+ if (!SEND_POSTMASTER_ANY_BOUNCE_NOTICE) {
+ bounce_status = 0;
+ } else {
+ postmaster = var_2bounce_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Double bounce to Postmaster. This is the last opportunity
+ * for this message to be delivered. Send the text with
+ * reason for the bounce, and the headers of the original
+ * message. Don't bother sending the boiler-plate text.
+ */
+ count = -1;
+ if (bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ /* No applicable recipients found - cancel this notice. */
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+ }
+ }
+
+ /*
+ * Non-bounce failed. Send a single bounce to the sender, subject to DSN
+ * NOTIFY restrictions.
+ */
+ else {
+ if ((bounce = post_mail_fopen_nowait(NULL_SENDER, recipient,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Send the bounce message header, some boilerplate text that
+ * pretends that we are a polite mail system, the text with
+ * reason for the bounce, and a copy of the original message.
+ */
+ count = -1;
+ if (bounce_header(bounce, bounce_info, recipient,
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_FAILURE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_FAILURE) > 0) {
+ bounce_original(bounce, bounce_info, dsn_ret ?
+ dsn_ret : DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ /* No applicable recipients found - cancel this notice. */
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+
+ /*
+ * Optionally, send a postmaster notice, subject to notify_classes
+ * restrictions.
+ *
+ * This postmaster notice is not critical, so if it fails don't
+ * retransmit the bounce that we just generated, just log a warning.
+ */
+#define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE)
+
+ if (bounce_status == 0 && SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE
+ && strcasecmp_utf8(recipient, mail_addr_double_bounce()) != 0) {
+
+ /*
+ * Send the text with reason for the bounce, and the headers of
+ * the original message. Don't bother sending the boiler-plate
+ * text. This postmaster notice is not critical, so if it fails
+ * don't retransmit the bounce that we just generated, just log a
+ * warning.
+ */
+ postmaster = var_bounce_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ count = -1;
+ if (bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ postmaster_status = post_mail_fclose(bounce);
+ if (postmaster_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ /* No applicable recipients found - cancel this notice. */
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ postmaster_status = 0;
+ }
+ }
+ if (postmaster_status)
+ msg_warn("%s: postmaster notice failed while bouncing to %s",
+ queue_id, recipient);
+ }
+ }
+
+ /*
+ * Optionally, delete the recipients from the queue file.
+ */
+ if (bounce_status == 0 && (flags & BOUNCE_FLAG_DELRCPT))
+ bounce_delrcpt(bounce_info);
+
+ /*
+ * Examine the completion status. Delete the bounce log file only when
+ * the bounce was posted successfully, and only if we are bouncing for
+ * real, not just warning.
+ */
+ if (bounce_status == 0 && mail_queue_remove(service, queue_id)
+ && errno != ENOENT)
+ msg_fatal("remove %s %s: %m", service, queue_id);
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/bounce_notify_util.c b/src/bounce/bounce_notify_util.c
new file mode 100644
index 0000000..781a525
--- /dev/null
+++ b/src/bounce/bounce_notify_util.c
@@ -0,0 +1,1010 @@
+/*++
+/* NAME
+/* bounce_notify_util 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* typedef struct {
+/* .in +4
+/* /* All private members... */
+/* .in -4
+/* } BOUNCE_INFO;
+/*
+/* BOUNCE_INFO *bounce_mail_init(service, queue_name, queue_id, encoding,
+/* smtputf8, dsn_envid, template)
+/* const char *service;
+/* const char *queue_name;
+/* const char *queue_id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *dsn_envid;
+/* const BOUNCE_TEMPLATE *template;
+/*
+/* BOUNCE_INFO *bounce_mail_one_init(queue_name, queue_id, encoding,
+/* smtputf8, dsn_envid, dsn_notify,
+/* rcpt_buf, dsn_buf, template)
+/* const char *queue_name;
+/* const char *queue_id;
+/* const char *encoding;
+/* int smtputf8;
+/* int dsn_notify;
+/* const char *dsn_envid;
+/* RCPT_BUF *rcpt_buf;
+/* DSN_BUF *dsn_buf;
+/* const BOUNCE_TEMPLATE *template;
+/*
+/* void bounce_mail_free(bounce_info)
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_header(fp, bounce_info, recipient, postmaster_copy)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* const char *recipient;
+/* int postmaster_copy;
+/*
+/* int bounce_boilerplate(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_recipient_log(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_diagnostic_log(fp, bounce_info, notify_filter)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* int notify_filter;
+/*
+/* int bounce_header_dsn(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_recipient_dsn(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_diagnostic_dsn(fp, bounce_info, notify_filter)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* int notify_filter;
+/*
+/* int bounce_original(fp, bounce_info, headers_only)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* int headers_only;
+/*
+/* void bounce_delrcpt(bounce_info)
+/* BOUNCE_INFO *bounce_info;
+/*
+/* void bounce_delrcpt_one(bounce_info)
+/* BOUNCE_INFO *bounce_info;
+/* DESCRIPTION
+/* This module implements the grunt work of sending a non-delivery
+/* notification. A bounce is sent in a form that satisfies RFC 1894
+/* (delivery status notifications).
+/*
+/* bounce_mail_init() bundles up its argument and attempts to
+/* open the corresponding logfile and message file. A BOUNCE_INFO
+/* structure contains all the necessary information about an
+/* undeliverable message.
+/*
+/* bounce_mail_one_init() provides the same function for only
+/* one recipient that is not read from bounce logfile.
+/*
+/* bounce_mail_free() releases memory allocated by bounce_mail_init()
+/* and closes any files opened by bounce_mail_init().
+/*
+/* bounce_header() produces a standard mail header with the specified
+/* recipient and starts a text/plain message segment for the
+/* human-readable problem description. postmaster_copy is either
+/* POSTMASTER_COPY or NO_POSTMASTER_COPY.
+/*
+/* bounce_boilerplate() produces the standard "sorry" text that
+/* creates the illusion that mail systems are civilized.
+/*
+/* bounce_recipient_log() sends a human-readable representation of
+/* logfile information for one recipient, with the recipient address
+/* and with the text why the recipient was undeliverable.
+/*
+/* bounce_diagnostic_log() sends a human-readable representation of
+/* logfile information for all undeliverable recipients. The
+/* notify_filter specifies what recipient status records should be
+/* reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
+/* In the absence of DSN NOTIFY information all records are reported.
+/* The result value is -1 in case of error, the number of reported
+/* recipients in case of success.
+/*
+/* bounce_header_dsn() starts a message/delivery-status message
+/* segment and sends the machine-readable information that identifies
+/* the reporting MTA.
+/*
+/* bounce_recipient_dsn() sends a machine-readable representation of
+/* logfile information for one recipient, with the recipient address
+/* and with the text why the recipient was undeliverable.
+/*
+/* bounce_diagnostic_dsn() sends a machine-readable representation of
+/* logfile information for all undeliverable recipients. The
+/* notify_filter specifies what recipient status records should be
+/* reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
+/* In the absence of DSN NOTIFY information all records are reported.
+/* The result value is -1 in case of error, the number of reported
+/* recipients in case of success.
+/*
+/* bounce_original() starts a message/rfc822 or text/rfc822-headers
+/* message segment and sends the original message, either full
+/* (DSN_RET_FULL) or message headers only (DSN_RET_HDRS).
+/*
+/* bounce_delrcpt() deletes recipients in the logfile from the original
+/* queue file.
+/*
+/* bounce_delrcpt_one() deletes one recipient from the original
+/* queue file.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h> /* sscanf() */
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <line_wrap.h>
+#include <stringops.h>
+#include <myflock.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <quote_822_local.h>
+#include <mail_params.h>
+#include <is_header.h>
+#include <record.h>
+#include <rec_type.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <bounce_log.h>
+#include <mail_date.h>
+#include <mail_proto.h>
+#include <lex_822.h>
+#include <deliver_completed.h>
+#include <dsn_mask.h>
+#include <smtputf8.h>
+#include <header_opts.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* bounce_mail_alloc - initialize */
+
+static BOUNCE_INFO *bounce_mail_alloc(const char *service,
+ const char *queue_name,
+ const char *queue_id,
+ const char *encoding,
+ int smtputf8,
+ const char *dsn_envid,
+ RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf,
+ BOUNCE_TEMPLATE *template,
+ BOUNCE_LOG *log_handle)
+{
+ BOUNCE_INFO *bounce_info;
+ int rec_type;
+ int prev_type;
+ int all_headers_seen = 0;
+ int skip_message_segment = 0;
+ int in_envelope = 1;
+
+ /*
+ * Bundle up a bunch of parameters and initialize information that will
+ * be discovered on the fly.
+ *
+ * XXX Instead of overriding the returned-message MIME encoding, separate
+ * the returned-message MIME encoding from the (boiler plate, delivery
+ * status) MIME encoding.
+ */
+ bounce_info = (BOUNCE_INFO *) mymalloc(sizeof(*bounce_info));
+ bounce_info->service = service;
+ bounce_info->queue_name = queue_name;
+ bounce_info->queue_id = queue_id;
+ bounce_info->smtputf8 = smtputf8;
+ /* Fix 20140708: override MIME encoding: addresses may be 8bit. */
+ /* Fix 20140718: override MIME encoding: 8bit $myhostname expansion. */
+ if (var_smtputf8_enable /* was: bounce_info->smtputf8 */ ) {
+ bounce_info->mime_encoding = "8bit";
+ } else if (strcmp(encoding, MAIL_ATTR_ENC_8BIT) == 0) {
+ bounce_info->mime_encoding = "8bit";
+ } else if (strcmp(encoding, MAIL_ATTR_ENC_7BIT) == 0) {
+ bounce_info->mime_encoding = "7bit";
+ } else {
+ if (strcmp(encoding, MAIL_ATTR_ENC_NONE) != 0)
+ msg_warn("%s: unknown encoding: %.200s",
+ bounce_info->queue_id, encoding);
+ bounce_info->mime_encoding = 0;
+ }
+ if (dsn_envid && *dsn_envid)
+ bounce_info->dsn_envid = dsn_envid;
+ else
+ bounce_info->dsn_envid = 0;
+ bounce_info->template = template;
+ bounce_info->buf = vstring_alloc(100);
+ bounce_info->sender = vstring_alloc(100);
+ bounce_info->arrival_time = 0;
+ bounce_info->orig_offs = 0;
+ bounce_info->message_size = 0;
+ bounce_info->orig_msgid = vstring_alloc(100);
+ bounce_info->rcpt_buf = rcpt_buf;
+ bounce_info->dsn_buf = dsn_buf;
+ bounce_info->log_handle = log_handle;
+
+ /*
+ * RFC 1894: diagnostic-type is an RFC 822 atom. We use X-$mail_name and
+ * must ensure it is valid.
+ */
+ bounce_info->mail_name = mystrdup(var_mail_name);
+ translit(bounce_info->mail_name, " \t\r\n()<>@,;:\\\".[]",
+ "-----------------");
+
+ /*
+ * Compute a supposedly unique boundary string. This assumes that a queue
+ * ID and a hostname contain acceptable characters for a boundary string,
+ * but the assumption is not verified.
+ */
+ vstring_sprintf(bounce_info->buf, "%s.%lu/%s",
+ queue_id, (unsigned long) event_time(), var_myhostname);
+ bounce_info->mime_boundary = mystrdup(STR(bounce_info->buf));
+
+ /*
+ * If the original message cannot be found, do not raise a run-time
+ * error. There is nothing we can do about the error, and all we are
+ * doing is to inform the sender of a delivery problem. Bouncing a
+ * message does not have to be a perfect job. But if the system IS
+ * running out of resources, raise a fatal run-time error and force a
+ * backoff.
+ */
+ if ((bounce_info->orig_fp = mail_queue_open(queue_name, queue_id,
+ O_RDWR, 0)) == 0
+ && errno != ENOENT)
+ msg_fatal("open %s %s: %m", service, queue_id);
+
+ /*
+ * Get time/size/sender information from the original message envelope
+ * records. If the envelope is corrupted just send whatever we can
+ * (remember this is a best effort, it does not have to be perfect).
+ *
+ * Lock the file for shared use, so that queue manager leaves it alone after
+ * restarting.
+ */
+#define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT)
+
+ if (bounce_info->orig_fp != 0) {
+ if (myflock(vstream_fileno(bounce_info->orig_fp), INTERNAL_LOCK,
+ DELIVER_LOCK_MODE) < 0)
+ msg_fatal("cannot get shared lock on %s: %m",
+ VSTREAM_PATH(bounce_info->orig_fp));
+ for (prev_type = 0;
+ (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0;
+ prev_type = rec_type) {
+
+ /*
+ * Postfix version dependent: data offset in SIZE record.
+ */
+ if (rec_type == REC_TYPE_SIZE) {
+ if (bounce_info->message_size == 0)
+ sscanf(STR(bounce_info->buf), "%ld %ld",
+ &bounce_info->message_size,
+ &bounce_info->orig_offs);
+ if (bounce_info->message_size < 0)
+ bounce_info->message_size = 0;
+ if (bounce_info->orig_offs < 0)
+ bounce_info->orig_offs = 0;
+ }
+
+ /*
+ * Information for the Arrival-Date: attribute.
+ */
+ else if (rec_type == REC_TYPE_TIME) {
+ if (bounce_info->arrival_time == 0
+ && (bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0)
+ bounce_info->arrival_time = 0;
+ }
+
+ /*
+ * Information for the X-Postfix-Sender: attribute.
+ */
+ else if (rec_type == REC_TYPE_FROM) {
+ quote_822_local_flags(bounce_info->sender,
+ VSTRING_LEN(bounce_info->buf) ?
+ STR(bounce_info->buf) :
+ mail_addr_mail_daemon(), 0);
+ }
+
+ /*
+ * Backwards compatibility: no data offset in SIZE record.
+ */
+ else if (rec_type == REC_TYPE_MESG) {
+ /* XXX Future: sender+recipient after message content. */
+ if (VSTRING_LEN(bounce_info->sender) == 0)
+ msg_warn("%s: no sender before message content record",
+ bounce_info->queue_id);
+ bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp);
+ if (var_threaded_bounce == 0)
+ skip_message_segment = 1;
+ else
+ in_envelope = 0;
+ }
+
+ /*
+ * Extract Message-ID for threaded bounces.
+ */
+ else if (in_envelope == 0
+ && (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT)) {
+ const HEADER_OPTS *hdr;
+ char *cp;
+
+ /*
+ * Skip records that we cannot use. Degrade if we could not
+ * skip over the message content.
+ */
+ if (var_threaded_bounce == 0 || all_headers_seen
+ || prev_type == REC_TYPE_CONT) {
+ /* void */ ;
+ }
+
+ /*
+ * Extract message-id header value.
+ */
+ else if (is_header(STR(bounce_info->buf))) {
+ if ((hdr = header_opts_find(
+ vstring_str(bounce_info->buf))) != 0
+ && hdr->type == HDR_MESSAGE_ID) {
+ vstring_truncate(bounce_info->buf,
+ trimblanks(STR(bounce_info->buf),
+ LEN(bounce_info->buf))
+ - STR(bounce_info->buf));
+ cp = STR(bounce_info->buf) + strlen(hdr->name) + 1;
+ while (ISSPACE(*cp))
+ cp++;
+ if (*cp == '<' && vstring_end(bounce_info->buf)[-1] == '>')
+ vstring_strcpy(bounce_info->orig_msgid, cp);
+ else
+ msg_warn("%s: ignoring malformed Message-ID",
+ bounce_info->queue_id);
+ }
+ }
+
+ /*
+ * Skip remainder of multiline header.
+ */
+ else if (ISSPACE(*STR(bounce_info->buf))) {
+ /* void */ ;
+ }
+
+ /*
+ * Start of body.
+ */
+ else {
+ all_headers_seen = 1;
+ skip_message_segment = 1;
+ }
+ }
+
+ /*
+ * In case we ever want to process records from the extracted
+ * segment, and in case there was no "start of body" event.
+ */
+ else if (rec_type == REC_TYPE_XTRA) {
+ if (VSTRING_LEN(bounce_info->orig_msgid) == 0)
+ if (var_threaded_bounce)
+ all_headers_seen = 1;
+ in_envelope = 1;
+ }
+
+ /*
+ * Are we done yet?
+ */
+ if (bounce_info->orig_offs > 0
+ && bounce_info->arrival_time > 0
+ && VSTRING_LEN(bounce_info->sender) > 0
+ && (var_threaded_bounce == 0 || all_headers_seen
+ || VSTRING_LEN(bounce_info->orig_msgid) > 0)) {
+ break;
+ }
+
+ /*
+ * Skip over (the remainder of) the message segment. If that
+ * fails, degrade.
+ */
+ if (skip_message_segment) {
+ if (vstream_fseek(bounce_info->orig_fp,
+ bounce_info->orig_offs +
+ bounce_info->message_size,
+ SEEK_SET) < 0)
+ /* void */ ;
+ skip_message_segment = 0;
+ }
+ }
+ }
+ return (bounce_info);
+}
+
+/* bounce_mail_init - initialize */
+
+BOUNCE_INFO *bounce_mail_init(const char *service,
+ const char *queue_name,
+ const char *queue_id,
+ const char *encoding,
+ int smtputf8,
+ const char *dsn_envid,
+ BOUNCE_TEMPLATE *template)
+{
+ BOUNCE_INFO *bounce_info;
+ BOUNCE_LOG *log_handle;
+ RCPT_BUF *rcpt_buf;
+ DSN_BUF *dsn_buf;
+
+ /*
+ * Initialize the bounce_info structure. If the bounce log cannot be
+ * found, do not raise a fatal run-time error. There is nothing we can do
+ * about the error, and all we are doing is to inform the sender of a
+ * delivery problem, Bouncing a message does not have to be a perfect
+ * job. But if the system IS running out of resources, raise a fatal
+ * run-time error and force a backoff.
+ */
+ if ((log_handle = bounce_log_open(service, queue_id, O_RDONLY, 0)) == 0) {
+ if (errno != ENOENT)
+ msg_fatal("open %s %s: %m", service, queue_id);
+ rcpt_buf = 0;
+ dsn_buf = 0;
+ } else {
+ rcpt_buf = rcpb_create();
+ dsn_buf = dsb_create();
+ }
+ bounce_info = bounce_mail_alloc(service, queue_name, queue_id, encoding,
+ smtputf8, dsn_envid, rcpt_buf, dsn_buf,
+ template, log_handle);
+ return (bounce_info);
+}
+
+/* bounce_mail_one_init - initialize */
+
+BOUNCE_INFO *bounce_mail_one_init(const char *queue_name,
+ const char *queue_id,
+ const char *encoding,
+ int smtputf8,
+ const char *dsn_envid,
+ RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf,
+ BOUNCE_TEMPLATE *template)
+{
+ BOUNCE_INFO *bounce_info;
+
+ /*
+ * Initialize the bounce_info structure for just one recipient.
+ */
+ bounce_info = bounce_mail_alloc("none", queue_name, queue_id, encoding,
+ smtputf8, dsn_envid, rcpt_buf, dsn_buf,
+ template, (BOUNCE_LOG *) 0);
+ return (bounce_info);
+}
+
+/* bounce_mail_free - undo bounce_mail_init */
+
+void bounce_mail_free(BOUNCE_INFO *bounce_info)
+{
+ if (bounce_info->log_handle) {
+ if (bounce_log_close(bounce_info->log_handle))
+ msg_warn("%s: read bounce log %s: %m",
+ bounce_info->queue_id, bounce_info->queue_id);
+ vstring_free(bounce_info->orig_msgid);
+ rcpb_free(bounce_info->rcpt_buf);
+ dsb_free(bounce_info->dsn_buf);
+ }
+ if (bounce_info->orig_fp && vstream_fclose(bounce_info->orig_fp))
+ msg_warn("%s: read message file %s %s: %m",
+ bounce_info->queue_id, bounce_info->queue_name,
+ bounce_info->queue_id);
+ vstring_free(bounce_info->buf);
+ vstring_free(bounce_info->sender);
+ myfree(bounce_info->mail_name);
+ myfree((void *) bounce_info->mime_boundary);
+ myfree((void *) bounce_info);
+}
+
+/* bounce_header - generate bounce message header */
+
+int bounce_header(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ const char *dest, int postmaster_copy)
+{
+ BOUNCE_TEMPLATE *template = bounce_info->template;
+
+ /*
+ * Print a minimal bounce header. The cleanup service will add other
+ * headers and will make all addresses fully qualified.
+ */
+#define STREQ(a, b) (strcasecmp((a), (b)) == 0)
+#define STRNE(a, b) (strcasecmp((a), (b)) != 0)
+
+ /*
+ * Generic headers.
+ */
+ bounce_template_headers(post_mail_fprintf, bounce, template,
+ STR(quote_822_local(bounce_info->buf, dest)),
+ postmaster_copy);
+
+ /*
+ * References and Reply-To header that references the original message-id
+ * for better threading in MUAs.
+ */
+ if (VSTRING_LEN(bounce_info->orig_msgid) > 0) {
+ post_mail_fprintf(bounce, "References: %s", STR(bounce_info->orig_msgid));
+ post_mail_fprintf(bounce, "In-Reply-To: %s", STR(bounce_info->orig_msgid));
+ }
+
+ /*
+ * Auto-Submitted header, as per RFC 3834.
+ */
+ post_mail_fprintf(bounce, "Auto-Submitted: %s", postmaster_copy ?
+ "auto-generated" : "auto-replied");
+
+ /*
+ * MIME header. Use 8bit encoding when either the bounced message or the
+ * template requires it.
+ */
+ post_mail_fprintf(bounce, "MIME-Version: 1.0");
+ post_mail_fprintf(bounce, "Content-Type: %s; report-type=%s;",
+ "multipart/report", "delivery-status");
+ post_mail_fprintf(bounce, "\tboundary=\"%s\"", bounce_info->mime_boundary);
+ if (bounce_info->mime_encoding)
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ STREQ(bounce_info->mime_encoding, MAIL_ATTR_ENC_7BIT) ?
+ bounce_template_encoding(template) :
+ bounce_info->mime_encoding);
+ post_mail_fputs(bounce, "");
+ post_mail_fputs(bounce, "This is a MIME-encapsulated message.");
+
+ /*
+ * MIME header.
+ */
+#define NOT_US_ASCII(tp) \
+ STRNE(bounce_template_charset(template), "us-ascii")
+
+#define NOT_7BIT_MIME(bp) \
+ (bp->mime_encoding && STRNE(bp->mime_encoding, MAIL_ATTR_ENC_7BIT))
+
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s", "Notification");
+ /* Fix 20140718: UTF-8 address or $myhostname expansion. */
+ post_mail_fprintf(bounce, "Content-Type: %s; charset=%s",
+ "text/plain", NOT_US_ASCII(template) ?
+ bounce_template_charset(template) :
+ NOT_7BIT_MIME(bounce_info) ?
+ "utf-8" : "us-ascii");
+ /* Fix 20140709: addresses may be 8bit. */
+ if (NOT_7BIT_MIME(bounce_info))
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ bounce_info->mime_encoding);
+ post_mail_fputs(bounce, "");
+
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_boilerplate - generate boiler-plate text */
+
+int bounce_boilerplate(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+
+ /*
+ * Print the boiler-plate text.
+ */
+ bounce_template_expand(post_mail_fputs, bounce, bounce_info->template);
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_print - line_wrap callback */
+
+static void bounce_print(const char *str, int len, int indent, void *context)
+{
+ VSTREAM *bounce = (VSTREAM *) context;
+
+ post_mail_fprintf(bounce, "%*s%.*s", indent, "", len, str);
+}
+
+/* bounce_print_wrap - print and wrap a line */
+
+static void bounce_print_wrap(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ const char *format,...)
+{
+ va_list ap;
+
+#define LENGTH 79
+#define INDENT 4
+
+ va_start(ap, format);
+ vstring_vsprintf(bounce_info->buf, format, ap);
+ va_end(ap);
+ line_wrap(STR(bounce_info->buf), LENGTH, INDENT,
+ bounce_print, (void *) bounce);
+}
+
+/* bounce_recipient_log - send one bounce log report entry */
+
+int bounce_recipient_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ DSN *dsn = &bounce_info->dsn_buf->dsn;
+
+ /*
+ * Mask control and non-ASCII characters (done in bounce_log_read()),
+ * wrap long lines and prepend one blank, so this data can safely be
+ * piped into other programs. Sort of like TCP Wrapper's safe_finger
+ * program.
+ */
+#define NON_NULL_EMPTY(s) ((s) && *(s))
+
+ post_mail_fputs(bounce, "");
+ if (NON_NULL_EMPTY(rcpt->orig_addr)) {
+ bounce_print_wrap(bounce, bounce_info, "<%s> (expanded from <%s>): %s",
+ rcpt->address, rcpt->orig_addr, dsn->reason);
+ } else {
+ bounce_print_wrap(bounce, bounce_info, "<%s>: %s",
+ rcpt->address, dsn->reason);
+ }
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_diagnostic_log - send bounce log report */
+
+int bounce_diagnostic_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ int notify_filter)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ int count = 0;
+
+ /*
+ * Append a human-readable copy of the delivery error log. We're doing a
+ * best effort, so there is no point raising a fatal run-time error in
+ * case of a logfile read error.
+ *
+ * XXX DSN If the logfile with failed recipients is unavailable, pretend
+ * that we found something anyway, so that this notification will not be
+ * canceled.
+ */
+ if (bounce_info->log_handle == 0
+ || bounce_log_rewind(bounce_info->log_handle)) {
+ if (IS_FAILURE_TEMPLATE(bounce_info->template)) {
+ post_mail_fputs(bounce, "");
+ post_mail_fputs(bounce, "\t--- Delivery report unavailable ---");
+ count = 1; /* XXX don't abort */
+ }
+ } else {
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0) {
+ if (rcpt->dsn_notify == 0 /* compat */
+ || (rcpt->dsn_notify & notify_filter)) {
+ count++;
+ if (bounce_recipient_log(bounce, bounce_info) != 0)
+ break;
+ }
+ }
+ }
+ return (vstream_ferror(bounce) ? -1 : count);
+}
+
+/* bounce_header_dsn - send per-MTA bounce DSN records */
+
+int bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+
+ /*
+ * MIME header.
+ */
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s",
+ "Delivery report");
+ /* Generate *global* only if the original requested SMTPUTF8 support. */
+ post_mail_fprintf(bounce, "Content-Type: message/%sdelivery-status",
+ (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED) ?
+ "global-" : "");
+ /* Fix 20140709: addresses may be 8bit. */
+ if (NOT_7BIT_MIME(bounce_info)
+ /* BC Fix 20170610: prevent MIME downgrade of message/delivery-status. */
+ && (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED))
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ bounce_info->mime_encoding);
+
+ /*
+ * According to RFC 1894: The body of a message/delivery-status consists
+ * of one or more "fields" formatted according to the ABNF of RFC 822
+ * header "fields" (see [6]). The per-message fields appear first,
+ * followed by a blank line.
+ */
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "Reporting-MTA: dns; %s", var_myhostname);
+#if 0
+ post_mail_fprintf(bounce, "Received-From-MTA: dns; %s", "whatever");
+#endif
+ if (NON_NULL_EMPTY(bounce_info->dsn_envid)) {
+ post_mail_fprintf(bounce, "Original-Envelope-Id: %s",
+ bounce_info->dsn_envid);
+ }
+ post_mail_fprintf(bounce, "X-%s-Queue-ID: %s",
+ bounce_info->mail_name, bounce_info->queue_id);
+
+#define IS_UTF8_ADDRESS(str, len) \
+ ((str)[0] != 0 && !allascii(str) && valid_utf8_string((str), (len)))
+
+ /* Fix 20140708: use "utf-8" or "rfc822" as appropriate. */
+ if (VSTRING_LEN(bounce_info->sender) > 0)
+ post_mail_fprintf(bounce, "X-%s-Sender: %s; %s",
+ bounce_info->mail_name, bounce_info->smtputf8
+ && IS_UTF8_ADDRESS(STR(bounce_info->sender),
+ VSTRING_LEN(bounce_info->sender)) ?
+ "utf-8" : "rfc822", STR(bounce_info->sender));
+ if (bounce_info->arrival_time > 0)
+ post_mail_fprintf(bounce, "Arrival-Date: %s",
+ mail_date(bounce_info->arrival_time));
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_recipient_dsn - send per-recipient DSN records */
+
+int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ DSN *dsn = &bounce_info->dsn_buf->dsn;
+
+ post_mail_fputs(bounce, "");
+ /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
+ post_mail_fprintf(bounce, "Final-Recipient: %s; %s",
+ bounce_info->smtputf8
+ && IS_UTF8_ADDRESS(rcpt->address,
+ strlen(rcpt->address)) ?
+ "utf-8" : "rfc822", rcpt->address);
+
+ /*
+ * XXX DSN
+ *
+ * RFC 3464 section 6.3.d: "If no ORCPT parameter was provided for this
+ * recipient, the Original-Recipient field MUST NOT appear."
+ *
+ * This is inconsistent with section 5.2.1.d: "If no ORCPT parameter was
+ * present in the RCPT command when the message was received, an ORCPT
+ * parameter MAY be added to the RCPT command when the message is
+ * relayed.". Postfix adds an ORCPT parameter under these conditions.
+ *
+ * Therefore, all down-stream MTAs will send DSNs with Original-Recipient
+ * field containing this same ORCPT value. When a down-stream MTA can use
+ * that information in their DSNs, it makes no sense that an up-stream
+ * MTA can't use that same information in its own DSNs.
+ *
+ * Postfix always reports an Original-Recipient field, because it is more
+ * more useful and more consistent.
+ */
+ if (NON_NULL_EMPTY(rcpt->dsn_orcpt)) {
+ post_mail_fprintf(bounce, "Original-Recipient: %s", rcpt->dsn_orcpt);
+ } else if (NON_NULL_EMPTY(rcpt->orig_addr)) {
+ /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
+ post_mail_fprintf(bounce, "Original-Recipient: %s; %s",
+ bounce_info->smtputf8
+ && IS_UTF8_ADDRESS(rcpt->orig_addr,
+ strlen(rcpt->orig_addr)) ?
+ "utf-8" : "rfc822", rcpt->orig_addr);
+ }
+ post_mail_fprintf(bounce, "Action: %s",
+ IS_FAILURE_TEMPLATE(bounce_info->template) ?
+ "failed" : dsn->action);
+ post_mail_fprintf(bounce, "Status: %s", dsn->status);
+ if (NON_NULL_EMPTY(dsn->mtype) && NON_NULL_EMPTY(dsn->mname))
+ bounce_print_wrap(bounce, bounce_info, "Remote-MTA: %s; %s",
+ dsn->mtype, dsn->mname);
+ if (NON_NULL_EMPTY(dsn->dtype) && NON_NULL_EMPTY(dsn->dtext))
+ bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: %s; %s",
+ dsn->dtype, dsn->dtext);
+ else
+ bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: X-%s; %s",
+ bounce_info->mail_name, dsn->reason);
+#if 0
+ if (dsn->time > 0)
+ post_mail_fprintf(bounce, "Last-Attempt-Date: %s",
+ mail_date(dsn->time));
+#endif
+ if (IS_DELAY_TEMPLATE(bounce_info->template))
+ post_mail_fprintf(bounce, "Will-Retry-Until: %s",
+ mail_date(bounce_info->arrival_time + var_max_queue_time));
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_diagnostic_dsn - send bounce log report, machine readable form */
+
+int bounce_diagnostic_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ int notify_filter)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ int count = 0;
+
+ /*
+ * Append a machine-readable copy of the delivery error log. We're doing
+ * a best effort, so there is no point raising a fatal run-time error in
+ * case of a logfile read error.
+ *
+ * XXX DSN If the logfile with failed recipients is unavailable, pretend
+ * that we found something anyway, so that this notification will not be
+ * canceled.
+ */
+ if (bounce_info->log_handle == 0
+ || bounce_log_rewind(bounce_info->log_handle)) {
+ if (IS_FAILURE_TEMPLATE(bounce_info->template))
+ count = 1; /* XXX don't abort */
+ } else {
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0) {
+ if (rcpt->dsn_notify == 0 /* compat */
+ || (rcpt->dsn_notify & notify_filter)) {
+ count++;
+ if (bounce_recipient_dsn(bounce, bounce_info) != 0)
+ break;
+ }
+ }
+ }
+ return (vstream_ferror(bounce) ? -1 : count);
+}
+
+/* bounce_original - send a copy of the original to the victim */
+
+int bounce_original(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ int headers_only)
+{
+ int status = 0;
+ int rec_type = 0;
+
+ /*
+ * When truncating a large message, don't damage the MIME structure: send
+ * the message headers only.
+ */
+ if (var_bounce_limit > 0
+ && bounce_info->orig_fp
+ && (bounce_info->message_size <= 0
+ || bounce_info->message_size > var_bounce_limit))
+ headers_only = DSN_RET_HDRS;
+
+ /*
+ * MIME headers.
+ */
+#define IS_UNDELIVERED_TEMPLATE(template) \
+ (IS_FAILURE_TEMPLATE(template) || IS_DELAY_TEMPLATE(template))
+
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s%s",
+ IS_UNDELIVERED_TEMPLATE(bounce_info->template) ?
+ "Undelivered " : "",
+ headers_only == DSN_RET_HDRS ?
+ "Message Headers" : "Message");
+ /* Generate *global* only if the original requested SMTPUTF8 support. */
+ if (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED)
+ post_mail_fprintf(bounce, "Content-Type: message/%s",
+ headers_only == DSN_RET_HDRS ?
+ "global-headers" : "global");
+ else
+ post_mail_fprintf(bounce, "Content-Type: %s",
+ headers_only == DSN_RET_HDRS ?
+ "text/rfc822-headers" : "message/rfc822");
+ if (NOT_7BIT_MIME(bounce_info))
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ bounce_info->mime_encoding);
+ post_mail_fputs(bounce, "");
+
+ /*
+ * Send place holder if original is unavailable.
+ */
+ if (bounce_info->orig_offs == 0 || vstream_fseek(bounce_info->orig_fp,
+ bounce_info->orig_offs, SEEK_SET) < 0) {
+ post_mail_fputs(bounce, "\t--- Undelivered message unavailable ---");
+ return (vstream_ferror(bounce));
+ }
+
+ /*
+ * XXX The cleanup server removes Return-Path: headers. This should be
+ * done only with mail that enters via a non-SMTP channel, but changing
+ * this now could break other software. Removing Return-Path: could break
+ * digital signatures, though this is unlikely. In any case,
+ * header_checks are more effective when the Return-Path: header is
+ * present, so we prepend one to the bounce message.
+ */
+ post_mail_fprintf(bounce, "Return-Path: <%s>", STR(bounce_info->sender));
+
+ /*
+ * Copy the original message contents. We're doing raw record output here
+ * so that we don't throw away binary transparency yet.
+ */
+#define IS_HEADER(s) (IS_SPACE_TAB(*(s)) || is_header(s))
+
+ while (status == 0 && (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) {
+ if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
+ break;
+ if (headers_only == DSN_RET_HDRS
+ && !IS_HEADER(vstring_str(bounce_info->buf)))
+ break;
+ status = (REC_PUT_BUF(bounce, rec_type, bounce_info->buf) != rec_type);
+ }
+
+ /*
+ * Final MIME headers. These require -- at the end of the boundary
+ * string.
+ *
+ * XXX This should be a separate bounce_terminate() entry so we can be
+ * assured that the terminator will always be sent.
+ */
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s--", bounce_info->mime_boundary);
+
+ return (status);
+}
+
+/* bounce_delrcpt - delete recipients from original queue file */
+
+void bounce_delrcpt(BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ if (bounce_info->orig_fp != 0
+ && bounce_info->log_handle != 0
+ && bounce_log_rewind(bounce_info->log_handle) == 0)
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0)
+ if (rcpt->offset > 0)
+ deliver_completed(bounce_info->orig_fp, rcpt->offset);
+}
+
+/* bounce_delrcpt_one - delete one recipient from original queue file */
+
+void bounce_delrcpt_one(BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ if (bounce_info->orig_fp != 0 && rcpt->offset > 0)
+ deliver_completed(bounce_info->orig_fp, rcpt->offset);
+}
diff --git a/src/bounce/bounce_notify_util_tester.c b/src/bounce/bounce_notify_util_tester.c
new file mode 100644
index 0000000..da13f47
--- /dev/null
+++ b/src/bounce/bounce_notify_util_tester.c
@@ -0,0 +1,167 @@
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <dsn_mask.h>
+#include <mail_params.h>
+#include <record.h>
+#include <rec_type.h>
+#include <hfrom_format.h>
+
+ /*
+ * Bounce service.
+ */
+#include <bounce_service.h>
+#include <bounce_template.h>
+
+ /*
+ * Testing library.
+ */
+#include <test_main.h>
+
+#define TEST_ENCODING "7bit"
+#define NO_SMTPUTF8 (0)
+#define TEST_DSN_ENVID "TEST-ENVID"
+#define TEST_RECIPIENT "test-recipient"
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* test_driver - test driver */
+
+static void test_driver(int argc, char **argv)
+{
+ BOUNCE_TEMPLATES *bounce_templates;
+ BOUNCE_INFO *bounce_info;
+ VSTRING *message_buf;
+ VSTREAM *message_stream;
+ VSTRING *rec_buf;
+ int rec_type;
+
+ /*
+ * Sanity checks.
+ */
+ if (argc != 4)
+ msg_fatal("usage: %s [options] service queue_name queue_id", argv[0]);
+
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ bounce_hfrom_format =
+ hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format);
+
+ /*
+ * Write one message to VSTRING.
+ */
+ message_buf = vstring_alloc(100);
+ if ((message_stream = vstream_memopen(message_buf, O_WRONLY)) == 0)
+ msg_fatal("vstream_memopen O_WRONLY: %m");
+ bounce_templates = bounce_templates_create();
+ bounce_info = bounce_mail_init(argv[1], argv[2], argv[3],
+ TEST_ENCODING, NO_SMTPUTF8, TEST_DSN_ENVID,
+ bounce_templates->failure);
+ if (bounce_header(message_stream, bounce_info, TEST_RECIPIENT,
+ NO_POSTMASTER_COPY) < 0)
+ msg_fatal("bounce_header: %m");
+ if (bounce_diagnostic_log(message_stream, bounce_info,
+ DSN_NOTIFY_OVERRIDE) <= 0)
+ msg_fatal("bounce_diagnostic_log: %m");
+ if (bounce_header_dsn(message_stream, bounce_info) != 0)
+ msg_fatal("bounce_header_dsn: %m");
+ if (bounce_diagnostic_dsn(message_stream, bounce_info,
+ DSN_NOTIFY_OVERRIDE) <= 0)
+ msg_fatal("bounce_diagnostic_dsn: %m");
+ bounce_original(message_stream, bounce_info, DSN_RET_FULL);
+ if (vstream_fclose(message_stream) != 0)
+ msg_fatal("vstream_fclose: %m");
+ bounce_mail_free(bounce_info);
+
+ /*
+ * Render the bounce message in human-readable form.
+ */
+ if ((message_stream = vstream_memopen(message_buf, O_RDONLY)) == 0)
+ msg_fatal("vstream_memopen O_RDONLY: %m");
+ rec_buf = vstring_alloc(100);
+ while ((rec_type = rec_get(message_stream, rec_buf, 0)) > 0) {
+ switch (rec_type) {
+ case REC_TYPE_CONT:
+ vstream_printf("%.*s", (int) LEN(rec_buf), STR(rec_buf));
+ break;
+ case REC_TYPE_NORM:
+ vstream_printf("%.*s\n", (int) LEN(rec_buf), STR(rec_buf));
+ break;
+ default:
+ msg_panic("unexpected message record type %d", rec_type);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (vstream_fclose(message_stream) != 0)
+ msg_fatal("vstream_fclose: %m");
+ vstring_free(rec_buf);
+
+ /*
+ * Clean up.
+ */
+ exit(0);
+}
+
+int var_bounce_limit;
+int var_max_queue_time;
+int var_delay_warn_time;
+char *var_notify_classes;
+char *var_bounce_rcpt;
+char *var_2bounce_rcpt;
+char *var_delay_rcpt;
+char *var_bounce_tmpl;
+bool var_threaded_bounce;
+char *var_hfrom_format; /* header_from_format */
+
+int bounce_hfrom_format;
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_BOUNCE_LIMIT, DEF_BOUNCE_LIMIT, &var_bounce_limit, 1, 0,
+ 0,
+ };
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000,
+ VAR_DELAY_WARN_TIME, DEF_DELAY_WARN_TIME, &var_delay_warn_time, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
+ VAR_BOUNCE_RCPT, DEF_BOUNCE_RCPT, &var_bounce_rcpt, 1, 0,
+ VAR_2BOUNCE_RCPT, DEF_2BOUNCE_RCPT, &var_2bounce_rcpt, 1, 0,
+ VAR_DELAY_RCPT, DEF_DELAY_RCPT, &var_delay_rcpt, 1, 0,
+ VAR_BOUNCE_TMPL, DEF_BOUNCE_TMPL, &var_bounce_tmpl, 0, 0,
+ VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_THREADED_BOUNCE, DEF_THREADED_BOUNCE, &var_threaded_bounce,
+ 0,
+ };
+
+ test_main(argc, argv, test_driver,
+ CA_TEST_MAIN_INT_TABLE(int_table),
+ CA_TEST_MAIN_STR_TABLE(str_table),
+ CA_TEST_MAIN_TIME_TABLE(time_table),
+ CA_TEST_MAIN_NBOOL_TABLE(nbool_table),
+ 0);
+
+ exit(0);
+}
diff --git a/src/bounce/bounce_notify_verp.c b/src/bounce/bounce_notify_verp.c
new file mode 100644
index 0000000..8bfd71b
--- /dev/null
+++ b/src/bounce/bounce_notify_verp.c
@@ -0,0 +1,267 @@
+/*++
+/* NAME
+/* bounce_notify_verp 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_notify_verp(flags, service, queue_name, queue_id,
+/* encoding, smtputf8, sender,
+/* dsn_envid, dsn_ret, verp_delims,
+/* templates)
+/* int flags;
+/* char *service;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *sender;
+/* char *dsn_envid;
+/* int dsn_ret;
+/* char *verp_delims;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_notify()
+/* (send bounce message) request. The logfile
+/* is removed after and a warning is posted.
+/* The bounce recipient address is encoded in VERP format.
+/* This routine must be used for single bounces only.
+/*
+/* When a message bounces, a full copy is sent to the originator,
+/* and an optional copy of the diagnostics with message headers is
+/* sent to the postmaster. The result is non-zero when the operation
+/* should be tried again.
+/*
+/* When a bounce is sent, the sender address is the empty
+/* address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <verp_sender.h>
+#include <bounce.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_notify_verp - send a bounce, VERP style */
+
+int bounce_notify_verp(int flags, char *service, char *queue_name,
+ char *queue_id, char *encoding,
+ int smtputf8, char *recipient,
+ char *dsn_envid, int dsn_ret,
+ char *verp_delims, BOUNCE_TEMPLATES *ts)
+{
+ const char *myname = "bounce_notify_verp";
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 0;
+ int postmaster_status;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ char *postmaster;
+ VSTRING *verp_buf;
+ VSTRING *new_id;
+
+ /*
+ * Sanity checks. We must be called only for undeliverable non-bounce
+ * messages.
+ */
+ if (*recipient == 0)
+ msg_panic("%s: attempt to bounce a single bounce", myname);
+ if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0)
+ msg_panic("%s: attempt to bounce a double bounce", myname);
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ */
+ bounce_info = bounce_mail_init(service, queue_name, queue_id,
+ encoding, smtputf8, dsn_envid,
+ ts->failure);
+
+ /*
+ * If we have no recipient list then we can't send VERP replies. Send
+ * *something* anyway so that the mail is not lost in a black hole.
+ */
+ if (bounce_info->log_handle == 0) {
+ DSN_BUF *dsn_buf = dsb_create();
+ RCPT_BUF *rcpt_buf = rcpb_create();
+
+ dsb_simple(dsn_buf, "5.0.0", "(error report unavailable)");
+ (void) DSN_FROM_DSN_BUF(dsn_buf);
+ vstring_strcpy(rcpt_buf->address, "(recipient address unavailable)");
+ (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf);
+ bounce_status = bounce_one_service(flags, queue_name, queue_id,
+ encoding, smtputf8, recipient,
+ dsn_envid, dsn_ret, rcpt_buf,
+ dsn_buf, ts);
+ rcpb_free(rcpt_buf);
+ dsb_free(dsn_buf);
+ bounce_mail_free(bounce_info);
+ return (bounce_status);
+ }
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * A non-bounce message was returned. Send a single bounce, one per
+ * recipient.
+ */
+ verp_buf = vstring_alloc(100);
+ new_id = vstring_alloc(10);
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0) {
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ /*
+ * Notify the originator, subject to DSN NOTIFY restrictions.
+ *
+ * Fix 20090114: Use the Postfix original recipient, because that is
+ * what the VERP consumer expects.
+ */
+ if (rcpt->dsn_notify != 0 /* compat */
+ && (rcpt->dsn_notify & DSN_NOTIFY_FAILURE) == 0) {
+ bounce_status = 0;
+ } else {
+ verp_sender(verp_buf, verp_delims, recipient, rcpt);
+ if ((bounce = post_mail_fopen_nowait(NULL_SENDER, STR(verp_buf),
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Send the bounce message header, some boilerplate text that
+ * pretends that we are a polite mail system, the text with
+ * reason for the bounce, and a copy of the original message.
+ */
+ if (bounce_header(bounce, bounce_info, STR(verp_buf),
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, dsn_ret ?
+ dsn_ret : DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else
+ bounce_status = 1;
+
+ /*
+ * Stop at the first sign of trouble, instead of making the
+ * problem worse.
+ */
+ if (bounce_status != 0)
+ break;
+
+ /*
+ * Optionally, mark this recipient as done.
+ */
+ if (flags & BOUNCE_FLAG_DELRCPT)
+ bounce_delrcpt_one(bounce_info);
+ }
+
+ /*
+ * Optionally, send a postmaster notice, subject to notify_classes
+ * restrictions.
+ *
+ * This postmaster notice is not critical, so if it fails don't
+ * retransmit the bounce that we just generated, just log a warning.
+ */
+#define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE)
+
+ if (SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE) {
+
+ /*
+ * Send the text with reason for the bounce, and the headers of
+ * the original message. Don't bother sending the boiler-plate
+ * text. This postmaster notice is not critical, so if it fails
+ * don't retransmit the bounce that we just generated, just log a
+ * warning.
+ */
+ postmaster = var_bounce_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ if (bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY) == 0
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ postmaster_status = post_mail_fclose(bounce);
+ if (postmaster_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ } else
+ postmaster_status = 1;
+
+ if (postmaster_status)
+ msg_warn("%s: postmaster notice failed while bouncing to %s",
+ queue_id, recipient);
+ }
+ }
+
+ /*
+ * Examine the completion status. Delete the bounce log file only when
+ * the bounce was posted successfully, and only if we are bouncing for
+ * real, not just warning.
+ */
+ if (bounce_status == 0 && mail_queue_remove(service, queue_id)
+ && errno != ENOENT)
+ msg_fatal("remove %s %s: %m", service, queue_id);
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(verp_buf);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/bounce_one_service.c b/src/bounce/bounce_one_service.c
new file mode 100644
index 0000000..29c4fc3
--- /dev/null
+++ b/src/bounce/bounce_one_service.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* bounce_one_service 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_one_service(flags, queue_name, queue_id, encoding,
+/* smtputf8, orig_sender, envid, ret,
+/* rcpt_buf, dsn_buf, templates)
+/* int flags;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *orig_sender;
+/* char *envid;
+/* int ret;
+/* RCPT_BUF *rcpt_buf;
+/* DSN_BUF *dsn_buf;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_one()
+/* (send bounce message for one recipient) request.
+/*
+/* When a message bounces, a full copy is sent to the originator,
+/* and an optional copy of the diagnostics with message headers is
+/* sent to the postmaster. The result is non-zero when the operation
+/* should be tried again.
+/*
+/* When a bounce is sent, the sender address is the empty
+/* address. When a bounce bounces, an optional double bounce
+/* with the entire undeliverable mail is sent to the postmaster,
+/* with as sender address the double bounce address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <bounce.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_one_service - send a bounce for one recipient */
+
+int bounce_one_service(int flags, char *queue_name, char *queue_id,
+ char *encoding, int smtputf8,
+ char *orig_sender, char *dsn_envid,
+ int dsn_ret, RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf, BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 1;
+ int postmaster_status = 1;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ VSTRING *new_id = vstring_alloc(10);
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ */
+ bounce_info = bounce_mail_one_init(queue_name, queue_id, encoding,
+ smtputf8, dsn_envid, rcpt_buf,
+ dsn_buf, ts->failure);
+
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * The choice of bounce sender address depends on the original sender
+ * address. For a single bounce (a non-delivery notification to the
+ * message originator), the sender address is the empty string. For a
+ * double bounce (typically a failed single bounce, or a postmaster
+ * notification that was produced by any of the mail processes) the
+ * sender address is defined by the var_double_bounce_sender
+ * configuration variable. When a double bounce cannot be delivered, the
+ * queue manager blackholes the resulting triple bounce message.
+ */
+
+ /*
+ * Double bounce failed. Never send a triple bounce.
+ *
+ * However, this does not prevent double bounces from bouncing on other
+ * systems. In order to cope with this, either the queue manager must
+ * recognize the double-bounce original sender address and discard mail,
+ * or every delivery agent must recognize the double-bounce sender
+ * address and substitute something else so mail does not come back at
+ * us.
+ */
+ if (strcasecmp_utf8(orig_sender, mail_addr_double_bounce()) == 0) {
+ msg_warn("%s: undeliverable postmaster notification discarded",
+ queue_id);
+ bounce_status = 0;
+ }
+
+ /*
+ * Single bounce failed. Optionally send a double bounce to postmaster,
+ * subject to notify_classes restrictions.
+ */
+#define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE)
+#define SEND_POSTMASTER_ANY_BOUNCE_NOTICE (notify_mask & ANY_BOUNCE)
+
+ else if (*orig_sender == 0) {
+ if (!SEND_POSTMASTER_ANY_BOUNCE_NOTICE) {
+ bounce_status = 0;
+ } else {
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ var_2bounce_rcpt,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Double bounce to Postmaster. This is the last opportunity
+ * for this message to be delivered. Send the text with
+ * reason for the bounce, and the headers of the original
+ * message. Don't bother sending the boiler-plate text.
+ */
+ if (!bounce_header(bounce, bounce_info, var_2bounce_rcpt,
+ POSTMASTER_COPY)
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ }
+ }
+ }
+
+ /*
+ * Non-bounce failed. Send a single bounce, subject to DSN NOTIFY
+ * restrictions.
+ */
+ else {
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ if (rcpt->dsn_notify != 0 /* compat */
+ && (rcpt->dsn_notify & DSN_NOTIFY_FAILURE) == 0) {
+ bounce_status = 0;
+ } else {
+ if ((bounce = post_mail_fopen_nowait(NULL_SENDER, orig_sender,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Send the bounce message header, some boilerplate text that
+ * pretends that we are a polite mail system, the text with
+ * reason for the bounce, and a copy of the original message.
+ */
+ if (bounce_header(bounce, bounce_info, orig_sender,
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, dsn_ret ?
+ dsn_ret : DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender non-delivery notification: %s",
+ queue_id, STR(new_id));
+ }
+ }
+
+ /*
+ * Optionally send a postmaster notice, subject to notify_classes
+ * restrictions.
+ *
+ * This postmaster notice is not critical, so if it fails don't
+ * retransmit the bounce that we just generated, just log a warning.
+ */
+#define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE)
+
+ if (bounce_status == 0 && SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE
+ && strcasecmp_utf8(orig_sender, mail_addr_double_bounce()) != 0) {
+
+ /*
+ * Send the text with reason for the bounce, and the headers of
+ * the original message. Don't bother sending the boiler-plate
+ * text. This postmaster notice is not critical, so if it fails
+ * don't retransmit the bounce that we just generated, just log a
+ * warning.
+ */
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ var_bounce_rcpt,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ if (bounce_header(bounce, bounce_info, var_bounce_rcpt,
+ POSTMASTER_COPY) == 0
+ && bounce_recipient_log(bounce, bounce_info) == 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_recipient_dsn(bounce, bounce_info) == 0)
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ postmaster_status = post_mail_fclose(bounce);
+ if (postmaster_status == 0)
+ msg_info("%s: postmaster non-delivery notification: %s",
+ queue_id, STR(new_id));
+ }
+ if (postmaster_status)
+ msg_warn("%s: postmaster notice failed while bouncing to %s",
+ queue_id, orig_sender);
+ }
+ }
+
+ /*
+ * Optionally, delete the recipient from the queue file.
+ */
+ if (bounce_status == 0 && (flags & BOUNCE_FLAG_DELRCPT))
+ bounce_delrcpt_one(bounce_info);
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/bounce_service.h b/src/bounce/bounce_service.h
new file mode 100644
index 0000000..b79542f
--- /dev/null
+++ b/src/bounce/bounce_service.h
@@ -0,0 +1,121 @@
+/*++
+/* NAME
+/* bounce_service 3h
+/* SUMMARY
+/* bounce message service
+/* SYNOPSIS
+/* #include <bounce_service.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <bounce_log.h>
+
+ /*
+ * Application-specific.
+ */
+#include <bounce_template.h>
+
+ /*
+ * bounce_service.c
+ */
+extern int bounce_hfrom_format;
+
+ /*
+ * bounce_append_service.c
+ */
+extern int bounce_append_service(int, char *, char *, RECIPIENT *, DSN *);
+
+ /*
+ * bounce_notify_service.c
+ */
+extern int bounce_notify_service(int, char *, char *, char *, char *, int, char *, char *, int, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_warn_service.c
+ */
+extern int bounce_warn_service(int, char *, char *, char *, char *, int, char *, char *, int, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_trace_service.c
+ */
+extern int bounce_trace_service(int, char *, char *, char *, char *, int, char *, char *, int, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_notify_verp.c
+ */
+extern int bounce_notify_verp(int, char *, char *, char *, char *, int, char *, char *, int, char *, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_one_service.c
+ */
+extern int bounce_one_service(int, char *, char *, char *, int, char *, char *, int, RCPT_BUF *, DSN_BUF *, BOUNCE_TEMPLATES *);
+
+ /*
+ * bounce_cleanup.c
+ */
+extern VSTRING *bounce_cleanup_path;
+extern void bounce_cleanup_register(char *, char *);
+extern void bounce_cleanup_log(void);
+extern void bounce_cleanup_unregister(void);
+
+#define bounce_cleanup_registered() (bounce_cleanup_path != 0)
+
+ /*
+ * bounce_notify_util.c
+ */
+typedef struct {
+ const char *service; /* bounce or defer */
+ const char *queue_name; /* incoming, etc. */
+ const char *queue_id; /* base name */
+ const char *mime_encoding; /* null or encoding */
+ const char *dsn_envid; /* DSN envelope ID */
+ const char *mime_boundary; /* for MIME */
+ BOUNCE_TEMPLATE *template; /* bounce message template */
+ VSTRING *buf; /* scratch pad */
+ VSTRING *sender; /* envelope sender */
+ VSTREAM *orig_fp; /* open queue file */
+ long orig_offs; /* start of content */
+ time_t arrival_time; /* time of arrival */
+ long message_size; /* size of content */
+ VSTRING *orig_msgid; /* original message-id */
+ RCPT_BUF *rcpt_buf; /* recipient info */
+ DSN_BUF *dsn_buf; /* delivery status info */
+ BOUNCE_LOG *log_handle; /* open logfile */
+ char *mail_name; /* $mail_name, cooked */
+ int smtputf8; /* SMTPUTF8 requested */
+} BOUNCE_INFO;
+
+ /* */
+
+extern BOUNCE_INFO *bounce_mail_init(const char *, const char *, const char *, const char *, int, const char *, BOUNCE_TEMPLATE *);
+extern BOUNCE_INFO *bounce_mail_one_init(const char *, const char *, const char *, int, const char *, RCPT_BUF *, DSN_BUF *, BOUNCE_TEMPLATE *);
+extern void bounce_mail_free(BOUNCE_INFO *);
+extern int bounce_header(VSTREAM *, BOUNCE_INFO *, const char *, int);
+extern int bounce_boilerplate(VSTREAM *, BOUNCE_INFO *);
+extern int bounce_recipient_log(VSTREAM *, BOUNCE_INFO *);
+extern int bounce_diagnostic_log(VSTREAM *, BOUNCE_INFO *, int);
+extern int bounce_header_dsn(VSTREAM *, BOUNCE_INFO *);
+extern int bounce_recipient_dsn(VSTREAM *, BOUNCE_INFO *);
+extern int bounce_diagnostic_dsn(VSTREAM *, BOUNCE_INFO *, int);
+extern int bounce_original(VSTREAM *, BOUNCE_INFO *, int);
+extern void bounce_delrcpt(BOUNCE_INFO *);
+extern void bounce_delrcpt_one(BOUNCE_INFO *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
diff --git a/src/bounce/bounce_template.c b/src/bounce/bounce_template.c
new file mode 100644
index 0000000..67a5cf7
--- /dev/null
+++ b/src/bounce/bounce_template.c
@@ -0,0 +1,548 @@
+/*++
+/* NAME
+/* bounce_template 3
+/* SUMMARY
+/* bounce template support
+/* SYNOPSIS
+/* #include <bounce_template.h>
+/*
+/* BOUNCE_TEMPLATE *bounce_template_create(def_template)
+/* const BOUNCE_TEMPLATE *def_template;
+/*
+/* void bounce_template_free(template)
+/* BOUNCE_TEMPLATE *template;
+/*
+/* void bounce_template_load(template, stream, buffer, origin)
+/* BOUNCE_TEMPLATE *template;
+/* VSTREAM *stream;
+/* const char *buffer;
+/* const char *origin;
+/*
+/* void bounce_template_headers(out_fn, stream, template,
+/* rcpt, postmaster_copy)
+/* int (*out_fn)(VSTREAM *, const char *, ...);
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATE *template;
+/* const char *rcpt;
+/* int postmaster_copy;
+/*
+/* const char *bounce_template_encoding(template)
+/* BOUNCE_TEMPLATE *template;
+/*
+/* const char *bounce_template_charset(template)
+/* BOUNCE_TEMPLATE *template;
+/*
+/* void bounce_template_expand(out_fn, stream, template)
+/* int (*out_fn)(VSTREAM *, const char *);
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATE *template;
+/*
+/* void bounce_template_dump(stream, template)
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATE *template;
+/*
+/* int IS_FAILURE_TEMPLATE(template)
+/* int IS_DELAY_TEMPLATE(template)
+/* int IS_SUCCESS_TEMPLATE(template)
+/* BOUNCE_TEMPLATE *template;
+/* DESCRIPTION
+/* This module implements the built-in and external bounce
+/* message template support. The content of a template are
+/* private. To access information within a template, use
+/* the API described in this document.
+/*
+/* bounce_template_create() creates a template, with the
+/* specified default settings. The template defaults are not
+/* copied.
+/*
+/* bounce_template_free() destroys a bounce message template.
+/*
+/* bounce_template_load() overrides a bounce template with the
+/* specified buffer from the specified origin. The buffer and
+/* origin are copied. Specify a null buffer and origin pointer
+/* to reset the template to the defaults specified with
+/* bounce_template_create().
+/*
+/* bounce_template_headers() sends the postmaster or non-postmaster
+/* From/Subject/To message headers to the specified stream.
+/* The recipient address is expected to be in RFC822 external
+/* form. The postmaster_copy argument is one of POSTMASTER_COPY
+/* or NO_POSTMASTER_COPY.
+/*
+/* bounce_template_encoding() returns the encoding (MAIL_ATTR_ENC_7BIT
+/* or MAIL_ATTR_ENC_8BIT) for the bounce template message text.
+/*
+/* bounce_template_charset() returns the character set for the
+/* bounce template message text.
+/*
+/* bounce_template_expand() expands the body text of the
+/* specified template and writes the result to the specified
+/* stream.
+/*
+/* bounce_template_dump() dumps the specified template to the
+/* specified stream.
+/*
+/* The IS_MUMBLE_TEMPLATE() macros are predicates that
+/* determine whether the template is of the specified type.
+/* DIAGNOSTICS
+/* Fatal error: out of memory, undefined macro name in template.
+/* SEE ALSO
+/* bounce_templates(3) bounce template group support
+/* 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 <mac_expand.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#ifndef NO_EAI
+#include <midna_domain.h>
+#endif
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <mail_conf.h>
+#include <is_header.h>
+#include <hfrom_format.h>
+
+/* Application-specific. */
+
+#include <bounce_template.h>
+#include <bounce_service.h>
+
+ /*
+ * The following tables implement support for bounce template expansions of
+ * $<parameter_name>_days ($<parameter_name>_hours, etc.). The expansion of
+ * these is the actual parameter value divided by the number of seconds in a
+ * day (hour, etc.), so that we can produce nicely formatted bounce messages
+ * with time values converted into the appropriate units.
+ *
+ * Ideally, the bounce template processor would strip the _days etc. suffix
+ * from the parameter name, and use the parameter name to look up the actual
+ * parameter value and its default value (the default value specifies the
+ * default time unit of that parameter (seconds, minutes, etc.)), and use
+ * this to convert the parameter string value into the corresponding number
+ * of seconds. The bounce template processor would then use the _hours etc.
+ * suffix from the bounce template to divide this number by the number of
+ * seconds in an hour, etc. and produce the number that is needed for the
+ * template.
+ *
+ * Unfortunately, there exists no code to look up default values by parameter
+ * name. If such code existed, then we could do the _days, _hours, etc.
+ * conversion with every main.cf time parameter without having to know in
+ * advance what time parameter names exist.
+ *
+ * So we have to either maintain our own table of all time related main.cf
+ * parameter names and defaults (like the postconf command does) or we make
+ * a special case for a few parameters of special interest.
+ *
+ * We go for the second solution. There are only a few parameters that need
+ * this treatment, and there will be more special cases when individual
+ * queue files get support for individual expiration times, and when other
+ * queue file information needs to be reported in bounce template messages.
+ *
+ * A really lame implementation would simply strip the optional s, h, d, etc.
+ * suffix from the actual (string) parameter value and not do any conversion
+ * at all to hours, days or weeks. But then the information in delay warning
+ * notices could be seriously incorrect.
+ */
+typedef struct {
+ const char *suffix; /* days, hours, etc. */
+ int suffix_len; /* byte count */
+ int divisor; /* divisor */
+} BOUNCE_TIME_DIVISOR;
+
+#define STRING_AND_LEN(x) (x), (sizeof(x) - 1)
+
+static const BOUNCE_TIME_DIVISOR time_divisors[] = {
+ STRING_AND_LEN("seconds"), 1,
+ STRING_AND_LEN("minutes"), 60,
+ STRING_AND_LEN("hours"), 60 * 60,
+ STRING_AND_LEN("days"), 24 * 60 * 60,
+ STRING_AND_LEN("weeks"), 7 * 24 * 60 * 60,
+ 0, 0,
+};
+
+ /*
+ * The few special-case main.cf parameters that have support for _days, etc.
+ * suffixes for automatic conversion when expanded into a bounce template.
+ */
+typedef struct {
+ const char *param_name; /* parameter name */
+ int param_name_len; /* name length */
+ int *value; /* parameter value */
+} BOUNCE_TIME_PARAMETER;
+
+static const BOUNCE_TIME_PARAMETER time_parameter[] = {
+ STRING_AND_LEN(VAR_DELAY_WARN_TIME), &var_delay_warn_time,
+ STRING_AND_LEN(VAR_MAX_QUEUE_TIME), &var_max_queue_time,
+ 0, 0,
+};
+
+ /*
+ * Parameters whose value may have to be converted to UTF-8 for presentation
+ * purposes.
+ */
+typedef struct {
+ const char *param_name; /* parameter name */
+ char **value; /* parameter value */
+} BOUNCE_STR_PARAMETER;
+
+static const BOUNCE_STR_PARAMETER str_parameter[] = {
+ VAR_MYHOSTNAME, &var_myhostname,
+ VAR_MYDOMAIN, &var_mydomain,
+ 0, 0,
+};
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* bounce_template_create - create one template */
+
+BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *prototype)
+{
+ BOUNCE_TEMPLATE *tp;
+
+ tp = (BOUNCE_TEMPLATE *) mymalloc(sizeof(*tp));
+ *tp = *prototype;
+ return (tp);
+}
+
+/* bounce_template_free - destroy one template */
+
+void bounce_template_free(BOUNCE_TEMPLATE *tp)
+{
+ if (tp->buffer) {
+ myfree(tp->buffer);
+ myfree((void *) tp->origin);
+ }
+ myfree((void *) tp);
+}
+
+/* bounce_template_reset - reset template to default */
+
+static void bounce_template_reset(BOUNCE_TEMPLATE *tp)
+{
+ myfree(tp->buffer);
+ myfree((void *) tp->origin);
+ *tp = *(tp->prototype);
+}
+
+/* bounce_template_load - override one template */
+
+void bounce_template_load(BOUNCE_TEMPLATE *tp, const char *origin,
+ const char *buffer)
+{
+
+ /*
+ * Clean up after a previous call.
+ */
+ if (tp->buffer)
+ bounce_template_reset(tp);
+
+ /*
+ * Postpone the work of template parsing until it is really needed. Most
+ * bounce service calls never need a template.
+ */
+ if (buffer && origin) {
+ tp->flags |= BOUNCE_TMPL_FLAG_NEW_BUFFER;
+ tp->buffer = mystrdup(buffer);
+ tp->origin = mystrdup(origin);
+ }
+}
+
+/* bounce_template_parse_buffer - initialize template */
+
+static void bounce_template_parse_buffer(BOUNCE_TEMPLATE *tp)
+{
+ char *tval = tp->buffer;
+ char *cp;
+ char **cpp;
+ int cpp_len;
+ int cpp_used;
+ int hlen;
+ char *hval;
+
+ /*
+ * Sanity check.
+ */
+ if ((tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) == 0)
+ msg_panic("bounce_template_parse_buffer: nothing to do here");
+ tp->flags &= ~BOUNCE_TMPL_FLAG_NEW_BUFFER;
+
+ /*
+ * Discard the unusable template and use the default one instead.
+ */
+#define CLEANUP_AND_RETURN() do { \
+ bounce_template_reset(tp); \
+ return; \
+ } while (0)
+
+ /*
+ * Parse pseudo-header labels and values.
+ *
+ * XXX EAI: allow UTF8 in template headers when responding to SMTPUTF8
+ * message. Sending SMTPUTF8 in response to non-SMTPUTF8 mail would make
+ * no sense.
+ */
+#define GETLINE(line, buf) \
+ (((line) = (buf)) != 0 ? ((buf) = split_at((buf), '\n'), (line)) : 0)
+
+ while ((GETLINE(cp, tval)) != 0 && (hlen = is_header(cp)) > 0) {
+ for (hval = cp + hlen; *hval && (*hval == ':' || ISSPACE(*hval)); hval++)
+ *hval = 0;
+ if (*hval == 0) {
+ msg_warn("%s: empty \"%s\" header value in %s template "
+ "-- ignoring this template",
+ tp->origin, cp, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+ if (!allascii(hval)) {
+ msg_warn("%s: non-ASCII \"%s\" header value in %s template "
+ "-- ignoring this template",
+ tp->origin, cp, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+ if (strcasecmp("charset", cp) == 0) {
+ tp->mime_charset = hval;
+ } else if (strcasecmp("from", cp) == 0) {
+ tp->std_from = tp->obs_from = hval;
+ } else if (strcasecmp("subject", cp) == 0) {
+ tp->subject = hval;
+ } else if (strcasecmp("postmaster-subject", cp) == 0) {
+ if (tp->postmaster_subject == 0) {
+ msg_warn("%s: inapplicable \"%s\" header label in %s template "
+ "-- ignoring this template",
+ tp->origin, cp, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+ tp->postmaster_subject = hval;
+ } else {
+ msg_warn("%s: unknown \"%s\" header label in %s template "
+ "-- ignoring this template",
+ tp->origin, cp, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+ }
+
+ /*
+ * Skip blank lines between header and message text.
+ */
+ while (cp && (*cp == 0 || allspace(cp)))
+ (void) GETLINE(cp, tval);
+ if (cp == 0) {
+ msg_warn("%s: missing message text in %s template "
+ "-- ignoring this template",
+ tp->origin, tp->class);
+ CLEANUP_AND_RETURN();
+ }
+
+ /*
+ * Is this 7bit or 8bit text? If the character set is US-ASCII, then
+ * don't allow 8bit text. Don't assume 8bit when charset was changed.
+ */
+#define NON_ASCII(p) ((p) && *(p) && !allascii((p)))
+
+ if (NON_ASCII(cp) || NON_ASCII(tval)) {
+ if (strcasecmp(tp->mime_charset, "us-ascii") == 0) {
+ msg_warn("%s: 8-bit message text in %s template",
+ tp->origin, tp->class);
+ msg_warn("please specify a charset value other than us-ascii");
+ msg_warn("-- ignoring this template for now");
+ CLEANUP_AND_RETURN();
+ }
+ tp->mime_encoding = MAIL_ATTR_ENC_8BIT;
+ }
+
+ /*
+ * Collect the message text and null-terminate the result.
+ */
+ cpp_len = 10;
+ cpp_used = 0;
+ cpp = (char **) mymalloc(sizeof(*cpp) * cpp_len);
+ while (cp) {
+ cpp[cpp_used++] = cp;
+ if (cpp_used >= cpp_len) {
+ cpp = (char **) myrealloc((void *) cpp,
+ sizeof(*cpp) * 2 * cpp_len);
+ cpp_len *= 2;
+ }
+ (void) GETLINE(cp, tval);
+ }
+ cpp[cpp_used] = 0;
+ tp->message_text = (const char **) cpp;
+}
+
+/* bounce_template_lookup - lookup $name value */
+
+static const char *bounce_template_lookup(const char *key, int unused_mode,
+ void *context)
+{
+ BOUNCE_TEMPLATE *tp = (BOUNCE_TEMPLATE *) context;
+ const BOUNCE_TIME_PARAMETER *bp;
+ const BOUNCE_TIME_DIVISOR *bd;
+ const BOUNCE_STR_PARAMETER *sp;
+ static VSTRING *buf;
+ int result;
+ const char *asc_val;
+ const char *utf8_val;
+
+ /*
+ * Look for parameter names that can have a time unit suffix, and scale
+ * the time value according to the suffix.
+ */
+ for (bp = time_parameter; bp->param_name; bp++) {
+ if (strncmp(key, bp->param_name, bp->param_name_len) == 0
+ && key[bp->param_name_len] == '_') {
+ for (bd = time_divisors; bd->suffix; bd++) {
+ if (strcmp(key + bp->param_name_len + 1, bd->suffix) == 0) {
+ result = bp->value[0] / bd->divisor;
+ if (result > 999 && bd->divisor < 86400) {
+ msg_warn("%s: excessive result \"%d\" in %s "
+ "template conversion of parameter \"%s\"",
+ tp->origin, result, tp->class, key);
+ msg_warn("please increase time unit \"%s\" of \"%s\" "
+ "in %s template", bd->suffix, key, tp->class);
+ msg_warn("for instructions see the bounce(5) manual");
+ } else if (result == 0 && bp->value[0] && bd->divisor > 1) {
+ msg_warn("%s: zero result in %s template "
+ "conversion of parameter \"%s\"",
+ tp->origin, tp->class, key);
+ msg_warn("please reduce time unit \"%s\" of \"%s\" "
+ "in %s template", bd->suffix, key, tp->class);
+ msg_warn("for instructions see the bounce(5) manual");
+ }
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ vstring_sprintf(buf, "%d", result);
+ return (STR(buf));
+ }
+ }
+ msg_fatal("%s: unrecognized suffix \"%s\" in parameter \"%s\"",
+ tp->origin,
+ key + bp->param_name_len + 1, key);
+ }
+ }
+
+ /*
+ * Look for parameter names that may have to be up-converted for
+ * presentation purposes.
+ */
+#ifndef NO_EAI
+ if (var_smtputf8_enable) {
+ for (sp = str_parameter; sp->param_name; sp++) {
+ if (strcmp(key, sp->param_name) == 0) {
+ asc_val = sp->value[0];
+ if (!allascii(asc_val)) {
+ msg_warn("%s: conversion \"%s\" failed: "
+ "non-ASCII input value: \"%s\"",
+ tp->origin, key, asc_val);
+ return (asc_val);
+ } else if ((utf8_val = midna_domain_to_utf8(asc_val)) == 0) {
+ msg_warn("%s: conversion \"%s\" failed: "
+ "input value: \"%s\"",
+ tp->origin, key, asc_val);
+ return (asc_val);
+ } else {
+ return (utf8_val);
+ }
+ }
+ }
+ }
+#endif
+ return (mail_conf_lookup_eval(key));
+}
+
+/* bounce_template_headers - send template headers */
+
+void bounce_template_headers(BOUNCE_XP_PRN_FN out_fn, VSTREAM *fp,
+ BOUNCE_TEMPLATE *tp,
+ const char *rcpt,
+ int postmaster_copy)
+{
+ if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
+ bounce_template_parse_buffer(tp);
+
+ out_fn(fp, "From: %s", bounce_hfrom_format == HFROM_FORMAT_CODE_STD ?
+ tp->std_from : tp->obs_from);
+ out_fn(fp, "Subject: %s", tp->postmaster_subject && postmaster_copy ?
+ tp->postmaster_subject : tp->subject);
+ out_fn(fp, "To: %s", rcpt);
+}
+
+/* bounce_template_expand - expand template to stream */
+
+void bounce_template_expand(BOUNCE_XP_PUT_FN out_fn, VSTREAM *fp,
+ BOUNCE_TEMPLATE *tp)
+{
+ VSTRING *buf = vstring_alloc(100);
+ const char **cpp;
+ int stat;
+
+ if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
+ bounce_template_parse_buffer(tp);
+
+ for (cpp = tp->message_text; *cpp; cpp++) {
+ stat = mac_expand(buf, *cpp, MAC_EXP_FLAG_PRINTABLE, (char *) 0,
+ bounce_template_lookup, (void *) tp);
+ if (stat & MAC_PARSE_ERROR)
+ msg_fatal("%s: bad $name syntax in %s template: %s",
+ tp->origin, tp->class, *cpp);
+ if (stat & MAC_PARSE_UNDEF)
+ msg_fatal("%s: undefined $name in %s template: %s",
+ tp->origin, tp->class, *cpp);
+ out_fn(fp, STR(buf));
+ }
+ vstring_free(buf);
+}
+
+/* bounce_template_dump - dump template to stream */
+
+void bounce_template_dump(VSTREAM *fp, BOUNCE_TEMPLATE *tp)
+{
+ const char **cpp;
+
+ if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER)
+ bounce_template_parse_buffer(tp);
+
+ vstream_fprintf(fp, "Charset: %s\n", tp->mime_charset);
+ vstream_fprintf(fp, "From: %s\n", bounce_hfrom_format == HFROM_FORMAT_CODE_STD ?
+ tp->std_from : tp->obs_from);
+ vstream_fprintf(fp, "Subject: %s\n", tp->subject);
+ if (tp->postmaster_subject)
+ vstream_fprintf(fp, "Postmaster-Subject: %s\n",
+ tp->postmaster_subject);
+ vstream_fprintf(fp, "\n");
+ for (cpp = tp->message_text; *cpp; cpp++)
+ vstream_fprintf(fp, "%s\n", *cpp);
+}
diff --git a/src/bounce/bounce_template.h b/src/bounce/bounce_template.h
new file mode 100644
index 0000000..87835fc
--- /dev/null
+++ b/src/bounce/bounce_template.h
@@ -0,0 +1,99 @@
+#ifndef _BOUNCE_TEMPLATE_H_INCLUDED_
+#define _BOUNCE_TEMPLATE_H_INCLUDED_
+
+/*++
+/* NAME
+/* bounce_template 3h
+/* SUMMARY
+/* bounce template support
+/* SYNOPSIS
+/* #include <bounce_template.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * Structure of a single bounce template. Each template is manipulated by
+ * itself, without any external markers and delimiters. Applications are not
+ * supposed to access BOUNCE_TEMPLATE attributes directly.
+ */
+typedef struct BOUNCE_TEMPLATE {
+ int flags;
+ const char *class; /* for diagnostics (fixed) */
+ const char *origin; /* built-in or pathname */
+ const char *mime_charset; /* character set (configurable) */
+ const char *mime_encoding; /* 7bit or 8bit (derived) */
+ const char *obs_from; /* originator (configurable) */
+ const char *std_from; /* originator (configurable) */
+ const char *subject; /* general subject (configurable) */
+ const char *postmaster_subject; /* postmaster subject (configurable) */
+ const char **message_text; /* message text (configurable) */
+ const struct BOUNCE_TEMPLATE *prototype; /* defaults */
+ char *buffer; /* ripped text */
+} BOUNCE_TEMPLATE;
+
+#define BOUNCE_TMPL_FLAG_NEW_BUFFER (1<<0)
+
+#define BOUNCE_TMPL_CLASS_FAILURE "failure"
+#define BOUNCE_TMPL_CLASS_DELAY "delay"
+#define BOUNCE_TMPL_CLASS_SUCCESS "success"
+#define BOUNCE_TMPL_CLASS_VERIFY "verify"
+
+#define IS_FAILURE_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_FAILURE[0])
+#define IS_DELAY_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_DELAY[0])
+#define IS_SUCCESS_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_SUCCESS[0])
+
+#define bounce_template_encoding(t) ((t)->mime_encoding)
+#define bounce_template_charset(t) ((t)->mime_charset)
+
+typedef int PRINTFPTRLIKE(2, 3) (*BOUNCE_XP_PRN_FN) (VSTREAM *, const char *,...);
+typedef int (*BOUNCE_XP_PUT_FN) (VSTREAM *, const char *);
+
+extern BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *);
+extern void bounce_template_free(BOUNCE_TEMPLATE *);
+extern void bounce_template_load(BOUNCE_TEMPLATE *, const char *, const char *);
+extern void bounce_template_headers(BOUNCE_XP_PRN_FN, VSTREAM *, BOUNCE_TEMPLATE *, const char *, int);
+extern void bounce_template_expand(BOUNCE_XP_PUT_FN, VSTREAM *, BOUNCE_TEMPLATE *);
+extern void bounce_template_dump(VSTREAM *, BOUNCE_TEMPLATE *);
+
+#define POSTMASTER_COPY 1 /* postmaster copy */
+#define NO_POSTMASTER_COPY 0 /* not postmaster copy */
+
+ /*
+ * Structure of a bounce template collection. These templates are read and
+ * written in their external representation, with markers and delimiters.
+ */
+typedef struct {
+ BOUNCE_TEMPLATE *failure;
+ BOUNCE_TEMPLATE *delay;
+ BOUNCE_TEMPLATE *success;
+ BOUNCE_TEMPLATE *verify;
+} BOUNCE_TEMPLATES;
+
+BOUNCE_TEMPLATES *bounce_templates_create(void);
+void bounce_templates_free(BOUNCE_TEMPLATES *);
+void bounce_templates_load(VSTREAM *, BOUNCE_TEMPLATES *);
+void bounce_templates_expand(VSTREAM *, BOUNCE_TEMPLATES *);
+void bounce_templates_dump(VSTREAM *, BOUNCE_TEMPLATES *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/bounce/bounce_templates.c b/src/bounce/bounce_templates.c
new file mode 100644
index 0000000..f81dfd4
--- /dev/null
+++ b/src/bounce/bounce_templates.c
@@ -0,0 +1,399 @@
+/*++
+/* NAME
+/* bounce_templates 3
+/* SUMMARY
+/* bounce template group support
+/* SYNOPSIS
+/* #include <bounce_template.h>
+/*
+/* typedef struct {
+/* .in +4
+/* BOUNCE_TEMPLATE *failure;
+/* BOUNCE_TEMPLATE *delay;
+/* BOUNCE_TEMPLATE *success;
+/* BOUNCE_TEMPLATE *verify;
+/* .in -4
+/* } BOUNCE_TEMPLATES;
+/*
+/* BOUNCE_TEMPLATES *bounce_templates_create(void)
+/*
+/* void bounce_templates_free(templates)
+/* BOUNCE_TEMPLATES *templates;
+/*
+/* void bounce_templates_load(stream, templates)
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATES *templates;
+/*
+/* void bounce_templates_expand(stream, templates)
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATES *templates;
+/*
+/* void bounce_templates_dump(stream, templates)
+/* VSTREAM *stream;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements support for bounce template groups
+/* (i.e. groups that contain one template of each type).
+/*
+/* bounce_templates_create() creates a bounce template group,
+/* with default settings.
+/*
+/* bounce_templates_free() destroys a bounce template group.
+/*
+/* bounce_templates_load() reads zero or more bounce templates
+/* from the specified file to override built-in templates.
+/*
+/* bounce_templates_expand() expands $name macros and writes
+/* the text portions of the specified bounce template group
+/* to the specified stream.
+/*
+/* bounce_templates_dump() writes the complete content of the
+/* specified bounce template group to the specified stream.
+/* The format is compatible with bounce_templates_load().
+/* DIAGNOSTICS
+/* Fatal error: out of memory, undefined macro name in template.
+/* SEE ALSO
+/* bounce_template(3) bounce template support
+/* 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 <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+/* Global library. */
+
+#include <mail_addr.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <bounce_template.h>
+
+ /*
+ * The fail template is for permanent failure.
+ */
+static const char *def_bounce_failure_body[] = {
+ "This is the mail system at host $myhostname.",
+ "",
+ "I'm sorry to have to inform you that your message could not",
+ "be delivered to one or more recipients. It's attached below.",
+ "",
+ "For further assistance, please send mail to " MAIL_ADDR_POSTMASTER ".",
+ "",
+ "If you do so, please include this problem report. You can",
+ "delete your own text from the attached returned message.",
+ "",
+ " The mail system",
+ 0,
+};
+
+static const BOUNCE_TEMPLATE def_bounce_failure_template = {
+ 0,
+ BOUNCE_TMPL_CLASS_FAILURE,
+ "[built-in]",
+ "us-ascii",
+ MAIL_ATTR_ENC_7BIT,
+ MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)",
+ "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">",
+ "Undelivered Mail Returned to Sender",
+ "Postmaster Copy: Undelivered Mail",
+ def_bounce_failure_body,
+ &def_bounce_failure_template,
+};
+
+ /*
+ * The delay template is for delayed mail notifications.
+ */
+static const char *def_bounce_delay_body[] = {
+ "This is the mail system at host $myhostname.",
+ "",
+ "####################################################################",
+ "# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #",
+ "####################################################################",
+ "",
+ "Your message could not be delivered for more than $delay_warning_time_hours hour(s).",
+ "It will be retried until it is $maximal_queue_lifetime_days day(s) old.",
+ "",
+ "For further assistance, please send mail to " MAIL_ADDR_POSTMASTER ".",
+ "",
+ "If you do so, please include this problem report. You can",
+ "delete your own text from the attached returned message.",
+ "",
+ " The mail system",
+ 0,
+};
+
+static const BOUNCE_TEMPLATE def_bounce_delay_template = {
+ 0,
+ BOUNCE_TMPL_CLASS_DELAY,
+ "[built-in]",
+ "us-ascii",
+ MAIL_ATTR_ENC_7BIT,
+ MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)",
+ "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">",
+ "Delayed Mail (still being retried)",
+ "Postmaster Warning: Delayed Mail",
+ def_bounce_delay_body,
+ &def_bounce_delay_template
+};
+
+ /*
+ * The success template is for "delivered", "expanded" and "relayed" success
+ * notifications.
+ */
+static const char *def_bounce_success_body[] = {
+ "This is the mail system at host $myhostname.",
+ "",
+ "Your message was successfully delivered to the destination(s)",
+ "listed below. If the message was delivered to mailbox you will",
+ "receive no further notifications. Otherwise you may still receive",
+ "notifications of mail delivery errors from other systems.",
+ "",
+ " The mail system",
+ 0,
+};
+
+static const BOUNCE_TEMPLATE def_bounce_success_template = {
+ 0,
+ BOUNCE_TMPL_CLASS_SUCCESS,
+ "[built-in]",
+ "us-ascii",
+ MAIL_ATTR_ENC_7BIT,
+ MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)",
+ "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">",
+ "Successful Mail Delivery Report",
+ 0,
+ def_bounce_success_body,
+ &def_bounce_success_template,
+};
+
+ /*
+ * The "verify" template is for verbose delivery (sendmail -v) and for
+ * address verification (sendmail -bv).
+ */
+static const char *def_bounce_verify_body[] = {
+ "This is the mail system at host $myhostname.",
+ "",
+ "Enclosed is the mail delivery report that you requested.",
+ "",
+ " The mail system",
+ 0,
+};
+
+static const BOUNCE_TEMPLATE def_bounce_verify_template = {
+ 0,
+ BOUNCE_TMPL_CLASS_VERIFY,
+ "[built-in]",
+ "us-ascii",
+ MAIL_ATTR_ENC_7BIT,
+ MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)",
+ "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">",
+ "Mail Delivery Status Report",
+ 0,
+ def_bounce_verify_body,
+ &def_bounce_verify_template,
+};
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* bounce_templates_create - create template group */
+
+BOUNCE_TEMPLATES *bounce_templates_create(void)
+{
+ BOUNCE_TEMPLATES *bs;
+
+ bs = (BOUNCE_TEMPLATES *) mymalloc(sizeof(*bs));
+ bs->failure = bounce_template_create(&def_bounce_failure_template);
+ bs->delay = bounce_template_create(&def_bounce_delay_template);
+ bs->success = bounce_template_create(&def_bounce_success_template);
+ bs->verify = bounce_template_create(&def_bounce_verify_template);
+ return (bs);
+}
+
+/* bounce_templates_free - destroy template group */
+
+void bounce_templates_free(BOUNCE_TEMPLATES *bs)
+{
+ bounce_template_free(bs->failure);
+ bounce_template_free(bs->delay);
+ bounce_template_free(bs->success);
+ bounce_template_free(bs->verify);
+ myfree((void *) bs);
+}
+
+/* bounce_templates_load - load template or group from stream */
+
+void bounce_templates_load(VSTREAM *fp, BOUNCE_TEMPLATES *ts)
+{
+ VSTRING *line_buf;
+ char *member_name;
+ VSTRING *multi_line_buf = 0;
+ VSTRING *saved_member_name = 0;
+ VSTRING *saved_end_marker = 0;
+ char *value;
+ int lineno;
+ const char *err;
+ char *cp;
+ int len; /* Grr... */
+
+ /*
+ * XXX That's a lot of non-reusable code to parse a configuration file.
+ * Unfortunately, much of the "name = value" infrastructure is married to
+ * the dict(3) class which doesn't really help here.
+ */
+ line_buf = vstring_alloc(100);
+ lineno = 1;
+ while (vstring_get_nonl(line_buf, fp) > 0) {
+ lineno++;
+ cp = STR(line_buf) + strspn(STR(line_buf), " \t\n\v\f\r");
+ if (*cp == 0 || *cp == '#')
+ continue;
+ if ((err = split_nameval(STR(line_buf), &member_name, &value)) != 0)
+ msg_fatal("%s, line %d: %s: \"%s\"",
+ VSTREAM_PATH(fp), lineno, err, STR(line_buf));
+ if (value[0] == '<' && value[1] == '<') {
+ value += 2;
+ while (ISSPACE(*value))
+ value++;
+ if (*value == 0)
+ msg_fatal("%s, line %d: missing end marker after <<",
+ VSTREAM_PATH(fp), lineno);
+ if (!ISALNUM(*value))
+ msg_fatal("%s, line %d: malformed end marker after <<",
+ VSTREAM_PATH(fp), lineno);
+ if (multi_line_buf == 0) {
+ saved_member_name = vstring_alloc(100);
+ saved_end_marker = vstring_alloc(100);
+ multi_line_buf = vstring_alloc(100);
+ } else
+ VSTRING_RESET(multi_line_buf);
+ vstring_strcpy(saved_member_name, member_name);
+ vstring_strcpy(saved_end_marker, value);
+ while (vstring_get_nonl(line_buf, fp) > 0) {
+ lineno++;
+ if (strcmp(STR(line_buf), STR(saved_end_marker)) == 0)
+ break;
+ if (VSTRING_LEN(multi_line_buf) > 0)
+ vstring_strcat(multi_line_buf, "\n");
+ vstring_strcat(multi_line_buf, STR(line_buf));
+ }
+ if (vstream_feof(fp))
+ msg_warn("%s, line %d: missing \"%s\" end marker",
+ VSTREAM_PATH(fp), lineno, value);
+ member_name = STR(saved_member_name);
+ value = STR(multi_line_buf);
+ }
+#define MATCH_TMPL_NAME(tname, tname_len, mname) \
+ (strncmp(tname, mname, tname_len = strlen(tname)) == 0 \
+ && strcmp(mname + tname_len, "_template") == 0)
+
+ if (MATCH_TMPL_NAME(ts->failure->class, len, member_name))
+ bounce_template_load(ts->failure, VSTREAM_PATH(fp), value);
+ else if (MATCH_TMPL_NAME(ts->delay->class, len, member_name))
+ bounce_template_load(ts->delay, VSTREAM_PATH(fp), value);
+ else if (MATCH_TMPL_NAME(ts->success->class, len, member_name))
+ bounce_template_load(ts->success, VSTREAM_PATH(fp), value);
+ else if (MATCH_TMPL_NAME(ts->verify->class, len, member_name))
+ bounce_template_load(ts->verify, VSTREAM_PATH(fp), value);
+ else
+ msg_warn("%s, line %d: unknown template name: %s "
+ "-- ignoring this template",
+ VSTREAM_PATH(fp), lineno, member_name);
+ }
+ vstring_free(line_buf);
+ if (multi_line_buf) {
+ vstring_free(saved_member_name);
+ vstring_free(saved_end_marker);
+ vstring_free(multi_line_buf);
+ }
+}
+
+/* bounce_plain_out - output line as plain text */
+
+static int bounce_plain_out(VSTREAM *fp, const char *text)
+{
+ vstream_fprintf(fp, "%s\n", text);
+ return (0);
+}
+
+/* bounce_templates_expand - dump expanded template group text to stream */
+
+void bounce_templates_expand(VSTREAM *fp, BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_TEMPLATE *tp;
+
+ tp = ts->failure;
+ vstream_fprintf(fp, "expanded_%s_text = <<EOF\n", tp->class);
+ bounce_template_expand(bounce_plain_out, fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->delay;
+ vstream_fprintf(fp, "expanded_%s_text = <<EOF\n", tp->class);
+ bounce_template_expand(bounce_plain_out, fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->success;
+ vstream_fprintf(fp, "expanded_%s_text = <<EOF\n", tp->class);
+ bounce_template_expand(bounce_plain_out, fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->verify;
+ vstream_fprintf(fp, "expanded_%s_text = <<EOF\n", tp->class);
+ bounce_template_expand(bounce_plain_out, fp, tp);
+ vstream_fprintf(fp, "EOF\n");
+}
+
+/* bounce_templates_dump - dump bounce template group to stream */
+
+void bounce_templates_dump(VSTREAM *fp, BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_TEMPLATE *tp;
+
+ tp = ts->failure;
+ vstream_fprintf(fp, "%s_template = <<EOF\n", tp->class);
+ bounce_template_dump(fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->delay;
+ vstream_fprintf(fp, "%s_template = <<EOF\n", tp->class);
+ bounce_template_dump(fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->success;
+ vstream_fprintf(fp, "%s_template = <<EOF\n", tp->class);
+ bounce_template_dump(fp, tp);
+ vstream_fprintf(fp, "EOF\n\n");
+
+ tp = ts->verify;
+ vstream_fprintf(fp, "%s_template = <<EOF\n", tp->class);
+ bounce_template_dump(fp, tp);
+ vstream_fprintf(fp, "EOF\n");
+}
diff --git a/src/bounce/bounce_trace_service.c b/src/bounce/bounce_trace_service.c
new file mode 100644
index 0000000..8a62305
--- /dev/null
+++ b/src/bounce/bounce_trace_service.c
@@ -0,0 +1,220 @@
+/*++
+/* NAME
+/* bounce_trace_service 3
+/* SUMMARY
+/* send status report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_trace_service(flags, service, queue_name, queue_id,
+/* encoding, smtputf8, sender, envid,
+/* ret, templates)
+/* int flags;
+/* char *service;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *sender;
+/* char *envid;
+/* int ret;
+/* BOUNCE_TEMPLATES *templates;
+/* DESCRIPTION
+/* This module implements the server side of the trace_flush()
+/* (send delivery notice) request. The logfile
+/* is removed after the notice is posted.
+/*
+/* A status report includes a prelude with human-readable text,
+/* a DSN-style report, and the email message that was subject of
+/* the status report.
+/*
+/* When a status report is sent, the sender address is the empty
+/* address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+#include <deliver_request.h> /* USR_VRFY and RECORD flags */
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_trace_service - send a delivery status notice */
+
+int bounce_trace_service(int flags, char *service, char *queue_name,
+ char *queue_id, char *encoding,
+ int smtputf8,
+ char *recipient, char *dsn_envid,
+ int unused_dsn_ret,
+ BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 1;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ VSTRING *new_id;
+ int count;
+ const char *sender;
+
+ /*
+ * For consistency with fail/delay notifications, send notification for a
+ * non-bounce message as a single-bounce message, send notification for a
+ * single-bounce message as a double-bounce message, and drop requests to
+ * send notification for a double-bounce message.
+ */
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+
+ if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) {
+ msg_info("%s: not sending trace/success notification for "
+ "double-bounce message", queue_id);
+ return (0);
+ } else if (*recipient == 0) {
+ if ((notify_mask & MAIL_ERROR_2BOUNCE) != 0) {
+ recipient = var_2bounce_rcpt;
+ sender = mail_addr_double_bounce();
+ } else {
+ msg_info("%s: not sending trace/success notification "
+ "for single-bounce message", queue_id);
+ if (mail_queue_remove(service, queue_id) && errno != ENOENT)
+ msg_fatal("remove %s %s: %m", service, queue_id);
+ return (0);
+ }
+ } else {
+ /* Always send notification for non-bounce message. */
+ sender = NULL_SENDER;
+ }
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ *
+ * XXX DSN The trace service produces information from the trace logfile
+ * which is used for three types of reports:
+ *
+ * a) "what-if" reports that show what would happen without actually
+ * delivering mail (sendmail -bv).
+ *
+ * b) A report of actual deliveries (sendmail -v).
+ *
+ * c) DSN NOTIFY=SUCCESS reports of successful delivery ("delivered",
+ * "expanded" or "relayed").
+ */
+#define NON_DSN_FLAGS (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD)
+
+ bounce_info = bounce_mail_init(service, queue_name, queue_id,
+ encoding, smtputf8, dsn_envid,
+ flags & NON_DSN_FLAGS ?
+ ts->verify : ts->success);
+
+ /*
+ * XXX With multi-recipient mail some queue file recipients may have
+ * NOTIFY=SUCCESS and others not. Depending on what subset of recipients
+ * are delivered, a trace file may or may not be created. Even when the
+ * last partial delivery attempt had no NOTIFY=SUCCESS recipients, a
+ * trace file may still exist from a previous partial delivery attempt.
+ * So as long as any recipient in the original queue file had
+ * NOTIFY=SUCCESS we have to always look for the trace file and be
+ * prepared for the file not to exist.
+ *
+ * See also comments in qmgr/qmgr_active.c.
+ */
+ if (bounce_info->log_handle == 0) {
+ if (msg_verbose)
+ msg_info("%s: no trace file -- not sending a notification",
+ queue_id);
+ bounce_mail_free(bounce_info);
+ return (0);
+ }
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * Send a single bounce with a template message header, some boilerplate
+ * text that pretends that we are a polite mail system, the text with
+ * per-recipient status, and a copy of the original message.
+ *
+ * XXX DSN We use the same trace file for "what-if", "verbose delivery" and
+ * "success" delivery reports. This saves file system overhead because
+ * there are fewer potential left-over files to remove up when we create
+ * a new queue file.
+ */
+ new_id = vstring_alloc(10);
+ if ((bounce = post_mail_fopen_nowait(sender, recipient,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ count = -1;
+ if (bounce_header(bounce, bounce_info, recipient,
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender delivery status notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+
+ /*
+ * Examine the completion status. Delete the trace log file only when the
+ * status notice was posted successfully.
+ */
+ if (bounce_status == 0 && mail_queue_remove(service, queue_id)
+ && errno != ENOENT)
+ msg_fatal("remove %s %s: %m", service, queue_id);
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/bounce_warn_service.c b/src/bounce/bounce_warn_service.c
new file mode 100644
index 0000000..bbb805d
--- /dev/null
+++ b/src/bounce/bounce_warn_service.c
@@ -0,0 +1,295 @@
+/*++
+/* NAME
+/* bounce_warn_service 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* int bounce_warn_service(flags, service, queue_name, queue_id,
+/* encoding, smtputf8, sender, envid,
+/* dsn_ret, templates)
+/* int flags;
+/* char *service;
+/* char *queue_name;
+/* char *queue_id;
+/* char *encoding;
+/* int smtputf8;
+/* char *sender;
+/* char *envid;
+/* int dsn_ret;
+/* BOUNCE_TEMPLATES *ts;
+/* DESCRIPTION
+/* This module implements the server side of the bounce_warn()
+/* (send delay notice) request. The logfile
+/* is not removed, and a warning is sent instead of a bounce.
+/*
+/* When a message bounces, a full copy is sent to the originator,
+/* and an optional copy of the diagnostics with message headers is
+/* sent to the postmaster. The result is non-zero when the operation
+/* should be tried again.
+/*
+/* When a bounce is sent, the sender address is the empty
+/* address. When a bounce bounces, an optional double bounce
+/* with the entire undeliverable mail is sent to the postmaster,
+/* with as sender address the double bounce address.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+
+/* bounce_warn_service - send a delayed mail notice */
+
+int bounce_warn_service(int unused_flags, char *service, char *queue_name,
+ char *queue_id, char *encoding,
+ int smtputf8, char *recipient,
+ char *dsn_envid, int dsn_ret,
+ BOUNCE_TEMPLATES *ts)
+{
+ BOUNCE_INFO *bounce_info;
+ int bounce_status = 1;
+ int postmaster_status = 1;
+ VSTREAM *bounce;
+ int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
+ var_notify_classes);
+ VSTRING *new_id = vstring_alloc(10);
+ char *postmaster;
+ int count;
+
+ /*
+ * Initialize. Open queue file, bounce log, etc.
+ *
+ * XXX DSN This service produces RFC 3464-style "delayed mail" reports from
+ * information in the defer logfile. That same file is used for three
+ * different types of report:
+ *
+ * a) On-demand reports of all delayed deliveries by the mailq(1) command.
+ * This reports all recipients that have a transient delivery error.
+ *
+ * b) RFC 3464-style "delayed mail" notifications by the defer(8) service.
+ * This reports to the sender all recipients that have no DSN NOTIFY
+ * information (compatibility) and all recipients that have DSN
+ * NOTIFY=DELAY; this reports to postmaster all recipients, subject to
+ * notify_classes restrictions.
+ *
+ * c) RFC 3464-style bounce reports by the bounce(8) service when mail is
+ * too old. This reports to the sender all recipients that have no DSN
+ * NOTIFY information (compatibility) and all recipients that have DSN
+ * NOTIFY=FAILURE; this reports to postmaster all recipients, subject to
+ * notify_classes restrictions.
+ */
+ bounce_info = bounce_mail_init(service, queue_name, queue_id,
+ encoding, smtputf8, dsn_envid, ts->delay);
+
+#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */
+#define NULL_TRACE_FLAGS 0
+
+ /*
+ * The choice of sender address depends on the recipient address. For a
+ * single bounce (a non-delivery notification to the message originator),
+ * the sender address is the empty string. For a double bounce (typically
+ * a failed single bounce, or a postmaster notification that was produced
+ * by any of the mail processes) the sender address is defined by the
+ * var_double_bounce_sender configuration variable. When a double bounce
+ * cannot be delivered, the queue manager blackholes the resulting triple
+ * bounce message.
+ */
+
+ /*
+ * Double bounce failed. Never send a triple bounce.
+ *
+ * However, this does not prevent double bounces from bouncing on other
+ * systems. In order to cope with this, either the queue manager must
+ * recognize the double-bounce recipient address and discard mail, or
+ * every delivery agent must recognize the double-bounce sender address
+ * and substitute something else so mail does not come back at us.
+ */
+ if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) {
+ msg_warn("%s: undeliverable postmaster notification discarded",
+ queue_id);
+ bounce_status = 0;
+ }
+
+ /*
+ * Single bounce failed. Optionally send a double bounce to postmaster,
+ * subject to notify_classes restrictions.
+ */
+#define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE)
+#define SEND_POSTMASTER_DELAY_NOTICE (notify_mask & MAIL_ERROR_DELAY)
+
+ else if (*recipient == 0) {
+ if (!SEND_POSTMASTER_DELAY_NOTICE) {
+ bounce_status = 0;
+ } else {
+ postmaster = var_delay_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Double bounce to Postmaster. This is the last opportunity
+ * for this message to be delivered. Send the text with
+ * reason for the bounce, and the headers of the original
+ * message. Don't bother sending the boiler-plate text.
+ */
+ count = -1;
+ if (!bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY)
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_FULL);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: postmaster delay notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+ }
+ }
+
+ /*
+ * Non-bounce failed. Send a single bounce, subject to DSN NOTIFY
+ * restrictions.
+ */
+ else {
+ if ((bounce = post_mail_fopen_nowait(NULL_SENDER, recipient,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+
+ /*
+ * Send the bounce message header, some boilerplate text that
+ * pretends that we are a polite mail system, the text with
+ * reason for the bounce, and a copy of the original message.
+ */
+ count = -1;
+ if (bounce_header(bounce, bounce_info, recipient,
+ NO_POSTMASTER_COPY) == 0
+ && bounce_boilerplate(bounce, bounce_info) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_DELAY)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_DELAY) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ bounce_status = post_mail_fclose(bounce);
+ if (bounce_status == 0)
+ msg_info("%s: sender delay notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ bounce_status = 0;
+ }
+ }
+
+ /*
+ * Optionally send a postmaster notice, subject to notify_classes
+ * restrictions.
+ *
+ * This postmaster notice is not critical, so if it fails don't
+ * retransmit the bounce that we just generated, just log a warning.
+ */
+ if (bounce_status == 0 && SEND_POSTMASTER_DELAY_NOTICE
+ && strcasecmp_utf8(recipient, mail_addr_double_bounce()) != 0) {
+
+ /*
+ * Send the text with reason for the bounce, and the headers of
+ * the original message. Don't bother sending the boiler-plate
+ * text. This postmaster notice is not critical, so if it fails
+ * don't retransmit the bounce that we just generated, just log a
+ * warning.
+ */
+ postmaster = var_delay_rcpt;
+ if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ postmaster,
+ MAIL_SRC_MASK_BOUNCE,
+ NULL_TRACE_FLAGS,
+ smtputf8,
+ new_id)) != 0) {
+ count = -1;
+ if (bounce_header(bounce, bounce_info, postmaster,
+ POSTMASTER_COPY) == 0
+ && (count = bounce_diagnostic_log(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE)) > 0
+ && bounce_header_dsn(bounce, bounce_info) == 0
+ && bounce_diagnostic_dsn(bounce, bounce_info,
+ DSN_NOTIFY_OVERRIDE) > 0) {
+ bounce_original(bounce, bounce_info, DSN_RET_HDRS);
+ postmaster_status = post_mail_fclose(bounce);
+ if (postmaster_status == 0)
+ msg_info("%s: postmaster delay notification: %s",
+ queue_id, STR(new_id));
+ } else {
+ (void) vstream_fclose(bounce);
+ if (count == 0)
+ postmaster_status = 0;
+ }
+ }
+ if (postmaster_status)
+ msg_warn("%s: postmaster notice failed while warning %s",
+ queue_id, recipient);
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ bounce_mail_free(bounce_info);
+ vstring_free(new_id);
+
+ return (bounce_status);
+}
diff --git a/src/bounce/logfile-no-msgid-no-eoh-event b/src/bounce/logfile-no-msgid-no-eoh-event
new file mode 100644
index 0000000..50233c8
--- /dev/null
+++ b/src/bounce/logfile-no-msgid-no-eoh-event
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 272
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-no-msgid-with-eoh-event b/src/bounce/logfile-no-msgid-with-eoh-event
new file mode 100644
index 0000000..00473a8
--- /dev/null
+++ b/src/bounce/logfile-no-msgid-with-eoh-event
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 271
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-with-msgid-no-eoh-event b/src/bounce/logfile-with-msgid-no-eoh-event
new file mode 100644
index 0000000..00473a8
--- /dev/null
+++ b/src/bounce/logfile-with-msgid-no-eoh-event
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 271
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-with-msgid-with-eoh-event b/src/bounce/logfile-with-msgid-with-eoh-event
new file mode 100644
index 0000000..00473a8
--- /dev/null
+++ b/src/bounce/logfile-with-msgid-with-eoh-event
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 271
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-with-msgid-with-filter b/src/bounce/logfile-with-msgid-with-filter
new file mode 100644
index 0000000..4d56216
--- /dev/null
+++ b/src/bounce/logfile-with-msgid-with-filter
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 291
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/logfile-with-msgid-with-long-line b/src/bounce/logfile-with-msgid-with-long-line
new file mode 100644
index 0000000..00473a8
--- /dev/null
+++ b/src/bounce/logfile-with-msgid-with-long-line
@@ -0,0 +1,13 @@
+
+recipient = rcpt-address
+original_recipient = rcpt-orig_addr
+offset = 271
+notify_flags = rcpt-dsn_notify
+status = dsn-status
+action = dsn-action
+diag_type = dsn-dtype
+diag_text = dsn-dtext
+mta_type = dsn-mtype
+mta_mname = dsn-mname
+reason = dsn-reason
+
diff --git a/src/bounce/msgfile-no-msgid-no-eoh-event b/src/bounce/msgfile-no-msgid-no-eoh-event
new file mode 100755
index 0000000..694f5db
--- /dev/null
+++ b/src/bounce/msgfile-no-msgid-no-eoh-event
Binary files differ
diff --git a/src/bounce/msgfile-no-msgid-with-eoh-event b/src/bounce/msgfile-no-msgid-with-eoh-event
new file mode 100755
index 0000000..b3b795e
--- /dev/null
+++ b/src/bounce/msgfile-no-msgid-with-eoh-event
Binary files differ
diff --git a/src/bounce/msgfile-with-msgid-no-eoh-event b/src/bounce/msgfile-with-msgid-no-eoh-event
new file mode 100755
index 0000000..cc16c66
--- /dev/null
+++ b/src/bounce/msgfile-with-msgid-no-eoh-event
Binary files differ
diff --git a/src/bounce/msgfile-with-msgid-with-eoh-event b/src/bounce/msgfile-with-msgid-with-eoh-event
new file mode 100755
index 0000000..ffe30c4
--- /dev/null
+++ b/src/bounce/msgfile-with-msgid-with-eoh-event
Binary files differ
diff --git a/src/bounce/msgfile-with-msgid-with-filter b/src/bounce/msgfile-with-msgid-with-filter
new file mode 100755
index 0000000..d6e77c4
--- /dev/null
+++ b/src/bounce/msgfile-with-msgid-with-filter
Binary files differ
diff --git a/src/bounce/msgfile-with-msgid-with-long-line b/src/bounce/msgfile-with-msgid-with-long-line
new file mode 100755
index 0000000..69d2801
--- /dev/null
+++ b/src/bounce/msgfile-with-msgid-with-long-line
Binary files differ
diff --git a/src/bounce/no-msgid-no-eoh-event-no-thread.ref b/src/bounce/no-msgid-no-eoh-event-no-thread.ref
new file mode 100644
index 0000000..630734d
--- /dev/null
+++ b/src/bounce/no-msgid-no-eoh-event-no-thread.ref
@@ -0,0 +1,47 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 19:04:29 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CklpP1JjKz4w4Z; Mon, 30 Nov 2020 00:04:29 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Subject: no-msgid-no-eoh-event
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/no-msgid-no-eoh-event-with-thread.ref b/src/bounce/no-msgid-no-eoh-event-with-thread.ref
new file mode 100644
index 0000000..630734d
--- /dev/null
+++ b/src/bounce/no-msgid-no-eoh-event-with-thread.ref
@@ -0,0 +1,47 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 19:04:29 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CklpP1JjKz4w4Z; Mon, 30 Nov 2020 00:04:29 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Subject: no-msgid-no-eoh-event
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/no-msgid-with-eoh-event-no-thread.ref b/src/bounce/no-msgid-with-eoh-event-no-thread.ref
new file mode 100644
index 0000000..39b5841
--- /dev/null
+++ b/src/bounce/no-msgid-with-eoh-event-no-thread.ref
@@ -0,0 +1,49 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 19:11:52 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4Cklyw0HDKz4w4Z; Mon, 30 Nov 2020 00:11:52 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Subject: no-msgid-with-eoh-event
+
+body text
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/no-msgid-with-eoh-event-with-thread.ref b/src/bounce/no-msgid-with-eoh-event-with-thread.ref
new file mode 100644
index 0000000..39b5841
--- /dev/null
+++ b/src/bounce/no-msgid-with-eoh-event-with-thread.ref
@@ -0,0 +1,49 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 19:11:52 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4Cklyw0HDKz4w4Z; Mon, 30 Nov 2020 00:11:52 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Subject: no-msgid-with-eoh-event
+
+body text
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/obs_template_test.ref b/src/bounce/obs_template_test.ref
new file mode 100644
index 0000000..e03bb5b
--- /dev/null
+++ b/src/bounce/obs_template_test.ref
@@ -0,0 +1,68 @@
+failure_template = <<EOF
+Charset: us-ascii
+From: MAILER-DAEMON (Mail Delivery System)
+Subject: Undelivered Mail Returned to Sender
+Postmaster-Subject: Postmaster Copy: Undelivered Mail
+
+This is the mail system at host $myhostname.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+delay_template = <<EOF
+Charset: us-ascii
+From: MAILER-DAEMON (Mail Delivery System)
+Subject: Delayed Mail (still being retried)
+Postmaster-Subject: Postmaster Warning: Delayed Mail
+
+This is the mail system at host $myhostname.
+
+####################################################################
+# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #
+####################################################################
+
+Your message could not be delivered for more than $delay_warning_time_hours hour(s).
+It will be retried until it is $maximal_queue_lifetime_days day(s) old.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+success_template = <<EOF
+Charset: us-ascii
+From: MAILER-DAEMON (Mail Delivery System)
+Subject: Successful Mail Delivery Report
+
+This is the mail system at host $myhostname.
+
+Your message was successfully delivered to the destination(s)
+listed below. If the message was delivered to mailbox you will
+receive no further notifications. Otherwise you may still receive
+notifications of mail delivery errors from other systems.
+
+ The mail system
+EOF
+
+verify_template = <<EOF
+Charset: us-ascii
+From: MAILER-DAEMON (Mail Delivery System)
+Subject: Mail Delivery Status Report
+
+This is the mail system at host $myhostname.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+EOF
diff --git a/src/bounce/template_test.ref b/src/bounce/template_test.ref
new file mode 100644
index 0000000..381c14d
--- /dev/null
+++ b/src/bounce/template_test.ref
@@ -0,0 +1,68 @@
+failure_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+Postmaster-Subject: Postmaster Copy: Undelivered Mail
+
+This is the mail system at host $myhostname.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+delay_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Delayed Mail (still being retried)
+Postmaster-Subject: Postmaster Warning: Delayed Mail
+
+This is the mail system at host $myhostname.
+
+####################################################################
+# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #
+####################################################################
+
+Your message could not be delivered for more than $delay_warning_time_hours hour(s).
+It will be retried until it is $maximal_queue_lifetime_days day(s) old.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+ The mail system
+EOF
+
+success_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Successful Mail Delivery Report
+
+This is the mail system at host $myhostname.
+
+Your message was successfully delivered to the destination(s)
+listed below. If the message was delivered to mailbox you will
+receive no further notifications. Otherwise you may still receive
+notifications of mail delivery errors from other systems.
+
+ The mail system
+EOF
+
+verify_template = <<EOF
+Charset: us-ascii
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Mail Delivery Status Report
+
+This is the mail system at host $myhostname.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+EOF
diff --git a/src/bounce/with-msgid-no-eoh-event-no-thread.ref b/src/bounce/with-msgid-no-eoh-event-no-thread.ref
new file mode 100644
index 0000000..020d9a4
--- /dev/null
+++ b/src/bounce/with-msgid-no-eoh-event-no-thread.ref
@@ -0,0 +1,49 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY0myNz4w4g; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-no-eoh-event
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-no-eoh-event-with-thread.ref b/src/bounce/with-msgid-no-eoh-event-with-thread.ref
new file mode 100644
index 0000000..d2aadd0
--- /dev/null
+++ b/src/bounce/with-msgid-no-eoh-event-with-thread.ref
@@ -0,0 +1,51 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+References: <12345@mta-name.example>
+In-Reply-To: <12345@mta-name.example>
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY0myNz4w4g; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-no-eoh-event
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-eoh-event-no-thread.ref b/src/bounce/with-msgid-with-eoh-event-no-thread.ref
new file mode 100644
index 0000000..70afb5f
--- /dev/null
+++ b/src/bounce/with-msgid-with-eoh-event-no-thread.ref
@@ -0,0 +1,51 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY10M8z4w4l; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-with-eoh-event
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+body text
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-eoh-event-with-thread.ref b/src/bounce/with-msgid-with-eoh-event-with-thread.ref
new file mode 100644
index 0000000..e46afbe
--- /dev/null
+++ b/src/bounce/with-msgid-with-eoh-event-with-thread.ref
@@ -0,0 +1,53 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+References: <12345@mta-name.example>
+In-Reply-To: <12345@mta-name.example>
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY10M8z4w4l; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-with-eoh-event
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+body text
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-filter-no-thread.ref b/src/bounce/with-msgid-with-filter-no-thread.ref
new file mode 100644
index 0000000..fa30ddf
--- /dev/null
+++ b/src/bounce/with-msgid-with-filter-no-thread.ref
@@ -0,0 +1,48 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sat, 5 Dec 2020 13:31:48 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CpJ7m6tprz4w4Y; Sat, 5 Dec 2020 18:31:48 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-no-eoh-event
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-filter-with-thread.ref b/src/bounce/with-msgid-with-filter-with-thread.ref
new file mode 100644
index 0000000..14d3373
--- /dev/null
+++ b/src/bounce/with-msgid-with-filter-with-thread.ref
@@ -0,0 +1,50 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+References: <12345@mta-name.example>
+In-Reply-To: <12345@mta-name.example>
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sat, 5 Dec 2020 13:31:48 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CpJ7m6tprz4w4Y; Sat, 5 Dec 2020 18:31:48 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-no-eoh-event
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-long-line-no-thread.ref b/src/bounce/with-msgid-with-long-line-no-thread.ref
new file mode 100644
index 0000000..fa5268b
--- /dev/null
+++ b/src/bounce/with-msgid-with-long-line-no-thread.ref
@@ -0,0 +1,50 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY194lz4w4n; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Whatever: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-with-long-line
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+--msgid.unix-time/mail.example--
diff --git a/src/bounce/with-msgid-with-long-line-with-thread.ref b/src/bounce/with-msgid-with-long-line-with-thread.ref
new file mode 100644
index 0000000..04e96d6
--- /dev/null
+++ b/src/bounce/with-msgid-with-long-line-with-thread.ref
@@ -0,0 +1,52 @@
+From: Mail Delivery System <MAILER-DAEMON>
+Subject: Undelivered Mail Returned to Sender
+To: test-recipient
+References: <12345@mta-name.example>
+In-Reply-To: <12345@mta-name.example>
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="msgid.unix-time/mail.example"
+Content-Transfer-Encoding: 7bit
+
+This is a MIME-encapsulated message.
+
+--msgid.unix-time/mail.example
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+
+<rcpt-address> (expanded from <rcpt-orig_addr>): dsn-reason
+
+--msgid.unix-time/mail.example
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail.example
+Original-Envelope-Id: TEST-ENVID
+X-Postfix-Queue-ID: msgid
+X-Postfix-Sender: rfc822; sender@sender.example
+Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST)
+
+Final-Recipient: rfc822; rcpt-address
+Original-Recipient: rfc822; rcpt-orig_addr
+Action: failed
+Status: dsn-status
+Remote-MTA: dsn-mtype; dsn-mname
+Diagnostic-Code: dsn-dtype; dsn-dtext
+
+--msgid.unix-time/mail.example
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <sender@sender.example>
+Received: by wzv.porcupine.org (Postfix, from userid 0)
+ id 4CkXPY194lz4w4n; Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+From: <sender@sender.example>
+To: <recipient@recipient.example>
+Whatever: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+Message-Id: <12345@mta-name.example>
+Subject: with-msgid-with-long-line
+Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC)
+
+--msgid.unix-time/mail.example--