/* Internet FTP client (interactive user)
 * Copyright 1991 Phil Karn, KA9Q
 */
#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "session.h"
#include "cmdparse.h"
#include "proc.h"
#include "tty.h"
#include "socket.h"
#include "ftp.h"
#include "ftpcli.h"
#include "commands.h"
#include "netuser.h"
#include "dirutil.h"
#include "internet.h"

#define	DIRBUF	256

static int doascii __ARGS((int argc,char *argv[],void *p));
static int dobatch __ARGS((int argc,char *argv[],void *p));
static int dobinary __ARGS((int argc,char *argv[],void *p));
static int doftpcd __ARGS((int argc,char *argv[],void *p));
static int doget __ARGS((int argc,char *argv[],void *p));
static int dohash __ARGS((int argc,char *argv[],void *p));
static int doverbose __ARGS((int argc,char *argv[],void *p));
static int dolist __ARGS((int argc,char *argv[],void *p));
static int dols __ARGS((int argc,char *argv[],void *p));
static int domkdir __ARGS((int argc,char *argv[],void *p));
static int domget __ARGS((int argc,char *argv[],void *p));
static int domput __ARGS((int argc,char *argv[],void *p));
static int doput __ARGS((int argc,char *argv[],void *p));
static int doquit __ARGS((int argc,char *argv[],void *p));
static int dormdir __ARGS((int argc,char *argv[],void *p));
static int dotype __ARGS((int argc,char *argv[],void *p));
static int getline __ARGS((struct session *sp,char *prompt,char *buf,int n));
static int getresp __ARGS((struct ftpcli *ftp,int mincode));
static long getsub __ARGS((struct ftpcli *ftp,char *command,char *remotename,
	char *localname));
static long putsub __ARGS((struct ftpcli *ftp,char *remotename,char *localname));
static void sendport __ARGS((int s,struct sockaddr_in *socket));

static char Notsess[] = "Not an FTP session!\n";

static struct cmds Ftpcmds[] = {
	"",		donothing,	0, 0, NULLCHAR,
	"ascii",	doascii,	0, 0, NULLCHAR,
	"batch",	dobatch,	0, 0, NULLCHAR,
	"binary",	dobinary,	0, 0, NULLCHAR,
	"cd",		doftpcd,	0, 2, "cd <directory>",
	"dir",		dolist,		0, 0, NULLCHAR,
	"list",		dolist,		0, 0, NULLCHAR,
	"get",		doget,		0, 2, "get <remotefile> <localfile>",
	"hash",		dohash,		0, 0, NULLCHAR,
	"ls",		dols,		0, 0, NULLCHAR,
	"mget",		domget,		0, 2, "mget <file> [<file> ...]",
	"mkdir",	domkdir,	0, 2, "mkdir <directory>",
	"mput",		domput,		0, 2, "mput <file> [<file> ...]",
	"nlst",		dols,		0, 0, NULLCHAR,
	"quit",		doquit,		0, 0, NULLCHAR,
	"rmdir",	dormdir,	0, 2, "rmdir <directory>",
	"put",		doput,		0, 2, "put <localfile> <remotefile>",
	"type",		dotype,		0, 0, NULLCHAR,
	"verbose",	doverbose,	0, 0, NULLCHAR,
	NULLCHAR,	NULLFP,		0, 0, NULLCHAR,
};

