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