Browse Source

Merge remote-tracking branch 'asn/bug3594_rebased_and_fixed'

Conflicts:
	src/common/util.c
	src/or/entrynodes.h
Nick Mathewson 11 years ago
parent
commit
c101ecc8dc
14 changed files with 686 additions and 90 deletions
  1. 3 0
      changes/bug3594
  2. 73 0
      src/common/util.c
  3. 4 0
      src/common/util.h
  4. 126 38
      src/or/config.c
  5. 14 0
      src/or/config.h
  6. 125 32
      src/or/connection.c
  7. 8 0
      src/or/connection.h
  8. 57 15
      src/or/entrynodes.c
  9. 7 4
      src/or/entrynodes.h
  10. 3 1
      src/or/or.h
  11. 52 0
      src/or/transports.c
  12. 4 0
      src/or/transports.h
  13. 150 0
      src/test/test_config.c
  14. 60 0
      src/test/test_util.c

+ 3 - 0
changes/bug3594

@@ -0,0 +1,3 @@
+  o Major bugfixes:
+    - Add support for passing arguments to managed pluggable transport
+      proxies. Implements ticket #3594.

+ 73 - 0
src/common/util.c

@@ -865,6 +865,39 @@ tor_digest_is_zero(const char *digest)
   return tor_memeq(digest, ZERO_DIGEST, DIGEST_LEN);
 }
 
+/** Return true if <b>string</b> is a valid '<key>=[<value>]' string.
+ *  <value> is optional, to indicate the empty string. Log at logging
+ *  <b>severity</b> if something ugly happens. */
+int
+string_is_key_value(int severity, const char *string)
+{
+  /* position of equal sign in string */
+  const char *equal_sign_pos = NULL;
+
+  tor_assert(string);
+
+  if (strlen(string) < 2) { /* "x=" is shortest args string */
+    tor_log(severity, LD_GENERAL, "'%s' is too short to be a k=v value.",
+            escaped(string));
+    return 0;
+  }
+
+  equal_sign_pos = strchr(string, '=');
+  if (!equal_sign_pos) {
+    tor_log(severity, LD_GENERAL, "'%s' is not a k=v value.", escaped(string));
+    return 0;
+  }
+
+  /* validate that the '=' is not in the beginning of the string. */
+  if (equal_sign_pos == string) {
+    tor_log(severity, LD_GENERAL, "'%s' is not a valid k=v value.",
+            escaped(string));
+    return 0;
+  }
+
+  return 1;
+}
+
 /** Return true iff the DIGEST256_LEN bytes in digest are all zero. */
 int
 tor_digest256_is_zero(const char *digest)
@@ -1176,6 +1209,46 @@ escaped(const char *s)
   return escaped_val_;
 }
 
+/** Escape every ";" or "\" character of <b>string</b>. Use
+ *  <b>escape_char</b> as the character to use for escaping.
+ *  The returned string is allocated on the heap and it's the
+ *  responsibility of the caller to free it. */
+char *
+tor_escape_str_for_socks_arg(const char *string)
+{
+  char *new_string = NULL;
+  char *new_cp = NULL;
+  size_t length, new_length;
+  static const char *chars_to_escape = ";\\";
+
+  tor_assert(string);
+
+  length = strlen(string);
+
+  if (!length) /* If we were given the empty string, return the same. */
+    return tor_strdup("");
+  /* (new_length > SIZE_MAX) => ((length * 2) + 1 > SIZE_MAX) =>
+     (length*2 > SIZE_MAX - 1) => (length > (SIZE_MAX - 1)/2) */
+  if (length > (SIZE_MAX - 1)/2) /* check for overflow */
+    return NULL;
+
+  /* this should be enough even if all characters must be escaped */
+  new_length = (length * 2) + 1;
+
+  new_string = new_cp = tor_malloc(new_length);
+
+  while (*string) {
+    if (strchr(chars_to_escape, *string))
+      *new_cp++ = '\\';
+
+    *new_cp++ = *string++;
+  }
+
+  *new_cp = '\0'; /* NUL-terminate the new string */
+
+  return new_string;
+}
+
 /* =====
  * Time
  * ===== */

+ 4 - 0
src/common/util.h

@@ -208,12 +208,16 @@ const char *find_whitespace_eos(const char *s, const char *eos);
 const char *find_str_at_start_of_line(const char *haystack,
                                       const char *needle);
 int string_is_C_identifier(const char *string);
+int string_is_key_value(int severity, const char *string);
 
 int tor_mem_is_zero(const char *mem, size_t len);
 int tor_digest_is_zero(const char *digest);
 int tor_digest256_is_zero(const char *digest);
 char *esc_for_log(const char *string) ATTR_MALLOC;
 const char *escaped(const char *string);
+
+char *tor_escape_str_for_socks_arg(const char *string);
+
 struct smartlist_t;
 int tor_vsscanf(const char *buf, const char *pattern, va_list ap)
 #ifdef __GNUC__

+ 126 - 38
src/or/config.c

@@ -484,7 +484,6 @@ static int options_transition_affects_descriptor(
       const or_options_t *old_options, const or_options_t *new_options);
 static int check_nickname_list(const char *lst, const char *name, char **msg);
 
-static int parse_bridge_line(const char *line, int validate_only);
 static int parse_client_transport_line(const char *line, int validate_only);
 
 static int parse_server_transport_line(const char *line, int validate_only);
