Patch to enable socks4/5 support for OR connections

Christopher Davis loafier at gmail.com
Fri Jan 16 21:10:27 UTC 2009


Hello,

I mentioned this work breifly in a post to or-talk just recently.
Hopefully, this can be useful to others.

Directory connections could be made to use SOCKS proxies, as well,
using the same framework. Although, as Roger explained in his reply 
to my post to or-talk, supporting OR connections seems to be enough
to get things working.

The SOCKS 5 client supports rfc 1929 user/pass auth if the 
configuration directives are set. The SOCKS 4 user id is always 
left empty.

Functions to connect through the proxy server are in connection.c.
I moved in the functions for HTTP CONNECT proxy support from
connection_or.c, so that there is a consistent framework. Hopefully
the comments are enough to explain how it works, but if not, I 
can expand them a bit. 

Thanks,
-- 
Christopher Davis
-------------- next part --------------
Index: src/or/connection_or.c
===================================================================
--- src/or/connection_or.c	(revision 18118)
+++ src/or/connection_or.c	(working copy)
@@ -187,66 +187,6 @@
   return 0;
 }
 
-/** Read conn's inbuf. If the http response from the proxy is all
- * here, make sure it's good news, and begin the tls handshake. If
- * it's bad news, close the connection and return -1. Else return 0
- * and hope for better luck next time.
- */
-static int
-connection_or_read_proxy_response(or_connection_t *or_conn)
-{
-  char *headers;
-  char *reason=NULL;
-  int status_code;
-  time_t date_header;
-  connection_t *conn = TO_CONN(or_conn);
-
-  switch (fetch_from_buf_http(conn->inbuf,
-                              &headers, MAX_HEADERS_SIZE,
-                              NULL, NULL, 10000, 0)) {
-    case -1: /* overflow */
-      log_warn(LD_PROTOCOL,
-               "Your https proxy sent back an oversized response. Closing.");
-      return -1;
-    case 0:
-      log_info(LD_OR,"https proxy response not all here yet. Waiting.");
-      return 0;
-    /* case 1, fall through */
-  }
-
-  if (parse_http_response(headers, &status_code, &date_header,
-                          NULL, &reason) < 0) {
-    log_warn(LD_OR,
-             "Unparseable headers from proxy (connecting to '%s'). Closing.",
-             conn->address);
-    tor_free(headers);
-    return -1;
-  }
-  if (!reason) reason = tor_strdup("[no reason given]");
-
-  if (status_code == 200) {
-    log_info(LD_OR,
-             "HTTPS connect to '%s' successful! (200 %s) Starting TLS.",
-             conn->address, escaped(reason));
-    tor_free(reason);
-    if (connection_tls_start_handshake(or_conn, 0) < 0) {
-      /* TLS handshaking error of some kind. */
-      connection_mark_for_close(conn);
-
-      return -1;
-    }
-    return 0;
-  }
-  /* else, bad news on the status code */
-  log_warn(LD_OR,
-           "The https proxy sent back an unexpected status code %d (%s). "
-           "Closing.",
-           status_code, escaped(reason));
-  tor_free(reason);
-  connection_mark_for_close(conn);
-  return -1;
-}
-
 /** Handle any new bytes that have come in on connection <b>conn</b>.
  * If conn is in 'open' state, hand it to
  * connection_or_process_cells_from_inbuf()
@@ -255,11 +195,24 @@
 int
 connection_or_process_inbuf(or_connection_t *conn)
 {
+  int ret;
   tor_assert(conn);
 
   switch (conn->_base.state) {
-    case OR_CONN_STATE_PROXY_READING:
-      return connection_or_read_proxy_response(conn);
+    case OR_CONN_STATE_PROXY_HANDSHAKING:
+      ret = connection_read_proxy_handshake(TO_CONN(conn));
+
+      /* start TLS after handshake completion, or deal with error */
+      if (ret == 1) {
+        tor_assert(TO_CONN(conn)->proxy_state == PROXY_CONNECTED);
+        if (connection_tls_start_handshake(conn, 0) < 0)
+          ret = -1;
+      }
+      if (ret < 0) {
+        connection_mark_for_close(TO_CONN(conn));
+      }
+
+      return ret;
     case OR_CONN_STATE_OPEN:
     case OR_CONN_STATE_OR_HANDSHAKING:
       return connection_or_process_cells_from_inbuf(conn);
