/* added "dele" for file deletion.  It was already available in
frpserv.c, performed general cleanup, added loop in login process, then
added help to escape from a problem login. 9/2/91 - K5JB */
/* FTP client (interactive user) code */
#define LINELEN 128     /* Length of command buffer */
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "global.h"
#include "mbuf.h"
#include "netuser.h"
#include "icmp.h"
#include "timer.h"
#include "tcp.h"
#include "ftp.h"
#include "session.h"
#include "cmdparse.h"
#include "telnet.h"
#include "iface.h"

#include "finger.h"



extern struct session *current;
extern char nospace[];
extern char badhost[];
char notsess[] = "Not an FTP session!\n";
char cantwrite[] = "Can't write %s\n";
char cantread[] = "Can't read %s\n";
char helpesc[] = "(Escape this with \"close\" from command mode.)\n";
char ftpprompt[] = "ftp> ";
int donothing(),doftpcd(),dolist(),doget(),dols(),doput(),dotype(),doabort(),
        domkdir(),dormdir(),dodele(), dohash(), dobell(), doftphelp();

struct cmds ftpabort[] = {
{	"",       donothing, 0, NULLCHAR,   NULLCHAR },
{	"abort",  doabort,   0, NULLCHAR,   NULLCHAR },
{	NULLCHAR, NULLFP,    0, "Only valid command is \"abort\"", NULLCHAR }
};

struct cmds ftpcmds[] = {
{	"",         donothing,  0, NULLCHAR,               NULLCHAR },
{	"?",        doftphelp,  0, NULLCHAR,               NULLCHAR },
{	"bell",     dobell,     0, NULLCHAR,               NULLCHAR },
{	"cd",       doftpcd,    2, "cd <directory>",       NULLCHAR },
{	"dir",      dolist,     0, NULLCHAR,               NULLCHAR },
{	"get",      doget,      2, "get remotefile <localfile>",   NULLCHAR },
{	"hash",     dohash,     0, NULLCHAR,               NULLCHAR },
{	"help",     doftphelp,  0, NULLCHAR,               NULLCHAR },
{	"ls",       dols,       0, NULLCHAR,               NULLCHAR },
{	"list",     dolist,     0, NULLCHAR,               NULLCHAR },
{	"mkdir",    domkdir,    2, "mkdir <directory>",    NULLCHAR },
{	"nlst",     dols,       0, NULLCHAR,               NULLCHAR },
{	"rmdir",    dormdir,    2, "rmdir <directory>",    NULLCHAR },
{	"put",      doput,      2, "put localfile <remotefile>",   NULLCHAR },
{	"type",     dotype,     0, NULLCHAR,               NULLCHAR },
{	"dele",     dodele,     2, "delete remotefile",    NULLCHAR },
{	NULLCHAR,   NULLFP,	0,		NULLCHAR,	NULLCHAR }
};

int
doftphelp()
{
	printf( "ftp commands: \n" );
	printf( "\t?\t\tbell\t\tcd\t\tdele\n" );
	printf( "\tdir\t\tget\t\thash\t\thelp\n");
	printf( "\tls\t\tlist\t\tmkdir\t\tnlst\n");
	printf( "\trmdir\t\tput\t\ttype\n\n");
	printf( "\"abort\" will abort an in-progress file transfer.\n" );
	printf( "remote servers usually accept \"quit\" to end connection.\n"); 
	printf( "your escape char will allow return to net> prompt.\n\n" );
	return(0);
}

int
dohash()
{
 	if(current->cb.ftp->hashbell & FTPHASH) {
 		current->cb.ftp->hashbell &= ~FTPHASH; 
		printf( "HASH MARK (#) on 1k boundaries is OFF\n"); 
	} else {	
 		current->cb.ftp->hashbell |=  FTPHASH;
		printf( "HASH MARK (#) on 1k boundaries is ON\n"); 
	}
	return(0);
}

