1 /* $NetBSD: spawn.c,v 1.3 2020/03/18 19:05:21 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 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 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 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 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 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