summaryrefslogtreecommitdiffstats
path: root/src/plugins/acl/acl-global-file.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/acl/acl-global-file.c')
-rw-r--r--src/plugins/acl/acl-global-file.c246
1 files changed, 246 insertions, 0 deletions
diff --git a/src/plugins/acl/acl-global-file.c b/src/plugins/acl/acl-global-file.c
new file mode 100644
index 0000000..02f6643
--- /dev/null
+++ b/src/plugins/acl/acl-global-file.c
@@ -0,0 +1,246 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "strescape.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;
+ bool debug;
+};
+
+struct acl_global_file *
+acl_global_file_init(const char *path, unsigned int refresh_interval_secs,
+ bool debug)
+{
+ 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;
+ file->debug = debug;
+ 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);
+}
+
+struct acl_global_file_parse_ctx {
+ struct acl_global_file *file;
+ ARRAY(struct acl_global_parse_rights) parse_rights;
+};
+
+static int
+acl_global_file_parse_line(struct acl_global_file_parse_ctx *ctx,
+ const char *line, const char **error_r)
+{
+ struct acl_global_parse_rights *pright;
+ const char *p, *vpattern;
+
+ if (*line == '"') {
+ line++;
+ if (str_unescape_next(&line, &vpattern) < 0) {
+ *error_r = "Missing '\"'";
+ return -1;
+ }
+ if (line[0] != ' ') {
+ *error_r = "Expecting space after '\"'";
+ return -1;
+ }
+ line++;
+ } else {
+ p = strchr(line, ' ');
+ if (p == NULL) {
+ *error_r = "Missing ACL rights";
+ return -1;
+ }
+ if (p == line) {
+ *error_r = "Empty ACL pattern";
+ return -1;
+ }
+ vpattern = t_strdup_until(line, p);
+ line = p + 1;
+ }
+
+ pright = array_append_space(&ctx->parse_rights);
+ pright->vpattern = p_strdup(ctx->file->rights_pool, vpattern);
+ if (acl_rights_parse_line(line, ctx->file->rights_pool,
+ &pright->rights, error_r) < 0)
+ return -1;
+ pright->rights.global = TRUE;
+ return 0;
+}
+
+static int acl_global_file_read(struct acl_global_file *file)
+{
+ struct acl_global_file_parse_ctx ctx;
+ struct acl_global_parse_rights *pright;
+ struct acl_global_rights *right;
+ struct istream *input;
+ const char *line, *error, *prev_vpattern;
+ unsigned int linenum = 0;
+ int ret = 0;
+
+ array_clear(&file->rights);
+ p_clear(file->rights_pool);
+
+ i_zero(&ctx);
+ ctx.file = file;
+ i_array_init(&ctx.parse_rights, 32);
+
+ input = i_stream_create_file(file->path, SIZE_MAX);
+ i_stream_set_return_partial_line(input, TRUE);
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ linenum++;
+ if (line[0] == '\0' || line[0] == '#')
+ continue;
+ T_BEGIN {
+ ret = acl_global_file_parse_line(&ctx, line, &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;
+ }
+ if (ret == 0) {
+ const struct stat *st;
+
+ if (i_stream_stat(input, TRUE, &st) < 0) {
+ i_error("Couldn't stat global ACL file %s: %s",
+ file->path, i_stream_get_error(input));
+ ret = -1;
+ } else {
+ file->prev_st = *st;
+ }
+ }
+ i_stream_destroy(&input);
+
+ /* sort all parsed rights */
+ array_sort(&ctx.parse_rights, acl_global_parse_rights_cmp);
+ /* combine identical patterns into same structs */
+ prev_vpattern = ""; right = NULL;
+ array_foreach_modifiable(&ctx.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_push_back(&right->rights, &pright->rights);
+ }
+
+ array_free(&ctx.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;
+}
+
+void acl_global_file_last_stat(struct acl_global_file *file, struct stat *st_r)
+{
+ *st_r = file->prev_st;
+}
+
+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;
+
+ array_foreach_modifiable(&file->rights, global_rights) {
+ if (!wildcard_match(vname, global_rights->vpattern))
+ continue;
+ if (file->debug) {
+ i_debug("Mailbox '%s' matches global ACL pattern '%s'",
+ vname, global_rights->vpattern);
+ }
+ 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)
+{
+ 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 TRUE;
+ }
+ return FALSE;
+}