/*
 *	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
 *	Copyright 1987 1988 David Trulli, All Rights Reserved.
 *	Permission granted for non-commercial copying and use, provided
 *	this notice is retained.
 */

/****************************************************************************
*	$Id: smtpcli.c 1.18 94/01/04 14:10:21 ROOT_DOS Exp $
*	29 May 92	1.2	GT		mailroute (): use gateway if defined.
*	02 Jun 92	1.3	GT		Add "smtp wait" and timeout on connect () call
*							   	in smtp_send ().
*	04 Jun 92	1.4	GT		Make smtptick () a separate process.
*	05 Jun 92	1.5	GT		Timeout on connect () to Gateway.
*	26 Jun 92			Paul@wolf.demon.co.uk Fixed routing for internal
*						      	email (doesn't go via the external gateway)
*	13 Jul 92	1.7	GT		Remove 1.6 changes.
*	05 Aug 92	    	amc@beryl.demon.co.uk changed printfs to tprintf
*	27 Aug 92   1.10  mt@kram.demon.co.uk added smtp separator
*	Dec 92				mt@kram.org don't use gateway for any mail MXed via
*									us. Add delay channel for local mail
*									Add smtp beep on|off
*	19 Apr 93	1.16	GT		If newproc () fails, reset tick flag.
*	08 May 93	1.17	GT		Fix warnings.
*									Fix "smtp list".
*							IAY 	Fix dot transparency as per RFC 821.
*	07 Nov 93	1.18	GT		Verbose: show "sending" progress.
*									mailroute (): cope with domain literals.
*
*  ST NOS Version by David Nash - dnash@chaos.demon.co.uk
*
*  __stdargs  - smtp_send, smtptick_body
*  dosmtplist - call statx to fix path seperator
*
****************************************************************************/

#include <stdio.h>
#include <fcntl.h>
#ifdef ATARI
#include <ext.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <time.h>
#include <setjmp.h>
#ifdef UNIX
#include <sys/types.h>
#endif
#ifdef	AMIGA
#include <stat.h>
#else
#include <sys/stat.h>
#endif
#ifdef	__TURBOC__
#include <dir.h>
#include <io.h>
#endif
#include "global.h"
#include <stdarg.h>
#include "config.h"
#include "domain.h"
#include "mbuf.h"
#include "cmdparse.h"
#include "proc.h"
#include "socket.h"
#include "timer.h"
#include "netuser.h"
#include "smtp.h"
#include "dirutil.h"
#include "commands.h"
#include "session.h"
#include "ip.h"

static struct timer Smtpcli_t;
static int32 Gateway;
static int32 Delaytime = 0;
int   near smtpverbose = 1;
char *near smtp_separator = NULL;
int   near are_we_an_mx;

static unsigned short Smtptrace = 0;	/* used for trace level */
static int dosmtptrace __ARGS ((int argc, char *argv[], void *p));

static unsigned short Smtpmaxcli = MAXSESSIONS;	/* the max client connections
												 * allowed */
static int Smtpsessions = 0; /* number of client connections currently open */
static int Smtpbatch;
int near Smtpmode = 0;
int near Smtpbeep = 0;

int32 near connect_wait_val = 60000L;		/* default connection timeout		 */

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

static void del_job __ARGS ((struct smtp_job * jp));
static void del_session __ARGS ((struct smtpcli * cb));
static int dogateway __ARGS ((int argc, char *argv[], void *p));
static int doseparator __ARGS ((int argc, char *argv[], void *p));
static int dosmtpmaxcli __ARGS ((int argc, char *argv[], void *p));
static int dotimer __ARGS ((int argc, char *argv[], void *p));
static int dodelay __ARGS ((int argc, char *argv[], void *p));
static int dowait __ARGS ((int argc, char *argv[], void *p));
static int dosmtpkill __ARGS ((int argc, char *argv[], void *p));
static int dosmtplist __ARGS ((int argc, char *argv[], void *p));
static int dobatch __ARGS ((int argc, char *argv[], void *p));
static int dobeep __ARGS ((int argc, char *argv[], void *p));
static void execjobs __ARGS ((void));
static int getresp __ARGS ((struct smtpcli * ftp, int mincode));
static void logerr __ARGS ((struct smtpcli * cb, char *line));
static struct smtpcli *lookup __ARGS ((int32 destaddr));
static struct smtpcli *newcb __ARGS ((void));
static int next_job __ARGS ((struct smtpcli * cb));
static void retmail __ARGS ((struct smtpcli * cb));
static void sendcmd __ARGS ((struct smtpcli * cb, char *fmt,...));
static int smtpsendfile __ARGS ((struct smtpcli * cb));
static int setsmtpmode __ARGS ((int argc, char *argv[], void *p));
static struct smtp_job *setupjob __ARGS ((struct smtpcli * cb, char *id, char *from));
static void __stdargs smtp_send __ARGS ((int unused, void *cb1, void *p));
static int smtpkick __ARGS ((int argc, char *argv[], void *p));
static void __stdargs smtptick_body __ARGS ((int unused, void *t, void *p));
static int doverbose __ARGS ((int argc, char *argv[], void *p));
static int should_enter_delay __ARGS ((struct smtpcli * cb));
static int explock __ARGS ((char *dir, char *pre, char *suf, int32 age));