@@ -312,11 +265,7 @@
   assert_connection_ok(TO_CONN(conn),0);
 
   switch (conn->_base.state) {
-    case OR_CONN_STATE_PROXY_FLUSHING:
-      log_debug(LD_OR,"finished sending CONNECT to proxy.");
-      conn->_base.state = OR_CONN_STATE_PROXY_READING;
-      connection_stop_writing(TO_CONN(conn));
-      break;
+    case OR_CONN_STATE_PROXY_HANDSHAKING:
     case OR_CONN_STATE_OPEN:
     case OR_CONN_STATE_OR_HANDSHAKING:
       connection_stop_writing(TO_CONN(conn));
@@ -334,6 +283,7 @@
 int
 connection_or_finished_connecting(or_connection_t *or_conn)
 {
+  int proxy_type;
   connection_t *conn;
   tor_assert(or_conn);
   conn = TO_CONN(or_conn);
@@ -343,28 +293,24 @@
             conn->address,conn->port);
   control_event_bootstrap(BOOTSTRAP_STATUS_HANDSHAKE, 0);
 
-  if (get_options()->HttpsProxy) {
-    char buf[1024];
-    char *base64_authenticator=NULL;
-    const char *authenticator = get_options()->HttpsProxyAuthenticator;
+  proxy_type = PROXY_NONE;
 
-    if (authenticator) {
-      base64_authenticator = alloc_http_authenticator(authenticator);
-      if (!base64_authenticator)
-        log_warn(LD_OR, "Encoding https authenticator failed");
+  if (get_options()->HttpsProxy)
+    proxy_type = PROXY_CONNECT;
+  else if (get_options()->Socks4Proxy)
+    proxy_type = PROXY_SOCKS4;
+  else if (get_options()->Socks5Proxy)
+    proxy_type = PROXY_SOCKS5;
+
+  if (proxy_type != PROXY_NONE) {
+    /* start proxy handshake */
+    if (connection_proxy_connect(conn, proxy_type) < 0) {
+      connection_mark_for_close(conn);
+      return -1;
     }
-    if (base64_authenticator) {
-      tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.1\r\n"
-                   "Proxy-Authorization: Basic %s\r\n\r\n",
-                   fmt_addr(&conn->addr),
-                   conn->port, base64_authenticator);
-      tor_free(base64_authenticator);
-    } else {
-      tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n\r\n",
-                   fmt_addr(&conn->addr), conn->port);
-    }
-    connection_write_to_buf(buf, strlen(buf), conn);
-    conn->state = OR_CONN_STATE_PROXY_FLUSHING;
+
+    connection_start_reading(conn);
+    conn->state = OR_CONN_STATE_PROXY_HANDSHAKING;
     return 0;
   }
 
@@ -730,6 +676,7 @@
   or_connection_t *conn;
   or_options_t *options = get_options();
   int socket_error = 0;
+  int using_proxy = 0;
   tor_addr_t addr;
 
   tor_assert(_addr);
@@ -748,19 +695,28 @@
   conn->_base.state = OR_CONN_STATE_CONNECTING;
   control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED, 0);
 
