service-process.c revision 02c335c23bf5fa225a467c19f2c063fb0dc7b8c3
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen/* Copyright (c) 2005-2016 Dovecot authors, see the included COPYING file */
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen#include "common.h"
64e244defe74f513ce94f33d000a048ddbe2ea23Timo Sirainen#include "array.h"
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen#include "aqueue.h"
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen#include "ioloop.h"
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen#include "istream.h"
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen#include "ostream.h"
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen#include "write-full.h"
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen#include "base64.h"
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen#include "hash.h"
fd7ca4bdc1fb63547d997b6ddd639284cb5a0d01Timo Sirainen#include "str.h"
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen#include "strescape.h"
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen#include "llist.h"
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen#include "hostpid.h"
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen#include "env-util.h"
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen#include "fd-close-on-exec.h"
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen#include "restrict-access.h"
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen#include "restrict-process-size.h"
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen#include "eacces-error.h"
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen#include "master-service.h"
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen#include "master-service-settings.h"
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen#include "dup2-array.h"
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen#include "service.h"
64e244defe74f513ce94f33d000a048ddbe2ea23Timo Sirainen#include "service-anvil.h"
64e244defe74f513ce94f33d000a048ddbe2ea23Timo Sirainen#include "service-listen.h"
87cc5e9025e7fb6408f0de64c48d2d2897773ba5Timo Sirainen#include "service-log.h"
939451389b8e0ad529277b84fe51dab38a8cf77cTimo Sirainen#include "service-process-notify.h"
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen#include "service-process.h"
64e244defe74f513ce94f33d000a048ddbe2ea23Timo Sirainen
64e244defe74f513ce94f33d000a048ddbe2ea23Timo Sirainen#include <unistd.h>
9c3577aeb78a27920439ad9f1e62ee03699378c3Timo Sirainen#include <fcntl.h>
64e244defe74f513ce94f33d000a048ddbe2ea23Timo Sirainen#include <syslog.h>
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen#include <signal.h>
64e244defe74f513ce94f33d000a048ddbe2ea23Timo Sirainen#include <sys/wait.h>
64e244defe74f513ce94f33d000a048ddbe2ea23Timo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainenstatic void service_reopen_inet_listeners(struct service *service)
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen{
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen struct service_listener *const *listeners;
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen unsigned int i, count;
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen int old_fd;
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen listeners = array_get(&service->listeners, &count);
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen for (i = 0; i < count; i++) {
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen if (!listeners[i]->reuse_port || listeners[i]->fd == -1)
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen continue;
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen
18ddd4fba186b1b407cae98bb388fa8add7db48dTimo Sirainen old_fd = listeners[i]->fd;
18ddd4fba186b1b407cae98bb388fa8add7db48dTimo Sirainen listeners[i]->fd = -1;
18ddd4fba186b1b407cae98bb388fa8add7db48dTimo Sirainen if (service_listener_listen(listeners[i]) < 0)
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen listeners[i]->fd = old_fd;
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen }
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen}
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainenstatic void
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainenservice_dup_fds(struct service *service)
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen{
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen struct service_listener *const *listeners;
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen ARRAY_TYPE(dup2) dups;
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen string_t *listener_settings;
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen int fd = MASTER_LISTEN_FD_FIRST;
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen unsigned int i, count, socket_listener_count;
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen /* stdin/stdout is already redirected to /dev/null. Other master fds
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen should have been opened with fd_close_on_exec() so we don't have to
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen worry about them.
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen because the destination fd might be another one's source fd we have
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen to be careful not to overwrite anything. dup() the fd when needed */
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen socket_listener_count = 0;
8eea67470c1bd8562a62e7445d930bb2079b1a43Timo Sirainen listeners = array_get(&service->listeners, &count);
64e244defe74f513ce94f33d000a048ddbe2ea23Timo Sirainen t_array_init(&dups, count + 10);
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen switch (service->type) {
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen case SERVICE_TYPE_LOG:
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen i_assert(fd == MASTER_LISTEN_FD_FIRST);
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen services_log_dup2(&dups, service->list, fd,
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen &socket_listener_count);
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen fd += socket_listener_count;
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen break;
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen case SERVICE_TYPE_ANVIL:
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen dup2_append(&dups, service_anvil_global->log_fdpass_fd[0],
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen MASTER_ANVIL_LOG_FDPASS_FD);
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen /* nonblocking anvil fd must be the first one. anvil treats it
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen as the master's fd */
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen dup2_append(&dups, service_anvil_global->nonblocking_fd[0], fd++);
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen dup2_append(&dups, service_anvil_global->blocking_fd[0], fd++);
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen socket_listener_count += 2;
acf3b7bf3a8891b118a71c45e6c48d17bc90b259Timo Sirainen break;
acf3b7bf3a8891b118a71c45e6c48d17bc90b259Timo Sirainen default:
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen break;
}
/* add listeners */
listener_settings = t_str_new(256);
for (i = 0; i < count; i++) {
if (listeners[i]->fd != -1) {
str_truncate(listener_settings, 0);
str_append_tabescaped(listener_settings, listeners[i]->name);
if (listeners[i]->type == SERVICE_LISTENER_INET) {
if (listeners[i]->set.inetset.set->ssl)
str_append(listener_settings, "\tssl");
if (listeners[i]->set.inetset.set->haproxy)
str_append(listener_settings, "\thaproxy");
}
dup2_append(&dups, listeners[i]->fd, fd++);
env_put(t_strdup_printf("SOCKET%d_SETTINGS=%s",
socket_listener_count, str_c(listener_settings)));
socket_listener_count++;
}
}
if (service->login_notify_fd != -1) {
dup2_append(&dups, service->login_notify_fd,
MASTER_LOGIN_NOTIFY_FD);
}
switch (service->type) {
case SERVICE_TYPE_LOG:
case SERVICE_TYPE_ANVIL:
case SERVICE_TYPE_CONFIG:
dup2_append(&dups, null_fd, MASTER_ANVIL_FD);
break;
case SERVICE_TYPE_UNKNOWN:
case SERVICE_TYPE_LOGIN:
case SERVICE_TYPE_STARTUP:
dup2_append(&dups, service_anvil_global->blocking_fd[1],
MASTER_ANVIL_FD);
break;
}
dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
if (service->type != SERVICE_TYPE_ANVIL) {
dup2_append(&dups, service->list->master_dead_pipe_fd[1],
MASTER_DEAD_FD);
} else {
dup2_append(&dups, global_master_dead_pipe_fd[1],
MASTER_DEAD_FD);
}
if (service->type == SERVICE_TYPE_LOG) {
/* keep stderr as-is. this is especially important when
log_path=/dev/stderr, but might be helpful even in other
situations for logging startup errors */
} else {
/* set log file to stderr. dup2() here immediately so that
we can set up logging to it without causing any log messages
to be lost. */
i_assert(service->log_fd[1] != -1);
env_put("LOG_SERVICE=1");
if (dup2(service->log_fd[1], STDERR_FILENO) < 0)
i_fatal("dup2(log fd) failed: %m");
i_set_failure_internal();
}
/* 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)
i_fatal("service(%s): dup2s failed", service->set->name);
i_assert(fd == MASTER_LISTEN_FD_FIRST + (int)socket_listener_count);
env_put(t_strdup_printf("SOCKET_COUNT=%d", socket_listener_count));
}
static void
drop_privileges(struct service *service)
{
struct restrict_access_settings rset;
bool disallow_root;
unsigned int len;
if (service->vsz_limit != 0)
restrict_process_size(service->vsz_limit);
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;
if (rset.chroot_dir != NULL) {
/* drop trailing / if it exists */
len = strlen(rset.chroot_dir);
if (rset.chroot_dir[len-1] == '/')
rset.chroot_dir = t_strndup(rset.chroot_dir, len-1);
}
rset.extra_groups = service->extra_gids;
restrict_access_set_env(&rset);
if (service->set->drop_priv_before_exec) {
disallow_root = service->type == SERVICE_TYPE_LOGIN;
restrict_access(&rset, NULL, disallow_root);
}
}
static void service_process_setup_config_environment(struct service *service)
{
const struct master_service_settings *set = service->list->service_set;
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 */
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("DEBUG_LOG_PATH=", set->debug_log_path, NULL));
env_put(t_strconcat("LOG_TIMESTAMP=", set->log_timestamp, NULL));
env_put(t_strconcat("SYSLOG_FACILITY=", set->syslog_facility, NULL));
if (set->verbose_proctitle)
env_put("VERBOSE_PROCTITLE=1");
env_put("SSL=no");
break;
default:
env_put(t_strconcat(MASTER_CONFIG_FILE_ENV"=",
services_get_config_socket_path(service->list), NULL));
break;
}
}
static void
service_process_setup_environment(struct service *service, unsigned int uid,
const char *hostdomain)
{
master_service_env_clean();
env_put(MASTER_IS_PARENT_ENV"=1");
service_process_setup_config_environment(service);
env_put(t_strdup_printf(MASTER_CLIENT_LIMIT_ENV"=%u",
service->client_limit));
env_put(t_strdup_printf(MASTER_PROCESS_LIMIT_ENV"=%u",
service->process_limit));
env_put(t_strdup_printf(MASTER_PROCESS_MIN_AVAIL_ENV"=%u",
service->set->process_min_avail));
env_put(t_strdup_printf(MASTER_SERVICE_IDLE_KILL_ENV"=%u",
service->idle_kill));
if (service->set->service_count != 0) {
env_put(t_strdup_printf(MASTER_SERVICE_COUNT_ENV"=%u",
service->set->service_count));
}
env_put(t_strdup_printf(MASTER_UID_ENV"=%u", uid));
env_put(t_strdup_printf(MY_HOSTNAME_ENV"=%s", my_hostname));
env_put(t_strdup_printf(MY_HOSTDOMAIN_ENV"=%s", hostdomain));
if (!service->set->master_set->version_ignore)
env_put(MASTER_DOVECOT_VERSION_ENV"="PACKAGE_VERSION);
if (ssl_manual_key_password != NULL && service->have_inet_listeners) {
/* manually given SSL password. give it only to services
that have inet listeners. */
env_put(t_strconcat(MASTER_SSL_KEY_PASSWORD_ENV"=",
ssl_manual_key_password, NULL));
}
if (service->type == SERVICE_TYPE_ANVIL &&
service_anvil_global->restarted)
env_put("ANVIL_RESTARTED=1");
}
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)
{
static unsigned int uid_counter = 0;
struct service_process *process;
unsigned int uid = ++uid_counter;
const char *hostdomain;
pid_t pid;
bool process_forked;
i_assert(service->status_fd[0] != -1);
if (service->to_throttle != NULL) {
/* throttling service, don't create new processes */
return NULL;
}
if (service->list->destroying) {
/* these services are being destroyed, no point in creating
new processes now */
return NULL;
}
/* look this up before fork()ing so that it gets cached for all the
future lookups. */
hostdomain = my_hostdomain();
if (service->type == SERVICE_TYPE_ANVIL &&
service_anvil_global->pid != 0) {
pid = service_anvil_global->pid;
uid = service_anvil_global->uid;
process_forked = FALSE;
} else {
pid = fork();
process_forked = TRUE;
service->list->fork_counter++;
}
if (pid < 0) {
service_error(service, "fork() failed: %m");
return NULL;
}
if (pid == 0) {
/* child */
service_process_setup_environment(service, uid, hostdomain);
service_reopen_inet_listeners(service);
service_dup_fds(service);
drop_privileges(service);
process_exec(service->executable, NULL);
}
i_assert(hash_table_lookup(service_pids, POINTER_CAST(pid)) == NULL);
process = i_new(struct service_process, 1);
process->service = service;
process->refcount = 1;
process->pid = pid;
process->uid = uid;
if (process_forked) {
process->to_status =
timeout_add(SERVICE_FIRST_STATUS_TIMEOUT_SECS * 1000,
service_process_status_timeout, process);
}
process->available_count = service->client_limit;
service->process_count++;
service->process_avail++;
DLLIST_PREPEND(&service->processes, process);
service_list_ref(service->list);
hash_table_insert(service_pids, POINTER_CAST(process->pid), process);
if (service->type == SERVICE_TYPE_ANVIL && process_forked)
service_anvil_process_created(process);
return process;
}
void service_process_destroy(struct service_process *process)
{
struct service *service = process->service;
struct service_list *service_list = service->list;
DLLIST_REMOVE(&service->processes, process);
hash_table_remove(service_pids, POINTER_CAST(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);
if (process->to_idle != NULL)
timeout_remove(&process->to_idle);
if (service->list->log_byes != NULL)
service_process_notify_add(service->list->log_byes, process);
process->destroyed = TRUE;
service_process_unref(process);
if (service->process_count < service->process_limit &&
service->type == SERVICE_TYPE_LOGIN)
service_login_notify(service, FALSE);
service_list_unref(service_list);
}
void service_process_ref(struct service_process *process)
{
i_assert(process->refcount > 0);
process->refcount++;
}
void service_process_unref(struct service_process *process)
{
i_assert(process->refcount > 0);
if (--process->refcount > 0)
return;
i_assert(process->destroyed);
i_free(process);
}
static const char *
get_exit_status_message(struct service *service, enum fatal_exit_status status)
{
string_t *str;
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:
str = t_str_new(128);
str_append(str, "Out of memory");
if (service->vsz_limit != 0) {
str_printfa(str, " (service %s { vsz_limit=%u MB }, "
"you may need to increase it)",
service->set->name,
(unsigned int)(service->vsz_limit/1024/1024));
}
if (getenv("CORE_OUTOFMEM") == NULL)
str_append(str, " - set CORE_OUTOFMEM=1 environment to get core dump");
return str_c(str);
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;
}
#ifndef HAVE_PR_SET_DUMPABLE
if (!service->set->drop_priv_before_exec && service->uid != 0) {
str_printfa(str, " (core not dumped - set service %s "
"{ drop_priv_before_exec=yes })",
service->set->name);
return;
}
if (*service->set->privileged_group != '\0' && service->uid != 0) {
str_printfa(str, " (core not dumped - service %s "
"{ privileged_group } prevented it)",
service->set->name);
return;
}
#else
if (!service->set->login_dump_core &&
service->type == SERVICE_TYPE_LOGIN) {
str_printfa(str, " (core not dumped - add -D parameter to "
"service %s { executable }", service->set->name);
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 (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 %s %s\n",
process->service->log_process_internal_fd,
dec2str(process->pid),
default_fatal ? "DEFAULT-FATAL" : "FATAL", 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;
}