acl-backend-vfile.c revision 515d649c1802beb48433b90125518c00d0a1fbb4
/* Copyright (c) 2006-2013 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "bsearch-insert-pos.h"
#include "str.h"
#include "strescape.h"
#include "istream.h"
#include "ostream.h"
#include "file-dotlock.h"
#include "nfs-workarounds.h"
#include "mail-storage-private.h"
#include "mailbox-list-private.h"
#include "mail-namespace.h"
#include "acl-cache.h"
#include "acl-backend-vfile.h"
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <utime.h>
#define ACL_VFILE_DEFAULT_CACHE_SECS 30
#define VALIDITY_MTIME_NOTFOUND 0
#define VALIDITY_MTIME_NOACCESS -1
struct acl_vfile_validity {
};
struct acl_backend_vfile_validity {
struct acl_vfile_validity mailbox_validity;
};
struct acl_letter_map {
char letter;
const char *name;
};
static const struct acl_letter_map acl_letter_map[] = {
{ 'l', MAIL_ACL_LOOKUP },
{ 'r', MAIL_ACL_READ },
{ 'w', MAIL_ACL_WRITE },
{ 's', MAIL_ACL_WRITE_SEEN },
{ 't', MAIL_ACL_WRITE_DELETED },
{ 'i', MAIL_ACL_INSERT },
{ 'p', MAIL_ACL_POST },
{ 'e', MAIL_ACL_EXPUNGE },
{ 'k', MAIL_ACL_CREATE },
{ 'x', MAIL_ACL_DELETE },
{ 'a', MAIL_ACL_ADMIN },
{ '\0', NULL }
};
static struct dotlock_settings dotlock_set = {
.timeout = 30,
.stale_timeout = 120
};
static struct acl_backend *acl_backend_vfile_alloc(void)
{
struct acl_backend_vfile *backend;
}
static int
{
struct acl_backend_vfile *backend =
(struct acl_backend_vfile *)_backend;
const char *const *tmp;
tmp++;
i_error("acl vfile: Invalid cache_secs value: %s",
*tmp + 11);
return -1;
}
} else {
return -1;
}
}
i_debug("acl vfile: Global ACL directory: %s",
}
sizeof(struct acl_backend_vfile_validity));
return 0;
}
{
struct acl_backend_vfile *backend =
(struct acl_backend_vfile *)_backend;
}
}
static const char *
{
enum mailbox_list_path_type type;
if (*name == '\0')
return NULL;
/* ACL files are very important. try to keep them among the main
mail files. that's not possible though with a) if the mailbox is
a file or b) if the mailbox path doesn't point to filesystem. */
return FALSE;
} else {
return NULL;
}
/* verify that the directory isn't same as INBOX's directory.
this is mainly for Maildir. */
MAILBOX_LIST_PATH_TYPE_MAILBOX, &inbox) > 0 &&
/* can't have default ACLs with this setup */
return NULL;
}
return dir;
}
static struct acl_object *
const char *name)
{
struct acl_backend_vfile *backend =
(struct acl_backend_vfile *)_backend;
struct acl_object_vfile *aclobj;
} T_END;
}
static const char *
{
const char *p;
}
static int
struct acl_vfile_validity *validity)
{
/* use the cached value */
}
return 0;
}
return 1;
}
return -1;
}
return 1;
}
static bool
{
struct acl_backend_vfile *backend =
(struct acl_backend_vfile *)_backend;
int ret;
if (old_validity != NULL)
else
/* See if the mailbox exists. If we wanted recursive lookups we could
skip this, but at least for now we assume that if an existing
mailbox has no ACL it's equivalent to default ACLs. */
MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) <= 0)
ret = -1;
else {
}
if (ret == 0 &&
}
}
return ret > 0;
}
static struct acl_object *
const char *child_name)
{
const char *parent;
/* stop at the first parent that
a) has global ACL file
b) has local ACL file
c) exists */
break;
child_name = parent;
}
/* use the root */
}
}
{
}
static const char *const *
bool dup_strings)
{
/* sort the rights first so we can easily drop duplicates */
/* @UNSAFE */
if (count > 0) {
}
if (dup_strings) {
for (i = 0; i < dest; i++)
}
}
return ret;
}
static const char *const *
{
const char *const *names;
unsigned int i;
/* parse IMAP ACL list */
acl++;
break;
}
return NULL;
}
acl++;
}
if (*acl != '\0') {
/* parse our own extended ACLs */
if (*acl != ':') {
*error_r = "Missing ':' prefix in ACL extensions";
return NULL;
}
}
}
}
static int
unsigned int linenum)
{
struct acl_rights rights;
return 0;
/* <id> [<imap acls>] [:<named acls>] */
if (*line == '"') {
if (*p == '\\' && p[1] != '\0')
p++;
else if (*p == '"')
break;
}
if (p[0] != '"' || (p[1] != ' ' && p[1] != '\0')) {
i_error("ACL file %s line %u: Invalid quoted ID",
return -1;
}
p++;
} else {
if (p == NULL)
p = "";
else {
p++;
}
}
if (*line != '-')
else {
line++;
}
return -1;
}
return 0;
}
{
struct acl_rights rights;
}
static int
bool *is_dir_r)
{
const char *line;
unsigned int linenum;
if (fd == -1) {
i_debug("acl vfile: no access to file %s",
path);
} else {
return -1;
}
return 1;
}
i_close_fd(&fd);
return 0;
}
i_close_fd(&fd);
return -1;
}
/* we opened a directory. */
i_close_fd(&fd);
return 0;
}
linenum = 1;
T_BEGIN {
linenum++);
} T_END;
if (ret < 0)
break;
}
if (ret < 0) {
/* parsing failure */
} else if (input->stream_errno != 0) {
ret = 0;
else {
ret = -1;
}
} else {
ret = 0;
else {
ret = -1;
}
} else {
ret = 1;
}
}
return 0;
return -1;
}
return ret;
}
static int
struct acl_vfile_validity *validity)
{
unsigned int i;
int ret;
bool is_dir;
return 0;
for (i = 0;; i++) {
&is_dir);
if (ret != 0)
break;
if (is_dir) {
/* opened a directory. use dir/.DEFAULT instead */
} else {
/* ESTALE - try again */
}
}
return ret <= 0 ? -1 : 0;
}
static int
struct acl_vfile_validity *validity)
{
struct acl_backend_vfile *backend =
int ret;
return 1;
return 0;
/* it's a directory. use dir/.DEFAULT instead */
}
if (ret < 0) {
/* if the file used to exist, we have to re-read it */
}
return -1;
}
/* same timestamp, but if it was modified within the
same second we want to refresh it again later (but
do it only after a couple of seconds so we don't
keep re-reading it all the time within those
seconds) */
if (validity->last_read_time != 0 &&
return 0;
}
return 1;
}
{
struct acl_backend_vfile_validity *validity;
return -1;
else
*mtime_r = 0;
return 0;
}
const struct acl_rights *r2)
{
int ret;
/* globals have higher priority than locals */
}
if (ret != 0)
return ret;
}
static void
bool dup_strings)
{
unsigned int i;
}
}
}
{
struct acl_rights *rights;
return;
/* merge identical identifiers */
/* add i's rights to dest and delete i */
} else {
if (++dest != i)
}
}
}
{
struct acl_rights_update ru;
}
{
struct acl_rights_update ru;
enum acl_modify_mode add_mode;
unsigned int i, count;
bool first_global = TRUE;
return;
/* Rights are sorted by their 1) locals first, globals next,
2) acl_id_type. We'll apply only the rights matching ourself.
replace all of the existing ACLs. Basically this means that if
user belongs to multiple matching groups or group-overrides, their
ACLs are merged. In all other situations the ACLs are replaced
(because there aren't duplicate rights entries and a user can't
match multiple usernames). */
i = 0;
else {
/* we're the owner. skip over all rights entries until we
reach ACL_ID_OWNER or higher, or alternatively when we
reach a global ACL (even ACL_ID_ANYONE overrides owner's
rights if it's global) */
for (i = 0; i < count; i++) {
break;
}
/* now continue applying the rest of the rights,
if there are any */
}
for (; i < count; i++) {
continue;
if (prev_match == NULL ||
/* replace old ACLs */
} else {
/* merging to existing ACLs */
}
prev_match = &rights[i];
/* If [neg_]rights is NULL it needs to be ignored.
The easiest way to do that is to just mark it with
REMOVE mode */
/* first global: reset negative ACLs so local ACLs
can't mess things up via them */
}
}
}
{
struct acl_backend_vfile *backend =
struct acl_backend_vfile_validity *old_validity;
struct acl_backend_vfile_validity validity;
int ret;
if (ret == 0) {
}
if (ret <= 0)
return ret;
/* either global or local ACLs changed, need to re-read both */
} else {
}
&validity.global_validity) < 0)
return -1;
&validity.local_validity) < 0)
return -1;
/* update cache only after we've successfully read everything */
return 0;
}
{
struct mailbox_permissions perm;
int fd;
i_error("Can't update acl object '%s': No local acl file path",
return -1;
}
/* first lock the ACL file */
if (fd == -1) {
return -1;
}
/* locked successfully, re-read the existing file to make sure we
don't lose any changes. */
if (acl_backend_vfile_object_refresh_cache(_aclobj) < 0) {
return -1;
}
return fd;
}
const char *const **rightsp,
const char *const *modify_rights,
enum acl_modify_mode modify_mode)
{
const char *const *old_rights = *rightsp;
const char *const *new_rights = NULL;
unsigned int i, j;
/* nothing to do here */
return FALSE;
}
switch (modify_mode) {
case ACL_MODIFY_MODE_REMOVE:
/* nothing to do */
return FALSE;
}
for (i = 0; old_rights[i] != NULL; i++) {
for (j = 0; modify_rights[j] != NULL; j++) {
break;
}
if (modify_rights[j] == NULL)
}
new_rights = &null;
break;
case ACL_MODIFY_MODE_ADD:
break;
case ACL_MODIFY_MODE_REPLACE:
new_rights = &null;
break;
case ACL_MODIFY_MODE_CLEAR:
/* ACL didn't exist before either */
return FALSE;
}
return TRUE;
}
*rightsp = new_rights;
if (old_rights == NULL)
return new_rights[0] != NULL;
/* see if anything changed */
return TRUE;
}
}
static bool
const struct acl_rights_update *update)
{
struct acl_rights *right;
/* this identifier no longer exists */
}
}
static bool
const struct acl_rights_update *update)
{
struct acl_rights right;
/* nothing to do */
return FALSE;
}
return TRUE;
}
return FALSE;
}
{
char c2[2];
unsigned int i, j, pos;
/* use letters if possible */
pos++;
break;
}
}
/* fallback to full name */
}
}
c2[0] = ':';
}
}
static void
bool neg)
{
/* need to escape it */
str_truncate(dest, 0);
} T_END;
}
static int
{
const struct acl_rights *rights;
unsigned int i, count;
int ret = 0;
/* rights are sorted with globals at the end, so we can stop at the
first global */
str_truncate(str, 0);
}
str_truncate(str, 0);
}
}
if (o_stream_nfinish(output) < 0) {
ret = -1;
}
/* we really don't want to lose ACL files' contents, so fsync() always
before renaming */
ret = -1;
}
return ret;
}
{
struct acl_backend_vfile_validity *validity;
/* we'll just recalculate or fail it later */
return;
}
}
static int
const struct acl_rights_update *update)
{
struct acl_backend_vfile *backend =
struct acl_backend_vfile_validity *validity;
const char *path;
unsigned int i;
int fd;
bool changed;
/* global ACLs can't be updated here */
if (fd == -1)
return -1;
acl_rights_cmp, &i))
else
if (!changed) {
return 0;
}
/* ACLs were really changed, write the new ones */
return -1;
}
/* set mtime to last_change, if it's higher than the file's
original mtime. if original mtime is higher, then we're
merging some changes and it's better for the mtime to get
updated. */
}
if (file_dotlock_replace(&dotlock, 0) < 0) {
return -1;
}
/* make sure dovecot-acl-list gets updated if we changed any
lookup rights. */
return 0;
}
{
struct acl_backend_vfile_validity *old_validity;
*last_changed_r = 0;
if (old_validity == NULL) {
return -1;
if (old_validity == NULL)
return 0;
}
return 0;
}
static struct acl_object_list_iter *
{
struct acl_object_vfile *aclobj =
(struct acl_object_vfile *)_aclobj;
struct acl_object_list_iter *iter;
/* we may have the object cached, but we don't have all the
rights read into memory */
}
return iter;
}
static int
struct acl_rights *rights_r)
{
struct acl_object_vfile *aclobj =
const struct acl_rights *rights;
return 0;
return 1;
}
static void
{
}
struct acl_backend_vfuncs acl_backend_vfile = {
};