/* 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
 */
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#ifdef	UNIX
#include <sys/types.h>
#endif
#include "global.h"
#include "config.h"
#include "bm.h"
#include "timer.h"
#include "ax_mbx.h"
#include "cmdparse.h"
#include "proc.h"
#include "socket.h"
#include "usock.h"
#include "session.h"
#include "ax25.h"
#include "smtp.h"
#include "dirutil.h"
#include "telnet.h"
#include "ftp.h"
#include "ftpserv.h"
#include "netrom.h"
#include "commands.h"
#include "netuser.h"

/*
#define MBDEBUG
*/

extern int Ax25mbox;
extern char *Arealist, *Helpdir, *Rewritefile;
static int Stelnet = -1;
static int Nrsocket = -1;
static struct mbx *Mbox[NUMMBX];
unsigned Maxlet = BM_NLET+1;
static char Mbbanner[] = "[NET-$]\nWelcome to the %s TCP/IP Mailbox (%s)\n";
static char Mbmenu[] = "A,B,C,D,E,F,G,H,J,K,L,N,R,S,T,U,W,Z,? >\n";
static char Longmenu[] = "(A)rea, (B)ye, (C)hat, (D)ownload, (E)scape, (F)inger, (G)ateway, (H)elp,\n(J)heard, (K)ill, (L)ist, (N)etrom, (R)ead, (S)end, (T)elnet, (U)pload,\n(W)hat, (Z)ap >\n";
#if	!defined(AX25)
static char Noservice[] = "Service unavailable.\n";
#endif
char Noperm[] = "Permission denied.\n";
static char Nosock[] = "Can't create socket\n";
static char Loginbanner[] = "\nKA9Q NOS (%s)\n\n";
static char Howtoend[] = "Terminate with /EX or ^Z in first column (^A aborts):\n";
static int donothing __ARGS((int argc,char *argv[],void *p));
static int dosend __ARGS((int argc,char *argv[],void *p));
static int dochat __ARGS((int argc,char *argv[],void *p));
static int dosid __ARGS((int argc,char *argv[],void *p));
static int dorevfwd __ARGS((int argc,char *argv[],void *p));
static int dombescape __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 dosysop __ARGS((int argc,char *argv[],void *p));
static int doarea __ARGS((int argc,char *argv[],void *p));
static int dobye __ARGS((int argc,char *argv[],void *p));
static int dombhelp __ARGS((int argc,char *argv[],void *p));
static int dogateway __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 int dombnetrom __ARGS((int argc,char *argv[],void *p));
static int dombnrident __ARGS((int argc,char *argv[],void *p));
static int dombnrnodes __ARGS((int argc,char *argv[],void *p));
static int dombnrconnect __ARGS((int argc,char *argv[],void *p));
static struct mbx *newmbx __ARGS((void));
static int mbx_parse __ARGS((struct mbx *m));
static int mbx_getname __ARGS((struct mbx *m));
static int mbxrecvline __ARGS((int s,char *buf,int len,int escape));
static int gw_connect __ARGS((struct mbx *m,int s,char *fsocket,int len));
static void gw_input __ARGS((int s,void *notused,void *p));
static void gw_superv __ARGS((int null,void *proc,void *p));
static void gw_alarm __ARGS((void *p));
static int mbx_to __ARGS((int argc,char *argv[],void *p));
static int mbx_data __ARGS((struct mbx *m));
static char *rewrite_address __ARGS((char *addr));

static struct cmds Mbcmds[] = {
	"",		doreadnext,	0, 0, NULLCHAR,
	"area",		doarea,		0, 0, NULLCHAR,
	"send",		dosend,		0, 0, NULLCHAR,
	"read",		doreadmsg,	0, 2, "R 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 filename",
	"upload",	dombupload,	0, 2, "U filename",
	"what",		dowhat,		0, 0, NULLCHAR,
	"zap",		dozap,		0, 2, "Z filename",
	"gateway",	dogateway,	0, 3, "G interface callsigns",
	"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",		dobye,		0, 0, NULLCHAR,
	"help",		dombhelp,	0, 0, NULLCHAR,
	"?",		dombhelp,	0, 0, NULLCHAR,
	"[",		dosid,		0, 0, NULLCHAR,
	"f>",		dorevfwd,	0, 0, NULLCHAR,
	"@",		dosysop,	0, 0, NULLCHAR,
	NULLCHAR,	NULLFP,		0, 0, "Huh?",
};

