view src/lib-storage/mailbox-list.c @ 22295:cebd11880aea

lib-storage: Cleanup - move code to mailbox_list_get_permissions_stat() Comments changed also a bit, but no functional changes.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Mon, 26 Jun 2017 18:41:27 +0300
parents 948b471d551d
children 0dd7baafb46e
line wrap: on
line source

/* Copyright (c) 2006-2017 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "abspath.h"
#include "ioloop.h"
#include "file-create-locked.h"
#include "mkdir-parents.h"
#include "str.h"
#include "sha1.h"
#include "hash.h"
#include "home-expand.h"
#include "time-util.h"
#include "unichar.h"
#include "settings-parser.h"
#include "iostream-ssl.h"
#include "fs-api-private.h"
#include "imap-utf7.h"
#include "mailbox-log.h"
#include "mailbox-tree.h"
#include "mail-storage-private.h"
#include "mail-storage-hooks.h"
#include "mailbox-list-private.h"

#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>

#define MAILBOX_LIST_FS_CONTEXT(obj) \
	MODULE_CONTEXT(obj, mailbox_list_fs_module)

struct mailbox_list_fs_context {
	union fs_api_module_context module_ctx;
	struct mailbox_list *list;
};

struct mailbox_list_module_register mailbox_list_module_register = { 0 };

static ARRAY(const struct mailbox_list *) mailbox_list_drivers;
static MODULE_CONTEXT_DEFINE_INIT(mailbox_list_fs_module,
				  &fs_api_module_register);

void mailbox_lists_init(void)
{
	i_array_init(&mailbox_list_drivers, 4);
}

void mailbox_lists_deinit(void)
{
	array_free(&mailbox_list_drivers);
}

static bool mailbox_list_driver_find(const char *name, unsigned int *idx_r)
{
	const struct mailbox_list *const *drivers;
	unsigned int i, count;

	drivers = array_get(&mailbox_list_drivers, &count);
	for (i = 0; i < count; i++) {
		if (strcasecmp(drivers[i]->name, name) == 0) {
			*idx_r = i;
			return TRUE;
		}
	}
	return FALSE;
}

void mailbox_list_register(const struct mailbox_list *list)
{
	unsigned int idx;

	if (mailbox_list_driver_find(list->name, &idx)) {
		i_fatal("mailbox_list_register(%s): duplicate driver",
			list->name);
	}

	array_append(&mailbox_list_drivers, &list, 1);
}

void mailbox_list_unregister(const struct mailbox_list *list)
{
	unsigned int idx;

	if (!mailbox_list_driver_find(list->name, &idx)) {
		i_fatal("mailbox_list_unregister(%s): unknown driver",
			list->name);
	}
	array_delete(&mailbox_list_drivers, idx, 1);
}

const struct mailbox_list *
mailbox_list_find_class(const char *driver)
{
	const struct mailbox_list *const *class_p;
	unsigned int idx;

	if (!mailbox_list_driver_find(driver, &idx))
		return NULL;

	class_p = array_idx(&mailbox_list_drivers, idx);
	return *class_p;
}

int mailbox_list_create(const char *driver, struct mail_namespace *ns,
			const struct mailbox_list_settings *set,
			enum mailbox_list_flags flags,
			struct mailbox_list **list_r, const char **error_r)
{
	const struct mailbox_list *class;
	struct mailbox_list *list;

	i_assert(ns->list == NULL ||
		 (flags & MAILBOX_LIST_FLAG_SECONDARY) != 0);

	i_assert(set->subscription_fname == NULL ||
		 *set->subscription_fname != '\0');

	if ((class = mailbox_list_find_class(driver)) == NULL) {
		*error_r = "Unknown driver name";
		return -1;
	}

	if ((class->props & MAILBOX_LIST_PROP_NO_MAILDIR_NAME) != 0 &&
	    *set->maildir_name != '\0') {
		*error_r = "maildir_name not supported by this driver";
		return -1;
	}
	if ((class->props & MAILBOX_LIST_PROP_NO_ALT_DIR) != 0 &&
	    set->alt_dir != NULL) {
		*error_r = "alt_dir not supported by this driver";
		return -1;
	}

	i_assert(set->root_dir == NULL || *set->root_dir != '\0' ||
		 (class->props & MAILBOX_LIST_PROP_NO_ROOT) != 0);

	list = class->v.alloc();
	array_create(&list->module_contexts, list->pool, sizeof(void *), 5);

	list->ns = ns;
	list->mail_set = ns->mail_set;
	list->flags = flags;
	list->root_permissions.file_create_mode = (mode_t)-1;
	list->root_permissions.dir_create_mode = (mode_t)-1;
	list->root_permissions.file_create_gid = (gid_t)-1;
	list->changelog_timestamp = (time_t)-1;

	/* copy settings */
	if (set->root_dir != NULL) {
		list->set.root_dir = p_strdup(list->pool, set->root_dir);
		list->set.index_dir = set->index_dir == NULL ||
			strcmp(set->index_dir, set->root_dir) == 0 ? NULL :
			p_strdup(list->pool, set->index_dir);
		list->set.index_pvt_dir = set->index_pvt_dir == NULL ||
			strcmp(set->index_pvt_dir, set->root_dir) == 0 ? NULL :
			p_strdup(list->pool, set->index_pvt_dir);
		list->set.control_dir = set->control_dir == NULL ||
			strcmp(set->control_dir, set->root_dir) == 0 ? NULL :
			p_strdup(list->pool, set->control_dir);
	}

	list->set.inbox_path = p_strdup(list->pool, set->inbox_path);
	list->set.subscription_fname =
		p_strdup(list->pool, set->subscription_fname);
	list->set.list_index_fname =
		p_strdup(list->pool, set->list_index_fname);
	list->set.maildir_name =
		p_strdup(list->pool, set->maildir_name);
	list->set.mailbox_dir_name =
		p_strdup(list->pool, set->mailbox_dir_name);
	list->set.alt_dir = p_strdup(list->pool, set->alt_dir);
	list->set.alt_dir_nocheck = set->alt_dir_nocheck;
	list->set.volatile_dir = p_strdup(list->pool, set->volatile_dir);
	list->set.index_control_use_maildir_name =
		set->index_control_use_maildir_name;
	list->set.iter_from_index_dir = set->iter_from_index_dir;

	if (*set->mailbox_dir_name == '\0')
		list->set.mailbox_dir_name = "";
	else if (set->mailbox_dir_name[strlen(set->mailbox_dir_name)-1] == '/') {
		list->set.mailbox_dir_name =
			p_strdup(list->pool, set->mailbox_dir_name);
	} else {
		list->set.mailbox_dir_name =
			p_strconcat(list->pool, set->mailbox_dir_name, "/", NULL);
	}
	list->set.escape_char = set->escape_char;
	list->set.broken_char = set->broken_char;
	list->set.utf8 = set->utf8;

	if (list->v.init != NULL) {
		if (list->v.init(list, error_r) < 0) {
			list->v.deinit(list);
			return -1;
		}
	}

	if (ns->mail_set->mail_debug) {
		i_debug("%s: root=%s, index=%s, indexpvt=%s, control=%s, inbox=%s, alt=%s",
			list->name,
			list->set.root_dir == NULL ? "" : list->set.root_dir,
			list->set.index_dir == NULL ? "" : list->set.index_dir,
			list->set.index_pvt_dir == NULL ? "" : list->set.index_pvt_dir,
			list->set.control_dir == NULL ?
			"" : list->set.control_dir,
			list->set.inbox_path == NULL ?
			"" : list->set.inbox_path,
			list->set.alt_dir == NULL ? "" : list->set.alt_dir);
	}
	if ((flags & MAILBOX_LIST_FLAG_SECONDARY) == 0)
		mail_namespace_finish_list_init(ns, list);

	*list_r = list;

	hook_mailbox_list_created(list);
	return 0;
}