@@ -1299,11 +1298,13 @@ options_act(const or_options_t *old_options)
   if (options->Bridges) {
     mark_bridge_list();
     for (cl = options->Bridges; cl; cl = cl->next) {
-      if (parse_bridge_line(cl->value, 0)<0) {
+      bridge_line_t *bridge_line = parse_bridge_line(cl->value);
+      if (!bridge_line) {
         log_warn(LD_BUG,
                  "Previously validated Bridge line could not be added!");
         return -1;
       }
+      bridge_add_from_config(bridge_line);
     }
     sweep_bridge_list();
   }
@@ -2946,14 +2947,14 @@ options_validate(or_options_t *old_options, or_options_t *options,
     size_t len;
 
     len = strlen(options->Socks5ProxyUsername);
-    if (len < 1 || len > 255)
+    if (len < 1 || len > MAX_SOCKS5_AUTH_FIELD_SIZE)
       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)
+    if (len < 1 || len > MAX_SOCKS5_AUTH_FIELD_SIZE)
       REJECT("Socks5ProxyPassword must be between 1 and 255 characters.");
   } else if (options->Socks5ProxyPassword)
     REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
@@ -3037,8 +3038,10 @@ options_validate(or_options_t *old_options, or_options_t *options,
     REJECT("If you set UseBridges, you must set TunnelDirConns.");
 
   for (cl = options->Bridges; cl; cl = cl->next) {
-    if (parse_bridge_line(cl->value, 1)<0)
-      REJECT("Bridge line did not parse. See logs for details.");
+      bridge_line_t *bridge_line = parse_bridge_line(cl->value);
+      if (!bridge_line)
+        REJECT("Bridge line did not parse. See logs for details.");
+      bridge_line_free(bridge_line);
   }
 
   for (cl = options->ClientTransportPlugin; cl; cl = cl->next) {
@@ -4096,21 +4099,72 @@ options_init_logs(or_options_t *options, int validate_only)
   return ok?0:-1;
 }
 
+/** Given a smartlist of SOCKS arguments to be passed to a transport
+ *  proxy in <b>args</b>, validate them and return -1 if they are
+ *  corrupted. Return 0 if they seem OK. */
+static int
+validate_transport_socks_arguments(const smartlist_t *args)
+{
+  char *socks_string = NULL;
+  size_t socks_string_len;
+
+  tor_assert(args);
+  tor_assert(smartlist_len(args) > 0);
+
+  SMARTLIST_FOREACH_BEGIN(args, const char *, s) {
+    if (!string_is_key_value(LOG_WARN, s)) { /* items should be k=v items */
+      log_warn(LD_CONFIG, "'%s' is not a k=v item.", s);
+      return -1;
+    }
+  } SMARTLIST_FOREACH_END(s);
+
+  socks_string = pt_stringify_socks_args(args);
+  if (!socks_string)
+    return -1;
+
+  socks_string_len = strlen(socks_string);
+  tor_free(socks_string);
+
+  if (socks_string_len > MAX_SOCKS5_AUTH_SIZE_TOTAL) {
+    log_warn(LD_CONFIG, "SOCKS arguments can't be more than %u bytes (%lu).",
+             MAX_SOCKS5_AUTH_SIZE_TOTAL,
+             (unsigned long) socks_string_len);
+    return -1;
+  }
+
+  return 0;
+}
+
+/** Deallocate a bridge_line_t structure. */
+/* private */ void
+bridge_line_free(bridge_line_t *bridge_line)
+{
+  if (!bridge_line)
+    return;
+
+  if (bridge_line->socks_args) {
+    SMARTLIST_FOREACH(bridge_line->socks_args, char*, s, tor_free(s));
+    smartlist_free(bridge_line->socks_args);
+  }
+  tor_free(bridge_line->transport_name);
+  tor_free(bridge_line);
+}
+
 /** Read the contents of a Bridge line from <b>line</b>. Return 0
  * if the line is well-formed, and -1 if it isn't. If
  * <b>validate_only</b> is 0, and the line is well-formed, then add
- * the bridge described in the line to our internal bridge list. */
-static int
-parse_bridge_line(const char *line, int validate_only)
+ * the bridge described in the line to our internal bridge list.
+ *
+ * Bridge line format:
+ * Bridge [transport] IP:PORT [id-fingerprint] [k=v] [k=v] ...
+ */
+/* private */ bridge_line_t *
+parse_bridge_line(const char *line)
 {
   smartlist_t *items = NULL;
-  int r;
   char *addrport=NULL, *fingerprint=NULL;
-  char *transport_name=NULL;
-  char *field1=NULL;
-  tor_addr_t addr;
-  uint16_t port = 0;
-  char digest[DIGEST_LEN];
+  char *field=NULL;
+  bridge_line_t *bridge_line = tor_malloc_zero(sizeof(bridge_line_t));
 
   items = smartlist_new();
   smartlist_split_string(items, line, NULL,
@@ -4120,68 +4174,102 @@ parse_bridge_line(const char *line, int validate_only)
     goto err;
   }
 
-  /* field1 is either a transport name or addrport */
-  field1 = smartlist_get(items, 0);
+  /* first field is either a transport name or addrport */
+  field = smartlist_get(items, 0);
   smartlist_del_keeporder(items, 0);
 
-  if (!(strstr(field1, ".") || strstr(field1, ":"))) {
-    /* new-style bridge line */
-    transport_name = field1;
+  if (string_is_C_identifier(field)) {
+    /* It's a transport name. */
+    bridge_line->transport_name = field;
     if (smartlist_len(items) < 1) {
       log_warn(LD_CONFIG, "Too few items to Bridge line.");
       goto err;
     }
-    addrport = smartlist_get(items, 0);
+    addrport = smartlist_get(items, 0); /* Next field is addrport then. */
     smartlist_del_keeporder(items, 0);
   } else {
-    addrport = field1;
+    addrport = field;
   }
 
-  if (tor_addr_port_lookup(addrport, &addr, &port)<0) {
+  /* Parse addrport. */
+  if (tor_addr_port_lookup(addrport,
+                           &bridge_line->addr, &bridge_line->port)<0) {
     log_warn(LD_CONFIG, "Error parsing Bridge address '%s'", addrport);
     goto err;
   }
-  if (!port) {
+  if (!bridge_line->port) {
     log_info(LD_CONFIG,
              "Bridge address '%s' has no port; using default port 443.",
              addrport);
-    port = 443;
+    bridge_line->port = 443;
   }
 
+  /* If transports are enabled, next field could be a fingerprint or a
+     socks argument. If transports are disabled, next field must be
+     a fingerprint. */
   if (smartlist_len(items)) {
-    fingerprint = smartlist_join_strings(items, "", 0, NULL);
+    if (bridge_line->transport_name) { /* transports enabled: */
+      field = smartlist_get(items, 0);
+      smartlist_del_keeporder(items, 0);
+
+      /* If it's a key=value pair, then it's a SOCKS argument for the
+         transport proxy... */
+      if (string_is_key_value(LOG_DEBUG, field)) {
+        bridge_line->socks_args = smartlist_new();
+        smartlist_add(bridge_line->socks_args, field);
+      } else { /* ...otherwise, it's the bridge fingerprint. */
+        fingerprint = field;
+      }
+
+    } else { /* transports disabled: */
+      fingerprint = smartlist_join_strings(items, "", 0, NULL);
+    }
+  }
+
+  /* Handle fingerprint, if it was provided. */
+  if (fingerprint) {
     if (strlen(fingerprint) != HEX_DIGEST_LEN) {
       log_warn(LD_CONFIG, "Key digest for Bridge is wrong length.");
       goto err;
     }
-    if (base16_decode(digest, DIGEST_LEN, fingerprint, HEX_DIGEST_LEN)<0) {
+    if (base16_decode(bridge_line->digest, DIGEST_LEN,
+                      fingerprint, HEX_DIGEST_LEN)<0) {
       log_warn(LD_CONFIG, "Unable to decode Bridge key digest.");
       goto err;
     }
   }
 
-  if (!validate_only) {
-    log_debug(LD_DIR, "Bridge at %s (transport: %s) (%s)",
-              fmt_addrport(&addr, port),
-              transport_name ? transport_name : "no transport",
-              fingerprint ? fingerprint : "no key listed");
-    bridge_add_from_config(&addr, port,
-                           fingerprint ? digest : NULL, transport_name);
+  /* If we are using transports, any remaining items in the smartlist
+     should be k=v values. */
+  if (bridge_line->transport_name && smartlist_len(items)) {
+    if (!bridge_line->socks_args)
+      bridge_line->socks_args = smartlist_new();
+
+    /* append remaining items of 'items' to 'socks_args' */
+    smartlist_add_all(bridge_line->socks_args, items);
+    smartlist_clear(items);
+
+    tor_assert(smartlist_len(bridge_line->socks_args) > 0);
+  }
+
+  if (bridge_line->socks_args) {
+    if (validate_transport_socks_arguments(bridge_line->socks_args) < 0)
+      goto err;
   }
 
-  r = 0;
   goto done;
 
  err:
-  r = -1;
+  bridge_line_free(bridge_line);
+  bridge_line = NULL;
 
  done:
   SMARTLIST_FOREACH(items, char*, s, tor_free(s));
   smartlist_free(items);
   tor_free(addrport);
-  tor_free(transport_name);
   tor_free(fingerprint);
-  return r;
+
+  return bridge_line;
 }
 
 /** Read the contents of a ClientTransportPlugin line from

+ 14 - 0
src/or/config.h

@@ -98,5 +98,19 @@ int addressmap_register_auto(const char *from, const char *to,
                              addressmap_entry_source_t addrmap_source,
                              const char **msg);
 
+/** Represents the information stored in a torrc Bridge line. */
+typedef struct bridge_line_t {
+  tor_addr_t addr; /* The IP address of the bridge. */
+  uint16_t port; /* The TCP port of the bridge. */
+  char *transport_name; /* The name of the pluggable transport that
+                           should be used to connect to the bridge. */
+  char digest[DIGEST_LEN]; /* The bridge's identity key digest. */
+  smartlist_t *socks_args;; /* SOCKS arguments for the pluggable
+                               transport proxy. */
+} bridge_line_t;
+
+void bridge_line_free(bridge_line_t *bridge_line);
+bridge_line_t *parse_bridge_line(const char *line);
+
 #endif
 

+ 125 - 32
src/or/connection.c

@@ -44,6 +44,7 @@
 #include "router.h"
 #include "transports.h"
 #include "routerparse.h"
+#include "transports.h"
 
 #ifdef USE_BUFFEREVENTS
 #include <event2/event.h>
@@ -1574,6 +1575,32 @@ connection_proxy_state_to_string(int state)
   return states[state];
 }
 
+/** Returns the global proxy type used by tor. Use this function for
+ *  logging or high-level purposes, don't use it to fill the
+ *  <b>proxy_type</b> field of or_connection_t; use the actual proxy
+ *  protocol instead.*/
+static int
+get_proxy_type(void)
+{
+  const or_options_t *options = get_options();
+
+  if (options->HTTPSProxy)
+    return PROXY_CONNECT;
+  else if (options->Socks4Proxy)
+    return PROXY_SOCKS4;
+  else if (options->Socks5Proxy)
+    return PROXY_SOCKS5;
+  else if (options->ClientTransportPlugin)
+    return PROXY_PLUGGABLE;
+  else
+    return PROXY_NONE;
+}
+
+/* One byte for the version, one for the command, two for the
+   port, and four for the addr... and, one more for the
+   username NUL: */
+#define SOCKS4_STANDARD_BUFFER_SIZE (1 + 1 + 2 + 4 + 1)
+
 /** 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
@@ -1628,17 +1655,45 @@ connection_proxy_connect(connection_t *conn, int type)
     }
 
     case PROXY_SOCKS4: {
-      unsigned char buf[9];
+      unsigned char *buf;
       uint16_t portn;
       uint32_t ip4addr;
+      size_t buf_size = 0;
+      char *socks_args_string = NULL;
 
-      /* Send a SOCKS4 connect request with empty user id */
+      /* Send a SOCKS4 connect request */
 
       if (tor_addr_family(&conn->addr) != AF_INET) {
         log_warn(LD_NET, "SOCKS4 client is incompatible with IPv6");
         return -1;
       }
 
+      { /* If we are here because we are trying to connect to a
+           pluggable transport proxy, check if we have any SOCKS
+           arguments to transmit. If we do, compress all arguments to
+           a single string in 'socks_args_string': */
+
+        if (get_proxy_type() == PROXY_PLUGGABLE) {
+          socks_args_string =
+            pt_get_socks_args_for_proxy_addrport(&conn->addr, conn->port);
+          if (socks_args_string)
+            log_debug(LD_NET, "Sending out '%s' as our SOCKS argument string.",
+                      socks_args_string);
+        }
+      }
+
+      { /* Figure out the buffer size we need for the SOCKS message: */
+
+        buf_size = SOCKS4_STANDARD_BUFFER_SIZE;
+
+        /* If we have a SOCKS argument string, consider its size when
+           calculating the buffer size: */
+        if (socks_args_string)
+          buf_size += strlen(socks_args_string);
+      }
+
+      buf = tor_malloc_zero(buf_size);
+
       ip4addr = tor_addr_to_ipv4n(&conn->addr);
       portn = htons(conn->port);
 
@@ -1646,9 +1701,23 @@ connection_proxy_connect(connection_t *conn, int type)
       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);
+      /* Next packet field is the userid. If we have pluggable
+         transport SOCKS arguments, we have to embed them
+         there. Otherwise, we use an empty userid.  */
+      if (socks_args_string) { /* place the SOCKS args string: */
+        tor_assert(strlen(socks_args_string) > 0);
+        tor_assert(buf_size >=
+                   SOCKS4_STANDARD_BUFFER_SIZE + strlen(socks_args_string));
+        strlcpy((char *)buf + 8, socks_args_string, buf_size - 8);
+        tor_free(socks_args_string);
+      } else {
+        buf[8] = 0; /* no userid */
+      }
+
+      connection_write_to_buf((char *)buf, buf_size, conn);
+      tor_free(buf);
+
       conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK;
       break;
     }
@@ -1660,8 +1729,13 @@ connection_proxy_connect(connection_t *conn, int type)
 
       buf[0] = 5; /* version */
 
+      /* We have to use SOCKS5 authentication, if we have a
+         Socks5ProxyUsername or if we want to pass arguments to our
+         pluggable transport proxy: */
+      if ((options->Socks5ProxyUsername) ||
+          (get_proxy_type() == PROXY_PLUGGABLE &&
+           (get_socks_args_by_bridge_addrport(&conn->addr, conn->port)))) {
       /* number of auth methods */
-      if (options->Socks5ProxyUsername) {
         buf[1] = 2;
         buf[2] = 0x00; /* no authentication */
         buf[3] = 0x02; /* rfc1929 Username/Passwd auth */
@@ -1855,15 +1929,49 @@ connection_read_proxy_handshake(connection_t *conn)
         unsigned char buf[1024];
         size_t reqsize, usize, psize;
         const char *user, *pass;
+        char *socks_args_string = NULL;
+
+        if (get_proxy_type() == PROXY_PLUGGABLE) {
+          socks_args_string =
+            pt_get_socks_args_for_proxy_addrport(&conn->addr, conn->port);
+          if (!socks_args_string) {
+            log_warn(LD_NET, "Could not create SOCKS args string.");
+            ret = -1;
+            break;
+          }
+
+          log_debug(LD_NET, "SOCKS5 arguments: %s", socks_args_string);
+          tor_assert(strlen(socks_args_string) > 0);
+          tor_assert(strlen(socks_args_string) <= MAX_SOCKS5_AUTH_SIZE_TOTAL);
+
+          if (strlen(socks_args_string) > MAX_SOCKS5_AUTH_FIELD_SIZE) {
+            user = socks_args_string;
+            usize = MAX_SOCKS5_AUTH_FIELD_SIZE;
+            pass = socks_args_string + MAX_SOCKS5_AUTH_FIELD_SIZE;
+            psize = strlen(socks_args_string) - MAX_SOCKS5_AUTH_FIELD_SIZE;
+          } else {
+            user = socks_args_string;
+            usize = strlen(socks_args_string);
+            pass = "\0";
+            psize = 1;
+          }
+        } else if (get_options()->Socks5ProxyUsername) {
+          user = get_options()->Socks5ProxyUsername;
+          pass = get_options()->Socks5ProxyPassword;
+          tor_assert(user && pass);
+          usize = strlen(user);
+          psize = strlen(pass);
+        } else {
+          log_err(LD_BUG, "We entered %s for no reason!", __func__);
+          tor_fragile_assert();
+          ret = -1;
+          break;
+        }
 
-        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);
+        /* Username and password lengths should have been checked
+           above and during torrc parsing. */
+        tor_assert(usize <= MAX_SOCKS5_AUTH_FIELD_SIZE &&
+                   psize <= MAX_SOCKS5_AUTH_FIELD_SIZE);
         reqsize = 3 + usize + psize;
 
         buf[0] = 1; /* negotiation version */
@@ -1872,6 +1980,9 @@ connection_read_proxy_handshake(connection_t *conn)
         buf[2 + usize] = psize;
         memcpy(buf + 3 + usize, pass, psize);
 
+        if (socks_args_string)
+          tor_free(socks_args_string);
+
         connection_write_to_buf((char *)buf, reqsize, conn);
 
         conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK;
@@ -4338,7 +4449,7 @@ get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type,
              options->Bridges) {
     const transport_t *transport = NULL;
     int r;
-    r = find_transport_by_bridge_addrport(&conn->addr, conn->port, &transport);
+    r = get_transport_by_bridge_addrport(&conn->addr, conn->port, &transport);
     if (r<0)
       return -1;
     if (transport) { /* transport found */
@@ -4353,24 +4464,6 @@ get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type,
   return 0;
 }
 
-/** Returns the global proxy type used by tor. */
-static int
-get_proxy_type(void)
-{
-  const or_options_t *options = get_options();
-
-  if (options->HTTPSProxy)
-    return PROXY_CONNECT;
-  else if (options->Socks4Proxy)
-    return PROXY_SOCKS4;
-  else if (options->Socks5Proxy)
-    return PROXY_SOCKS5;
-  else if (options->ClientTransportPlugin)
-    return PROXY_PLUGGABLE;
-  else
-    return PROXY_NONE;
-}
-
 /** Log a failed connection to a proxy server.
  *  <b>conn</b> is the connection we use the proxy server for. */
 void

+ 8 - 0
src/or/connection.h

@@ -89,6 +89,14 @@ int connection_connect(connection_t *conn, const char *address,
                        const tor_addr_t *addr,
                        uint16_t port, int *socket_error);
 
+/** Maximum size of information that we can fit into SOCKS5 username
+    or password fields. */
+#define MAX_SOCKS5_AUTH_FIELD_SIZE 255
+
+/** Total maximum size of information that we can fit into SOCKS5
+    username and password fields. */
+#define MAX_SOCKS5_AUTH_SIZE_TOTAL 2*MAX_SOCKS5_AUTH_FIELD_SIZE
+
 int connection_proxy_connect(connection_t *conn, int type);
 int connection_read_proxy_handshake(connection_t *conn);
 void log_failed_proxy_connection(connection_t *conn);

+ 57 - 15
src/or/entrynodes.c

@@ -53,6 +53,10 @@ typedef struct {
 
   /** When should we next try to fetch a descriptor for this bridge? */
   download_status_t fetch_status;
+
+  /** A smartlist of k=v values to be passed to the SOCKS proxy, if
+      transports are used for this bridge. */
+  smartlist_t *socks_args;
 } bridge_info_t;
 
 /** A list of our chosen entry guards. */
@@ -1513,6 +1517,11 @@ bridge_free(bridge_info_t *bridge)
     return;
 
   tor_free(bridge->transport_name);
+  if (bridge->socks_args) {
+    SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s));
+    smartlist_free(bridge->socks_args);
+  }
+
   tor_free(bridge);
 }
 
@@ -1691,30 +1700,52 @@ bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port,
   } SMARTLIST_FOREACH_END(bridge);
 }
 
