/* POP3 Server state machine - see RFC 1225
 *
 *      Jan 92  Erik Olson olson@phys.washington.edu
 *              Taken from POP2 server code in NOS 910618
 *              Rewritten/converted to POP3
 *      Feb 92  William Allen Simpson
 *              integrated with current work
 *      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;
 *              corrected confusion of sockmode()
 *	November 93	KO4KS/Brian A. Lantz
 *		Modified close_folder() for TNOS control files
 *
 *  "Need-to" list: XTND XMIT (to get WinQVTnet to work)
 *  LZW support by Dave Brown, N2RJT.
 */

#include "global.h"
#ifdef POP3SERVER
#include "ctype.h"
#include "commands.h"
#ifndef MSDOS
#include <time.h>
#include <setjmp.h>
#include "dirutil.h"
#endif
#include <sys/stat.h>
#include <stdarg.h>
#include "mbuf.h"
#include "socket.h"
#include "files.h"
#include "mailbox.h"
#include "bm.h"
#include "md5.h"
#include "stats.h"

#if defined(LZW)
#include "lzw.h"
#ifdef MAILCLIENT
extern int poplzw;
#else
static int poplzw = TRUE;	/* no way yet to reset this */
#endif
#endif


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


/* ---------------- common server data structures ---------------- */
/* POP message pointer element */

struct pop_msg {
	long len;
	long pos;
	int deleted;
	struct pop_msg *next;
};

/* POP server control block */

struct pop_scb {
	int socket;		/* socket number for this connection */
	char state;		/* server state */
#ifndef _lint
#define      LSTN       0
#endif
#define      AUTH       1
#define      TRANS      2
#ifndef _lint
#define      UPDATE     3
#endif
#define      DONE       5

	char buf[TLINELEN];	/* input line buffer */
	char count;		/* line buffer length */
	char username[64];	/* user/folder name */
	char *challenge;	/* challenge string */
	FILE *wf;		/* work folder file pointer */
	int folder_len;		/* number of msgs in current folder */
	int high_num;		/* highest message number accessed */
	long folder_file_size;	/* length of the current folder file, in bytes */
	char folder_modified;	/* mail folder contents modified flag */
	struct pop_msg *msg;	/* message database link-list */
};

#define NULLSCB  (struct pop_scb *)0


/* Response messages -- '\n' is converted to '\r\n' by the socket code */

static char	count_rsp[]		= "+OK you have %d messages\n",
		error_rsp[]		= "-ERR %s\n",
		greeting_msg[]		= "+OK %s POP3 ready %s\n",
#if defined(LZW)
		xlzw_rsp[]		= "+OK lzw %d %d\n",
#endif
		user_rsp[]		= "+OK user\n",
#ifdef notdef		
		pass_rsp[]		= "+OK password\n",
#endif
		stat_rsp[]		= "+OK %d %ld\n",
		list_single_rsp[]	= "+OK %d %ld\n",
		list_multi_rsp[]	= "+OK %d messages (%ld octets)\n",
		retr_rsp[]		= "+OK %ld octets\n",
		multi_end_rsp[]		= ".\n",
		msg_line[]		= "%s\n",
		dele_rsp[]		= "+OK message %d deleted\n",
		noop_rsp[]		= "+OK\n",
		last_rsp[]		= "+OK %d\n",
		signoff_msg[]		= "+OK Bye, bye-bye, bye now, goodbye\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 *username, char *password);
static int apoplogin (char *username, char *challenge, char *response);
static void open_folder (struct pop_scb *);
static void stat_message (struct pop_scb *);
static void list_message (struct pop_scb *);
static void retr_message (struct pop_scb *);
static void dele_message (struct pop_scb *);
static void noop_message (struct pop_scb *);
static void last_message (struct pop_scb *);
static void rset_message (struct pop_scb *);
static void top_message (struct pop_scb *);
static void close_folder (struct pop_scb *);
static void state_error (struct pop_scb *, const char *);
static void fatal_error (struct pop_scb *, const char *);
static void do_cleanup (struct pop_scb *);
static int newmail (struct pop_scb *);
static struct pop_msg *goto_msg (struct pop_scb *, int);
static void delete_msglist (struct pop_msg *b_msg);
static void pop_sm (struct pop_scb *scb);

