/* There are only two functions in this mailbox code that depend on the
 * underlying protocol, namely mbx_getname() and dochat(). All the other
 * functions can hopefully be used without modification on other stream
 * oriented protocols than AX.25 or NET/ROM.
 *
 * SM0RGV 890506, most work done previously by W9NK
 *
 *** Changed 900114 by KA9Q to use newline mapping features in stream socket
 *	interface code; everything here uses C eol convention (\n)
 *
 *	Numerous new commands and other changes by SM0RGV, 900120
 *
 *	14 Mar 92	GT	Fix declaration syntax error under BC++ 3.00.
 *	25 Jun 92	paul@wolf.demon.co.uk  Make privilege check for
 *					external email from mailbox
 *	25 Jun 92	paul@wolf.demon.co.uk  Add message if entering mailbox
 *					with an '@' password.
 *
 ****************************************************************************
 *	$Id: mailbox.c 1.6 94/02/02 14:38:17 ROOT_DOS Exp $
 *	08 May 93	1.5		GT	Fix warnings.		
 *	01 Feb 94	1.6		GT	New sendfile prototype.
 *
 * ST NOS version by David Nash
 *
 ***************************************************************************/
 
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>	/* ST NOS - sys_errlist > strerror	*/
#ifdef	UNIX
#include <sys/types.h>
#include <sys/stat.h>
#endif

#include "global.h"
#include "config.h"
#include "timer.h"
#include "proc.h"
#include "socket.h"
#include "usock.h"
#include "session.h"
#include "smtp.h"
#include "dirutil.h"
#include "telnet.h"
#include "ftp.h"
#include "ftpserv.h"
#include "commands.h"
#include "netuser.h"
#include "files.h"
#include "bm.h"
#include "mailbox.h"
#include "ax25mail.h"
#include "nr4mail.h"
#include "cmdparse.h"

/*
#define MBDEBUG
*/

struct mbx *Mbox[NUMMBX];
static char *Motd = NULLCHAR;
static int Attended = TRUE;	/* default to attended mode */
unsigned Maxlet = BM_NLET;

char Noperm[] = "Permission denied.\n";
char Nosock[] = "Can't create socket\n";

static char Mbbanner[] = "[NET-H$]\nWelcome %s to the %s TCP/IP Mailbox (%s)\n%s";
static char Mbmenu[] = "Current msg# %d : A,B,C,D,E,F,G,H,I,J,K,L,N,R,S,T,U,V,W,Z,? >\n";
static char Longmenu1[] = "(?)help    (A)rea     (B)ye      (C)hat     (D)ownload (E)scape   (F)inger\n";
static char Longmenu2[] = "(G)ateway  (H)elp     (I)nfo     (J)heard   (K)ill     (L)ist     (N)etrom\n";
static char Longmenu3[] = "(R)ead     (S)end     (T)elnet   (U)pload   (V)erbose  (W)hat     (Z)ap\n";
static char Loginbanner[] = "\nKA9Q NOS (%s)\n\n";
static char Howtoend[] = "Terminate with /EX or ^Z in first column (^A aborts):\n";

/* Prototypes for functions defined in mailbox.c */
int dombox(int argc,
           char **argv,
           void *p);
static int doattend(int argc,
                    char **argv,
                    void *p);
static int domaxmsg(int argc,
                    char **argv,
                    void *p);
static int domotd(int argc,
                  char **argv,
                  void *p);
int domboxdisplay(int argc,
                  char **argv,
                  void *p);
static int dotimeout(int argc,
                     char **argv,
                     void *p);
void listusers(int s);
struct mbx *newmbx(void);
static int mbx_getname(struct mbx *m);
void __stdargs mbx_incom(int s,
               void *t,
               void *p);
void exitbbs(struct mbx *m);
int mbx_parse(struct mbx *m);
int mbxrecvline(int s,
                char *buf,
                int len,
                int escape);
int domboxbye(int argc,
              char **argv,
              void *p);
static int dombhelp(int argc,
                    char **argv,
                    void *p);
static int dochat(int argc,
                  char **argv,
                  void *p);
static int dosend(int argc,
                  char **argv,
                  void *p);
static int dosid(int argc,
                 char **argv,
                 void *p);
int dombescape(int argc,
               char **argv,
               void *p);
static int dodownload(int argc,
                      char **argv,
                      void *p);
static int dombupload(int argc,
                      char **argv,
                      void *p);
static int dowhat(int argc,
                  char **argv,
                  void *p);
static int dozap(int argc,
                 char **argv,
                 void *p);
static int dosysop(int argc,
                   char **argv,
                   void *p);
static int dostars(int argc,
                   char **argv,
                   void *p);
static int doarea(int argc,
                  char **argv,
                  void *p);
void changearea(struct mbx *m,
                char *area);
static int dombtelnet(int argc,
                      char **argv,
                      void *p);
static int dombfinger(int argc,
                      char **argv,
                      void *p);
int gw_connect(struct mbx *m,
               int s,
               char *fsocket,
               int len);
static void __stdargs gw_input(int s,
                     void *notused,
                     void *p);
static void __stdargs gw_superv(int null,
                      void *proc,
                      void *p);
static void gw_alarm(void *p);
static int mbx_to(int argc,
                  char **argv,
                  void *p);
static int mbx_data(struct mbx *m,
                    struct list *cclist,
                    char *extra);
static int msgidcheck(char *string);
char *rewrite_address(char *addr);
static int uuencode(FILE *infile,
                    int s,
                    char *infilename);
static int check_mail_privilege(char *name);
static void string_tidy(char *str);

/************************************************************************/
/*			C O M M A N D S					*/
/************************************************************************/

static int doattend __ARGS((int argc,char *argv[],void *p));
static int domaxmsg __ARGS((int argc,char *argv[],void *p));
static int domotd __ARGS((int argc,char *argv[],void *p));
static int dotimeout __ARGS((int argc,char *argv[],void *p));
static int check_mail_privilege __ARGS((char *name));
static void string_tidy __ARGS((char *str));

/* mbox subcommand table */
static struct cmds Mbtab[] = {
	"attend",	doattend,	0, 0, NULLCHAR,
#ifdef	AX25
	"kick",		dombkick,	0, 0, NULLCHAR,
#endif
	"maxmsg",	domaxmsg,	0, 0, NULLCHAR,
	"motd",		domotd,		0, 0, NULLCHAR,
	"status",	domboxdisplay,	0, 0, NULLCHAR,
#ifdef	AX25
	"timer",	dombtimer,	0, 0, NULLCHAR,
#endif
	"tiptimeout",	dotimeout,	0, 0, NULLCHAR,
	NULLCHAR,
};


int
dombox(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(argc == 1)
		return domboxdisplay(argc,argv,p);
	return subcmd(Mbtab,argc,argv,p);
}

/* if unattended mode is set, ax25, telnet and maybe other sessions will
 * be restricted.
 */
static int
doattend(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Attended,"Attended flag",argc,argv);
}

static int
domaxmsg(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setuns(&Maxlet,"Maximum messages per area",argc,argv);
}

static int
domotd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(argc > 2) {
		tprintf("Usage: mbox motd \"<your message>\"\n");
		return 0;
	}

	if(argc < 2) {
		if(Motd != NULLCHAR)
			tputs(Motd);
	}
	else {
		if(Motd != NULLCHAR){
			free(Motd);
			Motd = NULLCHAR;	/* reset the pointer */
		}
		if(!strlen(argv[1]))
			return 0;		/* clearing the buffer */
		Motd = mallocw(strlen(argv[1])+5);/* allow for the EOL char */
		strcpy(Motd, argv[1]);
		strcat(Motd, "\n");		/* add the EOL char */
	}
	return 0;
}

