Return to visudo.c CVS log | Up to [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / plugins / sudoers |
1.1 ! misho 1: /* ! 2: * Copyright (c) 1996, 1998-2005, 2007-2011 ! 3: * Todd C. Miller <Todd.Miller@courtesan.com> ! 4: * ! 5: * Permission to use, copy, modify, and distribute this software for any ! 6: * purpose with or without fee is hereby granted, provided that the above ! 7: * copyright notice and this permission notice appear in all copies. ! 8: * ! 9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ! 10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ! 11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ! 12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ! 13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ! 14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ! 15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ! 16: * ! 17: * Sponsored in part by the Defense Advanced Research Projects ! 18: * Agency (DARPA) and Air Force Research Laboratory, Air Force ! 19: * Materiel Command, USAF, under agreement number F39502-99-1-0512. ! 20: */ ! 21: ! 22: /* ! 23: * Lock the sudoers file for safe editing (ala vipw) and check for parse errors. ! 24: */ ! 25: ! 26: #define _SUDO_MAIN ! 27: ! 28: #ifdef __TANDEM ! 29: # include <floss.h> ! 30: #endif ! 31: ! 32: #include <config.h> ! 33: ! 34: #include <sys/types.h> ! 35: #include <sys/param.h> ! 36: #include <sys/stat.h> ! 37: #include <sys/socket.h> ! 38: #include <sys/time.h> ! 39: #ifndef __TANDEM ! 40: # include <sys/file.h> ! 41: #endif ! 42: #include <sys/wait.h> ! 43: #include <stdio.h> ! 44: #ifdef STDC_HEADERS ! 45: # include <stdlib.h> ! 46: # include <stddef.h> ! 47: #else ! 48: # ifdef HAVE_STDLIB_H ! 49: # include <stdlib.h> ! 50: # endif ! 51: #endif /* STDC_HEADERS */ ! 52: #ifdef HAVE_STRING_H ! 53: # include <string.h> ! 54: #endif /* HAVE_STRING_H */ ! 55: #ifdef HAVE_STRINGS_H ! 56: # include <strings.h> ! 57: #endif /* HAVE_STRINGS_H */ ! 58: #ifdef HAVE_UNISTD_H ! 59: #include <unistd.h> ! 60: #endif /* HAVE_UNISTD_H */ ! 61: #include <stdarg.h> ! 62: #include <ctype.h> ! 63: #include <pwd.h> ! 64: #include <grp.h> ! 65: #include <signal.h> ! 66: #include <errno.h> ! 67: #include <fcntl.h> ! 68: #include <netinet/in.h> ! 69: #include <arpa/inet.h> ! 70: #include <netdb.h> ! 71: #if TIME_WITH_SYS_TIME ! 72: # include <time.h> ! 73: #endif ! 74: #ifdef HAVE_SETLOCALE ! 75: # include <locale.h> ! 76: #endif ! 77: ! 78: #include "sudoers.h" ! 79: #include "interfaces.h" ! 80: #include "parse.h" ! 81: #include "redblack.h" ! 82: #include "gettext.h" ! 83: #include "sudoers_version.h" ! 84: #include <gram.h> ! 85: ! 86: struct sudoersfile { ! 87: struct sudoersfile *prev, *next; ! 88: char *path; ! 89: char *tpath; ! 90: int fd; ! 91: int modified; ! 92: int doedit; ! 93: }; ! 94: TQ_DECLARE(sudoersfile) ! 95: ! 96: /* ! 97: * Function prototypes ! 98: */ ! 99: static void quit(int); ! 100: static char *get_args(char *); ! 101: static char *get_editor(char **); ! 102: static void get_hostname(void); ! 103: static char whatnow(void); ! 104: static int check_aliases(int, int); ! 105: static int check_syntax(char *, int, int); ! 106: static int edit_sudoers(struct sudoersfile *, char *, char *, int); ! 107: static int install_sudoers(struct sudoersfile *, int); ! 108: static int print_unused(void *, void *); ! 109: static int reparse_sudoers(char *, char *, int, int); ! 110: static int run_command(char *, char **); ! 111: static int visudo_printf(int msg_type, const char *fmt, ...); ! 112: static void setup_signals(void); ! 113: static void help(void) __attribute__((__noreturn__)); ! 114: static void usage(int); ! 115: ! 116: void cleanup(int); ! 117: ! 118: extern void yyerror(const char *); ! 119: extern void yyrestart(FILE *); ! 120: ! 121: /* ! 122: * External globals exported by the parser ! 123: */ ! 124: extern struct rbtree *aliases; ! 125: extern FILE *yyin; ! 126: extern char *sudoers, *errorfile; ! 127: extern int errorlineno, parse_error; ! 128: /* For getopt(3) */ ! 129: extern char *optarg; ! 130: extern int optind; ! 131: ! 132: /* ! 133: * Globals ! 134: */ ! 135: struct interface *interfaces; ! 136: struct sudo_user sudo_user; ! 137: struct passwd *list_pw; ! 138: sudo_printf_t sudo_printf = visudo_printf; ! 139: static struct sudoersfile_list sudoerslist; ! 140: static struct rbtree *alias_freelist; ! 141: ! 142: int ! 143: main(int argc, char *argv[]) ! 144: { ! 145: struct sudoersfile *sp; ! 146: char *args, *editor, *sudoers_path; ! 147: int ch, checkonly, quiet, strict, oldperms; ! 148: #if defined(SUDO_DEVEL) && defined(__OpenBSD__) ! 149: extern char *malloc_options; ! 150: malloc_options = "AFGJPR"; ! 151: #endif ! 152: ! 153: #if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) ! 154: setprogname(argc > 0 ? argv[0] : "visudo"); ! 155: #endif ! 156: ! 157: #ifdef HAVE_SETLOCALE ! 158: setlocale(LC_ALL, ""); ! 159: #endif ! 160: bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have visudo domain */ ! 161: textdomain("sudoers"); ! 162: ! 163: if (argc < 1) ! 164: usage(1); ! 165: ! 166: /* ! 167: * Arg handling. ! 168: */ ! 169: checkonly = oldperms = quiet = strict = FALSE; ! 170: sudoers_path = _PATH_SUDOERS; ! 171: while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) { ! 172: switch (ch) { ! 173: case 'V': ! 174: (void) printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION); ! 175: (void) printf(_("%s grammar version %d\n"), getprogname(), SUDOERS_GRAMMAR_VERSION); ! 176: exit(0); ! 177: case 'c': ! 178: checkonly++; /* check mode */ ! 179: break; ! 180: case 'f': ! 181: sudoers_path = optarg; /* sudoers file path */ ! 182: oldperms = TRUE; ! 183: break; ! 184: case 'h': ! 185: help(); ! 186: break; ! 187: case 's': ! 188: strict++; /* strict mode */ ! 189: break; ! 190: case 'q': ! 191: quiet++; /* quiet mode */ ! 192: break; ! 193: default: ! 194: usage(1); ! 195: } ! 196: } ! 197: argc -= optind; ! 198: argv += optind; ! 199: if (argc) ! 200: usage(1); ! 201: ! 202: sudo_setpwent(); ! 203: sudo_setgrent(); ! 204: ! 205: /* Mock up a fake sudo_user struct. */ ! 206: user_cmnd = ""; ! 207: if ((sudo_user.pw = sudo_getpwuid(getuid())) == NULL) ! 208: errorx(1, _("you do not exist in the %s database"), "passwd"); ! 209: get_hostname(); ! 210: ! 211: /* Setup defaults data structures. */ ! 212: init_defaults(); ! 213: ! 214: if (checkonly) ! 215: exit(check_syntax(sudoers_path, quiet, strict)); ! 216: ! 217: /* ! 218: * Parse the existing sudoers file(s) in quiet mode to highlight any ! 219: * existing errors and to pull in editor and env_editor conf values. ! 220: */ ! 221: if ((yyin = open_sudoers(sudoers_path, TRUE, NULL)) == NULL) { ! 222: error(1, "%s", sudoers_path); ! 223: } ! 224: init_parser(sudoers_path, 0); ! 225: yyparse(); ! 226: (void) update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER); ! 227: ! 228: editor = get_editor(&args); ! 229: ! 230: /* Install signal handlers to clean up temp files if we are killed. */ ! 231: setup_signals(); ! 232: ! 233: /* Edit the sudoers file(s) */ ! 234: tq_foreach_fwd(&sudoerslist, sp) { ! 235: if (!sp->doedit) ! 236: continue; ! 237: if (sp != tq_first(&sudoerslist)) { ! 238: printf(_("press return to edit %s: "), sp->path); ! 239: while ((ch = getchar()) != EOF && ch != '\n') ! 240: continue; ! 241: } ! 242: edit_sudoers(sp, editor, args, -1); ! 243: } ! 244: ! 245: /* Check edited files for a parse error and re-edit any that fail. */ ! 246: reparse_sudoers(editor, args, strict, quiet); ! 247: ! 248: /* Install the sudoers temp files. */ ! 249: tq_foreach_fwd(&sudoerslist, sp) { ! 250: if (!sp->modified) ! 251: (void) unlink(sp->tpath); ! 252: else ! 253: (void) install_sudoers(sp, oldperms); ! 254: } ! 255: ! 256: exit(0); ! 257: } ! 258: ! 259: /* ! 260: * List of editors that support the "+lineno" command line syntax. ! 261: * If an entry starts with '*' the tail end of the string is matched. ! 262: * No other wild cards are supported. ! 263: */ ! 264: static char *lineno_editors[] = { ! 265: "ex", ! 266: "nex", ! 267: "vi", ! 268: "nvi", ! 269: "vim", ! 270: "elvis", ! 271: "*macs", ! 272: "mg", ! 273: "vile", ! 274: "jove", ! 275: "pico", ! 276: "nano", ! 277: "ee", ! 278: "joe", ! 279: "zile", ! 280: NULL ! 281: }; ! 282: ! 283: /* ! 284: * Edit each sudoers file. ! 285: * Returns TRUE on success, else FALSE. ! 286: */ ! 287: static int ! 288: edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno) ! 289: { ! 290: int tfd; /* sudoers temp file descriptor */ ! 291: int modified; /* was the file modified? */ ! 292: int ac; /* argument count */ ! 293: char **av; /* argument vector for run_command */ ! 294: char *cp; /* scratch char pointer */ ! 295: char buf[PATH_MAX*2]; /* buffer used for copying files */ ! 296: char linestr[64]; /* string version of lineno */ ! 297: struct timeval tv, tv1, tv2; /* time before and after edit */ ! 298: struct timeval orig_mtim; /* starting mtime of sudoers file */ ! 299: off_t orig_size; /* starting size of sudoers file */ ! 300: ssize_t nread; /* number of bytes read */ ! 301: struct stat sb; /* stat buffer */ ! 302: ! 303: if (fstat(sp->fd, &sb) == -1) ! 304: error(1, _("unable to stat %s"), sp->path); ! 305: orig_size = sb.st_size; ! 306: mtim_get(&sb, &orig_mtim); ! 307: ! 308: /* Create the temp file if needed and set timestamp. */ ! 309: if (sp->tpath == NULL) { ! 310: easprintf(&sp->tpath, "%s.tmp", sp->path); ! 311: tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600); ! 312: if (tfd < 0) ! 313: error(1, "%s", sp->tpath); ! 314: ! 315: /* Copy sp->path -> sp->tpath and reset the mtime. */ ! 316: if (orig_size != 0) { ! 317: (void) lseek(sp->fd, (off_t)0, SEEK_SET); ! 318: while ((nread = read(sp->fd, buf, sizeof(buf))) > 0) ! 319: if (write(tfd, buf, nread) != nread) ! 320: error(1, _("write error")); ! 321: ! 322: /* Add missing newline at EOF if needed. */ ! 323: if (nread > 0 && buf[nread - 1] != '\n') { ! 324: buf[0] = '\n'; ! 325: if (write(tfd, buf, 1) != 1) ! 326: error(1, _("write error")); ! 327: } ! 328: } ! 329: (void) close(tfd); ! 330: } ! 331: (void) touch(-1, sp->tpath, &orig_mtim); ! 332: ! 333: /* Does the editor support +lineno? */ ! 334: if (lineno > 0) ! 335: { ! 336: char *editor_base = strrchr(editor, '/'); ! 337: if (editor_base != NULL) ! 338: editor_base++; ! 339: else ! 340: editor_base = editor; ! 341: if (*editor_base == 'r') ! 342: editor_base++; ! 343: ! 344: for (av = lineno_editors; (cp = *av) != NULL; av++) { ! 345: /* We only handle a leading '*' wildcard. */ ! 346: if (*cp == '*') { ! 347: size_t blen = strlen(editor_base); ! 348: size_t clen = strlen(++cp); ! 349: if (blen >= clen) { ! 350: if (strcmp(cp, editor_base + blen - clen) == 0) ! 351: break; ! 352: } ! 353: } else if (strcmp(cp, editor_base) == 0) ! 354: break; ! 355: } ! 356: /* Disable +lineno if editor doesn't support it. */ ! 357: if (cp == NULL) ! 358: lineno = -1; ! 359: } ! 360: ! 361: /* Find the length of the argument vector */ ! 362: ac = 3 + (lineno > 0); ! 363: if (args) { ! 364: int wasblank; ! 365: ! 366: ac++; ! 367: for (wasblank = FALSE, cp = args; *cp; cp++) { ! 368: if (isblank((unsigned char) *cp)) ! 369: wasblank = TRUE; ! 370: else if (wasblank) { ! 371: wasblank = FALSE; ! 372: ac++; ! 373: } ! 374: } ! 375: } ! 376: ! 377: /* Build up argument vector for the command */ ! 378: av = emalloc2(ac, sizeof(char *)); ! 379: if ((av[0] = strrchr(editor, '/')) != NULL) ! 380: av[0]++; ! 381: else ! 382: av[0] = editor; ! 383: ac = 1; ! 384: if (lineno > 0) { ! 385: (void) snprintf(linestr, sizeof(linestr), "+%d", lineno); ! 386: av[ac++] = linestr; ! 387: } ! 388: if (args) { ! 389: for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t"))) ! 390: av[ac++] = cp; ! 391: } ! 392: av[ac++] = sp->tpath; ! 393: av[ac++] = NULL; ! 394: ! 395: /* ! 396: * Do the edit: ! 397: * We cannot check the editor's exit value against 0 since ! 398: * XPG4 specifies that vi's exit value is a function of the ! 399: * number of errors during editing (?!?!). ! 400: */ ! 401: gettimeofday(&tv1, NULL); ! 402: if (run_command(editor, av) != -1) { ! 403: gettimeofday(&tv2, NULL); ! 404: /* ! 405: * Sanity checks. ! 406: */ ! 407: if (stat(sp->tpath, &sb) < 0) { ! 408: warningx(_("unable to stat temporary file (%s), %s unchanged"), ! 409: sp->tpath, sp->path); ! 410: return FALSE; ! 411: } ! 412: if (sb.st_size == 0 && orig_size != 0) { ! 413: warningx(_("zero length temporary file (%s), %s unchanged"), ! 414: sp->tpath, sp->path); ! 415: sp->modified = TRUE; ! 416: return FALSE; ! 417: } ! 418: } else { ! 419: warningx(_("editor (%s) failed, %s unchanged"), editor, sp->path); ! 420: return FALSE; ! 421: } ! 422: ! 423: /* Set modified bit if use changed the file. */ ! 424: modified = TRUE; ! 425: mtim_get(&sb, &tv); ! 426: if (orig_size == sb.st_size && timevalcmp(&orig_mtim, &tv, ==)) { ! 427: /* ! 428: * If mtime and size match but the user spent no measurable ! 429: * time in the editor we can't tell if the file was changed. ! 430: */ ! 431: timevalsub(&tv1, &tv2); ! 432: if (timevalisset(&tv2)) ! 433: modified = FALSE; ! 434: } ! 435: ! 436: /* ! 437: * If modified in this edit session, mark as modified. ! 438: */ ! 439: if (modified) ! 440: sp->modified = modified; ! 441: else ! 442: warningx(_("%s unchanged"), sp->tpath); ! 443: ! 444: return TRUE; ! 445: } ! 446: ! 447: /* ! 448: * Parse sudoers after editing and re-edit any ones that caused a parse error. ! 449: * Returns TRUE on success, else FALSE. ! 450: */ ! 451: static int ! 452: reparse_sudoers(char *editor, char *args, int strict, int quiet) ! 453: { ! 454: struct sudoersfile *sp, *last; ! 455: FILE *fp; ! 456: int ch; ! 457: ! 458: /* ! 459: * Parse the edited sudoers files and do sanity checking ! 460: */ ! 461: do { ! 462: sp = tq_first(&sudoerslist); ! 463: last = tq_last(&sudoerslist); ! 464: fp = fopen(sp->tpath, "r+"); ! 465: if (fp == NULL) ! 466: errorx(1, _("unable to re-open temporary file (%s), %s unchanged."), ! 467: sp->tpath, sp->path); ! 468: ! 469: /* Clean slate for each parse */ ! 470: init_defaults(); ! 471: init_parser(sp->path, quiet); ! 472: ! 473: /* Parse the sudoers temp file */ ! 474: yyrestart(fp); ! 475: if (yyparse() && !parse_error) { ! 476: warningx(_("unabled to parse temporary file (%s), unknown error"), ! 477: sp->tpath); ! 478: parse_error = TRUE; ! 479: errorfile = sp->path; ! 480: } ! 481: fclose(yyin); ! 482: if (!parse_error) { ! 483: if (!update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER) || ! 484: check_aliases(strict, quiet) != 0) { ! 485: parse_error = TRUE; ! 486: errorfile = sp->path; ! 487: } ! 488: } ! 489: ! 490: /* ! 491: * Got an error, prompt the user for what to do now ! 492: */ ! 493: if (parse_error) { ! 494: switch (whatnow()) { ! 495: case 'Q' : parse_error = FALSE; /* ignore parse error */ ! 496: break; ! 497: case 'x' : cleanup(0); ! 498: exit(0); ! 499: break; ! 500: } ! 501: } ! 502: if (parse_error) { ! 503: /* Edit file with the parse error */ ! 504: tq_foreach_fwd(&sudoerslist, sp) { ! 505: if (errorfile == NULL || strcmp(sp->path, errorfile) == 0) { ! 506: edit_sudoers(sp, editor, args, errorlineno); ! 507: break; ! 508: } ! 509: } ! 510: if (sp == NULL) { ! 511: errorx(1, _("internal error, unable to find %s in list!"), ! 512: sudoers); ! 513: } ! 514: } ! 515: ! 516: /* If any new #include directives were added, edit them too. */ ! 517: for (sp = last->next; sp != NULL; sp = sp->next) { ! 518: printf(_("press return to edit %s: "), sp->path); ! 519: while ((ch = getchar()) != EOF && ch != '\n') ! 520: continue; ! 521: edit_sudoers(sp, editor, args, errorlineno); ! 522: } ! 523: } while (parse_error); ! 524: ! 525: return TRUE; ! 526: } ! 527: ! 528: /* ! 529: * Set the owner and mode on a sudoers temp file and ! 530: * move it into place. Returns TRUE on success, else FALSE. ! 531: */ ! 532: static int ! 533: install_sudoers(struct sudoersfile *sp, int oldperms) ! 534: { ! 535: struct stat sb; ! 536: ! 537: /* ! 538: * Change mode and ownership of temp file so when ! 539: * we move it to sp->path things are kosher. ! 540: */ ! 541: if (oldperms) { ! 542: /* Use perms of the existing file. */ ! 543: if (fstat(sp->fd, &sb) == -1) ! 544: error(1, _("unable to stat %s"), sp->path); ! 545: if (chown(sp->tpath, sb.st_uid, sb.st_gid) != 0) { ! 546: warning(_("unable to set (uid, gid) of %s to (%u, %u)"), ! 547: sp->tpath, (unsigned int)sb.st_uid, (unsigned int)sb.st_gid); ! 548: } ! 549: if (chmod(sp->tpath, sb.st_mode & 0777) != 0) { ! 550: warning(_("unable to change mode of %s to 0%o"), sp->tpath, ! 551: (unsigned int)(sb.st_mode & 0777)); ! 552: } ! 553: } else { ! 554: if (chown(sp->tpath, SUDOERS_UID, SUDOERS_GID) != 0) { ! 555: warning(_("unable to set (uid, gid) of %s to (%u, %u)"), ! 556: sp->tpath, SUDOERS_UID, SUDOERS_GID); ! 557: return FALSE; ! 558: } ! 559: if (chmod(sp->tpath, SUDOERS_MODE) != 0) { ! 560: warning(_("unable to change mode of %s to 0%o"), sp->tpath, ! 561: SUDOERS_MODE); ! 562: return FALSE; ! 563: } ! 564: } ! 565: ! 566: /* ! 567: * Now that sp->tpath is sane (parses ok) it needs to be ! 568: * rename(2)'d to sp->path. If the rename(2) fails we try using ! 569: * mv(1) in case sp->tpath and sp->path are on different file systems. ! 570: */ ! 571: if (rename(sp->tpath, sp->path) == 0) { ! 572: efree(sp->tpath); ! 573: sp->tpath = NULL; ! 574: } else { ! 575: if (errno == EXDEV) { ! 576: char *av[4]; ! 577: warningx(_("%s and %s not on the same file system, using mv to rename"), ! 578: sp->tpath, sp->path); ! 579: ! 580: /* Build up argument vector for the command */ ! 581: if ((av[0] = strrchr(_PATH_MV, '/')) != NULL) ! 582: av[0]++; ! 583: else ! 584: av[0] = _PATH_MV; ! 585: av[1] = sp->tpath; ! 586: av[2] = sp->path; ! 587: av[3] = NULL; ! 588: ! 589: /* And run it... */ ! 590: if (run_command(_PATH_MV, av)) { ! 591: warningx(_("command failed: '%s %s %s', %s unchanged"), ! 592: _PATH_MV, sp->tpath, sp->path, sp->path); ! 593: (void) unlink(sp->tpath); ! 594: efree(sp->tpath); ! 595: sp->tpath = NULL; ! 596: return FALSE; ! 597: } ! 598: efree(sp->tpath); ! 599: sp->tpath = NULL; ! 600: } else { ! 601: warning(_("error renaming %s, %s unchanged"), sp->tpath, sp->path); ! 602: (void) unlink(sp->tpath); ! 603: return FALSE; ! 604: } ! 605: } ! 606: return TRUE; ! 607: } ! 608: ! 609: /* STUB */ ! 610: void ! 611: set_fqdn(void) ! 612: { ! 613: return; ! 614: } ! 615: ! 616: /* STUB */ ! 617: void ! 618: init_envtables(void) ! 619: { ! 620: return; ! 621: } ! 622: ! 623: /* STUB */ ! 624: int ! 625: user_is_exempt(void) ! 626: { ! 627: return FALSE; ! 628: } ! 629: ! 630: /* STUB */ ! 631: void ! 632: sudo_setspent(void) ! 633: { ! 634: return; ! 635: } ! 636: ! 637: /* STUB */ ! 638: void ! 639: sudo_endspent(void) ! 640: { ! 641: return; ! 642: } ! 643: ! 644: /* STUB */ ! 645: int ! 646: group_plugin_query(const char *user, const char *group, const struct passwd *pw) ! 647: { ! 648: return FALSE; ! 649: } ! 650: ! 651: /* ! 652: * Assuming a parse error occurred, prompt the user for what they want ! 653: * to do now. Returns the first letter of their choice. ! 654: */ ! 655: static char ! 656: whatnow(void) ! 657: { ! 658: int choice, c; ! 659: ! 660: for (;;) { ! 661: (void) fputs(_("What now? "), stdout); ! 662: choice = getchar(); ! 663: for (c = choice; c != '\n' && c != EOF;) ! 664: c = getchar(); ! 665: ! 666: switch (choice) { ! 667: case EOF: ! 668: choice = 'x'; ! 669: /* FALLTHROUGH */ ! 670: case 'e': ! 671: case 'x': ! 672: case 'Q': ! 673: return choice; ! 674: default: ! 675: (void) puts(_("Options are:\n" ! 676: " (e)dit sudoers file again\n" ! 677: " e(x)it without saving changes to sudoers file\n" ! 678: " (Q)uit and save changes to sudoers file (DANGER!)\n")); ! 679: } ! 680: } ! 681: } ! 682: ! 683: /* ! 684: * Install signal handlers for visudo. ! 685: */ ! 686: static void ! 687: setup_signals(void) ! 688: { ! 689: sigaction_t sa; ! 690: ! 691: /* ! 692: * Setup signal handlers to cleanup nicely. ! 693: */ ! 694: zero_bytes(&sa, sizeof(sa)); ! 695: sigemptyset(&sa.sa_mask); ! 696: sa.sa_flags = SA_RESTART; ! 697: sa.sa_handler = quit; ! 698: (void) sigaction(SIGTERM, &sa, NULL); ! 699: (void) sigaction(SIGHUP, &sa, NULL); ! 700: (void) sigaction(SIGINT, &sa, NULL); ! 701: (void) sigaction(SIGQUIT, &sa, NULL); ! 702: } ! 703: ! 704: static int ! 705: run_command(char *path, char **argv) ! 706: { ! 707: int status; ! 708: pid_t pid, rv; ! 709: ! 710: switch (pid = fork()) { ! 711: case -1: ! 712: error(1, _("unable to execute %s"), path); ! 713: break; /* NOTREACHED */ ! 714: case 0: ! 715: sudo_endpwent(); ! 716: sudo_endgrent(); ! 717: closefrom(STDERR_FILENO + 1); ! 718: execv(path, argv); ! 719: warning(_("unable to run %s"), path); ! 720: _exit(127); ! 721: break; /* NOTREACHED */ ! 722: } ! 723: ! 724: do { ! 725: rv = waitpid(pid, &status, 0); ! 726: } while (rv == -1 && errno == EINTR); ! 727: ! 728: if (rv == -1 || !WIFEXITED(status)) ! 729: return -1; ! 730: return WEXITSTATUS(status); ! 731: } ! 732: ! 733: static int ! 734: check_syntax(char *sudoers_path, int quiet, int strict) ! 735: { ! 736: struct stat sb; ! 737: int error; ! 738: ! 739: if (strcmp(sudoers_path, "-") == 0) { ! 740: yyin = stdin; ! 741: sudoers_path = "stdin"; ! 742: } else if ((yyin = fopen(sudoers_path, "r")) == NULL) { ! 743: if (!quiet) ! 744: warning(_("unable to open %s"), sudoers_path); ! 745: exit(1); ! 746: } ! 747: init_parser(sudoers_path, quiet); ! 748: if (yyparse() && !parse_error) { ! 749: if (!quiet) ! 750: warningx(_("failed to parse %s file, unknown error"), sudoers_path); ! 751: parse_error = TRUE; ! 752: errorfile = sudoers_path; ! 753: } ! 754: if (!parse_error && check_aliases(strict, quiet) != 0) { ! 755: parse_error = TRUE; ! 756: errorfile = sudoers_path; ! 757: } ! 758: error = parse_error; ! 759: if (!quiet) { ! 760: if (parse_error) { ! 761: if (errorlineno != -1) ! 762: (void) printf(_("parse error in %s near line %d\n"), ! 763: errorfile, errorlineno); ! 764: else ! 765: (void) printf(_("parse error in %s\n"), errorfile); ! 766: } else { ! 767: (void) printf(_("%s: parsed OK\n"), sudoers_path); ! 768: } ! 769: } ! 770: /* Check mode and owner in strict mode. */ ! 771: if (strict && yyin != stdin && fstat(fileno(yyin), &sb) == 0) { ! 772: if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID) { ! 773: error = TRUE; ! 774: if (!quiet) { ! 775: fprintf(stderr, ! 776: _("%s: wrong owner (uid, gid) should be (%u, %u)\n"), ! 777: sudoers_path, SUDOERS_UID, SUDOERS_GID); ! 778: } ! 779: } ! 780: if ((sb.st_mode & 07777) != SUDOERS_MODE) { ! 781: error = TRUE; ! 782: if (!quiet) { ! 783: fprintf(stderr, _("%s: bad permissions, should be mode 0%o\n"), ! 784: sudoers_path, SUDOERS_MODE); ! 785: } ! 786: } ! 787: } ! 788: ! 789: return error; ! 790: } ! 791: ! 792: /* ! 793: * Used to open (and lock) the initial sudoers file and to also open ! 794: * any subsequent files #included via a callback from the parser. ! 795: */ ! 796: FILE * ! 797: open_sudoers(const char *path, int doedit, int *keepopen) ! 798: { ! 799: struct sudoersfile *entry; ! 800: FILE *fp; ! 801: ! 802: /* Check for existing entry */ ! 803: tq_foreach_fwd(&sudoerslist, entry) { ! 804: if (strcmp(path, entry->path) == 0) ! 805: break; ! 806: } ! 807: if (entry == NULL) { ! 808: entry = emalloc(sizeof(*entry)); ! 809: entry->path = estrdup(path); ! 810: entry->modified = 0; ! 811: entry->prev = entry; ! 812: entry->next = NULL; ! 813: entry->fd = open(entry->path, O_RDWR | O_CREAT, SUDOERS_MODE); ! 814: entry->tpath = NULL; ! 815: entry->doedit = doedit; ! 816: if (entry->fd == -1) { ! 817: warning("%s", entry->path); ! 818: efree(entry); ! 819: return NULL; ! 820: } ! 821: if (!lock_file(entry->fd, SUDO_TLOCK)) ! 822: errorx(1, _("%s busy, try again later"), entry->path); ! 823: if ((fp = fdopen(entry->fd, "r")) == NULL) ! 824: error(1, "%s", entry->path); ! 825: tq_append(&sudoerslist, entry); ! 826: } else { ! 827: /* Already exists, open .tmp version if there is one. */ ! 828: if (entry->tpath != NULL) { ! 829: if ((fp = fopen(entry->tpath, "r")) == NULL) ! 830: error(1, "%s", entry->tpath); ! 831: } else { ! 832: if ((fp = fdopen(entry->fd, "r")) == NULL) ! 833: error(1, "%s", entry->path); ! 834: rewind(fp); ! 835: } ! 836: } ! 837: if (keepopen != NULL) ! 838: *keepopen = TRUE; ! 839: return fp; ! 840: } ! 841: ! 842: static char * ! 843: get_editor(char **args) ! 844: { ! 845: char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs; ! 846: ! 847: /* ! 848: * Check VISUAL and EDITOR environment variables to see which editor ! 849: * the user wants to use (we may not end up using it though). ! 850: * If the path is not fully-qualified, make it so and check that ! 851: * the specified executable actually exists. ! 852: */ ! 853: UserEditorArgs = NULL; ! 854: if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0') ! 855: UserEditor = getenv("EDITOR"); ! 856: if (UserEditor && *UserEditor == '\0') ! 857: UserEditor = NULL; ! 858: else if (UserEditor) { ! 859: UserEditorArgs = get_args(UserEditor); ! 860: if (find_path(UserEditor, &Editor, NULL, getenv("PATH"), 0) == FOUND) { ! 861: UserEditor = Editor; ! 862: } else { ! 863: if (def_env_editor) { ! 864: /* If we are honoring $EDITOR this is a fatal error. */ ! 865: errorx(1, _("specified editor (%s) doesn't exist"), UserEditor); ! 866: } else { ! 867: /* Otherwise, just ignore $EDITOR. */ ! 868: UserEditor = NULL; ! 869: } ! 870: } ! 871: } ! 872: ! 873: /* ! 874: * See if we can use the user's choice of editors either because ! 875: * we allow any $EDITOR or because $EDITOR is in the allowable list. ! 876: */ ! 877: Editor = EditorArgs = EditorPath = NULL; ! 878: if (def_env_editor && UserEditor) { ! 879: Editor = UserEditor; ! 880: EditorArgs = UserEditorArgs; ! 881: } else if (UserEditor) { ! 882: struct stat editor_sb; ! 883: struct stat user_editor_sb; ! 884: char *base, *userbase; ! 885: ! 886: if (stat(UserEditor, &user_editor_sb) != 0) { ! 887: /* Should never happen since we already checked above. */ ! 888: error(1, _("unable to stat editor (%s)"), UserEditor); ! 889: } ! 890: EditorPath = estrdup(def_editor); ! 891: Editor = strtok(EditorPath, ":"); ! 892: do { ! 893: EditorArgs = get_args(Editor); ! 894: /* ! 895: * Both Editor and UserEditor should be fully qualified but ! 896: * check anyway... ! 897: */ ! 898: if ((base = strrchr(Editor, '/')) == NULL) ! 899: continue; ! 900: if ((userbase = strrchr(UserEditor, '/')) == NULL) { ! 901: Editor = NULL; ! 902: break; ! 903: } ! 904: base++, userbase++; ! 905: ! 906: /* ! 907: * We compare the basenames first and then use stat to match ! 908: * for sure. ! 909: */ ! 910: if (strcmp(base, userbase) == 0) { ! 911: if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode) ! 912: && (editor_sb.st_mode & 0000111) && ! 913: editor_sb.st_dev == user_editor_sb.st_dev && ! 914: editor_sb.st_ino == user_editor_sb.st_ino) ! 915: break; ! 916: } ! 917: } while ((Editor = strtok(NULL, ":"))); ! 918: } ! 919: ! 920: /* ! 921: * Can't use $EDITOR, try each element of def_editor until we ! 922: * find one that exists, is regular, and is executable. ! 923: */ ! 924: if (Editor == NULL || *Editor == '\0') { ! 925: efree(EditorPath); ! 926: EditorPath = estrdup(def_editor); ! 927: Editor = strtok(EditorPath, ":"); ! 928: do { ! 929: EditorArgs = get_args(Editor); ! 930: if (sudo_goodpath(Editor, NULL)) ! 931: break; ! 932: } while ((Editor = strtok(NULL, ":"))); ! 933: ! 934: /* Bleah, none of the editors existed! */ ! 935: if (Editor == NULL || *Editor == '\0') ! 936: errorx(1, _("no editor found (editor path = %s)"), def_editor); ! 937: } ! 938: *args = EditorArgs; ! 939: return Editor; ! 940: } ! 941: ! 942: /* ! 943: * Split out any command line arguments and return them. ! 944: */ ! 945: static char * ! 946: get_args(char *cmnd) ! 947: { ! 948: char *args; ! 949: ! 950: args = cmnd; ! 951: while (*args && !isblank((unsigned char) *args)) ! 952: args++; ! 953: if (*args) { ! 954: *args++ = '\0'; ! 955: while (*args && isblank((unsigned char) *args)) ! 956: args++; ! 957: } ! 958: return *args ? args : NULL; ! 959: } ! 960: ! 961: /* ! 962: * Look up the hostname and set user_host and user_shost. ! 963: */ ! 964: static void ! 965: get_hostname(void) ! 966: { ! 967: char *p, thost[MAXHOSTNAMELEN + 1]; ! 968: ! 969: if (gethostname(thost, sizeof(thost)) != 0) { ! 970: user_host = user_shost = "localhost"; ! 971: return; ! 972: } ! 973: thost[sizeof(thost) - 1] = '\0'; ! 974: user_host = estrdup(thost); ! 975: ! 976: if ((p = strchr(user_host, '.'))) { ! 977: *p = '\0'; ! 978: user_shost = estrdup(user_host); ! 979: *p = '.'; ! 980: } else { ! 981: user_shost = user_host; ! 982: } ! 983: } ! 984: ! 985: static int ! 986: alias_remove_recursive(char *name, int type, int strict, int quiet) ! 987: { ! 988: struct member *m; ! 989: struct alias *a; ! 990: int error = 0; ! 991: ! 992: if ((a = alias_find(name, type)) != NULL) { ! 993: tq_foreach_fwd(&a->members, m) { ! 994: if (m->type == ALIAS) { ! 995: if (!alias_remove_recursive(m->name, type, strict, quiet)) ! 996: error = 1; ! 997: } ! 998: } ! 999: } ! 1000: alias_seqno++; ! 1001: a = alias_remove(name, type); ! 1002: if (a) ! 1003: rbinsert(alias_freelist, a); ! 1004: return error; ! 1005: } ! 1006: ! 1007: static int ! 1008: check_alias(char *name, int type, int strict, int quiet) ! 1009: { ! 1010: struct member *m; ! 1011: struct alias *a; ! 1012: int error = 0; ! 1013: ! 1014: if ((a = alias_find(name, type)) != NULL) { ! 1015: /* check alias contents */ ! 1016: tq_foreach_fwd(&a->members, m) { ! 1017: if (m->type == ALIAS) ! 1018: error += check_alias(m->name, type, strict, quiet); ! 1019: } ! 1020: } else { ! 1021: if (!quiet) { ! 1022: char *fmt; ! 1023: if (errno == ELOOP) { ! 1024: fmt = strict ? ! 1025: _("Error: cycle in %s_Alias `%s'") : ! 1026: _("Warning: cycle in %s_Alias `%s'"); ! 1027: } else { ! 1028: fmt = strict ? ! 1029: _("Error: %s_Alias `%s' referenced but not defined") : ! 1030: _("Warning: %s_Alias `%s' referenced but not defined"); ! 1031: } ! 1032: warningx(fmt, ! 1033: type == HOSTALIAS ? "Host" : type == CMNDALIAS ? "Cmnd" : ! 1034: type == USERALIAS ? "User" : type == RUNASALIAS ? "Runas" : ! 1035: "Unknown", name); ! 1036: } ! 1037: error++; ! 1038: } ! 1039: ! 1040: return error; ! 1041: } ! 1042: ! 1043: /* ! 1044: * Iterate through the sudoers datastructures looking for undefined ! 1045: * aliases or unused aliases. ! 1046: */ ! 1047: static int ! 1048: check_aliases(int strict, int quiet) ! 1049: { ! 1050: struct cmndspec *cs; ! 1051: struct member *m, *binding; ! 1052: struct privilege *priv; ! 1053: struct userspec *us; ! 1054: struct defaults *d; ! 1055: int atype, error = 0; ! 1056: ! 1057: alias_freelist = rbcreate(alias_compare); ! 1058: ! 1059: /* Forward check. */ ! 1060: tq_foreach_fwd(&userspecs, us) { ! 1061: tq_foreach_fwd(&us->users, m) { ! 1062: if (m->type == ALIAS) { ! 1063: alias_seqno++; ! 1064: error += check_alias(m->name, USERALIAS, strict, quiet); ! 1065: } ! 1066: } ! 1067: tq_foreach_fwd(&us->privileges, priv) { ! 1068: tq_foreach_fwd(&priv->hostlist, m) { ! 1069: if (m->type == ALIAS) { ! 1070: alias_seqno++; ! 1071: error += check_alias(m->name, HOSTALIAS, strict, quiet); ! 1072: } ! 1073: } ! 1074: tq_foreach_fwd(&priv->cmndlist, cs) { ! 1075: tq_foreach_fwd(&cs->runasuserlist, m) { ! 1076: if (m->type == ALIAS) { ! 1077: alias_seqno++; ! 1078: error += check_alias(m->name, RUNASALIAS, strict, quiet); ! 1079: } ! 1080: } ! 1081: if ((m = cs->cmnd)->type == ALIAS) { ! 1082: alias_seqno++; ! 1083: error += check_alias(m->name, CMNDALIAS, strict, quiet); ! 1084: } ! 1085: } ! 1086: } ! 1087: } ! 1088: ! 1089: /* Reverse check (destructive) */ ! 1090: tq_foreach_fwd(&userspecs, us) { ! 1091: tq_foreach_fwd(&us->users, m) { ! 1092: if (m->type == ALIAS) { ! 1093: alias_seqno++; ! 1094: if (!alias_remove_recursive(m->name, USERALIAS, strict, quiet)) ! 1095: error++; ! 1096: } ! 1097: } ! 1098: tq_foreach_fwd(&us->privileges, priv) { ! 1099: tq_foreach_fwd(&priv->hostlist, m) { ! 1100: if (m->type == ALIAS) { ! 1101: alias_seqno++; ! 1102: if (!alias_remove_recursive(m->name, HOSTALIAS, strict, ! 1103: quiet)) ! 1104: error++; ! 1105: } ! 1106: } ! 1107: tq_foreach_fwd(&priv->cmndlist, cs) { ! 1108: tq_foreach_fwd(&cs->runasuserlist, m) { ! 1109: if (m->type == ALIAS) { ! 1110: alias_seqno++; ! 1111: if (!alias_remove_recursive(m->name, RUNASALIAS, ! 1112: strict, quiet)) ! 1113: error++; ! 1114: } ! 1115: } ! 1116: if ((m = cs->cmnd)->type == ALIAS) { ! 1117: alias_seqno++; ! 1118: if (!alias_remove_recursive(m->name, CMNDALIAS, strict, ! 1119: quiet)) ! 1120: error++; ! 1121: } ! 1122: } ! 1123: } ! 1124: } ! 1125: tq_foreach_fwd(&defaults, d) { ! 1126: switch (d->type) { ! 1127: case DEFAULTS_HOST: ! 1128: atype = HOSTALIAS; ! 1129: break; ! 1130: case DEFAULTS_USER: ! 1131: atype = USERALIAS; ! 1132: break; ! 1133: case DEFAULTS_RUNAS: ! 1134: atype = RUNASALIAS; ! 1135: break; ! 1136: case DEFAULTS_CMND: ! 1137: atype = CMNDALIAS; ! 1138: break; ! 1139: default: ! 1140: continue; /* not an alias */ ! 1141: } ! 1142: tq_foreach_fwd(&d->binding, binding) { ! 1143: for (m = binding; m != NULL; m = m->next) { ! 1144: if (m->type == ALIAS) { ! 1145: alias_seqno++; ! 1146: if (!alias_remove_recursive(m->name, atype, strict, quiet)) ! 1147: error++; ! 1148: } ! 1149: } ! 1150: } ! 1151: } ! 1152: rbdestroy(alias_freelist, alias_free); ! 1153: ! 1154: /* If all aliases were referenced we will have an empty tree. */ ! 1155: if (!no_aliases() && !quiet) ! 1156: alias_apply(print_unused, strict ? "Error" : "Warning"); ! 1157: ! 1158: return strict ? error : 0; ! 1159: } ! 1160: ! 1161: static int ! 1162: print_unused(void *v1, void *v2) ! 1163: { ! 1164: struct alias *a = (struct alias *)v1; ! 1165: char *prefix = (char *)v2; ! 1166: ! 1167: warningx(_("%s: unused %s_Alias %s"), prefix, ! 1168: a->type == HOSTALIAS ? "Host" : a->type == CMNDALIAS ? "Cmnd" : ! 1169: a->type == USERALIAS ? "User" : a->type == RUNASALIAS ? "Runas" : ! 1170: "Unknown", a->name); ! 1171: return 0; ! 1172: } ! 1173: ! 1174: /* ! 1175: * Unlink any sudoers temp files that remain. ! 1176: */ ! 1177: void ! 1178: cleanup(int gotsignal) ! 1179: { ! 1180: struct sudoersfile *sp; ! 1181: ! 1182: tq_foreach_fwd(&sudoerslist, sp) { ! 1183: if (sp->tpath != NULL) ! 1184: (void) unlink(sp->tpath); ! 1185: } ! 1186: if (!gotsignal) { ! 1187: sudo_endpwent(); ! 1188: sudo_endgrent(); ! 1189: } ! 1190: } ! 1191: ! 1192: /* ! 1193: * Unlink sudoers temp files (if any) and exit. ! 1194: */ ! 1195: static void ! 1196: quit(int signo) ! 1197: { ! 1198: const char *signame, *myname; ! 1199: ! 1200: cleanup(signo); ! 1201: #define emsg " exiting due to signal: " ! 1202: myname = getprogname(); ! 1203: signame = strsignal(signo); ! 1204: if (write(STDERR_FILENO, myname, strlen(myname)) == -1 || ! 1205: write(STDERR_FILENO, emsg, sizeof(emsg) - 1) == -1 || ! 1206: write(STDERR_FILENO, signame, strlen(signame)) == -1 || ! 1207: write(STDERR_FILENO, "\n", 1) == -1) ! 1208: /* shut up glibc */; ! 1209: _exit(signo); ! 1210: } ! 1211: ! 1212: static void ! 1213: usage(int fatal) ! 1214: { ! 1215: (void) fprintf(fatal ? stderr : stdout, ! 1216: "usage: %s [-chqsV] [-f sudoers]\n", getprogname()); ! 1217: if (fatal) ! 1218: exit(1); ! 1219: } ! 1220: ! 1221: static void ! 1222: help(void) ! 1223: { ! 1224: (void) printf(_("%s - safely edit the sudoers file\n\n"), getprogname()); ! 1225: usage(0); ! 1226: (void) puts(_("\nOptions:\n" ! 1227: " -c check-only mode\n" ! 1228: " -f sudoers specify sudoers file location\n" ! 1229: " -h display help message and exit\n" ! 1230: " -q less verbose (quiet) syntax error messages\n" ! 1231: " -s strict syntax checking\n" ! 1232: " -V display version information and exit")); ! 1233: exit(0); ! 1234: } ! 1235: ! 1236: static int ! 1237: visudo_printf(int msg_type, const char *fmt, ...) ! 1238: { ! 1239: va_list ap; ! 1240: FILE *fp; ! 1241: ! 1242: switch (msg_type) { ! 1243: case SUDO_CONV_INFO_MSG: ! 1244: fp = stdout; ! 1245: break; ! 1246: case SUDO_CONV_ERROR_MSG: ! 1247: fp = stderr; ! 1248: break; ! 1249: default: ! 1250: errno = EINVAL; ! 1251: return -1; ! 1252: } ! 1253: ! 1254: va_start(ap, fmt); ! 1255: vfprintf(fp, fmt, ap); ! 1256: va_end(ap); ! 1257: ! 1258: return 0; ! 1259: }