From: Timo Sirainen Date: Mon, 27 Jan 2014 14:35:46 +0000 (+0200) Subject: acl plugin: Added an alternative global ACL file that can contain mailbox patterns. X-Git-Tag: 2.2.11~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4c158400b046fefefce0194603951a6587f51867;p=thirdparty%2Fdovecot%2Fcore.git acl plugin: Added an alternative global ACL file that can contain mailbox patterns. Instead of pointing the global ACL path to a directory use a file instead. The file format is " ". 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.. --- diff --git a/src/plugins/acl/Makefile.am b/src/plugins/acl/Makefile.am index 2da2fb7f4b..450e7f4185 100644 --- a/src/plugins/acl/Makefile.am +++ b/src/plugins/acl/Makefile.am @@ -24,6 +24,7 @@ lib01_acl_plugin_la_SOURCES = \ 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 \ @@ -36,6 +37,7 @@ noinst_HEADERS = \ 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 \ diff --git a/src/plugins/acl/acl-api-private.h b/src/plugins/acl/acl-api-private.h index d792a911cd..06b2397633 100644 --- a/src/plugins/acl/acl-api-private.h +++ b/src/plugins/acl/acl-api-private.h @@ -49,6 +49,7 @@ struct acl_backend { struct mailbox_list *list; struct acl_cache *cache; + struct acl_global_file *global_file; struct acl_object *default_aclobj; struct acl_mask *default_aclmask; @@ -69,7 +70,7 @@ struct acl_object { char *name; pool_t rights_pool; - ARRAY(struct acl_rights) rights; + ARRAY_TYPE(acl_rights) rights; }; struct acl_object_list_iter { @@ -103,6 +104,8 @@ int acl_rights_update_import(struct acl_rights_update *update, 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); @@ -117,5 +120,6 @@ bool acl_right_names_modify(pool_t pool, 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 diff --git a/src/plugins/acl/acl-api.c b/src/plugins/acl/acl-api.c index 358a68922d..c38454f699 100644 --- a/src/plugins/acl/acl-api.c +++ b/src/plugins/acl/acl-api.c @@ -7,6 +7,7 @@ #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" @@ -420,6 +421,19 @@ int acl_rights_parse_line(const char *line, pool_t pool, 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; @@ -803,3 +817,17 @@ void acl_object_remove_all_access(struct acl_object *aclobj) 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); +} diff --git a/src/plugins/acl/acl-api.h b/src/plugins/acl/acl-api.h index ce3116b339..beb28072d4 100644 --- a/src/plugins/acl/acl-api.h +++ b/src/plugins/acl/acl-api.h @@ -79,6 +79,7 @@ struct acl_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; diff --git a/src/plugins/acl/acl-backend-vfile.c b/src/plugins/acl/acl-backend-vfile.c index 0e40b25c8a..404b4c6935 100644 --- a/src/plugins/acl/acl-backend-vfile.c +++ b/src/plugins/acl/acl-backend-vfile.c @@ -6,6 +6,7 @@ #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" @@ -32,10 +33,11 @@ acl_backend_vfile_init(struct acl_backend *_backend, const char *data) { 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) @@ -52,10 +54,28 @@ acl_backend_vfile_init(struct acl_backend *_backend, const char *data) 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 = @@ -73,28 +93,27 @@ static void acl_backend_vfile_deinit(struct acl_backend *_backend) 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); @@ -104,7 +123,7 @@ acl_backend_vfile_get_local_dir(struct acl_backend *backend, 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; @@ -129,22 +148,30 @@ acl_backend_vfile_object_init(struct acl_backend *_backend, 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; } @@ -193,7 +220,8 @@ acl_backend_vfile_has_acl(struct acl_backend *_backend, const char *name) 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); @@ -212,16 +240,28 @@ acl_backend_vfile_has_acl(struct acl_backend *_backend, const char *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; @@ -489,9 +529,11 @@ static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj) 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 : @@ -511,9 +553,13 @@ static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj) } 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; diff --git a/src/plugins/acl/acl-backend-vfile.h b/src/plugins/acl/acl-backend-vfile.h index 01a2b5cb2c..28003d3c18 100644 --- a/src/plugins/acl/acl-backend-vfile.h +++ b/src/plugins/acl/acl-backend-vfile.h @@ -25,6 +25,8 @@ struct acl_backend_vfile_validity { 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; }; @@ -35,7 +37,7 @@ struct acl_backend_vfile_acllist { 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; diff --git a/src/plugins/acl/acl-global-file.c b/src/plugins/acl/acl-global-file.c new file mode 100644 index 0000000000..178faab3c8 --- /dev/null +++ b/src/plugins/acl/acl-global-file.c @@ -0,0 +1,191 @@ +/* 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 + +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; +} diff --git a/src/plugins/acl/acl-global-file.h b/src/plugins/acl/acl-global-file.h new file mode 100644 index 0000000000..912452a1b9 --- /dev/null +++ b/src/plugins/acl/acl-global-file.h @@ -0,0 +1,20 @@ +#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