master-service.c revision 7f1b897201d80c83c96b0d663f2a14c517d48f14
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2005-2015 Dovecot authors, see the included COPYING file */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "lib.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "lib-signals.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "ioloop.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "abspath.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "array.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "strescape.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "env-util.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "home-expand.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "process-title.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "restrict-access.h"
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen#include "fd-close-on-exec.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "settings-parser.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "syslog-util.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "master-instance.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "master-login.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "master-service-ssl.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include "master-service-private.h"
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen#include "master-service-settings.h"
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include <stdlib.h>
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen#include <unistd.h>
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include <sys/stat.h>
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#include <syslog.h>
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen#define DEFAULT_CONFIG_FILE_PATH SYSCONFDIR"/dovecot.conf"
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen/* getenv(MASTER_CONFIG_FILE_ENV) provides path to configuration file/socket */
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen#define MASTER_CONFIG_FILE_ENV "CONFIG_FILE"
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen/* getenv(MASTER_DOVECOT_VERSION_ENV) provides master's version number */
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen#define MASTER_DOVECOT_VERSION_ENV "DOVECOT_VERSION"
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen/* when we're full of connections, how often to check if login state has
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen changed. we normally notice it immediately because of a signal, so this is
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen just a fallback against race conditions. */
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen#define MASTER_SERVICE_STATE_CHECK_MSECS 1000
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen/* If die callback hasn't managed to stop the service for this many seconds,
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen force it. */
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen#define MASTER_SERVICE_DIE_TIMEOUT_MSECS (30*1000)
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainenstruct master_service *master_service;
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainenstatic void master_service_io_listeners_close(struct master_service *service);
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainenstatic void master_service_refresh_login_state(struct master_service *service);
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainenconst char *master_service_getopt_string(void)
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen{
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen return "c:i:ko:OL";
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen}
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainenstatic void sig_die(const siginfo_t *si, void *context)
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen{
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen struct master_service *service = context;
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen /* SIGINT comes either from master process or from keyboard. we don't
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen want to log it in either case.*/
a2583397219ebbb877fcf84107f0ca7f56362760Timo Sirainen if (si->si_signo != SIGINT) {
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen i_warning("Killed with signal %d (by pid=%s uid=%s code=%s)",
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen si->si_signo, dec2str(si->si_pid),
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen dec2str(si->si_uid),
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen lib_signal_code_to_str(si->si_signo, si->si_code));
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen } else if ((service->flags & MASTER_SERVICE_FLAG_NO_IDLE_DIE) != 0) {
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen /* never die when idling */
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen return;
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen } else if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen /* SIGINT came from master. die only if we're not handling
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen any clients currently. */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (service->master_status.available_count !=
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->total_available_count)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch return;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (service->idle_die_callback != NULL &&
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch !service->idle_die_callback())
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch return;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch }
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->killed = TRUE;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch io_loop_stop(service->ioloop);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch}
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Boschstatic void
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Boschsig_state_changed(const siginfo_t *si ATTR_UNUSED, void *context)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch{
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen struct master_service *service = context;
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch master_service_refresh_login_state(service);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch}
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Boschstatic void master_service_verify_version_string(struct master_service *service)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch{
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (service->version_string != NULL &&
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch strcmp(service->version_string, PACKAGE_VERSION) != 0) {
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_fatal("Dovecot version mismatch: "
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch "Master is v%s, %s is v"PACKAGE_VERSION" "
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen "(if you don't care, set version_ignore=yes)",
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen service->version_string, service->name);
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen }
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch}
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Boschstruct master_service *
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Boschmaster_service_init(const char *name, enum master_service_flags flags,
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch int *argc, char **argv[], const char *getopt_str)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch{
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch struct master_service *service;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch const char *value;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch unsigned int count;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_assert(name != NULL);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#ifdef DEBUG
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (getenv("GDB") == NULL &&
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch (flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch int count;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch value = getenv("SOCKET_COUNT");
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch count = value == NULL ? 0 : atoi(value);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch fd_debug_verify_leaks(MASTER_LISTEN_FD_FIRST + count, 1024);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch }
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch#endif
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* make sure we can dump core, at least until
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch privileges are dropped. (i'm not really sure why this
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch is needed, because doing the same just before exec
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch doesn't help, and exec shouldn't affect this with
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch non-setuid/gid binaries..) */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch restrict_access_allow_coredumps(TRUE);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch }
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* NOTE: we start rooted, so keep the code minimal until
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch restrict_access_by_env() is called */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch lib_init();
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* Set a logging prefix temporarily. This will be ignored once the log
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch is properly initialized */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_set_failure_prefix("%s(init): ", name);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* ignore these signals as early as possible */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch lib_signals_init();
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch lib_signals_ignore(SIGPIPE, TRUE);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch lib_signals_ignore(SIGALRM, FALSE);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (getenv(MASTER_UID_ENV) == NULL)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch flags |= MASTER_SERVICE_FLAG_STANDALONE;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch process_title_init(argv);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service = i_new(struct master_service, 1);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->argc = *argc;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->argv = *argv;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->name = i_strdup(name);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* keep getopt_str first in case it contains "+" */
8409959d66804dc963bc6fcdcc9a01da0d56a978Timo Sirainen service->getopt_str = *getopt_str == '\0' ?
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_strdup(master_service_getopt_string()) :
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_strconcat(getopt_str, master_service_getopt_string(), NULL);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->flags = flags;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->ioloop = io_loop_create();
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->service_count_left = UINT_MAX;
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen service->config_fd = -1;
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->config_path = i_strdup(getenv(MASTER_CONFIG_FILE_ENV));
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (service->config_path == NULL)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->config_path = i_strdup(DEFAULT_CONFIG_FILE_PATH);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch else
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->config_path_from_master = TRUE;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->version_string = getenv(MASTER_DOVECOT_VERSION_ENV);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->socket_count = 1;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch } else {
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->version_string = PACKAGE_VERSION;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch }
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch value = getenv("SOCKET_COUNT");
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (value != NULL)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->socket_count = atoi(value);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch value = getenv("SSL_SOCKET_COUNT");
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (value != NULL)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->ssl_socket_count = atoi(value);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch value = getenv("SOCKET_NAMES");
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (value != NULL) {
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->listener_names =
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch p_strsplit_tabescaped(default_pool, value);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->listener_names_count =
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch str_array_length((void *)service->listener_names);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch }
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->want_ssl_settings = service->ssl_socket_count > 0 ||
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch (flags & MASTER_SERVICE_FLAG_USE_SSL_SETTINGS) != 0;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* set up some kind of logging until we know exactly how and where
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch we want to log */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (getenv("LOG_SERVICE") != NULL)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_set_failure_internal();
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (getenv("USER") != NULL)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_set_failure_prefix("%s(%s): ", name, getenv("USER"));
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch else
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_set_failure_prefix("%s: ", name);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* initialize master_status structure */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch value = getenv(MASTER_UID_ENV);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (value == NULL ||
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch str_to_uint(value, &service->master_status.uid) < 0)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_fatal(MASTER_UID_ENV" missing");
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->master_status.pid = getpid();
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* set the default limit */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch value = getenv(MASTER_CLIENT_LIMIT_ENV);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (value == NULL || str_to_uint(value, &count) < 0 ||
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch count == 0)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch i_fatal(MASTER_CLIENT_LIMIT_ENV" missing");
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch master_service_set_client_limit(service, count);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* seve the process limit */
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch value = getenv(MASTER_PROCESS_LIMIT_ENV);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (value != NULL && str_to_uint(value, &count) == 0 &&
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen count > 0)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->process_limit = count;
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch value = getenv(MASTER_PROCESS_MIN_AVAIL_ENV);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (value != NULL && str_to_uint(value, &count) == 0 &&
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch count > 0)
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch service->process_min_avail = count;
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch /* set the default service count */
83773f7eb5e12e68f5efee7278bdab35d2ee84c0Timo Sirainen value = getenv(MASTER_SERVICE_COUNT_ENV);
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch if (value != NULL && str_to_uint(value, &count) == 0 &&
836e690c0e2d7494b8c0a6f4984dd13248841f2fStephan Bosch count > 0)
master_service_set_service_count(service, count);
/* set the idle kill timeout */
value = getenv(MASTER_SERVICE_IDLE_KILL_ENV);
if (value != NULL && str_to_uint(value, &count) == 0)
service->idle_kill_secs = count;
} else {
master_service_set_client_limit(service, 1);
master_service_set_service_count(service, 1);
}
if ((flags & MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN) != 0) {
/* since we're going to keep the config socket open anyway,
open it now so we can read settings even after privileges
are dropped. */
master_service_config_socket_try_open(service);
}
master_service_verify_version_string(service);
return service;
}
int master_getopt(struct master_service *service)
{
int c;
while ((c = getopt(service->argc, service->argv,
service->getopt_str)) > 0) {
if (!master_service_parse_option(service, c, optarg))
break;
}
return c;
}
void master_service_init_log(struct master_service *service,
const char *prefix)
{
const char *path, *timestamp;
if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0 &&
(service->flags & MASTER_SERVICE_FLAG_DONT_LOG_TO_STDERR) == 0) {
timestamp = getenv("LOG_STDERR_TIMESTAMP");
if (timestamp != NULL)
i_set_failure_timestamp_format(timestamp);
i_set_failure_file("/dev/stderr", "");
return;
}
if (getenv("LOG_SERVICE") != NULL && !service->log_directly) {
/* logging via log service */
i_set_failure_internal();
i_set_failure_prefix("%s", prefix);
return;
}
if (service->set == NULL) {
i_set_failure_file("/dev/stderr", prefix);
return;
}
if (strcmp(service->set->log_path, "syslog") != 0) {
/* error logging goes to file or stderr */
path = home_expand(service->set->log_path);
i_set_failure_file(path, prefix);
}
if (strcmp(service->set->log_path, "syslog") == 0 ||
strcmp(service->set->info_log_path, "syslog") == 0 ||
strcmp(service->set->debug_log_path, "syslog") == 0) {
/* something gets logged to syslog */
int facility;
if (!syslog_facility_find(service->set->syslog_facility,
&facility))
facility = LOG_MAIL;
i_set_failure_syslog("dovecot", LOG_NDELAY, facility);
i_set_failure_prefix("%s", prefix);
if (strcmp(service->set->log_path, "syslog") != 0) {
/* set error handlers back to file */
i_set_fatal_handler(default_fatal_handler);
i_set_error_handler(default_error_handler);
}
}
if (*service->set->info_log_path != '\0' &&
strcmp(service->set->info_log_path, "syslog") != 0) {
path = home_expand(service->set->info_log_path);
if (*path != '\0')
i_set_info_file(path);
}
if (*service->set->debug_log_path != '\0' &&
strcmp(service->set->debug_log_path, "syslog") != 0) {
path = home_expand(service->set->debug_log_path);
if (*path != '\0')
i_set_debug_file(path);
}
i_set_failure_timestamp_format(service->set->log_timestamp);
}
void master_service_set_die_with_master(struct master_service *service,
bool set)
{
service->die_with_master = set;
}
void master_service_set_die_callback(struct master_service *service,
void (*callback)(void))
{
service->die_callback = callback;
}
void master_service_set_idle_die_callback(struct master_service *service,
bool (*callback)(void))
{
service->idle_die_callback = callback;
}
static bool get_instance_config(const char *name, const char **config_path_r)
{
struct master_instance_list *list;
const struct master_instance *inst;
const char *instance_path, *path;
/* note that we don't have any settings yet. we're just finding out
which dovecot.conf we even want to read! so we must use the
hardcoded state_dir path. */
instance_path = t_strconcat(PKG_STATEDIR"/"MASTER_INSTANCE_FNAME, NULL);
list = master_instance_list_init(instance_path);
inst = master_instance_list_find_by_name(list, name);
if (inst != NULL) {
path = t_strdup_printf("%s/dovecot.conf", inst->base_dir);
if (t_readlink(path, config_path_r) < 0)
i_fatal("readlink(%s) failed: %m", path);
}
master_instance_list_deinit(&list);
return inst != NULL;
}
bool master_service_parse_option(struct master_service *service,
int opt, const char *arg)
{
const char *path;
switch (opt) {
case 'c':
service->config_path = i_strdup(arg);
service->config_path_changed_with_param = TRUE;
break;
case 'i':
if (!get_instance_config(arg, &path))
i_fatal("Unknown instance name: %s", arg);
service->config_path = i_strdup(path);
service->config_path_changed_with_param = TRUE;
break;
case 'k':
service->keep_environment = TRUE;
break;
case 'o':
if (!array_is_created(&service->config_overrides))
i_array_init(&service->config_overrides, 16);
array_append(&service->config_overrides, &arg, 1);
break;
case 'O':
service->flags |= MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS;
break;
case 'L':
service->log_directly = TRUE;
break;
default:
return FALSE;
}
return TRUE;
}
static void master_service_error(struct master_service *service)
{
master_service_stop_new_connections(service);
if (service->master_status.available_count ==
service->total_available_count || service->die_with_master) {
if (service->die_callback == NULL)
master_service_stop(service);
else {
service->to_die =
timeout_add(MASTER_SERVICE_DIE_TIMEOUT_MSECS,
master_service_stop,
service);
service->die_callback();
}
}
}
static void master_status_error(struct master_service *service)
{
/* status fd is a write-only pipe, so if we're here it means the
master wants us to die (or died itself). don't die until all
service connections are finished. */
io_remove(&service->io_status_error);
/* the log fd may also be closed already, don't die when trying to
log later */
i_set_failure_ignore_errors(TRUE);
master_service_error(service);
}
void master_service_init_finish(struct master_service *service)
{
enum libsig_flags sigint_flags = LIBSIG_FLAG_DELAYED;
struct stat st;
/* set default signal handlers */
if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) == 0)
sigint_flags |= LIBSIG_FLAG_RESTART;
lib_signals_set_handler(SIGINT, sigint_flags, sig_die, service);
lib_signals_set_handler(SIGTERM, LIBSIG_FLAG_DELAYED, sig_die, service);
if ((service->flags & MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE) != 0) {
lib_signals_set_handler(SIGUSR1, LIBSIG_FLAGS_SAFE,
sig_state_changed, service);
}
if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
if (fstat(MASTER_STATUS_FD, &st) < 0 || !S_ISFIFO(st.st_mode))
i_fatal("Must be started by dovecot master process");
/* start listening errors for status fd, it means master died */
service->io_status_error = io_add(MASTER_DEAD_FD, IO_ERROR,
master_status_error, service);
}
master_service_io_listeners_add(service);
if (service->want_ssl_settings &&
(service->flags & MASTER_SERVICE_FLAG_NO_SSL_INIT) == 0)
master_service_ssl_ctx_init(service);
if ((service->flags & MASTER_SERVICE_FLAG_STD_CLIENT) != 0) {
/* we already have a connection to be served */
service->master_status.available_count--;
}
master_status_update(service);
}
void master_service_env_clean(void)
{
const char *value = getenv(DOVECOT_PRESERVE_ENVS_ENV);
if (value == NULL || *value == '\0')
env_clean();
else T_BEGIN {
value = t_strconcat(value, " "DOVECOT_PRESERVE_ENVS_ENV, NULL);
env_clean_except(t_strsplit_spaces(value, " "));
} T_END;
}
void master_service_set_client_limit(struct master_service *service,
unsigned int client_limit)
{
unsigned int used;
i_assert(service->master_status.available_count ==
service->total_available_count);
used = service->total_available_count -
service->master_status.available_count;
i_assert(client_limit >= used);
service->total_available_count = client_limit;
service->master_status.available_count = client_limit - used;
}
unsigned int master_service_get_client_limit(struct master_service *service)
{
return service->total_available_count;
}
unsigned int master_service_get_process_limit(struct master_service *service)
{
return service->process_limit;
}
unsigned int master_service_get_process_min_avail(struct master_service *service)
{
return service->process_min_avail;
}
unsigned int master_service_get_idle_kill_secs(struct master_service *service)
{
return service->idle_kill_secs;
}
void master_service_set_service_count(struct master_service *service,
unsigned int count)
{
unsigned int used;
used = service->total_available_count -
service->master_status.available_count;
i_assert(count >= used);
if (service->total_available_count > count) {
service->total_available_count = count;
service->master_status.available_count = count - used;
}
service->service_count_left = count;
}
unsigned int master_service_get_service_count(struct master_service *service)
{
return service->service_count_left;
}
unsigned int master_service_get_socket_count(struct master_service *service)
{
return service->socket_count;
}
const char *master_service_get_socket_name(struct master_service *service,
int listen_fd)
{
unsigned int i;
i_assert(listen_fd >= MASTER_LISTEN_FD_FIRST);
i = listen_fd - MASTER_LISTEN_FD_FIRST;
return i < service->listener_names_count ?
service->listener_names[i] : "";
}
void master_service_set_avail_overflow_callback(struct master_service *service,
void (*callback)(void))
{
service->avail_overflow_callback = callback;
}
const char *master_service_get_config_path(struct master_service *service)
{
return service->config_path;
}
const char *master_service_get_version_string(struct master_service *service)
{
return service->version_string;
}
const char *master_service_get_name(struct master_service *service)
{
return service->name;
}
void master_service_run(struct master_service *service,
master_service_connection_callback_t *callback)
{
service->callback = callback;
io_loop_run(service->ioloop);
service->callback = NULL;
}
void master_service_stop(struct master_service *service)
{
io_loop_stop(service->ioloop);
}
void master_service_stop_new_connections(struct master_service *service)
{
unsigned int current_count;
if (service->stopping)
return;
service->stopping = TRUE;
master_service_io_listeners_remove(service);
master_service_io_listeners_close(service);
/* make sure we stop after servicing current connections */
current_count = service->total_available_count -
service->master_status.available_count;
service->service_count_left = current_count;
service->total_available_count = current_count;
if (current_count == 0)
master_service_stop(service);
else {
/* notify master that we're not accepting any more
connections */
service->master_status.available_count = 0;
master_status_update(service);
}
if (service->login != NULL)
master_login_stop(service->login);
}
bool master_service_is_killed(struct master_service *service)
{
return service->killed;
}
bool master_service_is_master_stopped(struct master_service *service)
{
return service->io_status_error == NULL;
}
void master_service_anvil_send(struct master_service *service, const char *cmd)
{
ssize_t ret;
if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0)
return;
ret = write(MASTER_ANVIL_FD, cmd, strlen(cmd));
if (ret < 0) {
if (errno == EPIPE) {
/* anvil process was probably recreated, don't bother
logging an error about losing connection to it */
return;
}
i_error("write(anvil) failed: %m");
} else if (ret == 0)
i_error("write(anvil) failed: EOF");
else {
i_assert((size_t)ret == strlen(cmd));
}
}
void master_service_client_connection_created(struct master_service *service)
{
i_assert(service->master_status.available_count > 0);
service->master_status.available_count--;
master_status_update(service);
}
void master_service_client_connection_accept(struct master_service_connection *conn)
{
conn->accepted = TRUE;
}
void master_service_client_connection_destroyed(struct master_service *service)
{
/* we can listen again */
master_service_io_listeners_add(service);
i_assert(service->total_available_count > 0);
i_assert(service->service_count_left > 0);
if (service->service_count_left == service->total_available_count) {
service->total_available_count--;
service->service_count_left--;
} else {
if (service->service_count_left != UINT_MAX)
service->service_count_left--;
i_assert(service->master_status.available_count <
service->total_available_count);
service->master_status.available_count++;
}
if (service->service_count_left == 0) {
i_assert(service->master_status.available_count ==
service->total_available_count);
master_service_stop(service);
} else if ((service->io_status_error == NULL ||
service->listeners == NULL) &&
service->master_status.available_count ==
service->total_available_count) {
/* we've finished handling all clients, and
a) master has closed the connection
b) there are no listeners (std-client?) */
master_service_stop(service);
} else {
master_status_update(service);
}
}
static void master_service_set_login_state(struct master_service *service,
enum master_login_state state)
{
if (service->to_overflow_state != NULL)
timeout_remove(&service->to_overflow_state);
switch (state) {
case MASTER_LOGIN_STATE_NONFULL:
service->call_avail_overflow = FALSE;
if (service->master_status.available_count > 0)
return;
/* some processes should now be able to handle new connections,
although we can't. but there may be race conditions, so
make sure that we'll check again soon if the state has
changed to "full" without our knowledge. */
service->to_overflow_state =
timeout_add(MASTER_SERVICE_STATE_CHECK_MSECS,
master_service_refresh_login_state,
service);
return;
case MASTER_LOGIN_STATE_FULL:
/* make sure we're listening for more connections */
service->call_avail_overflow = TRUE;
master_service_io_listeners_add(service);
return;
}
i_error("Invalid master login state: %d", state);
}
static void master_service_refresh_login_state(struct master_service *service)
{
int ret;
ret = lseek(MASTER_LOGIN_NOTIFY_FD, 0, SEEK_CUR);
if (ret < 0)
i_error("lseek(login notify fd) failed: %m");
else
master_service_set_login_state(service, ret);
}
void master_service_close_config_fd(struct master_service *service)
{
if (service->config_fd != -1) {
if (close(service->config_fd) < 0)
i_error("close(master config fd) failed: %m");
service->config_fd = -1;
}
}
void master_service_deinit(struct master_service **_service)
{
struct master_service *service = *_service;
*_service = NULL;
master_service_io_listeners_remove(service);
master_service_ssl_ctx_deinit(service);
master_service_close_config_fd(service);
if (service->to_die != NULL)
timeout_remove(&service->to_die);
if (service->to_overflow_state != NULL)
timeout_remove(&service->to_overflow_state);
if (service->to_status != NULL)
timeout_remove(&service->to_status);
if (service->io_status_error != NULL)
io_remove(&service->io_status_error);
if (service->io_status_write != NULL)
io_remove(&service->io_status_write);
if (array_is_created(&service->config_overrides))
array_free(&service->config_overrides);
if (service->set_parser != NULL) {
settings_parser_deinit(&service->set_parser);
pool_unref(&service->set_pool);
}
lib_signals_deinit();
/* run atexit callbacks before destroying ioloop */
lib_atexit_run();
io_loop_destroy(&service->ioloop);
if (service->listener_names != NULL)
p_strsplit_free(default_pool, service->listener_names);
i_free(service->listeners);
i_free(service->getopt_str);
i_free(service->name);
i_free(service->config_path);
i_free(service);
lib_deinit();
}
static void master_service_listen(struct master_service_listener *l)
{
struct master_service *service = l->service;
struct master_service_connection conn;
if (service->master_status.available_count == 0) {
/* we are full. stop listening for now, unless overflow
callback destroys one of the existing connections */
if (service->call_avail_overflow &&
service->avail_overflow_callback != NULL)
service->avail_overflow_callback();
if (service->master_status.available_count == 0) {
master_service_io_listeners_remove(service);
return;
}
}
memset(&conn, 0, sizeof(conn));
conn.listen_fd = l->fd;
conn.fd = net_accept(l->fd, &conn.remote_ip, &conn.remote_port);
if (conn.fd < 0) {
struct stat st;
int orig_errno = errno;
if (conn.fd == -1)
return;
if (errno == ENOTSOCK) {
/* it's not a socket. should be a fifo. */
} else if (errno == EINVAL &&
(fstat(l->fd, &st) == 0 && S_ISFIFO(st.st_mode))) {
/* BSDI fails accept(fifo) with EINVAL. */
} else {
errno = orig_errno;
i_error("net_accept() failed: %m");
/* try again later after one of the existing
connections has died */
master_service_io_listeners_remove(service);
return;
}
/* use the "listener" as the connection fd and stop the
listener. */
conn.fd = l->fd;
conn.listen_fd = l->fd;
conn.fifo = TRUE;
io_remove(&l->io);
l->fd = -1;
}
conn.ssl = l->ssl;
conn.name = l->name;
net_set_nonblock(conn.fd, TRUE);
master_service_client_connection_created(service);
service->callback(&conn);
if (!conn.accepted) {
if (close(conn.fd) < 0)
i_error("close(service connection) failed: %m");
master_service_client_connection_destroyed(service);
} else if (conn.fifo) {
/* reading FIFOs stays open forever, don't count them
as real clients */
master_service_client_connection_destroyed(service);
}
if (service->master_status.available_count == 0 &&
service->service_count_left == 1) {
/* we're not going to accept any more connections after this.
go ahead and close the connection early. don't do this
before calling callback, because it may want to access
the listen_fd (e.g. to check socket permissions). */
i_assert(service->listeners != NULL);
master_service_io_listeners_remove(service);
master_service_io_listeners_close(service);
}
}
static void io_listeners_init(struct master_service *service)
{
unsigned int i;
if (service->socket_count == 0)
return;
service->listeners =
i_new(struct master_service_listener, service->socket_count);
for (i = 0; i < service->socket_count; i++) {
struct master_service_listener *l = &service->listeners[i];
l->service = service;
l->fd = MASTER_LISTEN_FD_FIRST + i;
l->name = i < service->listener_names_count ?
service->listener_names[i] : "";
if (i >= service->socket_count - service->ssl_socket_count)
l->ssl = TRUE;
}
}
void master_service_io_listeners_add(struct master_service *service)
{
unsigned int i;
if (service->stopping)
return;
if (service->listeners == NULL)
io_listeners_init(service);
for (i = 0; i < service->socket_count; i++) {
struct master_service_listener *l = &service->listeners[i];
if (l->io == NULL && l->fd != -1) {
l->io = io_add(MASTER_LISTEN_FD_FIRST + i, IO_READ,
master_service_listen, l);
}
}
}
void master_service_io_listeners_remove(struct master_service *service)
{
unsigned int i;
if (service->listeners != NULL) {
for (i = 0; i < service->socket_count; i++) {
if (service->listeners[i].io != NULL)
io_remove(&service->listeners[i].io);
}
}
}
void master_service_ssl_io_listeners_remove(struct master_service *service)
{
unsigned int i;
if (service->listeners != NULL) {
for (i = 0; i < service->socket_count; i++) {
if (service->listeners[i].io != NULL &&
service->listeners[i].ssl)
io_remove(&service->listeners[i].io);
}
}
}
static void master_service_io_listeners_close(struct master_service *service)
{
unsigned int i;
if (service->listeners != NULL) {
/* close via listeners. some fds might be pipes that are
currently handled as clients. we don't want to close them. */
for (i = 0; i < service->socket_count; i++) {
if (service->listeners[i].fd != -1) {
if (close(service->listeners[i].fd) < 0) {
i_error("close(listener %d) failed: %m",
service->listeners[i].fd);
}
service->listeners[i].fd = -1;
}
}
} else {
for (i = 0; i < service->socket_count; i++) {
int fd = MASTER_LISTEN_FD_FIRST + i;
if (close(fd) < 0)
i_error("close(listener %d) failed: %m", fd);
}
}
}
static bool master_status_update_is_important(struct master_service *service)
{
if (service->master_status.available_count == 0)
return TRUE;
if (!service->initial_status_sent)
return TRUE;
return FALSE;
}
void master_status_update(struct master_service *service)
{
ssize_t ret;
bool important_update;
if ((service->flags & MASTER_SERVICE_FLAG_UPDATE_PROCTITLE) != 0 &&
service->set != NULL && service->set->verbose_proctitle) T_BEGIN {
unsigned int used_count = service->total_available_count -
service->master_status.available_count;
process_title_set(t_strdup_printf("[%u connections]",
used_count));
} T_END;
important_update = master_status_update_is_important(service);
if (service->master_status.pid == 0 ||
service->master_status.available_count ==
service->last_sent_status_avail_count) {
/* a) closed, b) updating to same state */
if (service->to_status != NULL)
timeout_remove(&service->to_status);
if (service->io_status_write != NULL)
io_remove(&service->io_status_write);
return;
}
if (ioloop_time == service->last_sent_status_time &&
!important_update) {
/* don't spam master */
if (service->to_status != NULL)
timeout_reset(service->to_status);
else {
service->to_status =
timeout_add(1000, master_status_update,
service);
}
if (service->io_status_write != NULL)
io_remove(&service->io_status_write);
return;
}
if (service->to_status != NULL)
timeout_remove(&service->to_status);
ret = write(MASTER_STATUS_FD, &service->master_status,
sizeof(service->master_status));
if (ret == sizeof(service->master_status)) {
/* success */
if (service->io_status_write != NULL) {
/* delayed important update sent successfully */
io_remove(&service->io_status_write);
}
service->last_sent_status_time = ioloop_time;
service->last_sent_status_avail_count =
service->master_status.available_count;
service->initial_status_sent = TRUE;
} else if (ret >= 0) {
/* shouldn't happen? */
i_error("write(master_status_fd) returned %d", (int)ret);
service->master_status.pid = 0;
} else if (errno != EAGAIN) {
/* failure */
if (errno != EPIPE)
i_error("write(master_status_fd) failed: %m");
service->master_status.pid = 0;
} else if (important_update) {
/* reader is busy, but it's important to get this notification
through. send it when possible. */
if (service->io_status_write == NULL) {
service->io_status_write =
io_add(MASTER_STATUS_FD, IO_WRITE,
master_status_update, service);
}
}
}
bool version_string_verify(const char *line, const char *service_name,
unsigned major_version)
{
unsigned int minor_version;
return version_string_verify_full(line, service_name,
major_version, &minor_version);
}
bool version_string_verify_full(const char *line, const char *service_name,
unsigned major_version,
unsigned int *minor_version_r)
{
unsigned int service_name_len = strlen(service_name);
bool ret;
if (strncmp(line, "VERSION\t", 8) != 0)
return FALSE;
line += 8;
if (strncmp(line, service_name, service_name_len) != 0 ||
line[service_name_len] != '\t')
return FALSE;
line += service_name_len + 1;
T_BEGIN {
const char *p = strchr(line, '\t');
if (p == NULL)
ret = FALSE;
else {
ret = str_uint_equals(t_strdup_until(line, p),
major_version);
if (str_to_uint(p+1, minor_version_r) < 0)
ret = FALSE;
}
} T_END;
return ret;
}