static struct cmds Smtpcmds[] = {
	{"batch", 			dobatch, 		0, 0, NULLCHAR},
	{"beep", 			dobeep, 			0, 0, NULLCHAR},
	{"delay", 			dodelay, 		0, 0, NULLCHAR},
	{"gateway", 		dogateway, 		0, 0, NULLCHAR},
	{"kick", 			smtpkick, 		0, 0, NULLCHAR},
   {"kill", 			dosmtpkill, 	0, 2, "kill <jobnumber>"},
	{"list", 			dosmtplist, 	0, 0, NULLCHAR},
	{"maxclients", 	dosmtpmaxcli, 	0, 0, NULLCHAR},
	{"mode", 			setsmtpmode, 	0, 0, NULLCHAR},
	{"separator", 		doseparator, 	0, 0, NULLCHAR},
	{"timer", 			dotimer, 		0, 0, NULLCHAR},
	{"trace", 			dosmtptrace, 	0, 0, NULLCHAR},
	{"wait", 			dowait, 			0, 0, NULLCHAR},
	{"verbose", 		doverbose, 		0, 0, NULLCHAR},		/* Actually for Server */
	{NULLCHAR},
};


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


static int dobatch(int argc, char *argv[], void *p)
{
	return setbool (&Smtpbatch, "SMTP batching", argc, argv);
}


static int dobeep(int argc, char *argv[], void *p)
{
	return setbool (&Smtpbeep, "SMTP beep on delivery", argc, argv);
}


static int dosmtpmaxcli(int argc, char *argv[], void *p)
{
	return setshort (&Smtpmaxcli, "Max clients", argc, argv);
}


static int setsmtpmode(int argc, char *argv[], void *p)
{
	if (argc < 2) {
		tprintf ("smtp mode: %s\n", (Smtpmode & QUEUE) ? "queue" : "route");
	}
	else {
		switch (*argv[1]) {
			case 'q':
				Smtpmode |= QUEUE;
				break;
			case 'r':
				Smtpmode &= ~QUEUE;
				break;
			default:
				tprintf ("Usage: smtp mode [queue | route]\n");
				break;
		}
	}

	return 0;
}


static int dogateway(int argc, char *argv[], void *p)
{
	int32 n;

	if (argc < 2)
		tprintf ("%s\n", inet_ntoa (Gateway));
	else
	if ((n = resolve (argv[1])) == 0) {
		tprintf (Badhost, argv[1]);
		return 1;
	}
	else
		Gateway = n;
	log(-1, "dogateway: resolve gateway = %x ", (void *)Gateway); /* DEBUG */	

	return 0;
}


static int doseparator(int argc, char *argv[], void *p)
{
	int i;
	char *t;

	if (argc < 2) {
		if (smtp_separator) {
			log(-1, "doseparator: free smtp_separator = %x ",(void *)smtp_separator); /* DEBUG */
			free(smtp_separator);
		}

		smtp_separator = NULL;
	}
	else {
		t = malloc(81);
		log(-1, "doseperator: malloc t = %x ", (void *)t);  /* DEBUG */
		if (t == NULL)
			return 1;

		t[0] = '\0';
		for (i = 1; i < argc; i++)	{
			strcat (t, argv[i]);
			if (i < (argc - 1))
				strcat (t, " ");

		}
		if ((smtp_separator = malloc(strlen (t) + 1)) == NULL)	{
			log(-1, "doseperator: free t = %x ", (void *)t);  /* DEBUG */
			free(t);
			return 1;
		}
		log(-1, "doseperator: malloc smtp_separator = %x ", (void *)smtp_separator); /* DEBUG */

		strcpy(smtp_separator, t);
		log(-1, "doseperator: free t = %x ", (void *)t);  /* DEBUG */
		free(t);
	}

	return 0;
}


static int dosmtptrace(int argc, char *argv[], void *p)
{
	return setshort (&Smtptrace, "SMTP tracing", argc, argv);
}


/* list jobs wating to be sent in the mqueue */

