/* Internet FTP client (interactive user)
 * Copyright 1991 Phil Karn, KA9Q
 */
/* Mods by G1EMM and PA0GRI */
/* modifications for encrypted password by ik1che 900419 */
/* added "resume" and "rput" commands for interrupted file transfers
 * by iw0cnb 15 Feb 92 */

/* VIEW command added by Simon G1FHY. Mod by Paul@wolf.demon.co.uk */
#include "global.h"
#include "commands.h"
#include "files.h"
#ifdef UNIX
#include <sys/stat.h>
#endif
#include "mbuf.h"
#include "session.h"
#ifdef MSDOS
#include "ctype.h"
#else
#include "socket.h"
#endif
#include "netuser.h"
#ifdef LZW
#include "lzw.h"
#endif

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: ftpcli.c,v 1.29 2001/05/06 16:32:56 brian Exp $";
#endif

#define	FTPDIRBUF	256

#ifdef ALLSESSIONS
static int compsub (struct ftpcli * ftp, char *localname, char *remotename);
static int txlate (int argc, char *argv[], void *p, const char *substr, int parm);
static int doascii (int argc, char *argv[], void *p);
static int dobatch (int argc, char *argv[], void *p);
static int dobinary (int argc, char *argv[], void *p);
static int docompare (int argc, char *argv[], void *p);
static int doftpcd (int argc, char *argv[], void *p);
static int doftpcdup (int argc, char *argv[], void *p);
static int doftpdel (int argc, char *argv[], void *p);
static int doget (int argc, char *argv[], void *p);
static int dohash (int argc, char *argv[], void *p);
static int doverbose (int argc, char *argv[], void *p);
static int dolist (int argc, char *argv[], void *p);
static int dols (int argc, char *argv[], void *p);
static int domd5 (int argc, char *argv[], void *p);
#ifdef ALLSESSIONS
static int doldir (int argc, char *argv[], void *p);
#endif
static int dolcd (int argc, char *argv[], void *p);
static int dolmkdir (int argc, char *argv[], void *p);
static int dolrename (int argc, char *argv[], void *p);
static int dolrmdir (int argc, char *argv[], void *p);
#ifdef LZW
static int doftplzw (int argc, char *argv[], void *p);
#endif
static int domcompare (int argc, char *argv[], void *p);
static int domkdir (int argc, char *argv[], void *p);
static int domget (int argc, char *argv[], void *p);
static int domput (int argc, char *argv[], void *p);
static int doput (int argc, char *argv[], void *p);
static int dopwd (int argc, char *argv[], void *p);
static int doftphelp (int argc, char *argv[], void *p);
static int doquit (int argc, char *argv[], void *p);
static int dormdir (int argc, char *argv[], void *p);
static int doftprename (int argc, char *argv[], void *p);
static int doresume (int argc, char *argv[], void *p);
static int dorput (int argc, char *argv[], void *p);
static int dotype (int argc, char *argv[], void *p);
static int doftpview (int argc, char *argv[], void *p);
static int ftpgetline (struct session * sp, const char *prompt, char *buf, int n);
static int getresp (struct ftpcli * ftp, int mincode);
static int doftpupdate (int argc, char *argv[], void *p);
static long getsub (struct ftpcli * ftp, const char *command, const char *remotename,
			    char *localname);
static long putsub (struct ftpcli * ftp, char *remotename, char *localname, int putr);
static void sendport (int s, struct sockaddr_in * thesocket);
static char *ftpcli_login (struct ftpcli * ftp, char *host);
#ifdef LZW
static int synclzw (register struct ftpcli * ftp);
#endif
#ifdef MSDOS
extern void strrev (char *str);
#endif

static char Notsess[] = "Not an FTP session!\n";
static int Ftp_type = ASCII_TYPE;
static int Ftp_logbsize = 8;

static struct cmds Ftpcmds[] =
{
	{ "",		donothing,	0, 0, NULLCHAR },
	{ "?",		doftphelp,	0, 0, NULLCHAR },
	{ "append",	doput,		0, 2, "append <localfile> <remotefile>" },
	{ "ascii",	doascii,	0, 0, NULLCHAR },
	{ "batch",	dobatch,	0, 0, NULLCHAR },
	{ "binary",	dobinary,	0, 0, NULLCHAR },
	{ "bye",	doquit,		0, 0, NULLCHAR },
	{ "cd",		doftpcd,	0, 2, "cd <directory>" },
	{ "cdup",	doftpcdup,	0, 0, NULLCHAR },
	{ "compare",	docompare,	0, 2, "compare <remotefile> [<localfile>]" },
	{ "del",	doftpdel,	0, 2, "del <remotefile>" },
	{ "dir",	dolist,		0, 0, NULLCHAR },
	{ "exit",	doquit,		0, 0, NULLCHAR },
	{ "get",	doget,		0, 2, "get <remotefile> <localfile>" },
	{ "hash",	dohash,		0, 0, NULLCHAR },
	{ "help",	doftphelp,	0, 0, NULLCHAR },
	{ "lcd",	dolcd,		0, 1, NULLCHAR },
#ifdef ALLSESSIONS
	{ "ldir",	doldir,		0, 1, NULLCHAR },
#endif
	{ "list",	dolist,		0, 0, NULLCHAR },
	{ "lmkdir",	dolmkdir,	0, 2, "lmkdir <local Directory>" },
	{ "lrename",	dolrename,	0, 3, "lrename <oldname> <newname>" },
	{ "lrmdir",	dolrmdir,	0, 2, "lrmdir <local Directory>" },
	{ "ls",		dols,		0, 0, NULLCHAR },
#ifdef LZW
	{ "lzw",	doftplzw,	0, 0, NULLCHAR },
#endif
	{ "mcompare",	domcompare,	0, 2, "mcompare <file> [<file> ...]" },
	{ "md",		domkdir,	0, 2, "md <directory>" },
	{ "md5",	domd5,		0, 2, "md5 <file>" },
	{ "mget",	domget,		0, 2, "mget <file> [<file> ...]" },
	{ "mkdir",	domkdir,	0, 2, "mkdir <directory>" },
	{ "mput",	domput,		0, 2, "mput <file> [<file> ...]" },
	{ "nlist",	dols,		0, 0, NULLCHAR },
	{ "nlst",	dols,		0, 0, NULLCHAR },
	{ "put",	doput,		0, 2, "put <localfile> <remotefile>" },
	{ "pwd",	dopwd,		0, 0, NULLCHAR },
	{ "quit",	doquit,		0, 0, NULLCHAR },
	{ "rename",	doftprename,	0, 3, "rename <oldname> <newname>" },
	{ "resume",	doresume,	0, 2, "resume <remotefile> <localfile>" },
	{ "rmdir",	dormdir,	0, 2, "rmdir <directory>" },
	{ "rput",	dorput,		0, 2, "rput <localfile> <remotefile>" },
	{ "type",	dotype,		0, 0, NULLCHAR },
	{ "update",	doftpupdate,	0, 0, NULLCHAR },
	{ "verbose",	doverbose,	0, 0, NULLCHAR },
	{ "view",	doftpview,	0, 2, "view <remotefile>"},
	{ NULLCHAR,	NULLFP ((int, char **, void *)),
					0, 0, NULLCHAR }
};


