/*
	Internet FTP client (interactive user)
   Copyright 1991 Phil Karn, KA9Q
*/

/* Modified by paul@wolf.demon.co.uk to support a local 'view' command */

/****************************************************************************
*	$Id: ftpcli.c 1.8 94/02/02 14:37:51 ROOT_DOS Exp $
*	13 Sep 92	1.4		GT	Log "cd", "get" and "mget" commands.
*	08 May 93	1.5		GT	Fix warnings.								
*	22 Dec 93	1.6		iain@nomed.demon.co.uk	Batch stuff.	
*	24 Jan 94	1.7		GT	Fix batch file type setting.			
*	01 Feb 94	1.8		GT	"ftpopt" command.							
*
*  Atari Version by David Nash - dnash@chaos.demon.co.uk
*
*  26.08.93 - Add CDUP command
*             Add dobytes, do_opt_bytes to add bytes verbosity level
*
****************************************************************************/

/***************************************************************************
 *	Modified by Iain Douglas ( iain@nomed.demon.co.uk 21/12/1993            *
 * Added source <filename> 	to redirect input from a file                *
 * Added close to allow users to close connection to current host without  *
 *             having to leave current session.                            *
 * Added open <host> to allow users to open connection to new hosts        *
 * Extensivly remodeled the main loop to allow for opening and closing     *
 * connections to hosts and scripted command files.                        *
 ***********************************************************************ID**/
 
#include <stdio.h>
#include <string.h>
#include <errno.h>									/* ATARI: sys_errlist > strerror*/
#include "global.h"
#include "files.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 "help.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 dobytes __ARGS((int argc,char *argv[],void *p));
static int doftpcd __ARGS((int argc,char *argv[],void *p));
static int doftpcdup __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 doftphelp __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 dopager __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 doview __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 int dosource __ARGS((int argc, char *argv[], void *p));
static int doopen __ARGS((int argc, char *argv[],void *p));
static int doclose __ARGS(( int argc, char *argv[], void *p));
static void init_ftpstruct(struct session *sp,struct ftpcli *ftp,
				  struct sockaddr_in *fsocket);
static void sourceline(struct session *sp,char *prompt,char *buf,int n);

static int do_opt_verbose __ARGS((int argc, char *argv[], void *p));
static int do_opt_bytes __ARGS((int argc, char *argv[], void *p));
static int do_opt_hash __ARGS((int argc, char *argv[], void *p));
static int do_opt_type __ARGS((int argc, char *argv[], void *p));
static int do_opt_ascii __ARGS((int argc, char *argv[], void *p));
static int do_opt_binary __ARGS((int argc, char *argv[], void *p));
static int do_opt_pager __ARGS((int argc, char *argv[], void *p));
static int do_opt_wrap __ARGS((int argc, char *argv[], void *p));
static void set_default_opts __ARGS((struct ftpcli *ftp));

#define NOTCONNECTED -2
#define REALLYQUIT -3

static char dummyhost[]="nohost.nowhere.nocom";
static char Notsess[] = "Not an FTP session!\n";

/* FTP options default settings. */

static int16 verbose = V_NORMAL;					/* report verbosity				*/
static char type 		= ASCII_TYPE;				/* transfer type					*/
static int logbsize 	= 0;							/* logical byte size				*/
static int pager 		= 0;							/* nz - paging on					*/
static int16 wrap 	= 0;							/* hash wrap column				*/


