diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
commit | 0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch) | |
tree | 3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/imap/imap-notify.c | |
parent | Initial commit. (diff) | |
download | dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip |
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/imap/imap-notify.c')
-rw-r--r-- | src/imap/imap-notify.c | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/src/imap/imap-notify.c b/src/imap/imap-notify.c new file mode 100644 index 0000000..fa2c9ef --- /dev/null +++ b/src/imap/imap-notify.c @@ -0,0 +1,523 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" +#include "ostream.h" +#include "imap-quote.h" +#include "mailbox-list-iter.h" +#include "mailbox-list-notify.h" +#include "mail-search.h" +#include "mail-search-build.h" +#include "imap-commands.h" +#include "imap-fetch.h" +#include "imap-list.h" +#include "imap-status.h" +#include "imap-notify.h" + +#define IMAP_NOTIFY_WATCH_ADD_DELAY_MSECS 1000 + +static int imap_notify_list(struct imap_notify_namespace *notify_ns, + const struct mailbox_list_notify_rec *rec, + enum mailbox_info_flags flags) +{ + string_t *str = t_str_new(128); + char ns_sep = mail_namespace_get_sep(notify_ns->ns); + + str_append(str, "* LIST ("); + imap_mailbox_flags2str(str, flags); + str_append(str, ") \""); + if (ns_sep == '\\') + str_append_c(str, '\\'); + str_append_c(str, ns_sep); + str_append(str, "\" "); + + imap_append_astring(str, rec->vname); + if (rec->old_vname != NULL) { + str_append(str, " (\"OLDNAME\" ("); + imap_append_astring(str, rec->old_vname); + str_append(str, "))"); + } + return client_send_line_next(notify_ns->ctx->client, str_c(str)); +} + +static int imap_notify_status(struct imap_notify_namespace *notify_ns, + const struct mailbox_list_notify_rec *rec) +{ + struct client *client = notify_ns->ctx->client; + struct mailbox *box; + struct imap_status_items items; + struct imap_status_result result; + enum mail_error error; + int ret = 1; + + i_zero(&items); + if (client_has_enabled(client, imap_feature_condstore)) + items.flags |= IMAP_STATUS_ITEM_HIGHESTMODSEQ; + + box = mailbox_alloc(notify_ns->ns->list, rec->vname, 0); + if ((rec->events & MAILBOX_LIST_NOTIFY_UIDVALIDITY) != 0) { + items.flags |= IMAP_STATUS_ITEM_UIDVALIDITY | + IMAP_STATUS_ITEM_UIDNEXT | IMAP_STATUS_ITEM_MESSAGES | + IMAP_STATUS_ITEM_UNSEEN; + } + if ((rec->events & (MAILBOX_LIST_NOTIFY_APPENDS | + MAILBOX_LIST_NOTIFY_EXPUNGES)) != 0) + items.flags |= IMAP_STATUS_ITEM_UIDNEXT | + IMAP_STATUS_ITEM_MESSAGES | IMAP_STATUS_ITEM_UNSEEN; + if ((rec->events & MAILBOX_LIST_NOTIFY_SEEN_CHANGES) != 0) + items.flags |= IMAP_STATUS_ITEM_UNSEEN; + if ((rec->events & MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES) != 0) { + /* if HIGHESTMODSEQ isn't being sent, don't send anything */ + } + if (imap_status_items_is_empty(&items)) { + /* don't send anything */ + } else if (imap_status_get_result(client, box, &items, &result) < 0) { + /* hide permission errors from client. we don't want to leak + information about existence of mailboxes where user doesn't + have access to */ + (void)mailbox_get_last_error(box, &error); + if (error != MAIL_ERROR_PERM) + ret = -1; + } else { + ret = imap_status_send(client, rec->vname, &items, &result); + } + mailbox_free(&box); + return ret; +} + +static int +imap_notify_next(struct imap_notify_namespace *notify_ns, + const struct mailbox_list_notify_rec *rec) +{ + enum mailbox_info_flags mailbox_flags; + int ret; + + if ((rec->events & MAILBOX_LIST_NOTIFY_CREATE) != 0) { + if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name, + &mailbox_flags) < 0) + mailbox_flags = 0; + if ((ret = imap_notify_list(notify_ns, rec, mailbox_flags)) <= 0) + return ret; + } + if ((rec->events & MAILBOX_LIST_NOTIFY_DELETE) != 0) { + if ((ret = imap_notify_list(notify_ns, rec, MAILBOX_NONEXISTENT)) < 0) + return ret; + } + if ((rec->events & MAILBOX_LIST_NOTIFY_RENAME) != 0) { + if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name, + &mailbox_flags) < 0) + mailbox_flags = 0; + if ((ret = imap_notify_list(notify_ns, rec, mailbox_flags)) < 0) + return ret; + } + if ((rec->events & MAILBOX_LIST_NOTIFY_SUBSCRIBE) != 0) { + if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name, + &mailbox_flags) < 0) + mailbox_flags = 0; + if ((ret = imap_notify_list(notify_ns, rec, + mailbox_flags | MAILBOX_SUBSCRIBED)) < 0) + return ret; + } + if ((rec->events & MAILBOX_LIST_NOTIFY_UNSUBSCRIBE) != 0) { + if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name, + &mailbox_flags) < 0) + mailbox_flags = 0; + if ((ret = imap_notify_list(notify_ns, rec, mailbox_flags)) < 0) + return ret; + } + if ((rec->events & (MAILBOX_LIST_NOTIFY_UIDVALIDITY | + MAILBOX_LIST_NOTIFY_APPENDS | + MAILBOX_LIST_NOTIFY_EXPUNGES | + MAILBOX_LIST_NOTIFY_SEEN_CHANGES | + MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES)) != 0) { + if ((ret = imap_notify_status(notify_ns, rec)) < 0) + return ret; + } + return 1; +} + +static bool +imap_notify_match_event(struct imap_notify_namespace *notify_ns, + const struct imap_notify_mailboxes *notify_boxes, + const struct mailbox_list_notify_rec *rec) +{ + enum imap_notify_event wanted_events = notify_boxes->events; + struct mailbox *box; + + /* check for mailbox list events first */ + if ((wanted_events & IMAP_NOTIFY_EVENT_MAILBOX_NAME) != 0) { + if ((rec->events & (MAILBOX_LIST_NOTIFY_CREATE | + MAILBOX_LIST_NOTIFY_DELETE | + MAILBOX_LIST_NOTIFY_RENAME)) != 0) + return TRUE; + } + if ((wanted_events & IMAP_NOTIFY_EVENT_SUBSCRIPTION_CHANGE) != 0) { + if ((rec->events & (MAILBOX_LIST_NOTIFY_SUBSCRIBE | + MAILBOX_LIST_NOTIFY_UNSUBSCRIBE)) != 0) + return TRUE; + } + + /* if this is an event for the selected mailbox, ignore it */ + box = notify_ns->ctx->client->mailbox; + if (box != NULL && mailbox_equals(box, notify_ns->ns, rec->vname)) + return FALSE; + + if ((wanted_events & (IMAP_NOTIFY_EVENT_MESSAGE_NEW | + IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE | + IMAP_NOTIFY_EVENT_FLAG_CHANGE)) != 0) { + if ((rec->events & MAILBOX_LIST_NOTIFY_UIDVALIDITY) != 0) + return TRUE; + } + if ((wanted_events & IMAP_NOTIFY_EVENT_MESSAGE_NEW) != 0) { + if ((rec->events & MAILBOX_LIST_NOTIFY_APPENDS) != 0) + return TRUE; + } + if ((wanted_events & IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) != 0) { + if ((rec->events & MAILBOX_LIST_NOTIFY_EXPUNGES) != 0) + return TRUE; + } + if ((wanted_events & IMAP_NOTIFY_EVENT_FLAG_CHANGE) != 0) { + if ((rec->events & (MAILBOX_LIST_NOTIFY_SEEN_CHANGES | + MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES)) != 0) + return TRUE; + } + return FALSE; +} + +bool imap_notify_match_mailbox(struct imap_notify_namespace *notify_ns, + const struct imap_notify_mailboxes *notify_boxes, + const char *vname) +{ + struct mailbox *box; + const char *name; + size_t name_len; + char ns_sep; + bool ret; + + switch (notify_boxes->type) { + case IMAP_NOTIFY_TYPE_SUBSCRIBED: + box = mailbox_alloc(notify_ns->ns->list, vname, 0); + ret = mailbox_is_subscribed(box); + mailbox_free(&box); + return ret; + case IMAP_NOTIFY_TYPE_SUBTREE: + ns_sep = mail_namespace_get_sep(notify_ns->ns); + array_foreach_elem(¬ify_boxes->names, name) { + name_len = strlen(name); + if (name_len == 0) { + /* everything under root. NOTIFY spec itself + doesn't define this, but we use it for + implementing "personal" */ + return TRUE; + } + if (str_begins(vname, name) && + (vname[name_len] == '\0' || + vname[name_len] == ns_sep)) + return TRUE; + } + break; + case IMAP_NOTIFY_TYPE_MAILBOX: + array_foreach_elem(¬ify_boxes->names, name) { + if (strcmp(name, vname) == 0) + return TRUE; + } + break; + } + return FALSE; +} + +static bool +imap_notify_match(struct imap_notify_namespace *notify_ns, + const struct mailbox_list_notify_rec *rec) +{ + const struct imap_notify_mailboxes *notify_boxes; + + array_foreach(¬ify_ns->mailboxes, notify_boxes) { + if (imap_notify_match_event(notify_ns, notify_boxes, rec) && + imap_notify_match_mailbox(notify_ns, notify_boxes, rec->vname)) + return TRUE; + } + return FALSE; +} + +static int imap_client_notify_ns(struct imap_notify_namespace *notify_ns) +{ + const struct mailbox_list_notify_rec *rec; + int ret, ret2 = 1; + + if (notify_ns->notify == NULL) + return 0; /* notifications not supported in this namespace */ + + while ((ret = mailbox_list_notify_next(notify_ns->notify, &rec)) > 0) { + if (imap_notify_match(notify_ns, rec)) T_BEGIN { + ret2 = imap_notify_next(notify_ns, rec); + } T_END; + if (ret2 <= 0) + break; + } + if (ret < 0) { + /* failed to get some notifications */ + return -1; + } + return ret2; +} + +static int +imap_client_notify_selected(struct client *client) +{ + struct imap_fetch_context *fetch_ctx = client->notify_ctx->fetch_ctx; + int ret; + + if (!fetch_ctx->state.fetching) + return 1; + + if ((ret = imap_fetch_more_no_lock_update(fetch_ctx)) == 0) + return 0; + /* finished the FETCH */ + if (imap_fetch_end(fetch_ctx) < 0) + return -1; + return ret; +} + +static int imap_client_notify_more(struct client *client) +{ + struct imap_notify_namespace *notify_ns; + int ret = 1; + + struct event_reason *reason = event_reason_begin("imap:notify_update"); + + /* send notifications for selected mailbox first. note that it may + leave the client's output stream in the middle of a FETCH reply. */ + if (client->notify_ctx->fetch_ctx != NULL) { + if ((ret = imap_client_notify_selected(client)) < 0) { + client->notify_ctx->fetch_ctx->state.failed = FALSE; + ret = -1; + } + } + + /* send notifications for non-selected mailboxes */ + array_foreach_modifiable(&client->notify_ctx->namespaces, notify_ns) { + if (ret == 0) + break; + if (imap_client_notify_ns(notify_ns) < 0) + ret = -1; + } + + if (ret < 0) { + client_send_line(client, + "* NO NOTIFY error, some events may have got lost"); + } + event_reason_end(&reason); + return ret; +} + +int imap_client_notify_newmails(struct client *client) +{ + struct imap_fetch_context *fetch_ctx = client->notify_ctx->fetch_ctx; + struct mailbox_status status; + struct mail_search_args *search_args; + struct mail_search_arg *arg; + + i_assert(client->mailbox != NULL); + + if (fetch_ctx == NULL) { + /* FETCH notifications not enabled in this session */ + return 1; + } + if (client->notify_ctx->notifying) + return imap_client_notify_more(client); + client->notify_ctx->notifying = TRUE; + + i_assert(!fetch_ctx->state.fetching); + + mailbox_get_open_status(client->mailbox, STATUS_UIDNEXT, &status); + + search_args = mail_search_build_init(); + arg = mail_search_build_add(search_args, SEARCH_UIDSET); + p_array_init(&arg->value.seqset, search_args->pool, 1); + seq_range_array_add_range(&arg->value.seqset, + client->notify_uidnext, status.uidnext-1); + client->notify_uidnext = status.uidnext; + + imap_fetch_begin(fetch_ctx, client->mailbox, search_args); + mail_search_args_unref(&search_args); + + return imap_client_notify_more(client); +} + +void imap_client_notify_finished(struct client *client) +{ + if (client->notify_ctx != NULL) + client->notify_ctx->notifying = FALSE; +} + +static void notify_callback(struct imap_notify_namespace *notify_ns) +{ + struct event_reason *reason = event_reason_begin("imap:notify_update"); + o_stream_cork(notify_ns->ctx->client->output); + imap_client_notify_ns(notify_ns); + o_stream_uncork(notify_ns->ctx->client->output); + event_reason_end(&reason); +} + +static enum mailbox_list_notify_event +imap_events_to_notify(enum imap_notify_event events) +{ + enum mailbox_list_notify_event ret = 0; + + if ((events & IMAP_NOTIFY_EVENT_MESSAGE_NEW) != 0) { + ret |= MAILBOX_LIST_NOTIFY_APPENDS | + MAILBOX_LIST_NOTIFY_UIDVALIDITY; + } + if ((events & IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) != 0) { + ret |= MAILBOX_LIST_NOTIFY_EXPUNGES | + MAILBOX_LIST_NOTIFY_UIDVALIDITY; + } + if ((events & IMAP_NOTIFY_EVENT_FLAG_CHANGE) != 0) { + ret |= MAILBOX_LIST_NOTIFY_SEEN_CHANGES | + MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES | + MAILBOX_LIST_NOTIFY_UIDVALIDITY; + } + if ((events & IMAP_NOTIFY_EVENT_MAILBOX_NAME) != 0) { + ret |= MAILBOX_LIST_NOTIFY_CREATE | + MAILBOX_LIST_NOTIFY_DELETE | + MAILBOX_LIST_NOTIFY_RENAME; + } + if ((events & IMAP_NOTIFY_EVENT_SUBSCRIPTION_CHANGE) != 0) { + ret |= MAILBOX_LIST_NOTIFY_SUBSCRIBE | + MAILBOX_LIST_NOTIFY_UNSUBSCRIBE; + } + return ret; +} + +static void imap_notify_callback(struct mailbox *box, struct client *client) +{ + struct client_command_context *cmd; + enum mailbox_sync_flags sync_flags = 0; + + i_assert(client->command_queue_size == 0); + i_assert(box == client->mailbox); + + /* create a fake command to handle this */ + cmd = client_command_alloc(client); + cmd->tag = "*"; + cmd->name = "NOTIFY-CALLBACK"; + client_command_init_finished(cmd); + + if (!client->notify_ctx->selected_immediate_expunges) + sync_flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES; + if (cmd_sync(cmd, sync_flags, 0, NULL)) + i_unreached(); + (void)cmd_sync_delayed(client); +} + +static void imap_notify_watch_selected_mailbox(struct client *client) +{ + i_assert(client->command_queue_size == 0); + + if (client->mailbox == NULL) { + /* mailbox not selected */ + return; + } + if (client->notify_ctx == NULL || !client->notify_ctx->selected_set) { + /* client doesn't want selected mailbox notifications */ + return; + + } + mailbox_notify_changes(client->mailbox, imap_notify_callback, client); + client->notify_ctx->watching_mailbox = TRUE; +} + +static void imap_notify_watch_timeout(struct client *client) +{ + timeout_remove(&client->notify_ctx->to_watch); + imap_notify_watch_selected_mailbox(client); +} + +void imap_client_notify_command_freed(struct client *client) +{ + struct imap_notify_context *ctx = client->notify_ctx; + + if (ctx == NULL) + return; + + if (client->command_queue_size > 0) { + /* don't add it until all commands are finished */ + i_assert(ctx->to_watch == NULL); + return; + } + + /* add mailbox watch back after a small delay. if another command + is started this timeout is aborted. */ + ctx->to_watch = timeout_add(IMAP_NOTIFY_WATCH_ADD_DELAY_MSECS, + imap_notify_watch_timeout, client); +} + +void imap_client_notify_command_allocated(struct client *client) +{ + struct imap_notify_context *ctx = client->notify_ctx; + + if (ctx == NULL) + return; + + /* remove mailbox watcher before starting any commands */ + if (ctx->watching_mailbox) { + mailbox_notify_changes_stop(client->mailbox); + ctx->watching_mailbox = FALSE; + } + timeout_remove(&ctx->to_watch); +} + +int imap_notify_begin(struct imap_notify_context *ctx) +{ + struct imap_notify_namespace *notify_ns; + const struct imap_notify_mailboxes *notify_boxes; + enum mailbox_list_notify_event notify_events; + int ret = -1; + + array_foreach_modifiable(&ctx->namespaces, notify_ns) { + notify_events = 0; + array_foreach(¬ify_ns->mailboxes, notify_boxes) { + notify_events |= + imap_events_to_notify(notify_boxes->events); + } + if (mailbox_list_notify_init(notify_ns->ns->list, notify_events, + ¬ify_ns->notify) < 0) { + /* notifications not supported */ + } else { + ret = 0; + mailbox_list_notify_wait(notify_ns->notify, + notify_callback, notify_ns); + } + } + /* enable NOTIFY as long as even one namespace supports it, + ignore the rest */ + return ret; +} + +void imap_notify_deinit(struct imap_notify_context **_ctx) +{ + struct imap_notify_context *ctx = *_ctx; + struct imap_notify_namespace *notify_ns; + + *_ctx = NULL; + + array_foreach_modifiable(&ctx->namespaces, notify_ns) { + if (notify_ns->notify != NULL) + mailbox_list_notify_deinit(¬ify_ns->notify); + } + timeout_remove(&ctx->to_watch); + if (ctx->fetch_ctx != NULL) + imap_fetch_free(&ctx->fetch_ctx); + pool_unref(&ctx->pool); +} + +void imap_notify_flush(struct imap_notify_context *ctx) +{ + struct imap_notify_namespace *notify_ns; + + array_foreach_modifiable(&ctx->namespaces, notify_ns) { + if (notify_ns->notify != NULL) + mailbox_list_notify_flush(notify_ns->notify); + } +} |