/*
 *	Simple mail user interface for KA9Q IP/TCP package.
 *	A.D. Barksdale Garbee II, aka Bdale, N3EUA
 *	Copyright 1986 Bdale Garbee, All Rights Reserved.
 *	Permission granted for non-commercial copying and use, provided
 *	this notice is retained.
 *	Copyright 1987 1988 Dave Trulli NN2Z, All Rights Reserved.
 *	Permission granted for non-commercial copying and use, provided
 *	this notice is retained.
 *
 *	Ported to NOS at 900120 by Anders Klemets SM0RGV.
 */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include "global.h"
#include "ftpserv.h"
#include "smtp.h"
#include "proc.h"
#include "usock.h"
#include "telnet.h"

#define		SETVBUF
#if	defined(UNIX) || defined(MICROSOFT)
#include	<sys/types.h>
#endif
/*
#if	defined(UNIX) || defined(MICROSOFT) || defined(__TURBOC__)
#include	<sys/stat.h>
#endif
#ifdef AZTEC
#include <stat.h>
#endif
*/
#include <fcntl.h>
#include "bm.h"
#include "ax_mbx.h"

#ifdef SETVBUF
#define		MYBUF	4096
char	*stdinbuf = NULLCHAR;	/* the stdio buffer for the mail file */
char	*stdoutbuf = NULLCHAR;	/* the stdio file io buffer for the temp file */
#endif

extern long ftell();
extern char *Arealist;
static char Badmsg[] = "Invalid Message number %d\n";
static char Nomail[] = "No messages\n";
static char Noaccess[] = "Unable to access %s\n";
static int readnotes __ARGS((struct mbx *m,FILE *ifile));
#ifdef	notdef
static int msgtofile __ARGS((struct mbx *m,int msg,FILE *tfile,int noheader));
#endif
static int isnewmail __ARGS((struct mbx *m));
static int htype __ARGS((char *s));
static int initnotes __ARGS((struct mbx *m));
static int lockit __ARGS((struct mbx *m));
static int prefix __ARGS((char *pref, char *full));
static long fsize __ARGS((char *name));

static int
initnotes(m)
struct mbx *m;
{
	FILE	*tmpfile();
	FILE	*ifile;
	register struct	let *cmsg;
	char buf[256];
	int 	i, ret;

	sprintf(buf,"%s/%s.txt",Mailspool,m->area);
	if ((ifile = fopen(buf,READ_TEXT)) == NULLFILE)
		return 0;
	fseek(ifile,0L,2);	 /* go to end of file */
	m->mboxsize = ftell(ifile);
	rewind(ifile);
	if(!strcmp(m->area,m->name)) /* our private mail area */
		m->mysize = m->mboxsize;
#ifdef	SETVBUF
	if (stdinbuf == NULLCHAR)
		stdinbuf = mallocw(MYBUF);
	setvbuf(ifile, stdinbuf, _IOFBF, MYBUF);
#endif
	if ((m->mfile = tmpfile()) == NULLFILE) {
		(void) fclose(ifile);
		return -1;
	}
#ifdef SETVBUF
	if (stdoutbuf == NULLCHAR)
		stdoutbuf = mallocw(MYBUF);
	setvbuf(m->mfile, stdoutbuf, _IOFBF, MYBUF);
#endif
	m->nmsgs = 0;
	m->current = 0;
	m->change = 0;
	m->newmsgs = 0;
	m->anyread = 0;
	/* Allocate space for reading messages */
	free((char *)m->mbox);
	m->mbox = (struct let *)callocw(Maxlet,sizeof(struct let));
	ret = readnotes(m,ifile);
	(void) fclose(ifile);
	if (ret != 0)
		return -1;
#ifdef SETVBUF
	if (stdinbuf != NULLCHAR) {
		(void) free(stdinbuf);
		stdinbuf = NULLCHAR;
	}
#endif
	for (cmsg = &m->mbox[1],i = 1; i <= m->nmsgs; i++, cmsg++)  
		if ((cmsg->status & BM_READ) == 0) {
			m->newmsgs++;
			if (m->current == 0)
				m->current = i;
		}
	/* start at one if no new messages */
	if (m->current == 0)
		m->current++;

	return 0;
}

/* readnotes assumes that ifile is pointing to the first
 * message that needs to be read.  For initial reads of a
 * notesfile, this will be the beginning of the file.  For
 * rereads when new mail arrives, it will be the first new
 * message.
 */