#ifdef	AX25
int
ax25start(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	int s,type;

	freeargs(argc,argv);	/* Args are not used */
	if (Axi_sock != -1)
		return 0;

	psignal(Curproc,0);	/* Don't keep the parser waiting */
	chname(Curproc,"AX25 listener");
	Axi_sock = socket(AF_AX25,SOCK_STREAM,0);
	/* bind() is done automatically */
	if(listen(Axi_sock,1) == -1){
		close_s(Axi_sock);
		return -1;
	}
	for(;;){
		if((s = accept(Axi_sock,NULLCHAR,NULLINT)) == -1)
			break;	/* Service is shutting down */

		type = AX25TNC;
		/* Eat the line that triggered the connection
		 * and then start the mailbox
		 */
		recvline(s,NULLCHAR,80); 
		newproc("mbox",2048,mbx_incom,s,(void *)&type,NULL);
	}
	close_s(Axi_sock);
	Axi_sock = -1;
	return 0;
}
int
ax250(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	close_s(Axi_sock);
	Axi_sock = -1;
	return 0;
}
#endif
/* Start up Telnet server */
int
telnet1(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct sockaddr_in lsocket;
	int s;
	int type;

	if(Stelnet != -1){
		freeargs(argc,argv);
		return 0;
	}
	psignal(Curproc,0); 	/* Don't keep the parser waiting */
	chname(Curproc,"Telnet listener");

	lsocket.sin_family = AF_INET;
	lsocket.sin_addr.s_addr = INADDR_ANY;
	if(argc < 2)
		lsocket.sin_port = IPPORT_TELNET;
	else
		lsocket.sin_port = atoi(argv[1]);
	freeargs(argc,argv);
	Stelnet = socket(AF_INET,SOCK_STREAM,0);
	bind(Stelnet,(char *)&lsocket,sizeof(lsocket));
	listen(Stelnet,1);
	for(;;){
		if((s = accept(Stelnet,NULLCHAR,(int *)NULL)) == -1)
			break;	/* Service is shutting down */

		if(availmem() < Memthresh){
			shutdown(s,1);
		} else {
			/* Spawn a server */
			type = TELNET;
			newproc("mbox",2048,mbx_incom,s,(void *)&type,NULL);
		}
	}
	return 0;
}
/* Stop telnet server */
int
telnet0(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	close_s(Stelnet);
	Stelnet = -1;
	return 0;
}

#ifdef	NETROM
int
nr4start(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	int s,type;

	freeargs(argc,argv);
	if (Nrsocket != -1)
		return -1;

	psignal(Curproc,0);	/* Don't keep the parser waiting */
	chname(Curproc,"NETROM listener");
	Nrsocket = socket(AF_NETROM,SOCK_SEQPACKET,0);
	/* bind() is done automatically */
	if (listen(Nrsocket,1) == -1) {
		close_s(Nrsocket);
		Nrsocket = -1;
		return -1;
	}
	for(;;){
		if((s = accept(Nrsocket,NULLCHAR,NULLINT)) == -1)
			break;	/* Service is shutting down */

		type = NRSESSION;
		newproc("mbox",2048,mbx_incom,s,(void *)&type,NULL);
	}
	close_s(Nrsocket);
	Nrsocket = -1;
	return 0;
}
int
nr40(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	close_s(Nrsocket);
	Nrsocket = -1;
	return 0;
}

#endif
int
dombox(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	domboxdisplay();
	return 0;
}

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

	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("%-10s %-5s    %-3u %s\n",m->name,
				   states[m->state],m->user,
				   j != -1 ? psocket(fsocket): "") == EOF)
				break;
		}
	}
}

static 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;
}

/* Incoming mailbox session */
void
mbx_incom(s,t,p)
int s;
void *t;
void *p;
{
	struct mbx *m;
	char *newargs[2];
	
	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 dobye()
	 * 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 */
	/* get the name of the remote station */
	if(mbx_getname(m) == -1)
		close_s(s);

	m->state = MBX_CMD;	/* start in command state */
	
	/* Now say hi */
	tprintf(Mbbanner,Hostname,Version);
	/* Enable our local message area */
	newargs[1] = m->name;
	doarea(2,newargs,(void *)m);
	tprintf(Longmenu);
	while(mbxrecvline(s,m->line,MBXLINE,-1) != EOF){
	if(mbx_parse(m) == 1)
			tprintf("Bad syntax.\n");
		if(isnewprivmail(m))
			tprintf("You have new mail.\n");
		scanmail(m);
		tprintf((m->sid & MBX_SID) ? ">\n" : Mbmenu);
		m->state = MBX_CMD;
	}
	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);
}

