/src/tor/src/lib/process/env.c
Line | Count | Source |
1 | | /* Copyright (c) 2003-2004, Roger Dingledine |
2 | | * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. |
3 | | * Copyright (c) 2007-2021, The Tor Project, Inc. */ |
4 | | /* See LICENSE for licensing information */ |
5 | | |
6 | | /** |
7 | | * \file env.c |
8 | | * \brief Inspect and manipulate the environment variables. |
9 | | **/ |
10 | | |
11 | | #include "orconfig.h" |
12 | | #include "lib/process/env.h" |
13 | | |
14 | | #include "lib/malloc/malloc.h" |
15 | | #include "lib/ctime/di_ops.h" |
16 | | #include "lib/container/smartlist.h" |
17 | | #include "lib/log/util_bug.h" |
18 | | #include "lib/log/log.h" |
19 | | |
20 | | #ifdef HAVE_UNISTD_H |
21 | | #include <unistd.h> |
22 | | #endif |
23 | | #include <stdlib.h> |
24 | | #include <string.h> |
25 | | #ifdef HAVE_CRT_EXTERNS_H |
26 | | /* For _NSGetEnviron on macOS */ |
27 | | #include <crt_externs.h> |
28 | | #endif |
29 | | |
30 | | #ifndef HAVE__NSGETENVIRON |
31 | | #ifndef HAVE_EXTERN_ENVIRON_DECLARED |
32 | | /* Some platforms declare environ under some circumstances, others don't. */ |
33 | | #ifndef RUNNING_DOXYGEN |
34 | | extern char **environ; |
35 | | #endif |
36 | | #endif /* !defined(HAVE_EXTERN_ENVIRON_DECLARED) */ |
37 | | #endif /* !defined(HAVE__NSGETENVIRON) */ |
38 | | |
39 | | /** Return the current environment. This is a portable replacement for |
40 | | * 'environ'. */ |
41 | | char ** |
42 | | get_environment(void) |
43 | 0 | { |
44 | | #ifdef HAVE__NSGETENVIRON |
45 | | /* This is for compatibility between OSX versions. Otherwise (for example) |
46 | | * when we do a mostly-static build on OSX 10.7, the resulting binary won't |
47 | | * work on OSX 10.6. */ |
48 | | return *_NSGetEnviron(); |
49 | | #else /* !defined(HAVE__NSGETENVIRON) */ |
50 | 0 | return environ; |
51 | 0 | #endif /* defined(HAVE__NSGETENVIRON) */ |
52 | 0 | } |
53 | | |
54 | | /** Helper: return the number of characters in <b>s</b> preceding the first |
55 | | * occurrence of <b>ch</b>. If <b>ch</b> does not occur in <b>s</b>, return |
56 | | * the length of <b>s</b>. Should be equivalent to strspn(s, "ch"). */ |
57 | | static inline size_t |
58 | | str_num_before(const char *s, char ch) |
59 | 0 | { |
60 | 0 | const char *cp = strchr(s, ch); |
61 | 0 | if (cp) |
62 | 0 | return cp - s; |
63 | 0 | else |
64 | 0 | return strlen(s); |
65 | 0 | } |
66 | | |
67 | | /** Return non-zero iff getenv would consider <b>s1</b> and <b>s2</b> |
68 | | * to have the same name as strings in a process's environment. */ |
69 | | int |
70 | | environment_variable_names_equal(const char *s1, const char *s2) |
71 | 0 | { |
72 | 0 | size_t s1_name_len = str_num_before(s1, '='); |
73 | 0 | size_t s2_name_len = str_num_before(s2, '='); |
74 | |
|
75 | 0 | return (s1_name_len == s2_name_len && |
76 | 0 | tor_memeq(s1, s2, s1_name_len)); |
77 | 0 | } |
78 | | |
79 | | /** Free <b>env</b> (assuming it was produced by |
80 | | * process_environment_make). */ |
81 | | void |
82 | | process_environment_free_(process_environment_t *env) |
83 | 0 | { |
84 | 0 | if (env == NULL) return; |
85 | | |
86 | | /* As both an optimization hack to reduce consing on Unixoid systems |
87 | | * and a nice way to ensure that some otherwise-Windows-specific |
88 | | * code will always get tested before changes to it get merged, the |
89 | | * strings which env->unixoid_environment_block points to are packed |
90 | | * into env->windows_environment_block. */ |
91 | 0 | tor_free(env->unixoid_environment_block); |
92 | 0 | tor_free(env->windows_environment_block); |
93 | |
|
94 | 0 | tor_free(env); |
95 | 0 | } |
96 | | |
97 | | /** Make a process_environment_t containing the environment variables |
98 | | * specified in <b>env_vars</b> (as C strings of the form |
99 | | * "NAME=VALUE"). */ |
100 | | process_environment_t * |
101 | | process_environment_make(struct smartlist_t *env_vars) |
102 | 0 | { |
103 | 0 | process_environment_t *env = tor_malloc_zero(sizeof(process_environment_t)); |
104 | 0 | int n_env_vars = smartlist_len(env_vars); |
105 | 0 | int i; |
106 | 0 | size_t total_env_length; |
107 | 0 | smartlist_t *env_vars_sorted; |
108 | |
|
109 | 0 | tor_assert(n_env_vars + 1 != 0); |
110 | 0 | env->unixoid_environment_block = tor_calloc(n_env_vars + 1, sizeof(char *)); |
111 | | /* env->unixoid_environment_block is already NULL-terminated, |
112 | | * because we assume that NULL == 0 (and check that during compilation). */ |
113 | |
|
114 | 0 | total_env_length = 1; /* terminating NUL of terminating empty string */ |
115 | 0 | for (i = 0; i < n_env_vars; ++i) { |
116 | 0 | const char *s = smartlist_get(env_vars, (int)i); |
117 | 0 | size_t slen = strlen(s); |
118 | |
|
119 | 0 | tor_assert(slen + 1 != 0); |
120 | 0 | tor_assert(slen + 1 < SIZE_MAX - total_env_length); |
121 | 0 | total_env_length += slen + 1; |
122 | 0 | } |
123 | |
|
124 | 0 | env->windows_environment_block = tor_malloc_zero(total_env_length); |
125 | | /* env->windows_environment_block is already |
126 | | * (NUL-terminated-empty-string)-terminated. */ |
127 | | |
128 | | /* Some versions of Windows supposedly require that environment |
129 | | * blocks be sorted. Or maybe some Windows programs (or their |
130 | | * runtime libraries) fail to look up strings in non-sorted |
131 | | * environment blocks. |
132 | | * |
133 | | * Also, sorting strings makes it easy to find duplicate environment |
134 | | * variables and environment-variable strings without an '=' on all |
135 | | * OSes, and they can cause badness. Let's complain about those. */ |
136 | 0 | env_vars_sorted = smartlist_new(); |
137 | 0 | smartlist_add_all(env_vars_sorted, env_vars); |
138 | 0 | smartlist_sort_strings(env_vars_sorted); |
139 | | |
140 | | /* Now copy the strings into the environment blocks. */ |
141 | 0 | { |
142 | 0 | char *cp = env->windows_environment_block; |
143 | 0 | const char *prev_env_var = NULL; |
144 | |
|
145 | 0 | for (i = 0; i < n_env_vars; ++i) { |
146 | 0 | const char *s = smartlist_get(env_vars_sorted, (int)i); |
147 | 0 | size_t slen = strlen(s); |
148 | 0 | size_t s_name_len = str_num_before(s, '='); |
149 | |
|
150 | 0 | if (s_name_len == slen) { |
151 | 0 | log_warn(LD_GENERAL, |
152 | 0 | "Preparing an environment containing a variable " |
153 | 0 | "without a value: %s", |
154 | 0 | s); |
155 | 0 | } |
156 | 0 | if (prev_env_var != NULL && |
157 | 0 | environment_variable_names_equal(s, prev_env_var)) { |
158 | 0 | log_warn(LD_GENERAL, |
159 | 0 | "Preparing an environment containing two variables " |
160 | 0 | "with the same name: %s and %s", |
161 | 0 | prev_env_var, s); |
162 | 0 | } |
163 | |
|
164 | 0 | prev_env_var = s; |
165 | | |
166 | | /* Actually copy the string into the environment. */ |
167 | 0 | memcpy(cp, s, slen+1); |
168 | 0 | env->unixoid_environment_block[i] = cp; |
169 | 0 | cp += slen+1; |
170 | 0 | } |
171 | |
|
172 | 0 | tor_assert(cp == env->windows_environment_block + total_env_length - 1); |
173 | 0 | } |
174 | |
|
175 | 0 | smartlist_free(env_vars_sorted); |
176 | |
|
177 | 0 | return env; |
178 | 0 | } |
179 | | |
180 | | /** Return a newly allocated smartlist containing every variable in |
181 | | * this process's environment, as a NUL-terminated string of the form |
182 | | * "NAME=VALUE". Note that on some/many/most/all OSes, the parent |
183 | | * process can put strings not of that form in our environment; |
184 | | * callers should try to not get crashed by that. |
185 | | * |
186 | | * The returned strings are heap-allocated, and must be freed by the |
187 | | * caller. */ |
188 | | struct smartlist_t * |
189 | | get_current_process_environment_variables(void) |
190 | 0 | { |
191 | 0 | smartlist_t *sl = smartlist_new(); |
192 | |
|
193 | 0 | char **environ_tmp; /* Not const char ** ? Really? */ |
194 | 0 | for (environ_tmp = get_environment(); *environ_tmp; ++environ_tmp) { |
195 | 0 | smartlist_add_strdup(sl, *environ_tmp); |
196 | 0 | } |
197 | |
|
198 | 0 | return sl; |
199 | 0 | } |
200 | | |
201 | | /** For each string s in <b>env_vars</b> such that |
202 | | * environment_variable_names_equal(s, <b>new_var</b>), remove it; if |
203 | | * <b>free_p</b> is non-zero, call <b>free_old</b>(s). If |
204 | | * <b>new_var</b> contains '=', insert it into <b>env_vars</b>. */ |
205 | | void |
206 | | set_environment_variable_in_smartlist(struct smartlist_t *env_vars, |
207 | | const char *new_var, |
208 | | void (*free_old)(void*), |
209 | | int free_p) |
210 | 0 | { |
211 | 0 | SMARTLIST_FOREACH_BEGIN(env_vars, const char *, s) { |
212 | 0 | if (environment_variable_names_equal(s, new_var)) { |
213 | 0 | SMARTLIST_DEL_CURRENT(env_vars, s); |
214 | 0 | if (free_p) { |
215 | 0 | free_old((void *)s); |
216 | 0 | } |
217 | 0 | } |
218 | 0 | } SMARTLIST_FOREACH_END(s); |
219 | |
|
220 | 0 | if (strchr(new_var, '=') != NULL) { |
221 | 0 | smartlist_add(env_vars, (void *)new_var); |
222 | 0 | } |
223 | 0 | } |