view src/lib-storage/index/mbox/mbox-storage.c @ 947:f5658274245f HEAD

If ~/mail isn't found, create it.
author Timo Sirainen <tss@iki.fi>
date Sat, 11 Jan 2003 17:44:16 +0200
parents fd8888f6f037
children 8028c4dcf38f
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "unlink-directory.h"
#include "subscription-file/subscription-file.h"
#include "mail-custom-flags.h"
#include "mbox-index.h"
#include "mbox-lock.h"
#include "mbox-storage.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define CREATE_MODE 0770 /* umask() should limit it more */

extern struct mail_storage mbox_storage;
extern struct mailbox mbox_mailbox;

static int mkdir_parents(const char *path)
{
	const char *p, *dir;

	p = path;
	if (*p == '/') p++;

	do {
		t_push();

		p = strchr(p, '/');
		if (p == NULL)
			dir = path;
		else {
			dir = t_strdup_until(path, p);
			p++;
		}

		if (mkdir(dir, CREATE_MODE) < 0 && errno != EEXIST) {
			t_pop();
			return -1;
		}

		t_pop();
	} while (p != NULL);

	return 0;
}

static int mbox_autodetect(const char *data)
{
	const char *path;
	struct stat st;

	/* Is it INBOX file? */
	if (*data != '\0' && stat(data, &st) == 0 && !S_ISDIR(st.st_mode) &&
	    access(data, R_OK|W_OK) == 0)
		return TRUE;

	/* or directory for IMAP folders? */
	path = t_strconcat(data, "/.imap", NULL);
	if (stat(path, &st) == 0 && S_ISDIR(st.st_mode) &&
	    access(path, R_OK|W_OK|X_OK) == 0)
		return TRUE;

	path = t_strconcat(data, "/inbox", NULL);
	if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode) &&
	    access(path, R_OK|W_OK) == 0)
		return TRUE;

	path = t_strconcat(data, "/mbox", NULL);
	if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode) &&
	    access(path, R_OK|W_OK) == 0)
		return TRUE;

	return FALSE;
}

static const char *get_root_dir(void)
{
	const char *home, *path;

	if (mbox_autodetect(""))
		return "/";

	home = getenv("HOME");
	if (home != NULL) {
		path = t_strconcat(home, "/mail", NULL);
		if (access(path, R_OK|W_OK|X_OK) == 0)
			return path;

		path = t_strconcat(home, "/Mail", NULL);
		if (access(path, R_OK|W_OK|X_OK) == 0)
			return path;
	}

	return NULL;
}

static const char *create_root_dir(void)
{
	const char *home, *path;

	home = getenv("HOME");
	if (home == NULL) {
		i_error("mbox: We need root IMAP folder, "
			"but can't find it or HOME environment");
		return NULL;
	}

	path = t_strconcat(home, "/mail", NULL);
	if (mkdir(path, CREATE_MODE) < 0) {
		i_error("mbox: Can't create root IMAP folder %s: %m", path);
		return NULL;
	}

	return path;
}

static struct mail_storage *mbox_create(const char *data, const char *user)
{
	struct mail_storage *storage;
	const char *root_dir, *inbox_file, *index_dir, *p;
	struct stat st;

	root_dir = inbox_file = index_dir = NULL;

	if (data == NULL || *data == '\0') {
		/* 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 */
		root_dir = get_root_dir();
	} else {
		/* <root folder> | <INBOX path>
		   [:INBOX=<path>] [:INDEX=<dir>] */
		p = strchr(data, ':');
		if (p == NULL) {
			if (stat(data, &st) < 0) {
				i_error("Invalid mbox file %s: %m", data);
				return NULL;
			}

			if (S_ISDIR(st.st_mode))
				root_dir = data;
			else {
				root_dir = get_root_dir();
				inbox_file = data;
			}
		} else {
			root_dir = t_strdup_until(data, p);
			do {
				p++;
				if (strncmp(p, "INBOX=", 6) == 0)
					inbox_file = t_strcut(p+6, ':');
				else if (strncmp(p, "INDEX=", 6) == 0)
					index_dir = t_strcut(p+6, ':');
				p = strchr(p, ':');
			} while (p != NULL);
		}
	}

	if (root_dir == NULL) {
		root_dir = create_root_dir();
		if (root_dir == NULL)
			return NULL;
	}

	if (inbox_file == NULL)
		inbox_file = t_strconcat(root_dir, "/inbox", NULL);
	if (index_dir == NULL)
		index_dir = root_dir;

