maildir-storage.c revision ca98d6a1bbe73499da758a36bfab2963375c8d06
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce/* Copyright (c) 2002-2010 Dovecot authors, see the included COPYING file */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "lib.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "ioloop.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "array.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "hostpid.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "str.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "mkdir-parents.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "eacces-error.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "unlink-directory.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "unlink-old-files.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "mailbox-log.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "mailbox-uidvalidity.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "maildir-storage.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "maildir-uidlist.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "maildir-keywords.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "maildir-sync.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include "index-mail.h"
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include <stdio.h>
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include <stdlib.h>
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include <dirent.h>
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include <unistd.h>
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#include <sys/stat.h>
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce#define MAILDIR_LIST_CONTEXT(obj) \
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce MODULE_CONTEXT(obj, maildir_mailbox_list_module)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestruct maildir_mailbox_list {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce union mailbox_list_module_context module_ctx;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const struct maildir_settings *set;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce};
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestruct rename_context {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce bool found;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce size_t oldnamelen;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *newname;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce};
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorceextern struct mail_storage maildir_storage;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorceextern struct mailbox maildir_mailbox;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic MODULE_CONTEXT_DEFINE_INIT(maildir_mailbox_list_module,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce &mailbox_list_module_register);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic const char *maildir_subdirs[] = { "cur", "new", "tmp" };
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic bool maildir_is_internal_name(const char *name)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return strcmp(name, "cur") == 0 ||
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce strcmp(name, "new") == 0 ||
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce strcmp(name, "tmp") == 0;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic bool maildir_storage_is_valid_existing_name(struct mailbox_list *list,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *name)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct maildir_mailbox_list *mlist = MAILDIR_LIST_CONTEXT(list);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *p;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (!mlist->module_ctx.super.is_valid_existing_name(list, name))
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return FALSE;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* Don't allow the mailbox name to end in cur/new/tmp */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce p = strrchr(name, '/');
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (p != NULL)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce name = p + 1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return !maildir_is_internal_name(name);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic bool maildir_storage_is_valid_create_name(struct mailbox_list *list,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *name)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct maildir_mailbox_list *mlist = MAILDIR_LIST_CONTEXT(list);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce bool ret = TRUE;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (!mlist->module_ctx.super.is_valid_create_name(list, name))
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return FALSE;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* Don't allow creating mailboxes under cur/new/tmp */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce T_BEGIN {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *const *tmp;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce for (tmp = t_strsplit(name, "/"); *tmp != NULL; tmp++) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (maildir_is_internal_name(*tmp)) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce ret = FALSE;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce break;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce } T_END;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return ret;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic struct mail_storage *maildir_storage_alloc(void)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct maildir_storage *storage;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce pool_t pool;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce pool = pool_alloconly_create("maildir storage", 512+256);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce storage = p_new(pool, struct maildir_storage, 1);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce storage->storage = maildir_storage;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce storage->storage.pool = pool;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return &storage->storage;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic int
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcemaildir_storage_create(struct mail_storage *_storage, struct mail_namespace *ns,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char **error_r ATTR_UNUSED)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct maildir_storage *storage = (struct maildir_storage *)_storage;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct mailbox_list *list = ns->list;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *dir;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce storage->set = mail_storage_get_driver_settings(_storage);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce storage->maildir_list_ext_id = (uint32_t)-1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce storage->temp_prefix = p_strdup(_storage->pool,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mailbox_list_get_temp_prefix(list));
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (list->set.control_dir == NULL && list->set.inbox_path == NULL &&
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce (ns->flags & NAMESPACE_FLAG_INBOX) != 0) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* put the temp files into tmp/ directory preferrably */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce storage->temp_prefix = p_strconcat(_storage->pool, "tmp/",
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce storage->temp_prefix, NULL);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce dir = mailbox_list_get_path(list, NULL,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce MAILBOX_LIST_PATH_TYPE_DIR);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce } else {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* control dir should also be writable */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce dir = mailbox_list_get_path(list, NULL,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce MAILBOX_LIST_PATH_TYPE_CONTROL);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce _storage->temp_path_prefix = p_strconcat(_storage->pool, dir, "/",
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce storage->temp_prefix, NULL);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return 0;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic void maildir_storage_get_list_settings(const struct mail_namespace *ns,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct mailbox_list_settings *set)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (set->layout == NULL)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce set->layout = MAILBOX_LIST_NAME_MAILDIRPLUSPLUS;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (set->subscription_fname == NULL)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce set->subscription_fname = MAILDIR_SUBSCRIPTION_FILE_NAME;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (set->inbox_path == NULL &&
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce (strcmp(set->layout, MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) == 0 ||
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce strcmp(set->layout, MAILBOX_LIST_NAME_FS) == 0) &&
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce (ns->flags & NAMESPACE_FLAG_INBOX) != 0) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* Maildir++ INBOX is the Maildir base itself */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce set->inbox_path = set->root_dir;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic const char *
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcemaildir_storage_find_root_dir(const struct mail_namespace *ns)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce bool debug = ns->mail_set->mail_debug;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *home, *path;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* we'll need to figure out the maildir location ourself.
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce It's ~/Maildir unless we are chrooted. */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (mail_user_get_home(ns->user, &home) > 0) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce path = t_strconcat(home, "/Maildir", NULL);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (access(path, R_OK|W_OK|X_OK) == 0) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (debug)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce i_debug("maildir: root exists (%s)", path);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return path;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (debug)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce i_debug("maildir: access(%s, rwx): failed: %m", path);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce } else {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (debug)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce i_debug("maildir: Home directory not set");
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (access("/cur", R_OK|W_OK|X_OK) == 0) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (debug)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce i_debug("maildir: /cur exists, assuming chroot");
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return "/";
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return NULL;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic bool maildir_storage_autodetect(const struct mail_namespace *ns,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct mailbox_list_settings *set)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce bool debug = ns->mail_set->mail_debug;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct stat st;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *path, *root_dir;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (set->root_dir != NULL)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce root_dir = set->root_dir;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce else {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce root_dir = maildir_storage_find_root_dir(ns);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (root_dir == NULL) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (debug)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce i_debug("maildir: couldn't find root dir");
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return FALSE;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce path = t_strconcat(root_dir, "/cur", NULL);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (stat(path, &st) < 0) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (debug)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce i_debug("maildir autodetect: stat(%s) failed: %m", path);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return FALSE;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (!S_ISDIR(st.st_mode)) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (debug)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce i_debug("maildir autodetect: %s not a directory", path);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return FALSE;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce set->root_dir = root_dir;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce maildir_storage_get_list_settings(ns, set);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return TRUE;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic int
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcemkdir_verify(struct mail_storage *storage, struct mail_namespace *ns,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *dir, mode_t mode, gid_t gid, const char *gid_origin,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce bool verify)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct stat st;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (verify) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (stat(dir, &st) == 0)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return 0;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (errno != ENOENT) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_storage_set_critical(storage,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce "stat(%s) failed: %m", dir);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return -1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (mkdir_parents_chgrp(dir, mode, gid, gid_origin) == 0)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return 0;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (errno == EEXIST) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (verify)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return 0;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_storage_set_error(storage, MAIL_ERROR_EXISTS,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce "Mailbox already exists");
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce } else if (errno == ENOENT) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_storage_set_error(storage, MAIL_ERROR_NOTFOUND,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce "Mailbox was deleted while it was being created");
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce } else if (errno == EACCES) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (ns->type == NAMESPACE_SHARED) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* shared namespace, don't log permission errors */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_storage_set_error(storage, MAIL_ERROR_PERM,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce MAIL_ERRSTR_NO_PERMISSION);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return -1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_storage_set_critical(storage, "%s",
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_error_create_eacces_msg("mkdir", dir));
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce } else {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_storage_set_critical(storage,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce "mkdir(%s) failed: %m", dir);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return -1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic int maildir_check_tmp(struct mail_storage *storage, const char *dir)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *path;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct stat st;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* if tmp/ directory exists, we need to clean it up once in a while */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce path = t_strconcat(dir, "/tmp", NULL);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (stat(path, &st) < 0) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (errno == ENOENT)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return 0;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (errno == EACCES) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_storage_set_critical(storage, "%s",
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_error_eacces_msg("stat", path));
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return -1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_storage_set_critical(storage, "stat(%s) failed: %m", path);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return -1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (st.st_atime > st.st_ctime + MAILDIR_TMP_DELETE_SECS) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* the directory should be empty. we won't do anything
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce until ctime changes. */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce } else if (st.st_atime < ioloop_time - MAILDIR_TMP_SCAN_SECS) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce /* time to scan */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce (void)unlink_old_files(path, "",
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce ioloop_time - MAILDIR_TMP_DELETE_SECS);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return 1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce/* create or fix maildir, ignore if it already exists */
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic int create_maildir(struct mailbox *box, bool verify)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *path;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce unsigned int i;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce int ret;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (!verify) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce ret = maildir_check_tmp(box->storage, box->path);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (ret > 0) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_storage_set_error(box->storage,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce MAIL_ERROR_EXISTS, "Mailbox already exists");
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return -1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (ret < 0)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return -1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce for (i = 0; i < N_ELEMENTS(maildir_subdirs); i++) {
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce path = t_strconcat(box->path, "/", maildir_subdirs[i], NULL);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce if (mkdir_verify(box->storage, box->list->ns, path,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce box->dir_create_mode, box->file_create_gid,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce box->file_create_gid_origin, verify) < 0)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return -1;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce }
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return 0;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic void maildir_lock_touch_timeout(struct maildir_mailbox *mbox)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce (void)maildir_uidlist_lock_touch(mbox->uidlist);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcestatic struct mailbox *
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorcemaildir_mailbox_alloc(struct mail_storage *storage, struct mailbox_list *list,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce const char *name, struct istream *input,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce enum mailbox_flags flags)
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce{
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce struct maildir_mailbox *mbox;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce pool_t pool;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce pool = pool_alloconly_create("maildir mailbox", 1024+512);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox = p_new(pool, struct maildir_mailbox, 1);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->ibox.box = maildir_mailbox;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->ibox.box.pool = pool;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->ibox.box.storage = storage;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->ibox.box.list = list;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->ibox.box.mail_vfuncs = &maildir_mail_vfuncs;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->ibox.save_commit_pre = maildir_transaction_save_commit_pre;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->ibox.save_commit_post = maildir_transaction_save_commit_post;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->ibox.save_rollback = maildir_transaction_save_rollback;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce index_storage_mailbox_alloc(&mbox->ibox, name, input, flags,
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce MAILDIR_INDEX_PREFIX);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->storage = (struct maildir_storage *)storage;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->maildir_ext_id =
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mail_index_ext_register(mbox->ibox.box.index, "maildir",
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce sizeof(mbox->maildir_hdr), 0, 0);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->uidlist = maildir_uidlist_init(mbox);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce mbox->keywords = maildir_keywords_init(mbox);
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce return &mbox->ibox.box;
5dbf360f2d6b0281c32f1bba6ebf5cc834c1716eSimo Sorce}
static int maildir_mailbox_open_existing(struct mailbox *box)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
struct stat st;
const char *shared_path;
shared_path = t_strconcat(box->path, "/dovecot-shared", NULL);
if (stat(shared_path, &st) == 0)
box->private_flags_mask = MAIL_SEEN;
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 (access(t_strconcat(box->path, "/cur", NULL), W_OK) < 0 &&
errno == EACCES)
mbox->ibox.backend_readonly = TRUE;
return index_storage_mailbox_open(box);
}
static int maildir_mailbox_open(struct mailbox *box)
{
struct stat st;
int ret;
bool inbox;
if (box->input != NULL) {
mail_storage_set_critical(box->storage,
"Maildir doesn't support streamed mailboxes");
return -1;
}
inbox = strcmp(box->name, "INBOX") == 0 &&
(box->list->ns->flags & NAMESPACE_FLAG_INBOX) != 0;
/* 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? */
if (inbox || (*box->name != '\0' && 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);
} else if (*box->name == '\0' || errno == ENOENT) {
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 char *path;
mode_t old_mask;
int fd;
old_mask = umask(0);
path = t_strconcat(box->path, "/dovecot-shared", NULL);
fd = open(path, O_WRONLY | O_CREAT, box->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, box->file_create_gid) < 0) {
if (errno == EPERM) {
mail_storage_set_critical(box->storage, "%s",
eperm_error_get_chgrp("fchown", path,
box->file_create_gid,
box->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 = mbox->uidlist;
int ret;
if (!box->opened) {
if (mailbox_open(box) < 0)
return -1;
}
if (maildir_uidlist_lock(uidlist) <= 0)
return -1;
if (!mail_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;
if (directory &&
(box->list->props & MAILBOX_LIST_PROP_NO_NOSELECT) == 0)
return 0;
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 void
maildir_storage_get_status(struct mailbox *box, enum mailbox_status_items items,
struct mailbox_status *status_r)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
index_storage_get_status(box, items, status_r);
if ((items & STATUS_GUID) != 0) {
(void)maildir_uidlist_get_mailbox_guid(mbox->uidlist,
status_r->mailbox_guid);
}
}
static const char *
maildir_get_unlink_dest(struct mailbox_list *list, const char *name)
{
const char *root_dir;
char sep;
if (list->mail_set->mail_full_filesystem_access &&
(*name == '/' || *name == '~'))
return NULL;
if (strcmp(mailbox_list_get_driver_name(list),
MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) != 0) {
/* Not maildir++ driver. Don't use this trick. */
return NULL;
}
root_dir = mailbox_list_get_path(list, NULL,
MAILBOX_LIST_PATH_TYPE_DIR);
sep = mailbox_list_get_hierarchy_sep(list);
return t_strdup_printf("%s/%c%c"MAILDIR_UNLINK_DIRNAME, root_dir,
sep, sep);
}
static int
maildir_delete_nonrecursive(struct mailbox_list *list, const char *path,
const char *name)
{
DIR *dir;
struct dirent *d;
string_t *full_path;
unsigned int dir_len;
bool unlinked_something = FALSE;
dir = opendir(path);
if (dir == NULL) {
if (errno == ENOENT) {
mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
} else {
mailbox_list_set_critical(list,
"opendir(%s) failed: %m", path);
}
return -1;
}
full_path = t_str_new(256);
str_append(full_path, path);
str_append_c(full_path, '/');
dir_len = str_len(full_path);
errno = 0;
while ((d = readdir(dir)) != NULL) {
if (d->d_name[0] == '.') {
/* skip . and .. */
if (d->d_name[1] == '\0')
continue;
if (d->d_name[1] == '.' && d->d_name[2] == '\0')
continue;
}
str_truncate(full_path, dir_len);
str_append(full_path, d->d_name);
if (maildir_is_internal_name(d->d_name)) {
if (unlink_directory(str_c(full_path), TRUE) < 0) {
mailbox_list_set_critical(list,
"unlink_directory(%s) failed: %m",
str_c(full_path));
} else {
unlinked_something = TRUE;
}
continue;
}
/* trying to unlink() a directory gives either EPERM or EISDIR
(non-POSIX). it doesn't really work anywhere in practise,
so don't bother stat()ing the file first */
if (unlink(str_c(full_path)) == 0)
unlinked_something = TRUE;
else if (errno != ENOENT && errno != EISDIR && errno != EPERM) {
mailbox_list_set_critical(list,
"unlink_directory(%s) failed: %m",
str_c(full_path));
}
}
if (closedir(dir) < 0) {
mailbox_list_set_critical(list, "closedir(%s) failed: %m",
path);
}
if (rmdir(path) == 0)
unlinked_something = TRUE;
else if (errno != ENOENT && errno != ENOTEMPTY) {
mailbox_list_set_critical(list, "rmdir(%s) failed: %m", path);
return -1;
}
if (!unlinked_something) {
mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
t_strdup_printf("Directory %s isn't empty, "
"can't delete it.", name));
return -1;
}
return 0;
}
static int
maildir_delete_with_trash(struct mailbox_list *list, const char *src,
const char *dest, const char *name)
{
unsigned int count;
/* rename the .maildir into ..DOVECOT-TRASH which atomically
marks it as being deleted. If we die before deleting the
..DOVECOT-TRASH directory, it gets deleted the next time
mailbox listing sees it. */
count = 0;
while (rename(src, dest) < 0) {
if (errno == ENOENT) {
/* it was just deleted under us by
another process */
mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
return -1;
}
if (!EDESTDIREXISTS(errno)) {
mailbox_list_set_critical(list,
"rename(%s, %s) failed: %m", src, dest);
return -1;
}
/* already existed, delete it and try again */
if (unlink_directory(dest, TRUE) < 0 &&
(errno != ENOTEMPTY || count >= 5)) {
mailbox_list_set_critical(list,
"unlink_directory(%s) failed: %m", dest);
return -1;
}
count++;
}
if (unlink_directory(dest, TRUE) < 0 && errno != ENOTEMPTY) {
mailbox_list_set_critical(list,
"unlink_directory(%s) failed: %m", dest);
/* it's already renamed to ..dir, which means it's
deleted as far as the client is concerned. Report
success. */
}
return 0;
}
static void mailbox_get_guid(struct mailbox_list *list, const char *name,
uint8_t mailbox_guid[MAIL_GUID_128_SIZE])
{
struct mailbox *box;
struct mailbox_status status;
box = mailbox_alloc(list, name, NULL, MAILBOX_FLAG_KEEP_RECENT);
if (mailbox_open(box) < 0)
memset(mailbox_guid, 0, MAIL_GUID_128_SIZE);
else {
mailbox_get_status(box, STATUS_GUID, &status);
memcpy(mailbox_guid, status.mailbox_guid, MAIL_GUID_128_SIZE);
}
mailbox_close(&box);
}
static int
maildir_list_delete_mailbox(struct mailbox_list *list, const char *name)
{
union mailbox_list_module_context *mlist = MAILDIR_LIST_CONTEXT(list);
uint8_t mailbox_guid[MAIL_GUID_128_SIZE];
uint8_t dir_sha128[MAIL_GUID_128_SIZE];
struct stat st;
const char *src, *dest, *base;
int ret;
mailbox_get_guid(list, name, mailbox_guid);
/* delete the index and control directories */
if (mlist->super.delete_mailbox(list, name) < 0)
return -1;
/* check if the mailbox actually exists */
src = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (lstat(src, &st) != 0 && errno == ENOENT) {
mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
return -1;
}
if (!S_ISDIR(st.st_mode)) {
/* a symlink most likely */
if (unlink(src) < 0 && errno != ENOENT) {
mailbox_list_set_critical(list,
"unlink(%s) failed: %m", src);
return -1;
}
return 0;
}
if (strcmp(name, "INBOX") == 0) {
/* we shouldn't get this far if this is the actual INBOX.
more likely we're just deleting a namespace/INBOX.
be anyway sure that we don't accidentally delete the entire
maildir (INBOX explicitly configured to maildir root). */
base = mailbox_list_get_path(list, NULL,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (strcmp(base, src) == 0) {
mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE,
"INBOX can't be deleted.");
return -1;
}
}
dest = maildir_get_unlink_dest(list, name);
if (dest == NULL) {
/* delete the directory directly without any renaming */
ret = maildir_delete_nonrecursive(list, src, name);
} else {
ret = maildir_delete_with_trash(list, src, dest, name);
}
if (ret == 0) {
mailbox_list_add_change(list, MAILBOX_LOG_RECORD_DELETE_MAILBOX,
mailbox_guid);
mailbox_name_get_sha128(name, dir_sha128);
mailbox_list_add_change(list, MAILBOX_LOG_RECORD_DELETE_DIR,
dir_sha128);
}
return 0;
}
static int
maildir_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
struct mailbox_list *newlist, const char *newname,
bool rename_children)
{
struct maildir_mailbox_list *oldmlist = MAILDIR_LIST_CONTEXT(oldlist);
const char *path1, *path2;
if (strcmp(oldname, "INBOX") == 0) {
/* INBOX often exists as the root ~/Maildir.
We can't rename it then. */
path1 = mailbox_list_get_path(oldlist, oldname,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
path2 = mailbox_list_get_path(oldlist, NULL,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (strcmp(path1, path2) == 0) {
mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
"Renaming INBOX isn't supported.");
return -1;
}
}
return oldmlist->module_ctx.super.
rename_mailbox(oldlist, oldname, newlist, newname,
rename_children);
}
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;
if (box->notify_callback == NULL)
index_mailbox_check_remove_all(&mbox->ibox);
else {
index_mailbox_check_add(&mbox->ibox,
t_strconcat(mbox->ibox.box.path, "/new", NULL));
index_mailbox_check_add(&mbox->ibox,
t_strconcat(mbox->ibox.box.path, "/cur", NULL));
}
}
static int
maildir_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx
ATTR_UNUSED,
const char *dir, const char *fname,
const char *mailbox_name ATTR_UNUSED,
enum mailbox_list_file_type type,
enum mailbox_info_flags *flags)
{
struct stat st, st2;
const char *path, *cur_path;
int ret;
if (maildir_is_internal_name(fname)) {
*flags |= MAILBOX_NONEXISTENT;
return 0;
}
switch (type) {
case MAILBOX_LIST_FILE_TYPE_FILE:
case MAILBOX_LIST_FILE_TYPE_OTHER:
/* non-directories are not */
*flags |= MAILBOX_NOSELECT;
return 0;
case MAILBOX_LIST_FILE_TYPE_DIR:
case MAILBOX_LIST_FILE_TYPE_UNKNOWN:
case MAILBOX_LIST_FILE_TYPE_SYMLINK:
break;
}
path = t_strdup_printf("%s/%s", dir, fname);
if (stat(path, &st) == 0) {
if (!S_ISDIR(st.st_mode)) {
if (strncmp(fname, ".nfs", 4) == 0) {
/* temporary NFS file */
*flags |= MAILBOX_NONEXISTENT;
} else {
*flags |= MAILBOX_NOSELECT |
MAILBOX_NOINFERIORS;
}
return 0;
}
ret = 1;
} else if (errno == ENOENT) {
/* doesn't exist - probably a non-existing subscribed mailbox */
*flags |= MAILBOX_NONEXISTENT;
ret = 0;
} else {
/* non-selectable. probably either access denied, or symlink
destination not found. don't bother logging errors. */
*flags |= MAILBOX_NOSELECT;
ret = 1;
}
if ((*flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) == 0) {
/* make sure it's a selectable mailbox */
cur_path = t_strconcat(path, "/cur", NULL);
if (stat(cur_path, &st2) < 0 || !S_ISDIR(st2.st_mode))
*flags |= MAILBOX_NOSELECT;
if (*ctx->list->set.maildir_name == '\0') {
/* now we can figure out based on the link count if we
have child mailboxes or not. for a selectable
mailbox we have 3 more links (cur/, new/ and tmp/)
than non-selectable. */
if ((*flags & MAILBOX_NOSELECT) == 0) {
if (st.st_nlink > 5)
*flags |= MAILBOX_CHILDREN;
else
*flags |= MAILBOX_NOCHILDREN;
} else {
if (st.st_nlink > 2)
*flags |= MAILBOX_CHILDREN;
else
*flags |= MAILBOX_NOCHILDREN;
}
} else {
/* link count 3 may mean either a selectable mailbox
or a non-selectable mailbox with 1 child. */
if (st.st_nlink > 3)
*flags |= MAILBOX_CHILDREN;
else if (st.st_nlink == 3) {
if ((*flags & MAILBOX_NOSELECT) != 0)
*flags |= MAILBOX_CHILDREN;
else
*flags |= MAILBOX_NOCHILDREN;
}
}
}
return ret;
}
static int
maildirplusplus_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
const char *dir, const char *fname,
const char *mailbox_name ATTR_UNUSED,
enum mailbox_list_file_type type,
enum mailbox_info_flags *flags)
{
struct maildir_mailbox_list *mlist = MAILDIR_LIST_CONTEXT(ctx->list);
int ret;
if (fname[1] == mailbox_list_get_hierarchy_sep(ctx->list) &&
strcmp(fname+2, MAILDIR_UNLINK_DIRNAME) == 0) {
const char *path;
struct stat st;
/* this directory is in the middle of being deleted,
or the process trying to delete it had died.
delete it ourself if it's been there longer than
one hour. */
path = t_strdup_printf("%s/%s", dir, fname);
if (stat(path, &st) == 0 &&
st.st_mtime < ioloop_time - 3600)
(void)unlink_directory(path, TRUE);
*flags |= MAILBOX_NONEXISTENT;
return 0;
}
switch (type) {
case MAILBOX_LIST_FILE_TYPE_DIR:
/* all directories are valid maildirs */
return 1;
case MAILBOX_LIST_FILE_TYPE_FILE:
case MAILBOX_LIST_FILE_TYPE_OTHER:
/* non-directories are not */
*flags |= MAILBOX_NOSELECT;
return 0;
case MAILBOX_LIST_FILE_TYPE_UNKNOWN:
case MAILBOX_LIST_FILE_TYPE_SYMLINK:
/* need to check with stat() to be sure */
break;
}
/* Check files beginning with .nfs always because they may be
temporary files created by the kernel */
if (mlist->set->maildir_stat_dirs || *fname == '\0' ||
strncmp(fname, ".nfs", 4) == 0) {
const char *path;
struct stat st;
/* if fname="", we're checking if a base maildir has INBOX */
path = *fname == '\0' ? t_strdup_printf("%s/cur", dir) :
t_strdup_printf("%s/%s", dir, fname);
if (stat(path, &st) == 0) {
if (S_ISDIR(st.st_mode))
ret = 1;
else {
if (strncmp(fname, ".nfs", 4) == 0)
*flags |= MAILBOX_NONEXISTENT;
else
*flags |= MAILBOX_NOSELECT;
ret = 0;
}
} else if (errno == ENOENT) {
/* just deleted? */
*flags |= MAILBOX_NONEXISTENT;
ret = 0;
} else {
*flags |= MAILBOX_NOSELECT;
ret = 0;
}
} else {
ret = 1;
}
return ret;
}
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 void maildir_storage_add_list(struct mail_storage *storage,
struct mailbox_list *list)
{
struct maildir_mailbox_list *mlist;
mlist = p_new(list->pool, struct maildir_mailbox_list, 1);
mlist->module_ctx.super = list->v;
mlist->set = mail_storage_get_driver_settings(storage);
if (strcmp(list->name, MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) == 0) {
list->v.iter_is_mailbox = maildirplusplus_iter_is_mailbox;
} else {
list->v.is_valid_existing_name =
maildir_storage_is_valid_existing_name;
list->v.is_valid_create_name =
maildir_storage_is_valid_create_name;
list->v.iter_is_mailbox = maildir_list_iter_is_mailbox;
}
list->v.delete_mailbox = maildir_list_delete_mailbox;
list->v.rename_mailbox = maildir_list_rename_mailbox;
MODULE_CONTEXT_SET(list, maildir_mailbox_list_module, mlist);
}
struct mail_storage maildir_storage = {
.name = MAILDIR_STORAGE_NAME,
.class_flags = 0,
.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 = {
index_storage_is_readonly,
index_storage_allow_new_keywords,
index_storage_mailbox_enable,
maildir_mailbox_open,
maildir_mailbox_close,
maildir_mailbox_create,
maildir_mailbox_update,
maildir_storage_get_status,
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,
index_transaction_set_max_modseq,
index_keywords_create,
index_keywords_create_from_indexes,
index_keywords_ref,
index_keywords_unref,
index_keyword_is_valid,
index_storage_get_seq_range,
index_storage_get_uid_range,
index_storage_get_expunges,
NULL,
NULL,
NULL,
index_mail_alloc,
index_header_lookup_init,
index_header_lookup_deinit,
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,
index_storage_is_inconsistent
}
};