1 /* $NetBSD: pipe_command.c,v 1.1.1.2 2010/11/27 10:35:28 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. The default is the limit given 91 /* with the \fIcommand_time_limit\fR configuration parameter. 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 = var_command_maxtime; 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 } 283 284 /* pipe_command_write - write to command with time limit */ 285 286 static ssize_t pipe_command_write(int fd, void *buf, size_t len, 287 int unused_timeout, 288 void *unused_context) 289 { 290 int maxtime = (pipe_command_timeout == 0) ? pipe_command_maxtime : 0; 291 const char *myname = "pipe_command_write"; 292 293 /* 294 * Don't wait when all available time was already used up. 295 */ 296 if (write_wait(fd, maxtime) < 0) { 297 if (pipe_command_timeout == 0) { 298 msg_warn("%s: write time limit exceeded", myname); 299 pipe_command_timeout = 1; 300 } 301 return (0); 302 } else { 303 return (write(fd, buf, len)); 304 } 305 } 306 307 /* pipe_command_read - read from command with time limit */ 308 309 static ssize_t pipe_command_read(int fd, void *buf, ssize_t len, 310 int unused_timeout, 311 void *unused_context) 312 { 313 int maxtime = (pipe_command_timeout == 0) ? pipe_command_maxtime : 0; 314 const char *myname = "pipe_command_read"; 315 316 /* 317 * Don't wait when all available time was already used up. 318 */ 319 if (read_wait(fd, maxtime) < 0) { 320 if (pipe_command_timeout == 0) { 321 msg_warn("%s: read time limit exceeded", myname); 322 pipe_command_timeout = 1; 323 } 324 return (0); 325 } else { 326 return (read(fd, buf, len)); 327 } 328 } 329 330 /* kill_command - terminate command forcibly */ 331 332 static void kill_command(pid_t pid, int sig, uid_t kill_uid, gid_t kill_gid) 333 { 334 uid_t saved_euid = geteuid(); 335 gid_t saved_egid = getegid(); 336 337 /* 338 * Switch privileges to that of the child process. Terminate the child 339 * and its offspring. 340 */ 341 set_eugid(kill_uid, kill_gid); 342 if (kill(-pid, sig) < 0 && kill(pid, sig) < 0) 343 msg_warn("cannot kill process (group) %lu: %m", 344 (unsigned long) pid); 345 set_eugid(saved_euid, saved_egid); 346 } 347 348 /* pipe_command_wait_or_kill - wait for command with time limit, or kill it */ 349 350 static int pipe_command_wait_or_kill(pid_t pid, WAIT_STATUS_T *statusp, int sig, 351 uid_t kill_uid, gid_t kill_gid) 352 { 353 int maxtime = (pipe_command_timeout == 0) ? pipe_command_maxtime : 1; 354 const char *myname = "pipe_command_wait_or_kill"; 355 int n; 356 357 /* 358 * Don't wait when all available time was already used up. 359 */ 360 if ((n = timed_waitpid(pid, statusp, 0, maxtime)) < 0 && errno == ETIMEDOUT) { 361 if (pipe_command_timeout == 0) { 362 msg_warn("%s: child wait time limit exceeded", myname); 363 pipe_command_timeout = 1; 364 } 365 kill_command(pid, sig, kill_uid, kill_gid); 366 n = waitpid(pid, statusp, 0); 367 } 368 return (n); 369 } 370 371 /* pipe_child_cleanup - child fatal error handler */ 372 373 static void pipe_child_cleanup(void) 374 { 375 376 /* 377 * WARNING: don't place code here. This code may run as mail_owner, as 378 * root, or as the user/group specified with the "user" attribute. The 379 * only safe action is to terminate. 380 * 381 * Future proofing. If you need exit() here then you broke Postfix. 382 */ 383 _exit(EX_TEMPFAIL); 384 } 385 386 /* pipe_command - execute command with extreme prejudice */ 387 388 int pipe_command(VSTREAM *src, DSN_BUF *why,...) 389 { 390 const char *myname = "pipe_command"; 391 va_list ap; 392 VSTREAM *cmd_in_stream; 393 VSTREAM *cmd_out_stream; 394 char log_buf[VSTREAM_BUFSIZE + 1]; 395 int log_len; 396 pid_t pid; 397 int write_status; 398 int write_errno; 399 WAIT_STATUS_T wait_status; 400 int cmd_in_pipe[2]; 401 int cmd_out_pipe[2]; 402 struct pipe_args args; 403 char **cpp; 404 ARGV *argv; 405 DSN_SPLIT dp; 406 const SYS_EXITS_DETAIL *sp; 407 408 /* 409 * Process the variadic argument list. This also does sanity checks on 410 * what data the caller is passing to us. 411 */ 412 va_start(ap, why); 413 get_pipe_args(&args, ap); 414 va_end(ap); 415 416 /* 417 * For convenience... 418 */ 419 if (args.command == 0) 420 args.command = args.argv[0]; 421 422 /* 423 * Set up pipes that connect us to the command input and output streams. 424 * We're using a rather disgusting hack to capture command output: set 425 * the output to non-blocking mode, and don't attempt to read the output 426 * until AFTER the process has terminated. The rationale for this is: 1) 427 * the command output will be used only when delivery fails; 2) the 428 * amount of output is expected to be small; 3) the output can be 429 * truncated without too much loss. I could even argue that truncating 430 * the amount of diagnostic output is a good thing to do, but I won't go 431 * that far. 432 * 433 * Turn on non-blocking writes to the child process so that we can enforce 434 * timeouts after partial writes. 435 * 436 * XXX Too much trouble with different systems returning weird write() 437 * results when a pipe is writable. 438 */ 439 if (pipe(cmd_in_pipe) < 0 || pipe(cmd_out_pipe) < 0) 440 msg_fatal("%s: pipe: %m", myname); 441 non_blocking(cmd_out_pipe[1], NON_BLOCKING); 442 #if 0 443 non_blocking(cmd_in_pipe[1], NON_BLOCKING); 444 #endif 445 446 /* 447 * Spawn off a child process and irrevocably change privilege to the 448 * user. This includes revoking all rights on open files (via the close 449 * on exec flag). If we cannot run the command now, try again some time 450 * later. 451 */ 452 switch (pid = fork()) { 453 454 /* 455 * Error. Instead of trying again right now, back off, give the 456 * system a chance to recover, and try again later. 457 */ 458 case -1: 459 msg_warn("fork: %m"); 460 dsb_unix(why, "4.3.0", sys_exits_detail(EX_OSERR)->text, 461 "Delivery failed: %m"); 462 return (PIPE_STAT_DEFER); 463 464 /* 465 * Child. Run the child in a separate process group so that the 466 * parent can kill not just the child but also its offspring. 467 * 468 * Redirect fatal exits to our own fatal exit handler (never leave the 469 * parent's handler enabled :-) so we can replace random exit status 470 * codes by EX_TEMPFAIL. 471 */ 472 case 0: 473 (void) msg_cleanup(pipe_child_cleanup); 474 475 /* 476 * In order to chroot it is necessary to switch euid back to root. 477 * Right after chroot we call set_ugid() so all privileges will be 478 * dropped again. 479 * 480 * XXX For consistency we use chroot_uid() to change root+current 481 * directory. However, we must not use chroot_uid() to change process 482 * privileges (assuming a version that accepts numeric privileges). 483 * That would create a maintenance problem, because we would have two 484 * different code paths to set the external command's privileges. 485 */ 486 if (args.chroot) { 487 seteuid(0); 488 chroot_uid(args.chroot, (char *) 0); 489 } 490 491 /* 492 * XXX If we put code before the set_ugid() call, then the code that 493 * changes root directory must switch back to the mail_owner UID, 494 * otherwise we'd be running with root privileges. 495 */ 496 set_ugid(args.uid, args.gid); 497 if (setsid() < 0) 498 msg_warn("setsid failed: %m"); 499 500 /* 501 * Pipe plumbing. 502 */ 503 close(cmd_in_pipe[1]); 504 close(cmd_out_pipe[0]); 505 if (DUP2(cmd_in_pipe[0], STDIN_FILENO) < 0 506 || DUP2(cmd_out_pipe[1], STDOUT_FILENO) < 0 507 || DUP2(cmd_out_pipe[1], STDERR_FILENO) < 0) 508 msg_fatal("%s: dup2: %m", myname); 509 close(cmd_in_pipe[0]); 510 close(cmd_out_pipe[1]); 511 512 /* 513 * Working directory plumbing. 514 */ 515 if (args.cwd && chdir(args.cwd) < 0) 516 msg_fatal("cannot change directory to \"%s\" for uid=%lu gid=%lu: %m", 517 args.cwd, (unsigned long) args.uid, 518 (unsigned long) args.gid); 519 520 /* 521 * Environment plumbing. Always reset the command search path. XXX 522 * That should probably be done by clean_env(). 523 */ 524 if (args.export) 525 clean_env(args.export); 526 if (setenv("PATH", _PATH_DEFPATH, 1)) 527 msg_fatal("%s: setenv: %m", myname); 528 if (args.env) 529 for (cpp = args.env; *cpp; cpp += 2) 530 if (setenv(cpp[0], cpp[1], 1)) 531 msg_fatal("setenv: %m"); 532 533 /* 534 * Process plumbing. If possible, avoid running a shell. 535 * 536 * As a safety for buggy libraries, we close the syslog socket. 537 * Otherwise we could leak a file descriptor that was created by a 538 * privileged process. 539 * 540 * XXX To avoid losing fatal error messages we open a VSTREAM and 541 * capture the output in the parent process. 542 */ 543 closelog(); 544 msg_vstream_init(var_procname, VSTREAM_ERR); 545 if (args.argv) { 546 execvp(args.argv[0], args.argv); 547 msg_fatal("%s: execvp %s: %m", myname, args.argv[0]); 548 } else if (args.shell && *args.shell) { 549 argv = argv_split(args.shell, " \t\r\n"); 550 argv_add(argv, args.command, (char *) 0); 551 argv_terminate(argv); 552 execvp(argv->argv[0], argv->argv); 553 msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]); 554 } else { 555 exec_command(args.command); 556 } 557 /* NOTREACHED */ 558 559 /* 560 * Parent. 561 */ 562 default: 563 close(cmd_in_pipe[0]); 564 close(cmd_out_pipe[1]); 565 566 cmd_in_stream = vstream_fdopen(cmd_in_pipe[1], O_WRONLY); 567 cmd_out_stream = vstream_fdopen(cmd_out_pipe[0], O_RDONLY); 568 569 /* 570 * Give the command a limited amount of time to run, by enforcing 571 * timeouts on all I/O from and to it. 572 */ 573 vstream_control(cmd_in_stream, 574 VSTREAM_CTL_WRITE_FN, pipe_command_write, 575 VSTREAM_CTL_END); 576 vstream_control(cmd_out_stream, 577 VSTREAM_CTL_READ_FN, pipe_command_read, 578 VSTREAM_CTL_END); 579 pipe_command_timeout = 0; 580 581 /* 582 * Pipe the message into the command. Examine the error report only 583 * if we can't recognize a more specific error from the command exit 584 * status or from the command output. 585 */ 586 write_status = mail_copy(args.sender, args.orig_rcpt, 587 args.delivered, src, 588 cmd_in_stream, args.flags, 589 args.eol, why); 590 write_errno = errno; 591 592 /* 593 * Capture a limited amount of command output, for inclusion in a 594 * bounce message. Turn tabs and newlines into whitespace, and 595 * replace other non-printable characters by underscore. 596 */ 597 log_len = vstream_fread(cmd_out_stream, log_buf, sizeof(log_buf) - 1); 598 (void) vstream_fclose(cmd_out_stream); 599 log_buf[log_len] = 0; 600 translit(log_buf, "\t\n", " "); 601 printable(log_buf, '_'); 602 603 /* 604 * Just because the child closes its output streams, don't assume 605 * that it will terminate. Instead, be prepared for the situation 606 * that the child does not terminate, even when the parent 607 * experiences no read/write timeout. Make sure that the child 608 * terminates before the parent attempts to retrieve its exit status, 609 * otherwise the parent could become stuck, and the mail system would 610 * eventually run out of delivery agents. Do a thorough job, and kill 611 * not just the child process but also its offspring. 612 */ 613 if (pipe_command_timeout) 614 kill_command(pid, SIGKILL, args.uid, args.gid); 615 if (pipe_command_wait_or_kill(pid, &wait_status, SIGKILL, 616 args.uid, args.gid) < 0) 617 msg_fatal("wait: %m"); 618 if (pipe_command_timeout) { 619 dsb_unix(why, "5.3.0", log_len ? 620 log_buf : sys_exits_detail(EX_SOFTWARE)->text, 621 "Command time limit exceeded: \"%s\"%s%s", 622 args.command, 623 log_len ? ". Command output: " : "", log_buf); 624 return (PIPE_STAT_BOUNCE); 625 } 626 627 /* 628 * Command exits. Give special treatment to sendmail style exit 629 * status codes. 630 */ 631 if (!NORMAL_EXIT_STATUS(wait_status)) { 632 if (WIFSIGNALED(wait_status)) { 633 dsb_unix(why, "4.3.0", log_len ? 634 log_buf : sys_exits_detail(EX_SOFTWARE)->text, 635 "Command died with signal %d: \"%s\"%s%s", 636 WTERMSIG(wait_status), args.command, 637 log_len ? ". Command output: " : "", log_buf); 638 return (PIPE_STAT_DEFER); 639 } 640 /* Use "D.S.N text" command output. XXX What diagnostic code? */ 641 else if (dsn_valid(log_buf) > 0) { 642 dsn_split(&dp, "5.3.0", log_buf); 643 dsb_unix(why, DSN_STATUS(dp.dsn), dp.text, "%s", dp.text); 644 return (DSN_CLASS(dp.dsn) == '4' ? 645 PIPE_STAT_DEFER : PIPE_STAT_BOUNCE); 646 } 647 /* Use <sysexits.h> compatible exit status. */ 648 else if (SYS_EXITS_CODE(WEXITSTATUS(wait_status))) { 649 sp = sys_exits_detail(WEXITSTATUS(wait_status)); 650 dsb_unix(why, sp->dsn, 651 log_len ? log_buf : sp->text, "%s%s%s", sp->text, 652 log_len ? ". Command output: " : "", log_buf); 653 return (sp->dsn[0] == '4' ? 654 PIPE_STAT_DEFER : PIPE_STAT_BOUNCE); 655 } 656 657 /* 658 * No "D.S.N text" or <sysexits.h> compatible status. Fake it. 659 */ 660 else { 661 sp = sys_exits_detail(WEXITSTATUS(wait_status)); 662 dsb_unix(why, sp->dsn, 663 log_len ? log_buf : sp->text, 664 "Command died with status %d: \"%s\"%s%s", 665 WEXITSTATUS(wait_status), args.command, 666 log_len ? ". Command output: " : "", log_buf); 667 return (PIPE_STAT_BOUNCE); 668 } 669 } else if (write_status & 670 MAIL_COPY_STAT_CORRUPT) { 671 return (PIPE_STAT_CORRUPT); 672 } else if (write_status && write_errno != EPIPE) { 673 vstring_prepend(why->reason, "Command failed: ", 674 sizeof("Command failed: ") - 1); 675 vstring_sprintf_append(why->reason, ": \"%s\"", args.command); 676 return (PIPE_STAT_BOUNCE); 677 } else { 678 return (PIPE_STAT_OK); 679 } 680 } 681 } 682