parent
eafeb64b9c
commit
245cef1e32
|
@ -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();
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue