/*
 *	pop3d		- IP/TCP/POP3 server for UNIX 4.3BSD
 *			  Post Office Protocol - Version 3 (RFC1081)
 *
 *	Katie Stevens
 *	dkstevens@ucdavis.edu
 * 	Computing Services
 *	University of California, Davis
 *
 **************************************
 *
 *	main.c
 *
 *	REVISIONS:
 *		02-27-90 [ks]	original implementation
 *	1.000	03-04-90 [ks]
 *	1.001	06-24-90 [ks]	implement optional TOP command
 */

#include <stdio.h>
#include <signal.h>
#include <ctype.h>
#include <sys/param.h>
#include "pop3.h"

#define VERSION		"1.001"
#define REVDATE		"June, 1990"

char *svr_hostname;				/* Hostname of POP3 server */
char svr_buf[SVR_BUFSIZ+2];			/* Buffer for server I/O */
char cli_user[CLI_BUFSIZ];			/* Save client username */

static char *svr_invalid = "-ERR Invalid command; valid commands:";

#if DEBUG
FILE *logfp = NULL;				/* File for recording session */
#endif

/* Routines in this file */
static void initialize();
static void svr_timeout();
static void int_hangup();
static void int_progerr();

static int svr_auth();
static int svr_pass();
static int svr_trans();
static int svr_fold();
static int svr_shutdown();

/**************************************************************************/

/* Initialize POP3 server */
static void
initialize()
{
	char buf[MAXHOSTNAMELEN+1];

#if DEBUG
	/* Prepare log file */
	logfp = fopen(LOGFILE,"w");
	fprintf(logfp,"POP3 server startup; version %s (%s)\n",
		VERSION,REVDATE);
#endif
	/* Get our hostname */
	gethostname(buf,MAXHOSTNAMELEN);
	svr_hostname = malloc(strlen(buf) + 1);
	if (svr_hostname == NULL)
		fail(FAIL_OUT_OF_MEMORY);
	strcpy(svr_hostname,buf);

	/* Handle process signals ourself */
	signal(SIGALRM, svr_timeout);		/* timer expiration */

	signal(SIGHUP, int_hangup);		/* socket signals */
	signal(SIGURG, int_hangup);
	signal(SIGTERM, int_hangup);

#ifdef SIGBUS
	signal(SIGBUS, int_progerr);		/* fatal program errors */
#endif
#ifdef SIGSEGV
	signal(SIGSEGV, int_progerr);
#endif
#ifdef SIGILL
	signal(SIGILL, int_progerr);
#endif
#ifdef SIGIOT
	signal(SIGIOT, int_progerr);
#endif

	return;
}

/* Timeout while waiting for next client command */
static void
svr_timeout()
{
	fld_release();				/* Release mailbox folder */
	fail(FAIL_LOST_CLIENT);			/* Exit POP3 server */
}
/* Timeout while waiting for next client command */
static void
int_hangup()
{
	fld_release();				/* Release mailbox folder */
	fail(FAIL_HANGUP);			/* Exit POP3 server */
}
/* Timeout while waiting for next client command */
static void
int_progerr()
{
	fld_release();				/* Release mailbox folder */
	fail(FAIL_PROGERR);			/* Exit POP3 server */
}

/**************************************************************************/

/* Server Authentification State; process client USER command */
static int
svr_auth(state,inbuf)
int state;
char *inbuf;
{
	if (strncmp(inbuf,"quit",4) == 0)
		return(svr_shutdown());
	/* Expecting USER command */
	if (strncmp(inbuf,"user",4) == 0) {
		inbuf += 4;
		EATSPACE(inbuf);
		strcpy(cli_user,inbuf);
		strcpy(svr_buf,"+OK please send PASS command\r\n");
		state = SVR_PASS_STATE;
	} else {
		sprintf(svr_buf,"%s  USER,  QUIT\r\n",svr_invalid);
	}
	return(state);
}

/* Server Password State; process client PASS command */
static int
svr_pass(state,inbuf)
int state;
char *inbuf;
{
	int cnt;

	if (strncmp(inbuf,"quit",4) == 0)
		return(svr_shutdown());
	/* Expecting PASS command */
	if (strncmp(inbuf,"pass",4) != 0) {
		sprintf(svr_buf,"%s  PASS,  QUIT\r\n",svr_invalid);
		return(state);
	}
	/* Verify usercode/password pair */
	inbuf += 4;
	EATSPACE(inbuf);
	if (verify_user(cli_user,inbuf) == -1) {
		strcpy(svr_buf,"-ERR invalid usercode or password, please try again\r\n");
		return(SVR_AUTH_STATE);
	}
	strcpy(svr_buf,DEF_MAIL_DIR);
	strcat(svr_buf,cli_user);
	return(fld_fromsp(svr_buf));
}

