maildir-storage.c revision e0965ef0a118013e7ff47efa3d2c01084fcf31c8
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (C) 2002-2003 Timo Sirainen */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen#include "lib.h"
d23c747de9d33966483fbdd41f08ad7766da7c5cTimo Sirainen#include "hostpid.h"
d23c747de9d33966483fbdd41f08ad7766da7c5cTimo Sirainen#include "home-expand.h"
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen#include "mkdir-parents.h"
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen#include "unlink-directory.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include "subscription-file/subscription-file.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include "maildir-storage.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include "maildir-uidlist.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include <stdio.h>
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include <stdlib.h>
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include <unistd.h>
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen#include <sys/stat.h>
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainen#define CREATE_MODE 0770 /* umask() should limit it more */
7fd51f7b0b4d990ec3cfef4e60ee685bf9fb32deTimo Sirainen
7fd51f7b0b4d990ec3cfef4e60ee685bf9fb32deTimo Sirainenstruct rename_context {
7fd51f7b0b4d990ec3cfef4e60ee685bf9fb32deTimo Sirainen int found;
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen size_t oldnamelen;
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen const char *newname;
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen};
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainenextern struct mail_storage maildir_storage;
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainenextern struct mailbox maildir_mailbox;
4ac2d38239cea8090154e17faefd77de5a71d882Timo Sirainen
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainenstatic const char *maildirs[] = { "cur", "new", "tmp", NULL };
4ac2d38239cea8090154e17faefd77de5a71d882Timo Sirainen
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainenstatic int verify_inbox(struct index_storage *storage);
32e1554df9abca74fef0af2ba2e4c37e90a06cd0Timo Sirainen
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainenstatic struct mail_storage *
32e1554df9abca74fef0af2ba2e4c37e90a06cd0Timo Sirainenmaildir_create(const char *data, const char *user,
a988c3fd9251806e38931a011aaa4006dd081cbdTimo Sirainen const char *namespace, char hierarchy_sep)
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen{
b337d3b6871b878d6467d7d8ed600433af5da5a1Timo Sirainen struct index_storage *storage;
b337d3b6871b878d6467d7d8ed600433af5da5a1Timo Sirainen const char *root_dir, *inbox_dir, *index_dir, *control_dir;
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen const char *home, *path, *p;
60b42c6dfdf9edcca8a96b380ef9a0adc60c2464Timo Sirainen size_t len;
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen inbox_dir = root_dir = index_dir = control_dir = NULL;
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen if (data == NULL || *data == '\0') {
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen /* we'll need to figure out the maildir location ourself.
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainen it's either root dir if we've already chroot()ed, or
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainen $HOME/Maildir otherwise */
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen if (access("/cur", R_OK|W_OK|X_OK) == 0)
2b95b7a9f4f06e7640ef431d9e6efc2423cacf1aTimo Sirainen root_dir = "/";
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen else {
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen home = getenv("HOME");
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen if (home != NULL) {
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen path = t_strconcat(home, "/Maildir", NULL);
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainen if (access(path, R_OK|W_OK|X_OK) == 0)
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen root_dir = path;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen }
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen }
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen } else {
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen /* <Maildir> [:INBOX=<dir>] [:INDEX=<dir>] [:CONTROL=<dir>] */
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen p = strchr(data, ':');
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen if (p == NULL)
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen root_dir = data;
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen else {
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen root_dir = t_strdup_until(data, p);
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen do {
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen p++;
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen if (strncmp(p, "INBOX=", 6) == 0)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen inbox_dir = t_strcut(p+6, ':');
c58906589cafc32df4c04ffbef933baadd3f2276Timo Sirainen else if (strncmp(p, "INDEX=", 6) == 0)
47ede56f4e6eebfe631a1f0febf74d7adcdbcd00Timo Sirainen index_dir = t_strcut(p+6, ':');
47ede56f4e6eebfe631a1f0febf74d7adcdbcd00Timo Sirainen else if (strncmp(p, "CONTROL=", 8) == 0)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen control_dir = t_strcut(p+8, ':');
c396c5cdd510d09aa35875ccfd643c5c21ed1f89Timo Sirainen p = strchr(p, ':');
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen } while (p != NULL);
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen }
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen }
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen if (root_dir == NULL)
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen return NULL;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
d9250ee7e2815bb2116134b58f7c860578148d6cTimo Sirainen /* strip trailing '/' */
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen len = strlen(root_dir);
092018b35bb1dc5bd61848a38189fe6ac8f791ddTimo Sirainen if (root_dir[len-1] == '/')
7327394e30c1020b9a2a49c72a7e3d0f7803e680Timo Sirainen root_dir = t_strndup(root_dir, len-1);
7327394e30c1020b9a2a49c72a7e3d0f7803e680Timo Sirainen
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen if (index_dir == NULL)
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen index_dir = root_dir;
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen else if (strcmp(index_dir, "MEMORY") == 0)
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen index_dir = NULL;
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen storage = i_new(struct index_storage, 1);
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen storage->storage = maildir_storage;
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen if (hierarchy_sep != '\0')
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen storage->storage.hierarchy_sep = hierarchy_sep;
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen storage->storage.namespace = i_strdup(namespace);
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen /* the default ".temp.xxx" prefix would be treated as directory */
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen storage->temp_prefix =
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen i_strconcat("temp.", my_hostname, ".", my_pid, ".", NULL);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen storage->dir = i_strdup(home_expand(root_dir));
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen storage->inbox_path = i_strdup(home_expand(inbox_dir));
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen storage->index_dir = i_strdup(home_expand(index_dir));
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen storage->control_dir = i_strdup(home_expand(control_dir));
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen storage->user = i_strdup(user);
c8cf8a605e0ddea7cb36fe04551aeca5090e684bTimo Sirainen storage->callbacks = i_new(struct mail_storage_callbacks, 1);
c8cf8a605e0ddea7cb36fe04551aeca5090e684bTimo Sirainen index_storage_init(storage);
c8cf8a605e0ddea7cb36fe04551aeca5090e684bTimo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen (void)verify_inbox(storage);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen return &storage->storage;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen}
ef50336eefcb9ba99f73c6af37420eaf8857a39bTimo Sirainen
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainenstatic void maildir_free(struct mail_storage *_storage)
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen{
14376e0584c178306c400750352869cf2aaf6feeTimo Sirainen struct index_storage *storage = (struct index_storage *) _storage;
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainen index_storage_deinit(storage);
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen i_free(storage->temp_prefix);
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen i_free(storage->dir);
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen i_free(storage->inbox_path);
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen i_free(storage->index_dir);
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen i_free(storage->control_dir);
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen i_free(storage->user);
288d6ef592719f2be3cad9f034e9be05f9839785Timo Sirainen i_free(storage->callbacks);
288d6ef592719f2be3cad9f034e9be05f9839785Timo Sirainen i_free(storage);
288d6ef592719f2be3cad9f034e9be05f9839785Timo Sirainen}
288d6ef592719f2be3cad9f034e9be05f9839785Timo Sirainen
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainenstatic int maildir_autodetect(const char *data)
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen{
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen struct stat st;
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen data = t_strcut(data, ':');
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen return stat(t_strconcat(data, "/cur", NULL), &st) == 0 &&
32e1554df9abca74fef0af2ba2e4c37e90a06cd0Timo Sirainen S_ISDIR(st.st_mode);
32e1554df9abca74fef0af2ba2e4c37e90a06cd0Timo Sirainen}
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainenstatic int maildir_is_valid_create_name(const char *name)
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen{
a988c3fd9251806e38931a011aaa4006dd081cbdTimo Sirainen size_t len;
a988c3fd9251806e38931a011aaa4006dd081cbdTimo Sirainen
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen len = strlen(name);
bb1da4a6320eec11890c852e74a08868837e7be3Timo Sirainen if (len == 0 || name[0] == MAILDIR_FS_SEP ||
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen name[len-1] == MAILDIR_FS_SEP ||
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen strchr(name, '*') != NULL || strchr(name, '%') != NULL)
9874ad56b94788297fdac4eae7cba5d651b48222Timo Sirainen return FALSE;
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen if (full_filesystem_access)
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen return TRUE;
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen if (*name == '~' || strchr(name, '/') != NULL)
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen return FALSE;
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen return TRUE;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen}
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainenstatic int maildir_is_valid_existing_name(const char *name)
a3f6d0302a83270253ff9d2ebd4fea0003e9ac95Timo Sirainen{
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen if (name[0] == '\0' || name[strlen(name)-1] == '/')
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen return FALSE;
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen if (full_filesystem_access)
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen return TRUE;
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen
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_fix_mailbox_name(struct index_storage *storage,
const char *name, int remove_namespace)
{
char *dup, *p, sep;
size_t len;
if (strncasecmp(name, "INBOX", 5) == 0 &&
(name[5] == '\0' || name[5] == storage->storage.hierarchy_sep)) {
/* use same case with all INBOX folders or we'll get
into trouble */
name = t_strconcat("INBOX", name+5, NULL);
if (name[5] == '\0') {
/* don't check namespace with INBOX */
return name;
}
}
if (storage->storage.namespace != NULL && remove_namespace) {
len = strlen(storage->storage.namespace);
if (strncmp(storage->storage.namespace, name, len) != 0) {
i_panic("maildir: expecting namespace '%s' in name "
"'%s'", storage->storage.namespace, name);
}
name += len;
}
if (full_filesystem_access && (*name == '/' || *name == '~'))
return name;
sep = storage->storage.hierarchy_sep;
if (sep == MAILDIR_FS_SEP)
return name;
dup = t_strdup_noconst(name);
for (p = dup; *p != '\0'; p++) {
if (*p == sep)
*p = MAILDIR_FS_SEP;
}
return dup;
}
const char *maildir_get_path(struct index_storage *storage, const char *name)
{
if (full_filesystem_access && (*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 (full_filesystem_access && (*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 && storage->inbox_path != NULL)
return storage->inbox_path;
if (full_filesystem_access && (*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 index_storage *storage,
const char *name)
{
if (storage->control_dir == NULL)
return maildir_get_path(storage, name);
if (full_filesystem_access && (*name == '/' || *name == '~'))
return maildir_get_absolute_path(name, FALSE);
return t_strconcat(storage->control_dir, "/"MAILDIR_FS_SEP_S,
name, NULL);
}
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(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) == -1 && errno != EEXIST) {
mail_storage_set_critical(&storage->storage,
"mkdir(%s) failed: %m", dir);
return -1;
}
return 0;
}
static int create_control_dir(struct index_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 index_storage *storage)
{
const char *inbox;
if (storage->inbox_path == NULL) {
/* first make sure the cur/ new/ and tmp/ dirs exist
in root dir */
if (create_maildir(storage, storage->dir, TRUE) < 0)
return -1;
/* create the .INBOX directory */
inbox = t_strconcat(storage->index_dir,
"/"MAILDIR_FS_SEP_S"INBOX", NULL);
if (mkdir_verify(storage, inbox, TRUE) < 0)
return -1;
} else {
if (create_maildir(storage, storage->inbox_path, TRUE) < 0)
return -1;
}
/* make sure the index directories exist */
if (create_index_dir(storage, "INBOX") < 0)
return -1;
if (create_control_dir(storage, "INBOX") < 0)
return -1;
return 0;
}
static uint32_t maildir_get_recent_count(struct index_mailbox *ibox)
{
return maildir_uidlist_get_recent_count(ibox->uidlist);
}
static struct mailbox *
maildir_open(struct index_storage *storage, const char *name,
enum mailbox_open_flags flags)
{
struct index_mailbox *ibox;
struct mail_index *index;
const char *path, *index_dir, *control_dir;
struct stat st;
path = maildir_get_path(storage, name);
index_dir = maildir_get_index_path(storage, name);
control_dir = maildir_get_control_path(storage, name);
index = index_storage_alloc(index_dir, path, MAILDIR_INDEX_PREFIX);
ibox = index_storage_mailbox_init(storage, &maildir_mailbox,
index, name, flags);
if (ibox == NULL)
return NULL;
ibox->path = i_strdup(path);
ibox->control_dir = i_strdup(control_dir);
ibox->get_recent_count = maildir_get_recent_count;
ibox->mail_interface = &maildir_mail;
ibox->uidlist = maildir_uidlist_init(ibox);
/* for shared mailboxes get the create mode from the
permissions of dovecot-shared file */
if (stat(t_strconcat(path, "/dovecot-shared", NULL), &st) < 0)
ibox->mail_create_mode = 0600;
else {
ibox->mail_create_mode = st.st_mode & 0666;
ibox->private_flags_mask = MAIL_SEEN;
}
return &ibox->box;
}
static struct mailbox *
maildir_mailbox_open(struct mail_storage *_storage,
const char *name, enum mailbox_open_flags flags)
{
struct index_storage *storage = (struct index_storage *)_storage;
const char *path;
struct stat st;
mail_storage_clear_error(_storage);
name = maildir_fix_mailbox_name(storage, name, TRUE);
if (strcmp(name, "INBOX") == 0) {
if (verify_inbox(storage) < 0)
return NULL;
return maildir_open(storage, "INBOX", flags);
}
if (!maildir_is_valid_existing_name(name)) {
mail_storage_set_error(_storage, "Invalid mailbox name");
return NULL;
}
path = maildir_get_path(storage, name);
if (stat(path, &st) == 0) {
/* exists - make sure the required directories are also there */
if (create_maildir(storage, path, TRUE) < 0 ||
create_index_dir(storage, 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_mailbox_create(struct mail_storage *_storage,
const char *name,
int directory __attr_unused__)
{
struct index_storage *storage = (struct index_storage *)_storage;
const char *path;
mail_storage_clear_error(_storage);
name = maildir_fix_mailbox_name(storage, name, TRUE);
if (!maildir_is_valid_create_name(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;
}
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);
name = maildir_fix_mailbox_name(storage, name, TRUE);
if (strcmp(name, "INBOX") == 0) {
mail_storage_set_error(_storage, "INBOX can't be deleted.");
return -1;
}
if (!maildir_is_valid_existing_name(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, *mask;
size_t oldnamelen;
int ret;
ret = 0;
oldnamelen = strlen(oldname);
mask = t_strdup_printf("%s%s%c*", storage->storage.namespace != NULL ?
storage->storage.namespace : "", oldname,
storage->storage.hierarchy_sep);
ctx = maildir_mailbox_list_init(&storage->storage, mask,
MAILBOX_LIST_FAST_FLAGS);
while ((list = maildir_mailbox_list_next(ctx)) != NULL) {
const char *list_name;
t_push();
list_name = maildir_fix_mailbox_name(storage, list->name, TRUE);
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);
oldname = maildir_fix_mailbox_name(storage, oldname, TRUE);
newname = maildir_fix_mailbox_name(storage, newname, TRUE);
if (!maildir_is_valid_existing_name(oldname) ||
!maildir_is_valid_create_name(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 index_storage *storage = (struct index_storage *)_storage;
const char *path;
path = t_strconcat(storage->control_dir != NULL ?
storage->control_dir : storage->dir,
"/" SUBSCRIPTION_FILE_NAME, NULL);
name = maildir_fix_mailbox_name(storage, name, FALSE);
return subsfile_set_subscribed(_storage, path, 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);
name = maildir_fix_mailbox_name(storage, name, TRUE);
if (!maildir_is_valid_existing_name(name)) {
*status = MAILBOX_NAME_INVALID;
return 0;
}
path = maildir_get_path(storage, name);
if (stat(path, &st) == 0) {
*status = MAILBOX_NAME_EXISTS;
return 0;
}
if (!maildir_is_valid_create_name(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 index_mailbox *ibox = (struct index_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(ibox->uidlist);
index_storage_mailbox_free(box);
return ret;
}
static void maildir_storage_auto_sync(struct mailbox *box,
enum mailbox_sync_flags flags,
unsigned int min_newmail_notify_interval)
{
struct index_mailbox *ibox = (struct index_mailbox *)box;
ibox->min_newmail_notify_interval = min_newmail_notify_interval;
if ((ibox->autosync_flags == 0 && flags == 0) ||
(ibox->autosync_flags != 0 && flags != 0)) {
/* flags or interval just changed. or nothing. */
ibox->autosync_flags = flags;
}
ibox->autosync_flags = flags;
if (flags == 0) {
index_mailbox_check_remove_all(ibox);
return;
}
index_mailbox_check_add(ibox,
t_strconcat(ibox->storage->dir, "/new", NULL), TRUE);
index_mailbox_check_add(ibox,
t_strconcat(ibox->storage->dir, "/cur", NULL), TRUE);
}
struct mail_storage maildir_storage = {
"maildir", /* name */
NULL, /* namespace */
'.', /* default hierarchy separator */
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,
NULL,
0
};
struct mailbox maildir_mailbox = {
NULL, /* name */
NULL, /* storage */
index_storage_is_readonly,
index_storage_allow_new_keywords,
maildir_storage_close,
index_storage_get_status,
maildir_storage_sync,
maildir_storage_auto_sync,
maildir_transaction_begin,
maildir_transaction_commit,
maildir_transaction_rollback,
index_storage_fetch,
index_storage_get_uids,
index_storage_search_get_sorting,
index_storage_search_init,
index_storage_search_deinit,
index_storage_search_next,
maildir_save,
maildir_copy,
index_storage_is_inconsistent
};