diff --git a/src/org/torproject/android/Orbot.java b/src/org/torproject/android/Orbot.java index c8a33f16..9527bc3b 100644 --- a/src/org/torproject/android/Orbot.java +++ b/src/org/torproject/android/Orbot.java @@ -57,6 +57,7 @@ public class Orbot extends Activity implements OnClickListener, TorConstants, On private ProgressDialog progressDialog; private ListView listApps; private boolean showingSettings = false; + private MenuItem mItemOnOff = null; /* Some tracking bits */ private int torStatus = STATUS_READY; //latest status reported from the tor service @@ -87,15 +88,9 @@ public class Orbot extends Activity implements OnClickListener, TorConstants, On MenuItem mItem = null; - /* - - mItem = menu.add(0, 1, Menu.NONE, getString(R.string.menu_home)); - mItem.setIcon(R.drawable.ic_menu_home); - - mItem = menu.add(0, 2, Menu.NONE, getString(R.string.menu_browse)); - mItem.setIcon(R.drawable.ic_menu_goto); - */ + mItemOnOff = menu.add(0, 1, Menu.NONE, getString(R.string.menu_start)); + mItemOnOff.setIcon(android.R.drawable.ic_menu_share); mItem = menu.add(0, 4, Menu.NONE, getString(R.string.menu_settings)); mItem.setIcon(R.drawable.ic_menu_register); @@ -132,7 +127,32 @@ public class Orbot extends Activity implements OnClickListener, TorConstants, On if (item.getItemId() == 1) { - this.showMain(); + + try + { + + if (mService == null) + { + + } + else if (mService.getStatus() == STATUS_READY) + { + mItemOnOff.setTitle(R.string.menu_stop); + startTor(); + + } + else + { + mItemOnOff.setTitle(R.string.menu_start); + stopTor(); + + } + + } + catch (RemoteException re) + { + Log.w(TAG, "Unable to start/top Tor from menu UI", re); + } } else if (item.getItemId() == 4) { @@ -706,6 +726,29 @@ public class Orbot extends Activity implements OnClickListener, TorConstants, On } + private void startTor () throws RemoteException + { + mService.setProfile(PROFILE_ON); //this means turn on + + imgStatus.setImageResource(R.drawable.torstarting); + lblStatus.setText(getString(R.string.status_starting_up)); + + Message msg = mHandler.obtainMessage(ENABLE_TOR_MSG); + mHandler.sendMessage(msg); + + updateStatus(""); + } + + private void stopTor () throws RemoteException + { + mService.setProfile(PROFILE_ONDEMAND); //these means turn off + + Message msg = mHandler.obtainMessage(DISABLE_TOR_MSG); + mHandler.sendMessage(msg); + + updateStatus(""); + } + /* * (non-Javadoc) * @see android.view.View.OnClickListener#onClick(android.view.View) @@ -725,26 +768,13 @@ public class Orbot extends Activity implements OnClickListener, TorConstants, On else if (mService.getStatus() == STATUS_READY) { - mService.setProfile(PROFILE_ON); //this means turn on - - imgStatus.setImageResource(R.drawable.torstarting); - lblStatus.setText(getString(R.string.status_starting_up)); - - Message msg = mHandler.obtainMessage(ENABLE_TOR_MSG); - mHandler.sendMessage(msg); - - updateStatus(""); + startTor(); } else { - mService.setProfile(PROFILE_ONDEMAND); //these means turn off - - Message msg = mHandler.obtainMessage(DISABLE_TOR_MSG); - mHandler.sendMessage(msg); - - updateStatus(""); + stopTor(); } diff --git a/src/org/torproject/android/Utils.java b/src/org/torproject/android/Utils.java index 0d5c6afa..cd48ebf0 100644 --- a/src/org/torproject/android/Utils.java +++ b/src/org/torproject/android/Utils.java @@ -9,10 +9,35 @@ import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; public class Utils { + public static String readString (InputStream stream) + { + String line = null; + + StringBuffer out = new StringBuffer(); + + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + + 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 */ diff --git a/src/org/torproject/android/net/ModSSLSocketFactory.java b/src/org/torproject/android/net/ModSSLSocketFactory.java new file mode 100644 index 00000000..924d3b0b --- /dev/null +++ b/src/org/torproject/android/net/ModSSLSocketFactory.java @@ -0,0 +1,453 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java $ + * $Revision: 659194 $ + * $Date: 2008-05-22 11:33:47 -0700 (Thu, 22 May 2008) $ + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.torproject.android.net; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.http.conn.scheme.HostNameResolver; +import org.apache.http.conn.scheme.LayeredSocketFactory; +import org.apache.http.conn.ssl.AllowAllHostnameVerifier; +import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.conn.ssl.StrictHostnameVerifier; +import org.apache.http.conn.ssl.X509HostnameVerifier; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +import android.util.Log; + + + +/** + * Layered socket factory for TLS/SSL connections, based on JSSE. + *. + *

+ * SSLSocketFactory can be used to validate the identity of the HTTPS + * server against a list of trusted certificates and to authenticate to + * the HTTPS server using a private key. + *

+ * + *

+ * SSLSocketFactory will enable server authentication when supplied with + * a {@link KeyStore truststore} file containg one or several trusted + * certificates. The client secure socket will reject the connection during + * the SSL session handshake if the target HTTPS server attempts to + * authenticate itself with a non-trusted certificate. + *

+ * + *

+ * Use JDK keytool utility to import a trusted certificate and generate a truststore file: + *

+ *     keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
+ *    
+ *

+ * + *

+ * SSLSocketFactory will enable client authentication when supplied with + * a {@link KeyStore keystore} file containg a private key/public certificate + * pair. The client secure socket will use the private key to authenticate + * itself to the target HTTPS server during the SSL session handshake if + * requested to do so by the server. + * The target HTTPS server will in its turn verify the certificate presented + * by the client in order to establish client's authenticity + *

