/* FTP Server state machine - see RFC 959 */

#define	LINELEN		128	/* Length of command buffer */

#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "netuser.h"
#include "timer.h"
#include "tcp.h"
#include "ftp.h"

/* Command table */
static char *commands[] = {
	"user",
#define	USER_CMD	0
	"acct",
#define	ACCT_CMD	1
	"pass",
#define	PASS_CMD	2
	"type",
#define	TYPE_CMD	3
	"list",
#define	LIST_CMD	4
	"cwd",
#define	CWD_CMD		5
	"dele",
#define	DELE_CMD	6
	"name",
#define	NAME_CMD	7
	"quit",
#define	QUIT_CMD	8
	"retr",
#define	RETR_CMD	9
	"stor",
#define	STOR_CMD	10
	"port",
#define	PORT_CMD	11
	"nlst",
#define	NLST_CMD	12
	"pwd",
#define	PWD_CMD		13
	"xpwd",			/* For compatibility with 4.2BSD */
#define	XPWD_CMD	14
	"mkd ",
#define	MKD_CMD		15
	"xmkd",			/* For compatibility with 4.2BSD */
#define	XMKD_CMD	16
	"xrmd",			/* For compatibility with 4.2BSD */
#define	XRMD_CMD	17
	"rmd ",
#define	RMD_CMD		18
	NULLCHAR
};

/* Response messages */
static char banner[] = "220 %s FTP version %s ready at %s\r\n";
static char badcmd[] = "500 Unknown command\r\n";
static char givepass[] = "331 Enter PASS command\r\n";
static char logged[] = "230 Logged in\r\n";
static char typeok[] = "200 Type OK\r\n";
static char deleok[] = "250 File deleted\r\n";
static char mkdok[] = "200 MKD ok\r\n";
static char delefail[] = "550 Delete failed\r\n";
static char pwdmsg[] = "257 \"%s\" is current directory\r\n";
static char badtype[] = "501 Unknown type \"%s\"\r\n";
static char badport[] = "501 Bad port syntax\r\n";
static char unimp[] = "502 Command not yet implemented\r\n";
static char bye[] = "221 Goodbye!\r\n";
static char nodir[] = "553 Can't read directory \"%s\"\r\n";
static char cantopen[] = "550 Can't read file \"%s\"\r\n";
static char sending[] = "150 Opening data connection for %s %s\r\n";
static char cantmake[] = "553 Can't create \"%s\"\r\n";
static char portok[] = "200 Port command okay\r\n";
static char rxok[] = "226 File received OK\r\n";
static char txok[] = "226 File sent OK\r\n";
static char noperm[] = "550 Permission denied\r\n";
static char noconn[] = "425 Data connection refused\r\n";
static char notlog[] = "530 Please log in with USER and PASS\r\n";

static struct tcb *ftp_tcb;

/* Start up FTP service */
ftp_start(argc,argv)
int argc;
char *argv[];
{
	struct socket lsocket;
	void ftpscr(),ftpscs();

	lsocket.address = ip_addr;
	if(argc < 2)
		lsocket.port = FTP_PORT;
	else
		lsocket.port = atoi(argv[1]);

	ftp_tcb = open_tcp(&lsocket,NULLSOCK,TCP_SERVER,0,ftpscr,NULLVFP,ftpscs,0,(char *)NULL);
}
ftp_stop()
{
	if(ftp_tcb != NULLTCB)
		close_tcp(ftp_tcb);
}
/* FTP Server Control channel State change upcall handler */
static
void
ftpscs(tcb,old,new)
struct tcb *tcb;
char old,new;
{
	extern char hostname[],version[];
	struct ftp *ftp,*ftp_create();
	void ftp_delete();
	char *inet_ntoa();
	long t;
	char *cp,*cp1;

	switch(new){
/* Setting QUICKSTART piggybacks the server's banner on the SYN/ACK segment;
 * leaving it unset waits for the three-way handshake to complete before
 * sending the banner. Piggybacking unfortunately breaks some old TCPs,
 * so its use is not (yet) recommended.
*/
#ifdef	QUICKSTART
	case SYN_RECEIVED:
#else
	case ESTABLISHED:
#endif
		if((ftp = ftp_create(LINELEN)) == NULLFTP){
			/* No space, kill connection */
			close_tcp(tcb);
			return;
		}
		ftp->control = tcb;		/* Downward link */
		tcb->user = (char *)ftp;	/* Upward link */

		/* Set default data port */
		ftp->port.address = tcb->conn.remote.address;
		ftp->port.port = FTPD_PORT;

		/* Note current directory */
		log(tcb,"open FTP");
		time(&t);
		cp = ctime(&t);
		if((cp1 = index(cp,'\n')) != NULLCHAR)
			*cp1 = '\0';
		tprintf(ftp->control,banner,hostname,version,cp);
		break;		
	case CLOSE_WAIT:
		close_tcp(tcb);
		break;
	case CLOSED:
		log(tcb,"close FTP");
		if((ftp = (struct ftp *)tcb->user) != NULLFTP)
			ftp_delete(ftp);
		/* Check if server is being shut down */
		if(tcb == ftp_tcb)
			ftp_tcb = NULLTCB;
		del_tcp(tcb);
		break;
	}
}

