/* POP2 Server state machine - see RFC 937
 *
 *  also see other credits in popcli.c
 *  10/89 Mike Stockett wa7dyx
 *  Modified 5/27/90 by Allen Gwinn, N5CKP, for later NOS releases.
 *  Added to NOS by PA0GRI 2/6/90 (and linted into "standard" C)
 *
 *	Aug-Oct 92	Mike Bilow, N1BEE, mikebw@ids.net
 *		Extensive bug fixes, changed uses of Borland stat()
 *		to fsize() in order to fix intermittent crashes
 *
 *	November 93	KO4KS/Brian A. Lantz
 *		Modified close_folder() for TNOS control files
 *
 */

#include "global.h"
#ifdef POP2SERVER
#include <time.h>
#ifndef MSDOS
#include <sys/stat.h>
#endif
#include "mbuf.h"
#include "socket.h"
#include "smtp.h"
#include "mailbox.h"
#include "bm.h"
#include "stats.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: pop2serv.c,v 1.19 1997/09/07 21:18:28 root Exp $";
#endif


#undef DEBUG				/* N1BEE */


/* ---------------- common server data structures ---------------- */

/* POP server control block */

struct pop_scb {
	int	socket; 	/* socket number for this connection */
	char	state;		/* server state */
#define 	LSTN	0
#define 	AUTH	1
#define 	MBOX	2
#define 	ITEM	3
#define 	NEXT	4
#define 	DONE	5
	char	buf[TLINELEN+1];	/* input line buffer */
	char	count;		/* line buffer length */
	char	username[32];	/* user/folder name */
	FILE	*wf;		/* work folder file pointer */
	int	folder_len;	/* number of msgs in current folder */
	int	msg_num;	/* current msg number */
	long	msg_len;	/* length of current msg */
	int	msg_status_size; /* size of the message status array */
	long	curpos; 	/* current msg's position in file */
	long	folder_file_size; /* length of the current folder file, in bytes */
	long	nextpos;	/* next msg's position in file */
	char	folder_modified; /* mail folder contents modified flag */
	int16	*msg_status;	/* message status array pointer */
};

#define NULLSCB 	(struct pop_scb *)0

/* Response messages */

static char	count_rsp[]    = "#%d messages in this folder\n",
		error_rsp[]    = "- ERROR: %s\n",
		greeting_msg[] = "+ POP2 %s\n",
/*		length_rsp[]   = "=%ld bytes in this message\n", */
		length_rsp[]   = "=%ld characters in Message #%d\n",
		msg_line[]     = "%s\n",
		no_mail_rsp[]  = "+ No mail, sorry\n",
		no_more_rsp[]  = "=%d No more messages in this folder\n",
		signoff_msg[]  = "+ Bye, thanks for calling\n";

static struct pop_scb *create_scb (void);
static void delete_scb (struct pop_scb *scb);
static void popserv (int s,void *unused,void *p);
static int poplogin (char *pass,char *username);
void state_error (struct pop_scb *,const char *);
void open_folder (struct pop_scb *);
void do_cleanup (struct pop_scb *);
void read_message (struct pop_scb *);
void retrieve_message (struct pop_scb *);
void deletemsg (struct pop_scb *,int);
void get_message (struct pop_scb *,int);
void print_message_length (struct pop_scb *);
void close_folder (struct pop_scb *);
#ifdef POP_FOLDERS
void select_folder (struct pop_scb *);
#endif

static void pop_sm (struct pop_scb *scb);

static int Spop = -1; /* prototype socket for service */



/* Start up POP receiver service */
int
pop2start (int argc, char **argv, void *p)
{
	return (installserver (argc, argv, &Spop, "POP2 listener", IPPORT_POP2,
		INADDR_ANY, "POP2 server", popserv, 4096, NULL));
}



/* Shutdown POP2 service (existing connections are allowed to finish) */

int
pop2stop (int argc, char **argv, void *p)
{
	return (deleteserver (&Spop));
}