int
dobell()
{
 	if(current->cb.ftp->hashbell & FTPBELL) {
 		current->cb.ftp->hashbell &= ~FTPBELL; 
		printf( "BEEP at end of transfer is OFF\n"); 
	} else {	
 		current->cb.ftp->hashbell |=  FTPBELL;
		printf( "BEEP at end of tranfer is ON\n"); 
	}
	return(0);
}


/* Send a message on the control channel */
/*VARARGS*/
static int
sndftpmsg(ftp,fmt,arg)
struct ftp *ftp;
char *fmt;
char *arg;
{
	struct mbuf *bp;
	int16 len;

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	len = strlen(fmt) + strlen(arg) + 10;   /* fudge factor */
	if((bp = alloc_mbuf(len)) == NULLBUF){
		printf(nospace);
		return 1;
	}
	sprintf(bp->data,fmt,arg);
	bp->cnt = strlen(bp->data);
	send_tcp(ftp->control,bp);
	return 0;
}


/* Parse user FTP commands */
static int
ftpparse(line,len)
char *line;
int16 len;
{
	struct mbuf *bp;
	int cmdparse();

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	line[len] = '\0';	/* oops, was left out in couple of places. Seems all
								routines need this, or won't hurt - K5JB */

	switch(current->cb.ftp->state){
		case RECEIVING_STATE:
		case SENDING_STATE:
			/* The only command allowed in data transfer state is ABORT */
			if(cmdparse(ftpabort,line) == -1){
				printf("Transfer in progress; only ABORT is acceptable\n");
			}
			fflush(stdout);
			break;
		case COMMAND_STATE:
			/* Save it now because cmdparse modifies the original */
			bp = qdata(line,len);

			if(cmdparse(ftpcmds,line) == -1){
				/* Send it direct */
				if(bp != NULLBUF)
					send_tcp(current->cb.ftp->control,bp);
				else
					printf(nospace);
			} else {
				free_p(bp);
			}
			printf( ftpprompt );
			fflush(stdout);
			break;
		case STARTUP_STATE:             /* Starting up autologin */
			printf("%sNot connected yet, ignoring: %s",helpesc,line);
			fflush(stdout);
			break;
		case USER_STATE:                /* Got the user name */
			printf(ftpprompt); 
			return sndftpmsg(current->cb.ftp,"USER %s",line);
		case PASS_STATE:                /* Got the password */
			printf(ftpprompt);
			return sndftpmsg(current->cb.ftp,"PASS %s",line);
	}
	return(0);	/* satisfy the compiler */
}

/* Handle top-level FTP command */
int
doftp(argc,argv)
int argc;
char *argv[];
{
	int32 resolve();
	int atoi(),go();
	char *inet_ntoa();
	void ftpccr(),ftpccs(),cooked();
	struct session *s;
	struct ftp *ftp,*ftp_create();
	struct tcb *tcb;
	struct socket lsocket,fsocket;

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	lsocket.address = ip_addr;
	lsocket.port = lport++;
	if((fsocket.address = resolve(argv[1])) == 0){
		printf(badhost,argv[1]);
		return 1;
	}
	if(argc < 3)
		fsocket.port = FTP_PORT;
	else
		fsocket.port = atoi(argv[2]);

	/* Allocate a session control block */
	if((s = newsession()) == NULLSESSION){
		printf("Too many sessions\n");
		return 1;
	}
	current = s;
	if((s->name = malloc((unsigned)strlen(argv[1])+1)) != NULLCHAR)
		strcpy(s->name,argv[1]);
	s->type = FTP;
	s->parse = ftpparse;

	/* Allocate an FTP control block */
	if((ftp = ftp_create(LINELEN)) == NULLFTP){
		s->type = FREE;
		printf(nospace);
		return 1;
	}
	ftp->hashbell = 0;
	ftp->state = STARTUP_STATE;
	cooked();	/* try this here instead of where it was */
	s->cb.ftp = ftp;        /* Downward link */
	ftp->session = s;       /* Upward link */

	/* Now open the control connection */
	tcb = open_tcp(&lsocket,&fsocket,TCP_ACTIVE,
			0,ftpccr,NULLVFP,ftpccs,0,(int *)ftp);
	ftp->control = tcb;
	go();
	return 0;
}

