/* rftp.c */

#define	RFTPMASTER

/*
 *  Includes
 */
#include <stdio.h>
#include <string.h>

#include "os.h"
#include "vrsion.h"
#include "vrftp.h"
#include "dfault.h"
#include "evtdef.h"
#include "hstdef.h"
#include "prodef.h"
#include "rcmnd.h"
#include "cliutl.h"
#include "cticks.h"
#include "tsxutl.h"
#include "kbdutl.h"
#include "rtfile.h"
#include "flinfo.h"
#include "usrblk.h"
#include "access.h"
#include "inicli.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	VOID	printtxt();	/* (txt) print text */
extern	int	dosess();	/* rftp spinner */
extern	int	setftp();	/* setup to receive incoming FTP request */
extern	VOID	rftpresp();	/* (s) send response to net */
extern	VOID	rftpd();	/* (code) rftp function decoder */
extern	VOID	ftporsp();	/* open of FTP connection response */
extern	int	ftpopen();	/* (i) open FTP connection to requestor */
extern	int	ftppasv();	/* listen for FTP connection from requestor */
extern	VOID	ftpd();		/* (code,dat) ftp transfer routines */
extern	int	getword();	/* (string,word) extract a word */
extern	int	Scheckpass();	/* (us,ps) password checker */
extern	int	Scompass();	/* (ps,en) password decoder */
extern	VOID	strlwr();	/* (st) convert string to lower case */
#endif

extern	char	*stptok();	/* (p,t,i,d) stop on token */
extern	char	*stpblk();	/* (ch) skip to chars */

/*
 *	Defines
 */

#define	FASCII		0
#define	FIMAGE		1

#define	FTPCLEAR	0
#define	FTPDONE		1
#define	FTPABORT	2
#define	FTPERROR	3

/*
 * READSIZE is the system BLOCK SIZE
 * BUFFERS must be an integral multiple of READSIZE
 */

#define BUFFERS		2048
#define	READSIZE	512
#define	RSPSTRING	200
#define GENBUFR		128

/*
 * Transfer Delay Time Limits
 */

#define	MINTFRDLY	3
#define	MAXTFRDLY	15

/*
 *	Global Variables For RFTP Control Channel
 */

int	pswdreqd = 1;		/* password required */

static int
	aflag = 0,		/* Attach to Service Flag */
	rflag = 0,		/* RFTP restart flag */
	uflag = 0,		/* Unix style DIR disabled */
	rftpcskt = -1,		/* socket for incoming ftp */
	portnum[8],		/* originator information */
	mflag = 0,		/* monitor processing */
	waitpos = 0,		/* marker for gathering strings from net */
	rfstate = 0,		/* state of the control channel */
	retstate = 0,		/* to emulate a subroutine call */
	rcnt = 0;		/* number of characters from control */

int	yflag = 0,		/* login timeout flag */
	zflag = 0;		/* inactivity timeout flag */

long	lzaptime = 0L,		/* login zap time in ticks */
	izaptime = 0L;		/* inactivity zap time in ticks */

/*
 *	Global Variables For FTP Data Channel
 */

static int 
	ftpdskt = -1,		/* socket for ftp data connection */
	ftpstate = 0,		/* state of the ftp data transfer */
	ftpfinished = FTPCLEAR,	/* ftp finished state */
	ftpfilemode = FIMAGE,	/* how to open the file */
	finished = TRUE,	/* file transfer flag */
	lstype = 0,		/* what type of ftp list to do */
	lsmode = 0,		/* '-f' listing mode flag */
	fcnt = 0,		/* transfer counter */
	xp = 0,			/* general array index */
	towrite = 0,		/* file -> net  counter */
	len = 0,		/* net  -> file counter */
	tfrdly = 0,		/* transfer suspend time */
	filcnt = 0,		/* number of files listed */
	filblks = 0;		/* number of data blocks in filcnt files */

unsigned int
	iosize = 0;		/* transfer size in 512 byte blocks */

static FILE
	*ftpfh = NULL;		/* file handle for ftp data */

/*
 *	Global Buffers / Pointers
 */

struct userblock
	user;			/* usernames / passwords / mailboxes */

char	xs[BUFFERS];		/* buffer space for file transfer */

static char
	command[GENBUFR],	/* command string */
	rsp[RSPSTRING],		/* response string */
	pathname[GENBUFR],	/* space to keep path name */
	filspec[GENBUFR];	/* current file filespec */
	scratch[GENBUFR],	/* scratch string */
	rnfrfile[GENBUFR],	/* rename this file */
	myuser[20];		/* user name on my machine */

static char
	*nextfile;		/* pointer to the next filename */

static int exmode = FALSE;	/* extended m___ modes */

static char *			/* password file */
	pass = "PAS:paswrd.fil";
static char *			/* message file */
	mesg = "TCP:rftp.msg";
static char *			/* help file */
	help = "TCP:rftp.hlp";


/*
 * Debugging options
 *
 *	bit 0	(0x01)	debug internal events
 *	bit 1	(0x02)	debug dosess
 *	bit 2	(0x04)	debug rftpd()
 *	bit 3	(0x08)	debug ftpd()
 */

#if ts$sys
static char *usetxt[] = {
       "",
#ifdef	DEBUGOPTION
       "  RFTP [?] [-d level] [-hmp filespec] [-y s] [-z s] [-aerv]",
       "	?               List the Help Text and Exit RFTP",
       "	d  level	Debug Level",
       "		1 -	enable event printing",
       "		2 -	enable dosess() printing",
       "		4 -	enable rftpd() printing",
       "		8 -	enable ftpd() printing",
#else
       "  RFTP [?] [-hmp filespec] [-y s] [-z s] [-aerv]",
       "	?               List the Help Text and Exit RFTP",
#endif
       "	a		Attach to Service",
       "	e		Enable Monitoring of all RFTP Transactions",
       "	h		Specify the Help Filespec",
       "	m		Specify the Message Filespec",
       "	p		Specify the Password Filespec",
       "	r		Restart RFTP after Disconnect",
       "	u		UNIX Style DIR Command",
       "	v		Verbose Mode",
       "	y  s		Login Timeout in seconds",
       "	z  s		Inactivity Timeout in seconds",
       "",
       0
       };
#endif

#if rt$sys
static char *usetxt[] = {
       "",
#ifdef	DEBUGOPTION
       "  RFTP [?] [-d level] [-hmp filespec] [-y s] [-z s] [-erv]",
       "	?               List the Help Text and Exit RFTP",
       "	d  level	Debug Level",
       "		1 -	enable event printing",
       "		2 -	enable dosess() printing",
       "		4 -	enable rftpd() printing",
       "		8 -	enable ftpd() printing",
#else
       "  RFTP [?] [-hmp filespec] [-y s] [-z s] [-erv]",
       "	?               List the Help Text and Exit RFTP",
#endif
       "	e		Enable Monitoring of all RFTP Transactions",
       "	h		Specify the Help Filespec",
       "	m		Specify the Message Filespec",
       "	p		Specify the Password Filespec",
       "	r		Restart RFTP after Disconnect",
       "	u		UNIX Style DIR Command",
       "	v		Verbose Mode",
       "	y  s		Login Timeout in seconds",
       "	z  s		Inactivity Timeout in seconds",
       "",
       0
       };
