view src/lib-storage/mail-namespace.c @ 8085:bf83aa9c3f4a HEAD

Removed pool parameter from mail_namespaces_init*(). Use mail_user's pool intead.
author Timo Sirainen <tss@iki.fi>
date Tue, 12 Aug 2008 14:54:27 -0400
parents db66611fd195
children 6dd0c6755afe
line wrap: on
line source

/* Copyright (c) 2005-2008 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "str.h"
#include "file-lock.h"
#include "mail-storage.h"
#include "mail-namespace.h"

#include <stdlib.h>

void (*hook_mail_namespaces_created)(struct mail_namespace *namespaces);

void mail_namespace_init_storage(struct mail_namespace *ns)
{
	ns->list = mail_storage_get_list(ns->storage);
	ns->prefix_len = strlen(ns->prefix);
	ns->real_sep = mailbox_list_get_hierarchy_sep(ns->list);

	if (ns->sep == '\0')
                ns->sep = ns->real_sep;

	if (ns->sep == '"' || ns->sep == '\\') {
		ns->sep_str[0] = '\\';
		ns->sep_str[1] = ns->sep;
	} else {
		ns->sep_str[0] = ns->sep;
	}
}

static struct mail_namespace *
namespace_add_env(const char *data, unsigned int num,
		  struct mail_user *user, enum mail_storage_flags flags,
		  enum file_lock_method lock_method)
{
        struct mail_namespace *ns;
	const char *sep, *type, *prefix, *error;

	ns = p_new(user->pool, struct mail_namespace, 1);

	sep = getenv(t_strdup_printf("NAMESPACE_%u_SEP", num));
	type = getenv(t_strdup_printf("NAMESPACE_%u_TYPE", num));
	prefix = getenv(t_strdup_printf("NAMESPACE_%u_PREFIX", num));
	if (getenv(t_strdup_printf("NAMESPACE_%u_INBOX", num)) != NULL)
		ns->flags |= NAMESPACE_FLAG_INBOX;
	if (getenv(t_strdup_printf("NAMESPACE_%u_HIDDEN", num)) != NULL)
		ns->flags |= NAMESPACE_FLAG_HIDDEN;
	if (getenv(t_strdup_printf("NAMESPACE_%u_LIST", num)) != NULL)
		ns->flags |= NAMESPACE_FLAG_LIST;
	if (getenv(t_strdup_printf("NAMESPACE_%u_SUBSCRIPTIONS", num)) != NULL)
		ns->flags |= NAMESPACE_FLAG_SUBSCRIPTIONS;

	if (type == NULL || *type == '\0' || strncmp(type, "private", 7) == 0)
		ns->type = NAMESPACE_PRIVATE;
	else if (strncmp(type, "shared", 6) == 0)
		ns->type = NAMESPACE_SHARED;
	else if (strncmp(type, "public", 6) == 0)
		ns->type = NAMESPACE_PUBLIC;
	else {
		i_error("Unknown namespace type: %s", type);
		return NULL;
	}

	if (prefix == NULL)
		prefix = "";

	if ((flags & MAIL_STORAGE_FLAG_DEBUG) != 0) {
		i_info("Namespace: type=%s, prefix=%s, sep=%s, "
		       "inbox=%s, hidden=%s, list=%s, subscriptions=%s",
		       type == NULL ? "" : type, prefix, sep == NULL ? "" : sep,
		       (ns->flags & NAMESPACE_FLAG_INBOX) ? "yes" : "no",
		       (ns->flags & NAMESPACE_FLAG_HIDDEN) ? "yes" : "no",
		       (ns->flags & NAMESPACE_FLAG_LIST) ? "yes" : "no",
		       (ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) ?
		       "yes" : "no");
	}

	if (sep != NULL)
		ns->sep = *sep;
	ns->prefix = p_strdup(user->pool, prefix);
	ns->user = user;

	if (mail_storage_create(ns, NULL, data, flags, lock_method,
				&error) < 0) {
		i_error("Namespace '%s': %s", ns->prefix, error);
		return NULL;
	}
	return ns;
}

static bool namespaces_check(struct mail_namespace *namespaces)
{
	struct mail_namespace *ns, *inbox_ns = NULL, *private_ns = NULL;
	unsigned int private_ns_count = 0;
	unsigned int subscriptions_count = 0;
	char list_sep = '\0';

	for (ns = namespaces; ns != NULL; ns = ns->next) {
		if ((ns->flags & NAMESPACE_FLAG_INBOX) != 0) {
			if (inbox_ns != NULL) {
				i_error("namespace configuration error: "
					"There can be only one namespace with "
					"inbox=yes");
				return FALSE;
			}
			inbox_ns = ns;
		}
		if (ns->type == NAMESPACE_PRIVATE) {
			private_ns = ns;
			private_ns_count++;
		}
		if (*ns->prefix != '\0' &&
		    (ns->flags & NAMESPACE_FLAG_LIST) != 0 &&
		    ns->prefix[strlen(ns->prefix)-1] != ns->sep) {
			i_error("namespace configuration error: "
				"list=yes requires prefix=%s "
				"to end with separator", ns->prefix);
			return FALSE;
		}
		if ((ns->flags & NAMESPACE_FLAG_LIST) != 0) {
			if (list_sep == '\0')
				list_sep = ns->sep;
			else if (list_sep != ns->sep) {
				i_error("namespace configuration error: "
					"All list=yes namespaces must use "
					"the same separator");
				return FALSE;
			}
		}
		if (*ns->prefix == '\0' &&
		    (ns->flags & NAMESPACE_FLAG_LIST) == 0) {
			i_error("namespace configuration error: "
				"Empty prefix requires list=yes");
			return FALSE;
		}
		if ((ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0)
			subscriptions_count++;
	}

	if (inbox_ns == NULL) {
		if (private_ns_count == 1) {
			/* just one private namespace. we'll assume it's
			   the INBOX namespace. */
			private_ns->flags |= NAMESPACE_FLAG_INBOX;
		} else {
			i_error("namespace configuration error: "
				"inbox=yes namespace missing");
			return FALSE;
		}
	}
	if (list_sep == '\0') {
		i_error("namespace configuration error: "
			"no list=yes namespaces");
		return FALSE;
	}
	if (subscriptions_count == 0) {
		i_error("namespace configuration error: "
			"no subscriptions=yes namespaces");
		return FALSE;
	}
	return TRUE;
}