static int dosmtplist (int argc, char *argv[], void *p)
{
	char tstring[80];
	char line[20];
	char host[LINELEN];
	char to[LINELEN];
	char from[LINELEN];
	char *cp;
	char status;
	struct stat stbuf;
	struct tm *tminfo;
	FILE *fp;

	int	statx(const char *, struct stat *);

	Current->flowmode = 1;						 /* Enable the more mechanism */
	tprintf ("S     Job    Size Date  Time  Host                 From\n");
	filedir (Mailqueue, 0, line);
	while (line[0] != '\0')	{
		(void) sprintf (tstring, "%s/%s", Mailqdir, line);
		if ((fp = fopen (tstring, READ_TEXT)) == NULLFILE) {
			tprintf ("Can't open %s: %s\n", tstring, sys_errlist[errno]);
			pwait (NULL);
			filedir (Mailqueue, 1, line);
			continue;
		}

		if ((cp = strrchr (line, '.')) != NULLCHAR)
			*cp = '\0';

		(void) sprintf (tstring, "%s/%s.lck", Mailqdir, line);
		if (access (tstring, 0))
			status = ' ';
		else
			status = 'L';

		/* Now see if it's in delay */
		if (status == ' ') {
			sprintf (tstring, "%s/%s.dly", Mailqdir, line);
			if (!access (tstring, 0))
				status = 'D';

		}

		(void) sprintf (tstring, "%s/%s.txt", Mailqdir, line);
		if (statx (tstring, &stbuf) == -1) { /* ATARI statx to fix path sep */ 
			tprintf ("Can't stat %s: %s\n", tstring, sys_errlist[errno]);
			pwait (NULL);
			filedir (Mailqueue, 1, line);
			continue;
		}

		tminfo = localtime (&stbuf.st_ctime);
		fgets (host, sizeof (host), fp);
		rip (host);
		fgets (from, sizeof (from), fp);
		rip (from);
		tprintf ("%c %7s %7ld %02d/%02d %02d:%02d %-20s %s\n      ",
				 status, line, stbuf.st_size,
				 tminfo->tm_mon + 1,
				 tminfo->tm_mday,
				 tminfo->tm_hour,
				 tminfo->tm_min,
				 host, from);
		while (fgets (to, sizeof (to), fp) != NULLCHAR) {
			rip (to);
			tprintf ("%s ", to);
		}

		tprintf ("\n");
		(void) fclose (fp);
		pwait (NULL);
		filedir (Mailqueue, 1, line);
	}

	Current->flowmode = 0;
	return 0;
}
	

/*  dosmtpkill - kill a job in the mqueue */

static int dosmtpkill(int argc, char *argv[], void *p)
{
	char s[SLINELEN];
	char *cp, c;

	sprintf (s, "%s/%s.lck", Mailqdir, argv[1]);
	cp = strrchr (s, '.');
	if (!access (s, 0)) {
		Current->ttystate.echo = Current->ttystate.edit = 0;
		c = keywait("Warning, the job is locked by SMTP. Remove (y/n)? ", 0);
		Current->ttystate.echo = Current->ttystate.edit = 1;
		if (c != 'y')
			return 0;

		(void)unlink(s);
	}

	strcpy(cp, ".wrk");
	if (unlink(s))
		tprintf("Job id %s not found\n", argv[1]);

	strcpy(cp, ".txt");
	(void)unlink(s);
	strcpy(cp, ".dly");
	(void)unlink(s);

	return 0;
}


/*  dotimer - Set outbound spool scan interval */

static int dotimer(int argc, char *argv[], void *p)
{
	if (argc < 2) {
		tprintf ("%lu/%lu\n",
				 read_timer (&Smtpcli_t) / 1000L,
				 dur_timer (&Smtpcli_t) / 1000L);
		return 0;
	}

	Smtpcli_t.func = (void (*) ()) smtptick;	 /* what to call on timeout */
	Smtpcli_t.arg = NULL;						 /* dummy value */
	set_timer (&Smtpcli_t, atol (argv[1]) * 1000L);		/* set timer duration */
	start_timer (&Smtpcli_t);					 /* and fire it up */

	return 0;
}


/*  dodelay - Set delay channel time */

static int dodelay(int argc, char *argv[], void *p)
{
	if (argc < 2) {
		tprintf ("%lu\n", Delaytime);
		return 0;
	}

	Delaytime = atol (argv[1]);

	return 0;
}


/*	dowait -	Displays or sets the connection wait timeout. */

static int dowait(int argc, char *argv[], void *p)
{
	if (argc < 2) {
		tprintf ("smtp connection timeout: %lu\n", connect_wait_val / 1000L);
		return (0);
	}

	connect_wait_val = atol (argv[1]) * 1000L;	 /* set timeout					 */

	return (0);
}


static int smtpkick(int argc, char *argv[], void *p)
{
	int32 addr = 0;

	if (argc > 1 && (addr = resolve (argv[1])) == 0) {
		tprintf (Badhost, argv[1]);
		return 1;
	}
	log(-1, "smtpkick: resolve addr = %x ", (void *)addr); /* DEBUG */
	smtptick ((void *) addr);
	return 0;
}


/* This is the routine that gets called every so often to do outgoing
 * mail processing. When called with a null argument, it runs the entire
 * queue; if called with a specific non-zero IP address from the remote
 * kick server, it only starts up sessions to that address.
 */

static char smtptick_running = 0;

int smtptick(void *t) {

	if (smtptick_running != 0)
		return (0);

	smtptick_running = 1;
	if (newproc ("smtp_tick", 1024, smtptick_body, 0, t, NULL, 0) == NULLPROC)	{
		start_timer (&Smtpcli_t);		/* wait a while */
		smtptick_running = 0;
	}

	return (0);
}


