/*
 *	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.
 */
/* Mods by G1EMM and PA0GRI */
#include "global.h"
#include "commands.h"
#ifndef MSDOS
#include <time.h>
#include <setjmp.h>
#endif
#ifdef UNIX
#ifdef BSD
#undef BSD
#endif
#endif
#include <sys/stat.h>
#include <stdarg.h>
#include "mbuf.h"
#include "proc.h"
#include "socket.h"
#ifdef	LZW
#include "lzw.h"
#else
#include "tcp.h"
#endif
#include "smtp.h"
#include "dirutil.h"
#include "session.h"
#include "mailutil.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: smtpcli.c,v 1.35 2001/05/14 12:05:21 brian Exp $";
#endif

#ifdef HOPPER
static int UseHopper = 0;		/* G8FSL SMTP hopper default OFF */
static int dohopper (int argc, char *argv[], void *p);	/* g8fsl */
#endif

#ifdef UNIX
extern void sm_status (int pos, char *str);
#endif

struct timer Smtpcli_t;
static uint32 Gateway;

int Rewritetrace = 0;		/* used for rewrite trace mode */

#ifdef SMTPTRACE
static unsigned short Smtptrace = 0;	/* used for trace level */
static int dosmtptrace (int argc, char *argv[], void *p);
static char smtp_recv[] = "smtpcli recv: %s\n";
#endif

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

#ifdef	LZW
int Smtpslzw = 1;
static int Smtpclzw = 1;
#endif

static int Smtpbatch = 0;

#ifdef MBFWD
int Smtpbidcheck = 1;
int Smtpheaders = 1;
#endif

int Smtpmode = 0;
int Smtpquiet = 0;
static int UseMX = 0;		/* use MX records in domain lookup */
int SMTPnotify = 1;
int SMTPnorelay = 0;

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

static int dorewritechk (int argc, char *argv[], void *p);
static int dorewritetr (int argc, char *argv[], void *p);
static void dosmtptick (int i, void *p1, void *p2);
static void dosmtpkick (int i, void *p1, void *p2);
static void del_job (struct smtp_job * jp);
static void del_session (struct smtpcli * cb);
static int dogateway (int argc, char *argv[], void *p);
static int dosmtpmaxcli (int argc, char *argv[], void *p);
static int dotimer (int argc, char *argv[], void *p);
static int doquiet (int argc, char *argv[], void *p);

#ifdef LZW
static int doclzw (int argc, char *argv[], void *p);
static int doslzw (int argc, char *argv[], void *p);
#endif

#ifdef HOLDMODS
static int doholdscan (int argc, char *argv[], void *p);
#endif

static int donotify (int argc, char *argv[], void *p);
static int dousemx (int argc, char *argv[], void *p);
static int dosmtpkill (int argc, char *argv[], void *p);
static int dosmtplist (int argc, char *argv[], void *p);
static int dobatch (int argc, char *argv[], void *p);
static void execjobs (void);
static int getresp (struct smtpcli * ftp, char *usebuf, int mincode);
static void logerr (struct smtpcli * cb, char *line);
static struct smtpcli *lookup (uint32 destaddr);
static struct smtpcli *newcb (void);
static int next_job (struct smtpcli * cb);
static void retmail (struct smtpcli * cb);
static void sendcmd (struct smtpcli * cb, const char *fmt,...);
static int smtpsendfile (struct smtpcli * cb);
static int setsmtpmode (int argc, char *argv[], void *p);
static void check_qtime (register struct smtpcli * cb);
static struct smtp_job *setupjob (struct smtpcli * cb, char *id, char *from);
static void smtp_send (int unused, void *cb1, void *p);
static int smtpkick (int argc, char *argv[], void *p);
static int dosmtpt4 (int argc, char *argv[], void *p);

#ifdef MBFWD
static int dobidcheck (int argc, char *argv[], void *p);
static int doheaders (int argc, char *argv[], void *p);
#endif

static int dodtimeout (int argc, char *argv[], void *p);
static int donorelay (int argc, char *argv[], void *p);

