/*
 Copyright (C) 1999-2004 IC & S  dbmail@ic-s.nl
 Copyright (c) 2004-2006 NFG Net Facilities Group BV support@nfg.nl

 This program is free software; you can redistribute it and/or 
 modify it under the terms of the GNU General Public License 
 as published by the Free Software Foundation; either 
 version 2 of the License, or (at your option) any later 
 version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/* 
 *
 * imapcommands.c
 * 
 * IMAP server command implementations
 */

#include "dbmail.h"
#define THIS_MODULE "imap"

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif


#ifndef MAX_RETRIES
#define MAX_RETRIES 12
#endif

extern const char *imap_flag_desc[];
extern const char *imap_flag_desc_escaped[];

int list_is_lsub = 0;

extern const char AcceptedMailboxnameChars[];

extern int imap_before_smtp;

/*
 * RETURN VALUES _ic_ functions:
 *
 * -1 Fatal error, close connection to user
 *  0 Succes
 *  1 Non-fatal error, connection stays alive
 */


/* 
 * ANY-STATE COMMANDS: capability, noop, logout
 */

/*
 * _ic_capability()
 *
 * returns a string to the client containing the server capabilities
 */
int _ic_capability(struct ImapSession *self)
{
	field_t val;
	gboolean override = FALSE;

	if (!check_state_and_args(self, "CAPABILITY", 0, 0, -1))
		return 1;	/* error, return */

	
	GETCONFIGVALUE("capability", "IMAP", val);
	if (strlen(val) > 0)
		override = TRUE;

	dbmail_imap_session_printf(self, "* CAPABILITY %s\r\n", override ? val : IMAP_CAPABILITY_STRING);
	dbmail_imap_session_printf(self, "%s OK CAPABILITY completed\r\n", self->tag);

	return 0;
}


/*
 * _ic_noop()
 *
 * performs No operation
 */
int _ic_noop(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	if (!check_state_and_args(self, "NOOP", 0, 0, -1))
		return 1;	/* error, return */

	if (ud->state == IMAPCS_SELECTED)
		dbmail_imap_session_mailbox_status(self, TRUE);
	else
		TRACE(TRACE_DEBUG,"state: %d", ud->state);
	
	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(&timestring);
	dbmail_imap_session_set_state(self,IMAPCS_LOGOUT);
	TRACE(TRACE_MESSAGE, "user (id:%llu) logging out @ [%s]", ud->userid, timestring);

	dbmail_imap_session_printf(self, "* BYE dbmail imap server kisses you goodbye\r\n");

	return 0;
}

/*
 * PRE-AUTHENTICATED STATE COMMANDS
 * login, authenticate
 */
/*
 * _ic_login()
 *
 * Performs login-request handling.
 */
int _ic_login(struct ImapSession *self)
{
	int result;
	timestring_t timestring;
	
	if (!check_state_and_args(self, "LOGIN", 2, 2, IMAPCS_NON_AUTHENTICATED))
		return 1;
	
	create_current_timestring(&timestring);
	if ((result = dbmail_imap_session_handle_auth(self, self->args[self->args_idx], self->args[self->args_idx+1])))
		return result;
	if (imap_before_smtp)
		db_log_ip(self->ci->ip_src);

	child_reg_connected_user(self->args[self->args_idx]);

	dbmail_imap_session_printf(self, "%s OK LOGIN completed\r\n", self->tag);

	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(&timestring);

	/* check authentication method */
	if (strcasecmp(self->args[self->args_idx], "login") != 0) {
		dbmail_imap_session_printf(self,
			"%s NO Invalid authentication mechanism specified\r\n",
			self->tag);
		return 1;
	}

	/* ask for username (base64 encoded) */
	username = g_new0(char,MAX_LINESIZE);
	if (dbmail_imap_session_prompt(self,"username", username)) {
		dbmail_imap_session_printf(self, "* BYE error reading username\r\n");
		g_free(username);
		return -1;
	}
	/* ask for password */
	password = g_new0(char,MAX_LINESIZE);
	if (dbmail_imap_session_prompt(self,"password", password)) {
		dbmail_imap_session_printf(self, "* BYE error reading password\r\n");
		g_free(username);
		g_free(password);
		return -1;
	}

	/* try to validate user */
	if ((result = dbmail_imap_session_handle_auth(self,username,password))) {
		g_free(username);
		g_free(password);
		return result;
	}

	if (imap_before_smtp)
		db_log_ip(self->ci->ip_src);

	dbmail_imap_session_printf(self, "%s OK AUTHENTICATE completed\r\n", self->tag);
	
	child_reg_connected_user(username);

	g_free(username);
	g_free(password);
	return 0;
}


/* 
 * AUTHENTICATED STATE COMMANDS 
 * select, examine, create, delete, rename, subscribe, 
 * unsubscribe, list, lsub, status, append
 */

/*
 * _ic_select()
 * 
 * select a specified mailbox
 */
#define PERMSTRING_SIZE 80
int _ic_select(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	u64_t key = 0;
	u64_t *msn = NULL;
	int result;
	char *mailbox;
	char permstring[PERMSTRING_SIZE];

	if (!check_state_and_args(self, "SELECT", 1, 1, IMAPCS_AUTHENTICATED))
		return 1;	/* error, return */

	mailbox = self->args[self->args_idx];
	
	if ((result = dbmail_imap_session_mailbox_open(self, mailbox))) 
		return result;

	dbmail_imap_session_set_state(self,IMAPCS_SELECTED);

	if ((result = dbmail_imap_session_mailbox_show_info(self)))
		return result;

	/* show idx of first unseen msg (if present) */
	if (ud->mailbox.exists) {
		key = db_first_unseen(ud->mailbox.uid);
		if ( (key > 0) && (msn = g_tree_lookup(self->mailbox->ids, &key))) {
			dbmail_imap_session_printf(self,
				"* OK [UNSEEN %llu] first unseen message\r\n", *msn);
		}
	}
	/* permission */
	switch (ud->mailbox.permission) {
	case IMAPPERM_READ:
		g_snprintf(permstring, PERMSTRING_SIZE, "READ-ONLY");
		break;
	case IMAPPERM_READWRITE:
		g_snprintf(permstring, PERMSTRING_SIZE, "READ-WRITE");
		break;
	default:
		TRACE(TRACE_ERROR, "detected invalid permission mode for mailbox %llu ('%s')",
		      ud->mailbox.uid, self->args[self->args_idx]);

		dbmail_imap_session_printf(self,
			"* BYE fatal: detected invalid mailbox settings\r\n");
		return -1;
	}

	dbmail_imap_session_printf(self, "%s OK [%s] SELECT completed\r\n", self->tag,
		permstring);
	return 0;
}