+ * + *

+ * Use the following sequence of actions to generate a keystore file + *

+ * + * @author Oleg Kalnichevski + * @author Julius Davies + */ + +public class ModSSLSocketFactory implements LayeredSocketFactory { + + public static final String TLS = "TLS"; + public static final String SSL = "SSL"; + public static final String SSLV2 = "SSLv2"; + + public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER + = new AllowAllHostnameVerifier(); + + public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER + = new BrowserCompatHostnameVerifier(); + + public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER + = new StrictHostnameVerifier(); + /** + * The factory using the default JVM settings for secure connections. + */ + private static ModSSLSocketFactory DEFAULT_FACTORY = null; + + /** + * Gets an singleton instance of the SSLProtocolSocketFactory. + * @return a SSLProtocolSocketFactory + */ + public static ModSSLSocketFactory getSocketFactory() { + if (DEFAULT_FACTORY == null) { + DEFAULT_FACTORY = new ModSSLSocketFactory(); + } + return DEFAULT_FACTORY; + } + + private final SSLContext sslcontext; + private final SSLSocketFactory socketfactory; + //private final HostNameResolver nameResolver; + private X509HostnameVerifier hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; + private SocksSocketFactory mSocksSocketFactory = null; + + public ModSSLSocketFactory( + String algorithm, + final KeyStore keystore, + final String keystorePassword, + final KeyStore truststore, + final SecureRandom random, + final HostNameResolver nameResolver) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException + { + super(); + if (algorithm == null) { + algorithm = SSL; + } + KeyManager[] keymanagers = null; + if (keystore != null) { + keymanagers = createKeyManagers(keystore, keystorePassword); + } + TrustManager[] trustmanagers = null; + if (truststore != null) { + trustmanagers = createTrustManagers(truststore); + } + this.sslcontext = SSLContext.getInstance(algorithm); + this.sslcontext.init(keymanagers, trustmanagers, random); + this.socketfactory = SSLSocketFactory.getSocketFactory(); + //this.nameResolver = nameResolver; + + + } + + public ModSSLSocketFactory( + final KeyStore keystore, + final String keystorePassword, + final KeyStore truststore) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException + { + this(SSL, keystore, keystorePassword, truststore, null, null); + } + + public ModSSLSocketFactory(final KeyStore keystore, final String keystorePassword) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException + { + this(SSL, keystore, keystorePassword, null, null, null); + } + + public ModSSLSocketFactory(final KeyStore truststore) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException + { + this(SSL, null, null, truststore, null, null); + } + + /** + * Constructs an HttpClient SSLSocketFactory backed by the given JSSE + * SSLSocketFactory. + * + * @hide + */ + public ModSSLSocketFactory(SSLSocketFactory socketfactory) { + super(); + this.sslcontext = null; + this.socketfactory = socketfactory; + //this.nameResolver = null; + } + + /** + * Creates the default SSL socket factory. + * This constructor is used exclusively to instantiate the factory for + * {@link #getSocketFactory getSocketFactory}. + */ + private ModSSLSocketFactory() { + super(); + this.sslcontext = null; + this.socketfactory = SSLSocketFactory.getSocketFactory(); + //this.nameResolver = null; + + //Log.i("TOR_SERVICE","ModSSLSocketFactory: proxied via " + host + ":" + port); + + this.mSocksSocketFactory = new SocksSocketFactory("127.0.0.1",9050); + } + + private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { + if (keystore == null) { + throw new IllegalArgumentException("Keystore may not be null"); + } + KeyManagerFactory kmfactory = KeyManagerFactory.getInstance( + KeyManagerFactory.getDefaultAlgorithm()); + kmfactory.init(keystore, password != null ? password.toCharArray(): null); + return kmfactory.getKeyManagers(); + } + + private static TrustManager[] createTrustManagers(final KeyStore keystore) + throws KeyStoreException, NoSuchAlgorithmException { + if (keystore == null) { + throw new IllegalArgumentException("Keystore may not be null"); + } + TrustManagerFactory tmfactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + tmfactory.init(keystore); + return tmfactory.getTrustManagers(); + } + + + // non-javadoc, see interface org.apache.http.conn.SocketFactory + public Socket createSocket() + throws IOException { + + // SSL Sockets don't work at the moment. + //throw new SSLException("SSL socket functionality broken"); + // the cast makes sure that the factory is working as expected + return (SSLSocket) this.socketfactory.createSocket(); + //return new Socket(); + //return null; + + } + + + // non-javadoc, see interface org.apache.http.conn.SocketFactory + public Socket connectSocket( + final Socket sock, + final String host, + final int port, + final InetAddress localAddress, + int localPort, + final HttpParams params + ) throws IOException { + + if (host == null) { + throw new IllegalArgumentException("Target host may not be null."); + } + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null."); + } + + //Socket underlying = (Socket) + // ((sock != null) ? sock : createSocket()); + Socket underlying = null; + + /*sock; + if (underlying == null)// underlying = new Socket(); + { + underlying = mSocksSocketFactory.createSocket(); + + }*/ + + Log.i("TOR_SERVICE","connecting socks factory"); + Socket sSocket = mSocksSocketFactory.connectSocket(underlying, host, port, localAddress, localPort, params); + Log.i("TOR_SERVICE","creating SSL Socket"); + + // SSLSocket sslsock = (SSLSocket) socketfactory.connectSocket(sSocket, host, port, localAddress, localPort, params); + SSLSocket sslsock = (SSLSocket)socketfactory.createSocket(sSocket, host, port, true); + + + Log.i("TOR_SERVICE","created SSL Socket!"); + + if ((localAddress != null) || (localPort > 0)) { + + // we need to bind explicitly + if (localPort < 0) + localPort = 0; // indicates "any" + + InetSocketAddress isa = + new InetSocketAddress(localAddress, localPort); + + Log.i("TOR_SERVICE","binding SSL Socket!"); + + sslsock.bind(isa); + } + + int connTimeout = HttpConnectionParams.getConnectionTimeout(params); + int soTimeout = HttpConnectionParams.getSoTimeout(params); + + InetSocketAddress remoteAddress; +// if (this.nameResolver != null) { +// remoteAddress = new InetSocketAddress(this.nameResolver.resolve(host), port); +// } else { + remoteAddress = new InetSocketAddress(host, port); +// } +// + + sslsock.connect(remoteAddress, connTimeout); + + // sslsock.setSoTimeout(0); + try { + hostnameVerifier.verify(host, sslsock); + // verifyHostName() didn't blowup - good! + } catch (IOException iox) { + // close the socket before re-throwing the exception + try { sslsock.close(); } catch (Exception x) { } + throw iox; + } + + return sslsock; + + } + + + /** + * Checks whether a socket connection is secure. + * This factory creates TLS/SSL socket connections + * which, by default, are considered secure. + *
+ * Derived classes may override this method to perform + * runtime checks, for example based on the cypher suite. + * + * @param sock the connected socket + * + * @return true + * + * @throws IllegalArgumentException if the argument is invalid + */ + public boolean isSecure(Socket sock) + throws IllegalArgumentException { + + if (sock == null) { + throw new IllegalArgumentException("Socket may not be null."); + } + // This instanceof check is in line with createSocket() above. + if (!(sock instanceof SSLSocket)) { + throw new IllegalArgumentException + ("Socket not created by this factory."); + } + // This check is performed last since it calls the argument object. + if (sock.isClosed()) { + throw new IllegalArgumentException("Socket is closed."); + } + + return true; + + } // isSecure + + + // non-javadoc, see interface LayeredSocketFactory + public Socket createSocket( + final Socket socket, + final String host, + final int port, + final boolean autoClose + ) throws IOException, UnknownHostException { + SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket( + socket, + host, + port, + autoClose + ); + hostnameVerifier.verify(host, sslSocket); + // verifyHostName() didn't blowup - good! + return sslSocket; + } + + public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) { + if ( hostnameVerifier == null ) { + throw new IllegalArgumentException("Hostname verifier may not be null"); + } + this.hostnameVerifier = hostnameVerifier; + } + + public X509HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + + public class SSLException extends IOException { + private static final long serialVersionUID = 1L; + + public SSLException(String msg) { + super(msg); + } + }; + + +} \ No newline at end of file diff --git a/src/org/torproject/android/net/MyDefaultClientConnectionOperator.java b/src/org/torproject/android/net/MyDefaultClientConnectionOperator.java new file mode 100644 index 00000000..94200880 --- /dev/null +++ b/src/org/torproject/android/net/MyDefaultClientConnectionOperator.java @@ -0,0 +1,70 @@ +package org.torproject.android.net; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.Socket; + +import org.apache.http.HttpHost; +import org.apache.http.conn.HttpHostConnectException; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.scheme.SocketFactory; +import org.apache.http.impl.conn.DefaultClientConnectionOperator; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; + +public class MyDefaultClientConnectionOperator extends + DefaultClientConnectionOperator { + + public MyDefaultClientConnectionOperator(SchemeRegistry schemes) { + super(schemes); + } + + @Override + public void openConnection(OperatedClientConnection conn, HttpHost target, + InetAddress local, HttpContext context, HttpParams params) + throws IOException { + if (conn == null) { + throw new IllegalArgumentException + ("Connection must not be null."); + } + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + // local address may be null + //@@@ is context allowed to be null? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if (conn.isOpen()) { + throw new IllegalArgumentException + ("Connection must not be open."); + } + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + final SocketFactory sf = schm.getSocketFactory(); + + Socket sock = sf.createSocket(); + conn.opening(sock, target); + + try { + Socket connsock = sf.connectSocket(sock, target.getHostName(), + schm.resolvePort(target.getPort()), + local, 0, params); + + if (sock != connsock) { + sock = connsock; + conn.opening(sock, target); + } + } catch (ConnectException ex) { + throw new HttpHostConnectException(target, ex); + } + prepareSocket(sock, context, params); + conn.openCompleted(sf.isSecure(sock), params); + } + +} diff --git a/src/org/torproject/android/net/MyThreadSafeClientConnManager.java b/src/org/torproject/android/net/MyThreadSafeClientConnManager.java new file mode 100644 index 00000000..77e9c2ca --- /dev/null +++ b/src/org/torproject/android/net/MyThreadSafeClientConnManager.java @@ -0,0 +1,21 @@ +package org.torproject.android.net; + +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.params.HttpParams; + + +public class MyThreadSafeClientConnManager extends ThreadSafeClientConnManager { + + public MyThreadSafeClientConnManager(HttpParams params, SchemeRegistry schreg) { + super(params, schreg); + + } + + @Override + protected ClientConnectionOperator createConnectionOperator( + SchemeRegistry schreg) { + return new MyDefaultClientConnectionOperator(schreg); + } +} diff --git a/src/org/torproject/android/net/SOCKSHttpClient.java b/src/org/torproject/android/net/SOCKSHttpClient.java new file mode 100644 index 00000000..d49d67ce --- /dev/null +++ b/src/org/torproject/android/net/SOCKSHttpClient.java @@ -0,0 +1,71 @@ +package org.torproject.android.net; + +import org.apache.http.HttpVersion; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; + +public class SOCKSHttpClient extends DefaultHttpClient { + + private final static String DEFAULT_HOST = "127.0.0.1"; + private final static int DEFAULT_PORT = 9050; + + private static ClientConnectionManager ccm = null; + private static HttpParams params = null; + + public SOCKSHttpClient () + { + + super(initConnectionManager(), initParams()); + + + } + + private void setSystemProperties () + { +// System.getProperties().put("socks.proxySet","true"); + // System.getProperties().put("socks.proxyHost",DEFAULT_HOST); + // System.getProperties().put("socks.proxyPort", DEFAULT_PORT+""); + + } + + private static ClientConnectionManager initConnectionManager () + { + if (ccm == null) + { + SchemeRegistry supportedSchemes = new SchemeRegistry(); + + + supportedSchemes.register(new Scheme("http", + SocksSocketFactory.getSocketFactory(), 80)); + + supportedSchemes.register(new Scheme("https", + ModSSLSocketFactory.getSocketFactory(), 443)); + + ccm = new MyThreadSafeClientConnManager(initParams(), supportedSchemes); + } + + return ccm; + } + + private static HttpParams initParams () + { + if (params == null) + { + // prepare parameters + params = new BasicHttpParams(); + HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); + HttpProtocolParams.setContentCharset(params, "UTF-8"); + HttpProtocolParams.setUseExpectContinue(params, true); + } + + return params; + } +} diff --git a/src/org/torproject/android/net/SocksSocketFactory.java b/src/org/torproject/android/net/SocksSocketFactory.java new file mode 100644 index 00000000..194a26f4 --- /dev/null +++ b/src/org/torproject/android/net/SocksSocketFactory.java @@ -0,0 +1,144 @@ +/** + * Shadow - Anonymous web browser for Android devices + * Copyright (C) 2009 Connell Gauld + * + * Thanks to University of Cambridge, + * Alastair Beresford and Andrew Rice + * + * 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 org.torproject.android.net; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import net.sourceforge.jsocks.socks.Socks5Proxy; +import net.sourceforge.jsocks.socks.SocksException; +import net.sourceforge.jsocks.socks.SocksSocket; + + +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.scheme.SocketFactory; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +import android.util.Log; + + +/** + * Provides sockets for an HttpClient connection. + * @author cmg47 + * + */ +public class SocksSocketFactory implements SocketFactory { + + SocksSocket server = null; + private static Socks5Proxy sProxy = null; + + private final static String DEFAULT_HOST = "127.0.0.1"; + private final static int DEFAULT_PORT = 9050; + + /** + * Construct a SocksSocketFactory that uses the provided SOCKS proxy. + * @param proxyaddress the IP address of the SOCKS proxy + * @param proxyport the port of the SOCKS proxy + */ + public SocksSocketFactory(String proxyaddress, int proxyport) { + + + try { + sProxy = new Socks5Proxy(proxyaddress, proxyport); + } catch (UnknownHostException e) { + // TODO Auto-generated catch block + Log.i("TOR_SERVICE","SocksSF couldn't connect",e); + } + + sProxy.resolveAddrLocally(false); + + + } + + @Override + public Socket connectSocket(Socket sock, String host, int port, + InetAddress localAddress, int localPort, HttpParams params) throws IOException, + UnknownHostException, ConnectTimeoutException { + + Log.i("TOR_SERVICE","SocksSocketFactory: connectSocket: " + host + ":" + port); + + if (host == null) { + throw new IllegalArgumentException("Target host may not be null."); + } + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null."); + } + + // int timeout = HttpConnectionParams.getConnectionTimeout(params); + + // Pipe this socket over the proxy + // sock = mSocksProxy.connectSocksProxy(sock, host, port, timeout); + + + + try { + sock = new SocksSocket(sProxy,host, port); + + + + sock.setSoTimeout(0); //indef + + + if ((localAddress != null) || (localPort > 0)) { + + // we need to bind explicitly + if (localPort < 0) + localPort = 0; // indicates "any" + + InetSocketAddress isa = + new InetSocketAddress(localAddress, localPort); + sock.bind(isa); + } + + + } catch (SocksException e) { + Log.e("TOR_SERVICE","error connecting socks to" + host + ":" + port,e); + } catch (UnknownHostException e) { + Log.e("TOR_SERVICE","error connecting socks to" + host + ":" + port,e); + } + + return sock; + + } + + + + @Override + public Socket createSocket() throws IOException { + return new Socket(); + } + + @Override + public boolean isSecure(Socket sock) throws IllegalArgumentException { + return false; + } + + public static SocketFactory getSocketFactory () + { + return new SocksSocketFactory (DEFAULT_HOST, DEFAULT_PORT); + } + +} diff --git a/src/org/torproject/android/service/NanoHTTPD.java b/src/org/torproject/android/service/NanoHTTPD.java new file mode 100644 index 00000000..59a4bf70 --- /dev/null +++ b/src/org/torproject/android/service/NanoHTTPD.java @@ -0,0 +1,733 @@ +package org.torproject.android.service; + +import java.io.*; +import java.util.*; +import java.net.*; + +/** + * A simple, tiny, nicely embeddable HTTP 1.0 server in Java + * + *

NanoHTTPD version 1.12, + * Copyright © 2001,2005-2010 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/) + * + *

Features + limitations:

+ * + *

Ways to use:

+ * + * See the end of the source file for distribution license + * (Modified BSD licence) + */ +public class NanoHTTPD +{ + // ================================================== + // API parts + // ================================================== + + private boolean keepRunning = true; + private Thread t = null; + + public void stop () + { + keepRunning = false; + t.interrupt(); + } + + /** + * Override this to customize the server.

+ * + * (By default, this delegates to serveFile() and allows directory listing.) + * + * @parm uri Percent-decoded URI without parameters, for example "/index.cgi" + * @parm method "GET", "POST" etc. + * @parm parms Parsed, percent decoded parameters from URI and, in case of POST, data. + * @parm header Header entries, percent decoded + * @return HTTP response, see class Response for details + */ + public Response serve( String uri, String method, Properties header, Properties parms ) + { + System.out.println( method + " '" + uri + "' " ); + + Enumeration e = header.propertyNames(); + while ( e.hasMoreElements()) + { + String value = (String)e.nextElement(); + System.out.println( " HDR: '" + value + "' = '" + + header.getProperty( value ) + "'" ); + } + e = parms.propertyNames(); + while ( e.hasMoreElements()) + { + String value = (String)e.nextElement(); + System.out.println( " PRM: '" + value + "' = '" + + parms.getProperty( value ) + "'" ); + } + + return serveFile( uri, header, new File("."), true ); + } + + /** + * HTTP response. + * Return one of these from serve(). + */ + public class Response + { + /** + * Default constructor: response = HTTP_OK, data = mime = 'null' + */ + public Response() + { + this.status = HTTP_OK; + } + + /** + * Basic constructor. + */ + public Response( String status, String mimeType, InputStream data ) + { + this.status = status; + this.mimeType = mimeType; + this.data = data; + } + + /** + * Convenience method that makes an InputStream out of + * given text. + */ + public Response( String status, String mimeType, String txt ) + { + this.status = status; + this.mimeType = mimeType; + this.data = new ByteArrayInputStream( txt.getBytes()); + } + + /** + * Adds given line to the header. + */ + public void addHeader( String name, String value ) + { + header.put( name, value ); + } + + /** + * HTTP status code after processing, e.g. "200 OK", HTTP_OK + */ + public String status; + + /** + * MIME type of content, e.g. "text/html" + */ + public String mimeType; + + /** + * Data of the response, may be null. + */ + public InputStream data; + + /** + * Headers for the HTTP response. Use addHeader() + * to add lines. + */ + public Properties header = new Properties(); + } + + /** + * Some HTTP response status codes + */ + public static final String + HTTP_OK = "200 OK", + HTTP_REDIRECT = "301 Moved Permanently", + HTTP_FORBIDDEN = "403 Forbidden", + HTTP_NOTFOUND = "404 Not Found", + HTTP_BADREQUEST = "400 Bad Request", + HTTP_INTERNALERROR = "500 Internal Server Error", + HTTP_NOTIMPLEMENTED = "501 Not Implemented"; + + /** + * Common mime types for dynamic content + */ + public static final String + MIME_PLAINTEXT = "text/plain", + MIME_HTML = "text/html", + MIME_DEFAULT_BINARY = "application/octet-stream"; + + // ================================================== + // Socket & server code + // ================================================== + + /** + * Starts a HTTP server to given port.

+ * Throws an IOException if the socket is already in use + */ + public NanoHTTPD( int port ) throws IOException + { + myTcpPort = port; + + final ServerSocket ss = new ServerSocket( myTcpPort ); + t = new Thread( new Runnable() + { + public void run() + { + try + { + while( keepRunning ) + new HTTPSession( ss.accept()); + } + catch ( IOException ioe ) + {} + } + }); + t.setDaemon( true ); + t.start(); + } + + /** + * Starts as a standalone file server and waits for Enter. + */ + public static void main( String[] args ) + { + System.out.println( "NanoHTTPD 1.12 (C) 2001,2005-2010 Jarno Elonen\n" + + "(Command line options: [port] [--licence])\n" ); + + // Show licence if requested + int lopt = -1; + for ( int i=0; i 0 && lopt != 0 ) + port = Integer.parseInt( args[0] ); + + if ( args.length > 1 && + args[1].toLowerCase().endsWith( "licence" )) + System.out.println( LICENCE + "\n" ); + + NanoHTTPD nh = null; + try + { + nh = new NanoHTTPD( port ); + } + catch( IOException ioe ) + { + System.err.println( "Couldn't start server:\n" + ioe ); + System.exit( -1 ); + } + nh.myFileDir = new File(""); + + System.out.println( "Now serving files in port " + port + " from \"" + + new File("").getAbsolutePath() + "\"" ); + System.out.println( "Hit Enter to stop.\n" ); + + try { System.in.read(); } catch( Throwable t ) {}; + } + + /** + * Handles one session, i.e. parses the HTTP request + * and returns the response. + */ + private class HTTPSession implements Runnable + { + public HTTPSession( Socket s ) + { + mySocket = s; + Thread t = new Thread( this ); + t.setDaemon( true ); + t.start(); + } + + public void run() + { + try + { + InputStream is = mySocket.getInputStream(); + if ( is == null) return; + BufferedReader in = new BufferedReader( new InputStreamReader( is )); + + // Read the request line + String inLine = in.readLine(); + if (inLine == null) return; + StringTokenizer st = new StringTokenizer( inLine ); + if ( !st.hasMoreTokens()) + sendError( HTTP_BADREQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html" ); + + String method = st.nextToken(); + + if ( !st.hasMoreTokens()) + sendError( HTTP_BADREQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html" ); + + String uri = st.nextToken(); + + // Decode parameters from the URI + Properties parms = new Properties(); + int qmi = uri.indexOf( '?' ); + if ( qmi >= 0 ) + { + decodeParms( uri.substring( qmi+1 ), parms ); + uri = decodePercent( uri.substring( 0, qmi )); + } + else uri = decodePercent(uri); + + + // If there's another token, it's protocol version, + // followed by HTTP headers. Ignore version but parse headers. + // NOTE: this now forces header names uppercase since they are + // case insensitive and vary by client. + Properties header = new Properties(); + if ( st.hasMoreTokens()) + { + String line = in.readLine(); + while ( line.trim().length() > 0 ) + { + int p = line.indexOf( ':' ); + header.put( line.substring(0,p).trim().toLowerCase(), line.substring(p+1).trim()); + line = in.readLine(); + } + } + + // If the method is POST, there may be parameters + // in data section, too, read it: + if ( method.equalsIgnoreCase( "POST" )) + { + long size = 0x7FFFFFFFFFFFFFFFl; + String contentLength = header.getProperty("content-length"); + if (contentLength != null) + { + try { size = Integer.parseInt(contentLength); } + catch (NumberFormatException ex) {} + } + String postLine = ""; + char buf[] = new char[512]; + int read = in.read(buf); + while ( read >= 0 && size > 0 && !postLine.endsWith("\r\n") ) + { + size -= read; + postLine += String.valueOf(buf, 0, read); + if ( size > 0 ) + read = in.read(buf); + } + postLine = postLine.trim(); + decodeParms( postLine, parms ); + } + + // Ok, now do the serve() + Response r = serve( uri, method, header, parms ); + if ( r == null ) + sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response." ); + else + sendResponse( r.status, r.mimeType, r.header, r.data ); + + in.close(); + } + catch ( IOException ioe ) + { + try + { + sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); + } + catch ( Throwable t ) {} + } + catch ( InterruptedException ie ) + { + // Thrown by sendError, ignore and exit the thread. + } + } + + /** + * Decodes the percent encoding scheme.
+ * For example: "an+example%20string" -> "an example string" + */ + private String decodePercent( String str ) throws InterruptedException + { + try + { + StringBuffer sb = new StringBuffer(); + for( int i=0; i= 0 ) + uri = uri.substring(0, uri.indexOf( '?' )); + + // Prohibit getting out of current directory + if ( uri.startsWith( ".." ) || uri.endsWith( ".." ) || uri.indexOf( "../" ) >= 0 ) + return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, + "FORBIDDEN: Won't serve ../ for security reasons." ); + + File f = new File( homeDir, uri ); + if ( !f.exists()) + return new Response( HTTP_NOTFOUND, MIME_PLAINTEXT, + "Error 404, file not found." ); + + // List the directory, if necessary + if ( f.isDirectory()) + { + // Browsers get confused without '/' after the + // directory, send a redirect. + if ( !uri.endsWith( "/" )) + { + uri += "/"; + Response r = new Response( HTTP_REDIRECT, MIME_HTML, + "Redirected: " + + uri + ""); + r.addHeader( "Location", uri ); + return r; + } + + // First try index.html and index.htm + if ( new File( f, "index.html" ).exists()) + f = new File( homeDir, uri + "/index.html" ); + else if ( new File( f, "index.htm" ).exists()) + f = new File( homeDir, uri + "/index.htm" ); + + // No index file, list the directory + else if ( allowDirectoryListing ) + { + String[] files = f.list(); + String msg = "

Directory " + uri + "


"; + + if ( uri.length() > 1 ) + { + String u = uri.substring( 0, uri.length()-1 ); + int slash = u.lastIndexOf( '/' ); + if ( slash >= 0 && slash < u.length()) + msg += "..
"; + } + + for ( int i=0; i" + + files[i] + ""; + + // Show file size + if ( curFile.isFile()) + { + long len = curFile.length(); + msg += "  ("; + if ( len < 1024 ) + msg += curFile.length() + " bytes"; + else if ( len < 1024 * 1024 ) + msg += curFile.length()/1024 + "." + (curFile.length()%1024/10%100) + " KB"; + else + msg += curFile.length()/(1024*1024) + "." + curFile.length()%(1024*1024)/10%100 + " MB"; + + msg += ")"; + } + msg += "
"; + if ( dir ) msg += ""; + } + return new Response( HTTP_OK, MIME_HTML, msg ); + } + else + { + return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, + "FORBIDDEN: No directory listing." ); + } + } + + try + { + // Get MIME type from file name extension, if possible + String mime = null; + int dot = f.getCanonicalPath().lastIndexOf( '.' ); + if ( dot >= 0 ) + mime = (String)theMimeTypes.get( f.getCanonicalPath().substring( dot + 1 ).toLowerCase()); + if ( mime == null ) + mime = MIME_DEFAULT_BINARY; + + // Support (simple) skipping: + long startFrom = 0; + String range = header.getProperty( "Range" ); + if ( range != null ) + { + if ( range.startsWith( "bytes=" )) + { + range = range.substring( "bytes=".length()); + int minus = range.indexOf( '-' ); + if ( minus > 0 ) + range = range.substring( 0, minus ); + try { + startFrom = Long.parseLong( range ); + } + catch ( NumberFormatException nfe ) {} + } + } + + FileInputStream fis = new FileInputStream( f ); + fis.skip( startFrom ); + Response r = new Response( HTTP_OK, mime, fis ); + r.addHeader( "Content-length", "" + (f.length() - startFrom)); + r.addHeader( "Content-range", "" + startFrom + "-" + + (f.length()-1) + "/" + f.length()); + return r; + } + catch( IOException ioe ) + { + return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." ); + } + } + + /** + * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE + */ + private static Hashtable theMimeTypes = new Hashtable(); + static + { + StringTokenizer st = new StringTokenizer( + "htm text/html "+ + "html text/html "+ + "txt text/plain "+ + "asc text/plain "+ + "gif image/gif "+ + "jpg image/jpeg "+ + "jpeg image/jpeg "+ + "png image/png "+ + "mp3 audio/mpeg "+ + "m3u audio/mpeg-url " + + "pdf application/pdf "+ + "doc application/msword "+ + "ogg application/x-ogg "+ + "zip application/octet-stream "+ + "exe application/octet-stream "+ + "class application/octet-stream " ); + while ( st.hasMoreTokens()) + theMimeTypes.put( st.nextToken(), st.nextToken()); + } + + /** + * GMT date formatter + */ + private static java.text.SimpleDateFormat gmtFrmt; + static + { + gmtFrmt = new java.text.SimpleDateFormat( "E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); + gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT")); + } + + /** + * The distribution licence + */ + private static final String LICENCE = + "Copyright (C) 2001,2005-2008 by Jarno Elonen \n"+ + "\n"+ + "Redistribution and use in source and binary forms, with or without\n"+ + "modification, are permitted provided that the following conditions\n"+ + "are met:\n"+ + "\n"+ + "Redistributions of source code must retain the above copyright notice,\n"+ + "this list of conditions and the following disclaimer. Redistributions in\n"+ + "binary form must reproduce the above copyright notice, this list of\n"+ + "conditions and the following disclaimer in the documentation and/or other\n"+ + "materials provided with the distribution. The name of the author may not\n"+ + "be used to endorse or promote products derived from this software without\n"+ + "specific prior written permission. \n"+ + " \n"+ + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"+ + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"+ + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"+ + "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"+ + "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"+ + "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"+ + "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"+ + "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"+ + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"+ + "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."; +} \ No newline at end of file diff --git a/src/org/torproject/android/service/TorService.java b/src/org/torproject/android/service/TorService.java index b9e08c25..583c75ee 100644 --- a/src/org/torproject/android/service/TorService.java +++ b/src/org/torproject/android/service/TorService.java @@ -42,6 +42,8 @@ public class TorService extends Service implements TorServiceConstants, Runnable private static final int NOTIFY_ID = 1; + /* removing this for now - it is a work in progress and a security risk - 3/17/2010 */ + //private static TorWebProxy _webProxy; /** Called when the activity is first created. */ @@ -307,6 +309,22 @@ public class TorService extends Service implements TorServiceConstants, Runnable procId = TorServiceUtils.findProcessId(TorServiceConstants.PRIVOXY_INSTALL_PATH); } + /* + //removing this for now + if (_webProxy != null) + { + try + { + //shutdown web proxy + _webProxy.stop(); + _webProxy = null; + } + catch (Exception e) + { + Log.i(TAG,"error stopping web proxy",e); + } + }*/ + } private void logNotice (String msg) @@ -400,6 +418,13 @@ public class TorService extends Service implements TorServiceConstants, Runnable } }.start(); + /* + //removing this for now - nf - 3/17/2010 + if (_webProxy == null) + { + _webProxy = new TorWebProxy(); + + }*/ } diff --git a/src/org/torproject/android/service/TorServiceUtils.java b/src/org/torproject/android/service/TorServiceUtils.java index 5789d8d2..2cf1c637 100644 --- a/src/org/torproject/android/service/TorServiceUtils.java +++ b/src/org/torproject/android/service/TorServiceUtils.java @@ -209,7 +209,7 @@ public class TorServiceUtils implements TorServiceConstants { } } catch (Exception e) { - Log.e(TAG, "Error executing shell cmd: " + e.getMessage(),e); + Log.w(TAG, "Error executing shell cmd: " + e.getMessage()); } return exitCode; diff --git a/src/org/torproject/android/service/TorTransProxy.java b/src/org/torproject/android/service/TorTransProxy.java index 6ed92f7a..5fd53562 100644 --- a/src/org/torproject/android/service/TorTransProxy.java +++ b/src/org/torproject/android/service/TorTransProxy.java @@ -97,12 +97,21 @@ public class TorTransProxy { { Log.i(TAG,"enabling transproxy for app: " + apps[i].getUsername() + "(" + apps[i].getUid() + ")"); + //TCP script.append("iptables -t nat"); script.append(command); script.append("OUTPUT -p tcp -m owner --uid-owner "); script.append(apps[i].getUid()); script.append(" -j DNAT --to 127.0.0.1:9040"); script.append(" || exit\n"); + + //UDP + script.append("iptables -t nat"); + script.append(command); + script.append("OUTPUT -p udp -m owner --uid-owner "); + script.append(apps[i].getUid()); + script.append(" -j DNAT --to 127.0.0.1:9040"); + script.append(" || exit\n"); } } @@ -120,5 +129,52 @@ public class TorTransProxy { } return false; } + + + public static boolean setTransparentProxyingByPort(Context context, String[] ports) { + + String command = null; + + command = IPTABLES_ADD; //ADD + + final StringBuilder script = new StringBuilder(); + + try { + int code; + + for (int i = 0; i < ports.length; i++) + { + Log.i(TAG,"enabling transproxy for port: " + ports[i]); + + //TCP + script.append("iptables -t nat"); + script.append("-A PREROUTING -p tcp --dport "); + script.append(ports[i]); + script.append(" -j DNAT --to 127.0.0.1:9040"); + script.append(" || exit\n"); + + //UDP + script.append("iptables -t nat"); + script.append("-A PREROUTING -p udp --dport "); + script.append(ports[i]); + script.append(" -j DNAT --to 127.0.0.1:9040"); + script.append(" || exit\n"); + + } + + StringBuilder res = new StringBuilder(); + + String[] cmd = {script.toString()}; + + code = TorServiceUtils.doShellCommand(cmd, res, true, true); + + String msg = res.toString(); + Log.e(TAG, msg); + + } catch (Exception e) { + Log.w(TAG, "error refreshing iptables: " + e); + } + return false; + } } diff --git a/src/org/torproject/android/service/TorWebProxy.java b/src/org/torproject/android/service/TorWebProxy.java new file mode 100644 index 00000000..6ca10a54 --- /dev/null +++ b/src/org/torproject/android/service/TorWebProxy.java @@ -0,0 +1,254 @@ +package org.torproject.android.service; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Iterator; +import java.util.Properties; +import java.util.Map.Entry; + +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.HttpVersion; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.params.ConnRoutePNames; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.torproject.android.Utils; + +import android.util.Log; + +public class TorWebProxy extends NanoHTTPD implements TorServiceConstants +{ + private final static String BASE_URI = "http://localhost:8888/"; + + private final static String DEFAULT_URI = "https://check.torproject.org/"; + private java.net.URLDecoder decoder; + + private String lastPage = null; + + private final static int PORT = 8888; + + public TorWebProxy() throws IOException + { + super(PORT); + + decoder = new java.net.URLDecoder(); + + Log.i(TAG,"TorWebProxy started on port: " + PORT); + } + + public Response serve( String uri, String method, Properties header, Properties params ) + { + Log.i(TAG,"TorWebProxy serve(): " + method + " '" + uri + "' " ); + + + InputStream contentStream = null; + + String requestUrl = null; + + if (uri.toLowerCase().startsWith("/http")) + { + //okay this is cool + requestUrl = uri.substring(1); + requestUrl = decoder.decode(requestUrl); + } + else if (params.getProperty("url")!=null) + { + requestUrl = params.getProperty("url"); + } + else if (uri.equals("/")) + { + requestUrl = DEFAULT_URI; + } + else //must be a relative path + { + if (lastPage != null) + { + + if (!uri.startsWith("/")) + uri = "/" + uri; + + try { + URL lastPageUrl = new URL(lastPage); + + StringBuilder sb = new StringBuilder(); + sb.append(lastPageUrl.getProtocol()); + sb.append("://"); + sb.append(lastPageUrl.getHost()); + + if (lastPageUrl.getPort()!=-1) + { + sb.append(":"); + sb.append(lastPageUrl.getPort()); + } + + sb.append(uri); + + requestUrl = sb.toString(); + + } catch (MalformedURLException e) { + Log.i(TAG, "TorWebProxy: " + e.getLocalizedMessage(),e); + + return new NanoHTTPD.Response(NanoHTTPD.HTTP_INTERNALERROR,"text/plain","Something bad happened: " + e.getLocalizedMessage()); + + } + + + } + } + + HttpUriRequest request = null; + HttpHost host = null; + + + URI rURI = null; + try { + rURI = new URI(requestUrl); + } catch (URISyntaxException e) { + Log.e(TAG,"error parsing uri: " + requestUrl,e); + return new NanoHTTPD.Response(NanoHTTPD.HTTP_INTERNALERROR,"text/plain","error"); + + } + + int port = rURI.getPort(); + + if (port == -1) + { + if (rURI.getScheme().equalsIgnoreCase("http")) + port = 80; + else if (rURI.getScheme().equalsIgnoreCase("https")) + port = 443; + } + + host = new HttpHost(rURI.getHost(),port, rURI.getScheme()); + + Log.i(TAG,"TorWebProxy server(): host=" + host.getSchemeName() + "://" + host.getHostName() + ":" + host.getPort()); + + if (method.equalsIgnoreCase("get")) + { + Log.i(TAG,"TorWebProxy serve(): GET: " + rURI.getPath() ); + request = new HttpGet (rURI.getPath()); + } + else if (method.equalsIgnoreCase("post")) + { + Log.i(TAG,"TorWebProxy serve(): POST: " + rURI.getPath() ); + + request = new HttpPost(rURI.getPath()); + + + //request = new HttpPost (requestUrl); + + Iterator> itSet = params.entrySet().iterator(); + + Entry entry = null; + + HttpParams hParams = request.getParams(); + + while (itSet.hasNext()) + { + entry = itSet.next(); + + hParams.setParameter((String)entry.getKey(), entry.getValue()); + } + + request.setParams(hParams); + + } + else + { + return new NanoHTTPD.Response(NanoHTTPD.HTTP_NOTIMPLEMENTED,"text/plain","No support for the method: " + method); + + } + + // SOCKSHttpClient client = new SOCKSHttpClient(); + + HttpHost proxy = new HttpHost("127.0.0.1", 8118, "http"); + SchemeRegistry supportedSchemes = new SchemeRegistry(); + // Register the "http" and "https" protocol schemes, they are + // required by the default operator to look up socket factories. + supportedSchemes.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); + supportedSchemes.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); + // prepare parameters + HttpParams hparams = new BasicHttpParams(); + HttpProtocolParams.setVersion(hparams, HttpVersion.HTTP_1_1); + HttpProtocolParams.setContentCharset(hparams, "UTF-8"); + HttpProtocolParams.setUseExpectContinue(hparams, true); + ClientConnectionManager ccm = new ThreadSafeClientConnManager(hparams, supportedSchemes); + + DefaultHttpClient client = new DefaultHttpClient(ccm, hparams); + client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); + + + try { + HttpResponse response = client.execute(host, request); + + if (response.getEntity() == null || response.getEntity().getContentType() == null) + { + return new NanoHTTPD.Response(NanoHTTPD.HTTP_INTERNALERROR,"text/plain","Something bad happened"); + + } + + String contentType = response.getEntity().getContentType().getValue(); + + int respCode = response.getStatusLine().getStatusCode(); + + Log.i(TAG,"TorWebProxy server(): resp=" + respCode + ";" + contentType); + + contentStream = response.getEntity().getContent(); + + if (contentType.indexOf("text/html")!=-1) + { + response.getEntity().getContentLength(); + + lastPage = requestUrl; + + String page = Utils.readString(contentStream); + + page = page.replace("href=\"", "href=\"" + BASE_URI); + page = page.replace("src=\"", "src=\"" + BASE_URI); + page = page.replace("action=\"", "action=\"" + BASE_URI); + + page = page.replace("HREF=\"", "href=\"" + BASE_URI); + page = page.replace("SRC=\"", "src=\"" + BASE_URI); + page = page.replace("ACTION=\"", "action=\"" + BASE_URI); + + + return new NanoHTTPD.Response( HTTP_OK, contentType, page ); + } + else + return new NanoHTTPD.Response( HTTP_OK, contentType, contentStream ); + + } catch (ClientProtocolException e) { + Log.w(TAG,"TorWebProxy",e); + + } catch (IOException e) { + Log.w(TAG,"TorWebProxy",e); + + } + catch (NullPointerException e) + { + Log.w(TAG,"TorWebProxy",e); + } + + return new NanoHTTPD.Response(NanoHTTPD.HTTP_INTERNALERROR,"text/plain","Something bad happened"); + + } + + + +} \ No newline at end of file