Commit Diff


commit - 98fc2f167ba8e7efae68c15faa0430e674146adf
commit + 813bc04fe92156f9bee8cb5d318e2e2fc39fceca
blob - d0ea33ff297130b499703d8f068c954783abcdb3
blob + b32106012fc6ebd934f47d5fcf9542f73853662d
--- Makefile
+++ Makefile
@@ -1,7 +1,7 @@
 #
 PROG= nsh
 
-SUBDIR += bgpnsh
+SUBDIR += bgpnsh nshdoas
 
 .PHONY: release dist
 
@@ -11,7 +11,6 @@ SUBDIR += bgpnsh
 DEBUG?=-O0 -g
 .endif
 
-NSH_REXEC_PATH?=/usr/local/bin/nsh
 
 .if make(install)
 DESTDIR?=/usr/local
@@ -23,7 +22,7 @@ MANDIR?=/man/man
 #CFLAGS=-O -DDHCPLEASES=\"/flash/dhcpd.leases\" -Wmissing-prototypes -Wformat -Wall -Wpointer-arith -Wbad-function-cast #-W
 CFLAGS?=-O
 CFLAGS+=-Wmissing-prototypes -Wformat -Wall -Wbad-function-cast -I/usr/local/include #-W -Wpointer-arith
-CPPFLAGS+=-DNSH_VERSION=${NSH_VERSION}  -DNSH_REXEC_PATH=${NSH_REXEC_PATH}
+CPPFLAGS+=-DNSH_VERSION=${NSH_VERSION}
 
 SRCS=arp.c compile.c main.c genget.c commands.c bgpcommands.c stats.c kroute.c
 SRCS+=ctl.c show.c if.c version.c route.c conf.c complete.c ieee80211.c
blob - d4f2f7232f6db50ce16ca840c67378a609085eb5
blob + 1dc1723750e610a7038f9c2052303baf89c17234
--- bgpnsh/Makefile
+++ bgpnsh/Makefile
@@ -1,7 +1,7 @@
 #
 PROG= bgpnsh
 
-.PATH=${.CURDIR}/..
+.PATH=${.CURDIR}/..:${.CURDIR}
 
 .include "../nsh-version.mk"
 
blob - e3a2faba7bbc29b9f4aabf953849d7af19c066a1
blob + f2b65a168c2ad92a6763a28bfe15066b933f20f1
--- cmdargs.c
+++ cmdargs.c
@@ -22,7 +22,9 @@
 #include <sys/socket.h>
 #include <sys/wait.h>
 
+#include <fcntl.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <signal.h>
 #include <errno.h>
@@ -54,12 +56,18 @@ cmdargs(char *cmd, char *arg[])
 int
 cmdargs_output(char *cmd, char *arg[], int stdoutfd, int stderrfd)
 {
-	return cmdargs_output_setenv(cmd, arg, stdoutfd, stderrfd, NULL);
+	return cmdargs_output_setenv(cmd, arg, stdoutfd, stderrfd, NULL, 0);
 }
 
 int
