view src/lib-storage/mail-storage-service.c @ 12772:e26af53b6b40

lib-storage: MAIL_STORAGE_SERVICE_FLAG_DEBUG didn't do anything.
author Timo Sirainen <tss@iki.fi>
date Fri, 04 Mar 2011 03:16:15 +0200
parents cec7fa92ff48
children 447bce266022
line wrap: on
line source

/* Copyright (c) 2009-2010 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "hostpid.h"
#include "module-dir.h"
#include "restrict-access.h"
#include "eacces-error.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 "master-service-settings-cache.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>

#ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#  include <sys/resource.h>
#endif

/* If time moves backwards more than this, kill ourself instead of sleeping. */
#define MAX_TIME_BACKWARDS_SLEEP 5
#define MAX_NOWARN_FORWARD_SECS 10

#define ERRSTR_INVALID_USER_SETTINGS \
	"Invalid user settings. Refer to server log for more information."

struct mail_storage_service_ctx {
	pool_t pool;
	struct master_service *service;
	struct auth_master_connection *conn;
	struct auth_master_user_list_ctx *auth_list;
	const struct setting_parser_info **set_roots;
	enum mail_storage_service_flags flags;

	const char *set_cache_module, *set_cache_service;
	struct master_service_settings_cache *set_cache;

	unsigned int debug:1;
	unsigned int log_initialized:1;
};

struct mail_storage_service_user {
	pool_t pool;
	struct mail_storage_service_input input;

	const char *system_groups_user, *uid_source, *gid_source;
	const struct mail_user_settings *user_set;
	const struct setting_parser_info *user_info;
	struct setting_parser_context *set_parser;
};

struct module *mail_storage_service_modules = NULL;

static bool
mail_user_set_get_mail_debug(const struct setting_parser_info *user_info,
			     const struct mail_user_settings *user_set)
{
	const struct mail_storage_settings *mail_set;

	mail_set = mail_user_set_get_driver_settings(user_info, user_set,
						MAIL_STORAGE_SET_DRIVER_NAME);
	return mail_set->mail_debug;
}

static void set_keyval(struct mail_storage_service_ctx *ctx,
		       struct mail_storage_service_user *user,
		       const char *key, const char *value)
{
	struct setting_parser_context *set_parser = user->set_parser;
	const char *str;

	if (master_service_set_has_config_override(ctx->service, key)) {
		/* this setting was already overridden with -o parameter */
		if (mail_user_set_get_mail_debug(user->user_info,
						 user->user_set)) {
			i_debug("Ignoring overridden (-o) userdb setting: %s",
				key);
		}
		return;
	}

	str = t_strconcat(key, "=", value, NULL);
	if (settings_parse_line(set_parser, str) < 0) {
		i_fatal("Invalid userdb input '%s': %s", str,
			settings_parser_get_error(set_parser));
	}
}