-/** Remember a new bridge at <b>addr</b>:<b>port</b>. If <b>digest</b>
- * is set, it tells us the identity key too.  If we already had the
- * bridge in our list, unmark it, and don't actually add anything new.
- * If <b>transport_name</b> is non-NULL - the bridge is associated with a
- * pluggable transport - we assign the transport to the bridge. */
+/** Register the bridge information in <b>bridge_line</b> to the
+ *  bridge subsystem. Steals reference of <b>bridge_line</b>. */
 void
-bridge_add_from_config(const tor_addr_t *addr, uint16_t port,
-                       const char *digest, const char *transport_name)
+bridge_add_from_config(bridge_line_t *bridge_line)
 {
   bridge_info_t *b;
 
-  bridge_resolve_conflicts(addr, port, digest, transport_name);
+  { /* Log the bridge we are about to register: */
+    log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)",
+              fmt_addrport(&bridge_line->addr, bridge_line->port),
+              bridge_line->transport_name ?
+              bridge_line->transport_name : "no transport",
+              tor_digest_is_zero(bridge_line->digest) ?
+              "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN));
+
+    if (bridge_line->socks_args) { /* print socks arguments */
+      int i = 0;
+
+      tor_assert(smartlist_len(bridge_line->socks_args) > 0);
+
+      log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:",
+                smartlist_len(bridge_line->socks_args));
+      SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg,
+                        log_debug(LD_CONFIG, "%d: %s", ++i, arg));
+    }
+  }
+
+  bridge_resolve_conflicts(&bridge_line->addr,
+                           bridge_line->port,
+                           bridge_line->digest,
+                           bridge_line->transport_name);
 
   b = tor_malloc_zero(sizeof(bridge_info_t));
