Commit Diff


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