1/* 2 * Copyright (c) 2004-2005, 2007-2010 Todd C. Miller <Todd.Miller@courtesan.com> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 16 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17 */ 18 19#include <config.h> 20 21#include <sys/types.h> 22#include <sys/param.h> 23#include <stdio.h> 24#ifdef STDC_HEADERS 25# include <stdlib.h> 26# include <stddef.h> 27#else 28# ifdef HAVE_STDLIB_H 29# include <stdlib.h> 30# endif 31#endif /* STDC_HEADERS */ 32#ifdef HAVE_STRING_H 33# include <string.h> 34#endif /* HAVE_STRING_H */ 35#ifdef HAVE_STRINGS_H 36# include <strings.h> 37#endif /* HAVE_STRINGS_H */ 38#ifdef HAVE_UNISTD_H 39# include <unistd.h> 40#endif /* HAVE_UNISTD_H */ 41#include <ctype.h> 42#include <pwd.h> 43#include <grp.h> 44 45#include "sudo.h" 46#include "parse.h" 47#include "lbuf.h" 48#include <gram.h> 49 50/* Characters that must be quoted in sudoers */ 51#define SUDOERS_QUOTED ":\\,=#\"" 52 53/* sudoers nsswitch routines */ 54struct sudo_nss sudo_nss_file = { 55 &sudo_nss_file, 56 NULL, 57 sudo_file_open, 58 sudo_file_close, 59 sudo_file_parse, 60 sudo_file_setdefs, 61 sudo_file_lookup, 62 sudo_file_display_cmnd, 63 sudo_file_display_defaults, 64 sudo_file_display_bound_defaults, 65 sudo_file_display_privs 66}; 67 68/* 69 * Parser externs. 70 */ 71extern FILE *yyin; 72extern char *errorfile; 73extern int errorlineno, parse_error; 74 75/* 76 * Local prototypes. 77 */ 78static void print_member __P((struct lbuf *, char *, int, int, int)); 79static int display_bound_defaults __P((int, struct lbuf *)); 80 81int 82sudo_file_open(nss) 83 struct sudo_nss *nss; 84{ 85 if (def_ignore_local_sudoers) 86 return -1; 87 nss->handle = open_sudoers(_PATH_SUDOERS, FALSE, NULL); 88 return nss->handle ? 0 : -1; 89} 90 91int 92sudo_file_close(nss) 93 struct sudo_nss *nss; 94{ 95 /* Free parser data structures and close sudoers file. */ 96 init_parser(NULL, 0); 97 if (nss->handle != NULL) { 98 fclose(nss->handle); 99 nss->handle = NULL; 100 yyin = NULL; 101 } 102 return 0; 103} 104 105/* 106 * Parse the specified sudoers file. 107 */ 108int 109sudo_file_parse(nss) 110 struct sudo_nss *nss; 111{ 112 if (nss->handle == NULL) 113 return -1; 114 115 init_parser(_PATH_SUDOERS, 0); 116 yyin = nss->handle; 117 if (yyparse() != 0 || parse_error) { 118 if (errorlineno != -1) { 119 log_error(0, "parse error in %s near line %d", 120 errorfile, errorlineno); 121 } else { 122 log_error(0, "parse error in %s", errorfile); 123 } 124 return -1; 125 } 126 return 0; 127} 128 129/* 130 * Wrapper around update_defaults() for nsswitch code. 131 */ 132int 133sudo_file_setdefs(nss) 134 struct sudo_nss *nss; 135{ 136 if (nss->handle == NULL) 137 return -1; 138 139 if (!update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER)) 140 return -1; 141 return 0; 142} 143 144/* 145 * Look up the user in the parsed sudoers file and check to see if they are 146 * allowed to run the specified command on this host as the target user. 147 */ 148int 149sudo_file_lookup(nss, validated, pwflag) 150 struct sudo_nss *nss; 151 int validated; 152 int pwflag; 153{ 154 int match, host_match, runas_match, cmnd_match; 155 struct cmndspec *cs; 156 struct cmndtag *tags = NULL; 157 struct privilege *priv; 158 struct userspec *us; 159 160 if (nss->handle == NULL) 161 return validated; 162 163 /* 164 * Only check the actual command if pwflag is not set. 165 * It is set for the "validate", "list" and "kill" pseudo-commands. 166 * Always check the host and user. 167 */ 168 if (pwflag) { 169 int nopass; 170 enum def_tupple pwcheck; 171 172 pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple; 173 nopass = (pwcheck == all) ? TRUE : FALSE; 174 175 if (list_pw == NULL) 176 SET(validated, FLAG_NO_CHECK); 177 CLR(validated, FLAG_NO_USER); 178 CLR(validated, FLAG_NO_HOST); 179 match = DENY; 180 tq_foreach_fwd(&userspecs, us) { 181 if (userlist_matches(sudo_user.pw, &us->users) != ALLOW) 182 continue; 183 tq_foreach_fwd(&us->privileges, priv) { 184 if (hostlist_matches(&priv->hostlist) != ALLOW) 185 continue; 186 tq_foreach_fwd(&priv->cmndlist, cs) { 187 /* Only check the command when listing another user. */ 188 if (user_uid == 0 || list_pw == NULL || 189 user_uid == list_pw->pw_uid || 190 cmnd_matches(cs->cmnd) == ALLOW) 191 match = ALLOW; 192 if ((pwcheck == any && cs->tags.nopasswd == TRUE) || 193 (pwcheck == all && cs->tags.nopasswd != TRUE)) 194 nopass = cs->tags.nopasswd; 195 } 196 } 197 } 198 if (match == ALLOW || user_uid == 0) { 199 /* User has an entry for this host. */ 200 SET(validated, VALIDATE_OK); 201 } else if (match == DENY) 202 SET(validated, VALIDATE_NOT_OK); 203 if (pwcheck == always && def_authenticate) 204 SET(validated, FLAG_CHECK_USER); 205 else if (pwcheck == never || nopass == TRUE) 206 def_authenticate = FALSE; 207 return validated; 208 } 209 210 /* Need to be runas user while stat'ing things. */ 211 set_perms(PERM_RUNAS); 212 213 match = UNSPEC; 214 tq_foreach_rev(&userspecs, us) { 215 if (userlist_matches(sudo_user.pw, &us->users) != ALLOW) 216 continue; 217 CLR(validated, FLAG_NO_USER); 218 tq_foreach_rev(&us->privileges, priv) { 219 host_match = hostlist_matches(&priv->hostlist); 220 if (host_match == ALLOW) 221 CLR(validated, FLAG_NO_HOST); 222 else 223 continue; 224 tq_foreach_rev(&priv->cmndlist, cs) { 225 runas_match = runaslist_matches(&cs->runasuserlist, 226 &cs->runasgrouplist); 227 if (runas_match == ALLOW) { 228 cmnd_match = cmnd_matches(cs->cmnd); 229 if (cmnd_match != UNSPEC) { 230 match = cmnd_match; 231 tags = &cs->tags; 232#ifdef HAVE_SELINUX 233 /* Set role and type if not specified on command line. */ 234 if (user_role == NULL) 235 user_role = cs->role ? estrdup(cs->role) : def_role; 236 if (user_type == NULL) 237 user_type = cs->type ? estrdup(cs->type) : def_type; 238#endif /* HAVE_SELINUX */ 239 goto matched2; 240 } 241 } 242 } 243 } 244 } 245 matched2: 246 if (match == ALLOW) { 247 SET(validated, VALIDATE_OK); 248 CLR(validated, VALIDATE_NOT_OK); 249 if (tags != NULL) { 250 if (tags->nopasswd != UNSPEC) 251 def_authenticate = !tags->nopasswd; 252 if (tags->noexec != UNSPEC) 253 def_noexec = tags->noexec; 254 if (tags->setenv != UNSPEC) 255 def_setenv = tags->setenv; 256 if (tags->log_input != UNSPEC) 257 def_log_input = tags->log_input; 258 if (tags->log_output != UNSPEC) 259 def_log_output = tags->log_output; 260 } 261 } else if (match == DENY) { 262 SET(validated, VALIDATE_NOT_OK); 263 CLR(validated, VALIDATE_OK); 264 if (tags != NULL && tags->nopasswd != UNSPEC) 265 def_authenticate = !tags->nopasswd; 266 } 267 set_perms(PERM_ROOT); 268 return validated; 269} 270 271#define TAG_CHANGED(t) \ 272 (cs->tags.t != UNSPEC && cs->tags.t != IMPLIED && cs->tags.t != tags->t) 273 274static void 275sudo_file_append_cmnd(cs, tags, lbuf) 276 struct cmndspec *cs; 277 struct cmndtag *tags; 278 struct lbuf *lbuf; 279{ 280 struct member *m; 281 282#ifdef HAVE_SELINUX 283 if (cs->role) 284 lbuf_append(lbuf, "ROLE=%s ", cs->role); 285 if (cs->type) 286 lbuf_append(lbuf, "TYPE=%s ", cs->type); 287#endif /* HAVE_SELINUX */ 288 if (TAG_CHANGED(setenv)) { 289 lbuf_append(lbuf, cs->tags.setenv ? "SETENV: " : "NOSETENV: "); 290 tags->setenv = cs->tags.setenv; 291 } 292 if (TAG_CHANGED(noexec)) { 293 lbuf_append(lbuf, cs->tags.noexec ? "NOEXEC: " : "EXEC: "); 294 tags->noexec = cs->tags.noexec; 295 } 296 if (TAG_CHANGED(nopasswd)) { 297 lbuf_append(lbuf, cs->tags.nopasswd ? "NOPASSWD: " : "PASSWD: "); 298 tags->nopasswd = cs->tags.nopasswd; 299 } 300 if (TAG_CHANGED(log_input)) { 301 lbuf_append(lbuf, cs->tags.log_input ? "LOG_INPUT: " : "NOLOG_INPUT: "); 302 tags->log_input = cs->tags.log_input; 303 } 304 if (TAG_CHANGED(log_output)) { 305 lbuf_append(lbuf, cs->tags.log_output ? "LOG_OUTPUT: " : "NOLOG_OUTPUT: "); 306 tags->log_output = cs->tags.log_output; 307 } 308 m = cs->cmnd; 309 print_member(lbuf, m->name, m->type, m->negated, 310 CMNDALIAS); 311} 312 313static int 314sudo_file_display_priv_short(pw, us, lbuf) 315 struct passwd *pw; 316 struct userspec *us; 317 struct lbuf *lbuf; 318{ 319 struct cmndspec *cs; 320 struct member *m; 321 struct privilege *priv; 322 struct cmndtag tags; 323 int nfound = 0; 324 325 tq_foreach_fwd(&us->privileges, priv) { 326 if (hostlist_matches(&priv->hostlist) != ALLOW) 327 continue; 328 tags.noexec = UNSPEC; 329 tags.setenv = UNSPEC; 330 tags.nopasswd = UNSPEC; 331 tags.log_input = UNSPEC; 332 tags.log_output = UNSPEC; 333 lbuf_append(lbuf, " "); 334 tq_foreach_fwd(&priv->cmndlist, cs) { 335 if (cs != tq_first(&priv->cmndlist)) 336 lbuf_append(lbuf, ", "); 337 lbuf_append(lbuf, "("); 338 if (!tq_empty(&cs->runasuserlist)) { 339 tq_foreach_fwd(&cs->runasuserlist, m) { 340 if (m != tq_first(&cs->runasuserlist)) 341 lbuf_append(lbuf, ", "); 342 print_member(lbuf, m->name, m->type, m->negated, 343 RUNASALIAS); 344 } 345 } else if (tq_empty(&cs->runasgrouplist)) { 346 lbuf_append(lbuf, "%s", def_runas_default); 347 } else { 348 lbuf_append(lbuf, "%s", pw->pw_name); 349 } 350 if (!tq_empty(&cs->runasgrouplist)) { 351 lbuf_append(lbuf, " : "); 352 tq_foreach_fwd(&cs->runasgrouplist, m) { 353 if (m != tq_first(&cs->runasgrouplist)) 354 lbuf_append(lbuf, ", "); 355 print_member(lbuf, m->name, m->type, m->negated, 356 RUNASALIAS); 357 } 358 } 359 lbuf_append(lbuf, ") "); 360 sudo_file_append_cmnd(cs, &tags, lbuf); 361 nfound++; 362 } 363 lbuf_append(lbuf, "\n"); 364 } 365 return nfound; 366} 367 368static int 369sudo_file_display_priv_long(pw, us, lbuf) 370 struct passwd *pw; 371 struct userspec *us; 372 struct lbuf *lbuf; 373{ 374 struct cmndspec *cs; 375 struct member *m; 376 struct privilege *priv; 377 struct cmndtag tags; 378 int nfound = 0; 379 380 tq_foreach_fwd(&us->privileges, priv) { 381 if (hostlist_matches(&priv->hostlist) != ALLOW) 382 continue; 383 tags.noexec = UNSPEC; 384 tags.setenv = UNSPEC; 385 tags.nopasswd = UNSPEC; 386 tags.log_input = UNSPEC; 387 tags.log_output = UNSPEC; 388 lbuf_append(lbuf, "\nSudoers entry:\n"); 389 tq_foreach_fwd(&priv->cmndlist, cs) { 390 lbuf_append(lbuf, " RunAsUsers: "); 391 if (!tq_empty(&cs->runasuserlist)) { 392 tq_foreach_fwd(&cs->runasuserlist, m) { 393 if (m != tq_first(&cs->runasuserlist)) 394 lbuf_append(lbuf, ", "); 395 print_member(lbuf, m->name, m->type, m->negated, 396 RUNASALIAS); 397 } 398 } else if (tq_empty(&cs->runasgrouplist)) { 399 lbuf_append(lbuf, "%s", def_runas_default); 400 } else { 401 lbuf_append(lbuf, "%s", pw->pw_name); 402 } 403 lbuf_append(lbuf, "\n"); 404 if (!tq_empty(&cs->runasgrouplist)) { 405 lbuf_append(lbuf, " RunAsGroups: "); 406 tq_foreach_fwd(&cs->runasgrouplist, m) { 407 if (m != tq_first(&cs->runasgrouplist)) 408 lbuf_append(lbuf, ", "); 409 print_member(lbuf, m->name, m->type, m->negated, 410 RUNASALIAS); 411 } 412 lbuf_append(lbuf, "\n"); 413 } 414 lbuf_append(lbuf, " Commands:\n\t"); 415 sudo_file_append_cmnd(cs, &tags, lbuf); 416 lbuf_append(lbuf, "\n"); 417 nfound++; 418 } 419 } 420 return nfound; 421} 422 423int 424sudo_file_display_privs(nss, pw, lbuf) 425 struct sudo_nss *nss; 426 struct passwd *pw; 427 struct lbuf *lbuf; 428{ 429 struct userspec *us; 430 int nfound = 0; 431 432 if (nss->handle == NULL) 433 goto done; 434 435 tq_foreach_fwd(&userspecs, us) { 436 if (userlist_matches(pw, &us->users) != ALLOW) 437 continue; 438 439 if (long_list) 440 nfound += sudo_file_display_priv_long(pw, us, lbuf); 441 else 442 nfound += sudo_file_display_priv_short(pw, us, lbuf); 443 } 444done: 445 return nfound; 446} 447 448/* 449 * Display matching Defaults entries for the given user on this host. 450 */ 451int 452sudo_file_display_defaults(nss, pw, lbuf) 453 struct sudo_nss *nss; 454 struct passwd *pw; 455 struct lbuf *lbuf; 456{ 457 struct defaults *d; 458 char *prefix; 459 int nfound = 0; 460 461 if (nss->handle == NULL) 462 goto done; 463 464 if (lbuf->len == 0 || isspace((unsigned char)lbuf->buf[lbuf->len - 1])) 465 prefix = " "; 466 else 467 prefix = ", "; 468 469 tq_foreach_fwd(&defaults, d) { 470 switch (d->type) { 471 case DEFAULTS_HOST: 472 if (hostlist_matches(&d->binding) != ALLOW) 473 continue; 474 break; 475 case DEFAULTS_USER: 476 if (userlist_matches(pw, &d->binding) != ALLOW) 477 continue; 478 break; 479 case DEFAULTS_RUNAS: 480 case DEFAULTS_CMND: 481 continue; 482 } 483 if (d->val != NULL) { 484 lbuf_append(lbuf, "%s%s%s", prefix, d->var, 485 d->op == '+' ? "+=" : d->op == '-' ? "-=" : "="); 486 if (strpbrk(d->val, " \t") != NULL) { 487 lbuf_append(lbuf, "\""); 488 lbuf_append_quoted(lbuf, "\"", "%s", d->val); 489 lbuf_append(lbuf, "\""); 490 } else 491 lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s", d->val); 492 } else 493 lbuf_append(lbuf, "%s%s%s", prefix, 494 d->op == FALSE ? "!" : "", d->var); 495 prefix = ", "; 496 nfound++; 497 } 498done: 499 return nfound; 500} 501 502/* 503 * Display Defaults entries that are per-runas or per-command 504 */ 505int 506sudo_file_display_bound_defaults(nss, pw, lbuf) 507 struct sudo_nss *nss; 508 struct passwd *pw; 509 struct lbuf *lbuf; 510{ 511 int nfound = 0; 512 513 /* XXX - should only print ones that match what the user can do. */ 514 nfound += display_bound_defaults(DEFAULTS_RUNAS, lbuf); 515 nfound += display_bound_defaults(DEFAULTS_CMND, lbuf); 516 517 return nfound; 518} 519 520/* 521 * Display Defaults entries of the given type. 522 */ 523static int 524display_bound_defaults(dtype, lbuf) 525 int dtype; 526 struct lbuf *lbuf; 527{ 528 struct defaults *d; 529 struct member *m, *binding = NULL; 530 char *dsep; 531 int atype, nfound = 0; 532 533 switch (dtype) { 534 case DEFAULTS_HOST: 535 atype = HOSTALIAS; 536 dsep = "@"; 537 break; 538 case DEFAULTS_USER: 539 atype = USERALIAS; 540 dsep = ":"; 541 break; 542 case DEFAULTS_RUNAS: 543 atype = RUNASALIAS; 544 dsep = ">"; 545 break; 546 case DEFAULTS_CMND: 547 atype = CMNDALIAS; 548 dsep = "!"; 549 break; 550 default: 551 return -1; 552 } 553 tq_foreach_fwd(&defaults, d) { 554 if (d->type != dtype) 555 continue; 556 557 nfound++; 558 if (binding != tq_first(&d->binding)) { 559 binding = tq_first(&d->binding); 560 if (nfound != 1) 561 lbuf_append(lbuf, "\n"); 562 lbuf_append(lbuf, " Defaults%s", dsep); 563 for (m = binding; m != NULL; m = m->next) { 564 if (m != binding) 565 lbuf_append(lbuf, ","); 566 print_member(lbuf, m->name, m->type, m->negated, atype); 567 lbuf_append(lbuf, " "); 568 } 569 } else 570 lbuf_append(lbuf, ", "); 571 if (d->val != NULL) { 572 lbuf_append(lbuf, "%s%s%s", d->var, d->op == '+' ? "+=" : 573 d->op == '-' ? "-=" : "=", d->val); 574 } else 575 lbuf_append(lbuf, "%s%s", d->op == FALSE ? "!" : "", d->var); 576 } 577 578 return nfound; 579} 580 581int 582sudo_file_display_cmnd(nss, pw) 583 struct sudo_nss *nss; 584 struct passwd *pw; 585{ 586 struct cmndspec *cs; 587 struct member *match; 588 struct privilege *priv; 589 struct userspec *us; 590 int rval = 1; 591 int host_match, runas_match, cmnd_match; 592 593 if (nss->handle == NULL) 594 goto done; 595 596 match = NULL; 597 tq_foreach_rev(&userspecs, us) { 598 if (userlist_matches(pw, &us->users) != ALLOW) 599 continue; 600 601 tq_foreach_rev(&us->privileges, priv) { 602 host_match = hostlist_matches(&priv->hostlist); 603 if (host_match != ALLOW) 604 continue; 605 tq_foreach_rev(&priv->cmndlist, cs) { 606 runas_match = runaslist_matches(&cs->runasuserlist, 607 &cs->runasgrouplist); 608 if (runas_match == ALLOW) { 609 cmnd_match = cmnd_matches(cs->cmnd); 610 if (cmnd_match != UNSPEC) { 611 match = host_match && runas_match ? cs->cmnd : NULL; 612 goto matched; 613 } 614 } 615 } 616 } 617 } 618 matched: 619 if (match != NULL && !match->negated) { 620 printf("%s%s%s\n", safe_cmnd, user_args ? " " : "", 621 user_args ? user_args : ""); 622 rval = 0; 623 } 624done: 625 return rval; 626} 627 628/* 629 * Print the contents of a struct member to stdout 630 */ 631static void 632_print_member(lbuf, name, type, negated, alias_type) 633 struct lbuf *lbuf; 634 char *name; 635 int type, negated, alias_type; 636{ 637 struct alias *a; 638 struct member *m; 639 struct sudo_command *c; 640 641 switch (type) { 642 case ALL: 643 lbuf_append(lbuf, "%sALL", negated ? "!" : ""); 644 break; 645 case COMMAND: 646 c = (struct sudo_command *) name; 647 if (negated) 648 lbuf_append(lbuf, "!"); 649 lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s", c->cmnd); 650 if (c->args) { 651 lbuf_append(lbuf, " "); 652 lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s", c->args); 653 } 654 break; 655 case ALIAS: 656 if ((a = alias_find(name, alias_type)) != NULL) { 657 tq_foreach_fwd(&a->members, m) { 658 if (m != tq_first(&a->members)) 659 lbuf_append(lbuf, ", "); 660 _print_member(lbuf, m->name, m->type, 661 negated ? !m->negated : m->negated, alias_type); 662 } 663 break; 664 } 665 /* FALLTHROUGH */ 666 default: 667 lbuf_append(lbuf, "%s%s", negated ? "!" : "", name); 668 break; 669 } 670} 671 672static void 673print_member(lbuf, name, type, negated, alias_type) 674 struct lbuf *lbuf; 675 char *name; 676 int type, negated, alias_type; 677{ 678 alias_seqno++; 679 _print_member(lbuf, name, type, negated, alias_type); 680} 681