static const char rcsid[] = "@(#) $Header: pop3d.c,v 1.2 94/10/06 16:15:46 deyke Exp $";

/*
 *      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
 *
 **************************************
 *
 *      pop3d.c
 *
 *      REVISIONS:
 *              02-27-90 [ks]   original implementation
 *      1.000   03-04-90 [ks]
 *      1.001   06-24-90 [ks]   allow TRANS state if 0 msgs in folder
 *                              implement optional TOP command
 *
 *      Modified for use with WAMPES by Dieter Deyke, DK5SG/N0PRA
 *
 */

/*

March, 1990

This is the source code for a POP3 server running under 4.3BSD.
This server was written at the University of California at Davis.
The server implements the minimal POP3 command list, plus two
extension commands.

POP3, the Post Office Protocol (version 3), is described in RFC1081.
A copy of RFC1081 is included with this distribution.

At UC Davis, POP is used to deliver mail to microcomputers using
a 4.3BSD system as a mail gateway.  This is an addition to the normal
model of using the 4.3BSD system as the mailbox and letting the
microcomputer merely read the mail from there.  If you ever look
at the source code, you may see that this server supports two extra
commands, "HOST", to allow clients to specify hostnames for gateway
service, and "MBOX" to allow clients to specify an alternate mailbox
file.  Be assured that you can ignore these extra commands, since
the server is completely upwards compatible and standard POP3
client programs will not use this command.

If you discover bugs or problems with this software, please feel
free to contact me.

	Katie Stevens
	Compuing Services
	University of California
	Davis, CA 95616

	(916)752-3426
	dkstevens@ucdavis.edu

*/

#include <sys/types.h>

#include <stdio.h>

#include <ctype.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "configure.h"
#include "lockfile.h"

#define DEBUG                   0

#define SVR_LISTEN_STATE        0x00            /* Wait for client connection */
#define SVR_AUTH_STATE          0x01            /* Expecting USER command */
#define SVR_PASS_STATE          0x02            /* Expecting PASS command */
#define SVR_TRANS_STATE         0x03            /* Process mailbox commands */
#define SVR_FOLD_STATE          0x04            /* Need to open another mbox */
#define SVR_DONE_STATE          -1

#define SVR_TIMEOUT_CLI         3600            /* 60 minutes */
#define SVR_TIMEOUT_SEND        600             /* 10 minutes */
#define SVR_BUFSIZ              1024
#define CLI_BUFSIZ              128

#define DEF_MAIL_DIR            MAIL_DIR "/"
#define DEF_POP3_DIR            SPOOL_DIR "/pop/"
#define POP3_TMPFILE            "/tmp/pop3XXXXXX"
#define POP3_RCPT_HDR           "X-POP3-Rcpt:"

#define BSMTP_HELO_STATE        0x00            /* Expecting HELO command */
#define BSMTP_MAIL_STATE        0x01            /* Expecting MAIL command */
#define BSMTP_RCPT_STATE        0x02            /* Processing RCPT cmds */
#define BSMTP_DATA_STATE        0x03            /* Processing message */

struct fld_item {
	long fmsg_entry;                /* Index in file of start of msg */
	long bcount;                    /* #bytes this msg (for scan listing) */
	int status;                     /* Status of this message */
#define MSG_DELETED     0x01                    /* Msg marked for deletion */
	char *pop_hdr;                  /* Extra header for POP3 client */
};

#define FLD_ENTRY_BLOCK         16

#define get_e_array(a,m) {\
	a = (struct fld_item *)malloc(sizeof(struct fld_item)*( (m) + 1));\
}

#define chk_e_size(a,m,i) {\
	if ( ( (i) ) && (!( (i) % (m) )) ) {\
		a = (struct fld_item *)realloc( (a), (sizeof(struct fld_item)*( (i) + (m) + 1)));\
	}\
}

#define FAIL_CONFUSION          51              /* unknown error */
#define FAIL_FILE_ERROR         52              /* file read/write error */
#define FAIL_HANGUP             53              /* client hung up on us */
#define FAIL_LOST_CLIENT        54              /* timeout waiting for client */
#define FAIL_OUT_OF_MEMORY      55              /* out of system memory */
#define FAIL_PROGERR            56              /* unexpected program error */

#define NULL_CHAR       '\0'
#define LF_CHAR         '\n'
#define CR_CHAR         '\r'
#define DOT_CHAR        '.'
#define LANKLE_CHAR     '<'
#define RANKLE_CHAR     '>'