/*
 * _ic_examine()
 * 
 * examines a specified mailbox 
 */
int _ic_examine(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	int result;
	char *mailbox;

	if (!check_state_and_args(self, "EXAMINE", 1, 1, IMAPCS_AUTHENTICATED))
		return 1;

	mailbox = self->args[self->args_idx];

	if ((result = dbmail_imap_session_mailbox_open(self, mailbox)))
		return result;

	/* update permission: examine forces read-only */
	ud->mailbox.permission = IMAPPERM_READ;

	dbmail_imap_session_set_state(self,IMAPCS_SELECTED);

	if ((result = dbmail_imap_session_mailbox_show_info(self)))
		return result;
	
	dbmail_imap_session_printf(self, "%s OK [READ-ONLY] EXAMINE completed\r\n", self->tag);
	return 0;
}


/*
 * _ic_create()
 *
 * create a mailbox
 */
int _ic_create(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	int result;
	const char *message;
	u64_t mboxid;
	
	if (!check_state_and_args(self, "CREATE", 1, 1, IMAPCS_AUTHENTICATED))
		return 1;

	/* Create the mailbox and its parents. */
	result = db_mailbox_create_with_parents(self->args[self->args_idx], BOX_COMMANDLINE, ud->userid, &mboxid, &message);

	if (result > 0) {
		dbmail_imap_session_printf(self, "%s NO %s\r\n", self->tag, message);
		return DM_EGENERAL;
	} else if (result < 0) {
		dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
		return DM_EQUERY;
	}

	dbmail_imap_session_printf(self, "%s OK CREATE completed\r\n", self->tag);
	return DM_SUCCESS;
}


/*
 * _ic_delete()
 *
 * deletes a specified mailbox
 */
int _ic_delete(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	int result, nchildren = 0;
	u64_t *children = NULL, mboxid;
	char *mailbox = self->args[0];

	if (!check_state_and_args(self, "DELETE", 1, 1, IMAPCS_AUTHENTICATED))
		return 1;	/* error, return */

	if (! (mboxid = dbmail_imap_session_mailbox_get_idnr(self, mailbox)) ) {
		dbmail_imap_session_printf(self, "%s NO mailbox doesn't exists\r\n", self->tag);
		return 1;
	}

	/* Check if the user has ACL delete rights to this mailbox;
	 * this also returns true is the user owns the mailbox. */
	result = dbmail_imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_DELETE);
	if (result != 0)
		return result;
	
	/* check if there is an attempt to delete inbox */
	if (strcasecmp(self->args[0], "inbox") == 0) {
		dbmail_imap_session_printf(self, "%s NO cannot delete special mailbox INBOX\r\n", self->tag);
		return 1;
	}

	/* check for children of this mailbox */
	result = db_listmailboxchildren(mboxid, ud->userid, &children, &nchildren);
	if (result == -1) {
		/* error */
		TRACE(TRACE_ERROR, "cannot retrieve list of mailbox children");
		dbmail_imap_session_printf(self, "* BYE dbase/memory error\r\n");
		return -1;
	}

	if (nchildren != 0) {
		/* mailbox has inferior names; error if \noselect specified */
		result = db_isselectable(mboxid);
		if (result == 0) {
			dbmail_imap_session_printf(self, "%s NO mailbox is non-selectable\r\n", self->tag);
			g_free(children);
			return 1;
		}
		if (result == -1) {
			dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
			g_free(children);
			return -1;	/* fatal */
		}

		/* mailbox has inferior names; remove all msgs and set noselect flag */
		result = db_removemsg(ud->userid, mboxid);
		if (result != -1)
			result = db_setselectable(mboxid, 0);	/* set non-selectable flag */

		if (result == -1) {
			dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
			g_free(children);
			return -1;	/* fatal */
		}

		/* check if this was the currently selected mailbox */
		if (mboxid == ud->mailbox.uid) 
			dbmail_imap_session_set_state(self,IMAPCS_AUTHENTICATED);

		/* ok done */
		dbmail_imap_session_printf(self, "%s OK DELETE completed\r\n", self->tag);
		g_free(children);
		return 0;
	}

	/* ok remove mailbox */
	if (db_delete_mailbox(mboxid, 0, 1)) {
		TRACE(TRACE_DEBUG,"db_delete_mailbox failed");
		dbmail_imap_session_printf(self,"%s NO DELETE failed\r\n", self->tag);
		return DM_EGENERAL;
	}

	/* check if this was the currently selected mailbox */
	if (mboxid == ud->mailbox.uid) 
		dbmail_imap_session_set_state(self, IMAPCS_AUTHENTICATED);

	dbmail_imap_session_printf(self, "%s OK DELETE completed\r\n", self->tag);
	return 0;
}


/*
 * _ic_rename()
 *
 * renames a specified mailbox
 */
