diff options
Diffstat (limited to '')
-rw-r--r-- | src/plugins/acl/acl-backend-vfile-update.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/src/plugins/acl/acl-backend-vfile-update.c b/src/plugins/acl/acl-backend-vfile-update.c new file mode 100644 index 0000000..7c48c4e --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile-update.c @@ -0,0 +1,260 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "bsearch-insert-pos.h" +#include "ioloop.h" +#include "str.h" +#include "strescape.h" +#include "file-dotlock.h" +#include "ostream.h" +#include "mail-storage.h" +#include "acl-cache.h" +#include "acl-backend-vfile.h" + +#include <utime.h> +#include <sys/stat.h> + +static struct dotlock_settings dotlock_set = { + .timeout = 30, + .stale_timeout = 120 +}; + +static int acl_backend_vfile_update_begin(struct acl_object_vfile *aclobj, + struct dotlock **dotlock_r) +{ + struct acl_object *_aclobj = &aclobj->aclobj; + struct mailbox_permissions perm; + int fd; + + if (aclobj->local_path == NULL) { + i_error("Can't update acl object '%s': No local acl file path", + aclobj->aclobj.name); + return -1; + } + + /* first lock the ACL file */ + mailbox_list_get_permissions(_aclobj->backend->list, + _aclobj->name, &perm); + fd = file_dotlock_open_group(&dotlock_set, aclobj->local_path, 0, + perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin, dotlock_r); + if (fd == -1) { + i_error("file_dotlock_open(%s) failed: %m", aclobj->local_path); + return -1; + } + + /* locked successfully, re-read the existing file to make sure we + don't lose any changes. */ + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + if (_aclobj->backend->v.object_refresh_cache(_aclobj) < 0) { + file_dotlock_delete(dotlock_r); + return -1; + } + return fd; +} + +static bool +vfile_object_modify_right(struct acl_object *aclobj, unsigned int idx, + const struct acl_rights_update *update) +{ + struct acl_rights *right; + bool c1, c2; + + right = array_idx_modifiable(&aclobj->rights, idx); + c1 = acl_right_names_modify(aclobj->rights_pool, &right->rights, + update->rights.rights, update->modify_mode); + c2 = acl_right_names_modify(aclobj->rights_pool, &right->neg_rights, + update->rights.neg_rights, + update->neg_modify_mode); + + if (right->rights == NULL && right->neg_rights == NULL) { + /* this identifier no longer exists */ + array_delete(&aclobj->rights, idx, 1); + c1 = TRUE; + } + return c1 || c2; +} + +static bool +vfile_object_add_right(struct acl_object *aclobj, unsigned int idx, + const struct acl_rights_update *update) +{ + struct acl_rights right; + bool c1, c2; + + if (update->modify_mode == ACL_MODIFY_MODE_REMOVE && + update->neg_modify_mode == ACL_MODIFY_MODE_REMOVE) { + /* nothing to do */ + return FALSE; + } + + i_zero(&right); + right.id_type = update->rights.id_type; + right.identifier = p_strdup(aclobj->rights_pool, + update->rights.identifier); + + c1 = acl_right_names_modify(aclobj->rights_pool, &right.rights, + update->rights.rights, update->modify_mode); + c2 = acl_right_names_modify(aclobj->rights_pool, &right.neg_rights, + update->rights.neg_rights, + update->neg_modify_mode); + if (c1 || c2) { + array_insert(&aclobj->rights, idx, &right, 1); + return TRUE; + } + return FALSE; +} + +static void +vfile_write_right(string_t *dest, const struct acl_rights *right, + bool neg) +{ + const char *const *rights = neg ? right->neg_rights : right->rights; + + if (neg) str_append_c(dest,'-'); + acl_rights_write_id(dest, right); + + if (strchr(str_c(dest), ' ') != NULL) T_BEGIN { + /* need to escape it */ + const char *escaped = t_strdup(str_escape(str_c(dest))); + str_truncate(dest, 0); + str_printfa(dest, "\"%s\"", escaped); + } T_END; + + str_append_c(dest, ' '); + acl_right_names_write(dest, rights); + str_append_c(dest, '\n'); +} + +static int +acl_backend_vfile_update_write(struct acl_object *aclobj, + int fd, const char *path) +{ + struct ostream *output; + string_t *str; + const struct acl_rights *rights; + unsigned int i, count; + int ret = 0; + + output = o_stream_create_fd_file(fd, 0, FALSE); + o_stream_cork(output); + + str = str_new(default_pool, 256); + /* rights are sorted with globals at the end, so we can stop at the + first global */ + rights = array_get(&aclobj->rights, &count); + for (i = 0; i < count && !rights[i].global; i++) { + if (rights[i].rights != NULL) { + vfile_write_right(str, &rights[i], FALSE); + o_stream_nsend(output, str_data(str), str_len(str)); + str_truncate(str, 0); + } + if (rights[i].neg_rights != NULL) { + vfile_write_right(str, &rights[i], TRUE); + o_stream_nsend(output, str_data(str), str_len(str)); + str_truncate(str, 0); + } + } + str_free(&str); + if (o_stream_finish(output) < 0) { + i_error("write(%s) failed: %s", path, + o_stream_get_error(output)); + ret = -1; + } + o_stream_destroy(&output); + /* we really don't want to lose ACL files' contents, so fsync() always + before renaming */ + if (fsync(fd) < 0) { + i_error("fsync(%s) failed: %m", path); + ret = -1; + } + return ret; +} + +static void acl_backend_vfile_update_cache(struct acl_object *_aclobj, int fd) +{ + struct acl_backend_vfile_validity *validity; + struct stat st; + + if (fstat(fd, &st) < 0) { + /* we'll just recalculate or fail it later */ + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + return; + } + + validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + validity->local_validity.last_read_time = ioloop_time; + validity->local_validity.last_mtime = st.st_mtime; + validity->local_validity.last_size = st.st_size; +} + +int acl_backend_vfile_object_update(struct acl_object *_aclobj, + const struct acl_rights_update *update) +{ + struct acl_object_vfile *aclobj = + (struct acl_object_vfile *)_aclobj; + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_aclobj->backend; + struct acl_backend_vfile_validity *validity; + struct dotlock *dotlock; + struct utimbuf ut; + time_t orig_mtime; + const char *path; + unsigned int i; + int fd; + bool changed; + + /* global ACLs can't be updated here */ + i_assert(!update->rights.global); + + fd = acl_backend_vfile_update_begin(aclobj, &dotlock); + if (fd == -1) + return -1; + + if (!array_bsearch_insert_pos(&_aclobj->rights, &update->rights, + acl_rights_cmp, &i)) + changed = vfile_object_add_right(_aclobj, i, update); + else + changed = vfile_object_modify_right(_aclobj, i, update); + if (!changed) { + file_dotlock_delete(&dotlock); + return 0; + } + + validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + orig_mtime = validity->local_validity.last_mtime; + + /* ACLs were really changed, write the new ones */ + path = file_dotlock_get_lock_path(dotlock); + if (acl_backend_vfile_update_write(_aclobj, fd, path) < 0) { + file_dotlock_delete(&dotlock); + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + return -1; + } + if (orig_mtime < update->last_change && update->last_change != 0) { + /* 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. */ + ut.actime = ioloop_time; + ut.modtime = update->last_change; + if (utime(path, &ut) < 0) + i_error("utime(%s) failed: %m", path); + } + acl_backend_vfile_update_cache(_aclobj, fd); + if (file_dotlock_replace(&dotlock, 0) < 0) { + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + return -1; + } + /* make sure dovecot-acl-list gets updated if we changed any + lookup rights. */ + if (acl_rights_has_nonowner_lookup_changes(&update->rights) || + update->modify_mode == ACL_MODIFY_MODE_REPLACE || + update->modify_mode == ACL_MODIFY_MODE_CLEAR) + (void)acl_backend_vfile_acllist_rebuild(backend); + return 0; +} |