1
0
mirror of https://github.com/slicer69/doas synced 2024-07-09 03:55:50 +00:00

Merge, update from OpenBSD

This commit is contained in:
katakk 2017-01-24 01:12:56 +09:00
parent a15e6ed35c
commit 453102110a
6 changed files with 278 additions and 188 deletions

4
doas.1
View File

@ -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
.An Ted Unangst Aq Mt tedu@openbsd.org

118
doas.c
View File

@ -17,6 +17,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#if defined(HAVE_INTTYPES_H)
#include <inttypes.h>
@ -39,6 +40,7 @@
#include <grp.h>
#include <syslog.h>
#include <errno.h>
#include <fcntl.h>
#if defined(HAVE_LOGIN_CAP_H)
#include <login_cap.h>
@ -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
}
/*

View File

@ -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 <tedu@openbsd.org>
.\"
@ -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

23
doas.h
View File

@ -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 <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;
@ -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

191
env.c
View File

@ -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 <tedu@openbsd.org>
*
@ -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, &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)
{
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;
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);
}

92
parse.y
View File

@ -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 <tedu@openbsd.org>
*
@ -22,7 +22,6 @@
#include <stdint.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
@ -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