/* Handle top-level FTP command */
int
doftp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct session *sp;
	struct ftpcli ftp;
	struct sockaddr_in fsocket;
	int resp,vsave;
	char *buf,*bufsav,*cp;
	int control;

	/* Allocate a session control block */
	if((sp = newsession(argv[1],FTP,1)) == NULLSESSION){
		tprintf("Too many sessions\n");
		return 1;
	}
	memset((char *)&ftp,0,sizeof(ftp));
	ftp.control = ftp.data = -1;
	ftp.verbose = V_NORMAL;

	sp->cb.ftp = &ftp;	/* Downward link */
	ftp.session = sp;	/* Upward link */

	fsocket.sin_family = AF_INET;
	if(argc < 3)
		fsocket.sin_port = IPPORT_FTP;
	else
		fsocket.sin_port = atoi(argv[2]);

	tprintf("Resolving %s... ",sp->name);
	if((fsocket.sin_addr.s_addr = resolve(sp->name)) == 0){
		tprintf(Badhost,sp->name);
		keywait(NULLCHAR,1);
		freesession(sp);
		return 1;
	}
	/* Open the control connection */
	if((control = sp->s = ftp.control = socket(AF_INET,SOCK_STREAM,0)) == -1){
		tprintf("Can't create socket\n");
		keywait(NULLCHAR,1);
		freesession(sp);
		return 1;
	}
	settos(sp->s,LOW_DELAY);
	sockmode(sp->s,SOCK_ASCII);
	setflush(sp->s,-1);	/* Flush output only when we call getresp() */
	tprintf("Trying %s...\n",psocket((struct sockaddr *)&fsocket));
	if(connect(control,(char *)&fsocket,sizeof(fsocket)) == -1)
		goto quit;
	tprintf("FTP session %u connected to %s\n",(unsigned)(sp-Sessions),
		sp->name);

	/* Wait for greeting from server */
	resp = getresp(&ftp,200);

	if(resp >= 400)
		goto quit;
	/* Now process responses and commands */
	buf = mallocw(LINELEN);
	while(resp != -1){
		if(resp == 220){
			/* Sign-on banner; prompt for and send USER command */
			getline(sp,"Enter user name: ",buf,LINELEN);
			/* Send the command only if the user response
			 * was non-null
			 */
			if(buf[0] != '\n'){
				usprintf(control,"USER %s",buf);
				resp = getresp(&ftp,200);
			} else
				resp = 200;	/* dummy */
		} else if(resp == 331){
			/* turn off echo */
			sp->ttystate.echo = 0;
			getline(sp,"Password: ",buf,LINELEN);
			tprintf("\n");
			/* Turn echo back on */
			sp->ttystate.echo = 1;
			/* Send the command only if the user response
			 * was non-null
			 */
			if(buf[0] != '\n'){
				usprintf(control,"PASS %s",buf);
				resp = getresp(&ftp,200);
			} else
				resp = 200;	/* dummy */
		} else {
			/* Test the control channel first */
			if(sockstate(control) == NULLCHAR)
				break;

			getline(sp,"ftp> ",buf,LINELEN);

			/* Copy because cmdparse modifies the original */
			bufsav = strdup(buf);
			if((resp = cmdparse(Ftpcmds,buf,&ftp)) != -1){
				/* Valid command, free buffer and get another */
				free(bufsav);
			} else {
				/* Not a local cmd, send to remote server */
				usputs(control,bufsav);
				free(bufsav);

				/* Enable display of server response */
				vsave = ftp.verbose;
				ftp.verbose = V_NORMAL;
				resp = getresp(&ftp,200);
				ftp.verbose = vsave;
			}
		}
	}
	free(buf);
quit:	cp = sockerr(control);
	tprintf("FTP session %u closed: %s\n",(unsigned)(sp - Sessions),
	 cp != NULLCHAR ? cp : "EOF");

	if(ftp.fp != NULLFILE && ftp.fp != stdout)
		fclose(ftp.fp);
	if(ftp.data != -1)
		close_s(ftp.data);
	if(ftp.control != -1)
		close_s(ftp.control);
	keywait(NULLCHAR,1);
	if(ftp.session != NULLSESSION)
		freesession(ftp.session);
	return 0;
}

/* Control verbosity level */
static int
doverbose(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;

	if((ftp = (struct ftpcli *)p) == NULLFTP)
		return -1;
	return setshort(&ftp->verbose,"Verbose",argc,argv);
}
/* Enable/disable command batching */
static int
dobatch(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;

	if((ftp = (struct ftpcli *)p) == NULLFTP)
		return -1;
	return setbool(&ftp->batch,"Command batching",argc,argv);
}
/* Set verbosity to high (convenience command) */
static int
dohash(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;

	if((ftp = (struct ftpcli *)p) == NULLFTP)
		return -1;
	ftp->verbose = V_HASH;
	return 0;
}
	
