/*
  Copyright (C) 1999-2004 IC & S  dbmail@ic-s.nl
  Copyright (c) 2005-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.
*/

/* 
 *
 * implementation for pop3 commands according to RFC 1081 */


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

#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;
extern volatile sig_atomic_t alarm_occured;

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 */
};

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 */
	char unique_id[UID_SIZE];
	PopSession_t session;	/* current connection session */

	/* setting Session variables */
	memset(&session,0,sizeof(session));
	
	/* clear the message list */
	dm_list_init(&session.messagelst);

	/* getting hostname */
	gethostname(myhostname, 64);
	myhostname[63] = '\0';	/* make sure string is terminated */
	
	
	/* create an unique timestamp + processid for APOP authentication */
	session.apop_stamp = g_new0(char,APOP_STAMP_SIZE);
	create_unique_id(unique_id, 0);
	snprintf(session.apop_stamp, APOP_STAMP_SIZE, "<%s@%s>", unique_id, myhostname);

	if (ci->tx) {
		/* sending greeting */
		field_t banner;
		GETCONFIGVALUE("banner", "POP", banner);
		if (strlen(banner) > 0) {
			ci_write(ci->tx, "+OK %s %s\r\n",
				banner, session.apop_stamp);
		} else {
			ci_write(ci->tx, "+OK DBMAIL pop3 server ready to rock %s\r\n",
				session.apop_stamp);
		}
		fflush(ci->tx);
	} else {
		TRACE(TRACE_MESSAGE, "TX stream is null!");
		g_free(session.apop_stamp);
		return 0;
	}

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

	/* setup the read buffer */
	buffer = g_new0(char,INCOMING_BUFFER_SIZE);

	/* lets start handling commands */
	while (done > 0) {

		if (db_check_connection())
			break;

		/* set the timeout counter */
		if (session.state == POP3_AUTHORIZATION_STATE)
			alarm(ci->login_timeout);
		else
			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);
				
				if (alarm_occured) {
					alarm_occured = 0;
					done = -2;
					session.SessionResult = 1;
					break;
				}

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

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

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

	/* We're done with this client. The rest is cleanup */
	
	session.state = POP3_UPDATE_STATE;

	/* memory cleanup */
	g_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, "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 */
			if (db_update_pop(&session) == DM_SUCCESS)
				ci_write(ci->tx, "+OK see ya later\r\n");
			else
				ci_write(ci->tx, "-ERR some deleted messages not removed\r\n");

			fflush(ci->tx);
			break;

		case 1:
			ci_write(ci->tx, "-ERR I'm leaving, you're too slow\r\n");
			TRACE(TRACE_ERROR, "client timed out, connection closed");
			break;

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

		case 3:
			TRACE(TRACE_ERROR, "authorization layer failure");
			break;
		case 4:
			TRACE(TRACE_ERROR, "storage layer failure");
			break;
		}
	} else if (done==0) { // QUIT issued before AUTH phase completed
		ci_write(ci->tx, "+OK see ya later\r\n");
		fflush(ci->tx);
	} else if (done==-2) {
		ci_write(ci->tx, "-ERR I'm leaving, you're too slow\r\n");
		TRACE(TRACE_ERROR, "client timed out before AUTH, connection closed");
	} else {
		TRACE(TRACE_ERROR, "error, incomplete session");
	}
	
	/* remove session info like messagelist etc. */
	db_session_cleanup(&session);

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

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

	/* reset timers */
	alarm(0);

	return 0;
}

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

	if (session->error_count >= MAX_ERRORS) {
		TRACE(TRACE_MESSAGE, "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);
		vfprintf((FILE *) stream, formatstring, argp);
		va_end(argp);
	}

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

int pop3(clientinfo_t *ci, char *buffer, 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;
	unsigned char *md5_apop_he;
	char *searchptr;
	u64_t user_idnr;
	struct message *msg;

	char *client_ip = ci->ip_src;
	FILE *stream = ci->tx;

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

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

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

	TRACE(TRACE_DEBUG, "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, "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, "command looked up as commandtype %d", 
			cmdtype);

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

	switch (cmdtype) {
		
	case POP3_QUIT:
		/* We return 0 here, and then pop3_handle_connection cleans up
		 * the connection, commits all changes, and sends the final
		 * "OK" message indicating that QUIT has completed. */
		return 0;
		
	case POP3_USER:
		if (session->state != POP3_AUTHORIZATION_STATE)
			return pop3_error(session, stream, "-ERR wrong command mode\r\n");

		if (session->username != NULL) {
			/* reset username */
			g_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 */
			session->username = g_new0(char,strlen(value) + 1);
			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\r\n");

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

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

		/* check in authorization layer if these credentials are correct */
		validate_result = auth_validate(ci, session->username, session->password, &result);

		switch (validate_result) {
		case -1:
			session->SessionResult = 3;
			return -1;
		case 0:
			TRACE(TRACE_ERROR, "user [%s] coming from [%s] tried to login with wrong password", 
				session->username, ci->ip_src);

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

			g_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, "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);

			child_reg_connected_user(session->username);

			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, "user %s logged in [messages=%llu, octets=%llu]", 
						session->username, 
						session->virtual_totalmessages, 
						session->virtual_totalsize);
			} else
				session->SessionResult = 4;	/* Database error. */

			return result;
		}
		return 1;

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

		tmpelement = dm_list_getstart(&session->messagelst);
		if (value != NULL) {
			/* they're asking for a specific message */
			while (tmpelement != NULL) {
				msg = (struct message *)tmpelement->data;
				if (msg->messageid == strtoull(value,NULL, 10) && msg->virtual_messagestatus < MESSAGE_STATUS_DELETE) {
					ci_write((FILE *) stream, "+OK %llu %llu\r\n", msg->messageid,msg->msize);
					found = 1;
				}
				tmpelement = tmpelement->nextnode;
			}
			if (!found)
				return pop3_error(session, stream, "-ERR [%s] no such message\r\n", value);
			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) {
				msg = (struct message *)tmpelement->data;
				if (msg->virtual_messagestatus < MESSAGE_STATUS_DELETE)
					ci_write((FILE *) stream, "%llu %llu\r\n", msg->messageid,msg->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\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, "RETR command, retrieving message");

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

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

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

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

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

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

				ci_write((FILE *) stream, "+OK message %llu deleted\r\n", msg->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\r\n");

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

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

		while (tmpelement != NULL) {
			msg = (struct message *) tmpelement->data;
			msg->virtual_messagestatus = msg->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\r\n");

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

		while (tmpelement != NULL) {
			msg = (struct message *) tmpelement->data;
			if (msg->virtual_messagestatus == MESSAGE_STATUS_NEW) {
				/* we need the last message that has been accessed */
				ci_write((FILE *) stream, "+OK %llu\r\n", msg->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\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\r\n");

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

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

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

		md5_apop_he = g_new0(unsigned char,strlen(searchptr) + 1);
		session->username = g_new0(char,strlen(value) + 1);

		strncpy((char *)md5_apop_he, searchptr, strlen(searchptr) + 1);
		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, "error finding if user exists. username = [%s]", 
					session->username);
			return -1;
		}
		if (strcasecmp(auth_getencryption(user_idnr), "") != 0) {
			/* it should be clear text */
			g_free(md5_apop_he);
			g_free(session->username);
			session->username = NULL;
			md5_apop_he = NULL;
			return pop3_error(session, stream, "-ERR APOP command is not supported for this user\r\n");
		}

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

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

		g_free(md5_apop_he);
		md5_apop_he = 0;

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

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

			g_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, "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);

			child_reg_connected_user(session->username);

			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, "user %s logged in [messages=%llu, octets=%llu]", 
						session->username, 
						session->virtual_totalmessages, 
						session->virtual_totalsize);
			} else
				session->SessionResult = 4;	/* Database error. */

			return result;
		}
		return 1;

	case POP3_AUTH:
		if (session->state != POP3_AUTHORIZATION_STATE)
			return pop3_error(session, stream, "-ERR wrong command mode\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\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, "TOP command (partially) retrieving message");

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

		/* selecting a message */
		TRACE(TRACE_DEBUG, "TOP command, selecting message");

		while (tmpelement != NULL) {
			msg = (struct message *) tmpelement->data;
			if (msg->messageid == top_messageid && msg->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, msg->realmessageid, top_lines, 0);
			}
			tmpelement = tmpelement->nextnode;
		}
		return pop3_error(session, stream, "-ERR no such message\r\n");

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

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


syntax highlighted by Code2HTML, v. 0.9.1