#define EATSPACE(s)     while (*s && isspace(*s & 0xff)) s++

static struct {
  char number[16];
  char date[16];
  char time[16];
  char author[16];
  char state[16];
} revision;

static FILE *fld_fp;                    /* Stream pointer for mailbox */
static char *cli_mbox;                  /* Filename of mailbox folder */
static char *svr_hostname;              /* Hostname of POP3 server */
static char cli_user[CLI_BUFSIZ];       /* Save client username */
static char flash_buf[SVR_BUFSIZ];
static char fld_fname[32];              /* Filename for working folder */
static char svr_buf[SVR_BUFSIZ+2];      /* Buffer for server I/O */
static int fld_highest = -1;            /* Max msg accessed by client */
static int fld_hostmbox;                /* 0=FromSP mailbox; 1=BSMTP */
static int fld_max = -1;                /* Actual # msgs in mailbox */
static int fld_write_ok = -1;           /* -1=client doesnt have write privs */
static struct fld_item *fld_msg;        /* Struct for mailbox stats */

static const char svr_invalid[] = "-ERR Invalid command; valid commands:";
static const char svr_nomsg[] = "-ERR no messages in mailbox\r\n";

#if DEBUG
#define LOGFILE         "/tmp/pop3svr.log"
static FILE *logfp;                     /* File for recording session */
#endif

#define isfromsp_start(buf)     (strncmp(buf,"From ",5) == 0)
#define isbsmtp_helo(buf)       (strncmp(buf,"helo",4) == 0)
#define isbsmtp_end(buf)        ((buf[0] == DOT_CHAR)&&(strlen(buf) == 2))

/* Routines in this file */

static int verify_user(const char *user, const char *pass);
static char *fgetl(char *buf, int n, FILE *fp);
static void cmd_prepare(char *buf);
static void fail(int err);
static int fld_bsmtp(char *hostname);
static int fld_fromsp(char *fname);
static void fld_delete(int msgnum);
static void fld_last(void);
static void fld_list(int msgnum);
static void fld_reset(void);
static void fld_retr(int msgnum, int linecnt);
static void fld_stat(void);
static int fld_select(char *mbox, int host_fld);
static void fld_release(void);
static void retr_bsmtp(int msgnum, int linecnt);
static void retr_fromsp(int msgnum, int linecnt);
static int msg_bsmtp(FILE *infp, FILE *outfp);
static int msg_fromsp(FILE *infp, FILE *outfp);
static char *bsmtp_rcpt(char *inbuf);
static void initialize(void);
static void svr_timeout(void);
static void int_hangup(void);
static void int_progerr(void);
static int svr_auth(int state, char *inbuf);
static int svr_pass(int state, char *inbuf);
static int svr_trans(int state, char *inbuf);
static int svr_fold(int state, char *inbuf);
static int svr_shutdown(void);
static void svr_data_out(char *buf);

/*---------------------------------------------------------------------------*/

#ifdef __386BSD__

#define crypt(key, salt) (key)

#else

char *crypt();

#endif

/* Verify a usercode/password */

static int verify_user(const char *user,const char *pass)
{
	struct passwd *pwd;
	char *cp;
	char salt[3];

	pwd = getpwnam(user);
	if (!pwd) return (-1);
	salt[0] = pwd->pw_passwd[0];
	salt[1] = pwd->pw_passwd[1];
	salt[2] = 0;
	cp = crypt((char *) pass,salt);
	if (strcmp(cp,pwd->pw_passwd))
		return(-1);
	setgid(pwd->pw_gid);
	initgroups((char *) user, pwd->pw_gid);
	setuid(pwd->pw_uid);
	chdir(pwd->pw_dir);
	umask(077);
	return 0;
}

/*---------------------------------------------------------------------------*/

/* Read a line of text from a stream. If more than n-1  */
/* characters are read without a line terminator (LF),  */
/* discard characters until line terminator is located. */

static char * fgetl(char *buf,int n,FILE *fp)
{
	if (fgets(buf,n,fp) == NULL)
		return(NULL);
	if ((strlen(buf) == (n-1))&&(buf[n-1] != LF_CHAR)) {
		buf[n-1] = LF_CHAR;
		while (fgets(flash_buf,SVR_BUFSIZ,fp) != NULL) {
			if (strlen(flash_buf) != (SVR_BUFSIZ-1))
				break;
			if (flash_buf[SVR_BUFSIZ-1] == LF_CHAR)
				break;
		}
	}
	return(buf);
}

