update control to reflect move of primary repo to collab-maint
[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_unhooked(const char *name)
74 {
75 #if defined(HAVE_DLOPEN) && defined(RTLD_NEXT)
76     sudo_fn_getenv_t fn;
77
78     fn = (sudo_fn_getenv_t)dlsym(RTLD_NEXT, "getenv");
79     if (fn != NULL)
80         return fn(name);
81 #endif /* HAVE_DLOPEN && RTLD_NEXT */
82     return rpl_getenv(name);
83 }
84
85 char *
86 getenv(const char *name)
87 {
88     char *val = NULL;
89
90     switch (process_hooks_getenv(name, &val)) {
91         case SUDO_HOOK_RET_STOP:
92             return val;
93         case SUDO_HOOK_RET_ERROR:
94             return NULL;
95         default:
96             return getenv_unhooked(name);
97     }
98 }
99
100 static int
101 rpl_putenv(PUTENV_CONST char *string)
102 {
103     char **ep;
104     size_t len;
105     bool found = false;
106
107     /* Look for existing entry. */
108     len = (strchr(string, '=') - string) + 1;
109     for (ep = environ; *ep != NULL; ep++) {
110         if (strncmp(string, *ep, len) == 0) {
111             *ep = (char *)string;
112             found = true;
113             break;
114         }
115     }
116     /* Prune out duplicate variables. */
117     if (found) {
118         while (*ep != NULL) {
119             if (strncmp(string, *ep, len) == 0) {
120                 char **cur = ep;
121                 while ((*cur = *(cur + 1)) != NULL)
122                     cur++;
123             } else {
124                 ep++;
125             }
126         }
127     }
128
129     /* Append at the end if not already found. */
130     if (!found) {
131         size_t env_len = (size_t)(ep - environ);
132         char **envp = erealloc3(priv_environ, env_len + 2, sizeof(char *));
133         if (environ != priv_environ)
134             memcpy(envp, environ, env_len * sizeof(char *));
135         envp[env_len++] = (char *)string;
136         envp[env_len] = NULL;
137         priv_environ = environ = envp;
138     }
139     return 0;
140 }
141
142 typedef int (*sudo_fn_putenv_t)(PUTENV_CONST char *);
143
144 static int
145 putenv_unhooked(PUTENV_CONST char *string)
146 {
147 #if defined(HAVE_DLOPEN) && defined(RTLD_NEXT)
148     sudo_fn_putenv_t fn;
149
150     fn = (sudo_fn_putenv_t)dlsym(RTLD_NEXT, "putenv");
151     if (fn != NULL)
152         return fn(string);
153 #endif /* HAVE_DLOPEN && RTLD_NEXT */
154     return rpl_putenv(string);
155 }
156
157 int
158 putenv(PUTENV_CONST char *string)
159 {
160     switch (process_hooks_putenv((char *)string)) {
161         case SUDO_HOOK_RET_STOP:
162             return 0;
163         case SUDO_HOOK_RET_ERROR:
164             return -1;
165         default:
166             return putenv_unhooked(string);
167     }
168 }
169
170 static int
171 rpl_setenv(const char *var, const char *val, int overwrite)
172 {
173     char *envstr, *dst;
174     const char *src;
175     size_t esize;
176
177     if (!var || *var == '\0') {
178         errno = EINVAL;
179         return -1;
180     }
181
182     /*
183      * POSIX says a var name with '=' is an error but BSD
184      * just ignores the '=' and anything after it.
185      */
186     for (src = var; *src != '\0' && *src != '='; src++)
187         ;
188     esize = (size_t)(src - var) + 2;
189     if (val) {
190         esize += strlen(val);   /* glibc treats a NULL val as "" */
191     }
192
193     /* Allocate and fill in envstr. */
194     if ((envstr = malloc(esize)) == NULL)
195         return -1;
196     for (src = var, dst = envstr; *src != '\0' && *src != '=';)
197         *dst++ = *src++;
198     *dst++ = '=';
199     if (val) {
200         for (src = val; *src != '\0';)
201             *dst++ = *src++;
202     }
203     *dst = '\0';
204
205     if (!overwrite && getenv(var) != NULL) {
206         free(envstr);
207         return 0;
208     }
209     return rpl_putenv(envstr);
210 }
211
212 typedef int (*sudo_fn_setenv_t)(const char *, const char *, int);
213
214 static int
215 setenv_unhooked(const char *var, const char *val, int overwrite)
216 {
217 #if defined(HAVE_SETENV) && defined(HAVE_DLOPEN) && defined(RTLD_NEXT)
218     sudo_fn_setenv_t fn;
219
220     fn = (sudo_fn_setenv_t)dlsym(RTLD_NEXT, "setenv");
221     if (fn != NULL)
222         return fn(var, val, overwrite);
223 #endif /* HAVE_SETENV && HAVE_DLOPEN && RTLD_NEXT */
224     return rpl_setenv(var, val, overwrite);
225 }
226
227 int
228 setenv(const char *var, const char *val, int overwrite)
229 {
230     switch (process_hooks_setenv(var, val, overwrite)) {
231         case SUDO_HOOK_RET_STOP:
232             return 0;
233         case SUDO_HOOK_RET_ERROR:
234             return -1;
235         default:
236             return setenv_unhooked(var, val, overwrite);
237     }
238 }
239
240 static int
241 rpl_unsetenv(const char *var)
242 {
243     char **ep = environ;
244     size_t len;
245
246     if (var == NULL || *var == '\0' || strchr(var, '=') != NULL) {
247         errno = EINVAL;
248         return -1;
249     }
250
251     len = strlen(var);
252     while (*ep != NULL) {
253         if (strncmp(var, *ep, len) == 0 && (*ep)[len] == '=') {
254             /* Found it; shift remainder + NULL over by one. */
255             char **cur = ep;
256             while ((*cur = *(cur + 1)) != NULL)
257                 cur++;
258             /* Keep going, could be multiple instances of the var. */
259         } else {
260             ep++;
261         }
262     }
263     return 0;
264 }
265
266 #ifdef UNSETENV_VOID
267 typedef void (*sudo_fn_unsetenv_t)(const char *);
268 #else
269 typedef int (*sudo_fn_unsetenv_t)(const char *);
270 #endif
271
272 static int
273 unsetenv_unhooked(const char *var)
274 {
275     int rval = 0;
276 #if defined(HAVE_UNSETENV) && defined(HAVE_DLOPEN) && defined(RTLD_NEXT)
277     sudo_fn_unsetenv_t fn;
278
279     fn = (sudo_fn_unsetenv_t)dlsym(RTLD_NEXT, "unsetenv");
280     if (fn != NULL) {
281 # ifdef UNSETENV_VOID
282         fn(var);
283 # else
284         rval = fn(var);
285 # endif
286     } else
287 #endif /* HAVE_UNSETENV && HAVE_DLOPEN && RTLD_NEXT */
288     {
289         rval = rpl_unsetenv(var);
290     }
291     return rval;
292 }
293
294 #ifdef UNSETENV_VOID
295 void
296 #else
297 int
298 #endif
299 unsetenv(const char *var)
300 {
301     int rval;
302
303     switch (process_hooks_unsetenv(var)) {
304         case SUDO_HOOK_RET_STOP:
305             rval = 0;
306             break;
307         case SUDO_HOOK_RET_ERROR:
308             rval = -1;
309             break;
310         default:
311             rval = unsetenv_unhooked(var);
312             break;
313     }
314 #ifndef UNSETENV_VOID
315     return rval;
316 #endif
317 }