view src/plugins/acl/acl-backend-vfile-acllist.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 532b29891022
children 7ed926ed7aa4
line wrap: on
line source

/* Copyright (c) 2007 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "str.h"
#include "safe-mkstemp.h"
#include "istream.h"
#include "ostream.h"
#include "mail-namespace.h"
#include "mail-storage.h"
#include "acl-plugin.h"
#include "acl-cache.h"
#include "acl-backend-vfile.h"

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

struct acl_mailbox_list_context_vfile {
	struct acl_mailbox_list_context ctx;

	unsigned int acllist_change_counter;
	unsigned int idx;
};

static void
acllist_clear(struct acl_backend_vfile *backend, uoff_t file_size)
{
	backend->acllist_change_counter++;
	if (backend->acllist_pool == NULL) {
		backend->acllist_pool =
			pool_alloconly_create("vfile acllist",
					      I_MAX(file_size / 2, 128));
		i_array_init(&backend->acllist, I_MAX(16, file_size / 60));
	} else {
		p_clear(backend->acllist_pool);
		array_clear(&backend->acllist);
	}
}

static int acl_backend_vfile_acllist_read(struct acl_backend_vfile *backend)
{
	struct acl_backend_vfile_acllist acllist;
	struct istream *input;
	struct stat st;
	const char *rootdir, *path, *line, *p;
	int fd, ret = 0;

	backend->acllist_last_check = ioloop_time;

	rootdir = mailbox_list_get_path(backend->backend.list, NULL,
					MAILBOX_LIST_PATH_TYPE_DIR);
	path = t_strdup_printf("%s/"ACLLIST_FILENAME, rootdir);

	if (backend->acllist_mtime != 0) {
		/* see if the file's mtime has changed */
		if (stat(path, &st) < 0) {
			if (errno == ENOENT)
				backend->acllist_mtime = 0;
			else
				i_error("stat(%s) failed: %m", path);
			return -1;
		}
		if (st.st_mtime == backend->acllist_mtime)
			return 0;
	}

	fd = open(path, O_RDONLY);
	if (fd == -1) {
		if (errno == ENOENT) {
			backend->acllist_mtime = 0;
			return -1;
		}
		i_error("open(%s) failed: %m", path);
		return -1;
	}
	if (fstat(fd, &st) < 0) {
		i_error("fstat(%s) failed: %m", path);
		(void)close(fd);
		return -1;
	}
	backend->acllist_mtime = st.st_mtime;
	acllist_clear(backend, st.st_size);

	input = i_stream_create_fd(fd, (size_t)-1, FALSE);
	while ((line = i_stream_read_next_line(input)) != NULL) {
		acllist.mtime = 0;
		for (p = line; *p >= '0' && *p <= '9'; p++)
			acllist.mtime = acllist.mtime * 10 + (*p - '0');

		if (p == line || *p != ' ' || p[1] == '\0') {
			i_error("Broken acllist file: %s", path);
			if (unlink(path) < 0 && errno != ENOENT)
				i_error("unlink(%s) failed: %m", path);
			return -1;
		}
		acllist.name = p_strdup(backend->acllist_pool, p + 1);
		array_append(&backend->acllist, &acllist, 1);
	}
	if (input->stream_errno != 0)
		ret = -1;
	i_stream_destroy(&input);

	if (close(fd) < 0)
		i_error("close(%s) failed: %m", path);
	return ret;
}

void acl_backend_vfile_acllist_refresh(struct acl_backend_vfile *backend)
{
	if (backend->acllist_last_check +
	    (time_t)backend->cache_secs > ioloop_time)
		return;

	if (acl_backend_vfile_acllist_read(backend) < 0) {
		acllist_clear(backend, 0);
		if (!backend->rebuilding_acllist)
			(void)acl_backend_vfile_acllist_rebuild(backend);
	}
}

static bool rights_has_lookup_changes(const struct acl_rights *rights)
{
	const char *const *p;

	if (rights->id_type == ACL_ID_OWNER) {
		/* ignore owner rights */
		return FALSE;
	}

	if (rights->rights == NULL)
		return FALSE;

	for (p = rights->rights; *p != NULL; p++) {
		if (strcmp(*p, MAIL_ACL_LOOKUP) == 0)
			return TRUE;
	}
	return FALSE;
}

static int
acllist_append(struct acl_backend_vfile *backend, struct ostream *output,
	       struct mail_storage *storage, const char *name)
{
	struct acl_object *aclobj;
	struct acl_object_list_iter *iter;
	struct acl_rights rights;
	struct acl_backend_vfile_acllist acllist;
	int ret;

	acl_cache_flush(backend->backend.cache, name);
	aclobj = acl_object_init_from_name(&backend->backend, storage, name);

	iter = acl_object_list_init(aclobj);
	while ((ret = acl_object_list_next(iter, &rights)) > 0) {
		if (rights_has_lookup_changes(&rights))
			break;
	}
	acl_object_list_deinit(&iter);

	if (acl_backend_vfile_object_get_mtime(aclobj, &acllist.mtime) < 0)
		ret = -1;

