7 /* Postfix external command spawner
9 /* \fBspawn\fR [generic Postfix daemon options] command_attributes...
11 /* The \fBspawn\fR(8) daemon provides the Postfix equivalent
13 /* It listens on a port as specified in the Postfix \fBmaster.cf\fR file
14 /* and spawns an external command whenever a connection is established.
15 /* The connection can be made over local IPC (such as UNIX-domain
16 /* sockets) or over non-local IPC (such as TCP sockets).
17 /* The command\'s standard input, output and error streams are connected
18 /* directly to the communication endpoint.
20 /* This daemon expects to be run from the \fBmaster\fR(8) process
22 /* COMMAND ATTRIBUTE SYNTAX
25 /* The external command attributes are given in the \fBmaster.cf\fR
26 /* file at the end of a service definition. The syntax is as follows:
27 /* .IP "\fBuser\fR=\fIusername\fR (required)"
28 /* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR"
29 /* The external command is executed with the rights of the
30 /* specified \fIusername\fR. The software refuses to execute
31 /* commands with root privileges, or with the privileges of the
32 /* mail system owner. If \fIgroupname\fR is specified, the
33 /* corresponding group ID is used instead of the group ID
35 /* .IP "\fBargv\fR=\fIcommand\fR... (required)"
36 /* The command to be executed. This must be specified as the
37 /* last command attribute.
38 /* The command is executed directly, i.e. without interpretation of
39 /* shell meta characters by a shell command interpreter.
41 /* In order to enforce standard Postfix process resource controls,
42 /* the \fBspawn\fR(8) daemon runs only one external command at a time.
43 /* As such, it presents a noticeable overhead by wasting precious
44 /* process resources. The \fBspawn\fR(8) daemon is expected to be
45 /* replaced by a more structural solution.
47 /* The \fBspawn\fR(8) daemon reports abnormal child exits.
48 /* Problems are logged to \fBsyslogd\fR(8).
52 /* This program needs root privilege in order to execute external
53 /* commands as the specified user. It is therefore security sensitive.
54 /* However the \fBspawn\fR(8) daemon does not talk to the external command
55 /* and thus is not vulnerable to data-driven attacks.
56 /* CONFIGURATION PARAMETERS
59 /* Changes to \fBmain.cf\fR are picked up automatically as \fBspawn\fR(8)
60 /* processes run for only a limited amount of time. Use the command
61 /* "\fBpostfix reload\fR" to speed up a change.
63 /* The text below provides only a parameter summary. See
64 /* \fBpostconf\fR(5) for more details including examples.
66 /* In the text below, \fItransport\fR is the first field of the entry
67 /* in the \fBmaster.cf\fR file.
68 /* RESOURCE AND RATE CONTROL
71 /* .IP "\fItransport\fB_time_limit ($command_time_limit)\fR"
72 /* The amount of time the command is allowed to run before it is
75 /* Postfix 2.4 and later support a suffix that specifies the
76 /* time unit: s (seconds), m (minutes), h (hours), d (days),
77 /* w (weeks). The default time unit is seconds.
81 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
82 /* The default location of the Postfix main.cf and master.cf
83 /* configuration files.
84 /* .IP "\fBdaemon_timeout (18000s)\fR"
85 /* How much time a Postfix daemon process may take to handle a
86 /* request before it is terminated by a built-in watchdog timer.
87 /* .IP "\fBexport_environment (see 'postconf -d' output)\fR"
88 /* The list of environment variables that a Postfix process will export
89 /* to non-Postfix processes.
90 /* .IP "\fBipc_timeout (3600s)\fR"
91 /* The time limit for sending or receiving information over an internal
92 /* communication channel.
93 /* .IP "\fBmail_owner (postfix)\fR"
94 /* The UNIX system account that owns the Postfix queue and most Postfix
96 /* .IP "\fBmax_idle (100s)\fR"
97 /* The maximum amount of time that an idle Postfix daemon process waits
98 /* for an incoming connection before terminating voluntarily.
99 /* .IP "\fBmax_use (100)\fR"
100 /* The maximal number of incoming connections that a Postfix daemon
101 /* process will service before terminating voluntarily.
102 /* .IP "\fBprocess_id (read-only)\fR"
103 /* The process ID of a Postfix command or daemon process.
104 /* .IP "\fBprocess_name (read-only)\fR"
105 /* The process name of a Postfix command or daemon process.
106 /* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
107 /* The location of the Postfix top-level queue directory.
108 /* .IP "\fBsyslog_facility (mail)\fR"
109 /* The syslog facility of Postfix logging.
110 /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
111 /* The mail system name that is prepended to the process name in syslog
112 /* records, so that "smtpd" becomes, for example, "postfix/smtpd".
114 /* postconf(5), configuration parameters
115 /* master(8), process manager
116 /* syslogd(8), system logging
120 /* The Secure Mailer license must be distributed with this software.
123 /* IBM T.J. Watson Research
125 /* Yorktown Heights, NY 10598, USA
128 /* System library. */
130 #include <sys_defs.h>
131 #include <sys/wait.h>
138 #ifdef STRCASECMP_IN_STRINGS_H
142 /* Utility library. */
147 #include <mymalloc.h>
148 #include <spawn_command.h>
149 #include <split_at.h>
150 #include <timed_wait.h>
151 #include <set_eugid.h>
153 /* Global library. */
155 #include <mail_version.h>
157 /* Single server skeleton. */
159 #include <mail_params.h>
160 #include <mail_server.h>
161 #include <mail_conf.h>
163 /* Application-specific. */
166 * Tunable parameters. Values are taken from the config file, after
167 * prepending the service name to _name, and so on.
169 int var_command_maxtime
; /* system-wide */
172 * For convenience. Instead of passing around lists of parameters, bundle
173 * them up in convenient structures.
176 char **argv
; /* argument vector */
177 uid_t uid
; /* command privileges */
178 gid_t gid
; /* command privileges */
179 int time_limit
; /* per-service time limit */
182 /* get_service_attr - get service attributes */
184 static void get_service_attr(SPAWN_ATTR
*attr
, char *service
, char **argv
)
186 const char *myname
= "get_service_attr";
189 char *user
; /* user name */
190 char *group
; /* group name */
200 * Figure out the command time limit for this transport.
203 get_mail_conf_time2(service
, _MAXTIME
, var_command_maxtime
, 's', 1, 0);
206 * Iterate over the command-line attribute list.
208 for ( /* void */ ; *argv
!= 0; argv
++) {
211 * user=username[:groupname]
213 if (strncasecmp("user=", *argv
, sizeof("user=") - 1) == 0) {
214 user
= *argv
+ sizeof("user=") - 1;
215 if ((group
= split_at(user
, ':')) != 0) /* XXX clobbers argv */
218 if ((pwd
= getpwnam(user
)) == 0)
219 msg_fatal("unknown user name: %s", user
);
220 attr
->uid
= pwd
->pw_uid
;
222 if ((grp
= getgrnam(group
)) == 0)
223 msg_fatal("unknown group name: %s", group
);
224 attr
->gid
= grp
->gr_gid
;
226 attr
->gid
= pwd
->pw_gid
;
233 else if (strncasecmp("argv=", *argv
, sizeof("argv=") - 1) == 0) {
234 *argv
+= sizeof("argv=") - 1; /* XXX clobbers argv */
243 msg_fatal("unknown attribute name: %s", *argv
);
247 * Sanity checks. Verify that every member has an acceptable value.
250 msg_fatal("missing user= attribute");
252 msg_fatal("missing argv= attribute");
254 msg_fatal("request to deliver as root");
255 if (attr
->uid
== var_owner_uid
)
256 msg_fatal("request to deliver as mail system owner");
258 msg_fatal("request to use privileged group id %ld", (long) attr
->gid
);
259 if (attr
->gid
== var_owner_gid
)
260 msg_fatal("request to use mail system owner group id %ld", (long) attr
->gid
);
261 if (attr
->uid
== (uid_t
) (-1))
262 msg_fatal("user must not have user ID -1");
263 if (attr
->gid
== (gid_t
) (-1))
264 msg_fatal("user must not have group ID -1");
267 * Give the poor tester a clue of what is going on.
270 msg_info("%s: uid %ld, gid %ld; time %d",
271 myname
, (long) attr
->uid
, (long) attr
->gid
, attr
->time_limit
);
274 /* spawn_service - perform service for client */
276 static void spawn_service(VSTREAM
*client_stream
, char *service
, char **argv
)
278 const char *myname
= "spawn_service";
279 static SPAWN_ATTR attr
;
280 WAIT_STATUS_T status
;
284 * This routine runs whenever a client connects to the UNIX-domain socket
285 * dedicated to running an external command.
288 msg_info("%s: service=%s, command=%s...", myname
, service
, argv
[0]);
291 * Look up service attributes and config information only once. This is
292 * safe since the information comes from a trusted source.
294 if (attr
.argv
== 0) {
295 get_service_attr(&attr
, service
, argv
);
299 * Execute the command.
301 export_env
= argv_split(var_export_environ
, ", \t\r\n");
302 status
= spawn_command(SPAWN_CMD_STDIN
, vstream_fileno(client_stream
),
303 SPAWN_CMD_STDOUT
, vstream_fileno(client_stream
),
304 SPAWN_CMD_STDERR
, vstream_fileno(client_stream
),
305 SPAWN_CMD_UID
, attr
.uid
,
306 SPAWN_CMD_GID
, attr
.gid
,
307 SPAWN_CMD_ARGV
, attr
.argv
,
308 SPAWN_CMD_TIME_LIMIT
, attr
.time_limit
,
309 SPAWN_CMD_EXPORT
, export_env
->argv
,
311 argv_free(export_env
);
314 * Warn about unsuccessful completion.
316 if (!NORMAL_EXIT_STATUS(status
)) {
317 if (WIFEXITED(status
))
318 msg_warn("command %s exit status %d",
319 attr
.argv
[0], WEXITSTATUS(status
));
320 if (WIFSIGNALED(status
))
321 msg_warn("command %s killed by signal %d",
322 attr
.argv
[0], WTERMSIG(status
));
326 /* pre_accept - see if tables have changed */
328 static void pre_accept(char *unused_name
, char **unused_argv
)
332 if ((table
= dict_changed_name()) != 0) {
333 msg_info("table %s has changed -- restarting", table
);
338 /* drop_privileges - drop privileges most of the time */
340 static void drop_privileges(char *unused_name
, char **unused_argv
)
342 set_eugid(var_owner_uid
, var_owner_gid
);
345 MAIL_VERSION_STAMP_DECLARE
;
347 /* main - pass control to the single-threaded skeleton */
349 int main(int argc
, char **argv
)
351 static const CONFIG_TIME_TABLE time_table
[] = {
352 VAR_COMMAND_MAXTIME
, DEF_COMMAND_MAXTIME
, &var_command_maxtime
, 1, 0,
357 * Fingerprint executables and core dumps.
359 MAIL_VERSION_STAMP_ALLOCATE
;
361 single_server_main(argc
, argv
, spawn_service
,
362 MAIL_SERVER_TIME_TABLE
, time_table
,
363 MAIL_SERVER_POST_INIT
, drop_privileges
,
364 MAIL_SERVER_PRE_ACCEPT
, pre_accept
,
365 MAIL_SERVER_PRIVILEGED
,