/* FTP Server Control channel Receiver upcall handler */
static
void
ftpscr(tcb,cnt)
struct tcb *tcb;
int16 cnt;
{
	register struct ftp *ftp;
	char c;
	struct mbuf *bp;
	void ftpcommand();

	if((ftp = (struct ftp *)tcb->user) == NULLFTP){
		/* Unknown connection, just kill it */
		close_tcp(tcb);
		return;
	}
	switch(ftp->state){
	case COMMAND_STATE:
		/* Assemble an input line in the session buffer. Return if incomplete */
		recv_tcp(tcb,&bp,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';
				ftpcommand(ftp);
				ftp->cnt = 0;
				break;
			default:	/* Assemble line */
				if(ftp->cnt != LINELEN-1)
					ftp->buf[ftp->cnt++] = c;
				break;
			}
		}
		/* else no linefeed present yet to terminate command */
		break;
	case SENDING_STATE:
	case RECEIVING_STATE:
		/* Leave commands pending on receive queue tntil
		 * present command is done
		 */
		break;
	}
}

/* FTP server data channel connection state change upcall handler */
void
ftpsds(tcb,old,new)
struct tcb *tcb;
char ol
char ol
char ol
char ol
char ol	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\n",
					tcb->rcv.nxt - tcb->irs - 2);
				fflush(stdout);
			}
		}
		break;
	case CLOSED:
		ftp->data = NULLTCB;
		del_tcp(tcb);
		break;
	}
}
/* 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;

	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;
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  otlog);
			return;
		}
	}
	arg = &cmd[strlen(*cmdp)];
	while(*arg == ' ')
		arg++;

	/* Execute specific command */
	switch(cmdp-commands){
	case USER_CMD:
		if((ftp->username = malloc((unsigned)strlen(arg)+1)) == NULLCHAR){
			close_tcp(ftp->control);
			break;
		}
		strcpy(ftp->username,arg);
		tprintf(ftp->control,givepass);
		break;
	case TYPE_CMD:
		switch(arg[0]){
		case 'A':
		case 'a':	/* Ascii */
			ftp->type = ASCII_TYPE;
			tprintf(ftp->control,typeok);
			break;
		case 'B':
		case 'b':	/* Binary */
		case 'I':
		case 'i':	/* Image */
			ftp->type = IMAGE_TYPE;
			tprintf(ftp->control,typeok);
			break;
		default:	/* Invalid */
			tprintf(ftp->control,badtype,arg);
			break;
		}
		break;
	case QUIT_CMD:
		tprintf(ftp->control,bye);
		close_tcp(ftp->control);
		break;
	case RETR_CMD:
		/* Disk operation; return ACK now */
		tcp_output(ftp->control);
		file = pathname(ftp->cd,arg);
		if(ftp->type == IMAGE_TYPE)
			mode = binmode[READ_BINARY];
		else
			mode = "r";
		if(!permcheck(ftp,RETR_CMD,file)){
		 	tprintf(ftp->control,noperm);
		} else if((ftp->fp = fopen(file,mode)) == NULLFILE){
			tprintf(ftp->control,cantopen,file);
		} else {
			log(ftp->control,"RETR %s\\%s",ftp->cd,arg); /* DG2KK: was "7" */
			dport.address = ip_addr;
			dport.port = FTPD_PORT;
			ftp->state = SENDING_STATE;
			tprintf(ftp->control,sending,"RETR",arg);
			ftp->data = open_tcp(&dport,&ftp->port,TCP_ACTIVE,
			 0,NULLVFP,ftpdt,ftpsds,ftp->control->tos,(char *)ftp);
		}
		free(file);
		break;
	case STOR_CMD:
		/* Disk operation; return ACK now */
		tcp_output(ftp->control);
		file = pathname(ftp->cd,arg);
		if(ftp->type == IMAGE_TYPE)
			mode = binmode[WRITE_BINARY];
		else
			mode = "w";
		if(!permcheck(ftp,STOR_CMD,file)){
		 	tprintf(ftp->control,noperm);
			free(file);
		 	break;
		} else if((ftp->fp = fopen(file,mode)) == NULLFILE){
			tprintf(ftp->control,cantmake,file);
		} else {
			log(ftp->control,"STOR %s\\%s",ftp->cd,arg); /* DG2KK */
			dport.address = ip_addr;
			dport.port = FTPD_PORT;
			ftp->state = RECEIVING_STATE;
			tprintf(ftp->control,sending,"STOR",arg);
			ftp->data = open_tcp(&dport,&ftp->port,TCP_ACTIVE,
			 0,ftpdr,NULLVFP,ftpsds,ftp->control->tos,(char *)ftp);
		}
		free(file);
		break;
	case PORT_CMD:
		if(pport(&ftp->port,arg) == -1){
			tprintf(ftp->control,badport);
		} else {
			tprintf(ftp->control,portok);
		}
		break;
#ifndef CPM
	case LIST_CMD:
		/* Disk operation; return ACK now */
		tcp_output(ftp->control);

		file = pathname(ftp->cd,arg);
		if(!permcheck(ftp,RETR_CMD,file)){
		 	tprintf(ftp->control,noperm);
		} else if((ftp->fp = dir(file,1)) == NULLFILE){
			tprintf(ftp->control,nodir,file);
		} else {
			dport.address = ip_addr;
			dport.port = FTPD_PORT;
			ftp->state = SENDING_STATE;
			tprintf(ftp->control,sending,"LIST",file);
			ftp->data = open_tcp(&dport,&ftp->port,TCP_ACTIVE,
			 0,NULLVFP,ftpdt,ftpsds,ftp->control->tos,(char *)ftp);
		}
		free(file);
		break;
	case NLST_CMD:
		/* Disk operation; return ACK now */
		tcp_output(ftp->control);

		file = pathname(ftp->cd,arg);
		if(!permcheck(ftp,RETR_CMD,file)){
		 	tprintf(ftp->control,noperm);
		} else if((ftp->fp = dir(file,0)) == NULLFILE){
			tprintf(ftp->control,nodir,file);
		} else {
			dport.address = ip_addr;
			dport.port = FTPD_PORT;
			ftp->state = SENDING_STATE;
			tprintf(ftp->control,sending,"NLST",file);
			ftp->data = open_tcp(&dport,&ftp->port,TCP_ACTIVE,
			 0,NULLVFP,ftpdt,ftpsds,ftp->control->tos,(char *)ftp);
		}
		free(file);
		break;
	case CWD_CMD:
		tcp_output(ftp->control);	/* Disk operation; return ACK now */

		file = pathname(ftp->cd,arg);
		if(!permcheck(ftp,RETR_CMD,file)){
		 	tprintf(ftp->control,noperm);
			free(file);
#if ( MSDOS || ATARI_ST )
		/* Don'tcha just LOVE %%$#@!! MS-DOS? */
		} else if(strcmp(file,"\\") == 0 || access(file,0) == 0){
#else
		} else if(access(file,0) == 0){	/* See if it exists */
#endif
			/* Succeeded, record in control block */
			free(ftp->cd);
			ftp->cd = file;
			tprintf(ftp->control,pwdmsg,file);
		} else {
			/* Failed, don't change anything */
			tprintf(ftp->control,nodir,file);
			free(file);
		}
		break;
	case XPWD_CMD:
	case PWD_CMD:
		tprintf(ftp->control,pwdmsg,ftp->cd);
		break;
#else
	case LIST_CMD:
	case NLST_CMD:
	case CWD_CMD:
	case XPWD_CMD:
	case PWD_CMD:
#endif
	case ACCT_CMD:		
		tprintf(ftp->control,unimp);
		break;
	case DELE_CMD:
		file = pathname(ftp->cd,arg);
		if(!permcheck(ftp,DELE_CMD,file)){
		 	tprintf(ftp->control,noperm);
		} else if(unlink(file) == 0){
			tprintf(ftp->control,deleok);
		} else {
			tprintf(ftp->control,delefail);
		}
		free(file);
		break;
	case PASS_CMD:
		tcp_output(ftp->control);	/* Send the ack now */
		ftplogin(ftp,arg);			
		break;
#ifndef	CPM
	case XMKD_CMD:
	case MKD_CMD:
		file = pathname(ftp->cd,arg);
		if(!permcheck(ftp,MKD_CMD,file)){
			tprintf(ftp->control,noperm);
		} else if(mkdir(file,0777) == 0){
			tprintf(ftp->control,mkdok);
		} else {
			tprintf(ftp->control,cantmake);
		}
		free(file);
		break;
	case XRMD_CMD:
	case RMD_CMD:
		file = pathname(ftp->cd,arg);
		if(!permcheck(ftp,RMD_CMD,file)){
		 	tprintf(ftp->control,noperm);
		} else if(rmdir(file) == 0){
			tprintf(ftp->control,deleok);
		} else {
			tprintf(ftp->control,delefail);
		}
		free(file);
		break;
	}
