// FTP LS
import java.net.*;
import java.io.*;

public class FtpLS {
    final public static boolean debug=true; // true for debug trace
    protected Socket ctlskt;  // control socket
    protected Socket datskt;  // data socket
    protected Reader datReader;  // IO Stream for data socket
    protected Writer datWriter;
    protected BufferedReader ctlin;  // Control port reader
    protected OutputStream ctlout;  // Control port writer
    public String lastResponse;  // Last response from host
    public boolean passive = false; // passive mode

    // Connect to host -- we don't do ACCT
    public boolean connect(String host,String user, String pw) 
      throws UnknownHostException, IOException {
	int status;
	ctlskt=new Socket(host,21);
	ctlin=new BufferedReader(
           new InputStreamReader(ctlskt.getInputStream()));
	ctlout=ctlskt.getOutputStream();
	do {
	    status=getResponse();
	} while (status!=220 && status!=-1);
	if (status==-1) {
	    return false;
	}
	status=sendCmd("USER " + user);
	if (status==331) status=sendCmd("PASS " + pw);
	if (status==332) return false; // no ACCT
	if  (status!=230) return false;
	return true;
    }

    // This creates a data socket, issues a port command
    // and then pipes everything from the 
    // data port to the specified writer (which could
    // be the console, or a string, or a file)
    protected int dataSocket(String datCmd) throws IOException {
	int port;
	int status;
	ServerSocket datsktsvr;
	if (passive) {
	    java.util.StringTokenizer token;
	    StringBuffer IP=new StringBuffer();
	    int i;
	    status=sendCmd("PASV");
	    if (status<200||status>299) return -1;
	    // find IP/port
	    for (i=4;i<lastResponse.length();i++) 
		if (Character.isDigit(lastResponse.charAt(i))) break;
	    if (i==lastResponse.length()) return -1; // not found
	    token=new java.util.StringTokenizer(lastResponse.substring(i),",) ");
	    if (token.countTokens()!=6) return -1; // unknown IP/port
	    for (i=0;i<4;i++) {
		IP.append(token.nextToken());
		if (i!=3) IP.append(".");
	    }
	    port=Integer.parseInt(token.nextToken())*256;
	    port+=Integer.parseInt(token.nextToken());
	    datskt=new Socket(IP.toString(),port);
	    status=sendCmd(datCmd);
	}
	else {
	    datsktsvr=new ServerSocket(0);
	    port=datsktsvr.getLocalPort();
	    //set host/port
	    StringBuffer cmd = new StringBuffer("PORT ");
	    cmd.append(ctlskt.getLocalAddress().getHostAddress());
	    for (int i = 0; i < cmd.length(); i++) {
		if ('.' == cmd.charAt(i))
		    cmd.setCharAt(i, ',');
	    }
	    cmd.append(",");
	    cmd.append(Integer.toString(port/256));
	    cmd.append(",");
	    cmd.append(Integer.toString(port&0xFF));
	    status=sendCmd(cmd.toString());
	    if (status!=200) return -1;
	    status=sendCmd(datCmd);
	    // wait for remote connection (could time out)
	    datskt=datsktsvr.accept();	
	}
	return 0;
    }

    public int dataSocketIn(String datCmd,Writer w) throws IOException {

	int status;
	status=dataSocket(datCmd);
	if (status!=0) return status;
	datReader=new InputStreamReader(datskt.getInputStream());
	// transfer data until socket closes
	int ch;
	do {
	    ch=datReader.read();
	    if (ch!=-1) w.write((char)ch);
	} while (ch!=-1);
	w.flush();
	datskt.close();
	return getResponse();
    }

    public int dataSocketOut(String datCmd,Reader r) throws IOException {
	int status;
	status=dataSocket(datCmd);
	if (status!=0) return status;
	datWriter=new OutputStreamWriter(datskt.getOutputStream());
	int ch;
	do {
	    ch=r.read();
	    if (ch!=-1) datWriter.write((char)ch);
	} while (ch!=-1);
	datWriter.flush();
	datskt.close();
	return getResponse();
    }

    // Get a response accounting for possible mutliline responses
    protected int getResponse() throws IOException {
	int n;
	String tmp;
	do {
	    lastResponse=ctlin.readLine();
	    if (debug) System.out.println("DEBUG Received: " + lastResponse);
	} while (lastResponse.charAt(3)=='-');
	tmp=lastResponse.substring(0,3);
	try {
	    n=Integer.parseInt(tmp);
	}
	catch (NumberFormatException e) {
	    n=-1;
	}
	return n;
    }

    // Send a command and return status code
    public int sendCmd(String cmd) throws IOException {
	if (debug) System.out.println("DEBUG Sent: " + cmd);
	String tmp=cmd+"\r\n";
	int n;
	ctlout.write(tmp.getBytes());
	ctlout.flush();
	return getResponse();
    }

    // Main driver

    public static void main(String [] args) throws Exception {
        if (args.length<3||args.length>4) {
	    System.out.println("usage: EZftp host user password [directory]");
	    System.exit(1);
	}
	FtpLS obj= new FtpLS();
	obj.passive=true;
	if (!obj.connect(args[0],args[1],args[2])) {
	    System.out.println("Can't connect");
	    System.exit(1);
	}

	// Last argument is directory if present
	if (args.length==4) 
	    obj.sendCmd("CWD " + args[3]);

	// print directory (chop off response code)
	obj.sendCmd("PWD");
	System.out.println(obj.lastResponse.substring(4));
	// Do a directory
	obj.dataSocketIn("LIST",new OutputStreamWriter(System.out));

	// Additional code
	FileWriter f = new FileWriter("ftp.test");
	obj.dataSocketIn("RETR readme",f);
	System.out.println(obj.lastResponse);

	// Write test
	FileReader fw = new FileReader("ftp.test");
	obj.dataSocketOut("STOR ftp.test",fw);
	System.out.println(obj.lastResponse);


	System.exit(0);
    }
}
