diff options
Diffstat (limited to '')
-rw-r--r-- | src/local/command.c | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/src/local/command.c b/src/local/command.c new file mode 100644 index 0000000..4781daf --- /dev/null +++ b/src/local/command.c @@ -0,0 +1,251 @@ +/*++ +/* NAME +/* command 3 +/* SUMMARY +/* message delivery to shell command +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_command(state, usr_attr, command) +/* LOCAL_STATE state; +/* USER_ATTR exp_attr; +/* const char *command; +/* DESCRIPTION +/* deliver_command() runs a command with a message as standard +/* input. A limited amount of standard output and standard error +/* output is captured for diagnostics purposes. +/* Duplicate commands for the same recipient are suppressed. +/* A limited amount of information is exported via the environment: +/* HOME, SHELL, LOGNAME, USER, EXTENSION, DOMAIN, RECIPIENT (entire +/* address) LOCAL (just the local part) and SENDER. The exported +/* information is censored with var_cmd_filter. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing the alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP command +/* The shell command to be executed. If possible, the command is +/* executed without actually invoking a shell. if the command is +/* the mailbox_command, it is subjected to $name expansion. +/* DIAGNOSTICS +/* deliver_command() returns non-zero when delivery should be +/* tried again, +/* SEE ALSO +/* mailbox(3) deliver to mailbox +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <htable.h> +#include <vstring.h> +#include <vstream.h> +#include <argv.h> +#include <mac_parse.h> + +/* Global library. */ + +#include <defer.h> +#include <bounce.h> +#include <sent.h> +#include <been_here.h> +#include <mail_params.h> +#include <pipe_command.h> +#include <mail_copy.h> +#include <dsn_util.h> +#include <mail_parm_split.h> + +/* Application-specific. */ + +#include "local.h" + +/* deliver_command - deliver to shell command */ + +int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command) +{ + const char *myname = "deliver_command"; + DSN_BUF *why = state.msg_attr.why; + int cmd_status; + int deliver_status; + ARGV *env; + int copy_flags; + char **cpp; + char *cp; + ARGV *export_env; + VSTRING *exec_dir; + int expand_status; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Skip this command if it was already delivered to as this user. + */ + if (been_here(state.dup_filter, "command %s:%ld %s", + state.msg_attr.user, (long) usr_attr.uid, command)) + return (0); + + /* + * Don't deliver a trace-only request. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to command: %s", command); + return (sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr))); + } + + /* + * DELIVERY RIGHTS + * + * Choose a default uid and gid when none have been selected (i.e. values + * are still zero). + */ + if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) + msg_panic("privileged default user id"); + if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) + msg_panic("privileged default group id"); + + /* + * Deliver. + */ + copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH + | MAIL_COPY_ORIG_RCPT; + if (local_deliver_hdr_mask & DELIVER_HDR_CMD) + copy_flags |= MAIL_COPY_DELIVERED; + + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("%s: seek queue file %s: %m", + myname, VSTREAM_PATH(state.msg_attr.fp)); + + /* + * Pass additional environment information. XXX This should be + * configurable. However, passing untrusted information via environment + * parameters opens up a whole can of worms. Lesson from web servers: + * don't let any network data even near a shell. It causes trouble. + */ + env = argv_alloc(1); + if (usr_attr.home) + argv_add(env, "HOME", usr_attr.home, ARGV_END); + argv_add(env, + "LOGNAME", state.msg_attr.user, + "USER", state.msg_attr.user, + "SENDER", state.msg_attr.sender, + "RECIPIENT", state.msg_attr.rcpt.address, + "LOCAL", state.msg_attr.local, + ARGV_END); + if (usr_attr.shell) + argv_add(env, "SHELL", usr_attr.shell, ARGV_END); + if (state.msg_attr.domain) + argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END); + if (state.msg_attr.extension) + argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END); + if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0]) + argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr, + ARGV_END); + +#define EXPORT_REQUEST(name, value) \ + if ((value)[0]) argv_add(env, (name), (value), ARGV_END); + + EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name); + EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr); + EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo); + EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto); + EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method); + EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender); + EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username); + + argv_terminate(env); + + /* + * Censor out undesirable characters from exported data. + */ + for (cpp = env->argv; *cpp; cpp += 2) + for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;) + *cp++ = '_'; + + /* + * Evaluate the command execution directory. Defer delivery if expansion + * fails. + */ + export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); + exec_dir = vstring_alloc(10); + expand_status = local_expand(exec_dir, var_exec_directory, + &state, &usr_attr, var_exec_exp_filter); + + if (expand_status & MAC_PARSE_ERROR) { + cmd_status = PIPE_STAT_DEFER; + dsb_simple(why, "4.3.5", "mail system configuration error"); + msg_warn("bad parameter value syntax for %s: %s", + VAR_EXEC_DIRECTORY, var_exec_directory); + } else { + cmd_status = pipe_command(state.msg_attr.fp, why, + CA_PIPE_CMD_UID(usr_attr.uid), + CA_PIPE_CMD_GID(usr_attr.gid), + CA_PIPE_CMD_COMMAND(command), + CA_PIPE_CMD_COPY_FLAGS(copy_flags), + CA_PIPE_CMD_SENDER(state.msg_attr.sender), + CA_PIPE_CMD_ORIG_RCPT(state.msg_attr.rcpt.orig_addr), + CA_PIPE_CMD_DELIVERED(state.msg_attr.delivered), + CA_PIPE_CMD_TIME_LIMIT(var_command_maxtime), + CA_PIPE_CMD_ENV(env->argv), + CA_PIPE_CMD_EXPORT(export_env->argv), + CA_PIPE_CMD_SHELL(var_local_cmd_shell), + CA_PIPE_CMD_CWD(*STR(exec_dir) ? + STR(exec_dir) : (char *) 0), + CA_PIPE_CMD_END); + } + vstring_free(exec_dir); + argv_free(export_env); + argv_free(env); + + /* + * Depending on the result, bounce or defer the message. + */ + switch (cmd_status) { + case PIPE_STAT_OK: + dsb_simple(why, "2.0.0", "delivered to command: %s", command); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + break; + case PIPE_STAT_BOUNCE: + case PIPE_STAT_DEFER: + /* Account for possible owner- sender address override. */ + deliver_status = bounce_workaround(state); + break; + case PIPE_STAT_CORRUPT: + deliver_status = DEL_STAT_DEFER; + break; + default: + msg_panic("%s: bad status %d", myname, cmd_status); + /* NOTREACHED */ + } + + return (deliver_status); +} |