/*---------------------------------------------------------------------------*/

/* Prepare client command for server */

static void cmd_prepare(char *buf)
{
	char *cp;

	if (buf == NULL)
		return;
	/* Convert command verb to lowercase */
	while (*buf != NULL_CHAR) {
		if (isupper(*buf))
			*buf = tolower(*buf);
		else if (isspace(*buf))
			break;
		++buf;
	}
	/* Strip trailing whitespace from client command */
	if ((cp = strchr(buf,CR_CHAR)) != NULL) {
		while ((cp != buf)&&(isspace(*cp))) --cp;
		if (!isspace(*cp)) ++cp;
		*cp = NULL_CHAR;
	}
	if ((cp = strchr(buf,LF_CHAR)) != NULL) {
		while ((cp != buf)&&(isspace(*cp))) --cp;
		if (!isspace(*cp)) ++cp;
		*cp = NULL_CHAR;
	}
	return;
}

/*---------------------------------------------------------------------------*/

/* Send an error message and exit POP3 server */

static void fail(int err)
{
	char *cp;

	switch(err) {
	case FAIL_FILE_ERROR:                   /* File I/O error */
		cp = "File I/O error";
		break;
	case FAIL_HANGUP:                       /* Client hung up on us */
		cp = "Lost connection to client";
		break;
	case FAIL_LOST_CLIENT:                  /* Timeout waiting for client */
		cp = "Timeout waiting for command from client";
		break;
	case FAIL_OUT_OF_MEMORY:                /* Failed malloc() */
		cp = "Out of memory!";
		break;
	case FAIL_PROGERR:                      /* Fatal program error */
		cp = "Fatal program error!";
		break;
	case FAIL_CONFUSION:                    /* Shouldnt happen */
	default:
		cp = "Complete confusion!";
		break;
	}
	fprintf(stdout,"-ERR POP3 Server Abnormal Shutdown: %s\r\n",cp);
	fflush(stdout);
#if DEBUG
	fprintf(logfp,"-ERR POP3 Server Abnormal Shutdown: %s\r\n",cp);
	fclose(logfp);
#endif
	exit(err);                              /* Exit with error */
}

/*---------------------------------------------------------------------------*/

/* Open a BSMTP wrapped mailbox */

static int fld_bsmtp(char *hostname)
{
	int cnt;

	/* Release previously opened mailbox */
	if (fld_fp != NULL)
		fld_release();
	/* Construct filename for new mailbox */
	cli_mbox = (char *) malloc(strlen(hostname)+strlen(DEF_POP3_DIR)+1);
	if (cli_mbox == NULL)
		fail(FAIL_OUT_OF_MEMORY);
	strcpy(cli_mbox,DEF_POP3_DIR);
	strcat(cli_mbox,hostname);
	/* Open mailbox */
	if ((cnt = fld_select(cli_mbox,1)) == -1) {
		sprintf(svr_buf,"-ERR cannot open mailbox %s\r\n",
			cli_mbox);
		free(cli_mbox);
		cli_mbox = NULL;
		return(SVR_FOLD_STATE);
	} else {
		sprintf(svr_buf,"+OK %d messages ready for %s in %s\r\n",
			cnt,cli_user,cli_mbox);
		return(SVR_TRANS_STATE);
	}
}

/*---------------------------------------------------------------------------*/

/* Open a FromSpace delimited mailbox */

static int fld_fromsp(char *fname)
{
	int cnt;

	/* Release previously opened mailbox */
	if (fld_fp != NULL)
		fld_release();
	/* Construct filename for new mailbox */
	cli_mbox = (char *) malloc(strlen(fname)+1);
	if (cli_mbox == NULL)
		fail(FAIL_OUT_OF_MEMORY);
	strcpy(cli_mbox,fname);
	/* Open mailbox */
	if ((cnt = fld_select(cli_mbox,0)) == -1) {
		sprintf(svr_buf,"-ERR cannot open mailbox %s\r\n",
			cli_mbox);
		free(cli_mbox);
		cli_mbox = NULL;
		return(SVR_FOLD_STATE);
	} else {
		sprintf(svr_buf,"+OK %d messages ready for %s in %s\r\n",
			cnt,cli_user,cli_mbox);
		return(SVR_TRANS_STATE);
	}
}

/*---------------------------------------------------------------------------*/

/* Mark a message for deletion */