/* Server Transaction State; process client mailbox command */
static int
svr_trans(state,inbuf)
int state;
char *inbuf;
{
	int msgnum;

	if (strncmp(inbuf,"quit",4) == 0)
		return(svr_shutdown());
	/* Expecting mailbox command */
	if (strncmp(inbuf,"dele",4) == 0) {
		inbuf += 4;
		EATSPACE(inbuf);
		if (*inbuf == NULL_CHAR)
			sprintf(svr_buf,"-ERR message number required (e.g.  DELE 1)\r\n");
		else
			fld_delete(atoi(inbuf));
	} else if (strncmp(inbuf,"host",4) == 0) {
		inbuf += 4;
		EATSPACE(inbuf);
		if (*inbuf == NULL_CHAR)
			sprintf(svr_buf,"-ERR must specify hostname\r\n");
		else
			state = fld_bsmtp(inbuf);
	} else if (strncmp(inbuf,"last",4) == 0) {
		fld_last();
	} else if (strncmp(inbuf,"list",4) == 0) {
		inbuf += 4;
		EATSPACE(inbuf);
		if (*inbuf == NULL_CHAR)
			fld_list(-1);
		else
			fld_list(atoi(inbuf));
	} else if (strncmp(inbuf,"mbox",4) == 0) {
		inbuf += 4;
		EATSPACE(inbuf);
		if (*inbuf == NULL_CHAR)
			sprintf(svr_buf,"-ERR must specify mailbox filename\r\n");
		else
			state = fld_fromsp(inbuf);
	} else if (strncmp(inbuf,"noop",4) == 0) {
		strcpy(svr_buf,"+OK\r\n");
	} else if (strncmp(inbuf,"retr",4) == 0) {
		inbuf += 4;
		EATSPACE(inbuf);
		if (*inbuf == NULL_CHAR) {
			sprintf(svr_buf,"-ERR message number required (e.g.  RETR 1)\r\n");
		} else
			fld_retr(atoi(inbuf),-1);
	} else if (strncmp(inbuf,"rset",4) == 0) {
		fld_reset();
	} else if (strncmp(inbuf,"stat",4) == 0) {
		fld_stat();
	} else if (strncmp(inbuf,"top",3) == 0) {
		inbuf += 3;
		EATSPACE(inbuf);
		if (*inbuf == NULL_CHAR) {
			sprintf(svr_buf,"-ERR message number and line count required (e.g.  TOP 1 7)\r\n");
		} else {
			msgnum = atoi(inbuf);
			while (!isspace(*inbuf)) ++inbuf;
			EATSPACE(inbuf);
			if (*inbuf == NULL_CHAR)
				sprintf(svr_buf,"-ERR line count required (e.g.  TOP 1 7)\r\n");
			else
				fld_retr(msgnum,atoi(inbuf));
		}
	} else {
		sprintf(svr_buf,
			"%s  DELE, HOST, LAST, LIST, MBOX, NOOP, RETR, RSET, STAT, TOP  or  QUIT\r\n",
			svr_invalid);
	}
	return(state);
}

/* Server Folder State; need to open another folder */
static int
svr_fold(state,inbuf)
int state;
char *inbuf;
{
	if (strncmp(inbuf,"quit",4) == 0)
		return(svr_shutdown());
	if (strncmp(inbuf,"host",4) == 0) {
		inbuf += 4;
		EATSPACE(inbuf);
		state = fld_bsmtp(inbuf);
	} else if (strncmp(inbuf,"mbox",4) == 0) {
		inbuf += 4;
		EATSPACE(inbuf);
		state = fld_fromsp(inbuf);
	} else if (strncmp(inbuf,"noop",4) == 0) {
		strcpy(svr_buf,"+OK\r\n");
	} else {
		sprintf(svr_buf,"%s  HOST,  MBOX,  NOOP  or  QUIT\r\n",svr_invalid);
	}
	return(state);
}

/* Prepare to shutdown POP3 server */
static int
svr_shutdown()
{
	fld_release();
	sprintf(svr_buf,"+OK %s POP3 Server (Version %s) shutdown.\r\n",
		svr_hostname,VERSION);
	return(SVR_DONE_STATE);
}

/**************************************/

void
svr_data_out(buf)
char *buf;
{
	/* Send out response to client */
	alarm(SVR_TIMEOUT_SEND);
	fputs(buf,stdout);
	fflush(stdout);
	alarm(0);
	return;
}

/**************************************************************************/

main()
{
	int svr_state = SVR_LISTEN_STATE;	/* State of POP3 server */
	char cli_buf[CLI_BUFSIZ];		/* Buffer for client cmds */

	initialize();

	fprintf(stdout,"+OK %s POP3 Server (Version %s) ready.\r\n",
		svr_hostname,VERSION);
	fflush(stdout);
	svr_state = SVR_AUTH_STATE;
	for ( ; ; ) {
		/* Wait for command from client */
		alarm(SVR_TIMEOUT_CLI);
		if (fgetl(cli_buf,CLI_BUFSIZ,stdin) == NULL)
			break;
		alarm(0);

		/* Take action on client command */
		cmd_prepare(cli_buf);
#if DEBUG
		if ((cli_buf[0] == 'p')||(cli_buf[0] == 'P'))
			fprintf(logfp,"CLI: PASS\n",cli_buf);
		else
			fprintf(logfp,"CLI: %s\n",cli_buf);
#endif
		switch(svr_state) {
		case SVR_AUTH_STATE:	/* Expecting USER command */
			svr_state = svr_auth(svr_state,cli_buf);
			break;
		case SVR_PASS_STATE:	/* Expecting PASS command */
			svr_state = svr_pass(svr_state,cli_buf);
			break;
		case SVR_TRANS_STATE:	/* Expecting mailbox command */
			svr_state = svr_trans(svr_state,cli_buf);
			break;
		case SVR_FOLD_STATE:	/* Need to open another mailbox */
			svr_state = svr_fold(svr_state,cli_buf);
			break;
		default:
			fail(FAIL_CONFUSION);		/* Wont return */
			break;
		}
#if DEBUG
		fprintf(logfp,"SVR: %s",svr_buf);
#endif

		/* Send out response to client */
		alarm(SVR_TIMEOUT_SEND);
		fputs(svr_buf,stdout);
		fflush(stdout);
		alarm(0);
		if (ferror(stdout))
			break;

		/* Exit when server has sent goodbye */
		if (svr_state == SVR_DONE_STATE)
			break;
	}
	exit(0);
}
