Annotation of src/usr.bin/sudo/visudo.c, Revision 1.19
1.1 millert 1: /*
1.19 ! millert 2: * Copyright (c) 1996, 1998-2005 Todd C. Miller <Todd.Miller@courtesan.com>
1.1 millert 3: *
1.17 millert 4: * Permission to use, copy, modify, and distribute this software for any
5: * purpose with or without fee is hereby granted, provided that the above
6: * copyright notice and this permission notice appear in all copies.
1.1 millert 7: *
1.17 millert 8: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.15 millert 15: *
16: * Sponsored in part by the Defense Advanced Research Projects
17: * Agency (DARPA) and Air Force Research Laboratory, Air Force
18: * Materiel Command, USAF, under agreement number F39502-99-1-0512.
1.1 millert 19: */
20:
21: /*
22: * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
23: */
24:
1.14 millert 25: #define _SUDO_MAIN
26:
1.17 millert 27: #ifdef __TANDEM
28: # include <floss.h>
29: #endif
30:
1.19 ! millert 31: #include <config.h>
1.1 millert 32:
1.11 millert 33: #include <sys/types.h>
34: #include <sys/param.h>
35: #include <sys/stat.h>
1.17 millert 36: #include <sys/time.h>
37: #ifndef __TANDEM
38: # include <sys/file.h>
39: #endif
1.11 millert 40: #include <sys/wait.h>
1.1 millert 41: #include <stdio.h>
42: #ifdef STDC_HEADERS
1.11 millert 43: # include <stdlib.h>
44: # include <stddef.h>
45: #else
46: # ifdef HAVE_STDLIB_H
47: # include <stdlib.h>
48: # endif
1.1 millert 49: #endif /* STDC_HEADERS */
1.11 millert 50: #ifdef HAVE_STRING_H
51: # include <string.h>
52: #else
53: # ifdef HAVE_STRINGS_H
54: # include <strings.h>
55: # endif
56: #endif /* HAVE_STRING_H */
1.1 millert 57: #ifdef HAVE_UNISTD_H
58: #include <unistd.h>
59: #endif /* HAVE_UNISTD_H */
1.14 millert 60: #ifdef HAVE_ERR_H
61: # include <err.h>
62: #else
63: # include "emul/err.h"
64: #endif /* HAVE_ERR_H */
1.1 millert 65: #include <ctype.h>
66: #include <pwd.h>
1.19 ! millert 67: #if TIME_WITH_SYS_TIME
! 68: # include <time.h>
! 69: #endif
1.1 millert 70: #include <signal.h>
71: #include <errno.h>
72: #include <fcntl.h>
1.19 ! millert 73: #ifndef HAVE_TIMESPEC
! 74: # include <emul/timespec.h>
! 75: #endif
1.1 millert 76:
77: #include "sudo.h"
78: #include "version.h"
79:
80: #ifndef lint
1.19 ! millert 81: __unused static const char rcsid[] = "$Sudo: visudo.c,v 1.166.2.9 2007/07/22 19:21:01 millert Exp $";
1.1 millert 82: #endif /* lint */
83:
1.19 ! millert 84: struct sudoersfile {
! 85: char *path;
! 86: char *tpath;
! 87: int fd;
! 88: off_t orig_size;
! 89: struct timespec orig_mtim;
! 90: };
! 91:
1.1 millert 92: /*
93: * Function prototypes
94: */
1.19 ! millert 95: static void usage __P((void)) __attribute__((__noreturn__));
1.1 millert 96: static char whatnow __P((void));
97: static RETSIGTYPE Exit __P((int));
1.19 ! millert 98: static void edit_sudoers __P((struct sudoersfile *, char *, char *, int));
! 99: static void visudo __P((struct sudoersfile *, char *, char *));
1.1 millert 100: static void setup_signals __P((void));
1.19 ! millert 101: static void install_sudoers __P((struct sudoersfile *));
! 102: static int check_syntax __P(());
1.11 millert 103: static int run_command __P((char *, char **));
1.19 ! millert 104: static char *get_args __P((char *));
! 105: static char *get_editor __P((char **));
! 106: static FILE *open_sudoers __P((struct sudoersfile *));
! 107:
1.17 millert 108: int command_matches __P((char *, char *));
1.1 millert 109: int addr_matches __P((char *));
1.4 millert 110: int hostname_matches __P((char *, char *, char *));
1.3 millert 111: int netgr_matches __P((char *, char *, char *, char *));
1.17 millert 112: int usergr_matches __P((char *, char *, struct passwd *));
113: int userpw_matches __P((char *, char *, struct passwd *));
1.1 millert 114: void init_parser __P((void));
1.17 millert 115: void yyerror __P((char *));
1.1 millert 116: void yyrestart __P((FILE *));
117:
118: /*
119: * External globals exported by the parser
120: */
1.19 ! millert 121: extern FILE *yyin;
1.1 millert 122: extern int errorlineno;
123: extern int pedantic;
1.11 millert 124: extern int quiet;
125:
126: /* For getopt(3) */
127: extern char *optarg;
128: extern int optind;
1.1 millert 129:
130: /*
131: * Globals
132: */
133: char **Argv;
134: struct sudo_user sudo_user;
1.17 millert 135: int Argc, parse_error = FALSE;
1.19 ! millert 136: static struct sudoersfile sudoers;
1.1 millert 137:
138: int
139: main(argc, argv)
140: int argc;
141: char **argv;
142: {
1.19 ! millert 143: char *args, *editor;
! 144: int ch, checkonly, n;
! 145:
! 146: /* Initialize sudoers struct. */
! 147: sudoers.path = _PATH_SUDOERS;
! 148: sudoers.tpath = _PATH_SUDOERS_TMP;
! 149: sudoers.fd = -1;
1.1 millert 150:
151: /* Warn about aliases that are used before being defined. */
152: pedantic = 1;
153:
1.17 millert 154: Argv = argv;
155: if ((Argc = argc) < 1)
156: usage();
1.1 millert 157:
158: /*
159: * Arg handling.
160: */
1.11 millert 161: checkonly = 0;
162: while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
163: switch (ch) {
164: case 'V':
1.14 millert 165: (void) printf("%s version %s\n", getprogname(), version);
1.11 millert 166: exit(0);
167: case 'c':
168: checkonly++; /* check mode */
169: break;
1.19 ! millert 170: case 'f': /* sudoers file path */
! 171: sudoers.path = optarg;
! 172: easprintf(&sudoers.tpath, "%s.tmp", optarg);
1.11 millert 173: break;
174: case 's':
175: pedantic++; /* strict mode */
176: break;
177: case 'q':
178: quiet++; /* quiet mode */
179: break;
180: default:
181: usage();
1.1 millert 182: }
183: }
1.11 millert 184: argc -= optind;
185: argv += optind;
186: if (argc)
187: usage();
1.1 millert 188:
189: /* Mock up a fake sudo_user struct. */
190: user_host = user_shost = user_cmnd = "";
1.14 millert 191: if ((sudo_user.pw = getpwuid(getuid())) == NULL)
192: errx(1, "you don't exist in the passwd database");
1.1 millert 193:
1.4 millert 194: /* Setup defaults data structures. */
195: init_defaults();
1.1 millert 196:
1.11 millert 197: if (checkonly)
1.19 ! millert 198: exit(check_syntax());
1.11 millert 199:
1.1 millert 200: /*
1.19 ! millert 201: * Open and parse the existing sudoers file(s) in quiet mode to highlight
! 202: * any existing errors and to pull in editor and env_editor conf values.
! 203: */
! 204: if ((yyin = open_sudoers(&sudoers)) == NULL)
! 205: err(1, "%s", sudoers.path);
! 206: n = quiet;
! 207: quiet = 1;
! 208: init_parser();
! 209: yyparse();
! 210: parse_error = FALSE;
! 211: quiet = n;
! 212:
! 213: /* Edit sudoers, check for parse errors and re-edit on failure. */
! 214: editor = get_editor(&args);
! 215: visudo(&sudoers, editor, args);
1.1 millert 216:
1.19 ! millert 217: /* Install the new sudoers file. */
! 218: install_sudoers(&sudoers);
1.3 millert 219:
1.19 ! millert 220: exit(0);
! 221: }
1.1 millert 222:
1.19 ! millert 223: /*
! 224: * Edit the sudoers file.
! 225: * Returns TRUE on success, else FALSE.
! 226: */
! 227: static void
! 228: edit_sudoers(sp, editor, args, lineno)
! 229: struct sudoersfile *sp;
! 230: char *editor, *args;
! 231: int lineno;
! 232: {
! 233: int ac; /* argument count */
! 234: char **av; /* argument vector for run_command */
! 235: char *cp; /* scratch char pointer */
! 236: char linestr[64]; /* string version of lineno */
! 237: struct timespec ts1, ts2; /* time before and after edit */
! 238: struct stat sb; /* stat buffer */
1.1 millert 239:
1.19 ! millert 240: /* Make timestamp on temp file match original. */
! 241: (void) touch(-1, sp->tpath, &sp->orig_mtim);
1.13 millert 242:
1.19 ! millert 243: /* Find the length of the argument vector */
! 244: ac = 3 + (lineno > 0);
! 245: if (args) {
! 246: int wasblank;
! 247:
! 248: ac++;
! 249: for (wasblank = FALSE, cp = args; *cp; cp++) {
! 250: if (isblank((unsigned char) *cp))
! 251: wasblank = TRUE;
! 252: else if (wasblank) {
! 253: wasblank = FALSE;
! 254: ac++;
! 255: }
! 256: }
! 257: }
! 258:
! 259: /* Build up argument vector for the command */
! 260: av = emalloc2(ac, sizeof(char *));
! 261: if ((av[0] = strrchr(editor, '/')) != NULL)
! 262: av[0]++;
! 263: else
! 264: av[0] = editor;
! 265: ac = 1;
! 266: if (lineno > 0) {
! 267: (void) snprintf(linestr, sizeof(linestr), "+%d", lineno);
! 268: av[ac++] = linestr;
! 269: }
! 270: if (args) {
! 271: for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t")))
! 272: av[ac++] = cp;
! 273: }
! 274: av[ac++] = sp->tpath;
! 275: av[ac++] = NULL;
1.1 millert 276:
277: /*
1.19 ! millert 278: * Do the edit:
! 279: * We cannot check the editor's exit value against 0 since
! 280: * XPG4 specifies that vi's exit value is a function of the
! 281: * number of errors during editing (?!?!).
1.4 millert 282: */
1.19 ! millert 283: gettime(&ts1);
! 284: if (run_command(editor, av) != -1) {
! 285: gettime(&ts2);
! 286: /*
! 287: * Sanity checks.
! 288: */
! 289: if (stat(sp->tpath, &sb) < 0) {
! 290: warnx("cannot stat temporary file (%s), %s unchanged",
! 291: sp->tpath, sp->path);
! 292: Exit(-1);
1.11 millert 293: }
1.19 ! millert 294: if (sb.st_size == 0) {
! 295: warnx("zero length temporary file (%s), %s unchanged",
! 296: sp->tpath, sp->path);
1.11 millert 297: Exit(-1);
298: }
1.19 ! millert 299: } else {
! 300: warnx("editor (%s) failed, %s unchanged", editor, sp->path);
! 301: Exit(-1);
1.11 millert 302: }
303:
1.19 ! millert 304: /* Check to see if the user changed the file. */
! 305: if (sp->orig_size == sb.st_size &&
! 306: sp->orig_mtim.tv_sec == mtim_getsec(sb) &&
! 307: sp->orig_mtim.tv_nsec == mtim_getnsec(sb)) {
! 308: /*
! 309: * If mtime and size match but the user spent no measurable
! 310: * time in the editor we can't tell if the file was changed.
! 311: */
! 312: #ifdef HAVE_TIMESPECSUB2
! 313: timespecsub(&ts1, &ts2);
! 314: #else
! 315: timespecsub(&ts1, &ts2, &ts2);
! 316: #endif
! 317: if (timespecisset(&ts2)) {
! 318: warnx("%s unchanged", sp->tpath);
! 319: Exit(0);
1.11 millert 320: }
321: }
1.19 ! millert 322: }
! 323:
! 324: /*
! 325: * Parse sudoers after editing and re-edit any ones that caused a parse error.
! 326: * Returns TRUE on success, else FALSE.
! 327: */
! 328: static void
! 329: visudo(sp, editor, args)
! 330: struct sudoersfile *sp;
! 331: char *editor, *args;
! 332: {
! 333: int ch;
1.4 millert 334:
335: /*
1.19 ! millert 336: * Parse the edited sudoers file and do sanity checking
1.1 millert 337: */
338: do {
1.19 ! millert 339: edit_sudoers(sp, editor, args, errorlineno);
1.11 millert 340:
1.19 ! millert 341: yyin = fopen(sp->tpath, "r+");
! 342: if (yyin == NULL) {
! 343: warnx("can't re-open temporary file (%s), %s unchanged.",
! 344: sp->tpath, sp->path);
! 345: Exit(-1);
1.11 millert 346: }
347:
1.19 ! millert 348: /* Add missing newline at EOF if needed. */
! 349: if (fseek(yyin, -1, SEEK_END) == 0 && (ch = fgetc(yyin)) != '\n')
! 350: fputc('\n', yyin);
! 351: rewind(yyin);
! 352:
! 353: /* Clean slate for each parse */
! 354: user_runas = NULL;
! 355: init_defaults();
! 356: init_parser();
! 357:
! 358: /* Parse the sudoers temp file */
! 359: yyrestart(yyin);
! 360: if (yyparse() && parse_error != TRUE) {
! 361: warnx("unabled to parse temporary file (%s), unknown error",
! 362: sp->tpath);
! 363: parse_error = TRUE;
1.1 millert 364: }
1.19 ! millert 365: fclose(yyin);
1.1 millert 366:
367: /*
368: * Got an error, prompt the user for what to do now
369: */
1.19 ! millert 370: if (parse_error) {
1.1 millert 371: switch (whatnow()) {
1.7 millert 372: case 'Q' : parse_error = FALSE; /* ignore parse error */
1.1 millert 373: break;
1.19 ! millert 374: case 'x' : Exit(0);
1.1 millert 375: break;
376: }
377: }
1.19 ! millert 378: } while (parse_error);
! 379: }
1.1 millert 380:
1.19 ! millert 381: /*
! 382: * Set the owner and mode on a sudoers temp file and
! 383: * move it into place. Returns TRUE on success, else FALSE.
! 384: */
! 385: static void
! 386: install_sudoers(sp)
! 387: struct sudoersfile *sp;
! 388: {
1.1 millert 389: /*
390: * Change mode and ownership of temp file so when
1.19 ! millert 391: * we move it to sp->path things are kosher.
1.1 millert 392: */
1.19 ! millert 393: if (chown(sp->tpath, SUDOERS_UID, SUDOERS_GID) != 0) {
1.14 millert 394: warn("unable to set (uid, gid) of %s to (%d, %d)",
1.19 ! millert 395: sp->tpath, SUDOERS_UID, SUDOERS_GID);
1.1 millert 396: Exit(-1);
397: }
1.19 ! millert 398: if (chmod(sp->tpath, SUDOERS_MODE) != 0) {
! 399: warn("unable to change mode of %s to 0%o", sp->tpath, SUDOERS_MODE);
1.1 millert 400: Exit(-1);
401: }
402:
403: /*
1.19 ! millert 404: * Now that sp->tpath is sane (parses ok) it needs to be
! 405: * rename(2)'d to sp->path. If the rename(2) fails we try using
! 406: * mv(1) in case sp->tpath and sp->path are on different file systems.
1.1 millert 407: */
1.19 ! millert 408: if (rename(sp->tpath, sp->path) != 0) {
1.1 millert 409: if (errno == EXDEV) {
1.19 ! millert 410: char *av[4];
1.17 millert 411: warnx("%s and %s not on the same file system, using mv to rename",
1.19 ! millert 412: sp->tpath, sp->path);
1.1 millert 413:
1.11 millert 414: /* Build up argument vector for the command */
415: if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
416: av[0]++;
417: else
418: av[0] = _PATH_MV;
1.19 ! millert 419: av[1] = sp->tpath;
! 420: av[2] = sp->path;
1.11 millert 421: av[3] = NULL;
1.1 millert 422:
1.11 millert 423: /* And run it... */
424: if (run_command(_PATH_MV, av)) {
1.14 millert 425: warnx("command failed: '%s %s %s', %s unchanged",
1.19 ! millert 426: _PATH_MV, sp->tpath, sp->path, sp->path);
1.1 millert 427: Exit(-1);
428: }
429: } else {
1.19 ! millert 430: warn("error renaming %s, %s unchanged", sp->tpath, sp->path);
1.1 millert 431: Exit(-1);
432: }
433: }
434: }
435:
436: /*
437: * Dummy *_matches routines.
438: * These exist to allow us to use the same parser as sudo(8).
439: */
440: int
1.17 millert 441: command_matches(path, sudoers_args)
1.1 millert 442: char *path;
443: char *sudoers_args;
444: {
445: return(TRUE);
446: }
447:
448: int
449: addr_matches(n)
450: char *n;
1.4 millert 451: {
452: return(TRUE);
453: }
454:
455: int
456: hostname_matches(s, l, p)
457: char *s, *l, *p;
1.1 millert 458: {
459: return(TRUE);
460: }
461:
462: int
1.17 millert 463: usergr_matches(g, u, pw)
1.1 millert 464: char *g, *u;
1.17 millert 465: struct passwd *pw;
466: {
467: return(TRUE);
468: }
469:
470: int
471: userpw_matches(s, u, pw)
472: char *s, *u;
473: struct passwd *pw;
1.1 millert 474: {
475: return(TRUE);
476: }
477:
478: int
1.3 millert 479: netgr_matches(n, h, sh, u)
480: char *n, *h, *sh, *u;
1.1 millert 481: {
482: return(TRUE);
1.2 millert 483: }
484:
485: void
486: set_fqdn()
487: {
488: return;
1.1 millert 489: }
490:
1.11 millert 491: int
1.17 millert 492: set_runaspw(user)
493: char *user;
494: {
495: extern int sudolineno, used_runas;
496:
497: if (used_runas) {
498: (void) fprintf(stderr,
499: "%s: runas_default set after old value is in use near line %d\n",
500: pedantic > 1 ? "Error" : "Warning", sudolineno);
501: if (pedantic > 1)
502: yyerror(NULL);
503: }
504: return(TRUE);
505: }
506:
507: int
1.11 millert 508: user_is_exempt()
509: {
510: return(TRUE);
511: }
512:
513: void
514: init_envtables()
515: {
516: return;
517: }
518:
1.1 millert 519: /*
520: * Assuming a parse error occurred, prompt the user for what they want
521: * to do now. Returns the first letter of their choice.
522: */
523: static char
524: whatnow()
525: {
526: int choice, c;
527:
528: for (;;) {
529: (void) fputs("What now? ", stdout);
530: choice = getchar();
531: for (c = choice; c != '\n' && c != EOF;)
532: c = getchar();
533:
1.3 millert 534: switch (choice) {
535: case EOF:
536: choice = 'x';
537: /* FALLTHROUGH */
538: case 'e':
539: case 'x':
540: case 'Q':
541: return(choice);
542: default:
543: (void) puts("Options are:");
544: (void) puts(" (e)dit sudoers file again");
545: (void) puts(" e(x)it without saving changes to sudoers file");
546: (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
1.1 millert 547: }
548: }
549: }
550:
551: /*
552: * Install signal handlers for visudo.
553: */
554: static void
555: setup_signals()
556: {
1.11 millert 557: sigaction_t sa;
1.1 millert 558:
559: /*
560: * Setup signal handlers to cleanup nicely.
561: */
1.11 millert 562: sigemptyset(&sa.sa_mask);
563: sa.sa_flags = SA_RESTART;
564: sa.sa_handler = Exit;
565: (void) sigaction(SIGTERM, &sa, NULL);
566: (void) sigaction(SIGHUP, &sa, NULL);
567: (void) sigaction(SIGINT, &sa, NULL);
568: (void) sigaction(SIGQUIT, &sa, NULL);
569: }
570:
571: static int
572: run_command(path, argv)
573: char *path;
574: char **argv;
575: {
576: int status;
577: pid_t pid;
578: sigset_t set, oset;
579:
580: (void) sigemptyset(&set);
581: (void) sigaddset(&set, SIGCHLD);
582: (void) sigprocmask(SIG_BLOCK, &set, &oset);
583:
584: switch (pid = fork()) {
585: case -1:
1.14 millert 586: warn("unable to run %s", path);
1.11 millert 587: Exit(-1);
588: break; /* NOTREACHED */
589: case 0:
590: (void) sigprocmask(SIG_SETMASK, &oset, NULL);
1.19 ! millert 591: endpwent();
! 592: closefrom(STDERR_FILENO + 1);
1.11 millert 593: execv(path, argv);
1.14 millert 594: warn("unable to run %s", path);
1.11 millert 595: _exit(127);
596: break; /* NOTREACHED */
597: }
598:
599: #ifdef sudo_waitpid
600: pid = sudo_waitpid(pid, &status, 0);
1.1 millert 601: #else
1.11 millert 602: pid = wait(&status);
603: #endif
604:
605: (void) sigprocmask(SIG_SETMASK, &oset, NULL);
606:
1.17 millert 607: if (pid == -1 || !WIFEXITED(status))
608: return(-1);
609: return(WEXITSTATUS(status));
1.11 millert 610: }
611:
612: static int
1.19 ! millert 613: check_syntax()
1.11 millert 614: {
615:
1.19 ! millert 616: if ((yyin = fopen(sudoers.path, "r")) == NULL) {
1.11 millert 617: if (!quiet)
1.19 ! millert 618: warn("unable to open %s", sudoers.path);
1.11 millert 619: exit(1);
620: }
621: init_parser();
622: if (yyparse() && parse_error != TRUE) {
623: if (!quiet)
1.19 ! millert 624: warnx("failed to parse %s file, unknown error", sudoers.path);
1.11 millert 625: parse_error = TRUE;
626: }
627: if (!quiet){
628: if (parse_error)
1.19 ! millert 629: (void) printf("parse error in %s near line %d\n", sudoers.path,
1.11 millert 630: errorlineno);
631: else
1.19 ! millert 632: (void) printf("%s file parsed OK\n", sudoers.path);
1.11 millert 633: }
634:
635: return(parse_error == TRUE);
1.1 millert 636: }
637:
1.19 ! millert 638: static FILE *
! 639: open_sudoers(sp)
! 640: struct sudoersfile *sp;
! 641: {
! 642: struct stat sb;
! 643: ssize_t nread;
! 644: FILE *fp;
! 645: char buf[PATH_MAX*2];
! 646: int tfd;
! 647:
! 648: /* Open and lock sudoers. */
! 649: sp->fd = open(sp->path, O_RDWR | O_CREAT, SUDOERS_MODE);
! 650: if (sp->fd == -1)
! 651: err(1, "%s", sp->path);
! 652: if (!lock_file(sp->fd, SUDO_TLOCK))
! 653: errx(1, "%s busy, try again later", sp->path);
! 654: if ((fp = fdopen(sp->fd, "r")) == NULL)
! 655: err(1, "%s", sp->path);
! 656:
! 657: /* Stash sudoers size and mtime. */
! 658: #ifdef HAVE_FSTAT
! 659: if (fstat(sp->fd, &sb) == -1)
! 660: #else
! 661: if (stat(sp->path, &sb) == -1)
! 662: #endif
! 663: err(1, "can't stat %s", sp->path);
! 664: sp->orig_size = sb.st_size;
! 665: sp->orig_mtim.tv_sec = mtim_getsec(sb);
! 666: sp->orig_mtim.tv_nsec = mtim_getnsec(sb);
! 667:
! 668: /* Create the temp file. */
! 669: tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
! 670: if (tfd < 0)
! 671: err(1, "%s", sp->tpath);
! 672:
! 673: /* Install signal handlers to clean up temp file if we are killed. */
! 674: setup_signals();
! 675:
! 676: /* Copy sp->path -> sp->tpath. */
! 677: if (sp->orig_size != 0) {
! 678: while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
! 679: if (write(tfd, buf, nread) != nread) {
! 680: warn("write error");
! 681: Exit(-1);
! 682: }
! 683:
! 684: /* Add missing newline at EOF if needed. */
! 685: if (nread > 0 && buf[nread - 1] != '\n') {
! 686: buf[0] = '\n';
! 687: write(tfd, buf, 1);
! 688: }
! 689: }
! 690: (void) close(tfd);
! 691: rewind(fp);
! 692:
! 693: return(fp);
! 694: }
! 695:
! 696: static char *
! 697: get_editor(args)
! 698: char **args;
! 699: {
! 700: char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
! 701:
! 702: /*
! 703: * Check VISUAL and EDITOR environment variables to see which editor
! 704: * the user wants to use (we may not end up using it though).
! 705: * If the path is not fully-qualified, make it so and check that
! 706: * the specified executable actually exists.
! 707: */
! 708: UserEditorArgs = NULL;
! 709: if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
! 710: UserEditor = getenv("EDITOR");
! 711: if (UserEditor && *UserEditor == '\0')
! 712: UserEditor = NULL;
! 713: else if (UserEditor) {
! 714: UserEditorArgs = get_args(UserEditor);
! 715: if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
! 716: UserEditor = Editor;
! 717: } else {
! 718: if (def_env_editor) {
! 719: /* If we are honoring $EDITOR this is a fatal error. */
! 720: warnx("specified editor (%s) doesn't exist!", UserEditor);
! 721: Exit(-1);
! 722: } else {
! 723: /* Otherwise, just ignore $EDITOR. */
! 724: UserEditor = NULL;
! 725: }
! 726: }
! 727: }
! 728:
! 729: /*
! 730: * See if we can use the user's choice of editors either because
! 731: * we allow any $EDITOR or because $EDITOR is in the allowable list.
! 732: */
! 733: Editor = EditorArgs = EditorPath = NULL;
! 734: if (def_env_editor && UserEditor) {
! 735: Editor = UserEditor;
! 736: EditorArgs = UserEditorArgs;
! 737: } else if (UserEditor) {
! 738: struct stat editor_sb;
! 739: struct stat user_editor_sb;
! 740: char *base, *userbase;
! 741:
! 742: if (stat(UserEditor, &user_editor_sb) != 0) {
! 743: /* Should never happen since we already checked above. */
! 744: warn("unable to stat editor (%s)", UserEditor);
! 745: Exit(-1);
! 746: }
! 747: EditorPath = estrdup(def_editor);
! 748: Editor = strtok(EditorPath, ":");
! 749: do {
! 750: EditorArgs = get_args(Editor);
! 751: /*
! 752: * Both Editor and UserEditor should be fully qualified but
! 753: * check anyway...
! 754: */
! 755: if ((base = strrchr(Editor, '/')) == NULL)
! 756: continue;
! 757: if ((userbase = strrchr(UserEditor, '/')) == NULL) {
! 758: Editor = NULL;
! 759: break;
! 760: }
! 761: base++, userbase++;
! 762:
! 763: /*
! 764: * We compare the basenames first and then use stat to match
! 765: * for sure.
! 766: */
! 767: if (strcmp(base, userbase) == 0) {
! 768: if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
! 769: && (editor_sb.st_mode & 0000111) &&
! 770: editor_sb.st_dev == user_editor_sb.st_dev &&
! 771: editor_sb.st_ino == user_editor_sb.st_ino)
! 772: break;
! 773: }
! 774: } while ((Editor = strtok(NULL, ":")));
! 775: }
! 776:
! 777: /*
! 778: * Can't use $EDITOR, try each element of def_editor until we
! 779: * find one that exists, is regular, and is executable.
! 780: */
! 781: if (Editor == NULL || *Editor == '\0') {
! 782: efree(EditorPath);
! 783: EditorPath = estrdup(def_editor);
! 784: Editor = strtok(EditorPath, ":");
! 785: do {
! 786: EditorArgs = get_args(Editor);
! 787: if (sudo_goodpath(Editor, NULL))
! 788: break;
! 789: } while ((Editor = strtok(NULL, ":")));
! 790:
! 791: /* Bleah, none of the editors existed! */
! 792: if (Editor == NULL || *Editor == '\0') {
! 793: warnx("no editor found (editor path = %s)", def_editor);
! 794: Exit(-1);
! 795: }
! 796: }
! 797: *args = EditorArgs;
! 798: return(Editor);
! 799: }
! 800:
! 801: /*
! 802: * Split out any command line arguments and return them.
! 803: */
! 804: static char *
! 805: get_args(cmnd)
! 806: char *cmnd;
! 807: {
! 808: char *args;
! 809:
! 810: args = cmnd;
! 811: while (*args && !isblank((unsigned char) *args))
! 812: args++;
! 813: if (*args) {
! 814: *args++ = '\0';
! 815: while (*args && isblank((unsigned char) *args))
! 816: args++;
! 817: }
! 818: return(*args ? args : NULL);
! 819: }
! 820:
1.1 millert 821: /*
822: * Unlink the sudoers temp file (if it exists) and exit.
823: * Used in place of a normal exit() and as a signal handler.
1.10 millert 824: * A positive parameter indicates we were called as a signal handler.
1.1 millert 825: */
826: static RETSIGTYPE
827: Exit(sig)
828: int sig;
829: {
1.16 millert 830: #define emsg " exiting due to signal.\n"
1.10 millert 831:
1.19 ! millert 832: (void) unlink(sudoers.tpath);
1.1 millert 833:
1.10 millert 834: if (sig > 0) {
1.14 millert 835: write(STDERR_FILENO, getprogname(), strlen(getprogname()));
1.10 millert 836: write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
1.16 millert 837: _exit(sig);
1.10 millert 838: }
839: exit(-sig);
1.1 millert 840: }
841:
842: static void
843: usage()
844: {
1.19 ! millert 845: (void) fprintf(stderr, "usage: %s [-c] [-q] [-s] [-V] [-f sudoers]\n",
1.14 millert 846: getprogname());
1.1 millert 847: exit(1);
848: }