/*
 *                      S M T P S R V R . C
 *
 *              SMTP Mail Server for MMDF under 4.2bsd
 *
 * Eric Shienbrood (BBN) 3 Apr 81 - SMTP server, based on BBN FTP server
 * Modified to talk to MMDF Jun 82 by Donn Neuhengen.
 * Doug Kingston, BRL: Took a machete to large chunks of unnecessary code.
 *      Reimplemented the interface to MMDF and used 4.2BSD style networking.
 *      Usage:  smtpsrvr <them> <us> <channel>
 */

#include "util.h"
#include "mmdf.h"
#include "ch.h"
#include "ap.h"
#include "phs.h"
#include "smtp.h"
#include <stdio.h>
#include <signal.h>
#include <sys/stat.h>
#include <pwd.h>
#ifndef NOFCNTL
#include <fcntl.h>
#endif

#include "ns.h"

#define BYTESIZE        8

/* mmdf configuration variables */
extern LLog *logptr;
extern char *supportaddr;
extern int errno;               /* Has Unix error codes */
extern int numfds;

extern char *getline();
extern char *index();
extern char *rindex();
extern char *malloc();
extern char *strdup();
extern struct passwd *getpwmid();
extern char *multcat();
extern Chan *ch_h2chan();

char *progname, *us, *them, *channel;   /* Arguments to program */
char *sender = 0;                       /* address of mail sender */
Chan *chanptr;                          /* pointer to incoming channel */

#define BUFL 600                /* length of buf */
char    buf[BUFL];              /* general usage */
char    netbuf[BUFL];           /* the place that has the valid characters */
char	saveaddr[BUFL];         /* save previous address in expn_save */
int     savecode;               /* save previous result code in expn_save */
int     expn_count;             /* number of expn expansions */
int     netcount = 0;           /* number of valid characters in netbuf */
char    *netptr  = netbuf;      /* next character to come out of netbuf */
char    *arg;                   /* zero if no argument - pts to comm param */
int     dont_mung;              /* used by getline() to limit munging */
int     numrecipients = 0;      /* number of valid recipients accepted */
int     stricked;               /* force rejection of non validated hosts */
#ifdef NODOMLIT
int	themknown=TRUE;		/* do we have symbolic name for them? */
#endif /* NODOMLIT */
char    *addrfix();
int     vrfy_child;             /* pid of child that handles vrfy requests */
int     vrfy_p2c[2];            /* fd's for vrfy's parent-to-child pipe */
int     vrfy_c2p[2];            /* fd's for vrfy's child-to-parent pipe */
FILE    *vrfy_out;              /* fd for vrfy's parent to write to child */
FILE    *vrfy_in;               /* fd for vrfy's parent to read from child */

/* character defines */
#define CR      '\r'    /* carriage return */
#define LF      '\n'    /* line feed */
#define CNULL   '\0'    /* null */


/****************************************************************
 *                                                              *
 *      C O M M A N D   D I S P A T C H   T A B L E             *
 *                                                              *
 ****************************************************************/

int helo(), mail(), quit(), help(), rcpt(), confirm();
int data(), rset(), reject(), expn(), vrfy();

struct comarr           /* format of the command table */
{
	char *cmdname;          /* ascii name */
	int (*cmdfunc)();       /* command procedure to call */
} commands[] = {
	"helo", helo,           "noop", confirm,
	"mail", mail,           "data", data,
	"rcpt", rcpt,           "help", help,
	"quit", quit,           "rset", rset,
        "expn", expn,           "vrfy", vrfy,
	NULL, NULL
};


/*
 *              M A I N
 *
 *      Takes commands from the assumed network connection (file desc 0)
 *      under the assumption that they follow the ARPA network mail
 *      transfer protocol RFC 788 and appropriate modifications.
 *      Command responses are returned via file desc 1.
 *
 *      There is a small daemon waiting for connections to be
 *      satisfied on socket 25 from any host.  As connections
 *      are completed by the ncpdaemon, the returned file descriptor
 *      is setup as the standard input (file desc 0) and standard
 *      output (file desc 1) and this program is executed to
 *      deal with each specific connection.  This allows multiple
 *      server connections, and minimizes the system overhead
 *      when connections are not in progress.  File descriptors
 *      zero and one are used to ease production debugging and
 *      to allow the forking of other relavent Unix programs
 *      with comparative ease.
 *
 *              while commands can be gotten
 *                      find command procedure
 *                              call command procedure
 *                              get next command
 *                      command not found
 *
 */
