/** * * All iptables "communication" is handled by this class. * * Copyright (C) 2009-2011 Rodrigo Zechin Rosauro * Copyright (C) 2011-2012 Umakanthan Chandran * * 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, Umakanthan Chandran * @version 1.2 */ package dev.ukanth.ufirewall; import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.NotificationManager; import android.app.PendingIntent; 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.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.os.Looper; import android.os.UserManager; import android.provider.Settings; import android.support.v4.app.NotificationCompat; import android.util.Base64; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; import android.widget.Toast; import com.stericson.RootTools.RootTools; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; 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.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; import java.util.concurrent.ExecutionException; import java.util.concurrent.RejectedExecutionException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import dev.ukanth.ufirewall.MainActivity.GetAppList; import dev.ukanth.ufirewall.service.NflogService; import dev.ukanth.ufirewall.service.RootShell.RootCommand; import dev.ukanth.ufirewall.util.G; import dev.ukanth.ufirewall.util.JsonHelper; import eu.chainfire.libsuperuser.Shell.SU; /** * Contains shared programming interfaces. * All iptables "communication" is handled by this class. */ public final class Api { /** application logcat tag */ public static final String TAG = "AFWall"; /** 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; /** special application UID used for dnsmasq DHCP/DNS */ public static final int SPECIAL_UID_TETHER = -12; /** special application UID used for netd DNS proxy */ //public static final int SPECIAL_UID_DNSPROXY = -13; /** special application UID used for NTP */ public static final int SPECIAL_UID_NTP = -14; public static final int NOTIFICATION_ID = 24556; private static String charsetName = "UTF8"; private static String algorithm = "DES"; private static int base64Mode = Base64.DEFAULT; private static final int WIFI_EXPORT = 0; private static final int DATA_EXPORT = 1; private static final int ROAM_EXPORT = 2; private static final int VPN_EXPORT = 3; private static final int LAN_EXPORT = 4; // Preferences public static String PREFS_NAME = "AFWallPrefs"; public static final String PREF_FIREWALL_STATUS = "AFWallStaus"; public static final String DEFAULT_PREFS_NAME = "AFWallPrefs"; //for import/export rules public static final String PREF_3G_PKG = "AllowedPKG3G"; public static final String PREF_WIFI_PKG = "AllowedPKGWifi"; public static final String PREF_ROAMING_PKG = "AllowedPKGRoaming"; public static final String PREF_VPN_PKG = "AllowedPKGVPN"; public static final String PREF_LAN_PKG = "AllowedPKGLAN"; //revertback to old approach for performance public static final String PREF_3G_PKG_UIDS = "AllowedPKG3G_UIDS"; public static final String PREF_WIFI_PKG_UIDS = "AllowedPKGWifi_UIDS"; public static final String PREF_ROAMING_PKG_UIDS = "AllowedPKGRoaming_UIDS"; public static final String PREF_VPN_PKG_UIDS = "AllowedPKGVPN_UIDS"; public static final String PREF_LAN_PKG_UIDS = "AllowedPKGLAN_UIDS"; public static final String PREF_PASSWORD = "Password"; public static final String PREF_CUSTOMSCRIPT = "CustomScript"; public static final String PREF_CUSTOMSCRIPT2 = "CustomScript2"; // Executed on shutdown public static final String PREF_MODE = "BlockMode"; public static final String PREF_ENABLED = "Enabled"; // Modes public static final String MODE_WHITELIST = "whitelist"; public static final String MODE_BLACKLIST = "blacklist"; // Messages public static final String STATUS_CHANGED_MSG = "dev.ukanth.ufirewall.intent.action.STATUS_CHANGED"; public static final String TOGGLE_REQUEST_MSG = "dev.ukanth.ufirewall.intent.action.TOGGLE_REQUEST"; public static final String CUSTOM_SCRIPT_MSG = "dev.ukanth.ufirewall.intent.action.CUSTOM_SCRIPT"; // Message extras (parameters) public static final String STATUS_EXTRA = "dev.ukanth.ufirewall.intent.extra.STATUS"; public static final String SCRIPT_EXTRA = "dev.ukanth.ufirewall.intent.extra.SCRIPT"; public static final String SCRIPT2_EXTRA = "dev.ukanth.ufirewall.intent.extra.SCRIPT2"; private static final String ITFS_WIFI[] = InterfaceTracker.ITFS_WIFI; private static final String ITFS_3G[] = InterfaceTracker.ITFS_3G; private static final String ITFS_VPN[] = InterfaceTracker.ITFS_VPN; // iptables can exit with status 4 if two processes tried to update the same table private static final int IPTABLES_TRY_AGAIN = 4; private static String AFWALL_CHAIN_NAME = "afwall"; private static final String dynChains[] = { "-3g-postcustom", "-3g-fork", "-wifi-postcustom", "-wifi-fork" }; private static final String staticChains[] = { "", "-3g", "-wifi", "-reject", "-vpn", "-3g-tether", "-3g-home", "-3g-roam", "-wifi-tether", "-wifi-wan", "-wifi-lan" }; // Cached applications public static List<PackageInfoData> applications = null; //for custom scripts public static String ipPath = null; public static String bbPath = null; public static boolean setv6 = false; private static Map<String,Integer> specialApps = null; private static boolean rulesUpToDate = false; /** * @brief Special user/group IDs that aren't associated with * any particular app. * * See: * include/private/android_filesystem_config.h * in platform/system/core.git. * * The accounts listed below are the only ones from * android_filesystem_config.h that are known to be used as * the UID of a process that uses the network. The other * accounts in that .h file are either: * * used as supplemental group IDs for granting extra * privileges to apps, * * used as UIDs of processes that don't need the network, * or * * have not yet been reported by users as needing the * network. * * The list is sorted in ascending UID order. */ private static final String[] specialAndroidAccounts = { "root", "adb", "media", "vpn", "drm", "gps", "shell", }; // returns c.getString(R.string.<acct>_item) private static String getSpecialDescription(Context c, String acct) { Resources r = c.getResources(); String pkg = c.getPackageName(); int rid = r.getIdentifier(acct + "_item", "string", pkg); return c.getString(rid); } public static String getIpPath() { return ipPath; } /** * Display a simple alert box * @param ctx context * @param msgText message */ public static void toast(Context ctx, CharSequence msgText) { if (ctx != null) { Toast.makeText(ctx, msgText, Toast.LENGTH_SHORT).show(); } } /*public static void alertDialog(final Context ctx, String msgText) { if (ctx != null) { AlertDialog.Builder builder = new AlertDialog.Builder(ctx); builder.setMessage(msgText) .setCancelable(false) .setPositiveButton(ctx.getString(R.string.OK), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); AlertDialog alert = builder.create(); alert.show(); } }*/ /*static String customScriptHeader(Context ctx) { final String dir = ctx.getDir("bin",0).getAbsolutePath(); String myiptables = dir + "/iptables"; String mybusybox = dir + "/busybox"; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { myiptables = dir + "/run_pie " + myiptables; mybusybox = dir + "/run_pie " + mybusybox; } return "" + "IPTABLES="+ myiptables + "\n" + "BUSYBOX="+mybusybox+"\n" + ""; }*/ static void setIpTablePath(Context ctx,boolean setv6) { boolean builtin; String pref = G.ip_path(); if (pref.equals("system")) { builtin = false; } else if (pref.equals("builtin")) { builtin = true; } else { // auto setting: // IPv4 iptables on ICS+ devices is mostly sane, so we'll use it by default // IPv6 ip6tables can return the wrong exit status (bug #215) so default to our fixed version if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && !setv6) { builtin = false; } else { builtin = true; } } String dir = ""; if (builtin) { dir = ctx.getDir("bin", 0).getAbsolutePath() + "/"; } Api.setv6 = setv6; Api.ipPath = dir + (setv6 ? "ip6tables" : "iptables"); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { dir = ctx.getDir("bin", 0).getAbsolutePath() + "/"; Api.ipPath = dir + "run_pie " + dir + (setv6 ? "ip6tables":"iptables"); } Api.bbPath = getBusyBoxPath(ctx); } public static String getBusyBoxPath(Context ctx) { if (G.bb_path().equals("system") && RootTools.isBusyboxAvailable()) { return "busybox "; } else { String dir = ctx.getDir("bin",0).getAbsolutePath(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { return dir + "/run_pie " + dir + "/busybox "; } else { return dir + "/busybox "; } } } /** * Get KLogripper Path * @param ctx * @return */ public static String getKLogPath(Context ctx) { String dir = ctx.getDir("bin",0).getAbsolutePath(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { return dir + "/run_pie " + dir + "/klogripper "; } else { return dir + "/klogripper "; } } /** * Get NFLog Path * @param ctx * @return */ public static String getNflogPath(Context ctx) { String dir = ctx.getDir("bin",0).getAbsolutePath(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { return dir + "/run_pie " + dir + "/nflog "; } else { return dir + "/nflog "; } } /** * 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(); } public static void replaceAll(StringBuilder builder, String from, String to ) { int index = builder.indexOf(from); while (index != -1) { builder.replace(index, index + from.length(), to); index += to.length(); // Move to the end of the replacement index = builder.indexOf(from, index); } } /** * Look up uid for each user by name, and if he exists, append an iptables rule. * @param listCommands current list of iptables commands to execute * @param users list of users to whom the rule applies * @param prefix "iptables" command and the portion of the rule preceding "-m owner --uid-owner X" * @param suffix the remainder of the iptables rule, following "-m owner --uid-owner X" */ private static void addRuleForUsers(List<String> listCommands, String users[], String prefix, String suffix) { for (String user : users) { int uid = android.os.Process.getUidForName(user); if (uid != -1) listCommands.add(prefix + " -m owner --uid-owner " + uid + " " + suffix); } } private static void addRulesForUidlist(List<String> cmds, List<Integer> uids, String chain, boolean whitelist) { String action = whitelist ? " -j RETURN" : " -j " + AFWALL_CHAIN_NAME + "-reject"; if (uids.indexOf(SPECIAL_UID_ANY) >= 0) { if (!whitelist) { cmds.add("-A " + chain + action); } // FIXME: in whitelist mode this blocks everything } else { for (Integer uid : uids) { if (uid != null && uid >= 0) { cmds.add("-A " + chain + " -m owner --uid-owner " + uid + action); } } /*// netd runs as root, and on Android 4.3+ it handles all DNS queries if (uids.indexOf(SPECIAL_UID_DNSPROXY) >= 0) { addRuleForUsers(cmds, new String[]{"root"}, "-A " + chain + " -p udp --dport 53", action); }*/ String pref = G.dns_proxy(); if(whitelist) { if (pref.equals("auto")) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { addRuleForUsers(cmds, new String[]{"root"}, "-A " + chain + " -p udp --dport 53", " -j RETURN"); } else { addRuleForUsers(cmds, new String[]{"root"}, "-A " + chain + " -p udp --dport 53", " -j " + AFWALL_CHAIN_NAME + "-reject"); } } else if(pref.equals("disable")){ addRuleForUsers(cmds, new String[]{"root"}, "-A " + chain + " -p udp --dport 53", " -j " + AFWALL_CHAIN_NAME + "-reject"); } else { addRuleForUsers(cmds, new String[]{"root"}, "-A " + chain + " -p udp --dport 53", " -j RETURN"); } } else { if(pref.equals("disable")){ addRuleForUsers(cmds, new String[]{"root"}, "-A " + chain + " -p udp --dport 53", " -j " + AFWALL_CHAIN_NAME + "-reject"); } else if (pref.equals("enable")) { addRuleForUsers(cmds, new String[]{"root"}, "-A " + chain + " -p udp --dport 53", " -j RETURN"); } } // NTP service runs as "system" user if (uids.indexOf(SPECIAL_UID_NTP) >= 0) { addRuleForUsers(cmds, new String[]{"system"}, "-A " + chain + " -p udp --dport 123", action); } boolean kernel_checked = uids.indexOf(SPECIAL_UID_KERNEL) >= 0; if (whitelist) { if (kernel_checked) { // reject any other UIDs, but allow the kernel through cmds.add("-A " + chain + " -m owner --uid-owner 0:999999999 -j " + AFWALL_CHAIN_NAME + "-reject"); } else { // kernel is blocked so reject everything cmds.add("-A " + chain + " -j " + AFWALL_CHAIN_NAME + "-reject"); } } else { if (kernel_checked) { // allow any other UIDs, but block the kernel cmds.add("-A " + chain + " -m owner --uid-owner 0:999999999 -j RETURN"); cmds.add("-A " + chain + " -j " + AFWALL_CHAIN_NAME + "-reject"); } } } } private static void addRejectRules(List<String> cmds, Context ctx) { // set up reject chain to log or not log // this can be changed dynamically through the Firewall Logs activity if (G.enableLog()) { if (G.logTarget().equals("LOG")) { cmds.add("-A " + AFWALL_CHAIN_NAME + "-reject" + " -m limit --limit 1000/min -j LOG --log-prefix \"{AFL}\" --log-level 4 --log-uid"); } else if (G.logTarget().equals("NFLOG")) { cmds.add("-A " + AFWALL_CHAIN_NAME + "-reject" + " -j NFLOG --nflog-prefix \"{AFL}\" --nflog-group 40"); } } cmds.add("-A " + AFWALL_CHAIN_NAME + "-reject" + " -j REJECT"); } private static void addCustomRules(Context ctx, String prefName, List<String> cmds) { String[] customRules = G.pPrefs.getString(prefName, "").split("[\\r\\n]+"); for (String s : customRules) { if (s.matches(".*\\S.*")) { cmds.add("#LITERAL# " + s); } } } /** * Reconfigure the firewall rules based on interface changes seen at runtime: tethering * enabled/disabled, IP address changes, etc. This should only affect a small number of * rules; we want to avoid calling applyIptablesRulesImpl() too often since applying * 100+ rules is expensive. * * @param ctx application context * @param cmds command list */ private static void addInterfaceRouting(Context ctx, List<String> cmds) { final InterfaceDetails cfg = InterfaceTracker.getCurrentCfg(ctx); final boolean whitelist = G.pPrefs.getString(PREF_MODE, MODE_WHITELIST).equals(MODE_WHITELIST); for (String s : dynChains) { cmds.add("-F " + AFWALL_CHAIN_NAME + s); } if (whitelist) { // always allow the DHCP client full wifi access addRuleForUsers(cmds, new String[]{"dhcp", "wifi"}, "-A " + AFWALL_CHAIN_NAME + "-wifi-postcustom", "-j RETURN"); } if (cfg.isTethered) { cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-postcustom -j " + AFWALL_CHAIN_NAME + "-wifi-tether"); cmds.add("-A " + AFWALL_CHAIN_NAME + "-3g-postcustom -j " + AFWALL_CHAIN_NAME + "-3g-tether"); } else { cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-postcustom -j " + AFWALL_CHAIN_NAME + "-wifi-fork"); cmds.add("-A " + AFWALL_CHAIN_NAME + "-3g-postcustom -j " + AFWALL_CHAIN_NAME + "-3g-fork"); } if (G.enableLAN() && !cfg.isTethered) { if(setv6 && !cfg.lanMaskV6.equals("")) { cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-fork -d " + cfg.lanMaskV6 + " -j " + AFWALL_CHAIN_NAME + "-wifi-lan"); cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-fork '!' -d " + cfg.lanMaskV6 + " -j " + AFWALL_CHAIN_NAME + "-wifi-wan"); } else if(!setv6 && !cfg.lanMaskV4.equals("")) { cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-fork -d " + cfg.lanMaskV4 + " -j " + AFWALL_CHAIN_NAME + "-wifi-lan"); cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-fork '!' -d "+ cfg.lanMaskV4 + " -j " + AFWALL_CHAIN_NAME + "-wifi-wan"); } else { // No IP address -> no traffic. This prevents a data leak between the time // the interface gets an IP address, and the time we process the intent // (which could be 5+ seconds). This is likely to catch a little bit of // legitimate traffic from time to time, so we won't log the failures. cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-fork -m owner --uid-owner root -j RETURN"); cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-fork -m owner --uid-owner system -j RETURN"); cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-fork -j REJECT"); } } else { cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-fork -j " + AFWALL_CHAIN_NAME + "-wifi-wan"); } if (G.enableRoam() && cfg.isRoaming) { cmds.add("-A " + AFWALL_CHAIN_NAME + "-3g-fork -j " + AFWALL_CHAIN_NAME + "-3g-roam"); } else { cmds.add("-A " + AFWALL_CHAIN_NAME + "-3g-fork -j " + AFWALL_CHAIN_NAME + "-3g-home"); } } private static void applyShortRules(Context ctx, List<String> cmds) { cmds.add("-P OUTPUT DROP"); addInterfaceRouting(ctx, cmds); cmds.add("-P OUTPUT ACCEPT"); } /** * 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(final Context ctx, List<Integer> uidsWifi, List<Integer> uids3g, List<Integer> uidsRoam, List<Integer> uidsVPN, List<Integer> uidsLAN, final boolean showErrors, List<String> out) { if (ctx == null) { return false; } assertBinaries(ctx, showErrors); if(G.isMultiUser()) { //FIXME: after setting this, we need to flush the iptables ? if(G.getMultiUserId() > 0) { AFWALL_CHAIN_NAME = "afwall" + G.getMultiUserId(); } } final boolean whitelist = G.pPrefs.getString(PREF_MODE, MODE_WHITELIST).equals(MODE_WHITELIST); List<String> cmds = new ArrayList<String>(); cmds.add("-P INPUT ACCEPT"); cmds.add("-P FORWARD ACCEPT"); // prevent data leaks due to incomplete rules cmds.add("-P OUTPUT DROP"); for (String s : staticChains) { cmds.add("#NOCHK# -N " + AFWALL_CHAIN_NAME + s); cmds.add("-F " + AFWALL_CHAIN_NAME + s); } for (String s : dynChains) { cmds.add("#NOCHK# -N " + AFWALL_CHAIN_NAME + s); // addInterfaceRouting() will flush these chains, but not create them } cmds.add("#NOCHK# -D OUTPUT -j " + AFWALL_CHAIN_NAME ); cmds.add("-I OUTPUT 1 -j " + AFWALL_CHAIN_NAME ); // custom rules in afwall-{3g,wifi,reject} supersede everything else addCustomRules(ctx, Api.PREF_CUSTOMSCRIPT, cmds); cmds.add("-A " + AFWALL_CHAIN_NAME + "-3g -j " + AFWALL_CHAIN_NAME + "-3g-postcustom"); cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi -j " + AFWALL_CHAIN_NAME + "-wifi-postcustom"); addRejectRules(cmds,ctx); if (G.enableInbound()) { // we don't have any rules in the INPUT chain prohibiting inbound traffic, but // local processes can't reply to half-open connections without this rule cmds.add("-A afwall -m state --state ESTABLISHED -j RETURN"); } addInterfaceRouting(ctx, cmds); // send wifi, 3G, VPN packets to the appropriate dynamic chain based on interface if (G.enableVPN()) { // if !enableVPN then we ignore those interfaces (pass all traffic) for (final String itf : ITFS_VPN) { cmds.add("-A " + AFWALL_CHAIN_NAME + " -o " + itf + " -j " + AFWALL_CHAIN_NAME + "-vpn"); } // KitKat policy based routing - see: // http://forum.xda-developers.com/showthread.php?p=48703545 // This covers mark range 0x3c - 0x47. The official range is believed to be // 0x3c - 0x45 but this is close enough. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { cmds.add("-A " + AFWALL_CHAIN_NAME + " -m mark --mark 0x3c/0xfffc -g " + AFWALL_CHAIN_NAME + "-vpn"); cmds.add("-A " + AFWALL_CHAIN_NAME + " -m mark --mark 0x40/0xfff8 -g " + AFWALL_CHAIN_NAME + "-vpn"); } } for (final String itf : ITFS_WIFI) { cmds.add("-A " + AFWALL_CHAIN_NAME + " -o " + itf + " -j " + AFWALL_CHAIN_NAME + "-wifi"); } for (final String itf : ITFS_3G) { cmds.add("-A " + AFWALL_CHAIN_NAME + " -o " + itf + " -j " + AFWALL_CHAIN_NAME + "-3g"); } final boolean any_wifi = uidsWifi.indexOf(SPECIAL_UID_ANY) >= 0; final boolean any_3g = uids3g.indexOf(SPECIAL_UID_ANY) >= 0; // special rules to allow 3G<->wifi tethering // note that this can only blacklist DNS/DHCP services, not all tethered traffic if (((!whitelist && (any_wifi || any_3g)) || (uids3g.indexOf(SPECIAL_UID_TETHER) >= 0) || (uidsWifi.indexOf(SPECIAL_UID_TETHER) >= 0))) { String users[] = { "root", "nobody" }; String action = " -j " + (whitelist ? "RETURN" : AFWALL_CHAIN_NAME + "-reject"); // DHCP replies to client addRuleForUsers(cmds, users, "-A " + AFWALL_CHAIN_NAME + "-wifi-tether", "-p udp --sport=67 --dport=68" + action); // DNS replies to client addRuleForUsers(cmds, users, "-A " + AFWALL_CHAIN_NAME + "-wifi-tether", "-p udp --sport=53" + action); addRuleForUsers(cmds, users, "-A " + AFWALL_CHAIN_NAME + "-wifi-tether", "-p tcp --sport=53" + action); // DNS requests to upstream servers addRuleForUsers(cmds, users, "-A " + AFWALL_CHAIN_NAME + "-3g-tether", "-p udp --dport=53" + action); addRuleForUsers(cmds, users, "-A " + AFWALL_CHAIN_NAME + "-3g-tether", "-p tcp --dport=53" + action); } // if tethered, try to match the above rules (if enabled). no match -> fall through to the // normal 3G/wifi rules cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-tether -j " + AFWALL_CHAIN_NAME + "-wifi-fork"); cmds.add("-A " + AFWALL_CHAIN_NAME + "-3g-tether -j " + AFWALL_CHAIN_NAME + "-3g-fork"); // NOTE: we still need to open a hole to let WAN-only UIDs talk to a DNS server // on the LAN if (whitelist) { cmds.add("-A " + AFWALL_CHAIN_NAME + "-wifi-lan -p udp --dport 53 -j RETURN"); } // now add the per-uid rules for 3G home, 3G roam, wifi WAN, wifi LAN, VPN // in whitelist mode the last rule in the list routes everything else to afwall-reject addRulesForUidlist(cmds, uids3g, AFWALL_CHAIN_NAME + "-3g-home", whitelist); addRulesForUidlist(cmds, uidsRoam, AFWALL_CHAIN_NAME + "-3g-roam", whitelist); addRulesForUidlist(cmds, uidsWifi, AFWALL_CHAIN_NAME + "-wifi-wan", whitelist); addRulesForUidlist(cmds, uidsLAN, AFWALL_CHAIN_NAME + "-wifi-lan", whitelist); addRulesForUidlist(cmds, uidsVPN, AFWALL_CHAIN_NAME + "-vpn", whitelist); cmds.add("-P OUTPUT ACCEPT"); iptablesCommands(cmds, out); return true; } /** * Add the repetitive parts (ipPath and such) to an iptables command list * * @param in Commands in the format: "-A foo ...", "#NOCHK# -A foo ...", or "#LITERAL# <UNIX command>" * @param out A list of UNIX commands to execute */ private static void iptablesCommands(List<String> in, List<String> out) { boolean firstLit = true; for (String s : in) { if (s.matches("#LITERAL# .*")) { if (firstLit) { // export vars for the benefit of custom scripts // "true" is a dummy command which needs to return success firstLit = false; out.add("export IPTABLES=\"" + ipPath + "\"; " + "export BUSYBOX=\"" + bbPath + "\"; " + "export IPV6=" + (setv6 ? "1" : "0") + "; " + "true"); } out.add(s.replaceFirst("^#LITERAL# ", "")); } else if (s.matches("#NOCHK# .*")) { out.add(s.replaceFirst("^#NOCHK# ", "#NOCHK# " + ipPath + " ")); } else { out.add(ipPath + " " + s); } } } private static void fixupLegacyCmds(List<String> cmds) { for (int i = 0; i < cmds.size(); i++) { String s = cmds.get(i); if (s.matches("#NOCHK# .*")) { s = s.replaceFirst("^#NOCHK# ", ""); } else { s += " || exit"; } cmds.set(i, s); } } /** * 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 * @param callback If non-null, use a callback instead of blocking the current thread */ public static boolean applySavedIptablesRules(Context ctx, boolean showErrors, RootCommand callback) { if (ctx == null) { return false; } initSpecial(); final String savedPkg_wifi_uid = G.pPrefs.getString(PREF_WIFI_PKG_UIDS, ""); final String savedPkg_3g_uid = G.pPrefs.getString(PREF_3G_PKG_UIDS, ""); final String savedPkg_roam_uid = G.pPrefs.getString(PREF_ROAMING_PKG_UIDS, ""); final String savedPkg_vpn_uid = G.pPrefs.getString(PREF_VPN_PKG_UIDS, ""); final String savedPkg_lan_uid = G.pPrefs.getString(PREF_LAN_PKG_UIDS, ""); boolean returnValue = false; List<String> cmds = new ArrayList<String>(); setIpTablePath(ctx,false); returnValue = applyIptablesRulesImpl(ctx, getListFromPref(savedPkg_wifi_uid), getListFromPref(savedPkg_3g_uid), getListFromPref(savedPkg_roam_uid), getListFromPref(savedPkg_vpn_uid), getListFromPref(savedPkg_lan_uid), showErrors, cmds); if (returnValue == false) { return false; } if (G.enableIPv6()) { setIpTablePath(ctx, true); returnValue = applyIptablesRulesImpl(ctx, getListFromPref(savedPkg_wifi_uid), getListFromPref(savedPkg_3g_uid), getListFromPref(savedPkg_roam_uid), getListFromPref(savedPkg_vpn_uid), getListFromPref(savedPkg_lan_uid), showErrors, cmds); if (returnValue == false) { return false; } } rulesUpToDate = true; if (G.logTarget().equals("NFLOG")) { Intent intent = new Intent(ctx.getApplicationContext(), NflogService.class); ctx.startService(intent); } if (callback != null) { callback.setRetryExitCode(IPTABLES_TRY_AGAIN).run(ctx, cmds); return true; } else { fixupLegacyCmds(cmds); try { final StringBuilder res = new StringBuilder(); int code = runScriptAsRoot(ctx, cmds, res); if (showErrors && code != 0) { String msg = res.toString(); // 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.", ""); } toast(ctx, ctx.getString(R.string.error_apply) + code + "\n\n" + msg.trim() ); } else { return true; } } catch (Exception e) { Log.e(TAG, "Exception while applying rules: " + e.getMessage()); if (showErrors) toast(ctx, ctx.getString(R.string.error_refresh) + e); } return false; } } @Deprecated public static boolean applySavedIptablesRules(Context ctx, boolean showErrors) { return applySavedIptablesRules(ctx, showErrors, null); } public static boolean fastApply(Context ctx, RootCommand callback) { if (!rulesUpToDate) { return applySavedIptablesRules(ctx, true, callback); } List<String> out = new ArrayList<String>(); List<String> cmds; cmds = new ArrayList<String>(); setIpTablePath(ctx, false); applyShortRules(ctx, cmds); iptablesCommands(cmds, out); if (G.enableIPv6()) { setIpTablePath(ctx, true); cmds = new ArrayList<String>(); applyShortRules(ctx, cmds); iptablesCommands(cmds, out); } callback.setRetryExitCode(IPTABLES_TRY_AGAIN).run(ctx, out); return true; } /** * Save current rules using the preferences storage. * @param ctx application context (mandatory) */ public static void saveRules(Context ctx) { rulesUpToDate = false; SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); List<PackageInfoData> apps = getApps(ctx,null); if(apps != null) { // Builds a pipe-separated list of names StringBuilder newpkg_wifi = new StringBuilder(); StringBuilder newpkg_3g = new StringBuilder(); StringBuilder newpkg_roam = new StringBuilder(); StringBuilder newpkg_vpn = new StringBuilder(); StringBuilder newpkg_lan = new StringBuilder(); for (int i=0; i<apps.size(); i++) { if (apps.get(i).selected_wifi) { if (newpkg_wifi.length() != 0) newpkg_wifi.append('|'); newpkg_wifi.append(apps.get(i).uid); } if (apps.get(i).selected_3g) { if (newpkg_3g.length() != 0) newpkg_3g.append('|'); newpkg_3g.append(apps.get(i).uid); } if (G.enableRoam() && apps.get(i).selected_roam) { if (newpkg_roam.length() != 0) newpkg_roam.append('|'); newpkg_roam.append(apps.get(i).uid); } if (G.enableVPN() && apps.get(i).selected_vpn) { if (newpkg_vpn.length() != 0) newpkg_vpn.append('|'); newpkg_vpn.append(apps.get(i).uid); } if (G.enableLAN() && apps.get(i).selected_lan) { if (newpkg_lan.length() != 0) newpkg_lan.append('|'); newpkg_lan.append(apps.get(i).uid); } } // save the new list of UIDs Editor edit = prefs.edit(); edit.putString(PREF_WIFI_PKG_UIDS, newpkg_wifi.toString()); edit.putString(PREF_3G_PKG_UIDS, newpkg_3g.toString()); edit.putString(PREF_ROAMING_PKG_UIDS, newpkg_roam.toString()); edit.putString(PREF_VPN_PKG_UIDS, newpkg_vpn.toString()); edit.putString(PREF_LAN_PKG_UIDS, newpkg_lan.toString()); edit.commit(); } } /** * Purge all iptables rules. * @param ctx mandatory context * @param showErrors indicates if errors should be alerted * @param callback If non-null, use a callback instead of blocking the current thread * @return true if the rules were purged */ public static boolean purgeIptables(Context ctx, boolean showErrors, RootCommand callback) { List<String> cmds = new ArrayList<String>(); List<String> out = new ArrayList<String>(); for (String s : staticChains) { cmds.add("-F " + AFWALL_CHAIN_NAME + s); } for (String s : dynChains) { cmds.add("-F " + AFWALL_CHAIN_NAME + s); } //make sure reset the OUTPUT chain to accept state. cmds.add("-P OUTPUT ACCEPT"); //Delete only when the afwall chain exist ! cmds.add("-D OUTPUT -j " + AFWALL_CHAIN_NAME); addCustomRules(ctx, Api.PREF_CUSTOMSCRIPT2, cmds); try { assertBinaries(ctx, showErrors); // IPv4 setIpTablePath(ctx, false); iptablesCommands(cmds, out); // IPv6 if(G.enableIPv6()) { setIpTablePath(ctx,true); iptablesCommands(cmds, out); } if (callback != null) { callback.setRetryExitCode(IPTABLES_TRY_AGAIN).run(ctx, out); } else { fixupLegacyCmds(out); if (runScriptAsRoot(ctx, out, new StringBuilder()) == -1) { if(showErrors) toast(ctx, ctx.getString(R.string.error_purge)); return false; } } return true; } catch (Exception e) { return false; } } @Deprecated public static boolean purgeIptables(Context ctx, boolean showErrors) { // warning: this is a blocking call return purgeIptables(ctx, showErrors, null); } /** * Retrieve the current set of IPv4 or IPv6 rules and pass it to a callback * * @param ctx application context * @param callback callback to receive rule list * @param useIPV6 true to list IPv6 rules, false to list IPv4 rules */ public static void fetchIptablesRules(Context ctx, boolean useIPV6, RootCommand callback) { List<String> cmds = new ArrayList<String>(); List<String> out = new ArrayList<String>(); cmds.add("-n -v -L"); if (useIPV6) { setIpTablePath(ctx, true); } else { setIpTablePath(ctx, false); } iptablesCommands(cmds, out); callback.run(ctx, out); } /** * Run a list of commands with both iptables and ip6tables * * @param ctx application context * @param cmds list of commands to run * @param callback callback for completion */ public static void apply46(Context ctx, List<String> cmds, RootCommand callback) { List<String> out = new ArrayList<String>(); setIpTablePath(ctx, false); iptablesCommands(cmds, out); if (G.enableIPv6()) { setIpTablePath(ctx, true); iptablesCommands(cmds, out); } callback.setRetryExitCode(IPTABLES_TRY_AGAIN).run(ctx, out); } /** * Delete all firewall rules. For diagnostic purposes only. * * @param ctx application context * @param callback callback for completion */ public static void flushAllRules(Context ctx, RootCommand callback) { List<String> cmds = new ArrayList<String>(); cmds.add("-F"); cmds.add("-X"); apply46(ctx, cmds, callback); } /** * Enable or disable logging by rewriting the afwall-reject chain. Logging * will be enabled or disabled based on the preference setting. * * @param ctx application context * @param callback callback for completion */ public static void updateLogRules(Context ctx, RootCommand callback) { if (!isEnabled(ctx)) { return; } List<String> cmds = new ArrayList<String>(); cmds.add("#NOCHK# -N " + AFWALL_CHAIN_NAME + "-reject"); cmds.add("-F " + AFWALL_CHAIN_NAME + "-reject"); addRejectRules(cmds, ctx); apply46(ctx, cmds, callback); } /** * Clear firewall logs by purging dmesg * * @param ctx application context * @param callback Callback for completion status */ public static void clearLog(Context ctx, RootCommand callback) { callback.run(ctx, getBusyBoxPath(ctx) + " dmesg -c"); } /** * Fetch kernel logs via busybox dmesg. This will include {AFL} lines from * logging rejected packets. * * @param ctx application context * @param callback Callback for completion status * @return true if logging is enabled, false otherwise */ public static boolean fetchLogs(Context ctx, RootCommand callback) { if(G.logTarget().equals("LOG")) { callback.run(ctx, getBusyBoxPath(ctx) + " dmesg"); return true; } else { return false; } } /** * List all interfaces via "ifconfig -a" * * @param ctx application context * @param callback Callback for completion status */ public static void runIfconfig(Context ctx, RootCommand callback) { callback.run(ctx, getBusyBoxPath(ctx) + " ifconfig -a"); } public boolean isSuPackage(PackageManager pm, String suPackage) { boolean found = false; try { PackageInfo info = pm.getPackageInfo(suPackage, 0); if(info.applicationInfo != null) { found = true; } //found = s + " v" + info.versionName; } catch (NameNotFoundException e) { } return found; } /** * @param ctx application context (mandatory) * @return a list of applications */ public static List<PackageInfoData> getApps(Context ctx, GetAppList appList) { initSpecial(); if (applications != null && applications.size() > 0) { // return cached instance return applications; } SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); String savedPkg_wifi_uid = prefs.getString(PREF_WIFI_PKG_UIDS, ""); String savedPkg_3g_uid = prefs.getString(PREF_3G_PKG_UIDS, ""); String savedPkg_roam_uid = prefs.getString(PREF_ROAMING_PKG_UIDS, ""); String savedPkg_vpn_uid = prefs.getString(PREF_VPN_PKG_UIDS, ""); String savedPkg_lan_uid = prefs.getString(PREF_LAN_PKG_UIDS, ""); List<Integer> selected_wifi = new ArrayList<Integer>(); List<Integer> selected_3g = new ArrayList<Integer>(); List<Integer> selected_roam = new ArrayList<Integer>(); List<Integer> selected_vpn = new ArrayList<Integer>(); List<Integer> selected_lan = new ArrayList<Integer>(); selected_wifi = getListFromPref(savedPkg_wifi_uid); selected_3g = getListFromPref(savedPkg_3g_uid); if (G.enableRoam()) { selected_roam = getListFromPref(savedPkg_roam_uid); } if (G.enableVPN()) { selected_vpn = getListFromPref(savedPkg_vpn_uid); } if (G.enableLAN()) { selected_lan = getListFromPref(savedPkg_lan_uid); } //revert back to old approach //always use the defaul preferences to store cache value - reduces the application usage size SharedPreferences cachePrefs = ctx.getSharedPreferences(DEFAULT_PREFS_NAME, Context.MODE_PRIVATE); int count = 0; try { PackageManager pkgmanager = ctx.getPackageManager(); List<ApplicationInfo> installed = pkgmanager.getInstalledApplications(PackageManager.GET_META_DATA); SparseArray<PackageInfoData> syncMap = new SparseArray<PackageInfoData>(); Editor edit = cachePrefs.edit(); boolean changed = false; String name = null; String cachekey = null; String cacheLabel = "cache.label."; PackageInfoData app = null; ApplicationInfo apinfo = null; for(int i = 0 ; i < installed.size(); i++) { //for (ApplicationInfo apinfo : installed) { count = count+1; apinfo = installed.get(i); if(appList != null ){ appList.doProgress(count); } boolean firstseen = false; app = syncMap.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 = cacheLabel + 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; firstseen = true; } if (app == null) { app = new PackageInfoData(); app.uid = apinfo.uid; app.installTime = new File(apinfo.sourceDir).lastModified(); app.names = new ArrayList<String>(); app.names.add(name); app.appinfo = apinfo; app.pkgName = apinfo.packageName; syncMap.put(apinfo.uid, app); } else { app.names.add(name); } app.firstseen = firstseen; // check if this application is selected if (!app.selected_wifi && Collections.binarySearch(selected_wifi, app.uid) >= 0) { app.selected_wifi = true; } if (!app.selected_3g && Collections.binarySearch(selected_3g, app.uid) >= 0) { app.selected_3g = true; } if (G.enableRoam() && !app.selected_roam && Collections.binarySearch(selected_roam, app.uid) >= 0) { app.selected_roam = true; } if (G.enableVPN() && !app.selected_vpn && Collections.binarySearch(selected_vpn, app.uid) >= 0) { app.selected_vpn = true; } if (G.enableLAN() && !app.selected_lan && Collections.binarySearch(selected_lan, app.uid) >= 0) { app.selected_lan = true; } } List<PackageInfoData> specialData = new ArrayList<>(); specialData.add(new PackageInfoData(SPECIAL_UID_ANY, ctx.getString(R.string.all_item), "dev.afwall.special.any")); specialData.add(new PackageInfoData(SPECIAL_UID_KERNEL, ctx.getString(R.string.kernel_item), "dev.afwall.special.kernel")); specialData.add(new PackageInfoData(SPECIAL_UID_TETHER, ctx.getString(R.string.tethering_item), "dev.afwall.special.tether")); specialData.add(new PackageInfoData(SPECIAL_UID_NTP, ctx.getString(R.string.ntp_item), "dev.afwall.special.ntp")); for (String acct : specialAndroidAccounts) { String dsc = getSpecialDescription(ctx, acct); String pkg = "dev.afwall.special." + acct; specialData.add(new PackageInfoData(acct, dsc, pkg)); } if(specialApps == null) { specialApps = new HashMap<String, Integer>(); } for (int i=0; i<specialData.size(); i++) { app = specialData.get(i); specialApps.put(app.pkgName, app.uid); //default DNS/NTP if (app.uid != -1 && syncMap.get(app.uid) == null) { // check if this application is allowed if (!app.selected_wifi && Collections.binarySearch(selected_wifi, app.uid) >= 0) { app.selected_wifi = true; } if (!app.selected_3g && Collections.binarySearch(selected_3g, app.uid) >= 0) { app.selected_3g = true; } if (G.enableRoam() && !app.selected_roam && Collections.binarySearch(selected_roam, app.uid) >= 0) { app.selected_roam = true; } if (G.enableVPN() && !app.selected_vpn && Collections.binarySearch(selected_vpn, app.uid) >= 0) { app.selected_vpn = true; } if (G.enableLAN() && !app.selected_lan && Collections.binarySearch(selected_lan, app.uid) >= 0) { app.selected_lan = true; } syncMap.put(app.uid, app); } } if (changed) { edit.commit(); } /* convert the map into an array */ applications = new ArrayList<PackageInfoData>(); for (int i = 0; i < syncMap.size(); i++) { applications.add(syncMap.valueAt(i)); } return applications; } catch (Exception e) { //toast(ctx, ctx.getString(R.string.error_common) + e); } return null; } private static List<Integer> getListFromPref(String savedPkg_uid) { StringTokenizer tok = new StringTokenizer(savedPkg_uid, "|"); List<Integer> listUids = new ArrayList<Integer>(); while(tok.hasMoreTokens()){ String uid = tok.nextToken(); if (!uid.equals("")) { try { listUids.add(Integer.parseInt(uid)); } catch (Exception ex) { } } } // Sort the array to allow using "Arrays.binarySearch" later Collections.sort(listUids); return listUids; } private static class RunCommand extends AsyncTask<Object, List<String>, Integer> { private int exitCode = -1; @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Integer doInBackground(Object... params) { @SuppressWarnings("unchecked") List<String> commands = (List<String>) params[0]; StringBuilder res = (StringBuilder) params[1]; try { if (!SU.available()) return exitCode; if (commands != null && commands.size() > 0) { List<String> output = SU.run(commands); if (output != null) { exitCode = 0; if (output.size() > 0) { for (String str : output) { res.append(str); res.append("\n"); } } } else { exitCode = 1; } } } catch (Exception ex) { if (res != null) res.append("\n" + ex); } return 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) * @return the script exit code * @throws IOException on any error executing the script, or writing it to disk */ public static int runScriptAsRoot(Context ctx, List<String> script, StringBuilder res) throws IOException { int returnCode = -1; if ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper())) { Log.e(TAG, "runScriptAsRoot should not be called from the main thread\nCall Trace:\n"); for (StackTraceElement e : new Throwable().getStackTrace()) { Log.e(TAG, e.toString()); } } try { returnCode = new RunCommand().execute(script, res, ctx).get(); } catch (RejectedExecutionException r) { Log.e(TAG, "runScript failed: " + r.getLocalizedMessage()); } catch (InterruptedException e) { Log.e(TAG, "Caught InterruptedException"); } catch (ExecutionException e) { Log.e(TAG, "runScript failed: " + e.getLocalizedMessage()); } catch (Exception e) { Log.e(TAG, "runScript failed: " + e.getLocalizedMessage()); } return returnCode; } private static boolean installBinary(Context ctx, int resId, String filename) { try { File f = new File(ctx.getDir("bin", 0), filename); if (f.exists()) { f.delete(); } copyRawFile(ctx, resId, f, "0755"); return true; } catch (Exception e) { Log.e(TAG, "installBinary failed: " + e.getLocalizedMessage()); return false; } } private static boolean migrateSettings(Context ctx, int lastVer, int currentVer) { if (lastVer <= 138) { // migrate busybox/iptables path settings from <= 1.2.7-BETA if (G.bb_path().equals("1")) { G.bb_path("system"); } else if (G.bb_path().equals("2")) { G.bb_path("builtin"); } if (G.ip_path().equals("1")) { G.ip_path("system"); } else if (G.ip_path().equals("2")) { G.ip_path("auto"); } } return true; } /** * 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) { int currentVer = -1, lastVer = -1; try { currentVer = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionCode; lastVer = G.appVersion(); if (lastVer == currentVer) { return true; } } catch (NameNotFoundException e) { Log.e(TAG, "packageManager can't look up versionCode"); } String abi = Build.CPU_ABI; boolean ret; if (abi.startsWith("x86")) { ret = installBinary(ctx, R.raw.busybox_x86, "busybox") && installBinary(ctx, R.raw.iptables_x86, "iptables") && installBinary(ctx, R.raw.ip6tables_x86, "ip6tables") && installBinary(ctx, R.raw.nflog_x86, "nflog") && installBinary(ctx, R.raw.klogripper_x86,"klogripper") && installBinary(ctx, R.raw.run_pie_x86,"run_pie"); } else if (abi.startsWith("mips")) { ret = installBinary(ctx, R.raw.busybox_mips, "busybox") && installBinary(ctx, R.raw.iptables_mips, "iptables") && installBinary(ctx, R.raw.ip6tables_mips, "ip6tables") && installBinary(ctx, R.raw.nflog_mips, "nflog") && installBinary(ctx, R.raw.klogripper_mips,"klogripper") && installBinary(ctx, R.raw.run_pie_mips,"run_pie"); } else { // default to ARM ret = installBinary(ctx, R.raw.busybox_arm, "busybox") && installBinary(ctx, R.raw.iptables_arm, "iptables") && installBinary(ctx, R.raw.ip6tables_arm, "ip6tables") && installBinary(ctx, R.raw.nflog_arm, "nflog") && installBinary(ctx, R.raw.klogripper_arm,"klogripper") && installBinary(ctx, R.raw.run_pie_arm,"run_pie"); } // arch-independent scripts ret &= installBinary(ctx, R.raw.afwallstart, "afwallstart"); Log.d(TAG, "binary installation for " + abi + (ret ? " succeeded" : " failed")); if (showErrors) { if (ret) { displayToasts(ctx, R.string.toast_bin_installed, Toast.LENGTH_LONG); } else { toast(ctx, ctx.getString(R.string.error_binary)); } } if (currentVer > 0) { if (migrateSettings(ctx, lastVer, currentVer) == false && showErrors) { toast(ctx, ctx.getString(R.string.error_migration)); } } if (ret == true && currentVer > 0) { // this indicates that migration from the old version was successful. G.appVersion(currentVer); } return ret; } public static void displayToasts(Context context, int id, int length) { Toast.makeText(context, context.getString(id), length).show(); } public static void displayToasts(Context context, String text, int length) { Toast.makeText(context, text, length).show(); } /** * Check if the firewall is enabled * @param ctx mandatory context * @return boolean */ public static boolean isEnabled(Context ctx) { if (ctx == null) return false; boolean flag = ctx.getSharedPreferences(PREF_FIREWALL_STATUS, Context.MODE_PRIVATE).getBoolean(PREF_ENABLED, false); //Log.d(TAG, "Checking for IsEnabled, Flag:" + flag); return flag; } /** * 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, boolean showErrors) { if (ctx == null) return; SharedPreferences prefs = ctx.getSharedPreferences(PREF_FIREWALL_STATUS,Context.MODE_PRIVATE); if (prefs.getBoolean(PREF_ENABLED, false) == enabled) { return; } rulesUpToDate = false; Editor edit = prefs.edit(); edit.putBoolean(PREF_ENABLED, enabled); if (!edit.commit()) { if(showErrors)toast(ctx, ctx.getString(R.string.error_write_pref)); return; } if(G.activeNotification()) { Api.showNotification(Api.isEnabled(ctx),ctx); } /* notify */ Intent message = new Intent(Api.STATUS_CHANGED_MSG); message.putExtra(Api.STATUS_EXTRA, enabled); ctx.sendBroadcast(message); } private static boolean removePackageRef(Context ctx, String pkg, int pkgRemoved,Editor editor, String store){ StringBuilder newuids = new StringBuilder(); StringTokenizer tok = new StringTokenizer(pkg, "|"); boolean changed = false; String uid_str = pkgRemoved + ""; while (tok.hasMoreTokens()) { String token = tok.nextToken(); if (uid_str.equals(token)) { changed = true; } else { if (newuids.length() > 0) newuids.append('|'); newuids.append(token); } } if (changed) { editor.putString(store, newuids.toString()); } return changed; } /** * Remove the cache.label key from preferences, so that next time the app appears on the top * @param pkgName * @param ctx */ public static void removeCacheLabel(String pkgName,Context ctx) { SharedPreferences prefs = ctx.getSharedPreferences("AFWallPrefs", Context.MODE_PRIVATE); try { prefs.edit().remove("cache.label." + pkgName).commit(); } catch(Exception e) { Log.e(TAG, e.getLocalizedMessage()); } } /** * Cleansup the uninstalled packages from the cache - will have slight performance * @param ctx */ /*@Deprecated public static void removeAllUnusedCacheLabel(Context ctx){ SharedPreferences prefs = ctx.getSharedPreferences("AFWallPrefs", Context.MODE_PRIVATE); final String cacheLabel = "cache.label."; String pkgName; String cacheKey; PackageManager pm = ctx.getPackageManager(); Map<String,?> keys = prefs.getAll(); for(Map.Entry<String,?> entry : keys.entrySet()){ if(entry.getKey().startsWith(cacheLabel)){ cacheKey = entry.getKey(); pkgName = entry.getKey().replace(cacheLabel, ""); if ( prefs.getString(cacheKey, "").length() > 0 && !isPackageExists(pm, pkgName)) { prefs.edit().remove(cacheKey).commit(); } } } }*/ /** * Cleanup the cache from profiles - Improve performance. * @param pm * @param targetPackage */ public static boolean isPackageExists(PackageManager pm, String targetPackage) { try { pm.getPackageInfo(targetPackage,PackageManager.GET_META_DATA); } catch (NameNotFoundException e) { return false; } return true; } /** * 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 */ public static void applicationRemoved(Context ctx, int pkgRemoved) { SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); Editor editor = prefs.edit(); // allowed application names separated by pipe '|' (persisted) String savedPks_wifi = prefs.getString(PREF_WIFI_PKG_UIDS, ""); String savedPks_3g = prefs.getString(PREF_3G_PKG_UIDS, ""); String savedPks_roam = prefs.getString(PREF_ROAMING_PKG_UIDS, ""); String savedPks_vpn = prefs.getString(PREF_VPN_PKG_UIDS, ""); String savedPks_lan = prefs.getString(PREF_LAN_PKG_UIDS, ""); boolean wChanged,rChanged,gChanged,vChanged = false; // look for the removed application in the "wi-fi" list wChanged = removePackageRef(ctx,savedPks_wifi,pkgRemoved, editor,PREF_WIFI_PKG_UIDS); // look for the removed application in the "3g" list gChanged = removePackageRef(ctx,savedPks_3g,pkgRemoved, editor,PREF_3G_PKG_UIDS); // look for the removed application in roaming list rChanged = removePackageRef(ctx,savedPks_roam,pkgRemoved, editor,PREF_ROAMING_PKG_UIDS); // look for the removed application in vpn list vChanged = removePackageRef(ctx,savedPks_vpn,pkgRemoved, editor,PREF_VPN_PKG_UIDS); // look for the removed application in lan list vChanged = removePackageRef(ctx,savedPks_lan,pkgRemoved, editor,PREF_LAN_PKG_UIDS); if(wChanged || gChanged || rChanged || vChanged) { 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 PackageInfoData { /** linux user id */ public int uid; /** application names belonging to this user id */ public List<String> names; /** rules saving & load **/ public String pkgName; /** indicates if this application is selected for wifi */ public boolean selected_wifi; /** indicates if this application is selected for 3g */ public boolean selected_3g; /** indicates if this application is selected for roam */ public boolean selected_roam; /** indicates if this application is selected for vpn */ public boolean selected_vpn; /** indicates if this application is selected for lan */ public boolean selected_lan; /** toString cache */ public String tostr; /** application info */ public ApplicationInfo appinfo; /** cached application icon */ public Drawable cached_icon; /** indicates if the icon has been loaded already */ public boolean icon_loaded; /* install time */ public long installTime; /** first time seen? */ public boolean firstseen; public PackageInfoData() { } public PackageInfoData(int uid, String name, String pkgNameStr) { this.uid = uid; this.names = new ArrayList<String>(); this.names.add(name); this.pkgName = pkgNameStr; } public PackageInfoData(String user, String name, String pkgNameStr) { this(android.os.Process.getUidForName(user), name, pkgNameStr); } /** * Screen representation of this application */ @Override public String toString() { if (tostr == null) { StringBuilder s = new StringBuilder(); //if (uid > 0) s.append(uid + ": "); for (int i=0; i<names.size(); i++) { if (i != 0) s.append(", "); s.append(names.get(i)); } s.append("\n"); tostr = s.toString(); } return tostr; } public String toStringWithUID() { if (tostr == null) { StringBuilder s = new StringBuilder(); s.append(uid + ": "); for (int i=0; i<names.size(); i++) { if (i != 0) s.append(", "); s.append(names.get(i)); } s.append("\n"); tostr = s.toString(); } return tostr; } } public static void saveSharedPreferencesToFileConfirm(final Context ctx) { String fileName = "afwall-backup-" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".json"; if(saveSharedPreferencesToFile(ctx, fileName)){ Api.toast(ctx, ctx.getString(R.string.export_rules_success) + " " + Environment.getExternalStorageDirectory().getPath() + "/afwall/" + fileName); } else { Api.toast(ctx, ctx.getString(R.string.export_rules_fail)); } } public static void saveAllPreferencesToFileConfirm(final Context ctx) { String fileName = "afwall-backup-all-" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".json"; if(saveAllPreferencesToFile(ctx,fileName)){ Api.toast(ctx, ctx.getString(R.string.export_rules_success) + " " + Environment.getExternalStorageDirectory().getPath() + "/afwall/"); } else { Api.toast(ctx, ctx.getString(R.string.export_rules_fail) ); } } private static void updateExportPackage(Map<String,JSONObject> exportMap, String packageName, int identifier) throws JSONException{ JSONObject obj; if (packageName != null) { if (exportMap.containsKey(packageName)) { obj = exportMap.get(packageName); obj.put(identifier +"", true); } else { obj = new JSONObject(); obj.put(identifier +"", true); exportMap.put(packageName, obj); } } } private static void updatePackage(Context ctx,String savedPkg_uid,Map<String,JSONObject> exportMap,int identifier) throws JSONException { StringTokenizer tok = new StringTokenizer(savedPkg_uid, "|"); while(tok.hasMoreTokens()){ String uid = tok.nextToken(); if (!uid.equals("")) { String packageName = ctx.getPackageManager().getNameForUid(Integer.parseInt(uid)); updateExportPackage(exportMap,packageName,identifier); } } } private static Map<String, JSONObject> getCurrentRulesAsMap(Context ctx) { List<PackageInfoData> apps = getApps(ctx,null); // Builds a pipe-separated list of names Map<String, JSONObject> exportMap = new HashMap<>(); try { for (int i=0; i<apps.size(); i++) { if (apps.get(i).selected_wifi) { updateExportPackage(exportMap,apps.get(i).pkgName,WIFI_EXPORT); } if (apps.get(i).selected_3g) { updateExportPackage(exportMap,apps.get(i).pkgName,DATA_EXPORT); } if (apps.get(i).selected_roam) { updateExportPackage(exportMap,apps.get(i).pkgName,ROAM_EXPORT); } if (apps.get(i).selected_vpn) { updateExportPackage(exportMap,apps.get(i).pkgName,VPN_EXPORT); } if (apps.get(i).selected_lan) { updateExportPackage(exportMap,apps.get(i).pkgName,LAN_EXPORT); } } }catch(JSONException e) { Log.e(TAG, e.getLocalizedMessage()); } return exportMap; } public static boolean saveAllPreferencesToFile(Context ctx,final String fileName) { boolean res = false; File sdCard = Environment.getExternalStorageDirectory(); if (isExternalStorageWritable()) { File dir = new File(sdCard.getAbsolutePath() + "/afwall/"); dir.mkdirs(); File file = new File(dir, fileName); try { FileOutputStream fOut = new FileOutputStream(file); OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut); JSONObject exportObject = new JSONObject(); //if multiprofile is enabled if(G.enableMultiProfile()){ JSONObject profileObject = new JSONObject(); //store all the profile settings for(String profile: G.profiles) { Map<String, JSONObject> exportMap = new HashMap<>(); SharedPreferences prefs = ctx.getSharedPreferences(profile, Context.MODE_PRIVATE); updatePackage(ctx,prefs.getString(PREF_WIFI_PKG_UIDS, ""),exportMap,WIFI_EXPORT); updatePackage(ctx,prefs.getString(PREF_3G_PKG_UIDS, ""),exportMap,DATA_EXPORT); updatePackage(ctx,prefs.getString(PREF_ROAMING_PKG_UIDS, ""),exportMap,ROAM_EXPORT); updatePackage(ctx,prefs.getString(PREF_VPN_PKG_UIDS, ""),exportMap,VPN_EXPORT); updatePackage(ctx,prefs.getString(PREF_LAN_PKG_UIDS, ""),exportMap,LAN_EXPORT); profileObject.put(profile, new JSONObject(exportMap)); } exportObject.put("profiles", profileObject); //if any additional profiles //int defaultProfileCount = 3; JSONObject addProfileObject = new JSONObject(); for(String profile: G.getAdditionalProfiles()) { Map<String, JSONObject> exportMap = new HashMap<>(); SharedPreferences prefs = ctx.getSharedPreferences(profile, Context.MODE_PRIVATE); updatePackage(ctx,prefs.getString(PREF_WIFI_PKG_UIDS, ""),exportMap,WIFI_EXPORT); updatePackage(ctx,prefs.getString(PREF_3G_PKG_UIDS, ""),exportMap,DATA_EXPORT); updatePackage(ctx,prefs.getString(PREF_ROAMING_PKG_UIDS, ""),exportMap,ROAM_EXPORT); updatePackage(ctx,prefs.getString(PREF_VPN_PKG_UIDS, ""),exportMap,VPN_EXPORT); updatePackage(ctx,prefs.getString(PREF_LAN_PKG_UIDS, ""),exportMap,LAN_EXPORT); addProfileObject.put(profile, new JSONObject(exportMap)); } //support for new profiles exportObject.put("additional_profiles", addProfileObject); } else { //default Profile - current one JSONObject obj = new JSONObject(getCurrentRulesAsMap(ctx)); exportObject.put("default", obj); } //now gets all the preferences exportObject.put("prefs", getAllAppPreferences(ctx,G.gPrefs)); myOutWriter.append(exportObject.toString()); res = true; myOutWriter.close(); fOut.close(); } catch (FileNotFoundException e) { Log.d(TAG, e.getLocalizedMessage()); } catch (IOException e) { Log.d(TAG, e.getLocalizedMessage()); } catch (JSONException e) { Log.d(TAG, e.getLocalizedMessage()); } catch (Exception e) { Log.d(TAG, e.getLocalizedMessage()); } } return res; } private static JSONArray getAllAppPreferences(Context ctx, SharedPreferences gPrefs) throws JSONException { Map<String,?> keys = gPrefs.getAll(); JSONArray arr = new JSONArray(); for(Map.Entry<String,?> entry : keys.entrySet()){ JSONObject obj = new JSONObject(); obj.put(entry.getKey(), entry.getValue().toString()); arr.put(obj); } return arr; } public static boolean saveSharedPreferencesToFile(Context ctx,final String fileName) { boolean res = false; File sdCard = Environment.getExternalStorageDirectory(); if (isExternalStorageWritable()) { File dir = new File(sdCard.getAbsolutePath() + "/afwall/"); dir.mkdirs(); File file = new File(dir,fileName); try { FileOutputStream fOut = new FileOutputStream(file); OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut); //default Profile - current one JSONObject obj = new JSONObject(getCurrentRulesAsMap(ctx)); JSONArray jArray = new JSONArray("[" + obj.toString() + "]"); myOutWriter.append(jArray.toString()); res = true; myOutWriter.close(); fOut.close(); } catch (FileNotFoundException e) { Log.e(TAG, e.getLocalizedMessage()); } catch (JSONException e) { Log.e(TAG, e.getLocalizedMessage()); } catch (IOException e) { Log.e(TAG, e.getLocalizedMessage()); } } return res; } private static boolean importRules(Context ctx,File file, StringBuilder msg) { boolean returnVal = false; BufferedReader br = null; try { StringBuilder text = new StringBuilder(); br = new BufferedReader(new FileReader(file)); String line; while ((line = br.readLine()) != null) { text.append(line); } String data = text.toString(); JSONArray array = new JSONArray(data); updateRulesFromJson(ctx,(JSONObject) array.get(0),PREFS_NAME); returnVal = true; } catch (FileNotFoundException e) { msg.append(ctx.getString(R.string.import_rules_missing)); } catch (IOException e) { Log.e(TAG, e.getLocalizedMessage()); } catch (JSONException e) { Log.e(TAG, e.getLocalizedMessage()); } finally { if (br != null) { try { br.close(); } catch (IOException e) { Log.e(TAG, e.getLocalizedMessage()); } } } return returnVal; } private static void updateRulesFromJson(Context ctx,JSONObject object,String preferenceName ) throws JSONException { final StringBuilder wifi_uids = new StringBuilder(); final StringBuilder data_uids = new StringBuilder(); final StringBuilder roam_uids = new StringBuilder(); final StringBuilder vpn_uids = new StringBuilder(); final StringBuilder lan_uids = new StringBuilder(); Map<String,Object> json = JsonHelper.toMap(object); final PackageManager pm = ctx.getPackageManager(); for (Map.Entry<String, Object> entry : json.entrySet()) { String pkgName = entry.getKey(); if(pkgName.contains(":")) { pkgName = pkgName.split(":")[0]; } JSONObject jsonObj = (JSONObject) JsonHelper.toJSON(entry.getValue()); Iterator<?> keys = jsonObj.keys(); while( keys.hasNext() ){ //get wifi/data/lan etc String key = (String)keys.next(); switch(Integer.parseInt(key)){ case WIFI_EXPORT: if (wifi_uids.length() != 0) { wifi_uids.append('|'); } if (pkgName.startsWith("dev.afwall.special")) { wifi_uids.append(specialApps.get(pkgName)); } else { try { wifi_uids.append(pm.getApplicationInfo(pkgName, 0).uid); } catch(NameNotFoundException e) { } } break; case DATA_EXPORT: if (data_uids.length() != 0) { data_uids.append('|'); } if (pkgName.startsWith("dev.afwall.special")) { data_uids.append(specialApps.get(pkgName)); } else { try { data_uids.append(pm.getApplicationInfo(pkgName, 0).uid); } catch(NameNotFoundException e) { } } break; case ROAM_EXPORT: if (roam_uids.length() != 0) { roam_uids.append('|'); } if (pkgName.startsWith("dev.afwall.special")) { roam_uids.append(specialApps.get(pkgName)); } else { try{ roam_uids.append(pm.getApplicationInfo(pkgName, 0).uid); } catch(NameNotFoundException e) { } } break; case VPN_EXPORT: if (vpn_uids.length() != 0) { vpn_uids.append('|'); } if (pkgName.startsWith("dev.afwall.special")) { vpn_uids.append(specialApps.get(pkgName)); } else { try { vpn_uids.append(pm.getApplicationInfo(pkgName, 0).uid); } catch(NameNotFoundException e) { } } break; case LAN_EXPORT: if (lan_uids.length() != 0) { lan_uids.append('|'); } if (pkgName.startsWith("dev.afwall.special")) { lan_uids.append(specialApps.get(pkgName)); } else { try { lan_uids.append(pm.getApplicationInfo(pkgName, 0).uid); } catch(NameNotFoundException e) { } } break; } } } final SharedPreferences prefs = ctx.getSharedPreferences(preferenceName, Context.MODE_PRIVATE); final Editor edit = prefs.edit(); edit.putString(PREF_WIFI_PKG_UIDS, wifi_uids.toString()); edit.putString(PREF_3G_PKG_UIDS, data_uids.toString()); edit.putString(PREF_ROAMING_PKG_UIDS, roam_uids.toString()); edit.putString(PREF_VPN_PKG_UIDS, vpn_uids.toString()); edit.putString(PREF_LAN_PKG_UIDS, lan_uids.toString()); edit.commit(); } private static boolean importAll(Context ctx,File file, StringBuilder msg) { boolean returnVal = false; BufferedReader br = null; try { StringBuilder text = new StringBuilder(); br = new BufferedReader(new FileReader(file)); String line; while ((line = br.readLine()) != null) { text.append(line); } String data = text.toString(); JSONObject object = new JSONObject(data); String[] ignore = { "appVersion", "fixLeak", "enableLogService", "enableLog" , "sort", "storedProfile"}; List<String> ignoreList = Arrays.asList(ignore); JSONArray prefArray = (JSONArray) object.get("prefs"); for(int i = 0 ; i < prefArray.length(); i++){ JSONObject prefObj = (JSONObject) prefArray.get(i); Iterator<?> keys = prefObj.keys(); while( keys.hasNext() ){ String key = (String)keys.next(); String value = (String) prefObj.get(key); if(!ignoreList.contains(key)) { //boolean type values if(value.equals("true") || value.equals("false")) { G.gPrefs.edit().putBoolean(key, Boolean.parseBoolean(value)).commit(); } else { try { //handle Long if(key.equals("multiUserId")) { G.gPrefs.edit().putLong(key, Long.parseLong(value)).commit(); } else if(key.equals("patternMax")) { G.gPrefs.edit().putString(key, value).commit(); } else { Integer intValue = Integer.parseInt(value); G.gPrefs.edit().putInt(key, intValue).commit(); } } catch(NumberFormatException e){ G.gPrefs.edit().putString(key, value).commit(); } } } } } if(G.enableMultiProfile()) { JSONObject profileObject = object.getJSONObject("profiles"); Iterator<?> keys = profileObject.keys(); while( keys.hasNext() ){ String key = (String)keys.next(); try { JSONObject obj = profileObject.getJSONObject(key); updateRulesFromJson(ctx,obj,key); }catch (JSONException e) { if(e.getMessage().contains("No value")) { continue; } } } //handle custom/additional profiles JSONObject customProfileObject = object.getJSONObject("additional_profiles"); keys = customProfileObject.keys(); while( keys.hasNext() ){ String key = (String)keys.next(); try { JSONObject obj = profileObject.getJSONObject(key); updateRulesFromJson(ctx,obj,key); }catch (JSONException e) { if(e.getMessage().contains("No value")) { continue; } } } } else { //now restore the default profile JSONObject defaultRules = object.getJSONObject("default"); updateRulesFromJson(ctx,defaultRules,PREFS_NAME); } returnVal = true; } catch (FileNotFoundException e) { msg.append(ctx.getString(R.string.import_rules_missing)); } catch (IOException e) { Log.e(TAG, e.getLocalizedMessage()); } catch (JSONException e) { Log.e(TAG, e.getLocalizedMessage()); } finally { if (br != null) { try { br.close(); } catch (IOException e) { Log.e(TAG, e.getLocalizedMessage()); } } } return returnVal; } @Deprecated /*private static boolean importRulesOld(Context ctx, File file) { boolean res = false; ObjectInputStream input = null; try { input = new ObjectInputStream(new FileInputStream(file)); Editor prefEdit = ctx.getSharedPreferences(PREFS_NAME,Context.MODE_PRIVATE).edit(); prefEdit.clear(); Map<String, ?> entries = (Map<String, ?>) input.readObject(); for (Entry<String, ?> entry : entries.entrySet()) { Object v = entry.getValue(); String key = entry.getKey(); if (v instanceof Boolean) prefEdit.putBoolean(key, ((Boolean) v).booleanValue()); else if (v instanceof Float) prefEdit.putFloat(key, ((Float) v).floatValue()); else if (v instanceof Integer) prefEdit.putInt(key, ((Integer) v).intValue()); else if (v instanceof Long) prefEdit.putLong(key, ((Long) v).longValue()); else if (v instanceof String) prefEdit.putString(key, ((String) v)); } prefEdit.commit(); res = true; } catch (FileNotFoundException e) { // toast(ctx, "Missing back.rules file"); Log.e(TAG, e.getLocalizedMessage()); } catch (IOException e) { // toast(ctx, "Error reading the backup file"); Log.e(TAG, e.getLocalizedMessage()); } catch (ClassNotFoundException e) { Log.e(TAG, e.getLocalizedMessage()); } finally { try { if (input != null) { input.close(); } } catch (IOException ex) { Log.e(TAG, ex.getLocalizedMessage()); } } return res; } */ @SuppressWarnings("unchecked") public static boolean loadSharedPreferencesFromFile(Context ctx,StringBuilder builder, String fileName){ boolean res = false; //File sdCard = Environment.getExternalStorageDirectory(); //File dir = new File(sdCard.getAbsolutePath() + "/afwall/"); //dir.mkdirs(); File file = new File(fileName); //new format if(file.exists()) { res = importRules(ctx,file,builder); } /*else { File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/afwall/"); file = new File(dir, "backup.rules"); if(file.exists()) { res = importRulesOld(ctx,file); } else { toast(ctx,ctx.getString(R.string.backup_notexist)); } }*/ return res; } @SuppressWarnings("unchecked") public static boolean loadAllPreferencesFromFile(Context ctx,StringBuilder builder,final String fileName) { boolean res = false; //File sdCard = Environment.getExternalStorageDirectory(); //File dir = new File(sdCard.getAbsolutePath() + "/afwall/"); //dir.mkdirs(); File file = new File(fileName); //new format if(file.exists()) { res = importAll(ctx,file,builder); } /*else { file = new File(dir, "backup.rules"); if(file.exists()) { res = importRulesOld(ctx,file); } else { toast(ctx,ctx.getString(R.string.backup_notexist)); } }*/ return res; } public static List<String> interfaceInfo(boolean showMatches) { List<String> ret = new ArrayList<String>(); try { for (File f : new File("/sys/class/net").listFiles()) { String name = f.getName(); if (!showMatches) { ret.add(name); } else { if (InterfaceTracker.matchName(InterfaceTracker.ITFS_WIFI, name) != null) { ret.add(name + ": wifi"); } else if (InterfaceTracker.matchName(InterfaceTracker.ITFS_3G, name) != null) { ret.add(name + ": 3G"); } else if (InterfaceTracker.matchName(InterfaceTracker.ITFS_VPN, name) != null) { ret.add(name + ": VPN"); } else { ret.add(name + ": unknown"); } } } } catch (Exception e) { Log.e(TAG, "can't list network interfaces: " + e.getLocalizedMessage()); } return ret; } private static class LogProbeCallback extends RootCommand.Callback { private Context ctx; public void cbFunc(RootCommand state) { if (state.exitCode != 0) { return; } boolean hasLOG = false, hasNFLOG = false; for(String str : state.res.toString().split("\n")) { if (str.equals("LOG")) { hasLOG = true; } else if (str.equals("NFLOG")) { hasNFLOG = true; } } if (hasLOG) { G.logTarget("LOG"); Log.d(TAG, "logging using LOG target"); } else if (hasNFLOG) { G.logTarget("NFLOG"); Log.d(TAG, "logging using NFLOG target"); } else { Log.e(TAG, "could not find LOG or NFLOG target"); //displayToasts(ctx, R.string.log_target_failed, Toast.LENGTH_SHORT); G.logTarget(""); G.enableLog(false); return; } G.enableLog(true); updateLogRules(ctx, new RootCommand() .setReopenShell(true) .setSuccessToast(R.string.log_was_enabled) .setFailureToast(R.string.log_target_failed)); } } public static void setLogging(final Context ctx, boolean isEnabled) { if (!isEnabled) { // easy case: just disable G.enableLog(false); G.logTarget(""); updateLogRules(ctx, new RootCommand() .setReopenShell(true) .setSuccessToast(R.string.log_was_disabled) .setFailureToast(R.string.log_toggle_failed)); return; } LogProbeCallback cb = new LogProbeCallback(); cb.ctx = ctx; // probe for LOG/NFLOG targets (unfortunately the file must be read by root) //check for ip6 enabled from preference and check against the same if(G.enableIPv6()) { new RootCommand() .setReopenShell(true) .setFailureToast(R.string.log_toggle_failed) .setCallback(cb) .setLogging(true) .run(ctx, "cat /proc/net/ip6_tables_targets"); } else { new RootCommand() .setReopenShell(true) .setFailureToast(R.string.log_toggle_failed) .setCallback(cb) .setLogging(true) .run(ctx, "cat /proc/net/ip_tables_targets"); } } @SuppressLint("InlinedApi") public static void showInstalledAppDetails(Context context, String packageName) { final String SCHEME = "package"; final String APP_PKG_NAME_21 = "com.android.settings.ApplicationPkgName"; final String APP_PKG_NAME_22 = "pkg"; final String APP_DETAILS_PACKAGE_NAME = "com.android.settings"; final String APP_DETAILS_CLASS_NAME = "com.android.settings.InstalledAppDetails"; Intent intent = new Intent(); final int apiLevel = Build.VERSION.SDK_INT; if (apiLevel >= 9) { // above 2.3 intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Uri uri = Uri.fromParts(SCHEME, packageName, null); intent.setData(uri); } else { // below 2.3 final String appPkgName = (apiLevel == 8 ? APP_PKG_NAME_22 : APP_PKG_NAME_21); intent.setAction(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setClassName(APP_DETAILS_PACKAGE_NAME, APP_DETAILS_CLASS_NAME); intent.putExtra(appPkgName, packageName); } context.startActivity(intent); } /*public static void showAlertDialogActivity(Context ctx,String title, String message) { Intent dialog = new Intent(ctx,AlertDialogActivity.class); dialog.putExtra("title", title); dialog.putExtra("message", message); dialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ctx.startActivity(dialog); }*/ public static boolean isNetfilterSupported() { if ((new File("/proc/config.gz")).exists() == false) { if ((new File("/proc/net/netfilter")).exists() == false) return false; if ((new File("/proc/net/ip_tables_targets")).exists() == false) return false; } return true; } private static void initSpecial() { if(specialApps == null || specialApps.size() == 0){ specialApps = new HashMap<String, Integer>(); specialApps.put("dev.afwall.special.any",SPECIAL_UID_ANY); specialApps.put("dev.afwall.special.kernel",SPECIAL_UID_KERNEL); specialApps.put("dev.afwall.special.tether",SPECIAL_UID_TETHER); //specialApps.put("dev.afwall.special.dnsproxy",SPECIAL_UID_DNSPROXY); specialApps.put("dev.afwall.special.ntp",SPECIAL_UID_NTP); for (String acct : specialAndroidAccounts) { String pkg = "dev.afwall.special." + acct; int uid = android.os.Process.getUidForName(acct); specialApps.put(pkg, uid); } } } public static void updateLanguage(Context context, String lang) { if (!"".equals(lang)) { Locale locale = new Locale(lang); Resources res = context.getResources(); DisplayMetrics dm = res.getDisplayMetrics(); Configuration conf = res.getConfiguration(); conf.locale = locale; res.updateConfiguration(conf, dm); } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public static void setUserOwner(Context context) { if(supportsMultipleUsers(context)){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { try { Method getUserHandle = UserManager.class.getMethod("getUserHandle"); int userHandle = (Integer) getUserHandle.invoke(context.getSystemService(Context.USER_SERVICE)); G.setMultiUserId(userHandle); } catch (Exception ex) { Log.e(TAG,"Exception on setUserOwner " + ex.getMessage()); } } } } @SuppressLint("NewApi") public static boolean supportsMultipleUsers(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); try { Method supportsMultipleUsers = UserManager.class.getMethod("supportsMultipleUsers"); return (Boolean)supportsMultipleUsers.invoke(um); } catch (Exception ex) { return false; } } return false; } public static String loadData(final Context context, final String resourceName) throws IOException { int resourceIdentifier = context .getApplicationContext() .getResources() .getIdentifier(resourceName, "raw", context.getApplicationContext().getPackageName()); if (resourceIdentifier != 0) { InputStream inputStream = context.getApplicationContext() .getResources().openRawResource(resourceIdentifier); BufferedReader reader = new BufferedReader(new InputStreamReader( inputStream, "UTF-8")); String line; StringBuffer data = new StringBuffer(); while ((line = reader.readLine()) != null) { data.append(line); } reader.close(); return data.toString(); } return null; } /* Checks if external storage is available for read and write */ public static boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { return true; } return false; } /* Checks if external storage is available to at least read */ public static boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } return false; } /** * Encrypt the password * @param key * @param data * @return */ public static String hideCrypt(String key, String data) { if (key == null || data == null) return null; String encodeStr = null; try { DESKeySpec desKeySpec = new DESKeySpec(key.getBytes(charsetName)); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec); byte[] dataBytes = data.getBytes(charsetName); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, secretKey); encodeStr = Base64.encodeToString(cipher.doFinal(dataBytes), base64Mode); } catch (Exception e) { Log.e(TAG, e.getLocalizedMessage()); } return encodeStr; } /** * Decrypt the password * @param key * @param data * @return */ public static String unhideCrypt(String key, String data) { if (key == null || data == null) return null; String decryptStr = null; try { byte[] dataBytes = Base64.decode(data, base64Mode); DESKeySpec desKeySpec = new DESKeySpec(key.getBytes(charsetName)); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] dataBytesDecrypted = (cipher.doFinal(dataBytes)); decryptStr = new String(dataBytesDecrypted); } catch (Exception e) { Log.e(TAG, e.getLocalizedMessage()); } return decryptStr; } public static void killLogProcess(final Context ctx,final String klogPath){ Thread thread = new Thread(){ @Override public void run() { try { new RootCommand().run(ctx, Api.getBusyBoxPath(ctx) + " pkill " + klogPath); }catch(Exception e) { Log.e(TAG,e.getMessage()); } } }; thread.start(); } public static boolean isMobileNetworkSupported(final Context ctx) { boolean hasMobileData = true; ConnectivityManager cm = (ConnectivityManager)ctx.getSystemService(Context.CONNECTIVITY_SERVICE); if (cm != null) { if (cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) { hasMobileData = false; } } return hasMobileData; } public static String getCurrentPackage(Context ctx) { PackageInfo pInfo = null; try { pInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); } catch (NameNotFoundException e) { Log.e(Api.TAG, "Package not found", e); } return pInfo.packageName; } public static void showNotification(boolean status, Context context) { if(G.activeNotification()) { final int NOTIF_ID = 33341; String notificationText = ""; NotificationManager mNotificationManager = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); NotificationCompat.Builder builder = new NotificationCompat.Builder(context); Intent appIntent = new Intent(context, MainActivity.class); //appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent in = PendingIntent.getActivity(context, 2, appIntent, PendingIntent.FLAG_UPDATE_CURRENT); int icon = R.drawable.notification; if(status) { if(G.enableMultiProfile()) { String profile = ""; switch(G.storedProfile()) { case "AFWallPrefs": profile = G.gPrefs.getString("default", context.getString(R.string.defaultProfile)); break; case "AFWallProfile1": profile = G.gPrefs.getString("profile1", context.getString(R.string.profile1)); break; case "AFWallProfile2": profile = G.gPrefs.getString("profile2", context.getString(R.string.profile2)); break; case "AFWallProfile3": profile = G.gPrefs.getString("profile3", context.getString(R.string.profile3)); break; default: profile = G.storedProfile(); break; } notificationText = context.getString(R.string.active) + "(" + profile + ")"; } else { notificationText = context.getString(R.string.active); } //notificationText = context.getString(R.string.active); icon = R.drawable.active; } else { notificationText = context.getString(R.string.inactive); icon = R.drawable.error; } //TODO: Action button's on notification //Intent deleteIntent = new Intent(context, BootBroadcast.class); //PendingIntent pendingIntentCancel = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setSmallIcon(icon).setOngoing(true) .setAutoCancel(false) .setContentTitle(context.getString(R.string.app_name)) .setTicker(context.getString(R.string.app_name)) //.addAction(R.drawable.apply, "", pendingIntentCancel) //.addAction(R.drawable.exit, "", pendingIntentCancel) .setContentText(notificationText); builder.setContentIntent(in); mNotificationManager.notify(NOTIF_ID, builder.build()); } } }