int
domboxdisplay(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	int i, j, len;
	struct mbx *m;
	char fsocket[MAXSOCKSIZE];
	static char *states[] = {"LOGIN","CMD","SUBJ","DATA","REVFWD",
				"TRYING","FORWARD"};

	tprintf("User       State    S#  Where\n");

	for (i = 0; i < NUMMBX; i++){
		if((m = Mbox[i]) != NULLMBX){
			len = MAXSOCKSIZE;
			j = getpeername(m->user,fsocket,&len);
			if(tprintf("%-11s%-9s%-4u%s\n",m->name,
				   states[m->state],m->user,
				   j != -1 ? psocket(fsocket): "") == EOF)
				break;
		}
	}
	return 0;
}

static int
dotimeout(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setuns(&Tiptimeout,"Tip connection timeout",argc,argv);
}


/**********************************************************************/

void
listusers(s)
int s;
{
	int outsave;

	usprintf(s,"\nCurrent remote users:\n");
	outsave = Curproc->output;
	Curproc->output = s;
	domboxdisplay(0,NULLCHARP,NULL);
	Curproc->output = outsave;
}

struct mbx *
newmbx()
{
	int i;
	struct mbx *m;

	for(i = 0; i < NUMMBX; i++){
		if(Mbox[i] == NULLMBX){
			m = Mbox[i] = (struct mbx *)callocw(1,sizeof(struct mbx));
			m->mbnum = i;
			return m;
		}
	}
	/* If we get here, there are no free mailbox sessions */
	return NULLMBX;
}

static int
mbx_getname(m)
struct mbx *m;
{
#ifdef	AX25
	char *cp;
#endif
	union sp sp;
	char tmp[MAXSOCKSIZE];
	char buf[MBXLINE];
	char *buf1;
	char *password;
	int len = MAXSOCKSIZE;
	int anony = 0;
	int oldmode;

	sp.p = tmp;
	sp.sa->sa_family = AF_LOCAL;	/* default to AF_LOCAL */
	getpeername(m->user,tmp,&len);
	m->path = mallocw(MBXLINE);
	/* This is one of the two parts of the mbox code that depends on the
	 * underlying protocol. We have to figure out the name of the
	 * calling station. This is only practical when AX.25 or NET/ROM is
	 * used. Telnet users have to identify themselves by a login procedure.
	 */
	switch(sp.sa->sa_family){
#ifdef	AX25
	case AF_NETROM:
	case AF_AX25:
		/* NETROM and AX25 socket address structures are "compatible" */
		pax25(m->name,sp.ax->ax25_addr);
		cp = strchr(m->name,'-');
		if(cp != NULLCHAR)			/* get rid of SSID */
			*cp = '\0';
		/* SMTP wants the name to be in lower case */
		cp = m->name;
		while(*cp){
			if(isupper(*cp))
				*cp = tolower(*cp);
			++cp;
		}
		anony = 1;
		/* Try to find the privileges of this user from the userfile */
		if((m->privs = userlogin(m->name,buf,&m->path,MBXLINE,&anony)) == -1)
			if((m->privs = userlogin("bbs",buf,&m->path,MBXLINE,&anony)) == -1)
				if((m->privs = userlogin("anonymous",buf,&m->path,MBXLINE,
					 &anony)) == -1){
						m->privs = 0;
						free(m->path);
						m->path = NULLCHAR;
				}
		if(m->privs & EXCLUDED_CMD)
			return -1;
		return 0;
#endif
	case AF_LOCAL:
	case AF_INET:
		m->state = MBX_LOGIN;
		tprintf(Loginbanner,Hostname);
		for(;;){
			tputs("login: ");
			usflush(m->user);
			if(mbxrecvline(m->user,m->name,sizeof(m->name),-1) == EOF)
				return -1;
			if(*m->name == '\0')
				continue;

			if ((buf1=userlookup(m->name,&password,NULLCHARP,NULLINT,NULL))!=NULLCHAR)
			{
				if (strcmp(password,"@")==0)
					tprintf("*** Enter your email address as your password\n");
				free(buf1);
			}

			tprintf("Password: %c%c%c",IAC,WILL,TN_ECHO);
			usflush(m->user);
			oldmode = sockmode(m->user,SOCK_BINARY);
			if(mbxrecvline(m->user,buf,MBXLINE,-1) == EOF)
				return -1;
			tprintf("%c%c%c",IAC,WONT,TN_ECHO);
			sockmode(m->user,oldmode);
			tputc('\n');
			usflush(m->user);
			/* This is needed if the password was send before the
			 * telnet no-echo options were receied. We neeed to
			 * flush the eold sequence from the input buffers, sigh
			 */
			if(socklen(m->user,0))/* discard any remaining input */
				recv_mbuf(m->user,NULL,0,NULLCHAR,0);
			if((m->privs = userlogin(m->name,buf,&m->path,MBXLINE,&anony))
			 != -1){
				if(anony)
					log(m->user,"MBOX login: %s Password: %s",m->name,buf);
				else
					log(m->user,"MBOX login: %s",m->name);
				if(m->privs & EXCLUDED_CMD)
					return -1;
				return 0;
			}
			tprintf("Login incorrect\n");
			*m->name = '\0';	/* wipe any garbage */
		}
	}
	return 0;
}

/* Incoming mailbox session */
void __stdargs
mbx_incom(s,t,p)
int s;
void *t;
void *p;
{
	struct mbx *m;
	struct usock *up;
	char *buf[3];
	int rval;

	sockmode(s,SOCK_ASCII);
	sockowner(s,Curproc);	/* We own it now */
	/* Secede from the parent's sockets, and use the network socket that
	 * was passed to us for both input and output. The reference
	 * count on this socket will still be 1; this allows the domboxbye()
	 * command to work by closing that socket with a single call.
	 * If we return, the socket will be closed automatically.
	 */
	close_s(Curproc->output);
	close_s(Curproc->input);
	Curproc->output = Curproc->input = s;

	/* We'll do our own flushing right before we read input */
	setflush(s,-1);

	log(s,"open MBOX");
	if((m = newmbx()) == NULLMBX){
		tprintf("Too many mailbox sessions\n");
		return;
	}
	m->user = s;
	m->escape = 24;		/* default escape character is Ctrl-X */
	m->type = (int) t;
	/* get the name of the remote station */
	if(mbx_getname(m) == -1) {
		exitbbs(m);
		return;
	}

	m->state = MBX_CMD;	/* start in command state */

	/* Now say hi */
	tprintf(Mbbanner,m->name,Hostname,Version,
		Motd != NULLCHAR ? Motd : "");
	/* Enable our local message area */
	buf[1] = m->name;
	doarea(2,buf,m);
	tprintf(Mbmenu,m->current);
	while(mbxrecvline(s,m->line,MBXLINE,-1) != EOF){
		if((rval = mbx_parse(m)) == -2)
			break;
		if(rval == 1)
			tprintf("Bad syntax.\n");
		if(!(m->sid & MBX_SID) && isnewprivmail(m) > 0L)
			tprintf("You have new mail.\n");
		scanmail(m);
		tprintf((m->sid & MBX_SID) ? ">\n" : Mbmenu, m->current);
		m->state = MBX_CMD;
	}
	exitbbs(m);
	/* nasty hack! we may have screwed up reference count */
	/* by invoking newproc("smtp_send",....); Fudge it!   */
	if((up = itop(Curproc->output)) != NULLUSOCK)
		up->refcnt = 1;
	close_s(Curproc->output);
}

