diff options
Diffstat (limited to 'src/auth/main.c')
-rw-r--r-- | src/auth/main.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/src/auth/main.c b/src/auth/main.c new file mode 100644 index 0000000..5f09fca --- /dev/null +++ b/src/auth/main.c @@ -0,0 +1,398 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" +#include "array.h" +#include "ioloop.h" +#include "net.h" +#include "lib-signals.h" +#include "restrict-access.h" +#include "child-wait.h" +#include "sql-api.h" +#include "module-dir.h" +#include "randgen.h" +#include "process-title.h" +#include "settings-parser.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "master-interface.h" +#include "dict.h" +#include "password-scheme.h" +#include "passdb-cache.h" +#include "mech.h" +#include "otp.h" +#include "mech-otp-common.h" +#include "auth.h" +#include "auth-penalty.h" +#include "auth-token.h" +#include "auth-request-handler.h" +#include "auth-request-stats.h" +#include "auth-worker-server.h" +#include "auth-worker-client.h" +#include "auth-master-connection.h" +#include "auth-client-connection.h" +#include "auth-policy.h" + +#include <unistd.h> +#include <sys/stat.h> + +#define AUTH_PENALTY_ANVIL_PATH "anvil-auth-penalty" + +enum auth_socket_type { + AUTH_SOCKET_UNKNOWN = 0, + AUTH_SOCKET_CLIENT, + AUTH_SOCKET_LOGIN_CLIENT, + AUTH_SOCKET_MASTER, + AUTH_SOCKET_USERDB, + AUTH_SOCKET_TOKEN, + AUTH_SOCKET_TOKEN_LOGIN +}; + +struct auth_socket_listener { + enum auth_socket_type type; + struct stat st; + char *path; +}; + +bool worker = FALSE, worker_restart_request = FALSE; +time_t process_start_time; +struct auth_penalty *auth_penalty; + +static pool_t auth_set_pool; +static struct module *modules = NULL; +static struct mechanisms_register *mech_reg; +static ARRAY(struct auth_socket_listener) listeners; + +void auth_refresh_proctitle(void) +{ + if (!global_auth_settings->verbose_proctitle || worker) + return; + + process_title_set(t_strdup_printf( + "[%u wait, %u passdb, %u userdb]", + auth_request_state_count[AUTH_REQUEST_STATE_NEW] + + auth_request_state_count[AUTH_REQUEST_STATE_MECH_CONTINUE] + + auth_request_state_count[AUTH_REQUEST_STATE_FINISHED], + auth_request_state_count[AUTH_REQUEST_STATE_PASSDB], + auth_request_state_count[AUTH_REQUEST_STATE_USERDB])); +} + +static const char *const *read_global_settings(void) +{ + struct master_service_settings_output set_output; + const char **services; + unsigned int i, count; + + auth_set_pool = pool_alloconly_create("auth settings", 8192); + global_auth_settings = + auth_settings_read(NULL, auth_set_pool, &set_output); + + /* strdup() the service names, because they're allocated from + set parser pool, and we'll later clear it. */ + count = str_array_length(set_output.specific_services); + services = p_new(auth_set_pool, const char *, count + 1); + for (i = 0; i < count; i++) { + services[i] = p_strdup(auth_set_pool, + set_output.specific_services[i]); + } + return services; +} + +static enum auth_socket_type +auth_socket_type_get(const char *path) +{ + const char *name, *suffix; + + name = strrchr(path, '/'); + if (name == NULL) + name = path; + else + name++; + + suffix = strrchr(name, '-'); + if (suffix == NULL) + suffix = name; + else + suffix++; + + if (strcmp(suffix, "login") == 0) + return AUTH_SOCKET_LOGIN_CLIENT; + else if (strcmp(suffix, "master") == 0) + return AUTH_SOCKET_MASTER; + else if (strcmp(suffix, "userdb") == 0) + return AUTH_SOCKET_USERDB; + else if (strcmp(suffix, "token") == 0) + return AUTH_SOCKET_TOKEN; + else if (strcmp(suffix, "tokenlogin") == 0) + return AUTH_SOCKET_TOKEN_LOGIN; + else + return AUTH_SOCKET_CLIENT; +} + +static void listeners_init(void) +{ + unsigned int i, n; + const char *path; + + i_array_init(&listeners, 8); + n = master_service_get_socket_count(master_service); + for (i = 0; i < n; i++) { + int fd = MASTER_LISTEN_FD_FIRST + i; + struct auth_socket_listener *l; + + l = array_idx_get_space(&listeners, fd); + if (net_getunixname(fd, &path) < 0) { + if (errno != ENOTSOCK) + i_fatal("getunixname(%d) failed: %m", fd); + /* not a unix socket, set its name and type lazily */ + } else { + l->type = auth_socket_type_get(path); + l->path = i_strdup(path); + if (l->type == AUTH_SOCKET_USERDB) { + if (stat(path, &l->st) < 0) + i_error("stat(%s) failed: %m", path); + } + } + } +} + +static bool auth_module_filter(const char *name, void *context ATTR_UNUSED) +{ + if (str_begins(name, "authdb_") || + str_begins(name, "mech_")) { + /* this is lazily loaded */ + return FALSE; + } + return TRUE; +} + +static void main_preinit(void) +{ + struct module_dir_load_settings mod_set; + const char *const *services; + + /* Load built-in SQL drivers (if any) */ + sql_drivers_init(); + sql_drivers_register_all(); + + /* Initialize databases so their configuration files can be readable + only by root. Also load all modules here. */ + passdbs_init(); + userdbs_init(); + /* init schemes before plugins are loaded */ + password_schemes_init(); + + services = read_global_settings(); + + i_zero(&mod_set); + mod_set.abi_version = DOVECOT_ABI_VERSION; + mod_set.require_init_funcs = TRUE; + mod_set.debug = global_auth_settings->debug; + mod_set.filter_callback = auth_module_filter; + + modules = module_dir_load(AUTH_MODULE_DIR, NULL, &mod_set); + module_dir_init(modules); + + if (!worker) + auth_penalty = auth_penalty_init(AUTH_PENALTY_ANVIL_PATH); + auth_request_stats_init(); + mech_init(global_auth_settings); + mech_reg = mech_register_init(global_auth_settings); + dict_drivers_register_builtin(); + auths_preinit(global_auth_settings, auth_set_pool, + mech_reg, services); + + listeners_init(); + if (!worker) + auth_token_init(); + + /* Password lookups etc. may require roots, allow it. */ + restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); + restrict_access_allow_coredumps(TRUE); +} + +void auth_module_load(const char *names) +{ + struct module_dir_load_settings mod_set; + + i_zero(&mod_set); + mod_set.abi_version = DOVECOT_ABI_VERSION; + mod_set.require_init_funcs = TRUE; + mod_set.debug = global_auth_settings->debug; + mod_set.ignore_missing = TRUE; + + modules = module_dir_load_missing(modules, AUTH_MODULE_DIR, names, + &mod_set); + module_dir_init(modules); +} + +static void main_init(void) +{ + process_start_time = ioloop_time; + + /* If auth caches aren't used, just ignore these signals */ + lib_signals_ignore(SIGHUP, TRUE); + lib_signals_ignore(SIGUSR2, TRUE); + + /* set proctitles before init()s, since they may set them to error */ + auth_refresh_proctitle(); + auth_worker_refresh_proctitle(""); + + child_wait_init(); + auth_worker_server_init(); + auths_init(); + auth_request_handler_init(); + auth_policy_init(); + + if (worker) { + /* workers have only a single connection from the master + auth process */ + master_service_set_client_limit(master_service, 1); + auth_worker_set_max_service_count( + master_service_get_service_count(master_service)); + /* make sure this process cycles if auth connection drops */ + master_service_set_service_count(master_service, 1); + } else { + /* caching is handled only by the main auth process */ + passdb_cache_init(global_auth_settings); + } +} + +static void main_deinit(void) +{ + struct auth_socket_listener *l; + + if (auth_penalty != NULL) { + /* cancel all pending anvil penalty lookups */ + auth_penalty_deinit(&auth_penalty); + } + /* deinit auth workers, which aborts pending requests */ + auth_worker_server_deinit(); + /* deinit passdbs and userdbs. it aborts any pending async requests. */ + auths_deinit(); + /* flush pending requests */ + auth_request_handler_deinit(); + /* there are no more auth requests */ + auths_free(); + dict_drivers_unregister_builtin(); + + auth_token_deinit(); + + auth_client_connections_destroy_all(); + auth_master_connections_destroy_all(); + auth_worker_connections_destroy_all(); + + auth_policy_deinit(); + mech_register_deinit(&mech_reg); + mech_otp_deinit(); + mech_deinit(global_auth_settings); + + /* allow modules to unregister their dbs/drivers/etc. before freeing + the whole data structures containing them. */ + module_dir_unload(&modules); + + userdbs_deinit(); + passdbs_deinit(); + passdb_cache_deinit(); + password_schemes_deinit(); + auth_request_stats_deinit(); + + sql_drivers_deinit(); + child_wait_deinit(); + + array_foreach_modifiable(&listeners, l) + i_free(l->path); + array_free(&listeners); + pool_unref(&auth_set_pool); +} + +static void worker_connected(struct master_service_connection *conn) +{ + if (auth_worker_has_client()) { + i_error("Auth workers can handle only a single client"); + return; + } + + master_service_client_connection_accept(conn); + (void)auth_worker_client_create(auth_default_service(), conn); +} + +static void client_connected(struct master_service_connection *conn) +{ + struct auth_socket_listener *l; + struct auth *auth; + + l = array_idx_modifiable(&listeners, conn->listen_fd); + if (l->type == AUTH_SOCKET_UNKNOWN) { + /* first connection from inet socket, figure out its type + from the listener name */ + l->type = auth_socket_type_get(conn->name); + l->path = i_strdup(conn->name); + } + auth = auth_default_service(); + switch (l->type) { + case AUTH_SOCKET_MASTER: + (void)auth_master_connection_create(auth, conn->fd, + l->path, NULL, FALSE); + break; + case AUTH_SOCKET_USERDB: + (void)auth_master_connection_create(auth, conn->fd, + l->path, &l->st, TRUE); + break; + case AUTH_SOCKET_LOGIN_CLIENT: + auth_client_connection_create(auth, conn->fd, TRUE, FALSE); + break; + case AUTH_SOCKET_CLIENT: + auth_client_connection_create(auth, conn->fd, FALSE, FALSE); + break; + case AUTH_SOCKET_TOKEN_LOGIN: + auth_client_connection_create(auth, conn->fd, TRUE, TRUE); + break; + case AUTH_SOCKET_TOKEN: + auth_client_connection_create(auth, conn->fd, FALSE, TRUE); + break; + default: + i_unreached(); + } + master_service_client_connection_accept(conn); +} + +static void auth_die(void) +{ + if (!worker) { + /* do nothing. auth clients should disconnect soon. */ + } else { + /* ask auth master to disconnect us */ + auth_worker_client_send_shutdown(); + } +} + +int main(int argc, char *argv[]) +{ + int c; + enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_NO_SSL_INIT; + + master_service = master_service_init("auth", service_flags, &argc, &argv, "w"); + master_service_init_log(master_service); + + while ((c = master_getopt(master_service)) > 0) { + switch (c) { + case 'w': + master_service_init_log_with_pid(master_service); + worker = TRUE; + break; + default: + return FATAL_DEFAULT; + } + } + + main_preinit(); + master_service_set_die_callback(master_service, auth_die); + main_init(); + master_service_init_finish(master_service); + master_service_run(master_service, worker ? worker_connected : + client_connected); + main_deinit(); + master_service_deinit(&master_service); + return 0; +} |