diff --git a/src/org/torproject/android/Orbot.java b/src/org/torproject/android/Orbot.java index eac3d64e..d017078d 100644 --- a/src/org/torproject/android/Orbot.java +++ b/src/org/torproject/android/Orbot.java @@ -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() { + @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(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(); } diff --git a/src/org/torproject/android/SettingsPreferences.java b/src/org/torproject/android/SettingsPreferences.java index cdd5befa..f101cfa8 100644 --- a/src/org/torproject/android/SettingsPreferences.java +++ b/src/org/torproject/android/SettingsPreferences.java @@ -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"); + } + } diff --git a/src/org/torproject/android/TorConstants.java b/src/org/torproject/android/TorConstants.java index c0930db2..a1988804 100644 --- a/src/org/torproject/android/TorConstants.java +++ b/src/org/torproject/android/TorConstants.java @@ -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; diff --git a/src/org/torproject/android/TorifiedApp.java b/src/org/torproject/android/TorifiedApp.java new file mode 100644 index 00000000..1ba8afe3 --- /dev/null +++ b/src/org/torproject/android/TorifiedApp.java @@ -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; + } + +} diff --git a/src/org/torproject/android/service/TorRoot.java b/src/org/torproject/android/service/TorRoot.java deleted file mode 100644 index 92c3ee8e..00000000 --- a/src/org/torproject/android/service/TorRoot.java +++ /dev/null @@ -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 lAppInfo = pMgr.getInstalledApplications(0); - - Iterator 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; - - } - } -} diff --git a/src/org/torproject/android/service/TorService.java b/src/org/torproject/android/service/TorService.java index 19c46e92..5af9c294 100644 --- a/src/org/torproject/android/service/TorService.java +++ b/src/org/torproject/android/service/TorService.java @@ -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(); diff --git a/src/org/torproject/android/service/TorServiceConstants.java b/src/org/torproject/android/service/TorServiceConstants.java index e70cb1b1..d27efb64 100644 --- a/src/org/torproject/android/service/TorServiceConstants.java +++ b/src/org/torproject/android/service/TorServiceConstants.java @@ -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; } diff --git a/src/org/torproject/android/service/TorServiceUtils.java b/src/org/torproject/android/service/TorServiceUtils.java index af6b31e3..3e549ccb 100644 --- a/src/org/torproject/android/service/TorServiceUtils.java +++ b/src/org/torproject/android/service/TorServiceUtils.java @@ -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 lAppInfo = pMgr.getInstalledApplications(0); + + Iterator 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(); diff --git a/src/org/torproject/android/service/TorTransProxy.java b/src/org/torproject/android/service/TorTransProxy.java new file mode 100644 index 00000000..032cd436 --- /dev/null +++ b/src/org/torproject/android/service/TorTransProxy.java @@ -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; + } + +}