opendoas

A portable version of the OpenBSD `doas` command
git clone https://pi.duncano.de/git/opendoas.git
Log | Files | Refs | README | LICENSE

commit 7f11114f0f07c653e0ea3d4ae093d7dcdda4a4ef
parent a55cefe3d13a0e6c66d4d58352bb09e1e8f5282b
Author: Duncaen <mail@duncano.de>
Date:   Sun,  5 Jun 2016 13:33:36 +0200

sync with upstream (setenv)

add a doas.conf setenv directive that allows setting environment
variables explicitly and by copying existing environment variables
of a different name. E.g.

permit nopass setenv { PS1=$SUDO_PS1 FOO=bar } keepenv :wheel

ok tedu@ benno@

Diffstat:
doas.c | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
doas.h | 4+++-
parse.y | 72+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
3 files changed, 144 insertions(+), 11 deletions(-)

diff --git a/doas.c b/doas.c @@ -1,4 +1,4 @@ -/* $OpenBSD: doas.c,v 1.52 2016/04/28 04:48:56 tedu Exp $ */ +/* $OpenBSD: doas.c,v 1.53 2016/06/05 00:46:34 djm Exp $ */ /* * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org> * @@ -289,6 +289,81 @@ copyenv(const char **oldenvp, struct rule *rule) return envp; } +/* find index of 'name' in environment envp */ +static int +findenv(const char **envp, const char *name, size_t namelen) +{ + int i; + + for (i = 0 ; envp[i] != NULL; i++) { + if (strlen(envp[i]) < namelen + 1) + continue; + if (strncmp(envp[i], name, namelen) == 0 && + envp[i][namelen] == '=') + return i; + } + return -1; +} + +/* merge rule->setenvlist into environment list; frees oldenvp */ +static char ** +dosetenv(char **oldenvp, struct rule *rule) +{ + size_t n, i, nset, nold; + char **envp, *cp, *cp2; + int found; + + if (!(rule->options & SETENV)) + return oldenvp; + + nset = arraylen(rule->setenvlist); + nold = arraylen((const char**)oldenvp); + + /* insert new variables */ + n = 0; + envp = NULL; + for (i = 0; i < nset; i++) { + if ((cp = strchr(rule->setenvlist[i], '=')) == NULL) + errx(1, "invalid setenv"); /* shouldn't happen */ + if (cp[1] == '\0' || cp - rule->setenvlist[i] > INT_MAX) + continue; /* skip variables with empty values */ + if ((envp = reallocarray(envp, n + 2, sizeof(*envp))) == NULL) + errx(1, "reallocarray failed"); + if (cp[1] == '$') { + /* FOO=$BAR: lookup and copy */ + if ((cp2 = getenv(cp + 2)) == NULL) + continue; /* not found; skip */ + if (asprintf(&(envp[n++]), "%.*s=%s", + (int)(cp - rule->setenvlist[i]), + rule->setenvlist[i], cp2) == -1) + errx(1, "asprintf failed"); + continue; + } else { + /* plain setenv */ + if ((envp[n++] = strdup(rule->setenvlist[i])) == NULL) + errx(1, "strdup failed"); + } + } + /* move old variables, dropping ones already set */ + for (i = 0; i < nold; i++) { + if ((cp = strchr(oldenvp[i], '=')) == NULL) + errx(1, "invalid env"); /* shouldn't happen */ + found = findenv(rule->setenvlist, oldenvp[i], cp - oldenvp[i]); + if (found != -1) + free(oldenvp[i]); /* discard */ + else { + if ((envp = reallocarray(envp, n + 2, + sizeof(*envp))) == NULL) + errx(1, "reallocarray failed"); + envp[n++] = oldenvp[i]; /* move */ + } + } + free(oldenvp); + if (n > 0) + envp[n] = NULL; + return envp; +} + static void __dead checkconfig(const char *confpath, int argc, char **argv, uid_t uid, gid_t *groups, int ngroups, uid_t target) @@ -511,6 +586,8 @@ main(int argc, char **argv, char **envp) envp = copyenv((const char **)envp, rule); + envp = dosetenv(envp, rule); + if (rule->cmd) { if (setenv("PATH", safepath, 1) == -1) err(1, "failed to set PATH '%s'", safepath); diff --git a/doas.h b/doas.h @@ -1,4 +1,4 @@ -/* $OpenBSD: doas.h,v 1.3 2015/07/21 11:04:06 zhuk Exp $ */ +/* $OpenBSD: doas.h,v 1.5 2016/06/05 00:46:34 djm Exp $ */ struct rule { int action; @@ -8,6 +8,7 @@ struct rule { const char *cmd; const char **cmdargs; const char **envlist; + const char **setenvlist; }; extern struct rule **rules; @@ -21,3 +22,4 @@ size_t arraylen(const char **); #define NOPASS 0x1 #define KEEPENV 0x2 +#define SETENV 0x4 diff --git a/parse.y b/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.10 2015/07/24 06:36:42 zhuk Exp $ */ +/* $OpenBSD: parse.y,v 1.16 2016/06/05 00:46:34 djm Exp $ */ /* * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org> * @@ -18,13 +18,13 @@ %{ #include <sys/types.h> #include <ctype.h> -#include <err.h> +#include <unistd.h> +#include <stdint.h> #include <stdarg.h> #include <stdio.h> -#include <stdint.h> #include <stdlib.h> #include <string.h> -#include <unistd.h> +#include <err.h> #include "openbsd.h" @@ -38,6 +38,7 @@ typedef struct { const char *cmd; const char **cmdargs; const char **envlist; + const char **setenvlist; }; const char *str; }; @@ -59,7 +60,7 @@ int yyparse(void); %} %token TPERMIT TDENY TAS TCMD TARGS -%token TNOPASS TKEEPENV +%token TNOPASS TKEEPENV TSETENV %token TSTRING %% @@ -78,6 +79,7 @@ rule: action ident target cmd { r->action = $1.action; r->options = $1.options; r->envlist = $1.envlist; + r->setenvlist = $1.setenvlist; r->ident = $2.str; r->target = $3.str; r->cmd = $4.cmd; @@ -98,6 +100,7 @@ action: TPERMIT options { $$.action = PERMIT; $$.options = $2.options; $$.envlist = $2.envlist; + $$.setenvlist = $2.setenvlist; } | TDENY { $$.action = DENY; } ; @@ -115,6 +118,14 @@ options: /* none */ { } else $$.envlist = $2.envlist; } + $$.setenvlist = $1.setenvlist; + if ($2.setenvlist) { + if ($$.setenvlist) { + yyerror("can't have two setenv sections"); + YYERROR; + } else + $$.setenvlist = $2.setenvlist; + } } ; option: TNOPASS { $$.options = NOPASS; @@ -125,10 +136,16 @@ option: TNOPASS { } | TKEEPENV '{' envlist '}' { $$.options = KEEPENV; $$.envlist = $3.envlist; + } | TSETENV '{' setenvlist '}' { + $$.options = SETENV; + $$.setenvlist = NULL; + $$.setenvlist = $3.setenvlist; } ; envlist: /* empty */ { $$.envlist = NULL; + if (!($$.envlist = calloc(1, sizeof(char *)))) + errx(1, "can't allocate envlist"); } | envlist TSTRING { int nenv = arraylen($1.envlist); if (!($$.envlist = reallocarray($1.envlist, nenv + 2, @@ -138,6 +155,28 @@ envlist: /* empty */ { $$.envlist[nenv + 1] = NULL; } +setenvlist: /* empty */ { + if (!($$.setenvlist = calloc(1, sizeof(char *)))) + errx(1, "can't allocate setenvlist"); + } | setenvlist TSTRING '=' TSTRING { + int nenv = arraylen($1.setenvlist); + char *cp = NULL; + + if (*$2.str == '\0' || strchr($2.str, '=') != NULL) { + yyerror("invalid setenv expression"); + YYERROR; + } + if (!($$.setenvlist = reallocarray($1.setenvlist, + nenv + 2, sizeof(char *)))) + errx(1, "can't allocate envlist"); + $$.setenvlist[nenv] = NULL; + if (asprintf(&cp, "%s=%s", $2.str, $4.str) <= 0 || + cp == NULL) + errx(1,"asprintf failed"); + $$.setenvlist[nenv] = cp; + $$.setenvlist[nenv + 1] = NULL; + } + ident: TSTRING { $$.str = $1.str; @@ -165,6 +204,8 @@ args: /* empty */ { argslist: /* empty */ { $$.cmdargs = NULL; + if (!($$.cmdargs = calloc(1, sizeof(char *)))) + errx(1, "can't allocate args"); } | argslist TSTRING { int nargs = arraylen($1.cmdargs); if (!($$.cmdargs = reallocarray($1.cmdargs, nargs + 2, @@ -181,6 +222,7 @@ yyerror(const char *fmt, ...) { va_list va; + fprintf(stderr, "doas: "); va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); @@ -199,6 +241,7 @@ struct keyword { { "args", TARGS }, { "nopass", TNOPASS }, { "keepenv", TKEEPENV }, + { "setenv", TSETENV }, }; int @@ -223,17 +266,18 @@ repeat: /* FALLTHROUGH */ case '{': case '}': + case '=': return c; case '#': /* skip comments; NUL is allowed; no continuation */ while ((c = getc(yyfp)) != '\n') if (c == EOF) - return 0; + goto eof; yylval.colno = 0; yylval.lineno++; return c; case EOF: - return 0; + goto eof; } /* parsing next word */ @@ -256,6 +300,8 @@ repeat: if (escape) { nonkw = 1; escape = 0; + yylval.colno = 0; + yylval.lineno++; continue; } goto eow; @@ -273,6 +319,7 @@ repeat: case '#': case ' ': case '\t': + case '=': if (!escape && !quotes) goto eow; break; @@ -287,8 +334,10 @@ repeat: } } *p++ = c; - if (p == ebuf) + if (p == ebuf) { yyerror("too long line"); + p = buf; + } escape = 0; } @@ -303,7 +352,7 @@ eow: * the main loop. */ if (c == EOF) - return 0; + goto eof; else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */ goto repeat; } @@ -318,4 +367,9 @@ eow: err(1, "strdup"); yylval.str = str; return TSTRING; + +eof: + if (ferror(yyfp)) + yyerror("input error reading config"); + return 0; }