static int
doftphelp (int argc, char *argv[], void *p OPTIONAL)
{
	dohelper ("FTP commands:\n", &Ftpcmds[1], NULLCHAR, (argv[0][0] == '?' && argc == 1) ? NULLCHAR : FTPHelp, (argc == 2) ? argv[1] : NULLCHAR);
	return 0;
}


/* Handle top-level FTP command */
int
doftp (int argc, char *argv[], void *p OPTIONAL)
{
struct session *sp;
struct ftpcli ftp;
struct sockaddr_in fsocket;
int resp, vsave;
char *buf, *bufsav, *un;
char const *cp;
char prmt[40];
#ifdef notyet
char l[17];
#endif
int control;
static FILE *fp1 = NULLFILE;
struct cur_dirs dirs;
int pauseonexit = 0;

	/*Make sure this comes from console - WG7J*/
	if (Curproc->input != Command->input)
		return 0;

	/* Allocate a session control block */
	if ((sp = newsession (argv[1], FTP, 0)) == NULLSESSION) {
		tputs (TooManySessions);
		return 1;
	}
	memset ((char *) &ftp, 0, sizeof (ftp));
	ftp.control = ftp.data = -1;
	ftp.verbose = V_BYTE;	/* changed to ver 4 default - KO4KS */
	ftp.type = (char) Ftp_type;
	ftp.logbsize = Ftp_logbsize;

	sp->cb.ftp = &ftp;	/* Downward link */
	ftp.session = sp;	/* Upward link */

	ftp.curdirs = &dirs;

	fsocket.sin_family = AF_INET;
	fsocket.sin_port = IPPORT_FTP;

	tprintf ("Resolving %s... ", sp->name);
	if ((fsocket.sin_addr.s_addr = resolve (sp->name)) == 0) {
		tprintf (Badhost, sp->name);
		(void) keywait (NULLCHAR, 1);
		freesession (sp);
		return 1;
	}
	/* Open the control connection */
	if ((control = sp->s = ftp.control = socket (AF_INET, SOCK_STREAM, 0)) == -1) {
		tputs (Nosock);
		(void) keywait (NULLCHAR, 1);
		freesession (sp);
		return 1;
	}
	(void) sockmode (sp->s, SOCK_ASCII);
	(void) setflush (sp->s, -1);	/* Flush output only when we call getresp() */
	tprintf ("Trying %s...\n", psocket ((struct sockaddr *) &fsocket));
	tprintf ("Local Directory - %s\n", init_dirs (&dirs));
	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;
	if ((un = getenv ("LOGNAME")) == NULLCHAR)
		un = getenv ("USER");
	if (un != NULLCHAR)
		sprintf (prmt, "Enter user name (%s): ", un);
	else
		sprintf (prmt, "Enter user name: ");
	/* Now process responses and commands */
	buf = mallocw (LINELEN);

	/*lint -esym(668, fp1) */
	if (argc > 2)	{
		if ((fp1 = fopen (argv[2], READ_TEXT)) == NULLFILE)
			goto quit1;
		if (argc > 3 && tolower (argv[3][0]) == 'p')
			pauseonexit = 1;
	} else
		pauseonexit = 1;

	while (resp != -1) {
		switch (resp) {
			case 220:	/* Sign-on banner; prompt for and send USER command */
				if ((cp = ftpcli_login (&ftp, sp->name)) == NULLCHAR) {
					if (argc > 2) {
						if (fgets (buf, LINELEN, fp1) == NULLCHAR)
							goto quit1;
					} else if (ftpgetline (sp, prmt, buf, LINELEN) == -1) {
						resp = -1;
						continue;
					}
					/* 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 {
						if (un != NULLCHAR) {
							usprintf (control, "USER %s\n", un);
							resp = getresp (&ftp, 200);
						} else {
							tputs ("No username sent\n");
							resp = 200;	/* dummy */
						}
					}
				} else {
					usprintf (control, "USER %s\n", cp);
					free (cp);
					resp = getresp (&ftp, 200);
				}
				break;
			case 331:
				if (ftp.password == NULLCHAR) {
					/* turn off echo */
					if (argc > 2) {
						if (fgets (buf, LINELEN, fp1) == NULLCHAR)
							goto quit1;
					} else {
						sp->ttystate.echo = 0;
						if (ftpgetline (sp, "Password: ", buf, LINELEN) == -1) {
							resp = -1;
							continue;
						}
						tputc ('\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 {
						tputs ("Password must be provided.\nLogin failed.\n");
						resp = 200;	/* dummy */
					}
				} else {
					usprintf (control, "PASS %s\n", ftp.password);
					resp = getresp (&ftp, 200);
					free (ftp.password);
					ftp.password = NULLCHAR;	/* clean up */
				}
				break;
			case 230:	/* Successful login */
				/* Find out what type of system we're talking to */
				tputs ("ftp> syst\n");
				usprintf (control, "SYST\n");
				resp = getresp (&ftp, 200);
				break;
			case 215:	/* Response to SYST command */
				cp = strchr (ftp.line, ' ');
				if (cp != NULLCHAR && strnicmp (cp + 1, System, strlen (System)) == 0) {
					ftp.type = IMAGE_TYPE;
					tputs ("Defaulting to binary mode\n");
				}
				resp = 200;	/* dummy */
				break;
#ifdef notyet
			case 399:	/* Encrypted password login */
				if (ftp.password == NULLCHAR) {
					if (ftpgetline (sp, "Key ? --> ", buf, LINELEN) == -1) {
						resp = -1;
						continue;
					}
					/* Send the command only if the user response
						 * was non-null
						 */
					if (buf[0] != '\n') {
						cp = strchr (ftp.line, ':');
						cp += 2;
						/*
							epass(htol(cp),buf,l);
						      */
						l[16] = '\0';
						free (ftp.line);
						ftp.line = NULLCHAR;
						usprintf (control, "PASS %s\n", l);
						resp = getresp (&ftp, 200);
					} else {
						tputs ("Password must be provided.\nLogin failed.\n");
						resp = 200;	/* dummy */
					}
				} else {
					cp = strchr (ftp.line, ':');
					cp += 2;
					/*
						epass(htol(cp),ftp.password,l);
						*/
					l[16] = '\0';
					free (ftp.line);
					ftp.line = NULLCHAR;
					usprintf (control, "PASS %s\n", l);
					resp = getresp (&ftp, 200);
					free (ftp.password);
					ftp.password = NULLCHAR;	/* clean up */
				}
				break;
#endif
			default:	/* Test the control channel first */
				if (sockstate (control) == NULLCHAR) {
					resp = -1;
					break;
				}
				if (argc > 2) {
					if (fgets (buf, LINELEN, fp1) == NULLCHAR)
						goto quit1;
				} else if (ftpgetline (sp, "ftp> ", buf, LINELEN) == -1) {
					resp = -1;
					continue;
				}
				/* 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 = (int16) vsave;
				}
		}
	}
quit1:	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)
		(void) fclose (ftp.fp);
	if (ftp.data != -1)
		close_s (ftp.data);
	if (ftp.control != -1)
		close_s (ftp.control);
	if (pauseonexit)
		(void) keywait (NULLCHAR, 1);
	if (fp1 != NULLFILE)
		(void) fclose (fp1);
	if (ftp.session != NULLSESSION)
		freesession (ftp.session);
	free_dirs (&dirs);
	return 0;
}


/* Control verbosity level */
static int
doverbose (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 (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);
}


/* Enable/disable update flag */
static int
doftpupdate (int argc, char *argv[], void *p)
{
register struct ftpcli *ftp;

	if ((ftp = (struct ftpcli *) p) == NULLFTP)
		return -1;
	return setbool (&ftp->update, "Update with MD5", argc, argv);
}


/* Set verbosity to high (convenience command) */
static int
dohash (int argc OPTIONAL, char *argv[] OPTIONAL, void *p)
{
register struct ftpcli *ftp;

	if ((ftp = (struct ftpcli *) p) == NULLFTP)
		return -1;
	tputs ("Hash Printing ");
	if (ftp->verbose == V_HASH) {
		ftp->verbose = V_HASH + 1;
		tputs ("Off\n");
	} else {
		tputs ("On\n");
		ftp->verbose = V_HASH;
	}
	return 0;
}


/* Close session */
static int
doquit (int argc OPTIONAL, char *argv[] OPTIONAL, void *p)
{
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP)
		return -1;
	usprintf (ftp->control, "QUIT\n");
	(void) getresp (ftp, 200);	/* Get the closing message */
	(void) getresp (ftp, 200);	/* Wait for the server to close */
	/* prevent spurious retry caused by "error" return */
	ftp->state = EXITING_STATE;
	return -1;
}


#ifdef LZW
static int
synclzw (register struct ftpcli *ftp)
{
int retval;

	usprintf (ftp->control, "XLZW %d %d\n", Lzwbits, Lzwmode);
	retval = getresp (ftp, 200);
	if (retval >= 200 && retval < 300) {
		ftp->lzwbits = Lzwbits;
		ftp->lzwmode = Lzwmode;
	}
	return retval;
}


/* Toggle LZW compressed streams mode on */
static int
doftplzw (int argc OPTIONAL, char *argv[] OPTIONAL, void *p)
{
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP)
		return -1;
	ftp->lzw ^= 1;
	tprintf ("200 Client LZW o%s\n", (ftp->lzw) ? "n" : "ff");
	return (200);
}

