maildir-storage.c revision 8754bb7a1f24705ffa5434f9e10d57e0b3b88d6e
a8c5a86d183db25a57bf193c06b41e092ec2e151Timo Sirainen/* Copyright (C) 2002-2003 Timo Sirainen */
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include "lib.h"
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include "hostpid.h"
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include "home-expand.h"
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include "mkdir-parents.h"
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include "unlink-directory.h"
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include "subscription-file/subscription-file.h"
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include "maildir-storage.h"
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include "maildir-uidlist.h"
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include "index-mail.h"
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#include <stdio.h>
e34d170f8f0e084bd94bfbc1a7085ece67e508dfTimo Sirainen#include <stdlib.h>
e34d170f8f0e084bd94bfbc1a7085ece67e508dfTimo Sirainen#include <unistd.h>
e34d170f8f0e084bd94bfbc1a7085ece67e508dfTimo Sirainen#include <sys/stat.h>
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen#define CREATE_MODE 0770 /* umask() should limit it more */
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainenstruct rename_context {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen int found;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen size_t oldnamelen;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen const char *newname;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen};
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainenextern struct mail_storage maildir_storage;
ad48319996942463675b53877092ab7e13a7a75aTimo Sirainenextern struct mailbox maildir_mailbox;
ad48319996942463675b53877092ab7e13a7a75aTimo Sirainen
ad48319996942463675b53877092ab7e13a7a75aTimo Sirainenstatic const char *maildirs[] = { "cur", "new", "tmp", NULL };
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
ad48319996942463675b53877092ab7e13a7a75aTimo Sirainenstatic int verify_inbox(struct maildir_storage *storage);
ad48319996942463675b53877092ab7e13a7a75aTimo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainenstatic struct mail_storage *
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainenmaildir_create(const char *data, const char *user,
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen enum mail_storage_flags flags,
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen enum mail_storage_lock_method lock_method)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen{
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen int debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen struct maildir_storage *storage;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen struct index_storage *istorage;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen const char *root_dir, *inbox_dir, *index_dir, *control_dir;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen const char *home, *path, *p;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen size_t len;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen pool_t pool;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen inbox_dir = root_dir = index_dir = control_dir = NULL;
650441dd8282f8fa6ae58d231c0c34488c3bdb49Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (data == NULL || *data == '\0') {
e169102fb38ce788b76c2a344bee7d77079dea05Timo Sirainen /* we'll need to figure out the maildir location ourself.
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen It's $HOME/Maildir unless we are chrooted. */
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if ((home = getenv("HOME")) != NULL) {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen path = t_strconcat(home, "/Maildir", NULL);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (access(path, R_OK|W_OK|X_OK) == 0) {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (debug) {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen i_info("maildir: root exists (%s)",
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen path);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen }
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen root_dir = path;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen } else {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (debug) {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen i_info("maildir: access(%s, rwx): "
650441dd8282f8fa6ae58d231c0c34488c3bdb49Timo Sirainen "failed: %m", path);
650441dd8282f8fa6ae58d231c0c34488c3bdb49Timo Sirainen }
650441dd8282f8fa6ae58d231c0c34488c3bdb49Timo Sirainen }
650441dd8282f8fa6ae58d231c0c34488c3bdb49Timo Sirainen } else {
650441dd8282f8fa6ae58d231c0c34488c3bdb49Timo Sirainen if (debug)
650441dd8282f8fa6ae58d231c0c34488c3bdb49Timo Sirainen i_info("maildir: HOME not set");
650441dd8282f8fa6ae58d231c0c34488c3bdb49Timo Sirainen }
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (access("/cur", R_OK|W_OK|X_OK) == 0) {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (debug)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen i_info("maildir: /cur exists, assuming chroot");
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen root_dir = "/";
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen }
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen } else {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen /* <Maildir> [:INBOX=<dir>] [:INDEX=<dir>] [:CONTROL=<dir>] */
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (debug)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen i_info("maildir: data=%s", data);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen p = strchr(data, ':');
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (p == NULL)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen root_dir = data;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen else {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen root_dir = t_strdup_until(data, p);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen do {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen p++;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (strncmp(p, "INBOX=", 6) == 0)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen inbox_dir = t_strcut(p+6, ':');
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen else if (strncmp(p, "INDEX=", 6) == 0)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen index_dir = t_strcut(p+6, ':');
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen else if (strncmp(p, "CONTROL=", 8) == 0)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen control_dir = t_strcut(p+8, ':');
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen p = strchr(p, ':');
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen } while (p != NULL);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen }
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen }
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (root_dir == NULL) {
e169102fb38ce788b76c2a344bee7d77079dea05Timo Sirainen if (debug)
082e82792b8ac33ad42beac510441b37a3c50737Timo Sirainen i_info("maildir: couldn't find root dir");
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen return NULL;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen }
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen /* strip trailing '/' */
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen len = strlen(root_dir);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (root_dir[len-1] == '/')
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen root_dir = t_strndup(root_dir, len-1);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (index_dir == NULL)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen index_dir = root_dir;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen else if (strcmp(index_dir, "MEMORY") == 0)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen index_dir = NULL;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (debug) {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen i_info("maildir: root=%s, index=%s, control=%s, inbox=%s",
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen root_dir, index_dir == NULL ? "" : index_dir,
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen control_dir == NULL ? "" : control_dir,
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen inbox_dir == NULL ? "" : inbox_dir);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen }
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen pool = pool_alloconly_create("storage", 512);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen storage = p_new(pool, struct maildir_storage, 1);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen storage->control_dir = p_strdup(pool, home_expand(control_dir));
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen istorage = INDEX_STORAGE(storage);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen istorage->storage = maildir_storage;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen istorage->storage.pool = pool;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen /* the default ".temp.xxx" prefix would be treated as directory */
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen istorage->temp_prefix =
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen p_strconcat(pool, "temp.", my_hostname, ".", my_pid, ".", NULL);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen istorage->dir = p_strdup(pool, home_expand(root_dir));
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen istorage->inbox_path = p_strdup(pool, home_expand(inbox_dir));
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen istorage->index_dir = p_strdup(pool, home_expand(index_dir));
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen istorage->user = p_strdup(pool, user);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen istorage->callbacks = p_new(pool, struct mail_storage_callbacks, 1);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen index_storage_init(istorage, flags, lock_method);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen (void)verify_inbox(storage);
e169102fb38ce788b76c2a344bee7d77079dea05Timo Sirainen return STORAGE(storage);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen}
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainenstatic void maildir_free(struct mail_storage *_storage)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen{
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen struct index_storage *storage = (struct index_storage *) _storage;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen index_storage_deinit(storage);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen pool_unref(storage->storage.pool);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen}
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainenstatic int maildir_autodetect(const char *data, enum mail_storage_flags flags)
e169102fb38ce788b76c2a344bee7d77079dea05Timo Sirainen{
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen int debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen struct stat st;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen const char *path;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen data = t_strcut(data, ':');
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
8fcf84e6b7a55049a6d407b17af50d6ae8c6386fPhil Carmody path = t_strconcat(data, "/cur", NULL);
8fcf84e6b7a55049a6d407b17af50d6ae8c6386fPhil Carmody if (stat(path, &st) < 0) {
8fcf84e6b7a55049a6d407b17af50d6ae8c6386fPhil Carmody if (debug)
8fcf84e6b7a55049a6d407b17af50d6ae8c6386fPhil Carmody i_info("maildir autodetect: stat(%s) failed: %m", path);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen return FALSE;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen }
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (!S_ISDIR(st.st_mode)) {
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen if (debug)
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen i_info("maildir autodetect: %s not a directory", path);
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen return FALSE;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen }
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen return TRUE;
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen}
5550482ce58e51584f83c10a4c63d35b66431742Timo Sirainen
static int maildir_is_valid_create_name(struct mail_storage *storage,
const char *name)
{
size_t len;
len = strlen(name);
if (len == 0 || name[0] == MAILDIR_FS_SEP ||
name[len-1] == MAILDIR_FS_SEP ||
strchr(name, '*') != NULL || strchr(name, '%') != NULL)
return FALSE;
if ((storage->flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0)
return TRUE;
if (*name == '~' || strchr(name, '/') != NULL)
return FALSE;
return TRUE;
}
static int maildir_is_valid_existing_name(struct mail_storage *storage,
const char *name)
{
if (name[0] == '\0' || name[strlen(name)-1] == '/')
return FALSE;
if ((storage->flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0)
return TRUE;
if (*name == '~' || strchr(name, '/') != NULL)
return FALSE;
return TRUE;
}
static const char *maildir_get_absolute_path(const char *name, int unlink)
{
const char *p;
name = home_expand(name);
p = strrchr(name, '/');
if (p == NULL)
return name;
return t_strconcat(t_strdup_until(name, p+1),
unlink ? MAILDIR_FS_SEP_S MAILDIR_FS_SEP_S :
MAILDIR_FS_SEP_S, p+1, NULL);
}
const char *maildir_get_path(struct index_storage *storage, const char *name)
{
if ((storage->storage.flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0 &&
(*name == '/' || *name == '~'))
return maildir_get_absolute_path(name, FALSE);
if (strcmp(name, "INBOX") == 0) {
return storage->inbox_path != NULL ?
storage->inbox_path : storage->dir;
}
return t_strconcat(storage->dir, "/"MAILDIR_FS_SEP_S, name, NULL);
}
static const char *
maildir_get_unlink_path(struct index_storage *storage, const char *name)
{
if ((storage->storage.flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0 &&
(*name == '/' || *name == '~'))
return maildir_get_absolute_path(name, TRUE);
return maildir_get_path(storage,
t_strconcat(MAILDIR_FS_SEP_S, name, NULL));
}
static const char *maildir_get_index_path(struct index_storage *storage,
const char *name)
{
if (storage->index_dir == NULL)
return NULL;
if (strcmp(name, "INBOX") == 0 &&
strcmp(storage->index_dir, storage->dir) == 0)
return storage->dir;
if ((storage->storage.flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0 &&
(*name == '/' || *name == '~'))
return maildir_get_absolute_path(name, FALSE);
return t_strconcat(storage->index_dir, "/"MAILDIR_FS_SEP_S, name, NULL);
}
static const char *maildir_get_control_path(struct maildir_storage *storage,
const char *name)
{
const char *default_path, *path, *default_uidlist;
struct stat st;
default_path = maildir_get_path(INDEX_STORAGE(storage), name);
if (storage->control_dir == NULL)
return default_path;
if ((STORAGE(storage)->flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0 &&
(*name == '/' || *name == '~'))
path = maildir_get_absolute_path(name, FALSE);
else {
path = t_strconcat(storage->control_dir, "/"MAILDIR_FS_SEP_S,
name, NULL);
}
default_uidlist =
t_strconcat(default_path, "/" MAILDIR_UIDLIST_NAME, NULL);
if (stat(default_uidlist, &st) == 0) {
const char *dest_uidlist =
t_strconcat(path, "/" MAILDIR_UIDLIST_NAME, NULL);
if (rename(default_uidlist, dest_uidlist) < 0) {
i_error("rename(%s, %s) failed: %m",
default_uidlist, dest_uidlist);
}
}
return path;
}
static int mkdir_verify(struct index_storage *storage,
const char *dir, int verify)
{
struct stat st;
if (verify) {
if (lstat(dir, &st) == 0)
return 0;
if (errno != ENOENT) {
mail_storage_set_critical(&storage->storage,
"lstat(%s) failed: %m", dir);
return -1;
}
}
if (mkdir_parents(dir, CREATE_MODE) < 0 &&
(errno != EEXIST || !verify)) {
if (errno != EEXIST && (!verify || errno != ENOENT)) {
mail_storage_set_critical(&storage->storage,
"mkdir(%s) failed: %m", dir);
}
return -1;
}
return 0;
}
/* create or fix maildir, ignore if it already exists */
static int create_maildir(struct index_storage *storage,
const char *dir, int verify)
{
const char **tmp, *path;
if (!verify && mkdir_verify(storage, dir, verify) < 0)
return -1;
for (tmp = maildirs; *tmp != NULL; tmp++) {
path = t_strconcat(dir, "/", *tmp, NULL);
if (mkdir_verify(storage, path, verify) < 0) {
if (!verify || errno != ENOENT)
return -1;
/* small optimization. if we're verifying, we don't
check that the root dir actually exists unless we
fail here. */
if (mkdir_verify(storage, dir, verify) < 0)
return -1;
if (mkdir_verify(storage, path, verify) < 0)
return -1;
}
}
return 0;
}
static int create_index_dir(struct index_storage *storage, const char *name)
{
const char *dir;
if (storage->index_dir == NULL)
return 0;
if (strcmp(storage->index_dir, storage->dir) == 0 ||
(strcmp(name, "INBOX") == 0 && storage->inbox_path != NULL &&
strcmp(storage->index_dir, storage->inbox_path) == 0))
return 0;
dir = t_strconcat(storage->index_dir, "/"MAILDIR_FS_SEP_S, name, NULL);
if (mkdir_parents(dir, CREATE_MODE) < 0 && errno != EEXIST) {
mail_storage_set_critical(&storage->storage,
"mkdir(%s) failed: %m", dir);
return -1;
}
return 0;
}
static int create_control_dir(struct maildir_storage *storage, const char *name)
{
const char *dir;
if (storage->control_dir == NULL)
return 0;
dir = t_strconcat(storage->control_dir, "/"MAILDIR_FS_SEP_S,
name, NULL);
if (mkdir_parents(dir, CREATE_MODE) < 0 && errno != EEXIST) {
mail_storage_set_critical(STORAGE(storage),
"mkdir(%s) failed: %m", dir);
return -1;
}
return 0;
}
static int verify_inbox(struct maildir_storage *storage)
{
struct index_storage *istorage = INDEX_STORAGE(storage);
const char *path;
path = istorage->inbox_path != NULL ?
istorage->inbox_path : istorage->dir;
if (create_maildir(istorage, path, TRUE) < 0)
return -1;
/* make sure the index directories exist */
if (create_index_dir(istorage, "INBOX") < 0)
return -1;
if (create_control_dir(storage, "INBOX") < 0)
return -1;
return 0;
}
static int maildir_is_recent(struct index_mailbox *ibox, uint32_t uid)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)ibox;
return maildir_uidlist_is_recent(mbox->uidlist, uid);
}
static struct mailbox *
maildir_open(struct maildir_storage *storage, const char *name,
enum mailbox_open_flags flags)
{
struct index_storage *istorage = INDEX_STORAGE(storage);
struct maildir_mailbox *mbox;
struct mail_index *index;
const char *path, *index_dir, *control_dir;
struct stat st;
int shared;
pool_t pool;
path = maildir_get_path(istorage, name);
index_dir = maildir_get_index_path(istorage, name);
control_dir = maildir_get_control_path(storage, name);
index = index_storage_alloc(index_dir, path, MAILDIR_INDEX_PREFIX);
/* for shared mailboxes get the create mode from the
permissions of dovecot-shared file. */
shared = stat(t_strconcat(path, "/dovecot-shared", NULL), &st) == 0;
if (shared)
mail_index_set_permissions(index, st.st_mode & 0666, st.st_gid);
pool = pool_alloconly_create("mailbox", 1024);
mbox = p_new(pool, struct maildir_mailbox, 1);
mbox->ibox.box = maildir_mailbox;
mbox->ibox.box.pool = pool;
mbox->ibox.storage = istorage;
mbox->ibox.mail_vfuncs = &maildir_mail_vfuncs;
mbox->ibox.is_recent = maildir_is_recent;
if (index_storage_mailbox_init(&mbox->ibox, index, name, flags) < 0) {
/* the memory was already freed */
return NULL;
}
mbox->storage = storage;
mbox->path = p_strdup(pool, path);
mbox->control_dir = p_strdup(pool, control_dir);
mbox->uidlist = maildir_uidlist_init(mbox);
if (!shared)
mbox->mail_create_mode = 0600;
else {
mbox->mail_create_mode = st.st_mode & 0666;
mbox->private_flags_mask = MAIL_SEEN;
}
return &mbox->ibox.box;
}
static struct mailbox *
maildir_mailbox_open(struct mail_storage *_storage, const char *name,
struct istream *input, enum mailbox_open_flags flags)
{
struct maildir_storage *storage = (struct maildir_storage *)_storage;
struct index_storage *istorage = INDEX_STORAGE(storage);
const char *path;
struct stat st;
mail_storage_clear_error(_storage);
if (input != NULL) {
mail_storage_set_critical(_storage,
"Maildir doesn't support streamed mailboxes");
return NULL;
}
if (strcmp(name, "INBOX") == 0) {
if (verify_inbox(storage) < 0)
return NULL;
return maildir_open(storage, "INBOX", flags);
}
if (!maildir_is_valid_existing_name(_storage, name)) {
mail_storage_set_error(_storage, "Invalid mailbox name");
return NULL;
}
path = maildir_get_path(istorage, name);
if (stat(path, &st) == 0) {
/* exists - make sure the required directories are also there */
if (create_maildir(istorage, path, TRUE) < 0 ||
create_index_dir(istorage, name) < 0 ||
create_control_dir(storage, name) < 0)
return NULL;
return maildir_open(storage, name, flags);
} else if (errno == ENOENT) {
mail_storage_set_error(_storage, "Mailbox doesn't exist: %s",
name);
return NULL;
} else {
mail_storage_set_critical(_storage, "stat(%s) failed: %m",
path);
return NULL;
}
}
static int maildir_create_shared(struct mail_storage *storage,
const char *path, mode_t mode, gid_t gid)
{
mode_t old_mask = umask(0);
int fd;
fd = open(path, O_WRONLY | O_CREAT, mode);
umask(old_mask);
if (fd == -1) {
mail_storage_set_critical(storage,
"open(%s) failed: %m", path);
return -1;
}
if (fchown(fd, (uid_t)-1, gid) < 0) {
mail_storage_set_critical(storage,
"fchown(%s) failed: %m", path);
}
(void)close(fd);
return 0;
}
static int maildir_mailbox_create(struct mail_storage *_storage,
const char *name,
int directory __attr_unused__)
{
struct index_storage *storage = (struct index_storage *)_storage;
struct stat st;
const char *path, *shared_path;
mail_storage_clear_error(_storage);
if (!maildir_is_valid_create_name(_storage, name)) {
mail_storage_set_error(_storage, "Invalid mailbox name");
return -1;
}
path = maildir_get_path(storage, name);
if (create_maildir(storage, path, FALSE) < 0) {
if (errno == EEXIST) {
mail_storage_set_error(_storage,
"Mailbox already exists");
}
return -1;
}
/* if dovecot-shared exists in the root dir, copy it to the
created mailbox */
shared_path = t_strconcat(storage->dir, "/dovecot-shared", NULL);
if (stat(shared_path, &st) == 0) {
path = t_strconcat(path, "/dovecot-shared", NULL);
(void)maildir_create_shared(_storage, path,
st.st_mode & 0666, st.st_gid);
}
return 0;
}
static int maildir_mailbox_delete(struct mail_storage *_storage,
const char *name)
{
struct index_storage *storage = (struct index_storage *)_storage;
struct stat st;
const char *src, *dest, *index_dir;
int count;
mail_storage_clear_error(_storage);
if (strcmp(name, "INBOX") == 0) {
mail_storage_set_error(_storage, "INBOX can't be deleted.");
return -1;
}
if (!maildir_is_valid_existing_name(_storage, name)) {
mail_storage_set_error(_storage, "Invalid mailbox name");
return -1;
}
/* rename the .maildir into ..maildir which marks it as being
deleted. delete indexes before the actual maildir. this way we
never see partially deleted mailboxes. */
src = maildir_get_path(storage, name);
dest = maildir_get_unlink_path(storage, name);
if (stat(src, &st) != 0 && errno == ENOENT) {
mail_storage_set_error(_storage, "Mailbox doesn't exist: %s",
name);
return -1;
}
if (storage->index_dir != NULL && *name != '/' && *name != '~' &&
strcmp(storage->index_dir, storage->dir) != 0) {
index_dir = t_strconcat(storage->index_dir,
"/"MAILDIR_FS_SEP_S, name, NULL);
index_storage_destroy_unrefed();
/* it can fail with some NFS implementations if indexes are
opened by another session.. can't really help it. */
if (unlink_directory(index_dir, TRUE) < 0 &&
errno != ENOTEMPTY) {
mail_storage_set_critical(_storage,
"unlink_directory(%s) failed: %m", index_dir);
return -1;
}
}
count = 0;
while (rename(src, dest) < 0 && count < 2) {
if (errno != EEXIST && errno != ENOTEMPTY) {
mail_storage_set_critical(_storage,
"rename(%s, %s) failed: %m", src, dest);
return -1;
}
/* ..dir already existed? delete it and try again */
if (unlink_directory(dest, TRUE) < 0) {
mail_storage_set_critical(_storage,
"unlink_directory(%s) failed: %m", dest);
return -1;
}
count++;
}
if (unlink_directory(dest, TRUE) < 0 && errno != ENOTEMPTY) {
mail_storage_set_critical(_storage,
"unlink_directory(%s) failed: %m", dest);
/* it's already renamed to ..dir, which means it's deleted
as far as client is concerned. Report success. */
}
return 0;
}
static int rename_indexes(struct index_storage *storage,
const char *oldname, const char *newname)
{
const char *oldpath, *newpath;
if (storage->index_dir == NULL ||
strcmp(storage->index_dir, storage->dir) == 0)
return 0;
/* Rename it's index. */
oldpath = t_strconcat(storage->index_dir, "/"MAILDIR_FS_SEP_S,
oldname, NULL);
newpath = t_strconcat(storage->index_dir, "/"MAILDIR_FS_SEP_S,
newname, NULL);
if (rename(oldpath, newpath) < 0 && errno != ENOENT) {
mail_storage_set_critical(&storage->storage,
"rename(%s, %s) failed: %m",
oldpath, newpath);
return -1;
}
return 0;
}
static int rename_subfolders(struct index_storage *storage,
const char *oldname, const char *newname)
{
struct mailbox_list_context *ctx;
struct mailbox_list *list;
const char *oldpath, *newpath, *new_listname;
size_t oldnamelen;
int ret;
ret = 0;
oldnamelen = strlen(oldname);
ctx = maildir_mailbox_list_init(&storage->storage, oldname, "*",
MAILBOX_LIST_FAST_FLAGS);
while ((list = maildir_mailbox_list_next(ctx)) != NULL) {
t_push();
i_assert(oldnamelen <= strlen(list->name));
new_listname = t_strconcat(newname,
list->name + oldnamelen, NULL);
oldpath = maildir_get_path(storage, list->name);
newpath = maildir_get_path(storage, new_listname);
/* FIXME: it's possible to merge two folders if either one of
them doesn't have existing root folder. We could check this
but I'm not sure if it's worth it. It could be even
considered as a feature.
Anyway, the bug with merging is that if both folders have
identically named subfolder they conflict. Just ignore those
and leave them under the old folder. */
if (rename(oldpath, newpath) == 0 ||
errno == EEXIST || errno == ENOTEMPTY)
ret = 1;
else {
mail_storage_set_critical(&storage->storage,
"rename(%s, %s) failed: %m",
oldpath, newpath);
ret = -1;
t_pop();
break;
}
(void)rename_indexes(storage, list->name, new_listname);
t_pop();
}
if (maildir_mailbox_list_deinit(ctx) < 0)
return -1;
return ret;
}
static int maildir_mailbox_rename(struct mail_storage *_storage,
const char *oldname, const char *newname)
{
struct index_storage *storage = (struct index_storage *)_storage;
const char *oldpath, *newpath;
int ret, found;
mail_storage_clear_error(_storage);
if (!maildir_is_valid_existing_name(_storage, oldname) ||
!maildir_is_valid_create_name(_storage, newname)) {
mail_storage_set_error(_storage, "Invalid mailbox name");
return -1;
}
if (strcmp(oldname, "INBOX") == 0) {
mail_storage_set_error(_storage,
"Renaming INBOX isn't supported.");
return -1;
}
/* NOTE: it's possible to rename a nonexisting folder which has
subfolders. In that case we should ignore the rename() error. */
oldpath = maildir_get_path(storage, oldname);
newpath = maildir_get_path(storage, newname);
ret = rename(oldpath, newpath);
if (ret == 0 || errno == ENOENT) {
(void)rename_indexes(storage, oldname, newname);
found = ret == 0;
ret = rename_subfolders(storage, oldname, newname);
if (ret < 0)
return -1;
if (!found && ret == 0) {
mail_storage_set_error(_storage,
"Mailbox doesn't exist");
return -1;
}
return 0;
}
if (errno == EEXIST) {
mail_storage_set_error(_storage,
"Target mailbox already exists");
return -1;
} else {
mail_storage_set_critical(_storage, "rename(%s, %s) failed: %m",
oldpath, newpath);
return -1;
}
}
static int maildir_set_subscribed(struct mail_storage *_storage,
const char *name, int set)
{
struct maildir_storage *storage = (struct maildir_storage *)_storage;
const char *path;
path = t_strconcat(storage->control_dir != NULL ?
storage->control_dir : INDEX_STORAGE(storage)->dir,
"/" SUBSCRIPTION_FILE_NAME, NULL);
return subsfile_set_subscribed(_storage, path,
INDEX_STORAGE(storage)->temp_prefix,
name, set);
}
static int maildir_get_mailbox_name_status(struct mail_storage *_storage,
const char *name,
enum mailbox_name_status *status)
{
struct index_storage *storage = (struct index_storage *)_storage;
struct stat st;
const char *path;
mail_storage_clear_error(_storage);
if (!maildir_is_valid_existing_name(_storage, name)) {
*status = MAILBOX_NAME_INVALID;
return 0;
}
path = maildir_get_path(storage, name);
if (strcmp(name, "INBOX") == 0 || stat(path, &st) == 0) {
*status = MAILBOX_NAME_EXISTS;
return 0;
}
if (!maildir_is_valid_create_name(_storage, name)) {
*status = MAILBOX_NAME_INVALID;
return 0;
}
if (errno == ENOENT) {
*status = MAILBOX_NAME_VALID;
return 0;
} else {
mail_storage_set_critical(_storage, "stat(%s) failed: %m",
path);
return -1;
}
}
static int maildir_storage_close(struct mailbox *box)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
int ret = 0;
/*FIXME:if (!maildir_try_flush_dirty_flags(ibox->index, TRUE)) {
mail_storage_set_index_error(ibox);
ret = -1;
}*/
maildir_uidlist_deinit(mbox->uidlist);
index_storage_mailbox_free(box);
return ret;
}
static void
maildir_notify_changes(struct mailbox *box, unsigned int min_interval,
mailbox_notify_callback_t *callback, void *context)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
struct index_storage *istorage = INDEX_STORAGE(mbox->storage);
mbox->ibox.min_notify_interval = min_interval;
mbox->ibox.notify_callback = callback;
mbox->ibox.notify_context = context;
if (callback == NULL) {
index_mailbox_check_remove_all(&mbox->ibox);
return;
}
index_mailbox_check_add(&mbox->ibox,
t_strconcat(istorage->dir, "/new", NULL), TRUE);
index_mailbox_check_add(&mbox->ibox,
t_strconcat(istorage->dir, "/cur", NULL), TRUE);
}
struct mail_storage maildir_storage = {
MEMBER(name) "maildir",
MEMBER(hierarchy_sep) '.',
{
maildir_create,
maildir_free,
maildir_autodetect,
index_storage_set_callbacks,
maildir_mailbox_open,
maildir_mailbox_create,
maildir_mailbox_delete,
maildir_mailbox_rename,
maildir_mailbox_list_init,
maildir_mailbox_list_next,
maildir_mailbox_list_deinit,
maildir_set_subscribed,
maildir_get_mailbox_name_status,
index_storage_get_last_error
}
};
struct mailbox maildir_mailbox = {
MEMBER(name) NULL,
MEMBER(storage) NULL,
{
index_storage_is_readonly,
index_storage_allow_new_keywords,
maildir_storage_close,
index_storage_get_status,
maildir_storage_sync_init,
index_mailbox_sync_next,
index_mailbox_sync_deinit,
maildir_notify_changes,
maildir_transaction_begin,
maildir_transaction_commit,
maildir_transaction_rollback,
index_keywords_create,
index_keywords_free,
index_storage_get_uids,
index_mail_alloc,
index_header_lookup_init,
index_header_lookup_deinit,
index_storage_search_get_sorting,
index_storage_search_init,
index_storage_search_deinit,
index_storage_search_next,
maildir_save_init,
maildir_save_continue,
maildir_save_finish,
maildir_save_cancel,
maildir_copy,
index_storage_is_inconsistent
}
};