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