tor-android/src/org/torproject/android/service/TorRoot.java

229 lines
6.5 KiB
Java

/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */
/* See LICENSE for licensing information */
package org.torproject.android.service;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import android.util.Log;
/**
* Contains shared programming interfaces.
* All iptables "communication" is handled by this class.
*/
public final class TorRoot {
private final static String TAG = "TOR_ROOT";
// Do we have root access?
private static boolean hasroot = false;
private final static String CMD_NAT_FLUSH = "iptables -t nat -F || exit\n";
private final static String CMD_NAT_IPTABLES_80 = "iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to 127.0.0.1:8118 || exit\n";
private final static String CMD_DNS_PROXYING = "iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to 127.0.0.1:5400 || exit\n";
public static boolean enableDNSProxying ()
{
final StringBuilder script = new StringBuilder();
int code;
//Enable UDP Proxying
script.append(CMD_DNS_PROXYING);
StringBuilder res = new StringBuilder();
try
{
code = runScriptAsRoot(script.toString(), res);
if (code != 0)
{
Log.w(TAG, "error apply DNS proxying: " + res.toString());
}
} catch (Exception e) {
Log.w(TAG, "error apply DNS proxying: " + res.toString(), e);
return false;
}
return true;
}
/**
* Purge and re-add all rules (internal implementation).
* @param ctx application context (mandatory)
* @param uids list of selected uids to allow or disallow (depending on the working mode)
* @param showErrors indicates if errors should be alerted
*/
public static boolean enabledWebProxying() {
final StringBuilder script = new StringBuilder();
try {
int code;
script.append(CMD_NAT_IPTABLES_80);
/*
int uid = android.os.Process.getUidForName("dhcp");
if (uid != -1) script.append("iptables -A OUTPUT " + itfFilter + " -m owner --uid-owner " + uid + " -j ACCEPT || exit\n");
uid = android.os.Process.getUidForName("wifi");
if (uid != -1) script.append("iptables -A OUTPUT " + itfFilter + " -m owner --uid-owner " + uid + " -j ACCEPT || exit\n");
*/
StringBuilder res = new StringBuilder();
code = runScriptAsRoot(script.toString(), res);
String msg = res.toString();
Log.e(TAG, msg);
} catch (Exception e) {
Log.w(TAG, "error refreshing iptables: " + e);
}
return false;
}
/**
* Purge all iptables rules.
* @return true if the rules were purged
*/
public static boolean purgeNatIptables() {
StringBuilder res = new StringBuilder();
try {
int code = runScriptAsRoot(CMD_NAT_FLUSH, res);
if (code != 0) {
Log.w(TAG, "error purging iptables. exit code: " + code + "\n" + res);
return false;
}
return true;
} catch (Exception e) {
Log.w(TAG,"error purging iptables: " + e);
return false;
}
}
/**
* Check if we have root access
* @return boolean true if we have root
*/
public static boolean hasRootAccess() {
if (hasroot) return true;
try {
// Run an empty script just to check root access
if (runScriptAsRoot("exit 0", null, 20000) == 0) {
hasroot = true;
return true;
}
} catch (Exception e) {
}
Log.w(TAG, "Could not acquire root access.");
return false;
}
/**
* Runs a script as root (multiple commands separated by "\n").
*
* @param script the script to be executed
* @param res the script output response (stdout + stderr)
* @param timeout timeout in milliseconds (-1 for none)
* @return the script exit code
*/
public static int runScriptAsRoot(String script, StringBuilder res, final long timeout) {
Log.i(TAG,"executing script: " + script);
final ScriptRunner runner = new ScriptRunner(script, res);
runner.start();
try {
if (timeout > 0) {
runner.join(timeout);
} else {
runner.join();
}
if (runner.isAlive()) {
// Timed-out
runner.interrupt();
runner.destroy();
runner.join(50);
}
} catch (InterruptedException ex) {}
return runner.exitcode;
}
/**
* Runs a script as root (multiple commands separated by "\n") with a default timeout of 5 seconds.
*
* @param script the script to be executed
* @param res the script output response (stdout + stderr)
* @param timeout timeout in milliseconds (-1 for none)
* @return the script exit code
* @throws IOException on any error executing the script, or writing it to disk
*/
public static int runScriptAsRoot(String script, StringBuilder res) throws IOException {
return runScriptAsRoot(script, res, 5000);
}
/**
* Internal thread used to execute scripts as root.
*/
private static final class ScriptRunner extends Thread {
private final String script;
private final StringBuilder res;
public int exitcode = -1;
private Process exec;
/**
* Creates a new script runner.
* @param script script to run
* @param res response output
*/
public ScriptRunner(String script, StringBuilder res) {
this.script = script;
this.res = res;
}
@Override
public void run() {
try {
// Create the "su" request to run the command
// note that this will create a shell that we must interact to (using stdin/stdout)
exec = Runtime.getRuntime().exec("su");
final OutputStreamWriter out = new OutputStreamWriter(exec.getOutputStream());
// Write the script to be executed
out.write(script);
// Ensure that the last character is an "enter"
if (!script.endsWith("\n")) out.write("\n");
out.flush();
// Terminate the "su" process
out.write("exit\n");
out.flush();
final char buf[] = new char[1024];
// Consume the "stdout"
InputStreamReader r = new InputStreamReader(exec.getInputStream());
int read=0;
while ((read=r.read(buf)) != -1) {
if (res != null) res.append(buf, 0, read);
}
// Consume the "stderr"
r = new InputStreamReader(exec.getErrorStream());
read=0;
while ((read=r.read(buf)) != -1) {
if (res != null) res.append(buf, 0, read);
}
// get the process exit code
if (exec != null) this.exitcode = exec.waitFor();
} catch (InterruptedException ex) {
if (res != null) res.append("\nOperation timed-out");
} catch (Exception ex) {
if (res != null) res.append("\n" + ex);
} finally {
destroy();
}
}
/**
* Destroy this script runner
*/
public synchronized void destroy() {
if (exec != null) exec.destroy();
exec = null;
}
}
}