Mercurial > dovecot > core-2.2
view src/plugins/acl/acl-backend-vfile.c @ 4289:58a7882ba128 HEAD
Don't check ACL file's timestamp changes more often than once in a second.
author | Timo Sirainen <timo.sirainen@movial.fi> |
---|---|
date | Tue, 30 May 2006 15:20:30 +0300 |
parents | 1cb59399b395 |
children | 1a35d53c18fc |
line wrap: on
line source
/* Copyright (C) 2006 Timo Sirainen */ #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-api-private.h" #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #define ACL_FILENAME "dovecot-acl" /* Minimum time between stat()ing the ACL file to see if its timestamp has changed. */ #define ACL_VALIDITY_SECS 1 /* Time difference to allow between this system's time and file server's time */ #define ACL_SYNC_SECS 1 #define ACL_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT 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_backend_vfile { struct acl_backend backend; const char *global_dir; }; struct acl_object_vfile { struct acl_object aclobj; char *global_path, *local_path; }; 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_init(const char *data) { struct acl_backend_vfile *backend; pool_t pool; pool = pool_alloconly_create("ACL backend", nearest_power(512)); backend = p_new(pool, struct acl_backend_vfile, 1); backend->global_dir = p_strdup(pool, data); backend->backend.pool = pool; backend->backend.cache = acl_cache_init(&backend->backend, sizeof(struct acl_backend_vfile_validity)); return &backend->backend; } 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, const char *name, const char *control_dir) { struct acl_backend_vfile *backend = (struct acl_backend_vfile *)_backend; struct acl_object_vfile *aclobj; aclobj = i_new(struct acl_object_vfile, 1); aclobj->aclobj.backend = _backend; aclobj->aclobj.name = i_strdup(name); aclobj->global_path = *backend->global_dir == '\0' ? NULL : i_strconcat(backend->global_dir, "/", name, NULL); aclobj->local_path = i_strconcat(control_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; 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(const char *acl, const char **error_r) { array_t ARRAY_DEFINE(rights, const char *); const char *const *names; unsigned int i; /* parse IMAP ACL list */ while (*acl == ' ' || *acl == '\t') acl++; ARRAY_CREATE(&rights, pool_datastack_create(), const char *, 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') { (void)array_append_space(&rights); return array_idx(&rights, 0); } /* parse our own extended ACLs */ i_assert(*acl == ':'); names = t_strsplit_spaces(acl, ", "); if (array_count(&rights) == 0) return names; for (; *names != NULL; names++) array_append(&rights, names, 1); (void)array_append_space(&rights); return array_idx(&rights, 0); } static int acl_object_vfile_parse_line(struct acl_object *aclobj, const char *path, const char *line, unsigned int linenum) { struct acl_rights rights; const char *p, *const *right_names, *error = NULL; if (*line == '\0' || *line == '#') return 0; /* <id> [<imap acls>] [:<named acls>] */ t_push(); 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(p, &error); if (*line != '-') { rights.modify_mode = ACL_MODIFY_MODE_REPLACE; rights.rights = right_names; } else { line++; rights.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; rights.neg_rights = right_names; } if (strncmp(line, "user=", 5) == 0) { rights.id_type = ACL_ID_USER; rights.identifier = line + 5; } else if (strncmp(line, "group=", 6) == 0) { rights.id_type = ACL_ID_GROUP; rights.identifier = line + 6; } else if (strncmp(line, "group-override=", 15) == 0) { rights.id_type = ACL_ID_GROUP_OVERRIDE; rights.identifier = line + 15; } else if (strcmp(line, "owner") == 0) { rights.id_type = ACL_ID_USER; rights.identifier = aclobj->backend->owner_username; } else if (strcmp(line, "authenticated") == 0) { rights.id_type = ACL_ID_AUTHENTICATED; } else if (strcmp(line, "anyone") == 0 || strcmp(line, "anonymous") == 0) { rights.id_type = ACL_ID_ANYONE; } else { error = t_strdup_printf("Unknown ID '%s'", line); } if (error != NULL) { mail_storage_set_critical(aclobj->backend->storage, "ACL file %s line %u: %s", path, linenum, error); t_pop(); return -1; } acl_cache_update(aclobj->backend->cache, aclobj->name, &rights); t_pop(); return 0; } static int acl_backend_vfile_read(struct acl_object *aclobj, const char *path, struct acl_vfile_validity *validity, bool try_retry, bool *is_dir_r) { struct mail_storage *storage = aclobj->backend->storage; 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) { validity->last_size = 0; validity->last_mtime = 0; validity->last_read_time = ioloop_time; return 1; } mail_storage_set_critical(storage, "open(%s) failed: %m", path); return -1; } if (fstat(fd, &st) < 0) { if (errno == ESTALE && try_retry) { (void)close(fd); return 0; } mail_storage_set_critical(storage, "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; } input = i_stream_create_file(fd, default_pool, 4096, FALSE); linenum = 1; while ((line = i_stream_read_next_line(input)) != NULL) { if (acl_object_vfile_parse_line(aclobj, path, line, linenum++) < 0) { ret = -1; break; } } if (input->stream_errno != 0) { if (input->stream_errno == ESTALE && try_retry) ret = 0; else { ret = -1; mail_storage_set_critical(storage, "read(%s) failed: %m", path); } } if (ret > 0) { if (fstat(fd, &st) < 0) { if (errno == ESTALE && try_retry) ret = 0; else { ret = -1; mail_storage_set_critical(storage, "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; mail_storage_set_critical(storage, "close(%s) failed: %m", path); return -1; } return ret; } static int acl_backend_vfile_read_with_retry(struct acl_object *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 stat st; if (validity == NULL) return 1; if (path == NULL || validity->last_check + ACL_VALIDITY_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 != 0; } mail_storage_set_critical(aclobj->backend->storage, "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) */ if (st.st_mtime < validity->last_read_time - ACL_SYNC_SECS || ioloop_time - validity->last_read_time <= ACL_SYNC_SECS) return 0; } return 1; } 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_validity *old_validity; struct acl_backend_vfile_validity validity; 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); return 0; } static int acl_backend_vfile_object_update(struct acl_object *aclobj, const struct acl_rights *rights) { /* 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; return iter; } static int acl_backend_vfile_object_list_next(struct acl_object_list_iter *iter, struct acl_rights *rights_r) { 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_init, acl_backend_vfile_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 };