static int fix_path(struct mail_user *user, const char *path, bool expand_home,
		    const char **path_r, const char **error_r)
{
	size_t len = strlen(path);

	if (len > 1 && path[len-1] == '/')
		path = t_strndup(path, len-1);
	if (!expand_home) {
		/* no ~ expansion */
	} else if (path[0] == '~' && path[1] != '/' && path[1] != '\0') {
		/* ~otheruser/dir */
		if (home_try_expand(&path) < 0) {
			*error_r = t_strconcat(
				"No home directory for system user. "
				"Can't expand ", t_strcut(path, '/'),
				" for ", NULL);
			return -1;
		}
	} else {
		if (mail_user_try_home_expand(user, &path) < 0) {
			*error_r = "Home directory not set for user. "
				"Can't expand ~/ for ";
			return -1;
		}
	}
	*path_r = path;
	return 0;
}

static const char *split_next_arg(const char *const **_args)
{
	const char *const *args = *_args;
	const char *str = args[0];

	args++;
	while (*args != NULL && **args == '\0') {
		args++;
		if (*args == NULL) {
			/* string ends with ":", just ignore it. */
			break;
		}
		str = t_strconcat(str, ":", *args, NULL);
		args++;
	}
	*_args = args;
	return str;
}

void mailbox_list_settings_init_defaults(struct mailbox_list_settings *set_r)
{
	i_zero(set_r);
	set_r->mailbox_dir_name = "";
	set_r->maildir_name = "";
	set_r->list_index_fname = MAILBOX_LIST_INDEX_DEFAULT_PREFIX;
}

static int
mailbox_list_settings_parse_full(struct mail_user *user, const char *data,
				 bool expand_home,
				 struct mailbox_list_settings *set_r,
				 const char **error_r)
{
	const char *const *tmp, *key, *value, **dest, *str, *error;

	*error_r = NULL;

	mailbox_list_settings_init_defaults(set_r);
	if (*data == '\0')
		return 0;

	/* <root dir> */
	tmp = t_strsplit(data, ":");
	str = split_next_arg(&tmp);
	if (fix_path(user, str, expand_home, &set_r->root_dir, &error) < 0) {
		*error_r = t_strconcat(error, "mail root dir in: ", data, NULL);
		return -1;
	}
	if (strncmp(set_r->root_dir, "INBOX=", 6) == 0) {
		/* probably mbox user trying to avoid root_dir */
		*error_r = t_strconcat("Mail root directory not given: ",
				       data, NULL);
		return -1;
	}

	while (*tmp != NULL) {
		str = split_next_arg(&tmp);
		if (strcmp(str, "UTF-8") == 0) {
			set_r->utf8 = TRUE;
			continue;
		}

		value = strchr(str, '=');
		if (value == NULL) {
			key = str;
			value = "";
		} else {
			key = t_strdup_until(str, value);
			value++;
		}

		if (strcmp(key, "INBOX") == 0)
			dest = &set_r->inbox_path;
		else if (strcmp(key, "INDEX") == 0)
			dest = &set_r->index_dir;
		else if (strcmp(key, "INDEXPVT") == 0)
			dest = &set_r->index_pvt_dir;
		else if (strcmp(key, "CONTROL") == 0)
			dest = &set_r->control_dir;
		else if (strcmp(key, "ALT") == 0)
			dest = &set_r->alt_dir;
		else if (strcmp(key, "ALTNOCHECK") == 0) {
			set_r->alt_dir_nocheck = TRUE;
			continue;
		} else if (strcmp(key, "LAYOUT") == 0)
			dest = &set_r->layout;
		else if (strcmp(key, "SUBSCRIPTIONS") == 0)
			dest = &set_r->subscription_fname;
		else if (strcmp(key, "DIRNAME") == 0)
			dest = &set_r->maildir_name;
		else if (strcmp(key, "MAILBOXDIR") == 0)
			dest = &set_r->mailbox_dir_name;
		else if (strcmp(key, "VOLATILEDIR") == 0)
			dest = &set_r->volatile_dir;
		else if (strcmp(key, "LISTINDEX") == 0)
			dest = &set_r->list_index_fname;
		else if (strcmp(key, "FULLDIRNAME") == 0) {
			set_r->index_control_use_maildir_name = TRUE;
			dest = &set_r->maildir_name;
		} else if (strcmp(key, "ITERINDEX") == 0) {
			set_r->iter_from_index_dir = TRUE;
			continue;
		} else {
			*error_r = t_strdup_printf("Unknown setting: %s", key);
			return -1;
		}
		if (fix_path(user, value, expand_home, dest, &error) < 0) {
			*error_r = t_strconcat(error, key, " in: ", data, NULL);
			return -1;
		}
	}

	if (set_r->index_dir != NULL && strcmp(set_r->index_dir, "MEMORY") == 0)
		set_r->index_dir = "";
	if (set_r->iter_from_index_dir &&
	    (set_r->index_dir == NULL || set_r->index_dir[0] == '\0')) {
		*error_r = "ITERINDEX requires INDEX to be explicitly set";
		return -1;
	}
	return 0;
}

int mailbox_list_settings_parse(struct mail_user *user, const char *data,
				struct mailbox_list_settings *set_r,
				const char **error_r)
{
	return mailbox_list_settings_parse_full(user, data, TRUE,
						set_r, error_r);
}

const char *mailbox_list_get_unexpanded_path(struct mailbox_list *list,
					     enum mailbox_list_path_type type)
{
	const struct mail_storage_settings *mail_set;
	const char *location = list->ns->unexpanded_set->location;
	struct mail_user *user = list->ns->user;
	struct mailbox_list_settings set;
	const char *p, *path, *error;

	if (*location == SETTING_STRVAR_EXPANDED[0]) {
		/* set using -o or userdb lookup. */
		return "";
	}

	i_assert(*location == SETTING_STRVAR_UNEXPANDED[0]);
	location++;

	if (*location == '\0') {
		mail_set = mail_user_set_get_driver_settings(user->set_info,
			user->unexpanded_set, MAIL_STORAGE_SET_DRIVER_NAME);
		i_assert(mail_set != NULL);
		location = mail_set->mail_location;
		if (*location == SETTING_STRVAR_EXPANDED[0])
			return "";
		i_assert(*location == SETTING_STRVAR_UNEXPANDED[0]);
		location++;
	}

	/* type:settings */
	p = strchr(location, ':');
	if (p == NULL)
		return "";

	if (mailbox_list_settings_parse_full(user, p + 1, FALSE,
					     &set, &error) < 0)
		return "";
	if (mailbox_list_set_get_root_path(&set, type, &path) <= 0)
		return "";
	return path;
}

static bool need_escape_dirstart(const char *vname, const char *maildir_name)
{
	size_t len;

	if (vname[0] == '.') {
		if (vname[1] == '\0' || vname[1] == '/')
			return TRUE; /* "." */
		if (vname[1] == '.' && (vname[2] == '\0' || vname[2] == '/'))
			return TRUE; /* ".." */
	}
	if (*maildir_name != '\0') {
		len = strlen(maildir_name);
		if (strncmp(maildir_name, vname, len) == 0 &&
		    (vname[len] == '\0' || vname[len] == '/'))
			return TRUE; /* e.g. dbox-Mails */
	}
	return FALSE;
}

