/* ftpb.c */

/*
 *  Includes
 */
#include <stdio.h>
#include <string.h>

#include "os.h"
#include "vrsion.h"
#include "vftp.h"
#include "dfault.h"
#include "evtdef.h"
#include "hstdef.h"
#include "prodef.h"
#include "ftppi.h"
#include "cliutl.h"
#include "tsxutl.h"
#include "kbdutl.h"
#include "usrblk.h"
#include "inicli.h"
#include "cticks.h"
#include "suspnd.h"

/*
 * Internal routines
 */

/*
 * Due to DECUS C aborts from lack of space
 * all functions/subroutines of type 'int'
 * are no longer explicitly defined.
 *
 * DECUS C defaults to 'int', VOID == int
 */

#if 0
extern	int	ftpgets();	/* (str,lim,echo) read line from keyboard */
extern	VOID	ftpexit();	/* (i) exit from ftp */
extern	int	dumpcon();	/* dump connection to screen */
extern	VOID	filter();	/* (cnt) filter telnet options */
extern	int	question();	/* (cstr,clen,qstr) pose a question */
extern	int	ftpresp();	/* (s,len) response function for tsxutl */
extern	VOID	lcd();		/* (s) change local directory */
extern	VOID	ldelete();	/* (s) delete a local file */
extern	VOID	ldelfil();	/* (s) subroutine of ldelete */
extern	VOID	lls();		/* (s) list local directory */
extern	VOID	lmkdir();	/* (s) make a local directory */
extern	VOID	lprotect();	/* (s) set protection flag on local file */
extern	VOID	setfilp();	/* (s) subroutine of lprotect */
extern	VOID	lunprotect();	/* (s) clr protection flag on a local file */
extern	VOID	clrfilp();	/* (s) subroutine of lunprotect */
extern	VOID	lsterr();	/* print error string from tsxutl */
extern	int	toggle();	/* (cstr,wstr,flag,on,off) toggle flags */
extern	VOID	ftppi();	/* (command) protocol interpreter */
extern	int	setmode();	/* (mode,filnam) set mode, return old mode */
extern	int	ftpopen();	/* (lpnum,dst,port) open FTP connection */
extern	int	ftpport();	/* prepare port */
extern	int	ftpcnct();	/* open FTP data connection */
extern	int	ftplstn();	/* listen for FTP data connection */
extern	int	ftpdo();	/* (s,filnam) do a command */
extern	int	ftpreplies();	/* (rcode) get response to server */
extern	int	ftpabort();	/* (how) abort FTP */
extern	int	rgetline();	/* get line from remote server */
extern	VOID	userftpd();	/* FTP recieve and send file functions */
extern	int	getword();	/* (s,word) remove word from string */
extern	int	checkoredir();	/* (cmnd,filnam) check redirected output */
extern	int	finduniq();	/* (nam,lst,lstsz) find unique string */
extern	int	checkevent();	/* process server events */
extern	int	getnname();	/* (s,word) get next name from list */
extern	VOID	strlwr();	/* (s) convert string to lower case */
extern	int	chkusr();	/* check username / password */
extern	int	Scompass();	/* (ps,en) compare passwords */
extern	VOID	printtxt();	/* (s) printout text */
#endif

extern	FILE *	opncap();	/* (filspc,mode) open the capture file */
extern	FILE *	opnred();	/* (filspc) open the list capture file */
extern	char	*stptok();	/* (p,word,ilen,delim) stop on token */
extern	char	*stpblk();	/* (s) skip spaces and tabs */

#define	FASCII		 0
#define FIMAGE		 1

#define FALSE		 0
#define TRUE		 1
#define SUCCESS		 2
#define HAVEDATA	 4
#define ERROR	   	-1
#define NONE	   	-2
#define ABORT	   	-3
#define INCOMPLETE	-4
#define AMBIGUOUS	-5

/*
 * Transfer Delay Time Limits
 */

#define	MINTFRDLY	3
#define	MAXTFRDLY	15

/*
 * READSIZE is the system BLOCK SIZE
 * BUFFERS must be an integral multiple of READSIZE
 * xs[] must be at least 1 byte larger than READSIZE
 */

#ifdef	DEBUGOPTION
#define BUFFERS		1024	/* size of buffer */
#else
#define BUFFERS		2048	/* size of buffer */
#endif

#define READSIZE	512	/* how much to read */
#define	GBUFSIZE	200	/* general buffer size */

extern	char *usetxt[];
extern	char *pass;		/* password file */
extern	char *capfil;		/* capture file */

extern	int
	pswdreqd,		/* password required */
	xp,			/* general pointer */
	towrite,		/* file transfer pointer */
	len,			/* file transfer length */
	ftpcskt,		/* current command port */
	ftpdskt,		/* current data port */
	ftpstate,		/* state for background process */
	ftpfilemode,		/* file open mode for transfer */
	finished,		/* file transfer complete flag */
	tfrdly,			/* transfer suspend time */
	tfrabtf,		/* transfer abort flag */
	hash,			/* hash mark printing */
	hashnum,		/* number of hash marks printed */
	hashcnt,		/* hash mark counter */
	linked,			/* not connected */
	sendport,		/* to send ports or not */
	passive,		/* to request passive mode or not */
	verbose,		/* informative messages */
	redirect,		/* check for ouput redirection */
	capture,		/* capture list flag */
	redout,			/* redirected output specified flag */
	redocap,		/* redirected output capture flag */
	bell,			/* sound bell */
	autologin,		/* login on connect */
	prompt,			/* check on multiple commands */
	fromtty,		/* default input from tty */
	fildev,			/* file oriented devices */
	wild;			/* expand wildcards */