static void
popserv(s,unused,p)
int s;
void *unused;
void *p;
{
struct pop_scb *scb;

	sockowner(s,Curproc);		/* We own it now */
	log(s,"open POP2");

	if((scb = create_scb()) == NULLSCB) {
		tputs(Nospace);
		log(s,"close POP2 - no space");
		close_s(s);
		return;
	}

#ifdef STATS_USE
	STATS_adduse (1);
	MiscUsers++;
#endif
	scb->socket = s;
	scb->state  = AUTH;

	usprintf(s,greeting_msg,Hostname);

loop:	if ((scb->count = recvline(s,scb->buf,TLINELEN)) == -1){
		/* He closed on us */

		goto quit;
	}

	rip(scb->buf);
	if (strlen(scb->buf) == 0)	/* Ignore blank cmd lines */
		goto loop;

#ifdef DEBUG
	if(Mailtrace >= 3) {
		tcmdprintf("POP2SERV(popserv): Processing line <%s>\n",scb->buf);
		/* getch(); */
	}
#endif /* DEBUG */

	/* Convert lower, and mixed case commands to UPPER case - Ashok */
	for(cp = scb->buf;*cp != ' ' && *cp != '\0';cp++)
	    *cp = toupper(*cp);

	pop_sm(scb);
	if (scb->state == DONE)
		goto quit;

	goto loop;

quit:
#ifdef STATS_USE
	MiscUsers--;
#endif
	log(scb->socket,"close POP2");
	close_s(scb->socket);
	delete_scb(scb);
}



/* Create control block, initialize */

static struct
pop_scb *create_scb()
{
register struct pop_scb *scb;

	if((scb = (struct pop_scb *)callocw(1,sizeof (struct pop_scb))) == NULLSCB)
		return NULLSCB;

	scb->username[0] = '\0';
	scb->msg_status = NULL;
	scb->wf = NULL;

	scb->count = scb->folder_file_size = scb->msg_num = 0;

	scb->folder_modified = FALSE;
	return scb;
}



/* Free resources, delete control block */

static void
delete_scb(scb)
register struct pop_scb *scb;
{
	if (scb == NULLSCB)
		return;
	if (scb->wf != NULL)
		fclose(scb->wf);
	if (scb->msg_status  != NULL)
		free((char *)scb->msg_status);

	free((char *)scb);
}



/* --------------------- start of POP server code ------------------------ */

#define BITS_PER_WORD	16

#define isSOM(x)	((strncmp(x,"From ",5) == 0))	/* Start Of Message */

/* Command string specifications */

static char	ackd_cmd[] = "ACKD",
		acks_cmd[] = "ACKS",
#ifdef POP_FOLDERS
		fold_cmd[] = "FOLD ",
#endif
		login_cmd[] = "HELO ",
		nack_cmd[] = "NACK",
		quit_cmd[] = "QUIT",
		read_cmd[] = "READ",
		retr_cmd[] = "RETR";


		
static void
pop_sm(scb)
struct pop_scb *scb;
{
char password[30];

	if (scb == NULLSCB)	/* be certain it is good -- wa6smn */
		return;

