added transproxy app chooser view

svn:r21761
This commit is contained in:
Nathan Freitas 2010-02-27 23:56:46 +00:00
parent 628270ace0
commit 11dafc0654
9 changed files with 509 additions and 300 deletions

View File

@ -3,11 +3,14 @@
package org.torproject.android;
import java.util.Arrays;
import java.util.Comparator;
import java.util.StringTokenizer;
import org.torproject.android.service.ITorService;
import org.torproject.android.service.ITorServiceCallback;
import org.torproject.android.service.TorRoot;
import org.torproject.android.service.TorServiceUtils;
import org.torproject.android.service.TorTransProxy;
import org.torproject.android.service.TorServiceConstants;
import android.app.Activity;
@ -32,12 +35,19 @@ import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.CompoundButton.OnCheckedChangeListener;
public class Orbot extends Activity implements OnClickListener, TorConstants
public class Orbot extends Activity implements OnClickListener, TorConstants, OnCheckedChangeListener
{
/* Useful UI bits */
@ -45,18 +55,17 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
private TextView lblStatus = null; //the main text display widget
private ImageView imgStatus = null; //the main touchable image for activating Orbot
private ProgressDialog progressDialog;
private ListView listApps;
private boolean showingSettings = false;
/* Some tracking bits */
private int torStatus = STATUS_REQUIRES_DEMAND; //latest status reported from the tor service
private int torStatus = STATUS_READY; //latest status reported from the tor service
private int currentView = 0; //the currently displayed UI view
private StringBuffer logBuffer = new StringBuffer(); //the output of the service log messages
private String lastUrl = null;
/* Tor Service interaction */
/* The primary interface we will be calling on the service. */
ITorService mService = null;
/** Called when the activity is first created. */
@Override
@ -83,15 +92,22 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
mItem = menu.add(0, 2, Menu.NONE, getString(R.string.menu_browse));
mItem.setIcon(R.drawable.ic_menu_goto);
mItem = menu.add(0, 3, Menu.NONE, getString(R.string.menu_info));
mItem.setIcon(R.drawable.ic_menu_about);
mItem = menu.add(0, 3, Menu.NONE, getString(R.string.menu_settings));
mItem = menu.add(0, 4, Menu.NONE, getString(R.string.menu_settings));
mItem.setIcon(R.drawable.ic_menu_register);
mItem = menu.add(0, 5, Menu.NONE, getString(R.string.menu_apps));
mItem.setIcon(R.drawable.ic_menu_register);
if (!TorServiceUtils.hasRoot())
mItem.setEnabled(false);
mItem = menu.add(0, 4, Menu.NONE, getString(R.string.menu_log));
mItem = menu.add(0,6, Menu.NONE, getString(R.string.menu_log));
mItem.setIcon(R.drawable.ic_menu_reports);
mItem = menu.add(0, 5, Menu.NONE, getString(R.string.menu_info));
mItem.setIcon(R.drawable.ic_menu_about);
return true;
}
@ -109,11 +125,11 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
{
this.showMain();
}
else if (item.getItemId() == 3)
else if (item.getItemId() == 4)
{
this.showSettings();
}
else if (item.getItemId() == 4)
else if (item.getItemId() == 6)
{
this.showMessageLog();
}
@ -121,10 +137,14 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
{
openBrowser(URL_TOR_CHECK);
}
else if (item.getItemId() == 5)
else if (item.getItemId() == 3)
{
showHelp();
}
else if (item.getItemId() == 5)
{
showApps();
}
return true;
}
@ -158,11 +178,9 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
protected void onPause() {
super.onPause();
}
/* (non-Javadoc)
* @see android.app.Activity#onResume()
*/
@ -171,6 +189,17 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
super.onResume();
updateStatus (""); //update the status, which checks the service status
if (showingSettings)
{
showingSettings = false;
processSettings();
}
}
/* (non-Javadoc)
@ -198,6 +227,8 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
protected void onStop() {
super.onStop();
TorServiceUtils.saveAppSettings(this);
unbindService();
}
@ -210,6 +241,24 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
{
bindService(); //connect the UI activity to the remote service
if (currentView == R.layout.layout_apps)
{
if (hasRoot)
{
TorServiceUtils.saveAppSettings(this);
if (enableTransparentProxy)
{
TorTransProxy.purgeNatIptables();
TorTransProxy.setDNSProxying();
TorTransProxy.setTransparentProxying(this, TorServiceUtils.getApps(this));
}
}
}
currentView = R.layout.layout_main;
setContentView(currentView);
@ -232,6 +281,67 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
}
private void loadApps ()
{
final TorifiedApp[] apps = TorServiceUtils.getApps(this);
Arrays.sort(apps, new Comparator<TorifiedApp>() {
@Override
public int compare(TorifiedApp o1, TorifiedApp o2) {
if (o1.isTorified() == o2.isTorified()) return o1.getName().compareTo(o2.getName());
if (o1.isTorified()) return -1;
return 1;
}
});
final LayoutInflater inflater = getLayoutInflater();
final ListAdapter adapter = new ArrayAdapter<TorifiedApp>(this,R.layout.layout_apps_item,R.id.itemtext,apps) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ListEntry entry;
if (convertView == null) {
// Inflate a new view
convertView = inflater.inflate(R.layout.layout_apps_item, parent, false);
entry = new ListEntry();
entry.box = (CheckBox) convertView.findViewById(R.id.itemcheck);
entry.text = (TextView) convertView.findViewById(R.id.itemtext);
convertView.setTag(entry);
entry.box.setOnCheckedChangeListener(Orbot.this);
} else {
// Convert an existing view
entry = (ListEntry) convertView.getTag();
}
final TorifiedApp app = apps[position];
entry.text.setText(app.getName());
final CheckBox box = entry.box;
box.setTag(app);
box.setChecked(app.isTorified());
return convertView;
}
};
this.listApps.setAdapter(adapter);
}
/**
* Called an application is check/unchecked
*/
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
final TorifiedApp app = (TorifiedApp) buttonView.getTag();
if (app != null) {
app.setTorified(isChecked);
}
TorServiceUtils.saveAppSettings(this);
}
private static class ListEntry {
private CheckBox box;
private TextView text;
}
/*
* Show the about view - a popup dialog
*/
@ -285,7 +395,16 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
.show();
}
private void showApps ()
{
currentView = R.layout.layout_apps;
setContentView(currentView);
listApps = (ListView)findViewById(R.id.applistview);
loadApps();
}
/*
* Show the message log UI
*/
@ -308,6 +427,7 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
private void showSettings ()
{
showingSettings = true;
startActivity(new Intent(this, SettingsPreferences.class));
@ -331,6 +451,19 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
enableTransparentProxy = prefs.getBoolean(PREF_TRANSPARENT, false);
if (hasRoot)
{
if (enableTransparentProxy)
{
TorTransProxy.setDNSProxying();
TorTransProxy.setTransparentProxying(this, TorServiceUtils.getApps(this));
}
else
{
TorTransProxy.purgeNatIptables();
}
}
String bridgeList = prefs.getString(PREF_BRIDGES_LIST,"");
if (useBridges)
@ -436,7 +569,7 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
}
}
else if (torStatus == STATUS_UNAVAILABLE)
else if (torStatus == STATUS_OFF)
{
imgStatus.setImageResource(R.drawable.torstopping);
lblStatus.setText(getString(R.string.status_shutting_down));
@ -493,15 +626,16 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
{
}
else if (mService.getStatus() == STATUS_REQUIRES_DEMAND)
else if (mService.getStatus() == STATUS_READY)
{
processSettings();
mService.setProfile(PROFILE_ON);
if (hasRoot && enableTransparentProxy)
{
TorRoot.enableDNSProxying();
TorRoot.enabledWebProxying();
TorTransProxy.setDNSProxying();
TorTransProxy.setTransparentProxying(this,TorServiceUtils.getApps(this));
}
}
else
@ -511,7 +645,9 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
if (hasRoot && enableTransparentProxy)
{
TorRoot.purgeNatIptables();
TorTransProxy.purgeNatIptables();
//TorRoot.setDNSProxying(false);
//TorRoot.setTransparentProxying(this,false);
}
}
}
@ -617,7 +753,7 @@ public class Orbot extends Activity implements OnClickListener, TorConstants
mIsBound = true;
hasRoot = TorRoot.hasRootAccess();
hasRoot = TorTransProxy.hasRootAccess();
}

