diff --git a/src/org/torproject/android/HttpProxy.java b/src/org/torproject/android/HttpProxy.java new file mode 100644 index 00000000..1da7c719 --- /dev/null +++ b/src/org/torproject/android/HttpProxy.java @@ -0,0 +1,639 @@ +package org.torproject.android; +/*
*/ +/* + * This is a simple multi-threaded Java proxy server + * for HTTP requests (HTTPS doesn't seem to work, because + * the CONNECT requests aren't always handled properly). + * I implemented the class as a thread so you can call it + * from other programs and kill it, if necessary (by using + * the closeSocket() method). + * + * We'll call this the 1.1 version of this class. All I + * changed was to separate the HTTP header elements with + * \r\n instead of just \n, to comply with the official + * HTTP specification. + * + * This can be used either as a direct proxy to other + * servers, or as a forwarding proxy to another proxy + * server. This makes it useful if you want to monitor + * traffic going to and from a proxy server (for example, + * you can run this on your local machine and set the + * fwdServer and fwdPort to a real proxy server, and then + * tell your browser to use "localhost" as the proxy, and + * you can watch the browser traffic going in and out). + * + * One limitation of this implementation is that it doesn't + * close the ProxyThread socket if the client disconnects + * or the server never responds, so you could end up with + * a bunch of loose threads running amuck and waiting for + * connections. As a band-aid, you can set the server socket + * to timeout after a certain amount of time (use the + * setTimeout() method in the ProxyThread class), although + * this can cause false timeouts if a remote server is simply + * slow to respond. + * + * Another thing is that it doesn't limit the number of + * socket threads it will create, so if you use this on a + * really busy machine that processed a bunch of requests, + * you may have problems. You should use thread pools if + * you're going to try something like this in a "real" + * application. + * + * Note that if you're using the "main" method to run this + * by itself and you don't need the debug output, it will + * run a bit faster if you pipe the std output to 'nul'. + * + * You may use this code as you wish, just don't pretend + * that you wrote it yourself, and don't hold me liable for + * anything that it does or doesn't do. If you're feeling + * especially honest, please include a link to nsftools.com + * along with the code. Thanks, and good luck. + * + * Julian Robichaux -- http://www.nsftools.com + */ +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; + +import net.sourceforge.jsocks.socks.Socks5Proxy; +import net.sourceforge.jsocks.socks.SocksSocket; + + +public class HttpProxy extends Thread +{ + public static final int DEFAULT_PORT = 8888; + + private ServerSocket server = null; + private int thisPort = DEFAULT_PORT; + private String fwdServer = ""; + private int fwdPort = 0; + private int ptTimeout = ProxyThread.DEFAULT_TIMEOUT; + private int debugLevel = 1; + private PrintStream debugOut = System.out; + private boolean keepRunning = true; + private boolean doSocks = false; + + private Socks5Proxy sProxy = null; + + /** + * @return the doSocks + */ + public boolean isDoSocks() { + return doSocks; + } + + + /** + * @param doSocks the doSocks to set + */ + public void setDoSocks(boolean doSocks) { + this.doSocks = doSocks; + } + + + /* here's a main method, in case you want to run this by itself */ + public static void main (String args[]) + { + int port = 0; + String fwdProxyServer = ""; + int fwdProxyPort = 0; + + if (args.length == 0) + { + System.err.println("USAGE: java jProxy[ ]"); + System.err.println(" the port this service listens on"); + System.err.println(" optional proxy server to forward requests to"); + System.err.println(" the port that the optional proxy server is on"); + System.err.println("\nHINT: if you don't want to see all the debug information flying by,"); + System.err.println("you can pipe the output to a file or to 'nul' using \">\". For example:"); + System.err.println(" to send output to the file prox.txt: java jProxy 8080 > prox.txt"); + System.err.println(" to make the output go away: java jProxy 8080 > nul"); + return; + } + + // get the command-line parameters + port = Integer.parseInt(args[0]); + if (args.length > 2) + { + fwdProxyServer = args[1]; + fwdProxyPort = Integer.parseInt(args[2]); + } + + // create and start the jProxy thread, using a 20 second timeout + // value to keep the threads from piling up too much + System.err.println(" ** Starting jProxy on port " + port + ". Press CTRL-C to end. **\n"); + HttpProxy jp = new HttpProxy(port, fwdProxyServer, fwdProxyPort, 20); + jp.setDebug(1, System.out); // or set the debug level to 2 for tons of output + jp.start(); + + // run forever; if you were calling this class from another + // program and you wanted to stop the jProxy thread at some + // point, you could write a loop that waits for a certain + // condition and then calls jProxy.closeSocket() to kill + // the running jProxy thread + while (true) + { + try { Thread.sleep(3000); } catch (Exception e) {} + } + + // if we ever had a condition that stopped the loop above, + // we'd want to do this to kill the running thread + //jp.closeSocket(); + //return; + } + + + /* the proxy server just listens for connections and creates + * a new thread for each connection attempt (the ProxyThread + * class really does all the work) + */ + public HttpProxy (int port) + { + thisPort = port; + + try { + sProxy = new Socks5Proxy(TorConstants.IP_LOCALHOST,TorConstants.PORT_SOCKS); + } catch (UnknownHostException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + sProxy.resolveAddrLocally(false); + + + } + + public HttpProxy (int port, String proxyServer, int proxyPort) + { + thisPort = port; + fwdServer = proxyServer; + fwdPort = proxyPort; + } + + public HttpProxy (int port, String proxyServer, int proxyPort, int timeout) + { + thisPort = port; + fwdServer = proxyServer; + fwdPort = proxyPort; + ptTimeout = timeout; + } + + + /* allow the user to decide whether or not to send debug + * output to the console or some other PrintStream + */ + public void setDebug (int level, PrintStream out) + { + debugLevel = level; + debugOut = out; + } + + + /* get the port that we're supposed to be listening on + */ + public int getPort () + { + return thisPort; + } + + + /* return whether or not the socket is currently open + */ + public boolean isRunning () + { + if (server == null) + return false; + else + return true; + } + + + /* closeSocket will close the open ServerSocket; use this + * to halt a running jProxy thread + */ + public void closeSocket () + { + try { + keepRunning = false; + // close the open server socket + server.close(); + // send it a message to make it stop waiting immediately + // (not really necessary) + /*Socket s = new Socket("localhost", thisPort); + OutputStream os = s.getOutputStream(); + os.write((byte)0); + os.close(); + s.close();*/ + } catch(Exception e) { + if (debugLevel > 0) + debugOut.println(e); + } + + server = null; + } + + + public void run() + { + try { + // create a server socket, and loop forever listening for + // client connections + server = new ServerSocket(thisPort); + + while (keepRunning) + { + Socket client = server.accept(); + ProxyThread t = new ProxyThread(client, doSocks, sProxy); + //t.setDebug(debugLevel, debugOut); + //t.setTimeout(ptTimeout); + t.start(); + } + } catch (Exception e) { + if (debugLevel > 0) + debugOut.println("jProxy Thread error: " + e); + } + + closeSocket(); + } + +} + + +/* + * The ProxyThread will take an HTTP request from the client + * socket and send it to either the server that the client is + * trying to contact, or another proxy server + */ +class ProxyThread extends Thread +{ + private Socket pSocket; + private String fwdServer = ""; + private int fwdPort = 0; + private int debugLevel = 0; + private PrintStream debugOut = System.out; + + // the socketTimeout is used to time out the connection to + // the remote server after a certain period of inactivity; + // the value is in milliseconds -- use zero if you don't want + // a timeout + public static final int DEFAULT_TIMEOUT = 20 * 1000; + private int socketTimeout = DEFAULT_TIMEOUT; + + private boolean doSocks = false; + + private static Socks5Proxy sProxy = null; + + public ProxyThread(Socket s, boolean doSocks, Socks5Proxy sProxy) + { + pSocket = s; + + this.sProxy = sProxy; + this.doSocks = doSocks; + } + + public ProxyThread(Socket s, String proxy, int port) + { + pSocket = s; + fwdServer = proxy; + fwdPort = port; + } + + + public void setTimeout (int timeout) + { + // assume that the user will pass the timeout value + // in seconds (because that's just more intuitive) + socketTimeout = timeout * 1000; + } + + + public void setDebug (int level, PrintStream out) + { + debugLevel = level; + debugOut = out; + } + + + public void run() + { + try + { + long startTime = System.currentTimeMillis(); + + // client streams (make sure you're using streams that use + // byte arrays, so things like GIF and JPEG files and file + // downloads will transfer properly) + BufferedInputStream clientIn = new BufferedInputStream(pSocket.getInputStream()); + BufferedOutputStream clientOut = new BufferedOutputStream(pSocket.getOutputStream()); + + // the socket to the remote server + Socket server = null; + + // other variables + byte[] request = null; + byte[] response = null; + int requestLength = 0; + int responseLength = 0; + int pos = -1; + StringBuffer host = new StringBuffer(""); + String hostName = ""; + int hostPort = 80; + + // get the header info (the web browser won't disconnect after + // it's sent a request, so make sure the waitForDisconnect + // parameter is false) + request = getHTTPData(clientIn, host, false); + requestLength = Array.getLength(request); + + // separate the host name from the host port, if necessary + // (like if it's "servername:8000") + hostName = host.toString(); + pos = hostName.indexOf(":"); + if (pos > 0) + { + try { hostPort = Integer.parseInt(hostName.substring(pos + 1)); + } catch (Exception e) { } + hostName = hostName.substring(0, pos); + } + + // either forward this request to another proxy server or + // send it straight to the Host + try + { + + if (!doSocks) + { + if ((fwdServer.length() > 0) && (fwdPort > 0)) + { + server = new Socket(fwdServer, fwdPort); + } else { + server = new Socket(hostName, hostPort); + } + } + else + { + + server = new SocksSocket(sProxy,hostName, hostPort); + } + + } catch (Exception e) { + // tell the client there was an error + String errMsg = "HTTP/1.0 500\nContent Type: text/plain\n\n" + + "Error connecting to the server:\n" + e + "\n"; + clientOut.write(errMsg.getBytes(), 0, errMsg.length()); + } + + if (server != null) + { + server.setSoTimeout(socketTimeout); + BufferedInputStream serverIn = new BufferedInputStream(server.getInputStream()); + BufferedOutputStream serverOut = new BufferedOutputStream(server.getOutputStream()); + + // send the request out + serverOut.write(request, 0, requestLength); + serverOut.flush(); + + // and get the response; if we're not at a debug level that + // requires us to return the data in the response, just stream + // it back to the client to save ourselves from having to + // create and destroy an unnecessary byte array. Also, we + // should set the waitForDisconnect parameter to 'true', + // because some servers (like Google) don't always set the + // Content-Length header field, so we have to listen until + // they decide to disconnect (or the connection times out). + if (debugLevel > 1) + { + response = getHTTPData(serverIn, true); + responseLength = Array.getLength(response); + } else { + responseLength = streamHTTPData(serverIn, clientOut, true); + } + + serverIn.close(); + serverOut.close(); + } + + // send the response back to the client, if we haven't already + if (debugLevel > 1) + clientOut.write(response, 0, responseLength); + + // if the user wants debug info, send them debug info; however, + // keep in mind that because we're using threads, the output won't + // necessarily be synchronous + if (debugLevel > 0) + { + long endTime = System.currentTimeMillis(); + debugOut.println("Request from " + pSocket.getInetAddress().getHostAddress() + + " on Port " + pSocket.getLocalPort() + + " to host " + hostName + ":" + hostPort + + "\n (" + requestLength + " bytes sent, " + + responseLength + " bytes returned, " + + Long.toString(endTime - startTime) + " ms elapsed)"); + debugOut.flush(); + } + if (debugLevel > 1) + { + debugOut.println("REQUEST:\n" + (new String(request))); + debugOut.println("RESPONSE:\n" + (new String(response))); + debugOut.flush(); + } + + // close all the client streams so we can listen again + clientOut.close(); + clientIn.close(); + pSocket.close(); + } catch (Exception e) { + if (debugLevel > 0) + debugOut.println("Error in ProxyThread: " + e); + //e.printStackTrace(); + } + + } + + + private byte[] getHTTPData (InputStream in, boolean waitForDisconnect) + { + // get the HTTP data from an InputStream, and return it as + // a byte array + // the waitForDisconnect parameter tells us what to do in case + // the HTTP header doesn't specify the Content-Length of the + // transmission + StringBuffer foo = new StringBuffer(""); + return getHTTPData(in, foo, waitForDisconnect); + } + + + private byte[] getHTTPData (InputStream in, StringBuffer host, boolean waitForDisconnect) + { + // get the HTTP data from an InputStream, and return it as + // a byte array, and also return the Host entry in the header, + // if it's specified -- note that we have to use a StringBuffer + // for the 'host' variable, because a String won't return any + // information when it's used as a parameter like that + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + streamHTTPData(in, bs, host, waitForDisconnect); + return bs.toByteArray(); + } + + + private int streamHTTPData (InputStream in, OutputStream out, boolean waitForDisconnect) + { + StringBuffer foo = new StringBuffer(""); + return streamHTTPData(in, out, foo, waitForDisconnect); + } + + private int streamHTTPData (InputStream in, OutputStream out, + StringBuffer host, boolean waitForDisconnect) + { + // get the HTTP data from an InputStream, and send it to + // the designated OutputStream + StringBuffer header = new StringBuffer(""); + String data = ""; + int responseCode = 200; + int contentLength = 0; + int pos = -1; + int byteCount = 0; + + try + { + // get the first line of the header, so we know the response code + data = readLine(in); + if (data != null) + { + header.append(data + "\r\n"); + pos = data.indexOf(" "); + if ((data.toLowerCase().startsWith("http")) && + (pos >= 0) && (data.indexOf(" ", pos+1) >= 0)) + { + String rcString = data.substring(pos+1, data.indexOf(" ", pos+1)); + try + { + responseCode = Integer.parseInt(rcString); + } catch (Exception e) { + if (debugLevel > 0) + debugOut.println("Error parsing response code " + rcString); + } + } + } + + // get the rest of the header info + while ((data = readLine(in)) != null) + { + // the header ends at the first blank line + if (data.length() == 0) + break; + header.append(data + "\r\n"); + + // check for the Host header + pos = data.toLowerCase().indexOf("host:"); + if (pos >= 0) + { + host.setLength(0); + host.append(data.substring(pos + 5).trim()); + } + + // check for the Content-Length header + pos = data.toLowerCase().indexOf("content-length:"); + if (pos >= 0) + contentLength = Integer.parseInt(data.substring(pos + 15).trim()); + } + + // add a blank line to terminate the header info + header.append("\r\n"); + + // convert the header to a byte array, and write it to our stream + out.write(header.toString().getBytes(), 0, header.length()); + + // if the header indicated that this was not a 200 response, + // just return what we've got if there is no Content-Length, + // because we may not be getting anything else + if ((responseCode != 200) && (contentLength == 0)) + { + out.flush(); + return header.length(); + } + + // get the body, if any; we try to use the Content-Length header to + // determine how much data we're supposed to be getting, because + // sometimes the client/server won't disconnect after sending us + // information... + if (contentLength > 0) + waitForDisconnect = false; + + if ((contentLength > 0) || (waitForDisconnect)) + { + try { + byte[] buf = new byte[4096]; + int bytesIn = 0; + while ( ((byteCount < contentLength) || (waitForDisconnect)) + && ((bytesIn = in.read(buf)) >= 0) ) + { + out.write(buf, 0, bytesIn); + byteCount += bytesIn; + } + } catch (Exception e) { + String errMsg = "Error getting HTTP body: " + e; + if (debugLevel > 0) + debugOut.println(errMsg); + //bs.write(errMsg.getBytes(), 0, errMsg.length()); + } + } + } catch (Exception e) { + if (debugLevel > 0) + debugOut.println("Error getting HTTP data: " + e); + } + + //flush the OutputStream and return + try { out.flush(); } catch (Exception e) {} + return (header.length() + byteCount); + } + + + private String readLine (InputStream in) + { + // reads a line of text from an InputStream + StringBuffer data = new StringBuffer(""); + int c; + + try + { + // if we have nothing to read, just return null + in.mark(1); + if (in.read() == -1) + return null; + else + in.reset(); + + while ((c = in.read()) >= 0) + { + // check for an end-of-line character + if ((c == 0) || (c == 10) || (c == 13)) + break; + else + data.append((char)c); + } + + // deal with the case where the end-of-line terminator is \r\n + if (c == 13) + { + in.mark(1); + if (in.read() != 10) + in.reset(); + } + } catch (Exception e) { + if (debugLevel > 0) + debugOut.println("Error getting header: " + e); + } + + // and return what we have + return data.toString(); + } + +} + diff --git a/src/org/torproject/android/SocksClient.java b/src/org/torproject/android/SocksClient.java new file mode 100644 index 00000000..188ebc1e --- /dev/null +++ b/src/org/torproject/android/SocksClient.java @@ -0,0 +1,129 @@ +/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */ +/* See LICENSE for licensing information */ +/** SOCKS aware echo client*/ + +package org.torproject.android; + +import java.io.*; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import net.sourceforge.jsocks.socks.*; + + +public class SocksClient implements Runnable { + + private int port; + private InetAddress hostIP; + + private Socket ss; + private InputStream in; + private OutputStream out; + + private static final int BUF_SIZE = 1024; + + public SocksClient(String host,int port) + throws IOException,UnknownHostException,SocksException{ + this.port = port; + + ss = new SocksSocket(host, port); + out = ss.getOutputStream(); + in = ss.getInputStream(); + System.out.println("Connected..."); + System.out.println("TO: "+host+":"+port); + System.out.println("ViaProxy: "+ss.getLocalAddress().getHostAddress() + +":"+ss.getLocalPort()); + + } + + public void close()throws IOException{ + ss.close(); + } + public void send(String s) throws IOException{ + out.write(s.getBytes()); + } + + public void run(){ + byte[] buf = new byte[1024]; + int bytes_read; + try{ + while((bytes_read = in.read(buf)) > 0){ + System.out.write(buf,0,bytes_read); + } + }catch(IOException io_ex){ + io_ex.printStackTrace(); + } + } + + public static void usage(){ + System.err.print( + "Usage: java SocksTest host port [socksHost socksPort]\n"); + } + + + public static void main(String args[]){ + int port; + String host; + int proxyPort; + String proxyHost; + + if(args.length > 1 && args.length < 5){ + try{ + + host = args[0]; + port = Integer.parseInt(args[1]); + + proxyPort =(args.length > 3)? Integer.parseInt(args[3]) + : TorConstants.PORT_SOCKS; + + host = args[0]; + proxyHost =(args.length > 2)? args[2] + : TorConstants.IP_LOCALHOST; + + Proxy.setDefaultProxy(proxyHost,proxyPort,"KOUKY001"); + //Proxy.setDefaultProxy(proxyHost,proxyPort); + InetRange inetRange = new InetRange(); + inetRange.add(InetAddress.getByName("localhost")); + Proxy.getDefaultProxy().setDirect(inetRange); + + + SocksClient st = new SocksClient(host,port); + Thread thread = new Thread(st); + thread.start(); + + BufferedReader in = new BufferedReader( + new InputStreamReader(System.in)); + String s; + + s = in.readLine(); + while(s != null){ + st.send(s+"\r\n"); + //try{ + //Thread.currentThread().sleep(10); + //}catch(InterruptedException i_ex){ + //} + s = in.readLine(); + } + st.close(); + System.exit(1); + + }catch(SocksException s_ex){ + System.err.println("SocksException:"+s_ex); + s_ex.printStackTrace(); + System.exit(1); + }catch(IOException io_ex){ + io_ex.printStackTrace(); + System.exit(1); + }catch(NumberFormatException num_ex){ + usage(); + num_ex.printStackTrace(); + System.exit(1); + } + + }else{ + usage(); + } + } + +}//End of class diff --git a/src/org/torproject/android/TorBinaryInstaller.java b/src/org/torproject/android/TorBinaryInstaller.java new file mode 100644 index 00000000..e69af7b1 --- /dev/null +++ b/src/org/torproject/android/TorBinaryInstaller.java @@ -0,0 +1,160 @@ +/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */ +/* See LICENSE for licensing information */ + +package org.torproject.android; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import android.util.Log; + +public class TorBinaryInstaller implements TorConstants { + + private final static String LOG_TAG = "Tor"; + + + public TorBinaryInstaller () + { + } + + /* + * Start the binary installation if the file doesn't exist or is forced + */ + public void start (boolean force) + { + boolean binaryExists = new File(TOR_BINARY_INSTALL_PATH).exists(); + + Log.i(LOG_TAG,"Tor binary exists=" + binaryExists); + + if (!binaryExists || force) + installFromZip (); + + } + + /* + * Extract the Tor binary from the APK file using ZIP + */ + private void installFromZip () + { + + try + { + ZipFile zip = new ZipFile(APK_PATH); + + ZipEntry zipen = zip.getEntry(TOR_BINARY_ZIP_KEY); + streamToFile(zip.getInputStream(zipen),TOR_BINARY_INSTALL_PATH); + + zipen = zip.getEntry(TORRC_ZIP_KEY); + streamToFile(zip.getInputStream(zipen),TORRC_INSTALL_PATH); + + zip.close(); + + Log.i(LOG_TAG,"SUCCESS: unzipped tor binary from apk"); + + } + catch (IOException ioe) + { + Log.i(LOG_TAG,"FAIL: unable to unzip tor binary from apk",ioe); + + } + } + + /* + * Write the inputstream contents to the file + */ + private static void streamToFile(InputStream stm, String targetFilename) + + { + + FileOutputStream stmOut = null; + + byte[] buffer = new byte[FILE_WRITE_BUFFER_SIZE]; + + int bytecount; + + + File outFile = new File(targetFilename); + + try { + outFile.createNewFile(); + + stmOut = new FileOutputStream(outFile); + } + + catch (java.io.IOException e) + + { + + Log.i(LOG_TAG,"Error opening output file " + targetFilename,e); + + return; + } + + + + try + + { + + while ((bytecount = stm.read(buffer)) > 0) + + { + + stmOut.write(buffer, 0, bytecount); + + } + + stmOut.close(); + + } + + catch (java.io.IOException e) + + { + + Log.i(LOG_TAG,"Error writing output file '" + targetFilename + "': " + e.toString()); + + return; + + } + + } + + //copy the file from inputstream to File output - alternative impl + public void copyFile (InputStream is, File outputFile) + { + + try { + outputFile.createNewFile(); + DataOutputStream out = new DataOutputStream(new FileOutputStream(outputFile)); + DataInputStream in = new DataInputStream(is); + + int b; + byte[] data = new byte[1024]; + + while ((b = in.read(data)) != -1) { + out.write(data); + } + // + out.flush(); + out.close(); + in.close(); + // chmod? + + + + } catch (IOException ex) { + Log.e(LOG_TAG, "error copying binary", ex); + } + + } + + + +} diff --git a/src/org/torproject/android/TorConstants.java b/src/org/torproject/android/TorConstants.java new file mode 100644 index 00000000..92b34efb --- /dev/null +++ b/src/org/torproject/android/TorConstants.java @@ -0,0 +1,75 @@ +/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */ +/* See LICENSE for licensing information */ + +package org.torproject.android; + +public interface TorConstants { + + //home directory of Android application + public final static String TOR_HOME = "/data/data/org.torproject.android/"; + + //name of the tor C binary + public final static String TOR_BINARY_ASSET_KEY = "tor"; + + //path to install the Tor binary too + public final static String TOR_BINARY_INSTALL_PATH = TOR_HOME + TOR_BINARY_ASSET_KEY; + + //key of the tor binary in the Zip file + public final static String TOR_BINARY_ZIP_KEY = "assets/" + TOR_BINARY_ASSET_KEY; + + //torrc file name + public final static String TORRC_ASSET_KEY = "torrc"; + + //path to install torrc to within the android app data folder + public final static String TORRC_INSTALL_PATH = TOR_HOME + TORRC_ASSET_KEY; + + //key of the torrc file in the Zip file + public final static String TORRC_ZIP_KEY = "assets/" + TORRC_ASSET_KEY; + + //where to send the notices log + public final static String TOR_LOG_PATH = TOR_HOME + "notices.log"; + + //control port cookie path + public final static String TOR_CONTROL_AUTH_COOKIE = TOR_HOME + "data/control_auth_cookie"; + + //how to launch tor + public final static String TOR_COMMAND_LINE_ARGS = "-f " + TORRC_INSTALL_PATH; + + //various console cmds + public final static String SHELL_CMD_CHMOD = "/system/bin/chmod"; + public final static String SHELL_CMD_KILL = "/system/bin/kill"; + public final static String SHELL_CMD_RM = "/system/bin/rm"; + public final static String SHELL_CMD_PS = "ps"; + public final static String CHMOD_EXE_VALUE = "777"; + + //path of the installed APK file + public final static String APK_PATH = "/data/app/org.torproject.android.apk"; + + //path to check Tor against + public final static String URL_TOR_CHECK = "http://check.torproject.org"; + + public final static int FILE_WRITE_BUFFER_SIZE = 2048; + + //HTTP Proxy server port + public final static int PORT_HTTP = 8118; //just like Privoxy! + + //Socks port client connects to, server is the Tor binary + public final static int PORT_SOCKS = 9050; + + //what is says! + public final static String IP_LOCALHOST = "127.0.0.1"; + public final static int TOR_CONTROL_PORT = 9051; + public final static int UPDATE_TIMEOUT = 3000; + + public final static String DEFAULT_HOME_PAGE = "file:///android_asset/help.html";// "http://check.torproject.org"; + + //status to communicate state + public final static int STATUS_OFF = 0; + public final static int STATUS_ON = 1; + public final static int STATUS_STARTING_UP = 2; + public final static int STATUS_SHUTTING_DOWN = 3; + + //control port + public final static String TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE = "Bootstrapped 100%"; + +} diff --git a/src/org/torproject/android/TorControlPanel.java b/src/org/torproject/android/TorControlPanel.java new file mode 100644 index 00000000..546cc00b --- /dev/null +++ b/src/org/torproject/android/TorControlPanel.java @@ -0,0 +1,587 @@ +/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */ +/* See LICENSE for licensing information */ + +package org.torproject.android; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + + +import net.freehaven.tor.control.EventHandler; + + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.webkit.JsResult; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +public class TorControlPanel extends Activity implements OnClickListener, TorConstants, EventHandler +{ + + private final static String TAG = "Tor"; + + private static Intent torService = null; + + private boolean updateLog = false; + private boolean updateStatus = false; + + private TextView lblStatus = null; + private ImageView imgStatus = null; + private String txtStatus = ""; + private int torStatus = STATUS_OFF; + + private Thread threadStatus = null; + + private WebView mWebView; + + private int currentView = 0; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(android.R.style.Theme_Black); + + + showMain(); + + + } + + + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + MenuItem mItem = menu.add(0, 1, Menu.NONE, "Home"); + MenuItem mItem2 = menu.add(0, 2, Menu.NONE, "Settings"); + MenuItem mItem3 = menu.add(0, 3, Menu.NONE, "Log"); + MenuItem mItem4 = menu.add(0, 4, Menu.NONE, "Help"); + + mItem.setIcon(R.drawable.ic_menu_home); + mItem2.setIcon(R.drawable.ic_menu_register); + mItem3.setIcon(R.drawable.ic_menu_reports); + mItem4.setIcon(R.drawable.ic_menu_about); + + return true; + } + + /* (non-Javadoc) + * @see android.app.Activity#onMenuItemSelected(int, android.view.MenuItem) + */ + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + + super.onMenuItemSelected(featureId, item); + + if (item.getItemId() == 1) + { + this.showMain(); + } + else if (item.getItemId() == 2) + { + this.showSettings(); + } + else if (item.getItemId() == 3) + { + this.showMessageLog(); + } + else if (item.getItemId() == 4) + { + this.showWeb(DEFAULT_HOME_PAGE); + } + + return true; + } + + public boolean onKeyDown(int keyCode, KeyEvent event){ + if(keyCode==KeyEvent.KEYCODE_BACK){ + if(currentView != R.layout.layout_main){ + + showMain (); + + return true; + } + else{ + return super.onKeyDown(keyCode, event); + } + } + + return super.onKeyDown(keyCode, event); + + } + + /* (non-Javadoc) + * @see android.app.Activity#onPause() + */ + @Override + protected void onPause() { + // TODO Auto-generated method stub + super.onPause(); + + TorService.setStatus(torStatus); + } + + + + + /* (non-Javadoc) + * @see android.app.Activity#onResume() + */ + @Override + protected void onResume() { + // TODO Auto-generated method stub + super.onResume(); + + torStatus = TorService.getStatus(); + + updateStatus (); + } + + + + + /* (non-Javadoc) + * @see android.app.Activity#onStart() + */ + @Override + protected void onStart() { + // TODO Auto-generated method stub + super.onStart(); + + torStatus = TorService.getStatus(); + + + updateStatus (); + } + + + + + /* (non-Javadoc) + * @see android.app.Activity#onStop() + */ + @Override + protected void onStop() { + // TODO Auto-generated method stub + super.onStop(); + + TorService.setStatus(torStatus); + } + + + + /* + * Show the main form UI + */ + private void showMain () + { + updateLog = false; + updateStatus = true; + + currentView = R.layout.layout_main; + setContentView(currentView); + + findViewById(R.id.imgStatus).setOnClickListener(this); + + lblStatus = (TextView)findViewById(R.id.lblStatus); + imgStatus = (ImageView)findViewById(R.id.imgStatus); + + updateStatus(); + } + + private void showWeb (String url) + { + + + currentView =R.layout.layout_web; + setContentView(currentView); + + mWebView = (WebView) findViewById(R.id.webview); + + WebSettings webSettings = mWebView.getSettings(); + webSettings.setSavePassword(false); + webSettings.setSaveFormData(false); + webSettings.setJavaScriptEnabled(true); + + + mWebView.setWebChromeClient(new MyWebChromeClient()); + + mWebView.loadUrl(url); + + + } + + + /* + * Show the message log UI + */ + private void showMessageLog () + { + currentView = R.layout.layout_log; + setContentView(currentView); + ((Button)findViewById(R.id.btnLogClear)).setOnClickListener(this); + + updateStatus = false; + updateLog = true; + + Thread thread = new Thread () + { + public void run () + { + + while (updateLog) + { + + try { + Thread.sleep(UPDATE_TIMEOUT); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + handler.sendEmptyMessage(0); + } + } + }; + + thread.start(); + + } + + /* + * Load the Tor log and display it in a text field + */ + private void updateMessageLog () + { + + TextView tvLog = (TextView)findViewById(R.id.messageLog); + + if (tvLog != null) + { + String output = loadTextFile(TOR_LOG_PATH); + + tvLog.setText(output); + } + + } + + /* + * Handle to reload Tor debug log every few seconds while viewing it + */ + private Handler handler = new Handler() { + + @Override + public void handleMessage(Message msg) { + + updateMessageLog (); + + } + + }; + + /* + * Handle to reload Tor debug log every few seconds while viewing it + */ + private Handler handlerStatus = new Handler() { + + @Override + public void handleMessage(Message msg) { + + updateStatus(); + + // Toast.makeText(this,txtStatus, Toast.LENGTH_SHORT).show(); + } + + }; + + + /* + * Load the basic settings application to display torrc + * TODO: these needs to be improved into an actual form GUI + */ + private void showSettings () + { + updateStatus = false; + updateLog = false; + + currentView = R.layout.layout_settings; + setContentView(currentView); + + + String output = loadTextFile(TORRC_INSTALL_PATH); + + TextView tvSettings = (TextView)findViewById(R.id.textSettings); + ((Button)findViewById(R.id.btnSettingsSave)).setOnClickListener(this); + tvSettings.setText(output); + + } + + + /* + * Set the state of the running/not running graphic and label + */ + public void updateStatus () + { + + if (imgStatus != null) + { + + if (torStatus == STATUS_ON) + { + imgStatus.setImageResource(R.drawable.toron); + lblStatus.setText("ORbot is running\n- touch the bot to stop -"); + updateStatus = false; + } + else if (torStatus == STATUS_STARTING_UP) + { + imgStatus.setImageResource(R.drawable.torstarting); + + lblStatus.setText("ORbot reports:\n\"" + txtStatus + "\""); + + + } + else if (torStatus == STATUS_SHUTTING_DOWN) + { + imgStatus.setImageResource(R.drawable.torstopping); + lblStatus.setText("ORbot is shutting down\nplease wait..."); + + } + else + { + imgStatus.setImageResource(R.drawable.toroff); + lblStatus.setText("ORbot is not running\n- touch the bot to start -"); + updateStatus = false; + } + } + + + + } + + /* + * (non-Javadoc) + * @see android.view.View.OnClickListener#onClick(android.view.View) + */ + public void onClick(View view) { + + // the start button + if (view.getId()==R.id.imgStatus) + { + //if Tor binary is not running, then start the service up + if (TorService.getStatus()==STATUS_OFF) + { + torStatus = STATUS_STARTING_UP; + txtStatus = "Connecting to Tor..."; + updateStatus(); + + startTorService (); + + + } + else + { + + torStatus = STATUS_SHUTTING_DOWN; + updateStatus(); + + stopService(torService); + + torStatus = STATUS_OFF; + + updateStatus(); + } + + } + else if (view.getId()==R.id.btnLogClear) + { + + saveTextFile(TOR_LOG_PATH,""); + } + else if (view.getId()==R.id.btnSettingsSave) + { + + TextView tvSettings = (TextView)findViewById(R.id.textSettings); + String newSettings = tvSettings.getText().toString(); + saveTextFile(TORRC_INSTALL_PATH, newSettings); + + } + + + } + + private void startTorService () + { + if (torService == null) + { + torService = new Intent(this, TorService.class); + //torService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + TorService.setActivity(this); + } + + startService(torService); + + + + } + + /* + * Load the log file text + */ + public static String loadTextFile (String path) + { + String line = null; + + StringBuffer out = new StringBuffer(); + + try { + BufferedReader reader = new BufferedReader((new FileReader(new File(path)))); + + while ((line = reader.readLine()) != null) + { + out.append(line); + out.append('\n'); + + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return out.toString(); + + } + + + /* + * Load the log file text + */ + public static boolean saveTextFile (String path, String contents) + { + + try { + + FileWriter writer = new FileWriter( path, false ); + writer.write( contents ); + + writer.close(); + + + + return true; + + } catch (IOException e) { + // Log.i(TAG, "error writing file: " + path, e); + e.printStackTrace(); + return false; + } + + + + } + + + @Override + public void bandwidthUsed(long read, long written) { + Log.i(TAG,"BW Used: read=" + read + " written=" + written); + + } + + + @Override + public void circuitStatus(String status, String circID, String path) { + Log.i(TAG,"CircuitStatus=" + status + ": " + circID); + + } + + + @Override + public void message(String severity, String msg) { + + // Log.println(priority, tag, msg)("["+severity+"] "+msg); + //Toast.makeText(, text, duration) + // Toast.makeText(ACTIVITY, severity + ": " + msg, Toast.LENGTH_SHORT); + Log.i(TAG, "[Tor Control Port] " + severity + ": " + msg); + + if (msg.indexOf(TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)!=-1) + { + torStatus = STATUS_ON; + + + + //setupWebProxy(true); + + } + + + txtStatus = msg; + handlerStatus.sendEmptyMessage(0); + + + } + + + @Override + public void newDescriptors(List orList) { + // TODO Auto-generated method stub + + } + + + @Override + public void orConnStatus(String status, String orName) { + + Log.i(TAG,"OrConnStatus=" + status + ": " + orName); + + } + + + @Override + public void streamStatus(String status, String streamID, String target) { + Log.i(TAG,"StreamStatus=" + status + ": " + streamID); + + } + + + @Override + public void unrecognized(String type, String msg) { + Log.i(TAG,"unrecognized log=" + type + ": " + msg); + + } + + + /** + * Provides a hook for calling "alert" from javascript. Useful for + * debugging your javascript. + */ + final class MyWebChromeClient extends WebChromeClient { + @Override + public boolean onJsAlert(WebView view, String url, String message, JsResult result) { + Log.d(TAG, message); + result.confirm(); + return true; + } + + + } + +} \ No newline at end of file diff --git a/src/org/torproject/android/TorService.java b/src/org/torproject/android/TorService.java new file mode 100644 index 00000000..bb1cca97 --- /dev/null +++ b/src/org/torproject/android/TorService.java @@ -0,0 +1,617 @@ +/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */ +/* See LICENSE for licensing information */ + +package org.torproject.android; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.ConnectException; +import java.net.Socket; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.Timer; +import java.util.TimerTask; + +import net.freehaven.tor.control.EventHandler; +import net.freehaven.tor.control.NullEventHandler; +import net.freehaven.tor.control.TorControlConnection; +import net.sourceforge.jsocks.socks.Proxy; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; +import android.widget.Toast; + +public class TorService extends Service implements TorConstants +{ + + private static TorControlPanel ACTIVITY = null; + + private final static String TAG = "TorService"; + + private static HttpProxy webProxy = null; + + private static int currentStatus = STATUS_OFF; + + private TorControlConnection conn = null; + + private Timer timer = new Timer (); + private final static int UPDATE_INTERVAL = 60000; + + /** Called when the activity is first created. */ + @Override + public void onCreate() { + super.onCreate(); + + Log.i(TAG,"TorService: onCreate"); + + timer.scheduleAtFixedRate( + new TimerTask() { + public void run() { + + //do nothing + // Log.i(TAG,"TorService: task is running"); + } + }, + 0, + UPDATE_INTERVAL); + + + int procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH); + + if (procId != -1) + { + Log.i(TAG,"Found existing Tor process"); + + try { + currentStatus = STATUS_STARTING_UP; + + initControlConnection(); + + getTorStatus(); + + if (webProxy != null) + { + if (webProxy.isRunning()) + { + //do nothing + Log.i(TAG, "Web Proxy is already running"); + } + else + { + //do nothing + Log.i(TAG, "killing Web Proxy"); + webProxy.closeSocket(); + setupWebProxy(true); + } + } + else //do something + { + setupWebProxy(true); + } + + currentStatus = STATUS_ON; + + } catch (RuntimeException e) { + Log.i(TAG,"Unable to connect to existing Tor instance,",e); + currentStatus = STATUS_OFF; + this.stopTor(); + + } catch (Exception e) { + Log.i(TAG,"Unable to connect to existing Tor instance,",e); + currentStatus = STATUS_OFF; + this.stopTor(); + + } + } + + } + + + /* (non-Javadoc) + * @see android.app.Service#onLowMemory() + */ + @Override + public void onLowMemory() { + // TODO Auto-generated method stub + super.onLowMemory(); + } + + + /* (non-Javadoc) + * @see android.app.Service#onUnbind(android.content.Intent) + */ + @Override + public boolean onUnbind(Intent intent) { + // TODO Auto-generated method stub + return super.onUnbind(intent); + } + + + public static int getStatus () + { + + return currentStatus; + + } + + public static void setStatus (int newStatus) + { + currentStatus = newStatus; + } + + + /* (non-Javadoc) + * @see android.app.Service#onRebind(android.content.Intent) + */ + @Override + public void onRebind(Intent intent) { + // TODO Auto-generated method stub + super.onRebind(intent); + + Log.i(TAG,"on rebind"); + } + + + /* (non-Javadoc) + * @see android.app.Service#onStart(android.content.Intent, int) + */ + @Override + public void onStart(Intent intent, int startId) { + // TODO Auto-generated method stub + super.onStart(intent, startId); + + Log.i(TAG,"onStart called"); + + initTor(); + + setupWebProxy (true); + } + + + public void onDestroy () + { + super.onDestroy(); + + Log.i(TAG,"onDestroy called"); + + if (timer != null) timer.cancel(); + + stopTor(); + } + + private void stopTor () + { + currentStatus = STATUS_SHUTTING_DOWN; + + setupWebProxy(false); + + killTorProcess (); + + currentStatus = STATUS_OFF; + + } + + + public static void setActivity(TorControlPanel activity) { + ACTIVITY = activity; + } + + private void setupWebProxy (boolean enabled) + { + if (enabled) + { + + if (webProxy != null) + { + webProxy.closeSocket(); + webProxy = null; + + } + + Log.i(TAG,"Starting up Web Proxy on port: " + PORT_HTTP); + //httpd s + webProxy = new HttpProxy(PORT_HTTP); + webProxy.setDoSocks(true); + webProxy.start(); + + //socks + try + { + + Proxy.setDefaultProxy(IP_LOCALHOST,PORT_SOCKS); + + + } + catch (Exception e) + { + Log.w(TAG,e.getMessage()); + } + + Log.i(TAG,"Web Proxy enabled..."); + + + //Settings.System.putString(getContentResolver(), Settings.System.HTTP_PROXY, proxySetting);//enable proxy + // Settings.Secure.putString(getContentResolver(), Settings.Secure.HTTP_PROXY, proxySetting);//enable proxy + + } + else + { + //Log.i(TAG,"Turning off Socks/Tor routing on Web Proxy"); + + if (webProxy != null) + { + //logNotice("Tor is disabled - browsing is not anonymous!"); + //webProxy.setDoSocks(false); + + webProxy.closeSocket(); + webProxy = null; + Log.i(TAG,"WebProxy ServerSocket closed"); + } + } + + } + + public void reloadConfig () + { + try + { + if (conn == null) + { + initControlConnection (); + } + + if (conn != null) + { + conn.signal("RELOAD"); + } + } + catch (Exception e) + { + Log.i(TAG,"Unable to reload configuration",e); + } + } + + private void killTorProcess () + { + + if (conn != null) + { + try { + Log.i(TAG,"sending SHUTDOWN signal"); + conn.signal("SHUTDOWN"); + } catch (IOException e) { + // TODO Auto-generated catch block + Log.i(TAG,"error shutting down Tor via connection",e); + } + conn = null; + } + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + + } + + int procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH); + + while (procId != -1) + { + + Log.i(TAG,"Found Tor PID=" + procId + " - killing now..."); + + doCommand(SHELL_CMD_KILL, procId + ""); + + procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH); + } + + + } + + private static void logNotice (String msg) + { + + Log.i(TAG, msg); + + } + + private void checkBinary () + { + + boolean binaryExists = new File(TOR_BINARY_INSTALL_PATH).exists(); + + if (!binaryExists) + { + killTorProcess (); + + TorBinaryInstaller installer = new TorBinaryInstaller(); + installer.start(true); + + binaryExists = new File(TOR_BINARY_INSTALL_PATH).exists(); + if (binaryExists) + { + logNotice("Tor binary installed!"); + + } + else + { + logNotice("Tor binary install FAILED!"); + return; + } + } + + Log.i(TAG,"Setting permission on Tor binary"); + doCommand(SHELL_CMD_CHMOD, CHMOD_EXE_VALUE + ' ' + TOR_BINARY_INSTALL_PATH); + } + + public void initTor () + { + try { + + currentStatus = STATUS_STARTING_UP; + + killTorProcess (); + + checkBinary (); + + doCommand(SHELL_CMD_RM,TOR_LOG_PATH); + + Log.i(TAG,"Starting tor process"); + doCommand(TOR_BINARY_INSTALL_PATH, TOR_COMMAND_LINE_ARGS); + + int procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH); + + if (procId == -1) + { + doCommand(TOR_BINARY_INSTALL_PATH, TOR_COMMAND_LINE_ARGS); + procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH); + } + + Log.i(TAG,"Tor process id=" + procId); + + currentStatus = STATUS_STARTING_UP; + logNotice("Tor is starting up..."); + + Thread.sleep(500); + initControlConnection (); + + } catch (Exception e) { + + Log.w(TAG,"unable to start Tor Process",e); + + e.printStackTrace(); + + } + + } + + private static void logStream (InputStream is) + { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + String line = null; + + + try { + while ((line = reader.readLine()) != null) + { + Log.i(TAG, line); + + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + } + + + + @Override + public IBinder onBind(Intent arg0) { + // TODO Auto-generated method stub + return null; + } + + public static int findProcessId(String command) + { + int procId = -1; + + Runtime r = Runtime.getRuntime(); + + Process procPs = null; + + try { + + procPs = r.exec(SHELL_CMD_PS); + + BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream())); + String line = null; + + while ((line = reader.readLine())!=null) + { + if (line.indexOf(command)!=-1) + { + + StringTokenizer st = new StringTokenizer(line," "); + st.nextToken(); //proc owner + + procId = Integer.parseInt(st.nextToken().trim()); + + break; + } + } + + } catch (Exception e) { + Log.e(TAG, "error: " + e.getMessage(), e); + } + + return procId; + + } + + public static Process doCommand(String command, String arg1) + { + + Runtime r = Runtime.getRuntime(); + + Process child = null; + + try { + if(child != null) { + child.destroy(); + child = null; + } + + child = r.exec(command + ' ' + arg1); + + + + } catch (Exception e) { + Log.e(TAG, "error: " + e.getMessage()); + } + + return child; + + } + + public static String generateHashPassword () + { + /* + PasswordDigest d = PasswordDigest.generateDigest(); + byte[] s = d.getSecret(); // pass this to authenticate + String h = d.getHashedPassword(); // pass this to the Tor on startup. +*/ + return null; + } + + public void initControlConnection () throws Exception, RuntimeException + { + for (int i = 0; i < 50; i++) + { + try + { + Log.i(TAG,"Connecting to control port: " + TOR_CONTROL_PORT); + Socket s = new Socket(IP_LOCALHOST, TOR_CONTROL_PORT); + conn = TorControlConnection.getConnection(s); + // conn.authenticate(new byte[0]); // See section 3.2 + + Log.i(TAG,"SUCCESS connected to control port"); + + // + File fileCookie = new File(TOR_CONTROL_AUTH_COOKIE); + byte[] cookie = new byte[(int)fileCookie.length()]; + new FileInputStream(new File(TOR_CONTROL_AUTH_COOKIE)).read(cookie); + conn.authenticate(cookie); + + Log.i(TAG,"SUCCESS authenticated to control port"); + + addEventHandler(); + + break; //don't need to retry + } + catch (ConnectException ce) + { + Log.i(TAG,"Attempt " + i + ": Error connecting to control port; retrying..."); + Thread.sleep(1000); + } + } + + + + } + + public void modifyConf () throws IOException + { + // Get one configuration variable. + List options = conn.getConf("contact"); + // Get a set of configuration variables. + // List options = conn.getConf(Arrays.asList(new String[]{ + // "contact", "orport", "socksport"})); + // Change a single configuration variable + conn.setConf("BandwidthRate", "1 MB"); + // Change several configuration variables + conn.setConf(Arrays.asList(new String[]{ + "HiddenServiceDir /home/tor/service1", + "HiddenServicePort 80", + })); + // Reset some variables to their defaults + conn.resetConf(Arrays.asList(new String[]{ + "contact", "socksport" + })); + // Flush the configuration to disk. + conn.saveConf(); + + } + + private void getTorStatus () throws IOException + { + try + { + + + + + if (conn != null) + { + // get a single value. + + // get several values + + if (currentStatus == STATUS_STARTING_UP) + { + //Map vals = conn.getInfo(Arrays.asList(new String[]{ + // "status/bootstrap-phase", "status","version"})); + + String bsPhase = conn.getInfo("status/bootstrap-phase"); + // Log.i(TAG, "bootstrap-phase: " + bsPhase); + + if (bsPhase.indexOf("PROGRESS=100")!=-1) + { + currentStatus = STATUS_ON; + } + } + else + { + // String status = conn.getInfo("status/circuit-established"); + // Log.i(TAG, "status/circuit-established=" + status); + } + } + else + { + currentStatus = STATUS_OFF; + } + } + catch (Exception e) + { + Log.i(TAG, "Unable to get Tor status from control port"); + } + + } + + + public void addEventHandler () throws IOException + { + // We extend NullEventHandler so that we don't need to provide empty + // implementations for all the events we don't care about. + // ... + Log.i(TAG,"adding control port event handler"); + + conn.setEventHandler(ACTIVITY); + + conn.setEvents(Arrays.asList(new String[]{ + "ORCONN", "CIRC", "NOTICE", "ERR"})); + // conn.setEvents(Arrays.asList(new String[]{ + // "DEBUG", "INFO", "NOTICE", "WARN", "ERR"})); + + Log.i(TAG,"SUCCESS added control port event handler"); + + } +} \ No newline at end of file