/* smtpcli.c
 *	Client routines for Simple Mail Transfer Protocol ala RFC821
 *	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.
 *	Modified 14 June 1987 by P. Karn for symbolic target addresses,
 *	also rebuilt locking mechanism
 *	Limit on max simultaneous sessions, reuse of connections - 12/87 NN2Z
 */
#include <stdio.h>
#if (!ATARI_ST || LATTICE)	/* DG2KK */
#include <fcntl.h>
#endif
#include "global.h"
#include "netuser.h"
#include "mbuf.h"
#include "timer.h"
#include "tcp.h"
#include "smtp.h"
#include "trace.h"
#include "cmdparse.h"

extern int16 lport;			/* local port placeholder */
extern int32 resolve();
static struct timer smtpcli_t;
int32 gateway;

#ifdef SMTPTRACE
int16	smtptrace = 0;			/* used for trace level */
int dosmtptrace();
#endif

int16	smtpmaxcli  = MAXSESSIONS;	/* the max client connections allowed */
int16	smtpcli = 0;			/* number of client connections
					* currently open */

static struct smtp_cb *cli_session[MAXSESSIONS]; /* queue of client sessions  */

int dosmtptick(),dogateway(),dosmtpmaxcli(),mlock(),dotimer();
void quit(),abort_trans(),rejextjob(),sendit(),del_session(),del_job();
void rejectjob(),execjobs(),smtp_transaction();
struct smtp_cb *newcb(),*lookup();
struct smtp_job *setupjob();

struct cmds smtpcmds[] = {
	"gateway",	dogateway,	0,	NULLCHAR,	NULLCHAR,
	"kick", 	dosmtptick,	0,	NULLCHAR,	NULLCHAR,
	"maxclients",	dosmtpmaxcli,	0,	NULLCHAR,	NULLCHAR,
	"timer",	dotimer,	0,	NULLCHAR,	NULLCHAR,
#ifdef SMTPTRACE
	"trace",	dosmtptrace,	0,	NULLCHAR,	NULLCHAR,
#endif
	NULLCHAR,	NULLFP, 	0,	
	"subcommands: gateway kick maxclients timer trace",
		NULLCHAR,
};

dosmtp(argc,argv)
int argc;
char *argv[];
{
	return subcmd(smtpcmds,argc,argv);
}

static int
dosmtpmaxcli(argc,argv)
int argc;
char *argv[];
{
	int x;
	if (argc < 2)
		printf("%d\n",smtpmaxcli);
	else {
		x = atoi(argv[1]);
		if (x > MAXSESSIONS)
			printf("max clients must be <= %d\n",MAXSESSIONS);
		else
			smtpmaxcli = x;
	}
	return 0;
}

static int
dogateway(argc,argv)
int argc;
char *argv[];
{
	char *inet_ntoa();
	int32 n;
	extern char badhost[];

	if(argc < 2){
		printf("%s\n",inet_ntoa(gateway));
	} else if((n = resolve(argv[1])) == 0){
		printf(badhost,argv[1]);
		return 1;
	} else
		gateway = n;
	return 0;
}

#ifdef SMTPTRACE
static int
dosmtptrace(argc,argv)
int argc;
char *argv[];
{
	if (argc < 2)
		printf("%d\n",smtptrace);
	else 
		smtptrace = atoi(argv[1]);
	return 0;
}
#endif

/* Set outbound spool poll interval */
static int
dotimer(argc,argv)
int argc;
char *argv[];
{
	int dosmtptick();

	if(argc < 2){
		printf("%d/%d\n",smtpcli_t.start - smtpcli_t.count,
		smtpcli_t.start);
		return 0;
	}
	smtpcli_t.func = (void (*)())dosmtptick;/* what to call on timeout */
	smtpcli_t.arg = NULLCHAR;		/* dummy value */
	smtpcli_t.start = atoi(argv[1]);	/* set timer duration */
	start_timer(&smtpcli_t);		/* and fire it up */
	return 0;
}

/* this is the routine that gets called every so often to do outgoing mail
   processing */
