diff options
Diffstat (limited to 'misc.c')
-rw-r--r-- | misc.c | 486 |
1 files changed, 468 insertions, 18 deletions
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: misc.c,v 1.109 2017/03/14 00:55:37 dtucker Exp $ */ | 1 | /* $OpenBSD: misc.c,v 1.113 2017/08/18 05:48:04 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
4 | * Copyright (c) 2005,2006 Damien Miller. All rights reserved. | 4 | * Copyright (c) 2005,2006 Damien Miller. All rights reserved. |
@@ -29,10 +29,16 @@ | |||
29 | #include <sys/types.h> | 29 | #include <sys/types.h> |
30 | #include <sys/ioctl.h> | 30 | #include <sys/ioctl.h> |
31 | #include <sys/socket.h> | 31 | #include <sys/socket.h> |
32 | #include <sys/stat.h> | ||
32 | #include <sys/time.h> | 33 | #include <sys/time.h> |
34 | #include <sys/wait.h> | ||
33 | #include <sys/un.h> | 35 | #include <sys/un.h> |
34 | 36 | ||
35 | #include <limits.h> | 37 | #include <limits.h> |
38 | #ifdef HAVE_LIBGEN_H | ||
39 | # include <libgen.h> | ||
40 | #endif | ||
41 | #include <signal.h> | ||
36 | #include <stdarg.h> | 42 | #include <stdarg.h> |
37 | #include <stdio.h> | 43 | #include <stdio.h> |
38 | #include <stdlib.h> | 44 | #include <stdlib.h> |
@@ -62,6 +68,9 @@ | |||
62 | #include "misc.h" | 68 | #include "misc.h" |
63 | #include "log.h" | 69 | #include "log.h" |
64 | #include "ssh.h" | 70 | #include "ssh.h" |
71 | #include "sshbuf.h" | ||
72 | #include "ssherr.h" | ||
73 | #include "uidswap.h" | ||
65 | #include "platform.h" | 74 | #include "platform.h" |
66 | 75 | ||
67 | /* remove newline at end of string */ | 76 | /* remove newline at end of string */ |
@@ -541,7 +550,7 @@ addargs(arglist *args, char *fmt, ...) | |||
541 | } else if (args->num+2 >= nalloc) | 550 | } else if (args->num+2 >= nalloc) |
542 | nalloc *= 2; | 551 | nalloc *= 2; |
543 | 552 | ||
544 | args->list = xreallocarray(args->list, nalloc, sizeof(char *)); | 553 | args->list = xrecallocarray(args->list, args->nalloc, nalloc, sizeof(char *)); |
545 | args->nalloc = nalloc; | 554 | args->nalloc = nalloc; |
546 | args->list[args->num++] = cp; | 555 | args->list[args->num++] = cp; |
547 | args->list[args->num] = NULL; | 556 | args->list[args->num] = NULL; |
@@ -715,22 +724,6 @@ read_keyfile_line(FILE *f, const char *filename, char *buf, size_t bufsz, | |||
715 | return -1; | 724 | return -1; |
716 | } | 725 | } |
717 | 726 | ||
718 | /* | ||
719 | * return 1 if the specified uid is a uid that may own a system directory | ||
720 | * otherwise 0. | ||
721 | */ | ||
722 | int | ||
723 | platform_sys_dir_uid(uid_t uid) | ||
724 | { | ||
725 | if (uid == 0) | ||
726 | return 1; | ||
727 | #ifdef PLATFORM_SYS_DIR_UID | ||
728 | if (uid == PLATFORM_SYS_DIR_UID) | ||
729 | return 1; | ||
730 | #endif | ||
731 | return 0; | ||
732 | } | ||
733 | |||
734 | int | 727 | int |
735 | secure_permissions(struct stat *st, uid_t uid) | 728 | secure_permissions(struct stat *st, uid_t uid) |
736 | { | 729 | { |
@@ -1152,6 +1145,7 @@ static const struct { | |||
1152 | const char *name; | 1145 | const char *name; |
1153 | int value; | 1146 | int value; |
1154 | } ipqos[] = { | 1147 | } ipqos[] = { |
1148 | { "none", INT_MAX }, /* can't use 0 here; that's CS0 */ | ||
1155 | { "af11", IPTOS_DSCP_AF11 }, | 1149 | { "af11", IPTOS_DSCP_AF11 }, |
1156 | { "af12", IPTOS_DSCP_AF12 }, | 1150 | { "af12", IPTOS_DSCP_AF12 }, |
1157 | { "af13", IPTOS_DSCP_AF13 }, | 1151 | { "af13", IPTOS_DSCP_AF13 }, |
@@ -1341,3 +1335,459 @@ daemonized(void) | |||
1341 | debug3("already daemonized"); | 1335 | debug3("already daemonized"); |
1342 | return 1; | 1336 | return 1; |
1343 | } | 1337 | } |
1338 | |||
1339 | |||
1340 | /* | ||
1341 | * Splits 's' into an argument vector. Handles quoted string and basic | ||
1342 | * escape characters (\\, \", \'). Caller must free the argument vector | ||
1343 | * and its members. | ||
1344 | */ | ||
1345 | int | ||
1346 | argv_split(const char *s, int *argcp, char ***argvp) | ||
1347 | { | ||
1348 | int r = SSH_ERR_INTERNAL_ERROR; | ||
1349 | int argc = 0, quote, i, j; | ||
1350 | char *arg, **argv = xcalloc(1, sizeof(*argv)); | ||
1351 | |||
1352 | *argvp = NULL; | ||
1353 | *argcp = 0; | ||
1354 | |||
1355 | for (i = 0; s[i] != '\0'; i++) { | ||
1356 | /* Skip leading whitespace */ | ||
1357 | if (s[i] == ' ' || s[i] == '\t') | ||
1358 | continue; | ||
1359 | |||
1360 | /* Start of a token */ | ||
1361 | quote = 0; | ||
1362 | if (s[i] == '\\' && | ||
1363 | (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\')) | ||
1364 | i++; | ||
1365 | else if (s[i] == '\'' || s[i] == '"') | ||
1366 | quote = s[i++]; | ||
1367 | |||
1368 | argv = xreallocarray(argv, (argc + 2), sizeof(*argv)); | ||
1369 | arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1); | ||
1370 | argv[argc] = NULL; | ||
1371 | |||
1372 | /* Copy the token in, removing escapes */ | ||
1373 | for (j = 0; s[i] != '\0'; i++) { | ||
1374 | if (s[i] == '\\') { | ||
1375 | if (s[i + 1] == '\'' || | ||
1376 | s[i + 1] == '\"' || | ||
1377 | s[i + 1] == '\\') { | ||
1378 | i++; /* Skip '\' */ | ||
1379 | arg[j++] = s[i]; | ||
1380 | } else { | ||
1381 | /* Unrecognised escape */ | ||
1382 | arg[j++] = s[i]; | ||
1383 | } | ||
1384 | } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t')) | ||
1385 | break; /* done */ | ||
1386 | else if (quote != 0 && s[i] == quote) | ||
1387 | break; /* done */ | ||
1388 | else | ||
1389 | arg[j++] = s[i]; | ||
1390 | } | ||
1391 | if (s[i] == '\0') { | ||
1392 | if (quote != 0) { | ||
1393 | /* Ran out of string looking for close quote */ | ||
1394 | r = SSH_ERR_INVALID_FORMAT; | ||
1395 | goto out; | ||
1396 | } | ||
1397 | break; | ||
1398 | } | ||
1399 | } | ||
1400 | /* Success */ | ||
1401 | *argcp = argc; | ||
1402 | *argvp = argv; | ||
1403 | argc = 0; | ||
1404 | argv = NULL; | ||
1405 | r = 0; | ||
1406 | out: | ||
1407 | if (argc != 0 && argv != NULL) { | ||
1408 | for (i = 0; i < argc; i++) | ||
1409 | free(argv[i]); | ||
1410 | free(argv); | ||
1411 | } | ||
1412 | return r; | ||
1413 | } | ||
1414 | |||
1415 | /* | ||
1416 | * Reassemble an argument vector into a string, quoting and escaping as | ||
1417 | * necessary. Caller must free returned string. | ||
1418 | */ | ||
1419 | char * | ||
1420 | argv_assemble(int argc, char **argv) | ||
1421 | { | ||
1422 | int i, j, ws, r; | ||
1423 | char c, *ret; | ||
1424 | struct sshbuf *buf, *arg; | ||
1425 | |||
1426 | if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL) | ||
1427 | fatal("%s: sshbuf_new failed", __func__); | ||
1428 | |||
1429 | for (i = 0; i < argc; i++) { | ||
1430 | ws = 0; | ||
1431 | sshbuf_reset(arg); | ||
1432 | for (j = 0; argv[i][j] != '\0'; j++) { | ||
1433 | r = 0; | ||
1434 | c = argv[i][j]; | ||
1435 | switch (c) { | ||
1436 | case ' ': | ||
1437 | case '\t': | ||
1438 | ws = 1; | ||
1439 | r = sshbuf_put_u8(arg, c); | ||
1440 | break; | ||
1441 | case '\\': | ||
1442 | case '\'': | ||
1443 | case '"': | ||
1444 | if ((r = sshbuf_put_u8(arg, '\\')) != 0) | ||
1445 | break; | ||
1446 | /* FALLTHROUGH */ | ||
1447 | default: | ||
1448 | r = sshbuf_put_u8(arg, c); | ||
1449 | break; | ||
1450 | } | ||
1451 | if (r != 0) | ||
1452 | fatal("%s: sshbuf_put_u8: %s", | ||
1453 | __func__, ssh_err(r)); | ||
1454 | } | ||
1455 | if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) || | ||
1456 | (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) || | ||
1457 | (r = sshbuf_putb(buf, arg)) != 0 || | ||
1458 | (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0)) | ||
1459 | fatal("%s: buffer error: %s", __func__, ssh_err(r)); | ||
1460 | } | ||
1461 | if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL) | ||
1462 | fatal("%s: malloc failed", __func__); | ||
1463 | memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf)); | ||
1464 | ret[sshbuf_len(buf)] = '\0'; | ||
1465 | sshbuf_free(buf); | ||
1466 | sshbuf_free(arg); | ||
1467 | return ret; | ||
1468 | } | ||
1469 | |||
1470 | /* | ||
1471 | * Runs command in a subprocess wuth a minimal environment. | ||
1472 | * Returns pid on success, 0 on failure. | ||
1473 | * The child stdout and stderr maybe captured, left attached or sent to | ||
1474 | * /dev/null depending on the contents of flags. | ||
1475 | * "tag" is prepended to log messages. | ||
1476 | * NB. "command" is only used for logging; the actual command executed is | ||
1477 | * av[0]. | ||
1478 | */ | ||
1479 | pid_t | ||
1480 | subprocess(const char *tag, struct passwd *pw, const char *command, | ||
1481 | int ac, char **av, FILE **child, u_int flags) | ||
1482 | { | ||
1483 | FILE *f = NULL; | ||
1484 | struct stat st; | ||
1485 | int fd, devnull, p[2], i; | ||
1486 | pid_t pid; | ||
1487 | char *cp, errmsg[512]; | ||
1488 | u_int envsize; | ||
1489 | char **child_env; | ||
1490 | |||
1491 | if (child != NULL) | ||
1492 | *child = NULL; | ||
1493 | |||
1494 | debug3("%s: %s command \"%s\" running as %s (flags 0x%x)", __func__, | ||
1495 | tag, command, pw->pw_name, flags); | ||
1496 | |||
1497 | /* Check consistency */ | ||
1498 | if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && | ||
1499 | (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) { | ||
1500 | error("%s: inconsistent flags", __func__); | ||
1501 | return 0; | ||
1502 | } | ||
1503 | if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) { | ||
1504 | error("%s: inconsistent flags/output", __func__); | ||
1505 | return 0; | ||
1506 | } | ||
1507 | |||
1508 | /* | ||
1509 | * If executing an explicit binary, then verify the it exists | ||
1510 | * and appears safe-ish to execute | ||
1511 | */ | ||
1512 | if (*av[0] != '/') { | ||
1513 | error("%s path is not absolute", tag); | ||
1514 | return 0; | ||
1515 | } | ||
1516 | temporarily_use_uid(pw); | ||
1517 | if (stat(av[0], &st) < 0) { | ||
1518 | error("Could not stat %s \"%s\": %s", tag, | ||
1519 | av[0], strerror(errno)); | ||
1520 | restore_uid(); | ||
1521 | return 0; | ||
1522 | } | ||
1523 | if (safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) { | ||
1524 | error("Unsafe %s \"%s\": %s", tag, av[0], errmsg); | ||
1525 | restore_uid(); | ||
1526 | return 0; | ||
1527 | } | ||
1528 | /* Prepare to keep the child's stdout if requested */ | ||
1529 | if (pipe(p) != 0) { | ||
1530 | error("%s: pipe: %s", tag, strerror(errno)); | ||
1531 | restore_uid(); | ||
1532 | return 0; | ||
1533 | } | ||
1534 | restore_uid(); | ||
1535 | |||
1536 | switch ((pid = fork())) { | ||
1537 | case -1: /* error */ | ||
1538 | error("%s: fork: %s", tag, strerror(errno)); | ||
1539 | close(p[0]); | ||
1540 | close(p[1]); | ||
1541 | return 0; | ||
1542 | case 0: /* child */ | ||
1543 | /* Prepare a minimal environment for the child. */ | ||
1544 | envsize = 5; | ||
1545 | child_env = xcalloc(sizeof(*child_env), envsize); | ||
1546 | child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH); | ||
1547 | child_set_env(&child_env, &envsize, "USER", pw->pw_name); | ||
1548 | child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name); | ||
1549 | child_set_env(&child_env, &envsize, "HOME", pw->pw_dir); | ||
1550 | if ((cp = getenv("LANG")) != NULL) | ||
1551 | child_set_env(&child_env, &envsize, "LANG", cp); | ||
1552 | |||
1553 | for (i = 0; i < NSIG; i++) | ||
1554 | signal(i, SIG_DFL); | ||
1555 | |||
1556 | if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { | ||
1557 | error("%s: open %s: %s", tag, _PATH_DEVNULL, | ||
1558 | strerror(errno)); | ||
1559 | _exit(1); | ||
1560 | } | ||
1561 | if (dup2(devnull, STDIN_FILENO) == -1) { | ||
1562 | error("%s: dup2: %s", tag, strerror(errno)); | ||
1563 | _exit(1); | ||
1564 | } | ||
1565 | |||
1566 | /* Set up stdout as requested; leave stderr in place for now. */ | ||
1567 | fd = -1; | ||
1568 | if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) | ||
1569 | fd = p[1]; | ||
1570 | else if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0) | ||
1571 | fd = devnull; | ||
1572 | if (fd != -1 && dup2(fd, STDOUT_FILENO) == -1) { | ||
1573 | error("%s: dup2: %s", tag, strerror(errno)); | ||
1574 | _exit(1); | ||
1575 | } | ||
1576 | closefrom(STDERR_FILENO + 1); | ||
1577 | |||
1578 | /* Don't use permanently_set_uid() here to avoid fatal() */ | ||
1579 | if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) { | ||
1580 | error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid, | ||
1581 | strerror(errno)); | ||
1582 | _exit(1); | ||
1583 | } | ||
1584 | if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) { | ||
1585 | error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid, | ||
1586 | strerror(errno)); | ||
1587 | _exit(1); | ||
1588 | } | ||
1589 | /* stdin is pointed to /dev/null at this point */ | ||
1590 | if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && | ||
1591 | dup2(STDIN_FILENO, STDERR_FILENO) == -1) { | ||
1592 | error("%s: dup2: %s", tag, strerror(errno)); | ||
1593 | _exit(1); | ||
1594 | } | ||
1595 | |||
1596 | execve(av[0], av, child_env); | ||
1597 | error("%s exec \"%s\": %s", tag, command, strerror(errno)); | ||
1598 | _exit(127); | ||
1599 | default: /* parent */ | ||
1600 | break; | ||
1601 | } | ||
1602 | |||
1603 | close(p[1]); | ||
1604 | if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) | ||
1605 | close(p[0]); | ||
1606 | else if ((f = fdopen(p[0], "r")) == NULL) { | ||
1607 | error("%s: fdopen: %s", tag, strerror(errno)); | ||
1608 | close(p[0]); | ||
1609 | /* Don't leave zombie child */ | ||
1610 | kill(pid, SIGTERM); | ||
1611 | while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) | ||
1612 | ; | ||
1613 | return 0; | ||
1614 | } | ||
1615 | /* Success */ | ||
1616 | debug3("%s: %s pid %ld", __func__, tag, (long)pid); | ||
1617 | if (child != NULL) | ||
1618 | *child = f; | ||
1619 | return pid; | ||
1620 | } | ||
1621 | |||
1622 | /* Returns 0 if pid exited cleanly, non-zero otherwise */ | ||
1623 | int | ||
1624 | exited_cleanly(pid_t pid, const char *tag, const char *cmd, int quiet) | ||
1625 | { | ||
1626 | int status; | ||
1627 | |||
1628 | while (waitpid(pid, &status, 0) == -1) { | ||
1629 | if (errno != EINTR) { | ||
1630 | error("%s: waitpid: %s", tag, strerror(errno)); | ||
1631 | return -1; | ||
1632 | } | ||
1633 | } | ||
1634 | if (WIFSIGNALED(status)) { | ||
1635 | error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status)); | ||
1636 | return -1; | ||
1637 | } else if (WEXITSTATUS(status) != 0) { | ||
1638 | do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO, | ||
1639 | "%s %s failed, status %d", tag, cmd, WEXITSTATUS(status)); | ||
1640 | return -1; | ||
1641 | } | ||
1642 | return 0; | ||
1643 | } | ||
1644 | |||
1645 | /* | ||
1646 | * Check a given path for security. This is defined as all components | ||
1647 | * of the path to the file must be owned by either the owner of | ||
1648 | * of the file or root and no directories must be group or world writable. | ||
1649 | * | ||
1650 | * XXX Should any specific check be done for sym links ? | ||
1651 | * | ||
1652 | * Takes a file name, its stat information (preferably from fstat() to | ||
1653 | * avoid races), the uid of the expected owner, their home directory and an | ||
1654 | * error buffer plus max size as arguments. | ||
1655 | * | ||
1656 | * Returns 0 on success and -1 on failure | ||
1657 | */ | ||
1658 | int | ||
1659 | safe_path(const char *name, struct stat *stp, const char *pw_dir, | ||
1660 | uid_t uid, char *err, size_t errlen) | ||
1661 | { | ||
1662 | char buf[PATH_MAX], homedir[PATH_MAX]; | ||
1663 | char *cp; | ||
1664 | int comparehome = 0; | ||
1665 | struct stat st; | ||
1666 | |||
1667 | if (realpath(name, buf) == NULL) { | ||
1668 | snprintf(err, errlen, "realpath %s failed: %s", name, | ||
1669 | strerror(errno)); | ||
1670 | return -1; | ||
1671 | } | ||
1672 | if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) | ||
1673 | comparehome = 1; | ||
1674 | |||
1675 | if (!S_ISREG(stp->st_mode)) { | ||
1676 | snprintf(err, errlen, "%s is not a regular file", buf); | ||
1677 | return -1; | ||
1678 | } | ||
1679 | if (!secure_permissions(stp, uid)) { | ||
1680 | snprintf(err, errlen, "bad ownership or modes for file %s", | ||
1681 | buf); | ||
1682 | return -1; | ||
1683 | } | ||
1684 | |||
1685 | /* for each component of the canonical path, walking upwards */ | ||
1686 | for (;;) { | ||
1687 | if ((cp = dirname(buf)) == NULL) { | ||
1688 | snprintf(err, errlen, "dirname() failed"); | ||
1689 | return -1; | ||
1690 | } | ||
1691 | strlcpy(buf, cp, sizeof(buf)); | ||
1692 | |||
1693 | if (stat(buf, &st) < 0 || | ||
1694 | !secure_permissions(&st, uid)) { | ||
1695 | snprintf(err, errlen, | ||
1696 | "bad ownership or modes for directory %s", buf); | ||
1697 | return -1; | ||
1698 | } | ||
1699 | |||
1700 | /* If are past the homedir then we can stop */ | ||
1701 | if (comparehome && strcmp(homedir, buf) == 0) | ||
1702 | break; | ||
1703 | |||
1704 | /* | ||
1705 | * dirname should always complete with a "/" path, | ||
1706 | * but we can be paranoid and check for "." too | ||
1707 | */ | ||
1708 | if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) | ||
1709 | break; | ||
1710 | } | ||
1711 | return 0; | ||
1712 | } | ||
1713 | |||
1714 | /* | ||
1715 | * Version of safe_path() that accepts an open file descriptor to | ||
1716 | * avoid races. | ||
1717 | * | ||
1718 | * Returns 0 on success and -1 on failure | ||
1719 | */ | ||
1720 | int | ||
1721 | safe_path_fd(int fd, const char *file, struct passwd *pw, | ||
1722 | char *err, size_t errlen) | ||
1723 | { | ||
1724 | struct stat st; | ||
1725 | |||
1726 | /* check the open file to avoid races */ | ||
1727 | if (fstat(fd, &st) < 0) { | ||
1728 | snprintf(err, errlen, "cannot stat file %s: %s", | ||
1729 | file, strerror(errno)); | ||
1730 | return -1; | ||
1731 | } | ||
1732 | return safe_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen); | ||
1733 | } | ||
1734 | |||
1735 | /* | ||
1736 | * Sets the value of the given variable in the environment. If the variable | ||
1737 | * already exists, its value is overridden. | ||
1738 | */ | ||
1739 | void | ||
1740 | child_set_env(char ***envp, u_int *envsizep, const char *name, | ||
1741 | const char *value) | ||
1742 | { | ||
1743 | char **env; | ||
1744 | u_int envsize; | ||
1745 | u_int i, namelen; | ||
1746 | |||
1747 | if (strchr(name, '=') != NULL) { | ||
1748 | error("Invalid environment variable \"%.100s\"", name); | ||
1749 | return; | ||
1750 | } | ||
1751 | |||
1752 | /* | ||
1753 | * If we're passed an uninitialized list, allocate a single null | ||
1754 | * entry before continuing. | ||
1755 | */ | ||
1756 | if (*envp == NULL && *envsizep == 0) { | ||
1757 | *envp = xmalloc(sizeof(char *)); | ||
1758 | *envp[0] = NULL; | ||
1759 | *envsizep = 1; | ||
1760 | } | ||
1761 | |||
1762 | /* | ||
1763 | * Find the slot where the value should be stored. If the variable | ||
1764 | * already exists, we reuse the slot; otherwise we append a new slot | ||
1765 | * at the end of the array, expanding if necessary. | ||
1766 | */ | ||
1767 | env = *envp; | ||
1768 | namelen = strlen(name); | ||
1769 | for (i = 0; env[i]; i++) | ||
1770 | if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=') | ||
1771 | break; | ||
1772 | if (env[i]) { | ||
1773 | /* Reuse the slot. */ | ||
1774 | free(env[i]); | ||
1775 | } else { | ||
1776 | /* New variable. Expand if necessary. */ | ||
1777 | envsize = *envsizep; | ||
1778 | if (i >= envsize - 1) { | ||
1779 | if (envsize >= 1000) | ||
1780 | fatal("child_set_env: too many env vars"); | ||
1781 | envsize += 50; | ||
1782 | env = (*envp) = xreallocarray(env, envsize, sizeof(char *)); | ||
1783 | *envsizep = envsize; | ||
1784 | } | ||
1785 | /* Need to set the NULL pointer at end of array beyond the new slot. */ | ||
1786 | env[i + 1] = NULL; | ||
1787 | } | ||
1788 | |||
1789 | /* Allocate space and format the variable in the appropriate slot. */ | ||
1790 | env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1); | ||
1791 | snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); | ||
1792 | } | ||
1793 | |||