static struct cmds Ftpcmds[] = {
	{ "",			donothing,	0, 0, NULLCHAR },
	{ "ascii",	doascii,		0, 0, NULLCHAR },
	{ "batch",	dobatch,		0, 0, NULLCHAR },
	{ "binary",	dobinary,	0, 0, NULLCHAR },
	{ "bytes",	dobytes,		0, 0, NULLCHAR },
	{ "cd",		doftpcd,		0, 2, "cd <directory>" },
	{ "cdup",	doftpcdup,	0,	0, NULLCHAR },
	{ "close",	doclose,		0, 0, NULLCHAR },
	{ "dir",		dolist,		0, 0, NULLCHAR },
	{ "list",	dolist,		0, 0, NULLCHAR },
	{ "get",		doget,		0, 2, "get <remotefile> <localfile>" },
	{ "hash",	dohash,		0, 0, NULLCHAR },
	{ "help",   doftphelp,  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> ...]" },
	{ "open",	doopen,     0, 2, "open <host>"},
	{ "nlst",	dols,			0, 0, NULLCHAR },
	{ "pager",  dopager,    0, 0, NULLCHAR },
	{ "quit",	doquit,		0, 0, NULLCHAR },
	{ "rmdir",	dormdir,		0, 2, "rmdir <directory>" },
	{ "put",		doput,		0, 2, "put <localfile> <remotefile>" },
	{ "source", dosource,   0, 2, "source <filename>" },
	{ "type",	dotype,		0, 0, NULLCHAR },
	{ "verbose",doverbose,	0, 0, NULLCHAR },
	{ "view",   doview,     0, 2, "view <remotefile>" },
	{ NULLCHAR,	NULLFP,		0, 0, NULLCHAR },
};

static struct cmds ftp_opt_cmds[] =
	{
		{ "",				donothing,			0,	0,	NULLCHAR },
		{ "verbose",	do_opt_verbose,	0,	0,	NULLCHAR },
		{ "bytes",		do_opt_bytes,		0,	0,	NULLCHAR },
		{ "hash",		do_opt_hash,		0,	0,	NULLCHAR },
		{ "type",		do_opt_type,		0,	0,	NULLCHAR },
		{ "ascii",		do_opt_ascii,		0,	0,	NULLCHAR },
		{ "binary",		do_opt_binary,		0,	0,	NULLCHAR },
		{ "pager",		do_opt_pager,		0,	0,	NULLCHAR },
		{ "wrap",		do_opt_wrap,		0,	0,	NULLCHAR },
		{ NULLCHAR,		NULLFP,				0,	0,	NULLCHAR }
	};
		

/*
	doftp - Handle top-level FTP command
*/

