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