diff options
Diffstat (limited to 'src/auth/auth-settings.c')
-rw-r--r-- | src/auth/auth-settings.c | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/src/auth/auth-settings.c b/src/auth/auth-settings.c new file mode 100644 index 0000000..4268b9a --- /dev/null +++ b/src/auth/auth-settings.c @@ -0,0 +1,553 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash-method.h" +#include "settings-parser.h" +#include "master-service-private.h" +#include "master-service-settings.h" +#include "service-settings.h" +#include "auth-settings.h" + +#include <stddef.h> + +static bool auth_settings_check(void *_set, pool_t pool, const char **error_r); +static bool auth_passdb_settings_check(void *_set, pool_t pool, const char **error_r); +static bool auth_userdb_settings_check(void *_set, pool_t pool, const char **error_r); + +/* <settings checks> */ +static struct file_listener_settings auth_unix_listeners_array[] = { + { "login/login", 0666, "", "" }, + { "token-login/tokenlogin", 0666, "", "" }, + { "auth-login", 0600, "$default_internal_user", "" }, + { "auth-client", 0600, "$default_internal_user", "" }, + { "auth-userdb", 0666, "$default_internal_user", "" }, + { "auth-master", 0600, "", "" } +}; +static struct file_listener_settings *auth_unix_listeners[] = { + &auth_unix_listeners_array[0], + &auth_unix_listeners_array[1], + &auth_unix_listeners_array[2], + &auth_unix_listeners_array[3], + &auth_unix_listeners_array[4], + &auth_unix_listeners_array[5] +}; +static buffer_t auth_unix_listeners_buf = { + { { auth_unix_listeners, sizeof(auth_unix_listeners) } } +}; +/* </settings checks> */ + +struct service_settings auth_service_settings = { + .name = "auth", + .protocol = "", + .type = "", + .executable = "auth", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &auth_unix_listeners_buf, + sizeof(auth_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT, + + .process_limit_1 = TRUE +}; + +/* <settings checks> */ +static struct file_listener_settings auth_worker_unix_listeners_array[] = { + { "auth-worker", 0600, "$default_internal_user", "" } +}; +static struct file_listener_settings *auth_worker_unix_listeners[] = { + &auth_worker_unix_listeners_array[0] +}; +static buffer_t auth_worker_unix_listeners_buf = { + { { auth_worker_unix_listeners, sizeof(auth_worker_unix_listeners) } } +}; +/* </settings checks> */ + +struct service_settings auth_worker_service_settings = { + .name = "auth-worker", + .protocol = "", + .type = "worker", + .executable = "auth -w", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 1, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &auth_worker_unix_listeners_buf, + sizeof(auth_worker_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; + +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_passdb_settings) + +static const struct setting_define auth_passdb_setting_defines[] = { + DEF(STR, name), + DEF(STR, driver), + DEF(STR, args), + DEF(STR, default_fields), + DEF(STR, override_fields), + DEF(STR, mechanisms), + DEF(STR, username_filter), + + DEF(ENUM, skip), + DEF(ENUM, result_success), + DEF(ENUM, result_failure), + DEF(ENUM, result_internalfail), + + DEF(BOOL, deny), + DEF(BOOL, pass), + DEF(BOOL, master), + DEF(ENUM, auth_verbose), + + SETTING_DEFINE_LIST_END +}; + +static const struct auth_passdb_settings auth_passdb_default_settings = { + .name = "", + .driver = "", + .args = "", + .default_fields = "", + .override_fields = "", + .mechanisms = "", + .username_filter = "", + + .skip = "never:authenticated:unauthenticated", + .result_success = "return-ok:return:return-fail:continue:continue-ok:continue-fail", + .result_failure = "continue:return:return-ok:return-fail:continue-ok:continue-fail", + .result_internalfail = "continue:return:return-ok:return-fail:continue-ok:continue-fail", + + .deny = FALSE, + .pass = FALSE, + .master = FALSE, + .auth_verbose = "default:yes:no" +}; + +const struct setting_parser_info auth_passdb_setting_parser_info = { + .defines = auth_passdb_setting_defines, + .defaults = &auth_passdb_default_settings, + + .type_offset = offsetof(struct auth_passdb_settings, name), + .struct_size = sizeof(struct auth_passdb_settings), + + .parent_offset = SIZE_MAX, + .parent = &auth_setting_parser_info, + + .check_func = auth_passdb_settings_check +}; + +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_userdb_settings) + +static const struct setting_define auth_userdb_setting_defines[] = { + DEF(STR, name), + DEF(STR, driver), + DEF(STR, args), + DEF(STR, default_fields), + DEF(STR, override_fields), + + DEF(ENUM, skip), + DEF(ENUM, result_success), + DEF(ENUM, result_failure), + DEF(ENUM, result_internalfail), + + DEF(ENUM, auth_verbose), + + SETTING_DEFINE_LIST_END +}; + +static const struct auth_userdb_settings auth_userdb_default_settings = { + /* NOTE: when adding fields, update also auth.c:userdb_dummy_set */ + .name = "", + .driver = "", + .args = "", + .default_fields = "", + .override_fields = "", + + .skip = "never:found:notfound", + .result_success = "return-ok:return:return-fail:continue:continue-ok:continue-fail", + .result_failure = "continue:return:return-ok:return-fail:continue-ok:continue-fail", + .result_internalfail = "continue:return:return-ok:return-fail:continue-ok:continue-fail", + + .auth_verbose = "default:yes:no" +}; + +const struct setting_parser_info auth_userdb_setting_parser_info = { + .defines = auth_userdb_setting_defines, + .defaults = &auth_userdb_default_settings, + + .type_offset = offsetof(struct auth_userdb_settings, name), + .struct_size = sizeof(struct auth_userdb_settings), + + .parent_offset = SIZE_MAX, + .parent = &auth_setting_parser_info, + + .check_func = auth_userdb_settings_check +}; + +/* we're kind of kludging here to avoid "auth_" prefix in the struct fields */ +#undef DEF +#undef DEF_NOPREFIX +#undef DEFLIST +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type("auth_"#name, name, struct auth_settings) +#define DEF_NOPREFIX(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_settings) +#define DEFLIST(field, name, defines) \ + { .type = SET_DEFLIST, .key = name, \ + .offset = offsetof(struct auth_settings, field), \ + .list_info = defines } + +static const struct setting_define auth_setting_defines[] = { + DEF(STR, mechanisms), + DEF(STR, realms), + DEF(STR, default_realm), + DEF(SIZE, cache_size), + DEF(TIME, cache_ttl), + DEF(TIME, cache_negative_ttl), + DEF(BOOL, cache_verify_password_with_worker), + DEF(STR, username_chars), + DEF(STR, username_translation), + DEF(STR, username_format), + DEF(STR, master_user_separator), + DEF(STR, anonymous_username), + DEF(STR, krb5_keytab), + DEF(STR, gssapi_hostname), + DEF(STR, winbind_helper_path), + DEF(STR, proxy_self), + DEF(TIME, failure_delay), + + DEF(STR, policy_server_url), + DEF(STR, policy_server_api_header), + DEF(UINT, policy_server_timeout_msecs), + DEF(STR, policy_hash_mech), + DEF(STR, policy_hash_nonce), + DEF(STR, policy_request_attributes), + DEF(BOOL, policy_reject_on_fail), + DEF(BOOL, policy_check_before_auth), + DEF(BOOL, policy_check_after_auth), + DEF(BOOL, policy_report_after_auth), + DEF(BOOL, policy_log_only), + DEF(UINT, policy_hash_truncate), + + DEF(BOOL, stats), + DEF(BOOL, verbose), + DEF(BOOL, debug), + DEF(BOOL, debug_passwords), + DEF(STR, verbose_passwords), + DEF(BOOL, ssl_require_client_cert), + DEF(BOOL, ssl_username_from_cert), + DEF(BOOL, use_winbind), + + DEF(UINT, worker_max_count), + + DEFLIST(passdbs, "passdb", &auth_passdb_setting_parser_info), + DEFLIST(userdbs, "userdb", &auth_userdb_setting_parser_info), + + DEF_NOPREFIX(STR, base_dir), + DEF_NOPREFIX(BOOL, verbose_proctitle), + DEF_NOPREFIX(UINT, first_valid_uid), + DEF_NOPREFIX(UINT, last_valid_uid), + DEF_NOPREFIX(UINT, first_valid_gid), + DEF_NOPREFIX(UINT, last_valid_gid), + + DEF_NOPREFIX(STR, ssl_client_ca_dir), + DEF_NOPREFIX(STR, ssl_client_ca_file), + + SETTING_DEFINE_LIST_END +}; + +static const struct auth_settings auth_default_settings = { + .mechanisms = "plain", + .realms = "", + .default_realm = "", + .cache_size = 0, + .cache_ttl = 60*60, + .cache_negative_ttl = 60*60, + .cache_verify_password_with_worker = FALSE, + .username_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@", + .username_translation = "", + .username_format = "%Lu", + .master_user_separator = "", + .anonymous_username = "anonymous", + .krb5_keytab = "", + .gssapi_hostname = "", + .winbind_helper_path = "/usr/bin/ntlm_auth", + .proxy_self = "", + .failure_delay = 2, + + .policy_server_url = "", + .policy_server_api_header = "", + .policy_server_timeout_msecs = 2000, + .policy_hash_mech = "sha256", + .policy_hash_nonce = "", + .policy_request_attributes = "login=%{requested_username} pwhash=%{hashed_password} remote=%{rip} device_id=%{client_id} protocol=%s session_id=%{session}", + .policy_reject_on_fail = FALSE, + .policy_check_before_auth = TRUE, + .policy_check_after_auth = TRUE, + .policy_report_after_auth = TRUE, + .policy_log_only = FALSE, + .policy_hash_truncate = 12, + + .stats = FALSE, + .verbose = FALSE, + .debug = FALSE, + .debug_passwords = FALSE, + .verbose_passwords = "no", + .ssl_require_client_cert = FALSE, + .ssl_username_from_cert = FALSE, + .ssl_client_ca_dir = "", + .ssl_client_ca_file = "", + + .use_winbind = FALSE, + + .worker_max_count = 30, + + .passdbs = ARRAY_INIT, + .userdbs = ARRAY_INIT, + + .base_dir = PKG_RUNDIR, + .verbose_proctitle = FALSE, + .first_valid_uid = 500, + .last_valid_uid = 0, + .first_valid_gid = 1, + .last_valid_gid = 0, +}; + +const struct setting_parser_info auth_setting_parser_info = { + .module_name = "auth", + .defines = auth_setting_defines, + .defaults = &auth_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct auth_settings), + + .parent_offset = SIZE_MAX, + + .check_func = auth_settings_check +}; + +/* <settings checks> */ +static bool +auth_settings_set_self_ips(struct auth_settings *set, pool_t pool, + const char **error_r) +{ + const char *const *tmp; + ARRAY(struct ip_addr) ips_array; + struct ip_addr *ips; + unsigned int ips_count; + int ret; + + if (*set->proxy_self == '\0') { + set->proxy_self_ips = p_new(pool, struct ip_addr, 1); + return TRUE; + } + + p_array_init(&ips_array, pool, 4); + tmp = t_strsplit_spaces(set->proxy_self, " "); + for (; *tmp != NULL; tmp++) { + ret = net_gethostbyname(*tmp, &ips, &ips_count); + if (ret != 0) { + *error_r = t_strdup_printf("auth_proxy_self_ips: " + "gethostbyname(%s) failed: %s", + *tmp, net_gethosterror(ret)); + } + array_append(&ips_array, ips, ips_count); + } + array_append_zero(&ips_array); + set->proxy_self_ips = array_front(&ips_array); + return TRUE; +} + +static bool +auth_verify_verbose_password(struct auth_settings *set, + const char **error_r) +{ + const char *p, *value = set->verbose_passwords; + unsigned int num; + + p = strchr(value, ':'); + if (p != NULL) { + if (str_to_uint(p+1, &num) < 0 || num == 0) { + *error_r = t_strdup_printf("auth_verbose_passwords: " + "Invalid truncation number: '%s'", p+1); + return FALSE; + } + value = t_strdup_until(value, p); + } + if (strcmp(value, "no") == 0) + return TRUE; + else if (strcmp(value, "plain") == 0) + return TRUE; + else if (strcmp(value, "sha1") == 0) + return TRUE; + else if (strcmp(value, "yes") == 0) { + /* just use it as alias for "plain" */ + set->verbose_passwords = "plain"; + return TRUE; + } else { + *error_r = "auth_verbose_passwords: Invalid value"; + return FALSE; + } +} + +static bool auth_settings_check(void *_set, pool_t pool, + const char **error_r) +{ + struct auth_settings *set = _set; + const char *p; + + if (set->debug_passwords) + set->debug = TRUE; + if (set->debug) + set->verbose = TRUE; + + if (set->worker_max_count == 0) { + *error_r = "auth_worker_max_count must be above zero"; + return FALSE; + } + + if (set->cache_size > 0 && set->cache_size < 1024) { + /* probably a configuration error. + older versions used megabyte numbers */ + *error_r = t_strdup_printf("auth_cache_size value is too small " + "(%"PRIuUOFF_T" bytes)", + set->cache_size); + return FALSE; + } + + if (!auth_verify_verbose_password(set, error_r)) + return FALSE; + + if (*set->username_chars == '\0') { + /* all chars are allowed */ + memset(set->username_chars_map, 1, + sizeof(set->username_chars_map)); + } else { + for (p = set->username_chars; *p != '\0'; p++) + set->username_chars_map[(int)(uint8_t)*p] = 1; + } + + if (*set->username_translation != '\0') { + p = set->username_translation; + for (; *p != '\0' && p[1] != '\0'; p += 2) + set->username_translation_map[(int)(uint8_t)*p] = p[1]; + } + set->realms_arr = + (const char *const *)p_strsplit_spaces(pool, set->realms, " "); + + if (*set->policy_server_url != '\0') { + if (*set->policy_hash_nonce == '\0') { + + *error_r = "auth_policy_hash_nonce must be set when policy server is used"; + return FALSE; + } + const struct hash_method *digest = hash_method_lookup(set->policy_hash_mech); + if (digest == NULL) { + *error_r = "invalid auth_policy_hash_mech given"; + return FALSE; + } + if (set->policy_hash_truncate > 0 && set->policy_hash_truncate >= digest->digest_size*8) { + *error_r = t_strdup_printf("policy_hash_truncate is not smaller than digest size (%u >= %u)", + set->policy_hash_truncate, + digest->digest_size*8); + return FALSE; + } + } + + if (!auth_settings_set_self_ips(set, pool, error_r)) + return FALSE; + return TRUE; +} + +static bool +auth_passdb_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct auth_passdb_settings *set = _set; + + if (set->driver == NULL || *set->driver == '\0') { + *error_r = "passdb is missing driver"; + return FALSE; + } + if (set->pass && strcmp(set->result_success, "return-ok") != 0) { + *error_r = "Obsolete pass=yes setting mixed with non-default result_success"; + return FALSE; + } + return TRUE; +} + +static bool +auth_userdb_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct auth_userdb_settings *set = _set; + + if (set->driver == NULL || *set->driver == '\0') { + *error_r = "userdb is missing driver"; + return FALSE; + } + return TRUE; +} +/* </settings checks> */ + +struct auth_settings *global_auth_settings; + +struct auth_settings * +auth_settings_read(const char *service, pool_t pool, + struct master_service_settings_output *output_r) +{ + static const struct setting_parser_info *set_roots[] = { + &auth_setting_parser_info, + NULL + }; + struct master_service_settings_input input; + struct setting_parser_context *set_parser; + const char *error; + void **sets; + + i_zero(&input); + input.roots = set_roots; + input.module = "auth"; + input.service = service; + if (master_service_settings_read(master_service, &input, + output_r, &error) < 0) + i_fatal("Error reading configuration: %s", error); + + pool_ref(pool); + set_parser = settings_parser_dup(master_service->set_parser, pool); + if (!settings_parser_check(set_parser, pool, &error)) + i_unreached(); + + sets = master_service_settings_parser_get_others(master_service, + set_parser); + settings_parser_deinit(&set_parser); + return sets[0]; +} |