From 453102110aa59b7446ee2480a3fc8878a4f785a5 Mon Sep 17 00:00:00 2001 From: katakk Date: Tue, 24 Jan 2017 01:12:56 +0900 Subject: [PATCH 1/2] Merge, update from OpenBSD --- doas.1 | 4 +- doas.c | 118 ++++++++++++++++++-------------- doas.conf.5 | 38 +++++++---- doas.h | 23 +++++-- env.c | 191 ++++++++++++++++++++++++++++++---------------------- parse.y | 92 ++++++++++++++----------- 6 files changed, 278 insertions(+), 188 deletions(-) diff --git a/doas.1 b/doas.1 index f55355c..4ef701e 100644 --- a/doas.1 +++ b/doas.1 @@ -88,7 +88,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 +106,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..6e832f9 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. .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 a59f031..9525812 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 * @@ -51,12 +51,31 @@ envcmp(struct envnode *a, struct envnode *b) } 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; @@ -67,41 +86,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; @@ -115,72 +140,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..fde406b 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 * @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -37,6 +36,7 @@ typedef struct { const char **cmdargs; const char **envlist; }; + const char **strlist; const char *str; }; int lineno; @@ -47,17 +47,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 +111,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 +135,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 +180,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 +199,7 @@ yyerror(const char *fmt, ...) parse_errors++; } -struct keyword { +static struct keyword { const char *word; int token; } keywords[] = { @@ -195,7 +209,9 @@ struct keyword { { "cmd", TCMD }, { "args", TARGS }, { "nopass", TNOPASS }, + { "persist", TPERSIST }, { "keepenv", TKEEPENV }, + { "setenv", TSETENV }, }; int From ce871f82bc51c4d02eb29810c67c56308544185a Mon Sep 17 00:00:00 2001 From: Jesse Smith Date: Fri, 17 Feb 2017 11:24:29 -0400 Subject: [PATCH 2/2] Updated documentation to include -- paramter and to make it clear the persist keyword does not currently work on Linux or FreeBSD. --- doas.1 | 3 +++ doas.conf.5 | 2 +- parse.y | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doas.1 b/doas.1 index 4ef701e..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 diff --git a/doas.conf.5 b/doas.conf.5 index 6e832f9..5090d9b 100644 --- a/doas.conf.5 +++ b/doas.conf.5 @@ -49,7 +49,7 @@ Options are: 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. +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 diff --git a/parse.y b/parse.y index fde406b..27fb290 100644 --- a/parse.y +++ b/parse.y @@ -22,6 +22,7 @@ #include #include #include +#include #include #include