static struct mail_namespace *
namespaces_sort(struct mail_namespace *src)
{
	struct mail_namespace **tmp, *next, *dest = NULL;

	for (; src != NULL; src = next) {
		next = src->next;

		for (tmp = &dest; *tmp != NULL; tmp = &(*tmp)->next) {
			if (strlen(src->prefix) < strlen((*tmp)->prefix))
				break;
		}
		src->next = *tmp;
		*tmp = src;
	}
	return dest;
}

int mail_namespaces_init(struct mail_user *user)
{
	struct mail_namespace *namespaces, *ns, **ns_p;
	enum mail_storage_flags flags;
        enum file_lock_method lock_method;
	const char *mail, *data, *error;
	unsigned int i;

	mail_storage_parse_env(&flags, &lock_method);
        namespaces = NULL; ns_p = &namespaces;

	/* first try NAMESPACE_* environments */
	for (i = 1; ; i++) {
		T_BEGIN {
			data = getenv(t_strdup_printf("NAMESPACE_%u", i));
		} T_END;

		if (data == NULL)
			break;

		T_BEGIN {
			*ns_p = namespace_add_env(data, i, user, flags,
						  lock_method);
		} T_END;

		if (*ns_p == NULL)
			return -1;

		ns_p = &(*ns_p)->next;
	}

	if (namespaces != NULL) {
		if (!namespaces_check(namespaces))
			return -1;
		namespaces = namespaces_sort(namespaces);
		user->namespaces = namespaces;

		if (hook_mail_namespaces_created != NULL) {
			T_BEGIN {
				hook_mail_namespaces_created(namespaces);
			} T_END;
		}
		return 0;
	}

	/* fallback to MAIL */
	mail = getenv("MAIL");
	if (mail == NULL) {
		/* support also maildir-specific environment */
		mail = getenv("MAILDIR");
		if (mail != NULL)
			mail = t_strconcat("maildir:", mail, NULL);
	}

	ns = p_new(user->pool, struct mail_namespace, 1);
	ns->type = NAMESPACE_PRIVATE;
	ns->flags = NAMESPACE_FLAG_INBOX | NAMESPACE_FLAG_LIST |
		NAMESPACE_FLAG_SUBSCRIPTIONS;
	ns->prefix = "";
	ns->user = user;

	if (mail_storage_create(ns, NULL, mail, flags, lock_method,
				&error) < 0) {
		if (mail != NULL && *mail != '\0')
			i_error("mail_location: %s", error);
		else {
			i_error("mail_location not set and "
				"autodetection failed: %s", error);
		}
		return -1;
	}
	user->namespaces = ns;

	if (hook_mail_namespaces_created != NULL) {
		T_BEGIN {
			hook_mail_namespaces_created(ns);
		} T_END;
	}
	return 0;
}

struct mail_namespace *
mail_namespaces_init_empty(struct mail_user *user)
{
	struct mail_namespace *ns;

	ns = p_new(user->pool, struct mail_namespace, 1);
	ns->user = user;
	ns->prefix = "";
	ns->flags = NAMESPACE_FLAG_INBOX | NAMESPACE_FLAG_LIST |
		NAMESPACE_FLAG_SUBSCRIPTIONS;
	user->namespaces = ns;
	return ns;
}

void mail_namespaces_deinit(struct mail_namespace **_namespaces)
{
	struct mail_namespace *namespaces = *_namespaces;

	*_namespaces = NULL;
	while (namespaces != NULL) {
		if (namespaces->storage != NULL)
			mail_storage_destroy(&namespaces->storage);
		namespaces = namespaces->next;
	}
}