static void fld_delete(int msgnum)
{
	if (fld_fp == NULL) {
		strcpy(svr_buf, svr_nomsg);
		return;
	}

	if ((msgnum < 1)||(msgnum > fld_max)) {
		sprintf(svr_buf,"-ERR invalid message; number out of range\r\n");
	} else {
		fld_msg[msgnum-1].status |= MSG_DELETED;
		sprintf(svr_buf,"+OK message %d marked for deletion\r\n",
			msgnum);
		if ((msgnum-1) > fld_highest)
			fld_highest =(msgnum-1);
	}
	return;
}

/*---------------------------------------------------------------------------*/

/* Report the highest access number for this mailbox */

static void fld_last(void)
{
	sprintf(svr_buf,"+OK %d\r\n",(fld_highest+1));
	return;
}

/*---------------------------------------------------------------------------*/

/* Give information about messages in mailbox folder */

static void fld_list(int msgnum)
{
	int i;

	if (fld_fp == NULL) {
		strcpy(svr_buf, svr_nomsg);
		return;
	}

	if (msgnum == -1) {
		sprintf(svr_buf,"+OK %d messages; msg# and size (in octets) for undeleted messages:\r\n",fld_max);
		svr_data_out(svr_buf);
		for (i=0; i<fld_max; ++i) {
			if ((fld_msg[i].status & MSG_DELETED) == 0) {
				sprintf(svr_buf,"%d %ld\r\n",
					(i+1),fld_msg[i].bcount);
				svr_data_out(svr_buf);
			}
		}
		sprintf(svr_buf,".\r\n");
	} else {
		if ((msgnum < 1)||(msgnum > fld_max))
			sprintf(svr_buf,"-ERR invalid message; number out of range\r\n");
		else if (fld_msg[msgnum-1].status & MSG_DELETED)
			sprintf(svr_buf,"-ERR message %d has been marked for deletion\r\n",
				msgnum);
		else
			sprintf(svr_buf,"+OK %d %ld\r\n",
				msgnum,fld_msg[msgnum-1].bcount);
	}
	return;
}

/*---------------------------------------------------------------------------*/

/* Reset deleted messages and highest access number */

static void fld_reset(void)
{
	int i;

	if (fld_fp == NULL) {
		strcpy(svr_buf, svr_nomsg);
		return;
	}
	/* Reset messages marked for deletion */
	for (i=0; i<fld_max; ++i) {
		fld_msg[i].status &= ~MSG_DELETED;
	}
	/* Reset highest access number for this mailbox */
	fld_highest = -1;
	sprintf(svr_buf,"+OK %d messages ready for %s in %s\r\n",
		fld_max,cli_user,cli_mbox);
	return;
}

/*---------------------------------------------------------------------------*/

/* Retrieve a message from mailbox */

static void fld_retr(int msgnum,int linecnt)
{
	if (fld_fp == NULL) {
		strcpy(svr_buf, svr_nomsg);
		return;
	}

	if ((msgnum < 1)||(msgnum > fld_max)) {
		sprintf(svr_buf,"-ERR invalid message; number out of range\r\n");
	} else if (fld_msg[msgnum-1].status & MSG_DELETED) {
		sprintf(svr_buf,"-ERR message %d has been marked for deletion\r\n",
			msgnum);
	} else {
		sprintf(svr_buf,"+OK message %d (%ld octets):\r\n",
			msgnum,fld_msg[msgnum-1].bcount);
		svr_data_out(svr_buf);
		if (fld_hostmbox == 0)
			retr_fromsp(--msgnum,linecnt);
		else
			retr_bsmtp(--msgnum,linecnt);
		sprintf(svr_buf,".\r\n");
		if ((linecnt != -1)&&(msgnum > fld_highest))
			fld_highest = msgnum;
	}
	return;
}

/*---------------------------------------------------------------------------*/

/* Give message count and total size (in octets) of a mailbox folder */

static void fld_stat(void)
{
	int i;
	long total_cnt = 0L;

	if (fld_fp == NULL) {
		strcpy(svr_buf, "+OK 0 0\r\n");
		return;
	}
	for (i=0; i<fld_max; ++i) {
		total_cnt += fld_msg[i].bcount;
	}
	sprintf(svr_buf,"+OK %d %ld\r\n",fld_max,total_cnt);
	return;
}

/*---------------------------------------------------------------------------*/

/* Attempt to load a mailbox folder */

