]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
acl plugin: Added an alternative global ACL file that can contain mailbox patterns.
authorTimo Sirainen <tss@iki.fi>
Mon, 27 Jan 2014 14:35:46 +0000 (16:35 +0200)
committerTimo Sirainen <tss@iki.fi>
Mon, 27 Jan 2014 14:35:46 +0000 (16:35 +0200)
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..

src/plugins/acl/Makefile.am
src/plugins/acl/acl-api-private.h
src/plugins/acl/acl-api.c
src/plugins/acl/acl-api.h
src/plugins/acl/acl-backend-vfile.c
src/plugins/acl/acl-backend-vfile.h
src/plugins/acl/acl-global-file.c [new file with mode: 0644]
src/plugins/acl/acl-global-file.h [new file with mode: 0644]

index 2da2fb7f4bc706ce7878c7e4e9328039075e4c0b..450e7f4185f7c712b0485feb5af25af80b3a629f 100644 (file)
@@ -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 \
index d792a911cd68ba8dc3dc6f5335c318aae804b59a..06b23976330fb4ca482bdc487ae9b8a564bff00d 100644 (file)
@@ -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
index 358a68922d1cec81f9f6bf574cdabbd33b37bf2c..c38454f6996980b2b63f974cf7ddab74aa2409a3 100644 (file)
@@ -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);
+}
index ce3116b3391ab86d2b4107559086b5d0d03f792b..beb28072d421df123a43346f60dd58f38563d3d8 100644 (file)
@@ -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;
index 0e40b25c8adc6d96a141bba28b125f3ec5444107..404b4c6935517a0ac903814148e1e2b99371268e 100644 (file)
@@ -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;
index 01a2b5cb2ce4b5824af6cf39d87d7dacb1a75c82..28003d3c185216a4be7ac053c34180c5e509ac6b 100644 (file)
@@ -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 (file)
index 0000000..178faab
--- /dev/null
@@ -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 <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;
+}
diff --git a/src/plugins/acl/acl-global-file.h b/src/plugins/acl/acl-global-file.h
new file mode 100644 (file)
index 0000000..912452a
--- /dev/null
@@ -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