/*
  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: pop3.c 1864 2005-08-23 08:19:09Z ilja $
 *
 * implementation for pop3 commands according to RFC 1081 */

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

#include "dbmail.h"
#include "pop3.h"
#include "db.h"
#include "debug.h"
#include "dbmailtypes.h"
#include "auth.h"
#include "clientinfo.h"
#include "pop3.h"
#ifdef PROC_TITLES
#include "proctitleutils.h"
#endif
#include "misc.h"

#define INCOMING_BUFFER_SIZE 512
#define APOP_STAMP_SIZE 255
#define MAX_USERID_SIZE 100

/* default timeout for server daemon */
#define DEFAULT_SERVER_TIMEOUT 300

/* max_errors defines the maximum number of allowed failures */
#define MAX_ERRORS 3

/* max_in_buffer defines the maximum number of bytes that are allowed to be
 * in the incoming buffer */
#define MAX_IN_BUFFER 255

extern int pop_before_smtp;

int pop3(void *stream, char *buffer, char *client_ip,
	 PopSession_t * session);

int pop3_error(PopSession_t * session, void *stream,
	       const char *formatstring, ...) PRINTF_ARGS(3, 4);

/* allowed pop3 commands */
const char *commands[] = {
	"quit",
	     /**< POP3_QUIT */
	"user",
	     /**< POP3_USER */
	"pass",
	     /**< POP3_PASS */
	"stat",
	     /**< POP3_STAT */
	"list",
	     /**< POP3_LIST */
	"retr",
	     /**< POP3_RETR */
	"dele",
	     /**< POP3_DELE */
	"noop",
	     /**< POP3_NOOP */
	"last",
	     /**< POP3_LAST */
	"rset",
	     /**< POP3_RSET */
	"uidl",
	     /**< POP3_UIDL */
	"apop",
	     /**< POP3_APOP */
	"auth",
	     /**< POP3_AUTH */
	"top",
	    /**< POP3_TOP */
	"capa"
	    /**< POP3_CAPA */
};

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