+  /* use a proxy server if available */
   if (options->HttpsProxy) {
-    /* we shouldn't connect directly. use the https proxy instead. */
+    using_proxy = 1;
     tor_addr_from_ipv4h(&addr, options->HttpsProxyAddr);
     port = options->HttpsProxyPort;
+  } else if (options->Socks4Proxy) {
+    using_proxy = 1;
+    tor_addr_from_ipv4h(&addr, options->Socks4ProxyAddr);
+    port = options->Socks4ProxyPort;
+  } else if (options->Socks5Proxy) {
+    using_proxy = 1;
+    tor_addr_from_ipv4h(&addr, options->Socks5ProxyAddr);
+    port = options->Socks5ProxyPort;
   }
 
   switch (connection_connect(TO_CONN(conn), conn->_base.address,
                              &addr, port, &socket_error)) {
     case -1:
       /* If the connection failed immediately, and we're using
-       * an https proxy, our https proxy is down. Don't blame the
-       * Tor server. */
-      if (!options->HttpsProxy) {
+       * a proxy, our proxy is down. Don't blame the Tor server.
+       */
+      if (!using_proxy) {
         entry_guard_register_connect_status(conn->identity_digest, 0,
                                             time(NULL));
         router_set_status(conn->identity_digest, 0);
Index: src/or/config.c
===================================================================
--- src/or/config.c	(revision 18118)
+++ src/or/config.c	(working copy)
@@ -238,6 +238,10 @@
   V(HttpProxyAuthenticator,      STRING,   NULL),
   V(HttpsProxy,                  STRING,   NULL),
   V(HttpsProxyAuthenticator,     STRING,   NULL),
+  V(Socks4Proxy,                 STRING,   NULL),
+  V(Socks5Proxy,                 STRING,   NULL),
+  V(Socks5ProxyUsername,         STRING,   NULL),
+  V(Socks5ProxyPassword,         STRING,   NULL),
   OBSOLETE("IgnoreVersion"),
   V(KeepalivePeriod,             INTERVAL, "5 minutes"),
   VAR("Log",                     LINELIST, Logs,             NULL),
@@ -3360,6 +3364,42 @@
       REJECT("HttpsProxyAuthenticator is too long (>= 48 chars).");
   }
 
+  if (options->Socks4Proxy) { /* parse it now */
+    if (parse_addr_port(LOG_WARN, options->Socks4Proxy, NULL,
+                        &options->Socks4ProxyAddr,
+                        &options->Socks4ProxyPort) <0)
+      REJECT("Socks4Proxy failed to parse or resolve. Please fix.");
+    if (options->Socks4ProxyPort == 0) { /* give it a default */
+      options->Socks4ProxyPort = 1080;
+    }
+  }
+
+  if (options->Socks5Proxy) { /* parse it now */
+    if (parse_addr_port(LOG_WARN, options->Socks5Proxy, NULL,
+                        &options->Socks5ProxyAddr,
+                        &options->Socks5ProxyPort) <0)
+      REJECT("Socks5Proxy failed to parse or resolve. Please fix.");
+    if (options->Socks5ProxyPort == 0) { /* give it a default */
+      options->Socks5ProxyPort = 1080;
+    }
+  }
+
+  if (options->Socks5ProxyUsername) {
+    size_t len;
+
+    len = strlen(options->Socks5ProxyUsername);
+    if (len < 1 || len > 255)
+      REJECT("Socks5ProxyUsername must be between 1 and 255 characters.");
+
+    if (!options->Socks5ProxyPassword)
+      REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
+
+    len = strlen(options->Socks5ProxyPassword);
+    if (len < 1 || len > 255)
+      REJECT("Socks5ProxyPassword must be between 1 and 255 characters.");
+  } else if (options->Socks5ProxyPassword)
+    REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
+
   if (options->HashedControlPassword) {
     smartlist_t *sl = decode_hashed_passwords(options->HashedControlPassword);
     if (!sl) {
Index: src/or/or.h
===================================================================
--- src/or/or.h	(revision 18118)
+++ src/or/or.h	(working copy)
@@ -221,6 +221,21 @@
 /* !!!! If _CONN_TYPE_MAX is ever over 15, we must grow the type field in
  * connection_t. */
 
+/* Proxy client types */
+#define PROXY_NONE 0
+#define PROXY_CONNECT 1
+#define PROXY_SOCKS4 2
+#define PROXY_SOCKS5 3
+
+/* Proxy client handshake states */
+#define PROXY_HTTPS_WANT_CONNECT_OK 1
+#define PROXY_SOCKS4_WANT_CONNECT_OK 2
+#define PROXY_SOCKS5_WANT_AUTH_METHOD_NONE 3
+#define PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929 4
+#define PROXY_SOCKS5_WANT_AUTH_RFC1929_OK 5
+#define PROXY_SOCKS5_WANT_CONNECT_OK 6
+#define PROXY_CONNECTED 7
+
 /** True iff <b>x</b> is an edge connection. */
 #define CONN_IS_EDGE(x) \
   ((x)->type == CONN_TYPE_EXIT || (x)->type == CONN_TYPE_AP)
@@ -241,26 +256,24 @@
 #define _OR_CONN_STATE_MIN 1
 /** State for a connection to an OR: waiting for connect() to finish. */
 #define OR_CONN_STATE_CONNECTING 1
-/** State for a connection to an OR: waiting for proxy command to flush. */
-#define OR_CONN_STATE_PROXY_FLUSHING 2
-/** State for a connection to an OR: waiting for proxy response. */
-#define OR_CONN_STATE_PROXY_READING 3
+/** State for a connection to an OR: waiting for proxy handshake to complete */
+#define OR_CONN_STATE_PROXY_HANDSHAKING 2
 /** State for a connection to an OR or client: SSL is handshaking, not done
  * yet. */
-#define OR_CONN_STATE_TLS_HANDSHAKING 4
+#define OR_CONN_STATE_TLS_HANDSHAKING 3
 /** State for a connection to an OR: We're doing a second SSL handshake for
  * renegotiation purposes. */
-#define OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING 5
+#define OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING 4
 /** State for a connection at an OR: We're waiting for the client to
  * renegotiate. */
-#define OR_CONN_STATE_TLS_SERVER_RENEGOTIATING 6
+#define OR_CONN_STATE_TLS_SERVER_RENEGOTIATING 5
 /** State for a connection to an OR: We're done with our SSL handshake, but we
  * haven't yet negotiated link protocol versions and sent a netinfo cell.
  */
-#define OR_CONN_STATE_OR_HANDSHAKING 7
+#define OR_CONN_STATE_OR_HANDSHAKING 6
 /** State for a connection to an OR: Ready to send/receive cells. */
-#define OR_CONN_STATE_OPEN 8
-#define _OR_CONN_STATE_MAX 8
+#define OR_CONN_STATE_OPEN 7
+#define _OR_CONN_STATE_MAX 7
 
 #define _EXIT_CONN_STATE_MIN 1
 /** State for an exit connection: waiting for response from dns farm. */
@@ -971,6 +984,9 @@
    * to the evdns_server_port is uses to listen to and answer connections. */
   struct evdns_server_port *dns_server_port;
 
+  /** CONNECT/SOCKS proxy client handshake state (for outgoing connections). */
+  unsigned int proxy_state:4;
+
 } connection_t;
 
 /** Stores flags and information related to the portion of a v2 Tor OR
@@ -2355,6 +2371,16 @@
   uint16_t HttpsProxyPort; /**< Parsed port for https proxy, if any. */
   char *HttpsProxyAuthenticator; /**< username:password string, if any. */
 
+  char *Socks4Proxy;
+  uint32_t Socks4ProxyAddr;
+  uint16_t Socks4ProxyPort;
+
+  char *Socks5Proxy;
+  uint32_t Socks5ProxyAddr;
+  uint16_t Socks5ProxyPort;
+  char *Socks5ProxyUsername;
+  char *Socks5ProxyPassword;
+
   /** List of configuration lines for replacement directory authorities.
    * If you just want to replace one class of authority at a time,
    * use the "Alternate*Authority" options below instead. */
@@ -2690,6 +2716,7 @@
                         int force_complete);
 int fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
                          int log_sockstype, int safe_socks);
+int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason);
 int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len);
 
 int peek_buf_has_control0_command(buf_t *buf);