static struct cmds Smtpcmds[] =
{
	{ "batch",		dobatch,	0, 0, NULLCHAR },
#ifdef MBFWD
	{ "bidcheck",		dobidcheck,	0, 0, NULLCHAR },
#endif
	{ "dtimeout",		dodtimeout,	0, 0, NULLCHAR },
	{ "gateway",		dogateway,	0, 0, NULLCHAR },
#ifdef MBFWD
	{ "headers",		doheaders,	0, 0, NULLCHAR },
#endif
#ifdef HOLDMODS
	{ "holdscanall",	doholdscan,	0, 0, NULLCHAR },
#endif
#ifdef HOPPER
	{ "hopper",		dohopper,	0, 0, NULLCHAR },
#endif
	{ "kick",		smtpkick,	0, 0, NULLCHAR },
	{ "kill",		dosmtpkill,	0, 2, "smtp kill <jobnumber>" },
	{ "list",		dosmtplist,	0, 0, NULLCHAR },
	{ "maxclients",		dosmtpmaxcli,	0, 0, NULLCHAR },
	{ "mode",		setsmtpmode,	0, 0, NULLCHAR },
	{ "norelay",		donorelay,	0, 0, NULLCHAR },
	{ "notify",		donotify,	0, 0, NULLCHAR },
	{ "quiet",		doquiet,	0, 0, NULLCHAR },
#ifdef	LZW
	{ "reclzw",		doslzw,		0, 0, NULLCHAR },
#endif
	{ "rewritecheck",	dorewritechk,	0, 2, "smtp rewritecheck address" },
	{ "rewritetrace",	dorewritetr,	0, 0, NULLCHAR },
#ifdef	LZW
	{ "sendlzw",		doclzw,		0, 0, NULLCHAR },
#endif
	{ "timer",		dotimer,	0, 0, NULLCHAR },
#ifdef SMTPTRACE
	{ "trace",		dosmtptrace,	0, 0, NULLCHAR },
#endif
	{ "t4",			dosmtpt4,	0, 0, NULLCHAR },
	{ "usemx",		dousemx,	0, 0, NULLCHAR },
	{ NULLCHAR,		NULL,		0, 0, NULLCHAR }
};



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



int Sholdscanall = 0;

#ifdef HOLDMODS
static int
doholdscan (int argc, char *argv[], void *p OPTIONAL)
{
	return setint (&Sholdscanall, "Scan all messages with wordhold/userhold files", argc, argv);
}
#endif



static int Sdtimer = 0;

static int
dodtimeout (int argc, char *argv[], void *p OPTIONAL)
{
	return setint (&Sdtimer, "Delivery timeout (hours)", argc, argv);
}



static int Smtpt4;

static int
dosmtpt4 (int argc, char *argv[], void *p OPTIONAL)
{
	return setint (&Smtpt4, "SMTP T4", argc, argv);
}



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



#ifdef MBFWD
static int
doheaders (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Smtpheaders, "SMTP RFC-822 headers in data for local BBS messages", argc, argv);
}



static int
dobidcheck (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Smtpbidcheck, "SMTP bid checking", argc, argv);
}
#endif


static int
donorelay (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&SMTPnorelay, "Restrict SMTP relay", argc, argv);
}


static int
donotify (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&SMTPnotify, "SMTP incoming mail notification", argc, argv);
}



#ifdef LZW
static int
doclzw (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Smtpclzw, "SMTP send lzw", argc, argv);
}



static int
doslzw (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Smtpslzw, "SMTP recv lzw", argc, argv);
}
#endif



static int
doquiet (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Smtpquiet, "SMTP quiet", argc, argv);
}



static int
dousemx (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&UseMX, "MX records used", argc, argv);
}



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



static int
setsmtpmode (int argc, char *argv[], void *p OPTIONAL)
{
	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:
				tputs ("Usage: smtp mode [queue | route]\n");
				break;
		}
	}
	return 0;
}



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

	if (argc < 2)
		tprintf ("%s\n", inet_ntoa (Gateway));
	else if (!stricmp (argv[1], "none"))
		Gateway = 0;
	else if ((n = resolve (argv[1])) == 0) {
		tprintf (Badhost, argv[1]);
		return 1;
	} else
		Gateway = n;
	return 0;
}



#ifdef HOPPER
static int
dohopper (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&UseHopper, "G8FSL mail hopper", argc, argv);
}
#endif



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



static int
dorewritechk (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
int oldtrace;
char *destination;

	oldtrace = Rewritetrace;
	Rewritetrace = 1;
	destination = rewrite_address (argv[1], 0);
	Rewritetrace = oldtrace;
	tprintf ("Address '%s' rewrites to '%s'\n", argv[1], destination);
	free (destination);
	return 0;
}