#endif

/*
 *  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();
		}
	}
}


main(argc,argv)
int argc;
char *argv[];
{
	int ev,i,j,k;
	register char c;
	register struct socket *skt;

	/*
	 * Initialize for TSX+ clients
	 */
	inicli();

	/*
	 * Initialize kb handler
	 * Set the break character to ^C
	 */
	kb_init(0x03);

	/*
	 * Verbose Mode Off
	 */
	kb_prnt = 0;

	/*
	 * parse arguments
	 */
	for(i=1; i<argc; i++) {
		if(argv[i][0] == '?') {
		    kb_prnt = 1;
		    printtxt(usetxt);
		    exit(0);
		} else
		if(argv[i][0] == '-') {
		    j = i;
		    k = 1;
		    while((c = argv[j][k]) != '\0') {
			switch(tolower(c)) {
#if ts$sys
			case 'a':	/* attach to service */
				aflag = 1;
				break;
#endif
			/*
			 * This is a 'hidden' option for
			 * bypassing the password checking.
			 */
			case 'b':	/* bypass password processing */
				pswdreqd = 0;
				break;
#ifdef	DEBUGOPTION
			case 'd':	/* debug, optional level */
				if(sscanf(argv[++i],"%d",&debug) <= 0)
					debug = -1;
				break;
#endif
			case 'e':	/* enable RFTP monitoring */
				mflag = 1;
				break;

			case 'h':	/* help file */
				help = argv[++i];
				break;

			case 'm':	/* message file */
				mesg = argv[++i];
				break;

			case 'p':	/* password file */
				pass = argv[++i];
				break;

			case 'r':	/* enable RFTP restarting */
				rflag = 1;
				break;
#if ts$sys
			/*
			 * This is a 'hidden' option for setting the
			 * the default subdirectory base 'ld' unit
			 * and setting the maximum subdirectory nesting
			 *	note:	subdbase + subdnest <= 8
			 */
			case 's':
				sscanf(argv[++i],"%d",&subdbase);
				sscanf(argv[++i],"%d",&subdnest);
				break;
#endif
			case 'u':	/* enable UNIX Style DIR */
				uflag = 1;
				break;

			case 'v':	/* verbose mode on */
				kb_prnt = 1;
				break;

			case 'y':	/* login timeout flag */
				sscanf(argv[++i],"%d",&yflag);
				lzaptime = 60L * yflag;
				break;

			case 'z':	/* inactivity timeout flag */
				sscanf(argv[++i],"%d",&zflag);
				izaptime = 60L * zflag;
				break;

			default:	/* unknown option */
				printf(
				"Unrecognized option -%c ignored\r\n", c);
				break;
			}
			k++;
		    }
		}
	}

#ifdef	DEBUGOPTION
	if (debug)
		kb_prnt = 1;
#endif

	/*
	 * identify self
	 */
	printtxt(vrstxt);

	/*
	 *  Start listening to the FTP port
	 */
	while(((rftpcskt = setftp()) < 0) && !fndbrk) {
		/*
		 * Could not get a socket, wait 5 seconds
		 */
		suspnd(300);
	}

	while(!fndbrk && ev != -1) {
		ev = dosess();
		if(ev==0)
			suspnd(0);
	}

	if(fndbrk) 
		kb_puts("\r\n^C\r\n");

	skclose(rftpcskt);
	skrelease(rftpcskt,1);
	skclose(ftpdskt);
	skrelease(ftpdskt,1);

	suspnd(0);
	errhandle();

	cd("dk:");

	kb_puts("\r\nEscaping from RFTP\r\n");
	exit(0);
}

/*
 *  dosess
 *
 *  dosess is an infinite loop serving message events
 */
int dosess()
{
	int  cl,ev,dat;

	/*
	 * Check for any relevant messages
	 * that need to be handled by me
	 */
	ev = Sgetevent(SCKTCLASS | CONCLASS | USERCLASS |
			ERRCLASS | MSGCLASS | ABORTCLASS, &cl, &dat);
	if(ev!=0) {

#ifdef	DEBUGOPTION
if(debug&0x02) {
	printf("dosess(): cl = %d, ev = %d, dat = %u\r\n", cl,ev,dat);
}
#endif

	  switch(cl) {
	    case CONCLASS:
	      /*
	       * RFTP Connection Socket
	       */
	      if(sktlist[dat].lpnum == 0) {
		rftpd(ev);
		break;
	      }
	      /*
	       * RFTP Data Socket
	       */
	      if(sktlist[dat].lpnum == 1) {
		ftpd(ev,dat);
		break;
	      }
	      break;

	    case MSGCLASS:	/* messages */
	    case ERRCLASS:	/* error message */
	      kb_puts(skerrstring(ev));
	      kb_nline();
	      break;

	    case ABORTCLASS:	/* abort */
	      switch(ev) {
		case CLIENT:
		  /*
		   * disable automatic restart
		   */
		  rflag = 0;
		  kb_puts("\r\nRFTP session aborted by TCPIP\r\n");
		  Stmrset(ABORTCLASS,EXITABORT,dat,5);

		case USERABORT:
		  if(skrelease(dat,0) < 0) {
		    return(-1);
		  } else {
		    skclose(dat);
		    Stmrset(ABORTCLASS,USERABORT,dat,1);
		  }
		  break;

		case EXITABORT:
		  return(-1);
		  break;

		default:
		  break;
	      }
	      break;

	    default:
	      break;
	  }
	} else {
	  rftpd(0);
	  ftpd(0,0);
	}

	return(ev);
}

/*
 *	set up to receive a telnet connection for ftp commands
 */
int setftp()
{
	int sknum;

	rfstate = 0;		/* state of the control channel */
	retstate = 0;		/* to emulate a subroutine call */
	rcnt = 0;		/* number of characters from control */

	ftpstate = 0;		/* state of the ftp data transfer */
	ftpfilemode = FASCII;	/* how to open the file */
	finished = TRUE;	/* file transfer flag */
	lstype = 0;		/* what type of ftp list to do */
	fcnt = 0;		/* transfer counter */

	/*
	 * Set the default directory
	 */
	cd("dk:");

	/*
	 * Initialize various things
	 */
	Snetinit();

	/*
	 * aflag = 1	indicates attach mode
	 * aflag = 0	indicates listen mode
	 */
	if(aflag) {
		/*
		 * Attach job to connection opened by TCPIP
		 */
		sknum = attach(0,HFTP);
		if(sknum==-1) {
			fndbrk++;
			return(-1);
		} else {
			Stmrset(ABORTCLASS,CLIENT,sknum,CONNWAITTIME);
		}
	} else {
		/*
		 * Create socket for control connection
		 */
		sknum = socket(0);
		if(sknum==-1)
			return(-1);
		/*
		 * default port
		 */
		listen(sknum,HFTP);
	}
	/*
	 * set unknown user name
	 */
	strcpy(myuser,"unknown");

	return(sknum);

}