#endif


/* Rename remote file */
static int
doftprename (int argc OPTIONAL, char *argv[], void *p)
{
register struct ftpcli *ftp;
int retval;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP)
		return -1;
	usprintf (ftp->control, "RNFR %s\n", argv[1]);
	retval = getresp (ftp, 350);

	if (retval != 350)
		return (retval);
	usprintf (ftp->control, "RNTO %s\n", argv[2]);
	return (getresp (ftp, 200));
}


/* Rename local file */
static int
dolrename (int argc OPTIONAL, char *argv[], void *p)
{
register struct ftpcli *ftp;
char fname1[128];
char fname2[128];

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP)
		return -1;
	strncpy (fname1, make_fname (ftp->curdirs->dir, argv[1]), 128);
	strncpy (fname2, make_fname (ftp->curdirs->dir, argv[2]), 128);
	if (rename (fname1, fname2) == -1)
		tprintf ("Can't rename: %s\n", SYS_ERRLIST(errno));
	else
		tprintf ("Local file renamed to '%s'\n", fname2);
	return 0;

}


static int
txlate (int argc OPTIONAL, char *argv[], void *p, const char *substr, int parm)
{
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP)
		return -1;
	usprintf (ftp->control, "%s %s\n", substr, (parm) ? argv[1] : "");
	return getresp (ftp, 200);
}