extern	unsigned int
	iosize;			/* transfer size in 512 byte blocks */

extern	long
	start,			/* transfer start time */
	tbytes;			/* number of bytes transferred */

extern	FILE *fromfp;		/* file pointer for input command file */
extern	FILE *ftpfh;		/* file pointer for ftp transfers */
extern	FILE *capfp;		/* capture file for multiple commands */
extern	FILE *redfp;		/* capyure file for redirected commands */

extern	char destname[];	/* who to connect to */
extern	char ftpcommand[];	/* command to execute */
extern	char printline[];	/* line to display */
extern	char s[];		/* line from other side */
extern	char spathp[];		/* save path buffer p */
extern	char spathq[];		/* save path buffer q */
extern	char xs[];		/* buffer space for file transfer */

extern	char fromfile[];

/*
 *  ftpopen
 *
 *  open an FTP connection
 */
int ftpopen(lpnum,dst,port)
int lpnum,port;
char *dst;
{
	register int flag;
	int cl,ev,dat;

	/*
	 * Open a control connection
 	 */
	ftpcskt = addsess(lpnum,dst,port);
	if(ftpcskt >= 0) {
		flag = 1;
	} else {
		flag = 0;
	}
	while(flag && !tstbrk()) {
		if(Sgetevent(ABORTCLASS,&cl,&dat))
			ftpexit(ABORT);

		errhandle();
		ev = Sgetevent(CONCLASS,&cl,&dat);
		switch(ev) {
		case CONOPEN:
			/*
			 * Log the Session
			 */
			sprintf(printline,"FTP: %s", destname);
			logsession(dat,printline);
			return(TRUE);

		case CONCLOSE:
		case CONFAIL:
			flag = 0;
			break;

		default:
			if(ev) {
				Stmrset(cl,ev,dat,1);
			} else {
				suspnd(0);
			}
		}
	}
	/*
	 * error on open
	 */
	skclose(ftpcskt);
	ftpcskt = skrelease(ftpcskt,1);
	skclose(ftpdskt);
	ftpdskt = skrelease(ftpdskt,1);
	errhandle();
	kbprintf("Unable to connect to %s\r\n",dst);
	return(FALSE);
}

/*
 * ftpport
 *
 * Open a connection (PASV mode) or setup a
 * Listen connection (ACTV mode).
 *
 * Returns socket number
 * or -1 on error.
 */
int ftpport()
{
	int sknum;

	/*
	 * If PASV mode is enabled then get port number
	 * from Server and open connection.
	 */
	if(passive) {
		sknum = ftpcnct();
	} else {
		sknum = -1;
	}
	/*
	 * If PASV mode is not selected
	 * or refused by server then allow
	 * server to do an ACTIVE open.
	 */
	if(sknum == -1) {
		sknum = ftplstn();
	}
#ifdef	DEBUGOPTION
if(debug&0x08) {
	printf("ftpport(): sknum = %d\r\n", sknum);
}
#endif
	/*
	 * socket number
	 */
	if(sknum == -1) {
		kb_puts("FTP Data Connection Failed to Open\r\n");
	}
	return(sknum);
}

/*
 * ftpcnct
 *
 * Client Opens Connections
 */
int ftpcnct()
{
	register struct socket *skt;
	register char *p;
	char st[24];
	int i,rcode,sknum;
	int ip[4],port[2];
	long stime;

	/*
	 * send PASV command
	 */
	skwrite(ftpcskt,"PASV\r\n",6);
	skenque(ftpcskt,1);
	/*
	 * get response
	 */
#ifdef	DEBUGOPTION
if(debug&0x08) {
	printf("ftplstn(): before ftpreplies() call \r\n");
}
#endif
	i = ftpreplies(&rcode);
#ifdef	DEBUGOPTION
if(debug&(0x08|0x10)) {
	printf("return from ftpreplies()\r\n");
}
#endif
	if(i != TRUE || rcode != 227) {
		return(-1);
	}
	/*
	 * Scan upto PORT Data
	 */
	p = s+4;
	while(*p < '0' || *p > '9') {
		if(*p++ == 0) {
			return(-1);
		}
	}
	/*
	 * full port number
	 */
	if(sscanf(	p,
			"%d,%d,%d,%d,%d,%d",
			ip+0,ip+1,ip+2,ip+3,
			port+0,port+1
							) != 6) {
		return(-1);
	}
	/*
	 * Create socket for data connection
	 */
	sknum = socket(1);
	if(sknum == -1)
		return(-1);

	/*
	 * Connecting to
	 */
	sprintf(	st,
			"%d.%d.%d.%d #%u",
			ip[0], ip[1], ip[2], ip[3],
			(port[0]<<8) + port[1]
		);
#ifdef	DEBUGOPTION
if(debug&0x02) {
	printf("%s\r\n",st);
}
#endif
	connect(sknum,st);
	/*
	 * Wait for connection to open
	 */
	stime = cticks(NULL);
	while(elapsed(stime) < 300L) {
		if(!sktest(sknum)){
			return(sknum);
		}
		suspnd(0);
	}
	skrelease(sknum,1);
	return(-1);
}

/*
 * ftplstn
 *
 * Server Opens Connections
 */
