mailbox-list.c revision a41f6dd2a2434206d2a445ac73c79861d3496a2a
/* Copyright (c) 2006-2015 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "array.h"
#include "abspath.h"
#include "ioloop.h"
#include "mkdir-parents.h"
#include "str.h"
#include "sha1.h"
#include "hash.h"
#include "home-expand.h"
#include "time-util.h"
#include "unichar.h"
#include "settings-parser.h"
#include "iostream-ssl.h"
#include "fs-api-private.h"
#include "imap-utf7.h"
#include "mailbox-log.h"
#include "mailbox-tree.h"
#include "mail-storage-private.h"
#include "mail-storage-hooks.h"
#include "mailbox-list-private.h"
#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <dirent.h>
/* 16 * (255+1) = 4096 which is the standard PATH_MAX. Having these settings
prevents malicious user from creating eg. "a/a/a/.../a" mailbox name and
then start renaming them to larger names from end to beginning, which
eventually would start causing the failures when trying to use too
long mailbox names. 255 is the standard single directory name length, so
allow up to that high. */
#define MAILBOX_MAX_HIERARCHY_LEVELS 16
#define MAILBOX_MAX_HIERARCHY_NAME_LENGTH 255
#define MAILBOX_LIST_FS_CONTEXT(obj) \
struct mailbox_list_fs_context {
union fs_api_module_context module_ctx;
struct mailbox_list *list;
};
struct mailbox_list_module_register mailbox_list_module_register = { 0 };
void mailbox_lists_init(void)
{
}
void mailbox_lists_deinit(void)
{
}
{
const struct mailbox_list *const *drivers;
unsigned int i, count;
for (i = 0; i < count; i++) {
*idx_r = i;
return TRUE;
}
}
return FALSE;
}
{
unsigned int idx;
i_fatal("mailbox_list_register(%s): duplicate driver",
}
}
{
unsigned int idx;
i_fatal("mailbox_list_unregister(%s): unknown driver",
}
}
const struct mailbox_list *
mailbox_list_find_class(const char *driver)
{
const struct mailbox_list *const *class_p;
unsigned int idx;
return NULL;
return *class_p;
}
const struct mailbox_list_settings *set,
enum mailbox_list_flags flags,
{
const struct mailbox_list *const *class_p;
struct mailbox_list *list;
unsigned int idx;
(flags & MAILBOX_LIST_FLAG_SECONDARY) != 0);
*error_r = "Unknown driver name";
return -1;
}
*error_r = "maildir_name not supported by this driver";
return -1;
}
*error_r = "alt_dir not supported by this driver";
return -1;
}
/* copy settings */
}
} else {
}
return -1;
}
}
i_debug("%s: root=%s, index=%s, indexpvt=%s, control=%s, inbox=%s, alt=%s",
}
if ((flags & MAILBOX_LIST_FLAG_SECONDARY) == 0)
return 0;
}
{
if (!expand_home) {
/* no ~ expansion */
if (home_try_expand(&path) < 0) {
*error_r = t_strconcat(
"No home directory for system user. "
" for ", NULL);
return -1;
}
} else {
*error_r = "Home directory not set for user. "
"Can't expand ~/ for ";
return -1;
}
}
return 0;
}
static const char *split_next_arg(const char *const **_args)
{
args++;
args++;
/* string ends with ":", just ignore it. */
break;
}
args++;
}
return str;
}
static int
bool expand_home,
struct mailbox_list_settings *set_r,
const char **error_r)
{
if (*data == '\0')
return 0;
/* <root dir> */
return -1;
}
/* probably mbox user trying to avoid root_dir */
return -1;
}
continue;
}
value = "";
} else {
value++;
}
continue;
} else {
return -1;
}
return -1;
}
}
return 0;
}
struct mailbox_list_settings *set_r,
const char **error_r)
{
}
enum mailbox_list_path_type type)
{
const struct mail_storage_settings *mail_set;
struct mailbox_list_settings set;
if (*location == SETTING_STRVAR_EXPANDED[0]) {
/* set using -o or userdb lookup. */
return "";
}
location++;
if (*location == '\0') {
if (*location == SETTING_STRVAR_EXPANDED[0])
return "";
location++;
}
/* type:settings */
if (p == NULL)
return "";
return "";
return "";
return path;
}
{
unsigned int len;
if (vname[0] == '.') {
return TRUE; /* "." */
return TRUE; /* ".." */
}
if (*maildir_name != '\0') {
return TRUE; /* e.g. dbox-Mails */
}
return FALSE;
}
static const char *
{
/* no escaping of namespace prefix */
}
/* escape the mailbox name */
if (*vname == '~') {
vname++;
}
*vname == '/' ||
(dirstart &&
} else {
}
}
return str_c(escaped_name);
}
static int
{
unsigned char chr;
return 0;
while (*src != '\0') {
else
return -1;
else
return -1;
src += 3;
} else {
}
}
*dest++ = '\0';
return 0;
}
{
char *ret, *p;
for (p = ret; *p != '\0'; p++) {
if (*p == src)
*p = dest;
}
return ret;
}
const char *vname)
{
const char *storage_name = vname;
storage_name = "INBOX";
/* skip namespace prefix, except if this is INBOX */
/* trying to access the namespace prefix itself */
storage_name = "";
} else {
/* we're converting a nonexistent mailbox name,
such as a LIST pattern. */
}
}
/* UTF-8 -> mUTF-7 conversion */
}
/* opening shared/$user. it's the same as INBOX. */
storage_name = "INBOX";
}
/* shared namespace root. the backend storage's
hierarchy separator isn't known yet, so do
nothing. */
return storage_name;
}
/* no need to convert broken chars */
return storage_name;
} else {
}
}
}
return ret;
}
const char *vname)
{
}
static const char *
{
unsigned int num;
}
else
num *= 16;
else
src += 2;
else
}
}
static void
{
unsigned int i;
char buf[3];
return;
i += 2;
}
}
}
static void
{
str_truncate(str, 0);
(unsigned char)*vname);
} else {
}
}
}
const char *storage_name)
{
unsigned int i, prefix_len, name_len;
const char *vname = storage_name;
/* user's INBOX - use as-is. NOTE: don't do case-insensitive
comparison, otherwise we can't differentiate between INBOX
and <ns prefix>/inBox. */
return vname;
}
/* convert to shared/$user, we don't really care about the
INBOX suffix here. */
vname = "";
}
if (*vname == '\0') {
/* return namespace prefix without the separator */
else {
}
/* mUTF-7 -> UTF-8 conversion */
}
}
return prefix_len == 0 ? vname :
}
/* @UNSAFE */
for (i = 0; i < name_len; i++) {
ret[i + prefix_len] =
}
}
return vname;
}
{
}
{
}
}
{
}
const struct mailbox_list_settings *
{
}
{
}
struct mail_namespace *
{
}
{
/* add the execute bit if either read or write bit is set */
return mode;
}
struct mail_user *
{
}
static int
struct mail_storage **storage_r)
{
struct mail_storage *const *storagep;
return 0;
}
}
data = "";
else
data++;
"Namespace %s: Failed to create storage '%s': %s",
return -1;
}
return 0;
}
struct mail_storage **storage_r)
{
const struct mailbox_settings *set;
}
return 0;
}
struct mail_storage **storage)
{
}
{
}
static void ATTR_NULL(2)
const char *name,
struct mailbox_permissions *permissions_r)
{
/* use safe defaults */
&path) < 0)
}
&path);
}
/* no filesystem support in storage */
path);
i_debug("Namespace %s: %s doesn't exist yet, "
"using default permissions",
}
/* return parent mailbox */
if (p == NULL) {
/* return root defaults */
parent_name = NULL;
} else {
}
return;
}
/* assume current defaults for mailboxes that don't exist or
can't be looked up for some other reason */
} else {
/* we're getting permissions from a file.
apply +x modes as necessary. */
}
/* directory's GID is used automatically for new
files */
/* group has same permissions as world, so don't bother
changing it */
/* using our own gid, no need to change it */
} else {
}
/* we need to stat() the parent directory to see if
it has setgid-bit set */
t_strdup_until(path, p);
if (parent_path != NULL &&
/* directory's GID is used automatically for
new files */
}
}
}
}
i_debug("Namespace %s: Using permissions from %s: "
(int)permissions_r->dir_create_mode,
}
}
struct mailbox_permissions *permissions_r)
{
}
struct mailbox_permissions *permissions_r)
{
else {
}
}
static const char *
const char *expanded_full)
{
const char *ret;
unsigned int i, slash_count = 0, slash2_count = 0;
/* get the expanded path up to the same amount of '/' characters.
if there isn't the same amount of '/' characters, it means %variable
expansion added more of them and we can't handle this. */
for (i = 0; unexpanded_start+i != unexpanded_stop; i++) {
if (unexpanded_start[i] == '/')
slash_count++;
}
for (; unexpanded_start[i] != '\0'; i++) {
if (unexpanded_start[i] == '/')
slash2_count++;
}
for (i = 0; expanded_full[i] != '\0'; i++) {
if (expanded_full[i] == '/') {
if (slash_count == 0)
break;
slash_count--;
}
}
if (slash_count != 0)
return "";
for (; expanded_full[i] != '\0'; i++) {
if (expanded_full[i] == '/') {
if (slash2_count == 0)
return "";
slash2_count--;
}
}
if (slash2_count != 0)
return "";
return ret;
}
static int
enum mailbox_list_path_type type,
struct mailbox_permissions *perm,
const char **error_r)
{
/* get the directory path up to last %variable. for example
/* home directory used */
i_unreached();
} else if (p == NULL) {
return 0;
} else {
while (p != unexpanded && *p != '/') p--;
if (p == unexpanded)
return 0;
i_unreached();
if (*expanded == '\0')
return 0;
}
/* get the first existing parent directory's permissions */
return -1;
}
/* if the parent directory doesn't have setgid-bit enabled, we don't
copy any permissions from it. */
return 0;
if (!home) {
the parent directory. we never want to create the %n
directory itself. */
/* this is the %n directory */
} else {
"mkdir(%s) failed: %m", expanded);
return -1;
}
}
/* change the group for user directories */
}
} else {
/* when using %h and the parent has setgid-bit,
copy the permissions from it for the home we're creating */
}
return 0;
}
enum mailbox_list_path_type type,
const char **error_r)
{
const char *root_dir;
struct mailbox_permissions perm;
/* looks like it already exists, don't bother checking
further. */
return 0;
}
i_unreached();
/* creating a subdirectory under an already existing root dir.
use the root's permissions */
} else {
return -1;
}
/* the rest of the directories exist only for one user. create them
with default directory permissions */
perm.file_create_gid_origin) < 0 &&
else
return -1;
}
return 0;
}
enum mailbox_list_path_type type)
{
const char *error;
return -1;
}
if (type == MAILBOX_LIST_PATH_TYPE_INDEX)
return 0;
}
static bool
const char **error_r)
{
bool ret, allow_internal_dirs;
return TRUE;
/* make sure it's not absolute path */
if (*name == '/') {
*error_r = "Begins with '/'";
return FALSE;
}
if (*name == '~') {
*error_r = "Begins with '~'";
return FALSE;
}
/* make sure the mailbox name doesn't contain any foolishness:
"../" could give access outside the mailbox directory.
"./" and "//" could fool ACL checks.
some mailbox formats have reserved directory names, such as
mailbox directory name, it's not valid. maildir++ is kludged here as
a special case because all of its mailbox dirs begin with "." */
T_BEGIN {
const char *const *names;
const char *n = *names;
if (*n == '\0') {
*error_r = "Has adjacent '/' chars";
break; /* // */
}
if (*n == '.') {
if (n[1] == '\0') {
*error_r = "Contains '.' part";
break; /* ./ */
}
if (n[1] == '.' && n[2] == '\0') {
*error_r = "Contains '..' part";
break; /* ../ */
}
}
/* don't allow maildir_name to be used as part
of the mailbox name */
*error_r = "Contains reserved name";
break;
}
if (!allow_internal_dirs &&
*error_r = "Contains reserved name";
break;
}
}
} T_END;
return ret;
}
{
if (*name == '\0') {
/* an ugly way to get to mailbox root (e.g. Maildir/
when it's not the INBOX) */
return TRUE;
}
*error_r = "Name is empty";
return FALSE;
}
/* either the list backend uses '/' as the hierarchy separator or
it doesn't use filesystem at all (PROP_NO_ROOT) */
*error_r = "Name must not have '/' characters";
return FALSE;
}
}
enum mailbox_list_path_type type,
const char **path_r)
{
int ret;
else
return ret;
}
enum mailbox_list_path_type type,
const char **path_r)
{
int ret;
i_unreached();
if (ret == 0)
else
return ret > 0;
}
enum mailbox_list_path_type type)
{
const char *path;
i_unreached();
return path;
}
enum mailbox_list_path_type type,
const char **path_r)
{
switch (type) {
break;
break;
else {
}
break;
}
break;
break;
/* in-memory indexes */
return 0;
}
} else {
}
break;
break;
}
}
{
}
{
}
{
/* the default implementation: */
if (*ref != '\0') {
/* merge reference and pattern */
}
return pattern;
}
{
struct mailbox_list_iterate_context *iter;
const char *pattern;
int ret;
if (mailbox_list_iter_deinit(&iter) < 0)
ret = -1;
return ret;
}
enum mailbox_info_flags *flags_r)
{
unsigned int len;
*flags_r = 0;
/* special handling for INBOX, mainly because with Maildir++
layout it needs to check if the cur/ directory exists,
which the Maildir++ layout backend itself can't do.. */
enum mailbox_existence existence;
int ret;
/* kludge: with imapc backend we can get here with
list=Maildir++ (for indexes), but list->ns->list=imapc */
if (ret < 0) {
const char *errstr;
enum mail_error error;
/* internal error or with imapc we can get here with
login failures */
}
mailbox_free(&box);
if (ret < 0)
return -1;
switch (existence) {
case MAILBOX_EXISTENCE_NONE:
return 0;
case MAILBOX_EXISTENCE_SELECT:
break;
}
return 1;
}
/* can't do this optimized. do it the slow way. */
struct mailbox_list_iterate_context *iter;
const struct mailbox_info *info;
const char *vname;
else
return mailbox_list_iter_deinit(&iter);
}
i_unreached();
dir = "/";
} else {
fname++;
}
/* looking up a regular mailbox under mail root dir */
/* looking up INBOX that's elsewhere */
} else {
/* looking up the root dir itself */
fname = "";
}
/* if INBOX is in e.g. ~/Maildir, it shouldn't be possible to
access it also via namespace prefix. */
&inbox) <= 0)
i_unreached();
return 0;
}
}
flags_r);
}
{
struct mailbox_permissions perm;
const char *path;
return TRUE;
/* don't do this in mailbox_list_create(), because _get_path() might be
overridden by storage (mbox). */
return FALSE;
return TRUE;
}
{
int ret;
if (list->index_root_dir_created)
return 1;
/* if index root dir hasn't been created yet, do it now */
&index_dir);
if (ret <= 0)
return ret;
&root_dir);
if (ret <= 0)
return ret;
return -1;
}
return 1;
}
enum mailbox_log_record_type type,
const guid_128_t mailbox_guid)
{
struct mailbox_log_record rec;
if (!mailbox_list_init_changelog(list) ||
return;
if (mailbox_list_mkdir_missing_index_root(list) <= 0)
return;
}
{
int ret;
/* make sure we'll refresh the file on next list */
return ret;
return 0;
}
{
const char *error;
"Invalid mailbox name");
return -1;
}
}
{
const char *error;
"Invalid mailbox name");
return -1;
}
}
{
unsigned char sha[SHA1_RESULTLEN];
}
{
}
{
}
{
return TRUE;
levels++;
level_len = 0;
} else {
level_len++;
}
}
return TRUE;
return TRUE;
return FALSE;
}
{
enum mailbox_list_file_type type;
#ifdef HAVE_DIRENT_D_TYPE
switch (d->d_type) {
case DT_UNKNOWN:
break;
case DT_REG:
break;
case DT_DIR:
break;
case DT_LNK:
break;
default:
break;
}
#else
#endif
return type;
}
const char *dir_path,
const struct dirent *d)
{
int ret;
return 1;
T_BEGIN {
"lstat(%s) failed: %m", path);
ret = -1;
ret = 0;
ret = -1;
} else {
/* it's an alias only if it points to the same
directory */
}
} T_END;
return ret;
}
static bool
{
/* ~/dir - use the configured home directory */
return FALSE;
} else {
if (home_try_expand(name) < 0)
return FALSE;
}
return TRUE;
}
const char **name)
{
unsigned int len;
return FALSE;
if (**name == '~') {
/* try to expand home directory */
/* fallback to using actual "~name" mailbox */
return FALSE;
}
} else {
if (**name != '/')
return FALSE;
}
/* okay, we have an absolute path now. but check first if it points to
same directory as one of our regular mailboxes. */
&path) <= 0)
return FALSE;
/* yeah, we can replace the full path with mailbox
name. this way we can use indexes. */
*name = mailbox_name;
return FALSE;
}
}
return TRUE;
}
enum mail_error *error_r)
{
"Unknown internal list error";
}
{
}
{
}
{
const char *str;
}
{
/* critical errors may contain sensitive data, so let user
see only "Internal error" with a timestamp to make it
easier to look from log files the actual error message. */
}
{
const char *error_string;
enum mail_error error;
return FALSE;
return TRUE;
}
{
struct fs_settings fs_set;
struct ssl_iostream_settings ssl_set;
struct mailbox_list_fs_context *ctx;
return -1;
/* add mailbox_list context to the parent fs, which allows
mailbox_list_fs_get_list() to work */
return 0;
}
{
struct mailbox_list_fs_context *ctx;
}