/* Pass PWD to server - included here to allow cmd to be viewed in help list */
static int
dopwd (int argc, char *argv[], void *p)
{
	return (txlate (argc, argv, p, "PWD", 0));
}


/* Translate 'cd' to 'cwd' for convenience */
static int
doftpcd (int argc, char *argv[], void *p)
{
	if (argc == 1)
		return (dopwd (argc, argv, p));
	return (txlate (argc, argv, p, "CWD", 1));
}


/* Pass CDUP to server - included here to allow cmd to be viewed in help list */
static int
doftpcdup (int argc, char *argv[], void *p)
{
	return (txlate (argc, argv, p, "CDUP", 0));
}


/* Translate 'del' to 'dele' for convenience */
static int
doftpdel (int argc, char *argv[], void *p)
{
	return (txlate (argc, argv, p, "DELE", 1));
}


/* Translate 'mkdir' to 'xmkd' for convenience */
static int
domkdir (int argc, char *argv[], void *p)
{
	return (txlate (argc, argv, p, "XMKD", 1));
}


/* Translate 'rmdir' to 'xrmd' for convenience */
static int
dormdir (int argc, char *argv[], void *p)
{
	return (txlate (argc, argv, p, "XRMD", 1));
}


static int
dobinary (int argc OPTIONAL, char *argv[] OPTIONAL, void *p)
{
char *args[2];
char buf[2];

	strcpy (buf, "I");
	args[1] = buf;
	return dotype (2, args, p);
}


static int
doascii (int argc OPTIONAL, char *argv[] OPTIONAL, void *p)
{
char *args[2];
char buf[2];

	strcpy (buf, "A");
	args[1] = buf;
	return dotype (2, args, p);
}


/* Handle "type" command from user */
static int
dotype (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:
				tputs ("Image\n");
				break;
			case ASCII_TYPE:
				tputs ("Ascii\n");
				break;
			case LOGICAL_TYPE:
				tprintf ("Logical bytesize %u\n", ftp->logbsize);
				break;
			default:
				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;
}


/* Handle "ftype" command */
int
doftype (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		tputs ("Ftp initial TYPE is ");
		switch (Ftp_type) {
			case IMAGE_TYPE:
				tputs ("Image\n");
				break;
			case ASCII_TYPE:
				tputs ("Ascii\n");
				break;
			case LOGICAL_TYPE:
				tprintf ("Logical bytesize %u\n", Ftp_logbsize);
				break;
			default:
				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':
			if (argc < 3)	{
				tprintf ("requires a bytesize parameter\nUsage: ftype logical <size>\n");
				return 1;
			}
			Ftp_type = LOGICAL_TYPE;
			Ftp_logbsize = atoi (argv[2]);
			break;
		default:
			tprintf ("Invalid type %s\n", argv[1]);
			return 1;
	}
	return 0;
}


/* View added to jnos1.08 by Simon G1FHY _ mod by Paul@wolf.demon.co.uk */
/* Start view transfer. Syntax: view <remote name> */
static int
doftpview (int argc OPTIONAL, char *argv[], void *p)
{
char *remotename;
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	remotename = argv[1];

	(void) getsub (ftp, "RETR", remotename, NULLCHAR);
	return 0;
}


/* Start receive transfer. Syntax: get <remote name> [<local name>] */
static int
doget (int argc, char *argv[], void *p)
{
char *remotename, *localname;
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	remotename = argv[1];
	if (argc < 3)
		localname = remotename;
	else
		localname = argv[2];

	if (!ftp->update || compsub (ftp, localname, remotename) != 0)
		(void) getsub (ftp, "RETR", remotename, localname);
	return 0;
}