/*
 * Dump response to client
 */
VOID rftpresp(s)
register char *s;
{
	skwrite(rftpcskt, s, strlen(s));
	skwrite(rftpcskt, "\r\n", 2);
	skenque(rftpcskt, 1);
	if(mflag) {
		kb_puts(s);
		kbprintf("  {%d}\r\n", strlen(s)+2);
	}
}

/*
 * transfer
 *
 * Transfer Message File
 */
transfer(fp,str)
FILE *fp;
char *str;
{
	int fini,i;

	fini = FALSE;
	strcpy(rsp,str);
	i = strlen(str);
	do {
		if(skroom(rftpcskt) > LOWWATER) {
			if(fgetss(rsp+i,RSPSTRING-1-i,fp) != NULL) {
				rftpresp(rsp);
			} else {
				fini = TRUE;
			}
		} else {
			suspnd(6);
		}
	} while(fini != TRUE);
}

/*
 * This is the error print routine for TSXUTL.C
 * It will be called only if there is an internal error
 */
VOID lsterr()
{
	if(*errstr) {
		kb_puts(errstr);
		kb_nline();
		errstr[0] = '\0';
	}
}

/*
 * This routine is a dumby routine for TSXUTL.C
 * It should never be called, but returns a 1 [ABORT]
 */
int rtresp()
{
	kb_puts("rtresp: This dumby routine should never be called");
	return(1);
}

/*
 *	Strip Telnet options
 */
VOID strip(s)
register char *s;
{
	register char *t;
	register int fndIAC;

	fndIAC = 0;
	t = s;

	do {
		if(fndIAC) {
			fndIAC = 0;
		} else
		if((*s & 0xFF) == 0xFF) {
			fndIAC = 1;
		} else {
			*t++ = *s;
		}
	} while(*s++);
}

/*
 *	Remote FTP Server
 */
