xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/pipe_command.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
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