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