int __stdargs doftp(int argc, char *argv[], void *p)
{
	struct session *sp;
	struct ftpcli ftp;
	struct sockaddr_in fsocket;
	int resp,vsave;
	char *buf,*bufsav,*cp,*tmp;
	int control,controlsave=NOTCONNECTED;


     /* Allocate a session control block  and init the ftp struct*/
     if(argc >1)
	  tmp = argv[1];
     else
	  tmp = dummyhost;

     if((sp = newsession(tmp,FTP)) == NULLSESSION)
	  {
	   tprintf("Too Many Sessions\n");
	   return 1;
	  }

     init_ftpstruct(sp,&ftp,&fsocket);

     resp=200;                 /* dummy resp and control values   */
     control= NOTCONNECTED;    /* to get into the main loop if    */
					 /* no host passed on the cmd line  */

     if(argc >1)               /* if there is a cmd line param    */
	  {                      /* attempt to connect to host      */
	   if((resp=doopen( argc, argv, &ftp))==1)
		goto quit;         /* only if there is a problem with */
					 /* the socket                      */
	   control=ftp.control;
	  }

	/* Now process responses and commands */
	buf = mallocw(LINELEN);

	while(resp != -1)
	   {
	    if(resp == 220)
		 {
		  /* Sign-on banner; prompt for and send USER command */
              if(ftp.sourcefileon==1)
			  sourceline(sp,"Enter user name: ",buf,LINELEN);
		  else
		  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)
		    {
		     sp->ttystate.echo = 0;  /* turn off echo */
                 if(ftp.sourcefileon==1)
			  sourceline(sp,"Password: ",buf,LINELEN);
		     else
			  getline(sp,"Password: ",buf,LINELEN);
		     tprintf("\n");
		     sp->ttystate.echo = 1; /* Turn echo back on */
		     /* 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
		    {
		     if(control != NOTCONNECTED) /* Test the control channel first */
			  {
			   if(sockstate(control) == NULLCHAR)
				 break;
			  }

		     if(ftp.sourcefileon==1)
			  sourceline(sp,"ftp> ",buf,LINELEN);
		     else
			  getline(sp,"ftp> ",buf,LINELEN);

		     /* Copy because cmdparse modifies the original */
		     bufsav = strdup(buf);
		     if((resp = cmdparse(Ftpcmds,buf,&ftp)) != -1)
			  {
			   free(bufsav); /* Valid command, free buffer */

			   if( resp == REALLYQUIT) /* if last command was quit */
				 resp = - 1;         /* then flag quit           */
			   else
				{
				 if(ftp.control== NOTCONNECTED)
				    {
				     tprintf("WARNING you are not connected to a remote host\n");
				     control=NOTCONNECTED;
				    }
				 else
				    {
				     control=ftp.control; /* remote connect established */
				     if(controlsave==0)   /* save info for use when quit*/
					  controlsave=control; /* requested */
				    }
				}
			  }
		     else
			  {
			   /* if no connection to remote host exists then don't  */
			   /* attempt to send the command to it. Assume it was a */
			   /* user typo as, usputs() returns -1 if it fails and  */
			   /* this causes  the session to quit.                  */

			   if(ftp.control != NOTCONNECTED)
				{
				 /* Not a local cmd, or user typo 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;
				}
			   else
				resp = 200;
			  }
		}
	}
     free(buf);
     /* if the user issued a close command before quit then restore the */
     /* value of control so that sockerr gives the right message.       */
     if(control == NOTCONNECTED)
	   control=controlsave;

quit:	if(controlsave != NOTCONNECTED)
	    cp = sockerr(control);
	else
	   cp=NULLCHAR;

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


/****************************************************************************
*	doftpopt			
*	Process default ftp options.
****************************************************************************/

int doftpopt (argc, argv, p)
int argc;
char *argv[];
void *p;
	{
	return (subcmd (ftp_opt_cmds, argc, argv, p));
	}	/* int doftpopt (argc, argv, p) */

/****************************************************************************
*	ftp default option processing functions.								*
****************************************************************************/

static int do_opt_verbose (argc, argv, p)
int argc;
char *argv[];
void *p;
	{
	return (setshort (&verbose, "Verbose", argc, argv));
	}	/* static int do_opt_verbose (argc, argv, p) */


static int do_opt_hash (argc, argv, p)
int argc;
char *argv[];
void *p;
	{
	verbose = V_HASH;
	return (0);
	}	/* static int do_opt_hash (argc, argv, p) */

static int do_opt_bytes(int argc, char *argv[], void *p)
{
	verbose = V_BYTE;
	return 0;
}

static int do_opt_type (argc, argv, p)
int argc;
char *argv[];
void *p;
	{
	if (argc < 2)
		{
		switch (type)
			{
			case IMAGE_TYPE:
				tprintf("Image\n");
				break;
				
			case ASCII_TYPE:
				tprintf("Ascii\n");
				break;
				
			case LOGICAL_TYPE:
				tprintf("Logical bytesize %u\n", logbsize);
				break;
			}	/* switch (type) */
			
		return 0;
		}	/* if (argc < 2) */
		
	switch (*argv[1])
		{
		case 'i':
		case 'I':
		case 'b':
		case 'B':
			type = IMAGE_TYPE;
			break;
			
		case 'a':
		case 'A':
			type = ASCII_TYPE;
			break;
			
		case 'L':
		case 'l':
			type = LOGICAL_TYPE;
			logbsize = atoi(argv[2]);
			break;

		default:
			tprintf("Invalid type %s\n",argv[1]);
			return 1;
		}	/* switch (*argv[1]) */
		
	return 0;
	}	/* static int do_opt_type (argc, argv, p) */


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

	args[1] = "I";
	return do_opt_type (2, args, p);
	}	/* static int do_opt_binary (argc, argv, p) */


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

	args[1] = "A";
	return do_opt_type (2, args, p);
	}	/* static int do_opt_ascii (argc, argv, p) */


static int do_opt_pager (argc, argv, p)
int argc;
char *argv[];
void *p;
	{
	return (setbool (&pager, "Pager", argc, argv));
	}	/* static int do_opt_pager (argc, argv, p) */


static int do_opt_wrap (argc, argv, p)
int argc;
char *argv[];
void *p;
	{
	return (setshort (&wrap, "Wrap column", argc, argv));
	}	/* static int do_opt_wrap (argc, argv, p) */
	

/* 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;
}

/*
	dobytes - Set verbosity to high (convenience command)
*/

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

	if((ftp = (struct ftpcli *)p) == NULLFTP)
		return -1;

	ftp->verbose = V_BYTE;
	return 0;
}

/* Simple help display - displays all commands and parameters */
static int
doftphelp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char buf[256];
    sprintf(buf,"%s/ftp.hlp",Helpdir);
    helproutine(argc,argv,Ftpcmds,buf);
	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 -3;
}

/* 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]);
	log (ftp->control, "FTP %s: CWD %s", ftp->session->name, argv[1]);
	return getresp(ftp,200);
}
 
/*
	doftpcdup - Translate 'cdup' to 'CDUP' for convenience
*/

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

	ftp = (struct ftpcli *)p;

	if (ftp == NULLFTP)
 		return -1;

 	usprintf(ftp->control,"CDUP\n",argv[1]);
 	log(ftp->control, "FTP %s: CDUP ", ftp->session->name, 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;
}

/* Handle "pager" command from user */
static int
dopager(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct ftpcli *ftp;
    struct session *s;

    ftp=(struct ftpcli *)p;
    s=ftp->session;

    if (ftp==NULLFTP){
        tprintf(Notsess);
        return 1;
    }

    return setbool(&s->flowmode,"Pager",argc,argv);
}

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

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

    getsub(ftp,"RETR",remotename,NULLCHAR);
	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;
}


