diff --git a/src/org/torproject/android/TorConstants.java b/src/org/torproject/android/TorConstants.java index df430c68..755a2098 100644 --- a/src/org/torproject/android/TorConstants.java +++ b/src/org/torproject/android/TorConstants.java @@ -43,5 +43,6 @@ public interface TorConstants { public final static String PREF_HAS_ROOT = "has_root"; public final static int RESULT_CLOSE_ALL = 0; + public final static String PREF_USE_SYSTEM_IPTABLES = "pref_use_sys_iptables"; } diff --git a/src/org/torproject/android/service/IptablesManager.java b/src/org/torproject/android/service/IptablesManager.java deleted file mode 100644 index 27817052..00000000 --- a/src/org/torproject/android/service/IptablesManager.java +++ /dev/null @@ -1,1058 +0,0 @@ -/** - * Contains shared programming interfaces. - * All iptables "communication" is handled by this class. - * - * Copyright (C) 2009-2010 Rodrigo Zechin Rosauro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * @author Rodrigo Zechin Rosauro - * @version 1.0 - */ - -package org.torproject.android.service; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.StringReader; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.StringTokenizer; - -import org.torproject.android.R; - -import android.Manifest; -import android.app.AlertDialog; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.util.Log; -import android.widget.Toast; - -/** - * Contains shared programming interfaces. - * All iptables "communication" is handled by this class. - */ -public final class IptablesManager { - /** application version string */ - public static final String VERSION = "1.5.1-dev"; - /** special application UID used to indicate "any application" */ - public static final int SPECIAL_UID_ANY = -10; - /** special application UID used to indicate the Linux Kernel */ - public static final int SPECIAL_UID_KERNEL = -11; - /** root script filename */ - private static final String SCRIPT_FILE = "droidwall.sh"; - - // Preferences - public static final String PREFS_NAME = "DroidWallPrefs"; - public static final String PREF_3G_UIDS = "AllowedUids3G"; - public static final String PREF_WIFI_UIDS = "AllowedUidsWifi"; - public static final String PREF_PASSWORD = "Password"; - public static final String PREF_MODE = "BlockMode"; - public static final String PREF_ENABLED = "Enabled"; - public static final String PREF_LOGENABLED = "LogEnabled"; - // Modes - public static final String MODE_WHITELIST = "whitelist"; - public static final String MODE_BLACKLIST = "blacklist"; - // Messages - public static final String STATUS_CHANGED_MSG = "com.googlecode.droidwall.intent.action.STATUS_CHANGED"; - public static final String TOGGLE_REQUEST_MSG = "com.googlecode.droidwall.intent.action.TOGGLE_REQUEST"; - public static final String STATUS_EXTRA = "com.googlecode.droidwall.intent.extra.STATUS"; - - // Cached applications - public static DroidApp applications[] = null; - // Do we have root access? - private static boolean hasroot = false; - // Flag indicating if this is an ARMv6 device (-1: unknown, 0: no, 1: yes) - private static int isARMv6 = -1; - - /** - * Display a simple alert box - * @param ctx context - * @param msg message - */ - public static void alert(Context ctx, CharSequence msg) { - if (ctx != null) { - new AlertDialog.Builder(ctx) - .setNeutralButton(android.R.string.ok, null) - .setMessage(msg) - .show(); - } - } - /** - * Check if this is an ARMv6 device - * @return true if this is ARMv6 - */ - private static boolean isARMv6() { - if (isARMv6 == -1) { - BufferedReader r = null; - try { - isARMv6 = 0; - r = new BufferedReader(new FileReader("/proc/cpuinfo")); - for (String line = r.readLine(); line != null; line = r.readLine()) { - if (line.startsWith("Processor") && line.contains("ARMv6")) { - isARMv6 = 1; - break; - } else if (line.startsWith("CPU architecture") && (line.contains("6TE") || line.contains("5TE"))) { - isARMv6 = 1; - break; - } - } - } catch (Exception ex) { - } finally { - if (r != null) try {r.close();} catch (Exception ex) {} - } - } - return (isARMv6 == 1); - } - /** - * Create the generic shell script header used to determine which iptables binary to use. - * @param ctx context - * @return script header - */ - private static String scriptHeader(Context ctx) { - final String dir = ctx.getDir("bin",0).getAbsolutePath(); - final String myiptables = dir + (isARMv6() ? "/iptables_g1" : "/iptables_n1"); - return "" + - "IPTABLES=iptables\n" + - "BUSYBOX=busybox\n" + - "GREP=grep\n" + - "ECHO=echo\n" + - "# Try to find busybox\n" + - "if " + dir + "/busybox_g1 --help >/dev/null 2>/dev/null ; then\n" + - " BUSYBOX="+dir+"/busybox_g1\n" + - " GREP=\"$BUSYBOX grep\"\n" + - " ECHO=\"$BUSYBOX echo\"\n" + - "elif busybox --help >/dev/null 2>/dev/null ; then\n" + - " BUSYBOX=busybox\n" + - "elif /system/xbin/busybox --help >/dev/null 2>/dev/null ; then\n" + - " BUSYBOX=/system/xbin/busybox\n" + - "elif /system/bin/busybox --help >/dev/null 2>/dev/null ; then\n" + - " BUSYBOX=/system/bin/busybox\n" + - "fi\n" + - "# Try to find grep\n" + - "if ! $ECHO 1 | $GREP -q 1 >/dev/null 2>/dev/null ; then\n" + - " if $ECHO 1 | $BUSYBOX grep -q 1 >/dev/null 2>/dev/null ; then\n" + - " GREP=\"$BUSYBOX grep\"\n" + - " fi\n" + - " # Grep is absolutely required\n" + - " if ! $ECHO 1 | $GREP -q 1 >/dev/null 2>/dev/null ; then\n" + - " $ECHO The grep command is required. DroidWall will not work.\n" + - " exit 1\n" + - " fi\n" + - "fi\n" + - "# Try to find iptables\n" + - "if " + myiptables + " --version >/dev/null 2>/dev/null ; then\n" + - " IPTABLES="+myiptables+"\n" + - "fi\n" + - ""; - } - /** - * Copies a raw resource file, given its ID to the given location - * @param ctx context - * @param resid resource id - * @param file destination file - * @param mode file permissions (E.g.: "755") - * @throws IOException on error - * @throws InterruptedException when interrupted - */ - private static void copyRawFile(Context ctx, int resid, File file, String mode) throws IOException, InterruptedException - { - final String abspath = file.getAbsolutePath(); - // Write the iptables binary - final FileOutputStream out = new FileOutputStream(file); - final InputStream is = ctx.getResources().openRawResource(resid); - byte buf[] = new byte[1024]; - int len; - while ((len = is.read(buf)) > 0) { - out.write(buf, 0, len); - } - out.close(); - is.close(); - // Change the permissions - Runtime.getRuntime().exec("chmod "+mode+" "+abspath).waitFor(); - } - /** - * Purge and re-add all rules (internal implementation). - * @param ctx application context (mandatory) - * @param uidsWifi list of selected UIDs for WIFI to allow or disallow (depending on the working mode) - * @param uids3g list of selected UIDs for 2G/3G to allow or disallow (depending on the working mode) - * @param showErrors indicates if errors should be alerted - */ - private static boolean applyIptablesRulesImpl(Context ctx, List uidsWifi, List uids3g, boolean showErrors) { - if (ctx == null) { - return false; - } - assertBinaries(ctx, showErrors); - final String ITFS_WIFI[] = {"tiwlan+", "wlan+", "eth+"}; - final String ITFS_3G[] = {"rmnet+","pdp+","ppp+","uwbr+","wimax+","vsnet+"}; - final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); - final boolean whitelist = prefs.getString(PREF_MODE, MODE_WHITELIST).equals(MODE_WHITELIST); - final boolean blacklist = !whitelist; - final boolean logenabled = ctx.getSharedPreferences(PREFS_NAME, 0).getBoolean(PREF_LOGENABLED, false); - - final StringBuilder script = new StringBuilder(); - try { - int code; - script.append(scriptHeader(ctx)); - script.append("" + - "$IPTABLES --version || exit 1\n" + - "# Create the droidwall chains if necessary\n" + - "$IPTABLES -L droidwall >/dev/null 2>/dev/null || $IPTABLES --new droidwall || exit 2\n" + - "$IPTABLES -L droidwall-3g >/dev/null 2>/dev/null || $IPTABLES --new droidwall-3g || exit 3\n" + - "$IPTABLES -L droidwall-wifi >/dev/null 2>/dev/null || $IPTABLES --new droidwall-wifi || exit 4\n" + - "$IPTABLES -L droidwall-reject >/dev/null 2>/dev/null || $IPTABLES --new droidwall-reject || exit 5\n" + - "# Add droidwall chain to OUTPUT chain if necessary\n" + - "$IPTABLES -L OUTPUT | $GREP -q droidwall || $IPTABLES -A OUTPUT -j droidwall || exit 6\n" + - "# Flush existing rules\n" + - "$IPTABLES -F droidwall || exit 7\n" + - "$IPTABLES -F droidwall-3g || exit 8\n" + - "$IPTABLES -F droidwall-wifi || exit 9\n" + - "$IPTABLES -F droidwall-reject || exit 10\n" + - ""); - // Check if logging is enabled - if (logenabled) { - script.append("" + - "# Create the log and reject rules (ignore errors on the LOG target just in case it is not available)\n" + - "$IPTABLES -A droidwall-reject -j LOG --log-prefix \"[DROIDWALL] \" --log-uid\n" + - "$IPTABLES -A droidwall-reject -j REJECT || exit 11\n" + - ""); - } else { - script.append("" + - "# Create the reject rule (log disabled)\n" + - "$IPTABLES -A droidwall-reject -j REJECT || exit 11\n" + - ""); - } - if (whitelist && logenabled) { - script.append("# Allow DNS lookups on white-list for a better logging (ignore errors)\n"); - script.append("$IPTABLES -A droidwall -p udp --dport 53 -j RETURN\n"); - } - script.append("# Main rules (per interface)\n"); - for (final String itf : ITFS_3G) { - script.append("$IPTABLES -A droidwall -o ").append(itf).append(" -j droidwall-3g || exit\n"); - } - for (final String itf : ITFS_WIFI) { - script.append("$IPTABLES -A droidwall -o ").append(itf).append(" -j droidwall-wifi || exit\n"); - } - - script.append("# Filtering rules\n"); - final String targetRule = (whitelist ? "RETURN" : "droidwall-reject"); - final boolean any_3g = uids3g.indexOf(SPECIAL_UID_ANY) >= 0; - final boolean any_wifi = uidsWifi.indexOf(SPECIAL_UID_ANY) >= 0; - if (whitelist && !any_wifi) { - // When "white listing" wifi, we need to ensure that the dhcp and wifi users are allowed - int uid = android.os.Process.getUidForName("dhcp"); - if (uid != -1) { - script.append("# dhcp user\n"); - script.append("$IPTABLES -A droidwall-wifi -m owner --uid-owner ").append(uid).append(" -j RETURN || exit\n"); - } - uid = android.os.Process.getUidForName("wifi"); - if (uid != -1) { - script.append("# wifi user\n"); - script.append("$IPTABLES -A droidwall-wifi -m owner --uid-owner ").append(uid).append(" -j RETURN || exit\n"); - } - } - if (any_3g) { - if (blacklist) { - /* block any application on this interface */ - script.append("$IPTABLES -A droidwall-3g -j ").append(targetRule).append(" || exit\n"); - } - } else { - /* release/block individual applications on this interface */ - for (final Integer uid : uids3g) { - if (uid >= 0) script.append("$IPTABLES -A droidwall-3g -m owner --uid-owner ").append(uid).append(" -j ").append(targetRule).append(" || exit\n"); - } - } - if (any_wifi) { - if (blacklist) { - /* block any application on this interface */ - script.append("$IPTABLES -A droidwall-wifi -j ").append(targetRule).append(" || exit\n"); - } - } else { - /* release/block individual applications on this interface */ - for (final Integer uid : uidsWifi) { - if (uid >= 0) script.append("$IPTABLES -A droidwall-wifi -m owner --uid-owner ").append(uid).append(" -j ").append(targetRule).append(" || exit\n"); - } - } - if (whitelist) { - if (!any_3g) { - if (uids3g.indexOf(SPECIAL_UID_KERNEL) >= 0) { - script.append("# hack to allow kernel packets on white-list\n"); - script.append("$IPTABLES -A droidwall-3g -m owner --uid-owner 0:999999999 -j droidwall-reject || exit\n"); - } else { - script.append("$IPTABLES -A droidwall-3g -j droidwall-reject || exit\n"); - } - } - if (!any_wifi) { - if (uidsWifi.indexOf(SPECIAL_UID_KERNEL) >= 0) { - script.append("# hack to allow kernel packets on white-list\n"); - script.append("$IPTABLES -A droidwall-wifi -m owner --uid-owner 0:999999999 -j droidwall-reject || exit\n"); - } else { - script.append("$IPTABLES -A droidwall-wifi -j droidwall-reject || exit\n"); - } - } - } else { - if (uids3g.indexOf(SPECIAL_UID_KERNEL) >= 0) { - script.append("# hack to BLOCK kernel packets on black-list\n"); - script.append("$IPTABLES -A droidwall-3g -m owner --uid-owner 0:999999999 -j RETURN || exit\n"); - script.append("$IPTABLES -A droidwall-3g -j droidwall-reject || exit\n"); - } - if (uidsWifi.indexOf(SPECIAL_UID_KERNEL) >= 0) { - script.append("# hack to BLOCK kernel packets on black-list\n"); - script.append("$IPTABLES -A droidwall-wifi -m owner --uid-owner 0:999999999 -j RETURN || exit\n"); - script.append("$IPTABLES -A droidwall-wifi -j droidwall-reject || exit\n"); - } - } - final StringBuilder res = new StringBuilder(); - code = runScriptAsRoot(ctx, script.toString(), res); - if (showErrors && code != 0) { - String msg = res.toString(); - Log.e("DroidWall", msg); - // Remove unnecessary help message from output - if (msg.indexOf("\nTry `iptables -h' or 'iptables --help' for more information.") != -1) { - msg = msg.replace("\nTry `iptables -h' or 'iptables --help' for more information.", ""); - } - alert(ctx, "Error applying iptables rules. Exit code: " + code + "\n\n" + msg.trim()); - } else { - return true; - } - } catch (Exception e) { - if (showErrors) alert(ctx, "error refreshing iptables: " + e); - } - return false; - } - /** - * Purge and re-add all saved rules (not in-memory ones). - * This is much faster than just calling "applyIptablesRules", since it don't need to read installed applications. - * @param ctx application context (mandatory) - * @param showErrors indicates if errors should be alerted - */ - public static boolean applySavedIptablesRules(Context ctx, boolean showErrors) { - if (ctx == null) { - return false; - } - final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); - final String savedUids_wifi = prefs.getString(PREF_WIFI_UIDS, ""); - final String savedUids_3g = prefs.getString(PREF_3G_UIDS, ""); - final List uids_wifi = new LinkedList(); - if (savedUids_wifi.length() > 0) { - // Check which applications are allowed on wifi - final StringTokenizer tok = new StringTokenizer(savedUids_wifi, "|"); - while (tok.hasMoreTokens()) { - final String uid = tok.nextToken(); - if (!uid.equals("")) { - try { - uids_wifi.add(Integer.parseInt(uid)); - } catch (Exception ex) { - } - } - } - } - final List uids_3g = new LinkedList(); - if (savedUids_3g.length() > 0) { - // Check which applications are allowed on 2G/3G - final StringTokenizer tok = new StringTokenizer(savedUids_3g, "|"); - while (tok.hasMoreTokens()) { - final String uid = tok.nextToken(); - if (!uid.equals("")) { - try { - uids_3g.add(Integer.parseInt(uid)); - } catch (Exception ex) { - } - } - } - } - return applyIptablesRulesImpl(ctx, uids_wifi, uids_3g, showErrors); - } - - /** - * Purge and re-add all rules. - * @param ctx application context (mandatory) - * @param showErrors indicates if errors should be alerted - */ - public static boolean applyIptablesRules(Context ctx, boolean showErrors) { - if (ctx == null) { - return false; - } - saveRules(ctx); - return applySavedIptablesRules(ctx, showErrors); - } - - /** - * Save current rules using the preferences storage. - * @param ctx application context (mandatory) - */ - public static void saveRules(Context ctx) { - final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); - final DroidApp[] apps = getApps(ctx); - // Builds a pipe-separated list of names - final StringBuilder newuids_wifi = new StringBuilder(); - final StringBuilder newuids_3g = new StringBuilder(); - for (int i=0; i/dev/null || exit\n", res); - if (code != 0) { - alert(ctx, res); - return false; - } - return true; - } catch (Exception e) { - alert(ctx, "error: " + e); - } - return false; - } - /** - * Display logs - * @param ctx application context - */ - public static void showLog(Context ctx) { - try { - StringBuilder res = new StringBuilder(); - int code = runScriptAsRoot(ctx, scriptHeader(ctx) + - "dmesg | $GREP DROIDWALL\n", res); - if (code != 0) { - if (res.length() == 0) { - res.append("Log is empty"); - } - alert(ctx, res); - return; - } - final BufferedReader r = new BufferedReader(new StringReader(res.toString())); - final Integer unknownUID = -99; - res = new StringBuilder(); - String line; - int start, end; - Integer appid; - final HashMap map = new HashMap(); - LogInfo loginfo = null; - while ((line = r.readLine()) != null) { - if (line.indexOf("[DROIDWALL]") == -1) continue; - appid = unknownUID; - if (((start=line.indexOf("UID=")) != -1) && ((end=line.indexOf(" ", start)) != -1)) { - appid = Integer.parseInt(line.substring(start+4, end)); - } - loginfo = map.get(appid); - if (loginfo == null) { - loginfo = new LogInfo(); - map.put(appid, loginfo); - } - loginfo.totalBlocked += 1; - if (((start=line.indexOf("DST=")) != -1) && ((end=line.indexOf(" ", start)) != -1)) { - String dst = line.substring(start+4, end); - if (loginfo.dstBlocked.containsKey(dst)) { - loginfo.dstBlocked.put(dst, loginfo.dstBlocked.get(dst) + 1); - } else { - loginfo.dstBlocked.put(dst, 1); - } - } - } - final DroidApp[] apps = getApps(ctx); - for (Integer id : map.keySet()) { - res.append("App ID "); - if (id != unknownUID) { - res.append(id); - for (DroidApp app : apps) { - if (app.uid == id) { - res.append(" (").append(app.names[0]); - if (app.names.length > 1) { - res.append(", ...)"); - } else { - res.append(")"); - } - break; - } - } - } else { - res.append("(kernel)"); - } - loginfo = map.get(id); - res.append(" - Blocked ").append(loginfo.totalBlocked).append(" packets"); - if (loginfo.dstBlocked.size() > 0) { - res.append(" ("); - boolean first = true; - for (String dst : loginfo.dstBlocked.keySet()) { - if (!first) { - res.append(", "); - } - res.append(loginfo.dstBlocked.get(dst)).append(" packets for ").append(dst); - first = false; - } - res.append(")"); - } - res.append("\n\n"); - } - if (res.length() == 0) { - res.append("Log is empty"); - } - alert(ctx, res); - } catch (Exception e) { - alert(ctx, "error: " + e); - } - } - - /** - * @param ctx application context (mandatory) - * @return a list of applications - */ - public static DroidApp[] getApps(Context ctx) { - if (applications != null) { - // return cached instance - return applications; - } - final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); - // allowed application names separated by pipe '|' (persisted) - final String savedUids_wifi = prefs.getString(PREF_WIFI_UIDS, ""); - final String savedUids_3g = prefs.getString(PREF_3G_UIDS, ""); - int selected_wifi[] = new int[0]; - int selected_3g[] = new int[0]; - if (savedUids_wifi.length() > 0) { - // Check which applications are allowed - final StringTokenizer tok = new StringTokenizer(savedUids_wifi, "|"); - selected_wifi = new int[tok.countTokens()]; - for (int i=0; i 0) { - // Check which applications are allowed - final StringTokenizer tok = new StringTokenizer(savedUids_3g, "|"); - selected_3g = new int[tok.countTokens()]; - for (int i=0; i installed = pkgmanager.getInstalledApplications(0); - final HashMap map = new HashMap(); - final Editor edit = prefs.edit(); - boolean changed = false; - String name = null; - String cachekey = null; - DroidApp app = null; - for (final ApplicationInfo apinfo : installed) { - app = map.get(apinfo.uid); - // filter applications which are not allowed to access the Internet - if (app == null && PackageManager.PERMISSION_GRANTED != pkgmanager.checkPermission(Manifest.permission.INTERNET, apinfo.packageName)) { - continue; - } - // try to get the application label from our cache - getApplicationLabel() is horribly slow!!!! - cachekey = "cache.label."+apinfo.packageName; - name = prefs.getString(cachekey, ""); - if (name.length() == 0) { - // get label and put on cache - name = pkgmanager.getApplicationLabel(apinfo).toString(); - edit.putString(cachekey, name); - changed = true; - } - if (app == null) { - app = new DroidApp(); - app.uid = apinfo.uid; - app.names = new String[] { name }; - map.put(apinfo.uid, app); - } else { - final String newnames[] = new String[app.names.length + 1]; - System.arraycopy(app.names, 0, newnames, 0, app.names.length); - newnames[app.names.length] = name; - app.names = newnames; - } - // check if this application is selected - if (!app.selected_wifi && Arrays.binarySearch(selected_wifi, app.uid) >= 0) { - app.selected_wifi = true; - } - if (!app.selected_3g && Arrays.binarySearch(selected_3g, app.uid) >= 0) { - app.selected_3g = true; - } - } - if (changed) { - edit.commit(); - } - /* add special applications to the list */ - final DroidApp special[] = { - new DroidApp(SPECIAL_UID_ANY,"(Any application) - Same as selecting all applications", false, false), - new DroidApp(SPECIAL_UID_KERNEL,"(Kernel) - Linux kernel", false, false), - new DroidApp(android.os.Process.getUidForName("root"), "(root) - Applications running as root", false, false), - new DroidApp(android.os.Process.getUidForName("media"), "Media server", false, false), - new DroidApp(android.os.Process.getUidForName("vpn"), "VPN networking", false, false), - new DroidApp(android.os.Process.getUidForName("shell"), "Linux shell", false, false), - }; - for (int i=0; i= 0) { - app.selected_wifi = true; - } - if (Arrays.binarySearch(selected_3g, app.uid) >= 0) { - app.selected_3g = true; - } - map.put(app.uid, app); - } - } - applications = new DroidApp[map.size()]; - int index = 0; - for (DroidApp application : map.values()) applications[index++] = application; - return applications; - } catch (Exception e) { - alert(ctx, "error: " + e); - } - return null; - } - /** - * Check if we have root access - * @param ctx mandatory context - * @param showErrors indicates if errors should be alerted - * @return boolean true if we have root - */ - public static boolean hasRootAccess(Context ctx, boolean showErrors) { - if (hasroot) return true; - final StringBuilder res = new StringBuilder(); - try { - // Run an empty script just to check root access - if (runScriptAsRoot(ctx, "exit 0", res) == 0) { - hasroot = true; - return true; - } - } catch (Exception e) { - } - if (showErrors) { - alert(ctx, "Could not acquire root access.\n" + - "You need a rooted phone to run DroidWall.\n\n" + - "If this phone is already rooted, please make sure DroidWall has enough permissions to execute the \"su\" command.\n" + - "Error message: " + res.toString()); - } - return false; - } - /** - * Runs a script, wither as root or as a regular user (multiple commands separated by "\n"). - * @param ctx mandatory context - * @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 runScript(Context ctx, String script, StringBuilder res, long timeout, boolean asroot) { - final File file = new File(ctx.getDir("bin",0), SCRIPT_FILE); - final ScriptRunner runner = new ScriptRunner(file, script, res, asroot); - runner.start(); - try { - if (timeout > 0) { - runner.join(timeout); - } else { - runner.join(); - } - if (runner.isAlive()) { - // Timed-out - runner.interrupt(); - runner.join(150); - runner.destroy(); - runner.join(50); - } - } catch (InterruptedException ex) {} - return runner.exitcode; - } - /** - * Runs a script as root (multiple commands separated by "\n"). - * @param ctx mandatory context - * @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(Context ctx, String script, StringBuilder res, long timeout) { - return runScript(ctx, script, res, timeout, true); - } - /** - * Runs a script as root (multiple commands separated by "\n") with a default timeout of 20 seconds. - * @param ctx mandatory context - * @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(Context ctx, String script, StringBuilder res) throws IOException { - return runScriptAsRoot(ctx, script, res, 40000); - } - /** - * Runs a script as a regular user (multiple commands separated by "\n") with a default timeout of 20 seconds. - * @param ctx mandatory context - * @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 runScript(Context ctx, String script, StringBuilder res) throws IOException { - return runScript(ctx, script, res, 40000, false); - } - /** - * Asserts that the binary files are installed in the cache directory. - * @param ctx context - * @param showErrors indicates if errors should be alerted - * @return false if the binary files could not be installed - */ - public static boolean assertBinaries(Context ctx, boolean showErrors) { - boolean changed = false; - try { - // Check iptables_g1 - File file = new File(ctx.getDir("bin",0), "iptables"); - - if ((!file.exists()) && isARMv6()) { - copyRawFile(ctx, R.raw.iptables_g1, file, "755"); - changed = true; - } - - // Check iptables_n1 - file = new File(ctx.getDir("bin",0), "iptables"); - if ((!file.exists()) && (!isARMv6())) { - copyRawFile(ctx, R.raw.iptables_n1, file, "755"); - changed = true; - } - - // Check busybox - /* - file = new File(ctx.getDir("bin",0), "busybox_g1"); - if (!file.exists()) { - copyRawFile(ctx, R.raw.busybox_g1, file, "755"); - changed = true; - } - */ - - if (changed) { - Toast.makeText(ctx, R.string.status_install_success, Toast.LENGTH_LONG).show(); - } - } catch (Exception e) { - if (showErrors) alert(ctx, "Error installing binary files: " + e); - return false; - } - return true; - } - /** - * Check if the firewall is enabled - * @param ctx mandatory context - * @return boolean - */ - public static boolean isEnabled(Context ctx) { - if (ctx == null) return false; - return ctx.getSharedPreferences(PREFS_NAME, 0).getBoolean(PREF_ENABLED, false); - } - - /** - * Defines if the firewall is enabled and broadcasts the new status - * @param ctx mandatory context - * @param enabled enabled flag - */ - public static void setEnabled(Context ctx, boolean enabled) { - if (ctx == null) return; - final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); - if (prefs.getBoolean(PREF_ENABLED, false) == enabled) { - return; - } - final Editor edit = prefs.edit(); - edit.putBoolean(PREF_ENABLED, enabled); - if (!edit.commit()) { - alert(ctx, "Error writing to preferences"); - return; - } - /* notify */ - final Intent message = new Intent(IptablesManager.STATUS_CHANGED_MSG); - message.putExtra(IptablesManager.STATUS_EXTRA, enabled); - ctx.sendBroadcast(message); - } - /** - * Called when an application in removed (un-installed) from the system. - * This will look for that application in the selected list and update the persisted values if necessary - * @param ctx mandatory app context - * @param uid UID of the application that has been removed - */ - public static void applicationRemoved(Context ctx, int uid) { - final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); - final Editor editor = prefs.edit(); - // allowed application names separated by pipe '|' (persisted) - final String savedUids_wifi = prefs.getString(PREF_WIFI_UIDS, ""); - final String savedUids_3g = prefs.getString(PREF_3G_UIDS, ""); - final String uid_str = uid + ""; - boolean changed = false; - // look for the removed application in the "wi-fi" list - if (savedUids_wifi.length() > 0) { - final StringBuilder newuids = new StringBuilder(); - final StringTokenizer tok = new StringTokenizer(savedUids_wifi, "|"); - while (tok.hasMoreTokens()) { - final String token = tok.nextToken(); - if (uid_str.equals(token)) { - Log.d("DroidWall", "Removing UID " + token + " from the wi-fi list (package removed)!"); - changed = true; - } else { - if (newuids.length() > 0) newuids.append('|'); - newuids.append(token); - } - } - if (changed) { - editor.putString(PREF_WIFI_UIDS, newuids.toString()); - } - } - // look for the removed application in the "3g" list - if (savedUids_3g.length() > 0) { - final StringBuilder newuids = new StringBuilder(); - final StringTokenizer tok = new StringTokenizer(savedUids_3g, "|"); - while (tok.hasMoreTokens()) { - final String token = tok.nextToken(); - if (uid_str.equals(token)) { - Log.d("DroidWall", "Removing UID " + token + " from the 3G list (package removed)!"); - changed = true; - } else { - if (newuids.length() > 0) newuids.append('|'); - newuids.append(token); - } - } - if (changed) { - editor.putString(PREF_3G_UIDS, newuids.toString()); - } - } - // if anything has changed, save the new prefs... - if (changed) { - editor.commit(); - if (isEnabled(ctx)) { - // .. and also re-apply the rules if the firewall is enabled - applySavedIptablesRules(ctx, false); - } - } - } - - /** - * Small structure to hold an application info - */ - public static final class DroidApp { - /** linux user id */ - int uid; - /** application names belonging to this user id */ - String names[]; - /** indicates if this application is selected for wifi */ - boolean selected_wifi; - /** indicates if this application is selected for 3g */ - boolean selected_3g; - /** toString cache */ - String tostr; - - public DroidApp() { - } - public DroidApp(int uid, String name, boolean selected_wifi, boolean selected_3g) { - this.uid = uid; - this.names = new String[] {name}; - this.selected_wifi = selected_wifi; - this.selected_3g = selected_3g; - } - /** - * Screen representation of this application - */ - @Override - public String toString() { - if (tostr == null) { - final StringBuilder s = new StringBuilder(); - if (uid > 0) s.append(uid + ": "); - for (int i=0; i dstBlocked; // Number of packets blocked per destination IP address - private LogInfo() { - this.dstBlocked = new HashMap(); - } - } - /** - * Internal thread used to execute scripts (as root or not). - */ - private static final class ScriptRunner extends Thread { - private final File file; - private final String script; - private final StringBuilder res; - private final boolean asroot; - public int exitcode = -1; - private Process exec; - - /** - * Creates a new script runner. - * @param file temporary script file - * @param script script to run - * @param res response output - * @param asroot if true, executes the script as root - */ - public ScriptRunner(File file, String script, StringBuilder res, boolean asroot) { - this.file = file; - this.script = script; - this.res = res; - this.asroot = asroot; - } - @Override - public void run() { - try { - file.createNewFile(); - final String abspath = file.getAbsolutePath(); - // make sure we have execution permission on the script file - Runtime.getRuntime().exec("chmod 777 "+abspath).waitFor(); - // Write the script to be executed - final OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file)); - if (new File("/system/bin/sh").exists()) { - out.write("#!/system/bin/sh\n"); - } - out.write(script); - if (!script.endsWith("\n")) out.write("\n"); - out.write("exit\n"); - out.flush(); - out.close(); - if (this.asroot) { - // Create the "su" request to run the script - exec = Runtime.getRuntime().exec("su -c "+abspath); - } else { - // Create the "sh" request to run the script - exec = Runtime.getRuntime().exec("sh "+abspath); - } - InputStreamReader r = new InputStreamReader(exec.getInputStream()); - final char buf[] = new char[1024]; - int read = 0; - // Consume the "stdout" - 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; - } - } -} diff --git a/src/org/torproject/android/service/TorBinaryInstaller.java b/src/org/torproject/android/service/TorBinaryInstaller.java index ff1e7201..07b50498 100644 --- a/src/org/torproject/android/service/TorBinaryInstaller.java +++ b/src/org/torproject/android/service/TorBinaryInstaller.java @@ -3,10 +3,12 @@ package org.torproject.android.service; +import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -22,6 +24,7 @@ import org.torproject.android.TorConstants; import android.content.Context; import android.util.Log; +import android.widget.Toast; public class TorBinaryInstaller implements TorServiceConstants { @@ -29,6 +32,8 @@ public class TorBinaryInstaller implements TorServiceConstants { File installFolder; Context context; + private static int isARMv6 = -1; + public TorBinaryInstaller (Context context, File installFolder) { this.installFolder = installFolder; @@ -151,4 +156,97 @@ public class TorBinaryInstaller implements TorServiceConstants { + /** + * Check if this is an ARMv6 device + * @return true if this is ARMv6 + */ + private static boolean isARMv6() { + if (isARMv6 == -1) { + BufferedReader r = null; + try { + isARMv6 = 0; + r = new BufferedReader(new FileReader("/proc/cpuinfo")); + for (String line = r.readLine(); line != null; line = r.readLine()) { + if (line.startsWith("Processor") && line.contains("ARMv6")) { + isARMv6 = 1; + break; + } else if (line.startsWith("CPU architecture") && (line.contains("6TE") || line.contains("5TE"))) { + isARMv6 = 1; + break; + } + } + } catch (Exception ex) { + } finally { + if (r != null) try {r.close();} catch (Exception ex) {} + } + } + return (isARMv6 == 1); + } + + /** + * Copies a raw resource file, given its ID to the given location + * @param ctx context + * @param resid resource id + * @param file destination file + * @param mode file permissions (E.g.: "755") + * @throws IOException on error + * @throws InterruptedException when interrupted + */ + private static void copyRawFile(Context ctx, int resid, File file, String mode) throws IOException, InterruptedException + { + final String abspath = file.getAbsolutePath(); + // Write the iptables binary + final FileOutputStream out = new FileOutputStream(file); + final InputStream is = ctx.getResources().openRawResource(resid); + byte buf[] = new byte[1024]; + int len; + while ((len = is.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.close(); + is.close(); + // Change the permissions + Runtime.getRuntime().exec("chmod "+mode+" "+abspath).waitFor(); + } + /** + * Asserts that the binary files are installed in the cache directory. + * @param ctx context + * @param showErrors indicates if errors should be alerted + * @return false if the binary files could not be installed + */ + public static boolean assertIpTablesBinaries(Context ctx, boolean showErrors) throws Exception { + boolean changed = false; + + // Check iptables_g1 + File file = new File(ctx.getDir("bin",0), "iptables"); + + if ((!file.exists()) && isARMv6()) { + copyRawFile(ctx, R.raw.iptables_g1, file, "755"); + changed = true; + } + + // Check iptables_n1 + file = new File(ctx.getDir("bin",0), "iptables"); + if ((!file.exists()) && (!isARMv6())) { + copyRawFile(ctx, R.raw.iptables_n1, file, "755"); + changed = true; + } + + // Check busybox + /* + file = new File(ctx.getDir("bin",0), "busybox_g1"); + if (!file.exists()) { + copyRawFile(ctx, R.raw.busybox_g1, file, "755"); + changed = true; + } + */ + + if (changed) { + Toast.makeText(ctx, R.string.status_install_success, Toast.LENGTH_LONG).show(); + } + + return true; + } + + } diff --git a/src/org/torproject/android/service/TorService.java b/src/org/torproject/android/service/TorService.java index df2990d5..2b1c91d9 100644 --- a/src/org/torproject/android/service/TorService.java +++ b/src/org/torproject/android/service/TorService.java @@ -1,11 +1,20 @@ /* Copyright (c) 2009-2011, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */ /* See LICENSE for licensing information */ +/* + * Code for iptables binary management taken from DroidWall GPLv3 + * Copyright (C) 2009-2010 Rodrigo Zechin Rosauro + */ + package org.torproject.android.service; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; import java.net.Socket; import java.util.ArrayList; import java.util.Arrays; @@ -39,6 +48,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.preference.PreferenceManager; import android.util.Log; +import android.widget.Toast; public class TorService extends Service implements TorServiceConstants, TorConstants, Runnable, EventHandler { @@ -414,8 +424,7 @@ public class TorService extends Service implements TorServiceConstants, TorConst private boolean checkTorBinaries () throws Exception { //check and install iptables - IptablesManager.assertBinaries(this, true); - + TorBinaryInstaller.assertIpTablesBinaries(this, true); appBinHome = getDir("bin",0); appDataHome = getCacheDir(); @@ -520,6 +529,8 @@ public class TorService extends Service implements TorServiceConstants, TorConst boolean hasRoot = prefs.getBoolean(PREF_HAS_ROOT,false); boolean enableTransparentProxy = prefs.getBoolean("pref_transparent", false); + TorTransProxy ttProxy = new TorTransProxy(); + if (hasRoot && enableTransparentProxy) { @@ -544,7 +555,7 @@ public class TorService extends Service implements TorServiceConstants, TorConst int status = code; while (st.hasMoreTokens()) { - status = TorTransProxy.setTransparentProxyingByPort(this, Integer.parseInt(st.nextToken())); + status = ttProxy.setTransparentProxyingByPort(this, Integer.parseInt(st.nextToken())); if(status != 0) code = status; } @@ -554,12 +565,12 @@ public class TorService extends Service implements TorServiceConstants, TorConst if(transProxyAll) { showAlert(getString(R.string.status), getString(R.string.setting_up_full_transparent_proxying_)); - code = TorTransProxy.setTransparentProxyingAll(this); + code = ttProxy.setTransparentProxyingAll(this); } else { showAlert(getString(R.string.status), getString(R.string.setting_up_app_based_transparent_proxying_)); - code = TorTransProxy.setTransparentProxyingByApp(this,AppManager.getApps(this)); + code = ttProxy.setTransparentProxyingByApp(this,AppManager.getApps(this)); } } @@ -576,7 +587,7 @@ public class TorService extends Service implements TorServiceConstants, TorConst { showAlert(getString(R.string.status), getString(R.string.transproxy_enabled_for_tethering_)); - TorTransProxy.enableTetheringRules(this); + ttProxy.enableTetheringRules(this); } } @@ -604,12 +615,13 @@ public class TorService extends Service implements TorServiceConstants, TorConst boolean hasRoot = prefs.getBoolean(PREF_HAS_ROOT,false); boolean enableTransparentProxy = prefs.getBoolean("pref_transparent", false); + if (hasRoot && enableTransparentProxy) { TorService.logMessage ("Clearing TransProxy rules"); - TorTransProxy.flushIptables(this); + new TorTransProxy().flushIptables(this); showAlert(getString(R.string.status), getString(R.string.transproxy_rules_cleared)); diff --git a/src/org/torproject/android/service/TorTransProxy.java b/src/org/torproject/android/service/TorTransProxy.java index bf5fee2a..462d2f08 100644 --- a/src/org/torproject/android/service/TorTransProxy.java +++ b/src/org/torproject/android/service/TorTransProxy.java @@ -6,17 +6,59 @@ import org.torproject.android.TorConstants; import org.torproject.android.settings.TorifiedApp; import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import android.util.Log; public class TorTransProxy implements TorServiceConstants { - private final static String TAG = TorConstants.TAG; - + private String ipTablesPath; + + public String getIpTablesPath (Context context) + { - public static int flushIptables(Context context) throws Exception { + if (ipTablesPath == null) + { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if (prefs.getBoolean(TorConstants.PREF_USE_SYSTEM_IPTABLES, false)) + { + //if the user wants us to use the built-in iptables, then we have to find it + File fileIpt = new File("/system/bin/iptables"); + + if (fileIpt.exists()) + ipTablesPath = fileIpt.getAbsolutePath(); + else + { + + fileIpt = new File("/system/xbin/iptables"); + + if (fileIpt.exists()) + return (ipTablesPath = fileIpt.getAbsolutePath()); + else + { + //use the bundled version + ipTablesPath = new File(context.getDir("bin", 0),"iptables").getAbsolutePath(); + } + } + } + else + { + //use the bundled version + + ipTablesPath = new File(context.getDir("bin", 0),"iptables").getAbsolutePath(); + } + + } + + return ipTablesPath; - String ipTablesPath = new File(context.getDir("bin", 0),"iptables").getAbsolutePath(); + } + + public int flushIptables(Context context) throws Exception { + String ipTablesPath = getIpTablesPath(context); + final StringBuilder script = new StringBuilder(); StringBuilder res = new StringBuilder(); @@ -124,16 +166,17 @@ public class TorTransProxy implements TorServiceConstants { } */ - public static int testOwnerModule(Context context) throws Exception + public int testOwnerModule(Context context) throws Exception { + TorBinaryInstaller.assertIpTablesBinaries(context, false); + boolean runRoot = true; boolean waitFor = true; - //redirectDNSResolvConf(); //not working yet int torUid = context.getApplicationInfo().uid; - String ipTablesPath = new File(context.getDir("bin", 0),"iptables").getAbsolutePath(); + String ipTablesPath = getIpTablesPath(context); StringBuilder script = new StringBuilder(); @@ -161,7 +204,7 @@ public class TorTransProxy implements TorServiceConstants { - public static int setTransparentProxyingByApp(Context context, TorifiedApp[] apps) throws Exception + public int setTransparentProxyingByApp(Context context, TorifiedApp[] apps) throws Exception { boolean runRoot = true; @@ -169,7 +212,7 @@ public class TorTransProxy implements TorServiceConstants { //redirectDNSResolvConf(); //not working yet - String ipTablesPath = new File(context.getDir("bin", 0),"iptables").getAbsolutePath(); + String ipTablesPath = getIpTablesPath(context); StringBuilder script = new StringBuilder(); @@ -261,7 +304,7 @@ public class TorTransProxy implements TorServiceConstants { return code; } - public static int setTransparentProxyingByPort(Context context, int port) throws Exception + public int setTransparentProxyingByPort(Context context, int port) throws Exception { //android.os.Debug.waitForDebugger(); @@ -269,7 +312,7 @@ public class TorTransProxy implements TorServiceConstants { //redirectDNSResolvConf(); //not working yet //String baseDir = context.getDir("bin",0).getAbsolutePath() + '/'; - String ipTablesPath = new File(context.getDir("bin", 0),"iptables").getAbsolutePath(); + String ipTablesPath = getIpTablesPath(context); StringBuilder script = new StringBuilder(); @@ -318,13 +361,13 @@ public class TorTransProxy implements TorServiceConstants { return code; } - public static int enableTetheringRules (Context context) throws Exception + public int enableTetheringRules (Context context) throws Exception { boolean runRoot = true; boolean waitFor = true; - String ipTablesPath = new File(context.getDir("bin", 0),"iptables").getAbsolutePath(); + String ipTablesPath = getIpTablesPath(context); StringBuilder script = new StringBuilder(); @@ -360,14 +403,14 @@ public class TorTransProxy implements TorServiceConstants { return code; } - public static int setTransparentProxyingAll(Context context) throws Exception + public int setTransparentProxyingAll(Context context) throws Exception { boolean runRoot = true; boolean waitFor = true; //redirectDNSResolvConf(); //not working yet - String ipTablesPath = new File(context.getDir("bin", 0),"iptables").getAbsolutePath(); + String ipTablesPath = getIpTablesPath(context); StringBuilder script = new StringBuilder(); diff --git a/src/org/torproject/android/wizard/Permissions.java b/src/org/torproject/android/wizard/Permissions.java index 151e6cb8..5e17b674 100644 --- a/src/org/torproject/android/wizard/Permissions.java +++ b/src/org/torproject/android/wizard/Permissions.java @@ -164,7 +164,7 @@ public class Permissions extends Activity implements TorConstants { if (hasRoot) { try { - int resp = TorTransProxy.testOwnerModule(context); + int resp = new TorTransProxy().testOwnerModule(context); if (resp < 0) { diff --git a/src/org/torproject/android/wizard/WizardHelper.java b/src/org/torproject/android/wizard/WizardHelper.java index 18be47f6..881d38db 100644 --- a/src/org/torproject/android/wizard/WizardHelper.java +++ b/src/org/torproject/android/wizard/WizardHelper.java @@ -93,17 +93,16 @@ public class WizardHelper implements TorConstants { @Override public void onClick(View view) { + boolean iCanHazRoot = TorServiceUtils.isRootPossible(); - boolean isRootPossible = TorServiceUtils.isRootPossible(); - - if (isRootPossible) + if (iCanHazRoot) { try { - int resp = TorTransProxy.testOwnerModule(context); + int resp = new TorTransProxy().testOwnerModule(context); if (resp < 0) { - isRootPossible = false; + iCanHazRoot = false; Toast.makeText(context, "ERROR: IPTables OWNER module not available", Toast.LENGTH_LONG).show(); Log.i(TorService.TAG,"ERROR: IPTables OWNER module not available"); @@ -111,21 +110,12 @@ public class WizardHelper implements TorConstants { } catch (Exception e) { - isRootPossible = false; + iCanHazRoot = false; Log.d(TorService.TAG,"ERROR: IPTables OWNER module not available",e); } } - /* - * we shouldn't store root here, as this step is just chekcing to see if root is possible - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - Editor pEdit = prefs.edit(); - pEdit.putBoolean("has_root",hasRoot); - pEdit.commit(); - */ - - if (isRootPossible) + if (iCanHazRoot) { currentDialog.dismiss(); showWizardStep2Root();