static int
readnotes(m,ifile)
struct mbx *m;
FILE *ifile ;
{
	char 	tstring[LINELEN];
	long	cpos;
	register struct	let *cmsg;
	register char *line;

	cmsg = (struct let *)NULL;
	line = tstring;
	while(fgets(line,LINELEN,ifile) != NULLCHAR) {
		/* scan for begining of a message */
		if(strncmp(line,"From ",5) == 0) {
			cpos = ftell(m->mfile);
			fputs(line,m->mfile);
			if (m->nmsgs == Maxlet) {
				tprintf("Mail box full: > %d messages\n",Maxlet);
				(void) fclose(m->mfile);
				m->mfile = NULLFILE;
				return -1;
			}
			m->nmsgs++;
			cmsg = &m->mbox[m->nmsgs];
			cmsg->start = cpos;
			cmsg->status = 0;
			cmsg->size = strlen(line);
			while (fgets(line,LINELEN,ifile) != NULLCHAR) {
				if (*line == '\n') { /* done header part */
					cmsg->size++;
					putc(*line, m->mfile);
					break;
				}
				if (htype(line) == STATUS) {
					if (line[8] == 'R') 
						cmsg->status |= BM_READ;
					continue;
				}
				cmsg->size += strlen(line);
				if (fputs(line,m->mfile) == EOF) {
					tprintf("tmp file: %s",sys_errlist[errno]);
					(void) fclose(m->mfile);
					m->mfile = NULLFILE;
					return -1;
				}

			}
		} else if (cmsg) {
			cmsg->size += strlen(line);
			fputs(line,m->mfile);
		}
	}
	return 0;
}

/* list headers of a notesfile a message */
int
dolistnotes(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	register struct	let *cmsg;
	register char	*cp, *s;
	char	smtp_date[SLINELEN];
	char	smtp_from[SLINELEN];
	char	smtp_subject[SLINELEN]; 
	char	tstring[LINELEN];
	int	start, stop;
	long	size;

	m = (struct mbx *) p;
	if (m->mfile == NULLFILE) {
		tprintf(Nomail);
		return 0;
	}

	tprintf("Mail area: %s  %d message%s -  %d new\n\n",m->area,m->nmsgs,

		m->nmsgs == 1 ? " " : "s ", m->newmsgs);
	if(argc > 1)
		start = atoi(argv[1]);
	else
		start = 1;
	if(argc > 2)
		stop = atoi(argv[2]);
	else
		stop = m->nmsgs;
	if(stop > m->nmsgs)
		stop = m->nmsgs;
	if(start < 1 || start > stop) {
		tprintf("Invalid range.\n");
		return 0;
	}
	for (cmsg = &m->mbox[start]; start <= stop; start++, cmsg++) {
		*smtp_date = '\0';
		*smtp_from = '\0';
		*smtp_subject = '\0';
		fseek(m->mfile,cmsg->start,0);
		size = cmsg->size;
		while (size > 0 && fgets(tstring,sizeof(tstring),m->mfile)
		       != NULLCHAR) {
			if (*tstring == '\n')	/* end of header */
				break;
			size -= strlen(tstring);
			rip(tstring);
			/* handle continuation later */
			if (*tstring == ' '|| *tstring == '\t')
				continue;
			switch(htype(tstring)) {
			case FROM:
				if((cp = getname(tstring)) == NULLCHAR) {
					cp = &tstring[6];
					while(*cp && *cp != ' ' && *cp != '(')
						cp++;
					*cp = '\0';
					cp = &tstring[6];
				}
				sprintf(smtp_from,"%.30s",cp);
				break;
			case SUBJECT:
				sprintf(smtp_subject,"%.34s",&tstring[9]);
				break;
			case DATE:
				if ((cp = strchr(tstring,',')) == NULLCHAR)
					cp = &tstring[6];
				else
					cp++;
				/* skip spaces */
				while (*cp == ' ') cp++;
				if(strlen(cp) < 17)
					break; 	/* not a valid length */
				s = smtp_date;
				/* copy day */
				if (atoi(cp) < 10 && *cp != '0') {
					*s++ = ' ';
				} else
					*s++ = *cp++;
				*s++ = *cp++;

				*s++ = ' ';
				while (*cp == ' ')
					cp++;
				/* copy month */
				*s++ = *cp++;
				*s++ = *cp++;
				*s++ = *cp++;
				while (*cp == ' ')
					cp++;
				/* skip year */
				while (isdigit(*cp))
					cp++;
				/* copy time */
				*s++ = *cp++;	/* space */
				*s++ = *cp++;	/* hour */
				*s++ = *cp++;
				*s++ = *cp++;	/* : */
				*s++ = *cp++;	/* min */
				*s++ = *cp++;
				*s = '\0';
				break;
			case NOHEADER:
				break;
			}
		}
		tprintf("%c%c%c%3d %-27.27s %-12.12s %5ld %.25s\n",
		    (start == m->current ? '>' : ' '),
		    (cmsg->status & BM_DELETE ? 'D' : ' '),
		    (cmsg->status & BM_READ ? 'Y' : 'N'),
		    start, smtp_from, smtp_date,
		    cmsg->size, smtp_subject);
	}
	return 0;
}