static int set_line(struct mail_storage_service_ctx *ctx,
		    struct mail_storage_service_user *user,
		    const char *line)
{
	struct setting_parser_context *set_parser = user->set_parser;
	bool mail_debug;
	const char *key;
	int ret;

	mail_debug = mail_user_set_get_mail_debug(user->user_info,
						  user->user_set);
	if (strchr(line, '=') == NULL)
		line = t_strconcat(line, "=yes", NULL);
	key = t_strcut(line, '=');

	if (!settings_parse_is_valid_key(set_parser, key)) {
		/* assume it's a plugin setting */
		key = t_strconcat("plugin/", key, NULL);
		line = t_strconcat("plugin/", line, NULL);
	}

	if (master_service_set_has_config_override(ctx->service, key)) {
		/* this setting was already overridden with -o parameter */
		if (mail_debug) {
			i_debug("Ignoring overridden (-o) userdb setting: %s",
				key);
		}
		return 1;
	}

	ret = settings_parse_line(set_parser, line);
	if (mail_debug && ret >= 0) {
		i_debug(ret == 0 ?
			"Unknown userdb setting: %s" :
			"Added userdb setting: %s", line);
	}
	return ret;
}

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 mail_storage_service_ctx *ctx,
		  struct mail_storage_service_user *user,
		  const struct auth_user_reply *reply,
		  const char **error_r)
{
	const char *home = reply->home;
	const char *chroot = reply->chroot;
	const char *const *str, *line, *p;
	unsigned int i, count;
	int ret = 0;

	if (reply->uid != (uid_t)-1) {
		if (reply->uid == 0) {
			*error_r = "userdb returned 0 as uid";
			return -1;
		}
		user->uid_source = "userdb lookup";
		set_keyval(ctx, user, "mail_uid", dec2str(reply->uid));
	}
	if (reply->gid != (uid_t)-1) {
		user->gid_source = "userdb lookup";
		set_keyval(ctx, user, "mail_gid", dec2str(reply->gid));
	}

	if (home != NULL && chroot == NULL &&
	    *user->user_set->valid_chroot_dirs != '\0' &&
	    (p = strstr(home, "/./")) != NULL) {
		/* wu-ftpd like <chroot>/./<home> - check only if there's even
		   a possibility of using them (non-empty valid_chroot_dirs) */
		chroot = t_strdup_until(home, p);
		home = p + 2;
	}

	if (home != NULL)
		set_keyval(ctx, user, "mail_home", reply->home);

	if (chroot != NULL) {
		if (!validate_chroot(user->user_set, chroot)) {
			*error_r = t_strdup_printf(
				"userdb returned invalid chroot directory: %s "
				"(see valid_chroot_dirs setting)", chroot);
			return -1;
		}
		set_keyval(ctx, user, "mail_chroot", chroot);
	}

	str = array_get(&reply->extra_fields, &count);
	for (i = 0; i < count && ret >= 0; i++) {
		line = str[i];
		if (strncmp(line, "system_groups_user=", 19) == 0) {
			user->system_groups_user =
				p_strdup(user->pool, line + 19);
		} else if (strncmp(line, "nice=", 5) == 0) {
#ifdef HAVE_SETPRIORITY
			int n = atoi(line + 5);

			if (n != 0) {
				if (setpriority(PRIO_PROCESS, 0, n) < 0)
					i_error("setpriority(%d) failed: %m", n);
			}
#endif
		} else T_BEGIN {
			ret = set_line(ctx, user, line);
		} T_END;
	}

	if (ret < 0) {
		*error_r = t_strdup_printf("Invalid userdb input '%s': %s",
			str[i], settings_parser_get_error(user->set_parser));
	}
	return ret;
}

static int
service_auth_userdb_lookup(struct mail_storage_service_ctx *ctx,
			   const struct mail_storage_service_input *input,
			   pool_t pool, const char **user,
			   const char *const **fields_r,
			   const char **error_r)
{
	struct auth_user_info info;
	const char *new_username;
	int ret;

	memset(&info, 0, sizeof(info));
	info.service = ctx->service->name;
	info.local_ip = input->local_ip;
	info.remote_ip = input->remote_ip;

	ret = auth_master_user_lookup(ctx->conn, *user, &info, pool,
				      &new_username, fields_r);
	if (ret > 0) {
		if (strcmp(*user, new_username) != 0) {
			if (ctx->debug)
				i_debug("changed username to %s", new_username);
			*user = t_strdup(new_username);
		}
		*user = new_username;
	} else if (ret == 0)
		*error_r = "Unknown user";
	else if (**fields_r != NULL) {
		*error_r = t_strdup(**fields_r);
		ret = -2;
	} else {
		*error_r = MAIL_ERRSTR_CRITICAL_MSG;
	}
	return ret;
}