int _ic_rename(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	u64_t mboxid, newmboxid, *children;
	u64_t parentmboxid = 0;
	size_t oldnamelen;
	int nchildren, i, result;
	char newname[IMAP_MAX_MAILBOX_NAMELEN],
	    name[IMAP_MAX_MAILBOX_NAMELEN];

	if (!check_state_and_args(self, "RENAME", 2, 2, IMAPCS_AUTHENTICATED))
		return 1;

	if ((mboxid = dbmail_imap_session_mailbox_get_idnr(self, self->args[0])) == 0) {
		dbmail_imap_session_printf(self, "%s NO mailbox does not exist\r\n", self->tag);
		return 1;
	}

	/* check if new name is valid */
        if (!checkmailboxname(self->args[1])) {
	        dbmail_imap_session_printf(self, "%s NO new mailbox name contains invalid characters\r\n", self->tag);
                return 1;
        }

	if ((newmboxid = dbmail_imap_session_mailbox_get_idnr(self, self->args[1])) != 0) {
		dbmail_imap_session_printf(self, "%s NO new mailbox already exists\r\n", self->tag);
		return 1;
	}

	oldnamelen = strlen(self->args[0]);

	/* check if new name would invade structure as in
	 * test (exists)
	 * rename test test/testing
	 * would create test/testing but delete test
	 */
	if (strncasecmp(self->args[0], self->args[1], (int) oldnamelen) == 0 &&
	    strlen(self->args[1]) > oldnamelen && self->args[1][oldnamelen] == '/') {
		dbmail_imap_session_printf(self,
			"%s NO new mailbox would invade mailbox structure\r\n",
			self->tag);
		return 1;
	}

	/* check if structure of new name is valid */
	/* i.e. only last part (after last '/' can be nonexistent) */
	for (i = strlen(self->args[1]) - 1; i >= 0 && self->args[1][i] != '/'; i--);
	if (i >= 0) {
		self->args[1][i] = '\0';	/* note: original char was '/' */

		if (db_findmailbox(self->args[1], ud->userid, &parentmboxid) == -1) {
			dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
			return -1;	/* fatal */
		}
		if (parentmboxid == 0) {
			/* parent mailbox does not exist */
			dbmail_imap_session_printf(self,
				"%s NO new mailbox would invade mailbox structure\r\n",
				self->tag);
			return 1;
		}

		/* ok, reset arg */
		self->args[1][i] = '/';
	}

	/* Check if the user has ACL delete rights to old name, 
	 * and create rights to the parent of the new name, or
	 * if the user just owns both mailboxes. */
	TRACE(TRACE_DEBUG, "Checking right to DELETE [%llu]", mboxid);
	result = dbmail_imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_DELETE);
	if (result != 0)
		return result;
	TRACE(TRACE_DEBUG, "We have the right to DELETE [%llu]", mboxid);
	if (!parentmboxid) {
		TRACE(TRACE_DEBUG, "Destination is a top-level mailbox; not checking right to CREATE.");
	} else {
		TRACE(TRACE_DEBUG, "Checking right to CREATE under [%llu]", parentmboxid);
		result = dbmail_imap_session_mailbox_check_acl(self, parentmboxid, ACL_RIGHT_CREATE);
		if (result != 0)
			return result;
		TRACE(TRACE_DEBUG, "We have the right to CREATE under [%llu]", parentmboxid);
	}

	/* check if it is INBOX to be renamed */
	if (strcasecmp(self->args[0], "inbox") == 0) {
		/* ok, renaming inbox */
		/* this means creating a new mailbox and moving all the INBOX msgs to the new mailbox */
		/* inferior names of INBOX are left unchanged */
		result = db_createmailbox(self->args[1], ud->userid, &newmboxid);
		if (result == -1) {
			dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
			return -1;
		}

		result = db_movemsg(newmboxid, mboxid);
		if (result == -1) {
			dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
			return -1;
		}

		/* ok done */
		dbmail_imap_session_printf(self, "%s OK RENAME completed\r\n", self->tag);
		return 0;
	}

	/* check for inferior names */
	result = db_listmailboxchildren(mboxid, ud->userid, &children, &nchildren);
	if (result == -1) {
		dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
		return -1;
	}

	/* replace name for each child */
	for (i = 0; i < nchildren; i++) {
		result = db_getmailboxname(children[i], ud->userid, name);
		if (result == -1) {
			dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
			g_free(children);
			return -1;
		}

		if (oldnamelen >= strlen(name)) {
			/* strange error, let's say its fatal */
			TRACE(TRACE_ERROR, "mailbox names appear to be corrupted");
			dbmail_imap_session_printf(self, "* BYE internal error regarding mailbox names\r\n");
			g_free(children);
			return -1;
		}

		g_snprintf(newname, IMAP_MAX_MAILBOX_NAMELEN, "%s%s", self->args[1], &name[oldnamelen]);

		result = db_setmailboxname(children[i], newname);
		if (result == -1) {
			dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
			g_free(children);
			return -1;
		}
			
	}
	if (children)
		g_free(children);

	/* now replace name */
	result = db_setmailboxname(mboxid, self->args[1]);
	if (result == -1) {
		dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
		return -1;
	}

	dbmail_imap_session_printf(self, "%s OK RENAME completed\r\n", self->tag);
	return 0;
}


/*
 * _ic_subscribe()
 *
 * subscribe to a specified mailbox
 */
int _ic_subscribe(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	u64_t mboxid;

	if (!check_state_and_args(self, "SUBSCRIBE", 1, 1, IMAPCS_AUTHENTICATED))
		return 1;

	if (! (mboxid = dbmail_imap_session_mailbox_get_idnr(self, self->args[0]))) {
		dbmail_imap_session_printf(self, "%s 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 OK UNSUBSCRIBE on mailbox that does not exist\r\n", self->tag);
		return 0;
	}

	/* check for the lookup-right. RFC is unclear about which right to
	   use, so I guessed it should be lookup */
	
	if (dbmail_imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_LOOKUP))
		return 1;

	if (db_unsubscribe(mboxid, ud->userid) == -1) {
		dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
		return -1;
	}

	dbmail_imap_session_printf(self, "%s OK UNSUBSCRIBE completed\r\n", self->tag);
	return 0;
}


/*
 * _ic_list()
 *
 * executes a list command
 */
int _ic_list(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	u64_t *children = NULL;
	int result;
	size_t slen;
	unsigned i;
	unsigned nchildren;
	char *pattern;
	char *thisname = list_is_lsub ? "LSUB" : "LIST";
	
	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(AcceptedMailboxnameChars); 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, "search with pattern: [%s]",pattern);
	
	result = db_findmailbox_by_regex(ud->userid, pattern, &children, &nchildren, list_is_lsub);
	if (result == -1) {
		dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
		g_free(children);
		g_free(pattern);
		return -1;
	}

	if (result == 1) {
		dbmail_imap_session_printf(self, "%s BAD invalid pattern specified\r\n",
			self->tag);
		g_free(children);
		g_free(pattern);
		return 1;
	}

	mb = g_new0(mailbox_t,1);

	for (i = 0; i < nchildren; i++) {
		if ((db_getmailbox_list_result(children[i], ud->userid, mb) != 0))
			continue;
		
		plist = NULL;
		if (mb->no_select)
			plist = g_list_append(plist, g_strdup("\\noselect"));
		if (mb->no_inferiors)
			plist = g_list_append(plist, g_strdup("\\noinferiors"));
		if (mb->no_children)
			plist = g_list_append(plist, g_strdup("\\hasnochildren"));
		else
			plist = g_list_append(plist, g_strdup("\\haschildren"));
		
		/* show */
		pstring = dbmail_imap_plist_as_string(plist);
		dbmail_imap_session_printf(self, "* %s %s \"%s\" \"%s\"\r\n", thisname, 
				pstring, MAILBOX_SEPARATOR, mb->name);
		
		g_list_destroy(plist);
		g_free(pstring);
	}


	if (children)
		g_free(children);

	g_free(pattern);
	g_free(mb);
	dbmail_imap_session_printf(self, "%s OK %s completed\r\n", self->tag, thisname);

	return 0;
}