static void __stdargs smtptick_body (int unused, void *t, void *p)
{
	struct smtpcli *cb;
	struct smtp_job *jp;
	struct list *ap;
	char tmpstring[LINELEN], wfilename[13], prefix[9];
	char from[LINELEN], to[LINELEN];
	char *cp, *cp1;
	int32 destaddr, target, num_files;
	FILE *wfile;

	target = (int32) t;

	if (Smtptrace > 5)
		tprintf ("smtp daemon entered, target = %s\n", inet_ntoa (target));

	if (availmem () < Memthresh) {
		/* Memory is tight, don't do anything */
		/* Restart timer */
		start_timer (&Smtpcli_t);
		smtptick_running = 0;
		return;
	}

	/* First see how many there are now */

	num_files = 0;
	for (filedir (Mailqueue, 0, wfilename); wfilename[0] != '\0';
		 filedir (Mailqueue, 1, wfilename))
		num_files++;

	/* Try to deal with that many messages, no more or we may loop forever */

	for (filedir (Mailqueue, 0, wfilename); wfilename[0] != '\0' && num_files--;
		 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;

		/* Try to bring the job outof delay (in case it's in delay) */

		explock (Mailqdir, prefix, "dly", Delaytime);

		/* Check that it's not in delay */
		if (mlock_suffix (Mailqdir, prefix, "dly")) {
			rmlock (Mailqdir, prefix);
			continue;
		}
		else
			rmlock_suffix (Mailqdir, prefix, "dly");

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

		(void) fgets (tmpstring, LINELEN, wfile);/* read target host */
		rip (tmpstring);

		if ((destaddr = mailroute (tmpstring, Ip_addr)) == 0)	{
			fclose (wfile);
			tprintf ("** smtp: Unknown address %s\n", tmpstring);
			(void) rmlock (Mailqdir, prefix);
			continue;
		}

		if (target != 0 && destaddr != target)	{
			fclose (wfile);
			(void) rmlock (Mailqdir, prefix);
			continue;							 /* Not the proper target of a kick */
		}

		if ((cb = lookup (destaddr)) == NULLSMTPCLI) {
			/* there are enough processes running already */
			if (Smtpsessions >= Smtpmaxcli) {
				if (Smtptrace)
					tprintf ("smtp daemon: too many processes\n");
				fclose (wfile);
				(void) rmlock (Mailqdir, prefix);
				break;
			}

			if ((cb = newcb()) == NULLSMTPCLI) {
				fclose(wfile);
				(void)rmlock(Mailqdir, prefix);
				break;
			}

			cb->ipdest   = destaddr;
			cb->destname = strdup(tmpstring);	/* implicit malloc				*/
			log(-1, "smtptick_body: strdup cb->destname = %x ", (void *)cb->destname); /* DEBUG */
		}
		else {
			if (cb->lock) {
				/* This system is already is sending mail lets not
				 * interfere with its send queue. */
				fclose (wfile);
				(void) rmlock (Mailqdir, prefix);
				continue;
			}
		}

		(void) fgets (from, LINELEN, wfile);	 /* read from */
		rip (from);
		if ((jp = setupjob(cb, prefix, from)) == NULLJOB)	{
			fclose(wfile);
			rmlock(Mailqdir, prefix);
			del_session(cb);
			break;
		}

		while (fgets(to, LINELEN, wfile) != NULLCHAR) {
			rip(to);
			if (addlist(&jp->to, to, DOMAIN) == NULLLIST) {
				fclose(wfile);
				del_session(cb);
			}
		log(-1, "smpttick_body: addlist jp->to = %x ", jp->to); /* DEBUG */
		log(-1, "smpttick_body: addlist jp->to->val = %x ", jp->to->val); /* DEBUG */

		}
		fclose(wfile);

		if (Smtptrace > 1) {
			tprintf("queue job %s From: %s To:", prefix, from);
			for (ap = jp->to; ap != NULLLIST; ap = ap->next)
				tprintf(" %s", ap->val);
			tprintf ("\n");
		}

		if (smtpverbose != 0 && Smtptrace <= 1) {
			tprintf ("Sending %s From: %s To:", prefix, from);
			for (ap = jp->to; ap != NULLLIST; ap = ap->next)
				tprintf (" %s", ap->val);
			tprintf ("\n");
		}
	}

	/* start sending that mail */

	execjobs ();

	/* Restart timer */

	start_timer(&Smtpcli_t);
	smtptick_running = 0;
	return;
}


static int should_enter_delay(struct smtpcli *cb)
{
	resolve_mx (cb->destname);
	log(-1, "should_enter_delay: resolve_mx cb->destname = %x ", (void *)cb->destname); /* DEBUG */
	if (are_we_an_mx)
		return 1;

	return 0;
}


void delay_job(int32 msgid)
{
	char t[20];

	if (Delaytime == 0)
		/* Not using SMTP delay */
		return;

	sprintf (t, "%lu", msgid);

	if (Smtptrace)
		tprintf ("Delivery Failed: adding job %s to delay queue\n", t);

	mlock_suffix (Mailqdir, t, "dly");
}