static bool parse_uid(const char *str, uid_t *uid_r)
{
	struct passwd *pw;

	if (str_to_uid(str, uid_r) == 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;

	if (str_to_gid(str, gid_r) == 0)
		return TRUE;

	gr = getgrnam(str);
	if (gr == NULL)
		return FALSE;

	*gid_r = gr->gr_gid;
	return TRUE;
}

static int
service_drop_privileges(struct mail_storage_service_user *user,
			const struct mail_user_settings *set,
			const char *home, const char *chroot,
			bool disallow_root, bool keep_setuid_root,
			bool setenv_only, const char **error_r)
{
	struct restrict_access_settings rset;
	uid_t current_euid, setuid_uid = 0;
	const char *cur_chroot;

	current_euid = geteuid();
	restrict_access_init(&rset);
	restrict_access_get_env(&rset);
	if (*set->mail_uid != '\0') {
		if (!parse_uid(set->mail_uid, &rset.uid)) {
			*error_r = t_strdup_printf("Unknown mail_uid user: %s",
						   set->mail_uid);
			return -1;
		}
		if (rset.uid < (uid_t)set->first_valid_uid ||
		    (set->last_valid_uid != 0 &&
		     rset.uid > (uid_t)set->last_valid_uid)) {
			*error_r = t_strdup_printf(
				"Mail access for users with UID %s "
				"not permitted (see first_valid_uid in config file).",
				dec2str(rset.uid));
			return -1;
		}
		rset.uid_source = user->uid_source;
	} else if (rset.uid == (uid_t)-1 &&
		   disallow_root && current_euid == 0) {
		*error_r = "User is missing UID (see mail_uid setting)";
		return -1;
	}
	if (*set->mail_gid != '\0') {
		if (!parse_gid(set->mail_gid, &rset.gid)) {
			*error_r = t_strdup_printf("Unknown mail_gid group: %s",
						   set->mail_gid);
			return -1;
		}
		if (rset.gid < (gid_t)set->first_valid_gid ||
		    (set->last_valid_gid != 0 &&
		     rset.gid > (gid_t)set->last_valid_gid)) {
			*error_r = t_strdup_printf(
				"Mail access for users with GID %s "
				"not permitted (see first_valid_gid in config file).",
				dec2str(rset.gid));
			return -1;
		}
		rset.gid_source = user->gid_source;
	} else if (rset.gid == (gid_t)-1 && disallow_root &&
		   set->first_valid_gid > 0 && getegid() == 0) {
		*error_r = "User is missing GID (see mail_gid setting)";
		return -1;
	}
	if (*set->mail_privileged_group != '\0') {
		if (!parse_gid(set->mail_privileged_group, &rset.privileged_gid)) {
			*error_r = t_strdup_printf(
				"Unknown mail_privileged_group: %s",
				set->mail_gid);
			return -1;
		}
	}
	if (*set->mail_access_groups != '\0') {
		rset.extra_groups = t_strconcat(set->mail_access_groups, ",",
						rset.extra_groups, NULL);
	}

	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 = *chroot == '\0' || keep_setuid_root ? NULL : chroot;
	rset.system_groups_user = user->system_groups_user;

	cur_chroot = restrict_access_get_current_chroot();
	if (cur_chroot != NULL) {
		if (rset.chroot_dir == NULL) {
			*error_r = "Process is already chrooted, "
				"can't un-chroot for this user";
			return -1;
		}
		if (strcmp(rset.chroot_dir, cur_chroot) != 0) {
			*error_r = t_strdup_printf(
				"Process is already chrooted to %s, "
				"can't chroot to %s", cur_chroot, chroot);
			return -1;
		}
		/* chrooting to same directory where we're already chrooted */
		rset.chroot_dir = NULL;
	}

	if (disallow_root &&
	    (rset.uid == 0 || (rset.uid == (uid_t)-1 && current_euid == 0))) {
		*error_r = "Mail access not allowed for root";
		return -1;
	}

	if (keep_setuid_root) {
		if (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;
		disallow_root = FALSE;
	}
	if (!setenv_only) {
		restrict_access(&rset, *home == '\0' ? NULL : home,
				disallow_root);
	} else {
		restrict_access_set_env(&rset);
	}
	if (setuid_uid != 0 && !setenv_only) {
		if (seteuid(setuid_uid) < 0)
			i_fatal("seteuid(%s) failed: %m", dec2str(setuid_uid));
	}
	return 0;
}

static int
mail_storage_service_init_post(struct mail_storage_service_ctx *ctx,
			       struct mail_storage_service_user *user,
			       const char *home, struct mail_user **mail_user_r,
			       const char **error_r)
{
	const struct mail_storage_settings *mail_set;
	struct mail_user *mail_user;

	mail_user = mail_user_alloc(user->input.username, user->user_info,
				    user->user_set);
	mail_user_set_home(mail_user, *home == '\0' ? NULL : home);
	mail_user_set_vars(mail_user, geteuid(), ctx->service->name,
			   &user->input.local_ip, &user->input.remote_ip);

	mail_set = mail_user_set_get_storage_set(mail_user);

	if (mail_set->mail_debug) {
		i_debug("Effective uid=%s, gid=%s, home=%s",
			dec2str(geteuid()), dec2str(getegid()), home);
	}

	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP) != 0 &&
	    (ctx->flags & MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS) == 0) {
		/* we don't want to write core files to any users' home
		   directories since they could contain information about other
		   users' mails as well. so do no chdiring to home. */
	} else if (*home != '\0' &&
		   (ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR) == 0) {
		/* If possible chdir to home directory, so that core file
		   could be written in case we crash. */
		if (chdir(home) < 0) {
			if (errno == EACCES) {
				i_error("%s", eacces_error_get("chdir",
						t_strconcat(home, "/", NULL)));
			} if (errno != ENOENT)
				i_error("chdir(%s) failed: %m", home);
			else if (mail_set->mail_debug)
				i_debug("Home dir not found: %s", home);
		}
	}

	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,
		     struct mail_storage_service_input *input)
{
	static struct var_expand_table static_tab[] = {
		{ 'u', NULL, "user" },
		{ 'n', NULL, "username" },
		{ 'd', NULL, "domain" },
		{ 's', NULL, "service" },
		{ 'l', NULL, "lip" },
		{ 'r', NULL, "rip" },
		{ '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 = input->username;
	tab[1].value = t_strcut(input->username, '@');
	tab[2].value = strchr(input->username, '@');
	if (tab[2].value != NULL) tab[2].value++;
	tab[3].value = service->name;
	tab[4].value = net_ip2addr(&input->local_ip);
	tab[5].value = net_ip2addr(&input->remote_ip);
	tab[6].value = my_pid;
	tab[7].value = dec2str(geteuid());
	return tab;
}

static const char *
user_expand_varstr(struct master_service *service,
		   struct mail_storage_service_input *input, 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, input));
	return str_c(ret);
}

