diff options
Diffstat (limited to 'src/bounce')
l--------- | src/bounce/.indent.pro | 1 | ||||
-rw-r--r-- | src/bounce/.printfck | 25 | ||||
-rw-r--r-- | src/bounce/2template_test.in | 136 | ||||
-rw-r--r-- | src/bounce/Makefile.in | 426 | ||||
-rwxr-xr-x | src/bounce/annotate.sh | 120 | ||||
-rw-r--r-- | src/bounce/bounce.c | 680 | ||||
-rw-r--r-- | src/bounce/bounce_append_service.c | 168 | ||||
-rw-r--r-- | src/bounce/bounce_cleanup.c | 177 | ||||
-rw-r--r-- | src/bounce/bounce_notify_service.c | 327 | ||||
-rw-r--r-- | src/bounce/bounce_notify_util.c | 903 | ||||
-rw-r--r-- | src/bounce/bounce_notify_verp.c | 267 | ||||
-rw-r--r-- | src/bounce/bounce_one_service.c | 266 | ||||
-rw-r--r-- | src/bounce/bounce_service.h | 115 | ||||
-rw-r--r-- | src/bounce/bounce_template.c | 540 | ||||
-rw-r--r-- | src/bounce/bounce_template.h | 94 | ||||
-rw-r--r-- | src/bounce/bounce_templates.c | 391 | ||||
-rw-r--r-- | src/bounce/bounce_trace_service.c | 220 | ||||
-rw-r--r-- | src/bounce/bounce_warn_service.c | 295 | ||||
-rw-r--r-- | src/bounce/template_test.ref | 68 |
19 files changed, 5219 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..8afb4ce --- /dev/null +++ b/src/bounce/2template_test.in @@ -0,0 +1,136 @@ +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 +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/Makefile.in b/src/bounce/Makefile.in new file mode 100644 index 0000000..80f038f --- /dev/null +++ b/src/bounce/Makefile.in @@ -0,0 +1,426 @@ +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= +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 >$@ + +main.cf: + echo queue_directory=. >main.cf + echo myhostname=example.com >>main.cf + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: update template_test 2template_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 + +# Avoid dependency on installed Postfix. +# XXX This still requires that default_privs, mail_owner etc. accounts exist. +template_test: $(PROG) main.cf template_test.ref + 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 + +2template_test: $(PROG) main.cf template_test.ref 2template_test.in + 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 + +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/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/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_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/check_arg.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/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_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..afbeb63 --- /dev/null +++ b/src/bounce/bounce.c @@ -0,0 +1,680 @@ +/*++ +/* 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. +/* 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> + +/* 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; + + /* + * 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; + +#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); + + /* + * 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) +{ + + /* + * 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, + 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_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..7c79ca0 --- /dev/null +++ b/src/bounce/bounce_notify_util.c @@ -0,0 +1,903 @@ +/*++ +/* 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> + +/* Application-specific. */ + +#include "bounce_service.h" + +#define STR vstring_str + +/* 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; + + /* + * 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->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)); + while ((rec_type = rec_get(bounce_info->orig_fp, + bounce_info->buf, 0)) > 0) { + + /* + * 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); + break; + } + if (bounce_info->orig_offs > 0 + && bounce_info->arrival_time > 0 + && VSTRING_LEN(bounce_info->sender) > 0) + break; + } + } + 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); + 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); + + /* + * 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 ontaining 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_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..f8cc4d6 --- /dev/null +++ b/src/bounce/bounce_service.h @@ -0,0 +1,115 @@ +/*++ +/* 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_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 */ + 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..e54082d --- /dev/null +++ b/src/bounce/bounce_template.c @@ -0,0 +1,540 @@ +/*++ +/* 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) +/* int IS_VERIFY_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 +/*--*/ + +/* 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> + +/* Application-specific. */ + +#include <bounce_template.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->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", tp->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", tp->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..9bec429 --- /dev/null +++ b/src/bounce/bounce_template.h @@ -0,0 +1,94 @@ +#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 *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 IS_VERIFY_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_verify[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 +/*--*/ + +#endif diff --git a/src/bounce/bounce_templates.c b/src/bounce/bounce_templates.c new file mode 100644 index 0000000..11daece --- /dev/null +++ b/src/bounce/bounce_templates.c @@ -0,0 +1,391 @@ +/*++ +/* 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 +/*--*/ + +/* 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)", + "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)", + "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)", + "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 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/template_test.ref b/src/bounce/template_test.ref new file mode 100644 index 0000000..e03bb5b --- /dev/null +++ b/src/bounce/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 |