maildir-storage.c revision bf5f442cdac24f666e0b4f959f3c13c27c7b1e9e
/* Copyright (c) 2002-2012 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "mkdir-parents.h"
#include "eacces-error.h"
#include "unlink-old-files.h"
#include "mailbox-uidvalidity.h"
#include "mailbox-list-private.h"
#include "maildir-storage.h"
#include "maildir-uidlist.h"
#include "maildir-keywords.h"
#include "maildir-sync.h"
#include "index-mail.h"
#include <sys/stat.h>
#define MAILDIR_LIST_CONTEXT(obj) \
MODULE_CONTEXT(obj, maildir_mailbox_list_module)
struct maildir_mailbox_list_context {
union mailbox_list_module_context module_ctx;
const struct maildir_settings *set;
};
extern struct mail_storage maildir_storage;
extern struct mailbox maildir_mailbox;
static MODULE_CONTEXT_DEFINE_INIT(maildir_mailbox_list_module,
&mailbox_list_module_register);
static const char *maildir_subdirs[] = { "cur", "new", "tmp" };
static struct mail_storage *maildir_storage_alloc(void)
{
struct maildir_storage *storage;
pool_t pool;
pool = pool_alloconly_create("maildir storage", 512+256);
storage = p_new(pool, struct maildir_storage, 1);
storage->storage = maildir_storage;
storage->storage.pool = pool;
return &storage->storage;
}
static int
maildir_storage_create(struct mail_storage *_storage, struct mail_namespace *ns,
const char **error_r ATTR_UNUSED)
{
struct maildir_storage *storage = (struct maildir_storage *)_storage;
struct mailbox_list *list = ns->list;
const char *dir;
storage->set = mail_storage_get_driver_settings(_storage);
storage->temp_prefix = p_strdup(_storage->pool,
mailbox_list_get_temp_prefix(list));
if (list->set.control_dir == NULL && list->set.inbox_path == NULL &&
(ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0) {
/* put the temp files into tmp/ directory preferrably */
storage->temp_prefix = p_strconcat(_storage->pool, "tmp/",
storage->temp_prefix, NULL);
dir = mailbox_list_get_path(list, NULL,
MAILBOX_LIST_PATH_TYPE_DIR);
} else {
/* control dir should also be writable */
dir = mailbox_list_get_path(list, NULL,
MAILBOX_LIST_PATH_TYPE_CONTROL);
}
_storage->temp_path_prefix = p_strconcat(_storage->pool, dir, "/",
storage->temp_prefix, NULL);
return 0;
}
static void maildir_storage_get_list_settings(const struct mail_namespace *ns,
struct mailbox_list_settings *set)
{
if (set->layout == NULL)
set->layout = MAILBOX_LIST_NAME_MAILDIRPLUSPLUS;
if (set->subscription_fname == NULL)
set->subscription_fname = MAILDIR_SUBSCRIPTION_FILE_NAME;
if (set->inbox_path == NULL && *set->maildir_name == '\0' &&
(strcmp(set->layout, MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) == 0 ||
strcmp(set->layout, MAILBOX_LIST_NAME_FS) == 0) &&
(ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0) {
/* Maildir++ INBOX is the Maildir base itself */
set->inbox_path = set->root_dir;
}
}
static const char *
maildir_storage_find_root_dir(const struct mail_namespace *ns)
{
bool debug = ns->mail_set->mail_debug;
const char *home, *path;
/* we'll need to figure out the maildir location ourself.
It's ~/Maildir unless we are chrooted. */
if (mail_user_get_home(ns->owner, &home) > 0) {
path = t_strconcat(home, "/Maildir", NULL);
if (access(path, R_OK|W_OK|X_OK) == 0) {
if (debug)
i_debug("maildir: root exists (%s)", path);
return path;
}
if (debug)
i_debug("maildir: access(%s, rwx): failed: %m", path);
} else {
if (debug)
i_debug("maildir: Home directory not set");
if (access("/cur", R_OK|W_OK|X_OK) == 0) {
if (debug)
i_debug("maildir: /cur exists, assuming chroot");
return "/";
}
}
return NULL;
}
static bool maildir_storage_autodetect(const struct mail_namespace *ns,
struct mailbox_list_settings *set)
{
bool debug = ns->mail_set->mail_debug;
struct stat st;
const char *path, *root_dir;
if (set->root_dir != NULL)
root_dir = set->root_dir;
else {
root_dir = maildir_storage_find_root_dir(ns);
if (root_dir == NULL) {
if (debug)
i_debug("maildir: couldn't find root dir");
return FALSE;
}
}
path = t_strconcat(root_dir, "/cur", NULL);
if (stat(path, &st) < 0) {
if (debug)
i_debug("maildir autodetect: stat(%s) failed: %m", path);
return FALSE;
}
if (!S_ISDIR(st.st_mode)) {
if (debug)
i_debug("maildir autodetect: %s not a directory", path);
return FALSE;
}
set->root_dir = root_dir;
maildir_storage_get_list_settings(ns, set);
return TRUE;
}
static int
mkdir_verify(struct mail_storage *storage, struct mail_namespace *ns,
const char *dir, mode_t mode, gid_t gid, const char *gid_origin,
bool verify)
{
struct stat st;
if (verify) {
if (stat(dir, &st) == 0)
return 0;
if (errno != ENOENT) {
mail_storage_set_critical(storage,
"stat(%s) failed: %m", dir);
return -1;
}
}
if (mkdir_parents_chgrp(dir, mode, gid, gid_origin) == 0)
return 0;
if (errno == EEXIST) {
if (verify)
return 0;
mail_storage_set_error(storage, MAIL_ERROR_EXISTS,
"Mailbox already exists");
} else if (errno == ENOENT) {
mail_storage_set_error(storage, MAIL_ERROR_NOTFOUND,
"Mailbox was deleted while it was being created");
} else if (errno == EACCES) {
if (ns->type == NAMESPACE_SHARED) {
/* shared namespace, don't log permission errors */
mail_storage_set_error(storage, MAIL_ERROR_PERM,
MAIL_ERRSTR_NO_PERMISSION);
return -1;
}
mail_storage_set_critical(storage, "%s",
mail_error_create_eacces_msg("mkdir", dir));
} else {
mail_storage_set_critical(storage,
"mkdir(%s) failed: %m", dir);
}
return -1;
}
static int maildir_check_tmp(struct mail_storage *storage, const char *dir)
{
const char *path;
struct stat st;
/* if tmp/ directory exists, we need to clean it up once in a while */
path = t_strconcat(dir, "/tmp", NULL);
if (stat(path, &st) < 0) {
if (errno == ENOENT || errno == ENAMETOOLONG)
return 0;
if (errno == EACCES) {
mail_storage_set_critical(storage, "%s",
mail_error_eacces_msg("stat", path));
return -1;
}
mail_storage_set_critical(storage, "stat(%s) failed: %m", path);
return -1;
}
if (st.st_atime > st.st_ctime + MAILDIR_TMP_DELETE_SECS) {
/* the directory should be empty. we won't do anything
until ctime changes. */
} else if (st.st_atime < ioloop_time - MAILDIR_TMP_SCAN_SECS) {
/* time to scan */
(void)unlink_old_files(path, "",
ioloop_time - MAILDIR_TMP_DELETE_SECS);
}
return 1;
}
/* create or fix maildir, ignore if it already exists */
static int create_maildir(struct mailbox *box, bool verify)
{
const struct mailbox_permissions *perm = mailbox_get_permissions(box);
const char *path;
unsigned int i;
enum mail_error error;
int ret = 0;
for (i = 0; i < N_ELEMENTS(maildir_subdirs); i++) {
path = t_strconcat(mailbox_get_path(box), "/",
maildir_subdirs[i], NULL);
if (mkdir_verify(box->storage, box->list->ns, path,
perm->dir_create_mode, perm->file_create_gid,
perm->file_create_gid_origin, verify) < 0) {
error = mailbox_get_last_mail_error(box);
if (error != MAIL_ERROR_EXISTS)
return -1;
/* try to create all of the directories in case one
of them doesn't exist */
ret = -1;
}
}
return ret;
}
static void maildir_lock_touch_timeout(struct maildir_mailbox *mbox)
{
(void)maildir_uidlist_lock_touch(mbox->uidlist);
}
static struct mailbox *
maildir_mailbox_alloc(struct mail_storage *storage, struct mailbox_list *list,
const char *vname, enum mailbox_flags flags)
{
struct maildir_mailbox *mbox;
pool_t pool;
pool = pool_alloconly_create("maildir mailbox", 1024*3);
mbox = p_new(pool, struct maildir_mailbox, 1);
mbox->box = maildir_mailbox;
mbox->box.pool = pool;
mbox->box.storage = storage;
mbox->box.list = list;
mbox->box.mail_vfuncs = &maildir_mail_vfuncs;
mbox->maildir_list_index_ext_id = (uint32_t)-1;
index_storage_mailbox_alloc(&mbox->box, vname, flags,
MAILDIR_INDEX_PREFIX);
mbox->storage = (struct maildir_storage *)storage;
return &mbox->box;
}
static int maildir_mailbox_open_existing(struct mailbox *box)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
mbox->uidlist = maildir_uidlist_init(mbox);
mbox->keywords = maildir_keywords_init(mbox);
if ((box->flags & MAILBOX_FLAG_KEEP_LOCKED) != 0) {
if (maildir_uidlist_lock(mbox->uidlist) <= 0)
return -1;
mbox->keep_lock_to = timeout_add(MAILDIR_LOCK_TOUCH_SECS * 1000,
maildir_lock_touch_timeout,
mbox);
}
if (index_storage_mailbox_open(box, FALSE) < 0)
return -1;
mbox->maildir_ext_id =
mail_index_ext_register(mbox->box.index, "maildir",
sizeof(mbox->maildir_hdr), 0, 0);
return 0;
}
static bool maildir_storage_is_readonly(struct mailbox *box)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
if (index_storage_is_readonly(box))
return TRUE;
if (maildir_is_backend_readonly(mbox)) {
/* return read-only only if there are no private flags
(that are stored in index files) */
if (mailbox_get_private_flags_mask(box) == 0)
return TRUE;
}
return FALSE;
}
static int
maildir_mailbox_exists(struct mailbox *box, bool auto_boxes ATTR_UNUSED,
enum mailbox_existence *existence_r)
{
return index_storage_mailbox_exists_full(box, "cur", existence_r);
}
static int maildir_mailbox_open(struct mailbox *box)
{
const char *box_path = mailbox_get_path(box);
const char *root_dir;
struct stat st;
int ret;
/* begin by checking if tmp/ directory exists and if it should be
cleaned up. */
ret = maildir_check_tmp(box->storage, box_path);
if (ret > 0) {
/* exists */
return maildir_mailbox_open_existing(box);
}
if (ret < 0)
return -1;
/* tmp/ directory doesn't exist. does the maildir? */
root_dir = mailbox_list_get_path(box->list, NULL,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (strcmp(box_path, root_dir) == 0) {
/* root directory. either INBOX or some other namespace root */
errno = ENOENT;
} else if (stat(box_path, &st) == 0) {
/* yes, we'll need to create the missing dirs */
if (create_maildir(box, TRUE) < 0)
return -1;
return maildir_mailbox_open_existing(box);
}
if (errno == ENOENT || errno == ENAMETOOLONG) {
mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(box->name));
return -1;
} else {
mail_storage_set_critical(box->storage,
"stat(%s) failed: %m", box_path);
return -1;
}
}
static int maildir_create_shared(struct mailbox *box)
{
const struct mailbox_permissions *perm = mailbox_get_permissions(box);
const char *path;
mode_t old_mask;
int fd;
old_mask = umask(0);
path = t_strconcat(mailbox_get_path(box), "/dovecot-shared", NULL);
fd = open(path, O_WRONLY | O_CREAT, perm->file_create_mode);
umask(old_mask);
if (fd == -1) {
mail_storage_set_critical(box->storage, "open(%s) failed: %m",
path);
return -1;
}
if (fchown(fd, (uid_t)-1, perm->file_create_gid) < 0) {
if (errno == EPERM) {
mail_storage_set_critical(box->storage, "%s",
eperm_error_get_chgrp("fchown", path,
perm->file_create_gid,
perm->file_create_gid_origin));
} else {
mail_storage_set_critical(box->storage,
"fchown(%s) failed: %m", path);
}
}
(void)close(fd);
return 0;
}
static int
maildir_mailbox_update(struct mailbox *box, const struct mailbox_update *update)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
struct maildir_uidlist *uidlist;
int ret;
if (!box->opened) {
if (mailbox_open(box) < 0)
return -1;
}
uidlist = mbox->uidlist;
if (maildir_uidlist_lock(uidlist) <= 0)
return -1;
if (!guid_128_is_empty(update->mailbox_guid))
maildir_uidlist_set_mailbox_guid(uidlist, update->mailbox_guid);
if (update->uid_validity != 0)
maildir_uidlist_set_uid_validity(uidlist, update->uid_validity);
if (update->min_next_uid != 0) {
maildir_uidlist_set_next_uid(uidlist, update->min_next_uid,
FALSE);
}
ret = maildir_uidlist_update(uidlist);
if (ret == 0)
ret = index_storage_mailbox_update(box, update);
maildir_uidlist_unlock(uidlist);
return ret;
}
static int
maildir_mailbox_create(struct mailbox *box, const struct mailbox_update *update,
bool directory)
{
const char *root_dir, *shared_path;
struct stat st;
int ret;
if (directory &&
(box->list->props & MAILBOX_LIST_PROP_NO_NOSELECT) == 0)
return 0;
ret = maildir_check_tmp(box->storage, mailbox_get_path(box));
if (ret > 0) {
mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
"Mailbox already exists");
return -1;
}
if (ret < 0)
return -1;
if (create_maildir(box, FALSE) < 0)
return -1;
/* if dovecot-shared exists in the root dir, copy it to newly
created mailboxes */
root_dir = mailbox_list_get_path(box->list, NULL,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
shared_path = t_strconcat(root_dir, "/dovecot-shared", NULL);
if (stat(shared_path, &st) == 0) {
if (maildir_create_shared(box) < 0)
return -1;
}
return update == NULL ? 0 : maildir_mailbox_update(box, update);
}
static int
maildir_mailbox_get_metadata(struct mailbox *box,
enum mailbox_metadata_items items,
struct mailbox_metadata *metadata_r)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
if (index_mailbox_get_metadata(box, items, metadata_r) < 0)
return -1;
if ((items & MAILBOX_METADATA_GUID) != 0) {
if (maildir_uidlist_get_mailbox_guid(mbox->uidlist,
metadata_r->guid) < 0)
return -1;
}
return 0;
}
static void maildir_mailbox_close(struct mailbox *box)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
if (mbox->keep_lock_to != NULL) {
maildir_uidlist_unlock(mbox->uidlist);
timeout_remove(&mbox->keep_lock_to);
}
if (mbox->flags_view != NULL)
mail_index_view_close(&mbox->flags_view);
if (mbox->keywords != NULL)
maildir_keywords_deinit(&mbox->keywords);
maildir_uidlist_deinit(&mbox->uidlist);
index_storage_mailbox_close(box);
}
static void maildir_notify_changes(struct mailbox *box)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
const char *box_path = mailbox_get_path(box);
if (box->notify_callback == NULL)
index_mailbox_check_remove_all(&mbox->box);
else {
index_mailbox_check_add(&mbox->box,
t_strconcat(box_path, "/new", NULL));
index_mailbox_check_add(&mbox->box,
t_strconcat(box_path, "/cur", NULL));
}
}
static bool
maildir_is_internal_name(struct mailbox_list *list ATTR_UNUSED,
const char *name)
{
return strcmp(name, "cur") == 0 ||
strcmp(name, "new") == 0 ||
strcmp(name, "tmp") == 0;
}
static void maildir_storage_add_list(struct mail_storage *storage,
struct mailbox_list *list)
{
struct maildir_mailbox_list_context *mlist;
mlist = p_new(list->pool, struct maildir_mailbox_list_context, 1);
mlist->module_ctx.super = list->v;
mlist->set = mail_storage_get_driver_settings(storage);
list->v.is_internal_name = maildir_is_internal_name;
MODULE_CONTEXT_SET(list, maildir_mailbox_list_module, mlist);
}
uint32_t maildir_get_uidvalidity_next(struct mailbox_list *list)
{
const char *path;
path = mailbox_list_get_path(list, NULL,
MAILBOX_LIST_PATH_TYPE_CONTROL);
path = t_strconcat(path, "/"MAILDIR_UIDVALIDITY_FNAME, NULL);
return mailbox_uidvalidity_next(list, path);
}
static enum mail_flags maildir_get_private_flags_mask(struct mailbox *box)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
const char *path, *path2;
struct stat st;
if (mbox->private_flags_mask_set)
return mbox->_private_flags_mask;
mbox->private_flags_mask_set = TRUE;
path = mailbox_list_get_path(box->list, NULL,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
path2 = mailbox_list_get_path(box->list, NULL,
MAILBOX_LIST_PATH_TYPE_INDEX);
if (strcmp(path, path2) == 0) {
/* no separate index directory. we can't have private flags,
so don't even bother checking if dovecot-shared exists */
} else {
path = t_strconcat(mailbox_get_path(box),
"/dovecot-shared", NULL);
if (stat(path, &st) == 0)
mbox->_private_flags_mask = MAIL_SEEN;
}
return mbox->_private_flags_mask;
}
bool maildir_is_backend_readonly(struct maildir_mailbox *mbox)
{
if (!mbox->backend_readonly_set) {
const char *box_path = mailbox_get_path(&mbox->box);
mbox->backend_readonly_set = TRUE;
if (access(t_strconcat(box_path, "/cur", NULL), W_OK) < 0 &&
errno == EACCES)
mbox->backend_readonly = TRUE;
}
return mbox->backend_readonly;
}
struct mail_storage maildir_storage = {
.name = MAILDIR_STORAGE_NAME,
.class_flags = MAIL_STORAGE_CLASS_FLAG_FILE_PER_MSG,
.v = {
maildir_get_setting_parser_info,
maildir_storage_alloc,
maildir_storage_create,
NULL,
maildir_storage_add_list,
maildir_storage_get_list_settings,
maildir_storage_autodetect,
maildir_mailbox_alloc,
NULL
}
};
struct mailbox maildir_mailbox = {
.v = {
maildir_storage_is_readonly,
index_storage_mailbox_enable,
maildir_mailbox_exists,
maildir_mailbox_open,
maildir_mailbox_close,
index_storage_mailbox_free,
maildir_mailbox_create,
maildir_mailbox_update,
index_storage_mailbox_delete,
index_storage_mailbox_rename,
index_storage_get_status,
maildir_mailbox_get_metadata,
index_storage_set_subscribed,
maildir_list_index_has_changed,
maildir_list_index_update_sync,
maildir_storage_sync_init,
index_mailbox_sync_next,
index_mailbox_sync_deinit,
NULL,
maildir_notify_changes,
index_transaction_begin,
index_transaction_commit,
index_transaction_rollback,
maildir_get_private_flags_mask,
index_mail_alloc,
index_storage_search_init,
index_storage_search_deinit,
index_storage_search_next_nonblock,
index_storage_search_next_update_seq,
maildir_save_alloc,
maildir_save_begin,
maildir_save_continue,
maildir_save_finish,
maildir_save_cancel,
maildir_copy,
maildir_transaction_save_commit_pre,
maildir_transaction_save_commit_post,
maildir_transaction_save_rollback,
index_storage_is_inconsistent
}
};