int ftplstn()
{
	register struct socket *skt;
	register char *myip;
	int port,rcode,sknum;

	/*
	 * Create socket for data connection
	 */
	sknum = socket(1);
	if(sknum == -1)
		return(-1);
	/*
	 * setup the appropriate listening port
	 * the default port HFTP-1 should be used
	 * only for single user mode
	 * TCPIP will select a unique port if the
	 * default port (skt->sktport) is zero,
	 * else the specified port will be used.
	 * return the socket number if ok,
	 * else return -1 on error.
	 */
	if(!sendport) {
		listen(sknum,HFTP-1);
		return(sknum);
	}
#ifdef	DEBUGOPTION
if(debug&0x08) {
	printf("ftplstn(): before dumpcon() call \r\n");
}
#endif
	dumpcon();
	/*
	 * Setup listener
	 */
	port = listen(sknum,0);
	/*
	 * get my ip number
	 */
	skt = (struct socket *) mapskt(sknum);
	myip = skt->tcpout.i.ipsource;
	/*
	 * full port number
	 */
	sprintf(printline,"PORT %d,%d,%d,%d,%d,%d\r\n",
		*(myip+0) & 0xFF, *(myip+1) & 0xFF,
		*(myip+2) & 0xFF, *(myip+3) & 0xFF,
		(port>>8) & 0xFF,      port & 0xFF);
#ifdef	DEBUGOPTION
if(debug&0x02) {
		printf(printline);
}
#endif
	/*
	 * send PORT command
	 */
	skwrite(ftpcskt,printline,strlen(printline));
	skenque(ftpcskt,1);
	/*
	 * get response
	 */
#ifdef	DEBUGOPTION
if(debug&0x08) {
	printf("ftplstn(): before ftpreplies() call \r\n");
}
#endif
	rcode = ftpreplies(&rcode);
#ifdef	DEBUGOPTION
if(debug&(0x08|0x10)) {
	printf("return from ftpreplies()\r\n");
}
#endif
	/*
	 * socket number
	 */
	if(rcode == TRUE) {
		return(sknum);
	} else {
		skrelease(sknum,1);
		return(-1);
	}
}

/*
 * ftpdo
 *
 * Do whatever command is sent from the user interface using
 * userftpd, the background file handler
 * Returns code from ftpreplies
 */
int ftpdo(str)
register char *str;
{
	register int i;
	int rcode;
	char name[50],name2[50];

	/*
	 * command to upper case
	 */
	for(i=0; i<4; i++) {
		str[i] = toupper(str[i]);
	}
	/*
	 * put file
	 */
	if(!strncmp(str,"STOR",4)) {
		/*
		 * first arg - local file
		 */
		getword(&str[5],name);
		/*
		 * check for second argument
		 */
		if(!str[5]) {
			/*
			 * if only one argument
			 */
			strcpy(&str[5],rmtfile(name));
		} else {
			/*
			 * second arg - removes quotes etc.
			 */
			getword(&str[5],name2);
			/*
			 * copy back into command
			 */
			strcpy(&str[5],name2);
		}
		/*
		 * open local file
		 */
		ftpfh = rtopen(name,"rn",0,0);
		if(ftpfh == NULL) {
			kb_puts(" Cannot open file to transfer.\r\n");
			return(-1);
		}
		/*
		 * open data connection
		 */
#ifdef	DEBUGOPTION
if(debug&0x04) {
		printf("ftpdo(STOR): before calling ftpport()\r\n");
}
#endif
		ftpdskt = ftpport();
		if(ftpdskt == -1) {
			rtclose(ftpfh);
			ftpfh = NULL;
			return(-1);
		}
		iosize = ftpfh->io_size;
		ftpstate = 20;
#ifdef	DEBUGOPTION
if(debug&0x04) {
		printf("ftpdo(STOR): after calling ftpport()\r\n");
}
#endif
	} else
	if(!strncmp(str,"RETR",4)) {
		/*
		 * get remote file
		 */
		getword(&str[5],name);
		/*
		 * Allow files larger than TSX default by
		 * setting first character of iotype = x
		 */
		if(!str[5]) {
			/*
			 * if only one argument
			 * strip name of device and subdirectories
			 */
			strcpy(name2,rmtfile(name));
	  	} else {
			/*
			 * two args present
			 */
			getword(&str[5],name2);
		}
		/*
		 * open local file
		 */
		ftpfh = rtopen(name2, "xwn",fildev,fromtty&&prompt);
		if(ftpfh == -1)
			return(-1);
		if(ftpfh == NULL) {
			kbprintf("Cannot open file to receive: %s\r\n",name);
			return(-1);
		}
		/*
		 * put remote name back into command
		 */
		strcpy(&str[5],name);
		/*
		 * open data connection
		 */
#ifdef	DEBUGOPTION
if(debug&0x04) {
		printf("ftpdo(RETR): before calling ftpport()\r\n");
}
#endif
		ftpdskt = ftpport();
		if(ftpdskt == -1) {
			rtclose(ftpfh);
			ftpfh = NULL;
			return(-1);
		}
		ftpstate = 30;
#ifdef	DEBUGOPTION
if(debug&0x04) {
		printf("ftpdo(RETR): after calling ftpport()\r\n");
}
#endif
	} else
	if(!strncmp(str,"LIST",4) || !strncmp(str,"NLST",4)) {
		/*
		 * data connection
		 */
#ifdef	DEBUGOPTION
if(debug&0x04) {
		printf("ftpdo(LIST/NLST): before calling ftpport()\r\n");
}
#endif
		ftpdskt = ftpport();
		if(ftpdskt == -1)
			return(-1);
		ftpstate = 40;
#ifdef	DEBUGOPTION
if(debug&0x04) {
		printf("ftpdo(LIST/NLST): after calling ftpport()\r\n");
}
#endif
	} else
	if(!strncmp(str,"TYPE",4)) {
		if(toupper(str[5]) == 'I') {
			/*
			 * remember mode
			 */
			ftpfilemode = FIMAGE;
		} else
		if(toupper(str[5]) == 'A') {
			ftpfilemode = FASCII;
		}
	}

	/*
	 * clear command connection
	 */
	dumpcon();
	/*
	 * send command
	 */
	skwrite(ftpcskt,str,strlen(str));
	/*
	 * <CRLF> terminates command
	 */
	skwrite(ftpcskt,"\r\n",2);
	skenque(ftpcskt,1);
#ifdef	DEBUGOPTION
if(debug&0x02) {
	/*
	 * show command sent
	 */
	printf("---> %s\r\n",str);
}
#endif
	/*
	 * get remote response
	 */
#ifdef	DEBUGOPTION
if(debug&0x04) {
	printf("ftpdo(): before ftpreplies() call\r\n");
}
#endif
	i = ftpreplies(&rcode);
#ifdef	DEBUGOPTION
if(debug&(0x04|0x10)) {
	printf("return from ftpreplies()\r\n");
}
#endif
	if((i == NONE) && strncmp(str,"QUIT",4)) {
		/*
		 * unexpected connection drop
		 */
		kb_puts("lost connection\r\n");
		linked = FALSE;
	}
	if(i == ABORT || i == NONE || i == ERROR)  {
		/*
		 * if error, no transfer
		 */
		skclose(ftpdskt);
		ftpdskt = skrelease(ftpdskt,1);
		ftpstate = 0;
		rtclose(ftpfh);
		ftpfh = NULL;
		/*
		 * dump any remaining comments
		 */
		dumpcon();
	}
	return(i);
}

