1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2007-2009 Sean C. Farley <scf@FreeBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer,
12 *    without modification, immediately at the beginning of the file.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32
33#include "namespace.h"
34#include <sys/types.h>
35#include <errno.h>
36#include <stdbool.h>
37#include <stddef.h>
38#include <stdlib.h>
39#include <string.h>
40#include <unistd.h>
41#include "un-namespace.h"
42
43
44static const char CorruptEnvFindMsg[] = "environment corrupt; unable to find ";
45static const char CorruptEnvValueMsg[] =
46    "environment corrupt; missing value for ";
47
48
49/*
50 * Standard environ.  environ variable is exposed to entire process.
51 *
52 *	origEnviron:	Upon cleanup on unloading of library or failure, this
53 *			allows environ to return to as it was before.
54 *	environSize:	Number of variables environ can hold.  Can only
55 *			increase.
56 *	intEnviron:	Internally-built environ.  Exposed via environ during
57 *			(re)builds of the environment.
58 */
59extern char **environ;
60static char **origEnviron;
61static char **intEnviron = NULL;
62static int environSize = 0;
63
64/*
65 * Array of environment variables built from environ.  Each element records:
66 *	name:		Pointer to name=value string
67 *	name length:	Length of name not counting '=' character
68 *	value:		Pointer to value within same string as name
69 *	value size:	Size (not length) of space for value not counting the
70 *			nul character
71 *	active state:	true/false value to signify whether variable is active.
72 *			Useful since multiple variables with the same name can
73 *			co-exist.  At most, one variable can be active at any
74 *			one time.
75 *	putenv:		Created from putenv() call.  This memory must not be
76 *			reused.
77 */
78static struct envVars {
79	size_t nameLen;
80	size_t valueSize;
81	char *name;
82	char *value;
83	bool active;
84	bool putenv;
85} *envVars = NULL;
86
87/*
88 * Environment array information.
89 *
90 *	envActive:	Number of active variables in array.
91 *	envVarsSize:	Size of array.
92 *	envVarsTotal:	Number of total variables in array (active or not).
93 */
94static int envActive = 0;
95static int envVarsSize = 0;
96static int envVarsTotal = 0;
97
98
99/* Deinitialization of new environment. */
100static void __attribute__ ((destructor)) __clean_env_destructor(void);
101
102
103/*
104 * A simple version of warnx() to avoid the bloat of including stdio in static
105 * binaries.
106 */
107static void
108__env_warnx(const char *msg, const char *name, size_t nameLen)
109{
110	static const char nl[] = "\n";
111	static const char progSep[] = ": ";
112
113	_write(STDERR_FILENO, _getprogname(), strlen(_getprogname()));
114	_write(STDERR_FILENO, progSep, sizeof(progSep) - 1);
115	_write(STDERR_FILENO, msg, strlen(msg));
116	_write(STDERR_FILENO, name, nameLen);
117	_write(STDERR_FILENO, nl, sizeof(nl) - 1);
118
119	return;
120}
121
122
123/*
124 * Inline strlen() for performance.  Also, perform check for an equals sign.
125 * Cheaper here than peforming a strchr() later.
126 */
127static inline size_t
128__strleneq(const char *str)
129{
130	const char *s;
131
132	for (s = str; *s != '\0'; ++s)
133		if (*s == '=')
134			return (0);
135
136	return (s - str);
137}
138
139
140/*
141 * Comparison of an environment name=value to a name.
142 */
143static inline bool
144strncmpeq(const char *nameValue, const char *name, size_t nameLen)
145{
146	if (strncmp(nameValue, name, nameLen) == 0 && nameValue[nameLen] == '=')
147		return (true);
148
149	return (false);
150}
151
152
153/*
154 * Using environment, returns pointer to value associated with name, if any,
155 * else NULL.  If the onlyActive flag is set to true, only variables that are
156 * active are returned else all are.
157 */
158static inline char *
159__findenv(const char *name, size_t nameLen, int *envNdx, bool onlyActive)
160{
161	int ndx;
162
163	/*
164	 * Find environment variable from end of array (more likely to be
165	 * active).  A variable created by putenv is always active, or it is not
166	 * tracked in the array.
167	 */
168	for (ndx = *envNdx; ndx >= 0; ndx--)
169		if (envVars[ndx].putenv) {
170			if (strncmpeq(envVars[ndx].name, name, nameLen)) {
171				*envNdx = ndx;
172				return (envVars[ndx].name + nameLen +
173				    sizeof ("=") - 1);
174			}
175		} else if ((!onlyActive || envVars[ndx].active) &&
176		    (envVars[ndx].nameLen == nameLen &&
177		    strncmpeq(envVars[ndx].name, name, nameLen))) {
178			*envNdx = ndx;
179			return (envVars[ndx].value);
180		}
181
182	return (NULL);
183}
184
185
186/*
187 * Using environ, returns pointer to value associated with name, if any, else
188 * NULL.  Used on the original environ passed into the program.
189 */
190static char *
191__findenv_environ(const char *name, size_t nameLen)
192{
193	int envNdx;
194
195	/* Find variable within environ. */
196	for (envNdx = 0; environ[envNdx] != NULL; envNdx++)
197		if (strncmpeq(environ[envNdx], name, nameLen))
198			return (&(environ[envNdx][nameLen + sizeof("=") - 1]));
199
200	return (NULL);
201}
202
203
204/*
205 * Remove variable added by putenv() from variable tracking array.
206 */
207static void
208__remove_putenv(int envNdx)
209{
210	envVarsTotal--;
211	if (envVarsTotal > envNdx)
212		memmove(&(envVars[envNdx]), &(envVars[envNdx + 1]),
213		    (envVarsTotal - envNdx) * sizeof (*envVars));
214	memset(&(envVars[envVarsTotal]), 0, sizeof (*envVars));
215
216	return;
217}
218
219
220/*
221 * Deallocate the environment built from environ as well as environ then set
222 * both to NULL.  Eases debugging of memory leaks.
223 */
224static void
225__clean_env(bool freeVars)
226{
227	int envNdx;
228
229	/* Deallocate environment and environ if created by *env(). */
230	if (envVars != NULL) {
231		for (envNdx = envVarsTotal - 1; envNdx >= 0; envNdx--)
232			/* Free variables or deactivate them. */
233			if (envVars[envNdx].putenv) {
234				if (!freeVars)
235					__remove_putenv(envNdx);
236			} else {
237				if (freeVars)
238					free(envVars[envNdx].name);
239				else
240					envVars[envNdx].active = false;
241			}
242		if (freeVars) {
243			free(envVars);
244			envVars = NULL;
245		} else
246			envActive = 0;
247
248		/* Restore original environ if it has not updated by program. */
249		if (origEnviron != NULL) {
250			if (environ == intEnviron)
251				environ = origEnviron;
252			free(intEnviron);
253			intEnviron = NULL;
254			environSize = 0;
255		}
256	}
257
258	return;
259}
260
261
262/*
263 * Using the environment, rebuild the environ array for use by other C library
264 * calls that depend upon it.
265 */
266static int
267__rebuild_environ(int newEnvironSize)
268{
269	char **tmpEnviron;
270	int envNdx;
271	int environNdx;
272	int tmpEnvironSize;
273
274	/* Resize environ. */
275	if (newEnvironSize > environSize) {
276		tmpEnvironSize = newEnvironSize * 2;
277		tmpEnviron = reallocarray(intEnviron, tmpEnvironSize + 1,
278		    sizeof(*intEnviron));
279		if (tmpEnviron == NULL)
280			return (-1);
281		environSize = tmpEnvironSize;
282		intEnviron = tmpEnviron;
283	}
284	envActive = newEnvironSize;
285
286	/* Assign active variables to environ. */
287	for (envNdx = envVarsTotal - 1, environNdx = 0; envNdx >= 0; envNdx--)
288		if (envVars[envNdx].active)
289			intEnviron[environNdx++] = envVars[envNdx].name;
290	intEnviron[environNdx] = NULL;
291
292	/* Always set environ which may have been replaced by program. */
293	environ = intEnviron;
294
295	return (0);
296}
297
298
299/*
300 * Enlarge new environment.
301 */
302static inline bool
303__enlarge_env(void)
304{
305	int newEnvVarsSize;
306	struct envVars *tmpEnvVars;
307
308	envVarsTotal++;
309	if (envVarsTotal > envVarsSize) {
310		newEnvVarsSize = envVarsTotal * 2;
311		tmpEnvVars = reallocarray(envVars, newEnvVarsSize,
312		    sizeof(*envVars));
313		if (tmpEnvVars == NULL) {
314			envVarsTotal--;
315			return (false);
316		}
317		envVarsSize = newEnvVarsSize;
318		envVars = tmpEnvVars;
319	}
320
321	return (true);
322}
323
324
325/*
326 * Using environ, build an environment for use by standard C library calls.
327 */
328static int
329__build_env(void)
330{
331	char **env;
332	int activeNdx;
333	int envNdx;
334	int savedErrno;
335	size_t nameLen;
336
337	/* Check for non-existant environment. */
338	if (environ == NULL || environ[0] == NULL)
339		return (0);
340
341	/* Count environment variables. */
342	for (env = environ, envVarsTotal = 0; *env != NULL; env++)
343		envVarsTotal++;
344	envVarsSize = envVarsTotal * 2;
345
346	/* Create new environment. */
347	envVars = calloc(envVarsSize, sizeof(*envVars));
348	if (envVars == NULL)
349		goto Failure;
350
351	/* Copy environ values and keep track of them. */
352	for (envNdx = envVarsTotal - 1; envNdx >= 0; envNdx--) {
353		envVars[envNdx].putenv = false;
354		envVars[envNdx].name =
355		    strdup(environ[envVarsTotal - envNdx - 1]);
356		if (envVars[envNdx].name == NULL)
357			goto Failure;
358		envVars[envNdx].value = strchr(envVars[envNdx].name, '=');
359		if (envVars[envNdx].value != NULL) {
360			envVars[envNdx].value++;
361			envVars[envNdx].valueSize =
362			    strlen(envVars[envNdx].value);
363		} else {
364			__env_warnx(CorruptEnvValueMsg, envVars[envNdx].name,
365			    strlen(envVars[envNdx].name));
366			errno = EFAULT;
367			goto Failure;
368		}
369
370		/*
371		 * Find most current version of variable to make active.  This
372		 * will prevent multiple active variables from being created
373		 * during this initialization phase.
374		 */
375		nameLen = envVars[envNdx].value - envVars[envNdx].name - 1;
376		envVars[envNdx].nameLen = nameLen;
377		activeNdx = envVarsTotal - 1;
378		if (__findenv(envVars[envNdx].name, nameLen, &activeNdx,
379		    false) == NULL) {
380			__env_warnx(CorruptEnvFindMsg, envVars[envNdx].name,
381			    nameLen);
382			errno = EFAULT;
383			goto Failure;
384		}
385		envVars[activeNdx].active = true;
386	}
387
388	/* Create a new environ. */
389	origEnviron = environ;
390	environ = NULL;
391	if (__rebuild_environ(envVarsTotal) == 0)
392		return (0);
393
394Failure:
395	savedErrno = errno;
396	__clean_env(true);
397	errno = savedErrno;
398
399	return (-1);
400}
401
402
403/*
404 * Destructor function with default argument to __clean_env().
405 */
406static void
407__clean_env_destructor(void)
408{
409	__clean_env(true);
410
411	return;
412}
413
414
415/*
416 * Returns the value of a variable or NULL if none are found.
417 */
418char *
419getenv(const char *name)
420{
421	int envNdx;
422	size_t nameLen;
423
424	/* Check for malformed name. */
425	if (name == NULL || (nameLen = __strleneq(name)) == 0) {
426		errno = EINVAL;
427		return (NULL);
428	}
429
430	/*
431	 * Variable search order:
432	 * 1. Check for an empty environ.  This allows an application to clear
433	 *    the environment.
434	 * 2. Search the external environ array.
435	 * 3. Search the internal environment.
436	 *
437	 * Since malloc() depends upon getenv(), getenv() must never cause the
438	 * internal environment storage to be generated.
439	 */
440	if (environ == NULL || environ[0] == NULL)
441		return (NULL);
442	else if (envVars == NULL || environ != intEnviron)
443		return (__findenv_environ(name, nameLen));
444	else {
445		envNdx = envVarsTotal - 1;
446		return (__findenv(name, nameLen, &envNdx, true));
447	}
448}
449
450
451/*
452 * Set the value of a variable.  Older settings are labeled as inactive.  If an
453 * older setting has enough room to store the new value, it will be reused.  No
454 * previous variables are ever freed here to avoid causing a segmentation fault
455 * in a user's code.
456 *
457 * The variables nameLen and valueLen are passed into here to allow the caller
458 * to calculate the length by means besides just strlen().
459 */
460static int
461__setenv(const char *name, size_t nameLen, const char *value, int overwrite)
462{
463	bool reuse;
464	char *env;
465	int envNdx;
466	int newEnvActive;
467	size_t valueLen;
468
469	/* Find existing environment variable large enough to use. */
470	envNdx = envVarsTotal - 1;
471	newEnvActive = envActive;
472	valueLen = strlen(value);
473	reuse = false;
474	if (__findenv(name, nameLen, &envNdx, false) != NULL) {
475		/* Deactivate entry if overwrite is allowed. */
476		if (envVars[envNdx].active) {
477			if (overwrite == 0)
478				return (0);
479			envVars[envNdx].active = false;
480			newEnvActive--;
481		}
482
483		/* putenv() created variable cannot be reused. */
484		if (envVars[envNdx].putenv)
485			__remove_putenv(envNdx);
486
487		/* Entry is large enough to reuse. */
488		else if (envVars[envNdx].valueSize >= valueLen)
489			reuse = true;
490	}
491
492	/* Create new variable if none was found of sufficient size. */
493	if (! reuse) {
494		/* Enlarge environment. */
495		envNdx = envVarsTotal;
496		if (!__enlarge_env())
497			return (-1);
498
499		/* Create environment entry. */
500		envVars[envNdx].name = malloc(nameLen + sizeof ("=") +
501		    valueLen);
502		if (envVars[envNdx].name == NULL) {
503			envVarsTotal--;
504			return (-1);
505		}
506		envVars[envNdx].nameLen = nameLen;
507		envVars[envNdx].valueSize = valueLen;
508
509		/* Save name of name/value pair. */
510		env = stpncpy(envVars[envNdx].name, name, nameLen);
511		*env++ = '=';
512	}
513	else
514		env = envVars[envNdx].value;
515
516	/* Save value of name/value pair. */
517	strcpy(env, value);
518	envVars[envNdx].value = env;
519	envVars[envNdx].active = true;
520	newEnvActive++;
521
522	/* No need to rebuild environ if an active variable was reused. */
523	if (reuse && newEnvActive == envActive)
524		return (0);
525	else
526		return (__rebuild_environ(newEnvActive));
527}
528
529
530/*
531 * If the program attempts to replace the array of environment variables
532 * (environ) environ or sets the first varible to NULL, then deactivate all
533 * variables and merge in the new list from environ.
534 */
535static int
536__merge_environ(void)
537{
538	char **env;
539	char *equals;
540
541	/*
542	 * Internally-built environ has been replaced or cleared (detected by
543	 * using the count of active variables against a NULL as the first value
544	 * in environ).  Clean up everything.
545	 */
546	if (intEnviron != NULL && (environ != intEnviron || (envActive > 0 &&
547	    environ[0] == NULL))) {
548		/* Deactivate all environment variables. */
549		if (envActive > 0) {
550			origEnviron = NULL;
551			__clean_env(false);
552		}
553
554		/*
555		 * Insert new environ into existing, yet deactivated,
556		 * environment array.
557		 */
558		origEnviron = environ;
559		if (origEnviron != NULL)
560			for (env = origEnviron; *env != NULL; env++) {
561				if ((equals = strchr(*env, '=')) == NULL) {
562					__env_warnx(CorruptEnvValueMsg, *env,
563					    strlen(*env));
564					errno = EFAULT;
565					return (-1);
566				}
567				if (__setenv(*env, equals - *env, equals + 1,
568				    1) == -1)
569					return (-1);
570			}
571	}
572
573	return (0);
574}
575
576
577/*
578 * The exposed setenv() that peforms a few tests before calling the function
579 * (__setenv()) that does the actual work of inserting a variable into the
580 * environment.
581 */
582int
583setenv(const char *name, const char *value, int overwrite)
584{
585	size_t nameLen;
586
587	/* Check for malformed name. */
588	if (name == NULL || (nameLen = __strleneq(name)) == 0) {
589		errno = EINVAL;
590		return (-1);
591	}
592
593	/* Initialize environment. */
594	if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1))
595		return (-1);
596
597	return (__setenv(name, nameLen, value, overwrite));
598}
599
600
601/*
602 * Insert a "name=value" string into the environment.  Special settings must be
603 * made to keep setenv() from reusing this memory block and unsetenv() from
604 * allowing it to be tracked.
605 */
606int
607putenv(char *string)
608{
609	char *equals;
610	int envNdx;
611	int newEnvActive;
612	size_t nameLen;
613
614	/* Check for malformed argument. */
615	if (string == NULL || (equals = strchr(string, '=')) == NULL ||
616	    (nameLen = equals - string) == 0) {
617		errno = EINVAL;
618		return (-1);
619	}
620
621	/* Initialize environment. */
622	if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1))
623		return (-1);
624
625	/* Deactivate previous environment variable. */
626	envNdx = envVarsTotal - 1;
627	newEnvActive = envActive;
628	if (__findenv(string, nameLen, &envNdx, true) != NULL) {
629		/* Reuse previous putenv slot. */
630		if (envVars[envNdx].putenv) {
631			envVars[envNdx].name = string;
632			return (__rebuild_environ(envActive));
633		} else {
634			newEnvActive--;
635			envVars[envNdx].active = false;
636		}
637	}
638
639	/* Enlarge environment. */
640	envNdx = envVarsTotal;
641	if (!__enlarge_env())
642		return (-1);
643
644	/* Create environment entry. */
645	envVars[envNdx].name = string;
646	envVars[envNdx].nameLen = -1;
647	envVars[envNdx].value = NULL;
648	envVars[envNdx].valueSize = -1;
649	envVars[envNdx].putenv = true;
650	envVars[envNdx].active = true;
651	newEnvActive++;
652
653	return (__rebuild_environ(newEnvActive));
654}
655
656
657/*
658 * Unset variable with the same name by flagging it as inactive.  No variable is
659 * ever freed.
660 */
661int
662unsetenv(const char *name)
663{
664	int envNdx;
665	size_t nameLen;
666	int newEnvActive;
667
668	/* Check for malformed name. */
669	if (name == NULL || (nameLen = __strleneq(name)) == 0) {
670		errno = EINVAL;
671		return (-1);
672	}
673
674	/* Initialize environment. */
675	if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1))
676		return (-1);
677
678	/* Deactivate specified variable. */
679	/* Remove all occurrences. */
680	envNdx = envVarsTotal - 1;
681	newEnvActive = envActive;
682	while (__findenv(name, nameLen, &envNdx, true) != NULL) {
683		envVars[envNdx].active = false;
684		if (envVars[envNdx].putenv)
685			__remove_putenv(envNdx);
686		envNdx--;
687		newEnvActive--;
688	}
689	if (newEnvActive != envActive)
690		__rebuild_environ(newEnvActive);
691
692	return (0);
693}
694