mbox-storage.c revision f81f4bc282cd1944cec187bae89c0701a416ed2a
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen/* Copyright (C) 2002-2003 Timo Sirainen */
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "lib.h"
70cb37c37e4dce8f57cd3f882f7444e76b918befTimo Sirainen#include "ioloop.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "array.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "istream.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "mkdir-parents.h"
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen#include "unlink-directory.h"
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen#include "home-expand.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "mbox-storage.h"
ba3a54872528db0eae3f36e45592219965b9faf8Timo Sirainen#include "mbox-lock.h"
ba3a54872528db0eae3f36e45592219965b9faf8Timo Sirainen#include "mbox-file.h"
ba3a54872528db0eae3f36e45592219965b9faf8Timo Sirainen#include "mbox-sync-private.h"
ba3a54872528db0eae3f36e45592219965b9faf8Timo Sirainen#include "mail-copy.h"
ba3a54872528db0eae3f36e45592219965b9faf8Timo Sirainen#include "index-mail.h"
ba3a54872528db0eae3f36e45592219965b9faf8Timo Sirainen
ba3a54872528db0eae3f36e45592219965b9faf8Timo Sirainen#include <stdio.h>
ba3a54872528db0eae3f36e45592219965b9faf8Timo Sirainen#include <stdlib.h>
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen#include <unistd.h>
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen#include <fcntl.h>
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen#include <sys/stat.h>
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen#define CREATE_MODE 0770 /* umask() should limit it more */
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen/* How often to touch the dotlock file when using KEEP_LOCKED flag */
0a00890f8ec34ee08d0d391441fca36ed42d7a0cTimo Sirainen#define MBOX_LOCK_TOUCH_MSECS (10*1000)
0a00890f8ec34ee08d0d391441fca36ed42d7a0cTimo Sirainen
0a00890f8ec34ee08d0d391441fca36ed42d7a0cTimo Sirainen/* Assume that if atime < mtime, there are new mails. If it's good enough for
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen UW-IMAP, it's good enough for us. */
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen#define STAT_GET_MARKED(st) \
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen ((st).st_size == 0 ? MAILBOX_UNMARKED : \
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen (st).st_atime < (st).st_mtime ? MAILBOX_MARKED : MAILBOX_UNMARKED)
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen#define MBOX_LIST_CONTEXT(obj) \
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen MODULE_CONTEXT(obj, mbox_mailbox_list_module)
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen/* NOTE: must be sorted for istream-header-filter. Note that it's not such
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen a good idea to change this list, as the messages will then change from
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen client's point of view. So if you do it, change all mailboxes' UIDVALIDITY
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen so all caches are reset. */
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainenconst char *mbox_hide_headers[] = {
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen "Content-Length",
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen "Status",
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "X-IMAP",
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "X-IMAPbase",
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "X-Keywords",
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "X-Status",
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen "X-UID"
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen};
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainenunsigned int mbox_hide_headers_count =
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen sizeof(mbox_hide_headers) / sizeof(mbox_hide_headers[0]);
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen/* A bit ugly duplification of the above list. It's safe to modify this list
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen without bad side effects, just keep the list sorted. */
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainenconst char *mbox_save_drop_headers[] = {
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "Content-Length",
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen "Status",
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen "X-Delivery-ID"
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "X-IMAP",
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "X-IMAPbase",
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "X-Keywords",
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "X-Status",
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen "X-UID"
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen};
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainenunsigned int mbox_save_drop_headers_count =
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen sizeof(mbox_save_drop_headers) / sizeof(mbox_save_drop_headers[0]);
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainenextern struct mail_storage mbox_storage;
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainenextern struct mailbox mbox_mailbox;
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(mbox_mailbox_list_module,
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen &mailbox_list_module_register);
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainenstatic int mbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen const char *dir, const char *fname,
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen enum mailbox_list_file_type type,
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen enum mailbox_info_flags *flags);
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainenstatic int mbox_list_delete_mailbox(struct mailbox_list *list,
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen const char *name);
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainenint mbox_set_syscall_error(struct mbox_mailbox *mbox, const char *function)
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen{
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen i_assert(function != NULL);
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen
12aad74464367f7e8be11eafe1af985bf7b1adecTimo Sirainen if (ENOSPACE(errno)) {
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen mail_storage_set_error(&mbox->storage->storage,
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen "Not enough disk space");
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen } else {
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
965ed6ea3fc8f7637bd0d159d2fdb283a191ce34Timo Sirainen "%s failed with mbox file %s: %m",
91e4199476cb2add8143c18583fa57e1decfea88Timo Sirainen function, mbox->path);
0727e38ac12efb8963a339daf56255e2be1f29fcTimo Sirainen }
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen return -1;
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen}
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainenstatic bool mbox_is_file(const char *path, const char *name, bool debug)
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen{
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen struct stat st;
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen if (stat(path, &st) < 0) {
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen if (debug) {
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen i_info("mbox autodetect: %s: stat(%s) failed: %m",
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen name, path);
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen }
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen return FALSE;
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen }
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen if (S_ISDIR(st.st_mode)) {
98a711be68ba64e1cabf8cacc150af44421e2ac9Timo Sirainen if (debug) {
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen i_info("mbox autodetect: %s: is a directory (%s)",
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen name, path);
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen }
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen return FALSE;
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen }
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen if (access(path, R_OK|W_OK) < 0) {
70cb37c37e4dce8f57cd3f882f7444e76b918befTimo Sirainen if (debug) {
70cb37c37e4dce8f57cd3f882f7444e76b918befTimo Sirainen i_info("mbox autodetect: %s: no R/W access (%s)",
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen name, path);
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen }
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen return FALSE;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen }
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen if (debug)
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen i_info("mbox autodetect: %s: yes (%s)", name, path);
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen return TRUE;
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen}
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainenstatic bool mbox_is_dir(const char *path, const char *name, bool debug)
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen{
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen struct stat st;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen if (stat(path, &st) < 0) {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (debug) {
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen i_info("mbox autodetect: %s: stat(%s) failed: %m",
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen name, path);
98a711be68ba64e1cabf8cacc150af44421e2ac9Timo Sirainen }
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen return FALSE;
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen }
70cb37c37e4dce8f57cd3f882f7444e76b918befTimo Sirainen if (!S_ISDIR(st.st_mode)) {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (debug) {
i_info("mbox autodetect: %s: is not a directory (%s)",
name, path);
}
return FALSE;
}
if (access(path, R_OK|W_OK|X_OK) < 0) {
if (debug) {
i_info("mbox autodetect: %s: no R/W/X access (%s)",
name, path);
}
return FALSE;
}
if (debug)
i_info("mbox autodetect: %s: yes (%s)", name, path);
return TRUE;
}
static bool mbox_autodetect(const char *data, enum mail_storage_flags flags)
{
bool debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
const char *path;
path = t_strcut(data, ':');
if (debug) {
if (strchr(data, ':') != NULL) {
i_info("mbox autodetect: data=%s, splitting ':' -> %s",
data, path);
} else {
i_info("mbox autodetect: data=%s", data);
}
}
if (*path != '\0' && mbox_is_file(path, "INBOX file", debug))
return TRUE;
if (mbox_is_dir(t_strconcat(path, "/"MBOX_INDEX_DIR_NAME, NULL),
"has "MBOX_INDEX_DIR_NAME"/", debug))
return TRUE;
if (mbox_is_file(t_strconcat(path, "/inbox", NULL), "has inbox", debug))
return TRUE;
if (mbox_is_file(t_strconcat(path, "/mbox", NULL), "has mbox", debug))
return TRUE;
return FALSE;
}
static const char *get_root_dir(enum mail_storage_flags flags)
{
const char *home, *path;
bool debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
home = getenv("HOME");
if (home != NULL) {
path = t_strconcat(home, "/mail", NULL);
if (access(path, R_OK|W_OK|X_OK) == 0) {
if (debug)
i_info("mbox: root exists (%s)", path);
return path;
}
if (debug)
i_info("mbox: root: access(%s, rwx) failed: %m", path);
path = t_strconcat(home, "/Mail", NULL);
if (access(path, R_OK|W_OK|X_OK) == 0) {
if (debug)
i_info("mbox: root exists (%s)", path);
return path;
}
if (debug)
i_info("mbox: root: access(%s, rwx) failed: %m", path);
}
if (debug)
i_info("mbox: checking if we are chrooted:");
if (mbox_autodetect("", flags))
return "/";
if (debug)
i_info("mbox: root mail directory not found");
return NULL;
}
static const char *
get_inbox_file(const char *root_dir, bool only_root, bool debug)
{
const char *user, *path;
if (!only_root && (user = getenv("USER")) != NULL) {
path = t_strconcat("/var/mail/", user, NULL);
if (access(path, R_OK|W_OK) == 0) {
if (debug)
i_info("mbox: INBOX exists (%s)", path);
return path;
}
if (debug)
i_info("mbox: INBOX: access(%s, rw) failed: %m", path);
path = t_strconcat("/var/spool/mail/", user, NULL);
if (access(path, R_OK|W_OK) == 0) {
if (debug)
i_info("mbox: INBOX exists (%s)", path);
return path;
}
if (debug)
i_info("mbox: INBOX: access(%s, rw) failed: %m", path);
}
path = t_strconcat(root_dir, "/inbox", NULL);
if (debug)
i_info("mbox: INBOX defaulted to %s", path);
return path;
}
static const char *create_root_dir(bool debug)
{
const char *home, *path;
home = getenv("HOME");
if (home == NULL) {
i_error("mbox: We need root mail directory, "
"but can't find it or HOME environment");
return NULL;
}
path = t_strconcat(home, "/mail", NULL);
if (mkdir_parents(path, CREATE_MODE) < 0) {
i_error("mbox: Can't create root mail directory %s: %m", path);
return NULL;
}
if (debug)
i_info("mbox: root directory created: %s", path);
return path;
}
static int
mbox_get_list_settings(struct mailbox_list_settings *list_set,
const char *data, enum mail_storage_flags flags,
const char **layout_r)
{
bool debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
const char *p;
struct stat st;
bool autodetect;
*layout_r = "fs";
memset(list_set, 0, sizeof(*list_set));
list_set->subscription_fname = MBOX_SUBSCRIPTION_FILE_NAME;
list_set->maildir_name = "";
autodetect = data == NULL || *data == '\0';
if (autodetect) {
if ((flags & MAIL_STORAGE_FLAG_NO_AUTODETECTION) != 0) {
i_error("mbox: root mail directory not given");
return -1;
}
/* we'll need to figure out the mail location ourself.
it's root dir if we've already chroot()ed, otherwise
either $HOME/mail or $HOME/Mail */
list_set->root_dir = get_root_dir(flags);
} else {
/* <root mail directory> | <INBOX path>
[:INBOX=<path>] [:INDEX=<dir>] */
if (debug)
i_info("mbox: data=%s", data);
p = strchr(data, ':');
if (p == NULL) {
/* if the data points to a file, treat it as an INBOX */
if ((flags & MAIL_STORAGE_FLAG_NO_AUTODETECTION) != 0 ||
stat(data, &st) < 0 || S_ISDIR(st.st_mode))
list_set->root_dir = data;
else {
list_set->root_dir = get_root_dir(flags);
list_set->inbox_path = data;
}
} else {
list_set->root_dir = t_strdup_until(data, p);
do {
p++;
if (strncmp(p, "INBOX=", 6) == 0) {
list_set->inbox_path =
t_strcut(p+6, ':');
} else if (strncmp(p, "INDEX=", 6) == 0) {
list_set->index_dir =
t_strcut(p+6, ':');
} else if (strncmp(p, "LAYOUT=", 7) == 0) {
*layout_r = t_strcut(p+7, ':');
}
p = strchr(p, ':');
} while (p != NULL);
}
}
if (list_set->root_dir == NULL) {
if ((flags & MAIL_STORAGE_FLAG_NO_AUTOCREATE) != 0)
return -1;
list_set->root_dir = create_root_dir(debug);
if (list_set->root_dir == NULL)
return -1;
} else {
/* strip trailing '/' */
size_t len = strlen(list_set->root_dir);
if (list_set->root_dir[len-1] == '/') {
list_set->root_dir =
t_strndup(list_set->root_dir, len-1);
}
list_set->root_dir = home_expand(list_set->root_dir);
/* make sure the directory exists */
if (*list_set->root_dir == '\0' ||
lstat(list_set->root_dir, &st) == 0) {
/* yep, go ahead */
} else if (errno != ENOENT && errno != ENOTDIR) {
i_error("lstat(%s) failed: %m", list_set->root_dir);
return -1;
} else if ((flags & MAIL_STORAGE_FLAG_NO_AUTOCREATE) != 0) {
return -1;
} else if (mkdir_parents(list_set->root_dir, CREATE_MODE) < 0 &&
errno != EEXIST) {
i_error("mkdir_parents(%s) failed: %m",
list_set->root_dir);
return -1;
}
}
if (list_set->inbox_path == NULL) {
list_set->inbox_path =
get_inbox_file(list_set->root_dir, !autodetect, debug);
}
if (list_set->index_dir != NULL &&
strcmp(list_set->index_dir, "MEMORY") == 0)
list_set->index_dir = "";
return 0;
}
static const char *
mbox_list_get_path(struct mailbox_list *list, const char *name,
enum mailbox_list_path_type type)
{
struct mbox_storage *storage = MBOX_LIST_CONTEXT(list);
const char *path, *p;
path = storage->list_module_ctx.super.get_path(list, name, type);
if (type == MAILBOX_LIST_PATH_TYPE_CONTROL ||
type == MAILBOX_LIST_PATH_TYPE_INDEX) {
p = strrchr(path, '/');
if (p == NULL)
return "";
return t_strconcat(t_strdup_until(path, p),
"/"MBOX_INDEX_DIR_NAME"/", p+1, NULL);
}
return path;
}
static struct mail_storage *mbox_alloc(void)
{
struct mbox_storage *storage;
pool_t pool;
pool = pool_alloconly_create("mbox storage", 512+256);
storage = p_new(pool, struct mbox_storage, 1);
storage->storage = mbox_storage;
storage->storage.pool = pool;
return &storage->storage;
}
static int mbox_create(struct mail_storage *_storage, const char *data)
{
struct mbox_storage *storage = (struct mbox_storage *)_storage;
struct mailbox_list_settings list_set;
const char *layout, *error;
if (mbox_get_list_settings(&list_set, data,
_storage->flags, &layout) < 0)
return -1;
list_set.mail_storage_flags = &_storage->flags;
list_set.lock_method = &_storage->lock_method;
if (mailbox_list_init(layout, &list_set,
mail_storage_get_list_flags(_storage->flags),
&_storage->list, &error) < 0) {
i_error("mbox %s: %s", layout, error);
return -1;
}
storage->list_module_ctx.super = _storage->list->v;
if (strcmp(layout, "fs") == 0 && *list_set.maildir_name == '\0') {
/* have to use .imap/ directories */
_storage->list->v.get_path = mbox_list_get_path;
}
_storage->list->v.iter_is_mailbox = mbox_list_iter_is_mailbox;
_storage->list->v.delete_mailbox = mbox_list_delete_mailbox;
MODULE_CONTEXT_SET_FULL(_storage->list, mbox_mailbox_list_module,
storage, &storage->list_module_ctx);
return 0;
}
static int create_mbox_index_dirs(struct mail_storage *storage,
const char *name)
{
const char *index_dir;
index_dir = mailbox_list_get_path(storage->list, name,
MAILBOX_LIST_PATH_TYPE_INDEX);
if (*index_dir == '\0')
return 0;
if (mkdir_parents(index_dir, CREATE_MODE) < 0) {
if (!ENOSPACE(errno)) {
mail_storage_set_critical(storage,
"mkdir_parents(%s) failed: %m", index_dir);
}
return -1;
}
return 0;
}
static int verify_inbox(struct mail_storage *storage)
{
const char *inbox_path;
int fd;
inbox_path = mailbox_list_get_path(storage->list, "INBOX",
MAILBOX_LIST_PATH_TYPE_MAILBOX);
/* make sure inbox file itself exists */
fd = open(inbox_path, O_RDWR | O_CREAT | O_EXCL, 0660);
if (fd != -1)
(void)close(fd);
else if (errno != EEXIST) {
mail_storage_set_critical(storage,
"open(%s, O_CREAT) failed: %m", inbox_path);
}
return 0;
}
static bool want_memory_indexes(struct mbox_storage *storage, const char *path)
{
const char *env;
struct stat st;
unsigned int min_size;
env = getenv("MBOX_MIN_INDEX_SIZE");
if (env == NULL)
return FALSE;
min_size = strtoul(env, NULL, 10);
if (min_size == 0)
return FALSE;
if (stat(path, &st) < 0) {
if (errno == ENOENT)
st.st_size = 0;
else {
mail_storage_set_critical(&storage->storage,
"stat(%s) failed: %m", path);
return FALSE;
}
}
return st.st_size / 1024 < min_size;
}
static void mbox_lock_touch_timeout(struct mbox_mailbox *mbox)
{
(void)file_dotlock_touch(mbox->mbox_dotlock);
}
static struct mbox_mailbox *
mbox_alloc_mailbox(struct mbox_storage *storage, struct mail_index *index,
const char *name, const char *path,
enum mailbox_open_flags flags)
{
struct mbox_mailbox *mbox;
pool_t pool;
pool = pool_alloconly_create("mbox mailbox", 1024+512);
mbox = p_new(pool, struct mbox_mailbox, 1);
mbox->ibox.box = mbox_mailbox;
mbox->ibox.box.pool = pool;
mbox->ibox.storage = &storage->storage;
mbox->ibox.mail_vfuncs = &mbox_mail_vfuncs;
mbox->ibox.index = index;
mbox->storage = storage;
mbox->path = p_strdup(mbox->ibox.box.pool, path);
mbox->mbox_fd = -1;
mbox->mbox_lock_type = F_UNLCK;
mbox->mbox_ext_idx =
mail_index_ext_register(index, "mbox", 0,
sizeof(uint64_t), sizeof(uint64_t));
mbox->mbox_very_dirty_syncs = getenv("MBOX_VERY_DIRTY_SYNCS") != NULL;
mbox->mbox_do_dirty_syncs = mbox->mbox_very_dirty_syncs ||
getenv("MBOX_DIRTY_SYNCS") != NULL;
if ((storage->storage.flags & MAIL_STORAGE_FLAG_KEEP_HEADER_MD5) != 0)
mbox->mbox_save_md5 = TRUE;
if ((flags & MAILBOX_OPEN_KEEP_LOCKED) != 0) {
if (mbox_lock(mbox, F_WRLCK, &mbox->mbox_global_lock_id) <= 0) {
struct mailbox *box = &mbox->ibox.box;
mailbox_close(&box);
return NULL;
}
if (mbox->mbox_dotlock != NULL) {
mbox->keep_lock_to =
timeout_add(MBOX_LOCK_TOUCH_MSECS,
mbox_lock_touch_timeout, mbox);
}
}
index_storage_mailbox_init(&mbox->ibox, name, flags,
want_memory_indexes(storage, path));
return mbox;
}
static struct mailbox *
mbox_open(struct mbox_storage *storage, const char *name,
enum mailbox_open_flags flags)
{
struct mail_storage *_storage = &storage->storage;
struct mbox_mailbox *mbox;
struct mail_index *index;
const char *path, *index_dir;
path = mailbox_list_get_path(_storage->list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
index_dir = mailbox_list_get_path(_storage->list, name,
MAILBOX_LIST_PATH_TYPE_INDEX);
if ((flags & MAILBOX_OPEN_NO_INDEX_FILES) != 0)
index_dir = "";
if (*index_dir != '\0') {
/* make sure the index directories exist */
if (create_mbox_index_dirs(_storage, name) < 0)
index_dir = "";
}
index = index_storage_alloc(index_dir, path, MBOX_INDEX_PREFIX);
mbox = mbox_alloc_mailbox(storage, index, name, path, flags);
if (access(path, R_OK|W_OK) < 0) {
if (errno < EACCES)
mbox_set_syscall_error(mbox, "access()");
else {
mbox->ibox.readonly = TRUE;
mbox->mbox_readonly = TRUE;
}
}
return &mbox->ibox.box;
}
static struct mailbox *
mbox_mailbox_open_stream(struct mbox_storage *storage, const char *name,
struct istream *input, enum mailbox_open_flags flags)
{
struct mail_storage *_storage = &storage->storage;
struct mail_index *index;
struct mbox_mailbox *mbox;
const char *path, *index_dir;
flags |= MAILBOX_OPEN_READONLY;
path = mailbox_list_get_path(_storage->list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if ((flags & MAILBOX_OPEN_NO_INDEX_FILES) != 0)
index_dir = "";
else {
index_dir = mailbox_list_get_path(_storage->list, name,
MAILBOX_LIST_PATH_TYPE_INDEX);
/* make sure the required directories are also there */
if (create_mbox_index_dirs(_storage, name) < 0)
index_dir = "";
}
index = index_storage_alloc(index_dir, path, MBOX_INDEX_PREFIX);
mbox = mbox_alloc_mailbox(storage, index, name, path, flags);
if (mbox == NULL)
return NULL;
i_stream_ref(input);
mbox->mbox_file_stream = input;
mbox->mbox_readonly = TRUE;
mbox->no_mbox_file = TRUE;
mbox->path = "(read-only mbox stream)";
return &mbox->ibox.box;
}
static struct mailbox *
mbox_mailbox_open(struct mail_storage *_storage, const char *name,
struct istream *input, enum mailbox_open_flags flags)
{
struct mbox_storage *storage = (struct mbox_storage *)_storage;
const char *path, *error;
struct stat st;
if (input != NULL)
return mbox_mailbox_open_stream(storage, name, input, flags);
if (strcmp(name, "INBOX") == 0) {
/* make sure INBOX exists */
if (verify_inbox(_storage) < 0)
return NULL;
return mbox_open(storage, "INBOX", flags);
}
path = mailbox_list_get_path(_storage->list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (stat(path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
mail_storage_set_error(_storage,
"Mailbox isn't selectable: %s", name);
return NULL;
}
return mbox_open(storage, name, flags);
}
if (ENOTFOUND(errno)) {
mail_storage_set_error(_storage,
MAILBOX_LIST_ERR_MAILBOX_NOT_FOUND, name);
} else if (mail_storage_errno2str(&error))
mail_storage_set_error(_storage, "%s", error);
else {
mail_storage_set_critical(_storage, "stat(%s) failed: %m",
path);
}
return NULL;
}
static int mbox_mailbox_create(struct mail_storage *_storage, const char *name,
bool directory)
{
const char *path, *p, *error;
struct stat st;
int fd;
/* make sure it doesn't exist already */
path = mailbox_list_get_path(_storage->list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (stat(path, &st) == 0) {
mail_storage_set_error(_storage, "Mailbox already exists");
return -1;
}
if (errno != ENOENT) {
if (errno == ENOTDIR) {
mail_storage_set_error(_storage,
"Mailbox doesn't allow inferior mailboxes");
} else if (mail_storage_errno2str(&error))
mail_storage_set_error(_storage, "%s", error);
else {
mail_storage_set_critical(_storage,
"stat() failed for mbox file %s: %m", path);
}
return -1;
}
/* create the hierarchy if needed */
p = directory ? path + strlen(path) : strrchr(path, '/');
if (p != NULL) {
p = t_strdup_until(path, p);
if (mkdir_parents(p, CREATE_MODE) < 0) {
if (mail_storage_errno2str(&error))
mail_storage_set_error(_storage, "%s", error);
else {
mail_storage_set_critical(_storage,
"mkdir_parents(%s) failed: %m", p);
}
return -1;
}
if (directory) {
/* wanted to create only the directory */
return 0;
}
}
/* create the mailbox file */
fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0660);
if (fd != -1) {
(void)close(fd);
return 0;
}
if (errno == EEXIST) {
/* mailbox was just created between stat() and open() call.. */
mail_storage_set_error(_storage, "Mailbox already exists");
} else if (mail_storage_errno2str(&error))
mail_storage_set_error(_storage, "%s", error);
else {
mail_storage_set_critical(_storage,
"Can't create mailbox %s: %m", name);
}
return -1;
}
static int mbox_storage_close(struct mailbox *box)
{
struct mbox_mailbox *mbox = (struct mbox_mailbox *)box;
const struct mail_index_header *hdr;
int ret = 0;
if (mbox->ibox.view != NULL) {
hdr = mail_index_get_header(mbox->ibox.view);
if ((hdr->flags & MAIL_INDEX_HDR_FLAG_HAVE_DIRTY) != 0 &&
!mbox->mbox_readonly) {
/* we've done changes to mbox which haven't been
written yet. do it now. */
if (mbox_sync(mbox, MBOX_SYNC_REWRITE) < 0)
ret = -1;
}
}
if (mbox->mbox_global_lock_id != 0)
(void)mbox_unlock(mbox, mbox->mbox_global_lock_id);
if (mbox->keep_lock_to != NULL)
timeout_remove(&mbox->keep_lock_to);
mbox_file_close(mbox);
if (mbox->mbox_file_stream != NULL)
i_stream_destroy(&mbox->mbox_file_stream);
index_storage_mailbox_free(box);
return ret;
}
static void mbox_notify_changes(struct mailbox *box)
{
struct mbox_mailbox *mbox = (struct mbox_mailbox *)box;
if (box->notify_callback == NULL)
index_mailbox_check_remove_all(&mbox->ibox);
else if (!mbox->no_mbox_file)
index_mailbox_check_add(&mbox->ibox, mbox->path);
}
static bool
is_inbox_file(struct mailbox_list *list, const char *path, const char *fname)
{
const char *inbox_path;
if (strcasecmp(fname, "INBOX") != 0)
return FALSE;
inbox_path = mailbox_list_get_path(list, "INBOX",
MAILBOX_LIST_PATH_TYPE_MAILBOX);
return strcmp(inbox_path, path) == 0;
}
static int mbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
const char *dir, const char *fname,
enum mailbox_list_file_type type,
enum mailbox_info_flags *flags_r)
{
struct mail_storage *storage = MBOX_LIST_CONTEXT(ctx->list);
const char *path, *root_dir;
size_t len;
struct stat st;
int ret = 1;
if (strcmp(fname, MBOX_INDEX_DIR_NAME) == 0) {
*flags_r = MAILBOX_NOSELECT;
return 0;
}
if (strcmp(fname, MBOX_SUBSCRIPTION_FILE_NAME) == 0) {
root_dir = mailbox_list_get_path(storage->list, NULL,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (strcmp(root_dir, dir) == 0) {
*flags_r = MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
return 0;
}
}
/* skip all .lock files */
len = strlen(fname);
if (len > 5 && strcmp(fname+len-5, ".lock") == 0) {
*flags_r = MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
return 0;
}
/* try to avoid stat() with these checks */
if (type == MAILBOX_LIST_FILE_TYPE_DIR) {
*flags_r = MAILBOX_NOSELECT | MAILBOX_CHILDREN;
return 1;
}
if (type != MAILBOX_LIST_FILE_TYPE_SYMLINK &&
type != MAILBOX_LIST_FILE_TYPE_UNKNOWN &&
(ctx->flags & MAILBOX_LIST_ITER_FAST_FLAGS) != 0) {
*flags_r = MAILBOX_NOINFERIORS;
return 1;
}
/* need to stat() then */
t_push();
path = t_strconcat(dir, "/", fname, NULL);
if (stat(path, &st) == 0) {
if (S_ISDIR(st.st_mode))
*flags_r = MAILBOX_NOSELECT | MAILBOX_CHILDREN;
else {
*flags_r = MAILBOX_NOINFERIORS | STAT_GET_MARKED(st);
if (is_inbox_file(ctx->list, path, fname) &&
strcmp(fname, "INBOX") != 0) {
/* it's possible for INBOX to have child
mailboxes as long as the inbox file itself
isn't in <mail root>/INBOX */
*flags_r &= ~MAILBOX_NOINFERIORS;
}
}
} else if (errno == EACCES || errno == ELOOP)
*flags_r = MAILBOX_NOSELECT;
else if (ENOTFOUND(errno))
ret = 0;
else {
mail_storage_set_critical(storage, "stat(%s) failed: %m", path);
ret = -1;
}
t_pop();
return ret;
}
static int mbox_list_delete_mailbox(struct mailbox_list *list,
const char *name)
{
struct mbox_storage *storage = MBOX_LIST_CONTEXT(list);
struct stat st;
const char *path, *index_dir, *error;
path = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (lstat(path, &st) < 0) {
if (ENOTFOUND(errno)) {
mailbox_list_set_error(list, t_strdup_printf(
MAILBOX_LIST_ERR_MAILBOX_NOT_FOUND, name));
} else if (mail_storage_errno2str(&error))
mailbox_list_set_error(list, error);
else {
mailbox_list_set_critical(list,
"lstat() failed for %s: %m", path);
}
return -1;
}
if (S_ISDIR(st.st_mode)) {
/* deleting a directory. allow it only if it doesn't contain
anything. Delete the ".imap" directory first in case there
have been indexes. */
index_dir = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
index_dir = *index_dir == '\0' ? "" :
t_strconcat(index_dir, "/"MBOX_INDEX_DIR_NAME, NULL);
if (*index_dir != '\0' && rmdir(index_dir) < 0 &&
!ENOTFOUND(errno) && errno != ENOTEMPTY) {
if (mail_storage_errno2str(&error))
mailbox_list_set_error(list, error);
else {
mailbox_list_set_critical(list,
"rmdir() failed for %s: %m", index_dir);
}
return -1;
}
if (rmdir(path) == 0)
return 0;
if (ENOTFOUND(errno)) {
mailbox_list_set_error(list, t_strdup_printf(
MAILBOX_LIST_ERR_MAILBOX_NOT_FOUND, name));
} else if (errno == ENOTEMPTY) {
mailbox_list_set_error(list, t_strdup_printf(
"Directory %s isn't empty, can't delete it.",
name));
} else if (mail_storage_errno2str(&error))
mailbox_list_set_error(list, error);
else {
mailbox_list_set_critical(list,
"rmdir() failed for %s: %m", path);
}
return -1;
}
/* delete index / control files first */
index_storage_destroy_unrefed();
if (storage->list_module_ctx.super.delete_mailbox(list, name) < 0)
return -1;
if (unlink(path) < 0) {
if (ENOTFOUND(errno)) {
mailbox_list_set_error(list, t_strdup_printf(
MAILBOX_LIST_ERR_MAILBOX_NOT_FOUND, name));
} else if (mail_storage_errno2str(&error))
mailbox_list_set_error(list, error);
else {
mailbox_list_set_critical(list,
"unlink() failed for %s: %m", path);
}
return -1;
}
return 0;
}
static void mbox_class_init(void)
{
mbox_transaction_class_init();
}
static void mbox_class_deinit(void)
{
mbox_transaction_class_deinit();
}
struct mail_storage mbox_storage = {
MEMBER(name) MBOX_STORAGE_NAME,
MEMBER(mailbox_is_file) TRUE,
{
mbox_class_init,
mbox_class_deinit,
mbox_alloc,
mbox_create,
NULL,
mbox_autodetect,
mbox_mailbox_open,
mbox_mailbox_create,
index_storage_get_last_error
}
};
struct mailbox mbox_mailbox = {
MEMBER(name) NULL,
MEMBER(storage) NULL,
{
index_storage_is_readonly,
index_storage_allow_new_keywords,
mbox_storage_close,
index_storage_get_status,
mbox_storage_sync_init,
index_mailbox_sync_next,
index_mailbox_sync_deinit,
mbox_notify_changes,
index_transaction_begin,
index_transaction_commit,
index_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_init,
index_storage_search_deinit,
index_storage_search_next_nonblock,
index_storage_search_next_update_seq,
mbox_save_init,
mbox_save_continue,
mbox_save_finish,
mbox_save_cancel,
mail_storage_copy,
index_storage_is_inconsistent
}
};