const char *
mailbox_list_escape_name_params(const char *vname, const char *ns_prefix,
				char ns_sep, char list_sep, char escape_char,
				const char *maildir_name)
{
	size_t ns_prefix_len = strlen(ns_prefix);
	string_t *escaped_name = t_str_new(64);
	char dirstart = TRUE;

	/* no escaping of namespace prefix */
	if (strncmp(ns_prefix, vname, ns_prefix_len) == 0) {
		str_append_n(escaped_name, vname, ns_prefix_len);
		vname += ns_prefix_len;
	}

	/* escape the mailbox name */
	if (*vname == '~') {
		str_printfa(escaped_name, "%c%02x", escape_char, *vname);
		vname++;
		dirstart = FALSE;
	}
	for (; *vname != '\0'; vname++) {
		if (*vname == ns_sep)
			str_append_c(escaped_name, list_sep);
		else if (*vname == list_sep ||
			 *vname == escape_char ||
			 *vname == '/' ||
			 (dirstart &&
			  need_escape_dirstart(vname, maildir_name))) {
			str_printfa(escaped_name, "%c%02x",
				    escape_char, *vname);
		} else {
			str_append_c(escaped_name, *vname);
		}
		dirstart = *vname == '/';
	}
	return str_c(escaped_name);
}

const char *
mailbox_list_escape_name(struct mailbox_list *list, const char *vname)
{
	return mailbox_list_escape_name_params(vname, list->ns->prefix,
				mail_namespace_get_sep(list->ns),
				mailbox_list_get_hierarchy_sep(list),
				list->set.escape_char, list->set.maildir_name);
}

static int
mailbox_list_unescape_broken_chars(struct mailbox_list *list, char *name)
{
	char *src, *dest;
	unsigned char chr;

	if ((src = strchr(name, list->set.broken_char)) == NULL)
		return 0;
	dest = src;

	while (*src != '\0') {
		if (*src == list->set.broken_char) {
			if (src[1] >= '0' && src[1] <= '9')
				chr = (src[1]-'0') * 0x10;
			else if (src[1] >= 'a' && src[1] <= 'f')
				chr = (src[1]-'a' + 10) * 0x10;
			else
				return -1;

			if (src[2] >= '0' && src[2] <= '9')
				chr += src[2]-'0';
			else if (src[2] >= 'a' && src[2] <= 'f')
				chr += src[2]-'a' + 10;
			else
				return -1;
			*dest++ = chr;
			src += 3;
		} else {
			*dest++ = *src++;
		}
	}
	*dest++ = '\0';
	return 0;
}

static char *mailbox_list_convert_sep(const char *storage_name, char src, char dest)
{
	char *ret, *p;

	ret = p_strdup(unsafe_data_stack_pool, storage_name);
	for (p = ret; *p != '\0'; p++) {
		if (*p == src)
			*p = dest;
	}
	return ret;
}

