/* 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. */ /* $Id: imapcommands.c 2207 2006-07-24 15:35:35Z paul $ * * imapcommands.c * * IMAP server command implementations */ #include "dbmail.h" #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifndef MAX_LINESIZE #define MAX_LINESIZE 1024 #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) { if (!check_state_and_args(self, "CAPABILITY", 0, 0, -1)) return 1; /* error, return */ dbmail_imap_session_printf(self, "* CAPABILITY %s\r\n", 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) { if (!check_state_and_args(self, "NOOP", 0, 0, -1)) return 1; /* error, return */ 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, "%s,%s: user (id:%llu) logging out @ [%s]", __FILE__, __func__, 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[0], self->args[1]))) return result; if (imap_before_smtp) db_log_ip(self->ci->ip_src); dbmail_imap_session_printf(self, "%s OK LOGIN completed\r\n", self->tag); 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[0], "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); 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; int result; unsigned idx = 0; char *mailbox; char permstring[PERMSTRING_SIZE]; if (!check_state_and_args(self, "SELECT", 1, 1, IMAPCS_AUTHENTICATED)) return 1; /* error, return */ mailbox = self->args[0]; // flush recent messages from previous select dbmail_imap_session_mailbox_update_recent(self); if ((result = dbmail_imap_session_mailbox_open(self, mailbox))) return result; if ((result = dbmail_imap_session_mailbox_show_info(self))) return result; /* show idx of first unseen msg (if present) */ key = db_first_unseen(ud->mailbox.uid); if (key == (u64_t) (-1)) { dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); return -1; } if (binary_search(ud->mailbox.seq_list, ud->mailbox.exists, key, &idx) != -1) dbmail_imap_session_printf(self, "* OK [UNSEEN %u] first unseen message\r\n", idx + 1); /* permission */ switch (ud->mailbox.permission) { case IMAPPERM_READ: g_snprintf(permstring, PERMSTRING_SIZE, "READ-ONLY"); //dbmail_imap_session_mailbox_select_recent(self); break; case IMAPPERM_READWRITE: g_snprintf(permstring, PERMSTRING_SIZE, "READ-WRITE"); dbmail_imap_session_mailbox_select_recent(self); break; default: trace(TRACE_ERROR, "IMAPD: select(): detected invalid permission mode for mailbox %llu ('%s')", ud->mailbox.uid, self->args[0]); dbmail_imap_session_printf(self, "* BYE fatal: detected invalid mailbox settings\r\n"); return -1; } dbmail_imap_session_set_state(self,IMAPCS_SELECTED); 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[0]; if ((result = dbmail_imap_session_mailbox_open(self, mailbox))) return result; if ((result = dbmail_imap_session_mailbox_show_info(self))) return result; /* update permission: examine forces read-only */ ud->mailbox.permission = IMAPPERM_READ; dbmail_imap_session_set_state(self,IMAPCS_SELECTED); 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[0], 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 is the owner of this mailbox. If so, then the user has the right to delete it. */ result = db_user_is_mailbox_owner(ud->userid, mboxid); if (result < 0) { dbmail_imap_session_printf(self, "* BYE internal database error\r\n"); return -1; } // FIXME: check permission flag on mailbox if (result == 0) { dbmail_imap_session_printf(self, "%s NO no permission to delete mailbox\r\n", self->tag); dbmail_imap_session_set_state(self,IMAPCS_AUTHENTICATED); return 1; } /* 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, "IMAPD: delete(): 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); dm_free(children); return 1; } if (result == -1) { dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); dm_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"); dm_free(children); return -1; /* fatal */ } /* check if this was the currently selected mailbox */ if (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); dm_free(children); return 0; } /* ok remove mailbox */ if (db_delete_mailbox(mboxid, 0, 1)) { trace(TRACE_DEBUG,"%s,%s: db_delete_mailbox failed", __FILE__, __func__); 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 (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, parentmboxid; 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; } // FIXME: check permissions flag on original mailbox /* 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; } if (dbmail_imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_ADMINISTER)) { 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 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"); dm_free(children); return -1; } if (oldnamelen >= strlen(name)) { /* strange error, let's say its fatal */ trace(TRACE_ERROR, "IMAPD: rename(): mailbox names appear to be corrupted"); dbmail_imap_session_printf(self, "* BYE internal error regarding mailbox names\r\n"); dm_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"); dm_free(children); return -1; } } if (children) dm_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 NO mailbox 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 NO mailbox 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"; mailbox_t *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(self->args[0]); self->args[0][i]; i++) { if (stridx(AcceptedMailboxnameChars, self->args[0][i]) == slen) { /* 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, "%s,%s: search with pattern: [%s]", __FILE__,__func__,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"); dm_free(children); g_free(pattern); return -1; } if (result == 1) { dbmail_imap_session_printf(self, "%s BAD invalid pattern specified\r\n", self->tag); dm_free(children); g_free(pattern); return 1; } if( (mb = (mailbox_t *)dm_malloc ( sizeof(mailbox_t) ) ) == NULL) { trace(TRACE_ERROR, "%s,%s: out-of-memory error.", __FILE__, __func__); return -1; } memset(mb,0,sizeof(mailbox_t)); 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_foreach(plist,(GFunc)g_free,NULL); g_list_free(plist); g_free(pstring); } if (children) dm_free(children); g_free(pattern); dm_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; mailbox_t mb; int i, endfound, result; GString *response; GList *plst = NULL; gchar *pstring, *astring; /* TODO: check_state_and_args */ if (ud->state != IMAPCS_AUTHENTICATED && ud->state != IMAPCS_SELECTED) { dbmail_imap_session_printf(self, "%s BAD STATUS command received in invalid state\r\n", self->tag); return 1; } if (!self->args[0] || !self->args[1] || !self->args[2]) { dbmail_imap_session_printf(self, "%s BAD missing argument(s) to STATUS\r\n", self->tag); 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; } /* zero init */ memset(&mb, 0, sizeof(mb)); /* check if mailbox exists */ if (db_findmailbox(self->args[0], ud->userid, &(mb.uid)) == -1) { dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); return -1; } if (mb.uid == 0) { /* 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; } /* retrieve mailbox data */ result = db_getmailbox(&mb); if (result == -1) { dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); return -1; /* fatal */ } 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]); dm_free(mb.seq_list); 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); dm_free(mb.seq_list); g_list_foreach(plst,(GFunc)g_free,NULL); g_list_free(plst); g_string_free(response,TRUE); g_free(astring); g_free(pstring); 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; mailbox_t mailbox; bzero(&mailbox, sizeof(mailbox_t)); for (i = 0; i < IMAP_NFLAGS; i++) flaglist[i] = 0; 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 == 0) { dbmail_imap_session_printf(self, "%s NO [TRYCREATE] could not find specified mailbox\r\n", self->tag); return 1; } trace(TRACE_DEBUG, "%s,%s: mailbox [%s] found, id: %llu", __FILE__, __func__, self->args[0], mboxid); /* check if user has right to append to mailbox */ mailbox.uid = mboxid; 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, "%s,%s: flag list found:", __FILE__, __func__); 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; } } i++; } i++; trace(TRACE_DEBUG, ")"); } if (!self->args[i]) { trace(TRACE_INFO, "%s,%s: unexpected end of arguments", __FILE__, __func__); dbmail_imap_session_printf(self, "%s BAD invalid arguments specified to APPEND\r\n", self->tag); return 1; } for (j = 0; j < IMAP_NFLAGS; j++) if (flaglist[j] == 1) trace(TRACE_DEBUG, "%s,%s: %s set", __FILE__, __func__, imap_flag_desc[j]); /** check ACL's for STORE */ mailbox.uid = mboxid; 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) { 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, "%s,%s: internal date [%s] found, next arg [%s]", __FILE__, __func__, 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); switch (result) { case -1: trace(TRACE_ERROR, "%s,%s: error appending msg", __FILE__, __func__); dbmail_imap_session_printf(self, "* BYE internal dbase error storing message\r\n"); break; case 1: trace(TRACE_ERROR, "%s,%s: faulty msg", __FILE__, __func__); dbmail_imap_session_printf(self, "%s NO invalid message specified\r\n", self->tag); break; case 2: trace(TRACE_INFO, "%s,%s: quotum would exceed", __FILE__, __func__); 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, IMAPFA_ADD) < 0) { trace(TRACE_ERROR, "%s,%s: error setting flags for message [%llu]", __FILE__, __func__, msg_idnr); return -1; } } 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_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_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; mailbox_t newmailbox; u64_t *msgids; u64_t nmsgs, i; unsigned idx; 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 dbase/memory error\r\n"); return -1; } if (result == 1) { dbmail_imap_session_printf(self, "%s OK EXPUNGE completed\r\n", self->tag); return 1; } /* show expunge info */ for (i = 0; i < nmsgs; i++) { /* find the message sequence number */ binary_search(ud->mailbox.seq_list, ud->mailbox.exists, msgids[i], &idx); dbmail_imap_session_printf(self, "* %u EXPUNGE\r\n", idx + 1); /* add one: IMAP MSN starts at 1 not zero */ } if (msgids) dm_free(msgids); msgids = NULL; /* update mailbox info */ memset(&newmailbox, 0, sizeof(newmailbox)); newmailbox.uid = ud->mailbox.uid; result = db_getmailbox(&newmailbox); if (result == -1) { dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); dm_free(newmailbox.seq_list); return -1; /* fatal */ } if (newmailbox.exists != ud->mailbox.exists) dbmail_imap_session_printf(self, "* %u EXISTS\r\n", newmailbox.exists); if (newmailbox.recent != ud->mailbox.recent) dbmail_imap_session_printf(self, "* %u RECENT\r\n", newmailbox.recent); dm_free(ud->mailbox.seq_list); memcpy((void *) &ud->mailbox, (void *) &newmailbox, sizeof(newmailbox)); 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, gboolean sorted) { imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; struct DbmailMailbox *mb; int result = 0; u64_t idx = 0; gchar *s = NULL; gchar *cmd = sorted?"SORT":"SEARCH"; if (ud->state != IMAPCS_SELECTED) { dbmail_imap_session_printf(self, "%s BAD %s command received in invalid state\r\n", self->tag, cmd); return 1; } if (!self->args[0]) { dbmail_imap_session_printf(self, "%s BAD invalid arguments to %s\r\n", self->tag, cmd); 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); dbmail_mailbox_set_uid(mb,self->use_uid); dbmail_mailbox_build_imap_search(mb, self->args, &idx, sorted); dbmail_mailbox_search(mb); /* ok, display results */ if (sorted) { dbmail_mailbox_sort(mb); s = dbmail_mailbox_sorted_as_string(mb); } else { s = dbmail_mailbox_ids_as_string(mb); } dbmail_imap_session_printf(self, "* %s %s", cmd, s?s:""); if (s) g_free(s); dbmail_imap_session_printf(self, "\r\n%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,0); } int _ic_sort(struct ImapSession *self) { return sorted_search(self,1); } /* * _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; u64_t i, fetch_start, fetch_end; u64_t fetch_max, row=0; int rows=0; unsigned fn; int result, idx; char *endptr; char *lastchar = NULL; 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; idx = 1; do { idx = dbmail_imap_session_fetch_parse_args(self, idx); if (idx == -2) { dbmail_imap_session_printf(self, "%s BAD invalid argument list to fetch\r\n", self->tag); return 1; } } while (idx > 0); fetch_max = self->use_uid ? (ud->mailbox.msguidnext - 1) : ud->mailbox.exists; /* now fetch results for each msg */ endptr = self->args[0]; while (*endptr) { if (endptr != self->args[0]) endptr++; /* skip delimiter */ fetch_start = strtoull(endptr, &endptr, 10); if (fetch_start == 0 || fetch_start > fetch_max) { if (self->fi->getUID) dbmail_imap_session_printf(self, "%s OK FETCH completed\r\n", self->tag); else dbmail_imap_session_printf(self, "%s BAD invalid message range specified\r\n", self->tag); return !self->fi->getUID; } switch (*endptr) { case ':': fetch_end = strtoull(++endptr, &lastchar, 10); endptr = lastchar; if (*endptr == '*') { fetch_end = fetch_max; endptr++; break; } if (fetch_end == 0 || fetch_end > fetch_max) { if (!self->fi->getUID) { dbmail_imap_session_printf(self, "%s BAD invalid message range specified\r\n", self->tag); return 1; } } if (fetch_end < fetch_start) { i = fetch_start; fetch_start = fetch_end; fetch_end = i; } break; case ',': case 0: fetch_end = fetch_start; break; default: dbmail_imap_session_printf(self, "%s BAD invalid character in message range\r\n", self->tag); return 1; } if (! self->use_uid) { if (fetch_start > 0) fetch_start--; if (fetch_end > 0) fetch_end--; } trace(TRACE_DEBUG,"%s,%s: fetch_start [%llu] fetch_end [%llu]", __FILE__, __func__, fetch_start, fetch_end); if ((rows=dbmail_imap_session_fetch_get_unparsed(self, fetch_start, fetch_end)) < 0) return -1; row=0; for (i = fetch_start; i <= fetch_end; i++) { self->msg_idnr = (self->use_uid ? i : ud->mailbox.seq_list[i]); if (self->use_uid) { if (i > fetch_max) { /* passed the last one */ dbmail_imap_session_printf(self, "%s OK FETCH completed\r\n", self->tag); return 0; } /* check if the message with this UID belongs to this mailbox */ if (binary_search (ud->mailbox.seq_list, ud->mailbox.exists, i, &fn) == -1) continue; dbmail_imap_session_printf(self, "* %u FETCH (", fn + 1); } else dbmail_imap_session_printf(self, "* %llu FETCH (", i + 1); /* go fetch the items */ fflush(self->ci->tx); if (dbmail_imap_session_fetch_get_items(self,row) < 0) return -1; row++; } } dbmail_imap_session_printf(self, "%s OK %sFETCH completed\r\n", self->tag, self->use_uid ? "UID " : ""); return 0; } /* * _ic_store() * * alter message-associated data in selected mailbox */ int _ic_store(struct ImapSession *self) { imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; char *endptr, *lastchar = NULL; u64_t i, store_start, store_end, seq_max; unsigned fn = 0; int result, j, isfirstout = 0; int be_silent = 0, action = IMAPFA_NONE; int flaglist[IMAP_NFLAGS], msgflags[IMAP_NFLAGS]; u64_t thisnum, lo, hi; memset(flaglist, 0, sizeof(int) * IMAP_NFLAGS); if (ud->state != IMAPCS_SELECTED) { dbmail_imap_session_printf(self, "%s BAD STORE command received in invalid state\r\n", self->tag); return 1; } if (!self->args[0] || !self->args[1] || !self->args[2]) { dbmail_imap_session_printf(self, "%s BAD missing argument(s) to STORE\r\n", self->tag); return 1; } /* multiple flags should be parenthesed */ if (self->args[3] && strcmp(self->args[2], "(") != 0) { dbmail_imap_session_printf(self, "%s BAD invalid argument(s) to STORE\r\n", self->tag); return 1; } /* retrieve action type */ if (strcasecmp(self->args[1], "flags") == 0) action = IMAPFA_REPLACE; else if (strcasecmp(self->args[1], "flags.silent") == 0) { action = IMAPFA_REPLACE; be_silent = 1; } else if (strcasecmp(self->args[1], "+flags") == 0) action = IMAPFA_ADD; else if (strcasecmp(self->args[1], "+flags.silent") == 0) { action = IMAPFA_ADD; be_silent = 1; } else if (strcasecmp(self->args[1], "-flags") == 0) action = IMAPFA_REMOVE; else if (strcasecmp(self->args[1], "-flags.silent") == 0) { action = IMAPFA_REMOVE; be_silent = 1; } if (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[2], "(") == 0) ? 3 : 2; for (; self->args[i] && strcmp(self->args[i], ")") != 0; i++) { for (j = 0; j < IMAP_NFLAGS; j++) if (strcasecmp(self->args[i], imap_flag_desc_escaped[j]) == 0) { flaglist[j] = 1; break; } if (j == IMAP_NFLAGS) { dbmail_imap_session_printf(self, "%s BAD invalid flag list to STORE command\r\n", self->tag); return 1; } } /** check ACL's for STORE */ if (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 (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 (flaglist[IMAP_FLAG_ANSWERED] == 1 || flaglist[IMAP_FLAG_FLAGGED] == 1 || flaglist[IMAP_FLAG_DRAFT] == 1 || flaglist[IMAP_FLAG_RECENT] == 1) { 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 */ db_getmailbox(&ud->mailbox); // resync mailbox seq_max = (self->use_uid ? (ud->mailbox.msguidnext - 1) : ud->mailbox.exists); /* set flags & show if needed */ endptr = self->args[0]; while (*endptr) { if (endptr != self->args[0]) endptr++; /* skip delimiter */ store_start = strtoull(endptr, &endptr, 10); if (store_start == 0xffffffff) // outlook's idea of '*' store_start = seq_max; if (store_start == 0 || store_start > seq_max) { dbmail_imap_session_printf(self, "%s BAD invalid message range specified\r\n", self->tag); return 1; } switch (*endptr) { case ':': store_end = strtoull(++endptr, &lastchar, 10); if (store_end == 0xffffffff) // outlook's idea of '*' store_end = seq_max; endptr = lastchar; if (*endptr == '*') { store_end = (self->use_uid ? (ud->mailbox.msguidnext - 1) : ud->mailbox.exists); endptr++; break; } if (store_end == 0 || store_end > seq_max) { dbmail_imap_session_printf(self, "%s BAD invalid message range specified\r\n", self->tag); return 1; } if (store_end < store_start) { i = store_start; store_start = store_end; store_end = i; } break; case ',': case 0: store_end = store_start; break; default: dbmail_imap_session_printf(self, "%s BAD invalid character in message range\r\n", self->tag); return 1; } if (!self->use_uid) { store_start--; store_end--; } if (store_start == store_end) { thisnum = (self->use_uid ? store_start : ud-> mailbox.seq_list[store_start]); if (self->use_uid) { /* check if the message with this UID belongs to this mailbox */ if (binary_search (ud->mailbox.seq_list, ud->mailbox.exists, store_start, &fn) == -1) continue; } if (ud->mailbox.permission == IMAPPERM_READWRITE) { result = db_set_msgflag(thisnum, ud->mailbox.uid, flaglist, action); if (result == -1) { dbmail_imap_session_printf(self, "\r\n* BYE internal dbase error\r\n"); return -1; } } if (!be_silent) { result = db_get_msgflag_all(thisnum, ud->mailbox.uid, msgflags); if (result == -1) { dbmail_imap_session_printf(self, "\r\n* BYE internal dbase error\r\n"); return -1; } dbmail_imap_session_printf(self, "* %llu FETCH (FLAGS (", self->use_uid ? (u64_t) (fn + 1) : store_start + 1); for (j = 0, isfirstout = 1; j < IMAP_NFLAGS; j++) { if (msgflags[j]) { dbmail_imap_session_printf(self, "%s%s", isfirstout ? "" : " ", imap_flag_desc_escaped [j]); if (isfirstout) isfirstout = 0; } } dbmail_imap_session_printf(self, "))\r\n"); } } else { if (!self->use_uid) { /* find the msgUID's to use */ lo = ud->mailbox.seq_list[store_start]; hi = ud->mailbox.seq_list[store_end]; } else { lo = store_start; hi = store_end; } if (ud->mailbox.permission == IMAPPERM_READWRITE) { result = db_set_msgflag_range(lo, hi, ud->mailbox.uid, flaglist, action); if (result == -1) { dbmail_imap_session_printf(self, "\r\n* BYE internal dbase error\r\n"); return -1; } } if (!be_silent) { for (i = store_start; i <= store_end; i++) { thisnum = (self->use_uid ? i : ud->mailbox.seq_list[i]); if (self->use_uid) { /* check if the message with this UID belongs to this mailbox */ if (binary_search (ud->mailbox.seq_list, ud->mailbox.exists, i, &fn) == -1) continue; } result = db_get_msgflag_all(thisnum, ud->mailbox. uid, msgflags); if (result == -1) { dbmail_imap_session_printf(self, "\r\n* BYE internal dbase error\r\n"); return -1; } dbmail_imap_session_printf(self, "* %llu FETCH (FLAGS (", self->use_uid ? (u64_t) (fn + 1) : i + 1); for (j = 0, isfirstout = 1; j < IMAP_NFLAGS; j++) { if (msgflags[j]) { dbmail_imap_session_printf(self, "%s%s", isfirstout ? "" : " ", imap_flag_desc_escaped [j]); if (isfirstout) isfirstout = 0; } } dbmail_imap_session_printf(self, "))\r\n"); } } } } dbmail_imap_session_printf(self, "%s OK %sSTORE completed\r\n", self->tag, self->use_uid ? "UID " : ""); return 0; } /* * _ic_copy() * * copy a message to another mailbox */ int _ic_copy(struct ImapSession *self) { imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData; u64_t i, copy_start, copy_end; unsigned fn; u64_t destmboxid, thisnum; int result; u64_t new_msgid; char *endptr, *lastchar = NULL; mailbox_t destmbox; bzero(&destmbox, sizeof(destmbox)); 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[1], ud->userid, &destmboxid) == -1) { dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); return -1; /* fatal */ } if (destmboxid == 0) { /* error: cannot select mailbox */ 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.uid = 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; } db_getmailbox(&ud->mailbox); // resync mailbox /* ok copy msgs */ endptr = self->args[0]; if (db_begin_transaction() < 0) return -1; while (*endptr) { if (endptr != self->args[0]) endptr++; /* skip delimiter */ copy_start = strtoull(endptr, &lastchar, 10); endptr = lastchar; if (copy_start == 0 || copy_start > (self->use_uid ? (ud->mailbox.msguidnext - 1) : ud->mailbox.exists)) { dbmail_imap_session_printf(self, "%s BAD invalid message range specified\r\n", self->tag); db_rollback_transaction(); return 1; } switch (*endptr) { case ':': copy_end = strtoull(++endptr, &lastchar, 10); endptr = lastchar; if (*endptr == '*') { copy_end = (self->use_uid ? (ud->mailbox.msguidnext - 1) : ud->mailbox.exists); endptr++; break; } if (copy_end == 0 || copy_end > (self->use_uid ? (ud->mailbox.msguidnext - 1) : ud->mailbox.exists)) { dbmail_imap_session_printf(self, "%s BAD invalid message range specified\r\n", self->tag); db_rollback_transaction(); return 1; } if (copy_end < copy_start) { i = copy_start; copy_start = copy_end; copy_end = i; } break; case ',': case 0: copy_end = copy_start; break; default: dbmail_imap_session_printf(self, "%s BAD invalid character in message range\r\n", self->tag); db_rollback_transaction(); return 1; } if (!self->use_uid) { copy_start--; copy_end--; } for (i = copy_start; i <= copy_end; i++) { thisnum = (self->use_uid ? i : ud->mailbox. seq_list[i]); if (self->use_uid) { /* check if the message with this UID belongs to this mailbox */ if (binary_search (ud->mailbox.seq_list, ud->mailbox.exists, i, &fn) == -1) continue; } result = db_copymsg(thisnum, destmboxid, ud->userid, &new_msgid); if (result == -1) { dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); db_rollback_transaction(); return -1; } if (result == -2) { dbmail_imap_session_printf(self, "%s NO quotum would exceed\r\n", self->tag); db_rollback_transaction(); return 1; } } } 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 (strcasecmp(self->args[0], "fetch") == 0) { self->args++; result = _ic_fetch(self); } else if (strcasecmp(self->args[0], "copy") == 0) { self->args++; result = _ic_copy(self); } else if (strcasecmp(self->args[0], "store") == 0) { self->args++; result = _ic_store(self); } else if (strcasecmp(self->args[0], "search") == 0) { self->args++; result = _ic_search(self); } else if (strcasecmp(self->args[0], "sort") == 0) { self->args++; result = _ic_sort(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[0], &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[0], 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[0], &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; mailbox_t mailbox; bzero(&mailbox, sizeof(mailbox_t)); if (!check_state_and_args(self, "SETACL", 3, 3, IMAPCS_AUTHENTICATED)) return 1; result = imap_acl_pre_administer(self->args[0], self->args[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.uid = 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[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; mailbox_t mailbox; if (!check_state_and_args(self, "DELETEACL", 2, 2, IMAPCS_AUTHENTICATED)) return 1; if (imap_acl_pre_administer(self->args[0], self->args[1], ud->userid, &mboxid, &targetuserid) == -1) { dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n"); return -1; } bzero(&mailbox, sizeof(mailbox_t)); mailbox.uid = 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[0], 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[0], acl_string); dm_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; mailbox_t mailbox; if (!check_state_and_args(self, "LISTRIGHTS", 2, 2, IMAPCS_AUTHENTICATED)) return 1; result = imap_acl_pre_administer(self->args[0], self->args[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? bzero(&mailbox, sizeof(mailbox_t)); mailbox.uid = 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[0], self->args[1], listrights_string); dbmail_imap_session_printf(self, "%s OK LISTRIGHTS completed\r\n", self->tag); dm_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[0], 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[0], myrights_string); dm_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; }