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