-  tor_addr_copy(&b->addr, addr);
-  b->port = port;
-  if (digest)
-    memcpy(b->identity, digest, DIGEST_LEN);
-  if (transport_name)
-    b->transport_name = tor_strdup(transport_name);
+  tor_addr_copy(&b->addr, &bridge_line->addr);
+  b->port = bridge_line->port;
+  if (bridge_line->digest)
+    memcpy(b->identity, bridge_line->digest, DIGEST_LEN);
+  if (bridge_line->transport_name)
+    b->transport_name = bridge_line->transport_name;
   b->fetch_status.schedule = DL_SCHED_BRIDGE;
+  b->socks_args = bridge_line->socks_args;
   if (!bridge_list)
     bridge_list = smartlist_new();
 
+  tor_free(bridge_line); /* Deallocate bridge_line now. */
+
   smartlist_add(bridge_list, b);
 }
 
@@ -1775,7 +1806,7 @@ find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
  * transport, but the transport could not be found.
  */
 int
-find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
+get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
                                   const transport_t **transport)
 {
   *transport = NULL;
@@ -1802,6 +1833,17 @@ find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
   return 0;
 }
 
+/** Return a smartlist containing all the SOCKS arguments that we
+ *  should pass to the SOCKS proxy. */
+const smartlist_t *
+get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
+{
+  bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr,
+                                                                    port,
+                                                                    NULL);
+  return bridge ? bridge->socks_args : NULL;
+}
+
 /** We need to ask <b>bridge</b> for its server descriptor. */
 static void
 launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge)