VOID rftpd(code)
int code;
{
	register struct socket *skt;
	register char *p;
	FILE *fp;
	char c,word[80];
	int i,icnt,rftpcmnd;
	long l;

	if(rftpcskt < 0)
		return;

	switch(rfstate) {
		case 0:
/*S*/		switch(code) {
		case CONOPEN:
			Stmrunset(ABORTCLASS,CLIENT,rftpcskt);

			/*
			 * Enable Timeout
			 */
			cticks(&skquetime);

			/*
			 * get default ftp information
			 */
			skgtftp(rftpcskt,portnum);
			portnum[4] = portnum[6];
			portnum[5] = portnum[7];

			ftpfilemode = FASCII;
			skt = (struct socket *) mapskt(rftpcskt);
			p = skt->tcpout.i.ipsource;

			sprintf(rsp,
	/*Net*/
	"220 Remote FTP Server %s [%d.%d.%d.%d] Ready",
		skt->hostname,
		*(p+0)&0xFF, *(p+1)&0xFF, *(p+2)&0xFF, *(p+3)&0xFF
	/*Net*/
				);
			rftpresp(rsp);

			/*
			 * check pass
			 */
			if(pswdreqd) {
				retstate = 1;
			} else {
				retstate = 2;
			}

			rfstate = 50;
			waitpos = 0; 
			break;

		case CONCLOSE:
			kb_puts("Connection Closed by Remote Client\r\n");
			rcnt = -1;
			break;

		case CONFAIL:
			kb_puts("Connection Failed\r\n");
			rcnt = -1;
			break;

		default:
			break;
/*S*/		}
			break;

		case 1:
			/*
			 * check for passwords
			 */
			rfstate = 50;  
			retstate = 1;
			waitpos = 0;  
			if(getword(command,word) == FALSE)
				break;

#ifdef	DEBUGOPTION
if(debug&0x04) {
	printf("rftpd(%d): %s %s\r\n", code,word,command);
}
#endif

			strlwr(command);
			rftpcmnd = rcmnd(word);
			switch(rftpcmnd) {
			case F_USER:
				/*
				 * keep user name
				 */
				strncpy(myuser,command,19);
				sprintf(rsp,
	/*Net*/
	"331 Password required for user %s", myuser
	/*Net*/
					);
				rftpresp(rsp);
				/*
				 * wait for password
				 */
				break;

			case F_PASS:
				if(Scheckpass(myuser,command)) {
	/*
	 * Version
	 */
					sprintf(rsp,
	/*Net*/
	"230-%s%s", vrstxt[1]+1, vrstxt[2]
	/*Net*/
					);
					rftpresp(rsp);
	/*
	 * Initiator
	 */
					sprintf(rsp,
	/*Net*/
	"230-Initiated from host: %d.%d.%d.%d",
		portnum[0], portnum[1],	portnum[2], portnum[3]
	/*Net*/
					);
					rftpresp(rsp);
	/*
	 * General FTP Message File
	 */
	if((fp = rtopen(mesg,"r",0,0)) != NULL) {
		transfer(fp,"230-");
		rtclose(fp);
	}
	/*
	 * User Specific Message File
	 */
	if(user.msgfil.path[0] != '\0' &&
		(fp = rtopen(&user.msgfil.path,"r",0,0)) != NULL) {
		sprintf(rsp,
	/*Net*/
	"230-Message to User %s", myuser
	/*Net*/
			);
		rftpresp(rsp);
		transfer(fp,"230-");
		rtclose(fp);
	}
					sprintf(rsp,
	/*Net*/
	"230 User %s logged in [password verified]", myuser
	/*Net*/
						);
					/*
					 * Log Session
					 */
					sprintf(scratch,
	/*Log*/
	"RFTP: %s %s", myuser,command
	/*Log*/
						);
					logsession(rftpcskt,scratch);
					retstate = 2;
				} else {
					sprintf(rsp,
	/*Net*/
	"531 User %s login failed [password not verified]", myuser
	/*Net*/
						);
				}
				rftpresp(rsp);
				break;

			case F_QUIT:
				rftpresp(
	/*Net*/
	"221 Goodbye"
	/*Net*/
					);
				rcnt = -1;
				break;

			default:
				rftpresp(
	/*Net*/
	"530 USER and PASS required to activate me"
	/*Net*/
					);
				break;
			}
			break;				
				
		case 2:
			/*
			 * interpret commands that are
			 * received from the other side
			 * set to a safe state to handle recursion
			 * wait for another command line from client
			 */
			rfstate = 50; 
			retstate = 2;
			waitpos = 0;
			if(getword(command,word) == FALSE)
				break;

#ifdef	DEBUGOPTION
if(debug&0x04) {
	printf("rftpd(%d): %s %s\r\n", code,word,command);
}
#endif

			strlwr(command);
			c = toupper(command[0]);
			rftpcmnd = rcmnd(word);
			switch(rftpcmnd) {
			case F_LIST:
			case F_NLST:			
				if(rftpcmnd==F_LIST) {
					lstype = 1;
				} else {
					lstype = 0;
				}
				/*
				 * If Special Format Requested
				 * Then go to UNIX Style
				 */
				if(streq(command,"-f")) {
					command[0] = '\0';
					lsmode = 1;
				} else {
					lsmode = 0;
				}
				if((command[0]=='\0') || streq(command,".")) {
					strcpy(command,"*.*");
				}
				/*
				 * set extended mode
				 */
				if(strchr(command,':')!=NULL ||
					strchr(command,'\\')!=NULL ||
					strchr(command,'/')!=NULL ||
					(command[0]=='.'&&command[1]=='.')) {
					exmode = TRUE;
				} else {
					exmode = FALSE;
				}
				/*
				 * find first name
				 */
				if(!faccess(command,LIST_PRIV)) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if((p = setpath(command)) == NULL) {
					sprintf(rsp,
	/*Net*/
	"522 %s", errstr
	/*Net*/
						);
				} else
				if((nextfile = filnam(p,NULL,1)) == NULL ) {
					sprintf(rsp,
	/*Net*/
	"520 %s", errstr
	/*Net*/
						);
				} else {
					/*
					 * save filespec
					 */
					strncpy(filspec,p,15);
					/*
					 * open the connection
					 */
					if(ftpopen(0) >= 0) {
						/*
						 * ready for data
						 */
						filcnt = 0;
						filblks = 0;
						ftpstate = 40;
					} else {
						lastname();
					}
				}
				if(errstr[0] != '\0') {
					errstr[0] = '\0';
					rftpresp(rsp);
				}
				break;

			case F_CDUP:
			case F_XCUP:
				strcpy(command,"..");

			case F_CWD:
			case F_XCWD:
				getpath(pathname);
				if(!daccess(command,CWD_PRIV)) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if(cd(command)) {
					sprintf(rsp,
	/*Net*/
	"522 %s", errstr
	/*Net*/
						);
					cd(pathname);
				} else {
					/*
					 * success
					 */
					sprintf(rsp,
	/*Net*/
	"250 \"/%s\" is Directory.", getpath(pathname)
	/*Net*/
						);
				}
				errstr[0] = '\0';
				rftpresp(rsp);
				break;

			case F_MKD:
			case F_XMKD:
				if(!faccess(command,MKD_PRIV)) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if((p = setpath(command)) == NULL) {
					sprintf(rsp,
	/*Net*/
	"522 %s", errstr
	/*Net*/
						);
				} else
				if(mkdir(p)) {
					sprintf(rsp,
	/*Net*/
	"523 %s", errstr
	/*Net*/
						);
				} else {
					/*
					 * created directory
					 */
					sprintf(rsp,
	/* Net*/
	"257 Directory \"/%s\" is created", expand(pathname,command)
	/*Net*/
						);
				}
				errstr[0] = '\0';
				rftpresp(rsp);
				respath();
				break;

			case F_RMD:
			case F_XRMD:
				if(!faccess(command,RMD_PRIV)) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if((p = setpath(command)) == NULL) {
					sprintf(rsp,
	/*Net*/
	"522 %s", errstr
	/*Net*/
						);
				} else
				if(delfil(1,p,0,0)) {
					sprintf(rsp,
	/*Net*/
	"523 %s", errstr
	/*Net*/
						);
				} else {
					/*
					 * deleted directory
					 */
					sprintf(rsp,
	/*Net*/
	"224 Directory /%s is deleted", expand(pathname,p)
	/*Net*/
						);
				}
				errstr[0] = '\0';
				rftpresp(rsp);
				respath();
				break;

			case F_DELE:
				if(!faccess(command,DELE_PRIV)) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if((p = setpath(command)) == NULL) {
					sprintf(rsp,
	/*Net*/
	"522 %s", errstr
	/*Net*/
						);
				} else
				if(delfil(0,p,0,0)) {
					sprintf(rsp,
	/*Net*/
	"523 %s", errstr
	/*Net*/
						);
				} else {
					/*
					 * deleted file
					 */
					sprintf(rsp,
	/* Net*/
	"224 File %s is deleted", expand(pathname,p)
	/*Net*/
						);
				}
				errstr[0] = '\0';
				rftpresp(rsp);
				respath();
				break;

			case F_STOR:
				ftpstate = 0;
				/*
				 * First, donot allow a write file
				 * command to delete an existing
				 * file if the user does not have
				 * access to the file and DELE_PRIV.
				 */
				if(!(i=faccess(command,STOR_PRIV))) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if((ftpfh=rtopen(command,"rn",0,0))!=NULL) {
					rtclose(ftpfh);
					if((ac_priv&DELE_PRIV)!=DELE_PRIV) {
						i = 0;
						sprintf(rsp,
	/* Net*/
	"522 File %s exists, no delete privilege", command
	/*Net*/
							);
					}
				}
				/*
				 * Allow files larger than TSX default by
				 * setting first character of iotype = x
				 */
				if(i &&
				  (ftpfh=rtopen(command,"xwn",0,0))==NULL) {
					i = 0;
					sprintf(rsp,
	/*Net*/
	"523 Cannot open file %s for write", command
	/*Net*/
						);
				}
				if(!i) {
					errstr[0] ='\0';
					rftpresp(rsp);
				} else {
					strncpy(filspec,command,GENBUFR-1);
					/*
					 * open the connection
					 */
					if(ftpopen(1) >= 0) {
						/*
						 * ready for data
						 */
						ftpstate = 30;
					} else {
						rtclose(ftpfh);
					}
				}
				break;

			case F_RETR:
			case F_SIZE:
				ftpstate = 0;
				if(!faccess(command,RETR_PRIV)) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
					errstr[0] ='\0';
					rftpresp(rsp);
				} else
				if((ftpfh=rtopen(command,"rn",0,0))==NULL) {
					sprintf(rsp,
	/*Net*/
	"520 File %s not found", command
	/*Net*/
						);
					rftpresp(rsp);
				} else
				if(rftpcmnd==F_RETR) {
					strncpy(filspec,command,GENBUFR-1);
					/*
					 * open connection
					 */
					if(ftpopen(1) >= 0) {
						/*
						 * ready for data
						 */
						iosize = ftpfh->io_size;
						ftpstate = 20;
					} else {
						rtclose(ftpfh);
					}
				} else {
					/*
					 * This code replaces
					 * ((unsigned) ftpfh->io_size) * 512L
					 * DECUS C compiler 'int * long'
					 * always treats the int as signed !!!
					 */
					l = (unsigned int) ftpfh->io_size;
					l *= 512L;
					sprintf(rsp,
	/*Net*/
	"213 %ld", l
	/*Net*/
						);
					rftpresp(rsp);
					rtclose(ftpfh);
				}
				break;

			case F_TYPE:
				if(c == 'I') {
					ftpfilemode = FIMAGE;
					rftpresp(
	/*Net*/
	"200 Type set to I, binary transfer mode"
	/*Net*/
						);
				} else 
				if(c == 'A') {
					ftpfilemode = FASCII;
					rftpresp(
	/*Net*/
	"200 Type set to A, ASCII transfer mode"
	/*Net*/
						);
				} else {
					sprintf(rsp,
	/*Net*/
	"504 Type parameter '%c' not accepted, not implemented", c
	/*Net*/
						);
					rftpresp(rsp);
				}
				break;

			case F_PASV:
				/*
				 * Passive Open
				 */
				ftpdskt = ftppasv(ftpdskt);
				break;

			case F_PORT:
				/*
				 * get the requested port number
				 * from the command given
				 */
				sscanf(command,"%d,%d,%d,%d,%d,%d",
					&portnum[0],&portnum[1],
					&portnum[2],&portnum[3],
					&portnum[4],&portnum[5]);
				sprintf(rsp,
	/*Net*/
	"200 Port command successful [%d.%d.%d.%d   %d,%d -> %u]",
		portnum[0], portnum[1], portnum[2], portnum[3],
		portnum[4], portnum[5],
		(portnum[4]<<8) + portnum[5]
	/*Net*/
					);
				rftpresp(rsp);
				break;

			case F_QUIT:
				rftpresp(
	/*Net*/
	"221 Goodbye"
	/*Net*/
					);
				rfstate = 60;
				break;

			case F_PWD:
			case F_XPWD:
				/*
				 * get directory
				 */
				getpath(pathname);
				sprintf(rsp,
	/*Net*/
	"257 \"/%s\" is the current directory", pathname
	/*Net*/
					);
				rftpresp(rsp);
				break;

			case F_USER:
				/*
				 * keep user name
				 */
				strncpy(myuser,command,19);
				/*
				 * check pass
				 */
				if(pswdreqd) {
					sprintf(rsp,
	/*Net*/
	"331 Password required for user %s", myuser
	/*Net*/
						);
					cd("dk:");
					retstate = 1;
				} else {
					/*
					 * confirm log in without password
					 */
					sprintf(rsp,
	/*Net*/
	"230 User %s logged in [without password]", myuser
	/*Net*/
						);
				}
				rftpresp(rsp);
				break;

			case F_STRU:
				/*
				 * only one STRU allowed
				 */
				if(c == 'F') {
					rftpresp(
	/*Net*/
	"200 Stru F, file structure"
	/*Net*/
						);
				} else {
					sprintf(rsp,
	/*Net*/
	"504 STRU parameter '%c' not accepted, not implemented", c
	/*Net*/
						);
					rftpresp(rsp);
				}
				break;

			case F_MODE:
				/*
				 * only one MODE allowed
				 */
				if(c == 'S') {
					rftpresp(
	/*Net*/
	"200 Mode S, stream mode"
	/*Net*/
						);
				} else {
					sprintf(rsp,
	/*Net*/
	"504 MODE parameter '%c' not accepted, not implemented", c
	/*Net*/
						);
					rftpresp(rsp);
				}
				break;

			case F_PCLR:
				if(!faccess(command,PCLR_PRIV)) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if((p = setpath(command)) == NULL) {
					sprintf(rsp,
	/*Net*/
	"522 %s", errstr
	/*Net*/
						);
				} else {
					/*
					 * unprotect file
					 */
					setclrp(p,0,0);
					sprintf(rsp,
	/* Net*/
	"259 File %s protection flag is clear", expand(pathname,p)
	/*Net*/
						);
				}
				errstr[0] = '\0';
				rftpresp(rsp);
				respath();
				break;

			case F_PSET:
				if(!faccess(command,PSET_PRIV)) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if((p = setpath(command)) == NULL) {
					sprintf(rsp,
	/*Net*/
	"522 %s", errstr
	/*Net*/
						);
				} else {
					/*
					 * protect file
					 */
					setclrp(p,1,0);
					sprintf(rsp,
	/* Net*/
	"259 File %s protection flag is set", expand(pathname,p)
	/*Net*/
						);
				}
				errstr[0] = '\0';
				rftpresp(rsp);
				respath();
				break;

			case F_RNFR:
				rnfrfile[0] = '\0';
				if(!faccess(command,RNFR_PRIV)) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if((ftpfh=rtopen(command,"rn",0,0))==NULL) {
					sprintf(rsp,
	/*Net*/
	"520 File %s not found", command
	/*Net*/
					);
				} else {
					/*
					 * Save file name
					 */
					strncpy(rnfrfile,command,GENBUFR-1);
					rtclose(ftpfh);
					sprintf(rsp,
	/*Net*/
	"350 File %s exists, need new name", command
	/*Net*/
						);
				}
				errstr[0] = '\0';
				rftpresp(rsp);
				break;

			case F_RNTO:
				/*
				 * Require a RNFR file
				 */
				if(rnfrfile[0] == '\0') {
					i = 0;
					sprintf(rsp,
	/*Net*/
	"550 Require RNFR command"
	/*Net*/
						);

				} else
				if((p = setpath(rnfrfile)) == NULL) {
					i = 0;
					sprintf(rsp,
	/*Net*/
	"522 %s", errstr
	/*Net*/
						);
				} else
				/*
				 * Second, donot allow a rename file
				 * command to delete an existing
				 * file if the user does not have
				 * access to the file and DELE_PRIV.
				 */
				if(!(i=faccess(rmtfile(command),RNTO_PRIV))) {
					sprintf(rsp,
	/*Net*/
	"521 %s", errstr
	/*Net*/
						);
				} else
				if(
		(ftpfh=fopen(rtfile(rmtfile(command),getdef()),"rn")) != NULL
					) {
					fclose(ftpfh);
					if((ac_priv&DELE_PRIV) != DELE_PRIV) {
						i = 0;
						sprintf(rsp,
	/* Net*/
	"526 RNTO file %s exists, no delete privilege", rmtfile(command)
	/*Net*/
							);
					}
				}
				if(i) {
					if(rename(p,command,0,0)) {
						/*
						 * failed
						 */
						sprintf(rsp,
	/*Net*/
	"550 %s", errstr
	/*Net*/
							);
					} else {
						/*
						 * rename file
						 */
						sprintf(rsp,
	/* Net*/
	"250-Renamed %s", expand(pathname,p)
	/*Net*/
							);
						rftpresp(rsp);
						sprintf(rsp,
	/* Net*/
	"250 To      %s", expand(pathname,rmtfile(command))
	/*Net*/
						);
					}
				}
				errstr[0] = '\0';
				rnfrfile[0] = '\0';
				rftpresp(rsp);
				respath();
				break;

			case F_ABOR:
				ftpfinished = FTPABORT;
				if(ftpdskt == -1) {
					rftpresp(
	/*Net*/
	"252 ABOR Command Done"
	/*Net*/
						);
				} else {
					rftpresp(
	/*Net*/
	"426 Transfer aborting. Data connection closing"
	/*Net*/
						);
				}
				break;

			case F_ACCT:
			case F_ALLO:
				rftpresp(
	/*Net*/
	"202 Allocate and Account not required for this server"
	/*Net*/
					);
				break;

			case F_HELP:
	/*Net*/
rftpresp("214-Begin Help");
rftpresp("214-");
rftpresp("214-FTP-11 server, commands:");
rftpresp("214-ABOR    ACCT    ALLO    APPE*   CWD     CDUP");
rftpresp("214-DELE    HELP    LIST    MKD     MODE    NLST");
rftpresp("214-NOOP    PASS    PASV    PCLR    PORT    PSET");
rftpresp("214-PWD     QUIT    REIN*   REST*   RETR    RMD");
rftpresp("214-RNFR    RNTO    SITE*   SIZE    SMNT*   STAT");
rftpresp("214-STOR    STOU*   STRU    SYST    TYPE    USER");
rftpresp("214-XCWD    XCUP    XMKD    XPWD    XRMD");
rftpresp("214-[*] - unsupported commands");
rftpresp("214-");
	for(i=0; filtxt[i]!=0; i++) {
		sprintf(rsp,"214-%s", filtxt[i]);
		rftpresp(rsp);
	}
	if((fp = rtopen(help,"r",0,0)) != NULL) {
		transfer(fp,"214-");
		rtclose(fp);
	}
rftpresp("214 End Help");
	/*Net*/
				break;

			case F_STAT:
				skt = (struct socket *) mapskt(rftpcskt);
				p = skt->tcpout.i.ipsource;
	/*Net*/
sprintf(rsp,"211-Begin Stat");
rftpresp(rsp);
sprintf(rsp,"211-%s%s", vrstxt[1]+1, vrstxt[2]);
rftpresp(rsp);
sprintf(rsp,"211-Remote FTP Server %s [%d.%d.%d.%d]", skt->hostname,
		*(p+0)&0xFF, *(p+1)&0xFF, *(p+2)&0xFF, *(p+3)&0xFF);
rftpresp(rsp);
sprintf(rsp,"211-Initiated from host: %d.%d.%d.%d",
		portnum[0], portnum[1],	portnum[2], portnum[3]);
rftpresp(rsp);
sprintf(rsp,"211-transfer type = %s, structure = file, mode = stream",
			ftpfilemode ? "binary" : "ascii");
rftpresp(rsp);
rftpresp(   "211 End Stat");
	/*Net*/
				break;

			case F_SYST:
				rftpresp(
	/*Net*/
#if ts$sys
	"215 TSX-Plus (PDP/LSI-11)"
#endif
#if rt$sys
	"215 RT-11 (PDP/LSI-11)"
#endif
	/*Net*/
					);
				break;

			case F_NOOP:
				rftpresp(
	/*Net*/
	"200 Okay"
	/*Net*/
					);
				break;

			default:
				if(rftpcmnd != F_NULL) {
					/*
					 * unimplemented commands
					 */
					sprintf(rsp,
	/*Net*/
	"502 Command '%s' not implemented", r_clist[rftpcmnd]
	/*Net*/
						);
				} else {
					/*
					 * command not understood
					 */
					sprintf(rsp,
	/*Net*/
	"500 Command '%s %s' not understood", word,command
	/*Net*/
						);
				}
				rftpresp(rsp);
				break;
			}
			break;

		case 50:
			/*
			 * subroutine to wait
			 * for a particular character
			 */
			icnt = 0;
			while(0<(rcnt=skread(rftpcskt,&command[waitpos],1))) {
				icnt = 1;
				if(command[waitpos] == '\n') {
					rfstate = retstate;
					/*
					 * find end of string
					 */
					while (	(command[waitpos] < 33) &&
						(waitpos >= 0) )
						waitpos--;
					/*
					 * put in terminator
					 */
					command[++waitpos] = '\0';
					/*
					 * strip Telnet options
					 */
					strip(command);
					/*
					 * want upper case
					 */
					for(i=0; i<4; i++)
					    command[i] = toupper(command[i]);
#ifdef	DEBUGOPTION
if(debug&0x04) {
	printf("rftpd(%d): command = '%s'\r\n", code,command);
}
#endif
					break;
				} else {
					if(waitpos < GENBUFR-1)
						waitpos += rcnt;
				}
			}
			if (icnt) {
				skdeque(rftpcskt);
				suspnd(-1);
			}
			break;

		case 60:
			/*
			 * wait for message to get through
			 * or connection is broken
			 */
			if(0 >= skenque(rftpcskt,1) || sktest(rftpcskt)) {
				rcnt = -1;
			}
			break;

		default:
			break;
	}

	if(retstate == 1) {
		/*
		 * Process Login Timeout
		 */
		if((yflag != 0) &&
		   (skquetime != 0L) &&
		   (elapsed(skquetime) > lzaptime)) {
			rftpresp(
	/*Net*/
	"221 Login Timeout"
	/*Net*/
				);
			rcnt = -1;
		}
	}
	if(retstate >= 2) {
		/*
		 * Process Inactivity Timeout
		 */
		if((zflag != 0) &&
		   (skquetime != 0L) &&
		   (elapsed(skquetime) > izaptime)) {
			rftpresp(
	/*Net*/
	"221 Inactivity Timeout"
	/*Net*/
				);
			rcnt = -1;
		}
	}

	if(rcnt < 0) {

#ifdef	DEBUGOPTION
if(debug&0x04) {
	printf("rftpd(%d): rfstate = %d, rcnt = %d\r\n",
		code,rfstate,rcnt);
}
#endif
		ftpfinished = FTPCLEAR;
		clrftp(ftpfinished);
		if(rftpcskt >= 0) {
			sksendwait(rftpcskt,(long) SENDWAIT);
			skclose(rftpcskt);
			rftpcskt = skrelease(rftpcskt,1);
		}
		if(rflag && !aflag) {
			/*
			 *  Start listening to the FTP port
			 */
			while((rftpcskt = setftp()) < 0 && !fndbrk) {
				/*
				 * Could not get a socket, wait 5 seconds
				 */
				suspnd(300);
			}
		} else {
			fndbrk = 1;
		}
	}
}

