/* Copyright (c) 2006-2018 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 "hex-binary.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>
struct mailbox_list_fs_context {
};
void mailbox_lists_init(void)
{
}
void mailbox_lists_deinit(void)
{
}
{
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 *
{
unsigned int idx;
return NULL;
return *class_p;
}
const struct mailbox_list_settings *set,
enum mailbox_list_flags flags,
{
(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;
}
if (set->no_noselect)
/* 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;
}
{
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;
continue;
continue;
continue;
} else {
return -1;
}
return -1;
}
}
if (set_r->iter_from_index_dir &&
*error_r = "ITERINDEX requires INDEX to be explicitly set";
return -1;
}
/* non-default LISTINDEX directory */
*error_r = "LISTINDEX directory is relative but INDEX=MEMORY";
return -1;
}
}
return 0;
}
struct mailbox_list_settings *set_r,
const char **error_r)
{
}
enum mailbox_list_path_type type)
{
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)
{
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;
return;
i += 2;
}
}
}
static void
{
str_truncate(str, 0);
(unsigned char)*vname);
} else {
}
}
}
const char *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)
{
return 0;
}
}
data = "";
"Namespace %s: Failed to create storage '%s': %s",
return -1;
}
return 0;
}
struct mail_storage **storage_r)
{
}
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 bool
struct mailbox_permissions *permissions_r)
{
path);
i_debug("Namespace %s: %s doesn't exist yet, "
"using default permissions",
}
return FALSE;
}
/* 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 */
}
}
return TRUE;
}
static void ATTR_NULL(2)
const char *name,
struct mailbox_permissions *permissions_r)
{
/* use safe defaults */
/* a) iterating from index dir. Use the index dir's permissions
as well, since they might be in a faster storage.
b) mail files don't exist in storage, but index files
might. */
}
&path) < 0)
}
&path);
}
/* no filesystem support in storage */
/* got permissions from the given path */
/* path couldn't be stat()ed, try parent mailbox */
if (p == NULL) {
/* return root defaults */
parent_name = NULL;
} else {
}
return;
} else {
/* assume current defaults for mailboxes that don't exist or
can't be looked up for some other reason */
}
}
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 {
}
}
const struct mailbox_permissions *src,
{
}
static const char *
const char *expanded_full)
{
const char *ret;
/* 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;
/* looks like it already exists, don't bother checking
further. */
"Root directory is a file: %s", path);
return -1;
}
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)
{
return TRUE;
/* 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;
}
/* 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. */
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;
}
}
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;
break;
}
/* relative path */
i_unreached();
break;
}
/* fall through - default to index directory */
break;
}
/* fall through */
/* in-memory indexes */
return 0;
}
} else {
}
break;
break;
}
}
{
}
{
}
{
/* the default implementation: */
if (*ref != '\0') {
/* merge reference and pattern */
}
return pattern;
}
{
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.. */
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;
/* 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. */
const char *vname;
else
return mailbox_list_iter_deinit(&iter);
}
i_unreached();
} else {
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);
}
{
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;
}
{
const char *index_dir;
if (list->index_root_dir_created)
return 1;
/* If index root dir hasn't been created yet, do it now.
Do this here even if the index directory is the same as mail root
directory, because it may not have been created elsewhere either. */
&index_dir))
return 0;
return -1;
return 1;
}
{
const char *index_dir;
/* LISTINDEX points outside the index root directory */
return 1;
&index_dir))
return 0;
return -1;
return 1;
}
enum mailbox_log_record_type type,
const guid_128_t mailbox_guid)
{
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;
}
}
{
}
{
}
{
}
{
#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;
return FALSE;
return TRUE;
}
{
if (err->last_error_is_internal)
}
{
}
{
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;
}
{
}
{
if (list->lock_refcount > 0) {
list->lock_refcount++;
return 0;
}
/* Use VOLATILEDIR. It's shared with all mailbox_lists, so use
hash of the namespace prefix as a way to make this lock name
unique across the namespaces. */
&lock_dir)) {
/* use index root directory */
if (mailbox_list_mkdir_missing_index_root(list) < 0)
return -1;
&lock_dir)) {
/* use mailbox root directory */
return -1;
} else {
/* No filesystem used by mailbox list (e.g. imapc).
Just assume it's locked */
return 0;
}
"Couldn't create mailbox list lock %s: %s",
return -1;
}
return 0;
}
{
if (--list->lock_refcount > 0)
return;
}