void
exitbbs(m)
struct mbx *m;
{
	closenotes(m);
	free(m->to);
	free(m->tofrom);
	free(m->origto);
	free(m->tomsgid);
	free(m->path);
	free((char *)m->mbox);
	Mbox[m->mbnum] = NULLMBX;
	free((char *)m);
}

/**********************************************************************/

static int dochat __ARGS((int argc,char *argv[],void *p));
static int dodownload __ARGS((int argc,char *argv[],void *p));
static int dombupload __ARGS((int argc,char *argv[],void *p));
static int dowhat __ARGS((int argc,char *argv[],void *p));
static int dozap __ARGS((int argc,char *argv[],void *p));
static int dosend __ARGS((int argc,char *argv[],void *p));
static int dosid __ARGS((int argc,char *argv[],void *p));
static int dosysop __ARGS((int argc,char *argv[],void *p));
static int dostars __ARGS((int argc,char *argv[],void *p));
static int dombhelp __ARGS((int argc,char *argv[],void *p));
static int dombtelnet __ARGS((int argc,char *argv[],void *p));
static int dombfinger __ARGS((int argc,char *argv[],void *p));
static void gw_alarm __ARGS((void *p));
static void __stdargs gw_input __ARGS((int s,void *notused,void *p));
static void __stdargs gw_superv __ARGS((int null,void *proc,void *p));
static int mbx_to __ARGS((int argc,char *argv[],void *p));
static int mbx_data __ARGS((struct mbx *m,struct list *cclist,char *extra));
static int msgidcheck __ARGS((char *string));
static int uuencode __ARGS((FILE *infile,int s,char *infilename));

static struct cmds Mbcmds[] = {
	"",		doreadnext,	0, 0, NULLCHAR,
	"area",		doarea,		0, 0, NULLCHAR,
	"send",		dosend,		0, 0, NULLCHAR,
	"read",		doreadmsg,	0, 2, "R numbers",
	"verbose",	doreadmsg,	0, 2, "V numbers",
#ifdef	AX25
	"jheard",	doaxheard,	0, 0, NULLCHAR,
#endif
	"kill",		dodelmsg,	0, 2, "K numbers",
	"list",		dolistnotes,	0, 0, NULLCHAR,
	"escape",	dombescape,	0, 0, NULLCHAR,
	"download",	dodownload,	0, 2, "D[U] filename",
	"upload",	dombupload,	0, 2, "U filename",
	"what",		dowhat,		0, 0, NULLCHAR,
	"zap",		dozap,		0, 2, "Z filename",
#ifdef AX25
	"gateway",	dogateway,	0, 3, "G interface callsigns",
#endif
	"telnet",	dombtelnet,	0, 2, "T hostname",
	"finger",	dombfinger,	0, 0, NULLCHAR,
#ifdef	NETROM
	"netrom",	dombnetrom,	0, 0, NULLCHAR,
#endif
	"chat",		dochat,		0, 0, NULLCHAR,
	"bye",		domboxbye,	0, 0, NULLCHAR,
	"help",		dombhelp,	0, 0, NULLCHAR,
	"info",		dombhelp,	0, 0, NULLCHAR,
	"?",		dombhelp,	0, 0, NULLCHAR,
	"[",		dosid,		0, 0, NULLCHAR,
#ifdef	AX25
	"f>",		dorevfwd,	0, 0, NULLCHAR,
#endif
	"@",		dosysop,	0, 0, NULLCHAR,
	"***",		dostars,	0, 0, NULLCHAR,
	NULLCHAR,	NULLFP,		0, 0, "Huh?",
};

/* "twocmds" defines the MBL/RLI two-letter commands, eg. "SB", "SP" and so on.
 * They have to be treated specially since cmdparse() wants a space between
 * the actual command and its arguments.
 * "SP FOO" is converted to "s  foo" and the second command letter is saved
 * in m->stype. Longer commands like "SEND" are unaffected, except for
 * commands starting with "[", i.e. the SID, since we don't know what it will
 * look like.
 */
static char twocmds[] = "slrd[";	/* S,L,R,D are two-letter commands */
int
mbx_parse(m)
struct mbx *m;
{
	char *cp;
	int i;
	char *newargv[2];
	/* Translate entire buffer to lower case */
	for (cp = m->line; *cp != '\0'; ++cp)
		if(isupper(*cp))
			*cp = tolower(*cp);
	/* Skip any spaces at the begining */
	for(cp = m->line;isspace(*cp);++cp)
		;
	m->stype = ' ';
	if(*cp != '\0' && *(cp+1) != '\0')
	for(i=0; i<strlen(twocmds); ++i){
		if(*cp == twocmds[i] && (isspace(*(cp+2)) || *(cp+2) == '\0'
		 || *cp == '[')){
			if(islower(*(++cp)))
				m->stype = toupper(*cp); /* Save the second character */
			else
				m->stype = *cp;
			*cp = ' ';
			break;
		}
	}
	/* See if the input line consists solely of digits */
	cp = m->line;
	for(cp = m->line;isspace(*cp);++cp)
		;
	newargv[1] = cp;
	for(;*cp != '\0' && isdigit(*cp);++cp)
		;
	if(*cp == '\0' && strlen(newargv[1]) > 0) {
		newargv[0] = "read";
		return doreadmsg(2,newargv,(void *)m);
	}
	else
		return cmdparse(Mbcmds,m->line,(void *)m);
}

/* This works like recvline(), but telnet options are answered and the
 * terminating newline character is not put into the buffer. If the
 * incoming character equals the value of escape, any queued input is
 * flushed and -2 returned.
 */
int
mbxrecvline(s,buf,len,escape)
int s;
char *buf;
int len;
int escape;
{
	int c, cnt = 0;
#ifdef	foo
	int opt
#endif

	if(buf == NULLCHAR)
		return 0;
	usflush(Curproc->output);
	while((c = recvchar(s)) != EOF){
		if(c == IAC){		/* Telnet command escape */
			if((c = recvchar(s)) == EOF)
				break;
#ifndef	foo
			if(c > 250 && c < 255 && recvchar(s) != EOF){
#endif
#ifdef	foo
			if(c > 250 && c < 255 && (opt = recvchar(s)) != EOF){
				switch(c){
				case WILL:
					tprintf("%c%c%c",IAC,DONT,opt);
					break;
				case WONT:
					tprintf("%c%c%c",IAC,DONT,opt);
					break;
				case DO:
					tprintf("%c%c%c",IAC,WONT,opt);
					break;
				case DONT:
					tprintf("%c%c%c",IAC,WONT,opt);
				}
#endif
/* to be fixed 			usflush(Curproc->output);*/
				continue;
			}
			if(c != IAC && (c = recvchar(s)) == EOF)
				break;
		}
		/* ordinary character */
		if(c == '\r' || c == '\n')
			break;
		if(uchar(c) == escape){
			if(socklen(s,0)) /* discard any remaining input */
				recv_mbuf(s,NULL,0,NULLCHAR,0);
			cnt = -2;
			break;
		}
		*buf++ = c;
		++cnt;
		if(cnt == len - 1)
			break;
	}
	if(c == EOF && cnt == 0)
		return -1;
	*buf = '\0';
	return cnt;
}

int
domboxbye(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;

	m = (struct mbx *)p;
	/* Now say goodbye */
	tprintf("Thank you %s, for calling the %s Tcp/Ip Mailbox.\n",m->name,
		Hostname);
	if(m->type == TIP)
		tprintf("Please hang up now.\n");

	return -2;	/* signal that exitbbs() should be called */
}
static int
dombhelp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char buf[255];
	int i;
	FILE *fp;
	if(*argv[0] == '?') {
		tputs(Longmenu1);
		tputs(Longmenu2);
		tputs(Longmenu3);
		return 0;
	}
	buf[0] = '\0';
	if(argc > 1)
		for(i=0; Mbcmds[i].name != NULLCHAR; ++i)
			if(!strncmp(Mbcmds[i].name,argv[1],strlen(argv[1]))) {
				sprintf(buf,"%s/%s.hlp",Helpdir,Mbcmds[i].name);
				break;
			}
	if(buf[0] == '\0')
		if(*argv[0] == 'i')			/* INFO command */
			sprintf(buf,"%s/info.hlp",Helpdir);
		else
			sprintf(buf,"%s/help.hlp",Helpdir);
	if((fp = fopen(buf,READ_TEXT)) != NULLFILE) {
		sendfile(fp,Curproc->output,ASCII_TYPE,0,0);
		fclose(fp);
	}
	else
		tprintf("No help available. (%s not found)\n",buf);
	return 0;
}