+ 7 - 4
src/or/entrynodes.h

@@ -97,9 +97,8 @@ int routerinfo_is_a_configured_bridge(const routerinfo_t *ri);
 int node_is_a_configured_bridge(const node_t *node);
 void learned_router_identity(const tor_addr_t *addr, uint16_t port,
                              const char *digest);
-void bridge_add_from_config(const tor_addr_t *addr, uint16_t port,
-                            const char *digest,
-                            const char *transport_name);
+struct bridge_line_t;
+void bridge_add_from_config(struct bridge_line_t *bridge_line);
 void retry_bridge_descriptor_fetch_directly(const char *digest);
 void fetch_bridge_descriptors(const or_options_t *options, time_t now);
 void learned_bridge_descriptor(routerinfo_t *ri, int from_cache);
@@ -109,13 +108,17 @@ int entries_known_but_down(const or_options_t *options);
 void entries_retry_all(const or_options_t *options);
 
 int any_bridge_supports_microdescriptors(void);
+const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr,
+                                                     uint16_t port);
+
+int any_bridges_dont_support_microdescriptors(void);
 
 void entry_guards_free_all(void);
 
 const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr,
                                                    uint16_t port);
 struct transport_t;
-int find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
+int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
                                       const struct transport_t **transport);
 
 int validate_pluggable_transports_config(void);

