1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	cleanup_bounce 3
6/* SUMMARY
7/*	bounce all recipients
8/* SYNOPSIS
9/*	#include "cleanup.h"
10/*
11/*	void	cleanup_bounce(state)
12/*	CLEANUP_STATE *state;
13/* DESCRIPTION
14/*	cleanup_bounce() updates the bounce log on request by client
15/*	programs that cannot handle such problems themselves.
16/*
17/*	Upon successful completion, all error flags are reset,
18/*	and the message is scheduled for deletion.
19/*	Otherwise, the CLEANUP_STAT_WRITE error flag is raised.
20/*
21/*	Arguments:
22/* .IP state
23/*	Queue file and message processing state. This state is
24/*	updated as records are processed and as errors happen.
25/* LICENSE
26/* .ad
27/* .fi
28/*	The Secure Mailer license must be distributed with this software.
29/* AUTHOR(S)
30/*	Wietse Venema
31/*	IBM T.J. Watson Research
32/*	P.O. Box 704
33/*	Yorktown Heights, NY 10598, USA
34/*--*/
35
36/* System library. */
37
38#include <sys_defs.h>
39
40/* Utility library. */
41
42#include <msg.h>
43#include <stringops.h>
44#include <stdlib.h>
45
46/* Global library. */
47
48#include <cleanup_user.h>
49#include <mail_params.h>
50#include <mail_proto.h>
51#include <bounce.h>
52#include <dsn_util.h>
53#include <record.h>
54#include <rec_type.h>
55#include <dsn_mask.h>
56#include <mail_queue.h>
57#include <rec_attr_map.h>
58
59/* Application-specific. */
60
61#include "cleanup.h"
62
63#define STR(x) vstring_str(x)
64
65/* cleanup_bounce_append - update bounce logfile */
66
67static void cleanup_bounce_append(CLEANUP_STATE *state, RECIPIENT *rcpt,
68				          DSN *dsn)
69{
70    MSG_STATS stats;
71
72    /*
73     * Don't log a spurious warning (for example, when soft_bounce is turned
74     * on). bounce_append() already logs a record when the logfile can't be
75     * updated. Set the write error flag, so that a maildrop queue file won't
76     * be destroyed.
77     */
78    if (bounce_append(BOUNCE_FLAG_CLEAN, state->queue_id,
79		      CLEANUP_MSG_STATS(&stats, state),
80		      rcpt, "none", dsn) != 0) {
81	state->errs |= CLEANUP_STAT_WRITE;
82    }
83}
84
85/* cleanup_bounce - bounce all recipients */
86
87int     cleanup_bounce(CLEANUP_STATE *state)
88{
89    const char *myname = "cleanup_bounce";
90    VSTRING *buf = vstring_alloc(100);
91    const CLEANUP_STAT_DETAIL *detail;
92    DSN_SPLIT dp;
93    const char *dsn_status;
94    const char *dsn_text;
95    char   *rcpt = 0;
96    RECIPIENT recipient;
97    DSN     dsn;
98    char   *attr_name;
99    char   *attr_value;
100    char   *dsn_orcpt = 0;
101    int     dsn_notify = 0;
102    char   *orig_rcpt = 0;
103    char   *start;
104    int     rec_type;
105    int     junk;
106    long    curr_offset;
107    const char *encoding;
108    const char *dsn_envid;
109    int     dsn_ret;
110    int     bounce_err;
111
112    /*
113     * Parse the failure reason if one was given, otherwise use a generic
114     * mapping from cleanup-internal error code to (DSN + text).
115     */
116    if (state->reason) {
117	dsn_split(&dp, "5.0.0", state->reason);
118	dsn_status = DSN_STATUS(dp.dsn);
119	dsn_text = dp.text;
120    } else {
121	detail = cleanup_stat_detail(state->errs);
122	dsn_status = detail->dsn;
123	dsn_text = detail->text;
124    }
125
126    /*
127     * Create a bounce logfile with one entry for each final recipient.
128     * Degrade gracefully in case of no recipients or no queue file.
129     *
130     * Victor Duchovni observes that the number of recipients in the queue file
131     * can potentially be very large due to virtual alias expansion. This can
132     * expand the recipient count by virtual_alias_expansion_limit (default:
133     * 1000) times.
134     *
135     * After a queue file write error, purge any unwritten data (so that
136     * vstream_fseek() won't fail while trying to flush it) and reset the
137     * stream error flags to avoid false alarms.
138     */
139    if (vstream_ferror(state->dst) || vstream_fflush(state->dst)) {
140	(void) vstream_fpurge(state->dst, VSTREAM_PURGE_BOTH);
141	vstream_clearerr(state->dst);
142    }
143    if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0)
144	msg_fatal("%s: seek %s: %m", myname, cleanup_path);
145
146    while ((state->errs & CLEANUP_STAT_WRITE) == 0) {
147	if ((curr_offset = vstream_ftell(state->dst)) < 0)
148	    msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
149	if ((rec_type = rec_get(state->dst, buf, 0)) <= 0
150	    || rec_type == REC_TYPE_END)
151	    break;
152	start = STR(buf);
153	if (rec_type == REC_TYPE_ATTR) {
154	    if (split_nameval(STR(buf), &attr_name, &attr_value) != 0
155		|| *attr_value == 0)
156		continue;
157	    /* Map attribute names to pseudo record type. */
158	    if ((junk = rec_attr_map(attr_name)) != 0) {
159		start = attr_value;
160		rec_type = junk;
161	    }
162	}
163	switch (rec_type) {
164	case REC_TYPE_DSN_ORCPT:		/* RCPT TO ORCPT parameter */
165	    if (dsn_orcpt != 0)			/* can't happen */
166		myfree(dsn_orcpt);
167	    dsn_orcpt = mystrdup(start);
168	    break;
169	case REC_TYPE_DSN_NOTIFY:		/* RCPT TO NOTIFY parameter */
170	    if (alldig(start) && (junk = atoi(start)) > 0
171		&& DSN_NOTIFY_OK(junk))
172		dsn_notify = junk;
173	    else
174		dsn_notify = 0;
175	    break;
176	case REC_TYPE_ORCP:			/* unmodified RCPT TO address */
177	    if (orig_rcpt != 0)			/* can't happen */
178		myfree(orig_rcpt);
179	    orig_rcpt = mystrdup(start);
180	    break;
181	case REC_TYPE_RCPT:			/* rewritten RCPT TO address */
182	    rcpt = start;
183	    RECIPIENT_ASSIGN(&recipient, curr_offset,
184			     dsn_orcpt ? dsn_orcpt : "", dsn_notify,
185			     orig_rcpt ? orig_rcpt : rcpt, rcpt);
186	    (void) DSN_SIMPLE(&dsn, dsn_status, dsn_text);
187	    cleanup_bounce_append(state, &recipient, &dsn);
188	    /* FALLTHROUGH */
189	case REC_TYPE_DRCP:			/* canceled recipient */
190	case REC_TYPE_DONE:			/* can't happen */
191	    if (orig_rcpt != 0) {
192		myfree(orig_rcpt);
193		orig_rcpt = 0;
194	    }
195	    if (dsn_orcpt != 0) {
196		myfree(dsn_orcpt);
197		dsn_orcpt = 0;
198	    }
199	    dsn_notify = 0;
200	    break;
201	}
202    }
203    if (orig_rcpt != 0)				/* can't happen */
204	myfree(orig_rcpt);
205    if (dsn_orcpt != 0)				/* can't happen */
206	myfree(dsn_orcpt);
207
208    /*
209     * No recipients. Yes, this can happen.
210     */
211    if ((state->errs & CLEANUP_STAT_WRITE) == 0 && rcpt == 0) {
212	RECIPIENT_ASSIGN(&recipient, 0, "", 0, "", "unknown");
213	(void) DSN_SIMPLE(&dsn, dsn_status, dsn_text);
214	cleanup_bounce_append(state, &recipient, &dsn);
215    }
216    vstring_free(buf);
217
218    /*
219     * Flush the bounce logfile to the sender. See also qmgr_active.c.
220     */
221    if ((state->errs & CLEANUP_STAT_WRITE) == 0) {
222	if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) == 0)
223	    encoding = MAIL_ATTR_ENC_NONE;
224	dsn_envid = state->dsn_envid ?
225	    state->dsn_envid : "";
226	/* Do not send unfiltered (body) content. */
227	dsn_ret = (state->errs & (CLEANUP_STAT_CONT | CLEANUP_STAT_SIZE)) ?
228	    DSN_RET_HDRS : state->dsn_ret;
229
230	if (state->verp_delims == 0 || var_verp_bounce_off) {
231	    bounce_err =
232		bounce_flush(BOUNCE_FLAG_CLEAN,
233			     state->queue_name, state->queue_id,
234			     encoding, state->sender, dsn_envid,
235			     dsn_ret);
236	} else {
237	    bounce_err =
238		bounce_flush_verp(BOUNCE_FLAG_CLEAN,
239				  state->queue_name, state->queue_id,
240				  encoding, state->sender, dsn_envid,
241				  dsn_ret, state->verp_delims);
242	}
243	if (bounce_err != 0) {
244	    msg_warn("%s: bounce message failure", state->queue_id);
245	    state->errs |= CLEANUP_STAT_WRITE;
246	}
247    }
248
249    /*
250     * Schedule this message (and trace logfile) for deletion when all is
251     * well. When all is not well these files would be deleted too, but the
252     * client would get a different completion status so we have to carefully
253     * maintain the bits anyway.
254     */
255    if ((state->errs &= CLEANUP_STAT_WRITE) == 0)
256	state->flags |= CLEANUP_FLAG_DISCARD;
257
258    return (state->errs);
259}
260