static int fld_select(char *mbox,int host_fld)
{
	struct stat stat_buf;
	FILE *mboxfp;
	int lock;

	/* Reset folder variables */
	fld_hostmbox = host_fld;
	fld_fp = NULL;
	fld_write_ok = -1;
	fld_highest = -1;

	/* Make sure mailbox is present and non-zero size */
	if (stat(mbox,&stat_buf) == -1)
		return 0;
	if (stat_buf.st_size == 0L)
		return 0;

	/* Create/save mailbox names */
	strcpy(fld_fname, POP3_TMPFILE);
	if (mktemp(fld_fname) == NULL)
		return -1;

	/* Secure mailbox for POP3 session; copy to temporary file */
	if ((mboxfp = fopen(mbox, "r+")) == NULL)
		return -1;
	if ((lock = dup(fileno(mboxfp))) == -1) {
		fclose(mboxfp);
		return -1;
	}
	if (lock_fd(lock, 0) == -1) {
		fclose(mboxfp);
		close(lock);
		return -1;
	}
	if ((fld_fp = fopen(fld_fname,"w")) == NULL) {
		fclose(mboxfp);
		close(lock);
		return -1;
	}

	/* Load messages from folder */
	if (fld_hostmbox == 0)
		fld_max = msg_fromsp(mboxfp, fld_fp);
	else
		fld_max = msg_bsmtp(mboxfp, fld_fp);
	if (fclose(mboxfp) == EOF)
		fld_max = -1;
	if (fclose(fld_fp) == EOF)
		fld_max = -1;
	/* Zero out the mailbox so other processes can use it */
	/* while we are using the temporary copy just made.   */
	fld_write_ok = access(mbox,W_OK);
	if ((fld_max > 0) && (fld_write_ok != -1)) {
		if ((mboxfp = fopen(mbox,"w")) == NULL) {
			fld_max = -1;
		} else if (fclose(mboxfp) == EOF) {
			fld_max = -1;
		}
	}

	/* Unlock mailbox */
	close(lock);

	/* Prepare to use temporary file for POP3 session */
	if (fld_max > 0) {
		if ((fld_fp = fopen(fld_fname,"r")) == NULL) {
			unlink(fld_fname);
			fld_max = -1;
		}
	} else {
		/* Either zero messages or error */
		unlink(fld_fname);
		fld_fp = NULL;
	}
	return(fld_max);
}

/*---------------------------------------------------------------------------*/

/* Close a mailbox folder; remove messages marked for deletion */