/* Get a collection of files */
static int
domget (int argc, char *argv[], void *p)
{
register struct ftpcli *ftp;
FILE *files, *filel;
char tmpname[80];
char *buf, *local;
#ifdef MSDOS
char *c;
#endif
int i;
#ifdef MSDOS
int inlist;
#endif
long r;

	if ((ftp = (struct ftpcli *) p) == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	(void) tmpnam (tmpname);
	buf = mallocw (FTPDIRBUF);
	ftp->state = RECEIVING_STATE;
	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '@') {
#ifdef MSDOS
			inlist = 1;
#endif
			if ((filel = fopen (make_fname (ftp->curdirs->dir, &argv[i][1]), "r")) == NULLFILE) {
				tprintf ("Can't open listfile: %s\n", &argv[i][1]);
				continue;
			}
			if ((files = fopen (tmpname, "w")) == NULLFILE) {
				tprintf ("Can't open tempfile: %s\n", tmpname);
				continue;
			}
			while (fgets (buf, FTPDIRBUF, filel) != NULLCHAR)
				fputs (buf, files);

			(void) fclose (files);
			(void) fclose (filel);
			if ((files = fopen (tmpname, "r")) == NULLFILE) {
				tprintf ("Can't open tempfile: %s\n", tmpname);
				continue;
			}
		} else {
#ifdef MSDOS
			inlist = 0;
#endif
			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, FTPDIRBUF, files) != NULLCHAR) {
			rip (buf);
			local = strdup (buf);
#ifdef	MSDOS
			if (inlist) {
				strrev (local);
				(void) strtok (local, "\\/[]<>,?#~()&%");
				strrev (local);
			}
			if ((c = strstr (local, ".")) != NULLCHAR) {
				c++;
				c = strtok (c, ".");	/* remove 2nd period if any*/
			}
#endif
			if (!ftp->update || compsub (ftp, buf, buf) != 0)
				(void) getsub (ftp, "RETR", buf, local);
			usflush (ftp->control);
			free (local);
			if (ftp->abort) {
				/* User abort */
				ftp->abort = 0;
				(void) fclose (files);
				unlink (tmpname);
				free (buf);
				ftp->state = COMMAND_STATE;
				return 1;
			}
		}
		(void) fclose (files);
		unlink (tmpname);
	}
	free (buf);
	ftp->state = COMMAND_STATE;
	ftp->abort = 0;
	return 0;
}


/* Resume interrupted file transfer. Syntax: resume <remote name> [<local name>] */
static int
doresume (int argc, char *argv[], void *p)
{
char *remotename, *localname;
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	remotename = argv[1];
	if (argc < 3)
		localname = remotename;
	else
		localname = argv[2];

	(void) getsub (ftp, "RSME", remotename, localname);
	return 0;
}

/* List remote directory. Syntax: dir <remote files> [<local name>] */
static int
dolist (int argc, char *argv[], void *p)
{
char *remotename, *localname;
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	remotename = argv[1];
	if (argc > 2)
		localname = argv[2];
	else
		localname = NULLCHAR;

	(void) getsub (ftp, "LIST", remotename, localname);
	return 0;
}


/* Remote directory list, short form. Syntax: ls <remote files> [<local name>] */
static int
dols (int argc, char *argv[], void *p)
{
char *remotename, *localname;
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	remotename = argv[1];
	if (argc > 2)
		localname = argv[2];
	else
		localname = NULLCHAR;

	(void) getsub (ftp, "NLST", remotename, localname);
	return 0;
}


static int
domd5 (int argc OPTIONAL, char *argv[], void *p)
{
char *remotename;
register struct ftpcli *ftp;
int control;
int resp;
int typewait = 0;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	control = ftp->control;
	remotename = argv[1];
	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;
			default:
				break;
		}
		ftp->typesent = ftp->type;
		if (!ftp->batch) {
			resp = getresp (ftp, 200);
			if (resp == -1 || resp > 299)
				goto failure;
		} else
			typewait = 1;

	}
	usprintf (control, "XMD5 %s\n", remotename);
	if (typewait)
		(void) getresp (ftp, 200);
	(void) getresp (ftp, 200);
failure:
	return 0;
}


static int
docompare (int argc, char *argv[], void *p)
{
char *remotename, *localname;
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	remotename = argv[1];
	if (argc > 2)
		localname = argv[2];
	else
		localname = remotename;

	if (compsub (ftp, localname, remotename) == 0)
		tputs ("Same\n");
	else
		tputs ("Different\n");
	return 0;
}


/* Compare a collection of files */
static int
domcompare (int argc, char *argv[], void *p)
{
register struct ftpcli *ftp;
FILE *files;
char *buf;
int i;
long r;
char tmpname[80];

	if ((ftp = (struct ftpcli *) p) == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	(void) tmpnam (tmpname);
	buf = mallocw (FTPDIRBUF);
	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, FTPDIRBUF, files) != NULLCHAR) {
			rip (buf);
			if (compsub (ftp, buf, buf) == 0)
				tprintf ("%s - Same\n", buf);
			else
				tprintf ("%s - Different\n", buf);

			if (ftp->abort) {
				/* User abort */
				ftp->abort = 0;
				(void) fclose (files);
				unlink (tmpname);
				free (buf);
				ftp->state = COMMAND_STATE;
				return 1;
			}
		}
		(void) fclose (files);
		unlink (tmpname);
	}
	free (buf);
	ftp->state = COMMAND_STATE;
	ftp->abort = 0;
	return 0;
}


/* Common subroutine to compare a local with a remote file
 * Return 1 if files are different, 0 if they are the same
 */
