1 /* $NetBSD: mail_stream.c,v 1.3 2022/10/08 16:12:45 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* mail_stream 3 6 /* SUMMARY 7 /* mail stream management 8 /* SYNOPSIS 9 /* #include <mail_stream.h> 10 /* 11 /* typedef struct { 12 /* .in +4 13 /* VSTREAM *stream; /* read/write stream */ 14 /* char *id; /* queue ID */ 15 /* struct timeval ctime; /* create time */ 16 /* private members... 17 /* .in -4 18 /* } MAIL_STREAM; 19 /* 20 /* MAIL_STREAM *mail_stream_file(queue, class, service, mode) 21 /* const char *queue; 22 /* const char *class; 23 /* const char *service; 24 /* int mode; 25 /* 26 /* MAIL_STREAM *mail_stream_service(class, service) 27 /* const char *class; 28 /* const char *service; 29 /* 30 /* MAIL_STREAM *mail_stream_command(command) 31 /* const char *command; 32 /* 33 /* void mail_stream_cleanup(info) 34 /* MAIL_STREAM *info; 35 /* 36 /* int mail_stream_finish(info, why) 37 /* MAIL_STREAM *info; 38 /* VSTRING *why; 39 /* 40 /* void mail_stream_ctl(info, op, ...) 41 /* MAIL_STREAM *info; 42 /* int op; 43 /* DESCRIPTION 44 /* This module provides a generic interface to Postfix queue file 45 /* format messages to file, to Postfix server, or to external command. 46 /* The routines that open a stream return a handle with an initialized 47 /* stream and queue id member. The handle is either given to a cleanup 48 /* routine, to dispose of a failed request, or to a finish routine, to 49 /* complete the request. 50 /* 51 /* mail_stream_file() opens a mail stream to a newly-created file and 52 /* arranges for trigger delivery at finish time. This call never fails. 53 /* But it may take forever. The mode argument specifies additional 54 /* file permissions that will be OR-ed in when the file is finished. 55 /* While embryonic files have mode 0600, finished files have mode 0700. 56 /* 57 /* mail_stream_command() opens a mail stream to external command, 58 /* and receives queue ID information from the command. The result 59 /* is a null pointer when the initial handshake fails. The command 60 /* is given to the shell only when necessary. At finish time, the 61 /* command is expected to send a completion status. 62 /* 63 /* mail_stream_service() opens a mail stream to Postfix service, 64 /* and receives queue ID information from the command. The result 65 /* is a null pointer when the initial handshake fails. At finish 66 /* time, the daemon is expected to send a completion status. 67 /* 68 /* mail_stream_cleanup() cancels the operation that was started with 69 /* any of the mail_stream_xxx() routines, and destroys the argument. 70 /* It is up to the caller to remove incomplete file objects. 71 /* 72 /* mail_stream_finish() completes the operation that was started with 73 /* any of the mail_stream_xxx() routines, and destroys the argument. 74 /* The result is any of the status codes defined in <cleanup_user.h>. 75 /* It is up to the caller to remove incomplete file objects. 76 /* The why argument can be a null pointer. 77 /* 78 /* mail_stream_ctl() selectively overrides information that 79 /* was specified with mail_stream_file(); none of the attributes 80 /* are applicable for other mail stream types. The arguments 81 /* are a list macros with arguments, terminated with 82 /* CA_MAIL_STREAM_CTL_END which has none. The following lists 83 /* the macros and the types of the corresponding arguments. 84 /* .IP "CA_MAIL_STREAM_CTL_QUEUE(const char *)" 85 /* The argument specifies an alternate destination queue. The 86 /* queue file is moved to the specified queue before the call 87 /* returns. Failure to rename the queue file results in a fatal 88 /* error. 89 /* .IP "CA_MAIL_STREAM_CTL_CLASS(const char *)" 90 /* The argument specifies an alternate trigger class. 91 /* .IP "CA_MAIL_STREAM_CTL_SERVICE(const char *)" 92 /* The argument specifies an alternate trigger service. 93 /* .IP "CA_MAIL_STREAM_CTL_MODE(int)" 94 /* The argument specifies alternate permissions that override 95 /* the permissions specified with mail_stream_file(). 96 /* .IP "CA_MAIL_STREAM_CTL_DELAY(int)" 97 /* Attempt to postpone initial delivery by advancing the queue 98 /* file modification time stamp by this amount. This has 99 /* effect only within the deferred mail queue. 100 /* This feature may have no effect with remote file systems. 101 /* LICENSE 102 /* .ad 103 /* .fi 104 /* The Secure Mailer license must be distributed with this software. 105 /* AUTHOR(S) 106 /* Wietse Venema 107 /* IBM T.J. Watson Research 108 /* P.O. Box 704 109 /* Yorktown Heights, NY 10598, USA 110 /* 111 /* Wietse Venema 112 /* Google, Inc. 113 /* 111 8th Avenue 114 /* New York, NY 10011, USA 115 /*--*/ 116 117 /* System library. */ 118 119 #include <sys_defs.h> 120 #include <sys/stat.h> 121 #include <unistd.h> 122 #include <errno.h> 123 #include <utime.h> 124 #include <string.h> 125 #include <stdarg.h> 126 127 /* Utility library. */ 128 129 #include <msg.h> 130 #include <mymalloc.h> 131 #include <vstring.h> 132 #include <vstream.h> 133 #include <stringops.h> 134 #include <argv.h> 135 #include <sane_fsops.h> 136 #include <warn_stat.h> 137 138 /* Global library. */ 139 140 #include <cleanup_user.h> 141 #include <mail_proto.h> 142 #include <mail_queue.h> 143 #include <opened.h> 144 #include <mail_params.h> 145 #include <mail_stream.h> 146 #include <mail_parm_split.h> 147 148 /* Application-specific. */ 149 150 static VSTRING *id_buf; 151 152 #define FREE_AND_WIPE(free, arg) do { if (arg) free(arg); arg = 0; } while (0) 153 154 #define STR(x) vstring_str(x) 155 156 /* mail_stream_cleanup - clean up after success or failure */ 157 158 void mail_stream_cleanup(MAIL_STREAM *info) 159 { 160 if (info->stream && info->close(info->stream)) 161 msg_warn("mail_stream_cleanup: close error"); 162 FREE_AND_WIPE(myfree, info->queue); 163 FREE_AND_WIPE(myfree, info->id); 164 FREE_AND_WIPE(myfree, info->class); 165 FREE_AND_WIPE(myfree, info->service); 166 myfree((void *) info); 167 } 168 169 #if defined(HAS_FUTIMES_AT) 170 #define CAN_STAMP_BY_STREAM 171 172 /* stamp_stream - update open file [am]time stamp */ 173 174 static int stamp_stream(VSTREAM *fp, time_t when) 175 { 176 struct timeval tv[2]; 177 178 if (when != 0) { 179 tv[0].tv_sec = tv[1].tv_sec = when; 180 tv[0].tv_usec = tv[1].tv_usec = 0; 181 return (futimesat(vstream_fileno(fp), (char *) 0, tv)); 182 } else { 183 return (futimesat(vstream_fileno(fp), (char *) 0, (struct timeval *) 0)); 184 } 185 } 186 187 #elif defined(HAS_FUTIMES) 188 #define CAN_STAMP_BY_STREAM 189 190 /* stamp_stream - update open file [am]time stamp */ 191 192 static int stamp_stream(VSTREAM *fp, time_t when) 193 { 194 struct timeval tv[2]; 195 196 if (when != 0) { 197 tv[0].tv_sec = tv[1].tv_sec = when; 198 tv[0].tv_usec = tv[1].tv_usec = 0; 199 return (futimes(vstream_fileno(fp), tv)); 200 } else { 201 return (futimes(vstream_fileno(fp), (struct timeval *) 0)); 202 } 203 } 204 205 #endif 206 207 /* stamp_path - update file [am]time stamp by pathname */ 208 209 static int stamp_path(const char *path, time_t when) 210 { 211 struct utimbuf tbuf; 212 213 if (when != 0) { 214 tbuf.actime = tbuf.modtime = when; 215 return (utime(path, &tbuf)); 216 } else { 217 return (utime(path, (struct utimbuf *) 0)); 218 } 219 } 220 221 /* mail_stream_finish_file - finish file mail stream */ 222 223 static int mail_stream_finish_file(MAIL_STREAM *info, VSTRING *unused_why) 224 { 225 int status = CLEANUP_STAT_OK; 226 static char wakeup[] = {TRIGGER_REQ_WAKEUP}; 227 struct stat st; 228 char *path_to_reset = 0; 229 static int incoming_fs_clock_ok = 0; 230 static int incoming_clock_warned = 0; 231 int check_incoming_fs_clock; 232 int err; 233 time_t want_stamp; 234 time_t expect_stamp; 235 236 /* 237 * Make sure the message makes it to file. Set the execute bit when no 238 * write error was detected. Some people believe that this code has a 239 * problem if the system crashes before fsync() returns; fchmod() could 240 * take effect before all the data blocks are written. Wietse claims that 241 * this is not a problem. Postfix rejects incomplete queue files, even 242 * when the +x attribute is set. Every Postfix queue file record has a 243 * type code and a length field. Files with missing records are rejected, 244 * as are files with unknown record type codes. Every Postfix queue file 245 * must end with an explicit END record. Postfix queue files without END 246 * record are discarded. 247 * 248 * Attempt to detect file system clocks that are ahead of local time, but 249 * don't check the file system clock all the time. The effect of file 250 * system clock drift can be difficult to understand (Postfix ignores new 251 * mail until the local clock catches up with the file mtime stamp). 252 * 253 * This clock drift detection code may not work with file systems that work 254 * on a local copy of the file and that update the server only after the 255 * file is closed. 256 * 257 * Optionally set a cooldown time. 258 * 259 * XXX: We assume that utime() does control the file modification time even 260 * when followed by an fchmod(), fsync(), close() sequence. This may fail 261 * with remote file systems when fsync() actually updates the file. Even 262 * then, we still delay the average message by 1/2 of the 263 * queue_run_delay. 264 * 265 * XXX: Victor does not like running utime() after the close(), since this 266 * creates a race even with local filesystems. But Wietse is not 267 * confident that utime() before fsync() and close() will work reliably 268 * with remote file systems. 269 * 270 * XXX Don't run the clock skew tests with Postfix sendmail submissions. 271 * Don't whine against unsuspecting users or applications. 272 */ 273 check_incoming_fs_clock = 274 (!incoming_fs_clock_ok && !strcmp(info->queue, MAIL_QUEUE_INCOMING)); 275 276 #ifdef DELAY_ACTION 277 if (strcmp(info->queue, MAIL_QUEUE_DEFERRED) != 0) 278 info->delay = 0; 279 if (info->delay > 0) 280 want_stamp = time((time_t *) 0) + info->delay; 281 else 282 #endif 283 want_stamp = 0; 284 285 /* 286 * If we can cheaply set the file time stamp (no pathname lookup) do it 287 * anyway, so that we can avoid whining later about file server/client 288 * clock skew. 289 * 290 * Otherwise, if we must set the file time stamp for delayed delivery, use 291 * whatever means we have to get the job done, no matter if it is 292 * expensive. 293 * 294 * XXX Unfortunately, Linux futimes() is not usable because it uses /proc. 295 * This may not be available because of chroot, or because of access 296 * restrictions after a process changes privileges. 297 */ 298 if (vstream_fflush(info->stream) 299 #ifdef CAN_STAMP_BY_STREAM 300 || stamp_stream(info->stream, want_stamp) 301 #else 302 || (want_stamp && stamp_path(VSTREAM_PATH(info->stream), want_stamp)) 303 #endif 304 || fchmod(vstream_fileno(info->stream), 0700 | info->mode) 305 #ifdef HAS_FSYNC 306 || fsync(vstream_fileno(info->stream)) 307 #endif 308 || (check_incoming_fs_clock 309 && fstat(vstream_fileno(info->stream), &st) < 0) 310 ) 311 status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE); 312 #ifdef TEST 313 st.st_mtime += 10; 314 #endif 315 316 /* 317 * Work around file system clock skew. If the file system clock is ahead 318 * of the local clock, Postfix won't deliver mail immediately, which is 319 * bad for performance. If the file system clock falls behind the local 320 * clock, it just looks silly in mail headers. 321 */ 322 if (status == CLEANUP_STAT_OK && check_incoming_fs_clock) { 323 /* Do NOT use time() result from before fsync(). */ 324 expect_stamp = want_stamp ? want_stamp : time((time_t *) 0); 325 if (st.st_mtime > expect_stamp) { 326 path_to_reset = mystrdup(VSTREAM_PATH(info->stream)); 327 if (incoming_clock_warned == 0) { 328 msg_warn("file system clock is %d seconds ahead of local clock", 329 (int) (st.st_mtime - expect_stamp)); 330 msg_warn("resetting file time stamps - this hurts performance"); 331 incoming_clock_warned = 1; 332 } 333 } else { 334 if (st.st_mtime < expect_stamp - 100) 335 msg_warn("file system clock is %d seconds behind local clock", 336 (int) (expect_stamp - st.st_mtime)); 337 incoming_fs_clock_ok = 1; 338 } 339 } 340 341 /* 342 * Close the queue file and mark it as closed. Be prepared for 343 * vstream_fclose() to fail even after vstream_fflush() and fsync() 344 * reported no error. Reason: after a file is closed, some networked file 345 * systems copy the file out to another machine. Running the queue on a 346 * remote file system is not recommended, if only for performance 347 * reasons. 348 */ 349 err = info->close(info->stream); 350 info->stream = 0; 351 if (status == CLEANUP_STAT_OK && err != 0) 352 status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE); 353 354 /* 355 * Work around file system clocks that are ahead of local time. 356 */ 357 if (path_to_reset != 0) { 358 if (status == CLEANUP_STAT_OK) { 359 if (stamp_path(path_to_reset, expect_stamp) < 0 && errno != ENOENT) 360 msg_fatal("%s: update file time stamps: %m", info->id); 361 } 362 myfree(path_to_reset); 363 } 364 365 /* 366 * When all is well, notify the next service that a new message has been 367 * queued. 368 */ 369 if (status == CLEANUP_STAT_OK && info->class && info->service) 370 mail_trigger(info->class, info->service, wakeup, sizeof(wakeup)); 371 372 /* 373 * Cleanup. 374 */ 375 mail_stream_cleanup(info); 376 return (status); 377 } 378 379 /* mail_stream_finish_ipc - finish IPC mail stream */ 380 381 static int mail_stream_finish_ipc(MAIL_STREAM *info, VSTRING *why) 382 { 383 int status = CLEANUP_STAT_WRITE; 384 385 /* 386 * Receive the peer's completion status. 387 */ 388 if ((why && attr_scan(info->stream, ATTR_FLAG_STRICT, 389 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), 390 RECV_ATTR_STR(MAIL_ATTR_WHY, why), 391 ATTR_TYPE_END) != 2) 392 || (!why && attr_scan(info->stream, ATTR_FLAG_MISSING, 393 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), 394 ATTR_TYPE_END) != 1)) 395 status = CLEANUP_STAT_WRITE; 396 397 /* 398 * Cleanup. 399 */ 400 mail_stream_cleanup(info); 401 return (status); 402 } 403 404 /* mail_stream_finish - finish action */ 405 406 int mail_stream_finish(MAIL_STREAM *info, VSTRING *why) 407 { 408 return (info->finish(info, why)); 409 } 410 411 /* mail_stream_file - destination is file */ 412 413 MAIL_STREAM *mail_stream_file(const char *queue, const char *class, 414 const char *service, int mode) 415 { 416 struct timeval tv; 417 MAIL_STREAM *info; 418 VSTREAM *stream; 419 420 stream = mail_queue_enter(queue, 0600 | mode, &tv); 421 if (msg_verbose) 422 msg_info("open %s", VSTREAM_PATH(stream)); 423 424 info = (MAIL_STREAM *) mymalloc(sizeof(*info)); 425 info->stream = stream; 426 info->finish = mail_stream_finish_file; 427 info->close = vstream_fclose; 428 info->queue = mystrdup(queue); 429 info->id = mystrdup(basename(VSTREAM_PATH(stream))); 430 info->class = mystrdup(class); 431 info->service = mystrdup(service); 432 info->mode = mode; 433 #ifdef DELAY_ACTION 434 info->delay = 0; 435 #endif 436 info->ctime = tv; 437 return (info); 438 } 439 440 /* mail_stream_service - destination is service */ 441 442 MAIL_STREAM *mail_stream_service(const char *class, const char *name) 443 { 444 VSTREAM *stream; 445 MAIL_STREAM *info; 446 447 if (id_buf == 0) 448 id_buf = vstring_alloc(10); 449 450 stream = mail_connect_wait(class, name); 451 if (attr_scan(stream, ATTR_FLAG_STRICT, 452 RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP), 453 RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id_buf), 0) != 1) { 454 vstream_fclose(stream); 455 return (0); 456 } else { 457 info = (MAIL_STREAM *) mymalloc(sizeof(*info)); 458 info->stream = stream; 459 info->finish = mail_stream_finish_ipc; 460 info->close = vstream_fclose; 461 info->queue = 0; 462 info->id = mystrdup(vstring_str(id_buf)); 463 info->class = 0; 464 info->service = 0; 465 return (info); 466 } 467 } 468 469 /* mail_stream_command - destination is command */ 470 471 MAIL_STREAM *mail_stream_command(const char *command) 472 { 473 VSTREAM *stream; 474 MAIL_STREAM *info; 475 ARGV *export_env; 476 int status; 477 478 if (id_buf == 0) 479 id_buf = vstring_alloc(10); 480 481 /* 482 * Treat fork() failure as a transient problem. Treat bad handshake as a 483 * permanent error. 484 * 485 * XXX Are we invoking a Postfix process or a non-Postfix process? In the 486 * former case we can share the full environment; in the latter case only 487 * a restricted environment should be propagated. Even though we are 488 * talking a Postfix-internal protocol there is no way we can tell what 489 * is being executed except by duplicating a lot of existing code. 490 */ 491 export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); 492 while ((stream = vstream_popen(O_RDWR, 493 CA_VSTREAM_POPEN_COMMAND(command), 494 CA_VSTREAM_POPEN_EXPORT(export_env->argv), 495 CA_VSTREAM_POPEN_END)) == 0) { 496 msg_warn("fork: %m"); 497 sleep(10); 498 } 499 argv_free(export_env); 500 vstream_control(stream, 501 CA_VSTREAM_CTL_PATH(command), 502 CA_VSTREAM_CTL_END); 503 504 if (attr_scan(stream, ATTR_FLAG_STRICT, 505 RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_POSTDROP), 506 RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id_buf), 0) != 1) { 507 if ((status = vstream_pclose(stream)) != 0) 508 msg_warn("command \"%s\" exited with status %d", command, status); 509 return (0); 510 } else { 511 info = (MAIL_STREAM *) mymalloc(sizeof(*info)); 512 info->stream = stream; 513 info->finish = mail_stream_finish_ipc; 514 info->close = vstream_pclose; 515 info->queue = 0; 516 info->id = mystrdup(vstring_str(id_buf)); 517 info->class = 0; 518 info->service = 0; 519 return (info); 520 } 521 } 522 523 /* mail_stream_ctl - update file-based mail stream properties */ 524 525 void mail_stream_ctl(MAIL_STREAM *info, int op,...) 526 { 527 const char *myname = "mail_stream_ctl"; 528 va_list ap; 529 char *new_queue = 0; 530 char *string_value; 531 532 /* 533 * Sanity check. None of the attributes below are applicable unless the 534 * target is a file-based stream. 535 */ 536 if (info->finish != mail_stream_finish_file) 537 msg_panic("%s: attempt to update non-file stream %s", 538 myname, info->id); 539 540 for (va_start(ap, op); op != MAIL_STREAM_CTL_END; op = va_arg(ap, int)) { 541 542 switch (op) { 543 544 /* 545 * Change the queue directory. We do this at the end of this 546 * call. 547 */ 548 case MAIL_STREAM_CTL_QUEUE: 549 if ((new_queue = va_arg(ap, char *)) == 0) 550 msg_panic("%s: NULL queue", 551 myname); 552 break; 553 554 /* 555 * Change the service that needs to be notified. 556 */ 557 case MAIL_STREAM_CTL_CLASS: 558 FREE_AND_WIPE(myfree, info->class); 559 if ((string_value = va_arg(ap, char *)) != 0) 560 info->class = mystrdup(string_value); 561 break; 562 563 case MAIL_STREAM_CTL_SERVICE: 564 FREE_AND_WIPE(myfree, info->service); 565 if ((string_value = va_arg(ap, char *)) != 0) 566 info->service = mystrdup(string_value); 567 break; 568 569 /* 570 * Change the (finished) file access mode. 571 */ 572 case MAIL_STREAM_CTL_MODE: 573 info->mode = va_arg(ap, int); 574 break; 575 576 /* 577 * Advance the (finished) file modification time. 578 */ 579 #ifdef DELAY_ACTION 580 case MAIL_STREAM_CTL_DELAY: 581 if ((info->delay = va_arg(ap, int)) < 0) 582 msg_panic("%s: bad delay time %d", myname, info->delay); 583 break; 584 #endif 585 586 default: 587 msg_panic("%s: bad op code %d", myname, op); 588 } 589 } 590 va_end(ap); 591 592 /* 593 * Rename the queue file after allocating memory for new information, so 594 * that the caller can still remove an embryonic file when memory 595 * allocation fails (there is no risk of deleting the wrong file). 596 * 597 * Wietse opposed the idea to update run-time error handler information 598 * here, because this module wasn't designed to defend against internal 599 * concurrency issues with error handlers that attempt to follow dangling 600 * pointers. 601 * 602 * This code duplicates mail_queue_rename(), except that we need the new 603 * path to update the stream pathname. 604 */ 605 if (new_queue != 0 && strcmp(info->queue, new_queue) != 0) { 606 char *saved_queue = info->queue; 607 char *saved_path = mystrdup(VSTREAM_PATH(info->stream)); 608 VSTRING *new_path = vstring_alloc(100); 609 610 (void) mail_queue_path(new_path, new_queue, info->id); 611 info->queue = mystrdup(new_queue); 612 vstream_control(info->stream, CA_VSTREAM_CTL_PATH(STR(new_path)), 613 CA_VSTREAM_CTL_END); 614 615 if (sane_rename(saved_path, STR(new_path)) == 0 616 || (mail_queue_mkdirs(STR(new_path)) == 0 617 && sane_rename(saved_path, STR(new_path)) == 0)) { 618 if (msg_verbose) 619 msg_info("%s: placed in %s queue", info->id, info->queue); 620 } else { 621 msg_fatal("%s: move to %s queue failed: %m", info->id, 622 info->queue); 623 } 624 625 myfree(saved_path); 626 myfree(saved_queue); 627 vstring_free(new_path); 628 } 629 } 630