	storage = i_new(struct mail_storage, 1);
	memcpy(storage, &mbox_storage, sizeof(struct mail_storage));

	storage->dir = i_strdup(root_dir);
	storage->inbox_file = i_strdup(inbox_file);
	storage->index_dir = i_strdup(index_dir);
	storage->user = i_strdup(user);
	storage->callbacks = i_new(struct mail_storage_callbacks, 1);
	return storage;
}

static void mbox_free(struct mail_storage *storage)
{
	i_free(storage->dir);
	i_free(storage->inbox_file);
	i_free(storage->index_dir);
	i_free(storage->user);
	i_free(storage->error);
	i_free(storage->callbacks);
	i_free(storage);
}

int mbox_is_valid_mask(const char *mask)
{
	const char *p;
	int newdir;

	/* make sure there's no "../" or "..\" stuff */
	newdir = TRUE;
	for (p = mask; *p != '\0'; p++) {
		if (newdir && p[0] == '.' && p[1] == '.' &&
		    (p[2] == '/' || p[2] == '\\'))
			return FALSE;
		newdir = p[0] == '/' || p[0] == '\\';
	}

	return TRUE;
}

static int mbox_is_valid_name(struct mail_storage *storage, const char *name)
{
	return name[0] != '\0' && name[0] != storage->hierarchy_sep &&
		name[strlen(name)-1] != storage->hierarchy_sep &&
		strchr(name, '*') == NULL && strchr(name, '%') == NULL &&
		mbox_is_valid_mask(name);
}

static const char *mbox_get_index_dir(struct mail_storage *storage,
				      const char *name)
{
	const char *p;

	p = strrchr(name, '/');
	if (p == NULL)
		return t_strconcat(storage->index_dir, "/.imap/", name, NULL);
	else {
		return t_strconcat(storage->index_dir, t_strdup_until(name, p),
				   "/.imap/", p+1, NULL);
	}
}

static int create_mbox_index_dirs(struct mail_storage *storage,
				  const char *name, int verify)
{
	const char *index_dir, *imap_dir;

	index_dir = mbox_get_index_dir(storage, name);
	imap_dir = t_strdup_until(index_dir, strstr(index_dir, ".imap/") + 5);

	if (mkdir(imap_dir, CREATE_MODE) == -1 && errno != EEXIST)
		return FALSE;
	if (mkdir(index_dir, CREATE_MODE) == -1 && (errno != EEXIST || !verify))
		return FALSE;

	return TRUE;
}

static void verify_inbox(struct mail_storage *storage)
{
	int fd;

	/* make sure inbox file itself exists */
	fd = open(storage->inbox_file, O_RDWR | O_CREAT | O_EXCL, 0660);
	if (fd != -1)
		(void)close(fd);

	/* make sure the index directories exist */
	(void)create_mbox_index_dirs(storage, "INBOX", TRUE);
}

static const char *mbox_get_path(struct mail_storage *storage, const char *name)
{
	if (strcasecmp(name, "INBOX") == 0)
		return storage->inbox_file;
	else
		return t_strconcat(storage->dir, "/", name, NULL);
}

static struct mailbox *mbox_open(struct mail_storage *storage, const char *name,
				 int readonly, int fast)
{
	struct index_mailbox *ibox;
	struct mail_index *index;
	const char *path, *index_dir;

	if (strcasecmp(name, "INBOX") == 0) {
		/* name = "INBOX"
		   path = "<inbox_file>/INBOX"
		   index_dir = "/mail/.imap/INBOX" */
		path = storage->inbox_file;
		index_dir = mbox_get_index_dir(storage, "/INBOX");
	} else {
		/* name = "foo/bar"
		   path = "/mail/foo/bar"
		   index_dir = "/mail/foo/.imap/bar" */
		path = mbox_get_path(storage, name);
		index_dir = mbox_get_index_dir(storage, name);
	}

	index = index_storage_lookup_ref(index_dir);
	if (index == NULL) {
		index = mbox_index_alloc(index_dir, path);
		index_storage_add(index);
	}

	ibox = index_storage_init(storage, &mbox_mailbox, index,
				  name, readonly, fast);
	if (ibox != NULL) {
		ibox->expunge_locked = mbox_expunge_locked;
		index_mailbox_check_add(ibox, index->mailbox_path);
	}
	return (struct mailbox *) ibox;
}

