mirror of
https://github.com/torproject/jtorctl.git
synced 2024-10-06 23:33:23 +00:00
r14101@catbus: nickm | 2007-08-08 16:43:33 -0400
From Karsten: drop TorControlConnection0.java, and merge TorControlConnection1 into TorControlConnection.
This commit is contained in:
parent
5e6fdc99c2
commit
da57f8432e
@ -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 <b>w</b> as the PrintWriter for debugging output,
|
||||
* which writes out all messages passed between Tor and the controller.
|
||||
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded
|
||||
* by "\<\<"
|
||||
*/
|
||||
public void setDebugging(java.io.PrintWriter w) {
|
||||
if (w instanceof java.io.PrintWriter)
|
||||
debugOutput = (java.io.PrintWriter) w;
|
||||
else
|
||||
debugOutput = new java.io.PrintWriter(w, true);
|
||||
}
|
||||
|
||||
/** Sets <b>s</b> as the PrintStream for debugging output,
|
||||
* which writes out all messages passed between Tor and the controller.
|
||||
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded
|
||||
* by "\<\<"
|
||||
*/
|
||||
public void setDebugging(java.io.PrintStream s) {
|
||||
debugOutput = new java.io.PrintWriter(s, true);
|
||||
}
|
||||
|
||||
/** Set the EventHandler object that will be notified of any
|
||||
* events Tor delivers to this connection. To make Tor send us
|
||||
* events, call setEvents(). */
|
||||
@ -124,7 +307,25 @@ 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
|
||||
* <b>kvList</b>. Each list element in <b>kvList</b> is expected to be
|
||||
* String of the format "key value".
|
||||
*
|
||||
* Tor behaves as though it had just read each of the key-value pairs
|
||||
* from its configuration file. Keywords with no corresponding values have
|
||||
* their configuration values reset to their defaults. setConf is
|
||||
* all-or-nothing: if there is an error in any of the configuration settings,
|
||||
* Tor sets none of them.
|
||||
*
|
||||
* When a configuration option takes multiple values, or when multiple
|
||||
* configuration keys form a context-sensitive group (see getConf below), then
|
||||
* setting any of the options in a setConf command is taken to reset all of
|
||||
* the others. For example, if two ORBindAddress values are configured, and a
|
||||
* command arrives containing a single ORBindAddress value, the new
|
||||
* command's value replaces the two old values.
|
||||
*
|
||||
* To remove all settings for a given option entirely (and go back to its
|
||||
* default value), include a String in <b>kvList</b> containing the key and no value.
|
||||
*/
|
||||
public void setConf(Collection kvList) throws IOException {
|
||||
if (kvList.size() == 0)
|
||||
return;
|
||||
StringBuffer b = new StringBuffer("SETCONF");
|
||||
for (Iterator it = kvList.iterator(); it.hasNext(); ) {
|
||||
String kv = (String) it.next();
|
||||
int i = kv.indexOf(' ');
|
||||
if (i == -1)
|
||||
b.append(" ").append(kv);
|
||||
b.append(" ").append(kv.substring(0,i)).append("=")
|
||||
.append(quote(kv.substring(i+1)));
|
||||
}
|
||||
b.append("\r\n");
|
||||
sendAndWaitForResponse(b.toString(), null);
|
||||
}
|
||||
|
||||
/** Try to reset the values listed in the collection 'keys' to their
|
||||
* default values.
|
||||
**/
|
||||
public abstract void resetConf(Collection keys) throws IOException;
|
||||
public void resetConf(Collection keys) throws IOException {
|
||||
if (keys.size() == 0)
|
||||
return;
|
||||
StringBuffer b = new StringBuffer("RESETCONF");
|
||||
for (Iterator it = keys.iterator(); it.hasNext(); ) {
|
||||
String key = (String) it.next();
|
||||
b.append(" ").append(key);
|
||||
}
|
||||
b.append("\r\n");
|
||||
sendAndWaitForResponse(b.toString(), null);
|
||||
}
|
||||
|
||||
/** Return the value of the configuration option 'key' */
|
||||
public List getConf(String key) throws IOException {
|
||||
@ -160,30 +403,169 @@ 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 <b>keys</b>.
|
||||
* Results are returned as a list of ConfigEntry objects.
|
||||
*
|
||||
* If an option appears multiple times in the configuration, all of its
|
||||
* key-value pairs are returned in order.
|
||||
*
|
||||
* Some options are context-sensitive, and depend on other options with
|
||||
* different keywords. These cannot be fetched directly. Currently there
|
||||
* is only one such option: clients should use the "HiddenServiceOptions"
|
||||
* virtual keyword to get all HiddenServiceDir, HiddenServicePort,
|
||||
* HiddenServiceNodes, and HiddenServiceExcludeNodes option settings.
|
||||
*/
|
||||
public List getConf(Collection keys) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("GETCONF");
|
||||
for (Iterator it = keys.iterator(); it.hasNext(); ) {
|
||||
String key = (String) it.next();
|
||||
sb.append(" ").append(key);
|
||||
}
|
||||
sb.append("\r\n");
|
||||
ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
ArrayList result = new ArrayList();
|
||||
for (Iterator it = lst.iterator(); it.hasNext(); ) {
|
||||
String kv = ((ReplyLine) it.next()).msg;
|
||||
int idx = kv.indexOf('=');
|
||||
if (idx >= 0)
|
||||
result.add(new ConfigEntry(kv.substring(0, idx),
|
||||
kv.substring(idx+1)));
|
||||
else
|
||||
result.add(new ConfigEntry(kv));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Tell Tor to begin sending us events of the types listed in 'events'.
|
||||
* Elements must be one of the EVENT_* values from TorControlCommands */
|
||||
public abstract void setEvents(List events) throws IOException;
|
||||
/** Request that the server inform the client about interesting events.
|
||||
* Each element of <b>events</b> is one of the following Strings:
|
||||
* ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" |
|
||||
* "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] .
|
||||
*
|
||||
* Any events not listed in the <b>events</b> are turned off; thus, calling
|
||||
* setEvents with an empty <b>events</b> argument turns off all event reporting.
|
||||
*/
|
||||
public void setEvents(List events) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("SETEVENTS");
|
||||
for (Iterator it = events.iterator(); it.hasNext(); ) {
|
||||
Object event = it.next();
|
||||
if (event instanceof String) {
|
||||
sb.append(" ").append((String)event);
|
||||
} else {
|
||||
int i = ((Number) event).intValue();
|
||||
sb.append(" ").append(EVENT_NAMES[i]);
|
||||
}
|
||||
}
|
||||
sb.append("\r\n");
|
||||
sendAndWaitForResponse(sb.toString(), null);
|
||||
}
|
||||
|
||||
/** Send Tor an authentication sequence 'auth' */
|
||||
// XXXX more info about how to set this up securely.
|
||||
public abstract void authenticate(byte[] auth) throws IOException;
|
||||
/** Authenticates the controller to the Tor server.
|
||||
*
|
||||
* By default, the current Tor implementation trusts all local users, and
|
||||
* the controller can authenticate itself by calling authenticate(new byte[0]).
|
||||
*
|
||||
* If the 'CookieAuthentication' option is true, Tor writes a "magic cookie"
|
||||
* file named "control_auth_cookie" into its data directory. To authenticate,
|
||||
* the controller must send the contents of this file in <b>auth</b>.
|
||||
*
|
||||
* If the 'HashedControlPassword' option is set, <b>auth</b> must contain the salted
|
||||
* hash of a secret password. The salted hash is computed according to the
|
||||
* S2K algorithm in RFC 2440 (OpenPGP), and prefixed with the s2k specifier.
|
||||
* This is then encoded in hexadecimal, prefixed by the indicator sequence
|
||||
* "16:".
|
||||
*
|
||||
* You can generate the salt of a password by calling
|
||||
* 'tor --hash-password <password>'
|
||||
* or by using the provided PasswordDigest class.
|
||||
* To authenticate under this scheme, the controller sends Tor the original
|
||||
* secret that was used to generate the password.
|
||||
*/
|
||||
public void authenticate(byte[] auth) throws IOException {
|
||||
String cmd = "AUTHENTICATE " + Bytes.hex(auth) + "\r\n";
|
||||
sendAndWaitForResponse(cmd, null);
|
||||
}
|
||||
|
||||
/** Tell Tor to save the value of its configuration to disk. */
|
||||
public abstract void saveConf() throws IOException;
|
||||
/** Instructs the server to write out its configuration options into its torrc.
|
||||
*/
|
||||
public void saveConf() throws IOException {
|
||||
sendAndWaitForResponse("SAVECONF\r\n", null);
|
||||
}
|
||||
|
||||
/** Send a signal to the Tor process. */
|
||||
public abstract void signal(String signal) throws IOException;
|
||||
/** Sends a signal from the controller to the Tor server.
|
||||
* <b>signal</b> is one of the following Strings:
|
||||
* <ul>
|
||||
* <li>"RELOAD" or "HUP" : Reload config items, refetch directory</li>
|
||||
* <li>"SHUTDOWN" or "INT" : Controlled shutdown: if server is an OP, exit immediately.
|
||||
* If it's an OR, close listeners and exit after 30 seconds</li>
|
||||
* <li>"DUMP" or "USR1" : Dump stats: log information about open connections and circuits</li>
|
||||
* <li>"DEBUG" or "USR2" : Debug: switch all open logs to loglevel debug</li>
|
||||
* <li>"HALT" or "TERM" : Immediate shutdown: clean up and exit now</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void signal(String signal) throws IOException {
|
||||
String cmd = "SIGNAL " + signal + "\r\n";
|
||||
sendAndWaitForResponse(cmd, null);
|
||||
}
|
||||
|
||||
/** Send a signal to the Tor process to shut it down or halt it.
|
||||
* Does not wait for a response. */
|
||||
public abstract void shutdownTor(String signal) throws IOException;
|
||||
public void shutdownTor(String signal) throws IOException {
|
||||
String s = "SIGNAL " + signal + "\r\n";
|
||||
Waiter w = new Waiter();
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> "+s);
|
||||
if (this.thread != null) {
|
||||
this.thread.stopListening();
|
||||
}
|
||||
synchronized (waiters) {
|
||||
output.write(s);
|
||||
output.flush();
|
||||
waiters.addLast(w); // Prevent react() from finding the list empty
|
||||
}
|
||||
}
|
||||
|
||||
/** Tell Tor to replace incoming addresses with those as listed in 'kvLines'.
|
||||
/** Tells the Tor server that future SOCKS requests for connections to a set of original
|
||||
* addresses should be replaced with connections to the specified replacement
|
||||
* addresses. Each element of <b>kvLines</b> is a String of the form
|
||||
* "old-address new-address". This function returns the new address mapping.
|
||||
*
|
||||
* The client may decline to provide a body for the original address, and
|
||||
* instead send a special null address ("0.0.0.0" for IPv4, "::0" for IPv6, or
|
||||
* "." for hostname), signifying that the server should choose the original
|
||||
* address itself, and return that address in the reply. The server
|
||||
* should ensure that it returns an element of address space that is unlikely
|
||||
* to be in actual use. If there is already an address mapped to the
|
||||
* destination address, the server may reuse that mapping.
|
||||
*
|
||||
* If the original address is already mapped to a different address, the old
|
||||
* mapping is removed. If the original address and the destination address
|
||||
* are the same, the server removes any mapping in place for the original
|
||||
* address.
|
||||
*
|
||||
* Mappings set by the controller last until the Tor process exits:
|
||||
* they never expire. If the controller wants the mapping to last only
|
||||
* a certain time, then it must explicitly un-map the address when that
|
||||
* time has elapsed.
|
||||
*/
|
||||
public abstract Map mapAddresses(Collection kvLines) throws IOException;
|
||||
public Map mapAddresses(Collection kvLines) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("MAPADDRESS");
|
||||
for (Iterator it = kvLines.iterator(); it.hasNext(); ) {
|
||||
String kv = (String) it.next();
|
||||
int i = kv.indexOf(' ');
|
||||
sb.append(" ").append(kv.substring(0,i)).append("=")
|
||||
.append(quote(kv.substring(i+1)));
|
||||
}
|
||||
sb.append("\r\n");
|
||||
ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
Map result = new HashMap();
|
||||
for (Iterator it = lst.iterator(); it.hasNext(); ) {
|
||||
String kv = ((ReplyLine) it.next()).msg;
|
||||
int idx = kv.indexOf('=');
|
||||
result.put(kv.substring(0, idx),
|
||||
kv.substring(idx+1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map mapAddresses(Map addresses) throws IOException {
|
||||
List kvList = new ArrayList();
|
||||
@ -201,9 +583,63 @@ 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:
|
||||
* <ul>
|
||||
* <li>"version" : The version of the server's software, including the name
|
||||
* of the software. (example: "Tor 0.0.9.4")</li>
|
||||
* <li>"desc/id/<OR identity>" or "desc/name/<OR nickname>" : the latest server
|
||||
* descriptor for a given OR, NUL-terminated. If no such OR is known, the
|
||||
* corresponding value is an empty string.</li>
|
||||
* <li>"network-status" : a space-separated list of all known OR identities.
|
||||
* This is in the same format as the router-status line in directories;
|
||||
* see tor-spec.txt for details.</li>
|
||||
* <li>"addr-mappings/all"</li>
|
||||
* <li>"addr-mappings/config"</li>
|
||||
* <li>"addr-mappings/cache"</li>
|
||||
* <li>"addr-mappings/control" : a space-separated list of address mappings, each
|
||||
* in the form of "from-address=to-address". The 'config' key
|
||||
* returns those address mappings set in the configuration; the 'cache'
|
||||
* key returns the mappings in the client-side DNS cache; the 'control'
|
||||
* key returns the mappings set via the control interface; the 'all'
|
||||
* target returns the mappings set through any mechanism.</li>
|
||||
* <li>"circuit-status" : A series of lines as for a circuit status event. Each line is of the form:
|
||||
* "CircuitID CircStatus Path"</li>
|
||||
* <li>"stream-status" : A series of lines as for a stream status event. Each is of the form:
|
||||
* "StreamID StreamStatus CircID Target"</li>
|
||||
* <li>"orconn-status" : A series of lines as for an OR connection status event. Each is of the
|
||||
* form: "ServerID ORStatus"</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Map getInfo(Collection keys) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("GETINFO");
|
||||
for (Iterator it = keys.iterator(); it.hasNext(); ) {
|
||||
sb.append(" ").append((String)it.next());
|
||||
}
|
||||
sb.append("\r\n");
|
||||
ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
Map m = new HashMap();
|
||||
for (Iterator it = lst.iterator(); it.hasNext(); ) {
|
||||
ReplyLine line = (ReplyLine) it.next();
|
||||
int idx = line.msg.indexOf('=');
|
||||
if (idx<0)
|
||||
break;
|
||||
String k = line.msg.substring(0,idx);
|
||||
Object v;
|
||||
if (line.rest != null) {
|
||||
v = line.rest;
|
||||
} else {
|
||||
v = line.msg.substring(idx+1);
|
||||
}
|
||||
m.put(k, v);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Return the value of the information field 'key' */
|
||||
public String getInfo(String key) throws IOException {
|
||||
List lst = new ArrayList();
|
||||
@ -212,34 +648,98 @@ 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 <b>circID</b> is zero, in
|
||||
* which case it is a request for the server to build a new circuit according
|
||||
* to the specified path, or the <b>circID</b> is nonzero, in which case it is a
|
||||
* request for the server to extend an existing circuit with that ID according
|
||||
* to the specified <b>path</b>.
|
||||
*
|
||||
* If successful, returns the Circuit ID of the (maybe newly created) circuit.
|
||||
*/
|
||||
public abstract String extendCircuit(String circID, String path) throws IOException;
|
||||
|
||||
/**
|
||||
* 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 <b>streamID</b> should be
|
||||
* associated with the circuit specified by <b>circID</b>.
|
||||
*
|
||||
* Each stream may be associated with
|
||||
* at most one circuit, and multiple streams may share the same circuit.
|
||||
* Streams can only be attached to completed circuits (that is, circuits that
|
||||
* have sent a circuit status "BUILT" event or are listed as built in a
|
||||
* getInfo circuit-status request).
|
||||
*
|
||||
* If <b>circID</b> is 0, responsibility for attaching the given stream is
|
||||
* returned to Tor.
|
||||
*
|
||||
* By default, Tor automatically attaches streams to
|
||||
* circuits itself, unless the configuration variable
|
||||
* "__LeaveStreamsUnattached" is set to "1". Attempting to attach streams
|
||||
* via TC when "__LeaveStreamsUnattached" is false may cause a race between
|
||||
* Tor and the controller, as both attempt to attach streams to circuits.
|
||||
*/
|
||||
public 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 <b>desc</b>.
|
||||
*
|
||||
* The descriptor, when parsed, must contain a number of well-specified
|
||||
* fields, including fields for its nickname and identity.
|
||||
*/
|
||||
public abstract void 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 <b>streamID</b>
|
||||
* to <b>address</b>. No remapping is performed on the new provided address.
|
||||
*
|
||||
* To be sure that the modified address will be used, this event must be sent
|
||||
* after a new stream event is received, and before attaching this stream to
|
||||
* a circuit.
|
||||
*/
|
||||
public abstract void 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 <b>streamID</b>.
|
||||
* <b>reason</b> should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal:
|
||||
* <ul>
|
||||
* <li>1 -- REASON_MISC (catch-all for unlisted reasons)</li>
|
||||
* <li>2 -- REASON_RESOLVEFAILED (couldn't look up hostname)</li>
|
||||
* <li>3 -- REASON_CONNECTREFUSED (remote host refused connection)</li>
|
||||
* <li>4 -- REASON_EXITPOLICY (OR refuses to connect to host or port)</li>
|
||||
* <li>5 -- REASON_DESTROY (Circuit is being destroyed)</li>
|
||||
* <li>6 -- REASON_DONE (Anonymized TCP connection was closed)</li>
|
||||
* <li>7 -- REASON_TIMEOUT (Connection timed out, or OR timed out while connecting)</li>
|
||||
* <li>8 -- (unallocated)</li>
|
||||
* <li>9 -- REASON_HIBERNATING (OR is temporarily hibernating)</li>
|
||||
* <li>10 -- REASON_INTERNAL (Internal error at the OR)</li>
|
||||
* <li>11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)</li>
|
||||
* <li>12 -- REASON_CONNRESET (Connection was unexpectedly reset)</li>
|
||||
* <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)</li>
|
||||
* </ul>
|
||||
*
|
||||
* Tor may hold the stream open for a while to flush any data that is pending.
|
||||
*/
|
||||
public abstract void 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 <b>circID</b>.
|
||||
* If <b>ifUnused</b> is true, do not close the circuit unless it is unused.
|
||||
*/
|
||||
public void closeCircuit(String circID, boolean ifUnused) throws IOException {
|
||||
sendAndWaitForResponse("CLOSECIRCUIT "+circID+
|
||||
(ifUnused?" IFUNUSED":"")+"\r\n", null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,616 +0,0 @@
|
||||
// $Id$
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/** Extends the TorControlConnection class to implement Version 1
|
||||
* of the TC protocol, as specified in control-spec.txt.
|
||||
*/
|
||||
public class TorControlConnection1 extends TorControlConnection
|
||||
implements TorControlCommands
|
||||
{
|
||||
protected java.io.BufferedReader input;
|
||||
protected java.io.Writer output;
|
||||
protected java.io.PrintWriter debugOutput;
|
||||
|
||||
static class ReplyLine {
|
||||
public String status;
|
||||
public String msg;
|
||||
public String rest;
|
||||
|
||||
ReplyLine(String status, String msg, String rest) {
|
||||
this.status = status; this.msg = msg; this.rest = rest;
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a new TorControlConnection to communicate with Tor over
|
||||
* a given socket. After calling this constructor, it is typical to
|
||||
* call launchThread and authenticate. */
|
||||
public TorControlConnection1(java.net.Socket connection)
|
||||
throws IOException {
|
||||
this(connection.getInputStream(), connection.getOutputStream());
|
||||
}
|
||||
|
||||
/** Create a new TorControlConnection to communicate with Tor over
|
||||
* an arbitrary pair of data streams.
|
||||
*/
|
||||
public TorControlConnection1(java.io.InputStream i, java.io.OutputStream o)
|
||||
throws IOException {
|
||||
this(new java.io.InputStreamReader(i),
|
||||
new java.io.OutputStreamWriter(o));
|
||||
}
|
||||
|
||||
public TorControlConnection1(java.io.Reader i, java.io.Writer o)
|
||||
throws IOException {
|
||||
this.output = o;
|
||||
if (i instanceof java.io.BufferedReader)
|
||||
this.input = (java.io.BufferedReader) i;
|
||||
else
|
||||
this.input = new java.io.BufferedReader(i);
|
||||
|
||||
this.waiters = new LinkedList();
|
||||
}
|
||||
|
||||
protected final void writeEscaped(String s) throws IOException {
|
||||
StringTokenizer st = new StringTokenizer(s, "\n");
|
||||
while (st.hasMoreTokens()) {
|
||||
String line = st.nextToken();
|
||||
if (line.startsWith("."))
|
||||
line = "."+line;
|
||||
if (line.endsWith("\r"))
|
||||
line += "\n";
|
||||
else
|
||||
line += "\r\n";
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> "+line);
|
||||
output.write(line);
|
||||
}
|
||||
output.write(".\r\n");
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> .\n");
|
||||
}
|
||||
|
||||
protected static final String quote(String s) {
|
||||
StringBuffer sb = new StringBuffer("\"");
|
||||
for (int i = 0; i < s.length(); ++i) {
|
||||
char c = s.charAt(i);
|
||||
switch (c)
|
||||
{
|
||||
case '\r':
|
||||
case '\n':
|
||||
case '\\':
|
||||
case '\"':
|
||||
sb.append('\\');
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
sb.append('\"');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected final ArrayList readReply() throws IOException {
|
||||
ArrayList reply = new ArrayList();
|
||||
char c;
|
||||
do {
|
||||
String line = input.readLine();
|
||||
if (line == null) {
|
||||
// if line is null, the end of the stream has been reached, i.e.
|
||||
// the connection to Tor has been closed!
|
||||
if (reply.isEmpty()) {
|
||||
// nothing received so far, can exit cleanly
|
||||
return reply;
|
||||
} else {
|
||||
// received half of a reply before the connection broke down
|
||||
throw new TorControlSyntaxError("Connection to Tor " +
|
||||
" broke down while receiving reply!");
|
||||
}
|
||||
}
|
||||
if (debugOutput != null)
|
||||
debugOutput.println("<< "+line);
|
||||
if (line.length() < 4)
|
||||
throw new TorControlSyntaxError("Line (\""+line+"\") too short");
|
||||
String status = line.substring(0,3);
|
||||
c = line.charAt(3);
|
||||
String msg = line.substring(4);
|
||||
String rest = null;
|
||||
if (c == '+') {
|
||||
StringBuffer data = new StringBuffer();
|
||||
while (true) {
|
||||
line = input.readLine();
|
||||
if (debugOutput != null)
|
||||
debugOutput.print("<< "+line);
|
||||
if (line.equals("."))
|
||||
break;
|
||||
else if (line.startsWith("."))
|
||||
line = line.substring(1);
|
||||
data.append(line).append('\n');
|
||||
}
|
||||
rest = data.toString();
|
||||
}
|
||||
reply.add(new ReplyLine(status, msg, rest));
|
||||
} while (c != ' ');
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
/** helper: implement the main background loop. */
|
||||
protected void react() throws IOException {
|
||||
while (true) {
|
||||
ArrayList lst = readReply();
|
||||
if (lst.isEmpty()) {
|
||||
// connection has been closed remotely! end the loop!
|
||||
return;
|
||||
}
|
||||
if (((ReplyLine)lst.get(0)).status.startsWith("6"))
|
||||
handleEvent(lst);
|
||||
else {
|
||||
Waiter w;
|
||||
synchronized (waiters) {
|
||||
w = (Waiter) waiters.removeFirst();
|
||||
}
|
||||
w.setResponse(lst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized ArrayList sendAndWaitForResponse(String s,String rest)
|
||||
throws IOException {
|
||||
checkThread();
|
||||
Waiter w = new Waiter();
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> "+s);
|
||||
synchronized (waiters) {
|
||||
output.write(s);
|
||||
output.flush();
|
||||
if (rest != null)
|
||||
writeEscaped(rest);
|
||||
waiters.addLast(w);
|
||||
}
|
||||
ArrayList lst = (ArrayList) w.getResponse();
|
||||
for (Iterator i = lst.iterator(); i.hasNext(); ) {
|
||||
ReplyLine c = (ReplyLine) i.next();
|
||||
if (! c.status.startsWith("2"))
|
||||
throw new TorControlError("Error reply: "+c.msg);
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
/** Helper: decode a CMD_EVENT command and dispatch it to our
|
||||
* EventHandler (if any). */
|
||||
protected void handleEvent(ArrayList events) {
|
||||
if (handler == null)
|
||||
return;
|
||||
|
||||
for (Iterator i = events.iterator(); i.hasNext(); ) {
|
||||
ReplyLine line = (ReplyLine) i.next();
|
||||
int idx = line.msg.indexOf(' ');
|
||||
String tp = line.msg.substring(0, idx).toUpperCase();
|
||||
String rest = line.msg.substring(idx+1);
|
||||
if (tp.equals("CIRC")) {
|
||||
List lst = Bytes.splitStr(null, rest);
|
||||
handler.circuitStatus((String)lst.get(1),
|
||||
(String)lst.get(0),
|
||||
(String)lst.get(2));
|
||||
} else if (tp.equals("STREAM")) {
|
||||
List lst = Bytes.splitStr(null, rest);
|
||||
handler.streamStatus((String)lst.get(1),
|
||||
(String)lst.get(0),
|
||||
(String)lst.get(3));
|
||||
// XXXX circID.
|
||||
} else if (tp.equals("ORCONN")) {
|
||||
List lst = Bytes.splitStr(null, rest);
|
||||
handler.orConnStatus((String)lst.get(1), (String)lst.get(0));
|
||||
} else if (tp.equals("BW")) {
|
||||
List lst = Bytes.splitStr(null, rest);
|
||||
handler.bandwidthUsed(Integer.parseInt((String)lst.get(0)),
|
||||
Integer.parseInt((String)lst.get(1)));
|
||||
} else if (tp.equals("NEWDESC")) {
|
||||
List lst = Bytes.splitStr(null, rest);
|
||||
handler.newDescriptors(lst);
|
||||
} else if (tp.equals("DEBUG") ||
|
||||
tp.equals("INFO") ||
|
||||
tp.equals("NOTICE") ||
|
||||
tp.equals("WARN") ||
|
||||
tp.equals("ERR")) {
|
||||
handler.message(tp, rest);
|
||||
} else {
|
||||
handler.unrecognized(tp, rest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes the values of the configuration options stored in
|
||||
* <b>kvList</b>. Each list element in <b>kvList</b> is expected to be
|
||||
* String of the format "key value".
|
||||
*
|
||||
* Tor behaves as though it had just read each of the key-value pairs
|
||||
* from its configuration file. Keywords with no corresponding values have
|
||||
* their configuration values reset to their defaults. setConf is
|
||||
* all-or-nothing: if there is an error in any of the configuration settings,
|
||||
* Tor sets none of them.
|
||||
*
|
||||
* When a configuration option takes multiple values, or when multiple
|
||||
* configuration keys form a context-sensitive group (see getConf below), then
|
||||
* setting any of the options in a setConf command is taken to reset all of
|
||||
* the others. For example, if two ORBindAddress values are configured, and a
|
||||
* command arrives containing a single ORBindAddress value, the new
|
||||
* command's value replaces the two old values.
|
||||
*
|
||||
* To remove all settings for a given option entirely (and go back to its
|
||||
* default value), include a String in <b>kvList</b> containing the key and no value.
|
||||
*/
|
||||
public void setConf(Collection kvList) throws IOException {
|
||||
if (kvList.size() == 0)
|
||||
return;
|
||||
StringBuffer b = new StringBuffer("SETCONF");
|
||||
for (Iterator it = kvList.iterator(); it.hasNext(); ) {
|
||||
String kv = (String) it.next();
|
||||
int i = kv.indexOf(' ');
|
||||
if (i == -1)
|
||||
b.append(" ").append(kv);
|
||||
b.append(" ").append(kv.substring(0,i)).append("=")
|
||||
.append(quote(kv.substring(i+1)));
|
||||
}
|
||||
b.append("\r\n");
|
||||
sendAndWaitForResponse(b.toString(), null);
|
||||
}
|
||||
|
||||
public void resetConf(Collection keys) throws IOException {
|
||||
if (keys.size() == 0)
|
||||
return;
|
||||
StringBuffer b = new StringBuffer("RESETCONF");
|
||||
for (Iterator it = keys.iterator(); it.hasNext(); ) {
|
||||
String key = (String) it.next();
|
||||
b.append(" ").append(key);
|
||||
}
|
||||
b.append("\r\n");
|
||||
sendAndWaitForResponse(b.toString(), null);
|
||||
}
|
||||
|
||||
/** Sets <b>w</b> as the PrintWriter for debugging output,
|
||||
* which writes out all messages passed between Tor and the controller.
|
||||
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded
|
||||
* by "\<\<"
|
||||
*/
|
||||
public void setDebugging(java.io.PrintWriter w) {
|
||||
if (w instanceof java.io.PrintWriter)
|
||||
debugOutput = (java.io.PrintWriter) w;
|
||||
else
|
||||
debugOutput = new java.io.PrintWriter(w, true);
|
||||
}
|
||||
|
||||
/** Sets <b>s</b> as the PrintStream for debugging output,
|
||||
* which writes out all messages passed between Tor and the controller.
|
||||
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded
|
||||
* by "\<\<"
|
||||
*/
|
||||
public void setDebugging(java.io.PrintStream s) {
|
||||
debugOutput = new java.io.PrintWriter(s, true);
|
||||
}
|
||||
|
||||
/** Requests the values of the configuration variables listed in <b>keys</b>.
|
||||
* Results are returned as a list of ConfigEntry objects.
|
||||
*
|
||||
* If an option appears multiple times in the configuration, all of its
|
||||
* key-value pairs are returned in order.
|
||||
*
|
||||
* Some options are context-sensitive, and depend on other options with
|
||||
* different keywords. These cannot be fetched directly. Currently there
|
||||
* is only one such option: clients should use the "HiddenServiceOptions"
|
||||
* virtual keyword to get all HiddenServiceDir, HiddenServicePort,
|
||||
* HiddenServiceNodes, and HiddenServiceExcludeNodes option settings.
|
||||
*/
|
||||
public List getConf(Collection keys) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("GETCONF");
|
||||
for (Iterator it = keys.iterator(); it.hasNext(); ) {
|
||||
String key = (String) it.next();
|
||||
sb.append(" ").append(key);
|
||||
}
|
||||
sb.append("\r\n");
|
||||
ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
ArrayList result = new ArrayList();
|
||||
for (Iterator it = lst.iterator(); it.hasNext(); ) {
|
||||
String kv = ((ReplyLine) it.next()).msg;
|
||||
int idx = kv.indexOf('=');
|
||||
if (idx >= 0)
|
||||
result.add(new ConfigEntry(kv.substring(0, idx),
|
||||
kv.substring(idx+1)));
|
||||
else
|
||||
result.add(new ConfigEntry(kv));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Request that the server inform the client about interesting events.
|
||||
* Each element of <b>events</b> is one of the following Strings:
|
||||
* ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" |
|
||||
* "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] .
|
||||
*
|
||||
* Any events not listed in the <b>events</b> are turned off; thus, calling
|
||||
* setEvents with an empty <b>events</b> argument turns off all event reporting.
|
||||
*/
|
||||
public void setEvents(List events) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("SETEVENTS");
|
||||
for (Iterator it = events.iterator(); it.hasNext(); ) {
|
||||
Object event = it.next();
|
||||
if (event instanceof String) {
|
||||
sb.append(" ").append((String)event);
|
||||
} else {
|
||||
int i = ((Number) event).intValue();
|
||||
sb.append(" ").append(EVENT_NAMES[i]);
|
||||
}
|
||||
}
|
||||
sb.append("\r\n");
|
||||
sendAndWaitForResponse(sb.toString(), null);
|
||||
}
|
||||
|
||||
|
||||
/** Authenticates the controller to the Tor server.
|
||||
*
|
||||
* By default, the current Tor implementation trusts all local users, and
|
||||
* the controller can authenticate itself by calling authenticate(new byte[0]).
|
||||
*
|
||||
* If the 'CookieAuthentication' option is true, Tor writes a "magic cookie"
|
||||
* file named "control_auth_cookie" into its data directory. To authenticate,
|
||||
* the controller must send the contents of this file in <b>auth</b>.
|
||||
*
|
||||
* If the 'HashedControlPassword' option is set, <b>auth</b> must contain the salted
|
||||
* hash of a secret password. The salted hash is computed according to the
|
||||
* S2K algorithm in RFC 2440 (OpenPGP), and prefixed with the s2k specifier.
|
||||
* This is then encoded in hexadecimal, prefixed by the indicator sequence
|
||||
* "16:".
|
||||
*
|
||||
* You can generate the salt of a password by calling
|
||||
* 'tor --hash-password <password>'
|
||||
* or by using the provided PasswordDigest class.
|
||||
* To authenticate under this scheme, the controller sends Tor the original
|
||||
* secret that was used to generate the password.
|
||||
*/
|
||||
public void authenticate(byte[] auth) throws IOException {
|
||||
String cmd = "AUTHENTICATE " + Bytes.hex(auth) + "\r\n";
|
||||
sendAndWaitForResponse(cmd, null);
|
||||
}
|
||||
|
||||
/** Instructs the server to write out its configuration options into its torrc.
|
||||
*/
|
||||
public void saveConf() throws IOException {
|
||||
sendAndWaitForResponse("SAVECONF\r\n", null);
|
||||
}
|
||||
|
||||
/** Sends a signal from the controller to the Tor server.
|
||||
* <b>signal</b> is one of the following Strings:
|
||||
* <ul>
|
||||
* <li>"RELOAD" or "HUP" : Reload config items, refetch directory</li>
|
||||
* <li>"SHUTDOWN" or "INT" : Controlled shutdown: if server is an OP, exit immediately.
|
||||
* If it's an OR, close listeners and exit after 30 seconds</li>
|
||||
* <li>"DUMP" or "USR1" : Dump stats: log information about open connections and circuits</li>
|
||||
* <li>"DEBUG" or "USR2" : Debug: switch all open logs to loglevel debug</li>
|
||||
* <li>"HALT" or "TERM" : Immediate shutdown: clean up and exit now</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void signal(String signal) throws IOException {
|
||||
String cmd = "SIGNAL " + signal + "\r\n";
|
||||
sendAndWaitForResponse(cmd, null);
|
||||
}
|
||||
|
||||
/** Send a signal to the Tor process to shut it down or halt it.
|
||||
* Does not wait for a response. */
|
||||
public void shutdownTor(String signal) throws IOException {
|
||||
String s = "SIGNAL " + signal + "\r\n";
|
||||
Waiter w = new Waiter();
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> "+s);
|
||||
if (this.thread != null) {
|
||||
this.thread.stopListening();
|
||||
}
|
||||
synchronized (waiters) {
|
||||
output.write(s);
|
||||
output.flush();
|
||||
waiters.addLast(w); // Prevent react() from finding the list empty
|
||||
}
|
||||
}
|
||||
|
||||
/** Tells the Tor server that future SOCKS requests for connections to a set of original
|
||||
* addresses should be replaced with connections to the specified replacement
|
||||
* addresses. Each element of <b>kvLines</b> is a String of the form
|
||||
* "old-address new-address". This function returns the new address mapping.
|
||||
*
|
||||
* The client may decline to provide a body for the original address, and
|
||||
* instead send a special null address ("0.0.0.0" for IPv4, "::0" for IPv6, or
|
||||
* "." for hostname), signifying that the server should choose the original
|
||||
* address itself, and return that address in the reply. The server
|
||||
* should ensure that it returns an element of address space that is unlikely
|
||||
* to be in actual use. If there is already an address mapped to the
|
||||
* destination address, the server may reuse that mapping.
|
||||
*
|
||||
* If the original address is already mapped to a different address, the old
|
||||
* mapping is removed. If the original address and the destination address
|
||||
* are the same, the server removes any mapping in place for the original
|
||||
* address.
|
||||
*
|
||||
* Mappings set by the controller last until the Tor process exits:
|
||||
* they never expire. If the controller wants the mapping to last only
|
||||
* a certain time, then it must explicitly un-map the address when that
|
||||
* time has elapsed.
|
||||
*/
|
||||
public Map mapAddresses(Collection kvLines) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("MAPADDRESS");
|
||||
for (Iterator it = kvLines.iterator(); it.hasNext(); ) {
|
||||
String kv = (String) it.next();
|
||||
int i = kv.indexOf(' ');
|
||||
sb.append(" ").append(kv.substring(0,i)).append("=")
|
||||
.append(quote(kv.substring(i+1)));
|
||||
}
|
||||
sb.append("\r\n");
|
||||
ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
Map result = new HashMap();
|
||||
for (Iterator it = lst.iterator(); it.hasNext(); ) {
|
||||
String kv = ((ReplyLine) it.next()).msg;
|
||||
int idx = kv.indexOf('=');
|
||||
result.put(kv.substring(0, idx),
|
||||
kv.substring(idx+1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Queries the Tor server for keyed values that are not stored in the torrc
|
||||
* configuration file. Returns a map of keys to values.
|
||||
*
|
||||
* Recognized keys include:
|
||||
* <ul>
|
||||
* <li>"version" : The version of the server's software, including the name
|
||||
* of the software. (example: "Tor 0.0.9.4")</li>
|
||||
* <li>"desc/id/<OR identity>" or "desc/name/<OR nickname>" : the latest server
|
||||
* descriptor for a given OR, NUL-terminated. If no such OR is known, the
|
||||
* corresponding value is an empty string.</li>
|
||||
* <li>"network-status" : a space-separated list of all known OR identities.
|
||||
* This is in the same format as the router-status line in directories;
|
||||
* see tor-spec.txt for details.</li>
|
||||
* <li>"addr-mappings/all"</li>
|
||||
* <li>"addr-mappings/config"</li>
|
||||
* <li>"addr-mappings/cache"</li>
|
||||
* <li>"addr-mappings/control" : a space-separated list of address mappings, each
|
||||
* in the form of "from-address=to-address". The 'config' key
|
||||
* returns those address mappings set in the configuration; the 'cache'
|
||||
* key returns the mappings in the client-side DNS cache; the 'control'
|
||||
* key returns the mappings set via the control interface; the 'all'
|
||||
* target returns the mappings set through any mechanism.</li>
|
||||
* <li>"circuit-status" : A series of lines as for a circuit status event. Each line is of the form:
|
||||
* "CircuitID CircStatus Path"</li>
|
||||
* <li>"stream-status" : A series of lines as for a stream status event. Each is of the form:
|
||||
* "StreamID StreamStatus CircID Target"</li>
|
||||
* <li>"orconn-status" : A series of lines as for an OR connection status event. Each is of the
|
||||
* form: "ServerID ORStatus"</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Map getInfo(Collection keys) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("GETINFO");
|
||||
for (Iterator it = keys.iterator(); it.hasNext(); ) {
|
||||
sb.append(" ").append((String)it.next());
|
||||
}
|
||||
sb.append("\r\n");
|
||||
ArrayList lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
Map m = new HashMap();
|
||||
for (Iterator it = lst.iterator(); it.hasNext(); ) {
|
||||
ReplyLine line = (ReplyLine) it.next();
|
||||
int idx = line.msg.indexOf('=');
|
||||
if (idx<0)
|
||||
break;
|
||||
String k = line.msg.substring(0,idx);
|
||||
Object v;
|
||||
if (line.rest != null) {
|
||||
v = line.rest;
|
||||
} else {
|
||||
v = line.msg.substring(idx+1);
|
||||
}
|
||||
m.put(k, v);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/** An extendCircuit request takes one of two forms: either the <b>circID</b> is zero, in
|
||||
* which case it is a request for the server to build a new circuit according
|
||||
* to the specified path, or the <b>circID</b> is nonzero, in which case it is a
|
||||
* request for the server to extend an existing circuit with that ID according
|
||||
* to the specified <b>path</b>.
|
||||
*
|
||||
* If successful, returns the Circuit ID of the (maybe newly created) circuit.
|
||||
*/
|
||||
public String extendCircuit(String circID, String path) throws IOException {
|
||||
ArrayList lst = sendAndWaitForResponse(
|
||||
"EXTENDCIRCUIT "+circID+" "+path+"\r\n", null);
|
||||
return ((ReplyLine)lst.get(0)).msg;
|
||||
}
|
||||
|
||||
/** Informs the Tor server that the stream specified by <b>streamID</b> should be
|
||||
* associated with the circuit specified by <b>circID</b>.
|
||||
*
|
||||
* Each stream may be associated with
|
||||
* at most one circuit, and multiple streams may share the same circuit.
|
||||
* Streams can only be attached to completed circuits (that is, circuits that
|
||||
* have sent a circuit status "BUILT" event or are listed as built in a
|
||||
* getInfo circuit-status request).
|
||||
*
|
||||
* If <b>circID</b> is 0, responsibility for attaching the given stream is
|
||||
* returned to Tor.
|
||||
*
|
||||
* By default, Tor automatically attaches streams to
|
||||
* circuits itself, unless the configuration variable
|
||||
* "__LeaveStreamsUnattached" is set to "1". Attempting to attach streams
|
||||
* via TC when "__LeaveStreamsUnattached" is false may cause a race between
|
||||
* Tor and the controller, as both attempt to attach streams to circuits.
|
||||
*/
|
||||
public void attachStream(String streamID, String circID)
|
||||
throws IOException {
|
||||
sendAndWaitForResponse("ATTACHSTREAM "+streamID+" "+circID+"\r\n", null);
|
||||
}
|
||||
|
||||
/** Tells Tor about the server descriptor in <b>desc</b>.
|
||||
*
|
||||
* The descriptor, when parsed, must contain a number of well-specified
|
||||
* fields, including fields for its nickname and identity.
|
||||
*/
|
||||
// More documentation here on format of desc?
|
||||
// No need for return value? control-spec.txt says reply is merely "250 OK" on success...
|
||||
public String postDescriptor(String desc) throws IOException {
|
||||
ArrayList lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc);
|
||||
return ((ReplyLine)lst.get(0)).msg;
|
||||
}
|
||||
|
||||
/** Tells Tor to change the exit address of the stream identified by <b>streamID</b>
|
||||
* to <b>address</b>. No remapping is performed on the new provided address.
|
||||
*
|
||||
* To be sure that the modified address will be used, this event must be sent
|
||||
* after a new stream event is received, and before attaching this stream to
|
||||
* a circuit.
|
||||
*/
|
||||
public void redirectStream(String streamID, String address) throws IOException {
|
||||
sendAndWaitForResponse("REDIRECTSTREAM "+streamID+" "+address+"\r\n",
|
||||
null);
|
||||
}
|
||||
|
||||
/** Tells Tor to close the stream identified by <b>streamID</b>.
|
||||
* <b>reason</b> should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal:
|
||||
* <ul>
|
||||
* <li>1 -- REASON_MISC (catch-all for unlisted reasons)</li>
|
||||
* <li>2 -- REASON_RESOLVEFAILED (couldn't look up hostname)</li>
|
||||
* <li>3 -- REASON_CONNECTREFUSED (remote host refused connection)</li>
|
||||
* <li>4 -- REASON_EXITPOLICY (OR refuses to connect to host or port)</li>
|
||||
* <li>5 -- REASON_DESTROY (Circuit is being destroyed)</li>
|
||||
* <li>6 -- REASON_DONE (Anonymized TCP connection was closed)</li>
|
||||
* <li>7 -- REASON_TIMEOUT (Connection timed out, or OR timed out while connecting)</li>
|
||||
* <li>8 -- (unallocated)</li>
|
||||
* <li>9 -- REASON_HIBERNATING (OR is temporarily hibernating)</li>
|
||||
* <li>10 -- REASON_INTERNAL (Internal error at the OR)</li>
|
||||
* <li>11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)</li>
|
||||
* <li>12 -- REASON_CONNRESET (Connection was unexpectedly reset)</li>
|
||||
* <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)</li>
|
||||
* </ul>
|
||||
*
|
||||
* Tor may hold the stream open for a while to flush any data that is pending.
|
||||
*/
|
||||
public void closeStream(String streamID, byte reason)
|
||||
throws IOException {
|
||||
sendAndWaitForResponse("CLOSESTREAM "+streamID+" "+reason+"\r\n",null);
|
||||
}
|
||||
|
||||
/** Tells Tor to close the circuit identified by <b>circID</b>.
|
||||
* If <b>ifUnused</b> is true, do not close the circuit unless it is unused.
|
||||
*/
|
||||
public void closeCircuit(String circID, boolean ifUnused) throws IOException {
|
||||
sendAndWaitForResponse("CLOSECIRCUIT "+circID+
|
||||
(ifUnused?" IFUNUSED":"")+"\r\n", null);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user