static int
dochat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char buf[8], *newargv[3];

	if(Attended){
		newargv[0] = "telnet";
		newargv[1] = Hostname;
		sprintf(buf,"%d",IPPORT_TTYLINK);
		newargv[2] = buf;
		return dombtelnet(3,newargv,p);
	}
	else {
		tprintf("Sorry - the system is unattended.\007\n");
	}
	/* It returns only after a disconnect or refusal */
	return 0;
}

static int
dosend(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	int cccnt = 0, fail = 0;
	char *host, *cp, fullfrom[MBXLINE], sigwork[LINELEN], *rhdr = NULLCHAR;
	struct list *ap, *cclist = NULLLIST;
	struct mbx *m;
	FILE *fp;

	m = (struct mbx *)p;
	if((m->stype != 'R' || (m->sid & MBX_SID)) && mbx_to(argc,argv,m)
	   == -1){
		if(m->sid & MBX_SID)
			tprintf("NO - syntax error\n");
		else {
			tprintf("S command syntax error - format is:\n");
			tprintf("  S[F] name [@ host] [< from_addr] [$bulletin_id]\n");
			tprintf("  SR [number]\n");
		}
		return 0;
	}
	if(m->stype != 'R' && msgidcheck(m->tomsgid)) {
		if(m->sid & MBX_SID)
			tputs("NO - ");
		tprintf("Already have %s\n",m->tomsgid);
		return 0;
	}
	if(m->stype == 'R' && !(m->sid & MBX_SID) &&
	   mbx_reply(argc,argv,m,&cclist,&rhdr) == -1)
		return 0;
	if((cp = rewrite_address(m->to)) != NULLCHAR)
	     if(strcmp(m->to,cp) != 0){
		  m->origto = m->to;
		  m->to = cp;
	     }
	     else
		  free(cp);
	if((m->origto != NULLCHAR || m->stype == 'R') && !(m->sid & MBX_SID))
		tprintf("To: %s\n", m->to);
	if(validate_address(m->to) == 0){
		if(m->sid & MBX_SID)
			tprintf("NO - bad address\n");
		else
			tprintf("Bad user or host name\n");
		free(rhdr);
		del_list(cclist);
		/* We don't free any more buffers here. They are freed upon
		 * the next call to mbx_to() or to domboxbye()
		 */
		return 0;
	}

/* Check to ensure that users without Telnet access don't use external
   Email - added by paul@wolf.demon.co.uk */
    if (!(m->privs & TELNET_CMD)){
/* Bang paths (!), redirectors (%) and hosts other than Hostname are
   disallowed */
        fail+=check_mail_privilege(m->to);

        for ( ap = cclist; ap != NULLLIST; ap = ap->next) {
            fail+=check_mail_privilege(ap->val);
        }

        if (fail){
            tprintf(Noperm);
            free(rhdr);
            del_list(cclist);
            /* We don't free any more buffers here. They are freed upon
             * the next call to mbx_to() or to domboxbye()
             */
            return 0;
        }


    }

	/* Display the Cc: line (during SR command) */
	for(ap = cclist; ap != NULLLIST; ap = ap->next) {
		if(cccnt == 0){
			tprintf("%s",Hdrs[CC]);
			cccnt = 4;
		}
		else {
			tputs(", ");
			cccnt += 2;
		}
		if(cccnt + strlen(ap->val) > 80 - 3) {
			tputs("\n    ");
			cccnt = 4;
		}
		tputs(ap->val);
		cccnt += strlen(ap->val);
	}
	if(cccnt)
		tputc('\n');
	m->state = MBX_SUBJ;
	if(m->stype != 'R' || (m->sid & MBX_SID) != 0) {
		tprintf((m->sid & MBX_SID) ? "OK\n" : "Subject: ");
		if(mbxrecvline(m->user,m->line,MBXLINE,-1) == -1)
			return 0;
	}
	else				/* Replying to a message */
		tprintf("Subject: %s\n",m->line);
	if(mbx_data(m,cclist,rhdr) == -1){
		free(rhdr);
		del_list(cclist);
		tputs("Can't create temp file for mail\n");
		return 0;
	}
	free(rhdr);
	m->state = MBX_DATA;
	if((m->sid & MBX_SID) == 0 && m->stype != 'F')
		tprintf("Enter message.  %s",Howtoend);

	if(m->stype != 'F' || (m->sid & MBX_SID) != 0)
		while(mbxrecvline(m->user,m->line,MBXLINE,-1) != -1){
			if(m->line[0] == 0x01){  /* CTRL-A */
				fclose(m->tfile);
				tputs("Aborted.\n");
				del_list(cclist);
				return 0;
			}
			if(m->line[0] != CTLZ && stricmp(m->line, "/ex"))
				fprintf(m->tfile,"%s\n",m->line);
			else
				break;	/* all done */
		}
	else {
		fprintf(m->tfile,"----- Forwarded message -----\n\n");
		msgtofile(m,m->current,m->tfile,0);
		fprintf(m->tfile,"----- End of forwarded message -----\n");
	}

	/* Insert customised signature if one is found */
	if(!(m->sid & MBX_SID)) {	/* not a forwarding BBS */
	     sprintf(sigwork,"%s/%s.sig",Signature,
		     m->tofrom ? m->tofrom : m->name);
	     if((fp = fopen(sigwork,READ_TEXT)) != NULLFILE){
		  while(fgets(sigwork,LINELEN,fp) != NULLCHAR)
			fputs(sigwork,m->tfile);
		  fclose(fp);
	     }
	}

	if((host = strrchr(m->to,'@')) == NULLCHAR) {
		host = Hostname;	/* use our hostname */
		if(m->origto != NULLCHAR) {
			/* rewrite_address() will be called again by our
			 * SMTP server, so revert to the original address.
			 */
		 	free(m->to);
			m->to = m->origto;
			m->origto = NULLCHAR;
		}
	}
	else
		host++;	/* use the host part of address */

	/* make up full from name for work file */
	if(m->tofrom != NULLCHAR)
		sprintf(fullfrom,"%s%%%s.bbs@%s",m->tofrom, m->name, Hostname);
	else
		sprintf(fullfrom,"%s@%s",m->name,Hostname);
	if(cclist != NULLLIST && stricmp(host,Hostname) != 0) {
		fseek(m->tfile,0L,0);	/* reset to beginning */
		fail = queuejob(m->tfile,Hostname,cclist,fullfrom,NULL);
		del_list(cclist);
		cclist = NULLLIST;
	}
	addlist(&cclist,m->to,0);
	fseek(m->tfile,0L,0);
	fail += queuejob(m->tfile,host,cclist,fullfrom,NULL);
	del_list(cclist);
	fclose(m->tfile);
	if(fail)
	     tputs("Couldn't queue message for delivery\n");
	else
	     if(m->tomsgid != NULLCHAR &&
		(fp = fopen(Historyfile,APPEND_TEXT)) != NULLFILE) {
		  fprintf(fp,"%s\n",m->tomsgid); /* Save BID in history file */
		  fclose(fp);
	     }
	smtptick(NULL);		/* wake SMTP to send that mail */
	return 0;
}

