[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[or-cvs] r11066: From Karsten: drop TorControlConnection0.java, and merge Tor (in torctl/trunk: . java/net/freehaven/tor/control)
Author: nickm
Date: 2007-08-08 16:44:14 -0400 (Wed, 08 Aug 2007)
New Revision: 11066
Removed:
torctl/trunk/java/net/freehaven/tor/control/TorControlConnection0.java
torctl/trunk/java/net/freehaven/tor/control/TorControlConnection1.java
Modified:
torctl/trunk/
torctl/trunk/ChangeLog
torctl/trunk/java/net/freehaven/tor/control/TorControlConnection.java
Log:
r14101@catbus: nickm | 2007-08-08 16:43:33 -0400
From Karsten: drop TorControlConnection0.java, and merge TorControlConnection1 into TorControlConnection.
Property changes on: torctl/trunk
___________________________________________________________________
svk:merge ticket from /torctl/trunk [r14101] on 8246c3cf-6607-4228-993b-4d95d33730f1
Modified: torctl/trunk/ChangeLog
===================================================================
--- torctl/trunk/ChangeLog 2007-08-08 14:43:30 UTC (rev 11065)
+++ torctl/trunk/ChangeLog 2007-08-08 20:44:14 UTC (rev 11066)
@@ -8,4 +8,6 @@
- Implement resetConf method in controllers.
- Fix bug when sending a signal that kills Tor; avoid a
NullPointerException when Tor exits. (java; from Karsten Loesing)
+ - Remove all v0 support; merge TorControlConnection1 into
+ TorControlConnection. (Java; from Karsten)
Modified: torctl/trunk/java/net/freehaven/tor/control/TorControlConnection.java
===================================================================
--- torctl/trunk/java/net/freehaven/tor/control/TorControlConnection.java 2007-08-08 14:43:30 UTC (rev 11065)
+++ torctl/trunk/java/net/freehaven/tor/control/TorControlConnection.java 2007-08-08 20:44:14 UTC (rev 11066)
@@ -7,13 +7,15 @@
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;
-/** A connection to a running Tor process. */
-public abstract class TorControlConnection// implements TorControlCommands {
+/** A connection to a running Tor process as specified in control-spec.txt. */
+public class TorControlConnection implements TorControlCommands
{
protected EventHandler handler;
@@ -22,6 +24,12 @@
protected ControlParseThread thread;
+ protected java.io.BufferedReader input;
+
+ protected java.io.Writer output;
+
+ protected java.io.PrintWriter debugOutput;
+
static class Waiter {
Object response;
public synchronized Object getResponse() {
@@ -40,45 +48,220 @@
}
}
- protected static int detectVersion(java.io.InputStream input,
- java.io.OutputStream output)
- throws IOException
- {
- java.io.DataInputStream dInput = new java.io.DataInputStream(input);
- byte out[] = { 0, 0, 13, 10 };
- output.write(out);
+ static class ReplyLine {
+ public String status;
+ public String msg;
+ public String rest;
- int len = dInput.readUnsignedShort();
- int tp = dInput.readUnsignedShort();
- if (tp == 0) {
- byte err[] = new byte[len];
- dInput.readFully(err);
- return 0;
- } else if ((len & 0xff00) != 0x0a00 &&
- (len & 0x00ff) != 0x000a &&
- (tp & 0xff00) != 0x0a00 &&
- (tp & 0x00ff) != 0x000a) {
- while (input.read() != '\n')
- ;
+ ReplyLine(String status, String msg, String rest) {
+ this.status = status; this.msg = msg; this.rest = rest;
}
- return 1;
}
-
+
public static TorControlConnection getConnection(java.net.Socket sock)
throws IOException
{
- int version = detectVersion(sock.getInputStream(),
- sock.getOutputStream());
- if (version == 0)
- return new TorControlConnection0(sock);
- else
- return new TorControlConnection1(sock);
+ return new TorControlConnection(sock);
}
- protected TorControlConnection() {
+ /** 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(java.net.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(java.io.InputStream i, java.io.OutputStream o)
+ throws IOException {
+ this(new java.io.InputStreamReader(i),
+ new java.io.OutputStreamWriter(o));
+ }
+
+ public TorControlConnection(java.io.Reader i, java.io.Writer o)
+ throws IOException {
+ this.output = o;
+ if (i instanceof java.io.BufferedReader)
+ this.input = (java.io.BufferedReader) i;
+ else
+ this.input = new java.io.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;
+ } else {
+ // 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 ArrayList sendAndWaitForResponse(String s,String rest)
+ throws IOException {
+ checkThread();
+ Waiter w = new Waiter();
+ if (debugOutput != null)
+ debugOutput.print(">> "+s);
+ synchronized (waiters) {
+ output.write(s);
+ output.flush();
+ if (rest != null)
+ writeEscaped(rest);
+ waiters.addLast(w);
+ }
+ ArrayList lst = (ArrayList) w.getResponse();
+ for (Iterator i = lst.iterator(); i.hasNext(); ) {
+ ReplyLine c = (ReplyLine) 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 = (ReplyLine) 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((String)lst.get(1),
+ (String)lst.get(0),
+ (String)lst.get(2));
+ } else if (tp.equals("STREAM")) {
+ List lst = Bytes.splitStr(null, rest);
+ handler.streamStatus((String)lst.get(1),
+ (String)lst.get(0),
+ (String)lst.get(3));
+ // XXXX circID.
+ } else if (tp.equals("ORCONN")) {
+ List lst = Bytes.splitStr(null, rest);
+ handler.orConnStatus((String)lst.get(1), (String)lst.get(0));
+ } else if (tp.equals("BW")) {
+ List lst = Bytes.splitStr(null, rest);
+ handler.bandwidthUsed(Integer.parseInt((String)lst.get(0)),
+ Integer.parseInt((String)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 <b>w</b> 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(java.io.PrintWriter w) {
+ if (w instanceof java.io.PrintWriter)
+ debugOutput = (java.io.PrintWriter) w;
+ else
+ debugOutput = new java.io.PrintWriter(w, true);
+ }
+
+ /** Sets <b>s</b> 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(java.io.PrintStream s) {
+ debugOutput = new java.io.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(). */
@@ -124,7 +307,25 @@
launchThread(true);
}
- protected abstract void react() throws IOException;
+ /** helper: implement the main background loop. */
+ protected void react() throws IOException {
+ while (true) {
+ ArrayList lst = readReply();
+ if (lst.isEmpty()) {
+ // connection has been closed remotely! end the loop!
+ return;
+ }
+ if (((ReplyLine)lst.get(0)).status.startsWith("6"))
+ handleEvent(lst);
+ else {
+ Waiter w;
+ synchronized (waiters) {
+ w = (Waiter) waiters.removeFirst();
+ }
+ w.setResponse(lst);
+ }
+ }
+ }
/** Change the value of the configuration option 'key' to 'val'.
*/
@@ -144,14 +345,56 @@
setConf(lst);
}
- /** Change the values of the configuration options stored in
- * 'kvList'. (The format is "key value"). */
- public abstract void setConf(Collection kvList) throws IOException;
-
+ /** Changes the values of the configuration options stored in
+ * <b>kvList</b>. Each list element in <b>kvList</b> 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 <b>kvList</b> 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 = (String) 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 abstract void resetConf(Collection keys) throws IOException;
+ 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 = (String) 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 {
@@ -160,30 +403,169 @@
return getConf(lst);
}
- /** Return a key-value map for the configuration options in 'keys' */
- public abstract List getConf(Collection keys) throws IOException;
+ /** Requests the values of the configuration variables listed in <b>keys</b>.
+ * 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 = (String) it.next();
+ sb.append(" ").append(key);
+ }
+ sb.append("\r\n");
+ ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
+ ArrayList result = new ArrayList();
+ for (Iterator it = lst.iterator(); it.hasNext(); ) {
+ String kv = ((ReplyLine) 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;
+ }
- /** Tell Tor to begin sending us events of the types listed in 'events'.
- * Elements must be one of the EVENT_* values from TorControlCommands */
- public abstract void setEvents(List events) throws IOException;
+ /** Request that the server inform the client about interesting events.
+ * Each element of <b>events</b> is one of the following Strings:
+ * ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" |
+ * "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] .
+ *
+ * Any events not listed in the <b>events</b> are turned off; thus, calling
+ * setEvents with an empty <b>events</b> 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(); ) {
+ Object event = it.next();
+ if (event instanceof String) {
+ sb.append(" ").append((String)event);
+ } else {
+ int i = ((Number) event).intValue();
+ sb.append(" ").append(EVENT_NAMES[i]);
+ }
+ }
+ sb.append("\r\n");
+ sendAndWaitForResponse(sb.toString(), null);
+ }
- /** Send Tor an authentication sequence 'auth' */
- // XXXX more info about how to set this up securely.
- public abstract void authenticate(byte[] auth) throws IOException;
+ /** 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 <b>auth</b>.
+ *
+ * If the 'HashedControlPassword' option is set, <b>auth</b> 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 <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);
+ }
- /** Tell Tor to save the value of its configuration to disk. */
- public abstract void saveConf() throws IOException;
+ /** Instructs the server to write out its configuration options into its torrc.
+ */
+ public void saveConf() throws IOException {
+ sendAndWaitForResponse("SAVECONF\r\n", null);
+ }
- /** Send a signal to the Tor process. */
- public abstract void signal(String signal) throws IOException;
+ /** Sends a signal from the controller to the Tor server.
+ * <b>signal</b> is one of the following Strings:
+ * <ul>
+ * <li>"RELOAD" or "HUP" : Reload config items, refetch directory</li>
+ * <li>"SHUTDOWN" or "INT" : Controlled shutdown: if server is an OP, exit immediately.
+ * If it's an OR, close listeners and exit after 30 seconds</li>
+ * <li>"DUMP" or "USR1" : Dump stats: log information about open connections and circuits</li>
+ * <li>"DEBUG" or "USR2" : Debug: switch all open logs to loglevel debug</li>
+ * <li>"HALT" or "TERM" : Immediate shutdown: clean up and exit now</li>
+ * </ul>
+ */
+ 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 abstract void shutdownTor(String signal) throws IOException;
+ public void shutdownTor(String signal) throws IOException {
+ String s = "SIGNAL " + signal + "\r\n";
+ Waiter w = new Waiter();
+ if (debugOutput != null)
+ debugOutput.print(">> "+s);
+ if (this.thread != null) {
+ this.thread.stopListening();
+ }
+ synchronized (waiters) {
+ output.write(s);
+ output.flush();
+ waiters.addLast(w); // Prevent react() from finding the list empty
+ }
+ }
- /** Tell Tor to replace incoming addresses with those as listed in 'kvLines'.
+ /** 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 <b>kvLines</b> 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 abstract Map mapAddresses(Collection kvLines) throws IOException;
+ public Map mapAddresses(Collection kvLines) throws IOException {
+ StringBuffer sb = new StringBuffer("MAPADDRESS");
+ for (Iterator it = kvLines.iterator(); it.hasNext(); ) {
+ String kv = (String) it.next();
+ int i = kv.indexOf(' ');
+ sb.append(" ").append(kv.substring(0,i)).append("=")
+ .append(quote(kv.substring(i+1)));
+ }
+ sb.append("\r\n");
+ ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
+ Map result = new HashMap();
+ for (Iterator it = lst.iterator(); it.hasNext(); ) {
+ String kv = ((ReplyLine) 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();
@@ -201,9 +583,63 @@
return (String) m.get(fromAddr);
}
- /** Look up the information values listed in keys. */
- public abstract Map getInfo(Collection keys) throws IOException;
-
+ /** 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:
+ * <ul>
+ * <li>"version" : The version of the server's software, including the name
+ * of the software. (example: "Tor 0.0.9.4")</li>
+ * <li>"desc/id/<OR identity>" or "desc/name/<OR nickname>" : the latest server
+ * descriptor for a given OR, NUL-terminated. If no such OR is known, the
+ * corresponding value is an empty string.</li>
+ * <li>"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.</li>
+ * <li>"addr-mappings/all"</li>
+ * <li>"addr-mappings/config"</li>
+ * <li>"addr-mappings/cache"</li>
+ * <li>"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.</li>
+ * <li>"circuit-status" : A series of lines as for a circuit status event. Each line is of the form:
+ * "CircuitID CircStatus Path"</li>
+ * <li>"stream-status" : A series of lines as for a stream status event. Each is of the form:
+ * "StreamID StreamStatus CircID Target"</li>
+ * <li>"orconn-status" : A series of lines as for an OR connection status event. Each is of the
+ * form: "ServerID ORStatus"</li>
+ * </ul>
+ */
+ public Map getInfo(Collection keys) throws IOException {
+ StringBuffer sb = new StringBuffer("GETINFO");
+ for (Iterator it = keys.iterator(); it.hasNext(); ) {
+ sb.append(" ").append((String)it.next());
+ }
+ sb.append("\r\n");
+ ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
+ Map m = new HashMap();
+ for (Iterator it = lst.iterator(); it.hasNext(); ) {
+ ReplyLine line = (ReplyLine) it.next();
+ int idx = line.msg.indexOf('=');
+ if (idx<0)
+ break;
+ String k = line.msg.substring(0,idx);
+ Object 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();
@@ -212,34 +648,98 @@
return (String) m.get(key);
}
- /**
- * Tell Tor to extend the circuit identified by 'circID' through the
- * servers named in the list 'path'.
+ /** An extendCircuit request takes one of two forms: either the <b>circID</b> is zero, in
+ * which case it is a request for the server to build a new circuit according
+ * to the specified path, or the <b>circID</b> is nonzero, in which case it is a
+ * request for the server to extend an existing circuit with that ID according
+ * to the specified <b>path</b>.
+ *
+ * If successful, returns the Circuit ID of the (maybe newly created) circuit.
*/
- public abstract String extendCircuit(String circID, String path) throws IOException;
+ public String extendCircuit(String circID, String path) throws IOException {
+ ArrayList lst = sendAndWaitForResponse(
+ "EXTENDCIRCUIT "+circID+" "+path+"\r\n", null);
+ return ((ReplyLine)lst.get(0)).msg;
+ }
+
+ /** Informs the Tor server that the stream specified by <b>streamID</b> should be
+ * associated with the circuit specified by <b>circID</b>.
+ *
+ * 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 <b>circID</b> 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);
+ }
- /**
- * Tell Tor to attach the stream identified by 'streamID' to the circuit
- * identified by 'circID'.
+ /** Tells Tor about the server descriptor in <b>desc</b>.
+ *
+ * The descriptor, when parsed, must contain a number of well-specified
+ * fields, including fields for its nickname and identity.
*/
- public abstract void attachStream(String streamID, String circID) throws IOException;
+ // 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 {
+ ArrayList lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc);
+ return ((ReplyLine)lst.get(0)).msg;
+ }
- /** Tell Tor about the server descriptor in 'desc' */
- public abstract String postDescriptor(String desc) throws IOException;
-
- /** Tell Tor to change the target of the stream identified by 'streamID'
- * to 'address'.
+ /** Tells Tor to change the exit address of the stream identified by <b>streamID</b>
+ * to <b>address</b>. 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 abstract void redirectStream(String streamID, String address) throws IOException;
+ public void redirectStream(String streamID, String address) throws IOException {
+ sendAndWaitForResponse("REDIRECTSTREAM "+streamID+" "+address+"\r\n",
+ null);
+ }
- /** Tell Tor to close the stream identified by 'streamID'.
+ /** Tells Tor to close the stream identified by <b>streamID</b>.
+ * <b>reason</b> should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal:
+ * <ul>
+ * <li>1 -- REASON_MISC (catch-all for unlisted reasons)</li>
+ * <li>2 -- REASON_RESOLVEFAILED (couldn't look up hostname)</li>
+ * <li>3 -- REASON_CONNECTREFUSED (remote host refused connection)</li>
+ * <li>4 -- REASON_EXITPOLICY (OR refuses to connect to host or port)</li>
+ * <li>5 -- REASON_DESTROY (Circuit is being destroyed)</li>
+ * <li>6 -- REASON_DONE (Anonymized TCP connection was closed)</li>
+ * <li>7 -- REASON_TIMEOUT (Connection timed out, or OR timed out while connecting)</li>
+ * <li>8 -- (unallocated)</li>
+ * <li>9 -- REASON_HIBERNATING (OR is temporarily hibernating)</li>
+ * <li>10 -- REASON_INTERNAL (Internal error at the OR)</li>
+ * <li>11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)</li>
+ * <li>12 -- REASON_CONNRESET (Connection was unexpectedly reset)</li>
+ * <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)</li>
+ * </ul>
+ *
+ * Tor may hold the stream open for a while to flush any data that is pending.
*/
- public abstract void closeStream(String streamID, byte reason)
- throws IOException;
+ public void closeStream(String streamID, byte reason)
+ throws IOException {
+ sendAndWaitForResponse("CLOSESTREAM "+streamID+" "+reason+"\r\n",null);
+ }
- /** Tell Tor to close the circuit identified by 'streamID'.
+ /** Tells Tor to close the circuit identified by <b>circID</b>.
+ * If <b>ifUnused</b> is true, do not close the circuit unless it is unused.
*/
- public abstract void closeCircuit(String circID, boolean ifUnused) throws IOException;
-
+ public void closeCircuit(String circID, boolean ifUnused) throws IOException {
+ sendAndWaitForResponse("CLOSECIRCUIT "+circID+
+ (ifUnused?" IFUNUSED":"")+"\r\n", null);
+ }
}
Deleted: torctl/trunk/java/net/freehaven/tor/control/TorControlConnection0.java
===================================================================
--- torctl/trunk/java/net/freehaven/tor/control/TorControlConnection0.java 2007-08-08 14:43:30 UTC (rev 11065)
+++ torctl/trunk/java/net/freehaven/tor/control/TorControlConnection0.java 2007-08-08 20:44:14 UTC (rev 11066)
@@ -1,431 +0,0 @@
-// $Id$
-// Copyright 2005 Nick Mathewson, Roger Dingledine
-// See LICENSE file for copying information
-package net.freehaven.tor.control;
-
-import java.io.IOException;
-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;
-
-/** DOCDOC */
-public class TorControlConnection0 extends TorControlConnection
- implements TorControlCommands
-{
- protected java.io.DataOutputStream outStream;
- protected java.io.DataInputStream inStream;
-
- static class Cmd {
- public int type;
- public byte[] body;
-
- Cmd(int t, byte[] b) { type = t; body = b; }
- Cmd(int t, int l) { type = t; body = new byte[l]; };
- }
-
- /** 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 TorControlConnection0(java.net.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 TorControlConnection0(java.io.InputStream i, java.io.OutputStream o)
- throws IOException {
- this.outStream = new java.io.DataOutputStream(o);
- this.inStream = new java.io.DataInputStream(i);
- this.waiters = new LinkedList();
- }
-
- /** helper: sends a single (unfragmentable) command to Tor */
- protected final void sendCommand0(int type, byte[] cmd)
- throws IOException {
- int length = cmd == null ? 0 : cmd.length;
- outStream.writeShort((short)length);
- outStream.writeShort(type);
- if (cmd != null)
- outStream.write(cmd);
- }
-
- /** helper: sends a single (possibly fragmented) command to Tor */
- protected void sendCommand(short type, byte[] cmd) throws IOException {
- synchronized(this.outStream) {
- if (cmd == null || cmd.length <= 65535) {
- sendCommand0(type, cmd);
- return;
- }
- int length = cmd.length;
- outStream.writeShort(65535);
- outStream.writeShort(CMD_FRAGMENTHEADER);
- outStream.writeShort(type);
- outStream.writeInt(length);
- outStream.write(cmd, 0, 65535);
- for (int pos=65535; pos < length; pos += 65535) {
- int flen = length-pos < 65535 ? length-pos : 65535;
- outStream.writeShort(flen);
- outStream.writeShort(CMD_FRAGMENT);
- this.outStream.write(cmd, pos, flen);
- }
- }
- }
-
- /** helper: read a possibly fragmented command from Tor */
- protected final Cmd readCommand0() throws IOException {
- int len = this.inStream.readUnsignedShort();
- int cmd = this.inStream.readUnsignedShort();
- byte[] result = new byte[len];
- this.inStream.readFully(result);
- return new Cmd(cmd, result);
- }
-
- /** Read a command from Tor, defragmenting as necessary */
- protected Cmd readCommand() throws IOException {
- synchronized (inStream) {
- Cmd c = readCommand0();
- if (c.type != CMD_FRAGMENT && c.type != CMD_FRAGMENTHEADER)
- return c;
-
- if (c.type == CMD_FRAGMENT)
- throw new TorControlSyntaxError("Fragment without header");
-
- int realType = Bytes.getU16(c.body, 0);
- int realLen = Bytes.getU32(c.body, 2);
-
- Cmd out = new Cmd(realType, realLen);
- System.arraycopy(c.body, 6, out.body, 0, c.body.length-6);
- int pos = c.body.length-6;
- while (pos < realLen) {
- c = readCommand0();
- if (c.type != CMD_FRAGMENT)
- throw new TorControlSyntaxError("Incomplete fragmented message");
- System.arraycopy(c.body, 0, out.body, pos, c.body.length);
- pos += c.body.length;
- }
- return out;
- }
- }
-
- /** helper: implement the main background loop. */
- protected void react() throws IOException {
- while (true) {
- Cmd c = readCommand();
- if (c.type == CMD_EVENT)
- handleEvent(c);
- else {
- Waiter w;
- synchronized (waiters) {
- w = (Waiter) waiters.removeFirst();
- }
- w.setResponse(c);
- }
- }
- }
-
- /** helper: Send a command and wait for the next reponse type command
- * to be received (in order) */
- protected synchronized Cmd _sendAndWaitForResponse(short type, byte[] cmd)
- throws IOException {
- checkThread();
- Waiter w = new Waiter();
- synchronized (waiters) {
- sendCommand(type, cmd);
- waiters.addLast(w);
- }
- return (Cmd) w.getResponse();
- }
-
- /** Send a message to Tor, and wait for a respose.
- *
- * @throw TorControlError if Tor tells us about an error
- * @throw TorControlSyntaxError if the response type wasn't exType1...4
- **/
- protected Cmd sendAndWaitForResponse(short type, byte[] cmd,
- short exType1, short exType2, short exType3, short exType4)
- throws IOException {
-
- Cmd c = _sendAndWaitForResponse(type, cmd);
- if (c.type == CMD_ERROR)
- throw new TorControlError(Bytes.getU16(c.body, 0),
- Bytes.getNulTerminatedStr(c.body, 2));
- if (c.type == exType1 || c.type == exType2 || c.type == exType3 ||
- c.type == exType4)
- return c;
-
- throw new TorControlSyntaxError("Unexpected reply type: "+c.type);
- }
-
- protected Cmd sendAndWaitForResponse(short type, byte[] cmd)
- throws IOException {
- return sendAndWaitForResponse(type, cmd, CMD_DONE, CMD_DONE, CMD_DONE, CMD_DONE);
- }
-
- protected Cmd sendAndWaitForResponse(short type, byte[] cmd, short exType1)
- throws IOException {
- return sendAndWaitForResponse(type, cmd, exType1, exType1, exType1,
- exType1);
- }
-
- protected Cmd sendAndWaitForResponse(short type, byte[] cmd,
- short exType1, short exType2)
- throws IOException {
- return sendAndWaitForResponse(type, cmd, exType1, exType2, exType2,
- exType2);
- }
-
- protected Cmd sendAndWaitForResponse(short type, byte[] cmd,
- short exType1, short exType2, short exType3)
- throws IOException {
- return sendAndWaitForResponse(type, cmd, exType1, exType2, exType3,
- exType3);
- }
-
- /** Helper: decode a CMD_EVENT command and dispatch it to our
- * EventHandler (if any). */
- protected void handleEvent(Cmd c) {
- if (handler == null)
- return;
- int type = Bytes.getU16(c.body, 0);
-
- switch (type) {
- case EVENT_CIRCSTATUS:
- handler.circuitStatus(CIRC_STATUS_NAMES[c.body[2]],
- Bytes.getU32S(c.body, 3),
- Bytes.getNulTerminatedStr(c.body, 7));
- break;
- case EVENT_STREAMSTATUS:
- handler.streamStatus(STREAM_STATUS_NAMES[c.body[2]],
- Bytes.getU32S(c.body, 3),
- Bytes.getNulTerminatedStr(c.body, 7));
- break;
- case EVENT_ORCONNSTATUS:
- handler.orConnStatus(OR_CONN_STATUS_NAMES[c.body[2]],
- Bytes.getNulTerminatedStr(c.body, 3));
- break;
- case EVENT_BANDWIDTH:
- handler.bandwidthUsed(Bytes.getU32(c.body, 2),
- Bytes.getU32(c.body, 6));
- break;
- case EVENT_NEWDESCRIPTOR:
- List lst = new ArrayList();
- Bytes.splitStr(lst, c.body, 2, (byte)',');
- handler.newDescriptors(lst);
- break;
- case EVENT_MSG_DEBUG:
- handler.message("DEBUG", Bytes.getNulTerminatedStr(c.body, 2));
- break;
- case EVENT_MSG_INFO:
- handler.message("INFO", Bytes.getNulTerminatedStr(c.body, 2));
- break;
- case EVENT_MSG_NOTICE:
- handler.message("NOTICE", Bytes.getNulTerminatedStr(c.body, 2));
- break;
- case EVENT_MSG_WARN:
- handler.message("WARN", Bytes.getNulTerminatedStr(c.body, 2));
- break;
- case EVENT_MSG_ERROR:
- handler.message("ERR", Bytes.getNulTerminatedStr(c.body, 2));
- break;
- default:
- throw new TorControlSyntaxError("Unrecognized event type.");
- }
- }
-
- /** Change the values of the configuration options stored in
- * 'kvList'. (The format is "key value"). */
- public void setConf(Collection kvList) throws IOException {
- StringBuffer b = new StringBuffer();
- for (Iterator it = kvList.iterator(); it.hasNext(); ) {
- String kv = (String) it.next();
- b.append(kv).append("\n");
- }
- sendAndWaitForResponse(CMD_SETCONF, b.toString().getBytes());
- }
-
- public void resetConf(Collection keylist) throws IOException {
- setConf(keylist);
- }
-
- public List getConf(Collection keys) throws IOException {
- StringBuffer s = new StringBuffer();
- for (Iterator it = keys.iterator(); it.hasNext(); ) {
- String key = (String) it.next();
- s.append(key).append("\n");
- }
- Cmd c = sendAndWaitForResponse(CMD_GETCONF, s.toString().getBytes(),
- CMD_CONFVALUE);
- List lines = new ArrayList();
- Bytes.splitStr(lines, c.body, 0, (byte)'\n');
- List result = new ArrayList();
- for (Iterator it = lines.iterator(); it.hasNext(); ) {
- String kv = (String) it.next();
- int idx = kv.indexOf(' ');
- result.add(new ConfigEntry(kv.substring(0, idx),
- kv.substring(idx+1)));
- }
- return result;
- }
-
- public void setEvents(List events) throws IOException {
- byte[] ba = new byte[events.size() * 2];
- int i;
- Iterator it;
- for(i=0, it = events.iterator(); it.hasNext(); i += 2) {
- Object event = it.next();
- short e = -1;
- if (event instanceof Number) {
- e = ((Number)event).shortValue();
- } else {
- String s = ((String) event).toUpperCase();
- for (int j = 0; i < EVENT_NAMES.length; ++i) {
- if (EVENT_NAMES[j].equals(s)) {
- e = (short)j;
- break;
- }
- }
- if (e < 0)
- throw new TorControlError("Unknown v0 code for event '"+s+"'");
- }
- Bytes.setU16(ba, i, e);
- }
- sendAndWaitForResponse(CMD_SETEVENTS, ba);
- System.out.println("OK");
- }
-
- public void authenticate(byte[] auth) throws IOException {
- if (auth == null)
- auth = new byte[0];
- sendAndWaitForResponse(CMD_AUTH, auth);
- }
-
- public void saveConf() throws IOException {
- sendAndWaitForResponse(CMD_SAVECONF, new byte[0]);
- }
-
- public void signal(String signal) throws IOException {
- int sig;
- signal = signal.toUpperCase();
- if (signal.equals("HUP") || signal.equals("RELOAD"))
- sig = SIGNAL_HUP;
- else if (signal.equals("INT") || signal.equals("SHUTDOWN"))
- sig = SIGNAL_INT;
- else if (signal.equals("USR1") || signal.equals("DUMP"))
- sig = SIGNAL_USR1;
- else if (signal.equals("USR2") || signal.equals("DEBUG"))
- sig = SIGNAL_USR2;
- else if (signal.equals("TERM") || signal.equals("HALT"))
- sig = SIGNAL_TERM;
- else
- throw new TorControlError("Unrecognized value for signal()");
- byte[] ba = { (byte)sig };
- sendAndWaitForResponse(CMD_SIGNAL, ba);
- }
-
- /** 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 {
- throw new RuntimeException("Method is not implemented!");
- }
-
- public Map mapAddresses(Collection kvLines) throws IOException {
- StringBuffer sb = new StringBuffer();
- for (Iterator it = kvLines.iterator(); it.hasNext(); ) {
- sb.append((String)it.next()).append("\n");
- }
- Cmd c = sendAndWaitForResponse(CMD_MAPADDRESS, sb.toString().getBytes());
- Map result = new HashMap();
- List lst = new ArrayList();
- Bytes.splitStr(lst, c.body, 0, (byte)'\n');
- for (Iterator it = lst.iterator(); it.hasNext(); ) {
- String kv = (String) it.next();
- int idx = kv.indexOf(' ');
- result.put(kv.substring(0, idx),
- kv.substring(idx+1));
- }
- return result;
- }
-
- public Map getInfo(Collection keys) throws IOException {
- StringBuffer sb = new StringBuffer();
- for (Iterator it = keys.iterator(); it.hasNext(); ) {
- sb.append(((String)it.next())+"\n");
- }
- Cmd c = sendAndWaitForResponse(CMD_GETINFO, sb.toString().getBytes(),
- CMD_INFOVALUE);
- Map m = new HashMap();
- List lst = new ArrayList();
- Bytes.splitStr(lst, c.body, 0, (byte)0);
- if ((lst.size() % 2) != 0)
- throw new TorControlSyntaxError(
- "Odd number of substrings from GETINFO");
- for (Iterator it = lst.iterator(); it.hasNext(); ) {
- Object k = it.next();
- Object v = it.next();
- m.put(k, v);
- }
- return m;
- }
-
- public String extendCircuit(String circID, String path) throws IOException {
- byte[] p = path.getBytes();
- byte[] ba = new byte[p.length+4];
- Bytes.setU32(ba, 0, (int)Long.parseLong(circID));
- System.arraycopy(p, 0, ba, 4, p.length);
- Cmd c = sendAndWaitForResponse(CMD_EXTENDCIRCUIT, ba);
- return Integer.toString(Bytes.getU32(c.body, 0));
- }
-
- public void attachStream(String streamID, String circID)
- throws IOException {
- byte[] ba = new byte[8];
- Bytes.setU32(ba, 0, (int)Long.parseLong(streamID));
- Bytes.setU32(ba, 4, (int)Long.parseLong(circID));
- sendAndWaitForResponse(CMD_ATTACHSTREAM, ba);
- }
-
- /** Tell Tor about the server descriptor in 'desc' */
- public String postDescriptor(String desc) throws IOException {
- return new String(
- sendAndWaitForResponse(CMD_POSTDESCRIPTOR, desc.getBytes()).body);
- }
-
- /** Tell Tor to change the target of the stream identified by 'streamID'
- * to 'address'.
- */
- public void redirectStream(String streamID, String address) throws IOException {
- byte[] addr = address.getBytes();
- byte[] ba = new byte[addr.length+4];
- Bytes.setU32(ba, 0, (int)Long.parseLong(streamID));
- System.arraycopy(addr, 0, ba, 4, addr.length);
- sendAndWaitForResponse(CMD_REDIRECTSTREAM, ba);
- }
-
- /** Tell Tor to close the stream identified by 'streamID'.
- */
- public void closeStream(String streamID, byte reason)
- throws IOException {
- byte[] ba = new byte[6];
- Bytes.setU32(ba, 0, (int)Long.parseLong(streamID));
- ba[4] = reason;
- ba[5] = (byte)0;
- sendAndWaitForResponse(CMD_CLOSESTREAM, ba);
- }
-
- /** Tell Tor to close the circuit identified by 'streamID'.
- */
- public void closeCircuit(String circID, boolean ifUnused) throws IOException {
- byte[] ba = new byte[5];
- Bytes.setU32(ba, 0, (int)Long.parseLong(circID));
- ba[4] = (byte)(ifUnused? 1 : 0);
- sendAndWaitForResponse(CMD_CLOSECIRCUIT, ba);
- }
-
-}
-
Deleted: torctl/trunk/java/net/freehaven/tor/control/TorControlConnection1.java
===================================================================
--- torctl/trunk/java/net/freehaven/tor/control/TorControlConnection1.java 2007-08-08 14:43:30 UTC (rev 11065)
+++ torctl/trunk/java/net/freehaven/tor/control/TorControlConnection1.java 2007-08-08 20:44:14 UTC (rev 11066)
@@ -1,616 +0,0 @@
-// $Id$
-// Copyright 2005 Nick Mathewson, Roger Dingledine
-// See LICENSE file for copying information
-package net.freehaven.tor.control;
-
-import java.io.IOException;
-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;
-
-/** Extends the TorControlConnection class to implement Version 1
-* of the TC protocol, as specified in control-spec.txt.
-*/
-public class TorControlConnection1 extends TorControlConnection
- implements TorControlCommands
-{
- protected java.io.BufferedReader input;
- protected java.io.Writer output;
- protected java.io.PrintWriter debugOutput;
-
- static class ReplyLine {
- public String status;
- public String msg;
- public 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 TorControlConnection1(java.net.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 TorControlConnection1(java.io.InputStream i, java.io.OutputStream o)
- throws IOException {
- this(new java.io.InputStreamReader(i),
- new java.io.OutputStreamWriter(o));
- }
-
- public TorControlConnection1(java.io.Reader i, java.io.Writer o)
- throws IOException {
- this.output = o;
- if (i instanceof java.io.BufferedReader)
- this.input = (java.io.BufferedReader) i;
- else
- this.input = new java.io.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;
- } else {
- // 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;
- }
-
- /** helper: implement the main background loop. */
- protected void react() throws IOException {
- while (true) {
- ArrayList lst = readReply();
- if (lst.isEmpty()) {
- // connection has been closed remotely! end the loop!
- return;
- }
- if (((ReplyLine)lst.get(0)).status.startsWith("6"))
- handleEvent(lst);
- else {
- Waiter w;
- synchronized (waiters) {
- w = (Waiter) waiters.removeFirst();
- }
- w.setResponse(lst);
- }
- }
- }
-
- protected synchronized ArrayList sendAndWaitForResponse(String s,String rest)
- throws IOException {
- checkThread();
- Waiter w = new Waiter();
- if (debugOutput != null)
- debugOutput.print(">> "+s);
- synchronized (waiters) {
- output.write(s);
- output.flush();
- if (rest != null)
- writeEscaped(rest);
- waiters.addLast(w);
- }
- ArrayList lst = (ArrayList) w.getResponse();
- for (Iterator i = lst.iterator(); i.hasNext(); ) {
- ReplyLine c = (ReplyLine) 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 = (ReplyLine) 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((String)lst.get(1),
- (String)lst.get(0),
- (String)lst.get(2));
- } else if (tp.equals("STREAM")) {
- List lst = Bytes.splitStr(null, rest);
- handler.streamStatus((String)lst.get(1),
- (String)lst.get(0),
- (String)lst.get(3));
- // XXXX circID.
- } else if (tp.equals("ORCONN")) {
- List lst = Bytes.splitStr(null, rest);
- handler.orConnStatus((String)lst.get(1), (String)lst.get(0));
- } else if (tp.equals("BW")) {
- List lst = Bytes.splitStr(null, rest);
- handler.bandwidthUsed(Integer.parseInt((String)lst.get(0)),
- Integer.parseInt((String)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);
- }
- }
- }
-
- /** Changes the values of the configuration options stored in
- * <b>kvList</b>. Each list element in <b>kvList</b> 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 <b>kvList</b> 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 = (String) 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);
- }
-
- 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 = (String) it.next();
- b.append(" ").append(key);
- }
- b.append("\r\n");
- sendAndWaitForResponse(b.toString(), null);
- }
-
- /** Sets <b>w</b> 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(java.io.PrintWriter w) {
- if (w instanceof java.io.PrintWriter)
- debugOutput = (java.io.PrintWriter) w;
- else
- debugOutput = new java.io.PrintWriter(w, true);
- }
-
- /** Sets <b>s</b> 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(java.io.PrintStream s) {
- debugOutput = new java.io.PrintWriter(s, true);
- }
-
- /** Requests the values of the configuration variables listed in <b>keys</b>.
- * 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 = (String) it.next();
- sb.append(" ").append(key);
- }
- sb.append("\r\n");
- ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
- ArrayList result = new ArrayList();
- for (Iterator it = lst.iterator(); it.hasNext(); ) {
- String kv = ((ReplyLine) 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 <b>events</b> is one of the following Strings:
- * ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" |
- * "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] .
- *
- * Any events not listed in the <b>events</b> are turned off; thus, calling
- * setEvents with an empty <b>events</b> 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(); ) {
- Object event = it.next();
- if (event instanceof String) {
- sb.append(" ").append((String)event);
- } else {
- int i = ((Number) event).intValue();
- sb.append(" ").append(EVENT_NAMES[i]);
- }
- }
- 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 <b>auth</b>.
- *
- * If the 'HashedControlPassword' option is set, <b>auth</b> 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 <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.
- * <b>signal</b> is one of the following Strings:
- * <ul>
- * <li>"RELOAD" or "HUP" : Reload config items, refetch directory</li>
- * <li>"SHUTDOWN" or "INT" : Controlled shutdown: if server is an OP, exit immediately.
- * If it's an OR, close listeners and exit after 30 seconds</li>
- * <li>"DUMP" or "USR1" : Dump stats: log information about open connections and circuits</li>
- * <li>"DEBUG" or "USR2" : Debug: switch all open logs to loglevel debug</li>
- * <li>"HALT" or "TERM" : Immediate shutdown: clean up and exit now</li>
- * </ul>
- */
- 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);
- if (this.thread != null) {
- this.thread.stopListening();
- }
- synchronized (waiters) {
- output.write(s);
- output.flush();
- waiters.addLast(w); // Prevent react() from finding the list empty
- }
- }
-
- /** 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 <b>kvLines</b> 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 = (String) it.next();
- int i = kv.indexOf(' ');
- sb.append(" ").append(kv.substring(0,i)).append("=")
- .append(quote(kv.substring(i+1)));
- }
- sb.append("\r\n");
- ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
- Map result = new HashMap();
- for (Iterator it = lst.iterator(); it.hasNext(); ) {
- String kv = ((ReplyLine) it.next()).msg;
- int idx = kv.indexOf('=');
- result.put(kv.substring(0, idx),
- kv.substring(idx+1));
- }
- return result;
- }
-
- /** 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:
- * <ul>
- * <li>"version" : The version of the server's software, including the name
- * of the software. (example: "Tor 0.0.9.4")</li>
- * <li>"desc/id/<OR identity>" or "desc/name/<OR nickname>" : the latest server
- * descriptor for a given OR, NUL-terminated. If no such OR is known, the
- * corresponding value is an empty string.</li>
- * <li>"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.</li>
- * <li>"addr-mappings/all"</li>
- * <li>"addr-mappings/config"</li>
- * <li>"addr-mappings/cache"</li>
- * <li>"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.</li>
- * <li>"circuit-status" : A series of lines as for a circuit status event. Each line is of the form:
- * "CircuitID CircStatus Path"</li>
- * <li>"stream-status" : A series of lines as for a stream status event. Each is of the form:
- * "StreamID StreamStatus CircID Target"</li>
- * <li>"orconn-status" : A series of lines as for an OR connection status event. Each is of the
- * form: "ServerID ORStatus"</li>
- * </ul>
- */
- public Map getInfo(Collection keys) throws IOException {
- StringBuffer sb = new StringBuffer("GETINFO");
- for (Iterator it = keys.iterator(); it.hasNext(); ) {
- sb.append(" ").append((String)it.next());
- }
- sb.append("\r\n");
- ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
- Map m = new HashMap();
- for (Iterator it = lst.iterator(); it.hasNext(); ) {
- ReplyLine line = (ReplyLine) it.next();
- int idx = line.msg.indexOf('=');
- if (idx<0)
- break;
- String k = line.msg.substring(0,idx);
- Object v;
- if (line.rest != null) {
- v = line.rest;
- } else {
- v = line.msg.substring(idx+1);
- }
- m.put(k, v);
- }
- return m;
- }
-
- /** An extendCircuit request takes one of two forms: either the <b>circID</b> is zero, in
- * which case it is a request for the server to build a new circuit according
- * to the specified path, or the <b>circID</b> is nonzero, in which case it is a
- * request for the server to extend an existing circuit with that ID according
- * to the specified <b>path</b>.
- *
- * If successful, returns the Circuit ID of the (maybe newly created) circuit.
- */
- public String extendCircuit(String circID, String path) throws IOException {
- ArrayList lst = sendAndWaitForResponse(
- "EXTENDCIRCUIT "+circID+" "+path+"\r\n", null);
- return ((ReplyLine)lst.get(0)).msg;
- }
-
- /** Informs the Tor server that the stream specified by <b>streamID</b> should be
- * associated with the circuit specified by <b>circID</b>.
- *
- * 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 <b>circID</b> 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 <b>desc</b>.
- *
- * 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 {
- ArrayList lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc);
- return ((ReplyLine)lst.get(0)).msg;
- }
-
- /** Tells Tor to change the exit address of the stream identified by <b>streamID</b>
- * to <b>address</b>. 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 <b>streamID</b>.
- * <b>reason</b> should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal:
- * <ul>
- * <li>1 -- REASON_MISC (catch-all for unlisted reasons)</li>
- * <li>2 -- REASON_RESOLVEFAILED (couldn't look up hostname)</li>
- * <li>3 -- REASON_CONNECTREFUSED (remote host refused connection)</li>
- * <li>4 -- REASON_EXITPOLICY (OR refuses to connect to host or port)</li>
- * <li>5 -- REASON_DESTROY (Circuit is being destroyed)</li>
- * <li>6 -- REASON_DONE (Anonymized TCP connection was closed)</li>
- * <li>7 -- REASON_TIMEOUT (Connection timed out, or OR timed out while connecting)</li>
- * <li>8 -- (unallocated)</li>
- * <li>9 -- REASON_HIBERNATING (OR is temporarily hibernating)</li>
- * <li>10 -- REASON_INTERNAL (Internal error at the OR)</li>
- * <li>11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)</li>
- * <li>12 -- REASON_CONNRESET (Connection was unexpectedly reset)</li>
- * <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)</li>
- * </ul>
- *
- * 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 <b>circID</b>.
- * If <b>ifUnused</b> 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);
- }
-
-}
-