/*
 *	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
 *	Added return of mail to sender as well as batching of commands 1/88 nn2z
 */

#include <stdio.h>
#ifndef ATARI_ST
#include <fcntl.h>
#endif
#include <time.h>
#ifdef UNIX
#include <sys/types.h>
#endif
#include "global.h"
#include "config.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 */
extern int32 resolve();
static struct timer smtpcli_t;		/* client startup timer */
static int32 queuetime = SEC2TICK(500); /* kicktime for queued mail */
int32 gateway;

#ifdef SMTPTRACE
int16	smtptrace = 0;			/* used for trace level */
int dosmtptrace();
#endif

int16	smtpmaxcli  = MAXSESSIONS;	/* the max client connections allowed */
int16	smtpsessions = 0;		/* number of client connections
					 * currently open */
int16	smtpmode = 0;

static struct smtp_cb *cli_session[MAXSESSIONS]; /* queue of client sessions  */

static char helocmd[] = "HELO %s\r\nMAIL FROM:<%s>\r\n";
static char mailcmd[] = "MAIL FROM:<%s>\r\n";
static char rcptcmd[] = "RCPT TO:<%s>\r\n";
static char datacmd[] = "DATA\r\n";
static char rsetcmd[] = "RSET\r\n";
static char quitcmd[] = "QUIT\r\n";
static char eom[] = "\r\n.\r\n";

static int smtptick(),dogateway(),dosmtpmaxcli(),dotimer();
static int setsmtpmode(),sendwindow();
static void quit(),abort_trans(),sendit(),del_job();
static void execjobs(),smtp_transaction();
static struct smtp_cb *newcb(),*lookup();
static struct smtp_job *setupjob();
void del_session(),logerr(),retmail();
int mlock(),rmlock(),nextjob();
extern void mbxfwd();

extern char *getnenv();

#if (defined(ATARI_ST) && defined(__TURBOC__))
static void sendit(struct smtp_cb *,char *,...);
#endif

