/* SMTP Server state machine - see RFC 821
 * Very simple implementation; no forwarding allowed
 * (who wants to re-create "sendmail" ??)
 */
#include <stdio.h>
#include "machdep.h"
#include "mbuf.h"
#include "netuser.h"
#include "timer.h"
#include "tcp.h"
#include "smtp.h"
#ifdef LATTICE
#define tmpfile(x) tmpnam("testingxxxxxxxxxx")
#endif

/* Command table */
static char *commands[] = {
	"helo",
#define	HELO_CMD	0
	"noop",
#define	NOOP_CMD	1
	"mail from:",
#define	MAIL_CMD	2
	"quit",
#define	QUIT_CMD	3
	"rcpt to:",
#define	RCPT_CMD	4
	"help",
#define	HELP_CMD	5
	"data",
#define	DATA_CMD	6
	"rset",
#define	RSET_CMD	7
	NULLCHAR
};

/* Reply messages */
static char help[] = "214-Commands:\r\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET\r\n214 End\r\n";
static char banner[] = "220 %s SMTP Ready\r\n";
static char closing[] = "221 Closing\r\n";
static char ok[] = "250 Ok\r\n";
static char reset[] = "250 Reset state\r\n";
static char sent[] = "250 Sent\r\n";
static char ourname[] = "250 %s, Share and Enjoy!\r\n";
static char enter[] = "354 Enter mail, end with .\r\n";
static char ioerr[] = "452 Temp file write error\r\n";
static char mboxerr[] = "452 Mailbox %s write error\r\n";
static char badcmd[] = "500 Command unrecognized\r\n";
static char syntax[] = "501 Syntax error\r\n";
static char needrcpt[] = "503 Need RCPT (recipient)\r\n";
static char badname[] = "550 Can't open mailbox for %s\r\n";
static struct tcb *smtp_tcb;
/* Start up SMTP receiver service */
smtp_start(argc,argv)
int argc;
char *argv[];
{
	struct socket lsocket;
	void r_mail(),s_mail();

	lsocket.address = ip_addr;
	if(argc < 2)
		lsocket.port = SMTP_PORT;
	else
		lsocket.port = atoi(argv[1]);

	smtp_tcb = open_tcp(&lsocket,NULLSOCK,
		TCP_PASSIVE,0,r_mail,NULLVFP,s_mail,0,(int *)NULL);
}

/* Shutdown SMTP service (existing connections are allowed to finish) */
smtp_stop()
{
	if(smtp_tcb != NULLTCB)
		close_tcp(smtp_tcb);
}
/* SMTP connection state change upcall handler */
static void
s_mail(tcb,old,new)
struct tcb *tcb;
char old,new;
{
	struct mail *mp,*mail_create();

	switch(new){
#ifdef	QUICKSTART
	case SYN_RECEIVED:
#else
	case ESTABLISHED:
#endif
		if((mp = mail_create(tcb)) == NULLMAIL){
			close_tcp(tcb);
			break;
		}
		tprintf(mp->tcb,banner,hostname);
		log(tcb,"open SMTP");
		break;		
	case CLOSE_WAIT:
		mp = (struct mail *)tcb->user;
		mail_delete(mp);				
		close_tcp(tcb);
		break;
	case CLOSED:
		log(tcb,"close SMTP");
		del_tcp(tcb);
		/* Check if server is being shut down */
		if(tcb == smtp_tcb)
			smtp_tcb = NULLTCB;
		break;
	}
}