static void
mail_storage_service_init_log(struct mail_storage_service_ctx *ctx,
			      struct mail_storage_service_user *user)
{
	ctx->log_initialized = TRUE;
	T_BEGIN {
		string_t *str;
		struct ioloop_log *log;

		str = t_str_new(256);
		var_expand(str, user->user_set->mail_log_prefix,
			   get_var_expand_table(ctx->service, &user->input));
		master_service_init_log(ctx->service, str_c(str));

		log = io_loop_log_new(current_ioloop);
		io_loop_log_set_prefix(log, str_c(str));
		io_loop_log_unref(&log);
	} T_END;
}

static void mail_storage_service_time_moved(time_t old_time, time_t new_time)
{
	long diff = new_time - old_time;

	if (diff > 0) {
		if (diff > MAX_NOWARN_FORWARD_SECS)
			i_warning("Time jumped forwards %ld seconds", diff);
		return;
	}
	diff = -diff;

	if (diff > MAX_TIME_BACKWARDS_SLEEP) {
		i_fatal("Time just moved backwards by %ld seconds. "
			"This might cause a lot of problems, "
			"so I'll just kill myself now. "
			"http://wiki.dovecot.org/TimeMovedBackwards", diff);
	} else {
		i_error("Time just moved backwards by %ld seconds. "
			"I'll sleep now until we're back in present. "
			"http://wiki.dovecot.org/TimeMovedBackwards", diff);
		/* Sleep extra second to make sure usecs also grows. */
		diff++;

		while (diff > 0 && sleep(diff) != 0) {
			/* don't use sleep()'s return value, because
			   it could get us to a long loop in case
			   interrupts just keep coming */
			diff = old_time - time(NULL) + 1;
		}
	}
}

struct mail_storage_service_ctx *
mail_storage_service_init(struct master_service *service,
			  const struct setting_parser_info *set_roots[],
			  enum mail_storage_service_flags flags)
{
	struct mail_storage_service_ctx *ctx;
	pool_t pool;
	unsigned int count;

	(void)umask(0077);
	io_loop_set_time_moved_callback(current_ioloop,
					mail_storage_service_time_moved);