/* Close session */
static int
doquit(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;

	ftp = (struct ftpcli *)p;
	if(ftp == NULLFTP)
		return -1;
	usprintf(ftp->control,"QUIT\n");
	getresp(ftp,200);	/* Get the closing message */
	getresp(ftp,200);	/* Wait for the server to close */
	return -1;
}

/* Translate 'cd' to 'cwd' for convenience */
static int
doftpcd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;

	ftp = (struct ftpcli *)p;
	if(ftp == NULLFTP)
		return -1;
	usprintf(ftp->control,"CWD %s\n",argv[1]);
	return getresp(ftp,200);
}
/* Translate 'mkdir' to 'xmkd' for convenience */
static int
domkdir(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;

	ftp = (struct ftpcli *)p;
	if(ftp == NULLFTP)
		return -1;
	usprintf(ftp->control,"XMKD %s\n",argv[1]);
	return getresp(ftp,200);
}
/* Translate 'rmdir' to 'xrmd' for convenience */
static int
dormdir(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;

	ftp = (struct ftpcli *)p;
	if(ftp == NULLFTP)
		return -1;
	usprintf(ftp->control,"XRMD %s\n",argv[1]);
	return getresp(ftp,200);
}
static int
dobinary(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char *args[2];

	args[1] = "I";
	return dotype(2,args,p);
}
static int
doascii(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char *args[2];

	args[1] = "A";
	return dotype(2,args,p);
}

/* Handle "type" command from user */
static int
dotype(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;

	ftp = (struct ftpcli *)p;
	if(ftp == NULLFTP)
		return -1;
	if(argc < 2){
		switch(ftp->type){
		case IMAGE_TYPE:
			tprintf("Image\n");
			break;
		case ASCII_TYPE:
			tprintf("Ascii\n");
			break;
		case LOGICAL_TYPE:
			tprintf("Logical bytesize %u\n",ftp->logbsize);
			break;
		}
		return 0;
	}
	switch(*argv[1]){
	case 'i':
	case 'I':
	case 'b':
	case 'B':
		ftp->type = IMAGE_TYPE;
		break;
	case 'a':
	case 'A':
		ftp->type = ASCII_TYPE;
		break;
	case 'L':
	case 'l':
		ftp->type = LOGICAL_TYPE;
		ftp->logbsize = atoi(argv[2]);
		break;
	default:
		tprintf("Invalid type %s\n",argv[1]);
		return 1;
	}
	return 0;
}
/* Start receive transfer. Syntax: get <remote name> [<local name>] */
static int
doget(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char *remotename,*localname;
	register struct ftpcli *ftp;

	ftp = (struct ftpcli *)p;
	if(ftp == NULLFTP){
		tprintf(Notsess);
		return 1;
	}
	remotename = argv[1];
	if(argc < 3)
		localname = remotename;
	else
		localname = argv[2];

	getsub(ftp,"RETR",remotename,localname);
	return 0;
}
/* Get a collection of files */
static int
domget(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;
	FILE *files;
	char tmpname[L_tmpnam+1];
	char *buf;
	int i;
	long r;

	if((ftp = (struct ftpcli *)p) == NULLFTP){
		tprintf(Notsess);
		return 1;
	}
	tmpnam(tmpname);
	buf = mallocw(DIRBUF);
	ftp->state = RECEIVING_STATE;
	for(i=1;i<argc;i++){
		r = getsub(ftp,"NLST",argv[i],tmpname);
		if(ftp->abort)
			break;	/* Aborted */
		if(r == -1 || (files = fopen(tmpname,"r")) == NULLFILE){
			tprintf("Can't NLST %s\n",argv[i]);
			unlink(tmpname);
			continue;
		}
		/* The tmp file now contains a list of the remote files, so
		 * go get 'em. Break out if the user signals an abort.
		 */
		while(fgets(buf,DIRBUF,files) != NULLCHAR){
			rip(buf);
			getsub(ftp,"RETR",buf,buf);
			if(ftp->abort){
				/* User abort */
				ftp->abort = 0;
				fclose(files);
				unlink(tmpname);
				free(buf);
				ftp->state = COMMAND_STATE;
				return 1;
			}
		}
		fclose(files);
		unlink(tmpname);
	}
	free(buf);
	ftp->state = COMMAND_STATE;
	ftp->abort = 0;
	return 0;
}
/* List remote directory. Syntax: dir <remote files> [<local name>] */
static int
dolist(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char *remotename,*localname;
	register struct ftpcli *ftp;

	ftp = (struct ftpcli *)p;
	if(ftp == NULLFTP){
		tprintf(Notsess);
		return 1;
	}
	remotename = argv[1];
	if(argc > 2)
		localname = argv[2];
	else
		localname = NULLCHAR;

	getsub(ftp,"LIST",remotename,localname);
	return 0;
}
/* Remote directory list, short form. Syntax: ls <remote files> [<local name>] */
static int
dols(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char *remotename,*localname;
	register struct ftpcli *ftp;

	ftp = (struct ftpcli *)p;
	if(ftp == NULLFTP){
		tprintf(Notsess);
		return 1;
	}
	remotename = argv[1];
	if(argc > 2)
		localname = argv[2];
	else
		localname = NULLCHAR;

	getsub(ftp,"NLST",remotename,localname);
	return 0;
}
/* Common code to LIST/NLST/RETR and mget
 * Returns number of bytes received if successful
 * Returns -1 on error
 */