static int
dorewritetr (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Rewritetrace, "Rewrite tracing", argc, argv);
}



/* list jobs waiting to be sent in the mqueue */
static int
dosmtplist (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
char tstring[80];
char line[20];
char host[LINELEN];
char to[LINELEN];
char from[LINELEN];
char *cp;
char status;
int flowsave;
struct stat stbuf;
struct tm *tminfo;
FILE *fp;

	flowsave = Current->flowmode;
	Current->flowmode = 1;	/* Enable the more mechanism */
	tputs ("S     Job    Size  Date  Time Host                 From\n");
	(void) filedir (Mailqueue, 0, line);
	while (line[0] != '\0') {
		sprintf (tstring, "%s/%s", Mailqdir, line);
		if ((fp = fopen (tstring, READ_TEXT)) == NULLFILE) {
			tprintf ("Can't open %s: %s\n", tstring, SYS_ERRLIST(errno));
			continue;
		}
		if ((cp = strrchr (line, '.')) != NULLCHAR)
			*cp = '\0';
		sprintf (tstring, "%s/%s.lck", Mailqdir, line);
		if (access (tstring, 0))
			status = ' ';
		else
			status = 'L';
		sprintf (tstring, "%s/%s.txt", Mailqdir, line);
		if (stat (tstring, &stbuf) == -1) {
			sprintf (tstring, "%s/%s.lck", Mailqdir, line);
			unlink (tstring);
			sprintf (tstring, "%s/%s.wrk", Mailqdir, line);
			unlink (tstring);
			goto skip;
		}
		tminfo = localtime (&stbuf.st_ctime);
		host[0] = from[0] = 0;
		(void) fgets (host, sizeof (host), fp);
		rip (host);
		(void) fgets (from, sizeof (from), fp);
		rip (from);
		tprintf ("%c %7s %7ld %02d/%02d %02d:%02d %-20s %s\n        ",
			 status, line, (long) 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);
		}
		tputc ('\n');
skip:
		(void) fclose (fp);
		kwait (NULL);
		(void) filedir (Mailqueue, 1, line);
	}
	Current->flowmode = flowsave;
	return 0;
}



