/*
 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.
*/

/* 
 * imap4.c
 *
 * implements an IMAP 4 rev 1 server.
 */

#include "dbmail.h"

#define COMMAND_SHOW_LEVEL TRACE_INFO

#define THIS_MODULE "imap"

const char *IMAP_COMMANDS[] = {
	"", "capability", "noop", "logout",
	"authenticate", "login",
	"select", "examine", "create", "delete", "rename", "subscribe",
	"unsubscribe",
	"list", "lsub", "status", "append",
	"check", "close", "expunge", "search", "fetch", "store", "copy",
	"uid", "sort", "getquotaroot", "getquota",
	"setacl", "deleteacl", "getacl", "listrights", "myrights",
	"namespace","thread","unselect","idle",
	"***NOMORE***"
};



const IMAP_COMMAND_HANDLER imap_handler_functions[] = {
	NULL,
	_ic_capability, _ic_noop, _ic_logout,
	_ic_authenticate, _ic_login,
	_ic_select, _ic_examine, _ic_create, _ic_delete, _ic_rename,
	_ic_subscribe, _ic_unsubscribe, _ic_list, _ic_lsub, _ic_status,
	_ic_append,
	_ic_check, _ic_close, _ic_expunge, _ic_search, _ic_fetch,
	_ic_store, _ic_copy, _ic_uid, _ic_sort,
	_ic_getquotaroot, _ic_getquota,
	_ic_setacl, _ic_deleteacl, _ic_getacl, _ic_listrights,
	_ic_myrights,
	_ic_namespace, _ic_thread, _ic_unselect, _ic_idle,
	NULL
};


imap_userdata_t * dbmail_imap_userdata_new(void)
{
	imap_userdata_t *ud;
	ud = g_new0(imap_userdata_t,1);
	ud->state = IMAPCS_NON_AUTHENTICATED;
	return ud;
}

/*
 * Main handling procedure
 *
 * returns EOF on logout/fatal error or 1 otherwise
 */