/*
 * ftpreplies
 *
 * get responses to commands to server
 * return TRUE on successful completion,
 * INCOMPLETE if more commands needed for operation,
 * NONE on lost connection, ABORT on an abort, and ERROR on failure
 * PRELIMINARY and TRANSIENT responses wait for completion
 */
int ftpreplies(rcode)
int *rcode;
{
	register int cnt,ev,j;
	int abrtcnt,digit;

#ifdef	DEBUGOPTION
if(debug&0x10) {
	printf("entering ftpreplies()\r\n");
}
#endif
	abrtcnt = 0;
	tfrabtf = 0;
	j = 0;
loop:	while(1) {
		/*
		 * get line from remote host
		 */
		cnt = rgetline();
#ifdef	DEBUGOPTION
if(debug&0x10 && !(debug&0x20)) {
		printf("cnt=rgetline(): =%d\r\n  s=%s\r\n",cnt,s);
}
#endif
		if(cnt == NONE)
			/*
			 * lost connection
			 */
			return(NONE);
		if(cnt == ABORT) {
			/*
			 * user abort
			 */
			if(ftpabort(&abrtcnt) == ABORT)
				return(ABORT);
			goto loop;
		}
		if(!sscanf(s,"%d",rcode))
			/*
			 * continuation line
			 */
			*rcode = -1;
		/*
		 * informative/error message or display on
		 */
		if(verbose || (*rcode == -1) || (*rcode >= 500))
			filter(cnt);

		if((*rcode/100) == 2) {
			/*
			 * wait till transfers complete
			 */
			while(ftpdskt >= 0) {
				ev = checkevent();
#ifdef	DEBUGOPTION
if(debug&0x10) {
				printf("checkevent()=%d\r\n",ev);
}
#endif
				if(ev == NONE) {
					/*
					 * lost connection
					 */
					return(NONE);
				}
				if(ev == ABORT) {
					/*
					 * user abort
					 */
					if(ftpabort(&abrtcnt) == ABORT)
						return(ABORT);
				}
				/*
				 * wait a while
				 */
				suspnd(6);
			}
		}
#ifdef	DEBUGOPTION
if(debug&0x10) {
		printf("*rcode=%d\r\n", *rcode);
}
#endif
		if(s[3] == '-') {
			/*
			 * line with continuations
			 * remember end code
			 */
			j = *rcode;
			continue;
		} else
		if(j) {
			if(*rcode == j) {
				/*
				 * end of continuation
				 */
				j = 0;
			} else {
				continue;
			}
		}
		/*
		 * first digit
		 */
		digit = (int)(*rcode/100);
		if(abrtcnt) {
			switch(digit) {
			case 1:		/* preliminary */
			case 4:		/* transient negative completion */
				continue;
	
			default:
				kb_puts(
				"Server response not understood.\r\n");
				kb_puts("Terminating command\r\n");

			case 2:		/* positive completion */
			case 3:		/* intermediate */
			case 5:		/* Permanent negative completion */
				return(ABORT);

			}
		} else {
			switch(digit) {
			case 1:		/* preliminary */
			case 4:		/* transient negative completion */
				continue;
	
			case 2:		/* positive completion */
				return(TRUE);

			case 3:		/* intermediate */
				return(INCOMPLETE);
		
			default:
				kb_puts(
				"Server response not understood.\r\n");
				kb_puts("Terminating command\r\n");

			case 5:		/* Permanent negative completion */
				return(ERROR);

			}
		}
	}
}

/*
 * ftpabort
 *
 * attempt to abort FTP operation
 * return ABORT on unconditional abort
 */