static void fld_release(void)
{
	char temp_fname[32];
	FILE *mboxfp, *tempfp;
	int lock;
	int i = 0;
	int savemsg = 1;

	/* If no messages in folder, just free memory for filename */
	if (fld_fp == NULL) {
		if (cli_mbox != NULL) {
			free(cli_mbox);
			cli_mbox = NULL;
		}
		return;
	}

	/* If user doesnt have write permission for this */
	/* mailbox, just delete the working mailbox.     */
	if (fld_write_ok == -1)
		goto cleanup;

	/* If all messages were deleted, just remove */
	/* the working mailbox file.                 */
	for (i=0; i<fld_max; ++i) {
		if ((fld_msg[i].status & MSG_DELETED) == 0)
			break;
	}
	if (i == fld_max)
		goto cleanup;
	i = 0;

	/* Lock original mailbox folder again, save anything that  */
	/* has beeen added to the mailbox during this POP3 session */
	if ((mboxfp = fopen(cli_mbox,"r+")) == NULL)
		fail(FAIL_FILE_ERROR);
	if ((lock = dup(fileno(mboxfp))) == -1)
		fail(FAIL_FILE_ERROR);
	if (lock_fd(lock, 0) == -1)
		fail(FAIL_FILE_ERROR);
	strcpy(temp_fname, POP3_TMPFILE);
	if (mktemp(temp_fname) == NULL)
		fail(FAIL_FILE_ERROR);
	if ((tempfp = fopen(temp_fname,"w")) == NULL)
		fail(FAIL_FILE_ERROR);
	while (fgetl(svr_buf,SVR_BUFSIZ,mboxfp) != NULL) {
		fputs(svr_buf,tempfp);
		if (ferror(tempfp))
			fail(FAIL_FILE_ERROR);
	}
	if (ferror(mboxfp))
		fail(FAIL_FILE_ERROR);
	if (fclose(mboxfp) == EOF)
		fail(FAIL_FILE_ERROR);
	if (fclose(tempfp) == EOF)
		fail(FAIL_FILE_ERROR);

	/* Transfer contents of working folder to original */
	/* mailbox folder; dont copy deleted messages.     */
	if ((mboxfp = fopen(cli_mbox,"w")) == NULL)
		fail(FAIL_FILE_ERROR);
	rewind(fld_fp);
	if (fld_hostmbox != 0) {
		/* BSMTP: Transfer all text upto and including HELO */
		while (fgetl(svr_buf,SVR_BUFSIZ,fld_fp) != NULL) {
			fputs(svr_buf,mboxfp);
			if (ferror(mboxfp))
				fail(FAIL_FILE_ERROR);
			cmd_prepare(svr_buf);
			if (isbsmtp_helo(svr_buf))
				break;
		}
		/* Transfer first message, unless marked for deletion */
		savemsg = !(fld_msg[i++].status & MSG_DELETED);
	}
	while (fgetl(svr_buf,SVR_BUFSIZ,fld_fp) != NULL) {
		if ((fld_hostmbox == 0) && (isfromsp_start(svr_buf))) {
			/* FromSPACE delimited mailbox */
			/* Transfer next msg, unless deleted */
			savemsg = !(fld_msg[i++].status & MSG_DELETED);
		}
		if (savemsg) {
			fputs(svr_buf,mboxfp);
			if (ferror(mboxfp))
				fail(FAIL_FILE_ERROR);
		}
		if ((fld_hostmbox != 0) && (isbsmtp_end(svr_buf))) {
			/* BSMTP mailbox */
			/* Transfer next msg, unless deleted */
			savemsg = !(fld_msg[i++].status & MSG_DELETED);
		}
	}
	if (ferror(fld_fp))
		fail(FAIL_FILE_ERROR);

	/* Transfer contents of temp file (messages added */
	/* to original mailbox during this POP3 session)  */
	/* back to mailbox folder                         */
	if ((tempfp = fopen(temp_fname,"r")) == NULL)
		fail(FAIL_FILE_ERROR);
	while (fgets(svr_buf,SVR_BUFSIZ,tempfp) != NULL) {
		fputs(svr_buf,mboxfp);
		if (ferror(mboxfp))
			fail(FAIL_FILE_ERROR);
	}
	if (ferror(tempfp))
		fail(FAIL_FILE_ERROR);
	if (fclose(tempfp) == EOF)
		fail(FAIL_FILE_ERROR);
	unlink(temp_fname);
	if (fclose(mboxfp) == EOF)
		fail(FAIL_FILE_ERROR);

	/* Unlock original mailbox folder */
	close(lock);

cleanup:
	/* Close and remove working copy of mailbox folder */
	fclose(fld_fp);
	fld_fp = NULL;
	unlink(fld_fname);
	for (i=0; i<fld_max; ++i) {
		if (fld_msg[i].pop_hdr != NULL)
			free(fld_msg[i].pop_hdr);
	}
	free( (char *)fld_msg );
	free(cli_mbox);
	cli_mbox = NULL;
	return;
}

/*---------------------------------------------------------------------------*/

/* Send a BSMTP wrapped message to the POP3 client */

static void retr_bsmtp(int msgnum,int linecnt)
{
	char *tp;
	int msgbody = 0;

	/* Locate start of message in mailbox file */
	if (fseek(fld_fp, fld_msg[msgnum].fmsg_entry, 0) == -1)
		return;
	/* Display message text for the client */
	if (fld_msg[msgnum].pop_hdr != NULL)
		svr_data_out(fld_msg[msgnum].pop_hdr);
	while (fgetl(svr_buf,SVR_BUFSIZ,fld_fp) != NULL) {
		if (isbsmtp_end(svr_buf))
			break;
		/* Use CR-LF line terminator */
		tp = strchr(svr_buf,LF_CHAR);
		if (tp != NULL)
			strcpy(tp,"\r\n");
		svr_data_out(svr_buf);
		if ((msgbody)&&(--linecnt == 0)) {
			break;
		} else {
			if (*svr_buf == CR_CHAR)
				msgbody = 1;
		}
	}
	return;
}

/*---------------------------------------------------------------------------*/

/* Send a FromSP delimited message to the POP3 client */

