Mercurial > dovecot > core-2.2
view src/lib-storage/mail-storage-service.c @ 9106:fce3926fe910 HEAD
mail_storage_service_init*() can now take multiple set_roots.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Wed, 15 Apr 2009 12:40:16 -0400 |
parents | a06b6fa612ad |
children | 14ebbf71ef3e |
line wrap: on
line source
/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hostpid.h" #include "module-dir.h" #include "restrict-access.h" #include "str.h" #include "var-expand.h" #include "dict.h" #include "settings-parser.h" #include "auth-master.h" #include "master-service-private.h" #include "master-service-settings.h" #include "mail-user.h" #include "mail-namespace.h" #include "mail-storage.h" #include "mail-storage-service.h" #include <stdlib.h> #include <sys/stat.h> #include <pwd.h> #include <grp.h> struct mail_storage_service_multi_ctx { struct master_service *service; enum mail_storage_service_flags flags; unsigned int modules_initialized:1; }; static struct module *modules = NULL; static void set_keyval(struct master_service *service, const char *key, const char *value) { const char *str; str = t_strconcat(key, "=", value, NULL); if (master_service_set(service, str) < 0) { i_fatal("Invalid userdb input '%s': %s", str, settings_parser_get_error(service->set_parser)); } } static bool validate_chroot(const struct mail_user_settings *user_set, const char *dir) { const char *const *chroot_dirs; if (*dir == '\0') return FALSE; if (*user_set->valid_chroot_dirs == '\0') return FALSE; chroot_dirs = t_strsplit(user_set->valid_chroot_dirs, ":"); while (*chroot_dirs != NULL) { if (**chroot_dirs != '\0' && strncmp(dir, *chroot_dirs, strlen(*chroot_dirs)) == 0) return TRUE; chroot_dirs++; } return FALSE; } static int user_reply_handle(struct master_service *service, const struct mail_user_settings *user_set, const struct auth_user_reply *reply, const char **system_groups_user_r, const char **error_r) { const char *const *str, *p, *line, *key; unsigned int i, count; int ret = 0; *system_groups_user_r = NULL; if (reply->uid != (uid_t)-1) { if (reply->uid == 0) { *error_r = "userdb returned 0 as uid"; return -1; } set_keyval(service, "mail_uid", dec2str(reply->uid)); } if (reply->gid != (uid_t)-1) set_keyval(service, "mail_gid", dec2str(reply->gid)); if (reply->home != NULL) set_keyval(service, "mail_home", reply->home); if (reply->chroot != NULL) { if (!validate_chroot(user_set, reply->chroot)) { *error_r = t_strdup_printf( "userdb returned invalid chroot directory: %s " "(see valid_chroot_dirs setting)", reply->chroot); return -1; } set_keyval(service, "mail_chroot", reply->chroot); } str = array_get(&reply->extra_fields, &count); for (i = 0; i < count && ret == 0; i++) T_BEGIN { line = str[i]; if (strncmp(line, "system_groups_user=", 19) == 0) { *system_groups_user_r = line + 19; continue; } if (strncmp(line, "mail=", 5) == 0) line = t_strconcat("mail_location=", line + 5, NULL); else if ((p = strchr(str[i], '=')) == NULL) line = t_strconcat(str[i], "=yes", NULL); else line = str[i]; key = t_strcut(line, '='); if (!settings_parse_is_valid_key(service->set_parser, key)) { /* assume it's a plugin setting */ line = t_strconcat("plugin/", line, NULL); } ret = settings_parse_line(service->set_parser, line); } T_END; if (ret < 0) { *error_r = t_strdup_printf("Invalid userdb input '%s': %s", str[i], settings_parser_get_error(service->set_parser)); } return ret; } static int service_auth_userdb_lookup(struct master_service *service, bool debug, const struct mail_user_settings *user_set, const char **user, const char **system_groups_user_r, const char **error_r) { struct auth_master_connection *conn; struct auth_user_reply reply; const char *system_groups_user, *orig_user = *user; unsigned int len; pool_t pool; int ret; pool = pool_alloconly_create("userdb lookup", 1024); conn = auth_master_init(user_set->auth_socket_path, debug); ret = auth_master_user_lookup(conn, *user, service->name, pool, &reply); if (ret > 0) { len = reply.chroot == NULL ? 0 : strlen(reply.chroot); *user = t_strdup(reply.user); if (user_reply_handle(service, user_set, &reply, &system_groups_user, error_r) < 0) ret = -1; *system_groups_user_r = t_strdup(system_groups_user); } else { if (ret == 0) *error_r = "unknown user"; else *error_r = "userdb lookup failed"; *system_groups_user_r = NULL; } if (ret > 0 && strcmp(*user, orig_user) != 0) { if (mail_user_set_get_storage_set(user_set)->mail_debug) i_info("changed username to %s", *user); i_set_failure_prefix(t_strdup_printf("%s(%s): ", service->name, *user)); } auth_master_deinit(&conn); pool_unref(&pool); return ret; } static bool parse_uid(const char *str, uid_t *uid_r) { struct passwd *pw; char *p; if (*str >= '0' && *str <= '9') { *uid_r = (uid_t)strtoul(str, &p, 10); if (*p == '\0') return TRUE; } pw = getpwnam(str); if (pw == NULL) return FALSE; *uid_r = pw->pw_uid; return TRUE; } static bool parse_gid(const char *str, gid_t *gid_r) { struct group *gr; char *p; if (*str >= '0' && *str <= '9') { *gid_r = (gid_t)strtoul(str, &p, 10); if (*p == '\0') return TRUE; } gr = getgrnam(str); if (gr == NULL) return FALSE; *gid_r = gr->gr_gid; return TRUE; } static void service_drop_privileges(const struct mail_user_settings *set, const char *system_groups_user, const char *home, bool disallow_root, bool keep_setuid_root) { struct restrict_access_settings rset; uid_t current_euid, setuid_uid = 0; current_euid = geteuid(); restrict_access_init(&rset); if (*set->mail_uid != '\0') { if (!parse_uid(set->mail_uid, &rset.uid)) i_fatal("Unknown mail_uid user: %s", set->mail_uid); if (rset.uid < (uid_t)set->first_valid_uid || (set->last_valid_uid != 0 && rset.uid > (uid_t)set->last_valid_uid)) { i_fatal("Mail access for users with UID %s " "not permitted (see first_valid_uid in config file).", dec2str(rset.uid)); } } if (*set->mail_gid != '\0') { if (!parse_gid(set->mail_gid, &rset.gid)) i_fatal("Unknown mail_gid group: %s", set->mail_gid); if (rset.gid < (gid_t)set->first_valid_gid || (set->last_valid_gid != 0 && rset.gid > (gid_t)set->last_valid_gid)) { i_fatal("Mail access for users with GID %s " "not permitted (see first_valid_gid in config file).", dec2str(rset.gid)); } } if (*set->mail_privileged_group != '\0') { if (!parse_uid(set->mail_privileged_group, &rset.privileged_gid)) i_fatal("Unknown mail_gid group: %s", set->mail_gid); } if (*set->mail_access_groups != '\0') rset.extra_groups = set->mail_access_groups; rset.first_valid_gid = set->first_valid_gid; rset.last_valid_gid = set->last_valid_gid; /* we can't chroot if we want to switch between users. there's not much point either (from security point of view) */ rset.chroot_dir = *set->mail_chroot == '\0' || keep_setuid_root ? NULL : set->mail_chroot; rset.system_groups_user = system_groups_user; if (disallow_root && (rset.uid == 0 || (rset.uid == (uid_t)-1 && current_euid == 0))) i_fatal("Mail access not allowed for root"); if (keep_setuid_root && current_euid != rset.uid) { if (current_euid != 0) { /* we're changing the UID, switch back to root first */ if (seteuid(0) < 0) i_fatal("seteuid(0) failed: %m"); } setuid_uid = rset.uid; rset.uid = (uid_t)-1; } restrict_access(&rset, *home == '\0' ? NULL : home, disallow_root); if (keep_setuid_root) { if (seteuid(setuid_uid) < 0) i_fatal("seteuid(%s) failed: %m", dec2str(setuid_uid)); } } static void mail_storage_service_init_settings(struct master_service *service, const struct setting_parser_info *set_roots[], bool preserve_home) { ARRAY_DEFINE(all_set_roots, const struct setting_parser_info *); const struct setting_parser_info *info = &mail_user_setting_parser_info; const char *error; unsigned int i; (void)umask(0077); mail_storage_init(); mail_storage_register_all(); mailbox_list_register_all(); t_array_init(&all_set_roots, 5); array_append(&all_set_roots, &info, 1); for (i = 0; set_roots[i] != NULL; i++) array_append(&all_set_roots, &set_roots[i], 1); (void)array_append_space(&all_set_roots); /* read settings after registering storages so they can have their own setting definitions too */ set_roots = array_idx_modifiable(&all_set_roots, 0); if (master_service_settings_read(service, set_roots, mail_storage_get_dynamic_parsers(), preserve_home, &error) < 0) i_fatal("Error reading configuration: %s", error); } static int mail_storage_service_init_post(struct master_service *service, const char *user, const char *home, const struct mail_user_settings *user_set, struct mail_user **mail_user_r, const char **error_r) { const struct mail_storage_settings *mail_set; struct mail_user *mail_user; mail_set = mail_user_set_get_storage_set(user_set); if (mail_set->mail_debug) { i_info("Effective uid=%s, gid=%s, home=%s", dec2str(geteuid()), dec2str(getegid()), home != NULL ? home : "(none)"); } /* If possible chdir to home directory, so that core file could be written in case we crash. */ if (*home != '\0') { if (chdir(home) < 0) { if (errno != ENOENT) i_error("chdir(%s) failed: %m", home); else if (mail_set->mail_debug) i_info("Home dir not found: %s", home); } } mail_user = mail_user_alloc(user, user_set); mail_user_set_home(mail_user, *home == '\0' ? NULL : home); mail_user_set_vars(mail_user, geteuid(), service->name, NULL, NULL); if (mail_user_init(mail_user, error_r) < 0) { mail_user_unref(&mail_user); return -1; } if (mail_namespaces_init(mail_user, error_r) < 0) { mail_user_unref(&mail_user); return -1; } *mail_user_r = mail_user; return 0; } static const struct var_expand_table * get_var_expand_table(struct master_service *service, const char *user) { static struct var_expand_table static_tab[] = { { 'u', NULL, "user" }, { 'n', NULL, "username" }, { 'd', NULL, "domain" }, { 's', NULL, "service" }, { 'p', NULL, "pid" }, { 'i', NULL, "uid" }, { '\0', NULL, NULL } }; struct var_expand_table *tab; tab = t_malloc(sizeof(static_tab)); memcpy(tab, static_tab, sizeof(static_tab)); tab[0].value = user; tab[1].value = t_strcut(user, '@'); tab[2].value = strchr(user, '@'); if (tab[2].value != NULL) tab[2].value++; tab[3].value = service->name; tab[4].value = my_pid; tab[5].value = dec2str(geteuid()); return tab; } static const char * user_expand_varstr(struct master_service *service, const char *user, const char *str) { string_t *ret; if (*str == SETTING_STRVAR_EXPANDED[0]) return str + 1; i_assert(*str == SETTING_STRVAR_UNEXPANDED[0]); ret = t_str_new(256); var_expand(ret, str + 1, get_var_expand_table(service, user)); return str_c(ret); } struct mail_user * mail_storage_service_init_user(struct master_service *service, const char *user, const struct setting_parser_info *set_roots[], enum mail_storage_service_flags flags) { const struct master_service_settings *set; const struct mail_user_settings *user_set; const struct mail_storage_settings *mail_set; struct mail_user *mail_user; void **sets; const char *orig_user, *home, *system_groups_user, *error; unsigned int len; bool userdb_lookup; userdb_lookup = (flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0; mail_storage_service_init_settings(service, set_roots, !userdb_lookup); if ((flags & MAIL_STORAGE_SERVICE_FLAG_DEBUG) != 0) set_keyval(service, "mail_debug", "yes"); /* now that we've read settings, we can set up logging */ master_service_init_log(service, t_strdup_printf("%s(%s): ", service->name, user)); set = master_service_settings_get(service); sets = master_service_settings_get_others(service); user_set = sets[0]; mail_set = mail_user_set_get_storage_set(user_set); if (userdb_lookup) { /* userdb lookup may change settings, do it as soon as possible. */ orig_user = user; if (service_auth_userdb_lookup(service, mail_set->mail_debug, user_set, &user, &system_groups_user, &error) <= 0) i_fatal("%s", error); } /* variable strings are expanded in mail_user_init(), but we need the home sooner so do it separately here. */ home = user_expand_varstr(service, user, user_set->mail_home); if (!userdb_lookup) { system_groups_user = NULL; if (*home == '\0' && getenv("HOME") != NULL) { home = getenv("HOME"); set_keyval(service, "mail_home", home); } } len = strlen(user_set->mail_chroot); if (len > 2 && strcmp(user_set->mail_chroot + len - 2, "/.") == 0 && strncmp(home, user_set->mail_chroot, len - 2) == 0) { /* If chroot ends with "/.", strip chroot dir from home dir */ home += len - 2; set_keyval(service, "mail_home", home); } modules = *user_set->mail_plugins == '\0' ? NULL : module_dir_load(user_set->mail_plugin_dir, user_set->mail_plugins, TRUE, master_service_get_version_string(service)); if ((flags & MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS) == 0) { service_drop_privileges(user_set, system_groups_user, home, (flags & MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT) != 0, FALSE); } /* privileges are now dropped */ dict_drivers_register_builtin(); module_dir_init(modules); mail_users_init(user_set->auth_socket_path, mail_set->mail_debug); if (mail_storage_service_init_post(service, user, home, user_set, &mail_user, &error) < 0) i_fatal("%s", error); return mail_user; } void mail_storage_service_deinit_user(void) { module_dir_unload(&modules); mail_storage_deinit(); mail_users_deinit(); dict_drivers_unregister_builtin(); } struct mail_storage_service_multi_ctx * mail_storage_service_multi_init(struct master_service *service, const struct setting_parser_info *set_roots[], enum mail_storage_service_flags flags) { struct mail_storage_service_multi_ctx *ctx; const struct master_service_settings *set; const struct mail_user_settings *user_set; const struct mail_storage_settings *mail_set; void **sets; ctx = i_new(struct mail_storage_service_multi_ctx, 1); ctx->service = service; ctx->flags = flags; mail_storage_service_init_settings(service, set_roots, FALSE); /* do all the global initialization. delay initializing plugins until we drop privileges the first time. */ master_service_init_log(service, t_strdup_printf("%s: ", service->name)); set = master_service_settings_get(service); sets = master_service_settings_get_others(service); user_set = sets[0]; mail_set = mail_user_set_get_storage_set(user_set); modules = *user_set->mail_plugins == '\0' ? NULL : module_dir_load(user_set->mail_plugin_dir, user_set->mail_plugins, TRUE, master_service_get_version_string(service)); dict_drivers_register_builtin(); mail_users_init(user_set->auth_socket_path, mail_set->mail_debug); return ctx; } int mail_storage_service_multi_next(struct mail_storage_service_multi_ctx *ctx, const char *user, struct mail_user **mail_user_r, const char **error_r) { const struct mail_user_settings *user_set; const struct mail_storage_settings *mail_set; const char *orig_user, *system_groups_user, *home; void **sets; unsigned int len; int ret; sets = master_service_settings_get_others(ctx->service); user_set = sets[0]; mail_set = mail_user_set_get_storage_set(user_set); if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0) { orig_user = user; ret = service_auth_userdb_lookup(ctx->service, mail_set->mail_debug, user_set, &user, &system_groups_user, error_r); if (ret <= 0) return ret; } else { system_groups_user = NULL; } /* variable strings are expanded in mail_user_init(), but we need the home sooner so do it separately here. */ home = user_expand_varstr(ctx->service, user, user_set->mail_home); if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS) == 0) { service_drop_privileges(user_set, system_groups_user, home, (ctx->flags & MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT) != 0, TRUE); } if (!ctx->modules_initialized) { /* privileges dropped for the first time. initialize the modules now to avoid code running as root. */ module_dir_init(modules); ctx->modules_initialized = TRUE; } /* we couldn't do chrooting, so if chrooting was enabled fix the home directory */ len = strlen(user_set->mail_chroot); if (len > 2 && strcmp(user_set->mail_chroot + len - 2, "/.") == 0 && strncmp(home, user_set->mail_chroot, len - 2) == 0) { /* home dir already contains the chroot dir */ } else if (len > 0) { set_keyval(ctx->service, "mail_home", t_strconcat(user_set->mail_chroot, "/", home, NULL)); } if (mail_storage_service_init_post(ctx->service, user, home, user_set, mail_user_r, error_r) < 0) return -1; return 1; } void mail_storage_service_multi_deinit(struct mail_storage_service_multi_ctx **_ctx) { struct mail_storage_service_multi_ctx *ctx = *_ctx; *_ctx = NULL; i_free(ctx); mail_storage_service_deinit_user(); } void *mail_storage_service_get_settings(struct master_service *service) { void **sets; sets = master_service_settings_get_others(service); return sets[1]; }