	if (ret > 0) {
		acllist.name = p_strdup(backend->acllist_pool, name);
		array_append(&backend->acllist, &acllist, 1);

		T_FRAME(
			const char *line;
			line = t_strdup_printf("%s %s\n",
					       dec2str(acllist.mtime), name);
			o_stream_send_str(output, line);
		);
	}
	acl_object_deinit(&aclobj);
	return ret < 0 ? -1 : 0;
}

int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend)
{
	struct mailbox_list *list = backend->backend.list;
	struct mail_namespace *ns;
	struct mailbox_list_iterate_context *iter;
	const struct mailbox_info *info;
	const char *rootdir, *acllist_path;
	struct ostream *output;
	struct stat st;
	string_t *path;
	mode_t mode;
	gid_t gid;
	int fd, ret;

	mailbox_list_get_permissions(list, &mode, &gid);

	path = t_str_new(256);
	rootdir = mailbox_list_get_path(list, NULL,
					MAILBOX_LIST_PATH_TYPE_DIR);
	str_printfa(path, "%s/%s", rootdir, mailbox_list_get_temp_prefix(list));

	/* Build it into a temporary file and rename() over. There's no need
	   to use locking, because even if multiple processes are rebuilding
	   the file at the same time the result should be the same. */
	fd = safe_mkstemp(path, mode, (uid_t)-1, gid);
	if (fd == -1) {
		i_error("safe_mkstemp(%s) failed: %m", str_c(path));
		return -1;
	}
	output = o_stream_create_fd_file(fd, 0, FALSE);

	ret = 0;
	acllist_clear(backend, 0);
	ns = mailbox_list_get_namespace(list);

	backend->rebuilding_acllist = TRUE;
	iter = mailbox_list_iter_init(list, "*", MAILBOX_LIST_ITER_RAW_LIST |
				      MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
	while ((info = mailbox_list_iter_next(iter)) != NULL) {
		if (acllist_append(backend, output, ns->storage,
				   info->name) < 0) {
			ret = -1;
			break;
		}
	}

	if (mailbox_list_iter_deinit(&iter) < 0)
		ret = -1;
	o_stream_destroy(&output);
	backend->rebuilding_acllist = FALSE;

	if (ret == 0) {
		if (fstat(fd, &st) < 0) {
			i_error("fstat(%s) failed: %m", str_c(path));
			ret = -1;
		}
	}
	if (close(fd) < 0) {
		i_error("close(%s) failed: %m", str_c(path));
		ret = -1;
	}

	if (ret == 0) {
		acllist_path = t_strdup_printf("%s/"ACLLIST_FILENAME, rootdir);
		if (rename(str_c(path), acllist_path) < 0) {
			i_error("rename(%s, %s) failed: %m",
				str_c(path), acllist_path);
			ret = -1;
		}
	}
	if (ret == 0) {
		backend->acllist_mtime = st.st_mtime;
		backend->acllist_last_check = ioloop_time;
	} else {
		acllist_clear(backend, 0);
		if (unlink(str_c(path)) < 0 && errno != ENOENT)
			i_error("unlink(%s) failed: %m", str_c(path));
	}
	return ret;
}

static const struct acl_backend_vfile_acllist *
acl_backend_vfile_acllist_find(struct acl_backend_vfile *backend,
			       const char *name)
{
	const struct acl_backend_vfile_acllist *acllist;
	unsigned int i, count;

	acllist = array_get(&backend->acllist, &count);
	for (i = 0; i < count; i++) {
		if (strcmp(acllist[i].name, name) == 0)
			return &acllist[i];
	}
	return NULL;
}

void acl_backend_vfile_acllist_verify(struct acl_backend_vfile *backend,
				      const char *name, time_t mtime)
{
	const struct acl_backend_vfile_acllist *acllist;

	acl_backend_vfile_acllist_refresh(backend);
	acllist = acl_backend_vfile_acllist_find(backend, name);
	if (acllist != NULL && acllist->mtime != mtime &&
	    !backend->rebuilding_acllist)
		(void)acl_backend_vfile_acllist_rebuild(backend);
}

struct acl_mailbox_list_context *
acl_backend_vfile_nonowner_iter_init(struct acl_backend *_backend)
{
	struct acl_backend_vfile *backend =
		(struct acl_backend_vfile *)_backend;
	struct acl_mailbox_list_context_vfile *ctx;

	acl_backend_vfile_acllist_refresh(backend);

	ctx = i_new(struct acl_mailbox_list_context_vfile, 1);
	ctx->ctx.backend = _backend;
	ctx->acllist_change_counter = backend->acllist_change_counter;
	return &ctx->ctx;
}

int acl_backend_vfile_nonowner_iter_next(struct acl_mailbox_list_context *_ctx,
					 const char **name_r)
{
	struct acl_mailbox_list_context_vfile *ctx =
		(struct acl_mailbox_list_context_vfile *)_ctx;
	struct acl_backend_vfile *backend =
		(struct acl_backend_vfile *)_ctx->backend;
	const struct acl_backend_vfile_acllist *acllist;
	unsigned int count;

	if (ctx->acllist_change_counter != backend->acllist_change_counter)
		return -1;

	acllist = array_get(&backend->acllist, &count);
	if (ctx->idx == count)
		return 0;

	*name_r = acllist[ctx->idx++].name;
	return 1;
}

void
acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx)
{
	i_free(ctx);
}