summaryrefslogtreecommitdiffstats
path: root/src/master/service.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/master/service.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/master/service.c752
1 files changed, 752 insertions, 0 deletions
diff --git a/src/master/service.c b/src/master/service.c
new file mode 100644
index 0000000..74e6fa5
--- /dev/null
+++ b/src/master/service.c
@@ -0,0 +1,752 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "ioloop.h"
+#include "array.h"
+#include "aqueue.h"
+#include "hash.h"
+#include "str.h"
+#include "net.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "service.h"
+#include "service-anvil.h"
+#include "service-process.h"
+#include "service-monitor.h"
+
+#include <unistd.h>
+#include <signal.h>
+
+#define SERVICE_DIE_TIMEOUT_MSECS (1000*6)
+#define SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS 2
+
+HASH_TABLE_TYPE(pid_process) service_pids;
+
+void service_error(struct service *service, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ i_error("service(%s): %s", service->set->name,
+ t_strdup_vprintf(format, args));
+ va_end(args);
+}
+
+static struct service_listener *
+service_create_file_listener(struct service *service,
+ enum service_listener_type type,
+ const struct file_listener_settings *set,
+ const char **error_r)
+{
+ struct service_listener *l;
+ const char *set_name;
+ gid_t gid;
+
+ l = p_new(service->list->pool, struct service_listener, 1);
+ l->service = service;
+ l->type = type;
+ l->fd = -1;
+ l->set.fileset.set = set;
+ l->name = strrchr(set->path, '/');
+ if (l->name != NULL)
+ l->name++;
+ else
+ l->name = set->path;
+
+ if (get_uidgid(set->user, &l->set.fileset.uid, &gid, error_r) < 0)
+ set_name = "user";
+ else if (get_gid(set->group, &l->set.fileset.gid, error_r) < 0)
+ set_name = "group";
+ else
+ return l;
+
+ *error_r = t_strdup_printf(
+ "%s (See service %s { %s_listener %s { %s } } setting)",
+ *error_r, service->set->name,
+ type == SERVICE_LISTENER_UNIX ? "unix" : "fifo",
+ set->path, set_name);
+ return NULL;
+}
+
+static int
+resolve_ip(const char *address, const struct ip_addr **ips_r,
+ unsigned int *ips_count_r, const char **error_r)
+{
+ struct ip_addr *ip_list;
+ unsigned int ips_count;
+ int ret;
+
+ if (address == NULL || strcmp(address, "*") == 0) {
+ /* IPv4 any */
+ ip_list = t_new(struct ip_addr, 1);
+ *ip_list = net_ip4_any;
+ *ips_r = ip_list;
+ *ips_count_r = 1;
+ return 0;
+ }
+
+ if (strcmp(address, "::") == 0 || strcmp(address, "[::]") == 0) {
+ /* IPv6 any */
+ ip_list = t_new(struct ip_addr, 1);
+ *ip_list = net_ip6_any;
+ *ips_r = ip_list;
+ *ips_count_r = 1;
+ return 0;
+ }
+
+ /* Return the first IP if there happens to be multiple. */
+ ret = net_gethostbyname(address, &ip_list, &ips_count);
+ if (ret != 0) {
+ *error_r = t_strdup_printf("Can't resolve address %s: %s",
+ address, net_gethosterror(ret));
+ return -1;
+ }
+
+ if (ips_count < 1) {
+ *error_r = t_strdup_printf("No IPs for address: %s", address);
+ return -1;
+ }
+
+ *ips_r = ip_list;
+ *ips_count_r = ips_count;
+ return 0;
+}
+
+static struct service_listener *
+service_create_one_inet_listener(struct service *service,
+ const struct inet_listener_settings *set,
+ const char *address, const struct ip_addr *ip)
+{
+ struct service_listener *l;
+
+ i_assert(set->port != 0);
+
+ l = p_new(service->list->pool, struct service_listener, 1);
+ l->service = service;
+ l->type = SERVICE_LISTENER_INET;
+ l->fd = -1;
+ l->set.inetset.set = set;
+ l->set.inetset.ip = *ip;
+ l->inet_address = p_strdup(service->list->pool, address);
+ l->name = set->name;
+
+ return l;
+}
+
+static int
+service_create_inet_listeners(struct service *service,
+ const struct inet_listener_settings *set,
+ const char **error_r)
+{
+ static struct service_listener *l;
+ const char *const *tmp, *addresses;
+ const struct ip_addr *ips;
+ unsigned int i, ips_count;
+ bool ssl_disabled = strcmp(service->set->master_set->ssl, "no") == 0;
+
+ if (set->port == 0) {
+ /* disabled */
+ return 0;
+ }
+
+ if (*set->address != '\0')
+ addresses = set->address;
+ else {
+ /* use the default listen address */
+ addresses = service->set->master_set->listen;
+ }
+
+ tmp = t_strsplit_spaces(addresses, ", ");
+ for (; *tmp != NULL; tmp++) {
+ const char *address = *tmp;
+
+ if (set->ssl && ssl_disabled)
+ continue;
+
+ if (resolve_ip(address, &ips, &ips_count, error_r) < 0)
+ return -1;
+
+ for (i = 0; i < ips_count; i++) {
+ l = service_create_one_inet_listener(service, set,
+ address, &ips[i]);
+ array_push_back(&service->listeners, &l);
+ }
+ service->have_inet_listeners = TRUE;
+ }
+ return 0;
+}
+
+static int service_get_groups(const char *groups, pool_t pool,
+ const char **gids_r, const char **error_r)
+{
+ const char *const *tmp;
+ string_t *str;
+ gid_t gid;
+
+ str = t_str_new(64);
+ for (tmp = t_strsplit(groups, ","); *tmp != NULL; tmp++) {
+ if (get_gid(*tmp, &gid, error_r) < 0)
+ return -1;
+
+ if (str_len(str) > 0)
+ str_append_c(str, ',');
+ str_append(str, dec2str(gid));
+ }
+ *gids_r = p_strdup(pool, str_c(str));
+ return 0;
+}
+
+static struct service *
+service_create(pool_t pool, const struct service_settings *set,
+ struct service_list *service_list, const char **error_r)
+{
+ struct file_listener_settings *const *unix_listeners;
+ struct file_listener_settings *const *fifo_listeners;
+ struct inet_listener_settings *const *inet_listeners;
+ struct service *service;
+ struct service_listener *l;
+ unsigned int i, unix_count, fifo_count, inet_count;
+
+ service = p_new(pool, struct service, 1);
+ service->list = service_list;
+ service->set = set;
+ service->throttle_msecs = SERVICE_STARTUP_FAILURE_THROTTLE_MIN_MSECS;
+
+ service->client_limit = set->client_limit != 0 ? set->client_limit :
+ set->master_set->default_client_limit;
+ if (set->service_count > 0 &&
+ service->client_limit > set->service_count)
+ service->client_limit = set->service_count;
+
+ service->vsz_limit = set->vsz_limit != UOFF_T_MAX ? set->vsz_limit :
+ set->master_set->default_vsz_limit;
+ service->idle_kill = set->idle_kill != 0 ? set->idle_kill :
+ set->master_set->default_idle_kill;
+ service->type = service->set->parsed_type;
+
+ if (set->process_limit == 0) {
+ /* use default */
+ service->process_limit =
+ set->master_set->default_process_limit;
+ } else {
+ service->process_limit = set->process_limit;
+ }
+
+ /* default gid to user's primary group */
+ if (get_uidgid(set->user, &service->uid, &service->gid, error_r) < 0) {
+ switch (set->user_default) {
+ case SERVICE_USER_DEFAULT_NONE:
+ *error_r = t_strdup_printf(
+ "%s (See service %s { user } setting)",
+ *error_r, set->name);
+ break;
+ case SERVICE_USER_DEFAULT_INTERNAL:
+ *error_r = t_strconcat(*error_r,
+ " (See default_internal_user setting)", NULL);
+ break;
+ case SERVICE_USER_DEFAULT_LOGIN:
+ *error_r = t_strconcat(*error_r,
+ " (See default_login_user setting)", NULL);
+ break;
+ }
+ return NULL;
+ }
+ if (*set->group != '\0') {
+ if (get_gid(set->group, &service->gid, error_r) < 0) {
+ *error_r = t_strdup_printf(
+ "%s (See service %s { group } setting)",
+ *error_r, set->name);
+ return NULL;
+ }
+ }
+ if (get_gid(set->privileged_group, &service->privileged_gid,
+ error_r) < 0) {
+ *error_r = t_strdup_printf(
+ "%s (See service %s { privileged_group } setting)",
+ *error_r, set->name);
+ return NULL;
+ }
+
+ if (*set->extra_groups != '\0') {
+ if (service_get_groups(set->extra_groups, pool,
+ &service->extra_gids, error_r) < 0) {
+ *error_r = t_strdup_printf(
+ "%s (See service %s { extra_groups } setting)",
+ *error_r, set->name);
+ return NULL;
+ }
+ }
+
+ /* set these later, so if something fails we don't have to worry about
+ closing them */
+ service->log_fd[0] = -1;
+ service->log_fd[1] = -1;
+ service->status_fd[0] = -1;
+ service->status_fd[1] = -1;
+ service->master_dead_pipe_fd[0] = -1;
+ service->master_dead_pipe_fd[1] = -1;
+ service->log_process_internal_fd = -1;
+ service->login_notify_fd = -1;
+
+ if (service->type == SERVICE_TYPE_ANVIL) {
+ service->status_fd[0] = service_anvil_global->status_fd[0];
+ service->status_fd[1] = service_anvil_global->status_fd[1];
+ }
+
+ if (array_is_created(&set->unix_listeners))
+ unix_listeners = array_get(&set->unix_listeners, &unix_count);
+ else {
+ unix_listeners = NULL;
+ unix_count = 0;
+ }
+ if (array_is_created(&set->fifo_listeners))
+ fifo_listeners = array_get(&set->fifo_listeners, &fifo_count);
+ else {
+ fifo_listeners = NULL;
+ fifo_count = 0;
+ }
+ if (array_is_created(&set->inet_listeners))
+ inet_listeners = array_get(&set->inet_listeners, &inet_count);
+ else {
+ inet_listeners = NULL;
+ inet_count = 0;
+ }
+
+ if (unix_count == 0 && service->type == SERVICE_TYPE_CONFIG) {
+ *error_r = "Service must have unix listeners";
+ return NULL;
+ }
+
+ p_array_init(&service->listeners, pool,
+ unix_count + fifo_count + inet_count);
+
+ for (i = 0; i < unix_count; i++) {
+ if (unix_listeners[i]->mode == 0) {
+ /* disabled */
+ continue;
+ }
+
+ l = service_create_file_listener(service, SERVICE_LISTENER_UNIX,
+ unix_listeners[i], error_r);
+ if (l == NULL)
+ return NULL;
+ array_push_back(&service->listeners, &l);
+ }
+ for (i = 0; i < fifo_count; i++) {
+ if (fifo_listeners[i]->mode == 0) {
+ /* disabled */
+ continue;
+ }
+
+ l = service_create_file_listener(service, SERVICE_LISTENER_FIFO,
+ fifo_listeners[i], error_r);
+ if (l == NULL)
+ return NULL;
+ array_push_back(&service->listeners, &l);
+ }
+ for (i = 0; i < inet_count; i++) {
+ if (service_create_inet_listeners(service, inet_listeners[i],
+ error_r) < 0)
+ return NULL;
+ }
+
+ service->executable = set->executable;
+ if (access(t_strcut(service->executable, ' '), X_OK) < 0) {
+ *error_r = t_strdup_printf("access(%s) failed: %m",
+ t_strcut(service->executable, ' '));
+ return NULL;
+ }
+ return service;
+}
+
+struct service *
+service_lookup(struct service_list *service_list, const char *name)
+{
+ struct service *service;
+
+ array_foreach_elem(&service_list->services, service) {
+ if (strcmp(service->set->name, name) == 0)
+ return service;
+ }
+ return NULL;
+}
+
+struct service *
+service_lookup_type(struct service_list *service_list, enum service_type type)
+{
+ struct service *service;
+
+ array_foreach_elem(&service_list->services, service) {
+ if (service->type == type)
+ return service;
+ }
+ return NULL;
+}
+
+static bool service_want(struct service_settings *set)
+{
+ char *const *proto;
+
+ if (*set->executable == '\0') {
+ /* silently allow service {} blocks for disabled extensions
+ (e.g. service managesieve {} block without pigeonhole
+ installed) */
+ return FALSE;
+ }
+
+ if (*set->protocol == '\0')
+ return TRUE;
+
+ for (proto = set->master_set->protocols_split; *proto != NULL; proto++) {
+ if (strcmp(*proto, set->protocol) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int
+services_create_real(const struct master_settings *set, pool_t pool,
+ struct service_list **services_r, const char **error_r)
+{
+ struct service_list *service_list;
+ struct service *service;
+ struct service_settings *const *service_settings;
+ const char *error;
+ unsigned int i, count;
+
+ service_list = p_new(pool, struct service_list, 1);
+ service_list->refcount = 1;
+ service_list->pool = pool;
+ service_list->service_set = master_service_settings_get(master_service);
+ service_list->set_pool = master_service_settings_detach(master_service);
+ service_list->set = set;
+ service_list->master_log_fd[0] = -1;
+ service_list->master_log_fd[1] = -1;
+ service_list->master_fd = -1;
+
+ service_settings = array_get(&set->services, &count);
+ p_array_init(&service_list->services, pool, count);
+
+ for (i = 0; i < count; i++) {
+ if (!service_want(service_settings[i]))
+ continue;
+ T_BEGIN {
+ service = service_create(pool, service_settings[i],
+ service_list, &error);
+ } T_END_PASS_STR_IF(service == NULL, &error);
+ if (service == NULL) {
+ *error_r = t_strdup_printf("service(%s) %s",
+ service_settings[i]->name, error);
+ return -1;
+ }
+
+ switch (service->type) {
+ case SERVICE_TYPE_LOG:
+ if (service_list->log != NULL) {
+ *error_r = "Multiple log services specified";
+ return -1;
+ }
+ service_list->log = service;
+ break;
+ case SERVICE_TYPE_CONFIG:
+ if (service_list->config != NULL) {
+ *error_r = "Multiple config services specified";
+ return -1;
+ }
+ service_list->config = service;
+ break;
+ case SERVICE_TYPE_ANVIL:
+ if (service_list->anvil != NULL) {
+ *error_r = "Multiple anvil services specified";
+ return -1;
+ }
+ service_list->anvil = service;
+ break;
+ default:
+ break;
+ }
+
+ array_push_back(&service_list->services, &service);
+ }
+
+ if (service_list->log == NULL) {
+ *error_r = "log service not specified";
+ return -1;
+ }
+
+ if (service_list->config == NULL) {
+ *error_r = "config process not specified";
+ return -1;
+ }
+
+ *services_r = service_list;
+ return 0;
+}
+
+int services_create(const struct master_settings *set,
+ struct service_list **services_r, const char **error_r)
+{
+ pool_t pool;
+
+ pool = pool_alloconly_create("services pool", 32768);
+ if (services_create_real(set, pool, services_r, error_r) < 0) {
+ pool_unref(&pool);
+ return -1;
+ }
+ return 0;
+}
+
+unsigned int service_signal(struct service *service, int signo,
+ unsigned int *uninitialized_count_r)
+{
+ struct service_process *process = service->processes;
+ unsigned int count = 0;
+
+ *uninitialized_count_r = 0;
+ for (; process != NULL; process = process->next) {
+ i_assert(process->service == service);
+
+ if (!SERVICE_PROCESS_IS_INITIALIZED(process) &&
+ signo != SIGKILL) {
+ /* too early to signal it */
+ *uninitialized_count_r += 1;
+ continue;
+ }
+
+ if (kill(process->pid, signo) == 0)
+ count++;
+ else if (errno != ESRCH) {
+ service_error(service, "kill(%s, %d) failed: %m",
+ dec2str(process->pid), signo);
+ }
+ }
+ if (count > 0 && signo != SIGUSR1) {
+ i_warning("Sent %s to %u %s processes",
+ signo == SIGTERM ? "SIGTERM" : "SIGKILL",
+ count, service->set->name);
+ }
+ return count;
+}
+
+static void service_login_notify_send(struct service *service)
+{
+ unsigned int uninitialized_count;
+
+ service->last_login_notify_time = ioloop_time;
+ timeout_remove(&service->to_login_notify);
+
+ service_signal(service, SIGUSR1, &uninitialized_count);
+}
+
+static void service_login_notify_timeout(struct service *service)
+{
+ service_login_notify_send(service);
+}
+
+void service_login_notify(struct service *service, bool all_processes_full)
+{
+ enum master_login_state state;
+ int diff;
+
+ if (service->last_login_full_notify == all_processes_full ||
+ service->login_notify_fd == -1)
+ return;
+
+ /* change the state always immediately. it's cheap. */
+ service->last_login_full_notify = all_processes_full;
+ state = all_processes_full ? MASTER_LOGIN_STATE_FULL :
+ MASTER_LOGIN_STATE_NONFULL;
+ if (lseek(service->login_notify_fd, state, SEEK_SET) < 0)
+ service_error(service, "lseek(notify fd) failed: %m");
+
+ /* but don't send signal to processes too often */
+ diff = ioloop_time - service->last_login_notify_time;
+ if (diff < SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS) {
+ if (service->to_login_notify != NULL)
+ return;
+
+ diff = (SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS - diff) * 1000;
+ service->to_login_notify =
+ timeout_add(diff, service_login_notify_timeout,
+ service);
+ } else {
+ service_login_notify_send(service);
+ }
+}
+
+static void services_kill_timeout(struct service_list *service_list)
+{
+ struct service *service, *log_service;
+ unsigned int service_uninitialized, uninitialized_count = 0;
+ unsigned int signal_count = 0;
+ int sig;
+
+ if (!service_list->sigterm_sent)
+ sig = SIGTERM;
+ else
+ sig = SIGKILL;
+ service_list->sigterm_sent = TRUE;
+
+ log_service = NULL;
+ array_foreach_elem(&service_list->services, service) {
+ if (service->type == SERVICE_TYPE_LOG)
+ log_service = service;
+ else {
+ signal_count += service_signal(service, sig,
+ &service_uninitialized);
+ uninitialized_count += service_uninitialized;
+ }
+ }
+ if (log_service == NULL) {
+ /* log service doesn't exist - shouldn't really happen */
+ } else if (signal_count > 0 || uninitialized_count > 0) {
+ /* kill log service later so the last remaining processes
+ can still have a chance of logging something */
+ } else {
+ if (!service_list->sigterm_sent_to_log)
+ sig = SIGTERM;
+ else
+ sig = SIGKILL;
+ service_list->sigterm_sent_to_log = TRUE;
+ signal_count += service_signal(log_service, sig, &service_uninitialized);
+ uninitialized_count += service_uninitialized;
+ }
+ if (signal_count > 0) {
+ string_t *str = t_str_new(128);
+ str_printfa(str, "Processes aren't dying after reload, "
+ "sent %s to %u processes.",
+ sig == SIGTERM ? "SIGTERM" : "SIGKILL", signal_count);
+ if (uninitialized_count > 0) {
+ str_printfa(str, " (%u processes still uninitialized)",
+ uninitialized_count);
+ }
+ i_warning("%s", str_c(str));
+ }
+}
+
+void services_destroy(struct service_list *service_list, bool wait)
+{
+ /* make sure we log if child processes died unexpectedly */
+ service_list->destroying = TRUE;
+ services_monitor_reap_children();
+
+ services_monitor_stop(service_list, wait);
+
+ if (service_list->refcount > 1 &&
+ service_list->service_set->shutdown_clients) {
+ service_list->to_kill =
+ timeout_add(SERVICE_DIE_TIMEOUT_MSECS,
+ services_kill_timeout, service_list);
+ }
+
+ service_list->destroyed = TRUE;
+ service_list_unref(service_list);
+}
+
+void service_list_ref(struct service_list *service_list)
+{
+ i_assert(service_list->refcount > 0);
+ service_list->refcount++;
+}
+
+void service_list_unref(struct service_list *service_list)
+{
+ struct service *service;
+ struct service_listener *listener;
+
+ i_assert(service_list->refcount > 0);
+ if (--service_list->refcount > 0)
+ return;
+
+ array_foreach_elem(&service_list->services, service) {
+ array_foreach_elem(&service->listeners, listener)
+ i_close_fd(&listener->fd);
+ }
+ i_close_fd(&service_list->master_fd);
+
+ timeout_remove(&service_list->to_kill);
+ pool_unref(&service_list->set_pool);
+ pool_unref(&service_list->pool);
+}
+
+const char *services_get_config_socket_path(struct service_list *service_list)
+{
+ struct service_listener *const *listeners;
+ unsigned int count;
+
+ listeners = array_get(&service_list->config->listeners, &count);
+ i_assert(count > 0);
+ return listeners[0]->set.fileset.set->path;
+}
+
+static void service_throttle_timeout(struct service *service)
+{
+ timeout_remove(&service->to_throttle);
+ service_monitor_listen_start(service);
+}
+
+static void service_drop_listener_connections(struct service *service)
+{
+ struct service_listener *listener;
+ int fd;
+
+ array_foreach_elem(&service->listeners, listener) {
+ switch (listener->type) {
+ case SERVICE_LISTENER_UNIX:
+ case SERVICE_LISTENER_INET:
+ if (listener->fd == -1) {
+ /* already stopped listening */
+ break;
+ }
+ while ((fd = net_accept(listener->fd,
+ NULL, NULL)) >= 0)
+ i_close_fd(&fd);
+ break;
+ case SERVICE_LISTENER_FIFO:
+ break;
+ }
+ }
+}
+
+void service_throttle(struct service *service, unsigned int msecs)
+{
+ if (service->to_throttle != NULL || service->list->destroyed)
+ return;
+
+ if (service->processes == NULL)
+ service_drop_listener_connections(service);
+
+ service_monitor_listen_stop(service);
+ service->to_throttle = timeout_add(msecs, service_throttle_timeout,
+ service);
+}
+
+void services_throttle_time_sensitives(struct service_list *list,
+ unsigned int msecs)
+{
+ struct service *service;
+
+ array_foreach_elem(&list->services, service) {
+ if (service->type == SERVICE_TYPE_UNKNOWN)
+ service_throttle(service, msecs);
+ }
+}
+
+void service_pids_init(void)
+{
+ hash_table_create_direct(&service_pids, default_pool, 0);
+}
+
+void service_pids_deinit(void)
+{
+ struct hash_iterate_context *iter;
+ void *key;
+ struct service_process *process;
+
+ /* free all child process information */
+ iter = hash_table_iterate_init(service_pids);
+ while (hash_table_iterate(iter, service_pids, &key, &process))
+ service_process_destroy(process);
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&service_pids);
+}