struct cmds smtpcmds[] = {
	"gateway",	dogateway,	0,	NULLCHAR,	NULLCHAR,
	"mode",		setsmtpmode,	0,	NULLCHAR,	NULLCHAR,
	"kick",		smtptick,	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",
		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
setsmtpmode(argc,argv)
int argc;
char *argv[];
{
	if (argc < 2) {
		printf("smtp mode: %s\n",
			(smtpmode & QUEUE) ? "queue" : "route");
	} else {
		switch(*argv[1]) {
		case 'q':
			smtpmode |= QUEUE;
			break;
		case 'r':
			smtpmode &= ~QUEUE;
			break;
		default:
			printf("Usage: smtp mode [queue | route]\n");
			break;
		}
	}
	return 0;
}
static int
dogateway(argc,argv)
int argc;
char *argv[];
{
	extern char badhost[];

	if(argc < 2){
		printf("%s\n",inet_ntoa(gateway));
	} else if((gateway = resolve(argv[1])) == 0 && argv[1][0] != '['){
		printf(badhost,argv[1]);
		return 1;
	}
	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[];
{
	if(argc < 2){
		printf("%lu/%lu (%lu)\n",TICK2SEC(smtpcli_t.start - smtpcli_t.count),
					 TICK2SEC(smtpcli_t.start),
					 TICK2SEC(queuetime));
		return 0;
	}
	stop_timer(&smtpcli_t);
	smtpcli_t.func = (void (*)())smtptick;	/* what to call on timeout */
	smtpcli_t.start = SEC2TICK(atol(argv[1])); /* set timer duration */
	start_timer(&smtpcli_t);		/* and fire it up */
	if (argc > 2)				/* queuetime specified? */
		queuetime = SEC2TICK(atol(argv[2]));
	return 0;
}
/* kick the timer when a job is queued */
void
queuekick()
{
	int32 savedstart;

	if (queuetime == 0)			/* no queue'd job kick? */
		return;

	savedstart = smtpcli_t.start;		/* keep normal smtp timer */
	stop_timer(&smtpcli_t);
	smtpcli_t.func = (void (*)())smtptick;
	smtpcli_t.start = queuetime;		/* set temporary timer value */
	start_timer(&smtpcli_t);		/* and fire it up */
	smtpcli_t.start = savedstart;		/* restore full time value */
}

/* this is the routine that gets called every so often to do outgoing mail
   processing */
static int
smtptick()
{
	register struct smtp_cb *cb;
	struct smtp_job *jp;
#ifdef SMTPTRACE
	struct list *ap;
#endif
	char	*mailqdir;
	char	*cp, *cp1;
	int32	destaddr;
	FILE	*wfile;
	char	sysname[LINELEN], tmpstring[LINELEN];
	char	wfilename[13], prefix[9];
	char	mailq[80];

#ifdef SMTPTRACE
	if (smtptrace > 5) {
		printf("smtp daemon entered\n");
		fflush(stdout);
	}
#endif
	strcpy(mailq,mailqdir = getnenv(MAILQDIR));
	strcat(mailq,mailqueue);
	for(filedir(mailq,0,wfilename);wfilename[0] != '\0';
		filedir(mailq,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 */
			rmlock(mailqdir,prefix);
			/* continue to next message. The failure
			 * may be temporary */
			continue;
		}

		fgets(sysname,LINELEN,wfile);	/* read target host */
		rip(sysname);

		if ((destaddr = mailroute(sysname)) == 0) {
			fclose(wfile);
			printf("** smtp: Unknown address %s\n",sysname);
			fflush(stdout);
			continue;
		}

		if ((cb = lookup(destaddr)) == NULLCB) {
			/* there are enough processes running already */
			if (smtpsessions >= smtpmaxcli) {
#ifdef SMTPTRACE
				if (smtptrace) {
					printf("smtp daemon: too many processes\n");
					fflush(stdout);
				}
#endif
				fclose(wfile);
				rmlock(mailqdir,prefix);
				continue;
			}
			if ((cb = newcb(tmpstring)) == NULLCB) {
				fclose(wfile);
				rmlock(mailqdir,prefix);
				continue;
			}
			cb->ipdest = destaddr;
		} else {
			/* This system already is sending mail lets not
			* interfere with it's send queue.
			*/
			if (cb->state != CLI_INIT_STATE && cb->state != CLI_OPEN_STATE) {
				fclose(wfile);
				rmlock(mailqdir,prefix);
				continue;
			}
		}

		fgets(tmpstring,LINELEN,wfile); /* read from */
		rip(tmpstring);
		if ((jp = setupjob(cb,prefix,sysname,tmpstring)) == NULLJOB) {
			fclose(wfile);
			rmlock(mailqdir,prefix);
			del_session(cb);
			continue;
		}
		while (fgets(tmpstring,LINELEN,wfile) != NULLCHAR) {
			rip(tmpstring);
			if (addlist(&jp->to,tmpstring,DOMAIN) == NULLLIST) {
				fclose(wfile);
				del_session(cb);
			}
		}
		fclose(wfile);
#ifdef SMTPTRACE
		if (smtptrace > 1) {
			printf("queue job %s From: %s To:",prefix,from);
			for (ap = jp->to; ap != NULLLIST; ap = ap->next)
				printf(" %s",ap->val);
			printf("\n");
			fflush(stdout);
		}
#endif
	}

	/* start sending that mail */
	execjobs();

	/* Restart timer */
	start_timer(&smtpcli_t);
}

/* this is the master state machine that handles a single SMTP transaction */
static void
smtp_transaction(cb)
register struct smtp_cb *cb;
{
	void smtp_cts();
	register char reply;
	register struct list *tp;
	int cnt;
	struct mbuf *bp,*bpl;
	char tbuf[LINELEN];
	int rcode;

#ifdef SMTPTRACE
	if (smtptrace > 5)
		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;

	/* Got a response - stop the timeout timer */
	stop_timer(&cb->tcb->timeout);

	reply = cb->buf[0];
	rcode = atoi(cb->buf);

	/* if service shutting 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,helocmd,hostname,cb->jobq->from);
		}
		break;
	case CLI_HELO_STATE:
		if (reply != '2')
			quit(cb);
		else {
			cb->state = CLI_MAIL_STATE;
			start_timer(&cb->tcb->timeout); /* prevent lockup */
		}
		break;
	case CLI_MAIL_STATE:
		if (reply != '2')
			quit(cb);
		else {
			cb->state = CLI_RCPT_STATE;
			cb->rcpts = 0;
			bpl = NULLBUF;
			for (tp = cb->jobq->to; tp != NULLLIST; tp = tp->next){
				sprintf(tbuf,rcptcmd,tp->val);
				if ((bp = qstring(tbuf)) == NULLBUF) {
					free_p(bpl);
					quit(cb);
					return;
				}
				append(&bpl,bp);
				cb->rcpts++;
#ifdef SMTPTRACE
				if (smtptrace) {
					printf(">>> %s",tbuf);
					fflush(stdout);
				}
#endif
			}
			send_tcp(cb->tcb,bpl);
		}
		break;
	case CLI_RCPT_STATE:
		if (reply == '5') {
			logerr(cb);
		} else if (reply == '2') {
			cb->goodrcpt = 1;
		} else {
			/* some kind of temporary failure */
			abort_trans(cb);
			break;
		}
		/* if more rcpts stay in this state (guarded by timer) */
		if (--cb->rcpts != 0) {
			start_timer(&cb->tcb->timeout);
			break;
		}

		/* check for no good rcpt on the list */
		if (cb->goodrcpt == 0) {
			if (cb->errlog != NULLLIST)
				retmail(cb);
			unlink(cb->wname);	/* unlink workfile */
			unlink(cb->tname);	/* unlink text */
			abort_trans(cb);
			break;
		}
		/* if this file open fails abort */
		if ((cb->tfile = fopen(cb->tname,"r")) == NULLFILE)
			abort_trans(cb);
		else {
			/* optimize for slow packet links by sending
			 * DATA cmd and the 1st window of text
			 */
			bp = qstring(datacmd);
			cb->cts = 1;
			cb->state = CLI_SEND_STATE;
			if ((cnt = cb->tcb->cwind - cb->tcb->sndcnt - 6) > 0) {
				if (sendwindow(cb,bp,cnt) == EOF)
				       cb->cts = 0;
			} else {
				send_tcp(cb->tcb,bp);
			}
		}
		break;
	case CLI_SEND_STATE:
		if (reply == '3') {
			cb->state = CLI_UNLK_STATE;
			if(cb->cts == 0)
				start_timer(&cb->tcb->timeout);
		} else {
			/* change cts to transmit upcall queueing more data */
			cb->cts = 0;
			quit(cb);
		}
		break;
	case CLI_UNLK_STATE:
		/* if a good transfer or permanent failure remove job */
		if (reply == '2' || reply == '5') {
			if (reply == '5')
				logerr(cb);
			/* close and unlink the textfile */
			if(cb->tfile != NULLFILE) {
				fclose(cb->tfile);
				cb->tfile = NULLFILE;
			}
			if (cb->errlog != NULLLIST)
				retmail(cb);
			unlink(cb->tname);
			unlink(cb->wname);	/* unlink workfile */
			log_tcp(cb->tcb,"SMTP sent job %s To: %s From: %s",
			cb->jobq->jobname,cb->jobq->to->val,cb->jobq->from);
		}
		if (nextjob(cb)) {
			cb->state = CLI_MAIL_STATE;
			sendit(cb,mailcmd,cb->jobq->from);
		} else {
#ifdef SEND_QUIT_IMMEDIATELY
			/* the quit sent already in smtp_cts */
			cb->state = CLI_QUIT_STATE;
			start_timer(&cb->tcb->timer);
#else
			cb->state = CLI_QUIT_STATE;
			sendit(cb,quitcmd);	/* issue a quit command */
#endif
		}
		break;
	case CLI_IDLE_STATE:	/* used after a RSET and more mail to send */
		if (reply != '2')
			quit(cb);
		else {
			cb->state = CLI_MAIL_STATE;
			sendit(cb,mailcmd,cb->jobq->from);
		}
		break;
	case CLI_QUIT_STATE:
		start_timer(&cb->tcb->timer);
		close_tcp(cb->tcb);		/* close up connection */
		break;
	}
}

