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