summaryrefslogtreecommitdiffstats
path: root/src/bounce/bounce_notify_util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bounce/bounce_notify_util.c')
-rw-r--r--src/bounce/bounce_notify_util.c1010
1 files changed, 1010 insertions, 0 deletions
diff --git a/src/bounce/bounce_notify_util.c b/src/bounce/bounce_notify_util.c
new file mode 100644
index 0000000..781a525
--- /dev/null
+++ b/src/bounce/bounce_notify_util.c
@@ -0,0 +1,1010 @@
+/*++
+/* NAME
+/* bounce_notify_util 3
+/* SUMMARY
+/* send non-delivery report to sender, server side
+/* SYNOPSIS
+/* #include "bounce_service.h"
+/*
+/* typedef struct {
+/* .in +4
+/* /* All private members... */
+/* .in -4
+/* } BOUNCE_INFO;
+/*
+/* BOUNCE_INFO *bounce_mail_init(service, queue_name, queue_id, encoding,
+/* smtputf8, dsn_envid, template)
+/* const char *service;
+/* const char *queue_name;
+/* const char *queue_id;
+/* const char *encoding;
+/* int smtputf8;
+/* const char *dsn_envid;
+/* const BOUNCE_TEMPLATE *template;
+/*
+/* BOUNCE_INFO *bounce_mail_one_init(queue_name, queue_id, encoding,
+/* smtputf8, dsn_envid, dsn_notify,
+/* rcpt_buf, dsn_buf, template)
+/* const char *queue_name;
+/* const char *queue_id;
+/* const char *encoding;
+/* int smtputf8;
+/* int dsn_notify;
+/* const char *dsn_envid;
+/* RCPT_BUF *rcpt_buf;
+/* DSN_BUF *dsn_buf;
+/* const BOUNCE_TEMPLATE *template;
+/*
+/* void bounce_mail_free(bounce_info)
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_header(fp, bounce_info, recipient, postmaster_copy)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* const char *recipient;
+/* int postmaster_copy;
+/*
+/* int bounce_boilerplate(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_recipient_log(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_diagnostic_log(fp, bounce_info, notify_filter)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* int notify_filter;
+/*
+/* int bounce_header_dsn(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_recipient_dsn(fp, bounce_info)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/*
+/* int bounce_diagnostic_dsn(fp, bounce_info, notify_filter)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* int notify_filter;
+/*
+/* int bounce_original(fp, bounce_info, headers_only)
+/* VSTREAM *fp;
+/* BOUNCE_INFO *bounce_info;
+/* int headers_only;
+/*
+/* void bounce_delrcpt(bounce_info)
+/* BOUNCE_INFO *bounce_info;
+/*
+/* void bounce_delrcpt_one(bounce_info)
+/* BOUNCE_INFO *bounce_info;
+/* DESCRIPTION
+/* This module implements the grunt work of sending a non-delivery
+/* notification. A bounce is sent in a form that satisfies RFC 1894
+/* (delivery status notifications).
+/*
+/* bounce_mail_init() bundles up its argument and attempts to
+/* open the corresponding logfile and message file. A BOUNCE_INFO
+/* structure contains all the necessary information about an
+/* undeliverable message.
+/*
+/* bounce_mail_one_init() provides the same function for only
+/* one recipient that is not read from bounce logfile.
+/*
+/* bounce_mail_free() releases memory allocated by bounce_mail_init()
+/* and closes any files opened by bounce_mail_init().
+/*
+/* bounce_header() produces a standard mail header with the specified
+/* recipient and starts a text/plain message segment for the
+/* human-readable problem description. postmaster_copy is either
+/* POSTMASTER_COPY or NO_POSTMASTER_COPY.
+/*
+/* bounce_boilerplate() produces the standard "sorry" text that
+/* creates the illusion that mail systems are civilized.
+/*
+/* bounce_recipient_log() sends a human-readable representation of
+/* logfile information for one recipient, with the recipient address
+/* and with the text why the recipient was undeliverable.
+/*
+/* bounce_diagnostic_log() sends a human-readable representation of
+/* logfile information for all undeliverable recipients. The
+/* notify_filter specifies what recipient status records should be
+/* reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
+/* In the absence of DSN NOTIFY information all records are reported.
+/* The result value is -1 in case of error, the number of reported
+/* recipients in case of success.
+/*
+/* bounce_header_dsn() starts a message/delivery-status message
+/* segment and sends the machine-readable information that identifies
+/* the reporting MTA.
+/*
+/* bounce_recipient_dsn() sends a machine-readable representation of
+/* logfile information for one recipient, with the recipient address
+/* and with the text why the recipient was undeliverable.
+/*
+/* bounce_diagnostic_dsn() sends a machine-readable representation of
+/* logfile information for all undeliverable recipients. The
+/* notify_filter specifies what recipient status records should be
+/* reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
+/* In the absence of DSN NOTIFY information all records are reported.
+/* The result value is -1 in case of error, the number of reported
+/* recipients in case of success.
+/*
+/* bounce_original() starts a message/rfc822 or text/rfc822-headers
+/* message segment and sends the original message, either full
+/* (DSN_RET_FULL) or message headers only (DSN_RET_HDRS).
+/*
+/* bounce_delrcpt() deletes recipients in the logfile from the original
+/* queue file.
+/*
+/* bounce_delrcpt_one() deletes one recipient from the original
+/* queue file.
+/* DIAGNOSTICS
+/* Fatal error: error opening existing file.
+/* BUGS
+/* SEE ALSO
+/* bounce(3) basic bounce service client interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h> /* sscanf() */
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <line_wrap.h>
+#include <stringops.h>
+#include <myflock.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <quote_822_local.h>
+#include <mail_params.h>
+#include <is_header.h>
+#include <record.h>
+#include <rec_type.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <bounce_log.h>
+#include <mail_date.h>
+#include <mail_proto.h>
+#include <lex_822.h>
+#include <deliver_completed.h>
+#include <dsn_mask.h>
+#include <smtputf8.h>
+#include <header_opts.h>
+
+/* Application-specific. */
+
+#include "bounce_service.h"
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* bounce_mail_alloc - initialize */
+
+static BOUNCE_INFO *bounce_mail_alloc(const char *service,
+ const char *queue_name,
+ const char *queue_id,
+ const char *encoding,
+ int smtputf8,
+ const char *dsn_envid,
+ RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf,
+ BOUNCE_TEMPLATE *template,
+ BOUNCE_LOG *log_handle)
+{
+ BOUNCE_INFO *bounce_info;
+ int rec_type;
+ int prev_type;
+ int all_headers_seen = 0;
+ int skip_message_segment = 0;
+ int in_envelope = 1;
+
+ /*
+ * Bundle up a bunch of parameters and initialize information that will
+ * be discovered on the fly.
+ *
+ * XXX Instead of overriding the returned-message MIME encoding, separate
+ * the returned-message MIME encoding from the (boiler plate, delivery
+ * status) MIME encoding.
+ */
+ bounce_info = (BOUNCE_INFO *) mymalloc(sizeof(*bounce_info));
+ bounce_info->service = service;
+ bounce_info->queue_name = queue_name;
+ bounce_info->queue_id = queue_id;
+ bounce_info->smtputf8 = smtputf8;
+ /* Fix 20140708: override MIME encoding: addresses may be 8bit. */
+ /* Fix 20140718: override MIME encoding: 8bit $myhostname expansion. */
+ if (var_smtputf8_enable /* was: bounce_info->smtputf8 */ ) {
+ bounce_info->mime_encoding = "8bit";
+ } else if (strcmp(encoding, MAIL_ATTR_ENC_8BIT) == 0) {
+ bounce_info->mime_encoding = "8bit";
+ } else if (strcmp(encoding, MAIL_ATTR_ENC_7BIT) == 0) {
+ bounce_info->mime_encoding = "7bit";
+ } else {
+ if (strcmp(encoding, MAIL_ATTR_ENC_NONE) != 0)
+ msg_warn("%s: unknown encoding: %.200s",
+ bounce_info->queue_id, encoding);
+ bounce_info->mime_encoding = 0;
+ }
+ if (dsn_envid && *dsn_envid)
+ bounce_info->dsn_envid = dsn_envid;
+ else
+ bounce_info->dsn_envid = 0;
+ bounce_info->template = template;
+ bounce_info->buf = vstring_alloc(100);
+ bounce_info->sender = vstring_alloc(100);
+ bounce_info->arrival_time = 0;
+ bounce_info->orig_offs = 0;
+ bounce_info->message_size = 0;
+ bounce_info->orig_msgid = vstring_alloc(100);
+ bounce_info->rcpt_buf = rcpt_buf;
+ bounce_info->dsn_buf = dsn_buf;
+ bounce_info->log_handle = log_handle;
+
+ /*
+ * RFC 1894: diagnostic-type is an RFC 822 atom. We use X-$mail_name and
+ * must ensure it is valid.
+ */
+ bounce_info->mail_name = mystrdup(var_mail_name);
+ translit(bounce_info->mail_name, " \t\r\n()<>@,;:\\\".[]",
+ "-----------------");
+
+ /*
+ * Compute a supposedly unique boundary string. This assumes that a queue
+ * ID and a hostname contain acceptable characters for a boundary string,
+ * but the assumption is not verified.
+ */
+ vstring_sprintf(bounce_info->buf, "%s.%lu/%s",
+ queue_id, (unsigned long) event_time(), var_myhostname);
+ bounce_info->mime_boundary = mystrdup(STR(bounce_info->buf));
+
+ /*
+ * If the original message cannot be found, do not raise a run-time
+ * error. There is nothing we can do about the error, and all we are
+ * doing is to inform the sender of a delivery problem. Bouncing a
+ * message does not have to be a perfect job. But if the system IS
+ * running out of resources, raise a fatal run-time error and force a
+ * backoff.
+ */
+ if ((bounce_info->orig_fp = mail_queue_open(queue_name, queue_id,
+ O_RDWR, 0)) == 0
+ && errno != ENOENT)
+ msg_fatal("open %s %s: %m", service, queue_id);
+
+ /*
+ * Get time/size/sender information from the original message envelope
+ * records. If the envelope is corrupted just send whatever we can
+ * (remember this is a best effort, it does not have to be perfect).
+ *
+ * Lock the file for shared use, so that queue manager leaves it alone after
+ * restarting.
+ */
+#define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT)
+
+ if (bounce_info->orig_fp != 0) {
+ if (myflock(vstream_fileno(bounce_info->orig_fp), INTERNAL_LOCK,
+ DELIVER_LOCK_MODE) < 0)
+ msg_fatal("cannot get shared lock on %s: %m",
+ VSTREAM_PATH(bounce_info->orig_fp));
+ for (prev_type = 0;
+ (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0;
+ prev_type = rec_type) {
+
+ /*
+ * Postfix version dependent: data offset in SIZE record.
+ */
+ if (rec_type == REC_TYPE_SIZE) {
+ if (bounce_info->message_size == 0)
+ sscanf(STR(bounce_info->buf), "%ld %ld",
+ &bounce_info->message_size,
+ &bounce_info->orig_offs);
+ if (bounce_info->message_size < 0)
+ bounce_info->message_size = 0;
+ if (bounce_info->orig_offs < 0)
+ bounce_info->orig_offs = 0;
+ }
+
+ /*
+ * Information for the Arrival-Date: attribute.
+ */
+ else if (rec_type == REC_TYPE_TIME) {
+ if (bounce_info->arrival_time == 0
+ && (bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0)
+ bounce_info->arrival_time = 0;
+ }
+
+ /*
+ * Information for the X-Postfix-Sender: attribute.
+ */
+ else if (rec_type == REC_TYPE_FROM) {
+ quote_822_local_flags(bounce_info->sender,
+ VSTRING_LEN(bounce_info->buf) ?
+ STR(bounce_info->buf) :
+ mail_addr_mail_daemon(), 0);
+ }
+
+ /*
+ * Backwards compatibility: no data offset in SIZE record.
+ */
+ else if (rec_type == REC_TYPE_MESG) {
+ /* XXX Future: sender+recipient after message content. */
+ if (VSTRING_LEN(bounce_info->sender) == 0)
+ msg_warn("%s: no sender before message content record",
+ bounce_info->queue_id);
+ bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp);
+ if (var_threaded_bounce == 0)
+ skip_message_segment = 1;
+ else
+ in_envelope = 0;
+ }
+
+ /*
+ * Extract Message-ID for threaded bounces.
+ */
+ else if (in_envelope == 0
+ && (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT)) {
+ const HEADER_OPTS *hdr;
+ char *cp;
+
+ /*
+ * Skip records that we cannot use. Degrade if we could not
+ * skip over the message content.
+ */
+ if (var_threaded_bounce == 0 || all_headers_seen
+ || prev_type == REC_TYPE_CONT) {
+ /* void */ ;
+ }
+
+ /*
+ * Extract message-id header value.
+ */
+ else if (is_header(STR(bounce_info->buf))) {
+ if ((hdr = header_opts_find(
+ vstring_str(bounce_info->buf))) != 0
+ && hdr->type == HDR_MESSAGE_ID) {
+ vstring_truncate(bounce_info->buf,
+ trimblanks(STR(bounce_info->buf),
+ LEN(bounce_info->buf))
+ - STR(bounce_info->buf));
+ cp = STR(bounce_info->buf) + strlen(hdr->name) + 1;
+ while (ISSPACE(*cp))
+ cp++;
+ if (*cp == '<' && vstring_end(bounce_info->buf)[-1] == '>')
+ vstring_strcpy(bounce_info->orig_msgid, cp);
+ else
+ msg_warn("%s: ignoring malformed Message-ID",
+ bounce_info->queue_id);
+ }
+ }
+
+ /*
+ * Skip remainder of multiline header.
+ */
+ else if (ISSPACE(*STR(bounce_info->buf))) {
+ /* void */ ;
+ }
+
+ /*
+ * Start of body.
+ */
+ else {
+ all_headers_seen = 1;
+ skip_message_segment = 1;
+ }
+ }
+
+ /*
+ * In case we ever want to process records from the extracted
+ * segment, and in case there was no "start of body" event.
+ */
+ else if (rec_type == REC_TYPE_XTRA) {
+ if (VSTRING_LEN(bounce_info->orig_msgid) == 0)
+ if (var_threaded_bounce)
+ all_headers_seen = 1;
+ in_envelope = 1;
+ }
+
+ /*
+ * Are we done yet?
+ */
+ if (bounce_info->orig_offs > 0
+ && bounce_info->arrival_time > 0
+ && VSTRING_LEN(bounce_info->sender) > 0
+ && (var_threaded_bounce == 0 || all_headers_seen
+ || VSTRING_LEN(bounce_info->orig_msgid) > 0)) {
+ break;
+ }
+
+ /*
+ * Skip over (the remainder of) the message segment. If that
+ * fails, degrade.
+ */
+ if (skip_message_segment) {
+ if (vstream_fseek(bounce_info->orig_fp,
+ bounce_info->orig_offs +
+ bounce_info->message_size,
+ SEEK_SET) < 0)
+ /* void */ ;
+ skip_message_segment = 0;
+ }
+ }
+ }
+ return (bounce_info);
+}
+
+/* bounce_mail_init - initialize */
+
+BOUNCE_INFO *bounce_mail_init(const char *service,
+ const char *queue_name,
+ const char *queue_id,
+ const char *encoding,
+ int smtputf8,
+ const char *dsn_envid,
+ BOUNCE_TEMPLATE *template)
+{
+ BOUNCE_INFO *bounce_info;
+ BOUNCE_LOG *log_handle;
+ RCPT_BUF *rcpt_buf;
+ DSN_BUF *dsn_buf;
+
+ /*
+ * Initialize the bounce_info structure. If the bounce log cannot be
+ * found, do not raise a fatal run-time error. There is nothing we can do
+ * about the error, and all we are doing is to inform the sender of a
+ * delivery problem, Bouncing a message does not have to be a perfect
+ * job. But if the system IS running out of resources, raise a fatal
+ * run-time error and force a backoff.
+ */
+ if ((log_handle = bounce_log_open(service, queue_id, O_RDONLY, 0)) == 0) {
+ if (errno != ENOENT)
+ msg_fatal("open %s %s: %m", service, queue_id);
+ rcpt_buf = 0;
+ dsn_buf = 0;
+ } else {
+ rcpt_buf = rcpb_create();
+ dsn_buf = dsb_create();
+ }
+ bounce_info = bounce_mail_alloc(service, queue_name, queue_id, encoding,
+ smtputf8, dsn_envid, rcpt_buf, dsn_buf,
+ template, log_handle);
+ return (bounce_info);
+}
+
+/* bounce_mail_one_init - initialize */
+
+BOUNCE_INFO *bounce_mail_one_init(const char *queue_name,
+ const char *queue_id,
+ const char *encoding,
+ int smtputf8,
+ const char *dsn_envid,
+ RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf,
+ BOUNCE_TEMPLATE *template)
+{
+ BOUNCE_INFO *bounce_info;
+
+ /*
+ * Initialize the bounce_info structure for just one recipient.
+ */
+ bounce_info = bounce_mail_alloc("none", queue_name, queue_id, encoding,
+ smtputf8, dsn_envid, rcpt_buf, dsn_buf,
+ template, (BOUNCE_LOG *) 0);
+ return (bounce_info);
+}
+
+/* bounce_mail_free - undo bounce_mail_init */
+
+void bounce_mail_free(BOUNCE_INFO *bounce_info)
+{
+ if (bounce_info->log_handle) {
+ if (bounce_log_close(bounce_info->log_handle))
+ msg_warn("%s: read bounce log %s: %m",
+ bounce_info->queue_id, bounce_info->queue_id);
+ vstring_free(bounce_info->orig_msgid);
+ rcpb_free(bounce_info->rcpt_buf);
+ dsb_free(bounce_info->dsn_buf);
+ }
+ if (bounce_info->orig_fp && vstream_fclose(bounce_info->orig_fp))
+ msg_warn("%s: read message file %s %s: %m",
+ bounce_info->queue_id, bounce_info->queue_name,
+ bounce_info->queue_id);
+ vstring_free(bounce_info->buf);
+ vstring_free(bounce_info->sender);
+ myfree(bounce_info->mail_name);
+ myfree((void *) bounce_info->mime_boundary);
+ myfree((void *) bounce_info);
+}
+
+/* bounce_header - generate bounce message header */
+
+int bounce_header(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ const char *dest, int postmaster_copy)
+{
+ BOUNCE_TEMPLATE *template = bounce_info->template;
+
+ /*
+ * Print a minimal bounce header. The cleanup service will add other
+ * headers and will make all addresses fully qualified.
+ */
+#define STREQ(a, b) (strcasecmp((a), (b)) == 0)
+#define STRNE(a, b) (strcasecmp((a), (b)) != 0)
+
+ /*
+ * Generic headers.
+ */
+ bounce_template_headers(post_mail_fprintf, bounce, template,
+ STR(quote_822_local(bounce_info->buf, dest)),
+ postmaster_copy);
+
+ /*
+ * References and Reply-To header that references the original message-id
+ * for better threading in MUAs.
+ */
+ if (VSTRING_LEN(bounce_info->orig_msgid) > 0) {
+ post_mail_fprintf(bounce, "References: %s", STR(bounce_info->orig_msgid));
+ post_mail_fprintf(bounce, "In-Reply-To: %s", STR(bounce_info->orig_msgid));
+ }
+
+ /*
+ * Auto-Submitted header, as per RFC 3834.
+ */
+ post_mail_fprintf(bounce, "Auto-Submitted: %s", postmaster_copy ?
+ "auto-generated" : "auto-replied");
+
+ /*
+ * MIME header. Use 8bit encoding when either the bounced message or the
+ * template requires it.
+ */
+ post_mail_fprintf(bounce, "MIME-Version: 1.0");
+ post_mail_fprintf(bounce, "Content-Type: %s; report-type=%s;",
+ "multipart/report", "delivery-status");
+ post_mail_fprintf(bounce, "\tboundary=\"%s\"", bounce_info->mime_boundary);
+ if (bounce_info->mime_encoding)
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ STREQ(bounce_info->mime_encoding, MAIL_ATTR_ENC_7BIT) ?
+ bounce_template_encoding(template) :
+ bounce_info->mime_encoding);
+ post_mail_fputs(bounce, "");
+ post_mail_fputs(bounce, "This is a MIME-encapsulated message.");
+
+ /*
+ * MIME header.
+ */
+#define NOT_US_ASCII(tp) \
+ STRNE(bounce_template_charset(template), "us-ascii")
+
+#define NOT_7BIT_MIME(bp) \
+ (bp->mime_encoding && STRNE(bp->mime_encoding, MAIL_ATTR_ENC_7BIT))
+
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s", "Notification");
+ /* Fix 20140718: UTF-8 address or $myhostname expansion. */
+ post_mail_fprintf(bounce, "Content-Type: %s; charset=%s",
+ "text/plain", NOT_US_ASCII(template) ?
+ bounce_template_charset(template) :
+ NOT_7BIT_MIME(bounce_info) ?
+ "utf-8" : "us-ascii");
+ /* Fix 20140709: addresses may be 8bit. */
+ if (NOT_7BIT_MIME(bounce_info))
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ bounce_info->mime_encoding);
+ post_mail_fputs(bounce, "");
+
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_boilerplate - generate boiler-plate text */
+
+int bounce_boilerplate(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+
+ /*
+ * Print the boiler-plate text.
+ */
+ bounce_template_expand(post_mail_fputs, bounce, bounce_info->template);
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_print - line_wrap callback */
+
+static void bounce_print(const char *str, int len, int indent, void *context)
+{
+ VSTREAM *bounce = (VSTREAM *) context;
+
+ post_mail_fprintf(bounce, "%*s%.*s", indent, "", len, str);
+}
+
+/* bounce_print_wrap - print and wrap a line */
+
+static void bounce_print_wrap(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ const char *format,...)
+{
+ va_list ap;
+
+#define LENGTH 79
+#define INDENT 4
+
+ va_start(ap, format);
+ vstring_vsprintf(bounce_info->buf, format, ap);
+ va_end(ap);
+ line_wrap(STR(bounce_info->buf), LENGTH, INDENT,
+ bounce_print, (void *) bounce);
+}
+
+/* bounce_recipient_log - send one bounce log report entry */
+
+int bounce_recipient_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ DSN *dsn = &bounce_info->dsn_buf->dsn;
+
+ /*
+ * Mask control and non-ASCII characters (done in bounce_log_read()),
+ * wrap long lines and prepend one blank, so this data can safely be
+ * piped into other programs. Sort of like TCP Wrapper's safe_finger
+ * program.
+ */
+#define NON_NULL_EMPTY(s) ((s) && *(s))
+
+ post_mail_fputs(bounce, "");
+ if (NON_NULL_EMPTY(rcpt->orig_addr)) {
+ bounce_print_wrap(bounce, bounce_info, "<%s> (expanded from <%s>): %s",
+ rcpt->address, rcpt->orig_addr, dsn->reason);
+ } else {
+ bounce_print_wrap(bounce, bounce_info, "<%s>: %s",
+ rcpt->address, dsn->reason);
+ }
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_diagnostic_log - send bounce log report */
+
+int bounce_diagnostic_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ int notify_filter)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ int count = 0;
+
+ /*
+ * Append a human-readable copy of the delivery error log. We're doing a
+ * best effort, so there is no point raising a fatal run-time error in
+ * case of a logfile read error.
+ *
+ * XXX DSN If the logfile with failed recipients is unavailable, pretend
+ * that we found something anyway, so that this notification will not be
+ * canceled.
+ */
+ if (bounce_info->log_handle == 0
+ || bounce_log_rewind(bounce_info->log_handle)) {
+ if (IS_FAILURE_TEMPLATE(bounce_info->template)) {
+ post_mail_fputs(bounce, "");
+ post_mail_fputs(bounce, "\t--- Delivery report unavailable ---");
+ count = 1; /* XXX don't abort */
+ }
+ } else {
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0) {
+ if (rcpt->dsn_notify == 0 /* compat */
+ || (rcpt->dsn_notify & notify_filter)) {
+ count++;
+ if (bounce_recipient_log(bounce, bounce_info) != 0)
+ break;
+ }
+ }
+ }
+ return (vstream_ferror(bounce) ? -1 : count);
+}
+
+/* bounce_header_dsn - send per-MTA bounce DSN records */
+
+int bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+
+ /*
+ * MIME header.
+ */
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s",
+ "Delivery report");
+ /* Generate *global* only if the original requested SMTPUTF8 support. */
+ post_mail_fprintf(bounce, "Content-Type: message/%sdelivery-status",
+ (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED) ?
+ "global-" : "");
+ /* Fix 20140709: addresses may be 8bit. */
+ if (NOT_7BIT_MIME(bounce_info)
+ /* BC Fix 20170610: prevent MIME downgrade of message/delivery-status. */
+ && (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED))
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ bounce_info->mime_encoding);
+
+ /*
+ * According to RFC 1894: The body of a message/delivery-status consists
+ * of one or more "fields" formatted according to the ABNF of RFC 822
+ * header "fields" (see [6]). The per-message fields appear first,
+ * followed by a blank line.
+ */
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "Reporting-MTA: dns; %s", var_myhostname);
+#if 0
+ post_mail_fprintf(bounce, "Received-From-MTA: dns; %s", "whatever");
+#endif
+ if (NON_NULL_EMPTY(bounce_info->dsn_envid)) {
+ post_mail_fprintf(bounce, "Original-Envelope-Id: %s",
+ bounce_info->dsn_envid);
+ }
+ post_mail_fprintf(bounce, "X-%s-Queue-ID: %s",
+ bounce_info->mail_name, bounce_info->queue_id);
+
+#define IS_UTF8_ADDRESS(str, len) \
+ ((str)[0] != 0 && !allascii(str) && valid_utf8_string((str), (len)))
+
+ /* Fix 20140708: use "utf-8" or "rfc822" as appropriate. */
+ if (VSTRING_LEN(bounce_info->sender) > 0)
+ post_mail_fprintf(bounce, "X-%s-Sender: %s; %s",
+ bounce_info->mail_name, bounce_info->smtputf8
+ && IS_UTF8_ADDRESS(STR(bounce_info->sender),
+ VSTRING_LEN(bounce_info->sender)) ?
+ "utf-8" : "rfc822", STR(bounce_info->sender));
+ if (bounce_info->arrival_time > 0)
+ post_mail_fprintf(bounce, "Arrival-Date: %s",
+ mail_date(bounce_info->arrival_time));
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_recipient_dsn - send per-recipient DSN records */
+
+int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ DSN *dsn = &bounce_info->dsn_buf->dsn;
+
+ post_mail_fputs(bounce, "");
+ /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
+ post_mail_fprintf(bounce, "Final-Recipient: %s; %s",
+ bounce_info->smtputf8
+ && IS_UTF8_ADDRESS(rcpt->address,
+ strlen(rcpt->address)) ?
+ "utf-8" : "rfc822", rcpt->address);
+
+ /*
+ * XXX DSN
+ *
+ * RFC 3464 section 6.3.d: "If no ORCPT parameter was provided for this
+ * recipient, the Original-Recipient field MUST NOT appear."
+ *
+ * This is inconsistent with section 5.2.1.d: "If no ORCPT parameter was
+ * present in the RCPT command when the message was received, an ORCPT
+ * parameter MAY be added to the RCPT command when the message is
+ * relayed.". Postfix adds an ORCPT parameter under these conditions.
+ *
+ * Therefore, all down-stream MTAs will send DSNs with Original-Recipient
+ * field containing this same ORCPT value. When a down-stream MTA can use
+ * that information in their DSNs, it makes no sense that an up-stream
+ * MTA can't use that same information in its own DSNs.
+ *
+ * Postfix always reports an Original-Recipient field, because it is more
+ * more useful and more consistent.
+ */
+ if (NON_NULL_EMPTY(rcpt->dsn_orcpt)) {
+ post_mail_fprintf(bounce, "Original-Recipient: %s", rcpt->dsn_orcpt);
+ } else if (NON_NULL_EMPTY(rcpt->orig_addr)) {
+ /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
+ post_mail_fprintf(bounce, "Original-Recipient: %s; %s",
+ bounce_info->smtputf8
+ && IS_UTF8_ADDRESS(rcpt->orig_addr,
+ strlen(rcpt->orig_addr)) ?
+ "utf-8" : "rfc822", rcpt->orig_addr);
+ }
+ post_mail_fprintf(bounce, "Action: %s",
+ IS_FAILURE_TEMPLATE(bounce_info->template) ?
+ "failed" : dsn->action);
+ post_mail_fprintf(bounce, "Status: %s", dsn->status);
+ if (NON_NULL_EMPTY(dsn->mtype) && NON_NULL_EMPTY(dsn->mname))
+ bounce_print_wrap(bounce, bounce_info, "Remote-MTA: %s; %s",
+ dsn->mtype, dsn->mname);
+ if (NON_NULL_EMPTY(dsn->dtype) && NON_NULL_EMPTY(dsn->dtext))
+ bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: %s; %s",
+ dsn->dtype, dsn->dtext);
+ else
+ bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: X-%s; %s",
+ bounce_info->mail_name, dsn->reason);
+#if 0
+ if (dsn->time > 0)
+ post_mail_fprintf(bounce, "Last-Attempt-Date: %s",
+ mail_date(dsn->time));
+#endif
+ if (IS_DELAY_TEMPLATE(bounce_info->template))
+ post_mail_fprintf(bounce, "Will-Retry-Until: %s",
+ mail_date(bounce_info->arrival_time + var_max_queue_time));
+ return (vstream_ferror(bounce));
+}
+
+/* bounce_diagnostic_dsn - send bounce log report, machine readable form */
+
+int bounce_diagnostic_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ int notify_filter)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+ int count = 0;
+
+ /*
+ * Append a machine-readable copy of the delivery error log. We're doing
+ * a best effort, so there is no point raising a fatal run-time error in
+ * case of a logfile read error.
+ *
+ * XXX DSN If the logfile with failed recipients is unavailable, pretend
+ * that we found something anyway, so that this notification will not be
+ * canceled.
+ */
+ if (bounce_info->log_handle == 0
+ || bounce_log_rewind(bounce_info->log_handle)) {
+ if (IS_FAILURE_TEMPLATE(bounce_info->template))
+ count = 1; /* XXX don't abort */
+ } else {
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0) {
+ if (rcpt->dsn_notify == 0 /* compat */
+ || (rcpt->dsn_notify & notify_filter)) {
+ count++;
+ if (bounce_recipient_dsn(bounce, bounce_info) != 0)
+ break;
+ }
+ }
+ }
+ return (vstream_ferror(bounce) ? -1 : count);
+}
+
+/* bounce_original - send a copy of the original to the victim */
+
+int bounce_original(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
+ int headers_only)
+{
+ int status = 0;
+ int rec_type = 0;
+
+ /*
+ * When truncating a large message, don't damage the MIME structure: send
+ * the message headers only.
+ */
+ if (var_bounce_limit > 0
+ && bounce_info->orig_fp
+ && (bounce_info->message_size <= 0
+ || bounce_info->message_size > var_bounce_limit))
+ headers_only = DSN_RET_HDRS;
+
+ /*
+ * MIME headers.
+ */
+#define IS_UNDELIVERED_TEMPLATE(template) \
+ (IS_FAILURE_TEMPLATE(template) || IS_DELAY_TEMPLATE(template))
+
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s%s",
+ IS_UNDELIVERED_TEMPLATE(bounce_info->template) ?
+ "Undelivered " : "",
+ headers_only == DSN_RET_HDRS ?
+ "Message Headers" : "Message");
+ /* Generate *global* only if the original requested SMTPUTF8 support. */
+ if (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED)
+ post_mail_fprintf(bounce, "Content-Type: message/%s",
+ headers_only == DSN_RET_HDRS ?
+ "global-headers" : "global");
+ else
+ post_mail_fprintf(bounce, "Content-Type: %s",
+ headers_only == DSN_RET_HDRS ?
+ "text/rfc822-headers" : "message/rfc822");
+ if (NOT_7BIT_MIME(bounce_info))
+ post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
+ bounce_info->mime_encoding);
+ post_mail_fputs(bounce, "");
+
+ /*
+ * Send place holder if original is unavailable.
+ */
+ if (bounce_info->orig_offs == 0 || vstream_fseek(bounce_info->orig_fp,
+ bounce_info->orig_offs, SEEK_SET) < 0) {
+ post_mail_fputs(bounce, "\t--- Undelivered message unavailable ---");
+ return (vstream_ferror(bounce));
+ }
+
+ /*
+ * XXX The cleanup server removes Return-Path: headers. This should be
+ * done only with mail that enters via a non-SMTP channel, but changing
+ * this now could break other software. Removing Return-Path: could break
+ * digital signatures, though this is unlikely. In any case,
+ * header_checks are more effective when the Return-Path: header is
+ * present, so we prepend one to the bounce message.
+ */
+ post_mail_fprintf(bounce, "Return-Path: <%s>", STR(bounce_info->sender));
+
+ /*
+ * Copy the original message contents. We're doing raw record output here
+ * so that we don't throw away binary transparency yet.
+ */
+#define IS_HEADER(s) (IS_SPACE_TAB(*(s)) || is_header(s))
+
+ while (status == 0 && (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) {
+ if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
+ break;
+ if (headers_only == DSN_RET_HDRS
+ && !IS_HEADER(vstring_str(bounce_info->buf)))
+ break;
+ status = (REC_PUT_BUF(bounce, rec_type, bounce_info->buf) != rec_type);
+ }
+
+ /*
+ * Final MIME headers. These require -- at the end of the boundary
+ * string.
+ *
+ * XXX This should be a separate bounce_terminate() entry so we can be
+ * assured that the terminator will always be sent.
+ */
+ post_mail_fputs(bounce, "");
+ post_mail_fprintf(bounce, "--%s--", bounce_info->mime_boundary);
+
+ return (status);
+}
+
+/* bounce_delrcpt - delete recipients from original queue file */
+
+void bounce_delrcpt(BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ if (bounce_info->orig_fp != 0
+ && bounce_info->log_handle != 0
+ && bounce_log_rewind(bounce_info->log_handle) == 0)
+ while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
+ bounce_info->dsn_buf) != 0)
+ if (rcpt->offset > 0)
+ deliver_completed(bounce_info->orig_fp, rcpt->offset);
+}
+
+/* bounce_delrcpt_one - delete one recipient from original queue file */
+
+void bounce_delrcpt_one(BOUNCE_INFO *bounce_info)
+{
+ RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
+
+ if (bounce_info->orig_fp != 0 && rcpt->offset > 0)
+ deliver_completed(bounce_info->orig_fp, rcpt->offset);
+}