/*
 *	ftporsp
 *
 *	Open of FTP Data Connection Status
 */
VOID ftporsp(i)
int i;
{
	long l;

	if(i) {
		sprintf(rsp,
	/*Net*/
	"150 Opening %s data connection for %s",
		(ftpfilemode==FASCII ? "an ASCII" : "a BINARY"), filspec
	/*Net*/
			);
		if(ftpfh->_flag & _IOREAD) {
				/*
				 * This code replaces
				 * ((unsigned int) ftpfh->io_size) * 512L
				 * DECUS C compiler 'int * long'
				 * always treats the int as signed !!!
				 */
				l = (unsigned int) ftpfh->io_size;
				l *= 512L;
			sprintf(rsp+strlen(rsp),
	/*Net*/
	"  (%ld bytes)", l
	/*Net*/
				);
		}
	} else {
		sprintf(rsp,
	/*Net*/
	"150 Opening an ASCII data connection for a file list"
	/*Net*/
			);
	}
	rftpresp(rsp);
}

/*
 *	ftpopen
 *
 *	Open of FTP Data Connection
 */
int ftpopen(i)
int i;
{
	char st[24];

	/*
	 * Only Status with Passive Open
	 */
	if(ftpdskt >= 0) {
		ftporsp(i);
		return(ftpdskt);
	}

	/*
	 * Clear ftpfinished
	 */
	ftpfinished = FTPCLEAR;
	/*
	 * Request a socket
	 */
	ftpdskt = socket(1);
	if(ftpdskt >= 0 ) {
		ftporsp(i);
	} else {
		rftpresp(
	/*Net*/
	"599 RFTP Socket allocation error, retry operation"
	/*Net*/
			);
		return(-1);
	}

	/*
	 * wait for control data to be sent
	 */
	if(sksendwait(rftpcskt,(long) SENDWAIT)) {
		rfstate = 0;
		rcnt = -1;
		return(-1);
	}

	/*
	 * Force the from port
	 */
	skfromport(ftpdskt,HFTP-1);

	/*
	 * Connecting to
	 */
	sprintf(st,"%d.%d.%d.%d #%u",
		portnum[0], portnum[1],
		portnum[2], portnum[3],
		(portnum[4]<<8)  +  portnum[5]);

	connect(ftpdskt,st);

	/*
	 * reset to default port
	 */
	portnum[4] = portnum[6];
	portnum[5] = portnum[7];

	return(ftpdskt);
}