int
dosmtptick()
{
	register struct smtp_cb *cb;
	char	tmpstring[LINELEN], wfilename[13], prefix[9];
	char	from[LINELEN], to[LINELEN];
	char *cp, *cp1;
	int32 destaddr;
	FILE *wfile;

#ifdef SMTPTRACE
	if (smtptrace > 5) {
		printf("smtp daemon entered\n");
		fflush(stdout);
	}
#endif
	for(filedir(mailqueue,0,wfilename);wfilename[0] != '\0';
		filedir(mailqueue,1,wfilename)){

		/* save the prefix of the file name which it job id */
		cp = wfilename;
		cp1 = prefix;
		while (*cp && *cp != '.')
			*cp1++ = *cp++;
		*cp1 = '\0';

		/* lock this file from the smtp daemon */
		if (mlock(mailqdir,prefix))
			continue;

		sprintf(tmpstring,"%s%s",mailqdir,wfilename);
		if ((wfile = fopen(tmpstring,"r")) == NULLFILE) {
			/* probably too many open files */
			(void) rmlock(mailqdir,prefix);
			/* continue to next message. The failure
			* may be temporary */
			continue;
		}

		fgets(tmpstring,LINELEN,wfile); /* read target host */
		rip(tmpstring);
		fgets(from,LINELEN,wfile);	/* read target host */
		rip(from);
		fgets(to,LINELEN,wfile);	/* read target user */
		rip(to);
		fclose(wfile);

		if ((destaddr = mailroute(tmpstring)) == 0) {
			printf("** smtpcli: Unknown address %s\n",tmpstring);
			fflush(stdout);
			(void) rmlock(mailqdir,prefix);
			continue;
		}

		if ((cb = lookup(destaddr)) == NULLCB) {
			/* there are enough processes running already */
			if (smtpcli >= smtpmaxcli) {
#ifdef SMTPTRACE
				if (smtptrace) {
					printf("smtp daemon: too many processes\n");
					fflush(stdout);
				}
#endif
					
				(void) rmlock(mailqdir,prefix);
				break;
			}
			if ((cb = newcb()) == NULLCB) {
				(void) rmlock(mailqdir,prefix);
				break;
			} 
			cb->ipaddr = destaddr;
		} else {
			/* This system is already is sending mail lets not
			* interfere with its send queue.
			*/
			if (cb->state != CLI_IDLE) {
				(void) rmlock(mailqdir,prefix);
				continue;
			}
		}
#ifdef SMTPTRACE
		if (smtptrace > 1) {
			printf("queue job %s To: %s From: %s\n",prefix,to,from);
			fflush(stdout);
		}
#endif

		if (setupjob(cb,prefix,to,from) == NULLJOB) {
			(void) rmlock(mailqdir,prefix);
			del_session(cb);
			break;
		}
	}

	/* start sending that mail */
	execjobs();

	/* Restart timer */
	start_timer(&smtpcli_t);
#ifdef SMTPTRACE
	if (smtptrace > 5) {
		printf("smtp daemon done\n");
		fflush(stdout);
	}
#endif
}

/* this is the master state machine that handles a single SMTP transaction */
void
smtp_transaction(cb)
struct smtp_cb *cb;
{
	void smtp_cts();
	char reply;
	int rcode;

#ifdef SMTPTRACE
	if (smtptrace > 7) 
		printf("smtp_transaction() enter state=%u\n",cb->state);
	if (smtptrace) {
		printf("%s\n",cb->buf);
		fflush(stdout);
	}
#endif
	/* Another line follows; ignore this one */
	if(cb->buf[0] == '0' || cb->buf[3] == '-')
		return;

	reply = cb->buf[0];
	rcode = atoi(cb->buf);

	/* if service shuting down */
	if (rcode == 421) {
		quit(cb);
		return;
	}

