Instead of pointing the global ACL path to a directory use a file instead.
The file format is "<mailbox pattern> <normal ACL line>". Most importantly
this can be used to specify default ACLs for namespaces.
The mailbox pattern uses "*" and "?" wildcards currently. I'm not sure if I
should still change them to IMAP "*" and "%" wildcards. That would make the
behavior more complex ("%" depends on hierarchy separator), slightly slower
and quota code is already also using the */? wildcards..
acl-backend-vfile-acllist.c \
acl-backend-vfile-update.c \
acl-cache.c \
+ acl-global-file.c \
acl-lookup-dict.c \
acl-mailbox.c \
acl-mailbox-list.c \
acl-api-private.h \
acl-backend-vfile.h \
acl-cache.h \
+ acl-global-file.h \
acl-lookup-dict.h \
acl-plugin.h \
acl-shared-storage.h \
struct mailbox_list *list;
struct acl_cache *cache;
+ struct acl_global_file *global_file;
struct acl_object *default_aclobj;
struct acl_mask *default_aclmask;
char *name;
pool_t rights_pool;
- ARRAY(struct acl_rights) rights;
+ ARRAY_TYPE(acl_rights) rights;
};
struct acl_object_list_iter {
const char *acl_rights_export(const struct acl_rights *rights);
int acl_rights_parse_line(const char *line, pool_t pool,
struct acl_rights *rights_r, const char **error_r);
+void acl_rights_dup(const struct acl_rights *src,
+ pool_t pool, struct acl_rights *dest_r);
int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2);
void acl_rights_sort(struct acl_object *aclobj);
enum acl_modify_mode modify_mode);
void acl_object_rebuild_cache(struct acl_object *aclobj);
void acl_object_remove_all_access(struct acl_object *aclobj);
+void acl_object_add_global_acls(struct acl_object *aclobj);
#endif
#include "hash.h"
#include "mail-user.h"
#include "mailbox-list.h"
+#include "acl-global-file.h"
#include "acl-cache.h"
#include "acl-api-private.h"
return 0;
}
+void acl_rights_dup(const struct acl_rights *src,
+ pool_t pool, struct acl_rights *dest_r)
+{
+ memset(dest_r, 0, sizeof(*dest_r));
+ dest_r->id_type = src->id_type;
+ dest_r->identifier = p_strdup(pool, src->identifier);
+ dest_r->rights = src->rights == NULL ? NULL :
+ p_strarray_dup(pool, src->rights);
+ dest_r->neg_rights = src->neg_rights == NULL ? NULL :
+ p_strarray_dup(pool, src->neg_rights);
+ dest_r->global = src->global;
+}
+
int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2)
{
int ret;
rights.rights = &null;
array_append(&aclobj->rights, &rights, 1);
}
+
+void acl_object_add_global_acls(struct acl_object *aclobj)
+{
+ struct acl_backend *backend = aclobj->backend;
+ const char *vname, *error;
+
+ if (mailbox_list_is_valid_name(backend->list, aclobj->name, &error))
+ vname = mailbox_list_get_vname(backend->list, aclobj->name);
+ else
+ vname = "";
+
+ acl_global_file_get(backend->global_file, vname,
+ aclobj->rights_pool, &aclobj->rights);
+}
/* These rights are global for all users */
unsigned int global:1;
};
+ARRAY_DEFINE_TYPE(acl_rights, struct acl_rights);
struct acl_rights_update {
struct acl_rights rights;
#include "istream.h"
#include "nfs-workarounds.h"
#include "mail-storage-private.h"
+#include "acl-global-file.h"
#include "acl-cache.h"
#include "acl-backend-vfile.h"
{
struct acl_backend_vfile *backend =
(struct acl_backend_vfile *)_backend;
+ struct stat st;
const char *const *tmp;
tmp = t_strsplit(data, ":");
- backend->global_dir = p_strdup_empty(_backend->pool, *tmp);
+ backend->global_path = p_strdup_empty(_backend->pool, *tmp);
backend->cache_secs = ACL_VFILE_DEFAULT_CACHE_SECS;
if (*tmp != NULL)
return -1;
}
}
+ if (backend->global_path != NULL) {
+ if (stat(backend->global_path, &st) < 0) {
+ if (errno != ENOENT) {
+ i_error("acl vfile: stat(%s) failed: %m",
+ backend->global_path);
+ return -1;
+ }
+ } else if (!S_ISDIR(st.st_mode)) {
+ _backend->global_file =
+ acl_global_file_init(backend->global_path, backend->cache_secs);
+ }
+ }
if (_backend->debug) {
- i_debug("acl vfile: Global ACL directory: %s",
- backend->global_dir == NULL ? "(none)" :
- backend->global_dir);
+ if (backend->global_path == NULL)
+ i_debug("acl vfile: Global ACLs disabled");
+ else if (_backend->global_file != NULL) {
+ i_debug("acl vfile: Global ACL file: %s",
+ backend->global_path);
+ } else {
+ i_debug("acl vfile: Global ACL legacy directory: %s",
+ backend->global_path);
+ }
}
_backend->cache =
array_free(&backend->acllist);
pool_unref(&backend->acllist_pool);
}
+ if (_backend->global_file != NULL)
+ acl_global_file_deinit(&_backend->global_file);
pool_unref(&backend->backend.pool);
}
static const char *
acl_backend_vfile_get_local_dir(struct acl_backend *backend,
- const char *name)
+ const char *name, const char *vname)
{
struct mail_namespace *ns = mailbox_list_get_namespace(backend->list);
struct mailbox_list *list = ns->list;
struct mail_storage *storage;
enum mailbox_list_path_type type;
- const char *dir, *inbox, *vname, *error;
+ const char *dir, *inbox;
if (*name == '\0')
name = NULL;
- else if (!mailbox_list_is_valid_name(list, name, &error))
- 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. */
- vname = name == NULL ? "" : mailbox_list_get_vname(backend->list, name);
if (mailbox_list_get_storage(&list, vname, &storage) < 0)
return NULL;
i_assert(list == ns->list);
MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_MAILBOX;
if (name == NULL) {
if (!mailbox_list_get_root_path(list, type, &dir))
- return FALSE;
+ return NULL;
} else {
if (mailbox_list_get_path(list, name, type, &dir) <= 0)
return NULL;
struct acl_backend_vfile *backend =
(struct acl_backend_vfile *)_backend;
struct acl_object_vfile *aclobj;
- const char *dir, *vname;
+ const char *dir, *vname, *error;
aclobj = i_new(struct acl_object_vfile, 1);
aclobj->aclobj.backend = _backend;
aclobj->aclobj.name = i_strdup(name);
T_BEGIN {
- if (backend->global_dir != NULL) {
- vname = mailbox_list_get_vname(backend->backend.list, name);
- aclobj->global_path =
- i_strconcat(backend->global_dir, "/", vname, NULL);
+ if (*name == '\0' ||
+ mailbox_list_is_valid_name(_backend->list, name, &error)) {
+ vname = *name == '\0' ? "" :
+ mailbox_list_get_vname(_backend->list, name);
+
+ dir = acl_backend_vfile_get_local_dir(_backend, name, vname);
+ aclobj->local_path = dir == NULL ? NULL :
+ i_strconcat(dir, "/"ACL_FILENAME, NULL);
+ if (backend->global_path != NULL &&
+ _backend->global_file == NULL) {
+ aclobj->global_path =
+ i_strconcat(backend->global_path, "/", name, NULL);
+ }
+ } else {
+ /* Invalid mailbox name, just use the default
+ global ACL files */
}
-
- dir = acl_backend_vfile_get_local_dir(_backend, name);
- aclobj->local_path = dir == NULL ? NULL :
- i_strconcat(dir, "/"ACL_FILENAME, NULL);
} T_END;
return &aclobj->aclobj;
}
struct acl_backend_vfile *backend =
(struct acl_backend_vfile *)_backend;
struct acl_backend_vfile_validity *old_validity, new_validity;
- const char *path, *local_path, *global_path, *dir;
+ const char *path, *local_path, *global_path, *dir, *vname = "";
+ const char *error;
int ret;
old_validity = acl_cache_get_validity(_backend->cache, name);
ret = acl_backend_vfile_exists(backend, path,
&new_validity.mailbox_validity);
}
+
if (ret == 0 &&
- (dir = acl_backend_vfile_get_local_dir(_backend, name)) != NULL) {
- local_path = t_strconcat(dir, "/", name, NULL);
- ret = acl_backend_vfile_exists(backend, local_path,
- &new_validity.local_validity);
+ (*name == '\0' ||
+ mailbox_list_is_valid_name(_backend->list, name, &error))) {
+ vname = *name == '\0' ? "" :
+ mailbox_list_get_vname(_backend->list, name);
+ dir = acl_backend_vfile_get_local_dir(_backend, name, vname);
+ if (dir != NULL) {
+ local_path = t_strconcat(dir, "/", name, NULL);
+ ret = acl_backend_vfile_exists(backend, local_path,
+ &new_validity.local_validity);
+ }
}
- if (ret == 0 && backend->global_dir != NULL) {
- global_path = t_strconcat(backend->global_dir, "/", name, NULL);
- ret = acl_backend_vfile_exists(backend, global_path,
- &new_validity.global_validity);
+
+ if (ret == 0 && backend->global_path != NULL) {
+ if (_backend->global_file != NULL)
+ ret = acl_global_file_have_any(_backend->global_file, vname) ? 1 : 0;
+ else {
+ global_path = t_strconcat(backend->global_path, "/", name, NULL);
+ ret = acl_backend_vfile_exists(backend, global_path,
+ &new_validity.global_validity);
+ }
}
acl_cache_set_validity(_backend->cache, name, &new_validity);
return ret > 0;
old_validity = acl_cache_get_validity(_aclobj->backend->cache,
_aclobj->name);
- ret = acl_backend_vfile_refresh(_aclobj, aclobj->global_path,
- old_validity == NULL ? NULL :
- &old_validity->global_validity);
+ ret = _aclobj->backend->global_file != NULL ?
+ acl_global_file_refresh(_aclobj->backend->global_file) :
+ acl_backend_vfile_refresh(_aclobj, aclobj->global_path,
+ old_validity == NULL ? NULL :
+ &old_validity->global_validity);
if (ret == 0) {
ret = acl_backend_vfile_refresh(_aclobj, aclobj->local_path,
old_validity == NULL ? NULL :
}
memset(&validity, 0, sizeof(validity));
- if (acl_backend_vfile_read_with_retry(_aclobj, TRUE, aclobj->global_path,
- &validity.global_validity) < 0)
- return -1;
+ if (_aclobj->backend->global_file != NULL)
+ acl_object_add_global_acls(_aclobj);
+ else {
+ if (acl_backend_vfile_read_with_retry(_aclobj, TRUE, aclobj->global_path,
+ &validity.global_validity) < 0)
+ return -1;
+ }
if (acl_backend_vfile_read_with_retry(_aclobj, FALSE, aclobj->local_path,
&validity.local_validity) < 0)
return -1;
struct acl_object_vfile {
struct acl_object aclobj;
+ /* if backend->global_file is NULL, assume legacy separate global
+ ACL file per mailbox */
char *global_path, *local_path;
};
struct acl_backend_vfile {
struct acl_backend backend;
- const char *global_dir;
+ const char *global_path;
pool_t acllist_pool;
ARRAY(struct acl_backend_vfile_acllist) acllist;
--- /dev/null
+/* Copyright (c) 2014 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "wildcard-match.h"
+#include "acl-api-private.h"
+#include "acl-global-file.h"
+
+#include <sys/stat.h>
+
+struct acl_global_rights {
+ const char *vpattern;
+ ARRAY_TYPE(acl_rights) rights;
+};
+
+struct acl_global_parse_rights {
+ const char *vpattern;
+ struct acl_rights rights;
+};
+
+struct acl_global_file {
+ char *path;
+ struct stat prev_st;
+ time_t last_refresh_time;
+
+ pool_t rights_pool;
+ ARRAY(struct acl_global_rights) rights;
+
+ unsigned int refresh_interval_secs;
+};
+
+struct acl_global_file *
+acl_global_file_init(const char *path, unsigned int refresh_interval_secs)
+{
+ struct acl_global_file *file;
+
+ file = i_new(struct acl_global_file, 1);
+ file->path = i_strdup(path);
+ file->refresh_interval_secs = refresh_interval_secs;
+ i_array_init(&file->rights, 32);
+ file->rights_pool = pool_alloconly_create("acl global file rights", 1024);
+ return file;
+}
+
+void acl_global_file_deinit(struct acl_global_file **_file)
+{
+ struct acl_global_file *file = *_file;
+
+ *_file = NULL;
+
+ array_free(&file->rights);
+ pool_unref(&file->rights_pool);
+ i_free(file->path);
+ i_free(file);
+}
+
+static int acl_global_parse_rights_cmp(const struct acl_global_parse_rights *r1,
+ const struct acl_global_parse_rights *r2)
+{
+ return strcmp(r1->vpattern, r2->vpattern);
+}
+
+static int acl_global_file_read(struct acl_global_file *file)
+{
+ ARRAY(struct acl_global_parse_rights) parse_rights;
+ struct acl_global_parse_rights *pright;
+ struct acl_global_rights *right;
+ struct istream *input;
+ const char *line, *p, *error, *prev_vpattern;
+ unsigned int linenum = 0;
+ int ret = 0;
+
+ array_clear(&file->rights);
+ p_clear(file->rights_pool);
+
+ i_array_init(&parse_rights, 32);
+
+ input = i_stream_create_file(file->path, (size_t)-1);
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ linenum++;
+ if (line[0] == '\0' || line[0] == '#')
+ continue;
+ T_BEGIN {
+ p = strchr(line, ' ');
+ if (p == NULL) {
+ error = "Missing ACL rights";
+ ret = -1;
+ } else if (p == line) {
+ error = "Empty ACL pattern";
+ ret = -1;
+ } else {
+ pright = array_append_space(&parse_rights);
+ pright->vpattern = p_strdup_until(file->rights_pool,
+ line, p++);
+ ret = acl_rights_parse_line(p, file->rights_pool,
+ &pright->rights, &error);
+ }
+ if (ret < 0) {
+ i_error("Global ACL file %s line %u: %s",
+ file->path, linenum, error);
+ }
+ } T_END;
+ if (ret < 0)
+ break;
+ }
+ if (ret == 0 && input->stream_errno != 0) {
+ i_error("Couldn't read global ACL file %s: %s",
+ file->path, i_stream_get_error(input));
+ ret = -1;
+ }
+ i_stream_destroy(&input);
+
+ /* sort all parsed rights */
+ array_sort(&parse_rights, acl_global_parse_rights_cmp);
+ /* combine identical patterns into same structs */
+ prev_vpattern = ""; right = NULL;
+ array_foreach_modifiable(&parse_rights, pright) {
+ if (right == NULL ||
+ strcmp(prev_vpattern, pright->vpattern) != 0) {
+ right = array_append_space(&file->rights);
+ right->vpattern = pright->vpattern;
+ p_array_init(&right->rights, file->rights_pool, 4);
+ }
+ array_append(&right->rights, &pright->rights, 1);
+ }
+
+ array_free(&parse_rights);
+ return ret;
+}
+
+int acl_global_file_refresh(struct acl_global_file *file)
+{
+ struct stat st;
+
+ if (file->last_refresh_time + (time_t)file->refresh_interval_secs > ioloop_time)
+ return 0;
+ if (file->last_refresh_time != 0) {
+ if (stat(file->path, &st) < 0) {
+ i_error("stat(%s) failed: %m", file->path);
+ return -1;
+ }
+ if (st.st_ino == file->prev_st.st_ino &&
+ st.st_size == file->prev_st.st_size &&
+ CMP_ST_MTIME(&st, &file->prev_st)) {
+ /* no change to the file */
+ file->last_refresh_time = ioloop_time;
+ return 0;
+ }
+ }
+ if (acl_global_file_read(file) < 0)
+ return -1;
+ file->last_refresh_time = ioloop_time;
+ return 0;
+}
+
+static struct acl_global_rights *
+acl_global_file_find_rights(struct acl_global_file *file, const char *vname)
+{
+ struct acl_global_rights *rights;
+
+ i_assert(file->last_refresh_time != 0);
+
+ array_foreach_modifiable(&file->rights, rights) {
+ if (wildcard_match(vname, rights->vpattern))
+ return rights;
+ }
+ return NULL;
+}
+
+void acl_global_file_get(struct acl_global_file *file, const char *vname,
+ pool_t pool, ARRAY_TYPE(acl_rights) *rights_r)
+{
+ struct acl_global_rights *global_rights;
+ const struct acl_rights *rights;
+ struct acl_rights *new_rights;
+
+ global_rights = acl_global_file_find_rights(file, vname);
+ if (global_rights == NULL)
+ return;
+ array_foreach(&global_rights->rights, rights) {
+ new_rights = array_append_space(rights_r);
+ acl_rights_dup(rights, pool, new_rights);
+ }
+}
+
+bool acl_global_file_have_any(struct acl_global_file *file, const char *vname)
+{
+ return acl_global_file_find_rights(file, vname) != NULL;
+}
--- /dev/null
+#ifndef ACL_GLOBAL_FILE_H
+#define ACL_GLOBAL_FILE_H
+
+#include "acl-api.h"
+
+struct acl_global_file *
+acl_global_file_init(const char *path, unsigned int refresh_interval_secs);
+void acl_global_file_deinit(struct acl_global_file **file);
+
+/* Read the global ACLs into memory. */
+int acl_global_file_refresh(struct acl_global_file *file);
+
+/* Return global ACL rights matching the mailbox name. The file must already
+ have been refreshed at least once. */
+void acl_global_file_get(struct acl_global_file *file, const char *vname,
+ pool_t pool, ARRAY_TYPE(acl_rights) *rights_r);
+/* Returns TRUE if there are any global ACLs matching the mailbox name. */
+bool acl_global_file_have_any(struct acl_global_file *file, const char *vname);
+
+#endif