/*
 *	ftppasv
 *
 *	Passive Open of FTP Data Connection
 */
int ftppasv(sknum)
int sknum;
{
	register struct socket *skt;
	register char *p;
	register int port;

	/*
	 * Clear ftpfinished
	 */
	ftpfinished = FTPCLEAR;
	if(sknum >= 0) {
		skclose(sknum);
		skrelease(sknum,1);
	}
	/*
	 * Request a Socket and
	 * Setup Listen Connection
	 */
	if( (sknum=socket(1)) >= 0 && (port=listen(sknum,0)) != -1 ) {
		skt = (struct socket *) mapskt(sknum);
		p = skt->tcpout.i.ipsource;
		sprintf(rsp,
	/*Net*/
	"227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
		*(p+0)&0xFF, *(p+1)&0xFF, *(p+2)&0xFF, *(p+3)&0xFF,
		(port>>8)&0xFF, port&0xFF
	/*Net*/
			);
		rftpresp(rsp);
	} else {
		rftpresp(
	/*Net*/
	"599 RFTP Socket allocation error, retry operation"
	/*Net*/
			);
		sknum = -1;
	}

	/*
	 * wait for control data to be sent
	 */
	if(sksendwait(rftpcskt,(long) SENDWAIT)) {
		rfstate = 0;
		rcnt = -1;
		sknum = -1;
	}

	return(sknum);
}