/* "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[] = "slr[";		/* S,L,R are two-letter commands */
static int
mbx_parse(m)
struct mbx *m;
{
	char *cp;
	int i;
	/* 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;
		}
	}
	return cmdparse(Mbcmds,m->line,(void *)m);
}

static int
mbx_getname(m)
struct mbx *m;
{
	char *cp;
	union sp sp;
	char tmp[MAXSOCKSIZE];
	char buf[MBXLINE];
	int len = MAXSOCKSIZE;
	int anony = 0;

	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.
	 */
	sp.p = tmp;
	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;
				}
		return 0;
#endif
	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;
			tprintf("Password: %c%c%c",IAC,WILL,TN_ECHO);
			usflush(m->user);
			if(mbxrecvline(m->user,buf,MBXLINE,-1) == EOF)
				return -1;
			tprintf("%c%c%c\n",IAC,WONT,TN_ECHO);
			usflush(m->user);
			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);
				return 0;
			}
			tprintf("Login incorrect\n");
		}
	}
	return 0;
}

/* 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.
 */
static int
mbxrecvline(s,buf,len,escape)
int s;
char *buf;
int len;
int escape;
{
	int c, cnt = 0, opt;
	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;
			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);
				}
/* to be fixed 			usflush(Curproc->output);*/
				continue;
			}
			if(c != IAC && opt == EOF)
				break;
		}
		/* ordinary character */
		if(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;
}

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

	m = (struct mbx *)p;
	close_s(m->user);
	return 0;
}
static int
dombhelp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char buf[255];
	int i;
	FILE *fp;
	if(*argv[0] == '?') {
		tprintf(Longmenu);
		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')
		sprintf(buf,"%s/help.hlp",Helpdir);
	if((fp = fopen(buf,READ_TEXT)) != NULLFILE) {
		sendfile(fp,Curproc->output,ASCII_TYPE);
		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];

	newargv[0] = "telnet";
	newargv[1] = Hostname;
	sprintf(buf,"%d",IPPORT_TTYLINK);
	newargv[2] = buf;
	return dombtelnet(3,newargv,p);
}

static int
dosend(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	int s;
	char *host, *cp, fullfrom[MBXLINE];
	struct mbx *m;

	m = (struct mbx *)p;
	s = m->user;
	if(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 name [@ host] [< from_addr] [$bulletin_id]\n");
		}
		return 0;
	}
	if((cp = rewrite_address(m->to)) != NULLCHAR)
		if(strcmp(m->to,cp) != 0) {
			m->origto = m->to;
			m->to = cp;
		}
	if(validate_address(m->to) == 0){
		if(m->sid & MBX_SID)
			tprintf("NO - bad address\n");
		else
			tprintf("Bad user or host name\n");
		/* We don't free any buffers here. They are freed upon the next
		 * call to mbx_to() or to dobye()
		 */
		return 0;
	}
	m->state = MBX_SUBJ;
	tprintf((m->sid & MBX_SID) ? "OK\n" : "Subject: ");
	if(mbxrecvline(s,m->line,MBXLINE,-1) == -1)
		return 0;
	if(mbx_data(m) == -1){
		tprintf("Can't create temp file for mail\n");
		return 0;
	}
	m->state = MBX_DATA;
	if((m->sid & MBX_SID) == 0)
		tprintf("Enter message.  %s",Howtoend);

	while(mbxrecvline(s,m->line,MBXLINE,-1) != -1){
		if(m->line[0] == 0x01){  /* CTRL-A */
			fclose(m->tfile);
			tprintf("Aborted.\n");
			return 0;
		}
		if(m->line[0] == CTLZ ||
		 strcmp(m->line, "/ex") == 0
		  || strcmp(m->line, "/EX") == 0){
			if((host = strchr(m->to,'@')) == NULLCHAR)
				host = Hostname;	/* use our hostname */
			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);
			fseek(m->tfile,0L,0);		/* reset to beginning */
			if(queuejob(m->tfile,host,m->to,fullfrom) != 0)
				tprintf("Couldn't queue message for delivery\n");
			break;
		} else
			fprintf(m->tfile,"%s\n",m->line);		/* not done yet! */
	}
	fclose(m->tfile);
	return 0;
}

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

	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_SID_RLI;
	return 0;
}

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

	m = (struct mbx *)p;
	if(m->sid & MBX_SID){
		/* RLI BBS' expect us to disconnect if we
		 * have no mail for them, which of course
		 * we don't, being rather haughty about our
		 * protocol superiority.
		 */
		if(m->sid & MBX_SID_RLI)
			dobye(0,NULL,m);
		return 0;
	}
	return -1;
}