main (argc, argv)
int argc;
char **argv;
{
	register struct comarr *comp;
	char    replybuf[128];
	char *i;
	long atime;
	Chan *curchan;
	char    tmp_buf[LINESIZE];
	char    tmpstr[LINESIZE];
	char    *Ags[20];
	int     n, Agc;

	progname = argv[0];
	mmdf_init( progname );

	if (argc != 4){
		ll_log( logptr, LLOGFAT, "wrong number of args!" );
		exit(NOTOK);
	}

	/* force rejection of unknown hosts */
	if (*progname == 'r')
		stricked++;

	/*
	 * first look up address of sender. Used to be in server but
	 * now must be in this routine so that we can send a reject
	 * reply.
	 */
	them = argv[1];
	us = argv[2];
				/* A numeric address - don't like it */
	if(*them >= '0' && *them <= '9')
	    if ( stricked){
		ll_log (logptr, LLOGTMP, "smtpsrvr can't lookup '%s'", them);
		sprintf(replybuf,
		       "421 %s: Cannot resolve your address. '%s'\r\n",us,them);
		netreply (replybuf);
		exit (-1);
	    }
	    else {   /* make into [x.x.x.x] format */
		them = strdup(them);
	        strcpy(tmpstr, them);  
		sprintf(them, "[%s]", tmpstr);
#ifdef NODOMLIT
		themknown = FALSE;
#endif /* NODOMLIT */
	    }
	    
	/*
	 * found out who you are I might even believe you.
	 */

	/*
	 * the channel arg is now a comma seperated list of channels
	 * useful for multiple sources ( As on UCL's ether )
	 */
	strcpy (tmp_buf, argv[3]);
	Agc = str2arg (tmp_buf, 20, Ags, (char *)0);
	channel = Ags[Agc-1];
	for(chanptr = (Chan *)0, n = 0 ; n < Agc ; n++){
		if ( (curchan = ch_nm2struct(Ags[n])) == (Chan *) NOTOK) {
			ll_log (logptr, LLOGTMP, "smtpsrvr (%s) bad channel",
				Ags[n]);
			continue;
		}
		/*
		 * Is this a valid host for this channel ?
		 */
		switch(tb_k2val (curchan -> ch_table, TRUE, them, tmpstr)){
		default:        /* Either NOTOK or MAYBE */
			if ((n != (Agc-1)) || stricked)
				continue;
			/* fall through so we get some channel name to use */
		case OK:
			chanptr = curchan;
			channel = curchan -> ch_name;
			break;
		}
		break;
	}
	time(&atime);

	if (chanptr == (Chan *) 0){
		ll_log (logptr, LLOGTMP, "smtpsrvr (%s) no channel for host",
								    them);
		sprintf (replybuf,
			"421 %s: Your name, '%s', is unknown to us.\r\n",
			us, them);
		netreply (replybuf);
		exit (-1);
	}
	ch_llinit (chanptr);
	ll_log( logptr, LLOGGEN, "OPEN: %s %.19s (%s)",
			them, ctime(&atime), channel);
	phs_note(chanptr, PHS_RESTRT);

	mmdfstart();

	/* say we're listening */
	sprintf (replybuf, "220 %s Server SMTP (Complaints/bugs to:  %s)\r\n",
			us, supportaddr);
	netreply (replybuf);

nextcomm:
	while (i = getline())
	{
		if (i == (char *)NOTOK)         /* handle error ??? */
			byebye( 1 );

		/* find and call command procedure */
		comp = commands;
		while( comp->cmdname != NULL)   /* while there are names */
		{
		    if (strcmp(buf, comp->cmdname) == 0) /* a winner */
		    {
			(*comp->cmdfunc)();     /* call comm proc */
			goto nextcomm;          /* back for more */
		    }
		    comp++;             /* bump to next candidate */
		}
		netreply("500 Unknown or unimplemented command\r\n" );
		ll_log(logptr, LLOGBTR, "unknown command '%s'", buf);
	}
	byebye(0);
}

/*name:
	getline

function:
	get commands from the standard input terminated by <cr><lf>.
	afix a pointer( arg ) to any arguments passed.
	ignore carriage returns
	map UPPER case to lower case.
	manage the netptr and netcount variables

algorithm:
	while we havent received a line feed and buffer not full

		if netcount is zero or less
			get more data from net
				error: return 0
		check for delimiter character
			null terminate first string
			set arg pointer to next character
		check for carriage return
			ignore it
		if looking at command name
			convert upper case to lower case

	if command line (not mail)
		null terminate last token
	manage netptr

returns:
	0 for EOF
	-1 when an error occurs on network connection
	ptr to last character (null) in command line

globals:
	dont_mung
	netcount=
	netptr =
	buf=
*/