static struct mailbox *
mbox_open_mailbox(struct mail_storage *storage,
		  const char *name, int readonly, int fast)
{
	const char *path;
	struct stat st;

	mail_storage_clear_error(storage);

	/* INBOX is always case-insensitive */
	if (strcasecmp(name, "INBOX") == 0) {
		/* make sure inbox exists */
		verify_inbox(storage);
		return mbox_open(storage, "INBOX", readonly, fast);
	}

	if (!mbox_is_valid_name(storage, name)) {
		mail_storage_set_error(storage, "Invalid mailbox name");
		return FALSE;
	}

	path = mbox_get_path(storage, name);
	if (stat(path, &st) == 0) {
		if (S_ISDIR(st.st_mode)) {
			mail_storage_set_error(storage,
				"Mailbox isn't selectable: %s", name);
			return NULL;
		}

		/* exists - make sure the required directories are also there */
		(void)create_mbox_index_dirs(storage, name, TRUE);

		return mbox_open(storage, name, readonly, fast);
	} else if (errno == ENOENT) {
		mail_storage_set_error(storage, "Mailbox doesn't exist: %s",
				       name);
		return NULL;
	} else {
		mail_storage_set_critical(storage, "Can't open mailbox %s: %m",
					  name);
		return NULL;
	}
}

static int mbox_create_mailbox(struct mail_storage *storage, const char *name)
{
	const char *path, *p;
	struct stat st;
	int fd;

	mail_storage_clear_error(storage);

	if (strcasecmp(name, "INBOX") == 0)
		name = "INBOX";

	if (!mbox_is_valid_name(storage, name)) {
		mail_storage_set_error(storage, "Invalid mailbox name");
		return FALSE;
	}

	/* make sure it doesn't exist already */
	path = mbox_get_path(storage, name);
	if (stat(path, &st) == 0) {
		mail_storage_set_error(storage, "Mailbox already exists");
		return FALSE;
	}

	if (errno == ENOTDIR) {
		mail_storage_set_error(storage,
			"Mailbox doesn't allow inferior mailboxes");
		return FALSE;
	}

	if (errno != ENOENT) {
		mail_storage_set_critical(storage,
			"stat() failed for mbox file %s: %m", path);
		return FALSE;
	}

	/* create the hierarchy if needed */
	p = strrchr(path, '/');
	if (p != NULL) {
		if (mkdir_parents(t_strdup_until(path, p)) < 0) {
			mail_storage_set_critical(storage,
				"mkdir_parents() failed for mbox path %s: %m",
				path);
			return FALSE;
		}
	}

	/* create the mailbox file */
	fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0660);
	if (fd != -1) {
		(void)close(fd);
		return TRUE;
	} else if (errno == EEXIST) {
		/* mailbox was just created between stat() and open() call.. */
		mail_storage_set_error(storage, "Mailbox already exists");
		return FALSE;
	} else {
		mail_storage_set_critical(storage, "Can't create mailbox "
					  "%s: %m", name);
		return FALSE;
	}
}

static int mbox_delete_mailbox(struct mail_storage *storage, const char *name)
{
	const char *index_dir, *path;
	struct stat st;

	mail_storage_clear_error(storage);

	if (strcasecmp(name, "INBOX") == 0) {
		mail_storage_set_error(storage, "INBOX can't be deleted.");
		return FALSE;
	}

	if (!mbox_is_valid_name(storage, name)) {
		mail_storage_set_error(storage, "Invalid mailbox name");
		return FALSE;
	}

	path = mbox_get_path(storage, name);
	if (lstat(path, &st) < 0) {
		if (errno == ENOENT) {
			mail_storage_set_error(storage,
					       "Mailbox doesn't exist: %s",
					       name);
		} else {
			mail_storage_set_critical(storage, "lstat() failed for "
						  "%s: %m", path);
		}
		return FALSE;
	}

	if (S_ISDIR(st.st_mode)) {
		/* deleting a folder, only allow it if it's empty */
		if (rmdir(path) == 0)
			return TRUE;

		if (errno == ENOTEMPTY) {
			mail_storage_set_error(storage, "Folder %s "
					       "isn't empty, can't delete it.",
					       name);
		} else {
			mail_storage_set_critical(storage, "rmdir() failed for "
						  "%s: %m", path);
		}
		return FALSE;
	}

	/* first unlink the mbox file */
	if (unlink(path) < 0) {
		if (errno == ENOENT) {
			mail_storage_set_error(storage,
					       "Mailbox doesn't exist: %s",
					       name);
		} else {
			mail_storage_set_critical(storage,
						  "unlink() failed for %s: %m",
						  path);
		}
		return FALSE;
	}

	/* next delete the index directory */
	index_dir = mbox_get_index_dir(storage, name);
	if (unlink_directory(index_dir, TRUE) < 0 && errno != ENOENT) {
		mail_storage_set_critical(storage, "unlink_directory(%s) "
					  "failed: %m", index_dir);
		return FALSE;
	}
	return TRUE;
}