@@ -2949,6 +2976,10 @@
 int connection_connect(connection_t *conn, const char *address,
                        const tor_addr_t *addr,
                        uint16_t port, int *socket_error);
+
+int connection_proxy_connect(connection_t *conn, int type);
+int connection_read_proxy_handshake(connection_t *conn);
+
 int retry_all_listeners(smartlist_t *replaced_conns,
                         smartlist_t *new_conns);
 
Index: src/or/buffers.c
===================================================================
--- src/or/buffers.c	(revision 18118)
+++ src/or/buffers.c	(working copy)
@@ -1600,6 +1600,177 @@
   }
 }
 
+/** Inspect a reply from SOCKS server stored in <b>buf</b> according
+ * to <b>state</b>, removing the protocol data upon success. Return 0 on
+ * incomplete response, 1 on success and -1 on error, in which case
+ * <b>reason</b> is set to a descriptive message (free() when finished
+ * with it).
+ *
+ * As a special case, 2 is returned when user/pass is required
+ * during SOCKS5 handshake and user/pass is configured.
+ */
+int
+fetch_from_buf_socks_client(buf_t *buf, int state, char **reason)
+{
+  unsigned char *data;
+  size_t addrlen;
+
+  if (buf->datalen < 2)
+    return 0;
+
+  buf_pullup(buf, 128, 0);
+  tor_assert(buf->head && buf->head->datalen >= 2);
+
+  data = (unsigned char *) buf->head->data;
+
+  switch (state) {
+    case PROXY_SOCKS4_WANT_CONNECT_OK:
+      /* Wait for the complete response */
+      if (buf->head->datalen < 8)
+        return 0;
+
+      if (data[1] != 0x5a) {
+        switch (data[1]) {
+          case 0x5b:
+            *reason = tor_strdup("server rejected connection");
+            break;
+          case 0x5c:
+            *reason = tor_strdup("server cannot connect to identd "
+                                 "on this client");
+            break;
+          case 0x5d:
+            *reason = tor_strdup("user id does not match identd");
+            break;
+          default:
+            *reason = tor_strdup("invalid SOCKS 4 response code");
+            break;
+        }
+
+        return -1;
+      }
+
+      /* Success */
+      buf_remove_from_front(buf, 8);
+      return 1;
+
+    case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE:
+      /* we don't have any credentials */
+      if (data[1] != 0x00) {
+        *reason = tor_strdup("server doesn't support any of our "
+                             "available authentication methods");
+        return -1;
+      }
+
+      log_info(LD_NET, "SOCKS 5 client: continuing without authentication");
+      buf_clear(buf);
+      return 1;
+
+    case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929:
+      /* we have a username and password. return 1 if we can proceed without
+       * providing authentication, or 2 otherwise. */
+      switch (data[1]) {
+        case 0x00:
+          log_info(LD_NET, "SOCKS 5 client: we have auth details but server "
+                            "doesn't require authentication.");
+          buf_clear(buf);
+          return 1;
+        case 0x02:
+          log_info(LD_NET, "SOCKS 5 client: need authentication.");
+          buf_clear(buf);
+          return 2;
+        /* fall through */
+      }
+
+      *reason = tor_strdup("server doesn't support any of our available "
+                           "authentication methods");
+      return -1;
+
+    case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK:
+      /* handle server reply to rfc1929 authentication */
+      if (data[1] != 0x00) {
+        *reason = tor_strdup("authentication failed");
+        return -1;
+      }
+
+      log_info(LD_NET, "SOCKS 5 client: authentication successful.");
+      buf_clear(buf);
+      return 1;
+
+    case PROXY_SOCKS5_WANT_CONNECT_OK:
+      /* response is variable length. BND.ADDR, etc, isn't needed
+       * (don't bother with buf_pullup()), but make sure to eat all
+       * the data used */
+
+      /* wait for address type field to arrive */
+      if (buf->datalen < 4)
+        return 0;
+
+      switch (data[3]) {
+        case 0x01: /* ip4 */
+          addrlen = 4;
+          break;
+        case 0x04: /* ip6 */
+          addrlen = 16;
+          break;
+        case 0x03: /* fqdn (can this happen here?) */
+          if (buf->datalen < 5)
+            return 0;
+          addrlen = 1 + data[4];
+          break;
+        default:
+          *reason = tor_strdup("invalid response to connect request");
+          return -1;
+      }
+
+      /* wait for address and port */
+      if (buf->datalen < 6 + addrlen)
+        return 0;
+
+      if (data[1] != 0x00) {
+
+        switch (data[1]) {
+          case 0x01:
+            *reason = tor_strdup("general SOCKS server failure");
+            break;
+          case 0x02:
+            *reason = tor_strdup("connection not allowed by ruleset");
+            break;
+          case 0x03:
+            *reason = tor_strdup("Network unreachable");
+            break;
+          case 0x04:
+            *reason = tor_strdup("Host unreachable");
+            break;
+          case 0x05:
+            *reason = tor_strdup("Connection refused");
+            break;
+          case 0x06:
+            *reason = tor_strdup("TTL expired");
+            break;
+          case 0x07:
+            *reason = tor_strdup("Command not supported");
+            break;
+          case 0x08:
+            *reason = tor_strdup("Address type not supported");
+            break;
+          default:
+            *reason = tor_strdup("unknown reason");
+            break;
+        }
+
+        return -1;
+      }
+
+      buf_remove_from_front(buf, 6 + addrlen);
+      return 1;
+  }
+
+  /* shouldn't get here... */
+  tor_assert(0);
+
+  return -1;
+}
+
 /** Return 1 iff buf looks more like it has an (obsolete) v0 controller
  * command on it than any valid v1 controller command. */
 int