/* This is the master state machine that handles a single SMTP transaction.
 * It is called with a queue of jobs for a particular host.
 * The logic is complicated by the "Smtpbatch" variable, which controls
 * the batching of SMTP commands. If Smtpbatch is true, then many of the
 * SMTP commands are sent in one swell foop before waiting for any of
 * the responses. Unfortunately, this breaks many brain-damaged SMTP servers
 * out there, so provisions have to be made to operate SMTP in lock-step mode.
 */

static void __stdargs smtp_send (int unused, void *cb1, void *p)
{
	struct smtpcli *cb;
	struct list *tp;
	struct sockaddr_in fsocket;
	char *cp;
	int rcode;
	int rcpts;
	int goodrcpt;
	int i;
	int init = 1;

	cb = (struct smtpcli *)cb1;
	cb->lock = 1;
	fsocket.sin_family 		= AF_INET;
	fsocket.sin_addr.s_addr = cb->ipdest;
	fsocket.sin_port 			= IPPORT_SMTP;

	cb->s = socket(AF_INET, SOCK_STREAM, 0);
	sockmode(cb->s, SOCK_ASCII);
	setflush(cb->s, -1);				/* We'll explicitly flush before reading */

	if (Smtptrace)
		tprintf ("SMTP client Trying...\n");

	/* Run connect () under a timeout in case the destination is unreachable. */

	alarm(connect_wait_val);
	if (connect (cb->s, (char *) &fsocket, SOCKSIZE) == 0) {
		alarm (0L);
		if (Smtptrace)
			tprintf ("Connected\n");
	}
	else {
		alarm(0L);
		if (Delaytime && (cb->ipdest == Ip_addr || should_enter_delay(cb)))
			delay_job(atol(cb->jobq->jobname));
	else
		if (Gateway != 0L) {
			
			/* Try sending it via the gateway, as long as it's not for us. */

			if (Smtptrace)
				tprintf ("SMTP client Trying gateway...\n");

			close_s(cb->s);
			cb->ipdest = Gateway;
			fsocket.sin_addr.s_addr = Gateway;
			cb->s = socket(AF_INET, SOCK_STREAM, 0);
			sockmode(cb->s, SOCK_ASCII);
			setflush(cb->s, -1);
			alarm(connect_wait_val);
			
			if (connect(cb->s, (char *) &fsocket, SOCKSIZE) != 0) {
				alarm(0L);
				cp = sockerr(cb->s);
				if (Smtptrace)
					tprintf("Connect failed: %s\n", cp != NULLCHAR ? cp : "");
				log(cb->s, "SMTP %s Connect failed: %s", psocket (&fsocket),
					 cp != NULLCHAR ? cp : "");

				goto quit;
			}
			else {
				alarm(0L);
				if (Smtptrace)
					tprintf ("Connected to Gateway\n");
			}

		}
		else {
			cp = sockerr(cb->s);
			if (Smtptrace)
				tprintf ("Connect failed: %s\n", cp != NULLCHAR ? cp : "");
			log (cb->s, "SMTP %s Connect failed: %s", psocket(&fsocket),
																	cp != NULLCHAR ? cp : "");
			goto quit;
		}

	}

	if (!Smtpbatch) {
		rcode = getresp (cb, 200);
		if (rcode == -1 || rcode >= 400)
			goto quit;

	}

	/* Say HELO */

	sendcmd(cb, "HELO %s\n", Hostname);

	if (!Smtpbatch) {
		rcode = getresp(cb, 200);
		if (rcode == -1 || rcode >= 400)
			goto quit;
	}

	do	{										 /* For each message... */
		/* if this file open fails, skip it */

		if ((cb->tfile = fopen(cb->tname, READ_TEXT)) == NULLFILE)
			continue;

		/* Send MAIL and RCPT commands */

		sendcmd (cb, "MAIL FROM:<%s>\n", cb->jobq->from);
		if (!Smtpbatch) {
			rcode = getresp(cb, 200);
			if (rcode == -1 || rcode >= 400)
				goto quit;
		}

		rcpts = 0;
		goodrcpt = 0;
		for (tp = cb->jobq->to; tp != NULLLIST; tp = tp->next) {
			sendcmd(cb, "RCPT TO:<%s>\n", tp->val);
			if (!Smtpbatch) {
				rcode = getresp (cb, 200);
				if (rcode == -1)
					goto quit;

				if (rcode < 400)
					goodrcpt = 1;				 /* At least one good */
			}
			rcpts++;
		}

		/* Send DATA command */

		sendcmd(cb, "DATA\n");
		if (!Smtpbatch) {
			rcode = getresp(cb, 200);
			if (rcode == -1 || rcode >= 400)
				goto quit;
		}

		if (Smtpbatch)	{
			/* Now wait for the responses to come back. The first time we
			 * do this, we wait first for the start banner and HELO
			 * response. In any case, we wait for the response to the MAIL
			 * command here. */
			 
			for (i = init ? 3 : 1; i > 0; i--) {
				rcode = getresp(cb, 200);
				if (rcode == -1 || rcode >= 400)
					goto quit;
			}
			init = 0;

			/* Now process the responses to the RCPT commands */
			
			for (i = rcpts; i != 0; i--) {
				rcode = getresp (cb, 200);
				if (rcode == -1)
					goto quit;

				if (rcode < 400)
					goodrcpt = 1;				 /* At least one good */

			}
			
			/* And finally get the response to the DATA command. Some
			 * servers will return failure here if no recipients are valid,
			 * some won't. */
			 
			rcode = getresp (cb, 200);
			if (rcode == -1 || rcode >= 400)
				goto quit;

			/* check for no good rcpt on the list */

			if (goodrcpt == 0) {
				sendcmd(cb, ".\n");			 /* Get out of data mode */
				goto quit;
			}
		}

		/* Send the file. This also closes it */

		smtpsendfile(cb);

		/* Wait for the OK response */
		rcode = getresp(cb, 200);
		if (rcode == -1)
			goto quit;

		if ((rcode >= 200 && rcode < 300) || rcode >= 500)	{

			/* if a good transfer or permanent failure remove job */

			if (cb->errlog != NULLLIST)
				retmail (cb);

			/* Unlink the textfile */

			(void) unlink (cb->tname);
			(void) unlink (cb->wname);			 /* unlink workfile */

			log(cb->s, "SMTP sent job %s To: %s From: %s",
				 cb->jobq->jobname, cb->jobq->to->val, cb->jobq->from);

			if (smtpverbose != 0 && Smtptrace <= 1) {
				/* Show sending progress. */

				tprintf("Sent %s\n", cb->jobq->jobname);
			}
		}

	} while (next_job(cb));

quit:
	sendcmd(cb, "QUIT\n");
	if (cb->errlog != NULLLIST) {
		retmail (cb);
		unlink (cb->wname);				 			/* unlink workfile 				*/
		unlink (cb->tname);				 			/* unlink text 					*/
	}

	close_s(cb->s);
	if (cb->tfile != NULLFILE)
		fclose(cb->tfile);

	cb->lock = 0;
	del_session(cb);
}