	switch(cb->state) {
	case CLI_OPEN_STATE:
		if (reply != '2')
			quit(cb);
		else {
			cb->state = CLI_HELO_STATE;
			sendit(cb,"HELO %s\r\n",hostname);
		}
		break;
	case CLI_HELO_STATE:
		if (reply != '2')
			quit(cb);
		else {
			cb->state = CLI_MAIL_STATE;
			/* send both to speed things up */
			sendit(cb,"MAIL FROM:<%s>\r\nRCPT TO:<%s>\r\n",
				cb->jobq->from,cb->jobq->to);
		}
		break;			
	case CLI_MAIL_STATE:
		if (reply != '2')
			quit(cb);
		else {
			cb->state = CLI_RCPT_STATE;
			/* the RCPT is sent already */
		}
		break;
	case CLI_RCPT_STATE:
		if (reply == '5') {
			rejectjob(cb);
			abort_trans(cb);
		} else if (reply != '2')
			abort_trans(cb);
		else {
			/* open text file here because it will be too
			* late to abort in the data state.
			*/
			/* if this file open fails abort */
			if ((cb->tfile = fopen(cb->tname,"r")) == NULLFILE)
				abort_trans(cb);
			else {
				cb->state = CLI_DATA_STATE;
				sendit(cb,"DATA\r\n");
			}
		}
		break;
	case CLI_DATA_STATE:
		if (reply != '3')
			abort_trans(cb);
		else {
			cb->state = CLI_SEND_STATE;
			/* Kick the data transfer to get it started */
			smtp_cts(cb->tcb,cb->tcb->window - cb->tcb->sndcnt);
		}
		break;
	case CLI_SEND_STATE:
		/* the transmitter upcall routine will advance the
		   state pointer on end of file, so we do nada... */
		break;
	case CLI_UNLK_STATE:
		if (reply == '5') {
			rejectjob(cb);
			abort_trans(cb);
		} else if (reply != '2')
			abort_trans(cb);
		else {
			unlink(cb->wname);	/* unlink workfile */
			/* close and unlink the textfile */
			if(cb->tfile != NULLFILE) {
				fclose(cb->tfile);
				cb->tfile = NULLFILE;
			}
			unlink(cb->tname);
			abort_trans(cb);
		}
		break;
	case CLI_QUIT_STATE:
		close_tcp(cb->tcb); /* close up connection */
		break;
	}
}

/* abort the currrent job. Remove the lockfile.
 * If more work exists set up the next job if
 * not then shut down.
*/
static void
abort_trans(cb)
struct smtp_cb *cb;
{
	if(cb->tfile != NULLFILE) {
		fclose(cb->tfile);
		cb->tfile = NULLFILE;
	}
	(void) rmlock(mailqdir,cb->jobq->jobname);
	if (nextjob(cb)) {
		sendit(cb,"RSET\r\n");
		cb->state = CLI_HELO_STATE;
	} else {
		sendit(cb,"QUIT\r\n");		/* issue a quit command */
		cb->state = CLI_QUIT_STATE;
	}
}

/* close down link after a failure */
static void
quit(cb)
struct smtp_cb *cb;
{
	cb->state = CLI_QUIT_STATE;
	sendit(cb,"QUIT\r\n");		/* issue a quit command */
}

/* smtp receiver upcall routine.  fires up the state machine to parse input */
static
void
smtp_rec(tcb,cnt)
struct tcb *tcb;
int16 cnt;
{
	register struct smtp_cb *cb;
	char *inet_ntoa(), c;
	struct mbuf *bp;

#ifdef SMTPTRACE
	if (smtptrace > 5)  {
		printf("smtp_rec called\n");
		fflush(stdout);
	}
#endif
	cb = (struct smtp_cb *)tcb->user;	/* point to our struct */
	recv_tcp(tcb,&bp,cnt);	/* suck up chars from low level routine */

	/* Assemble input line in buffer, return if incomplete */
	while(pullup(&bp,&c,1) == 1) {
		switch(c) {
		case '\r':	/* strip cr's */
			continue;
		case '\n':	/* line is finished, go do it! */
			cb->buf[cb->cnt] = '\0';
			smtp_transaction(cb);
			cb->cnt = 0;
			break;
		default:	/* other chars get added to buffer */
			if(cb->cnt != LINELEN-1)
				cb->buf[cb->cnt++] = c;
			break;
		}
	}
}

/* smtp transmitter ready upcall routine.  twiddles cts flag */
static 
void
smtp_cts(tcb,cnt)
struct tcb *tcb;
int16 cnt;
{
	register struct smtp_cb *cb;
	struct mbuf *bp;
	char *cp;
	int c;

#ifdef SMTPTRACE
	if (smtptrace > 5) {
		printf("smtp_cts called avail %d\n",cnt);
		fflush(stdout);
	}
#endif
	cb = (struct smtp_cb *)tcb->user;	/* point to our struct */

	/* don't do anything until/unless we're supposed to be sending */
	if(cb->state != CLI_SEND_STATE)
		return;

	if((bp = alloc_mbuf(cnt)) == NULLBUF){
		/* Hard to know what to do here */
		return;
	}
	cp = bp->data;
	while(cnt > 1 && (c = getc(cb->tfile)) != EOF){
#if	(ATARI_ST)
		if (c == '\n') {
			*cp++ = '\r';
			bp->cnt++;
			cnt--;
		}
#endif

		*cp++ = c;
		bp->cnt++;
		cnt--;
	}
	if(bp->cnt != 0)
		send_tcp(tcb,bp);
	else
		free_p(bp);

	if(cnt > 1){	/* EOF seen */
		sendit(cb,"\r\n.\r\n");
		cb->state = CLI_UNLK_STATE;
	}
}

