1 /*        $NetBSD: abounce.c,v 1.4 2025/02/25 19:15:45 christos Exp $ */
2 
3 /*++
4 /* NAME
5 /*        abounce 3
6 /* SUMMARY
7 /*        asynchronous bounce/defer/trace service client
8 /* SYNOPSIS
9 /*        #include <abounce.h>
10 /*
11 /*        void      abounce_flush(flags, queue, id, encoding, sendopts, sender,
12 /*                                      dsn_envid, dsn_ret, callback, context)
13 /*        int       flags;
14 /*        const char *queue;
15 /*        const char *id;
16 /*        const char *encoding;
17 /*        int       sendopts;
18 /*        const char *sender;
19 /*        const char *dsn_envid;
20 /*        int       dsn_ret;
21 /*        void      (*callback)(int status, void *context);
22 /*        void      *context;
23 /*
24 /*        void      abounce_flush_verp(flags, queue, id, encoding, sendopts, sender,
25 /*                                      dsn_envid, dsn_ret, verp, callback, context)
26 /*        int       flags;
27 /*        const char *queue;
28 /*        const char *id;
29 /*        const char *encoding;
30 /*        int       sendopts;
31 /*        const char *sender;
32 /*        const char *dsn_envid;
33 /*        int       dsn_ret;
34 /*        const char *verp;
35 /*        void      (*callback)(int status, void *context);
36 /*        void      *context;
37 /*
38 /*        void      adefer_flush(flags, queue, id, encoding, sendopts, sender,
39 /*                                      dsn_envid, dsn_ret, callback, context)
40 /*        int       flags;
41 /*        const char *queue;
42 /*        const char *id;
43 /*        const char *encoding;
44 /*        int       sendopts;
45 /*        const char *sender;
46 /*        const char *dsn_envid;
47 /*        int       dsn_ret;
48 /*        void      (*callback)(int status, void *context);
49 /*        void      *context;
50 /*
51 /*        void      adefer_flush_verp(flags, queue, id, encoding, sendopts, sender,
52 /*                                      dsn_envid, dsn_ret, verp, callback, context)
53 /*        int       flags;
54 /*        const char *queue;
55 /*        const char *id;
56 /*        const char *encoding;
57 /*        int       sendopts;
58 /*        const char *sender;
59 /*        const char *dsn_envid;
60 /*        int       dsn_ret;
61 /*        const char *verp;
62 /*        void      (*callback)(int status, void *context);
63 /*        void      *context;
64 /*
65 /*        void      adefer_warn(flags, queue, id, encoding, sendopts, sender,
66 /*                                      dsn_envid, dsn_ret, callback, context)
67 /*        int       flags;
68 /*        const char *queue;
69 /*        const char *id;
70 /*        const char *encoding;
71 /*        int       sendopts;
72 /*        const char *sender;
73 /*        const char *dsn_envid;
74 /*        int       dsn_ret;
75 /*        void      (*callback)(int status, void *context);
76 /*        void      *context;
77 /*
78 /*        void      atrace_flush(flags, queue, id, encoding, sendopts, sender,
79 /*                                      dsn_envid, dsn_ret, callback, context)
80 /*        int       flags;
81 /*        const char *queue;
82 /*        const char *id;
83 /*        const char *encoding;
84 /*        int       sendopts;
85 /*        const char *sender;
86 /*        const char *dsn_envid;
87 /*        int       dsn_ret;
88 /*        void      (*callback)(int status, void *context);
89 /*        void      *context;
90 /* DESCRIPTION
91 /*        This module implements an asynchronous interface to the
92 /*        bounce/defer/trace service for submitting sender notifications
93 /*        without waiting for completion of the request.
94 /*
95 /*        abounce_flush() bounces the specified message to
96 /*        the specified sender, including the bounce log that was
97 /*        built with bounce_append().
98 /*
99 /*        abounce_flush_verp() is like abounce_flush() but sends
100 /*        one VERP style notification per undeliverable recipient.
101 /*
102 /*        adefer_flush() bounces the specified message to
103 /*        the specified sender, including the defer log that was
104 /*        built with defer_append().
105 /*        adefer_flush() requests that the deferred recipients are deleted
106 /*        from the original queue file.
107 /*
108 /*        adefer_flush_verp() is like adefer_flush() but sends
109 /*        one VERP style notification per undeliverable recipient.
110 /*
111 /*        adefer_warn() sends a "mail is delayed" notification to
112 /*        the specified sender, including the defer log that was
113 /*        built with defer_append().
114 /*
115 /*        atrace_flush() returns the specified message to the specified
116 /*        sender, including the message delivery record log that was
117 /*        built with vtrace_append().
118 /*
119 /*        Arguments:
120 /* .IP flags
121 /*        The bitwise OR of zero or more of the following (specify
122 /*        BOUNCE_FLAG_NONE to request no special processing):
123 /* .RS
124 /* .IP BOUNCE_FLAG_CLEAN
125 /*        Delete the bounce log in case of an error (as in: pretend
126 /*        that we never even tried to bounce this message).
127 /* .IP BOUNCE_FLAG_DELRCPT
128 /*        When specified with a flush operation, request that
129 /*        recipients be deleted from the queue file.
130 /*
131 /*        Note: the bounce daemon ignores this request when the
132 /*        recipient queue file offset is <= 0.
133 /* .IP BOUNCE_FLAG_COPY
134 /*        Request that a postmaster copy is sent.
135 /* .RE
136 /* .IP queue
137 /*        The message queue name of the original message file.
138 /* .IP id
139 /*        The message queue id if the original message file. The bounce log
140 /*        file has the same name as the original message file.
141 /* .IP encoding
142 /*        The body content encoding: MAIL_ATTR_ENC_{7BIT,8BIT,NONE}.
143 /* .IP sendopts
144 /*        Sender-requested SMTPUTF8 or RequireTLS support.
145 /* .IP sender
146 /*        The sender envelope address.
147 /* .IP dsn_envid
148 /*        Optional DSN envelope ID.
149 /* .IP ret
150 /*        Optional DSN return full/headers option.
151 /* .IP verp
152 /*        VERP delimiter characters.
153 /* .IP callback
154 /*        Name of a routine that receives the notification status as
155 /*        documented for bounce_flush() or defer_flush().
156 /* .IP context
157 /*        Application-specific context that is passed through to the
158 /*        callback routine. Use proper casts or the world will come
159 /*        to an end.
160 /* DIAGNOSTICS
161 /*        In case of success, these functions log the action, and return a
162 /*        zero result via the callback routine. Otherwise, the functions
163 /*        return a non-zero result via the callback routine, and when
164 /*        BOUNCE_FLAG_CLEAN is disabled, log that message delivery is deferred.
165 /* LICENSE
166 /* .ad
167 /* .fi
168 /*        The Secure Mailer license must be distributed with this software.
169 /* AUTHOR(S)
170 /*        Wietse Venema
171 /*        IBM T.J. Watson Research
172 /*        P.O. Box 704
173 /*        Yorktown Heights, NY 10598, USA
174 /*
175 /*        Wietse Venema
176 /*        Google, Inc.
177 /*        111 8th Avenue
178 /*        New York, NY 10011, USA
179 /*
180 /*        Wietse Venema
181 /*        porcupine.org
182 /*--*/
183 
184 /* System library. */
185 
186 #include <sys_defs.h>
187 
188 /* Utility library. */
189 
190 #include <msg.h>
191 #include <mymalloc.h>
192 #include <events.h>
193 #include <vstream.h>
194 
195 /* Global library. */
196 
197 #include <mail_params.h>
198 #include <mail_proto.h>
199 #include <abounce.h>
200 
201 /* Application-specific. */
202 
203  /*
204   * Each bounce/defer flush/warn request is implemented by sending the
205   * request to the bounce/defer server, and by creating a pseudo thread that
206   * suspends itself until the server replies (or dies). Upon wakeup, the
207   * pseudo thread delivers the request completion status to the application
208   * and destroys itself. The structure below maintains all the necessary
209   * request state while the pseudo thread is suspended.
210   */
211 typedef struct {
212     int     command;                              /* bounce request type */
213     int     flags;                      /* bounce options */
214     char   *id;                                   /* queue ID for logging */
215     VSTRING *request;                             /* serialized request */
216     ABOUNCE_FN callback;                /* application callback */
217     void   *context;                              /* application context */
218     VSTREAM *fp;                        /* server I/O handle */
219 } ABOUNCE_STATE;
220 
221  /*
222   * Encapsulate common code.
223   */
224 #define ABOUNCE_EVENT_ENABLE(fd, callback, context, timeout) do { \
225           event_enable_read((fd), (callback), (context)); \
226           event_request_timer((callback), (context), (timeout)); \
227     } while (0)
228 
229  /*
230   * If we set the reply timeout too short, then we make the problem worse by
231   * increasing overload. With 1000s timeout mail will keep flowing, but there
232   * will be a large number of blocked bounce processes, and some resource is
233   * likely to run out.
234   */
235 #define ABOUNCE_TIMEOUT       1000
236 
237  /*
238   * The initial buffer size for a serialized request.
239   */
240 #define ABOUNCE_BUFSIZE       VSTREAM_BUFSIZE
241 
242  /*
243   * We share most of the verp and non-verp code paths.
244   */
245 #define ABOUNCE_NO_VERP       ((char *) 0)
246 
247  /*
248   * SLMs.
249   */
250 #define STR(x) vstring_str(x)
251 #define LEN(x) VSTRING_LEN(x)
252 
253 /* abounce_done - deliver status to application and clean up pseudo thread */
254 
abounce_done(ABOUNCE_STATE * ap,int status)255 static void abounce_done(ABOUNCE_STATE *ap, int status)
256 {
257     if (ap->fp) {
258           event_disable_readwrite(vstream_fileno(ap->fp));
259           (void) vstream_fclose(ap->fp);
260     }
261     if (status != 0 && (ap->flags & BOUNCE_FLAG_CLEAN) == 0)
262           msg_info("%s: status=deferred (%s failed)", ap->id,
263                      ap->command == BOUNCE_CMD_FLUSH ? "bounce" :
264                      ap->command == BOUNCE_CMD_WARN ? "delay warning" :
265                      ap->command == BOUNCE_CMD_VERP ? "verp" :
266                      ap->command == BOUNCE_CMD_TRACE ? "trace" :
267                      "whatever");
268     ap->callback(status, ap->context);
269     myfree(ap->id);
270     vstring_free(ap->request);
271     myfree((void *) ap);
272 }
273 
274 /* abounce_receive - receive server reply */
275 
abounce_receive(int event,void * context)276 static void abounce_receive(int event, void *context)
277 {
278     ABOUNCE_STATE *ap = (ABOUNCE_STATE *) context;
279     int     status;
280 
281     if (event != EVENT_TIME)
282           event_cancel_timer(abounce_receive, context);
283 
284     if (event == EVENT_READ
285           && attr_scan(ap->fp, ATTR_FLAG_STRICT,
286                          RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
287                          ATTR_TYPE_END) == 1) {
288           abounce_done(ap, status);
289     } else {
290           abounce_done(ap, -1);
291     }
292 }
293 
294 /* abounce_send - send the request and suspend until the server replies */
295 
abounce_send(int event,void * context)296 static void abounce_send(int event, void *context)
297 {
298     ABOUNCE_STATE *ap = (ABOUNCE_STATE *) context;
299 
300     /*
301      * Receive the server's protocol name announcement. At this point the
302      * server is ready to receive a request without blocking the sender. Send
303      * the request and suspend until the server replies (or dies).
304      */
305     if (event != EVENT_TIME)
306           event_cancel_timer(abounce_send, context);
307 
308     non_blocking(vstream_fileno(ap->fp), BLOCKING);
309     if (event == EVENT_READ
310           && attr_scan(ap->fp, ATTR_FLAG_STRICT,
311                        RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_BOUNCE),
312                          ATTR_TYPE_END) == 0
313           && vstream_fwrite(ap->fp, STR(ap->request),
314                                 LEN(ap->request)) == LEN(ap->request)
315           && vstream_fflush(ap->fp) == 0) {
316           ABOUNCE_EVENT_ENABLE(vstream_fileno(ap->fp), abounce_receive,
317                                    (void *) ap, ABOUNCE_TIMEOUT);
318     } else {
319           abounce_done(ap, -1);
320     }
321 }
322 
323 /* abounce_connect - connect and suspend until the server replies */
324 
abounce_connect(const char * class,const char * service,int command,int flags,const char * queue,const char * id,const char * encoding,int sendopts,const char * sender,const char * dsn_envid,int dsn_ret,const char * verp,ABOUNCE_FN callback,void * context)325 static void abounce_connect(const char *class, const char *service,
326                                           int command, int flags,
327                                           const char *queue, const char *id,
328                                           const char *encoding, int sendopts,
329                                           const char *sender,
330                                           const char *dsn_envid, int dsn_ret,
331                                           const char *verp, ABOUNCE_FN callback,
332                                           void *context)
333 {
334     ABOUNCE_STATE *ap;
335 
336     /*
337      * Save pseudo thread state. Connect to the server. Prior to Postfix 3.6
338      * the asynchronous bounce flush/warn client called mail_connect_wait()
339      * which sleeps and retries several times before terminating with a fatal
340      * error. This block-and-sleep behavior was not consistent with a) the
341      * rest of the code in this module, and with b) the synchronous bounce
342      * client which gives up immediately. It should be safe to give up
343      * immediately because that leaves the bounce/defer/trace logs in the
344      * queue. In particular, this should not increase the simultaneous number
345      * of asynchronous bounce/defer/trace flush/warn requests that are in
346      * flight.
347      */
348     ap = (ABOUNCE_STATE *) mymalloc(sizeof(*ap));
349     ap->command = command;
350     ap->flags = flags;
351     ap->id = mystrdup(id);
352     ap->request = vstring_alloc(ABOUNCE_BUFSIZE);
353     ap->callback = callback;
354     ap->context = context;
355     ap->fp = mail_connect(class, service, NON_BLOCKING);
356 
357     /*
358      * Format the request now, so that we don't have to save a lot of
359      * arguments now and format the request later.
360      */
361     if (ap->fp != 0) {
362           /* Note: all code paths must terminate or enable I/O events. */
363           VSTREAM *mp = vstream_memopen(ap->request, O_WRONLY);
364 
365           if (attr_print(mp, ATTR_FLAG_MORE,
366                            SEND_ATTR_INT(MAIL_ATTR_NREQ, command),
367                            SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
368                            SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
369                            SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
370                            SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
371                            SEND_ATTR_INT(MAIL_ATTR_SENDOPTS, sendopts),
372                            SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
373                            SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
374                            SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret),
375                            ATTR_TYPE_END) != 0
376               || (verp != 0
377                     && attr_print(mp, ATTR_FLAG_MORE,
378                                     SEND_ATTR_STR(MAIL_ATTR_VERPDL, verp),
379                                     ATTR_TYPE_END) != 0)
380               || attr_print(mp, ATTR_FLAG_NONE,
381                                 ATTR_TYPE_END) != 0
382               || vstream_fclose(mp) != 0)
383               msg_panic("abounce_connect: write request to memory stream: %m");
384 
385           /*
386            * Suspend until the server replies (or dies).
387            */
388           ABOUNCE_EVENT_ENABLE(vstream_fileno(ap->fp), abounce_send,
389                                    (void *) ap, ABOUNCE_TIMEOUT);
390     } else {
391           abounce_done(ap, -1);
392     }
393 }
394 
395 /* abounce_flush_verp - asynchronous bounce flush */
396 
abounce_flush_verp(int flags,const char * queue,const char * id,const char * encoding,int sendopts,const char * sender,const char * dsn_envid,int dsn_ret,const char * verp,ABOUNCE_FN callback,void * context)397 void    abounce_flush_verp(int flags, const char *queue, const char *id,
398                                          const char *encoding, int sendopts,
399                                          const char *sender, const char *dsn_envid,
400                                          int dsn_ret, const char *verp,
401                                          ABOUNCE_FN callback,
402                                          void *context)
403 {
404     abounce_connect(MAIL_CLASS_PRIVATE, var_bounce_service,
405                         BOUNCE_CMD_VERP, flags, queue, id, encoding, sendopts,
406                         sender, dsn_envid, dsn_ret, verp, callback, context);
407 }
408 
409 /* adefer_flush_verp - asynchronous defer flush */
410 
adefer_flush_verp(int flags,const char * queue,const char * id,const char * encoding,int sendopts,const char * sender,const char * dsn_envid,int dsn_ret,const char * verp,ABOUNCE_FN callback,void * context)411 void    adefer_flush_verp(int flags, const char *queue, const char *id,
412                                         const char *encoding, int sendopts,
413                                         const char *sender, const char *dsn_envid,
414                                         int dsn_ret, const char *verp,
415                                         ABOUNCE_FN callback, void *context)
416 {
417     flags |= BOUNCE_FLAG_DELRCPT;
418     abounce_connect(MAIL_CLASS_PRIVATE, var_defer_service,
419                         BOUNCE_CMD_VERP, flags, queue, id, encoding, sendopts,
420                         sender, dsn_envid, dsn_ret, verp, callback, context);
421 }
422 
423 /* abounce_flush - asynchronous bounce flush */
424 
abounce_flush(int flags,const char * queue,const char * id,const char * encoding,int sendopts,const char * sender,const char * dsn_envid,int dsn_ret,ABOUNCE_FN callback,void * context)425 void    abounce_flush(int flags, const char *queue, const char *id,
426                                   const char *encoding, int sendopts,
427                                   const char *sender, const char *dsn_envid,
428                                   int dsn_ret, ABOUNCE_FN callback,
429                                   void *context)
430 {
431     abounce_connect(MAIL_CLASS_PRIVATE, var_bounce_service, BOUNCE_CMD_FLUSH,
432                         flags, queue, id, encoding, sendopts, sender, dsn_envid,
433                         dsn_ret, ABOUNCE_NO_VERP, callback, context);
434 }
435 
436 /* adefer_flush - asynchronous defer flush */
437 
adefer_flush(int flags,const char * queue,const char * id,const char * encoding,int sendopts,const char * sender,const char * dsn_envid,int dsn_ret,ABOUNCE_FN callback,void * context)438 void    adefer_flush(int flags, const char *queue, const char *id,
439                                  const char *encoding, int sendopts,
440                                  const char *sender, const char *dsn_envid,
441                                  int dsn_ret, ABOUNCE_FN callback, void *context)
442 {
443     flags |= BOUNCE_FLAG_DELRCPT;
444     abounce_connect(MAIL_CLASS_PRIVATE, var_defer_service, BOUNCE_CMD_FLUSH,
445                         flags, queue, id, encoding, sendopts, sender, dsn_envid,
446                         dsn_ret, ABOUNCE_NO_VERP, callback, context);
447 }
448 
449 /* adefer_warn - send copy of defer log to sender as warning bounce */
450 
adefer_warn(int flags,const char * queue,const char * id,const char * encoding,int sendopts,const char * sender,const char * dsn_envid,int dsn_ret,ABOUNCE_FN callback,void * context)451 void    adefer_warn(int flags, const char *queue, const char *id,
452                                 const char *encoding, int sendopts,
453                                 const char *sender, const char *dsn_envid,
454                                 int dsn_ret, ABOUNCE_FN callback, void *context)
455 {
456     abounce_connect(MAIL_CLASS_PRIVATE, var_defer_service, BOUNCE_CMD_WARN,
457                         flags, queue, id, encoding, sendopts, sender, dsn_envid,
458                         dsn_ret, ABOUNCE_NO_VERP, callback, context);
459 }
460 
461 /* atrace_flush - asynchronous trace flush */
462 
atrace_flush(int flags,const char * queue,const char * id,const char * encoding,int sendopts,const char * sender,const char * dsn_envid,int dsn_ret,ABOUNCE_FN callback,void * context)463 void    atrace_flush(int flags, const char *queue, const char *id,
464                                  const char *encoding, int sendopts,
465                                  const char *sender, const char *dsn_envid,
466                                  int dsn_ret, ABOUNCE_FN callback, void *context)
467 {
468     abounce_connect(MAIL_CLASS_PRIVATE, var_trace_service, BOUNCE_CMD_TRACE,
469                         flags, queue, id, encoding, sendopts, sender, dsn_envid,
470                         dsn_ret, ABOUNCE_NO_VERP, callback, context);
471 }
472