7 /* deliver message to external command
9 /* #include <pipe_command.h>
11 /* int pipe_command(src, why, key, value, ...)
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.
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>.
28 /* An open message queue file, positioned at the start of the actual
31 /* Delivery status information.
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
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
98 /* Panic: interface violations (for example, a zero-valued
99 /* user ID or group ID, or a missing command).
101 /* pipe_command() returns one of the following status codes:
103 /* The command has taken responsibility for further delivery of
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.
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.
121 /* The Secure Mailer license must be distributed with this software.
124 /* IBM T.J. Watson Research
126 /* Yorktown Heights, NY 10598, USA
129 /* System library. */
131 #include <sys_defs.h>
132 #include <sys/wait.h>
144 /* Utility library. */
148 #include <msg_vstream.h>
150 #include <stringops.h>
152 #include <timed_wait.h>
153 #include <set_ugid.h>
154 #include <set_eugid.h>
156 #include <chroot_uid.h>
158 /* Global library. */
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>
169 /* Application-specific. */
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 */
188 static int pipe_command_timeout
; /* command has timed out */
189 static int pipe_command_maxtime
; /* available time to complete */
191 /* get_pipe_args - capture the variadic argument list */
193 static void get_pipe_args(struct pipe_args
* args
, va_list ap
)
195 const char *myname
= "get_pipe_args";
199 * First, set the default values.
208 args
->uid
= var_default_uid
;
209 args
->gid
= var_default_gid
;
216 pipe_command_maxtime
= var_command_maxtime
;
219 * Then, override the defaults with user-supplied inputs.
221 while ((key
= va_arg(ap
, int)) != PIPE_CMD_END
) {
223 case PIPE_CMD_COPY_FLAGS
:
224 args
->flags
|= va_arg(ap
, int);
226 case PIPE_CMD_SENDER
:
227 args
->sender
= va_arg(ap
, char *);
229 case PIPE_CMD_ORIG_RCPT
:
230 args
->orig_rcpt
= va_arg(ap
, char *);
232 case PIPE_CMD_DELIVERED
:
233 args
->delivered
= va_arg(ap
, char *);
236 args
->eol
= va_arg(ap
, char *);
240 msg_panic("%s: got PIPE_CMD_ARGV and PIPE_CMD_COMMAND", myname
);
241 args
->argv
= va_arg(ap
, char **);
243 case PIPE_CMD_COMMAND
:
245 msg_panic("%s: got PIPE_CMD_ARGV and PIPE_CMD_COMMAND", myname
);
246 args
->command
= va_arg(ap
, char *);
249 args
->uid
= va_arg(ap
, uid_t
); /* in case uid_t is short */
252 args
->gid
= va_arg(ap
, gid_t
); /* in case gid_t is short */
254 case PIPE_CMD_TIME_LIMIT
:
255 pipe_command_maxtime
= va_arg(ap
, int);
258 args
->env
= va_arg(ap
, char **);
260 case PIPE_CMD_EXPORT
:
261 args
->export
= va_arg(ap
, char **);
264 args
->shell
= va_arg(ap
, char *);
267 args
->cwd
= va_arg(ap
, char *);
269 case PIPE_CMD_CHROOT
:
270 args
->chroot
= va_arg(ap
, char *);
273 msg_panic("%s: unknown key: %d", myname
, key
);
276 if (args
->command
== 0 && args
->argv
== 0)
277 msg_panic("%s: missing PIPE_CMD_ARGV or PIPE_CMD_COMMAND", myname
);
279 msg_panic("%s: privileged uid", myname
);
281 msg_panic("%s: privileged gid", myname
);
284 /* pipe_command_write - write to command with time limit */
286 static ssize_t
pipe_command_write(int fd
, void *buf
, size_t len
,
288 void *unused_context
)
290 int maxtime
= (pipe_command_timeout
== 0) ? pipe_command_maxtime
: 0;
291 const char *myname
= "pipe_command_write";
294 * Don't wait when all available time was already used up.
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;
303 return (write(fd
, buf
, len
));
307 /* pipe_command_read - read from command with time limit */
309 static ssize_t
pipe_command_read(int fd
, void *buf
, ssize_t len
,
311 void *unused_context
)
313 int maxtime
= (pipe_command_timeout
== 0) ? pipe_command_maxtime
: 0;
314 const char *myname
= "pipe_command_read";
317 * Don't wait when all available time was already used up.
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;
326 return (read(fd
, buf
, len
));
330 /* kill_command - terminate command forcibly */
332 static void kill_command(pid_t pid
, int sig
, uid_t kill_uid
, gid_t kill_gid
)
334 uid_t saved_euid
= geteuid();
335 gid_t saved_egid
= getegid();
338 * Switch privileges to that of the child process. Terminate the child
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
);
348 /* pipe_command_wait_or_kill - wait for command with time limit, or kill it */
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
)
353 int maxtime
= (pipe_command_timeout
== 0) ? pipe_command_maxtime
: 1;
354 const char *myname
= "pipe_command_wait_or_kill";
358 * Don't wait when all available time was already used up.
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;
365 kill_command(pid
, sig
, kill_uid
, kill_gid
);
366 n
= waitpid(pid
, statusp
, 0);
371 /* pipe_child_cleanup - child fatal error handler */
373 static void pipe_child_cleanup(void)
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.
381 * Future proofing. If you need exit() here then you broke Postfix.
386 /* pipe_command - execute command with extreme prejudice */
388 int pipe_command(VSTREAM
*src
, DSN_BUF
*why
,...)
390 const char *myname
= "pipe_command";
392 VSTREAM
*cmd_in_stream
;
393 VSTREAM
*cmd_out_stream
;
394 char log_buf
[VSTREAM_BUFSIZE
+ 1];
399 WAIT_STATUS_T wait_status
;
402 struct pipe_args args
;
406 const SYS_EXITS_DETAIL
*sp
;
409 * Process the variadic argument list. This also does sanity checks on
410 * what data the caller is passing to us.
413 get_pipe_args(&args
, ap
);
419 if (args
.command
== 0)
420 args
.command
= args
.argv
[0];
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
433 * Turn on non-blocking writes to the child process so that we can enforce
434 * timeouts after partial writes.
436 * XXX Too much trouble with different systems returning weird write()
437 * results when a pipe is writable.
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
);
443 non_blocking(cmd_in_pipe
[1], NON_BLOCKING
);
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
452 switch (pid
= fork()) {
455 * Error. Instead of trying again right now, back off, give the
456 * system a chance to recover, and try again later.
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
);
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.
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.
473 (void) msg_cleanup(pipe_child_cleanup
);
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
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.
488 chroot_uid(args
.chroot
, (char *) 0);
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.
496 set_ugid(args
.uid
, args
.gid
);
498 msg_warn("setsid failed: %m");
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]);
513 * Working directory plumbing.
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
);
521 * Environment plumbing. Always reset the command search path. XXX
522 * That should probably be done by clean_env().
525 clean_env(args
.export
);
526 if (setenv("PATH", _PATH_DEFPATH
, 1))
527 msg_fatal("%s: setenv: %m", myname
);
529 for (cpp
= args
.env
; *cpp
; cpp
+= 2)
530 if (setenv(cpp
[0], cpp
[1], 1))
531 msg_fatal("setenv: %m");
534 * Process plumbing. If possible, avoid running a shell.
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.
540 * XXX To avoid losing fatal error messages we open a VSTREAM and
541 * capture the output in the parent process.
544 msg_vstream_init(var_procname
, VSTREAM_ERR
);
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]);
555 exec_command(args
.command
);
563 close(cmd_in_pipe
[0]);
564 close(cmd_out_pipe
[1]);
566 cmd_in_stream
= vstream_fdopen(cmd_in_pipe
[1], O_WRONLY
);
567 cmd_out_stream
= vstream_fdopen(cmd_out_pipe
[0], O_RDONLY
);
570 * Give the command a limited amount of time to run, by enforcing
571 * timeouts on all I/O from and to it.
573 vstream_control(cmd_in_stream
,
574 VSTREAM_CTL_WRITE_FN
, pipe_command_write
,
576 vstream_control(cmd_out_stream
,
577 VSTREAM_CTL_READ_FN
, pipe_command_read
,
579 pipe_command_timeout
= 0;
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.
586 write_status
= mail_copy(args
.sender
, args
.orig_rcpt
,
588 cmd_in_stream
, args
.flags
,
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.
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
, '_');
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.
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",
623 log_len
? ". Command output: " : "", log_buf
);
624 return (PIPE_STAT_BOUNCE
);
628 * Command exits. Give special treatment to sendmail style exit
631 if (!NORMAL_EXIT_STATUS(wait_status
)) {
632 if (WIFSIGNALED(wait_status
)) {
633 dsb_unix(why
, "5.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
);
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
);
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
);
658 * No "D.S.N text" or <sysexits.h> compatible status. Fake it.
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
);
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
);
678 return (PIPE_STAT_OK
);