/*
	mget - Get a collection of files
*/

static int domget(int argc, char *argv[], void *p)
{
	int i;
	long r;
	char *buf;
 	char tmpname[L_tmpnam+1];
	FILE *files;
	struct ftpcli *ftp;

	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);	/* get list of names    */
		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;
}


/*
	dolist - 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){
		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;
}


/*
	getsub - Common code to LIST/NLST/RETR and mget
				Returns number of bytes received if successful
				Returns -1 on error
*/

static long getsub(struct ftpcli *ftp,
					     char *command,
						  char *remotename,
						  char *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;
	}

	if (localname == NULLCHAR) {					/* open the file					*/
		fp = NULLFILE;
	} else
		if ((fp = fopen(localname, mode)) == NULLFILE) {
			tprintf("Can't write %s: %s\n", localname, strerror(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 ||
		 	(fp == NULLFILE)
		)  {

		/* 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;
		
#ifndef ATARI		
	total = recvfile(fp, ftp->data, ftp->type, ftp->verbose >= V_HASH, ftp->wrap);
#else
  	total = recvfile(fp,ftp->data,ftp->type,
						  (ftp->verbose >= V_HASH) ? ftp->verbose : 0, ftp->wrap);		
#endif

	/*
		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;

	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;
	
	log(ftp->control, "FTP %s: %s remote: %s local: %s",
		 ftp->session->name, command, remotename, localname);
	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;

	log(ftp->control, "FTP failed %s: %s remote %s local %s\n",
		 ftp->session->name, command, remotename, localname);
	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,strerror(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();

#ifndef ATARI 	
	total = sendfile (fp,ftp->data,ftp->type,ftp->verbose >= V_HASH, ftp->wrap);
#else

	total = sendfile(fp,ftp->data,ftp->type,
						  (ftp->verbose >= V_HASH) ? ftp->verbose : 0, ftp->wrap);
#endif
	
	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);
}

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

		if((ftp = (struct ftpcli *)p) == NULLFTP)
				return -1;

		/* test for sourcing fom a file within a sourced file */
		if(ftp->sourcefileon==1)
			 {
				tprintf("Already sourcing commands from file\n");
				return -1;
			 }

		if((ftp->source=fopen(argv[1],"r"))==NULL) {
		 	char tmpbuf[255];
			 	sprintf(tmpbuf, "%s\\%s\0", Dscripts, argv[1]);  /* try script dir */

		 	if ((ftp->source = fopen(tmpbuf, "r")) == NULL) {
				tprintf("Unable to open file %s\n",argv[1]);
				return -1;
			}
	 	}
		/* flag sourcing commands from file */
		ftp->sourcefileon=1;
		return 1;
	 }

static void sourceline(struct session *sp,char *prompt,char *buf,int n)
	 {

	  register struct ftpcli *ftp;

	  if((ftp = (struct ftpcli *)sp->cb.ftp) == NULLFTP)
				return;

		/* set pager mode to off, if it's on */

		if(sp->flowmode==1)
				sp->flowmode=0;

		/* read a line from the script file, if fgets returns a NULL then
			 assume that EOF has been reached and act accordingly */

		if((fgets(buf,n,ftp->source))==NULL)
			 {
				ftp->sourcefileon=0;
				fclose(ftp->source);
				*buf=0;
			 }
		else
			 {
				/* check line contains a "\n" as the command may fail if not
					 as might the next as well */

				if((strstr(buf,"\n"))==NULL)
					 tprintf("The following command may fail, line possibly too long\n");

				if(sp->ttystate.echo != 0)
				    tprintf("%s %s\n",prompt,buf);
				else
				    tprintf("%s",prompt);

			 }
	 }


static int doopen (int argc, char *argv[], void *p)
   {
     int control =-1 ;
     int resp;

     struct ftpcli  *ftp;
     struct session *sp;
     struct sockaddr_in *fsocket;

     ftp = (struct ftpcli *)p;
     sp = (struct session *)ftp->session;
     fsocket = (struct sockaddr_in *)ftp->fsocket;

     if((ftp->control != -1) && (ftp->control != -2))
	   {
	    tprintf("You must close the current connection before opening another\n");
	    return 200;
	   }

	if(strcmp(sp->name,dummyhost)==0)
	   {
	     free(sp->name);
	     sp->name=strdup (argv[1]);
	   }

	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);
		free(sp->name);
		sp->name=strdup(dummyhost);
		return 200;
	}

	/* 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);
            free(sp->name);
		sp->name=strdup(dummyhost);
		return 1;
	}

	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(struct sockaddr_in)) == -1)
	   {
	    tprintf ("FTP Session %u unable to conect to %s\n",(unsigned)(sp-Sessions),
			  sp->name);
	    ftp->control=ftp->data=NOTCONNECTED;
	    return 200;
	   }

	tprintf("FTP session %u connected to %s\n",(unsigned)(sp-Sessions),
		sp->name);

	/* Wait for greeting from server */
	resp = getresp(ftp,200);
	return resp;
   }