#ifdef	notdef
/*  save msg on stream - if noheader set don't output the header */
static int
msgtofile(m,msg,tfile,noheader)
struct mbx *m;
int msg;
FILE *tfile;   /* already open for write */
int noheader;
{
	char	tstring[LINELEN];
	long 	size;

	if (m->mfile == NULLFILE) {
		tprintf(Nomail);
		return -1;
	}
	fseek(m->mfile,m->mbox[msg].start,0);
	size = m->mbox[msg].size;
	if ((m->mbox[msg].status & BM_READ) == 0) {
		m->mbox[msg].status |= BM_READ;
		m->change = 1;
	}

	if (noheader) {
		/* skip header */
		while (size > 0 && fgets(tstring,sizeof(tstring),m->mfile)
		       != NULLCHAR) {
			size -= strlen(tstring);
			if (*tstring == '\n')
				break;
		}
	}
	while (size > 0 && fgets(tstring,sizeof(tstring),m->mfile)
	       != NULLCHAR) {
		size -= strlen(tstring);
		fputs(tstring,tfile);
		if (ferror(tfile)) {
			tprintf("Error writing mail file\n");
			(void) fclose(tfile);
			return -1;
		}
	}
	return 0;
}
#endif

/*  dodelmsg - delete message in current notesfile */
int
dodelmsg(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	int msg,i;
	m = (struct mbx *) p;
	if (m->mfile == NULLFILE) {
		tprintf(Nomail);
		return 0;
	}
	for(i = 1; i < argc; ++i) {
		msg = atoi(argv[i]);
		if(msg < 0 || msg > m->nmsgs) {
			tprintf(Badmsg,msg);
			continue;
		}
		/* Check if we have permission to delete others mail */
		if(!(m->privs & FTP_WRITE) && strcmp(m->area,m->name)) {
			tprintf(Noperm);
			return 0;
		}
		m->mbox[msg].status |= BM_DELETE;
		m->change = 1;
	}
	return 0;
}
/* close the temp file while coping mail back to the mailbox */
int
closenotes(m)
struct mbx *m;
{
	register struct	let *cmsg;
	register char *line;
	char tstring[LINELEN], buf[256];
	long size;
	int i, nostatus = 0;
	FILE	*nfile;

	if (m->mfile == NULLFILE)
		return 0;
	/* Don't write back message areas others than your own unless you
	 * have SYSOP privs.
	 */
	if ((strcmp(m->name,m->area) && !(m->privs & SYSOP_CMD)) ||
	    !m->change) {
		(void) fclose(m->mfile);
		m->mfile = NULLFILE;
		m->mboxsize = 0;
		return 0;
	}
	/* If this area is a public message area, then we will not add a
	 * Status line to indicate that the message has been read.
	 */
	nfile = fopen(Arealist,READ_TEXT);
	if(nfile != NULLFILE) {
		while(fgets(buf,sizeof(buf),nfile) != NULLCHAR) {
			if((line = strchr(buf,' ')) == NULLCHAR)
				if((line = strchr(buf,'\t')) == NULLCHAR)
					continue;
			*line = '\0';
			if((line = strchr(buf,'\t')) != NULLCHAR)
				*line = '\0';
			if(stricmp(m->area,buf) == 0){	/* found it */
				/* pretend no messages were read */
				nostatus = 1;
				break;
			}
		}
		fclose(nfile);
	}