/*
 * _ic_lsub()
 *
 * list subscribed mailboxes
 */
int _ic_lsub(struct ImapSession *self)
{
	int result;

	list_is_lsub = 1;
	result = _ic_list(self);
	list_is_lsub = 0;
	return result;
}


/*
 * _ic_status()
 *
 * inquire the status of a mailbox
 */
int _ic_status(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	mailbox_t mb;
	int i, endfound, result;
	GString *response;
	GList *plst = NULL;
	gchar *pstring, *astring;
	
	
	if (!check_state_and_args(self, "STATUS", 3, 0, IMAPCS_AUTHENTICATED))
		return 1;

	if (strcmp(self->args[1], "(") != 0) {
		dbmail_imap_session_printf(self, "%s BAD argument list should be parenthesed\r\n", self->tag);
		return 1;
	}

	/* check final arg: should be ')' and no new '(' in between */
	for (i = 2, endfound = 0; self->args[i]; i++) {
		if (strcmp(self->args[i], ")") == 0) {
			endfound = i;
			break;
		}

		if (strcmp(self->args[i], "(") == 0) {
			dbmail_imap_session_printf(self, "%s BAD too many parentheses specified\r\n", self->tag);
			return 1;
		}
	}

	if (endfound == 2) {
		dbmail_imap_session_printf(self, "%s BAD argument list empty\r\n", self->tag);
		return 1;
	}

	if (self->args[endfound + 1]) {
		dbmail_imap_session_printf(self, "%s BAD argument list too long\r\n", self->tag);
		return 1;
	}


	/* 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]);
			return 1;
		}
	}
	astring = dbmail_imap_astring_as_string(self->args[0]);
	pstring = dbmail_imap_plist_as_string(plst); 

	response = g_string_new("");
	g_string_printf(response, "* STATUS %s %s", astring, pstring);	
	dbmail_imap_session_printf(self, "%s\r\n", response->str);
	dbmail_imap_session_printf(self, "%s OK STATUS completed\r\n", self->tag);

	g_list_destroy(plst);
	g_string_free(response,TRUE);
	g_free(astring);
	g_free(pstring);

	return 0;
}

/*
 * _ic_idle
 *
 * non-expunging close for select mailbox and return to AUTH state
 *
 */

int _ic_idle(struct ImapSession *self)
{
	int result;
	if (!check_state_and_args(self, "IDLE", 0, 0, IMAPCS_AUTHENTICATED))
		return 1;	/* error, return */

	if ((result = dbmail_imap_session_idle(self)) != 0)
		return result;

	dbmail_imap_session_printf(self, "%s OK IDLE terminated\r\n", self->tag);
	return 0;
}



/*
 * _ic_append()
 *
 * append a message to a mailbox
 */
int _ic_append(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	u64_t mboxid;
	u64_t msg_idnr;
	int i, j, result;
	timestring_t sqldate;
	int flaglist[IMAP_NFLAGS];
	int flagcount = 0;
	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, "mailbox [%s] found, id: %llu", 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, "flag list found:");

		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, "unexpected end of arguments");
		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 set", 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, "internal date [%s] found, next arg [%s]", sqldate, self->args[i]);
	} else {
		sqldate[0] = '\0';
	}

	/* ok literal msg should be in self->args[i] */
	/* insert this msg */

	result = db_imap_append_msg(self->args[i], strlen(self->args[i]), mboxid, ud->userid, sqldate, &msg_idnr);
	switch (result) {
	case -1:
		TRACE(TRACE_ERROR, "error appending msg");
		dbmail_imap_session_printf(self, "* BYE internal dbase error storing message\r\n");
		break;

	case 1:
		TRACE(TRACE_ERROR, "faulty msg");
		dbmail_imap_session_printf(self, "%s NO invalid message specified\r\n", self->tag);
		break;

	case 2:
		TRACE(TRACE_INFO, "quotum would exceed");
		dbmail_imap_session_printf(self, "%s NO not enough quotum left\r\n", self->tag);
		break;

	case 0:
		dbmail_imap_session_printf(self, "%s OK APPEND completed\r\n", self->tag);
		break;
	}

	if (result == 0 && flagcount > 0) {
		if (db_set_msgflag(msg_idnr, mboxid, flaglist, IMAPFA_ADD) < 0) {
			TRACE(TRACE_ERROR, "error setting flags for message [%llu]", 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_mailbox_status(self, TRUE);

	dbmail_imap_session_printf(self, "%s OK CHECK completed\r\n", self->tag);
	return 0;
}


/*
 * _ic_close()
 *
 * expunge deleted messages from selected mailbox & return to AUTH state
 * do not show expunge-output
 */
int _ic_close(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	int result;

	if (!check_state_and_args(self, "CLOSE", 0, 0, IMAPCS_SELECTED))
		return 1;	/* error, return */

	/* check if the user has to right to expunge all messages from the
	   mailbox. */
	result = acl_has_right(&ud->mailbox, ud->userid, ACL_RIGHT_DELETE);
	if (result < 0) {
		dbmail_imap_session_printf(self, "* BYE Internal database error\r\n");
		return -1;
	}
	/* only perform the expunge if the user has the right to do it */
	if (result == 1)
		if (ud->mailbox.permission == IMAPPERM_READWRITE)
			db_expunge(ud->mailbox.uid, ud->userid, NULL,
				   NULL);


	/* ok, update state (always go to IMAPCS_AUTHENTICATED) */
	dbmail_imap_session_set_state(self, IMAPCS_AUTHENTICATED);
	dbmail_imap_session_printf(self, "%s OK CLOSE completed\r\n", self->tag);
	return 0;
}

/*
 * _ic_unselect
 *
 * non-expunging close for select mailbox and return to AUTH state
 *
 */

int _ic_unselect(struct ImapSession *self)
{
	if (!check_state_and_args(self, "UNSELECT", 0, 0, IMAPCS_SELECTED))
		return 1;	/* error, return */

	dbmail_imap_session_mailbox_close(self);
	dbmail_imap_session_printf(self, "%s OK UNSELECT completed\r\n", self->tag);
	return 0;
}

/*
 * _ic_expunge()
 *
 * expunge deleted messages from selected mailbox
 * show expunge output per message
 */
int _ic_expunge(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	u64_t *msgids, *msn;
	u64_t nmsgs, i, uid;
	int result;
	

	if (!check_state_and_args(self, "EXPUNGE", 0, 0, IMAPCS_SELECTED))
		return 1; /* error, return */

	if (ud->mailbox.permission != IMAPPERM_READWRITE) {
		dbmail_imap_session_printf(self,
			"%s NO you do not have write permission on this folder\r\n",
			self->tag);
		return 1;
	}

	result = acl_has_right(&ud->mailbox, ud->userid, ACL_RIGHT_DELETE);
	if (result < 0) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	}
	if (result == 0) {
		dbmail_imap_session_printf(self, "%s NO you do not have delete rights on this mailbox\r\n", 
		self->tag);
		return 1;
	}

	/* delete messages */
	result = db_expunge(ud->mailbox.uid, ud->userid, &msgids, &nmsgs);
	if (result == -1) {
		dbmail_imap_session_printf(self, "* BYE db error\r\n");
		return -1;
	}
	if (result == 1) {
		dbmail_imap_session_printf(self, "%s OK EXPUNGE completed\r\n", self->tag);
		return 1;
	}

	for (i=0; i<nmsgs; i++) {
		uid = msgids[i];
		msn = g_tree_lookup(self->mailbox->ids, &uid);

		if (! msn) {
			TRACE(TRACE_DEBUG,"can't find uid [%llu]", uid);
			break;
		}
		dbmail_imap_session_printf(self, "* %llu EXPUNGE\r\n", *msn);
		dbmail_mailbox_remove_uid(self->mailbox, &uid);
	}
		
	if (msgids)
		g_free(msgids);
	msgids = NULL;

	dbmail_imap_session_printf(self, "%s OK EXPUNGE completed\r\n", self->tag);
	return 0;
}


/*
 * _ic_search()
 *
 * search the selected mailbox for messages
 *
 */

static int sorted_search(struct ImapSession *self, search_order_t order)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	struct DbmailMailbox *mb;
	int result = 0;
	gchar *s = NULL;
	const gchar *cmd;
	gboolean sorted;

	if (order == SEARCH_SORTED)
		sorted = 1;
	
	if (ud->state != IMAPCS_SELECTED) {
		dbmail_imap_session_printf(self,
			"%s BAD %s command received in invalid state\r\n",
			self->tag, self->command);
		return 1;
	}
	
	if (!self->args[0]) {
		dbmail_imap_session_printf(self, "%s BAD invalid arguments to %s\r\n",
			self->tag, self->command);
		return 1;
	}
	
	/* check ACL */
	if (! (result = acl_has_right(&ud->mailbox, ud->userid, ACL_RIGHT_READ))) {
		dbmail_imap_session_printf(self, "%s NO no permission to search mailbox\r\n", self->tag);
		return 1;
	}
	if (result < 0) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	}

	mb = dbmail_mailbox_new(ud->mailbox.uid);
	switch(order) {
		case SEARCH_SORTED:
			cmd = "SORT";
			break;
		case SEARCH_UNORDERED:
			cmd = "SEARCH";
			break;
		case SEARCH_THREAD_REFERENCES:
		case SEARCH_THREAD_ORDEREDSUBJECT:
			cmd = "THREAD";
			break;
		default:// shouldn't happen
			cmd = "NO";
			break;
	}
	if (g_tree_nnodes(mb->ids) > 0) {
		dbmail_mailbox_set_uid(mb,self->use_uid);

		if (dbmail_mailbox_build_imap_search(mb, self->args, &(self->args_idx), order) < 0) {
			dbmail_imap_session_printf(self, "%s BAD invalid arguments to %s\r\n",
				self->tag, cmd);
			return 1;
		}
		dbmail_mailbox_search(mb);
		/* ok, display results */
		switch(order) {
			case SEARCH_SORTED:
				dbmail_mailbox_sort(mb);
				s = dbmail_mailbox_sorted_as_string(mb);
			break;
			case SEARCH_UNORDERED:
				s = dbmail_mailbox_ids_as_string(mb);
			break;
			case SEARCH_THREAD_ORDEREDSUBJECT:
				s = dbmail_mailbox_orderedsubject(mb);
			break;
			case SEARCH_THREAD_REFERENCES:
				s = NULL; // TODO: unsupported
			break;
		}

	}

	if (s) {
		dbmail_imap_session_printf(self, "* %s %s\r\n", cmd, s);
		g_free(s);
	} else {
		dbmail_imap_session_printf(self, "* %s\r\n", cmd);
	}

	dbmail_imap_session_printf(self, "%s OK %s completed\r\n", self->tag, cmd);
	dbmail_mailbox_free(mb);
	
	return 0;
}

