mailbox-list.c revision 6600c05e2ab38e9f662582b63c56b0c980a03748
76b43e4417bab52e913da39b5f5bc2a130d3f149Timo Sirainen/* Copyright (c) 2006-2010 Dovecot authors, see the included COPYING file */
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "lib.h"
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen#include "array.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "ioloop.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "mkdir-parents.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "str.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "sha1.h"
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen#include "home-expand.h"
63a61b7a739ae0f3f520215137d9c50f94d0f34fTimo Sirainen#include "close-keep-errno.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "eacces-error.h"
18565c69efcd7db003dbf27cf625ed822e889fb1Timo Sirainen#include "read-full.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "write-full.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "safe-mkstemp.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "unlink-directory.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "settings-parser.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "imap-match.h"
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen#include "imap-utf7.h"
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen#include "mailbox-log.h"
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen#include "mailbox-tree.h"
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen#include "mail-storage-private.h"
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen#include "mailbox-list-private.h"
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen#include <time.h>
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen#include <unistd.h>
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen#include <dirent.h>
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen#include <sys/stat.h>
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen/* 20 * (200+1) < 4096 which is the standard PATH_MAX. Having these settings
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen prevents malicious user from creating eg. "a/a/a/.../a" mailbox name and
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen then start renaming them to larger names from end to beginning, which
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen eventually would start causing the failures when trying to use too
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen long mailbox names. */
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#define MAILBOX_MAX_HIERARCHY_LEVELS 20
af1f4b17a92ca7b2661737e65c7849df289d3070Timo Sirainen#define MAILBOX_MAX_HIERARCHY_NAME_LENGTH 200
af1f4b17a92ca7b2661737e65c7849df289d3070Timo Sirainen
b5e6f6f27c1461f0f9f202615eeb738a645188c3Timo Sirainenstruct ns_list_iterate_context {
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen struct mailbox_list_iterate_context ctx;
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen struct mailbox_list_iterate_context *backend_ctx;
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen struct mail_namespace *namespaces;
01cbf4ac5d44137ab434791be7f838d98d0fcf3bTimo Sirainen pool_t pool;
4b41116563110d00330896a568eff1078c382827Timo Sirainen const char **patterns, **patterns_ns_match;
4b41116563110d00330896a568eff1078c382827Timo Sirainen enum namespace_type type_mask;
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen};
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainenstruct mailbox_list_module_register mailbox_list_module_register = { 0 };
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainenstatic ARRAY_DEFINE(mailbox_list_drivers, const struct mailbox_list *);
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainenvoid mailbox_lists_init(void)
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen{
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen i_array_init(&mailbox_list_drivers, 4);
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen}
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainenvoid mailbox_lists_deinit(void)
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen{
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen array_free(&mailbox_list_drivers);
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen}
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainenstatic bool mailbox_list_driver_find(const char *name, unsigned int *idx_r)
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen{
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen const struct mailbox_list *const *drivers;
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen unsigned int i, count;
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
d30da25fb6be1f1c667d93767c9194000194b618Timo Sirainen drivers = array_get(&mailbox_list_drivers, &count);
d30da25fb6be1f1c667d93767c9194000194b618Timo Sirainen for (i = 0; i < count; i++) {
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen if (strcasecmp(drivers[i]->name, name) == 0) {
597dce34068d603fb759b4dff404b34049213e51Timo Sirainen *idx_r = i;
63a61b7a739ae0f3f520215137d9c50f94d0f34fTimo Sirainen return TRUE;
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen }
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen }
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen return FALSE;
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen}
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainenvoid mailbox_list_register(const struct mailbox_list *list)
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen{
4b41116563110d00330896a568eff1078c382827Timo Sirainen unsigned int idx;
910fa4e4204a73d3d24c03f3059dd24e727ca057Timo Sirainen
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen if (mailbox_list_driver_find(list->name, &idx)) {
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen i_fatal("mailbox_list_register(%s): duplicate driver",
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen list->name);
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen }
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
6060b7c8edf8fce73470d0df6a2479b69b01c537Timo Sirainen array_append(&mailbox_list_drivers, &list, 1);
7f773564b94e6054a40d3785cb63c29f1e4d4deeTimo Sirainen}
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
ccc895c0358108d2304239063e940b7d75f364abTimo Sirainenvoid mailbox_list_unregister(const struct mailbox_list *list)
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen{
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen unsigned int idx;
fe6c1556d3529a6376d4cbb3766c34aebde0de99Timo Sirainen
4b41116563110d00330896a568eff1078c382827Timo Sirainen if (!mailbox_list_driver_find(list->name, &idx)) {
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen i_fatal("mailbox_list_unregister(%s): unknown driver",
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen list->name);
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen }
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen array_delete(&mailbox_list_drivers, idx, 1);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen}
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
b5e6f6f27c1461f0f9f202615eeb738a645188c3Timo Sirainenconst struct mailbox_list *
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainenmailbox_list_find_class(const char *driver)
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen{
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen const struct mailbox_list *const *class_p;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen unsigned int idx;
4b41116563110d00330896a568eff1078c382827Timo Sirainen
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen if (!mailbox_list_driver_find(driver, &idx))
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen return NULL;
b5e6f6f27c1461f0f9f202615eeb738a645188c3Timo Sirainen
345212e8f61ebf14ff4f80df26df9e655eb5121eTimo Sirainen class_p = array_idx(&mailbox_list_drivers, idx);
345212e8f61ebf14ff4f80df26df9e655eb5121eTimo Sirainen return *class_p;
345212e8f61ebf14ff4f80df26df9e655eb5121eTimo Sirainen}
345212e8f61ebf14ff4f80df26df9e655eb5121eTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainenint mailbox_list_create(const char *driver, struct mail_namespace *ns,
345212e8f61ebf14ff4f80df26df9e655eb5121eTimo Sirainen const struct mailbox_list_settings *set,
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen enum mailbox_list_flags flags, const char **error_r)
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen{
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen const struct mailbox_list *const *class_p;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen struct mailbox_list *list;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen unsigned int idx;
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen i_assert(ns->list == NULL);
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen i_assert(set->subscription_fname == NULL ||
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen *set->subscription_fname != '\0');
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen if (!mailbox_list_driver_find(driver, &idx)) {
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen *error_r = "Unknown driver name";
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen return -1;
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen }
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen class_p = array_idx(&mailbox_list_drivers, idx);
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen if (((*class_p)->props & MAILBOX_LIST_PROP_NO_MAILDIR_NAME) != 0 &&
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen set->maildir_name != NULL && *set->maildir_name != '\0') {
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen *error_r = "maildir_name not supported by this driver";
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen return -1;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen }
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen if (((*class_p)->props & MAILBOX_LIST_PROP_NO_ALT_DIR) != 0 &&
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen set->alt_dir != NULL) {
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen *error_r = "alt_dir not supported by this driver";
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen return -1;
b5e6f6f27c1461f0f9f202615eeb738a645188c3Timo Sirainen }
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen i_assert(set->root_dir == NULL || *set->root_dir != '\0' ||
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen ((*class_p)->props & MAILBOX_LIST_PROP_NO_ROOT) != 0);
4b41116563110d00330896a568eff1078c382827Timo Sirainen
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen list = (*class_p)->v.alloc();
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen array_create(&list->module_contexts, list->pool, sizeof(void *), 5);
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen list->ns = ns;
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen list->mail_set = ns->mail_set;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen list->flags = flags;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen list->file_create_mode = (mode_t)-1;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen list->dir_create_mode = (mode_t)-1;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen list->file_create_gid = (gid_t)-1;
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen list->changelog_timestamp = (time_t)-1;
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen /* copy settings */
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen if (set->root_dir != NULL) {
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen list->set.root_dir = p_strdup(list->pool, set->root_dir);
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen list->set.index_dir = set->index_dir == NULL ||
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen strcmp(set->index_dir, set->root_dir) == 0 ? NULL :
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen p_strdup(list->pool, set->index_dir);
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen list->set.control_dir = set->control_dir == NULL ||
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen strcmp(set->control_dir, set->root_dir) == 0 ? NULL :
a0b0d629931773c17a236f6214adbe0e13b9b3fdTimo Sirainen p_strdup(list->pool, set->control_dir);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen }
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen list->set.inbox_path = p_strdup(list->pool, set->inbox_path);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen list->set.subscription_fname =
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen p_strdup(list->pool, set->subscription_fname);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.maildir_name = set->maildir_name == NULL ? "" :
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen p_strdup(list->pool, set->maildir_name);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.mailbox_dir_name =
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen p_strdup(list->pool, set->mailbox_dir_name);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.alt_dir = p_strdup(list->pool, set->alt_dir);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen if (set->mailbox_dir_name == NULL || *set->mailbox_dir_name == '\0')
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.mailbox_dir_name = "";
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen else if (set->mailbox_dir_name[strlen(set->mailbox_dir_name)-1] == '/') {
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.mailbox_dir_name =
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen p_strdup(list->pool, set->mailbox_dir_name);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen } else {
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.mailbox_dir_name =
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen p_strconcat(list->pool, set->mailbox_dir_name, "/", NULL);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen }
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen if (ns->mail_set->mail_debug) {
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen i_debug("%s: root=%s, index=%s, control=%s, inbox=%s",
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->name,
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.root_dir == NULL ? "" : list->set.root_dir,
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.index_dir == NULL ? "" : list->set.index_dir,
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.control_dir == NULL ?
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen "" : list->set.control_dir,
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen list->set.inbox_path == NULL ?
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen "" : list->set.inbox_path);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen }
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen mail_namespace_finish_list_init(ns, list);
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen hook_mailbox_list_created(list);
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen return 0;
b5e6f6f27c1461f0f9f202615eeb738a645188c3Timo Sirainen}
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainen
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainenstatic int fix_path(struct mail_user *user, const char *path,
a5c8dc283ef673fcdae158513b8032e74b45f59aTimo Sirainen const char **path_r, const char **error_r)
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen{
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainen size_t len = strlen(path);
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainen
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainen if (len > 1 && path[len-1] == '/')
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainen path = t_strndup(path, len-1);
a5c8dc283ef673fcdae158513b8032e74b45f59aTimo Sirainen if (path[0] == '~' && path[1] != '/') {
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainen /* ~otheruser/dir */
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen if (home_try_expand(&path) < 0) {
a1808be0774cbcb28fec45341aabf803ec44bae5Timo Sirainen *error_r = t_strconcat(
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen "No home directory for system user. "
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainen "Can't expand ", t_strcut(path, '/'),
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen " for ", NULL);
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen return -1;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen }
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen } else {
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen if (mail_user_try_home_expand(user, &path) < 0) {
1b5366b2234892f8930a29351da06b193e385150Timo Sirainen *error_r = "Home directory not set for user. "
1b5366b2234892f8930a29351da06b193e385150Timo Sirainen "Can't expand ~/ for ";
1b5366b2234892f8930a29351da06b193e385150Timo Sirainen return -1;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen }
1b5366b2234892f8930a29351da06b193e385150Timo Sirainen }
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen *path_r = path;
1b5366b2234892f8930a29351da06b193e385150Timo Sirainen return 0;
1b5366b2234892f8930a29351da06b193e385150Timo Sirainen}
1b5366b2234892f8930a29351da06b193e385150Timo Sirainen
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainenstatic const char *split_next_arg(const char *const **_args)
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainen{
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen const char *const *args = *_args;
aa215ab623706463cea1d047f975ffe51d3f0c05Timo Sirainen const char *str = args[0];
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen
a5c8dc283ef673fcdae158513b8032e74b45f59aTimo Sirainen args++;
a5c8dc283ef673fcdae158513b8032e74b45f59aTimo Sirainen while (*args != NULL && **args == '\0') {
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen args++;
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen if (*args == NULL) {
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen /* string ends with ":", just ignore it. */
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen break;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen }
b5e6f6f27c1461f0f9f202615eeb738a645188c3Timo Sirainen str = t_strconcat(str, ":", *args, NULL);
b2ecd50bb98c44816cb07c17aa17fae2b425f941Timo Sirainen args++;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen }
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen *_args = args;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen return str;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen}
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainenint mailbox_list_settings_parse(struct mail_user *user, const char *data,
4b41116563110d00330896a568eff1078c382827Timo Sirainen struct mailbox_list_settings *set_r,
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen const char **error_r)
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen{
f480b30abdddf6f1beb8a2c5b1ce4bf8999400dbTimo Sirainen const char *const *tmp, *key, *value, **dest, *str, *error;
f480b30abdddf6f1beb8a2c5b1ce4bf8999400dbTimo Sirainen
f480b30abdddf6f1beb8a2c5b1ce4bf8999400dbTimo Sirainen *error_r = NULL;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen memset(set_r, 0, sizeof(*set_r));
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen if (*data == '\0')
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen return 0;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen /* <root dir> */
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen tmp = t_strsplit(data, ":");
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen str = split_next_arg(&tmp);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen if (fix_path(user, str, &set_r->root_dir, &error) < 0) {
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen *error_r = t_strconcat(error, "mail root dir in: ", data, NULL);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen return -1;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen }
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen
8f017a40470ef2f4b530000d947a8bce44350a5eTimo Sirainen while (*tmp != NULL) {
59151b71059df1190acd75d8717ed04a7920c862Timo Sirainen str = split_next_arg(&tmp);
59151b71059df1190acd75d8717ed04a7920c862Timo Sirainen value = strchr(str, '=');
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen if (value == NULL) {
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen key = str;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen value = "";
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen } else {
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen key = t_strdup_until(str, value);
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen value++;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen }
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen if (strcmp(key, "INBOX") == 0)
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen dest = &set_r->inbox_path;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen else if (strcmp(key, "INDEX") == 0)
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen dest = &set_r->index_dir;
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen else if (strcmp(key, "CONTROL") == 0)
64a67c0296b120b6e15169ac9d84dec964e55969Timo Sirainen dest = &set_r->control_dir;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen else if (strcmp(key, "ALT") == 0)
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen dest = &set_r->alt_dir;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen else if (strcmp(key, "LAYOUT") == 0)
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen dest = &set_r->layout;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen else if (strcmp(key, "SUBSCRIPTIONS") == 0)
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen dest = &set_r->subscription_fname;
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen else if (strcmp(key, "DIRNAME") == 0)
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen dest = &set_r->maildir_name;
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen else if (strcmp(key, "MAILBOXDIR") == 0)
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen dest = &set_r->mailbox_dir_name;
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen else {
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen *error_r = t_strdup_printf("Unknown setting: %s", key);
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen return -1;
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen }
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen if (fix_path(user, value, dest, &error) < 0) {
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen *error_r = t_strconcat(error, key, " in: ", data, NULL);
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen return -1;
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen }
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen }
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen if (set_r->index_dir != NULL && strcmp(set_r->index_dir, "MEMORY") == 0)
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen set_r->index_dir = "";
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen return 0;
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen}
0d86aa0d47f7393c669c084b34c0537b193688adTimo Sirainen
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainenconst char *mailbox_list_get_unexpanded_path(struct mailbox_list *list,
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen enum mailbox_list_path_type type)
ae1b268ffff743ad9927c304a1344c5cbd7f909dTimo Sirainen{
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen const struct mail_storage_settings *mail_set;
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen const char *location = list->ns->unexpanded_set->location;
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen struct mail_user *user = list->ns->user;
a757f31393b9d6fc7760a9dec8363404ab3ae576Timo Sirainen struct mailbox_list_settings set;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen const char *p, *error;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen if (*location == SETTING_STRVAR_EXPANDED[0]) {
90adcaa0a00eba29b7fbd50ca66be11c8d086d6aTimo Sirainen /* set using -o or userdb lookup. */
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen return "";
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen }
87460b08cb97b31cde640d4975a6aa2c1d0e7226Timo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen i_assert(*location == SETTING_STRVAR_UNEXPANDED[0]);
87460b08cb97b31cde640d4975a6aa2c1d0e7226Timo Sirainen location++;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen if (*location == '\0') {
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen mail_set = mail_user_set_get_driver_settings(user->set_info,
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen user->unexpanded_set, MAIL_STORAGE_SET_DRIVER_NAME);
838e367716bbd5e44b4a1691db9cbf72af53e9f0Timo Sirainen i_assert(mail_set != NULL);
01404d41657a104c5ea1c12bb87f9c321e9c1ac4Timo Sirainen location = mail_set->mail_location;
01404d41657a104c5ea1c12bb87f9c321e9c1ac4Timo Sirainen if (*location == SETTING_STRVAR_EXPANDED[0])
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen return "";
i_assert(*location == SETTING_STRVAR_UNEXPANDED[0]);
location++;
}
/* type:settings */
p = strchr(location, ':');
if (p == NULL)
return "";
if (mailbox_list_settings_parse(user, p + 1, &set, &error) < 0)
return "";
return mailbox_list_get_root_path(&set, type);
}
void mailbox_list_destroy(struct mailbox_list **_list)
{
struct mailbox_list *list = *_list;
*_list = NULL;
i_free_and_null(list->error_string);
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;
}
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;
}
int mailbox_list_get_storage(struct mailbox_list **list, const char **name,
struct mail_storage **storage_r)
{
if ((*list)->v.get_storage != NULL)
return (*list)->v.get_storage(list, name, storage_r);
else {
*storage_r = (*list)->ns->storage;
return 0;
}
}
void mailbox_list_get_closest_storage(struct mailbox_list *list,
struct mail_storage **storage)
{
*storage = list->ns->storage;
}
static void
mailbox_list_get_permissions_full(struct mailbox_list *list, const char *name,
mode_t *file_mode_r, mode_t *dir_mode_r,
gid_t *gid_r, const char **gid_origin_r)
{
const char *path;
struct stat st;
/* use safe defaults */
*file_mode_r = 0600;
*dir_mode_r = 0700;
*gid_r = (gid_t)-1;
*gid_origin_r = "defaults";
path = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR);
if (path == NULL) {
/* no filesystem support in storage */
} else if (stat(path, &st) < 0) {
if (!ENOTFOUND(errno)) {
mailbox_list_set_critical(list, "stat(%s) failed: %m",
path);
} else if (list->mail_set->mail_debug) {
i_debug("Namespace %s: Permission lookup failed from %s",
list->ns->prefix, path);
}
if (name != NULL) {
/* return defaults */
mailbox_list_get_permissions_full(list, NULL,
file_mode_r,
dir_mode_r, gid_r,
gid_origin_r);
return;
}
} else {
*file_mode_r = (st.st_mode & 0666) | 0600;
*dir_mode_r = (st.st_mode & 0777) | 0700;
*gid_origin_r = path;
if (!S_ISDIR(st.st_mode)) {
/* we're getting permissions from a file.
apply +x modes as necessary. */
*dir_mode_r = get_dir_mode(*dir_mode_r);
}
if (S_ISDIR(st.st_mode) && (st.st_mode & S_ISGID) != 0) {
/* directory's GID is used automatically for new
files */
*gid_r = (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 */
*gid_r = (gid_t)-1;
} else if (getegid() == st.st_gid) {
/* using our own gid, no need to change it */
*gid_r = (gid_t)-1;
} else {
*gid_r = st.st_gid;
}
}
if (name == NULL) {
list->file_create_mode = *file_mode_r;
list->dir_create_mode = *dir_mode_r;
list->file_create_gid = *gid_r;
list->file_create_gid_origin =
p_strdup(list->pool, *gid_origin_r);
}
if (list->mail_set->mail_debug && name == NULL) {
i_debug("Namespace %s: Using permissions from %s: "
"mode=0%o gid=%ld", list->ns->prefix,
path != NULL ? path : "",
(int)list->dir_create_mode,
list->file_create_gid == (gid_t)-1 ? -1L :
(long)list->file_create_gid);
}
}
void mailbox_list_get_permissions(struct mailbox_list *list,
const char *name,
mode_t *mode_r, gid_t *gid_r,
const char **gid_origin_r)
{
mode_t dir_mode;
if (list->file_create_mode != (mode_t)-1 && name == NULL) {
*mode_r = list->file_create_mode;
*gid_r = list->file_create_gid;
*gid_origin_r = list->file_create_gid_origin;
return;
}
mailbox_list_get_permissions_full(list, name, mode_r, &dir_mode, gid_r,
gid_origin_r);
}
void mailbox_list_get_dir_permissions(struct mailbox_list *list,
const char *name,
mode_t *mode_r, gid_t *gid_r,
const char **gid_origin_r)
{
mode_t file_mode;
if (list->dir_create_mode != (mode_t)-1 && name == NULL) {
*mode_r = list->dir_create_mode;
*gid_r = list->file_create_gid;
*gid_origin_r = list->file_create_gid_origin;
return;
}
mailbox_list_get_permissions_full(list, name, &file_mode,
mode_r, gid_r, gid_origin_r);
}
static int
mailbox_list_stat_parent(struct mailbox_list *list, const char *path,
const char **root_dir_r, struct stat *st_r)
{
const char *p;
while (stat(path, st_r) < 0) {
if (errno != ENOENT || strcmp(path, "/") == 0) {
mailbox_list_set_critical(list, "stat(%s) failed: %m",
path);
return -1;
}
p = strrchr(path, '/');
if (p == NULL)
path = "/";
else
path = t_strdup_until(path, p);
}
*root_dir_r = path;
return 0;
}
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;
}
int mailbox_list_mkdir(struct mailbox_list *list, const char *path,
enum mailbox_list_path_type type)
{
const char *expanded, *unexpanded, *root_dir, *p, *origin;
struct stat st;
mode_t mode;
gid_t gid;
mailbox_list_get_dir_permissions(list, NULL, &mode, &gid, &origin);
/* 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 == NULL)
expanded = "";
else {
while (p != unexpanded && *p != '/') p--;
if (p == unexpanded)
expanded = "";
else {
expanded = mailbox_list_get_path(list, NULL, type);
expanded = get_expanded_path(unexpanded, p, expanded);
}
}
if (*expanded != '\0') {
/* up to this directory get the permissions from the first
parent directory that exists, if it has setgid bit
enabled. */
if (mailbox_list_stat_parent(list, expanded,
&root_dir, &st) < 0)
return -1;
if ((st.st_mode & S_ISGID) != 0 && root_dir != expanded) {
if (mkdir_parents_chgrp(expanded, st.st_mode,
(gid_t)-1, root_dir) < 0 &&
errno != EEXIST) {
mailbox_list_set_critical(list,
"mkdir(%s) failed: %m", expanded);
return -1;
}
}
if (gid == (gid_t)-1 && (mode & S_ISGID) == 0) {
/* change the group for user directories */
gid = getegid();
}
}
/* the rest of the directories exist only for one user. create them
with default directory permissions */
if (mkdir_parents_chgrp(path, mode, gid, origin) < 0 &&
errno != EEXIST) {
mailbox_list_set_critical(list, "mkdir(%s) failed: %m", path);
return -1;
}
return 0;
}
bool mailbox_list_is_valid_pattern(struct mailbox_list *list,
const char *pattern)
{
bool ret;
T_BEGIN {
ret = list->v.is_valid_pattern(list, pattern);
} T_END;
return ret;
}
bool mailbox_list_is_valid_existing_name(struct mailbox_list *list,
const char *name)
{
bool ret;
if (*name == '\0' && *list->ns->prefix != '\0') {
/* an ugly way to get to mailbox root (e.g. Maildir/ when
it's not the INBOX) */
return TRUE;
}
T_BEGIN {
ret = list->v.is_valid_existing_name(list, name);
} T_END;
return ret;
}
bool mailbox_list_is_valid_create_name(struct mailbox_list *list,
const char *name)
{
const char *p;
int ret;
/* safer to just disallow all control characters */
for (p = name; *p != '\0'; p++) {
if (*p < ' ')
return FALSE;
}
T_BEGIN {
string_t *str = t_str_new(256);
ret = imap_utf7_to_utf8(name, str);
} T_END;
return ret < 0 ? FALSE :
list->v.is_valid_create_name(list, name);
}
const char *mailbox_list_get_path(struct mailbox_list *list, const char *name,
enum mailbox_list_path_type type)
{
return list->v.get_path(list, name, type);
}
const char *
mailbox_list_get_root_path(const struct mailbox_list_settings *set,
enum mailbox_list_path_type type)
{
const char *path;
switch (type) {
case MAILBOX_LIST_PATH_TYPE_DIR:
return set->root_dir;
case MAILBOX_LIST_PATH_TYPE_ALT_DIR:
return set->alt_dir;
case MAILBOX_LIST_PATH_TYPE_MAILBOX:
if (*set->mailbox_dir_name == '\0')
return set->root_dir;
path = t_strconcat(set->root_dir, "/",
set->mailbox_dir_name, NULL);
return t_strndup(path, strlen(path)-1);
case MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX:
if (*set->mailbox_dir_name == '\0')
return set->root_dir;
path = t_strconcat(set->alt_dir, "/",
set->mailbox_dir_name, NULL);
return t_strndup(path, strlen(path)-1);
case MAILBOX_LIST_PATH_TYPE_CONTROL:
return set->control_dir != NULL ?
set->control_dir : set->root_dir;
case MAILBOX_LIST_PATH_TYPE_INDEX:
return set->index_dir != NULL ?
set->index_dir : set->root_dir;
}
i_unreached();
}
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_list_get_mailbox_name_status(struct mailbox_list *list,
const char *name,
enum mailbox_name_status *status)
{
if (!mailbox_list_is_valid_existing_name(list, name)) {
*status = MAILBOX_NAME_INVALID;
return 0;
}
return list->v.get_mailbox_name_status(list, name, status);
}
struct mailbox_list_iterate_context *
mailbox_list_iter_init(struct mailbox_list *list, const char *pattern,
enum mailbox_list_iter_flags flags)
{
const char *patterns[2];
patterns[0] = pattern;
patterns[1] = NULL;
return mailbox_list_iter_init_multiple(list, patterns, flags);
}
struct mailbox_list_iterate_context *
mailbox_list_iter_init_multiple(struct mailbox_list *list,
const char *const *patterns,
enum mailbox_list_iter_flags flags)
{
i_assert(*patterns != NULL);
return list->v.iter_init(list, patterns, flags);
}
static bool
ns_match_simple(struct ns_list_iterate_context *ctx, struct mail_namespace *ns)
{
if ((ctx->type_mask & ns->type) == 0)
return FALSE;
if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) {
if (ns->alias_for != NULL)
return FALSE;
}
return TRUE;
}
static bool
ns_match_inbox(struct mail_namespace *ns, const char *pattern)
{
struct imap_match_glob *glob;
if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) == 0)
return FALSE;
glob = imap_match_init(pool_datastack_create(), pattern,
TRUE, ns->sep);
return imap_match(glob, "INBOX") == IMAP_MATCH_YES;
}
static bool
ns_match_next(struct ns_list_iterate_context *ctx, struct mail_namespace *ns,
const char *pattern)
{
struct imap_match_glob *glob;
enum imap_match_result result;
const char *prefix_without_sep;
unsigned int len;
len = ns->prefix_len;
if (len > 0 && ns->prefix[len-1] == ns->sep)
len--;
if ((ns->flags & (NAMESPACE_FLAG_LIST_PREFIX |
NAMESPACE_FLAG_LIST_CHILDREN)) == 0) {
/* non-listable namespace matches only with exact prefix */
if (strncmp(ns->prefix, pattern, ns->prefix_len) != 0)
return FALSE;
}
prefix_without_sep = t_strndup(ns->prefix, len);
if (*prefix_without_sep == '\0')
result = IMAP_MATCH_CHILDREN;
else {
glob = imap_match_init(pool_datastack_create(), pattern,
TRUE, ns->sep);
result = imap_match(glob, prefix_without_sep);
}
if ((ctx->ctx.flags & MAILBOX_LIST_ITER_STAR_WITHIN_NS) == 0) {
switch (result) {
case IMAP_MATCH_YES:
case IMAP_MATCH_CHILDREN:
return TRUE;
case IMAP_MATCH_NO:
case IMAP_MATCH_PARENT:
break;
}
return FALSE;
}
switch (result) {
case IMAP_MATCH_YES:
/* allow matching prefix only when it's done without
wildcards */
if (strcmp(prefix_without_sep, pattern) == 0)
return TRUE;
break;
case IMAP_MATCH_CHILDREN: {
/* allow this only if there isn't another namespace
with longer prefix that matches this pattern
(namespaces are sorted by prefix length) */
struct mail_namespace *tmp;
T_BEGIN {
for (tmp = ns->next; tmp != NULL; tmp = tmp->next) {
if (ns_match_simple(ctx, tmp) &&
ns_match_next(ctx, tmp, pattern))
break;
}
} T_END;
if (tmp == NULL)
return TRUE;
break;
}
case IMAP_MATCH_NO:
case IMAP_MATCH_PARENT:
break;
}
return FALSE;
}
static bool
ns_match(struct ns_list_iterate_context *ctx, struct mail_namespace *ns)
{
unsigned int i;
if (!ns_match_simple(ctx, ns))
return FALSE;
/* filter out namespaces whose prefix doesn't match. this same code
handles both with and without STAR_WITHIN_NS, so the "without" case
is slower than necessary, but this shouldn't matter much */
T_BEGIN {
for (i = 0; ctx->patterns_ns_match[i] != NULL; i++) {
if (ns_match_inbox(ns, ctx->patterns_ns_match[i]))
break;
if (ns_match_next(ctx, ns, ctx->patterns_ns_match[i]))
break;
}
} T_END;
return ctx->patterns_ns_match[i] != NULL;
}
static struct mail_namespace *
ns_next(struct ns_list_iterate_context *ctx, struct mail_namespace *ns)
{
for (; ns != NULL; ns = ns->next) {
if (ns_match(ctx, ns))
break;
}
return ns;
}
static const struct mailbox_info *
mailbox_list_ns_iter_next(struct mailbox_list_iterate_context *_ctx)
{
struct ns_list_iterate_context *ctx =
(struct ns_list_iterate_context *)_ctx;
const struct mailbox_info *info;
info = ctx->backend_ctx == NULL ? NULL :
mailbox_list_iter_next(ctx->backend_ctx);
if (info == NULL && ctx->namespaces != NULL) {
/* go to the next namespace */
if (mailbox_list_iter_deinit(&ctx->backend_ctx) < 0)
_ctx->failed = TRUE;
ctx->ctx.list->ns = ctx->namespaces;
ctx->backend_ctx =
mailbox_list_iter_init_multiple(ctx->namespaces->list,
ctx->patterns,
_ctx->flags);
ctx->namespaces = ns_next(ctx, ctx->namespaces->next);
return mailbox_list_ns_iter_next(_ctx);
}
return info;
}
static int
mailbox_list_ns_iter_deinit(struct mailbox_list_iterate_context *_ctx)
{
struct ns_list_iterate_context *ctx =
(struct ns_list_iterate_context *)_ctx;
int ret;
if (ctx->backend_ctx != NULL) {
if (mailbox_list_iter_deinit(&ctx->backend_ctx) < 0)
_ctx->failed = TRUE;
}
ret = _ctx->failed ? -1 : 0;
pool_unref(&ctx->pool);
return ret;
}
static const char **
dup_patterns_without_stars(pool_t pool, const char *const *patterns,
unsigned int count)
{
const char **dup;
unsigned int i;
dup = p_new(pool, const char *, count + 1);
for (i = 0; i < count; i++) {
char *p = p_strdup(pool, patterns[i]);
dup[i] = p;
for (; *p != '\0'; p++) {
if (*p == '*')
*p = '%';
}
}
return dup;
}
struct mailbox_list_iterate_context *
mailbox_list_iter_init_namespaces(struct mail_namespace *namespaces,
const char *const *patterns,
enum namespace_type type_mask,
enum mailbox_list_iter_flags flags)
{
struct ns_list_iterate_context *ctx;
unsigned int i, count;
pool_t pool;
i_assert(namespaces != NULL);
pool = pool_alloconly_create("mailbox list namespaces", 1024);
ctx = p_new(pool, struct ns_list_iterate_context, 1);
ctx->pool = pool;
ctx->type_mask = type_mask;
ctx->ctx.flags = flags;
ctx->ctx.list = p_new(pool, struct mailbox_list, 1);
ctx->ctx.list->v.iter_next = mailbox_list_ns_iter_next;
ctx->ctx.list->v.iter_deinit = mailbox_list_ns_iter_deinit;
count = str_array_length(patterns);
ctx->patterns = p_new(pool, const char *, count + 1);
for (i = 0; i < count; i++)
ctx->patterns[i] = p_strdup(pool, patterns[i]);
if ((flags & MAILBOX_LIST_ITER_STAR_WITHIN_NS) != 0) {
/* create copies of patterns with '*' wildcard changed to '%' */
ctx->patterns_ns_match =
dup_patterns_without_stars(pool, ctx->patterns, count);
} else {
ctx->patterns_ns_match = ctx->patterns;
}
namespaces = ns_next(ctx, namespaces);
ctx->ctx.list->ns = namespaces;
if (namespaces != NULL) {
ctx->backend_ctx =
mailbox_list_iter_init_multiple(namespaces->list,
patterns, flags);
ctx->namespaces = ns_next(ctx, namespaces->next);
}
return &ctx->ctx;
}
const struct mailbox_info *
mailbox_list_iter_next(struct mailbox_list_iterate_context *ctx)
{
const struct mailbox_info *info;
info = ctx->list->v.iter_next(ctx);
if (info != NULL)
ctx->list->ns->flags |= NAMESPACE_FLAG_USABLE;
return info;
}
int mailbox_list_iter_deinit(struct mailbox_list_iterate_context **_ctx)
{
struct mailbox_list_iterate_context *ctx = *_ctx;
*_ctx = NULL;
return ctx->list->v.iter_deinit(ctx);
}
int mailbox_list_mailbox(struct mailbox_list *list, const char *name,
enum mailbox_info_flags *flags_r)
{
const char *path, *fname, *rootdir, *dir, *inbox;
struct stat st;
unsigned int len;
rootdir = mailbox_list_get_path(list, NULL,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
path = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR);
if (rootdir == NULL) {
/* shouldn't happen with anything except shared mailboxes */
return 0;
}
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. */
inbox = mailbox_list_get_path(list, "INBOX",
MAILBOX_LIST_PATH_TYPE_MAILBOX);
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,
&st, flags_r);
}
static bool mailbox_list_init_changelog(struct mailbox_list *list)
{
const char *path;
mode_t mode;
gid_t gid;
const char *gid_origin;
if (list->changelog != NULL)
return TRUE;
/* don't do this in mailbox_list_create(), because _get_path() might be
overridden by storage (mbox). */
path = mailbox_list_get_path(list, NULL, MAILBOX_LIST_PATH_TYPE_INDEX);
if (path == NULL)
return FALSE;
path = t_strconcat(path, "/"MAILBOX_LOG_FILE_NAME, NULL);
list->changelog = mailbox_log_alloc(path);
mailbox_list_get_permissions(list, NULL, &mode, &gid, &gid_origin);
mailbox_log_set_permissions(list->changelog, mode, gid, gid_origin);
return TRUE;
}
void mailbox_list_add_change(struct mailbox_list *list,
enum mailbox_log_record_type type,
const uint8_t mailbox_guid[MAIL_GUID_128_SIZE])
{
struct mailbox_log_record rec;
time_t stamp;
if (!mailbox_list_init_changelog(list) ||
mail_guid_128_is_empty(mailbox_guid))
return;
if (!list->index_root_dir_created) {
if (mailbox_list_create_missing_index_dir(list, NULL) < 0)
return;
}
stamp = list->changelog_timestamp != (time_t)-1 ?
list->changelog_timestamp : ioloop_time;
memset(&rec, 0, sizeof(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)
{
uint8_t guid[MAIL_GUID_128_SIZE];
if (list->v.set_subscribed(list, name, set) < 0)
return -1;
/* subscriptions are about names, not about mailboxes. it's possible
to have a subscription to nonexistent mailbox. renames also don't
change subscriptions. so instead of using actual GUIDs, we'll use
hash of the name. */
mailbox_name_get_sha128(name, guid);
mailbox_list_add_change(list, set ? MAILBOX_LOG_RECORD_SUBSCRIBE :
MAILBOX_LOG_RECORD_UNSUBSCRIBE, guid);
return 0;
}
int mailbox_list_delete_dir(struct mailbox_list *list, const char *name)
{
if (!mailbox_list_is_valid_existing_name(list, name) || *name == '\0') {
mailbox_list_set_error(list, MAIL_ERROR_PARAMS,
"Invalid mailbox name");
return -1;
}
return list->v.delete_dir(list, name);
}
void mailbox_name_get_sha128(const char *name, uint8_t guid[MAIL_GUID_128_SIZE])
{
unsigned char sha[SHA1_RESULTLEN];
sha1_get_digest(name, strlen(name), sha);
memcpy(guid, sha, I_MIN(MAIL_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;
}
static void node_fix_parents(struct mailbox_node *node)
{
/* If we happened to create any of the parents, we need to mark them
nonexistent. */
node = node->parent;
for (; node != NULL; node = node->parent) {
if ((node->flags & MAILBOX_MATCHED) == 0)
node->flags |= MAILBOX_NONEXISTENT;
}
}
static void
mailbox_list_iter_update_real(struct mailbox_list_iter_update_context *ctx,
const char *name)
{
struct mail_namespace *ns = ctx->iter_ctx->list->ns;
struct mailbox_node *node;
enum mailbox_info_flags create_flags = 0, always_flags;
enum imap_match_result match;
const char *p;
bool created, add_matched;
if (ctx->update_only ||
(ctx->iter_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0)
create_flags = MAILBOX_NONEXISTENT | MAILBOX_NOCHILDREN;
always_flags = ctx->leaf_flags;
add_matched = TRUE;
for (;;) {
created = FALSE;
match = imap_match(ctx->glob, name);
if (match == IMAP_MATCH_YES) {
node = ctx->update_only ?
mailbox_tree_lookup(ctx->tree_ctx, name) :
mailbox_tree_get(ctx->tree_ctx, name, &created);
if (created) {
node->flags = create_flags;
if (create_flags != 0)
node_fix_parents(node);
}
if (node != NULL) {
if (!ctx->update_only && add_matched)
node->flags |= MAILBOX_MATCHED;
node->flags |= always_flags;
}
/* We don't want to show the parent mailboxes unless
something else matches them, but if they are matched
we want to show them having child subscriptions */
add_matched = FALSE;
} else {
if ((match & IMAP_MATCH_PARENT) == 0)
break;
/* We've a (possibly) non-subscribed parent mailbox
which has a subscribed child mailbox. Make sure we
return the parent mailbox. */
}
if (!ctx->match_parents)
break;
/* see if parent matches */
p = strrchr(name, ns->sep);
if (p == NULL)
break;
name = t_strdup_until(name, p);
create_flags &= ~MAILBOX_NOCHILDREN;
always_flags = MAILBOX_CHILDREN | ctx->parent_flags;
}
}
void mailbox_list_iter_update(struct mailbox_list_iter_update_context *ctx,
const char *name)
{
T_BEGIN {
mailbox_list_iter_update_real(ctx, name);
} T_END;
}
bool mailbox_list_name_is_too_large(const char *name, char sep)
{
unsigned int levels = 1, level_len = 0;
for (; *name != '\0'; name++) {
if (*name == sep) {
if (level_len > MAILBOX_MAX_HIERARCHY_NAME_LENGTH)
return TRUE;
levels++;
level_len = 0;
} else {
level_len++;
}
}
if (level_len > MAILBOX_MAX_HIERARCHY_NAME_LENGTH)
return TRUE;
if (levels > MAILBOX_MAX_HIERARCHY_LEVELS)
return TRUE;
return FALSE;
}
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;
}
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;
unsigned int 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_path(list, NULL,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
len = strlen(root_dir);
if (strncmp(root_dir, *name, len) == 0 && (*name)[len] == '/') {
mailbox_name = *name + len + 1;
path = mailbox_list_get_path(list, mailbox_name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
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;
}
int mailbox_list_create_parent_dir(struct mailbox_list *list,
const char *mailbox, const char *path)
{
const char *p, *dir, *origin;
gid_t gid;
mode_t mode;
p = strrchr(path, '/');
if (p == NULL)
return 0;
dir = t_strdup_until(path, p);
mailbox_list_get_dir_permissions(list, mailbox, &mode, &gid, &origin);
if (mkdir_parents_chgrp(dir, mode, gid, origin) < 0 &&
errno != EEXIST) {
mailbox_list_set_critical(list, "mkdir_parents(%s) failed: %m",
dir);
return -1;
}
return 0;
}
int mailbox_list_create_missing_index_dir(struct mailbox_list *list,
const char *name)
{
const char *root_dir, *index_dir, *parent_dir, *p, *origin;
mode_t mode;
gid_t gid;
unsigned int n = 0;
list->index_root_dir_created = TRUE;
root_dir = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
index_dir = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_INDEX);
if (*index_dir == '\0' || strcmp(index_dir, root_dir) == 0)
return 0;
mailbox_list_get_dir_permissions(list, name, &mode, &gid, &origin);
while (mkdir_chgrp(index_dir, mode, gid, origin) < 0) {
if (errno == EEXIST)
break;
p = strrchr(index_dir, '/');
if (errno != ENOENT || p == NULL || ++n == 2) {
mailbox_list_set_critical(list,
"mkdir(%s) failed: %m", index_dir);
return -1;
}
/* create the parent directory first */
parent_dir = t_strdup_until(index_dir, p);
if (mailbox_list_mkdir(list, parent_dir,
MAILBOX_LIST_PATH_TYPE_INDEX) < 0)
return -1;
}
return 0;
}
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";
}
void mailbox_list_clear_error(struct mailbox_list *list)
{
i_free_and_null(list->error_string);
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->error = error;
}
void mailbox_list_set_internal_error(struct mailbox_list *list)
{
struct tm *tm;
char str[256];
tm = localtime(&ioloop_time);
i_free(list->error_string);
list->error_string =
strftime(str, sizeof(str),
MAIL_ERRSTR_CRITICAL_MSG_STAMP, tm) > 0 ?
i_strdup(str) : i_strdup(MAIL_ERRSTR_CRITICAL_MSG);
list->error = MAIL_ERROR_TEMP;
}
void mailbox_list_set_critical(struct mailbox_list *list, const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
i_error("%s", t_strdup_vprintf(fmt, va));
va_end(va);
/* 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;
}