Rewritten parser that converts sudoers into a set of data structures.
This eliminates ordering issues and makes it possible to apply sudoers Defaults entries before searching for the command.
This commit is contained in:
@@ -26,9 +26,14 @@
|
||||
#ifndef YYSTYPE_DEFINED
|
||||
#define YYSTYPE_DEFINED
|
||||
typedef union {
|
||||
char *string;
|
||||
int BOOLEAN;
|
||||
struct alias *alias;
|
||||
struct cmndspec *cmndspec;
|
||||
struct defaults *defaults;
|
||||
struct member *member;
|
||||
struct privilege *privilege;
|
||||
struct sudo_command command;
|
||||
struct cmndtag tag;
|
||||
char *string;
|
||||
int tok;
|
||||
} YYSTYPE;
|
||||
#endif /* YYSTYPE_DEFINED */
|
674
gram.y
Normal file
674
gram.y
Normal file
@@ -0,0 +1,674 @@
|
||||
%{
|
||||
/*
|
||||
* Copyright (c) 1996, 1998-2004 Todd C. Miller <Todd.Miller@courtesan.com>
|
||||
*
|
||||
* 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.
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* Sponsored in part by the Defense Advanced Research Projects
|
||||
* Agency (DARPA) and Air Force Research Laboratory, Air Force
|
||||
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/param.h>
|
||||
#include <stdio.h>
|
||||
#ifdef STDC_HEADERS
|
||||
# include <stdlib.h>
|
||||
# include <stddef.h>
|
||||
#else
|
||||
# ifdef HAVE_STDLIB_H
|
||||
# include <stdlib.h>
|
||||
# endif
|
||||
#endif /* STDC_HEADERS */
|
||||
#ifdef HAVE_STRING_H
|
||||
# include <string.h>
|
||||
#else
|
||||
# ifdef HAVE_STRINGS_H
|
||||
# include <strings.h>
|
||||
# endif
|
||||
#endif /* HAVE_STRING_H */
|
||||
#ifdef HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif /* HAVE_UNISTD_H */
|
||||
#include <pwd.h>
|
||||
#if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
|
||||
# include <malloc.h>
|
||||
#endif /* HAVE_MALLOC_H && !STDC_HEADERS */
|
||||
#if defined(YYBISON) && defined(HAVE_ALLOCA_H) && !defined(__GNUC__)
|
||||
# include <alloca.h>
|
||||
#endif /* YYBISON && HAVE_ALLOCA_H && !__GNUC__ */
|
||||
|
||||
#include "sudo.h"
|
||||
#include "parse.h"
|
||||
|
||||
#ifndef lint
|
||||
static const char rcsid[] = "$Sudo$";
|
||||
#endif /* lint */
|
||||
|
||||
/*
|
||||
* Globals
|
||||
*/
|
||||
extern int sudolineno;
|
||||
extern char *sudoers;
|
||||
int parse_error;
|
||||
int pedantic = FALSE;
|
||||
int verbose = FALSE;
|
||||
int errorlineno = -1;
|
||||
char *errorfile = NULL;
|
||||
|
||||
struct alias *aliases; /* XXX - use RB or binary search tree */
|
||||
struct defaults *defaults;
|
||||
struct userspec *userspecs;
|
||||
|
||||
/*
|
||||
* Local protoypes
|
||||
*/
|
||||
static void add_alias __P((struct alias *));
|
||||
static void add_defaults __P((int, struct member *, struct defaults *));
|
||||
static void add_userspec __P((struct member *, struct privilege *));
|
||||
void yyerror __P((const char *));
|
||||
|
||||
void
|
||||
yyerror(s)
|
||||
const char *s;
|
||||
{
|
||||
/* Save the line the first error occurred on. */
|
||||
if (errorlineno == -1) {
|
||||
errorlineno = sudolineno ? sudolineno - 1 : 0;
|
||||
errorfile = estrdup(sudoers);
|
||||
}
|
||||
if (verbose && s != NULL) {
|
||||
#ifndef TRACELEXER
|
||||
(void) fprintf(stderr, ">>> %s: %s near line %d <<<\n", sudoers, s,
|
||||
sudolineno ? sudolineno - 1 : 0);
|
||||
#else
|
||||
(void) fprintf(stderr, "<*> ");
|
||||
#endif
|
||||
}
|
||||
parse_error = TRUE;
|
||||
}
|
||||
%}
|
||||
|
||||
%union {
|
||||
struct alias *alias;
|
||||
struct cmndspec *cmndspec;
|
||||
struct defaults *defaults;
|
||||
struct member *member;
|
||||
struct privilege *privilege;
|
||||
struct sudo_command command;
|
||||
struct cmndtag tag;
|
||||
char *string;
|
||||
int tok;
|
||||
}
|
||||
|
||||
%start file /* special start symbol */
|
||||
%token <command> COMMAND /* absolute pathname w/ optional args */
|
||||
%token <string> ALIAS /* an UPPERCASE alias name */
|
||||
%token <string> DEFVAR /* a Defaults variable name */
|
||||
%token <string> NTWKADDR /* w.x.y.z */
|
||||
%token <string> NETGROUP /* a netgroup (+NAME) */
|
||||
%token <string> USERGROUP /* a usergroup (%NAME) */
|
||||
%token <string> WORD /* a word */
|
||||
%token <tok> DEFAULTS /* Defaults entry */
|
||||
%token <tok> DEFAULTS_HOST /* Host-specific defaults entry */
|
||||
%token <tok> DEFAULTS_USER /* User-specific defaults entry */
|
||||
%token <tok> DEFAULTS_RUNAS /* Runas-specific defaults entry */
|
||||
%token <tok> RUNAS /* ( runas_list ) */
|
||||
%token <tok> NOPASSWD /* no passwd req for command */
|
||||
%token <tok> PASSWD /* passwd req for command (default) */
|
||||
%token <tok> NOEXEC /* preload dummy execve() for cmnd */
|
||||
%token <tok> EXEC /* don't preload dummy execve() */
|
||||
%token <tok> MONITOR /* monitor children of cmnd */
|
||||
%token <tok> NOMONITOR /* disable monitoring of children */
|
||||
%token <tok> ALL /* ALL keyword */
|
||||
%token <tok> COMMENT /* comment and/or carriage return */
|
||||
%token <tok> HOSTALIAS /* Host_Alias keyword */
|
||||
%token <tok> CMNDALIAS /* Cmnd_Alias keyword */
|
||||
%token <tok> USERALIAS /* User_Alias keyword */
|
||||
%token <tok> RUNASALIAS /* Runas_Alias keyword */
|
||||
%token <tok> ':' '=' ',' '!' '+' '-' /* union member tokens */
|
||||
%token <tok> ERROR
|
||||
|
||||
%type <alias> cmndalias
|
||||
%type <alias> cmndaliases
|
||||
%type <alias> hostalias
|
||||
%type <alias> hostaliases
|
||||
%type <alias> runasalias
|
||||
%type <alias> runasaliases
|
||||
%type <alias> useralias
|
||||
%type <alias> useraliases
|
||||
%type <cmndspec> cmndspec
|
||||
%type <cmndspec> cmndspeclist
|
||||
%type <defaults> defaults_entry
|
||||
%type <defaults> defaults_list
|
||||
%type <member> cmnd
|
||||
%type <member> opcmnd
|
||||
%type <member> cmndlist
|
||||
%type <member> host
|
||||
%type <member> hostlist
|
||||
%type <member> ophost
|
||||
%type <member> oprunasuser
|
||||
%type <member> opuser
|
||||
%type <member> runaslist
|
||||
%type <member> runasspec
|
||||
%type <member> runasuser
|
||||
%type <member> user
|
||||
%type <member> userlist
|
||||
%type <privilege> privilege
|
||||
%type <privilege> privileges
|
||||
%type <tag> cmndtag
|
||||
|
||||
%%
|
||||
|
||||
file : { ; }
|
||||
| line
|
||||
;
|
||||
|
||||
line : entry
|
||||
| line entry
|
||||
;
|
||||
|
||||
entry : COMMENT {
|
||||
;
|
||||
}
|
||||
| error COMMENT {
|
||||
yyerrok;
|
||||
}
|
||||
| userlist privileges {
|
||||
add_userspec($1, $2);
|
||||
}
|
||||
| USERALIAS useraliases {
|
||||
add_alias($2);
|
||||
}
|
||||
| HOSTALIAS hostaliases {
|
||||
add_alias($2);
|
||||
}
|
||||
| CMNDALIAS cmndaliases {
|
||||
add_alias($2);
|
||||
}
|
||||
| RUNASALIAS runasaliases {
|
||||
add_alias($2);
|
||||
}
|
||||
| DEFAULTS defaults_list {
|
||||
add_defaults(DEFAULTS, NULL, $2);
|
||||
}
|
||||
| DEFAULTS_USER userlist defaults_list {
|
||||
add_defaults(DEFAULTS_USER, $2, $3);
|
||||
}
|
||||
| DEFAULTS_RUNAS runaslist defaults_list {
|
||||
add_defaults(DEFAULTS_RUNAS, $2, $3);
|
||||
}
|
||||
| DEFAULTS_HOST hostlist defaults_list {
|
||||
add_defaults(DEFAULTS_HOST, $2, $3);
|
||||
}
|
||||
;
|
||||
|
||||
defaults_list : defaults_entry
|
||||
| defaults_list ',' defaults_entry {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
defaults_entry : DEFVAR {
|
||||
NEW_DEFAULT($$, $1, NULL, TRUE);
|
||||
}
|
||||
| '!' DEFVAR {
|
||||
NEW_DEFAULT($$, $2, NULL, FALSE);
|
||||
}
|
||||
| DEFVAR '=' WORD {
|
||||
NEW_DEFAULT($$, $1, $3, TRUE);
|
||||
}
|
||||
| DEFVAR '+' WORD {
|
||||
NEW_DEFAULT($$, $1, $3, '+');
|
||||
}
|
||||
| DEFVAR '-' WORD {
|
||||
NEW_DEFAULT($$, $1, $3, '-');
|
||||
}
|
||||
;
|
||||
|
||||
privileges : privilege
|
||||
| privileges ':' privilege {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
privilege : hostlist '=' cmndspeclist {
|
||||
struct cmndtag tags;
|
||||
struct privilege *p = emalloc(sizeof(*p));
|
||||
struct cmndspec *cs;
|
||||
p->hostlist = $1;
|
||||
p->cmndlist = $3;
|
||||
tags.nopasswd = tags.noexec = tags.monitor = UNSPEC;
|
||||
/* propagate tags */
|
||||
for (cs = $3; cs != NULL; cs = cs->next) {
|
||||
if (cs->tags.nopasswd == UNSPEC)
|
||||
cs->tags.nopasswd = tags.nopasswd;
|
||||
if (cs->tags.noexec == UNSPEC)
|
||||
cs->tags.noexec = tags.noexec;
|
||||
if (cs->tags.monitor == UNSPEC)
|
||||
cs->tags.monitor = tags.monitor;
|
||||
memcpy(&tags, &cs->tags, sizeof(tags));
|
||||
}
|
||||
p->last = NULL;
|
||||
p->next = NULL;
|
||||
$$ = p;
|
||||
}
|
||||
;
|
||||
|
||||
ophost : host {
|
||||
$$ = $1;
|
||||
$$->negated = FALSE;
|
||||
}
|
||||
| '!' host {
|
||||
$$ = $2;
|
||||
$$->negated = TRUE;
|
||||
}
|
||||
;
|
||||
|
||||
host : ALIAS {
|
||||
NEW_MEMBER($$, $1, HOSTALIAS);
|
||||
}
|
||||
| ALL {
|
||||
NEW_MEMBER($$, NULL, ALL);
|
||||
}
|
||||
| NETGROUP {
|
||||
NEW_MEMBER($$, $1, NETGROUP);
|
||||
}
|
||||
| NTWKADDR {
|
||||
NEW_MEMBER($$, $1, NTWKADDR);
|
||||
}
|
||||
| WORD {
|
||||
NEW_MEMBER($$, $1, WORD);
|
||||
}
|
||||
;
|
||||
|
||||
cmndspeclist : cmndspec
|
||||
| cmndspeclist ',' cmndspec {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
cmndspec : runasspec cmndtag opcmnd {
|
||||
struct cmndspec *cs = emalloc(sizeof(*cs));
|
||||
cs->runaslist = $1;
|
||||
cs->tags = $2;
|
||||
cs->cmnd = $3;
|
||||
cs->last = NULL;
|
||||
cs->next = NULL;
|
||||
$$ = cs;
|
||||
}
|
||||
;
|
||||
|
||||
opcmnd : cmnd {
|
||||
$$ = $1;
|
||||
$$->negated = FALSE;
|
||||
}
|
||||
| '!' cmnd {
|
||||
$$ = $2;
|
||||
$$->negated = TRUE;
|
||||
}
|
||||
;
|
||||
|
||||
runasspec : /* empty */ {
|
||||
$$ = NULL;
|
||||
}
|
||||
| RUNAS runaslist {
|
||||
$$ = $2;
|
||||
}
|
||||
;
|
||||
|
||||
runaslist : oprunasuser
|
||||
| runaslist ',' oprunasuser {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
oprunasuser : runasuser {
|
||||
$$ = $1;
|
||||
$$->negated = FALSE;
|
||||
}
|
||||
| '!' runasuser {
|
||||
$$ = $2;
|
||||
$$->negated = TRUE;
|
||||
}
|
||||
;
|
||||
|
||||
runasuser : ALIAS {
|
||||
NEW_MEMBER($$, $1, RUNASALIAS);
|
||||
}
|
||||
| ALL {
|
||||
NEW_MEMBER($$, NULL, ALL);
|
||||
}
|
||||
| NETGROUP {
|
||||
NEW_MEMBER($$, $1, NETGROUP);
|
||||
}
|
||||
| USERGROUP {
|
||||
NEW_MEMBER($$, $1, USERGROUP);
|
||||
}
|
||||
| WORD {
|
||||
NEW_MEMBER($$, $1, WORD);
|
||||
}
|
||||
;
|
||||
|
||||
cmndtag : /* empty */ {
|
||||
$$.nopasswd = $$.noexec = $$.monitor = UNSPEC;
|
||||
}
|
||||
| cmndtag NOPASSWD {
|
||||
$$.nopasswd = TRUE;
|
||||
}
|
||||
| cmndtag PASSWD {
|
||||
$$.nopasswd = FALSE;
|
||||
}
|
||||
| cmndtag NOEXEC {
|
||||
$$.noexec = TRUE;
|
||||
}
|
||||
| cmndtag EXEC {
|
||||
$$.noexec = FALSE;
|
||||
}
|
||||
| cmndtag MONITOR {
|
||||
$$.monitor = TRUE;
|
||||
}
|
||||
| cmndtag NOMONITOR {
|
||||
$$.monitor = FALSE;
|
||||
}
|
||||
;
|
||||
|
||||
cmnd : ALL {
|
||||
NEW_MEMBER($$, NULL, ALL);
|
||||
if (safe_cmnd)
|
||||
free(safe_cmnd);
|
||||
safe_cmnd = estrdup(user_cmnd);
|
||||
}
|
||||
| ALIAS {
|
||||
NEW_MEMBER($$, $1, CMNDALIAS);
|
||||
}
|
||||
| COMMAND {
|
||||
struct sudo_command *c = emalloc(sizeof(*c));
|
||||
c->cmnd = $1.cmnd;
|
||||
c->args = $1.args;
|
||||
NEW_MEMBER($$, (char *)c, COMMAND);
|
||||
}
|
||||
;
|
||||
|
||||
hostaliases : hostalias
|
||||
| hostaliases ':' hostalias {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
hostalias : ALIAS '=' hostlist {
|
||||
NEW_ALIAS($$, $1, HOSTALIAS, $3);
|
||||
}
|
||||
;
|
||||
|
||||
hostlist : ophost
|
||||
| hostlist ',' ophost {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
cmndaliases : cmndalias
|
||||
| cmndaliases ':' cmndalias {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
cmndalias : ALIAS '=' cmndlist {
|
||||
NEW_ALIAS($$, $1, CMNDALIAS, $3);
|
||||
}
|
||||
;
|
||||
|
||||
cmndlist : opcmnd
|
||||
| cmndlist ',' opcmnd {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
runasaliases : runasalias
|
||||
| runasaliases ':' runasalias {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
runasalias : ALIAS '=' runaslist {
|
||||
NEW_ALIAS($$, $1, RUNASALIAS, $3);
|
||||
}
|
||||
;
|
||||
|
||||
useraliases : useralias
|
||||
| useraliases ':' useralias {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
useralias : ALIAS '=' userlist {
|
||||
NEW_ALIAS($$, $1, USERALIAS, $3);
|
||||
}
|
||||
;
|
||||
|
||||
userlist : opuser
|
||||
| userlist ',' opuser {
|
||||
LIST_APPEND($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
opuser : user {
|
||||
$$ = $1;
|
||||
$$->negated = FALSE;
|
||||
}
|
||||
| '!' user {
|
||||
$$ = $2;
|
||||
$$->negated = TRUE;
|
||||
}
|
||||
;
|
||||
|
||||
user : ALIAS {
|
||||
NEW_MEMBER($$, $1, USERALIAS);
|
||||
}
|
||||
| ALL {
|
||||
NEW_MEMBER($$, NULL, ALL);
|
||||
}
|
||||
| NETGROUP {
|
||||
NEW_MEMBER($$, $1, NETGROUP);
|
||||
}
|
||||
| USERGROUP {
|
||||
NEW_MEMBER($$, $1, USERGROUP);
|
||||
}
|
||||
| WORD {
|
||||
NEW_MEMBER($$, $1, WORD);
|
||||
}
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
/*
|
||||
* Add a list of aliases to the end of the global aliases list.
|
||||
*/
|
||||
static void
|
||||
add_alias(a)
|
||||
struct alias *a;
|
||||
{
|
||||
if (aliases == NULL)
|
||||
aliases = a;
|
||||
else {
|
||||
if (aliases->last != NULL)
|
||||
aliases->last->next = a;
|
||||
else /* if (aliases->next == NULL) */
|
||||
aliases->next = a;
|
||||
aliases->last = a->last ? a->last : a;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a list of defaults structures to the defaults list.
|
||||
* The binding, if non-NULL, specifies a list of hosts, users, or
|
||||
* runas users the entries apply to (specified by the type).
|
||||
*/
|
||||
static void
|
||||
add_defaults(type, binding, defs)
|
||||
int type;
|
||||
struct member *binding;
|
||||
struct defaults *defs;
|
||||
{
|
||||
struct defaults *d;
|
||||
|
||||
/*
|
||||
* Set type and binding (who it applies to) for new entries.
|
||||
*/
|
||||
for (d = defs; d != NULL; d = d->next) {
|
||||
d->type = type;
|
||||
d->binding = binding;
|
||||
}
|
||||
if (defaults == NULL)
|
||||
defaults = defs;
|
||||
else {
|
||||
if (defaults->last != NULL)
|
||||
defaults->last->next = defs;
|
||||
else /* if (defaults->next == NULL) */
|
||||
defaults->next = defs;
|
||||
defaults->last = defs->last ? defs->last : defs;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new struct userspec, populate it, and insert it at the
|
||||
* and of the userspecs list.
|
||||
*/
|
||||
static void
|
||||
add_userspec(members, privs)
|
||||
struct member *members;
|
||||
struct privilege *privs;
|
||||
{
|
||||
struct userspec *u;
|
||||
|
||||
u = emalloc(sizeof(*u));
|
||||
u->user = members;
|
||||
u->privileges = privs;
|
||||
u->last = NULL;
|
||||
u->next = NULL;
|
||||
if (userspecs == NULL)
|
||||
userspecs = u;
|
||||
else {
|
||||
if (userspecs->last != NULL)
|
||||
userspecs->last->next = u;
|
||||
else /* if (userspecs->next == NULL) */
|
||||
userspecs->next = u;
|
||||
userspecs->last = u;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Free up space used by data structures from a previous parser run and sets
|
||||
* the current sudoers file to path.
|
||||
*/
|
||||
void
|
||||
init_parser(path, quiet)
|
||||
char *path;
|
||||
int quiet;
|
||||
{
|
||||
struct alias *a;
|
||||
struct defaults *d;
|
||||
struct member *m, *lastbinding;
|
||||
struct userspec *us;
|
||||
struct privilege *priv;
|
||||
struct cmndspec *cs;
|
||||
VOID *next;
|
||||
|
||||
for (a = aliases ; a != NULL; a = a->next) {
|
||||
for (m = a->first_member; m != NULL; m = next) {
|
||||
next = m->next;
|
||||
if (m->name != NULL)
|
||||
free(m->name);
|
||||
free(m);
|
||||
}
|
||||
}
|
||||
aliases = NULL;
|
||||
|
||||
for (us = userspecs ; us != NULL; us = next) {
|
||||
for (m = us->user; m != NULL; m = next) {
|
||||
next = m->next;
|
||||
if (m->name != NULL)
|
||||
free(m->name);
|
||||
free(m);
|
||||
}
|
||||
for (priv = us->privileges; priv != NULL; priv = next) {
|
||||
for (m = priv->hostlist; m != NULL; m = next) {
|
||||
next = m->next;
|
||||
if (m->name != NULL)
|
||||
free(m->name);
|
||||
free(m);
|
||||
}
|
||||
for (cs = priv->cmndlist; cs != NULL; cs = next) {
|
||||
for (m = cs->runaslist; m != NULL; m = next) {
|
||||
next = m->next;
|
||||
if (m->name != NULL)
|
||||
free(m->name);
|
||||
free(m);
|
||||
}
|
||||
if (cs->cmnd->name != NULL)
|
||||
free(cs->cmnd->name);
|
||||
free(cs->cmnd);
|
||||
next = cs->next;
|
||||
free(cs);
|
||||
}
|
||||
next = priv->next;
|
||||
free(priv);
|
||||
}
|
||||
next = us->next;
|
||||
free(us);
|
||||
}
|
||||
userspecs = NULL;
|
||||
|
||||
lastbinding = NULL;
|
||||
for (d = defaults ; d != NULL; d = next) {
|
||||
if (d->binding != lastbinding) {
|
||||
for (m = d->binding; m != NULL; m = next) {
|
||||
next = m->next;
|
||||
if (m->name != NULL)
|
||||
free(m->name);
|
||||
free(m);
|
||||
}
|
||||
lastbinding = d->binding;
|
||||
}
|
||||
next = d->next;
|
||||
free(d->var);
|
||||
if (d->val != NULL)
|
||||
free(d->val);
|
||||
free(d);
|
||||
}
|
||||
defaults = NULL;
|
||||
|
||||
if (sudoers != NULL)
|
||||
free(sudoers);
|
||||
sudoers = estrdup(path);
|
||||
|
||||
parse_error = FALSE;
|
||||
errorlineno = -1;
|
||||
sudolineno = 1;
|
||||
verbose = !quiet;
|
||||
}
|
605
parse.c
605
parse.c
@@ -24,7 +24,6 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
#ifdef STDC_HEADERS
|
||||
# include <stdlib.h>
|
||||
@@ -44,526 +43,204 @@
|
||||
#ifdef HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif /* HAVE_UNISTD_H */
|
||||
#ifdef HAVE_FNMATCH
|
||||
# include <fnmatch.h>
|
||||
#endif /* HAVE_FNMATCH */
|
||||
#ifdef HAVE_EXTENDED_GLOB
|
||||
# include <glob.h>
|
||||
#endif /* HAVE_EXTENDED_GLOB */
|
||||
#ifdef HAVE_NETGROUP_H
|
||||
# include <netgroup.h>
|
||||
#endif /* HAVE_NETGROUP_H */
|
||||
#include <ctype.h>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#ifdef HAVE_DIRENT_H
|
||||
# include <dirent.h>
|
||||
# define NAMLEN(dirent) strlen((dirent)->d_name)
|
||||
#else
|
||||
# define dirent direct
|
||||
# define NAMLEN(dirent) (dirent)->d_namlen
|
||||
# ifdef HAVE_SYS_NDIR_H
|
||||
# include <sys/ndir.h>
|
||||
# endif
|
||||
# ifdef HAVE_SYS_DIR_H
|
||||
# include <sys/dir.h>
|
||||
# endif
|
||||
# ifdef HAVE_NDIR_H
|
||||
# include <ndir.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "sudo.h"
|
||||
#include "parse.h"
|
||||
#include "interfaces.h"
|
||||
|
||||
#ifndef HAVE_FNMATCH
|
||||
# include "emul/fnmatch.h"
|
||||
#endif /* HAVE_FNMATCH */
|
||||
#ifndef HAVE_EXTENDED_GLOB
|
||||
# include "emul/glob.h"
|
||||
#endif /* HAVE_EXTENDED_GLOB */
|
||||
#include "gram.h"
|
||||
|
||||
#ifndef lint
|
||||
static const char rcsid[] = "$Sudo$";
|
||||
#endif /* lint */
|
||||
|
||||
/*
|
||||
* Globals
|
||||
* Parsed sudoers info.
|
||||
*/
|
||||
int parse_error = FALSE;
|
||||
extern int keepall;
|
||||
extern FILE *yyin, *yyout;
|
||||
extern struct userspec *userspecs;
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
* Parse the specified sudoers file.
|
||||
*/
|
||||
static int has_meta __P((char *));
|
||||
int
|
||||
parse_sudoers(path)
|
||||
const char *path;
|
||||
{
|
||||
extern FILE *yyin;
|
||||
|
||||
yyin = open_sudoers(_PATH_SUDOERS, NULL);
|
||||
init_parser(_PATH_SUDOERS, 0);
|
||||
return(yyparse());
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up the user in the sudoers file and check to see if they are
|
||||
* Look up the user in the parsed sudoers file and check to see if they are
|
||||
* allowed to run the specified command on this host as the target user.
|
||||
*/
|
||||
int
|
||||
sudoers_lookup(pwflag)
|
||||
int pwflag;
|
||||
{
|
||||
int error, nopass;
|
||||
enum def_tupple pwcheck;
|
||||
|
||||
yyin = sudoers_fp;
|
||||
yyout = stdout;
|
||||
|
||||
/* Allocate space for data structures in the parser. */
|
||||
init_parser(_PATH_SUDOERS);
|
||||
|
||||
/* If pwcheck *could* be "all" or "any", keep more state. */
|
||||
if (pwflag > 0)
|
||||
keepall = TRUE;
|
||||
|
||||
/* Need to be runas user while stat'ing things in the parser. */
|
||||
set_perms(PERM_RUNAS);
|
||||
error = yyparse();
|
||||
if (error || parse_error) {
|
||||
set_perms(PERM_ROOT);
|
||||
return(VALIDATE_ERROR);
|
||||
}
|
||||
int rval, validated, matched;
|
||||
enum def_tupple pwcheck = 0;
|
||||
struct cmndspec *cs;
|
||||
struct cmndtag *tags = NULL;
|
||||
struct privilege *priv;
|
||||
struct userspec *us;
|
||||
|
||||
/*
|
||||
* The pw options may have changed during sudoers parse so we
|
||||
* wait until now to set this.
|
||||
* We use pwflag to tell us when a password should be required
|
||||
* for pseudo-commands. XXX - pass in pwcheck, not pwflag
|
||||
*/
|
||||
if (pwflag)
|
||||
pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple;
|
||||
else
|
||||
pwcheck = 0;
|
||||
|
||||
/*
|
||||
* Assume the worst. If the stack is empty the user was
|
||||
* not mentioned at all.
|
||||
*/
|
||||
if (def_authenticate)
|
||||
error = VALIDATE_NOT_OK;
|
||||
else
|
||||
error = VALIDATE_NOT_OK | FLAG_NOPASS;
|
||||
if (pwcheck) {
|
||||
SET(error, FLAG_NO_CHECK);
|
||||
} else {
|
||||
SET(error, FLAG_NO_HOST);
|
||||
if (!top)
|
||||
SET(error, FLAG_NO_USER);
|
||||
}
|
||||
/* Assume the worst. */
|
||||
validated = VALIDATE_NOT_OK | FLAG_NO_HOST | FLAG_NO_USER;
|
||||
if (pwflag)
|
||||
SET(validated, FLAG_NO_CHECK);
|
||||
else if (!def_authenticate)
|
||||
validated |= FLAG_NOPASS;
|
||||
|
||||
/*
|
||||
* Only check the actual command if pwflag is not set.
|
||||
* It is set for the "validate", "list" and "kill" pseudo-commands.
|
||||
* Always check the host and user.
|
||||
*/
|
||||
nopass = -1;
|
||||
if (pwflag) {
|
||||
int found;
|
||||
int nopass = UNSPEC;
|
||||
|
||||
if (pwcheck == always && def_authenticate)
|
||||
nopass = FLAG_CHECK_USER;
|
||||
else if (pwcheck == never || !def_authenticate)
|
||||
nopass = FLAG_NOPASS;
|
||||
found = 0;
|
||||
while (top) {
|
||||
if (host_matches == TRUE) {
|
||||
found = 1;
|
||||
if (pwcheck == any && no_passwd == TRUE)
|
||||
nopass = FLAG_NOPASS;
|
||||
else if (pwcheck == all && nopass != 0)
|
||||
nopass = (no_passwd == TRUE) ? FLAG_NOPASS : 0;
|
||||
}
|
||||
top--;
|
||||
}
|
||||
if (found) {
|
||||
set_perms(PERM_ROOT);
|
||||
if (nopass == -1)
|
||||
nopass = 0;
|
||||
return(VALIDATE_OK | nopass);
|
||||
}
|
||||
} else {
|
||||
while (top) {
|
||||
if (host_matches == TRUE) {
|
||||
CLR(error, FLAG_NO_HOST);
|
||||
if (runas_matches == TRUE && cmnd_matches == TRUE) {
|
||||
/*
|
||||
* User was granted access to cmnd on host as user.
|
||||
*/
|
||||
set_perms(PERM_ROOT);
|
||||
return(VALIDATE_OK |
|
||||
(no_passwd == TRUE ? FLAG_NOPASS : 0) |
|
||||
(no_execve == TRUE ? FLAG_NOEXEC : 0) |
|
||||
(monitor_cmnd == TRUE ? FLAG_MONITOR : 0));
|
||||
} else if ((runas_matches == TRUE && cmnd_matches == FALSE) ||
|
||||
(runas_matches == FALSE && cmnd_matches == TRUE)) {
|
||||
/*
|
||||
* User was explicitly denied access to cmnd on host.
|
||||
*/
|
||||
set_perms(PERM_ROOT);
|
||||
return(VALIDATE_NOT_OK |
|
||||
(no_passwd == TRUE ? FLAG_NOPASS : 0) |
|
||||
(no_execve == TRUE ? FLAG_NOEXEC : 0) |
|
||||
(monitor_cmnd == TRUE ? FLAG_MONITOR : 0));
|
||||
CLR(validated, FLAG_NO_USER);
|
||||
CLR(validated, FLAG_NO_HOST);
|
||||
matched = FALSE;
|
||||
for (us = userspecs; us != NULL; us = us->next) {
|
||||
if (user_matches(sudo_user.pw, us->user) == TRUE) {
|
||||
priv = us->privileges;
|
||||
if (host_matches(user_shost, user_host, priv->hostlist) == TRUE) {
|
||||
matched = TRUE;
|
||||
for (cs = priv->cmndlist; cs != NULL; cs = cs->next) {
|
||||
if ((pwcheck == any && nopass != TRUE) ||
|
||||
(pwcheck == all && nopass == TRUE))
|
||||
nopass = cs->tags.nopasswd;
|
||||
}
|
||||
}
|
||||
}
|
||||
top--;
|
||||
}
|
||||
if (matched == TRUE) {
|
||||
/* User has an entry for this host. */
|
||||
CLR(validated, VALIDATE_NOT_OK);
|
||||
SET(validated, VALIDATE_OK);
|
||||
if (pwcheck == always && def_authenticate)
|
||||
SET(validated, FLAG_CHECK_USER);
|
||||
else if (pwcheck == never || !def_authenticate || nopass == TRUE)
|
||||
SET(validated, FLAG_NOPASS);
|
||||
}
|
||||
return(validated);
|
||||
}
|
||||
|
||||
/* Need to be runas user while stat'ing things. */
|
||||
set_perms(PERM_RUNAS);
|
||||
|
||||
matched = UNSPEC;
|
||||
for (us = userspecs; us != NULL; us = us->next) {
|
||||
if (user_matches(sudo_user.pw, us->user) == TRUE) {
|
||||
CLR(validated, FLAG_NO_USER);
|
||||
priv = us->privileges;
|
||||
if (host_matches(user_shost, user_host, priv->hostlist) == TRUE) {
|
||||
CLR(validated, FLAG_NO_HOST);
|
||||
for (cs = priv->cmndlist; cs != NULL; cs = cs->next) {
|
||||
if (runas_matches(runas_pw, cs->runaslist) == TRUE) {
|
||||
rval = cmnd_matches(user_cmnd, user_args, cs->cmnd);
|
||||
if (rval != UNSPEC) {
|
||||
matched = rval;
|
||||
tags = &cs->tags;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (matched == TRUE) {
|
||||
CLR(validated, VALIDATE_NOT_OK);
|
||||
SET(validated, VALIDATE_OK);
|
||||
if (tags != NULL) {
|
||||
if (tags->nopasswd == TRUE)
|
||||
SET(validated, FLAG_NOPASS);
|
||||
if (tags->noexec == TRUE)
|
||||
SET(validated, FLAG_NOEXEC);
|
||||
if (tags->monitor == TRUE)
|
||||
SET(validated, FLAG_MONITOR);
|
||||
}
|
||||
}
|
||||
set_perms(PERM_ROOT);
|
||||
|
||||
/*
|
||||
* The user was neither explicitly granted nor denied access.
|
||||
*/
|
||||
if (nopass == -1)
|
||||
nopass = 0;
|
||||
return(error | nopass);
|
||||
return(validated);
|
||||
}
|
||||
|
||||
/*
|
||||
* If path doesn't end in /, return TRUE iff cmnd & path name the same inode;
|
||||
* otherwise, return TRUE if user_cmnd names one of the inodes in path.
|
||||
* Print out privileges for the specified user.
|
||||
*/
|
||||
int
|
||||
command_matches(sudoers_cmnd, sudoers_args)
|
||||
char *sudoers_cmnd;
|
||||
char *sudoers_args;
|
||||
{
|
||||
struct stat sudoers_stat;
|
||||
struct dirent *dent;
|
||||
char **ap, *base, buf[PATH_MAX];
|
||||
glob_t gl;
|
||||
DIR *dirp;
|
||||
|
||||
/* Check for pseudo-commands */
|
||||
if (strchr(user_cmnd, '/') == NULL) {
|
||||
/*
|
||||
* Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND
|
||||
* a) there are no args in sudoers OR
|
||||
* b) there are no args on command line and none req by sudoers OR
|
||||
* c) there are args in sudoers and on command line and they match
|
||||
*/
|
||||
if (strcmp(sudoers_cmnd, "sudoedit") != 0 ||
|
||||
strcmp(user_cmnd, "sudoedit") != 0)
|
||||
return(FALSE);
|
||||
if (!sudoers_args ||
|
||||
(!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
|
||||
(sudoers_args &&
|
||||
fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
|
||||
if (safe_cmnd)
|
||||
free(safe_cmnd);
|
||||
safe_cmnd = estrdup(sudoers_cmnd);
|
||||
return(TRUE);
|
||||
} else
|
||||
return(FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* If sudoers_cmnd has meta characters in it, use fnmatch(3)
|
||||
* to do the matching.
|
||||
*/
|
||||
if (has_meta(sudoers_cmnd)) {
|
||||
/*
|
||||
* Return true if we find a match in the glob(3) results AND
|
||||
* a) there are no args in sudoers OR
|
||||
* b) there are no args on command line and none required by sudoers OR
|
||||
* c) there are args in sudoers and on command line and they match
|
||||
* else return false.
|
||||
*
|
||||
* Could optimize patterns ending in "/*" to "/user_base"
|
||||
*/
|
||||
#define GLOB_FLAGS (GLOB_NOSORT | GLOB_MARK | GLOB_BRACE | GLOB_TILDE)
|
||||
if (glob(sudoers_cmnd, GLOB_FLAGS, NULL, &gl) != 0) {
|
||||
globfree(&gl);
|
||||
return(FALSE);
|
||||
}
|
||||
/* For each glob match, compare basename, st_dev and st_ino. */
|
||||
for (ap = gl.gl_pathv; *ap != NULL; ap++) {
|
||||
/* only stat if basenames are the same */
|
||||
if ((base = strrchr(*ap, '/')) != NULL)
|
||||
base++;
|
||||
else
|
||||
base = *ap;
|
||||
if (strcmp(user_base, base) != 0 ||
|
||||
stat(*ap, &sudoers_stat) == -1)
|
||||
continue;
|
||||
if (user_stat->st_dev == sudoers_stat.st_dev &&
|
||||
user_stat->st_ino == sudoers_stat.st_ino) {
|
||||
if (safe_cmnd)
|
||||
free(safe_cmnd);
|
||||
safe_cmnd = estrdup(*ap);
|
||||
break;
|
||||
}
|
||||
}
|
||||
globfree(&gl);
|
||||
if (*ap == NULL)
|
||||
return(FALSE);
|
||||
|
||||
if (!sudoers_args ||
|
||||
(!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
|
||||
(sudoers_args &&
|
||||
fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
|
||||
if (safe_cmnd)
|
||||
free(safe_cmnd);
|
||||
safe_cmnd = estrdup(user_cmnd);
|
||||
return(TRUE);
|
||||
} else
|
||||
return(FALSE);
|
||||
} else {
|
||||
size_t dlen = strlen(sudoers_cmnd);
|
||||
|
||||
/*
|
||||
* No meta characters
|
||||
* Check to make sure this is not a directory spec (doesn't end in '/')
|
||||
*/
|
||||
if (sudoers_cmnd[dlen - 1] != '/') {
|
||||
/* Only proceed if user_base and basename(sudoers_cmnd) match */
|
||||
if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
|
||||
base = sudoers_cmnd;
|
||||
else
|
||||
base++;
|
||||
if (strcmp(user_base, base) != 0 ||
|
||||
stat(sudoers_cmnd, &sudoers_stat) == -1)
|
||||
return(FALSE);
|
||||
|
||||
/*
|
||||
* Return true if inode/device matches AND
|
||||
* a) there are no args in sudoers OR
|
||||
* b) there are no args on command line and none req by sudoers OR
|
||||
* c) there are args in sudoers and on command line and they match
|
||||
*/
|
||||
if (user_stat->st_dev != sudoers_stat.st_dev ||
|
||||
user_stat->st_ino != sudoers_stat.st_ino)
|
||||
return(FALSE);
|
||||
if (!sudoers_args ||
|
||||
(!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
|
||||
(sudoers_args &&
|
||||
fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
|
||||
if (safe_cmnd)
|
||||
free(safe_cmnd);
|
||||
safe_cmnd = estrdup(sudoers_cmnd);
|
||||
return(TRUE);
|
||||
} else
|
||||
return(FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Grot through sudoers_cmnd's directory entries, looking for user_base.
|
||||
*/
|
||||
dirp = opendir(sudoers_cmnd);
|
||||
if (dirp == NULL)
|
||||
return(FALSE);
|
||||
|
||||
if (strlcpy(buf, sudoers_cmnd, sizeof(buf)) >= sizeof(buf))
|
||||
return(FALSE);
|
||||
while ((dent = readdir(dirp)) != NULL) {
|
||||
/* ignore paths > PATH_MAX (XXX - log) */
|
||||
buf[dlen] = '\0';
|
||||
if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
|
||||
continue;
|
||||
|
||||
/* only stat if basenames are the same */
|
||||
if (strcmp(user_base, dent->d_name) != 0 ||
|
||||
stat(buf, &sudoers_stat) == -1)
|
||||
continue;
|
||||
if (user_stat->st_dev == sudoers_stat.st_dev &&
|
||||
user_stat->st_ino == sudoers_stat.st_ino) {
|
||||
if (safe_cmnd)
|
||||
free(safe_cmnd);
|
||||
safe_cmnd = estrdup(buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dirp);
|
||||
return(dent != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns TRUE if "n" is one of our ip addresses or if
|
||||
* "n" is a network that we are on, else returns FALSE.
|
||||
*/
|
||||
int
|
||||
addr_matches(n)
|
||||
char *n;
|
||||
{
|
||||
int i;
|
||||
char *m;
|
||||
struct in_addr addr, mask;
|
||||
|
||||
/* If there's an explicit netmask, use it. */
|
||||
if ((m = strchr(n, '/'))) {
|
||||
*m++ = '\0';
|
||||
addr.s_addr = inet_addr(n);
|
||||
if (strchr(m, '.'))
|
||||
mask.s_addr = inet_addr(m);
|
||||
else {
|
||||
i = 32 - atoi(m);
|
||||
mask.s_addr = 0xffffffff;
|
||||
mask.s_addr >>= i;
|
||||
mask.s_addr <<= i;
|
||||
mask.s_addr = htonl(mask.s_addr);
|
||||
}
|
||||
*(m - 1) = '/';
|
||||
|
||||
for (i = 0; i < num_interfaces; i++)
|
||||
if ((interfaces[i].addr.s_addr & mask.s_addr) == addr.s_addr)
|
||||
return(TRUE);
|
||||
} else {
|
||||
addr.s_addr = inet_addr(n);
|
||||
|
||||
for (i = 0; i < num_interfaces; i++)
|
||||
if (interfaces[i].addr.s_addr == addr.s_addr ||
|
||||
(interfaces[i].addr.s_addr & interfaces[i].netmask.s_addr)
|
||||
== addr.s_addr)
|
||||
return(TRUE);
|
||||
}
|
||||
|
||||
return(FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns 0 if the hostname matches the pattern and non-zero otherwise.
|
||||
*/
|
||||
int
|
||||
hostname_matches(shost, lhost, pattern)
|
||||
char *shost;
|
||||
char *lhost;
|
||||
char *pattern;
|
||||
{
|
||||
if (has_meta(pattern)) {
|
||||
if (strchr(pattern, '.'))
|
||||
return(fnmatch(pattern, lhost, FNM_CASEFOLD));
|
||||
else
|
||||
return(fnmatch(pattern, shost, FNM_CASEFOLD));
|
||||
} else {
|
||||
if (strchr(pattern, '.'))
|
||||
return(strcasecmp(lhost, pattern));
|
||||
else
|
||||
return(strcasecmp(shost, pattern));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns TRUE if the user/uid from sudoers matches the specified user/uid,
|
||||
* else returns FALSE.
|
||||
*/
|
||||
int
|
||||
userpw_matches(sudoers_user, user, pw)
|
||||
char *sudoers_user;
|
||||
char *user;
|
||||
void
|
||||
display_privs(pw)
|
||||
struct passwd *pw;
|
||||
{
|
||||
if (pw != NULL && *sudoers_user == '#') {
|
||||
uid_t uid = atoi(sudoers_user + 1);
|
||||
if (uid == pw->pw_uid)
|
||||
return(1);
|
||||
}
|
||||
return(strcmp(sudoers_user, user) == 0);
|
||||
}
|
||||
struct cmndspec *cs;
|
||||
struct member *m, *runas;
|
||||
struct privilege *priv;
|
||||
struct userspec *us;
|
||||
|
||||
/*
|
||||
* Returns TRUE if the given user belongs to the named group,
|
||||
* else returns FALSE.
|
||||
* XXX - reduce the number of passwd/group lookups
|
||||
*/
|
||||
int
|
||||
usergr_matches(group, user, pw)
|
||||
char *group;
|
||||
char *user;
|
||||
struct passwd *pw;
|
||||
{
|
||||
struct group *grp;
|
||||
gid_t pw_gid;
|
||||
char **cur;
|
||||
printf("User %s may run the following commands on this host:\n",
|
||||
pw->pw_name);
|
||||
|
||||
/* make sure we have a valid usergroup, sudo style */
|
||||
if (*group++ != '%')
|
||||
return(FALSE);
|
||||
for (us = userspecs; us != NULL; us = us->next) {
|
||||
if (user_matches(pw, us->user) != TRUE ||
|
||||
host_matches(user_shost, user_host, us->privileges->hostlist) != TRUE)
|
||||
continue;
|
||||
|
||||
/* look up user's primary gid in the passwd file */
|
||||
if (pw == NULL && (pw = getpwnam(user)) == NULL)
|
||||
return(FALSE);
|
||||
pw_gid = pw->pw_gid;
|
||||
|
||||
if ((grp = getgrnam(group)) == NULL)
|
||||
return(FALSE);
|
||||
|
||||
/* check against user's primary (passwd file) gid */
|
||||
if (grp->gr_gid == pw_gid)
|
||||
return(TRUE);
|
||||
|
||||
/* check to see if user is explicitly listed in the group */
|
||||
for (cur = grp->gr_mem; *cur; cur++) {
|
||||
if (strcmp(*cur, user) == 0)
|
||||
return(TRUE);
|
||||
}
|
||||
|
||||
return(FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns TRUE if "host" and "user" belong to the netgroup "netgr",
|
||||
* else return FALSE. Either of "host", "shost" or "user" may be NULL
|
||||
* in which case that argument is not checked...
|
||||
*/
|
||||
int
|
||||
netgr_matches(netgr, host, shost, user)
|
||||
char *netgr;
|
||||
char *host;
|
||||
char *shost;
|
||||
char *user;
|
||||
{
|
||||
#ifdef HAVE_GETDOMAINNAME
|
||||
static char *domain = (char *) -1;
|
||||
#else
|
||||
static char *domain = NULL;
|
||||
#endif /* HAVE_GETDOMAINNAME */
|
||||
|
||||
/* make sure we have a valid netgroup, sudo style */
|
||||
if (*netgr++ != '+')
|
||||
return(FALSE);
|
||||
|
||||
#ifdef HAVE_GETDOMAINNAME
|
||||
/* get the domain name (if any) */
|
||||
if (domain == (char *) -1) {
|
||||
domain = (char *) emalloc(MAXHOSTNAMELEN);
|
||||
if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') {
|
||||
free(domain);
|
||||
domain = NULL;
|
||||
priv = us->privileges;
|
||||
runas = NULL;
|
||||
for (cs = priv->cmndlist; cs != NULL; cs = cs->next) {
|
||||
fputs(" ", stdout);
|
||||
if (cs->runaslist != NULL)
|
||||
runas = cs->runaslist;
|
||||
if (runas != NULL) {
|
||||
fputs("(", stdout);
|
||||
for (m = runas; m != NULL; m = m->next) {
|
||||
if (m != runas)
|
||||
fputs(", ", stdout);
|
||||
print_member(m);
|
||||
}
|
||||
fputs(") ", stdout);
|
||||
}
|
||||
if (cs->tags.monitor != UNSPEC && cs->tags.monitor != def_monitor)
|
||||
printf("%sMONITOR: ", cs->tags.monitor ? "" : "NO");
|
||||
if (cs->tags.noexec != UNSPEC && cs->tags.noexec != def_noexec)
|
||||
printf("%sEXEC: ", cs->tags.noexec ? "NO" : "");
|
||||
if (cs->tags.nopasswd != UNSPEC && cs->tags.nopasswd != !def_authenticate)
|
||||
printf("%sPASSWD: ", cs->tags.nopasswd ? "NO" : "");
|
||||
print_member(cs->cmnd);
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
#endif /* HAVE_GETDOMAINNAME */
|
||||
|
||||
#ifdef HAVE_INNETGR
|
||||
if (innetgr(netgr, host, user, domain))
|
||||
return(TRUE);
|
||||
else if (host != shost && innetgr(netgr, shost, user, domain))
|
||||
return(TRUE);
|
||||
#endif /* HAVE_INNETGR */
|
||||
|
||||
return(FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns TRUE if "s" has shell meta characters in it,
|
||||
* else returns FALSE.
|
||||
* Print the contents of a struct member to stdout
|
||||
*/
|
||||
static int
|
||||
has_meta(s)
|
||||
char *s;
|
||||
void
|
||||
print_member(m)
|
||||
struct member *m;
|
||||
{
|
||||
char *t;
|
||||
|
||||
for (t = s; *t; t++) {
|
||||
if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']')
|
||||
return(TRUE);
|
||||
struct sudo_command *c;
|
||||
|
||||
if (m->negated)
|
||||
printf("!");
|
||||
if (m->name == NULL)
|
||||
printf("ALL");
|
||||
else if (m->type != COMMAND)
|
||||
printf("%s", m->name);
|
||||
else {
|
||||
c = (struct sudo_command *) m->name;
|
||||
printf("%s%s%s", c->cmnd, c->args ? " " : "",
|
||||
c->args ? c->args : "");
|
||||
}
|
||||
return(FALSE);
|
||||
}
|
||||
|
186
parse.h
186
parse.h
@@ -19,88 +19,164 @@
|
||||
#ifndef _SUDO_PARSE_H
|
||||
#define _SUDO_PARSE_H
|
||||
|
||||
/*
|
||||
* Data structure used in parsing sudoers;
|
||||
* top of stack values are the ones that
|
||||
* apply when parsing is done & can be
|
||||
* accessed by *_matches macros
|
||||
*/
|
||||
#define STACKINCREMENT (32)
|
||||
struct matchstack {
|
||||
int user;
|
||||
int cmnd;
|
||||
int host;
|
||||
int runas;
|
||||
int nopass;
|
||||
int noexec;
|
||||
int monitor;
|
||||
};
|
||||
#undef ALLOW
|
||||
#define ALLOW 1
|
||||
#undef DENY
|
||||
#define DENY 0
|
||||
#undef UNSPEC
|
||||
#define UNSPEC -1
|
||||
/* XXX - use NOTFOUND instead? */
|
||||
|
||||
/*
|
||||
* Data structure describing a command in the
|
||||
* sudoers file.
|
||||
* A command with args. XXX - merge into struct member.
|
||||
*/
|
||||
struct sudo_command {
|
||||
char *cmnd;
|
||||
char *args;
|
||||
};
|
||||
|
||||
#define user_matches (match[top-1].user)
|
||||
#define cmnd_matches (match[top-1].cmnd)
|
||||
#define host_matches (match[top-1].host)
|
||||
#define runas_matches (match[top-1].runas)
|
||||
#define no_passwd (match[top-1].nopass)
|
||||
#define no_execve (match[top-1].noexec)
|
||||
#define monitor_cmnd (match[top-1].monitor)
|
||||
|
||||
/*
|
||||
* Structure containing command matches if "sudo -l" is used.
|
||||
* Tags associated with a command.
|
||||
* Possible valus: TRUE, FALSE, UNSPEC.
|
||||
*/
|
||||
struct command_match {
|
||||
char *runas;
|
||||
size_t runas_len;
|
||||
size_t runas_size;
|
||||
char *cmnd;
|
||||
size_t cmnd_len;
|
||||
size_t cmnd_size;
|
||||
int nopasswd;
|
||||
int noexecve;
|
||||
int monitor;
|
||||
struct cmndtag {
|
||||
char nopasswd;
|
||||
char noexec;
|
||||
char monitor;
|
||||
char extra;
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure describing an alias match in parser.
|
||||
* The parses sudoers file is stored as a collection of linked lists,
|
||||
* modelled after the yacc grammar.
|
||||
*
|
||||
* There is no separate head struct, the first entry acts as the list head.
|
||||
* Because of this, the "last" field is only valid in the first entry.
|
||||
* The lack of a separate list head structure allows us to avoid keeping
|
||||
* static state in the parser and makes it easy to append sublists.
|
||||
*/
|
||||
typedef struct {
|
||||
int type;
|
||||
char *name;
|
||||
int val;
|
||||
} aliasinfo;
|
||||
|
||||
/*
|
||||
* Structure containing Cmnd_Alias's if "sudo -l" is used.
|
||||
* Structure describing a user specification and list thereof.
|
||||
*/
|
||||
struct generic_alias {
|
||||
int type;
|
||||
char *alias;
|
||||
char *entries;
|
||||
size_t entries_size;
|
||||
size_t entries_len;
|
||||
struct userspec {
|
||||
struct member *user; /* list of users */
|
||||
struct privilege *privileges; /* list of privileges */
|
||||
struct userspec *last, *next;
|
||||
};
|
||||
|
||||
/* The matching stack and number of entries on it. */
|
||||
extern struct matchstack *match;
|
||||
extern int top;
|
||||
/*
|
||||
* Structure describing a privilege specification.
|
||||
*/
|
||||
struct privilege {
|
||||
struct member *hostlist; /* list of hosts */
|
||||
struct cmndspec *cmndlist; /* list of Cmnd_Specs */
|
||||
struct privilege *last, *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure describing a linked list of Cmnd_Specs.
|
||||
*/
|
||||
struct cmndspec {
|
||||
struct member *runaslist; /* list of runas users */
|
||||
struct member *cmnd; /* command to allow/deny */
|
||||
struct cmndtag tags; /* tag specificaion */
|
||||
struct cmndspec *last, *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* Generic structure to hold users, hosts, commands.
|
||||
*/
|
||||
struct member {
|
||||
char *name; /* member name */
|
||||
short type; /* type (see gram.h) */
|
||||
short negated; /* negated via '!'? */
|
||||
struct member *last, *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* Generic structure to hold {User,Host,Runas,Cmnd}_Alias
|
||||
*/
|
||||
struct alias {
|
||||
char *name; /* alias name */
|
||||
int type; /* {USER,HOST,RUNAS,CMND}ALIAS */
|
||||
struct member *first_member; /* list of alias members */
|
||||
struct alias *last, *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure describing a Defaults entry and a list thereof.
|
||||
*/
|
||||
struct defaults {
|
||||
char *var; /* variable name */
|
||||
char *val; /* variable value */
|
||||
struct member *binding; /* user/host/runas binding */
|
||||
int type; /* DEFAULTS{,_USER,_RUNAS,_HOST} */
|
||||
int op; /* TRUE, FALSE, '+', '-' */
|
||||
struct defaults *last, *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* Allocat space for a struct alias and populate it.
|
||||
*/
|
||||
#define NEW_ALIAS(r, n, t, m) do { \
|
||||
(r) = emalloc(sizeof(struct alias)); \
|
||||
(r)->name = (n); \
|
||||
(r)->type = (t); \
|
||||
(r)->first_member = (m); \
|
||||
(r)->last = NULL; \
|
||||
(r)->next = NULL; \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Allocat space for a defaults entry and populate it.
|
||||
*/
|
||||
#define NEW_DEFAULT(r, v1, v2, o) do { \
|
||||
(r) = emalloc(sizeof(struct defaults)); \
|
||||
(r)->var = (v1); \
|
||||
(r)->val = (v2); \
|
||||
(r)->op = (o); \
|
||||
(r)->last = NULL; \
|
||||
(r)->next = NULL; \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Allocat space for a member and populate it.
|
||||
*/
|
||||
#define NEW_MEMBER(r, n, t) do { \
|
||||
(r) = emalloc(sizeof(struct member)); \
|
||||
(r)->name = (n); \
|
||||
(r)->type = (t); \
|
||||
(r)->last = NULL; \
|
||||
(r)->next = NULL; \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Append an entry to the tail of a list.
|
||||
*/
|
||||
#define LIST_APPEND(h, e) do { \
|
||||
if ((h)->last != NULL) \
|
||||
(h)->last->next = (e); \
|
||||
else /* if ((h)->next == NULL) */ \
|
||||
(h)->next = (e); \
|
||||
(h)->last = (e); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int addr_matches __P((char *));
|
||||
int alias_matches __P((char *, int, VOID *, VOID *));
|
||||
int cmnd_matches __P((char *, char *, struct member *));
|
||||
int command_matches __P((char *, char *));
|
||||
int host_matches __P((char *, char *, struct member *));
|
||||
int hostname_matches __P((char *, char *, char *));
|
||||
int netgr_matches __P((char *, char *, char *, char *));
|
||||
int userpw_matches __P((char *, char *, struct passwd *));
|
||||
int runas_matches __P((struct passwd *, struct member *));
|
||||
int user_matches __P((struct passwd *, struct member *));
|
||||
int usergr_matches __P((char *, char *, struct passwd *));
|
||||
void init_parser __P((char *));
|
||||
int userpw_matches __P((char *, char *, struct passwd *));
|
||||
void init_parser __P((char *, int));
|
||||
void print_member __P((struct member *m));
|
||||
|
||||
#endif /* _SUDO_PARSE_H */
|
||||
|
1285
parse.yacc
1285
parse.yacc
File diff suppressed because it is too large
Load Diff
2082
sudo.tab.c
2082
sudo.tab.c
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user