static int
compsub (struct ftpcli *ftp, char *localname, char *remotename)
{
char const *mode = NULLCHAR;
char *cp;
FILE *fp;
int control;
int resp, i;
int typewait = 0;
char remhash[16];
char lochash[16];

	control = ftp->control;

	switch (ftp->type) {
		case IMAGE_TYPE:
		case LOGICAL_TYPE:
			mode = READ_BINARY;
			break;
		case ASCII_TYPE:
		default:
			mode = READ_TEXT;
			break;
	}
	if ((fp = fopen (make_fname (ftp->curdirs->dir, localname), mode)) == NULLFILE) {
		tprintf ("Can't read local file %s\n", make_fname (ftp->curdirs->dir, localname));
		return 1;
	}
	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;
			default:
				break;
		}
		ftp->typesent = ftp->type;
		if (!ftp->batch) {
			resp = getresp (ftp, 200);
			if (resp == -1 || resp > 299)
				goto failure;
		} else
			typewait = 1;
	}
	usprintf (control, "XMD5 %s\n", remotename);
	/* Try to overlap the two MD5 operations */
	(void) md5hash (fp, lochash, ftp->type == ASCII_TYPE);
	(void) fclose (fp);
	if (typewait && (resp = getresp (ftp, 200)) > 299)
		goto failure;
	if ((resp = getresp (ftp, 200)) > 299) {
		if (resp == 500)
			ftp->update = 0;	/* XMD5 not supported */
		goto failure;
	}
	if ((cp = strchr (ftp->line, ' ')) == NULLCHAR) {
		tputs ("Error in response\n");
		goto failure;
	}
	/* Convert ascii/hex back to binary */
	(void) readhex (remhash, cp, sizeof (remhash));
	if (ftp->verbose > 1) {
		tputs ("Loc ");
		for (i = 0; i < (int) sizeof (lochash); i++)
			tprintf ("%02x", lochash[i] & 0xff);
		tprintf (" %s\n", make_fname (ftp->curdirs->dir, localname));
	}
	if (memcmp (lochash, remhash, sizeof (remhash)) == 0)
		return 0;
	else
		return 1;
failure:
	return 1;
}


/* Common code to LIST/NLST/RETR/RSME and mget
 * Returns number of bytes received if successful
 * Returns -1 on error
 */
static long
getsub (register struct ftpcli *ftp, const char *command, const char *remotename, char *localname)
{
unsigned long total;
FILE *fp;
int resp, i, control, savmode;
char const *mode;
struct sockaddr_in lsocket;
struct sockaddr_in lcsocket;
int32 startclk, rate;
int vsave;
int typewait = 0;
int prevstate;
unsigned long starting;

	if (ftp == NULLFTP)
		return -1;
	control = ftp->control;
	savmode = ftp->type;
#ifdef __GNUC__
	mode = 0;		/* semi-spurious warning */
#endif

	switch (ftp->type) {
		case IMAGE_TYPE:
		case LOGICAL_TYPE:
			if (strcmp (command, "RSME") == 0)
				mode = APPEND_BINARY;
			else
				mode = WRITE_BINARY;
			break;
		default:
		case ASCII_TYPE:
			if (strcmp (command, "RSME") == 0)
				mode = APPEND_TEXT;
			else
				mode = WRITE_TEXT;
			break;
	}
	/* Open the file */
	if (localname == NULLCHAR) {
		fp = NULLFILE;
	} else if ((fp = fopen (make_fname (ftp->curdirs->dir, 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);
	(void) 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;
			default:
				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;
	(void) getsockname (ftp->data, (char *) &lsocket, &i);	/* Get port number */
	if (!i)
		goto failure;
	i = SOCKSIZE;
	(void) getsockname (ftp->control, (char *) &lcsocket, &i);
	if (!i)
		goto failure;
	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;
	}
#ifdef LZW
	if (ftp->lzw && ftp->type == ASCII_TYPE) {
		int retval = 200;

		retval = synclzw (ftp);
		if (retval >= 200 && retval < 300)
			lzwinit (ftp->data, ftp->lzwbits, ftp->lzwmode);
	}
#endif

	/* 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 */
	(void) accept (ftp->data, NULLCHAR, (int *) NULL);
	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;
	if (strcmp (command, "RSME") == 0) {
		if ((starting = getsize (fp)) == (unsigned long) -1)
			starting = 0L;
		usprintf (control, "%lu %lu\n", starting, checksum (fp, (long) starting));
		usflush (control);
		if (fp != NULLFILE)
			fseek (fp, (long) starting, SEEK_SET);
	}
	total = (unsigned long) recvfile (fp, ftp->data, ftp->type, (ftp->verbose >= V_HASH) ? ftp->verbose : 0);
	/* 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)
		(void) fclose (fp);
	if (remotename == NULLCHAR)
		remotename = "";
	if (total == (unsigned long) -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 = (long) ((total * 1000) / (uint32) startclk);
			 else 		/* Avoid overflow */
				rate = (long) (total / (uint32) (startclk / 1000));
		}
		tprintf ("%s %s: %lu bytes in %lu sec (%lu/sec)\n",
			 command, remotename, total, startclk / 1000, rate);
	}
	/* Get the "Sent" message */
	(void) getresp (ftp, 200);

	ftp->state = (char) prevstate;
	ftp->verbose = (int16) vsave;
	ftp->type = (char) savmode;
	return ((long) total);

failure:
	/* Error, quit */
	if (fp != NULLFILE && fp != stdout)
		(void) fclose (fp);
	close_s (ftp->data);
	ftp->data = -1;
	ftp->state = (char) prevstate;
	ftp->type = (char) savmode;
	return -1;
}


