Annotation of src/usr.bin/sudo/visudo.c, Revision 1.29
1.1 millert 1: /*
1.27 millert 2: * Copyright (c) 1996, 1998-2005, 2007-2009
1.22 millert 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:
1.19 millert 89: struct sudoersfile {
90: char *path;
1.27 millert 91: char *tpath;
1.22 millert 92: int fd;
93: int tfd;
94: int modified;
1.27 millert 95: int doedit;
1.22 millert 96: struct sudoersfile *next;
1.19 millert 97: };
98:
1.1 millert 99: /*
100: * Function prototypes
101: */
1.22 millert 102: static RETSIGTYPE quit __P((int));
103: static char *get_args __P((char *));
104: static char *get_editor __P((char **));
1.25 millert 105: static void get_hostname __P((void));
1.1 millert 106: static char whatnow __P((void));
1.25 millert 107: static int check_aliases __P((int, int));
1.22 millert 108: static int check_syntax __P((char *, int, int));
109: static int edit_sudoers __P((struct sudoersfile *, char *, char *, int));
110: static int install_sudoers __P((struct sudoersfile *, int));
111: static int print_unused __P((void *, void *));
112: static int reparse_sudoers __P((char *, char *, int, int));
113: static int run_command __P((char *, char **));
1.29 ! millert 114: static void print_selfref __P((char *, int, int, int));
! 115: static void print_undefined __P((char *, int, int, int));
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.25 millert 125: extern struct rbtree *aliases;
1.19 millert 126: extern FILE *yyin;
1.22 millert 127: extern char *sudoers, *errorfile;
128: extern int errorlineno, parse_error;
1.11 millert 129: /* For getopt(3) */
130: extern char *optarg;
131: extern int optind;
1.1 millert 132:
133: /*
134: * Globals
135: */
1.22 millert 136: int Argc;
1.1 millert 137: char **Argv;
1.22 millert 138: int num_interfaces;
139: struct interface *interfaces;
1.1 millert 140: struct sudo_user sudo_user;
1.22 millert 141: struct passwd *list_pw;
142: static struct sudoerslist {
143: struct sudoersfile *first, *last;
144: } sudoerslist;
1.25 millert 145: static struct rbtree *alias_freelist;
1.1 millert 146:
147: int
148: main(argc, argv)
149: int argc;
150: char **argv;
151: {
1.22 millert 152: struct sudoersfile *sp;
153: char *args, *editor, *sudoers_path;
154: int ch, checkonly, quiet, strict, oldperms;
1.24 millert 155: #if defined(SUDO_DEVEL) && defined(__OpenBSD__)
156: extern char *malloc_options;
157: malloc_options = "AFGJPR";
158: #endif
1.1 millert 159:
1.17 millert 160: Argv = argv;
161: if ((Argc = argc) < 1)
162: usage();
1.1 millert 163:
164: /*
165: * Arg handling.
166: */
1.22 millert 167: checkonly = oldperms = quiet = strict = FALSE;
168: sudoers_path = _PATH_SUDOERS;
1.11 millert 169: while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
170: switch (ch) {
171: case 'V':
1.27 millert 172: (void) printf("%s version %s\n", getprogname(), PACKAGE_VERSION);
1.11 millert 173: exit(0);
174: case 'c':
175: checkonly++; /* check mode */
176: break;
1.22 millert 177: case 'f':
178: sudoers_path = optarg; /* sudoers file path */
1.20 millert 179: oldperms = TRUE;
1.11 millert 180: break;
181: case 's':
1.22 millert 182: strict++; /* strict mode */
1.11 millert 183: break;
184: case 'q':
185: quiet++; /* quiet mode */
186: break;
187: default:
188: usage();
1.1 millert 189: }
190: }
1.11 millert 191: argc -= optind;
192: argv += optind;
193: if (argc)
194: usage();
1.1 millert 195:
1.22 millert 196: sudo_setpwent();
197: sudo_setgrent();
198:
1.1 millert 199: /* Mock up a fake sudo_user struct. */
1.25 millert 200: user_cmnd = "";
1.22 millert 201: if ((sudo_user.pw = sudo_getpwuid(getuid())) == NULL)
202: errorx(1, "you don't exist in the passwd database");
1.25 millert 203: get_hostname();
1.1 millert 204:
1.4 millert 205: /* Setup defaults data structures. */
206: init_defaults();
1.1 millert 207:
1.11 millert 208: if (checkonly)
1.22 millert 209: exit(check_syntax(sudoers_path, quiet, strict));
1.11 millert 210:
1.1 millert 211: /*
1.22 millert 212: * Parse the existing sudoers file(s) in quiet mode to highlight any
213: * existing errors and to pull in editor and env_editor conf values.
214: */
1.27 millert 215: if ((yyin = open_sudoers(sudoers_path, TRUE, NULL)) == NULL) {
1.22 millert 216: error(1, "%s", sudoers_path);
1.27 millert 217: }
1.22 millert 218: init_parser(sudoers_path, 0);
1.19 millert 219: yyparse();
1.22 millert 220: (void) update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER);
1.19 millert 221:
222: editor = get_editor(&args);
1.1 millert 223:
1.22 millert 224: /* Install signal handlers to clean up temp files if we are killed. */
225: setup_signals();
226:
227: /* Edit the sudoers file(s) */
228: tq_foreach_fwd(&sudoerslist, sp) {
1.27 millert 229: if (!sp->doedit)
230: continue;
1.22 millert 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.29 ! millert 431: if (yyparse() && !parse_error) {
1.22 millert 432: warningx("unabled to parse temporary file (%s), unknown error",
1.19 millert 433: sp->tpath);
434: parse_error = TRUE;
1.29 ! millert 435: errorfile = sp->path;
1.1 millert 436: }
1.19 millert 437: fclose(yyin);
1.29 ! millert 438: if (!parse_error && check_aliases(strict, quiet) != 0) {
1.22 millert 439: parse_error = TRUE;
1.29 ! millert 440: errorfile = sp->path;
! 441: }
1.1 millert 442:
443: /*
444: * Got an error, prompt the user for what to do now
445: */
1.19 millert 446: if (parse_error) {
1.1 millert 447: switch (whatnow()) {
1.7 millert 448: case 'Q' : parse_error = FALSE; /* ignore parse error */
1.1 millert 449: break;
1.22 millert 450: case 'x' : cleanup(0);
451: exit(0);
1.1 millert 452: break;
453: }
454: }
1.22 millert 455: if (parse_error) {
456: /* Edit file with the parse error */
457: tq_foreach_fwd(&sudoerslist, sp) {
458: if (errorfile == NULL || strcmp(sp->path, errorfile) == 0) {
459: edit_sudoers(sp, editor, args, errorlineno);
460: break;
461: }
462: }
463: if (sp == NULL)
464: errorx(1, "internal error, can't find %s in list!", sudoers);
465: }
466:
467: /* If any new #include directives were added, edit them too. */
468: for (sp = last->next; sp != NULL; sp = sp->next) {
469: printf("press return to edit %s: ", sp->path);
470: while ((ch = getchar()) != EOF && ch != '\n')
471: continue;
472: edit_sudoers(sp, editor, args, errorlineno);
473: }
1.19 millert 474: } while (parse_error);
1.22 millert 475:
476: return(TRUE);
1.19 millert 477: }
1.1 millert 478:
1.19 millert 479: /*
480: * Set the owner and mode on a sudoers temp file and
481: * move it into place. Returns TRUE on success, else FALSE.
482: */
1.22 millert 483: static int
1.20 millert 484: install_sudoers(sp, oldperms)
1.19 millert 485: struct sudoersfile *sp;
1.20 millert 486: int oldperms;
1.19 millert 487: {
1.20 millert 488: struct stat sb;
489:
1.1 millert 490: /*
491: * Change mode and ownership of temp file so when
1.19 millert 492: * we move it to sp->path things are kosher.
1.1 millert 493: */
1.20 millert 494: if (oldperms) {
495: /* Use perms of the existing file. */
496: #ifdef HAVE_FSTAT
497: if (fstat(sp->fd, &sb) == -1)
498: #else
499: if (stat(sp->path, &sb) == -1)
500: #endif
1.22 millert 501: error(1, "can't stat %s", sp->path);
1.20 millert 502: (void) chown(sp->tpath, sb.st_uid, sb.st_gid);
503: (void) chmod(sp->tpath, sb.st_mode & 0777);
504: } else {
505: if (chown(sp->tpath, SUDOERS_UID, SUDOERS_GID) != 0) {
1.22 millert 506: warning("unable to set (uid, gid) of %s to (%d, %d)",
1.20 millert 507: sp->tpath, SUDOERS_UID, SUDOERS_GID);
1.22 millert 508: return(FALSE);
1.20 millert 509: }
510: if (chmod(sp->tpath, SUDOERS_MODE) != 0) {
1.22 millert 511: warning("unable to change mode of %s to 0%o", sp->tpath,
512: SUDOERS_MODE);
513: return(FALSE);
1.20 millert 514: }
1.1 millert 515: }
516:
517: /*
1.19 millert 518: * Now that sp->tpath is sane (parses ok) it needs to be
519: * rename(2)'d to sp->path. If the rename(2) fails we try using
520: * mv(1) in case sp->tpath and sp->path are on different file systems.
1.1 millert 521: */
1.22 millert 522: if (rename(sp->tpath, sp->path) == 0) {
523: efree(sp->tpath);
524: sp->tpath = NULL;
525: } else {
1.1 millert 526: if (errno == EXDEV) {
1.19 millert 527: char *av[4];
1.22 millert 528: warningx("%s and %s not on the same file system, using mv to rename",
1.19 millert 529: sp->tpath, sp->path);
1.1 millert 530:
1.11 millert 531: /* Build up argument vector for the command */
532: if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
533: av[0]++;
534: else
535: av[0] = _PATH_MV;
1.19 millert 536: av[1] = sp->tpath;
537: av[2] = sp->path;
1.11 millert 538: av[3] = NULL;
1.1 millert 539:
1.11 millert 540: /* And run it... */
541: if (run_command(_PATH_MV, av)) {
1.22 millert 542: warningx("command failed: '%s %s %s', %s unchanged",
1.19 millert 543: _PATH_MV, sp->tpath, sp->path, sp->path);
1.22 millert 544: (void) unlink(sp->tpath);
545: efree(sp->tpath);
546: sp->tpath = NULL;
547: return(FALSE);
1.1 millert 548: }
1.22 millert 549: efree(sp->tpath);
550: sp->tpath = NULL;
1.1 millert 551: } else {
1.22 millert 552: warning("error renaming %s, %s unchanged", sp->tpath, sp->path);
553: (void) unlink(sp->tpath);
554: return(FALSE);
1.1 millert 555: }
556: }
557: return(TRUE);
558: }
559:
1.22 millert 560: /* STUB */
561: void
562: set_fqdn()
1.4 millert 563: {
1.22 millert 564: return;
1.4 millert 565: }
566:
1.22 millert 567: /* STUB */
568: void
569: init_envtables()
1.1 millert 570: {
1.22 millert 571: return;
1.1 millert 572: }
573:
1.22 millert 574: /* STUB */
1.1 millert 575: int
1.22 millert 576: user_is_exempt()
1.17 millert 577: {
1.22 millert 578: return(FALSE);
1.17 millert 579: }
580:
1.22 millert 581: /* STUB */
582: void
583: sudo_setspent()
1.1 millert 584: {
1.22 millert 585: return;
1.2 millert 586: }
587:
1.22 millert 588: /* STUB */
1.2 millert 589: void
1.22 millert 590: sudo_endspent()
1.2 millert 591: {
592: return;
1.1 millert 593: }
594:
1.22 millert 595: char *
596: sudo_getepw(pw)
597: const struct passwd *pw;
1.11 millert 598: {
1.22 millert 599: return (pw->pw_passwd);
1.11 millert 600: }
601:
1.1 millert 602: /*
603: * Assuming a parse error occurred, prompt the user for what they want
604: * to do now. Returns the first letter of their choice.
605: */
606: static char
607: whatnow()
608: {
609: int choice, c;
610:
611: for (;;) {
612: (void) fputs("What now? ", stdout);
613: choice = getchar();
614: for (c = choice; c != '\n' && c != EOF;)
615: c = getchar();
616:
1.3 millert 617: switch (choice) {
618: case EOF:
619: choice = 'x';
620: /* FALLTHROUGH */
621: case 'e':
622: case 'x':
623: case 'Q':
624: return(choice);
625: default:
626: (void) puts("Options are:");
627: (void) puts(" (e)dit sudoers file again");
628: (void) puts(" e(x)it without saving changes to sudoers file");
629: (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
1.1 millert 630: }
631: }
632: }
633:
634: /*
635: * Install signal handlers for visudo.
636: */
637: static void
638: setup_signals()
639: {
1.11 millert 640: sigaction_t sa;
1.1 millert 641:
642: /*
643: * Setup signal handlers to cleanup nicely.
644: */
1.22 millert 645: zero_bytes(&sa, sizeof(sa));
1.11 millert 646: sigemptyset(&sa.sa_mask);
647: sa.sa_flags = SA_RESTART;
1.22 millert 648: sa.sa_handler = quit;
1.11 millert 649: (void) sigaction(SIGTERM, &sa, NULL);
650: (void) sigaction(SIGHUP, &sa, NULL);
651: (void) sigaction(SIGINT, &sa, NULL);
652: (void) sigaction(SIGQUIT, &sa, NULL);
653: }
654:
655: static int
656: run_command(path, argv)
657: char *path;
658: char **argv;
659: {
660: int status;
1.21 millert 661: pid_t pid, rv;
1.11 millert 662:
663: switch (pid = fork()) {
664: case -1:
1.22 millert 665: error(1, "unable to run %s", path);
1.11 millert 666: break; /* NOTREACHED */
667: case 0:
1.22 millert 668: sudo_endpwent();
669: sudo_endgrent();
1.19 millert 670: closefrom(STDERR_FILENO + 1);
1.11 millert 671: execv(path, argv);
1.22 millert 672: warning("unable to run %s", path);
1.11 millert 673: _exit(127);
674: break; /* NOTREACHED */
675: }
676:
1.21 millert 677: do {
1.11 millert 678: #ifdef sudo_waitpid
1.21 millert 679: rv = sudo_waitpid(pid, &status, 0);
1.1 millert 680: #else
1.21 millert 681: rv = wait(&status);
1.11 millert 682: #endif
1.21 millert 683: } while (rv == -1 && errno == EINTR);
1.11 millert 684:
1.21 millert 685: if (rv == -1 || !WIFEXITED(status))
1.17 millert 686: return(-1);
687: return(WEXITSTATUS(status));
1.11 millert 688: }
689:
690: static int
1.22 millert 691: check_syntax(sudoers_path, quiet, strict)
692: char *sudoers_path;
693: int quiet;
694: int strict;
1.11 millert 695: {
1.22 millert 696: struct stat sb;
697: int error;
1.11 millert 698:
1.22 millert 699: if ((yyin = fopen(sudoers_path, "r")) == NULL) {
1.11 millert 700: if (!quiet)
1.22 millert 701: warning("unable to open %s", sudoers_path);
1.11 millert 702: exit(1);
703: }
1.22 millert 704: init_parser(sudoers_path, quiet);
1.29 ! millert 705: if (yyparse() && !parse_error) {
1.11 millert 706: if (!quiet)
1.22 millert 707: warningx("failed to parse %s file, unknown error", sudoers_path);
1.11 millert 708: parse_error = TRUE;
1.29 ! millert 709: errorfile = sudoers_path;
1.11 millert 710: }
1.29 ! millert 711: if (!parse_error && check_aliases(strict, quiet) != 0) {
! 712: parse_error = TRUE;
! 713: errorfile = sudoers_path;
1.25 millert 714: }
1.22 millert 715: error = parse_error;
716: if (!quiet) {
1.29 ! millert 717: if (parse_error) {
! 718: if (errorlineno != -1)
! 719: (void) printf("parse error in %s near line %d\n", errorfile,
! 720: errorlineno);
! 721: else
! 722: (void) printf("parse error in %s\n", errorfile);
! 723: } else {
1.22 millert 724: (void) printf("%s: parsed OK\n", sudoers_path);
1.29 ! millert 725: }
1.22 millert 726: }
727: /* Check mode and owner in strict mode. */
728: #ifdef HAVE_FSTAT
729: if (strict && fstat(fileno(yyin), &sb) == 0)
730: #else
731: if (strict && stat(sudoers_path, &sb) == 0)
732: #endif
733: {
734: if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID) {
735: error = TRUE;
736: if (!quiet) {
737: fprintf(stderr, "%s: wrong owner (uid, gid) should be (%d, %d)\n",
738: sudoers_path, SUDOERS_UID, SUDOERS_GID);
739: }
740: }
741: if ((sb.st_mode & 07777) != SUDOERS_MODE) {
742: error = TRUE;
743: if (!quiet) {
744: fprintf(stderr, "%s: bad permissions, should be mode 0%o\n",
745: sudoers_path, SUDOERS_MODE);
746: }
747: }
1.11 millert 748: }
749:
1.22 millert 750: return(error);
1.1 millert 751: }
752:
1.22 millert 753: /*
754: * Used to open (and lock) the initial sudoers file and to also open
755: * any subsequent files #included via a callback from the parser.
756: */
757: FILE *
1.27 millert 758: open_sudoers(path, doedit, keepopen)
1.22 millert 759: const char *path;
1.27 millert 760: int doedit;
1.22 millert 761: int *keepopen;
1.19 millert 762: {
1.22 millert 763: struct sudoersfile *entry;
1.19 millert 764: FILE *fp;
765:
1.22 millert 766: /* Check for existing entry */
767: tq_foreach_fwd(&sudoerslist, entry) {
768: if (strcmp(path, entry->path) == 0)
769: break;
770: }
771: if (entry == NULL) {
772: entry = emalloc(sizeof(*entry));
773: entry->path = estrdup(path);
774: entry->modified = 0;
775: entry->next = NULL;
776: entry->fd = open(entry->path, O_RDWR | O_CREAT, SUDOERS_MODE);
777: entry->tpath = NULL;
1.27 millert 778: entry->tfd = -1;
779: entry->doedit = doedit;
1.22 millert 780: if (entry->fd == -1) {
781: warning("%s", entry->path);
782: efree(entry);
783: return(NULL);
784: }
785: if (!lock_file(entry->fd, SUDO_TLOCK))
786: errorx(1, "%s busy, try again later", entry->path);
787: if ((fp = fdopen(entry->fd, "r")) == NULL)
788: error(1, "%s", entry->path);
789: /* XXX - macro here? */
790: if (sudoerslist.last == NULL)
791: sudoerslist.first = sudoerslist.last = entry;
792: else {
793: sudoerslist.last->next = entry;
794: sudoerslist.last = entry;
795: }
796: } else {
797: /* Already exists, open .tmp version if there is one. */
798: if (entry->tpath != NULL) {
799: if ((fp = fopen(entry->tpath, "r")) == NULL)
800: error(1, "%s", entry->tpath);
801: } else {
802: if ((fp = fdopen(entry->fd, "r")) == NULL)
803: error(1, "%s", entry->path);
1.26 millert 804: rewind(fp);
1.19 millert 805: }
806: }
1.26 millert 807: if (keepopen != NULL)
808: *keepopen = TRUE;
1.19 millert 809: return(fp);
810: }
811:
812: static char *
813: get_editor(args)
814: char **args;
815: {
816: char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
817:
818: /*
819: * Check VISUAL and EDITOR environment variables to see which editor
820: * the user wants to use (we may not end up using it though).
821: * If the path is not fully-qualified, make it so and check that
822: * the specified executable actually exists.
823: */
824: UserEditorArgs = NULL;
825: if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
826: UserEditor = getenv("EDITOR");
827: if (UserEditor && *UserEditor == '\0')
828: UserEditor = NULL;
829: else if (UserEditor) {
830: UserEditorArgs = get_args(UserEditor);
831: if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
832: UserEditor = Editor;
833: } else {
834: if (def_env_editor) {
835: /* If we are honoring $EDITOR this is a fatal error. */
1.22 millert 836: errorx(1, "specified editor (%s) doesn't exist!", UserEditor);
1.19 millert 837: } else {
838: /* Otherwise, just ignore $EDITOR. */
839: UserEditor = NULL;
840: }
841: }
842: }
843:
844: /*
845: * See if we can use the user's choice of editors either because
846: * we allow any $EDITOR or because $EDITOR is in the allowable list.
847: */
848: Editor = EditorArgs = EditorPath = NULL;
849: if (def_env_editor && UserEditor) {
850: Editor = UserEditor;
851: EditorArgs = UserEditorArgs;
852: } else if (UserEditor) {
853: struct stat editor_sb;
854: struct stat user_editor_sb;
855: char *base, *userbase;
856:
857: if (stat(UserEditor, &user_editor_sb) != 0) {
858: /* Should never happen since we already checked above. */
1.22 millert 859: error(1, "unable to stat editor (%s)", UserEditor);
1.19 millert 860: }
861: EditorPath = estrdup(def_editor);
862: Editor = strtok(EditorPath, ":");
863: do {
864: EditorArgs = get_args(Editor);
865: /*
866: * Both Editor and UserEditor should be fully qualified but
867: * check anyway...
868: */
869: if ((base = strrchr(Editor, '/')) == NULL)
870: continue;
871: if ((userbase = strrchr(UserEditor, '/')) == NULL) {
872: Editor = NULL;
873: break;
874: }
875: base++, userbase++;
876:
877: /*
878: * We compare the basenames first and then use stat to match
879: * for sure.
880: */
881: if (strcmp(base, userbase) == 0) {
882: if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
883: && (editor_sb.st_mode & 0000111) &&
884: editor_sb.st_dev == user_editor_sb.st_dev &&
885: editor_sb.st_ino == user_editor_sb.st_ino)
886: break;
887: }
888: } while ((Editor = strtok(NULL, ":")));
889: }
890:
891: /*
892: * Can't use $EDITOR, try each element of def_editor until we
893: * find one that exists, is regular, and is executable.
894: */
895: if (Editor == NULL || *Editor == '\0') {
896: efree(EditorPath);
897: EditorPath = estrdup(def_editor);
898: Editor = strtok(EditorPath, ":");
899: do {
900: EditorArgs = get_args(Editor);
901: if (sudo_goodpath(Editor, NULL))
902: break;
903: } while ((Editor = strtok(NULL, ":")));
904:
905: /* Bleah, none of the editors existed! */
1.22 millert 906: if (Editor == NULL || *Editor == '\0')
907: errorx(1, "no editor found (editor path = %s)", def_editor);
1.19 millert 908: }
909: *args = EditorArgs;
910: return(Editor);
911: }
912:
913: /*
914: * Split out any command line arguments and return them.
915: */
916: static char *
917: get_args(cmnd)
918: char *cmnd;
919: {
920: char *args;
921:
922: args = cmnd;
923: while (*args && !isblank((unsigned char) *args))
924: args++;
925: if (*args) {
926: *args++ = '\0';
927: while (*args && isblank((unsigned char) *args))
928: args++;
929: }
930: return(*args ? args : NULL);
931: }
932:
1.1 millert 933: /*
1.25 millert 934: * Look up the hostname and set user_host and user_shost.
935: */
936: static void
937: get_hostname()
938: {
939: char *p, thost[MAXHOSTNAMELEN + 1];
940:
941: if (gethostname(thost, sizeof(thost)) != 0) {
942: user_host = user_shost = "localhost";
943: return;
944: }
945: thost[sizeof(thost) - 1] = '\0';
946: user_host = estrdup(thost);
947:
948: if ((p = strchr(user_host, '.'))) {
949: *p = '\0';
950: user_shost = estrdup(user_host);
951: *p = '.';
952: } else {
953: user_shost = user_host;
954: }
955: }
956:
1.29 ! millert 957: static int
! 958: alias_remove_recursive(name, type, strict, quiet)
1.25 millert 959: char *name;
960: int type;
1.29 ! millert 961: int strict;
! 962: int quiet;
1.25 millert 963: {
964: struct member *m;
965: struct alias *a;
1.29 ! millert 966: int error = 0;
1.25 millert 967:
968: if ((a = alias_find(name, type)) != NULL) {
969: tq_foreach_fwd(&a->members, m) {
970: if (m->type == ALIAS) {
1.29 ! millert 971: if (strcmp(name, m->name) == 0) {
! 972: print_selfref(m->name, type, strict, quiet);
! 973: error = 1;
! 974: } else {
! 975: if (!alias_remove_recursive(m->name, type, strict, quiet))
! 976: error = 1;
! 977: }
1.25 millert 978: }
979: }
980: }
981: alias_seqno++;
982: a = alias_remove(name, type);
983: if (a)
984: rbinsert(alias_freelist, a);
1.29 ! millert 985: return(error);
1.25 millert 986: }
987:
988: /*
1.22 millert 989: * Iterate through the sudoers datastructures looking for undefined
990: * aliases or unused aliases.
1.1 millert 991: */
1.22 millert 992: static int
1.25 millert 993: check_aliases(strict, quiet)
1.22 millert 994: int strict;
1.25 millert 995: int quiet;
1.1 millert 996: {
1.22 millert 997: struct cmndspec *cs;
1.24 millert 998: struct member *m, *binding;
1.22 millert 999: struct privilege *priv;
1000: struct userspec *us;
1.24 millert 1001: struct defaults *d;
1002: int atype, error = 0;
1.22 millert 1003:
1.25 millert 1004: alias_freelist = rbcreate(alias_compare);
1005:
1.22 millert 1006: /* Forward check. */
1007: tq_foreach_fwd(&userspecs, us) {
1008: tq_foreach_fwd(&us->users, m) {
1.23 millert 1009: if (m->type == ALIAS) {
1010: alias_seqno++;
1.25 millert 1011: if (alias_find(m->name, USERALIAS) == NULL) {
1012: print_undefined(m->name, USERALIAS, strict, quiet);
1.22 millert 1013: error++;
1014: }
1015: }
1016: }
1017: tq_foreach_fwd(&us->privileges, priv) {
1018: tq_foreach_fwd(&priv->hostlist, m) {
1.23 millert 1019: if (m->type == ALIAS) {
1020: alias_seqno++;
1.25 millert 1021: if (alias_find(m->name, HOSTALIAS) == NULL) {
1022: print_undefined(m->name, HOSTALIAS, strict, quiet);
1.22 millert 1023: error++;
1024: }
1025: }
1026: }
1027: tq_foreach_fwd(&priv->cmndlist, cs) {
1028: tq_foreach_fwd(&cs->runasuserlist, m) {
1.23 millert 1029: if (m->type == ALIAS) {
1030: alias_seqno++;
1.25 millert 1031: if (alias_find(m->name, RUNASALIAS) == NULL) {
1032: print_undefined(m->name, RUNASALIAS, strict, quiet);
1.22 millert 1033: error++;
1034: }
1035: }
1036: }
1.23 millert 1037: if ((m = cs->cmnd)->type == ALIAS) {
1038: alias_seqno++;
1.25 millert 1039: if (alias_find(m->name, CMNDALIAS) == NULL) {
1040: print_undefined(m->name, CMNDALIAS, strict, quiet);
1.22 millert 1041: error++;
1042: }
1043: }
1044: }
1045: }
1046: }
1047:
1048: /* Reverse check (destructive) */
1049: tq_foreach_fwd(&userspecs, us) {
1050: tq_foreach_fwd(&us->users, m) {
1.25 millert 1051: if (m->type == ALIAS) {
1.29 ! millert 1052: if (!alias_remove_recursive(m->name, USERALIAS, strict, quiet))
! 1053: error++;
1.25 millert 1054: }
1.22 millert 1055: }
1056: tq_foreach_fwd(&us->privileges, priv) {
1057: tq_foreach_fwd(&priv->hostlist, m) {
1.23 millert 1058: if (m->type == ALIAS)
1.29 ! millert 1059: if (!alias_remove_recursive(m->name, HOSTALIAS, strict,
! 1060: quiet))
! 1061: error++;
1.22 millert 1062: }
1063: tq_foreach_fwd(&priv->cmndlist, cs) {
1064: tq_foreach_fwd(&cs->runasuserlist, m) {
1.23 millert 1065: if (m->type == ALIAS)
1.29 ! millert 1066: if (!alias_remove_recursive(m->name, RUNASALIAS,
! 1067: strict, quiet))
! 1068: error++;
1.22 millert 1069: }
1.23 millert 1070: if ((m = cs->cmnd)->type == ALIAS)
1.29 ! millert 1071: if (!alias_remove_recursive(m->name, CMNDALIAS, strict,
! 1072: quiet))
! 1073: error++;
1.22 millert 1074: }
1075: }
1076: }
1.24 millert 1077: tq_foreach_fwd(&defaults, d) {
1078: switch (d->type) {
1079: case DEFAULTS_HOST:
1080: atype = HOSTALIAS;
1081: break;
1082: case DEFAULTS_USER:
1083: atype = USERALIAS;
1084: break;
1085: case DEFAULTS_RUNAS:
1086: atype = RUNASALIAS;
1087: break;
1088: case DEFAULTS_CMND:
1089: atype = CMNDALIAS;
1090: break;
1091: default:
1092: continue; /* not an alias */
1093: }
1094: tq_foreach_fwd(&d->binding, binding) {
1095: for (m = binding; m != NULL; m = m->next) {
1096: if (m->type == ALIAS)
1.29 ! millert 1097: if (!alias_remove_recursive(m->name, atype, strict, quiet))
! 1098: error++;
1.24 millert 1099: }
1100: }
1101: }
1.25 millert 1102: rbdestroy(alias_freelist, alias_free);
1.24 millert 1103:
1.22 millert 1104: /* If all aliases were referenced we will have an empty tree. */
1.29 ! millert 1105: if (!no_aliases() && !quiet)
1.25 millert 1106: alias_apply(print_unused, strict ? "Error" : "Warning");
1.29 ! millert 1107:
! 1108: return (strict ? error : 0);
1.25 millert 1109: }
1110:
1111: static void
1112: print_undefined(name, type, strict, quiet)
1113: char *name;
1114: int type;
1115: int strict;
1116: int quiet;
1117: {
1118: if (!quiet) {
1119: warningx("%s: %s_Alias `%s' referenced but not defined",
1.29 ! millert 1120: strict ? "Error" : "Warning",
! 1121: type == HOSTALIAS ? "Host" : type == CMNDALIAS ? "Cmnd" :
! 1122: type == USERALIAS ? "User" : type == RUNASALIAS ? "Runas" :
! 1123: "Unknown", name);
! 1124: }
! 1125: }
! 1126:
! 1127: static void
! 1128: print_selfref(name, type, strict, quiet)
! 1129: char *name;
! 1130: int type;
! 1131: int strict;
! 1132: int quiet;
! 1133: {
! 1134: if (!quiet) {
! 1135: warningx("%s: %s_Alias `%s' references self",
1.25 millert 1136: strict ? "Error" : "Warning",
1137: type == HOSTALIAS ? "Host" : type == CMNDALIAS ? "Cmnd" :
1138: type == USERALIAS ? "User" : type == RUNASALIAS ? "Runas" :
1139: "Unknown", name);
1140: }
1.22 millert 1141: }
1142:
1143: static int
1144: print_unused(v1, v2)
1145: void *v1;
1146: void *v2;
1147: {
1148: struct alias *a = (struct alias *)v1;
1149: char *prefix = (char *)v2;
1150:
1151: warningx("%s: unused %s_Alias %s", prefix,
1152: a->type == HOSTALIAS ? "Host" : a->type == CMNDALIAS ? "Cmnd" :
1153: a->type == USERALIAS ? "User" : a->type == RUNASALIAS ? "Runas" :
1154: "Unknown", a->name);
1155: return(0);
1156: }
1.10 millert 1157:
1.22 millert 1158: /*
1159: * Unlink any sudoers temp files that remain.
1160: */
1161: void
1162: cleanup(gotsignal)
1163: int gotsignal;
1164: {
1165: struct sudoersfile *sp;
1.1 millert 1166:
1.22 millert 1167: tq_foreach_fwd(&sudoerslist, sp) {
1168: if (sp->tpath != NULL)
1169: (void) unlink(sp->tpath);
1.10 millert 1170: }
1.22 millert 1171: if (!gotsignal) {
1172: sudo_endpwent();
1173: sudo_endgrent();
1174: }
1175: }
1176:
1177: /*
1178: * Unlink sudoers temp files (if any) and exit.
1179: */
1180: static RETSIGTYPE
1181: quit(signo)
1182: int signo;
1183: {
1184: cleanup(signo);
1185: #define emsg " exiting due to signal.\n"
1186: write(STDERR_FILENO, getprogname(), strlen(getprogname()));
1187: write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
1188: _exit(signo);
1.1 millert 1189: }
1190:
1191: static void
1192: usage()
1193: {
1.19 millert 1194: (void) fprintf(stderr, "usage: %s [-c] [-q] [-s] [-V] [-f sudoers]\n",
1.14 millert 1195: getprogname());
1.1 millert 1196: exit(1);
1197: }