1 /* $NetBSD: post_mail.c,v 1.4 2022/10/08 16:12:45 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* post_mail 3
6 /* SUMMARY
7 /* convenient mail posting interface
8 /* SYNOPSIS
9 /* #include <post_mail.h>
10 /*
11 /* VSTREAM *post_mail_fopen(sender, recipient, source_class, trace_flags,
12 /* utf8_flags, queue_id)
13 /* const char *sender;
14 /* const char *recipient;
15 /* int source_class;
16 /* int trace_flags;
17 /* int utf8_flags;
18 /* VSTRING *queue_id;
19 /*
20 /* VSTREAM *post_mail_fopen_nowait(sender, recipient, source_class,
21 /* trace_flags, utf8_flags, queue_id)
22 /* const char *sender;
23 /* const char *recipient;
24 /* int source_class;
25 /* int trace_flags;
26 /* int utf8_flags;
27 /* VSTRING *queue_id;
28 /*
29 /* void post_mail_fopen_async(sender, recipient, source_class,
30 /* trace_flags, utf8_flags,
31 /* queue_id, notify, context)
32 /* const char *sender;
33 /* const char *recipient;
34 /* int source_class;
35 /* int trace_flags;
36 /* int utf8_flags;
37 /* VSTRING *queue_id;
38 /* void (*notify)(VSTREAM *stream, void *context);
39 /* void *context;
40 /*
41 /* int post_mail_fprintf(stream, format, ...)
42 /* VSTREAM *stream;
43 /* const char *format;
44 /*
45 /* int post_mail_fputs(stream, str)
46 /* VSTREAM *stream;
47 /* const char *str;
48 /*
49 /* int post_mail_buffer(stream, buf, len)
50 /* VSTREAM *stream;
51 /* const char *buffer;
52 /*
53 /* int POST_MAIL_BUFFER(stream, buf)
54 /* VSTREAM *stream;
55 /* VSTRING *buffer;
56 /*
57 /* int post_mail_fclose(stream)
58 /* VSTREAM *STREAM;
59 /*
60 /* void post_mail_fclose_async(stream, notify, context)
61 /* VSTREAM *stream;
62 /* void (*notify)(int status, void *context);
63 /* void *context;
64 /* DESCRIPTION
65 /* This module provides a convenient interface for the most
66 /* common case of sending one message to one recipient. It
67 /* allows the application to concentrate on message content,
68 /* without having to worry about queue file structure details.
69 /*
70 /* post_mail_fopen() opens a connection to the cleanup service
71 /* and waits until the service is available, does some option
72 /* negotiation, generates message envelope records, and generates
73 /* Received: and Date: message headers. The result is a stream
74 /* handle that can be used for sending message records.
75 /*
76 /* post_mail_fopen_nowait() tries to contact the cleanup service
77 /* only once, and does not wait until the cleanup service is
78 /* available. Otherwise it is identical to post_mail_fopen().
79 /*
80 /* post_mail_fopen_async() contacts the cleanup service and
81 /* invokes the caller-specified notify routine, with the
82 /* open stream and the caller-specified context when the
83 /* service responds, or with a null stream and the caller-specified
84 /* context when the request could not be completed. It is the
85 /* responsibility of the application to close an open stream.
86 /*
87 /* post_mail_fprintf() formats message content (header or body)
88 /* and sends it to the cleanup service.
89 /*
90 /* post_mail_fputs() sends pre-formatted content (header or body)
91 /* to the cleanup service.
92 /*
93 /* post_mail_buffer() sends a pre-formatted buffer to the
94 /* cleanup service.
95 /*
96 /* POST_MAIL_BUFFER() is a wrapper for post_mail_buffer() that
97 /* evaluates its buffer argument more than once.
98 /*
99 /* post_mail_fclose() completes the posting of a message.
100 /*
101 /* post_mail_fclose_async() completes the posting of a message
102 /* and upon completion invokes the caller-specified notify
103 /* routine, with the cleanup status and caller-specified context
104 /* as arguments.
105 /*
106 /* Arguments:
107 /* .IP sender
108 /* The sender envelope address. It is up to the application
109 /* to produce From: headers.
110 /* .IP recipient
111 /* The recipient envelope address. It is up to the application
112 /* to produce To: headers.
113 /* .IP source_class
114 /* The message source class, as defined in \fB<mail_proto.h>\fR.
115 /* Depending on the setting of the internal_mail_source_classes
116 /* and smtputf8_autodetect_classes parameters, the message
117 /* will or won't be subject to content inspection or SMTPUTF8
118 /* autodetection.
119 /* .IP trace_flags
120 /* Message tracing flags as specified in \fB<deliver_request.h>\fR.
121 /* .IP utf8_flags
122 /* Flags defined in <smtputf8.h>. Flags other than
123 /* SMTPUTF8_FLAG_REQUESTED are ignored.
124 /* .IP queue_id
125 /* Null pointer, or pointer to buffer that receives the queue
126 /* ID of the new message.
127 /* .IP stream
128 /* A stream opened by mail_post_fopen().
129 /* .IP notify
130 /* Application call-back routine.
131 /* .IP context
132 /* Application call-back context.
133 /* DIAGNOSTICS
134 /* post_mail_fopen_nowait() returns a null pointer when the
135 /* cleanup service is not available immediately.
136 /*
137 /* post_mail_fopen_async() returns a null pointer when the
138 /* attempt to contact the cleanup service fails immediately.
139 /*
140 /* post_mail_fprintf(), post_mail_fputs() post_mail_fclose(),
141 /* and post_mail_buffer() return the binary OR of the error
142 /* status codes defined in \fI<cleanup_user.h>\fR.
143 /*
144 /* Fatal errors: cleanup initial handshake errors. This means
145 /* the client and server speak incompatible protocols.
146 /* SEE ALSO
147 /* cleanup_user(3h) cleanup options and results
148 /* cleanup_strerror(3) translate results to text
149 /* cleanup(8) cleanup service
150 /* LICENSE
151 /* .ad
152 /* .fi
153 /* The Secure Mailer license must be distributed with this software.
154 /* AUTHOR(S)
155 /* Wietse Venema
156 /* IBM T.J. Watson Research
157 /* P.O. Box 704
158 /* Yorktown Heights, NY 10598, USA
159 /*
160 /* Wietse Venema
161 /* Google, Inc.
162 /* 111 8th Avenue
163 /* New York, NY 10011, USA
164 /*--*/
165
166 /* System library. */
167
168 #include <sys_defs.h>
169 #include <sys/time.h>
170 #include <stdlib.h> /* 44BSD stdarg.h uses abort() */
171 #include <stdarg.h>
172 #include <string.h>
173
174 /* Utility library. */
175
176 #include <msg.h>
177 #include <vstream.h>
178 #include <vstring.h>
179 #include <mymalloc.h>
180 #include <events.h>
181
182 /* Global library. */
183
184 #include <mail_params.h>
185 #include <record.h>
186 #include <rec_type.h>
187 #include <mail_proto.h>
188 #include <cleanup_user.h>
189 #include <post_mail.h>
190 #include <mail_date.h>
191
192 /*
193 * Call-back state for asynchronous connection requests.
194 */
195 typedef struct {
196 char *sender;
197 char *recipient;
198 int source_class;
199 int trace_flags;
200 int utf8_flags;
201 POST_MAIL_NOTIFY notify;
202 void *context;
203 VSTREAM *stream;
204 VSTRING *queue_id;
205 } POST_MAIL_STATE;
206
207 /*
208 * Call-back state for asynchronous close requests.
209 */
210 typedef struct {
211 int status;
212 VSTREAM *stream;
213 POST_MAIL_FCLOSE_NOTIFY notify;
214 void *context;
215 } POST_MAIL_FCLOSE_STATE;
216
217 /* post_mail_init - initial negotiations */
218
post_mail_init(VSTREAM * stream,const char * sender,const char * recipient,int source_class,int trace_flags,int utf8_flags,VSTRING * queue_id)219 static void post_mail_init(VSTREAM *stream, const char *sender,
220 const char *recipient,
221 int source_class, int trace_flags,
222 int utf8_flags, VSTRING *queue_id)
223 {
224 VSTRING *id = queue_id ? queue_id : vstring_alloc(100);
225 struct timeval now;
226 const char *date;
227 int cleanup_flags =
228 int_filt_flags(source_class) | CLEANUP_FLAG_MASK_INTERNAL
229 | smtputf8_autodetect(source_class)
230 | ((utf8_flags & SMTPUTF8_FLAG_REQUESTED) ? CLEANUP_FLAG_SMTPUTF8 : 0);
231
232 GETTIMEOFDAY(&now);
233 date = mail_date(now.tv_sec);
234
235 /*
236 * The comment in the next paragraph is likely obsolete. Fix 20030610
237 * changed the verify server to use asynchronous submission of mail
238 * probes, to avoid blocking the post_mail client for in_flow_delay
239 * seconds when the cleanup service receives email messages faster than
240 * they are delivered. Instead, the post_mail client waits until the
241 * cleanup server announces its availability to receive input. A similar
242 * change was made at the end of submission, to avoid blocking the
243 * post_mail client for up to trigger_timeout seconds when the cleanup
244 * server attempts to notify a queue manager that is overwhelmed.
245 *
246 * XXX Don't flush buffers while sending the initial message records. That
247 * would cause deadlock between verify(8) and cleanup(8) servers.
248 */
249 vstream_control(stream, VSTREAM_CTL_BUFSIZE, 2 * VSTREAM_BUFSIZE,
250 VSTREAM_CTL_END);
251
252 /*
253 * Negotiate with the cleanup service. Give up if we can't agree.
254 */
255 if (attr_scan(stream, ATTR_FLAG_STRICT,
256 RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP),
257 RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id),
258 ATTR_TYPE_END) != 1
259 || attr_print(stream, ATTR_FLAG_NONE,
260 SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
261 ATTR_TYPE_END) != 0)
262 msg_fatal("unable to contact the %s service", var_cleanup_service);
263
264 /*
265 * Generate a minimal envelope section. The cleanup service will add a
266 * size record.
267 */
268 rec_fprintf(stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
269 REC_TYPE_TIME_ARG(now));
270 rec_fprintf(stream, REC_TYPE_ATTR, "%s=%s",
271 MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL);
272 rec_fprintf(stream, REC_TYPE_ATTR, "%s=%d",
273 MAIL_ATTR_TRACE_FLAGS, trace_flags);
274 rec_fputs(stream, REC_TYPE_FROM, sender);
275 rec_fputs(stream, REC_TYPE_RCPT, recipient);
276 rec_fputs(stream, REC_TYPE_MESG, "");
277
278 /*
279 * Do the Received: and Date: header lines. This allows us to shave a few
280 * cycles by using the expensive date conversion result for both.
281 */
282 post_mail_fprintf(stream, "Received: by %s (%s)",
283 var_myhostname, var_mail_name);
284 post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date);
285 post_mail_fprintf(stream, "Date: %s", date);
286 if (queue_id == 0)
287 vstring_free(id);
288 }
289
290 /* post_mail_fopen - prepare for posting a message */
291
post_mail_fopen(const char * sender,const char * recipient,int source_class,int trace_flags,int utf8_flags,VSTRING * queue_id)292 VSTREAM *post_mail_fopen(const char *sender, const char *recipient,
293 int source_class, int trace_flags,
294 int utf8_flags, VSTRING *queue_id)
295 {
296 VSTREAM *stream;
297
298 stream = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service);
299 post_mail_init(stream, sender, recipient, source_class, trace_flags,
300 utf8_flags, queue_id);
301 return (stream);
302 }
303
304 /* post_mail_fopen_nowait - prepare for posting a message */
305
post_mail_fopen_nowait(const char * sender,const char * recipient,int source_class,int trace_flags,int utf8_flags,VSTRING * queue_id)306 VSTREAM *post_mail_fopen_nowait(const char *sender, const char *recipient,
307 int source_class, int trace_flags,
308 int utf8_flags, VSTRING *queue_id)
309 {
310 VSTREAM *stream;
311
312 if ((stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service,
313 BLOCKING)) != 0)
314 post_mail_init(stream, sender, recipient, source_class, trace_flags,
315 utf8_flags, queue_id);
316 else
317 msg_warn("connect to %s/%s: %m",
318 MAIL_CLASS_PUBLIC, var_cleanup_service);
319 return (stream);
320 }
321
322 /* post_mail_open_event - handle asynchronous connection events */
323
post_mail_open_event(int event,void * context)324 static void post_mail_open_event(int event, void *context)
325 {
326 POST_MAIL_STATE *state = (POST_MAIL_STATE *) context;
327 const char *myname = "post_mail_open_event";
328
329 switch (event) {
330
331 /*
332 * Initial server reply. Stop the watchdog timer, disable further
333 * read events that end up calling this function, and notify the
334 * requestor.
335 */
336 case EVENT_READ:
337 if (msg_verbose)
338 msg_info("%s: read event", myname);
339 event_cancel_timer(post_mail_open_event, context);
340 event_disable_readwrite(vstream_fileno(state->stream));
341 non_blocking(vstream_fileno(state->stream), BLOCKING);
342 post_mail_init(state->stream, state->sender,
343 state->recipient, state->source_class,
344 state->trace_flags, state->utf8_flags,
345 state->queue_id);
346 myfree(state->sender);
347 myfree(state->recipient);
348 state->notify(state->stream, state->context);
349 myfree((void *) state);
350 return;
351
352 /*
353 * No connection or no initial reply within a conservative time
354 * limit. The system is broken and we give up.
355 */
356 case EVENT_TIME:
357 if (state->stream) {
358 msg_warn("timeout connecting to service: %s", var_cleanup_service);
359 event_disable_readwrite(vstream_fileno(state->stream));
360 vstream_fclose(state->stream);
361 } else {
362 msg_warn("connect to service: %s: %m", var_cleanup_service);
363 }
364 myfree(state->sender);
365 myfree(state->recipient);
366 state->notify((VSTREAM *) 0, state->context);
367 myfree((void *) state);
368 return;
369
370 /*
371 * Some exception.
372 */
373 case EVENT_XCPT:
374 msg_warn("error connecting to service: %s", var_cleanup_service);
375 event_cancel_timer(post_mail_open_event, context);
376 event_disable_readwrite(vstream_fileno(state->stream));
377 vstream_fclose(state->stream);
378 myfree(state->sender);
379 myfree(state->recipient);
380 state->notify((VSTREAM *) 0, state->context);
381 myfree((void *) state);
382 return;
383
384 /*
385 * Broken software or hardware.
386 */
387 default:
388 msg_panic("%s: unknown event type %d", myname, event);
389 }
390 }
391
392 /* post_mail_fopen_async - prepare for posting a message */
393
post_mail_fopen_async(const char * sender,const char * recipient,int source_class,int trace_flags,int utf8_flags,VSTRING * queue_id,void (* notify)(VSTREAM *,void *),void * context)394 void post_mail_fopen_async(const char *sender, const char *recipient,
395 int source_class, int trace_flags,
396 int utf8_flags, VSTRING *queue_id,
397 void (*notify) (VSTREAM *, void *),
398 void *context)
399 {
400 VSTREAM *stream;
401 POST_MAIL_STATE *state;
402
403 stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, NON_BLOCKING);
404 state = (POST_MAIL_STATE *) mymalloc(sizeof(*state));
405 state->sender = mystrdup(sender);
406 state->recipient = mystrdup(recipient);
407 state->source_class = source_class;
408 state->trace_flags = trace_flags;
409 state->utf8_flags = utf8_flags;
410 state->notify = notify;
411 state->context = context;
412 state->stream = stream;
413 state->queue_id = queue_id;
414
415 /*
416 * To keep interfaces as simple as possible we report all errors via the
417 * same interface as all successes.
418 */
419 if (stream != 0) {
420 event_enable_read(vstream_fileno(stream), post_mail_open_event,
421 (void *) state);
422 event_request_timer(post_mail_open_event, (void *) state,
423 var_daemon_timeout);
424 } else {
425 event_request_timer(post_mail_open_event, (void *) state, 0);
426 }
427 }
428
429 /* post_mail_fprintf - format and send message content */
430
post_mail_fprintf(VSTREAM * cleanup,const char * format,...)431 int post_mail_fprintf(VSTREAM *cleanup, const char *format,...)
432 {
433 int status;
434 va_list ap;
435
436 va_start(ap, format);
437 status = rec_vfprintf(cleanup, REC_TYPE_NORM, format, ap);
438 va_end(ap);
439 return (status != REC_TYPE_NORM ? CLEANUP_STAT_WRITE : 0);
440 }
441
442 /* post_mail_buffer - send pre-formatted buffer */
443
post_mail_buffer(VSTREAM * cleanup,const char * buf,int len)444 int post_mail_buffer(VSTREAM *cleanup, const char *buf, int len)
445 {
446 return (rec_put(cleanup, REC_TYPE_NORM, buf, len) != REC_TYPE_NORM ?
447 CLEANUP_STAT_WRITE : 0);
448 }
449
450 /* post_mail_fputs - send pre-formatted message content */
451
post_mail_fputs(VSTREAM * cleanup,const char * str)452 int post_mail_fputs(VSTREAM *cleanup, const char *str)
453 {
454 ssize_t len = str ? strlen(str) : 0;
455
456 return (rec_put(cleanup, REC_TYPE_NORM, str, len) != REC_TYPE_NORM ?
457 CLEANUP_STAT_WRITE : 0);
458 }
459
460 /* post_mail_fclose - finish posting of message */
461
post_mail_fclose(VSTREAM * cleanup)462 int post_mail_fclose(VSTREAM *cleanup)
463 {
464 int status = 0;
465
466 /*
467 * Send the message end marker only when there were no errors.
468 */
469 if (vstream_ferror(cleanup) != 0) {
470 status = CLEANUP_STAT_WRITE;
471 } else {
472 rec_fputs(cleanup, REC_TYPE_XTRA, "");
473 rec_fputs(cleanup, REC_TYPE_END, "");
474 if (vstream_fflush(cleanup)
475 || attr_scan(cleanup, ATTR_FLAG_MISSING,
476 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
477 ATTR_TYPE_END) != 1)
478 status = CLEANUP_STAT_WRITE;
479 }
480 (void) vstream_fclose(cleanup);
481 return (status);
482 }
483
484 /* post_mail_fclose_event - event handler */
485
post_mail_fclose_event(int event,void * context)486 static void post_mail_fclose_event(int event, void *context)
487 {
488 POST_MAIL_FCLOSE_STATE *state = (POST_MAIL_FCLOSE_STATE *) context;
489 int status = state->status;
490
491 switch (event) {
492
493 /*
494 * Final server reply. Pick up the completion status.
495 */
496 case EVENT_READ:
497 if (status == 0) {
498 if (vstream_ferror(state->stream) != 0
499 || attr_scan(state->stream, ATTR_FLAG_MISSING,
500 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
501 ATTR_TYPE_END) != 1)
502 status = CLEANUP_STAT_WRITE;
503 }
504 break;
505
506 /*
507 * No response or error.
508 */
509 default:
510 msg_warn("error talking to service: %s", var_cleanup_service);
511 status = CLEANUP_STAT_WRITE;
512 break;
513 }
514
515 /*
516 * Stop the watchdog timer, and disable further read events that end up
517 * calling this function.
518 */
519 event_cancel_timer(post_mail_fclose_event, context);
520 event_disable_readwrite(vstream_fileno(state->stream));
521
522 /*
523 * Notify the requestor and clean up.
524 */
525 state->notify(status, state->context);
526 (void) vstream_fclose(state->stream);
527 myfree((void *) state);
528 }
529
530 /* post_mail_fclose_async - finish posting of message */
531
post_mail_fclose_async(VSTREAM * stream,void (* notify)(int status,void * context),void * context)532 void post_mail_fclose_async(VSTREAM *stream,
533 void (*notify) (int status, void *context),
534 void *context)
535 {
536 POST_MAIL_FCLOSE_STATE *state;
537 int status = 0;
538
539
540 /*
541 * Send the message end marker only when there were no errors.
542 */
543 if (vstream_ferror(stream) != 0) {
544 status = CLEANUP_STAT_WRITE;
545 } else {
546 rec_fputs(stream, REC_TYPE_XTRA, "");
547 rec_fputs(stream, REC_TYPE_END, "");
548 if (vstream_fflush(stream))
549 status = CLEANUP_STAT_WRITE;
550 }
551
552 /*
553 * Bundle up the suspended state.
554 */
555 state = (POST_MAIL_FCLOSE_STATE *) mymalloc(sizeof(*state));
556 state->status = status;
557 state->stream = stream;
558 state->notify = notify;
559 state->context = context;
560
561 /*
562 * To keep interfaces as simple as possible we report all errors via the
563 * same interface as all successes.
564 */
565 if (status == 0) {
566 event_enable_read(vstream_fileno(stream), post_mail_fclose_event,
567 (void *) state);
568 event_request_timer(post_mail_fclose_event, (void *) state,
569 var_daemon_timeout);
570 } else {
571 event_request_timer(post_mail_fclose_event, (void *) state, 0);
572 }
573 }
574