Index: src/or/connection.c
===================================================================
--- src/or/connection.c	(revision 18118)
+++ src/or/connection.c	(working copy)
@@ -32,6 +32,10 @@
 static void client_check_address_changed(int sock);
 static void set_constrained_socket_buffers(int sock, int size);
 
+static const char *connection_proxy_state_to_string(int state);
+static int connection_read_https_proxy_response(connection_t *conn);
+static void connection_send_socks5_connect(connection_t *conn);
+
 /** The last IPv4 address that our network interface seemed to have been
  * binding to, in host order.  We use this to detect when our IP changes. */
 static uint32_t last_interface_ip = 0;
@@ -92,8 +96,7 @@
     case CONN_TYPE_OR:
       switch (state) {
         case OR_CONN_STATE_CONNECTING: return "connect()ing";
-        case OR_CONN_STATE_PROXY_FLUSHING: return "proxy flushing";
-        case OR_CONN_STATE_PROXY_READING: return "proxy reading";
+        case OR_CONN_STATE_PROXY_HANDSHAKING: return "handshaking (proxy)";
         case OR_CONN_STATE_TLS_HANDSHAKING: return "handshaking (TLS)";
         case OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING:
           return "renegotiating (TLS)";
@@ -1315,6 +1318,353 @@
   return inprogress ? 0 : 1;
 }
 