extern void updateCtl (const char *who, struct let *info);

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



/* Start up POP receiver service */
int
pop3start (int argc, char **argv, void *p OPTIONAL)
{
	return (installserver (argc, argv, &Spop, "POP3 listener", IPPORT_POP3,
		INADDR_ANY, "POP3 server", popserv, 2048, NULL));
}



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

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



static void
popserv (int s, void *unused OPTIONAL, void *p OPTIONAL)
{
struct pop_scb *scb;
char *cp;
char challenge[80];
time_t now;

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

	if ((scb = create_scb ()) == NULLSCB) {
		tputs (Nospace);
		log (s, "close POP3 - no space");
		close_s (s);
		return;
	}
#ifdef STATS_USE
	STATS_adduse (1);
	MiscUsers++;
#endif
	scb->socket = s;
	scb->state = AUTH;
	(void) time (&now);
	sprintf (challenge, "<%lu@%s>", now, Hostname);
	scb->challenge = strdup (challenge);

	(void) sockmode (s, SOCK_ASCII);	/* N1BEE */
	usprintf (s, greeting_msg, Hostname, scb->challenge);

loop:
	if ((scb->count = (char) recvline (s, (unsigned char *) scb->buf, TLINELEN)) == -1)
		/* He closed on us */
		goto quit;

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

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

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

	goto loop;

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



/* Create control block, initialize */

static struct
pop_scb *
create_scb (void)
{
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 = NULL;
	scb->wf = NULL;

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

	scb->folder_modified = FALSE;
	return scb;
}



/* Free msg link-list */
static void
delete_msglist (struct pop_msg *b_msg)
{
struct pop_msg *msg, *msg2;

	msg = b_msg;
	while (msg != NULL) {
		msg2 = msg->next;
		free (msg);
		msg = msg2;
	}
}



/* Free resources, delete control block */

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

	free ((char *) scb);
}



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


#define isSOM(x)        ((strncmp(x,"From ",5) == 0))

/* Command string specifications */

static char	user_cmd[]	= "USER ",
		pass_cmd[]	= "PASS ",
		apop_cmd[]	= "APOP ",
		quit_cmd[]	= "QUIT",
		stat_cmd[]	= "STAT",
		list_cmd[]	= "LIST",
#ifdef LZW
		xlzw_cmd[]	= "XLZW ",
#endif
		retr_cmd[]	= "RETR",
		dele_cmd[]	= "DELE",
		noop_cmd[]	= "NOOP",
		rset_cmd[]	= "RSET",
		top_cmd[]	= "TOP",
		last_cmd[]	= "LAST";