/* Handle null line to avoid trapping on first command in table */
int
donothing()


{
#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif

	return(0);
}
/* Translate 'cd' to 'cwd' for convenience */
int
doftpcd(argc,argv)
int argc;
char *argv[];
{
	register struct ftp *ftp;
	argc=argc;
#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	ftp = current->cb.ftp;
	return sndftpmsg(ftp,"CWD %s\r\n",argv[1]);
}
/* Translate 'mkdir' to 'xmkd' for convenience */
int
domkdir(argc,argv)
int argc;
char *argv[];
{
	register struct ftp *ftp;
	argc=argc;
#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	ftp = current->cb.ftp;
	return sndftpmsg(ftp,"XMKD %s\r\n",argv[1]);
}
/* Translate 'rmdir' to 'xrmd' for convenience */
int
dormdir(argc,argv)
int argc;
char *argv[];
{
	register struct ftp *ftp;
	argc=argc;
#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	ftp = current->cb.ftp;
	return sndftpmsg(ftp,"XRMD %s\r\n",argv[1]);
}
/* delete file, missing for some reason - K5JB */
int
dodele(argc,argv)
int argc;
char *argv[];
{
	register struct ftp *ftp;
	argc=argc;
#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	ftp = current->cb.ftp;
	return sndftpmsg(ftp,"DELE %s\r\n",argv[1]);
}
/* Handle "type" command from user */
int
dotype(argc,argv)
int argc;
char *argv[];
{
	register struct ftp *ftp;

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	ftp = current->cb.ftp;
	if(argc < 2){
		switch(ftp->type){
			case IMAGE_TYPE:
				printf("Image\n%s",ftpprompt);
				break;
			case ASCII_TYPE:
				printf("Ascii\n%s",ftpprompt);
				break;
			}
		return 0;
	}
	switch(*argv[1]){
		case 'i':
		case 'b':
			ftp->type = IMAGE_TYPE;
			sndftpmsg(ftp,"TYPE I\r\n");
			break;
		case 'a':
			ftp->type = ASCII_TYPE;
			sndftpmsg(ftp,"TYPE A\r\n");
			break;
		case 'l':
			ftp->type = IMAGE_TYPE;
			sndftpmsg(ftp,"TYPE L %s\r\n",argv[2]);
			break;
		default:
			printf("Invalid type %s\n%s",argv[1], ftpprompt);
			return 1;
	}
	return 0;
}

/* create data port, and send PORT message */
static void
ftpsetup(ftp,recv,send,state)
struct ftp *ftp;
void (*send)();
void (*recv)();
void (*state)();
{
	struct socket lsocket;
	struct mbuf *bp;
	int time();
#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	lsocket.address = ip_addr;
	lsocket.port = lport++;

	/* Compose and send PORT a,a,a,a,p,p message */

	if((bp = alloc_mbuf(35)) == NULLBUF){   /* 5 more than worst case */
		printf(nospace);
		return;
	}
	/* I know, this looks gross, but it works! */
	sprintf(bp->data,"PORT %u,%u,%u,%u,%u,%u\r\n",
		hibyte(hiword(lsocket.address)),
		lobyte(hiword(lsocket.address)),
		hibyte(loword(lsocket.address)),
		lobyte(loword(lsocket.address)),
		hibyte(lsocket.port),
		lobyte(lsocket.port));
	bp->cnt = strlen(bp->data);
	send_tcp(ftp->control,bp);

	ftp->databytes = 0;
	ftp->lasthash = 0;
	ftp->starttime = time(NULL);

	/* Post a listen on the data connection */
	ftp->data = open_tcp(&lsocket,NULLSOCK,TCP_PASSIVE,0,
		recv,send,state,0,(int *)ftp);
}

