diff --git a/net/freehaven/tor/control/Bytes.java b/net/freehaven/tor/control/Bytes.java index 24f929210b85f8a3328375a6f8fc870c2d3ba7e6..6166e449bc211d1c97b4933002b0fa0d6fd73573 100644 --- a/net/freehaven/tor/control/Bytes.java +++ b/net/freehaven/tor/control/Bytes.java @@ -3,7 +3,9 @@ // See LICENSE file for copying information package net.freehaven.tor.control; +import java.util.ArrayList; import java.util.List; +import java.util.StringTokenizer; /** * Static class to do bytewise structure manipulation in Java. @@ -79,5 +81,33 @@ final class Bytes { } } + /** + * Read bytes from 'ba' starting at 'pos', dividing them into strings + * along the character in 'split' and writing them into 'lst' + */ + public static List splitStr(List lst, String str) { + if (lst == null) + lst = new ArrayList(); + StringTokenizer st = new StringTokenizer(str); + while (st.hasMoreTokens()) + lst.add(st.nextToken()); + return lst; + } + + private static final char[] NYBBLES = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + public static final String hex(byte[] ba) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < ba.length; ++i) { + int b = ((int)ba[i]) & 0xff; + buf.append(NYBBLES[b >> 4]); + buf.append(NYBBLES[b&0x0f]); + } + return buf.toString(); + } + private Bytes() {}; } diff --git a/net/freehaven/tor/control/EventHandler.java b/net/freehaven/tor/control/EventHandler.java index c5162f738084b29d2da03ec21615760a92aa7945..d908bd204a2b35a46dbb6c95e92f74adfb0528cc 100644 --- a/net/freehaven/tor/control/EventHandler.java +++ b/net/freehaven/tor/control/EventHandler.java @@ -37,5 +37,11 @@ public interface EventHandler { /** * Invoked when Tor logs a message. */ - public void message(int type, String msg); + public void message(String severity, String msg); + /** + * Invoked in an unspecified handler. + */ + public void unrecognized(String type, String msg); + } + diff --git a/net/freehaven/tor/control/PasswordDigest.java b/net/freehaven/tor/control/PasswordDigest.java index d58c5675dc446948b1d90c4f66d34765c504e617..f632e9d40001f9318c4fe46da9a98bd5a74cc338 100644 --- a/net/freehaven/tor/control/PasswordDigest.java +++ b/net/freehaven/tor/control/PasswordDigest.java @@ -97,13 +97,7 @@ public class PasswordDigest { /** Return a hexadecimal encoding of a byte array. */ // XXX There must be a better way to do this in Java. private static final String encodeBytes(byte[] ba) { - StringBuffer buf = new StringBuffer(); - for (int i = 0; i < ba.length; ++i) { - int b = ((int)ba[i]) & 0xff; - buf.append(NYBBLES[b >> 4]); - buf.append(NYBBLES[b&0x0f]); - } - return buf.toString(); + return Bytes.hex(ba); } } \ No newline at end of file diff --git a/net/freehaven/tor/control/TorControlConnection.java b/net/freehaven/tor/control/TorControlConnection.java index e0137c57d33e637120d6457b03367b2547703d6a..daf9d8f16e25a79bf74d74371d9cbe9f166d46df 100644 --- a/net/freehaven/tor/control/TorControlConnection.java +++ b/net/freehaven/tor/control/TorControlConnection.java @@ -41,7 +41,6 @@ public abstract class TorControlConnection// implements TorControlCommands { this.waiters = new LinkedList(); } - /** Set the EventHandler object that will be notified of any * events Tor delivers to this connection. To make Tor send us * events, call listenForEvents(). */ @@ -155,7 +154,7 @@ public abstract class TorControlConnection// implements TorControlCommands { * Tell Tor to extend the circuit identified by 'circID' through the * servers named in the list 'path'. */ - public abstract int extendCircuit(String circID, String path) throws IOException; + public abstract String extendCircuit(String circID, String path) throws IOException; /** * Tell Tor to attach the stream identified by 'streamID' to the circuit @@ -173,11 +172,11 @@ public abstract class TorControlConnection// implements TorControlCommands { /** Tell Tor to close the stream identified by 'streamID'. */ - public abstract void closeStream(String streamID, byte reason, byte flags) + public abstract void closeStream(String streamID, byte reason) throws IOException; /** Tell Tor to close the circuit identified by 'streamID'. */ - public abstract void closeCircuit(String circID, byte flags) throws IOException; + public abstract void closeCircuit(String circID, boolean ifUnused) throws IOException; } diff --git a/net/freehaven/tor/control/TorControlConnection0.java b/net/freehaven/tor/control/TorControlConnection0.java index 29c935019da9055ffbb1d058e2abd18afbef4fe3..d3171b260953ec3a9ecab21707051395987caccb 100644 --- a/net/freehaven/tor/control/TorControlConnection0.java +++ b/net/freehaven/tor/control/TorControlConnection0.java @@ -27,8 +27,6 @@ public class TorControlConnection0 extends TorControlConnection 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. */ @@ -222,11 +220,19 @@ public class TorControlConnection0 extends TorControlConnection 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(type, Bytes.getNulTerminatedStr(c.body, 2)); + handler.message("ERR", Bytes.getNulTerminatedStr(c.body, 2)); break; default: throw new TorControlSyntaxError("Unrecognized event type."); @@ -345,13 +351,13 @@ public class TorControlConnection0 extends TorControlConnection return m; } - public int extendCircuit(String circID, String path) throws IOException { + 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 Bytes.getU32(c.body, 0); + return Integer.toString(Bytes.getU32(c.body, 0)); } public void attachStream(String streamID, String circID) @@ -381,21 +387,21 @@ public class TorControlConnection0 extends TorControlConnection /** Tell Tor to close the stream identified by 'streamID'. */ - public void closeStream(String streamID, byte reason, byte flags) + 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] = flags; + ba[5] = (byte)0; sendAndWaitForResponse(CMD_CLOSESTREAM, ba); } /** Tell Tor to close the circuit identified by 'streamID'. */ - public void closeCircuit(String circID, byte flags) throws IOException { + public void closeCircuit(String circID, boolean ifUnused) throws IOException { byte[] ba = new byte[5]; Bytes.setU32(ba, 0, (int)Long.parseLong(circID)); - ba[4] = flags; + ba[4] = (byte)(ifUnused? 1 : 0); sendAndWaitForResponse(CMD_CLOSECIRCUIT, ba); } diff --git a/net/freehaven/tor/control/TorControlConnection1.java b/net/freehaven/tor/control/TorControlConnection1.java new file mode 100644 index 0000000000000000000000000000000000000000..ed72bdb05872474e751867f2a98ab40b9e7b4c8c --- /dev/null +++ b/net/freehaven/tor/control/TorControlConnection1.java @@ -0,0 +1,346 @@ +// $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; + +/** DOCDOC */ +public class TorControlConnection1 extends TorControlConnection + implements TorControlCommands +{ + protected java.io.BufferedReader input; + protected java.io.Writer output; + + 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(".")) + output.write("."); + output.write(line); + if (line.endsWith("\r")) + output.write("\n"); + else + output.write("\r\n"); + } + output.write(".\r\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.length() < 4) + throw new TorControlSyntaxError("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 (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 (((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 { + Waiter w = new Waiter(); + synchronized (waiters) { + output.write(s); + 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); + } + } + } + + /** Change the values of the configuration options stored in + * 'kvList'. (The format is "key 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 Map 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); + Map result = new HashMap(); + 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 void setEvents(List events) throws IOException { + StringBuffer sb = new StringBuffer("SETEVENTS"); + for (Iterator it = events.iterator(); it.hasNext(); ) { + String event = (String) it.next(); + sb.append(" ").append(event); + } + sb.append("\r\n"); + sendAndWaitForResponse(sb.toString(), null); + } + + public void authenticate(byte[] auth) throws IOException { + String cmd = "AUTHENTICATE " + Bytes.hex(auth) + "\r\n"; + sendAndWaitForResponse(cmd, null); + } + + public void saveConf() throws IOException { + sendAndWaitForResponse("SAVECONF\r\n", null); + } + + public void signal(String signal) throws IOException { + String cmd = "AUTHENTICATE " + signal + "\r\n"; + sendAndWaitForResponse(cmd, null); + } + + 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 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; + } + + public String extendCircuit(String circID, String path) throws IOException { + ArrayList lst = sendAndWaitForResponse( + "EXTENDCIRCUIT "+circID+" "+path+"\r\n", null); + return ((ReplyLine)lst.get(0)).msg; + } + + 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 String postDescriptor(String desc) throws IOException { + ArrayList lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc); + return ((ReplyLine)lst.get(0)).msg; + } + + /** Tell Tor to change the target of the stream identified by 'streamID' + * to 'address'. + */ + public void redirectStream(String streamID, String address) throws IOException { + sendAndWaitForResponse("REDIRECTSTREAM "+streamID+" "+address+"\r\n", + null); + } + + /** Tell Tor to close the stream identified by 'streamID'. + */ + public void closeStream(String streamID, byte reason) + throws IOException { + sendAndWaitForResponse("CLOSESTREAM "+streamID+" "+reason+"\r\n",null); + } + + /** Tell Tor to close the circuit identified by 'streamID'. + */ + public void closeCircuit(String circID, boolean ifUnused) throws IOException { + sendAndWaitForResponse("CLOSECIRCUIT "+circID+ + (ifUnused?" IFUNUSED":"")+"\r\n", null); + } + +} diff --git a/net/freehaven/tor/control/TorControlError.java b/net/freehaven/tor/control/TorControlError.java index 7244784d3d0093ae7be495b1512b1d2544fedcb8..1d04c80a24cec9cc481fb2446eaf763a8f5121da 100644 --- a/net/freehaven/tor/control/TorControlError.java +++ b/net/freehaven/tor/control/TorControlError.java @@ -12,11 +12,16 @@ public class TorControlError extends RuntimeException { super(s); errorType = type; } + public TorControlError(String s) { + this(-1, s); + } public int getErrorType() { return errorType; } public String getErrorMsg() { try { + if (errorType == -1) + return null; return TorControlCommands.ERROR_MSGS[errorType]; } catch (ArrayIndexOutOfBoundsException ex) { return "Unrecongized error #"+errorType;