mailbox-list.c revision 7d2d0ae1131c8b632cc7e86000adaaf8c9ef42a9
/* Copyright (c) 2006-2017 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "array.h"
#include "path-util.h"
#include "ioloop.h"
#include "file-create-locked.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>
#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 *class;
struct mailbox_list *list;
(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;
*error_r = "BROKENCHAR value must be a single character";
return -1;
}
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;
}
{
if (vname[0] == '.') {
return TRUE; /* "." */
return TRUE; /* ".." */
}
if (*maildir_name != '\0') {
return TRUE; /* e.g. dbox-Mails */
}
return FALSE;
}
const char *
const char *maildir_name)
{
/* no escaping of namespace prefix */
vname += ns_prefix_len;
}
/* escape the mailbox name */
if (*vname == '~') {
vname++;
}
*vname == escape_char ||
*vname == '/' ||
(dirstart &&
escape_char, *vname);
} else {
}
}
return str_c(escaped_name);
}
const char *
{
}
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)
{
}
const char *
{
unsigned int num;
src += ns_prefix_len;
}
if (*src == escape_char &&
else
num *= 16;
else
src += 2;
else
}
}
const char *
{
}
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)
{
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 = "";
"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)
{
}
{
/* the current API doesn't allow returning an error, so imap code
looks at the list's last error. make sure the error is cleared
so the error-check doesn't return something irrelevant */
}
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)
{
*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;
}
{
if (list->index_root_dir_created)
return 1;
/* if index root dir hasn't been created yet, do it now */
&index_dir))
return 0;
&root_dir))
return 0;
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];
}
{
}
{
}
{
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)
{
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";
}
{
}
enum mail_error *error_r)
{
if (list->last_error_is_internal) {
return list->last_internal_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 */
/* a bit kludgy notification to the fs that we're now finished setting
up the module context. */
(void)fs_get_properties(*fs_r);
return 0;
}
{
struct mailbox_list_fs_context *ctx;
}