imap-commands-util.c revision e0512d13ca0cd97a306a9a96073868bb158e314a
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
2fbc2a7c65d30e46803195ebb4547176b85c22c7Timo Sirainen#include "imap-common.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "array.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "buffer.h"
2fbc2a7c65d30e46803195ebb4547176b85c22c7Timo Sirainen#include "str.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "str-sanitize.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "imap-resp-code.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "imap-parser.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "imap-sync.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "imap-util.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "mail-storage.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "mail-namespace.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "imap-commands-util.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen/* Maximum length for mailbox name, including it's path. This isn't fully
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen exact since the user can create folder hierarchy with small names, then
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen rename them to larger names. Mail storages should set more strict limits
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen to them, mbox/maildir currently allow paths only up to PATH_MAX. */
e25885d4c7c4b392c66bbf26a9b892362d90f001Timo Sirainen#define MAILBOX_MAX_NAME_LEN 512
e25885d4c7c4b392c66bbf26a9b892362d90f001Timo Sirainen
e25885d4c7c4b392c66bbf26a9b892362d90f001Timo Sirainenstruct mail_namespace *
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenclient_find_namespace(struct client_command_context *cmd, const char **mailbox)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen{
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mail_namespace *ns;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ns = mail_namespace_find(cmd->client->user->namespaces, mailbox);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (ns != NULL)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return ns;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen client_send_tagline(cmd, "NO Unknown namespace.");
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return NULL;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen}
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenbool client_verify_mailbox_name(struct client_command_context *cmd,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *mailbox,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen enum client_verify_mailbox_mode mode)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen{
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mail_namespace *ns;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen enum mailbox_name_status mailbox_status;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *orig_mailbox, *p, *resp_code = NULL;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen orig_mailbox = mailbox;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ns = client_find_namespace(cmd, &mailbox);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (ns == NULL)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* make sure it even looks valid */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (*mailbox == '\0') {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen client_send_tagline(cmd, "NO Empty mailbox name.");
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (ns->real_sep != ns->sep && ns->prefix_len < strlen(orig_mailbox)) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* make sure there are no real separators used in the mailbox
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen name. */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen orig_mailbox += ns->prefix_len;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen for (p = orig_mailbox; *p != '\0'; p++) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (*p == ns->real_sep) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen client_send_tagline(cmd, t_strdup_printf(
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen "NO Character not allowed "
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen "in mailbox name: '%c'",
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen ns->real_sep));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
2e1e493b248dec0127b1eabeea5a8bc330378fcdTimo Sirainen }
2e1e493b248dec0127b1eabeea5a8bc330378fcdTimo Sirainen
2e1e493b248dec0127b1eabeea5a8bc330378fcdTimo Sirainen /* make sure two hierarchy separators aren't next to each others */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen for (p = mailbox+1; *p != '\0'; p++) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (p[0] == ns->real_sep && p[-1] == ns->real_sep) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen client_send_tagline(cmd, "NO Invalid mailbox name.");
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (strlen(mailbox) > MAILBOX_MAX_NAME_LEN) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen client_send_tagline(cmd, "NO Mailbox name too long.");
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* check what our storage thinks of it */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (mailbox_list_get_mailbox_name_status(ns->list, mailbox,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen &mailbox_status) < 0) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen client_send_list_error(cmd, ns->list);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
f8a86fdfb0048f9c87bf223373b35416ceb5856bTimo Sirainen switch (mailbox_status) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen case MAILBOX_NAME_EXISTS:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen switch (mode) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen case CLIENT_VERIFY_MAILBOX_NAME:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen case CLIENT_VERIFY_MAILBOX_SHOULD_EXIST:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen case CLIENT_VERIFY_MAILBOX_SHOULD_EXIST_TRYCREATE:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return TRUE;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen case CLIENT_VERIFY_MAILBOX_SHOULD_NOT_EXIST:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen break;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen }
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen if (mode == CLIENT_VERIFY_MAILBOX_NAME ||
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen mode == CLIENT_VERIFY_MAILBOX_SHOULD_EXIST)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return TRUE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen client_send_tagline(cmd, t_strconcat(
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen "NO [", IMAP_RESP_CODE_ALREADYEXISTS,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen "] Mailbox exists.", NULL));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen break;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
case MAILBOX_NAME_VALID:
switch (mode) {
case CLIENT_VERIFY_MAILBOX_NAME:
case CLIENT_VERIFY_MAILBOX_SHOULD_NOT_EXIST:
return TRUE;
case CLIENT_VERIFY_MAILBOX_SHOULD_EXIST:
resp_code = IMAP_RESP_CODE_NONEXISTENT;
break;
case CLIENT_VERIFY_MAILBOX_SHOULD_EXIST_TRYCREATE:
resp_code = "TRYCREATE";
break;
}
client_send_tagline(cmd, t_strconcat(
"NO [", resp_code, "] Mailbox doesn't exist: ",
str_sanitize(orig_mailbox, MAILBOX_MAX_NAME_LEN),
NULL));
break;
case MAILBOX_NAME_INVALID:
client_send_tagline(cmd, t_strconcat(
"NO Invalid mailbox name: ",
str_sanitize(orig_mailbox, MAILBOX_MAX_NAME_LEN),
NULL));
break;
case MAILBOX_NAME_NOINFERIORS:
client_send_tagline(cmd,
"NO Mailbox parent doesn't allow inferior mailboxes.");
break;
default:
i_unreached();
}
return FALSE;
}
bool client_verify_open_mailbox(struct client_command_context *cmd)
{
if (cmd->client->mailbox != NULL)
return TRUE;
else {
client_send_tagline(cmd, "BAD No mailbox selected.");
return FALSE;
}
}
const char *
imap_get_error_string(const char *error_string, enum mail_error error)
{
const char *resp_code = NULL;
switch (error) {
case MAIL_ERROR_NONE:
break;
case MAIL_ERROR_TEMP:
resp_code = IMAP_RESP_CODE_SERVERBUG;
break;
case MAIL_ERROR_NOTPOSSIBLE:
case MAIL_ERROR_PARAMS:
resp_code = IMAP_RESP_CODE_CANNOT;
break;
case MAIL_ERROR_PERM:
resp_code = IMAP_RESP_CODE_NOPERM;
break;
case MAIL_ERROR_NOSPACE:
resp_code = IMAP_RESP_CODE_OVERQUOTA;
break;
case MAIL_ERROR_NOTFOUND:
resp_code = IMAP_RESP_CODE_NONEXISTENT;
break;
case MAIL_ERROR_EXISTS:
resp_code = IMAP_RESP_CODE_ALREADYEXISTS;
break;
case MAIL_ERROR_EXPUNGED:
resp_code = IMAP_RESP_CODE_EXPUNGEISSUED;
break;
case MAIL_ERROR_INUSE:
resp_code = IMAP_RESP_CODE_INUSE;
break;
}
if (resp_code == NULL || *error_string == '[')
return t_strconcat("NO ", error_string, NULL);
else
return t_strdup_printf("NO [%s] %s", resp_code, error_string);
}
void client_send_list_error(struct client_command_context *cmd,
struct mailbox_list *list)
{
const char *error_string;
enum mail_error error;
error_string = mailbox_list_get_last_error(list, &error);
client_send_tagline(cmd, imap_get_error_string(error_string, error));
}
void client_send_storage_error(struct client_command_context *cmd,
struct mail_storage *storage)
{
const char *error_string;
enum mail_error error;
if (cmd->client->mailbox != NULL &&
mailbox_is_inconsistent(cmd->client->mailbox)) {
/* we can't do forced CLOSE, so have to disconnect */
client_disconnect_with_error(cmd->client,
"IMAP session state is inconsistent, please relogin.");
return;
}
error_string = mail_storage_get_last_error(storage, &error);
client_send_tagline(cmd, imap_get_error_string(error_string, error));
}
void client_send_untagged_storage_error(struct client *client,
struct mail_storage *storage)
{
const char *error_string;
enum mail_error error;
if (client->mailbox != NULL &&
mailbox_is_inconsistent(client->mailbox)) {
/* we can't do forced CLOSE, so have to disconnect */
client_disconnect_with_error(client,
"IMAP session state is inconsistent, please relogin.");
return;
}
error_string = mail_storage_get_last_error(storage, &error);
client_send_line(client, t_strconcat("* NO ", error_string, NULL));
}
bool client_parse_mail_flags(struct client_command_context *cmd,
const struct imap_arg *args,
enum mail_flags *flags_r,
const char *const **keywords_r)
{
const char *atom;
enum mail_flags flag;
ARRAY_DEFINE(keywords, const char *);
*flags_r = 0;
*keywords_r = NULL;
p_array_init(&keywords, cmd->pool, 16);
while (args->type != IMAP_ARG_EOL) {
if (args->type != IMAP_ARG_ATOM) {
client_send_command_error(cmd,
"Flags list contains non-atoms.");
return FALSE;
}
atom = IMAP_ARG_STR(args);
if (*atom == '\\') {
/* system flag */
atom = t_str_ucase(atom);
flag = imap_parse_system_flag(atom);
if (flag != 0 && flag != MAIL_RECENT)
*flags_r |= flag;
else {
client_send_tagline(cmd, t_strconcat(
"BAD Invalid system flag ",
atom, NULL));
return FALSE;
}
} else {
/* keyword validity checks are done by lib-storage */
array_append(&keywords, &atom, 1);
}
args++;
}
if (array_count(&keywords) == 0)
*keywords_r = NULL;
else {
(void)array_append_space(&keywords); /* NULL-terminate */
*keywords_r = array_idx(&keywords, 0);
}
return TRUE;
}
static const char *get_keywords_string(const ARRAY_TYPE(keywords) *keywords)
{
string_t *str;
const char *const *names;
unsigned int i, count;
str = t_str_new(256);
names = array_get(keywords, &count);
for (i = 0; i < count; i++) {
str_append_c(str, ' ');
str_append(str, names[i]);
}
return str_c(str);
}
#define SYSTEM_FLAGS "\\Answered \\Flagged \\Deleted \\Seen \\Draft"
void client_send_mailbox_flags(struct client *client, bool selecting)
{
unsigned int count = array_count(client->keywords.names);
const char *str;
if (!selecting && count == client->keywords.announce_count) {
/* no changes to keywords and we're not selecting a mailbox */
return;
}
client->keywords.announce_count = count;
str = count == 0 ? "" : get_keywords_string(client->keywords.names);
client_send_line(client,
t_strconcat("* FLAGS ("SYSTEM_FLAGS, str, ")", NULL));
if (mailbox_is_readonly(client->mailbox)) {
client_send_line(client, "* OK [PERMANENTFLAGS ()] "
"Read-only mailbox.");
} else {
bool star = mailbox_allow_new_keywords(client->mailbox);
client_send_line(client,
t_strconcat("* OK [PERMANENTFLAGS ("SYSTEM_FLAGS, str,
star ? " \\*" : "",
")] Flags permitted.", NULL));
}
}
void client_update_mailbox_flags(struct client *client,
const ARRAY_TYPE(keywords) *keywords)
{
client->keywords.names = keywords;
client->keywords.announce_count = 0;
}
const char *const *
client_get_keyword_names(struct client *client, ARRAY_TYPE(keywords) *dest,
const ARRAY_TYPE(keyword_indexes) *src)
{
const unsigned int *kw_indexes;
const char *const *all_names;
unsigned int i, kw_count, all_count;
client_send_mailbox_flags(client, FALSE);
all_names = array_get(client->keywords.names, &all_count);
kw_indexes = array_get(src, &kw_count);
/* convert indexes to names */
array_clear(dest);
for (i = 0; i < kw_count; i++) {
i_assert(kw_indexes[i] < all_count);
array_append(dest, &all_names[kw_indexes[i]], 1);
}
(void)array_append_space(dest);
return array_idx(dest, 0);
}
bool mailbox_equals(const struct mailbox *box1,
const struct mail_namespace *ns2, const char *name2)
{
struct mail_namespace *ns1 = mailbox_get_namespace(box1);
const char *name1;
if (ns1 != ns2)
return FALSE;
name1 = mailbox_get_name(box1);
if (strcmp(name1, name2) == 0)
return TRUE;
return strcasecmp(name1, "INBOX") == 0 &&
strcasecmp(name2, "INBOX") == 0;
}
void msgset_generator_init(struct msgset_generator_context *ctx, string_t *str)
{
memset(ctx, 0, sizeof(*ctx));
ctx->str = str;
ctx->last_uid = (uint32_t)-1;
}
void msgset_generator_next(struct msgset_generator_context *ctx, uint32_t uid)
{
if (uid != ctx->last_uid+1) {
if (ctx->first_uid == 0)
;
else if (ctx->first_uid == ctx->last_uid)
str_printfa(ctx->str, "%u,", ctx->first_uid);
else {
str_printfa(ctx->str, "%u:%u,",
ctx->first_uid, ctx->last_uid);
}
ctx->first_uid = uid;
}
ctx->last_uid = uid;
}
void msgset_generator_finish(struct msgset_generator_context *ctx)
{
if (ctx->first_uid == ctx->last_uid)
str_printfa(ctx->str, "%u", ctx->first_uid);
else
str_printfa(ctx->str, "%u:%u", ctx->first_uid, ctx->last_uid);
}