char *
getline()
{
	register char *inp;     /* input pointer in netbuf */
	register char *outp;    /* output pointer in buf */
	register int c;         /* temporary char */
	extern char *progname;

	inp = netptr;
	outp = buf;
	arg = 0;

	do
	{
		if( --netcount <= 0 )
		{
			if (setjmp(timerest)) {
				ll_log( logptr, LLOGTMP,
					"%s net input read error (%d)",
					them, errno);
				return((char *)NOTOK);
			}
			s_alarm (NTIMEOUT);
			netcount = read (0, netbuf, 512);
			s_alarm( 0 );
			if (netcount == 0)      /* EOF */
				return( 0 );
			if (netcount < 0) {     /* error */
				ll_log( logptr, LLOGTMP,
					"%s net input read error (%d)",
					them, errno);
				return((char *)NOTOK);
			}
			inp = netbuf;
		}
		c = *inp++ & 0377;

		if (c == '\r' ||        /* ignore CR */
		    c >= 0200)          /* or any telnet codes that */
			continue;       /*  try to sneak through */
		if (dont_mung == 0 && arg == NULL)
		{
			/* if char is a delim afix token */
			if (c == ' ' || c == ',')
			{
				c = CNULL;       /* make null term'ed */
				arg = outp + 1; /* set arg ptr */
			}
			else if (c >= 'A' && c <= 'Z')
			/* do case mapping (UPPER -> lower) */
				c += 'a' - 'A';
		}
		*outp++ = c;
	} while( c != '\n' && outp < &buf[BUFL] );

	if( dont_mung == 0 )
		*--outp = 0;            /* null term the last token */

	/* scan off blanks in argument */
	if (arg) {
		while (*arg == ' ')
			arg++;
		if (*arg == '\0')
			arg = 0;        /* if all blanks, no argument */
	}
	if (dont_mung == 0)
		ll_log( logptr, LLOGFTR, "'%s', '%s'", buf,
			arg == 0 ? "<noarg>" : arg );

	/* reset netptr for next trip in */
	netptr = inp;
	/* return success */
	return (outp);
}

/*
 *  Process the HELO command
 */
helo()
{
	char replybuf[128];

/*
 * Use this if you wish to be forgiving of hosts who don't announce
 * their full domain name:
 *
 *      (void) sprintf(replybuf, "%s.%s.%s", them, locname, locdomain);
 *      if(arg == 0 || !lexequ(arg, them) && !lexequ(arg, replybuf))
 *              sprintf(replybuf, "250 %s - you are a charlatan\r\n", us);
 *      else  {                
 *		if (lexequ(arg, replybuf))
 *                      them = strdup(replybuf);
 *              sprintf (replybuf, "250 %s\r\n", us);
 *      }                      
 *      netreply (replybuf);   
 *
 * -- DSH (suggested by: Hans van Staveren <sater@cs.vu.nl>)
 */

	if(arg == 0 || !lexequ(arg, them))
		sprintf(replybuf, "250 %s - you are a charlatan\r\n", us);
	else 
		sprintf (replybuf, "250 %s\r\n", us);
	netreply (replybuf);
}

/*
 *      mail
 *
 *      handle the MAIL command  ("MAIL from:<user@host>")
 */
