fix typo in changelog
[debian/sudo] / visudo.c
1 /*
2  * Copyright (c) 1996, 1998-2005 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
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.
7  *
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.
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.
19  */
20
21 /*
22  * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
23  */
24
25 #define _SUDO_MAIN
26
27 #ifdef __TANDEM
28 # include <floss.h>
29 #endif
30
31 #include <config.h>
32
33 #include <sys/types.h>
34 #include <sys/param.h>
35 #include <sys/stat.h>
36 #include <sys/time.h>
37 #ifndef __TANDEM
38 # include <sys/file.h>
39 #endif
40 #include <sys/wait.h>
41 #include <stdio.h>
42 #ifdef STDC_HEADERS
43 # include <stdlib.h>
44 # include <stddef.h>
45 #else
46 # ifdef HAVE_STDLIB_H
47 #  include <stdlib.h>
48 # endif
49 #endif /* STDC_HEADERS */
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 */
57 #ifdef HAVE_UNISTD_H
58 #include <unistd.h>
59 #endif /* HAVE_UNISTD_H */
60 #ifdef HAVE_ERR_H
61 # include <err.h>
62 #else
63 # include "emul/err.h"
64 #endif /* HAVE_ERR_H */
65 #include <ctype.h>
66 #include <pwd.h>
67 #if TIME_WITH_SYS_TIME
68 # include <time.h>
69 #endif
70 #include <signal.h>
71 #include <errno.h>
72 #include <fcntl.h>
73 #ifndef HAVE_TIMESPEC
74 # include <emul/timespec.h>
75 #endif
76
77 #include "sudo.h"
78 #include "version.h"
79
80 #ifndef lint
81 __unused static const char rcsid[] = "$Sudo: visudo.c,v 1.166.2.11 2008/06/21 00:47:52 millert Exp $";
82 #endif /* lint */
83
84 struct sudoersfile {
85     char *path;
86     char *tpath;
87     int fd;
88     off_t orig_size;
89     struct timespec orig_mtim;
90 };
91
92 /*
93  * Function prototypes
94  */
95 static void usage               __P((void)) __attribute__((__noreturn__));
96 static char whatnow             __P((void));
97 static RETSIGTYPE Exit          __P((int));
98 static void edit_sudoers        __P((struct sudoersfile *, char *, char *, int));
99 static void visudo              __P((struct sudoersfile *, char *, char *));
100 static void setup_signals       __P((void));
101 static void install_sudoers     __P((struct sudoersfile *, int));
102 static int check_syntax         __P(());
103 static int run_command          __P((char *, char **));
104 static char *get_args           __P((char *));
105 static char *get_editor         __P((char **));
106 static FILE *open_sudoers       __P((struct sudoersfile *));
107
108 int command_matches             __P((char *, char *));
109 int addr_matches                __P((char *));
110 int hostname_matches            __P((char *, char *, char *));
111 int netgr_matches               __P((char *, char *, char *, char *));
112 int usergr_matches              __P((char *, char *, struct passwd *));
113 int userpw_matches              __P((char *, char *, struct passwd *));
114 void init_parser                __P((void));
115 void yyerror                    __P((char *));
116 void yyrestart                  __P((FILE *));
117
118 /*
119  * External globals exported by the parser
120  */
121 extern FILE *yyin;
122 extern int errorlineno;
123 extern int pedantic;
124 extern int quiet;
125
126 /* For getopt(3) */
127 extern char *optarg;
128 extern int optind;
129
130 /*
131  * Globals
132  */
133 char **Argv;
134 struct sudo_user sudo_user;
135 int Argc, parse_error = FALSE;
136 static struct sudoersfile sudoers;
137
138 int
139 main(argc, argv)
140     int argc;
141     char **argv;
142 {
143     char *args, *editor;
144     int ch, checkonly, n, oldperms;
145
146     /* Initialize sudoers struct. */
147     sudoers.path = _PATH_SUDOERS;
148     sudoers.tpath = _PATH_SUDOERS_TMP;
149     sudoers.fd = -1;
150
151     /* Warn about aliases that are used before being defined. */
152     pedantic = 1;
153
154     Argv = argv;
155     if ((Argc = argc) < 1)
156         usage();
157
158     /*
159      * Arg handling.
160      */
161     checkonly = oldperms = FALSE;
162     while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
163         switch (ch) {
164             case 'V':
165                 (void) printf("%s version %s\n", getprogname(), version);
166                 exit(0);
167             case 'c':
168                 checkonly++;            /* check mode */
169                 break;
170             case 'f':                   /* sudoers file path */
171                 sudoers.path = optarg;
172                 easprintf(&sudoers.tpath, "%s.tmp", optarg);
173                 oldperms = TRUE;
174                 break;
175             case 's':
176                 pedantic++;             /* strict mode */
177                 break;
178             case 'q':
179                 quiet++;                /* quiet mode */
180                 break;
181             default:
182                 usage();
183         }
184     }
185     argc -= optind;
186     argv += optind;
187     if (argc)
188         usage();
189
190     /* Mock up a fake sudo_user struct. */
191     user_host = user_shost = user_cmnd = "";
192     if ((sudo_user.pw = getpwuid(getuid())) == NULL)
193         errx(1, "you don't exist in the passwd database");
194
195     /* Setup defaults data structures. */
196     init_defaults();
197
198     if (checkonly)
199         exit(check_syntax());
200
201     /*
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);
217
218     /* Install the new sudoers file. */
219     install_sudoers(&sudoers, oldperms);
220
221     exit(0);
222 }
223
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 */
240
241     /* Make timestamp on temp file match original. */
242     (void) touch(-1, sp->tpath, &sp->orig_mtim);
243
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;
277
278     /*
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 (?!?!).
283      */
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);
294         }
295         if (sb.st_size == 0) {
296             warnx("zero length temporary file (%s), %s unchanged",
297                 sp->tpath, sp->path);
298             Exit(-1);
299         }
300     } else {
301         warnx("editor (%s) failed, %s unchanged", editor, sp->path);
302         Exit(-1);
303     }
304
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);
321         }
322     }
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;
335
336     /*
337      * Parse the edited sudoers file and do sanity checking
338      */
339     do {
340         edit_sudoers(sp, editor, args, errorlineno);
341
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);
347         }
348
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;
365         }
366         fclose(yyin);
367
368         /*
369          * Got an error, prompt the user for what to do now
370          */
371         if (parse_error) {
372             switch (whatnow()) {
373                 case 'Q' :      parse_error = FALSE;    /* ignore parse error */
374                                 break;
375                 case 'x' :      Exit(0);
376                                 break;
377             }
378         }
379     } while (parse_error);
380 }
381
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
387 install_sudoers(sp, oldperms)
388     struct sudoersfile *sp;
389     int oldperms;
390 {
391     struct stat sb;
392
393     /*
394      * Change mode and ownership of temp file so when
395      * we move it to sp->path things are kosher.
396      */
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         }
417     }
418
419     /*
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.
423      */
424     if (rename(sp->tpath, sp->path) != 0) {
425         if (errno == EXDEV) {
426             char *av[4];
427             warnx("%s and %s not on the same file system, using mv to rename",
428               sp->tpath, sp->path);
429
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;
435             av[1] = sp->tpath;
436             av[2] = sp->path;
437             av[3] = NULL;
438
439             /* And run it... */
440             if (run_command(_PATH_MV, av)) {
441                 warnx("command failed: '%s %s %s', %s unchanged",
442                     _PATH_MV, sp->tpath, sp->path, sp->path);
443                 Exit(-1);
444             }
445         } else {
446             warn("error renaming %s, %s unchanged", sp->tpath, sp->path);
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
457 command_matches(path, sudoers_args)
458     char *path;
459     char *sudoers_args;
460 {
461     return(TRUE);
462 }
463
464 int
465 addr_matches(n)
466     char *n;
467 {
468     return(TRUE);
469 }
470
471 int
472 hostname_matches(s, l, p)
473     char *s, *l, *p;
474 {
475     return(TRUE);
476 }
477
478 int
479 usergr_matches(g, u, pw)
480     char *g, *u;
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;
490 {
491     return(TRUE);
492 }
493
494 int
495 netgr_matches(n, h, sh, u)
496     char *n, *h, *sh, *u;
497 {
498     return(TRUE);
499 }
500
501 void
502 set_fqdn()
503 {
504     return;
505 }
506
507 int
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
524 user_is_exempt()
525 {
526     return(TRUE);
527 }
528
529 void
530 init_envtables()
531 {
532     return;
533 }
534
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
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");
563         }
564     }
565 }
566
567 /*
568  * Install signal handlers for visudo.
569  */
570 static void
571 setup_signals()
572 {
573         sigaction_t sa;
574
575         /*
576          * Setup signal handlers to cleanup nicely.
577          */
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, rv;
594
595     switch (pid = fork()) {
596         case -1:
597             warn("unable to run %s", path);
598             Exit(-1);
599             break;      /* NOTREACHED */
600         case 0:
601             endpwent();
602             closefrom(STDERR_FILENO + 1);
603             execv(path, argv);
604             warn("unable to run %s", path);
605             _exit(127);
606             break;      /* NOTREACHED */
607     }
608
609     do {
610 #ifdef sudo_waitpid
611         rv = sudo_waitpid(pid, &status, 0);
612 #else
613         rv = wait(&status);
614 #endif
615     } while (rv == -1 && errno == EINTR);
616
617     if (rv == -1 || !WIFEXITED(status))
618         return(-1);
619     return(WEXITSTATUS(status));
620 }
621
622 static int
623 check_syntax()
624 {
625
626     if ((yyin = fopen(sudoers.path, "r")) == NULL) {
627         if (!quiet)
628             warn("unable to open %s", sudoers.path);
629         exit(1);
630     }
631     init_parser();
632     if (yyparse() && parse_error != TRUE) {
633         if (!quiet)
634             warnx("failed to parse %s file, unknown error", sudoers.path);
635         parse_error = TRUE;
636     }
637     if (!quiet){
638         if (parse_error)
639             (void) printf("parse error in %s near line %d\n", sudoers.path,
640                 errorlineno);
641         else
642             (void) printf("%s file parsed OK\n", sudoers.path);
643     }
644
645     return(parse_error == TRUE);
646 }
647
648 static FILE *
649 open_sudoers(sp)
650     struct sudoersfile *sp;
651 {
652     struct stat sb;
653     ssize_t nread;
654     FILE *fp;
655     char buf[PATH_MAX*2];
656     int tfd;
657
658     /* Open and lock sudoers. */
659     sp->fd = open(sp->path, O_RDWR | O_CREAT, SUDOERS_MODE);
660     if (sp->fd == -1)
661         err(1, "%s", sp->path);
662     if (!lock_file(sp->fd, SUDO_TLOCK))
663         errx(1, "%s busy, try again later", sp->path);
664     if ((fp = fdopen(sp->fd, "r")) == NULL)
665         err(1, "%s", sp->path);
666
667     /* Stash sudoers size and mtime. */
668 #ifdef HAVE_FSTAT
669     if (fstat(sp->fd, &sb) == -1)
670 #else
671     if (stat(sp->path, &sb) == -1)
672 #endif
673         err(1, "can't stat %s", sp->path);
674     sp->orig_size = sb.st_size;
675     sp->orig_mtim.tv_sec = mtim_getsec(sb);
676     sp->orig_mtim.tv_nsec = mtim_getnsec(sb);
677
678     /* Create the temp file. */
679     tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
680     if (tfd < 0)
681         err(1, "%s", sp->tpath);
682
683     /* Install signal handlers to clean up temp file if we are killed. */
684     setup_signals();
685
686     /* Copy sp->path -> sp->tpath. */
687     if (sp->orig_size != 0) {
688         while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
689             if (write(tfd, buf, nread) != nread) {
690                 warn("write error");
691                 Exit(-1);
692             }
693
694         /* Add missing newline at EOF if needed. */
695         if (nread > 0 && buf[nread - 1] != '\n') {
696             buf[0] = '\n';
697             write(tfd, buf, 1);
698         }
699     }
700     (void) close(tfd);
701     rewind(fp);
702
703     return(fp);
704 }
705
706 static char *
707 get_editor(args)
708     char **args;
709 {
710     char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
711
712     /*
713      * Check VISUAL and EDITOR environment variables to see which editor
714      * the user wants to use (we may not end up using it though).
715      * If the path is not fully-qualified, make it so and check that
716      * the specified executable actually exists.
717      */
718     UserEditorArgs = NULL;
719     if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
720         UserEditor = getenv("EDITOR");
721     if (UserEditor && *UserEditor == '\0')
722         UserEditor = NULL;
723     else if (UserEditor) {
724         UserEditorArgs = get_args(UserEditor);
725         if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
726             UserEditor = Editor;
727         } else {
728             if (def_env_editor) {
729                 /* If we are honoring $EDITOR this is a fatal error. */
730                 warnx("specified editor (%s) doesn't exist!", UserEditor);
731                 Exit(-1);
732             } else {
733                 /* Otherwise, just ignore $EDITOR. */
734                 UserEditor = NULL;
735             }
736         }
737     }
738
739     /*
740      * See if we can use the user's choice of editors either because
741      * we allow any $EDITOR or because $EDITOR is in the allowable list.
742      */
743     Editor = EditorArgs = EditorPath = NULL;
744     if (def_env_editor && UserEditor) {
745         Editor = UserEditor;
746         EditorArgs = UserEditorArgs;
747     } else if (UserEditor) {
748         struct stat editor_sb;
749         struct stat user_editor_sb;
750         char *base, *userbase;
751
752         if (stat(UserEditor, &user_editor_sb) != 0) {
753             /* Should never happen since we already checked above. */
754             warn("unable to stat editor (%s)", UserEditor);
755             Exit(-1);
756         }
757         EditorPath = estrdup(def_editor);
758         Editor = strtok(EditorPath, ":");
759         do {
760             EditorArgs = get_args(Editor);
761             /*
762              * Both Editor and UserEditor should be fully qualified but
763              * check anyway...
764              */
765             if ((base = strrchr(Editor, '/')) == NULL)
766                 continue;
767             if ((userbase = strrchr(UserEditor, '/')) == NULL) {
768                 Editor = NULL;
769                 break;
770             }
771             base++, userbase++;
772
773             /*
774              * We compare the basenames first and then use stat to match
775              * for sure.
776              */
777             if (strcmp(base, userbase) == 0) {
778                 if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
779                     && (editor_sb.st_mode & 0000111) &&
780                     editor_sb.st_dev == user_editor_sb.st_dev &&
781                     editor_sb.st_ino == user_editor_sb.st_ino)
782                     break;
783             }
784         } while ((Editor = strtok(NULL, ":")));
785     }
786
787     /*
788      * Can't use $EDITOR, try each element of def_editor until we
789      * find one that exists, is regular, and is executable.
790      */
791     if (Editor == NULL || *Editor == '\0') {
792         efree(EditorPath);
793         EditorPath = estrdup(def_editor);
794         Editor = strtok(EditorPath, ":");
795         do {
796             EditorArgs = get_args(Editor);
797             if (sudo_goodpath(Editor, NULL))
798                 break;
799         } while ((Editor = strtok(NULL, ":")));
800
801         /* Bleah, none of the editors existed! */
802         if (Editor == NULL || *Editor == '\0') {
803             warnx("no editor found (editor path = %s)", def_editor);
804             Exit(-1);
805         }
806     }
807     *args = EditorArgs;
808     return(Editor);
809 }
810
811 /*
812  * Split out any command line arguments and return them.
813  */
814 static char *
815 get_args(cmnd)
816     char *cmnd;
817 {
818     char *args;
819
820     args = cmnd;
821     while (*args && !isblank((unsigned char) *args))
822         args++;
823     if (*args) {
824         *args++ = '\0';
825         while (*args && isblank((unsigned char) *args))
826             args++;
827     }
828     return(*args ? args : NULL);
829 }
830
831 /*
832  * Unlink the sudoers temp file (if it exists) and exit.
833  * Used in place of a normal exit() and as a signal handler.
834  * A positive parameter indicates we were called as a signal handler.
835  */
836 static RETSIGTYPE
837 Exit(sig)
838     int sig;
839 {
840 #define emsg     " exiting due to signal.\n"
841
842     (void) unlink(sudoers.tpath);
843
844     if (sig > 0) {
845         write(STDERR_FILENO, getprogname(), strlen(getprogname()));
846         write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
847         _exit(sig);
848     }
849     exit(-sig);
850 }
851
852 static void
853 usage()
854 {
855     (void) fprintf(stderr, "usage: %s [-c] [-q] [-s] [-V] [-f sudoers]\n",
856         getprogname());
857     exit(1);
858 }