        mail_storage_init();
	mail_storage_register_all();
	mailbox_list_register_all();

	pool = pool_alloconly_create("mail storage service", 2048);
	ctx = p_new(pool, struct mail_storage_service_ctx, 1);
	ctx->pool = pool;
	ctx->service = service;
	ctx->flags = flags;

	/* @UNSAFE */
	if (set_roots == NULL)
		count = 0;
	else
		for (count = 0; set_roots[count] != NULL; count++) ;
	ctx->set_roots =
		p_new(pool, const struct setting_parser_info *, count + 2);
	ctx->set_roots[0] = &mail_user_setting_parser_info;
	if (set_roots != NULL) {
		memcpy(ctx->set_roots + 1, set_roots,
		       sizeof(*ctx->set_roots) * count);
	}

	/* do all the global initialization. delay initializing plugins until
	   we drop privileges the first time. */
	if ((flags & MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT) == 0) {
		/* note: we may not have read any settings yet, so this logging
		   may still be going to wrong location */
		const char *log_prefix = t_strconcat(service->name, ": ", NULL);

		master_service_init_log(service, log_prefix);
		io_loop_set_default_log_prefix(current_ioloop, log_prefix);
	}
	dict_drivers_register_builtin();
	return ctx;
}

struct auth_master_connection *
mail_storage_service_get_auth_conn(struct mail_storage_service_ctx *ctx)
{
	i_assert(ctx->conn != NULL);
	return ctx->conn;
}

int mail_storage_service_read_settings(struct mail_storage_service_ctx *ctx,
				       const struct mail_storage_service_input *input,
				       pool_t pool,
				       const struct setting_parser_info **user_info_r,
				       const struct setting_parser_context **parser_r,
				       const char **error_r)
{
	struct master_service_settings_input set_input;
	const struct setting_parser_info *const *roots;
	struct master_service_settings_output set_output;
	const struct dynamic_settings_parser *dyn_parsers;
	unsigned int i;

	memset(&set_input, 0, sizeof(set_input));
	set_input.roots = ctx->set_roots;
	set_input.preserve_user = TRUE;
	/* settings reader may exec doveconf, which is going to clear
	   environment, and if we're not doing a userdb lookup we want to
	   use $HOME */
	set_input.preserve_home =
		(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) == 0;
	set_input.use_sysexits =
		(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USE_SYSEXITS) != 0;

	if (input != NULL) {
		set_input.module = input->module;
		set_input.service = input->service;
		set_input.username = input->username;
		set_input.local_ip = input->local_ip;
		set_input.remote_ip = input->remote_ip;
	}
	if (ctx->set_cache == NULL) {
		ctx->set_cache_module = p_strdup(ctx->pool, set_input.module);
		ctx->set_cache_service = p_strdup(ctx->pool, set_input.service);
		ctx->set_cache = master_service_settings_cache_init(
			ctx->service, set_input.module, set_input.service);
	} else {
		/* already looked up settings at least once.
		   we really shouldn't be execing anymore. */
		set_input.never_exec = TRUE;
	}

	dyn_parsers = mail_storage_get_dynamic_parsers(pool);
	if (null_strcmp(set_input.module, ctx->set_cache_module) == 0 &&
	    null_strcmp(set_input.service, ctx->set_cache_service) == 0) {
		if (master_service_settings_cache_read(ctx->set_cache,
						       &set_input, dyn_parsers,
						       parser_r, error_r) < 0) {
			*error_r = t_strdup_printf(
				"Error reading configuration: %s", *error_r);
			return -1;
		}
	} else {
		settings_parser_dyn_update(pool, &set_input.roots, dyn_parsers);
		if (master_service_settings_read(ctx->service, &set_input,
						 &set_output, error_r) < 0) {
			*error_r = t_strdup_printf(
				"Error reading configuration: %s", *error_r);
			return -1;
		}
		*parser_r = ctx->service->set_parser;
	}

	roots = settings_parser_get_roots(*parser_r);
	for (i = 0; roots[i] != NULL; i++) {
		if (strcmp(roots[i]->module_name,
			   mail_user_setting_parser_info.module_name) == 0) {
			*user_info_r = roots[i];
			return 0;
		}
	}
	i_unreached();
	return -1;
}

