imap-commands-util.c [plain text]
#include "imap-common.h"
#include "array.h"
#include "buffer.h"
#include "str.h"
#include "str-sanitize.h"
#include "imap-resp-code.h"
#include "imap-parser.h"
#include "imap-sync.h"
#include "imap-util.h"
#include "mail-storage.h"
#include "mail-namespace.h"
#include "imap-commands-util.h"
#define MAILBOX_MAX_NAME_LEN 512
struct mail_namespace *
client_find_namespace(struct client_command_context *cmd, const char *mailbox,
const char **storage_name_r,
enum mailbox_name_status *mailbox_status_r)
{
struct mail_namespace *namespaces = cmd->client->user->namespaces;
struct mail_namespace *ns;
const char *storage_name, *p;
unsigned int storage_name_len;
storage_name = mailbox;
ns = mail_namespace_find(namespaces, &storage_name);
if (ns == NULL) {
client_send_tagline(cmd, t_strdup_printf(
"NO Client tried to access nonexistent namespace. "
"(Mailbox name should probably be prefixed with: %s)",
mail_namespace_find_inbox(namespaces)->prefix));
return NULL;
}
if (mailbox_status_r == NULL) {
*storage_name_r = storage_name;
return ns;
}
if (*storage_name == '\0' && !(*mailbox != '\0' && ns->list)) {
client_send_tagline(cmd, "NO Empty mailbox name.");
return NULL;
}
storage_name_len = strlen(storage_name);
if ((cmd->client->set->parsed_workarounds &
WORKAROUND_TB_EXTRA_MAILBOX_SEP) != 0 &&
storage_name_len > 0 &&
storage_name[storage_name_len-1] == ns->real_sep) {
storage_name = t_strndup(storage_name, storage_name_len-1);
}
if (strlen(mailbox) == ns->prefix_len) {
client_send_tagline(cmd, "NO Invalid mailbox name.");
return NULL;
}
if (ns->real_sep != ns->sep && ns->prefix_len < strlen(mailbox)) {
mailbox += ns->prefix_len;
for (p = mailbox; *p != '\0'; p++) {
if (*p == ns->real_sep) {
client_send_tagline(cmd, t_strdup_printf(
"NO Character not allowed "
"in mailbox name: '%c'",
ns->real_sep));
return NULL;
}
}
}
for (p = storage_name+1; *p != '\0'; p++) {
if (p[0] == ns->real_sep && p[-1] == ns->real_sep) {
client_send_tagline(cmd, "NO Invalid mailbox name.");
return NULL;
}
}
if (storage_name_len > MAILBOX_MAX_NAME_LEN) {
client_send_tagline(cmd, "NO Mailbox name too long.");
return NULL;
}
if (mailbox_list_get_mailbox_name_status(ns->list, storage_name,
mailbox_status_r) < 0) {
client_send_list_error(cmd, ns->list);
return NULL;
}
*storage_name_r = storage_name;
return ns;
}
void client_fail_mailbox_name_status(struct client_command_context *cmd,
const char *mailbox_name,
const char *resp_code,
enum mailbox_name_status status)
{
switch (status) {
case MAILBOX_NAME_EXISTS_MAILBOX:
case MAILBOX_NAME_EXISTS_DIR:
client_send_tagline(cmd, t_strconcat(
"NO [", IMAP_RESP_CODE_ALREADYEXISTS,
"] Mailbox already exists: ",
str_sanitize(mailbox_name, MAILBOX_MAX_NAME_LEN),
NULL));
break;
case MAILBOX_NAME_VALID:
if (resp_code == NULL)
resp_code = "";
else
resp_code = t_strconcat("[", resp_code, "] ", NULL);
client_send_tagline(cmd, t_strconcat(
"NO ", resp_code, "Mailbox doesn't exist: ",
str_sanitize(mailbox_name, MAILBOX_MAX_NAME_LEN),
NULL));
break;
case MAILBOX_NAME_INVALID:
client_send_tagline(cmd, t_strconcat(
"NO Invalid mailbox name: ",
str_sanitize(mailbox_name, MAILBOX_MAX_NAME_LEN),
NULL));
break;
case MAILBOX_NAME_NOINFERIORS:
client_send_tagline(cmd,
"NO Parent mailbox doesn't allow child mailboxes.");
break;
}
}
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(struct client_command_context *cmd,
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:
if ((cmd->cmd_flags & COMMAND_FLAG_USE_NONEXISTENT) != 0)
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(cmd, 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)) {
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(cmd, 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)) {
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 (!IMAP_ARG_IS_EOL(args)) {
if (!imap_arg_get_atom(args, &atom)) {
client_send_command_error(cmd,
"Flags list contains non-atoms.");
return FALSE;
}
if (*atom == '\\') {
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 {
array_append(&keywords, &atom, 1);
}
args++;
}
if (array_count(&keywords) == 0)
*keywords_r = NULL;
else {
(void)array_append_space(&keywords);
*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;
str = t_str_new(256);
array_foreach(keywords, names) {
const char *name = *names;
str_append_c(str, ' ');
str_append(str, name);
}
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) {
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 all_count;
client_send_mailbox_flags(client, FALSE);
all_names = array_get(client->keywords.names, &all_count);
array_clear(dest);
array_foreach(src, kw_indexes) {
unsigned int kw_index = *kw_indexes;
i_assert(kw_index < all_count);
array_append(dest, &all_names[kw_index], 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);
}