/* abort the currrent job.
 * If more work exists set up the next job if
 * not then shut down.
*/
static void
abort_trans(cb)
register struct smtp_cb *cb;
{
	if(cb->tfile != NULLFILE) {
		fclose(cb->tfile);
		cb->tfile = NULLFILE;
	}
	if (nextjob(cb)) {
		sendit(cb,rsetcmd);
		cb->state = CLI_IDLE_STATE;
		start_timer(&cb->tcb->timeout);
	} else
		quit(cb);
}

/* close down link after a failure */
static void
quit(cb)
struct smtp_cb *cb;
{
	cb->state = CLI_QUIT_STATE;
	sendit(cb,quitcmd);		/* issue a quit command */
#ifdef CLOSE_IT_OURSELVES
	close_tcp(cb->tcb);		/* close up connection */
#endif
}

/* 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 c;
	struct mbuf *bp;

#ifdef SMTPTRACE
	if (smtptrace > 7)  {
		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;

#ifdef SMTPTRACE
	if (smtptrace > 7) {
		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->cts != 0) {
		if (sendwindow(cb,NULLBUF,cnt) == EOF)
			cb->cts = 0;
	}

	if (tcb->sndcnt == 0)			/* no data outstanding? */
		start_timer(&tcb->timeout);	/* watch out for lockups */
	else
		stop_timer(&tcb->timeout);	/* not when data queued */
}