const char *mailbox_list_default_get_storage_name(struct mailbox_list *list,
						  const char *vname)
{
	struct mail_namespace *ns = list->ns;
	size_t prefix_len = strlen(ns->prefix);
	const char *storage_name = vname;
	string_t *str;
	char list_sep, ns_sep, *ret;

	if (strcasecmp(storage_name, "INBOX") == 0 &&
	    (ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0)
		storage_name = "INBOX";
	else if (list->set.escape_char != '\0')
		storage_name = mailbox_list_escape_name(list, vname);

	if (prefix_len > 0 && (strcmp(storage_name, "INBOX") != 0 ||
			       (ns->flags & NAMESPACE_FLAG_INBOX_USER) == 0)) {
		/* skip namespace prefix, except if this is INBOX */
		if (strncmp(ns->prefix, storage_name, prefix_len) == 0)
			storage_name += prefix_len;
		else if (strncmp(ns->prefix, storage_name, prefix_len-1) == 0 &&
			 strlen(storage_name) == prefix_len-1 &&
			 ns->prefix[prefix_len-1] == mail_namespace_get_sep(ns)) {
			/* trying to access the namespace prefix itself */
			storage_name = "";
		} else {
			/* we're converting a nonexistent mailbox name,
			   such as a LIST pattern. */
		}
	}

	if (!list->set.utf8) {
		/* UTF-8 -> mUTF-7 conversion */
		str = t_str_new(strlen(storage_name)*2);
		if (imap_utf8_to_utf7(storage_name, str) < 0)
			i_panic("Mailbox name not UTF-8: %s", vname);
		storage_name = str_c(str);
	}

	list_sep = mailbox_list_get_hierarchy_sep(list);
	ns_sep = mail_namespace_get_sep(ns);

	if (*storage_name == '\0' && ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
	    (ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
	    !list->mail_set->mail_shared_explicit_inbox) {
		/* opening shared/$user. it's the same as INBOX. */
		storage_name = "INBOX";
	}

	if (list_sep != ns_sep && list->set.escape_char == '\0') {
		if (ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
		    (ns->flags & NAMESPACE_FLAG_AUTOCREATED) == 0) {
			/* shared namespace root. the backend storage's
			   hierarchy separator isn't known yet, so do
			   nothing. */
			return storage_name;
		}

		ret = mailbox_list_convert_sep(storage_name, ns_sep, list_sep);
	} else if (list->set.broken_char == '\0' ||
		   strchr(storage_name, list->set.broken_char) == NULL) {
		/* no need to convert broken chars */
		return storage_name;
	} else {
		ret = p_strdup(unsafe_data_stack_pool, storage_name);
	}

	if (list->set.broken_char != '\0') {
		if (mailbox_list_unescape_broken_chars(list, ret) < 0) {
			ret = mailbox_list_convert_sep(storage_name,
						       ns_sep, list_sep);
		}
	}
	return ret;
}

const char *mailbox_list_get_storage_name(struct mailbox_list *list,
					  const char *vname)
{
	return list->v.get_storage_name(list, vname);
}

const char *
mailbox_list_unescape_name_params(const char *src, const char *ns_prefix,
				  char ns_sep, char list_sep, char escape_char)
{
	size_t ns_prefix_len = strlen(ns_prefix);
	string_t *dest = t_str_new(strlen(src));
	unsigned int num;

	if (strncmp(src, ns_prefix, ns_prefix_len) == 0) {
		str_append_n(dest, src, ns_prefix_len);
		src += ns_prefix_len;
	}

	for (; *src != '\0'; src++) {
		if (*src == escape_char &&
		    i_isxdigit(src[1]) && i_isxdigit(src[2])) {
			if (src[1] >= '0' && src[1] <= '9')
				num = src[1] - '0';
			else
				num = i_toupper(src[1]) - 'A' + 10;
			num *= 16;
			if (src[2] >= '0' && src[2] <= '9')
				num += src[2] - '0';
			else
				num += i_toupper(src[2]) - 'A' + 10;

			str_append_c(dest, num);
			src += 2;
		} else if (*src == list_sep)
			str_append_c(dest, ns_sep);
		else
			str_append_c(dest, *src);
	}
	return str_c(dest);
}

const char *
mailbox_list_unescape_name(struct mailbox_list *list, const char *src)
{
	return mailbox_list_unescape_name_params(src, list->ns->prefix,
				mail_namespace_get_sep(list->ns),
				mailbox_list_get_hierarchy_sep(list),
				list->set.escape_char);
}

static void
mailbox_list_escape_broken_chars(struct mailbox_list *list, string_t *str)
{
	unsigned int i;
	char buf[3];

	if (strchr(str_c(str), list->set.broken_char) == NULL)
		return;

	for (i = 0; i < str_len(str); i++) {
		if (str_c(str)[i] == list->set.broken_char) {
			i_snprintf(buf, sizeof(buf), "%02x",
				   list->set.broken_char);
			str_insert(str, i+1, buf);
			i += 2;
		}
	}
}

static void
mailbox_list_escape_broken_name(struct mailbox_list *list,
				const char *vname, string_t *str)
{
	str_truncate(str, 0);
	for (; *vname != '\0'; vname++) {
		if (*vname == '&' || (unsigned char)*vname >= 0x80) {
			str_printfa(str, "%c%02x", list->set.broken_char,
				    (unsigned char)*vname);
		} else {
			str_append_c(str, *vname);
		}
	}
}

const char *mailbox_list_default_get_vname(struct mailbox_list *list,
					   const char *storage_name)
{
	size_t i, prefix_len, name_len;
	const char *vname = storage_name;
	char list_sep, ns_sep, *ret;

	if ((list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
	    strcmp(vname, "INBOX") == 0 &&
	    list->ns->user == list->ns->owner) {
		/* user's INBOX - use as-is. NOTE: don't do case-insensitive
		   comparison, otherwise we can't differentiate between INBOX
		   and <ns prefix>/inBox. */
		return vname;
	}
	if (strcmp(vname, "INBOX") == 0 &&
	    list->ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
	    (list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
	    !list->mail_set->mail_shared_explicit_inbox) {
		/* convert to shared/$user, we don't really care about the
		   INBOX suffix here. */
		vname = "";
	}
	if (*vname == '\0') {
		/* return namespace prefix without the separator */
		if (list->ns->prefix_len == 0)
			return list->ns->prefix;
		else {
			return t_strndup(list->ns->prefix,
					 list->ns->prefix_len - 1);
		}
	} else if (!list->set.utf8) {
		/* mUTF-7 -> UTF-8 conversion */
		string_t *str = t_str_new(strlen(vname));
		if (imap_utf7_to_utf8(vname, str) == 0) {
			if (list->set.broken_char != '\0')
				mailbox_list_escape_broken_chars(list, str);
			vname = str_c(str);
		} else if (list->set.broken_char != '\0') {
			mailbox_list_escape_broken_name(list, vname, str);
			vname = str_c(str);
		}
	}

	prefix_len = strlen(list->ns->prefix);
	if (list->set.escape_char != '\0') {
		vname = mailbox_list_unescape_name(list, vname);
		return prefix_len == 0 ? vname :
			t_strconcat(list->ns->prefix, vname, NULL);
	}

	list_sep = mailbox_list_get_hierarchy_sep(list);
	ns_sep = mail_namespace_get_sep(list->ns);

	if (list_sep != ns_sep || prefix_len > 0) {
		/* @UNSAFE */
		name_len = strlen(vname);
		ret = t_malloc(MALLOC_ADD(prefix_len, name_len) + 1);
		memcpy(ret, list->ns->prefix, prefix_len);
		for (i = 0; i < name_len; i++) {
			ret[i + prefix_len] =
				vname[i] == list_sep ? ns_sep : vname[i];
		}
		ret[i + prefix_len] = '\0';
		vname = ret;
	}
	return vname;
}

const char *mailbox_list_get_vname(struct mailbox_list *list, const char *name)
{
	return list->v.get_vname(list, name);
}

void mailbox_list_destroy(struct mailbox_list **_list)
{
	struct mailbox_list *list = *_list;

	*_list = NULL;
	i_free_and_null(list->error_string);
	i_free(list->last_internal_error);

	if (hash_table_is_created(list->guid_cache)) {
		hash_table_destroy(&list->guid_cache);
		pool_unref(&list->guid_cache_pool);
	}

	if (list->subscriptions != NULL)
		mailbox_tree_deinit(&list->subscriptions);
	if (list->changelog != NULL)
		mailbox_log_free(&list->changelog);
	list->v.deinit(list);
}

const char *mailbox_list_get_driver_name(const struct mailbox_list *list)
{
	return list->name;
}

const struct mailbox_list_settings *
mailbox_list_get_settings(const struct mailbox_list *list)
{
	return &list->set;
}

enum mailbox_list_flags mailbox_list_get_flags(const struct mailbox_list *list)
{
	return list->flags;
}

struct mail_namespace *
mailbox_list_get_namespace(const struct mailbox_list *list)
{
	return list->ns;
}

static mode_t get_dir_mode(mode_t mode)
{
	/* add the execute bit if either read or write bit is set */
	if ((mode & 0600) != 0) mode |= 0100;
	if ((mode & 0060) != 0) mode |= 0010;
	if ((mode & 0006) != 0) mode |= 0001;
	return mode;
}

struct mail_user *
mailbox_list_get_user(const struct mailbox_list *list)
{
	return list->ns->user;
}

static int
mailbox_list_get_storage_driver(struct mailbox_list *list, const char *driver,
				struct mail_storage **storage_r)
{
	struct mail_storage *const *storagep;
	const char *error, *data;

	array_foreach(&list->ns->all_storages, storagep) {
		if (strcmp((*storagep)->name, driver) == 0) {
			*storage_r = *storagep;
			return 0;
		}
	}

	data = strchr(list->ns->set->location, ':');
	if (data == NULL)
		data = "";
	else
		data++;
	if (mail_storage_create_full(list->ns, driver, data, 0,
				     storage_r, &error) < 0) {
		mailbox_list_set_critical(list,
			"Namespace %s: Failed to create storage '%s': %s",
			list->ns->prefix, driver, error);
		return -1;
	}
	return 0;
}

int mailbox_list_get_storage(struct mailbox_list **list, const char *vname,
			     struct mail_storage **storage_r)
{
	const struct mailbox_settings *set;

	if ((*list)->v.get_storage != NULL)
		return (*list)->v.get_storage(list, vname, storage_r);

	set = mailbox_settings_find((*list)->ns, vname);
	if (set != NULL && set->driver != NULL && set->driver[0] != '\0') {
		return mailbox_list_get_storage_driver(*list, set->driver,
						       storage_r);
	}
	*storage_r = mail_namespace_get_default_storage((*list)->ns);
	return 0;
}

void mailbox_list_get_default_storage(struct mailbox_list *list,
				      struct mail_storage **storage)
{
	*storage = mail_namespace_get_default_storage(list->ns);
}

char mailbox_list_get_hierarchy_sep(struct mailbox_list *list)
{
	/* the current API doesn't allow returning an error, so imap code
	   looks at the list's last error. make sure the error is cleared
	   so the error-check doesn't return something irrelevant */
	mailbox_list_clear_error(list);
	return list->v.get_hierarchy_sep(list);
}

static bool
mailbox_list_get_permissions_stat(struct mailbox_list *list, const char *path,
				  struct mailbox_permissions *permissions_r)
{
	struct stat st;

	if (stat(path, &st) < 0) {
		if (errno == EACCES) {
			mailbox_list_set_critical(list, "%s",
				mail_error_eacces_msg("stat", path));
		} else if (!ENOTFOUND(errno)) {
			mailbox_list_set_critical(list, "stat(%s) failed: %m",
						  path);
		} else if (list->mail_set->mail_debug) {
			i_debug("Namespace %s: %s doesn't exist yet, "
				"using default permissions",
				list->ns->prefix, path);
		}
		return FALSE;
	}

	permissions_r->file_uid = st.st_uid;
	permissions_r->file_gid = st.st_gid;
	permissions_r->file_create_mode = (st.st_mode & 0666) | 0600;
	permissions_r->dir_create_mode = (st.st_mode & 0777) | 0700;
	permissions_r->file_create_gid_origin = path;

	if (!S_ISDIR(st.st_mode)) {
		/* we're getting permissions from a file.
		   apply +x modes as necessary. */
		permissions_r->dir_create_mode =
			get_dir_mode(permissions_r->dir_create_mode);
	}

	if (S_ISDIR(st.st_mode) && (st.st_mode & S_ISGID) != 0) {
		/* directory's GID is used automatically for new files */
		permissions_r->file_create_gid = (gid_t)-1;
	} else if ((st.st_mode & 0070) >> 3 == (st.st_mode & 0007)) {
		/* group has same permissions as world, so don't bother
		   changing it */
		permissions_r->file_create_gid = (gid_t)-1;
	} else if (getegid() == st.st_gid) {
		/* using our own gid, no need to change it */
		permissions_r->file_create_gid = (gid_t)-1;
	} else {
		permissions_r->file_create_gid = st.st_gid;
	}
	if (!S_ISDIR(st.st_mode) &&
	    permissions_r->file_create_gid != (gid_t)-1) {
		/* we need to stat() the parent directory to see if
		   it has setgid-bit set */
		const char *p = strrchr(path, '/');
		const char *parent_path = p == NULL ? NULL :
			t_strdup_until(path, p);
		if (parent_path != NULL &&
		    stat(parent_path, &st) == 0 &&
		    (st.st_mode & S_ISGID) != 0) {
			/* directory's GID is used automatically for
			   new files */
			permissions_r->file_create_gid = (gid_t)-1;
		}
	}
	return TRUE;
}

static void ATTR_NULL(2)
mailbox_list_get_permissions_internal(struct mailbox_list *list,
				      const char *name,
				      struct mailbox_permissions *permissions_r)
{
	const char *path, *parent_name, *p;

	i_zero(permissions_r);

	/* use safe defaults */
	permissions_r->file_uid = (uid_t)-1;
	permissions_r->file_gid = (gid_t)-1;
	permissions_r->file_create_mode = 0600;
	permissions_r->dir_create_mode = 0700;
	permissions_r->file_create_gid = (gid_t)-1;
	permissions_r->file_create_gid_origin = "defaults";

	if (name != NULL) {
		if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR,
					  &path) < 0)
			name = NULL;
	}
	if (name == NULL) {
		(void)mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_DIR,
						 &path);
	}

	if (path == NULL ||
	    (list->flags & MAILBOX_LIST_FLAG_NO_MAIL_FILES) != 0) {
		/* no filesystem support in storage */
	} else if (mailbox_list_get_permissions_stat(list, path, permissions_r)) {
		/* got permissions from the given path */
		permissions_r->gid_origin_is_mailbox_path = name != NULL;
	} else if (name != NULL) {
		/* path couldn't be stat()ed, try parent mailbox */
		p = strrchr(name, mailbox_list_get_hierarchy_sep(list));
		if (p == NULL) {
			/* return root defaults */
			parent_name = NULL;
		} else {
			parent_name = t_strdup_until(name, p);
		}
		mailbox_list_get_permissions(list, parent_name,
					     permissions_r);
		return;
	} else {
		/* assume current defaults for mailboxes that don't exist or
		   can't be looked up for some other reason */
		permissions_r->file_uid = geteuid();
		permissions_r->file_gid = getegid();
	}
	if (name == NULL) {
		mailbox_permissions_copy(&list->root_permissions, permissions_r,
					 list->pool);
	}

	if (list->mail_set->mail_debug && name == NULL) {
		i_debug("Namespace %s: Using permissions from %s: "
			"mode=0%o gid=%s", list->ns->prefix,
			path != NULL ? path : "",
			(int)permissions_r->dir_create_mode,
			permissions_r->file_create_gid == (gid_t)-1 ? "default" :
			dec2str(permissions_r->file_create_gid));
	}
}

void mailbox_list_get_permissions(struct mailbox_list *list, const char *name,
				  struct mailbox_permissions *permissions_r)
{
	mailbox_list_get_permissions_internal(list, name, permissions_r);
}

void mailbox_list_get_root_permissions(struct mailbox_list *list,
				       struct mailbox_permissions *permissions_r)
{
	if (list->root_permissions.file_create_mode != (mode_t)-1)
		*permissions_r = list->root_permissions;
	else {
		mailbox_list_get_permissions_internal(list, NULL,
						      permissions_r);
	}
}

void mailbox_permissions_copy(struct mailbox_permissions *dest,
			      const struct mailbox_permissions *src,
			      pool_t pool)
{
	*dest = *src;
	dest->file_create_gid_origin =
		p_strdup(pool, src->file_create_gid_origin);
}

static const char *
get_expanded_path(const char *unexpanded_start, const char *unexpanded_stop,
		  const char *expanded_full)
{
	const char *ret;
	unsigned int i, slash_count = 0, slash2_count = 0;

	/* get the expanded path up to the same amount of '/' characters.
	   if there isn't the same amount of '/' characters, it means %variable
	   expansion added more of them and we can't handle this. */
	for (i = 0; unexpanded_start+i != unexpanded_stop; i++) {
		if (unexpanded_start[i] == '/')
			slash_count++;
	}
	for (; unexpanded_start[i] != '\0'; i++) {
		if (unexpanded_start[i] == '/')
			slash2_count++;
	}

	for (i = 0; expanded_full[i] != '\0'; i++) {
		if (expanded_full[i] == '/') {
			if (slash_count == 0)
				break;
			slash_count--;
		}
	}
	if (slash_count != 0)
		return "";

	ret = t_strndup(expanded_full, i);
	for (; expanded_full[i] != '\0'; i++) {
		if (expanded_full[i] == '/') {
			if (slash2_count == 0)
				return "";
			slash2_count--;
		}
	}
	if (slash2_count != 0)
		return "";
	return ret;
}

static int
mailbox_list_try_mkdir_root_parent(struct mailbox_list *list,
				   enum mailbox_list_path_type type,
				   struct mailbox_permissions *perm,
				   const char **error_r)
{
	const char *expanded, *unexpanded, *root_dir, *p;
	struct stat st;
	bool home = FALSE;

	/* get the directory path up to last %variable. for example
	   unexpanded path may be "/var/mail/%d/%2n/%n/Maildir", and we want
	   to get expanded="/var/mail/domain/nn" */
	unexpanded = mailbox_list_get_unexpanded_path(list, type);
	p = strrchr(unexpanded, '%');
	if ((p == unexpanded && p[1] == 'h') ||
	    (p == NULL && unexpanded[0] == '~')) {
		/* home directory used */
		if (!mailbox_list_get_root_path(list, type, &expanded))
			i_unreached();
		home = TRUE;
	} else if (p == NULL) {
		return 0;
	} else {
		while (p != unexpanded && *p != '/') p--;
		if (p == unexpanded)
			return 0;

		if (!mailbox_list_get_root_path(list, type, &expanded))
			i_unreached();
		expanded = get_expanded_path(unexpanded, p, expanded);
		if (*expanded == '\0')
			return 0;
	}

	/* get the first existing parent directory's permissions */
	if (stat_first_parent(expanded, &root_dir, &st) < 0) {
		*error_r = errno == EACCES ?
			mail_error_eacces_msg("stat", root_dir) :
			t_strdup_printf("stat(%s) failed: %m", root_dir);
		return -1;
	}

	/* if the parent directory doesn't have setgid-bit enabled, we don't
	   copy any permissions from it. */
	if ((st.st_mode & S_ISGID) == 0)
		return 0;

	if (!home) {
		/* assuming we have e.g. /var/vmail/%d/%n directory, here we
		   want to create up to /var/vmail/%d with permissions from
		   the parent directory. we never want to create the %n
		   directory itself. */
		if (root_dir == expanded) {
			/* this is the %n directory */
		} else {
			if (mkdir_parents_chgrp(expanded, st.st_mode,
						(gid_t)-1, root_dir) < 0 &&
			    errno != EEXIST) {
				*error_r = t_strdup_printf(
					"mkdir(%s) failed: %m", expanded);
				return -1;
			}
		}
		if (perm->file_create_gid == (gid_t)-1 &&
		    (perm->dir_create_mode & S_ISGID) == 0) {
			/* change the group for user directories */
			perm->dir_create_mode |= S_ISGID;
			perm->file_create_gid = getegid();
			perm->file_create_gid_origin = "egid";
			perm->gid_origin_is_mailbox_path = FALSE;
		}
	} else {
		/* when using %h and the parent has setgid-bit,
		   copy the permissions from it for the home we're creating */
		perm->file_create_mode = st.st_mode & 0666;
		perm->dir_create_mode = st.st_mode;
		perm->file_create_gid = (gid_t)-1;
		perm->file_create_gid_origin = "parent";
		perm->gid_origin_is_mailbox_path = FALSE;
	}
	return 0;
}

int mailbox_list_try_mkdir_root(struct mailbox_list *list, const char *path,
				enum mailbox_list_path_type type,
				const char **error_r)
{
	const char *root_dir;
	struct stat st;
	struct mailbox_permissions perm;

	if (stat(path, &st) == 0) {
		/* looks like it already exists, don't bother checking
		   further. */
		return 0;
	}

	mailbox_list_get_root_permissions(list, &perm);

	if (!mailbox_list_get_root_path(list, type, &root_dir))
		i_unreached();
	i_assert(strncmp(root_dir, path, strlen(root_dir)) == 0);
	if (strcmp(root_dir, path) != 0 && stat(root_dir, &st) == 0) {
		/* creating a subdirectory under an already existing root dir.
		   use the root's permissions */
	} else {
		if (mailbox_list_try_mkdir_root_parent(list, type,
						       &perm, error_r) < 0)
			return -1;
	}

	/* the rest of the directories exist only for one user. create them
	   with default directory permissions */
	if (mkdir_parents_chgrp(path, perm.dir_create_mode,
				perm.file_create_gid,
				perm.file_create_gid_origin) < 0 &&
	    errno != EEXIST) {
		if (errno == EACCES)
			*error_r = mail_error_create_eacces_msg("mkdir", path);
		else
			*error_r = t_strdup_printf("mkdir(%s) failed: %m", path);
		return -1;
	}
	return 0;
}

int mailbox_list_mkdir_root(struct mailbox_list *list, const char *path,
			    enum mailbox_list_path_type type)
{
	const char *error;

	if (mailbox_list_try_mkdir_root(list, path, type, &error) < 0) {
		mailbox_list_set_critical(list, "%s", error);
		return -1;
	}
	if (type == MAILBOX_LIST_PATH_TYPE_INDEX)
		list->index_root_dir_created = TRUE;
	return 0;
}

static bool
mailbox_list_is_valid_fs_name(struct mailbox_list *list, const char *name,
			      const char **error_r)
{
	bool ret, allow_internal_dirs;

	*error_r = NULL;

	if (list->mail_set->mail_full_filesystem_access)
		return TRUE;

	/* make sure it's not absolute path */
	if (*name == '/') {
		*error_r = "Begins with '/'";
		return FALSE;
	}
	if (*name == '~') {
		*error_r = "Begins with '~'";
		return FALSE;
	}

	/* make sure the mailbox name doesn't contain any foolishness:
	   "../" could give access outside the mailbox directory.
	   "./" and "//" could fool ACL checks.

	   some mailbox formats have reserved directory names, such as
	   Maildir's cur/new/tmp. if any of those would conflict with the
	   mailbox directory name, it's not valid. maildir++ is kludged here as
	   a special case because all of its mailbox dirs begin with "." */
	allow_internal_dirs = list->v.is_internal_name == NULL ||
		*list->set.maildir_name != '\0' ||
		strcmp(list->name, MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) == 0;
	T_BEGIN {
		const char *const *names;

		names = t_strsplit(name, "/");
		for (; *names != NULL; names++) {
			const char *n = *names;

			if (*n == '\0') {
				*error_r = "Has adjacent '/' chars";
				break; /* // */
			}
			if (*n == '.') {
				if (n[1] == '\0') {
					*error_r = "Contains '.' part";
					break; /* ./ */
				}
				if (n[1] == '.' && n[2] == '\0') {
					*error_r = "Contains '..' part";
					break; /* ../ */
				}
			}
			if (*list->set.maildir_name != '\0' &&
			    strcmp(list->set.maildir_name, n) == 0) {
				/* don't allow maildir_name to be used as part
				   of the mailbox name */
				*error_r = "Contains reserved name";
				break;
			}
			if (!allow_internal_dirs &&
			    list->v.is_internal_name(list, n)) {
				*error_r = "Contains reserved name";
				break;
			}
		}
		ret = *names == NULL;
	} T_END;

	return ret;
}


bool mailbox_list_is_valid_name(struct mailbox_list *list,
				const char *name, const char **error_r)
{
	if (*name == '\0') {
		if (*list->ns->prefix != '\0') {
			/* an ugly way to get to mailbox root (e.g. Maildir/
			   when it's not the INBOX) */
			return TRUE;
		}
		*error_r = "Name is empty";
		return FALSE;
	}

	/* either the list backend uses '/' as the hierarchy separator or
	   it doesn't use filesystem at all (PROP_NO_ROOT) */
	if ((list->props & MAILBOX_LIST_PROP_NO_ROOT) == 0 &&
	    mailbox_list_get_hierarchy_sep(list) != '/' &&
	    strchr(name, '/') != NULL) {
		*error_r = "Name must not have '/' characters";
		return FALSE;
	}

	return mailbox_list_is_valid_fs_name(list, name, error_r);
}

int mailbox_list_get_path(struct mailbox_list *list, const char *name,
			  enum mailbox_list_path_type type,
			  const char **path_r)
{
	int ret;

	if ((ret = list->v.get_path(list, name, type, path_r)) <= 0)
		*path_r = NULL;
	else
		i_assert(*path_r != NULL);
	return ret;
}

bool mailbox_list_get_root_path(struct mailbox_list *list,
				enum mailbox_list_path_type type,
				const char **path_r)
{
	int ret;

	if ((ret = list->v.get_path(list, NULL, type, path_r)) < 0)
		i_unreached();
	if (ret == 0)
		*path_r = NULL;
	else
		i_assert(*path_r != NULL);
	return ret > 0;
}

const char *mailbox_list_get_root_forced(struct mailbox_list *list,
					 enum mailbox_list_path_type type)
{
	const char *path;

	if (!mailbox_list_get_root_path(list, type, &path))
		i_unreached();
	return path;
}

bool mailbox_list_set_get_root_path(const struct mailbox_list_settings *set,
				    enum mailbox_list_path_type type,
				    const char **path_r)
{
	const char *path = NULL;

	switch (type) {
	case MAILBOX_LIST_PATH_TYPE_DIR:
		path = set->root_dir;
		break;
	case MAILBOX_LIST_PATH_TYPE_ALT_DIR:
		path = set->alt_dir;
		break;
	case MAILBOX_LIST_PATH_TYPE_MAILBOX:
		if (*set->mailbox_dir_name == '\0')
			path = set->root_dir;
		else {
			path = t_strconcat(set->root_dir, "/",
					   set->mailbox_dir_name, NULL);
			path = t_strndup(path, strlen(path)-1);
		}
		break;
	case MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX:
		if (*set->mailbox_dir_name == '\0')
			path = set->root_dir;
		else if (set->alt_dir != NULL) {
			path = t_strconcat(set->alt_dir, "/",
					   set->mailbox_dir_name, NULL);
			path = t_strndup(path, strlen(path)-1);
		}
		break;
	case MAILBOX_LIST_PATH_TYPE_CONTROL:
		path = set->control_dir != NULL ?
			set->control_dir : set->root_dir;
		break;
	case MAILBOX_LIST_PATH_TYPE_INDEX:
		if (set->index_dir != NULL) {
			if (set->index_dir[0] == '\0') {
				/* in-memory indexes */
				return 0;
			}
			path = set->index_dir;
		} else {
			path = set->root_dir;
		}
		break;
	case MAILBOX_LIST_PATH_TYPE_INDEX_PRIVATE:
		path = set->index_pvt_dir;
		break;
	}
	*path_r = path;
	return path != NULL;
}

const char *mailbox_list_get_temp_prefix(struct mailbox_list *list)
{
	return list->v.get_temp_prefix(list, FALSE);
}

const char *mailbox_list_get_global_temp_prefix(struct mailbox_list *list)
{
	return list->v.get_temp_prefix(list, TRUE);
}

const char *mailbox_list_join_refpattern(struct mailbox_list *list,
					 const char *ref, const char *pattern)
{
	if (list->v.join_refpattern != NULL)
		return list->v.join_refpattern(list, ref, pattern);

	/* the default implementation: */
	if (*ref != '\0') {
		/* merge reference and pattern */
		pattern = t_strconcat(ref, pattern, NULL);
	}
	return pattern;
}

int mailbox_has_children(struct mailbox_list *list, const char *name)
{
	struct mailbox_list_iterate_context *iter;
	const char *pattern;
	int ret;

	pattern = t_strdup_printf("%s%c%%", name,
				  mail_namespace_get_sep(list->ns));
	iter = mailbox_list_iter_init(list, pattern,
				      MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
	ret = mailbox_list_iter_next(iter) != NULL ? 1 : 0;
	if (mailbox_list_iter_deinit(&iter) < 0)
		ret = -1;
	return ret;
}

int mailbox_list_mailbox(struct mailbox_list *list, const char *name,
			 enum mailbox_info_flags *flags_r)
{
	const char *path, *fname, *rootdir, *dir, *inbox;
	size_t len;

	*flags_r = 0;

	if ((list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
	    strcasecmp(name, "INBOX") == 0) {
		/* special handling for INBOX, mainly because with Maildir++
		   layout it needs to check if the cur/ directory exists,
		   which the Maildir++ layout backend itself can't do.. */
		struct mailbox *box;
		enum mailbox_existence existence;
		int ret;

		/* kludge: with imapc backend we can get here with
		   list=Maildir++ (for indexes), but list->ns->list=imapc */
		box = mailbox_alloc(list->ns->list, "INBOX", 0);
		ret = mailbox_exists(box, FALSE, &existence);
		if (ret < 0) {
			const char *errstr;
			enum mail_error error;

			/* internal error or with imapc we can get here with
			   login failures */
			errstr = mailbox_get_last_error(box, &error);
			mailbox_list_set_error(list, error, errstr);
		}
		mailbox_free(&box);
		if (ret < 0)
			return -1;
		switch (existence) {
		case MAILBOX_EXISTENCE_NONE:
		case MAILBOX_EXISTENCE_NOSELECT:
			*flags_r |= MAILBOX_NONEXISTENT;
			return 0;
		case MAILBOX_EXISTENCE_SELECT:
			break;
		}
		return 1;
	}

	if (list->v.get_mailbox_flags == NULL) {
		/* can't do this optimized. do it the slow way. */
		struct mailbox_list_iterate_context *iter;
		const struct mailbox_info *info;
		const char *vname;

		vname = mailbox_list_get_vname(list, name);
		iter = mailbox_list_iter_init(list, vname, 0);
		info = mailbox_list_iter_next(iter);
		if (info == NULL)
			*flags_r = MAILBOX_NONEXISTENT;
		else
			*flags_r = info->flags;
		return mailbox_list_iter_deinit(&iter);
	}

	if (!list->set.iter_from_index_dir) {
		rootdir = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_MAILBOX);
		if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR, &path) <= 0)
			i_unreached();
	} else {
		rootdir = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_INDEX);
		if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_INDEX, &path) <= 0)
			i_unreached();
	}

	fname = strrchr(path, '/');
	if (fname == NULL) {
		fname = path;
		dir = "/";
	} else {
		dir = t_strdup_until(path, fname);
		fname++;
	}

	len = strlen(rootdir);
	if (strncmp(path, rootdir, len) == 0 && path[len] == '/') {
		/* looking up a regular mailbox under mail root dir */
	} else if ((list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
		   strcasecmp(name, "INBOX") == 0) {
		/* looking up INBOX that's elsewhere */
	} else {
		/* looking up the root dir itself */
		dir = path;
		fname = "";
	}
	if (*fname == '\0' && *name == '\0' &&
	    (list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
		/* if INBOX is in e.g. ~/Maildir, it shouldn't be possible to
		   access it also via namespace prefix. */
		if (mailbox_list_get_path(list, "INBOX",
					  MAILBOX_LIST_PATH_TYPE_MAILBOX,
					  &inbox) <= 0)
			i_unreached();
		if (strcmp(inbox, dir) == 0) {
			*flags_r |= MAILBOX_NONEXISTENT;
			return 0;
		}
	}
	return list->v.get_mailbox_flags(list, dir, fname,
					 MAILBOX_LIST_FILE_TYPE_UNKNOWN,
					 flags_r);
}

static bool mailbox_list_init_changelog(struct mailbox_list *list)
{
	struct mailbox_permissions perm;
	const char *path;

	if (list->changelog != NULL)
		return TRUE;

	/* don't do this in mailbox_list_create(), because _get_path() might be
	   overridden by storage (mbox). */
	if (!mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_INDEX, &path))
		return FALSE;

	path = t_strconcat(path, "/"MAILBOX_LOG_FILE_NAME, NULL);
	list->changelog = mailbox_log_alloc(path);

	mailbox_list_get_root_permissions(list, &perm);
	mailbox_log_set_permissions(list->changelog, perm.file_create_mode,
				    perm.file_create_gid,
				    perm.file_create_gid_origin);
	return TRUE;
}

