cmd-select.c revision 17e367eb70fbd5d200a4deaee062a044e1db3805
0N/A/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
0N/A
0N/A#include "common.h"
0N/A#include "seq-range-array.h"
0N/A#include "commands.h"
0N/A#include "mail-search-build.h"
0N/A#include "imap-seqset.h"
0N/A#include "imap-fetch.h"
0N/A#include "imap-sync.h"
0N/A
0N/A#include <stdlib.h>
0N/A
0N/Astruct imap_select_context {
0N/A struct client_command_context *cmd;
0N/A struct mail_storage *storage;
0N/A struct mailbox *box;
0N/A
0N/A struct imap_fetch_context *fetch_ctx;
0N/A
0N/A uint32_t qresync_uid_validity;
0N/A uint64_t qresync_modseq;
0N/A ARRAY_TYPE(seq_range) qresync_known_uids;
0N/A ARRAY_TYPE(uint32_t) qresync_sample_seqset;
0N/A ARRAY_TYPE(uint32_t) qresync_sample_uidset;
0N/A
0N/A unsigned int condstore:1;
0N/A};
0N/A
0N/Astatic int select_qresync_get_uids(struct imap_select_context *ctx,
0N/A const ARRAY_TYPE(seq_range) *seqset,
0N/A const ARRAY_TYPE(seq_range) *uidset)
0N/A{
0N/A const struct seq_range *seq_range, *uid_range;
0N/A struct seq_range_iter seq_iter;
0N/A unsigned int i, seq_count, uid_count, diff, n = 0;
0N/A uint32_t seq;
0N/A
0N/A /* change all n:m ranges to n,m and store the results */
0N/A seq_range = array_get(seqset, &seq_count);
0N/A uid_range = array_get(uidset, &uid_count);
0N/A
0N/A seq_range_array_iter_init(&seq_iter, seqset);
0N/A i_array_init(&ctx->qresync_sample_uidset, uid_count);
0N/A i_array_init(&ctx->qresync_sample_seqset, uid_count);
0N/A for (i = 0; i < uid_count; i++) {
0N/A if (!seq_range_array_iter_nth(&seq_iter, n++, &seq))
0N/A return -1;
0N/A array_append(&ctx->qresync_sample_uidset,
0N/A &uid_range[i].seq1, 1);
0N/A array_append(&ctx->qresync_sample_seqset, &seq, 1);
0N/A
0N/A diff = uid_range[i].seq2 - uid_range[i].seq1;
0N/A if (diff > 0) {
0N/A n += diff - 1;
0N/A if (!seq_range_array_iter_nth(&seq_iter, n++, &seq))
0N/A return -1;
0N/A
0N/A array_append(&ctx->qresync_sample_uidset,
0N/A &uid_range[i].seq2, 1);
0N/A array_append(&ctx->qresync_sample_seqset, &seq, 1);
0N/A }
0N/A }
0N/A if (seq_range_array_iter_nth(&seq_iter, n, &seq))
0N/A return -1;
0N/A return 0;
0N/A}
0N/A
0N/Astatic bool
0N/Aselect_parse_qresync(struct imap_select_context *ctx,
0N/A const struct imap_arg *args)
0N/A{
0N/A ARRAY_TYPE(seq_range) seqset, uidset;
0N/A unsigned int count;
0N/A
0N/A if ((ctx->cmd->client->enabled_features &
0N/A MAILBOX_FEATURE_QRESYNC) == 0) {
0N/A client_send_command_error(ctx->cmd, "QRESYNC not enabled");
0N/A return FALSE;
0N/A }
0N/A if (args->type != IMAP_ARG_LIST) {
0N/A client_send_command_error(ctx->cmd,
0N/A "QRESYNC parameters missing");
0N/A return FALSE;
0N/A }
0N/A args = IMAP_ARG_LIST_ARGS(args);
0N/A for (count = 0; args[count].type != IMAP_ARG_EOL; count++) ;
0N/A
0N/A if (count < 2 || count > 4 ||
0N/A args[0].type != IMAP_ARG_ATOM ||
0N/A args[1].type != IMAP_ARG_ATOM ||
0N/A (count > 2 && args[2].type != IMAP_ARG_ATOM) ||
0N/A (count > 3 && args[3].type != IMAP_ARG_LIST)) {
0N/A client_send_command_error(ctx->cmd,
0N/A "Invalid QRESYNC parameters");
0N/A return FALSE;
0N/A }
0N/A ctx->qresync_uid_validity =
0N/A strtoul(IMAP_ARG_STR_NONULL(&args[0]), NULL, 10);
0N/A ctx->qresync_modseq =
0N/A strtoull(IMAP_ARG_STR_NONULL(&args[1]), NULL, 10);
0N/A if (count > 2) {
0N/A i_array_init(&ctx->qresync_known_uids, 64);
0N/A if (imap_seq_set_parse(IMAP_ARG_STR_NONULL(&args[2]),
0N/A &ctx->qresync_known_uids) < 0) {
0N/A client_send_command_error(ctx->cmd,
0N/A "Invalid QRESYNC known-uids");
0N/A return FALSE;
0N/A }
0N/A } else {
0N/A i_array_init(&ctx->qresync_known_uids, 64);
0N/A seq_range_array_add_range(&ctx->qresync_known_uids,
0N/A 1, (uint32_t)-1);
0N/A }
0N/A if (count > 3) {
0N/A args = IMAP_ARG_LIST_ARGS(&args[3]);
0N/A if (args[0].type != IMAP_ARG_ATOM ||
0N/A args[1].type != IMAP_ARG_ATOM ||
0N/A args[2].type != IMAP_ARG_EOL) {
0N/A client_send_command_error(ctx->cmd,
0N/A "Invalid QRESYNC known set parameters");
0N/A return FALSE;
0N/A }
0N/A t_array_init(&seqset, 32);
0N/A if (imap_seq_set_parse(IMAP_ARG_STR_NONULL(&args[0]),
0N/A &seqset) < 0) {
0N/A client_send_command_error(ctx->cmd,
0N/A "Invalid QRESYNC known-sequence-set");
0N/A return FALSE;
0N/A }
0N/A t_array_init(&uidset, 32);
0N/A if (imap_seq_set_parse(IMAP_ARG_STR_NONULL(&args[1]),
0N/A &uidset) < 0) {
0N/A client_send_command_error(ctx->cmd,
0N/A "Invalid QRESYNC known-uid-set");
0N/A return FALSE;
0N/A }
0N/A if (select_qresync_get_uids(ctx, &seqset, &uidset) < 0) {
0N/A client_send_command_error(ctx->cmd,
0N/A "Invalid QRESYNC sets");
0N/A return FALSE;
0N/A }
0N/A }
0N/A return TRUE;
0N/A}
0N/A
0N/Astatic bool
0N/Aselect_parse_options(struct imap_select_context *ctx,
const struct imap_arg *args)
{
const char *name;
while (args->type != IMAP_ARG_EOL) {
if (args->type != IMAP_ARG_ATOM) {
client_send_command_error(ctx->cmd,
"SELECT options contain non-atoms.");
return FALSE;
}
name = t_str_ucase(IMAP_ARG_STR(args));
args++;
if (strcmp(name, "CONDSTORE") == 0)
ctx->condstore = TRUE;
else if (strcmp(name, "QRESYNC") == 0) {
if (!select_parse_qresync(ctx, args))
return FALSE;
args++;
} else {
client_send_command_error(ctx->cmd,
"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)
{
if (ret < 0) {
if (ctx->box != NULL)
mailbox_close(&ctx->box);
client_send_storage_error(ctx->cmd, ctx->storage);
ctx->cmd->client->mailbox = NULL;
} else {
client_send_tagline(ctx->cmd, mailbox_is_readonly(ctx->box) ?
"OK [READ-ONLY] Select completed." :
"OK [READ-WRITE] Select completed.");
}
select_context_free(ctx);
}
static bool cmd_select_continue(struct client_command_context *cmd)
{
struct imap_select_context *ctx = cmd->context;
int ret;
if ((ret = imap_fetch_more(ctx->fetch_ctx)) == 0) {
/* unfinished */
return FALSE;
}
ret = imap_fetch_deinit(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;
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;
fetch_ctx = imap_fetch_init(ctx->cmd, ctx->box);
if (fetch_ctx == NULL)
return -1;
fetch_ctx->search_args = search_args;
fetch_ctx->send_vanished = TRUE;
fetch_ctx->qresync_sample_seqset = &ctx->qresync_sample_seqset;
fetch_ctx->qresync_sample_uidset = &ctx->qresync_sample_uidset;
if (!imap_fetch_add_unchanged_since(fetch_ctx, ctx->qresync_modseq) ||
!imap_fetch_init_handler(fetch_ctx, "UID", NULL) ||
!imap_fetch_init_handler(fetch_ctx, "FLAGS", NULL) ||
!imap_fetch_init_handler(fetch_ctx, "MODSEQ", NULL)) {
(void)imap_fetch_deinit(fetch_ctx);
return -1;
}
if (imap_fetch_begin(fetch_ctx) == 0) {
if (imap_fetch_more(fetch_ctx) == 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 FALSE;
}
}
return imap_fetch_deinit(fetch_ctx);
}
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_open_flags open_flags = 0;
if (readonly)
open_flags |= MAILBOX_OPEN_READONLY | MAILBOX_OPEN_KEEP_RECENT;
ctx->box = mailbox_open(ctx->storage, mailbox, NULL, open_flags);
if (ctx->box == NULL)
return -1;
if (client->enabled_features != 0)
mailbox_enable(ctx->box, client->enabled_features);
if (mailbox_sync(ctx->box, MAILBOX_SYNC_FLAG_FULL_READ,
STATUS_MESSAGES | STATUS_RECENT |
STATUS_FIRST_UNSEEN_SEQ | STATUS_UIDVALIDITY |
STATUS_UIDNEXT | STATUS_KEYWORDS |
STATUS_HIGHESTMODSEQ, &status) < 0)
return -1;
client->mailbox = ctx->box;
client->select_counter++;
client->messages_count = status.messages;
client->recent_count = status.recent;
client->uidvalidity = status.uidvalidity;
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));
if (status.highest_modseq == 0) {
client_send_line(client,
"* OK [NOMODSEQ] No permanent modsequences");
} else {
client_send_line(client,
t_strdup_printf("* OK [HIGHESTMODSEQ %llu]",
(unsigned long long)status.highest_modseq));
client->sync_last_full_modseq = status.highest_modseq;
}
if (ctx->qresync_uid_validity == status.uidvalidity) {
if (select_qresync(ctx) < 0)
return -1;
}
return 0;
}
bool cmd_select_full(struct client_command_context *cmd, bool readonly)
{
struct client *client = cmd->client;
struct mailbox *box;
struct imap_select_context *ctx;
const struct imap_arg *args;
const char *mailbox;
int ret;
/* <mailbox> [(optional parameters)] */
if (!client_read_args(cmd, 0, 0, &args))
return FALSE;
if (!IMAP_ARG_TYPE_IS_STRING(args[0].type)) {
client_send_command_error(cmd, "Invalid arguments.");
return FALSE;
}
mailbox = IMAP_ARG_STR(&args[0]);
ctx = p_new(cmd->pool, struct imap_select_context, 1);
ctx->cmd = cmd;
ctx->storage = client_find_storage(cmd, &mailbox);
if (ctx->storage == NULL)
return TRUE;
if (args[1].type == IMAP_ARG_LIST) {
if (!select_parse_options(ctx, IMAP_ARG_LIST_ARGS(&args[1]))) {
select_context_free(ctx);
return TRUE;
}
}
i_assert(client->mailbox_change_lock == NULL);
client->mailbox_change_lock = cmd;
if (client->mailbox != NULL) {
client_search_updates_free(client);
box = client->mailbox;
client->mailbox = NULL;
if (mailbox_close(&box) < 0) {
client_send_untagged_storage_error(client,
mailbox_get_storage(box));
}
/* CLOSED response is required by QRESYNC */
client_send_line(client, "* OK [CLOSED]");
}
if (ctx->condstore) {
/* Enable while no mailbox is opened to avoid sending
HIGHESTMODSEQ for previously opened mailbox */
client_enable(client, MAILBOX_FEATURE_CONDSTORE);
}
ret = select_open(ctx, mailbox, readonly);
cmd_select_finish(ctx, ret);
return TRUE;
}
bool cmd_select(struct client_command_context *cmd)
{
return cmd_select_full(cmd, FALSE);
}