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