/* mftp.c */
/*
o	added MORE -- read ascii file to screen
o	REST
o	APPEND
o	copy date-time on file transfers
p	print 'Connected to %s' after connect?
after PASS, if SYST returns UNIX and unix mode, don't set TYPE
o	after Transfer complete, %d bytes sent/received in %0.2f seconds (%0.1f kB/s)
o	@file executes ftp commands from file
o	save/get user,password from registry based on site
newer remotefile [localfile] = get remotefile if newer than local
REGET means resume (download)
RESTART n means resume preceding put or get at offset n
UMASK [mask] set (or print) the default umask
*/
/* Strip newline from the ! command input */

#include "scon.h"
#include "signal.h"

int txmax;
int do_command(char *cmdline);

char *note[]= {
	"Usage: [-v][-i][-d][-p] ftp.site",
	"-v  Do not print server responses",
	"-i  Do not prompt in multiple commands",
	"-d  Print commands sent to the server",
	"-p  Do not use passive (PASV) transfers",
	"",
	"^C aborts commands",
	0};

#define NLINE (2*MAX_PATH+128)
char curdir[MAX_PATH];
char locdir[MAX_PATH];

char server[MAX_PATH];	// server\tuser\tpassword
char resumecmd[2*MAX_PATH+128];

int bprompt= 1;
int bpasv= 1;
int verbose= 1;
int ascii= 1;
int globbing= 1;
int aborted= 0;
int btty;

FILE *fpat= 0;

enum ecmd {eAmbiguous=1,
eAPPEND,eASCII,eBINARY,eBYE,eCD,eCLOSE,eCMDFILE,eDEBUG,eDELETE,eDIR,
eGET,eGLOB,eHELP,eLCD,eLS,eMDELETE,eMGET,eMKDIR,eMORE,
eMPUT,eNOOP,eOPEN,ePASV,ePROMPT,ePUT,ePWD,eQUOTE,eREMOTEHELP,
eRENAME,eREST,eRMDIR,eRUN,eSTATUS,eSYST,eTYPE,eUSER,eVERBOSE};
struct cmdstr {
	char *name;
	enum ecmd c;
	char *help;
}
 ambiguous= {"", eAmbiguous, 0},
 acmds[]= {
{"!",eRUN, "Run a shell command."},
{"?",eHELP, "Local help information."},
{"@",eCMDFILE, "Get ftp commands from local file @cmds.txt."},
{"append",eAPPEND, "Append local to remote file."},
{"ascii",eASCII, "Use ascii transfers."},
{"binary",eBINARY, "Use binary transfers."},
{"bye",eBYE, "Exit ftp."},
{"cd",eCD, "Change remote directory."},
{"close",eCLOSE, "End ftp connection."},
{"debug",eDEBUG, "Toggle debugging [default off]."},
{"delete",eDELETE, "Delete a remote file."},
{"dir",eDIR, "List remote directory."},
{"disconnect",eCLOSE, "End ftp connection."},
{"get",eGET, "Download a remote file."},
{"glob",eGLOB, "Toggle filename wildcard expansion."},
{"help",eHELP, "Show local help information."},
{"lcd",eLCD, "Change local directory."},
{"literal",eQUOTE, "Send an ftp command."},
{"ls",eLS, "List files in a remote directory."},
{"mdelete",eMDELETE, "Delete multiple."},
{"mget",eMGET, "Download multiple."},
{"mkdir",eMKDIR, "Create remote directory."},
{"more", eMORE, "Display remote file in pages."},
{"mput",eMPUT, "Upload multiple."},
{"noop",eNOOP, "Do nothing."},
{"open",eOPEN, "Connect to ftp site."},
{"passive",ePASV, "Toggle passive transfers [default on]."},
{"pasv",ePASV, "Toggle passive transfers [default on]."},
{"prompt",ePROMPT, "Toggle prompting in multiple commands [default on]."},
{"put",ePUT, "Upload a file."},
{"pwd",ePWD, "Show current remote directory."},
{"quit",eBYE, "Exit ftp."},
{"quote",eQUOTE, "Send an ftp command."},
{"recv",eGET, "Download a file."},
{"reget",eREST, "Continue an interrupted download. "},
{"remotehelp",eREMOTEHELP, "Show help from the ftp server."},
{"rename",eRENAME, "Rename a remote file."},
{"rmdir",eRMDIR, "Remove a remote directory."},
{"send",ePUT, "Upload a file."},
{"status",eSTATUS, "Show the remote status."},
{"system",eSYST, "Show the remote system."},
{"type",eTYPE, "Set the file transfer type."},
{"user",eUSER, "Send user and password."},
{"verbose",eVERBOSE, "Toggle verbose mode [default on]."},
{0,0}};
/*
""Beep when command completed.","List contents of multiple remote directories.",
"Toggle printing `#' for each buffer transferred.","Toggle use of PORT cmd for each data connection.",
"Toggle packet tracing.","Obey command file."
*/

