subscription-file.c revision 4b11f96880f61e5583b9a22b89e1c166710016f9
0N/A/* Copyright (c) 2002-2015 Dovecot authors, see the included COPYING file */
0N/A
0N/A#include "lib.h"
0N/A#include "str.h"
0N/A#include "strescape.h"
0N/A#include "istream.h"
0N/A#include "ostream.h"
0N/A#include "nfs-workarounds.h"
0N/A#include "mkdir-parents.h"
0N/A#include "file-dotlock.h"
0N/A#include "mailbox-list-private.h"
0N/A#include "subscription-file.h"
0N/A
0N/A#include <unistd.h>
0N/A#include <fcntl.h>
0N/A
0N/A#define SUBSCRIPTION_FILE_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT
0N/A#define SUBSCRIPTION_FILE_LOCK_TIMEOUT 120
0N/A#define SUBSCRIPTION_FILE_CHANGE_TIMEOUT 30
0N/A
0N/Astruct subsfile_list_context {
0N/A struct mailbox_list *list;
0N/A struct istream *input;
0N/A char *path;
0N/A string_t *name;
0N/A
0N/A unsigned int version;
0N/A bool failed;
0N/A};
0N/A
0N/Astatic const char version2_header[] = "V\t2\n\n";
0N/A
0N/Astatic void subsread_set_syscall_error(struct mailbox_list *list,
0N/A const char *function, const char *path)
0N/A{
0N/A if (errno == EACCES && !list->mail_set->mail_debug) {
0N/A mailbox_list_set_error(list, MAIL_ERROR_PERM,
0N/A "No permission to read subscriptions");
0N/A } else {
0N/A mailbox_list_set_critical(list,
0N/A "%s failed with subscription file %s: %m",
0N/A function, path);
0N/A }
0N/A}
0N/A
0N/Astatic void subswrite_set_syscall_error(struct mailbox_list *list,
0N/A const char *function, const char *path)
0N/A{
0N/A if (errno == EACCES && !list->mail_set->mail_debug) {
0N/A mailbox_list_set_error(list, MAIL_ERROR_PERM,
0N/A "No permission to modify subscriptions");
0N/A } else {
0N/A mailbox_list_set_critical(list,
0N/A "%s failed with subscription file %s: %m",
0N/A function, path);
0N/A }
0N/A}
0N/A
0N/Astatic void
0N/Asubsfile_list_read_header(struct mailbox_list *list, struct istream *input,
0N/A unsigned int *version_r)
0N/A{
0N/A const unsigned char version2_header_len = strlen(version2_header);
0N/A const unsigned char *data;
0N/A size_t size;
0N/A int ret;
0N/A
0N/A *version_r = 0;
0N/A
0N/A ret = i_stream_read_data(input, &data, &size, version2_header_len-1);
0N/A if (ret < 0) {
0N/A i_assert(ret == -1);
0N/A subswrite_set_syscall_error(list, "read()", i_stream_get_name(input));
0N/A return;
0N/A }
0N/A if (memcmp(data, version2_header, version2_header_len) == 0) {
0N/A *version_r = 2;
0N/A i_stream_skip(input, version2_header_len);
0N/A }
0N/A}
0N/A
0N/Astatic const char *next_line(struct mailbox_list *list, const char *path,
0N/A struct istream *input, bool *failed_r,
0N/A bool ignore_estale)
0N/A{
0N/A const char *line;
0N/A
0N/A *failed_r = FALSE;
0N/A
0N/A while ((line = i_stream_next_line(input)) == NULL) {
0N/A switch (i_stream_read(input)) {
0N/A case -1:
0N/A if (input->stream_errno != 0 &&
0N/A (input->stream_errno != ESTALE || !ignore_estale)) {
0N/A subswrite_set_syscall_error(list,
0N/A "read()", path);
0N/A *failed_r = TRUE;
0N/A }
0N/A return NULL;
0N/A case -2:
0N/A /* mailbox name too large */
0N/A mailbox_list_set_critical(list,
0N/A "Subscription file %s contains lines longer "
0N/A "than %u characters", path,
0N/A (unsigned int)list->mailbox_name_max_length);
0N/A *failed_r = TRUE;
0N/A return NULL;
0N/A }
0N/A }
0N/A
0N/A return line;
0N/A}
0N/A
0N/Aint subsfile_set_subscribed(struct mailbox_list *list, const char *path,
0N/A const char *temp_prefix, const char *name,
0N/A bool set)
0N/A{
0N/A const struct mail_storage_settings *mail_set = list->mail_set;
0N/A struct dotlock_settings dotlock_set;
0N/A struct dotlock *dotlock;
0N/A struct mailbox_permissions perm;
0N/A const char *line, *dir, *fname, *escaped_name;
0N/A struct istream *input = NULL;
0N/A struct ostream *output;
0N/A int fd_in, fd_out;
0N/A enum mailbox_list_path_type type;
0N/A bool found, changed = FALSE, failed = FALSE;
0N/A unsigned int version = 0;
0N/A
0N/A if (strcasecmp(name, "INBOX") == 0)
0N/A name = "INBOX";
0N/A
0N/A memset(&dotlock_set, 0, sizeof(dotlock_set));
0N/A dotlock_set.use_excl_lock = mail_set->dotlock_use_excl;
0N/A dotlock_set.nfs_flush = mail_set->mail_nfs_storage;
0N/A dotlock_set.temp_prefix = temp_prefix;
0N/A dotlock_set.timeout = SUBSCRIPTION_FILE_LOCK_TIMEOUT;
0N/A dotlock_set.stale_timeout = SUBSCRIPTION_FILE_CHANGE_TIMEOUT;
0N/A
0N/A mailbox_list_get_root_permissions(list, &perm);
0N/A fd_out = file_dotlock_open_group(&dotlock_set, path, 0,
0N/A perm.file_create_mode,
0N/A perm.file_create_gid,
0N/A perm.file_create_gid_origin, &dotlock);
0N/A if (fd_out == -1 && errno == ENOENT) {
0N/A /* directory hasn't been created yet. */
0N/A type = list->set.control_dir != NULL ?
0N/A MAILBOX_LIST_PATH_TYPE_CONTROL :
0N/A MAILBOX_LIST_PATH_TYPE_DIR;
0N/A fname = strrchr(path, '/');
0N/A if (fname != NULL) {
0N/A dir = t_strdup_until(path, fname);
0N/A if (mailbox_list_mkdir_root(list, dir, type) < 0)
0N/A return -1;
0N/A }
0N/A fd_out = file_dotlock_open_group(&dotlock_set, path, 0,
0N/A perm.file_create_mode,
0N/A perm.file_create_gid,
0N/A perm.file_create_gid_origin,
0N/A &dotlock);
0N/A }
0N/A if (fd_out == -1) {
0N/A if (errno == EAGAIN) {
0N/A mailbox_list_set_error(list, MAIL_ERROR_TEMP,
0N/A "Timeout waiting for subscription file lock");
0N/A } else {
0N/A subswrite_set_syscall_error(list, "file_dotlock_open()",
0N/A path);
0N/A }
0N/A return -1;
0N/A }
0N/A
0N/A fd_in = nfs_safe_open(path, O_RDONLY);
0N/A if (fd_in == -1 && errno != ENOENT) {
0N/A subswrite_set_syscall_error(list, "open()", path);
0N/A file_dotlock_delete(&dotlock);
0N/A return -1;
0N/A }
0N/A if (fd_in != -1) {
0N/A input = i_stream_create_fd_autoclose(&fd_in, list->mailbox_name_max_length+1);
0N/A i_stream_set_return_partial_line(input, TRUE);
0N/A subsfile_list_read_header(list, input, &version);
0N/A }
0N/A
0N/A found = FALSE;
0N/A output = o_stream_create_fd_file(fd_out, 0, FALSE);
0N/A o_stream_cork(output);
0N/A if (version >= 2)
0N/A o_stream_send_str(output, version2_header);
0N/A if (version < 2 || name[0] == '\0')
0N/A escaped_name = name;
0N/A else {
0N/A const char *const *tmp;
0N/A char separators[2];
0N/A string_t *str = t_str_new(64);
0N/A
0N/A separators[0] = mailbox_list_get_hierarchy_sep(list);
0N/A separators[1] = '\0';
0N/A tmp = t_strsplit(name, separators);
0N/A str_append_tabescaped(str, *tmp);
0N/A for (tmp++; *tmp != NULL; tmp++) {
0N/A str_append_c(str, '\t');
0N/A str_append_tabescaped(str, *tmp);
0N/A }
0N/A escaped_name = str_c(str);
0N/A }
0N/A if (input != NULL) {
0N/A while ((line = next_line(list, path, input,
0N/A &failed, FALSE)) != NULL) {
0N/A if (strcmp(line, escaped_name) == 0) {
0N/A found = TRUE;
0N/A if (!set) {
0N/A changed = TRUE;
0N/A continue;
0N/A }
0N/A }
0N/A
0N/A o_stream_nsend_str(output, line);
0N/A o_stream_nsend(output, "\n", 1);
0N/A }
0N/A i_stream_destroy(&input);
0N/A }
0N/A
0N/A if (!failed && set && !found) {
0N/A /* append subscription */
0N/A line = t_strconcat(escaped_name, "\n", NULL);
0N/A o_stream_nsend_str(output, line);
0N/A changed = TRUE;
0N/A }
0N/A
0N/A if (changed && !failed) {
0N/A if (o_stream_nfinish(output) < 0) {
0N/A subswrite_set_syscall_error(list, "write()", path);
0N/A failed = TRUE;
0N/A } else if (mail_set->parsed_fsync_mode != FSYNC_MODE_NEVER) {
0N/A if (fsync(fd_out) < 0) {
0N/A subswrite_set_syscall_error(list, "fsync()",
0N/A path);
0N/A failed = TRUE;
0N/A }
0N/A }
0N/A } else {
0N/A o_stream_ignore_last_errors(output);
0N/A }
0N/A o_stream_destroy(&output);
0N/A
0N/A if (failed || !changed) {
0N/A if (file_dotlock_delete(&dotlock) < 0) {
0N/A subswrite_set_syscall_error(list,
0N/A "file_dotlock_delete()", path);
0N/A failed = TRUE;
0N/A }
0N/A } else {
0N/A enum dotlock_replace_flags flags =
0N/A DOTLOCK_REPLACE_FLAG_VERIFY_OWNER;
0N/A if (file_dotlock_replace(&dotlock, flags) < 0) {
0N/A subswrite_set_syscall_error(list,
0N/A "file_dotlock_replace()", path);
0N/A failed = TRUE;
0N/A }
0N/A }
0N/A return failed ? -1 : (changed ? 1 : 0);
0N/A}
0N/A
0N/Astruct subsfile_list_context *
0N/Asubsfile_list_init(struct mailbox_list *list, const char *path)
0N/A{
0N/A struct subsfile_list_context *ctx;
0N/A int fd;
0N/A
0N/A ctx = i_new(struct subsfile_list_context, 1);
0N/A ctx->list = list;
0N/A
0N/A fd = nfs_safe_open(path, O_RDONLY);
0N/A if (fd == -1) {
0N/A if (errno != ENOENT) {
0N/A subsread_set_syscall_error(list, "open()", path);
0N/A ctx->failed = TRUE;
0N/A }
0N/A } else {
0N/A ctx->input = i_stream_create_fd_autoclose(&fd,
0N/A list->mailbox_name_max_length+1);
0N/A i_stream_set_return_partial_line(ctx->input, TRUE);
0N/A subsfile_list_read_header(ctx->list, ctx->input, &ctx->version);
0N/A }
0N/A ctx->path = i_strdup(path);
0N/A ctx->name = str_new(default_pool, 128);
0N/A return ctx;
0N/A}
0N/A
0N/Aint subsfile_list_deinit(struct subsfile_list_context **_ctx)
0N/A{
0N/A struct subsfile_list_context *ctx = *_ctx;
0N/A int ret = ctx->failed ? -1 : 0;
0N/A
0N/A *_ctx = NULL;
0N/A
0N/A if (ctx->input != NULL)
0N/A i_stream_destroy(&ctx->input);
0N/A str_free(&ctx->name);
0N/A i_free(ctx->path);
0N/A i_free(ctx);
0N/A return ret;
0N/A}
0N/A
0N/Aint subsfile_list_fstat(struct subsfile_list_context *ctx, struct stat *st_r)
0N/A{
0N/A const struct stat *st;
0N/A
0N/A if (ctx->failed)
0N/A return -1;
0N/A
0N/A if (i_stream_stat(ctx->input, FALSE, &st) < 0) {
0N/A ctx->failed = TRUE;
0N/A return -1;
0N/A }
0N/A *st_r = *st;
0N/A return 0;
0N/A}
0N/A
0N/Astatic const char *
0N/Asubsfile_list_unescaped(struct subsfile_list_context *ctx, const char *line)
0N/A{
0N/A const char *p;
0N/A
0N/A str_truncate(ctx->name, 0);
0N/A while ((p = strchr(line, '\t')) != NULL) {
0N/A str_append_tabunescaped(ctx->name, line, p-line);
0N/A str_append_c(ctx->name, mailbox_list_get_hierarchy_sep(ctx->list));
0N/A line = p+1;
0N/A }
0N/A str_append_tabunescaped(ctx->name, line, strlen(line));
0N/A return str_c(ctx->name);
0N/A}
0N/A
0N/Aconst char *subsfile_list_next(struct subsfile_list_context *ctx)
0N/A{
0N/A const char *line;
0N/A unsigned int i;
0N/A int fd;
0N/A
0N/A if (ctx->failed || ctx->input == NULL)
0N/A return NULL;
0N/A
0N/A for (i = 0;; i++) {
0N/A line = next_line(ctx->list, ctx->path, ctx->input, &ctx->failed,
0N/A i < SUBSCRIPTION_FILE_ESTALE_RETRY_COUNT);
0N/A if (ctx->input->stream_errno != ESTALE ||
0N/A i == SUBSCRIPTION_FILE_ESTALE_RETRY_COUNT)
0N/A break;
0N/A
0N/A /* Reopen the subscription file and re-send everything.
0N/A this isn't the optimal behavior, but it's allowed by
0N/A IMAP and this way we don't have to read everything into
0N/A memory or try to play any guessing games. */
0N/A i_stream_destroy(&ctx->input);
0N/A
0N/A fd = nfs_safe_open(ctx->path, O_RDONLY);
0N/A if (fd == -1) {
0N/A /* In case of ENOENT all the subscriptions got lost.
0N/A Just return end of subscriptions list in that
0N/A case. */
0N/A if (errno != ENOENT) {
0N/A subsread_set_syscall_error(ctx->list, "open()",
0N/A ctx->path);
0N/A ctx->failed = TRUE;
0N/A }
0N/A return NULL;
0N/A }
0N/A
0N/A ctx->input = i_stream_create_fd_autoclose(&fd,
0N/A ctx->list->mailbox_name_max_length+1);
0N/A i_stream_set_return_partial_line(ctx->input, TRUE);
0N/A }
0N/A
0N/A if (ctx->version > 1 && line != NULL)
0N/A line = subsfile_list_unescaped(ctx, line);
0N/A return line;
0N/A}
0N/A