opendoas

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

commit 5e9d76849fdad47f59b11993ee8ca319bd11414d
parent 555da719ab036a43d3ec8636840f5e74e1ff97e0
Author: tedu <tedu>
Date:   Mon, 27 Jun 2016 15:41:17 +0000

revise environment handling. Add a setenv keyword for manipulating the environment. keepenv now means only retain everything. (for one release, the old use of keepenv will still work.) Allow setting variables to new or existing values, and also removing vars when keepenv is used. ok djm martijn tb

Diffstat:
doas.conf.5 | 24+++++++++++++++++-------
env.c | 185+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
parse.y | 17++++++++++++++---
3 files changed, 137 insertions(+), 89 deletions(-)

diff --git a/doas.conf.5 b/doas.conf.5 @@ -57,9 +57,16 @@ The default is to reset the environment, except for the variables .Ev USER and .Ev USERNAME . -.It Ic keepenv { Oo Ar variable ... Oc Ic } +.It Ic setenv { Oo Ar variable ... Oc Ic Oo Ar variable=value ... Oc Ic } In addition to the variables mentioned above, keep the space-separated specified variables. +Variables may also be removed with a leading - or set using the latter syntax. +If the first character of +.Ar value +is a +.Ql $ +then the value to be set is taken from the existing environment +variable of the same name. .El .It Ar identity The username to match. @@ -110,21 +117,24 @@ it isn't considered a keyword. The following example permits users in group wsrc to build ports, wheel to execute commands as any user while keeping the environment variables -.Ev ENV , -.Ev PS1 , +.Ev PS1 +and +.Ev SSH_AUTH_SOCK and -.Ev SSH_AUTH_SOCK , -and additionally permits tedu to run procmap as root without a password. +unsetting +.Ev ENV , +permits tedu to run procmap as root without a password, +and additionally permits root to run unrestricted commands as itself. .Bd -literal -offset indent # Non-exhaustive list of variables needed to # build release(8) and ports(7) -permit nopass keepenv { \e +permit nopass setenv { \e FTPMODE PKG_CACHE PKG_PATH SM_PATH SSH_AUTH_SOCK \e DESTDIR DISTDIR FETCH_CMD FLAVOR GROUP MAKE MAKECONF \e MULTI_PACKAGES NOMAN OKAY_FILES OWNER PKG_DBDIR \e PKG_DESTDIR PKG_TMPDIR PORTSDIR RELEASEDIR SHARED_ONLY \e SUBPACKAGE WRKOBJDIR SUDO_PORT_V1 } :wsrc -permit nopass keepenv { ENV PS1 SSH_AUTH_SOCK } :wheel +permit nopass setenv { -ENV PS1=$DOAS_PS1 SSH_AUTH_SOCK } :wheel permit nopass tedu as root cmd /usr/sbin/procmap .Ed .Sh SEE ALSO diff --git a/env.c b/env.c @@ -39,19 +39,38 @@ struct env { u_int count; }; -int +static int envcmp(struct envnode *a, struct envnode *b) { return strcmp(a->key, b->key); } RB_GENERATE_STATIC(envtree, envnode, node, envcmp) -struct env *createenv(char **); -struct env *filterenv(struct env *, struct rule *); -char **flattenenv(struct env *); +static struct envnode * +createnode(const char *key, const char *value) +{ + struct envnode *node; -struct env * -createenv(char **envp) + 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 struct env * +createenv(struct rule *rule) { struct env *env; u_int i; @@ -62,34 +81,40 @@ createenv(char **envp) RB_INIT(&env->root); env->count = 0; - for (i = 0; envp[i] != NULL; i++) { - struct envnode *node; - const char *e, *eq; - - e = envp[i]; - - if ((eq = strchr(e, '=')) == NULL || eq == e) - continue; - node = malloc(sizeof(*node)); - if (!node) - err(1, NULL); - node->key = strndup(envp[i], eq - e); - node->value = strdup(eq + 1); - if (!node->key || !node->value) - err(1, NULL); - if (RB_FIND(envtree, &env->root, node)) { - free((char *)node->key); - free((char *)node->value); - free(node); - } else { - RB_INSERT(envtree, &env->root, node); - env->count++; + 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 keybuf[1024]; + + e = environ[i]; + + /* ignore invalid or overlong names */ + if ((eq = strchr(e, '=')) == NULL || eq == e) + continue; + len = eq - e; + if (len > sizeof(keybuf) - 1) + continue; + memcpy(keybuf, e, len); + keybuf[len] = '\0'; + + node = createnode(keybuf, eq + 1); + if (RB_INSERT(envtree, &env->root, node)) { + /* ignore any later duplicates */ + freenode(node); + } else { + env->count++; + } } } + return env; } -char ** +static char ** flattenenv(struct env *env) { char **envp; @@ -110,72 +135,74 @@ flattenenv(struct env *env) } static void -copyenv(struct env *orig, struct env *copy, const char **envlist) +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++) { - key.key = envlist[i]; - if ((node = RB_FIND(envtree, &orig->root, &key))) { - RB_REMOVE(envtree, &orig->root, node); - orig->count--; - RB_INSERT(envtree, &copy->root, node); - copy->count++; + 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 == '$') + val = getenv(val + 1); + } 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++; } } } -struct env * -filterenv(struct env *orig, struct rule *rule) +char ** +prepenv(struct rule *rule) { - const char *safeset[] = { + static const char *safeset[] = { "DISPLAY", "HOME", "LOGNAME", "MAIL", "PATH", "TERM", "USER", "USERNAME", NULL }; - const char *badset[] = { - "ENV", - NULL - }; - struct env *copy; - struct envnode *node, key; - u_int i; - - if ((rule->options & KEEPENV) && !rule->envlist) { - for (i = 0; badset[i]; i++) { - key.key = badset[i]; - if ((node = RB_FIND(envtree, &orig->root, &key))) { - RB_REMOVE(envtree, &orig->root, node); - free((char *)node->key); - free((char *)node->value); - free(node); - orig->count--; - } - } - return orig; - } - - copy = malloc(sizeof(*copy)); - if (!copy) - err(1, NULL); - RB_INIT(&copy->root); - copy->count = 0; + struct env *env; + + env = createenv(rule); + /* if we started with blank, fill some defaults then apply rules */ + if (!(rule->options & KEEPENV)) + fillenv(env, safeset); if (rule->envlist) - copyenv(orig, copy, rule->envlist); - copyenv(orig, copy, safeset); - - return copy; -} + fillenv(env, rule->envlist); -char ** -prepenv(struct rule *rule) -{ - extern char **environ; - struct env *env; - - env = createenv(environ); - env = filterenv(env, rule); return flattenenv(env); } diff --git a/parse.y b/parse.y @@ -51,6 +51,7 @@ FILE *yyfp; struct rule **rules; int nrules, maxrules; int parse_errors = 0; +int obsolete_warned = 0; void yyerror(const char *, ...); int yylex(void); @@ -59,7 +60,7 @@ int yyparse(void); %} %token TPERMIT TDENY TAS TCMD TARGS -%token TNOPASS TKEEPENV +%token TNOPASS TKEEPENV TSETENV %token TSTRING %% @@ -100,6 +101,8 @@ action: TPERMIT options { $$.envlist = $2.envlist; } | TDENY { $$.action = DENY; + $$.options = 0; + $$.envlist = NULL; } ; options: /* none */ { @@ -110,7 +113,7 @@ options: /* none */ { $$.envlist = $1.envlist; if ($2.envlist) { if ($$.envlist) { - yyerror("can't have two keepenv sections"); + yyerror("can't have two setenv sections"); YYERROR; } else $$.envlist = $2.envlist; @@ -123,7 +126,14 @@ option: TNOPASS { $$.options = KEEPENV; $$.envlist = NULL; } | TKEEPENV '{' envlist '}' { - $$.options = KEEPENV; + $$.options = 0; + if (!obsolete_warned) { + warnx("keepenv with list is obsolete"); + obsolete_warned = 1; + } + $$.envlist = $3.envlist; + } | TSETENV '{' envlist '}' { + $$.options = 0; $$.envlist = $3.envlist; } ; @@ -199,6 +209,7 @@ struct keyword { { "args", TARGS }, { "nopass", TNOPASS }, { "keepenv", TKEEPENV }, + { "setenv", TSETENV }, }; int