void my_abort(int dummy){
	++aborted;
}
void resignal(){
	aborted= 0;
	signal(SIGINT, my_abort);
}
char *strcatok(char *d, char *s, int nd){
	// safe strcat that does no partial copying
	int z= strlen(d);
	if(nd - z - strlen(s) <= 0)
		return 0;
	strcpy(d+z, s);
	return d;
}
void trim(char *s){
	char *cp= s;
	while(*cp && *cp<=' ')++cp;
	strcpy(s, cp);
	cp= s+strlen(s);
	while(--cp>=s && *cp<=' ')*cp= 0;
}
void prompt(char *pr, char *ans, int n){
	if(fpat && feof(fpat)){
		fclose(fpat);
		fpat= 0;
	}
	if(verbose && btty && 0==fpat){
		printf("%s", pr);
		fflush(stdout);
	}
	fgets(ans, n, fpat? fpat : stdin);
}
int query(char *pr, char *arg){
	char ans[512];
	int v= verbose;

	if(!bprompt || fpat)
		return 1;
	verbose= 1;
	sprintf(ans, pr, arg);
	prompt(ans, ans, sizeof(ans));
	verbose= v;
	trim(ans);
	return ans[0]=='y' || ans[0]=='Y';
}
int chomp(char *word, int nw, char *src){
	char *cp= src;
	int i;

	--nw;
	while(*cp && *cp<=' ')++cp;
	if(*cp=='"'){
		++cp;
		for(i= 0; i<nw && *cp && *cp!='"'; i++)
			word[i]= *cp++;
		if(*cp=='"')
			++cp;
	}else{
		for(i= 0; i<nw && *cp>' '; i++)
			word[i]= *cp++;
	}
	word[i]= 0;
	strcpy(src, cp);
	trim(src);
	return word[0];
}
int main(int ac, char **av){
	WSADATA WsaData;
	char *cp;
	char cmdline[NLINE];
	btty= _isatty(_fileno(stdin));
	bprompt= btty;
	while(cp= *++av)if(*cp++=='-')switch(*cp++){
	case 'v':	verbose= 0; break;
	case 'i':	bprompt= 0; break;
	case 'd':	bdebug= 1; break;
	case 'p':	bpasv= 0; break;
	default:
			for(av= note; *av; ++av)
				fprintf(stderr, "%s\n", *av);
			exit(5);
	}else if(server[0]==0){
		strncpy(server, *av, sizeof(server));
	}else{
		strcatok(server, "\t", sizeof(server));
		strcatok(server, *av, sizeof(server));
	}
	if(WSAStartup(0x0101,&WsaData)){
		printf("Winsock cannot start\n");
		exit(20);
	}
	_getcwd(locdir,sizeof(locdir));
	signal(SIGINT, my_abort);
	if(server[0]){
		sprintf(cmdline, "open %s", server);
		do_command(cmdline);
	}
	do{
		FILE *fout= verbose? stdout : 0;
		resignal();
		flush_control_socket(fout);
		cmdline[0]= 0;
		prompt("ftp> ", cmdline, sizeof(cmdline));
		flush_control_socket(fout);
	}while(do_command(cmdline) && !feof(stdin));
	if(fpat)
		fclose(fpat);
	SocClose(&socCtrl);
	WSACleanup();
	return 0;
}
static struct cmdstr *lookup(char *cmd){
	int i,k= 0;
	char *name, *nnext= acmds[0].name;

