Add an absurd amount of logging.

This commit is contained in:
akwizgran
2019-04-02 16:52:28 +01:00
parent ea006d21f9
commit 5751958eaf

View File

@@ -13,7 +13,6 @@ import java.io.PrintWriter;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@@ -22,11 +21,18 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.CancellationException; import java.util.logging.Logger;
/** A connection to a running Tor process as specified in control-spec.txt. */ import static java.util.logging.Logger.getLogger;
/**
* A connection to a running Tor process as specified in control-spec.txt.
*/
public class TorControlConnection implements TorControlCommands { public class TorControlConnection implements TorControlCommands {
private static final Logger LOG =
getLogger(TorControlConnection.class.getName());
private final LinkedList<Waiter> waiters; private final LinkedList<Waiter> waiters;
private final BufferedReader input; private final BufferedReader input;
private final Writer output; private final Writer output;
@@ -42,24 +48,45 @@ public class TorControlConnection implements TorControlCommands {
List<ReplyLine> response; // Locking: this List<ReplyLine> response; // Locking: this
boolean interrupted; boolean interrupted;
synchronized List<ReplyLine> getResponse() throws InterruptedException { List<ReplyLine> getResponse() throws InterruptedException {
LOG.info("Entering synchronized (waiter " + hashCode() + ")");
synchronized (this) {
LOG.info("Entered synchronized (waiter " + hashCode() + ")");
while (response == null) { while (response == null) {
LOG.info("Waiter " + hashCode() + " waiting for response");
wait(); wait();
if (interrupted) { if (interrupted) {
LOG.info("Waiter " + hashCode() + " interrupted");
throw new InterruptedException(); throw new InterruptedException();
} }
} }
LOG.info("Waiter " + hashCode() + " got response " + response);
LOG.info("Leaving synchronized (waiter " + hashCode() + ")");
return response; return response;
} }
synchronized void setResponse(List<ReplyLine> response) {
this.response = response;
notifyAll();
} }
synchronized void interrupt() { void setResponse(List<ReplyLine> response) {
LOG.info("Entering synchronized (waiter " + hashCode() + ")");
synchronized (this) {
LOG.info("Entered synchronized (waiter " + hashCode() + ")");
LOG.info("Setting response for waiter " + hashCode() + ": "
+ response);
this.response = response;
notifyAll();
LOG.info("Leaving synchronized (waiter " + hashCode() + ")");
}
}
void interrupt() {
LOG.info("Entering synchronized (waiter " + hashCode() + ")");
synchronized (this) {
LOG.info("Entered synchronized (waiter " + hashCode() + ")");
LOG.info("Interrupting waiter " + hashCode());
interrupted = true; interrupted = true;
notifyAll(); notifyAll();
LOG.info("Leaving synchronized (waiter " + hashCode() + ")");
}
} }
} }
@@ -70,18 +97,28 @@ public class TorControlConnection implements TorControlCommands {
final String rest; final String rest;
ReplyLine(String status, String msg, 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;
}
@Override
public String toString() {
return status + " " + msg + " " + rest;
} }
} }
/** Create a new TorControlConnection to communicate with Tor over /**
* Create a new TorControlConnection to communicate with Tor over
* a given socket. After calling this constructor, it is typical to * a given socket. After calling this constructor, it is typical to
* call launchThread and authenticate. */ * call launchThread and authenticate.
*/
public TorControlConnection(Socket connection) throws IOException { public TorControlConnection(Socket connection) throws IOException {
this(connection.getInputStream(), connection.getOutputStream()); this(connection.getInputStream(), connection.getOutputStream());
} }
/** Create a new TorControlConnection to communicate with Tor over /**
* Create a new TorControlConnection to communicate with Tor over
* an arbitrary pair of data streams. * an arbitrary pair of data streams.
*/ */
public TorControlConnection(InputStream i, OutputStream o) { public TorControlConnection(InputStream i, OutputStream o) {
@@ -94,7 +131,7 @@ public class TorControlConnection implements TorControlCommands {
this.input = (BufferedReader) i; this.input = (BufferedReader) i;
else else
this.input = new BufferedReader(i); this.input = new BufferedReader(i);
this.waiters = new LinkedList<Waiter>(); this.waiters = new LinkedList<>();
} }
protected final void writeEscaped(String s) throws IOException { protected final void writeEscaped(String s) throws IOException {
@@ -102,13 +139,13 @@ public class TorControlConnection implements TorControlCommands {
while (st.hasMoreTokens()) { while (st.hasMoreTokens()) {
String line = st.nextToken(); String line = st.nextToken();
if (line.startsWith(".")) if (line.startsWith("."))
line = "."+line; line = "." + line;
if (line.endsWith("\r")) if (line.endsWith("\r"))
line += "\n"; line += "\n";
else else
line += "\r\n"; line += "\r\n";
if (debugOutput != null) if (debugOutput != null)
debugOutput.print(">> "+line); debugOutput.print(">> " + line);
output.write(line); output.write(line);
} }
output.write(".\r\n"); output.write(".\r\n");
@@ -116,12 +153,11 @@ public class TorControlConnection implements TorControlCommands {
debugOutput.print(">> .\n"); debugOutput.print(">> .\n");
} }
protected static final String quote(String s) { protected static String quote(String s) {
StringBuffer sb = new StringBuffer("\""); StringBuffer sb = new StringBuffer("\"");
for (int i = 0; i < s.length(); ++i) { for (int i = 0; i < s.length(); ++i) {
char c = s.charAt(i); char c = s.charAt(i);
switch (c) switch (c) {
{
case '\r': case '\r':
case '\n': case '\n':
case '\\': case '\\':
@@ -135,7 +171,7 @@ public class TorControlConnection implements TorControlCommands {
} }
protected final ArrayList<ReplyLine> readReply() throws IOException { protected final ArrayList<ReplyLine> readReply() throws IOException {
ArrayList<ReplyLine> reply = new ArrayList<ReplyLine>(); ArrayList<ReplyLine> reply = new ArrayList<>();
char c; char c;
do { do {
String line = input.readLine(); String line = input.readLine();
@@ -151,10 +187,11 @@ public class TorControlConnection implements TorControlCommands {
" broke down while receiving reply!"); " broke down while receiving reply!");
} }
if (debugOutput != null) if (debugOutput != null)
debugOutput.println("<< "+line); debugOutput.println("<< " + line);
if (line.length() < 4) if (line.length() < 4)
throw new TorControlSyntaxError("Line (\""+line+"\") too short"); throw new TorControlSyntaxError(
String status = line.substring(0,3); "Line (\"" + line + "\") too short");
String status = line.substring(0, 3);
c = line.charAt(3); c = line.charAt(3);
String msg = line.substring(4); String msg = line.substring(4);
String rest = null; String rest = null;
@@ -163,7 +200,7 @@ public class TorControlConnection implements TorControlCommands {
while (true) { while (true) {
line = input.readLine(); line = input.readLine();
if (debugOutput != null) if (debugOutput != null)
debugOutput.print("<< "+line); debugOutput.print("<< " + line);
if (line.equals(".")) if (line.equals("."))
break; break;
else if (line.startsWith(".")) else if (line.startsWith("."))
@@ -178,36 +215,61 @@ public class TorControlConnection implements TorControlCommands {
return reply; return reply;
} }
protected synchronized List<ReplyLine> sendAndWaitForResponse(String s, protected List<ReplyLine> sendAndWaitForResponse(String s,
String rest) throws IOException { String rest) throws IOException {
if (parseThreadException != null) throw parseThreadException; LOG.info("Entering synchronized (connection)");
synchronized (this) {
LOG.info("Entered synchronized (connection)");
LOG.info("Sending '" + s + "', '" + rest +
"' and waiting for response");
if (parseThreadException != null) {
LOG.info("Throwing previously caught exception "
+ parseThreadException);
throw parseThreadException;
}
checkThread(); checkThread();
Waiter w = new Waiter(); Waiter w = new Waiter();
LOG.info("Created waiter " + w.hashCode());
if (debugOutput != null) if (debugOutput != null)
debugOutput.print(">> "+s); debugOutput.print(">> " + s);
LOG.info("Entering synchronized (waiters)");
synchronized (waiters) { synchronized (waiters) {
LOG.info("Entered synchronized (waiters)");
output.write(s); output.write(s);
if (rest != null) LOG.info("Wrote '" + s + "'");
if (rest != null) {
writeEscaped(rest); writeEscaped(rest);
LOG.info("Wrote escaped '" + rest + "'");
}
output.flush(); output.flush();
LOG.info("Flushed output");
waiters.addLast(w); waiters.addLast(w);
LOG.info("Added waiter, " + waiters.size() + " waiting");
LOG.info("Leaving synchronized (waiters)");
} }
List<ReplyLine> lst; List<ReplyLine> lst;
try { try {
LOG.info("Getting response from waiter " + w.hashCode());
lst = w.getResponse(); lst = w.getResponse();
LOG.info("Got response from waiter " + w.hashCode() + ": " +
lst);
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
throw new IOException("Interrupted"); throw new IOException("Interrupted");
} }
for (Iterator<ReplyLine> i = lst.iterator(); i.hasNext(); ) { for (Iterator<ReplyLine> i = lst.iterator(); i.hasNext(); ) {
ReplyLine c = i.next(); ReplyLine c = i.next();
if (! c.status.startsWith("2")) if (!c.status.startsWith("2"))
throw new TorControlError("Error reply: "+c.msg); throw new TorControlError("Error reply: " + c.msg);
} }
LOG.info("Leaving synchronized (connection)");
return lst; return lst;
} }
}
/** Helper: decode a CMD_EVENT command and dispatch it to our /**
* EventHandler (if any). */ * Helper: decode a CMD_EVENT command and dispatch it to our
* EventHandler (if any).
*/
protected void handleEvent(ArrayList<ReplyLine> events) { protected void handleEvent(ArrayList<ReplyLine> events) {
if (handler == null) if (handler == null)
return; return;
@@ -216,7 +278,7 @@ public class TorControlConnection implements TorControlCommands {
ReplyLine line = i.next(); ReplyLine line = i.next();
int idx = line.msg.indexOf(' '); int idx = line.msg.indexOf(' ');
String tp = line.msg.substring(0, idx).toUpperCase(); String tp = line.msg.substring(0, idx).toUpperCase();
String rest = line.msg.substring(idx+1); String rest = line.msg.substring(idx + 1);
if (tp.equals("CIRC")) { if (tp.equals("CIRC")) {
List<String> lst = Bytes.splitStr(null, rest); List<String> lst = Bytes.splitStr(null, rest);
handler.circuitStatus(lst.get(1), handler.circuitStatus(lst.get(1),
@@ -253,7 +315,8 @@ public class TorControlConnection implements TorControlCommands {
} }
/** Sets <b>w</b> as the PrintWriter for debugging output, /**
* Sets <b>w</b> as the PrintWriter for debugging output,
* which writes out all messages passed between Tor and the controller. * which writes out all messages passed between Tor and the controller.
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded * Outgoing messages are preceded by "\>\>" and incoming messages are preceded
* by "\<\<" * by "\<\<"
@@ -262,7 +325,8 @@ public class TorControlConnection implements TorControlCommands {
debugOutput = w; debugOutput = w;
} }
/** Sets <b>s</b> as the PrintStream for debugging output, /**
* Sets <b>s</b> as the PrintStream for debugging output,
* which writes out all messages passed between Tor and the controller. * which writes out all messages passed between Tor and the controller.
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded * Outgoing messages are preceded by "\>\>" and incoming messages are preceded
* by "\<\<" * by "\<\<"
@@ -271,9 +335,11 @@ public class TorControlConnection implements TorControlCommands {
debugOutput = new PrintWriter(s, true); debugOutput = new PrintWriter(s, true);
} }
/** Set the EventHandler object that will be notified of any /**
* Set the EventHandler object that will be notified of any
* events Tor delivers to this connection. To make Tor send us * events Tor delivers to this connection. To make Tor send us
* events, call setEvents(). */ * events, call setEvents().
*/
public void setEventHandler(EventHandler handler) { public void setEventHandler(EventHandler handler) {
this.handler = handler; this.handler = handler;
} }
@@ -283,14 +349,20 @@ public class TorControlConnection implements TorControlCommands {
* This is necessary to handle asynchronous events and synchronous * This is necessary to handle asynchronous events and synchronous
* responses that arrive independantly over the same socket. * responses that arrive independantly over the same socket.
*/ */
public synchronized Thread launchThread(boolean daemon) { public Thread launchThread(boolean daemon) {
LOG.info("Entering synchronized (connection)");
synchronized (this) {
LOG.info("Entered synchronized (connection)");
ControlParseThread th = new ControlParseThread(); ControlParseThread th = new ControlParseThread();
LOG.info("Launching parse thread " + th.hashCode());
if (daemon) if (daemon)
th.setDaemon(true); th.setDaemon(true);
th.start(); th.start();
this.thread = th; this.thread = th;
LOG.info("Leaving synchronized (connection)");
return th; return th;
} }
}
protected class ControlParseThread extends Thread { protected class ControlParseThread extends Thread {
@@ -299,82 +371,109 @@ public class TorControlConnection implements TorControlCommands {
try { try {
react(); react();
} catch (IOException ex) { } catch (IOException ex) {
LOG.info("Parse thread " + hashCode()
+ " caught exception " + ex);
parseThreadException = ex; parseThreadException = ex;
} }
} }
} }
protected synchronized void checkThread() { protected void checkThread() {
LOG.info("Entering synchronized (connection)");
synchronized (this) {
LOG.info("Entered synchronized (connection)");
if (thread == null) if (thread == null)
launchThread(true); launchThread(true);
LOG.info("Leaving synchronized (connection)");
}
} }
/** helper: implement the main background loop. */ /**
* helper: implement the main background loop.
*/
protected void react() throws IOException { protected void react() throws IOException {
while (true) { while (true) {
ArrayList<ReplyLine> lst = readReply(); ArrayList<ReplyLine> lst = readReply();
LOG.info("Read reply: " + lst);
if (lst.isEmpty()) { if (lst.isEmpty()) {
// interrupted queued waiters, there won't be any response. // interrupted queued waiters, there won't be any response.
LOG.info("Entering synchronized (waiters)");
synchronized (waiters) { synchronized (waiters) {
LOG.info("Entered synchronized (waiters)");
if (!waiters.isEmpty()) { if (!waiters.isEmpty()) {
for (Waiter w : waiters) { for (Waiter w : waiters) {
LOG.info("Interrupting waiter " + w.hashCode());
w.interrupt(); w.interrupt();
} }
} else {
LOG.info("No waiters");
} }
LOG.info("Leaving synchronized (waiters)");
} }
throw new IOException("Tor is no longer running"); throw new IOException("Tor is no longer running");
} }
if ((lst.get(0)).status.startsWith("6")) if ((lst.get(0)).status.startsWith("6")) {
LOG.info("Reply is an event");
handleEvent(lst); handleEvent(lst);
else { } else {
LOG.info("Entering synchronized (waiters)");
synchronized (waiters) { synchronized (waiters) {
if (!waiters.isEmpty()) LOG.info("Entered synchronized (waiters)");
{ if (!waiters.isEmpty()) {
Waiter w; Waiter w;
w = waiters.removeFirst(); w = waiters.removeFirst();
LOG.info("Setting response for waiter " + w.hashCode());
w.setResponse(lst); w.setResponse(lst);
} else {
LOG.info("No waiters");
} }
LOG.info("Leaving synchronized (waiters)");
} }
} }
} }
} }
/** 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 { public void setConf(String key, String value) throws IOException {
List<String> lst = new ArrayList<String>(); List<String> lst = new ArrayList<>();
lst.add(key+" "+value); lst.add(key + " " + value);
setConf(lst); setConf(lst);
} }
/** Change the values of the configuration options stored in kvMap. */ /**
* Change the values of the configuration options stored in kvMap.
*/
public void setConf(Map<String, String> kvMap) throws IOException { public void setConf(Map<String, String> kvMap) throws IOException {
List<String> lst = new ArrayList<String>(); List<String> lst = new ArrayList<>();
for (Iterator<Map.Entry<String,String>> it = kvMap.entrySet().iterator(); it.hasNext(); ) { for (Iterator<Map.Entry<String, String>> it =
Map.Entry<String,String> ent = it.next(); kvMap.entrySet().iterator(); it.hasNext(); ) {
lst.add(ent.getKey()+" "+ent.getValue()+"\n"); Map.Entry<String, String> ent = it.next();
lst.add(ent.getKey() + " " + ent.getValue() + "\n");
} }
setConf(lst); setConf(lst);
} }
/** Changes the values of the configuration options stored in /**
* Changes the values of the configuration options stored in
* <b>kvList</b>. Each list element in <b>kvList</b> is expected to be * <b>kvList</b>. Each list element in <b>kvList</b> is expected to be
* String of the format "key value". * String of the format "key value".
* * <p>
* Tor behaves as though it had just read each of the key-value pairs * Tor behaves as though it had just read each of the key-value pairs
* from its configuration file. Keywords with no corresponding values have * from its configuration file. Keywords with no corresponding values have
* their configuration values reset to their defaults. setConf is * their configuration values reset to their defaults. setConf is
* all-or-nothing: if there is an error in any of the configuration settings, * all-or-nothing: if there is an error in any of the configuration settings,
* Tor sets none of them. * Tor sets none of them.
* * <p>
* When a configuration option takes multiple values, or when multiple * When a configuration option takes multiple values, or when multiple
* configuration keys form a context-sensitive group (see getConf below), then * 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 * 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 * the others. For example, if two ORBindAddress values are configured, and a
* command arrives containing a single ORBindAddress value, the new * command arrives containing a single ORBindAddress value, the new
* command's value replaces the two old values. * command's value replaces the two old values.
* * <p>
* To remove all settings for a given option entirely (and go back to its * 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. * default value), include a String in <b>kvList</b> containing the key and no value.
*/ */
@@ -387,14 +486,15 @@ public class TorControlConnection implements TorControlCommands {
int i = kv.indexOf(' '); int i = kv.indexOf(' ');
if (i == -1) if (i == -1)
b.append(" ").append(kv); b.append(" ").append(kv);
b.append(" ").append(kv.substring(0,i)).append("=") b.append(" ").append(kv.substring(0, i)).append("=")
.append(quote(kv.substring(i+1))); .append(quote(kv.substring(i + 1)));
} }
b.append("\r\n"); b.append("\r\n");
sendAndWaitForResponse(b.toString(), null); sendAndWaitForResponse(b.toString(), null);
} }
/** Try to reset the values listed in the collection 'keys' to their /**
* Try to reset the values listed in the collection 'keys' to their
* default values. * default values.
**/ **/
public void resetConf(Collection<String> keys) throws IOException { public void resetConf(Collection<String> keys) throws IOException {
@@ -409,26 +509,30 @@ public class TorControlConnection implements TorControlCommands {
sendAndWaitForResponse(b.toString(), null); sendAndWaitForResponse(b.toString(), null);
} }
/** Return the value of the configuration option 'key' */ /**
* Return the value of the configuration option 'key'
*/
public List<ConfigEntry> getConf(String key) throws IOException { public List<ConfigEntry> getConf(String key) throws IOException {
List<String> lst = new ArrayList<String>(); List<String> lst = new ArrayList<>();
lst.add(key); lst.add(key);
return getConf(lst); return getConf(lst);
} }
/** Requests the values of the configuration variables listed in <b>keys</b>. /**
* Requests the values of the configuration variables listed in <b>keys</b>.
* Results are returned as a list of ConfigEntry objects. * Results are returned as a list of ConfigEntry objects.
* * <p>
* If an option appears multiple times in the configuration, all of its * If an option appears multiple times in the configuration, all of its
* key-value pairs are returned in order. * key-value pairs are returned in order.
* * <p>
* Some options are context-sensitive, and depend on other options with * Some options are context-sensitive, and depend on other options with
* different keywords. These cannot be fetched directly. Currently there * different keywords. These cannot be fetched directly. Currently there
* is only one such option: clients should use the "HiddenServiceOptions" * is only one such option: clients should use the "HiddenServiceOptions"
* virtual keyword to get all HiddenServiceDir, HiddenServicePort, * virtual keyword to get all HiddenServiceDir, HiddenServicePort,
* HiddenServiceNodes, and HiddenServiceExcludeNodes option settings. * HiddenServiceNodes, and HiddenServiceExcludeNodes option settings.
*/ */
public List<ConfigEntry> getConf(Collection<String> keys) throws IOException { public List<ConfigEntry> getConf(Collection<String> keys)
throws IOException {
StringBuffer sb = new StringBuffer("GETCONF"); StringBuffer sb = new StringBuffer("GETCONF");
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) { for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
String key = it.next(); String key = it.next();
@@ -436,24 +540,25 @@ public class TorControlConnection implements TorControlCommands {
} }
sb.append("\r\n"); sb.append("\r\n");
List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null); List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
List<ConfigEntry> result = new ArrayList<ConfigEntry>(); List<ConfigEntry> result = new ArrayList<>();
for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) { for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
String kv = (it.next()).msg; String kv = (it.next()).msg;
int idx = kv.indexOf('='); int idx = kv.indexOf('=');
if (idx >= 0) if (idx >= 0)
result.add(new ConfigEntry(kv.substring(0, idx), result.add(new ConfigEntry(kv.substring(0, idx),
kv.substring(idx+1))); kv.substring(idx + 1)));
else else
result.add(new ConfigEntry(kv)); result.add(new ConfigEntry(kv));
} }
return result; return result;
} }
/** Request that the server inform the client about interesting events. /**
* Request that the server inform the client about interesting events.
* Each element of <b>events</b> is one of the following Strings: * Each element of <b>events</b> is one of the following Strings:
* ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" | * ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" |
* "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] . * "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] .
* * <p>
* Any events not listed in the <b>events</b> are turned off; thus, calling * 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. * setEvents with an empty <b>events</b> argument turns off all event reporting.
*/ */
@@ -466,21 +571,22 @@ public class TorControlConnection implements TorControlCommands {
sendAndWaitForResponse(sb.toString(), null); sendAndWaitForResponse(sb.toString(), null);
} }
/** Authenticates the controller to the Tor server. /**
* * Authenticates the controller to the Tor server.
* <p>
* By default, the current Tor implementation trusts all local users, and * By default, the current Tor implementation trusts all local users, and
* the controller can authenticate itself by calling authenticate(new byte[0]). * the controller can authenticate itself by calling authenticate(new byte[0]).
* * <p>
* If the 'CookieAuthentication' option is true, Tor writes a "magic cookie" * If the 'CookieAuthentication' option is true, Tor writes a "magic cookie"
* file named "control_auth_cookie" into its data directory. To authenticate, * file named "control_auth_cookie" into its data directory. To authenticate,
* the controller must send the contents of this file in <b>auth</b>. * 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 * 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 * 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. * S2K algorithm in RFC 2440 (OpenPGP), and prefixed with the s2k specifier.
* This is then encoded in hexadecimal, prefixed by the indicator sequence * This is then encoded in hexadecimal, prefixed by the indicator sequence
* "16:". * "16:".
* * <p>
* You can generate the salt of a password by calling * You can generate the salt of a password by calling
* 'tor --hash-password <password>' * 'tor --hash-password <password>'
* or by using the provided PasswordDigest class. * or by using the provided PasswordDigest class.
@@ -492,13 +598,15 @@ public class TorControlConnection implements TorControlCommands {
sendAndWaitForResponse(cmd, null); sendAndWaitForResponse(cmd, null);
} }
/** Instructs the server to write out its configuration options into its torrc. /**
* Instructs the server to write out its configuration options into its torrc.
*/ */
public void saveConf() throws IOException { public void saveConf() throws IOException {
sendAndWaitForResponse("SAVECONF\r\n", null); sendAndWaitForResponse("SAVECONF\r\n", null);
} }
/** Sends a signal from the controller to the Tor server. /**
* Sends a signal from the controller to the Tor server.
* <b>signal</b> is one of the following Strings: * <b>signal</b> is one of the following Strings:
* <ul> * <ul>
* <li>"RELOAD" or "HUP" : Reload config items, refetch directory</li> * <li>"RELOAD" or "HUP" : Reload config items, refetch directory</li>
@@ -514,24 +622,30 @@ public class TorControlConnection implements TorControlCommands {
sendAndWaitForResponse(cmd, null); sendAndWaitForResponse(cmd, null);
} }
/** Send a signal to the Tor process to shut it down or halt it. /**
* Does not wait for a response. */ * 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 { public void shutdownTor(String signal) throws IOException {
String s = "SIGNAL " + signal + "\r\n"; String s = "SIGNAL " + signal + "\r\n";
Waiter w = new Waiter(); Waiter w = new Waiter();
if (debugOutput != null) if (debugOutput != null)
debugOutput.print(">> "+s); debugOutput.print(">> " + s);
LOG.info("Entering synchronized (waiters)");
synchronized (waiters) { synchronized (waiters) {
LOG.info("Entered synchronized (waiters)");
output.write(s); output.write(s);
output.flush(); output.flush();
LOG.info("Leaving synchronized (waiters)");
} }
} }
/** Tells the Tor server that future SOCKS requests for connections to a set of original /**
* 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 should be replaced with connections to the specified replacement
* addresses. Each element of <b>kvLines</b> is a String of the form * addresses. Each element of <b>kvLines</b> is a String of the form
* "old-address new-address". This function returns the new address mapping. * "old-address new-address". This function returns the new address mapping.
* * <p>
* The client may decline to provide a body for the original address, and * 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 * 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 * "." for hostname), signifying that the server should choose the original
@@ -539,56 +653,61 @@ public class TorControlConnection implements TorControlCommands {
* should ensure that it returns an element of address space that is unlikely * 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 * to be in actual use. If there is already an address mapped to the
* destination address, the server may reuse that mapping. * destination address, the server may reuse that mapping.
* * <p>
* If the original address is already mapped to a different address, the old * If the original address is already mapped to a different address, the old
* mapping is removed. If the original address and the destination address * mapping is removed. If the original address and the destination address
* are the same, the server removes any mapping in place for the original * are the same, the server removes any mapping in place for the original
* address. * address.
* * <p>
* Mappings set by the controller last until the Tor process exits: * Mappings set by the controller last until the Tor process exits:
* they never expire. If the controller wants the mapping to last only * 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 * a certain time, then it must explicitly un-map the address when that
* time has elapsed. * time has elapsed.
*/ */
public Map<String,String> mapAddresses(Collection<String> kvLines) throws IOException { public Map<String, String> mapAddresses(Collection<String> kvLines)
throws IOException {
StringBuffer sb = new StringBuffer("MAPADDRESS"); StringBuffer sb = new StringBuffer("MAPADDRESS");
for (Iterator<String> it = kvLines.iterator(); it.hasNext(); ) { for (Iterator<String> it = kvLines.iterator(); it.hasNext(); ) {
String kv = it.next(); String kv = it.next();
int i = kv.indexOf(' '); int i = kv.indexOf(' ');
sb.append(" ").append(kv.substring(0,i)).append("=") sb.append(" ").append(kv.substring(0, i)).append("=")
.append(quote(kv.substring(i+1))); .append(quote(kv.substring(i + 1)));
} }
sb.append("\r\n"); sb.append("\r\n");
List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null); List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
Map<String,String> result = new HashMap<String,String>(); Map<String, String> result = new HashMap<>();
for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) { for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
String kv = (it.next()).msg; String kv = (it.next()).msg;
int idx = kv.indexOf('='); int idx = kv.indexOf('=');
result.put(kv.substring(0, idx), result.put(kv.substring(0, idx),
kv.substring(idx+1)); kv.substring(idx + 1));
} }
return result; return result;
} }
public Map<String,String> mapAddresses(Map<String,String> addresses) throws IOException { public Map<String, String> mapAddresses(Map<String, String> addresses)
List<String> kvList = new ArrayList<String>(); throws IOException {
for (Iterator<Map.Entry<String, String>> it = addresses.entrySet().iterator(); it.hasNext(); ) { List<String> kvList = new ArrayList<>();
Map.Entry<String,String> e = it.next(); for (Iterator<Map.Entry<String, String>> it =
kvList.add(e.getKey()+" "+e.getValue()); addresses.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, String> e = it.next();
kvList.add(e.getKey() + " " + e.getValue());
} }
return mapAddresses(kvList); return mapAddresses(kvList);
} }
public String mapAddress(String fromAddr, String toAddr) throws IOException { public String mapAddress(String fromAddr, String toAddr)
List<String> lst = new ArrayList<String>(); throws IOException {
lst.add(fromAddr+" "+toAddr+"\n"); List<String> lst = new ArrayList<>();
Map<String,String> m = mapAddresses(lst); lst.add(fromAddr + " " + toAddr + "\n");
Map<String, String> m = mapAddresses(lst);
return m.get(fromAddr); return m.get(fromAddr);
} }
/** Queries the Tor server for keyed values that are not stored in the torrc /**
* Queries the Tor server for keyed values that are not stored in the torrc
* configuration file. Returns a map of keys to values. * configuration file. Returns a map of keys to values.
* * <p>
* Recognized keys include: * Recognized keys include:
* <ul> * <ul>
* <li>"version" : The version of the server's software, including the name * <li>"version" : The version of the server's software, including the name
@@ -616,25 +735,26 @@ public class TorControlConnection implements TorControlCommands {
* form: "ServerID ORStatus"</li> * form: "ServerID ORStatus"</li>
* </ul> * </ul>
*/ */
public Map<String,String> getInfo(Collection<String> keys) throws IOException { public Map<String, String> getInfo(Collection<String> keys)
throws IOException {
StringBuffer sb = new StringBuffer("GETINFO"); StringBuffer sb = new StringBuffer("GETINFO");
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) { for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
sb.append(" ").append(it.next()); sb.append(" ").append(it.next());
} }
sb.append("\r\n"); sb.append("\r\n");
List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null); List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
Map<String,String> m = new HashMap<String,String>(); Map<String, String> m = new HashMap<>();
for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) { for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
ReplyLine line = it.next(); ReplyLine line = it.next();
int idx = line.msg.indexOf('='); int idx = line.msg.indexOf('=');
if (idx<0) if (idx < 0)
break; break;
String k = line.msg.substring(0,idx); String k = line.msg.substring(0, idx);
String v; String v;
if (line.rest != null) { if (line.rest != null) {
v = line.rest; v = line.rest;
} else { } else {
v = line.msg.substring(idx+1); v = line.msg.substring(idx + 1);
} }
m.put(k, v); m.put(k, v);
} }
@@ -642,41 +762,44 @@ public class TorControlConnection implements TorControlCommands {
} }
/**
/** Return the value of the information field 'key' */ * Return the value of the information field 'key'
*/
public String getInfo(String key) throws IOException { public String getInfo(String key) throws IOException {
List<String> lst = new ArrayList<String>(); List<String> lst = new ArrayList<>();
lst.add(key); lst.add(key);
Map<String,String> m = getInfo(lst); Map<String, String> m = getInfo(lst);
return m.get(key); return m.get(key);
} }
/** An extendCircuit request takes one of two forms: either the <b>circID</b> is zero, in /**
* An extendCircuit request takes one of two forms: either the <b>circID</b> is zero, in
* which case it is a request for the server to build a new circuit according * which case it is a request for the server to build a new circuit according
* to the specified path, or the <b>circID</b> is nonzero, in which case it is a * 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 * request for the server to extend an existing circuit with that ID according
* to the specified <b>path</b>. * to the specified <b>path</b>.
* * <p>
* If successful, returns the Circuit ID of the (maybe newly created) circuit. * If successful, returns the Circuit ID of the (maybe newly created) circuit.
*/ */
public String extendCircuit(String circID, String path) throws IOException { public String extendCircuit(String circID, String path) throws IOException {
List<ReplyLine> lst = sendAndWaitForResponse( List<ReplyLine> lst = sendAndWaitForResponse(
"EXTENDCIRCUIT "+circID+" "+path+"\r\n", null); "EXTENDCIRCUIT " + circID + " " + path + "\r\n", null);
return (lst.get(0)).msg; return (lst.get(0)).msg;
} }
/** Informs the Tor server that the stream specified by <b>streamID</b> should be /**
* Informs the Tor server that the stream specified by <b>streamID</b> should be
* associated with the circuit specified by <b>circID</b>. * associated with the circuit specified by <b>circID</b>.
* * <p>
* Each stream may be associated with * Each stream may be associated with
* at most one circuit, and multiple streams may share the same circuit. * at most one circuit, and multiple streams may share the same circuit.
* Streams can only be attached to completed circuits (that is, circuits that * 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 * have sent a circuit status "BUILT" event or are listed as built in a
* getInfo circuit-status request). * getInfo circuit-status request).
* * <p>
* If <b>circID</b> is 0, responsibility for attaching the given stream is * If <b>circID</b> is 0, responsibility for attaching the given stream is
* returned to Tor. * returned to Tor.
* * <p>
* By default, Tor automatically attaches streams to * By default, Tor automatically attaches streams to
* circuits itself, unless the configuration variable * circuits itself, unless the configuration variable
* "__LeaveStreamsUnattached" is set to "1". Attempting to attach streams * "__LeaveStreamsUnattached" is set to "1". Attempting to attach streams
@@ -685,34 +808,41 @@ public class TorControlConnection implements TorControlCommands {
*/ */
public void attachStream(String streamID, String circID) public void attachStream(String streamID, String circID)
throws IOException { throws IOException {
sendAndWaitForResponse("ATTACHSTREAM "+streamID+" "+circID+"\r\n", null); sendAndWaitForResponse(
"ATTACHSTREAM " + streamID + " " + circID + "\r\n", null);
} }
/** Tells Tor about the server descriptor in <b>desc</b>. /**
* * Tells Tor about the server descriptor in <b>desc</b>.
* <p>
* The descriptor, when parsed, must contain a number of well-specified * The descriptor, when parsed, must contain a number of well-specified
* fields, including fields for its nickname and identity. * fields, including fields for its nickname and identity.
*/ */
// More documentation here on format of desc? // More documentation here on format of desc?
// No need for return value? control-spec.txt says reply is merely "250 OK" on success... // No need for return value? control-spec.txt says reply is merely "250 OK" on success...
public String postDescriptor(String desc) throws IOException { public String postDescriptor(String desc) throws IOException {
List<ReplyLine> lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc); List<ReplyLine> lst =
sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc);
return (lst.get(0)).msg; return (lst.get(0)).msg;
} }
/** Tells Tor to change the exit address of the stream identified by <b>streamID</b> /**
* Tells Tor to change the exit address of the stream identified by <b>streamID</b>
* to <b>address</b>. No remapping is performed on the new provided address. * to <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 * 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 * after a new stream event is received, and before attaching this stream to
* a circuit. * a circuit.
*/ */
public void redirectStream(String streamID, String address) throws IOException { public void redirectStream(String streamID, String address)
sendAndWaitForResponse("REDIRECTSTREAM "+streamID+" "+address+"\r\n", throws IOException {
sendAndWaitForResponse(
"REDIRECTSTREAM " + streamID + " " + address + "\r\n",
null); null);
} }
/** Tells Tor to close the stream identified by <b>streamID</b>. /**
* Tells Tor to close the stream identified by <b>streamID</b>.
* <b>reason</b> should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal: * <b>reason</b> should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal:
* <ul> * <ul>
* <li>1 -- REASON_MISC (catch-all for unlisted reasons)</li> * <li>1 -- REASON_MISC (catch-all for unlisted reasons)</li>
@@ -729,23 +859,27 @@ public class TorControlConnection implements TorControlCommands {
* <li>12 -- REASON_CONNRESET (Connection was unexpectedly reset)</li> * <li>12 -- REASON_CONNRESET (Connection was unexpectedly reset)</li>
* <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)</li> * <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)</li>
* </ul> * </ul>
* * <p>
* Tor may hold the stream open for a while to flush any data that is pending. * Tor may hold the stream open for a while to flush any data that is pending.
*/ */
public void closeStream(String streamID, byte reason) public void closeStream(String streamID, byte reason)
throws IOException { throws IOException {
sendAndWaitForResponse("CLOSESTREAM "+streamID+" "+reason+"\r\n",null); sendAndWaitForResponse(
"CLOSESTREAM " + streamID + " " + reason + "\r\n", null);
} }
/** Tells Tor to close the circuit identified by <b>circID</b>. /**
* Tells Tor to close the circuit identified by <b>circID</b>.
* If <b>ifUnused</b> is true, do not close the circuit unless it is unused. * If <b>ifUnused</b> is true, do not close the circuit unless it is unused.
*/ */
public void closeCircuit(String circID, boolean ifUnused) throws IOException { public void closeCircuit(String circID, boolean ifUnused)
sendAndWaitForResponse("CLOSECIRCUIT "+circID+ throws IOException {
(ifUnused?" IFUNUSED":"")+"\r\n", null); sendAndWaitForResponse("CLOSECIRCUIT " + circID +
(ifUnused ? " IFUNUSED" : "") + "\r\n", null);
} }
/** Tells Tor to exit when this control connection is closed. This command /**
* Tells Tor to exit when this control connection is closed. This command
* was added in Tor 0.2.2.28-beta. * was added in Tor 0.2.2.28-beta.
*/ */
public void takeOwnership() throws IOException { public void takeOwnership() throws IOException {
@@ -758,7 +892,7 @@ public class TorControlConnection implements TorControlCommands {
* <p/> * <p/>
* ADD_ONION was added in Tor 0.2.7.1-alpha. * ADD_ONION was added in Tor 0.2.7.1-alpha.
*/ */
public Map<String,String> addOnion(Map<Integer,String> portLines) public Map<String, String> addOnion(Map<Integer, String> portLines)
throws IOException { throws IOException {
return addOnion("NEW:BEST", portLines, null); return addOnion("NEW:BEST", portLines, null);
} }
@@ -769,7 +903,7 @@ public class TorControlConnection implements TorControlCommands {
* <p/> * <p/>
* ADD_ONION was added in Tor 0.2.7.1-alpha. * ADD_ONION was added in Tor 0.2.7.1-alpha.
*/ */
public Map<String,String> addOnion(Map<Integer,String> portLines, public Map<String, String> addOnion(Map<Integer, String> portLines,
boolean ephemeral, boolean detach) boolean ephemeral, boolean detach)
throws IOException { throws IOException {
return addOnion("NEW:BEST", portLines, ephemeral, detach); return addOnion("NEW:BEST", portLines, ephemeral, detach);
@@ -780,8 +914,8 @@ public class TorControlConnection implements TorControlCommands {
* <p/> * <p/>
* ADD_ONION was added in Tor 0.2.7.1-alpha. * ADD_ONION was added in Tor 0.2.7.1-alpha.
*/ */
public Map<String,String> addOnion(String privKey, public Map<String, String> addOnion(String privKey,
Map<Integer,String> portLines) Map<Integer, String> portLines)
throws IOException { throws IOException {
return addOnion(privKey, portLines, null); return addOnion(privKey, portLines, null);
} }
@@ -791,11 +925,11 @@ public class TorControlConnection implements TorControlCommands {
* <p/> * <p/>
* ADD_ONION was added in Tor 0.2.7.1-alpha. * ADD_ONION was added in Tor 0.2.7.1-alpha.
*/ */
public Map<String,String> addOnion(String privKey, public Map<String, String> addOnion(String privKey,
Map<Integer,String> portLines, Map<Integer, String> portLines,
boolean ephemeral, boolean detach) boolean ephemeral, boolean detach)
throws IOException { throws IOException {
List<String> flags = new ArrayList<String>(); List<String> flags = new ArrayList<>();
if (ephemeral) if (ephemeral)
flags.add("DiscardPK"); flags.add("DiscardPK");
if (detach) if (detach)
@@ -808,14 +942,15 @@ public class TorControlConnection implements TorControlCommands {
* <p/> * <p/>
* ADD_ONION was added in Tor 0.2.7.1-alpha. * ADD_ONION was added in Tor 0.2.7.1-alpha.
*/ */
public Map<String,String> addOnion(String privKey, public Map<String, String> addOnion(String privKey,
Map<Integer,String> portLines, Map<Integer, String> portLines,
List<String> flags) List<String> flags)
throws IOException { throws IOException {
if (privKey.indexOf(':') < 0) if (privKey.indexOf(':') < 0)
throw new IllegalArgumentException("Invalid privKey"); throw new IllegalArgumentException("Invalid privKey");
if (portLines == null || portLines.size() < 1) if (portLines == null || portLines.size() < 1)
throw new IllegalArgumentException("Must provide at least one port line"); throw new IllegalArgumentException(
"Must provide at least one port line");
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append("ADD_ONION ").append(privKey); b.append("ADD_ONION ").append(privKey);
if (flags != null && flags.size() > 0) { if (flags != null && flags.size() > 0) {
@@ -826,7 +961,7 @@ public class TorControlConnection implements TorControlCommands {
separator = ","; separator = ",";
} }
} }
for (Map.Entry<Integer,String> portLine : portLines.entrySet()) { for (Map.Entry<Integer, String> portLine : portLines.entrySet()) {
int virtPort = portLine.getKey(); int virtPort = portLine.getKey();
String target = portLine.getValue(); String target = portLine.getValue();
b.append(" Port=").append(virtPort); b.append(" Port=").append(virtPort);
@@ -835,7 +970,7 @@ public class TorControlConnection implements TorControlCommands {
} }
b.append("\r\n"); b.append("\r\n");
List<ReplyLine> lst = sendAndWaitForResponse(b.toString(), null); List<ReplyLine> lst = sendAndWaitForResponse(b.toString(), null);
Map<String,String> ret = new HashMap<String,String>(); Map<String, String> ret = new HashMap<>();
ret.put(HS_ADDRESS, (lst.get(0)).msg.split("=", 2)[1]); ret.put(HS_ADDRESS, (lst.get(0)).msg.split("=", 2)[1]);
if (lst.size() > 2) if (lst.size() > 2)
ret.put(HS_PRIVKEY, (lst.get(1)).msg.split("=", 2)[1]); ret.put(HS_PRIVKEY, (lst.get(1)).msg.split("=", 2)[1]);
@@ -852,7 +987,8 @@ public class TorControlConnection implements TorControlCommands {
sendAndWaitForResponse("DEL_ONION " + hostname + "\r\n", null); sendAndWaitForResponse("DEL_ONION " + hostname + "\r\n", null);
} }
/** Tells Tor to forget any cached client state relating to the hidden /**
* Tells Tor to forget any cached client state relating to the hidden
* service with the given hostname (excluding the .onion extension). * service with the given hostname (excluding the .onion extension).
*/ */
public void forgetHiddenService(String hostname) throws IOException { public void forgetHiddenService(String hostname) throws IOException {