static void
mail_storage_service_first_init(struct mail_storage_service_ctx *ctx,
				const struct setting_parser_info *user_info,
				const struct mail_user_settings *user_set)
{
	enum auth_master_flags flags = 0;

	i_assert(ctx->conn == NULL);

	ctx->debug = mail_user_set_get_mail_debug(user_info, user_set);
	if (ctx->debug)
		flags |= AUTH_MASTER_FLAG_DEBUG;
	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_IDLE_TIMEOUT) != 0)
		flags |= AUTH_MASTER_FLAG_NO_IDLE_TIMEOUT;
	ctx->conn = auth_master_init(user_set->auth_socket_path, flags);

	i_assert(mail_user_auth_master_conn == NULL);
	mail_user_auth_master_conn = ctx->conn;
}

static void
mail_storage_service_load_modules(struct mail_storage_service_ctx *ctx,
				  const struct setting_parser_info *user_info,
				  const struct mail_user_settings *user_set)
{
	struct module_dir_load_settings mod_set;

	if (*user_set->mail_plugins == '\0')
		return;
	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS) != 0)
		return;

	memset(&mod_set, 0, sizeof(mod_set));
	mod_set.version = master_service_get_version_string(ctx->service);
	mod_set.binary_name = master_service_get_name(ctx->service);
	mod_set.setting_name = "mail_plugins";
	mod_set.require_init_funcs = TRUE;
	mod_set.debug = mail_user_set_get_mail_debug(user_info, user_set);

	mail_storage_service_modules =
		module_dir_load_missing(mail_storage_service_modules,
					user_set->mail_plugin_dir,
					user_set->mail_plugins, &mod_set);
}

int mail_storage_service_lookup(struct mail_storage_service_ctx *ctx,
				const struct mail_storage_service_input *input,
				struct mail_storage_service_user **user_r,
				const char **error_r)
{
	const bool userdb_lookup = !input->no_userdb_lookup &&
		(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0;
	struct mail_storage_service_user *user;
	const char *username = input->username;
	const struct setting_parser_info *user_info;
	const struct mail_user_settings *user_set;
	const char *const *userdb_fields, *error;
	struct auth_user_reply reply;
	const struct setting_parser_context *set_parser;
	pool_t user_pool, temp_pool;
	int ret = 1;

	user_pool = pool_alloconly_create("mail storage service user", 1024*5);

	if (mail_storage_service_read_settings(ctx, input, user_pool,
					       &user_info, &set_parser,
					       &error) < 0) {
		i_error("user %s: %s", username, error);
		pool_unref(&user_pool);
		*error_r = MAIL_ERRSTR_CRITICAL_MSG;
		return -1;
	}
	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT) == 0 &&
	    !ctx->log_initialized) {
		/* initialize logging again, in case we only read the
		   settings for the first above */
		ctx->log_initialized = TRUE;
		master_service_init_log(ctx->service,
			t_strconcat(ctx->service->name, ": ", NULL));
	}
	user_set = settings_parser_get_list(set_parser)[1];

	if (ctx->conn == NULL)
		mail_storage_service_first_init(ctx, user_info, user_set);
	/* load global plugins */
	mail_storage_service_load_modules(ctx, user_info, user_set);

	temp_pool = pool_alloconly_create("userdb lookup", 2048);
	if (userdb_lookup) {
		ret = service_auth_userdb_lookup(ctx, input, temp_pool,
						 &username, &userdb_fields,
						 error_r);
		if (ret <= 0) {
			pool_unref(&temp_pool);
			pool_unref(&user_pool);
			return ret;
		}
	} else {
		userdb_fields = input->userdb_fields;
	}

	user = p_new(user_pool, struct mail_storage_service_user, 1);
	memset(user_r, 0, sizeof(user_r));
	user->pool = user_pool;
	user->input = *input;
	user->input.userdb_fields = NULL;
	user->input.username = p_strdup(user_pool, username);
	user->user_info = user_info;

	user->set_parser = settings_parser_dup(set_parser, user_pool);
	if (!settings_parser_check(user->set_parser, user_pool, &error))
		i_panic("settings_parser_check() failed: %s", error);

	user->user_set = settings_parser_get_list(user->set_parser)[1];
	user->gid_source = "mail_gid setting";
	user->uid_source = "mail_uid setting";

	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_DEBUG) != 0)
		(void)settings_parse_line(user->set_parser, "mail_debug=yes");

	if (!userdb_lookup) {
		const char *home = getenv("HOME");
		if (home != NULL)
			set_keyval(ctx, user, "mail_home", home);
	}

	if (userdb_fields != NULL) {
		auth_user_fields_parse(userdb_fields, temp_pool, &reply);
		if (user_reply_handle(ctx, user, &reply, &error) < 0) {
			i_error("user %s: Invalid settings in userdb: %s",
				username, error);
			*error_r = ERRSTR_INVALID_USER_SETTINGS;
			ret = -2;
		}
	}
	pool_unref(&temp_pool);

	/* load per-user plugins */
	if (ret > 0) {
		mail_storage_service_load_modules(ctx, user_info,
						  user->user_set);
	}

	*user_r = user;
	return ret;
}