/* smtp state change upcall routine. */
static
void
smtp_state(tcb,old,new)
struct tcb *tcb;
char old,new;
{
	register struct smtp_cb *cb;
	extern char *tcpstates[];

#ifdef SMTPTRACE
	if (smtptrace > 5) {
		printf("smtp_state called: %s\n",tcpstates[new]);
		fflush(stdout);
	}
#endif
	cb = (struct smtp_cb *)tcb->user;
	switch(new) {
	case ESTABLISHED:
		cb->state = CLI_OPEN_STATE;	/* shouldn't be needed */
		break;
	case CLOSE_WAIT:
		close_tcp(tcb); 		/* shut things down */
		break;
	case CLOSED:
		/* if this close was not done by us ie. a RST */
		if (cb->state != CLI_QUIT_STATE) {
			if(cb->tfile != NULLFILE)
				fclose(cb->tfile);
		}
		del_session(cb);
		del_tcp(tcb);
		break;
	}
}

/* Send message back to server */
/*VARARGS*/
static void
sendit(cb,fmt,arg1,arg2)
struct smtp_cb *cb;
char *fmt,*arg1,*arg2;
{
	struct mbuf *bp,*qdata();
	char tmpstring[256];

#ifdef SMTPTRACE
	if (smtptrace) {
		printf(">>> ");
		printf(fmt,arg1,arg2);
		fflush(stdout);
	}
#endif
	sprintf(tmpstring,fmt,arg1,arg2);
	bp = qdata(tmpstring,(int16)strlen(tmpstring));
	send_tcp(cb->tcb,bp);
}

/* create mail lockfile */
int
mlock(dir,id)
char *dir,*id;
{
	char lockname[LINELEN];
	int fd;
	/* Try to create the lock file in an atomic operation */
	sprintf(lockname,"%s%s.lck",dir,id);
#if (ATARI_ST && !LATTICE)	/* DG2KK */
	if(!access(lockname,0) || (fd = creat(lockname, 0666)) == -1)
		return -1;
#else
	if((fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT, 0666)) == -1)
		return -1;
#endif
	close(fd);
	return 0;
}

/* remove mail lockfile */
int
rmlock(dir,id)
char *dir,*id;
{
	char lockname[LINELEN];
	sprintf(lockname,"%s%s.lck",dir,id);
	return(unlink(lockname));
}

/* free the message struct and data */
static void
del_session(cb)
register struct smtp_cb *cb;
{
	register int i;
	register struct smtp_job *jp,*tp;

	if (cb == NULL)
		return;
	for(i=0; i<MAXSESSIONS; i++) 
		if(cli_session[i] == cb) {
			cli_session[i] = NULLCB;
			break;
		}

	if(cb->wname != NULLCHAR)
		free(cb->wname);
	if(cb->tname != NULLCHAR)
		free(cb->tname);
	for (jp = cb->jobq; jp != NULLJOB;jp = tp) {
			tp = jp->next;
			(void) rmlock(mailqdir,jp->jobname);
			del_job(jp);
	}
	free((char *)cb);
	smtpcli--;	/* number of connections active */
}

void
del_job(jp)
register struct smtp_job *jp;
{
	if(jp->to != NULLCHAR)
		free(jp->to);
	if(jp->from != NULLCHAR)
		free(jp->from);
	free((char *)jp);
}

/* move a bad job out of the send queue into a holding area */
static void
rejectjob(cb)
struct smtp_cb *cb;
{
	char dest[LINELEN];
#ifdef SMTPTRACE
	if (smtptrace > 5) {
		printf("smtp job %s rejected\n",cb->wname);
		fflush(stdout);
	}
#endif
	/* remove job from queue and save for admin */
	sprintf(dest,"%s%s.txt",baddir,cb->jobq->jobname);
	(void) rename(cb->tname,dest);
	strcpy(rindex(dest,'.'),".wrk");
	(void) rename(cb->wname,dest);
	(void) rmlock(mailqdir,cb->jobq->jobname);
}

/* look to see if a smtp control block exists for this ipaddr */
static struct smtp_cb *
lookup(destaddr)
int32 destaddr;
{
	register int i;

	for(i=0; i<MAXSESSIONS; i++) {
		if (cli_session[i] == NULLCB)
			continue;
		if (cli_session[i]->ipaddr == destaddr)
			return cli_session[i];
	}
	return NULLCB;
}