static 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,sys_errlist[errno]);
	else
		sendfile(fp,m->user,ASCII_TYPE);
	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,sys_errlist[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;
		fprintf(fp,"%s",buf);
#if !defined(UNIX) && !defined(__TURBOC__) && !defined(AMIGA)
		/* Needed only if the OS uses a CR/LF
		 * convention and fputc doesn't do
		 * an automatic translation
		 */
		if(fputc('\r',fp) == EOF)
			break;
#endif
		if(fputc('\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,sys_errlist[errno]);
	else
		sendfile(fp,m->user,ASCII_TYPE);
	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",sys_errlist[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 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;
		cmdparse(Cmds,m->line,NULL);
	}
	return 0;
}

static int
doarea(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	char buf[MBXLINE], *cp;
	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);
		fclose(fp);
		return 0;
	}
	if((m->privs & SYSOP_CMD) || strcmp(m->name,argv[1]) == 0){
		closenotes(m);
		m->nmsgs = m->newmsgs = 0;
		strcpy(m->area,argv[1]);
		scanmail(m);
		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((fp = fopen(Arealist,READ_TEXT)) == NULLFILE){
		tprintf("There are no public message areas.\n");
		return 0;
	}
	while(fgets(buf,MBXLINE,fp) != NULLCHAR){
		/* The first word on each line is all that matters */
		if((cp = strchr(buf,' ')) == NULLCHAR)
			if((cp = strchr(buf,'\t')) == NULLCHAR)
				continue;
		*cp = '\0';
		if((cp = strchr(buf,'\t')) != NULLCHAR)
			*cp = '\0';
		if(stricmp(argv[1],buf) == 0){
			closenotes(m);
			m->nmsgs = m->newmsgs = 0;
			strcpy(m->area,buf);
			scanmail(m);
			tprintf("%s: %d message%s.\n", m->area, m->nmsgs,
			  m->nmsgs == 1 ? "" : "s");
			fclose(fp);
			return 0;
		}
	}
	fclose(fp);
	tprintf("No such message area as: %s\n",argv[1]);
	return 0;
}

#ifdef AX25
static int
dogateway(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	struct sockaddr_ax fsocket;
	int ndigis,i,s;
	char digis[MAXDIGIS][AXALEN];
	char target[AXALEN];

	m = (struct mbx *)p;
	if(!(m->privs & AX25_CMD)){
		tprintf(Noperm);
		return 0;
	}
	/* If digipeaters are given, put them in the routing table */
	if(argc > 3){
		setcall(target,argv[2]);
		ndigis = argc - 3;
		if(ndigis > MAXDIGIS){
			tprintf("Too many digipeaters\n");
			return 1;
		}
		for(i=0;i<ndigis;i++){
			if(setcall(digis[i],argv[i+3]) == -1){
				tprintf("Bad digipeater %s\n",argv[i+3]);
				return 1;
			}
		}
		if(ax_add(target,AX_LOCAL,digis,ndigis) == NULLAXR){
			tprintf("Route add failed\n");
			return 1;
		}
	}
	if((s = socket(AF_AX25,SOCK_STREAM,0)) == -1){
		tprintf(Nosock);
		return 0;
	}
	fsocket.sax_family = AF_AX25;
	setcall(fsocket.ax25_addr,argv[2]);
	strncpy(fsocket.iface,argv[1],ILEN);
	m->startmsg = mallocw(80);
	sprintf(m->startmsg,"*** LINKED to %s\n",m->name);
	return gw_connect(m,s,(char *)&fsocket, sizeof(struct sockaddr_ax));
}
#else
static int
dogateway(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	tputs(Noservice);
	return 0;
}
#endif

static int
dombtelnet(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	int s;
	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;
	}
	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.
 */
static 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;
	
	child = newproc("gateway supervisor",256,gw_superv,0,Curproc,m);
	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,240);
	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);
	
	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
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
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);
}

