Line data Source code
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 0 : get_environment(void)
43 : {
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 : #endif /* defined(HAVE__NSGETENVIRON) */
52 : }
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 86 : str_num_before(const char *s, char ch)
59 : {
60 86 : const char *cp = strchr(s, ch);
61 86 : if (cp)
62 69 : return cp - s;
63 : else
64 17 : return strlen(s);
65 : }
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 39 : environment_variable_names_equal(const char *s1, const char *s2)
71 : {
72 39 : size_t s1_name_len = str_num_before(s1, '=');
73 39 : size_t s2_name_len = str_num_before(s2, '=');
74 :
75 63 : return (s1_name_len == s2_name_len &&
76 24 : tor_memeq(s1, s2, s1_name_len));
77 : }
78 :
79 : /** Free <b>env</b> (assuming it was produced by
80 : * process_environment_make). */
81 : void
82 3 : process_environment_free_(process_environment_t *env)
83 : {
84 3 : 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 3 : tor_free(env->unixoid_environment_block);
92 3 : tor_free(env->windows_environment_block);
93 :
94 3 : tor_free(env);
95 : }
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 5 : process_environment_make(struct smartlist_t *env_vars)
102 : {
103 5 : process_environment_t *env = tor_malloc_zero(sizeof(process_environment_t));
104 5 : int n_env_vars = smartlist_len(env_vars);
105 5 : int i;
106 5 : size_t total_env_length;
107 5 : smartlist_t *env_vars_sorted;
108 :
109 5 : tor_assert(n_env_vars + 1 != 0);
110 5 : 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 5 : total_env_length = 1; /* terminating NUL of terminating empty string */
115 13 : for (i = 0; i < n_env_vars; ++i) {
116 8 : const char *s = smartlist_get(env_vars, (int)i);
117 8 : size_t slen = strlen(s);
118 :
119 8 : tor_assert(slen + 1 != 0);
120 8 : tor_assert(slen + 1 < SIZE_MAX - total_env_length);
121 8 : total_env_length += slen + 1;
122 : }
123 :
124 5 : 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 5 : env_vars_sorted = smartlist_new();
137 5 : smartlist_add_all(env_vars_sorted, env_vars);
138 5 : smartlist_sort_strings(env_vars_sorted);
139 :
140 : /* Now copy the strings into the environment blocks. */
141 : {
142 5 : char *cp = env->windows_environment_block;
143 5 : const char *prev_env_var = NULL;
144 :
145 13 : for (i = 0; i < n_env_vars; ++i) {
146 8 : const char *s = smartlist_get(env_vars_sorted, (int)i);
147 8 : size_t slen = strlen(s);
148 8 : size_t s_name_len = str_num_before(s, '=');
149 :
150 8 : if (s_name_len == slen) {
151 0 : log_warn(LD_GENERAL,
152 : "Preparing an environment containing a variable "
153 : "without a value: %s",
154 : s);
155 : }
156 12 : if (prev_env_var != NULL &&
157 4 : environment_variable_names_equal(s, prev_env_var)) {
158 0 : log_warn(LD_GENERAL,
159 : "Preparing an environment containing two variables "
160 : "with the same name: %s and %s",
161 : prev_env_var, s);
162 : }
163 :
164 8 : prev_env_var = s;
165 :
166 : /* Actually copy the string into the environment. */
167 8 : memcpy(cp, s, slen+1);
168 8 : env->unixoid_environment_block[i] = cp;
169 8 : cp += slen+1;
170 : }
171 :
172 5 : tor_assert(cp == env->windows_environment_block + total_env_length - 1);
173 : }
174 :
175 5 : smartlist_free(env_vars_sorted);
176 :
177 5 : return env;
178 : }
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 0 : get_current_process_environment_variables(void)
190 : {
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 : }
197 :
198 0 : return sl;
199 : }
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 2 : 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 : {
211 18 : SMARTLIST_FOREACH_BEGIN(env_vars, const char *, s) {
212 16 : if (environment_variable_names_equal(s, new_var)) {
213 1 : SMARTLIST_DEL_CURRENT(env_vars, s);
214 1 : if (free_p) {
215 1 : free_old((void *)s);
216 : }
217 : }
218 16 : } SMARTLIST_FOREACH_END(s);
219 :
220 2 : if (strchr(new_var, '=') != NULL) {
221 2 : smartlist_add(env_vars, (void *)new_var);
222 : }
223 2 : }
|