	line = tstring;
	scanmail(m);
	sprintf(buf,"%s/%s.txt",Mailspool,m->area);
	if ((nfile = fopen(buf,WRITE_TEXT)) == NULLFILE) {
		tprintf(Noaccess,buf);
		(void) fclose(m->mfile);
		m->mfile = NULLFILE;
		m->mboxsize = 0;
		return -1;
	}
	/* copy tmp file back to notes file */
	for (cmsg = &m->mbox[1],i = 1; i <= m->nmsgs; i++, cmsg++) {
		fseek(m->mfile,cmsg->start,0);
		size = cmsg->size;
		if ((cmsg->status & BM_DELETE))
			continue;
		/* copy the header */
		while (size > 0 && fgets(line,LINELEN,m->mfile) != NULLCHAR) {
			size -= strlen(line);
			if (*line == '\n') {
				if ((cmsg->status & BM_READ) != 0 && !nostatus)
					fprintf(nfile,"Status: R\n");
				fprintf(nfile,"\n");
				break;
			}
			fputs(line,nfile);
			pwait(NULL);
		}
		while (size > 0 && fgets(line,LINELEN,m->mfile) != NULLCHAR) {
			fputs(line,nfile);
			size -= strlen(line);
			pwait(NULL);
			if (ferror(nfile)) {
				tprintf("Error writing mail file\n");
				(void) fclose(nfile);
				(void) fclose(m->mfile);
				m->mfile = NULLFILE;
				m->mboxsize = 0;
				return -1;
			}
		}
	}
	m->nmsgs = 0;
	if (!strcmp(m->name,m->area))
		m->mysize = ftell(nfile); /* Update the size of our mailbox */
	/* remove a zero length file */
	if (ftell(nfile) == 0L)
		(void) unlink(buf);
	(void) fclose(nfile);
	(void) fclose(m->mfile);
	m->mfile = NULLFILE;
	m->mboxsize = 0;
	return 0;
}

static int
lockit(m)
struct mbx *m;
{
	int c;

	while(mlock(Mailspool,m->area)) {
		c = keywait("Mail file is busy, Abort or Retry ? ",0);
		if (c == 'A' || c == 'a' || c == EOF) {
			if (m->mfile != NULLFILE)
				(void) fclose(m->mfile);
			m->mfile = NULLFILE;
			return 1;
		}
	}
	return 0;
}

/* read the next message or the current one of new */
int
doreadnext(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	char buf[10], *newargv[2];
	m = (struct mbx *) p;
	if (m->mfile == NULLFILE)
		return 0;
	if ((m->mbox[m->current].status & BM_READ) != 0) {
		if (m->current == 1 && m->anyread == 0)
			;
		else if (m->current < m->nmsgs) {
			m->current++;
		} else {
			tprintf("Last message\n");
			return 0;
		}
	}
	sprintf(buf,"%d",m->current);
	newargv[1] = buf;
	m->anyread = 1;
	return doreadmsg(2,newargv,p);
}

/*  display message on the crt given msg number */
int
doreadmsg(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	int msg;
	register int c;
	register int col, lin;
	char	buf[MAXCOL+2];		/* line buffer */
	int	cnt, i, tcpcon;
	long 	size;
	struct usock *up;
	
	m = (struct mbx *) p;
	if (m->mfile == NULLFILE) {
		tprintf(Nomail);
		return 0;
	}
	if((up = itop(Curproc->output)) != NULLUSOCK && up->type == TYPE_TCP)
		tcpcon = 1;	/* This is a TCP (i.e. Telnet) connection */
	else
		tcpcon = 0;
	lin = MAXLIN-1;
	for(i = 1; i < argc; ++i) {
		msg = atoi(argv[i]);
		if( msg < 0 || msg > m->nmsgs) {
			tprintf(Badmsg,msg);
			return 0;
		}
		fseek(m->mfile,m->mbox[msg].start,0);
		size = m->mbox[msg].size;
		m->current = msg;
		
		tprintf("Message #%d %s\n", msg,
			m->mbox[msg].status & BM_DELETE ? "[Deleted]" : "");
		if ((m->mbox[msg].status & BM_READ) == 0) {
			m->mbox[msg].status |= BM_READ;
			m->change = 1;
			m->newmsgs--;
		}
		--lin;
		col = 0;
		while (!feof(m->mfile) && size > 0) {
			for (col = 0;  col < MAXCOL-1;) {
				c = getc(m->mfile);
				size--;
				if (feof(m->mfile) || size == 0) /* end this line */
					break;
				if (c == '\t') {
					cnt = col + 8 - (col & 7);
					if (cnt >= MAXCOL) /* end this line */
						break;
					while (col < cnt)
						buf[col++] = ' ';
				} else {
					if (c == '\n')
						break;
					buf[col++] = c;
				}
			}
			buf[col++] = '\n';
			buf[col] = '\0';
			tputs(buf);
			col = 0;
			if(tcpcon && --lin == 0){
				tprintf("--More--%c%c%c",IAC,WILL,TN_ECHO);
				usflush(Curproc->output);
				/* discard the response */
				while((c = recvchar(Curproc->input)) == IAC){
					c = recvchar(Curproc->input);
					if(c > 250 && c < 255)
						recvchar(Curproc->input);
				}    
				tprintf("%c%c%c\r%11s\r",IAC,WONT,TN_ECHO," ");
				usflush(Curproc->output);
				lin = MAXLIN-1;
				if(c == -1 || c == 'q' || c == 'Q') 
					break;
				if(c == '\n' || c == '\r')
					lin = 1;
			}
		}
	}
	return 0;
}