/* FTP Client Data channel State change upcall handler */
static void
ftpcds(tcb,old,new)
struct tcb *tcb;
char old,new;
{
	struct ftp *ftp;
	int time();
	old=old;
#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	if((ftp = (struct ftp *)tcb->user) == NULLFTP){
		/* Unknown connection, kill it */
		close_tcp(tcb);
		return;
	}
	switch(new){
		case FINWAIT2:
		case TIME_WAIT:
			if(ftp->state == SENDING_STATE){
				/* We've received an ack of our FIN, so
				 * return to command mode
				 */
				ftp->state = COMMAND_STATE;
				if(current != NULLSESSION && current->cb.ftp == ftp){
					printf("Put complete, %lu bytes sent in %d secs\n",
						tcb->snd.una - tcb->iss - 2, (time(NULL)) - ftp->starttime );
					if( ftp->hashbell & FTPBELL ) printf( "\a" );
					if( ftp->hashbell & FTPHASH ) printf( "\n" );
					printf(ftpprompt);
					fflush(stdout);
				}
			}
			break;
		case CLOSE_WAIT:
			close_tcp(tcb);
			if(ftp->state == RECEIVING_STATE){
				/* End of file received on incoming file */
#ifdef  CPM
				if(ftp->type == ASCII_TYPE)
					putc(CTLZ,ftp->fp);
#endif
				if(ftp->fp != stdout)
					fclose(ftp->fp);
				ftp->fp = NULLFILE;
				ftp->state = COMMAND_STATE;
				if(current != NULLSESSION && current->cb.ftp == ftp){
					printf("Get complete, %lu bytes received in %d secs\n",
						tcb->rcv.nxt - tcb->irs - 2, (time(NULL)) - ftp->starttime );
					if( ftp->hashbell & FTPBELL ) printf( "\a" );
					if( ftp->hashbell & FTPHASH ) printf( "\n" );
					printf(ftpprompt);
					fflush(stdout);
				}
			}
			break;
		case CLOSED:
			ftp->data = NULLTCB;
			del_tcp(tcb);
			break;
	}
}
/* Start receive transfer. Syntax: get <remote name> [<local name>] */
int
doget(argc,argv)
int argc;
char *argv[];
{
	void ftpdr();
	char *remotename,*localname;
	register struct ftp *ftp;
	char *mode;

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	ftp = current->cb.ftp;
	if(ftp == NULLFTP){
		printf(notsess);
		return 1;
	}
	remotename = argv[1];
	if(argc < 3)
		localname = remotename;
	else
		localname = argv[2];

	if(ftp->fp != NULLFILE && ftp->fp != stdout)
		fclose(ftp->fp);
	ftp->fp = NULLFILE;

	if(ftp->type == IMAGE_TYPE)
		mode = binmode[WRITE_BINARY];
	else
		mode = "w";

	if((ftp->fp = fopen(localname,mode)) == NULLFILE){
		printf(cantwrite,localname);
		return 1;
	}
	ftp->state = RECEIVING_STATE;
	ftpsetup(ftp,ftpdr,NULLVFP,ftpcds);

        /* Generate the command to start the transfer */
	return sndftpmsg(ftp,"RETR %s\r\n",remotename);
}
/* List remote directory. Syntax: dir <remote directory/file> [<local name>] */
int
dolist(argc,argv)
int argc;
char *argv[];
{
	void ftpdr();
	register struct ftp *ftp;

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	ftp = current->cb.ftp;
	if(ftp == NULLFTP){
		printf(notsess);
		return 1;
	}
	if(ftp->fp != NULLFILE && ftp->fp != stdout)
		fclose(ftp->fp);
	ftp->fp = NULLFILE;

	if(argc < 3){
		ftp->fp = stdout;
	} else if((ftp->fp = fopen(argv[2],"w")) == NULLFILE){
		printf(cantwrite,argv[2]);
		return 1;
	}
	ftp->state = RECEIVING_STATE;
	ftpsetup(ftp,ftpdr,NULLVFP,ftpcds);
        /* Generate the command to start the transfer
         * It's done this way to avoid confusing the 4.2 FTP server
         * if there's no argument
         */
	if(argc > 1)
		return sndftpmsg(ftp,"LIST %s\r\n",argv[1]);
	else
		return sndftpmsg(ftp,"LIST\r\n","");
}
/* Abbreviated (name only) list of remote directory.
 * Syntax: ls <remote directory/file> [<local name>]
 */
