1/*++
2/* NAME
3/*	command 3
4/* SUMMARY
5/*	message delivery to shell command
6/* SYNOPSIS
7/*	#include "local.h"
8/*
9/*	int	deliver_command(state, usr_attr, command)
10/*	LOCAL_STATE state;
11/*	USER_ATTR exp_attr;
12/*	const char *command;
13/* DESCRIPTION
14/*	deliver_command() runs a command with a message as standard
15/*	input.  A limited amount of standard output and standard error
16/*	output is captured for diagnostics purposes.
17/*	Duplicate commands for the same recipient are suppressed.
18/*	A limited amount of information is exported via the environment:
19/*	HOME, SHELL, LOGNAME, USER, EXTENSION, DOMAIN, RECIPIENT (entire
20/*	address) LOCAL (just the local part) and SENDER. The exported
21/*	information is censored with var_cmd_filter.
22/*
23/*	Arguments:
24/* .IP state
25/*	The attributes that specify the message, recipient and more.
26/*	Attributes describing the alias, include or forward expansion.
27/*	A table with the results from expanding aliases or lists.
28/* .IP usr_attr
29/*	Attributes describing user rights and environment.
30/* .IP command
31/*	The shell command to be executed. If possible, the command is
32/*	executed without actually invoking a shell. if the command is
33/*	the mailbox_command, it is subjected to $name expansion.
34/* DIAGNOSTICS
35/*	deliver_command() returns non-zero when delivery should be
36/*	tried again,
37/* SEE ALSO
38/*	mailbox(3) deliver to mailbox
39/* LICENSE
40/* .ad
41/* .fi
42/*	The Secure Mailer license must be distributed with this software.
43/* AUTHOR(S)
44/*	Wietse Venema
45/*	IBM T.J. Watson Research
46/*	P.O. Box 704
47/*	Yorktown Heights, NY 10598, USA
48/*--*/
49
50/* System library. */
51
52#include <sys_defs.h>
53#include <unistd.h>
54#include <stdlib.h>
55#include <stdarg.h>
56#include <string.h>
57
58/* Utility library. */
59
60#include <msg.h>
61#include <htable.h>
62#include <vstring.h>
63#include <vstream.h>
64#include <argv.h>
65#include <mac_parse.h>
66
67/* Global library. */
68
69#include <defer.h>
70#include <bounce.h>
71#include <sent.h>
72#include <been_here.h>
73#include <mail_params.h>
74#include <pipe_command.h>
75#include <mail_copy.h>
76#include <dsn_util.h>
77
78/* Application-specific. */
79
80#include "local.h"
81
82/* deliver_command - deliver to shell command */
83
84int     deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command)
85{
86    const char *myname = "deliver_command";
87    DSN_BUF *why = state.msg_attr.why;
88    int     cmd_status;
89    int     deliver_status;
90    ARGV   *env;
91    int     copy_flags;
92    char  **cpp;
93    char   *cp;
94    ARGV   *export_env;
95    VSTRING *exec_dir;
96    int     expand_status;
97
98    /*
99     * Make verbose logging easier to understand.
100     */
101    state.level++;
102    if (msg_verbose)
103	MSG_LOG_STATE(myname, state);
104
105    /*
106     * DUPLICATE ELIMINATION
107     *
108     * Skip this command if it was already delivered to as this user.
109     */
110    if (been_here(state.dup_filter, "command %s:%ld %s",
111		  state.msg_attr.user, (long) usr_attr.uid, command))
112	return (0);
113
114    /*
115     * Don't deliver a trace-only request.
116     */
117    if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
118	dsb_simple(why, "2.0.0", "delivers to command: %s", command);
119	return (sent(BOUNCE_FLAGS(state.request),
120		     SENT_ATTR(state.msg_attr)));
121    }
122
123    /*
124     * DELIVERY RIGHTS
125     *
126     * Choose a default uid and gid when none have been selected (i.e. values
127     * are still zero).
128     */
129    if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0)
130	msg_panic("privileged default user id");
131    if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0)
132	msg_panic("privileged default group id");
133
134    /*
135     * Deliver.
136     */
137    copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH
138	| MAIL_COPY_ORIG_RCPT;
139    if (local_deliver_hdr_mask & DELIVER_HDR_CMD)
140	copy_flags |= MAIL_COPY_DELIVERED;
141
142    if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
143	msg_fatal("%s: seek queue file %s: %m",
144		  myname, VSTREAM_PATH(state.msg_attr.fp));
145
146    /*
147     * Pass additional environment information. XXX This should be
148     * configurable. However, passing untrusted information via environment
149     * parameters opens up a whole can of worms. Lesson from web servers:
150     * don't let any network data even near a shell. It causes trouble.
151     */
152    env = argv_alloc(1);
153    if (usr_attr.home)
154	argv_add(env, "HOME", usr_attr.home, ARGV_END);
155    argv_add(env,
156	     "LOGNAME", state.msg_attr.user,
157	     "USER", state.msg_attr.user,
158	     "SENDER", state.msg_attr.sender,
159	     "RECIPIENT", state.msg_attr.rcpt.address,
160	     "LOCAL", state.msg_attr.local,
161	     ARGV_END);
162    if (usr_attr.shell)
163	argv_add(env, "SHELL", usr_attr.shell, ARGV_END);
164    if (state.msg_attr.domain)
165	argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END);
166    if (state.msg_attr.extension)
167	argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END);
168    if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0])
169	argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr,
170		 ARGV_END);
171
172#define EXPORT_REQUEST(name, value) \
173	if ((value)[0]) argv_add(env, (name), (value), ARGV_END);
174
175    EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name);
176    EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr);
177    EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo);
178    EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto);
179    EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method);
180    EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender);
181    EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username);
182
183    argv_terminate(env);
184
185    /*
186     * Censor out undesirable characters from exported data.
187     */
188    for (cpp = env->argv; *cpp; cpp += 2)
189	for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;)
190	    *cp++ = '_';
191
192    /*
193     * Evaluate the command execution directory. Defer delivery if expansion
194     * fails.
195     */
196    export_env = argv_split(var_export_environ, ", \t\r\n");
197    exec_dir = vstring_alloc(10);
198    expand_status = local_expand(exec_dir, var_exec_directory,
199				 &state, &usr_attr, var_exec_exp_filter);
200
201    if (expand_status & MAC_PARSE_ERROR) {
202	cmd_status = PIPE_STAT_DEFER;
203	dsb_simple(why, "4.3.5", "mail system configuration error");
204	msg_warn("bad parameter value syntax for %s: %s",
205		 VAR_EXEC_DIRECTORY, var_exec_directory);
206    } else {
207	cmd_status = pipe_command(state.msg_attr.fp, why,
208				  PIPE_CMD_UID, usr_attr.uid,
209				  PIPE_CMD_GID, usr_attr.gid,
210				  PIPE_CMD_COMMAND, command,
211				  PIPE_CMD_COPY_FLAGS, copy_flags,
212				  PIPE_CMD_SENDER, state.msg_attr.sender,
213			  PIPE_CMD_ORIG_RCPT, state.msg_attr.rcpt.orig_addr,
214			       PIPE_CMD_DELIVERED, state.msg_attr.delivered,
215				  PIPE_CMD_TIME_LIMIT, var_command_maxtime,
216				  PIPE_CMD_ENV, env->argv,
217				  PIPE_CMD_EXPORT, export_env->argv,
218				  PIPE_CMD_SHELL, var_local_cmd_shell,
219				  PIPE_CMD_CWD, *STR(exec_dir) ?
220				  STR(exec_dir) : (char *) 0,
221				  PIPE_CMD_END);
222    }
223    vstring_free(exec_dir);
224    argv_free(export_env);
225    argv_free(env);
226
227    /*
228     * Depending on the result, bounce or defer the message.
229     */
230    switch (cmd_status) {
231    case PIPE_STAT_OK:
232	dsb_simple(why, "2.0.0", "delivered to command: %s", command);
233	deliver_status = sent(BOUNCE_FLAGS(state.request),
234			      SENT_ATTR(state.msg_attr));
235	break;
236    case PIPE_STAT_BOUNCE:
237    case PIPE_STAT_DEFER:
238	if (STR(why->status)[0] == '4')
239	    deliver_status =
240		defer_append(BOUNCE_FLAGS(state.request),
241			     BOUNCE_ATTR(state.msg_attr));
242	else
243	    /* Account for possible owner- sender address override. */
244	    deliver_status = bounce_workaround(state);
245	break;
246    case PIPE_STAT_CORRUPT:
247	deliver_status = DEL_STAT_DEFER;
248	break;
249    default:
250	msg_panic("%s: bad status %d", myname, cmd_status);
251	/* NOTREACHED */
252    }
253
254    return (deliver_status);
255}
256