int IMAPClientHandler(clientinfo_t * ci)
{
	char line[MAX_LINESIZE], *tag = NULL, *cpy, **args, *command;
	int done, result, readresult, nfaultyresponses, serr;
	size_t i;
	imap_userdata_t *ud = NULL;
	struct ImapSession *session;

	session = dbmail_imap_session_new();
	session->timeout = ci->login_timeout;
	dbmail_imap_session_setClientinfo(session,ci);

	if (! (ud = dbmail_imap_userdata_new()))
		return -1;
	
	session->ci->userData = ud;

	/* greet user */
	field_t banner;
	GETCONFIGVALUE("banner", "IMAP", banner);
	if (strlen(banner) > 0) {
		if (dbmail_imap_session_printf(session,
			     "* OK %s\r\n", banner) < 0) {
			dbmail_imap_session_delete(session);
			return EOF;
		}
	} else {
		if (dbmail_imap_session_printf(session,
			     "* OK dbmail imap (protocol version 4r1) server %s "
			     "ready to run\r\n", IMAP_SERVER_VERSION) < 0) {
			dbmail_imap_session_delete(session);
			return EOF;
		}
	}
	fflush(session->ci->tx);

	done = 0;
	args = NULL;
	nfaultyresponses = 0;

	do {
		if (db_check_connection()) {
			TRACE(TRACE_DEBUG,"database has gone away");
			done=1;
			break;
		}
		
		if (nfaultyresponses >= MAX_FAULTY_RESPONSES) {
			/* we have had just about it with this user */
			sleep(2);	/* avoid DOS attacks */
			if (dbmail_imap_session_printf(session, "* BYE [TRY RFC]\r\n") < 0) {
				dbmail_imap_session_delete(session);
				return EOF;
			}
			done = 1;
			break;
		}

		if (ferror(session->ci->rx)) {
			if (errno) {
				serr = errno;
				TRACE(TRACE_ERROR, "[%s] on read-stream\n", strerror(serr));
				if (serr == EPIPE) {
					dbmail_imap_session_delete(session);
					return -1;	/* broken pipe */
				} 
			}
			clearerr(session->ci->rx);
		}

		if (ferror(session->ci->tx)) {
			if (errno) {
				serr = errno;
				TRACE(TRACE_ERROR, "[%s] on write-stream\n", strerror(serr));
				if (serr == EPIPE) {
					dbmail_imap_session_delete(session);
					return -1;	/* broken pipe */
				}
			}
			clearerr(session->ci->tx);
		}

		readresult = dbmail_imap_session_readln(session, line);
		if (readresult < 0) { /* Fatal error: EOF &c. */
			dbmail_imap_session_delete(session);
			return -1;
		}

		/* There is an error possible (line too long, readresult == 0)
		 * that we need to note, but carry through with processing the
		 * command in order to generate the proper tag on the
		 * BAD response */

		if (!session->ci->rx || !session->ci->tx) {
			/* if a timeout occured the streams will be closed & set to NULL */
			TRACE(TRACE_ERROR, "timeout occurred.");
			dbmail_imap_session_delete(session);
			return 1;
		}

		/* strip eol chars */
		cpy = &line[strlen(line)];
		cpy--;

		while (cpy >= line && (*cpy == '\r' || *cpy == '\n')) {
			*cpy = '\0';
			cpy--;
		}

		TRACE(COMMAND_SHOW_LEVEL, "COMMAND: [%s]\n", line);

		if (!(*line)) {
			if (dbmail_imap_session_printf(session, "* BAD No tag specified\r\n") < 0) {
				dbmail_imap_session_delete(session);
				return EOF;
			}
			nfaultyresponses++;
			continue;
		}
		
		/* read tag & command */
		cpy = line;

		i = stridx(cpy, ' ');	/* find next space */
		if (i == strlen(cpy)) {
			if (checktag(cpy)) {
				if (dbmail_imap_session_printf(session, "%s BAD No command specified\r\n", cpy) < 0) {
					dbmail_imap_session_delete(session);
					return EOF;
				}
			} else {
				if (dbmail_imap_session_printf(session, "* BAD Invalid tag specified\r\n") < 0) {
					dbmail_imap_session_delete(session);
					return EOF;
				}
			}
			nfaultyresponses++;
			continue;
		}
		
		tag = g_strdup(cpy);	/* set tag */
		tag[i] = '\0';
		dbmail_imap_session_setTag(session,tag);
		g_free(tag);
		
		cpy[i] = '\0';
		cpy = cpy + i + 1;	/* cpy points to command now */

		/* check tag */
		if (!checktag(session->tag)) {
			if (dbmail_imap_session_printf(session, "* BAD Invalid tag specified\r\n") < 0) {
				dbmail_imap_session_delete(session);
				return EOF;
			}
			nfaultyresponses++;
			continue;
		}

		if (readresult == 0) { /* Nonfatal error: too long line &c. */
			if (dbmail_imap_session_printf(session, "%s BAD Line too long\r\n",session->tag) < 0) {
				dbmail_imap_session_delete(session);
				return EOF;
			}
			nfaultyresponses++;
			continue;
		}

		i = stridx(cpy, ' ');	/* find next space */
		
		command = strdup(cpy);	/* set command */
		if (i < strlen(command))
			command[i]='\0';
		dbmail_imap_session_setCommand(session,command);
		g_free(command);
		
		if (i == strlen(cpy)) {
			/* no arguments present */
			args = build_args_array_ext(session, "");
		} else {
			cpy[i] = '\0';	/* terminated command */
			cpy = cpy + i + 1;	/* cpy points to args now */
			args = build_args_array_ext(session, cpy);	/* build argument array */

			if (!session->ci->rx || !session->ci->tx || ferror(session->ci->rx) || ferror(session->ci->tx)) {
				/* some error occurred during the read of extra command info */
				TRACE(TRACE_ERROR, "error reading extra command info");
				dbmail_imap_session_delete(session);
				return -1;
			}
		}

		if (!args) {
			if (dbmail_imap_session_printf(session, "%s BAD invalid argument specified\r\n",session->tag) < 0) {
				dbmail_imap_session_delete(session);
				return EOF;
			}
				
			nfaultyresponses++;
			continue;
		}

		for (i = IMAP_COMM_NONE; i < IMAP_COMM_LAST && strcasecmp(session->command, IMAP_COMMANDS[i]); i++);

		if (i <= IMAP_COMM_NONE || i >= IMAP_COMM_LAST) {
			/* unknown command */
			if (dbmail_imap_session_printf(session, "%s BAD command not recognized\r\n",session->tag)) {
				dbmail_imap_session_delete(session);
				return EOF;
			}
			nfaultyresponses++;
			continue;
		}

		/* reset the faulty responses counter. This is quick fix which
		 * is useful for programs that depend on sending faulty
		 * commands to the server, and checking the response. 
		 * (IB: 2004-08-23) */
		nfaultyresponses = 0;
		session->command_type = i;

		TRACE(TRACE_INFO, "Executing command %s...\n", IMAP_COMMANDS[i]);

		result = (*imap_handler_functions[i]) (session);

		if (result == -1) {
			TRACE(TRACE_ERROR,"command return with error [%s]", IMAP_COMMANDS[i]);
			done = 1;	/* fatal error occurred, kick this user */
		}

		if (result == 1)
			nfaultyresponses++;	/* server returned BAD or NO response */

		if (result == 0 && i == IMAP_COMM_LOGOUT)
			done = 1;

		fflush(session->ci->tx);	/* write! */

		dbmail_imap_session_args_free(session, FALSE);

		TRACE(TRACE_INFO, "Finished command %s [%d]\n", IMAP_COMMANDS[i], result);

	} while (!done);

	/* cleanup */
	dbmail_imap_session_printf(session, "%s OK completed\r\n", session->tag);
	TRACE(TRACE_MESSAGE, "Closing connection for client from IP [%s]\n", session->ci->ip_src);
	dbmail_imap_session_delete(session);

	return EOF;
}


syntax highlighted by Code2HTML, v. 0.9.1