From edc7bcdd26d2aecbe904796c216199c57facfbbe Mon Sep 17 00:00:00 2001 From: Jorrit Jongma Date: Mon, 29 Oct 2012 18:12:01 +0100 Subject: [PATCH] Initial commit --- libsuperuser/.classpath | 8 + libsuperuser/.project | 33 +++ libsuperuser/AndroidManifest.xml | 8 + libsuperuser/proguard-project.txt | 20 ++ libsuperuser/project.properties | 15 ++ .../chainfire/libsuperuser/Application.java | 79 ++++++ .../src/eu/chainfire/libsuperuser/Debug.java | 34 +++ .../src/eu/chainfire/libsuperuser/Shell.java | 247 ++++++++++++++++++ .../ShellOnMainThreadException.java | 12 + 9 files changed, 456 insertions(+) create mode 100644 libsuperuser/.classpath create mode 100644 libsuperuser/.project create mode 100644 libsuperuser/AndroidManifest.xml create mode 100644 libsuperuser/proguard-project.txt create mode 100644 libsuperuser/project.properties create mode 100644 libsuperuser/src/eu/chainfire/libsuperuser/Application.java create mode 100644 libsuperuser/src/eu/chainfire/libsuperuser/Debug.java create mode 100644 libsuperuser/src/eu/chainfire/libsuperuser/Shell.java create mode 100644 libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java diff --git a/libsuperuser/.classpath b/libsuperuser/.classpath new file mode 100644 index 0000000..a662f00 --- /dev/null +++ b/libsuperuser/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/libsuperuser/.project b/libsuperuser/.project new file mode 100644 index 0000000..c7bc2fd --- /dev/null +++ b/libsuperuser/.project @@ -0,0 +1,33 @@ + + + libsuperuser + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/libsuperuser/AndroidManifest.xml b/libsuperuser/AndroidManifest.xml new file mode 100644 index 0000000..a829063 --- /dev/null +++ b/libsuperuser/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/libsuperuser/proguard-project.txt b/libsuperuser/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/libsuperuser/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/libsuperuser/project.properties b/libsuperuser/project.properties new file mode 100644 index 0000000..b5ebc37 --- /dev/null +++ b/libsuperuser/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-4 +android.library=true diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/Application.java b/libsuperuser/src/eu/chainfire/libsuperuser/Application.java new file mode 100644 index 0000000..07feb48 --- /dev/null +++ b/libsuperuser/src/eu/chainfire/libsuperuser/Application.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +import android.content.Context; +import android.os.Handler; +import android.widget.Toast; + +/** + * Base application class to extend from, solving some issues with + * toasts and AsyncTasks you are likely to run into + */ +public class Application extends android.app.Application { + /** + * Shows a toast message + * + * @param context Any context belonging to this application + * @param message The message to show + */ + public static void toast(Context context, String message) { + // this is a static method so it is easier to call, + // as the context checking and casting is done for you + + if (context == null) return; + + if (!(context instanceof Application)) { + context = context.getApplicationContext(); + } + + if (context instanceof Application) { + final Context c = context; + final String m = message; + + ((Application)context).runInApplicationThread(new Runnable() { + @Override + public void run() { + Toast.makeText(c, m, Toast.LENGTH_LONG).show(); + } + }); + } + } + + private static Handler mApplicationHandler = new Handler(); + + /** + * Run a runnable in the main application thread + * + * @param r Runnable to run + */ + public void runInApplicationThread(Runnable r) { + mApplicationHandler.post(r); + } + + @Override + public void onCreate() { + super.onCreate(); + + try { + // workaround bug in AsyncTask, can show up (for example) when you toast from a service + // this makes sure AsyncTask's internal handler is created from the right (main) thread + Class.forName("android.os.AsyncTask"); + } catch (ClassNotFoundException e) { + } + } +} diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java b/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java new file mode 100644 index 0000000..eb6ceae --- /dev/null +++ b/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2012 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +import android.util.Log; + +/** + * Utility class that intentionally does nothing when not in debug mode + */ +public class Debug { + /** + * Log a message if we are in debug mode + * @param message The message to log + */ + public static void log(String message) { + if (BuildConfig.DEBUG) { + Log.d("libsuperuser", "[libsuperuser]" + (!message.startsWith("[") && !message.startsWith(" ") ? " " : "") + message); + } + } +} diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java new file mode 100644 index 0000000..e3c5571 --- /dev/null +++ b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2012 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import android.os.Looper; + +/** + * Class providing functionality to execute commands in a (root) shell + */ +public class Shell { + /** + * Runs commands using the supplied shell, and returns the output, or null in + * case of errors. + * + * Note that due to compatibility with older Android versions, + * wantSTDERR is not implemented using redirectErrorStream, but rather appended + * to the output. STDOUT and STDERR are thus not guaranteed to be in the correct + * order in the output. + * + * Note as well that this code will intentionally crash when run in debug mode + * from the main thread of the application. You should always execute shell + * commands from a background thread. + * + * When in debug mode, the code will also excessively log the commands passed to + * and the output returned from the shell. + * + * @param shell The shell to use for executing the commands + * @param commands The commands to execute + * @param wantSTDERR Return STDERR in the output ? + * @return Output of the commands, or null in case of an error + */ + public static List run(String shell, String[] commands, boolean wantSTDERR) { + if (BuildConfig.DEBUG) { + // check if we're running in the main thread, and if so, crash if we're in debug mode, + // to let the developer know attention is needed here. + + if (Looper.myLooper() == Looper.getMainLooper()) { + Debug.log("Application attempted to run a shell command from the main thread"); + throw new ShellOnMainThreadException(); + } + + Debug.log(String.format("[%s%%] START", shell.toUpperCase())); + } + + List res = new ArrayList(); + + try { + Process process = Runtime.getRuntime().exec(shell); + DataOutputStream STDIN = new DataOutputStream(process.getOutputStream()); + BufferedReader STDOUT = new BufferedReader(new InputStreamReader(process.getInputStream())); + BufferedReader STDERR = new BufferedReader(new InputStreamReader(process.getErrorStream())); + for (String write : commands) { + if (BuildConfig.DEBUG) Debug.log(String.format("[%s+] %s", shell.toUpperCase(), write)); + STDIN.writeBytes(write + "\n"); + STDIN.flush(); + } + STDIN.writeBytes("exit\n"); + STDIN.flush(); + + process.waitFor(); + if (process.exitValue() == 255) { + // in case of su, probably denied + return null; + } + + while (STDOUT.ready()) { + String read = STDOUT.readLine(); + if (BuildConfig.DEBUG) Debug.log(String.format("[%s-] %s", shell.toUpperCase(), read)); + res.add(read); + } + while (STDERR.ready()) { + String read = STDERR.readLine(); + if (BuildConfig.DEBUG) Debug.log(String.format("[%s*] %s", shell.toUpperCase(), read)); + if (wantSTDERR) res.add(read); + } + + process.destroy(); + } catch (IOException e) { + // shell probably not found + return null; + } catch (InterruptedException e) { + // this should really be re-thrown + return null; + } + + if (BuildConfig.DEBUG) Debug.log(String.format("[%s%%] END", shell.toUpperCase())); + return res; + } + + /** + * This class provides utility functions to easily execute commands using SH + */ + public static class SH { + /** + * Runs command and return output + * + * @param command The command to run + * @return Output of the command, or null in case of an error + */ + public static List run(String command) { + return Shell.run("sh", new String[] { command }, false); + } + + /** + * Runs commands and return output + * + * @param commands The commands to run + * @return Output of the commands, or null in case of an error + */ + public static List run(List commands) { + return Shell.run("sh", commands.toArray(new String[commands.size()]), false); + } + + /** + * Runs command and return output + * + * @param commands The commands to run + * @return Output of the commands, or null in case of an error + */ + public static List run(String[] commands) { + return Shell.run("sh", commands, false); + } + } + + /** + * This class provides utility functions to easily execute commands using SU + * (root shell), as well as detecting whether or not root is available, and + * if so which version. + */ + public static class SU { + /** + * Runs command as root (if available) and return output + * + * @param command The command to run + * @return Output of the command, or null if root isn't available or in case of an error + */ + public static List run(String command) { + return Shell.run("su", new String[] { command }, false); + } + + /** + * Runs commands as root (if available) and return output + * + * @param command The commands to run + * @return Output of the commands, or null if root isn't available or in case of an error + */ + public static List run(List commands) { + return Shell.run("su", commands.toArray(new String[commands.size()]), false); + } + + /** + * Runs commands as root (if available) and return output + * + * @param command The commands to run + * @return Output of the commands, or null if root isn't available or in case of an error + */ + public static List run(String[] commands) { + return Shell.run("su", commands, false); + } + + /** + * Detects whether or not superuser access is available, by checking the output + * of the "id" command if available, checking if a shell runs at all otherwise + * + * @return True if superuser access available + */ + public static boolean available() { + // this is only one of many ways this can be done + + List ret = run(new String[] { + "id", + "echo -EOC-" + }); + if (ret == null) return false; + + for (String line : ret) { + if (line.contains("uid=")) { + // id command is working, let's see if we are actually root + return line.contains("uid=0"); + } else if (line.contains("-EOC-")) { + // if we end up here, the id command isn't present, but at + // least the su commands starts some kind of shell, let's + // hope it has root priviliges - no way to know without + // additional native binaries + return true; + } + } + return false; + } + + /** + * Detects the version of the su binary installed (if any), if supported by the binary. + * Most binaries support two different version numbers, the public version that is + * displayed to users, and an internal version number that is used for version number + * comparisons. Returns null if su not available or retrieving the version isn't supported. + * + * Note that su binary version and GUI (APK) version can be completely different. + * + * @param internal Request human-readable version or application internal version + * @return String containing the su version or null + */ + public static String version(boolean internal) { + // we add an additional exit call, because the command + // line options are not available in all su versions, + // thus potentially launching a shell instead + + List ret = Shell.run("sh", new String[] { + internal ? "su -V" : "su -v", + "exit" + }, false); + if (ret == null) return null; + + for (String line : ret) { + if (!internal) { + if (line.contains(".")) return line; + } else { + try { + if (Integer.parseInt(line) > 0) return line; + } catch(NumberFormatException e) { + } + } + } + return null; + } + } +} diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java b/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java new file mode 100644 index 0000000..eda8d1d --- /dev/null +++ b/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java @@ -0,0 +1,12 @@ +package eu.chainfire.libsuperuser; + +/** + * Exception class used to crash application when shell commands are executed + * from the main thread, and we are in debug mode. + */ +@SuppressWarnings("serial") +public class ShellOnMainThreadException extends RuntimeException { + public ShellOnMainThreadException() { + super("Application attempted to run a shell command from the main thread"); + } +}