/*
 *	FTP receive and send file functions
 */
VOID ftpd(code,dat)
int code;
{
	register char *p;
	register char *q;
	int i;
	long l;
	char ps[96];

	if(ftpdskt < 0)
		return;

#ifdef	DEBUGOPTION
if(debug&0x08) {
	printf("ftpd(%d,%d): ftpstate = %d\r\n", code,dat,ftpstate);
}
#endif

	switch(ftpstate){
		default:
			break;

		/*
		 * list file names in current dir
		 */
		case 40:
			if(code==CONFAIL || code==CONCLOSE) {
				/*
				 * something went wrong
				 */
				fcnt = -1;
				break;
			}
			if(code!=CONOPEN) {
				/*
				 * waiting for connection to open
				 */
				break;
			}
			ftpstate = 41;

		case 41:
			if(nextfile==NULL) {
				/*
				 * waiting for file name
				 */
				break;
			}
			ftpstate = 42;

			/*
			 * send the "nextfile" string
			 * and then see if there is
			 * another file name to send
			 */
		case 42:
			/*
			 * Check for aborted transfers
			 */
			if(ftpfinished) {
				ftpstate = 10;
				break;
			}
			if(skroom(ftpdskt) < LOWWATER)
				break;
			xp = 0;
			while(nextfile!=NULL && xp<READSIZE) {

			if(lstype) {
			    if(filcnt) {
				rsp[0] = '\0';
			    } else {
				strcpy(rsp,"\r\n");
			    }
			    if(uflag == 0) {
				/*
				 * RT-11 STYLE
				 */
				for(i=0; nextfile!=NULL && i<2; i++) {
					rtparse(nextfile,"");
					flinfo(filiop());
					if(i)
						strcat(rsp,"      ");
					sprintf(scratch,
				"%-6.6s.%-3.3s %6.6u%s %02.2u-%3.3s-%04.4u",
						rtname(),
						rtext(),
						flsize,
						flprot,
						flday,
						flmnth,
						flyear);
					strcat(rsp,scratch);
					filblks += flsize;
					filcnt++;
					nextfile=filnam(nextfile,nextfile,1);
				}
				strcat(rsp,"\r\n");

				if(nextfile == NULL) {
					p = rtwfile(filspec,getdef());
					sprintf(scratch,
			"\r\nDirectory [%s] / %d Files / %u Blocks\r\n\r\n",
					expand(ps,p), filcnt, filblks);
					strcat(rsp,scratch);
				}
			    } else {
				/*
				 * UNIX STYLE
				 */
				rtparse(nextfile,"");
				flinfo(filiop());
				if(streq(rtext(),"dsk")) {
					strcat(rsp,"d");
				} else {
					strcat(rsp,"-");
				}
				strcat(rsp,"rwxrwxrwx   1 system  user");
				/*
				 * This code replaces
				 * ((unsigned int) flsize) * 512L
				 * DECUS C compiler 'int * long' always
				 * treats the int as signed !!!
				 */
				l = (unsigned int) flsize;
				l *= 512L;
				sprintf(scratch,
				  "%10.10ld  %3.3s %2.2d %04.4d  %s.%s\r\n",
					l,
					flmnth,
					flday,
					flyear,
					rtname(),
					rtext());
				strcat(rsp,scratch);
				filcnt++;
				nextfile=filnam(nextfile,nextfile,1);
				if(nextfile == NULL)
					strcat(rsp,"\r\n");
			    }
			} else {
				strcpy(rsp, exmode ?
					expand(ps,lclfile(nextfile)) :
					rmtfile(nextfile));
				if(lsmode) {
					rtparse(nextfile);
					if(streq(rtext(),"dsk")) {
						strcat(rsp,"/");
					}
				}
				strcat(rsp, "\r\n");
				nextfile = filnam(nextfile,nextfile,1);
			}
			i = strlen(rsp);
			skwrite(ftpdskt,rsp,i);
			xp += i;

			}
			skenque(ftpdskt,1);
			/*
			 * normal end
			 */
			if(nextfile == NULL) {
				/*
				 * push data through
				 */
				ftpfinished = FTPDONE;
				ftpstate = 10;
			}
			suspnd(-1);
			break;
			
		/*
		 * Incoming File Transfer
		 */
		case 30:
			if(code==CONFAIL || code==CONCLOSE) {
				/*
				 * something went wrong
				 */
				fcnt = -1;
			}
			if(code!=CONOPEN) {
				/*
				 * waiting for connection to open
				 */
				break;
			}
			ftpstate = 31;

		case 31:
			if(ftpfh==NULL) {
				/*
				 * waiting for file to open
				 */
				break;
			}
			ftpstate = 32;

			/*
			 * Initialize parameters
			 */
			len = 0;
			xp = 0;
			tfrdly = MINTFRDLY;

		case 32:
			/*
			 * 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)) {
					ftpfinished = FTPERROR;
					fcnt = -1;
					break;
				    }
				}
				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);
			/*
			 * adjust counts
			 */
			if(fcnt > 0) {
				/*
				 * read successful
				 * adjust counts and dequeue data
				 */
				len -= fcnt;
				xp += fcnt;
				skdeque(ftpdskt);
				if(len != 0) {
					suspnd(tfrdly);
				}
				suspnd(-1);
			}
			/*
			 * connection closed
			 */
			if(fcnt < 0) {
				/*
				 * write last block
				 */
				if(ftpfh != NULL) {
				    if(xp) {
					if(!fwrite(xs,1,xp,ftpfh)) {
					    ftpfinished = FTPERROR;
					    break;
					}
				    }
				    if(rtclose(ftpfh)) {
					ftpfh = NULL;
					ftpfinished = FTPERROR;
					break;
				    }
				}
				if(ftpfinished == FTPCLEAR)
					ftpfinished = FTPDONE;
			}
			break;

		/*
		 * Outgoing File Transfer
		 */
		case 20:
			if(code==CONFAIL || code==CONCLOSE) {
				/*
				 * something went wrong
				 */
				fcnt = -1;
			}
			if(code!=CONOPEN) {
				/*
				 * waiting for connection to open
				 */
				break;
			}
			ftpstate = 21;

		case 21:
			if(ftpfh==NULL) {
				/*
				 * waiting for file to open
				 */
				break;
			}
			ftpstate = 22;

			/*
			 * Initialize parameters
			 */
			finished = FALSE;
			towrite = 0;
			xp = 0;
			tfrdly = MINTFRDLY;

		case 22:
			/*
			 * Check for aborted transfers
			 */
			if(ftpfinished) {
				ftpstate = 10;
				break;
			}

			/*
			 * 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) {
				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;
				skenque(ftpdskt,0);
			}
			/*
			 * 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)) {
				ftpfinished = FTPDONE;
				ftpstate = 10;
			} else {
				if(skqued(ftpdskt)) {
					suspnd(tfrdly);
				}
				suspnd(-1);
				break;
			}

			/*
			 * Outgoing Transfer Finished
			 */
		case 10:
			/*
			 * wait for other side to accept
			 * everything and then start close
			 */
			if(0 >= skenque(ftpdskt,1) || sktest(ftpdskt)) {
				fcnt = -1;
			}
			break;

		case 0:
			/*
			 * Queue CONCLASS events until ftpstate set
			 */
			if(code != 0) {
				Stmrset(CONCLASS,code,dat,1);
			}
			break;
	}

	/*
	 * after proccessing data from connection,
	 */
	if(fcnt < 0) {

#ifdef	DEBUGOPTION
if(debug&0x08) {
	printf("ftpd(%d,%d): ftpstate = %d, fcnt = %d\r\n",
		code,dat,ftpstate,fcnt);
}
#endif
		clrftp(ftpfinished);
		ftpfinished = FTPCLEAR;
	}
}

