%{ /* * Copyright (c) 1996, 1998-2005, 2007-2010 * Todd C. Miller * * 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 #include #include #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif /* STDC_HEADERS */ #ifdef HAVE_STRING_H # include #endif /* HAVE_STRING_H */ #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ #if defined(YYBISON) && defined(HAVE_ALLOCA_H) && !defined(__GNUC__) # include #endif /* YYBISON && HAVE_ALLOCA_H && !__GNUC__ */ #include #include "sudo.h" #include "parse.h" /* * We must define SIZE_MAX for yacc's skeleton.c. * If there is no SIZE_MAX or SIZE_T_MAX we have to assume that size_t * could be signed (as it is on SunOS 4.x). */ #ifndef SIZE_MAX # ifdef SIZE_T_MAX # define SIZE_MAX SIZE_T_MAX # else # define SIZE_MAX INT_MAX # endif /* SIZE_T_MAX */ #endif /* SIZE_MAX */ /* * Globals */ extern int sudolineno; extern char *sudoers; int parse_error; int pedantic = FALSE; int verbose = FALSE; int errorlineno = -1; char *errorfile = NULL; struct defaults_list defaults; struct userspec_list userspecs; /* * Local protoypes */ static void add_defaults __P((int, struct member *, struct defaults *)); static void add_userspec __P((struct member *, struct privilege *)); static struct defaults *new_default __P((char *, char *, int)); static struct member *new_member __P((char *, int)); 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 cmndspec *cmndspec; struct defaults *defaults; struct member *member; struct runascontainer *runas; struct privilege *privilege; struct sudo_command command; struct cmndtag tag; struct selinux_info seinfo; char *string; int tok; } %start file /* special start symbol */ %token COMMAND /* absolute pathname w/ optional args */ %token ALIAS /* an UPPERCASE alias name */ %token DEFVAR /* a Defaults variable name */ %token NTWKADDR /* ipv4 or ipv6 address */ %token NETGROUP /* a netgroup (+NAME) */ %token USERGROUP /* a usergroup (%NAME) */ %token WORD /* a word */ %token DEFAULTS /* Defaults entry */ %token DEFAULTS_HOST /* Host-specific defaults entry */ %token DEFAULTS_USER /* User-specific defaults entry */ %token DEFAULTS_RUNAS /* Runas-specific defaults entry */ %token DEFAULTS_CMND /* Command-specific defaults entry */ %token NOPASSWD /* no passwd req for command */ %token PASSWD /* passwd req for command (default) */ %token NOEXEC /* preload dummy execve() for cmnd */ %token EXEC /* don't preload dummy execve() */ %token SETENV /* user may set environment for cmnd */ %token NOSETENV /* user may not set environment */ %token LOG_INPUT /* log user's cmnd input */ %token NOLOG_INPUT /* don't log user's cmnd input */ %token LOG_OUTPUT /* log cmnd output */ %token NOLOG_OUTPUT /* don't log cmnd output */ %token ALL /* ALL keyword */ %token COMMENT /* comment and/or carriage return */ %token HOSTALIAS /* Host_Alias keyword */ %token CMNDALIAS /* Cmnd_Alias keyword */ %token USERALIAS /* User_Alias keyword */ %token RUNASALIAS /* Runas_Alias keyword */ %token ':' '=' ',' '!' '+' '-' /* union member tokens */ %token '(' ')' /* runas tokens */ %token ERROR %token TYPE /* SELinux type */ %token ROLE /* SELinux role */ %type cmndspec %type cmndspeclist %type defaults_entry %type defaults_list %type cmnd %type opcmnd %type cmndlist %type host %type hostlist %type ophost %type opuser %type user %type userlist %type opgroup %type group %type grouplist %type runasspec %type runaslist %type privilege %type privileges %type cmndtag %type selinux %type rolespec %type typespec %% file : { ; } | line ; line : entry | line entry ; entry : COMMENT { ; } | error COMMENT { yyerrok; } | userlist privileges { add_userspec($1, $2); } | USERALIAS useraliases { ; } | HOSTALIAS hostaliases { ; } | CMNDALIAS cmndaliases { ; } | RUNASALIAS runasaliases { ; } | DEFAULTS defaults_list { add_defaults(DEFAULTS, NULL, $2); } | DEFAULTS_USER userlist defaults_list { add_defaults(DEFAULTS_USER, $2, $3); } | DEFAULTS_RUNAS userlist defaults_list { add_defaults(DEFAULTS_RUNAS, $2, $3); } | DEFAULTS_HOST hostlist defaults_list { add_defaults(DEFAULTS_HOST, $2, $3); } | DEFAULTS_CMND cmndlist defaults_list { add_defaults(DEFAULTS_CMND, $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 privilege *p = emalloc(sizeof(*p)); list2tq(&p->hostlist, $1); list2tq(&p->cmndlist, $3); p->prev = p; p->next = NULL; $$ = p; } ; ophost : host { $$ = $1; $$->negated = FALSE; } | '!' host { $$ = $2; $$->negated = TRUE; } ; host : ALIAS { $$ = new_member($1, ALIAS); } | 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); #ifdef HAVE_SELINUX /* propagate role and type */ if ($3->role == NULL) $3->role = $3->prev->role; if ($3->type == NULL) $3->type = $3->prev->type; #endif /* HAVE_SELINUX */ /* propagate tags and runas list */ if ($3->tags.nopasswd == UNSPEC) $3->tags.nopasswd = $3->prev->tags.nopasswd; if ($3->tags.noexec == UNSPEC) $3->tags.noexec = $3->prev->tags.noexec; if ($3->tags.setenv == UNSPEC && $3->prev->tags.setenv != IMPLIED) $3->tags.setenv = $3->prev->tags.setenv; if ($3->tags.log_input == UNSPEC) $3->tags.log_input = $3->prev->tags.log_input; if ($3->tags.log_output == UNSPEC) $3->tags.log_output = $3->prev->tags.log_output; if ((tq_empty(&$3->runasuserlist) && tq_empty(&$3->runasgrouplist)) && (!tq_empty(&$3->prev->runasuserlist) || !tq_empty(&$3->prev->runasgrouplist))) { $3->runasuserlist = $3->prev->runasuserlist; $3->runasgrouplist = $3->prev->runasgrouplist; } $$ = $1; } ; cmndspec : runasspec selinux cmndtag opcmnd { struct cmndspec *cs = emalloc(sizeof(*cs)); if ($1 != NULL) { list2tq(&cs->runasuserlist, $1->runasusers); list2tq(&cs->runasgrouplist, $1->runasgroups); efree($1); } else { tq_init(&cs->runasuserlist); tq_init(&cs->runasgrouplist); } #ifdef HAVE_SELINUX cs->role = $2.role; cs->type = $2.type; #endif cs->tags = $3; cs->cmnd = $4; cs->prev = cs; cs->next = NULL; /* sudo "ALL" implies the SETENV tag */ if (cs->cmnd->type == ALL && !cs->cmnd->negated && cs->tags.setenv == UNSPEC) cs->tags.setenv = IMPLIED; $$ = cs; } ; opcmnd : cmnd { $$ = $1; $$->negated = FALSE; } | '!' cmnd { $$ = $2; $$->negated = TRUE; } ; rolespec : ROLE '=' WORD { $$ = $3; } ; typespec : TYPE '=' WORD { $$ = $3; } ; selinux : /* empty */ { $$.role = NULL; $$.type = NULL; } | rolespec { $$.role = $1; $$.type = NULL; } | typespec { $$.type = $1; $$.role = NULL; } | rolespec typespec { $$.role = $1; $$.type = $2; } | typespec rolespec { $$.type = $1; $$.role = $2; } ; runasspec : /* empty */ { $$ = NULL; } | '(' runaslist ')' { $$ = $2; } ; runaslist : userlist { $$ = emalloc(sizeof(struct runascontainer)); $$->runasusers = $1; $$->runasgroups = NULL; } | userlist ':' grouplist { $$ = emalloc(sizeof(struct runascontainer)); $$->runasusers = $1; $$->runasgroups = $3; } | ':' grouplist { $$ = emalloc(sizeof(struct runascontainer)); $$->runasusers = NULL; $$->runasgroups = $2; } ; cmndtag : /* empty */ { $$.nopasswd = $$.noexec = $$.setenv = $$.log_input = $$.log_output = UNSPEC; } | cmndtag NOPASSWD { $$.nopasswd = TRUE; } | cmndtag PASSWD { $$.nopasswd = FALSE; } | cmndtag NOEXEC { $$.noexec = TRUE; } | cmndtag EXEC { $$.noexec = FALSE; } | cmndtag SETENV { $$.setenv = TRUE; } | cmndtag NOSETENV { $$.setenv = FALSE; } | cmndtag LOG_INPUT { $$.log_input = TRUE; } | cmndtag NOLOG_INPUT { $$.log_input = FALSE; } | cmndtag LOG_OUTPUT { $$.log_output = TRUE; } | cmndtag NOLOG_OUTPUT { $$.log_output = FALSE; } ; cmnd : ALL { $$ = new_member(NULL, ALL); } | ALIAS { $$ = new_member($1, ALIAS); } | 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 ; hostalias : ALIAS '=' hostlist { char *s; if ((s = alias_add($1, HOSTALIAS, $3)) != NULL) { yyerror(s); YYERROR; } } ; hostlist : ophost | hostlist ',' ophost { list_append($1, $3); $$ = $1; } ; cmndaliases : cmndalias | cmndaliases ':' cmndalias ; cmndalias : ALIAS '=' cmndlist { char *s; if ((s = alias_add($1, CMNDALIAS, $3)) != NULL) { yyerror(s); YYERROR; } } ; cmndlist : opcmnd | cmndlist ',' opcmnd { list_append($1, $3); $$ = $1; } ; runasaliases : runasalias | runasaliases ':' runasalias ; runasalias : ALIAS '=' userlist { char *s; if ((s = alias_add($1, RUNASALIAS, $3)) != NULL) { yyerror(s); YYERROR; } } ; useraliases : useralias | useraliases ':' useralias ; useralias : ALIAS '=' userlist { char *s; if ((s = alias_add($1, USERALIAS, $3)) != NULL) { yyerror(s); YYERROR; } } ; userlist : opuser | userlist ',' opuser { list_append($1, $3); $$ = $1; } ; opuser : user { $$ = $1; $$->negated = FALSE; } | '!' user { $$ = $2; $$->negated = TRUE; } ; user : ALIAS { $$ = new_member($1, ALIAS); } | ALL { $$ = new_member(NULL, ALL); } | NETGROUP { $$ = new_member($1, NETGROUP); } | USERGROUP { $$ = new_member($1, USERGROUP); } | WORD { $$ = new_member($1, WORD); } ; grouplist : opgroup | grouplist ',' opgroup { list_append($1, $3); $$ = $1; } ; opgroup : group { $$ = $1; $$->negated = FALSE; } | '!' group { $$ = $2; $$->negated = TRUE; } ; group : ALIAS { $$ = new_member($1, ALIAS); } | ALL { $$ = new_member(NULL, ALL); } | WORD { $$ = new_member($1, WORD); } ; %% static struct defaults * new_default(var, val, op) char *var; char *val; int op; { struct defaults *d; d = emalloc(sizeof(struct defaults)); d->var = var; d->val = val; tq_init(&d->binding); d->type = 0; d->op = op; d->prev = d; d->next = NULL; return d; } static struct member * new_member(name, type) char *name; int type; { struct member *m; m = emalloc(sizeof(struct member)); m->name = name; m->type = type; m->prev = m; m->next = NULL; return m; } /* * 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, bmem, defs) int type; struct member *bmem; struct defaults *defs; { struct defaults *d; struct member_list binding; /* * We can only call list2tq once on bmem as it will zero * out the prev pointer when it consumes bmem. */ list2tq(&binding, bmem); /* * 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; } tq_append(&defaults, 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)); list2tq(&u->users, members); list2tq(&u->privileges, privs); u->prev = u; u->next = NULL; tq_append(&userspecs, 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 defaults *d; struct member *m, *binding; struct userspec *us; struct privilege *priv; struct cmndspec *cs; struct sudo_command *c; while ((us = tq_pop(&userspecs)) != NULL) { while ((m = tq_pop(&us->users)) != NULL) { efree(m->name); efree(m); } while ((priv = tq_pop(&us->privileges)) != NULL) { struct member *runasuser = NULL, *runasgroup = NULL; #ifdef HAVE_SELINUX char *role = NULL, *type = NULL; #endif /* HAVE_SELINUX */ while ((m = tq_pop(&priv->hostlist)) != NULL) { efree(m->name); efree(m); } while ((cs = tq_pop(&priv->cmndlist)) != NULL) { #ifdef HAVE_SELINUX /* Only free the first instance of a role/type. */ if (cs->role != role) { role = cs->role; efree(cs->role); } if (cs->type != type) { type = cs->type; efree(cs->type); } #endif /* HAVE_SELINUX */ if (tq_last(&cs->runasuserlist) != runasuser) { runasuser = tq_last(&cs->runasuserlist); while ((m = tq_pop(&cs->runasuserlist)) != NULL) { efree(m->name); efree(m); } } if (tq_last(&cs->runasgrouplist) != runasgroup) { runasgroup = tq_last(&cs->runasgrouplist); while ((m = tq_pop(&cs->runasgrouplist)) != NULL) { efree(m->name); efree(m); } } if (cs->cmnd->type == COMMAND) { c = (struct sudo_command *) cs->cmnd->name; efree(c->cmnd); efree(c->args); } efree(cs->cmnd->name); efree(cs->cmnd); efree(cs); } efree(priv); } efree(us); } tq_init(&userspecs); binding = NULL; while ((d = tq_pop(&defaults)) != NULL) { if (tq_last(&d->binding) != binding) { binding = tq_last(&d->binding); while ((m = tq_pop(&d->binding)) != NULL) { if (m->type == COMMAND) { c = (struct sudo_command *) m->name; efree(c->cmnd); efree(c->args); } efree(m->name); efree(m); } } efree(d->var); efree(d->val); efree(d); } tq_init(&defaults); init_aliases(); init_lexer(); efree(sudoers); sudoers = path ? estrdup(path) : NULL; parse_error = FALSE; errorlineno = -1; errorfile = NULL; verbose = !quiet; }