Annotation of src/usr.bin/sudo/visudo.c, Revision 1.27
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:
89: #ifndef lint
1.27 ! millert 90: __unused static const char rcsid[] = "$Sudo: visudo.c,v 1.234 2009/05/25 12:02:42 millert Exp $";
1.1 millert 91: #endif /* lint */
92:
1.19 millert 93: struct sudoersfile {
94: char *path;
1.27 ! millert 95: char *tpath;
1.22 millert 96: int fd;
97: int tfd;
98: int modified;
1.27 ! millert 99: int doedit;
1.22 millert 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.27 ! millert 175: (void) printf("%s version %s\n", getprogname(), PACKAGE_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: */
1.27 ! millert 218: if ((yyin = open_sudoers(sudoers_path, TRUE, NULL)) == NULL) {
1.22 millert 219: error(1, "%s", sudoers_path);
1.27 ! millert 220: }
1.22 millert 221: init_parser(sudoers_path, 0);
1.19 millert 222: yyparse();
1.22 millert 223: (void) update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER);
1.19 millert 224:
225: editor = get_editor(&args);
1.1 millert 226:
1.22 millert 227: /* Install signal handlers to clean up temp files if we are killed. */
228: setup_signals();
229:
230: /* Edit the sudoers file(s) */
231: tq_foreach_fwd(&sudoerslist, sp) {
1.27 ! millert 232: if (!sp->doedit)
! 233: continue;
1.22 millert 234: if (sp != tq_first(&sudoerslist)) {
235: printf("press return to edit %s: ", sp->path);
236: while ((ch = getchar()) != EOF && ch != '\n')
237: continue;
238: }
239: edit_sudoers(sp, editor, args, -1);
240: }
241:
242: /* Check edited files for a parse error and re-edit any that fail. */
243: reparse_sudoers(editor, args, strict, quiet);
244:
245: /* Install the sudoers temp files. */
246: tq_foreach_fwd(&sudoerslist, sp) {
247: if (!sp->modified)
248: (void) unlink(sp->tpath);
249: else
250: (void) install_sudoers(sp, oldperms);
251: }
1.3 millert 252:
1.19 millert 253: exit(0);
254: }
1.1 millert 255:
1.19 millert 256: /*
1.22 millert 257: * Edit each sudoers file.
1.19 millert 258: * Returns TRUE on success, else FALSE.
259: */
1.22 millert 260: static int
1.19 millert 261: edit_sudoers(sp, editor, args, lineno)
262: struct sudoersfile *sp;
263: char *editor, *args;
264: int lineno;
265: {
1.22 millert 266: int tfd; /* sudoers temp file descriptor */
267: int modified; /* was the file modified? */
1.19 millert 268: int ac; /* argument count */
269: char **av; /* argument vector for run_command */
270: char *cp; /* scratch char pointer */
1.22 millert 271: char buf[PATH_MAX*2]; /* buffer used for copying files */
1.19 millert 272: char linestr[64]; /* string version of lineno */
273: struct timespec ts1, ts2; /* time before and after edit */
1.22 millert 274: struct timespec orig_mtim; /* starting mtime of sudoers file */
275: off_t orig_size; /* starting size of sudoers file */
276: ssize_t nread; /* number of bytes read */
1.19 millert 277: struct stat sb; /* stat buffer */
1.1 millert 278:
1.22 millert 279: #ifdef HAVE_FSTAT
280: if (fstat(sp->fd, &sb) == -1)
281: #else
282: if (stat(sp->path, &sb) == -1)
283: #endif
284: error(1, "can't stat %s", sp->path);
285: orig_size = sb.st_size;
286: orig_mtim.tv_sec = mtim_getsec(sb);
287: orig_mtim.tv_nsec = mtim_getnsec(sb);
288:
289: /* Create the temp file if needed and set timestamp. */
290: if (sp->tpath == NULL) {
291: easprintf(&sp->tpath, "%s.tmp", sp->path);
292: tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
293: if (tfd < 0)
294: error(1, "%s", sp->tpath);
295:
296: /* Copy sp->path -> sp->tpath and reset the mtime. */
297: if (orig_size != 0) {
298: (void) lseek(sp->fd, (off_t)0, SEEK_SET);
299: while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
300: if (write(tfd, buf, nread) != nread)
301: error(1, "write error");
302:
303: /* Add missing newline at EOF if needed. */
304: if (nread > 0 && buf[nread - 1] != '\n') {
305: buf[0] = '\n';
306: write(tfd, buf, 1);
307: }
308: }
309: (void) close(tfd);
310: }
311: (void) touch(-1, sp->tpath, &orig_mtim);
1.13 millert 312:
1.19 millert 313: /* Find the length of the argument vector */
314: ac = 3 + (lineno > 0);
315: if (args) {
316: int wasblank;
317:
318: ac++;
319: for (wasblank = FALSE, cp = args; *cp; cp++) {
320: if (isblank((unsigned char) *cp))
321: wasblank = TRUE;
322: else if (wasblank) {
323: wasblank = FALSE;
324: ac++;
325: }
326: }
327: }
328:
329: /* Build up argument vector for the command */
330: av = emalloc2(ac, sizeof(char *));
331: if ((av[0] = strrchr(editor, '/')) != NULL)
332: av[0]++;
333: else
334: av[0] = editor;
335: ac = 1;
336: if (lineno > 0) {
337: (void) snprintf(linestr, sizeof(linestr), "+%d", lineno);
338: av[ac++] = linestr;
339: }
340: if (args) {
341: for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t")))
342: av[ac++] = cp;
343: }
344: av[ac++] = sp->tpath;
345: av[ac++] = NULL;
1.1 millert 346:
347: /*
1.19 millert 348: * Do the edit:
349: * We cannot check the editor's exit value against 0 since
350: * XPG4 specifies that vi's exit value is a function of the
351: * number of errors during editing (?!?!).
1.4 millert 352: */
1.19 millert 353: gettime(&ts1);
354: if (run_command(editor, av) != -1) {
355: gettime(&ts2);
356: /*
357: * Sanity checks.
358: */
359: if (stat(sp->tpath, &sb) < 0) {
1.22 millert 360: warningx("cannot stat temporary file (%s), %s unchanged",
1.19 millert 361: sp->tpath, sp->path);
1.22 millert 362: return(FALSE);
1.11 millert 363: }
1.22 millert 364: if (sb.st_size == 0 && orig_size != 0) {
365: warningx("zero length temporary file (%s), %s unchanged",
1.19 millert 366: sp->tpath, sp->path);
1.22 millert 367: sp->modified = TRUE;
368: return(FALSE);
1.11 millert 369: }
1.19 millert 370: } else {
1.22 millert 371: warningx("editor (%s) failed, %s unchanged", editor, sp->path);
372: return(FALSE);
1.11 millert 373: }
374:
1.22 millert 375: /* Set modified bit if use changed the file. */
376: modified = TRUE;
377: if (orig_size == sb.st_size &&
378: orig_mtim.tv_sec == mtim_getsec(sb) &&
379: orig_mtim.tv_nsec == mtim_getnsec(sb)) {
1.19 millert 380: /*
381: * If mtime and size match but the user spent no measurable
382: * time in the editor we can't tell if the file was changed.
383: */
384: #ifdef HAVE_TIMESPECSUB2
385: timespecsub(&ts1, &ts2);
386: #else
387: timespecsub(&ts1, &ts2, &ts2);
388: #endif
1.22 millert 389: if (timespecisset(&ts2))
390: modified = FALSE;
1.11 millert 391: }
1.22 millert 392:
393: /*
394: * If modified in this edit session, mark as modified.
395: */
396: if (modified)
397: sp->modified = modified;
398: else
399: warningx("%s unchanged", sp->tpath);
400:
401: return(TRUE);
1.19 millert 402: }
403:
404: /*
405: * Parse sudoers after editing and re-edit any ones that caused a parse error.
406: * Returns TRUE on success, else FALSE.
407: */
1.22 millert 408: static int
409: reparse_sudoers(editor, args, strict, quiet)
1.19 millert 410: char *editor, *args;
1.22 millert 411: int strict, quiet;
1.19 millert 412: {
1.22 millert 413: struct sudoersfile *sp, *last;
414: FILE *fp;
1.19 millert 415: int ch;
1.4 millert 416:
417: /*
1.22 millert 418: * Parse the edited sudoers files and do sanity checking
1.1 millert 419: */
420: do {
1.22 millert 421: sp = tq_first(&sudoerslist);
422: last = tq_last(&sudoerslist);
423: fp = fopen(sp->tpath, "r+");
424: if (fp == NULL)
425: errorx(1, "can't re-open temporary file (%s), %s unchanged.",
1.19 millert 426: sp->tpath, sp->path);
427:
428: /* Clean slate for each parse */
429: init_defaults();
1.22 millert 430: init_parser(sp->path, quiet);
1.19 millert 431:
432: /* Parse the sudoers temp file */
1.22 millert 433: yyrestart(fp);
1.19 millert 434: if (yyparse() && parse_error != TRUE) {
1.22 millert 435: warningx("unabled to parse temporary file (%s), unknown error",
1.19 millert 436: sp->tpath);
437: parse_error = TRUE;
1.1 millert 438: }
1.19 millert 439: fclose(yyin);
1.25 millert 440: if (check_aliases(strict, quiet) != 0)
1.22 millert 441: parse_error = TRUE;
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.11 millert 705: if (yyparse() && parse_error != TRUE) {
706: if (!quiet)
1.22 millert 707: warningx("failed to parse %s file, unknown error", sudoers_path);
1.11 millert 708: parse_error = TRUE;
709: }
1.25 millert 710: if (!parse_error) {
711: if (check_aliases(strict, quiet) != 0)
712: parse_error = TRUE;
713: }
1.22 millert 714: error = parse_error;
715: if (!quiet) {
1.11 millert 716: if (parse_error)
1.26 millert 717: (void) printf("parse error in %s near line %d\n", errorfile,
1.11 millert 718: errorlineno);
719: else
1.22 millert 720: (void) printf("%s: parsed OK\n", sudoers_path);
721: }
722: /* Check mode and owner in strict mode. */
723: #ifdef HAVE_FSTAT
724: if (strict && fstat(fileno(yyin), &sb) == 0)
725: #else
726: if (strict && stat(sudoers_path, &sb) == 0)
727: #endif
728: {
729: if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID) {
730: error = TRUE;
731: if (!quiet) {
732: fprintf(stderr, "%s: wrong owner (uid, gid) should be (%d, %d)\n",
733: sudoers_path, SUDOERS_UID, SUDOERS_GID);
734: }
735: }
736: if ((sb.st_mode & 07777) != SUDOERS_MODE) {
737: error = TRUE;
738: if (!quiet) {
739: fprintf(stderr, "%s: bad permissions, should be mode 0%o\n",
740: sudoers_path, SUDOERS_MODE);
741: }
742: }
1.11 millert 743: }
744:
1.22 millert 745: return(error);
1.1 millert 746: }
747:
1.22 millert 748: /*
749: * Used to open (and lock) the initial sudoers file and to also open
750: * any subsequent files #included via a callback from the parser.
751: */
752: FILE *
1.27 ! millert 753: open_sudoers(path, doedit, keepopen)
1.22 millert 754: const char *path;
1.27 ! millert 755: int doedit;
1.22 millert 756: int *keepopen;
1.19 millert 757: {
1.22 millert 758: struct sudoersfile *entry;
1.19 millert 759: FILE *fp;
760:
1.22 millert 761: /* Check for existing entry */
762: tq_foreach_fwd(&sudoerslist, entry) {
763: if (strcmp(path, entry->path) == 0)
764: break;
765: }
766: if (entry == NULL) {
767: entry = emalloc(sizeof(*entry));
768: entry->path = estrdup(path);
769: entry->modified = 0;
770: entry->next = NULL;
771: entry->fd = open(entry->path, O_RDWR | O_CREAT, SUDOERS_MODE);
772: entry->tpath = NULL;
1.27 ! millert 773: entry->tfd = -1;
! 774: entry->doedit = doedit;
1.22 millert 775: if (entry->fd == -1) {
776: warning("%s", entry->path);
777: efree(entry);
778: return(NULL);
779: }
780: if (!lock_file(entry->fd, SUDO_TLOCK))
781: errorx(1, "%s busy, try again later", entry->path);
782: if ((fp = fdopen(entry->fd, "r")) == NULL)
783: error(1, "%s", entry->path);
784: /* XXX - macro here? */
785: if (sudoerslist.last == NULL)
786: sudoerslist.first = sudoerslist.last = entry;
787: else {
788: sudoerslist.last->next = entry;
789: sudoerslist.last = entry;
790: }
791: } else {
792: /* Already exists, open .tmp version if there is one. */
793: if (entry->tpath != NULL) {
794: if ((fp = fopen(entry->tpath, "r")) == NULL)
795: error(1, "%s", entry->tpath);
796: } else {
797: if ((fp = fdopen(entry->fd, "r")) == NULL)
798: error(1, "%s", entry->path);
1.26 millert 799: rewind(fp);
1.19 millert 800: }
801: }
1.26 millert 802: if (keepopen != NULL)
803: *keepopen = TRUE;
1.19 millert 804: return(fp);
805: }
806:
807: static char *
808: get_editor(args)
809: char **args;
810: {
811: char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
812:
813: /*
814: * Check VISUAL and EDITOR environment variables to see which editor
815: * the user wants to use (we may not end up using it though).
816: * If the path is not fully-qualified, make it so and check that
817: * the specified executable actually exists.
818: */
819: UserEditorArgs = NULL;
820: if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
821: UserEditor = getenv("EDITOR");
822: if (UserEditor && *UserEditor == '\0')
823: UserEditor = NULL;
824: else if (UserEditor) {
825: UserEditorArgs = get_args(UserEditor);
826: if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
827: UserEditor = Editor;
828: } else {
829: if (def_env_editor) {
830: /* If we are honoring $EDITOR this is a fatal error. */
1.22 millert 831: errorx(1, "specified editor (%s) doesn't exist!", UserEditor);
1.19 millert 832: } else {
833: /* Otherwise, just ignore $EDITOR. */
834: UserEditor = NULL;
835: }
836: }
837: }
838:
839: /*
840: * See if we can use the user's choice of editors either because
841: * we allow any $EDITOR or because $EDITOR is in the allowable list.
842: */
843: Editor = EditorArgs = EditorPath = NULL;
844: if (def_env_editor && UserEditor) {
845: Editor = UserEditor;
846: EditorArgs = UserEditorArgs;
847: } else if (UserEditor) {
848: struct stat editor_sb;
849: struct stat user_editor_sb;
850: char *base, *userbase;
851:
852: if (stat(UserEditor, &user_editor_sb) != 0) {
853: /* Should never happen since we already checked above. */
1.22 millert 854: error(1, "unable to stat editor (%s)", UserEditor);
1.19 millert 855: }
856: EditorPath = estrdup(def_editor);
857: Editor = strtok(EditorPath, ":");
858: do {
859: EditorArgs = get_args(Editor);
860: /*
861: * Both Editor and UserEditor should be fully qualified but
862: * check anyway...
863: */
864: if ((base = strrchr(Editor, '/')) == NULL)
865: continue;
866: if ((userbase = strrchr(UserEditor, '/')) == NULL) {
867: Editor = NULL;
868: break;
869: }
870: base++, userbase++;
871:
872: /*
873: * We compare the basenames first and then use stat to match
874: * for sure.
875: */
876: if (strcmp(base, userbase) == 0) {
877: if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
878: && (editor_sb.st_mode & 0000111) &&
879: editor_sb.st_dev == user_editor_sb.st_dev &&
880: editor_sb.st_ino == user_editor_sb.st_ino)
881: break;
882: }
883: } while ((Editor = strtok(NULL, ":")));
884: }
885:
886: /*
887: * Can't use $EDITOR, try each element of def_editor until we
888: * find one that exists, is regular, and is executable.
889: */
890: if (Editor == NULL || *Editor == '\0') {
891: efree(EditorPath);
892: EditorPath = estrdup(def_editor);
893: Editor = strtok(EditorPath, ":");
894: do {
895: EditorArgs = get_args(Editor);
896: if (sudo_goodpath(Editor, NULL))
897: break;
898: } while ((Editor = strtok(NULL, ":")));
899:
900: /* Bleah, none of the editors existed! */
1.22 millert 901: if (Editor == NULL || *Editor == '\0')
902: errorx(1, "no editor found (editor path = %s)", def_editor);
1.19 millert 903: }
904: *args = EditorArgs;
905: return(Editor);
906: }
907:
908: /*
909: * Split out any command line arguments and return them.
910: */
911: static char *
912: get_args(cmnd)
913: char *cmnd;
914: {
915: char *args;
916:
917: args = cmnd;
918: while (*args && !isblank((unsigned char) *args))
919: args++;
920: if (*args) {
921: *args++ = '\0';
922: while (*args && isblank((unsigned char) *args))
923: args++;
924: }
925: return(*args ? args : NULL);
926: }
927:
1.1 millert 928: /*
1.25 millert 929: * Look up the hostname and set user_host and user_shost.
930: */
931: static void
932: get_hostname()
933: {
934: char *p, thost[MAXHOSTNAMELEN + 1];
935:
936: if (gethostname(thost, sizeof(thost)) != 0) {
937: user_host = user_shost = "localhost";
938: return;
939: }
940: thost[sizeof(thost) - 1] = '\0';
941: user_host = estrdup(thost);
942:
943: if ((p = strchr(user_host, '.'))) {
944: *p = '\0';
945: user_shost = estrdup(user_host);
946: *p = '.';
947: } else {
948: user_shost = user_host;
949: }
950: }
951:
952: static void
953: alias_remove_recursive(name, type)
954: char *name;
955: int type;
956: {
957: struct member *m;
958: struct alias *a;
959:
960: if ((a = alias_find(name, type)) != NULL) {
961: tq_foreach_fwd(&a->members, m) {
962: if (m->type == ALIAS) {
963: alias_remove_recursive(m->name, type);
964: }
965: }
966: }
967: alias_seqno++;
968: a = alias_remove(name, type);
969: if (a)
970: rbinsert(alias_freelist, a);
971: }
972:
973: /*
1.22 millert 974: * Iterate through the sudoers datastructures looking for undefined
975: * aliases or unused aliases.
1.1 millert 976: */
1.22 millert 977: static int
1.25 millert 978: check_aliases(strict, quiet)
1.22 millert 979: int strict;
1.25 millert 980: int quiet;
1.1 millert 981: {
1.22 millert 982: struct cmndspec *cs;
1.24 millert 983: struct member *m, *binding;
1.22 millert 984: struct privilege *priv;
985: struct userspec *us;
1.24 millert 986: struct defaults *d;
987: int atype, error = 0;
1.22 millert 988:
1.25 millert 989: alias_freelist = rbcreate(alias_compare);
990:
1.22 millert 991: /* Forward check. */
992: tq_foreach_fwd(&userspecs, us) {
993: tq_foreach_fwd(&us->users, m) {
1.23 millert 994: if (m->type == ALIAS) {
995: alias_seqno++;
1.25 millert 996: if (alias_find(m->name, USERALIAS) == NULL) {
997: print_undefined(m->name, USERALIAS, strict, quiet);
1.22 millert 998: error++;
999: }
1000: }
1001: }
1002: tq_foreach_fwd(&us->privileges, priv) {
1003: tq_foreach_fwd(&priv->hostlist, m) {
1.23 millert 1004: if (m->type == ALIAS) {
1005: alias_seqno++;
1.25 millert 1006: if (alias_find(m->name, HOSTALIAS) == NULL) {
1007: print_undefined(m->name, HOSTALIAS, strict, quiet);
1.22 millert 1008: error++;
1009: }
1010: }
1011: }
1012: tq_foreach_fwd(&priv->cmndlist, cs) {
1013: tq_foreach_fwd(&cs->runasuserlist, m) {
1.23 millert 1014: if (m->type == ALIAS) {
1015: alias_seqno++;
1.25 millert 1016: if (alias_find(m->name, RUNASALIAS) == NULL) {
1017: print_undefined(m->name, RUNASALIAS, strict, quiet);
1.22 millert 1018: error++;
1019: }
1020: }
1021: }
1.23 millert 1022: if ((m = cs->cmnd)->type == ALIAS) {
1023: alias_seqno++;
1.25 millert 1024: if (alias_find(m->name, CMNDALIAS) == NULL) {
1025: print_undefined(m->name, CMNDALIAS, strict, quiet);
1.22 millert 1026: error++;
1027: }
1028: }
1029: }
1030: }
1031: }
1032:
1033: /* Reverse check (destructive) */
1034: tq_foreach_fwd(&userspecs, us) {
1035: tq_foreach_fwd(&us->users, m) {
1.25 millert 1036: if (m->type == ALIAS) {
1037: (void) alias_remove_recursive(m->name, USERALIAS);
1038: }
1.22 millert 1039: }
1040: tq_foreach_fwd(&us->privileges, priv) {
1041: tq_foreach_fwd(&priv->hostlist, m) {
1.23 millert 1042: if (m->type == ALIAS)
1.25 millert 1043: (void) alias_remove_recursive(m->name, HOSTALIAS);
1.22 millert 1044: }
1045: tq_foreach_fwd(&priv->cmndlist, cs) {
1046: tq_foreach_fwd(&cs->runasuserlist, m) {
1.23 millert 1047: if (m->type == ALIAS)
1.25 millert 1048: (void) alias_remove_recursive(m->name, RUNASALIAS);
1.22 millert 1049: }
1.23 millert 1050: if ((m = cs->cmnd)->type == ALIAS)
1.25 millert 1051: (void) alias_remove_recursive(m->name, CMNDALIAS);
1.22 millert 1052: }
1053: }
1054: }
1.24 millert 1055: tq_foreach_fwd(&defaults, d) {
1056: switch (d->type) {
1057: case DEFAULTS_HOST:
1058: atype = HOSTALIAS;
1059: break;
1060: case DEFAULTS_USER:
1061: atype = USERALIAS;
1062: break;
1063: case DEFAULTS_RUNAS:
1064: atype = RUNASALIAS;
1065: break;
1066: case DEFAULTS_CMND:
1067: atype = CMNDALIAS;
1068: break;
1069: default:
1070: continue; /* not an alias */
1071: }
1072: tq_foreach_fwd(&d->binding, binding) {
1073: for (m = binding; m != NULL; m = m->next) {
1074: if (m->type == ALIAS)
1.25 millert 1075: (void) alias_remove_recursive(m->name, atype);
1.24 millert 1076: }
1077: }
1078: }
1.25 millert 1079: rbdestroy(alias_freelist, alias_free);
1.24 millert 1080:
1.22 millert 1081: /* If all aliases were referenced we will have an empty tree. */
1082: if (no_aliases())
1083: return(0);
1.25 millert 1084: if (!quiet) {
1085: alias_apply(print_unused, strict ? "Error" : "Warning");
1086: }
1.22 millert 1087: return (strict ? 1 : 0);
1.25 millert 1088: }
1089:
1090: static void
1091: print_undefined(name, type, strict, quiet)
1092: char *name;
1093: int type;
1094: int strict;
1095: int quiet;
1096: {
1097: if (!quiet) {
1098: warningx("%s: %s_Alias `%s' referenced but not defined",
1099: strict ? "Error" : "Warning",
1100: type == HOSTALIAS ? "Host" : type == CMNDALIAS ? "Cmnd" :
1101: type == USERALIAS ? "User" : type == RUNASALIAS ? "Runas" :
1102: "Unknown", name);
1103: }
1.22 millert 1104: }
1105:
1106: static int
1107: print_unused(v1, v2)
1108: void *v1;
1109: void *v2;
1110: {
1111: struct alias *a = (struct alias *)v1;
1112: char *prefix = (char *)v2;
1113:
1114: warningx("%s: unused %s_Alias %s", prefix,
1115: a->type == HOSTALIAS ? "Host" : a->type == CMNDALIAS ? "Cmnd" :
1116: a->type == USERALIAS ? "User" : a->type == RUNASALIAS ? "Runas" :
1117: "Unknown", a->name);
1118: return(0);
1119: }
1.10 millert 1120:
1.22 millert 1121: /*
1122: * Unlink any sudoers temp files that remain.
1123: */
1124: void
1125: cleanup(gotsignal)
1126: int gotsignal;
1127: {
1128: struct sudoersfile *sp;
1.1 millert 1129:
1.22 millert 1130: tq_foreach_fwd(&sudoerslist, sp) {
1131: if (sp->tpath != NULL)
1132: (void) unlink(sp->tpath);
1.10 millert 1133: }
1.22 millert 1134: if (!gotsignal) {
1135: sudo_endpwent();
1136: sudo_endgrent();
1137: }
1138: }
1139:
1140: /*
1141: * Unlink sudoers temp files (if any) and exit.
1142: */
1143: static RETSIGTYPE
1144: quit(signo)
1145: int signo;
1146: {
1147: cleanup(signo);
1148: #define emsg " exiting due to signal.\n"
1149: write(STDERR_FILENO, getprogname(), strlen(getprogname()));
1150: write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
1151: _exit(signo);
1.1 millert 1152: }
1153:
1154: static void
1155: usage()
1156: {
1.19 millert 1157: (void) fprintf(stderr, "usage: %s [-c] [-q] [-s] [-V] [-f sudoers]\n",
1.14 millert 1158: getprogname());
1.1 millert 1159: exit(1);
1160: }