+ 3 - 1
src/or/or.h

@@ -238,7 +238,9 @@ typedef enum {
 #define PROXY_SOCKS5 3
 /* !!!! If there is ever a PROXY_* type over 2, we must grow the proxy_type
  * field in or_connection_t */
-/* pluggable transports proxy type */
+
+/* Pluggable transport proxy type. Don't use this in or_connection_t,
+ * instead use the actual underlying proxy type (see above).  */
 #define PROXY_PLUGGABLE 4
 
 /* Proxy client handshake states */

+ 52 - 0
src/or/transports.c

@@ -95,6 +95,7 @@
 #include "util.h"
 #include "router.h"
 #include "statefile.h"
+#include "entrynodes.h"
 
 static process_environment_t *
 create_managed_proxy_environment(const managed_proxy_t *mp);
@@ -1420,6 +1421,57 @@ pt_get_extra_info_descriptor_string(void)
   return the_string;
 }
 
+/** Stringify the SOCKS arguments in <b>socks_args</b> according to
+ *  180_pluggable_transport.txt.  The string is allocated on the heap
+ *  and it's the responsibility of the caller to free it after use. */
+char *
+pt_stringify_socks_args(const smartlist_t *socks_args)
+{
+  /* tmp place to store escaped socks arguments, so that we can
+     concatenate them up afterwards */
+  smartlist_t *sl_tmp = NULL;
+  char *escaped_string = NULL;
+  char *new_string = NULL;
+
+  tor_assert(socks_args);
+  tor_assert(smartlist_len(socks_args) > 0);
+
+  sl_tmp = smartlist_new();
+
+  SMARTLIST_FOREACH_BEGIN(socks_args, const char *, s) {
+    /* Escape ';' and '\'. */
+    escaped_string = tor_escape_str_for_socks_arg(s);
+    if (!escaped_string)
+      goto done;
+
+    smartlist_add(sl_tmp, escaped_string);
+  } SMARTLIST_FOREACH_END(s);
+
+  new_string = smartlist_join_strings(sl_tmp, ";", 0, NULL);
+
+ done:
+  SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s));
+  smartlist_free(sl_tmp);
+
+  return new_string;
+}
+
+/** Return a string of the SOCKS arguments that we should pass to the
+ *  pluggable transports proxy in <b>addr</b>:<b>port</b> according to
+ *  180_pluggable_transport.txt.  The string is allocated on the heap
+ *  and it's the responsibility of the caller to free it after use. */
+char *
+pt_get_socks_args_for_proxy_addrport(const tor_addr_t *addr, uint16_t port)
+{
+  const smartlist_t *socks_args = NULL;
+
+  socks_args = get_socks_args_by_bridge_addrport(addr, port);
+  if (!socks_args)
+    return NULL;
+
+  return pt_stringify_socks_args(socks_args);
+}
+
 /** The tor config was read.
  *  Destroy all managed proxies that were marked by a previous call to
  *  prepare_proxy_list_for_config_read() and are not used by the new

+ 4 - 0
src/or/transports.h

@@ -55,6 +55,10 @@ void pt_prepare_proxy_list_for_config_read(void);
 void sweep_proxy_list(void);
 
 smartlist_t *get_transport_proxy_ports(void);
+char *pt_stringify_socks_args(const smartlist_t *socks_args);
+
+char *pt_get_socks_args_for_proxy_addrport(const tor_addr_t *addr,
+                                            uint16_t port);
 
 #ifdef PT_PRIVATE
 /** State of the managed proxy configuration protocol. */

