1 /* $NetBSD: spawn.c,v 1.2 2017/02/14 01:16:48 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). 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 /* Wietse Venema 128 /* Google, Inc. 129 /* 111 8th Avenue 130 /* New York, NY 10011, USA 131 /*--*/ 132 133 /* System library. */ 134 135 #include <sys_defs.h> 136 #include <sys/wait.h> 137 #include <unistd.h> 138 #include <stdlib.h> 139 #include <string.h> 140 #include <pwd.h> 141 #include <grp.h> 142 #include <fcntl.h> 143 #ifdef STRCASECMP_IN_STRINGS_H 144 #include <strings.h> 145 #endif 146 147 /* Utility library. */ 148 149 #include <msg.h> 150 #include <argv.h> 151 #include <dict.h> 152 #include <mymalloc.h> 153 #include <spawn_command.h> 154 #include <split_at.h> 155 #include <timed_wait.h> 156 #include <set_eugid.h> 157 158 /* Global library. */ 159 160 #include <mail_version.h> 161 162 /* Single server skeleton. */ 163 164 #include <mail_params.h> 165 #include <mail_server.h> 166 #include <mail_conf.h> 167 #include <mail_parm_split.h> 168 169 /* Application-specific. */ 170 171 /* 172 * Tunable parameters. Values are taken from the config file, after 173 * prepending the service name to _name, and so on. 174 */ 175 int var_command_maxtime; /* system-wide */ 176 177 /* 178 * For convenience. Instead of passing around lists of parameters, bundle 179 * them up in convenient structures. 180 */ 181 typedef struct { 182 char **argv; /* argument vector */ 183 uid_t uid; /* command privileges */ 184 gid_t gid; /* command privileges */ 185 int time_limit; /* per-service time limit */ 186 } SPAWN_ATTR; 187 188 /* get_service_attr - get service attributes */ 189 190 static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv) 191 { 192 const char *myname = "get_service_attr"; 193 struct passwd *pwd; 194 struct group *grp; 195 char *user; /* user name */ 196 char *group; /* group name */ 197 198 /* 199 * Initialize. 200 */ 201 user = 0; 202 group = 0; 203 attr->argv = 0; 204 205 /* 206 * Figure out the command time limit for this transport. 207 */ 208 attr->time_limit = 209 get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0); 210 211 /* 212 * Iterate over the command-line attribute list. 213 */ 214 for ( /* void */ ; *argv != 0; argv++) { 215 216 /* 217 * user=username[:groupname] 218 */ 219 if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) { 220 user = *argv + sizeof("user=") - 1; 221 if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */ 222 if (*group == 0) 223 group = 0; 224 if ((pwd = getpwnam(user)) == 0) 225 msg_fatal("unknown user name: %s", user); 226 attr->uid = pwd->pw_uid; 227 if (group != 0) { 228 if ((grp = getgrnam(group)) == 0) 229 msg_fatal("unknown group name: %s", group); 230 attr->gid = grp->gr_gid; 231 } else { 232 attr->gid = pwd->pw_gid; 233 } 234 } 235 236 /* 237 * argv=command... 238 */ 239 else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) { 240 *argv += sizeof("argv=") - 1; /* XXX clobbers argv */ 241 attr->argv = argv; 242 break; 243 } 244 245 /* 246 * Bad. 247 */ 248 else 249 msg_fatal("unknown attribute name: %s", *argv); 250 } 251 252 /* 253 * Sanity checks. Verify that every member has an acceptable value. 254 */ 255 if (user == 0) 256 msg_fatal("missing user= attribute"); 257 if (attr->argv == 0) 258 msg_fatal("missing argv= attribute"); 259 if (attr->uid == 0) 260 msg_fatal("request to deliver as root"); 261 if (attr->uid == var_owner_uid) 262 msg_fatal("request to deliver as mail system owner"); 263 if (attr->gid == 0) 264 msg_fatal("request to use privileged group id %ld", (long) attr->gid); 265 if (attr->gid == var_owner_gid) 266 msg_fatal("request to use mail system owner group id %ld", (long) attr->gid); 267 if (attr->uid == (uid_t) (-1)) 268 msg_fatal("user must not have user ID -1"); 269 if (attr->gid == (gid_t) (-1)) 270 msg_fatal("user must not have group ID -1"); 271 272 /* 273 * Give the poor tester a clue of what is going on. 274 */ 275 if (msg_verbose) 276 msg_info("%s: uid %ld, gid %ld; time %d", 277 myname, (long) attr->uid, (long) attr->gid, attr->time_limit); 278 } 279 280 /* spawn_service - perform service for client */ 281 282 static void spawn_service(VSTREAM *client_stream, char *service, char **argv) 283 { 284 const char *myname = "spawn_service"; 285 static SPAWN_ATTR attr; 286 WAIT_STATUS_T status; 287 ARGV *export_env; 288 289 /* 290 * This routine runs whenever a client connects to the UNIX-domain socket 291 * dedicated to running an external command. 292 */ 293 if (msg_verbose) 294 msg_info("%s: service=%s, command=%s...", myname, service, argv[0]); 295 296 /* 297 * Look up service attributes and config information only once. This is 298 * safe since the information comes from a trusted source. 299 */ 300 if (attr.argv == 0) { 301 get_service_attr(&attr, service, argv); 302 } 303 304 /* 305 * Execute the command. 306 */ 307 export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); 308 status = spawn_command(CA_SPAWN_CMD_STDIN(vstream_fileno(client_stream)), 309 CA_SPAWN_CMD_STDOUT(vstream_fileno(client_stream)), 310 CA_SPAWN_CMD_STDERR(vstream_fileno(client_stream)), 311 CA_SPAWN_CMD_UID(attr.uid), 312 CA_SPAWN_CMD_GID(attr.gid), 313 CA_SPAWN_CMD_ARGV(attr.argv), 314 CA_SPAWN_CMD_TIME_LIMIT(attr.time_limit), 315 CA_SPAWN_CMD_EXPORT(export_env->argv), 316 CA_SPAWN_CMD_END); 317 argv_free(export_env); 318 319 /* 320 * Warn about unsuccessful completion. 321 */ 322 if (!NORMAL_EXIT_STATUS(status)) { 323 if (WIFEXITED(status)) 324 msg_warn("command %s exit status %d", 325 attr.argv[0], WEXITSTATUS(status)); 326 if (WIFSIGNALED(status)) 327 msg_warn("command %s killed by signal %d", 328 attr.argv[0], WTERMSIG(status)); 329 } 330 } 331 332 /* pre_accept - see if tables have changed */ 333 334 static void pre_accept(char *unused_name, char **unused_argv) 335 { 336 const char *table; 337 338 if ((table = dict_changed_name()) != 0) { 339 msg_info("table %s has changed -- restarting", table); 340 exit(0); 341 } 342 } 343 344 /* drop_privileges - drop privileges most of the time */ 345 346 static void drop_privileges(char *unused_name, char **unused_argv) 347 { 348 set_eugid(var_owner_uid, var_owner_gid); 349 } 350 351 MAIL_VERSION_STAMP_DECLARE; 352 353 /* main - pass control to the single-threaded skeleton */ 354 355 int main(int argc, char **argv) 356 { 357 static const CONFIG_TIME_TABLE time_table[] = { 358 VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0, 359 0, 360 }; 361 362 /* 363 * Fingerprint executables and core dumps. 364 */ 365 MAIL_VERSION_STAMP_ALLOCATE; 366 367 single_server_main(argc, argv, spawn_service, 368 CA_MAIL_SERVER_TIME_TABLE(time_table), 369 CA_MAIL_SERVER_POST_INIT(drop_privileges), 370 CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), 371 CA_MAIL_SERVER_PRIVILEGED, 372 0); 373 } 374