commit - 98fc2f167ba8e7efae68c15faa0430e674146adf
commit + 813bc04fe92156f9bee8cb5d318e2e2fc39fceca
blob - d0ea33ff297130b499703d8f068c954783abcdb3
blob + b32106012fc6ebd934f47d5fcf9542f73853662d
--- Makefile
+++ Makefile
#
PROG= nsh
-SUBDIR += bgpnsh
+SUBDIR += bgpnsh nshdoas
.PHONY: release dist
DEBUG?=-O0 -g
.endif
-NSH_REXEC_PATH?=/usr/local/bin/nsh
.if make(install)
DESTDIR?=/usr/local
#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
#
PROG= bgpnsh
-.PATH=${.CURDIR}/..
+.PATH=${.CURDIR}/..:${.CURDIR}
.include "../nsh-version.mk"
blob - e3a2faba7bbc29b9f4aabf953849d7af19c066a1
blob + f2b65a168c2ad92a6763a28bfe15066b933f20f1
--- cmdargs.c
+++ cmdargs.c
#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>
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;
}
break;
default:
+ if (pipefd)
+ return 0;
signal(SIGALRM, sigalarm);
wait(&status); /* Wait for cmd to complete */
if (WIFEXITED(status)) /* normal exit? */
}
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
#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" */
/* 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
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
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
+#include <sys/socket.h>
#include "externs.h"
int read_pass(char *, size_t);
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;
}
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);
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
+obj/**
blob - /dev/null
blob + 89ad14b1c093f2143b239a8c9c2000cc7feeefad (mode 644)
--- /dev/null
+++ nshdoas/Makefile
+#
+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
+/* $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
+/* $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
+/* $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
+/* $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;
+}