+cmdargs_nowait(char *cmd, char *arg[], int pipefd)
+{
+	return cmdargs_output_setenv(cmd, arg, -1, -1, NULL, pipefd);
+}
+
+int
 cmdargs_output_setenv(char *cmd, char *arg[], int stdoutfd, int stderrfd,
-    char **env)
+    char **env, int pipefd)
 {
 	sig_t sigint, sigquit, sigchld;
 	int status = -1;
@@ -110,6 +118,8 @@ cmdargs_output_setenv(char *cmd, char *arg[], int stdo
 		}
 			break;
 		default:
+			if (pipefd)
+				return 0;
 			signal(SIGALRM, sigalarm);
 			wait(&status);  /* Wait for cmd to complete */
 			if (WIFEXITED(status)) /* normal exit? */
@@ -127,6 +137,31 @@ cmdargs_output_setenv(char *cmd, char *arg[], int stdo
 }
 
 int
+cmdargs_wait_for_child(void)
+{
+	sig_t sigint, sigquit, sigchld;
+	int status = 127;
+
+	sigint = signal(SIGINT, SIG_IGN);
+	sigquit = signal(SIGQUIT, SIG_IGN);
+	sigchld = signal(SIGCHLD, SIG_DFL);
+
+	if (child != -1) {
+		signal(SIGALRM, sigalarm);
+		wait(&status);  /* Wait for cmd to complete */
+		if (WIFEXITED(status)) /* normal exit? */
+			status = WEXITSTATUS(status); /* exit code */
+		signal(SIGINT, sigint);
+		signal(SIGQUIT, sigquit);
+		signal(SIGCHLD, sigchld);
+		signal(SIGALRM, SIG_DFL);
+		child = -1;
+	}
+
+	return status;
+}
+
+int
 nsh_setrtable(int rtableid)
 {
 	int ret = 0;
blob - 6c8970da0e631c868440bba600cfb153a60347ad
blob + 30ff4b70780cf2728b91af6036abf28a54ca59df
--- externs.h
+++ externs.h
@@ -11,8 +11,17 @@
 
 #define NSH_VERSION_STR NSH_STRINGVAL(NSH_VERSION)
 
+#ifndef NSH_REXEC_PATH
+#define NSH_REXEC_PATH /usr/local/bin/nsh
+#endif
+
+#ifndef NSHDOAS_PATH
+#define NSHDOAS_PATH /usr/local/libexec/nshdoas
+#endif
+
 #define NSH_REXEC_PATH_STR		NSH_STRINGVAL(NSH_REXEC_PATH)
 #define NSH_REXEC_EXIT_CODE_QUIT	42
+#define NSHDOAS_PATH_STR		NSH_STRINGVAL(NSHDOAS_PATH)
 
 #define NO_ARG(x)	(strcasecmp(x, "no") == 0) /* absolute "no" */
 
@@ -150,9 +159,11 @@ extern char metricnames[];
 /* ctl.c declarations moved to ctl.h */
 
 /* cmdargs.c */
-int cmdargs_output_setenv(char *, char **, int, int, char **);
+int cmdargs_output_setenv(char *, char **, int, int, char **, int);
 int cmdargs_output(char *, char **, int, int);
 int cmdargs(char *, char **);
+int cmdargs_nowait(char *, char **, int);
+int cmdargs_wait_for_child(void);
 int nsh_setrtable(int);
 
 /* commands.c */
blob - e0969dbbb8268b782af52a08caaad3fdc026cd70
blob + c27d517cfd6ac1a2d1ea8b72a58a488b65ac546b
--- main.c
+++ main.c
@@ -110,6 +110,17 @@ main(int argc, char *argv[])
 
 		if (!privexec)
 			printf("%% NSH v%s\n", vers);
+	} else {
+		/*
+		 * We need line-buffered mode to cross seamlessly into
+		 * the privileged process in case an 'enable' command
+		 * appears on standard input.
+		 */
+		if (setvbuf(stdin, NULL, _IOLBF, 0) != 0) {
+			/* should not happen, warn just in case */
+			printf("%% failed to enable line-buffered mode "
+			    "for stdin\n");
+		}
 	}
 
 	/* create temporal tables (if they aren't already there) */
blob - af71bd01fd67ae20381615194fddba516473b430
blob + 6eb375fbb75af8281fcd5619915fb64f2123f566
--- passwd.c
+++ passwd.c
@@ -32,6 +32,7 @@
 #include <pwd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/socket.h>
 #include "externs.h"
 
 int read_pass(char *, size_t);
@@ -277,6 +278,21 @@ enable_passwd(int argc, char **argv)
 		printf("%% Too many arguments\n");
 		return 0;
 	}
+
+	return 0;
+}
+
+static int
+write_line(const char *line, size_t len, int fd)
+{
+	ssize_t written = 0, w;
+
+	do {
+		w = write(fd, line, len);
+		if (w == -1)
+			return -1;
+		written += w;
+	} while (written < len);
 
 	return 0;
 }
@@ -285,13 +301,12 @@ int
 enable(int argc, char **argv)
 {
 	char *doas_argv[] = {
-		DOAS, NSH_REXEC_PATH_STR, "-e", NULL
-	};
-	char *su_argv[] = {
-		SU, "root", "-c", NSH_REXEC_PATH_STR " -e", NULL
-		
+		NSHDOAS_PATH_STR, NULL, NULL, NULL
 	};
+	char buf[64];
 	int exit_code;
+	int pfd[2];
+	pid_t pid;
 
 	if (argc != 1)
 		return enable_passwd(argc, argv);
@@ -304,36 +319,51 @@ enable(int argc, char **argv)
 		return 0;
 	}
 
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pfd) == -1) {
+		printf("%% socketpair failed: %s\n", strerror(errno));
+		return 0;
+	}
+
+	if (!interactive_mode) {
+		snprintf(buf, sizeof(buf), "%d", pfd[0]);
+		doas_argv[1] = "-F";
+		doas_argv[2] = buf;
+	}
+
 	/*
 	 * Start an nsh child process in privileged mode.
 	 * The 'priv' flag will remain at zero in our own process.
 	 */
-	printf("%% Obtaining root privileges via %s\n", DOAS);
-	exit_code = cmdargs(doas_argv[0], doas_argv);
-	if (exit_code == 0)
+	pid = cmdargs_nowait(doas_argv[0], doas_argv, pfd[0]);
+	if (pid == -1)
 		return 0;
-	else if (exit_code == NSH_REXEC_EXIT_CODE_QUIT) {
-		/* The child exited due to a 'quit' command. */
-		quit();
-	}
 
-	/*
-	 * XXX We cannot differentiate a doas exit code of 1 from an
-	 * nsh exit code of 1. Under normal circumstances nsh will exit
-	 * with code zero. Just assume that doas failed to run the
-	 * command if we get here and retry with su.
-	 */
+	close(pfd[0]);
 