int mailbox_list_mkdir_missing_index_root(struct mailbox_list *list)
{
	const char *root_dir, *index_dir;
	int ret;

	if (list->index_root_dir_created)
		return 1;

	/* if index root dir hasn't been created yet, do it now */
	ret = mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_INDEX,
					 &index_dir);
	if (ret <= 0)
		return ret;
	ret = mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_MAILBOX,
					 &root_dir);
	if (ret <= 0)
		return ret;

	if (strcmp(root_dir, index_dir) != 0) {
		if (mailbox_list_mkdir_root(list, index_dir,
					    MAILBOX_LIST_PATH_TYPE_INDEX) < 0)
			return -1;
	}
	list->index_root_dir_created = TRUE;
	return 1;
}

void mailbox_list_add_change(struct mailbox_list *list,
			     enum mailbox_log_record_type type,
			     const guid_128_t mailbox_guid)
{
	struct mailbox_log_record rec;
	time_t stamp;

	if (!mailbox_list_init_changelog(list) ||
	    guid_128_is_empty(mailbox_guid))
		return;

	if (mailbox_list_mkdir_missing_index_root(list) <= 0)
		return;

	stamp = list->changelog_timestamp != (time_t)-1 ?
		list->changelog_timestamp : ioloop_time;

	i_zero(&rec);
	rec.type = type;
	memcpy(rec.mailbox_guid, mailbox_guid, sizeof(rec.mailbox_guid));
	mailbox_log_record_set_timestamp(&rec, stamp);
	(void)mailbox_log_append(list->changelog, &rec);
}