int _ic_search(struct ImapSession *self)
{
	return sorted_search(self,SEARCH_UNORDERED);
}

int _ic_sort(struct ImapSession *self)
{
	return sorted_search(self,SEARCH_SORTED);
}

int _ic_thread(struct ImapSession *self)
{
	if (MATCH(self->args[0],"ORDEREDSUBJECT"))
		return sorted_search(self,SEARCH_THREAD_ORDEREDSUBJECT);
	if (MATCH(self->args[0],"REFERENCES"))
		dbmail_imap_session_printf(self, "%s BAD THREAD=REFERENCES not supported\r\n",self->tag);
		//return sorted_search(self,SEARCH_THREAD_REFERENCES);

	return 1;
}

int _dm_imapsession_get_ids(struct ImapSession *self, const char *set)
{
	int retry = 2;
	int result = DM_SUCCESS;

	dbmail_mailbox_set_uid(self->mailbox,self->use_uid);

	if (self->ids) {
		g_tree_destroy(self->ids);
		self->ids = NULL;
	}
	while (retry > 0) {

		self->ids = dbmail_mailbox_get_set(self->mailbox, set, self->use_uid);
		if (self->ids && g_tree_nnodes(self->ids)> 0)
			break;

		if ((result = dbmail_mailbox_open(self->mailbox))!= DM_SUCCESS)
			return result;
		retry--;
	}

	if ( (!self->ids) || (g_tree_nnodes(self->ids)==0) ) {
		dbmail_imap_session_printf(self, "%s BAD invalid message range specified\r\n", self->tag);
		return DM_EGENERAL;
	}

	return DM_SUCCESS;
}



/*
 * _ic_fetch()
 *
 * fetch message(s) from the selected mailbox
 */
	