int pop3_handle_connection(clientinfo_t * ci)
{
	/*
	   Handles connection and calls
	   pop command handler
	 */

	int done = 1;		/* loop state */
	char *buffer = NULL;	/* connection buffer */
	char myhostname[64];
	int cnt;		/* counter */
	PopSession_t session;	/* current connection session */
	char unique_id[UID_SIZE];

	/* setting Session variables */
	session.error_count = 0;
	session.was_apop = 0;
	session.username = NULL;
	session.password = NULL;
	session.apop_stamp = NULL;
	session.SessionResult = 0;
	list_init(&session.messagelst);

	/* reset counters */
	session.totalsize = 0;
	session.virtual_totalsize = 0;
	session.totalmessages = 0;
	session.virtual_totalmessages = 0;

	/* getting hostname */
	gethostname(myhostname, 64);
	myhostname[63] = '\0';	/* make sure string is terminated */

	buffer = (char *) dm_malloc(INCOMING_BUFFER_SIZE * sizeof(char));

	if (!buffer) {
		trace(TRACE_MESSAGE,
		      "pop3_handle_connection(): Could not allocate buffer");
		return 0;
	}

	/* create an unique timestamp + processid for APOP authentication */
	session.apop_stamp =
	    (char *) dm_malloc(APOP_STAMP_SIZE * sizeof(char));
	if (!session.apop_stamp) {
		trace(TRACE_MESSAGE,
		      "pop3_handle_connection(): Could not allocate buffer for apop");
		dm_free(buffer);
		return 0;
	}
	/* create an unique timestamp + processid for APOP authentication */
	create_unique_id(unique_id, 0);
	snprintf(session.apop_stamp, APOP_STAMP_SIZE,
		 "<%s@%s>", unique_id, myhostname);

	if (ci->tx) {
		/* sending greeting */
		ci_write(ci->tx,
			"+OK DBMAIL pop3 server ready to rock %s\r\n",
			session.apop_stamp);
		fflush(ci->tx);
	} else {
		trace(TRACE_MESSAGE,
		      "pop3_handle_connection(): TX stream is null!");
		dm_free(session.apop_stamp);
		dm_free(buffer);
		return 0;
	}

	/* set authorization state */
	session.state = POP3_AUTHORIZATION_STATE;

	while (done > 0) {

		if (db_check_connection()) {
			trace(TRACE_ERROR,"%s,%s: database has gone away", __FILE__, __func__);
			done=-1;
			continue;
		}

		/* set the timeout counter */
		alarm(ci->timeout);

		/* clear the buffer */
		memset(buffer, 0, INCOMING_BUFFER_SIZE);

		for (cnt = 0; cnt < INCOMING_BUFFER_SIZE - 1; cnt++) {
			do {
				clearerr(ci->rx);
				fread(&buffer[cnt], 1, 1, ci->rx);

				/* leave, an alarm has occured during fread */
				if (!ci->rx) {
					dm_free(buffer);
					dm_free(session.apop_stamp);
					return 0;
				}

			} while (ferror(ci->rx) && errno == EINTR);

			if (buffer[cnt] == '\n' || feof(ci->rx)
			    || ferror(ci->rx)) {
				buffer[cnt + 1] = '\0';
				break;
			}
		}

		if (feof(ci->rx) || ferror(ci->rx))
			done = -1;	/* check client eof  */
		else {
			alarm(0);	/* reset function handle timeout */
			done = pop3(ci->tx, buffer, ci->ip, &session);	/* handle pop3 commands */
		}
		fflush(ci->tx);
	}

	/* we've reached the state */
	session.state = POP3_UPDATE_STATE;

	/* memory cleanup */
	dm_free(buffer);
	buffer = NULL;

	dm_free(session.apop_stamp);
	session.apop_stamp = NULL;

	if (session.username != NULL
	    && (session.was_apop || session.password != NULL)) {
		switch (session.SessionResult) {
		case 0:
			{
				trace(TRACE_MESSAGE,
				      "pop3_handle_connection(): user %s logging out"
				      " [messages=%llu, octets=%llu]",
				      session.username,
				      session.virtual_totalmessages,
				      session.virtual_totalsize);

				/* if everything went well, write down everything and do a cleanup */
				db_update_pop(&session);
				break;
			}

		case 1:
			trace(TRACE_ERROR,
			      "pop3_handle_connection(): EOF from client, "
			      " connection terminated");
			break;

		case 2:
			trace(TRACE_ERROR,
			      "pop3_handle_connection(): alert! possible flood attempt,"
			      " closing connection");
			break;

		case 3:
			trace(TRACE_ERROR,
			      "pop3_handle_connection(): authorization layer failure");
			break;
		case 4:
			trace(TRACE_ERROR,
			      "pop3_handle_connection(): storage layer failure");
			break;
		}
	} else
		trace(TRACE_ERROR,
		      "pop3_handle_connection(): error, uncomplete session");

	/* clean session. Removes message list etc */
	db_session_cleanup(&session);

	if (session.username != NULL) {
		/* username cleanup */
		dm_free(session.username);
		session.username = NULL;
	}

	if (session.password != NULL) {
		/* password cleanup */
		dm_free(session.password);
		session.password = NULL;
	}

	/* reset timers */
	alarm(0);
	//__debug_dumpallocs();

	return 0;
}

int pop3_error(PopSession_t * session, void *stream,
	       const char *formatstring, ...)
{
	va_list argp;
	char tmp[1024];

	if (session->error_count >= MAX_ERRORS) {
		trace(TRACE_MESSAGE,
		      "pop3_error(): too many errors (MAX_ERRORS is %d)",
		      MAX_ERRORS);
		ci_write((FILE *) stream,
			"-ERR loser, go play somewhere else\r\n");
		session->SessionResult = 2;	/* possible flood */
		return -3;
	} else {
		va_start(argp, formatstring);
		vsnprintf(tmp, sizeof(tmp), formatstring, argp);
		va_end(argp);
		ci_write((FILE *) stream, tmp);
	}

	trace(TRACE_DEBUG, "pop3_error(): an invalid command was issued");
	session->error_count++;
	return 1;
}