int ftpabort(i)
int *i;
{
	finished = TRUE;
	switch(ftpstate) {
		default:	/* Invalid states */
		case 0:
			break;

		case 20:	/* finished == TRUE */
		case 21:	/* will terminate transfers */
		case 22:
			break;

		case 30:
		case 31:
		case 40:
		case 41:
			/*
			 * Send the abort as an urgent message
			 */
			sksendwait(ftpcskt,(long) SENDWAIT);
			skwurgent(ftpcskt,1);
			/* IAC IP IAC DM */
			skwrite(ftpcskt,"\377\364\377\362",4);
			skwrite(ftpcskt,"ABOR\r\n",6);
			sksendwait(ftpcskt,(long) SENDWAIT);
			skwurgent(ftpcskt,0);
			break;
	}
	*i += 1;
	if(*i > 2) {
		if(fromfp != NULL) {
			fclose(fromfp);
			fromfp = NULL;
			fromtty = TRUE;
			kb_puts("\r\nCommand File Input Aborted");
		}
		kb_puts("\r\nUnconditonal ABORT\r\n");
		return(ABORT);
	} else {
		kb_puts("\r\nAttempting to ABORT operation ...\r\n");
		return(0);
	}
}

/*
 * rgetline - get a line from remote server
 *
 * return ABORT on an abort, NONE on lost connection,
 * length of received line on success
 */
int rgetline()
{
	register int i,icnt,cnt,ev;

#ifdef	DEBUGOPTION
if(debug&0x20) {
	printf("entering rgetline()\r\n");
}
#endif
	i = 0;
	while(1) {
		ev = checkevent();
#ifdef	DEBUGOPTION
if(debug&0x20) {
		printf("checkevent()=%d\r\n", ev);
}
#endif
		switch(ev) {
		case NONE:	/* lost connection */
			skdeque(ftpcskt);

		case ABORT:	/* abort */
#ifdef	DEBUGOPTION
if(debug&0x20) {
			printf("leaving rgetline()\r\n");
}
#endif
			return(ev);

		case HAVEDATA:
			/*
			 * get some from queue
			 */
			icnt = 0;
			while(1) {
				cnt = skread(ftpcskt,&s[i],1);
				if(cnt == 0)
					/*
					 * nothing available
					 */
					break;
				icnt = 1;
				if((s[i++] == '\n') || (cnt < 0)) {
					/*
					 * end of line
					 */
					s[i] = '\0';
#ifdef	DEBUGOPTION
if(debug&0x20) {
					printf(
			"i=%d, s=%s\r\nleaving rgetline()\r\n", i,s);
}
#endif
					skdeque(ftpcskt);
					/*
					 * return line length
					 */
					return(i);
				}
				if(i > GBUFSIZE - 2)
					i = GBUFSIZE - 2;
			}
			if (icnt)
				skdeque(ftpcskt);
			break;

		default:	/* ignore other events */
			suspnd(6);
			break;
		}
	}
}

/*
 * userftpd
 *
 *  FTP receive and send file functions
 */