+/** Convert state number to string representation for logging purposes.
+ */
+static const char *
+connection_proxy_state_to_string(int state)
+{
+  static const char *unknown = "???";
+  static const char *states[] = {
+    "PROXY_NONE",
+    "PROXY_HTTPS_WANT_CONNECT_OK",
+    "PROXY_SOCKS4_WANT_CONNECT_OK",
+    "PROXY_SOCKS5_WANT_AUTH_METHOD_NONE",
+    "PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929",
+    "PROXY_SOCKS5_WANT_AUTH_RFC1929_OK",
+    "PROXY_SOCKS5_WANT_CONNECT_OK",
+    "PROXY_CONNECTED",
+  };
+
+  if (state < PROXY_NONE || state > PROXY_CONNECTED)
+    return unknown;
+
+  return states[state];
+}
+
+/** Write a proxy request of <b>type</b> (socks4, socks5, https) to conn
+ * for conn->addr:conn->port, authenticating with the auth details given
+ * in the configuration (if available). SOCKS 5 and HTTP CONNECT proxies
+ * support authentication.
+ *
+ * Returns -1 if conn->addr is incompatible with the proxy protocol, and
+ * 0 otherwise.
+ *
+ * Use connection_read_proxy_handshake() to complete the handshake.
+ */
+int
+connection_proxy_connect(connection_t *conn, int type)
+{
+  or_options_t *options;
+
+  tor_assert(conn);
+
+  options = get_options();
+
+  switch (type) {
+    case PROXY_CONNECT: {
+      char buf[1024];
+      char *base64_authenticator=NULL;
+      const char *authenticator = options->HttpsProxyAuthenticator;
+
+      /* Send HTTP CONNECT and authentication (if available) in
+       * one request */
+
+      if (authenticator) {
+        base64_authenticator = alloc_http_authenticator(authenticator);
+        if (!base64_authenticator)
+          log_warn(LD_OR, "Encoding https authenticator failed");
+      }
+
+      if (base64_authenticator) {
+        tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.1\r\n"
+                     "Proxy-Authorization: Basic %s\r\n\r\n",
+                     fmt_addr(&conn->addr),
+                     conn->port, base64_authenticator);
+        tor_free(base64_authenticator);
+      } else {
+        tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n\r\n",
+                     fmt_addr(&conn->addr), conn->port);
+      }
+
+      connection_write_to_buf(buf, strlen(buf), conn);
+      conn->proxy_state = PROXY_HTTPS_WANT_CONNECT_OK;
+      break;
+    }
+
+    case PROXY_SOCKS4: {
+      unsigned char buf[9];
+      uint16_t portn;
+      uint32_t ip4addr;
+
+      /* Send a SOCKS4 connect request with empty user id */
+
+      if (tor_addr_family(&conn->addr) != AF_INET) {
+        log_warn(LD_NET, "SOCKS4 client is incompatible with with IPv6");
+        return -1;
+      }
+
+      ip4addr = tor_addr_to_ipv4n(&conn->addr);
+      portn = htons(conn->port);
+
+      buf[0] = 4; /* version */
+      buf[1] = SOCKS_COMMAND_CONNECT; /* command */
+      memcpy(buf + 2, &portn, 2); /* port */
+      memcpy(buf + 4, &ip4addr, 4); /* addr */
+      buf[8] = 0; /* userid (empty) */
+
+      connection_write_to_buf((char *)buf, sizeof buf, conn);
+      conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK;
+      break;
+    }
+
+    case PROXY_SOCKS5: {
+      unsigned char buf[4]; /* fields: vers, num methods, method list */
+
+      /* Send a SOCKS5 greeting (connect request must wait) */
+
+      buf[0] = 5; /* version */
+
+      /* number of auth methods */
+      if (options->Socks5ProxyUsername) {
+        buf[1] = 2;
+        buf[2] = 0x00; /* no authentication */
+        buf[3] = 0x02; /* rfc1929 Username/Passwd auth */
+        conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929;
+      } else {
+        buf[1] = 1;
+        buf[2] = 0x00; /* no authentication */
+        conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_NONE;
+      }
+
+      connection_write_to_buf((char *)buf, 2 + buf[1], conn);
+      break;
+    }
+
+    default:
+      log_err(LD_BUG, "Invalid proxy protocol, %d", type);
+      tor_fragile_assert();
+      return -1;
+  }
+
+  log_debug(LD_NET, "set state %s",
+            connection_proxy_state_to_string(conn->proxy_state));
+
+  return 0;
+}
+
+/** Read conn's inbuf. If the http response from the proxy is all
+ * here, make sure it's good news, then return 1. If it's bad news,
+ * return -1. Else return 0 and hope for better luck next time.
+ */
+static int
+connection_read_https_proxy_response(connection_t *conn)
+{
+  char *headers;
+  char *reason=NULL;
+  int status_code;
+  time_t date_header;
+
+  switch (fetch_from_buf_http(conn->inbuf,
+                              &headers, MAX_HEADERS_SIZE,
+                              NULL, NULL, 10000, 0)) {
+    case -1: /* overflow */
+      log_warn(LD_PROTOCOL,
+               "Your https proxy sent back an oversized response. Closing.");
+      return -1;
+    case 0:
+      log_info(LD_NET,"https proxy response not all here yet. Waiting.");
+      return 0;
+    /* case 1, fall through */
+  }
+
+  if (parse_http_response(headers, &status_code, &date_header,
+                          NULL, &reason) < 0) {
+    log_warn(LD_NET,
+             "Unparseable headers from proxy (connecting to '%s'). Closing.",
+             conn->address);
+    tor_free(headers);
+    return -1;
+  }
+  if (!reason) reason = tor_strdup("[no reason given]");
+
+  if (status_code == 200) {
+    log_info(LD_NET,
+             "HTTPS connect to '%s' successful! (200 %s) Starting TLS.",
+             conn->address, escaped(reason));
+    tor_free(reason);
+    return 1;
+  }
+  /* else, bad news on the status code */
+  log_warn(LD_NET,
+           "The https proxy sent back an unexpected status code %d (%s). "
+           "Closing.",
+           status_code, escaped(reason));
+  tor_free(reason);
+  return -1;
+}
+
+/** Send SOCKS5 CONNECT command to <b>conn</b>, copying <b>conn->addr</b>
+ * and <b>conn->port</b> into the request.
+ */
+static void
+connection_send_socks5_connect(connection_t *conn)
+{
+  unsigned char buf[1024];
+  size_t reqsize = 6;
+  uint16_t port = htons(conn->port);
+
+  buf[0] = 5; /* version */
+  buf[1] = SOCKS_COMMAND_CONNECT; /* command */
+  buf[2] = 0; /* reserved */
+
+  if (tor_addr_family(&conn->addr) == AF_INET) {
+    uint32_t addr = tor_addr_to_ipv4n(&conn->addr);
+
+    buf[3] = 1;
+    reqsize += 4;
+    memcpy(buf + 4, &addr, 4);
+    memcpy(buf + 8, &port, 2);
+  } else { /* AF_INET6 */
+    buf[3] = 4;
+    reqsize += 16;
+    memcpy(buf + 4, tor_addr_to_in6(&conn->addr), 16);
+    memcpy(buf + 20, &port, 2);
+  }
+
+  connection_write_to_buf((char *)buf, reqsize, conn);
+
+  conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK;
+}
+
+/** Call this from connection_*_process_inbuf() to advance the proxy
+ * handshake.
+ *
+ * No matter what proxy protocol is used, if this function returns 1, the
+ * handshake is complete, and the data remaining on inbuf may contain the
+ * start of the communication with the requested server.
+ *
+ * Returns 0 if the current buffer contains an incomplete response, and -1
+ * on error.
+ */
+int
+connection_read_proxy_handshake(connection_t *conn)
+{
+  int ret = 0;
+  char *reason = NULL;
+
+  log_debug(LD_NET, "enter state %s",
+            connection_proxy_state_to_string(conn->proxy_state));
+
+  switch (conn->proxy_state) {
+    case PROXY_HTTPS_WANT_CONNECT_OK:
+      ret = connection_read_https_proxy_response(conn);
+      if (ret == 1)
+        conn->proxy_state = PROXY_CONNECTED;
+      break;
+
+    case PROXY_SOCKS4_WANT_CONNECT_OK:
+      ret = fetch_from_buf_socks_client(conn->inbuf,
+                                        conn->proxy_state,
+                                        &reason);
+      if (ret == 1)
+        conn->proxy_state = PROXY_CONNECTED;
+      break;
+
+    case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE:
+      ret = fetch_from_buf_socks_client(conn->inbuf,
+                                        conn->proxy_state,
+                                        &reason);
+      /* no auth needed, do connect */
+      if (ret == 1) {
+        connection_send_socks5_connect(conn);
+        ret = 0;
+      }
+      break;
+
+    case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929:
+      ret = fetch_from_buf_socks_client(conn->inbuf,
+                                        conn->proxy_state,
+                                        &reason);
+
+      /* send auth if needed, otherwise do connect */
+      if (ret == 1) {
+        connection_send_socks5_connect(conn);
+        ret = 0;
+      } else if (ret == 2) {
+        unsigned char buf[1024];
+        size_t reqsize, usize, psize;
+        const char *user, *pass;
+
+        user = get_options()->Socks5ProxyUsername;
+        pass = get_options()->Socks5ProxyPassword;
+        tor_assert(user && pass);
+
+        /* XXX len of user and pass must be <= 255 !!! */
+        usize = strlen(user);
+        psize = strlen(pass);
+        tor_assert(usize <= 255 && psize <= 255);
+        reqsize = 3 + usize + psize;
+
+        buf[0] = 1; /* negotiation version */
+        buf[1] = usize;
+        memcpy(buf + 2, user, usize);
+        buf[2 + usize] = psize;
+        memcpy(buf + 3 + usize, pass, psize);
+
+        connection_write_to_buf((char *)buf, reqsize, conn);
+
+        conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK;
+        ret = 0;
+      }
+      break;
+
+    case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK:
+      ret = fetch_from_buf_socks_client(conn->inbuf,
+                                        conn->proxy_state,
+                                        &reason);
+      /* send the connect request */
+      if (ret == 1) {
+        connection_send_socks5_connect(conn);
+        ret = 0;
+      }
+      break;
+
+    case PROXY_SOCKS5_WANT_CONNECT_OK:
+      ret = fetch_from_buf_socks_client(conn->inbuf,
+                                        conn->proxy_state,
+                                        &reason);
+      if (ret == 1)
+        conn->proxy_state = PROXY_CONNECTED;
+      break;
+
+    default:
+      log_err(LD_BUG, "Invalid proxy_state for reading, %d",
+              conn->proxy_state);
+      tor_fragile_assert();
+      ret = -1;
+      break;
+  }
+
+  log_debug(LD_NET, "leaving state %s",
+            connection_proxy_state_to_string(conn->proxy_state));
+
+  if (ret < 0) {
+    if (reason) {
+      log_warn(LD_NET, "Proxy Client: unable to connect to %s:%d (%s)",
+                conn->address, conn->port, escaped(reason));
+      tor_free(reason);
+    } else {
+      log_warn(LD_NET, "Proxy Client: unable to connect to %s:%d",
+                conn->address, conn->port);
+    }
+  } else if (ret == 1) {
+    log_info(LD_NET, "Proxy Client: connection to %s:%d successful",
+              conn->address, conn->port);
+  }
+
+  return ret;
+}
+
 /**
  * Launch any configured listener connections of type <b>type</b>.  (A
  * listener is configured if <b>port_option</b> is non-zero.  If any
@@ -2075,7 +2425,7 @@
   }
 
   if (connection_speaks_cells(conn) &&
-      conn->state > OR_CONN_STATE_PROXY_READING) {
+      conn->state > OR_CONN_STATE_PROXY_HANDSHAKING) {
     int pending;
     or_connection_t *or_conn = TO_OR_CONN(conn);
     size_t initial_size;
@@ -2303,7 +2653,7 @@
     : connection_bucket_write_limit(conn, now);
 
   if (connection_speaks_cells(conn) &&
-      conn->state > OR_CONN_STATE_PROXY_READING) {
+      conn->state > OR_CONN_STATE_PROXY_HANDSHAKING) {
     or_connection_t *or_conn = TO_OR_CONN(conn);
     if (conn->state == OR_CONN_STATE_TLS_HANDSHAKING ||
         conn->state == OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING) {
@@ -3033,7 +3383,7 @@
     }
 //    tor_assert(conn->addr && conn->port);
     tor_assert(conn->address);
-    if (conn->state > OR_CONN_STATE_PROXY_READING)
+    if (conn->state > OR_CONN_STATE_PROXY_HANDSHAKING)
       tor_assert(or_conn->tls);
   }
 
Index: doc/tor.1.in
===================================================================
--- doc/tor.1.in	(revision 18118)
+++ doc/tor.1.in	(working copy)
@@ -292,6 +292,25 @@
 patch if you want it to support others.
 .LP
 .TP
+\fBSocks4Proxy\fR \fIhost\fR[:\fIport\fR]\fP
+Tor will make all OR connections through the SOCKS 4 proxy at host:port
+(or host:1080 if port is not specified).
+.LP
+.TP
+\fBSocks5Proxy\fR \fIhost\fR[:\fIport\fR]\fP
+Tor will make all OR connections through the SOCKS 5 proxy at host:port
+(or host:1080 if port is not specified).
+.LP
+.TP
+\fBSocks5ProxyUsername\fR \fIusername\fP
+.LP
+.TP
+\fBSocks5ProxyPassword\fR \fIpassword\fP
+If defined, authenticate to the SOCKS 5 server using username and password
+in accordance to RFC 1929. Both username and password must be between 1 and 255
+characters.
+.LP
+.TP
 \fBKeepalivePeriod \fR\fINUM\fP
 To keep firewalls from expiring connections, send a padding keepalive
 cell every NUM seconds on open connections that are in use. If the


More information about the tor-dev mailing list