/** * 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 Api { /** 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_g1"); 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_n1"); 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(Api.STATUS_CHANGED_MSG); message.putExtra(Api.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; } } }