Mercurial > dovecot > original-hg > dovecot-1.2
view src/plugins/acl/acl-backend-vfile.c @ 6940:414c9d631a81 HEAD
Replaced t_push/t_pop calls with T_FRAME*() macros.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Wed, 05 Dec 2007 17:47:44 +0200 |
parents | 65c69a53a7be |
children | 0008fb28e62b |
line wrap: on
line source
/* Copyright (c) 2006-2007 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "istream.h" #include "nfs-workarounds.h" #include "mail-storage-private.h" #include "acl-cache.h" #include "acl-backend-vfile.h" #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #define ACL_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT #define ACL_VFILE_DEFAULT_CACHE_SECS (60*5) #define VALIDITY_MTIME_NOTFOUND 0 #define VALIDITY_MTIME_NOACCESS -1 struct acl_vfile_validity { time_t last_check; time_t last_read_time; time_t last_mtime; off_t last_size; }; struct acl_backend_vfile_validity { struct acl_vfile_validity global_validity, local_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 }, { 'e', MAIL_ACL_EXPUNGE }, { 'k', MAIL_ACL_CREATE }, { 'x', MAIL_ACL_DELETE }, { 'a', MAIL_ACL_ADMIN }, { '\0', NULL } }; static struct acl_backend *acl_backend_vfile_alloc(void) { struct acl_backend_vfile *backend; pool_t pool; pool = pool_alloconly_create("ACL backend", 512); backend = p_new(pool, struct acl_backend_vfile, 1); backend->backend.pool = pool; return &backend->backend; } static int acl_backend_vfile_init(struct acl_backend *_backend, const char *data) { struct acl_backend_vfile *backend = (struct acl_backend_vfile *)_backend; const char *const *tmp; tmp = t_strsplit(data, ":"); backend->global_dir = p_strdup_empty(_backend->pool, *tmp); backend->cache_secs = ACL_VFILE_DEFAULT_CACHE_SECS; if (*tmp != NULL) tmp++; for (; *tmp != NULL; tmp++) { if (strncmp(*tmp, "cache_secs=", 11) == 0) backend->cache_secs = atoi(*tmp + 11); else { i_error("acl vfile: Unknown parameter: %s", *tmp); return -1; } } if (_backend->debug) { i_info("acl vfile: Global ACL directory: %s", backend->global_dir); } _backend->cache = acl_cache_init(_backend, sizeof(struct acl_backend_vfile_validity)); return 0; } static void acl_backend_vfile_deinit(struct acl_backend *backend) { pool_unref(&backend->pool); } static struct acl_object * acl_backend_vfile_object_init(struct acl_backend *_backend, struct mail_storage *storage, const char *name) { struct acl_backend_vfile *backend = (struct acl_backend_vfile *)_backend; struct acl_object_vfile *aclobj; const char *dir; bool is_file; aclobj = i_new(struct acl_object_vfile, 1); aclobj->aclobj.backend = _backend; aclobj->aclobj.name = i_strdup(name); aclobj->global_path = backend->global_dir == NULL ? NULL : i_strconcat(backend->global_dir, "/", name, NULL); if (storage == NULL) { /* the default ACL for mailbox list */ dir = mailbox_list_get_path(_backend->list, NULL, MAILBOX_LIST_PATH_TYPE_DIR); } else { dir = mail_storage_get_mailbox_path(storage, name, &is_file); if (is_file) { dir = mailbox_list_get_path(_backend->list, name, MAILBOX_LIST_PATH_TYPE_CONTROL); } } aclobj->local_path = i_strconcat(dir, "/"ACL_FILENAME, NULL); return &aclobj->aclobj; } static void acl_backend_vfile_object_deinit(struct acl_object *_aclobj) { struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj; if (array_is_created(&aclobj->rights)) array_free(&aclobj->rights); if (aclobj->rights_pool != NULL) pool_unref(&aclobj->rights_pool); i_free(aclobj->local_path); i_free(aclobj->global_path); i_free(aclobj->aclobj.name); i_free(aclobj); } static const char *const * acl_parse_rights(pool_t pool, const char *acl, const char **error_r) { ARRAY_DEFINE(rights, const char *); const char *const *names, **ret_rights; unsigned int i, count; /* parse IMAP ACL list */ while (*acl == ' ' || *acl == '\t') acl++; t_array_init(&rights, 64); for (; *acl != '\0' && *acl != ':'; acl++) { for (i = 0; acl_letter_map[i].letter != '\0'; i++) { if (acl_letter_map[i].letter == *acl) break; } if (acl_letter_map[i].letter == '\0') { *error_r = t_strdup_printf("Unknown ACL '%c'", *acl); return NULL; } array_append(&rights, &acl_letter_map[i].name, 1); } if (*acl != '\0') { /* parse our own extended ACLs */ i_assert(*acl == ':'); names = t_strsplit_spaces(acl, ", "); for (; *names != NULL; names++) { const char *name = p_strdup(pool, *names); array_append(&rights, &name, 1); } } /* @UNSAFE */ count = array_count(&rights); ret_rights = p_new(pool, const char *, count + 1); if (count > 0) { memcpy(ret_rights, array_idx(&rights, 0), sizeof(const char *) * count); } return ret_rights; } static int acl_object_vfile_parse_line(struct acl_object_vfile *aclobj, const char *path, const char *line, unsigned int linenum) { struct acl_rights_update rights; const char *p, *const *right_names, *error = NULL; if (*line == '\0' || *line == '#') return 0; /* <id> [<imap acls>] [:<named acls>] */ p = strchr(line, ' '); if (p == NULL) p = ""; else { line = t_strdup_until(line, p); p++; } memset(&rights, 0, sizeof(rights)); right_names = acl_parse_rights(aclobj->rights_pool, p, &error); if (*line != '-') { rights.modify_mode = ACL_MODIFY_MODE_REPLACE; rights.rights.rights = right_names; } else { line++; rights.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; rights.rights.neg_rights = right_names; } switch (*line) { case 'u': if (strncmp(line, "user=", 5) == 0) { rights.rights.id_type = ACL_ID_USER; rights.rights.identifier = line + 5; break; } case 'o': if (strcmp(line, "owner") == 0) { rights.rights.id_type = ACL_ID_OWNER; break; } case 'g': if (strncmp(line, "group=", 6) == 0) { rights.rights.id_type = ACL_ID_GROUP; rights.rights.identifier = line + 6; break; } else if (strncmp(line, "group-override=", 15) == 0) { rights.rights.id_type = ACL_ID_GROUP_OVERRIDE; rights.rights.identifier = line + 15; break; } case 'a': if (strcmp(line, "authenticated") == 0) { rights.rights.id_type = ACL_ID_AUTHENTICATED; break; } else if (strcmp(line, "anyone") == 0 || strcmp(line, "anonymous") == 0) { rights.rights.id_type = ACL_ID_ANYONE; break; } default: error = t_strdup_printf("Unknown ID '%s'", line); break; } if (error != NULL) { i_error("ACL file %s line %u: %s", path, linenum, error); return -1; } rights.rights.identifier = p_strdup(aclobj->rights_pool, rights.rights.identifier); array_append(&aclobj->rights, &rights.rights, 1); acl_cache_update(aclobj->aclobj.backend->cache, aclobj->aclobj.name, &rights); return 0; } static void acl_backend_remove_all_access(struct acl_object *aclobj) { struct acl_rights_update rights; memset(&rights, 0, sizeof(rights)); rights.rights.id_type = ACL_ID_ANYONE; rights.modify_mode = ACL_MODIFY_MODE_REPLACE; acl_cache_update(aclobj->backend->cache, aclobj->name, &rights); } static int acl_backend_vfile_read(struct acl_object_vfile *aclobj, const char *path, struct acl_vfile_validity *validity, bool try_retry, bool *is_dir_r) { struct istream *input; struct stat st; const char *line; unsigned int linenum; int fd, ret = 1; *is_dir_r = FALSE; fd = nfs_safe_open(path, O_RDONLY); if (fd == -1) { if (errno == ENOENT) { if (aclobj->aclobj.backend->debug) i_info("acl vfile: file %s not found", path); validity->last_mtime = VALIDITY_MTIME_NOTFOUND; } else if (errno == EACCES) { if (aclobj->aclobj.backend->debug) i_info("acl vfile: no access to file %s", path); acl_backend_remove_all_access(&aclobj->aclobj); validity->last_mtime = VALIDITY_MTIME_NOACCESS; } else { i_error("open(%s) failed: %m", path); return -1; } validity->last_size = 0; validity->last_read_time = ioloop_time; return 1; } if (fstat(fd, &st) < 0) { if (errno == ESTALE && try_retry) { (void)close(fd); return 0; } i_error("fstat(%s) failed: %m", path); (void)close(fd); return -1; } if (S_ISDIR(st.st_mode)) { /* we opened a directory. */ *is_dir_r = TRUE; (void)close(fd); return 0; } if (aclobj->aclobj.backend->debug) i_info("acl vfile: reading file %s", path); input = i_stream_create_fd(fd, 4096, FALSE); if (!array_is_created(&aclobj->rights)) { aclobj->rights_pool = pool_alloconly_create("acl rights", I_MAX(256, st.st_size / 2)); i_array_init(&aclobj->rights, I_MAX(16, st.st_size / 40)); } else { array_clear(&aclobj->rights); p_clear(aclobj->rights_pool); } linenum = 1; while ((line = i_stream_read_next_line(input)) != NULL) { T_FRAME( ret = acl_object_vfile_parse_line(aclobj, path, line, linenum++); ); if (ret < 0) break; } if (input->stream_errno != 0) { if (input->stream_errno == ESTALE && try_retry) ret = 0; else { ret = -1; i_error("read(%s) failed: %m", path); } } if (ret > 0) { if (fstat(fd, &st) < 0) { if (errno == ESTALE && try_retry) ret = 0; else { ret = -1; i_error("read(%s) failed: %m", path); } } else { validity->last_read_time = ioloop_time; validity->last_mtime = st.st_mtime; validity->last_size = st.st_size; } } i_stream_unref(&input); if (close(fd) < 0) { if (errno == ESTALE && try_retry) return 0; i_error("close(%s) failed: %m", path); return -1; } return ret; } static int acl_backend_vfile_read_with_retry(struct acl_object_vfile *aclobj, const char *path, struct acl_vfile_validity *validity) { unsigned int i; int ret; bool is_dir; if (path == NULL) return 0; for (i = 0;; i++) { ret = acl_backend_vfile_read(aclobj, path, validity, i < ACL_ESTALE_RETRY_COUNT, &is_dir); if (ret != 0) break; if (is_dir) { /* opened a directory. use dir/.DEFAULT instead */ path = t_strconcat(path, "/.DEFAULT", NULL); } else { /* ESTALE - try again */ } } return ret <= 0 ? -1 : 0; } static int acl_backend_vfile_refresh(struct acl_object *aclobj, const char *path, struct acl_vfile_validity *validity) { struct acl_backend_vfile *backend = (struct acl_backend_vfile *)aclobj->backend; struct stat st; if (validity == NULL) return 1; if (path == NULL || validity->last_check + (time_t)backend->cache_secs > ioloop_time) return 0; validity->last_check = ioloop_time; if (stat(path, &st) < 0) { if (errno == ENOENT) { /* if the file used to exist, we have to re-read it */ return validity->last_mtime != VALIDITY_MTIME_NOTFOUND; } if (errno == EACCES) return validity->last_mtime != VALIDITY_MTIME_NOACCESS; i_error("stat(%s) failed: %m", path); return -1; } if (st.st_mtime == validity->last_mtime && st.st_size == validity->last_size) { /* 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) */ time_t cache_secs = backend->cache_secs; if (st.st_mtime < validity->last_read_time - cache_secs || ioloop_time - validity->last_read_time <= cache_secs) return 0; } return 1; } int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj, time_t *mtime_r) { struct acl_backend_vfile_validity *validity; validity = acl_cache_get_validity(aclobj->backend->cache, aclobj->name); if (validity == NULL) return -1; if (validity->local_validity.last_mtime != 0) *mtime_r = validity->local_validity.last_mtime; else if (validity->global_validity.last_mtime != 0) *mtime_r = validity->global_validity.last_mtime; else *mtime_r = 0; return 0; } static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj) { 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 *old_validity; struct acl_backend_vfile_validity validity; time_t mtime; int ret; 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); if (ret == 0) { ret = acl_backend_vfile_refresh(_aclobj, aclobj->local_path, old_validity == NULL ? NULL : &old_validity->local_validity); } if (ret <= 0) return ret; /* either global or local ACLs changed, need to re-read both */ acl_cache_flush(_aclobj->backend->cache, _aclobj->name); memset(&validity, 0, sizeof(validity)); if (acl_backend_vfile_read_with_retry(aclobj, aclobj->global_path, &validity.global_validity) < 0) return -1; if (acl_backend_vfile_read_with_retry(aclobj, aclobj->local_path, &validity.local_validity) < 0) return -1; acl_cache_set_validity(_aclobj->backend->cache, _aclobj->name, &validity); if (acl_backend_vfile_object_get_mtime(_aclobj, &mtime) == 0) acl_backend_vfile_acllist_verify(backend, _aclobj->name, mtime); return 0; } static int acl_backend_vfile_object_update(struct acl_object *aclobj ATTR_UNUSED, const struct acl_rights_update *rights ATTR_UNUSED) { /* FIXME */ return -1; } static struct acl_object_list_iter * acl_backend_vfile_object_list_init(struct acl_object *aclobj) { struct acl_object_list_iter *iter; iter = i_new(struct acl_object_list_iter, 1); iter->aclobj = aclobj; if (aclobj->backend->v.object_refresh_cache(aclobj) < 0) iter->failed = TRUE; return iter; } static int acl_backend_vfile_object_list_next(struct acl_object_list_iter *iter, struct acl_rights *rights_r) { struct acl_object_vfile *aclobj = (struct acl_object_vfile *)iter->aclobj; const struct acl_rights *rights; if (!array_is_created(&aclobj->rights) || iter->idx == array_count(&aclobj->rights)) return 0; rights = array_idx(&aclobj->rights, iter->idx++); *rights_r = *rights; return 1; } static void acl_backend_vfile_object_list_deinit(struct acl_object_list_iter *iter) { i_free(iter); } struct acl_backend_vfuncs acl_backend_vfile = { acl_backend_vfile_alloc, acl_backend_vfile_init, acl_backend_vfile_deinit, acl_backend_vfile_nonowner_iter_init, acl_backend_vfile_nonowner_iter_next, acl_backend_vfile_nonowner_iter_deinit, acl_backend_vfile_object_init, acl_backend_vfile_object_deinit, acl_backend_vfile_object_refresh_cache, acl_backend_vfile_object_update, acl_backend_vfile_object_list_init, acl_backend_vfile_object_list_next, acl_backend_vfile_object_list_deinit };