/* create a new  smtp control block */
static struct smtp_cb *
newcb()
{
	register int i;
	struct smtp_cb *cb;

	for(i=0; i<MAXSESSIONS; i++) {
		if(cli_session[i] == NULLCB) {
			cb = (struct smtp_cb *)calloc(1,sizeof(struct smtp_cb));
			if (cb == NULLCB)
				return(NULLCB);
			cb->wname = malloc((unsigned)strlen(mailqdir) + JOBNAME);
			if (cb->wname == NULLCHAR) {
				free((char *)cb);
				return(NULLCB);
			}
			cb->tname = malloc((unsigned)strlen(mailqdir) + JOBNAME);
			if (cb->tname == NULLCHAR) {
				free(cb->wname);
				free((char *)cb);
				return(NULLCB);
			}
			cb->state = CLI_IDLE;
			cli_session[i] = cb;
			smtpcli++;	/* number of connections active */
			return(cb);
		}
	}
	return NULLCB;
}

static void
execjobs()
{
	struct socket lsocket, fsocket;
	void smtp_rec(), smtp_cts(), smtp_state();
	register struct smtp_cb *cb;
	int i;

	for(i=0; i<MAXSESSIONS; i++) {
		cb = cli_session[i];
		if (cb == NULLCB) 
			continue;
		if(cb->state != CLI_IDLE)
			continue;

		sprintf(cb->tname,"%s%s.txt",mailqdir,cb->jobq->jobname);
		sprintf(cb->wname,"%s%s.wrk",mailqdir,cb->jobq->jobname);

		/* setup the socket */
		fsocket.address = cb->ipaddr;
		fsocket.port = SMTP_PORT;
		lsocket.address = ip_addr;	/* our ip address */
		lsocket.port = lport++; 	/* next unused port */
#ifdef SMTPTRACE
		if (smtptrace) {
			printf("Trying Connection to %s\n",inet_ntoa(fsocket.address));
			fflush(stdout);
		}
#endif

		/* open smtp connection */
		cb->state = CLI_OPEN_STATE;	/* init state placeholder */
		cb->tcb = open_tcp(&lsocket,&fsocket,TCP_ACTIVE,tcp_window,
			smtp_rec,smtp_cts,smtp_state,0,(char *)cb);
		cb->tcb->user = (char *)cb;	/* Upward pointer */
	}
}
	
/* add this job to control block queue */
struct smtp_job *
setupjob(cb,id,to,from)
struct smtp_cb *cb;
char *id,*to,*from;
{
	register struct smtp_job *p1,*p2;

	p1 = (struct smtp_job *)calloc(1,sizeof(struct smtp_job));
	if (p1 == NULLJOB)
		return NULLJOB;
	p1->to = malloc((unsigned)strlen(to) + 1);
	if (p1->to == NULLCHAR) {
		free((char *)p1);
		return NULLJOB;
	}
	p1->from = malloc((unsigned)strlen(from) + 1);
	if (p1->from == NULLCHAR) {
		free(p1->to);
		free((char *)p1);
		return NULLJOB;
	}
	strcpy(p1->to,to);
	strcpy(p1->from,from);
	strcpy(p1->jobname,id);
	if ((p2 = cb->jobq) == NULLJOB)
		cb->jobq = p1;
	else {
		while(p2->next != NULLJOB)
			p2 = p2->next;
		p2->next = p1;
	}
	return p1;
}

/* called to advance to the next job */
static int
nextjob(cb)
struct smtp_cb *cb;
{
	struct smtp_job *jp;
	jp = cb->jobq->next;
	del_job(cb->jobq);
	if (jp == NULLJOB) {
		cb->jobq = NULLJOB;
		return 0;
	}
	cb->jobq = jp;
	sprintf(cb->tname,"%s%s.txt",mailqdir,cb->jobq->jobname);
	sprintf(cb->wname,"%s%s.wrk",mailqdir,cb->jobq->jobname);
#ifdef SMTPTRACE
	if (smtptrace > 5) {
		printf("sending %s\n",cb->jobq->jobname);
		fflush(stdout);
	}
#endif
		return 1;

}


/* mail routing funtion. For now just used the hosts file */
int32
mailroute(dest)
char *dest;
{
	int32 destaddr;

	/* look up address or use the gateway */
	if ((destaddr = resolve(dest)) == 0)
		if (gateway != 0) 
			destaddr = gateway; /* Use the gateway	*/
	return destaddr;
	
}