int mailbox_list_set_subscribed(struct mailbox_list *list,
				const char *name, bool set)
{
	int ret;

	/* make sure we'll refresh the file on next list */
	list->subscriptions_mtime = (time_t)-1;

	if ((ret = list->v.set_subscribed(list, name, set)) <= 0)
		return ret;
	return 0;
}

int mailbox_list_delete_dir(struct mailbox_list *list, const char *name)
{
	const char *error;

	if (!mailbox_list_is_valid_name(list, name, &error) || *name == '\0') {
		mailbox_list_set_error(list, MAIL_ERROR_PARAMS,
				       "Invalid mailbox name");
		return -1;
	}
	return list->v.delete_dir(list, name);
}

int mailbox_list_delete_symlink(struct mailbox_list *list, const char *name)
{
	const char *error;

	if (!mailbox_list_is_valid_name(list, name, &error) || *name == '\0') {
		mailbox_list_set_error(list, MAIL_ERROR_PARAMS,
				       "Invalid mailbox name");
		return -1;
	}
	return list->v.delete_symlink(list, name);
}

void mailbox_name_get_sha128(const char *name, guid_128_t guid_128_r)
{
	unsigned char sha[SHA1_RESULTLEN];

	sha1_get_digest(name, strlen(name), sha);
	memcpy(guid_128_r, sha, I_MIN(GUID_128_SIZE, sizeof(sha)));
}