/*
 * File transfer finishup
 */
static VOID clrftp(fstate)
int fstate;
{
	if(ftpfh != NULL) {
		rtclose(ftpfh);
		ftpfh = NULL;
	}
	/*
	 * File list finishup
	 */
	lastname();
	respath();
	/*
	 * File rename finishup
	 */
	rnfrfile[0] = '\0';
	/*
	 * Reset FTP transfer state
	 */
	ftpstate = 0;
	fcnt = 0;
	/*
	 * Close data connection
	 */
	if(ftpdskt >= 0) {
		sksendwait(ftpdskt,(long) SENDWAIT);
		skclose(ftpdskt);
		ftpdskt = skrelease(ftpdskt,1);
		switch(fstate) {
		default:
			break;

		case FTPDONE:
			rftpresp(
	/*Net*/
	"226 Transfer complete"
	/*Net*/
				);
			break;

		case FTPABORT:
			rftpresp(
	/*Net*/
	"226 Abort successful"
	/*Net*/
			);
			break;

		case FTPERROR:
			rftpresp(
	/*Net*/
	"552 Disk write error, probably disk full"
	/*Net*/
				);
			break;
		}
	}
}

/*
 * 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
 */

static int getword(string,word)
char *string;
register char *word;
{
	register char *p;
	register int i;
	char *q;

	i = 0;

	/*
	 * skip leading blanks
	 */
	p = stpblk(string);
	if(!(*p)) {
		/*
		 * no words in string
		 */
		word[0] = '\0';
		return(FALSE);
	}
	if(*p == '\"') {
		/*
		 * word delimited by quotes
		 */
		while(p[++i] && p[i] != '\"')
			word[i-1] = p[i];
		word[i-1] = '\0';
		if(!p[i]) {
			/*
			 * Missing \". Assumed at end of string.
			 */
		} else {
			i++;
		}
		q = p+i;
	} else {
		/*
		 * get word, max len 79
		 */
		q = stptok(p, word, 79, " \t\r\n");
	}
     	/*
	 * remove trailing blanks
	 */
	p = stpblk(q);
	/*
	 * remove extracted stuff
	 */
	strcpy(string,p);
	return(TRUE);
}

static 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);
}

/*
 *  Scheckpass ( us, ps )
 *
 *  Check the password file for the user/password
 *  combination. An inaccessable password file
 *  results in an invalid return.
 *
 *  If the user/password are validated then
 *  the default directory is accessed and the
 *  users access rights are loaded.  An inaccessable
 *  default directory results in an invalid return.
 *
 *  Returns valid(1)/invalid(0)
 */
int Scheckpass(us,ps)
char *us,*ps;
{
	FILE *fp;
	
	if(NULL==(fp = rtopen(pass,"rn",0,0))) 
		return(0);
	while (NULL != fread(&user,sizeof(struct userblock),1,fp)) {
		/*
		 * does password check ?
		 */
		if(!strncmp(us,&user.username,USERPASSLEN) &&
			(PASSNOTREQUIRED & user.userflag[0]  ||
				Scompass(ps,&user.password))) {
			rtclose(fp);
			if(!(RFTP_PRIV & user.userflag[1])) {
				rftpresp(
	/*Net*/
	"532-User does not have FTP privilege"
	/*Net*/
					);
				return(0);
			}

			/*
			 * Expand the user specifications
			 */
			lduserpaths();
			/*
			 * Set Users default directory
			 */
			getpath(pathname);
			if(cd(&user.defdir.path)) {
				/*
				 * failed
				 */
				sprintf(rsp,
	/*Net*/
	"530-%s", errstr
	/*Net*/
					);
				rftpresp(rsp);
				cd(pathname);
				errstr[0] = '\0';
				return(0);
			}
			/*
			 * success
			 */
			sprintf(rsp,
	/*Net*/
	"230-Logged into Default Directory - /%s", getpath(pathname)
	/*Net*/
				);
			rftpresp(rsp);
			return(1);
		}
	}
	rtclose(fp);
	return(0);
}

/*
 *  Scompass ( ps, en )
 *
 *  Compute and check the encrypted password
 */
int Scompass(ps,en)
char *ps,*en;
{
	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);
}

static VOID strlwr(st)
register char *st;
{
	while(*st) {
		*st = tolower(*st);
		*st++;
	}
}
                           