diff --git a/doas.1 b/doas.1 index f55355c..7a61144 100644 --- a/doas.1 +++ b/doas.1 @@ -25,6 +25,7 @@ .Op Fl a Ar style .Op Fl C Ar config .Op Fl u Ar user +.Op Fl - .Ar command .Op Ar args .Sh DESCRIPTION @@ -80,6 +81,8 @@ or Execute the command as .Ar user . The default is root. +.It Fl - +Any dashes after a combined double dash (--) will be interpreted as part of the command to be run or its paramters. Not an argument passed to doas itself. .El .Sh EXIT STATUS .Ex -std doas @@ -88,7 +91,7 @@ It may fail for one of the following reasons: .Bl -bullet -compact .It The config file -.Pa /etc/doas.conf +.Pa /usr/local/etc/doas.conf could not be parsed. .It The user attempted to run a command which is not permitted. @@ -106,4 +109,4 @@ The command first appeared in .Ox 5.8 . .Sh AUTHORS -.An Ted Unangst Aq Mt tedu@openbsd.org \ No newline at end of file +.An Ted Unangst Aq Mt tedu@openbsd.org diff --git a/doas.c b/doas.c index 571c95c..60beffb 100644 --- a/doas.c +++ b/doas.c @@ -17,6 +17,7 @@ #include #include +#include #if defined(HAVE_INTTYPES_H) #include @@ -39,6 +40,7 @@ #include #include #include +#include #if defined(HAVE_LOGIN_CAP_H) #include @@ -83,18 +85,6 @@ errc(int eval, int code, const char *format) } #endif -size_t -arraylen(const char **arr) -{ - size_t cnt = 0; - - while (*arr) { - cnt++; - arr++; - } - return cnt; -} - static int parseuid(const char *s, uid_t *uid) { @@ -254,6 +244,54 @@ checkconfig(const char *confpath, int argc, char **argv, } } +#if defined(USE_BSD_AUTH) +static void +authuser(char *myname, char *login_style, int persist) +{ + char *challenge = NULL, *response, rbuf[1024], cbuf[128]; + auth_session_t *as; + int fd = -1; + + if (persist) + fd = open("/dev/tty", O_RDWR); + if (fd != -1) { + if (ioctl(fd, TIOCCHKVERAUTH) == 0) + goto good; + } + + if (!(as = auth_userchallenge(myname, login_style, "auth-doas", + &challenge))) + errx(1, "Authorization failed"); + if (!challenge) { + char host[HOST_NAME_MAX + 1]; + if (gethostname(host, sizeof(host))) + snprintf(host, sizeof(host), "?"); + snprintf(cbuf, sizeof(cbuf), + "\rdoas (%.32s@%.32s) password: ", 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)) { + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "failed auth for %s", myname); + errc(1, EPERM, NULL); + } + explicit_bzero(rbuf, sizeof(rbuf)); +good: + if (fd != -1) { + int secs = 5 * 60; + ioctl(fd, TIOCSETVERAUTH, &secs); + close(fd); + } +} +#endif + int main(int argc, char **argv) { @@ -283,11 +321,6 @@ main(int argc, char **argv) setprogname("doas"); #endif - /* - if (pledge("stdio rpath getpw tty proc exec id", NULL) == -1) - err(1, "pledge"); - */ - #ifndef linux closefrom(STDERR_FILENO + 1); #endif @@ -295,6 +328,7 @@ main(int argc, char **argv) uid = getuid(); while ((ch = getopt(argc, argv, "a:C:nsu:")) != -1) { +/* while ((ch = getopt(argc, argv, "a:C:Lnsu:")) != -1) { */ switch (ch) { case 'a': login_style = optarg; @@ -302,6 +336,12 @@ main(int argc, char **argv) case 'C': confpath = optarg; break; +/* case 'L': + i = open("/dev/tty", O_RDWR); + if (i != -1) + ioctl(i, TIOCCLRVERAUTH); + exit(i != -1); +*/ case 'u': if (parseuid(optarg, &target) != 0) errx(1, "unknown user"); @@ -343,9 +383,11 @@ main(int argc, char **argv) if (sflag) { sh = getenv("SHELL"); - if (sh == NULL || *sh == '\0') - shargv[0] = pw->pw_shell; - else + if (sh == NULL || *sh == '\0') { + shargv[0] = strdup(pw->pw_shell); + if (shargv[0] == NULL) + err(1, NULL); + } else shargv[0] = sh; argv = shargv; argc = 1; @@ -357,11 +399,14 @@ main(int argc, char **argv) exit(1); /* fail safe */ } + if (geteuid()) + errx(1, "not installed setuid"); + parseconfig(DOAS_CONF, 1); /* cmdline is used only for logging, no need to abort on truncate */ #ifndef linux - (void) strlcpy(cmdline, argv[0], sizeof(cmdline)); + (void)strlcpy(cmdline, argv[0], sizeof(cmdline)); for (i = 1; i < argc; i++) { if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline)) break; @@ -379,7 +424,7 @@ main(int argc, char **argv) cmd = argv[0]; if (!permit(uid, groups, ngroups, &rule, target, cmd, - (const char**)argv + 1)) { + (const char **)argv + 1)) { syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed command for %s: %s", myname, cmdline); errc(1, EPERM, NULL); @@ -387,36 +432,10 @@ main(int argc, char **argv) if (!(rule->options & NOPASS)) { #if defined(USE_BSD_AUTH) - char *challenge = NULL, *response, rbuf[1024], cbuf[128]; - auth_session_t *as; - if (nflag) errx(1, "Authorization required"); - if (!(as = auth_userchallenge(myname, login_style, "auth-doas", - &challenge))) - errx(1, "Authorization failed"); - if (!challenge) { - char host[MAXHOSTNAME + 1]; - if (gethostname(host, sizeof(host))) - snprintf(host, sizeof(host), "?"); - snprintf(cbuf, sizeof(cbuf), - "\rdoas (%.32s@%.32s) password: ", 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)) { - syslog(LOG_AUTHPRIV | LOG_NOTICE, - "failed auth for %s", myname); - errc(1, EPERM, NULL); - } - explicit_bzero(rbuf, sizeof(rbuf)); + authuser(myname, login_style, rule->options & PERSIST); #elif defined(USE_PAM) #define PAM_END(msg) do { \ syslog(LOG_ERR, "%s: %s", msg, pam_strerror(pamh, pam_err)); \ @@ -518,7 +537,6 @@ main(int argc, char **argv) #else #error No auth module! #endif - } /* diff --git a/doas.conf.5 b/doas.conf.5 index c9218f4..5090d9b 100644 --- a/doas.conf.5 +++ b/doas.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: doas.conf.5,v 1.26 2016/06/11 17:17:10 tedu Exp $ +.\" $OpenBSD: doas.conf.5,v 1.31 2016/12/05 10:58:07 schwarze Exp $ .\" .\"Copyright (c) 2015 Ted Unangst .\" @@ -13,7 +13,7 @@ .\"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. -.Dd $Mdocdate: June 11 2016 $ +.Dd $Mdocdate: December 5 2016 $ .Dt DOAS.CONF 5 .Os .Sh NAME @@ -35,7 +35,7 @@ The rules have the following format: .Op Ar options .Ar identity .Op Ic as Ar target -.Op Ic cmd Ar command Op Ic args ... +.Op Ic cmd Ar command Op Ic args No ... .Ed .Pp Rules consist of the following parts: @@ -47,6 +47,9 @@ Options are: .Bl -tag -width keepenv .It Ic nopass The user is not required to enter a password. +.It Ic persist +After the user successfully authenticates, do not ask for a password +again for some time. Works on OpenBSD only, persist is not available on Linux or FreeBSD. .It Ic keepenv The user's environment is maintained. The default is to reset the environment, except for the variables @@ -59,9 +62,18 @@ 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 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 +.Sq - +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. @@ -78,7 +90,7 @@ Be advised that it is best to specify absolute paths. If a relative path is specified, only a restricted .Ev PATH will be searched. -.It Ic args ... +.It Ic args Op Ar argument ... Arguments to command. The command arguments provided by the user need to match those specified. The keyword @@ -109,25 +121,27 @@ If quotes or backslashes are used in a word, it is not considered a keyword. .El .Sh EXAMPLES -The following example permits users in group wsrc to build ports, +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 , -permits tedu to run procmap as root without a password, +.Ev SSH_AUTH_SOCK +and +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 setenv { -ENV PS1=$DOAS_PS1 SSH_AUTH_SOCK } :wheel permit nopass tedu as root cmd /usr/sbin/procmap permit nopass keepenv root as root .Ed diff --git a/doas.h b/doas.h index 922e89c..6d255bb 100644 --- a/doas.h +++ b/doas.h @@ -1,4 +1,20 @@ -/* $OpenBSD: doas.h,v 1.8 2016/06/19 19:29:43 martijn Exp $ */ +/* $OpenBSD: doas.h,v 1.12 2016/10/05 17:40:25 tedu Exp $ */ +/* + * Copyright (c) 2015 Ted Unangst + * + * 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; @@ -10,11 +26,9 @@ struct rule { }; extern struct rule **rules; -extern int nrules, maxrules; +extern int nrules; extern int parse_errors; -size_t arraylen(const char **); - char **prepenv(struct rule *); #define PERMIT 1 @@ -22,6 +36,7 @@ char **prepenv(struct rule *); #define NOPASS 0x1 #define KEEPENV 0x2 +#define PERSIST 0x4 #ifndef UID_MAX #define UID_MAX 65535 diff --git a/env.c b/env.c index 9d03ff7..a2cd977 100644 --- a/env.c +++ b/env.c @@ -1,4 +1,4 @@ -/* $OpenBSD: env.c,v 1.2 2016/06/19 19:29:43 martijn Exp $ */ +/* $OpenBSD: env.c,v 1.5 2016/09/15 00:58:23 deraadt Exp $ */ /* * Copyright (c) 2016 Ted Unangst * @@ -54,12 +54,31 @@ RB_PROTOTYPE_STATIC(envtree, envnode, node, envcmp); #endif 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; @@ -70,41 +89,47 @@ createenv(char **envp) RB_INIT(&env->root); env->count = 0; - for (i = 0; envp[i] != NULL; i++) { - struct envnode *node; - const char *e, *eq; + if (rule->options & KEEPENV) { + extern const char **environ; - e = envp[i]; + for (i = 0; environ[i] != NULL; i++) { + struct envnode *node; + const char *e, *eq; + size_t len; + char name[1024]; - 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++; + 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; } -char ** +static char ** flattenenv(struct env *env) { char **envp; struct envnode *node; u_int i; - envp = reallocarray(NULL, (env->count + 1), sizeof(char *)); + envp = reallocarray(NULL, env->count + 1, sizeof(char *)); if (!envp) err(1, NULL); i = 0; @@ -118,72 +143,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, ©->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) -{ - 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(©->root); - copy->count = 0; - - if (rule->envlist) - copyenv(orig, copy, rule->envlist); - copyenv(orig, copy, safeset); - - return copy; -} - char ** prepenv(struct rule *rule) { - extern char **environ; + static const char *safeset[] = { + "DISPLAY", "HOME", "LOGNAME", "MAIL", + "PATH", "TERM", "USER", "USERNAME", + NULL + }; struct env *env; - - env = createenv(environ); - env = filterenv(env, rule); + + env = createenv(rule); + + /* if we started with blank, fill some defaults then apply rules */ + if (!(rule->options & KEEPENV)) + fillenv(env, safeset); + if (rule->envlist) + fillenv(env, rule->envlist); + return flattenenv(env); } diff --git a/parse.y b/parse.y index 323c299..27fb290 100644 --- a/parse.y +++ b/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.18 2016/06/07 16:49:23 tedu Exp $ */ +/* $OpenBSD: parse.y,v 1.26 2017/01/02 01:40:20 tedu Exp $ */ /* * Copyright (c) 2015 Ted Unangst * @@ -37,6 +37,7 @@ typedef struct { const char **cmdargs; const char **envlist; }; + const char **strlist; const char *str; }; int lineno; @@ -47,17 +48,30 @@ typedef struct { FILE *yyfp; struct rule **rules; -int nrules, maxrules; +int nrules; +static int maxrules; + int parse_errors = 0; -void yyerror(const char *, ...); -int yylex(void); -int yyparse(void); +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 TKEEPENV +%token TNOPASS TPERSIST TKEEPENV TSETENV %token TSTRING %% @@ -98,15 +112,23 @@ action: TPERMIT options { $$.envlist = $2.envlist; } | TDENY { $$.action = DENY; + $$.options = 0; + $$.envlist = NULL; } ; -options: /* none */ - | options option { +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 keepenv sections"); + yyerror("can't have two setenv sections"); YYERROR; } else $$.envlist = $2.envlist; @@ -114,24 +136,29 @@ options: /* none */ } ; option: TNOPASS { $$.options = NOPASS; + $$.envlist = NULL; + } | TPERSIST { + $$.options = PERSIST; + $$.envlist = NULL; } | TKEEPENV { $$.options = KEEPENV; - } | TKEEPENV '{' envlist '}' { - $$.options = KEEPENV; - $$.envlist = $3.envlist; + $$.envlist = NULL; + } | TSETENV '{' strlist '}' { + $$.options = 0; + $$.envlist = $3.strlist; } ; -envlist: /* empty */ { - 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, +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 envlist"); - $$.envlist[nenv] = $2.str; - $$.envlist[nenv + 1] = NULL; - } + errx(1, "can't allocate strlist"); + $$.strlist[nstr] = $2.str; + $$.strlist[nstr + 1] = NULL; + } ; ident: TSTRING { @@ -154,20 +181,8 @@ cmd: /* optional */ { args: /* empty */ { $$.cmdargs = NULL; - } | TARGS argslist { - $$.cmdargs = $2.cmdargs; - } ; - -argslist: /* empty */ { - 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, - sizeof(char *)))) - errx(1, "can't allocate args"); - $$.cmdargs[nargs] = $2.str; - $$.cmdargs[nargs + 1] = NULL; + } | TARGS strlist { + $$.cmdargs = $2.strlist; } ; %% @@ -185,7 +200,7 @@ yyerror(const char *fmt, ...) parse_errors++; } -struct keyword { +static struct keyword { const char *word; int token; } keywords[] = { @@ -195,7 +210,9 @@ struct keyword { { "cmd", TCMD }, { "args", TARGS }, { "nopass", TNOPASS }, + { "persist", TPERSIST }, { "keepenv", TKEEPENV }, + { "setenv", TSETENV }, }; int