diff options
Diffstat (limited to 'src/imapcommands.c')
-rw-r--r-- | src/imapcommands.c | 2156 |
1 files changed, 2156 insertions, 0 deletions
diff --git a/src/imapcommands.c b/src/imapcommands.c new file mode 100644 index 00000000..d9207664 --- /dev/null +++ b/src/imapcommands.c @@ -0,0 +1,2156 @@ +/* + Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl + Copyright (c) 2004-2006 NFG Net Facilities Group BV support@nfg.nl + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + * + * imapcommands.c + * + * IMAP server command implementations + */ + +#include "dbmail.h" +#define THIS_MODULE "imap" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + + +#ifndef MAX_RETRIES +#define MAX_RETRIES 12 +#endif + +extern const char *imap_flag_desc[]; +extern const char *imap_flag_desc_escaped[]; + +int list_is_lsub = 0; + +extern const char AcceptedMailboxnameChars[]; + +extern int imap_before_smtp; + +/* + * RETURN VALUES _ic_ functions: + * + * -1 Fatal error, close connection to user + * 0 Succes + * 1 Non-fatal error, connection stays alive + */ + + +/* + * ANY-STATE COMMANDS: capability, noop, logout + */ + +/* + * _ic_capability() + * + * returns a string to the client containing the server capabilities + */ +int _ic_capability(struct ImapSession *self) +{ + field_t val; + gboolean override = FALSE; + + if (!check_state_and_args(self, "CAPABILITY", 0, 0, -1)) + return 1; /* error, return */ + + + GETCONFIGVALUE("capability", "IMAP", val); + if (strlen(val) > 0) + override = TRUE; + + dbmail_imap_session_printf(self, "* CAPABILITY %s\r\n", override ? val : IMAP_CAPABILITY_STRING); + dbmail_imap_session_printf(self, "%s OK CAPABILITY completed\r\n", self->tag); + + return 0; +} + + +/* + * _ic_noop() + * + * performs No operation + */ +int _ic_noop(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + if (!check_state_and_args(self, "NOOP", 0, 0, -1)) + return 1; /* error, return */ + + if (ud->state == IMAPCS_SELECTED) + dbmail_imap_session_mailbox_status(self, TRUE); + + dbmail_imap_session_printf(self, "%s OK NOOP completed\r\n", self->tag); + return 0; +} + + +/* + * _ic_logout() + * + * prepares logout from IMAP-server + */ +int _ic_logout(struct ImapSession *self) +{ + timestring_t timestring; + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + + // flush recent messages from previous select + dbmail_imap_session_mailbox_update_recent(self); + + if (!check_state_and_args(self, "LOGOUT", 0, 0, -1)) + return 1; /* error, return */ + + create_current_timestring(×tring); + dbmail_imap_session_set_state(self,IMAPCS_LOGOUT); + TRACE(TRACE_MESSAGE, "user (id:%llu) logging out @ [%s]", ud->userid, timestring); + + dbmail_imap_session_printf(self, "* BYE dbmail imap server kisses you goodbye\r\n"); + + return 0; +} + +/* + * PRE-AUTHENTICATED STATE COMMANDS + * login, authenticate + */ +/* + * _ic_login() + * + * Performs login-request handling. + */ +int _ic_login(struct ImapSession *self) +{ + int result; + timestring_t timestring; + + if (!check_state_and_args(self, "LOGIN", 2, 2, IMAPCS_NON_AUTHENTICATED)) + return 1; + + create_current_timestring(×tring); + if ((result = dbmail_imap_session_handle_auth(self, self->args[self->args_idx], self->args[self->args_idx+1]))) + return result; + if (imap_before_smtp) + db_log_ip(self->ci->ip_src); + + child_reg_connected_user(self->args[self->args_idx]); + + dbmail_imap_session_printf(self, "%s OK LOGIN completed\r\n", self->tag); + + if (! self->mbxinfo) + dbmail_imap_session_get_mbxinfo(self); + + return 0; +} + + +/* + * _ic_authenticate() + * + * performs authentication using LOGIN mechanism: + * + * + */ +int _ic_authenticate(struct ImapSession *self) +{ + int result; + char *username; + char *password; + + timestring_t timestring; + + if (!check_state_and_args(self, "AUTHENTICATE", 1, 1, IMAPCS_NON_AUTHENTICATED)) + return 1; + + create_current_timestring(×tring); + + /* check authentication method */ + if (strcasecmp(self->args[self->args_idx], "login") != 0) { + dbmail_imap_session_printf(self, + "%s NO Invalid authentication mechanism specified\r\n", + self->tag); + return 1; + } + + /* ask for username (base64 encoded) */ + username = g_new0(char,MAX_LINESIZE); + if (dbmail_imap_session_prompt(self,"username", username)) { + dbmail_imap_session_printf(self, "* BYE error reading username\r\n"); + g_free(username); + return -1; + } + /* ask for password */ + password = g_new0(char,MAX_LINESIZE); + if (dbmail_imap_session_prompt(self,"password", password)) { + dbmail_imap_session_printf(self, "* BYE error reading password\r\n"); + g_free(username); + g_free(password); + return -1; + } + + /* try to validate user */ + if ((result = dbmail_imap_session_handle_auth(self,username,password))) { + g_free(username); + g_free(password); + return result; + } + + if (imap_before_smtp) + db_log_ip(self->ci->ip_src); + + dbmail_imap_session_printf(self, "%s OK AUTHENTICATE completed\r\n", self->tag); + + child_reg_connected_user(username); + + if (! self->mbxinfo) + dbmail_imap_session_get_mbxinfo(self); + + g_free(username); + g_free(password); + return 0; +} + + +/* + * AUTHENTICATED STATE COMMANDS + * select, examine, create, delete, rename, subscribe, + * unsubscribe, list, lsub, status, append + */ + +/* + * _ic_select() + * + * select a specified mailbox + */ +#define PERMSTRING_SIZE 80 +int _ic_select(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + u64_t key = 0; + u64_t *msn = NULL; + int result; + char *mailbox; + char permstring[PERMSTRING_SIZE]; + + if (!check_state_and_args(self, "SELECT", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; /* error, return */ + + mailbox = self->args[self->args_idx]; + + if ((result = dbmail_imap_session_mailbox_open(self, mailbox))) + return result; + + /* update permission: select implies read-write */ + ud->mailbox->permission = IMAPPERM_READWRITE; + + dbmail_imap_session_set_state(self,IMAPCS_SELECTED); + + if ((result = dbmail_imap_session_mailbox_show_info(self))) + return result; + + /* show idx of first unseen msg (if present) */ + if (ud->mailbox->exists) { + key = db_first_unseen(ud->mailbox->uid); + if ( (key > 0) && (msn = g_tree_lookup(self->mailbox->ids, &key))) { + dbmail_imap_session_printf(self, + "* OK [UNSEEN %llu] first unseen message\r\n", *msn); + } + } + /* permission */ + switch (ud->mailbox->permission) { + case IMAPPERM_READ: + g_snprintf(permstring, PERMSTRING_SIZE, "READ-ONLY"); + break; + case IMAPPERM_READWRITE: + g_snprintf(permstring, PERMSTRING_SIZE, "READ-WRITE"); + break; + default: + TRACE(TRACE_ERROR, "detected invalid permission mode for mailbox %llu ('%s')", + ud->mailbox->uid, self->args[self->args_idx]); + + dbmail_imap_session_printf(self, + "* BYE fatal: detected invalid mailbox settings\r\n"); + return -1; + } + + dbmail_imap_session_printf(self, "%s OK [%s] SELECT completed\r\n", self->tag, + permstring); + return 0; +} + + +/* + * _ic_examine() + * + * examines a specified mailbox + */ +int _ic_examine(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result; + char *mailbox; + + if (!check_state_and_args(self, "EXAMINE", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; + + mailbox = self->args[self->args_idx]; + + if ((result = dbmail_imap_session_mailbox_open(self, mailbox))) + return result; + + /* update permission: examine forces read-only */ + ud->mailbox->permission = IMAPPERM_READ; + + dbmail_imap_session_set_state(self,IMAPCS_SELECTED); + + if ((result = dbmail_imap_session_mailbox_show_info(self))) + return result; + + dbmail_imap_session_printf(self, "%s OK [READ-ONLY] EXAMINE completed\r\n", self->tag); + return 0; +} + + +/* + * _ic_create() + * + * create a mailbox + */ +int _ic_create(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result; + const char *message; + u64_t mboxid; + + if (!check_state_and_args(self, "CREATE", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; + + /* Create the mailbox and its parents. */ + result = db_mailbox_create_with_parents(self->args[self->args_idx], BOX_COMMANDLINE, ud->userid, &mboxid, &message); + + if (result > 0) { + dbmail_imap_session_printf(self, "%s NO %s\r\n", self->tag, message); + return DM_EGENERAL; + } else if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return DM_EQUERY; + } + + dbmail_imap_session_printf(self, "%s OK CREATE completed\r\n", self->tag); + return DM_SUCCESS; +} + + +/* + * _ic_delete() + * + * deletes a specified mailbox + */ +int _ic_delete(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result, nchildren = 0; + u64_t *children = NULL, mboxid; + char *mailbox = self->args[0]; + + if (!check_state_and_args(self, "DELETE", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; /* error, return */ + + if (! (mboxid = dbmail_imap_session_mailbox_get_idnr(self, mailbox)) ) { + dbmail_imap_session_printf(self, "%s NO mailbox doesn't exists\r\n", self->tag); + return 1; + } + + /* Check if the user has ACL delete rights to this mailbox; + * this also returns true is the user owns the mailbox. */ + result = dbmail_imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_DELETE); + if (result != 0) + return result; + + /* check if there is an attempt to delete inbox */ + if (strcasecmp(self->args[0], "inbox") == 0) { + dbmail_imap_session_printf(self, "%s NO cannot delete special mailbox INBOX\r\n", self->tag); + return 1; + } + + /* check for children of this mailbox */ + result = db_listmailboxchildren(mboxid, ud->userid, &children, &nchildren); + if (result == -1) { + /* error */ + TRACE(TRACE_ERROR, "cannot retrieve list of mailbox children"); + dbmail_imap_session_printf(self, "* BYE dbase/memory error\r\n"); + return -1; + } + + if (nchildren != 0) { + /* mailbox has inferior names; error if \noselect specified */ + result = db_isselectable(mboxid); + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO mailbox is non-selectable\r\n", self->tag); + g_free(children); + return 1; + } + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + g_free(children); + return -1; /* fatal */ + } + + /* mailbox has inferior names; remove all msgs and set noselect flag */ + result = db_removemsg(ud->userid, mboxid); + if (result != -1) + result = db_setselectable(mboxid, 0); /* set non-selectable flag */ + + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + g_free(children); + return -1; /* fatal */ + } + + /* check if this was the currently selected mailbox */ + if (ud->mailbox && (mboxid == ud->mailbox->uid)) + dbmail_imap_session_set_state(self,IMAPCS_AUTHENTICATED); + + /* ok done */ + dbmail_imap_session_printf(self, "%s OK DELETE completed\r\n", self->tag); + g_free(children); + return 0; + } + + /* ok remove mailbox */ + if (db_delete_mailbox(mboxid, 0, 1)) { + TRACE(TRACE_DEBUG,"db_delete_mailbox failed"); + dbmail_imap_session_printf(self,"%s NO DELETE failed\r\n", self->tag); + return DM_EGENERAL; + } + + /* check if this was the currently selected mailbox */ + if (ud->mailbox && (mboxid == ud->mailbox->uid)) + dbmail_imap_session_set_state(self, IMAPCS_AUTHENTICATED); + + dbmail_imap_session_printf(self, "%s OK DELETE completed\r\n", self->tag); + return 0; +} + + +/* + * _ic_rename() + * + * renames a specified mailbox + */ +int _ic_rename(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + u64_t mboxid, newmboxid, *children; + u64_t parentmboxid = 0; + size_t oldnamelen; + int nchildren, i, result; + char newname[IMAP_MAX_MAILBOX_NAMELEN], + name[IMAP_MAX_MAILBOX_NAMELEN]; + + if (!check_state_and_args(self, "RENAME", 2, 2, IMAPCS_AUTHENTICATED)) + return 1; + + if ((mboxid = dbmail_imap_session_mailbox_get_idnr(self, self->args[0])) == 0) { + dbmail_imap_session_printf(self, "%s NO mailbox does not exist\r\n", self->tag); + return 1; + } + + /* check if new name is valid */ + if (!checkmailboxname(self->args[1])) { + dbmail_imap_session_printf(self, "%s NO new mailbox name contains invalid characters\r\n", self->tag); + return 1; + } + + if ((newmboxid = dbmail_imap_session_mailbox_get_idnr(self, self->args[1])) != 0) { + dbmail_imap_session_printf(self, "%s NO new mailbox already exists\r\n", self->tag); + return 1; + } + + oldnamelen = strlen(self->args[0]); + + /* check if new name would invade structure as in + * test (exists) + * rename test test/testing + * would create test/testing but delete test + */ + if (strncasecmp(self->args[0], self->args[1], (int) oldnamelen) == 0 && + strlen(self->args[1]) > oldnamelen && self->args[1][oldnamelen] == '/') { + dbmail_imap_session_printf(self, + "%s NO new mailbox would invade mailbox structure\r\n", + self->tag); + return 1; + } + + /* check if structure of new name is valid */ + /* i.e. only last part (after last '/' can be nonexistent) */ + for (i = strlen(self->args[1]) - 1; i >= 0 && self->args[1][i] != '/'; i--); + if (i >= 0) { + self->args[1][i] = '\0'; /* note: original char was '/' */ + + if (db_findmailbox(self->args[1], ud->userid, &parentmboxid) == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; /* fatal */ + } + if (parentmboxid == 0) { + /* parent mailbox does not exist */ + dbmail_imap_session_printf(self, + "%s NO new mailbox would invade mailbox structure\r\n", + self->tag); + return 1; + } + + /* ok, reset arg */ + self->args[1][i] = '/'; + } + + /* Check if the user has ACL delete rights to old name, + * and create rights to the parent of the new name, or + * if the user just owns both mailboxes. */ + TRACE(TRACE_DEBUG, "Checking right to DELETE [%llu]", mboxid); + result = dbmail_imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_DELETE); + if (result != 0) + return result; + TRACE(TRACE_DEBUG, "We have the right to DELETE [%llu]", mboxid); + if (!parentmboxid) { + TRACE(TRACE_DEBUG, "Destination is a top-level mailbox; not checking right to CREATE."); + } else { + TRACE(TRACE_DEBUG, "Checking right to CREATE under [%llu]", parentmboxid); + result = dbmail_imap_session_mailbox_check_acl(self, parentmboxid, ACL_RIGHT_CREATE); + if (result != 0) + return result; + TRACE(TRACE_DEBUG, "We have the right to CREATE under [%llu]", parentmboxid); + } + + /* check if it is INBOX to be renamed */ + if (strcasecmp(self->args[0], "inbox") == 0) { + /* ok, renaming inbox */ + /* this means creating a new mailbox and moving all the INBOX msgs to the new mailbox */ + /* inferior names of INBOX are left unchanged */ + result = db_createmailbox(self->args[1], ud->userid, &newmboxid); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; + } + + result = db_movemsg(newmboxid, mboxid); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; + } + + /* ok done */ + dbmail_imap_session_printf(self, "%s OK RENAME completed\r\n", self->tag); + return 0; + } + + /* check for inferior names */ + result = db_listmailboxchildren(mboxid, ud->userid, &children, &nchildren); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; + } + + /* replace name for each child */ + for (i = 0; i < nchildren; i++) { + result = db_getmailboxname(children[i], ud->userid, name); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + g_free(children); + return -1; + } + + if (oldnamelen >= strlen(name)) { + /* strange error, let's say its fatal */ + TRACE(TRACE_ERROR, "mailbox names appear to be corrupted"); + dbmail_imap_session_printf(self, "* BYE internal error regarding mailbox names\r\n"); + g_free(children); + return -1; + } + + g_snprintf(newname, IMAP_MAX_MAILBOX_NAMELEN, "%s%s", self->args[1], &name[oldnamelen]); + + result = db_setmailboxname(children[i], newname); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + g_free(children); + return -1; + } + + } + if (children) + g_free(children); + + /* now replace name */ + result = db_setmailboxname(mboxid, self->args[1]); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; + } + + dbmail_imap_session_printf(self, "%s OK RENAME completed\r\n", self->tag); + return 0; +} + + +/* + * _ic_subscribe() + * + * subscribe to a specified mailbox + */ +int _ic_subscribe(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + u64_t mboxid; + + if (!check_state_and_args(self, "SUBSCRIBE", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; + + if (! (mboxid = dbmail_imap_session_mailbox_get_idnr(self, self->args[0]))) { + dbmail_imap_session_printf(self, "%s OK UNSUBSCRIBE on mailbox that does not exist\r\n", self->tag); + return 0; + } + + /* check for the lookup-right. RFC is unclear about which right to + use, so I guessed it should be lookup */ + + if (dbmail_imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_LOOKUP)) + return 1; + + if (db_subscribe(mboxid, ud->userid) == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; + } + + dbmail_imap_session_printf(self, "%s OK SUBSCRIBE completed\r\n", self->tag); + return 0; +} + + +/* + * _ic_unsubscribe() + * + * removes a mailbox from the users' subscription list + */ +int _ic_unsubscribe(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + u64_t mboxid; + + if (!check_state_and_args(self, "UNSUBSCRIBE", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; + + if (! (mboxid = dbmail_imap_session_mailbox_get_idnr(self, self->args[0]))) { + dbmail_imap_session_printf(self, "%s OK UNSUBSCRIBE on mailbox that does not exist\r\n", self->tag); + return 0; + } + + /* check for the lookup-right. RFC is unclear about which right to + use, so I guessed it should be lookup */ + + if (dbmail_imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_LOOKUP)) + return 1; + + if (db_unsubscribe(mboxid, ud->userid) == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; + } + + dbmail_imap_session_printf(self, "%s OK UNSUBSCRIBE completed\r\n", self->tag); + return 0; +} + + +/* + * _ic_list() + * + * executes a list command + */ +int _ic_list(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + u64_t *children = NULL; + int result; + size_t slen; + unsigned i; + unsigned nchildren; + char *pattern; + char *thisname = list_is_lsub ? "LSUB" : "LIST"; + + MailboxInfo *mb = NULL; + GList * plist = NULL; + gchar * pstring; + + + if (!check_state_and_args(self, thisname, 2, 2, IMAPCS_AUTHENTICATED)) + return 1; + + /* check if self->args are both empty strings, i.e. A001 LIST "" "" + this has special meaning; show root & delimiter */ + if (strlen(self->args[0]) == 0 && strlen(self->args[1]) == 0) { + dbmail_imap_session_printf(self, "* %s (\\NoSelect) \"/\" \"\"\r\n", + thisname); + dbmail_imap_session_printf(self, "%s OK %s completed\r\n", self->tag, thisname); + return 0; + } + + /* check the reference name, should contain only accepted mailboxname chars */ + for (i = 0, slen = strlen(AcceptedMailboxnameChars); self->args[0][i]; i++) { + if (index(AcceptedMailboxnameChars, self->args[0][i]) == NULL) { + /* wrong char found */ + dbmail_imap_session_printf(self, + "%s BAD reference name contains invalid characters\r\n", + self->tag); + return 1; + } + } + pattern = g_strdup_printf("%s%s", self->args[0], self->args[1]); + + TRACE(TRACE_INFO, "search with pattern: [%s]",pattern); + + result = db_findmailbox_by_regex(ud->userid, pattern, &children, &nchildren, list_is_lsub); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + g_free(children); + g_free(pattern); + return -1; + } + + if (result == 1) { + dbmail_imap_session_printf(self, "%s BAD invalid pattern specified\r\n", + self->tag); + g_free(children); + g_free(pattern); + return 1; + } + + mb = g_new0(MailboxInfo,1); + + for (i = 0; i < nchildren; i++) { + if ((db_getmailbox_list_result(children[i], ud->userid, mb) != 0)) + continue; + + plist = NULL; + if (mb->no_select) + plist = g_list_append(plist, g_strdup("\\noselect")); + if (mb->no_inferiors) + plist = g_list_append(plist, g_strdup("\\noinferiors")); + if (mb->no_children) + plist = g_list_append(plist, g_strdup("\\hasnochildren")); + else + plist = g_list_append(plist, g_strdup("\\haschildren")); + + /* show */ + pstring = dbmail_imap_plist_as_string(plist); + dbmail_imap_session_printf(self, "* %s %s \"%s\" \"%s\"\r\n", thisname, + pstring, MAILBOX_SEPARATOR, mb->name); + + g_list_destroy(plist); + g_free(pstring); + } + + + if (children) + g_free(children); + + g_free(pattern); + g_free(mb); + dbmail_imap_session_printf(self, "%s OK %s completed\r\n", self->tag, thisname); + + return 0; +} + + +/* + * _ic_lsub() + * + * list subscribed mailboxes + */ +int _ic_lsub(struct ImapSession *self) +{ + int result; + + list_is_lsub = 1; + result = _ic_list(self); + list_is_lsub = 0; + return result; +} + + +/* + * _ic_status() + * + * inquire the status of a mailbox + */ +int _ic_status(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + MailboxInfo *mb; + u64_t id; + int i, endfound, result; + GString *response; + GList *plst = NULL; + gchar *pstring, *astring; + + + if (!check_state_and_args(self, "STATUS", 3, 0, IMAPCS_AUTHENTICATED)) + return 1; + + if (strcmp(self->args[1], "(") != 0) { + dbmail_imap_session_printf(self, "%s BAD argument list should be parenthesed\r\n", self->tag); + return 1; + } + + /* check final arg: should be ')' and no new '(' in between */ + for (i = 2, endfound = 0; self->args[i]; i++) { + if (strcmp(self->args[i], ")") == 0) { + endfound = i; + break; + } + + if (strcmp(self->args[i], "(") == 0) { + dbmail_imap_session_printf(self, "%s BAD too many parentheses specified\r\n", self->tag); + return 1; + } + } + + if (endfound == 2) { + dbmail_imap_session_printf(self, "%s BAD argument list empty\r\n", self->tag); + return 1; + } + + if (self->args[endfound + 1]) { + dbmail_imap_session_printf(self, "%s BAD argument list too long\r\n", self->tag); + return 1; + } + + /* check if mailbox exists */ + if (db_findmailbox(self->args[0], ud->userid, &id) == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; + } + + mb = dbmail_imap_session_mbxinfo_lookup(self, id); + + if (mb == NULL) { + /* mailbox does not exist */ + dbmail_imap_session_printf(self, "%s NO specified mailbox does not exist\r\n", self->tag); + return 1; + } + + result = acl_has_right(mb, ud->userid, ACL_RIGHT_READ); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no rights to get status for mailbox\r\n", self->tag); + return 1; + } + + for (i = 2; self->args[i]; i++) { + if (strcasecmp(self->args[i], "messages") == 0) + plst = g_list_append_printf(plst,"MESSAGES %u", mb->exists); + else if (strcasecmp(self->args[i], "recent") == 0) + plst = g_list_append_printf(plst,"RECENT %u", mb->recent); + else if (strcasecmp(self->args[i], "unseen") == 0) + plst = g_list_append_printf(plst,"UNSEEN %u", mb->unseen); + else if (strcasecmp(self->args[i], "uidnext") == 0) + plst = g_list_append_printf(plst,"UIDNEXT %llu", mb->msguidnext); + else if (strcasecmp(self->args[i], "uidvalidity") == 0) + plst = g_list_append_printf(plst,"UIDVALIDITY %llu", mb->uid); + else if (strcasecmp(self->args[i], ")") == 0) + break; + else { + dbmail_imap_session_printf(self, + "\r\n%s BAD unrecognized option '%s' specified\r\n", + self->tag, self->args[i]); + return 1; + } + } + astring = dbmail_imap_astring_as_string(self->args[0]); + pstring = dbmail_imap_plist_as_string(plst); + + response = g_string_new(""); + g_string_printf(response, "* STATUS %s %s", astring, pstring); + dbmail_imap_session_printf(self, "%s\r\n", response->str); + dbmail_imap_session_printf(self, "%s OK STATUS completed\r\n", self->tag); + + g_list_destroy(plst); + g_string_free(response,TRUE); + g_free(astring); + g_free(pstring); + + return 0; +} + +/* + * _ic_idle + * + * non-expunging close for select mailbox and return to AUTH state + * + */ + +int _ic_idle(struct ImapSession *self) +{ + int result; + if (!check_state_and_args(self, "IDLE", 0, 0, IMAPCS_AUTHENTICATED)) + return 1; /* error, return */ + + if ((result = dbmail_imap_session_idle(self)) != 0) + return result; + + dbmail_imap_session_printf(self, "%s OK IDLE terminated\r\n", self->tag); + return 0; +} + + + +/* + * _ic_append() + * + * append a message to a mailbox + */ +int _ic_append(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + u64_t mboxid; + u64_t msg_idnr; + int i, j, result; + timestring_t sqldate; + int flaglist[IMAP_NFLAGS]; + int flagcount = 0; + GList *keywords = NULL; + MailboxInfo *mailbox = NULL; + MessageInfo *msginfo = NULL; + + memset(flaglist,0,sizeof(flaglist)); + + if (!self->args[0] || !self->args[1]) { + dbmail_imap_session_printf(self, "%s BAD invalid arguments specified to APPEND\r\n", + self->tag); + return 1; + } + + /* find the mailbox to place the message */ + if (db_findmailbox(self->args[0], ud->userid, &mboxid) == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error"); + return -1; + } + + if (mboxid) + mailbox = dbmail_imap_session_mbxinfo_lookup(self, mboxid); + + if (! mailbox ) { + dbmail_imap_session_printf(self, "%s NO [TRYCREATE] could not find specified mailbox\r\n", + self->tag); + return 1; + } + + /* check if user has right to append to mailbox */ + result = acl_has_right(mailbox, ud->userid, ACL_RIGHT_INSERT); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no permission to append to mailbox\r\n", + self->tag); + dbmail_imap_session_set_state(self, IMAPCS_AUTHENTICATED); + return 1; + } + + i = 1; + + /* check if a flag list has been specified */ + /* FIXME: We need to take of care of the Flags that are set here. They + should be set to the new message! + */ + if (self->args[i][0] == '(') { + /* ok fetch the flags specified */ + TRACE(TRACE_DEBUG, "flag list found:"); + + i++; + while (self->args[i] && self->args[i][0] != ')') { + TRACE(TRACE_DEBUG, "[%s]", self->args[i]); + for (j = 0; j < IMAP_NFLAGS; j++) { + if (strcasecmp (self->args[i], imap_flag_desc_escaped[j]) == 0) { + flaglist[j] = 1; + flagcount++; + break; + } + } + if (j == IMAP_NFLAGS) { + TRACE(TRACE_DEBUG,"found keyword [%s]", self->args[i]); + keywords = g_list_append(keywords,g_strdup(self->args[i])); + flagcount++; + } + + i++; + } + + i++; + TRACE(TRACE_DEBUG, ")"); + } + + if (!self->args[i]) { + TRACE(TRACE_INFO, "unexpected end of arguments"); + dbmail_imap_session_printf(self, + "%s BAD invalid arguments specified to APPEND\r\n", + self->tag); + return 1; + } + + /** check ACL's for STORE */ + if (flaglist[IMAP_FLAG_SEEN] == 1) { + result = acl_has_right(mailbox, ud->userid, ACL_RIGHT_SEEN); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; /* fatal */ + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no right to store \\SEEN flag\r\n", self->tag); + return 1; + } + } + if (flaglist[IMAP_FLAG_DELETED] == 1) { + result = acl_has_right(mailbox, ud->userid, ACL_RIGHT_DELETE); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; /* fatal */ + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no right to store \\DELETED flag\r\n", self->tag); + return 1; + } + } + if (flaglist[IMAP_FLAG_ANSWERED] == 1 || + flaglist[IMAP_FLAG_FLAGGED] == 1 || + flaglist[IMAP_FLAG_DRAFT] == 1 || + flaglist[IMAP_FLAG_RECENT] == 1 || + g_list_length(keywords) > 0) { + result = acl_has_right(mailbox, ud->userid, ACL_RIGHT_WRITE); + if (result < 0) { + dbmail_imap_session_printf(self, "*BYE internal database error\r\n"); + return -1; + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no right to store flags\r\n", self->tag); + return 1; + } + } + + + /* there could be a literal date here, check if the next argument exists + * if so, assume this is the literal date. + */ + if (self->args[i + 1]) { + struct tm tm; + char *dt = self->args[i]; + + memset(&tm, 0, sizeof(struct tm)); + + dt = g_strstrip(dt); + + if (strptime(dt, "%d-%b-%Y %T", &tm) != NULL) + strftime(sqldate, sizeof(sqldate), "%Y-%m-%d %H:%M:%S", &tm); + else + sqldate[0] = '\0'; + /* internal date specified */ + + i++; + TRACE(TRACE_DEBUG, "internal date [%s] found, next arg [%s]", sqldate, self->args[i]); + } else { + sqldate[0] = '\0'; + } + + /* ok literal msg should be in self->args[i] */ + /* insert this msg */ + + result = db_imap_append_msg(self->args[i], strlen(self->args[i]), mboxid, ud->userid, sqldate, &msg_idnr); + + if (self->msginfo) + msginfo = g_tree_lookup(self->msginfo, &msg_idnr); + + switch (result) { + case -1: + TRACE(TRACE_ERROR, "error appending msg"); + dbmail_imap_session_printf(self, "* BYE internal dbase error storing message\r\n"); + break; + + case 1: + TRACE(TRACE_ERROR, "faulty msg"); + dbmail_imap_session_printf(self, "%s NO invalid message specified\r\n", self->tag); + break; + + case 2: + TRACE(TRACE_INFO, "quotum would exceed"); + dbmail_imap_session_printf(self, "%s NO not enough quotum left\r\n", self->tag); + break; + + case 0: + dbmail_imap_session_printf(self, "%s OK APPEND completed\r\n", self->tag); + break; + } + + if (result == 0 && flagcount > 0) { + if (db_set_msgflag(msg_idnr, mboxid, flaglist, keywords, IMAPFA_ADD, msginfo) < 0) { + TRACE(TRACE_ERROR, "error setting flags for message [%llu]", msg_idnr); + g_list_destroy(keywords); + return -1; + } + } + + g_list_destroy(keywords); + + return result; +} + + + +/* + * SELECTED-STATE COMMANDS + * sort, check, close, expunge, search, fetch, store, copy, uid + */ + +/* + * _ic_check() + * + * request a checkpoint for the selected mailbox + * (equivalent to NOOP) + */ +int _ic_check(struct ImapSession *self) +{ + int result; + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + + if (!check_state_and_args(self, "CHECK", 0, 0, IMAPCS_SELECTED)) + return 1; /* error, return */ + + result = acl_has_right(ud->mailbox, ud->userid, ACL_RIGHT_READ); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE Internal database error\r\n"); + return -1; + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no permission to do check on " + "mailbox\r\n", self->tag); + return 1; + } + + dbmail_imap_session_mailbox_status(self, TRUE); + + dbmail_imap_session_printf(self, "%s OK CHECK completed\r\n", self->tag); + return 0; +} + + +/* + * _ic_close() + * + * expunge deleted messages from selected mailbox & return to AUTH state + * do not show expunge-output + */ +int _ic_close(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result; + + if (!check_state_and_args(self, "CLOSE", 0, 0, IMAPCS_SELECTED)) + return 1; /* error, return */ + + /* check if the user has to right to expunge all messages from the + mailbox. */ + result = acl_has_right(ud->mailbox, ud->userid, ACL_RIGHT_DELETE); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE Internal database error\r\n"); + return -1; + } + /* only perform the expunge if the user has the right to do it */ + if (result == 1) + if (ud->mailbox->permission == IMAPPERM_READWRITE) + db_expunge(ud->mailbox->uid, ud->userid, NULL, + NULL); + + + /* ok, update state (always go to IMAPCS_AUTHENTICATED) */ + dbmail_imap_session_set_state(self, IMAPCS_AUTHENTICATED); + dbmail_imap_session_printf(self, "%s OK CLOSE completed\r\n", self->tag); + return 0; +} + +/* + * _ic_unselect + * + * non-expunging close for select mailbox and return to AUTH state + * + */ + +int _ic_unselect(struct ImapSession *self) +{ + if (!check_state_and_args(self, "UNSELECT", 0, 0, IMAPCS_SELECTED)) + return 1; /* error, return */ + + dbmail_imap_session_mailbox_close(self); + dbmail_imap_session_printf(self, "%s OK UNSELECT completed\r\n", self->tag); + return 0; +} + +/* + * _ic_expunge() + * + * expunge deleted messages from selected mailbox + * show expunge output per message + */ +int _ic_expunge(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + u64_t *msgids, *msn; + u64_t nmsgs, i, uid; + int result; + + + if (!check_state_and_args(self, "EXPUNGE", 0, 0, IMAPCS_SELECTED)) + return 1; /* error, return */ + + if (ud->mailbox->permission != IMAPPERM_READWRITE) { + dbmail_imap_session_printf(self, + "%s NO you do not have write permission on this folder\r\n", + self->tag); + return 1; + } + + result = acl_has_right(ud->mailbox, ud->userid, ACL_RIGHT_DELETE); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO you do not have delete rights on this mailbox\r\n", + self->tag); + return 1; + } + + /* delete messages */ + result = db_expunge(ud->mailbox->uid, ud->userid, &msgids, &nmsgs); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE db error\r\n"); + return -1; + } + if (result == 1) { + dbmail_imap_session_printf(self, "%s OK EXPUNGE completed\r\n", self->tag); + return 1; + } + + for (i=0; i<nmsgs; i++) { + uid = msgids[i]; + msn = g_tree_lookup(self->mailbox->ids, &uid); + + if (! msn) { + TRACE(TRACE_DEBUG,"can't find uid [%llu]", uid); + break; + } + dbmail_imap_session_printf(self, "* %llu EXPUNGE\r\n", *msn); + dbmail_mailbox_remove_uid(self->mailbox, &uid); + } + + if (msgids) + g_free(msgids); + msgids = NULL; + + dbmail_imap_session_printf(self, "%s OK EXPUNGE completed\r\n", self->tag); + return 0; +} + + +/* + * _ic_search() + * + * search the selected mailbox for messages + * + */ + +static int sorted_search(struct ImapSession *self, search_order_t order) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + struct DbmailMailbox *mb; + int result = 0; + gchar *s = NULL; + const gchar *cmd; + gboolean sorted; + + if (order == SEARCH_SORTED) + sorted = 1; + + if (ud->state != IMAPCS_SELECTED) { + dbmail_imap_session_printf(self, + "%s BAD %s command received in invalid state\r\n", + self->tag, self->command); + return 1; + } + + if (!self->args[0]) { + dbmail_imap_session_printf(self, "%s BAD invalid arguments to %s\r\n", + self->tag, self->command); + return 1; + } + + /* check ACL */ + if (! (result = acl_has_right(ud->mailbox, ud->userid, ACL_RIGHT_READ))) { + dbmail_imap_session_printf(self, "%s NO no permission to search mailbox\r\n", self->tag); + return 1; + } + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + + mb = dbmail_mailbox_new(ud->mailbox->uid); + switch(order) { + case SEARCH_SORTED: + cmd = "SORT"; + break; + case SEARCH_UNORDERED: + cmd = "SEARCH"; + break; + case SEARCH_THREAD_REFERENCES: + case SEARCH_THREAD_ORDEREDSUBJECT: + cmd = "THREAD"; + break; + default:// shouldn't happen + cmd = "NO"; + break; + } + if (g_tree_nnodes(mb->ids) > 0) { + dbmail_mailbox_set_uid(mb,self->use_uid); + + if (dbmail_mailbox_build_imap_search(mb, self->args, &(self->args_idx), order) < 0) { + dbmail_imap_session_printf(self, "%s BAD invalid arguments to %s\r\n", + self->tag, cmd); + return 1; + } + dbmail_mailbox_search(mb); + /* ok, display results */ + switch(order) { + case SEARCH_SORTED: + dbmail_mailbox_sort(mb); + s = dbmail_mailbox_sorted_as_string(mb); + break; + case SEARCH_UNORDERED: + s = dbmail_mailbox_ids_as_string(mb); + break; + case SEARCH_THREAD_ORDEREDSUBJECT: + s = dbmail_mailbox_orderedsubject(mb); + break; + case SEARCH_THREAD_REFERENCES: + s = NULL; // TODO: unsupported + break; + } + + } + + if (s) { + dbmail_imap_session_printf(self, "* %s %s\r\n", cmd, s); + g_free(s); + } else { + dbmail_imap_session_printf(self, "* %s\r\n", cmd); + } + + dbmail_imap_session_printf(self, "%s OK %s completed\r\n", self->tag, cmd); + dbmail_mailbox_free(mb); + + return 0; +} + +int _ic_search(struct ImapSession *self) +{ + return sorted_search(self,SEARCH_UNORDERED); +} + +int _ic_sort(struct ImapSession *self) +{ + return sorted_search(self,SEARCH_SORTED); +} + +int _ic_thread(struct ImapSession *self) +{ + if (MATCH(self->args[0],"ORDEREDSUBJECT")) + return sorted_search(self,SEARCH_THREAD_ORDEREDSUBJECT); + if (MATCH(self->args[0],"REFERENCES")) + dbmail_imap_session_printf(self, "%s BAD THREAD=REFERENCES not supported\r\n",self->tag); + //return sorted_search(self,SEARCH_THREAD_REFERENCES); + + return 1; +} + +int _dm_imapsession_get_ids(struct ImapSession *self, const char *set) +{ + int retry = 2; + int result = DM_SUCCESS; + + dbmail_mailbox_set_uid(self->mailbox,self->use_uid); + + if (self->ids) { + g_tree_destroy(self->ids); + self->ids = NULL; + } + while (retry > 0) { + + self->ids = dbmail_mailbox_get_set(self->mailbox, set, self->use_uid); + if (self->ids && g_tree_nnodes(self->ids)> 0) + break; + + if ((result = dbmail_mailbox_open(self->mailbox))!= DM_SUCCESS) + return result; + retry--; + } + + if ( (!self->ids) || (g_tree_nnodes(self->ids)==0) ) { + dbmail_imap_session_printf(self, "%s BAD invalid message range specified\r\n", self->tag); + return DM_EGENERAL; + } + + return DM_SUCCESS; +} + + + +/* + * _ic_fetch() + * + * fetch message(s) from the selected mailbox + */ + +int _ic_fetch(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result, state, setidx; + + if (!check_state_and_args (self, "FETCH", 2, 0, IMAPCS_SELECTED)) + return 1; + + /* check if the user has the right to fetch messages in this mailbox */ + result = acl_has_right(ud->mailbox, ud->userid, ACL_RIGHT_READ); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no permission to fetch from mailbox\r\n", self->tag); + return 1; + } + + dbmail_imap_session_resetFi(self); + + self->fi->getUID = self->use_uid; + + setidx = self->args_idx; + self->args_idx++; //skip on past this for the fetch_parse_args coming next... + + state = 1; + do { + if ( (state = dbmail_imap_session_fetch_parse_args(self)) == -2) { + dbmail_imap_session_printf(self, "%s BAD invalid argument list to fetch\r\n", self->tag); + return 1; + } + TRACE(TRACE_DEBUG,"dbmail_imap_session_fetch_parse_args loop idx %llu state %d ", self->args_idx, state); + self->args_idx++; + } while (state > 0); + + result = DM_SUCCESS; + + if (g_tree_nnodes(self->mailbox->ids) > 0) { + if ((result = _dm_imapsession_get_ids(self, self->args[setidx])) == DM_SUCCESS) { + self->ids_list = g_tree_keys(self->ids); + result = dbmail_imap_session_fetch_get_items(self); + } + } + + dbmail_imap_session_fetch_free(self); + + if (result == DM_SUCCESS) + dbmail_imap_session_printf(self, "%s OK %sFETCH completed\r\n", self->tag, self->use_uid ? "UID " : ""); + + return result; +} + + + +/* + * _ic_store() + * + * alter message-associated data in selected mailbox + */ + +static gboolean _do_store(u64_t *id, gpointer UNUSED value, struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + cmd_store_t *cmd = (cmd_store_t *)self->cmd; + + u64_t *msn; + MessageInfo *msginfo; + char *s; + int i; + + msginfo = g_tree_lookup(self->msginfo, id); + if (! msginfo) { + TRACE(TRACE_WARNING, "unable to lookup msginfo struct for [%llu]", *id); + return TRUE; + } + + msn = g_tree_lookup(self->mailbox->ids, id); + + if (ud->mailbox->permission == IMAPPERM_READWRITE) { + if (db_set_msgflag(*id, ud->mailbox->uid, cmd->flaglist, cmd->keywords, cmd->action, msginfo) < 0) { + dbmail_imap_session_printf(self, "\r\n* BYE internal dbase error\r\n"); + return TRUE; + } + } + + // Set the system flags + for (i = 0; i < IMAP_NFLAGS; i++) { + + if (i == IMAP_FLAG_RECENT) // Skip recent_flag because it is already part of the query. + continue; + + switch (cmd->action) { + case IMAPFA_ADD: + if (cmd->flaglist[i]) + msginfo->flags[i] = 1; + break; + case IMAPFA_REMOVE: + if (cmd->flaglist[i]) + msginfo->flags[i] = 0; + break; + case IMAPFA_REPLACE: + if (cmd->flaglist[i]) + msginfo->flags[i] = 1; + else + msginfo->flags[i] = 0; + break; + } + } + + // Set the user keywords as labels + g_list_merge(&(msginfo->keywords), cmd->keywords, cmd->action, (GCompareFunc)g_ascii_strcasecmp); + + // reporting callback + if (! cmd->silent) { + s = imap_flags_as_string(msginfo); + dbmail_imap_session_printf(self,"* %llu FETCH (FLAGS %s)\r\n", *msn, s); + g_free(s); + } + + return FALSE; +} + +int _ic_store(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + cmd_store_t *cmd; + int result, i, j, k; + + cmd = g_new0(cmd_store_t,1); + + if (!check_state_and_args (self, "STORE", 2, 0, IMAPCS_SELECTED)) + return 1; + + k = self->args_idx; + /* multiple flags should be parenthesed */ + if (self->args[k+3] && strcmp(self->args[k+2], "(") != 0) { + dbmail_imap_session_printf(self, "%s BAD invalid argument(s) to STORE\r\n", + self->tag); + return 1; + } + + cmd->silent = FALSE; + + /* retrieve action type */ + if (MATCH(self->args[k+1], "flags")) + cmd->action = IMAPFA_REPLACE; + else if (MATCH(self->args[k+1], "flags.silent")) { + cmd->action = IMAPFA_REPLACE; + cmd->silent = TRUE; + } else if (MATCH(self->args[k+1], "+flags")) + cmd->action = IMAPFA_ADD; + else if (MATCH(self->args[k+1], "+flags.silent")) { + cmd->action = IMAPFA_ADD; + cmd->silent = TRUE; + } else if (MATCH(self->args[k+1], "-flags")) + cmd->action = IMAPFA_REMOVE; + else if (MATCH(self->args[k+1], "-flags.silent")) { + cmd->action = IMAPFA_REMOVE; + cmd->silent = TRUE; + } + + if (cmd->action == IMAPFA_NONE) { + dbmail_imap_session_printf(self, "%s BAD invalid STORE action specified\r\n", self->tag); + return 1; + } + + /* now fetch flag list */ + i = (strcmp(self->args[k+2], "(") == 0) ? 3 : 2; + + for (; self->args[k+i] && strcmp(self->args[k+i], ")") != 0; i++) { + for (j = 0; j < IMAP_NFLAGS; j++) { + /* storing the recent flag explicitely is not allowed */ + if (MATCH(self->args[k+i],"\\Recent")) { + dbmail_imap_session_printf(self, "%s BAD invalid flag list to STORE command\r\n", self->tag); + return 1; + } + + if (MATCH(self->args[k+i], imap_flag_desc_escaped[j])) { + cmd->flaglist[j] = 1; + break; + } + } + + if (j == IMAP_NFLAGS) + cmd->keywords = g_list_append(cmd->keywords,g_strdup(self->args[k+i])); + } + + /** check ACL's for STORE */ + if (cmd->flaglist[IMAP_FLAG_SEEN] == 1) { + result = acl_has_right(ud->mailbox, ud->userid, ACL_RIGHT_SEEN); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error"); + return -1; /* fatal */ + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no right to store \\SEEN flag\r\n", self->tag); + return 1; + } + } + if (cmd->flaglist[IMAP_FLAG_DELETED] == 1) { + result = acl_has_right(ud->mailbox, ud->userid, ACL_RIGHT_DELETE); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; /* fatal */ + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no right to store \\DELETED flag\r\n", self->tag); + return 1; + } + } + if (cmd->flaglist[IMAP_FLAG_ANSWERED] == 1 || + cmd->flaglist[IMAP_FLAG_FLAGGED] == 1 || + cmd->flaglist[IMAP_FLAG_DRAFT] == 1 || + g_list_length(cmd->keywords) > 0 ) { + result = acl_has_right(ud->mailbox, ud->userid, ACL_RIGHT_WRITE); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error"); + return -1; + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no right to store flags", self->tag); + return 1; + } + } + /* end of ACL checking. If we get here without returning, the user has + the right to store the flags */ + + self->cmd = cmd; + + result = DM_SUCCESS; + + if (g_tree_nnodes(self->mailbox->ids) > 0) { + if ((result = _dm_imapsession_get_ids(self, self->args[k])) == DM_SUCCESS) { + GTree *t; + t = self->msginfo; + if ((self->msginfo = dbmail_imap_session_get_msginfo(self, self->mailbox->ids)) == NULL) + TRACE(TRACE_DEBUG, "unable to retrieve msginfo"); + if(t) + g_tree_destroy(t); + + g_tree_foreach(self->ids, (GTraverseFunc) _do_store, self); + } + } + + if (result == DM_SUCCESS) + dbmail_imap_session_printf(self, "%s OK %sSTORE completed\r\n", self->tag, self->use_uid ? "UID " : ""); + + return result; +} + + +/* + * _ic_copy() + * + * copy a message to another mailbox + */ + +static gboolean _do_copy(u64_t *id, gpointer UNUSED value, struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + cmd_copy_t *cmd = (cmd_copy_t *)self->cmd; + u64_t newid; + int result; + + result = db_copymsg(*id, cmd->mailbox_id, ud->userid, &newid); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + db_rollback_transaction(); + return TRUE; + } + if (result == -2) { + dbmail_imap_session_printf(self, "%s NO quotum would exceed\r\n", self->tag); + db_rollback_transaction(); + return TRUE; + } + return FALSE; +} + + +int _ic_copy(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + u64_t destmboxid; + int result; + MailboxInfo *destmbox; + cmd_copy_t cmd; + + if (!check_state_and_args(self, "COPY", 2, 2, IMAPCS_SELECTED)) + return 1; /* error, return */ + + /* check if destination mailbox exists */ + if (db_findmailbox(self->args[self->args_idx+1], ud->userid, &destmboxid) == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; /* fatal */ + } + if (destmboxid == 0) { + dbmail_imap_session_printf(self, "%s NO [TRYCREATE] specified mailbox does not exist\r\n", + self->tag); + return 1; + } + // check if user has right to COPY from source mailbox + result = acl_has_right(ud->mailbox, ud->userid, ACL_RIGHT_READ); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; /* fatal */ + } + if (result == 0) { + dbmail_imap_session_printf(self, "%s NO no permission to copy from mailbox\r\n", + self->tag); + return 1; + } + // check if user has right to COPY to destination mailbox + destmbox = dbmail_imap_session_mbxinfo_lookup(self, destmboxid); + + result = acl_has_right(destmbox, ud->userid, ACL_RIGHT_INSERT); + if (result < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; /* fatal */ + } + if (result == 0) { + dbmail_imap_session_printf(self, + "%s NO no permission to copy to mailbox\r\n", self->tag); + return 1; + } + + cmd.mailbox_id = destmboxid; + self->cmd = &cmd; + + if (db_begin_transaction() < 0) + return -1; + + if (g_tree_nnodes(self->mailbox->ids) > 0) { + + if ((_dm_imapsession_get_ids(self, self->args[self->args_idx]) == DM_SUCCESS)) { + g_tree_foreach(self->ids, (GTraverseFunc) _do_copy, self); + } else { + db_rollback_transaction(); + return DM_EGENERAL; + } + } + + if (db_commit_transaction() < 0) + return -1; + + dbmail_imap_session_printf(self, "%s OK %sCOPY completed\r\n", self->tag, + self->use_uid ? "UID " : ""); + return 0; +} + + +/* + * _ic_uid() + * + * fetch/store/copy/search message UID's + */ +int _ic_uid(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result; + + if (ud->state != IMAPCS_SELECTED) { + dbmail_imap_session_printf(self, + "%s BAD UID command received in invalid state\r\n", + self->tag); + return 1; + } + + if (!self->args[0]) { + dbmail_imap_session_printf(self, "%s BAD missing argument(s) to UID\r\n", + self->tag); + return 1; + } + + self->use_uid = 1; /* set global var to make clear we will be using UID's */ + + /* ACL rights for UID are handled by the other functions called below */ + if (MATCH(self->args[self->args_idx], "fetch")) { + self->args_idx++; + result = _ic_fetch(self); + } else if (MATCH(self->args[self->args_idx], "copy")) { + self->args_idx++; + result = _ic_copy(self); + } else if (MATCH(self->args[self->args_idx], "store")) { + self->args_idx++; + result = _ic_store(self); + } else if (MATCH(self->args[self->args_idx], "search")) { + self->args_idx++; + result = _ic_search(self); + } else if (MATCH(self->args[self->args_idx], "sort")) { + self->args_idx++; + result = _ic_sort(self); + } else if (MATCH(self->args[self->args_idx], "thread")) { + self->args_idx++; + result = _ic_thread(self); + } else { + dbmail_imap_session_printf(self, "%s BAD invalid UID command\r\n", self->tag); + result = 1; + } + + self->use_uid = 0; + + return result; +} + + +/* Helper function for _ic_getquotaroot() and _ic_getquota(). + * Send all resource limits in `quota'. + */ +void send_quota(struct ImapSession *self, quota_t * quota) +{ + int r; + u64_t usage, limit; + char *name; + + for (r = 0; r < quota->n_resources; r++) { + if (quota->resource[r].limit > 0) { + switch (quota->resource[r].type) { + case RT_STORAGE: + name = "STORAGE"; + usage = quota->resource[r].usage / 1024; + limit = quota->resource[r].limit / 1024; + break; + default: + continue; + } + dbmail_imap_session_printf(self, + "* QUOTA \"%s\" (%s %llu %llu)\r\n", + quota->root, name, usage, limit); + } + } +} + +/* + * _ic_getquotaroot() + * + * get quota root and send quota + */ +int _ic_getquotaroot(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + quota_t *quota; + char *root, *errormsg; + + if (!check_state_and_args(self, "GETQUOTAROOT", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; /* error, return */ + + root = quota_get_quotaroot(ud->userid, self->args[self->args_idx], &errormsg); + if (root == NULL) { + dbmail_imap_session_printf(self, "%s NO %s\r\n", self->tag, errormsg); + return 1; + } + + quota = quota_get_quota(ud->userid, root, &errormsg); + if (quota == NULL) { + dbmail_imap_session_printf(self, "%s NO %s\r\n", self->tag, errormsg); + return 1; + } + + dbmail_imap_session_printf(self, "* QUOTAROOT \"%s\" \"%s\"\r\n", self->args[self->args_idx], + quota->root); + send_quota(self, quota); + quota_free(quota); + + dbmail_imap_session_printf(self, "%s OK GETQUOTAROOT completed\r\n", self->tag); + return 0; +} + +/* + * _ic_getquot() + * + * get quota + */ +int _ic_getquota(struct ImapSession *self) +{ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + quota_t *quota; + char *errormsg; + + if (!check_state_and_args(self, "GETQUOTA", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; /* error, return */ + + quota = quota_get_quota(ud->userid, self->args[self->args_idx], &errormsg); + if (quota == NULL) { + dbmail_imap_session_printf(self, "%s NO %s\r\n", self->tag, errormsg); + return 1; + } + + send_quota(self, quota); + quota_free(quota); + + dbmail_imap_session_printf(self, "%s OK GETQUOTA completed\r\n", self->tag); + return 0; +} + +/* returns -1 on error, 0 if user or mailbox not found and 1 otherwise */ +static int imap_acl_pre_administer(const char *mailboxname, + const char *username, + u64_t executing_userid, + u64_t * mboxid, u64_t * target_userid) +{ + int result; + result = db_findmailbox(mailboxname, executing_userid, mboxid); + if (result < 1) + return result; + + result = auth_user_exists(username, target_userid); + if (result < 1) + return result; + + return 1; +} + +int _ic_setacl(struct ImapSession *self) +{ + /* SETACL mailboxname identifier mod_rights */ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result; + u64_t mboxid; + u64_t targetuserid; + MailboxInfo *mailbox; + + if (!check_state_and_args(self, "SETACL", 3, 3, IMAPCS_AUTHENTICATED)) + return 1; + + result = imap_acl_pre_administer(self->args[self->args_idx], self->args[self->args_idx+1], ud->userid, + &mboxid, &targetuserid); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } else if (result == 0) { + dbmail_imap_session_printf(self, "%s NO SETACL failure: can't set acl\r\n", + self->tag); + return 1; + } + // has the rights to 'administer' this mailbox? + mailbox = dbmail_imap_session_mbxinfo_lookup(self, mboxid); + + if (acl_has_right(mailbox, ud->userid, ACL_RIGHT_ADMINISTER) != 1) { + dbmail_imap_session_printf(self, "%s NO SETACL failure: can't set acl, " + "you don't have the proper rights\r\n", self->tag); + return 1; + } + // set the new acl + if (acl_set_rights(targetuserid, mboxid, self->args[self->args_idx+2]) < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + + dbmail_imap_session_printf(self, "%s OK SETACL completed\r\n", self->tag); + return 0; +} + + +int _ic_deleteacl(struct ImapSession *self) +{ + // DELETEACL mailboxname identifier + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + u64_t mboxid; + u64_t targetuserid; + MailboxInfo *mailbox; + + if (!check_state_and_args(self, "DELETEACL", 2, 2, IMAPCS_AUTHENTICATED)) + return 1; + + if (imap_acl_pre_administer(self->args[self->args_idx], self->args[self->args_idx+1], ud->userid, + &mboxid, &targetuserid) == -1) { + dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); + return -1; + } + + mailbox = dbmail_imap_session_mbxinfo_lookup(self, mboxid); + + // has the rights to 'administer' this mailbox? + if (acl_has_right(mailbox, ud->userid, ACL_RIGHT_ADMINISTER) != 1) { + dbmail_imap_session_printf(self, "%s NO DELETEACL failure: can't delete " + "acl\r\n", self->tag); + return 1; + } + // set the new acl + if (acl_delete_acl(targetuserid, mboxid) < 0) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + + dbmail_imap_session_printf(self, "%s OK DELETEACL completed\r\n", self->tag); + return 0; +} + +int _ic_getacl(struct ImapSession *self) +{ + /* GETACL mailboxname */ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result; + u64_t mboxid; + char *acl_string; + + if (!check_state_and_args(self, "GETACL", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; + + result = db_findmailbox(self->args[self->args_idx], ud->userid, &mboxid); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } else if (result == 0) { + dbmail_imap_session_printf(self, "%s NO GETACL failure: can't get acl\r\n", + self->tag); + return 1; + } + // get acl string (string of identifier-rights pairs) + if (!(acl_string = acl_get_acl(mboxid))) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + + dbmail_imap_session_printf(self, "* ACL \"%s\" %s\r\n", self->args[self->args_idx], acl_string); + g_free(acl_string); + dbmail_imap_session_printf(self, "%s OK GETACL completed\r\n", self->tag); + return 0; +} + +int _ic_listrights(struct ImapSession *self) +{ + /* LISTRIGHTS mailboxname identifier */ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result; + u64_t mboxid; + u64_t targetuserid; + char *listrights_string; + MailboxInfo *mailbox; + + if (!check_state_and_args(self, "LISTRIGHTS", 2, 2, IMAPCS_AUTHENTICATED)) + return 1; + + result = imap_acl_pre_administer(self->args[self->args_idx], self->args[self->args_idx+1], ud->userid, + &mboxid, &targetuserid); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } else if (result == 0) { + dbmail_imap_session_printf(self, + "%s, NO LISTRIGHTS failure: can't set acl\r\n", + self->tag); + return 1; + } + // has the rights to 'administer' this mailbox? + mailbox = dbmail_imap_session_mbxinfo_lookup(self, mboxid); + + if (acl_has_right(mailbox, ud->userid, ACL_RIGHT_ADMINISTER) != 1) { + dbmail_imap_session_printf(self, + "%s NO LISTRIGHTS failure: can't set acl\r\n", + self->tag); + return 1; + } + // set the new acl + if (!(listrights_string = acl_listrights(targetuserid, mboxid))) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + + dbmail_imap_session_printf(self, "* LISTRIGHTS \"%s\" %s %s\r\n", + self->args[self->args_idx], self->args[self->args_idx+1], listrights_string); + dbmail_imap_session_printf(self, "%s OK LISTRIGHTS completed\r\n", self->tag); + g_free(listrights_string); + return 0; +} + +int _ic_myrights(struct ImapSession *self) +{ + /* MYRIGHTS mailboxname */ + imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; + int result; + u64_t mboxid; + char *myrights_string; + + if (!check_state_and_args(self, "LISTRIGHTS", 1, 1, IMAPCS_AUTHENTICATED)) + return 1; + + result = db_findmailbox(self->args[self->args_idx], ud->userid, &mboxid); + if (result == -1) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } else if (result == 0) { + dbmail_imap_session_printf(self, + "%s NO MYRIGHTS failure: unknown mailbox\r\n", + self->tag); + return 1; + } + + if (!(myrights_string = acl_myrights(ud->userid, mboxid))) { + dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); + return -1; + } + + dbmail_imap_session_printf(self, "* MYRIGHTS \"%s\" %s\r\n", self->args[self->args_idx], + myrights_string); + g_free(myrights_string); + dbmail_imap_session_printf(self, "%s OK MYRIGHTS complete\r\n", self->tag); + return 0; +} + +int _ic_namespace(struct ImapSession *self) +{ + /* NAMESPACE command */ + if (!check_state_and_args(self, "NAMESPACE", 0, 0, IMAPCS_AUTHENTICATED)) + return 1; + + dbmail_imap_session_printf(self, "* NAMESPACE ((\"\" \"%s\")) ((\"%s\" \"%s\")) " + "((\"%s\" \"%s\"))\r\n", + MAILBOX_SEPARATOR, NAMESPACE_USER, + MAILBOX_SEPARATOR, NAMESPACE_PUBLIC, MAILBOX_SEPARATOR); + dbmail_imap_session_printf(self, "%s OK NAMESPACE complete\r\n", self->tag); + return 0; +} |