Browse Source

Merge branch 'bug5105-v2-squashed'

Conflicts:
	src/or/transports.c
Nick Mathewson 13 years ago
parent
commit
eaedcba493
5 changed files with 531 additions and 186 deletions
  1. 11 0
      changes/bug5105
  2. 189 10
      src/common/util.c
  3. 25 5
      src/common/util.h
  4. 64 171
      src/or/transports.c
  5. 242 0
      src/test/test_util.c

+ 11 - 0
changes/bug5105

@@ -0,0 +1,11 @@
+  o Minor bugfixes:
+
+    - Ensure that variables set in Tor's environment cannot override
+      environment variables which Tor tries to pass to a managed
+      pluggable-transport proxy.  Previously, Tor would pass every
+      variable in its environment to managed proxies along with the
+      new ones, in such a way that on many operating systems, the
+      inherited environment variables would override those which Tor
+      tried to explicitly set.  Bugfix on 0.2.3.12-alpha for most
+      Unixoid systems; bugfix on 0.2.3.9-alpha for Windows.
+

+ 189 - 10
src/common/util.c

@@ -172,6 +172,35 @@ _tor_malloc_zero(size_t size DMALLOC_PARAMS)
   return result;
   return result;
 }
 }
 
 
+/** Allocate a chunk of <b>nmemb</b>*<b>size</b> bytes of memory, fill
+ * the memory with zero bytes, and return a pointer to the result.
+ * Log and terminate the process on error.  (Same as
+ * calloc(<b>nmemb</b>,<b>size</b>), but never returns NULL.)
+ *
+ * XXXX This implementation probably asserts in cases where it could
+ * work, because it only tries dividing SIZE_MAX by size (according to
+ * the calloc(3) man page, the size of an element of the nmemb-element
+ * array to be allocated), not by nmemb (which could in theory be
+ * smaller than size).  Don't do that then.
+ */
+void *
+_tor_calloc(size_t nmemb, size_t size DMALLOC_PARAMS)
+{
+  /* You may ask yourself, "wouldn't it be smart to use calloc instead of
+   * malloc+memset?  Perhaps libc's calloc knows some nifty optimization trick
+   * we don't!"  Indeed it does, but its optimizations are only a big win when
+   * we're allocating something very big (it knows if it just got the memory
+   * from the OS in a pre-zeroed state).  We don't want to use tor_malloc_zero
+   * for big stuff, so we don't bother with calloc. */
+  void *result;
+  size_t max_nmemb = (size == 0) ? SIZE_MAX : SIZE_MAX/size;
+
+  tor_assert(nmemb < max_nmemb);
+
+  result = _tor_malloc_zero((nmemb * size) DMALLOC_FN_ARGS);
+  return result;
+}
+
 /** Change the size of the memory block pointed to by <b>ptr</b> to <b>size</b>
 /** Change the size of the memory block pointed to by <b>ptr</b> to <b>size</b>
  * bytes long; return the new memory block.  On error, log and
  * bytes long; return the new memory block.  On error, log and
  * terminate. (Like realloc(ptr,size), but never returns NULL.)
  * terminate. (Like realloc(ptr,size), but never returns NULL.)
@@ -3284,11 +3313,7 @@ process_handle_new(void)
  */
  */
 int
 int
 tor_spawn_background(const char *const filename, const char **argv,
 tor_spawn_background(const char *const filename, const char **argv,
-#ifdef _WIN32
-                     LPVOID envp,
-#else
-                     const char **envp,
-#endif
+                     process_environment_t *env,
                      process_handle_t **process_handle_out)
                      process_handle_t **process_handle_out)
 {
 {
 #ifdef _WIN32
 #ifdef _WIN32
@@ -3305,8 +3330,6 @@ tor_spawn_background(const char *const filename, const char **argv,
   SECURITY_ATTRIBUTES saAttr;
   SECURITY_ATTRIBUTES saAttr;
   char *joined_argv;
   char *joined_argv;
 
 
-  (void)envp; // Unused on Windows
-
   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
   saAttr.bInheritHandle = TRUE;
   saAttr.bInheritHandle = TRUE;
   /* TODO: should we set explicit security attributes? (#2046, comment 5) */
   /* TODO: should we set explicit security attributes? (#2046, comment 5) */
@@ -3371,7 +3394,7 @@ tor_spawn_background(const char *const filename, const char **argv,
   /*(TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess()
   /*(TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess()
    * work?) */
    * work?) */
                  0,             // creation flags
                  0,             // creation flags
-                 envp,          // use parent's environment
+                 (env==NULL) ? NULL : env->windows_environment_block,
                  NULL,          // use parent's current directory
                  NULL,          // use parent's current directory
                  &siStartInfo,  // STARTUPINFO pointer
                  &siStartInfo,  // STARTUPINFO pointer
                  &(process_handle->pid));  // receives PROCESS_INFORMATION
                  &(process_handle->pid));  // receives PROCESS_INFORMATION
@@ -3501,8 +3524,8 @@ tor_spawn_background(const char *const filename, const char **argv,
     /* Call the requested program. We need the cast because
     /* Call the requested program. We need the cast because
        execvp doesn't define argv as const, even though it
        execvp doesn't define argv as const, even though it
        does not modify the arguments */
        does not modify the arguments */
-    if (envp)
-      execve(filename, (char *const *) argv, (char*const*)envp);
+    if (env)
+      execve(filename, (char *const *) argv, env->unixoid_environment_block);
     else
     else
       execvp(filename, (char *const *) argv);
       execvp(filename, (char *const *) argv);
 
 
@@ -3692,6 +3715,162 @@ tor_get_exit_code(const process_handle_t *process_handle,
   return PROCESS_EXIT_EXITED;
   return PROCESS_EXIT_EXITED;
 }
 }
 
 
+/** Return non-zero iff getenv would consider <b>s1</b> and <b>s2</b>
+ * to have the same name as strings in a process's environment. */
+int
+environment_variable_names_equal(const char *s1, const char *s2)
+{
+  size_t s1_name_len = strcspn(s1, "=");
+  size_t s2_name_len = strcspn(s2, "=");
+
+  return (s1_name_len == s2_name_len &&
+          tor_memeq(s1, s2, s1_name_len));
+}
+
+/** Free <b>env</b> (assuming it was produced by
+ * process_environment_make). */
+void
+process_environment_free(process_environment_t *env)
+{
+  if (env == NULL) return;
+
+  /* As both an optimization hack to reduce consing on Unixoid systems
+   * and a nice way to ensure that some otherwise-Windows-specific
+   * code will always get tested before changes to it get merged, the
+   * strings which env->unixoid_environment_block points to are packed
+   * into env->windows_environment_block. */
+  tor_free(env->unixoid_environment_block);
+  tor_free(env->windows_environment_block);
+
+  tor_free(env);
+}
+
+/** Make a process_environment_t containing the environment variables
+ * specified in <b>env_vars</b> (as C strings of the form
+ * "NAME=VALUE"). */
+process_environment_t *
+process_environment_make(struct smartlist_t *env_vars)
+{
+  process_environment_t *env = tor_malloc_zero(sizeof(process_environment_t));
+  size_t n_env_vars = smartlist_len(env_vars);
+  size_t i;
+  size_t total_env_length;
+  smartlist_t *env_vars_sorted;
+
+  tor_assert(n_env_vars + 1 != 0);
+  env->unixoid_environment_block = tor_calloc(n_env_vars + 1, sizeof(char *));
+  /* env->unixoid_environment_block is already NULL-terminated,
+   * because we assume that NULL == 0 (and check that during compilation). */
+
+  total_env_length = 1; /* terminating NUL of terminating empty string */
+  for (i = 0; i < n_env_vars; ++i) {
+    const char *s = smartlist_get(env_vars, i);
+    size_t slen = strlen(s);
+
+    tor_assert(slen + 1 != 0);
+    tor_assert(slen + 1 < SIZE_MAX - total_env_length);
+    total_env_length += slen + 1;
+  }
+
+  env->windows_environment_block = tor_malloc_zero(total_env_length);
+  /* env->windows_environment_block is already
+   * (NUL-terminated-empty-string)-terminated. */
+
+  /* Some versions of Windows supposedly require that environment
+   * blocks be sorted.  Or maybe some Windows programs (or their
+   * runtime libraries) fail to look up strings in non-sorted
+   * environment blocks.
+   *
+   * Also, sorting strings makes it easy to find duplicate environment
+   * variables and environment-variable strings without an '=' on all
+   * OSes, and they can cause badness.  Let's complain about those. */
+  env_vars_sorted = smartlist_new();
+  smartlist_add_all(env_vars_sorted, env_vars);
+  smartlist_sort_strings(env_vars_sorted);
+
+  /* Now copy the strings into the environment blocks. */
+  {
+    char *cp = env->windows_environment_block;
+    const char *prev_env_var = NULL;
+
+    for (i = 0; i < n_env_vars; ++i) {
+      const char *s = smartlist_get(env_vars_sorted, i);
+      size_t slen = strlen(s);
+      size_t s_name_len = strcspn(s, "=");
+
+      if (s_name_len == slen) {
+        log_warn(LD_GENERAL,
+                 "Preparing an environment containing a variable "
+                 "without a value: %s",
+                 s);
+      }
+      if (prev_env_var != NULL &&
+          environment_variable_names_equal(s, prev_env_var)) {
+        log_warn(LD_GENERAL,
+                 "Preparing an environment containing two variables "
+                 "with the same name: %s and %s",
+                 prev_env_var, s);
+      }
+
+      prev_env_var = s;
+
+      /* Actually copy the string into the environment. */
+      memcpy(cp, s, slen+1);
+      env->unixoid_environment_block[i] = cp;
+      cp += slen+1;
+    }
+
+    tor_assert(cp == env->windows_environment_block + total_env_length - 1);
+  }
+
+  return env;
+}
+
+/** Return a newly allocated smartlist containing every variable in
+ * this process's environment, as a NUL-terminated string of the form
+ * "NAME=VALUE".  Note that on some/many/most/all OSes, the parent
+ * process can put strings not of that form in our environment;
+ * callers should try to not get crashed by that.
+ *
+ * The returned strings are heap-allocated, and must be freed by the
+ * caller. */
+struct smartlist_t *
+get_current_process_environment_variables(void)
+{
+  smartlist_t *sl = smartlist_new();
+
+  char **environ_tmp; /* Not const char ** ? Really? */
+  for (environ_tmp = environ; *environ_tmp; ++environ_tmp) {
+    smartlist_add(sl, tor_strdup(*environ_tmp));
+  }
+
+  return sl;
+}
+
+/** For each string s in <b>env_vars</b> such that
+ * environment_variable_names_equal(s, <b>new_var</b>), remove it; if
+ * <b>free_p</b> is non-zero, call <b>free_old</b>(s).  If
+ * <b>new_var</b> contains '=', insert it into <b>env_vars</b>. */
+void
+set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
+                                      const char *new_var,
+                                      void (*free_old)(void*),
+                                      int free_p)
+{
+  SMARTLIST_FOREACH_BEGIN(env_vars, const char *, s) {
+    if (environment_variable_names_equal(s, new_var)) {
+      SMARTLIST_DEL_CURRENT(env_vars, s);
+      if (free_p) {
+        free_old((void *)s);
+      }
+    }
+  } SMARTLIST_FOREACH_END(s);
+
+  if (strchr(new_var, '=') != NULL) {
+    smartlist_add(env_vars, (void *)new_var);
+  }
+}
+
 #ifdef _WIN32
 #ifdef _WIN32
 /** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes.  If
 /** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes.  If
  * <b>hProcess</b> is NULL, the function will return immediately if there is
  * <b>hProcess</b> is NULL, the function will return immediately if there is

+ 25 - 5
src/common/util.h

@@ -73,6 +73,7 @@
 void *_tor_malloc(size_t size DMALLOC_PARAMS) ATTR_MALLOC;
 void *_tor_malloc(size_t size DMALLOC_PARAMS) ATTR_MALLOC;
 void *_tor_malloc_zero(size_t size DMALLOC_PARAMS) ATTR_MALLOC;
 void *_tor_malloc_zero(size_t size DMALLOC_PARAMS) ATTR_MALLOC;
 void *_tor_malloc_roundup(size_t *size DMALLOC_PARAMS) ATTR_MALLOC;
 void *_tor_malloc_roundup(size_t *size DMALLOC_PARAMS) ATTR_MALLOC;
+void *_tor_calloc(size_t nmemb, size_t size DMALLOC_PARAMS) ATTR_MALLOC;
 void *_tor_realloc(void *ptr, size_t size DMALLOC_PARAMS);
 void *_tor_realloc(void *ptr, size_t size DMALLOC_PARAMS);
 char *_tor_strdup(const char *s DMALLOC_PARAMS) ATTR_MALLOC ATTR_NONNULL((1));
 char *_tor_strdup(const char *s DMALLOC_PARAMS) ATTR_MALLOC ATTR_NONNULL((1));
 char *_tor_strndup(const char *s, size_t n DMALLOC_PARAMS)
 char *_tor_strndup(const char *s, size_t n DMALLOC_PARAMS)
@@ -107,6 +108,7 @@ extern int dmalloc_free(const char *file, const int line, void *pnt,
 
 
 #define tor_malloc(size)       _tor_malloc(size DMALLOC_ARGS)
 #define tor_malloc(size)       _tor_malloc(size DMALLOC_ARGS)
 #define tor_malloc_zero(size)  _tor_malloc_zero(size DMALLOC_ARGS)
 #define tor_malloc_zero(size)  _tor_malloc_zero(size DMALLOC_ARGS)
+#define tor_calloc(nmemb,size) _tor_calloc(nmemb, size DMALLOC_ARGS)
 #define tor_malloc_roundup(szp) _tor_malloc_roundup(szp DMALLOC_ARGS)
 #define tor_malloc_roundup(szp) _tor_malloc_roundup(szp DMALLOC_ARGS)
 #define tor_realloc(ptr, size) _tor_realloc(ptr, size DMALLOC_ARGS)
 #define tor_realloc(ptr, size) _tor_realloc(ptr, size DMALLOC_ARGS)
 #define tor_strdup(s)          _tor_strdup(s DMALLOC_ARGS)
 #define tor_strdup(s)          _tor_strdup(s DMALLOC_ARGS)
@@ -363,12 +365,9 @@ void tor_check_port_forwarding(const char *filename,
                                int dir_port, int or_port, time_t now);
                                int dir_port, int or_port, time_t now);
 
 
 typedef struct process_handle_t process_handle_t;
 typedef struct process_handle_t process_handle_t;
+typedef struct process_environment_t process_environment_t;
 int tor_spawn_background(const char *const filename, const char **argv,
 int tor_spawn_background(const char *const filename, const char **argv,
-#ifdef _WIN32
-                         LPVOID envp,
-#else
-                         const char **envp,
-#endif
+                         process_environment_t *env,
                          process_handle_t **process_handle_out);
                          process_handle_t **process_handle_out);
 
 
 #define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
 #define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
@@ -377,6 +376,27 @@ int tor_spawn_background(const char *const filename, const char **argv,
 HANDLE load_windows_system_library(const TCHAR *library_name);
 HANDLE load_windows_system_library(const TCHAR *library_name);
 #endif
 #endif
 
 
+int environment_variable_names_equal(const char *s1, const char *s2);
+
+struct process_environment_t {
+  /** A pointer to a sorted empty-string-terminated sequence of
+   * NUL-terminated strings of the form "NAME=VALUE". */
+  char *windows_environment_block;
+  /** A pointer to a NULL-terminated array of pointers to
+   * NUL-terminated strings of the form "NAME=VALUE". */
+  char **unixoid_environment_block;
+};
+
+process_environment_t *process_environment_make(struct smartlist_t *env_vars);
+void process_environment_free(process_environment_t *env);
+
+struct smartlist_t *get_current_process_environment_variables(void);
+
+void set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
+                                           const char *new_var,
+                                           void (*free_old)(void*),
+                                           int free_p);
+
 /* Values of process_handle_t.status. PROCESS_STATUS_NOTRUNNING must be
 /* Values of process_handle_t.status. PROCESS_STATUS_NOTRUNNING must be
  * 0 because tor_check_port_forwarding depends on this being the initial
  * 0 because tor_check_port_forwarding depends on this being the initial
  * statue of the static instance of process_handle_t */
  * statue of the static instance of process_handle_t */

