xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/post_mail.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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