1%{ 2/* 3 * Copyright (c) 1996, 1998-2005, 2007-2010 4 * Todd C. Miller <Todd.Miller@courtesan.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 18 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 19 * 20 * Sponsored in part by the Defense Advanced Research Projects 21 * Agency (DARPA) and Air Force Research Laboratory, Air Force 22 * Materiel Command, USAF, under agreement number F39502-99-1-0512. 23 */ 24 25#include <config.h> 26 27#include <sys/types.h> 28#include <sys/param.h> 29#include <stdio.h> 30#ifdef STDC_HEADERS 31# include <stdlib.h> 32# include <stddef.h> 33#else 34# ifdef HAVE_STDLIB_H 35# include <stdlib.h> 36# endif 37#endif /* STDC_HEADERS */ 38#ifdef HAVE_STRING_H 39# include <string.h> 40#endif /* HAVE_STRING_H */ 41#ifdef HAVE_STRINGS_H 42# include <strings.h> 43#endif /* HAVE_STRINGS_H */ 44#ifdef HAVE_UNISTD_H 45# include <unistd.h> 46#endif /* HAVE_UNISTD_H */ 47#if defined(YYBISON) && defined(HAVE_ALLOCA_H) && !defined(__GNUC__) 48# include <alloca.h> 49#endif /* YYBISON && HAVE_ALLOCA_H && !__GNUC__ */ 50#include <limits.h> 51 52#include "sudo.h" 53#include "parse.h" 54#include "gram.h" 55 56/* 57 * We must define SIZE_MAX for yacc's skeleton.c. 58 * If there is no SIZE_MAX or SIZE_T_MAX we have to assume that size_t 59 * could be signed (as it is on SunOS 4.x). 60 */ 61#ifndef SIZE_MAX 62# ifdef SIZE_T_MAX 63# define SIZE_MAX SIZE_T_MAX 64# else 65# define SIZE_MAX INT_MAX 66# endif /* SIZE_T_MAX */ 67#endif /* SIZE_MAX */ 68 69/* 70 * Globals 71 */ 72extern int sudolineno; 73extern int last_token; 74extern char *sudoers; 75int sudoers_warnings = TRUE; 76int parse_error = FALSE; 77int pedantic = FALSE; 78int errorlineno = -1; 79char *errorfile = NULL; 80 81struct defaults_list defaults; 82struct userspec_list userspecs; 83 84/* 85 * Local protoypes 86 */ 87static void add_defaults __P((int, struct member *, struct defaults *)); 88static void add_userspec __P((struct member *, struct privilege *)); 89static struct defaults *new_default __P((char *, char *, int)); 90static struct member *new_member __P((char *, int)); 91 void yyerror __P((const char *)); 92 93void 94yyerror(s) 95 const char *s; 96{ 97 /* If we last saw a newline the error is on the preceding line. */ 98 if (last_token == COMMENT) 99 sudolineno--; 100 101 /* Save the line the first error occurred on. */ 102 if (errorlineno == -1) { 103 errorlineno = sudolineno; 104 errorfile = estrdup(sudoers); 105 } 106 if (sudoers_warnings && s != NULL) { 107#ifndef TRACELEXER 108 (void) fprintf(stderr, ">>> %s: %s near line %d <<<\n", sudoers, s, 109 sudolineno); 110#else 111 (void) fprintf(stderr, "<*> "); 112#endif 113 } 114 parse_error = TRUE; 115} 116%} 117 118%union { 119 struct cmndspec *cmndspec; 120 struct defaults *defaults; 121 struct member *member; 122 struct runascontainer *runas; 123 struct privilege *privilege; 124 struct sudo_command command; 125 struct cmndtag tag; 126 struct selinux_info seinfo; 127 char *string; 128 int tok; 129} 130 131%start file /* special start symbol */ 132%token <command> COMMAND /* absolute pathname w/ optional args */ 133%token <string> ALIAS /* an UPPERCASE alias name */ 134%token <string> DEFVAR /* a Defaults variable name */ 135%token <string> NTWKADDR /* ipv4 or ipv6 address */ 136%token <string> NETGROUP /* a netgroup (+NAME) */ 137%token <string> USERGROUP /* a usergroup (%NAME) */ 138%token <string> WORD /* a word */ 139%token <tok> DEFAULTS /* Defaults entry */ 140%token <tok> DEFAULTS_HOST /* Host-specific defaults entry */ 141%token <tok> DEFAULTS_USER /* User-specific defaults entry */ 142%token <tok> DEFAULTS_RUNAS /* Runas-specific defaults entry */ 143%token <tok> DEFAULTS_CMND /* Command-specific defaults entry */ 144%token <tok> NOPASSWD /* no passwd req for command */ 145%token <tok> PASSWD /* passwd req for command (default) */ 146%token <tok> NOEXEC /* preload dummy execve() for cmnd */ 147%token <tok> EXEC /* don't preload dummy execve() */ 148%token <tok> SETENV /* user may set environment for cmnd */ 149%token <tok> NOSETENV /* user may not set environment */ 150%token <tok> LOG_INPUT /* log user's cmnd input */ 151%token <tok> NOLOG_INPUT /* don't log user's cmnd input */ 152%token <tok> LOG_OUTPUT /* log cmnd output */ 153%token <tok> NOLOG_OUTPUT /* don't log cmnd output */ 154%token <tok> ALL /* ALL keyword */ 155%token <tok> COMMENT /* comment and/or carriage return */ 156%token <tok> HOSTALIAS /* Host_Alias keyword */ 157%token <tok> CMNDALIAS /* Cmnd_Alias keyword */ 158%token <tok> USERALIAS /* User_Alias keyword */ 159%token <tok> RUNASALIAS /* Runas_Alias keyword */ 160%token <tok> ':' '=' ',' '!' '+' '-' /* union member tokens */ 161%token <tok> '(' ')' /* runas tokens */ 162%token <tok> ERROR 163%token <tok> TYPE /* SELinux type */ 164%token <tok> ROLE /* SELinux role */ 165 166%type <cmndspec> cmndspec 167%type <cmndspec> cmndspeclist 168%type <defaults> defaults_entry 169%type <defaults> defaults_list 170%type <member> cmnd 171%type <member> opcmnd 172%type <member> cmndlist 173%type <member> host 174%type <member> hostlist 175%type <member> ophost 176%type <member> opuser 177%type <member> user 178%type <member> userlist 179%type <member> opgroup 180%type <member> group 181%type <member> grouplist 182%type <runas> runasspec 183%type <runas> runaslist 184%type <privilege> privilege 185%type <privilege> privileges 186%type <tag> cmndtag 187%type <seinfo> selinux 188%type <string> rolespec 189%type <string> typespec 190 191%% 192 193file : { ; } 194 | line 195 ; 196 197line : entry 198 | line entry 199 ; 200 201entry : COMMENT { 202 ; 203 } 204 | error COMMENT { 205 yyerrok; 206 } 207 | userlist privileges { 208 add_userspec($1, $2); 209 } 210 | USERALIAS useraliases { 211 ; 212 } 213 | HOSTALIAS hostaliases { 214 ; 215 } 216 | CMNDALIAS cmndaliases { 217 ; 218 } 219 | RUNASALIAS runasaliases { 220 ; 221 } 222 | DEFAULTS defaults_list { 223 add_defaults(DEFAULTS, NULL, $2); 224 } 225 | DEFAULTS_USER userlist defaults_list { 226 add_defaults(DEFAULTS_USER, $2, $3); 227 } 228 | DEFAULTS_RUNAS userlist defaults_list { 229 add_defaults(DEFAULTS_RUNAS, $2, $3); 230 } 231 | DEFAULTS_HOST hostlist defaults_list { 232 add_defaults(DEFAULTS_HOST, $2, $3); 233 } 234 | DEFAULTS_CMND cmndlist defaults_list { 235 add_defaults(DEFAULTS_CMND, $2, $3); 236 } 237 ; 238 239defaults_list : defaults_entry 240 | defaults_list ',' defaults_entry { 241 list_append($1, $3); 242 $$ = $1; 243 } 244 ; 245 246defaults_entry : DEFVAR { 247 $$ = new_default($1, NULL, TRUE); 248 } 249 | '!' DEFVAR { 250 $$ = new_default($2, NULL, FALSE); 251 } 252 | DEFVAR '=' WORD { 253 $$ = new_default($1, $3, TRUE); 254 } 255 | DEFVAR '+' WORD { 256 $$ = new_default($1, $3, '+'); 257 } 258 | DEFVAR '-' WORD { 259 $$ = new_default($1, $3, '-'); 260 } 261 ; 262 263privileges : privilege 264 | privileges ':' privilege { 265 list_append($1, $3); 266 $$ = $1; 267 } 268 ; 269 270privilege : hostlist '=' cmndspeclist { 271 struct privilege *p = emalloc(sizeof(*p)); 272 list2tq(&p->hostlist, $1); 273 list2tq(&p->cmndlist, $3); 274 p->prev = p; 275 p->next = NULL; 276 $$ = p; 277 } 278 ; 279 280ophost : host { 281 $$ = $1; 282 $$->negated = FALSE; 283 } 284 | '!' host { 285 $$ = $2; 286 $$->negated = TRUE; 287 } 288 ; 289 290host : ALIAS { 291 $$ = new_member($1, ALIAS); 292 } 293 | ALL { 294 $$ = new_member(NULL, ALL); 295 } 296 | NETGROUP { 297 $$ = new_member($1, NETGROUP); 298 } 299 | NTWKADDR { 300 $$ = new_member($1, NTWKADDR); 301 } 302 | WORD { 303 $$ = new_member($1, WORD); 304 } 305 ; 306 307cmndspeclist : cmndspec 308 | cmndspeclist ',' cmndspec { 309 list_append($1, $3); 310#ifdef HAVE_SELINUX 311 /* propagate role and type */ 312 if ($3->role == NULL) 313 $3->role = $3->prev->role; 314 if ($3->type == NULL) 315 $3->type = $3->prev->type; 316#endif /* HAVE_SELINUX */ 317 /* propagate tags and runas list */ 318 if ($3->tags.nopasswd == UNSPEC) 319 $3->tags.nopasswd = $3->prev->tags.nopasswd; 320 if ($3->tags.noexec == UNSPEC) 321 $3->tags.noexec = $3->prev->tags.noexec; 322 if ($3->tags.setenv == UNSPEC && 323 $3->prev->tags.setenv != IMPLIED) 324 $3->tags.setenv = $3->prev->tags.setenv; 325 if ($3->tags.log_input == UNSPEC) 326 $3->tags.log_input = $3->prev->tags.log_input; 327 if ($3->tags.log_output == UNSPEC) 328 $3->tags.log_output = $3->prev->tags.log_output; 329 if ((tq_empty(&$3->runasuserlist) && 330 tq_empty(&$3->runasgrouplist)) && 331 (!tq_empty(&$3->prev->runasuserlist) || 332 !tq_empty(&$3->prev->runasgrouplist))) { 333 $3->runasuserlist = $3->prev->runasuserlist; 334 $3->runasgrouplist = $3->prev->runasgrouplist; 335 } 336 $$ = $1; 337 } 338 ; 339 340cmndspec : runasspec selinux cmndtag opcmnd { 341 struct cmndspec *cs = emalloc(sizeof(*cs)); 342 if ($1 != NULL) { 343 list2tq(&cs->runasuserlist, $1->runasusers); 344 list2tq(&cs->runasgrouplist, $1->runasgroups); 345 efree($1); 346 } else { 347 tq_init(&cs->runasuserlist); 348 tq_init(&cs->runasgrouplist); 349 } 350#ifdef HAVE_SELINUX 351 cs->role = $2.role; 352 cs->type = $2.type; 353#endif 354 cs->tags = $3; 355 cs->cmnd = $4; 356 cs->prev = cs; 357 cs->next = NULL; 358 /* sudo "ALL" implies the SETENV tag */ 359 if (cs->cmnd->type == ALL && !cs->cmnd->negated && 360 cs->tags.setenv == UNSPEC) 361 cs->tags.setenv = IMPLIED; 362 $$ = cs; 363 } 364 ; 365 366opcmnd : cmnd { 367 $$ = $1; 368 $$->negated = FALSE; 369 } 370 | '!' cmnd { 371 $$ = $2; 372 $$->negated = TRUE; 373 } 374 ; 375 376rolespec : ROLE '=' WORD { 377 $$ = $3; 378 } 379 ; 380 381typespec : TYPE '=' WORD { 382 $$ = $3; 383 } 384 ; 385 386selinux : /* empty */ { 387 $$.role = NULL; 388 $$.type = NULL; 389 } 390 | rolespec { 391 $$.role = $1; 392 $$.type = NULL; 393 } 394 | typespec { 395 $$.type = $1; 396 $$.role = NULL; 397 } 398 | rolespec typespec { 399 $$.role = $1; 400 $$.type = $2; 401 } 402 | typespec rolespec { 403 $$.type = $1; 404 $$.role = $2; 405 } 406 ; 407 408runasspec : /* empty */ { 409 $$ = NULL; 410 } 411 | '(' runaslist ')' { 412 $$ = $2; 413 } 414 ; 415 416runaslist : userlist { 417 $$ = emalloc(sizeof(struct runascontainer)); 418 $$->runasusers = $1; 419 $$->runasgroups = NULL; 420 } 421 | userlist ':' grouplist { 422 $$ = emalloc(sizeof(struct runascontainer)); 423 $$->runasusers = $1; 424 $$->runasgroups = $3; 425 } 426 | ':' grouplist { 427 $$ = emalloc(sizeof(struct runascontainer)); 428 $$->runasusers = NULL; 429 $$->runasgroups = $2; 430 } 431 ; 432 433cmndtag : /* empty */ { 434 $$.nopasswd = $$.noexec = $$.setenv = 435 $$.log_input = $$.log_output = UNSPEC; 436 } 437 | cmndtag NOPASSWD { 438 $$.nopasswd = TRUE; 439 } 440 | cmndtag PASSWD { 441 $$.nopasswd = FALSE; 442 } 443 | cmndtag NOEXEC { 444 $$.noexec = TRUE; 445 } 446 | cmndtag EXEC { 447 $$.noexec = FALSE; 448 } 449 | cmndtag SETENV { 450 $$.setenv = TRUE; 451 } 452 | cmndtag NOSETENV { 453 $$.setenv = FALSE; 454 } 455 | cmndtag LOG_INPUT { 456 $$.log_input = TRUE; 457 } 458 | cmndtag NOLOG_INPUT { 459 $$.log_input = FALSE; 460 } 461 | cmndtag LOG_OUTPUT { 462 $$.log_output = TRUE; 463 } 464 | cmndtag NOLOG_OUTPUT { 465 $$.log_output = FALSE; 466 } 467 ; 468 469cmnd : ALL { 470 $$ = new_member(NULL, ALL); 471 } 472 | ALIAS { 473 $$ = new_member($1, ALIAS); 474 } 475 | COMMAND { 476 struct sudo_command *c = emalloc(sizeof(*c)); 477 c->cmnd = $1.cmnd; 478 c->args = $1.args; 479 $$ = new_member((char *)c, COMMAND); 480 } 481 ; 482 483hostaliases : hostalias 484 | hostaliases ':' hostalias 485 ; 486 487hostalias : ALIAS '=' hostlist { 488 char *s; 489 if ((s = alias_add($1, HOSTALIAS, $3)) != NULL) { 490 yyerror(s); 491 YYERROR; 492 } 493 } 494 ; 495 496hostlist : ophost 497 | hostlist ',' ophost { 498 list_append($1, $3); 499 $$ = $1; 500 } 501 ; 502 503cmndaliases : cmndalias 504 | cmndaliases ':' cmndalias 505 ; 506 507cmndalias : ALIAS '=' cmndlist { 508 char *s; 509 if ((s = alias_add($1, CMNDALIAS, $3)) != NULL) { 510 yyerror(s); 511 YYERROR; 512 } 513 } 514 ; 515 516cmndlist : opcmnd 517 | cmndlist ',' opcmnd { 518 list_append($1, $3); 519 $$ = $1; 520 } 521 ; 522 523runasaliases : runasalias 524 | runasaliases ':' runasalias 525 ; 526 527runasalias : ALIAS '=' userlist { 528 char *s; 529 if ((s = alias_add($1, RUNASALIAS, $3)) != NULL) { 530 yyerror(s); 531 YYERROR; 532 } 533 } 534 ; 535 536useraliases : useralias 537 | useraliases ':' useralias 538 ; 539 540useralias : ALIAS '=' userlist { 541 char *s; 542 if ((s = alias_add($1, USERALIAS, $3)) != NULL) { 543 yyerror(s); 544 YYERROR; 545 } 546 } 547 ; 548 549userlist : opuser 550 | userlist ',' opuser { 551 list_append($1, $3); 552 $$ = $1; 553 } 554 ; 555 556opuser : user { 557 $$ = $1; 558 $$->negated = FALSE; 559 } 560 | '!' user { 561 $$ = $2; 562 $$->negated = TRUE; 563 } 564 ; 565 566user : ALIAS { 567 $$ = new_member($1, ALIAS); 568 } 569 | ALL { 570 $$ = new_member(NULL, ALL); 571 } 572 | NETGROUP { 573 $$ = new_member($1, NETGROUP); 574 } 575 | USERGROUP { 576 $$ = new_member($1, USERGROUP); 577 } 578 | WORD { 579 $$ = new_member($1, WORD); 580 } 581 ; 582 583grouplist : opgroup 584 | grouplist ',' opgroup { 585 list_append($1, $3); 586 $$ = $1; 587 } 588 ; 589 590opgroup : group { 591 $$ = $1; 592 $$->negated = FALSE; 593 } 594 | '!' group { 595 $$ = $2; 596 $$->negated = TRUE; 597 } 598 ; 599 600group : ALIAS { 601 $$ = new_member($1, ALIAS); 602 } 603 | ALL { 604 $$ = new_member(NULL, ALL); 605 } 606 | WORD { 607 $$ = new_member($1, WORD); 608 } 609 ; 610 611%% 612static struct defaults * 613new_default(var, val, op) 614 char *var; 615 char *val; 616 int op; 617{ 618 struct defaults *d; 619 620 d = emalloc(sizeof(struct defaults)); 621 d->var = var; 622 d->val = val; 623 tq_init(&d->binding); 624 d->type = 0; 625 d->op = op; 626 d->prev = d; 627 d->next = NULL; 628 629 return d; 630} 631 632static struct member * 633new_member(name, type) 634 char *name; 635 int type; 636{ 637 struct member *m; 638 639 m = emalloc(sizeof(struct member)); 640 m->name = name; 641 m->type = type; 642 m->prev = m; 643 m->next = NULL; 644 645 return m; 646} 647 648/* 649 * Add a list of defaults structures to the defaults list. 650 * The binding, if non-NULL, specifies a list of hosts, users, or 651 * runas users the entries apply to (specified by the type). 652 */ 653static void 654add_defaults(type, bmem, defs) 655 int type; 656 struct member *bmem; 657 struct defaults *defs; 658{ 659 struct defaults *d; 660 struct member_list binding; 661 662 /* 663 * We can only call list2tq once on bmem as it will zero 664 * out the prev pointer when it consumes bmem. 665 */ 666 list2tq(&binding, bmem); 667 668 /* 669 * Set type and binding (who it applies to) for new entries. 670 */ 671 for (d = defs; d != NULL; d = d->next) { 672 d->type = type; 673 d->binding = binding; 674 } 675 tq_append(&defaults, defs); 676} 677 678/* 679 * Allocate a new struct userspec, populate it, and insert it at the 680 * and of the userspecs list. 681 */ 682static void 683add_userspec(members, privs) 684 struct member *members; 685 struct privilege *privs; 686{ 687 struct userspec *u; 688 689 u = emalloc(sizeof(*u)); 690 list2tq(&u->users, members); 691 list2tq(&u->privileges, privs); 692 u->prev = u; 693 u->next = NULL; 694 tq_append(&userspecs, u); 695} 696 697/* 698 * Free up space used by data structures from a previous parser run and sets 699 * the current sudoers file to path. 700 */ 701void 702init_parser(path, quiet) 703 char *path; 704 int quiet; 705{ 706 struct defaults *d; 707 struct member *m, *binding; 708 struct userspec *us; 709 struct privilege *priv; 710 struct cmndspec *cs; 711 struct sudo_command *c; 712 713 while ((us = tq_pop(&userspecs)) != NULL) { 714 while ((m = tq_pop(&us->users)) != NULL) { 715 efree(m->name); 716 efree(m); 717 } 718 while ((priv = tq_pop(&us->privileges)) != NULL) { 719 struct member *runasuser = NULL, *runasgroup = NULL; 720#ifdef HAVE_SELINUX 721 char *role = NULL, *type = NULL; 722#endif /* HAVE_SELINUX */ 723 724 while ((m = tq_pop(&priv->hostlist)) != NULL) { 725 efree(m->name); 726 efree(m); 727 } 728 while ((cs = tq_pop(&priv->cmndlist)) != NULL) { 729#ifdef HAVE_SELINUX 730 /* Only free the first instance of a role/type. */ 731 if (cs->role != role) { 732 role = cs->role; 733 efree(cs->role); 734 } 735 if (cs->type != type) { 736 type = cs->type; 737 efree(cs->type); 738 } 739#endif /* HAVE_SELINUX */ 740 if (tq_last(&cs->runasuserlist) != runasuser) { 741 runasuser = tq_last(&cs->runasuserlist); 742 while ((m = tq_pop(&cs->runasuserlist)) != NULL) { 743 efree(m->name); 744 efree(m); 745 } 746 } 747 if (tq_last(&cs->runasgrouplist) != runasgroup) { 748 runasgroup = tq_last(&cs->runasgrouplist); 749 while ((m = tq_pop(&cs->runasgrouplist)) != NULL) { 750 efree(m->name); 751 efree(m); 752 } 753 } 754 if (cs->cmnd->type == COMMAND) { 755 c = (struct sudo_command *) cs->cmnd->name; 756 efree(c->cmnd); 757 efree(c->args); 758 } 759 efree(cs->cmnd->name); 760 efree(cs->cmnd); 761 efree(cs); 762 } 763 efree(priv); 764 } 765 efree(us); 766 } 767 tq_init(&userspecs); 768 769 binding = NULL; 770 while ((d = tq_pop(&defaults)) != NULL) { 771 if (tq_last(&d->binding) != binding) { 772 binding = tq_last(&d->binding); 773 while ((m = tq_pop(&d->binding)) != NULL) { 774 if (m->type == COMMAND) { 775 c = (struct sudo_command *) m->name; 776 efree(c->cmnd); 777 efree(c->args); 778 } 779 efree(m->name); 780 efree(m); 781 } 782 } 783 efree(d->var); 784 efree(d->val); 785 efree(d); 786 } 787 tq_init(&defaults); 788 789 init_aliases(); 790 791 init_lexer(); 792 793 efree(sudoers); 794 sudoers = path ? estrdup(path) : NULL; 795 796 parse_error = FALSE; 797 errorlineno = -1; 798 errorfile = sudoers; 799 sudoers_warnings = !quiet; 800} 801