mailbox-list.c revision 94b0ff77495c3ed14bdd4b5d7ae1eb37e8c9efb5
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (c) 2006-2009 Dovecot authors, see the included COPYING file */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "lib.h"
8e361d2906b0e44f7175a20981f8d2280645b58bTimo Sirainen#include "array.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "ioloop.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "str.h"
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen#include "home-expand.h"
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen#include "unlink-directory.h"
cc833a7a4e2258afdc834ace4bfe6579820a1df3Timo Sirainen#include "imap-match.h"
cc833a7a4e2258afdc834ace4bfe6579820a1df3Timo Sirainen#include "imap-utf7.h"
97cb20eb77d486ef67eac50567e3080faca025c1Timo Sirainen#include "mailbox-tree.h"
97cb20eb77d486ef67eac50567e3080faca025c1Timo Sirainen#include "mail-storage-private.h"
cc833a7a4e2258afdc834ace4bfe6579820a1df3Timo Sirainen#include "mailbox-list-private.h"
7e235b3a5f622813121cd18f351e036650aaf8f8Timo Sirainen
b66a207ddcfc72a634186ec7e9a82df28ffc1d4eTimo Sirainen#include <time.h>
7e235b3a5f622813121cd18f351e036650aaf8f8Timo Sirainen#include <unistd.h>
b66a207ddcfc72a634186ec7e9a82df28ffc1d4eTimo Sirainen#include <dirent.h>
b66a207ddcfc72a634186ec7e9a82df28ffc1d4eTimo Sirainen#include <sys/stat.h>
fadd878cd6098f5b873c21c121209a922679dae4Timo Sirainen
863ea896fb31a16d1baec31e57650243b5547db6Timo Sirainen/* 20 * (200+1) < 4096 which is the standard PATH_MAX. Having these settings
863ea896fb31a16d1baec31e57650243b5547db6Timo Sirainen prevents malicious user from creating eg. "a/a/a/.../a" mailbox name and
b66a207ddcfc72a634186ec7e9a82df28ffc1d4eTimo Sirainen then start renaming them to larger names from end to beginning, which
471e447023ab73a73f0f78da2afc0c55905330ddTimo Sirainen eventually would start causing the failures when trying to use too
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen long mailbox names. */
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen#define MAILBOX_MAX_HIERARCHY_LEVELS 20
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen#define MAILBOX_MAX_HIERARCHY_NAME_LENGTH 200
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainenstruct ns_list_iterate_context {
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen struct mailbox_list_iterate_context ctx;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen struct mailbox_list_iterate_context *backend_ctx;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen struct mail_namespace *namespaces;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen pool_t pool;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const char **patterns;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen};
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstruct mailbox_list_module_register mailbox_list_module_register = { 0 };
bbce20cb4e5739e9a06058cf8ee1f38a7f6884f6Timo Sirainen
bbce20cb4e5739e9a06058cf8ee1f38a7f6884f6Timo Sirainenvoid (*hook_mailbox_list_created)(struct mailbox_list *list);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic ARRAY_DEFINE(mailbox_list_drivers, const struct mailbox_list *);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenvoid mailbox_lists_init(void)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_array_init(&mailbox_list_drivers, 4);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen}
55a210942dc7da58b2fd0b11bed8da6b030af5c1Timo Sirainen
fc7b17677ac1a5fa3f7fe13d5ef7dcfea8d9b4a1Timo Sirainenvoid mailbox_lists_deinit(void)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
52d2b356e3ddb4e59ee09c10d47add9d3280284bAki Tuomi array_free(&mailbox_list_drivers);
52d2b356e3ddb4e59ee09c10d47add9d3280284bAki Tuomi}
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
8e361d2906b0e44f7175a20981f8d2280645b58bTimo Sirainenstatic bool mailbox_list_driver_find(const char *name, unsigned int *idx_r)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const struct mailbox_list *const *drivers;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int i, count;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen drivers = array_get(&mailbox_list_drivers, &count);
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen for (i = 0; i < count; i++) {
44ff75ca53188056ff5a3e50428e3f2078800b3cTimo Sirainen if (strcasecmp(drivers[i]->name, name) == 0) {
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen *idx_r = i;
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen return TRUE;
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen }
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen }
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen return FALSE;
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainen}
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
51795bfe9d05d92fe942cb451aec2b9d16d32a11Timo Sirainenvoid mailbox_list_register(const struct mailbox_list *list)
88b8aea03a24ef7a9efc30399080487b7eb03537Timo Sirainen{
88b8aea03a24ef7a9efc30399080487b7eb03537Timo Sirainen unsigned int idx;
88b8aea03a24ef7a9efc30399080487b7eb03537Timo Sirainen
44ff75ca53188056ff5a3e50428e3f2078800b3cTimo Sirainen if (mailbox_list_driver_find(list->name, &idx)) {
8872e5c991430f96138a46e36b7f3c2c40d8e5c2Timo Sirainen i_fatal("mailbox_list_register(%s): duplicate driver",
8872e5c991430f96138a46e36b7f3c2c40d8e5c2Timo Sirainen list->name);
8872e5c991430f96138a46e36b7f3c2c40d8e5c2Timo Sirainen }
651fc0f1e43fef3e02e0e7b5f498973b05f641d7Timo Sirainen
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen array_append(&mailbox_list_drivers, &list, 1);
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen}
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen
d9fda7e3a0fa5551547ac3e3054b837fc77f4bfbTimo Sirainenvoid mailbox_list_unregister(const struct mailbox_list *list)
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen{
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen unsigned int idx;
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen if (!mailbox_list_driver_find(list->name, &idx)) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_fatal("mailbox_list_unregister(%s): unknown driver",
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen list->name);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen array_delete(&mailbox_list_drivers, idx, 1);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen}
029cfcdce65b284d5230adf1c920a5f526b03b5cTimo Sirainen
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainenint mailbox_list_create(const char *driver, struct mail_namespace *ns,
45e62043058738e294f89504c319d852e25943ccTimo Sirainen const struct mailbox_list_settings *set,
45e62043058738e294f89504c319d852e25943ccTimo Sirainen enum mailbox_list_flags flags, const char **error_r)
45e62043058738e294f89504c319d852e25943ccTimo Sirainen{
45e62043058738e294f89504c319d852e25943ccTimo Sirainen const struct mailbox_list *const *class_p;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen struct mailbox_list *list;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen unsigned int idx;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
d5960ce1c0adda5c9e259bc429123ebc29c60baeTimo Sirainen i_assert(ns->list == NULL);
d5960ce1c0adda5c9e259bc429123ebc29c60baeTimo Sirainen
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen i_assert(set->root_dir == NULL || *set->root_dir != '\0');
b780aa272b742a43579cdb523cc79cc8d4521306Timo Sirainen i_assert(set->subscription_fname == NULL ||
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen *set->subscription_fname != '\0');
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (!mailbox_list_driver_find(driver, &idx)) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen *error_r = t_strdup_printf("Unknown mailbox list driver: %s",
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen driver);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen return -1;
6eb30032b4a50c383dea4c9c74342d906de6ad36Timo Sirainen }
43d32cbe60fdaef2699d99f1ca259053e9350411Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen class_p = array_idx(&mailbox_list_drivers, idx);
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen list = (*class_p)->v.alloc();
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen array_create(&list->module_contexts, list->pool, sizeof(void *), 5);
bdd7a96c363346f7c38f389791be1487ca08775bTimo Sirainen
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen list->ns = ns;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen list->mail_set = ns->mail_set;
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen list->flags = flags;
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen list->file_create_mode = (mode_t)-1;
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen list->dir_create_mode = (mode_t)-1;
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen list->file_create_gid = (gid_t)-1;
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen
c0d1bfc45e224251cb549de8d8804861e8acb517Timo Sirainen /* copy settings */
c0d1bfc45e224251cb549de8d8804861e8acb517Timo Sirainen list->set.root_dir = p_strdup(list->pool, set->root_dir);
c0d1bfc45e224251cb549de8d8804861e8acb517Timo Sirainen list->set.index_dir = set->index_dir == NULL ||
c0d1bfc45e224251cb549de8d8804861e8acb517Timo Sirainen strcmp(set->index_dir, set->root_dir) == 0 ? NULL :
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen p_strdup(list->pool, set->index_dir);
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen list->set.control_dir = set->control_dir == NULL ||
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen strcmp(set->control_dir, set->root_dir) == 0 ? NULL :
4bc96ba6f1d67a90a75fa131bcd2cd508ea5a05aTimo Sirainen p_strdup(list->pool, set->control_dir);
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen
f537e7efaec891d6b3320ca94331d09ca8c4a4dbTimo Sirainen list->set.inbox_path = p_strdup(list->pool, set->inbox_path);
6321d9d33937c7fc13a8ff04c220a9e377efeeb8Timo Sirainen list->set.subscription_fname =
6321d9d33937c7fc13a8ff04c220a9e377efeeb8Timo Sirainen p_strdup(list->pool, set->subscription_fname);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen list->set.maildir_name = set->maildir_name == NULL ||
2e99f3f3bb35715ce5e0a75a2f2a9bac3ab4224bTimo Sirainen (list->props & MAILBOX_LIST_PROP_NO_MAILDIR_NAME) != 0 ? "" :
2e99f3f3bb35715ce5e0a75a2f2a9bac3ab4224bTimo Sirainen p_strdup(list->pool, set->maildir_name);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen list->set.mailbox_dir_name =
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen p_strdup(list->pool, set->mailbox_dir_name);
902222fb0928d1701f20a384b73f327b1d9a15ddTimo Sirainen
902222fb0928d1701f20a384b73f327b1d9a15ddTimo Sirainen if (set->mailbox_dir_name == NULL || *set->mailbox_dir_name == '\0')
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen list->set.mailbox_dir_name = "";
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen else if (set->mailbox_dir_name[strlen(set->mailbox_dir_name)-1] == '/') {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen list->set.mailbox_dir_name =
2a6af811ea3de3cf9e2f15e446674dd21b0705f3Timo Sirainen p_strdup(list->pool, set->mailbox_dir_name);
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen } else {
6ded8819b9002150a95a7615e4f64f091c250464Timo Sirainen list->set.mailbox_dir_name =
6ded8819b9002150a95a7615e4f64f091c250464Timo Sirainen p_strconcat(list->pool, set->mailbox_dir_name, "/", NULL);
2f8da04d700cc23fcd6630226a4866e828b761bdTimo Sirainen }
2f8da04d700cc23fcd6630226a4866e828b761bdTimo Sirainen
b87a4156eca6dcf6b29c504eb0cb9be2fdb11b63Timo Sirainen if (ns->mail_set->mail_debug) {
ad48319996942463675b53877092ab7e13a7a75aTimo Sirainen i_info("%s: root=%s, index=%s, control=%s, inbox=%s",
92dab926b2f2270057b40a907a00cf8eb2309ed6Timo Sirainen list->name,
92dab926b2f2270057b40a907a00cf8eb2309ed6Timo Sirainen list->set.root_dir == NULL ? "" : list->set.root_dir,
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen list->set.index_dir == NULL ? "" : list->set.index_dir,
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen list->set.control_dir == NULL ?
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen "" : list->set.control_dir,
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen list->set.inbox_path == NULL ?
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen "" : list->set.inbox_path);
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen }
95a1a5195d56f3cf5d1e529aad668f87ad3b979bTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mail_namespace_finish_list_init(ns, list);
if (hook_mailbox_list_created != NULL)
hook_mailbox_list_created(list);
return 0;
}
static int fix_path(struct mail_namespace *ns, const char *path,
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 (path[0] == '~' && path[1] != '/') {
/* ~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(ns->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) {
str = t_strconcat(str, ":", NULL);
break;
}
str = t_strconcat(str, ":", *args, NULL);
args++;
}
*_args = args;
return str;
}
int mailbox_list_settings_parse(const char *data,
struct mailbox_list_settings *set,
struct mail_namespace *ns, const char **error_r)
{
const char *const *tmp, *key, *value, **dest, *str, *error;
*error_r = NULL;
if (*data == '\0')
return 0;
/* <root dir> */
tmp = t_strsplit(data, ":");
str = split_next_arg(&tmp);
if (fix_path(ns, str, &set->root_dir, &error) < 0) {
*error_r = t_strconcat(error, "mail root dir in: ", data, NULL);
return -1;
}
while (*tmp != NULL) {
str = split_next_arg(&tmp);
value = strchr(str, '=');
if (value == NULL) {
key = str;
value = "";
} else {
key = t_strdup_until(str, value);
value++;
}
if (strcmp(key, "INBOX") == 0)
dest = &set->inbox_path;
else if (strcmp(key, "INDEX") == 0)
dest = &set->index_dir;
else if (strcmp(key, "CONTROL") == 0)
dest = &set->control_dir;
else if (strcmp(key, "ALT") == 0)
dest = &set->alt_dir;
else if (strcmp(key, "LAYOUT") == 0)
dest = &set->layout;
else if (strcmp(key, "SUBSCRIPTIONS") == 0)
dest = &set->subscription_fname;
else if (strcmp(key, "DIRNAME") == 0)
dest = &set->maildir_name;
else if (strcmp(key, "MAILBOXDIR") == 0)
dest = &set->mailbox_dir_name;
else {
*error_r = t_strdup_printf("Unknown setting: %s", key);
return -1;
}
if (fix_path(ns, value, dest, &error) < 0) {
*error_r = t_strconcat(error, key, " in: ", data, NULL);
return -1;
}
}
if (set->index_dir != NULL && strcmp(set->index_dir, "MEMORY") == 0)
set->index_dir = "";
return 0;
}
void mailbox_list_destroy(struct mailbox_list **_list)
{
struct mailbox_list *list = *_list;
*_list = NULL;
i_free_and_null(list->error_string);
list->v.deinit(list);
}
const char *mailbox_list_get_driver_name(const struct mailbox_list *list)
{
return list->name;
}
char mailbox_list_get_hierarchy_sep(const struct mailbox_list *list)
{
return list->hierarchy_sep;
}
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;
path = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR);
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_info("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;
}
/* return safe defaults */
*file_mode_r = 0600;
*dir_mode_r = 0700;
*gid_r = (gid_t)-1;
*gid_origin_r = "defaults";
} else {
*file_mode_r = st.st_mode & 0666;
*dir_mode_r = st.st_mode & 0777;
*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_info("Namespace %s: Using permissions from %s: "
"mode=0%o gid=%ld", list->ns->prefix, 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);
}
bool mailbox_list_is_valid_pattern(struct mailbox_list *list,
const char *pattern)
{
return list->v.is_valid_pattern(list, pattern);
}
bool mailbox_list_is_valid_existing_name(struct mailbox_list *list,
const char *name)
{
return list->v.is_valid_existing_name(list, name);
}
bool mailbox_list_is_valid_create_name(struct mailbox_list *list,
const char *name)
{
int ret;
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)
{
mailbox_list_clear_error(list);
return list->v.get_path(list, name, type);
}
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)
{
mailbox_list_clear_error(list);
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);
mailbox_list_clear_error(list);
return list->v.iter_init(list, patterns, flags);
}
static struct mail_namespace *
ns_next(struct ns_list_iterate_context *ctx, struct mail_namespace *ns)
{
if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) {
while (ns != NULL && ns->alias_for != NULL)
ns = ns->next;
}
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 = 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 (mailbox_list_iter_deinit(&ctx->backend_ctx) < 0)
_ctx->failed = TRUE;
ret = _ctx->failed ? -1 : 0;
pool_unref(&ctx->pool);
return ret;
}
struct mailbox_list_iterate_context *
mailbox_list_iter_init_namespaces(struct mail_namespace *namespaces,
const char *const *patterns,
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", 512);
ctx = p_new(pool, struct ns_list_iterate_context, 1);
ctx->pool = pool;
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]);
namespaces = ns_next(ctx, namespaces);
ctx->ctx.list->ns = namespaces;
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_set_subscribed(struct mailbox_list *list,
const char *name, bool set)
{
mailbox_list_clear_error(list);
return list->v.set_subscribed(list, name, set);
}
int mailbox_list_delete_mailbox(struct mailbox_list *list, const char *name)
{
if (!mailbox_list_is_valid_existing_name(list, name)) {
mailbox_list_set_error(list, MAIL_ERROR_PARAMS,
"Invalid mailbox name");
return -1;
}
if (strcmp(name, "INBOX") == 0 &&
(list->ns->flags & NAMESPACE_FLAG_INBOX) != 0) {
mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE,
"INBOX can't be deleted.");
return -1;
}
return list->v.delete_mailbox(list, name);
}
static bool nullequals(const void *p1, const void *p2)
{
return (p1 == NULL && p2 == NULL) || (p1 != NULL && p2 != NULL);
}
int mailbox_list_rename_mailbox(struct mailbox_list *oldlist,
const char *oldname,
struct mailbox_list *newlist,
const char *newname, bool rename_children)
{
struct mail_storage *oldstorage;
struct mail_storage *newstorage;
if (mailbox_list_get_storage(&oldlist, &oldname, &oldstorage) < 0)
return -1;
newstorage = oldstorage;
mailbox_list_get_closest_storage(newlist, &newstorage);
if (!mailbox_list_is_valid_existing_name(oldlist, oldname) ||
!mailbox_list_is_valid_create_name(newlist, newname)) {
mailbox_list_set_error(oldlist, MAIL_ERROR_PARAMS,
"Invalid mailbox name");
return -1;
}
if (strcmp(oldstorage->name, newstorage->name) != 0) {
mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
"Can't rename mailbox to another storage type.");
return -1;
}
if (!nullequals(oldlist->set.index_dir, newlist->set.index_dir) ||
!nullequals(oldlist->set.control_dir, newlist->set.control_dir)) {
mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
"Can't rename mailboxes across specified storages.");
return -1;
}
if (oldlist->ns->type != NAMESPACE_PRIVATE ||
newlist->ns->type != NAMESPACE_PRIVATE) {
mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
"Renaming not supported across non-private namespaces.");
return -1;
}
return oldlist->v.rename_mailbox(oldlist, oldname, newlist, newname,
rename_children);
}
static int mailbox_list_try_delete(struct mailbox_list *list, const char *dir)
{
if (unlink_directory(dir, TRUE) == 0 || errno == ENOENT)
return 0;
if (errno == ENOTEMPTY) {
/* We're most likely using NFS and we can't delete
.nfs* files. */
mailbox_list_set_error(list, MAIL_ERROR_INUSE,
"Mailbox is still open in another session, "
"can't delete it.");
} else {
mailbox_list_set_critical(list,
"unlink_directory(%s) failed: %m", dir);
}
return -1;
}
int mailbox_list_delete_index_control(struct mailbox_list *list,
const char *name)
{
const char *path, *index_dir, *dir;
path = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
/* delete the index directory first, so that if we crash we don't
leave indexes for deleted mailboxes lying around */
index_dir = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_INDEX);
if (*index_dir != '\0' && strcmp(index_dir, path) != 0) {
if (mailbox_list_try_delete(list, index_dir) < 0)
return -1;
}
/* control directory next */
dir = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_CONTROL);
if (*dir != '\0' && strcmp(dir, path) != 0 &&
strcmp(dir, index_dir) != 0) {
if (mailbox_list_try_delete(list, dir) < 0)
return -1;
}
return 0;
}
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;
}
bool mailbox_list_try_get_absolute_path(struct mailbox_list *list,
const char **name)
{
if (!list->mail_set->mail_full_filesystem_access)
return FALSE;
if (**name == '/')
return TRUE;
if (**name != '~')
return FALSE;
/* try to expand home directory */
if ((*name)[1] == '/') {
/* ~/dir - use the configured home directory */
if (mail_user_try_home_expand(list->ns->user, name) == 0)
return TRUE;
} else {
/* ~otheruser/dir - assume we're using system users */
if (home_try_expand(name) == 0)
return TRUE;
}
/* fallback to using ~dir */
return FALSE;
}
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;
}