static void
pop_sm (struct pop_scb *scb)
{
char password[40];
#if defined(LZW)
int lzwmode, lzwbits;
#endif

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

#if defined(LZW)
	if (strncmp (scb->buf, xlzw_cmd, strlen (xlzw_cmd)) == 0) {
		sscanf (scb->buf, "XLZW %d %d", &lzwbits, &lzwmode);
		if ((lzwmode == 0 || lzwmode == 1) && (lzwbits > 8 && lzwbits < 17) && poplzw) {
			usprintf (scb->socket, xlzw_rsp, lzwbits, lzwmode);
			lzwinit (scb->socket, lzwbits, lzwmode);
		} else if (!poplzw)
			state_error (scb, "(AUTH) LZW compression disabled");
		else
			state_error (scb, "LZW bits or mode invalid");
		return;
	}
#endif

	switch (scb->state) {

		case AUTH:
			if (strncmp (scb->buf, user_cmd, strlen (user_cmd)) == 0) {
				strncpy (scb->username, &scb->buf[strlen(user_cmd)], 64);
				(void) usputs (scb->socket, user_rsp);

			} else if (strncmp (scb->buf, pass_cmd, strlen (pass_cmd)) == 0) {
				if (strlen (scb->buf) > strlen (pass_cmd) + sizeof (password)) {
					state_error (scb, "Access DENIED!!");
					return;
				}
				strncpy (password, &scb->buf[strlen(pass_cmd)], 40);

				if (!poplogin (scb->username, password)) {
					log (scb->socket, "POP3 access DENIED to %s", scb->username);
					state_error (scb, "Access DENIED!!");
					return;
				}
				log (scb->socket, "POP3 access granted to %s", scb->username);
				open_folder (scb);
			} else if (strncmp (scb->buf, apop_cmd, strlen (apop_cmd)) == 0) {
				if (strlen (scb->buf) > strlen (apop_cmd) + sizeof (password)) {
					state_error (scb, "Access DENIED!!");
					return;
				}
				sscanf (scb->buf, "APOP %64s %40s", scb->username, password);

				if (!apoplogin (scb->username, scb->challenge, password)) {
					log (scb->socket, "POP3 apop access DENIED to %s", scb->username);
					state_error (scb, "Access DENIED!!");
					return;
				}
				log (scb->socket, "POP3 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 APOP, USER, PASS or QUIT");
			break;

		case TRANS:
			if (strncmp (scb->buf, stat_cmd, strlen (stat_cmd)) == 0)
				stat_message (scb);

			else if (strncmp (scb->buf, list_cmd, strlen (list_cmd)) == 0)
				list_message (scb);

			else if (strncmp (scb->buf, retr_cmd, strlen (retr_cmd)) == 0)
				retr_message (scb);

			else if (strncmp (scb->buf, dele_cmd, strlen (dele_cmd)) == 0)
				dele_message (scb);

			else if (strncmp (scb->buf, noop_cmd, strlen (noop_cmd)) == 0)
				noop_message (scb);

			else if (strncmp (scb->buf, last_cmd, strlen (last_cmd)) == 0)
				last_message (scb);

			else if (strncmp (scb->buf, top_cmd, strlen (top_cmd)) == 0)
				top_message (scb);

			else if (strncmp (scb->buf, rset_cmd, strlen (rset_cmd)) == 0)
				rset_message (scb);

			else if (strncmp (scb->buf, quit_cmd, strlen (quit_cmd)) == 0)
				do_cleanup (scb);

			else
				state_error (scb,
				"(TRANS) unsupported/unrecognized command");
			break;

		case DONE:
			break;

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



static void
do_cleanup (struct pop_scb *scb)
{
	close_folder (scb);
	(void) usputs (scb->socket, signoff_msg);
	scb->state = DONE;
}



static void
state_error (struct pop_scb *scb, const char *msg)
{
	usprintf (scb->socket, error_rsp, msg);
	/* scb->state = DONE; *//* Don't automatically hang up */
}



static void
fatal_error (struct pop_scb *scb, const char *msg)
{
	usprintf (scb->socket, error_rsp, msg);
	scb->state = DONE;
}



static void
close_folder (struct pop_scb *scb)
{
char folder_pathname[128];
char line[TLINELEN + 1];
FILE *fd;
int deleted = FALSE;
struct pop_msg *msg;
char *cp;
int firstIDline = 0, nextisBID = 0;
int 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 */

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

		delete_msglist (scb->msg);
		scb->msg = 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) {
			fatal_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)) {
			(void) fgets (line, TLINELEN, fd);
			fputs (line, scb->wf);
		}

		(void) fclose (fd);
	}
	/* now create the updated mail folder */

	if (mlock (Mailspool, scb->username))
		fatal_error (scb, "Can't lock the mailbox");
	if ((fd = fopen (folder_pathname, "w")) == NULL) {
		fatal_error (scb, "Unable to update mail folder");
		rmlock (Mailspool, scb->username);
		return;
	}
	rewind (scb->wf);

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

	msg = scb->msg;
	while (!feof (scb->wf)) {
		(void) 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;

			if (msg != NULL)
				msg = msg->next;
			if (msg != NULL)
				deleted = msg->deleted;
			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);

	(void) fclose (fd);
	rmlock (Mailspool, scb->username);
	if (fsize (folder_pathname) == 0L)	/* N1BEE */
		unlink (folder_pathname);
	(void) fclose (scb->wf);
	scb->wf = NULL;
	delete_msglist (scb->msg);
	scb->msg = NULL;

}