int mail_storage_service_next(struct mail_storage_service_ctx *ctx,
			      struct mail_storage_service_user *user,
			      struct mail_user **mail_user_r)
{
	const struct mail_user_settings *user_set = user->user_set;
	const char *home, *chroot, *error;
	unsigned int len;
	bool disallow_root =
		(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT) != 0;
	bool temp_priv_drop =
		(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP) != 0;

	/* variable strings are expanded in mail_user_init(),
	   but we need the home and chroot sooner so do them separately here. */
	home = user_expand_varstr(ctx->service, &user->input,
				  user_set->mail_home);
	chroot = user_expand_varstr(ctx->service, &user->input,
				    user_set->mail_chroot);

	if (*home != '/' && *home != '\0') {
		i_error("user %s: "
			"Relative home directory paths not supported: %s",
			user->input.username, home);
		return -2;
	}

	len = strlen(chroot);
	if (len > 2 && strcmp(chroot + len - 2, "/.") == 0 &&
	    strncmp(home, chroot, len - 2) == 0) {
		/* mail_chroot = /chroot/. means that the home dir already
		   contains the chroot dir. remove it from home. */
		if (!temp_priv_drop) {
			home += len - 2;
			if (*home == '\0')
				home = "/";
			chroot = t_strndup(chroot, len - 2);

			set_keyval(ctx, user, "mail_home", home);
			set_keyval(ctx, user, "mail_chroot", chroot);
		}
	} else if (len > 0 && temp_priv_drop) {
		/* we're dropping privileges only temporarily, so we can't
		   chroot. fix home directory so we can access it. */
		if (*home == '\0' || strcmp(home, "/") == 0)
			home = chroot;
		else
			home = t_strconcat(chroot, home, NULL);
		chroot = "";
		set_keyval(ctx, user, "mail_home", home);
	}

	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT) == 0)
		mail_storage_service_init_log(ctx, user);

	if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS) == 0) {
		if (service_drop_privileges(user, user_set, home, chroot,
					    disallow_root, temp_priv_drop,
					    FALSE, &error) < 0) {
			i_error("user %s: Couldn't drop privileges: %s",
				user->input.username, error);
			return -1;
		}
		if (!temp_priv_drop ||
		    (ctx->flags & MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS) != 0)
			restrict_access_allow_coredumps(TRUE);
	}

	/* privileges are dropped. initialize plugins that haven't been
	   initialized yet. */
	module_dir_init(mail_storage_service_modules);

	if (mail_storage_service_init_post(ctx, user, home,
					   mail_user_r, &error) < 0) {
		i_error("user %s: Initialization failed: %s",
			user->input.username, error);
		return -2;
	}
	return 0;
}

void mail_storage_service_restrict_setenv(struct mail_storage_service_ctx *ctx,
					  struct mail_storage_service_user *user)
{
	const struct mail_user_settings *user_set = user->user_set;
	const char *home, *chroot, *error;

	home = user_expand_varstr(ctx->service, &user->input,
				  user_set->mail_home);
	chroot = user_expand_varstr(ctx->service, &user->input,
				    user_set->mail_chroot);

