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