	for(i= 0; cmd[i]; i++)
		cmd[i]= tolower(cmd[i]);

	if(i==0)
		return &ambiguous;
	for(k= 0; name= nnext; k++){
		nnext= acmds[k+1].name;
		if(!strncmp(cmd, name, i)){
			if(name[i] && nnext && !strncmp(cmd, nnext, i))
				return &ambiguous;
			break;
		}
	}
	return &acmds[k];
}
void do_open(char *cmdline){
	char fname[MAX_PATH];
	if(cmdline[0]==0)
		strcpy(cmdline, server);
	chomp(fname, sizeof(fname), cmdline);
	if(!fname[0])
		return;
#ifdef WIN32
	if(0==*cmdline)
		key_getvalue(HKEY_CURRENT_USER, "SOFTWARE\\Mftp", fname, cmdline, MAX_PATH);
#endif
	socCtrl= ConnectToServer(fname, 21);
	Sleep(1);
	if(socCtrl==INVALID_SOCKET){
		printf("Connection failed\n");
		return;
	}
	printf("Connected to %s\n", fname);
	if(400<=GetReply(reply, sizeof(reply), verbose? stdout:0)){
		SocClose(&socCtrl);
	}else{
		strcpy(server, fname);	// save server name
		sprintf(fname, "user %s", cmdline);
		do_command(fname);
	}
}
void do_resume(char *cmdline){
	char fname[MAX_PATH];
	char *cp, *dp;
	int n= 0;
	int nbytes= 0;

	if(ascii){
		printf("reget of ascii transfer not permitted.\n");
		return;
	}
	chomp(fname, sizeof(fname), cmdline);
	if(!fname[0]){
		printf("Nothing to reget.\n");
		return;
	}
	for(cp= fname; *cp; ++cp)*cp= tolower(*cp);
	if(!strcmp(fname, "get"))
		n= 1;
	else if(!strcmp(fname, "put"))
		n= 2;
	chomp(fname, sizeof(fname), cmdline);
	if(fname[0])
		nbytes= atoi(fname);
	chomp(fname, sizeof(fname), cmdline);
	if(!fname[0] || n==0){
		printf("Usage: reget get|put bytes localfile [remotefile]\n");
		return;
	}
	trim(cmdline);
	if(!*cmdline)
		strcpy(cmdline, fname);
	if(cp= strrchr(fname, '/')){
		*cp++= 0;
		chdir(fname);
	}else{
		cp= fname;
	}
	if(dp= strrchr(cmdline, '/')){
		*dp++= 0;
		send_command_fp("CWD %s\r\n", cmdline, 0);
	}
	if(n==1)
		getfile(cp, dp, nbytes);
	else
		putfile("STOR", dp, cp, nbytes);
	send_command_fp("CWD %s\r\n", curdir, 0);
	chdir(locdir);
}
static int getpassword(char *buf, int nb){
	int n= getraw(buf, nb-1);
	buf[n]= 0;
	putchar('\n');
	return n;
}
int ftpok(){
	if(socCtrl!=INVALID_SOCKET)
		return 1;
	printf("Not connected!\n");
	return 0;
}
static int send_ok_command(char *msgfmt, char *arg){
	if(ftpok())
		return send_command(msgfmt, arg);
	return 0;
}
int do_command_const(const char *cmdc){
	char cmdline[MAX_PATH];
	strncpy(cmdline, cmdc, MAX_PATH);
	return do_command(cmdline);
}
int do_command(char *cmdline){
	char cmd[16];
	char fname[MAX_PATH];
	int n;
	char *cp;

erreport("LEFTOVER");
	if(!*cmdline)
		return 1;
	if(*cmdline=='!' || *cmdline=='?' || *cmdline=='@'){
		cmd[0]= *cmdline++;
		cmd[1]= 0;
		if(cp= strchr(cmdline, '\n'))
			*cp= 0;
	}else{	// alphabetic command
		chomp(cmd, sizeof(cmd), cmdline);
	}
	switch(lookup(cmd)->c){
	default:
		if(fpat){
			fclose(fpat);
			fpat= 0;
		}
		if(verbose && cmd[0]>' ')
			printf("No such command: %s %s\n", cmd, cmdline);
		break;
	case eAmbiguous:
		if(fpat){
			fclose(fpat);
			fpat= 0;
		}
		if(verbose && cmd[0]>' ')
			printf("Ambiguous command: %s\n", cmd);
		break;
	case eAPPEND:
		if(ftpok())
			if(chomp(fname, sizeof(fname), cmdline))
				putfile("APPE", fname, 0, 0);
		break;
	case eASCII:
		do_command_const("TYPE A\r\n");
		break;
	case eBINARY:
		do_command_const("TYPE I\r\n");
		break;
	case eBYE:
		return 0;
	case eCD:
		send_ok_command("CWD %s\r\n", cmdline);
		do_command_const("pwd");
		break;
	case eCLOSE:
		SocClose(&socCtrl);
		break;
	case eCMDFILE:	// @ command
		chomp(fname, sizeof(fname), cmdline);
		if(fpat)
			fclose(fpat);
		fpat= fopen(fname, "rt");
		break;
	case eDEBUG:
		bdebug= !bdebug;
		printf("Debug %s\n", bdebug? "on":"off");
		break;
	case eDELETE:
		if(ftpok()){
			if(!chomp(fname, sizeof(fname), cmdline)){
				prompt("Delete remote file: ", cmdline, sizeof(cmdline));
				chomp(fname, sizeof(fname), cmdline);
				if(!fname[0])
					break;
			}
			do{
				if(aborted || 598<=send_command("DELE %s\r\n", fname))
					break;
			}while(chomp(fname, sizeof(fname), cmdline));
		} break;
	case eDIR:
		if(ftpok())
			lister("LIST %s\r\n", cmdline, 0);
		break;
	case eGET:
		if(ftpok()){
			if(!chomp(fname, sizeof(fname), cmdline)){
				prompt("Remote file: ", cmdline, sizeof(cmdline));
				if(!chomp(fname, sizeof(fname), cmdline))
					break;
				if(!cmdline[0])
					prompt("Local file: ", cmdline, sizeof(cmdline));
			}
			trim(cmdline);
			getfile(fname,cmdline,0);
		} break;
	case eGLOB:
		globbing= !globbing;
		if(verbose)
			printf("%slobbing.\n", globbing? "G":"Not g");
		break;
		break;
	case eHELP:
		if(cmdline[0]){
			struct cmdstr *pcmd;
			chomp(cmd, sizeof(cmd), cmdline);
			pcmd= lookup(cmd);
			if(pcmd->name)
				printf("%s\t%s\n", pcmd->name, pcmd->help);
			else if(cmd[0])
				printf("%s?\n", cmd);
		}else for(n= 0; acmds[n].name; n++){
			printf("%-15s", acmds[n].name);
			if(4==n%5 || 0==acmds[n+1].name)
				printf("\n");
		} break;
	case eLCD:
		if(cmdline[0]){
			if(chdir(cmdline)<0){
				perror("chdir");
			}else{
				_getcwd(locdir,sizeof(locdir));
			}
		}
		printf("Local directory is %s\n", locdir);
		break;
	case eLS:
		if(ftpok())
			lister("NLST %s\r\n", cmdline, 0);
		break;
	case eMDELETE:
		if(ftpok())
			mget(cmdline, 1);
		break;
	case eMGET:
		if(ftpok())
			mget(cmdline, 0);
		break;
	case eMKDIR:
		chomp(fname, sizeof(fname), cmdline);
		send_ok_command("MKD %s\r\n", fname);
		break;
	case eMORE:
		if(ftpok())
			lister("RETR %s\r\n", cmdline, 1);
		break;
	case eMPUT:
		if(ftpok())
			mput(cmdline);
		break;
	case eNOOP:
		trim(cmdline); txmax= atoi(cmdline);
		send_ok_command("NOOP\r\n", 0);
		break;
	case eOPEN:
		if(socCtrl!=INVALID_SOCKET)
			printf("Already connected!\n");
		else
			do_open(cmdline);
		break;
	case ePASV:
		bpasv= !bpasv;
		if(verbose)
			printf("%ssing passive transfers\n", bpasv? "U":"Not u");
		break;
	case ePROMPT:
		bprompt= !bprompt;
		if(verbose)
			printf("prompt %s\n", bprompt? "on":"off");
		break;
	case ePUT:
		if(ftpok()){
			if(!chomp(fname, sizeof(fname), cmdline)){
				prompt("Local file: ", cmdline, sizeof(cmdline));
				if(!chomp(fname, sizeof(fname), cmdline))
					break;
				if(!cmdline[0])
					prompt("Remote file: ", cmdline, sizeof(cmdline));
			}
			trim(cmdline);
			putfile("STOR", fname, cmdline, 0);
		} break;
	case ePWD:
		n= send_ok_command("PWD\r\n", 0);
		if(2==n/100){
			char *cp= strchr(reply, '"');
			if(cp){
				++cp;
				for(n= 0; n<sizeof(curdir)&&cp[n]&&cp[n]!='"'; n++)
					curdir[n]= cp[n];
				curdir[n]= 0;
			}
		}
		if(!verbose)
			printf("\"%s\" is the current directory.\n", curdir);
		break;
	case eQUOTE:
		send_ok_command("%s\r\n", cmdline);
		break;
	case eREMOTEHELP:
		send_ok_command("HELP %s\r\n", cmdline);
		break;
	case eRENAME:
		chomp(fname, sizeof(fname), cmdline);
		if(400<=send_ok_command("RNFR %s\r\n", fname))
			break;
		chomp(fname, sizeof(fname), cmdline);
		if(!fname[0])
			prompt("New Name: ", fname, sizeof(fname));
		send_command("RNTO %s\r\n", fname);
		break;
	case eREST:
		if(!ftpok())
			break;
		n= 0;
		if(cmdline[0]<=' ')
			strcpy(cmdline, resumecmd);
		do_resume(cmdline);
		break;
	case eRMDIR:
		send_ok_command("RMD %s\r\n", cmdline);
		break;
	case eRUN:
		system(cmdline);
		break;
	case eSTATUS:
		send_ok_command("STAT %s\r\n", cmdline);
		break;
	case eSYST:
		send_ok_command("SYST\r\n", 0);
		break;
	case eTYPE:
		if(cmdline[0])
			ascii= tolower(cmdline[0])=='a';
		send_ok_command("TYPE %s\r\n", ascii? "A":"I");
		break;
	case eUSER:
		if(!ftpok())
			break;
		if(cp= strchr(server, '\t'))
			*cp= 0;
		chomp(fname, sizeof(fname), cmdline);
		if(fname[0]<=' '){
			prompt("User: ", fname, sizeof(fname));
			if(fname[0]<=' '){
				strcpy(fname, "anonymous");
				strcpy(cmdline, "me@here.com");
			}
		}
		trim(fname);
		if(400<=send_command("USER %s\r\n", fname))
			break;

		// append user name
		strcatok(server, "\t", sizeof(server));
		strcatok(server, fname, sizeof(server));
		if(cmdline[0]<=' '){
			if(btty){
				printf("Password: ");
				fflush(stdout);
				getpassword(cmdline, NLINE);
			}else{
				prompt("Password: ", cmdline, NLINE);
			}
		}
		trim(cmdline);
		if(400<=send_command("PASS %s\r\n", cmdline)){
			printf("...login failed\n");
			break;
		}
		// append password
		strcatok(server, "\t", sizeof(server));
		strcatok(server, cmdline, sizeof(server));

		if(do_command_const("pwd")){
#ifdef WIN32
			strcpy(cmdline, server);
			chomp(fname, sizeof(fname), cmdline);
			key_setstring(HKEY_CURRENT_USER, "SOFTWARE\\Mftp", fname, cmdline);
#endif
		}
		break;
	case eVERBOSE:
		verbose= !verbose;
		printf("verbose %s\n", verbose? "on":"off");
		break;
	}
	return 1;
}