	if (service_drop_privileges(user, user_set, home, chroot,
				    FALSE, FALSE, TRUE,
				    &error) < 0)
		i_fatal("%s", error);
}

int mail_storage_service_lookup_next(struct mail_storage_service_ctx *ctx,
				     const struct mail_storage_service_input *input,
				     struct mail_storage_service_user **user_r,
				     struct mail_user **mail_user_r,
				     const char **error_r)
{
	struct mail_storage_service_user *user;
	int ret;

	ret = mail_storage_service_lookup(ctx, input, &user, error_r);
	if (ret <= 0)
		return ret;

	ret = mail_storage_service_next(ctx, user, mail_user_r);
	if (ret < 0) {
		mail_storage_service_user_free(&user);
		*error_r = ret == -2 ? ERRSTR_INVALID_USER_SETTINGS :
			MAIL_ERRSTR_CRITICAL_MSG;
		return ret;
	}
	*user_r = user;
	return 1;
}

void mail_storage_service_user_free(struct mail_storage_service_user **_user)
{
	struct mail_storage_service_user *user = *_user;

	*_user = NULL;
	settings_parser_deinit(&user->set_parser);
	pool_unref(&user->pool);
}

void mail_storage_service_init_settings(struct mail_storage_service_ctx *ctx,
					const struct mail_storage_service_input *input)
{
	const struct setting_parser_info *user_info;
	const struct mail_user_settings *user_set;
	const struct setting_parser_context *set_parser;
	const char *error;
	pool_t temp_pool;

	if (ctx->conn != NULL)
		return;

	temp_pool = pool_alloconly_create("service all settings", 4096);
	if (mail_storage_service_read_settings(ctx, input, temp_pool,
					       &user_info, &set_parser,
					       &error) < 0)
		i_fatal("%s", error);
	user_set = settings_parser_get_list(set_parser)[1];

	mail_storage_service_first_init(ctx, user_info, user_set);
	pool_unref(&temp_pool);
}

unsigned int
mail_storage_service_all_init(struct mail_storage_service_ctx *ctx)
{
	if (ctx->auth_list != NULL)
		(void)auth_master_user_list_deinit(&ctx->auth_list);
	mail_storage_service_init_settings(ctx, NULL);

	ctx->auth_list = auth_master_user_list_init(ctx->conn);
	return auth_master_user_list_count(ctx->auth_list);
}

int mail_storage_service_all_next(struct mail_storage_service_ctx *ctx,
				  const char **username_r)
{
	i_assert((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0);

	*username_r = auth_master_user_list_next(ctx->auth_list);
	if (*username_r != NULL)
		return 1;
	return auth_master_user_list_deinit(&ctx->auth_list);
}

void mail_storage_service_deinit(struct mail_storage_service_ctx **_ctx)
{
	struct mail_storage_service_ctx *ctx = *_ctx;

	*_ctx = NULL;
	if (ctx->auth_list != NULL)
		(void)auth_master_user_list_deinit(&ctx->auth_list);
	if (ctx->conn != NULL) {
		if (mail_user_auth_master_conn == ctx->conn)
			mail_user_auth_master_conn = NULL;
		auth_master_deinit(&ctx->conn);
	}
	if (ctx->set_cache != NULL)
		master_service_settings_cache_deinit(&ctx->set_cache);
	pool_unref(&ctx->pool);

	module_dir_unload(&mail_storage_service_modules);
	mail_storage_deinit();
	dict_drivers_unregister_builtin();
}

void **mail_storage_service_user_get_set(struct mail_storage_service_user *user)
{
	return settings_parser_get_list(user->set_parser) + 1;
}

const struct mail_storage_service_input *
mail_storage_service_user_get_input(struct mail_storage_service_user *user)
{
	return &user->input;
}

struct setting_parser_context *
mail_storage_service_user_get_settings_parser(struct mail_storage_service_user *user)
{
	return user->set_parser;
}

void *mail_storage_service_get_settings(struct master_service *service)
{
	void **sets, *set;

	T_BEGIN {
		sets = master_service_settings_get_others(service);
		set = sets[1];
	} T_END;
	return set;
}