Annotation of src/usr.bin/sudo/visudo.c, Revision 1.20
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.20 ! millert 81: __unused static const char rcsid[] = "$Sudo: visudo.c,v 1.166.2.10 2007/09/01 13:39:13 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.20 ! millert 101: static void install_sudoers __P((struct sudoersfile *, int));
1.19 millert 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;
1.20 ! millert 144: int ch, checkonly, n, oldperms;
1.19 millert 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.20 ! millert 161: checkonly = oldperms = FALSE;
1.11 millert 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.20 ! millert 173: oldperms = TRUE;
1.11 millert 174: break;
175: case 's':
176: pedantic++; /* strict mode */
177: break;
178: case 'q':
179: quiet++; /* quiet mode */
180: break;
181: default:
182: usage();
1.1 millert 183: }
184: }
1.11 millert 185: argc -= optind;
186: argv += optind;
187: if (argc)
188: usage();
1.1 millert 189:
190: /* Mock up a fake sudo_user struct. */
191: user_host = user_shost = user_cmnd = "";
1.14 millert 192: if ((sudo_user.pw = getpwuid(getuid())) == NULL)
193: errx(1, "you don't exist in the passwd database");
1.1 millert 194:
1.4 millert 195: /* Setup defaults data structures. */
196: init_defaults();
1.1 millert 197:
1.11 millert 198: if (checkonly)
1.19 millert 199: exit(check_syntax());
1.11 millert 200:
1.1 millert 201: /*
1.19 millert 202: * Open and parse the existing sudoers file(s) in quiet mode to highlight
203: * any existing errors and to pull in editor and env_editor conf values.
204: */
205: if ((yyin = open_sudoers(&sudoers)) == NULL)
206: err(1, "%s", sudoers.path);
207: n = quiet;
208: quiet = 1;
209: init_parser();
210: yyparse();
211: parse_error = FALSE;
212: quiet = n;
213:
214: /* Edit sudoers, check for parse errors and re-edit on failure. */
215: editor = get_editor(&args);
216: visudo(&sudoers, editor, args);
1.1 millert 217:
1.19 millert 218: /* Install the new sudoers file. */
1.20 ! millert 219: install_sudoers(&sudoers, oldperms);
1.3 millert 220:
1.19 millert 221: exit(0);
222: }
1.1 millert 223:
1.19 millert 224: /*
225: * Edit the sudoers file.
226: * Returns TRUE on success, else FALSE.
227: */
228: static void
229: edit_sudoers(sp, editor, args, lineno)
230: struct sudoersfile *sp;
231: char *editor, *args;
232: int lineno;
233: {
234: int ac; /* argument count */
235: char **av; /* argument vector for run_command */
236: char *cp; /* scratch char pointer */
237: char linestr[64]; /* string version of lineno */
238: struct timespec ts1, ts2; /* time before and after edit */
239: struct stat sb; /* stat buffer */
1.1 millert 240:
1.19 millert 241: /* Make timestamp on temp file match original. */
242: (void) touch(-1, sp->tpath, &sp->orig_mtim);
1.13 millert 243:
1.19 millert 244: /* Find the length of the argument vector */
245: ac = 3 + (lineno > 0);
246: if (args) {
247: int wasblank;
248:
249: ac++;
250: for (wasblank = FALSE, cp = args; *cp; cp++) {
251: if (isblank((unsigned char) *cp))
252: wasblank = TRUE;
253: else if (wasblank) {
254: wasblank = FALSE;
255: ac++;
256: }
257: }
258: }
259:
260: /* Build up argument vector for the command */
261: av = emalloc2(ac, sizeof(char *));
262: if ((av[0] = strrchr(editor, '/')) != NULL)
263: av[0]++;
264: else
265: av[0] = editor;
266: ac = 1;
267: if (lineno > 0) {
268: (void) snprintf(linestr, sizeof(linestr), "+%d", lineno);
269: av[ac++] = linestr;
270: }
271: if (args) {
272: for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t")))
273: av[ac++] = cp;
274: }
275: av[ac++] = sp->tpath;
276: av[ac++] = NULL;
1.1 millert 277:
278: /*
1.19 millert 279: * Do the edit:
280: * We cannot check the editor's exit value against 0 since
281: * XPG4 specifies that vi's exit value is a function of the
282: * number of errors during editing (?!?!).
1.4 millert 283: */
1.19 millert 284: gettime(&ts1);
285: if (run_command(editor, av) != -1) {
286: gettime(&ts2);
287: /*
288: * Sanity checks.
289: */
290: if (stat(sp->tpath, &sb) < 0) {
291: warnx("cannot stat temporary file (%s), %s unchanged",
292: sp->tpath, sp->path);
293: Exit(-1);
1.11 millert 294: }
1.19 millert 295: if (sb.st_size == 0) {
296: warnx("zero length temporary file (%s), %s unchanged",
297: sp->tpath, sp->path);
1.11 millert 298: Exit(-1);
299: }
1.19 millert 300: } else {
301: warnx("editor (%s) failed, %s unchanged", editor, sp->path);
302: Exit(-1);
1.11 millert 303: }
304:
1.19 millert 305: /* Check to see if the user changed the file. */
306: if (sp->orig_size == sb.st_size &&
307: sp->orig_mtim.tv_sec == mtim_getsec(sb) &&
308: sp->orig_mtim.tv_nsec == mtim_getnsec(sb)) {
309: /*
310: * If mtime and size match but the user spent no measurable
311: * time in the editor we can't tell if the file was changed.
312: */
313: #ifdef HAVE_TIMESPECSUB2
314: timespecsub(&ts1, &ts2);
315: #else
316: timespecsub(&ts1, &ts2, &ts2);
317: #endif
318: if (timespecisset(&ts2)) {
319: warnx("%s unchanged", sp->tpath);
320: Exit(0);
1.11 millert 321: }
322: }
1.19 millert 323: }
324:
325: /*
326: * Parse sudoers after editing and re-edit any ones that caused a parse error.
327: * Returns TRUE on success, else FALSE.
328: */
329: static void
330: visudo(sp, editor, args)
331: struct sudoersfile *sp;
332: char *editor, *args;
333: {
334: int ch;
1.4 millert 335:
336: /*
1.19 millert 337: * Parse the edited sudoers file and do sanity checking
1.1 millert 338: */
339: do {
1.19 millert 340: edit_sudoers(sp, editor, args, errorlineno);
1.11 millert 341:
1.19 millert 342: yyin = fopen(sp->tpath, "r+");
343: if (yyin == NULL) {
344: warnx("can't re-open temporary file (%s), %s unchanged.",
345: sp->tpath, sp->path);
346: Exit(-1);
1.11 millert 347: }
348:
1.19 millert 349: /* Add missing newline at EOF if needed. */
350: if (fseek(yyin, -1, SEEK_END) == 0 && (ch = fgetc(yyin)) != '\n')
351: fputc('\n', yyin);
352: rewind(yyin);
353:
354: /* Clean slate for each parse */
355: user_runas = NULL;
356: init_defaults();
357: init_parser();
358:
359: /* Parse the sudoers temp file */
360: yyrestart(yyin);
361: if (yyparse() && parse_error != TRUE) {
362: warnx("unabled to parse temporary file (%s), unknown error",
363: sp->tpath);
364: parse_error = TRUE;
1.1 millert 365: }
1.19 millert 366: fclose(yyin);
1.1 millert 367:
368: /*
369: * Got an error, prompt the user for what to do now
370: */
1.19 millert 371: if (parse_error) {
1.1 millert 372: switch (whatnow()) {
1.7 millert 373: case 'Q' : parse_error = FALSE; /* ignore parse error */
1.1 millert 374: break;
1.19 millert 375: case 'x' : Exit(0);
1.1 millert 376: break;
377: }
378: }
1.19 millert 379: } while (parse_error);
380: }
1.1 millert 381:
1.19 millert 382: /*
383: * Set the owner and mode on a sudoers temp file and
384: * move it into place. Returns TRUE on success, else FALSE.
385: */
386: static void
1.20 ! millert 387: install_sudoers(sp, oldperms)
1.19 millert 388: struct sudoersfile *sp;
1.20 ! millert 389: int oldperms;
1.19 millert 390: {
1.20 ! millert 391: struct stat sb;
! 392:
1.1 millert 393: /*
394: * Change mode and ownership of temp file so when
1.19 millert 395: * we move it to sp->path things are kosher.
1.1 millert 396: */
1.20 ! millert 397: if (oldperms) {
! 398: /* Use perms of the existing file. */
! 399: #ifdef HAVE_FSTAT
! 400: if (fstat(sp->fd, &sb) == -1)
! 401: #else
! 402: if (stat(sp->path, &sb) == -1)
! 403: #endif
! 404: err(1, "can't stat %s", sp->path);
! 405: (void) chown(sp->tpath, sb.st_uid, sb.st_gid);
! 406: (void) chmod(sp->tpath, sb.st_mode & 0777);
! 407: } else {
! 408: if (chown(sp->tpath, SUDOERS_UID, SUDOERS_GID) != 0) {
! 409: warn("unable to set (uid, gid) of %s to (%d, %d)",
! 410: sp->tpath, SUDOERS_UID, SUDOERS_GID);
! 411: Exit(-1);
! 412: }
! 413: if (chmod(sp->tpath, SUDOERS_MODE) != 0) {
! 414: warn("unable to change mode of %s to 0%o", sp->tpath, SUDOERS_MODE);
! 415: Exit(-1);
! 416: }
1.1 millert 417: }
418:
419: /*
1.19 millert 420: * Now that sp->tpath is sane (parses ok) it needs to be
421: * rename(2)'d to sp->path. If the rename(2) fails we try using
422: * mv(1) in case sp->tpath and sp->path are on different file systems.
1.1 millert 423: */
1.19 millert 424: if (rename(sp->tpath, sp->path) != 0) {
1.1 millert 425: if (errno == EXDEV) {
1.19 millert 426: char *av[4];
1.17 millert 427: warnx("%s and %s not on the same file system, using mv to rename",
1.19 millert 428: sp->tpath, sp->path);
1.1 millert 429:
1.11 millert 430: /* Build up argument vector for the command */
431: if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
432: av[0]++;
433: else
434: av[0] = _PATH_MV;
1.19 millert 435: av[1] = sp->tpath;
436: av[2] = sp->path;
1.11 millert 437: av[3] = NULL;
1.1 millert 438:
1.11 millert 439: /* And run it... */
440: if (run_command(_PATH_MV, av)) {
1.14 millert 441: warnx("command failed: '%s %s %s', %s unchanged",
1.19 millert 442: _PATH_MV, sp->tpath, sp->path, sp->path);
1.1 millert 443: Exit(-1);
444: }
445: } else {
1.19 millert 446: warn("error renaming %s, %s unchanged", sp->tpath, sp->path);
1.1 millert 447: Exit(-1);
448: }
449: }
450: }
451:
452: /*
453: * Dummy *_matches routines.
454: * These exist to allow us to use the same parser as sudo(8).
455: */
456: int
1.17 millert 457: command_matches(path, sudoers_args)
1.1 millert 458: char *path;
459: char *sudoers_args;
460: {
461: return(TRUE);
462: }
463:
464: int
465: addr_matches(n)
466: char *n;
1.4 millert 467: {
468: return(TRUE);
469: }
470:
471: int
472: hostname_matches(s, l, p)
473: char *s, *l, *p;
1.1 millert 474: {
475: return(TRUE);
476: }
477:
478: int
1.17 millert 479: usergr_matches(g, u, pw)
1.1 millert 480: char *g, *u;
1.17 millert 481: struct passwd *pw;
482: {
483: return(TRUE);
484: }
485:
486: int
487: userpw_matches(s, u, pw)
488: char *s, *u;
489: struct passwd *pw;
1.1 millert 490: {
491: return(TRUE);
492: }
493:
494: int
1.3 millert 495: netgr_matches(n, h, sh, u)
496: char *n, *h, *sh, *u;
1.1 millert 497: {
498: return(TRUE);
1.2 millert 499: }
500:
501: void
502: set_fqdn()
503: {
504: return;
1.1 millert 505: }
506:
1.11 millert 507: int
1.17 millert 508: set_runaspw(user)
509: char *user;
510: {
511: extern int sudolineno, used_runas;
512:
513: if (used_runas) {
514: (void) fprintf(stderr,
515: "%s: runas_default set after old value is in use near line %d\n",
516: pedantic > 1 ? "Error" : "Warning", sudolineno);
517: if (pedantic > 1)
518: yyerror(NULL);
519: }
520: return(TRUE);
521: }
522:
523: int
1.11 millert 524: user_is_exempt()
525: {
526: return(TRUE);
527: }
528:
529: void
530: init_envtables()
531: {
532: return;
533: }
534:
1.1 millert 535: /*
536: * Assuming a parse error occurred, prompt the user for what they want
537: * to do now. Returns the first letter of their choice.
538: */
539: static char
540: whatnow()
541: {
542: int choice, c;
543:
544: for (;;) {
545: (void) fputs("What now? ", stdout);
546: choice = getchar();
547: for (c = choice; c != '\n' && c != EOF;)
548: c = getchar();
549:
1.3 millert 550: switch (choice) {
551: case EOF:
552: choice = 'x';
553: /* FALLTHROUGH */
554: case 'e':
555: case 'x':
556: case 'Q':
557: return(choice);
558: default:
559: (void) puts("Options are:");
560: (void) puts(" (e)dit sudoers file again");
561: (void) puts(" e(x)it without saving changes to sudoers file");
562: (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
1.1 millert 563: }
564: }
565: }
566:
567: /*
568: * Install signal handlers for visudo.
569: */
570: static void
571: setup_signals()
572: {
1.11 millert 573: sigaction_t sa;
1.1 millert 574:
575: /*
576: * Setup signal handlers to cleanup nicely.
577: */
1.11 millert 578: sigemptyset(&sa.sa_mask);
579: sa.sa_flags = SA_RESTART;
580: sa.sa_handler = Exit;
581: (void) sigaction(SIGTERM, &sa, NULL);
582: (void) sigaction(SIGHUP, &sa, NULL);
583: (void) sigaction(SIGINT, &sa, NULL);
584: (void) sigaction(SIGQUIT, &sa, NULL);
585: }
586:
587: static int
588: run_command(path, argv)
589: char *path;
590: char **argv;
591: {
592: int status;
593: pid_t pid;
594: sigset_t set, oset;
595:
596: (void) sigemptyset(&set);
597: (void) sigaddset(&set, SIGCHLD);
598: (void) sigprocmask(SIG_BLOCK, &set, &oset);
599:
600: switch (pid = fork()) {
601: case -1:
1.14 millert 602: warn("unable to run %s", path);
1.11 millert 603: Exit(-1);
604: break; /* NOTREACHED */
605: case 0:
606: (void) sigprocmask(SIG_SETMASK, &oset, NULL);
1.19 millert 607: endpwent();
608: closefrom(STDERR_FILENO + 1);
1.11 millert 609: execv(path, argv);
1.14 millert 610: warn("unable to run %s", path);
1.11 millert 611: _exit(127);
612: break; /* NOTREACHED */
613: }
614:
615: #ifdef sudo_waitpid
616: pid = sudo_waitpid(pid, &status, 0);
1.1 millert 617: #else
1.11 millert 618: pid = wait(&status);
619: #endif
620:
621: (void) sigprocmask(SIG_SETMASK, &oset, NULL);
622:
1.17 millert 623: if (pid == -1 || !WIFEXITED(status))
624: return(-1);
625: return(WEXITSTATUS(status));
1.11 millert 626: }
627:
628: static int
1.19 millert 629: check_syntax()
1.11 millert 630: {
631:
1.19 millert 632: if ((yyin = fopen(sudoers.path, "r")) == NULL) {
1.11 millert 633: if (!quiet)
1.19 millert 634: warn("unable to open %s", sudoers.path);
1.11 millert 635: exit(1);
636: }
637: init_parser();
638: if (yyparse() && parse_error != TRUE) {
639: if (!quiet)
1.19 millert 640: warnx("failed to parse %s file, unknown error", sudoers.path);
1.11 millert 641: parse_error = TRUE;
642: }
643: if (!quiet){
644: if (parse_error)
1.19 millert 645: (void) printf("parse error in %s near line %d\n", sudoers.path,
1.11 millert 646: errorlineno);
647: else
1.19 millert 648: (void) printf("%s file parsed OK\n", sudoers.path);
1.11 millert 649: }
650:
651: return(parse_error == TRUE);
1.1 millert 652: }
653:
1.19 millert 654: static FILE *
655: open_sudoers(sp)
656: struct sudoersfile *sp;
657: {
658: struct stat sb;
659: ssize_t nread;
660: FILE *fp;
661: char buf[PATH_MAX*2];
662: int tfd;
663:
664: /* Open and lock sudoers. */
665: sp->fd = open(sp->path, O_RDWR | O_CREAT, SUDOERS_MODE);
666: if (sp->fd == -1)
667: err(1, "%s", sp->path);
668: if (!lock_file(sp->fd, SUDO_TLOCK))
669: errx(1, "%s busy, try again later", sp->path);
670: if ((fp = fdopen(sp->fd, "r")) == NULL)
671: err(1, "%s", sp->path);
672:
673: /* Stash sudoers size and mtime. */
674: #ifdef HAVE_FSTAT
675: if (fstat(sp->fd, &sb) == -1)
676: #else
677: if (stat(sp->path, &sb) == -1)
678: #endif
679: err(1, "can't stat %s", sp->path);
680: sp->orig_size = sb.st_size;
681: sp->orig_mtim.tv_sec = mtim_getsec(sb);
682: sp->orig_mtim.tv_nsec = mtim_getnsec(sb);
683:
684: /* Create the temp file. */
685: tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
686: if (tfd < 0)
687: err(1, "%s", sp->tpath);
688:
689: /* Install signal handlers to clean up temp file if we are killed. */
690: setup_signals();
691:
692: /* Copy sp->path -> sp->tpath. */
693: if (sp->orig_size != 0) {
694: while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
695: if (write(tfd, buf, nread) != nread) {
696: warn("write error");
697: Exit(-1);
698: }
699:
700: /* Add missing newline at EOF if needed. */
701: if (nread > 0 && buf[nread - 1] != '\n') {
702: buf[0] = '\n';
703: write(tfd, buf, 1);
704: }
705: }
706: (void) close(tfd);
707: rewind(fp);
708:
709: return(fp);
710: }
711:
712: static char *
713: get_editor(args)
714: char **args;
715: {
716: char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
717:
718: /*
719: * Check VISUAL and EDITOR environment variables to see which editor
720: * the user wants to use (we may not end up using it though).
721: * If the path is not fully-qualified, make it so and check that
722: * the specified executable actually exists.
723: */
724: UserEditorArgs = NULL;
725: if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
726: UserEditor = getenv("EDITOR");
727: if (UserEditor && *UserEditor == '\0')
728: UserEditor = NULL;
729: else if (UserEditor) {
730: UserEditorArgs = get_args(UserEditor);
731: if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
732: UserEditor = Editor;
733: } else {
734: if (def_env_editor) {
735: /* If we are honoring $EDITOR this is a fatal error. */
736: warnx("specified editor (%s) doesn't exist!", UserEditor);
737: Exit(-1);
738: } else {
739: /* Otherwise, just ignore $EDITOR. */
740: UserEditor = NULL;
741: }
742: }
743: }
744:
745: /*
746: * See if we can use the user's choice of editors either because
747: * we allow any $EDITOR or because $EDITOR is in the allowable list.
748: */
749: Editor = EditorArgs = EditorPath = NULL;
750: if (def_env_editor && UserEditor) {
751: Editor = UserEditor;
752: EditorArgs = UserEditorArgs;
753: } else if (UserEditor) {
754: struct stat editor_sb;
755: struct stat user_editor_sb;
756: char *base, *userbase;
757:
758: if (stat(UserEditor, &user_editor_sb) != 0) {
759: /* Should never happen since we already checked above. */
760: warn("unable to stat editor (%s)", UserEditor);
761: Exit(-1);
762: }
763: EditorPath = estrdup(def_editor);
764: Editor = strtok(EditorPath, ":");
765: do {
766: EditorArgs = get_args(Editor);
767: /*
768: * Both Editor and UserEditor should be fully qualified but
769: * check anyway...
770: */
771: if ((base = strrchr(Editor, '/')) == NULL)
772: continue;
773: if ((userbase = strrchr(UserEditor, '/')) == NULL) {
774: Editor = NULL;
775: break;
776: }
777: base++, userbase++;
778:
779: /*
780: * We compare the basenames first and then use stat to match
781: * for sure.
782: */
783: if (strcmp(base, userbase) == 0) {
784: if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
785: && (editor_sb.st_mode & 0000111) &&
786: editor_sb.st_dev == user_editor_sb.st_dev &&
787: editor_sb.st_ino == user_editor_sb.st_ino)
788: break;
789: }
790: } while ((Editor = strtok(NULL, ":")));
791: }
792:
793: /*
794: * Can't use $EDITOR, try each element of def_editor until we
795: * find one that exists, is regular, and is executable.
796: */
797: if (Editor == NULL || *Editor == '\0') {
798: efree(EditorPath);
799: EditorPath = estrdup(def_editor);
800: Editor = strtok(EditorPath, ":");
801: do {
802: EditorArgs = get_args(Editor);
803: if (sudo_goodpath(Editor, NULL))
804: break;
805: } while ((Editor = strtok(NULL, ":")));
806:
807: /* Bleah, none of the editors existed! */
808: if (Editor == NULL || *Editor == '\0') {
809: warnx("no editor found (editor path = %s)", def_editor);
810: Exit(-1);
811: }
812: }
813: *args = EditorArgs;
814: return(Editor);
815: }
816:
817: /*
818: * Split out any command line arguments and return them.
819: */
820: static char *
821: get_args(cmnd)
822: char *cmnd;
823: {
824: char *args;
825:
826: args = cmnd;
827: while (*args && !isblank((unsigned char) *args))
828: args++;
829: if (*args) {
830: *args++ = '\0';
831: while (*args && isblank((unsigned char) *args))
832: args++;
833: }
834: return(*args ? args : NULL);
835: }
836:
1.1 millert 837: /*
838: * Unlink the sudoers temp file (if it exists) and exit.
839: * Used in place of a normal exit() and as a signal handler.
1.10 millert 840: * A positive parameter indicates we were called as a signal handler.
1.1 millert 841: */
842: static RETSIGTYPE
843: Exit(sig)
844: int sig;
845: {
1.16 millert 846: #define emsg " exiting due to signal.\n"
1.10 millert 847:
1.19 millert 848: (void) unlink(sudoers.tpath);
1.1 millert 849:
1.10 millert 850: if (sig > 0) {
1.14 millert 851: write(STDERR_FILENO, getprogname(), strlen(getprogname()));
1.10 millert 852: write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
1.16 millert 853: _exit(sig);
1.10 millert 854: }
855: exit(-sig);
1.1 millert 856: }
857:
858: static void
859: usage()
860: {
1.19 millert 861: (void) fprintf(stderr, "usage: %s [-c] [-q] [-s] [-V] [-f sudoers]\n",
1.14 millert 862: getprogname());
1.1 millert 863: exit(1);
864: }