Annotation of src/usr.bin/sudo/visudo.c, Revision 1.22
1.1 millert 1: /*
1.22 ! millert 2: * Copyright (c) 1996, 1998-2005, 2007-2008
! 3: * Todd C. Miller <Todd.Miller@courtesan.com>
1.1 millert 4: *
1.17 millert 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.
1.1 millert 8: *
1.17 millert 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.
1.15 millert 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.
1.1 millert 20: */
21:
22: /*
23: * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
24: */
25:
1.14 millert 26: #define _SUDO_MAIN
27:
1.17 millert 28: #ifdef __TANDEM
29: # include <floss.h>
30: #endif
31:
1.19 millert 32: #include <config.h>
1.1 millert 33:
1.11 millert 34: #include <sys/types.h>
35: #include <sys/param.h>
36: #include <sys/stat.h>
1.22 ! millert 37: #include <sys/socket.h>
1.17 millert 38: #include <sys/time.h>
39: #ifndef __TANDEM
40: # include <sys/file.h>
41: #endif
1.11 millert 42: #include <sys/wait.h>
1.1 millert 43: #include <stdio.h>
44: #ifdef STDC_HEADERS
1.11 millert 45: # include <stdlib.h>
46: # include <stddef.h>
47: #else
48: # ifdef HAVE_STDLIB_H
49: # include <stdlib.h>
50: # endif
1.1 millert 51: #endif /* STDC_HEADERS */
1.11 millert 52: #ifdef HAVE_STRING_H
53: # include <string.h>
54: #else
55: # ifdef HAVE_STRINGS_H
56: # include <strings.h>
57: # endif
58: #endif /* HAVE_STRING_H */
1.1 millert 59: #ifdef HAVE_UNISTD_H
60: #include <unistd.h>
61: #endif /* HAVE_UNISTD_H */
62: #include <ctype.h>
63: #include <pwd.h>
1.22 ! millert 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>
1.19 millert 71: #if TIME_WITH_SYS_TIME
72: # include <time.h>
73: #endif
1.22 ! millert 74: #ifdef __STDC__
! 75: # include <stdarg.h>
! 76: #else
! 77: # include <varargs.h>
! 78: #endif
1.19 millert 79: #ifndef HAVE_TIMESPEC
80: # include <emul/timespec.h>
81: #endif
1.1 millert 82:
83: #include "sudo.h"
1.22 ! millert 84: #include "interfaces.h"
! 85: #include "parse.h"
! 86: #include <gram.h>
1.1 millert 87: #include "version.h"
88:
89: #ifndef lint
1.22 ! millert 90: __unused static const char rcsid[] = "$Sudo: visudo.c,v 1.220 2008/11/09 20:18:22 millert Exp $";
1.1 millert 91: #endif /* lint */
92:
1.19 millert 93: struct sudoersfile {
94: char *path;
1.22 ! millert 95: int fd;
1.19 millert 96: char *tpath;
1.22 ! millert 97: int tfd;
! 98: int modified;
! 99: struct sudoersfile *next;
1.19 millert 100: };
101:
1.1 millert 102: /*
103: * Function prototypes
104: */
1.22 ! millert 105: static RETSIGTYPE quit __P((int));
! 106: static char *get_args __P((char *));
! 107: static char *get_editor __P((char **));
1.1 millert 108: static char whatnow __P((void));
1.22 ! millert 109: static int check_aliases __P((int));
! 110: static int check_syntax __P((char *, int, int));
! 111: static int edit_sudoers __P((struct sudoersfile *, char *, char *, int));
! 112: static int install_sudoers __P((struct sudoersfile *, int));
! 113: static int print_unused __P((void *, void *));
! 114: static int reparse_sudoers __P((char *, char *, int, int));
! 115: static int run_command __P((char *, char **));
1.1 millert 116: static void setup_signals __P((void));
1.22 ! millert 117: static void usage __P((void)) __attribute__((__noreturn__));
1.19 millert 118:
1.22 ! millert 119: extern void yyerror __P((const char *));
! 120: extern void yyrestart __P((FILE *));
1.1 millert 121:
122: /*
123: * External globals exported by the parser
124: */
1.19 millert 125: extern FILE *yyin;
1.22 ! millert 126: extern char *sudoers, *errorfile;
! 127: extern int errorlineno, parse_error;
1.11 millert 128: /* For getopt(3) */
129: extern char *optarg;
130: extern int optind;
1.1 millert 131:
132: /*
133: * Globals
134: */
1.22 ! millert 135: int Argc;
1.1 millert 136: char **Argv;
1.22 ! millert 137: int num_interfaces;
! 138: struct interface *interfaces;
1.1 millert 139: struct sudo_user sudo_user;
1.22 ! millert 140: struct passwd *list_pw;
! 141: static struct sudoerslist {
! 142: struct sudoersfile *first, *last;
! 143: } sudoerslist;
1.1 millert 144:
145: int
146: main(argc, argv)
147: int argc;
148: char **argv;
149: {
1.22 ! millert 150: struct sudoersfile *sp;
! 151: char *args, *editor, *sudoers_path;
! 152: int ch, checkonly, quiet, strict, oldperms;
1.1 millert 153:
1.17 millert 154: Argv = argv;
155: if ((Argc = argc) < 1)
156: usage();
1.1 millert 157:
158: /*
159: * Arg handling.
160: */
1.22 ! millert 161: checkonly = oldperms = quiet = strict = FALSE;
! 162: sudoers_path = _PATH_SUDOERS;
1.11 millert 163: while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
164: switch (ch) {
165: case 'V':
1.14 millert 166: (void) printf("%s version %s\n", getprogname(), version);
1.11 millert 167: exit(0);
168: case 'c':
169: checkonly++; /* check mode */
170: break;
1.22 ! millert 171: case 'f':
! 172: sudoers_path = optarg; /* sudoers file path */
1.20 millert 173: oldperms = TRUE;
1.11 millert 174: break;
175: case 's':
1.22 ! millert 176: strict++; /* strict mode */
1.11 millert 177: break;
178: case 'q':
179: quiet++; /* quiet mode */
180: break;
181: default:
182: usage();
1.1 millert 183: }
184: }
1.11 millert 185: argc -= optind;
186: argv += optind;
187: if (argc)
188: usage();
1.1 millert 189:
1.22 ! millert 190: sudo_setpwent();
! 191: sudo_setgrent();
! 192:
1.1 millert 193: /* Mock up a fake sudo_user struct. */
194: user_host = user_shost = user_cmnd = "";
1.22 ! millert 195: if ((sudo_user.pw = sudo_getpwuid(getuid())) == NULL)
! 196: errorx(1, "you don't exist in the passwd database");
1.1 millert 197:
1.4 millert 198: /* Setup defaults data structures. */
199: init_defaults();
1.1 millert 200:
1.11 millert 201: if (checkonly)
1.22 ! millert 202: exit(check_syntax(sudoers_path, quiet, strict));
1.11 millert 203:
1.1 millert 204: /*
1.22 ! millert 205: * Parse the existing sudoers file(s) in quiet mode to highlight any
! 206: * existing errors and to pull in editor and env_editor conf values.
! 207: */
! 208: if ((yyin = open_sudoers(sudoers_path, NULL)) == NULL)
! 209: error(1, "%s", sudoers_path);
! 210: init_parser(sudoers_path, 0);
1.19 millert 211: yyparse();
1.22 ! millert 212: (void) update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER);
1.19 millert 213:
214: editor = get_editor(&args);
1.1 millert 215:
1.22 ! millert 216: /* Install signal handlers to clean up temp files if we are killed. */
! 217: setup_signals();
! 218:
! 219: /* Edit the sudoers file(s) */
! 220: tq_foreach_fwd(&sudoerslist, sp) {
! 221: if (sp != tq_first(&sudoerslist)) {
! 222: printf("press return to edit %s: ", sp->path);
! 223: while ((ch = getchar()) != EOF && ch != '\n')
! 224: continue;
! 225: }
! 226: edit_sudoers(sp, editor, args, -1);
! 227: }
! 228:
! 229: /* Check edited files for a parse error and re-edit any that fail. */
! 230: reparse_sudoers(editor, args, strict, quiet);
! 231:
! 232: /* Install the sudoers temp files. */
! 233: tq_foreach_fwd(&sudoerslist, sp) {
! 234: if (!sp->modified)
! 235: (void) unlink(sp->tpath);
! 236: else
! 237: (void) install_sudoers(sp, oldperms);
! 238: }
1.3 millert 239:
1.19 millert 240: exit(0);
241: }
1.1 millert 242:
1.19 millert 243: /*
1.22 ! millert 244: * Edit each sudoers file.
1.19 millert 245: * Returns TRUE on success, else FALSE.
246: */
1.22 ! millert 247: static int
1.19 millert 248: edit_sudoers(sp, editor, args, lineno)
249: struct sudoersfile *sp;
250: char *editor, *args;
251: int lineno;
252: {
1.22 ! millert 253: int tfd; /* sudoers temp file descriptor */
! 254: int modified; /* was the file modified? */
1.19 millert 255: int ac; /* argument count */
256: char **av; /* argument vector for run_command */
257: char *cp; /* scratch char pointer */
1.22 ! millert 258: char buf[PATH_MAX*2]; /* buffer used for copying files */
1.19 millert 259: char linestr[64]; /* string version of lineno */
260: struct timespec ts1, ts2; /* time before and after edit */
1.22 ! millert 261: struct timespec orig_mtim; /* starting mtime of sudoers file */
! 262: off_t orig_size; /* starting size of sudoers file */
! 263: ssize_t nread; /* number of bytes read */
1.19 millert 264: struct stat sb; /* stat buffer */
1.1 millert 265:
1.22 ! millert 266: #ifdef HAVE_FSTAT
! 267: if (fstat(sp->fd, &sb) == -1)
! 268: #else
! 269: if (stat(sp->path, &sb) == -1)
! 270: #endif
! 271: error(1, "can't stat %s", sp->path);
! 272: orig_size = sb.st_size;
! 273: orig_mtim.tv_sec = mtim_getsec(sb);
! 274: orig_mtim.tv_nsec = mtim_getnsec(sb);
! 275:
! 276: /* Create the temp file if needed and set timestamp. */
! 277: if (sp->tpath == NULL) {
! 278: easprintf(&sp->tpath, "%s.tmp", sp->path);
! 279: tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
! 280: if (tfd < 0)
! 281: error(1, "%s", sp->tpath);
! 282:
! 283: /* Copy sp->path -> sp->tpath and reset the mtime. */
! 284: if (orig_size != 0) {
! 285: (void) lseek(sp->fd, (off_t)0, SEEK_SET);
! 286: while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
! 287: if (write(tfd, buf, nread) != nread)
! 288: error(1, "write error");
! 289:
! 290: /* Add missing newline at EOF if needed. */
! 291: if (nread > 0 && buf[nread - 1] != '\n') {
! 292: buf[0] = '\n';
! 293: write(tfd, buf, 1);
! 294: }
! 295: }
! 296: (void) close(tfd);
! 297: }
! 298: (void) touch(-1, sp->tpath, &orig_mtim);
1.13 millert 299:
1.19 millert 300: /* Find the length of the argument vector */
301: ac = 3 + (lineno > 0);
302: if (args) {
303: int wasblank;
304:
305: ac++;
306: for (wasblank = FALSE, cp = args; *cp; cp++) {
307: if (isblank((unsigned char) *cp))
308: wasblank = TRUE;
309: else if (wasblank) {
310: wasblank = FALSE;
311: ac++;
312: }
313: }
314: }
315:
316: /* Build up argument vector for the command */
317: av = emalloc2(ac, sizeof(char *));
318: if ((av[0] = strrchr(editor, '/')) != NULL)
319: av[0]++;
320: else
321: av[0] = editor;
322: ac = 1;
323: if (lineno > 0) {
324: (void) snprintf(linestr, sizeof(linestr), "+%d", lineno);
325: av[ac++] = linestr;
326: }
327: if (args) {
328: for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t")))
329: av[ac++] = cp;
330: }
331: av[ac++] = sp->tpath;
332: av[ac++] = NULL;
1.1 millert 333:
334: /*
1.19 millert 335: * Do the edit:
336: * We cannot check the editor's exit value against 0 since
337: * XPG4 specifies that vi's exit value is a function of the
338: * number of errors during editing (?!?!).
1.4 millert 339: */
1.19 millert 340: gettime(&ts1);
341: if (run_command(editor, av) != -1) {
342: gettime(&ts2);
343: /*
344: * Sanity checks.
345: */
346: if (stat(sp->tpath, &sb) < 0) {
1.22 ! millert 347: warningx("cannot stat temporary file (%s), %s unchanged",
1.19 millert 348: sp->tpath, sp->path);
1.22 ! millert 349: return(FALSE);
1.11 millert 350: }
1.22 ! millert 351: if (sb.st_size == 0 && orig_size != 0) {
! 352: warningx("zero length temporary file (%s), %s unchanged",
1.19 millert 353: sp->tpath, sp->path);
1.22 ! millert 354: sp->modified = TRUE;
! 355: return(FALSE);
1.11 millert 356: }
1.19 millert 357: } else {
1.22 ! millert 358: warningx("editor (%s) failed, %s unchanged", editor, sp->path);
! 359: return(FALSE);
1.11 millert 360: }
361:
1.22 ! millert 362: /* Set modified bit if use changed the file. */
! 363: modified = TRUE;
! 364: if (orig_size == sb.st_size &&
! 365: orig_mtim.tv_sec == mtim_getsec(sb) &&
! 366: orig_mtim.tv_nsec == mtim_getnsec(sb)) {
1.19 millert 367: /*
368: * If mtime and size match but the user spent no measurable
369: * time in the editor we can't tell if the file was changed.
370: */
371: #ifdef HAVE_TIMESPECSUB2
372: timespecsub(&ts1, &ts2);
373: #else
374: timespecsub(&ts1, &ts2, &ts2);
375: #endif
1.22 ! millert 376: if (timespecisset(&ts2))
! 377: modified = FALSE;
1.11 millert 378: }
1.22 ! millert 379:
! 380: /*
! 381: * If modified in this edit session, mark as modified.
! 382: */
! 383: if (modified)
! 384: sp->modified = modified;
! 385: else
! 386: warningx("%s unchanged", sp->tpath);
! 387:
! 388: return(TRUE);
1.19 millert 389: }
390:
391: /*
392: * Parse sudoers after editing and re-edit any ones that caused a parse error.
393: * Returns TRUE on success, else FALSE.
394: */
1.22 ! millert 395: static int
! 396: reparse_sudoers(editor, args, strict, quiet)
1.19 millert 397: char *editor, *args;
1.22 ! millert 398: int strict, quiet;
1.19 millert 399: {
1.22 ! millert 400: struct sudoersfile *sp, *last;
! 401: FILE *fp;
1.19 millert 402: int ch;
1.4 millert 403:
404: /*
1.22 ! millert 405: * Parse the edited sudoers files and do sanity checking
1.1 millert 406: */
407: do {
1.22 ! millert 408: sp = tq_first(&sudoerslist);
! 409: last = tq_last(&sudoerslist);
! 410: fp = fopen(sp->tpath, "r+");
! 411: if (fp == NULL)
! 412: errorx(1, "can't re-open temporary file (%s), %s unchanged.",
1.19 millert 413: sp->tpath, sp->path);
414:
415: /* Clean slate for each parse */
416: init_defaults();
1.22 ! millert 417: init_parser(sp->path, quiet);
1.19 millert 418:
419: /* Parse the sudoers temp file */
1.22 ! millert 420: yyrestart(fp);
1.19 millert 421: if (yyparse() && parse_error != TRUE) {
1.22 ! millert 422: warningx("unabled to parse temporary file (%s), unknown error",
1.19 millert 423: sp->tpath);
424: parse_error = TRUE;
1.1 millert 425: }
1.19 millert 426: fclose(yyin);
1.22 ! millert 427: if (check_aliases(strict) != 0)
! 428: parse_error = TRUE;
1.1 millert 429:
430: /*
431: * Got an error, prompt the user for what to do now
432: */
1.19 millert 433: if (parse_error) {
1.1 millert 434: switch (whatnow()) {
1.7 millert 435: case 'Q' : parse_error = FALSE; /* ignore parse error */
1.1 millert 436: break;
1.22 ! millert 437: case 'x' : cleanup(0);
! 438: exit(0);
1.1 millert 439: break;
440: }
441: }
1.22 ! millert 442: if (parse_error) {
! 443: /* Edit file with the parse error */
! 444: tq_foreach_fwd(&sudoerslist, sp) {
! 445: if (errorfile == NULL || strcmp(sp->path, errorfile) == 0) {
! 446: edit_sudoers(sp, editor, args, errorlineno);
! 447: break;
! 448: }
! 449: }
! 450: if (sp == NULL)
! 451: errorx(1, "internal error, can't find %s in list!", sudoers);
! 452: }
! 453:
! 454: /* If any new #include directives were added, edit them too. */
! 455: for (sp = last->next; sp != NULL; sp = sp->next) {
! 456: printf("press return to edit %s: ", sp->path);
! 457: while ((ch = getchar()) != EOF && ch != '\n')
! 458: continue;
! 459: edit_sudoers(sp, editor, args, errorlineno);
! 460: }
1.19 millert 461: } while (parse_error);
1.22 ! millert 462:
! 463: return(TRUE);
1.19 millert 464: }
1.1 millert 465:
1.19 millert 466: /*
467: * Set the owner and mode on a sudoers temp file and
468: * move it into place. Returns TRUE on success, else FALSE.
469: */
1.22 ! millert 470: static int
1.20 millert 471: install_sudoers(sp, oldperms)
1.19 millert 472: struct sudoersfile *sp;
1.20 millert 473: int oldperms;
1.19 millert 474: {
1.20 millert 475: struct stat sb;
476:
1.1 millert 477: /*
478: * Change mode and ownership of temp file so when
1.19 millert 479: * we move it to sp->path things are kosher.
1.1 millert 480: */
1.20 millert 481: if (oldperms) {
482: /* Use perms of the existing file. */
483: #ifdef HAVE_FSTAT
484: if (fstat(sp->fd, &sb) == -1)
485: #else
486: if (stat(sp->path, &sb) == -1)
487: #endif
1.22 ! millert 488: error(1, "can't stat %s", sp->path);
1.20 millert 489: (void) chown(sp->tpath, sb.st_uid, sb.st_gid);
490: (void) chmod(sp->tpath, sb.st_mode & 0777);
491: } else {
492: if (chown(sp->tpath, SUDOERS_UID, SUDOERS_GID) != 0) {
1.22 ! millert 493: warning("unable to set (uid, gid) of %s to (%d, %d)",
1.20 millert 494: sp->tpath, SUDOERS_UID, SUDOERS_GID);
1.22 ! millert 495: return(FALSE);
1.20 millert 496: }
497: if (chmod(sp->tpath, SUDOERS_MODE) != 0) {
1.22 ! millert 498: warning("unable to change mode of %s to 0%o", sp->tpath,
! 499: SUDOERS_MODE);
! 500: return(FALSE);
1.20 millert 501: }
1.1 millert 502: }
503:
504: /*
1.19 millert 505: * Now that sp->tpath is sane (parses ok) it needs to be
506: * rename(2)'d to sp->path. If the rename(2) fails we try using
507: * mv(1) in case sp->tpath and sp->path are on different file systems.
1.1 millert 508: */
1.22 ! millert 509: if (rename(sp->tpath, sp->path) == 0) {
! 510: efree(sp->tpath);
! 511: sp->tpath = NULL;
! 512: } else {
1.1 millert 513: if (errno == EXDEV) {
1.19 millert 514: char *av[4];
1.22 ! millert 515: warningx("%s and %s not on the same file system, using mv to rename",
1.19 millert 516: sp->tpath, sp->path);
1.1 millert 517:
1.11 millert 518: /* Build up argument vector for the command */
519: if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
520: av[0]++;
521: else
522: av[0] = _PATH_MV;
1.19 millert 523: av[1] = sp->tpath;
524: av[2] = sp->path;
1.11 millert 525: av[3] = NULL;
1.1 millert 526:
1.11 millert 527: /* And run it... */
528: if (run_command(_PATH_MV, av)) {
1.22 ! millert 529: warningx("command failed: '%s %s %s', %s unchanged",
1.19 millert 530: _PATH_MV, sp->tpath, sp->path, sp->path);
1.22 ! millert 531: (void) unlink(sp->tpath);
! 532: efree(sp->tpath);
! 533: sp->tpath = NULL;
! 534: return(FALSE);
1.1 millert 535: }
1.22 ! millert 536: efree(sp->tpath);
! 537: sp->tpath = NULL;
1.1 millert 538: } else {
1.22 ! millert 539: warning("error renaming %s, %s unchanged", sp->tpath, sp->path);
! 540: (void) unlink(sp->tpath);
! 541: return(FALSE);
1.1 millert 542: }
543: }
544: return(TRUE);
545: }
546:
1.22 ! millert 547: /* STUB */
! 548: void
! 549: set_fqdn()
1.4 millert 550: {
1.22 ! millert 551: return;
1.4 millert 552: }
553:
1.22 ! millert 554: /* STUB */
! 555: void
! 556: init_envtables()
1.1 millert 557: {
1.22 ! millert 558: return;
1.1 millert 559: }
560:
1.22 ! millert 561: /* STUB */
1.1 millert 562: int
1.22 ! millert 563: user_is_exempt()
1.17 millert 564: {
1.22 ! millert 565: return(FALSE);
1.17 millert 566: }
567:
1.22 ! millert 568: /* STUB */
! 569: void
! 570: sudo_setspent()
1.1 millert 571: {
1.22 ! millert 572: return;
1.2 millert 573: }
574:
1.22 ! millert 575: /* STUB */
1.2 millert 576: void
1.22 ! millert 577: sudo_endspent()
1.2 millert 578: {
579: return;
1.1 millert 580: }
581:
1.22 ! millert 582: char *
! 583: sudo_getepw(pw)
! 584: const struct passwd *pw;
1.11 millert 585: {
1.22 ! millert 586: return (pw->pw_passwd);
1.11 millert 587: }
588:
1.1 millert 589: /*
590: * Assuming a parse error occurred, prompt the user for what they want
591: * to do now. Returns the first letter of their choice.
592: */
593: static char
594: whatnow()
595: {
596: int choice, c;
597:
598: for (;;) {
599: (void) fputs("What now? ", stdout);
600: choice = getchar();
601: for (c = choice; c != '\n' && c != EOF;)
602: c = getchar();
603:
1.3 millert 604: switch (choice) {
605: case EOF:
606: choice = 'x';
607: /* FALLTHROUGH */
608: case 'e':
609: case 'x':
610: case 'Q':
611: return(choice);
612: default:
613: (void) puts("Options are:");
614: (void) puts(" (e)dit sudoers file again");
615: (void) puts(" e(x)it without saving changes to sudoers file");
616: (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
1.1 millert 617: }
618: }
619: }
620:
621: /*
622: * Install signal handlers for visudo.
623: */
624: static void
625: setup_signals()
626: {
1.11 millert 627: sigaction_t sa;
1.1 millert 628:
629: /*
630: * Setup signal handlers to cleanup nicely.
631: */
1.22 ! millert 632: zero_bytes(&sa, sizeof(sa));
1.11 millert 633: sigemptyset(&sa.sa_mask);
634: sa.sa_flags = SA_RESTART;
1.22 ! millert 635: sa.sa_handler = quit;
1.11 millert 636: (void) sigaction(SIGTERM, &sa, NULL);
637: (void) sigaction(SIGHUP, &sa, NULL);
638: (void) sigaction(SIGINT, &sa, NULL);
639: (void) sigaction(SIGQUIT, &sa, NULL);
640: }
641:
642: static int
643: run_command(path, argv)
644: char *path;
645: char **argv;
646: {
647: int status;
1.21 millert 648: pid_t pid, rv;
1.11 millert 649:
650: switch (pid = fork()) {
651: case -1:
1.22 ! millert 652: error(1, "unable to run %s", path);
1.11 millert 653: break; /* NOTREACHED */
654: case 0:
1.22 ! millert 655: sudo_endpwent();
! 656: sudo_endgrent();
1.19 millert 657: closefrom(STDERR_FILENO + 1);
1.11 millert 658: execv(path, argv);
1.22 ! millert 659: warning("unable to run %s", path);
1.11 millert 660: _exit(127);
661: break; /* NOTREACHED */
662: }
663:
1.21 millert 664: do {
1.11 millert 665: #ifdef sudo_waitpid
1.21 millert 666: rv = sudo_waitpid(pid, &status, 0);
1.1 millert 667: #else
1.21 millert 668: rv = wait(&status);
1.11 millert 669: #endif
1.21 millert 670: } while (rv == -1 && errno == EINTR);
1.11 millert 671:
1.21 millert 672: if (rv == -1 || !WIFEXITED(status))
1.17 millert 673: return(-1);
674: return(WEXITSTATUS(status));
1.11 millert 675: }
676:
677: static int
1.22 ! millert 678: check_syntax(sudoers_path, quiet, strict)
! 679: char *sudoers_path;
! 680: int quiet;
! 681: int strict;
1.11 millert 682: {
1.22 ! millert 683: struct stat sb;
! 684: int error;
1.11 millert 685:
1.22 ! millert 686: if ((yyin = fopen(sudoers_path, "r")) == NULL) {
1.11 millert 687: if (!quiet)
1.22 ! millert 688: warning("unable to open %s", sudoers_path);
1.11 millert 689: exit(1);
690: }
1.22 ! millert 691: init_parser(sudoers_path, quiet);
1.11 millert 692: if (yyparse() && parse_error != TRUE) {
693: if (!quiet)
1.22 ! millert 694: warningx("failed to parse %s file, unknown error", sudoers_path);
1.11 millert 695: parse_error = TRUE;
696: }
1.22 ! millert 697: error = parse_error;
! 698: if (!quiet) {
1.11 millert 699: if (parse_error)
1.22 ! millert 700: (void) printf("parse error in %s near line %d\n", sudoers_path,
1.11 millert 701: errorlineno);
702: else
1.22 ! millert 703: (void) printf("%s: parsed OK\n", sudoers_path);
! 704: }
! 705: /* Check mode and owner in strict mode. */
! 706: #ifdef HAVE_FSTAT
! 707: if (strict && fstat(fileno(yyin), &sb) == 0)
! 708: #else
! 709: if (strict && stat(sudoers_path, &sb) == 0)
! 710: #endif
! 711: {
! 712: if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID) {
! 713: error = TRUE;
! 714: if (!quiet) {
! 715: fprintf(stderr, "%s: wrong owner (uid, gid) should be (%d, %d)\n",
! 716: sudoers_path, SUDOERS_UID, SUDOERS_GID);
! 717: }
! 718: }
! 719: if ((sb.st_mode & 07777) != SUDOERS_MODE) {
! 720: error = TRUE;
! 721: if (!quiet) {
! 722: fprintf(stderr, "%s: bad permissions, should be mode 0%o\n",
! 723: sudoers_path, SUDOERS_MODE);
! 724: }
! 725: }
1.11 millert 726: }
727:
1.22 ! millert 728: return(error);
1.1 millert 729: }
730:
1.22 ! millert 731: /*
! 732: * Used to open (and lock) the initial sudoers file and to also open
! 733: * any subsequent files #included via a callback from the parser.
! 734: */
! 735: FILE *
! 736: open_sudoers(path, keepopen)
! 737: const char *path;
! 738: int *keepopen;
1.19 millert 739: {
1.22 ! millert 740: struct sudoersfile *entry;
1.19 millert 741: FILE *fp;
742:
1.22 ! millert 743: /* Check for existing entry */
! 744: tq_foreach_fwd(&sudoerslist, entry) {
! 745: if (strcmp(path, entry->path) == 0)
! 746: break;
! 747: }
! 748: if (entry == NULL) {
! 749: entry = emalloc(sizeof(*entry));
! 750: entry->path = estrdup(path);
! 751: entry->modified = 0;
! 752: entry->next = NULL;
! 753: entry->fd = open(entry->path, O_RDWR | O_CREAT, SUDOERS_MODE);
! 754: entry->tpath = NULL;
! 755: if (entry->fd == -1) {
! 756: warning("%s", entry->path);
! 757: efree(entry);
! 758: return(NULL);
! 759: }
! 760: if (!lock_file(entry->fd, SUDO_TLOCK))
! 761: errorx(1, "%s busy, try again later", entry->path);
! 762: if ((fp = fdopen(entry->fd, "r")) == NULL)
! 763: error(1, "%s", entry->path);
! 764: /* XXX - macro here? */
! 765: if (sudoerslist.last == NULL)
! 766: sudoerslist.first = sudoerslist.last = entry;
! 767: else {
! 768: sudoerslist.last->next = entry;
! 769: sudoerslist.last = entry;
! 770: }
! 771: if (keepopen != NULL)
! 772: *keepopen = TRUE;
! 773: } else {
! 774: /* Already exists, open .tmp version if there is one. */
! 775: if (entry->tpath != NULL) {
! 776: if ((fp = fopen(entry->tpath, "r")) == NULL)
! 777: error(1, "%s", entry->tpath);
! 778: } else {
! 779: if ((fp = fdopen(entry->fd, "r")) == NULL)
! 780: error(1, "%s", entry->path);
1.19 millert 781: }
782: }
783: return(fp);
784: }
785:
786: static char *
787: get_editor(args)
788: char **args;
789: {
790: char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
791:
792: /*
793: * Check VISUAL and EDITOR environment variables to see which editor
794: * the user wants to use (we may not end up using it though).
795: * If the path is not fully-qualified, make it so and check that
796: * the specified executable actually exists.
797: */
798: UserEditorArgs = NULL;
799: if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
800: UserEditor = getenv("EDITOR");
801: if (UserEditor && *UserEditor == '\0')
802: UserEditor = NULL;
803: else if (UserEditor) {
804: UserEditorArgs = get_args(UserEditor);
805: if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
806: UserEditor = Editor;
807: } else {
808: if (def_env_editor) {
809: /* If we are honoring $EDITOR this is a fatal error. */
1.22 ! millert 810: errorx(1, "specified editor (%s) doesn't exist!", UserEditor);
1.19 millert 811: } else {
812: /* Otherwise, just ignore $EDITOR. */
813: UserEditor = NULL;
814: }
815: }
816: }
817:
818: /*
819: * See if we can use the user's choice of editors either because
820: * we allow any $EDITOR or because $EDITOR is in the allowable list.
821: */
822: Editor = EditorArgs = EditorPath = NULL;
823: if (def_env_editor && UserEditor) {
824: Editor = UserEditor;
825: EditorArgs = UserEditorArgs;
826: } else if (UserEditor) {
827: struct stat editor_sb;
828: struct stat user_editor_sb;
829: char *base, *userbase;
830:
831: if (stat(UserEditor, &user_editor_sb) != 0) {
832: /* Should never happen since we already checked above. */
1.22 ! millert 833: error(1, "unable to stat editor (%s)", UserEditor);
1.19 millert 834: }
835: EditorPath = estrdup(def_editor);
836: Editor = strtok(EditorPath, ":");
837: do {
838: EditorArgs = get_args(Editor);
839: /*
840: * Both Editor and UserEditor should be fully qualified but
841: * check anyway...
842: */
843: if ((base = strrchr(Editor, '/')) == NULL)
844: continue;
845: if ((userbase = strrchr(UserEditor, '/')) == NULL) {
846: Editor = NULL;
847: break;
848: }
849: base++, userbase++;
850:
851: /*
852: * We compare the basenames first and then use stat to match
853: * for sure.
854: */
855: if (strcmp(base, userbase) == 0) {
856: if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
857: && (editor_sb.st_mode & 0000111) &&
858: editor_sb.st_dev == user_editor_sb.st_dev &&
859: editor_sb.st_ino == user_editor_sb.st_ino)
860: break;
861: }
862: } while ((Editor = strtok(NULL, ":")));
863: }
864:
865: /*
866: * Can't use $EDITOR, try each element of def_editor until we
867: * find one that exists, is regular, and is executable.
868: */
869: if (Editor == NULL || *Editor == '\0') {
870: efree(EditorPath);
871: EditorPath = estrdup(def_editor);
872: Editor = strtok(EditorPath, ":");
873: do {
874: EditorArgs = get_args(Editor);
875: if (sudo_goodpath(Editor, NULL))
876: break;
877: } while ((Editor = strtok(NULL, ":")));
878:
879: /* Bleah, none of the editors existed! */
1.22 ! millert 880: if (Editor == NULL || *Editor == '\0')
! 881: errorx(1, "no editor found (editor path = %s)", def_editor);
1.19 millert 882: }
883: *args = EditorArgs;
884: return(Editor);
885: }
886:
887: /*
888: * Split out any command line arguments and return them.
889: */
890: static char *
891: get_args(cmnd)
892: char *cmnd;
893: {
894: char *args;
895:
896: args = cmnd;
897: while (*args && !isblank((unsigned char) *args))
898: args++;
899: if (*args) {
900: *args++ = '\0';
901: while (*args && isblank((unsigned char) *args))
902: args++;
903: }
904: return(*args ? args : NULL);
905: }
906:
1.1 millert 907: /*
1.22 ! millert 908: * Iterate through the sudoers datastructures looking for undefined
! 909: * aliases or unused aliases.
1.1 millert 910: */
1.22 ! millert 911: static int
! 912: check_aliases(strict)
! 913: int strict;
1.1 millert 914: {
1.22 ! millert 915: struct cmndspec *cs;
! 916: struct member *m;
! 917: struct privilege *priv;
! 918: struct userspec *us;
! 919: int error = 0;
! 920:
! 921: /* Forward check. */
! 922: tq_foreach_fwd(&userspecs, us) {
! 923: tq_foreach_fwd(&us->users, m) {
! 924: if (m->type == USERALIAS) {
! 925: if (find_alias(m->name, m->type) == NULL) {
! 926: warningx("%s: User_Alias `%s' referenced but not defined",
! 927: strict ? "Error" : "Warning", m->name);
! 928: error++;
! 929: }
! 930: }
! 931: }
! 932: tq_foreach_fwd(&us->privileges, priv) {
! 933: tq_foreach_fwd(&priv->hostlist, m) {
! 934: if (m->type == HOSTALIAS) {
! 935: if (find_alias(m->name, m->type) == NULL) {
! 936: warningx("%s: Host_Alias `%s' referenced but not defined",
! 937: strict ? "Error" : "Warning", m->name);
! 938: error++;
! 939: }
! 940: }
! 941: }
! 942: tq_foreach_fwd(&priv->cmndlist, cs) {
! 943: tq_foreach_fwd(&cs->runasuserlist, m) {
! 944: if (m->type == RUNASALIAS) {
! 945: if (find_alias(m->name, m->type) == NULL) {
! 946: warningx("%s: Runas_Alias `%s' referenced but not defined",
! 947: strict ? "Error" : "Warning", m->name);
! 948: error++;
! 949: }
! 950: }
! 951: }
! 952: if ((m = cs->cmnd)->type == CMNDALIAS) {
! 953: if (find_alias(m->name, m->type) == NULL) {
! 954: warningx("%s: Cmnd_Alias `%s' referenced but not defined",
! 955: strict ? "Error" : "Warning", m->name);
! 956: error++;
! 957: }
! 958: }
! 959: }
! 960: }
! 961: }
! 962:
! 963: /* Reverse check (destructive) */
! 964: tq_foreach_fwd(&userspecs, us) {
! 965: tq_foreach_fwd(&us->users, m) {
! 966: if (m->type == USERALIAS)
! 967: (void) alias_remove(m->name, m->type);
! 968: }
! 969: tq_foreach_fwd(&us->privileges, priv) {
! 970: tq_foreach_fwd(&priv->hostlist, m) {
! 971: if (m->type == HOSTALIAS)
! 972: (void) alias_remove(m->name, m->type);
! 973: }
! 974: tq_foreach_fwd(&priv->cmndlist, cs) {
! 975: tq_foreach_fwd(&cs->runasuserlist, m) {
! 976: if (m->type == RUNASALIAS)
! 977: (void) alias_remove(m->name, m->type);
! 978: }
! 979: if ((m = cs->cmnd)->type == CMNDALIAS)
! 980: (void) alias_remove(m->name, m->type);
! 981: }
! 982: }
! 983: }
! 984: /* If all aliases were referenced we will have an empty tree. */
! 985: if (no_aliases())
! 986: return(0);
! 987: alias_apply(print_unused, strict ? "Error" : "Warning");
! 988: return (strict ? 1 : 0);
! 989: }
! 990:
! 991: static int
! 992: print_unused(v1, v2)
! 993: void *v1;
! 994: void *v2;
! 995: {
! 996: struct alias *a = (struct alias *)v1;
! 997: char *prefix = (char *)v2;
! 998:
! 999: warningx("%s: unused %s_Alias %s", prefix,
! 1000: a->type == HOSTALIAS ? "Host" : a->type == CMNDALIAS ? "Cmnd" :
! 1001: a->type == USERALIAS ? "User" : a->type == RUNASALIAS ? "Runas" :
! 1002: "Unknown", a->name);
! 1003: return(0);
! 1004: }
1.10 millert 1005:
1.22 ! millert 1006: /*
! 1007: * Unlink any sudoers temp files that remain.
! 1008: */
! 1009: void
! 1010: cleanup(gotsignal)
! 1011: int gotsignal;
! 1012: {
! 1013: struct sudoersfile *sp;
1.1 millert 1014:
1.22 ! millert 1015: tq_foreach_fwd(&sudoerslist, sp) {
! 1016: if (sp->tpath != NULL)
! 1017: (void) unlink(sp->tpath);
1.10 millert 1018: }
1.22 ! millert 1019: if (!gotsignal) {
! 1020: sudo_endpwent();
! 1021: sudo_endgrent();
! 1022: }
! 1023: }
! 1024:
! 1025: /*
! 1026: * Unlink sudoers temp files (if any) and exit.
! 1027: */
! 1028: static RETSIGTYPE
! 1029: quit(signo)
! 1030: int signo;
! 1031: {
! 1032: cleanup(signo);
! 1033: #define emsg " exiting due to signal.\n"
! 1034: write(STDERR_FILENO, getprogname(), strlen(getprogname()));
! 1035: write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
! 1036: _exit(signo);
1.1 millert 1037: }
1038:
1039: static void
1040: usage()
1041: {
1.19 millert 1042: (void) fprintf(stderr, "usage: %s [-c] [-q] [-s] [-V] [-f sudoers]\n",
1.14 millert 1043: getprogname());
1.1 millert 1044: exit(1);
1045: }