int _ic_fetch(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	int result, state, setidx;

	if (!check_state_and_args (self, "FETCH", 2, 0, IMAPCS_SELECTED))
		return 1;

	/* check if the user has the right to fetch messages in this mailbox */
	result = acl_has_right(&ud->mailbox, ud->userid, ACL_RIGHT_READ);
	if (result < 0) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	}
	if (result == 0) {
		dbmail_imap_session_printf(self, "%s NO no permission to fetch from mailbox\r\n", self->tag);
		return 1;
	}
	
	dbmail_imap_session_resetFi(self);

	self->fi->getUID = self->use_uid;

	setidx = self->args_idx;
	self->args_idx++; //skip on past this for the fetch_parse_args coming next...

	state = 1;
	do {
		if ( (state = dbmail_imap_session_fetch_parse_args(self)) == -2) {
			dbmail_imap_session_printf(self, "%s BAD invalid argument list to fetch\r\n", self->tag);
			return 1;
		}
		TRACE(TRACE_DEBUG,"dbmail_imap_session_fetch_parse_args loop idx %llu state %d ", self->args_idx, state);
		self->args_idx++;
	} while (state > 0);

	result = DM_SUCCESS;

  	if (g_tree_nnodes(self->mailbox->ids) > 0) {
 		if ((result = _dm_imapsession_get_ids(self, self->args[setidx])) == DM_SUCCESS) {
  			self->ids_list = g_tree_keys(self->ids);
  			result = dbmail_imap_session_fetch_get_items(self);
  		}
	}

	dbmail_imap_session_fetch_free(self);

	if (result == DM_SUCCESS)
		dbmail_imap_session_printf(self, "%s OK %sFETCH completed\r\n", self->tag, self->use_uid ? "UID " : "");

	return result;
}



/*
 * _ic_store()
 *
 * alter message-associated data in selected mailbox
 */

static gboolean _do_store(u64_t *id, gpointer UNUSED value, struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	cmd_store_t *cmd = (cmd_store_t *)self->cmd;

	u64_t *msn;
	msginfo_t *msginfo;
	char *s;
	int i;

	msginfo = g_tree_lookup(self->msginfo, id);
	if (! msginfo) {
		TRACE(TRACE_WARNING, "unable to lookup msginfo struct for [%llu]", *id);
		return TRUE;
	}

	msn = g_tree_lookup(self->mailbox->ids, id);

	if (ud->mailbox.permission == IMAPPERM_READWRITE) {
		if (db_set_msgflag(*id, ud->mailbox.uid, cmd->flaglist, cmd->action) < 0) {
			dbmail_imap_session_printf(self, "\r\n* BYE internal dbase error\r\n");
			return TRUE;
		}
	}

	for (i = 0; i < IMAP_NFLAGS; i++) {
		// Skip recent_flag because it is part of the query.
		if (i == IMAP_FLAG_RECENT)
			continue;
		switch (cmd->action) {
			case IMAPFA_ADD:
				if (cmd->flaglist[i])
					msginfo->flags[i] = 1;
			break;
			case IMAPFA_REMOVE:
				if (cmd->flaglist[i]) 
					msginfo->flags[i] = 0;
			break;
			case IMAPFA_REPLACE:
				if (cmd->flaglist[i]) 
					msginfo->flags[i] = 1;
				else
					msginfo->flags[i] = 0;
			break;
		}
	}

	if (! cmd->silent) {
		s = imap_flags_as_string(msginfo);
		dbmail_imap_session_printf(self,"* %llu FETCH (FLAGS %s)\r\n", *msn, s);
		g_free(s);
	}

	return FALSE;
}

