1 /* $NetBSD: spawn.c,v 1.1.1.1 2009/06/23 10:08:57 tron 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). 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 "\fItransport\fB_time_limit ($command_time_limit)\fR" 72 /* The amount of time the command is allowed to run before it is 73 /* terminated. 74 /* 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. 78 /* MISCELLANEOUS 79 /* .ad 80 /* .fi 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 95 /* daemon processes. 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". 113 /* SEE ALSO 114 /* postconf(5), configuration parameters 115 /* master(8), process manager 116 /* syslogd(8), system logging 117 /* LICENSE 118 /* .ad 119 /* .fi 120 /* The Secure Mailer license must be distributed with this software. 121 /* AUTHOR(S) 122 /* Wietse Venema 123 /* IBM T.J. Watson Research 124 /* P.O. Box 704 125 /* Yorktown Heights, NY 10598, USA 126 /*--*/ 127 128 /* System library. */ 129 130 #include <sys_defs.h> 131 #include <sys/wait.h> 132 #include <unistd.h> 133 #include <stdlib.h> 134 #include <string.h> 135 #include <pwd.h> 136 #include <grp.h> 137 #include <fcntl.h> 138 #ifdef STRCASECMP_IN_STRINGS_H 139 #include <strings.h> 140 #endif 141 142 /* Utility library. */ 143 144 #include <msg.h> 145 #include <argv.h> 146 #include <dict.h> 147 #include <mymalloc.h> 148 #include <spawn_command.h> 149 #include <split_at.h> 150 #include <timed_wait.h> 151 #include <set_eugid.h> 152 153 /* Global library. */ 154 155 #include <mail_version.h> 156 157 /* Single server skeleton. */ 158 159 #include <mail_params.h> 160 #include <mail_server.h> 161 #include <mail_conf.h> 162 163 /* Application-specific. */ 164 165 /* 166 * Tunable parameters. Values are taken from the config file, after 167 * prepending the service name to _name, and so on. 168 */ 169 int var_command_maxtime; /* system-wide */ 170 171 /* 172 * For convenience. Instead of passing around lists of parameters, bundle 173 * them up in convenient structures. 174 */ 175 typedef struct { 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 */ 180 } SPAWN_ATTR; 181 182 /* get_service_attr - get service attributes */ 183 184 static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv) 185 { 186 const char *myname = "get_service_attr"; 187 struct passwd *pwd; 188 struct group *grp; 189 char *user; /* user name */ 190 char *group; /* group name */ 191 192 /* 193 * Initialize. 194 */ 195 user = 0; 196 group = 0; 197 attr->argv = 0; 198 199 /* 200 * Figure out the command time limit for this transport. 201 */ 202 attr->time_limit = 203 get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0); 204 205 /* 206 * Iterate over the command-line attribute list. 207 */ 208 for ( /* void */ ; *argv != 0; argv++) { 209 210 /* 211 * user=username[:groupname] 212 */ 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 */ 216 if (*group == 0) 217 group = 0; 218 if ((pwd = getpwnam(user)) == 0) 219 msg_fatal("unknown user name: %s", user); 220 attr->uid = pwd->pw_uid; 221 if (group != 0) { 222 if ((grp = getgrnam(group)) == 0) 223 msg_fatal("unknown group name: %s", group); 224 attr->gid = grp->gr_gid; 225 } else { 226 attr->gid = pwd->pw_gid; 227 } 228 } 229 230 /* 231 * argv=command... 232 */ 233 else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) { 234 *argv += sizeof("argv=") - 1; /* XXX clobbers argv */ 235 attr->argv = argv; 236 break; 237 } 238 239 /* 240 * Bad. 241 */ 242 else 243 msg_fatal("unknown attribute name: %s", *argv); 244 } 245 246 /* 247 * Sanity checks. Verify that every member has an acceptable value. 248 */ 249 if (user == 0) 250 msg_fatal("missing user= attribute"); 251 if (attr->argv == 0) 252 msg_fatal("missing argv= attribute"); 253 if (attr->uid == 0) 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"); 257 if (attr->gid == 0) 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"); 265 266 /* 267 * Give the poor tester a clue of what is going on. 268 */ 269 if (msg_verbose) 270 msg_info("%s: uid %ld, gid %ld; time %d", 271 myname, (long) attr->uid, (long) attr->gid, attr->time_limit); 272 } 273 274 /* spawn_service - perform service for client */ 275 276 static void spawn_service(VSTREAM *client_stream, char *service, char **argv) 277 { 278 const char *myname = "spawn_service"; 279 static SPAWN_ATTR attr; 280 WAIT_STATUS_T status; 281 ARGV *export_env; 282 283 /* 284 * This routine runs whenever a client connects to the UNIX-domain socket 285 * dedicated to running an external command. 286 */ 287 if (msg_verbose) 288 msg_info("%s: service=%s, command=%s...", myname, service, argv[0]); 289 290 /* 291 * Look up service attributes and config information only once. This is 292 * safe since the information comes from a trusted source. 293 */ 294 if (attr.argv == 0) { 295 get_service_attr(&attr, service, argv); 296 } 297 298 /* 299 * Execute the command. 300 */ 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, 310 SPAWN_CMD_END); 311 argv_free(export_env); 312 313 /* 314 * Warn about unsuccessful completion. 315 */ 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)); 323 } 324 } 325 326 /* pre_accept - see if tables have changed */ 327 328 static void pre_accept(char *unused_name, char **unused_argv) 329 { 330 const char *table; 331 332 if ((table = dict_changed_name()) != 0) { 333 msg_info("table %s has changed -- restarting", table); 334 exit(0); 335 } 336 } 337 338 /* drop_privileges - drop privileges most of the time */ 339 340 static void drop_privileges(char *unused_name, char **unused_argv) 341 { 342 set_eugid(var_owner_uid, var_owner_gid); 343 } 344 345 MAIL_VERSION_STAMP_DECLARE; 346 347 /* main - pass control to the single-threaded skeleton */ 348 349 int main(int argc, char **argv) 350 { 351 static const CONFIG_TIME_TABLE time_table[] = { 352 VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0, 353 0, 354 }; 355 356 /* 357 * Fingerprint executables and core dumps. 358 */ 359 MAIL_VERSION_STAMP_ALLOCATE; 360 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, 366 0); 367 } 368