├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── org │ │ └── ethack │ │ └── orwall │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── org │ │ ├── ethack │ │ └── orwall │ │ │ ├── BackgroundProcess.java │ │ │ ├── BootBroadcast.java │ │ │ ├── NetworkReceiver.java │ │ │ ├── PreferencesActivity.java │ │ │ ├── TabbedMain.java │ │ │ ├── UninstallBroadcast.java │ │ │ ├── WizardActivity.java │ │ │ ├── adapter │ │ │ ├── AppListAdapter.java │ │ │ └── TabsPagerAdapter.java │ │ │ ├── database │ │ │ └── natDBHelper.java │ │ │ ├── fragments │ │ │ ├── AppFragment.java │ │ │ ├── HomeFragment.java │ │ │ └── WizardFragment.java │ │ │ └── lib │ │ │ ├── AppPreferenceList.java │ │ │ ├── AppRule.java │ │ │ ├── AppRuleComparator.java │ │ │ ├── CheckSum.java │ │ │ ├── Constants.java │ │ │ ├── InstallScripts.java │ │ │ ├── Iptables.java │ │ │ ├── NatRules.java │ │ │ ├── NetworkHelper.java │ │ │ ├── PackageComparator.java │ │ │ ├── PackageInfoData.java │ │ │ ├── Preferences.java │ │ │ └── Util.java │ │ └── sufficientlysecure │ │ └── rootcommands │ │ ├── Mount.java │ │ ├── Remounter.java │ │ ├── RootCommands.java │ │ ├── Shell.java │ │ ├── SystemCommands.java │ │ ├── Toolbox.java │ │ ├── command │ │ ├── Command.java │ │ ├── ExecutableCommand.java │ │ ├── SimpleCommand.java │ │ └── SimpleExecutableCommand.java │ │ └── util │ │ ├── BrokenBusyboxException.java │ │ ├── Log.java │ │ ├── RootAccessDeniedException.java │ │ ├── UnsupportedArchitectureException.java │ │ └── Utils.java │ └── res │ ├── create_ico.sh │ ├── drawable-hdpi │ ├── android_unknown_app.png │ ├── ic_action_settings.png │ └── v2.png │ ├── drawable-mdpi │ ├── android_unknown_app.png │ ├── ic_action_settings.png │ └── v2.png │ ├── drawable-xhdpi │ ├── android_unknown_app.png │ ├── ic_action_settings.png │ └── v2.png │ ├── drawable-xxhdpi │ ├── android_unknown_app.png │ ├── ic_action_settings.png │ └── v2.png │ ├── drawable-xxxhdpi │ └── android_unknown_app.png │ ├── layout │ ├── about.xml │ ├── activity_tabbed_main.xml │ ├── activity_wizard.xml │ ├── advanced_connection.xml │ ├── app_row.xml │ ├── fragment_tabbed_apps.xml │ ├── fragment_tabbed_home.xml │ └── fragment_wizard.xml │ ├── raw │ ├── activate_portal.sh │ ├── deactivate_portal.sh │ └── userinit.sh │ ├── values-de │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-it │ └── strings.xml │ ├── values │ ├── integers.xml │ ├── no_translation.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── fragment_apps_prefs.xml │ ├── fragment_network_prefs.xml │ ├── fragment_proxy_ports.xml │ ├── network_preference.xml │ ├── other_preferences.xml │ └── preferences_header.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── orWall.iml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea 4 | .DS_Store 5 | /build 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/.gitmodules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: 3 | - oraclejdk8 4 | android: 5 | components: 6 | - tools 7 | - platform-tools 8 | - build-tools-24.0.1 9 | - android-24 10 | 11 | script: 12 | - ./gradlew build 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/EthACKdotOrg/orWall.svg?branch=master)](https://travis-ci.org/EthACKdotOrg/orWall) 2 | 3 | # orWall 4 | 5 | Because real life is a Dystopia 6 | 7 | 8 | ## Website 9 | https://orwall.org/ 10 | 11 | ## What's this? 12 | orWall will force selected applications through Orbot while preventing unchecked applications to have network access. 13 | In order to do so, it will call the iptables binary. This binary, present on your Android device, requires superuser access (aka root). It's the application that manages the firewall on Linux and, by extension, on Android. 14 | 15 | In short, orWall will add special iptables rules in order to redirect traffic for applications through Tor; it will also add required rules in order to block traffic for other apps. 16 | The redirection is based on the application user id. Each android application runs as a dedicated user, and iptables has support for traffic filtering based on the process owner, meaning it's really easy and pretty safe to do this kind of thing on an Android device. 17 | 18 | The application works in two stages: first, an init-script will block all incoming and outgoing traffic. This should prevent leaks, knowing Android sends stuff before you can even access the device. 19 | Second stage comes once the device is fully booted: orWall itself takes the lead on the firewall, and add required rules in order to allow Orbot traffic, and redirect selected application to Orbot TransPort. 20 | 21 | ## Where can we find the APK? 22 | orWall is published on [f-droid](https://f-droid.org/repository/browse/?fdid=org.ethack.orwall), and we provide a GPG signed APK in the release tab. 23 | 24 | Beware, we found out people are messing around and push the APK on Google Play, as a paid app, without mentioning sources nor author. That's not the official one. We don't know if the app code is the same we provide. 25 | 26 | If you find such an app, please contact us. 27 | 28 | ### Coming soon 29 | - Support for other Onion Router applications (i2p) 30 | - Support for application dedicated stream (for Orbot) 31 | 32 | ### External libraries 33 | - [super-command](https://github.com/dschuermann/superuser-commands) (Apache2) for root accesses 34 | 35 | ### Support us 36 | - Bitcoin: 1Kriu9owRhEsFkj8Lc6Wr5xTv8YTNphhXn 37 | - Litecoin: LXjW5tKRHbrbxwTZmitj4JeBqcm4xpqvJ2 38 | 39 | ### Follow us on Twitter 40 | https://twitter.com/orWallApp 41 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion '24.0.1' 6 | 7 | defaultConfig { 8 | applicationId "org.ethack.orwall" 9 | minSdkVersion 16 10 | targetSdkVersion 24 11 | versionCode 40 12 | versionName "1.2.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | lintOptions { 21 | abortOnError false 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | testCompile 'junit:junit:4.12' 28 | compile 'com.android.support:appcompat-v7:24.0.0' 29 | 30 | // You must install or update the Support Repository through the SDK manager to use this dependency. 31 | //compile 'com.android.support:support-v4:19.+' 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/cedric/android-studio/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/org/ethack/orwall/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 59 | 62 | 63 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/BackgroundProcess.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall; 2 | 3 | import android.app.IntentService; 4 | import android.content.Intent; 5 | 6 | import org.ethack.orwall.lib.Constants; 7 | import org.ethack.orwall.lib.Iptables; 8 | import org.ethack.orwall.lib.Util; 9 | import org.sufficientlysecure.rootcommands.util.Log; 10 | 11 | /** 12 | * Allows to run background commands in order to avoid any blocking stuff in main thread. 13 | */ 14 | public class BackgroundProcess extends IntentService { 15 | 16 | private Iptables iptables; 17 | 18 | public BackgroundProcess() { 19 | super("BackgroundProcess"); 20 | } 21 | 22 | @Override 23 | protected void onHandleIntent(Intent workIntent) { 24 | this.iptables = new Iptables(this); 25 | 26 | String action = workIntent.getStringExtra(Constants.ACTION); 27 | 28 | if (action != null) { 29 | if (action.equals(Constants.ACTION_PORTAL)) { 30 | boolean activate = workIntent.getBooleanExtra(Constants.PARAM_ACTIVATE, false); 31 | managePortal(activate); 32 | 33 | } else if (action.equals(Constants.ACTION_ADD_RULE)) { 34 | long appUID = workIntent.getLongExtra(Constants.PARAM_APPUID, 0); 35 | String appName = workIntent.getStringExtra(Constants.PARAM_APPNAME); 36 | String onionType = workIntent.getStringExtra(Constants.PARAM_ONIONTYPE); 37 | Boolean localHost = workIntent.getBooleanExtra(Constants.PARAM_LOCALHOST, false); 38 | Boolean localNetwork = workIntent.getBooleanExtra(Constants.PARAM_LOCALNETWORK, false); 39 | addRule(appUID, appName, onionType, localHost, localNetwork); 40 | 41 | } else if (action.equals(Constants.ACTION_RM_RULE)) { 42 | long appUID = workIntent.getLongExtra(Constants.PARAM_APPUID, 0); 43 | String appName = workIntent.getStringExtra(Constants.PARAM_APPNAME); 44 | String onionType = workIntent.getStringExtra(Constants.PARAM_ONIONTYPE); 45 | Boolean localHost = workIntent.getBooleanExtra(Constants.PARAM_LOCALHOST, false); 46 | Boolean localNetwork = workIntent.getBooleanExtra(Constants.PARAM_LOCALNETWORK, false); 47 | rmRule(appUID, appName, onionType, localHost, localNetwork); 48 | 49 | } else if (action.equals(Constants.ACTION_DISABLE_ORWALL)) { 50 | iptables.deactivate(); 51 | iptables.deactivateV6(); 52 | 53 | } else if (action.equals(Constants.ACTION_ENABLE_ORWALL)) { 54 | iptables.boot(); 55 | } else { 56 | Log.e("BackgroundProcess", "Just got an unknown action!"); 57 | } 58 | } else { 59 | Log.e("BackgroundProcess", "Just got an undefined action!"); 60 | } 61 | } 62 | 63 | private void managePortal(boolean activate) { 64 | Util.enableCaptiveDetection(activate, this); 65 | } 66 | 67 | private void addRule(Long appUID, String appName, String onionType, Boolean localHost, Boolean localNetwork) { 68 | 69 | if (onionType.equals(Constants.DB_ONION_TYPE_TOR)) { 70 | iptables.natApp(this, appUID, 'A', appName); 71 | } else 72 | if (onionType.equals(Constants.DB_ONION_TYPE_BYPASS)) { 73 | iptables.bypass(appUID, appName, true); 74 | } 75 | 76 | if (localHost) { 77 | iptables.localHost(appUID, appName, true); 78 | } 79 | 80 | if (localNetwork) { 81 | iptables.localNetwork(appUID, appName, true); 82 | } 83 | } 84 | 85 | private void rmRule(Long appUID, String appName, String onionType, Boolean localHost, Boolean localNetwork) { 86 | if (onionType.equals(Constants.DB_ONION_TYPE_TOR)) { 87 | iptables.natApp(this, appUID, 'D', appName); 88 | } else 89 | if (onionType.equals(Constants.DB_ONION_TYPE_BYPASS)) { 90 | iptables.bypass(appUID, appName, false); 91 | } 92 | 93 | if (localHost) { 94 | iptables.localHost(appUID, appName, false); 95 | } 96 | 97 | if (localNetwork) { 98 | iptables.localNetwork(appUID, appName, false); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/BootBroadcast.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import org.ethack.orwall.lib.Iptables; 8 | import org.ethack.orwall.lib.Preferences; 9 | 10 | /** 11 | * Do think at startup. 12 | */ 13 | public class BootBroadcast extends BroadcastReceiver { 14 | 15 | public BootBroadcast() { 16 | } 17 | 18 | @Override 19 | public void onReceive(final Context context, final Intent intent) { 20 | Iptables iptables = new Iptables(context); 21 | 22 | // Enforce init-script if sharedpreference says it 23 | // We want to do it the earlier. 24 | // Also, we want to get a fresh status regarding the init-script support: this can be 25 | // a reboot after a ROM upgrade or change. 26 | boolean enforceInit = Preferences.isEnforceInitScript(context); 27 | if (Iptables.initSupported() && enforceInit) { 28 | Iptables.installInitScript(context); 29 | } 30 | // Apply boot-up rules in order to enable traffic for orbot and other things. 31 | 32 | if (Preferences.isOrwallEnabled(context)){ 33 | iptables.boot(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/NetworkReceiver.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Set; 5 | import java.util.HashSet; 6 | 7 | import android.content.BroadcastReceiver; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.util.Log; 11 | 12 | import org.ethack.orwall.lib.Iptables; 13 | import org.ethack.orwall.lib.NetworkHelper; 14 | import org.ethack.orwall.lib.Preferences; 15 | 16 | public class NetworkReceiver extends BroadcastReceiver { 17 | private static String TAG = "NetworkReceiver"; 18 | 19 | public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; 20 | public static final String EXTRA_ACTIVE_TETHER = "activeArray"; 21 | 22 | public NetworkReceiver() { 23 | } 24 | 25 | @Override 26 | public void onReceive(Context context, Intent intent) { 27 | if (!Preferences.isOrwallEnabled(context)){ 28 | return; 29 | } 30 | 31 | String action = intent.getAction(); 32 | 33 | Log.d(TAG, "Got a Network Change event: " + action); 34 | 35 | Iptables iptables = new Iptables(context); 36 | 37 | if (action.equals(ACTION_TETHER_STATE_CHANGED)){ 38 | // try the faster way 39 | Set set = new HashSet<>(0); 40 | ArrayList active = intent.getStringArrayListExtra(EXTRA_ACTIVE_TETHER); 41 | if (active != null){ 42 | for(String intf: active) set.add(intf); 43 | } else { 44 | // hum, try the old fashioned way 45 | NetworkHelper.getTetheredInterfaces(context, set); 46 | } 47 | 48 | Set oldIntfs = Preferences.getTetherInterfaces(context); 49 | 50 | if (!set.equals(oldIntfs)) 51 | iptables.tetherUpdate(context, oldIntfs, set); 52 | } 53 | else 54 | if (action.equals("android.net.wifi.WIFI_STATE_CHANGED") || action.equals("android.net.conn.CONNECTIVITY_CHANGE")) { 55 | Log.d(TAG, "Will do some LAN stuff"); 56 | 57 | iptables.LANPolicy(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/PreferencesActivity.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.os.Bundle; 7 | import android.preference.PreferenceActivity; 8 | import android.preference.PreferenceFragment; 9 | import android.preference.PreferenceManager; 10 | 11 | import org.ethack.orwall.lib.Constants; 12 | import org.ethack.orwall.lib.Iptables; 13 | import org.ethack.orwall.lib.Preferences; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | 19 | public class PreferencesActivity extends PreferenceActivity { 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | } 24 | 25 | @Override 26 | public void onBuildHeaders(List
target) { 27 | loadHeadersFromResource(R.xml.preferences_header, target); 28 | } 29 | 30 | @Override 31 | protected boolean isValidFragment(String fragmentName) { 32 | String prepend = "org.ethack.orwall.PreferencesActivity$"; 33 | String[] fragments = { 34 | prepend + "SpecialApps", 35 | prepend + "NetworkPrefs", 36 | prepend + "ProxyPorts", 37 | }; 38 | 39 | return Arrays.asList(fragments).contains(fragmentName); 40 | } 41 | 42 | @Override 43 | public void onDestroy() { 44 | super.onDestroy(); 45 | } 46 | 47 | public static class SpecialApps extends PreferenceFragment { 48 | private SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() { 49 | @Override 50 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { 51 | 52 | } 53 | }; 54 | 55 | @Override 56 | public void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | addPreferencesFromResource(R.xml.fragment_apps_prefs); 59 | } 60 | 61 | @Override 62 | public void onResume() { 63 | super.onResume(); 64 | getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener); 65 | } 66 | 67 | @Override 68 | public void onPause() { 69 | super.onPause(); 70 | getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener); 71 | } 72 | } 73 | 74 | public static class NetworkPrefs extends PreferenceFragment { 75 | private SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() { 76 | @Override 77 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { 78 | 79 | if (!sharedPreferences.getBoolean(Preferences.PREF_KEY_ORWALL_ENABLED, true)) return; 80 | 81 | Iptables iptables = new Iptables(getActivity()); 82 | 83 | switch (s) { 84 | case Preferences.PREF_KEY_ADB_ENABLED: 85 | iptables.enableADB(sharedPreferences.getBoolean(s, false)); 86 | break; 87 | case Preferences.PREF_KEY_SSH_ENABLED: 88 | iptables.enableSSH(sharedPreferences.getBoolean(s, false)); 89 | break; 90 | case "enable_captive_portal": 91 | Context context = getActivity(); 92 | Intent bgpProcess = new Intent(context, BackgroundProcess.class); 93 | bgpProcess.putExtra(Constants.PARAM_ACTIVATE, sharedPreferences.getBoolean(s, false)); 94 | bgpProcess.putExtra(Constants.ACTION, Constants.ACTION_PORTAL); 95 | context.startService(bgpProcess); 96 | break; 97 | } 98 | } 99 | }; 100 | 101 | @Override 102 | public void onCreate(Bundle savedInstanceState) { 103 | super.onCreate(savedInstanceState); 104 | PreferenceManager.setDefaultValues(getActivity(), R.xml.other_preferences, true); 105 | addPreferencesFromResource(R.xml.fragment_network_prefs); 106 | } 107 | 108 | @Override 109 | public void onResume() { 110 | super.onResume(); 111 | getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener); 112 | } 113 | 114 | @Override 115 | public void onPause() { 116 | super.onPause(); 117 | getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener); 118 | } 119 | } 120 | 121 | public static class ProxyPorts extends PreferenceFragment { 122 | @Override 123 | public void onCreate(Bundle savedInstanceState) { 124 | super.onCreate(savedInstanceState); 125 | addPreferencesFromResource(R.xml.fragment_proxy_ports); 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/TabbedMain.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall; 2 | 3 | import android.app.ActionBar; 4 | import android.app.ActionBar.Tab; 5 | import android.app.FragmentTransaction; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.support.v4.app.FragmentActivity; 9 | import android.support.v4.view.ViewPager; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | 13 | import org.ethack.orwall.adapter.TabsPagerAdapter; 14 | import org.ethack.orwall.lib.Iptables; 15 | import org.ethack.orwall.lib.NatRules; 16 | import org.ethack.orwall.lib.Preferences; 17 | import org.sufficientlysecure.rootcommands.util.Log; 18 | 19 | import java.util.Set; 20 | 21 | /** 22 | * New main layout: using a tabbed layout allows to get a cleaner view 23 | * and a more friendly experience for end-users. 24 | */ 25 | public class TabbedMain extends FragmentActivity implements ActionBar.TabListener { 26 | 27 | // Debug tag 28 | private String TAG = "TabbedMain"; 29 | 30 | // Private variables we may need across multiple methods 31 | private ViewPager viewPager; 32 | private TabsPagerAdapter mAdapter; 33 | private ActionBar actionBar; 34 | // TODO: use R content for tab names if needed. 35 | private String[] tabs = {"Home", "Apps"/*, "Logs"*/}; 36 | 37 | @Override 38 | public void onTabReselected(Tab tab, FragmentTransaction ft) { 39 | } 40 | 41 | @Override 42 | public void onTabSelected(Tab tab, FragmentTransaction ft) { 43 | // on tab selected 44 | // show respected fragment view 45 | Log.d(TAG, String.valueOf(tab.getPosition())); 46 | viewPager.setCurrentItem(tab.getPosition()); 47 | } 48 | 49 | @Override 50 | public void onTabUnselected(Tab tab, FragmentTransaction ft) { 51 | } 52 | 53 | @Override 54 | protected void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | setContentView(R.layout.activity_tabbed_main); 57 | 58 | // Import old settings to SQLite, and remove them from SharedPreferences 59 | NatRules natRules = new NatRules(this); 60 | Set oldRules = getSharedPreferences(Preferences.PREFERENCES, MODE_PRIVATE).getStringSet("nat_rules", null); 61 | if (natRules.getRuleCount() == 0 && oldRules != null) { 62 | natRules.importFromSharedPrefs(oldRules); 63 | getSharedPreferences(Preferences.PREFERENCES, MODE_PRIVATE).edit().remove("nat_rules").apply(); 64 | } 65 | 66 | // Is it the first application run? 67 | if (Preferences.isFirstRun(this)) { 68 | // Initialize orWall iptables rules - #72 should be better after that 69 | Iptables iptables = new Iptables(this); 70 | iptables.boot(); 71 | // Start Wizard 72 | Intent wizard = new Intent(this, WizardActivity.class); 73 | startActivity(wizard); 74 | } 75 | 76 | viewPager = (ViewPager) findViewById(R.id.pager); 77 | actionBar = getActionBar(); 78 | mAdapter = new TabsPagerAdapter(getSupportFragmentManager()); 79 | 80 | viewPager.setAdapter(mAdapter); 81 | actionBar.setHomeButtonEnabled(false); 82 | actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 83 | 84 | // create the tab header 85 | for (String tab : tabs) { 86 | actionBar.addTab(actionBar.newTab().setText(tab).setTabListener(this)); 87 | } 88 | 89 | // changer in order to take care of tab switching 90 | viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { 91 | @Override 92 | public void onPageSelected(int position) { 93 | actionBar.setSelectedNavigationItem(position); 94 | } 95 | 96 | @Override 97 | public void onPageScrolled(int arg0, float arg1, int arg2) { 98 | } 99 | 100 | @Override 101 | public void onPageScrollStateChanged(int arg0) { 102 | } 103 | }); 104 | } 105 | 106 | 107 | /** 108 | * No more menu 109 | * 110 | * @param menu 111 | * @return 112 | */ 113 | @Override 114 | public boolean onCreateOptionsMenu(Menu menu) { 115 | // We don't need menu anymore now. "Settings" entry is on home page, as well as quick actions 116 | return true; 117 | } 118 | 119 | /** 120 | * We do not provide a menu. 121 | * 122 | * @param item 123 | * @return 124 | */ 125 | @Override 126 | public boolean onOptionsItemSelected(MenuItem item) { 127 | return true; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/UninstallBroadcast.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.util.Log; 8 | import org.ethack.orwall.lib.AppRule; 9 | import org.ethack.orwall.lib.NatRules; 10 | 11 | public class UninstallBroadcast extends BroadcastReceiver { 12 | private final static String TAG = "UninstallBroadcast"; 13 | 14 | public UninstallBroadcast() { 15 | } 16 | 17 | @Override 18 | public void onReceive(Context context, Intent intent) { 19 | Uri data = intent.getData(); 20 | 21 | if (!data.getScheme().equals("package")) { 22 | Log.d(TAG, "Intent scheme was not 'package'"); 23 | return; 24 | } 25 | 26 | boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 27 | 28 | if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction()) && !replacing) { 29 | final long uid = intent.getIntExtra(Intent.EXTRA_UID, -123); 30 | final String appName = intent.getData().getSchemeSpecificPart(); 31 | Log.d("UninstallBroadcast", "AppName: " + appName + ", AppUID: " + uid); 32 | 33 | // is the app present in rules? 34 | NatRules natRules = new NatRules(context); 35 | 36 | AppRule rule = natRules.getAppRule(uid); 37 | if (rule.isStored()) { 38 | 39 | // First: remove rule from firewall if any 40 | rule.uninstall(context); 41 | 42 | // Second: remove app from NatRules if present 43 | natRules.removeAppFromRules(uid); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/WizardActivity.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentActivity; 6 | import android.support.v4.app.FragmentManager; 7 | import android.support.v4.app.FragmentStatePagerAdapter; 8 | import android.support.v4.view.PagerAdapter; 9 | import android.support.v4.view.ViewPager; 10 | 11 | import org.ethack.orwall.fragments.WizardFragment; 12 | import org.ethack.orwall.lib.Preferences; 13 | 14 | /** 15 | * Simple wizard activity. 16 | * It will displays orWall capabilities, explain some stuff 17 | *

18 | * Aim: provide main information in a smooth though complete way to the User, so that 19 | * he knows what to do. 20 | *

21 | * Taken from example in here: 22 | * https://developer.android.com/training/animation/screen-slide.html 23 | */ 24 | public class WizardActivity extends FragmentActivity { 25 | 26 | // Number of pages (wizard steps) 27 | private static final int NUM_PAGES = 3; 28 | 29 | // Pager widget 30 | private ViewPager viewPager; 31 | 32 | // Pager adapter 33 | private PagerAdapter pagerAdapter; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_wizard); 39 | 40 | viewPager = (ViewPager) findViewById(R.id.wizard_pager); 41 | pagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); 42 | viewPager.setAdapter(pagerAdapter); 43 | viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { 44 | @Override 45 | public void onPageSelected(int position) { 46 | invalidateOptionsMenu(); 47 | } 48 | }); 49 | } 50 | 51 | @Override 52 | public void onBackPressed() { 53 | if (viewPager.getCurrentItem() == 0) { 54 | // If the user is currently looking at the first step, allow the system to handle the 55 | // Back button. This calls finish() on this activity and pops the back stack. 56 | Preferences.setFirstRun(this, false); 57 | super.onBackPressed(); 58 | } else { 59 | // Otherwise, select the previous step. 60 | viewPager.setCurrentItem(viewPager.getCurrentItem() - 1); 61 | } 62 | } 63 | 64 | /** 65 | * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in 66 | * sequence. 67 | */ 68 | private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { 69 | public ScreenSlidePagerAdapter(FragmentManager fm) { 70 | super(fm); 71 | } 72 | 73 | @Override 74 | public Fragment getItem(int position) { 75 | return WizardFragment.create(position); 76 | } 77 | 78 | @Override 79 | public int getCount() { 80 | return NUM_PAGES; 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/adapter/TabsPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.adapter; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentPagerAdapter; 6 | 7 | import org.ethack.orwall.fragments.AppFragment; 8 | import org.ethack.orwall.fragments.HomeFragment; 9 | 10 | /** 11 | * A simple wrapper for tab management. 12 | */ 13 | public class TabsPagerAdapter extends FragmentPagerAdapter { 14 | 15 | public TabsPagerAdapter(FragmentManager fragmentManager) { 16 | super(fragmentManager); 17 | } 18 | 19 | @Override 20 | public Fragment getItem(int index) { 21 | switch (index) { 22 | case 0: 23 | return new HomeFragment(); 24 | case 1: 25 | return new AppFragment(); 26 | } 27 | return null; 28 | } 29 | 30 | @Override 31 | public int getCount() { 32 | return 2; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/database/natDBHelper.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.database; 2 | 3 | import android.content.Context; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.database.sqlite.SQLiteOpenHelper; 6 | 7 | 8 | /** 9 | * Simple DB helper in order to manage SQLite for NAT rules. 10 | * This also prepare the way for more features. 11 | */ 12 | public class natDBHelper extends SQLiteOpenHelper { 13 | 14 | public static final String NAT_TABLE_NAME = "rules"; 15 | public static final String COLUMN_APPUID = "appUID"; 16 | public static final String COLUMN_APPNAME = "appName"; 17 | public static final String COLUMN_ONIONTYPE = "onionType"; 18 | public static final String COLUMN_LOCALHOST = "localhost"; 19 | public static final String COLUMN_LOCALNETWORK = "localnetwork"; 20 | 21 | /* 22 | @Deprecated 23 | private static final String COLUMN_ONIONPORT = "onionPort"; 24 | @Deprecated 25 | private static final String DB_PORT_TYPE_FENCED = "Fenced"; 26 | @Deprecated 27 | private static final String COLUMN_PORTTYPE = "portType"; 28 | 29 | private static final String NAT_TABLE_CREATE_V1 = 30 | String.format( 31 | "CREATE TABLE %s (" + 32 | "%s INTEGER PRIMARY KEY," + 33 | "%s TEXT NOT NULL," + 34 | "%s TEXT NOT NULL DEFAULT \"%s\"," + 35 | "%s INTEGER NOT NULL DEFAULT '%d'," + 36 | "%s TEXT NOT NULL DEFAULT \"TransProxy\")", 37 | NAT_TABLE_NAME, 38 | COLUMN_APPUID, 39 | COLUMN_APPNAME, 40 | COLUMN_ONIONTYPE, Constants.DB_ONION_TYPE_TOR, 41 | COLUMN_ONIONPORT, Constants.ORBOT_TRANSPROXY, 42 | COLUMN_PORTTYPE 43 | ); 44 | */ 45 | private static final String NAT_TABLE_CREATE_V2 = 46 | String.format( 47 | "CREATE TABLE %s (" + 48 | "%s INTEGER PRIMARY KEY," + 49 | "%s TEXT NOT NULL," + 50 | "%s TEXT," + 51 | "%s INTEGER," + 52 | "%s INTEGER)", 53 | NAT_TABLE_NAME, 54 | COLUMN_APPUID, 55 | COLUMN_APPNAME, 56 | COLUMN_ONIONTYPE, 57 | COLUMN_LOCALHOST, 58 | COLUMN_LOCALNETWORK 59 | ); 60 | 61 | private static final int DATABASE_VERSION = 2; 62 | private static final String DB_NAME = "nat.s3db"; 63 | 64 | public natDBHelper(Context context) { 65 | super(context, DB_NAME, null, DATABASE_VERSION); 66 | } 67 | 68 | @Override 69 | public void onCreate(SQLiteDatabase db) { 70 | db.execSQL(NAT_TABLE_CREATE_V2); 71 | } 72 | 73 | @Override 74 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 75 | if (oldVersion == newVersion){ 76 | return; 77 | } 78 | 79 | db.beginTransaction(); 80 | try{ 81 | for(int version = oldVersion; version < newVersion; version++){ 82 | switch (version){ 83 | // VERSION 1 -----> 2 84 | case 1: 85 | db.execSQL(String.format("ALTER TABLE %s RENAME TO %s_backup;", NAT_TABLE_NAME, NAT_TABLE_NAME)); 86 | db.execSQL(NAT_TABLE_CREATE_V2); 87 | db.execSQL(String.format( 88 | "INSERT INTO %s(%s, %s, %s, %s, %s) SELECT %s, %s, %s, 0, 0 FROM %s_backup;", 89 | NAT_TABLE_NAME, COLUMN_APPUID, COLUMN_APPNAME, COLUMN_ONIONTYPE, COLUMN_LOCALHOST, COLUMN_LOCALNETWORK, 90 | COLUMN_APPUID, COLUMN_APPNAME, COLUMN_ONIONTYPE, NAT_TABLE_NAME)); 91 | db.execSQL(String.format("DROP TABLE %s_backup;", NAT_TABLE_NAME)); 92 | } 93 | } 94 | 95 | db.setTransactionSuccessful(); 96 | } finally{ 97 | db.endTransaction(); 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/fragments/AppFragment.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.fragments; 2 | 3 | import android.content.pm.PackageInfo; 4 | import android.content.pm.PackageManager; 5 | import android.os.Bundle; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.util.LongSparseArray; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ListView; 12 | 13 | import org.ethack.orwall.R; 14 | import org.ethack.orwall.adapter.AppListAdapter; 15 | import org.ethack.orwall.lib.AppRule; 16 | import org.ethack.orwall.lib.AppRuleComparator; 17 | import org.ethack.orwall.lib.Constants; 18 | import org.ethack.orwall.lib.Iptables; 19 | import org.ethack.orwall.lib.NatRules; 20 | import org.ethack.orwall.lib.PackageInfoData; 21 | import org.ethack.orwall.lib.Preferences; 22 | import org.sufficientlysecure.rootcommands.RootCommands; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Collections; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | /** 30 | * Manage "apps" tab fragment. 31 | * 32 | * @link org.ethack.orwall.TabbedMain 33 | */ 34 | public class AppFragment extends Fragment { 35 | 36 | @Override 37 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 38 | 39 | View view; 40 | 41 | view = inflater.inflate(R.layout.fragment_tabbed_apps, container, false); 42 | Iptables iptables = new Iptables(getActivity()); 43 | // Do we have root access ? 44 | if (RootCommands.rootAccessGiven()) { 45 | view.findViewById(R.id.warn_root).setVisibility(View.GONE); 46 | } else { 47 | view.findViewById(R.id.warn_root).setVisibility(View.VISIBLE); 48 | } 49 | // Hopefully there IS iptables on this device… 50 | if (Iptables.iptablesExists()) { 51 | view.findViewById(R.id.warn_iptables).setVisibility(View.GONE); 52 | } else { 53 | view.findViewById(R.id.warn_iptables).setVisibility(View.VISIBLE); 54 | } 55 | if (Iptables.initSupported() && !iptables.isInitialized()) { 56 | view.findViewById(R.id.warn_init).setVisibility(View.VISIBLE); 57 | } 58 | 59 | ListView listView = (ListView) view.findViewById(R.id.id_enabled_apps); 60 | 61 | // Toggle hint layer 62 | boolean hide_hint = Preferences.isHidePressHint(getActivity()); 63 | 64 | if (hide_hint) { 65 | view.findViewById(R.id.hint_press).setVisibility(View.GONE); 66 | } else { 67 | view.findViewById(R.id.id_hide_hint).setOnClickListener(new View.OnClickListener() { 68 | @Override 69 | public void onClick(View view) { 70 | ((View) view.getParent()).setVisibility(View.GONE); 71 | Preferences.setHidePressHint(getActivity(), true); 72 | } 73 | }); 74 | } 75 | 76 | // get enabled apps 77 | NatRules natRules = new NatRules(this.getActivity()); 78 | List enabledApps = natRules.getAllRules(); 79 | LongSparseArray rulesIndex = new LongSparseArray<>(); 80 | for (AppRule app: enabledApps) rulesIndex.put(app.getAppUID(), app); 81 | 82 | // get disabled apps (filtered with enabled) 83 | List disabledApps = listDisabledApps(rulesIndex); 84 | // Get special, disabled apps 85 | List specialDisabled = listSpecialApps(rulesIndex); 86 | 87 | // Merge both disabled apps 88 | disabledApps.addAll(specialDisabled); 89 | 90 | // Sort collection using a dedicated method 91 | Collections.sort(enabledApps, new AppRuleComparator(getActivity().getPackageManager())); 92 | Collections.sort(disabledApps, new AppRuleComparator(getActivity().getPackageManager())); 93 | 94 | // merge both collections so that enabled apps are above disabled 95 | enabledApps.addAll(disabledApps); 96 | 97 | listView.setAdapter(new AppListAdapter(this.getActivity(), enabledApps)); 98 | 99 | return view; 100 | } 101 | 102 | /** 103 | * List all disabled application. Meaning: installed app requiring Internet, but NOT in NatRules. 104 | * It also filters out special apps like orbot and i2p. 105 | * 106 | * @return List of AppRule 107 | */ 108 | private List listDisabledApps(LongSparseArray index) { 109 | PackageManager packageManager = this.getActivity().getPackageManager(); 110 | List pkgList = new ArrayList<>(); 111 | 112 | List pkgInstalled = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS); 113 | 114 | for (PackageInfo pkgInfo : pkgInstalled) { 115 | if (needInternet(pkgInfo) && !isReservedApp(pkgInfo)) { 116 | if (index.indexOfKey((long) pkgInfo.applicationInfo.uid) < 0) { 117 | AppRule app = new AppRule(false, pkgInfo.packageName, (long) pkgInfo.applicationInfo.uid, Constants.DB_ONION_TYPE_NONE, false, false); 118 | app.setAppName(packageManager.getApplicationLabel(pkgInfo.applicationInfo).toString()); 119 | pkgList.add(app); 120 | } 121 | } 122 | } 123 | return pkgList; 124 | } 125 | 126 | private List listSpecialApps(LongSparseArray index) { 127 | List pkgList = new ArrayList<>(); 128 | Map specialApps = PackageInfoData.specialApps(); 129 | 130 | for (PackageInfoData pkgInfo: specialApps.values()) { 131 | if (index.indexOfKey(pkgInfo.getUid()) < 0) { 132 | AppRule app = new AppRule(false, pkgInfo.getPkgName(), pkgInfo.getUid(), Constants.DB_ONION_TYPE_NONE, false, false); 133 | app.setAppName(pkgInfo.getName()); 134 | pkgList.add(app); 135 | } 136 | } 137 | 138 | return pkgList; 139 | } 140 | 141 | /** 142 | * Checks if application requires Internet 143 | * 144 | * @param pkg PackageInfo object 145 | * @return true if package requires internet 146 | */ 147 | private boolean needInternet(PackageInfo pkg) { 148 | String[] permissions = (pkg.requestedPermissions); 149 | if (permissions != null) { 150 | for (String perm : permissions) { 151 | if (perm.equals("android.permission.INTERNET")) { 152 | return true; 153 | } 154 | } 155 | } 156 | return false; 157 | } 158 | 159 | /** 160 | * Check if app name is a reserved one, like orbot or i2p 161 | * 162 | * @param pkg PackageInfo object 163 | * @return true if package name matches one of the reserved names 164 | */ 165 | private boolean isReservedApp(PackageInfo pkg) { 166 | return ( 167 | pkg.packageName.equals(Constants.ORBOT_APP_NAME) || 168 | pkg.packageName.equals("org.ethack.orwall") 169 | ); 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/fragments/WizardFragment.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.CompoundButton; 9 | import android.widget.Switch; 10 | import android.widget.TextView; 11 | 12 | import org.ethack.orwall.R; 13 | import org.ethack.orwall.lib.InstallScripts; 14 | import org.ethack.orwall.lib.Iptables; 15 | import org.ethack.orwall.lib.Preferences; 16 | import org.ethack.orwall.lib.Util; 17 | import org.sufficientlysecure.rootcommands.RootCommands; 18 | 19 | import java.util.Locale; 20 | 21 | /** 22 | * A simple {@link Fragment} subclass. 23 | * Will display a simple Wizard explaining User what orWall can do. 24 | */ 25 | public class WizardFragment extends Fragment { 26 | 27 | /** 28 | * The argument key for the page number this fragment represents. 29 | */ 30 | public static final String ARG_PAGE = "page"; 31 | 32 | /** 33 | * The fragment's page number, which is set to the argument value for {@link #ARG_PAGE}. 34 | */ 35 | private int mPageNumber; 36 | 37 | 38 | public WizardFragment() { 39 | } 40 | 41 | /** 42 | * Factory method for this fragment class. Constructs a new fragment for the given page number. 43 | */ 44 | public static WizardFragment create(int position) { 45 | WizardFragment fragment = new WizardFragment(); 46 | Bundle args = new Bundle(); 47 | args.putInt(ARG_PAGE, position); 48 | fragment.setArguments(args); 49 | return fragment; 50 | } 51 | 52 | @Override 53 | public void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | mPageNumber = getArguments().getInt(ARG_PAGE); 56 | } 57 | 58 | 59 | @Override 60 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 61 | Bundle savedInstanceState) { 62 | ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_wizard, container, false); 63 | 64 | int[] titles = { 65 | R.string.wizard_title_one, 66 | R.string.wizard_title_two, 67 | R.string.wizard_title_three, 68 | }; 69 | 70 | String title = getString(R.string.wizard_title_one); 71 | String step = "Step 1: "; 72 | if (mPageNumber < titles.length) { 73 | title = getString(titles[mPageNumber]); 74 | step = String.format(Locale.US, "Step %d: ", mPageNumber + 1); 75 | } 76 | ((TextView) rootView.findViewById(R.id.wizard_step_title)) 77 | .setText(step + title); 78 | 79 | int[] fragments = { 80 | R.string.wizard_first, 81 | R.string.wizard_second, 82 | R.string.wizard_third, 83 | }; 84 | 85 | String fragment = getString(fragments[0]); 86 | if (mPageNumber < fragments.length) { 87 | fragment = getString(fragments[mPageNumber]); 88 | } 89 | ((TextView) rootView.findViewById(R.id.wizard_fragment_content)).setText(fragment); 90 | 91 | rootView.findViewById(R.id.wizard_close).setOnClickListener(new View.OnClickListener() { 92 | @Override 93 | public void onClick(View view) { 94 | Preferences.setFirstRun(getActivity(), false); 95 | getActivity().finish(); 96 | } 97 | }); 98 | 99 | // Add some stuff on the very first Wizard page 100 | if (mPageNumber == 0) { 101 | ViewGroup main_content = (ViewGroup) rootView.findViewById(R.id.id_main_content); 102 | final Iptables iptables = new Iptables(getActivity()); 103 | // Extract scripts 104 | InstallScripts installScripts = new InstallScripts(getActivity()); 105 | installScripts.run(); 106 | 107 | // init-script installation 108 | // install init as default behavior 109 | Iptables.installInitScript(getActivity()); 110 | boolean enforceInit = Preferences.isEnforceInitScript(getActivity()); 111 | boolean initSupported = Iptables.initSupported(); 112 | 113 | Switch initScript = new Switch(getActivity()); 114 | initScript.setChecked( (enforceInit && initSupported) ); 115 | initScript.setText(getString(R.string.wizard_init_script_text)); 116 | initScript.setEnabled(initSupported); 117 | 118 | initScript.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 119 | @Override 120 | public void onCheckedChanged(CompoundButton compoundButton, boolean b) { 121 | boolean checked = compoundButton.isChecked(); 122 | if (checked) { 123 | Iptables.installInitScript(getActivity()); 124 | } else { 125 | Iptables.removeIniScript(getActivity()); 126 | } 127 | } 128 | }); 129 | 130 | main_content.addView(initScript); 131 | 132 | // Root status 133 | Switch rootStatus = new Switch(getActivity()); 134 | rootStatus.setChecked(RootCommands.rootAccessGiven()); 135 | rootStatus.setEnabled(false); 136 | rootStatus.setText(getString(R.string.wizard_init_root_text)); 137 | main_content.addView(rootStatus); 138 | 139 | // Does iptables exist? 140 | Switch iptablesStatus = new Switch(getActivity()); 141 | iptablesStatus.setChecked(Iptables.iptablesExists()); 142 | iptablesStatus.setEnabled(false); 143 | iptablesStatus.setText(getString(R.string.wizard_init_iptables_text)); 144 | main_content.addView(iptablesStatus); 145 | 146 | // Does current kernel support IPTables comments? 147 | Switch iptablesComments = new Switch(getActivity()); 148 | iptablesComments.setChecked(iptables.getSupportComment()); 149 | iptablesComments.setEnabled(false); 150 | iptablesComments.setText(getString(R.string.wizard_init_ipt_comments_text)); 151 | main_content.addView(iptablesComments); 152 | 153 | // Is orbot installed? 154 | Switch orbotStatus = new Switch(getActivity()); 155 | orbotStatus.setChecked(Util.isOrbotInstalled(getActivity())); 156 | orbotStatus.setEnabled(false); 157 | orbotStatus.setText(getString(R.string.wizard_orbot_status_text)); 158 | main_content.addView(orbotStatus); 159 | 160 | } 161 | 162 | return rootView; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/AppPreferenceList.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.preference.ListPreference; 7 | import android.preference.PreferenceManager; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.ListAdapter; 12 | import android.widget.ListView; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | import java.util.Collections; 17 | import java.util.List; 18 | 19 | /** 20 | * Created by cedric on 7/26/14. 21 | */ 22 | public class AppPreferenceList extends ListPreference { 23 | 24 | private final PackageManager packageManager; 25 | 26 | public AppPreferenceList(Context context, AttributeSet attributeSet) { 27 | super(context, attributeSet); 28 | this.packageManager = context.getPackageManager(); 29 | } 30 | 31 | public AppPreferenceList(Context context) { 32 | super(context); 33 | this.packageManager = context.getPackageManager(); 34 | } 35 | 36 | @Override 37 | protected View onCreateDialogView() { 38 | PreferenceManager preferenceManager = getPreferenceManager(); 39 | String chosen_app = preferenceManager.getSharedPreferences().getString(this.getKey(), "0"); 40 | ListView view = new ListView(getContext()); 41 | view.setAdapter(adapter()); 42 | setEntries(entries()); 43 | setEntryValues(entryValues()); 44 | setValue(chosen_app); 45 | setPersistent(true); 46 | setDefaultValue(chosen_app); 47 | return view; 48 | } 49 | 50 | private ListAdapter adapter() { 51 | return new ArrayAdapter(getContext(), android.R.layout.select_dialog_singlechoice); 52 | } 53 | 54 | private CharSequence[] entries() { 55 | List pkgList = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS); 56 | Collections.sort(pkgList, new PackageComparator(packageManager)); 57 | 58 | Collection list = new ArrayList<>(); 59 | 60 | for (PackageInfo pkg : pkgList) { 61 | if (isInternet(pkg)) 62 | list.add(packageManager.getApplicationLabel(pkg.applicationInfo)); 63 | } 64 | 65 | return list.toArray(new CharSequence[list.size()]); 66 | } 67 | 68 | private CharSequence[] entryValues() { 69 | List pkgList = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS); 70 | Collections.sort(pkgList, new PackageComparator(packageManager)); 71 | 72 | Collection list = new ArrayList<>(); 73 | 74 | for (PackageInfo pkg : pkgList) { 75 | if (isInternet(pkg)) 76 | list.add(Long.toString(pkg.applicationInfo.uid)); 77 | } 78 | return list.toArray(new CharSequence[list.size()]); 79 | } 80 | 81 | private boolean isInternet(PackageInfo pkg) { 82 | String[] permissions = (pkg.requestedPermissions); 83 | if (permissions != null) { 84 | for (String perm : permissions) { 85 | if (perm.equals("android.permission.INTERNET")) { 86 | return true; 87 | } 88 | } 89 | } 90 | return false; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/AppRule.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import org.ethack.orwall.BackgroundProcess; 7 | 8 | import java.util.ArrayList; 9 | 10 | /** 11 | * Data structure: application NAT rule. 12 | */ 13 | public class AppRule { 14 | private Boolean stored; 15 | private String pkgName; 16 | private Long appUID; 17 | private String onionType; 18 | private Boolean localHost; 19 | private Boolean localNetwork; 20 | 21 | // Variables dedicated for ListView 22 | // We need them for persistence across scroll 23 | private String label; 24 | private String appName; 25 | 26 | public AppRule(Boolean stored, String pkgName, Long appUID, String onionType, Boolean localHost, Boolean localNetwork) { 27 | this.stored = stored; 28 | this.pkgName = pkgName; 29 | this.appUID = appUID; 30 | this.onionType = onionType; 31 | this.localHost = localHost; 32 | this.localNetwork = localNetwork; 33 | // set to a null value - used in AppListAdapter 34 | this.label = null; 35 | this.appName = null; 36 | } 37 | 38 | public AppRule() { 39 | // Empty constructor in order to use setters. 40 | this.stored = false; 41 | this.pkgName = null; 42 | this.appUID = null; 43 | this.onionType = Constants.DB_ONION_TYPE_NONE; 44 | this.localHost = false; 45 | this.localNetwork = false; 46 | // set to a null value - used in AppListAdapter 47 | this.label = null; 48 | this.appName = null; 49 | } 50 | 51 | public Boolean isStored(){ 52 | return this.stored; 53 | } 54 | 55 | public void setStored(Boolean stored) { 56 | this.stored = stored; 57 | } 58 | 59 | public Boolean isEmpty(){ 60 | return !this.localHost && !this.localNetwork && this.onionType.equals(Constants.DB_ONION_TYPE_NONE); 61 | } 62 | 63 | public String getPkgName() { 64 | return this.pkgName; 65 | } 66 | 67 | public void setPkgName(String pkgName) { 68 | this.pkgName = pkgName; 69 | } 70 | 71 | public String getOnionType() { 72 | return this.onionType; 73 | } 74 | 75 | public String getDisplay(){ 76 | String ret = this.appName; 77 | ArrayList flags = new ArrayList<>(); 78 | switch (this.onionType) { 79 | case Constants.DB_ONION_TYPE_NONE: 80 | break; 81 | case Constants.DB_ONION_TYPE_BYPASS: 82 | flags.add("Bypass"); 83 | break; 84 | case Constants.DB_ONION_TYPE_TOR: 85 | flags.add("Tor"); 86 | break; 87 | } 88 | if (this.localHost) { 89 | flags.add("Localhost"); 90 | } 91 | if (this.localNetwork) { 92 | flags.add("LocalNetwork"); 93 | } 94 | 95 | if (!flags.isEmpty()){ 96 | ret += " (" + flags.get(0); 97 | for(int i = 1; i < flags.size(); i++){ 98 | ret += " - " + flags.get(i); 99 | } 100 | ret += ")"; 101 | } 102 | return ret; 103 | } 104 | 105 | public void setOnionType(String onionType) { 106 | this.onionType = onionType; 107 | } 108 | 109 | public Boolean getLocalHost() { 110 | return this.localHost; 111 | } 112 | 113 | public void setLocalHost(Boolean localHost) { 114 | this.localHost = localHost; 115 | } 116 | 117 | public Boolean getLocalNetwork() { 118 | return this.localNetwork; 119 | } 120 | 121 | public void setLocalNetwork(Boolean localNetwork) { 122 | this.localNetwork = localNetwork; 123 | } 124 | 125 | public Long getAppUID() { 126 | return this.appUID; 127 | } 128 | 129 | public void setAppUID(Long appUID) { 130 | this.appUID = appUID; 131 | } 132 | 133 | public String getLabel() { 134 | return this.label; 135 | } 136 | 137 | public void setLabel(String label) { 138 | this.label = label; 139 | } 140 | 141 | public String getAppName() { 142 | return this.appName; 143 | } 144 | 145 | public void setAppName(String appName) { 146 | this.appName = appName; 147 | } 148 | 149 | private Intent newBackground(Context context, Intent intent){ 150 | Intent bg = (intent == null? new Intent(context, BackgroundProcess.class): intent); 151 | bg.putExtra(Constants.PARAM_APPUID, getAppUID()); 152 | bg.putExtra(Constants.PARAM_APPNAME, getPkgName()); 153 | bg.putExtra(Constants.PARAM_ONIONTYPE, getOnionType()); 154 | bg.putExtra(Constants.PARAM_LOCALHOST, getLocalHost()); 155 | bg.putExtra(Constants.PARAM_LOCALNETWORK, getLocalNetwork()); 156 | return bg; 157 | } 158 | 159 | public void install(Context context, Intent intent){ 160 | Intent bg = newBackground(context, intent); 161 | bg.putExtra(Constants.ACTION, Constants.ACTION_ADD_RULE); 162 | context.startService(bg); 163 | } 164 | 165 | public void uninstall(Context context, Intent intent){ 166 | Intent bg = newBackground(context, intent); 167 | bg.putExtra(Constants.ACTION, Constants.ACTION_RM_RULE); 168 | context.startService(bg); 169 | } 170 | 171 | public void install(Context context){ 172 | install(context, null); 173 | } 174 | 175 | public void uninstall(Context context){ 176 | uninstall(context, null); 177 | } 178 | 179 | 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/AppRuleComparator.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.content.pm.PackageInfo; 4 | import android.content.pm.PackageManager; 5 | import android.util.Log; 6 | 7 | import java.util.Comparator; 8 | 9 | /** 10 | * Comparator: allows to sort appRule collection using application name. 11 | */ 12 | public class AppRuleComparator implements Comparator { 13 | private static final String TAG = "AppRuleComparator"; 14 | 15 | private PackageManager packageManager; 16 | 17 | public AppRuleComparator(PackageManager packageManager) { 18 | this.packageManager = packageManager; 19 | } 20 | 21 | private String getLabel(AppRule appRule){ 22 | if (appRule.getAppName() == null){ 23 | if (appRule.getPkgName().startsWith(Constants.SPECIAL_APPS_PREFIX)) { 24 | appRule.setAppName(PackageInfoData.specialApps().get(appRule.getPkgName()).getName()); 25 | } else { 26 | try { 27 | PackageInfo pkgInfo1 = packageManager.getPackageInfo(appRule.getPkgName(), PackageManager.GET_PERMISSIONS); 28 | appRule.setAppName(packageManager.getApplicationLabel(pkgInfo1.applicationInfo).toString()); 29 | } catch (PackageManager.NameNotFoundException e) { 30 | Log.e(TAG, e.getMessage()); 31 | } 32 | } 33 | } 34 | return appRule.getAppName(); 35 | } 36 | 37 | @Override 38 | public int compare(AppRule appRule1, AppRule appRule2) { 39 | String label1 = getLabel(appRule1); 40 | String label2 = getLabel(appRule2); 41 | 42 | if (label1 != null && label2 != null) { 43 | return label1.compareTo(label2); 44 | } else { 45 | return 0; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/CheckSum.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.FileInputStream; 6 | import java.io.FileNotFoundException; 7 | import java.io.IOException; 8 | import java.security.MessageDigest; 9 | import java.security.NoSuchAlgorithmException; 10 | 11 | /** 12 | * Allows to checksum files. 13 | * Used for init-script installation. 14 | */ 15 | public class CheckSum { 16 | 17 | private String method; 18 | private String file; 19 | 20 | /** 21 | * Class builder — default method is MD5 22 | * 23 | * @param file String, path to the file 24 | */ 25 | public CheckSum(String file) { 26 | this.file = file; 27 | this.method = "MD5"; 28 | } 29 | 30 | /** 31 | * Create the hash 32 | * 33 | * @return String, file hash 34 | */ 35 | public String hash() { 36 | MessageDigest md; 37 | try { 38 | md = MessageDigest.getInstance(method); 39 | } catch (NoSuchAlgorithmException e) { 40 | Log.e("Hash", "No such algorithm: " + method); 41 | return Constants.E_NO_SUCH_ALGO; 42 | } 43 | FileInputStream fis; 44 | try { 45 | fis = new FileInputStream(file); 46 | } catch (FileNotFoundException e) { 47 | Log.e("Hash", "No such file: " + file); 48 | return Constants.E_NO_SUCH_FILE; 49 | } 50 | 51 | byte[] dataBytes = new byte[1024]; 52 | int nread; 53 | 54 | try { 55 | while ((nread = fis.read(dataBytes)) != -1) { 56 | md.update(dataBytes, 0, nread); 57 | } 58 | } catch (IOException e) { 59 | return "E_IOEXCEPTION"; 60 | } 61 | 62 | byte[] mdbytes = md.digest(); 63 | 64 | StringBuffer sb = new StringBuffer(); 65 | for (byte mdb : mdbytes) { 66 | sb.append(Integer.toHexString(0xFF & mdb)); 67 | } 68 | Log.d("Checksum", sb.toString()); 69 | return sb.toString(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/Constants.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | /** 4 | * Constants used across the code 5 | */ 6 | public class Constants { 7 | public final static String SPECIAL_APPS_PREFIX = "orwall.special."; 8 | public final static String IPTABLES = "/system/bin/iptables"; 9 | public final static String IP6TABLES = "/system/bin/ip6tables"; 10 | 11 | public final static String ACTION = "org.ethack.orwall.backgroundProcess.action"; 12 | public final static String ACTION_PORTAL = "org.ethack.orwall.backgroundProcess.action.portal"; 13 | public final static String PARAM_ACTIVATE = "org.ethack.orwall.captive.activate"; 14 | 15 | public final static String ACTION_ADD_RULE = "org.ethack.orwall.backgroundProcess.action.addRule"; 16 | public final static String ACTION_RM_RULE = "org.ethack.orwall.backgroundProcess.action.rmRule"; 17 | public final static String PARAM_APPUID = "org.ethack.orwall.backgroundProcess.action.rule.appUid"; 18 | public final static String PARAM_APPNAME = "org.ethack.orwall.backgroundProcess.action.rule.appName"; 19 | public final static String PARAM_LOCALHOST = "org.ethack.orwall.backgroundProcess.action.rule.localHost"; 20 | public final static String PARAM_LOCALNETWORK = "org.ethack.orwall.backgroundProcess.action.rule.localNetwork"; 21 | public final static String PARAM_ONIONTYPE = "org.ethack.orwall.backgroundProcess.action.rule.onionType"; 22 | 23 | public final static String ACTION_DISABLE_ORWALL = "org.ethack.orwall.backgroundProcess.action.disable_orwall"; 24 | public final static String ACTION_ENABLE_ORWALL = "org.ethack.orwall.backgroundProcess.action.enable_orwall"; 25 | 26 | public final static String E_NO_SUCH_FILE = "E_NO_SUCH_FILE"; 27 | public final static String E_NO_SUCH_ALGO = "E_NO_SUCH_ALGO"; 28 | 29 | public final static String DB_ONION_TYPE_NONE = "None"; 30 | public final static String DB_ONION_TYPE_TOR = "Tor"; 31 | public final static String DB_ONION_TYPE_BYPASS = "Bypass"; 32 | 33 | public final static String ORBOT_APP_NAME = "org.torproject.android"; 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/InstallScripts.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import org.ethack.orwall.R; 7 | 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | 13 | /** 14 | * Created by cedric on 7/25/14. 15 | */ 16 | public class InstallScripts extends Thread { 17 | private final Context context; 18 | 19 | public InstallScripts(Context context) { 20 | this.context = context; 21 | } 22 | 23 | /** 24 | * Thanks to AFWall :) 25 | * 26 | * @param ctx 27 | * @param resId 28 | * @param filename 29 | * @return 30 | */ 31 | private static boolean installBinary(Context ctx, int resId, String filename) { 32 | try { 33 | File f = new File(ctx.getDir("bin", 0), filename); 34 | if (f.exists()) { 35 | f.delete(); 36 | } 37 | copyRawFile(ctx, resId, f, "0755"); 38 | return true; 39 | } catch (Exception e) { 40 | Log.e("InstallScripts", "installBinary failed: " + e.getLocalizedMessage()); 41 | return false; 42 | } 43 | } 44 | 45 | /** 46 | * Copies a raw resource file, given its ID to the given location 47 | * 48 | * @param ctx context 49 | * @param resid resource id 50 | * @param file destination file 51 | * @param mode file permissions (E.g.: "755") 52 | * @throws java.io.IOException on error 53 | * @throws InterruptedException when interrupted 54 | *

55 | * Thanks AFWall source code 56 | */ 57 | private static void copyRawFile(Context ctx, int resid, File file, String mode) throws IOException, InterruptedException { 58 | final String abspath = file.getAbsolutePath(); 59 | final FileOutputStream out = new FileOutputStream(file); 60 | final InputStream is = ctx.getResources().openRawResource(resid); 61 | byte buf[] = new byte[1024]; 62 | int len; 63 | while ((len = is.read(buf)) > 0) { 64 | out.write(buf, 0, len); 65 | } 66 | out.close(); 67 | is.close(); 68 | // Change the permissions 69 | Runtime.getRuntime().exec("chmod " + mode + " " + abspath).waitFor(); 70 | } 71 | 72 | @Override 73 | public void run() { 74 | if (!installBinary(context, R.raw.activate_portal, "activate_portal.sh")) { 75 | Log.e("Init", "Unable to install activate_portal script"); 76 | } 77 | if (!installBinary(context, R.raw.deactivate_portal, "deactivate_portal.sh")) { 78 | Log.e("Init", "Unable to install deactivate_portal script"); 79 | } 80 | if (!installBinary(context, R.raw.userinit, "userinit.sh")) { 81 | Log.e("Init", "We're fucked… unable to extract userinit.sh script"); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/NatRules.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.database.Cursor; 7 | import android.database.sqlite.SQLiteConstraintException; 8 | import android.database.sqlite.SQLiteDatabase; 9 | 10 | import org.ethack.orwall.database.natDBHelper; 11 | import org.sufficientlysecure.rootcommands.util.Log; 12 | 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | import java.util.Set; 16 | 17 | /** 18 | * Helper: manage apps in SQLite, in order to prevent concurrent accesses to the DB. 19 | */ 20 | public class NatRules { 21 | private final static String TAG = "NatRules"; 22 | private natDBHelper dbHelper; 23 | private Context context; 24 | 25 | public NatRules(Context context) { 26 | this.dbHelper = new natDBHelper(context); 27 | this.context = context; 28 | } 29 | 30 | public boolean removeAppFromRules(Long appUID) { 31 | String filter = natDBHelper.COLUMN_APPUID + "=?"; 32 | String[] filterArgs = {String.valueOf(appUID)}; 33 | 34 | SQLiteDatabase db = this.dbHelper.getWritableDatabase(); 35 | int result = db.delete(natDBHelper.NAT_TABLE_NAME, filter, filterArgs); 36 | db.close(); 37 | return (result == 1); 38 | } 39 | 40 | public boolean addAppToRules(Long appUID, String appName, String onionType, Boolean localHost, Boolean localNetwork) { 41 | 42 | ContentValues contentValues = new ContentValues(); 43 | contentValues.put(natDBHelper.COLUMN_APPNAME, appName); 44 | contentValues.put(natDBHelper.COLUMN_APPUID, String.valueOf(appUID)); 45 | contentValues.put(natDBHelper.COLUMN_ONIONTYPE, onionType); 46 | contentValues.put(natDBHelper.COLUMN_LOCALHOST, localHost); 47 | contentValues.put(natDBHelper.COLUMN_LOCALNETWORK, localNetwork); 48 | 49 | SQLiteDatabase db = this.dbHelper.getWritableDatabase(); 50 | long result = db.insert(natDBHelper.NAT_TABLE_NAME, null, contentValues); 51 | db.close(); 52 | return (result > 0); 53 | } 54 | 55 | public boolean addAppToRules(AppRule appRule) { 56 | return addAppToRules( 57 | appRule.getAppUID(), 58 | appRule.getPkgName(), 59 | appRule.getOnionType(), 60 | appRule.getLocalHost(), 61 | appRule.getLocalNetwork() 62 | ); 63 | } 64 | 65 | public ArrayList getAllRules() { 66 | ArrayList list = new ArrayList<>(); 67 | 68 | SQLiteDatabase db = this.dbHelper.getReadableDatabase(); 69 | String[] selection = { 70 | natDBHelper.COLUMN_APPNAME, 71 | natDBHelper.COLUMN_APPUID, 72 | natDBHelper.COLUMN_ONIONTYPE, 73 | natDBHelper.COLUMN_LOCALHOST, 74 | natDBHelper.COLUMN_LOCALNETWORK 75 | }; 76 | Cursor cursor = db.query(natDBHelper.NAT_TABLE_NAME, selection, null, null, null, null, null); 77 | 78 | if (!cursor.moveToFirst()) { 79 | Log.e(TAG, "getAllRules size is null!"); 80 | return list; 81 | } 82 | 83 | AppRule appRule; 84 | 85 | do { 86 | appRule = new AppRule( 87 | true, 88 | cursor.getString(0), 89 | cursor.getLong(1), 90 | cursor.getString(2), 91 | cursor.getLong(3) == 1, 92 | cursor.getLong(4) == 1 93 | ); 94 | list.add(appRule); 95 | } while (cursor.moveToNext()); 96 | 97 | cursor.close(); 98 | db.close(); 99 | Log.d(TAG, "getAllRules size: " + String.valueOf(list.size())); 100 | return list; 101 | } 102 | 103 | public int getRuleCount() { 104 | SQLiteDatabase db = this.dbHelper.getReadableDatabase(); 105 | Cursor cursor = db.query(natDBHelper.NAT_TABLE_NAME, null, null, null, null, null, null); 106 | cursor.moveToFirst(); 107 | 108 | int total = cursor.getCount(); 109 | cursor.close(); 110 | db.close(); 111 | return total; 112 | } 113 | 114 | public void importFromSharedPrefs(Set oldRules) { 115 | PackageManager packageManager = this.context.getPackageManager(); 116 | for (Object rule : oldRules.toArray()) { 117 | HashMap r = (HashMap) rule; 118 | Long uid = (Long) r.values().toArray()[0]; 119 | String name = (String) r.keySet().toArray()[0]; 120 | // ensure we migrate only existing applications 121 | try { 122 | packageManager.getApplicationInfo(name, PackageManager.GET_META_DATA); 123 | addAppToRules(uid, name, Constants.DB_ONION_TYPE_TOR, false, false); 124 | } catch (PackageManager.NameNotFoundException e) { 125 | } 126 | } 127 | } 128 | 129 | public boolean update(AppRule appRule) { 130 | ContentValues contentValues = new ContentValues(); 131 | contentValues.put(natDBHelper.COLUMN_APPNAME, appRule.getPkgName()); 132 | contentValues.put(natDBHelper.COLUMN_APPUID, String.valueOf(appRule.getAppUID())); 133 | contentValues.put(natDBHelper.COLUMN_ONIONTYPE, appRule.getOnionType()); 134 | contentValues.put(natDBHelper.COLUMN_LOCALHOST, appRule.getLocalHost()?1:0); 135 | contentValues.put(natDBHelper.COLUMN_LOCALNETWORK, appRule.getLocalNetwork()?1:0); 136 | 137 | String filter = natDBHelper.COLUMN_APPUID + "=?"; 138 | String[] filterArgs = {String.valueOf(appRule.getAppUID())}; 139 | SQLiteDatabase db = this.dbHelper.getWritableDatabase(); 140 | 141 | int nb_row = 0; 142 | try { 143 | nb_row = db.update(natDBHelper.NAT_TABLE_NAME, contentValues, filter, filterArgs); 144 | } catch (SQLiteConstraintException e) { 145 | Log.e(TAG, "Constraint exception"); 146 | Log.e(TAG, e.getMessage()); 147 | } 148 | db.close(); 149 | 150 | return (nb_row == 1); 151 | } 152 | 153 | public AppRule getAppRule(Long appUID) { 154 | SQLiteDatabase db = this.dbHelper.getReadableDatabase(); 155 | 156 | String[] filterArgs = { 157 | String.valueOf(appUID) 158 | }; 159 | String[] selection = { 160 | natDBHelper.COLUMN_APPNAME, 161 | natDBHelper.COLUMN_APPUID, 162 | natDBHelper.COLUMN_ONIONTYPE, 163 | natDBHelper.COLUMN_LOCALHOST, 164 | natDBHelper.COLUMN_LOCALNETWORK 165 | }; 166 | 167 | Cursor cursor = db.query( 168 | natDBHelper.NAT_TABLE_NAME, 169 | selection, 170 | natDBHelper.COLUMN_APPUID + "=?", 171 | filterArgs, 172 | null, 173 | null, 174 | null 175 | ); 176 | 177 | AppRule appRule; 178 | if (cursor.moveToFirst()) { 179 | appRule = new AppRule( 180 | true, 181 | cursor.getString(0), 182 | cursor.getLong(1), 183 | cursor.getString(2), 184 | cursor.getLong(3) == 1, 185 | cursor.getLong(4) == 1 186 | ); 187 | } else { 188 | appRule = new AppRule(); 189 | Log.e(TAG, "Unable to get rules for " + String.valueOf(appUID)); 190 | } 191 | cursor.close(); 192 | db.close(); 193 | 194 | return appRule; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/NetworkHelper.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.content.Context; 4 | import android.net.DhcpInfo; 5 | import android.net.ConnectivityManager; 6 | import android.net.wifi.WifiManager; 7 | import android.util.Log; 8 | 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Method; 11 | import java.net.Inet4Address; 12 | import java.net.InetAddress; 13 | import java.net.InterfaceAddress; 14 | import java.net.NetworkInterface; 15 | import java.net.SocketException; 16 | import java.nio.ByteOrder; 17 | import java.util.Collections; 18 | import java.util.Iterator; 19 | import java.util.Locale; 20 | import java.util.Set; 21 | 22 | /** 23 | * Small helper in order to get some network information 24 | */ 25 | public class NetworkHelper { 26 | 27 | private static String TAG = "NetworkHelper"; 28 | 29 | /** 30 | * Tries to detect if we're sharing the connection or not. 31 | * It's not that easy, as it seems there is no simple API to call for that :(. 32 | * 33 | * @param context Context in order to get ConnectivityManager 34 | * @return boolean (true if connection is shared) 35 | */ 36 | public static boolean isTether(Context context) { 37 | ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 38 | 39 | Method[] methods = connectivityManager.getClass().getDeclaredMethods(); 40 | String[] tethered = {}; 41 | for (Method method : methods) { 42 | if (method.getName().equals("getTetheredIfaces")) { 43 | try { 44 | tethered = (String[]) method.invoke(connectivityManager); 45 | } catch (IllegalArgumentException e) { 46 | Log.e(TAG, e.getMessage()); 47 | } catch (IllegalAccessException e) { 48 | Log.e(TAG, e.getMessage()); 49 | } catch (InvocationTargetException e) { 50 | Log.e(TAG, e.getMessage()); 51 | } 52 | } 53 | } 54 | return (tethered.length != 0); 55 | } 56 | 57 | private static int netmaskToCIDR(int netmask){ 58 | switch (netmask){ 59 | case 0x80000000: return 1; 60 | case 0xC0000000: return 2; 61 | case 0xE0000000: return 3; 62 | case 0xF0000000: return 4; 63 | case 0xF8000000: return 5; 64 | case 0xFC000000: return 6; 65 | case 0xFE000000: return 7; 66 | case 0xFF000000: return 8; 67 | case 0xFF800000: return 9; 68 | case 0xFFC00000: return 10; 69 | case 0xFFE00000: return 11; 70 | case 0xFFF00000: return 12; 71 | case 0xFFF80000: return 13; 72 | case 0xFFFC0000: return 14; 73 | case 0xFFFE0000: return 15; 74 | case 0xFFFF0000: return 16; 75 | case 0xFFFF8000: return 17; 76 | case 0xFFFFC000: return 18; 77 | case 0xFFFFE000: return 19; 78 | case 0xFFFFF000: return 20; 79 | case 0xFFFFF800: return 21; 80 | case 0xFFFFFC00: return 22; 81 | case 0xFFFFFE00: return 23; 82 | case 0xFFFFFF00: return 24; 83 | case 0xFFFFFF80: return 25; 84 | case 0xFFFFFFC0: return 26; 85 | case 0xFFFFFFE0: return 27; 86 | case 0xFFFFFFF0: return 28; 87 | case 0xFFFFFFF8: return 29; 88 | case 0xFFFFFFFC: return 30; 89 | case 0xFFFFFFFE: return 31; 90 | case 0xFFFFFFFF: return 32; 91 | default: 92 | return 0; 93 | } 94 | } 95 | 96 | private static String getNetwork(DhcpInfo dhcp){ 97 | int ip = dhcp.ipAddress; 98 | int mask = dhcp.netmask; 99 | if (ip == 0 || mask == 0) return null; 100 | 101 | if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) { 102 | ip = Integer.reverseBytes(ip); 103 | mask = Integer.reverseBytes(mask); 104 | } 105 | 106 | ip &= mask; 107 | mask = netmaskToCIDR(mask); 108 | if (mask == 0) return null; 109 | 110 | int a = (ip >> 24) & 0xFF; 111 | int b = (ip >> 16) & 0xFF; 112 | int c = (ip >> 8) & 0xFF; 113 | int d = ip & 0xFF; 114 | 115 | return String.format(Locale.US, "%d.%d.%d.%d/%d", a, b, c, d, mask); 116 | } 117 | 118 | /** 119 | * Provide a simple way to get subnet 120 | * 121 | * @param context Context in order to get WifiManager 122 | * @return subnet as a String 123 | */ 124 | 125 | public static String getSubnet(Context context) { 126 | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 127 | return getNetwork(wifiManager.getDhcpInfo()); 128 | } 129 | 130 | public static void getTetheredInterfaces(Context context, Set set){ 131 | ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); 132 | for(Method method: cm.getClass().getDeclaredMethods()){ 133 | if(method.getName().equals("getTetheredIfaces")){ 134 | try { 135 | String[] intfs = (String[]) method.invoke(cm); 136 | if (intfs != null) 137 | Collections.addAll(set, intfs); 138 | break; 139 | } catch (Exception e) { 140 | return; 141 | } 142 | } 143 | } 144 | } 145 | 146 | public static String getMask(String intf){ 147 | try { 148 | NetworkInterface ntwrk = NetworkInterface.getByName(intf); 149 | Iterator addrList = ntwrk.getInterfaceAddresses().iterator(); 150 | while (addrList.hasNext()) { 151 | InterfaceAddress addr = addrList.next(); 152 | InetAddress ip = addr.getAddress(); 153 | if (ip instanceof Inet4Address) { 154 | String mask = ip.getHostAddress() + "/" + 155 | addr.getNetworkPrefixLength(); 156 | return mask; 157 | } 158 | } 159 | } catch (SocketException e) { 160 | e.printStackTrace(); 161 | } 162 | return null; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/PackageComparator.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.content.pm.PackageInfo; 4 | import android.content.pm.PackageManager; 5 | 6 | import java.util.Comparator; 7 | 8 | /** 9 | * Package comparator in order to sort a collection of application. 10 | */ 11 | public class PackageComparator implements Comparator { 12 | private PackageManager packageManager; 13 | 14 | public PackageComparator(PackageManager packageManager) { 15 | this.packageManager = packageManager; 16 | } 17 | 18 | @Override 19 | public int compare(PackageInfo packageInfo, PackageInfo packageInfo2) { 20 | String label1 = packageManager.getApplicationLabel(packageInfo.applicationInfo).toString(); 21 | String label2 = packageManager.getApplicationLabel(packageInfo2.applicationInfo).toString(); 22 | return label1.compareTo(label2); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/PackageInfoData.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Small structure holding special application info 8 | * Taken from AFWall+: 9 | * https://github.com/ukanth/afwallplus/blob/fd4843824e6e6678c8b50204b68a01f6fb8ed3d0/src/dev/ukanth/ufirewall/Api.java#L1569 10 | */ 11 | public class PackageInfoData { 12 | // Linux user ID 13 | private long uid; 14 | // Application name related to UID 15 | private String name; 16 | private String pkgName; 17 | 18 | public PackageInfoData(long uid, String name, String pkgName) { 19 | this.uid = uid; 20 | this.name = name; 21 | this.pkgName = pkgName; 22 | } 23 | 24 | public PackageInfoData(String user, String name, String pkgName) { 25 | this(android.os.Process.getUidForName(user), name, pkgName); 26 | } 27 | 28 | public void setUid(long uid) { 29 | this.uid = uid; 30 | } 31 | public long getUid() { 32 | return this.uid; 33 | } 34 | 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | public String getName() { 39 | return this.name; 40 | } 41 | 42 | public void setPkgName(String pkgName) { 43 | this.pkgName = pkgName; 44 | } 45 | public String getPkgName() { 46 | return this.pkgName; 47 | } 48 | 49 | /** 50 | * It seems some system things don't show up in packagemanager. 51 | * This code comes from AFWall+, as they already hit this small problem: 52 | * https://github.com/ukanth/afwallplus/blob/fd4843824e6e6678c8b50204b68a01f6fb8ed3d0/src/dev/ukanth/ufirewall/Api.java#L2313-L2327 53 | */ 54 | public static Map specialApps() { 55 | String prefix = Constants.SPECIAL_APPS_PREFIX; 56 | Map specialApps = new HashMap<>(); 57 | specialApps.put(prefix+"media", new PackageInfoData("media", "Media Server", prefix+"media")); 58 | specialApps.put(prefix+"vpn", new PackageInfoData("vpn", "VPN Service", prefix+"vpn")); 59 | specialApps.put(prefix+"shell", new PackageInfoData("shell", "Linux Shell", prefix+"shell")); 60 | specialApps.put(prefix+"adb", new PackageInfoData("adb", "Android Debug Bridge (ADB)", prefix+"adb")); 61 | 62 | return specialApps; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/Preferences.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.support.annotation.Nullable; 6 | 7 | import java.util.Set; 8 | 9 | public final class Preferences { 10 | public final static String PREFERENCES = "org.ethack.orwall_preferences"; 11 | public final static String PREF_KEY_FIRST_RUN = "first_run"; 12 | public final static String PREF_KEY_SIP_APP = "sip_app"; 13 | public final static String PREF_KEY_SIP_ENABLED = "sip_enabled"; 14 | public final static String PREF_KEY_SPEC_BROWSER = "browser_app"; 15 | public final static String PREF_KEY_BROWSER_ENABLED = "browser_enabled"; 16 | //public final static String PREF_KEY_IS_TETHER_ENABLED = "is_tether_enabled"; 17 | public final static String PREF_KEY_TETHER_INTFS = "tether_interfaces"; 18 | public final static String PREF_TRANS_PORT = "proxy_transport"; 19 | public final static String PREF_DNS_PORT = "proxy_dns"; 20 | public final static String PREF_KEY_ADB_ENABLED = "enable_adb"; 21 | public final static String PREF_KEY_SSH_ENABLED = "enable_ssh"; 22 | public final static String PREF_KEY_ENFORCE_INIT = "enforce_init_script"; 23 | //public final static String PREF_KEY_DISABLE_INIT = "deactivate_init_script"; 24 | public final static String PREF_KEY_BROWSER_GRACETIME = "browser_gracetime"; 25 | //public final static String PREF_KEY_IPT_SUPPORTS_COMMENTS = "ipt_comments"; 26 | public final static String PREF_KEY_ORWALL_ENABLED = "orwall_enabled"; 27 | public final static String PREF_KEY_CURRENT_SUBNET = "current_subnet"; 28 | public final static String PREF_KEY_HIDE_PRESS_HINT = "hide_press_hint"; 29 | public final static String PREF_KEY_TETHER_NETWORK = "tether_net_"; 30 | 31 | public static long ORBOT_TRANSPROXY = 9040; 32 | public static long ORBOT_DNS_PROXY = 5400; 33 | 34 | public final static long BROWSER_GRACETIME = 5; 35 | 36 | private static boolean getBoolean(Context context, String key, boolean def){ 37 | return context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE).getBoolean(key, def); 38 | } 39 | 40 | private static void putBoolean(Context context, String key, boolean value){ 41 | context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE).edit().putBoolean(key, value).apply(); 42 | } 43 | 44 | private static String getString(Context context, String key, String def){ 45 | return context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE).getString(key, def); 46 | } 47 | 48 | private static void setString(Context context, String key, String value){ 49 | context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE).edit().putString(key, value).apply(); 50 | } 51 | 52 | private static Set getStringSet(Context context, String key, Set def){ 53 | return context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE).getStringSet(key, def); 54 | } 55 | 56 | private static void setStringSet(Context context, String key, Set value){ 57 | context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE).edit().putStringSet(key, value).apply(); 58 | } 59 | 60 | public static boolean isFirstRun(Context context){ 61 | return getBoolean(context, PREF_KEY_FIRST_RUN, true); 62 | } 63 | 64 | public static void setFirstRun(Context context, boolean value){ 65 | putBoolean(context, PREF_KEY_FIRST_RUN, value); 66 | } 67 | 68 | public static String getSIPApp(Context context){ 69 | return getString(context, PREF_KEY_SIP_APP, "0"); 70 | } 71 | 72 | public static boolean isSIPEnabled(Context context){ 73 | return getBoolean(context, PREF_KEY_SIP_ENABLED, false); 74 | } 75 | 76 | public static void setSIPEnabled(Context context, boolean value){ 77 | putBoolean(context, PREF_KEY_SIP_ENABLED, value); 78 | } 79 | 80 | public static String getBrowserApp(Context context){ 81 | return getString(context, PREF_KEY_SPEC_BROWSER, "0"); 82 | } 83 | 84 | public static boolean isBrowserEnabled(Context context){ 85 | return getBoolean(context, PREF_KEY_BROWSER_ENABLED, false); 86 | } 87 | 88 | public static void setBrowserEnabled(Context context, boolean value){ 89 | putBoolean(context, PREF_KEY_BROWSER_ENABLED, value); 90 | } 91 | 92 | public static Set getTetherInterfaces(Context context){ 93 | return getStringSet(context, PREF_KEY_TETHER_INTFS, null); 94 | } 95 | 96 | public static void setTetherInterfaces(Context context, @Nullable Set value){ 97 | setStringSet(context, PREF_KEY_TETHER_INTFS, value); 98 | } 99 | 100 | public static void cleanIptablesPreferences(Context context){ 101 | SharedPreferences sharedPreferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE); 102 | sharedPreferences.edit().remove(PREF_KEY_CURRENT_SUBNET).apply(); 103 | sharedPreferences.edit().remove(PREF_KEY_TETHER_INTFS).apply(); 104 | } 105 | 106 | public static String getTransPort(Context context){ 107 | return getString(context, PREF_TRANS_PORT, String.valueOf(ORBOT_TRANSPROXY)); 108 | } 109 | 110 | public static String getDNSPort(Context context){ 111 | return getString(context, PREF_DNS_PORT, String.valueOf(ORBOT_DNS_PROXY)); 112 | } 113 | 114 | public static boolean isADBEnabled(Context context){ 115 | return getBoolean(context, PREF_KEY_ADB_ENABLED, false); 116 | } 117 | 118 | public static boolean isSSHEnabled(Context context){ 119 | return getBoolean(context, PREF_KEY_SSH_ENABLED, false); 120 | } 121 | 122 | public static boolean isEnforceInitScript(Context context){ 123 | return getBoolean(context, PREF_KEY_ENFORCE_INIT, true); 124 | } 125 | 126 | public static void setEnforceInitScript(Context context, boolean value){ 127 | putBoolean(context, PREF_KEY_ENFORCE_INIT, value); 128 | } 129 | 130 | public static String getBrowserGraceTime(Context context){ 131 | return getString(context, PREF_KEY_BROWSER_GRACETIME, String.valueOf(BROWSER_GRACETIME)); 132 | } 133 | 134 | public static boolean isOrwallEnabled(Context context){ 135 | return getBoolean(context, PREF_KEY_ORWALL_ENABLED, true); 136 | } 137 | 138 | public static void setOrwallEnabled(Context context, boolean value){ 139 | putBoolean(context, PREF_KEY_ORWALL_ENABLED, value); 140 | } 141 | 142 | public static String getCurrentSubnet(Context context){ 143 | return getString(context, PREF_KEY_CURRENT_SUBNET, null); 144 | } 145 | 146 | public static void setCurrentSubnet(Context context, String value){ 147 | setString(context, PREF_KEY_CURRENT_SUBNET, value); 148 | } 149 | 150 | public static boolean isHidePressHint(Context context){ 151 | return getBoolean(context, PREF_KEY_HIDE_PRESS_HINT, false); 152 | } 153 | 154 | public static void setHidePressHint(Context context, boolean value){ 155 | putBoolean(context, PREF_KEY_HIDE_PRESS_HINT, value); 156 | } 157 | 158 | public static String getTetherNetwork(Context context, String intf){ 159 | return getString(context, PREF_KEY_TETHER_NETWORK + intf, null); 160 | } 161 | 162 | public static void setTetherNetwork(Context context, String intf, String network){ 163 | setString(context, PREF_KEY_TETHER_NETWORK + intf, network); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/org/ethack/orwall/lib/Util.java: -------------------------------------------------------------------------------- 1 | package org.ethack.orwall.lib; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.util.Log; 7 | 8 | import org.sufficientlysecure.rootcommands.Shell; 9 | import org.sufficientlysecure.rootcommands.command.SimpleCommand; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.util.concurrent.TimeoutException; 14 | 15 | public class Util { 16 | public static boolean isOrbotInstalled(Context context) { 17 | try { 18 | PackageManager pm = context.getPackageManager(); 19 | pm.getPackageInfo(Constants.ORBOT_APP_NAME, PackageManager.GET_ACTIVITIES); 20 | return true; 21 | } catch (PackageManager.NameNotFoundException e) { 22 | return false; 23 | } 24 | } 25 | 26 | public static int getOrbotUID(Context context){ 27 | try { 28 | PackageManager pm = context.getPackageManager(); 29 | return pm.getApplicationInfo(Constants.ORBOT_APP_NAME, 0).uid; 30 | } catch (PackageManager.NameNotFoundException e) { 31 | return 0; 32 | } 33 | } 34 | 35 | /** 36 | * Apply or remove rules for captive portal detection. 37 | * Captive portal detection works with DNS and redirection detection. 38 | * Once the device is connected, Android will probe the network in order to get a page, located on Google servers. 39 | * If it can connect to it, this means we're not in a captive network; otherwise, it will prompt for network login. 40 | * @param status boolean, true if we want to enable this probe. 41 | * @param context application context 42 | */ 43 | public static void enableCaptiveDetection(boolean status, Context context) { 44 | // TODO: find a way to disable it on android <4.4 45 | // TODO: we may want to get some setting writer directly through the API. 46 | // This seems to be done with a System app only. orWall may become a system app. 47 | if (Build.VERSION.SDK_INT > 18) { 48 | 49 | String CMD; 50 | if (status) { 51 | CMD = new File(context.getDir("bin", 0), "activate_portal.sh").getAbsolutePath(); 52 | } else { 53 | CMD = new File(context.getDir("bin", 0), "deactivate_portal.sh").getAbsolutePath(); 54 | } 55 | Shell shell = null; 56 | try { 57 | shell = Shell.startRootShell(); 58 | } catch (IOException e) { 59 | Log.e("Shell", "Unable to get shell"); 60 | } 61 | 62 | if (shell != null) { 63 | SimpleCommand command = new SimpleCommand(CMD); 64 | try { 65 | shell.add(command).waitForFinish(); 66 | } catch (IOException e) { 67 | Log.e("Shell", "IO Error"); 68 | } catch (TimeoutException e) { 69 | Log.e("Shell", "Timeout"); 70 | } finally { 71 | try { 72 | shell.close(); 73 | } catch (IOException e) { 74 | 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/Mount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks (RootTools) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.sufficientlysecure.rootcommands; 19 | 20 | import java.io.File; 21 | import java.util.Arrays; 22 | import java.util.HashSet; 23 | import java.util.Set; 24 | 25 | public class Mount { 26 | protected final File mDevice; 27 | protected final File mMountPoint; 28 | protected final String mType; 29 | protected final Set mFlags; 30 | 31 | Mount(File device, File path, String type, String flagsStr) { 32 | mDevice = device; 33 | mMountPoint = path; 34 | mType = type; 35 | mFlags = new HashSet(Arrays.asList(flagsStr.split(","))); 36 | } 37 | 38 | public File getDevice() { 39 | return mDevice; 40 | } 41 | 42 | public File getMountPoint() { 43 | return mMountPoint; 44 | } 45 | 46 | public String getType() { 47 | return mType; 48 | } 49 | 50 | public Set getFlags() { 51 | return mFlags; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return String.format("%s on %s type %s %s", mDevice, mMountPoint, mType, mFlags); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/Remounter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks (RootTools) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.sufficientlysecure.rootcommands; 19 | 20 | import java.io.File; 21 | import java.io.FileReader; 22 | import java.io.IOException; 23 | import java.io.LineNumberReader; 24 | import java.util.ArrayList; 25 | import java.util.Locale; 26 | 27 | import org.sufficientlysecure.rootcommands.command.SimpleCommand; 28 | import org.sufficientlysecure.rootcommands.util.Log; 29 | 30 | //no modifier, this means it is package-private. Only our internal classes can use this. 31 | class Remounter { 32 | 33 | private Shell shell; 34 | 35 | public Remounter(Shell shell) { 36 | super(); 37 | this.shell = shell; 38 | } 39 | 40 | /** 41 | * This will take a path, which can contain the file name as well, and attempt to remount the 42 | * underlying partition. 43 | *

44 | * For example, passing in the following string: 45 | * "/system/bin/some/directory/that/really/would/never/exist" will result in /system ultimately 46 | * being remounted. However, keep in mind that the longer the path you supply, the more work 47 | * this has to do, and the slower it will run. 48 | * 49 | * @param file 50 | * file path 51 | * @param mountType 52 | * mount type: pass in RO (Read only) or RW (Read Write) 53 | * @return a boolean which indicates whether or not the partition has been 54 | * remounted as specified. 55 | */ 56 | protected boolean remount(String file, String mountType) { 57 | 58 | // if the path has a trailing slash get rid of it. 59 | if (file.endsWith("/") && !file.equals("/")) { 60 | file = file.substring(0, file.lastIndexOf("/")); 61 | } 62 | // Make sure that what we are trying to remount is in the mount list. 63 | boolean foundMount = false; 64 | while (!foundMount) { 65 | try { 66 | for (Mount mount : getMounts()) { 67 | Log.d(RootCommands.TAG, mount.getMountPoint().toString()); 68 | 69 | if (file.equals(mount.getMountPoint().toString())) { 70 | foundMount = true; 71 | break; 72 | } 73 | } 74 | } catch (Exception e) { 75 | Log.e(RootCommands.TAG, "Exception", e); 76 | return false; 77 | } 78 | if (!foundMount) { 79 | try { 80 | file = (new File(file).getParent()).toString(); 81 | } catch (Exception e) { 82 | Log.e(RootCommands.TAG, "Exception", e); 83 | return false; 84 | } 85 | } 86 | } 87 | Mount mountPoint = findMountPointRecursive(file); 88 | 89 | Log.d(RootCommands.TAG, "Remounting " + mountPoint.getMountPoint().getAbsolutePath() 90 | + " as " + mountType.toLowerCase(Locale.US)); 91 | final boolean isMountMode = mountPoint.getFlags().contains(mountType.toLowerCase(Locale.US)); 92 | 93 | if (!isMountMode) { 94 | // grab an instance of the internal class 95 | try { 96 | SimpleCommand command = new SimpleCommand("busybox mount -o remount," 97 | + mountType.toLowerCase(Locale.US) + " " + mountPoint.getDevice().getAbsolutePath() 98 | + " " + mountPoint.getMountPoint().getAbsolutePath(), 99 | "toolbox mount -o remount," + mountType.toLowerCase(Locale.US) + " " 100 | + mountPoint.getDevice().getAbsolutePath() + " " 101 | + mountPoint.getMountPoint().getAbsolutePath(), "mount -o remount," 102 | + mountType.toLowerCase(Locale.US) + " " 103 | + mountPoint.getDevice().getAbsolutePath() + " " 104 | + mountPoint.getMountPoint().getAbsolutePath(), 105 | "/system/bin/toolbox mount -o remount," + mountType.toLowerCase(Locale.US) + " " 106 | + mountPoint.getDevice().getAbsolutePath() + " " 107 | + mountPoint.getMountPoint().getAbsolutePath()); 108 | 109 | // execute on shell 110 | shell.add(command).waitForFinish(); 111 | 112 | } catch (Exception e) { 113 | } 114 | 115 | mountPoint = findMountPointRecursive(file); 116 | } 117 | 118 | if (mountPoint != null) { 119 | Log.d(RootCommands.TAG, mountPoint.getFlags() + " AND " + mountType.toLowerCase(Locale.US)); 120 | if (mountPoint.getFlags().contains(mountType.toLowerCase(Locale.US))) { 121 | Log.d(RootCommands.TAG, mountPoint.getFlags().toString()); 122 | return true; 123 | } else { 124 | Log.d(RootCommands.TAG, mountPoint.getFlags().toString()); 125 | } 126 | } else { 127 | Log.d(RootCommands.TAG, "mountPoint is null"); 128 | } 129 | return false; 130 | } 131 | 132 | private Mount findMountPointRecursive(String file) { 133 | try { 134 | ArrayList mounts = getMounts(); 135 | for (File path = new File(file); path != null;) { 136 | for (Mount mount : mounts) { 137 | if (mount.getMountPoint().equals(path)) { 138 | return mount; 139 | } 140 | } 141 | } 142 | return null; 143 | } catch (IOException e) { 144 | throw new RuntimeException(e); 145 | } catch (Exception e) { 146 | Log.e(RootCommands.TAG, "Exception", e); 147 | } 148 | return null; 149 | } 150 | 151 | /** 152 | * This will return an ArrayList of the class Mount. The class mount contains the following 153 | * property's: device mountPoint type flags 154 | *

155 | * These will provide you with any information you need to work with the mount points. 156 | * 157 | * @return ArrayList an ArrayList of the class Mount. 158 | * @throws Exception 159 | * if we cannot return the mount points. 160 | */ 161 | protected static ArrayList getMounts() throws Exception { 162 | 163 | final String tempFile = "/data/local/RootToolsMounts"; 164 | 165 | // copy /proc/mounts to tempfile. Directly reading it does not work on 4.3 166 | Shell shell = Shell.startRootShell(); 167 | Toolbox tb = new Toolbox(shell); 168 | tb.copyFile("/proc/mounts", tempFile, false, false); 169 | tb.setFilePermissions(tempFile, "777"); 170 | shell.close(); 171 | 172 | LineNumberReader lnr = null; 173 | lnr = new LineNumberReader(new FileReader(tempFile)); 174 | String line; 175 | ArrayList mounts = new ArrayList(); 176 | while ((line = lnr.readLine()) != null) { 177 | 178 | Log.d(RootCommands.TAG, line); 179 | 180 | String[] fields = line.split(" "); 181 | mounts.add(new Mount(new File(fields[0]), // device 182 | new File(fields[1]), // mountPoint 183 | fields[2], // fstype 184 | fields[3] // flags 185 | )); 186 | } 187 | lnr.close(); 188 | 189 | return mounts; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/RootCommands.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sufficientlysecure.rootcommands; 18 | 19 | import org.sufficientlysecure.rootcommands.util.Log; 20 | 21 | public class RootCommands { 22 | public static boolean DEBUG = false; 23 | public static int DEFAULT_TIMEOUT = 10000; 24 | 25 | public static final String TAG = "RootCommands"; 26 | 27 | /** 28 | * General method to check if user has su binary and accepts root access for this program! 29 | * 30 | * @return true if everything worked 31 | */ 32 | public static boolean rootAccessGiven() { 33 | boolean rootAccess = false; 34 | 35 | try { 36 | Shell rootShell = Shell.startRootShell(); 37 | 38 | Toolbox tb = new Toolbox(rootShell); 39 | if (tb.isRootAccessGiven()) { 40 | rootAccess = true; 41 | } 42 | 43 | rootShell.close(); 44 | } catch (Exception e) { 45 | Log.e(TAG, "Problem while checking for root access!", e); 46 | } 47 | 48 | return rootAccess; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/Shell.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks, Jeremy Lakeman (RootTools) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.sufficientlysecure.rootcommands; 19 | 20 | import java.io.BufferedReader; 21 | import java.io.Closeable; 22 | import java.io.DataOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStreamReader; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import org.sufficientlysecure.rootcommands.command.Command; 29 | import org.sufficientlysecure.rootcommands.util.Log; 30 | import org.sufficientlysecure.rootcommands.util.RootAccessDeniedException; 31 | import org.sufficientlysecure.rootcommands.util.Utils; 32 | 33 | public class Shell implements Closeable { 34 | private final Process shellProcess; 35 | private final BufferedReader stdOutErr; 36 | private final DataOutputStream outputStream; 37 | private final List commands = new ArrayList(); 38 | private boolean close = false; 39 | 40 | private static final String LD_LIBRARY_PATH = System.getenv("LD_LIBRARY_PATH"); 41 | private static final String token = "F*D^W@#FGF"; 42 | 43 | /** 44 | * Start root shell 45 | * 46 | * @param customEnv 47 | * @param baseDirectory 48 | * @return 49 | * @throws IOException 50 | */ 51 | public static Shell startRootShell(ArrayList customEnv, String baseDirectory) 52 | throws IOException, RootAccessDeniedException { 53 | Log.d(RootCommands.TAG, "Starting Root Shell!"); 54 | 55 | // On some versions of Android (ICS) LD_LIBRARY_PATH is unset when using su 56 | // We need to pass LD_LIBRARY_PATH over su for some commands to work correctly. 57 | if (customEnv == null) { 58 | customEnv = new ArrayList(); 59 | } 60 | customEnv.add("LD_LIBRARY_PATH=" + LD_LIBRARY_PATH); 61 | 62 | Shell shell = new Shell(Utils.getSuPath(), customEnv, baseDirectory); 63 | 64 | return shell; 65 | } 66 | 67 | /** 68 | * Start root shell without custom environment and base directory 69 | * 70 | * @return 71 | * @throws IOException 72 | */ 73 | public static Shell startRootShell() throws IOException, RootAccessDeniedException { 74 | return startRootShell(null, null); 75 | } 76 | 77 | /** 78 | * Start default sh shell 79 | * 80 | * @param customEnv 81 | * @param baseDirectory 82 | * @return 83 | * @throws IOException 84 | */ 85 | public static Shell startShell(ArrayList customEnv, String baseDirectory) 86 | throws IOException { 87 | Log.d(RootCommands.TAG, "Starting Shell!"); 88 | Shell shell = new Shell("sh", customEnv, baseDirectory); 89 | return shell; 90 | } 91 | 92 | /** 93 | * Start default sh shell without custom environment and base directory 94 | * 95 | * @return 96 | * @throws IOException 97 | */ 98 | public static Shell startShell() throws IOException { 99 | return startShell(null, null); 100 | } 101 | 102 | /** 103 | * Start custom shell defined by shellPath 104 | * 105 | * @param shellPath 106 | * @param customEnv 107 | * @param baseDirectory 108 | * @return 109 | * @throws IOException 110 | */ 111 | public static Shell startCustomShell(String shellPath, ArrayList customEnv, 112 | String baseDirectory) throws IOException { 113 | Log.d(RootCommands.TAG, "Starting Custom Shell!"); 114 | Shell shell = new Shell(shellPath, customEnv, baseDirectory); 115 | 116 | return shell; 117 | } 118 | 119 | /** 120 | * Start custom shell without custom environment and base directory 121 | * 122 | * @param shellPath 123 | * @return 124 | * @throws IOException 125 | */ 126 | public static Shell startCustomShell(String shellPath) throws IOException { 127 | return startCustomShell(shellPath, null, null); 128 | } 129 | 130 | private Shell(String shell, ArrayList customEnv, String baseDirectory) 131 | throws IOException, RootAccessDeniedException { 132 | Log.d(RootCommands.TAG, "Starting shell: " + shell); 133 | 134 | // start shell process! 135 | shellProcess = Utils.runWithEnv(shell, customEnv, baseDirectory); 136 | 137 | // StdErr is redirected to StdOut, defined in Command.getCommand() 138 | stdOutErr = new BufferedReader(new InputStreamReader(shellProcess.getInputStream())); 139 | outputStream = new DataOutputStream(shellProcess.getOutputStream()); 140 | 141 | outputStream.write("echo Started\n".getBytes()); 142 | outputStream.flush(); 143 | 144 | while (true) { 145 | String line = stdOutErr.readLine(); 146 | if (line == null) 147 | throw new RootAccessDeniedException( 148 | "stdout line is null! Access was denied or this executeable is not a shell!"); 149 | if ("".equals(line)) 150 | continue; 151 | if ("Started".equals(line)) 152 | break; 153 | 154 | destroyShellProcess(); 155 | throw new IOException("Unable to start shell, unexpected output \"" + line + "\""); 156 | } 157 | 158 | new Thread(inputRunnable, "Shell Input").start(); 159 | new Thread(outputRunnable, "Shell Output").start(); 160 | } 161 | 162 | private Runnable inputRunnable = new Runnable() { 163 | public void run() { 164 | try { 165 | writeCommands(); 166 | } catch (IOException e) { 167 | Log.e(RootCommands.TAG, "IO Exception", e); 168 | } 169 | } 170 | }; 171 | 172 | private Runnable outputRunnable = new Runnable() { 173 | public void run() { 174 | try { 175 | readOutput(); 176 | } catch (IOException e) { 177 | Log.e(RootCommands.TAG, "IOException", e); 178 | } catch (InterruptedException e) { 179 | Log.e(RootCommands.TAG, "InterruptedException", e); 180 | } 181 | } 182 | }; 183 | 184 | /** 185 | * Destroy shell process considering that the process could already be terminated 186 | */ 187 | private void destroyShellProcess() { 188 | try { 189 | // Yes, this really is the way to check if the process is 190 | // still running. 191 | shellProcess.exitValue(); 192 | } catch (IllegalThreadStateException e) { 193 | // Only call destroy() if the process is still running; 194 | // Calling it for a terminated process will not crash, but 195 | // (starting with at least ICS/4.0) spam the log with INFO 196 | // messages ala "Failed to destroy process" and "kill 197 | // failed: ESRCH (No such process)". 198 | shellProcess.destroy(); 199 | } 200 | 201 | Log.d(RootCommands.TAG, "Shell destroyed"); 202 | } 203 | 204 | /** 205 | * Writes queued commands one after another into the opened shell. After an execution a token is 206 | * written to seperate command output on read 207 | * 208 | * @throws IOException 209 | */ 210 | private void writeCommands() throws IOException { 211 | try { 212 | int commandIndex = 0; 213 | while (true) { 214 | DataOutputStream out; 215 | synchronized (commands) { 216 | while (!close && commandIndex >= commands.size()) { 217 | commands.wait(); 218 | } 219 | out = this.outputStream; 220 | } 221 | if (commandIndex < commands.size()) { 222 | Command next = commands.get(commandIndex); 223 | next.writeCommand(out); 224 | String line = "\necho " + token + " " + commandIndex + " $?\n"; 225 | out.write(line.getBytes()); 226 | out.flush(); 227 | commandIndex++; 228 | } else if (close) { 229 | out.write("\nexit 0\n".getBytes()); 230 | out.flush(); 231 | Log.d(RootCommands.TAG, "Closing shell"); 232 | shellProcess.waitFor(); 233 | out.close(); 234 | return; 235 | } else { 236 | Thread.sleep(50); 237 | } 238 | } 239 | } catch (InterruptedException e) { 240 | Log.e(RootCommands.TAG, "interrupted while writing command", e); 241 | } 242 | } 243 | 244 | /** 245 | * Reads output line by line, seperated by token written after every command 246 | * 247 | * @throws IOException 248 | * @throws InterruptedException 249 | */ 250 | private void readOutput() throws IOException, InterruptedException { 251 | Command command = null; 252 | 253 | // index of current command 254 | int commandIndex = 0; 255 | while (true) { 256 | String lineStdOut = stdOutErr.readLine(); 257 | 258 | // terminate on EOF 259 | if (lineStdOut == null) 260 | break; 261 | 262 | if (command == null) { 263 | 264 | // break on close after last command 265 | if (commandIndex >= commands.size()) { 266 | if (close) 267 | break; 268 | continue; 269 | } 270 | 271 | // get current command 272 | command = commands.get(commandIndex); 273 | } 274 | 275 | int pos = lineStdOut.indexOf(token); 276 | if (pos > 0) { 277 | command.processOutput(lineStdOut.substring(0, pos)); 278 | } 279 | if (pos >= 0) { 280 | lineStdOut = lineStdOut.substring(pos); 281 | String fields[] = lineStdOut.split(" "); 282 | int id = Integer.parseInt(fields[1]); 283 | if (id == commandIndex) { 284 | command.setExitCode(Integer.parseInt(fields[2])); 285 | 286 | // go to next command 287 | commandIndex++; 288 | command = null; 289 | continue; 290 | } 291 | } 292 | command.processOutput(lineStdOut); 293 | } 294 | Log.d(RootCommands.TAG, "Read all output"); 295 | shellProcess.waitFor(); 296 | stdOutErr.close(); 297 | destroyShellProcess(); 298 | 299 | while (commandIndex < commands.size()) { 300 | if (command == null) { 301 | command = commands.get(commandIndex); 302 | } 303 | command.terminated("Unexpected Termination!"); 304 | commandIndex++; 305 | command = null; 306 | } 307 | } 308 | 309 | /** 310 | * Add command to shell queue 311 | * 312 | * @param command 313 | * @return 314 | * @throws IOException 315 | */ 316 | public Command add(Command command) throws IOException { 317 | if (close) 318 | throw new IOException("Unable to add commands to a closed shell"); 319 | synchronized (commands) { 320 | commands.add(command); 321 | // set shell on the command object, to know where the command is running on 322 | command.addedToShell(this, (commands.size() - 1)); 323 | commands.notifyAll(); 324 | } 325 | 326 | return command; 327 | } 328 | 329 | /** 330 | * Close shell 331 | * 332 | * @throws IOException 333 | */ 334 | public void close() throws IOException { 335 | synchronized (commands) { 336 | this.close = true; 337 | commands.notifyAll(); 338 | } 339 | } 340 | 341 | /** 342 | * Returns number of queued commands 343 | * 344 | * @return 345 | */ 346 | public int getCommandsSize() { 347 | return commands.size(); 348 | } 349 | 350 | } -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/SystemCommands.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sufficientlysecure.rootcommands; 18 | 19 | import android.annotation.TargetApi; 20 | import android.content.ContentResolver; 21 | import android.content.Context; 22 | import android.location.LocationManager; 23 | import android.os.PowerManager; 24 | import android.provider.Settings; 25 | 26 | /** 27 | * This methods work when the apk is installed as a system app (under /system/app) 28 | */ 29 | public class SystemCommands { 30 | Context context; 31 | 32 | public SystemCommands(Context context) { 33 | super(); 34 | this.context = context; 35 | } 36 | 37 | /** 38 | * Get GPS status 39 | * 40 | * @return 41 | */ 42 | public boolean getGPS() { 43 | return ((LocationManager) context.getSystemService(Context.LOCATION_SERVICE)) 44 | .isProviderEnabled(LocationManager.GPS_PROVIDER); 45 | } 46 | 47 | /** 48 | * Enable/Disable GPS 49 | * 50 | * @param value 51 | */ 52 | @TargetApi(8) 53 | public void setGPS(boolean value) { 54 | ContentResolver localContentResolver = context.getContentResolver(); 55 | Settings.Secure.setLocationProviderEnabled(localContentResolver, 56 | LocationManager.GPS_PROVIDER, value); 57 | } 58 | 59 | /** 60 | * TODO: Not ready yet 61 | */ 62 | @TargetApi(8) 63 | public void reboot() { 64 | PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 65 | pm.reboot("recovery"); 66 | pm.reboot(null); 67 | 68 | // not working: 69 | // reboot(null); 70 | } 71 | 72 | /** 73 | * Reboot the device immediately, passing 'reason' (may be null) to the underlying __reboot 74 | * system call. Should not return. 75 | * 76 | * Taken from com.android.server.PowerManagerService.reboot 77 | */ 78 | // public void reboot(String reason) { 79 | // 80 | // // final String finalReason = reason; 81 | // Runnable runnable = new Runnable() { 82 | // public void run() { 83 | // synchronized (this) { 84 | // // ShutdownThread.reboot(mContext, finalReason, false); 85 | // try { 86 | // Class clazz = Class.forName("com.android.internal.app.ShutdownThread"); 87 | // 88 | // // if (mReboot) { 89 | // Method method = clazz.getMethod("reboot", Context.class, String.class, 90 | // Boolean.TYPE); 91 | // method.invoke(null, context, null, false); 92 | // 93 | // // if (mReboot) { 94 | // // Method method = clazz.getMethod("reboot", Context.class, String.class, 95 | // // Boolean.TYPE); 96 | // // method.invoke(null, mContext, mReason, mConfirm); 97 | // // } else { 98 | // // Method method = clazz.getMethod("shutdown", Context.class, Boolean.TYPE); 99 | // // method.invoke(null, mContext, mConfirm); 100 | // // } 101 | // } catch (Exception e) { 102 | // e.printStackTrace(); 103 | // } 104 | // } 105 | // 106 | // } 107 | // }; 108 | // // ShutdownThread must run on a looper capable of displaying the UI. 109 | // mHandler.post(runnable); 110 | // 111 | // // PowerManager.reboot() is documented not to return so just wait for the inevitable. 112 | // // synchronized (runnable) { 113 | // // while (true) { 114 | // // try { 115 | // // runnable.wait(); 116 | // // } catch (InterruptedException e) { 117 | // // } 118 | // // } 119 | // // } 120 | // } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/command/Command.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks, Jeremy Lakeman (RootTools) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.sufficientlysecure.rootcommands.command; 19 | 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | import java.util.concurrent.TimeoutException; 23 | 24 | import org.sufficientlysecure.rootcommands.RootCommands; 25 | import org.sufficientlysecure.rootcommands.Shell; 26 | import org.sufficientlysecure.rootcommands.util.BrokenBusyboxException; 27 | import org.sufficientlysecure.rootcommands.util.Log; 28 | 29 | public abstract class Command { 30 | final String command[]; 31 | boolean finished = false; 32 | boolean brokenBusyboxDetected = false; 33 | int exitCode; 34 | int id; 35 | int timeout = RootCommands.DEFAULT_TIMEOUT; 36 | Shell shell = null; 37 | 38 | public Command(String... command) { 39 | this.command = command; 40 | } 41 | 42 | public Command(int timeout, String... command) { 43 | this.command = command; 44 | this.timeout = timeout; 45 | } 46 | 47 | /** 48 | * This is called from Shell after adding it 49 | * 50 | * @param shell 51 | * @param id 52 | */ 53 | public void addedToShell(Shell shell, int id) { 54 | this.shell = shell; 55 | this.id = id; 56 | } 57 | 58 | /** 59 | * Gets command string executed on the shell 60 | * 61 | * @return 62 | */ 63 | public String getCommand() { 64 | StringBuilder sb = new StringBuilder(); 65 | for (int i = 0; i < command.length; i++) { 66 | // redirect stderr to stdout 67 | sb.append(command[i] + " 2>&1"); 68 | sb.append('\n'); 69 | } 70 | Log.d(RootCommands.TAG, "Sending command(s): " + sb.toString()); 71 | return sb.toString(); 72 | } 73 | 74 | public void writeCommand(OutputStream out) throws IOException { 75 | out.write(getCommand().getBytes()); 76 | } 77 | 78 | public void processOutput(String line) { 79 | Log.d(RootCommands.TAG, "ID: " + id + ", Output: " + line); 80 | 81 | /* 82 | * Try to detect broken toolbox/busybox binaries (see 83 | * https://code.google.com/p/busybox-android/issues/detail?id=1) 84 | * 85 | * It is giving "Value too large for defined data type" on certain file operations (e.g. ls 86 | * and chown) in certain directories (e.g. /data/data) 87 | */ 88 | if (line.contains("Value too large for defined data type")) { 89 | Log.e(RootCommands.TAG, "Busybox is broken with high probability due to line: " + line); 90 | brokenBusyboxDetected = true; 91 | } 92 | 93 | // now execute specific output parsing 94 | output(id, line); 95 | } 96 | 97 | public abstract void output(int id, String line); 98 | 99 | public void processAfterExecution(int exitCode) { 100 | Log.d(RootCommands.TAG, "ID: " + id + ", ExitCode: " + exitCode); 101 | 102 | afterExecution(id, exitCode); 103 | } 104 | 105 | public abstract void afterExecution(int id, int exitCode); 106 | 107 | public void commandFinished(int id) { 108 | Log.d(RootCommands.TAG, "Command " + id + " finished."); 109 | } 110 | 111 | public void setExitCode(int code) { 112 | synchronized (this) { 113 | exitCode = code; 114 | finished = true; 115 | commandFinished(id); 116 | this.notifyAll(); 117 | } 118 | } 119 | 120 | /** 121 | * Close the shell 122 | * 123 | * @param reason 124 | */ 125 | public void terminate(String reason) { 126 | try { 127 | shell.close(); 128 | Log.d(RootCommands.TAG, "Terminating the shell."); 129 | terminated(reason); 130 | } catch (IOException e) { 131 | } 132 | } 133 | 134 | public void terminated(String reason) { 135 | setExitCode(-1); 136 | Log.d(RootCommands.TAG, "Command " + id + " did not finish, because of " + reason); 137 | } 138 | 139 | /** 140 | * Waits for this command to finish and forwards exitCode into afterExecution method 141 | * 142 | * @param timeout 143 | * @throws TimeoutException 144 | * @throws BrokenBusyboxException 145 | */ 146 | public void waitForFinish() throws TimeoutException, BrokenBusyboxException { 147 | synchronized (this) { 148 | while (!finished) { 149 | try { 150 | this.wait(timeout); 151 | } catch (InterruptedException e) { 152 | Log.e(RootCommands.TAG, "InterruptedException in waitForFinish()", e); 153 | } 154 | 155 | if (!finished) { 156 | finished = true; 157 | terminate("Timeout"); 158 | throw new TimeoutException("Timeout has occurred."); 159 | } 160 | } 161 | 162 | if (brokenBusyboxDetected) { 163 | throw new BrokenBusyboxException(); 164 | } 165 | 166 | processAfterExecution(exitCode); 167 | } 168 | } 169 | 170 | } -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/command/ExecutableCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sufficientlysecure.rootcommands.command; 18 | 19 | import java.io.File; 20 | 21 | import android.annotation.SuppressLint; 22 | import android.content.Context; 23 | import android.os.Build; 24 | 25 | public abstract class ExecutableCommand extends Command { 26 | public static final String EXECUTABLE_PREFIX = "lib"; 27 | public static final String EXECUTABLE_SUFFIX = "_exec.so"; 28 | 29 | /** 30 | * This class provides a way to use your own binaries! 31 | * 32 | * Include your own executables, renamed from * to lib*_exec.so, in your libs folder under the 33 | * architecture directories. Now they will be deployed by Android the same way libraries are 34 | * deployed! 35 | * 36 | * See README for more information how to use your own executables! 37 | * 38 | * @param context 39 | * @param executableName 40 | * @param parameters 41 | */ 42 | public ExecutableCommand(Context context, String executableName, String parameters) { 43 | super(getLibDirectory(context) + File.separator + EXECUTABLE_PREFIX + executableName 44 | + EXECUTABLE_SUFFIX + " " + parameters); 45 | } 46 | 47 | /** 48 | * Get full path to lib directory of app 49 | * 50 | * @return dir as String 51 | */ 52 | @SuppressLint("NewApi") 53 | private static String getLibDirectory(Context context) { 54 | if (Build.VERSION.SDK_INT >= 9) { 55 | return context.getApplicationInfo().nativeLibraryDir; 56 | } else { 57 | return context.getApplicationInfo().dataDir + File.separator + "lib"; 58 | } 59 | } 60 | 61 | public abstract void output(int id, String line); 62 | 63 | public abstract void afterExecution(int id, int exitCode); 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sufficientlysecure.rootcommands.command; 18 | 19 | public class SimpleCommand extends Command { 20 | private StringBuilder sb = new StringBuilder(); 21 | 22 | public SimpleCommand(String... command) { 23 | super(command); 24 | } 25 | 26 | @Override 27 | public void output(int id, String line) { 28 | sb.append(line).append('\n'); 29 | } 30 | 31 | @Override 32 | public void afterExecution(int id, int exitCode) { 33 | } 34 | 35 | public String getOutput() { 36 | return sb.toString(); 37 | } 38 | 39 | public int getExitCode() { 40 | return exitCode; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/command/SimpleExecutableCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sufficientlysecure.rootcommands.command; 18 | 19 | import android.content.Context; 20 | 21 | public class SimpleExecutableCommand extends ExecutableCommand { 22 | private StringBuilder sb = new StringBuilder(); 23 | 24 | public SimpleExecutableCommand(Context context, String executableName, String parameters) { 25 | super(context, executableName, parameters); 26 | } 27 | 28 | @Override 29 | public void output(int id, String line) { 30 | sb.append(line).append('\n'); 31 | } 32 | 33 | @Override 34 | public void afterExecution(int id, int exitCode) { 35 | } 36 | 37 | public String getOutput() { 38 | return sb.toString(); 39 | } 40 | 41 | public int getExitCode() { 42 | return exitCode; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/util/BrokenBusyboxException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sufficientlysecure.rootcommands.util; 18 | 19 | import java.io.IOException; 20 | 21 | public class BrokenBusyboxException extends IOException { 22 | private static final long serialVersionUID = 8337358201589488409L; 23 | 24 | public BrokenBusyboxException() { 25 | super(); 26 | } 27 | 28 | public BrokenBusyboxException(String detailMessage) { 29 | super(detailMessage); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/util/Log.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sufficientlysecure.rootcommands.util; 18 | 19 | import org.sufficientlysecure.rootcommands.RootCommands; 20 | 21 | /** 22 | * Wraps Android Logging to enable or disable debug output using Constants 23 | * 24 | */ 25 | public final class Log { 26 | 27 | public static void v(String tag, String msg) { 28 | if (RootCommands.DEBUG) { 29 | android.util.Log.v(tag, msg); 30 | } 31 | } 32 | 33 | public static void v(String tag, String msg, Throwable tr) { 34 | if (RootCommands.DEBUG) { 35 | android.util.Log.v(tag, msg, tr); 36 | } 37 | } 38 | 39 | public static void d(String tag, String msg) { 40 | if (RootCommands.DEBUG) { 41 | android.util.Log.d(tag, msg); 42 | } 43 | } 44 | 45 | public static void d(String tag, String msg, Throwable tr) { 46 | if (RootCommands.DEBUG) { 47 | android.util.Log.d(tag, msg, tr); 48 | } 49 | } 50 | 51 | public static void i(String tag, String msg) { 52 | if (RootCommands.DEBUG) { 53 | android.util.Log.i(tag, msg); 54 | } 55 | } 56 | 57 | public static void i(String tag, String msg, Throwable tr) { 58 | if (RootCommands.DEBUG) { 59 | android.util.Log.i(tag, msg, tr); 60 | } 61 | } 62 | 63 | public static void w(String tag, String msg) { 64 | android.util.Log.w(tag, msg); 65 | } 66 | 67 | public static void w(String tag, String msg, Throwable tr) { 68 | android.util.Log.w(tag, msg, tr); 69 | } 70 | 71 | public static void w(String tag, Throwable tr) { 72 | android.util.Log.w(tag, tr); 73 | } 74 | 75 | public static void e(String tag, String msg) { 76 | android.util.Log.e(tag, msg); 77 | } 78 | 79 | public static void e(String tag, String msg, Throwable tr) { 80 | android.util.Log.e(tag, msg, tr); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/util/RootAccessDeniedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sufficientlysecure.rootcommands.util; 18 | 19 | import java.io.IOException; 20 | 21 | public class RootAccessDeniedException extends IOException { 22 | private static final long serialVersionUID = 9088998884166225540L; 23 | 24 | public RootAccessDeniedException() { 25 | super(); 26 | } 27 | 28 | public RootAccessDeniedException(String detailMessage) { 29 | super(detailMessage); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/util/UnsupportedArchitectureException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sufficientlysecure.rootcommands.util; 18 | 19 | public class UnsupportedArchitectureException extends Exception { 20 | private static final long serialVersionUID = 7826528799780001655L; 21 | 22 | public UnsupportedArchitectureException() { 23 | super(); 24 | } 25 | 26 | public UnsupportedArchitectureException(String detailMessage) { 27 | super(detailMessage); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/org/sufficientlysecure/rootcommands/util/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Dominik Schürmann 3 | * Copyright (c) 2012 Michael Elsdörfer (Android Autostarts) 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Adam Shanks (RootTools) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package org.sufficientlysecure.rootcommands.util; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.util.ArrayList; 24 | import java.util.Map; 25 | 26 | import org.sufficientlysecure.rootcommands.RootCommands; 27 | 28 | public class Utils { 29 | /* 30 | * The emulator and ADP1 device both have a su binary in /system/xbin/su, but it doesn't allow 31 | * apps to use it (su app_29 $ su su: uid 10029 not allowed to su). 32 | * 33 | * Cyanogen used to have su in /system/bin/su, in newer versions it's a symlink to 34 | * /system/xbin/su. 35 | * 36 | * The Archos tablet has it in /data/bin/su, since they don't have write access to /system yet. 37 | */ 38 | static final String[] BinaryPlaces = { "/data/bin/", "/system/bin/", "/system/xbin/", "/sbin/", 39 | "/data/local/xbin/", "/data/local/bin/", "/system/sd/xbin/", "/system/bin/failsafe/", 40 | "/data/local/" }; 41 | 42 | /** 43 | * Determine the path of the su executable. 44 | * 45 | * Code from https://github.com/miracle2k/android-autostarts, use under Apache License was 46 | * agreed by Michael Elsdörfer 47 | */ 48 | public static String getSuPath() { 49 | for (String p : BinaryPlaces) { 50 | File su = new File(p + "su"); 51 | if (su.exists()) { 52 | Log.d(RootCommands.TAG, "su found at: " + p); 53 | return su.getAbsolutePath(); 54 | } else { 55 | Log.v(RootCommands.TAG, "No su in: " + p); 56 | } 57 | } 58 | Log.d(RootCommands.TAG, "No su found in a well-known location, " + "will just use \"su\"."); 59 | return "su"; 60 | } 61 | 62 | /** 63 | * This code is adapted from java.lang.ProcessBuilder.start(). 64 | * 65 | * The problem is that Android doesn't allow us to modify the map returned by 66 | * ProcessBuilder.environment(), even though the docstring indicates that it should. This is 67 | * because it simply returns the SystemEnvironment object that System.getenv() gives us. The 68 | * relevant portion in the source code is marked as "// android changed", so presumably it's not 69 | * the case in the original version of the Apache Harmony project. 70 | * 71 | * Note that simply passing the environment variables we want to Process.exec won't be good 72 | * enough, since that would override the environment we inherited completely. 73 | * 74 | * We needed to be able to set a CLASSPATH environment variable for our new process in order to 75 | * use the "app_process" command directly. Note: "app_process" takes arguments passed on to the 76 | * Dalvik VM as well; this might be an alternative way to set the class path. 77 | * 78 | * Code from https://github.com/miracle2k/android-autostarts, use under Apache License was 79 | * agreed by Michael Elsdörfer 80 | */ 81 | public static Process runWithEnv(String command, ArrayList customAddedEnv, 82 | String baseDirectory) throws IOException { 83 | 84 | Map environment = System.getenv(); 85 | String[] envArray = new String[environment.size() 86 | + (customAddedEnv != null ? customAddedEnv.size() : 0)]; 87 | int i = 0; 88 | for (Map.Entry entry : environment.entrySet()) { 89 | envArray[i++] = entry.getKey() + "=" + entry.getValue(); 90 | } 91 | if (customAddedEnv != null) { 92 | for (String entry : customAddedEnv) { 93 | envArray[i++] = entry; 94 | } 95 | } 96 | 97 | Process process; 98 | if (baseDirectory == null) { 99 | process = Runtime.getRuntime().exec(command, envArray, null); 100 | } else { 101 | process = Runtime.getRuntime().exec(command, envArray, new File(baseDirectory)); 102 | } 103 | return process; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/res/create_ico.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | convert $1 -resize 144x drawable-xxhdpi/$(basename $1) 3 | convert $1 -resize 96x drawable-xhdpi/$(basename $1) 4 | convert $1 -resize 72x drawable-hdpi/$(basename $1) 5 | convert $1 -resize 48x drawable-mdpi/$(basename $1) 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/android_unknown_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-hdpi/android_unknown_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-hdpi/ic_action_settings.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-hdpi/v2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/android_unknown_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-mdpi/android_unknown_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-mdpi/ic_action_settings.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-mdpi/v2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/android_unknown_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-xhdpi/android_unknown_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-xhdpi/ic_action_settings.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-xhdpi/v2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/android_unknown_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-xxhdpi/android_unknown_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-xxhdpi/ic_action_settings.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-xxhdpi/v2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/android_unknown_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthACKdotOrg/orWall/37b361e24d1f090e824ad025f7eba128ea6ce1e4/app/src/main/res/drawable-xxxhdpi/android_unknown_app.png -------------------------------------------------------------------------------- /app/src/main/res/layout/about.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 13 | 14 | 21 | 22 | 27 | 28 | 35 | 36 | 43 | 44 | 51 | 57 | 58 | 65 | 70 | 71 | 78 | 84 | 85 | 92 | 97 | 98 | 105 | 110 | 111 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_tabbed_main.xml: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_wizard.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/layout/advanced_connection.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 19 | 20 | 27 | 28 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 52 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_tabbed_apps.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 16 | 17 | 26 | 27 | 36 | 37 | 42 | 43 | 47 | 48 | 49 | 50 | 62 | 63 | 70 | 71 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_tabbed_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 17 | 18 | 27 | 28 | 37 | 38 | 39 | 44 | 45 | 50 | 51 | 59 | 60 | 68 | 69 | 77 | 78 | 81 | 85 | 90 | 91 | 96 | 97 | 103 | 104 | 110 | 111 | 117 | 118 | 124 | 125 | 131 | 132 | 133 | 134 | 139 | 140 |