Validate onion hostnames, only try to launch Tor on ARM, added comments.

This commit is contained in:
akwizgran
2013-04-25 14:47:55 +01:00
parent 32b575d16a
commit db475577c8

View File

@@ -23,6 +23,7 @@ import java.util.Scanner;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import net.freehaven.tor.control.EventHandler; import net.freehaven.tor.control.EventHandler;
@@ -54,6 +55,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
private static final int SOCKS_PORT = 59050, CONTROL_PORT = 59051; private static final int SOCKS_PORT = 59050, CONTROL_PORT = 59051;
private static final int COOKIE_TIMEOUT = 3000; // Milliseconds private static final int COOKIE_TIMEOUT = 3000; // Milliseconds
private static final int HOSTNAME_TIMEOUT = 30 * 1000; // Milliseconds private static final int HOSTNAME_TIMEOUT = 30 * 1000; // Milliseconds
private static final Pattern ONION = Pattern.compile("[a-z2-7]{16}.onion");
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(TorPlugin.class.getName()); Logger.getLogger(TorPlugin.class.getName());
@@ -63,7 +65,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
private final long maxLatency, pollingInterval; private final long maxLatency, pollingInterval;
private volatile boolean running = false; private volatile boolean running = false;
private volatile Process torProcess = null; private volatile Process tor = null;
private volatile ServerSocket socket = null; private volatile ServerSocket socket = null;
TorPlugin(Executor pluginExecutor, Context appContext, TorPlugin(Executor pluginExecutor, Context appContext,
@@ -89,41 +91,59 @@ class TorPlugin implements DuplexPlugin, EventHandler {
} }
public boolean start() throws IOException { public boolean start() throws IOException {
// Check that we have a Tor binary for this architecture
if(!Build.CPU_ABI.startsWith("armeabi")) {
if(LOG.isLoggable(INFO))
LOG.info("No Tor binary for this architecture");
return false;
}
// Try to connect to an existing Tor process, if any
Socket s; Socket s;
try { try {
s = new Socket("127.0.0.1", CONTROL_PORT); s = new Socket("127.0.0.1", CONTROL_PORT); // FIXME: Never closed
if(LOG.isLoggable(INFO)) LOG.info("Tor is already running"); if(LOG.isLoggable(INFO)) LOG.info("Tor is already running");
} catch(IOException e) { } catch(IOException e) {
// Install the binary, GeoIP database and config file if necessary
if(!isInstalled() && !install()) { if(!isInstalled() && !install()) {
if(LOG.isLoggable(INFO)) LOG.info("Could not install Tor"); if(LOG.isLoggable(INFO)) LOG.info("Could not install Tor");
return false; return false;
} }
if(LOG.isLoggable(INFO)) LOG.info("Starting Tor"); if(LOG.isLoggable(INFO)) LOG.info("Starting Tor");
// Watch for the auth cookie file being created/updated
File cookieFile = getCookieFile(); File cookieFile = getCookieFile();
cookieFile.getParentFile().mkdirs(); cookieFile.getParentFile().mkdirs();
cookieFile.createNewFile(); cookieFile.createNewFile();
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
FileObserver obs = new WriteObserver(cookieFile, latch); FileObserver obs = new WriteObserver(cookieFile, latch);
obs.startWatching(); obs.startWatching();
// Start a new Tor process
String torPath = getTorFile().getAbsolutePath(); String torPath = getTorFile().getAbsolutePath();
String configPath = getConfigFile().getAbsolutePath(); String configPath = getConfigFile().getAbsolutePath();
String[] command = { torPath, "-f", configPath }; String[] command = { torPath, "-f", configPath };
String home = "HOME=" + getTorDirectory().getAbsolutePath(); String home = "HOME=" + getTorDirectory().getAbsolutePath();
String[] environment = { home }; String[] environment = { home };
File dir = getTorDirectory(); File dir = getTorDirectory();
torProcess = Runtime.getRuntime().exec(command, environment, dir); try {
tor = Runtime.getRuntime().exec(command, environment, dir);
} catch(SecurityException e1) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e1.toString(), e1);
return false;
}
// Log the process's standard output until it detaches
if(LOG.isLoggable(INFO)) { if(LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream()); Scanner stdout = new Scanner(tor.getInputStream());
while(stdout.hasNextLine()) LOG.info(stdout.nextLine()); while(stdout.hasNextLine()) LOG.info(stdout.nextLine());
stdout.close(); stdout.close();
} }
try { try {
int exit = torProcess.waitFor(); // Wait for the process to detach successfully
int exit = tor.waitFor();
if(exit != 0) { if(exit != 0) {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit); LOG.warning("Tor exited with value " + exit);
return false; return false;
} }
// Wait for the auth cookie file to be created/updated
if(!latch.await(COOKIE_TIMEOUT, MILLISECONDS)) { if(!latch.await(COOKIE_TIMEOUT, MILLISECONDS)) {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
LOG.warning("Auth cookie not created"); LOG.warning("Auth cookie not created");
@@ -135,14 +155,17 @@ class TorPlugin implements DuplexPlugin, EventHandler {
LOG.warning("Interrupted while starting Tor"); LOG.warning("Interrupted while starting Tor");
return false; return false;
} }
s = new Socket("127.0.0.1", CONTROL_PORT); // Now we should be able to connect to the new process
s = new Socket("127.0.0.1", CONTROL_PORT); // FIXME: Never closed
} }
// Open a control connection and authenticate using the cookie file
TorControlConnection control = new TorControlConnection(s); TorControlConnection control = new TorControlConnection(s);
control.launchThread(true); control.launchThread(true);
control.authenticate(read(getCookieFile())); control.authenticate(read(getCookieFile()));
control.setEventHandler(this); control.setEventHandler(this);
control.setEvents(Arrays.asList("NOTICE", "WARN", "ERR")); control.setEvents(Arrays.asList("NOTICE", "WARN", "ERR"));
running = true; running = true;
// Bind a server socket to receive incoming hidden service connections
pluginExecutor.execute(new Runnable() { pluginExecutor.execute(new Runnable() {
public void run() { public void run() {
bind(); bind();
@@ -159,20 +182,25 @@ class TorPlugin implements DuplexPlugin, EventHandler {
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
try { try {
// Unzip the Tor binary to the filesystem
in = getTorInputStream(); in = getTorInputStream();
out = new FileOutputStream(getTorFile()); out = new FileOutputStream(getTorFile());
copy(in, out); copy(in, out);
// Unzip the GeoIP database to the filesystem
in = getGeoIpInputStream(); in = getGeoIpInputStream();
out = new FileOutputStream(getGeoIpFile()); out = new FileOutputStream(getGeoIpFile());
copy(in, out); copy(in, out);
// Copy the config file to the filesystem
in = getConfigInputStream(); in = getConfigInputStream();
out = new FileOutputStream(getConfigFile()); out = new FileOutputStream(getConfigFile());
copy(in, out); copy(in, out);
// Make the Tor binary executable
if(!setExecutable(getTorFile())) { if(!setExecutable(getTorFile())) {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
LOG.warning("Could not make Tor executable"); LOG.warning("Could not make Tor executable");
return false; return false;
} }
// Create a file to indicate that installation succeeded
File done = getDoneFile(); File done = getDoneFile();
done.createNewFile(); done.createNewFile();
return true; return true;
@@ -227,6 +255,8 @@ class TorPlugin implements DuplexPlugin, EventHandler {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while executing chmod"); LOG.warning("Interrupted while executing chmod");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} catch(SecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }
return false; return false;
} }
@@ -265,10 +295,12 @@ class TorPlugin implements DuplexPlugin, EventHandler {
} }
private void bind() { private void bind() {
// If there's already a port number stored in config, reuse it
String portString = callback.getConfig().get("port"); String portString = callback.getConfig().get("port");
int port; int port;
if(StringUtils.isNullOrEmpty(portString)) port = 0; if(StringUtils.isNullOrEmpty(portString)) port = 0;
else port = Integer.parseInt(portString); else port = Integer.parseInt(portString);
// Bind a server socket to receive connections from the Tor process
ServerSocket ss = null; ServerSocket ss = null;
try { try {
ss = new ServerSocket(); ss = new ServerSocket();
@@ -282,15 +314,18 @@ class TorPlugin implements DuplexPlugin, EventHandler {
return; return;
} }
socket = ss; socket = ss;
// Store the port number
final String localPort = String.valueOf(ss.getLocalPort()); final String localPort = String.valueOf(ss.getLocalPort());
TransportConfig c = new TransportConfig(); TransportConfig c = new TransportConfig();
c.put("port", localPort); c.put("port", localPort);
callback.mergeConfig(c); callback.mergeConfig(c);
// Create a hidden service if necessary
pluginExecutor.execute(new Runnable() { pluginExecutor.execute(new Runnable() {
public void run() { public void run() {
publishHiddenService(localPort); publishHiddenService(localPort);
} }
}); });
// Accept incoming hidden service connections from the Tor process
acceptContactConnections(ss); acceptContactConnections(ss);
} }
@@ -308,11 +343,13 @@ class TorPlugin implements DuplexPlugin, EventHandler {
if(!hostnameFile.exists()) { if(!hostnameFile.exists()) {
if(LOG.isLoggable(INFO)) LOG.info("Creating hidden service"); if(LOG.isLoggable(INFO)) LOG.info("Creating hidden service");
try { try {
// Watch for the hostname file being created/updated
hostnameFile.getParentFile().mkdirs(); hostnameFile.getParentFile().mkdirs();
hostnameFile.createNewFile(); hostnameFile.createNewFile();
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
FileObserver obs = new WriteObserver(hostnameFile, latch); FileObserver obs = new WriteObserver(hostnameFile, latch);
obs.startWatching(); obs.startWatching();
// Open a control connection and update the Tor config
String dir = getTorDirectory().getAbsolutePath(); String dir = getTorDirectory().getAbsolutePath();
List<String> config = Arrays.asList("HiddenServiceDir " + dir, List<String> config = Arrays.asList("HiddenServiceDir " + dir,
"HiddenServicePort 80 127.0.0.1:" + port); "HiddenServicePort 80 127.0.0.1:" + port);
@@ -323,6 +360,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
control.authenticate(read(getCookieFile())); control.authenticate(read(getCookieFile()));
control.setConf(config); control.setConf(config);
control.saveConf(); control.saveConf();
// Wait for the hostname file to be created/updated
if(!latch.await(HOSTNAME_TIMEOUT, MILLISECONDS)) { if(!latch.await(HOSTNAME_TIMEOUT, MILLISECONDS)) {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
LOG.warning("Hidden service not created"); LOG.warning("Hidden service not created");
@@ -337,6 +375,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
LOG.warning("Interrupted while creating hidden service"); LOG.warning("Interrupted while creating hidden service");
} }
} }
// Publish the hidden service's onion hostname in transport properties
try { try {
String hostname = new String(read(hostnameFile), "UTF-8").trim(); String hostname = new String(read(hostnameFile), "UTF-8").trim();
if(LOG.isLoggable(INFO)) LOG.info("Hidden service " + hostname); if(LOG.isLoggable(INFO)) LOG.info("Hidden service " + hostname);
@@ -379,9 +418,9 @@ class TorPlugin implements DuplexPlugin, EventHandler {
control.shutdownTor("TERM"); control.shutdownTor("TERM");
} catch(IOException e) { } catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
if(torProcess != null) { if(tor != null) {
if(LOG.isLoggable(INFO)) LOG.info("Killing Tor"); if(LOG.isLoggable(INFO)) LOG.info("Killing Tor");
torProcess.destroy(); tor.destroy();
} }
} }
} }
@@ -419,7 +458,10 @@ class TorPlugin implements DuplexPlugin, EventHandler {
if(p == null) return null; if(p == null) return null;
String onion = p.get("onion"); String onion = p.get("onion");
if(StringUtils.isNullOrEmpty(onion)) return null; if(StringUtils.isNullOrEmpty(onion)) return null;
// FIXME: Check that it's an onion hostname if(!ONION.matcher(onion).matches()) {
if(LOG.isLoggable(INFO)) LOG.info("Invalid hostname: " + onion);
return null;
}
try { try {
if(LOG.isLoggable(INFO)) LOG.info("Connecting to " + onion); if(LOG.isLoggable(INFO)) LOG.info("Connecting to " + onion);
Socks5Proxy proxy = new Socks5Proxy("127.0.0.1", SOCKS_PORT); Socks5Proxy proxy = new Socks5Proxy("127.0.0.1", SOCKS_PORT);