	switch(scb->state) {
	case AUTH:
#ifdef DEBUG
		if(Mailtrace >= 3)
			tcmdprintf("POP2SERV(pop_sm): Entering case AUTH\n");
#endif /* DEBUG */
		if (strncmp(scb->buf,login_cmd,strlen(login_cmd)) == 0){
			sscanf(scb->buf,"HELO %31s %29s",scb->username,password);
#ifdef DEBUG
			if(Mailtrace >= 3) {
				tcmdprintf("POP2SERV(pop_sm): Processing USER %s PASS %s\n",scb->username,password);
				tcmdprintf("POP2SERV(pop_sm): Calling poplogin() for %s:%s:\n",scb->username,password);
			}
#endif /* DEBUG */

			if (!poplogin(scb->username,password)) {
				log(scb->socket,"POP2 access DENIED to %s",
					    scb->username);
				state_error(scb,"Access DENIED!!");
				return;
			}

			log(scb->socket,"POP2 access granted to %s",
				    scb->username);
			open_folder(scb);
		} else if (strncmp(scb->buf,quit_cmd,strlen(quit_cmd)) == 0){
			do_cleanup(scb);
		} else
			state_error(scb,"(AUTH) Expected HELO or QUIT command");
#ifdef DEBUG
		if(Mailtrace >= 3)
			tcmdprintf("POP2SERV(pop_sm): Leaving case AUTH\n");
#endif /* DEBUG */
		break;

	case MBOX:
		if (strncmp(scb->buf,read_cmd,strlen(read_cmd)) == 0)
			read_message(scb);

#ifdef POP_FOLDERS
		else if (strncmp(scb->buf,fold_cmd,strlen(fold_cmd)) == 0)
			select_folder(scb);

#endif

		else if (strncmp(scb->buf,quit_cmd,strlen(quit_cmd)) == 0) {
			do_cleanup(scb);
		} else
			state_error(scb,
#ifdef POP_FOLDERS
				    "(MBOX) Expected FOLD, READ, or QUIT command");
#else
				    "(MBOX) Expected READ or QUIT command");
#endif
		break;

	case ITEM:
		if (strncmp(scb->buf,read_cmd,strlen(read_cmd)) == 0)
			read_message(scb);

#ifdef POP_FOLDERS

		else if (strncmp(scb->buf,fold_cmd,strlen(fold_cmd)) == 0)
			select_folder(scb);
#endif

		else if (strncmp(scb->buf,retr_cmd,strlen(retr_cmd)) == 0)
			retrieve_message(scb);
		else if (strncmp(scb->buf,quit_cmd,strlen(quit_cmd)) == 0)
			do_cleanup(scb);
		else
			state_error(scb,
#ifdef POP_FOLDERS
			   "(ITEM) Expected FOLD, READ, RETR, or QUIT command");
#else
			   "(ITEM) Expected READ, RETR, or QUIT command");
#endif
		break;

	case NEXT:
		if (strncmp(scb->buf,ackd_cmd,strlen(ackd_cmd)) == 0){
				/* ACKD processing */
			deletemsg(scb,scb->msg_num);
			scb->msg_num++;
			get_message(scb,scb->msg_num);
		} else if (strncmp(scb->buf,acks_cmd,strlen(acks_cmd)) == 0){
				/* ACKS processing */
			scb->msg_num++;
			get_message(scb,scb->msg_num);
		} else if (strncmp(scb->buf,nack_cmd,strlen(nack_cmd)) == 0){
				/* NACK processing */
			fseek(scb->wf,scb->curpos,SEEK_SET);
		} else {
			state_error(scb,"(NEXT) Expected ACKD, ACKS, or NACK command");
			return;
		}

		print_message_length(scb);
		scb->state  = ITEM;
		break;

	case DONE:
		do_cleanup(scb);
		break;

	default:
		state_error(scb,"(TOP) State Error!!");
		break;
	}

#ifdef DEBUG
	if(Mailtrace >= 3)
		tcmdprintf("POP2SERV(pop_sm): Leaving state machine; state %u\n",scb->state);
#endif /* DEBUG */
}



static void
do_cleanup (struct pop_scb *scb)
{
	void close_folder (struct pop_scb *);

	close_folder(scb);
	(void) usputs(scb->socket,signoff_msg);
	scb->state = DONE;
}



static void
state_error (struct pop_scb *scb, const char *msg)
{
	if(Mailtrace >= 2)
		tcmdprintf(error_rsp,msg);
	usprintf(scb->socket,error_rsp,msg);
	scb->state = DONE;
}



#ifdef POP_FOLDERS

static void
select_folder(scb)
struct pop_scb	*scb;
{
	sscanf(scb->buf,"FOLD %s",scb->username);

	if (scb->wf != NULL)
		close_folder(scb);

	open_folder(scb);
}

#endif