static void
open_folder (struct pop_scb *scb)
{
char folder_pathname[FILE_PATH_SIZE];
char line[TLINELEN];
long pos;
FILE *fd;
struct stat folder_stat;
struct pop_msg *msg;

	sprintf (folder_pathname, "%s/%s.txt", Mailspool, scb->username);
	scb->folder_len = 0;
	scb->folder_file_size = 0;
	/* if (stat(folder_pathname,&folder_stat)){ */
	if ((folder_stat.st_size = fsize (folder_pathname)) == -1) {	/* N1BEE */
		usprintf (scb->socket, count_rsp, scb->folder_len);
		scb->state = TRANS;
		return;		/* no file = OK */
	}
	scb->folder_file_size = folder_stat.st_size;
	if ((fd = fopen (folder_pathname, "r")) == NULL) {
		state_error (scb, "Unable to open mail folder");
		return;
	}
	if ((scb->wf = tmpfile ()) == NULL) {
		state_error (scb, "Unable to create work folder");
		return;
	}
	scb->msg = callocw (sizeof (struct pop_msg), 1);	/* create first element */

	if (scb->msg == NULL) {
		fatal_error (scb, "Unable to create pointer list");
		return;
	}
	scb->msg->next = NULL;
	msg = scb->msg;
	msg->len = 0;
	msg->deleted = 0;

	while (!feof (fd)) {
		pos = ftell (scb->wf);
		(void) fgets (line, TLINELEN, fd);

		/* scan for begining of a message */

		if (isSOM (line)) {
			kwait (NULL);	/* be nice to the others */
			scb->folder_len++;
			msg->next = callocw (sizeof (struct pop_msg), 1);

			if (msg->next == NULL) {
				fatal_error (scb, "Unable to create pointer list");
				return;
			}
			msg = msg->next;
			msg->pos = pos;
			msg->next = NULL;
			msg->len = 0;
			msg->deleted = 0;

			/* now put  the line in the work file */
		}
		fputs (line, scb->wf);
		rip (line);
		if (*line == '.')
			msg->len++;
		msg->len += (long) (strlen (line) + 2);	/* Add msg len count */
	}

	(void) fclose (fd);
	scb->high_num = 0;	/* reset high read */

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

	scb->state = TRANS;
}



static void
stat_message (struct pop_scb *scb)
{
long total = 0;
int count = 0;
struct pop_msg *msg;

	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;

	if (scb->folder_len)	/* add everything up */
		for (msg = scb->msg->next; msg != NULL; msg = msg->next)
			if (!msg->deleted) {
				total += msg->len;
				++count;
			}
	usprintf (scb->socket, stat_rsp, count, total);
}



static void
list_message (struct pop_scb *scb)
{
struct pop_msg *msg;
int msg_no = 0, oldf;
long total = 0;
char *cp;

	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;
	cp = skipwhite (&(scb->buf[sizeof (list_cmd) - 1]));	/* accomodate LIST <sp> as LIST */
	if (*cp) {
		msg_no = atoi (cp);
		msg = goto_msg (scb, msg_no);
		if (msg == NULL || msg->deleted)
			state_error (scb, "non existent or deleted message");
		else
			usprintf (scb->socket, list_single_rsp, msg_no, msg->len);
	} else {		/* multiline */
		if (scb->folder_len)	/* add everything */
			for (msg = scb->msg->next; msg != NULL; msg = msg->next)
				if (!msg->deleted)
					total += msg->len, ++msg_no;

		oldf = setflush (scb->socket, -1);	/* we will flush it explicitely */
		usprintf (scb->socket, list_multi_rsp, msg_no, total);

		if (scb->folder_len)
			for (msg = scb->msg->next, msg_no = 1; msg != NULL;
			     msg = msg->next, msg_no++)
				if (!msg->deleted)
					usprintf (scb->socket, "%d %ld\n", msg_no, msg->len);

		(void) usputs (scb->socket, multi_end_rsp);
		usflush (scb->socket);
		(void) setflush (scb->socket, oldf);
	}
}