static int
dosid(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	char *cp;

	m = (struct mbx *)p;
	if(argc == 1)
		return 1;
	if(argv[1][strlen(argv[1]) - 1] != ']') /* must be an SID */
		return 1;
	m->sid = MBX_SID;
	/* Now check to see if this is an RLI board.
	 * As usual, Hank does it a bit differently from
	 * the rest of the world.
	 */
	if(m->stype == 'R' && strncmp(argv[1],"li",2) == 0)/* [RLI] at a minimum */
		m->sid |= MBX_RLI_SID;
	/* Check to see if the BBS supports a kludge called "hierarchical
	 * routing designators."
	 *
	 * No need to check for ']' -- it must be there or this is not
	 * a valid mbox id -- it is checked earlier (fix de OH3LKU)
	 */
	if((cp = strchr(argv[1],'-')) != NULLCHAR
	 && (cp=strchr(cp+1,'h')) != NULLCHAR
	 && strchr(cp+1,'$'))
		m->sid |= MBX_HIER_SID;	
	return 0;
}

int
dombescape(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;

	m = (struct mbx *)p;
	if(argc < 2){
		tprintf("The escape character is: ");
		if(m->escape < 32)
			tprintf("CTRL-%c\n",m->escape+'A'-1);
		else
			tprintf("'%c'\n",m->escape);
		return 0;
	}
	if(strlen(argv[1]) > 1)
		if(isdigit(*argv[1]))
			m->escape = (char) atoi(argv[1]);
		else
			return 1;
	else
		m->escape = *argv[1];
	return 0;
}

static int
dodownload(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	FILE *fp;
	char *file;

	m = (struct mbx *)p;
	file = pathname(m->path,argv[1]);
	if(!permcheck(m->path,m->privs,RETR_CMD,file)){
		tprintf(Noperm);
		return 0;
	}
	if((fp = fopen(file,READ_TEXT)) == NULLFILE)
		tprintf("Can't open \"%s\": %s\n",file,strerror(errno));
	else
		if(m->stype == 'U'){			/* uuencode ? */
			fclose(fp);
			fp = fopen(file,READ_BINARY);	/* assume non-ascii */
			uuencode(fp,m->user,file);
		} else
			sendfile(fp,m->user,ASCII_TYPE,0,0);
	free(file);
	fclose(fp);
	return 0;
}

static int
dombupload(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	FILE *fp;
	char *file, buf[LINELEN];

	m = (struct mbx *)p;
	file = pathname(m->path,argv[1]);
	if(!permcheck(m->path,m->privs,STOR_CMD,file)){
		tprintf(Noperm);
		return 0;
	}
	if((fp = fopen(file,WRITE_TEXT)) == NULLFILE){
		tprintf("Can't create \"%s\": %s\n",file,strerror(errno));
		free(file);
		return 0;
	}
	log(m->user,"MBOX upload: %s",file);
	tprintf("Send file,  %s",Howtoend);
	for(;;){
		if(mbxrecvline(m->user,buf,LINELEN,-1) == -1){
			unlink(file);
			break;
		}
		if(buf[0] == 0x01){  /* CTRL-A */
			unlink(file);
			tprintf("Aborted.\n");
			break;
		}
		if(buf[0] == CTLZ || !stricmp("/ex",buf))
			break;
		fputs(buf,fp);
#if !defined(UNIX) && !defined(__TURBOC__) && !defined(AMIGA)
		/* Needed only if the OS uses a CR/LF
		 * convention and putc doesn't do
		 * an automatic translation
		 */
		if(putc('\r',fp) == EOF)
			break;
#endif
		if(putc('\n',fp) == EOF)
			break;
	}
	free(file);
	fclose(fp);
	return 0;
}

static int
dowhat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	FILE *fp;
	char *file;

	m = (struct mbx *)p;
	if(argc < 2)
		file = strdup(m->path);
	else
		file = pathname(m->path,argv[1]);
	if(!permcheck(m->path,m->privs,RETR_CMD,file)){
		tprintf(Noperm);
		return 0;
	}
	if((fp = dir(file,1)) == NULLFILE)
		tprintf("Can't read directory: \"%s\": %s\n",file,strerror(errno));
	else
		sendfile(fp,m->user,ASCII_TYPE,0,0);
	free(file);
	fclose(fp);
	return 0;
}

static int
dozap(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	char *file;

	m = (struct mbx *)p;
	file = pathname(m->path,argv[1]);
	if(!permcheck(m->path,m->privs,DELE_CMD,file)){
		tprintf(Noperm);
		return 0;
	}
	if(unlink(file))
		tprintf("Zap failed: %s\n",strerror(errno));
	log(m->user,"MBOX Zap: %s",file);
	free(file);
	return 0;
}

static int
dosysop(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	int c;
	extern struct cmds far Cmds[];

	m = (struct mbx *) p;
	if(!(m->privs & SYSOP_CMD)){
		tprintf(Noperm);
		return 0;
	}
	dombescape(1,NULLCHARP,p);
	for(;;){
		tprintf("Net> ");
		usflush(Curproc->output);
		c = mbxrecvline(Curproc->input,m->line,MBXLINE,m->escape);
		if(c == EOF || c == -2)
			break;
		log(m->user,"MBOX sysop: %s",m->line);
		cmdparse(Cmds,m->line,NULL);
	}
	return 0;
}

/* Handle the "*** Done" command when reverse forwarding ends or the
 * "*** LINKED to" command.
 */
static int
dostars(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	int anony = 1;
	m = (struct mbx *)p;
	/* The "*** LINKED to" command is only allowed to stations with
	 * SYSOP privileges to prevent others from obtaining the same.
	 */
	if((m->privs & SYSOP_CMD) && argc == 4 && !strcmp(argv[1],"linked")) {
		strcpy(m->name,argv[3]);
		/* Try to find the privileges of this user from the userfile */
		if((m->privs = userlogin(m->name,NULLCHAR,&m->path,MBXLINE,
					 &anony)) == -1)
		     if((m->privs = userlogin("bbs",NULLCHAR,&m->path,
				      MBXLINE,&anony)) == -1)
			  if((m->privs = userlogin("anonymous",NULLCHAR,
					   &m->path,MBXLINE,&anony)) == -1){
						m->privs = 0;
						free(m->path);
						m->path = NULLCHAR;
			  }
		tprintf("Oh, hello %s.\n",m->name);
		if(m->privs & EXCLUDED_CMD)
			return domboxbye(0,NULLCHARP,p);
		changearea(m,m->name);
		return 0;
	}
	if(argc > 1 && (m->sid & MBX_SID))	/* "*** Done" or similar */
		return 2;
	return -1;
}

static int
doarea(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	FILE *fp;

	m = (struct mbx *) p;
	if(argc < 2){
		tprintf("Current message area is: %s\n",m->area);
		tprintf("Available areas are:\n%-15s  Your private mail area\n",
		  m->name);
		if((fp = fopen(Arealist,READ_TEXT)) == NULLFILE)
			return 0;
		sendfile(fp,m->user,ASCII_TYPE,0,0);
		fclose(fp);
		return 0;
	}
	if((m->privs & SYSOP_CMD) || strcmp(m->name,argv[1]) == 0){
		changearea(m,argv[1]);
		if(m->nmsgs){
			if(!strcmp(m->name,m->area))
				tprintf("You have ");
			else
				tprintf("%s: ",m->area);
			tprintf("%d message%s -  %d new.\n", m->nmsgs,
			  m->nmsgs == 1 ? " " : "s ", m->newmsgs);
		}
		return 0;
	}
	if(isarea(argv[1])) {
		changearea(m,argv[1]);
		tprintf("%s: %d message%s.\n", m->area, m->nmsgs,
		  m->nmsgs == 1 ? "" : "s");
	}
	else
		tprintf("No such message area: %s\n",argv[1]);
	return 0;
}