VOID userftpd()
{
	register char *p;
	register char *q;
	register int fcnt;
	int ei,ef,i;
	long etime,ri,rf;

#ifdef	DEBUGOPTION
if(debug&0x40) {
	if(ftpstate) {
		printf("userftpd(%d):", ftpstate);
	}
}
#endif
	fcnt = 0;
	switch(ftpstate) {
	default:	/* unknown */
		break;

	case 40:	/* start LIST */
		if(sktest(ftpdskt))
			break;
		/*
		 * Initialize parameters
		 */
		tbytes = 0L;
		/*
		 * connection made
		 */
		start = cticks(NULL);
		ftpstate = 41;

	case 41:	/* get started */
		fcnt = skread(ftpdskt,xs,READSIZE);
		if(fcnt>0) {
			/*
			 * how much
			 */
			tbytes += fcnt;

			skdeque(ftpdskt);
			if(capture == TRUE) {
				if(!fwrite(xs,1,fcnt,capfp)) {
					tfrabtf = 1;
				}
			} else
			if(redocap == TRUE) {
				if(!fwrite(xs,1,fcnt,redfp)) {
					tfrabtf = 1;
				}
			} else {
				xs[fcnt] = '\0';
				kb_puts(xs);
			}
		}
#ifdef	DEBUGOPTION
if(debug&0x40)	{
		printf(" fcnt %d tbytes %ld\r\n",fcnt,tbytes);
}
#endif
		break;

	case 30:	/* receive */
		if(sktest(ftpdskt))
			break;
		/*
		 * Initialize parameters
		 */
		len = 0;
		xp = 0;
		tfrdly = MINTFRDLY;
		hashcnt = 0;
		hashnum = 0;
		tbytes = 0L;
		/*
		 * connection made
		 */
		start = cticks(NULL);
		ftpstate = 31;

	case 31:
		/*
		 * file has already been opened,
		 * take everything from the connection
		 * and place into the open file: ftpfh
		 */
		/*
		 * the data transfer process is a cooperative
		 * task performed by this program and TCPIP.
		 * this test sequence attempts to maximize the
		 * the transfer rate by adjusting the process
		 * suspension time during the data transfer
		 */
		if(skqlen(ftpdskt) < LOWWATER) {
			if(tfrdly < MAXTFRDLY) tfrdly++;
		} else {
			if(tfrdly > MINTFRDLY) --tfrdly;
		}

		/*
		 * wait until xs is full
		 * before writing to disk
		 */
		if(len <= 0) {
			if(xp) {
			    if(ftpfh != NULL)
				if(!fwrite(xs,1,xp,ftpfh)) {
				    tfrabtf = 1;
				}
			    xp = 0;
			}
			/*
			 * expected or desired len to go
			 */
			len = BUFFERS;
		}
		/*
		 * how much to read
		 */
		if(len < BUFFERS) {
			i = len;
		} else {
			i = BUFFERS;
		}
		fcnt = skread(ftpdskt,&xs[xp],i);
#ifdef	DEBUGOPTION
if(debug&0x40) {
		printf(" len %d xp %d fcnt %d", len,xp,fcnt);
}
#endif
		/*
		 * adjust counts
		 */
		if(fcnt>0) {
			len -= fcnt;
			xp += fcnt;
			hashcnt += fcnt;
			tbytes += fcnt;
			skdeque(ftpdskt);
			if(len != 0) {
				suspnd(tfrdly);
			}
			suspnd(-1);
		}
		/*
		 * connection closed
		 */
		if(fcnt<0) {
			/*
			 * write last block
			 */
			if(xp) {
			    if(ftpfh != NULL)
				if(!fwrite(xs,1,xp,ftpfh)) {
				    tfrabtf = 1;
				}
			    xp = 0;
			}
		}
		/*
		 * hash mark printing
		 */
		while(hash && hashcnt >= HASHSIZE) {
			hashcnt -= HASHSIZE;
			if(++hashnum >= 75) {
				kb_puts("#\r\n");
				hashnum = 0;
			} else {
				kb_out('#');
			}
		}
		break;

	case 20:	/* send */
		if(sktest(ftpdskt))
			break;
		/*
		 * Initialize parameters
		 */
		finished = FALSE;
		towrite = 0;
		xp = 0;
		tfrdly = MINTFRDLY;
		hashcnt = 0;
		hashnum = 0;
		tbytes = 0L;
		/*
		 * connection made
		 */
		start = cticks(NULL);
		ftpstate = 21;

	case 21:
		/*
		 * the data transfer process is a cooperative
		 * task performed by this program and TCPIP.
		 * this test sequence attempts to maximize the
		 * the transfer rate by adjusting the process
		 * suspension time during the data transfer
		 */
		if(skqued(ftpdskt)) {
			if(tfrdly<MAXTFRDLY) tfrdly++;
		} else {
			if(tfrdly>MINTFRDLY) --tfrdly;
		}

		/*
		 * transfer file(s) to the
		 * other host via ftp request
		 * file is already open=ftpfh
		 */
		if(towrite <= xp && finished == FALSE) {
			/*
			 * need to read again
			 */
			for(p=xs,i=0; i<BUFFERS; i+=READSIZE) {
			    if((iosize != 0) &&
				(fcnt = fread(p,1,READSIZE,ftpfh))) {
				p += fcnt;
				--iosize;
			    } else {
				finished = TRUE;
				rtclose(ftpfh);
				ftpfh = NULL;
				break;
			    }
			}
			fcnt = p - xs;
			if(ftpfilemode == FASCII) {
			    for (p=q=xs,i=0; i<fcnt; i++) {
				if (!(*p++ = *q++)) {
				    --p;
				}
			    }
			    fcnt = p - xs;
			}
			towrite = fcnt;
			xp = 0;
		}
		fcnt = towrite - xp;
		if(fcnt>0) {
			fcnt = skwrite(ftpdskt,&xs[xp],fcnt);
		}
		if(fcnt>0) {
			/*
			 * write successful
			 * adjust counts and queue data
			 */
			xp += fcnt;
			hashcnt += fcnt;
			tbytes += fcnt;
			skenque(ftpdskt,0);
		}
#ifdef	DEBUGOPTION
if(debug&0x40) {
		printf(" finished %d fcnt %d xp %d towrite %d",
			finished,fcnt,xp,towrite);
}
#endif
		/*
		 * hash printing
		 */
		while(hash && hashcnt >= HASHSIZE) {
			hashcnt -= HASHSIZE;
			if(++hashnum >= 75) {
				kb_puts("#\r\n");
				hashnum = 0;
			} else {
				kb_out('#');
			}
		}
		/*
		 * done if:  the file
		 * is all read from disk
		 * and all sent or other side
		 * has ruined connection
		 */
		if((towrite <= xp && finished == TRUE) || sktest(ftpdskt)) {
			ftpstate = 22;
		} else {
			if(skqued(ftpdskt)) {
				suspnd(tfrdly);
			}
			suspnd(-1);
			break;
		}

	case 22:
		/* send done
		 * wait for other side to accept
		 * everything and then start close
		 */
		if(0 >= (i = skenque(ftpdskt,1)) || sktest(ftpdskt)) {
			fcnt = -1;
		}
#ifdef	DEBUGOPTION
if(debug&0x40) {
		printf(" skenque = %d", i);
}
#endif
		break;
	}
#ifdef	DEBUGOPTION
if(debug&0x40) {
	if(ftpstate) {
		printf("\r\n");
	}
}
#endif
	/*
	 * after processing from connection,
	 * if the connection is closed,
	 * reset up shop.
	 */
	if(fcnt < 0) {
		/*
		 * transfer finished / connection lost
		 */
		if(ftpfh != NULL) {
			/*
			 * close file
			 */
			if(rtclose(ftpfh)) {
				tfrabtf = 1;
			}
			ftpfh = NULL;
		}
		/*
		 * Hash Mark Printing
		 */
		if(hash && hashnum)
			kb_nline();
		/*
		 * check transfer abort flag
		 */
		if(tfrabtf) {
			kb_puts("Write Error, File Truncated\r\n");
		}
		/*
		 * done
		 */
		ftpstate = 0;
		tfrabtf = 0;
		fcnt = 0;
		/*
		 * how long to transfer
		 */
		if(verbose) {
			/*
			 * Time calculation
			 */
			etime = elapsed(start);
			ei = etime/TICKSPERSEC;
			if(!ei) {
				ei = 1;
				ef = 0;
				etime = TICKSPERSEC;
			} else {
			/*	ei = ei;	*/
				ef = (10L*(etime%TICKSPERSEC))/TICKSPERSEC;
			/*	etime = etime;	*/
			}
			/*
			 * Rate calculation
			 */
			if(tbytes > 30000000L) {
				ri = (tbytes/etime)*TICKSPERSEC;
			} else {
				ri  = (tbytes*TICKSPERSEC)/etime;
			}
			rf  = ri%1000L;
			ri /= 1000L;

			kbprintf(
	"Transferred %ld bytes in %d.%d seconds (%ld.%03ld kbytes/sec)\r\n",
				tbytes,ei,ef,ri,rf);
		}
		/*
		 * close connection
		 */
		skclose(ftpdskt);
		if(bell)
			kb_out(7);
	}
	/*
	 * Release socket when closed
	 */
	if(ftpdskt >= 0 && ftpstate == 0) {
#ifdef	DEBUGOPTION
if(debug&0x40) {
		printf("userftpd : ftpdskt = %d, state = %d\r\n",
			ftpdskt,skstate(ftpdskt));
}
#endif
		ftpdskt = skrelease(ftpdskt,0);
	}
}