mail()
{
	char    replybuf[256];
	char    info[128];
	char    *lastdmn;
	struct rp_bufstruct thereply;
	int	len;
	AP_ptr  domain, route, mbox, themap, ap_sender;

	if (arg == 0 || *arg == 0) {
		netreply("501 No argument supplied\r\n");
		return;
	} else if( sender ) {
		netreply("503 MAIL command already accepted\r\n");
		return;
	} else if (!equal(arg, "from:", 5)) {
		netreply("501 No sender named\r\n");
		return;
	}

	ll_log( logptr, LLOGFTR, "mail from: '%s'", arg );

	/* Scan FROM: parts of arg */
	sender = index (arg, ':') + 1;
	sender = addrfix( sender );
	/*
	 * If the From part is not the same as where it came from
	 * then add on the extra part of the route.
	 */

#ifdef NODOMLIT
	if(themknown && ((ap_sender = ap_s2tree(sender)) != (AP_ptr)NOTOK)){
#else
	if ((ap_sender = ap_s2tree(sender)) != (AP_ptr)NOTOK){
#endif /* NODOMLIT */
		/*
		 * this must be a bit of a sledge hammer approach ??
		 */
		ap_t2parts(ap_sender, (AP_ptr *)0, (AP_ptr *)0,
						&mbox, &domain, &route);
		themap = ap_new(APV_DOMN, them);
		if(ap_dmnormalize(themap, (Chan *)0) == MAYBE)
			goto tout;
		if(route != (AP_ptr)0){
			/*
			 * only normalize the bits that we need
			 */
			if(ap_dmnormalize(route, (Chan *)0) == MAYBE)
				goto tout;
			lastdmn = route->ap_obvalue;
			}
		else if(domain != (AP_ptr)0) {
			if(ap_dmnormalize(domain, (Chan *)0) == MAYBE)
				goto tout;
			lastdmn = domain->ap_obvalue;
		}
		else /* is this a protocol violation? - add themap as domain? */
			lastdmn = (char *)0;

		/*
		 * Check the from part here. Make exceptions for local machines
		 */
		if (	lexequ(themap->ap_obvalue, lastdmn)
		     || islochost(themap->ap_obvalue, lastdmn))
				ap_free(themap);
		else {
			if (route != (AP_ptr)0)
				themap->ap_chain = route;
				route = themap;
		}
		sender = ap_p2s((AP_ptr)0, (AP_ptr)0, mbox, domain, route);
		if(sender == (char *)MAYBE){    /* help !! */
	tout:;
			sender = (char *) 0;
			netreply("451 Nameserver timeout during parsing\r\n");
			return;
		}
		strcpy(arg, sender);
		free(sender);
		sender = arg;
	}

	/* Supply necessary flags, "tiCHANNEL" will be supplied by winit */
	if (*sender == '\0') {
		/* No return mail */
/*  until mailing list fix is done -- er.. *WHAT* mailing list fix -- [DSH]
		sprintf( info, "mvqdh%s*k%d*", them, NS_NETTIME ); */
		sprintf( info, "mvqh%s*k%d*", them, NS_NETTIME );
		sender = "Orphanage";           /* Placeholder */
	} else
/*  until mailing list fix is done 
		sprintf( info, "mvdh%s*k%d*", them, NS_NETTIME ); */
		sprintf( info, "mvh%s*k%d*", them, NS_NETTIME );

	if( rp_isbad( mm_winit(channel, info, sender))) {
		netreply("451 Temporary problem initializing\r\n");
		sender = (char *) 0;
		mm_end( NOTOK );
		mmdfstart();
	} else if( rp_isbad( mm_rrply( &thereply, &len ))) {
		netreply( "451 Temporary problem initializing\r\n" );
		sender = (char *) 0;
		mm_end( NOTOK );
		mmdfstart();
	} else if( rp_gbval( thereply.rp_val ) == RP_BNO) {
		sprintf (replybuf, "501 %s\r\n", thereply.rp_line);
		netreply (replybuf);
		sender = (char *) 0;
		mm_end( NOTOK );
		mmdfstart();
	} else if( rp_gbval( thereply.rp_val ) == RP_BTNO) {
		sprintf (replybuf, "451 %s\r\n", thereply.rp_line);
		netreply (replybuf);
		sender = (char *) 0;
		mm_end( NOTOK );
		mmdfstart();
	} else
		netreply("250 OK\r\n");
	numrecipients = 0;
}

islochost(them, from)
char *them, *from;
{
	int lo, lt;
	extern char *locfullmachine, *locfullname;

	if (locfullmachine == (char *)0)
		return(0);
	lo = strlen(locfullname);
	lt = strlen(them);
	return(
		/* return true if they claim to be us */
		lexequ(from, locfullname)
		/* and are one of our local machines */
	     && lt > lo
	     && them[lt - lo - 1] == '.'
	     && lexequ(&them[lt - lo], locfullname)
	);
}

/*
 *  Process the RCPT command  ("RCPT TO:<forward-path>")
 */
rcpt()
{
	register char *p;
	struct rp_bufstruct thereply;
	char    replybuf[256];
	int     len;

	/* parse destination arg */
	if( sender == (char *)0 ) {
		netreply("503 You must give a MAIL command first\r\n");
		return;
	} else if (arg == (char *)0 || !equal(arg, "to:", 3)) {
		netreply("501 No recipient named.\r\n");
		return;
	}
	p = index( arg, ':' ) + 1;
	p = addrfix( p );

	if (setjmp(timerest)) {
		netreply( "451 Mail system problem\r\n" );
		return;
	}
	s_alarm (DTIMEOUT);
	if( rp_isbad( mm_wadr( (char *)0, p ))) {
		if( rp_isbad( mm_rrply( &thereply, &len )))
			netreply( "451 Mail system problem\r\n" );
		else {
			sprintf (replybuf, "451 %s\r\n", thereply.rp_line);
			netreply (replybuf);
		}
	} else {
		if( rp_isbad( mm_rrply( &thereply, &len )))
			netreply("451 Mail system problem\r\n");
		else if( rp_gbval( thereply.rp_val ) == RP_BNO) {
			sprintf (replybuf, "550 %s\r\n", thereply.rp_line);
			netreply (replybuf);
		}
		else if( rp_gbval( thereply.rp_val ) == RP_BTNO) {
			sprintf (replybuf, "451 %s\r\n", thereply.rp_line);
			netreply (replybuf);
		}
		else {
			netreply("250 Recipient OK.\r\n");
			numrecipients++;
		}
	}
	s_alarm (0);
}

/*
 *      ADDRFIX()  --  This function takes the SMTP "path" and removes
 *      the leading and trailing "<>"'s which would make the address
 *      illegal to RFC822 mailers.  Note that although the spec states
 *      that the angle brackets are required, we will accept addresses
 *      without them.   (DPK@BRL, 4 Jan 83)
 */
char *
addrfix( addrp )
char *addrp;
{
	register char   *cp;

	if( cp = index( addrp, '<' )) {
		addrp = ++cp;
		if( cp = rindex( addrp, '>' ))
			*cp = 0;
	}
	compress (addrp, addrp);
#ifdef DEBUG
	ll_log( logptr, LLOGFTR, "addrfix(): '%s'", addrp );
#endif
	return( addrp );
}

/*
 *  Process the DATA command.  Send text to MMDF.
 */
data()
{
	register char *p, *bufptr;
	time_t  tyme;
	int     errflg, werrflg;
	struct rp_bufstruct thereply;
	int     len, msglen;

	errflg = werrflg = msglen = 0;
	if (numrecipients == 0) {
		netreply("503 No recipients have been specified.\r\n");
		return;
	}

	if (setjmp(timerest)) {
		netreply( "451 Mail system problem\r\n" );
		return;
	}
	s_alarm (DTIMEOUT);
	if( rp_isbad(mm_waend())) {
		netreply("451 Unknown mail system trouble.\r\n");
		return;
	}
	s_alarm (0);

	netreply ("354 Enter Mail, end by a line with only '.'\r\n");

	dont_mung = 1;      /* tell getline only to drop cr */
#ifdef DEBUG
	ll_log( logptr, LLOGFTR, "... body of message ..." );
#endif
	while (1) {             /* forever */
		if ((p = getline()) == 0) {
			p = "\n***Sender closed connection***\n";
			mm_wtxt( p , strlen(p) );
			errflg++;
			break;
		}
		if (p == (char *)NOTOK) {
			p = "\n***Error on net connection***\n";
			mm_wtxt( p , strlen(p) );
			if (!errflg++)
				ll_log(logptr, LLOGTMP,
					"netread error from host %s", them);
			break;
		}

		/* are we done? */
		if (buf[0] == '.')
			if (buf[1] == '\n')
				break;          /* yep */
			else
				bufptr = &buf[1];       /* skip leading . */
		else
			bufptr = &buf[0];
		/* If write error occurs, stop writing but keep reading. */
		if (!werrflg) {
			if (setjmp(timerest)) {
				netreply( "451 Mail system problem\r\n" );
				return;
			}
			s_alarm (DTIMEOUT);
			msglen += (len = p-bufptr);
			if( rp_isbad(mm_wtxt( bufptr, len ))) {
				werrflg++;
				ll_log( logptr, LLOGTMP, "error from submit");
			}
			s_alarm (0);
		}
	}
	dont_mung = 0;  /* set getline to normal operation */
#ifdef DEBUG
	ll_log( logptr, LLOGBTR, "Finished receiving text." );
#endif

	if (werrflg) {
		netreply("451-Mail trouble (write error to mailsystem)\r\n");
		netreply("451 Please try again later.\r\n");
		byebye( 1 );
	}
	if (errflg) {
	    time (&tyme);
	    byebye(1);
	}

	if (setjmp(timerest)) {
		netreply( "451 Mail system problem\r\n" );
		return;
	}
	s_alarm (DTIMEOUT);
	if( rp_isbad(mm_wtend()) || rp_isbad( mm_rrply( &thereply, &len)))
		netreply("451 Unknown mail trouble, try later\r\n");
	else if( rp_isgood(thereply.rp_val)) {
		sprintf (buf, "250 %s\r\n", thereply.rp_line);
		netreply (buf);
		phs_msg(chanptr, numrecipients, (long) msglen);
	}
	else if( rp_gbval(thereply.rp_val) == RP_BNO) {
		sprintf (buf, "554 %s\r\n", thereply.rp_line);
		netreply (buf);
	}
	else {
		sprintf (buf, "451 %s\r\n", thereply.rp_line);
		netreply (buf);
	}
	s_alarm (0);
	sender = (char *) 0;
	numrecipients = 0;
}

/*
 *  Process the RSET command
 */
rset()
{
	mm_end( NOTOK );
	sender = (char *)0;
	mmdfstart();
	confirm();
}

mmdfstart()
{
	if( rp_isbad( mm_init() ) || rp_isbad( mm_sbinit() )) {
		ll_log( logptr, LLOGFAT, "can't reinitialize mail system" );
		netreply("421 Server can't initialize mail system (mmdf)\r\n");
		byebye( 2 );
	}
	numrecipients = 0;
}

/*
 *  handle the QUIT command
 */
quit()
{
	time_t  timenow;

	time (&timenow);
	sprintf (buf, "221 %s says goodbye to %s at %.19s.\r\n",
		us, them, ctime(&timenow));
	netreply(buf);
	byebye( 0 );
}

byebye( retval )
int retval;
{
	if (retval == OK) {
		mm_sbend();
		phs_note(chanptr, PHS_REEND);
	}
	mm_end( retval == 0 ? OK : NOTOK );
	exit( retval );
}

/*
 *  Reply that the current command has been logged and noted
 */
confirm()
{
	netreply("250 OK\r\n");
}

/*
 *  Process the HELP command by giving a list of valid commands
 */
help()
{
	register i;
	register struct comarr *p;
	char    replybuf[256];

	netreply("214-The following commands are accepted:\r\n214-" );
	for(p = commands, i = 1; p->cmdname; p++, i++) {
		sprintf (replybuf, "%s%s", p->cmdname, ((i%10)?" ":"\r\n214-") );
		netreply (replybuf);
	}
	sprintf (replybuf, "\r\n214 Send complaints/bugs to:  %s\r\n", supportaddr);
	netreply (replybuf);
}

/*
 *  Send appropriate ascii responses over the network connection.
 */

netreply(string)
char *string;
{
	if (setjmp(timerest)) {
		byebye( 1 );
	}
	s_alarm (NTIMEOUT);
	if(write(1,string, strlen(string)) < 0){
		s_alarm( 0 );
		ll_log( logptr, LLOGFST,
			"(netreply) error (%d) in writing [%s] ...",
			errno, string);
		byebye( 1 );
	}
	s_alarm( 0 );
#ifdef DEBUG
	ll_log( logptr, LLOGFTR, "%s", string);
#endif /* DEBUG */
}

/*
 *      expn
 *
 *      handle the EXPN command  ("EXPN user@host")
 */
expn()
{

	if (arg == 0 || *arg == 0) {
		netreply("501 No argument supplied\r\n");
		return;
	}

	expn_count = 0;
	expn_str( addrfix(arg) );  /* feed fixed address into the expander */
	expn_dump();               /* dump the saved (last) address */
	return;
} 

expn_str( arg )
register char *arg;
{
	char    buf[LINESIZE];
	char	alstr[LINESIZE];
	char    comment[LINESIZE];
	register char    *p, *q;
	FILE    *fp;
	int	ret, gotalias, flags;
	Chan    *thechan;
	struct passwd	 *pw;

	gotalias=1;
	if ((ret = aliasfetch(TRUE, arg, buf, &flags)) != OK) {
		if (ret == MAYBE) {
			expn_save(MAYBE,"%s (Nameserver Timeout)",arg);
			return;
		}
		if ((pw = getpwmid (arg)) != (struct passwd *) NULL) {
			expn_save(OK,"%s <%s@%s>", pw->pw_gecos,
				pw->pw_name,us);
			return;
		}
		strcpy(buf, arg);
		gotalias--;
	}

	strcpy(alstr, arg);

	/* just say OK if it is a private alias */
	if (gotalias && (flags & AL_PUBLIC) != AL_PUBLIC) {
		expn_save(OK,"<%s@%s>",alstr,us);
		return;
	}

	/* check for a simple comma-separated list of addresses */
	if ((p = index(buf, ',')) != 0 && strindex (":include:", buf) < 0 &&
	    index (buf, '<') == 0 && index (buf, '|') == 0 && gotalias)
	{
		/* q is start of substring; it lags behind p */
		for (q=buf; p != 0; q=p, p=index(p, ',')) {
			*p++ = '\0';  /* null the comma */
			expn_str(q);
		}
		return;
	}

	/* check for a simple RHS with no @'s and no /'s (e.g. foo:bar) */
	if (index (buf, '/') == 0)
	{
		/* check for a simple RHS (e.g. foo:bar) */
		if ((p = index (buf, '@')) == 0)
		{
			if( buf[0] == '~' ) {
				if ((pw=getpwmid (buf[1])) != (struct passwd *) NULL) {
					expn_save(OK,"%s <%s@%s>", pw->pw_gecos,
						pw->pw_name,us);
					return;
				}
				expn_save(NOTOK,"%s (Unknown address)",arg);
				return;
			}
			if (gotalias)
				return(expn_str(buf));  /* recurse */
			else {
				expn_save(NOTOK,"%s (Unknown address)",arg);
				return;
			}

		}

		/* check for aliases of the form: user@domain */
		*p++ = '\0';
		thechan=ch_h2chan(p,1);
		switch ((int) thechan) {
			case OK:
				return(expn_str(buf));  /* user@US -- recurse */
			case NOTOK:
			case MAYBE:
				break;
			default:
				/* check if list/bboard type channel */
				if (isstr(thechan->ch_host) &&
				    (thechan = ch_h2chan(thechan->ch_host,1))
								== (Chan *) OK)
					return(expn_str(buf));  /* recurse */
		}
		if (thechan == (Chan *) MAYBE) {
			expn_save(MAYBE,"%s@%s (Nameserver Timeout)",buf,p);
			return;
		}
		expn_save(OK,"<%s@%s>",buf,p);
		return;
	}

	if (!gotalias) {
		expn_save(NOTOK,"%s (Unknown Address)", arg);
		return;
	}

	/* Assume if multiple entries, that only the first one is used. */
	if ((q = index (buf, ',')) != 0)
		*q = '\0';

	/* check for aliases of the form: [user]|program */
	if ((q = index (buf, '|')) != 0) {
		*q++ = '\0';
		expn_save (OK,"%s@%s (Mail piped into process: %s)", 
			  alstr, us, q);
		return;
	}

	if ((q = index (buf, '/')) == 0) {
	        expn_save(NOTOK,"%s (Bad format for alias is %s)",alstr,buf);
		return;
	}

	/* check for < and :include: */
	if (index (buf, '<') != 0 || strindex (":include:", buf) >= 0) {
		if ((p = index (buf, '@')) != 0) {
			*p++ = '\0';
			if (ch_h2chan (p, 1)  != (Chan *) OK) {  /* not local */
				expn_save (OK,"<%s@%s>",buf,p);
				return;
			}

		}
		if ((fp = fopen (q, "r")) == NULL) {
			expn_save (NOTOK,"%s@%s (Unable to open file %s)",
				  alstr,us,q);
			return;
		}
		while (fgets (buf, LINESIZE, fp) != NULL) {
			*(buf+strlen(buf)-1) = '\0';
			expn_str(buf);
		}
		fclose (fp);
		return;
	}

	/* assume alias of the form:  [user]/file */
	*q++ = '\0';
	expn_save (OK,"%s@%s (Mail filed into %s)",
			alstr, us, q);
	return;


}

expn_save(code,pattern,a1,a2,a3,a4,a5,a6,a7)
int code;
char *pattern,
     *a1,*a2,*a3,*a4,*a5,*a6,*a7;
{
	if (expn_count > 0) {
		sprintf(buf,"250-%s\r\n",saveaddr);
		netreply(buf);
	}

	sprintf(saveaddr,pattern,a1,a2,a3,a4,a5,a6,a7);
	if (expn_count == 0)
		savecode=code;
	else
		if (code != savecode)
			savecode=OK;
	expn_count++;
}

expn_dump()
{

	if (expn_count == 1 && savecode == NOTOK)
		sprintf(buf,"550 %s\r\n", saveaddr);
	else
		sprintf(buf,"250 %s\r\n", saveaddr);

	netreply(buf);
}

/*
 *      vrfy
 *
 *      handle the VRFY command  ("VRFY user@host")
 */
vrfy()
{
	register int fd;
	register char *cp;
	char linebuf[LINESIZE];
	char replybuf[LINESIZE];
	struct rp_bufstruct thereply;
	int len;

	if (arg == 0 || *arg == 0) {
		netreply("501 No argument supplied\r\n");
		return;
	}

	if (!vrfy_child) {

		/* Start up the verifying child */

		if (pipe (vrfy_p2c) < OK || pipe (vrfy_c2p) < OK) {
			netreply("550 Mail system problem1.\r\n");
			return;
		}

		switch (vrfy_child = fork()) {

		    case NOTOK:
			ll_log(logptr, LLOGFST, "Fork of vrfy child failed.");
			netreply("550 Mail system problem2.\r\n");
			return;

		    default:
			/* parent */
			vrfy_out = fdopen(vrfy_p2c[1],"w");
			vrfy_in  = fdopen(vrfy_c2p[0],"r");
			break;

		    case 0:
			/* child */
#ifndef NODUP2
			dup2(vrfy_p2c[0],0);
			dup2(vrfy_c2p[1],1);
#else
			(void) close(0);
			(void) close(1);
#ifndef NOFCNTL
			fcntl(vrfy_p2c[0], F_DUPFD, 0);
			fcntl(vrfy_c2p[1], F_DUPFD, 1);
#else
			/* something else */
#endif
#endif
			for (fd = 2; fd < numfds; fd++)
				(void)close(fd);

			if (rp_isbad(mm_init()) || rp_isbad(mm_sbinit()) ||
			    rp_isbad(mm_winit ((char *)0, "vmr", (char *)0)) ||
			    rp_isbad(mm_rrply( &thereply, &len )) ||
			    rp_isbad(thereply.rp_val)) {
				ll_log(logptr, LLOGFST, 
					"Vrfy child couldn't start up submit.");
				exit(1);
			}

			while (fgets (linebuf, LINESIZE, stdin)) {
				if (cp = rindex(linebuf, '\n'))
					*cp-- = 0;
				verify(linebuf);
			}

			mm_end(NOTOK);
			exit(0);

		}

	}

	if (setjmp(timerest)) {
		ll_log(logptr,LLOGFST,"Timeout waiting for vrfy child.");
		netreply("550 Mail system problem.\r\n");
		vrfy_kill();
		return;
	}

	s_alarm(DTIMEOUT);
	fprintf(vrfy_out,"%s\n",arg);
	fflush(vrfy_out);
	if (ferror(vrfy_out)) {
		ll_log(logptr,LLOGFST,"Unable to write to vrfy child.");
		vrfy_kill();
		netreply("550 Mail system problem.\r\n");
		return;
	}
	if (fgets (linebuf, LINESIZE, vrfy_in) == NULL) {
		ll_log(logptr,LLOGFST,"Unable to read from vrfy child.");
		vrfy_kill();
		netreply("550 Mail system problem.\r\n");
		return;
	}
	s_alarm(0);

	if (cp = rindex(linebuf, '\n'))
		*cp-- = 0;
	sprintf(replybuf,"%s\r\n",linebuf);
	netreply(replybuf);

	return;
}

verify(p)
char *p;
{
	register char *l, *r;
	struct rp_bufstruct thereply;
	char replybuf[256];
	int len;

	if (setjmp(timerest)) {
		ll_log(logptr,LLOGFST,
			         "Timeout in vrfy child waiting for submit.");
	        exit(1);
	}
	s_alarm (DTIMEOUT);
	if( rp_isbad( mm_wadr( (char *)0, p ))) {
		if( rp_isbad( mm_rrply( &thereply, &len ))) {
			ll_log(logptr,LLOGFST,
			      "Error in vrfy child reading reply from submit.");
		       exit(1);
		} else {
			sprintf (replybuf, "550 %s\r\n", thereply.rp_line);
			vrfyreply (replybuf);
		}
	} else {
		if( rp_isbad( mm_rrply( &thereply, &len ))) {
			ll_log(logptr,LLOGFST,
			     "Error in vrfy child reading reply from submit.");
			exit(1);
		} else if (rp_isbad(thereply.rp_val)) {
			sprintf (replybuf, "550 %s\r\n", thereply.rp_line);
			vrfyreply (replybuf);
		} else {
			if ((l=index(thereply.rp_line, '"')) &&
			    (r=rindex(thereply.rp_line, '"')) &&
			    (l != r) ) {
				*l = '<';
			        *r = '>';
			}
			if (l && r && !index(l,'@')) {
			    *l++ = '\0';
			    *r++ = '\0';
			    sprintf (replybuf, "250 %s<%s@%s>%s\r\n",
 				   thereply.rp_line, l, us, r);
			} else
			    sprintf (replybuf, "250 %s\r\n", thereply.rp_line);
			vrfyreply (replybuf);
		}
	}
	s_alarm (0);

	return;
}

vrfyreply(string)
char *string;
{
	if (setjmp(timerest)) {
		exit(1);
	}
	s_alarm (NTIMEOUT);
	if(write(1,string, strlen(string)) < 0) {
		s_alarm(0);
		ll_log( logptr, LLOGFST,
			"(vrfyreply) error (%d) in writing [%s] ...",
			errno, string);
		exit(1);
	}
	s_alarm(0);
#ifdef DEBUG
	ll_log( logptr, LLOGFTR, "child writing: %s", string);
#endif /* DEBUG */
}

vrfy_kill()
{
	ll_log( logptr, LLOGFST, "Killing vrfy child.");

	if (vrfy_child) {
		kill (vrfy_child, 9);
		vrfy_child = 0;
	}

	if (vrfy_in != (FILE *) EOF && vrfy_in != NULL) {
		(void) close (fileno (vrfy_in));
		fclose(vrfy_in);
	}

	if (vrfy_out != (FILE *) EOF && vrfy_out != NULL) {
		(void) close (fileno (vrfy_out));
		fclose(vrfy_out);
	}

	vrfy_in = vrfy_out = NULL;

	return;
}
