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