int pop3(void *stream, char *buffer, char *client_ip,
	 PopSession_t * session)
{
	/* returns a 0  on a quit
	 *           -1  on a failure
	 *                          1  on a success */
	char *command, *value;
	Pop3Cmd_t cmdtype;
	int found = 0;
	int indx = 0;
	u64_t result;
	int validate_result;
	u64_t top_lines, top_messageid;
	struct element *tmpelement;
	char *md5_apop_he;
	char *searchptr;
	u64_t user_idnr;

	/* buffer overflow attempt */
	if (strlen(buffer) > MAX_IN_BUFFER) {
		trace(TRACE_DEBUG, "pop3(): buffer overflow attempt");
		return -3;
	}

	/* check for command issued */
	while (strchr(validchars, buffer[indx]))
		indx++;

	/* end buffer */
	buffer[indx] = '\0';

	trace(TRACE_DEBUG, "pop3(): incoming buffer: [%s]", buffer);

	command = buffer;

	value = strstr(command, " ");	/* look for the separator */

	if (value != NULL) {
		*value = '\0';	/* set a \0 on the command end */
		value++;	/* skip space */

		if (strlen(value) == 0)
			value = NULL;	/* no value specified */
		else {
			trace(TRACE_DEBUG,
			      "pop3(): command issued :cmd [%s], value [%s]\n",
			      command, value);
		}
	}

	/* find command that was issued */
	for (cmdtype = POP3_QUIT; cmdtype <= POP3_CAPA; cmdtype++)
		if (strcasecmp(command, commands[cmdtype]) == 0) {
			session->was_apop = 1;
			break;
		}

	trace(TRACE_DEBUG, "pop3(): command looked up as commandtype %d",
	      cmdtype);

	/* commands that are allowed to have no arguments */
	if ((value == NULL) && (cmdtype != POP3_QUIT)
	    && (cmdtype != POP3_LIST) && (cmdtype != POP3_STAT)
	    && (cmdtype != POP3_RSET) && (cmdtype != POP3_NOOP)
	    && (cmdtype != POP3_LAST) && (cmdtype != POP3_UIDL)
	    && (cmdtype != POP3_AUTH) && (cmdtype != POP3_CAPA)) {
		return pop3_error(session, stream,
				  "-ERR your command does not compute\r\n");
	}

	switch (cmdtype) {
	case POP3_QUIT:
		{
			ci_write((FILE *) stream, "+OK see ya later\r\n");
			return 0;
		}
	case POP3_USER:
		{
			if (session->state != POP3_AUTHORIZATION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			if (session->username != NULL) {
				/* reset username */
				dm_free(session->username);
				session->username = NULL;
			}

			if (session->username == NULL) {
				if (strlen(value) > MAX_USERID_SIZE)
					return pop3_error(session, stream,
							  "-ERR userid is too long\r\n");

				/* create memspace for username */
				memtst((session->username =
					(char *) dm_malloc(strlen(value) +
							   1)) == NULL);
				strncpy(session->username, value,
					strlen(value) + 1);
			}

			ci_write((FILE *) stream,
				"+OK Password required for %s\r\n",
				session->username);
			return 1;
		}

	case POP3_PASS:
		{
			if (session->state != POP3_AUTHORIZATION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			if (session->password != NULL) {
				dm_free(session->password);
				session->password = NULL;
			}

			if (session->password == NULL) {
				/* create memspace for password */
				memtst((session->password =
					(char *) dm_malloc(strlen(value) +
							   1)) == NULL);
				strncpy(session->password, value,
					strlen(value) + 1);
			}

			/* check in authorization layer if these credentials are correct */
			validate_result = auth_validate(session->username,
							session->password,
							&result);
			switch (validate_result) {
			case -1:
				session->SessionResult = 3;
				return -1;
			case 0:
				{
					trace(TRACE_ERROR,
					      "pop3(): user [%s] tried to "
					      "login with wrong password",
					      session->username);

					/* clear username, must be re-entered according to RFC */
					dm_free(session->username);
					session->username = NULL;

					/* also, if the password is set, clear it */
					if (session->password != NULL) {
						dm_free(session->password);
						session->password = NULL;
					}
					return pop3_error(session, stream,
							  "-ERR username/password incorrect\r\n");
				}
			default:
				{
					/* user logged in OK */
					session->state =
					    POP3_TRANSACTION_STATE;

					/* now we're going to build up a session for this user */
					trace(TRACE_DEBUG,
					      "pop3(): validation OK, building a session for user [%s]",
					      session->username);

					/* if pop_before_smtp is active, log this ip */
					if (pop_before_smtp)
						db_log_ip(client_ip);

					result =
					    db_createsession(result,
							     session);
					if (result == 1) {
						ci_write((FILE *) stream,
							"+OK %s has %llu messages (%llu octets)\r\n",
							session->username,
							session->
							virtual_totalmessages,
							session->
							virtual_totalsize);
						trace(TRACE_MESSAGE,
						      "pop3(): user %s logged in [messages=%llu, octets=%llu]",
						      session->username,
						      session->
						      virtual_totalmessages,
						      session->
						      virtual_totalsize);
#ifdef PROC_TITLES
						/* sets the program ARGV's with username */
						set_proc_title
						    ("USER %s [%s]",
						     session->username,
						     client_ip);
#endif
					} else
						session->SessionResult = 4;	/* something went wrong on DB layer */
					return result;
				}
			}
			return 1;
		}

	case POP3_LIST:
		{
			if (session->state != POP3_TRANSACTION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			tmpelement = list_getstart(&session->messagelst);
			if (value != NULL) {
				/* they're asking for a specific message */
				while (tmpelement != NULL) {
					if (((struct message *)
					     tmpelement->data)->
					    messageid == strtoull(value,
								  NULL, 10)
					    && ((struct message *)
						tmpelement->data)->
					    virtual_messagestatus < MESSAGE_STATUS_DELETE) {
						ci_write((FILE *) stream,
							"+OK %llu %llu\r\n",
							((struct message *)
							 tmpelement->
							 data)->messageid,
							((struct message *)
							 tmpelement->
							 data)->msize);
						found = 1;
					}
					tmpelement = tmpelement->nextnode;
				}
				if (!found)
					return pop3_error(session, stream,
							  "-ERR no such message\r\n");
				else
					return 1;
			}

			/* just drop the list */
			ci_write((FILE *) stream,
				"+OK %llu messages (%llu octets)\r\n",
				session->virtual_totalmessages,
				session->virtual_totalsize);

			if (session->virtual_totalmessages > 0) {
				/* traversing list */
				while (tmpelement != NULL) {
					if (((struct message *)
					     tmpelement->data)->
					    virtual_messagestatus < MESSAGE_STATUS_DELETE)
						ci_write((FILE *) stream,
							"%llu %llu\r\n",
							((struct message *)
							 tmpelement->
							 data)->messageid,
							((struct message *)
							 tmpelement->
							 data)->msize);
					tmpelement = tmpelement->nextnode;
				}
			}
			ci_write((FILE *) stream, ".\r\n");
			return 1;
		}

	case POP3_STAT:
		{
			if (session->state != POP3_TRANSACTION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			ci_write((FILE *) stream, "+OK %llu %llu\r\n",
				session->virtual_totalmessages,
				session->virtual_totalsize);

			return 1;
		}

	case POP3_RETR:
		{
			trace(TRACE_DEBUG,
			      "pop3():RETR command, retrieving message");

			if (session->state != POP3_TRANSACTION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			tmpelement = list_getstart(&(session->messagelst));

			/* selecting a message */
			trace(TRACE_DEBUG,
			      "pop3(): RETR command, selecting message");
			while (tmpelement != NULL) {
				if (((struct message *) tmpelement->data)->messageid == strtoull(value, NULL, 10) && ((struct message *) tmpelement->data)->virtual_messagestatus < MESSAGE_STATUS_DELETE) {	/* message is not deleted */
					((struct message *) tmpelement->
					 data)->virtual_messagestatus = MESSAGE_STATUS_SEEN;
					ci_write((FILE *) stream,
						"+OK %llu octets\r\n",
						((struct message *)
						 tmpelement->data)->msize);
					return
					    db_send_message_lines((void *)
								  stream,
								  ((struct
								    message
								    *)
								   tmpelement->
								   data)->
								  realmessageid,
								  -2, 0);
				}
				tmpelement = tmpelement->nextnode;
			}
			return pop3_error(session, stream,
					  "-ERR no such message\r\n");
		}

	case POP3_DELE:
		{
			if (session->state != POP3_TRANSACTION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			tmpelement = list_getstart(&(session->messagelst));

			/* selecting a message */
			while (tmpelement != NULL) {
				if (((struct message *) tmpelement->data)->messageid == strtoull(value, NULL, 10) && ((struct message *) tmpelement->data)->virtual_messagestatus < MESSAGE_STATUS_DELETE) {	/* message is not deleted */
					((struct message *) tmpelement->
					 data)->virtual_messagestatus = MESSAGE_STATUS_DELETE;
					/* decrease our virtual list fields */
					session->virtual_totalsize -=
					    ((struct message *)
					     tmpelement->data)->msize;
					session->virtual_totalmessages -=
					    1;

					ci_write((FILE *) stream,
						"+OK message %llu deleted\r\n",
						((struct message *)
						 tmpelement->data)->
						messageid);
					return 1;
				}
				tmpelement = tmpelement->nextnode;
			}
			return pop3_error(session, stream,
					  "-ERR [%s] no such message\r\n",
					  value);
		}

	case POP3_RSET:
		{
			if (session->state != POP3_TRANSACTION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			tmpelement = list_getstart(&(session->messagelst));

			session->virtual_totalsize = session->totalsize;
			session->virtual_totalmessages =
			    session->totalmessages;

			while (tmpelement != NULL) {
				((struct message *) tmpelement->data)->
				    virtual_messagestatus =
				    ((struct message *) tmpelement->data)->
				    messagestatus;
				tmpelement = tmpelement->nextnode;
			}

			ci_write((FILE *) stream,
				"+OK %llu messages (%llu octets)\r\n",
				session->virtual_totalmessages,
				session->virtual_totalsize);

			return 1;
		}

	case POP3_LAST:
		{
			if (session->state != POP3_TRANSACTION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			tmpelement = list_getstart(&(session->messagelst));

			while (tmpelement != NULL) {
				if (((struct message *) tmpelement->data)->
				    virtual_messagestatus == MESSAGE_STATUS_NEW) {
					/* we need the last message that has been accessed */
					ci_write((FILE *) stream,
						"+OK %llu\r\n",
						((struct message *)
						 tmpelement->data)->
						messageid - 1);
					return 1;
				}
				tmpelement = tmpelement->nextnode;
			}

			/* all old messages */
			ci_write((FILE *) stream, "+OK %llu\r\n",
				session->virtual_totalmessages);

			return 1;
		}

	case POP3_NOOP:
		{
			if (session->state != POP3_TRANSACTION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			ci_write((FILE *) stream, "+OK\r\n");
			return 1;
		}

	case POP3_UIDL:
		{
			if (session->state != POP3_TRANSACTION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			tmpelement = list_getstart(&(session->messagelst));

			if (value != NULL) {
				/* they're asking for a specific message */
				while (tmpelement != NULL) {
					if (((struct message *)
					     tmpelement->data)->
					    messageid == strtoull(value,
								  NULL, 10)
					    && ((struct message *)
						tmpelement->data)->
					    virtual_messagestatus < MESSAGE_STATUS_DELETE) {
						ci_write((FILE *) stream,
							"+OK %llu %s\r\n",
							((struct message *)
							 tmpelement->
							 data)->messageid,
							((struct message *)
							 tmpelement->
							 data)->uidl);
						found = 1;
					}
					tmpelement = tmpelement->nextnode;
				}
				if (!found)
					return pop3_error(session, stream,
							  "-ERR no such message\r\n");
				else
					return 1;
			}

			/* just drop the list */
			ci_write((FILE *) stream,
				"+OK Some very unique numbers for you\r\n");

			if (session->virtual_totalmessages > 0) {
				/* traversing list */
				while (tmpelement != NULL) {
					if (((struct message *)
					     tmpelement->data)->
					    virtual_messagestatus < MESSAGE_STATUS_DELETE)
						ci_write((FILE *) stream,
							"%llu %s\r\n",
							((struct message *)
							 tmpelement->
							 data)->messageid,
							((struct message *)
							 tmpelement->
							 data)->uidl);

					tmpelement = tmpelement->nextnode;
				}
			}

			ci_write((FILE *) stream, ".\r\n");

			return 1;
		}

	case POP3_APOP:
		{
			if (session->state != POP3_AUTHORIZATION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			/* find out where the md5 hash starts */
			searchptr = strstr(value, " ");

			if (searchptr == NULL) 
				return pop3_error(session, stream,
						  "-ERR your command does not compute\r\n");

			/* skip the space */
			searchptr = searchptr + 1;

			/* value should now be the username */
			value[searchptr - value - 1] = '\0';

			if (strlen(searchptr) != 32)
				return pop3_error(session, stream,
						  "-ERR the thingy you issued is not a valid md5 hash\r\n");

			/* create memspace for md5 hash */
			memtst((md5_apop_he =
				(char *) dm_malloc(strlen(searchptr) +
						   1)) == NULL);
			strncpy(md5_apop_he, searchptr,
				strlen(searchptr) + 1);

			if (strlen(value) > MAX_USERID_SIZE)
				return pop3_error(session, stream,
						  "-ERR userid is too long\r\n");

			/* create memspace for username */
			memtst((session->username =
				(char *) dm_malloc(strlen(value) + 1)) ==
			       NULL);
			strncpy(session->username, value,
				strlen(value) + 1);

			/*
			 * check the encryption used for this user
			 * note that if the user does not exist it is not noted
			 * by db_getencryption()
			 */
			if (auth_user_exists(session->username, &user_idnr)
			    == -1) {
				trace(TRACE_ERROR,
				      "%s,%s: error finding if user exists. "
				      "username = [%s]", __FILE__,
				      __func__, session->username);
				return -1;
			}
			if (strcasecmp(auth_getencryption(user_idnr), "")
			    != 0) {
				/* it should be clear text */
				dm_free(md5_apop_he);
				dm_free(session->username);
				session->username = NULL;
				md5_apop_he = 0;
				return pop3_error(session, stream,
						  "-ERR APOP command is not supported for this user\r\n");
			}

			trace(TRACE_DEBUG,
			      "pop3(): APOP auth, username [%s], md5_hash [%s]",
			      session->username, md5_apop_he);

			result =
			    auth_md5_validate(session->username,
					      md5_apop_he,
					      session->apop_stamp);

			dm_free(md5_apop_he);
			md5_apop_he = 0;

			switch (result) {
			case -1:
				session->SessionResult = 3;
				return -1;
			case 0:
				trace(TRACE_ERROR,
				      "pop3(): user [%s] tried to login with wrong password",
				      session->username);

				dm_free(session->username);
				session->username = NULL;

				dm_free(session->password);
				session->password = NULL;

				return pop3_error(session, stream,
						  "-ERR authentication attempt is invalid\r\n");

			default:
				{
					/* user logged in OK */
					session->state =
					    POP3_TRANSACTION_STATE;

					/* user seems to be valid, let's build a session */
					trace(TRACE_DEBUG,
					      "pop3(): validation OK, building a session for user [%s]",
					      session->username);

					/* if pop_before_smtp is active, log this ip */
					if (pop_before_smtp)
						db_log_ip(client_ip);

					result =
					    db_createsession(result,
							     session);
					if (result == 1) {
						ci_write((FILE *) stream,
							"+OK %s has %llu messages (%llu octets)\r\n",
							session->username,
							session->
							virtual_totalmessages,
							session->
							virtual_totalsize);

						trace(TRACE_MESSAGE,
						      "pop3(): user %s logged in [messages=%llu, octets=%llu]",
						      session->username,
						      session->
						      virtual_totalmessages,
						      session->
						      virtual_totalsize);
#ifdef PROC_TITLES
						set_proc_title
						    ("USER %s [%s]",
						     session->username,
						     client_ip);
#endif
					} else
						session->SessionResult = 4;	/* storage layer error */

					return result;
				}
			}
			return 1;
		}

	case POP3_AUTH:
		{
			if (session->state != POP3_AUTHORIZATION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");
			return pop3_error(session, stream,
					"-ERR no AUTH mechanisms supported\r\n");
		}
	
	case POP3_TOP:
		{
			if (session->state != POP3_TRANSACTION_STATE)
				return pop3_error(session, stream,
						  "-ERR wrong command mode, sir\r\n");

			/* find out how many lines they want */
			searchptr = strstr(value, " ");

			/* insufficient parameters */
			if (searchptr == NULL)
				return pop3_error(session, stream,
						  "-ERR your command does not compute\r\n");

			/* skip the space */
			searchptr = searchptr + 1;

			/* value should now be the the message that needs to be retrieved */
			value[searchptr - value - 1] = '\0';

			/* check if searchptr or value are negative. If so return an
			   error. This is done by only letting the strings contain
			   digits (0-9) */
			if (strspn(searchptr, "0123456789") !=
			    strlen(searchptr))
				return pop3_error(session, stream,
						  "-ERR wrong parameter\r\n");
			if (strspn(value, "0123456789") != strlen(value))
				return pop3_error(session, stream,
						  "-ERR wrong parameter\r\n");

			top_lines = strtoull(searchptr, NULL, 10);
			top_messageid = strtoull(value, NULL, 10);
			if (top_messageid < 1)
				return pop3_error(session, stream,
						  "-ERR wrong parameter\r\n");

			trace(TRACE_DEBUG,
			      "pop3():TOP command (partially) retrieving message");

			tmpelement = list_getstart(&(session->messagelst));

			/* selecting a message */
			trace(TRACE_DEBUG,
			      "pop3(): TOP command, selecting message");

			while (tmpelement != NULL) {
				if (((struct message *) tmpelement->data)->messageid == top_messageid && ((struct message *) tmpelement->data)->virtual_messagestatus < MESSAGE_STATUS_DELETE) {	/* message is not deleted */
					ci_write((FILE *) stream,
						"+OK %llu lines of message %llu\r\n",
						top_lines, top_messageid);
					return
					    db_send_message_lines(stream,
								  ((struct
								    message
								    *)
								   tmpelement->
								   data)->
								  realmessageid,
								  top_lines,
								  0);
				}
				tmpelement = tmpelement->nextnode;
			}
			return pop3_error(session, stream,
					  "-ERR no such message\r\n");

			return 1;
		}
	case POP3_CAPA:
		{
			ci_write((FILE *) stream,
				"+OK, Capability list follows\r\n"
				"TOP\r\nUSER\r\nUIDL\r\n.\r\n");
			return 1;
		}

	default:
		{
			return pop3_error(session, stream,
					  "-ERR command not understood, sir\r\n");
		}
	}
	return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1