static void
retr_message (struct pop_scb *scb)
{
char line[TLINELEN];
long cnt;
int msg_no, oldf;
struct pop_msg *msg;
int first_line = TRUE;

	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;
	if (scb->buf[sizeof (retr_cmd) - 1] != ' ') {
		state_error (scb, "no such message");
		return;
	}
	msg_no = atoi (&(scb->buf[sizeof (retr_cmd) - 1]));
	msg = goto_msg (scb, msg_no);
	if (msg == NULL || msg->deleted) {
		state_error (scb, "no such message");
		return;
	}
	cnt = msg->len;
	oldf = setflush (scb->socket, -1);	/* we will flush it explicitely */
	fseek (scb->wf, msg->pos, SEEK_SET);	/* Go there */

	while (!feof (scb->wf) && (cnt > 0)) {
		(void) fgets (line, TLINELEN, scb->wf);
		rip (line);
		if (first_line) {
			first_line = FALSE;
			if (isSOM (line)) {
				cnt -= (long) (strlen (line) + 2);
				usprintf (scb->socket, retr_rsp, cnt);
			} else {
				usprintf (scb->socket, retr_rsp, cnt);
				usprintf (scb->socket, msg_line, line);
			}
			continue;
		}
		if (*line == '.') {
			(void) usputc (scb->socket, '.');
			cnt--;
		}
		usprintf (scb->socket, msg_line, line);
		cnt -= (long) (strlen (line) + 2);	/* Compensate for CRLF */
	}
	(void) usputs (scb->socket, ".\n");
	usflush (scb->socket);
	(void) setflush (scb->socket, oldf);
	if (msg_no >= scb->high_num)
		scb->high_num = msg_no;	/* bump high water mark */
}



static void
noop_message (struct pop_scb *scb)
{
	(void) usputs (scb->socket, noop_rsp);
}



static void
last_message (struct pop_scb *scb)
{
	usprintf (scb->socket, last_rsp, scb->high_num);
}



static void
rset_message (struct pop_scb *scb)
{
struct pop_msg *msg;
long total = 0;

	if (scb->folder_len)
		for (msg = scb->msg->next; msg != NULL; msg = msg->next)
			msg->deleted = FALSE, total += msg->len;

	scb->high_num = 0;	/* reset last */
	scb->folder_modified = FALSE;
	usprintf (scb->socket, list_multi_rsp, scb->folder_len, total);
}