int _ic_store(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	cmd_store_t cmd;
	int result, i, j, k;

	memset(cmd.flaglist, 0, sizeof(int) * IMAP_NFLAGS);

	if (!check_state_and_args (self, "STORE", 2, 0, IMAPCS_SELECTED))
		return 1;

	k = self->args_idx;
	/* multiple flags should be parenthesed */
	if (self->args[k+3] && strcmp(self->args[k+2], "(") != 0) {
		dbmail_imap_session_printf(self, "%s BAD invalid argument(s) to STORE\r\n",
			self->tag);
		return 1;
	}

	cmd.silent = FALSE;

	/* retrieve action type */
	if (MATCH(self->args[k+1], "flags"))
		cmd.action = IMAPFA_REPLACE;
	else if (MATCH(self->args[k+1], "flags.silent")) {
		cmd.action = IMAPFA_REPLACE;
		cmd.silent = TRUE;
	} else if (MATCH(self->args[k+1], "+flags"))
		cmd.action = IMAPFA_ADD;
	else if (MATCH(self->args[k+1], "+flags.silent")) {
		cmd.action = IMAPFA_ADD;
		cmd.silent = TRUE;
	} else if (MATCH(self->args[k+1], "-flags"))
		cmd.action = IMAPFA_REMOVE;
	else if (MATCH(self->args[k+1], "-flags.silent")) {
		cmd.action = IMAPFA_REMOVE;
		cmd.silent = TRUE;
	}

	if (cmd.action == IMAPFA_NONE) {
		dbmail_imap_session_printf(self, "%s BAD invalid STORE action specified\r\n", self->tag);
		return 1;
	}

	/* now fetch flag list */
	i = (strcmp(self->args[k+2], "(") == 0) ? 3 : 2;

	for (; self->args[k+i] && strcmp(self->args[k+i], ")") != 0; i++) {
		for (j = 0; j < IMAP_NFLAGS; j++) {
			/* storing the recent flag explicitely is not allowed */
			if (MATCH(self->args[k+i],"\\Recent")) {
				j = IMAP_NFLAGS;
				break;
			}
				
			if (MATCH(self->args[k+i], imap_flag_desc_escaped[j])) {
				cmd.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 (cmd.flaglist[IMAP_FLAG_SEEN] == 1) {
		result = acl_has_right(&ud->mailbox, ud->userid, ACL_RIGHT_SEEN);
		if (result < 0) {
			dbmail_imap_session_printf(self, "* BYE internal database error");
			return -1;	/* fatal */
		}
		if (result == 0) {
			dbmail_imap_session_printf(self, "%s NO no right to store \\SEEN flag\r\n", self->tag);
			return 1;
		}
	}
	if (cmd.flaglist[IMAP_FLAG_DELETED] == 1) {
		result = acl_has_right(&ud->mailbox, ud->userid, ACL_RIGHT_DELETE);
		if (result < 0) {
			dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
			return -1;	/* fatal */
		}
		if (result == 0) {
			dbmail_imap_session_printf(self, "%s NO no right to store \\DELETED flag\r\n", self->tag);
			return 1;
		}
	}
	if (cmd.flaglist[IMAP_FLAG_ANSWERED] == 1 ||
	    cmd.flaglist[IMAP_FLAG_FLAGGED] == 1 ||
	    cmd.flaglist[IMAP_FLAG_DRAFT] == 1) {
		result = acl_has_right(&ud->mailbox, ud->userid, ACL_RIGHT_WRITE);
		if (result < 0) {
			dbmail_imap_session_printf(self, "* BYE internal database error");
			return -1;
		}
		if (result == 0) {
			dbmail_imap_session_printf(self, "%s NO no right to store flags", self->tag);
			return 1;
		}
	}
	/* end of ACL checking. If we get here without returning, the user has
	   the right to store the flags */

	self->cmd = &cmd;
	
	result = DM_SUCCESS;

  	if (g_tree_nnodes(self->mailbox->ids) > 0) {
 		if ((result = _dm_imapsession_get_ids(self, self->args[k])) == DM_SUCCESS) {
 			GTree *t;
 			t = self->msginfo;
 			if ((self->msginfo = dbmail_imap_session_get_msginfo(self, self->mailbox->ids)) == NULL)
 				TRACE(TRACE_DEBUG, "unable to retrieve msginfo");
 			if(t)
 				g_tree_destroy(t);
 
 			g_tree_foreach(self->ids, (GTraverseFunc) _do_store, self);
  		}
  	}	

	if (result == DM_SUCCESS)
		dbmail_imap_session_printf(self, "%s OK %sSTORE completed\r\n", self->tag, self->use_uid ? "UID " : "");

	return result;
}


/*
 * _ic_copy()
 *
 * copy a message to another mailbox
 */

static gboolean _do_copy(u64_t *id, gpointer UNUSED value, struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	cmd_copy_t *cmd = (cmd_copy_t *)self->cmd;
	u64_t newid;
	int result;

	result = db_copymsg(*id, cmd->mailbox_id, ud->userid, &newid);
	if (result == -1) {
		dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
		db_rollback_transaction();
		return TRUE;
	}
	if (result == -2) {
		dbmail_imap_session_printf(self, "%s NO quotum would exceed\r\n", self->tag);
		db_rollback_transaction();
		return TRUE;
	}
	return FALSE;
}


int _ic_copy(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	u64_t destmboxid;
	int result;
	mailbox_t destmbox;
	cmd_copy_t cmd;
	
	memset(&destmbox, 0, 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[self->args_idx+1], ud->userid, &destmboxid) == -1) {
		dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
		return -1;	/* fatal */
	}
	if (destmboxid == 0) {
		/* 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;
	}

	cmd.mailbox_id = destmboxid;
	self->cmd = &cmd;

	if (db_begin_transaction() < 0)
		return -1;

	if (g_tree_nnodes(self->mailbox->ids) > 0) {
  
 		if ((_dm_imapsession_get_ids(self, self->args[self->args_idx]) == DM_SUCCESS)) {
 			g_tree_foreach(self->ids, (GTraverseFunc) _do_copy, self);
 		} else {
 			db_rollback_transaction();
  			return DM_EGENERAL;
  		}
  	}	

	if (db_commit_transaction() < 0)
		return -1;

	dbmail_imap_session_printf(self, "%s OK %sCOPY completed\r\n", self->tag,
		self->use_uid ? "UID " : "");
	return 0;
}


/*
 * _ic_uid()
 *
 * fetch/store/copy/search message UID's
 */
int _ic_uid(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	int result;

	if (ud->state != IMAPCS_SELECTED) {
		dbmail_imap_session_printf(self,
			"%s BAD UID command received in invalid state\r\n",
			self->tag);
		return 1;
	}

	if (!self->args[0]) {
		dbmail_imap_session_printf(self, "%s BAD missing argument(s) to UID\r\n",
			self->tag);
		return 1;
	}

	self->use_uid = 1;	/* set global var to make clear we will be using UID's */
	
	/* ACL rights for UID are handled by the other functions called below */
	if (MATCH(self->args[self->args_idx], "fetch")) {
		self->args_idx++; 
		result = _ic_fetch(self);
	} else if (MATCH(self->args[self->args_idx], "copy")) {
		self->args_idx++;
		result = _ic_copy(self);
	} else if (MATCH(self->args[self->args_idx], "store")) {
		self->args_idx++;
		result = _ic_store(self);
	} else if (MATCH(self->args[self->args_idx], "search")) {
		self->args_idx++;
		result = _ic_search(self);
	} else if (MATCH(self->args[self->args_idx], "sort")) {
		self->args_idx++;
		result = _ic_sort(self);
	} else if (MATCH(self->args[self->args_idx], "thread")) {
		self->args_idx++;
		result = _ic_thread(self);
	} else {
		dbmail_imap_session_printf(self, "%s BAD invalid UID command\r\n", self->tag);
		result = 1;
	}

	self->use_uid = 0;

	return result;
}


/* Helper function for _ic_getquotaroot() and _ic_getquota().
 * Send all resource limits in `quota'.
 */
void send_quota(struct ImapSession *self, quota_t * quota)
{
	int r;
	u64_t usage, limit;
	char *name;

	for (r = 0; r < quota->n_resources; r++) {
		if (quota->resource[r].limit > 0) {
			switch (quota->resource[r].type) {
			case RT_STORAGE:
				name = "STORAGE";
				usage = quota->resource[r].usage / 1024;
				limit = quota->resource[r].limit / 1024;
				break;
			default:
				continue;
			}
			dbmail_imap_session_printf(self,
				"* QUOTA \"%s\" (%s %llu %llu)\r\n",
				quota->root, name, usage, limit);
		}
	}
}

/*
 * _ic_getquotaroot()
 *
 * get quota root and send quota
 */
int _ic_getquotaroot(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	quota_t *quota;
	char *root, *errormsg;

	if (!check_state_and_args(self, "GETQUOTAROOT", 1, 1, IMAPCS_AUTHENTICATED))
		return 1;	/* error, return */

	root = quota_get_quotaroot(ud->userid, self->args[self->args_idx], &errormsg);
	if (root == NULL) {
		dbmail_imap_session_printf(self, "%s NO %s\r\n", self->tag, errormsg);
		return 1;
	}

	quota = quota_get_quota(ud->userid, root, &errormsg);
	if (quota == NULL) {
		dbmail_imap_session_printf(self, "%s NO %s\r\n", self->tag, errormsg);
		return 1;
	}

	dbmail_imap_session_printf(self, "* QUOTAROOT \"%s\" \"%s\"\r\n", self->args[self->args_idx],
		quota->root);
	send_quota(self, quota);
	quota_free(quota);

	dbmail_imap_session_printf(self, "%s OK GETQUOTAROOT completed\r\n", self->tag);
	return 0;
}

/*
 * _ic_getquot()
 *
 * get quota
 */
int _ic_getquota(struct ImapSession *self)
{
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	quota_t *quota;
	char *errormsg;

	if (!check_state_and_args(self, "GETQUOTA", 1, 1, IMAPCS_AUTHENTICATED))
		return 1;	/* error, return */

	quota = quota_get_quota(ud->userid, self->args[self->args_idx], &errormsg);
	if (quota == NULL) {
		dbmail_imap_session_printf(self, "%s NO %s\r\n", self->tag, errormsg);
		return 1;
	}

	send_quota(self, quota);
	quota_free(quota);

	dbmail_imap_session_printf(self, "%s OK GETQUOTA completed\r\n", self->tag);
	return 0;
}

/* returns -1 on error, 0 if user or mailbox not found and 1 otherwise */
static int imap_acl_pre_administer(const char *mailboxname,
				   const char *username,
				   u64_t executing_userid,
				   u64_t * mboxid, u64_t * target_userid)
{
	int result;
	result = db_findmailbox(mailboxname, executing_userid, mboxid);
	if (result < 1)
		return result;

	result = auth_user_exists(username, target_userid);
	if (result < 1)
		return result;

	return 1;
}

int _ic_setacl(struct ImapSession *self)
{
	/* SETACL mailboxname identifier mod_rights */
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	int result;
	u64_t mboxid;
	u64_t targetuserid;
	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[self->args_idx], self->args[self->args_idx+1], ud->userid,
					 &mboxid, &targetuserid);
	if (result == -1) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	} else if (result == 0) {
		dbmail_imap_session_printf(self, "%s NO SETACL failure: can't set acl\r\n",
			self->tag);
		return 1;
	}
	// has the rights to 'administer' this mailbox? 
	mailbox.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[self->args_idx+2]) < 0) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	}

	dbmail_imap_session_printf(self, "%s OK SETACL completed\r\n", self->tag);
	return 0;
}