static long
getsub(ftp,command,remotename,localname)
register struct ftpcli *ftp;
char *command,*remotename,*localname;
{
	unsigned long total;
	FILE *fp;
	int cnt,resp,i,control,savmode;
	char *mode;
	struct sockaddr_in lsocket;
	struct sockaddr_in lcsocket;
	int32 startclk,rate;
	int vsave;
	int typewait = 0;
	int prevstate;

	if(ftp == NULLFTP)
		return -1;
	control = ftp->control;
	savmode = ftp->type;

	switch(ftp->type){
	case IMAGE_TYPE:
	case LOGICAL_TYPE:
		mode = WRITE_BINARY;
		break;
	case ASCII_TYPE:
		mode = WRITE_TEXT;
		break;
	}
	/* Open the file */
	if(localname == NULLCHAR){
		fp = NULLFILE;
	} else if((fp = fopen(localname,mode)) == NULLFILE){
		tprintf("Can't write %s: %s\n",localname,sys_errlist[errno]);
		return -1;
	}
	/* Open the data connection */
	ftp->data = socket(AF_INET,SOCK_STREAM,0);
	listen(ftp->data,0);	/* Accept only one connection */
	prevstate = ftp->state;
	ftp->state = RECEIVING_STATE;

	/* Send TYPE message, if necessary */
	if(strcmp(command,"LIST") == 0 || strcmp(command,"NLST") == 0){
		/* Directory listings are always in ASCII */
		ftp->type = ASCII_TYPE;
	}
	if(ftp->typesent != ftp->type){
		switch(ftp->type){
		case ASCII_TYPE:
			usprintf(control,"TYPE A\n");
			break;
		case IMAGE_TYPE:
			usprintf(control,"TYPE I\n");
			break;
		case LOGICAL_TYPE:
			usprintf(control,"TYPE L %d\n",ftp->logbsize);
			break;
		}
		ftp->typesent = ftp->type;
		if(!ftp->batch){
			resp = getresp(ftp,200);
			if(resp == -1 || resp > 299)
				goto failure;
		} else
			typewait = 1;
	}
	/* Send the PORT message. Use the IP address
	 * on the local end of our control connection.
	 */
	i = SOCKSIZE;
	getsockname(ftp->data,(char *)&lsocket,&i); /* Get port number */
	i = SOCKSIZE;
	getsockname(ftp->control,(char *)&lcsocket,&i);
	lsocket.sin_addr.s_addr = lcsocket.sin_addr.s_addr;
	sendport(control,&lsocket);
	if(!ftp->batch){
		/* Get response to PORT command */
		resp = getresp(ftp,200);
		if(resp == -1 || resp > 299)
			goto failure;
	}
	/* Generate the command to start the transfer */
	if(remotename != NULLCHAR)
		usprintf(control,"%s %s\n",command,remotename);
	else
		usprintf(control,"%s\n",command);

	if(ftp->batch){
		/* Get response to TYPE command, if sent */
		if(typewait){
			resp = getresp(ftp,200);
			if(resp == -1 || resp > 299)
				goto failure;
		}
		/* Get response to PORT command */
		resp = getresp(ftp,200);
		if(resp == -1 || resp > 299)
			goto failure;
	}
	/* Get the intermediate "150" response */
	resp = getresp(ftp,100);
	if(resp == -1 || resp >= 400)
		goto failure;

	/* Wait for the server to open the data connection */
	cnt = 0;
	ftp->data = accept(ftp->data,NULLCHAR,&cnt);
	startclk = msclock();

	/* If output is to the screen, temporarily disable hash marking */
	vsave = ftp->verbose;
	if(vsave >= V_HASH && fp == NULLFILE)
		ftp->verbose = V_NORMAL;
	total = recvfile(fp,ftp->data,ftp->type,ftp->verbose >= V_HASH);
	/* Immediately close the data connection; some servers (e.g., TOPS-10)
	 * wait for the data connection to close completely before returning
	 * the completion message on the control channel
	 */
	close_s(ftp->data);
	ftp->data = -1;

#ifdef	CPM
	if(fp != NULLFILE && ftp->type == ASCII_TYPE)
		putc(CTLZ,fp);
#endif
	if(fp != NULLFILE && fp != stdout)
		fclose(fp);
	if(remotename == NULLCHAR)
		remotename = "";
	if(total == -1){
		tprintf("%s %s: Error/abort during data transfer\n",command,remotename);
	} else if(ftp->verbose >= V_SHORT){
		startclk = msclock() - startclk;
		rate = 0;
		if(startclk != 0){	/* Avoid divide-by-zero */
			if(total < 4294967L) {
				rate = (total*1000)/startclk;
			} else {	/* Avoid overflow */
				rate = total/(startclk/1000);
			}
		}
		tprintf("%s %s: %lu bytes in %lu sec (%lu/sec)\n",
		 command,remotename, total,startclk/1000,rate);
	}
	/* Get the "Sent" message */
	getresp(ftp,200);

	ftp->state = prevstate;
	ftp->verbose = vsave;
	ftp->type = savmode;
	return total;

failure:
	/* Error, quit */
	if(fp != NULLFILE && fp != stdout)
		fclose(fp);
	close_s(ftp->data);
	ftp->data = -1;
	ftp->state = prevstate;
	ftp->type = savmode;
	return -1;
}
/* Send a file. Syntax: put <local name> [<remote name>] */
static int
doput(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;
	char *remotename,*localname;

	if((ftp = (struct ftpcli *)p) == NULLFTP){
		tprintf(Notsess);
		return 1;
	}
	localname = argv[1];
	if(argc < 3)
		remotename = localname;
	else
		remotename = argv[2];

	putsub(ftp,remotename,localname);
	return 0;
}
/* Put a collection of files */
static int
domput(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct ftpcli *ftp;
	FILE *files;
	int i;
	char tmpname[L_tmpnam+1];	
	char *buf;

	if((ftp = (struct ftpcli *)p) == NULLFTP){
		tprintf(Notsess);
		return 1;
	}
	tmpnam(tmpname);
	if((files = fopen(tmpname,"w+")) == NULLFILE){
		tprintf("Can't list local files\n");
		unlink(tmpname);
		return 1;
	}
	for(i=1;i<argc;i++)
		getdir(argv[i],0,files);

	rewind(files);
	buf = mallocw(DIRBUF);
	ftp->state = SENDING_STATE;
	while(fgets(buf,DIRBUF,files) != NULLCHAR){
		rip(buf);
		putsub(ftp,buf,buf);
		if(ftp->abort)
			break;		/* User abort */
	}
	fclose(files);
	unlink(tmpname);
	free(buf);
	ftp->state = COMMAND_STATE;
	ftp->abort = 0;
	return 0;
}
/* Common code to put, mput.
 * Returns number of bytes sent if successful
 * Returns -1 on error
 */