/*  mlock - create mail lockfile 														*/

int mlock(char *dir, char *id)
{
	return mlock_suffix (dir, id, "lck");
}


int mlock_suffix(char *dir, char *id, char *suffix)
{
	char lockname[LINELEN];
	int fd;

#if defined(MSDOS) || defined(ATARI)
	if (strlen (id) > 8)	{							 /* truncate long filenames */
		id[8] = '\0';
		if (id[7] == '/')
			id[7] = '\0';

	}
#endif
	/* Try to create the lock file in an atomic operation */

	sprintf(lockname, "%s/%s.%s", dir, id, suffix);

	if ((fd = open(lockname, O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1)
		return -1;

	close(fd);
	return 0;
}


/*  rmlock - Remove mail lockfile */

int rmlock(char *dir, char *id)
{
	return rmlock_suffix(dir, id, "lck");
}


int rmlock_suffix(char *dir, char *id, char *suffix)
{
	char lockname[LINELEN];

#if defined(MSDOS) || defined(ATARI)
	if (strlen (id) > 8)	{							 /* truncate long filenames */
		id[8] = '\0';
		if (id[7] == '/')
			id[7] = '\0';

	}
#endif

	sprintf (lockname, "%s/%s.%s", dir, id, suffix);
	return (unlink (lockname));
}


/*  del_session - Free the message struct and data */

static void del_session(struct smtpcli *cb)
{
	int i;
	struct smtp_job *jp, *tp;

	if (cb == NULLSMTPCLI)
		return;

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

	log(-1,"del_session: free cb->wname = %x ", (void *)cb->wname);  /* DEBUG */
	free(cb->wname);
	log(-1,"del_session: free cb->tname = %x ", (void *)cb->tname);  /* DEBUG */
	free(cb->tname);
	log(-1,"del_session: free cb->destname = %x ", (void *)cb->destname);  /* DEBUG */
	free(cb->destname);

	for (jp = cb->jobq; jp != NULLJOB; jp = tp) {
		tp = jp->next;
		del_job(jp);
	}

	del_list(cb->errlog);
	log(-1,"del_session: free cb = %x ", (void *)cb);  /* DEBUG */
	free((void *)cb);
	Smtpsessions--;								 /* number of connections active */
}


/*  del_job - Free smtp_job structure													*/

static void del_job(struct smtp_job *jp)
{
	if (*jp->jobname != '\0')
		(void) rmlock(Mailqdir, jp->jobname);

	log(-1,"del_job: free jp->from = %x ", (void *)jp->from);  /* DEBUG */
	free(jp->from);
	del_list(jp->to);
	log(-1,"del_job: free jp = %x ", (void *)jp);  /* DEBUG */
	free((void *)jp);
}


/*  del_list - Delete a list of list structs */

void del_list(struct list *lp)
{
	struct list *tp, *tp1;

	for (tp = lp; tp != NULLLIST; tp = tp1) {
		tp1 = tp->next;
		log(-1, "del_list: free tp->val = %x ", (void *)tp->val);  /* DEBUG */
		free(tp->val);
		log(-1, "del_list: free tp = %x ", (void *)tp);  /* DEBUG */
		free((char *) tp);
	}
}


/*  retmail - Stub for calling mdaemon to return message to sender */

static void retmail(struct smtpcli *cb)
{
	FILE *infile;

	if (Smtptrace > 5)
		tprintf("smtp job %s returned to sender\n", cb->wname);

	if ((infile = fopen(cb->tname, READ_TEXT)) == NULLFILE)
		return;

	mdaemon(infile, cb->jobq->from, cb->errlog, 1);
	fclose(infile);
}


/*  lookup - Look to see if a smtp control block exists for this ipdest */

static struct smtpcli *lookup(int32 destaddr)
{
	int i;

	for (i = 0; i < MAXSESSIONS; i++) {
		if (cli_session[i] == NULLSMTPCLI)
			continue;

		if (cli_session[i]->ipdest == destaddr)
			return cli_session[i];
	}
	return NULLSMTPCLI;
}


/*  newcb - create a new  smtp control block */

static struct smtpcli *newcb(void)
{
	int i;
	struct smtpcli *cb;

	for (i = 0; i < MAXSESSIONS; i++) {
		if (cli_session[i] == NULLSMTPCLI) {
			cb = (struct smtpcli *) calloc(1, sizeof (struct smtpcli));
			log(-1,"newcb: calloc cb = %x ", (void *)cb); /*DEBUG */
			cb->wname = malloc((unsigned) strlen (Mailqdir) + JOBNAME);
			log(-1,"newcb: malloc cb->wname = %x ", (void *)cb->wname); /* DEBUG */
			cb->tname = malloc((unsigned) strlen (Mailqdir) + JOBNAME);
			log(-1,"newcb: malloc cb->tname = %x ", (void *)cb->tname); /* DEBUG */
			cli_session[i] = cb;
			Smtpsessions++;			 				/* number of connections active */
			
			return (cb);
		}
	}
	return NULLSMTPCLI;
}


static void execjobs(void)
{
	int i;
	struct smtpcli *cb;

	for (i = 0; i < MAXSESSIONS; i++) {
		cb = cli_session[i];
		if (cb == NULLSMTPCLI)
			continue;

		if (cb->lock)
			continue;

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

		newproc("smtp_send", 1024, smtp_send, 0, cb, NULL, 0);

		if (Smtptrace)
			tprintf("Trying Connection to %s\n", inet_ntoa (cb->ipdest));
	}
}


/*  smtp_job - Add this job to control block queue */

static struct smtp_job *setupjob(struct smtpcli *cb, char *id, char *from)
{
	struct smtp_job *p1, *p2;

	p1 = (struct smtp_job *) calloc(1, sizeof (struct smtp_job));
	log(-1, "setupjob: calloc p1 = %x ", (void *)p1); /* DEBUG */

	p1->from = strdup(from);						/* implicit malloc				*/
	log(-1, "setupjob: strdup p1->from = %x ", (void *)p1->from); /* DEBUG */
	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;
}


/*  next_job - Called to advance to the next job */

static int next_job(struct smtpcli *cb)
{
	struct smtp_job *jp;

	jp = cb->jobq->next;
	del_job(cb->jobq);
	
	/* remove the error log of previous message */

	del_list(cb->errlog);
	cb->errlog = NULLLIST;
	cb->jobq = jp;
	if (jp == NULLJOB)
		return 0;

	sprintf(cb->tname, "%s/%s.txt", Mailqdir, jp->jobname);
	sprintf(cb->wname, "%s/%s.wrk", Mailqdir, jp->jobname);

	if (Smtptrace > 5)
		tprintf("sending job %s\n", jp->jobname);

	return 1;

}


/*  mailroute - Mail routing function. */

int32 mailroute(char *dest, int32 gateway)
{
	int32 destaddr;

	/* Test for domain literal first. */

	if (*dest == '[')
		return (aton(dest));

/* If the destination is this site, then *don't* go through a mail
 * server (MX site)
 */

	if (stricmp(dest, Hostname) == 0)
		if ((destaddr = resolve(dest)) != 0) {
			log(-1, "next_job: resolve destaddr = %x ", (void *)destaddr); /* DEBUG */
			return destaddr;
		}
	log(-1, "next_job: resolve destaddr = %x ", (void *)destaddr); /* DEBUG */	
			

	/* look up address or use the gateway */

	destaddr = resolve_mx(dest);
	log(-1, "next_job: resolve_mx destaddr = %x ", (void *)destaddr); /* DEBUG */
	
	if (destaddr == 0 && (destaddr = resolve (dest)) == 0) {
#ifdef DSERVER
		if (zone_filename (dest, NULL))
			destaddr = gateway;
		else
#endif
		if (Gateway != 0)
			destaddr = Gateway;					/* Use the gateway  					*/
	}

	return destaddr;
}


/*  logerr - Save line in error list */

static void logerr(struct smtpcli *cb, char *line)
{
	struct list *lp, *tp;

	tp = (struct list *)calloc(1, sizeof (struct list));
	log(-1, "logerr: calloc tp = %x ", (void *)tp); /* DEBUG */
	tp->val = strdup(line);						/* implicit malloc					*/
	log(-1, "logerr: strdup tp->val = %x ", (void *)tp->val); /* DEBUG */
	/* find end of list */
	if ((lp = cb->errlog) == NULLLIST)
		cb->errlog = tp;
	else {
		while (lp->next != NULLLIST)
			lp = lp->next;

		lp->next = tp;
	}
}


static int smtpsendfile(struct smtpcli *cb)
{
	int error = 0;

	strcpy(cb->buf, "\n");

	while (fgets (cb->buf, sizeof (cb->buf), cb->tfile) != NULLCHAR) {

		if (cb->buf[0] == '.')						/* handle dot transparency		*/
			usputc(cb->s,'.');						/* add extra '.' 					*/

		usputs (cb->s, cb->buf);
	}

	fclose(cb->tfile);
	cb->tfile = NULLFILE;

	/* Send the end-of-message command */

	if (cb->buf[strlen (cb->buf) - 1] == '\n')
		sendcmd(cb, ".\n");
	else
		sendcmd(cb, "\n.\n");

	return error;
}


/*  sendcmd - Do a printf() on the socket with optional local tracing */

static void sendcmd(struct smtpcli * cb, char *fmt,...)
{
	va_list args;

	va_start(args, fmt);
	if (Smtptrace)	{
		vsprintf(cb->buf, fmt, args);
		tprintf("smtp sent: %s", cb->buf);
	}

	vsprintf(cb->buf, fmt, args);
	usputs(cb->s, cb->buf);
	va_end(args);
}

	
/*  getresp - Wait for, read & display server response. Return the result code. */

static int getresp(struct smtpcli *cb, int mincode)
{
	int rval;
	char line[LINELEN];

	usflush (cb->s);
	for (;;)	{
		/* Get line */
		if (recvline(cb->s, line, LINELEN) == -1) {
			rval = -1;
			break;
		}

		rip(line);								 /* Remove cr/lf */
		rval = atoi(line);

		if (Smtptrace)
			tprintf("smtp recv: %s\n", line);	/* Display to user 				  */

		if (rval >= 500) {							/* Save permanent error replies */
			char tmp[LINELEN];

			if (cb->errlog == NULLLIST) {
				sprintf(tmp, "While talking to %s:",
						 cb->destname);
				logerr(cb, tmp);
			}

			if (cb->buf[0] != '\0') {					/* save offending command */
				rip(cb->buf);
				sprintf(tmp, ">>> %s", cb->buf);
				logerr(cb, tmp);
				cb->buf[0] = '\0';
			}

			sprintf(tmp, "<<< %s", line);
			logerr(cb, tmp);					 			/* save the error reply 	*/
		}

		/* Messages with dashes are continued */

		if (line[3] != '-' && rval >= mincode)
			break;
	}
	
	return rval;
}


static int doverbose(int argc, char *argv[], void *p)
{
	return setbool(&smtpverbose, "SMTP verbose mode", argc, argv);
}


unsigned long golden_ymdhms(long year, long month, long day, long hour, long minute, long second)
{
	long result = 365L * year + day + 31L * (month - 1);

	if (month < 3)
		result += (year - 1L) / 4L - (75L * ((year - 1) / 100 + 1)) / 100L;
	else
		result = result - (40L * month + 230L) / 100L + year / 4L - (75L * (year / 100 + 1)) / 100L;

	result *= 86400L;
	result += 3600 * hour;
	result += 60 * minute;
	result += second;

	return result;
}


static int explock(char *dir, char *pre, char *suf, int32 age)
{
	union FILE_TIME {
		struct ftime ft;
		unsigned int ft_int[2];
	}  file_time;

	struct ffblk fileinfo;
	struct tm *tm_now;
	time_t t_now;
	char t[80];
	unsigned long now, fage, difference;

	sprintf(t, "%s/%s.%s", dir, pre, suf);
	if (findfirst (t, &fileinfo, 0) == -1)
		return 1;

	if (Delaytime == 0)
		return !unlink (t);

	t_now  = time(NULL);
	tm_now = localtime(&t_now);
	now = golden_ymdhms(tm_now->tm_year + 1900, tm_now->tm_mon + 1,
						 tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min,
						 tm_now->tm_sec);
	file_time.ft_int[0] = fileinfo.ff_ftime;
	file_time.ft_int[1] = fileinfo.ff_fdate;
	fage = golden_ymdhms (file_time.ft.ft_year + 1980, file_time.ft.ft_month,
			 file_time.ft.ft_day, file_time.ft.ft_hour, file_time.ft.ft_min,
						  file_time.ft.ft_tsec * 2);
	difference = now - fage;
	if (difference >= Delaytime)
		return !unlink(t);

	return 0;
}