/* SMTP receiver upcall handler */
static void
r_mail(tcb,cnt)
struct tcb *tcb;
int16 cnt;
{
	register struct mail *mp;
	char *index(),*inet_ntoa(),c;
	struct mbuf *bp;
	void docommand(),deliver(),doline();

	if((mp = (struct mail *)tcb->user) == NULLMAIL){
		/* Unknown session */
		close_tcp(tcb);
		return;
	}
	recv_tcp(tcb,&bp,cnt);
	/* Assemble an input line in the session buffer.
	 * Return if incomplete
	 */
	while(pullup(&bp,&c,1) == 1){
		switch(c){
		case '\r':	/* Strip cr's */
			continue;
		case '\n':	/* Complete line; process it */
			mp->buf[mp->cnt] = '\0';
			doline(mp);
			break;
		default:	/* Assemble line */
			mp->buf[mp->cnt++] = c;
			break;
		}
	}
}
/* Process a line read on an SMTP connection (any state) */
static
void
doline(mp)
register struct mail *mp;
{
	switch(mp->state){
	case COMMAND_STATE:
		docommand(mp);
		break;
	case DATA_STATE:
		tcp_output(mp->tcb);	/* Send ACK; disk I/O is slow */
		if(mp->buf[0] == '.' && strlen(mp->buf) == 1){
			fprintf(mp->data,"\n");	/* Leave a blank line between msgs */

			mp->state = COMMAND_STATE;
			deliver(mp);	/* Also sends appropriate response */
			break;
		}
		/* Append to data file */
		if(fprintf(mp->data,"%s\n",mp->buf) < 0){
			mp->state = COMMAND_STATE;
			tprintf(mp->tcb,ioerr);
		}
		break;
	}
	mp->cnt = 0;
}
/* Create control block, initialize */
static struct mail *
mail_create(tcb)
register struct tcb *tcb;
{
	register struct mail *mp;
	char *calloc();

	if((mp = (struct mail *)calloc(1,sizeof (struct mail))) == NULLMAIL){
		return NULLMAIL;
	}
	mp->tcb = tcb;		/* Downward pointer */
	tcb->user = (int *)mp;	/* Upward pointer */
	return mp;
}

/* Free resources, delete control block */
static
mail_delete(mp)
register struct mail *mp;
{
	register struct addr *ap,*ap1;

	if(mp->system != NULLCHAR)
		free(mp->system);
	for(ap = mp->to;ap != NULLADDR;ap = ap1){
		if(ap->val != NULLCHAR)
			free(ap->val);
		ap1 = ap->next;
		free((char *)ap);
	}
	if(mp->data != NULLFILE)
		fclose(mp->data);
	free((char *)mp);
}

