/*
 Copyright (C) 1999-2004 IC & S  dbmail@ic-s.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: imap4.c 1741 2005-04-06 20:49:21Z paul $
 * imap4.c
 *
 * implements an IMAP 4 rev 1 server.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>

#include "imap4.h"
#include "imaputil.h"
#include "imapcommands.h"
#include "misc.h"
#include "clientinfo.h"
#include "debug.h"
#include "db.h"
#include "auth.h"



#define COMMAND_SHOW_LEVEL TRACE_INFO

#define null_free(p) { dm_free(p); p = NULL; }


/* cache */
cache_t cached_msg;

/* consts */
const char AcceptedChars[] =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    "!@#$%^&*()-=_+`~[]{}\\|'\" ;:,.<>/? \n\r";

const char AcceptedTagChars[] =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    "!@#$%^&-=_`~\\|'\" ;:,.<>/? ";

const char AcceptedMailboxnameChars[] =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    "-=/ _.&,+@()[]";

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",
	"***NOMORE***"
};


enum IMAP_COMMAND_TYPES { IMAP_COMM_NONE,
	IMAP_COMM_CAPABILITY, IMAP_COMM_NOOP, IMAP_COMM_LOGOUT,
	IMAP_COMM_AUTH, IMAP_COMM_LOGIN,
	IMAP_COMM_SELECT, IMAP_COMM_EXAMINE, IMAP_COMM_CREATE,
	IMAP_COMM_DELETE, IMAP_COMM_RENAME, IMAP_COMM_SUBSCRIBE,
	IMAP_COMM_UNSUBSCRIBE, IMAP_COMM_LIST, IMAP_COMM_LSUB,
	IMAP_COMM_STATUS, IMAP_COMM_APPEND,
	IMAP_COMM_CHECK, IMAP_COMM_CLOSE, IMAP_COMM_EXPUNGE,
	IMAP_COMM_SEARCH, IMAP_COMM_FETCH, IMAP_COMM_STORE,
	IMAP_COMM_COPY, IMAP_COMM_UID, IMAP_COMM_SORT,
	IMAP_COMM_GETQUOTAROOT, IMAP_COMM_GETQUOTA,
	IMAP_COMM_SETACL, IMAP_COMM_DELETEACL, IMAP_COMM_GETACL,
	IMAP_COMM_LISTRIGHTS, IMAP_COMM_MYRIGHTS,
	IMAP_COMM_NAMESPACE,
	IMAP_COMM_LAST
};


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,
	NULL
};



void ci_cleanup(ClientInfo *ci);
void ci_cleanup(ClientInfo *ci)
{
	close_cache();
	null_free(((imap_userdata_t*)ci->userData)->mailbox.seq_list);
	null_free(ci->userData);
}


/*
 * Main handling procedure
 *
 * returns EOF on logout/fatal error or 1 otherwise
 */
