diff --git a/changes/bug5105 b/changes/bug5105
new file mode 100644
index 0000000000..6a923d9fdd
--- /dev/null
+++ b/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.
+
diff --git a/src/common/util.c b/src/common/util.c
index 52fea2186a..509477ad84 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -172,6 +172,35 @@ _tor_malloc_zero(size_t size DMALLOC_PARAMS)
return result;
}
+/** Allocate a chunk of nmemb*size 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(nmemb,size), 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 ptr to size
* bytes long; return the new memory block. On error, log and
* terminate. (Like realloc(ptr,size), but never returns NULL.)
@@ -3284,11 +3313,7 @@ process_handle_new(void)
*/
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)
{
#ifdef _WIN32
@@ -3305,8 +3330,6 @@ tor_spawn_background(const char *const filename, const char **argv,
SECURITY_ATTRIBUTES saAttr;
char *joined_argv;
- (void)envp; // Unused on Windows
-
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
/* 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()
* work?) */
0, // creation flags
- envp, // use parent's environment
+ (env==NULL) ? NULL : env->windows_environment_block,
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&(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
execvp doesn't define argv as const, even though it
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
execvp(filename, (char *const *) argv);
@@ -3692,6 +3715,162 @@ tor_get_exit_code(const process_handle_t *process_handle,
return PROCESS_EXIT_EXITED;
}
+/** Return non-zero iff getenv would consider s1 and s2
+ * 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 env (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 env_vars (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 env_vars such that
+ * environment_variable_names_equal(s, new_var), remove it; if
+ * free_p is non-zero, call free_old(s). If
+ * new_var contains '=', insert it into env_vars. */
+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
/** Read from a handle h into buf, up to count bytes. If
* hProcess is NULL, the function will return immediately if there is
diff --git a/src/common/util.h b/src/common/util.h
index 1bad24bf72..567efaafef 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -73,6 +73,7 @@
void *_tor_malloc(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_calloc(size_t nmemb, size_t size DMALLOC_PARAMS) ATTR_MALLOC;
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_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_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_realloc(ptr, size) _tor_realloc(ptr, size 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);
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,
-#ifdef _WIN32
- LPVOID envp,
-#else
- const char **envp,
-#endif
+ process_environment_t *env,
process_handle_t **process_handle_out);
#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);
#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
* 0 because tor_check_port_forwarding depends on this being the initial
* statue of the static instance of process_handle_t */
diff --git a/src/or/transports.c b/src/or/transports.c
index 2cd422c1a8..564603e1fe 100644
--- a/src/or/transports.c
+++ b/src/or/transports.c
@@ -91,13 +91,8 @@
#include "util.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);
@@ -287,35 +282,23 @@ launch_managed_proxy(managed_proxy_t *mp)
{
int retval;
+ process_environment_t *env = create_managed_proxy_environment(mp);
+
#ifdef _WIN32
-
- LPVOID envp=NULL;
-
- set_managed_proxy_environment(&envp, mp);
- tor_assert(envp);
-
/* 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);
-
- tor_free(envp);
-
#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
+ process_environment_free(env);
+
if (retval == PROCESS_STATUS_ERROR) {
log_warn(LD_GENERAL, "Managed proxy at '%s' failed at launch.",
mp->argv[0]);
@@ -969,170 +952,80 @@ get_bindaddr_for_server_proxy(const managed_proxy_t *mp)
return bindaddr_result;
}
-#ifdef _WIN32
-
-/** Prepare the environment envp of managed proxy mp.
- * envp 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 mp's
+ * process. */
+static process_environment_t *
+create_managed_proxy_environment(const managed_proxy_t *mp)
{
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();
+ /* 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);
-
- if (mp->is_server) {
- tor_asprintf(&orport_env, "TOR_PT_ORPORT=127.0.0.1:%s",
- options->ORPort->value);
-
- bindaddr_tmp = get_bindaddr_for_server_proxy(mp);
- tor_asprintf(&bindaddr_env, "TOR_PT_SERVER_BINDADDR=%s", bindaddr_tmp);
-
- strlcpy(extended_env, "TOR_PT_EXTENDED_SERVER_PORT=",
- sizeof(extended_env));
-
- smartlist_add(envs, orport_env);
- smartlist_add(envs, extended_env);
- smartlist_add(envs, bindaddr_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);
}
- /* It seems like some versions of Windows need a sorted lpEnvironment
- block. */
- smartlist_sort_strings(envs);
+ smartlist_add(envs, tor_strdup("TOR_PT_MANAGED_TRANSPORT_VER=1"));
- /* An environment block consists of a null-terminated block of
- null-terminated strings: */
+ {
+ char *transports_to_launch =
+ smartlist_join_strings(mp->transports_to_launch, ",", 0, NULL);
- /* Calculate the block's size. */
- SMARTLIST_FOREACH(envs, const char *, s,
- env_size += strlen(s) + 1);
- env_size += 1; /* space for last NUL */
+ smartlist_add_asprintf(envs,
+ mp->is_server ?
+ "TOR_PT_SERVER_TRANSPORTS=%s" :
+ "TOR_PT_CLIENT_TRANSPORTS=%s",
+ transports_to_launch);
- *envp = tor_malloc(env_size);
- tmp = *envp;
+ tor_free(transports_to_launch);
+ }
- /* 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 */
+ if (mp->is_server) {
+ smartlist_add_asprintf(envs, "TOR_PT_ORPORT=127.0.0.1:%s",
+ options->ORPort->value);
- /* 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);
+ {
+ char *bindaddr_tmp = get_bindaddr_for_server_proxy(mp);
+ smartlist_add_asprintf(envs, "TOR_PT_SERVER_BINDADDR=%s", bindaddr_tmp);
+ tor_free(bindaddr_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="));
+ }
+
+ 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);
+
+ env = process_environment_make(merged_env_vars);
smartlist_free(envs);
+
+ SMARTLIST_FOREACH(merged_env_vars, void *, x, tor_free(x));
+ smartlist_free(merged_env_vars);
+
+ return env;
}
-#else /* _WIN32 */
-
-/** Prepare the environment envp of managed proxy mp.
- * envp 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++;
- }
- 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;
-
- state_loc = get_datadir_fname("pt_state/"); /* XXX temp */
- transports_to_launch =
- smartlist_join_strings(mp->transports_to_launch, ",", 0, NULL);
-
- while (*environ_tmp) {
- *tmp = tor_strdup(*environ_tmp);
- tmp++, environ_tmp++;
- }
-
- 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;
-
- tor_free(state_loc);
- tor_free(transports_to_launch);
- tor_free(bindaddr);
-}
-
-#endif /* _WIN32 */
-
/** Create and return a new managed proxy for transport using
* proxy_argv. If is_server is true, it's a server
* managed proxy. */
diff --git a/src/test/test_util.c b/src/test/test_util.c
index 508c15596d..4fabc22ce4 100644
--- a/src/test/test_util.c
+++ b/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
+ * lines. 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) \
{ #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(n_bits_set, 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
};