/* fill the rest of the window with data and send out the eof commands.
* It is done this way to minimize the number of packets sent.
*/
static int
sendwindow(cb,ibp,cnt)
register struct smtp_cb *cb;
struct mbuf *ibp;
int16 cnt;
{
	struct mbuf *bpl = ibp;
	register struct mbuf *bp;
	char *cp;
	int c;
	int last_c = 0;

	cnt++;
	while((bp = alloc_mbuf(cnt)) == NULLBUF && (cnt /= 2) > 8)
		;
	if(bp == NULLBUF){
		/* Hard to know what to do here */
		return EOF;
	}
	cp = bp->data;
	while(cnt > 1 && (c = getc(cb->tfile)) != EOF){
#if (defined(UNIX) || defined(MAC) || defined(AMIGA) || defined(ATARI_ST))
		if(c == '\n'){
			*cp++ = '\r';
			bp->cnt++;
			cnt--;
		}
#endif
		*cp++ = last_c = c;
		bp->cnt++;
		cnt--;
	}
	append(&bpl,bp);
	if (cnt > 1) {	/* EOF seen */
		fclose(cb->tfile);
		cb->tfile = NULLFILE;
		/* send the end of data character. */
		last_c = (last_c == '\n')? 2 : 0;
		if (cnt < sizeof(eom) - 1 - last_c) {
			append(&bpl,qstring(eom + last_c));
			cnt = 0;	/* dont let anyone else in */
		} else {
			memcpy(&bp->data[bp->cnt],eom + last_c,sizeof(eom) - 1 - last_c);
			bp->cnt += sizeof(eom) - 1 - last_c;
			cnt -= sizeof(eom) - 1 - last_c;
		}
#ifdef SEND_QUIT_IMMEDIATELY
		/* send the quit in this packet if last job */
		if (cb->jobq->next == NULLJOB) {
			if (cnt < sizeof(quitcmd) - 1) {
				bp = qstring(quitcmd);
				append(&bpl,bp);
			} else {
				memcpy(&bp->data[bp->cnt],
				quitcmd,sizeof(quitcmd) - 1);
				bp->cnt += sizeof(quitcmd) - 1;
			}
		}
#endif
		send_tcp(cb->tcb,bpl);
#ifdef SEND_QUIT_IMMEDIATELY
		if (cb->jobq->next == NULLJOB)
			close_tcp(cb->tcb);	/* close up connection */
#endif
		return EOF;
	} else {
		send_tcp(cb->tcb,bpl);
		return 0;
	}
}