/* kill a job in the mqueue */
static int
dosmtpkill (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
char s[SLINELEN];
char *cp, c;

	sprintf (s, "%s/%s.lck", Mailqdir, argv[1]);
	cp = strrchr (s, '.');
	if (cp != NULLCHAR)	{	/* shouldn't occur */
		if (!access (s, 0)) {
			Current->ttystate.echo = Current->ttystate.edit = 0;
			c = (char) keywait ("Warning, 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);
	}
	return 0;
}



/* Set outbound spool scan interval */
static int
dotimer (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		tprintf ("smtp timer = %lu/%lu\n", read_timer (&Smtpcli_t) / 1000L,
			 dur_timer (&Smtpcli_t) / 1000L);
		return 0;
	}
	Smtpcli_t.func = (void (*)(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_detached_timer (&Smtpcli_t);	/* and fire it up */
	return 0;
}



static void
dosmtpkick (int i OPTIONAL, void *p1, void *p2)
{
uint32 addr = 0;

	if ((int) p2 > 1 && (addr = resolve ((char *) p1)) == 0)
		tprintf (Badhost, (char *) p1);
	else
		dosmtptick (0, (void *) addr, (void *) 0);
	if (p1)
		free (p1);
}



static int
smtpkick (int argc, char *argv[], void *p OPTIONAL)
{
	(void) newproc ("smtp client", 1024, dosmtpkick, 0, (argc > 1) ? (void *) strdup (argv[1]) : (void *) 0, (void *) argc, 0);
	return 0;
}



void
smtptick (void *t)
{
	(void) newproc ("smtp client", 1024, dosmtptick, 0, t, (void *) 0, 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 void
dosmtptick (int t OPTIONAL, void *p1, void *p2 OPTIONAL)
{
register 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;
uint32 destaddr, target;
FILE *wfile;
static int Ssmtpcli = 0;

	target = (uint32) p1;
#ifdef SMTPTRACE
	if (Smtptrace > 5)
		tcmdprintf ("smtp daemon entered, target = %s\n", inet_ntoa (target));
#endif
	/* permit only one general purpose smtp client at a time */
	if (!target) {
		if (Ssmtpcli) {
			start_detached_timer (&Smtpcli_t);
			return;
		} else
			Ssmtpcli = 1;
	}
	kwait (NULL);
	for ((void) filedir (Mailqueue, 0, wfilename); wfilename[0] != '\0'; (void) 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';

		kwait (NULL);

		/* first check to see if the *.txt file is missing */
		sprintf (tmpstring, "%s/%s", Mailqdir, wfilename);
		strcpy (&tmpstring[strlen (tmpstring) - 3], "txt");
		if (access (tmpstring, 0)) {
			/* it's missing, remove *.wrk and *.lck (if exists) */
			sprintf (tmpstring, "%s/%s", Mailqdir, wfilename);
			unlink (tmpstring);
			rmlock (Mailqdir, prefix);
			continue;
		}
		/* lock this file from the smtp daemon */
		if (mlock (Mailqdir, prefix))
			continue;

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

		kwait (NULL);
		if ((destaddr = mailroute (tmpstring)) == 0) {
			(void) fclose (wfile);
			tcmdprintf ("** smtp: Unknown address %s\n", tmpstring);
			rmlock (Mailqdir, prefix);
			continue;
		}
		if (target != 0 && destaddr != target) {
			(void) fclose (wfile);
			rmlock (Mailqdir, prefix);
			continue;	/* Not the proper target of a kick */
		}
		kwait (NULL);
		if ((cb = lookup (destaddr)) == NULLSMTPCLI) {
			/* there are enough processes running already */
			if (Smtpsessions >= Smtpmaxcli) {
#ifdef SMTPTRACE
				if (Smtptrace)
					tcmdprintf ("smtp daemon: too many processes\n");
#endif
				log (-1, "smtp daemon: too many processes");
				(void) fclose (wfile);
				rmlock (Mailqdir, prefix);
				break;
			}
			if ((cb = newcb ()) == NULLSMTPCLI) {
				(void) fclose (wfile);
				rmlock (Mailqdir, prefix);
				kwait (NULL);
				break;
			}
			cb->ipdest = destaddr;
			cb->destname = strdup (tmpstring);
		} else {
			if (cb->lock) {
				/* This system is already is sending mail lets not
				* interfere with its send queue.
				*/
				(void) fclose (wfile);
				rmlock (Mailqdir, prefix);
				continue;
			}
		}

		kwait (NULL);
		(void) fgets (from, LINELEN, wfile);	/* read from */
		rip (from);
		if ((jp = setupjob (cb, prefix, from)) == NULLJOB) {
			(void) fclose (wfile);
			rmlock (Mailqdir, prefix);
			del_session (cb);
			break;
		}
		kwait (NULL);
		while (wfile != NULLFILE && fgets (to, LINELEN, wfile) != NULLCHAR) {
			rip (to);
			if (addlist (&jp->to, to, DOMAIN, to) == NULLLIST) {
				(void) fclose (wfile);
				wfile = NULLFILE;
				del_session (cb);
			}
			kwait (NULL);
		}
		if (wfile != NULLFILE) {
			(void) fclose (wfile);
			wfile = NULLFILE;
		}
#ifdef SMTPTRACE
		if (Smtptrace > 1) {
			tcmdprintf ("queue job %s From: %s To:", prefix, from);
			for (ap = jp->to; ap != NULLLIST; ap = ap->next)
				tcmdprintf (" %s", ap->val);
			tcmdprintf ("\n");
		}
#endif
	}

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

	/* Restart timer */
	start_detached_timer (&Smtpcli_t);
	Ssmtpcli = 0;
	return;
}



/* 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
smtp_send (int unused OPTIONAL, void *cb1, void *p OPTIONAL)
{
register struct smtpcli *cb;
register struct list *tp;
struct sockaddr_in fsocket;
char const *cp;
uint32 Altmx[5];
int rcode;
int rcpts;
int goodrcpt;
int i;
int smtpbatch;
int init = 1;
#ifdef	LZW
int lzwmode, lzwbits, ampr;
#endif

	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);
	(void) sockmode (cb->s, SOCK_ASCII);
	(void) setflush (cb->s, -1);	/* We'll explicitly flush before reading */
#ifdef SMTPTRACE
	if (Smtptrace)
		tcmdprintf ("SMTP client Trying...\n");
#endif
	/* Set a timeout for this connection */
	kalarm (Smtpt4 * 1000L);
	if (connect (cb->s, (char *) &fsocket, SOCKSIZE) != 0) {
		kalarm (0L);
		(void) shutdown (cb->s, 2);	/* K2MF: To make sure it doesn't linger around */
		close_s (cb->s);/* to make sure it's closed */

		/* Selcuk: Let's try other MX's before Gateway */
		if (UseMX && fsocket.sin_addr.s_addr != Gateway
		    && fsocket.sin_addr.s_addr != Ip_addr) {
			if (resolve_amx (cb->destname, fsocket.sin_addr.s_addr, Altmx)) {
#ifdef SMTPTRACE
				if (Smtptrace > 1)
					tcmdprintf ("SMTP client trying MX...\n");
#endif
				for (i = 0; Altmx[i]; i++) {
					fsocket.sin_addr.s_addr = Altmx[i];
					/* n5knx: don't deliver to self, that's a loop!  Also, to avoid indirect
					   loops we ignore sites with lower preference than ourselves */
					if (ismyaddr (fsocket.sin_addr.s_addr))
						break;
					cb->s = socket (AF_INET, SOCK_STREAM, 0);
					(void) sockmode (cb->s, SOCK_ASCII);
					(void) setflush (cb->s, -1);
					kalarm (Smtpt4 * 1000L);
					if (connect (cb->s, (char *) &fsocket, SOCKSIZE) == 0)
						goto connected;
					else {
						kalarm (0L);
						(void) shutdown (cb->s, 2);	/* K2MF: To make sure it doesn't linger around */
						close_s (cb->s);
					}
				}
			}
		}
		if (Smtpt4 && Gateway && (fsocket.sin_addr.s_addr != Gateway)
#ifdef HOPPER
		    && (Gateway != Ip_addr)	/* g8fsl via g0mhd */
# endif
			) {
			/* Try it via the gateway */
			fsocket.sin_addr.s_addr = Gateway;
			cb->s = socket (AF_INET, SOCK_STREAM, 0);
			(void) sockmode (cb->s, SOCK_ASCII);
			(void) setflush (cb->s, -1);	/* We'll explicitly flush before reading */
#ifdef SMTPTRACE
			if (Smtptrace)
				tcmdprintf ("SMTP client Trying gateway...\n");
#endif
			/* Set a timeout for this connection */
			kalarm (Smtpt4 * 1000L);
			if (connect (cb->s, (char *) &fsocket, SOCKSIZE) != 0) {
				kalarm (0L);
				cp = sockerr (cb->s);
				(void) shutdown (cb->s, 2);	/* K2MF: To make sure it doesn't linger around */
				close_s (cb->s);	/* to make sure it's closed */
#ifdef SMTPTRACE
				if (Smtptrace)
					tcmdprintf ("Connect failed: %s\n", cp != NULLCHAR ? cp : "");
#endif
				log (cb->s, "SMTP %s Connect failed: %s", psocket (&fsocket),
				     cp != NULLCHAR ? cp : "");
				goto quit;
			}
		} else
			goto quit;
	}
connected:
	kalarm (0L);
#ifdef SMTPTRACE
	if (Smtptrace)
		tcmdprintf ("Connected\n");
#endif
	kwait (NULL);

#ifdef	LZW
	rcode = getresp (cb, cb->buf, 200);
	if (rcode == -1 || rcode >= 400)
		goto quit;

#ifdef __GNUC__
	ampr = (((fsocket.sin_addr.s_addr & 0xff000000LU) >> 24) == 44);
#else
	ampr = (((fsocket.sin_addr.s_addr & 0xff000000) >> 24) == 44);
#endif

	/* even if LZW is enabled, don't use it for local connections
	   or for hosts not on net 44 */
	if (Smtpclzw && ampr && strncmp (&cb->buf[4], Hostname, strlen (Hostname))) {
		char cpp[LINELEN];

		sendcmd (cb, "XLZW %d %d\n", Lzwbits, Lzwmode);
		usflush (cb->s);
		if (recvline (cb->s, (unsigned char *) cpp, sizeof (cpp)) == -1)
			goto quit;
		rip (cpp);
#ifdef	SMTPTRACE
		if (Smtptrace)
			tcmdprintf (smtp_recv, cpp);	/* Display to user */
#endif
		rcode = lzwmode = lzwbits = 0;
		sscanf (cpp, "%d %d %d", &rcode, &lzwbits, &lzwmode);
		if ((rcode >= 200) && (rcode < 300)) {
			smtpbatch = 1;
			if (lzwmode != Lzwmode || lzwbits != Lzwbits) {
				lzwmode = LZWCOMPACT;
				lzwbits = LZWBITS;
			}
			lzwinit (cb->s, lzwbits, lzwmode);
		} else
			smtpbatch = Smtpbatch;
	} else
		smtpbatch = Smtpbatch;
#else
	smtpbatch = Smtpbatch;
	if (!smtpbatch) {
		rcode = getresp (cb, NULLCHAR, 200);
		if (rcode == -1 || rcode >= 400)
			goto quit;
	}
#endif
	/* Say HELO */
	sendcmd (cb, "HELO %s\n", Hostname);
	if (!smtpbatch) {
		rcode = getresp (cb, NULLCHAR, 200);
		if (rcode == -1 || rcode >= 400)
			goto quit;
	}
	do {			/* For each message... */

		kwait (NULL);
		/* if this file open fails, skip it */
		if ((cb->tfile = fopen (cb->tname, READ_TEXT)) == NULLFILE)
			continue;
		if (!filelength (fileno (cb->tfile))) {
			log (-1, "Skipping null length SMTP data file");
			(void) fclose (cb->tfile);
			cb->tfile = NULLFILE;
			(void) unlink (cb->tname);
			(void) unlink (cb->wname);	/* unlink workfile */
			continue;
		}
		/* Send MAIL and RCPT commands */
		sendcmd (cb, "MAIL FROM:<%s>\n", cb->jobq->from);
		if (!smtpbatch) {
			rcode = getresp (cb, NULLCHAR, 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, NULLCHAR, 200);
				if (rcode == -1)
					goto quit;
				if (rcode < 400)
					goodrcpt = 1;	/* At least one good */
			}
			rcpts++;
		}
		/* Send DATA command */
		sendcmd (cb, "DATA\n");
		kwait (NULL);
		if (!smtpbatch) {
			rcode = getresp (cb, NULLCHAR, 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.
			 */
#ifdef	LZW
			for (i = init ? 2 : 1; i > 0; i--) {
#else
			for (i = init ? 3 : 1; i > 0; i--) {
#endif
				rcode = getresp (cb, NULLCHAR, 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, NULLCHAR, 200);
				if (rcode == -1)
					goto quit;
				if (rcode < 400)
					goodrcpt = 1;	/* At least one good */
			}
			kwait (NULL);
			/* 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, NULLCHAR, 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 */
		(void) smtpsendfile (cb);

		/* Wait for the OK response */
		rcode = getresp (cb, NULLCHAR, 200);
		if (rcode == -1)
			goto quit;
		kwait (NULL);
		if ((rcode >= 400) && (rcode < 500))	/* temp failure? */
			check_qtime (cb);
		if ((rcode >= 200 && rcode < 300) || rcode >= 500) {
			/* if a good transfer or permanent failure remove job */

			if (cb->errlog != NULLLIST)
				retmail (cb);
#ifdef STATS_MSG
			STATS_addmsg (1, 1);
#endif
			/* 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 (cb->jobq->next != NULLJOB) {
			/*
			 * Reset the remote sendmail's state.
			 * This is just to deal with certain stupidities in early
			 * versions of sendmail 8.6.  We don't check the value of
			 * rcode because we don't really care what it is and some
			 * supposedly SMTP compliant mailers don't recognize RSET.
			 */
			sendcmd (cb, "RSET\n");
			/* Wait for response */
			rcode = getresp (cb, NULLCHAR, 200);
			if (rcode == -1)
				goto quit;
		}
	} while (next_job (cb));
quit:
	kwait (NULL);
	sendcmd (cb, "QUIT\n");
	check_qtime (cb);
	if (cb->errlog != NULLLIST) {
		retmail (cb);
		(void) unlink (cb->wname);	/* unlink workfile */
		(void) unlink (cb->tname);	/* unlink text */
	}
	close_s (cb->s);
	if (cb->tfile != NULLFILE)
		(void) fclose (cb->tfile);
	cb->lock = 0;
	del_session (cb);
}



/* check if msg stayed too long in the mqueue */
static void
check_qtime (register struct smtpcli *cb)
{
struct stat tstat;
time_t now;
char tmp[80];

	if (cb == NULLSMTPCLI || cb->jobq == NULLJOB)
		return;

	if (Sdtimer && cb->errlog == NULLLIST) {
		(void) time (&now);
		if (cb->tfile == NULLFILE)
			if ((cb->tfile = fopen (cb->tname, READ_TEXT)) == NULLFILE)
				return;

		(void) fstat (fileno (cb->tfile), &tstat);
		(void) fclose (cb->tfile);
		cb->tfile = NULLFILE;
		if ((now - tstat.st_ctime) > (time_t) (Sdtimer * 3600L)) {
			sprintf (tmp, " >>> Your message could not be delivered for %d hour(s); giving up!", Sdtimer);
			logerr (cb, tmp);
		}
	}
	return;
}



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

	if (cb == NULLSMTPCLI)
		return;
	for (i = 0; i < MAXSESSIONS; i++)
		if (cli_session[i] == cb) {
			cli_session[i] = NULLSMTPCLI;
			break;
		}
	free (cb->wname);
	free (cb->tname);
	free (cb->destname);
	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 (register struct smtp_job *jp)
{
	if (*jp->jobname != '\0')
		rmlock (Mailqdir, jp->jobname);
	free (jp->from);
	del_list (jp->to);
	free ((char *) jp);
}



/* delete a list of list structs */
void
del_list (struct list *lp)
{
register struct list *tp, *tp1;

	for (tp = lp; tp != NULLLIST; tp = tp1) {
		tp1 = tp->next;
		free (tp->val);
		free (tp->orig);
		free ((char *) tp);
	}
}



/* stub for calling mdaemon to return message to sender */
static void
retmail (struct smtpcli *cb)
{
FILE *infile;

#ifdef SMTPTRACE
	if (Smtptrace > 5)
		tcmdprintf ("smtp job %s returned to sender\n", cb->wname);
#endif
	if ((infile = fopen (cb->tname, READ_TEXT)) == NULLFILE)
		return;
	/*	mdaemon(infile,cb->jobq->from,cb->errlog,1);	*/
	(void) mdaemon (infile, "sysop", cb->errlog, 1);
	(void) fclose (infile);
}



/* look to see if a smtp control block exists for this ipdest */
static struct smtpcli *
lookup (uint32 destaddr)
{
register 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;
}



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

	for (i = 0; i < MAXSESSIONS; i++) {
		if (cli_session[i] == NULLSMTPCLI) {
			cb = (struct smtpcli *) callocw (1, sizeof (struct smtpcli));

			cb->wname = mallocw ((unsigned) strlen (Mailqdir) + JOBNAME);
			cb->tname = mallocw ((unsigned) strlen (Mailqdir) + JOBNAME);
			cli_session[i] = cb;
			Smtpsessions++;	/* number of connections active */
			return (cb);
		}
	}
	return NULLSMTPCLI;
}



static void
execjobs (void)
{
register struct smtpcli *cb;
register int i, insave, outsave;

	for (i = 0; i < MAXSESSIONS; i++) {
		kwait (NULL);
		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);

		/* This solves the nasty hack in mailbox.c, from Mark ve3dte */
		insave = Curproc->input;
		outsave = Curproc->output;
		Curproc->input = -1;
		Curproc->output = -1;
		/* Now we can call newproc with null parent sockets! */
		(void) newproc ("smtp_send", 1024, smtp_send, 0, cb, NULL, 0);
		/* now restore parent sockets so parent can continue */
		Curproc->input = insave;
		Curproc->output = outsave;

#ifdef SMTPTRACE
		if (Smtptrace)
			tcmdprintf ("Trying Connection to %s\n", inet_ntoa (cb->ipdest));
#endif
	}
}



/* add this job to control block queue */
static struct smtp_job *
setupjob (struct smtpcli *cb, char *id, char *from)
{
register struct smtp_job *p1, *p2;

	p1 = (struct smtp_job *) callocw (1, sizeof (struct smtp_job));

	p1->from = strdup (from);
	strncpy (p1->jobname, id, 9);
	/* 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 */
static int
next_job (cb)
register struct smtpcli *cb;
{
	register 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);
#ifdef SMTPTRACE
	if (Smtptrace > 5)
		tcmdprintf ("sending job %s\n", jp->jobname);
#endif
	return 1;
}



/* Mail routing function. For now just use the hosts file */
uint32
mailroute (char *dest)
{
uint32 destaddr = 0L;
#ifdef HOPPER
struct route *rp;
#endif

#ifdef SMTPTRACE
	if (Smtptrace > 6)
		tcmdprintf ("MX lookup for = %s\n", dest);
#endif

#ifdef HOPPER
	if (*dest == '\0') {
#ifdef SMTPTRACE
		if (Smtptrace > 6)
			tcmdprintf ("Local mail\n");
#endif
		return Ip_addr;
	}
#endif

	/* look up address or use the gateway */
	if (UseMX) {
		destaddr = resolve_mx (dest);
#ifdef SMTPTRACE
		if (Smtptrace > 6)
			tcmdprintf ("MX lookup returned = %s\n", inet_ntoa (destaddr));
#endif
	}
	kwait (NULL);
	if (destaddr == 0L)
		if ((destaddr = resolve (dest)) == 0L)
			if (Gateway != 0)
				destaddr = Gateway;	/* Use the gateway  */

#ifdef SMTPTRACE
	if (Smtptrace > 6)
		tcmdprintf ("Address resolver returned = %s\n", inet_ntoa (destaddr));
#endif

#ifdef HOPPER
	if (UseHopper && (destaddr != Ip_addr)) {
		if ((rp = rt_lookup (destaddr)) != NULLROUTE)
			if (rp->gateway != 0L)
				destaddr = rp->gateway;
#ifdef SMTPTRACE
		if (Smtptrace > 6)
			tcmdprintf ("Hopper returned = %s\n", inet_ntoa (destaddr));
#endif
	}
#endif

#ifdef SMTPTRACE
	if (Smtptrace > 6)
		tcmdprintf ("Mailroute returned = %s\n", inet_ntoa (destaddr));
#endif
	return destaddr;
}



/* save line in error list */
static void
logerr (struct smtpcli *cb, char *line)
{
register struct list *lp, *tp;

	tp = (struct list *) callocw (1, sizeof (struct list));

	tp->val = strdup (line);
	/* 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 (register struct smtpcli *cb)
{
int error = 0;
int lastlineCR = 1;

	strcpy (cb->buf, "\n");
	while (fgets (cb->buf, sizeof (cb->buf), cb->tfile) != NULLCHAR) {
		/* Escape a '.' character at the beginning of a line */
		if (*(cb->buf) == '.')
			usputc (cb->s, '.');
		usputs (cb->s, cb->buf);
		/* Did this line end in CR? */
		lastlineCR = (cb->buf[strlen (cb->buf) - 1] == '\n');
		kwait (NULL);
	}
	(void) fclose (cb->tfile);
	cb->tfile = NULLFILE;
	/* Send the end-of-message command */
	if (lastlineCR)
		sendcmd (cb, ".\n");
	else
		sendcmd (cb, "\n.\n");
	return error;
}



/* do a printf() on the socket with optional local tracing */
static void
sendcmd (struct smtpcli *cb, const char *fmt,...)
{
va_list args;

	va_start (args, fmt);	/*lint !e718 !e746 */
	(void) vsprintf (cb->buf, fmt, args);
#ifdef	SMTPTRACE
	if (Smtptrace) {
		tcmdprintf ("smtp sent: ");
#ifdef UNIX
		sm_status (0, cb->buf);
#else
		(void) usvprintf (Curproc->output, fmt, args);
#endif
	}
#endif
	usputs (cb->s, cb->buf);
	va_end (args);
}



/* Wait for, read and display response from server. Return the result code. */
static int
getresp (
struct smtpcli *cb,
char *usebuf,
int mincode			/* Keep reading until at least this code comes back */
) {
int rval;
char buf[LINELEN], *line;

	line = (usebuf) ? usebuf : buf;
	usflush (cb->s);
	for (;;) {
		kwait (NULL);
		/* Get line */
		kalarm (Smtpt4 * 1000L);	/* set a timeout */
		if (recvline (cb->s, (unsigned char *) line, LINELEN) == -1) {
			kalarm (0L);	/* reset a timeout */
			log (cb->s, "SMTP Client timeout waiting for response");
			rval = -1;
			break;
		}
		kalarm (0L);	/* reset a timeout */
		rip (line);	/* Remove cr/lf */
		rval = atoi (line);
#ifdef	SMTPTRACE
		if (Smtptrace)
			tcmdprintf (smtp_recv, line);	/* Display to user */
#endif
		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;
}
