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