xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/post_mail.c (revision dd3ee07da436799d8de85f3055253118b76bf345)
1 /*	$NetBSD: post_mail.c,v 1.3 2020/03/18 19:05:16 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 
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      * XXX Don't flush buffers while sending the initial message records.
237      * That would cause deadlock between verify(8) and cleanup(8) servers.
238      */
239     vstream_control(stream, VSTREAM_CTL_BUFSIZE, 2 * VSTREAM_BUFSIZE,
240 		    VSTREAM_CTL_END);
241 
242     /*
243      * Negotiate with the cleanup service. Give up if we can't agree.
244      */
245     if (attr_scan(stream, ATTR_FLAG_STRICT,
246 		  RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id),
247 		  ATTR_TYPE_END) != 1
248 	|| attr_print(stream, ATTR_FLAG_NONE,
249 		      SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
250 		      ATTR_TYPE_END) != 0)
251 	msg_fatal("unable to contact the %s service", var_cleanup_service);
252 
253     /*
254      * Generate a minimal envelope section. The cleanup service will add a
255      * size record.
256      */
257     rec_fprintf(stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
258 		REC_TYPE_TIME_ARG(now));
259     rec_fprintf(stream, REC_TYPE_ATTR, "%s=%s",
260 		MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL);
261     rec_fprintf(stream, REC_TYPE_ATTR, "%s=%d",
262 		MAIL_ATTR_TRACE_FLAGS, trace_flags);
263     rec_fputs(stream, REC_TYPE_FROM, sender);
264     rec_fputs(stream, REC_TYPE_RCPT, recipient);
265     rec_fputs(stream, REC_TYPE_MESG, "");
266 
267     /*
268      * Do the Received: and Date: header lines. This allows us to shave a few
269      * cycles by using the expensive date conversion result for both.
270      */
271     post_mail_fprintf(stream, "Received: by %s (%s)",
272 		      var_myhostname, var_mail_name);
273     post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date);
274     post_mail_fprintf(stream, "Date: %s", date);
275     if (queue_id == 0)
276 	vstring_free(id);
277 }
278 
279 /* post_mail_fopen - prepare for posting a message */
280 
281 VSTREAM *post_mail_fopen(const char *sender, const char *recipient,
282 			         int source_class, int trace_flags,
283 			         int utf8_flags, VSTRING *queue_id)
284 {
285     VSTREAM *stream;
286 
287     stream = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service);
288     post_mail_init(stream, sender, recipient, source_class, trace_flags,
289 		   utf8_flags, queue_id);
290     return (stream);
291 }
292 
293 /* post_mail_fopen_nowait - prepare for posting a message */
294 
295 VSTREAM *post_mail_fopen_nowait(const char *sender, const char *recipient,
296 				        int source_class, int trace_flags,
297 				        int utf8_flags, VSTRING *queue_id)
298 {
299     VSTREAM *stream;
300 
301     if ((stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service,
302 			       BLOCKING)) != 0)
303 	post_mail_init(stream, sender, recipient, source_class, trace_flags,
304 		       utf8_flags, queue_id);
305     else
306 	msg_warn("connect to %s/%s: %m",
307 		 MAIL_CLASS_PUBLIC, var_cleanup_service);
308     return (stream);
309 }
310 
311 /* post_mail_open_event - handle asynchronous connection events */
312 
313 static void post_mail_open_event(int event, void *context)
314 {
315     POST_MAIL_STATE *state = (POST_MAIL_STATE *) context;
316     const char *myname = "post_mail_open_event";
317 
318     switch (event) {
319 
320 	/*
321 	 * Initial server reply. Stop the watchdog timer, disable further
322 	 * read events that end up calling this function, and notify the
323 	 * requestor.
324 	 */
325     case EVENT_READ:
326 	if (msg_verbose)
327 	    msg_info("%s: read event", myname);
328 	event_cancel_timer(post_mail_open_event, context);
329 	event_disable_readwrite(vstream_fileno(state->stream));
330 	non_blocking(vstream_fileno(state->stream), BLOCKING);
331 	post_mail_init(state->stream, state->sender,
332 		       state->recipient, state->source_class,
333 		       state->trace_flags, state->utf8_flags,
334 		       state->queue_id);
335 	myfree(state->sender);
336 	myfree(state->recipient);
337 	state->notify(state->stream, state->context);
338 	myfree((void *) state);
339 	return;
340 
341 	/*
342 	 * No connection or no initial reply within a conservative time
343 	 * limit. The system is broken and we give up.
344 	 */
345     case EVENT_TIME:
346 	if (state->stream) {
347 	    msg_warn("timeout connecting to service: %s", var_cleanup_service);
348 	    event_disable_readwrite(vstream_fileno(state->stream));
349 	    vstream_fclose(state->stream);
350 	} else {
351 	    msg_warn("connect to service: %s: %m", var_cleanup_service);
352 	}
353 	myfree(state->sender);
354 	myfree(state->recipient);
355 	state->notify((VSTREAM *) 0, state->context);
356 	myfree((void *) state);
357 	return;
358 
359 	/*
360 	 * Some exception.
361 	 */
362     case EVENT_XCPT:
363 	msg_warn("error connecting to service: %s", var_cleanup_service);
364 	event_cancel_timer(post_mail_open_event, context);
365 	event_disable_readwrite(vstream_fileno(state->stream));
366 	vstream_fclose(state->stream);
367 	myfree(state->sender);
368 	myfree(state->recipient);
369 	state->notify((VSTREAM *) 0, state->context);
370 	myfree((void *) state);
371 	return;
372 
373 	/*
374 	 * Broken software or hardware.
375 	 */
376     default:
377 	msg_panic("%s: unknown event type %d", myname, event);
378     }
379 }
380 
381 /* post_mail_fopen_async - prepare for posting a message */
382 
383 void    post_mail_fopen_async(const char *sender, const char *recipient,
384 			              int source_class, int trace_flags,
385 			              int utf8_flags, VSTRING *queue_id,
386 			              void (*notify) (VSTREAM *, void *),
387 			              void *context)
388 {
389     VSTREAM *stream;
390     POST_MAIL_STATE *state;
391 
392     stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, NON_BLOCKING);
393     state = (POST_MAIL_STATE *) mymalloc(sizeof(*state));
394     state->sender = mystrdup(sender);
395     state->recipient = mystrdup(recipient);
396     state->source_class = source_class;
397     state->trace_flags = trace_flags;
398     state->utf8_flags = utf8_flags;
399     state->notify = notify;
400     state->context = context;
401     state->stream = stream;
402     state->queue_id = queue_id;
403 
404     /*
405      * To keep interfaces as simple as possible we report all errors via the
406      * same interface as all successes.
407      */
408     if (stream != 0) {
409 	event_enable_read(vstream_fileno(stream), post_mail_open_event,
410 			  (void *) state);
411 	event_request_timer(post_mail_open_event, (void *) state,
412 			    var_daemon_timeout);
413     } else {
414 	event_request_timer(post_mail_open_event, (void *) state, 0);
415     }
416 }
417 
418 /* post_mail_fprintf - format and send message content */
419 
420 int     post_mail_fprintf(VSTREAM *cleanup, const char *format,...)
421 {
422     int     status;
423     va_list ap;
424 
425     va_start(ap, format);
426     status = rec_vfprintf(cleanup, REC_TYPE_NORM, format, ap);
427     va_end(ap);
428     return (status != REC_TYPE_NORM ? CLEANUP_STAT_WRITE : 0);
429 }
430 
431 /* post_mail_buffer - send pre-formatted buffer */
432 
433 int     post_mail_buffer(VSTREAM *cleanup, const char *buf, int len)
434 {
435     return (rec_put(cleanup, REC_TYPE_NORM, buf, len) != REC_TYPE_NORM ?
436 	    CLEANUP_STAT_WRITE : 0);
437 }
438 
439 /* post_mail_fputs - send pre-formatted message content */
440 
441 int     post_mail_fputs(VSTREAM *cleanup, const char *str)
442 {
443     ssize_t len = str ? strlen(str) : 0;
444 
445     return (rec_put(cleanup, REC_TYPE_NORM, str, len) != REC_TYPE_NORM ?
446 	    CLEANUP_STAT_WRITE : 0);
447 }
448 
449 /* post_mail_fclose - finish posting of message */
450 
451 int     post_mail_fclose(VSTREAM *cleanup)
452 {
453     int     status = 0;
454 
455     /*
456      * Send the message end marker only when there were no errors.
457      */
458     if (vstream_ferror(cleanup) != 0) {
459 	status = CLEANUP_STAT_WRITE;
460     } else {
461 	rec_fputs(cleanup, REC_TYPE_XTRA, "");
462 	rec_fputs(cleanup, REC_TYPE_END, "");
463 	if (vstream_fflush(cleanup)
464 	    || attr_scan(cleanup, ATTR_FLAG_MISSING,
465 			 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
466 			 ATTR_TYPE_END) != 1)
467 	    status = CLEANUP_STAT_WRITE;
468     }
469     (void) vstream_fclose(cleanup);
470     return (status);
471 }
472 
473 /* post_mail_fclose_event - event handler */
474 
475 static void post_mail_fclose_event(int event, void *context)
476 {
477     POST_MAIL_FCLOSE_STATE *state = (POST_MAIL_FCLOSE_STATE *) context;
478     int     status = state->status;
479 
480     switch (event) {
481 
482 	/*
483 	 * Final server reply. Pick up the completion status.
484 	 */
485     case EVENT_READ:
486 	if (status == 0) {
487 	    if (vstream_ferror(state->stream) != 0
488 		|| attr_scan(state->stream, ATTR_FLAG_MISSING,
489 			     ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
490 			     ATTR_TYPE_END) != 1)
491 		status = CLEANUP_STAT_WRITE;
492 	}
493 	break;
494 
495 	/*
496 	 * No response or error.
497 	 */
498     default:
499 	msg_warn("error talking to service: %s", var_cleanup_service);
500 	status = CLEANUP_STAT_WRITE;
501 	break;
502     }
503 
504     /*
505      * Stop the watchdog timer, and disable further read events that end up
506      * calling this function.
507      */
508     event_cancel_timer(post_mail_fclose_event, context);
509     event_disable_readwrite(vstream_fileno(state->stream));
510 
511     /*
512      * Notify the requestor and clean up.
513      */
514     state->notify(status, state->context);
515     (void) vstream_fclose(state->stream);
516     myfree((void *) state);
517 }
518 
519 /* post_mail_fclose_async - finish posting of message */
520 
521 void    post_mail_fclose_async(VSTREAM *stream,
522 			         void (*notify) (int status, void *context),
523 			               void *context)
524 {
525     POST_MAIL_FCLOSE_STATE *state;
526     int     status = 0;
527 
528 
529     /*
530      * Send the message end marker only when there were no errors.
531      */
532     if (vstream_ferror(stream) != 0) {
533 	status = CLEANUP_STAT_WRITE;
534     } else {
535 	rec_fputs(stream, REC_TYPE_XTRA, "");
536 	rec_fputs(stream, REC_TYPE_END, "");
537 	if (vstream_fflush(stream))
538 	    status = CLEANUP_STAT_WRITE;
539     }
540 
541     /*
542      * Bundle up the suspended state.
543      */
544     state = (POST_MAIL_FCLOSE_STATE *) mymalloc(sizeof(*state));
545     state->status = status;
546     state->stream = stream;
547     state->notify = notify;
548     state->context = context;
549 
550     /*
551      * To keep interfaces as simple as possible we report all errors via the
552      * same interface as all successes.
553      */
554     if (status == 0) {
555 	event_enable_read(vstream_fileno(stream), post_mail_fclose_event,
556 			  (void *) state);
557 	event_request_timer(post_mail_fclose_event, (void *) state,
558 			    var_daemon_timeout);
559     } else {
560 	event_request_timer(post_mail_fclose_event, (void *) state, 0);
561     }
562 }
563