#endif
}
static
int
pport(sock,arg)
struct socket *sock;
char *arg;
{
	int32 n;
	int atoi(),i;

	n = 0;
	for(i=0;i<4;i++){
		n = atoi(arg) + (n << 8);
		if((arg = index(arg,',')) == NULLCHAR)
			return -1;
		arg++;
	}
	sock->address = n;
	n = atoi(arg);
	if((arg = index(arg,',')) == NULLCHAR)
		return -1;
	arg++;
	n = atoi(arg) + (n << 8);
	sock->port = n;
	return 0;
}
/* Attempt to log in the user whose name is in ftp->username and password
 * in pass
 */
static
ftplogin(ftp,pass)
struct ftp *ftp;
char *pass;
{
	char buf[80],*cp,*cp1;
	FILE *fp;
	int anony = 0;

	if((fp = fopen(userfile,"r")) == NULLFILE){
		/* Userfile doesn't exist */
		tprintf(ftp->control,noperm);
		return;
	}
	while(fgets(buf,sizeof(buf),fp),!feof(fp)){
		if(buf[0] == '#')
			continue;	/* Comment */
		if((cp = index(buf,' ')) == NULLCHAR)
			/* Bogus entry */
			continue;
		*cp++ = '\0';		/* Now points to password */
		if(strcmp(ftp->username,buf) == 0)
			break;		/* Found user name */
	}
	if(feof(fp)){
		/* User name not found in file */
		fclose(fp);
		tprintf(ftp->control,noperm);
		return;
	}
	fclose(fp);
	/* Look for space after password field in file */
	if((cp1 = index(cp,' ')) == NULLCHAR){
		/* Invalid file entry */
		tprintf(ftp->control,noperm);
		return;
	}
	*cp1++ = '\0';	/* Now points to path field */
	if(strcmp(cp,"*") == 0)
		anony = 1;	/* User ID is password-free */
	if(!anony && strcmp(cp,pass) != 0){
		/* Password required, but wrong one given */
		tprintf(ftp->control,noperm);
		return;
	}
	if((cp = index(cp1-' ')) == NULLCHAR){
		/* Permission field missing */
		tprintf(ftp->control,noperm);
		return;
	}
	*cp++ = '\0';	/* now points to permission field */

	/* Set up current directory and path prefix */
	ftp->cd = malloc((unsigned)strlen(cp1)+1);
	ftp->path = malloc((unsigned)strlen(cp1)+1);
	strcpy(ftp->cd,cp1);
	strcpy(ftp->path,cp1);
	
	/* And finally setally setally setally setally set                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                			return 1;
		return 0;
	case DELE_CMD:
	case RMD_CMD:
		/* User must have permission to (over)write files */
		if(ftp->perms & FTP_WRITE)
			return 1;
		return 0;
	case STOR_CMD:
	case MKD_CMD:
		/* User must have permission to (over)write files, or permission
		 * to create them if the file doesn't already exist
		 */
		if(ftp->perms & FTP_WRITE)
			return 1;
#ifdef LATTICE
		/* DG2KK: Lattice's access() doesn't work.
		 */
		fp = fopen(file,"r");
		if(fp == NULLFILE && (ftp->perms & FTP_CREATE)) {
#else
		if(access(file,2) == -1 && (ftp->perms & FTP_CREATE)) {
#endif
			return 1;
		}
#ifdef LATTICE
		fclose(fp);
#endif
		return 0;
	}
	return 0;	/* "can't happen" -- keep lint happy */
}
