/*
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;
}
syntax highlighted by Code2HTML, v. 0.9.1