int
dols(argc,argv)
int argc;
char *argv[];
{
	void ftpdr();
	register struct ftp *ftp;

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	ftp = current->cb.ftp;
	if(ftp == NULLFTP){
		printf(notsess);
		return 1;
	}
	if(ftp->fp != NULLFILE && ftp->fp != stdout)
		fclose(ftp->fp);
	ftp->fp = NULLFILE;

	if(argc < 3){
		ftp->fp = stdout;
	} else if((ftp->fp = fopen(argv[2],"w")) == NULLFILE){
		printf(cantwrite,argv[2]);
		return 1;
	}
	ftp->state = RECEIVING_STATE;
	ftpsetup(ftp,ftpdr,NULLVFP,ftpcds);
        /* Generate the command to start the transfer */
	if(argc > 1)
		return sndftpmsg(ftp,"NLST %s\r\n",argv[1]);
	else
		return sndftpmsg(ftp,"NLST\r\n","");
}
/* Start transmit. Syntax: put <local name> [<remote name>] */
int
doput(argc,argv)
int argc;
char *argv[];
{
	void ftpdt();
	char *remotename,*localname;
	char *mode;
	struct ftp *ftp;

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	if((ftp = current->cb.ftp) == NULLFTP){
		printf(notsess);
		return 1;
	}
	localname = argv[1];
	if(argc < 3)
		remotename = localname;
	else
		remotename = argv[2];

	if(ftp->fp != NULLFILE && ftp->fp != stdout)
		fclose(ftp->fp);

	if(ftp->type == IMAGE_TYPE)
		mode = binmode[READ_BINARY];
	else
		mode = "r";

	if((ftp->fp = fopen(localname,mode)) == NULLFILE){
		printf(cantread,localname);
		return 1;
	}
	ftp->state = SENDING_STATE;
	ftpsetup(ftp,NULLVFP,ftpdt,ftpcds);

        /* Generate the command to start the transfer */
	return sndftpmsg(ftp,"STOR %s\r\n",remotename);
}
/* Abort a GET or PUT operation in progress. Note: this will leave
 * the partial file on the local or remote system
 */
int
doabort()


{
	register struct ftp *ftp;

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	ftp = current->cb.ftp;

	/* Close the local file */
	if(ftp->fp != NULLFILE && ftp->fp != stdout)
		fclose(ftp->fp);
	ftp->fp = NULLFILE;

	switch(ftp->state){
		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.
			 */
			close_tcp(ftp->data);
			printf("Put aborted\n%s",ftpprompt);
			break;
		case RECEIVING_STATE:
			/* Just exterminate the data channel TCB; this will
			 * generate a RST on the next data packet which will
			 * abort the sender
			 */
			del_tcp(ftp->data);
			ftp->data = NULLTCB;
			printf("Get aborted\n%s",ftpprompt);
			break;
		}
	ftp->state = COMMAND_STATE;
	fflush(stdout);
	return(0);
}
/* FTP Client Control channel Receiver upcall routine */
void
ftpccr(tcb,cnt)
register struct tcb *tcb;
int16 cnt;
{
	struct mbuf *bp;
	struct ftp *ftp;
	void doreply();
	char c;

#ifdef COHPROF
profile(__LINE__,__FILE__);
#endif
	if((ftp = (struct ftp *)tcb->user) == NULLFTP){
	/* Unknown connection; kill it */
		close_tcp(tcb);
		return;
	}
	/* Hold output if we're not the current session */
	if(mode != CONV_MODE || current == NULLSESSION || current->cb.ftp != ftp)
		return;

	if(recv_tcp(tcb,&bp,cnt) > 0){
		while(pullup(&bp,&c,1) == 1){
			switch(c){
				case '\r':      /* Strip cr's */
					continue;
				case '\n':      /* Complete line; process it */
					ftp->buf[ftp->cnt] = '\0';
					doreply(ftp);
					ftp->cnt = 0;
					break;
				default:        /* Assemble line */
					if(ftp->cnt != LINELEN-1)
						ftp->buf[ftp->cnt++] = c;
					break;
			}
		}
		fflush(stdout);
	}
}