struct mailbox_log *mailbox_list_get_changelog(struct mailbox_list *list)
{
	return !mailbox_list_init_changelog(list) ? NULL : list->changelog;
}

void mailbox_list_set_changelog_timestamp(struct mailbox_list *list,
					  time_t stamp)
{
	list->changelog_timestamp = stamp;
}

enum mailbox_list_file_type
mailbox_list_get_file_type(const struct dirent *d ATTR_UNUSED)
{
	enum mailbox_list_file_type type;

#ifdef HAVE_DIRENT_D_TYPE
	switch (d->d_type) {
	case DT_UNKNOWN:
		type = MAILBOX_LIST_FILE_TYPE_UNKNOWN;
		break;
	case DT_REG:
		type = MAILBOX_LIST_FILE_TYPE_FILE;
		break;
	case DT_DIR:
		type = MAILBOX_LIST_FILE_TYPE_DIR;
		break;
	case DT_LNK:
		type = MAILBOX_LIST_FILE_TYPE_SYMLINK;
		break;
	default:
		type = MAILBOX_LIST_FILE_TYPE_OTHER;
		break;
	}
#else
	type = MAILBOX_LIST_FILE_TYPE_UNKNOWN;
#endif
	return type;
}

int mailbox_list_dirent_is_alias_symlink(struct mailbox_list *list,
					 const char *dir_path,
					 const struct dirent *d)
{
	struct stat st;
	int ret;

	if (mailbox_list_get_file_type(d) == MAILBOX_LIST_FILE_TYPE_SYMLINK)
		return 1;

	T_BEGIN {
		const char *path, *linkpath;

		path = t_strconcat(dir_path, "/", d->d_name, NULL);
		if (lstat(path, &st) < 0) {
			mailbox_list_set_critical(list,
						  "lstat(%s) failed: %m", path);
			ret = -1;
		} else if (!S_ISLNK(st.st_mode)) {
			ret = 0;
		} else if (t_readlink(path, &linkpath) < 0) {
			i_error("readlink(%s) failed: %m", path);
			ret = -1;
		} else {
			/* it's an alias only if it points to the same
			   directory */
			ret = strchr(linkpath, '/') == NULL ? 1 : 0;
		}
	} T_END;
	return ret;
}