static void
close_folder (struct pop_scb *scb)
{
char folder_pathname[128];
char line[TLINELEN+1];
FILE *fd;
int deleted = FALSE;
int msg_no = 0;
struct stat folder_stat;
int newmail (struct pop_scb *);
int isdeleted (struct pop_scb *,int);
char *cp;
int firstIDline = 0, nextisBID = 0, k;
int public, lines = 0;
long last;
struct let lt;

	if (scb->wf == NULL)
		return;

	if (!scb->folder_modified) {
		/* no need to re-write the folder if we have not modified it */

		fclose(scb->wf);
		scb->wf = NULL;

		free((char *)scb->msg_status);
		scb->msg_status = NULL;
		return;
	}


	sprintf(folder_pathname,"%s/%s.txt",Mailspool,scb->username);

	if (newmail(scb)) {
		/* copy new mail into the work file and save the
		   message count for later */

		if ((fd = fopen(folder_pathname,"r")) == NULL) {
			state_error(scb,"Unable to add new mail to folder");
			return;
		}

		fseek(scb->wf,0,SEEK_END);
		fseek(fd,scb->folder_file_size,SEEK_SET);
		while (!feof(fd)) {
			fgets(line,TLINELEN,fd);
			fputs(line,scb->wf);
		}

		fclose(fd);
	}

	/* now create the updated mail folder */

	if ((fd = fopen(folder_pathname,"w")) == NULL){
		state_error(scb,"Unable to update mail folder");
		return;
	}

	rewind(scb->wf);

	sprintf(line,"%s/control/%s.ctl",Mailspool,scb->username);
	remove (line);
	lt.start = last = 0;

	while (!feof(scb->wf)){
		fgets(line,TLINELEN,scb->wf);
		if (feof(scb->wf))
			continue;
		kwait(NULL);	/* give other processes time in long copy */

		if (isSOM(line)){
			lt.size = last - lt.start - lines; 
			if (lt.size && (deleted == FALSE))
				updateCtl (scb->username, &lt);
			lines = lt.status = 0;
			lt.start = last;
			firstIDline = 0;

			msg_no++;
			if (msg_no <= scb->folder_len)
				deleted = isdeleted(scb,msg_no);
			else
				deleted = FALSE;
		}


		lines++;
		if (!firstIDline && nextisBID && (cp=strstr(line,"AA")) != NULLCHAR) {
			/*what follows is the message-number*/
			lt.bid = atol(cp+2);
			nextisBID = 0;
			firstIDline = 1;
			}

		if (!strncmp ("Received: ", line, 10))
			nextisBID = 1;
		if (!strncmp ("Status: R", line, 9))
			lt.status = BM_READ;

		if (deleted)
			continue;

		fputs(line,fd);
		last = ftell (fd);
	}

	lt.size = last - lt.start - lines; 
	if (deleted == FALSE)
		updateCtl (scb->username, &lt);

	fclose(fd);

	/* trash the updated mail folder if it is empty */

/*	if ((stat(folder_pathname,&folder_stat) == 0) && (folder_stat.st_size == 0)) */
	if(fsize(folder_pathname) == 0L)		/* N1BEE */
		unlink(folder_pathname);

	fclose(scb->wf);
	scb->wf = NULL;

	free((char *)scb->msg_status);
	scb->msg_status = NULL;
}