/* Process replies from the server */
void
doreply(ftp)
register struct ftp *ftp;
{
	void noecho(),echo();

	fwrite(ftp->buf,1,(unsigned)ftp->cnt,stdout);
	fputc('\n', stdout);
	if (ftp->cnt < 3) return;
	ftp->buf[3] = '\0';
	switch(ftp->state){
		case SENDING_STATE:
		case RECEIVING_STATE:
			if (ftp->buf[0] == '5')
				doabort();
			return;
			break;
		case STARTUP_STATE:
			if (!strcmp(ftp->buf, "220")){
				ftp->state = USER_STATE;
				printf("Enter user name: ");
				fflush(stdout);
				return;
			} else  {
				ftp->state = COMMAND_STATE;
			}
			break;
		case USER_STATE:	/* revised this part to loop the login - K5JB */
			if (!strcmp(ftp->buf, "331")) {
				ftp->state = PASS_STATE;
				noecho();
				printf("Password: ");
				fflush(stdout);
				return;
			} else
				ftp->state = COMMAND_STATE;
				break;
		case PASS_STATE:
			echo();
			if (!strcmp(ftp->buf, "550")){
				ftp->state = USER_STATE;
				printf("%sTry your user name again: ",helpesc);
				fflush(stdout);
				return;
			}else
					ftp->state = COMMAND_STATE;
			break;
	}
	printf(ftpprompt);
	fflush(stdout);
}

/* FTP Client Control channel State change upcall routine */
void
ftpccs(tcb,old,new)
register struct tcb *tcb;
char old,new;
{
	void ftp_delete();
	struct ftp *ftp;
	char notify = 0;
	extern char *tcpstates[];
	extern char *reasons[];
	extern char *unreach[];
	extern char *exceed[];
	int cmdmode();
	old=old;
	/* Can't add a check for unknown connection here, it would loop
	 * on a close upcall! We're just careful later on.
	 */
	ftp = (struct ftp *)tcb->user;

	if(current != NULLSESSION && current->cb.ftp == ftp)
		notify = 1;

	switch(new){
		case CLOSE_WAIT:
			if(notify)
				printf("%s\n",tcpstates[(int)new]);
			close_tcp(tcb);
			break;
		case CLOSED:    /* heh heh */
			if(notify){
				printf("%s (%s",tcpstates[(int)new],reasons[(int)tcb->reason]);
				if(tcb->reason == NETWORK){
					switch(tcb->type){
						case DEST_UNREACH:
							printf(": %s unreachable\n",unreach[(int)tcb->code]);
							break;
						case TIME_EXCEED:
							printf(": %s time exceeded",exceed[(int)tcb->code]);
							break;
					}
				}
				printf(")\n");
				cmdmode();
			}
			del_tcp(tcb);
			if(ftp != NULLFTP)
				ftp_delete(ftp);
			break;
		default:
			if(notify)
				printf("%s\n",tcpstates[(int)new]);
			break;
	}
	if(notify)
	fflush(stdout);
}