static void
top_message (struct pop_scb *scb)
{
char *ptr;
char line[TLINELEN];
struct pop_msg *msg;
int msg_no = 0, lines = 0, oldf;
long total = 0;


	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;
	if (scb->buf[sizeof (top_cmd) - 1] != ' ') {
		state_error (scb, "No message specified");
		return;
	}
	for (ptr = scb->buf + sizeof (top_cmd); *ptr == ' '; ++ptr)
		;
	/* Space drop */
	for ( ; *ptr != ' ' && *ptr != '\0'; ++ptr)
		;
	/* token drop */
	msg_no = atoi (&(scb->buf[sizeof (top_cmd) - 1]));
	lines = atoi (++ptr);	/* Get # lines to top */
	if (lines < 0)
		lines = 0;

	msg = goto_msg (scb, msg_no);
	if (msg == NULL || msg->deleted) {
		state_error (scb, "non existent or deleted message");
		return;
	}
	oldf = setflush (scb->socket, -1);	/* we will flush it explicitely */
	fseek (scb->wf, msg->pos, SEEK_SET);	/* Go there */
	total = msg->len;	/* Length of current message */
	(void) usputs (scb->socket, noop_rsp);	/* Give OK */
	do {
		(void) fgets (line, TLINELEN, scb->wf);
		rip (line);
		if (*line == '.') {
			(void) usputc (scb->socket, '.');
			total--;
		}
		total -= (long) (strlen (line) + 2);
		usprintf (scb->socket, msg_line, line);
	} while (*line != '\0' && total > 0);
	for ( ; total > 0 && lines; --lines) {
		(void) fgets (line, TLINELEN, scb->wf);
		rip (line);
		if (*line == '.') {
			(void) usputc (scb->socket, '.');
			total--;
		}
		total -= (long) (strlen (line) + 2);
		usprintf (scb->socket, msg_line, line);
	}
	(void) usputs (scb->socket, multi_end_rsp);
	usflush (scb->socket);
	(void) setflush (scb->socket, oldf);

}



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

	if ((fp = fopen (Popusers, "r")) == NULLFILE) {
		/* User file doesn't exist */
		tprintf ("POP users file %s not found\n", Popusers);
		return (FALSE);
	}
	while ((void) 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)) {
		/* User name not found in file */

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

	if (cp == NULLCHAR || (cp1 = strchr (cp, ':')) == NULLCHAR)
		return (FALSE);

	*cp1 = '\0';
	if (strcmp (cp, pass) != 0) 	/* Password required, but wrong one given */
		return (FALSE);

	/* whew! finally made it!! */

	return (TRUE);
}



static int
apoplogin (char *username, char *challenge, char *response)
{
char buf[80], missive[80];
char *cp = NULLCHAR;
unsigned char *dp, *ep;
char *cp1;
FILE *fp;
MD5_CTX mdContext;

	if ((fp = fopen (Popusers, "r")) == NULLFILE) {
		/* User file doesn't exist */
		tprintf ("POP users file %s not found\n", Popusers);
		return (FALSE);
	}
	while ((void) 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)) {
		/* User name not found in file */

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

	if (cp == NULLCHAR || (cp1 = strchr (cp, ':')) == NULLCHAR)
		return (FALSE);

	*cp1 = '\0';

	MD5Init (&mdContext);
	MD5Update (&mdContext, (unsigned char *) challenge, strlen (challenge));
	MD5Update (&mdContext, (unsigned char *) cp, strlen (cp));
	MD5Final (&mdContext);

	cp = missive;
	dp = mdContext.digest;
	ep = dp + sizeof (mdContext.digest);
	for (; dp < ep; cp += 2)
		(void) sprintf (cp, "%02x", *dp++ & 0xff);
	*cp = '\0';

	if (strcmp (missive, response) != 0) 	/* Password required, but wrong one given */
		return (FALSE);

	/* whew! finally made it!! */

	return (TRUE);
}



static void
dele_message (struct pop_scb *scb)
{
struct pop_msg *msg;
int msg_no;

	if (scb == NULLSCB)	/* check for null -- wa6smn */
		return;
	if (scb->buf[sizeof (retr_cmd) - 1] != ' ') {
		state_error (scb, "no such message");
		return;
	}
	msg_no = atoi (&(scb->buf[sizeof (retr_cmd) - 1]));
	msg = goto_msg (scb, msg_no);
	if (msg == NULL || msg->deleted) {
		state_error (scb, "attempt to access deleted message");
		return;
	}
	if (msg->deleted) {	/* Don't bother if already dead */
		state_error (scb, "message already deleted");
		return;
	}
	msg->deleted = TRUE;
	scb->folder_modified = TRUE;
	usprintf (scb->socket, dele_rsp, msg_no);
}



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)) == -1) {	/* 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 struct pop_msg *
goto_msg (struct pop_scb *scb, int msg_no)
{
int msg_num;
struct pop_msg *msg;

	msg_num = msg_no - 1;
	if (scb->folder_len == 0 || msg_num < 0)
		return NULL;
	for (msg = scb->msg->next; msg_num && msg != NULL; --msg_num)
		msg = msg->next;
	return msg;
}

#endif
