// Simple FTP server
import java.io.*;
import java.net.*;
import java.util.*;



// **** MAIN CLASS
public class SFtpServer extends LineServer {

    private ParseTableEntry parseTable[] = {
	new ParseTableEntry("USER",SFtpServer.class,"userCmd"),
	new ParseTableEntry("QUIT",SFtpServer.class,"quitCmd"),
	new ParseTableEntry("PASS",SFtpServer.class,"passCmd"),
	new ParseTableEntry("ACCT",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("SMNT",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("CWD",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("CDUP",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("REIN",SFtpServer.class,"reinCmd"),
	new ParseTableEntry("PASV",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("STRU",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("MODE",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("TYPE",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("STOU",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("APPE",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("ALLO",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("REST",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("RMD",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("MKD",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("PWD",SFtpServer.class,"pwdCmd"),
	new ParseTableEntry("SITE",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("SYST",SFtpServer.class,"systCmd"),
	new ParseTableEntry("HELP",SFtpServer.class,"helpCmd"),
	new ParseTableEntry("NOOP",SFtpServer.class,"noopCmd"),
	new ParseTableEntry("STAT",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("RNFR",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("RNTO",SFtpServer.class,"unimplCmd"),
	new ParseTableEntry("DELE",SFtpServer.class,"unimplCmd"),

	new ParseTableEntry("PORT",SFtpServer.class,"portCmd"),
	new ParseTableEntry("RETR",SFtpServer.class,"retrCmd"),
	new ParseTableEntry("LIST",SFtpServer.class,"listCmd"),
	new ParseTableEntry("NLST",SFtpServer.class,"listCmd"),
	// always make this the last entry
	new ParseTableEntry("",SFtpServer.class,"unknownCmd")
    };

    int loginstate=0; // 0=none, 1=user OK, 2= user/pw ok, 3=user Not OK
    String dataportip;
    int dataport=20;

    public void writeString(String s) throws IOException {
	owrite.write(s,0,s.length());
	owrite.write("\r\n");
	owrite.flush();
    }

    public boolean init() throws IOException {
	// set default initial IP
	dataportip=socket.getInetAddress().getHostAddress();
	writeString("220-SFtp. Anonymous logins accepted");
	writeString("220-Your IP: " + dataportip);
	writeString("220-Default data port: " + dataport);
	writeString("220 Server ready");
	return true;
    }

    public boolean process(String line) throws IOException {
	boolean rv=false;
	StringTokenizer token=new StringTokenizer(line);
	String word=null;
	try {
	    word=token.nextToken();
	}
	catch (NoSuchElementException nsee) {} // just a blank line
	if (word==null) return true; // ignore blank lines
	String output;
	rv=ParseTableEntry.parse(parseTable,this,word,line,token);
	owrite.flush();
	return rv; 
    }

    // getMethod only works on public methods
    public boolean unknownCmd(String line,StringTokenizer token) 
throws IOException {
	writeString("500 Unknown command ");
	return true;
    }

    public boolean quitCmd(String line,StringTokenizer token) 
throws IOException {
	writeString("221 Goodbye");
	return false;
    }

    public boolean unimplCmd(String line,StringTokenizer token) 
throws IOException {
	writeString("202 Command not implemented.");
	return true;
    }

    public boolean pwdCmd(String line,StringTokenizer token) throws IOException {
	if (loginstate!=2) return loginerr();
	writeString("257 \"/\" is the current working directory.");
	return true;
    }

    public boolean loginerr() throws IOException {
	writeString("530 Please login");
	return true;
    }

    public boolean reinCmd(String line,StringTokenizer token) 
throws IOException {
	writeString("200 Command OK.");
	loginstate=0;
	return true;
    }

    public boolean systCmd(String line,StringTokenizer token) 
throws IOException {
	if (loginstate!=2) return loginerr();
	writeString("215 UNIX Type: L8");
	return true;
    }

    public boolean passCmd(String line,StringTokenizer token) 
throws IOException {
	String pw=token.nextToken();
	if (loginstate==0) {
	    writeString("503 Login with USER First");
	    return true;
	}
	if (loginstate==1 && pw!=null && !pw.equals("")) {
	    System.err.println("Login from " + pw);
	    loginstate=2;
	    writeString("230 User logged in");
	    return true;
	}
	loginstate=0;
	writeString("530 Login incorrect");
	return false;
    }
    public boolean userCmd(String line,StringTokenizer token) 
throws IOException {
	// we only allow anonymous
	if (token.nextToken().equals("anonymous")) {
	    loginstate=1;
	}
	else {
	    loginstate=3;
	}
	writeString("331 Send e-mail as password");
	return true;
    }

    public boolean noopCmd(String line,StringTokenizer token) 
throws IOException {
	writeString("200 Command OK");
	return true;
    }

    public boolean portCmd(String line,StringTokenizer token) 
throws IOException {
	StringBuffer b=new StringBuffer();
	try {
	    for (int i=0;i<4;i++) {
		String tmp=token.nextToken(" ,");
		if (tmp==null) throw new Exception();
		int n=Integer.parseInt(tmp); // throw if not integer
		if (n<0||n>255) throw new Exception();
		b.append(tmp);
		if (i!=3) b.append(".");
	    }
	    dataportip=b.toString();

	    String tmp=token.nextToken(" ,");
	    dataport=Integer.parseInt(tmp)*256;
	    tmp=token.nextToken();
	    dataport+=Integer.parseInt(tmp);
	}
	catch (Exception e) {
	    writeString("501 Illegal Port Command");
	    return true;
	}
	writeString("200 Command OK");
	return true;
    }

    public boolean retrCmd(String line,StringTokenizer token) 
throws IOException {
	String fn=token.nextToken();
	if (fn.equals("a.txt")) {
	    sendData(new FileReader("SFtpServer.java"));
	}
	else if (fn.equals("b.txt")) {
	    sendData(new FileReader("ParseTableEntry.java"));
	}
	else {
	    writeString("550 No such file");
	}
	return true;
    }

    public boolean listCmd(String line,StringTokenizer token) 
throws IOException {

	sendData(new StringReader("a.txt\r\nb.txt\r\n"));
	return true;
    }


    public boolean helpCmd(String line,StringTokenizer token)
 throws IOException {
	writeString("214-Simple Java Server by alw@al-williams.com");
	writeString("214 No further help available.");
	return true;
    }


    public boolean sendData(Reader r) throws IOException {
	int c;
	Socket s=null;
	try {
	    writeString("150 Opening Data connection");
	    s=new Socket(dataportip,dataport);
	    OutputStreamWriter w=new OutputStreamWriter(s.getOutputStream());
	    do {
		c=r.read();
		if (c!=-1) w.write((char)c);
	    } while (c!=-1);
	    w.flush();
	    writeString("226 Transfer Complete");
	    s.close();
	    return true;
	}
	catch (Exception e) { 
	    writeString("426 Transfer error");
	    s.close();
	    return true; 
	}
    }

    static public void main(String args[]) {
	SFtpServer.startServer(21,SFtpServer.class);
    }
}
