1 /* $NetBSD: pipe_command.c,v 1.1.1.3 2011/03/02 19:32:17 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* pipe_command 3 6 /* SUMMARY 7 /* deliver message to external command 8 /* SYNOPSIS 9 /* #include <pipe_command.h> 10 /* 11 /* int pipe_command(src, why, key, value, ...) 12 /* VSTREAM *src; 13 /* DSN_BUF *why; 14 /* int key; 15 /* DESCRIPTION 16 /* pipe_command() runs a command with a message as standard 17 /* input. A limited amount of standard output and standard error 18 /* output is captured for diagnostics purposes. 19 /* 20 /* If the command invokes exit() with a non-zero status, 21 /* the delivery status is taken from an RFC 3463-style code 22 /* at the beginning of command output. If that information is 23 /* unavailable, the delivery status is taken from the command 24 /* exit status as per <sysexits.h>. 25 /* 26 /* Arguments: 27 /* .IP src 28 /* An open message queue file, positioned at the start of the actual 29 /* message content. 30 /* .IP why 31 /* Delivery status information. 32 /* .IP key 33 /* Specifies what value will follow. pipe_command() takes a list 34 /* of (key, value) arguments, terminated by PIPE_CMD_END. The 35 /* following is a listing of key codes together with the expected 36 /* value type. 37 /* .RS 38 /* .IP "PIPE_CMD_COMMAND (char *)" 39 /* Specifies the command to execute as a string. The string is 40 /* passed to the shell when it contains shell meta characters 41 /* or when it appears to be a shell built-in command, otherwise 42 /* the command is executed without invoking a shell. 43 /* One of PIPE_CMD_COMMAND or PIPE_CMD_ARGV must be specified. 44 /* See also the PIPE_CMD_SHELL attribute below. 45 /* .IP "PIPE_CMD_ARGV (char **)" 46 /* The command is specified as an argument vector. This vector is 47 /* passed without further inspection to the \fIexecvp\fR() routine. 48 /* One of PIPE_CMD_COMMAND or PIPE_CMD_ARGV must be specified. 49 /* .IP "PIPE_CMD_CHROOT (char *)" 50 /* Root and working directory for command execution. This takes 51 /* effect before PIPE_CMD_CWD. A null pointer means don't 52 /* change root and working directory anyway. Failure to change 53 /* directory causes mail delivery to be deferred. 54 /* .IP "PIPE_CMD_CWD (char *)" 55 /* Working directory for command execution, after changing process 56 /* privileges to PIPE_CMD_UID and PIPE_CMD_GID. A null pointer means 57 /* don't change directory anyway. Failure to change directory 58 /* causes mail delivery to be deferred. 59 /* .IP "PIPE_CMD_ENV (char **)" 60 /* Additional environment information, in the form of a null-terminated 61 /* list of name, value, name, value, ... elements. By default only the 62 /* command search path is initialized to _PATH_DEFPATH. 63 /* .IP "PIPE_CMD_EXPORT (char **)" 64 /* Null-terminated array with names of environment parameters 65 /* that can be exported. By default, everything is exported. 66 /* .IP "PIPE_CMD_COPY_FLAGS (int)" 67 /* Flags that are passed on to the \fImail_copy\fR() routine. 68 /* The default flags value is 0 (zero). 69 /* .IP "PIPE_CMD_SENDER (char *)" 70 /* The envelope sender address, which is passed on to the 71 /* \fImail_copy\fR() routine. 72 /* .IP "PIPE_CMD_ORIG_RCPT (char *)" 73 /* The original recipient envelope address, which is passed on 74 /* to the \fImail_copy\fR() routine. 75 /* .IP "PIPE_CMD_DELIVERED (char *)" 76 /* The recipient envelope address, which is passed on to the 77 /* \fImail_copy\fR() routine. 78 /* .IP "PIPE_CMD_EOL (char *)" 79 /* End-of-line delimiter. The default is to use the newline character. 80 /* .IP "PIPE_CMD_UID (uid_t)" 81 /* The user ID to execute the command as. The default is 82 /* the user ID corresponding to the \fIdefault_privs\fR 83 /* configuration parameter. The user ID must be non-zero. 84 /* .IP "PIPE_CMD_GID (gid_t)" 85 /* The group ID to execute the command as. The default is 86 /* the group ID corresponding to the \fIdefault_privs\fR 87 /* configuration parameter. The group ID must be non-zero. 88 /* .IP "PIPE_CMD_TIME_LIMIT (int)" 89 /* The amount of time the command is allowed to run before it 90 /* is terminated with SIGKILL. A non-negative PIPE_CMD_TIME_LIMIT 91 /* value must be specified. 92 /* .IP "PIPE_CMD_SHELL (char *)" 93 /* The shell to use when executing the command specified with 94 /* PIPE_CMD_COMMAND. This shell is invoked regardless of the 95 /* command content. 96 /* .RE 97 /* DIAGNOSTICS 98 /* Panic: interface violations (for example, a zero-valued 99 /* user ID or group ID, or a missing command). 100 /* 101 /* pipe_command() returns one of the following status codes: 102 /* .IP PIPE_STAT_OK 103 /* The command has taken responsibility for further delivery of 104 /* the message. 105 /* .IP PIPE_STAT_DEFER 106 /* The command failed with a "try again" type error. 107 /* The reason is given via the \fIwhy\fR argument. 108 /* .IP PIPE_STAT_BOUNCE 109 /* The command indicated that the message was not acceptable, 110 /* or the command did not finish within the time limit. 111 /* The reason is given via the \fIwhy\fR argument. 112 /* .IP PIPE_STAT_CORRUPT 113 /* The queue file is corrupted. 114 /* SEE ALSO 115 /* mail_copy(3) deliver to any. 116 /* mark_corrupt(3) mark queue file as corrupt. 117 /* sys_exits(3) sendmail-compatible exit status codes. 118 /* LICENSE 119 /* .ad 120 /* .fi 121 /* The Secure Mailer license must be distributed with this software. 122 /* AUTHOR(S) 123 /* Wietse Venema 124 /* IBM T.J. Watson Research 125 /* P.O. Box 704 126 /* Yorktown Heights, NY 10598, USA 127 /*--*/ 128 129 /* System library. */ 130 131 #include <sys_defs.h> 132 #include <sys/wait.h> 133 #include <signal.h> 134 #include <unistd.h> 135 #include <errno.h> 136 #include <stdarg.h> 137 #include <fcntl.h> 138 #include <stdlib.h> 139 #ifdef USE_PATHS_H 140 #include <paths.h> 141 #endif 142 #include <syslog.h> 143 144 /* Utility library. */ 145 146 #include <msg.h> 147 #include <vstream.h> 148 #include <msg_vstream.h> 149 #include <vstring.h> 150 #include <stringops.h> 151 #include <iostuff.h> 152 #include <timed_wait.h> 153 #include <set_ugid.h> 154 #include <set_eugid.h> 155 #include <argv.h> 156 #include <chroot_uid.h> 157 158 /* Global library. */ 159 160 #include <mail_params.h> 161 #include <mail_copy.h> 162 #include <clean_env.h> 163 #include <pipe_command.h> 164 #include <exec_command.h> 165 #include <sys_exits.h> 166 #include <dsn_util.h> 167 #include <dsn_buf.h> 168 169 /* Application-specific. */ 170 171 struct pipe_args { 172 int flags; /* see mail_copy.h */ 173 char *sender; /* envelope sender */ 174 char *orig_rcpt; /* original recipient */ 175 char *delivered; /* envelope recipient */ 176 char *eol; /* carriagecontrol */ 177 char **argv; /* either an array */ 178 char *command; /* or a plain string */ 179 uid_t uid; /* privileges */ 180 gid_t gid; /* privileges */ 181 char **env; /* extra environment */ 182 char **export; /* exportable environment */ 183 char *shell; /* command shell */ 184 char *cwd; /* preferred working directory */ 185 char *chroot; /* root directory */ 186 }; 187 188 static int pipe_command_timeout; /* command has timed out */ 189 static int pipe_command_maxtime; /* available time to complete */ 190 191 /* get_pipe_args - capture the variadic argument list */ 192 193 static void get_pipe_args(struct pipe_args * args, va_list ap) 194 { 195 const char *myname = "get_pipe_args"; 196 int key; 197 198 /* 199 * First, set the default values. 200 */ 201 args->flags = 0; 202 args->sender = 0; 203 args->orig_rcpt = 0; 204 args->delivered = 0; 205 args->eol = "\n"; 206 args->argv = 0; 207 args->command = 0; 208 args->uid = var_default_uid; 209 args->gid = var_default_gid; 210 args->env = 0; 211 args->export = 0; 212 args->shell = 0; 213 args->cwd = 0; 214 args->chroot = 0; 215 216 pipe_command_maxtime = -1; 217 218 /* 219 * Then, override the defaults with user-supplied inputs. 220 */ 221 while ((key = va_arg(ap, int)) != PIPE_CMD_END) { 222 switch (key) { 223 case PIPE_CMD_COPY_FLAGS: 224 args->flags |= va_arg(ap, int); 225 break; 226 case PIPE_CMD_SENDER: 227 args->sender = va_arg(ap, char *); 228 break; 229 case PIPE_CMD_ORIG_RCPT: 230 args->orig_rcpt = va_arg(ap, char *); 231 break; 232 case PIPE_CMD_DELIVERED: 233 args->delivered = va_arg(ap, char *); 234 break; 235 case PIPE_CMD_EOL: 236 args->eol = va_arg(ap, char *); 237 break; 238 case PIPE_CMD_ARGV: 239 if (args->command) 240 msg_panic("%s: got PIPE_CMD_ARGV and PIPE_CMD_COMMAND", myname); 241 args->argv = va_arg(ap, char **); 242 break; 243 case PIPE_CMD_COMMAND: 244 if (args->argv) 245 msg_panic("%s: got PIPE_CMD_ARGV and PIPE_CMD_COMMAND", myname); 246 args->command = va_arg(ap, char *); 247 break; 248 case PIPE_CMD_UID: 249 args->uid = va_arg(ap, uid_t); /* in case uid_t is short */ 250 break; 251 case PIPE_CMD_GID: 252 args->gid = va_arg(ap, gid_t); /* in case gid_t is short */ 253 break; 254 case PIPE_CMD_TIME_LIMIT: 255 pipe_command_maxtime = va_arg(ap, int); 256 break; 257 case PIPE_CMD_ENV: 258 args->env = va_arg(ap, char **); 259 break; 260 case PIPE_CMD_EXPORT: 261 args->export = va_arg(ap, char **); 262 break; 263 case PIPE_CMD_SHELL: 264 args->shell = va_arg(ap, char *); 265 break; 266 case PIPE_CMD_CWD: 267 args->cwd = va_arg(ap, char *); 268 break; 269 case PIPE_CMD_CHROOT: 270 args->chroot = va_arg(ap, char *); 271 break; 272 default: 273 msg_panic("%s: unknown key: %d", myname, key); 274 } 275 } 276 if (args->command == 0 && args->argv == 0) 277 msg_panic("%s: missing PIPE_CMD_ARGV or PIPE_CMD_COMMAND", myname); 278 if (args->uid == 0) 279 msg_panic("%s: privileged uid", myname); 280 if (args->gid == 0) 281 msg_panic("%s: privileged gid", myname); 282 if (pipe_command_maxtime < 0) 283 msg_panic("%s: missing or invalid PIPE_CMD_TIME_LIMIT", myname); 284 } 285 286 /* pipe_command_write - write to command with time limit */ 287 288 static ssize_t pipe_command_write(int fd, void *buf, size_t len, 289 int unused_timeout, 290 void *unused_context) 291 { 292 int maxtime = (pipe_command_timeout == 0) ? pipe_command_maxtime : 0; 293 const char *myname = "pipe_command_write"; 294 295 /* 296 * Don't wait when all available time was already used up. 297 */ 298 if (write_wait(fd, maxtime) < 0) { 299 if (pipe_command_timeout == 0) { 300 msg_warn("%s: write time limit exceeded", myname); 301 pipe_command_timeout = 1; 302 } 303 return (0); 304 } else { 305 return (write(fd, buf, len)); 306 } 307 } 308 309 /* pipe_command_read - read from command with time limit */ 310 311 static ssize_t pipe_command_read(int fd, void *buf, ssize_t len, 312 int unused_timeout, 313 void *unused_context) 314 { 315 int maxtime = (pipe_command_timeout == 0) ? pipe_command_maxtime : 0; 316 const char *myname = "pipe_command_read"; 317 318 /* 319 * Don't wait when all available time was already used up. 320 */ 321 if (read_wait(fd, maxtime) < 0) { 322 if (pipe_command_timeout == 0) { 323 msg_warn("%s: read time limit exceeded", myname); 324 pipe_command_timeout = 1; 325 } 326 return (0); 327 } else { 328 return (read(fd, buf, len)); 329 } 330 } 331 332 /* kill_command - terminate command forcibly */ 333 334 static void kill_command(pid_t pid, int sig, uid_t kill_uid, gid_t kill_gid) 335 { 336 uid_t saved_euid = geteuid(); 337 gid_t saved_egid = getegid(); 338 339 /* 340 * Switch privileges to that of the child process. Terminate the child 341 * and its offspring. 342 */ 343 set_eugid(kill_uid, kill_gid); 344 if (kill(-pid, sig) < 0 && kill(pid, sig) < 0) 345 msg_warn("cannot kill process (group) %lu: %m", 346 (unsigned long) pid); 347 set_eugid(saved_euid, saved_egid); 348 } 349 350 /* pipe_command_wait_or_kill - wait for command with time limit, or kill it */ 351 352 static int pipe_command_wait_or_kill(pid_t pid, WAIT_STATUS_T *statusp, int sig, 353 uid_t kill_uid, gid_t kill_gid) 354 { 355 int maxtime = (pipe_command_timeout == 0) ? pipe_command_maxtime : 1; 356 const char *myname = "pipe_command_wait_or_kill"; 357 int n; 358 359 /* 360 * Don't wait when all available time was already used up. 361 */ 362 if ((n = timed_waitpid(pid, statusp, 0, maxtime)) < 0 && errno == ETIMEDOUT) { 363 if (pipe_command_timeout == 0) { 364 msg_warn("%s: child wait time limit exceeded", myname); 365 pipe_command_timeout = 1; 366 } 367 kill_command(pid, sig, kill_uid, kill_gid); 368 n = waitpid(pid, statusp, 0); 369 } 370 return (n); 371 } 372 373 /* pipe_child_cleanup - child fatal error handler */ 374 375 static void pipe_child_cleanup(void) 376 { 377 378 /* 379 * WARNING: don't place code here. This code may run as mail_owner, as 380 * root, or as the user/group specified with the "user" attribute. The 381 * only safe action is to terminate. 382 * 383 * Future proofing. If you need exit() here then you broke Postfix. 384 */ 385 _exit(EX_TEMPFAIL); 386 } 387 388 /* pipe_command - execute command with extreme prejudice */ 389 390 int pipe_command(VSTREAM *src, DSN_BUF *why,...) 391 { 392 const char *myname = "pipe_command"; 393 va_list ap; 394 VSTREAM *cmd_in_stream; 395 VSTREAM *cmd_out_stream; 396 char log_buf[VSTREAM_BUFSIZE + 1]; 397 int log_len; 398 pid_t pid; 399 int write_status; 400 int write_errno; 401 WAIT_STATUS_T wait_status; 402 int cmd_in_pipe[2]; 403 int cmd_out_pipe[2]; 404 struct pipe_args args; 405 char **cpp; 406 ARGV *argv; 407 DSN_SPLIT dp; 408 const SYS_EXITS_DETAIL *sp; 409 410 /* 411 * Process the variadic argument list. This also does sanity checks on 412 * what data the caller is passing to us. 413 */ 414 va_start(ap, why); 415 get_pipe_args(&args, ap); 416 va_end(ap); 417 418 /* 419 * For convenience... 420 */ 421 if (args.command == 0) 422 args.command = args.argv[0]; 423 424 /* 425 * Set up pipes that connect us to the command input and output streams. 426 * We're using a rather disgusting hack to capture command output: set 427 * the output to non-blocking mode, and don't attempt to read the output 428 * until AFTER the process has terminated. The rationale for this is: 1) 429 * the command output will be used only when delivery fails; 2) the 430 * amount of output is expected to be small; 3) the output can be 431 * truncated without too much loss. I could even argue that truncating 432 * the amount of diagnostic output is a good thing to do, but I won't go 433 * that far. 434 * 435 * Turn on non-blocking writes to the child process so that we can enforce 436 * timeouts after partial writes. 437 * 438 * XXX Too much trouble with different systems returning weird write() 439 * results when a pipe is writable. 440 */ 441 if (pipe(cmd_in_pipe) < 0 || pipe(cmd_out_pipe) < 0) 442 msg_fatal("%s: pipe: %m", myname); 443 non_blocking(cmd_out_pipe[1], NON_BLOCKING); 444 #if 0 445 non_blocking(cmd_in_pipe[1], NON_BLOCKING); 446 #endif 447 448 /* 449 * Spawn off a child process and irrevocably change privilege to the 450 * user. This includes revoking all rights on open files (via the close 451 * on exec flag). If we cannot run the command now, try again some time 452 * later. 453 */ 454 switch (pid = fork()) { 455 456 /* 457 * Error. Instead of trying again right now, back off, give the 458 * system a chance to recover, and try again later. 459 */ 460 case -1: 461 msg_warn("fork: %m"); 462 dsb_unix(why, "4.3.0", sys_exits_detail(EX_OSERR)->text, 463 "Delivery failed: %m"); 464 return (PIPE_STAT_DEFER); 465 466 /* 467 * Child. Run the child in a separate process group so that the 468 * parent can kill not just the child but also its offspring. 469 * 470 * Redirect fatal exits to our own fatal exit handler (never leave the 471 * parent's handler enabled :-) so we can replace random exit status 472 * codes by EX_TEMPFAIL. 473 */ 474 case 0: 475 (void) msg_cleanup(pipe_child_cleanup); 476 477 /* 478 * In order to chroot it is necessary to switch euid back to root. 479 * Right after chroot we call set_ugid() so all privileges will be 480 * dropped again. 481 * 482 * XXX For consistency we use chroot_uid() to change root+current 483 * directory. However, we must not use chroot_uid() to change process 484 * privileges (assuming a version that accepts numeric privileges). 485 * That would create a maintenance problem, because we would have two 486 * different code paths to set the external command's privileges. 487 */ 488 if (args.chroot) { 489 seteuid(0); 490 chroot_uid(args.chroot, (char *) 0); 491 } 492 493 /* 494 * XXX If we put code before the set_ugid() call, then the code that 495 * changes root directory must switch back to the mail_owner UID, 496 * otherwise we'd be running with root privileges. 497 */ 498 set_ugid(args.uid, args.gid); 499 if (setsid() < 0) 500 msg_warn("setsid failed: %m"); 501 502 /* 503 * Pipe plumbing. 504 */ 505 close(cmd_in_pipe[1]); 506 close(cmd_out_pipe[0]); 507 if (DUP2(cmd_in_pipe[0], STDIN_FILENO) < 0 508 || DUP2(cmd_out_pipe[1], STDOUT_FILENO) < 0 509 || DUP2(cmd_out_pipe[1], STDERR_FILENO) < 0) 510 msg_fatal("%s: dup2: %m", myname); 511 close(cmd_in_pipe[0]); 512 close(cmd_out_pipe[1]); 513 514 /* 515 * Working directory plumbing. 516 */ 517 if (args.cwd && chdir(args.cwd) < 0) 518 msg_fatal("cannot change directory to \"%s\" for uid=%lu gid=%lu: %m", 519 args.cwd, (unsigned long) args.uid, 520 (unsigned long) args.gid); 521 522 /* 523 * Environment plumbing. Always reset the command search path. XXX 524 * That should probably be done by clean_env(). 525 */ 526 if (args.export) 527 clean_env(args.export); 528 if (setenv("PATH", _PATH_DEFPATH, 1)) 529 msg_fatal("%s: setenv: %m", myname); 530 if (args.env) 531 for (cpp = args.env; *cpp; cpp += 2) 532 if (setenv(cpp[0], cpp[1], 1)) 533 msg_fatal("setenv: %m"); 534 535 /* 536 * Process plumbing. If possible, avoid running a shell. 537 * 538 * As a safety for buggy libraries, we close the syslog socket. 539 * Otherwise we could leak a file descriptor that was created by a 540 * privileged process. 541 * 542 * XXX To avoid losing fatal error messages we open a VSTREAM and 543 * capture the output in the parent process. 544 */ 545 closelog(); 546 msg_vstream_init(var_procname, VSTREAM_ERR); 547 if (args.argv) { 548 execvp(args.argv[0], args.argv); 549 msg_fatal("%s: execvp %s: %m", myname, args.argv[0]); 550 } else if (args.shell && *args.shell) { 551 argv = argv_split(args.shell, " \t\r\n"); 552 argv_add(argv, args.command, (char *) 0); 553 argv_terminate(argv); 554 execvp(argv->argv[0], argv->argv); 555 msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]); 556 } else { 557 exec_command(args.command); 558 } 559 /* NOTREACHED */ 560 561 /* 562 * Parent. 563 */ 564 default: 565 close(cmd_in_pipe[0]); 566 close(cmd_out_pipe[1]); 567 568 cmd_in_stream = vstream_fdopen(cmd_in_pipe[1], O_WRONLY); 569 cmd_out_stream = vstream_fdopen(cmd_out_pipe[0], O_RDONLY); 570 571 /* 572 * Give the command a limited amount of time to run, by enforcing 573 * timeouts on all I/O from and to it. 574 */ 575 vstream_control(cmd_in_stream, 576 VSTREAM_CTL_WRITE_FN, pipe_command_write, 577 VSTREAM_CTL_END); 578 vstream_control(cmd_out_stream, 579 VSTREAM_CTL_READ_FN, pipe_command_read, 580 VSTREAM_CTL_END); 581 pipe_command_timeout = 0; 582 583 /* 584 * Pipe the message into the command. Examine the error report only 585 * if we can't recognize a more specific error from the command exit 586 * status or from the command output. 587 */ 588 write_status = mail_copy(args.sender, args.orig_rcpt, 589 args.delivered, src, 590 cmd_in_stream, args.flags, 591 args.eol, why); 592 write_errno = errno; 593 594 /* 595 * Capture a limited amount of command output, for inclusion in a 596 * bounce message. Turn tabs and newlines into whitespace, and 597 * replace other non-printable characters by underscore. 598 */ 599 log_len = vstream_fread(cmd_out_stream, log_buf, sizeof(log_buf) - 1); 600 (void) vstream_fclose(cmd_out_stream); 601 log_buf[log_len] = 0; 602 translit(log_buf, "\t\n", " "); 603 printable(log_buf, '_'); 604 605 /* 606 * Just because the child closes its output streams, don't assume 607 * that it will terminate. Instead, be prepared for the situation 608 * that the child does not terminate, even when the parent 609 * experiences no read/write timeout. Make sure that the child 610 * terminates before the parent attempts to retrieve its exit status, 611 * otherwise the parent could become stuck, and the mail system would 612 * eventually run out of delivery agents. Do a thorough job, and kill 613 * not just the child process but also its offspring. 614 */ 615 if (pipe_command_timeout) 616 kill_command(pid, SIGKILL, args.uid, args.gid); 617 if (pipe_command_wait_or_kill(pid, &wait_status, SIGKILL, 618 args.uid, args.gid) < 0) 619 msg_fatal("wait: %m"); 620 if (pipe_command_timeout) { 621 dsb_unix(why, "5.3.0", log_len ? 622 log_buf : sys_exits_detail(EX_SOFTWARE)->text, 623 "Command time limit exceeded: \"%s\"%s%s", 624 args.command, 625 log_len ? ". Command output: " : "", log_buf); 626 return (PIPE_STAT_BOUNCE); 627 } 628 629 /* 630 * Command exits. Give special treatment to sendmail style exit 631 * status codes. 632 */ 633 if (!NORMAL_EXIT_STATUS(wait_status)) { 634 if (WIFSIGNALED(wait_status)) { 635 dsb_unix(why, "4.3.0", log_len ? 636 log_buf : sys_exits_detail(EX_SOFTWARE)->text, 637 "Command died with signal %d: \"%s\"%s%s", 638 WTERMSIG(wait_status), args.command, 639 log_len ? ". Command output: " : "", log_buf); 640 return (PIPE_STAT_DEFER); 641 } 642 /* Use "D.S.N text" command output. XXX What diagnostic code? */ 643 else if (dsn_valid(log_buf) > 0) { 644 dsn_split(&dp, "5.3.0", log_buf); 645 dsb_unix(why, DSN_STATUS(dp.dsn), dp.text, "%s", dp.text); 646 return (DSN_CLASS(dp.dsn) == '4' ? 647 PIPE_STAT_DEFER : PIPE_STAT_BOUNCE); 648 } 649 /* Use <sysexits.h> compatible exit status. */ 650 else if (SYS_EXITS_CODE(WEXITSTATUS(wait_status))) { 651 sp = sys_exits_detail(WEXITSTATUS(wait_status)); 652 dsb_unix(why, sp->dsn, 653 log_len ? log_buf : sp->text, "%s%s%s", sp->text, 654 log_len ? ". Command output: " : "", log_buf); 655 return (sp->dsn[0] == '4' ? 656 PIPE_STAT_DEFER : PIPE_STAT_BOUNCE); 657 } 658 659 /* 660 * No "D.S.N text" or <sysexits.h> compatible status. Fake it. 661 */ 662 else { 663 sp = sys_exits_detail(WEXITSTATUS(wait_status)); 664 dsb_unix(why, sp->dsn, 665 log_len ? log_buf : sp->text, 666 "Command died with status %d: \"%s\"%s%s", 667 WEXITSTATUS(wait_status), args.command, 668 log_len ? ". Command output: " : "", log_buf); 669 return (PIPE_STAT_BOUNCE); 670 } 671 } else if (write_status & 672 MAIL_COPY_STAT_CORRUPT) { 673 return (PIPE_STAT_CORRUPT); 674 } else if (write_status && write_errno != EPIPE) { 675 vstring_prepend(why->reason, "Command failed: ", 676 sizeof("Command failed: ") - 1); 677 vstring_sprintf_append(why->reason, ": \"%s\"", args.command); 678 return (PIPE_STAT_BOUNCE); 679 } else { 680 return (PIPE_STAT_OK); 681 } 682 } 683 } 684