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