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