commit 90fedfb73083b2f7c1e18269ee044c0f3098cfa9 from: Stefan Sperling date: Mon Sep 25 07:49:36 2023 UTC add support for backing up and restoring every user's ~/.nshenv Include data from users' ~/.nshenv in show running-config and write-config. Add nshenv and restoreenv commands which handle saving and restoring. These commands are not supposed to be user-facing. Their only purpose is to preserve data during write-config and restore it during nsh -i. During restore we take care not to overwrite existing ~/.nshenv files. The purpose of this feature is to restore ~/.nshenv files which were lost during a disaster. If the user has already saved new settings then we can just leave them as they are. commit - eb5d12f955f3d56f05ad6ec160eba2786b113dd8 commit + 90fedfb73083b2f7c1e18269ee044c0f3098cfa9 blob - 10708daf18f31248cb47bdfec873ce1807bc56a1 blob + ee5a22a8a9c0f345939fe9fadab2fa4e45ba507a --- commands.c +++ commands.c @@ -72,6 +72,7 @@ char hname[HSIZE]; char hbuf[MAXHOSTNAMELEN]; /* host name */ char ifname[IFNAMSIZ]; /* interface name */ struct intlist *whichlist; +struct hashtable *restore_envs; /* per-user environment variables to restore */ pid_t child; @@ -133,6 +134,8 @@ static int docmd(int, char **); static int setenvcmd(int, char **); static int unsetenvcmd(int, char **); static int saveenvcmd(int, char **); +static int nshenvcmd(int, char **); +static int restoreenvcmd(int, char **); static int shell(int, char*[]); static int ping(int, char*[]); static int ping6(int, char*[]); @@ -1753,7 +1756,7 @@ static char dohelp[] = "Superfluous, do is ignored and its arguments executed", setenvhelp[] = "Set an environment variable", unsetenvhelp[] ="Delete an environment variable", - saveenvhelp[] = "Save environment variables set by setenv to ~/.nshenv", + saveenvhelp[] = "Save environment variables set by setenv to ~/" NSHENV_FILE, shellhelp[] = "Invoke a subshell", savehelp[] = "Save the current configuration", nreboothelp[] = "Reboot the system", @@ -2015,6 +2018,8 @@ Command cmdtab[] = { { "setenv", setenvhelp, CMPL(E) 0, 0, setenvcmd, 0, 0, 0, 0 }, { "unsetenv", unsetenvhelp, CMPL(e) 0, 0, unsetenvcmd, 0, 0, 0, 0 }, { "saveenv", saveenvhelp, CMPL0 0, 0, saveenvcmd, 0, 0, 0, 0 }, + { "nshenv", 0, CMPL0 0, 0, nshenvcmd, 1, 1, 0, 0 }, + { "restoreenv", 0, CMPL0 0, 0, restoreenvcmd, 1, 1, 0, 0 }, { "!", shellhelp, CMPL0 0, 0, shell, 1, 0, 0, 0 }, { "?", helphelp, CMPL(c) 0, 0, help, 0, 0, 0, 0 }, { "manual", manhelp, CMPL(H) (char **)mantab, sizeof(struct ghs), manual,0, 0, 0, 0 }, @@ -2455,15 +2460,16 @@ saveenvcmd(int argc, char **argv) return 0; } - ret = snprintf(path, sizeof(path), "%s/.nshenv", home); + ret = snprintf(path, sizeof(path), "%s/%s", home, NSHENV_FILE); if (ret < 0 || (size_t)ret >= sizeof(path)) { - printf("%% path to ~/.nshenv is too long\n"); + printf("%% path to ~/%s is too long\n", NSHENV_FILE); return 0; } - ret = snprintf(tmppath, sizeof(tmppath), "%s/.nshenv-XXXXXXXXXX", home); + ret = snprintf(tmppath, sizeof(tmppath), "%s/%s-XXXXXXXXXX", + home, NSHENV_FILE); if (ret < 0 || (size_t)ret >= sizeof(tmppath)) { - printf("%% path to ~/.nshenv is too long\n"); + printf("%% path to ~/%s is too long\n", NSHENV_FILE); return 0; } @@ -2496,10 +2502,252 @@ saveenvcmd(int argc, char **argv) } fclose(f); + + return 0; +} + +static int +nshenvcmd(int argc, char **argv) +{ + char *username, *envstr, *eq, *name, *value; + void *envstr0; + struct hashtable *user_env; + + if (restore_envs == NULL) { + restore_envs = hashtable_alloc(); + if (restore_envs == NULL) { + printf("%% hashtable_alloc: %s", strerror(errno)); + return 1; + } + } + + if (argc != 4) { + printf("%% Invalid arguments\n"); + return 1; + } + + if (strcmp(argv[1], "user") != 0) { + printf("%% bad argument: %s\n", argv[1]); + return 1; + } + + username = strdup(argv[2]); + if (username == NULL) { + printf("%% strdup: %s", strerror(errno)); + return 1; + } + + /* Get or create this user's environment table. */ + user_env = hashtable_get_value(restore_envs, + username, strlen(username)); + if (user_env == NULL) { + user_env = hashtable_alloc(); + if (user_env == NULL) { + printf("%% hashtable_alloc: %s", strerror(errno)); + return 1; + } + + if (hashtable_add(restore_envs, username, strlen(username), + user_env, sizeof(user_env))) { + printf("%% %s: hashtable_add(\"%s\", user_env) failed\n", + __func__, username); + hashtable_free(user_env); + return 1; + } + } + + envstr = strdup(argv[3]); + if (envstr == NULL) { + printf("%% strdup: %s", strerror(errno)); + return 1; + } + + eq = strchr(envstr, '='); + if (eq == NULL) { + printf("%% bad argument: %s\n", envstr); + free(envstr); + return 1; + } + + *eq = '\0'; + name = envstr; + value = eq + 1; + + /* Try to remove first, in case of updating an existing variable. */ + if (hashtable_remove(user_env, &envstr0, NULL, NULL, + name, strlen(name)) == 0) + free(envstr0); + + if (hashtable_add(user_env, name, strlen(name), value, strlen(value))) { + printf("%% %s: hashtable_add(\"%s\", \"%s\") failed\n", + __func__, name, value); + free(envstr); + } + + return 0; +} + +struct nshenv_restore_info { + char *username; + uid_t uid; + gid_t gid; + char *homedir; +}; + +static int +restore_nshenv(void *keyptr, size_t keysize, void *value, size_t valsize, void *arg) +{ + struct nshenv_restore_info *info = arg; + char path[PATH_MAX], tmppath[PATH_MAX]; + FILE *f = NULL; + char *username = keyptr; + struct hashtable *user_env = value; + void *name0, *val0; + struct stat sb; + int ret, fd; + + if (stat(info->homedir, &sb) == -1) { + printf("%% stat %s: %s\n", info->homedir, strerror(errno)); + goto done; + } + if (!S_ISDIR(sb.st_mode)) { + printf("%% ignoring %s: not a directory\n", info->homedir); + goto done; + } + if (sb.st_uid != info->uid) { + printf("%% ignoring %s: not owned by UID %d\n", + info->homedir, info->uid); + goto done; + } + + ret = snprintf(path, sizeof(path), "%s/%s", info->homedir, NSHENV_FILE); + if (ret < 0 || (size_t)ret >= sizeof(path)) { + printf("%% path to ~/%s is too long\n", NSHENV_FILE); + goto done; + } + + ret = snprintf(tmppath, sizeof(tmppath), "%s/%s-XXXXXXXXXX", + info->homedir, NSHENV_FILE); + if (ret < 0 || (size_t)ret >= sizeof(tmppath)) { + printf("%% path to ~/%s is too long\n", NSHENV_FILE); + goto done; + } + + fd = mkstemp(tmppath); + if (fd == -1) { + printf("%s: mkstemp %s: %s", __func__, tmppath, + strerror(errno)); + goto done; + } + + f = fdopen(fd, "w"); + if (f == NULL) { + printf("%s: fdopen %s: %s", __func__, tmppath, + strerror(errno)); + close(fd); + unlink(tmppath); + goto done; + } + + hashtable_foreach(user_env, savevar, f); + + if (fchown(fileno(f), info->uid, info->gid) == -1) { + printf("%% chown %d:%d %s: %s\n", info->uid, info->gid, + tmppath, strerror(errno)); + unlink(tmppath); + goto done; + } + + if (fchmod(fileno(f), S_IRUSR | S_IWUSR) == -1) { + printf("%% chmod 600 %s: %s\n", tmppath, strerror(errno)); + unlink(tmppath); + goto done; + } + + /* Do not overwrite an already existing ~/.nshenv. */ + if (stat(path, &sb) == 0) + goto done; + + if (errno == ENOENT) { + /* This is the happy path. With a small TOCTOU race. */ + if (rename(tmppath, path) == -1) { + printf("%% rename %s %s: %s\n", tmppath, path, + strerror(errno)); + if (unlink(tmppath) == -1) { + printf("%% unlink %s: %s\n", tmppath, + strerror(errno)); + } + } + } else { + unlink(tmppath); + goto done; + } +done: + if (f) + fclose(f); + /* This user's environment has been dealt with. */ + if (hashtable_remove(restore_envs, &name0, &val0, NULL, + username, strlen(username)) == 0) { + free(name0); + user_env = val0; + hashtable_free(user_env); + } + return 0; } +static int +restoreenvcmd(int argc, char **argv) +{ + struct nshenv_restore_info info; + const char *errstr; + + if (restore_envs == NULL) + return 0; + + if (argc != 9) { + printf("%% Invalid arguments.\n"); + return 1; + } + + if (strcmp(argv[1], "user") != 0) { + printf("%% Invalid argument: %s\n", argv[1]); + return 1; + } + if (strcmp(argv[3], "uid") != 0) { + printf("%% Invalid argument: %s\n", argv[3]); + return 1; + } + if (strcmp(argv[5], "gid") != 0) { + printf("%% Invalid argument: %s\n", argv[5]); + return 1; + } + if (strcmp(argv[7], "home") != 0) { + printf("%% Invalid argument: %s\n", argv[7]); + return 1; + } + + info.username = argv[2]; + info.uid = strtonum(argv[4], 0, UID_MAX, &errstr); + if (errstr) { + printf("%% UID is %s\n", errstr); + return 1; + } + info.gid = strtonum(argv[6], 0, GID_MAX, &errstr); + if (errstr) { + printf("%% GID is %s\n", errstr); + return 1; + } + info.homedir = argv[8]; + + hashtable_foreach(restore_envs, restore_nshenv, &info); + + hashtable_free(restore_envs); + restore_envs = NULL; + return 0; +} + /* * Shell command. */ blob - b2f75596c7a2341b035284c80351db2dd73c1944 blob + b6011215245899c4fcf52e13d739455224f24a67 --- conf.c +++ conf.c @@ -72,6 +72,7 @@ void conf_parent(FILE *, int, char *); void conf_patch(FILE *, int, char *); void conf_brcfg(FILE *, int, struct if_nameindex *, char *); void conf_ifxflags(FILE *, int, char *); +void conf_env(FILE *); void conf_rtables(FILE *); void conf_rtables_rtable(FILE *, int); void conf_rdomain(FILE *, int, char *); @@ -296,8 +297,77 @@ conf(FILE *output) fprintf(output, "!\n"); conf_nameserver(output); + + fprintf(output, "!\n"); + conf_env(output); return(0); +} + +static int +has_whitespace(const char *s) +{ + size_t len = strlen(s); + int i; + + for (i = 0; i < len; i++) { + if (isspace((unsigned char)s[i])) + return 1; + } + + return 0; +} + +static void +conf_writeenv(FILE *output, const char *pw_name, uid_t pw_uid, gid_t pw_gid, + const char *pw_dir) +{ + char path[PATH_MAX]; + FILE *f; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + int ret; + + /* We do not support quoting (yet?) */ + if (has_whitespace(pw_name)) + return; + + ret = snprintf(path, sizeof(path), "%s/%s", pw_dir, NSHENV_FILE); + if (ret < 0 || (size_t)ret >= sizeof(path)) + return; + + f = fopen(path, "r"); + if (f == NULL) + return; + + while ((linelen = getline(&line, &linesize, f)) != -1) { + fprintf(output, "nshenv user %s %s", pw_name, line); + if (line[linelen - 1] != '\n') + fputc('\n', output); + } + fprintf(output, "restoreenv user %s uid %d gid %d home %s\n", + pw_name, pw_uid, pw_gid, pw_dir); + + fclose(f); +} + +void +conf_env(FILE *output) +{ + struct passwd *pw; + + conf_writeenv(output, "root", 0, 0, "/root"); + + pw = getpwent(); + while (pw) { + if (pw->pw_name[0] != '_' && isprefix("/home/", pw->pw_dir)) { + conf_writeenv(output, pw->pw_name, pw->pw_uid, + pw->pw_gid, pw->pw_dir); + } + pw = getpwent(); + } + endpwent(); } void conf_rtables(FILE *output) blob - 2e4977cfe088faa4a96086ab42f1262d2a78f9c1 blob + 967626263d58541331225ba3a9c37919e886c2e8 --- externs.h +++ externs.h @@ -90,6 +90,7 @@ void conf_sppp(FILE *, int, char *); #define LEASEPREFIX "/var/db/dhclient.leases" #define DHCPLEASECTL "/usr/sbin/dhcpleasectl" #define SLAACCTL "/usr/sbin/slaacctl" +#define NSHENV_FILE ".nshenv" int conf(FILE *); void conf_interfaces(FILE *, char *, int); u_long default_mtu(char *); blob - c88fd19c4873d1a21e811a7bf9b973e94f2177e2 blob + 2bc0f9de47cf0695e3abe05740222a5c133c2cf1 --- nsh.8 +++ nsh.8 @@ -2774,6 +2774,19 @@ It is necessary to return to unprivileged mode with th command in order to save any variables which were set in unprivileged mode. .Pp +The +.Cm nshenv +and +.Cm restoreenv +commands are used internally by +.Nm +to preserve per-user environment variable settings during +.Cm write-config +and to restore them when the written config is reloaded and +a user's +.Pa ~/.nshenv +file is found to be missing on disk. +.Pp .Tg flush .Ic flush .Op routes | arp | ndp | line | bridge-dyn | bridge-all | bridge-rule | pf | history |\&? | help