static int mbox_rename_mailbox(struct mail_storage *storage,
			       const char *oldname, const char *newname)
{
	const char *oldpath, *newpath, *old_indexdir, *new_indexdir, *p;

	mail_storage_clear_error(storage);

	if (!mbox_is_valid_name(storage, oldname) ||
	    !mbox_is_valid_name(storage, newname)) {
		mail_storage_set_error(storage, "Invalid mailbox name");
		return FALSE;
	}

	if (strcasecmp(oldname, "INBOX") == 0)
		oldname = "INBOX";

	oldpath = mbox_get_path(storage, oldname);
	newpath = mbox_get_path(storage, newname);

	/* create the hierarchy */
	p = strrchr(newpath, '/');
	if (p != NULL) {
		if (mkdir_parents(t_strdup_until(newpath, p)) < 0) {
			mail_storage_set_critical(storage,
				"mkdir_parents() failed for mbox path %s: %m",
				newpath);
			return FALSE;
		}
	}

	/* NOTE: renaming INBOX works just fine with us, it's simply created
	   the next time it's needed. */
	if (link(oldpath, newpath) == 0)
		(void)unlink(oldpath);
	else if (errno == EEXIST) {
		mail_storage_set_error(storage,
				       "Target mailbox already exists");
		return FALSE;
	} else {
		mail_storage_set_critical(storage, "link(%s, %s) failed: %m",
					  oldpath, newpath);
		return FALSE;
	}

	/* we need to rename the index directory as well */
	old_indexdir = mbox_get_index_dir(storage, oldname);
	new_indexdir = mbox_get_index_dir(storage, newname);
	(void)rename(old_indexdir, new_indexdir);

	return TRUE;
}

static int mbox_get_mailbox_name_status(struct mail_storage *storage,
					const char *name,
					enum mailbox_name_status *status)
{
	struct stat st;
	const char *path;

	mail_storage_clear_error(storage);

	if (strcasecmp(name, "INBOX") == 0)
		name = "INBOX";

	if (!mbox_is_valid_name(storage, name)) {
		*status = MAILBOX_NAME_INVALID;
		return TRUE;
	}

	path = mbox_get_path(storage, name);
	if (stat(path, &st) == 0) {
		*status = MAILBOX_NAME_EXISTS;
		return TRUE;
	} else if (errno == ENOENT) {
		*status = MAILBOX_NAME_VALID;
		return TRUE;
	} else if (errno == ENOTDIR) {
		*status = MAILBOX_NAME_NOINFERIORS;
		return TRUE;
	} else {
		mail_storage_set_critical(storage, "mailbox name status: "
					  "stat(%s) failed: %m", path);
		return FALSE;
	}
}

static int mbox_storage_close(struct mailbox *box)
{
	struct index_mailbox *ibox = (struct index_mailbox *) box;
	int failed = FALSE;

	/* update flags by rewrite mbox file */
	if (!mbox_index_rewrite(ibox->index)) {
		mail_storage_set_index_error(ibox);
		failed = TRUE;
	}

	return index_storage_close(box) && !failed;
}

struct mail_storage mbox_storage = {
	"mbox", /* name */

	'/', /* hierarchy_sep - can't be changed */

	mbox_create,
	mbox_free,
	mbox_autodetect,
	index_storage_set_callbacks,
	mbox_open_mailbox,
	mbox_create_mailbox,
	mbox_delete_mailbox,
	mbox_rename_mailbox,
	mbox_find_mailboxes,
	subsfile_set_subscribed,
	mbox_find_subscribed,
	mbox_get_mailbox_name_status,
	mail_storage_get_last_error,

	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL, NULL,

	0
};

struct mailbox mbox_mailbox = {
	NULL, /* name */
	NULL, /* storage */

	mbox_storage_close,
	index_storage_get_status,
	index_storage_sync,
	index_storage_expunge,
	index_storage_update_flags,
	index_storage_copy,
	index_storage_fetch,
	index_storage_search,
	mbox_storage_save,
	mail_storage_is_inconsistency_error,

	FALSE,
	FALSE,
	FALSE
};