From da57f8432e990b5e6cc3879b489e1cf75bb328d8 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 8 Aug 2007 20:44:14 +0000 Subject: [PATCH] r14101@catbus: nickm | 2007-08-08 16:43:33 -0400 From Karsten: drop TorControlConnection0.java, and merge TorControlConnection1 into TorControlConnection. --- .../tor/control/TorControlConnection.java | 648 ++++++++++++++++-- .../tor/control/TorControlConnection0.java | 431 ------------ .../tor/control/TorControlConnection1.java | 616 ----------------- 3 files changed, 574 insertions(+), 1121 deletions(-) delete mode 100644 net/freehaven/tor/control/TorControlConnection0.java delete mode 100644 net/freehaven/tor/control/TorControlConnection1.java diff --git a/net/freehaven/tor/control/TorControlConnection.java b/net/freehaven/tor/control/TorControlConnection.java index 809153a..1b52e0f 100644 --- a/net/freehaven/tor/control/TorControlConnection.java +++ b/net/freehaven/tor/control/TorControlConnection.java @@ -7,13 +7,15 @@ import java.io.IOException; 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 @@ public abstract class TorControlConnection// implements TorControlCommands { 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 @@ public abstract class TorControlConnection// implements TorControlCommands { } } - 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 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(java.io.PrintWriter w) { + if (w instanceof java.io.PrintWriter) + debugOutput = (java.io.PrintWriter) w; + else + debugOutput = new java.io.PrintWriter(w, true); + } + + /** 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(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 @@ public abstract class TorControlConnection// implements TorControlCommands { 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 @@ public abstract class TorControlConnection// implements TorControlCommands { 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 + * 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 = (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 @@ public abstract class TorControlConnection// implements TorControlCommands { 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 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 = (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 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(); ) { + 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 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); + } - /** 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. + * signal is one of the following Strings: + * + */ + 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 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 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 @@ public abstract class TorControlConnection// implements TorControlCommands { 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: + * + */ + 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 @@ public abstract class TorControlConnection// implements TorControlCommands { 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 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 abstract String extendCircuit(String circID, String path) throws IOException; - - /** - * Tell Tor to attach the stream identified by 'streamID' to the circuit - * identified by 'circID'. + 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 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 abstract void attachStream(String streamID, String circID) throws IOException; + public void attachStream(String streamID, String circID) + throws IOException { + sendAndWaitForResponse("ATTACHSTREAM "+streamID+" "+circID+"\r\n", null); + } - /** 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 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. */ - public abstract void redirectStream(String streamID, String address) 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 to close the stream identified by 'streamID'. + /** 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 abstract void closeStream(String streamID, byte reason) - throws IOException; + public void redirectStream(String streamID, String address) throws IOException { + sendAndWaitForResponse("REDIRECTSTREAM "+streamID+" "+address+"\r\n", + null); + } - /** Tell Tor to close the circuit identified by 'streamID'. + /** 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: + * + * + * Tor may hold the stream open for a while to flush any data that is pending. */ - public abstract void closeCircuit(String circID, boolean ifUnused) throws IOException; + 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); + } } diff --git a/net/freehaven/tor/control/TorControlConnection0.java b/net/freehaven/tor/control/TorControlConnection0.java deleted file mode 100644 index d731740..0000000 --- a/net/freehaven/tor/control/TorControlConnection0.java +++ /dev/null @@ -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); - } - -} - diff --git a/net/freehaven/tor/control/TorControlConnection1.java b/net/freehaven/tor/control/TorControlConnection1.java deleted file mode 100644 index bd3f1e8..0000000 --- a/net/freehaven/tor/control/TorControlConnection1.java +++ /dev/null @@ -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 - * 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 = (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 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(java.io.PrintWriter w) { - if (w instanceof java.io.PrintWriter) - debugOutput = (java.io.PrintWriter) w; - else - debugOutput = new java.io.PrintWriter(w, true); - } - - /** 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(java.io.PrintStream s) { - debugOutput = new java.io.PrintWriter(s, true); - } - - /** 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 = (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 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(); ) { - 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 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); - 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 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 = (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: - *
    - *
  • "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((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 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 { - ArrayList lst = sendAndWaitForResponse( - "EXTENDCIRCUIT "+circID+" "+path+"\r\n", null); - return ((ReplyLine)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 { - 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 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); - } - -} -