1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	forward 3
6/* SUMMARY
7/*	message forwarding
8/* SYNOPSIS
9/*	#include "local.h"
10/*
11/*	int	forward_init()
12/*
13/*	int	forward_append(attr)
14/*	DELIVER_ATTR attr;
15/*
16/*	int	forward_finish(request, attr, cancel)
17/*	DELIVER_REQUEST *request;
18/*	DELIVER_ATTR attr;
19/*	int	cancel;
20/* DESCRIPTION
21/*	This module implements the client interface for message
22/*	forwarding.
23/*
24/*	forward_init() initializes internal data structures.
25/*
26/*	forward_append() appends a recipient to the list of recipients
27/*	that will receive a message with the specified message sender
28/*	and delivered-to addresses.
29/*
30/*	forward_finish() forwards the actual message contents and
31/*	releases the memory allocated by forward_init() and by
32/*	forward_append(). When the \fIcancel\fR argument is true, no
33/*	messages will be forwarded. The \fIattr\fR argument specifies
34/*	the original message delivery attributes as they were before
35/*	alias or forward expansions.
36/* DIAGNOSTICS
37/*	A non-zero result means that the requested operation should
38/*	be tried again.
39/*	Warnings: problems connecting to the forwarding service,
40/*	corrupt message file. A corrupt message is saved to the
41/*	"corrupt" queue for further inspection.
42/*	Fatal: out of memory.
43/*	Panic: missing forward_init() or forward_finish() call.
44/* LICENSE
45/* .ad
46/* .fi
47/*	The Secure Mailer license must be distributed with this software.
48/* AUTHOR(S)
49/*	Wietse Venema
50/*	IBM T.J. Watson Research
51/*	P.O. Box 704
52/*	Yorktown Heights, NY 10598, USA
53/*--*/
54
55/* System library. */
56
57#include <sys_defs.h>
58#include <sys/time.h>
59#include <unistd.h>
60
61/* Utility library. */
62
63#include <msg.h>
64#include <mymalloc.h>
65#include <htable.h>
66#include <argv.h>
67#include <vstring.h>
68#include <vstream.h>
69#include <vstring_vstream.h>
70#include <iostuff.h>
71#include <stringops.h>
72
73/* Global library. */
74
75#include <mail_proto.h>
76#include <cleanup_user.h>
77#include <sent.h>
78#include <record.h>
79#include <rec_type.h>
80#include <mark_corrupt.h>
81#include <mail_date.h>
82#include <mail_params.h>
83#include <dsn_mask.h>
84
85/* Application-specific. */
86
87#include "local.h"
88
89 /*
90  * Use one cleanup service connection for each (delivered to, sender) pair.
91  */
92static HTABLE *forward_dt;
93
94typedef struct FORWARD_INFO {
95    VSTREAM *cleanup;			/* clean up service handle */
96    char   *queue_id;			/* forwarded message queue id */
97    struct timeval posting_time;	/* posting time */
98} FORWARD_INFO;
99
100/* forward_init - prepare for forwarding */
101
102int     forward_init(void)
103{
104
105    /*
106     * Sanity checks.
107     */
108    if (forward_dt != 0)
109	msg_panic("forward_init: missing forward_finish call");
110
111    forward_dt = htable_create(0);
112    return (0);
113}
114
115/* forward_open - open connection to cleanup service */
116
117static FORWARD_INFO *forward_open(DELIVER_REQUEST *request, const char *sender)
118{
119    VSTRING *buffer = vstring_alloc(100);
120    FORWARD_INFO *info;
121    VSTREAM *cleanup;
122
123    /*
124     * Contact the cleanup service and save the new mail queue id. Request
125     * that the cleanup service bounces bad messages to the sender so that we
126     * can avoid the trouble of bounce management.
127     *
128     * In case you wonder what kind of bounces, examples are "too many hops",
129     * "message too large", perhaps some others. The reason not to bounce
130     * ourselves is that we don't really know who the recipients are.
131     */
132    cleanup = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, BLOCKING);
133    if (cleanup == 0)
134	return (0);
135    close_on_exec(vstream_fileno(cleanup), CLOSE_ON_EXEC);
136    if (attr_scan(cleanup, ATTR_FLAG_STRICT,
137		  ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, buffer,
138		  ATTR_TYPE_END) != 1) {
139	vstream_fclose(cleanup);
140	return (0);
141    }
142    info = (FORWARD_INFO *) mymalloc(sizeof(FORWARD_INFO));
143    info->cleanup = cleanup;
144    info->queue_id = mystrdup(STR(buffer));
145    GETTIMEOFDAY(&info->posting_time);
146
147#define FORWARD_CLEANUP_FLAGS (CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_INTERNAL)
148
149    attr_print(cleanup, ATTR_FLAG_NONE,
150	       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, FORWARD_CLEANUP_FLAGS,
151	       ATTR_TYPE_END);
152
153    /*
154     * Send initial message envelope information. For bounces, set the
155     * designated sender: mailing list owner, posting user, whatever.
156     */
157    rec_fprintf(cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
158		REC_TYPE_TIME_ARG(info->posting_time));
159    rec_fputs(cleanup, REC_TYPE_FROM, sender);
160
161    /*
162     * Don't send the original envelope ID or full/headers return mask if it
163     * was reset due to mailing list expansion.
164     */
165    if (request->dsn_ret)
166	rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%d",
167		    MAIL_ATTR_DSN_RET, request->dsn_ret);
168    if (request->dsn_envid && *(request->dsn_envid))
169	rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%s",
170		    MAIL_ATTR_DSN_ENVID, request->dsn_envid);
171
172    /*
173     * Zero-length attribute values are place holders for unavailable
174     * attribute values. See qmgr_message.c. They are not meant to be
175     * propagated to queue files.
176     */
177#define PASS_ATTR(fp, name, value) do { \
178    if ((value) && *(value)) \
179	rec_fprintf((fp), REC_TYPE_ATTR, "%s=%s", (name), (value)); \
180    } while (0)
181
182    /*
183     * XXX encapsulate these as one object.
184     */
185    PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_NAME, request->client_name);
186    PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_ADDR, request->client_addr);
187    PASS_ATTR(cleanup, MAIL_ATTR_LOG_PROTO_NAME, request->client_proto);
188    PASS_ATTR(cleanup, MAIL_ATTR_LOG_HELO_NAME, request->client_helo);
189    PASS_ATTR(cleanup, MAIL_ATTR_SASL_METHOD, request->sasl_method);
190    PASS_ATTR(cleanup, MAIL_ATTR_SASL_USERNAME, request->sasl_username);
191    PASS_ATTR(cleanup, MAIL_ATTR_SASL_SENDER, request->sasl_sender);
192    PASS_ATTR(cleanup, MAIL_ATTR_LOG_IDENT, request->log_ident);
193    PASS_ATTR(cleanup, MAIL_ATTR_RWR_CONTEXT, request->rewrite_context);
194
195    vstring_free(buffer);
196    return (info);
197}
198
199/* forward_append - append recipient to message envelope */
200
201int     forward_append(DELIVER_ATTR attr)
202{
203    FORWARD_INFO *info;
204    HTABLE *table_snd;
205
206    /*
207     * Sanity checks.
208     */
209    if (msg_verbose)
210	msg_info("forward delivered=%s sender=%s recip=%s",
211		 attr.delivered, attr.sender, attr.rcpt.address);
212    if (forward_dt == 0)
213	msg_panic("forward_append: missing forward_init call");
214
215    /*
216     * In order to find the recipient list, first index a table by
217     * delivered-to header address, then by envelope sender address.
218     */
219    if ((table_snd = (HTABLE *) htable_find(forward_dt, attr.delivered)) == 0) {
220	table_snd = htable_create(0);
221	htable_enter(forward_dt, attr.delivered, (char *) table_snd);
222    }
223    if ((info = (FORWARD_INFO *) htable_find(table_snd, attr.sender)) == 0) {
224	if ((info = forward_open(attr.request, attr.sender)) == 0)
225	    return (-1);
226	htable_enter(table_snd, attr.sender, (char *) info);
227    }
228
229    /*
230     * Append the recipient to the message envelope. Don't send the original
231     * recipient or notification mask if it was reset due to mailing list
232     * expansion.
233     */
234    if (*attr.rcpt.dsn_orcpt)
235	rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%s",
236		    MAIL_ATTR_DSN_ORCPT, attr.rcpt.dsn_orcpt);
237    if (attr.rcpt.dsn_notify)
238	rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%d",
239		    MAIL_ATTR_DSN_NOTIFY, attr.rcpt.dsn_notify);
240    if (*attr.rcpt.orig_addr)
241	rec_fputs(info->cleanup, REC_TYPE_ORCP, attr.rcpt.orig_addr);
242    rec_fputs(info->cleanup, REC_TYPE_RCPT, attr.rcpt.address);
243
244    return (vstream_ferror(info->cleanup));
245}
246
247/* forward_send - send forwarded message */
248
249static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request,
250			        DELIVER_ATTR attr, char *delivered)
251{
252    const char *myname = "forward_send";
253    VSTRING *buffer = vstring_alloc(100);
254    int     status;
255    int     rec_type = 0;
256
257    /*
258     * Start the message content segment. Prepend our Delivered-To: header to
259     * the message data. Stop at the first error. XXX Rely on the front-end
260     * services to enforce record size limits.
261     */
262    rec_fputs(info->cleanup, REC_TYPE_MESG, "");
263    vstring_strcpy(buffer, delivered);
264    rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)",
265		var_myhostname, var_mail_name);
266    rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s",
267		info->queue_id, mail_date(info->posting_time.tv_sec));
268    if (local_deliver_hdr_mask & DELIVER_HDR_FWD)
269	rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s",
270		    lowercase(STR(buffer)));
271    if ((status = vstream_ferror(info->cleanup)) == 0)
272	if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0)
273	    msg_fatal("%s: seek queue file %s: %m:",
274		      myname, VSTREAM_PATH(attr.fp));
275    while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) {
276	if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM)
277	    break;
278	status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type);
279    }
280    if (status == 0 && rec_type != REC_TYPE_XTRA) {
281	msg_warn("%s: bad record type: %d in message content",
282		 info->queue_id, rec_type);
283	status |= mark_corrupt(attr.fp);
284    }
285
286    /*
287     * Send the end-of-data marker only when there were no errors.
288     */
289    if (status == 0) {
290	rec_fputs(info->cleanup, REC_TYPE_XTRA, "");
291	rec_fputs(info->cleanup, REC_TYPE_END, "");
292    }
293
294    /*
295     * Retrieve the cleanup service completion status only if there are no
296     * problems.
297     */
298    if (status == 0)
299	if (vstream_fflush(info->cleanup)
300	    || attr_scan(info->cleanup, ATTR_FLAG_MISSING,
301			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
302			 ATTR_TYPE_END) != 1)
303	    status = 1;
304
305    /*
306     * Log successful forwarding.
307     *
308     * XXX DSN alias and .forward expansion already report SUCCESS, so don't do
309     * it again here.
310     */
311    if (status == 0) {
312	attr.rcpt.dsn_notify =
313	    (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ?
314	     DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS);
315	dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY,
316		   "forwarded as %s", info->queue_id);
317	status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr));
318    }
319
320    /*
321     * Cleanup.
322     */
323    vstring_free(buffer);
324    return (status);
325}
326
327/* forward_finish - complete message forwarding requests and clean up */
328
329int     forward_finish(DELIVER_REQUEST *request, DELIVER_ATTR attr, int cancel)
330{
331    HTABLE_INFO **dt_list;
332    HTABLE_INFO **dt;
333    HTABLE_INFO **sn_list;
334    HTABLE_INFO **sn;
335    HTABLE *table_snd;
336    char   *delivered;
337    char   *sender;
338    FORWARD_INFO *info;
339    int     status = cancel;
340
341    /*
342     * Sanity checks.
343     */
344    if (forward_dt == 0)
345	msg_panic("forward_finish: missing forward_init call");
346
347    /*
348     * Walk over all delivered-to header addresses and over each envelope
349     * sender address.
350     */
351    for (dt = dt_list = htable_list(forward_dt); *dt; dt++) {
352	delivered = dt[0]->key;
353	table_snd = (HTABLE *) dt[0]->value;
354	for (sn = sn_list = htable_list(table_snd); *sn; sn++) {
355	    sender = sn[0]->key;
356	    info = (FORWARD_INFO *) sn[0]->value;
357	    if (status == 0)
358		status |= forward_send(info, request, attr, delivered);
359	    if (msg_verbose)
360		msg_info("forward_finish: delivered %s sender %s status %d",
361			 delivered, sender, status);
362	    (void) vstream_fclose(info->cleanup);
363	    myfree(info->queue_id);
364	    myfree((char *) info);
365	}
366	myfree((char *) sn_list);
367	htable_free(table_snd, (void (*) (char *)) 0);
368    }
369    myfree((char *) dt_list);
370    htable_free(forward_dt, (void (*) (char *)) 0);
371    forward_dt = 0;
372    return (status);
373}
374