static void retr_fromsp(int msgnum,int linecnt)
{
	char *cp, *tp;
	int msgbody = 0;

	/* Locate start of message in mailbox file */
	if (fseek(fld_fp, fld_msg[msgnum].fmsg_entry, 0) == -1)
		return;

	/* Setup for byte-stuff on lines that start with '.' */
	cp = svr_buf;
	*cp = DOT_CHAR;
	++cp;
	/* Display message for the client */
	if (fld_msg[msgnum].pop_hdr != NULL)
		svr_data_out(fld_msg[msgnum].pop_hdr);
	while (fgetl(cp,SVR_BUFSIZ,fld_fp) != NULL) {
		if (isfromsp_start(cp))
			break;
		/* Use CR-LF line terminator */
		tp = strchr(cp,LF_CHAR);
		if (tp != NULL)
			strcpy(tp,"\r\n");
		/* Byte-stuff lines that start with '.' */
		if (*cp == DOT_CHAR)
			svr_data_out(svr_buf);
		else
			svr_data_out(cp);
		if ((msgbody)&&(--linecnt == 0)) {
			break;
		} else {
			if (*cp == CR_CHAR)
				msgbody = 1;
		}
	}
	return;
}

/*---------------------------------------------------------------------------*/

/* Load messages from a mailbox wrapped in BSMTP format */
/* Assume BSMTP mailbox starts with a HELO command.     */
/* Assume BSMTP mailbox doesnt end with a QUIT command. */

static int msg_bsmtp(FILE *infp, FILE *outfp)
{
	register struct fld_item *mp;
	int i = 0;
	int mbox_state = BSMTP_HELO_STATE;

	/* Get an array for storing info about messages in folder */
	get_e_array(fld_msg, FLD_ENTRY_BLOCK);
	if (fld_msg == NULL)
		fail(FAIL_OUT_OF_MEMORY);
	mp = &fld_msg[0];
	mp->pop_hdr = NULL;
	/* Load messages from mailbox folder to temporary folder */
	while (fgetl(svr_buf,SVR_BUFSIZ,infp) != NULL) {
		fputs(svr_buf, outfp);
		if (ferror(outfp))
			return -1;
		switch(mbox_state) {
		case BSMTP_HELO_STATE:          /* Look for HELO command */
			cmd_prepare(svr_buf);
			if (strncmp(svr_buf,"helo",4) == 0)
				mbox_state = BSMTP_MAIL_STATE;
			break;
		case BSMTP_MAIL_STATE:          /* Process MAIL command */
			cmd_prepare(svr_buf);
			if (strncmp(svr_buf,"mail",4) == 0)
				mbox_state = BSMTP_RCPT_STATE;
			break;
		case BSMTP_RCPT_STATE:          /* Process RCPT command(s) */
			cmd_prepare(svr_buf);
			if (strncmp(svr_buf,"rcpt",4) == 0) {
				/* Save recipient for POP3 client */
				sprintf(flash_buf,"%s %s\r\n",
					POP3_RCPT_HDR,bsmtp_rcpt(svr_buf));
				if (mp->pop_hdr == NULL) {
					mp->bcount = 0L;
					mp->status = 0;
					mp->pop_hdr = (char *) malloc(strlen(flash_buf)+1);
					if (mp->pop_hdr == NULL)
						fail(FAIL_OUT_OF_MEMORY);
					strcpy(mp->pop_hdr,flash_buf);
				} else {
					mp->pop_hdr = (char *) realloc(mp->pop_hdr,
						(strlen(mp->pop_hdr)+strlen(flash_buf)+1));
					if (mp->pop_hdr == NULL)
						fail(FAIL_OUT_OF_MEMORY);
					strcat(mp->pop_hdr,flash_buf);
				}
				mp->bcount += (long)strlen(flash_buf);
			} else if (strncmp(svr_buf,"data",4) == 0) {
				mbox_state = BSMTP_DATA_STATE;
				/* Save entry point of message text */
				mp->fmsg_entry = ftell(outfp);
			}
			break;
		case BSMTP_DATA_STATE:          /* Reading mail message */
			if (isbsmtp_end(svr_buf)) {
				/* Prepare for next message in mailbox */
				mbox_state = BSMTP_MAIL_STATE;
				++i;
				/* Resize message array, if needed */
				chk_e_size(fld_msg, FLD_ENTRY_BLOCK, i);
				if (fld_msg == NULL)
					fail(FAIL_OUT_OF_MEMORY);
				mp = &fld_msg[i];
				mp->pop_hdr = NULL;
			} else {
				mp->bcount += ((long)strlen(svr_buf) + 1L);
				if (svr_buf[0] == DOT_CHAR)
					--mp->bcount;
			}
			break;
		default:                        /* Shouldnt happen */
			fail(FAIL_CONFUSION);
			break;
		}
	}
	if (ferror(infp))
		return -1;
	if (i == 0)
		free((char *)fld_msg);
	return(i);
}