/* smtp state change upcall routine. */
static
void
smtp_state(tcb,old,new)
register struct tcb *tcb;
char old,new;
{
	register struct smtp_cb *cb;
	extern char *tcpstates[];

#ifdef SMTPTRACE
	if (smtptrace > 7) {
		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->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;
{
	char tmpstring[256];

#ifdef SMTPTRACE
	if (smtptrace) {
		printf(">>> ");
		printf(fmt,arg1,arg2);
		fflush(stdout);
	}
#endif
	sprintf(tmpstring,fmt,arg1,arg2);
	send_tcp(cb->tcb,qstring(tmpstring));
}

/* 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);
#ifdef ATARI_ST
	if(!access(lockname,0) || (fd = creat(lockname, 0600)) == -1)
#else
	if((fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT,0600)) == -1)
#endif
		return -1;
	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 */
void
del_session(cb)
register struct smtp_cb *cb;
{
	register struct smtp_job *jp,*tp;
	register int i;

	if (cb == NULLCB)
		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;
		del_job(jp);
	}
	del_list(cb->errlog);
	free((char *)cb);
	smtpsessions--; /* number of connections active */
}

static void
del_job(jp)
register struct smtp_job *jp;
{
	if (*jp->jobname != '\0')
		rmlock(getnenv(MAILQDIR),jp->jobname);
	if(jp->system != NULLCHAR)
		free(jp->system);
	if(jp->from != NULLCHAR)
		free(jp->from);
	del_list(jp->to);
	free((char *)jp);
}

/* delete a list of list structs */
void
del_list(lp)
struct list *lp;
{
	register struct list *tp, *tp1;
	for (tp = lp; tp != NULLLIST; tp = tp1) {
		tp1 = tp->next;
		if(tp->val != NULLCHAR)
			free(tp->val);
		free((char *)tp);
	}
}

/* return message to sender */
void
retmail(cb)
struct smtp_cb *cb;
{
	register struct list *lp;
	register FILE *tfile;
	register int c;
	FILE *infile,*tmpfile();
	char *host,*to;
	time_t t,time();
#ifdef SMTPTRACE
	if (smtptrace > 5) {
		printf("smtp job %s returned to sender\n",cb->wname);
		fflush(stdout);
	}
#endif
	/* A null From<> so no looping replys to MAIL-DAEMONS */
	to = cb->jobq->from;
	if (*to == '\0')
		return;
	if ((host = index(to,'@')) == NULLCHAR)
		host = hostname;
	else
		host++;
	if ((infile = fopen(cb->tname,"r")) == NULLFILE)
		return;
	if ((tfile = tmpfile()) == NULLFILE) {
		fclose(infile);
		return;
	}
	time(&t);
	fprintf(tfile,"Date: %s",ptime(&t));
	fprintf(tfile,"Message-Id: <%ld@%s>\n",get_msgid(),hostname);
	fprintf(tfile,"From: MAILER-DAEMON@%s\n",hostname);
	fprintf(tfile,"To: %s\n",to);
	fprintf(tfile,"Subject: Failed mail\n\n");
	fprintf(tfile,"  ===== Transcript of session with %s follows =====\n\n",cb->jobq->system);

	for (lp = cb->errlog; lp != NULLLIST; lp = lp->next)
		fprintf(tfile,"%s\n",lp->val);

	fprintf(tfile,"\n  ===== Unsent message follows ====\n");

	while((c = getc(infile)) != EOF)
		if (putc(c,tfile) == EOF)
			break;
	fclose(infile);
	fseek(tfile,0L,0);
	if ((smtpmode & QUEUE) != 0)
		router_queue(cb->tcb,tfile,"",to);
	else
		queuejob(cb->tcb,tfile,host,to,"");
	fclose(tfile);
}

/* look to see if a smtp control block exists for this ipdest */
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]->ipdest == destaddr)
			return cli_session[i];
	}
	return NULLCB;
}

/* create a new	 smtp control block */
static struct smtp_cb *
newcb()
{
	register int i;
	register struct smtp_cb *cb;
	char *mailqdir;

	mailqdir = getnenv(MAILQDIR);
	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_INIT_STATE;
			cli_session[i] = cb;
			smtpsessions++; /* 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;
	register int i;
	char *mailqdir;

	mailqdir = getnenv(MAILQDIR);
	for(i=0; i<MAXSESSIONS; i++) {
		cb = cli_session[i];
		if (cb == NULLCB)
			continue;
		if(cb->state != CLI_INIT_STATE)
			continue;

		sprintf(cb->tname,"%s%s.txt",mailqdir,cb->jobq->jobname);
		sprintf(cb->wname,"%s%s.wrk",mailqdir,cb->jobq->jobname);
#ifdef AX25
		if (cb->ipdest == 0xffffffffL){ /* check for AX.25 BBS hack */
			cb->state = CLI_MAIL_STATE; /* lock against further adds */
			mbxfwd(cb);		/* forward to BBS(es) */
			continue;
		}
