/** * OnionCoffee - Anonymous Communication through TOR Network * Copyright (C) 2005-2007 RWTH Aachen University, Informatik IV * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package TorJava.Proxy; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import uk.ac.cam.cl.dtg.android.tor.TorProxy.TorProxyControlService; import TorJava.TCPStream; import TorJava.TCPStreamProperties; import TorJava.TorKeeper; import TorJava.Common.TorResolveFailedException; class SocksConnection extends Thread { static final int payload = 498; // maximum length of Tor-Cell Payload static final int sleep_millis = 10; Socket local; TCPStream remote = null; private StreamFerry mForward = null; private StreamFerry mBackward = null; private TorProxyControlService torService = null; public SocksConnection(Socket local, TorProxyControlService torService) { // start connection this.local = local; this.setName("SocksConnection"); this.torService = torService; this.start(); } static void relay(Socket local,DataInputStream read_remote,DataOutputStream write_remote) { try{ byte[] data = new byte[payload]; DataInputStream read_local = new DataInputStream(local.getInputStream()); DataOutputStream write_local = new DataOutputStream(local.getOutputStream()); /*DataInputStream read_remote = new DataInputStream(remote.getInputStream()); DataOutputStream write_remote = new DataOutputStream(remote.getOutputStream());*/ boolean action = false; while(local.isConnected()) { // data from local? if (read_local.available()>0) { int cc = read_local.read(data); write_remote.write(data,0,cc); write_remote.flush(); action = true; } // data from remote? if (read_remote.available()>0) { int cc = read_remote.read(data); //System.out.println(" << "+cc+" bytes"); write_local.write(data,0,cc); write_local.flush(); action = true; } // rest a bit, if no action if(!action) Thread.sleep(sleep_millis); action = false; //System.out.println("loop..."); } } catch(Exception e) { System.err.println(e.getMessage()); } } private void socks4(DataInputStream read_local,DataOutputStream write_local) { TCPStream remote=null; Socket remoteS=null; byte[] command = new byte[8]; byte[] answer = new byte[8]; try{ // read socks-command read_local.read(command,1,1); if (command[1]!=1) { answer[1] = 91; // failed write_local.write(answer); write_local.flush(); throw new Exception("only support for CONNECT"); } // read port and IP for Socks4 read_local.read(command,2,6); byte[] raw_ip = new byte[4]; System.arraycopy(command,4,raw_ip,0,4); int port = ((((int)command[2])&0xff)<<8) + (((int)command[3])&0xff); // read user name (and throw away) while(read_local.readByte()!=0) {} // check for SOCKS4a if ((raw_ip[0]==0)&&(raw_ip[1]==0)&&(raw_ip[2]==0)&&(raw_ip[3]!=0)) { StringBuffer sb = new StringBuffer(256); byte b; do { b = read_local.readByte(); if (b!=0) sb.append((char)b); } while(b!=0); String hostname = sb.toString(); // connect if (torService.isTorProcessActive()) remote = TorKeeper.getTor().connect(new TCPStreamProperties(hostname,port)); else remoteS = new Socket(hostname,port); } else { // SOCKS 4 InetAddress in_addr = InetAddress.getByAddress(raw_ip); if (torService.isTorProcessActive()) remote = TorKeeper.getTor().connect(new TCPStreamProperties(in_addr,port)); else remoteS = new Socket(in_addr,port); } // send OK for socks4 write_local.write(answer); // MAIN DATA TRANSFERCOPY LOOP if (torService.isTorProcessActive()) relay(local,new DataInputStream(remote.getInputStream()),new DataOutputStream(remote.getOutputStream())); else relay(local,new DataInputStream(remoteS.getInputStream()),new DataOutputStream(remoteS.getOutputStream())); } catch(Exception e) { System.err.println(e); } finally{ if(remote!=null) remote.close(); if(remoteS!=null){ try{ remoteS.close(); } catch(Exception e){};} } } private void socks5(DataInputStream read_local,DataOutputStream write_local) { Socket remoteS=null; byte[] methods; byte[] command = new byte[8]; byte[] answer = new byte[2]; try{ answer[0] = 5; answer[1] = (byte)0xff; // read methods read_local.read(command,0,1); if (command[0]<=0) { write_local.write(answer); write_local.flush(); throw new Exception("number of supported methods must be >0"); } methods = new byte[command[0]]; read_local.readFully(methods); // check for anonymous/unauthenticated connection boolean found_anonymous = false; for(int i=0;i<methods.length;++i) found_anonymous = found_anonymous || (methods[i] == 0); if (!found_anonymous) { write_local.write(answer); write_local.flush(); throw new Exception("no accepted method listed by client"); } // ok, we can tell the client to connect without username/password answer[1] = 0; write_local.write(answer); write_local.flush(); // read and parse client request command = new byte[4]; read_local.readFully(command); if (command[0]!=5 ) throw new Exception("why the f*** does the client change its version number?"); if (command[1]!=1 ) throw new Exception("only CONNECT supported"); if (command[2]!=0 ) throw new Exception("do not play around with reserved fields"); if ((command[3]!=1)&&(command[3] != 3)) throw new Exception("only IPv4 and HOSTNAME supported"); // parse address InetAddress in_addr=null; String hostname=null; byte[] address; if (command[3]==1) { address = new byte[4]; read_local.readFully(address); in_addr = InetAddress.getByAddress(address); } else { read_local.read(command,0,1); address = new byte[((256+command[0]) & 0xff)+1]; address[0] = command[0]; read_local.readFully(address, 1, address.length - 1); hostname = new String(address, 1, address.length - 1); } // read port byte[] port = new byte[2]; read_local.readFully(port); int int_port = ((((int)port[0])&0xff)<<8) + (((int)port[1])&0xff); // build connection byte success = 0x00; // Success if (torService.isTorProcessActive()) { try { if (hostname==null) remote = TorKeeper.getTor().connect(new TCPStreamProperties(in_addr,int_port)); else remote = TorKeeper.getTor().connect(new TCPStreamProperties(hostname,int_port)); } catch(TorResolveFailedException e) { success = 0x04; // Host unreachable } } else { /*if (hostname==null) remoteS = new Socket(in_addr,int_port); else remoteS = new Socket(hostname,int_port);*/ throw new Exception("Tor not active"); } // send reply to client answer = new byte[6+address.length]; answer[0] = 5; // version answer[1] = success; // success answer[2] = 0; // reserved answer[3] = command[3]; System.arraycopy(address,0,answer,4,address.length); System.arraycopy(port,0,answer,4+address.length,2); write_local.write(answer); write_local.flush(); // MAIN DATA TRANSFERCOPY LOOP if (success == 0x00) { if (torService.isTorProcessActive()) { relay(write_local, read_local, remote); return; } //relay(local,new DataInputStream(remote.getInputStream()),new DataOutputStream(remote.getOutputStream())); //else relay(local,new DataInputStream(remoteS.getInputStream()),new DataOutputStream(remoteS.getOutputStream())); } } catch(Exception e) { System.err.println(e); } //finally{ if(remote!=null) remote.close(); if(remoteS!=null){ try{ remoteS.close(); } catch(Exception e){};} //} } public void run() { //String id = "none"; try{ DataInputStream read_local = new DataInputStream(local.getInputStream()); DataOutputStream write_local = new DataOutputStream(local.getOutputStream()); // read socks-version byte[] version = new byte[1]; read_local.read(version,0,1); // prepare answer byte[] answer = new byte[2]; answer[0] = 0; answer[1] = 90; // granted // parse command if (version[0]==4) { socks4(read_local, write_local); } else if (version[0] == 5) { socks5(read_local, write_local); } else { answer[1] = 91; // failed write_local.write(answer); write_local.flush(); throw new Exception("only support for Socks-4(a)"); } } catch(Exception e) { System.err.println(e); try{ local.close(); }catch(Exception m){}; //e.printStackTrace(); } //finally{ //System.out.println(id+" closing down"); // try{ local.close(); }catch(Exception e){}; //} } private void relay(DataOutputStream write_local, DataInputStream read_local, TCPStream remote) { mForward = new StreamFerry(remote.getInputStream(), write_local); mBackward = new StreamFerry(read_local, remote.getOutputStream()); } public void close() { try { if (!local.isOutputShutdown()) local.shutdownOutput(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try { if (!local.isInputShutdown()) local.shutdownInput(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } //mForward.close(); //mBackward.close(); try { local.close(); remote.close(); } catch (IOException e) { // Not much we can do here } } private class StreamFerry extends Thread { private static final int BUFFER_SIZE = 498; private InputStream in = null; private OutputStream out = null; boolean stopped = false; public StreamFerry(InputStream in, OutputStream out) { this.in = in; this.out = out; this.start(); } @Override public void run() { byte[] buffer = new byte[BUFFER_SIZE]; int read = 0; while (!stopped) { try { read = in.read(buffer); if (read <= 0) { //Log.d("SocksConnection", "Read returned - " + read + " so closing..."); closeConn(); stopped = true; } else { //String buffer_s = new String(buffer, 0, read); //Log.v("SocksConnection", buffer_s); out.write(buffer, 0, read); out.flush(); } } catch (IOException e) { //Log.d("SocksConnection", "IOException " + e.toString()); closeConn(); stopped = true; } } } public void closeConn() { SocksConnection.this.close(); } public void close() { stopped = true; /*try { in.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }*/ } } }