shared-storage.c revision 5b82f3b2f544cf891a390083f1bcf60409be20b8
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen/* Copyright (c) 2008-2016 Dovecot authors, see the included COPYING file */
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen#include "lib.h"
0531ee2f23b4562eb6c212593680c820fa9e828bTimo Sirainen#include "array.h"
0531ee2f23b4562eb6c212593680c820fa9e828bTimo Sirainen#include "str.h"
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen#include "ioloop.h"
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen#include "var-expand.h"
e28b88ee83b47dc2257140600f491482704c7b79Stephan Bosch#include "index-storage.h"
e28b88ee83b47dc2257140600f491482704c7b79Stephan Bosch#include "mailbox-list-private.h"
e28b88ee83b47dc2257140600f491482704c7b79Stephan Bosch#include "fail-mail-storage.h"
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen#include "shared-storage.h"
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen#include <ctype.h>
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainenextern struct mail_storage shared_storage;
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Boschstatic struct mail_storage *shared_storage_alloc(void)
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch{
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch struct shared_storage *storage;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch pool_t pool;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch pool = pool_alloconly_create("shared storage", 1024);
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch storage = p_new(pool, struct shared_storage, 1);
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch storage->storage = shared_storage;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch storage->storage.pool = pool;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch return &storage->storage;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch}
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Boschstatic int
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Boschshared_storage_create(struct mail_storage *_storage, struct mail_namespace *ns,
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch const char **error_r)
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch{
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch struct shared_storage *storage = (struct shared_storage *)_storage;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch struct mail_storage *storage_class;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch const char *driver, *p;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch char *wildcardp, key;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch bool have_username;
2753f93e5d9cd5fbd773540de3772d182fcced5eStephan Bosch
c3c07d6527ad28c8546cbbf84c257d178e23c184Timo Sirainen /* location must begin with the actual mailbox driver */
c3c07d6527ad28c8546cbbf84c257d178e23c184Timo Sirainen p = strchr(ns->set->location, ':');
c3c07d6527ad28c8546cbbf84c257d178e23c184Timo Sirainen if (p == NULL) {
7ef3553585e556f35d5919589cfdc1de3329e4bbTimo Sirainen *error_r = "Shared mailbox location not prefixed with driver";
return -1;
}
driver = t_strdup_until(ns->set->location, p);
storage->location = p_strdup(_storage->pool, ns->set->location);
storage->unexpanded_location =
p_strdup(_storage->pool, ns->unexpanded_set->location);
storage->storage_class_name = p_strdup(_storage->pool, driver);
storage_class = mail_user_get_storage_class(_storage->user, driver);
if (storage_class != NULL)
_storage->class_flags = storage_class->class_flags;
else if (strcmp(driver, "auto") != 0) {
*error_r = t_strconcat("Unknown shared storage driver: ",
driver, NULL);
return -1;
}
wildcardp = strchr(ns->prefix, '%');
if (wildcardp == NULL) {
*error_r = "Shared namespace prefix doesn't contain %";
return -1;
}
storage->ns_prefix_pattern = p_strdup(_storage->pool, wildcardp);
have_username = FALSE;
for (p = storage->ns_prefix_pattern; *p != '\0'; p++) {
if (*p != '%')
continue;
key = p[1];
if (key == 'u' || key == 'n')
have_username = TRUE;
else if (key != '%' && key != 'd')
break;
}
if (*p != '\0') {
*error_r = "Shared namespace prefix contains unknown variables";
return -1;
}
if (!have_username) {
*error_r = "Shared namespace prefix doesn't contain %u or %n";
return -1;
}
if (p[-1] != mail_namespace_get_sep(ns) &&
(ns->flags & (NAMESPACE_FLAG_LIST_PREFIX |
NAMESPACE_FLAG_LIST_CHILDREN)) != 0) {
*error_r = "Shared namespace prefix doesn't end with hierarchy separator";
return -1;
}
/* truncate prefix after the above checks are done, so they can log
the full prefix in error conditions */
*wildcardp = '\0';
ns->prefix_len = strlen(ns->prefix);
return 0;
}
static void
shared_storage_get_list_settings(const struct mail_namespace *ns ATTR_UNUSED,
struct mailbox_list_settings *set)
{
set->layout = "shared";
}
static void
get_nonexistent_user_location(struct shared_storage *storage,
const char *username, string_t *location)
{
/* user wasn't found. we'll still need to create the storage
to avoid exposing which users exist and which don't. */
str_append(location, storage->storage_class_name);
str_append_c(location, ':');
/* use a reachable but nonexistent path as the mail root directory */
str_append(location, storage->storage.user->set->base_dir);
str_append(location, "/user-not-found/");
str_append(location, username);
}
static bool shared_namespace_exists(struct mail_namespace *ns)
{
const char *path;
struct stat st;
path = mailbox_list_get_root_forced(ns->list, MAILBOX_LIST_PATH_TYPE_DIR);
if (path == NULL) {
/* we can't know if this exists */
return TRUE;
}
return stat(path, &st) == 0;
}
int shared_storage_get_namespace(struct mail_namespace **_ns,
const char **_name)
{
struct mail_storage *_storage = (*_ns)->storage;
struct mailbox_list *list = (*_ns)->list;
struct shared_storage *storage = (struct shared_storage *)_storage;
struct mail_user *user = _storage->user;
struct mail_namespace *new_ns, *ns = *_ns;
struct mail_namespace_settings *ns_set, *unexpanded_ns_set;
struct mail_user *owner;
const char *domain = NULL, *username = NULL, *userdomain = NULL;
const char *name, *p, *next, **dest, *error;
string_t *prefix, *location;
char ns_sep = mail_namespace_get_sep(ns);
int ret;
p = storage->ns_prefix_pattern;
for (name = *_name; *p != '\0';) {
if (*p != '%') {
if (*p != *name)
break;
p++; name++;
continue;
}
switch (*++p) {
case 'd':
dest = &domain;
break;
case 'n':
dest = &username;
break;
case 'u':
dest = &userdomain;
break;
default:
/* we checked this already above */
i_unreached();
}
p++;
next = strchr(name, *p != '\0' ? *p : ns_sep);
if (next == NULL) {
*dest = name;
name = "";
break;
}
*dest = t_strdup_until(name, next);
name = next;
}
if (*p != '\0') {
if (*name == '\0' ||
(name[1] == '\0' && *name == ns_sep)) {
/* trying to open <prefix>/<user> mailbox */
name = "INBOX";
} else {
mailbox_list_set_critical(list,
"Invalid namespace prefix %s vs %s",
storage->ns_prefix_pattern, *_name);
return -1;
}
}
/* successfully matched the name. */
if (userdomain != NULL) {
/* user@domain given */
domain = strchr(userdomain, '@');
if (domain == NULL)
username = userdomain;
else {
username = t_strdup_until(userdomain, domain);
domain++;
}
} else if (username == NULL) {
/* trying to open namespace "shared/domain"
namespace prefix. */
mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(*_name));
return -1;
} else {
if (domain == NULL) {
/* no domain given, use ours (if we have one) */
domain = i_strchr_to_next(user->username, '@');
}
userdomain = domain == NULL ? username :
t_strconcat(username, "@", domain, NULL);
}
if (*userdomain == '\0') {
mailbox_list_set_error(list, MAIL_ERROR_PARAMS,
"Empty username doesn't exist");
return -1;
}
/* expand the namespace prefix and see if it already exists.
this should normally happen only when the mailbox is being opened */
struct var_expand_table tab[] = {
{ 'u', userdomain, "user" },
{ 'n', username, "username" },
{ 'd', domain, "domain" },
{ 'h', NULL, "home" },
{ '\0', NULL, NULL }
};
prefix = t_str_new(128);
str_append(prefix, ns->prefix);
if (var_expand(prefix, storage->ns_prefix_pattern, tab, &error) <= 0) {
mailbox_list_set_critical(list,
"Failed to expand namespace prefix '%s': %s",
storage->ns_prefix_pattern, error);
return -1;
}
*_ns = mail_namespace_find_prefix(user->namespaces, str_c(prefix));
if (*_ns != NULL) {
*_name = mailbox_list_get_storage_name(ns->list,
t_strconcat(ns->prefix, name, NULL));
return 0;
}
owner = mail_user_alloc(userdomain, user->set_info,
user->unexpanded_set);
owner->_service_user = user->_service_user;
owner->creator = user;
owner->autocreated = TRUE;
owner->session_id = p_strdup(owner->pool, user->session_id);
if (mail_user_init(owner, &error) < 0) {
if (!owner->nonexistent) {
mailbox_list_set_critical(list,
"Couldn't create namespace '%s' for user %s: %s",
ns->prefix, userdomain, error);
mail_user_unref(&owner);
return -1;
}
ret = 0;
} else if (!var_has_key(storage->location, 'h', "home")) {
ret = 1;
} else {
/* we'll need to look up the user's home directory */
if ((ret = mail_user_get_home(owner, &tab[3].value)) < 0) {
mailbox_list_set_critical(list, "Namespace '%s': "
"Could not lookup home for user %s",
ns->prefix, userdomain);
mail_user_unref(&owner);
return -1;
}
}
location = t_str_new(256);
if (ret > 0 &&
var_expand(location, storage->location, tab, &error) <= 0) {
mailbox_list_set_critical(list,
"Failed to expand namespace location '%s': %s",
storage->location, error);
return -1;
}
/* create the new namespace */
new_ns = i_new(struct mail_namespace, 1);
new_ns->refcount = 1;
new_ns->type = MAIL_NAMESPACE_TYPE_SHARED;
new_ns->user = user;
new_ns->prefix = i_strdup(str_c(prefix));
new_ns->owner = owner;
new_ns->flags = (NAMESPACE_FLAG_SUBSCRIPTIONS & ns->flags) |
NAMESPACE_FLAG_LIST_PREFIX | NAMESPACE_FLAG_HIDDEN |
NAMESPACE_FLAG_AUTOCREATED | NAMESPACE_FLAG_INBOX_ANY;
new_ns->user_set = user->set;
new_ns->mail_set = _storage->set;
i_array_init(&new_ns->all_storages, 2);
if (ret <= 0) {
get_nonexistent_user_location(storage, userdomain, location);
new_ns->flags |= NAMESPACE_FLAG_UNUSABLE;
if (ns->user->mail_debug) {
i_debug("shared: Tried to access mails of "
"nonexistent user %s", userdomain);
}
}
ns_set = p_new(user->pool, struct mail_namespace_settings, 1);
ns_set->type = "shared";
ns_set->separator = p_strdup_printf(user->pool, "%c", ns_sep);
ns_set->prefix = new_ns->prefix;
ns_set->location = p_strdup(user->pool, str_c(location));
ns_set->hidden = TRUE;
ns_set->list = "yes";
new_ns->set = ns_set;
unexpanded_ns_set =
p_new(user->pool, struct mail_namespace_settings, 1);
*unexpanded_ns_set = *ns_set;
unexpanded_ns_set->location =
p_strdup(user->pool, storage->unexpanded_location);
new_ns->unexpanded_set = unexpanded_ns_set;
/* We need to create a prefix="" namespace for the owner */
if (mail_namespaces_init_location(owner, str_c(location), &error) < 0) {
/* owner gets freed by namespace deinit */
mail_namespace_destroy(new_ns);
return -1;
}
if (mail_storage_create(new_ns, NULL, _storage->flags |
MAIL_STORAGE_FLAG_NO_AUTOVERIFY, &error) < 0) {
mailbox_list_set_critical(list, "Namespace '%s': %s",
new_ns->prefix, error);
/* owner gets freed by namespace deinit */
mail_namespace_destroy(new_ns);
return -1;
}
if ((new_ns->flags & NAMESPACE_FLAG_UNUSABLE) == 0 &&
!shared_namespace_exists(new_ns)) {
/* this user doesn't have a usable storage */
new_ns->flags |= NAMESPACE_FLAG_UNUSABLE;
}
/* mark the shared namespace root as usable, since it now has
child namespaces */
ns->flags |= NAMESPACE_FLAG_USABLE;
*_name = mailbox_list_get_storage_name(new_ns->list,
t_strconcat(new_ns->prefix, name, NULL));
*_ns = new_ns;
if (_storage->class_flags == 0) {
/* flags are unset if we were using "auto" storage */
_storage->class_flags =
mail_namespace_get_default_storage(new_ns)->class_flags;
}
mail_user_add_namespace(user, &new_ns);
return 0;
}
struct mail_storage shared_storage = {
.name = MAIL_SHARED_STORAGE_NAME,
.class_flags = 0, /* unknown at this point */
.v = {
NULL,
shared_storage_alloc,
shared_storage_create,
index_storage_destroy,
NULL,
shared_storage_get_list_settings,
NULL,
fail_mailbox_alloc,
NULL
}
};