static void
open_folder (struct pop_scb *scb)
{
char folder_pathname[64];
char line[TLINELEN+1];
FILE *fd;
struct stat folder_stat;


	sprintf(folder_pathname,"%.45s/%.8s.txt",Mailspool,scb->username);
#ifdef DEBUG
	if(Mailtrace >= 3) {
		tcmdprintf("POP2SERV(open_folder): will open %s\n",folder_pathname);
	}
#endif /* DEBUG */
	scb->folder_len       = 0;
	scb->folder_file_size = 0;

	/* Ordinarily, we would call stat() to find out if the file exists
	   and get its size at the same time.  However, there is a bug in
	   Borland's stat() code which crashes DesqView and OS/2 (!) if
	   stat() is called on a file which does not exist.  -- N1BEE
	*/

	/* if (stat(folder_pathname,&folder_stat)){ */
	if((folder_stat.st_size = fsize(folder_pathname)) == -1L) { /* N1BEE */
#ifdef DEBUG
		if(Mailtrace >= 3) {
			tcmdprintf("POP2SERV(open_folder): folder not found (empty)\n");
		}
#endif /* DEBUG */
		(void) usputs(scb->socket,no_mail_rsp);

		/* state remains AUTH, expecting HELO or QUIT */
		return;
	}

	scb->folder_file_size = folder_stat.st_size;
	if ((fd = fopen(folder_pathname,"r")) == NULL){
		state_error(scb,"POP2SERV(open_folder): Unable to open mail folder");
		return;
	}

	if ((scb->wf = tmpfile()) == NULL) {
		state_error(scb,"POP2SERV(open_folder): Unable to create work folder");
		return;
	}

	while(!feof(fd)) {
		fgets(line,TLINELEN,fd);

		/* scan for begining of a message */

		if (isSOM(line))
			scb->folder_len++;

		/* now put  the line in the work file */

		fputs(line,scb->wf);
	}

	fclose(fd);

	scb->msg_status_size = (scb->folder_len) / BITS_PER_WORD;

	if ((((scb->folder_len) % BITS_PER_WORD) != 0) ||
	    (scb->msg_status_size == 0))
		scb->msg_status_size++;

	if ((scb->msg_status = (int16 *) callocw(scb->msg_status_size,
				sizeof(int16))) == NULL) {
		state_error(scb,"Unable to create message status array");
		return;
	}

	usprintf(scb->socket,count_rsp,scb->folder_len);

	scb->state  = MBOX;

#ifdef DEBUG
	if(Mailtrace >= 3)
		tcmdprintf("POP2SERV: open_folder() completed successfully.\n");
#endif /* DEBUG */
}



static void
read_message (struct pop_scb *scb)
{
	void get_message (struct pop_scb *,int);
	void print_message_length (struct pop_scb *);

	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;
	if (scb->buf[sizeof(read_cmd) - 1] == ' ')
		scb->msg_num = atoi(&(scb->buf[sizeof(read_cmd) - 1]));
	else
		scb->msg_num++;

	get_message(scb,scb->msg_num);
	print_message_length(scb);
	scb->state  = ITEM;
}



static void
retrieve_message (struct pop_scb *scb)
{
char line[TLINELEN+1];
long cnt;

	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;
	if (scb->msg_len == 0) {
		state_error(scb,"Attempt to access a DELETED message!");
		return;
	}

	cnt  = scb->msg_len;
	while(!feof(scb->wf) && (cnt > 0)) {
		fgets(line,TLINELEN,scb->wf);
		rip(line);

		usprintf(scb->socket,msg_line,line);
		cnt -= (strlen(line)+2);	/* Compensate for CRLF */
	}

	scb->state = NEXT;
}



static void
get_message (struct pop_scb *scb, int msg_no)
{
char line[TLINELEN+1];
long ftell (FILE *);
int isdeleted (struct pop_scb *, int);

	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;
	scb->msg_len = 0;
	if (msg_no > scb->folder_len) {
		scb->curpos  = 0;
		scb->nextpos = 0;
		return;
	} else {
		/* find the message and its length */

		rewind(scb->wf);
		while (!feof(scb->wf) && (msg_no > -1)) {
			if (msg_no > 0)
				scb->curpos = ftell(scb->wf);
		        
			fgets(line,TLINELEN,scb->wf);
			rip(line);

			if (isSOM(line))
				msg_no--;

			if (msg_no != 0)
				continue;

			scb->nextpos  = ftell(scb->wf);
			scb->msg_len += (strlen(line)+2);	/* Add CRLF */
		}
	}

	if (scb->msg_len > 0)
		fseek(scb->wf,scb->curpos,SEEK_SET);

	/* we need the pointers even if the message was deleted */

	if  (isdeleted(scb,scb->msg_num))
		scb->msg_len = 0;
}