int _ic_deleteacl(struct ImapSession *self)
{
	// DELETEACL mailboxname identifier
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	u64_t mboxid;
	u64_t targetuserid;
	mailbox_t mailbox;

	if (!check_state_and_args(self, "DELETEACL", 2, 2, IMAPCS_AUTHENTICATED))
		return 1;

	if (imap_acl_pre_administer(self->args[self->args_idx], self->args[self->args_idx+1], ud->userid,
				    &mboxid, &targetuserid) == -1) {
		dbmail_imap_session_printf(self, "* BYE internal dbase error\r\n");
		return -1;
	}
	
	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[self->args_idx], ud->userid, &mboxid);
	if (result == -1) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	} else if (result == 0) {
		dbmail_imap_session_printf(self, "%s NO GETACL failure: can't get acl\r\n",
			self->tag);
		return 1;
	}
	// get acl string (string of identifier-rights pairs)
	if (!(acl_string = acl_get_acl(mboxid))) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	}

	dbmail_imap_session_printf(self, "* ACL \"%s\" %s\r\n", self->args[self->args_idx], acl_string);
	g_free(acl_string);
	dbmail_imap_session_printf(self, "%s OK GETACL completed\r\n", self->tag);
	return 0;
}

int _ic_listrights(struct ImapSession *self)
{
	/* LISTRIGHTS mailboxname identifier */
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	int result;
	u64_t mboxid;
	u64_t targetuserid;
	char *listrights_string;
	mailbox_t mailbox;

	if (!check_state_and_args(self, "LISTRIGHTS", 2, 2, IMAPCS_AUTHENTICATED))
		return 1;

	result = imap_acl_pre_administer(self->args[self->args_idx], self->args[self->args_idx+1], ud->userid,
					 &mboxid, &targetuserid);
	if (result == -1) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	} else if (result == 0) {
		dbmail_imap_session_printf(self,
			"%s, NO LISTRIGHTS failure: can't set acl\r\n",
			self->tag);
		return 1;
	}
	// has the rights to 'administer' this mailbox? 
	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[self->args_idx], self->args[self->args_idx+1], listrights_string);
	dbmail_imap_session_printf(self, "%s OK LISTRIGHTS completed\r\n", self->tag);
	g_free(listrights_string);
	return 0;
}

int _ic_myrights(struct ImapSession *self)
{
	/* MYRIGHTS mailboxname */
	imap_userdata_t *ud = (imap_userdata_t *) self->ci->userData;
	int result;
	u64_t mboxid;
	char *myrights_string;

	if (!check_state_and_args(self, "LISTRIGHTS", 1, 1, IMAPCS_AUTHENTICATED))
		return 1;

	result = db_findmailbox(self->args[self->args_idx], ud->userid, &mboxid);
	if (result == -1) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	} else if (result == 0) {
		dbmail_imap_session_printf(self,
			"%s NO MYRIGHTS failure: unknown mailbox\r\n",
			self->tag);
		return 1;
	}

	if (!(myrights_string = acl_myrights(ud->userid, mboxid))) {
		dbmail_imap_session_printf(self, "* BYE internal database error\r\n");
		return -1;
	}

	dbmail_imap_session_printf(self, "* MYRIGHTS \"%s\" %s\r\n", self->args[self->args_idx],
		myrights_string);
	g_free(myrights_string);
	dbmail_imap_session_printf(self, "%s OK MYRIGHTS complete\r\n", self->tag);
	return 0;
}

int _ic_namespace(struct ImapSession *self)
{
	/* NAMESPACE command */
	if (!check_state_and_args(self, "NAMESPACE", 0, 0, IMAPCS_AUTHENTICATED))
		return 1;

	dbmail_imap_session_printf(self, "* NAMESPACE ((\"\" \"%s\")) ((\"%s\" \"%s\")) "
		"((\"%s\" \"%s\"))\r\n",
		MAILBOX_SEPARATOR, NAMESPACE_USER,
		MAILBOX_SEPARATOR, NAMESPACE_PUBLIC, MAILBOX_SEPARATOR);
	dbmail_imap_session_printf(self, "%s OK NAMESPACE complete\r\n", self->tag);
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1