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