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