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 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 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 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 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 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 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 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 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 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 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 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