#endif
		/* setup the socket */
		fsocket.address = cb->ipdest;
		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 */
		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 */
static struct smtp_job *
setupjob(cb,id,systemname,from)
struct smtp_cb *cb;
char *id,*systemname,*from;
{
	register struct smtp_job *p1,*p2;

	p1 = (struct smtp_job *)calloc(1,sizeof(struct smtp_job));
	if (p1 == NULLJOB)
		return NULLJOB;
	p1->from = malloc((unsigned)strlen(from) + 1);
	if (p1->from == NULLCHAR) {
		free((char *)p1);
		return NULLJOB;
	}
	p1->system = malloc((unsigned)strlen(systemname) + 1);
	if (p1->system == NULLCHAR) {
		free(p1->from);
		free((char *)p1);
		return(NULLJOB);
	}
	strcpy(p1->system,systemname);
	strcpy(p1->from,from);
	strcpy(p1->jobname,id);
	/* now add to end of jobq */
	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 */
int
nextjob(cb)
register struct smtp_cb *cb;
{
	register struct smtp_job *jp;
	char *mailqdir;

	jp = cb->jobq->next;
	del_job(cb->jobq);
	cb->jobq = jp;
	if (jp == NULLJOB)
		return 0;
	/* remove the error log of previous message */
	del_list(cb->errlog);
	cb->errlog = NULLLIST;
	cb->goodrcpt = 0;
	mailqdir = getnenv(MAILQDIR);
	sprintf(cb->tname,"%s%s.txt",mailqdir,jp->jobname);
	sprintf(cb->wname,"%s%s.wrk",mailqdir,jp->jobname);
#ifdef SMTPTRACE
	if (smtptrace > 5) {
		printf("sending job %s\n",jp->jobname);
		fflush(stdout);
	}
#endif
	return 1;
}


/* mail routing function. For now just uses the hosts file */
int32
mailroute(dest)
char *dest;
{
	int32 destaddr;
	char cpdest[80],*cp,*rindex();

	/* special case: it is destined to us */
	if (!strcmp(dest,hostname))
		return ip_addr;			/* return OUR address */

#ifdef AX25
	/* if it is of the form call.bbs, check if we can forward */
	/* to an AX.25 bbs with that name			  */
	if ((cp = index(dest,'.')) != NULLCHAR &&
	    index(cp + 1,'.') == NULLCHAR &&
	    !strncasecmp(cp,".bbs",5)) {
	    sprintf(cpdest,"%s%s",getnenv(MAILQDIR),dest);
	    if (!access(cpdest,4))		/* file exists in MAILQ? */
		return 0xffffffffL;		/* it is a BBS! */
	}
#endif
	/* look up address or use the gateway */
	if ((destaddr = resolve(dest)) == 0) {
	    /* if it includes a subdomain (like .ampr.org), remove it.*/
	    /* dangerous, but it allows adresses like pe1chl.ampr.org */
	    /* to be mapped to "pe1chl" in the hosts file (temp fix)  */
	    strcpy(cpdest,dest);
	    while ((cp = rindex(cpdest,'.')) != NULLCHAR){
		*cp = '\0';
		if ((destaddr = resolve(cpdest)) != 0)
		    return destaddr;
	    }

	    if (gateway != 0)
		destaddr = gateway; /* Use the gateway	*/
	}
	return destaddr;
}

/* save error reply for in error list */
void
logerr(cb)
struct smtp_cb *cb;
{
	register struct list *lp,*tp;
	if ((tp = (struct list *)calloc(1,sizeof(struct list))) == NULLLIST)
		return;
	if ((tp->val = malloc((unsigned)strlen(cb->buf)+1)) == NULLCHAR) {
		free((char *)tp);
		return;
	}
	/* find end of list */
	if ((lp = cb->errlog) == NULLLIST)
		cb->errlog = tp;
	else {
		while(lp->next != NULLLIST)
			lp = lp->next;
		lp->next = tp;
	}
	strcpy(tp->val,cb->buf);
}