+ 64 - 171
src/or/transports.c

@@ -91,13 +91,8 @@
 #include "util.h"
 #include "util.h"
 #include "router.h"
 #include "router.h"
 
 
-#ifdef _WIN32
-static void set_managed_proxy_environment(LPVOID *envp,
-                                          const managed_proxy_t *mp);
-#else
-static void set_managed_proxy_environment(char ***envp,
-                                         const managed_proxy_t *mp);
-#endif
+static process_environment_t *
+create_managed_proxy_environment(const managed_proxy_t *mp);
 
 
 static INLINE int proxy_configuration_finished(const managed_proxy_t *mp);
 static INLINE int proxy_configuration_finished(const managed_proxy_t *mp);
 
 
@@ -287,35 +282,23 @@ launch_managed_proxy(managed_proxy_t *mp)
 {
 {
   int retval;
   int retval;
 
 
-#ifdef _WIN32
-
-  LPVOID envp=NULL;
-
-  set_managed_proxy_environment(&envp, mp);
-  tor_assert(envp);
+  process_environment_t *env = create_managed_proxy_environment(mp);
 
 
+#ifdef _WIN32
   /* Passing NULL as lpApplicationName makes Windows search for the .exe */
   /* Passing NULL as lpApplicationName makes Windows search for the .exe */
-  retval = tor_spawn_background(NULL, (const char **)mp->argv, envp,
+  retval = tor_spawn_background(NULL,
+                                (const char **)mp->argv,
+                                env,
                                 &mp->process_handle);
                                 &mp->process_handle);
-
-  tor_free(envp);
-
 #else
 #else
-
-  char **envp=NULL;
-
-  /* prepare the environment variables for the managed proxy */
-  set_managed_proxy_environment(&envp, mp);
-  tor_assert(envp);
-
-  retval = tor_spawn_background(mp->argv[0], (const char **)mp->argv,
-                                (const char **)envp, &mp->process_handle);
-
-  /* free the memory allocated by set_managed_proxy_environment(). */
-  free_execve_args(envp);
-
+  retval = tor_spawn_background(mp->argv[0],
+                                (const char **)mp->argv,
+                                env,
+                                &mp->process_handle);
 #endif
 #endif
 
 
+  process_environment_free(env);
+
   if (retval == PROCESS_STATUS_ERROR) {
   if (retval == PROCESS_STATUS_ERROR) {
     log_warn(LD_GENERAL, "Managed proxy at '%s' failed at launch.",
     log_warn(LD_GENERAL, "Managed proxy at '%s' failed at launch.",
              mp->argv[0]);
              mp->argv[0]);
@@ -969,170 +952,80 @@ get_bindaddr_for_server_proxy(const managed_proxy_t *mp)
   return bindaddr_result;
   return bindaddr_result;
 }
 }
 
 
-#ifdef _WIN32
-
-/** Prepare the environment <b>envp</b> of managed proxy <b>mp</b>.
- *  <b>envp</b> is allocated on the heap and should be freed by the
- *  caller after its use. */
-static void
-set_managed_proxy_environment(LPVOID *envp, const managed_proxy_t *mp)
+/** Return a newly allocated process_environment_t * for <b>mp</b>'s
+ * process. */
+static process_environment_t *
+create_managed_proxy_environment(const managed_proxy_t *mp)
 {
 {
   const or_options_t *options = get_options();
   const or_options_t *options = get_options();
 
 
-  char *tmp=NULL;
-  char *state_tmp=NULL;
-  char *state_env=NULL;
-  char *transports_to_launch=NULL;
-  char *transports_env=NULL;
-  char *bindaddr_tmp=NULL;
-  char *bindaddr_env=NULL;
-  char *orport_env=NULL;
-
-  char version_env[31]; /* XXX temp */
-  char extended_env[43]; /* XXX temp */
-
-  int env_size = 0;
-
-  /* A smartlist carrying all the env. variables that the managed
-     proxy should inherit. */
+  /* Environment variables to be added to or set in mp's environment. */
   smartlist_t *envs = smartlist_new();
   smartlist_t *envs = smartlist_new();
+  /* XXXX The next time someone touches this code, shorten the name of
+   * set_environment_variable_in_smartlist, add a
+   * set_env_var_in_smartlist_asprintf function, and get rid of the
+   * silly extra envs smartlist. */
 
 
-  /* Copy the whole environment of the Tor process.
-     It should also copy PATH and HOME of the Tor process.*/
-  char **environ_tmp = environ;
-  while (*environ_tmp)
-    smartlist_add(envs, *environ_tmp++);
+  /* The final environment to be passed to mp. */
+  smartlist_t *merged_env_vars = get_current_process_environment_variables();
 
 
-  /* Create the TOR_PT_* environment variables. */
-  state_tmp = get_datadir_fname("pt_state/"); /* XXX temp */
-  tor_asprintf(&state_env, "TOR_PT_STATE_LOCATION=%s", state_tmp);
+  process_environment_t *env;
 
 
-  strcpy(version_env, "TOR_PT_MANAGED_TRANSPORT_VER=1");
-
-  transports_to_launch =
-    smartlist_join_strings(mp->transports_to_launch, ",", 0, NULL);
-
-  tor_asprintf(&transports_env,
-               mp->is_server ?
-               "TOR_PT_SERVER_TRANSPORTS=%s" : "TOR_PT_CLIENT_TRANSPORTS=%s",
-               transports_to_launch);
-
-  smartlist_add(envs, state_env);
-  smartlist_add(envs, version_env);
-  smartlist_add(envs, transports_env);
+  {
+    char *state_tmp = get_datadir_fname("pt_state/"); /* XXX temp */
+    smartlist_add_asprintf(envs, "TOR_PT_STATE_LOCATION=%s", state_tmp);
+    tor_free(state_tmp);
+  }
 
 
-  if (mp->is_server) {
-    tor_asprintf(&orport_env, "TOR_PT_ORPORT=127.0.0.1:%s",
-                 options->ORPort->value);
+  smartlist_add(envs, tor_strdup("TOR_PT_MANAGED_TRANSPORT_VER=1"));
 
 
-    bindaddr_tmp = get_bindaddr_for_server_proxy(mp);
-    tor_asprintf(&bindaddr_env, "TOR_PT_SERVER_BINDADDR=%s", bindaddr_tmp);
+  {
+    char *transports_to_launch =
+      smartlist_join_strings(mp->transports_to_launch, ",", 0, NULL);
 
 
-    strlcpy(extended_env, "TOR_PT_EXTENDED_SERVER_PORT=",
-            sizeof(extended_env));
+    smartlist_add_asprintf(envs,
+                           mp->is_server ?
+                           "TOR_PT_SERVER_TRANSPORTS=%s" :
+                           "TOR_PT_CLIENT_TRANSPORTS=%s",
+                           transports_to_launch);
 
 
-    smartlist_add(envs, orport_env);
-    smartlist_add(envs, extended_env);
-    smartlist_add(envs, bindaddr_env);
+    tor_free(transports_to_launch);
   }
   }
 
 
-  /* It seems like some versions of Windows need a sorted lpEnvironment
-     block. */
-  smartlist_sort_strings(envs);
-
-  /* An environment block consists of a null-terminated block of
-     null-terminated strings: */
-
-  /* Calculate the block's size. */
-  SMARTLIST_FOREACH(envs, const char *, s,
-                    env_size += strlen(s) + 1);
-  env_size += 1; /* space for last NUL */
-
-  *envp = tor_malloc(env_size);
-  tmp = *envp;
-
-  /* Create the block. */
-  SMARTLIST_FOREACH_BEGIN(envs, const char *, s) {
-    memcpy(tmp, s, strlen(s)); /* copy the env. variable string */
-    tmp += strlen(s);
-    memset(tmp, '\0', 1); /* append NUL at the end of the string */
-    tmp += 1;
-  } SMARTLIST_FOREACH_END(s);
-  memset(tmp, '\0', 1); /* last NUL */
-
-  /* Finally, free the whole mess. */
-  tor_free(state_tmp);
-  tor_free(state_env);
-  tor_free(transports_to_launch);
-  tor_free(transports_env);
-  tor_free(bindaddr_tmp);
-  tor_free(bindaddr_env);
-  tor_free(orport_env);
-
-  smartlist_free(envs);
-}
+  if (mp->is_server) {
+    smartlist_add_asprintf(envs, "TOR_PT_ORPORT=127.0.0.1:%s",
+                           options->ORPort->value);
 
 
-#else /* _WIN32 */
+    {
+      char *bindaddr_tmp = get_bindaddr_for_server_proxy(mp);
+      smartlist_add_asprintf(envs, "TOR_PT_SERVER_BINDADDR=%s", bindaddr_tmp);
+      tor_free(bindaddr_tmp);
+    }
 
 
-/** Prepare the environment <b>envp</b> of managed proxy <b>mp</b>.
- *  <b>envp</b> is allocated on the heap and should be freed by the
- *  caller after its use. */
-static void
-set_managed_proxy_environment(char ***envp, const managed_proxy_t *mp)
-{
-  const or_options_t *options = get_options();
-  char **tmp=NULL;
-  char *state_loc=NULL;
-  char *transports_to_launch=NULL;
-  char *bindaddr=NULL;
-  int environ_size=0;
-  char **environ_tmp = get_environment();
-  char **environ_save = environ_tmp;
-
-  int n_envs = mp->is_server ? ENVIRON_SIZE_SERVER : ENVIRON_SIZE_CLIENT;
-
-  while (*environ_tmp) {
-    environ_size++;
-    environ_tmp++;
+    /* XXX023 Remove the '=' here once versions of obfsproxy which
+     * assert that this env var exists are sufficiently dead.
+     *
+     * (If we remove this line entirely, some joker will stick this
+     * variable in Tor's environment and crash PTs that try to parse
+     * it even when not run in server mode.) */
+    smartlist_add(envs, tor_strdup("TOR_PT_EXTENDED_SERVER_PORT="));
   }
   }
-  environ_tmp = environ_save;
 
 
-  /* allocate enough space for our env. vars and a NULL pointer */
-  *envp = tor_malloc(sizeof(char*)*(environ_size+n_envs+1));
-  tmp = *envp;
+  SMARTLIST_FOREACH_BEGIN(envs, const char *, env_var) {
+    set_environment_variable_in_smartlist(merged_env_vars, env_var,
+                                          _tor_free, 1);
+  } SMARTLIST_FOREACH_END(env_var);
 
 
-  state_loc = get_datadir_fname("pt_state/"); /* XXX temp */
-  transports_to_launch =
-    smartlist_join_strings(mp->transports_to_launch, ",", 0, NULL);
+  env = process_environment_make(merged_env_vars);
 
 
-  while (*environ_tmp) {
-    *tmp = tor_strdup(*environ_tmp);
-    tmp++, environ_tmp++;
-  }
+  smartlist_free(envs);
 
 
-  tor_asprintf(tmp++, "TOR_PT_STATE_LOCATION=%s", state_loc);
-  tor_asprintf(tmp++, "TOR_PT_MANAGED_TRANSPORT_VER=1"); /* temp */
-  if (mp->is_server) {
-    bindaddr = get_bindaddr_for_server_proxy(mp);
-
-    /* XXX temp */
-    tor_asprintf(tmp++, "TOR_PT_ORPORT=127.0.0.1:%d",
-                 router_get_advertised_or_port(options));
-    tor_asprintf(tmp++, "TOR_PT_SERVER_BINDADDR=%s", bindaddr);
-    tor_asprintf(tmp++, "TOR_PT_SERVER_TRANSPORTS=%s", transports_to_launch);
-    tor_asprintf(tmp++, "TOR_PT_EXTENDED_SERVER_PORT=");
-  } else {
-    tor_asprintf(tmp++, "TOR_PT_CLIENT_TRANSPORTS=%s", transports_to_launch);
-  }
-  *tmp = NULL;
+  SMARTLIST_FOREACH(merged_env_vars, void *, x, tor_free(x));
+  smartlist_free(merged_env_vars);
 
 
-  tor_free(state_loc);
-  tor_free(transports_to_launch);
-  tor_free(bindaddr);
+  return env;
 }
 }
 
 
-#endif /* _WIN32 */
-
 /** Create and return a new managed proxy for <b>transport</b> using
 /** Create and return a new managed proxy for <b>transport</b> using
  *  <b>proxy_argv</b>. If <b>is_server</b> is true, it's a server
  *  <b>proxy_argv</b>. If <b>is_server</b> is true, it's a server
  *  managed proxy. */
  *  managed proxy. */

+ 242 - 0
src/test/test_util.c

@@ -1856,6 +1856,245 @@ test_util_eat_whitespace(void *ptr)
   ;
   ;
 }
 }
 
 