const char *mail_namespace_fix_sep(struct mail_namespace *ns, const char *name)
{
	char *ret, *p;

	if (ns->sep == ns->real_sep)
		return name;

	ret = p_strdup(unsafe_data_stack_pool, name);
	for (p = ret; *p != '\0'; p++) {
		if (*p == ns->sep)
			*p = ns->real_sep;
	}
	return ret;
}

const char *mail_namespace_get_vname(struct mail_namespace *ns, string_t *dest,
				     const char *name)
{
	str_truncate(dest, 0);
	if ((ns->flags & NAMESPACE_FLAG_INBOX) == 0 ||
	    strcasecmp(name, "INBOX") != 0)
		str_append(dest, ns->prefix);

	for (; *name != '\0'; name++) {
		if (*name == ns->real_sep)
			str_append_c(dest, ns->sep);
		else
			str_append_c(dest, *name);
	}
	return str_c(dest);
}

char mail_namespace_get_root_sep(const struct mail_namespace *namespaces)
{
	while ((namespaces->flags & NAMESPACE_FLAG_LIST) == 0)
		namespaces = namespaces->next;
	return namespaces->sep;
}

static struct mail_namespace *
mail_namespace_find_mask(struct mail_namespace *namespaces,
			 const char **mailbox,
			 enum namespace_flags flags,
			 enum namespace_flags mask)
{
        struct mail_namespace *ns = namespaces;
	const char *box = *mailbox;
	struct mail_namespace *best = NULL;
	size_t best_len = 0;
	bool inbox;

	inbox = strncasecmp(box, "INBOX", 5) == 0;
	if (inbox && box[5] == '\0') {
		/* find the INBOX namespace */
		*mailbox = "INBOX";
		while (ns != NULL) {
			if ((ns->flags & NAMESPACE_FLAG_INBOX) != 0 &&
			    (ns->flags & mask) == flags)
				return ns;
			if (*ns->prefix == '\0')
				best = ns;
			ns = ns->next;
		}
		return best;
	}

	for (; ns != NULL; ns = ns->next) {
		if (ns->prefix_len >= best_len &&
		    (strncmp(ns->prefix, box, ns->prefix_len) == 0 ||
		     (inbox && strncmp(ns->prefix, "INBOX", 5) == 0 &&
		      strncmp(ns->prefix+5, box+5, ns->prefix_len-5) == 0)) &&
		    (ns->flags & mask) == flags) {
			best = ns;
			best_len = ns->prefix_len;
		}
	}

	if (best != NULL) {
		if (best_len > 0)
			*mailbox += best_len;
		else if (inbox && (box[5] == best->sep || box[5] == '\0'))
			*mailbox = t_strconcat("INBOX", box+5, NULL);

		*mailbox = mail_namespace_fix_sep(best, *mailbox);
	}

	return best;
}

struct mail_namespace *
mail_namespace_find(struct mail_namespace *namespaces, const char **mailbox)
{
	return mail_namespace_find_mask(namespaces, mailbox, 0, 0);
}

struct mail_namespace *
mail_namespace_find_visible(struct mail_namespace *namespaces,
			    const char **mailbox)
{
	return mail_namespace_find_mask(namespaces, mailbox, 0,
					NAMESPACE_FLAG_HIDDEN);
}

struct mail_namespace *
mail_namespace_find_subscribable(struct mail_namespace *namespaces,
				 const char **mailbox)
{
	return mail_namespace_find_mask(namespaces, mailbox,
					NAMESPACE_FLAG_SUBSCRIPTIONS,
					 NAMESPACE_FLAG_SUBSCRIPTIONS);
}

struct mail_namespace *
mail_namespace_find_inbox(struct mail_namespace *namespaces)
{
	while ((namespaces->flags & NAMESPACE_FLAG_INBOX) == 0)
		namespaces = namespaces->next;
	return namespaces;
}

bool mail_namespace_update_name(const struct mail_namespace *ns,
				const char **mailbox)
{
	struct mail_namespace tmp_ns = *ns;

	/* FIXME: a bit kludgy.. */
	tmp_ns.next = NULL;
	return mail_namespace_find_mask(&tmp_ns, mailbox, 0, 0) != NULL;
}

struct mail_namespace *
mail_namespace_find_prefix(struct mail_namespace *namespaces,
			   const char *prefix)
{
        struct mail_namespace *ns;
	unsigned int len = strlen(prefix);

	for (ns = namespaces; ns != NULL; ns = ns->next) {
		if (ns->prefix_len == len &&
		    strcmp(ns->prefix, prefix) == 0)
			return ns;
	}
	return NULL;
}

struct mail_namespace *
mail_namespace_find_prefix_nosep(struct mail_namespace *namespaces,
				 const char *prefix)
{
        struct mail_namespace *ns;
	unsigned int len = strlen(prefix);

	for (ns = namespaces; ns != NULL; ns = ns->next) {
		if (ns->prefix_len == len + 1 &&
		    strncmp(ns->prefix, prefix, len) == 0 &&
		    ns->prefix[len] == ns->sep)
			return ns;
	}
	return NULL;
}