summaryrefslogtreecommitdiffstats
path: root/src/imap/cmd-select.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap/cmd-select.c')
-rw-r--r--src/imap/cmd-select.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/src/imap/cmd-select.c b/src/imap/cmd-select.c
new file mode 100644
index 0000000..c9af870
--- /dev/null
+++ b/src/imap/cmd-select.c
@@ -0,0 +1,426 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "seq-range-array.h"
+#include "time-util.h"
+#include "imap-commands.h"
+#include "mail-search-build.h"
+#include "imap-search-args.h"
+#include "imap-seqset.h"
+#include "imap-fetch.h"
+#include "imap-sync.h"
+
+
+struct imap_select_context {
+ struct client_command_context *cmd;
+ struct mail_namespace *ns;
+ struct mailbox *box;
+
+ struct imap_fetch_context *fetch_ctx;
+
+ uint32_t qresync_uid_validity;
+ uint64_t qresync_modseq;
+ ARRAY_TYPE(seq_range) qresync_known_uids;
+ ARRAY_TYPE(uint32_t) qresync_sample_seqset;
+ ARRAY_TYPE(uint32_t) qresync_sample_uidset;
+
+ bool condstore:1;
+};
+
+static int select_qresync_get_uids(struct imap_select_context *ctx,
+ const ARRAY_TYPE(seq_range) *seqset,
+ const ARRAY_TYPE(seq_range) *uidset)
+{
+ const struct seq_range *uid_range;
+ struct seq_range_iter seq_iter;
+ unsigned int i, uid_count, diff, n = 0;
+ uint32_t seq;
+
+ /* change all n:m ranges to n,m and store the results */
+ uid_range = array_get(uidset, &uid_count);
+
+ seq_range_array_iter_init(&seq_iter, seqset);
+ i_array_init(&ctx->qresync_sample_uidset, uid_count);
+ i_array_init(&ctx->qresync_sample_seqset, uid_count);
+ for (i = 0; i < uid_count; i++) {
+ if (!seq_range_array_iter_nth(&seq_iter, n++, &seq))
+ return -1;
+ array_push_back(&ctx->qresync_sample_uidset,
+ &uid_range[i].seq1);
+ array_push_back(&ctx->qresync_sample_seqset, &seq);
+
+ diff = uid_range[i].seq2 - uid_range[i].seq1;
+ if (diff > 0) {
+ n += diff - 1;
+ if (!seq_range_array_iter_nth(&seq_iter, n++, &seq))
+ return -1;
+
+ array_push_back(&ctx->qresync_sample_uidset,
+ &uid_range[i].seq2);
+ array_push_back(&ctx->qresync_sample_seqset, &seq);
+ }
+ }
+ if (seq_range_array_iter_nth(&seq_iter, n, &seq))
+ return -1;
+ return 0;
+}
+
+static bool
+select_parse_qresync_known_set(struct imap_select_context *ctx,
+ const struct imap_arg *args,
+ const char **error_r)
+{
+ ARRAY_TYPE(seq_range) seqset, uidset;
+ const char *str;
+
+ t_array_init(&seqset, 32);
+ if (!imap_arg_get_atom(args, &str) ||
+ imap_seq_set_nostar_parse(str, &seqset) < 0) {
+ *error_r = "Invalid QRESYNC known-sequence-set";
+ return FALSE;
+ }
+ args++;
+
+ t_array_init(&uidset, 32);
+ if (!imap_arg_get_atom(args, &str) ||
+ imap_seq_set_nostar_parse(str, &uidset) < 0) {
+ *error_r = "Invalid QRESYNC known-uid-set";
+ return FALSE;
+ }
+ args++;
+
+ if (select_qresync_get_uids(ctx, &seqset, &uidset) < 0) {
+ *error_r = "Invalid QRESYNC sets";
+ return FALSE;
+ }
+ if (!IMAP_ARG_IS_EOL(args)) {
+ *error_r = "Too many parameters to QRESYNC known set";
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+select_parse_qresync(struct imap_select_context *ctx,
+ const struct imap_arg *args, const char **error_r)
+{
+ const struct imap_arg *list_args;
+ const char *str;
+ unsigned int count;
+
+ if (!client_has_enabled(ctx->cmd->client, imap_feature_qresync)) {
+ *error_r = "QRESYNC not enabled";
+ return FALSE;
+ }
+ if (!imap_arg_get_list_full(args, &args, &count)) {
+ *error_r = "QRESYNC parameters missing";
+ return FALSE;
+ }
+
+ if (!imap_arg_get_atom(&args[0], &str) ||
+ str_to_uint32(str, &ctx->qresync_uid_validity) < 0 ||
+ !imap_arg_get_atom(&args[1], &str) ||
+ str_to_uint64(str, &ctx->qresync_modseq) < 0) {
+ *error_r = "Invalid QRESYNC parameters";
+ return FALSE;
+ }
+ args += 2;
+
+ i_array_init(&ctx->qresync_known_uids, 64);
+ if (imap_arg_get_atom(args, &str)) {
+ if (imap_seq_set_nostar_parse(str, &ctx->qresync_known_uids) < 0) {
+ *error_r = "Invalid QRESYNC known-uids";
+ return FALSE;
+ }
+ args++;
+ } else {
+ seq_range_array_add_range(&ctx->qresync_known_uids,
+ 1, (uint32_t)-1);
+ }
+ if (imap_arg_get_list(args, &list_args)) {
+ if (!select_parse_qresync_known_set(ctx, list_args, error_r))
+ return FALSE;
+ args++;
+ }
+ if (!IMAP_ARG_IS_EOL(args)) {
+ *error_r = "Invalid QRESYNC parameters";
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+select_parse_options(struct imap_select_context *ctx,
+ const struct imap_arg *args, const char **error_r)
+{
+ const char *name;
+
+ while (!IMAP_ARG_IS_EOL(args)) {
+ if (!imap_arg_get_atom(args, &name)) {
+ *error_r = "SELECT options contain non-atoms.";
+ return FALSE;
+ }
+ name = t_str_ucase(name);
+ args++;
+
+ if (strcmp(name, "CONDSTORE") == 0)
+ ctx->condstore = TRUE;
+ else if (strcmp(name, "QRESYNC") == 0) {
+ if (!select_parse_qresync(ctx, args, error_r))
+ return FALSE;
+ args++;
+ } else {
+ *error_r = "Unknown FETCH modifier";
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void select_context_free(struct imap_select_context *ctx)
+{
+ if (array_is_created(&ctx->qresync_known_uids))
+ array_free(&ctx->qresync_known_uids);
+ if (array_is_created(&ctx->qresync_sample_seqset))
+ array_free(&ctx->qresync_sample_seqset);
+ if (array_is_created(&ctx->qresync_sample_uidset))
+ array_free(&ctx->qresync_sample_uidset);
+}
+
+static void cmd_select_finish(struct imap_select_context *ctx, int ret)
+{
+ const char *resp_code;
+
+ if (ret < 0) {
+ if (ctx->box != NULL)
+ mailbox_free(&ctx->box);
+ ctx->cmd->client->mailbox = NULL;
+ } else {
+ resp_code = mailbox_is_readonly(ctx->box) ?
+ "READ-ONLY" : "READ-WRITE";
+ client_send_tagline(ctx->cmd, t_strdup_printf(
+ "OK [%s] %s completed", resp_code,
+ ctx->cmd->client->mailbox_examined ? "Examine" : "Select"));
+ }
+ select_context_free(ctx);
+}
+
+static bool cmd_select_continue(struct client_command_context *cmd)
+{
+ struct imap_select_context *ctx = cmd->context;
+ int ret;
+
+ if (imap_fetch_more(ctx->fetch_ctx, cmd) == 0) {
+ /* unfinished */
+ return FALSE;
+ }
+
+ ret = imap_fetch_end(ctx->fetch_ctx);
+ if (ret < 0)
+ client_send_box_error(ctx->cmd, ctx->box);
+ imap_fetch_free(&ctx->fetch_ctx);
+ cmd_select_finish(ctx, ret);
+ return TRUE;
+}
+
+static int select_qresync(struct imap_select_context *ctx)
+{
+ struct imap_fetch_context *fetch_ctx;
+ struct mail_search_args *search_args;
+ struct imap_fetch_qresync_args qresync_args;
+ int ret;
+
+ search_args = mail_search_build_init();
+ search_args->args = p_new(search_args->pool, struct mail_search_arg, 1);
+ search_args->args->type = SEARCH_UIDSET;
+ search_args->args->value.seqset = ctx->qresync_known_uids;
+ imap_search_add_changed_since(search_args, ctx->qresync_modseq);
+
+ i_zero(&qresync_args);
+ qresync_args.qresync_sample_seqset = &ctx->qresync_sample_seqset;
+ qresync_args.qresync_sample_uidset = &ctx->qresync_sample_uidset;
+
+ if (imap_fetch_send_vanished(ctx->cmd->client, ctx->box,
+ search_args, &qresync_args) < 0) {
+ mail_search_args_unref(&search_args);
+ return -1;
+ }
+
+ fetch_ctx = imap_fetch_alloc(ctx->cmd->client, ctx->cmd->pool,
+ t_strdup_printf("%s %s", ctx->cmd->name, ctx->cmd->args));
+
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_uid_init);
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_flags_init);
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_modseq_init);
+
+ imap_fetch_begin(fetch_ctx, ctx->box, search_args);
+ mail_search_args_unref(&search_args);
+
+ if (imap_fetch_more(fetch_ctx, ctx->cmd) == 0) {
+ /* unfinished */
+ ctx->fetch_ctx = fetch_ctx;
+ ctx->cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+
+ ctx->cmd->func = cmd_select_continue;
+ ctx->cmd->context = ctx;
+ return 0;
+ }
+ ret = imap_fetch_end(fetch_ctx);
+ imap_fetch_free(&fetch_ctx);
+ return ret < 0 ? -1 : 1;
+}
+
+static int
+select_open(struct imap_select_context *ctx, const char *mailbox, bool readonly)
+{
+ struct client *client = ctx->cmd->client;
+ struct mailbox_status status;
+ enum mailbox_flags flags = 0;
+ int ret = 0;
+
+ if (readonly)
+ flags |= MAILBOX_FLAG_READONLY;
+ else
+ flags |= MAILBOX_FLAG_DROP_RECENT;
+ ctx->box = mailbox_alloc(ctx->ns->list, mailbox, flags);
+ event_add_str(ctx->cmd->global_event, "mailbox",
+ mailbox_get_vname(ctx->box));
+ if (mailbox_open(ctx->box) < 0) {
+ client_send_box_error(ctx->cmd, ctx->box);
+ mailbox_free(&ctx->box);
+ return -1;
+ }
+
+ ret = mailbox_enable(ctx->box, client_enabled_mailbox_features(client));
+ if (ret < 0 ||
+ mailbox_sync(ctx->box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) {
+ client_send_box_error(ctx->cmd, ctx->box);
+ return -1;
+ }
+ mailbox_get_open_status(ctx->box, STATUS_MESSAGES | STATUS_RECENT |
+ STATUS_FIRST_UNSEEN_SEQ | STATUS_UIDVALIDITY |
+ STATUS_UIDNEXT | STATUS_KEYWORDS |
+ STATUS_HIGHESTMODSEQ, &status);
+
+ client->mailbox = ctx->box;
+ client->mailbox_examined = readonly;
+ client->messages_count = status.messages;
+ client->recent_count = status.recent;
+ client->uidvalidity = status.uidvalidity;
+ client->notify_uidnext = status.uidnext;
+
+ client_update_mailbox_flags(client, status.keywords);
+ client_send_mailbox_flags(client, TRUE);
+
+ client_send_line(client,
+ t_strdup_printf("* %u EXISTS", status.messages));
+ client_send_line(client,
+ t_strdup_printf("* %u RECENT", status.recent));
+
+ if (status.first_unseen_seq != 0) {
+ client_send_line(client,
+ t_strdup_printf("* OK [UNSEEN %u] First unseen.",
+ status.first_unseen_seq));
+ }
+
+ client_send_line(client,
+ t_strdup_printf("* OK [UIDVALIDITY %u] UIDs valid",
+ status.uidvalidity));
+
+ client_send_line(client,
+ t_strdup_printf("* OK [UIDNEXT %u] Predicted next UID",
+ status.uidnext));
+
+ client->nonpermanent_modseqs = status.nonpermanent_modseqs;
+ if (status.nonpermanent_modseqs) {
+ client_send_line(client,
+ "* OK [NOMODSEQ] No permanent modsequences");
+ } else if (!status.no_modseq_tracking) {
+ client_send_line(client,
+ t_strdup_printf("* OK [HIGHESTMODSEQ %"PRIu64"] Highest",
+ status.highest_modseq));
+ client->sync_last_full_modseq = status.highest_modseq;
+ }
+
+ if (ctx->qresync_uid_validity == status.uidvalidity &&
+ status.uidvalidity != 0 && !client->nonpermanent_modseqs) {
+ if ((ret = select_qresync(ctx)) < 0) {
+ client_send_box_error(ctx->cmd, ctx->box);
+ return -1;
+ }
+ } else {
+ ret = 1;
+ }
+ return ret;
+}
+
+static void close_selected_mailbox(struct client *client)
+{
+ if (client->mailbox == NULL)
+ return;
+
+ imap_client_close_mailbox(client);
+ /* CLOSED response is required by QRESYNC */
+ client_send_line(client, "* OK [CLOSED] Previous mailbox closed.");
+}
+
+bool cmd_select_full(struct client_command_context *cmd, bool readonly)
+{
+ struct client *client = cmd->client;
+ struct imap_select_context *ctx;
+ const struct imap_arg *args, *list_args;
+ const char *mailbox, *client_error;
+ int ret;
+
+ /* <mailbox> [(optional parameters)] */
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!imap_arg_get_astring(args, &mailbox)) {
+ close_selected_mailbox(client);
+ client_send_command_error(cmd, "Invalid arguments.");
+ return FALSE;
+ }
+
+ ctx = p_new(cmd->pool, struct imap_select_context, 1);
+ ctx->cmd = cmd;
+ ctx->ns = client_find_namespace_full(cmd->client, &mailbox, &client_error);
+ if (ctx->ns == NULL) {
+ /* send * OK [CLOSED] before the tagged reply */
+ close_selected_mailbox(client);
+ client_send_tagline(cmd, client_error);
+ return TRUE;
+ }
+
+ if (imap_arg_get_list(&args[1], &list_args)) {
+ if (!select_parse_options(ctx, list_args, &client_error)) {
+ select_context_free(ctx);
+ /* send * OK [CLOSED] before the tagged reply */
+ close_selected_mailbox(client);
+ client_send_command_error(ctx->cmd, client_error);
+ return TRUE;
+ }
+ }
+
+ i_assert(client->mailbox_change_lock == NULL);
+ client->mailbox_change_lock = cmd;
+
+ close_selected_mailbox(client);
+
+ if (ctx->condstore) {
+ /* Enable while no mailbox is opened to avoid sending
+ HIGHESTMODSEQ for previously opened mailbox */
+ client_enable(client, imap_feature_condstore);
+ }
+
+ ret = select_open(ctx, mailbox, readonly);
+ if (ret == 0)
+ return FALSE;
+ cmd_select_finish(ctx, ret);
+ return TRUE;
+}
+
+bool cmd_select(struct client_command_context *cmd)
+{
+ return cmd_select_full(cmd, FALSE);
+}