int IMAPClientHandler(ClientInfo * ci)
{
	char line[IMAP_MAX_LINESIZE];
	char *tag = NULL, *cpy, **args, *command;
	int done, result;
	size_t i;
	int nfaultyresponses;
	imap_userdata_t *ud = NULL;
	mailbox_t newmailbox;
	int this_was_noop = 0;

	/* init: add userdata */
	ci->userData = dm_malloc(sizeof(imap_userdata_t));
	if (!ci->userData) {
		/* out of mem */
		trace(TRACE_ERROR,
		      "IMAPClientHandler(): not enough memory.");
		return -1;
	}

	memset(ci->userData, 0, sizeof(imap_userdata_t));
	((imap_userdata_t *) ci->userData)->state =
	    IMAPCS_NON_AUTHENTICATED;
	ud = ci->userData;

	/* greet user */
	if (ci_write(ci->tx,
		     "* OK dbmail imap (protocol version 4r1) server %s "
		     "ready to run\r\n", IMAP_SERVER_VERSION)) {
		ci_cleanup(ci);
		return EOF;
	}
	fflush(ci->tx);

	/* init: cache */
	if (init_cache() != 0) {
		trace(TRACE_ERROR,
		      "IMAPClientHandler(): cannot open temporary file\n");
		if (ci_write(ci->tx, "* BYE internal system failure\r\n"))
			return -1;
		ci_cleanup(ci);
		return EOF;
	}

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

	do {
		if (nfaultyresponses >= MAX_FAULTY_RESPONSES) {
			/* we have had just about it with this user */
			sleep(2);	/* avoid DOS attacks */
			if (ci_write(ci->tx, "* BYE [TRY RFC]\r\n")) {
				ci_cleanup(ci);
				return EOF;
			}
			done = 1;
			break;
		}

		if (ferror(ci->rx)) {
			trace(TRACE_ERROR,
			      "IMAPClientHandler(): error [%s] on read-stream\n",
			      strerror(errno));
			if (errno == EPIPE) {
				ci_cleanup(ci);
				return -1;	/* broken pipe */
			} else
				clearerr(ci->rx);
		}

		if (ferror(ci->tx)) {
			trace(TRACE_ERROR,
			      "IMAPClientHandler(): error [%s] on write-stream\n",
			      strerror(errno));
			if (errno == EPIPE) {
				ci_cleanup(ci);
				return -1;	/* broken pipe */
			} else
				clearerr(ci->tx);
		}
		
		if (db_check_connection()) {
			trace(TRACE_ERROR,"%s,%s: database has gone away", __FILE__, __func__);
			ci_cleanup(ci);
			return -1;
		}

		alarm(ci->timeout);	/* install timeout handler */
		if (fgets(line, IMAP_MAX_LINESIZE, ci->rx) == NULL) {
			ci_cleanup(ci);
			return -1;
		}
		alarm(0);	/* remove timeout handler */

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

		trace(TRACE_DEBUG,
		      "IMAPClientHandler(): line read for PID %d\n",
		      getpid());

		if (!checkchars(line)) {
			/* foute tekens ingetikt */
			if (ci_write(ci->tx,
				     "* BYE Input contains invalid "
				     "characters\r\n")) {
				ci_cleanup(ci);
				return EOF;
			}
			ci_cleanup(ci);
			return 1;
		}

		/* clarify data a little */
		cpy = &line[strlen(line)];
		cpy--;
		while (cpy >= line && (*cpy == '\r' || *cpy == '\n')) {
			*cpy = '\0';
			cpy--;
		}

//      clarify_data(line);

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

		if (!(*line)) {
			
			if (ci_write(ci->tx, "* BAD No tag specified\r\n")) {
				ci_cleanup(ci);
				return EOF;
			}
			nfaultyresponses++;
			continue;
		}
		
		/* read tag & command */
		cpy = line;

		i = stridx(cpy, ' ');	/* find next space */
		if (i == strlen(cpy)) {
			if (checktag(cpy)) {
				if (ci_write(ci->tx,
					     "%s BAD No command specified\r\n",
					     cpy)) {
					ci_cleanup(ci);
					return EOF;
				}
			} else {
				if (ci_write(ci->tx,
					     "* BAD Invalid tag specified\r\n")) {
					ci_cleanup(ci);
					return EOF;
				}
			}
			nfaultyresponses++;
			continue;
			
		}
		
		tag = cpy;	/* set tag */
		cpy[i] = '\0';
		cpy = cpy + i + 1;	/* cpy points to command now */

		/* check tag */
		if (!checktag(tag)) {

			if (ci_write(ci->tx, 
				     "* BAD Invalid tag specified\r\n")) {
				ci_cleanup(ci);
				return EOF;
			}
			nfaultyresponses++;
			continue;
		}

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

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

		if (!args) {
			if (ci_write(ci->tx,
				     "%s BAD invalid argument specified\r\n",
				     tag)) {
				ci_cleanup(ci);
				return EOF;
			}
				
			nfaultyresponses++;
			continue;
		}

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

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

			/* free used memory */
			for (i = 0; args[i]; i++) {
				dm_free(args[i]);
				args[i] = NULL;
			}

			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;

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


// dirty hack to bypass a NOOP problem: 
// unilateral server responses are not recognised by some clients 
// if they are after the OK response
		this_was_noop = 0;

		if (i != IMAP_COMM_NOOP)
			result =
			    (*imap_handler_functions[i]) (tag, args, ci);
		else {
			this_was_noop = 1;
			result = 0;
		}

		//result = (*imap_handler_functions[i])(tag, args, ci);
		if (result == -1) {
			trace(TRACE_ERROR,"%s,%s: command return with error [%s]",
					__FILE__, __FUNCTION__, 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(ci->tx);	/* write! */

		trace(TRACE_INFO,
		      "IMAPClientHandler(): Finished command %s\n",
		      IMAP_COMMANDS[i]);

		/* check if mailbox status has changed (notify client) */
		if (ud->state == IMAPCS_SELECTED) {
			if (i == IMAP_COMM_NOOP ||
			    i == IMAP_COMM_CHECK ||
			    i == IMAP_COMM_SELECT ||
			    i == IMAP_COMM_EXPUNGE) {
				/* update mailbox info */
				memset(&newmailbox, 0, sizeof(newmailbox));
				newmailbox.uid = ud->mailbox.uid;

				result = db_getmailbox(&newmailbox);
				if (result == -1) {
					if (ci_write(ci->tx,
						     "* BYE internal dbase "
						     "error\r\n")) {
						ci_cleanup(ci);
						return EOF;
					}
					trace(TRACE_ERROR,
					      "IMAPClientHandler(): could not "
					      "get mailbox info\n");
					ci_cleanup(ci);
					for (i = 0; args[i]; i++) {
						dm_free(args[i]);
						args[i] = NULL;
					}

					return -1;
				}

				if (newmailbox.exists !=
				    ud->mailbox.exists) {
					if(ci_write(ci->tx, "* %u EXISTS\r\n",
						    newmailbox.exists)) {
						ci_cleanup(ci);
						return EOF;
					}
					trace(TRACE_INFO,
					      "IMAPClientHandler(): ok update "
					      "sent\r\n");
				}

				if (newmailbox.recent !=
				    ud->mailbox.recent)
					if(ci_write(ci->tx, "* %u RECENT\r\n",
						    newmailbox.recent)) {
						ci_cleanup(ci);
						return EOF;
					}

				dm_free(ud->mailbox.seq_list);
				memcpy((void *) &ud->mailbox, 
				       (void *)&newmailbox,
				       sizeof(newmailbox));
			}
		}
		if (this_was_noop) {
			if(ci_write(ci->tx, "%s OK NOOP completed\r\n", tag)) {
				ci_cleanup(ci);
				return EOF; 
			}
			trace(TRACE_DEBUG, "%s,%s: tag = %s", __FILE__, __func__,
			      tag);
		}
		for (i = 0; args[i]; i++) {
			dm_free(args[i]);
			args[i] = NULL;
		}

	} while (!done);

	/* cleanup */
	if (ci_write(ci->tx, "%s OK completed\r\n", tag)) {
		ci_cleanup(ci);
		return EOF;
	}
	ci_cleanup(ci);
	trace(TRACE_MESSAGE,
	      "IMAPClientHandler(): Closing connection for client from IP [%s]\n",
	      ci->ip);

	__debug_dumpallocs();

	return EOF;
}


syntax highlighted by Code2HTML, v. 0.9.1