service-process.c revision 7487ff578435377bbeefffdbfb78ca09ed1292df
145N/A/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
145N/A
145N/A#include "common.h"
145N/A#include "array.h"
145N/A#include "aqueue.h"
145N/A#include "ioloop.h"
145N/A#include "istream.h"
145N/A#include "ostream.h"
145N/A#include "write-full.h"
145N/A#include "base64.h"
145N/A#include "hash.h"
145N/A#include "str.h"
145N/A#include "hostpid.h"
145N/A#include "env-util.h"
145N/A#include "fd-close-on-exec.h"
145N/A#include "restrict-access.h"
145N/A#include "master-service-settings.h"
145N/A#include "dup2-array.h"
145N/A#include "service.h"
145N/A#include "service-log.h"
5630N/A#include "service-auth-server.h"
145N/A#include "service-auth-source.h"
145N/A#include "service-process.h"
145N/A
187N/A#include <stdlib.h>
3996N/A#include <unistd.h>
187N/A#include <syslog.h>
187N/A#include <signal.h>
187N/A#include <sys/wait.h>
145N/A
187N/Astatic void
187N/Aservice_dup_fds(struct service *service, int auth_fd, int std_fd)
187N/A{
145N/A struct service_listener *const *listeners;
1273N/A ARRAY_TYPE(dup2) dups;
1273N/A unsigned int i, count, n = 0, socket_listener_count, ssl_socket_count;
1273N/A
5630N/A /* stdin/stdout is already redirected to /dev/null. Other master fds
5630N/A should have been opened with fd_close_on_exec() so we don't have to
5630N/A worry about them.
5630N/A
5630N/A because the destination fd might be another one's source fd we have
187N/A to be careful not to overwrite anything. dup() the fd when needed */
7034N/A
197N/A socket_listener_count = 0;
197N/A listeners = array_get(&service->listeners, &count);
197N/A t_array_init(&dups, count + 10);
197N/A
187N/A if (service->type == SERVICE_TYPE_LOG) {
5630N/A i_assert(n == 0);
187N/A services_log_dup2(&dups, service->list, MASTER_LISTEN_FD_FIRST,
5630N/A &socket_listener_count);
5630N/A n += socket_listener_count;
5630N/A }
5630N/A
5630N/A /* first add non-ssl listeners */
7034N/A for (i = 0; i < count; i++) {
5630N/A if (listeners[i]->fd != -1 &&
5630N/A !listeners[i]->set.inetset.set->ssl) {
5630N/A dup2_append(&dups, listeners[i]->fd,
145N/A MASTER_LISTEN_FD_FIRST + n);
4409N/A n++; socket_listener_count++;
4409N/A }
4409N/A }
4409N/A /* then ssl-listeners */
4409N/A ssl_socket_count = 0;
4409N/A for (i = 0; i < count; i++) {
192N/A if (listeners[i]->fd != -1 &&
192N/A listeners[i]->set.inetset.set->ssl) {
192N/A dup2_append(&dups, listeners[i]->fd,
145N/A MASTER_LISTEN_FD_FIRST + n);
145N/A n++; socket_listener_count++;
145N/A ssl_socket_count++;
187N/A }
4409N/A }
3468N/A
187N/A dup2_append(&dups, null_fd, MASTER_RESERVED_FD);
5630N/A dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
5630N/A
5630N/A switch (service->type) {
5630N/A case SERVICE_TYPE_AUTH_SOURCE:
187N/A case SERVICE_TYPE_AUTH_SERVER:
1900N/A i_assert(auth_fd != -1);
191N/A dup2_append(&dups, auth_fd, MASTER_AUTH_FD);
145N/A env_put(t_strdup_printf("MASTER_AUTH_FD=%d", MASTER_AUTH_FD));
5630N/A break;
5630N/A default:
5630N/A i_assert(auth_fd == -1);
5630N/A dup2_append(&dups, null_fd, MASTER_AUTH_FD);
5630N/A break;
187N/A }
2541N/A
187N/A if (std_fd != -1) {
187N/A dup2_append(&dups, std_fd, STDIN_FILENO);
187N/A dup2_append(&dups, std_fd, STDOUT_FILENO);
187N/A env_put("LOGGED_IN=1");
187N/A }
1900N/A
1900N/A if (service->type != SERVICE_TYPE_LOG) {
1900N/A /* set log file to stderr. dup2() here immediately so that
187N/A we can set up logging to it without causing any log messages
187N/A to be lost. */
187N/A i_assert(service->log_fd[1] != -1);
145N/A
197N/A env_put("LOG_SERVICE=1");
197N/A if (dup2(service->log_fd[1], STDERR_FILENO) < 0)
197N/A i_fatal("dup2(log fd) failed: %m");
145N/A i_set_failure_internal();
197N/A } else {
dup2_append(&dups, null_fd, STDERR_FILENO);
}
/* make sure we don't leak syslog fd. try to do it as late as possible,
but also before dup2()s in case syslog fd is one of them. */
closelog();
if (dup2_array(&dups) < 0)
service_error(service, "dup2s failed");
env_put(t_strdup_printf("SOCKET_COUNT=%d", socket_listener_count));
env_put(t_strdup_printf("SSL_SOCKET_COUNT=%d", ssl_socket_count));
}
static int validate_uid_gid(struct master_settings *set, uid_t uid, gid_t gid,
const char *user)
{
if (uid == 0) {
i_error("Logins with UID 0 not permitted (user %s)", user);
return FALSE;
}
if (uid < (uid_t)set->first_valid_uid ||
(set->last_valid_uid != 0 && uid > (uid_t)set->last_valid_uid)) {
i_error("Logins with UID %s (user %s) not permitted "
"(see first_valid_uid in config file)",
dec2str(uid), user);
return FALSE;
}
if (gid < (gid_t)set->first_valid_gid ||
(set->last_valid_gid != 0 && gid > (gid_t)set->last_valid_gid)) {
i_error("Logins for users with primary group ID %s (user %s) "
"not permitted (see first_valid_gid in config file).",
dec2str(gid), user);
return FALSE;
}
return TRUE;
}
static void auth_args_apply(const char *const *args,
struct restrict_access_settings *rset,
const char **home)
{
const char *key, *value;
string_t *expanded_vars;
expanded_vars = t_str_new(128);
str_append(expanded_vars, "VARS_EXPANDED=");
for (; *args != NULL; args++) {
if (strncmp(*args, "uid=", 4) == 0)
rset->uid = (uid_t)strtoul(*args + 4, NULL, 10);
else if (strncmp(*args, "gid=", 4) == 0)
rset->gid = (gid_t)strtoul(*args + 4, NULL, 10);
else if (strncmp(*args, "home=", 5) == 0) {
*home = *args + 5;
env_put(t_strconcat("HOME=", *args + 5, NULL));
} else if (strncmp(*args, "chroot=", 7) == 0)
rset->chroot_dir = *args + 7;
else if (strncmp(*args, "system_groups_user=", 19) == 0)
rset->system_groups_user = *args + 19;
else if (strncmp(*args, "mail_access_groups=", 19) == 0) {
rset->extra_groups =
rset->extra_groups == NULL ? *args + 19 :
t_strconcat(*args + 19, ",",
rset->extra_groups, NULL);
} else {
/* unknown, set as environment */
value = strchr(*args, '=');
if (value == NULL) {
/* boolean */
key = *args;
value = "=1";
} else {
key = t_strdup_until(*args, value);
if (strcmp(key, "mail") == 0) {
/* FIXME: kind of ugly to have it
here.. */
key = "mail_location";
}
}
str_append(expanded_vars, key);
str_append_c(expanded_vars, ' ');
env_put(t_strconcat(t_str_ucase(key), value, NULL));
}
}
env_put(str_c(expanded_vars));
}
static void drop_privileges(struct service *service,
const char *const *auth_args)
{
struct master_settings *master_set = service->set->master_set;
struct restrict_access_settings rset;
const char *user, *home = NULL;
bool disallow_root;
restrict_access_init(&rset);
rset.uid = service->uid;
rset.gid = service->gid;
rset.privileged_gid = service->privileged_gid;
rset.chroot_dir = *service->set->chroot == '\0' ? NULL :
service->set->chroot;
rset.extra_groups = service->extra_gids;
if (auth_args == NULL) {
/* non-authenticating service. don't use *_valid_gid checks */
} else {
i_assert(auth_args[0] != NULL);
rset.first_valid_gid = master_set->first_valid_gid;
rset.last_valid_gid = master_set->last_valid_gid;
user = auth_args[0];
env_put(t_strconcat("USER=", user, NULL));
auth_args_apply(auth_args + 1, &rset, &home);
if (!validate_uid_gid(master_set, rset.uid, rset.gid, user))
exit(FATAL_DEFAULT);
}
if (home != NULL) {
if (chdir(home) < 0 && errno != ENOENT)
i_error("chdir(%s) failed: %m", home);
}
if (service->set->drop_priv_before_exec) {
disallow_root = service->type == SERVICE_TYPE_AUTH_SERVER ||
service->type == SERVICE_TYPE_AUTH_SOURCE;
restrict_access(&rset, home, disallow_root);
} else {
restrict_access_set_env(&rset);
}
}
static void
service_process_setup_environment(struct service *service, unsigned int uid)
{
const struct master_service_settings *set;
struct service_listener *const *listeners;
const char *const *p;
unsigned int limit, count;
/* remove all environment, and put back what we need */
env_clean();
for (p = service->list->child_process_env; *p != NULL; p++)
env_put(*p);
switch (service->type) {
case SERVICE_TYPE_CONFIG:
env_put(t_strconcat(MASTER_CONFIG_FILE_ENV"=",
service->config_file_path, NULL));
break;
case SERVICE_TYPE_LOG:
/* give the log's configuration directly, so it won't depend
on config process */
set = master_service_settings_get(master_service);
env_put("DOVECONF_ENV=1");
env_put(t_strconcat("LOG_PATH=", set->log_path, NULL));
env_put(t_strconcat("INFO_LOG_PATH=", set->info_log_path, NULL));
env_put(t_strconcat("LOG_TIMESTAMP=", set->log_timestamp, NULL));
env_put(t_strconcat("SYSLOG_FACILITY=", set->syslog_facility, NULL));
break;
default:
listeners = array_get(&service->list->config->listeners,
&count);
i_assert(count > 0);
env_put(t_strconcat(MASTER_CONFIG_FILE_ENV"=",
listeners[0]->set.fileset.set->path, NULL));
break;
}
limit = service->set->client_limit;
if (limit == 0) {
/* fallback to default limit */
limit = service->set->master_set->default_client_limit;
}
env_put(t_strdup_printf(MASTER_CLIENT_LIMIT_ENV"=%u", limit));
env_put(t_strdup_printf(MASTER_UID_ENV"=%u", uid));
if (!service->set->master_set->version_ignore)
env_put(MASTER_DOVECOT_VERSION_ENV"="PACKAGE_VERSION);
}
static void service_process_status_timeout(struct service_process *process)
{
service_error(process->service,
"Initial status notification not received in %d "
"seconds, killing the process",
SERVICE_FIRST_STATUS_TIMEOUT_SECS);
if (kill(process->pid, SIGKILL) < 0 && errno != ESRCH) {
service_error(process->service, "kill(%s, SIGKILL) failed: %m",
dec2str(process->pid));
}
timeout_remove(&process->to_status);
}
struct service_process *
service_process_create(struct service *service, const char *const *auth_args,
int std_fd, const unsigned char *data, size_t data_size)
{
static unsigned int uid_counter = 0;
struct service_process *process;
unsigned int uid = ++uid_counter;
string_t *str;
int fd[2];
pid_t pid;
switch (service->type) {
case SERVICE_TYPE_AUTH_SOURCE:
case SERVICE_TYPE_AUTH_SERVER:
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) {
service_error(service, "socketpair() failed: %m");
return NULL;
}
fd_close_on_exec(fd[0], TRUE);
fd_close_on_exec(fd[1], TRUE);
break;
default:
fd[0] = fd[1] = -1;
break;
}
pid = fork();
if (pid < 0) {
service_error(service, "fork() failed: %m");
if (fd[0] != -1) {
(void)close(fd[0]);
(void)close(fd[1]);
}
return NULL;
}
if (pid == 0) {
/* child */
if (fd[0] != -1)
(void)close(fd[0]);
service_process_setup_environment(service, uid);
if (data_size > 0) {
str = t_str_new(data_size*3);
str_append(str, "CLIENT_INPUT=");
base64_encode(data, data_size, str);
env_put(str_c(str));
}
service_dup_fds(service, fd[1], std_fd);
drop_privileges(service, auth_args);
process_exec(service->executable, NULL);
}
switch (service->type) {
case SERVICE_TYPE_AUTH_SERVER:
process = i_malloc(sizeof(struct service_process_auth_server));
process->service = service;
service_process_auth_server_init(process, fd[0]);
(void)close(fd[1]);
break;
case SERVICE_TYPE_AUTH_SOURCE:
process = i_malloc(sizeof(struct service_process_auth_source));
process->service = service;
service_process_auth_source_init(process, fd[0]);
(void)close(fd[1]);
break;
default:
process = i_new(struct service_process, 1);
process->service = service;
i_assert(fd[0] == -1);
break;
}
process->refcount = 1;
process->pid = pid;
process->uid = uid;
process->to_status =
timeout_add(SERVICE_FIRST_STATUS_TIMEOUT_SECS * 1000,
service_process_status_timeout, process);
process->available_count = service->set->client_limit;
if (process->available_count == 0) {
/* fallback to default limit */
process->available_count =
service->set->master_set->default_client_limit;
}
service->process_count++;
service->process_avail++;
hash_table_insert(service->list->pids, &process->pid, process);
return process;
}
static int service_process_write_bye(struct service_process *process)
{
const char *data;
data = t_strdup_printf("%d %s BYE\n",
process->service->log_process_internal_fd,
dec2str(process->pid));
if (write(process->service->list->master_log_fd[1],
data, strlen(data)) < 0) {
if (errno != EAGAIN)
i_error("write(log process) failed: %m");
return -1;
}
return 0;
}
static void service_list_log_flush_byes(struct service_list *service_list)
{
struct service_process *const *processes, *process;
while (aqueue_count(service_list->bye_queue) > 0) {
processes = array_idx_modifiable(&service_list->bye_arr, 0);
process = processes[aqueue_idx(service_list->bye_queue, 0)];
if (service_process_write_bye(process) < 0) {
if (errno != EAGAIN)
services_log_clear_byes(service_list);
return;
}
service_process_unref(process);
aqueue_delete_tail(service_list->bye_queue);
}
io_remove(&service_list->io_log_write);
}
static void service_process_log_bye(struct service_process *process)
{
struct service_list *service_list = process->service->list;
if (process->service->log_fd[1] == -1) {
/* stopping all services */
return;
}
if (service_process_write_bye(process) < 0) {
if (errno != EAGAIN)
return;
if (service_list->io_log_write == NULL) {
service_list->io_log_write =
io_add(service_list->master_log_fd[1], IO_WRITE,
service_list_log_flush_byes,
service_list);
}
aqueue_append(service_list->bye_queue, &process);
service_process_ref(process);
}
}
void service_process_destroy(struct service_process *process)
{
struct service *service = process->service;
hash_table_remove(service->list->pids, &process->pid);
if (process->available_count > 0)
service->process_avail--;
service->process_count--;
i_assert(service->process_avail <= service->process_count);
if (process->to_status != NULL)
timeout_remove(&process->to_status);
switch (process->service->type) {
case SERVICE_TYPE_AUTH_SERVER:
service_process_auth_server_deinit(process);
break;
case SERVICE_TYPE_AUTH_SOURCE:
service_process_auth_source_deinit(process);
break;
default:
break;
}
service_process_log_bye(process);
process->destroyed = TRUE;
service_process_unref(process);
}
void service_process_ref(struct service_process *process)
{
i_assert(process->refcount > 0);
process->refcount++;
}
int service_process_unref(struct service_process *process)
{
i_assert(process->refcount > 0);
if (--process->refcount > 0)
return TRUE;
i_assert(process->destroyed);
i_free(process);
return FALSE;
}
static const char *
get_exit_status_message(struct service *service, enum fatal_exit_status status)
{
switch (status) {
case FATAL_LOGOPEN:
return "Can't open log file";
case FATAL_LOGWRITE:
return "Can't write to log file";
case FATAL_LOGERROR:
return "Internal logging error";
case FATAL_OUTOFMEM:
if (service->set->vsz_limit == 0)
return "Out of memory";
return t_strdup_printf("Out of memory (vsz_limit=%u MB)",
service->set->vsz_limit);
case FATAL_EXEC:
return "exec() failed";
case FATAL_DEFAULT:
return "Fatal failure";
}
return NULL;
}
static void log_coredump(struct service *service, string_t *str, int status)
{
#ifdef WCOREDUMP
int signum = WTERMSIG(status);
if (WCOREDUMP(status)) {
str_append(str, " (core dumped)");
return;
}
if (signum != SIGABRT && signum != SIGSEGV && signum != SIGBUS)
return;
/* let's try to figure out why we didn't get a core dump */
if (core_dumps_disabled) {
str_printfa(str, " (core dumps disabled)");
return;
}
#ifdef HAVE_PR_SET_DUMPABLE
if (!service->set->drop_priv_before_exec) {
str_append(str, " (core not dumped - set drop_priv_before_exec=yes)");
return;
}
if (*service->set->privileged_group != '\0') {
str_append(str, " (core not dumped - privileged_group prevented it)");
return;
}
#endif
str_append(str, " (core not dumped)");
#endif
}
static void
service_process_get_status_error(string_t *str, struct service_process *process,
int status, bool *default_fatal_r)
{
struct service *service = process->service;
const char *msg;
*default_fatal_r = FALSE;
str_printfa(str, "service(%s): child %s ", service->set->name,
dec2str(process->pid));
if (WIFSIGNALED(status)) {
str_printfa(str, "killed with signal %d", WTERMSIG(status));
log_coredump(service, str, status);
return;
}
if (!WIFEXITED(status)) {
str_printfa(str, "died with status %d", status);
return;
}
status = WEXITSTATUS(status);
if (status == 0) {
str_truncate(str, 0);
return;
}
str_printfa(str, "returned error %d", status);
msg = get_exit_status_message(service, status);
if (msg != NULL)
str_printfa(str, " (%s)", msg);
if (status == FATAL_DEFAULT)
*default_fatal_r = TRUE;
}
static void service_process_log(struct service_process *process,
bool default_fatal, const char *str)
{
const char *data;
if (!default_fatal || process->service->log_fd[1] == -1) {
i_error("%s", str);
return;
}
/* log it via the log process in charge of handling
this process's logging */
data = t_strdup_printf("%d %s DEFAULT-FATAL %s\n",
process->service->log_process_internal_fd,
dec2str(process->pid), str);
if (write(process->service->list->master_log_fd[1],
data, strlen(data)) < 0) {
i_error("write(log process) failed: %m");
i_error("%s", str);
}
}
void service_process_log_status_error(struct service_process *process,
int status)
{
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
/* fast path */
return;
}
T_BEGIN {
string_t *str = t_str_new(256);
bool default_fatal;
service_process_get_status_error(str, process, status,
&default_fatal);
if (str_len(str) > 0)
service_process_log(process, default_fatal, str_c(str));
} T_END;
}