/* subroutine to do the actual switch from one area to another */
void
changearea(m,area)
struct mbx *m;
char *area;
{
	closenotes(m);
	m->nmsgs = m->newmsgs = m->current = 0;
	strcpy(m->area,area);
	scanmail(m);
}

static int
dombtelnet(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	int s, len, i;
	char dsocket[MAXSOCKSIZE];
	struct sockaddr_in fsocket;

	m = (struct mbx *) p;
	fsocket.sin_family = AF_INET;
	if(argc < 3)
		fsocket.sin_port = IPPORT_TELNET;
	else
		fsocket.sin_port = atoi(argv[2]);

	if((fsocket.sin_addr.s_addr = resolve(argv[1])) == 0){
		tprintf(Badhost,argv[1]);
		return 0;
	}
	/* Only local telnets are are allowed to the unprivileged user */
	if(!(m->privs & TELNET_CMD) && !ismyaddr(fsocket.sin_addr.s_addr)){
		tprintf(Noperm);
		return 0;
	}
	if((s = socket(AF_INET,SOCK_STREAM,0)) == -1){
		tprintf(Nosock);
		return 0;
	}
	if(fsocket.sin_port == IPPORT_TTYLINK) {
		m->startmsg = mallocw(80);
		len = MAXSOCKSIZE;
		i = getpeername(m->user,dsocket,&len);
		sprintf(m->startmsg,"*** Incoming call from %s@%s ***\n",
			m->name,i != -1 ? psocket(dsocket): Hostname);
	}
	return gw_connect(m,s,(char *)&fsocket,SOCKSIZE);
}

static int
dombfinger(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	char *host, *user = NULLCHAR, buf[8], *newargv[3];

	if(argc > 2){
		tprintf("Usage: F user@host  or  F @host  or  F user.\n");
		return 0;
	}
	host = Hostname;
	if(argc == 2){
		if((host = strchr(argv[1], '@')) != NULLCHAR){
			*host = '\0';
			host++;
		} else
			host = Hostname;
		user = argv[1];
	}
	m = (struct mbx *) p;
	m->startmsg = mallocw(80);
	if(user != NULLCHAR)
		sprintf(m->startmsg,"%s\n",user);
	else
		strcpy(m->startmsg,"\n");
	newargv[0] = "telnet";
	newargv[1] = host;
	sprintf(buf,"%d",IPPORT_FINGER);
	newargv[2] = buf;
	return dombtelnet(3,newargv,p);
}

/* Generic mbox gateway code. It sends and frees the contents of m->startmsg
 * when the connection has been established unless it a null pointer.
 */
int
gw_connect(m,s,fsocket,len)
struct mbx *m;
int s;
char *fsocket;
int len;
{
	int c;
	char *cp;
	struct proc *child;
	struct gwalarm *gwa;

	sockmode(s,SOCK_ASCII);
	child = newproc("gateway supervisor",256,gw_superv,0,Curproc,m,0);
	tprintf("Trying %s...  ",psocket((struct sockaddr *)fsocket));
	dombescape(0,NULLCHARP,(void *)m);
	usflush(Curproc->output);
	if(connect(s,fsocket,len) == -1){
		cp = sockerr(s);
		tprintf("Connection failed: ");
		if(cp != NULLCHAR)
			tprintf("%s errno %d\n",cp,errno);
		else
			tprintf("Escape character sent.\n");
		free(m->startmsg);
		m->startmsg = NULLCHAR;
		killproc(child);
		close_s(s);
		return 0;
	}
	/* The user did not type the escape character */
	killproc(child);
	tputs("Connected.\n");
	
	if(m->startmsg != NULLCHAR){
		usputs(s,m->startmsg);
		free(m->startmsg);
		m->startmsg = NULLCHAR;
	}
	/* Since NOS does not flush the output socket after a certain
	 * period of time, we have to arrange that ourselves.
	 */
	gwa = (struct gwalarm *) mallocw(sizeof(struct gwalarm));
	gwa->s1 = Curproc->output;
	gwa->s2 = s;
	set_timer(&gwa->t,240L);
	gwa->t.func = gw_alarm;
	gwa->t.arg = (void *) gwa;
	start_timer(&gwa->t);
	/* Fork off the receive process */
	child = newproc("gateway in",1024,gw_input,s,NULL,Curproc,0);
	
	for(;;){
		if((c = recvchar(Curproc->input)) == EOF)
			break;
		if(c == m->escape){
			tputs("Disconnecting.");
			if(socklen(Curproc->input,0))
				recv_mbuf(Curproc->input,NULL,0,NULLCHAR,0);
			break;
		}
		if(usputc(s,(char)c) == EOF)
			break;
	}
	stop_timer(&gwa->t);
	free((char *)gwa);
	close_s(s);
	killproc(child); /* get rid of the receive process */
	tprintf("%c%c%c\n",IAC,WONT,TN_ECHO);
	return 0;
}

static void
__stdargs gw_input(s,notused,p)
int s;
void *notused;
void *p;
{
	int c;
	char *cp;
	struct proc *parent;

	parent = (struct proc *) p;
	while((c = recvchar(s)) != EOF)
		tputc((char)c);
	tprintf("Disconnected ");
	cp = sockerr(s);
	if(cp != NULLCHAR)
		tputs(cp);
	/* Tell the parent that we are no longer connected */
	alert(parent,ENOTCONN);
	pwait(Curproc); /* Now wait to be killed */
}

/* Check if the escape character is typed while the parent process is busy
 * doing other things. 
 */
static void
__stdargs gw_superv(null,proc,p)
int null;
void *proc;
void *p;
{
	struct proc *parent;
	struct mbx *m;
	int c;
	parent = (struct proc *) proc;
	m = (struct mbx *) p;
	while((c = recvchar(Curproc->input)) != EOF)
		if(c == m->escape){
			/* flush anything in the input queue */
			if(socklen(Curproc->input,0))
				recv_mbuf(Curproc->input,NULL,0,NULLCHAR,0);
			break;
		}
	alert(parent,EINTR);	 /* Tell the parent to quit */
	pwait(Curproc);		 /* Please kill me */
}

static void
gw_alarm(p)
void *p;
{
	struct gwalarm *gwa = (struct gwalarm *)p;
	char oldbl;
	struct usock *up;

	/* Flush sockets s1 and s2, but first make sure that the socket
	 * is set to non-blocking mode, to prevent the flush from blocking
	 * if the high water mark has been reached.
	 */
	if((up = itop(gwa->s1)) != NULLUSOCK) {
		oldbl = up->noblock;
		up->noblock = 1;
		usflush(gwa->s1);
		up->noblock = oldbl;
	}
	if((up = itop(gwa->s2)) != NULLUSOCK) {
		oldbl = up->noblock;
		up->noblock = 1;
		usflush(gwa->s2);
		up->noblock = oldbl;
	}
	start_timer(&gwa->t);
}

