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