+ 150 - 0
src/test/test_config.c

@@ -10,6 +10,8 @@
 #include "confparse.h"
 #include "connection_edge.h"
 #include "test.h"
+#include "util.h"
+#include "address.h"
 
 static void
 test_config_addressmap(void *arg)
@@ -169,11 +171,159 @@ test_config_addressmap(void *arg)
   ;
 }
 
+/* Test helper function: Make sure that a bridge line gets parsed
+ * properly. Also make sure that the resulting bridge_line_t structure
+ * has its fields set correctly. */
+static void
+good_bridge_line_test(const char *string, const char *test_addrport,
+                      const char *test_digest, const char *test_transport,
+                      const smartlist_t *test_socks_args)
+{
+  char *tmp = NULL;
+  bridge_line_t *bridge_line = parse_bridge_line(string);
+  test_assert(bridge_line);
+
+  /* test addrport */
+  tmp = tor_strdup(fmt_addrport(&bridge_line->addr, bridge_line->port));
+  test_streq(test_addrport, tmp);
+  tor_free(tmp);
+
+  /* If we were asked to validate a digest, but we did not get a
+     digest after parsing, we failed. */
+  if (test_digest && tor_digest_is_zero(bridge_line->digest))
+    test_assert(0);
+
+  /* If we were not asked to validate a digest, and we got a digest
+     after parsing, we failed again. */
+  if (!test_digest && !tor_digest_is_zero(bridge_line->digest))
+    test_assert(0);
+
+  /* If we were asked to validate a digest, and we got a digest after
+     parsing, make sure it's correct. */
+  if (test_digest) {
+    tmp = tor_strdup(hex_str(bridge_line->digest, DIGEST_LEN));
+    tor_strlower(tmp);
+    test_streq(test_digest, tmp);
+    tor_free(tmp);
+  }
+
+  /* If we were asked to validate a transport name, make sure tha it
+     matches with the transport name that was parsed. */
+  if (test_transport && !bridge_line->transport_name)
+    test_assert(0);
+  if (!test_transport && bridge_line->transport_name)
+    test_assert(0);
+  if (test_transport)
+    test_streq(test_transport, bridge_line->transport_name);
+
+  /* Validate the SOCKS argument smartlist. */
+  if (test_socks_args && !bridge_line->socks_args)
+    test_assert(0);
+  if (!test_socks_args && bridge_line->socks_args)
+    test_assert(0);
+  if (test_socks_args)
+    test_assert(smartlist_strings_eq(test_socks_args,
+                                     bridge_line->socks_args));
+
+ done:
+  tor_free(tmp);
+  bridge_line_free(bridge_line);
+}
+
+/* Test helper function: Make sure that a bridge line is
+ * unparseable. */
+static void
+bad_bridge_line_test(const char *string)
+{
+  bridge_line_t *bridge_line = parse_bridge_line(string);
+  test_assert(!bridge_line);
+
+ done:
+  bridge_line_free(bridge_line);
+}
+
+static void
+test_config_parse_bridge_line(void *arg)
+{
+  (void) arg;
+  good_bridge_line_test("192.0.2.1:4123",
+                        "192.0.2.1:4123", NULL, NULL, NULL);
+
+  good_bridge_line_test("192.0.2.1",
+                        "192.0.2.1:443", NULL, NULL, NULL);
+
+  good_bridge_line_test("transport [::1]",
+                        "[::1]:443", NULL, "transport", NULL);
+
+  good_bridge_line_test("transport 192.0.2.1:12 "
+                        "4352e58420e68f5e40bf7c74faddccd9d1349413",
+                        "192.0.2.1:12",
+                        "4352e58420e68f5e40bf7c74faddccd9d1349413",
+                        "transport", NULL);
+
+  {
+    smartlist_t *sl_tmp = smartlist_new();
+    smartlist_add_asprintf(sl_tmp, "twoandtwo=five");
+
+    good_bridge_line_test("transport 192.0.2.1:12 "
+                    "4352e58420e68f5e40bf7c74faddccd9d1349413 twoandtwo=five",
+                    "192.0.2.1:12", "4352e58420e68f5e40bf7c74faddccd9d1349413",
+                    "transport", sl_tmp);
+
+    SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s));
+    smartlist_free(sl_tmp);
+  }
+
+  {
+    smartlist_t *sl_tmp = smartlist_new();
+    smartlist_add_asprintf(sl_tmp, "twoandtwo=five");
+    smartlist_add_asprintf(sl_tmp, "z=z");
+
+    good_bridge_line_test("transport 192.0.2.1:12 twoandtwo=five z=z",
+                          "192.0.2.1:12", NULL, "transport", sl_tmp);
+
+    SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s));
+    smartlist_free(sl_tmp);
+  }
+
+  good_bridge_line_test("192.0.2.1:1231 "
+                        "4352e58420e68f5e40bf7c74faddccd9d1349413",
+                        "192.0.2.1:1231",
+                        "4352e58420e68f5e40bf7c74faddccd9d1349413",
+                        NULL, NULL);
+
+  /* Empty line */
+  bad_bridge_line_test("");
+  /* bad transport name */
+  bad_bridge_line_test("tr$n_sp0r7 190.20.2.2");
+  /* weird ip address */
+  bad_bridge_line_test("a.b.c.d");
+  /* invalid fpr */
+  bad_bridge_line_test("2.2.2.2:1231 4352e58420e68f5e40bf7c74faddccd9d1349");
+  /* no k=v in the end */
+  bad_bridge_line_test("obfs2 2.2.2.2:1231 "
+                       "4352e58420e68f5e40bf7c74faddccd9d1349413 what");
+  /* no addrport */
+  bad_bridge_line_test("asdw");
+  /* huge k=v value that can't fit in SOCKS fields */
+  bad_bridge_line_test(
+           "obfs2 2.2.2.2:1231 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+           "aa=b");
+}
+
 #define CONFIG_TEST(name, flags)                          \
   { #name, test_config_ ## name, flags, NULL, NULL }
 
 struct testcase_t config_tests[] = {
   CONFIG_TEST(addressmap, 0),
+  CONFIG_TEST(parse_bridge_line, 0),
   END_OF_TESTCASES
 };
 

+ 60 - 0
src/test/test_util.c

@@ -796,6 +796,64 @@ test_util_expand_filename(void)
 }
 #endif
 