static long
putsub(ftp,remotename,localname)
register struct ftpcli *ftp;
char *remotename,*localname;
{
	char *mode;
	int i,resp,control;
	unsigned long total;
	FILE *fp;
	struct sockaddr_in lsocket,lcsocket;
	int32 startclk,rate;
	int typewait = 0;
	int prevstate;

	control = ftp->control;
	if(ftp->type == IMAGE_TYPE)
		mode = READ_BINARY;
	else
		mode = READ_TEXT;

	/* Open the file */
	if((fp = fopen(localname,mode)) == NULLFILE){
		tprintf("Can't read %s: %s\n",localname,sys_errlist[errno]);
		return -1;
	}
	if(ftp->type == ASCII_TYPE && isbinary(fp)){
		tprintf("Warning: type is ASCII and %s appears to be binary\n",localname);
	}
	/* Open the data connection */
	ftp->data = socket(AF_INET,SOCK_STREAM,0);
	listen(ftp->data,0);
	prevstate = ftp->state;
	ftp->state = SENDING_STATE;

	/* Send TYPE message, if necessary */
	if(ftp->typesent != ftp->type){
		switch(ftp->type){
		case ASCII_TYPE:
			usprintf(control,"TYPE A\n");
			break;
		case IMAGE_TYPE:
			usprintf(control,"TYPE I\n");
			break;
		case LOGICAL_TYPE:
			usprintf(control,"TYPE L %d\n",ftp->logbsize);
			break;
		}
		ftp->typesent = ftp->type;

		/* Get response to TYPE command */
		if(!ftp->batch){
			resp = getresp(ftp,200);
			if(resp == -1 || resp > 299){
				goto failure;
			}
		} else
			typewait = 1;
	}
	/* Send the PORT message. Use the IP address
	 * on the local end of our control connection.
	 */
	i = SOCKSIZE;
	getsockname(ftp->data,(char *)&lsocket,&i);
	i = SOCKSIZE;
	getsockname(ftp->control,(char *)&lcsocket,&i);
	lsocket.sin_addr.s_addr = lcsocket.sin_addr.s_addr;
	sendport(control,&lsocket);
	if(!ftp->batch){
		/* Get response to PORT command */
		resp = getresp(ftp,200);
		if(resp == -1 || resp > 299){
			goto failure;
		}
	}
	/* Generate the command to start the transfer */
	usprintf(control,"STOR %s\n",remotename);

	if(ftp->batch){
		/* Get response to TYPE command, if sent */
		if(typewait){
			resp = getresp(ftp,200);
			if(resp == -1 || resp > 299){
				goto failure;
			}
		}
		/* Get response to PORT command */
		resp = getresp(ftp,200);
		if(resp == -1 || resp > 299){
			goto failure;
		}
	}
	/* Get the intermediate "150" response */
	resp = getresp(ftp,100);
	if(resp == -1 || resp >= 400){
		goto failure;
	}

	/* Wait for the data connection to open. Otherwise the first
	 * block of data would go out with the SYN, and this may confuse
	 * some other TCPs
	 */
	accept(ftp->data,NULLCHAR,(int *)NULL);

	startclk = msclock();

	total = sendfile(fp,ftp->data,ftp->type,ftp->verbose >= V_HASH);
	close_s(ftp->data);
	ftp->data = -1;
	fclose(fp);

	/* Wait for control channel ack before calculating transfer time;
	 * this accounts for transmitted data in the pipe
	 */
	getresp(ftp,200);

	if(total == -1){
		tprintf("STOR %s: Error/abort during data transfer\n",remotename);
	} else if(ftp->verbose >= V_SHORT){
		startclk = msclock() - startclk;
		rate = 0;
		if(startclk != 0){	/* Avoid divide-by-zero */
			if(total < 4294967L) {
				rate = (total*1000)/startclk;
			} else {	/* Avoid overflow */
				rate = total/(startclk/1000);
			}
		}
		tprintf("STOR %s: %lu bytes in %lu sec (%lu/sec)\n",
		 remotename,total,startclk/1000,rate);
	}
	ftp->state = prevstate;
	return total;

failure:
	/* Error, quit */
	fclose(fp);
	close_s(ftp->data);
	ftp->data = -1;
	ftp->state = prevstate;
	return -1;
}
/* Abort a GET or PUT operation in progress. Note: this will leave
 * the partial file on the local or remote system
 */
