diff --git a/doc/control-spec.txt b/doc/control-spec.txt
index 4e5b298386..96ac7e56a7 100644
--- a/doc/control-spec.txt
+++ b/doc/control-spec.txt
@@ -405,19 +405,32 @@ $Id$
3.10. EXTENDCIRCUIT
Sent from the client to the server. The format is:
- "EXTENDCIRCUIT" SP CircuitID SP ServerID *("," ServerID) CRLF
+ "EXTENDCIRCUIT" SP CircuitID SP
+ ServerID *("," ServerID) SP
+ ("purpose=" Purpose) CRLF
- This request takes one of two forms: either the Circuit ID is zero, in
+ This request takes one of two forms: either the CircuitID is zero, in
which case it is a request for the server to build a new circuit according
- to the specified path, or the Circuit ID is nonzero, in which case it is a
+ to the specified path, or the CircuitID is nonzero, in which case it is a
request for the server to extend an existing circuit with that ID according
to the specified path.
- If the request is successful, the server sends a reply containing a message
- body consisting of the Circuit ID of the (maybe newly created) circuit.
- The syntax is "250" SP "EXTENDED" SP CircuitID CRLF.
+ If CircuitID is 0 and "purpose=" is specified, then the circuit's
+ purpose is set. Two choices are recognized: "general" and
+ "controller". If not specified, circuits are created as "general".
-3.11. ATTACHSTREAM
+ If the request is successful, the server sends a reply containing a
+ message body consisting of the CircuitID of the (maybe newly created)
+ circuit. The syntax is "250" SP "EXTENDED" SP CircuitID CRLF.
+
+3.11. SETCIRCUITPURPOSE
+
+ Sent from the client to the server. The format is:
+ "SETCIRCUITPURPOSE" SP CircuitID SP Purpose CRLF
+
+ This changes the circuit's purpose. See EXTENDCIRCUIT above for details.
+
+3.12. ATTACHSTREAM
Sent from the client to the server. The syntax is:
"ATTACHSTREAM" SP StreamID SP CircuitID CRLF
@@ -446,7 +459,7 @@ $Id$
via TC when "__LeaveStreamsUnattached" is false may cause a race between
Tor and the controller, as both attempt to attach streams to circuits.}
-3.12. POSTDESCRIPTOR
+3.13. POSTDESCRIPTOR
Sent from the client to the server. The syntax is:
"+POSTDESCRIPTOR" CRLF Descriptor CRLF "." CRLF
@@ -462,7 +475,7 @@ $Id$
why the server was not added. If the descriptor is added, Tor replies with
"250 OK".
-3.13. REDIRECTSTREAM
+3.14. REDIRECTSTREAM
Sent from the client to the server. The syntax is:
"REDIRECTSTREAM" SP StreamID SP Address (SP Port) CRLF
@@ -477,7 +490,7 @@ $Id$
Tor replies with "250 OK" on success.
-3.14. CLOSESTREAM
+3.15. CLOSESTREAM
Sent from the client to the server. The syntax is:
@@ -491,7 +504,7 @@ $Id$
Tor replies with "250 OK" on success, or a 512 if there aren't enough
arguments, or a 552 if it doesn't recognize the StreamID or reason.
-3.15. CLOSECIRCUIT
+3.16. CLOSECIRCUIT
The syntax is:
CLOSECIRCUIT SP CircuitID *(SP Flag) CRLF
@@ -506,7 +519,7 @@ $Id$
Tor replies with "250 OK" on success, or a 512 if there aren't enough
arguments, or a 552 if it doesn't recognize the CircuitID.
-3.16. QUIT
+3.17. QUIT
Tells the server to hang up on this controller connection. This command
can be used before authenticating.
diff --git a/src/or/control.c b/src/or/control.c
index a843b98152..bd845bb04e 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -169,6 +169,8 @@ static int handle_control_getinfo(connection_t *conn, uint32_t len,
const char *body);
static int handle_control_extendcircuit(connection_t *conn, uint32_t len,
const char *body);
+static int handle_control_setcircuitpurpose(connection_t *conn, uint32_t len,
+ const char *body);
static int handle_control_attachstream(connection_t *conn, uint32_t len,
const char *body);
static int handle_control_postdescriptor(connection_t *conn, uint32_t len,
@@ -1541,6 +1543,25 @@ handle_control_getinfo(connection_t *conn, uint32_t len, const char *body)
return 0;
}
+/** If string contains a recognized circuit purpose,
+ * possibly prefaced with the string "purpose=", then assign it
+ * and return 0. Otherwise return -1. */
+static int
+get_purpose(char *string, uint8_t *purpose)
+{
+ if (!strcmpstart(string, "purpose="))
+ string += strlen("purpose=");
+
+ if (!strcmp(string, "general"))
+ *purpose = CIRCUIT_PURPOSE_C_GENERAL;
+ else if (!strcmp(string, "controller"))
+ *purpose = CIRCUIT_PURPOSE_CONTROLLER;
+ else { /* not a recognized purpose */
+ return -1;
+ }
+ return 0;
+}
+
/** Called when we get an EXTENDCIRCUIT message. Try to extend the listed
* circuit, and report success or failure. */
static int
@@ -1552,6 +1573,7 @@ handle_control_extendcircuit(connection_t *conn, uint32_t len,
circuit_t *circ = NULL;
int zero_circ, v0;
char reply[4];
+ uint8_t intended_purpose = CIRCUIT_PURPOSE_C_GENERAL;
v0 = STATE_IS_V0(conn->state);
router_nicknames = smartlist_create();
@@ -1600,6 +1622,13 @@ handle_control_extendcircuit(connection_t *conn, uint32_t len,
if (!zero_circ && !circ) {
goto done;
}
+ if (zero_circ && smartlist_len(args)>2) {
+ if (get_purpose(smartlist_get(args,2), &intended_purpose) < 0) {
+ connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n",
+ (char *)smartlist_get(args,2));
+ goto done;
+ }
+ }
}
routers = smartlist_create();
@@ -1625,7 +1654,7 @@ handle_control_extendcircuit(connection_t *conn, uint32_t len,
if (zero_circ) {
/* start a new circuit */
- circ = circuit_init(CIRCUIT_PURPOSE_C_GENERAL, 0, 0, 0);
+ circ = circuit_init(intended_purpose, 0, 0, 0);
}
/* now circ refers to something that is ready to be extended */
@@ -1677,6 +1706,44 @@ handle_control_extendcircuit(connection_t *conn, uint32_t len,
return 0;
}
+/** Called when we get a SETCIRCUITPURPOSE message. If we can find
+ * the circuit and it's a valid purpose, change it. */
+static int
+handle_control_setcircuitpurpose(connection_t *conn, uint32_t len,
+ const char *body)
+{
+ circuit_t *circ;
+ uint8_t new_purpose;
+ smartlist_t *args = smartlist_create();
+ smartlist_split_string(args, body, " ",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ if (smartlist_len(args)<2) {
+ connection_printf_to_buf(conn,
+ "512 Missing argument to SETCIRCUITPURPOSE\r\n");
+ goto done;
+ }
+
+ if (!(circ = get_circ(smartlist_get(args,0)))) {
+ connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n",
+ (char*)smartlist_get(args, 0));
+ goto done;
+ }
+
+ if (get_purpose(smartlist_get(args,1), &new_purpose) < 0) {
+ connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n",
+ (char *)smartlist_get(args,1));
+ goto done;
+ }
+
+ circ->purpose = new_purpose;
+ connection_write_str_to_buf("250 OK\r\n", conn);
+
+done:
+ SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
+ smartlist_free(args);
+ return 0;
+}
+
/** Called when we get an ATTACHSTREAM message. Try to attach the requested
* stream, and report success or failure. */
static int
@@ -2187,6 +2254,9 @@ connection_control_process_inbuf_v1(connection_t *conn)
} else if (!strcasecmp(conn->incoming_cmd, "EXTENDCIRCUIT")) {
if (handle_control_extendcircuit(conn, data_len, args))
return -1;
+ } else if (!strcasecmp(conn->incoming_cmd, "SETCIRCUITPURPOSE")) {
+ if (handle_control_setcircuitpurpose(conn, data_len, args))
+ return -1;
} else if (!strcasecmp(conn->incoming_cmd, "ATTACHSTREAM")) {
if (handle_control_attachstream(conn, data_len, args))
return -1;
diff --git a/src/or/or.h b/src/or/or.h
index eee2c80db5..efaa00235e 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -423,7 +423,9 @@ typedef enum {
#define CIRCUIT_PURPOSE_S_REND_JOINED 16
/** A testing circuit; not meant to be used for actual traffic. */
#define CIRCUIT_PURPOSE_TESTING 17
-#define _CIRCUIT_PURPOSE_MAX 17
+/** A controller made this circuit and Tor should not use it. */
+#define CIRCUIT_PURPOSE_CONTROLLER 18
+#define _CIRCUIT_PURPOSE_MAX 18
/** True iff the circuit purpose p is for a circuit that
* originated at this node. */