384 lines
12 KiB
C
384 lines
12 KiB
C
/*++
|
|
/* NAME
|
|
/* spawn 8
|
|
/* SUMMARY
|
|
/* Postfix external command spawner
|
|
/* SYNOPSIS
|
|
/* \fBspawn\fR [generic Postfix daemon options] command_attributes...
|
|
/* DESCRIPTION
|
|
/* The \fBspawn\fR(8) daemon monitors a TCP or UNIX-domain stream
|
|
/* socket, configured in \fBmaster.cf\fR with a service type
|
|
/* \fBinet\fR or \fBunix\fR.
|
|
/*
|
|
/* This daemon spawns an external command whenever a connection
|
|
/* is established, with the standard input, output and error file
|
|
/* descriptors connected to the remote client.
|
|
/*
|
|
/* The command process is subject to the time limit specified
|
|
/* with the parameter \fItransport\fR_time_limit (default:
|
|
/* command_time_limit) where \fItransport\fR equals the service
|
|
/* name field in master.cf. A process that exceeds the time limit
|
|
/* will receive a SIGKILL signal.
|
|
/*
|
|
/* The \fBspawn\fR(8) daemon service typically has a process limit >
|
|
/* 1 in its \fBmaster.cf\fR service definition, so that the number
|
|
/* of processes can scale with demand.
|
|
/* COMMAND ATTRIBUTE SYNTAX
|
|
/* .ad
|
|
/* .fi
|
|
/* The external command attributes are given in the \fBmaster.cf\fR
|
|
/* file at the end of a service definition. The syntax is as follows:
|
|
/* .IP "\fBuser\fR=\fIusername\fR (required)"
|
|
/* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR"
|
|
/* The external command is executed with the rights of the
|
|
/* specified \fIusername\fR. The software refuses to execute
|
|
/* commands with root privileges, or with the privileges of the
|
|
/* mail system owner. If \fIgroupname\fR is specified, the
|
|
/* corresponding group ID is used instead of the group ID
|
|
/* of \fIusername\fR.
|
|
/* .IP "\fBargv\fR=\fIcommand\fR... (required)"
|
|
/* The command to be executed. This must be specified as the
|
|
/* last command attribute.
|
|
/* The command is executed directly, i.e. without interpretation of
|
|
/* shell meta characters by a shell command interpreter.
|
|
/* .sp
|
|
/* If a command argument must contain whitespace, or if a command
|
|
/* argument must begin with "{", enclose the argument with "{" and
|
|
/* "}". This form will ignore whitespace after the outer "{" and
|
|
/* before the outer "}". Example:
|
|
/* .sp
|
|
/* .fi
|
|
/* argv=/bin/sh -c { shell syntax here }
|
|
/* DIAGNOSTICS
|
|
/* The \fBspawn\fR(8) daemon reports abnormal child exits.
|
|
/* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
|
|
/* SECURITY
|
|
/* .fi
|
|
/* .ad
|
|
/* The \fBspawn\fR(8) daemon needs root privilege in order to
|
|
/* execute external commands as the specified user. It is therefore
|
|
/* security sensitive.
|
|
/*
|
|
/* However, the \fBspawn\fR(8) daemon does not receive data from
|
|
/* or about service clients or external commands, and thus is not
|
|
/* vulnerable to data-driven attacks.
|
|
/* CONFIGURATION PARAMETERS
|
|
/* .ad
|
|
/* .fi
|
|
/* Changes to \fBmain.cf\fR are picked up automatically as \fBspawn\fR(8)
|
|
/* processes run for only a limited amount of time. Use the command
|
|
/* "\fBpostfix reload\fR" to speed up a change.
|
|
/*
|
|
/* The text below provides only a parameter summary. See
|
|
/* \fBpostconf\fR(5) for more details including examples.
|
|
/*
|
|
/* In the text below, \fItransport\fR is the first field of the entry
|
|
/* in the \fBmaster.cf\fR file.
|
|
/* RESOURCE AND RATE CONTROL
|
|
/* .ad
|
|
/* .fi
|
|
/* .IP "\fBtransport_time_limit ($command_time_limit)\fR"
|
|
/* A transport-specific override for the command_time_limit parameter
|
|
/* value, where \fItransport\fR is the master.cf name of the message
|
|
/* delivery transport.
|
|
/* MISCELLANEOUS
|
|
/* .ad
|
|
/* .fi
|
|
/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
|
|
/* The default location of the Postfix main.cf and master.cf
|
|
/* configuration files.
|
|
/* .IP "\fBdaemon_timeout (18000s)\fR"
|
|
/* How much time a Postfix daemon process may take to handle a
|
|
/* request before it is terminated by a built-in watchdog timer.
|
|
/* .IP "\fBexport_environment (see 'postconf -d' output)\fR"
|
|
/* The list of environment variables that a Postfix process will export
|
|
/* to non-Postfix processes.
|
|
/* .IP "\fBipc_timeout (3600s)\fR"
|
|
/* The time limit for sending or receiving information over an internal
|
|
/* communication channel.
|
|
/* .IP "\fBmail_owner (postfix)\fR"
|
|
/* The UNIX system account that owns the Postfix queue and most Postfix
|
|
/* daemon processes.
|
|
/* .IP "\fBmax_idle (100s)\fR"
|
|
/* The maximum amount of time that an idle Postfix daemon process waits
|
|
/* for an incoming connection before terminating voluntarily.
|
|
/* .IP "\fBmax_use (100)\fR"
|
|
/* The maximal number of incoming connections that a Postfix daemon
|
|
/* process will service before terminating voluntarily.
|
|
/* .IP "\fBprocess_id (read-only)\fR"
|
|
/* The process ID of a Postfix command or daemon process.
|
|
/* .IP "\fBprocess_name (read-only)\fR"
|
|
/* The process name of a Postfix command or daemon process.
|
|
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
|
|
/* The location of the Postfix top-level queue directory.
|
|
/* .IP "\fBsyslog_facility (mail)\fR"
|
|
/* The syslog facility of Postfix logging.
|
|
/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
|
|
/* A prefix that is prepended to the process name in syslog
|
|
/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
|
|
/* .PP
|
|
/* Available in Postfix 3.3 and later:
|
|
/* .IP "\fBservice_name (read-only)\fR"
|
|
/* The master.cf service name of a Postfix daemon process.
|
|
/* SEE ALSO
|
|
/* postconf(5), configuration parameters
|
|
/* master(8), process manager
|
|
/* postlogd(8), Postfix logging
|
|
/* syslogd(8), system logging
|
|
/* LICENSE
|
|
/* .ad
|
|
/* .fi
|
|
/* The Secure Mailer license must be distributed with this software.
|
|
/* AUTHOR(S)
|
|
/* Wietse Venema
|
|
/* IBM T.J. Watson Research
|
|
/* P.O. Box 704
|
|
/* Yorktown Heights, NY 10598, USA
|
|
/*
|
|
/* Wietse Venema
|
|
/* Google, Inc.
|
|
/* 111 8th Avenue
|
|
/* New York, NY 10011, USA
|
|
/*--*/
|
|
|
|
/* System library. */
|
|
|
|
#include <sys_defs.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <fcntl.h>
|
|
#ifdef STRCASECMP_IN_STRINGS_H
|
|
#include <strings.h>
|
|
#endif
|
|
|
|
/* Utility library. */
|
|
|
|
#include <msg.h>
|
|
#include <argv.h>
|
|
#include <dict.h>
|
|
#include <mymalloc.h>
|
|
#include <spawn_command.h>
|
|
#include <split_at.h>
|
|
#include <timed_wait.h>
|
|
#include <set_eugid.h>
|
|
|
|
/* Global library. */
|
|
|
|
#include <mail_version.h>
|
|
|
|
/* Single server skeleton. */
|
|
|
|
#include <mail_params.h>
|
|
#include <mail_server.h>
|
|
#include <mail_conf.h>
|
|
#include <mail_parm_split.h>
|
|
|
|
/* Application-specific. */
|
|
|
|
/*
|
|
* Tunable parameters. Values are taken from the config file, after
|
|
* prepending the service name to _name, and so on.
|
|
*/
|
|
int var_command_maxtime; /* system-wide */
|
|
|
|
/*
|
|
* For convenience. Instead of passing around lists of parameters, bundle
|
|
* them up in convenient structures.
|
|
*/
|
|
typedef struct {
|
|
char **argv; /* argument vector */
|
|
uid_t uid; /* command privileges */
|
|
gid_t gid; /* command privileges */
|
|
int time_limit; /* per-service time limit */
|
|
} SPAWN_ATTR;
|
|
|
|
/* get_service_attr - get service attributes */
|
|
|
|
static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv)
|
|
{
|
|
const char *myname = "get_service_attr";
|
|
struct passwd *pwd;
|
|
struct group *grp;
|
|
char *user; /* user name */
|
|
char *group; /* group name */
|
|
|
|
/*
|
|
* Initialize.
|
|
*/
|
|
user = 0;
|
|
group = 0;
|
|
attr->argv = 0;
|
|
|
|
/*
|
|
* Figure out the command time limit for this transport.
|
|
*/
|
|
attr->time_limit =
|
|
get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0);
|
|
|
|
/*
|
|
* Iterate over the command-line attribute list.
|
|
*/
|
|
for ( /* void */ ; *argv != 0; argv++) {
|
|
|
|
/*
|
|
* user=username[:groupname]
|
|
*/
|
|
if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) {
|
|
user = *argv + sizeof("user=") - 1;
|
|
if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */
|
|
if (*group == 0)
|
|
group = 0;
|
|
if ((pwd = getpwnam(user)) == 0)
|
|
msg_fatal("unknown user name: %s", user);
|
|
attr->uid = pwd->pw_uid;
|
|
if (group != 0) {
|
|
if ((grp = getgrnam(group)) == 0)
|
|
msg_fatal("unknown group name: %s", group);
|
|
attr->gid = grp->gr_gid;
|
|
} else {
|
|
attr->gid = pwd->pw_gid;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* argv=command...
|
|
*/
|
|
else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) {
|
|
*argv += sizeof("argv=") - 1; /* XXX clobbers argv */
|
|
attr->argv = argv;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Bad.
|
|
*/
|
|
else
|
|
msg_fatal("unknown attribute name: %s", *argv);
|
|
}
|
|
|
|
/*
|
|
* Sanity checks. Verify that every member has an acceptable value.
|
|
*/
|
|
if (user == 0)
|
|
msg_fatal("missing user= attribute");
|
|
if (attr->argv == 0)
|
|
msg_fatal("missing argv= attribute");
|
|
if (attr->uid == 0)
|
|
msg_fatal("request to deliver as root");
|
|
if (attr->uid == var_owner_uid)
|
|
msg_fatal("request to deliver as mail system owner");
|
|
if (attr->gid == 0)
|
|
msg_fatal("request to use privileged group id %ld", (long) attr->gid);
|
|
if (attr->gid == var_owner_gid)
|
|
msg_fatal("request to use mail system owner group id %ld", (long) attr->gid);
|
|
if (attr->uid == (uid_t) (-1))
|
|
msg_fatal("user must not have user ID -1");
|
|
if (attr->gid == (gid_t) (-1))
|
|
msg_fatal("user must not have group ID -1");
|
|
|
|
/*
|
|
* Give the poor tester a clue of what is going on.
|
|
*/
|
|
if (msg_verbose)
|
|
msg_info("%s: uid %ld, gid %ld; time %d",
|
|
myname, (long) attr->uid, (long) attr->gid, attr->time_limit);
|
|
}
|
|
|
|
/* spawn_service - perform service for client */
|
|
|
|
static void spawn_service(VSTREAM *client_stream, char *service, char **argv)
|
|
{
|
|
const char *myname = "spawn_service";
|
|
static SPAWN_ATTR attr;
|
|
WAIT_STATUS_T status;
|
|
ARGV *export_env;
|
|
|
|
/*
|
|
* This routine runs whenever a client connects to the UNIX-domain socket
|
|
* dedicated to running an external command.
|
|
*/
|
|
if (msg_verbose)
|
|
msg_info("%s: service=%s, command=%s...", myname, service, argv[0]);
|
|
|
|
/*
|
|
* Look up service attributes and config information only once. This is
|
|
* safe since the information comes from a trusted source.
|
|
*/
|
|
if (attr.argv == 0) {
|
|
get_service_attr(&attr, service, argv);
|
|
}
|
|
|
|
/*
|
|
* Execute the command.
|
|
*/
|
|
export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
|
|
status = spawn_command(CA_SPAWN_CMD_STDIN(vstream_fileno(client_stream)),
|
|
CA_SPAWN_CMD_STDOUT(vstream_fileno(client_stream)),
|
|
CA_SPAWN_CMD_STDERR(vstream_fileno(client_stream)),
|
|
CA_SPAWN_CMD_UID(attr.uid),
|
|
CA_SPAWN_CMD_GID(attr.gid),
|
|
CA_SPAWN_CMD_ARGV(attr.argv),
|
|
CA_SPAWN_CMD_TIME_LIMIT(attr.time_limit),
|
|
CA_SPAWN_CMD_EXPORT(export_env->argv),
|
|
CA_SPAWN_CMD_END);
|
|
argv_free(export_env);
|
|
|
|
/*
|
|
* Warn about unsuccessful completion.
|
|
*/
|
|
if (!NORMAL_EXIT_STATUS(status)) {
|
|
if (WIFEXITED(status))
|
|
msg_warn("command %s exit status %d",
|
|
attr.argv[0], WEXITSTATUS(status));
|
|
if (WIFSIGNALED(status))
|
|
msg_warn("command %s killed by signal %d",
|
|
attr.argv[0], WTERMSIG(status));
|
|
}
|
|
}
|
|
|
|
/* pre_accept - see if tables have changed */
|
|
|
|
static void pre_accept(char *unused_name, char **unused_argv)
|
|
{
|
|
const char *table;
|
|
|
|
if ((table = dict_changed_name()) != 0) {
|
|
msg_info("table %s has changed -- restarting", table);
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
/* drop_privileges - drop privileges most of the time */
|
|
|
|
static void drop_privileges(char *unused_name, char **unused_argv)
|
|
{
|
|
set_eugid(var_owner_uid, var_owner_gid);
|
|
}
|
|
|
|
MAIL_VERSION_STAMP_DECLARE;
|
|
|
|
/* main - pass control to the single-threaded skeleton */
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
static const CONFIG_TIME_TABLE time_table[] = {
|
|
VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0,
|
|
0,
|
|
};
|
|
|
|
/*
|
|
* Fingerprint executables and core dumps.
|
|
*/
|
|
MAIL_VERSION_STAMP_ALLOCATE;
|
|
|
|
single_server_main(argc, argv, spawn_service,
|
|
CA_MAIL_SERVER_TIME_TABLE(time_table),
|
|
CA_MAIL_SERVER_POST_INIT(drop_privileges),
|
|
CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
|
|
CA_MAIL_SERVER_PRIVILEGED,
|
|
0);
|
|
}
|