Skip to content
Snippets Groups Projects
jtorctl.patch 38.5 KiB
Newer Older
diff -Bbur jtorctl/net/freehaven/tor/control/examples/DebuggingEventHandler.java jtorctl-briar/net/freehaven/tor/control/examples/DebuggingEventHandler.java
--- jtorctl/net/freehaven/tor/control/examples/DebuggingEventHandler.java	2014-04-02 11:26:56.000000000 +0100
+++ jtorctl-briar/net/freehaven/tor/control/examples/DebuggingEventHandler.java	2014-04-02 11:31:48.000000000 +0100
@@ -3,12 +3,12 @@
 package net.freehaven.tor.control.examples;
 
 import java.io.PrintWriter;
-import java.util.Iterator;
+import java.util.List;
 import net.freehaven.tor.control.EventHandler;
 
 public class DebuggingEventHandler implements EventHandler {
 
-    protected PrintWriter out;
+    private final PrintWriter out;
     public DebuggingEventHandler(PrintWriter p) {
         out = p;
@@ -27,11 +30,13 @@
         out.println("Bandwidth usage: "+read+" bytes read; "+
                     written+" bytes written.");
     }
-    public void newDescriptors(java.util.List<String> orList) {
+
+    public void newDescriptors(List<String> orList) {
         out.println("New descriptors for routers:");
-        for (Iterator<String> i = orList.iterator(); i.hasNext(); )
-            out.println("   "+i.next());
+        for (String or : orList)
+            out.println("   "+or);
     }
+
     public void message(String type, String msg) {
         out.println("["+type+"] "+msg.trim());
     }
diff -Bbur jtorctl/net/freehaven/tor/control/examples/Main.java jtorctl-briar/net/freehaven/tor/control/examples/Main.java
--- jtorctl/net/freehaven/tor/control/examples/Main.java	2014-04-02 11:26:56.000000000 +0100
+++ jtorctl-briar/net/freehaven/tor/control/examples/Main.java	2014-04-02 11:56:39.000000000 +0100
@@ -2,14 +2,17 @@
 // See LICENSE file for copying information
 package net.freehaven.tor.control.examples;
 
-import net.freehaven.tor.control.*;
 import java.io.PrintWriter;
+import java.io.EOFException;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.Socket;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
-import java.util.Iterator;
+
+import net.freehaven.tor.control.*;
 
 public class Main implements TorControlCommands {
 
             } else {
                 System.err.println("Unrecognized command: "+args[0]);
             }
-        } catch (java.io.EOFException ex) {
+        } catch (EOFException ex) {
             System.out.println("Control socket closed by Tor.");
+        } catch (TorControlError ex) {
+            System.err.println("Error from Tor process: "+
+                               ex+" ["+ex.getErrorMsg()+"]");
         } catch (IOException ex) {
             System.err.println("IO exception when talking to Tor process: "+
                                ex);
             ex.printStackTrace(System.err);
-        } catch (TorControlError ex) {
-            System.err.println("Error from Tor process: "+
-                               ex+" ["+ex.getErrorMsg()+"]");
         }
     }
 
     private static TorControlConnection getConnection(String[] args,
-                                                      boolean daemon)
-        throws IOException {
-        TorControlConnection conn = TorControlConnection.getConnection(
-                                    new java.net.Socket("127.0.0.1", 9100));
-        //if (conn instanceof TorControlConnection1) {
-        //    System.err.println("Debugging");
-        //    ((TorControlConnection1)conn).setDebugging(System.err);
-        //}
+        boolean daemon) throws IOException {
+        Socket s = new Socket("127.0.0.1", 9100);
+	TorControlConnection conn = new TorControlConnection(s);
         conn.launchThread(daemon);
         conn.authenticate(new byte[0]);
         return conn;
     public static void getConfig(String[] args) throws IOException {
         // Usage: get-config key key key
         TorControlConnection conn = getConnection(args);
-        List<ConfigEntry> lst = conn.getConf(Arrays.asList(args).subList(1,args.length));
-        for (Iterator<ConfigEntry> i = lst.iterator(); i.hasNext(); ) {
-            ConfigEntry e = i.next();
+        List<String> keys = Arrays.asList(args).subList(1, args.length);
+        List<ConfigEntry> lst = conn.getConf(keys);
+        for (ConfigEntry e : lst) {
             System.out.println("KEY: "+e.key);
             System.out.println("VAL: "+e.value);
 
     public static void getInfo(String[] args) throws IOException {
         TorControlConnection conn = getConnection(args);
-        Map<String,String> m = conn.getInfo(Arrays.asList(args).subList(1,args.length));
-        for (Iterator<Map.Entry<String, String>> i = m.entrySet().iterator(); i.hasNext(); ) {
-            Map.Entry<String,String> e = i.next();
+        List<String> keys = Arrays.asList(args).subList(1, args.length);
+        Map<String,String> m = conn.getInfo(keys);
+        for (Map.Entry<String,String> e : m.entrySet()) {
             System.out.println("KEY: "+e.getKey());
             System.out.println("VAL: "+e.getValue());
     public static void listenForEvents(String[] args) throws IOException {
         // Usage: listen [circ|stream|orconn|bw|newdesc|info|notice|warn|error]*
         TorControlConnection conn = getConnection(args, false);
-        ArrayList<String> lst = new ArrayList<String>();
-        for (int i = 1; i < args.length; ++i) {
-            lst.add(args[i]);
-        }
+        List<String> lst = Arrays.asList(args).subList(1, args.length);
         conn.setEventHandler(
             new DebuggingEventHandler(new PrintWriter(System.out, true)));
         conn.setEvents(lst);
@@ -130,17 +125,13 @@
     }
 
     public static void authDemo(String[] args) throws IOException {
-
         PasswordDigest pwd = PasswordDigest.generateDigest();
-        java.net.Socket s = new java.net.Socket("127.0.0.1", 9100);
-        TorControlConnection conn = TorControlConnection.getConnection(s);
+        Socket s = new java.net.Socket("127.0.0.1", 9100);
+        TorControlConnection conn = new TorControlConnection(s);
         conn.launchThread(true);
         conn.authenticate(new byte[0]);
-
         conn.setConf("HashedControlPassword", pwd.getHashedPassword());
-
-        conn = TorControlConnection.getConnection(
-                                    new java.net.Socket("127.0.0.1", 9100));
+        conn = new TorControlConnection(new Socket("127.0.0.1", 9100));
         conn.launchThread(true);
         conn.authenticate(pwd.getSecret());
     }
diff -Bbur jtorctl/net/freehaven/tor/control/PasswordDigest.java jtorctl-briar/net/freehaven/tor/control/PasswordDigest.java
--- jtorctl/net/freehaven/tor/control/PasswordDigest.java	2014-04-02 11:26:56.000000000 +0100
+++ jtorctl-briar/net/freehaven/tor/control/PasswordDigest.java	2014-04-02 12:29:20.000000000 +0100
@@ -2,6 +2,7 @@
 // See LICENSE file for copying information
 package net.freehaven.tor.control;
 
+import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.security.MessageDigest;
 
  */
 public class PasswordDigest {
 
-    byte[] secret;
-    String hashedKey;
+    private final byte[] secret;
+    private final String hashedKey;
 
     /** Return a new password digest with a random secret and salt. */
     public static PasswordDigest generateDigest() {
             rng.nextBytes(specifier);
             specifier[8] = 96;
         }
-        hashedKey = "16:"+encodeBytes(secretToKey(secret, specifier));
+        hashedKey = "16:"+Bytes.hex(secretToKey(secret, specifier));
     /** Return the secret used to generate this password hash.
@@ -63,7 +64,7 @@
         MessageDigest d;
         try {
             d = MessageDigest.getInstance("SHA-1");
-        } catch (java.security.NoSuchAlgorithmException ex) {
+        } catch (NoSuchAlgorithmException ex) {
             throw new RuntimeException("Can't run without sha-1.");
         }
         int c = (specifier[8])&0xff;
@@ -86,12 +87,5 @@
         System.arraycopy(specifier, 0, key, 0, 9);
         return key;
     }
-
-    /** Return a hexadecimal encoding of a byte array. */
-    // XXX There must be a better way to do this in Java.
-    private static final String encodeBytes(byte[] ba) {
-        return Bytes.hex(ba);
-    }
-
 }
 
diff -Bbur jtorctl/net/freehaven/tor/control/TorControlConnection.java jtorctl-briar/net/freehaven/tor/control/TorControlConnection.java
--- jtorctl/net/freehaven/tor/control/TorControlConnection.java	2014-04-02 11:26:56.000000000 +0100
+++ jtorctl-briar/net/freehaven/tor/control/TorControlConnection.java	2014-04-02 12:29:34.000000000 +0100
@@ -2,96 +2,93 @@
 // See LICENSE file for copying information
 package net.freehaven.tor.control;
 
+import java.io.BufferedReader;
 import java.io.IOException;
-import java.net.SocketException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.Socket;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
-import java.util.concurrent.CancellationException;
 
 /** A connection to a running Tor process as specified in control-spec.txt. */
-public class TorControlConnection implements TorControlCommands
-{
+public class TorControlConnection implements TorControlCommands {
 
-    protected EventHandler handler;
+    private final LinkedList<Waiter> waiters;
+    private final BufferedReader input;
+    private final Writer output;
 
-    protected LinkedList<Waiter> waiters;
+    private ControlParseThread thread; // Locking: this
 
-    protected ControlParseThread thread;
+    private volatile EventHandler handler;
+    private volatile PrintWriter debugOutput;
+    private volatile IOException parseThreadException;
 
-    protected java.io.BufferedReader input;
+    private static class Waiter {
     
-    protected java.io.Writer output;
-    
-    protected java.io.PrintWriter debugOutput;
-    
-    static class Waiter {
         List<ReplyLine> response;
-        public synchronized List<ReplyLine> getResponse() {
-            try {
+
+        synchronized List<ReplyLine> getResponse() throws InterruptedException {
                 while (response == null) {
                     wait();
                 }
-            } catch (InterruptedException ex) {
-                throw new CancellationException(
-                    "Please don't interrupt library calls.");
-            }
             return response;
         }
-        public synchronized void setResponse(List<ReplyLine> response) {
+
+        synchronized void setResponse(List<ReplyLine> response) {
             this.response = response;
             notifyAll();
         }
     }
 
-    static class ReplyLine {
-        public String status;
-        public String msg;
-        public String rest;
+    private static class ReplyLine {
+        final String status;
+        final String msg;
+        final String rest;
 
         ReplyLine(String status, String msg, String rest) {
-            this.status = status; this.msg = msg; this.rest = rest;
-        }
+            this.status = status;
+            this.msg = msg;
+            this.rest = rest;
     }
-    
-    public static TorControlConnection getConnection(java.net.Socket sock)
-        throws IOException
-    {
-        return new TorControlConnection(sock);
     }
 
     /** 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());
+    public TorControlConnection(Socket s) throws IOException {
+        this(s.getInputStream(), s.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) {
-        this(new java.io.InputStreamReader(i),
-             new java.io.OutputStreamWriter(o));
+    public TorControlConnection(InputStream i, OutputStream o) {
+        this(new InputStreamReader(i), new OutputStreamWriter(o));
     }
 
-    public TorControlConnection(java.io.Reader i, java.io.Writer o) {
-        this.output = o;
-        if (i instanceof java.io.BufferedReader)
-            this.input = (java.io.BufferedReader) i;
+    public TorControlConnection(Reader i, Writer o) {
+        if (i instanceof BufferedReader)
+            input = (BufferedReader) i;
         else
-            this.input = new java.io.BufferedReader(i);
-
-        this.waiters = new LinkedList<Waiter>();
+            input = new BufferedReader(i);
+        output = o;
+        waiters = new LinkedList<Waiter>();
     }
 
-    protected final void writeEscaped(String s) throws IOException {
+    private final void writeEscaped(String s) throws IOException {
         StringTokenizer st = new StringTokenizer(s, "\n");
         while (st.hasMoreTokens()) {
@@ -110,12 +107,11 @@
             debugOutput.print(">> .\n");
     }
 
-    protected static final String quote(String s) {
-        StringBuffer sb = new StringBuffer("\"");
+    private static final String quote(String s) {
+        StringBuilder sb = new StringBuilder("\"");
         for (int i = 0; i < s.length(); ++i) {
         return sb.toString();
     }
 
-    protected final ArrayList<ReplyLine> readReply() throws IOException {
+    private ArrayList<ReplyLine> readReply() throws IOException {
         ArrayList<ReplyLine> reply = new ArrayList<ReplyLine>();
         char c;
         do {
             String msg = line.substring(4);
             String rest = null;
             if (c == '+') {
-                StringBuffer data = new StringBuffer();
+                StringBuilder data = new StringBuilder();
                 while (true) {
                     if (debugOutput != null)
@@ -172,8 +168,9 @@
         return reply;
     }
 
-    protected synchronized List<ReplyLine> sendAndWaitForResponse(String s,String rest)
-        throws IOException {
+    private synchronized List<ReplyLine> sendAndWaitForResponse(String s,
+        String rest) throws IOException {
+        if(parseThreadException != null) throw parseThreadException;
         checkThread();
         Waiter w = new Waiter();
         if (debugOutput != null)
@@ -185,38 +182,38 @@
             output.flush();
             waiters.addLast(w);
         }
-        List<ReplyLine> lst = w.getResponse();
-        for (Iterator<ReplyLine> i = lst.iterator(); i.hasNext(); ) {
-            ReplyLine c = i.next();
-            if (! c.status.startsWith("2"))
-                throw new TorControlError("Error reply: "+c.msg);
+        List<ReplyLine> lst;
+        try {
+            lst = w.getResponse();
+        } catch (InterruptedException ex) {
+            throw new IOException(ex.toString());
+        }
+        for (ReplyLine line : lst) {
+            if (! line.status.startsWith("2"))
+                throw new TorControlError("Error reply: "+line.msg);
         }
         return lst;
     }
 
     /** Helper: decode a CMD_EVENT command and dispatch it to our
      * EventHandler (if any). */
-    protected void handleEvent(ArrayList<ReplyLine> events) {
+    private void handleEvent(ArrayList<ReplyLine> events) {
         if (handler == null)
             return;
 
-        for (Iterator<ReplyLine> i = events.iterator(); i.hasNext(); ) {
-            ReplyLine line = i.next();
+        for (ReplyLine line : events) {
             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<String> lst = Bytes.splitStr(null, rest);
-                handler.circuitStatus(lst.get(1),
-                                      lst.get(0),
-                                      lst.get(1).equals("LAUNCHED")
-                                          || lst.size() < 2 ? ""
-                                          : lst.get(2));
+                String path;
+                if (lst.get(1).equals("LAUNCHED") || lst.size() < 3) path = "";
+                else path = lst.get(2);
+                handler.circuitStatus(lst.get(1), lst.get(0), path);
             } else if (tp.equals("STREAM")) {
                 List<String> lst = Bytes.splitStr(null, rest);
-                handler.streamStatus(lst.get(1),
-                                     lst.get(0),
-                                     lst.get(3));
+                handler.streamStatus(lst.get(1), lst.get(0), lst.get(3));
             } else if (tp.equals("ORCONN")) {
                 List<String> lst = Bytes.splitStr(null, rest);
@@ -240,23 +237,22 @@
         }
     }
 
-
     /** 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 "\<\<"
+    * Outgoing messages are preceded by "\>\>" and incoming messages are
+    * preceded by "\<\<"
     */
-    public void setDebugging(java.io.PrintWriter w) {
+    public void setDebugging(PrintWriter w) {
         debugOutput = w;
     }
     
     /** 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 "\<\<"
+    * 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);
+    public void setDebugging(PrintStream s) {
+        debugOutput = new PrintWriter(s, true);
     }
 
     /** Set the EventHandler object that will be notified of any
@@ -271,50 +267,43 @@
      * This is necessary to handle asynchronous events and synchronous
      * responses that arrive independantly over the same socket.
      */
-    public Thread launchThread(boolean daemon) {
+    public synchronized Thread launchThread(boolean daemon) {
     	ControlParseThread th = new ControlParseThread();
         if (daemon)
             th.setDaemon(true);
         return th;
     }
 
-    protected class ControlParseThread extends Thread {
-    	boolean stopped = false;
+    private class ControlParseThread extends Thread {
+
         @Override
     	public void run() {
             try {
                 react();
-            } catch (SocketException ex) {
-            	if (stopped) // we expected this exception
-                    return;
-                throw new RuntimeException(ex);
             } catch (IOException ex) {
-                throw new RuntimeException(ex);
+                parseThreadException = ex;
             }
         }
-        public void stopListening() {
-            this.stopped = true;
     }
 
-    protected final void checkThread() {
+    private synchronized void checkThread() {
         if (thread == null)
             launchThread(true);
     }
 
     /** helper: implement the main background loop. */
-    protected void react() throws IOException {
+    private void react() throws IOException {
         while (true) {
             ArrayList<ReplyLine> lst = readReply();
                 // connection has been closed remotely! end the loop!
                 return;
             }
-            if ((lst.get(0)).status.startsWith("6"))
+            if ((lst.get(0)).status.startsWith("6")) {
                 synchronized (waiters) {
@@ -324,20 +313,16 @@
         }
     }
 
-    /** Change the value of the configuration option 'key' to 'val'.
-     */
+    /** Change the value of the configuration option 'key' to 'val'. */
     public void setConf(String key, String value) throws IOException {
-        List<String> lst = new ArrayList<String>();
-        lst.add(key+" "+value);
-        setConf(lst);
+        setConf(Arrays.asList(key+" "+value));
     }
 
     /** Change the values of the configuration options stored in kvMap. */
     public void setConf(Map<String, String> kvMap) throws IOException {
         List<String> lst = new ArrayList<String>();
-        for (Iterator<Map.Entry<String,String>> it = kvMap.entrySet().iterator(); it.hasNext(); ) {
-            Map.Entry<String,String> ent = it.next();
-            lst.add(ent.getKey()+" "+ent.getValue()+"\n");
+        for (Map.Entry<String,String> e : kvMap.entrySet()) {
+            lst.add(e.getKey()+" "+e.getValue()+"\n");
@@ -345,34 +330,33 @@
     /** 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<String> kvList) throws IOException {
         if (kvList.size() == 0)
             return;
-        StringBuffer b = new StringBuffer("SETCONF");
-        for (Iterator<String> it = kvList.iterator(); it.hasNext(); ) {
-            String kv = it.next();
+        StringBuilder b = new StringBuilder("SETCONF");
+        for (String kv : kvList) {
             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(" ").append(kv.substring(0,i)).append("=");
+            b.append(quote(kv.substring(i+1)));
         }
         b.append("\r\n");
         sendAndWaitForResponse(b.toString(), null);
     public void resetConf(Collection<String> keys) throws IOException {
         if (keys.size() == 0)
             return;
-        StringBuffer b = new StringBuffer("RESETCONF");
-        for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
-            String key = it.next();
+        StringBuilder b = new StringBuilder("RESETCONF");
+        for (String key : keys) {
             b.append(" ").append(key);
         }
         b.append("\r\n");
@@ -402,10 +385,10 @@
     /** 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"
@@ -413,23 +396,24 @@
      * HiddenServiceNodes, and HiddenServiceExcludeNodes option settings.
      */
     public List<ConfigEntry> getConf(Collection<String> keys) throws IOException {
-        StringBuffer sb = new StringBuffer("GETCONF");
-        for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
-            String key = it.next();
+        StringBuilder sb = new StringBuilder("GETCONF");
+        for (String key : keys) {
             sb.append(" ").append(key);
         }
         sb.append("\r\n");
         List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
         List<ConfigEntry> result = new ArrayList<ConfigEntry>();
-        for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
-            String kv = (it.next()).msg;
+        for (ReplyLine line : lst) {
+            String kv = line.msg;
             int idx = kv.indexOf('=');
-            if (idx >= 0)
-                result.add(new ConfigEntry(kv.substring(0, idx),
-                                           kv.substring(idx+1)));
-            else
+            if (idx >= 0) {
+                String key = kv.substring(0, idx);
+                String value = kv.substring(idx+1);
+                result.add(new ConfigEntry(key, value));
+            } else {
@@ -437,37 +421,38 @@
      * 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<String> events) throws IOException {
-        StringBuffer sb = new StringBuffer("SETEVENTS");
-        for (Iterator<String> it = events.iterator(); it.hasNext(); ) {
-            sb.append(" ").append(it.next());
+        StringBuilder sb = new StringBuilder("SETEVENTS");
+        for (String event : events) {
+            sb.append(" ").append(event);
         }
         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]).
+     * <p>
      * 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>.
+     * <p>
      * 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>'
+     * <tt>'tor --hash-password &lt;password&gt;'</tt>
      * 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.
      */
         if (debugOutput != null)
             debugOutput.print(">> "+s);
-        if (this.thread != null) {
-            this.thread.stopListening();
-    	}
         synchronized (waiters) {
@@ -519,7 +501,7 @@
      * 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
@@ -527,56 +509,52 @@
      * 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.
+     * <p>
      * 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.
+     * <p>
      * 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<String,String> mapAddresses(Collection<String> kvLines) throws IOException {
-        StringBuffer sb = new StringBuffer("MAPADDRESS");
-        for (Iterator<String> it = kvLines.iterator(); it.hasNext(); ) {
-            String kv = it.next();
+        StringBuilder sb = new StringBuilder("MAPADDRESS");
+        for (String kv : kvLines) {
             int i = kv.indexOf(' ');
-            sb.append(" ").append(kv.substring(0,i)).append("=")
-                .append(quote(kv.substring(i+1)));
+            sb.append(" ").append(kv.substring(0,i)).append("=");
+            sb.append(quote(kv.substring(i+1)));
         }
         sb.append("\r\n");
         List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
         Map<String,String> result = new HashMap<String,String>();
-        for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
-            String kv = (it.next()).msg;
+        for (ReplyLine line : lst) {
+            String kv = line.msg;
             int idx = kv.indexOf('=');
-            result.put(kv.substring(0, idx),
-                       kv.substring(idx+1));
+            result.put(kv.substring(0, idx), kv.substring(idx+1));
     public Map<String,String> mapAddresses(Map<String,String> addresses) throws IOException {
         List<String> kvList = new ArrayList<String>();
-        for (Iterator<Map.Entry<String, String>> it = addresses.entrySet().iterator(); it.hasNext(); ) {
-            Map.Entry<String,String> e = it.next();
+        for (Map.Entry<String,String> e : addresses.entrySet()) {
             kvList.add(e.getKey()+" "+e.getValue());
     public String mapAddress(String fromAddr, String toAddr) throws IOException {
-        List<String> lst = new ArrayList<String>();
-        lst.add(fromAddr+" "+toAddr+"\n");
-        Map<String,String> m = mapAddresses(lst);
+        String s = fromAddr+" "+toAddr+"\n";
+        Map<String,String> m = mapAddresses(Arrays.asList(s));
     /** 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
@@ -605,15 +583,14 @@
     public Map<String,String> getInfo(Collection<String> keys) throws IOException {
-        StringBuffer sb = new StringBuffer("GETINFO");
-        for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
-            sb.append(" ").append(it.next());
+        StringBuilder sb = new StringBuilder("GETINFO");
+        for (String key : keys) {
+            sb.append(" ").append(key);
         }
         sb.append("\r\n");
         List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
         Map<String,String> m = new HashMap<String,String>();
-        for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
-            ReplyLine line = it.next();
+        for (ReplyLine line : lst) {
             int idx = line.msg.indexOf('=');
             if (idx<0)
                 break;
@@ -629,13 +606,9 @@
         return m;
     }
     
-    
-    
-    /** Return the value of the information field 'key' */
+    /** Returns the value of the information field 'key' */
     public String getInfo(String key) throws IOException {
-        List<String> lst = new ArrayList<String>();
-        lst.add(key);
-        Map<String,String> m = getInfo(lst);
+        Map<String,String> m = getInfo(Arrays.asList(key));
@@ -644,40 +617,39 @@
      * 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>.
+     * <p>
      * If successful, returns the Circuit ID of the (maybe newly created) circuit.
      */
     public String extendCircuit(String circID, String path) throws IOException {
         List<ReplyLine> lst = sendAndWaitForResponse(
                           "EXTENDCIRCUIT "+circID+" "+path+"\r\n", null);
+        return 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>.  
+     * <p>
      * 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.
-     * 
+     * <p>
      * 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 {
+    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.
      */
@@ -685,12 +657,12 @@
     // No need for return value?  control-spec.txt says reply is merely "250 OK" on success...
     public String postDescriptor(String desc) throws IOException {
         List<ReplyLine> lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc);
+        return 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.
+     * <p>
      * 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.
@@ -720,8 +692,7 @@
      *
      * 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 {
+    public void closeStream(String streamID, byte reason) throws IOException {
         sendAndWaitForResponse("CLOSESTREAM "+streamID+" "+reason+"\r\n",null);
@@ -729,8 +700,8 @@
      * 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);
+        String arg = ifUnused ? " IFUNUSED" : "";
+        sendAndWaitForResponse("CLOSECIRCUIT "+circID+arg+"\r\n", null);
     }
 }
 
diff -Bbur jtorctl/net/freehaven/tor/control/TorControlError.java jtorctl-briar/net/freehaven/tor/control/TorControlError.java
--- jtorctl/net/freehaven/tor/control/TorControlError.java	2014-04-02 11:26:56.000000000 +0100
+++ jtorctl-briar/net/freehaven/tor/control/TorControlError.java	2014-04-02 12:28:01.000000000 +0100
@@ -2,13 +2,17 @@
 // See LICENSE file for copying information
 package net.freehaven.tor.control;
 
+import java.io.IOException;
+
 /**
  * An exception raised when Tor tells us about an error.
  */
-public class TorControlError extends RuntimeException {
-    static final long serialVersionUID = 2;
+public class TorControlError extends IOException {
+
+    private static final long serialVersionUID = 3;
+    private final int errorType;
 
-    int errorType;
     public TorControlError(int type, String s) {
         super(s);
         errorType = type;
diff -Bbur jtorctl/net/freehaven/tor/control/TorControlSyntaxError.java jtorctl-briar/net/freehaven/tor/control/TorControlSyntaxError.java
--- jtorctl/net/freehaven/tor/control/TorControlSyntaxError.java	2014-04-02 11:26:56.000000000 +0100
+++ jtorctl-briar/net/freehaven/tor/control/TorControlSyntaxError.java	2014-04-02 12:27:52.000000000 +0100
@@ -2,12 +2,16 @@
 // See LICENSE file for copying information
 package net.freehaven.tor.control;
 
+import java.io.IOException;
+
 /**
  * An exception raised when Tor behaves in an unexpected way.
  */
-public class TorControlSyntaxError extends RuntimeException {
-    static final long serialVersionUID = 2;
+public class TorControlSyntaxError extends IOException {
 
-    public TorControlSyntaxError(String s) { super(s); }
-}
+    private static final long serialVersionUID = 3;
+    public TorControlSyntaxError(String s) {
+        super(s);
+    }
+}