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