/*
 * checkevent
 *
 * get and process network events
 * returns ABORT on an abort, HAVEDATA if data available
 * on command connection, NONE if connection lost.
 * TRUE if no relevant event.
 */
int checkevent()
{
	int cl,ev,dat;

	/*
	 * check for abort
	 */
	if(tstbrk())
		return(ABORT);
	/*
	 * do ftp stuff
	 */
	userftpd();
	/*
	 * look for events
	 */
	ev = Sgetevent(CONCLASS | MSGCLASS |
		       ERRCLASS | ABORTCLASS, &cl,&dat);
	if(ev) {
	    switch(cl) {
		case ABORTCLASS:	/* abort */
		    ftpexit(ABORT);
		    break;

		case ERRCLASS:		/* error messages */
		case MSGCLASS:		/* messages */
		    kb_puts(skerrstring(ev));
		    kb_nline();
		    break;

		case CONCLASS:		/* connection */
		    if(dat == ftpcskt) {
			switch(ev) {
			    case CONDATA:
				/*
				 * data has arrived
				 */
				return(HAVEDATA);

			    case CONCLOSE:
				/*
				 * connection lost
				 */
				skclose(ftpdskt);
				skclose(ftpcskt);
				linked = FALSE;
				return(NONE);

			     default:
				break;
			}
			break;
		    }
		    break;

		default:
		    break;
	    }
	}
	return(TRUE);
}

/*
 * getword: remove a word from a string.  Things within quotes are
 * assumed to be one word.
 * return TRUE on success, FALSE on end of string
 */
int getword(string,word)
char *string;
register char *word;
{
	register char *p;
	register int i;
	char *q;

	i = 0;
#ifdef	DEBUGOPTION
if(debug&0x80) {
	printf("getword: string is %s\r\n",string);
}
#endif
	/*
	 * skip leading blanks
	 */
	p = stpblk(string);
	if(!(*p)) {
		/*
		 * no words in string
		 */
		word[0] = '\0';
		return(FALSE);
	}
	if(*p == '!') {
		/*
		 * ! is a word
		 */
		word[0] = *p;
		word[1] = '\0';
		strcpy(string,++p);
		return(TRUE);
	}
	if(*p == '\"') {
		/*
		 * word delimited by quotes
		 */
		while(p[++i] && p[i] != '\"')
			word[i-1] = p[i];
		word[i-1] = '\0';
		if(!p[i]) {
			kb_puts("Missing \". Assumed at end of string.\r\n");
		} else {
			i++;
		}
		q = p+i;
	} else {
		/*
		 * get word, max len 50
		 */
		q = stptok(p, word, 50, " \t\r\n");
	}
     	/*
	 * remove trailing blanks
	 */
	p = stpblk(q);
	/*
	 * remove extracted stuff
	 */
	strcpy(string,p);
	return(TRUE);
}

/*
 * checkoredir: check for output redirection.
 * If the command contains a >, assume a
 * filename follows and extract it.
 * Remove the redirection from the original command.
 * Return TRUE if redirection file is specified and allowed
 * else return FALSE 
 */
int checkoredir(command,filename)
char *command,*filename;
{
	register int i;

	filename[0] = '\0';
	/*
	 * process command part
	 */
	for(i=0;(command[i] != '>'); i++) {
		if(!command[i])
			/*
			 * no redirection
			 */
			return(FALSE);
	}
	/*
	 * get redirected filename
	 */
	getword(&command[i+1],filename);
	command[i] = '\0';
	return(redirect ? TRUE : FALSE);
}

/*
 * finduniq:
 *
 * find name that is a unique prefix of one of the entries in
 * a list.  Return position of the entry,
 * NONE if none, AMBIGUOUS if more than one.
 */
int finduniq(name,list,listsize)
char *name, *list[];
int listsize;
{
	register int i,j,ilen;

	j = NONE;
	ilen = strlen(name);
	strlwr(name);
	for(i=0; i<listsize; i++) {
		/*
		 * prefix
		 */
		if(!strncmp(name,list[i],ilen)) {
			if(ilen == strlen(list[i]))
				/*
				 * exact match
				 */
				return(i+1);
			if(j != NONE) {
				/*
				 * more than one match
				 */
				j = AMBIGUOUS;
			} else {
				/*
				 * note prefix found
				 */
				j = i+1;
			}
	  	}
	}
	/*
	 * prefix
	 */
	return(j);
}

/*
 *  opncap
 *
 *  open the capture file
 */