static bool
mailbox_list_try_get_home_path(struct mailbox_list *list, const char **name)
{
	if ((*name)[1] == '/') {
		/* ~/dir - use the configured home directory */
		if (mail_user_try_home_expand(list->ns->user, name) < 0)
			return FALSE;
	} else {
		/* ~otheruser/dir - assume we're using system users */
		if (home_try_expand(name) < 0)
			return FALSE;
	}
	return TRUE;
}

bool mailbox_list_try_get_absolute_path(struct mailbox_list *list,
					const char **name)
{
	const char *root_dir, *path, *mailbox_name;
	size_t len;

	if (!list->mail_set->mail_full_filesystem_access)
		return FALSE;

	if (**name == '~') {
		/* try to expand home directory */
		if (!mailbox_list_try_get_home_path(list, name)) {
			/* fallback to using actual "~name" mailbox */
			return FALSE;
		}
	} else {
		if (**name != '/')
			return FALSE;
	}

	/* okay, we have an absolute path now. but check first if it points to
	   same directory as one of our regular mailboxes. */
	root_dir = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_MAILBOX);
	len = strlen(root_dir);
	if (strncmp(root_dir, *name, len) == 0 && (*name)[len] == '/') {
		mailbox_name = *name + len + 1;
		if (mailbox_list_get_path(list, mailbox_name,
					  MAILBOX_LIST_PATH_TYPE_MAILBOX,
					  &path) <= 0)
			return FALSE;
		if (strcmp(path, *name) == 0) {
			/* yeah, we can replace the full path with mailbox
			   name. this way we can use indexes. */
			*name = mailbox_name;
			return FALSE;
		}
	}
	return TRUE;
}

const char *mailbox_list_get_last_error(struct mailbox_list *list,
					enum mail_error *error_r)
{
	if (error_r != NULL)
		*error_r = list->error;

	return list->error_string != NULL ? list->error_string :
		"Unknown internal list error";
}

enum mail_error mailbox_list_get_last_mail_error(struct mailbox_list *list)
{
	return list->error;
}

const char *mailbox_list_get_last_internal_error(struct mailbox_list *list,
						 enum mail_error *error_r)
{
	if (error_r != NULL)
		*error_r = list->error;
	if (list->last_error_is_internal) {
		i_assert(list->last_internal_error != NULL);
		return list->last_internal_error;
	}
	return mailbox_list_get_last_error(list, error_r);
}

void mailbox_list_clear_error(struct mailbox_list *list)
{
	i_free_and_null(list->error_string);

	i_free(list->last_internal_error);
	list->last_error_is_internal = FALSE;
	list->error = MAIL_ERROR_NONE;
}

void mailbox_list_set_error(struct mailbox_list *list,
			    enum mail_error error, const char *string)
{
	i_free(list->error_string);
	list->error_string = i_strdup(string);

	list->last_error_is_internal = FALSE;
	list->error = error;
}

void mailbox_list_set_internal_error(struct mailbox_list *list)
{
	const char *str;

	str = t_strflocaltime(MAIL_ERRSTR_CRITICAL_MSG_STAMP, ioloop_time);
	i_free(list->error_string);
	list->error_string = i_strdup(str);
	list->error = MAIL_ERROR_TEMP;
}

void mailbox_list_set_critical(struct mailbox_list *list, const char *fmt, ...)
{
	va_list va;

	i_free(list->last_internal_error);
	va_start(va, fmt);
	list->last_internal_error = i_strdup_vprintf(fmt, va);
	va_end(va);
	list->last_error_is_internal = TRUE;
	i_error("%s", list->last_internal_error);

	/* critical errors may contain sensitive data, so let user
	   see only "Internal error" with a timestamp to make it
	   easier to look from log files the actual error message. */
	mailbox_list_set_internal_error(list);
}

bool mailbox_list_set_error_from_errno(struct mailbox_list *list)
{
	const char *error_string;
	enum mail_error error;

	if (!mail_error_from_errno(&error, &error_string))
		return FALSE;

	mailbox_list_set_error(list, error, error_string);
	return TRUE;
}

int mailbox_list_init_fs(struct mailbox_list *list, const char *driver,
			 const char *args, const char *root_dir,
			 struct fs **fs_r, const char **error_r)
{
	struct fs_settings fs_set;
	struct ssl_iostream_settings ssl_set;
	struct mailbox_list_fs_context *ctx;
	struct fs *parent_fs;

	i_zero(&ssl_set);
	i_zero(&fs_set);
	mail_user_init_fs_settings(list->ns->user, &fs_set, &ssl_set);
	fs_set.root_path = root_dir;
	fs_set.temp_file_prefix = mailbox_list_get_global_temp_prefix(list);

	if (fs_init(driver, args, &fs_set, fs_r, error_r) < 0)
		return -1;

	/* add mailbox_list context to the parent fs, which allows
	   mailbox_list_fs_get_list() to work */
	for (parent_fs = *fs_r; parent_fs->parent != NULL;
	     parent_fs = parent_fs->parent) ;

	ctx = p_new(list->pool, struct mailbox_list_fs_context, 1);
	ctx->list = list;
	MODULE_CONTEXT_SET(parent_fs, mailbox_list_fs_module, ctx);

	/* a bit kludgy notification to the fs that we're now finished setting
	   up the module context. */
	(void)fs_get_properties(*fs_r);
	return 0;
}

struct mailbox_list *mailbox_list_fs_get_list(struct fs *fs)
{
	struct mailbox_list_fs_context *ctx;

	while (fs->parent != NULL)
		fs = fs->parent;

	ctx = MAILBOX_LIST_FS_CONTEXT(fs);
	return ctx == NULL ? NULL : ctx->list;
}