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
get_pipe_args(struct pipe_args * args,va_list ap)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
pipe_command_write(int fd,void * buf,size_t len,int unused_timeout,void * unused_context)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
pipe_command_read(int fd,void * buf,size_t len,int unused_timeout,void * unused_context)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
kill_command(pid_t pid,int sig,uid_t kill_uid,gid_t kill_gid)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
pipe_command_wait_or_kill(pid_t pid,WAIT_STATUS_T * statusp,int sig,uid_t kill_uid,gid_t kill_gid)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
pipe_child_cleanup(void)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
pipe_command(VSTREAM * src,DSN_BUF * why,...)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