int
doabort(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct session *sp;
	register struct ftpcli *ftp;

	sp = (struct session *)p;
	if(sp == NULLSESSION)
		return -1;

	/* Default is the current session, but it can be overridden with
	 * an argument.
	 */
	if(argc > 1)
		sp = sessptr(argv[1]);

	if(sp == NULLSESSION || sp->type != FTP){
		tprintf("Not an active FTP session\n");
		return 1;
	}
	ftp = sp->cb.ftp;
	switch(ftp->state){
	case COMMAND_STATE:
		tprintf("No active transfer\n");
		return 1;
	case SENDING_STATE:
		/* Send a premature EOF.
		 * Unfortunately we can't just reset the connection
		 * since the remote side might end up waiting forever
		 * for us to send something.
		 */
		shutdown(ftp->data,1);	/* Note fall-thru */
		ftp->abort = 1;
		break;
	case RECEIVING_STATE:
		/* Just blow away the receive socket */
		shutdown(ftp->data,2);	/* Note fall-thru */
		ftp->abort = 1;
		break;
	}
	return 0;
}
/* send PORT message */
static void
sendport(s,socket)
int s;
struct sockaddr_in *socket;
{
	/* Send PORT a,a,a,a,p,p message */
	usprintf(s,"PORT %u,%u,%u,%u,%u,%u\n",
		hibyte(hiword(socket->sin_addr.s_addr)),
		lobyte(hiword(socket->sin_addr.s_addr)),
		hibyte(loword(socket->sin_addr.s_addr)),
		lobyte(loword(socket->sin_addr.s_addr)),
		hibyte(socket->sin_port),
		lobyte(socket->sin_port));
}

/* Wait for, read and display response from FTP server. Return the result code.
 */
static int
getresp(ftp,mincode)
struct ftpcli *ftp;
int mincode;	/* Keep reading until at least this code comes back */
{
	register char *line;
	int rval;

	usflush(ftp->control);
	line = mallocw(LINELEN);
	for(;;){
		/* Get line */
		if(recvline(ftp->control,line,LINELEN) == -1){
			rval = -1;
			break;
		}
		rip(line);		/* Remove cr/lf */
		rval = atoi(line);
		if(rval >= 400 || ftp->verbose >= V_NORMAL)
			tprintf("%s\n",line);	/* Display to user */

		/* Messages with dashes are continued */
		if(line[3] != '-' && rval >= mincode)
			break;
	}
	free(line);
	return rval;
}

/* Issue a prompt and read a line from the user */
static int
getline(sp,prompt,buf,n)
struct session *sp;
char *prompt;
char *buf;
int n;
{
	/* If there's something already there, don't issue prompt */
	if(socklen(sp->input,0) == 0)
		tprintf(prompt);

	usflush(sp->output);
	return recvline(sp->input,buf,n);
}