View File

@ -5,6 +5,7 @@ package org.torproject.android;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.util.Log;
public class SettingsPreferences
extends PreferenceActivity {
@ -13,4 +14,15 @@ public class SettingsPreferences
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
/* (non-Javadoc)
* @see android.app.Activity#onStop()
*/
@Override
protected void onStop() {
super.onStop();
Log.i(getClass().getName(),"Exiting Preferences");
}
}

View File

@ -12,8 +12,8 @@ public interface TorConstants {
//path to check Tor against
public final static String URL_TOR_CHECK = "https://check.torproject.org";
public final static int STATUS_UNAVAILABLE = -1;
public final static int STATUS_REQUIRES_DEMAND = 0;
public final static int STATUS_OFF = -1;
public final static int STATUS_READY = 0;
public final static int STATUS_ON = 1;
public final static int STATUS_CONNECTING = 2;

View File

@ -0,0 +1,100 @@
package org.torproject.android;
public class TorifiedApp {
private boolean enabled;
private int uid;
private String username;
private String procname;
private String name;
private boolean torified = false;
/**
* @return the torified
*/
public boolean isTorified() {
return torified;
}
/**
* @param torified the torified to set
*/
public void setTorified(boolean torified) {
this.torified = torified;
}
private int[] enabledPorts;
/**
* @return the enabledPorts
*/
public int[] getEnabledPorts() {
return enabledPorts;
}
/**
* @param enabledPorts the enabledPorts to set
*/
public void setEnabledPorts(int[] enabledPorts) {
this.enabledPorts = enabledPorts;
}
/**
* @return the enabled
*/
public boolean isEnabled() {
return enabled;
}
/**
* @param enabled the enabled to set
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* @return the uid
*/
public int getUid() {
return uid;
}
/**
* @param uid the uid to set
*/
public void setUid(int uid) {
this.uid = uid;
}
/**
* @return the username
*/
public String getUsername() {
return username;
}
/**
* @param username the username to set
*/
public void setUsername(String username) {
this.username = username;
}
/**
* @return the procname
*/
public String getProcname() {
return procname;
}
/**
* @param procname the procname to set
*/
public void setProcname(String procname) {
this.procname = procname;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
}

View File

@ -1,259 +0,0 @@
/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */
/* See LICENSE for licensing information */
package org.torproject.android.service;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.List;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.Log;
/**
* Contains shared programming interfaces.
* All iptables "communication" is handled by this class.
*/
public final class TorRoot {
private final static String TAG = "TOR_ROOT";
// Do we have root access?
private static boolean hasroot = false;
private final static String CMD_NAT_FLUSH = "iptables -t nat -F || exit\n";
private final static String CMD_NAT_IPTABLES_80 = "iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to 127.0.0.1:8118 || exit\n";
private final static String CMD_NAT_IPTABLES_443 = "iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to 127.0.0.1:9040 || exit\n";
private final static String CMD_DNS_PROXYING = "iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to 127.0.0.1:5400 || exit\n";
public static boolean enableDNSProxying ()
{
final StringBuilder script = new StringBuilder();
int code;
//Enable UDP Proxying
script.append(CMD_DNS_PROXYING);
StringBuilder res = new StringBuilder();
try
{
code = runScriptAsRoot(script.toString(), res);
if (code != 0)
{
Log.w(TAG, "error apply DNS proxying: " + res.toString());
}
} catch (Exception e) {
Log.w(TAG, "error apply DNS proxying: " + res.toString(), e);
return false;
}
return true;
}
/**
* Purge and re-add all rules (internal implementation).
* @param ctx application context (mandatory)
* @param uids list of selected uids to allow or disallow (depending on the working mode)
* @param showErrors indicates if errors should be alerted
*/
public static boolean enabledWebProxying() {
final StringBuilder script = new StringBuilder();
try {
int code;
script.append(CMD_NAT_IPTABLES_80);
script.append(CMD_NAT_IPTABLES_443);
/*
int uid = android.os.Process.getUidForName("dhcp");
if (uid != -1) script.append("iptables -A OUTPUT " + itfFilter + " -m owner --uid-owner " + uid + " -j ACCEPT || exit\n");
uid = android.os.Process.getUidForName("wifi");
if (uid != -1) script.append("iptables -A OUTPUT " + itfFilter + " -m owner --uid-owner " + uid + " -j ACCEPT || exit\n");
*/
StringBuilder res = new StringBuilder();
code = runScriptAsRoot(script.toString(), res);
String msg = res.toString();
Log.e(TAG, msg);
} catch (Exception e) {
Log.w(TAG, "error refreshing iptables: " + e);
}
return false;
}
/**
* Purge all iptables rules.
* @return true if the rules were purged
*/
public static boolean purgeNatIptables() {
StringBuilder res = new StringBuilder();
try {
int code = runScriptAsRoot(CMD_NAT_FLUSH, res);
if (code != 0) {
Log.w(TAG, "error purging iptables. exit code: " + code + "\n" + res);
return false;
}
return true;
} catch (Exception e) {
Log.w(TAG,"error purging iptables: " + e);
return false;
}
}
/**
* Check if we have root access
* @return boolean true if we have root
*/
public static boolean hasRootAccess() {
if (hasroot) return true;
try {
// Run an empty script just to check root access
if (runScriptAsRoot("exit 0", null, 20000) == 0) {
hasroot = true;
return true;
}
} catch (Exception e) {
}
Log.w(TAG, "Could not acquire root access.");
return false;
}
/**
* Runs a script as root (multiple commands separated by "\n").
*
* @param script the script to be executed
* @param res the script output response (stdout + stderr)
* @param timeout timeout in milliseconds (-1 for none)
* @return the script exit code
*/
public static int runScriptAsRoot(String script, StringBuilder res, final long timeout) {
Log.i(TAG,"executing script: " + script);
final ScriptRunner runner = new ScriptRunner(script, res);
runner.start();
try {
if (timeout > 0) {
runner.join(timeout);
} else {
runner.join();
}
if (runner.isAlive()) {
// Timed-out
runner.interrupt();
runner.destroy();
runner.join(50);
}
} catch (InterruptedException ex) {}
return runner.exitcode;
}
/**
* Runs a script as root (multiple commands separated by "\n") with a default timeout of 5 seconds.
*
* @param script the script to be executed
* @param res the script output response (stdout + stderr)
* @param timeout timeout in milliseconds (-1 for none)
* @return the script exit code
* @throws IOException on any error executing the script, or writing it to disk
*/
public static int runScriptAsRoot(String script, StringBuilder res) throws IOException {
return runScriptAsRoot(script, res, 5000);
}
/**
* Internal thread used to execute scripts as root.
*/
private static final class ScriptRunner extends Thread {
private final String script;
private final StringBuilder res;
public int exitcode = -1;
private Process exec;
/**
* Creates a new script runner.
* @param script script to run
* @param res response output
*/
public ScriptRunner(String script, StringBuilder res) {
this.script = script;
this.res = res;
}
@Override
public void run() {
try {
// Create the "su" request to run the command
// note that this will create a shell that we must interact to (using stdin/stdout)
exec = Runtime.getRuntime().exec("su");
final OutputStreamWriter out = new OutputStreamWriter(exec.getOutputStream());
// Write the script to be executed
out.write(script);
// Ensure that the last character is an "enter"
if (!script.endsWith("\n")) out.write("\n");
out.flush();
// Terminate the "su" process
out.write("exit\n");
out.flush();
final char buf[] = new char[1024];
// Consume the "stdout"
InputStreamReader r = new InputStreamReader(exec.getInputStream());
int read=0;
while ((read=r.read(buf)) != -1) {
if (res != null) res.append(buf, 0, read);
}
// Consume the "stderr"
r = new InputStreamReader(exec.getErrorStream());
read=0;
while ((read=r.read(buf)) != -1) {
if (res != null) res.append(buf, 0, read);
}
// get the process exit code
if (exec != null) this.exitcode = exec.waitFor();
} catch (InterruptedException ex) {
if (res != null) res.append("\nOperation timed-out");
} catch (Exception ex) {
if (res != null) res.append("\n" + ex);
} finally {
destroy();
}
}
/**
* Destroy this script runner
*/
public synchronized void destroy() {
if (exec != null) exec.destroy();
exec = null;
}
}
public void getApps (Context context)
{
PackageManager pMgr = context.getPackageManager();
List<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator();
ApplicationInfo aInfo = null;
while (itAppInfo.hasNext())
{
aInfo = itAppInfo.next();
boolean appEnabled = aInfo.enabled;
int uid = aInfo.uid; //-m owner --uid-owner
String username = pMgr.getNameForUid(uid);
String procName = aInfo.processName;
String name = aInfo.name;
}
}
}

View File

@ -32,7 +32,7 @@ public class TorService extends Service implements TorServiceConstants, Runnable
private static int currentStatus = STATUS_REQUIRES_DEMAND;
private static int currentStatus = STATUS_READY;
private TorControlConnection conn = null;
@ -80,12 +80,12 @@ public class TorService extends Service implements TorServiceConstants, Runnable
} catch (RuntimeException e) {
Log.i(TAG,"Unable to connect to existing Tor instance,",e);
currentStatus = STATUS_REQUIRES_DEMAND;
currentStatus = STATUS_OFF;
this.stopTor();
} catch (Exception e) {
Log.i(TAG,"Unable to connect to existing Tor instance,",e);
currentStatus = STATUS_REQUIRES_DEMAND;
currentStatus = STATUS_OFF;
this.stopTor();
}
@ -203,7 +203,7 @@ public class TorService extends Service implements TorServiceConstants, Runnable
}
catch (Exception e)
{
currentStatus = STATUS_REQUIRES_DEMAND;
currentStatus = STATUS_OFF;
this.showToolbarNotification("Orbot", "Unable to start Tor", R.drawable.tornotification);
Log.i(TAG,"Unable to start Tor: " + e.getMessage(),e);
}
@ -226,14 +226,14 @@ public class TorService extends Service implements TorServiceConstants, Runnable
private void stopTor ()
{
currentStatus = STATUS_UNAVAILABLE;
currentStatus = STATUS_OFF;
sendCallbackMessage("Web proxy shutdown");
killTorProcess ();
currentStatus = STATUS_REQUIRES_DEMAND;
currentStatus = STATUS_READY;
showToolbarNotification ("Orbot","Anonymous browsing is disabled",R.drawable.tornotificationoff);
sendCallbackMessage("Anonymous browsing is disabled");
@ -591,7 +591,7 @@ public class TorService extends Service implements TorServiceConstants, Runnable
}
else
{
currentStatus = STATUS_UNAVAILABLE;
currentStatus = STATUS_OFF;
sendCallbackMessage ("shutting down...");
_torInstance.stopTor();

View File

@ -76,12 +76,11 @@ public interface TorServiceConstants {
//control port
public final static String TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE = "Bootstrapped 100%";
public final static int STATUS_UNAVAILABLE = -1;
public final static int STATUS_REQUIRES_DEMAND = 0;
public final static int STATUS_OFF = -1;
public final static int STATUS_READY = 0;
public final static int STATUS_ON = 1;
public final static int STATUS_CONNECTING = 2;
public final static int PROFILE_OFF = -1;
public final static int PROFILE_ONDEMAND = 0;
public final static int PROFILE_ON = 1;
}

View File

@ -5,12 +5,109 @@ package org.torproject.android.service;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.torproject.android.TorifiedApp;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.Log;
public class TorServiceUtils implements TorServiceConstants {
private static TorifiedApp[] apps = null;
private final static String PREFS_KEY = "OrbotPrefs";
private final static String PREFS_KEY_TORIFIED = "PrefTord";
public static void saveAppSettings (Context context)
{
if (apps == null)
return;
final SharedPreferences prefs = context.getSharedPreferences(PREFS_KEY, 0);
StringBuilder tordApps = new StringBuilder();
for (int i = 0; i < apps.length; i++)
{
if (apps[i].isTorified())
{
tordApps.append(apps[i].getUsername());
tordApps.append("|");
}
}
Editor edit = prefs.edit();
edit.putString(PREFS_KEY_TORIFIED, tordApps.toString());
edit.commit();
}
public static TorifiedApp[] getApps (Context context)
{
if (apps != null)
return apps;
final SharedPreferences prefs = context.getSharedPreferences(PREFS_KEY, 0);
String tordAppString = prefs.getString(PREFS_KEY_TORIFIED, "");
String[] tordApps;
StringTokenizer st = new StringTokenizer(tordAppString,"|");
tordApps = new String[st.countTokens()];
int tordIdx = 0;
while (st.hasMoreTokens())
{
tordApps[tordIdx++] = st.nextToken();
}
Arrays.sort(tordApps);
//else load the apps up
PackageManager pMgr = context.getPackageManager();
List<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator();
apps = new TorifiedApp[lAppInfo.size()];
ApplicationInfo aInfo = null;
int appIdx = 0;
while (itAppInfo.hasNext())
{
aInfo = itAppInfo.next();
apps[appIdx] = new TorifiedApp();
apps[appIdx].setEnabled(aInfo.enabled);
apps[appIdx].setUid(aInfo.uid);
apps[appIdx].setUsername(pMgr.getNameForUid(apps[appIdx].getUid()));
apps[appIdx].setProcname(aInfo.processName);
apps[appIdx].setName(pMgr.getApplicationLabel(aInfo).toString());
// check if this application is allowed
if (Arrays.binarySearch(tordApps, apps[appIdx].getUsername()) >= 0) {
apps[appIdx].setTorified(true);
}
else
{
apps[appIdx].setTorified(false);
}
appIdx++;
}
return apps;
}
public static int findProcessId(String command)
{
@ -104,18 +201,17 @@ public class TorServiceUtils implements TorServiceConstants {
{
Log.i(TAG,"executing commands: " + cmds.length);
Runtime runtime = Runtime.getRuntime();
Process proc = null;
int exitCode = -1;
try {
proc = runtime.exec(cmds[0]);
proc = Runtime.getRuntime().exec("su");
OutputStreamWriter out = new OutputStreamWriter(proc.getOutputStream());
for (int i = 1; i < cmds.length; i++)
for (int i = 0; i < cmds.length; i++)
{
out.write(cmds[i]);
out.write("\n");
@ -123,7 +219,8 @@ public class TorServiceUtils implements TorServiceConstants {
out.flush();
out.write("exit\n");
out.flush();
/*
final char buf[] = new char[1024];
// Consume the "stdout"
InputStreamReader reader = new InputStreamReader(proc.getInputStream());
@ -136,7 +233,7 @@ public class TorServiceUtils implements TorServiceConstants {
read=0;
while ((read=reader.read(buf)) != -1) {
if (log != null) log.append(buf, 0, read);
}
}*/
exitCode = proc.waitFor();

View File

@ -0,0 +1,124 @@
package org.torproject.android.service;
import java.util.Iterator;
import java.util.List;
import org.torproject.android.TorifiedApp;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.Log;
public class TorTransProxy {
private final static String TAG = "TorTransProxy";
private final static String CMD_NAT_FLUSH = "iptables -t nat -F || exit\n";
// private final static String CMD_NAT_IPTABLES_ALL = "iptables -t nat -A OUTPUT -j DNAT --to 127.0.0.1:9040 || exit\n";
private final static String CMD_DNS_PROXYING_ADD = "iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to 127.0.0.1:5400 || exit\n";
private final static String CMD_DNS_PROXYING_DELETE = "iptables -t nat -D PREROUTING -p udp --dport 53 -j DNAT --to 127.0.0.1:5400 || exit\n";
private final static String IPTABLES_ADD = " -A ";
private final static String IPTABLES_DELETE = " -D ";
private static boolean hasRoot = false;
/**
* Check if we have root access
* @return boolean true if we have root
*/
public static boolean hasRootAccess() {
if (hasRoot) return true;
try {
// Run an empty script just to check root access
String[] cmd = {"exit 0"};
if (TorServiceUtils.doRootCommand(cmd, null) == 0) {
hasRoot = true;
return true;
}
} catch (Exception e) {
}
Log.w(TAG, "Could not acquire root access.");
return false;
}
public static int setDNSProxying ()
{
final StringBuilder log = new StringBuilder();
int code;
String[] cmds = {CMD_DNS_PROXYING_ADD};
code = TorServiceUtils.doRootCommand(cmds, log);
return code;
}
public static boolean purgeNatIptables() {
StringBuilder res = new StringBuilder();
try {
String[] cmds = {CMD_NAT_FLUSH};
int code = TorServiceUtils.doRootCommand(cmds, res);
if (code != 0) {
Log.w(TAG, "error purging iptables. exit code: " + code + "\n" + res);
return false;
}
return true;
} catch (Exception e) {
Log.w(TAG,"error purging iptables: " + e);
return false;
}
}
public static boolean setTransparentProxying(Context context, TorifiedApp[] apps) {
String command = null;
command = IPTABLES_ADD; //ADD
final StringBuilder script = new StringBuilder();
try {
int code;
for (int i = 0; i < apps.length; i++)
{
if (apps[i].getUsername().startsWith("org.torproject.android")) //we never want to Tor this!
continue;
if (apps[i].isTorified())
{
Log.i(TAG,"enabling transproxy for app: " + apps[i].getUsername() + "(" + apps[i].getUid() + ")");
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");
}
}
StringBuilder res = new StringBuilder();
String[] cmd = {script.toString()};
code = TorServiceUtils.doRootCommand(cmd, res);
String msg = res.toString();
Log.e(TAG, msg);
} catch (Exception e) {
Log.w(TAG, "error refreshing iptables: " + e);
}
return false;
}
}