diff options
Diffstat (limited to 'src/auth/auth.c')
-rw-r--r-- | src/auth/auth.c | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/src/auth/auth.c b/src/auth/auth.c new file mode 100644 index 0000000..845c43c --- /dev/null +++ b/src/auth/auth.c @@ -0,0 +1,450 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" +#include "array.h" +#include "settings-parser.h" +#include "master-service-settings.h" +#include "mech.h" +#include "userdb.h" +#include "passdb.h" +#include "passdb-template.h" +#include "userdb-template.h" +#include "auth.h" + +struct event *auth_event; +struct event_category event_category_auth = { + .name = "auth", +}; + +static const struct auth_userdb_settings userdb_dummy_set = { + .name = "", + .driver = "static", + .args = "", + .default_fields = "", + .override_fields = "", + + .skip = "never", + .result_success = "return-ok", + .result_failure = "continue", + .result_internalfail = "continue", + + .auth_verbose = "default", +}; + +ARRAY_TYPE(auth) auths; + +static enum auth_passdb_skip auth_passdb_skip_parse(const char *str) +{ + if (strcmp(str, "never") == 0) + return AUTH_PASSDB_SKIP_NEVER; + if (strcmp(str, "authenticated") == 0) + return AUTH_PASSDB_SKIP_AUTHENTICATED; + if (strcmp(str, "unauthenticated") == 0) + return AUTH_PASSDB_SKIP_UNAUTHENTICATED; + i_unreached(); +} + +static enum auth_userdb_skip auth_userdb_skip_parse(const char *str) +{ + if (strcmp(str, "never") == 0) + return AUTH_USERDB_SKIP_NEVER; + if (strcmp(str, "found") == 0) + return AUTH_USERDB_SKIP_FOUND; + if (strcmp(str, "notfound") == 0) + return AUTH_USERDB_SKIP_NOTFOUND; + i_unreached(); +} + +static enum auth_db_rule auth_db_rule_parse(const char *str) +{ + if (strcmp(str, "return") == 0) + return AUTH_DB_RULE_RETURN; + if (strcmp(str, "return-ok") == 0) + return AUTH_DB_RULE_RETURN_OK; + if (strcmp(str, "return-fail") == 0) + return AUTH_DB_RULE_RETURN_FAIL; + if (strcmp(str, "continue") == 0) + return AUTH_DB_RULE_CONTINUE; + if (strcmp(str, "continue-ok") == 0) + return AUTH_DB_RULE_CONTINUE_OK; + if (strcmp(str, "continue-fail") == 0) + return AUTH_DB_RULE_CONTINUE_FAIL; + i_unreached(); +} + +static void +auth_passdb_preinit(struct auth *auth, const struct auth_passdb_settings *set, + struct auth_passdb **passdbs) +{ + struct auth_passdb *auth_passdb, **dest; + + auth_passdb = p_new(auth->pool, struct auth_passdb, 1); + auth_passdb->set = set; + auth_passdb->skip = auth_passdb_skip_parse(set->skip); + auth_passdb->result_success = + auth_db_rule_parse(set->result_success); + auth_passdb->result_failure = + auth_db_rule_parse(set->result_failure); + auth_passdb->result_internalfail = + auth_db_rule_parse(set->result_internalfail); + + auth_passdb->default_fields_tmpl = + passdb_template_build(auth->pool, set->default_fields); + auth_passdb->override_fields_tmpl = + passdb_template_build(auth->pool, set->override_fields); + + /* for backwards compatibility: */ + if (set->pass) + auth_passdb->result_success = AUTH_DB_RULE_CONTINUE; + + for (dest = passdbs; *dest != NULL; dest = &(*dest)->next) ; + *dest = auth_passdb; + + auth_passdb->passdb = passdb_preinit(auth->pool, set); + /* make sure any %variables in default_fields exist in cache_key */ + if (auth_passdb->passdb->default_cache_key != NULL) { + auth_passdb->cache_key = + p_strconcat(auth->pool, auth_passdb->passdb->default_cache_key, + set->default_fields, NULL); + } + else { + auth_passdb->cache_key = NULL; + } +} + +static void +auth_userdb_preinit(struct auth *auth, const struct auth_userdb_settings *set) +{ + struct auth_userdb *auth_userdb, **dest; + + auth_userdb = p_new(auth->pool, struct auth_userdb, 1); + auth_userdb->set = set; + auth_userdb->skip = auth_userdb_skip_parse(set->skip); + auth_userdb->result_success = + auth_db_rule_parse(set->result_success); + auth_userdb->result_failure = + auth_db_rule_parse(set->result_failure); + auth_userdb->result_internalfail = + auth_db_rule_parse(set->result_internalfail); + + auth_userdb->default_fields_tmpl = + userdb_template_build(auth->pool, set->driver, + set->default_fields); + auth_userdb->override_fields_tmpl = + userdb_template_build(auth->pool, set->driver, + set->override_fields); + + for (dest = &auth->userdbs; *dest != NULL; dest = &(*dest)->next) ; + *dest = auth_userdb; + + auth_userdb->userdb = userdb_preinit(auth->pool, set); + /* make sure any %variables in default_fields exist in cache_key */ + if (auth_userdb->userdb->default_cache_key != NULL) { + auth_userdb->cache_key = + p_strconcat(auth->pool, auth_userdb->userdb->default_cache_key, + set->default_fields, NULL); + } + else { + auth_userdb->cache_key = NULL; + } +} + +static bool auth_passdb_list_have_verify_plain(const struct auth *auth) +{ + const struct auth_passdb *passdb; + + for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) { + if (passdb->passdb->iface.verify_plain != NULL) + return TRUE; + } + return FALSE; +} + +static bool auth_passdb_list_have_lookup_credentials(const struct auth *auth) +{ + const struct auth_passdb *passdb; + + for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) { + if (passdb->passdb->iface.lookup_credentials != NULL) + return TRUE; + } + return FALSE; +} + +static bool auth_passdb_list_have_set_credentials(const struct auth *auth) +{ + const struct auth_passdb *passdb; + + for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next) { + if (passdb->passdb->iface.set_credentials != NULL) + return TRUE; + } + for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) { + if (passdb->passdb->iface.set_credentials != NULL) + return TRUE; + } + return FALSE; +} + +static bool +auth_mech_verify_passdb(const struct auth *auth, const struct mech_module_list *list) +{ + switch (list->module.passdb_need) { + case MECH_PASSDB_NEED_NOTHING: + break; + case MECH_PASSDB_NEED_VERIFY_PLAIN: + if (!auth_passdb_list_have_verify_plain(auth)) + return FALSE; + break; + case MECH_PASSDB_NEED_VERIFY_RESPONSE: + case MECH_PASSDB_NEED_LOOKUP_CREDENTIALS: + if (!auth_passdb_list_have_lookup_credentials(auth)) + return FALSE; + break; + case MECH_PASSDB_NEED_SET_CREDENTIALS: + if (!auth_passdb_list_have_lookup_credentials(auth)) + return FALSE; + if (!auth_passdb_list_have_set_credentials(auth)) + return FALSE; + break; + } + return TRUE; +} + +static void auth_mech_list_verify_passdb(const struct auth *auth) +{ + const struct mech_module_list *list; + + for (list = auth->reg->modules; list != NULL; list = list->next) { + if (!auth_mech_verify_passdb(auth, list)) + break; + } + + if (list != NULL) { + if (auth->passdbs == NULL) { + i_fatal("No passdbs specified in configuration file. " + "%s mechanism needs one", + list->module.mech_name); + } + i_fatal("%s mechanism can't be supported with given passdbs", + list->module.mech_name); + } +} + +static struct auth * ATTR_NULL(2) +auth_preinit(const struct auth_settings *set, const char *service, pool_t pool, + const struct mechanisms_register *reg) +{ + struct auth_passdb_settings *const *passdbs; + struct auth_userdb_settings *const *userdbs; + struct auth *auth; + unsigned int i, count, db_count, passdb_count, last_passdb = 0; + + auth = p_new(pool, struct auth, 1); + auth->pool = pool; + auth->service = p_strdup(pool, service); + auth->set = set; + auth->reg = reg; + + if (array_is_created(&set->passdbs)) + passdbs = array_get(&set->passdbs, &db_count); + else { + passdbs = NULL; + db_count = 0; + } + + /* initialize passdbs first and count them */ + for (passdb_count = 0, i = 0; i < db_count; i++) { + if (passdbs[i]->master) + continue; + + /* passdb { skip=unauthenticated } as the first passdb doesn't + make sense, since user is never authenticated at that point. + skip over them silently. */ + if (auth->passdbs == NULL && + auth_passdb_skip_parse(passdbs[i]->skip) == AUTH_PASSDB_SKIP_UNAUTHENTICATED) + continue; + + auth_passdb_preinit(auth, passdbs[i], &auth->passdbs); + passdb_count++; + last_passdb = i; + } + if (passdb_count != 0 && passdbs[last_passdb]->pass) + i_fatal("Last passdb can't have pass=yes"); + + for (i = 0; i < db_count; i++) { + if (!passdbs[i]->master) + continue; + + /* skip skip=unauthenticated, as explained above */ + if (auth->masterdbs == NULL && + auth_passdb_skip_parse(passdbs[i]->skip) == AUTH_PASSDB_SKIP_UNAUTHENTICATED) + continue; + + if (passdbs[i]->deny) + i_fatal("Master passdb can't have deny=yes"); + if (passdbs[i]->pass && passdb_count == 0) { + i_fatal("Master passdb can't have pass=yes " + "if there are no passdbs"); + } + auth_passdb_preinit(auth, passdbs[i], &auth->masterdbs); + } + + if (array_is_created(&set->userdbs)) { + userdbs = array_get(&set->userdbs, &count); + for (i = 0; i < count; i++) + auth_userdb_preinit(auth, userdbs[i]); + } + + if (auth->userdbs == NULL) { + /* use a dummy userdb static. */ + auth_userdb_preinit(auth, &userdb_dummy_set); + } + return auth; +} + +static void auth_passdb_init(struct auth_passdb *passdb) +{ + passdb_init(passdb->passdb); + + i_assert(passdb->passdb->default_pass_scheme != NULL || + passdb->cache_key == NULL); +} + +static void auth_init(struct auth *auth) +{ + struct auth_passdb *passdb; + struct auth_userdb *userdb; + + for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next) + auth_passdb_init(passdb); + for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) + auth_passdb_init(passdb); + for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next) + userdb_init(userdb->userdb); +} + +static void auth_deinit(struct auth *auth) +{ + struct auth_passdb *passdb; + struct auth_userdb *userdb; + + for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next) + passdb_deinit(passdb->passdb); + for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) + passdb_deinit(passdb->passdb); + for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next) + userdb_deinit(userdb->userdb); +} + +struct auth *auth_find_service(const char *name) +{ + struct auth *const *a; + unsigned int i, count; + + a = array_get(&auths, &count); + if (name != NULL) { + for (i = 1; i < count; i++) { + if (strcmp(a[i]->service, name) == 0) + return a[i]; + } + /* not found. maybe we can instead find a !service */ + for (i = 1; i < count; i++) { + if (a[i]->service[0] == '!' && + strcmp(a[i]->service + 1, name) != 0) + return a[i]; + } + } + return a[0]; +} + +struct auth *auth_default_service(void) +{ + struct auth *const *a; + unsigned int count; + + a = array_get(&auths, &count); + return a[0]; +} + +void auths_preinit(const struct auth_settings *set, pool_t pool, + const struct mechanisms_register *reg, + const char *const *services) +{ + struct master_service_settings_output set_output; + const struct auth_settings *service_set; + struct auth *auth; + unsigned int i; + const char *not_service = NULL; + bool check_default = TRUE; + + auth_event = event_create(NULL); + event_set_forced_debug(auth_event, set->debug); + event_add_category(auth_event, &event_category_auth); + i_array_init(&auths, 8); + + auth = auth_preinit(set, NULL, pool, reg); + array_push_back(&auths, &auth); + + for (i = 0; services[i] != NULL; i++) { + if (services[i][0] == '!') { + if (not_service != NULL) { + i_fatal("Can't have multiple protocol " + "!services (seen %s and %s)", + not_service, services[i]); + } + not_service = services[i]; + } + service_set = auth_settings_read(services[i], pool, + &set_output); + auth = auth_preinit(service_set, services[i], pool, reg); + array_push_back(&auths, &auth); + } + + if (not_service != NULL && str_array_find(services, not_service+1)) + check_default = FALSE; + + array_foreach_elem(&auths, auth) { + if (auth->service != NULL || check_default) + auth_mech_list_verify_passdb(auth); + } +} + +void auths_init(void) +{ + struct auth *auth; + + /* sanity checks */ + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USER_IDX].key == 'u'); + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USERNAME_IDX].key == 'n'); + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX].key == 'd'); + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].key == '\0' && + auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].long_key == NULL); + i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].key != '\0' || + auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].long_key != NULL); + + array_foreach_elem(&auths, auth) + auth_init(auth); +} + +void auths_deinit(void) +{ + struct auth *auth; + + array_foreach_elem(&auths, auth) + auth_deinit(auth); + event_unref(&auth_event); +} + +void auths_free(void) +{ + struct auth **auth; + unsigned int i, count; + + /* deinit in reverse order, because modules have been allocated by + the first auth pool that used them */ + auth = array_get_modifiable(&auths, &count); + for (i = count; i > 0; i--) + pool_unref(&auth[i-1]->pool); + array_free(&auths); +} |