]> granicus.if.org Git - sudo/blob - src/env_hooks.c
Add SPDX-License-Identifier to files.
[sudo] / src / env_hooks.c
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 <sys/types.h>
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #ifdef HAVE_STRING_H
31 # include <string.h>
32 #endif /* HAVE_STRING_H */
33 #ifdef HAVE_STRINGS_H
34 # include <strings.h>
35 #endif /* HAVE_STRINGS_H */
36 #include <errno.h>
37
38 #include "sudo.h"
39 #include "sudo_plugin.h"
40 #include "sudo_dso.h"
41
42 extern char **environ;          /* global environment pointer */
43 static char **priv_environ;     /* private environment pointer */
44
45 /*
46  * NOTE: we don't use dlsym() to find the libc getenv()
47  *       since this may allocate memory on some systems (glibc)
48  *       which leads to a hang if malloc() calls getenv (jemalloc).
49  */
50 char *
51 getenv_unhooked(const char *name)
52 {
53     char **ep, *val = NULL;
54     size_t namelen = 0;
55
56     /* For BSD compatibility, treat '=' in name like end of string. */
57     while (name[namelen] != '\0' && name[namelen] != '=')
58         namelen++;
59     for (ep = environ; *ep != NULL; ep++) {
60         if (strncmp(*ep, name, namelen) == 0 && (*ep)[namelen] == '=') {
61             val = *ep + namelen + 1;
62             break;
63         }
64     }
65     return val;
66 }
67
68 __dso_public char *
69 getenv(const char *name)
70 {
71     char *val = NULL;
72
73     switch (process_hooks_getenv(name, &val)) {
74         case SUDO_HOOK_RET_STOP:
75             return val;
76         case SUDO_HOOK_RET_ERROR:
77             return NULL;
78         default:
79             return getenv_unhooked(name);
80     }
81 }
82
83 static int
84 rpl_putenv(PUTENV_CONST char *string)
85 {
86     char **ep;
87     size_t len;
88     bool found = false;
89
90     /* Look for existing entry. */
91     len = (strchr(string, '=') - string) + 1;
92     for (ep = environ; *ep != NULL; ep++) {
93         if (strncmp(string, *ep, len) == 0) {
94             *ep = (char *)string;
95             found = true;
96             break;
97         }
98     }
99     /* Prune out duplicate variables. */
100     if (found) {
101         while (*ep != NULL) {
102             if (strncmp(string, *ep, len) == 0) {
103                 char **cur = ep;
104                 while ((*cur = *(cur + 1)) != NULL)
105                     cur++;
106             } else {
107                 ep++;
108             }
109         }
110     }
111
112     /* Append at the end if not already found. */
113     if (!found) {
114         size_t env_len = (size_t)(ep - environ);
115         char **envp = reallocarray(priv_environ, env_len + 2, sizeof(char *));
116         if (envp == NULL)
117             return -1;
118         if (environ != priv_environ)
119             memcpy(envp, environ, env_len * sizeof(char *));
120         envp[env_len++] = (char *)string;
121         envp[env_len] = NULL;
122         priv_environ = environ = envp;
123     }
124     return 0;
125 }
126
127 typedef int (*sudo_fn_putenv_t)(PUTENV_CONST char *);
128
129 static int
130 putenv_unhooked(PUTENV_CONST char *string)
131 {
132     sudo_fn_putenv_t fn;
133
134     fn = (sudo_fn_putenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "putenv");
135     if (fn != NULL)
136         return fn(string);
137     return rpl_putenv(string);
138 }
139
140 __dso_public int
141 putenv(PUTENV_CONST char *string)
142 {
143     switch (process_hooks_putenv((char *)string)) {
144         case SUDO_HOOK_RET_STOP:
145             return 0;
146         case SUDO_HOOK_RET_ERROR:
147             return -1;
148         default:
149             return putenv_unhooked(string);
150     }
151 }
152
153 static int
154 rpl_setenv(const char *var, const char *val, int overwrite)
155 {
156     char *envstr, *dst;
157     const char *src;
158     size_t esize;
159
160     if (!var || *var == '\0') {
161         errno = EINVAL;
162         return -1;
163     }
164
165     /*
166      * POSIX says a var name with '=' is an error but BSD
167      * just ignores the '=' and anything after it.
168      */
169     for (src = var; *src != '\0' && *src != '='; src++)
170         continue;
171     esize = (size_t)(src - var) + 2;
172     if (val) {
173         esize += strlen(val);   /* glibc treats a NULL val as "" */
174     }
175
176     /* Allocate and fill in envstr. */
177     if ((envstr = malloc(esize)) == NULL)
178         return -1;
179     for (src = var, dst = envstr; *src != '\0' && *src != '=';)
180         *dst++ = *src++;
181     *dst++ = '=';
182     if (val) {
183         for (src = val; *src != '\0';)
184             *dst++ = *src++;
185     }
186     *dst = '\0';
187
188     if (!overwrite && getenv(var) != NULL) {
189         free(envstr);
190         return 0;
191     }
192     if (rpl_putenv(envstr) == -1) {
193         free(envstr);
194         return -1;
195     }
196     return 0;
197 }
198
199 typedef int (*sudo_fn_setenv_t)(const char *, const char *, int);
200
201 static int
202 setenv_unhooked(const char *var, const char *val, int overwrite)
203 {
204     sudo_fn_setenv_t fn;
205
206     fn = (sudo_fn_setenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "setenv");
207     if (fn != NULL)
208         return fn(var, val, overwrite);
209     return rpl_setenv(var, val, overwrite);
210 }
211
212 __dso_public int
213 setenv(const char *var, const char *val, int overwrite)
214 {
215     switch (process_hooks_setenv(var, val, overwrite)) {
216         case SUDO_HOOK_RET_STOP:
217             return 0;
218         case SUDO_HOOK_RET_ERROR:
219             return -1;
220         default:
221             return setenv_unhooked(var, val, overwrite);
222     }
223 }
224
225 static int
226 rpl_unsetenv(const char *var)
227 {
228     char **ep = environ;
229     size_t len;
230
231     if (var == NULL || *var == '\0' || strchr(var, '=') != NULL) {
232         errno = EINVAL;
233         return -1;
234     }
235
236     len = strlen(var);
237     while (*ep != NULL) {
238         if (strncmp(var, *ep, len) == 0 && (*ep)[len] == '=') {
239             /* Found it; shift remainder + NULL over by one. */
240             char **cur = ep;
241             while ((*cur = *(cur + 1)) != NULL)
242                 cur++;
243             /* Keep going, could be multiple instances of the var. */
244         } else {
245             ep++;
246         }
247     }
248     return 0;
249 }
250
251 #ifdef UNSETENV_VOID
252 typedef void (*sudo_fn_unsetenv_t)(const char *);
253 #else
254 typedef int (*sudo_fn_unsetenv_t)(const char *);
255 #endif
256
257 static int
258 unsetenv_unhooked(const char *var)
259 {
260     int ret = 0;
261     sudo_fn_unsetenv_t fn;
262
263     fn = (sudo_fn_unsetenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "unsetenv");
264     if (fn != NULL) {
265 # ifdef UNSETENV_VOID
266         fn(var);
267 # else
268         ret = fn(var);
269 # endif
270     } else {
271         ret = rpl_unsetenv(var);
272     }
273     return ret;
274 }
275
276 #ifdef UNSETENV_VOID
277 __dso_public void
278 #else
279 __dso_public int
280 #endif
281 unsetenv(const char *var)
282 {
283     int ret;
284
285     switch (process_hooks_unsetenv(var)) {
286         case SUDO_HOOK_RET_STOP:
287             ret = 0;
288             break;
289         case SUDO_HOOK_RET_ERROR:
290             ret = -1;
291             break;
292         default:
293             ret = unsetenv_unhooked(var);
294             break;
295     }
296 #ifndef UNSETENV_VOID
297     return ret;
298 #endif
299 }