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