/* States for send line parser state machine */
#define		LOOK_FOR_USER		2
#define		IN_USER			3
#define		AFTER_USER		4
#define		LOOK_FOR_HOST		5
#define		IN_HOST			6
#define		AFTER_HOST		7
#define		LOOK_FOR_FROM		8
#define		IN_FROM			9
#define		AFTER_FROM		10
#define		LOOK_FOR_MSGID		11
#define		IN_MSGID		12
#define		FINAL_STATE		13
#define		ERROR_STATE		14

/* Prepare the addressee.  If the address is bad, return -1, otherwise
 * return 0
 */
static int
mbx_to(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register char *cp;
	int state, i;
	char *user, *host, *from, *msgid;
	int userlen = 0, hostlen = 0, fromlen = 0, msgidlen = 0;
	struct mbx *m;

	m = (struct mbx *)p;
	/* Free anything that might be allocated
	 * since the last call to mbx_to() or mbx_reply()
	 */
	free(m->to);
	m->to = NULLCHAR;
	free(m->tofrom);
	m->tofrom = NULLCHAR;
	free(m->tomsgid);
	m->tomsgid = NULLCHAR;
	free(m->origto);
	m->origto = NULLCHAR;

	if(argc == 1)
		return -1;
	i = 1;
	cp = argv[i];
	state = LOOK_FOR_USER;
	while(state < FINAL_STATE){
#ifdef MBDEBUG
		tprintf("State is %d, char is %c\n", state, *cp);
#endif
		switch(state){
		case LOOK_FOR_USER:
			if(*cp == '@' || *cp == '<' || *cp == '$'){
				state = ERROR_STATE;		/* no user */
			} else {
				user = cp;			/* point at start */
				userlen++;			/* start counting */
				state = IN_USER;
			}
			break;
		case IN_USER:
			switch(*cp){
			case '\0':
				state = AFTER_USER;		/* done with username */
				break;
			case '@':
				state = LOOK_FOR_HOST;		/* hostname should follow */
				break;
			case '<':
				state = LOOK_FOR_FROM;		/* from name should follow */
				break;
			case '$':
				state = LOOK_FOR_MSGID;	/* message id should follow */
				break;
			default:
				userlen++;			/* part of username */
			}
			break;
		case AFTER_USER:
			switch(*cp){
			case '@':
				state = LOOK_FOR_HOST;		/* hostname follows */
				break;
			case '<':
				state = LOOK_FOR_FROM;		/* fromname follows */
				break;
			case '$':
			state = LOOK_FOR_MSGID;	/* message id follows */
				break;
			default:
				state = ERROR_STATE;
			}
			break;
		case LOOK_FOR_HOST:
			if(*cp == '@' || *cp == '<' || *cp == '$'){
				state = ERROR_STATE;
				break;
			}
			if(*cp == '\0')
				break;
			host = cp;
			hostlen++;
			state = IN_HOST;
			break;
		case IN_HOST:
			switch(*cp){
			case '\0':
				state = AFTER_HOST;		/* found user@host */
				break;
			case '@':
				state = ERROR_STATE;		/* user@host@? */
				break;
			case '<':
				state = LOOK_FOR_FROM;		/* fromname follows */
				break;
			case '$':
				state = LOOK_FOR_MSGID;	/* message id follows */
				break;
			default:
				hostlen++;
			}
			break;
		case AFTER_HOST:
			switch(*cp){
			case '@':
				state = ERROR_STATE;		/* user@host @ */
				break;
			case '<':
				state = LOOK_FOR_FROM;		/* user@host < */
				break;
			case '$':
				state = LOOK_FOR_MSGID;	/* user@host $ */
				break;
			default:
				state = ERROR_STATE;		/* user@host foo */
			}
			break;
		case LOOK_FOR_FROM:
			if(*cp == '@' || *cp == '<' || *cp == '$'){
				state = ERROR_STATE;
				break;
			}
			if(*cp == '\0')
				break;
			from = cp;
			fromlen++;
			state = IN_FROM;
			break;
		case IN_FROM:
			switch(*cp){
			case '\0':
				state = AFTER_FROM;		/* user@host <foo */
				break;
			case '<':
				state = ERROR_STATE;		/* user@host <foo< */
				break;
			case '$':
				state = LOOK_FOR_MSGID;	/* message id follows */
				break;
			default:
				fromlen++;
			}
			break;
		case AFTER_FROM:
			switch(*cp){
			case '@':				/* user@host <foo @ */
			case '<':				/* user@host <foo < */
				state = ERROR_STATE;
				break;
			case '$':
				state = LOOK_FOR_MSGID;	/* user@host <foo $ */
				break;
			default:
				state = ERROR_STATE;		/* user@host foo */
			}
			break;
		case LOOK_FOR_MSGID:
			if(*cp == '\0')
				break;
			msgid = cp;
			msgidlen++;
			state = IN_MSGID;
			break;
		case IN_MSGID:
			if(*cp == '\0')
				state = FINAL_STATE;
			else
				msgidlen++;
			break;
		default:
			/* what are we doing in this state? */
			state = ERROR_STATE;
		}
		if(*(cp) == '\0'){
			++i;
			if(i < argc)
			cp = argv[i];
			else break;
		} else
			++cp;
	}
	if(state == ERROR_STATE || state == LOOK_FOR_HOST
	 || state == LOOK_FOR_FROM || state == LOOK_FOR_MSGID)
		return -1;		/* syntax error */

	m->to = mallocw(userlen + hostlen + 2);

	strncpy(m->to, user, userlen);
	m->to[userlen] = '\0';

	if(hostlen){
		m->to[userlen] = '@';
		strncpy(m->to + userlen + 1, host, hostlen);
		m->to[userlen + hostlen + 1] = '\0';
	}
	if(fromlen){
		m->tofrom = mallocw(fromlen + 1);
		strncpy(m->tofrom, from, fromlen);
		m->tofrom[fromlen] = '\0';
	}
	if(msgidlen){
		m->tomsgid = mallocw(msgidlen + 1);
		strncpy(m->tomsgid, msgid, msgidlen);
		m->tomsgid[msgidlen] = '\0';
	}
	return 0;
}

/* This opens the data file and writes the mail header into it.
 * Returns 0 if OK, and -1 if not.
 */
static int
mbx_data(m,cclist,extra)
struct mbx *m;
struct list *cclist;	/* list of carbon copy recipients */
char *extra;		/* optional extra header lines */
{
	time_t t;
	struct list *ap;
	int cccnt = 0;
	
	if((m->tfile = tmpfile()) == NULLFILE)
		return -1;
	time(&t);
	fprintf(m->tfile,Hdrs[RECEIVED]);
	if(m->tofrom != NULLCHAR)
		fprintf(m->tfile,"from %s.bbs ",m->name);
	fprintf(m->tfile,"by %s (%s)\n\tid AA%ld ; %s",
		Hostname, Version, get_msgid(), ptime(&t));
	fprintf(m->tfile,"%s%s",Hdrs[DATE],ptime(&t));
	fprintf(m->tfile,Hdrs[MSGID]);
	if(m->tomsgid)
		fprintf(m->tfile,"<%s@%s.bbs>\n", m->tomsgid, m->name);
	else
		fprintf(m->tfile,"<%ld@%s>\n",get_msgid(), Hostname);
	fprintf(m->tfile,Hdrs[FROM]);
	if(m->tofrom)
		fprintf(m->tfile,"%s%%%s.bbs@%s\n",
			m->tofrom, m->name, Hostname);
	else
		fprintf(m->tfile,"%s@%s\n", m->name, Hostname);
	fprintf(m->tfile,"%s%s\n",Hdrs[TO],m->origto != NULLCHAR ? m->origto : m->to);
	/* Write Cc: line */
	for(ap = cclist; ap != NULLLIST; ap = ap->next) {
		if(cccnt == 0){
			fprintf(m->tfile,"%s",Hdrs[CC]);
			cccnt = 4;
		}
		else {
		       fprintf(m->tfile,", ");
		       cccnt += 2;
		}
		if(cccnt + strlen(ap->val) > 80 - 3) {
		       fprintf(m->tfile,"\n    ");
		       cccnt = 4;
		}
		fputs(ap->val,m->tfile);
		cccnt += strlen(ap->val);
	}
	if(cccnt)
		fputc('\n',m->tfile);
	fprintf(m->tfile,"%s%s\n",Hdrs[SUBJECT],m->line);
	if(!isspace(m->stype) && ((m->stype != 'R' && m->stype != 'F') ||
	  (m->sid & MBX_SID) !=0))
		  fprintf(m->tfile,"%s%c\n", Hdrs[BBSTYPE],m->stype);
	if(extra != NULLCHAR)
		fprintf(m->tfile,extra);
	fprintf(m->tfile,"\n");

	return 0;
}

