/** * 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 <http://www.gnu.org/licenses/>. * * @author Rodrigo Zechin Rosauro * @version 1.0 */ package com.zte.appopscontrol.firewall; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; 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 MyApi { /** application version string */ public static final String VERSION = "1.5.1"; /** 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 WIFI_PREFS_NAME = "wifi"; public static final String MOBILE_PREFS_NAME = "mobile"; 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; /** * 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(); } } /** * 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) { return "IPTABLES=iptables\n" + "GREP=grep\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<Integer> uidsWifi, List<Integer> 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 = true;//!whitelist; final StringBuilder script = new StringBuilder(); try { int code; script.append(scriptHeader(ctx)); script.append("" + "$IPTABLES --version || exit 1\n" + "# Create the ztewall chains if necessary\n" + "$IPTABLES -L ztewall >/dev/null 2>/dev/null || $IPTABLES --new ztewall || exit 2\n" + "$IPTABLES -L ztewall-mobile >/dev/null 2>/dev/null || $IPTABLES --new ztewall-mobile || exit 3\n" + "$IPTABLES -L ztewall-wifi >/dev/null 2>/dev/null || $IPTABLES --new ztewall-wifi || exit 4\n" + "$IPTABLES -L ztewall-reject >/dev/null 2>/dev/null || $IPTABLES --new ztewall-reject || exit 5\n" + "# Add ztewall chain to OUTPUT chain if necessary\n" + "$IPTABLES -L OUTPUT | $GREP -q ztewall || $IPTABLES -I OUTPUT 1 -j ztewall || exit 6\n" + "# Flush existing rules\n" + "$IPTABLES -F ztewall || exit 7\n" + "$IPTABLES -F ztewall-mobile || exit 8\n" + "$IPTABLES -F ztewall-wifi || exit 9\n" + ""); script.append("" + "# Create the reject rule (log disabled)\n" + "$IPTABLES -A ztewall-reject -j REJECT || exit 11\n" + ""); script.append("# Main rules (per interface)\n"); for (final String itf : ITFS_3G) { script.append("$IPTABLES -A ztewall -o ").append(itf).append(" -j ztewall-mobile || exit\n"); } for (final String itf : ITFS_WIFI) { script.append("$IPTABLES -A ztewall -o ").append(itf).append(" -j ztewall-wifi || exit\n"); } script.append("# Filtering rules\n"); final String targetRule = "droidwall-reject"; final boolean any_3g = uids3g.indexOf(SPECIAL_UID_ANY) >= 0; final boolean any_wifi = uidsWifi.indexOf(SPECIAL_UID_ANY) >= 0; if (any_3g) { if (blacklist) { /* block any application on this interface */ script.append("$IPTABLES -A ztewall-mobile -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 ztewall-mobile -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 ztewall-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 ztewall-wifi -m owner --uid-owner ").append(uid).append(" -j ").append(targetRule).append(" || exit\n"); } } if (uids3g.indexOf(SPECIAL_UID_KERNEL) >= 0) { script.append("# hack to BLOCK kernel packets on black-list\n"); script.append("$IPTABLES -A ztewall-mobile -m owner --uid-owner 0:999999999 -j RETURN || exit\n"); script.append("$IPTABLES -A ztewall-mobile -j ztewall-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 ztewall-wifi -m owner --uid-owner 0:999999999 -j RETURN || exit\n"); script.append("$IPTABLES -A ztewall-wifi -j ztewall-reject || exit\n"); } final StringBuilder res = new StringBuilder(); code = runScriptAsRoot(ctx, script.toString(), res); if (showErrors && code != 0) { String msg = res.toString(); Log.e("ZteWall", 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; } /** * ZTE add * it will init the Iptables with ztewall if needed * @param ctx application context (mandatory) */ public static boolean initIptables(Context ctx){ if (ctx == null) { return false; } final String ITFS_WIFI[] = {"tiwlan+", "wlan+", "eth+"}; final String ITFS_3G[] = {"rmnet+","pdp+","ppp+","uwbr+","wimax+","vsnet+"}; final StringBuilder script = new StringBuilder(); try { int code; script.append(scriptHeader(ctx)); script.append("" + "$IPTABLES --version || exit 1\n" + "# Create the ztewall chains if necessary\n" + "$IPTABLES -L ztewall >/dev/null 2>/dev/null || $IPTABLES --new ztewall || exit 2\n" + "$IPTABLES -L ztewall-mobile >/dev/null 2>/dev/null || $IPTABLES --new ztewall-mobile || exit 3\n" + "$IPTABLES -L ztewall-wifi >/dev/null 2>/dev/null || $IPTABLES --new ztewall-wifi || exit 4\n" + "$IPTABLES -L ztewall-reject >/dev/null 2>/dev/null || $IPTABLES --new ztewall-reject || exit 5\n" + "# Add ztewall chain to OUTPUT chain if necessary\n" + "$IPTABLES -L OUTPUT | $GREP -q ztewall || $IPTABLES -I OUTPUT 1 -j ztewall || exit 6\n" + "# Flush existing rules\n" + "$IPTABLES -F ztewall || exit 7\n" + "$IPTABLES -F ztewall-mobile || exit 8\n" + "$IPTABLES -F ztewall-wifi || exit 9\n" + ""); script.append("" + "# Create the reject rule (log disabled)\n" + "$IPTABLES -A ztewall-reject -j REJECT || exit 11\n" + ""); script.append("# Main rules (per interface)\n"); for (final String itf : ITFS_3G) { script.append("$IPTABLES -A ztewall -o ").append(itf).append(" -j ztewall-mobile || exit\n"); } for (final String itf : ITFS_WIFI) { script.append("$IPTABLES -A ztewall -o ").append(itf).append(" -j ztewall-wifi || exit\n"); } final StringBuilder res = new StringBuilder(); code = runScriptAsRoot(ctx, script.toString(), res); if (code != 0) { String msg = res.toString(); Log.e("ZteWall", 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) { alert(ctx, "error refreshing iptables: " + e); } return false; } /** * ZTE add * it will clean all ztewall Iptables * @param ctx application context (mandatory) */ public static boolean cleanIptables(Context ctx){ if (ctx == null) { return false; } final StringBuilder script = new StringBuilder(); try { int code; script.append(scriptHeader(ctx)); script.append("" + "$IPTABLES --version || exit 1\n" + "# Clean ztewall chains\n" + "# Flush existing rules\n" + "$IPTABLES -F ztewall || exit 7\n" + "$IPTABLES -F ztewall-mobile || exit 8\n" + "$IPTABLES -F ztewall-wifi || exit 9\n" + "$IPTABLES -F ztewall-reject || exit 7\n" + ""); final StringBuilder res = new StringBuilder(); code = runScriptAsRoot(ctx, script.toString(), res); if (code != 0) { String msg = res.toString(); Log.e("ZteWall", 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) { alert(ctx, "error refreshing iptables: " + e); } return false; } /** * ZTE add * it will init the Iptables with ztewall if needed * @param ctx application context (mandatory) */ public static boolean modifyByUID(Context ctx, int uid, boolean isWifi, boolean disable){ if (ctx == null) { return false; } final StringBuilder script = new StringBuilder(); String tmpUid; if(uid > 10000) tmpUid = "a" + (uid - 10000); else if(uid == 1000) tmpUid = "system"; else tmpUid = "" + uid; try { int code; script.append(scriptHeader(ctx)); if(isWifi){ if(disable){ script.append("" + "$IPTABLES -L ztewall-wifi | $GREP -i " + uid + " && exit 11\n" + ""); // if not exit in above step, then add new one script.append("" + "$IPTABLES -A ztewall-wifi -m owner --uid-owner " + uid + " -j ztewall-reject" + ""); } else{ // need to delete the item script.append("" + "item=`$IPTABLES -L ztewall-wifi --line-numbers | $GREP " + tmpUid + "`\n"); script.append("" + "line=${item%% *}" + "\n"); script.append("" + "$IPTABLES -D ztewall-wifi $line" + ""); } }else{ if(disable){ script.append("" + "$IPTABLES -L ztewall-mobile | $GREP -i " + uid + " && exit 11 \n" + ""); // if not exit in above step, then add new one script.append("" + "$IPTABLES -A ztewall-mobile -m owner --uid-owner " + uid + " -j ztewall-reject" + ""); } else{ // need to delete the item script.append("" + "item=`$IPTABLES -L ztewall-mobile --line-numbers | $GREP " + tmpUid + "`\n"); script.append("" + "line=${item%% *}" + "\n"); script.append("" + "$IPTABLES -D ztewall-mobile $line" + ""); } } final StringBuilder res = new StringBuilder(); code = runScriptAsRoot(ctx, script.toString(), res); if (code != 0) { String msg = res.toString(); Log.e("ZteWall", 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) { 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 wifiPrefs = ctx.getSharedPreferences(WIFI_PREFS_NAME, 0); final SharedPreferences mobilePrefs = ctx.getSharedPreferences(MOBILE_PREFS_NAME, 0); List<String> wifiUids = new ArrayList<String>(); List<String> mobileUids = new ArrayList<String>(); Collection<String> key = wifiPrefs.getAll().keySet(); for (Iterator it = key.iterator(); it.hasNext();) { String s = (String) it.next(); boolean bEnabled = wifiPrefs.getBoolean(s, true); if(bEnabled) continue; else{ wifiUids.add(s); } } key = mobilePrefs.getAll().keySet(); for (Iterator it = key.iterator(); it.hasNext();) { String s = (String) it.next(); boolean bEnabled = mobilePrefs.getBoolean(s, true); if(bEnabled) continue; else{ mobileUids.add(s); } } final StringBuilder script = new StringBuilder(); try { int code; script.append(scriptHeader(ctx)); for(String uid : wifiUids){ script.append("" + "$IPTABLES -A ztewall-wifi -m owner --uid-owner " + uid + " -j ztewall-reject" + "\n"); } for(String uid : mobileUids){ script.append("" + "$IPTABLES -A ztewall-mobile -m owner --uid-owner " + uid + " -j ztewall-reject" + "\n"); } final StringBuilder res = new StringBuilder(); code = runScriptAsRoot(ctx, script.toString(), res); if (code != 0) { String msg = res.toString(); Log.e("ZteWall", 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) { alert(ctx, "error refreshing iptables: " + e); } return false; } /** * 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<apps.length; i++) { if (apps[i].selected_wifi) { if (newuids_wifi.length() != 0) newuids_wifi.append('|'); newuids_wifi.append(apps[i].uid); } if (apps[i].selected_3g) { if (newuids_3g.length() != 0) newuids_3g.append('|'); newuids_3g.append(apps[i].uid); } } // save the new list of UIDs final Editor edit = prefs.edit(); edit.putString(PREF_WIFI_UIDS, newuids_wifi.toString()); edit.putString(PREF_3G_UIDS, newuids_3g.toString()); edit.commit(); } /** * Purge all iptables rules. * @param ctx mandatory context * @param showErrors indicates if errors should be alerted * @return true if the rules were purged */ public static boolean purgeIptables(Context ctx, boolean showErrors) { StringBuilder res = new StringBuilder(); try { assertBinaries(ctx, showErrors); int code = runScriptAsRoot(ctx, scriptHeader(ctx) + "$IPTABLES -F droidwall\n" + "$IPTABLES -F droidwall-reject\n" + "$IPTABLES -F droidwall-3g\n" + "$IPTABLES -F droidwall-wifi\n", res); if (code == -1) { if (showErrors) alert(ctx, "error purging iptables. exit code: " + code + "\n" + res); return false; } return true; } catch (Exception e) { if (showErrors) alert(ctx, "error purging iptables: " + e); return false; } } /** * Display iptables rules output * @param ctx application context */ public static void showIptablesRules(Context ctx) { try { final StringBuilder res = new StringBuilder(); runScriptAsRoot(ctx, scriptHeader(ctx) + "$ECHO $IPTABLES\n" + "$IPTABLES -L -v\n", res); alert(ctx, res); } catch (Exception e) { alert(ctx, "error: " + e); } } /** * Display logs * @param ctx application context * @return true if the clogs were cleared */ public static boolean clearLog(Context ctx) { try { final StringBuilder res = new StringBuilder(); int code = runScriptAsRoot(ctx, "dmesg -c >/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<Integer, LogInfo> map = new HashMap<Integer, LogInfo>(); 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<selected_wifi.length; i++) { final String uid = tok.nextToken(); if (!uid.equals("")) { try { selected_wifi[i] = Integer.parseInt(uid); } catch (Exception ex) { selected_wifi[i] = -1; } } } // Sort the array to allow using "Arrays.binarySearch" later Arrays.sort(selected_wifi); } if (savedUids_3g.length() > 0) { // Check which applications are allowed final StringTokenizer tok = new StringTokenizer(savedUids_3g, "|"); selected_3g = new int[tok.countTokens()]; for (int i=0; i<selected_3g.length; i++) { final String uid = tok.nextToken(); if (!uid.equals("")) { try { selected_3g[i] = Integer.parseInt(uid); } catch (Exception ex) { selected_3g[i] = -1; } } } // Sort the array to allow using "Arrays.binarySearch" later Arrays.sort(selected_3g); } try { final PackageManager pkgmanager = ctx.getPackageManager(); final List<ApplicationInfo> installed = pkgmanager.getInstalledApplications(0); final HashMap<Integer, DroidApp> map = new HashMap<Integer, DroidApp>(); 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), new DroidApp(android.os.Process.getUidForName("gps"), "GPS", false, false), }; for (int i=0; i<special.length; i++) { app = special[i]; if (app.uid != -1 && !map.containsKey(app.uid)) { // check if this application is allowed if (Arrays.binarySearch(selected_wifi, app.uid) >= 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_armv5 File file = new File(ctx.getDir("bin",0), "iptables_armv5"); if (!file.exists()) { //copyRawFile(ctx, R.raw.iptables_armv5, 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.toast_bin_installed, 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(MyApi.STATUS_CHANGED_MSG); message.putExtra(MyApi.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<names.length; i++) { if (i != 0) s.append(", "); s.append(names[i]); } s.append("\n"); tostr = s.toString(); } return tostr; } } /** * Small internal structure used to hold log information */ private static final class LogInfo { private int totalBlocked; // Total number of packets blocked private HashMap<String, Integer> dstBlocked; // Number of packets blocked per destination IP address private LogInfo() { this.dstBlocked = new HashMap<String, Integer>(); } } /** * 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; } } }