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