From 0237df937f2d8a97f70476e78b8ab907c8f39062 Mon Sep 17 00:00:00 2001 From: goapunk Date: Fri, 29 Mar 2019 18:40:51 +0100 Subject: [PATCH] fix loging, include jtorctl for debugging --- bramble-core/build.gradle | 1 - .../java/net/freehaven/tor/control/.cvsignore | 1 + .../java/net/freehaven/tor/control/Bytes.java | 114 +++ .../freehaven/tor/control/ConfigEntry.java | 20 + .../freehaven/tor/control/EventHandler.java | 75 ++ .../tor/control/NullEventHandler.java | 18 + .../freehaven/tor/control/PasswordDigest.java | 98 ++ .../java/net/freehaven/tor/control/README | 4 + .../tor/control/TorControlCommands.java | 151 +++ .../tor/control/TorControlConnection.java | 862 ++++++++++++++++++ .../tor/control/TorControlError.java | 39 + .../tor/control/TorControlSyntaxError.java | 16 + .../freehaven/tor/control/examples/.cvsignore | 1 + .../examples/DebuggingEventHandler.java | 44 + .../freehaven/tor/control/examples/Main.java | 146 +++ .../bramble/plugin/tor/TorPlugin.java | 4 +- bramble-core/witness.gradle | 1 - 17 files changed, 1591 insertions(+), 4 deletions(-) create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/.cvsignore create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/Bytes.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/ConfigEntry.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/EventHandler.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/NullEventHandler.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/PasswordDigest.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/README create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/TorControlCommands.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/TorControlConnection.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/TorControlError.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/TorControlSyntaxError.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/examples/.cvsignore create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/examples/DebuggingEventHandler.java create mode 100644 bramble-core/src/main/java/net/freehaven/tor/control/examples/Main.java diff --git a/bramble-core/build.gradle b/bramble-core/build.gradle index 3ac200b46..d338dd4d6 100644 --- a/bramble-core/build.gradle +++ b/bramble-core/build.gradle @@ -15,7 +15,6 @@ dependencies { implementation 'org.bitlet:weupnp:0.1.4' implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'org.whispersystems:curve25519-java:0.5.0' - implementation 'org.briarproject:jtorctl:0.3' annotationProcessor 'com.google.dagger:dagger-compiler:2.19' diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/.cvsignore b/bramble-core/src/main/java/net/freehaven/tor/control/.cvsignore new file mode 100644 index 000000000..6b468b62a --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/.cvsignore @@ -0,0 +1 @@ +*.class diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/Bytes.java b/bramble-core/src/main/java/net/freehaven/tor/control/Bytes.java new file mode 100644 index 000000000..07c25d081 --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/Bytes.java @@ -0,0 +1,114 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control; + +import java.util.Arrays; +import java.util.List; + +/** + * Static class to do bytewise structure manipulation in Java. + */ +/* XXXX There must be a better way to do most of this. + * XXXX The string logic here uses default encoding, which is stupid. + */ +final class Bytes { + + /** Write the two-byte value in 's' into the byte array 'ba', starting at + * the index 'pos'. */ + public static void setU16(byte[] ba, int pos, short s) { + ba[pos] = (byte)((s >> 8) & 0xff); + ba[pos+1] = (byte)((s ) & 0xff); + } + + /** Write the four-byte value in 'i' into the byte array 'ba', starting at + * the index 'pos'. */ + public static void setU32(byte[] ba, int pos, int i) { + ba[pos] = (byte)((i >> 24) & 0xff); + ba[pos+1] = (byte)((i >> 16) & 0xff); + ba[pos+2] = (byte)((i >> 8) & 0xff); + ba[pos+3] = (byte)((i ) & 0xff); + } + + /** Return the four-byte value starting at index 'pos' within 'ba' */ + public static int getU32(byte[] ba, int pos) { + return + ((ba[pos ]&0xff)<<24) | + ((ba[pos+1]&0xff)<<16) | + ((ba[pos+2]&0xff)<< 8) | + ((ba[pos+3]&0xff)); + } + + public static String getU32S(byte[] ba, int pos) { + return String.valueOf( (getU32(ba,pos))&0xffffffffL ); + } + + /** Return the two-byte value starting at index 'pos' within 'ba' */ + public static int getU16(byte[] ba, int pos) { + return + ((ba[pos ]&0xff)<<8) | + ((ba[pos+1]&0xff)); + } + + /** Return the string starting at position 'pos' of ba and extending + * until a zero byte or the end of the string. */ + public static String getNulTerminatedStr(byte[] ba, int pos) { + int len, maxlen = ba.length-pos; + for (len=0; len lst, byte[] ba, int pos, byte split) { + while (pos < ba.length && ba[pos] != 0) { + int len; + for (len=0; pos+len < ba.length; ++len) { + if (ba[pos+len] == 0 || ba[pos+len] == split) + break; + } + if (len>0) + lst.add(new String(ba, pos, len)); + pos += len; + if (ba[pos] == split) + ++pos; + } + } + + /** + * Read bytes from 'ba' starting at 'pos', dividing them into strings + * along the character in 'split' and writing them into 'lst' + */ + public static List splitStr(List lst, String str) { + // split string on spaces, include trailing/leading + String[] tokenArray = str.split(" ", -1); + if (lst == null) { + lst = Arrays.asList( tokenArray ); + } else { + lst.addAll( Arrays.asList( tokenArray ) ); + } + return lst; + } + + private static final char[] NYBBLES = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + public static final String hex(byte[] ba) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < ba.length; ++i) { + int b = (ba[i]) & 0xff; + buf.append(NYBBLES[b >> 4]); + buf.append(NYBBLES[b&0x0f]); + } + return buf.toString(); + } + + private Bytes() {}; +} + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/ConfigEntry.java b/bramble-core/src/main/java/net/freehaven/tor/control/ConfigEntry.java new file mode 100644 index 000000000..c9e4cfcc9 --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/ConfigEntry.java @@ -0,0 +1,20 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control; + +/** A single key-value pair from Tor's configuration. */ +public class ConfigEntry { + public ConfigEntry(String k, String v) { + key = k; + value = v; + is_default = false; + } + public ConfigEntry(String k) { + key = k; + value = ""; + is_default = true; + } + public final String key; + public final String value; + public final boolean is_default; +} diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/EventHandler.java b/bramble-core/src/main/java/net/freehaven/tor/control/EventHandler.java new file mode 100644 index 000000000..11c45a4ab --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/EventHandler.java @@ -0,0 +1,75 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control; + +/** + * Abstract interface whose methods are invoked when Tor sends us an event. + * + * @see TorControlConnection#setEventHandler + * @see TorControlConnection#setEvents + */ +public interface EventHandler { + /** + * Invoked when a circuit's status has changed. + * Possible values for status are: + *
    + *
  • "LAUNCHED" : circuit ID assigned to new circuit
  • + *
  • "BUILT" : all hops finished, can now accept streams
  • + *
  • "EXTENDED" : one more hop has been completed
  • + *
  • "FAILED" : circuit closed (was not built)
  • + *
  • "CLOSED" : circuit closed (was built)
  • + *
+ * + * circID is the alphanumeric identifier of the affected circuit, + * and path is a comma-separated list of alphanumeric ServerIDs. + */ + public void circuitStatus(String status, String circID, String path); + /** + * Invoked when a stream's status has changed. + * Possible values for status are: + *
    + *
  • "NEW" : New request to connect
  • + *
  • "NEWRESOLVE" : New request to resolve an address
  • + *
  • "SENTCONNECT" : Sent a connect cell along a circuit
  • + *
  • "SENTRESOLVE" : Sent a resolve cell along a circuit
  • + *
  • "SUCCEEDED" : Received a reply; stream established
  • + *
  • "FAILED" : Stream failed and not retriable.
  • + *
  • "CLOSED" : Stream closed
  • + *
  • "DETACHED" : Detached from circuit; still retriable.
  • + *
+ * + * streamID is the alphanumeric identifier of the affected stream, + * and its target is specified as address:port. + */ + public void streamStatus(String status, String streamID, String target); + /** + * Invoked when the status of a connection to an OR has changed. + * Possible values for status are ["LAUNCHED" | "CONNECTED" | "FAILED" | "CLOSED"]. + * orName is the alphanumeric identifier of the OR affected. + */ + public void orConnStatus(String status, String orName); + /** + * Invoked once per second. read and written are + * the number of bytes read and written, respectively, in + * the last second. + */ + public void bandwidthUsed(long read, long written); + /** + * Invoked whenever Tor learns about new ORs. The orList object + * contains the alphanumeric ServerIDs associated with the new ORs. + */ + public void newDescriptors(java.util.List orList); + /** + * Invoked when Tor logs a message. + * severity is one of ["DEBUG" | "INFO" | "NOTICE" | "WARN" | "ERR"], + * and msg is the message string. + */ + public void message(String severity, String msg); + /** + * Invoked when an unspecified message is received. + * is the message type, and is the message string. + */ + public void unrecognized(String type, String msg); + +} + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/NullEventHandler.java b/bramble-core/src/main/java/net/freehaven/tor/control/NullEventHandler.java new file mode 100644 index 000000000..26958e03b --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/NullEventHandler.java @@ -0,0 +1,18 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control; + +/** + * Implementation of EventHandler that ignores all events. Useful + * when you only want to override one method. + */ +public class NullEventHandler implements EventHandler { + public void circuitStatus(String status, String circID, String path) {} + public void streamStatus(String status, String streamID, String target) {} + public void orConnStatus(String status, String orName) {} + public void bandwidthUsed(long read, long written) {} + public void newDescriptors(java.util.List orList) {} + public void message(String severity, String msg) {} + public void unrecognized(String type, String msg) {} +} + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/PasswordDigest.java b/bramble-core/src/main/java/net/freehaven/tor/control/PasswordDigest.java new file mode 100644 index 000000000..3b7e9e82a --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/PasswordDigest.java @@ -0,0 +1,98 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +/** + * A hashed digest of a secret password (used to set control connection + * security.) + * + * For the actual hashing algorithm, see RFC2440's secret-to-key conversion. + */ +public class PasswordDigest { + + private final byte[] secret; + private final String hashedKey; + + /** Return a new password digest with a random secret and salt. */ + public static PasswordDigest generateDigest() { + byte[] secret = new byte[20]; + SecureRandom rng = new SecureRandom(); + rng.nextBytes(secret); + return new PasswordDigest(secret); + } + + /** Construct a new password digest with a given secret and random salt */ + public PasswordDigest(byte[] secret) { + this(secret, null); + } + + /** Construct a new password digest with a given secret and random salt. + * Note that the 9th byte of the specifier determines the number of hash + * iterations as in RFC2440. + */ + public PasswordDigest(byte[] secret, byte[] specifier) { + this.secret = secret.clone(); + if (specifier == null) { + specifier = new byte[9]; + SecureRandom rng = new SecureRandom(); + rng.nextBytes(specifier); + specifier[8] = 96; + } + hashedKey = "16:"+encodeBytes(secretToKey(secret, specifier)); + } + + /** Return the secret used to generate this password hash. + */ + public byte[] getSecret() { + return secret.clone(); + } + + /** Return the hashed password in the format used by Tor. */ + public String getHashedPassword() { + return hashedKey; + } + + /** Parameter used by RFC2440's s2k algorithm. */ + private static final int EXPBIAS = 6; + + /** Implement rfc2440 s2k */ + public static byte[] secretToKey(byte[] secret, byte[] specifier) { + MessageDigest d; + try { + d = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException("Can't run without sha-1."); + } + int c = (specifier[8])&0xff; + int count = (16 + (c&15)) << ((c>>4) + EXPBIAS); + + byte[] tmp = new byte[8+secret.length]; + System.arraycopy(specifier, 0, tmp, 0, 8); + System.arraycopy(secret, 0, tmp, 8, secret.length); + while (count > 0) { + if (count >= tmp.length) { + d.update(tmp); + count -= tmp.length; + } else { + d.update(tmp, 0, count); + count = 0; + } + } + byte[] key = new byte[20+9]; + System.arraycopy(d.digest(), 0, key, 9, 20); + System.arraycopy(specifier, 0, key, 0, 9); + return key; + } + + /** Return a hexadecimal encoding of a byte array. */ + // XXX There must be a better way to do this in Java. + private static final String encodeBytes(byte[] ba) { + return Bytes.hex(ba); + } + +} + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/README b/bramble-core/src/main/java/net/freehaven/tor/control/README new file mode 100644 index 000000000..b310c7d5a --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/README @@ -0,0 +1,4 @@ +We broke the version detection stuff in Tor 0.1.2.16 / 0.2.0.4-alpha. +Somebody should rip out the v0 control protocol stuff from here, and +it should start working again. -RD + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/TorControlCommands.java b/bramble-core/src/main/java/net/freehaven/tor/control/TorControlCommands.java new file mode 100644 index 000000000..14486e318 --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/TorControlCommands.java @@ -0,0 +1,151 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control; + +/** Interface defining constants used by the Tor controller protocol. + */ +// XXXX Take documentation for these from control-spec.txt +public interface TorControlCommands { + + public static final short CMD_ERROR = 0x0000; + public static final short CMD_DONE = 0x0001; + public static final short CMD_SETCONF = 0x0002; + public static final short CMD_GETCONF = 0x0003; + public static final short CMD_CONFVALUE = 0x0004; + public static final short CMD_SETEVENTS = 0x0005; + public static final short CMD_EVENT = 0x0006; + public static final short CMD_AUTH = 0x0007; + public static final short CMD_SAVECONF = 0x0008; + public static final short CMD_SIGNAL = 0x0009; + public static final short CMD_MAPADDRESS = 0x000A; + public static final short CMD_GETINFO = 0x000B; + public static final short CMD_INFOVALUE = 0x000C; + public static final short CMD_EXTENDCIRCUIT = 0x000D; + public static final short CMD_ATTACHSTREAM = 0x000E; + public static final short CMD_POSTDESCRIPTOR = 0x000F; + public static final short CMD_FRAGMENTHEADER = 0x0010; + public static final short CMD_FRAGMENT = 0x0011; + public static final short CMD_REDIRECTSTREAM = 0x0012; + public static final short CMD_CLOSESTREAM = 0x0013; + public static final short CMD_CLOSECIRCUIT = 0x0014; + + public static final String[] CMD_NAMES = { + "ERROR", + "DONE", + "SETCONF", + "GETCONF", + "CONFVALUE", + "SETEVENTS", + "EVENT", + "AUTH", + "SAVECONF", + "SIGNAL", + "MAPADDRESS", + "GETINFO", + "INFOVALUE", + "EXTENDCIRCUIT", + "ATTACHSTREAM", + "POSTDESCRIPTOR", + "FRAGMENTHEADER", + "FRAGMENT", + "REDIRECTSTREAM", + "CLOSESTREAM", + "CLOSECIRCUIT", + }; + + public static final short EVENT_CIRCSTATUS = 0x0001; + public static final short EVENT_STREAMSTATUS = 0x0002; + public static final short EVENT_ORCONNSTATUS = 0x0003; + public static final short EVENT_BANDWIDTH = 0x0004; + public static final short EVENT_NEWDESCRIPTOR = 0x0006; + public static final short EVENT_MSG_DEBUG = 0x0007; + public static final short EVENT_MSG_INFO = 0x0008; + public static final short EVENT_MSG_NOTICE = 0x0009; + public static final short EVENT_MSG_WARN = 0x000A; + public static final short EVENT_MSG_ERROR = 0x000B; + + public static final String[] EVENT_NAMES = { + "(0)", + "CIRC", + "STREAM", + "ORCONN", + "BW", + "OLDLOG", + "NEWDESC", + "DEBUG", + "INFO", + "NOTICE", + "WARN", + "ERR", + }; + + public static final byte CIRC_STATUS_LAUNCHED = 0x01; + public static final byte CIRC_STATUS_BUILT = 0x02; + public static final byte CIRC_STATUS_EXTENDED = 0x03; + public static final byte CIRC_STATUS_FAILED = 0x04; + public static final byte CIRC_STATUS_CLOSED = 0x05; + + public static final String[] CIRC_STATUS_NAMES = { + "LAUNCHED", + "BUILT", + "EXTENDED", + "FAILED", + "CLOSED", + }; + + public static final byte STREAM_STATUS_SENT_CONNECT = 0x00; + public static final byte STREAM_STATUS_SENT_RESOLVE = 0x01; + public static final byte STREAM_STATUS_SUCCEEDED = 0x02; + public static final byte STREAM_STATUS_FAILED = 0x03; + public static final byte STREAM_STATUS_CLOSED = 0x04; + public static final byte STREAM_STATUS_NEW_CONNECT = 0x05; + public static final byte STREAM_STATUS_NEW_RESOLVE = 0x06; + public static final byte STREAM_STATUS_DETACHED = 0x07; + + public static final String[] STREAM_STATUS_NAMES = { + "SENT_CONNECT", + "SENT_RESOLVE", + "SUCCEEDED", + "FAILED", + "CLOSED", + "NEW_CONNECT", + "NEW_RESOLVE", + "DETACHED" + }; + + public static final byte OR_CONN_STATUS_LAUNCHED = 0x00; + public static final byte OR_CONN_STATUS_CONNECTED = 0x01; + public static final byte OR_CONN_STATUS_FAILED = 0x02; + public static final byte OR_CONN_STATUS_CLOSED = 0x03; + + public static final String[] OR_CONN_STATUS_NAMES = { + "LAUNCHED","CONNECTED","FAILED","CLOSED" + }; + + public static final byte SIGNAL_HUP = 0x01; + public static final byte SIGNAL_INT = 0x02; + public static final byte SIGNAL_USR1 = 0x0A; + public static final byte SIGNAL_USR2 = 0x0C; + public static final byte SIGNAL_TERM = 0x0F; + + public static final String ERROR_MSGS[] = { + "Unspecified error", + "Internal error", + "Unrecognized message type", + "Syntax error", + "Unrecognized configuration key", + "Invalid configuration value", + "Unrecognized byte code", + "Unauthorized", + "Failed authentication attempt", + "Resource exhausted", + "No such stream", + "No such circuit", + "No such OR", + }; + + public static final String HS_ADDRESS = "onionAddress"; + public static final String HS_PRIVKEY = "onionPrivKey"; + +} + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/TorControlConnection.java b/bramble-core/src/main/java/net/freehaven/tor/control/TorControlConnection.java new file mode 100644 index 000000000..9c5d8f08d --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/TorControlConnection.java @@ -0,0 +1,862 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Writer; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.concurrent.CancellationException; + +/** A connection to a running Tor process as specified in control-spec.txt. */ +public class TorControlConnection implements TorControlCommands { + + private final LinkedList waiters; + private final BufferedReader input; + private final Writer output; + + private ControlParseThread thread; // Locking: this + + private volatile EventHandler handler; + private volatile PrintWriter debugOutput; + private volatile IOException parseThreadException; + + static class Waiter { + + List response; // Locking: this + boolean interrupted; + + synchronized List getResponse() throws InterruptedException { + while (response == null) { + wait(); + if (interrupted) { + throw new InterruptedException(); + } + } + return response; + } + + synchronized void setResponse(List response) { + this.response = response; + notifyAll(); + } + + synchronized void interrupt() { + interrupted = true; + notifyAll(); + } + } + + static class ReplyLine { + + final String status; + final String msg; + final String rest; + + ReplyLine(String status, String msg, String rest) { + this.status = status; this.msg = msg; this.rest = rest; + } + } + + /** Create a new TorControlConnection to communicate with Tor over + * a given socket. After calling this constructor, it is typical to + * call launchThread and authenticate. */ + public TorControlConnection(Socket connection) throws IOException { + this(connection.getInputStream(), connection.getOutputStream()); + } + + /** Create a new TorControlConnection to communicate with Tor over + * an arbitrary pair of data streams. + */ + public TorControlConnection(InputStream i, OutputStream o) { + this(new InputStreamReader(i), new OutputStreamWriter(o)); + } + + public TorControlConnection(Reader i, Writer o) { + this.output = o; + if (i instanceof BufferedReader) + this.input = (BufferedReader) i; + else + this.input = new BufferedReader(i); + this.waiters = new LinkedList(); + } + + protected final void writeEscaped(String s) throws IOException { + StringTokenizer st = new StringTokenizer(s, "\n"); + while (st.hasMoreTokens()) { + String line = st.nextToken(); + if (line.startsWith(".")) + line = "."+line; + if (line.endsWith("\r")) + line += "\n"; + else + line += "\r\n"; + if (debugOutput != null) + debugOutput.print(">> "+line); + output.write(line); + } + output.write(".\r\n"); + if (debugOutput != null) + debugOutput.print(">> .\n"); + } + + protected static final String quote(String s) { + StringBuffer sb = new StringBuffer("\""); + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + switch (c) + { + case '\r': + case '\n': + case '\\': + case '\"': + sb.append('\\'); + } + sb.append(c); + } + sb.append('\"'); + return sb.toString(); + } + + protected final ArrayList readReply() throws IOException { + ArrayList reply = new ArrayList(); + char c; + do { + String line = input.readLine(); + if (line == null) { + // if line is null, the end of the stream has been reached, i.e. + // the connection to Tor has been closed! + if (reply.isEmpty()) { + // nothing received so far, can exit cleanly + return reply; + } + // received half of a reply before the connection broke down + throw new TorControlSyntaxError("Connection to Tor " + + " broke down while receiving reply!"); + } + if (debugOutput != null) + debugOutput.println("<< "+line); + if (line.length() < 4) + throw new TorControlSyntaxError("Line (\""+line+"\") too short"); + String status = line.substring(0,3); + c = line.charAt(3); + String msg = line.substring(4); + String rest = null; + if (c == '+') { + StringBuffer data = new StringBuffer(); + while (true) { + line = input.readLine(); + if (debugOutput != null) + debugOutput.print("<< "+line); + if (line.equals(".")) + break; + else if (line.startsWith(".")) + line = line.substring(1); + data.append(line).append('\n'); + } + rest = data.toString(); + } + reply.add(new ReplyLine(status, msg, rest)); + } while (c != ' '); + + return reply; + } + + protected synchronized List sendAndWaitForResponse(String s, + String rest) throws IOException { + if (parseThreadException != null) throw parseThreadException; + checkThread(); + Waiter w = new Waiter(); + if (debugOutput != null) + debugOutput.print(">> "+s); + synchronized (waiters) { + output.write(s); + if (rest != null) + writeEscaped(rest); + output.flush(); + waiters.addLast(w); + } + List lst; + try { + lst = w.getResponse(); + } catch (InterruptedException ex) { + throw new IOException("Interrupted"); + } + for (Iterator i = lst.iterator(); i.hasNext(); ) { + ReplyLine c = i.next(); + if (! c.status.startsWith("2")) + throw new TorControlError("Error reply: "+c.msg); + } + return lst; + } + + /** Helper: decode a CMD_EVENT command and dispatch it to our + * EventHandler (if any). */ + protected void handleEvent(ArrayList events) { + if (handler == null) + return; + + for (Iterator i = events.iterator(); i.hasNext(); ) { + ReplyLine line = i.next(); + int idx = line.msg.indexOf(' '); + String tp = line.msg.substring(0, idx).toUpperCase(); + String rest = line.msg.substring(idx+1); + if (tp.equals("CIRC")) { + List lst = Bytes.splitStr(null, rest); + handler.circuitStatus(lst.get(1), + lst.get(0), + lst.get(1).equals("LAUNCHED") + || lst.size() < 3 ? "" + : lst.get(2)); + } else if (tp.equals("STREAM")) { + List lst = Bytes.splitStr(null, rest); + handler.streamStatus(lst.get(1), + lst.get(0), + lst.get(3)); + // XXXX circID. + } else if (tp.equals("ORCONN")) { + List lst = Bytes.splitStr(null, rest); + handler.orConnStatus(lst.get(1), lst.get(0)); + } else if (tp.equals("BW")) { + List lst = Bytes.splitStr(null, rest); + handler.bandwidthUsed(Integer.parseInt(lst.get(0)), + Integer.parseInt(lst.get(1))); + } else if (tp.equals("NEWDESC")) { + List lst = Bytes.splitStr(null, rest); + handler.newDescriptors(lst); + } else if (tp.equals("DEBUG") || + tp.equals("INFO") || + tp.equals("NOTICE") || + tp.equals("WARN") || + tp.equals("ERR")) { + handler.message(tp, rest); + } else { + handler.unrecognized(tp, rest); + } + } + } + + + /** Sets w as the PrintWriter for debugging output, + * which writes out all messages passed between Tor and the controller. + * Outgoing messages are preceded by "\>\>" and incoming messages are preceded + * by "\<\<" + */ + public void setDebugging(PrintWriter w) { + debugOutput = w; + } + + /** Sets s as the PrintStream for debugging output, + * which writes out all messages passed between Tor and the controller. + * Outgoing messages are preceded by "\>\>" and incoming messages are preceded + * by "\<\<" + */ + public void setDebugging(PrintStream s) { + debugOutput = new PrintWriter(s, true); + } + + /** Set the EventHandler object that will be notified of any + * events Tor delivers to this connection. To make Tor send us + * events, call setEvents(). */ + public void setEventHandler(EventHandler handler) { + this.handler = handler; + } + + /** + * Start a thread to react to Tor's responses in the background. + * This is necessary to handle asynchronous events and synchronous + * responses that arrive independantly over the same socket. + */ + public synchronized Thread launchThread(boolean daemon) { + ControlParseThread th = new ControlParseThread(); + if (daemon) + th.setDaemon(true); + th.start(); + this.thread = th; + return th; + } + + protected class ControlParseThread extends Thread { + + @Override + public void run() { + try { + react(); + } catch (IOException ex) { + parseThreadException = ex; + } + } + } + + protected synchronized void checkThread() { + if (thread == null) + launchThread(true); + } + + /** helper: implement the main background loop. */ + protected void react() throws IOException { + while (true) { + ArrayList lst = readReply(); + if (lst.isEmpty()) { + // interrupted queued waiters, there won't be any response. + synchronized (waiters) { + if (!waiters.isEmpty()) { + for (Waiter w : waiters) { + w.interrupt(); + } + } + } + throw new IOException("Tor is no longer running"); + } + if ((lst.get(0)).status.startsWith("6")) + handleEvent(lst); + else { + synchronized (waiters) { + if (!waiters.isEmpty()) + { + Waiter w; + w = waiters.removeFirst(); + w.setResponse(lst); + } + } + + } + } + } + + /** Change the value of the configuration option 'key' to 'val'. + */ + public void setConf(String key, String value) throws IOException { + List lst = new ArrayList(); + lst.add(key+" "+value); + setConf(lst); + } + + /** Change the values of the configuration options stored in kvMap. */ + public void setConf(Map kvMap) throws IOException { + List lst = new ArrayList(); + for (Iterator> it = kvMap.entrySet().iterator(); it.hasNext(); ) { + Map.Entry ent = it.next(); + lst.add(ent.getKey()+" "+ent.getValue()+"\n"); + } + setConf(lst); + } + + /** Changes the values of the configuration options stored in + * kvList. Each list element in kvList is expected to be + * String of the format "key value". + * + * Tor behaves as though it had just read each of the key-value pairs + * from its configuration file. Keywords with no corresponding values have + * their configuration values reset to their defaults. setConf is + * all-or-nothing: if there is an error in any of the configuration settings, + * Tor sets none of them. + * + * When a configuration option takes multiple values, or when multiple + * configuration keys form a context-sensitive group (see getConf below), then + * setting any of the options in a setConf command is taken to reset all of + * the others. For example, if two ORBindAddress values are configured, and a + * command arrives containing a single ORBindAddress value, the new + * command's value replaces the two old values. + * + * To remove all settings for a given option entirely (and go back to its + * default value), include a String in kvList containing the key and no value. + */ + public void setConf(Collection kvList) throws IOException { + if (kvList.size() == 0) + return; + StringBuffer b = new StringBuffer("SETCONF"); + for (Iterator it = kvList.iterator(); it.hasNext(); ) { + String kv = it.next(); + int i = kv.indexOf(' '); + if (i == -1) + b.append(" ").append(kv); + b.append(" ").append(kv.substring(0,i)).append("=") + .append(quote(kv.substring(i+1))); + } + b.append("\r\n"); + sendAndWaitForResponse(b.toString(), null); + } + + /** Try to reset the values listed in the collection 'keys' to their + * default values. + **/ + public void resetConf(Collection keys) throws IOException { + if (keys.size() == 0) + return; + StringBuffer b = new StringBuffer("RESETCONF"); + for (Iterator it = keys.iterator(); it.hasNext(); ) { + String key = it.next(); + b.append(" ").append(key); + } + b.append("\r\n"); + sendAndWaitForResponse(b.toString(), null); + } + + /** Return the value of the configuration option 'key' */ + public List getConf(String key) throws IOException { + List lst = new ArrayList(); + lst.add(key); + return getConf(lst); + } + + /** Requests the values of the configuration variables listed in keys. + * Results are returned as a list of ConfigEntry objects. + * + * If an option appears multiple times in the configuration, all of its + * key-value pairs are returned in order. + * + * Some options are context-sensitive, and depend on other options with + * different keywords. These cannot be fetched directly. Currently there + * is only one such option: clients should use the "HiddenServiceOptions" + * virtual keyword to get all HiddenServiceDir, HiddenServicePort, + * HiddenServiceNodes, and HiddenServiceExcludeNodes option settings. + */ + public List getConf(Collection keys) throws IOException { + StringBuffer sb = new StringBuffer("GETCONF"); + for (Iterator it = keys.iterator(); it.hasNext(); ) { + String key = it.next(); + sb.append(" ").append(key); + } + sb.append("\r\n"); + List lst = sendAndWaitForResponse(sb.toString(), null); + List result = new ArrayList(); + for (Iterator it = lst.iterator(); it.hasNext(); ) { + String kv = (it.next()).msg; + int idx = kv.indexOf('='); + if (idx >= 0) + result.add(new ConfigEntry(kv.substring(0, idx), + kv.substring(idx+1))); + else + result.add(new ConfigEntry(kv)); + } + return result; + } + + /** Request that the server inform the client about interesting events. + * Each element of events is one of the following Strings: + * ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" | + * "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] . + * + * Any events not listed in the events are turned off; thus, calling + * setEvents with an empty events argument turns off all event reporting. + */ + public void setEvents(List events) throws IOException { + StringBuffer sb = new StringBuffer("SETEVENTS"); + for (Iterator it = events.iterator(); it.hasNext(); ) { + sb.append(" ").append(it.next()); + } + sb.append("\r\n"); + sendAndWaitForResponse(sb.toString(), null); + } + + /** Authenticates the controller to the Tor server. + * + * By default, the current Tor implementation trusts all local users, and + * the controller can authenticate itself by calling authenticate(new byte[0]). + * + * If the 'CookieAuthentication' option is true, Tor writes a "magic cookie" + * file named "control_auth_cookie" into its data directory. To authenticate, + * the controller must send the contents of this file in auth. + * + * If the 'HashedControlPassword' option is set, auth must contain the salted + * hash of a secret password. The salted hash is computed according to the + * S2K algorithm in RFC 2440 (OpenPGP), and prefixed with the s2k specifier. + * This is then encoded in hexadecimal, prefixed by the indicator sequence + * "16:". + * + * You can generate the salt of a password by calling + * 'tor --hash-password ' + * or by using the provided PasswordDigest class. + * To authenticate under this scheme, the controller sends Tor the original + * secret that was used to generate the password. + */ + public void authenticate(byte[] auth) throws IOException { + String cmd = "AUTHENTICATE " + Bytes.hex(auth) + "\r\n"; + sendAndWaitForResponse(cmd, null); + } + + /** Instructs the server to write out its configuration options into its torrc. + */ + public void saveConf() throws IOException { + sendAndWaitForResponse("SAVECONF\r\n", null); + } + + /** Sends a signal from the controller to the Tor server. + * signal is one of the following Strings: + *
    + *
  • "RELOAD" or "HUP" : Reload config items, refetch directory
  • + *
  • "SHUTDOWN" or "INT" : Controlled shutdown: if server is an OP, exit immediately. + * If it's an OR, close listeners and exit after 30 seconds
  • + *
  • "DUMP" or "USR1" : Dump stats: log information about open connections and circuits
  • + *
  • "DEBUG" or "USR2" : Debug: switch all open logs to loglevel debug
  • + *
  • "HALT" or "TERM" : Immediate shutdown: clean up and exit now
  • + *
+ */ + public void signal(String signal) throws IOException { + String cmd = "SIGNAL " + signal + "\r\n"; + sendAndWaitForResponse(cmd, null); + } + + /** Send a signal to the Tor process to shut it down or halt it. + * Does not wait for a response. */ + public void shutdownTor(String signal) throws IOException { + String s = "SIGNAL " + signal + "\r\n"; + Waiter w = new Waiter(); + if (debugOutput != null) + debugOutput.print(">> "+s); + synchronized (waiters) { + output.write(s); + output.flush(); + } + } + + /** Tells the Tor server that future SOCKS requests for connections to a set of original + * addresses should be replaced with connections to the specified replacement + * addresses. Each element of kvLines is a String of the form + * "old-address new-address". This function returns the new address mapping. + * + * The client may decline to provide a body for the original address, and + * instead send a special null address ("0.0.0.0" for IPv4, "::0" for IPv6, or + * "." for hostname), signifying that the server should choose the original + * address itself, and return that address in the reply. The server + * should ensure that it returns an element of address space that is unlikely + * to be in actual use. If there is already an address mapped to the + * destination address, the server may reuse that mapping. + * + * If the original address is already mapped to a different address, the old + * mapping is removed. If the original address and the destination address + * are the same, the server removes any mapping in place for the original + * address. + * + * Mappings set by the controller last until the Tor process exits: + * they never expire. If the controller wants the mapping to last only + * a certain time, then it must explicitly un-map the address when that + * time has elapsed. + */ + public Map mapAddresses(Collection kvLines) throws IOException { + StringBuffer sb = new StringBuffer("MAPADDRESS"); + for (Iterator it = kvLines.iterator(); it.hasNext(); ) { + String kv = it.next(); + int i = kv.indexOf(' '); + sb.append(" ").append(kv.substring(0,i)).append("=") + .append(quote(kv.substring(i+1))); + } + sb.append("\r\n"); + List lst = sendAndWaitForResponse(sb.toString(), null); + Map result = new HashMap(); + for (Iterator it = lst.iterator(); it.hasNext(); ) { + String kv = (it.next()).msg; + int idx = kv.indexOf('='); + result.put(kv.substring(0, idx), + kv.substring(idx+1)); + } + return result; + } + + public Map mapAddresses(Map addresses) throws IOException { + List kvList = new ArrayList(); + for (Iterator> it = addresses.entrySet().iterator(); it.hasNext(); ) { + Map.Entry e = it.next(); + kvList.add(e.getKey()+" "+e.getValue()); + } + return mapAddresses(kvList); + } + + public String mapAddress(String fromAddr, String toAddr) throws IOException { + List lst = new ArrayList(); + lst.add(fromAddr+" "+toAddr+"\n"); + Map m = mapAddresses(lst); + return m.get(fromAddr); + } + + /** Queries the Tor server for keyed values that are not stored in the torrc + * configuration file. Returns a map of keys to values. + * + * Recognized keys include: + *
    + *
  • "version" : The version of the server's software, including the name + * of the software. (example: "Tor 0.0.9.4")
  • + *
  • "desc/id/" or "desc/name/" : the latest server + * descriptor for a given OR, NUL-terminated. If no such OR is known, the + * corresponding value is an empty string.
  • + *
  • "network-status" : a space-separated list of all known OR identities. + * This is in the same format as the router-status line in directories; + * see tor-spec.txt for details.
  • + *
  • "addr-mappings/all"
  • + *
  • "addr-mappings/config"
  • + *
  • "addr-mappings/cache"
  • + *
  • "addr-mappings/control" : a space-separated list of address mappings, each + * in the form of "from-address=to-address". The 'config' key + * returns those address mappings set in the configuration; the 'cache' + * key returns the mappings in the client-side DNS cache; the 'control' + * key returns the mappings set via the control interface; the 'all' + * target returns the mappings set through any mechanism.
  • + *
  • "circuit-status" : A series of lines as for a circuit status event. Each line is of the form: + * "CircuitID CircStatus Path"
  • + *
  • "stream-status" : A series of lines as for a stream status event. Each is of the form: + * "StreamID StreamStatus CircID Target"
  • + *
  • "orconn-status" : A series of lines as for an OR connection status event. Each is of the + * form: "ServerID ORStatus"
  • + *
+ */ + public Map getInfo(Collection keys) throws IOException { + StringBuffer sb = new StringBuffer("GETINFO"); + for (Iterator it = keys.iterator(); it.hasNext(); ) { + sb.append(" ").append(it.next()); + } + sb.append("\r\n"); + List lst = sendAndWaitForResponse(sb.toString(), null); + Map m = new HashMap(); + for (Iterator it = lst.iterator(); it.hasNext(); ) { + ReplyLine line = it.next(); + int idx = line.msg.indexOf('='); + if (idx<0) + break; + String k = line.msg.substring(0,idx); + String v; + if (line.rest != null) { + v = line.rest; + } else { + v = line.msg.substring(idx+1); + } + m.put(k, v); + } + return m; + } + + + + /** Return the value of the information field 'key' */ + public String getInfo(String key) throws IOException { + List lst = new ArrayList(); + lst.add(key); + Map m = getInfo(lst); + return m.get(key); + } + + /** An extendCircuit request takes one of two forms: either the circID is zero, in + * which case it is a request for the server to build a new circuit according + * to the specified path, or the circID is nonzero, in which case it is a + * request for the server to extend an existing circuit with that ID according + * to the specified path. + * + * If successful, returns the Circuit ID of the (maybe newly created) circuit. + */ + public String extendCircuit(String circID, String path) throws IOException { + List lst = sendAndWaitForResponse( + "EXTENDCIRCUIT "+circID+" "+path+"\r\n", null); + return (lst.get(0)).msg; + } + + /** Informs the Tor server that the stream specified by streamID should be + * associated with the circuit specified by circID. + * + * Each stream may be associated with + * at most one circuit, and multiple streams may share the same circuit. + * Streams can only be attached to completed circuits (that is, circuits that + * have sent a circuit status "BUILT" event or are listed as built in a + * getInfo circuit-status request). + * + * If circID is 0, responsibility for attaching the given stream is + * returned to Tor. + * + * By default, Tor automatically attaches streams to + * circuits itself, unless the configuration variable + * "__LeaveStreamsUnattached" is set to "1". Attempting to attach streams + * via TC when "__LeaveStreamsUnattached" is false may cause a race between + * Tor and the controller, as both attempt to attach streams to circuits. + */ + public void attachStream(String streamID, String circID) + throws IOException { + sendAndWaitForResponse("ATTACHSTREAM "+streamID+" "+circID+"\r\n", null); + } + + /** Tells Tor about the server descriptor in desc. + * + * The descriptor, when parsed, must contain a number of well-specified + * fields, including fields for its nickname and identity. + */ + // More documentation here on format of desc? + // No need for return value? control-spec.txt says reply is merely "250 OK" on success... + public String postDescriptor(String desc) throws IOException { + List lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc); + return (lst.get(0)).msg; + } + + /** Tells Tor to change the exit address of the stream identified by streamID + * to address. No remapping is performed on the new provided address. + * + * To be sure that the modified address will be used, this event must be sent + * after a new stream event is received, and before attaching this stream to + * a circuit. + */ + public void redirectStream(String streamID, String address) throws IOException { + sendAndWaitForResponse("REDIRECTSTREAM "+streamID+" "+address+"\r\n", + null); + } + + /** Tells Tor to close the stream identified by streamID. + * reason should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal: + *
    + *
  • 1 -- REASON_MISC (catch-all for unlisted reasons)
  • + *
  • 2 -- REASON_RESOLVEFAILED (couldn't look up hostname)
  • + *
  • 3 -- REASON_CONNECTREFUSED (remote host refused connection)
  • + *
  • 4 -- REASON_EXITPOLICY (OR refuses to connect to host or port)
  • + *
  • 5 -- REASON_DESTROY (Circuit is being destroyed)
  • + *
  • 6 -- REASON_DONE (Anonymized TCP connection was closed)
  • + *
  • 7 -- REASON_TIMEOUT (Connection timed out, or OR timed out while connecting)
  • + *
  • 8 -- (unallocated)
  • + *
  • 9 -- REASON_HIBERNATING (OR is temporarily hibernating)
  • + *
  • 10 -- REASON_INTERNAL (Internal error at the OR)
  • + *
  • 11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)
  • + *
  • 12 -- REASON_CONNRESET (Connection was unexpectedly reset)
  • + *
  • 13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)
  • + *
+ * + * Tor may hold the stream open for a while to flush any data that is pending. + */ + public void closeStream(String streamID, byte reason) + throws IOException { + sendAndWaitForResponse("CLOSESTREAM "+streamID+" "+reason+"\r\n",null); + } + + /** Tells Tor to close the circuit identified by circID. + * If ifUnused is true, do not close the circuit unless it is unused. + */ + public void closeCircuit(String circID, boolean ifUnused) throws IOException { + sendAndWaitForResponse("CLOSECIRCUIT "+circID+ + (ifUnused?" IFUNUSED":"")+"\r\n", null); + } + + /** Tells Tor to exit when this control connection is closed. This command + * was added in Tor 0.2.2.28-beta. + */ + public void takeOwnership() throws IOException { + sendAndWaitForResponse("TAKEOWNERSHIP\r\n", null); + } + + /** + * Tells Tor to generate and set up a new onion service using the best + * supported algorithm. + *

+ * ADD_ONION was added in Tor 0.2.7.1-alpha. + */ + public Map addOnion(Map portLines) + throws IOException { + return addOnion("NEW:BEST", portLines, null); + } + + /** + * Tells Tor to generate and set up a new onion service using the best + * supported algorithm. + *

+ * ADD_ONION was added in Tor 0.2.7.1-alpha. + */ + public Map addOnion(Map portLines, + boolean ephemeral, boolean detach) + throws IOException { + return addOnion("NEW:BEST", portLines, ephemeral, detach); + } + + /** + * Tells Tor to set up an onion service using the provided private key. + *

+ * ADD_ONION was added in Tor 0.2.7.1-alpha. + */ + public Map addOnion(String privKey, + Map portLines) + throws IOException { + return addOnion(privKey, portLines, null); + } + + /** + * Tells Tor to set up an onion service using the provided private key. + *

+ * ADD_ONION was added in Tor 0.2.7.1-alpha. + */ + public Map addOnion(String privKey, + Map portLines, + boolean ephemeral, boolean detach) + throws IOException { + List flags = new ArrayList(); + if (ephemeral) + flags.add("DiscardPK"); + if (detach) + flags.add("Detach"); + return addOnion(privKey, portLines, flags); + } + + /** + * Tells Tor to set up an onion service. + *

+ * ADD_ONION was added in Tor 0.2.7.1-alpha. + */ + public Map addOnion(String privKey, + Map portLines, + List flags) + throws IOException { + if (privKey.indexOf(':') < 0) + throw new IllegalArgumentException("Invalid privKey"); + if (portLines == null || portLines.size() < 1) + throw new IllegalArgumentException("Must provide at least one port line"); + StringBuilder b = new StringBuilder(); + b.append("ADD_ONION ").append(privKey); + if (flags != null && flags.size() > 0) { + b.append(" Flags="); + String separator = ""; + for (String flag : flags) { + b.append(separator).append(flag); + separator = ","; + } + } + for (Map.Entry portLine : portLines.entrySet()) { + int virtPort = portLine.getKey(); + String target = portLine.getValue(); + b.append(" Port=").append(virtPort); + if (target != null && target.length() > 0) + b.append(",").append(target); + } + b.append("\r\n"); + List lst = sendAndWaitForResponse(b.toString(), null); + Map ret = new HashMap(); + ret.put(HS_ADDRESS, (lst.get(0)).msg.split("=", 2)[1]); + if (lst.size() > 2) + ret.put(HS_PRIVKEY, (lst.get(1)).msg.split("=", 2)[1]); + return ret; + } + + /** + * Tells Tor to take down an onion service previously set up with + * addOnion(). The hostname excludes the .onion extension. + *

+ * DEL_ONION was added in Tor 0.2.7.1-alpha. + */ + public void delOnion(String hostname) throws IOException { + sendAndWaitForResponse("DEL_ONION " + hostname + "\r\n", null); + } + + /** Tells Tor to forget any cached client state relating to the hidden + * service with the given hostname (excluding the .onion extension). + */ + public void forgetHiddenService(String hostname) throws IOException { + sendAndWaitForResponse("HSFORGET " + hostname + "\r\n", null); + } +} + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/TorControlError.java b/bramble-core/src/main/java/net/freehaven/tor/control/TorControlError.java new file mode 100644 index 000000000..197c4a844 --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/TorControlError.java @@ -0,0 +1,39 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control; + +import java.io.IOException; + +/** + * An exception raised when Tor tells us about an error. + */ +public class TorControlError extends IOException { + + static final long serialVersionUID = 3; + + private final int errorType; + + public TorControlError(int type, String s) { + super(s); + errorType = type; + } + + public TorControlError(String s) { + this(-1, s); + } + + public int getErrorType() { + return errorType; + } + + public String getErrorMsg() { + try { + if (errorType == -1) + return null; + return TorControlCommands.ERROR_MSGS[errorType]; + } catch (ArrayIndexOutOfBoundsException ex) { + return "Unrecongized error #"+errorType; + } + } +} + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/TorControlSyntaxError.java b/bramble-core/src/main/java/net/freehaven/tor/control/TorControlSyntaxError.java new file mode 100644 index 000000000..af81d18c6 --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/TorControlSyntaxError.java @@ -0,0 +1,16 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control; + +import java.io.IOException; + +/** + * An exception raised when Tor behaves in an unexpected way. + */ +public class TorControlSyntaxError extends IOException { + + static final long serialVersionUID = 3; + + public TorControlSyntaxError(String s) { super(s); } +} + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/examples/.cvsignore b/bramble-core/src/main/java/net/freehaven/tor/control/examples/.cvsignore new file mode 100644 index 000000000..6b468b62a --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/examples/.cvsignore @@ -0,0 +1 @@ +*.class diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/examples/DebuggingEventHandler.java b/bramble-core/src/main/java/net/freehaven/tor/control/examples/DebuggingEventHandler.java new file mode 100644 index 000000000..c2f5756f3 --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/examples/DebuggingEventHandler.java @@ -0,0 +1,44 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control.examples; + +import java.io.PrintWriter; +import java.util.Iterator; +import net.freehaven.tor.control.EventHandler; + +public class DebuggingEventHandler implements EventHandler { + + private final PrintWriter out; + + public DebuggingEventHandler(PrintWriter p) { + out = p; + } + + public void circuitStatus(String status, String circID, String path) { + out.println("Circuit "+circID+" is now "+status+" (path="+path+")"); + } + public void streamStatus(String status, String streamID, String target) { + out.println("Stream "+streamID+" is now "+status+" (target="+target+")"); + } + public void orConnStatus(String status, String orName) { + out.println("OR connection to "+orName+" is now "+status); + } + public void bandwidthUsed(long read, long written) { + out.println("Bandwidth usage: "+read+" bytes read; "+ + written+" bytes written."); + } + public void newDescriptors(java.util.List orList) { + out.println("New descriptors for routers:"); + for (Iterator i = orList.iterator(); i.hasNext(); ) + out.println(" "+i.next()); + } + public void message(String type, String msg) { + out.println("["+type+"] "+msg.trim()); + } + + public void unrecognized(String type, String msg) { + out.println("unrecognized event ["+type+"] "+msg.trim()); + } + +} + diff --git a/bramble-core/src/main/java/net/freehaven/tor/control/examples/Main.java b/bramble-core/src/main/java/net/freehaven/tor/control/examples/Main.java new file mode 100644 index 000000000..3eb812944 --- /dev/null +++ b/bramble-core/src/main/java/net/freehaven/tor/control/examples/Main.java @@ -0,0 +1,146 @@ +// Copyright 2005 Nick Mathewson, Roger Dingledine +// See LICENSE file for copying information +package net.freehaven.tor.control.examples; + +import net.freehaven.tor.control.*; +import java.io.EOFException; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.Arrays; +import java.util.Map; +import java.util.Iterator; + +public class Main implements TorControlCommands { + + public static void main(String args[]) { + if (args.length < 1) { + System.err.println("No command given."); + return; + } + try { + if (args[0].equals("set-config")) { + setConfig(args); + } else if (args[0].equals("get-config")) { + getConfig(args); + } else if (args[0].equals("get-info")) { + getInfo(args); + } else if (args[0].equals("listen")) { + listenForEvents(args); + } else if (args[0].equals("signal")) { + signal(args); + } else if (args[0].equals("auth")) { + authDemo(args); + } else { + System.err.println("Unrecognized command: "+args[0]); + } + } catch (EOFException ex) { + System.out.println("Control socket closed by Tor."); + } catch (TorControlError ex) { + System.err.println("Error from Tor process: "+ + ex+" ["+ex.getErrorMsg()+"]"); + } catch (IOException ex) { + System.err.println("IO exception when talking to Tor process: "+ + ex); + ex.printStackTrace(System.err); + } + } + + private static TorControlConnection getConnection(String[] args, + boolean daemon) throws IOException { + Socket s = new Socket("127.0.0.1", 9100); + TorControlConnection conn = new TorControlConnection(s); + conn.launchThread(daemon); + conn.authenticate(new byte[0]); + return conn; + } + + private static TorControlConnection getConnection(String[] args) + throws IOException { + return getConnection(args, true); + } + + public static void setConfig(String[] args) throws IOException { + // Usage: "set-config [-save] key value key value key value" + TorControlConnection conn = getConnection(args); + ArrayList lst = new ArrayList(); + int i = 1; + boolean save = false; + if (args[i].equals("-save")) { + save = true; + ++i; + } + for (; i < args.length; i +=2) { + lst.add(args[i]+" "+args[i+1]); + } + conn.setConf(lst); + if (save) { + conn.saveConf(); + } + } + + public static void getConfig(String[] args) throws IOException { + // Usage: get-config key key key + TorControlConnection conn = getConnection(args); + List lst = conn.getConf(Arrays.asList(args).subList(1,args.length)); + for (Iterator i = lst.iterator(); i.hasNext(); ) { + ConfigEntry e = i.next(); + System.out.println("KEY: "+e.key); + System.out.println("VAL: "+e.value); + } + } + + public static void getInfo(String[] args) throws IOException { + TorControlConnection conn = getConnection(args); + Map m = conn.getInfo(Arrays.asList(args).subList(1,args.length)); + for (Iterator> i = m.entrySet().iterator(); i.hasNext(); ) { + Map.Entry e = i.next(); + System.out.println("KEY: "+e.getKey()); + System.out.println("VAL: "+e.getValue()); + } + } + + public static void listenForEvents(String[] args) throws IOException { + // Usage: listen [circ|stream|orconn|bw|newdesc|info|notice|warn|error]* + TorControlConnection conn = getConnection(args, false); + ArrayList lst = new ArrayList(); + for (int i = 1; i < args.length; ++i) { + lst.add(args[i]); + } + conn.setEventHandler( + new DebuggingEventHandler(new PrintWriter(System.out, true))); + conn.setEvents(lst); + } + + public static void signal(String[] args) throws IOException { + // Usage signal [reload|shutdown|dump|debug|halt] + TorControlConnection conn = getConnection(args, false); + // distinguish shutdown signal from other signals + if ("SHUTDOWN".equalsIgnoreCase(args[1]) + || "HALT".equalsIgnoreCase(args[1])) { + conn.shutdownTor(args[1].toUpperCase()); + } else { + conn.signal(args[1].toUpperCase()); + } + } + + public static void authDemo(String[] args) throws IOException { + + PasswordDigest pwd = PasswordDigest.generateDigest(); + Socket s = new Socket("127.0.0.1", 9100); + TorControlConnection conn = new TorControlConnection(s); + conn.launchThread(true); + conn.authenticate(new byte[0]); + + conn.setConf("HashedControlPassword", pwd.getHashedPassword()); + + s = new Socket("127.0.0.1", 9100); + conn = new TorControlConnection(s); + conn.launchThread(true); + conn.authenticate(pwd.getSecret()); + } + +} + diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index fe5dad17b..0720acfff 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -153,7 +153,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { configFile = new File(torDirectory, "torrc"); doneFile = new File(torDirectory, "done"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); - jtorCTLFile = new File(torDirectory, "jtorctlog"); + jtorCTLFile = new File(torDirectory, "jtorctl.out"); connectionStatus = new ConnectionStatus(); // Don't execute more than one connection status check at a time connectionStatusExecutor = @@ -262,10 +262,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { // Open a control connection and authenticate using the cookie file controlSocket = new Socket("127.0.0.1", CONTROL_PORT); controlConnection = new TorControlConnection(controlSocket); + controlConnection.authenticate(read(cookieFile)); controlConnection.setConf("LOG", "debug file torlog"); controlConnection.setDebugging( new PrintWriter(new FileOutputStream(jtorCTLFile), true)); - controlConnection.authenticate(read(cookieFile)); // FIXME Spam the control port with enable/disable network commands spamControlPort(); // Tell Tor to exit when the control connection is closed diff --git a/bramble-core/witness.gradle b/bramble-core/witness.gradle index e541dea73..495cccf3e 100644 --- a/bramble-core/witness.gradle +++ b/bramble-core/witness.gradle @@ -22,7 +22,6 @@ dependencyVerification { 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb', - 'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619', 'org.codehaus.mojo:animal-sniffer-annotations:1.14:animal-sniffer-annotations-1.14.jar:2068320bd6bad744c3673ab048f67e30bef8f518996fa380033556600669905d',