diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/master/service.c | |
parent | Initial commit. (diff) | |
download | dovecot-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.c | 752 |
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); +} |