#ifdef	NETROM
static char mbnrid[80];
static struct cmds Mbnrcmds[] = {
	"",		donothing,	0, 0, NULLCHAR,
	"connect",	dombnrconnect,	0, 0, NULLCHAR,
	"ident",	dombnrident,	0, 0, NULLCHAR,
	"nodes",	dombnrnodes,	0, 0, NULLCHAR,
	"users",	dombox,		0, 0, NULLCHAR,
	NULLCHAR,	NULLFP,		0, 0, NULLCHAR,
};

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

	if(Nrifaces[0].iface == NULLIF){
		tprintf("NET/ROM not activated.\n");
		return 0;
	}
	m = (struct mbx *) p;
	sprintf(mbnrid,"%s:%s",Nrifaces[0].alias,
	  pax25(m->line,Nrifaces[0].iface->hwaddr));
	tprintf("Connected to %s	",mbnrid);
	dombescape(1,NULLCHARP,p);
	 
	while(mbxrecvline(m->user,m->line,MBXLINE,m->escape) >= 0)
		if(cmdparse(Mbnrcmds,m->line,(void *)m) == -1)
			tprintf("%s> Invalid command (CONNECT IDENT NODES USERS)\n",
			  mbnrid);
	return 0;
}

/* Called every time a blank line is received. */
static int
donothing(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return 0;
}

static int
dombnrident(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	tprintf("%s> %s (%s)\n",mbnrid,Hostname,Version);
	return 0;
}

static int
dombnrnodes(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(argc < 2)
		return doroutedump();
	return dorouteinfo(argc,argv,p);
}

static int
dombnrconnect(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct mbx *m;
	char *np, buf[7];
	int s;
	struct sockaddr_nr lsocket, fsocket;
	char alias[AXBUF];

	m = (struct mbx *) p;
	if(!(m->privs & NETROM_CMD)){
		tprintf(Noperm);
		return 0;
	}
	if((s = socket(AF_NETROM,SOCK_SEQPACKET,0)) == -1){
		tprintf(Nosock);
		return 0;
	}
	lsocket.nr_family = AF_NETROM;
	/* Set up our local username, bind would use Mycall instead */
	if(strlen(m->name) > 6)
		strncpy(buf,m->name,6);
	else
		strcpy(buf,m->name);
	buf[6] = '\0';
	putalias(alias,buf,0);
	setcall(lsocket.nr_addr.user,alias);

	/* Putting anything else than Mycall here will not work */
	memcpy(lsocket.nr_addr.node,Mycall,AXALEN);
	bind(s,(char *)&lsocket,sizeof(struct sockaddr_nr));

	/* See if the requested destination could be an alias, and
	 * find and use it if it is.  Otherwise assume it is an ax.25
	 * address.
	 */
	if(putalias(alias,argv[1],0) != -1 &&
		(np = find_nralias(alias)) != NULLCHAR){
		memcpy(fsocket.nr_addr.user,np,AXALEN);
		memcpy(fsocket.nr_addr.node,np,AXALEN);
	} else {	/* parse ax25 callsign */
		/* Only the user callsign of the remote station is never used by */
		/* NET/ROM, but it is needed for the psocket() call. */
		setcall(fsocket.nr_addr.user,argv[1]);
		setcall(fsocket.nr_addr.node,argv[1]);
	}
	fsocket.nr_family = AF_NETROM;
	return gw_connect(m,s,(char *)&fsocket, sizeof(struct sockaddr_nr));
}
#endif

/* 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()
	 */
	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)
struct mbx *m;
{
	time_t t;
	
	if((m->tfile = tmpfile()) == NULLFILE)
		return -1;
	time(&t);
	fprintf(m->tfile,"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,"Date: %s",ptime(&t));
	fprintf(m->tfile,"Message-Id: ");
	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,"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,"To: %s\n",m->origto != NULLCHAR ? m->origto : m->to);
	fprintf(m->tfile,"Subject: %s\n",m->line);
	if(!isspace(m->stype))
		fprintf(m->tfile,"X-BBS-Msg-Type: %c\n", m->stype);
	fprintf(m->tfile,"\n");
	
	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".
 * If the third word on the line has an 'r' character in it, the function
 * will recurse with the new address.
 */
static 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;
		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 == '$' && isdigit(*(cp+1))) {
				/* The strlen() check here should not be
				 * necessary, but if strcpy() on an empty 
				 * string crashes Turbo-C maybe strcat() does
				 * too...
				 */
				if(strlen(argv[*++cp - '0'-1]))
					strcat(cp2,argv[*cp - '0'-1]);
				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) {
			cp2 = rewrite_address(retstr);
			free(retstr);
			return cp2;
		}
		return retstr;
	}
	fclose(fp);
	return NULLCHAR;
}