static int
poplogin(username,pass)
char *pass;
char *username;
{
char buf[80];
char *cp;
char *cp1;
FILE *fp;

#ifdef DEBUG
	if(Mailtrace >= 3)
		tcmdprintf("POP2SERV(poplogin): Opening POP users file %s\n",Popusers);
#endif /* DEBUG */

	if((fp = fopen(Popusers,"r")) == NULLFILE) {
		/* User file doesn't exist */
		tprintf("POP2 users file %s not found\n",Popusers);
		return(FALSE);
	}

#ifdef DEBUG
	if(Mailtrace >= 3)
		tcmdprintf("POP2SERV(poplogin): Login request from %s:%s:\n",username,pass);
#endif /* DEBUG */

	while(fgets(buf,sizeof(buf),fp),!feof(fp)) {
		if(buf[0] == '#')
			continue;	/* Comment */

		if((cp = strchr(buf,':')) == NULLCHAR)
			/* Bogus entry */
			continue;

		*cp++ = '\0';		/* Now points to password */
		if(strcmp(username,buf) == 0)
			break;		/* Found user name */
	}

	if(feof(fp)) {
#ifdef DEBUG
		if(Mailtrace >= 3)
			tcmdprintf("POP2SERV(poplogin): username not found in POPUSERS\n");
#endif /* DEBUG */
		/* User name not found in file */

		fclose(fp);
		return(FALSE);
	}
	fclose(fp);

	if ((cp1 = strchr(cp,':')) == NULLCHAR) {
#ifdef DEBUG
		if(Mailtrace >= 3)
			tcmdprintf("POP2SERV(poplogin): No second ':' in POPUSERS entry\n");
#endif /* DEBUG */
		return(FALSE);
	}

	*cp1 = '\0';
	if(strcmp(cp,pass) != 0) {
#ifdef DEBUG
		if(Mailtrace >= 3)
			tcmdprintf("POP2SERV(poplogin): Wrong password (%s) from user %s, expecting %s\n",pass,username,cp);
#endif /* DEBUG */
		/* Password required, but wrong one given */
		return(FALSE);
	}

	/* whew! finally made it!! */
#ifdef DEBUG
	if(Mailtrace >= 3)
		tcmdprintf("POP2SERV(poplogin): %s authenticated\n",username);
#endif /* DEBUG */

	return(TRUE);
}



static int
isdeleted (struct pop_scb *scb, int msg_no)
{
int16 mask = 1,offset;

	msg_no--;
	offset = msg_no / BITS_PER_WORD;
	mask <<= msg_no % BITS_PER_WORD;
	return (((scb->msg_status[offset]) & mask)? TRUE:FALSE);
}



static void
deletemsg (struct pop_scb *scb, int msg_no)
{
int16 mask = 1,offset;

	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;
	msg_no--;
	offset = msg_no / BITS_PER_WORD;
	mask <<= msg_no % BITS_PER_WORD;
	scb->msg_status[offset] |= mask;
	scb->folder_modified = TRUE;
}



static int
newmail (struct pop_scb *scb)
{
char folder_pathname[64];
struct stat folder_stat;

	sprintf(folder_pathname,"%s/%s.txt",Mailspool,scb->username);

	/* if (stat(folder_pathname,&folder_stat)) { */
	if((folder_stat.st_size = fsize(folder_pathname)) == -1L) { /* N1BEE */
		state_error(scb,"Unable to get old mail folder's status");
		return(FALSE);
	} else
		return ((folder_stat.st_size > scb->folder_file_size)? TRUE:FALSE);
}



static void
print_message_length (struct pop_scb *scb)
{
char *print_control_string;

	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;
	if (scb->msg_len > 0)
		print_control_string = length_rsp;
	else if (scb->msg_num <= scb->folder_len)
		print_control_string = length_rsp;
	else
		print_control_string = no_more_rsp;

	(void)usprintf(scb->socket,print_control_string,scb->msg_len,scb->msg_num);
}

#endif