FILE *
opncap(filspc,mode)
char *filspc,*mode;
{
	FILE *fp;

	if((fp = fopen(filspc, mode)) == NULL) {
	  kbprintf("Command Aborted: File %s failed to open.\r\n\n", filspc);
	}
	return(fp);
}

/*
 *  opnred
 *
 *  open the redirect capture file
 */
FILE *
opnred(filspc)
char *filspc;
{
	FILE *fp;

	/*
	 * open local file
	 */
	fp = rtopen(filspc, "wn",1,1);
	if(fp == NULL) {
	  kbprintf("Command Aborted: File %s failed to open.\r\n\n", filspc);
	}
	if(fp == -1) {
	  fp = NULL;
	}
	return(fp);
}

/*
 * getnname:
 *
 * get next name from captured list
 * names delimited by newlines - <CR> or <LF>
 */
int getnname(word)
register char *word;
{
	register int chr;
	register char *str;

	if(capfp == NULL) {
		return(FALSE);
	}

	/*
	 * skip initial newlines
	 */
	while((chr = getc(capfp)) != EOF) {
		if (chr != '\n' && chr != '\r') {
			break;
		}
	}
	if(chr == EOF || chr == '\0') {
		return(FALSE);
	}

	str = word;
	*(word++) = chr;

	while((chr = getc(capfp)) != EOF) {
		if(chr == '\n' || chr == '\r' || chr == '\0') {
			break;
		} else {
			*(word++) = chr;
		}
	}
	*word = '\0';

#ifdef	DEBUGOPTION
if(debug&0x100) {
	printf("getnname: name is %s\r\n",str);
}
#endif

	if(chr == '\0') {
		ungetc(chr,capfp);
		return(TRUE);
	}
	if(chr == EOF) {
		return(TRUE);
	}


	/*
	 * skip trailing newline
	 */
	while((chr = getc(capfp)) != EOF) {
		if(chr != '\n' && chr != '\r') {
			ungetc(chr,capfp);
			break;
		}
	}

	return(TRUE);
}

VOID strlwr(st)
register char *st;
{
	while(*st) {
		*st = tolower(*st);
		*st++;
	}
}

char *stptok( p, toword, ilen, delim)
register char *p;
char *toword;
int ilen;
char *delim;
{
	register char *adv;
	register int i;
	int j,end;
 
	adv = toword;
	end = 0;
	j = strlen(delim);

 	do { 
 		for(i=0; i<j; i++)
 			if(*p == delim[i] || (!*p))
				end++;
 		if(!end) {
 			if(adv >= (toword+ilen-1)) 
				end++;
 			*adv++=*p++;
 		}
 	} while(!end);
 	*adv='\0';
 	return(p);
}
 
char *stpblk(ch)
register char *ch;
{
 	while(*ch == ' ' || *ch == '\t') ch++;
 	return(ch);
}

/*
 *  chkusr
 *
 *  Check the password file for the user/password
 *  combination. An inaccessable password file
 *  results in an invalid return.
 *
 *  If the user/password are validated and
 *  the user has the rquired privelege,
 *  then a valid return is made.
 *
 *  Returns valid(0)/invalid(1)
 */
int chkusr()
{
	FILE *fp;
	struct userblock user;
	char username[USERPASSLEN+2];
	char password[USERPASSLEN+2];
	char command[100];
	char word[100];

	/*
	 * username
	 */
	if(question(command,100,"Username: "))
		return(1);
	getword(command,word);
	username[0] = '\0';
	strncat(username,word,USERPASSLEN);
	strlwr(username);
	/*
	 * password
	 */
	if(!(*stpblk(command))) {
		kb_puts("Password: ");
		if(ftpgets(command,100,0) == ABORT)
			return(1);
	}
	getword(command,word);
	password[0] = '\0';
	strncat(password,word,USERPASSLEN);
	strlwr(password);
	/*
	 * Check username / password file
	 */
	if(NULL==(fp = rtopen(pass,"rn",0,0))) {
		return(1);
	}
	while (NULL != fread(&user,sizeof(struct userblock),1,fp)) {
		/*
		 * does username / password check ?
		 */
		if(!strncmp(username,&user.username,USERPASSLEN) &&
				(user.userflag[0]&PASSNOTREQUIRED ||
				Scompass(password,&user.password))
			) {
			rtclose(fp);
			/*
			 * Check FTP privileges
			 */
			if(!(user.userflag[1] & LFTP_PRIV)) {
				kb_puts("No Privilege for FTP\r\n");
				return(1);
			}
			/*
			 * User valid
			 */
			return(0);
		}
	}
	kb_puts("Invalid Username / Password.\r\n");
	rtclose(fp);
	return(1);
}

/*
 *  Scompass ( ps, en )
 *
 *  Compute and check the encrypted password
 */
int Scompass(ps,en)
register char *ps,*en;
{
	register int i,ck;
	char *p,c;

	ck = 0;
	p = ps;
	/*
	 * checksum the string
	 */
	while (*p)
		ck += *p++;
	c = ck;
	/*
	 * XOR with checksum
	 */
	i = USERPASSLEN;
	while (i--) {
		if((((*ps ^ c)|32)&127)!=*en)
			return(0);
		/*
		 * increment checksum to hide length
		 */
		if(*ps) {
			ps++;
		} else {
			c++;
		}
		en++;
	}
	return(1);
}

/*
 *  printout text
 */
VOID printtxt(txt)
char **txt;
{
	register char **dp;

	for (dp = txt; *dp; dp++) {
		if (**dp == '_') {
			kb_puts(*dp + 1);
		} else {
			kb_puts(*dp);
			kb_nline();
		}
	}
}
                                                                                                                                                                                                                                                                                                                                                                                                                  