+/** Return a newly allocated smartlist containing the lines of text in
+ * <b>lines</b>.  The returned strings are heap-allocated, and must be
+ * freed by the caller.
+ *
+ * XXXX? Move to container.[hc] ? */
+static smartlist_t *
+smartlist_new_from_text_lines(const char *lines)
+{
+  smartlist_t *sl = smartlist_new();
+  char *last_line;
+
+  smartlist_split_string(sl, lines, "\n", 0, 0);
+
+  last_line = smartlist_pop_last(sl);
+  if (last_line != NULL && *last_line != '\0') {
+    smartlist_add(sl, last_line);
+  }
+
+  return sl;
+}
+
+/** Test smartlist_new_from_text_lines */
+static void
+test_util_sl_new_from_text_lines(void *ptr)
+{
+  (void)ptr;
+
+  { /* Normal usage */
+    smartlist_t *sl = smartlist_new_from_text_lines("foo\nbar\nbaz\n");
+    int sl_len = smartlist_len(sl);
+
+    tt_want_int_op(sl_len, ==, 3);
+
+    if (sl_len > 0) tt_want_str_op(smartlist_get(sl, 0), ==, "foo");
+    if (sl_len > 1) tt_want_str_op(smartlist_get(sl, 1), ==, "bar");
+    if (sl_len > 2) tt_want_str_op(smartlist_get(sl, 2), ==, "baz");
+
+    SMARTLIST_FOREACH(sl, void *, x, tor_free(x));
+    smartlist_free(sl);
+  }
+
+  { /* No final newline */
+    smartlist_t *sl = smartlist_new_from_text_lines("foo\nbar\nbaz");
+    int sl_len = smartlist_len(sl);
+
+    tt_want_int_op(sl_len, ==, 3);
+
+    if (sl_len > 0) tt_want_str_op(smartlist_get(sl, 0), ==, "foo");
+    if (sl_len > 1) tt_want_str_op(smartlist_get(sl, 1), ==, "bar");
+    if (sl_len > 2) tt_want_str_op(smartlist_get(sl, 2), ==, "baz");
+
+    SMARTLIST_FOREACH(sl, void *, x, tor_free(x));
+    smartlist_free(sl);
+  }
+
+  { /* No newlines */
+    smartlist_t *sl = smartlist_new_from_text_lines("foo");
+    int sl_len = smartlist_len(sl);
+
+    tt_want_int_op(sl_len, ==, 1);
+
+    if (sl_len > 0) tt_want_str_op(smartlist_get(sl, 0), ==, "foo");
+
+    SMARTLIST_FOREACH(sl, void *, x, tor_free(x));
+    smartlist_free(sl);
+  }
+
+  { /* No text at all */
+    smartlist_t *sl = smartlist_new_from_text_lines("");
+    int sl_len = smartlist_len(sl);
+
+    tt_want_int_op(sl_len, ==, 0);
+
+    SMARTLIST_FOREACH(sl, void *, x, tor_free(x));
+    smartlist_free(sl);
+  }
+}
+
+/** Test process_environment_make */
+static void
+test_util_make_environment(void *ptr)
+{
+  const char *env_vars_string =
+    "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/bin\n"
+    "HOME=/home/foozer\n";
+  const char expected_windows_env_block[] =
+    "HOME=/home/foozer\000"
+    "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/bin\000"
+    "\000";
+  size_t expected_windows_env_block_len = sizeof(expected_windows_env_block);
+
+  smartlist_t *env_vars = smartlist_new_from_text_lines(env_vars_string);
+  smartlist_t *env_vars_sorted = smartlist_new();
+  smartlist_t *env_vars_in_unixoid_env_block_sorted = smartlist_new();
+
+  process_environment_t *env;
+
+  (void)ptr;
+
+  env = process_environment_make(env_vars);
+
+  /* Check that the Windows environment block is correct. */
+  tt_want(tor_memeq(expected_windows_env_block, env->windows_environment_block,
+                    expected_windows_env_block_len));
+
+  /* Now for the Unixoid environment block.  We don't care which order
+   * these environment variables are in, so we sort both lists first. */
+
+  smartlist_add_all(env_vars_sorted, env_vars);
+
+  {
+    char **v;
+    for (v = env->unixoid_environment_block; *v; ++v) {
+      smartlist_add(env_vars_in_unixoid_env_block_sorted, *v);
+    }
+  }
+
+  smartlist_sort_strings(env_vars_sorted);
+  smartlist_sort_strings(env_vars_in_unixoid_env_block_sorted);
+
+  tt_want_int_op(smartlist_len(env_vars_sorted), ==,
+                 smartlist_len(env_vars_in_unixoid_env_block_sorted));
+  {
+    int len = smartlist_len(env_vars_sorted);
+    int i;
+
+    if (smartlist_len(env_vars_in_unixoid_env_block_sorted) < len) {
+      len = smartlist_len(env_vars_in_unixoid_env_block_sorted);
+    }
+
+    for (i = 0; i < len; ++i) {
+      tt_want_str_op(smartlist_get(env_vars_sorted, i), ==,
+                     smartlist_get(env_vars_in_unixoid_env_block_sorted, i));
+    }
+  }
+
+  /* Clean up. */
+  smartlist_free(env_vars_in_unixoid_env_block_sorted);
+  smartlist_free(env_vars_sorted);
+
+  SMARTLIST_FOREACH(env_vars, char *, x, tor_free(x));
+  smartlist_free(env_vars);
+
+  process_environment_free(env);
+}
+
+/** Test set_environment_variable_in_smartlist */
+static void
+test_util_set_env_var_in_sl(void *ptr)
+{
+  /* The environment variables in these strings are in arbitrary
+   * order; we sort the resulting lists before comparing them.
+   *
+   * (They *will not* end up in the order shown in
+   * expected_resulting_env_vars_string.) */
+
+  const char *base_env_vars_string =
+    "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/bin\n"
+    "HOME=/home/foozer\n"
+    "TERM=xterm\n"
+    "SHELL=/bin/ksh\n"
+    "USER=foozer\n"
+    "LOGNAME=foozer\n"
+    "USERNAME=foozer\n"
+    "LANG=en_US.utf8\n"
+    ;
+
+  const char *new_env_vars_string =
+    "TERM=putty\n"
+    "DISPLAY=:18.0\n"
+    ;
+
+  const char *expected_resulting_env_vars_string =
+    "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/bin\n"
+    "HOME=/home/foozer\n"
+    "TERM=putty\n"
+    "SHELL=/bin/ksh\n"
+    "USER=foozer\n"
+    "LOGNAME=foozer\n"
+    "USERNAME=foozer\n"
+    "LANG=en_US.utf8\n"
+    "DISPLAY=:18.0\n"
+    ;
+
+  smartlist_t *merged_env_vars =
+    smartlist_new_from_text_lines(base_env_vars_string);
+  smartlist_t *new_env_vars =
+    smartlist_new_from_text_lines(new_env_vars_string);
+  smartlist_t *expected_resulting_env_vars =
+    smartlist_new_from_text_lines(expected_resulting_env_vars_string);
+
+  /* Elements of merged_env_vars are heap-allocated, and must be
+   * freed.  Some of them are (or should) be freed by
+   * set_environment_variable_in_smartlist.
+   *
+   * Elements of new_env_vars are heap-allocated, but are copied into
+   * merged_env_vars, so they are not freed separately at the end of
+   * the function.
+   *
+   * Elements of expected_resulting_env_vars are heap-allocated, and
+   * must be freed. */
+
+  (void)ptr;
+
+  SMARTLIST_FOREACH(new_env_vars, char *, env_var,
+                    set_environment_variable_in_smartlist(merged_env_vars,
+                                                          env_var,
+                                                          _tor_free,
+                                                          1));
+
+  smartlist_sort_strings(merged_env_vars);
+  smartlist_sort_strings(expected_resulting_env_vars);
+
+  tt_want_int_op(smartlist_len(merged_env_vars), ==,
+                 smartlist_len(expected_resulting_env_vars));
+  {
+    int len = smartlist_len(merged_env_vars);
+    int i;
+
+    if (smartlist_len(expected_resulting_env_vars) < len) {
+      len = smartlist_len(expected_resulting_env_vars);
+    }
+
+    for (i = 0; i < len; ++i) {
+      tt_want_str_op(smartlist_get(merged_env_vars, i), ==,
+                     smartlist_get(expected_resulting_env_vars, i));
+    }
+  }
+
+  /* Clean up. */
+  SMARTLIST_FOREACH(merged_env_vars, char *, x, tor_free(x));
+  smartlist_free(merged_env_vars);
+
+  smartlist_free(new_env_vars);
+
+  SMARTLIST_FOREACH(expected_resulting_env_vars, char *, x, tor_free(x));
+  smartlist_free(expected_resulting_env_vars);
+}
+
 #define UTIL_LEGACY(name)                                               \
 #define UTIL_LEGACY(name)                                               \
   { #name, legacy_test_helper, 0, &legacy_setup, test_util_ ## name }
   { #name, legacy_test_helper, 0, &legacy_setup, test_util_ ## name }
 
 
@@ -1895,6 +2134,9 @@ struct testcase_t util_tests[] = {
   UTIL_TEST(split_lines, 0),
   UTIL_TEST(split_lines, 0),
   UTIL_TEST(n_bits_set, 0),
   UTIL_TEST(n_bits_set, 0),
   UTIL_TEST(eat_whitespace, 0),
   UTIL_TEST(eat_whitespace, 0),
+  UTIL_TEST(sl_new_from_text_lines, 0),
+  UTIL_TEST(make_environment, 0),
+  UTIL_TEST(set_env_var_in_sl, 0),
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };