package org.shadowsocks;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Calendar;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.util.Log;
public class Utils {
/**
* Internal thread used to execute scripts (as root or not).
*/
private static final class ScriptRunner extends Thread {
private final File file;
private final String script;
private final StringBuilder res;
private final boolean asroot;
public int exitcode = -1;
// private Process exec;
private int mProcId;
private FileDescriptor mTermFd;
/**
* Creates a new script runner.
*
* @param file temporary script file
* @param script script to run
* @param res response output
* @param asroot if true, executes the script as root
*/
public ScriptRunner(File file, String script, StringBuilder res,
boolean asroot) {
this.file = file;
this.script = script;
this.res = res;
this.asroot = asroot;
}
private int createSubprocess(int[] processId, String cmd) {
ArrayList<String> argList = parse(cmd);
String arg0 = argList.get(0);
String[] args = argList.toArray(new String[1]);
mTermFd = Exec.createSubprocess(arg0, args, null, processId);
return processId[0];
}
/**
* Destroy this script runner
*/
@Override
public synchronized void destroy() {
try {
Exec.hangupProcessGroup(mProcId);
Exec.close(mTermFd);
} catch (NoClassDefFoundError ignore) {
// Nothing
}
}
private ArrayList<String> parse(String cmd) {
final int PLAIN = 0;
final int WHITESPACE = 1;
final int INQUOTE = 2;
int state = WHITESPACE;
ArrayList<String> result = new ArrayList<String>();
int cmdLen = cmd.length();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < cmdLen; i++) {
char c = cmd.charAt(i);
if (state == PLAIN) {
if (Character.isWhitespace(c)) {
result.add(builder.toString());
builder.delete(0, builder.length());
state = WHITESPACE;
} else if (c == '"') {
state = INQUOTE;
} else {
builder.append(c);
}
} else if (state == WHITESPACE) {
if (Character.isWhitespace(c)) {
// do nothing
} else if (c == '"') {
state = INQUOTE;
} else {
state = PLAIN;
builder.append(c);
}
} else if (state == INQUOTE) {
if (c == '\\') {
if (i + 1 < cmdLen) {
i += 1;
builder.append(cmd.charAt(i));
}
} else if (c == '"') {
state = PLAIN;
} else {
builder.append(c);
}
}
}
if (builder.length() > 0) {
result.add(builder.toString());
}
return result;
}
@Override
public void run() {
try {
new File(DEFOUT_FILE).createNewFile();
file.createNewFile();
final String abspath = file.getAbsolutePath();
// TODO: Rewrite this line
// make sure we have execution permission on the script file
// Runtime.getRuntime().exec("chmod 755 " + abspath).waitFor();
// Write the script to be executed
final OutputStreamWriter out = new OutputStreamWriter(
new FileOutputStream(file));
out.write("#!/system/bin/sh\n");
out.write(script);
if (!script.endsWith("\n"))
out.write("\n");
out.write("exit\n");
out.flush();
out.close();
if (this.asroot) {
// Create the "su" request to run the script
// exec = Runtime.getRuntime().exec(
// root_shell + " -c " + abspath);
int pid[] = new int[1];
mProcId = createSubprocess(pid, root_shell + " -c "
+ abspath);
} else {
// Create the "sh" request to run the script
// exec = Runtime.getRuntime().exec(getShell() + " " +
// abspath);
int pid[] = new int[1];
mProcId = createSubprocess(pid, getShell() + " " + abspath);
}
final InputStream stdout = new FileInputStream(DEFOUT_FILE);
final byte buf[] = new byte[8192];
int read = 0;
exitcode = Exec.waitFor(mProcId);
// Read stdout
while (stdout.available() > 0) {
read = stdout.read(buf);
if (res != null)
res.append(new String(buf, 0, read));
}
} catch (Exception ex) {
if (res != null)
res.append("\n" + ex);
} finally {
destroy();
}
}
}
public final static String TAG = "ShadowsocksProxy";
public final static String DEFAULT_SHELL = "/system/bin/sh";
public final static String DEFAULT_ROOT = "/system/bin/su";
public final static String ALTERNATIVE_ROOT = "/system/xbin/su";
public final static String DEFAULT_IPTABLES = "/data/data/org.shadowsocks/iptables";
public final static String ALTERNATIVE_IPTABLES = "/system/bin/iptables";
public final static String SCRIPT_FILE = "/data/data/org.shadowsocks/script";
public final static String DEFOUT_FILE = "/data/data/org.shadowsocks/defout";
public final static int TIME_OUT = -99;
private static boolean initialized = false;
private static int hasRedirectSupport = -1;
private static int isRoot = -1;
private static String shell = null;
private static String root_shell = null;
private static String iptables = null;
private static String data_path = null;
private static long prevTime = 0;
private static void checkIptables() {
if (!isRoot()) {
iptables = DEFAULT_IPTABLES;
return;
}
// Check iptables binary
iptables = DEFAULT_IPTABLES;
String lines = null;
boolean compatible = false;
boolean version = false;
StringBuilder sb = new StringBuilder();
String command = iptables + " --version\n" + iptables
+ " -L -t nat -n\n" + "exit\n";
int exitcode = runScript(command, sb, 10 * 1000, true);
if (exitcode == TIME_OUT)
return;
lines = sb.toString();
if (lines.contains("OUTPUT")) {
compatible = true;
}
if (lines.contains("v1.4.")) {
version = true;
}
if (!compatible || !version) {
iptables = ALTERNATIVE_IPTABLES;
if (!new File(iptables).exists())
iptables = "iptables";
}
}
public static Drawable getAppIcon(Context c, int uid) {
PackageManager pm = c.getPackageManager();
Drawable appIcon = c.getResources().getDrawable(
R.drawable.sym_def_app_icon);
String[] packages = pm.getPackagesForUid(uid);
if (packages != null) {
if (packages.length == 1) {
try {
ApplicationInfo appInfo = pm.getApplicationInfo(
packages[0], 0);
appIcon = pm.getApplicationIcon(appInfo);
} catch (NameNotFoundException e) {
Log.e(c.getPackageName(),
"No package found matching with the uid " + uid);
}
}
} else {
Log.e(c.getPackageName(), "Package not found for uid " + uid);
}
return appIcon;
}
public static String getDataPath(Context ctx) {
if (data_path == null) {
if (Environment.MEDIA_MOUNTED.equals(Environment
.getExternalStorageState())) {
data_path = Environment.getExternalStorageDirectory()
.getAbsolutePath();
} else {
data_path = ctx.getFilesDir().getAbsolutePath();
}
Log.d(TAG, "Python Data Path: " + data_path);
}
return data_path;
}
public static boolean getHasRedirectSupport() {
if (hasRedirectSupport == -1)
initHasRedirectSupported();
return hasRedirectSupport == 1 ? true : false;
}
public static String getIptables() {
if (iptables == null)
checkIptables();
return iptables;
}
private static String getShell() {
if (shell == null) {
shell = DEFAULT_SHELL;
if (!new File(shell).exists())
shell = "sh";
}
return shell;
}
public static String getSignature(Context ctx) {
Signature sig = null;
try {
Signature[] sigs;
sigs = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(),
PackageManager.GET_SIGNATURES).signatures;
if (sigs != null && sigs.length > 0)
sig = sigs[0];
} catch (Exception ignore) {
// Nothing
}
if (sig == null)
return null;
return sig.toCharsString().substring(11, 256);
}
public static void initHasRedirectSupported() {
if (!Utils.isRoot())
return;
StringBuilder sb = new StringBuilder();
String command = Utils.getIptables()
+ " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154";
int exitcode = runScript(command, sb, 10 * 1000, true);
String lines = sb.toString();
hasRedirectSupport = 1;
// flush the check command
Utils.runRootCommand(command.replace("-A", "-D"));
if (exitcode == TIME_OUT)
return;
if (lines.contains("No chain/target/match")) {
hasRedirectSupport = 0;
}
}
public static boolean isInitialized() {
if (initialized)
return true;
else {
initialized = true;
return false;
}
}
public static boolean isRoot() {
if (isRoot != -1)
return isRoot == 1 ? true : false;
// switch between binaries
if (new File(DEFAULT_ROOT).exists()) {
root_shell = DEFAULT_ROOT;
} else if (new File(ALTERNATIVE_ROOT).exists()) {
root_shell = ALTERNATIVE_ROOT;
} else {
root_shell = "su";
}
String lines = null;
StringBuilder sb = new StringBuilder();
String command = "ls /\n" + "exit\n";
int exitcode = runScript(command, sb, 10 * 1000, true);
if (exitcode == TIME_OUT) {
return false;
}
lines = sb.toString();
if (lines.contains("system")) {
isRoot = 1;
}
return isRoot == 1 ? true : false;
}
public static boolean runCommand(String command) {
return runCommand(command, 10 * 1000);
}
public static boolean runCommand(String command, int timeout) {
Log.d(TAG, command);
runScript(command, null, timeout, false);
return true;
}
public static boolean runRootCommand(String command) {
return runRootCommand(command, 10 * 1000);
}
public static boolean runRootCommand(String command, int timeout) {
if (!isRoot())
return false;
Log.d(TAG, command);
runScript(command, null, timeout, true);
return true;
}
private synchronized static int runScript(String script, StringBuilder res,
long timeout, boolean asroot) {
final File file = new File(SCRIPT_FILE);
final ScriptRunner runner = new ScriptRunner(file, script, res, asroot);
runner.start();
try {
if (timeout > 0) {
runner.join(timeout);
} else {
runner.join();
}
if (runner.isAlive()) {
// Timed-out
runner.destroy();
runner.join(1000);
return TIME_OUT;
}
} catch (InterruptedException ex) {
return TIME_OUT;
}
return runner.exitcode;
}
/**
* @param tag
* @param message
*/
public static void log(String tag, String message)
{
long currentTime = Calendar.getInstance().getTimeInMillis();
Log.d(tag, message + ". Used time: " + (currentTime - prevTime) + " ms:");
prevTime = currentTime;
}
}