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