static int doclose ( int argc, char *argv[], void *p)
   {
	struct ftpcli *ftp;
	ftp=(struct ftpcli*)p;
	if((ftp->control == -2) || (ftp->control == -1))
	   {
	    tprintf("You must open a connection before attempting to close it\n");
	    return 200;
	   }

	usprintf(ftp->control,"QUIT\n");
	getresp(ftp,200);	/* Get the closing message */
	getresp(ftp,200);	/* Wait for the server to close */

	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);

	ftp->control=ftp->data=NOTCONNECTED;
	free(ftp->session->name);
	ftp->session->name=strdup(dummyhost);
	set_default_opts (ftp);				/* reset defaults					*/
	return 200;
   }

static void init_ftpstruct(struct session *sp,struct ftpcli *ftp,
				  struct sockaddr_in *fsocket)
   {
     memset((char *)ftp,0,sizeof(struct ftpcli));
     ftp->control = ftp->data = NOTCONNECTED;
     sp->cb.ftp = ftp;	/* Downward link */
     ftp->session = sp;	/* Upward link */
     ftp->fsocket = fsocket;  /* to help with doopen */
     fsocket->sin_family = AF_INET;
     set_default_opts (ftp);
   }


/****************************************************************************
*	set_default_opts														*
*	Set session default options from persistent defaults set by "ftpopt".	*
****************************************************************************/

static void set_default_opts (ftp)
struct ftpcli *ftp;
	{
	ftp->verbose = verbose;
	ftp->typesent = 0;
	ftp->type = type;
	ftp->logbsize = logbsize;
	ftp->wrap = wrap;
	ftp->session->flowmode = pager;
	}	/* static void set_default_opts (ftp) */
