mail-storage.c revision 44320b37d20bb75e0d433423318db2f4e29d5b91
/* Copyright (c) 2002-2013 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "llist.h"
#include "str.h"
#include "unichar.h"
#include "istream.h"
#include "eacces-error.h"
#include "mkdir-parents.h"
#include "time-util.h"
#include "var-expand.h"
#include "mail-index-private.h"
#include "mail-index-alloc-cache.h"
#include "mailbox-tree.h"
#include "mailbox-list-private.h"
#include "mail-storage-private.h"
#include "mail-storage-settings.h"
#include "mail-namespace.h"
#include "mail-search.h"
#include "mail-search-register.h"
#include "mailbox-search-result-private.h"
#include "mailbox-guid-cache.h"
#include <stdlib.h>
#include <ctype.h>
extern struct mail_search_register *mail_search_register_imap;
extern struct mail_search_register *mail_search_register_human;
struct mail_storage_module_register mail_storage_module_register = { 0 };
struct mail_module_register mail_module_register = { 0 };
void mail_storage_init(void)
{
}
void mail_storage_deinit(void)
{
if (mail_search_register_human != NULL)
if (mail_search_register_imap != NULL)
}
{
/* append it after the list, so the autodetection order is correct */
}
{
struct mail_storage *const *classes;
unsigned int i, count;
for (i = 0; i < count; i++) {
if (classes[i] == storage_class) {
break;
}
}
}
{
struct mail_storage *const *classes;
unsigned int i, count;
for (i = 0; i < count; i++) {
return classes[i];
}
return NULL;
}
static struct mail_storage *
struct mailbox_list_settings *set)
{
struct mail_storage *const *classes;
unsigned int i, count;
for (i = 0; i < count; i++) {
return classes[i];
}
}
return NULL;
}
static void
{
const char *p;
/* check if data is in driver:data format (eg. mbox:~/mail) */
p = *data;
while (i_isalnum(*p)) p++;
if (*p == ':' && p != *data) {
/* no autodetection if the storage driver is given. */
*data = p + 1;
}
}
static struct mail_storage *
struct mailbox_list_settings *list_set,
{
const char *home;
/* no mail_location, autodetect */
/* explicit autodetection with "auto" driver. */
/* handle the same as with driver=NULL */
}
} else {
if (storage_class == NULL) {
"Unknown mail storage driver %s", driver);
return NULL;
}
}
/* no root directory given. is this allowed? */
const struct mailbox_list *list;
if (storage_class == NULL &&
(flags & MAIL_STORAGE_FLAG_NO_AUTODETECTION) == 0) {
/* autodetection should take care of this */
} else if (storage_class != NULL &&
/* root not required for this storage */
/* root not required for this layout */
} else {
*error_r = "Root mail directory not given";
return NULL;
}
}
if (storage_class != NULL) {
return storage_class;
}
if (storage_class != NULL)
return storage_class;
"Mail storage autodetection failed with home=%s", home);
"Autodetection failed for %s (home=%s)",
} else {
"Ambiguous mail location setting, "
"don't know what to do with it: %s "
"(try prefixing it with mbox: or maildir:)",
}
return NULL;
}
static int
const char **error_r)
{
/* exists */
return 1;
return -1;
return -1;
} else if (!autocreate) {
"Root mail directory doesn't exist: %s", root_dir);
return -1;
} else {
/* doesn't exist */
return 0;
}
}
static int
{
bool autocreate;
int ret;
&root_dir)) {
/* storage doesn't use directories (e.g. shared root) */
return 0;
}
if ((flags & MAIL_STORAGE_FLAG_NO_AUTOVERIFY) != 0) {
return 0;
/* we don't need to verify, but since debugging is
enabled, check and log if the root doesn't exist */
i_debug("Namespace %s: Creating storage despite: %s",
}
return 0;
}
if (ret == 0) {
error_r);
}
return ret < 0 ? -1 : 0;
}
static bool
const struct mail_storage *storage_class,
const struct mailbox_list_settings *set)
{
return FALSE;
return FALSE;
/* allow multiple independent shared namespaces */
return FALSE;
}
return TRUE;
}
static struct mail_storage *
const struct mail_storage *storage_class,
const struct mailbox_list_settings *set)
{
return storage;
}
return NULL;
}
{
struct mailbox_list *list;
struct mailbox_list_settings list_set;
enum mailbox_list_flags list_flags = 0;
const char *p;
if ((flags & MAIL_STORAGE_FLAG_KEEP_HEADER_MD5) == 0 &&
/* if pop3_uidl_format contains %m, we want to keep the
header MD5 sums stored even if we're not running POP3
right now. */
if (p[1] == '%')
p += 2;
else if (var_get_key(++p) == 'm') {
break;
}
}
}
/* autodetect */
/* internal shared namespace */
} else {
error_r) < 0)
return -1;
}
error_r);
if (storage_class == NULL)
return -1;
/* first storage for namespace */
return -1;
}
return -1;
}
}
/* using an existing storage */
return 0;
}
return -1;
}
T_BEGIN {
} T_END;
return 0;
}
{
/* set *_storage=NULL only after calling destroy() callback.
for example mdbox wants to access ns->storage */
return;
}
i_panic("Trying to deinit storage without freeing mailbox %s",
}
if (storage->obj_refcount != 0)
i_panic("Trying to deinit storage before freeing its objects");
}
{
if (storage->obj_refcount++ == 0)
}
{
if (--storage->obj_refcount == 0) {
}
}
{
}
{
}
{
const char *str;
}
const char *fmt, ...)
{
/* 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. */
}
struct mail_storage *src)
{
const char *str;
enum mail_error error;
return;
}
struct mailbox_list *list)
{
const char *str;
enum mail_error error;
}
{
else
}
const struct mail_storage_settings *
{
}
{
}
struct mail_storage_callbacks *callbacks,
void *context)
{
}
{
}
enum mail_error *error_r)
{
/* We get here only in error situations, so we have to return some
error. If storage->error is NONE, it means we forgot to set it at
some point.. */
"BUG: Unknown internal error";
}
/* This shouldn't happen.. */
i_strdup_printf("BUG: Unknown 0x%x error",
}
return storage->error_string;
}
enum mail_error *error_r)
{
}
{
enum mail_error error;
return error;
}
{
return (storage->class_flags &
}
{
const char *error_string;
enum mail_error error;
return FALSE;
/* debugging is enabled - admin may be debugging a
(permission) problem, so return FALSE to get the caller to
log the full error message. */
return FALSE;
}
return TRUE;
}
const struct mailbox_settings *
{
struct mailbox_settings *const *box_set;
struct mail_namespace *ns;
return NULL;
if (ns->prefix_len > 0 &&
/* namespace prefix itself */
vname = "";
}
}
return *box_set;
}
return NULL;
}
enum mailbox_flags flags)
{
struct mail_storage *storage;
/* make sure INBOX shows up in uppercase everywhere. do this
regardless of whether we're in inbox=yes namespace, because
clients expect INBOX to be case insensitive regardless of
server's internal configuration. */
vname = "INBOX";
}
/* just use the first storage. FIXME: does this break? */
}
T_BEGIN {
} T_END;
return box;
}
const guid_128_t guid,
enum mailbox_flags flags)
{
struct mailbox_metadata metadata;
const char *vname;
&metadata) < 0) {
/* GUID mismatch, refresh cache and try again */
mailbox_free(&box);
} else {
/* successfully opened the correct mailbox */
return box;
}
i_error("mailbox_alloc_guid(%s): "
"Couldn't verify mailbox GUID: %s",
mailbox_free(&box);
} else {
}
}
return box;
}
{
if (box->inbox_user)
return TRUE;
}
{
const char *errstr;
enum mail_error error;
if (error != MAIL_ERROR_NOTFOUND) {
"Failed to autocreate mailbox %s: %s",
return -1;
}
MAILBOX_SET_AUTO_SUBSCRIBE) == 0) {
"Failed to autosubscribe to mailbox %s: %s",
return -1;
}
}
return 0;
}
{
int ret;
if (mailbox_autocreate(box) < 0)
return -1;
"Opening INBOX failed: %s",
}
return ret;
}
static bool
const char **error_r)
{
unsigned int i;
/* Make sure the vname is correct: non-empty, doesn't begin or end
with separator and no adjacent separators */
for (i = 0; vname[i] != '\0'; i++) {
if (prev_sep) {
*error_r = "Has adjacent hierarchy separators";
return FALSE;
}
} else {
}
}
if (prev_sep && i > 0) {
*error_r = "Ends with hierarchy separator";
return FALSE;
}
return TRUE;
}
{
if (box->inbox_user) {
/* this is INBOX - don't bother with further checks */
return 0;
}
if (ns->prefix_len > 0) {
if (vname[0] != '\0') {
vname++;
if (vname[0] == '\0') {
/* "namespace/" isn't a valid mailbox name. */
"Invalid mailbox name");
return -1;
}
}
}
"Character not allowed in mailbox name: '%c'", list_sep));
return -1;
}
"Invalid mailbox name: Begins with hierarchy separator");
return -1;
}
return -1;
}
return 0;
}
{
const char *path;
return 0;
if (mailbox_verify_name(box) < 0)
return -1;
/* Make sure box->_path is set, so mailbox_get_path() works from
now on. Note that this may also fail with some backends if the
mailbox doesn't exist. */
return -1;
/* if this is an autocreated mailbox, create it now */
if (mailbox_autocreate(box) < 0)
return -1;
&path) < 0)
return -1;
}
return 0;
}
static bool mailbox_name_has_control_chars(const char *name)
{
const char *p;
for (p = name; *p != '\0'; p++) {
if ((unsigned char)*p < ' ')
return TRUE;
}
return FALSE;
}
{
/* mailbox_alloc() already checks that vname is valid UTF8,
so we don't need to verify that.
check vname instead of storage name, because vname is what is
visible to users, while storage name may be a fixed length GUID. */
if (mailbox_verify_name(box) < 0)
return -1;
"Control characters not allowed in new mailbox names");
return -1;
}
"Mailbox name too long");
return -1;
}
return 0;
}
const char *name)
{
NAMESPACE_FLAG_LIST_CHILDREN)) == 0)
continue;
continue;
/* if prefix has multiple hierarchies, match
any of the hierarchies */
return TRUE;
}
return FALSE;
}
enum mailbox_existence *existence_r)
{
switch (box->open_error) {
case 0:
break;
case MAIL_ERROR_NOTFOUND:
return 0;
default:
/* unsure if this exists or not */
return -1;
}
if (mailbox_verify_name(box) < 0) {
/* the mailbox name is invalid. we don't know if it currently
exists or not, but since it can never be accessed in any way
report it as if it didn't exist. */
return 0;
}
return 0;
}
return -1;
/* listable namespace prefix always exists. */
return 0;
}
/* if this is a shared namespace with only INBOX and
mail_shared_explicit_inbox=no, we'll need to mark the namespace as
usable here since nothing else will. */
return 0;
}
static int ATTR_NULL(2)
{
int ret;
return 0;
switch (box->open_error) {
case 0:
break;
case MAIL_ERROR_NOTFOUND:
return -1;
default:
return -1;
}
if (mailbox_verify_existing_name(box) < 0)
return -1;
MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) == 0) {
"Storage doesn't support streamed mailboxes");
return -1;
}
}
T_BEGIN {
} T_END;
} T_END;
if (ret < 0) {
return -1;
}
return 0;
}
{
/* most importantly we don't do this because we want to avoid
a loop: mdbox storage rebuild -> mailbox_open() ->
mailbox_mark_index_deleted() -> mailbox_sync() ->
mdbox storage rebuild. */
return FALSE;
}
return FALSE;
return FALSE;
return FALSE;
return TRUE;
}
{
if (!box->mailbox_deleted)
return -1;
/* mailbox has been marked as deleted. if this deletion
started (and crashed) a long time ago, it can be confusing
to user that the mailbox can't be opened. so we'll just
undelete it and reopen. */
if(!mailbox_try_undelete(box))
return -1;
return -1;
}
return 0;
}
{
const char *index_dir;
int ret;
return 1;
&index_dir);
if (ret <= 0)
return ret; /* error / no private indexes */
return -1;
return 1;
}
{
int ret;
return 1;
if (mailbox_get_private_flags_mask(box) == 0)
return 0;
return ret;
return -1;
return 1;
}
{
}
{
if (mailbox_verify_name(box) < 0)
return -1;
}
{
return box->enabled_features;
}
{
return;
}
{
return;
if (box->transaction_count != 0) {
i_panic("Trying to close mailbox %s with open transactions",
}
}
{
}
{
const char *name1;
return FALSE;
return TRUE;
}
bool directory)
{
int ret;
if (mailbox_verify_create_name(box) < 0)
return -1;
return ret;
}
{
update->min_first_recent_uid == 0 ||
if (mailbox_verify_existing_name(box) < 0)
return -1;
}
{
struct mail_index_transaction *trans;
enum mail_index_transaction_flags trans_flags = 0;
enum mailbox_flags old_flag;
int ret;
/* we already marked it deleted. this allows plugins to
"lock" the deletion earlier. */
return 0;
}
if (ret < 0)
return -1;
if (del)
else
if (mail_index_transaction_commit(&trans) < 0) {
return -1;
}
/* sync the mailbox. this finishes the index deletion and it can
succeed only for a single session. we do it here, so the rest of
the deletion code doesn't have to worry about race conditions. */
return -1;
return 0;
}
{
int ret;
"Storage root can't be deleted");
return -1;
}
"INBOX can't be deleted.");
return -1;
}
if (mailbox_open(box) < 0) {
return -1;
/* \noselect mailbox */
}
/* deletion failed. revert the mark so it can maybe be
tried again later. */
return -1;
}
return ret;
}
{
int ret;
/* FIXME: should be a parameter to delete(), but since it changes API
don't do it for now */
return ret;
}
static bool
struct mail_storage *storage2,
const char **error_r)
{
return TRUE;
return FALSE;
}
/* e.g. mdbox where all mails are in storage/ directory and
they can't be easily moved from there. */
return FALSE;
}
return TRUE;
}
{
}
static bool
struct mailbox_list *list2,
const char **error_r)
{
*error_r = "one namespace has alt dir and another doesn't";
return FALSE;
}
*error_r = "one namespace has index dir and another doesn't";
return FALSE;
}
*error_r = "one namespace has control dir and another doesn't";
return FALSE;
}
return TRUE;
}
{
/* Check only name validity, \Noselect don't necessarily exist. */
if (mailbox_verify_name(src) < 0)
return -1;
"Can't rename mailbox root");
return -1;
}
if (mailbox_verify_create_name(dest) < 0) {
return -1;
}
i_debug("Can't rename '%s' to '%s': %s",
}
"Can't rename mailboxes across specified storages.");
return -1;
}
"Renaming not supported across non-private namespaces.");
return -1;
}
"Can't rename mailbox to itself.");
return -1;
}
}
{
if (mailbox_verify_name(box) < 0)
return -1;
}
{
struct mailbox_node *node;
}
{
}
struct mail_namespace *
{
}
{
}
{
}
{
}
{
}
{
return FALSE;
}
static void
struct mailbox_status *status_r)
{
}
enum mailbox_status_items items,
struct mailbox_status *status_r)
{
if (mailbox_verify_existing_name(box) < 0)
return -1;
}
enum mailbox_status_items items,
struct mailbox_status *status_r)
{
i_unreached();
}
struct mailbox_metadata *metadata_r)
{
if (mailbox_verify_existing_name(box) < 0)
return -1;
return -1;
return 0;
}
{
return MAIL_SEEN; /* FIXME */
else
return 0;
}
int mailbox_attribute_set(struct mailbox_transaction_context *t,
const struct mail_attribute_value *value)
{
}
int mailbox_attribute_unset(struct mailbox_transaction_context *t,
{
struct mail_attribute_value value;
}
const struct mail_attribute_value *value,
const char **str_r)
{
const unsigned char *data;
return 0;
}
"Attribute string value has NULs");
return -1;
}
}
return -1;
}
return 0;
}
int mailbox_attribute_get(struct mailbox_transaction_context *t,
struct mail_attribute_value *value_r)
{
int ret;
return ret;
return 1;
}
int mailbox_attribute_get_stream(struct mailbox_transaction_context *t,
struct mail_attribute_value *value_r)
{
int ret;
return ret;
return 1;
}
struct mailbox_attribute_iter *
const char *prefix)
{
}
{
}
{
}
struct mailbox_sync_context *
{
struct mailbox_sync_context *ctx;
if (box->transaction_count != 0) {
i_panic("Trying to sync mailbox %s with open transactions",
}
T_BEGIN {
} T_END;
return ctx;
}
struct mailbox_sync_rec *sync_rec_r)
{
}
struct mailbox_sync_status *status_r)
{
const char *errormsg;
enum mail_error error;
int ret;
if (error == MAIL_ERROR_NOTPOSSIBLE) {
}
}
if (ret == 0)
return ret;
}
{
struct mailbox_sync_context *ctx;
struct mailbox_sync_status status;
/* we don't care about mailbox's current state, so we might
as well fix inconsistency state */
}
}
{
}
{
}
struct mail_search_context *
mailbox_search_init(struct mailbox_transaction_context *t,
struct mail_search_args *args,
const enum mail_sort_type *sort_program,
struct mailbox_header_lookup_ctx *wanted_headers)
{
if (!args->simplified)
}
{
int ret;
return ret;
}
{
bool tryagain;
if (!tryagain)
return FALSE;
}
return TRUE;
}
{
return FALSE;
else {
return TRUE;
}
}
{
return ctx->seen_lost_data;
}
int mailbox_search_result_build(struct mailbox_transaction_context *t,
struct mail_search_args *args,
struct mail_search_result **result_r)
{
struct mail_search_context *ctx;
int ret;
if (ret < 0)
return ret;
}
struct mailbox_transaction_context *
{
struct mailbox_transaction_context *trans;
box->transaction_count++;
return trans;
}
int mailbox_transaction_commit(struct mailbox_transaction_context **t)
{
int ret;
/* Store changes temporarily so that plugins overriding
transaction_commit() can look at them. */
return ret;
}
struct mailbox_transaction_context **_t,
struct mail_transaction_commit_changes *changes_r)
{
struct mailbox_transaction_context *t = *_t;
unsigned int save_count = t->save_count;
int ret;
t->box->transaction_count--;
T_BEGIN {
} T_END;
/* either all the saved messages get UIDs or none, because a) we
failed, b) MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS not set,
c) backend doesn't support it (e.g. virtual plugin) */
return ret;
}
{
struct mailbox_transaction_context *t = *_t;
t->box->transaction_count--;
t->box->v.transaction_rollback(t);
}
{
return box->transaction_count;
}
void mailbox_transaction_set_max_modseq(struct mailbox_transaction_context *t,
{
}
struct mailbox *
mailbox_transaction_get_mailbox(const struct mailbox_transaction_context *t)
{
return t->box;
}
struct mail_save_context *
mailbox_save_alloc(struct mailbox_transaction_context *t)
{
struct mail_save_context *ctx;
return ctx;
}
enum mail_flags flags,
struct mail_keywords *keywords)
{
}
{
const char *const *keywords_list;
struct mail_keywords *keywords;
}
{
}
{
}
{
}
const char *envelope)
{
}
{
}
{
}
{
}
unsigned int order)
{
}
{
}
{
int ret;
return -1;
}
if (!(*ctx)->copying_via_save)
"Saving messages not supported");
ret = -1;
} else {
}
if (ret < 0) {
return -1;
}
return 0;
}
{
}
static void
enum mail_flags pvt_flags)
{
struct mail_save_private_changes *save;
if (!array_is_created(&t->pvt_saves))
}
{
int ret;
if (ret == 0 && !copying_via_save) {
if (pvt_flags != 0)
t->save_count++;
}
return ret;
}
{
struct mail_private *mail;
/* the dest_mail is no longer valid. if we're still saving
more mails, the mail sequence may get reused. make sure
the mail gets reset in between */
}
}
struct mailbox_transaction_context *
{
return ctx->transaction;
}
{
int ret;
mailbox_set_deleted(t->box);
return -1;
}
/* bypass virtual storage, so hard linking can be used whenever
possible */
if (ret == 0) {
if (pvt_flags != 0)
t->save_count++;
}
return ret;
}
{
return -1;
return 0;
}
{
}
{
}
{
"Mailbox was deleted under us");
}
const char **path_r)
{
int ret;
return 0;
}
return 1;
}
if (ret < 0) {
return -1;
}
return ret;
}
{
}
{
return;
return;
}
}
{
}
}
{
(void)mailbox_get_permissions(box);
}
int *fd_r)
{
int fd;
*fd_r = -1;
if (fd != -1) {
/* ok */
/* O_EXCL used, caller will handle this error */
return 0;
return -1;
"Mailbox doesn't allow inferior mailboxes");
return -1;
return -1;
} else {
"open(%s, O_CREAT) failed: %m", path);
return -1;
}
/* ok */
} else {
"fchown(%s) failed: %m", path);
}
}
return 1;
}
enum mailbox_list_path_type type)
{
const char *root_dir;
if (!perm->gid_origin_is_mailbox_path) {
/* mailbox root directory doesn't exist, create it */
return -1;
}
}
perm->file_create_gid_origin) == 0)
return 1;
return 0;
"Mailbox doesn't allow inferior mailboxes");
return -1;
return -1;
} else {
"mkdir_parents(%s) failed: %m", path);
return -1;
}
}
enum mailbox_list_path_type type)
{
int ret;
return ret;
&mail_dir) < 0)
return -1;
return 0;
/* the directory might not have been created yet */
}
/* we call this function even when the directory exists, so first do a
quick check to see if we need to mkdir anything */
return 0;
}
unsigned int secs)
{
}
{
enum mail_index_open_flags index_flags = 0;
#ifndef MMAP_CONFLICTS_WRITE
if (set->mmap_disable)
#endif
if (set->dotlock_use_excl)
if (set->mail_nfs_index)
return index_flags;
}