Home | History | Annotate | Line # | Download | only in bounce
      1 /*	$NetBSD: bounce_notify_util.c,v 1.6 2026/05/09 18:49:14 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	bounce_notify_util 3
      6 /* SUMMARY
      7 /*	send non-delivery report to sender, server side
      8 /* SYNOPSIS
      9 /*	#include "bounce_service.h"
     10 /*
     11 /*	typedef struct {
     12 /* .in +4
     13 /*		/* All private members... */
     14 /* .in -4
     15 /*	} BOUNCE_INFO;
     16 /*
     17 /*	BOUNCE_INFO *bounce_mail_init(service, queue_name, queue_id, encoding,
     18 /*					sendopts, dsn_envid, template)
     19 /*	const char *service;
     20 /*	const char *queue_name;
     21 /*	const char *queue_id;
     22 /*	const char *encoding;
     23 /*	int	sendopts;
     24 /*	const char *dsn_envid;
     25 /*	const BOUNCE_TEMPLATE *template;
     26 /*
     27 /*	BOUNCE_INFO *bounce_mail_one_init(queue_name, queue_id, encoding,
     28 /*					sendopts, dsn_envid, dsn_notify,
     29 /*					rcpt_buf, dsn_buf, template)
     30 /*	const char *queue_name;
     31 /*	const char *queue_id;
     32 /*	const char *encoding;
     33 /*	int	sendopts;
     34 /*	int	dsn_notify;
     35 /*	const char *dsn_envid;
     36 /*	RCPT_BUF *rcpt_buf;
     37 /*	DSN_BUF	*dsn_buf;
     38 /*	const BOUNCE_TEMPLATE *template;
     39 /*
     40 /*	void	bounce_mail_free(bounce_info)
     41 /*	BOUNCE_INFO *bounce_info;
     42 /*
     43 /*	int	bounce_header(fp, bounce_info, recipient, postmaster_copy)
     44 /*	VSTREAM *fp;
     45 /*	BOUNCE_INFO *bounce_info;
     46 /*	const char *recipient;
     47 /*	int	postmaster_copy;
     48 /*
     49 /*	int	bounce_boilerplate(fp, bounce_info)
     50 /*	VSTREAM *fp;
     51 /*	BOUNCE_INFO *bounce_info;
     52 /*
     53 /*	int     bounce_recipient_log(fp, bounce_info)
     54 /*	VSTREAM *fp;
     55 /*	BOUNCE_INFO *bounce_info;
     56 /*
     57 /*	int     bounce_diagnostic_log(fp, bounce_info, notify_filter)
     58 /*	VSTREAM *fp;
     59 /*	BOUNCE_INFO *bounce_info;
     60 /*	int	notify_filter;
     61 /*
     62 /*	int     bounce_header_dsn(fp, bounce_info)
     63 /*	VSTREAM *fp;
     64 /*	BOUNCE_INFO *bounce_info;
     65 /*
     66 /*	int     bounce_recipient_dsn(fp, bounce_info)
     67 /*	VSTREAM *fp;
     68 /*	BOUNCE_INFO *bounce_info;
     69 /*
     70 /*	int     bounce_diagnostic_dsn(fp, bounce_info, notify_filter)
     71 /*	VSTREAM *fp;
     72 /*	BOUNCE_INFO *bounce_info;
     73 /*	int	notify_filter;
     74 /*
     75 /*	int	bounce_original(fp, bounce_info, headers_only)
     76 /*	VSTREAM *fp;
     77 /*	BOUNCE_INFO *bounce_info;
     78 /*	int	headers_only;
     79 /*
     80 /*	void	bounce_delrcpt(bounce_info)
     81 /*	BOUNCE_INFO *bounce_info;
     82 /*
     83 /*	void	bounce_delrcpt_one(bounce_info)
     84 /*	BOUNCE_INFO *bounce_info;
     85 /* DESCRIPTION
     86 /*	This module implements the grunt work of sending a non-delivery
     87 /*	notification. A bounce is sent in a form that satisfies RFC 1894
     88 /*	(delivery status notifications).
     89 /*
     90 /*	bounce_mail_init() bundles up its argument and attempts to
     91 /*	open the corresponding logfile and message file. A BOUNCE_INFO
     92 /*	structure contains all the necessary information about an
     93 /*	undeliverable message.
     94 /*
     95 /*	bounce_mail_one_init() provides the same function for only
     96 /*	one recipient that is not read from bounce logfile.
     97 /*
     98 /*	bounce_mail_free() releases memory allocated by bounce_mail_init()
     99 /*	and closes any files opened by bounce_mail_init().
    100 /*
    101 /*	bounce_header() produces a standard mail header with the specified
    102 /*	recipient and starts a text/plain message segment for the
    103 /*	human-readable problem description. postmaster_copy is either
    104 /*	POSTMASTER_COPY or NO_POSTMASTER_COPY.
    105 /*
    106 /*	bounce_boilerplate() produces the standard "sorry" text that
    107 /*	creates the illusion that mail systems are civilized.
    108 /*
    109 /*	bounce_recipient_log() sends a human-readable representation of
    110 /*	logfile information for one recipient, with the recipient address
    111 /*	and with the text why the recipient was undeliverable.
    112 /*
    113 /*	bounce_diagnostic_log() sends a human-readable representation of
    114 /*	logfile information for all undeliverable recipients. The
    115 /*	notify_filter specifies what recipient status records should be
    116 /*	reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
    117 /*	In the absence of DSN NOTIFY information all records are reported.
    118 /*	The result value is -1 in case of error, the number of reported
    119 /*	recipients in case of success.
    120 /*
    121 /*	bounce_header_dsn() starts a message/delivery-status message
    122 /*	segment and sends the machine-readable information that identifies
    123 /*	the reporting MTA.
    124 /*
    125 /*	bounce_recipient_dsn() sends a machine-readable representation of
    126 /*	logfile information for one recipient, with the recipient address
    127 /*	and with the text why the recipient was undeliverable.
    128 /*
    129 /*	bounce_diagnostic_dsn() sends a machine-readable representation of
    130 /*	logfile information for all undeliverable recipients. The
    131 /*	notify_filter specifies what recipient status records should be
    132 /*	reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
    133 /*	In the absence of DSN NOTIFY information all records are reported.
    134 /*	The result value is -1 in case of error, the number of reported
    135 /*	recipients in case of success.
    136 /*
    137 /*	bounce_original() starts a message/rfc822 or text/rfc822-headers
    138 /*	message segment and sends the original message, either full
    139 /*	(DSN_RET_FULL) or message headers only (DSN_RET_HDRS).
    140 /*
    141 /*	bounce_delrcpt() deletes recipients in the logfile from the original
    142 /*	queue file.
    143 /*
    144 /*	bounce_delrcpt_one() deletes one recipient from the original
    145 /*	queue file.
    146 /* DIAGNOSTICS
    147 /*	Fatal error: error opening existing file.
    148 /* BUGS
    149 /* SEE ALSO
    150 /*	bounce(3) basic bounce service client interface
    151 /* LICENSE
    152 /* .ad
    153 /* .fi
    154 /*	The Secure Mailer license must be distributed with this software.
    155 /* AUTHOR(S)
    156 /*	Wietse Venema
    157 /*	IBM T.J. Watson Research
    158 /*	P.O. Box 704
    159 /*	Yorktown Heights, NY 10598, USA
    160 /*
    161 /*	Wietse Venema
    162 /*	Google, Inc.
    163 /*	111 8th Avenue
    164 /*	New York, NY 10011, USA
    165 /*
    166 /*	Wietse Venema
    167 /*	porcupine.org
    168 /*--*/
    169 
    170 /* System library. */
    171 
    172 #include <sys_defs.h>
    173 #include <sys/stat.h>
    174 #include <stdlib.h>
    175 #include <stdio.h>			/* sscanf() */
    176 #include <unistd.h>
    177 #include <errno.h>
    178 #include <string.h>
    179 #include <ctype.h>
    180 
    181 #ifdef STRCASECMP_IN_STRINGS_H
    182 #include <strings.h>
    183 #endif
    184 
    185 /* Utility library. */
    186 
    187 #include <msg.h>
    188 #include <mymalloc.h>
    189 #include <events.h>
    190 #include <vstring.h>
    191 #include <vstream.h>
    192 #include <line_wrap.h>
    193 #include <stringops.h>
    194 #include <myflock.h>
    195 
    196 /* Global library. */
    197 
    198 #include <mail_queue.h>
    199 #include <quote_822_local.h>
    200 #include <mail_params.h>
    201 #include <is_header.h>
    202 #include <record.h>
    203 #include <rec_type.h>
    204 #include <post_mail.h>
    205 #include <mail_addr.h>
    206 #include <mail_error.h>
    207 #include <bounce_log.h>
    208 #include <mail_date.h>
    209 #include <mail_proto.h>
    210 #include <lex_822.h>
    211 #include <deliver_completed.h>
    212 #include <dsn_mask.h>
    213 #include <smtputf8.h>
    214 #include <header_opts.h>
    215 
    216 /* Application-specific. */
    217 
    218 #include "bounce_service.h"
    219 
    220 #define STR vstring_str
    221 #define LEN VSTRING_LEN
    222 
    223 /* bounce_mail_alloc - initialize */
    224 
    225 static BOUNCE_INFO *bounce_mail_alloc(const char *service,
    226 				              const char *queue_name,
    227 				              const char *queue_id,
    228 				              const char *encoding,
    229 				              int sendopts,
    230 				              const char *dsn_envid,
    231 				              RCPT_BUF *rcpt_buf,
    232 				              DSN_BUF *dsn_buf,
    233 				              BOUNCE_TEMPLATE *template,
    234 				              BOUNCE_LOG *log_handle)
    235 {
    236     BOUNCE_INFO *bounce_info;
    237     int     rec_type;
    238     int     skip_message_segment = 0;
    239 
    240     /*
    241      * Bundle up a bunch of parameters and initialize information that will
    242      * be discovered on the fly.
    243      *
    244      * XXX Instead of overriding the returned-message MIME encoding, separate
    245      * the returned-message MIME encoding from the (boiler plate, delivery
    246      * status) MIME encoding.
    247      */
    248     bounce_info = (BOUNCE_INFO *) mymalloc(sizeof(*bounce_info));
    249     bounce_info->service = service;
    250     bounce_info->queue_name = queue_name;
    251     bounce_info->queue_id = queue_id;
    252     bounce_info->sendopts = sendopts;
    253     /* Fix 20140708: override MIME encoding: addresses may be 8bit. */
    254     /* Fix 20140718: override MIME encoding: 8bit $myhostname expansion. */
    255     if (var_smtputf8_enable /* was: bounce_info->smtputf8 */ ) {
    256 	bounce_info->mime_encoding = "8bit";
    257     } else if (strcmp(encoding, MAIL_ATTR_ENC_8BIT) == 0) {
    258 	bounce_info->mime_encoding = "8bit";
    259     } else if (strcmp(encoding, MAIL_ATTR_ENC_7BIT) == 0) {
    260 	bounce_info->mime_encoding = "7bit";
    261     } else {
    262 	if (strcmp(encoding, MAIL_ATTR_ENC_NONE) != 0)
    263 	    msg_warn("%s: unknown encoding: %.200s",
    264 		     bounce_info->queue_id, encoding);
    265 	bounce_info->mime_encoding = 0;
    266     }
    267     if (dsn_envid && *dsn_envid)
    268 	bounce_info->dsn_envid = dsn_envid;
    269     else
    270 	bounce_info->dsn_envid = 0;
    271     bounce_info->template = template;
    272     bounce_info->buf = vstring_alloc(100);
    273     bounce_info->sender = vstring_alloc(100);
    274     bounce_info->arrival_time = 0;
    275     bounce_info->orig_offs = 0;
    276     bounce_info->message_size = 0;
    277     bounce_info->orig_msgid = vstring_alloc(100);
    278     bounce_info->rcpt_buf = rcpt_buf;
    279     bounce_info->dsn_buf = dsn_buf;
    280     bounce_info->log_handle = log_handle;
    281 
    282     /*
    283      * RFC 1894: diagnostic-type is an RFC 822 atom. We use X-$mail_name and
    284      * must ensure it is valid.
    285      */
    286     bounce_info->mail_name = mystrdup(var_mail_name);
    287     translit(bounce_info->mail_name, " \t\r\n()<>@,;:\\\".[]",
    288 	     "-----------------");
    289 
    290     /*
    291      * Compute a supposedly unique boundary string. This assumes that a queue
    292      * ID and a hostname contain acceptable characters for a boundary string,
    293      * but the assumption is not verified.
    294      */
    295     vstring_sprintf(bounce_info->buf, "%s.%lu/%s",
    296 		    queue_id, (unsigned long) event_time(), var_myhostname);
    297     bounce_info->mime_boundary = mystrdup(STR(bounce_info->buf));
    298 
    299     /*
    300      * If the original message cannot be found, do not raise a run-time
    301      * error. There is nothing we can do about the error, and all we are
    302      * doing is to inform the sender of a delivery problem. Bouncing a
    303      * message does not have to be a perfect job. But if the system IS
    304      * running out of resources, raise a fatal run-time error and force a
    305      * backoff.
    306      */
    307     if ((bounce_info->orig_fp = mail_queue_open(queue_name, queue_id,
    308 						O_RDWR, 0)) == 0
    309 	&& errno != ENOENT)
    310 	msg_fatal("open %s %s: %m", service, queue_id);
    311 
    312     /*
    313      * Get time/size/sender information from the original message envelope
    314      * records. If the envelope is corrupted just send whatever we can
    315      * (remember this is a best effort, it does not have to be perfect).
    316      *
    317      * Lock the file for shared use, so that queue manager leaves it alone after
    318      * restarting.
    319      */
    320 #define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT)
    321 
    322     if (bounce_info->orig_fp != 0) {
    323 	if (myflock(vstream_fileno(bounce_info->orig_fp), INTERNAL_LOCK,
    324 		    DELIVER_LOCK_MODE) < 0)
    325 	    msg_fatal("cannot get shared lock on %s: %m",
    326 		      VSTREAM_PATH(bounce_info->orig_fp));
    327 	while ((rec_type =
    328 		rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) {
    329 
    330 	    /*
    331 	     * Postfix version dependent: data offset in SIZE record.
    332 	     */
    333 	    if (rec_type == REC_TYPE_SIZE) {
    334 		if (bounce_info->message_size == 0)
    335 		    sscanf(STR(bounce_info->buf), "%ld %ld",
    336 			   &bounce_info->message_size,
    337 			   &bounce_info->orig_offs);
    338 		if (bounce_info->message_size < 0)
    339 		    bounce_info->message_size = 0;
    340 		if (bounce_info->orig_offs < 0)
    341 		    bounce_info->orig_offs = 0;
    342 	    }
    343 
    344 	    /*
    345 	     * Information for the Arrival-Date: attribute.
    346 	     */
    347 	    else if (rec_type == REC_TYPE_TIME) {
    348 		if (bounce_info->arrival_time == 0
    349 		    && (bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0)
    350 		    bounce_info->arrival_time = 0;
    351 	    }
    352 
    353 	    /*
    354 	     * Information for the X-Postfix-Sender: attribute.
    355 	     */
    356 	    else if (rec_type == REC_TYPE_FROM) {
    357 		quote_822_local_flags(bounce_info->sender,
    358 				      VSTRING_LEN(bounce_info->buf) ?
    359 				      STR(bounce_info->buf) :
    360 				      mail_addr_mail_daemon(),
    361 				      QUOTE_FLAG_8BITCLEAN);
    362 	    }
    363 
    364 	    /*
    365 	     * Backwards compatibility: no data offset in SIZE record.
    366 	     */
    367 	    else if (rec_type == REC_TYPE_MESG) {
    368 		/* XXX Future: sender+recipient after message content. */
    369 		if (VSTRING_LEN(bounce_info->sender) == 0)
    370 		    msg_warn("%s: no sender before message content record",
    371 			     bounce_info->queue_id);
    372 		bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp);
    373 		skip_message_segment = 1;
    374 	    }
    375 
    376 	    /*
    377 	     * Extract Message-ID from extracted segment, for use in threaded
    378 	     * bounces.
    379 	     */
    380 	    else if (rec_type == REC_TYPE_ATTR && var_threaded_bounce) {
    381 		char   *cp = STR(bounce_info->buf);
    382 		ssize_t len = sizeof(MAIL_ATTR_MESSAGE_ID);
    383 		char   *err;
    384 
    385 		if (strncmp(cp, MAIL_ATTR_MESSAGE_ID "=", len) == 0) {
    386 		    cp += len;
    387 		    if ((err = extpar(&cp, "<>", EXTPAR_FLAG_NONE)) != 0) {
    388 			msg_warn("%s: malformed Message-ID attribute: %s",
    389 				 bounce_info->queue_id, err);
    390 			myfree(err);
    391 		    } else {
    392 			vstring_sprintf(bounce_info->orig_msgid, "<%s>", cp);
    393 		    }
    394 		}
    395 	    }
    396 
    397 	    /*
    398 	     * Are we done yet?
    399 	     */
    400 	    if (bounce_info->orig_offs > 0
    401 		&& bounce_info->arrival_time > 0
    402 		&& VSTRING_LEN(bounce_info->sender) > 0
    403 		&& (var_threaded_bounce == 0
    404 		    || VSTRING_LEN(bounce_info->orig_msgid) > 0)) {
    405 		break;
    406 	    }
    407 
    408 	    /*
    409 	     * Skip over (the remainder of) the message segment. If that
    410 	     * fails, degrade.
    411 	     */
    412 	    if (skip_message_segment) {
    413 		if (vstream_fseek(bounce_info->orig_fp,
    414 				  bounce_info->orig_offs +
    415 				  bounce_info->message_size,
    416 				  SEEK_SET) < 0)
    417 		     /* void */ ;
    418 		skip_message_segment = 0;
    419 	    }
    420 	}
    421     }
    422     return (bounce_info);
    423 }
    424 
    425 /* bounce_mail_init - initialize */
    426 
    427 BOUNCE_INFO *bounce_mail_init(const char *service,
    428 			              const char *queue_name,
    429 			              const char *queue_id,
    430 			              const char *encoding,
    431 			              int sendopts,
    432 			              const char *dsn_envid,
    433 			              BOUNCE_TEMPLATE *template)
    434 {
    435     BOUNCE_INFO *bounce_info;
    436     BOUNCE_LOG *log_handle;
    437     RCPT_BUF *rcpt_buf;
    438     DSN_BUF *dsn_buf;
    439 
    440     /*
    441      * Initialize the bounce_info structure. If the bounce log cannot be
    442      * found, do not raise a fatal run-time error. There is nothing we can do
    443      * about the error, and all we are doing is to inform the sender of a
    444      * delivery problem, Bouncing a message does not have to be a perfect
    445      * job. But if the system IS running out of resources, raise a fatal
    446      * run-time error and force a backoff.
    447      */
    448     if ((log_handle = bounce_log_open(service, queue_id, O_RDONLY, 0)) == 0) {
    449 	if (errno != ENOENT)
    450 	    msg_fatal("open %s %s: %m", service, queue_id);
    451 	rcpt_buf = 0;
    452 	dsn_buf = 0;
    453     } else {
    454 	rcpt_buf = rcpb_create();
    455 	dsn_buf = dsb_create();
    456     }
    457     bounce_info = bounce_mail_alloc(service, queue_name, queue_id, encoding,
    458 				    sendopts, dsn_envid, rcpt_buf, dsn_buf,
    459 				    template, log_handle);
    460     return (bounce_info);
    461 }
    462 
    463 /* bounce_mail_one_init - initialize */
    464 
    465 BOUNCE_INFO *bounce_mail_one_init(const char *queue_name,
    466 				          const char *queue_id,
    467 				          const char *encoding,
    468 				          int sendopts,
    469 				          const char *dsn_envid,
    470 				          RCPT_BUF *rcpt_buf,
    471 				          DSN_BUF *dsn_buf,
    472 				          BOUNCE_TEMPLATE *template)
    473 {
    474     BOUNCE_INFO *bounce_info;
    475 
    476     /*
    477      * Initialize the bounce_info structure for just one recipient.
    478      */
    479     bounce_info = bounce_mail_alloc("none", queue_name, queue_id, encoding,
    480 				    sendopts, dsn_envid, rcpt_buf, dsn_buf,
    481 				    template, (BOUNCE_LOG *) 0);
    482     return (bounce_info);
    483 }
    484 
    485 /* bounce_mail_free - undo bounce_mail_init */
    486 
    487 void    bounce_mail_free(BOUNCE_INFO *bounce_info)
    488 {
    489     if (bounce_info->log_handle) {
    490 	if (bounce_log_close(bounce_info->log_handle))
    491 	    msg_warn("%s: read bounce log %s: %m",
    492 		     bounce_info->queue_id, bounce_info->queue_id);
    493 	vstring_free(bounce_info->orig_msgid);
    494 	rcpb_free(bounce_info->rcpt_buf);
    495 	dsb_free(bounce_info->dsn_buf);
    496     }
    497     if (bounce_info->orig_fp && vstream_fclose(bounce_info->orig_fp))
    498 	msg_warn("%s: read message file %s %s: %m",
    499 		 bounce_info->queue_id, bounce_info->queue_name,
    500 		 bounce_info->queue_id);
    501     vstring_free(bounce_info->buf);
    502     vstring_free(bounce_info->sender);
    503     myfree(bounce_info->mail_name);
    504     myfree((void *) bounce_info->mime_boundary);
    505     myfree((void *) bounce_info);
    506 }
    507 
    508 /* bounce_header - generate bounce message header */
    509 
    510 int     bounce_header(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
    511 		              const char *dest, int postmaster_copy)
    512 {
    513     BOUNCE_TEMPLATE *template = bounce_info->template;
    514 
    515     /*
    516      * Print a minimal bounce header. The cleanup service will add other
    517      * headers and will make all addresses fully qualified.
    518      */
    519 #define STREQ(a, b) (strcasecmp((a), (b)) == 0)
    520 #define STRNE(a, b) (strcasecmp((a), (b)) != 0)
    521 
    522     /*
    523      * Generic headers.
    524      */
    525     bounce_template_headers(post_mail_fprintf, bounce, template,
    526 			    STR(quote_822_local(bounce_info->buf, dest)),
    527 			    postmaster_copy);
    528 
    529     /*
    530      * References and Reply-To header that references the original message-id
    531      * for better threading in MUAs.
    532      */
    533     if (VSTRING_LEN(bounce_info->orig_msgid) > 0) {
    534 	post_mail_fprintf(bounce, "References: %s", STR(bounce_info->orig_msgid));
    535 	post_mail_fprintf(bounce, "In-Reply-To: %s", STR(bounce_info->orig_msgid));
    536     }
    537 
    538     /*
    539      * Trade confidentiality against availability. See also up-stream code in
    540      * edit_notification_properties().
    541      */
    542     if (var_tls_required_enable
    543 	&& (bounce_info->sendopts & SOPT_REQUIRETLS_HEADER) != 0)
    544 	post_mail_fprintf(bounce, "TLS-Required: no");
    545 
    546     /*
    547      * Auto-Submitted header, as per RFC 3834.
    548      */
    549     post_mail_fprintf(bounce, "Auto-Submitted: %s", postmaster_copy ?
    550 		      "auto-generated" : "auto-replied");
    551 
    552     /*
    553      * MIME header. Use 8bit encoding when either the bounced message or the
    554      * template requires it.
    555      */
    556     post_mail_fprintf(bounce, "MIME-Version: 1.0");
    557     post_mail_fprintf(bounce, "Content-Type: %s; report-type=%s;",
    558 		      "multipart/report", "delivery-status");
    559     post_mail_fprintf(bounce, "\tboundary=\"%s\"", bounce_info->mime_boundary);
    560     if (bounce_info->mime_encoding)
    561 	post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
    562 		     STREQ(bounce_info->mime_encoding, MAIL_ATTR_ENC_7BIT) ?
    563 			  bounce_template_encoding(template) :
    564 			  bounce_info->mime_encoding);
    565     post_mail_fputs(bounce, "");
    566     post_mail_fputs(bounce, "This is a MIME-encapsulated message.");
    567 
    568     /*
    569      * MIME header.
    570      */
    571 #define NOT_US_ASCII(tp) \
    572 	STRNE(bounce_template_charset(template), "us-ascii")
    573 
    574 #define NOT_7BIT_MIME(bp) \
    575 	(bp->mime_encoding && STRNE(bp->mime_encoding, MAIL_ATTR_ENC_7BIT))
    576 
    577     post_mail_fputs(bounce, "");
    578     post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
    579     post_mail_fprintf(bounce, "Content-Description: %s", "Notification");
    580     /* Fix 20140718: UTF-8 address or $myhostname expansion. */
    581     post_mail_fprintf(bounce, "Content-Type: %s; charset=%s",
    582 		      "text/plain", NOT_US_ASCII(template) ?
    583 		      bounce_template_charset(template) :
    584 		      NOT_7BIT_MIME(bounce_info) ?
    585 		      "utf-8" : "us-ascii");
    586     /* Fix 20140709: addresses may be 8bit. */
    587     if (NOT_7BIT_MIME(bounce_info))
    588 	post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
    589 			  bounce_info->mime_encoding);
    590     post_mail_fputs(bounce, "");
    591 
    592     return (vstream_ferror(bounce));
    593 }
    594 
    595 /* bounce_boilerplate - generate boiler-plate text */
    596 
    597 int     bounce_boilerplate(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
    598 {
    599 
    600     /*
    601      * Print the boiler-plate text.
    602      */
    603     bounce_template_expand(post_mail_fputs, bounce, bounce_info->template);
    604     return (vstream_ferror(bounce));
    605 }
    606 
    607 /* bounce_print - line_wrap callback */
    608 
    609 static void bounce_print(const char *str, int len, int indent, void *context)
    610 {
    611     VSTREAM *bounce = (VSTREAM *) context;
    612 
    613     post_mail_fprintf(bounce, "%*s%.*s", indent, "", len, str);
    614 }
    615 
    616 /* bounce_print_wrap - print and wrap a line */
    617 
    618 static void bounce_print_wrap(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
    619 			              const char *format,...)
    620 {
    621     va_list ap;
    622 
    623 #define LENGTH	79
    624 #define INDENT	4
    625 
    626     va_start(ap, format);
    627     vstring_vsprintf(bounce_info->buf, format, ap);
    628     va_end(ap);
    629     line_wrap(STR(bounce_info->buf), LENGTH, INDENT,
    630 	      bounce_print, (void *) bounce);
    631 }
    632 
    633 /* bounce_recipient_log - send one bounce log report entry */
    634 
    635 int     bounce_recipient_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
    636 {
    637     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
    638     DSN    *dsn = &bounce_info->dsn_buf->dsn;
    639 
    640     /*
    641      * Mask control and non-ASCII characters (done in bounce_log_read()),
    642      * wrap long lines and prepend one blank, so this data can safely be
    643      * piped into other programs. Sort of like TCP Wrapper's safe_finger
    644      * program.
    645      */
    646 #define NON_NULL_EMPTY(s) ((s) && *(s))
    647 
    648     post_mail_fputs(bounce, "");
    649     if (NON_NULL_EMPTY(rcpt->orig_addr)) {
    650 	bounce_print_wrap(bounce, bounce_info, "<%s> (expanded from <%s>): %s",
    651 			  rcpt->address, rcpt->orig_addr, dsn->reason);
    652     } else {
    653 	bounce_print_wrap(bounce, bounce_info, "<%s>: %s",
    654 			  rcpt->address, dsn->reason);
    655     }
    656     return (vstream_ferror(bounce));
    657 }
    658 
    659 /* bounce_diagnostic_log - send bounce log report */
    660 
    661 int     bounce_diagnostic_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
    662 			              int notify_filter)
    663 {
    664     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
    665     int     count = 0;
    666 
    667     /*
    668      * Append a human-readable copy of the delivery error log. We're doing a
    669      * best effort, so there is no point raising a fatal run-time error in
    670      * case of a logfile read error.
    671      *
    672      * XXX DSN If the logfile with failed recipients is unavailable, pretend
    673      * that we found something anyway, so that this notification will not be
    674      * canceled.
    675      */
    676     if (bounce_info->log_handle == 0
    677 	|| bounce_log_rewind(bounce_info->log_handle)) {
    678 	if (IS_FAILURE_TEMPLATE(bounce_info->template)) {
    679 	    post_mail_fputs(bounce, "");
    680 	    post_mail_fputs(bounce, "\t--- Delivery report unavailable ---");
    681 	    count = 1;				/* XXX don't abort */
    682 	}
    683     } else {
    684 	while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
    685 			       bounce_info->dsn_buf) != 0) {
    686 	    if (rcpt->dsn_notify == 0		/* compat */
    687 		|| (rcpt->dsn_notify & notify_filter)) {
    688 		count++;
    689 		if (bounce_recipient_log(bounce, bounce_info) != 0)
    690 		    break;
    691 	    }
    692 	}
    693     }
    694     return (vstream_ferror(bounce) ? -1 : count);
    695 }
    696 
    697 /* bounce_header_dsn - send per-MTA bounce DSN records */
    698 
    699 int     bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
    700 {
    701 
    702     /*
    703      * MIME header.
    704      */
    705     post_mail_fputs(bounce, "");
    706     post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
    707     post_mail_fprintf(bounce, "Content-Description: %s",
    708 		      "Delivery report");
    709     /* Generate *global* only if the original requested SMTPUTF8 support. */
    710     post_mail_fprintf(bounce, "Content-Type: message/%sdelivery-status",
    711 		      (bounce_info->sendopts & SMTPUTF8_FLAG_REQUESTED) ?
    712 		      "global-" : "");
    713     /* Fix 20140709: addresses may be 8bit. */
    714     if (NOT_7BIT_MIME(bounce_info)
    715     /* BC Fix 20170610: prevent MIME downgrade of message/delivery-status. */
    716 	&& (bounce_info->sendopts & SMTPUTF8_FLAG_REQUESTED))
    717 	post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
    718 			  bounce_info->mime_encoding);
    719 
    720     /*
    721      * According to RFC 1894: The body of a message/delivery-status consists
    722      * of one or more "fields" formatted according to the ABNF of RFC 822
    723      * header "fields" (see [6]).  The per-message fields appear first,
    724      * followed by a blank line.
    725      */
    726     post_mail_fputs(bounce, "");
    727     post_mail_fprintf(bounce, "Reporting-MTA: dns; %s", var_myhostname);
    728 #if 0
    729     post_mail_fprintf(bounce, "Received-From-MTA: dns; %s", "whatever");
    730 #endif
    731     if (NON_NULL_EMPTY(bounce_info->dsn_envid)) {
    732 	post_mail_fprintf(bounce, "Original-Envelope-Id: %s",
    733 			  bounce_info->dsn_envid);
    734     }
    735     post_mail_fprintf(bounce, "X-%s-Queue-ID: %s",
    736 		      bounce_info->mail_name, bounce_info->queue_id);
    737 
    738 #define IS_UTF8_ADDRESS(str) \
    739 	((str)[0] != 0 && !allascii(str) && valid_utf8_stringz(str))
    740 
    741     /* Fix 20140708: use "utf-8" or "rfc822" as appropriate. */
    742     if (VSTRING_LEN(bounce_info->sender) > 0)
    743 	post_mail_fprintf(bounce, "X-%s-Sender: %s; %s",
    744 			  bounce_info->mail_name,
    745 			  (bounce_info->sendopts & SMTPUTF8_FLAG_ALL)
    746 			  && IS_UTF8_ADDRESS(STR(bounce_info->sender)) ?
    747 			  "utf-8" : "rfc822", STR(bounce_info->sender));
    748     if (bounce_info->arrival_time > 0)
    749 	post_mail_fprintf(bounce, "Arrival-Date: %s",
    750 			  mail_date(bounce_info->arrival_time));
    751     return (vstream_ferror(bounce));
    752 }
    753 
    754 /* bounce_recipient_dsn - send per-recipient DSN records */
    755 
    756 int     bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
    757 {
    758     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
    759     DSN    *dsn = &bounce_info->dsn_buf->dsn;
    760 
    761     post_mail_fputs(bounce, "");
    762     /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
    763     post_mail_fprintf(bounce, "Final-Recipient: %s; %s",
    764 		      (bounce_info->sendopts & SMTPUTF8_FLAG_ALL)
    765 		      && IS_UTF8_ADDRESS(rcpt->address) ?
    766 		      "utf-8" : "rfc822", rcpt->address);
    767 
    768     /*
    769      * XXX DSN
    770      *
    771      * RFC 3464 section 6.3.d: "If no ORCPT parameter was provided for this
    772      * recipient, the Original-Recipient field MUST NOT appear."
    773      *
    774      * This is inconsistent with section 5.2.1.d: "If no ORCPT parameter was
    775      * present in the RCPT command when the message was received, an ORCPT
    776      * parameter MAY be added to the RCPT command when the message is
    777      * relayed.". Postfix adds an ORCPT parameter under these conditions.
    778      *
    779      * Therefore, all down-stream MTAs will send DSNs with Original-Recipient
    780      * field containing this same ORCPT value. When a down-stream MTA can use
    781      * that information in their DSNs, it makes no sense that an up-stream
    782      * MTA can't use that same information in its own DSNs.
    783      *
    784      * Postfix always reports an Original-Recipient field, because it is more
    785      * more useful and more consistent.
    786      */
    787     if (NON_NULL_EMPTY(rcpt->dsn_orcpt)) {
    788 	post_mail_fprintf(bounce, "Original-Recipient: %s", rcpt->dsn_orcpt);
    789     } else if (NON_NULL_EMPTY(rcpt->orig_addr)) {
    790 	/* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
    791 	post_mail_fprintf(bounce, "Original-Recipient: %s; %s",
    792 			  (bounce_info->sendopts & SMTPUTF8_FLAG_ALL)
    793 			  && IS_UTF8_ADDRESS(rcpt->orig_addr) ?
    794 			  "utf-8" : "rfc822", rcpt->orig_addr);
    795     }
    796     post_mail_fprintf(bounce, "Action: %s",
    797 		      IS_FAILURE_TEMPLATE(bounce_info->template) ?
    798 		      "failed" : dsn->action);
    799     post_mail_fprintf(bounce, "Status: %s", dsn->status);
    800     if (NON_NULL_EMPTY(dsn->mtype) && NON_NULL_EMPTY(dsn->mname))
    801 	bounce_print_wrap(bounce, bounce_info, "Remote-MTA: %s; %s",
    802 			  dsn->mtype, dsn->mname);
    803     if (NON_NULL_EMPTY(dsn->dtype) && NON_NULL_EMPTY(dsn->dtext))
    804 	bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: %s; %s",
    805 			  dsn->dtype, dsn->dtext);
    806     else
    807 	bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: X-%s; %s",
    808 			  bounce_info->mail_name, dsn->reason);
    809 #if 0
    810     if (dsn->time > 0)
    811 	post_mail_fprintf(bounce, "Last-Attempt-Date: %s",
    812 			  mail_date(dsn->time));
    813 #endif
    814     if (IS_DELAY_TEMPLATE(bounce_info->template))
    815 	post_mail_fprintf(bounce, "Will-Retry-Until: %s",
    816 		 mail_date(bounce_info->arrival_time + var_max_queue_time));
    817     return (vstream_ferror(bounce));
    818 }
    819 
    820 /* bounce_diagnostic_dsn - send bounce log report, machine readable form */
    821 
    822 int     bounce_diagnostic_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
    823 			              int notify_filter)
    824 {
    825     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
    826     int     count = 0;
    827 
    828     /*
    829      * Append a machine-readable copy of the delivery error log. We're doing
    830      * a best effort, so there is no point raising a fatal run-time error in
    831      * case of a logfile read error.
    832      *
    833      * XXX DSN If the logfile with failed recipients is unavailable, pretend
    834      * that we found something anyway, so that this notification will not be
    835      * canceled.
    836      */
    837     if (bounce_info->log_handle == 0
    838 	|| bounce_log_rewind(bounce_info->log_handle)) {
    839 	if (IS_FAILURE_TEMPLATE(bounce_info->template))
    840 	    count = 1;				/* XXX don't abort */
    841     } else {
    842 	while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
    843 			       bounce_info->dsn_buf) != 0) {
    844 	    if (rcpt->dsn_notify == 0		/* compat */
    845 		|| (rcpt->dsn_notify & notify_filter)) {
    846 		count++;
    847 		if (bounce_recipient_dsn(bounce, bounce_info) != 0)
    848 		    break;
    849 	    }
    850 	}
    851     }
    852     return (vstream_ferror(bounce) ? -1 : count);
    853 }
    854 
    855 /* bounce_original - send a copy of the original to the victim */
    856 
    857 int     bounce_original(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
    858 			        int headers_only)
    859 {
    860     int     status = 0;
    861     int     rec_type = 0;
    862 
    863     /*
    864      * When truncating a large message, don't damage the MIME structure: send
    865      * the message headers only.
    866      */
    867     if (var_bounce_limit > 0
    868 	&& bounce_info->orig_fp
    869 	&& (bounce_info->message_size <= 0
    870 	    || bounce_info->message_size > var_bounce_limit))
    871 	headers_only = DSN_RET_HDRS;
    872 
    873     /*
    874      * MIME headers.
    875      */
    876 #define IS_UNDELIVERED_TEMPLATE(template) \
    877         (IS_FAILURE_TEMPLATE(template) || IS_DELAY_TEMPLATE(template))
    878 
    879     post_mail_fputs(bounce, "");
    880     post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
    881     post_mail_fprintf(bounce, "Content-Description: %s%s",
    882 		      IS_UNDELIVERED_TEMPLATE(bounce_info->template) ?
    883 		      "Undelivered " : "",
    884 		      headers_only == DSN_RET_HDRS ?
    885 		      "Message Headers" : "Message");
    886     /* Generate *global* only if the original requested SMTPUTF8 support. */
    887     if (bounce_info->sendopts & SMTPUTF8_FLAG_REQUESTED)
    888 	post_mail_fprintf(bounce, "Content-Type: message/%s",
    889 			  headers_only == DSN_RET_HDRS ?
    890 			  "global-headers" : "global");
    891     else
    892 	post_mail_fprintf(bounce, "Content-Type: %s",
    893 			  headers_only == DSN_RET_HDRS ?
    894 			  "text/rfc822-headers" : "message/rfc822");
    895     if (NOT_7BIT_MIME(bounce_info))
    896 	post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
    897 			  bounce_info->mime_encoding);
    898     post_mail_fputs(bounce, "");
    899 
    900     /*
    901      * Send place holder if original is unavailable.
    902      */
    903     if (bounce_info->orig_offs == 0 || vstream_fseek(bounce_info->orig_fp,
    904 				    bounce_info->orig_offs, SEEK_SET) < 0) {
    905 	post_mail_fputs(bounce, "\t--- Undelivered message unavailable ---");
    906 	return (vstream_ferror(bounce));
    907     }
    908 
    909     /*
    910      * XXX The cleanup server removes Return-Path: headers. This should be
    911      * done only with mail that enters via a non-SMTP channel, but changing
    912      * this now could break other software. Removing Return-Path: could break
    913      * digital signatures, though this is unlikely. In any case,
    914      * header_checks are more effective when the Return-Path: header is
    915      * present, so we prepend one to the bounce message.
    916      */
    917     post_mail_fprintf(bounce, "Return-Path: <%s>", STR(bounce_info->sender));
    918 
    919     /*
    920      * Copy the original message contents. We're doing raw record output here
    921      * so that we don't throw away binary transparency yet.
    922      */
    923 #define IS_HEADER(s) (IS_SPACE_TAB(*(s)) || is_header(s))
    924 
    925     while (status == 0 && (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) {
    926 	if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
    927 	    break;
    928 	if (headers_only == DSN_RET_HDRS
    929 	    && !IS_HEADER(vstring_str(bounce_info->buf)))
    930 	    break;
    931 	status = (REC_PUT_BUF(bounce, rec_type, bounce_info->buf) != rec_type);
    932     }
    933 
    934     /*
    935      * Final MIME headers. These require -- at the end of the boundary
    936      * string.
    937      *
    938      * XXX This should be a separate bounce_terminate() entry so we can be
    939      * assured that the terminator will always be sent.
    940      */
    941     post_mail_fputs(bounce, "");
    942     post_mail_fprintf(bounce, "--%s--", bounce_info->mime_boundary);
    943 
    944     return (status);
    945 }
    946 
    947 /* bounce_delrcpt - delete recipients from original queue file */
    948 
    949 void    bounce_delrcpt(BOUNCE_INFO *bounce_info)
    950 {
    951     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
    952 
    953     if (bounce_info->orig_fp != 0
    954 	&& bounce_info->log_handle != 0
    955 	&& bounce_log_rewind(bounce_info->log_handle) == 0)
    956 	while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
    957 			       bounce_info->dsn_buf) != 0)
    958 	    if (rcpt->offset > 0)
    959 		deliver_completed(bounce_info->orig_fp, rcpt->offset);
    960 }
    961 
    962 /* bounce_delrcpt_one - delete one recipient from original queue file */
    963 
    964 void    bounce_delrcpt_one(BOUNCE_INFO *bounce_info)
    965 {
    966     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
    967 
    968     if (bounce_info->orig_fp != 0 && rcpt->offset > 0)
    969 	deliver_completed(bounce_info->orig_fp, rcpt->offset);
    970 }
    971