update Debian standards version
[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 #ifndef lint
90 __unused static const char rcsid[] = "$Sudo: visudo.c,v 1.234 2009/05/25 12:02:42 millert Exp $";
91 #endif /* lint */
92
93 struct sudoersfile {
94     char *path;
95     char *tpath;
96     int fd;
97     int tfd;
98     int modified;
99     int doedit;
100     struct sudoersfile *next;
101 };
102
103 /*
104  * Function prototypes
105  */
106 static RETSIGTYPE quit          __P((int));
107 static char *get_args           __P((char *));
108 static char *get_editor         __P((char **));
109 static void get_hostname        __P((void));
110 static char whatnow             __P((void));
111 static int check_aliases        __P((int, int));
112 static int check_syntax         __P((char *, int, int));
113 static int edit_sudoers         __P((struct sudoersfile *, char *, char *, int));
114 static int install_sudoers      __P((struct sudoersfile *, int));
115 static int print_unused         __P((void *, void *));
116 static int reparse_sudoers      __P((char *, char *, int, int));
117 static int run_command          __P((char *, char **));
118 static void print_undefined     __P((char *name, int, int, int));
119 static void setup_signals       __P((void));
120 static void usage               __P((void)) __attribute__((__noreturn__));
121
122 extern void yyerror             __P((const char *));
123 extern void yyrestart           __P((FILE *));
124
125 /*
126  * External globals exported by the parser
127  */
128 extern struct rbtree *aliases;
129 extern FILE *yyin;
130 extern char *sudoers, *errorfile;
131 extern int errorlineno, parse_error;
132 /* For getopt(3) */
133 extern char *optarg;
134 extern int optind;
135
136 /*
137  * Globals
138  */
139 int Argc;
140 char **Argv;
141 int num_interfaces;
142 struct interface *interfaces;
143 struct sudo_user sudo_user;
144 struct passwd *list_pw;
145 static struct sudoerslist {
146     struct sudoersfile *first, *last;
147 } sudoerslist;
148 static struct rbtree *alias_freelist;
149
150 int
151 main(argc, argv)
152     int argc;
153     char **argv;
154 {
155     struct sudoersfile *sp;
156     char *args, *editor, *sudoers_path;
157     int ch, checkonly, quiet, strict, oldperms;
158 #if defined(SUDO_DEVEL) && defined(__OpenBSD__)
159     extern char *malloc_options;
160     malloc_options = "AFGJPR";
161 #endif
162
163     Argv = argv;
164     if ((Argc = argc) < 1)
165         usage();
166
167     /*
168      * Arg handling.
169      */
170     checkonly = oldperms = quiet = strict = FALSE;
171     sudoers_path = _PATH_SUDOERS;
172     while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
173         switch (ch) {
174             case 'V':
175                 (void) printf("%s version %s\n", getprogname(), PACKAGE_VERSION);
176                 exit(0);
177             case 'c':
178                 checkonly++;            /* check mode */
179                 break;
180             case 'f':
181                 sudoers_path = optarg;  /* sudoers file path */
182                 oldperms = TRUE;
183                 break;
184             case 's':
185                 strict++;               /* strict mode */
186                 break;
187             case 'q':
188                 quiet++;                /* quiet mode */
189                 break;
190             default:
191                 usage();
192         }
193     }
194     argc -= optind;
195     argv += optind;
196     if (argc)
197         usage();
198
199     sudo_setpwent();
200     sudo_setgrent();
201
202     /* Mock up a fake sudo_user struct. */
203     user_cmnd = "";
204     if ((sudo_user.pw = sudo_getpwuid(getuid())) == NULL)
205         errorx(1, "you don't exist in the passwd database");
206     get_hostname();
207
208     /* Setup defaults data structures. */
209     init_defaults();
210
211     if (checkonly)
212         exit(check_syntax(sudoers_path, quiet, strict));
213
214     /*
215      * Parse the existing sudoers file(s) in quiet mode to highlight any
216      * existing errors and to pull in editor and env_editor conf values.
217      */
218     if ((yyin = open_sudoers(sudoers_path, TRUE, NULL)) == NULL) {
219         error(1, "%s", sudoers_path);
220     }
221     init_parser(sudoers_path, 0);
222     yyparse();
223     (void) update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER);
224
225     editor = get_editor(&args);
226
227     /* Install signal handlers to clean up temp files if we are killed. */
228     setup_signals();
229
230     /* Edit the sudoers file(s) */
231     tq_foreach_fwd(&sudoerslist, sp) {
232         if (!sp->doedit)
233             continue;
234         if (sp != tq_first(&sudoerslist)) {
235             printf("press return to edit %s: ", sp->path);
236             while ((ch = getchar()) != EOF && ch != '\n')
237                     continue;
238         }
239         edit_sudoers(sp, editor, args, -1);
240     }
241
242     /* Check edited files for a parse error and re-edit any that fail. */
243     reparse_sudoers(editor, args, strict, quiet);
244
245     /* Install the sudoers temp files. */
246     tq_foreach_fwd(&sudoerslist, sp) {
247         if (!sp->modified)
248             (void) unlink(sp->tpath);
249         else
250             (void) install_sudoers(sp, oldperms);
251     }
252
253     exit(0);
254 }
255
256 /*
257  * Edit each sudoers file.
258  * Returns TRUE on success, else FALSE.
259  */
260 static int
261 edit_sudoers(sp, editor, args, lineno)
262     struct sudoersfile *sp;
263     char *editor, *args;
264     int lineno;
265 {
266     int tfd;                            /* sudoers temp file descriptor */
267     int modified;                       /* was the file modified? */
268     int ac;                             /* argument count */
269     char **av;                          /* argument vector for run_command */
270     char *cp;                           /* scratch char pointer */
271     char buf[PATH_MAX*2];               /* buffer used for copying files */
272     char linestr[64];                   /* string version of lineno */
273     struct timespec ts1, ts2;           /* time before and after edit */
274     struct timespec orig_mtim;          /* starting mtime of sudoers file */
275     off_t orig_size;                    /* starting size of sudoers file */
276     ssize_t nread;                      /* number of bytes read */
277     struct stat sb;                     /* stat buffer */
278
279 #ifdef HAVE_FSTAT
280     if (fstat(sp->fd, &sb) == -1)
281 #else
282     if (stat(sp->path, &sb) == -1)
283 #endif
284         error(1, "can't stat %s", sp->path);
285     orig_size = sb.st_size;
286     orig_mtim.tv_sec = mtim_getsec(sb);
287     orig_mtim.tv_nsec = mtim_getnsec(sb);
288
289     /* Create the temp file if needed and set timestamp. */
290     if (sp->tpath == NULL) {
291         easprintf(&sp->tpath, "%s.tmp", sp->path);
292         tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
293         if (tfd < 0)
294             error(1, "%s", sp->tpath);
295
296         /* Copy sp->path -> sp->tpath and reset the mtime. */
297         if (orig_size != 0) {
298             (void) lseek(sp->fd, (off_t)0, SEEK_SET);
299             while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
300                 if (write(tfd, buf, nread) != nread)
301                     error(1, "write error");
302
303             /* Add missing newline at EOF if needed. */
304             if (nread > 0 && buf[nread - 1] != '\n') {
305                 buf[0] = '\n';
306                 write(tfd, buf, 1);
307             }
308         }
309         (void) close(tfd);
310     }
311     (void) touch(-1, sp->tpath, &orig_mtim);
312
313     /* Find the length of the argument vector */
314     ac = 3 + (lineno > 0);
315     if (args) {
316         int wasblank;
317
318         ac++;
319         for (wasblank = FALSE, cp = args; *cp; cp++) {
320             if (isblank((unsigned char) *cp))
321                 wasblank = TRUE;
322             else if (wasblank) {
323                 wasblank = FALSE;
324                 ac++;
325             }
326         }
327     }
328
329     /* Build up argument vector for the command */
330     av = emalloc2(ac, sizeof(char *));
331     if ((av[0] = strrchr(editor, '/')) != NULL)
332         av[0]++;
333     else
334         av[0] = editor;
335     ac = 1;
336     if (lineno > 0) {
337         (void) snprintf(linestr, sizeof(linestr), "+%d", lineno);
338         av[ac++] = linestr;
339     }
340     if (args) {
341         for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t")))
342             av[ac++] = cp;
343     }
344     av[ac++] = sp->tpath;
345     av[ac++] = NULL;
346
347     /*
348      * Do the edit:
349      *  We cannot check the editor's exit value against 0 since
350      *  XPG4 specifies that vi's exit value is a function of the
351      *  number of errors during editing (?!?!).
352      */
353     gettime(&ts1);
354     if (run_command(editor, av) != -1) {
355         gettime(&ts2);
356         /*
357          * Sanity checks.
358          */
359         if (stat(sp->tpath, &sb) < 0) {
360             warningx("cannot stat temporary file (%s), %s unchanged",
361                 sp->tpath, sp->path);
362             return(FALSE);
363         }
364         if (sb.st_size == 0 && orig_size != 0) {
365             warningx("zero length temporary file (%s), %s unchanged",
366                 sp->tpath, sp->path);
367             sp->modified = TRUE;
368             return(FALSE);
369         }
370     } else {
371         warningx("editor (%s) failed, %s unchanged", editor, sp->path);
372         return(FALSE);
373     }
374
375     /* Set modified bit if use changed the file. */
376     modified = TRUE;
377     if (orig_size == sb.st_size &&
378         orig_mtim.tv_sec == mtim_getsec(sb) &&
379         orig_mtim.tv_nsec == mtim_getnsec(sb)) {
380         /*
381          * If mtime and size match but the user spent no measurable
382          * time in the editor we can't tell if the file was changed.
383          */
384 #ifdef HAVE_TIMESPECSUB2
385         timespecsub(&ts1, &ts2);
386 #else
387         timespecsub(&ts1, &ts2, &ts2);
388 #endif
389         if (timespecisset(&ts2))
390             modified = FALSE;
391     }
392
393     /*
394      * If modified in this edit session, mark as modified.
395      */
396     if (modified)
397         sp->modified = modified;
398     else
399         warningx("%s unchanged", sp->tpath);
400
401     return(TRUE);
402 }
403
404 /*
405  * Parse sudoers after editing and re-edit any ones that caused a parse error.
406  * Returns TRUE on success, else FALSE.
407  */
408 static int
409 reparse_sudoers(editor, args, strict, quiet)
410     char *editor, *args;
411     int strict, quiet;
412 {
413     struct sudoersfile *sp, *last;
414     FILE *fp;
415     int ch;
416
417     /*
418      * Parse the edited sudoers files and do sanity checking
419      */
420     do {
421         sp = tq_first(&sudoerslist);
422         last = tq_last(&sudoerslist);
423         fp = fopen(sp->tpath, "r+");
424         if (fp == NULL)
425             errorx(1, "can't re-open temporary file (%s), %s unchanged.",
426                 sp->tpath, sp->path);
427
428         /* Clean slate for each parse */
429         init_defaults();
430         init_parser(sp->path, quiet);
431
432         /* Parse the sudoers temp file */
433         yyrestart(fp);
434         if (yyparse() && parse_error != TRUE) {
435             warningx("unabled to parse temporary file (%s), unknown error",
436                 sp->tpath);
437             parse_error = TRUE;
438         }
439         fclose(yyin);
440         if (check_aliases(strict, quiet) != 0)
441             parse_error = TRUE;
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 != TRUE) {
706         if (!quiet)
707             warningx("failed to parse %s file, unknown error", sudoers_path);
708         parse_error = TRUE;
709     }
710     if (!parse_error) {
711         if (check_aliases(strict, quiet) != 0)
712             parse_error = TRUE;
713     }
714     error = parse_error;
715     if (!quiet) {
716         if (parse_error)
717             (void) printf("parse error in %s near line %d\n", errorfile,
718                 errorlineno);
719         else
720             (void) printf("%s: parsed OK\n", sudoers_path);
721     }
722     /* Check mode and owner in strict mode. */
723 #ifdef HAVE_FSTAT
724     if (strict && fstat(fileno(yyin), &sb) == 0)
725 #else
726     if (strict && stat(sudoers_path, &sb) == 0)
727 #endif
728     {
729         if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID) {
730             error = TRUE;
731             if (!quiet) {
732                 fprintf(stderr, "%s: wrong owner (uid, gid) should be (%d, %d)\n",
733                     sudoers_path, SUDOERS_UID, SUDOERS_GID);
734                 }
735         }
736         if ((sb.st_mode & 07777) != SUDOERS_MODE) {
737             error = TRUE;
738             if (!quiet) {
739                 fprintf(stderr, "%s: bad permissions, should be mode 0%o\n",
740                     sudoers_path, SUDOERS_MODE);
741             }
742         }
743     }
744
745     return(error);
746 }
747
748 /*
749  * Used to open (and lock) the initial sudoers file and to also open
750  * any subsequent files #included via a callback from the parser.
751  */
752 FILE *
753 open_sudoers(path, doedit, keepopen)
754     const char *path;
755     int doedit;
756     int *keepopen;
757 {
758     struct sudoersfile *entry;
759     FILE *fp;
760
761     /* Check for existing entry */
762     tq_foreach_fwd(&sudoerslist, entry) {
763         if (strcmp(path, entry->path) == 0)
764             break;
765     }
766     if (entry == NULL) {
767         entry = emalloc(sizeof(*entry));
768         entry->path = estrdup(path);
769         entry->modified = 0;
770         entry->next = NULL;
771         entry->fd = open(entry->path, O_RDWR | O_CREAT, SUDOERS_MODE);
772         entry->tpath = NULL;
773         entry->tfd = -1;
774         entry->doedit = doedit;
775         if (entry->fd == -1) {
776             warning("%s", entry->path);
777             efree(entry);
778             return(NULL);
779         }
780         if (!lock_file(entry->fd, SUDO_TLOCK))
781             errorx(1, "%s busy, try again later", entry->path);
782         if ((fp = fdopen(entry->fd, "r")) == NULL)
783             error(1, "%s", entry->path);
784         /* XXX - macro here? */
785         if (sudoerslist.last == NULL)
786             sudoerslist.first = sudoerslist.last = entry;
787         else {
788             sudoerslist.last->next = entry;
789             sudoerslist.last = entry;
790         }
791     } else {
792         /* Already exists, open .tmp version if there is one. */
793         if (entry->tpath != NULL) {
794             if ((fp = fopen(entry->tpath, "r")) == NULL)
795                 error(1, "%s", entry->tpath);
796         } else {
797             if ((fp = fdopen(entry->fd, "r")) == NULL)
798                 error(1, "%s", entry->path);
799             rewind(fp);
800         }
801     }
802     if (keepopen != NULL)
803         *keepopen = TRUE;
804     return(fp);
805 }
806
807 static char *
808 get_editor(args)
809     char **args;
810 {
811     char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
812
813     /*
814      * Check VISUAL and EDITOR environment variables to see which editor
815      * the user wants to use (we may not end up using it though).
816      * If the path is not fully-qualified, make it so and check that
817      * the specified executable actually exists.
818      */
819     UserEditorArgs = NULL;
820     if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
821         UserEditor = getenv("EDITOR");
822     if (UserEditor && *UserEditor == '\0')
823         UserEditor = NULL;
824     else if (UserEditor) {
825         UserEditorArgs = get_args(UserEditor);
826         if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
827             UserEditor = Editor;
828         } else {
829             if (def_env_editor) {
830                 /* If we are honoring $EDITOR this is a fatal error. */
831                 errorx(1, "specified editor (%s) doesn't exist!", UserEditor);
832             } else {
833                 /* Otherwise, just ignore $EDITOR. */
834                 UserEditor = NULL;
835             }
836         }
837     }
838
839     /*
840      * See if we can use the user's choice of editors either because
841      * we allow any $EDITOR or because $EDITOR is in the allowable list.
842      */
843     Editor = EditorArgs = EditorPath = NULL;
844     if (def_env_editor && UserEditor) {
845         Editor = UserEditor;
846         EditorArgs = UserEditorArgs;
847     } else if (UserEditor) {
848         struct stat editor_sb;
849         struct stat user_editor_sb;
850         char *base, *userbase;
851
852         if (stat(UserEditor, &user_editor_sb) != 0) {
853             /* Should never happen since we already checked above. */
854             error(1, "unable to stat editor (%s)", UserEditor);
855         }
856         EditorPath = estrdup(def_editor);
857         Editor = strtok(EditorPath, ":");
858         do {
859             EditorArgs = get_args(Editor);
860             /*
861              * Both Editor and UserEditor should be fully qualified but
862              * check anyway...
863              */
864             if ((base = strrchr(Editor, '/')) == NULL)
865                 continue;
866             if ((userbase = strrchr(UserEditor, '/')) == NULL) {
867                 Editor = NULL;
868                 break;
869             }
870             base++, userbase++;
871
872             /*
873              * We compare the basenames first and then use stat to match
874              * for sure.
875              */
876             if (strcmp(base, userbase) == 0) {
877                 if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
878                     && (editor_sb.st_mode & 0000111) &&
879                     editor_sb.st_dev == user_editor_sb.st_dev &&
880                     editor_sb.st_ino == user_editor_sb.st_ino)
881                     break;
882             }
883         } while ((Editor = strtok(NULL, ":")));
884     }
885
886     /*
887      * Can't use $EDITOR, try each element of def_editor until we
888      * find one that exists, is regular, and is executable.
889      */
890     if (Editor == NULL || *Editor == '\0') {
891         efree(EditorPath);
892         EditorPath = estrdup(def_editor);
893         Editor = strtok(EditorPath, ":");
894         do {
895             EditorArgs = get_args(Editor);
896             if (sudo_goodpath(Editor, NULL))
897                 break;
898         } while ((Editor = strtok(NULL, ":")));
899
900         /* Bleah, none of the editors existed! */
901         if (Editor == NULL || *Editor == '\0')
902             errorx(1, "no editor found (editor path = %s)", def_editor);
903     }
904     *args = EditorArgs;
905     return(Editor);
906 }
907
908 /*
909  * Split out any command line arguments and return them.
910  */
911 static char *
912 get_args(cmnd)
913     char *cmnd;
914 {
915     char *args;
916
917     args = cmnd;
918     while (*args && !isblank((unsigned char) *args))
919         args++;
920     if (*args) {
921         *args++ = '\0';
922         while (*args && isblank((unsigned char) *args))
923             args++;
924     }
925     return(*args ? args : NULL);
926 }
927
928 /*
929  * Look up the hostname and set user_host and user_shost.
930  */
931 static void
932 get_hostname()
933 {
934     char *p, thost[MAXHOSTNAMELEN + 1];
935
936     if (gethostname(thost, sizeof(thost)) != 0) {
937         user_host = user_shost = "localhost";
938         return;
939     }
940     thost[sizeof(thost) - 1] = '\0';
941     user_host = estrdup(thost);
942
943     if ((p = strchr(user_host, '.'))) {
944         *p = '\0';
945         user_shost = estrdup(user_host);
946         *p = '.';
947     } else {
948         user_shost = user_host;
949     }
950 }
951
952 static void
953 alias_remove_recursive(name, type)
954     char *name;
955     int type;
956 {
957     struct member *m;
958     struct alias *a;
959
960     if ((a = alias_find(name, type)) != NULL) {
961         tq_foreach_fwd(&a->members, m) {
962             if (m->type == ALIAS) {
963                 alias_remove_recursive(m->name, type);
964             }
965         }
966     }
967     alias_seqno++;
968     a = alias_remove(name, type);
969     if (a)
970         rbinsert(alias_freelist, a);
971 }
972
973 /*
974  * Iterate through the sudoers datastructures looking for undefined
975  * aliases or unused aliases.
976  */
977 static int
978 check_aliases(strict, quiet)
979     int strict;
980     int quiet;
981 {
982     struct cmndspec *cs;
983     struct member *m, *binding;
984     struct privilege *priv;
985     struct userspec *us;
986     struct defaults *d;
987     int atype, error = 0;
988
989     alias_freelist = rbcreate(alias_compare);
990
991     /* Forward check. */
992     tq_foreach_fwd(&userspecs, us) {
993         tq_foreach_fwd(&us->users, m) {
994             if (m->type == ALIAS) {
995                 alias_seqno++;
996                 if (alias_find(m->name, USERALIAS) == NULL) {
997                     print_undefined(m->name, USERALIAS, strict, quiet);
998                     error++;
999                 }
1000             }
1001         }
1002         tq_foreach_fwd(&us->privileges, priv) {
1003             tq_foreach_fwd(&priv->hostlist, m) {
1004                 if (m->type == ALIAS) {
1005                     alias_seqno++;
1006                     if (alias_find(m->name, HOSTALIAS) == NULL) {
1007                         print_undefined(m->name, HOSTALIAS, strict, quiet);
1008                         error++;
1009                     }
1010                 }
1011             }
1012             tq_foreach_fwd(&priv->cmndlist, cs) {
1013                 tq_foreach_fwd(&cs->runasuserlist, m) {
1014                     if (m->type == ALIAS) {
1015                         alias_seqno++;
1016                         if (alias_find(m->name, RUNASALIAS) == NULL) {
1017                             print_undefined(m->name, RUNASALIAS, strict, quiet);
1018                             error++;
1019                         }
1020                     }
1021                 }
1022                 if ((m = cs->cmnd)->type == ALIAS) {
1023                     alias_seqno++;
1024                     if (alias_find(m->name, CMNDALIAS) == NULL) {
1025                         print_undefined(m->name, CMNDALIAS, strict, quiet);
1026                         error++;
1027                     }
1028                 }
1029             }
1030         }
1031     }
1032
1033     /* Reverse check (destructive) */
1034     tq_foreach_fwd(&userspecs, us) {
1035         tq_foreach_fwd(&us->users, m) {
1036             if (m->type == ALIAS) {
1037                 (void) alias_remove_recursive(m->name, USERALIAS);
1038             }
1039         }
1040         tq_foreach_fwd(&us->privileges, priv) {
1041             tq_foreach_fwd(&priv->hostlist, m) {
1042                 if (m->type == ALIAS)
1043                     (void) alias_remove_recursive(m->name, HOSTALIAS);
1044             }
1045             tq_foreach_fwd(&priv->cmndlist, cs) {
1046                 tq_foreach_fwd(&cs->runasuserlist, m) {
1047                     if (m->type == ALIAS)
1048                         (void) alias_remove_recursive(m->name, RUNASALIAS);
1049                 }
1050                 if ((m = cs->cmnd)->type == ALIAS)
1051                     (void) alias_remove_recursive(m->name, CMNDALIAS);
1052             }
1053         }
1054     }
1055     tq_foreach_fwd(&defaults, d) {
1056         switch (d->type) {
1057             case DEFAULTS_HOST:
1058                 atype = HOSTALIAS;
1059                 break;
1060             case DEFAULTS_USER:
1061                 atype = USERALIAS;
1062                 break;
1063             case DEFAULTS_RUNAS:
1064                 atype = RUNASALIAS;
1065                 break;
1066             case DEFAULTS_CMND:
1067                 atype = CMNDALIAS;
1068                 break;
1069             default:
1070                 continue; /* not an alias */
1071         }
1072         tq_foreach_fwd(&d->binding, binding) {
1073             for (m = binding; m != NULL; m = m->next) {
1074                 if (m->type == ALIAS)
1075                     (void) alias_remove_recursive(m->name, atype);
1076             }
1077         }
1078     }
1079     rbdestroy(alias_freelist, alias_free);
1080
1081     /* If all aliases were referenced we will have an empty tree. */
1082     if (no_aliases())
1083         return(0);
1084     if (!quiet) {
1085         alias_apply(print_unused, strict ? "Error" : "Warning");
1086     }
1087     return (strict ? 1 : 0);
1088 }
1089
1090 static void
1091 print_undefined(name, type, strict, quiet)
1092     char *name;
1093     int type;
1094     int strict;
1095     int quiet;
1096 {
1097     if (!quiet) {
1098         warningx("%s: %s_Alias `%s' referenced but not defined",
1099             strict ? "Error" : "Warning",
1100             type == HOSTALIAS ? "Host" : type == CMNDALIAS ? "Cmnd" :
1101             type == USERALIAS ? "User" : type == RUNASALIAS ? "Runas" :
1102             "Unknown", name);
1103     }
1104 }
1105
1106 static int
1107 print_unused(v1, v2)
1108     void *v1;
1109     void *v2;
1110 {
1111     struct alias *a = (struct alias *)v1;
1112     char *prefix = (char *)v2;
1113
1114     warningx("%s: unused %s_Alias %s", prefix,
1115         a->type == HOSTALIAS ? "Host" : a->type == CMNDALIAS ? "Cmnd" :
1116         a->type == USERALIAS ? "User" : a->type == RUNASALIAS ? "Runas" :
1117         "Unknown", a->name);
1118     return(0);
1119 }
1120
1121 /*
1122  * Unlink any sudoers temp files that remain.
1123  */
1124 void
1125 cleanup(gotsignal)
1126     int gotsignal;
1127 {
1128     struct sudoersfile *sp;
1129
1130     tq_foreach_fwd(&sudoerslist, sp) {
1131         if (sp->tpath != NULL)
1132             (void) unlink(sp->tpath);
1133     }
1134     if (!gotsignal) {
1135         sudo_endpwent();
1136         sudo_endgrent();
1137     }
1138 }
1139
1140 /*
1141  * Unlink sudoers temp files (if any) and exit.
1142  */
1143 static RETSIGTYPE
1144 quit(signo)
1145     int signo;
1146 {
1147     cleanup(signo);
1148 #define emsg     " exiting due to signal.\n"
1149     write(STDERR_FILENO, getprogname(), strlen(getprogname()));
1150     write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
1151     _exit(signo);
1152 }
1153
1154 static void
1155 usage()
1156 {
1157     (void) fprintf(stderr, "usage: %s [-c] [-q] [-s] [-V] [-f sudoers]\n",
1158         getprogname());
1159     exit(1);
1160 }