1 /*
2 * SPDX-License-Identifier: ISC
3 *
4 * Copyright (c) 2010, 2012-2016 Todd C. Miller <Todd.Miller@sudo.ws>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*
20 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22 */
23
24 #include <config.h>
25
26 #include <stdlib.h>
27 #include <string.h>
28 #include <errno.h>
29
30 #include "sudo.h"
31 #include "sudo_plugin.h"
32 #include "sudo_dso.h"
33
34 extern char **environ; /* global environment pointer */
35 static char **priv_environ; /* private environment pointer */
36
37 /*
38 * NOTE: we don't use dlsym() to find the libc getenv()
39 * since this may allocate memory on some systems (glibc)
40 * which leads to a hang if malloc() calls getenv (jemalloc).
41 */
42 char *
getenv_unhooked(const char * name)43 getenv_unhooked(const char *name)
44 {
45 char **ep, *val = NULL;
46 size_t namelen = 0;
47
48 /* For BSD compatibility, treat '=' in name like end of string. */
49 while (name[namelen] != '\0' && name[namelen] != '=')
50 namelen++;
51 for (ep = environ; *ep != NULL; ep++) {
52 if (strncmp(*ep, name, namelen) == 0 && (*ep)[namelen] == '=') {
53 val = *ep + namelen + 1;
54 break;
55 }
56 }
57 return val;
58 }
59
60 sudo_dso_public char *
getenv(const char * name)61 getenv(const char *name)
62 {
63 char *val = NULL;
64
65 switch (process_hooks_getenv(name, &val)) {
66 case SUDO_HOOK_RET_STOP:
67 return val;
68 case SUDO_HOOK_RET_ERROR:
69 return NULL;
70 default:
71 return getenv_unhooked(name);
72 }
73 }
74
75 static int
rpl_putenv(PUTENV_CONST char * string)76 rpl_putenv(PUTENV_CONST char *string)
77 {
78 char **ep;
79 size_t len;
80 bool found = false;
81
82 /* Look for existing entry. */
83 len = (strchr(string, '=') - string) + 1;
84 for (ep = environ; *ep != NULL; ep++) {
85 if (strncmp(string, *ep, len) == 0) {
86 *ep = (char *)string;
87 found = true;
88 break;
89 }
90 }
91 /* Prune out duplicate variables. */
92 if (found) {
93 while (*ep != NULL) {
94 if (strncmp(string, *ep, len) == 0) {
95 char **cur = ep;
96 while ((*cur = *(cur + 1)) != NULL)
97 cur++;
98 } else {
99 ep++;
100 }
101 }
102 }
103
104 /* Append at the end if not already found. */
105 if (!found) {
106 size_t env_len = (size_t)(ep - environ);
107 char **envp = reallocarray(priv_environ, env_len + 2, sizeof(char *));
108 if (envp == NULL)
109 return -1;
110 if (environ != priv_environ)
111 memcpy(envp, environ, env_len * sizeof(char *));
112 envp[env_len++] = (char *)string;
113 envp[env_len] = NULL;
114 priv_environ = environ = envp;
115 }
116 return 0;
117 }
118
119 typedef int (*sudo_fn_putenv_t)(PUTENV_CONST char *);
120
121 static int
putenv_unhooked(PUTENV_CONST char * string)122 putenv_unhooked(PUTENV_CONST char *string)
123 {
124 sudo_fn_putenv_t fn;
125
126 fn = (sudo_fn_putenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "putenv");
127 if (fn != NULL)
128 return fn(string);
129 return rpl_putenv(string);
130 }
131
132 sudo_dso_public int
putenv(PUTENV_CONST char * string)133 putenv(PUTENV_CONST char *string)
134 {
135 switch (process_hooks_putenv((char *)string)) {
136 case SUDO_HOOK_RET_STOP:
137 return 0;
138 case SUDO_HOOK_RET_ERROR:
139 return -1;
140 default:
141 return putenv_unhooked(string);
142 }
143 }
144
145 static int
rpl_setenv(const char * var,const char * val,int overwrite)146 rpl_setenv(const char *var, const char *val, int overwrite)
147 {
148 char *envstr, *dst;
149 const char *src;
150 size_t esize;
151
152 if (!var || *var == '\0') {
153 errno = EINVAL;
154 return -1;
155 }
156
157 /*
158 * POSIX says a var name with '=' is an error but BSD
159 * just ignores the '=' and anything after it.
160 */
161 for (src = var; *src != '\0' && *src != '='; src++)
162 continue;
163 esize = (size_t)(src - var) + 2;
164 if (val) {
165 esize += strlen(val); /* glibc treats a NULL val as "" */
166 }
167
168 /* Allocate and fill in envstr. */
169 if ((envstr = malloc(esize)) == NULL)
170 return -1;
171 for (src = var, dst = envstr; *src != '\0' && *src != '=';)
172 *dst++ = *src++;
173 *dst++ = '=';
174 if (val) {
175 for (src = val; *src != '\0';)
176 *dst++ = *src++;
177 }
178 *dst = '\0';
179
180 if (!overwrite && getenv(var) != NULL) {
181 free(envstr);
182 return 0;
183 }
184 if (rpl_putenv(envstr) == -1) {
185 free(envstr);
186 return -1;
187 }
188 return 0;
189 }
190
191 typedef int (*sudo_fn_setenv_t)(const char *, const char *, int);
192
193 static int
setenv_unhooked(const char * var,const char * val,int overwrite)194 setenv_unhooked(const char *var, const char *val, int overwrite)
195 {
196 sudo_fn_setenv_t fn;
197
198 fn = (sudo_fn_setenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "setenv");
199 if (fn != NULL)
200 return fn(var, val, overwrite);
201 return rpl_setenv(var, val, overwrite);
202 }
203
204 sudo_dso_public int
setenv(const char * var,const char * val,int overwrite)205 setenv(const char *var, const char *val, int overwrite)
206 {
207 switch (process_hooks_setenv(var, val, overwrite)) {
208 case SUDO_HOOK_RET_STOP:
209 return 0;
210 case SUDO_HOOK_RET_ERROR:
211 return -1;
212 default:
213 return setenv_unhooked(var, val, overwrite);
214 }
215 }
216
217 static int
rpl_unsetenv(const char * var)218 rpl_unsetenv(const char *var)
219 {
220 char **ep = environ;
221 size_t len;
222
223 if (var == NULL || *var == '\0' || strchr(var, '=') != NULL) {
224 errno = EINVAL;
225 return -1;
226 }
227
228 len = strlen(var);
229 while (*ep != NULL) {
230 if (strncmp(var, *ep, len) == 0 && (*ep)[len] == '=') {
231 /* Found it; shift remainder + NULL over by one. */
232 char **cur = ep;
233 while ((*cur = *(cur + 1)) != NULL)
234 cur++;
235 /* Keep going, could be multiple instances of the var. */
236 } else {
237 ep++;
238 }
239 }
240 return 0;
241 }
242
243 #ifdef UNSETENV_VOID
244 typedef void (*sudo_fn_unsetenv_t)(const char *);
245 #else
246 typedef int (*sudo_fn_unsetenv_t)(const char *);
247 #endif
248
249 static int
unsetenv_unhooked(const char * var)250 unsetenv_unhooked(const char *var)
251 {
252 int ret = 0;
253 sudo_fn_unsetenv_t fn;
254
255 fn = (sudo_fn_unsetenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "unsetenv");
256 if (fn != NULL) {
257 # ifdef UNSETENV_VOID
258 fn(var);
259 # else
260 ret = fn(var);
261 # endif
262 } else {
263 ret = rpl_unsetenv(var);
264 }
265 return ret;
266 }
267
268 #ifdef UNSETENV_VOID
269 sudo_dso_public void
270 #else
271 sudo_dso_public int
272 #endif
unsetenv(const char * var)273 unsetenv(const char *var)
274 {
275 int ret;
276
277 switch (process_hooks_unsetenv(var)) {
278 case SUDO_HOOK_RET_STOP:
279 ret = 0;
280 break;
281 case SUDO_HOOK_RET_ERROR:
282 ret = -1;
283 break;
284 default:
285 ret = unsetenv_unhooked(var);
286 break;
287 }
288 #ifndef UNSETENV_VOID
289 return ret;
290 #endif
291 }
292