/* Returns true if string is in history file or if string appears to be a
 * message id generated by our system.
 */
static int
msgidcheck(string)
char *string;
{
     FILE *fp;
     char buf[LINELEN], *cp;
     if(string == NULLCHAR)
	  return 0;
     /* BID's that we have generated ourselves are not kept in the history
      * file. Such BID's are in the nnnn_hhhh form, where hhhh is a part of
      * our hostname, truncated so that the BID is no longer than 11
      * characters.
      */
     if((cp = strchr(string,'_')) != NULLCHAR && *(cp+1) != '\0' && 
	strnicmp(cp+1,Hostname,strlen(cp+1)) == 0)
	  return 1;

     if((fp = fopen(Historyfile,READ_TEXT)) == NULLFILE)
	  return 0;
     while(fgets(buf,LINELEN,fp) != NULLCHAR) {
	  rip(buf);
	  if(stricmp(string,buf) == 0) {	/* found */
	       fclose(fp);
	       return 1;
	  }
     }
     fclose(fp);
     return 0;
}
     
/* Read the rewrite file for lines where the first word is a regular
 * expression and the second word are rewriting rules. The special
 * character '$' followed by a digit denotes the string that matched
 * a '*' character. The '*' characters are numbered from 1 to 9.
 * Example: the line "*@*.* $2@$1.ampr.org" would rewrite the address
 * "foo@bar.xxx" to "bar@foo.ampr.org".
 * $H is replaced by our hostname, and $$ is an escaped $ character.
 * If the third word on the line has an 'r' character in it, the function
 * will recurse with the new address.
 */
char *
rewrite_address(addr)
char *addr;
{
	char *argv[10], buf[MBXLINE], *cp, *cp2, *retstr;
	int cnt;
	FILE *fp;
	if ((fp = fopen(Rewritefile,READ_TEXT)) == NULLFILE)
		return NULLCHAR;
	memset((char *)argv,0,10*sizeof(char *));
	while(fgets(buf,MBXLINE,fp) != NULLCHAR) {
		if(*buf == '#')		/* skip commented lines */
			continue;
		string_tidy(buf);   /* clean up the buffer */
		if((cp = strchr(buf,' ')) == NULLCHAR) /* get the first word */
			if((cp = strchr(buf,'\t')) == NULLCHAR)
				continue;
		*cp = '\0';
		if((cp2 = strchr(buf,'\t')) != NULLCHAR){
			*cp = ' ';
			cp = cp2;
			*cp = '\0';
		}
		if(!wildmat(addr,buf,argv))
			continue;		/* no match */
		rip(++cp);
		cp2 = retstr = (char *) callocw(1,MBXLINE);
		while(*cp != '\0' && *cp != ' ' && *cp != '\t')
			if(*cp == '$') {
				if(isdigit(*(++cp)))
					if(argv[*cp - '0'-1] != '\0')
						strcat(cp2,argv[*cp - '0'-1]);
				if(*cp == 'h' || *cp == 'H') /* Our hostname */
					strcat(cp2,Hostname);
				if(*cp == '$')	/* Escaped $ character */
					strcat(cp2,"$");
				cp2 = retstr + strlen(retstr);
				cp++;
			}
			else
				*cp2++ = *cp++;
		for(cnt=0; argv[cnt] != NULLCHAR; ++cnt)
			free(argv[cnt]);
		fclose(fp);
		/* If there remains an 'r' character on the line, repeat
		 * everything by recursing.
		 */
		if(strchr(cp,'r') != NULLCHAR || strchr(cp,'R') != NULLCHAR) {
			if((cp2 = rewrite_address(retstr)) != NULLCHAR) {
				free(retstr);
				return cp2;
			}
		}
		return retstr;
	}
	fclose(fp);
	return NULLCHAR;
}

/* uuencode a file -- translated from C++; both versions copyright 1990
   by David R. Evans, G4AMJ/NQ0I
*/

static int
uuencode(infile,s,infilename)
FILE *infile;
int s;			/* output socket */
char *infilename;
{
  unsigned long cnt = 0L;			/* 11 Mar 92  GT  BC3 couldn't cope.	*/
  int n_read_so_far = 0;
  int n_written_so_far = 0;
  int in_chars;
  int n;
  int mode = 0755;
  unsigned char in[3], out[4], line[100];
#ifdef UNIX
  struct stat stb;
  
  if(stat(infilename,&stb) != -1)
       mode = stb.st_mode & 0777;	/* get real file protection mode */
#endif

  usprintf(s, "begin %03o %s\n", mode, infilename);

  /* do the encode */
  for(;;) {
    in_chars = fread(in, 1, 3, infile);
    out[0] = in[0] >> 2;
    out[1] = in[0] << 6;
    out[1] = out[1] >> 2;
    out[1] = out[1] | (in[1] >> 4);
    out[2] = in[1] << 4;
    out[2] = out[2] >> 2;
    out[2] = out[2] | (in[2] >> 6);
    out[3] = in[2] << 2;
    out[3] = out[3] >> 2;
    for (n = 0; n < 4; n++)
      out[n] += ' ';
    n_read_so_far += in_chars;
    for (n = 0; n < 4; n++)
      line[n_written_so_far++] = out[n];
    if (((in_chars != 3) || (n_written_so_far == 60)) && n_read_so_far > 0) {
      line[(n_read_so_far + 2) / 3 * 4] = '\0';
      
      usprintf(s,"%c%s\n",n_read_so_far + ' ', line);
      cnt += n_read_so_far;
      n_read_so_far = 0;
      n_written_so_far = 0;
    }
    if (in_chars == 0)
      break;
  }
  if (usprintf(s," \nend\nsize %lu\n", cnt) == EOF)
    return 1;
  return 0;
}

/* Added by paul@wolf.demon.co.uk - Check to see if 'name' contains
 * a bang path, a mail redirector, or a host name != Hostname
 *  Returns 1 if so, 0 if the name is OK
 */

static int
check_mail_privilege (name)
char *name;
{
    char    *host;

    if ((strchr(name,'!') != NULLCHAR ) || ( strchr(name,'%') != NULLCHAR))
        return 1;
    if ((host = strrchr(name,'@')) == NULLCHAR)
        return 0;
    host++;
    if (stricmp(host,Hostname) != 0)
        return 1;

    return 0;
}

/* string_tidy - reduce runs of whitespace to 1 space */
static void string_tidy(char *str)   
{
char *ip=str;
char *op=str;
int c;
while( c = *ip++)
  {
    if( c!=' ' && c != '\t' ) {
      *op++ = c;
      continue;
    }
    *op++=' ';
    while( *ip && (*ip == ' ' || *ip =='\t') ) ip++ ;
  }
  *op='\0';  
}
