617 lines
14 KiB
Java
617 lines
14 KiB
Java
/* 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");
|
|
|
|
}
|
|
} |