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