acl-backend-vfile.c revision 3f3260616dd0132974d18edf3fdbbb4abc45b1c3
/* Copyright (c) 2006-2010 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 "mail-namespace.h"
#include "acl-cache.h"
#include "acl-backend-vfile.h"
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.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 *
{
struct mail_namespace *ns;
if (*name == '\0')
} else {
}
/* verify that the directory isn't same as INBOX's directory.
this is mainly for Maildir. */
/* 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;
const char *dir;
struct mail_namespace *ns =
} 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. */
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 */
parent = "";
}
}
{
}
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++;
}
switch (*line) {
case 'u':
strlen(ACL_ID_NAME_USER_PREFIX)) == 0) {
break;
}
case 'o':
break;
}
case 'g':
strlen(ACL_ID_NAME_GROUP_PREFIX)) == 0) {
break;
strlen(ACL_ID_NAME_GROUP_OVERRIDE_PREFIX)) == 0) {
break;
}
case 'a':
break;
break;
}
default:
break;
}
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;
}
return 0;
}
return -1;
}
/* we opened a directory. */
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 mail_namespace *ns;
struct acl_rights_update ru;
enum acl_modify_mode add_mode;
const struct acl_rights *rights;
unsigned int i, count;
return;
for (i = 0; i < count; i++) {
if (!owner_applied &&
/* owner rights weren't explicitly specified.
replace all the current rights */
}
}
/* 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 */
}
}
if (!owner_applied && count > 0)
}
{
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;
}
{
const char *gid_origin;
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;
}
}
{
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);
}
if (o_stream_flush(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 =
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;
}
if (file_dotlock_replace(&dotlock, 0) < 0) {
return -1;
}
/* make sure dovecot-acl-list gets updated if we added any
lookup rights. */
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 = {
};