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