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