-	printf("%% Obtaining root privileges via %s\n", SU);
-	exit_code = cmdargs(su_argv[0], su_argv);
+	if (!interactive_mode) {
+		char *line = NULL;
+		size_t linesize = 0;
+		ssize_t linelen;
 
-	if (exit_code == -1 || exit_code == 127) {
-		printf("%% Entering privileged mode failed: "
-		    "Could not re-execute nsh\n");
-	} else if (exit_code == NSH_REXEC_EXIT_CODE_QUIT) {
+		while ((linelen = getline(&line, &linesize, stdin)) != -1) {
+			if (write_line(line, linelen, pfd[1]) == -1) {
+				printf("%% writing to privileged child: %s\n",
+				    strerror(errno));
+				break;
+			}
+			if (strcmp(line, "disable\n") == 0)
+				break;
+		}
+		free(line);
+	}
+
+	exit_code = cmdargs_wait_for_child();
+	if (exit_code == 0)
+		return 0;
+	else if (exit_code == NSH_REXEC_EXIT_CODE_QUIT) {
 		/* The child exited due to a 'quit' command. */
 		quit();
-	} else if (exit_code) {
+	} else  {
 		printf("%% Privileged mode child process exited "
 		    "with error code %d\n", exit_code);
 	}
blob - /dev/null
blob + 66fa671ac5ac8390f2891dc49b5bc3f1b2b477f3 (mode 644)
--- /dev/null
+++ nshdoas/.gitignore
@@ -0,0 +1 @@
+obj/**
blob - /dev/null
blob + 89ad14b1c093f2143b239a8c9c2000cc7feeefad (mode 644)
--- /dev/null
+++ nshdoas/Makefile
@@ -0,0 +1,32 @@
+#
+PROG= nshdoas
+
+.PATH=${.CURDIR}/..
+
+.include "../nsh-version.mk"
+
+.if ${NSH_RELEASE} != Yes
+DEBUG?=-O0 -g
+.endif
+
+.if make(install)
+DESTDIR?=/usr/local
+BINDIR?=/libexec
+MANDIR?=/man/man
+.endif
+
+CFLAGS+=-Wmissing-prototypes -Wformat -Wall -Wbad-function-cast
+CPPFLAGS+=-DNSH_VERSION=${NSH_VERSION}
+CPPFLAGS+=-I${.CURDIR}/.. -I ${.CURDIR}
+
+SRCS=parse.y nshdoas.c env.c
+LDADD=-lutil
+
+BINOWN= root
+BINMODE=4555
+
+YFLAGS=
+
+NOMAN=yes
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + b98fe353b18676f6509ebe00978b5e3754e305b7 (mode 644)
--- /dev/null
+++ nshdoas/doas.h
@@ -0,0 +1,49 @@
+/* $OpenBSD: doas.h,v 1.19 2021/11/30 20:08:15 tobias Exp $ */
+/*
+ * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct rule {
+	int action;
+	int options;
+	const char *ident;
+	const char *target;
+	const char *cmd;
+	const char **cmdargs;
+	const char **envlist;
+};
+
+extern struct rule **rules;
+extern size_t nrules;
+extern int parse_error;
+
+extern const char *formerpath;
+
+struct passwd;
+
+char **prepenv(const struct rule *, const struct passwd *,
+    const struct passwd *);
+
+#define PERMIT	1
+#define DENY	2
+
+#define NOPASS		0x1
+#define KEEPENV		0x2
+#define PERSIST		0x4
+#define NOLOG		0x8
+
+#define AUTH_FAILED	-1
+#define AUTH_OK		0
+#define AUTH_RETRIES	3
blob - /dev/null
blob + 2d93a4089b6b4ea71ec6c922c88f22b79d15a6d6 (mode 644)
--- /dev/null
+++ nshdoas/env.c
@@ -0,0 +1,235 @@
+/* $OpenBSD: env.c,v 1.10 2019/07/07 19:21:28 tedu Exp $ */
+/*
+ * Copyright (c) 2016 Ted Unangst <tedu@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/tree.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <err.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pwd.h>
+
+#include "doas.h"
+
+const char *formerpath;
+
+struct envnode {
+	RB_ENTRY(envnode) node;
+	const char *key;
+	const char *value;
+};
+
+struct env {
+	RB_HEAD(envtree, envnode) root;
+	u_int count;
+};
+
+static void fillenv(struct env *env, const char **envlist);
+
+static int
+envcmp(struct envnode *a, struct envnode *b)
+{
+	return strcmp(a->key, b->key);
+}
+RB_GENERATE_STATIC(envtree, envnode, node, envcmp)
+
+static struct envnode *
+createnode(const char *key, const char *value)
+{
+	struct envnode *node;
+
+	node = malloc(sizeof(*node));
+	if (!node)
+		err(1, NULL);
+	node->key = strdup(key);
+	node->value = strdup(value);
+	if (!node->key || !node->value)
+		err(1, NULL);
+	return node;
+}
+
+static void
+freenode(struct envnode *node)
+{
+	free((char *)node->key);
+	free((char *)node->value);
+	free(node);
+}
+
+static void
+addnode(struct env *env, const char *key, const char *value)
+{
+	struct envnode *node;
+
+	node = createnode(key, value);
+	RB_INSERT(envtree, &env->root, node);
+	env->count++;
+}
+
+static struct env *
+createenv(const struct rule *rule, const struct passwd *mypw,
+    const struct passwd *targpw)
+{
+	static const char *copyset[] = {
+		"DISPLAY", "TERM",
+		NULL
+	};
+	struct env *env;
+	u_int i;
+
+	env = malloc(sizeof(*env));
+	if (!env)
+		err(1, NULL);
+	RB_INIT(&env->root);
+	env->count = 0;
+
+	addnode(env, "DOAS_USER", mypw->pw_name);
+	addnode(env, "HOME", targpw->pw_dir);
+	addnode(env, "LOGNAME", targpw->pw_name);
+	addnode(env, "PATH", getenv("PATH"));
+	addnode(env, "SHELL", targpw->pw_shell);
+	addnode(env, "USER", targpw->pw_name);
+
+	fillenv(env, copyset);
+
+	if (rule->options & KEEPENV) {
+		extern const char **environ;
+
+		for (i = 0; environ[i] != NULL; i++) {
+			struct envnode *node;
+			const char *e, *eq;
+			size_t len;
+			char name[1024];
+
+			e = environ[i];
+
+			/* ignore invalid or overlong names */
+			if ((eq = strchr(e, '=')) == NULL || eq == e)
+				continue;
+			len = eq - e;
+			if (len > sizeof(name) - 1)
+				continue;
+			memcpy(name, e, len);
+			name[len] = '\0';
+
+			node = createnode(name, eq + 1);
+			if (RB_INSERT(envtree, &env->root, node)) {
+				/* ignore any later duplicates */
+				freenode(node);
+			} else {
+				env->count++;
+			}
+		}
+	}
+
+	return env;
+}
+
+static char **
+flattenenv(struct env *env)
+{
+	char **envp;
+	struct envnode *node;
+	u_int i;
+
+	envp = reallocarray(NULL, env->count + 1, sizeof(char *));
+	if (!envp)
+		err(1, NULL);
+	i = 0;
+	RB_FOREACH(node, envtree, &env->root) {
+		if (asprintf(&envp[i], "%s=%s", node->key, node->value) == -1)
+			err(1, NULL);
+		i++;
+	}
+	envp[i] = NULL;
+	return envp;
+}
+
+static void
+fillenv(struct env *env, const char **envlist)
+{
+	struct envnode *node, key;
+	const char *e, *eq;
+	const char *val;
+	char name[1024];
+	u_int i;
+	size_t len;
+
+	for (i = 0; envlist[i]; i++) {
+		e = envlist[i];
+
+		/* parse out env name */
+		if ((eq = strchr(e, '=')) == NULL)
+			len = strlen(e);
+		else
+			len = eq - e;
+		if (len > sizeof(name) - 1)
+			continue;
+		memcpy(name, e, len);
+		name[len] = '\0';
+
+		/* delete previous copies */
+		key.key = name;
+		if (*name == '-')
+			key.key = name + 1;
+		if ((node = RB_FIND(envtree, &env->root, &key))) {
+			RB_REMOVE(envtree, &env->root, node);
+			freenode(node);
+			env->count--;
+		}
+		if (*name == '-')
+			continue;
+
+		/* assign value or inherit from environ */
+		if (eq) {
+			val = eq + 1;
+			if (*val == '$') {
+				if (strcmp(val + 1, "PATH") == 0)
+					val = formerpath;
+				else
+					val = getenv(val + 1);
+			}
+		} else {
+			if (strcmp(name, "PATH") == 0)
+				val = formerpath;
+			else
+				val = getenv(name);
+		}
+		/* at last, we have something to insert */
+		if (val) {
+			node = createnode(name, val);
+			RB_INSERT(envtree, &env->root, node);
+			env->count++;
+		}
+	}
+}
+
+char **
+prepenv(const struct rule *rule, const struct passwd *mypw,
+    const struct passwd *targpw)
+{
+	struct env *env;
+
+	env = createenv(rule, mypw, targpw);
+	if (rule->envlist)
+		fillenv(env, rule->envlist);
+
+	return flattenenv(env);
+}
blob - /dev/null
blob + 17c1440bb3ba28fd3d976677c1d582586f2aebd6 (mode 644)
--- /dev/null
+++ nshdoas/nshdoas.c
@@ -0,0 +1,405 @@
+/* $OpenBSD: doas.c,v 1.98 2022/12/22 19:53:22 kn Exp $ */
+/*
+ * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+
+#include <limits.h>
+#include <login_cap.h>
+#include <bsd_auth.h>
+#include <readpassphrase.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <err.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <syslog.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "externs.h"
+
+#include "doas.h"
+
+static void __dead
+usage(void)
+{
+	fprintf(stderr, "usage: nshdoas [-n] [-a style]\n");
+	exit(1);
+}
+
+static int
+parseuid(const char *s, uid_t *uid)
+{
+	struct passwd *pw;
+	const char *errstr;
+
+	if ((pw = getpwnam(s)) != NULL) {
+		*uid = pw->pw_uid;
+		if (*uid == UID_MAX)
+			return -1;
+		return 0;
+	}
+	*uid = strtonum(s, 0, UID_MAX - 1, &errstr);
+	if (errstr)
+		return -1;
+	return 0;
+}
+
+static int
+uidcheck(const char *s, uid_t desired)
+{
+	uid_t uid;
+
+	if (parseuid(s, &uid) != 0)
+		return -1;
+	if (uid != desired)
+		return -1;
+	return 0;
+}
+
+static int
+parsegid(const char *s, gid_t *gid)
+{
+	struct group *gr;
+	const char *errstr;
+
+	if ((gr = getgrnam(s)) != NULL) {
+		*gid = gr->gr_gid;
+		if (*gid == GID_MAX)
+			return -1;
+		return 0;
+	}
+	*gid = strtonum(s, 0, GID_MAX - 1, &errstr);
+	if (errstr)
+		return -1;
+	return 0;
+}
+
+static int
+match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
+    const char **cmdargs, struct rule *r)
+{
+	int i;
+
+	if (r->ident[0] == ':') {
+		gid_t rgid;
+		if (parsegid(r->ident + 1, &rgid) == -1)
+			return 0;
+		for (i = 0; i < ngroups; i++) {
+			if (rgid == groups[i])
+				break;
+		}
+		if (i == ngroups)
+			return 0;
+	} else {
+		if (uidcheck(r->ident, uid) != 0)
+			return 0;
+	}
+	if (r->target && uidcheck(r->target, target) != 0)
+		return 0;
+	if (r->cmd) {
+		if (strcmp(r->cmd, cmd))
+			return 0;
+		if (r->cmdargs) {
+			/* if arguments were given, they should match explicitly */
+			for (i = 0; r->cmdargs[i]; i++) {
+				if (!cmdargs[i])
+					return 0;
+				if (strcmp(r->cmdargs[i], cmdargs[i]))
+					return 0;
+			}
+			if (cmdargs[i])
+				return 0;
+		}
+	}
+	return 1;
+}
+
+static int
+permit(uid_t uid, gid_t *groups, int ngroups, const struct rule **lastr,
+    uid_t target, const char *cmd, const char **cmdargs)
+{
+	size_t i;
+
+	*lastr = NULL;
+	for (i = 0; i < nrules; i++) {
+		if (match(uid, groups, ngroups, target, cmd,
+		    cmdargs, rules[i]))
+			*lastr = rules[i];
+	}
+	if (!*lastr)
+		return 0;
+	return (*lastr)->action;
+}
+
+static void
+parseconfig(const char *filename, int checkperms)
+{
+	extern FILE *yyfp;
+	extern int yyparse(void);
+	struct stat sb;
+
+	yyfp = fopen(filename, "r");
+	if (!yyfp)
+		err(1, checkperms ? "doas is not enabled, %s" :
+		    "could not open config file %s", filename);
+
+	if (checkperms) {
+		if (fstat(fileno(yyfp), &sb) != 0)
+			err(1, "fstat(\"%s\")", filename);
+		if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
+			errx(1, "%s is writable by group or other", filename);
+		if (sb.st_uid != 0)
+			errx(1, "%s is not owned by root", filename);
+	}
+
+	yyparse();
+	fclose(yyfp);
+	if (parse_error)
+		exit(1);
+}
+
+static int
+authuser_checkpass(char *myname, char *login_style)
+{
+	char *challenge = NULL, *response, rbuf[1024], cbuf[128];
+	auth_session_t *as;
+
+	if (!(as = auth_userchallenge(myname, login_style, "auth-doas",
+	    &challenge))) {
+		warnx("Authentication failed");
+		return AUTH_FAILED;
+	}
+	if (!challenge) {
+		char host[HOST_NAME_MAX + 1];
+
+		if (gethostname(host, sizeof(host)))
+			snprintf(host, sizeof(host), "?");
+		snprintf(cbuf, sizeof(cbuf),
+		    "%s \%.32s@%.32s password: ",
+		    getprogname(), myname, host);
+		challenge = cbuf;
+	}
+	response = readpassphrase(challenge, rbuf, sizeof(rbuf),
+	    RPP_REQUIRE_TTY);
+	if (response == NULL && errno == ENOTTY) {
+		syslog(LOG_AUTHPRIV | LOG_NOTICE,
+		    "tty required for %s", myname);
+		errx(1, "a tty is required");
+	}
+	if (!auth_userresponse(as, response, 0)) {
+		explicit_bzero(rbuf, sizeof(rbuf));
+		syslog(LOG_AUTHPRIV | LOG_NOTICE,
+		    "failed auth for %s", myname);
+		warnx("Authentication failed");
+		return AUTH_FAILED;
+	}
+	explicit_bzero(rbuf, sizeof(rbuf));
+	return AUTH_OK;
+}
+
+static void
+authuser(char *myname, char *login_style, int persist)
+{
+	int i, fd = -1;
+
+	if (persist)
+		fd = open("/dev/tty", O_RDWR);
+	if (fd != -1) {
+		if (ioctl(fd, TIOCCHKVERAUTH) == 0)
+			goto good;
+	}
+	for (i = 0; i < AUTH_RETRIES; i++) {
+		if (authuser_checkpass(myname, login_style) == AUTH_OK)
+			goto good;
+	}
+	exit(1);
+good:
+	if (fd != -1) {
+		int secs = 5 * 60;
+		ioctl(fd, TIOCSETVERAUTH, &secs);
+		close(fd);
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	const char *p;
+	char *const cmd[] = { NSH_REXEC_PATH_STR, "-e", NULL };
+	char mypwbuf[_PW_BUF_LEN], targpwbuf[_PW_BUF_LEN];
+	struct passwd mypwstore, targpwstore;
+	struct passwd *mypw, *targpw;
+	const struct rule *rule;
+	uid_t uid;
+	uid_t target = 0;
+	gid_t groups[NGROUPS_MAX + 1];
+	int ngroups;
+	int ch, rv;
+	int nflag = 0;
+	char cwdpath[PATH_MAX];
+	const char *cwd;
+	const char *errstr;
+	char *login_style = NULL;
+	char **envp = NULL;
+	int nshfd = -1, action;
+
+	setprogname("nshdoas");
+
+	uid = getuid();
+
+	while ((ch = getopt(argc, argv, "a:F:n:")) != -1) {
+		switch (ch) {
+		case 'a':
+			login_style = optarg;
+			break;
+		case 'F':
+			nshfd = strtonum(optarg, STDERR_FILENO + 1, INT_MAX,
+			    &errstr);
+			if (errstr)
+				err(1, "nshfd is %s", errstr);
+			printf("%s: nshfd is %d\n", getprogname(), nshfd);
+			break;
+		case 'n':
+			nflag = 1;
+			break;
+		default:
+			usage();
+			break;
+		}
+	}
+	argv += optind;
+	argc -= optind;
+
+	if (argc)
+		usage();
+
+	if (nshfd == -1)
+		closefrom(STDERR_FILENO + 1);
+	else
+		closefrom(nshfd + 1);
+
+	rv = getpwuid_r(uid, &mypwstore, mypwbuf, sizeof(mypwbuf), &mypw);
+	if (rv != 0)
+		err(1, "getpwuid_r failed");
+	if (mypw == NULL)
+		errx(1, "no passwd entry for self");
+	ngroups = getgroups(NGROUPS_MAX, groups);
+	if (ngroups == -1)
+		err(1, "can't get groups");
+	groups[ngroups++] = getgid();
+
+	if (geteuid())
+		errx(1, "not installed setuid");
+
+	rv = getpwuid_r(target, &targpwstore, targpwbuf, sizeof(targpwbuf), &targpw);
+	if (rv != 0)
+		err(1, "getpwuid_r failed");
+	if (targpw == NULL)
+		errx(1, "no passwd entry for target");
+
+	parseconfig("/etc/doas.conf", 1);
+
+	action = permit(uid, groups, ngroups, &rule, target, cmd[0],
+	    (const char **)cmd + 1);
+	if (action == 0) {
+		printf("%% No rule for %s found in /etc/doas.conf; "
+		    "root password required\n", mypw->pw_name);
+		authuser(targpw->pw_name, login_style, 0);
+		rule = NULL;
+	} else {
+		if (action != PERMIT) {
+			syslog(LOG_AUTHPRIV | LOG_NOTICE,
+			    "command not permitted for %s: %s",
+			    mypw->pw_name, cmd[0]);
+			errc(1, EPERM, NULL);
+		}
+		if (!(rule->options & NOPASS)) {
+			if (nflag)
+				errx(1, "Authentication required");
+
+			authuser(mypw->pw_name, login_style,
+			    rule->options & PERSIST);
+		}
+	}
+
+	if ((p = getenv("PATH")) != NULL)
+		formerpath = strdup(p);
+	if (formerpath == NULL)
+		formerpath = "";
+
+	if (unveil(_PATH_LOGIN_CONF, "r") == -1)
+		err(1, "unveil %s", _PATH_LOGIN_CONF);
+	if (unveil(_PATH_LOGIN_CONF ".db", "r") == -1)
+		err(1, "unveil %s.db", _PATH_LOGIN_CONF);
+	if (unveil(_PATH_LOGIN_CONF_D, "r") == -1)
+		err(1, "unveil %s", _PATH_LOGIN_CONF_D);
+	if (unveil(NSH_REXEC_PATH_STR, "x") == -1)
+		err(1, "unveil %s", NSH_REXEC_PATH_STR);
+
+	if (pledge("stdio rpath getpw exec id", NULL) == -1)
+		err(1, "pledge");
+
+	if (setusercontext(NULL, targpw, target, LOGIN_SETGROUP |
+	    LOGIN_SETPATH |
+	    LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
+	    LOGIN_SETUSER | LOGIN_SETENV | LOGIN_SETRTABLE) != 0)
+		errx(1, "failed to set user context for target");
+
+	if (pledge("stdio rpath exec", NULL) == -1)
+		err(1, "pledge");
+
+	if (getcwd(cwdpath, sizeof(cwdpath)) == NULL)
+		cwd = "(failed)";
+	else
+		cwd = cwdpath;
+
+	if (pledge("stdio exec", NULL) == -1)
+		err(1, "pledge");
+
+	if (rule == NULL || !(rule->options & NOLOG)) {
+		syslog(LOG_AUTHPRIV | LOG_INFO,
+		    "%s ran command %s as %s from %s",
+		    mypw->pw_name, cmd[0], targpw->pw_name, cwd);
+	}
+
+	if (rule)
+		envp = prepenv(rule, mypw, targpw);
+
+	/* setusercontext set path for the next process, so reset it for us */
+	if (setenv("PATH", formerpath, 1) == -1)
+		err(1, "failed to set PATH '%s'", formerpath);
+
+	if (nshfd != -1) {
+		/*
+		 * Redirect stdin to the nsh process pipe.
+		 * Commands will arrive here.
+		 */
+		fpurge(stdin);
+		if (dup2(nshfd, STDIN_FILENO) == -1)
+			err(1, "dup2");
+	}
+
+	execvpe(cmd[0], cmd, envp);
+	if (errno == ENOENT)
+		errx(1, "%s: command not found", cmd[0]);
+	err(1, "%s", cmd[0]);
+}
blob - /dev/null
blob + 604becb5445077766203a899c2923610e2745869 (mode 644)
--- /dev/null
+++ nshdoas/parse.y
@@ -0,0 +1,354 @@
+/* $OpenBSD: parse.y,v 1.31 2022/03/22 20:36:49 deraadt Exp $ */
+/*
+ * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+#include <sys/types.h>
+#include <ctype.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <err.h>
+
+#include "doas.h"
+
+typedef struct {
+	union {
+		struct {
+			int action;
+			int options;
+			const char *cmd;
+			const char **cmdargs;
+			const char **envlist;
+		};
+		const char **strlist;
+		const char *str;
+	};
+	unsigned long lineno;
+	unsigned long colno;
+} yystype;
+#define YYSTYPE yystype
+
+FILE *yyfp;
+
+struct rule **rules;
+size_t nrules;
+static size_t maxrules;
+
+int parse_error = 0;
+
+static void yyerror(const char *, ...);
+static int yylex(void);
+
+static size_t
+arraylen(const char **arr)
+{
+	size_t cnt = 0;
+
+	while (*arr) {
+		cnt++;
+		arr++;
+	}
+	return cnt;
+}
+
+%}
+
+%token TPERMIT TDENY TAS TCMD TARGS
+%token TNOPASS TNOLOG TPERSIST TKEEPENV TSETENV
+%token TSTRING
+
+%%
+
+grammar:	/* empty */
+		| grammar '\n'
+		| grammar rule '\n'
+		| error '\n'
+		;
+
+rule:		action ident target cmd {
+			struct rule *r;
+
+			r = calloc(1, sizeof(*r));
+			if (!r)
+				errx(1, "can't allocate rule");
+			r->action = $1.action;
+			r->options = $1.options;
+			r->envlist = $1.envlist;
+			r->ident = $2.str;
+			r->target = $3.str;
+			r->cmd = $4.cmd;
+			r->cmdargs = $4.cmdargs;
+			if (nrules == maxrules) {
+				if (maxrules == 0)
+					maxrules = 32;
+				rules = reallocarray(rules, maxrules,
+				    2 * sizeof(*rules));
+				if (!rules)
+					errx(1, "can't allocate rules");
+				maxrules *= 2;
+			}
+			rules[nrules++] = r;
+		} ;
+
+action:		TPERMIT options {
+			$$.action = PERMIT;
+			$$.options = $2.options;
+			$$.envlist = $2.envlist;
+		} | TDENY {
+			$$.action = DENY;
+			$$.options = 0;
+			$$.envlist = NULL;
+		} ;
+
+options:	/* none */ {
+			$$.options = 0;
+			$$.envlist = NULL;
+		} | options option {
+			$$.options = $1.options | $2.options;
+			$$.envlist = $1.envlist;
+			if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) {
+				yyerror("can't combine nopass and persist");
+				YYERROR;
+			}
+			if ($2.envlist) {
+				if ($$.envlist) {
+					yyerror("can't have two setenv sections");
+					YYERROR;
+				} else
+					$$.envlist = $2.envlist;
+			}
+		} ;
+option:		TNOPASS {
+			$$.options = NOPASS;
+			$$.envlist = NULL;
+		} | TNOLOG {
+			$$.options = NOLOG;
+			$$.envlist = NULL;
+		} | TPERSIST {
+			$$.options = PERSIST;
+			$$.envlist = NULL;
+		} | TKEEPENV {
+			$$.options = KEEPENV;
+			$$.envlist = NULL;
+		} | TSETENV '{' strlist '}' {
+			$$.options = 0;
+			$$.envlist = $3.strlist;
+		} ;
+
+strlist:	/* empty */ {
+			if (!($$.strlist = calloc(1, sizeof(char *))))
+				errx(1, "can't allocate strlist");
+		} | strlist TSTRING {
+			int nstr = arraylen($1.strlist);
+
+			if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
+			    sizeof(char *))))
+				errx(1, "can't allocate strlist");
+			$$.strlist[nstr] = $2.str;
+			$$.strlist[nstr + 1] = NULL;
+		} ;
+
+
+ident:		TSTRING {
+			$$.str = $1.str;
+		} ;
+
+target:		/* optional */ {
+			$$.str = NULL;
+		} | TAS TSTRING {
+			$$.str = $2.str;
+		} ;
+
+cmd:		/* optional */ {
+			$$.cmd = NULL;
+			$$.cmdargs = NULL;
+		} | TCMD TSTRING args {
+			$$.cmd = $2.str;
+			$$.cmdargs = $3.cmdargs;
+		} ;
+
+args:		/* empty */ {
+			$$.cmdargs = NULL;
+		} | TARGS strlist {
+			$$.cmdargs = $2.strlist;
+		} ;
+
+%%
+
+void
+yyerror(const char *fmt, ...)
+{
+	va_list va;
+
+	fprintf(stderr, "doas: ");
+	va_start(va, fmt);
+	vfprintf(stderr, fmt, va);
+	va_end(va);
+	fprintf(stderr, " at line %lu\n", yylval.lineno + 1);
+	parse_error = 1;
+}
+
+static struct keyword {
+	const char *word;
+	int token;
+} keywords[] = {
+	{ "deny", TDENY },
+	{ "permit", TPERMIT },
+	{ "as", TAS },
+	{ "cmd", TCMD },
+	{ "args", TARGS },
+	{ "nopass", TNOPASS },
+	{ "nolog", TNOLOG },
+	{ "persist", TPERSIST },
+	{ "keepenv", TKEEPENV },
+	{ "setenv", TSETENV },
+};
+
+int
+yylex(void)
+{
+	char buf[1024], *ebuf, *p, *str;
+	int c, quoted = 0, quotes = 0, qerr = 0, escape = 0, nonkw = 0;
+	unsigned long qpos = 0;
+	size_t i;
+
+	p = buf;
+	ebuf = buf + sizeof(buf);
+
+repeat:
+	/* skip whitespace first */
+	for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
+		yylval.colno++;
+
+	/* check for special one-character constructions */
+	switch (c) {
+		case '\n':
+			yylval.colno = 0;
+			yylval.lineno++;
+			/* FALLTHROUGH */
+		case '{':
+		case '}':
+			return c;
+		case '#':
+			/* skip comments; NUL is allowed; no continuation */
+			while ((c = getc(yyfp)) != '\n')
+				if (c == EOF)
+					goto eof;
+			yylval.colno = 0;
+			yylval.lineno++;
+			return c;
+		case EOF:
+			goto eof;
+	}
+
+	/* parsing next word */
+	for (;; c = getc(yyfp), yylval.colno++) {
+		switch (c) {
+		case '\0':
+			yyerror("unallowed character NUL in column %lu",
+			    yylval.colno + 1);
+			escape = 0;
+			continue;
+		case '\\':
+			escape = !escape;
+			if (escape)
+				continue;
+			break;
+		case '\n':
+			if (quotes && !qerr) {
+				yyerror("unterminated quotes in column %lu",
+				    qpos + 1);
+				qerr = 1;
+			}
+			if (escape) {
+				nonkw = 1;
+				escape = 0;
+				yylval.colno = ULONG_MAX;
+				yylval.lineno++;
+				continue;
+			}
+			goto eow;
+		case EOF:
+			if (escape)
+				yyerror("unterminated escape in column %lu",
+				    yylval.colno);
+			if (quotes && !qerr)
+				yyerror("unterminated quotes in column %lu",
+				    qpos + 1);
+			goto eow;
+		case '{':
+		case '}':
+		case '#':
+		case ' ':
+		case '\t':
+			if (!escape && !quotes)
+				goto eow;
+			break;
+		case '"':
+			if (!escape) {
+				quoted = 1;
+				quotes = !quotes;
+				if (quotes) {
+					nonkw = 1;
+					qerr = 0;
+					qpos = yylval.colno;
+				}
+				continue;
+			}
+		}
+		*p++ = c;
+		if (p == ebuf) {
+			yyerror("too long line");
+			p = buf;
+		}
+		escape = 0;
+	}
+
+eow:
+	*p = 0;
+	if (c != EOF)
+		ungetc(c, yyfp);
+	if (p == buf) {
+		/*
+		 * There could be a number of reasons for empty buffer,
+		 * and we handle all of them here, to avoid cluttering
+		 * the main loop.
+		 */
+		if (c == EOF)
+			goto eof;
+		else if (!quoted)    /* accept, e.g., empty args: cmd foo args "" */
+			goto repeat;
+	}
+	if (!nonkw) {
+		for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
+			if (strcmp(buf, keywords[i].word) == 0)
+				return keywords[i].token;
+		}
+	}
+	if ((str = strdup(buf)) == NULL)
+		err(1, "%s", __func__);
+	yylval.str = str;
+	return TSTRING;
+
+eof:
+	if (ferror(yyfp))
+		yyerror("input error reading config");
+	return 0;
+}