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