void
scanmail(m)		 /* Get any new mail */
struct mbx *m;
{
	FILE *nfile;
	int ret, cnt;
	char buf[256];

	if (!isnewmail(m))
		return;
	if(m->mfile == NULLFILE) { /* This is the first time */
		if(lockit(m))
			return;
		initnotes(m);
		rmlock(Mailspool,m->area);
		return;
	}
	sprintf(buf,"%s/%s.txt",Mailspool,m->area);
	if ((nfile = fopen(buf,READ_TEXT)) == NULLFILE)
		tprintf(Noaccess,buf);
	else {
		/* seek to end of old msgs */
		fseek(nfile,m->mboxsize,0);
		/* seek to end of tempfile */
		fseek(m->mfile,0L,2);
		cnt = m->nmsgs;
		ret = readnotes(m,nfile);   /* get the new mail */
		m->newmsgs += m->nmsgs - cnt;
		m->mboxsize = ftell(nfile);
		if(!strcmp(m->name,m->area))
			m->mysize = m->mboxsize;
		(void) fclose(nfile);
		if (ret != 0) {
			tprintf("Error updating mail file\n");
			return;
		}
	}
}
	
/* check the current mailbox to see if new mail has arrived.
* checks to see if the file has increased in size.
* returns true if new mail has arrived.
*/
static int
isnewmail(m)
struct mbx *m;
{
	char buf[256];
	sprintf(buf,"%s/%s.txt",Mailspool,m->area);
	if (fsize(buf) > m->mboxsize)
		return 1;
	return 0;
}

/* Check if we have any new mail in the private mail area */
int
isnewprivmail(m)
struct mbx *m;
{
	long cnt;
	char buf[256];
	sprintf(buf,"%s/%s.txt",Mailspool,m->name);
	if((cnt = fsize(buf)) > m->mysize) {
		m->mysize = cnt; /* not more than once */
		return 1;
	}
	return 0;
}

static struct token hd[] = {
	"Status: ", STATUS,
	"Status: ", STATUS,
	"Received: ", RECEIVED,
	"From: ", FROM,
	"To: ", TO,
	"Date: ", DATE,
	"Message-Id: ", MSGID,
	"Subject: ", SUBJECT,
	"Reply-To: ", REPLYTO,
	"Sender: ", SENDER,
	NULLCHAR, 0
};

/* return the header token type */
static int
htype(s)
char *s;
{
	register char *p;
	register struct token *hp;

	p = s;
	/* check to see if there is a ':' before and white space */
	while (*p != '\0' && *p != ' ' && *p != ':')
		p++;
	if (*p != ':')
		return NOHEADER;

	for (p = s, hp = hd; hp->str != NULLCHAR; hp++) {
		if (prefix(hp->str,p))
			return hp->type;
	}
	return UNKNOWN;
}

static int
prefix(pref,full)
register char *pref, *full;
{
	register char fc, pc;

	while ((pc = *pref++) != '\0') {
		fc = *full++;
		if (isupper(fc))
			fc = tolower(fc);
		if (isupper(pc))
			pc = tolower(pc);
		if (fc != pc)
			return 0;
	}
	return 1;
}

/* This function returns the length of a file. The proper thing would be
 * to use stat(), but it fails when using DesqView together with Turbo-C
 * code.
 */
static long
fsize(name)
char *name;
{
	long cnt = 0L;
	FILE *fp;
	if((fp = fopen(name,READ_TEXT)) == NULLFILE)
		return -1L;
	fseek(fp,0L,2);
	cnt = ftell(fp);
	fclose(fp);
	return cnt;
}