+/** Test tor_escape_str_for_socks_arg(). */
+static void
+test_util_escape_string_socks(void)
+{
+  char *escaped_string = NULL;
+
+  /** Simple backslash escape. */
+  escaped_string = tor_escape_str_for_socks_arg("This is a backslash: \\");
+  test_assert(escaped_string);
+  test_streq(escaped_string, "This is a backslash: \\\\");
+  tor_free(escaped_string);
+
+  /** Simple semicolon escape. */
+  escaped_string = tor_escape_str_for_socks_arg("First rule: Do not use ;");
+  test_assert(escaped_string);
+  test_streq(escaped_string, "First rule: Do not use \\;");
+  tor_free(escaped_string);
+
+  /** Empty string. */
+  escaped_string = tor_escape_str_for_socks_arg("");
+  test_assert(escaped_string);
+  test_streq(escaped_string, "");
+  tor_free(escaped_string);
+
+  /** Escape all characters. */
+  escaped_string = tor_escape_str_for_socks_arg(";\\;\\");
+  test_assert(escaped_string);
+  test_streq(escaped_string, "\\;\\\\\\;\\\\");
+  tor_free(escaped_string);
+
+  escaped_string = tor_escape_str_for_socks_arg(";");
+  test_assert(escaped_string);
+  test_streq(escaped_string, "\\;");
+  tor_free(escaped_string);
+
+ done:
+  tor_free(escaped_string);
+}
+
+static void
+test_util_string_is_key_value(void *ptr)
+{
+  (void)ptr;
+  test_assert(string_is_key_value(LOG_WARN, "key=value"));
+  test_assert(string_is_key_value(LOG_WARN, "k=v"));
+  test_assert(string_is_key_value(LOG_WARN, "key="));
+  test_assert(string_is_key_value(LOG_WARN, "x="));
+  test_assert(string_is_key_value(LOG_WARN, "xx="));
+  test_assert(!string_is_key_value(LOG_WARN, "=value"));
+  test_assert(!string_is_key_value(LOG_WARN, "=x"));
+  test_assert(!string_is_key_value(LOG_WARN, "="));
+
+  /* ??? */
+  /* test_assert(!string_is_key_value(LOG_WARN, "===")); */
+ done:
+  ;
+}
+
 /** Test basic string functionality. */
 static void
 test_util_strmisc(void)
@@ -3263,6 +3321,8 @@ struct testcase_t util_tests[] = {
 #ifndef _WIN32
   UTIL_LEGACY(expand_filename),
 #endif
+  UTIL_LEGACY(escape_string_socks),
+  UTIL_LEGACY(string_is_key_value),
   UTIL_LEGACY(strmisc),
   UTIL_LEGACY(pow2),
   UTIL_LEGACY(gzip),