1/*++ 2/* NAME 3/* spawn 8 4/* SUMMARY 5/* Postfix external command spawner 6/* SYNOPSIS 7/* \fBspawn\fR [generic Postfix daemon options] command_attributes... 8/* DESCRIPTION 9/* The \fBspawn\fR(8) daemon provides the Postfix equivalent 10/* of \fBinetd\fR. 11/* It listens on a port as specified in the Postfix \fBmaster.cf\fR file 12/* and spawns an external command whenever a connection is established. 13/* The connection can be made over local IPC (such as UNIX-domain 14/* sockets) or over non-local IPC (such as TCP sockets). 15/* The command\'s standard input, output and error streams are connected 16/* directly to the communication endpoint. 17/* 18/* This daemon expects to be run from the \fBmaster\fR(8) process 19/* manager. 20/* COMMAND ATTRIBUTE SYNTAX 21/* .ad 22/* .fi 23/* The external command attributes are given in the \fBmaster.cf\fR 24/* file at the end of a service definition. The syntax is as follows: 25/* .IP "\fBuser\fR=\fIusername\fR (required)" 26/* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR" 27/* The external command is executed with the rights of the 28/* specified \fIusername\fR. The software refuses to execute 29/* commands with root privileges, or with the privileges of the 30/* mail system owner. If \fIgroupname\fR is specified, the 31/* corresponding group ID is used instead of the group ID 32/* of \fIusername\fR. 33/* .IP "\fBargv\fR=\fIcommand\fR... (required)" 34/* The command to be executed. This must be specified as the 35/* last command attribute. 36/* The command is executed directly, i.e. without interpretation of 37/* shell meta characters by a shell command interpreter. 38/* BUGS 39/* In order to enforce standard Postfix process resource controls, 40/* the \fBspawn\fR(8) daemon runs only one external command at a time. 41/* As such, it presents a noticeable overhead by wasting precious 42/* process resources. The \fBspawn\fR(8) daemon is expected to be 43/* replaced by a more structural solution. 44/* DIAGNOSTICS 45/* The \fBspawn\fR(8) daemon reports abnormal child exits. 46/* Problems are logged to \fBsyslogd\fR(8). 47/* SECURITY 48/* .fi 49/* .ad 50/* This program needs root privilege in order to execute external 51/* commands as the specified user. It is therefore security sensitive. 52/* However the \fBspawn\fR(8) daemon does not talk to the external command 53/* and thus is not vulnerable to data-driven attacks. 54/* CONFIGURATION PARAMETERS 55/* .ad 56/* .fi 57/* Changes to \fBmain.cf\fR are picked up automatically as \fBspawn\fR(8) 58/* processes run for only a limited amount of time. Use the command 59/* "\fBpostfix reload\fR" to speed up a change. 60/* 61/* The text below provides only a parameter summary. See 62/* \fBpostconf\fR(5) for more details including examples. 63/* 64/* In the text below, \fItransport\fR is the first field of the entry 65/* in the \fBmaster.cf\fR file. 66/* RESOURCE AND RATE CONTROL 67/* .ad 68/* .fi 69/* .IP "\fItransport\fB_time_limit ($command_time_limit)\fR" 70/* The amount of time the command is allowed to run before it is 71/* terminated. 72/* 73/* Postfix 2.4 and later support a suffix that specifies the 74/* time unit: s (seconds), m (minutes), h (hours), d (days), 75/* w (weeks). The default time unit is seconds. 76/* MISCELLANEOUS 77/* .ad 78/* .fi 79/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" 80/* The default location of the Postfix main.cf and master.cf 81/* configuration files. 82/* .IP "\fBdaemon_timeout (18000s)\fR" 83/* How much time a Postfix daemon process may take to handle a 84/* request before it is terminated by a built-in watchdog timer. 85/* .IP "\fBexport_environment (see 'postconf -d' output)\fR" 86/* The list of environment variables that a Postfix process will export 87/* to non-Postfix processes. 88/* .IP "\fBipc_timeout (3600s)\fR" 89/* The time limit for sending or receiving information over an internal 90/* communication channel. 91/* .IP "\fBmail_owner (postfix)\fR" 92/* The UNIX system account that owns the Postfix queue and most Postfix 93/* daemon processes. 94/* .IP "\fBmax_idle (100s)\fR" 95/* The maximum amount of time that an idle Postfix daemon process waits 96/* for an incoming connection before terminating voluntarily. 97/* .IP "\fBmax_use (100)\fR" 98/* The maximal number of incoming connections that a Postfix daemon 99/* process will service before terminating voluntarily. 100/* .IP "\fBprocess_id (read-only)\fR" 101/* The process ID of a Postfix command or daemon process. 102/* .IP "\fBprocess_name (read-only)\fR" 103/* The process name of a Postfix command or daemon process. 104/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" 105/* The location of the Postfix top-level queue directory. 106/* .IP "\fBsyslog_facility (mail)\fR" 107/* The syslog facility of Postfix logging. 108/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" 109/* The mail system name that is prepended to the process name in syslog 110/* records, so that "smtpd" becomes, for example, "postfix/smtpd". 111/* SEE ALSO 112/* postconf(5), configuration parameters 113/* master(8), process manager 114/* syslogd(8), system logging 115/* LICENSE 116/* .ad 117/* .fi 118/* The Secure Mailer license must be distributed with this software. 119/* AUTHOR(S) 120/* Wietse Venema 121/* IBM T.J. Watson Research 122/* P.O. Box 704 123/* Yorktown Heights, NY 10598, USA 124/*--*/ 125 126/* System library. */ 127 128#include <sys_defs.h> 129#include <sys/wait.h> 130#include <unistd.h> 131#include <stdlib.h> 132#include <string.h> 133#include <pwd.h> 134#include <grp.h> 135#include <fcntl.h> 136#ifdef STRCASECMP_IN_STRINGS_H 137#include <strings.h> 138#endif 139 140/* Utility library. */ 141 142#include <msg.h> 143#include <argv.h> 144#include <dict.h> 145#include <mymalloc.h> 146#include <spawn_command.h> 147#include <split_at.h> 148#include <timed_wait.h> 149#include <set_eugid.h> 150 151/* Global library. */ 152 153#include <mail_version.h> 154 155/* Single server skeleton. */ 156 157#include <mail_params.h> 158#include <mail_server.h> 159#include <mail_conf.h> 160 161/* Application-specific. */ 162 163 /* 164 * Tunable parameters. Values are taken from the config file, after 165 * prepending the service name to _name, and so on. 166 */ 167int var_command_maxtime; /* system-wide */ 168 169 /* 170 * For convenience. Instead of passing around lists of parameters, bundle 171 * them up in convenient structures. 172 */ 173typedef struct { 174 char **argv; /* argument vector */ 175 uid_t uid; /* command privileges */ 176 gid_t gid; /* command privileges */ 177 int time_limit; /* per-service time limit */ 178} SPAWN_ATTR; 179 180/* get_service_attr - get service attributes */ 181 182static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv) 183{ 184 const char *myname = "get_service_attr"; 185 struct passwd *pwd; 186 struct group *grp; 187 char *user; /* user name */ 188 char *group; /* group name */ 189 190 /* 191 * Initialize. 192 */ 193 user = 0; 194 group = 0; 195 attr->argv = 0; 196 197 /* 198 * Figure out the command time limit for this transport. 199 */ 200 attr->time_limit = 201 get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0); 202 203 /* 204 * Iterate over the command-line attribute list. 205 */ 206 for ( /* void */ ; *argv != 0; argv++) { 207 208 /* 209 * user=username[:groupname] 210 */ 211 if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) { 212 user = *argv + sizeof("user=") - 1; 213 if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */ 214 if (*group == 0) 215 group = 0; 216 if ((pwd = getpwnam(user)) == 0) 217 msg_fatal("unknown user name: %s", user); 218 attr->uid = pwd->pw_uid; 219 if (group != 0) { 220 if ((grp = getgrnam(group)) == 0) 221 msg_fatal("unknown group name: %s", group); 222 attr->gid = grp->gr_gid; 223 } else { 224 attr->gid = pwd->pw_gid; 225 } 226 } 227 228 /* 229 * argv=command... 230 */ 231 else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) { 232 *argv += sizeof("argv=") - 1; /* XXX clobbers argv */ 233 attr->argv = argv; 234 break; 235 } 236 237 /* 238 * Bad. 239 */ 240 else 241 msg_fatal("unknown attribute name: %s", *argv); 242 } 243 244 /* 245 * Sanity checks. Verify that every member has an acceptable value. 246 */ 247 if (user == 0) 248 msg_fatal("missing user= attribute"); 249 if (attr->argv == 0) 250 msg_fatal("missing argv= attribute"); 251 if (attr->uid == 0) 252 msg_fatal("request to deliver as root"); 253 if (attr->uid == var_owner_uid) 254 msg_fatal("request to deliver as mail system owner"); 255 if (attr->gid == 0) 256 msg_fatal("request to use privileged group id %ld", (long) attr->gid); 257 if (attr->gid == var_owner_gid) 258 msg_fatal("request to use mail system owner group id %ld", (long) attr->gid); 259 if (attr->uid == (uid_t) (-1)) 260 msg_fatal("user must not have user ID -1"); 261 if (attr->gid == (gid_t) (-1)) 262 msg_fatal("user must not have group ID -1"); 263 264 /* 265 * Give the poor tester a clue of what is going on. 266 */ 267 if (msg_verbose) 268 msg_info("%s: uid %ld, gid %ld; time %d", 269 myname, (long) attr->uid, (long) attr->gid, attr->time_limit); 270} 271 272/* spawn_service - perform service for client */ 273 274static void spawn_service(VSTREAM *client_stream, char *service, char **argv) 275{ 276 const char *myname = "spawn_service"; 277 static SPAWN_ATTR attr; 278 WAIT_STATUS_T status; 279 ARGV *export_env; 280 281 /* 282 * This routine runs whenever a client connects to the UNIX-domain socket 283 * dedicated to running an external command. 284 */ 285 if (msg_verbose) 286 msg_info("%s: service=%s, command=%s...", myname, service, argv[0]); 287 288 /* 289 * Look up service attributes and config information only once. This is 290 * safe since the information comes from a trusted source. 291 */ 292 if (attr.argv == 0) { 293 get_service_attr(&attr, service, argv); 294 } 295 296 /* 297 * Execute the command. 298 */ 299 export_env = argv_split(var_export_environ, ", \t\r\n"); 300 status = spawn_command(SPAWN_CMD_STDIN, vstream_fileno(client_stream), 301 SPAWN_CMD_STDOUT, vstream_fileno(client_stream), 302 SPAWN_CMD_STDERR, vstream_fileno(client_stream), 303 SPAWN_CMD_UID, attr.uid, 304 SPAWN_CMD_GID, attr.gid, 305 SPAWN_CMD_ARGV, attr.argv, 306 SPAWN_CMD_TIME_LIMIT, attr.time_limit, 307 SPAWN_CMD_EXPORT, export_env->argv, 308 SPAWN_CMD_END); 309 argv_free(export_env); 310 311 /* 312 * Warn about unsuccessful completion. 313 */ 314 if (!NORMAL_EXIT_STATUS(status)) { 315 if (WIFEXITED(status)) 316 msg_warn("command %s exit status %d", 317 attr.argv[0], WEXITSTATUS(status)); 318 if (WIFSIGNALED(status)) 319 msg_warn("command %s killed by signal %d", 320 attr.argv[0], WTERMSIG(status)); 321 } 322} 323 324/* pre_accept - see if tables have changed */ 325 326static void pre_accept(char *unused_name, char **unused_argv) 327{ 328 const char *table; 329 330 if ((table = dict_changed_name()) != 0) { 331 msg_info("table %s has changed -- restarting", table); 332 exit(0); 333 } 334} 335 336/* drop_privileges - drop privileges most of the time */ 337 338static void drop_privileges(char *unused_name, char **unused_argv) 339{ 340 set_eugid(var_owner_uid, var_owner_gid); 341} 342 343MAIL_VERSION_STAMP_DECLARE; 344 345/* main - pass control to the single-threaded skeleton */ 346 347int main(int argc, char **argv) 348{ 349 static const CONFIG_TIME_TABLE time_table[] = { 350 VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0, 351 0, 352 }; 353 354 /* 355 * Fingerprint executables and core dumps. 356 */ 357 MAIL_VERSION_STAMP_ALLOCATE; 358 359 single_server_main(argc, argv, spawn_service, 360 MAIL_SERVER_TIME_TABLE, time_table, 361 MAIL_SERVER_POST_INIT, drop_privileges, 362 MAIL_SERVER_PRE_ACCEPT, pre_accept, 363 MAIL_SERVER_PRIVILEGED, 364 0); 365} 366