/*---------------------------------------------------------------------------*/

/* Load messages from a mailbox delimited by FromSPACE */

static int msg_fromsp(FILE *infp, FILE *outfp)
{
	int i = 0;
	register struct fld_item *mp;

	/* Get an array for storing info about messages in folder */
	get_e_array(fld_msg, FLD_ENTRY_BLOCK);
	if (fld_msg == NULL)
		fail(FAIL_OUT_OF_MEMORY);
	mp = &fld_msg[0];
	/* Load messages from mailbox folder to temporary folder */
	while (fgetl(svr_buf,SVR_BUFSIZ,infp) != NULL) {
		fputs(svr_buf, outfp);
		if (ferror(outfp))
			return -1;
		if (isfromsp_start(svr_buf)) {
			/* Make sure there is room in array for this entry */
			chk_e_size(fld_msg, FLD_ENTRY_BLOCK, i);
			if (fld_msg == NULL)
				fail(FAIL_OUT_OF_MEMORY);
			/* Reset stats for this message */
			mp = &fld_msg[i];
			mp->fmsg_entry = ftell(outfp);
			mp->status = 0;
			sprintf(flash_buf,"%s %s@%s\r\n",POP3_RCPT_HDR,
					cli_user,svr_hostname);
			mp->pop_hdr = (char *) malloc(strlen(flash_buf)+1);
			if (mp->pop_hdr == NULL)
				fail(FAIL_OUT_OF_MEMORY);
			strcpy(mp->pop_hdr,flash_buf);
			mp->bcount = strlen(mp->pop_hdr);

			++i;
		} else {
			mp->bcount += ((long)strlen(svr_buf) + 1L);
		}
	}
	if (ferror(infp))
		return -1;
	if (i == 0)
		free((char *)fld_msg);
	return(i);
}

/*---------------------------------------------------------------------------*/

/* Isolate a recipient address in a BSMTP   RCPT TO:<addr>   command */

static char *bsmtp_rcpt(char *inbuf)
{
	char *cp;

	cp = strchr(inbuf,RANKLE_CHAR);
	if (cp == NULL)
		return("postmaster");
	*cp = NULL_CHAR;
	cp = strchr(inbuf,LANKLE_CHAR);
	if (cp == NULL)
		return("postmaster");
	return(cp+1);
}

/*---------------------------------------------------------------------------*/

/* Initialize POP3 server */

static void initialize(void)
{
	char buf[256];

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

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

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

	signal(SIGBUS, (void (*)(int)) int_progerr);    /* fatal program errors */
	signal(SIGSEGV, (void (*)(int)) int_progerr);
	signal(SIGILL, (void (*)(int)) int_progerr);
	signal(SIGIOT, (void (*)(int)) int_progerr);

	return;
}

/*---------------------------------------------------------------------------*/

/* Timeout while waiting for next client command */

static void svr_timeout(void)
{
	fld_release();                          /* Release mailbox folder */
	fail(FAIL_LOST_CLIENT);                 /* Exit POP3 server */
}

/*---------------------------------------------------------------------------*/

/* Timeout while waiting for next client command */

static void int_hangup(void)
{
	fld_release();                          /* Release mailbox folder */
	fail(FAIL_HANGUP);                      /* Exit POP3 server */
}

/*---------------------------------------------------------------------------*/

/* Timeout while waiting for next client command */

static void int_progerr(void)
{
	fld_release();                          /* Release mailbox folder */
	fail(FAIL_PROGERR);                     /* Exit POP3 server */
}

/*---------------------------------------------------------------------------*/

/* Server Authentification State; process client USER command */

static int svr_auth(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(int state,char *inbuf)
{
	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(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(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(void)
{
	fld_release();
	sprintf(svr_buf,"+OK %s POP3 Server (Version %s) shutdown.\r\n",
		svr_hostname,revision.number);
	return(SVR_DONE_STATE);
}

/*---------------------------------------------------------------------------*/

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

/*---------------------------------------------------------------------------*/

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

	sscanf((char *) rcsid, "%*s %*s %*s %s %s %s %s %s",
		revision.number,
		revision.date,
		revision.time,
		revision.author,
		revision.state);

	initialize();

	fprintf(stdout,"+OK %s POP3 Server (Version %s) ready.\r\n",
		svr_hostname,revision.number);
	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) {
			alarm(0);
			int_hangup();
		}
		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;
	}
	return 0;
}