/* Parse and execute mail commands */
static
void
docommand(mp)
register struct mail *mp;
{
	char mailbox[50];
	char *cmd,*arg,*cp,*cp1,**cmdp;
	char *index(),*malloc(),*getname();
	struct tcb *tcb;
	struct addr *ap;
#ifdef LATTICE
	FILE *fp;
#else
	FILE *tmpfile(),*fp;
#endif
#ifdef	DATE
	long t;
	char *ctime();
#endif

	cmd = mp->buf;
	if(mp->cnt < 4){
		/* Can't be a legal SMTP command */
		tprintf(mp->tcb,badcmd);
		return;
	}	
	cmd = mp->buf;

	/* Translate entire buffer to lower case */
	for(cp = cmd;*cp != '\0';cp++)
		*cp = tolower(*cp);

	/* Find command in table; if not present, return syntax error */
	for(cmdp = commands;*cmdp != NULLCHAR;cmdp++)
		if(strncmp(*cmdp,cmd,strlen(*cmdp)) == 0)
			break;
	if(*cmdp == NULLCHAR){
		tprintf(mp->tcb,badcmd);
		return;
	}
	arg = &cmd[strlen(*cmdp)];
	/* Skip spaces after command */
	while(*arg == ' ')
		*arg++;
	/* Execute specific command */
	switch(cmdp-commands){
	case HELO_CMD:
		if((mp->system = malloc((unsigned)strlen(arg)+1)) == NULLCHAR){
			/* If the system is out of memory, just close */
			close_tcp(mp->tcb);
			mail_delete(mp);
			break;			
		} else {
			strcpy(mp->system,arg);
			tprintf(mp->tcb,ourname,hostname);
		}
		break;
	case NOOP_CMD:
	case MAIL_CMD:	/* Rather useless */
		tprintf(mp->tcb,ok);
		break;
	case QUIT_CMD:
		tprintf(mp->tcb,closing);
		close_tcp(mp->tcb);
		mail_delete(mp);
		break;
	case RCPT_CMD:	/* Specify recipient */
		if((cp = getname(arg)) == NULLCHAR){
			tprintf(mp->tcb,syntax);
			break;
		}
		/* Strip the @host part; all names must be local */
		if((cp1 = index(cp,'@')) != NULLCHAR)
			*cp1 = '\0';

		/* Check to see if we can open the mailbox */
		sprintf(mailbox,MAILSPOOL,cp);
#ifdef	AMIGA
		/* probably could use AMIGA's Lock()/Unlock(), but that
		   seems like overkill */
		if((fp = fopen(mailbox,"r")) == NULL){
#else
		if((fp = fopen(mailbox,"a+")) == NULL){
#endif
			tprintf(mp->tcb,badname,cp);
			break;
		}
		fclose(fp);
		/* Allocate an entry on the recipient list. This
		 * assembles the list backwards, but what the heck.
		 */
		if((ap = (struct addr *)malloc(sizeof(struct addr))) == NULLADDR){
			close_tcp(mp->tcb);
			mail_delete(mp);
			break;
		}
		if((ap->val = malloc((unsigned)strlen(mailbox)+1)) == NULLCHAR){
			free((char *)ap);
			close_tcp(mp->tcb);
			mail_delete(mp);
			break;
		}
		strcpy(ap->val,mailbox);
		ap->next = mp->to;
		mp->to = ap;
		tprintf(mp->tcb,ok);
		break;
	case HELP_CMD:
		tprintf(mp->tcb,help);
		break;
	case DATA_CMD:
		if(mp->to == NULLADDR){
			tprintf(mp->tcb,needrcpt);
			break;
		}
		tcp_output(mp->tcb);	/* Send ACK; disk I/O is slow */
		if((mp->data = tmpfile()) == NULLFILE){
			tprintf(mp->tcb,ioerr);
			break;
		}
#ifdef	DATE
		/* Add timestamp; ctime adds newline */
		time(&t);
		if(mp->system != NULLCHAR)
			fprintf(mp->data,
				"Received: from %s by %s\n\twith SMTP ; %s",
				mp->system,
				hostname,
				ctime(&t));
		else
			fprintf(mp->data,"Received: %s\n",ctime(&t));
#endif
		if(ferror(mp->data)){
			tprintf(mp->tcb,ioerr);
		} else {
			mp->state = DATA_STATE;
			tprintf(mp->tcb,enter);
		}
		break;
	case RSET_CMD:
		tcb = mp->tcb;
		mail_delete(mp);
		if((mp = mail_create(tcb)) == NULLMAIL){
			close_tcp(tcb);
		} else {
			mp->state = COMMAND_STATE;
			tprintf(mp->tcb,reset);
		}
		break;
	}
}
/* Given a string of the form <user@host>, extract the part inside the
 * brackets and return a pointer to it.
 */
static
char *
getname(cp)
register char *cp;
{
	char *cp1;
	char *index(),*strncpy();

	if((cp = index(cp,'<')) == NULLCHAR){
		return NULLCHAR;
	}
	cp++;	/* cp -> first char of name */
	if((cp1 = index(cp,'>')) == NULLCHAR){
		return NULLCHAR;
	}
	*cp1 = '\0';
	return cp;
}
/* Deliver mail to the appropriate mail boxes and delete temp file */
static
void
deliver(mp)
register struct mail *mp;
{
	char *index();
	int c;
	register struct addr *ap;
	register FILE *fp;

	for(ap = mp->to;ap != NULLADDR;ap = ap->next){
		fseek(mp->data,0L,0);	/* rewind */
		fp = fopen(ap->val,"a+");
		while((c = getc(mp->data)) != EOF){
			if(putc(c,fp) == EOF)
				break;
		}
		if(ferror(fp)){
			fclose(fp);
			tprintf(mp->tcb,mboxerr,ap->val);
			fclose(mp->data);
			mp->data = NULLFILE;
			return;
		}
		fclose(fp);
	}
	tprintf(mp->tcb,sent);
	fclose(mp->data);
	mp->data = NULLFILE;
}
