diff options
Diffstat (limited to 'src/bounce/bounce_notify_util.c')
-rw-r--r-- | src/bounce/bounce_notify_util.c | 1010 |
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); +} |