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