/* Send a file. Syntax: put <local name> [<remote name>] */
static int
doput (int argc, char *argv[], void *p)
{
register struct ftpcli *ftp;
char *remotename, *localname;

	if ((ftp = (struct ftpcli *) p) == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	localname = argv[1];
	if (argc < 3)
		remotename = localname;
	else
		remotename = argv[2];

	if (!ftp->update || compsub (ftp, localname, remotename) != 0)
		(void) putsub (ftp, remotename, localname, (*argv[0] == 'a') ? 2 : 0);
	return 0;
}


/* Put a collection of files */
static int
domput (int argc, char *argv[], void *p)
{
register struct ftpcli *ftp;
FILE *files;
int i;
#if 0
int j;
#endif
char tmpname[80];
char *buf, *file;

	if ((ftp = (struct ftpcli *) p) == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	(void) tmpnam (tmpname);
	if ((files = fopen (tmpname, "w+")) == NULLFILE) {
		tputs ("Can't list local files\n");
		unlink (tmpname);
		return 1;
	}
	for (i = 1; i < argc; i++) {
		/* Use the path in the ftp client struct, since user may have done
	         * a lcd command to change dir !
	         */
		file = pathname (ftp->curdirs->dir, argv[i]);
#if 0				/* was #ifdef MSDOS */
		/* Shift everything back one byte, pathname returns with a leading '/'! */
		for (j = 1; j <= strlen (file); j++)
			file[j - 1] = file[j];
#endif
		(void) getdir (file, 0, files);
		free (file);
	}

	rewind (files);
	buf = mallocw (FTPDIRBUF);
	ftp->state = SENDING_STATE;
	while (fgets (buf, FTPDIRBUF, files) != NULLCHAR) {
		rip (buf);
		if (!ftp->update || compsub (ftp, buf, buf) != 0)
			(void) putsub (ftp, buf, buf, 0);
		if (ftp->abort)
			break;	/* User abort */
		usflush (ftp->control);
	}
	(void) fclose (files);
	unlink (tmpname);
	free (buf);
	ftp->state = COMMAND_STATE;
	ftp->abort = 0;
	return 0;
}


/* Put a file, appending data to it - iw0cnb */
static int
dorput (int argc, char *argv[], void *p)
{
register struct ftpcli *ftp;
char *remotename, *localname;

	if ((ftp = (struct ftpcli *) p) == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	localname = argv[1];
	if (argc < 3)
		remotename = localname;
	else
		remotename = argv[2];

	(void) putsub (ftp, remotename, localname, 1);
	return 0;
}


/* Common code to put, mput.
 * Returns number of bytes sent if successful
 * Returns -1 on error
 */
static long
putsub (ftp, remotename, localname, putr)
register struct ftpcli *ftp;
char *remotename, *localname;
int putr;			/* Flag: 0 if standard put, 1 if put with resume, if 2 append */
{
char const *mode;
int i, resp, control;
unsigned long total;
FILE *fp;
struct sockaddr_in lsocket, lcsocket;
int32 startclk, rate;
int typewait = 0;
int prevstate;
char *line;
unsigned long starting;
unsigned long check, local_check;

	control = ftp->control;
	if (ftp->type == IMAGE_TYPE)
		mode = READ_BINARY;
	else
		mode = READ_TEXT;

	/* Open the file */
	if ((fp = fopen (make_fname (ftp->curdirs->dir, 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);
	(void) 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;
			default:
				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;
	(void) getsockname (ftp->data, (char *) &lsocket, &i);
	if (!i)
		goto failure;
	i = SOCKSIZE;
	(void) getsockname (ftp->control, (char *) &lcsocket, &i);
	if (!i)
		goto failure;
	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;
		}
	}
#ifdef LZW
	if (ftp->lzw && ftp->type == ASCII_TYPE) {
		int retval = 200;

		retval = synclzw (ftp);
		if (retval >= 200 && retval < 300)
			lzwinit (ftp->data, ftp->lzwbits, ftp->lzwmode);
	}
#endif

	/* Generate the command to start the transfer */
	if (putr == 1)
		usprintf (control, "RPUT %s\n", remotename);
	else if (putr == 2)
		usprintf (control, "APPE %s\n", remotename);
	else
		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
	 */
	(void) accept (ftp->data, NULLCHAR, (int *) NULL);

	startclk = msclock ();

	if (putr == 1) {	/* Wait for file offset and checksum */
		char *ctmp;
		line = mallocw (40);
		if (recvline (control, (unsigned char *) line, 40) == -1) {
			free (line);
			goto failure;
		}
		starting = (unsigned long) atol (line);
		ctmp = strchr (line, ' ');
		if (ctmp != NULLCHAR)
			check = (unsigned long) atol (ctmp);
		else
			check = 0;
		free (line);
		local_check = checksum (fp, (long) starting);
		if (ftp->verbose >= V_HASH)
			tprintf ("Remote checksum: %lu - Local checksum: %lu - Offset: %lu\n", check, local_check, starting);
		check -= local_check;
		if (check != 0) {
			tprintf ("Can't send %s: files are different\n", localname);
			(void) shutdown (ftp->data, 1);
			(void) getresp (ftp, 200);
			goto failure;
		}
	}
	total = (unsigned long) sendfile (fp, ftp->data, ftp->type, (ftp->verbose >= V_HASH) ? ftp->verbose : 0);
	close_s (ftp->data);
	ftp->data = -1;
	(void) fclose (fp);

	/* Wait for control channel ack before calculating transfer time;
	 * this accounts for transmitted data in the pipe.
	 */
	(void) getresp (ftp, 200);

	if (total == (unsigned long) -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 = (long) ((total * 1000) / (uint32) startclk);
			else 		/* Avoid overflow */
				rate = (long) (total / (uint32)(startclk / 1000));
		}
		tprintf ("STOR %s: %lu bytes in %lu sec (%lu/sec)\n",
			 remotename, total, startclk / 1000, rate);
	}
	ftp->state = (char) prevstate;
	return ((long) total);

failure:
	/* Error, quit */
	(void) fclose (fp);
	close_s (ftp->data);
	ftp->data = -1;
	ftp->state = (char) 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 (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) {
		tputs ("Not an active FTP session\n");
		return 1;
	}
	ftp = sp->cb.ftp;
	switch (ftp->state) {
		case COMMAND_STATE:
			tputs ("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.
			 */
			(void) shutdown (ftp->data, 1);	/* Note fall-thru */
			ftp->abort = 1;
			break;
		case RECEIVING_STATE:
			/* Just blow away the receive socket */
			(void) shutdown (ftp->data, 2);	/* Note fall-thru */
			ftp->abort = 1;
			break;
		default:
			break;
	}
	return 0;
}


/* send PORT message */
static void
sendport (int s, struct sockaddr_in *thesocket)
{
	/* Send PORT a,a,a,a,p,p message */
	usprintf (s, "PORT %u,%u,%u,%u,%u,%u\n",
		  hibyte (hiword (thesocket->sin_addr.s_addr)),
		  lobyte (hiword (thesocket->sin_addr.s_addr)),
		  hibyte (loword (thesocket->sin_addr.s_addr)),
		  lobyte (loword (thesocket->sin_addr.s_addr)),
		  hibyte (thesocket->sin_port),
		  lobyte (thesocket->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 */
{
int rval;

	usflush (ftp->control);
	for (;;) {
		/* Get line */
		if (recvline (ftp->control, (unsigned char *) ftp->line, LINELEN) == -1) {
			rval = -1;
			break;
		}
		rip (ftp->line);/* Remove cr/lf */
		rval = atoi (ftp->line);
		if (rval >= 400 || ftp->verbose >= V_NORMAL)
			tprintf ("%s\n", ftp->line);	/* Display to user */

		/* Messages with dashes are continued */
		if (ftp->line[3] != '-' && rval >= mincode)
			break;
	}
	return rval;
}


/* Issue a prompt and read a line from the user */
static int
ftpgetline (struct session *sp, const char *prompt, char *buf, int n)
{
	/* If there's something already there, don't issue prompt */
	if (socklen (sp->input, 0) == 0)
		tputs (prompt);

	usflush (sp->output);
	return recvline (sp->input, (unsigned char *) buf, (unsigned) n);
}


/* Attempt to log in the user whose name is in ftp->username and password
 * in pass
 */
static char *
ftpcli_login (struct ftpcli *ftp, char *host)
{
char buf[80], *cp = NULLCHAR, *cp1;
FILE *fp;


	if ((fp = fopen (Hostfile, "r")) == NULLFILE)
		return NULLCHAR;

	while ((void) fgets (buf, sizeof (buf), fp), !feof (fp)) {
		buf[strlen (buf) - 1] = '\0';	/* Nuke the newline */
		if (buf[0] == '#')
			continue;	/* Comment */
		if ((cp = strchr (buf, ' ')) == NULLCHAR)
			/* Bogus entry */
			continue;
		*cp++ = '\0';	/* Now points to user name */
		if (strcmp (host, buf) == 0)
			break;	/* Found host name */
	}
	if (feof (fp)) {
		/* User name not found in file */
		(void) fclose (fp);
		return NULLCHAR;
	}
	(void) fclose (fp);
	/* Look for space after user field in file */
	cp = skipwhite (cp);
	if ((cp1 = strpbrk (cp, " \t")) == NULLCHAR)
		/* if not there then we'll prompt */
		ftp->password = NULLCHAR;
	else
		*cp1++ = '\0';	/* Now points to password */
	cp1 = skipwhite (cp1);
	ftp->password = (strcmp (cp, "*")) ? strdup (cp1) : strdup ("anonymous");
	return strdup (cp);
}


static int
dolcd (int argc, char *argv[], void *p)
{
register struct ftpcli *ftp;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	if (argc > 1) {
		if (!dir_ok (argv[1], ftp->curdirs)) {
			tprintf ("Invalid Drive/Directory - %s\n", argv[1]);
			return 1;
		}
	}
	tprintf ("Local Directory - %s\n", ftp->curdirs->dir);
	return 0;
}


#ifdef ALLSESSIONS
static int
doldir (int argc, char *argv[], void *p)
{
register struct ftpcli *ftp;
char **margv;

	margv = (char **) callocw (2, sizeof (char *));

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	tputc ('\n');
	if (argc == 1)
		margv[1] = strdup (ftp->curdirs->dir);
	else
		margv[1] = strdup (make_dir_path (argc, argv[1], ftp->curdirs->dir));
	(void) dodir (2, margv, p);
	free (margv[1]);
	free (margv);
	tputc ('\n');
	return 0;
}

#endif


static int
dolmkdir (int argc OPTIONAL, char *argv[], void *p)
{
register struct ftpcli *ftp;
char *buf;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	/*	undosify(argv[1]);	done in make_fname	*/
	buf = strdup (make_fname (ftp->curdirs->dir, argv[1]));
	if (mkdir (buf, 0777) == -1)
		tprintf ("Can't make %s: %s\n", buf, SYS_ERRLIST(errno));
	else
		tprintf ("Directory %s Created\n", buf);
	free (buf);
	return 0;
}


static int
dolrmdir (int argc OPTIONAL, char *argv[], void *p)
{
register struct ftpcli *ftp;
char *buf;

	ftp = (struct ftpcli *) p;
	if (ftp == NULLFTP) {
		tputs (Notsess);
		return 1;
	}
	buf = strdup (make_fname (ftp->curdirs->dir, argv[1]));
	if (rmdir (buf) == -1)
		tprintf ("Can't remove %s: %s\n", buf, SYS_ERRLIST(errno));
	else
		tprintf ("Directory %s Removed\n", buf);
	free (buf);
	return 0;
}

#endif
