[or-cvs] [tor/master] Enable Tor to connect through SOCKS 4/5 proxies

Nick Mathewson nickm at seul.org
Wed Aug 26 15:34:35 UTC 2009


Author: Christopher Davis <chrisd at mangrin.org>
Date: Thu, 18 Jun 2009 16:59:18 -0700
Subject: Enable Tor to connect through SOCKS 4/5 proxies
Commit: 75472c19c3fdcda913eb8117c917ddfd445b2b77

Added a sanity check in config.c and a check in directory.c
directory_initiate_command_rend() to catch any direct connection attempts
when a socks proxy is configured.
---
 doc/tor.1.in           |   19 +++
 src/or/buffers.c       |  171 +++++++++++++++++++++++
 src/or/config.c        |   46 ++++++
 src/or/connection.c    |  360 +++++++++++++++++++++++++++++++++++++++++++++++-
 src/or/connection_or.c |  137 ++++++------------
 src/or/directory.c     |    9 ++
 src/or/or.h            |   51 ++++++--
 7 files changed, 687 insertions(+), 106 deletions(-)

diff --git a/doc/tor.1.in b/doc/tor.1.in
index 3ac0f92..45d3ce7 100644
--- a/doc/tor.1.in
+++ b/doc/tor.1.in
@@ -292,6 +292,25 @@ HTTPS proxy authentication that Tor supports; feel free to submit a
 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
diff --git a/src/or/buffers.c b/src/or/buffers.c
index ed39bc0..31725c5 100644
--- a/src/or/buffers.c
+++ b/src/or/buffers.c
@@ -1611,6 +1611,177 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
   }
 }
 
+/** 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
diff --git a/src/or/config.c b/src/or/config.c
index bcabb67..e1830fa 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -240,6 +240,10 @@ static config_var_t _option_vars[] = {
   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),
@@ -3409,6 +3413,42 @@ options_validate(or_options_t *old_options, or_options_t *options,
       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) {
@@ -3557,6 +3597,12 @@ options_validate(or_options_t *old_options, or_options_t *options,
   if (options->PreferTunneledDirConns && !options->TunnelDirConns)
     REJECT("Must set TunnelDirConns if PreferTunneledDirConns is set.");
 
+  if ((options->Socks4Proxy || options->Socks5Proxy) &&
+      !options->HttpProxy && !options->PreferTunneledDirConns)
+    REJECT("When Socks4Proxy or Socks5Proxy is configured, "
+           "PreferTunneledDirConns and TunnelDirConns must both be "
+           "set to 1, or HttpProxy must be configured.");
+
   if (options->AutomapHostsSuffixes) {
     SMARTLIST_FOREACH(options->AutomapHostsSuffixes, char *, suf,
     {
diff --git a/src/or/connection.c b/src/or/connection.c
index c8406fe..ccfbe19 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -32,6 +32,10 @@ static int connection_process_inbuf(connection_t *conn, int package_partial);
 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 @@ conn_state_to_string(int type, int state)
     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)";
@@ -1289,6 +1292,353 @@ connection_connect(connection_t *conn, const char *address,
   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
@@ -2049,7 +2399,7 @@ connection_read_to_buf(connection_t *conn, int *max_to_read, int *socket_error)
   }
 
   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;
@@ -2277,7 +2627,7 @@ connection_handle_write(connection_t *conn, int force)
     : 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) {
@@ -3003,7 +3353,7 @@ assert_connection_ok(connection_t *conn, time_t now)
     }
 //    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);
   }
 
diff --git a/src/or/connection_or.c b/src/or/connection_or.c
index 54dc1ab..bbfa611 100644
--- a/src/or/connection_or.c
+++ b/src/or/connection_or.c
@@ -187,66 +187,6 @@ connection_or_reached_eof(or_connection_t *conn)
   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 @@ connection_or_read_proxy_response(or_connection_t *or_conn)
 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 @@ connection_or_finished_flushing(or_connection_t *conn)
   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 @@ connection_or_finished_flushing(or_connection_t *conn)
 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 @@ connection_or_finished_connecting(or_connection_t *or_conn)
             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 (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);
+  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;
     }
-    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;
   }
 
@@ -753,6 +699,7 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port,
   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);
@@ -771,19 +718,27 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port,
   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, 1, time(NULL));
       connection_or_connect_failed(conn,
diff --git a/src/or/directory.c b/src/or/directory.c
index 1b972a3..d6457e0 100644
--- a/src/or/directory.c
+++ b/src/or/directory.c
@@ -747,6 +747,15 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr,
 
   log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose));
 
+  /* ensure that we don't make direct connections when a SOCKS server is
+   * configured. */
+  if (!anonymized_connection && !use_begindir && !options->HttpProxy &&
+      (options->Socks4Proxy || options->Socks5Proxy)) {
+    log_warn(LD_DIR, "Cannot connect to a directory server through a "
+                     "SOCKS proxy!");
+    return;
+  }
+
   conn = dir_connection_new(AF_INET);
 
   /* set up conn so it's got all the data we need to remember */
diff --git a/src/or/or.h b/src/or/or.h
index b6ee72d..9305992 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -220,6 +220,21 @@ typedef enum {
 /* !!!! 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)
@@ -240,26 +255,24 @@ typedef enum {
 #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. */
@@ -964,6 +977,9 @@ typedef struct connection_t {
    * 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
@@ -2342,6 +2358,16 @@ typedef struct {
   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. */
@@ -2679,6 +2705,7 @@ int fetch_from_buf_http(buf_t *buf,
                         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);
@@ -2939,6 +2966,10 @@ void connection_expire_held_open(void);
 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);
 
-- 
1.5.6.5




More information about the tor-commits mailing list