import java.io.*; import java.net.*; import java.sql.*; import java.security.MessageDigest; public class FlickrFtpd extends Thread { // server config private static String upload_root = "/path/to/flickr.com/temp"; // where to store uploaded files private static String ingest_path = "/usr/bin/php -q /path/to/flickr.com/sendto/ftp_process.gne"; private static int localPort = 9021; // port to listen on private static boolean debug = true; // print stack traces private static boolean log = true; // show stuff // per instance class variables private Socket incoming; // global class variables private static FlickrFtpd loadedServer; private ServerSocket server; private ServerSocket passiveSocket; private Socket dataSocket; private InetAddress remoteNode; private int remotePort = 1; // constants static final String XFER_COMPLETE = "226 transfer complete"; private static final String BINARY_XFER = "150 Binary data connection"; private static final String COMMAND_OK = "200 command succesful "; private static final String FAULT = "550 "; private static final String NOT_LOGGED_IN = "530 Not logged in"; private static final String FILE_OK = "250 file action okay"; private static final int inactivityTimer = 5 * 60 * 1000; static final String TELNET = "ISO-8859-1"; // db stuff protected Connection db_conn = null; protected ResultSet db_rs = null; protected Statement db_stmt = null; protected PreparedStatement db_pstmt = null; protected static String db_url = "jdbc:mysql://localhost/flickr?user=ftp-rw"; // misc static ThreadGroup tg = new ThreadGroup( "FlickrFtpd"); static boolean shutdown = false; public static void main(String[] args) { shutdown = false; new FlickrFtpd().start(); // kick off a (simulated) daemon thread } public static boolean kill() { int j=0; Thread meMySelfI = Thread.currentThread(); do { /* iter across the thread group, killing all members. */ shutdown = true; Thread list[] = new Thread[ tg.activeCount()]; // get all members of the group (including submembers) int i = tg.enumerate( list); // no members means that we have gracefully suceeded if ( i == 0 ) return true; // if some of the threads do IO during the shut down they will // need time to accomplish the IO. So, I give it to 'em // after the first attempt. if ( j > 0) try { meMySelfI.sleep( 500);} catch (Exception e) {}; // try to shudown each thread in the group while ( i-- > 0) { FlickrFtpd tftp = (FlickrFtpd)list[i]; tftp.interrupt(); // first, do it politely meMySelfI.yield(); // give 'em time to respond tftp.forceClose(); // second, use a big hammer meMySelfI.yield(); // give 'em time to respond } } while ( j++ <= 3); return false; } // set for a specific instance to notify runing as a (simulated) daemon thread. boolean isDaemon = false; public FlickrFtpd() { super( tg, null, "FtpDaemon"); isDaemon = true; } public FlickrFtpd(Socket incoming) { super( tg, null, incoming.toString()); //~~ not a real good name.... this.incoming = incoming; } private void daemon() { try { server = new ServerSocket(localPort); while (true) { Socket incoming = server.accept(); new FlickrFtpd( incoming ).start(); } } catch ( Exception e ) { // usually network errors (including timeout) if ( server != null) try { server.close(); server = null; } catch (Exception e1) {}; if ( log) System.out.println( "forced Server exit "+e); if ( debug) e.printStackTrace(); } } // initiate either a server or a user session public void run() { if (isDaemon) { daemon(); return; }; boolean loggedIn = false; int i, h1; String di,str1,user="unknown",user_id="0"; InetAddress localNode; byte dataBuffer[] = new byte[1024]; String command = null; StringBuffer statusMessage = new StringBuffer(40); File targetFile = null; try { // start mysql Class.forName("com.mysql.jdbc.Driver").newInstance(); this.db_conn = DriverManager.getConnection(db_url); this.db_stmt = this.db_conn.createStatement(); this.db_stmt.executeUpdate("INSERT INTO test_table (name) VALUES ('hello world')"); incoming.setSoTimeout(inactivityTimer); // enforce I/O timeout remoteNode = incoming.getInetAddress(); localNode = InetAddress.getLocalHost(); BufferedReader in = new BufferedReader(new InputStreamReader(incoming.getInputStream(), TELNET)); PrintWriter out = new PrintWriter(new OutputStreamWriter( incoming.getOutputStream(), TELNET),true); str1 = "220 Flickr FTP Server Ready"; out.println(str1); if (log) System.out.println( remoteNode.getHostName()+" "+str1); boolean done = false; char dataType = 0; while ( !done ) { statusMessage.setLength(0); // obtain and tokenize command String str = in.readLine(); if ( str == null ) break; // EOS reached i = str.indexOf( ' '); if ( i == -1 ) i = str.length(); command = str.substring( 0, i).toUpperCase().intern(); if ( log) System.out.print( user +"@"+remoteNode.getHostName()+" "+ (String)((command != "PASS")? str : "PASS ***")); str = str.substring( i).trim(); try { if ( command == "USER" ) { user = str; statusMessage.append( "331 Password"); } else if ( command == "PASS" ) { String pass = str; String pass_md5 = md5(pass); this.db_rs = this.db_stmt.executeQuery("SELECT * FROM users WHERE email='"+user+"' AND password='"+pass_md5+"'"); if (this.db_rs.first()){ loggedIn = true; user_id = this.db_rs.getString("id"); System.out.println("Account id is "+user_id); } statusMessage.append(loggedIn?"230 logged in User":"530 Login Incorrect"); } else if ( !loggedIn ) { statusMessage.append( "530 Not logged in"); } else if ( command == "RETR" ) { statusMessage.append( "999 Not likely"); } else if ( command == "STOR" ) { out.println( BINARY_XFER); // trim a leading slash off the filename if there is one if (str.substring(0, 1).equals("/")) str = str.substring(1); String filename = user_id + "_" + str; // TODO: sanitise filename targetFile = new File(upload_root + "/" + filename); RandomAccessFile dataFile = null; InputStream inStream = null; OutputStream outStream = null; BufferedReader br = null; PrintWriter pw = null; try { int amount; dataSocket = setupDataLink(); // ensure timeout on reads. dataSocket.setSoTimeout( inactivityTimer); dataFile = new RandomAccessFile( targetFile, "rw"); inStream = dataSocket.getInputStream(); while ( (amount = inStream.read( dataBuffer)) != -1 ) dataFile.write( dataBuffer, 0, amount); statusMessage.append( XFER_COMPLETE); shell_exec(ingest_path + " " + user_id + " " + filename); } finally { try {if ( inStream != null ) inStream.close();} catch ( Exception e1 ) {}; try {if ( outStream != null ) outStream.close();} catch ( Exception e1 ) {}; try {if ( dataFile != null ) dataFile.close();} catch ( Exception e1 ) {}; try {if ( dataSocket != null ) dataSocket.close();} catch ( Exception e1 ) {}; dataSocket = null; } } else if ( command == "REST" ) { statusMessage.append("502 Sorry, no resuming"); } else if ( command == "TYPE" ) { if ( Character.toUpperCase( str.charAt( 0)) == 'I'){ statusMessage.append( COMMAND_OK); } else { statusMessage.append( "504 Only binary baybee"); } } else if ( command == "DELE" || command == "RMD" || command == "XRMD" || command == "MKD" || command == "XMKD" || command == "RNFR" || command == "RNTO" || command == "CDUP" || command == "XCDUP" || command == "CWD" || command == "SIZE" || command == "MDTM" ) { statusMessage.append("502 None of that malarky!"); } else if ( command == "QUIT" ) { statusMessage.append( COMMAND_OK).append( "GOOD BYE"); done = true; } else if ( command == "PWD" | command == "XPWD" ) { statusMessage.append( "257 \"/\" is current directory"); } else if ( command == "PORT" ) { int lng,lng1,lng2, ip2; String a1="",a2=""; lng = str.length() - 1; lng2 = str.lastIndexOf(","); lng1 = str.lastIndexOf(",",lng2-1); for ( i=lng1+1;i<lng2;i++ ) { a1 = a1 + str.charAt(i); } for ( i=lng2+1;i<=lng;i++ ) { a2 = a2 + str.charAt(i); } remotePort = Integer.parseInt(a1); ip2 = Integer.parseInt(a2); remotePort = (remotePort <<8) + ip2; statusMessage.append( COMMAND_OK).append( remotePort); } else if ( command == "LIST" | command == "NLST" ) { try { out.println("150 ASCII data"); dataSocket = setupDataLink(); PrintWriter out2 = new PrintWriter( dataSocket.getOutputStream(),true); if ((command == "NLST")) { out2.println("."); out2.println(".."); } else { out2.println("total 8.0k"); out2.println("dr--r--r-- 1 owner group 213 Aug 26 16:31 ."); out2.println("dr--r--r-- 1 owner group 213 Aug 26 16:31 .."); } // socket MUST be closed before signalling EOD dataSocket.close(); dataSocket = null; statusMessage.setLength( 0); statusMessage.append( XFER_COMPLETE); } finally { try {if ( dataSocket != null ) dataSocket.close();} catch ( Exception e ) {}; dataSocket = null; } } else if ( command == "NOOP" ) { statusMessage.append( COMMAND_OK); } else if ( command == "SYST" ) { statusMessage.append( "215 UNIX"); // allows NS to do long dir } else if ( command == "MODE" ) { if ( Character.toUpperCase( str.charAt( 0)) == 'S'){ statusMessage.append( COMMAND_OK); } else { statusMessage.append( "504"); } } else if ( command == "STRU" ) { if ( str.equals( "F") ) { statusMessage.append( COMMAND_OK); } else { statusMessage.append( "504"); } } else if ( command == "PASV" ) { try { int num = 0, j = 0; if ( passiveSocket != null ) try { passiveSocket.close();} catch (Exception e) {}; passiveSocket = new ServerSocket( 0); // any port // ensure timeout on reads. passiveSocket.setSoTimeout( inactivityTimer); statusMessage.append( "227 Entering Passive Mode ("); String s = localNode.getHostAddress().replace( '.', ',');// get host # statusMessage.append( s).append( ','); num = passiveSocket.getLocalPort();// get port # j = (num >> 8) & 0xff; statusMessage.append( j); statusMessage.append( ','); j = num & 0xff; statusMessage.append( j); statusMessage.append( ')'); } catch ( Exception e) { try {if ( passiveSocket != null ) passiveSocket.close();} catch ( Exception e1 ) {}; passiveSocket = null; throw e; } } else { statusMessage.append( "502 unimplemented ").append( command); } } // shutdown causes an interruption to be thrown catch ( InterruptedException e) { throw( e); } catch ( Exception e ) // catch all for any errors (including files) { statusMessage.append( FAULT).append( e.getMessage()); if ( debug ) { System.out.println( "\nFAULT - lastfile "+targetFile); e.printStackTrace(); }; } // send result status to remote out.println( statusMessage); if ( log) System.out.println( "\t" + statusMessage); } } catch ( Exception e ) // usually network errors (including timeout) { if ( log) System.out.println( "forced instance exit "+e); if ( debug) e.printStackTrace(); } finally // exiting server instance { // tear down mysql if (this.db_rs != null) { try { this.db_rs.close(); } catch (SQLException SQLE) { ; } } if (this.db_stmt != null) { try { this.db_stmt.close(); } catch (SQLException SQLE) { ; } } if (this.db_pstmt != null) { try { this.db_pstmt.close(); } catch (SQLException SQLE) { ; } } if (this.db_conn != null) { try { this.db_conn.close(); } catch (SQLException SQLE) { ; } } forceClose(); } } private void forceClose() { // make sure network resources are released and that remote end // knows we are going away. if (incoming != null ) try { incoming.close(); incoming = null; } catch ( Exception e ) {}; if (passiveSocket != null ) try { passiveSocket.close(); passiveSocket = null; } catch ( Exception e ) {}; if (dataSocket != null ) try { dataSocket.close(); dataSocket = null; } catch ( Exception e ) {}; if ( isDaemon && server != null) try { server.close(); server = null; } catch ( Exception e) {}; } private final Socket setupDataLink() throws java.io.IOException { Socket dataSocket = ( passiveSocket != null) ? passiveSocket.accept() : new Socket( remoteNode, remotePort); // ensure timeout on reads. dataSocket.setSoTimeout( inactivityTimer); return dataSocket; } private String md5(String data){ StringBuffer sb = new StringBuffer(); try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); messageDigest.update(data.getBytes()); byte[] digestBytes = messageDigest.digest(); /* convert to hexstring */ String hex = null; for (int i = 0; i < digestBytes.length; i++) { hex = Integer.toHexString(0xFF & digestBytes[i]); if (hex.length() < 2) { sb.append("0"); } sb.append(hex); } } catch (Exception ex) { System.out.println(ex.getMessage()); } return sb.toString(); } private String shell_exec(String cmdline) { String line = ""; try { // windows //Process p = Runtime.getRuntime().exec(cmdline); // linux Process p = Runtime.getRuntime().exec(new String[] { "/bin/sh", "-c", cmdline }); BufferedReader input = new BufferedReader (new InputStreamReader(p.getInputStream())); while ((line += input.readLine()) != null) { } input.close(); } catch (Exception err) { err.printStackTrace(); } return line; } }