1 /* $NetBSD: spawn.c,v 1.4 2022/10/08 16:12:49 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* spawn 8
6 /* SUMMARY
7 /* Postfix external command spawner
8 /* SYNOPSIS
9 /* \fBspawn\fR [generic Postfix daemon options] command_attributes...
10 /* DESCRIPTION
11 /* The \fBspawn\fR(8) daemon provides the Postfix equivalent
12 /* of \fBinetd\fR.
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.
19 /*
20 /* This daemon expects to be run from the \fBmaster\fR(8) process
21 /* manager.
22 /* COMMAND ATTRIBUTE SYNTAX
23 /* .ad
24 /* .fi
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
34 /* of \fIusername\fR.
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.
40 /* BUGS
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.
46 /* DIAGNOSTICS
47 /* The \fBspawn\fR(8) daemon reports abnormal child exits.
48 /* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
49 /* SECURITY
50 /* .fi
51 /* .ad
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
57 /* .ad
58 /* .fi
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.
62 /*
63 /* The text below provides only a parameter summary. See
64 /* \fBpostconf\fR(5) for more details including examples.
65 /*
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
69 /* .ad
70 /* .fi
71 /* .IP "\fBtransport_time_limit ($command_time_limit)\fR"
72 /* A transport-specific override for the command_time_limit parameter
73 /* value, where \fItransport\fR is the master.cf name of the message
74 /* delivery transport.
75 /* MISCELLANEOUS
76 /* .ad
77 /* .fi
78 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
79 /* The default location of the Postfix main.cf and master.cf
80 /* configuration files.
81 /* .IP "\fBdaemon_timeout (18000s)\fR"
82 /* How much time a Postfix daemon process may take to handle a
83 /* request before it is terminated by a built-in watchdog timer.
84 /* .IP "\fBexport_environment (see 'postconf -d' output)\fR"
85 /* The list of environment variables that a Postfix process will export
86 /* to non-Postfix processes.
87 /* .IP "\fBipc_timeout (3600s)\fR"
88 /* The time limit for sending or receiving information over an internal
89 /* communication channel.
90 /* .IP "\fBmail_owner (postfix)\fR"
91 /* The UNIX system account that owns the Postfix queue and most Postfix
92 /* daemon processes.
93 /* .IP "\fBmax_idle (100s)\fR"
94 /* The maximum amount of time that an idle Postfix daemon process waits
95 /* for an incoming connection before terminating voluntarily.
96 /* .IP "\fBmax_use (100)\fR"
97 /* The maximal number of incoming connections that a Postfix daemon
98 /* process will service before terminating voluntarily.
99 /* .IP "\fBprocess_id (read-only)\fR"
100 /* The process ID of a Postfix command or daemon process.
101 /* .IP "\fBprocess_name (read-only)\fR"
102 /* The process name of a Postfix command or daemon process.
103 /* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
104 /* The location of the Postfix top-level queue directory.
105 /* .IP "\fBsyslog_facility (mail)\fR"
106 /* The syslog facility of Postfix logging.
107 /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
108 /* A prefix that is prepended to the process name in syslog
109 /* records, so that, for example, "smtpd" becomes "prefix/smtpd".
110 /* .PP
111 /* Available in Postfix 3.3 and later:
112 /* .IP "\fBservice_name (read-only)\fR"
113 /* The master.cf service name of a Postfix daemon process.
114 /* SEE ALSO
115 /* postconf(5), configuration parameters
116 /* master(8), process manager
117 /* postlogd(8), Postfix logging
118 /* syslogd(8), system logging
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 /* Wietse Venema
130 /* Google, Inc.
131 /* 111 8th Avenue
132 /* New York, NY 10011, USA
133 /*--*/
134
135 /* System library. */
136
137 #include <sys_defs.h>
138 #include <sys/wait.h>
139 #include <unistd.h>
140 #include <stdlib.h>
141 #include <string.h>
142 #include <pwd.h>
143 #include <grp.h>
144 #include <fcntl.h>
145 #ifdef STRCASECMP_IN_STRINGS_H
146 #include <strings.h>
147 #endif
148
149 /* Utility library. */
150
151 #include <msg.h>
152 #include <argv.h>
153 #include <dict.h>
154 #include <mymalloc.h>
155 #include <spawn_command.h>
156 #include <split_at.h>
157 #include <timed_wait.h>
158 #include <set_eugid.h>
159
160 /* Global library. */
161
162 #include <mail_version.h>
163
164 /* Single server skeleton. */
165
166 #include <mail_params.h>
167 #include <mail_server.h>
168 #include <mail_conf.h>
169 #include <mail_parm_split.h>
170
171 /* Application-specific. */
172
173 /*
174 * Tunable parameters. Values are taken from the config file, after
175 * prepending the service name to _name, and so on.
176 */
177 int var_command_maxtime; /* system-wide */
178
179 /*
180 * For convenience. Instead of passing around lists of parameters, bundle
181 * them up in convenient structures.
182 */
183 typedef struct {
184 char **argv; /* argument vector */
185 uid_t uid; /* command privileges */
186 gid_t gid; /* command privileges */
187 int time_limit; /* per-service time limit */
188 } SPAWN_ATTR;
189
190 /* get_service_attr - get service attributes */
191
get_service_attr(SPAWN_ATTR * attr,char * service,char ** argv)192 static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv)
193 {
194 const char *myname = "get_service_attr";
195 struct passwd *pwd;
196 struct group *grp;
197 char *user; /* user name */
198 char *group; /* group name */
199
200 /*
201 * Initialize.
202 */
203 user = 0;
204 group = 0;
205 attr->argv = 0;
206
207 /*
208 * Figure out the command time limit for this transport.
209 */
210 attr->time_limit =
211 get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0);
212
213 /*
214 * Iterate over the command-line attribute list.
215 */
216 for ( /* void */ ; *argv != 0; argv++) {
217
218 /*
219 * user=username[:groupname]
220 */
221 if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) {
222 user = *argv + sizeof("user=") - 1;
223 if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */
224 if (*group == 0)
225 group = 0;
226 if ((pwd = getpwnam(user)) == 0)
227 msg_fatal("unknown user name: %s", user);
228 attr->uid = pwd->pw_uid;
229 if (group != 0) {
230 if ((grp = getgrnam(group)) == 0)
231 msg_fatal("unknown group name: %s", group);
232 attr->gid = grp->gr_gid;
233 } else {
234 attr->gid = pwd->pw_gid;
235 }
236 }
237
238 /*
239 * argv=command...
240 */
241 else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) {
242 *argv += sizeof("argv=") - 1; /* XXX clobbers argv */
243 attr->argv = argv;
244 break;
245 }
246
247 /*
248 * Bad.
249 */
250 else
251 msg_fatal("unknown attribute name: %s", *argv);
252 }
253
254 /*
255 * Sanity checks. Verify that every member has an acceptable value.
256 */
257 if (user == 0)
258 msg_fatal("missing user= attribute");
259 if (attr->argv == 0)
260 msg_fatal("missing argv= attribute");
261 if (attr->uid == 0)
262 msg_fatal("request to deliver as root");
263 if (attr->uid == var_owner_uid)
264 msg_fatal("request to deliver as mail system owner");
265 if (attr->gid == 0)
266 msg_fatal("request to use privileged group id %ld", (long) attr->gid);
267 if (attr->gid == var_owner_gid)
268 msg_fatal("request to use mail system owner group id %ld", (long) attr->gid);
269 if (attr->uid == (uid_t) (-1))
270 msg_fatal("user must not have user ID -1");
271 if (attr->gid == (gid_t) (-1))
272 msg_fatal("user must not have group ID -1");
273
274 /*
275 * Give the poor tester a clue of what is going on.
276 */
277 if (msg_verbose)
278 msg_info("%s: uid %ld, gid %ld; time %d",
279 myname, (long) attr->uid, (long) attr->gid, attr->time_limit);
280 }
281
282 /* spawn_service - perform service for client */
283
spawn_service(VSTREAM * client_stream,char * service,char ** argv)284 static void spawn_service(VSTREAM *client_stream, char *service, char **argv)
285 {
286 const char *myname = "spawn_service";
287 static SPAWN_ATTR attr;
288 WAIT_STATUS_T status;
289 ARGV *export_env;
290
291 /*
292 * This routine runs whenever a client connects to the UNIX-domain socket
293 * dedicated to running an external command.
294 */
295 if (msg_verbose)
296 msg_info("%s: service=%s, command=%s...", myname, service, argv[0]);
297
298 /*
299 * Look up service attributes and config information only once. This is
300 * safe since the information comes from a trusted source.
301 */
302 if (attr.argv == 0) {
303 get_service_attr(&attr, service, argv);
304 }
305
306 /*
307 * Execute the command.
308 */
309 export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
310 status = spawn_command(CA_SPAWN_CMD_STDIN(vstream_fileno(client_stream)),
311 CA_SPAWN_CMD_STDOUT(vstream_fileno(client_stream)),
312 CA_SPAWN_CMD_STDERR(vstream_fileno(client_stream)),
313 CA_SPAWN_CMD_UID(attr.uid),
314 CA_SPAWN_CMD_GID(attr.gid),
315 CA_SPAWN_CMD_ARGV(attr.argv),
316 CA_SPAWN_CMD_TIME_LIMIT(attr.time_limit),
317 CA_SPAWN_CMD_EXPORT(export_env->argv),
318 CA_SPAWN_CMD_END);
319 argv_free(export_env);
320
321 /*
322 * Warn about unsuccessful completion.
323 */
324 if (!NORMAL_EXIT_STATUS(status)) {
325 if (WIFEXITED(status))
326 msg_warn("command %s exit status %d",
327 attr.argv[0], WEXITSTATUS(status));
328 if (WIFSIGNALED(status))
329 msg_warn("command %s killed by signal %d",
330 attr.argv[0], WTERMSIG(status));
331 }
332 }
333
334 /* pre_accept - see if tables have changed */
335
pre_accept(char * unused_name,char ** unused_argv)336 static void pre_accept(char *unused_name, char **unused_argv)
337 {
338 const char *table;
339
340 if ((table = dict_changed_name()) != 0) {
341 msg_info("table %s has changed -- restarting", table);
342 exit(0);
343 }
344 }
345
346 /* drop_privileges - drop privileges most of the time */
347
drop_privileges(char * unused_name,char ** unused_argv)348 static void drop_privileges(char *unused_name, char **unused_argv)
349 {
350 set_eugid(var_owner_uid, var_owner_gid);
351 }
352
353 MAIL_VERSION_STAMP_DECLARE;
354
355 /* main - pass control to the single-threaded skeleton */
356
main(int argc,char ** argv)357 int main(int argc, char **argv)
358 {
359 static const CONFIG_TIME_TABLE time_table[] = {
360 VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0,
361 0,
362 };
363
364 /*
365 * Fingerprint executables and core dumps.
366 */
367 MAIL_VERSION_STAMP_ALLOCATE;
368
369 single_server_main(argc, argv, spawn_service,
370 CA_MAIL_SERVER_TIME_TABLE(time_table),
371 CA_MAIL_SERVER_POST_INIT(drop_privileges),
372 CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
373 CA_MAIL_SERVER_PRIVILEGED,
374 0);
375 }
376