mbox-list.c revision 3f1eb142cec7c5c990646bebd93f457e105b01d1
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen/* Copyright (C) 2002-2003 Timo Sirainen */
863f2bc9983c33221f5936421fc9c06caf21639aTimo Sirainen
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen#include "lib.h"
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen#include "unlink-directory.h"
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen#include "imap-match.h"
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen#include "subscription-file/subscription-file.h"
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen#include "mbox-storage.h"
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen#include "home-expand.h"
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen#include <dirent.h>
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen#include <sys/stat.h>
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
863f2bc9983c33221f5936421fc9c06caf21639aTimo Sirainen/* atime < mtime is a reliable way to know that something changed in the file.
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen atime >= mtime is not however reliable, especially because atime gets
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen updated whenever we open the mbox file, and STATUS/EXAMINE shouldn't change
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen \Marked mailbox to \Unmarked.. */
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen#define STAT_GET_MARKED(st) \
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen ((st).st_size == 0 ? MAILBOX_UNMARKED : \
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen (st).st_atime < (st).st_mtime ? MAILBOX_MARKED : 0)
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainenstruct list_dir_context {
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen struct list_dir_context *prev;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
8dec8eab222cb7a0b4ef5e066cb1d7dac8a526d1Timo Sirainen DIR *dirp;
8dec8eab222cb7a0b4ef5e066cb1d7dac8a526d1Timo Sirainen char *real_path, *virtual_path;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen};
463cf881132dc89c6a1400614a80b70e67ca915fTimo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstruct mbox_list_context {
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen struct mailbox_list_context mailbox_ctx;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen struct index_storage *istorage;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
1358e2c58ce29231485a5cfa454756d429ad3d2cTimo Sirainen enum mailbox_list_flags flags;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
c1847a38dfbc8bad29547d96432ef9d707dbd345Timo Sirainen const char *prefix;
c1847a38dfbc8bad29547d96432ef9d707dbd345Timo Sirainen struct imap_match_glob *glob;
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen struct subsfile_list_context *subsfile_ctx;
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen int failed;
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen struct mailbox_list *(*next)(struct mbox_list_context *ctx);
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen pool_t list_pool;
cd18d7bb3e8d5921c3c852bd0a27fbeff9a9babbTimo Sirainen struct mailbox_list list;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen struct list_dir_context *dir;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen};
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic struct mailbox_list *mbox_list_subs(struct mbox_list_context *ctx);
1358e2c58ce29231485a5cfa454756d429ad3d2cTimo Sirainenstatic struct mailbox_list *mbox_list_inbox(struct mbox_list_context *ctx);
c1847a38dfbc8bad29547d96432ef9d707dbd345Timo Sirainenstatic struct mailbox_list *mbox_list_path(struct mbox_list_context *ctx);
1e535244304ef4342379b6ef50dbf9f0106873eeTimo Sirainenstatic struct mailbox_list *mbox_list_next(struct mbox_list_context *ctx);
c1847a38dfbc8bad29547d96432ef9d707dbd345Timo Sirainenstatic struct mailbox_list *mbox_list_none(struct mbox_list_context *ctx);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic const char *mask_get_dir(struct mail_storage *storage, const char *mask)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen{
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen const char *p, *last_dir;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen size_t len;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
if (storage->namespace != NULL) {
len = strlen(storage->namespace);
if (strncmp(mask, storage->namespace, len) != 0)
return NULL;
mask += len;
}
last_dir = NULL;
for (p = mask; *p != '\0' && *p != '%' && *p != '*'; p++) {
if (*p == '/')
last_dir = p;
}
return last_dir == NULL ? NULL : t_strdup_until(mask, last_dir);
}
static const char *
mbox_get_path(struct index_storage *storage, const char *name)
{
if (!full_filesystem_access || name == NULL ||
(*name != '/' && *name != '~' && *name != '\0'))
return t_strconcat(storage->dir, "/", name, NULL);
else
return home_expand(name);
}
static int list_opendir(struct mail_storage *storage,
const char *path, int root, DIR **dirp)
{
*dirp = opendir(*path == '\0' ? "/" : path);
if (*dirp != NULL)
return 1;
if (ENOTFOUND(errno)) {
/* root) user gave invalid hiearchy, ignore
sub) probably just race condition with other client
deleting the mailbox. */
return 0;
}
if (errno == EACCES) {
if (!root) {
/* subfolder, ignore */
return 0;
}
mail_storage_set_error(storage, "Access denied");
return -1;
}
mail_storage_set_critical(storage, "opendir(%s) failed: %m", path);
return -1;
}
struct mailbox_list_context *
mbox_mailbox_list_init(struct mail_storage *storage, const char *mask,
enum mailbox_list_flags flags)
{
struct index_storage *istorage = (struct index_storage *)storage;
struct mbox_list_context *ctx;
const char *path, *virtual_path;
DIR *dirp;
mail_storage_clear_error(storage);
if (storage->hierarchy_sep != '/' && strchr(mask, '/') != NULL) {
/* this will never match, return nothing */
ctx = i_new(struct mbox_list_context, 1);
ctx->mailbox_ctx.storage = storage;
ctx->next = mbox_list_none;
return &ctx->mailbox_ctx;
}
mask = mbox_fix_mailbox_name(istorage, mask, FALSE);
/* check that we're not trying to do any "../../" lists */
if (!mbox_is_valid_mask(mask)) {
mail_storage_set_error(storage, "Invalid mask");
return NULL;
}
if ((flags & MAILBOX_LIST_SUBSCRIBED) != 0) {
ctx = i_new(struct mbox_list_context, 1);
ctx->mailbox_ctx.storage = storage;
ctx->istorage = istorage;
ctx->flags = flags;
ctx->next = mbox_list_subs;
path = t_strconcat(istorage->dir,
"/" SUBSCRIPTION_FILE_NAME, NULL);
ctx->subsfile_ctx =
subsfile_list_init(storage, path);
if (ctx->subsfile_ctx == NULL) {
i_free(ctx);
return NULL;
}
ctx->glob = imap_match_init(default_pool, mask, TRUE, '/');
ctx->list_pool = pool_alloconly_create("mbox_list", 1024);
return &ctx->mailbox_ctx;
}
/* if we're matching only subdirectories, don't bother scanning the
parent directories */
virtual_path = mask_get_dir(storage, mask);
path = mbox_get_path(istorage, virtual_path);
if (list_opendir(storage, path, TRUE, &dirp) < 0)
return NULL;
/* if user gave invalid directory, we just don't show any results. */
ctx = i_new(struct mbox_list_context, 1);
ctx->mailbox_ctx.storage = storage;
ctx->istorage = istorage;
ctx->flags = flags;
ctx->glob = imap_match_init(default_pool, mask, TRUE, '/');
ctx->list_pool = pool_alloconly_create("mbox_list", 1024);
ctx->prefix = storage->namespace == NULL ? "" :
mbox_fix_mailbox_name(istorage, storage->namespace, FALSE);
if (virtual_path == NULL && imap_match(ctx->glob, "INBOX") > 0)
ctx->next = mbox_list_inbox;
else if (virtual_path != NULL && dirp != NULL)
ctx->next = mbox_list_path;
else
ctx->next = mbox_list_next;
if (dirp != NULL) {
ctx->dir = i_new(struct list_dir_context, 1);
ctx->dir->dirp = dirp;
ctx->dir->real_path = i_strdup(path);
ctx->dir->virtual_path = virtual_path == NULL ? NULL :
i_strconcat(ctx->prefix, virtual_path, NULL);
}
return &ctx->mailbox_ctx;
}
static void list_dir_context_free(struct list_dir_context *dir)
{
(void)closedir(dir->dirp);
i_free(dir->real_path);
i_free(dir->virtual_path);
i_free(dir);
}
int mbox_mailbox_list_deinit(struct mailbox_list_context *_ctx)
{
struct mbox_list_context *ctx = (struct mbox_list_context *)_ctx;
int ret = ctx->failed ? -1 : 0;
if (ctx->subsfile_ctx != NULL) {
if (subsfile_list_deinit(ctx->subsfile_ctx) < 0)
ret = -1;
}
while (ctx->dir != NULL) {
struct list_dir_context *dir = ctx->dir;
ctx->dir = dir->prev;
list_dir_context_free(dir);
}
if (ctx->list_pool != NULL)
pool_unref(ctx->list_pool);
if (ctx->glob != NULL)
imap_match_deinit(ctx->glob);
i_free(ctx);
return ret;
}
struct mailbox_list *mbox_mailbox_list_next(struct mailbox_list_context *_ctx)
{
struct mbox_list_context *ctx = (struct mbox_list_context *)_ctx;
return ctx->next(ctx);
}
static int list_file(struct mbox_list_context *ctx, const char *fname)
{
struct list_dir_context *dir;
const char *list_path, *real_path, *path;
struct stat st;
DIR *dirp;
size_t len;
enum imap_match_result match, match2;
int ret, noselect;
/* skip all hidden files */
if (fname[0] == '.')
return 0;
/* skip all .lock files */
len = strlen(fname);
if (len > 5 && strcmp(fname+len-5, ".lock") == 0)
return 0;
/* check the mask */
if (ctx->dir->virtual_path == NULL)
list_path = t_strconcat(ctx->prefix, fname, NULL);
else {
list_path = t_strconcat(ctx->dir->virtual_path,
"/", fname, NULL);
}
if ((match = imap_match(ctx->glob, list_path)) < 0)
return 0;
/* see if it's a directory */
real_path = t_strconcat(ctx->dir->real_path, "/", fname, NULL);
if (stat(real_path, &st) == 0)
noselect = FALSE;
else if (errno == EACCES || errno == ELOOP)
noselect = TRUE;
else {
if (ENOTFOUND(errno))
return 0;
mail_storage_set_critical(ctx->mailbox_ctx.storage,
"stat(%s) failed: %m", real_path);
return -1;
}
if (!noselect && S_ISDIR(st.st_mode)) {
/* subdirectory. scan inside it. */
path = t_strconcat(list_path, "/", NULL);
match2 = imap_match(ctx->glob, path);
ctx->list.flags = MAILBOX_NOSELECT | MAILBOX_CHILDREN;
if (match > 0)
ctx->list.name = p_strdup(ctx->list_pool, list_path);
else if (match2 > 0)
ctx->list.name = p_strdup(ctx->list_pool, path);
else
ctx->list.name = NULL;
ret = match2 < 0 ? 0 :
list_opendir(ctx->mailbox_ctx.storage,
real_path, FALSE, &dirp);
if (ret > 0) {
dir = i_new(struct list_dir_context, 1);
dir->dirp = dirp;
dir->real_path = i_strdup(real_path);
dir->virtual_path = i_strdup(list_path);
dir->prev = ctx->dir;
ctx->dir = dir;
} else if (ret < 0)
return -1;
return match > 0 || match2 > 0;
} else if (match > 0 &&
strcmp(real_path, ctx->istorage->inbox_path) != 0 &&
strcasecmp(list_path, "INBOX") != 0) {
/* don't match any INBOX here, it's added separately.
we might also have ~/mail/inbox, ~/mail/Inbox etc.
Just ignore them for now. */
ctx->list.flags = noselect ? MAILBOX_NOSELECT :
(MAILBOX_NOINFERIORS | STAT_GET_MARKED(st));
ctx->list.name = p_strdup(ctx->list_pool, list_path);
return 1;
}
return 0;
}
static struct mailbox_list *list_fix_name(struct mbox_list_context *ctx)
{
char *p, *str, sep;
if (strchr(ctx->list.name, '/') != NULL) {
str = p_strdup(ctx->list_pool, ctx->list.name);
ctx->list.name = str;
sep = ctx->mailbox_ctx.storage->hierarchy_sep;
for (p = str; *p != '\0'; p++) {
if (*p == '/')
*p = sep;
}
}
return &ctx->list;
}
static struct mailbox_list *mbox_list_subs(struct mbox_list_context *ctx)
{
struct stat st;
const char *name, *path, *p;
enum imap_match_result match = IMAP_MATCH_NO;
while ((name = subsfile_list_next(ctx->subsfile_ctx)) != NULL) {
match = imap_match(ctx->glob, name);
if (match == IMAP_MATCH_YES || match == IMAP_MATCH_PARENT)
break;
}
if (name == NULL)
return NULL;
ctx->list.flags = 0;
ctx->list.name = name;
if (match == IMAP_MATCH_PARENT) {
/* placeholder */
ctx->list.flags = MAILBOX_PLACEHOLDER;
while ((p = strrchr(name, '/')) != NULL) {
name = t_strdup_until(name, p);
if (imap_match(ctx->glob, name) > 0) {
p_clear(ctx->list_pool);
ctx->list.name = p_strdup(ctx->list_pool, name);
return list_fix_name(ctx);
}
}
i_unreached();
}
(void)list_fix_name(ctx);
if ((ctx->flags & MAILBOX_LIST_FAST_FLAGS) != 0)
return &ctx->list;
t_push();
name = mbox_fix_mailbox_name(ctx->istorage, ctx->list.name, TRUE);
path = mbox_get_path(ctx->istorage, name);
if (stat(path, &st) == 0) {
if (S_ISDIR(st.st_mode))
ctx->list.flags = MAILBOX_NOSELECT | MAILBOX_CHILDREN;
else {
ctx->list.flags = MAILBOX_NOINFERIORS |
STAT_GET_MARKED(st);
}
} else {
if (strcasecmp(ctx->list.name, "INBOX") == 0)
ctx->list.flags = MAILBOX_UNMARKED;
else
ctx->list.flags = MAILBOX_NONEXISTENT;
}
t_pop();
return &ctx->list;
}
static struct mailbox_list *mbox_list_inbox(struct mbox_list_context *ctx)
{
struct stat st;
if (ctx->dir != NULL && ctx->dir->virtual_path != NULL)
ctx->next = mbox_list_path;
else
ctx->next = mbox_list_next;
/* INBOX exists always, even if the file doesn't. */
ctx->list.flags = strncmp(ctx->prefix, "INBOX/", 6) == 0 ?
MAILBOX_CHILDREN : MAILBOX_NOINFERIORS;
if ((ctx->flags & MAILBOX_LIST_FAST_FLAGS) == 0) {
if (stat(ctx->istorage->inbox_path, &st) < 0)
ctx->list.flags |= MAILBOX_UNMARKED;
else
ctx->list.flags |= STAT_GET_MARKED(st);
}
ctx->list.name = "INBOX";
return &ctx->list;
}
static struct mailbox_list *mbox_list_path(struct mbox_list_context *ctx)
{
ctx->next = mbox_list_next;
ctx->list.flags = MAILBOX_NOSELECT | MAILBOX_CHILDREN;
ctx->list.name = p_strconcat(ctx->list_pool, ctx->prefix,
ctx->dir->virtual_path, "/", NULL);
if (imap_match(ctx->glob, ctx->list.name) > 0)
return list_fix_name(ctx);
else
return ctx->next(ctx);
}
static struct mailbox_list *mbox_list_next(struct mbox_list_context *ctx)
{
struct list_dir_context *dir;
struct dirent *d;
int ret;
p_clear(ctx->list_pool);
while (ctx->dir != NULL) {
/* NOTE: list_file() may change ctx->dir */
while ((d = readdir(ctx->dir->dirp)) != NULL) {
t_push();
ret = list_file(ctx, d->d_name);
t_pop();
if (ret > 0)
return list_fix_name(ctx);
if (ret < 0) {
ctx->failed = TRUE;
return NULL;
}
}
dir = ctx->dir;
ctx->dir = dir->prev;
list_dir_context_free(dir);
}
/* finished */
return NULL;
}
static struct mailbox_list *
mbox_list_none(struct mbox_list_context *ctx __attr_unused__)
{
return NULL;
}