Imported Upstream version 1.8.7
[debian/sudo] / plugins / sudoers / visudo.c
index 43119e233c57e35517baedfe2e776a86d8dee974..1fb79321991db7575ed42921193c7a0cceeec706 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 1998-2005, 2007-2011
+ * Copyright (c) 1996, 1998-2005, 2007-2013
  *     Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
 #include <config.h>
 
 #include <sys/types.h>
-#include <sys/param.h>
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <sys/time.h>
+#include <sys/uio.h>
 #ifndef __TANDEM
 # include <sys/file.h>
 #endif
 #include <fcntl.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
-#include <netdb.h>
 #if TIME_WITH_SYS_TIME
 # include <time.h>
 #endif
-#ifdef HAVE_SETLOCALE
-# include <locale.h>
-#endif
 
 #include "sudoers.h"
-#include "interfaces.h"
 #include "parse.h"
 #include "redblack.h"
 #include "gettext.h"
 #include "sudoers_version.h"
+#include "sudo_conf.h"
 #include <gram.h>
 
 struct sudoersfile {
@@ -100,31 +96,30 @@ static void quit(int);
 static char *get_args(char *);
 static char *get_editor(char **);
 static void get_hostname(void);
-static char whatnow(void);
-static int check_aliases(int, int);
-static int check_syntax(char *, int, int);
-static int edit_sudoers(struct sudoersfile *, char *, char *, int);
-static int install_sudoers(struct sudoersfile *, int);
+static int whatnow(void);
+static int check_aliases(bool, bool);
+static bool check_syntax(char *, bool, bool, bool);
+static bool edit_sudoers(struct sudoersfile *, char *, char *, int);
+static bool install_sudoers(struct sudoersfile *, bool);
 static int print_unused(void *, void *);
-static int reparse_sudoers(char *, char *, int, int);
+static bool reparse_sudoers(char *, char *, bool, bool);
 static int run_command(char *, char **);
-static int visudo_printf(int msg_type, const char *fmt, ...);
 static void setup_signals(void);
 static void help(void) __attribute__((__noreturn__));
 static void usage(int);
+static void visudo_cleanup(void);
 
-void cleanup(int);
-
-extern void yyerror(const char *);
-extern void yyrestart(FILE *);
+extern void sudoerserror(const char *);
+extern void sudoersrestart(FILE *);
 
 /*
  * External globals exported by the parser
  */
 extern struct rbtree *aliases;
-extern FILE *yyin;
+extern FILE *sudoersin;
 extern char *sudoers, *errorfile;
-extern int errorlineno, parse_error;
+extern int errorlineno;
+extern bool parse_error;
 /* For getopt(3) */
 extern char *optarg;
 extern int optind;
@@ -132,71 +127,80 @@ extern int optind;
 /*
  * Globals
  */
-struct interface *interfaces;
 struct sudo_user sudo_user;
 struct passwd *list_pw;
-sudo_printf_t sudo_printf = visudo_printf;
 static struct sudoersfile_list sudoerslist;
 static struct rbtree *alias_freelist;
+static bool checkonly;
+
+__dso_public int main(int argc, char *argv[]);
 
 int
 main(int argc, char *argv[])
 {
     struct sudoersfile *sp;
     char *args, *editor, *sudoers_path;
-    int ch, checkonly, quiet, strict, oldperms;
+    int ch, exitcode = 0;
+    bool quiet, strict, oldperms;
+    debug_decl(main, SUDO_DEBUG_MAIN)
+
 #if defined(SUDO_DEVEL) && defined(__OpenBSD__)
-    extern char *malloc_options;
-    malloc_options = "AFGJPR";
+    {
+       extern char *malloc_options;
+       malloc_options = "AFGJPR";
+    }
 #endif
 
 #if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME)
     setprogname(argc > 0 ? argv[0] : "visudo");
 #endif
 
-#ifdef HAVE_SETLOCALE 
-    setlocale(LC_ALL, "");
-#endif
+    sudoers_setlocale(SUDOERS_LOCALE_USER, NULL);
     bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have visudo domain */
     textdomain("sudoers");
 
     if (argc < 1)
        usage(1);
 
+    /* Register fatal/fatalx callback. */
+    fatal_callback_register(visudo_cleanup);
+
+    /* Read sudo.conf. */
+    sudo_conf_read(NULL);
+
     /*
      * Arg handling.
      */
-    checkonly = oldperms = quiet = strict = FALSE;
+    checkonly = oldperms = quiet = strict = false;
     sudoers_path = _PATH_SUDOERS;
     while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
        switch (ch) {
            case 'V':
                (void) printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION);
                (void) printf(_("%s grammar version %d\n"), getprogname(), SUDOERS_GRAMMAR_VERSION);
-               exit(0);
+               goto done;
            case 'c':
-               checkonly++;            /* check mode */
+               checkonly = true;       /* check mode */
                break;
            case 'f':
                sudoers_path = optarg;  /* sudoers file path */
-               oldperms = TRUE;
+               oldperms = true;
                break;
            case 'h':
                help();
                break;
            case 's':
-               strict++;               /* strict mode */
+               strict = true;          /* strict mode */
                break;
            case 'q':
-               quiet++;                /* quiet mode */
+               quiet = false;          /* quiet mode */
                break;
            default:
                usage(1);
        }
     }
-    argc -= optind;
-    argv += optind;
-    if (argc)
+    /* There should be no other command line arguments. */
+    if (argc - optind != 0)
        usage(1);
 
     sudo_setpwent();
@@ -205,24 +209,25 @@ main(int argc, char *argv[])
     /* Mock up a fake sudo_user struct. */
     user_cmnd = "";
     if ((sudo_user.pw = sudo_getpwuid(getuid())) == NULL)
-       errorx(1, _("you do not exist in the %s database"), "passwd");
+       fatalx(_("you do not exist in the %s database"), "passwd");
     get_hostname();
 
     /* Setup defaults data structures. */
     init_defaults();
 
-    if (checkonly)
-       exit(check_syntax(sudoers_path, quiet, strict));
+    if (checkonly) {
+       exitcode = check_syntax(sudoers_path, quiet, strict, oldperms) ? 0 : 1;
+       goto done;
+    }
 
     /*
-     * Parse the existing sudoers file(s) in quiet mode to highlight any
-     * existing errors and to pull in editor and env_editor conf values.
+     * Parse the existing sudoers file(s) to highlight any existing
+     * errors and to pull in editor and env_editor conf values.
      */
-    if ((yyin = open_sudoers(sudoers_path, TRUE, NULL)) == NULL) {
-       error(1, "%s", sudoers_path);
-    }
-    init_parser(sudoers_path, 0);
-    yyparse();
+    if ((sudoersin = open_sudoers(sudoers_path, true, NULL)) == NULL)
+       exit(1);
+    init_parser(sudoers_path, false);
+    sudoersparse();
     (void) update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER);
 
     editor = get_editor(&args);
@@ -242,29 +247,54 @@ main(int argc, char *argv[])
        edit_sudoers(sp, editor, args, -1);
     }
 
-    /* Check edited files for a parse error and re-edit any that fail. */
-    reparse_sudoers(editor, args, strict, quiet);
-
-    /* Install the sudoers temp files. */
-    tq_foreach_fwd(&sudoerslist, sp) {
-       if (!sp->modified)
-           (void) unlink(sp->tpath);
-       else
+    /*
+     * Check edited files for a parse error, re-edit any that fail
+     * and install the edited files as needed.
+     */
+    if (reparse_sudoers(editor, args, strict, quiet)) {
+       tq_foreach_fwd(&sudoerslist, sp) {
            (void) install_sudoers(sp, oldperms);
+       }
     }
 
-    exit(0);
+done:
+    sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode);                
+    exit(exitcode);
 }
 
+/*
+ * List of editors that support the "+lineno" command line syntax.
+ * If an entry starts with '*' the tail end of the string is matched.
+ * No other wild cards are supported.
+ */
+static char *lineno_editors[] = {
+    "ex",
+    "nex",
+    "vi",
+    "nvi",
+    "vim",
+    "elvis",
+    "*macs",
+    "mg",
+    "vile",
+    "jove",
+    "pico",
+    "nano",
+    "ee",
+    "joe",
+    "zile",
+    NULL
+};
+
 /*
  * Edit each sudoers file.
- * Returns TRUE on success, else FALSE.
+ * Returns true on success, else false.
  */
-static int
+static bool
 edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
 {
     int tfd;                           /* sudoers temp file descriptor */
-    int modified;                      /* was the file modified? */
+    bool modified;                     /* was the file modified? */
     int ac;                            /* argument count */
     char **av;                         /* argument vector for run_command */
     char *cp;                          /* scratch char pointer */
@@ -275,13 +305,11 @@ edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
     off_t orig_size;                   /* starting size of sudoers file */
     ssize_t nread;                     /* number of bytes read */
     struct stat sb;                    /* stat buffer */
+    bool rval = false;                 /* return value */
+    debug_decl(edit_sudoers, SUDO_DEBUG_UTIL)
 
-#ifdef HAVE_FSTAT
     if (fstat(sp->fd, &sb) == -1)
-#else
-    if (stat(sp->path, &sb) == -1)
-#endif
-       error(1, _("unable to stat %s"), sp->path);
+       fatal(_("unable to stat %s"), sp->path);
     orig_size = sb.st_size;
     mtim_get(&sb, &orig_mtim);
 
@@ -290,37 +318,65 @@ edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
        easprintf(&sp->tpath, "%s.tmp", sp->path);
        tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
        if (tfd < 0)
-           error(1, "%s", sp->tpath);
+           fatal("%s", sp->tpath);
 
        /* Copy sp->path -> sp->tpath and reset the mtime. */
        if (orig_size != 0) {
            (void) lseek(sp->fd, (off_t)0, SEEK_SET);
            while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
                if (write(tfd, buf, nread) != nread)
-                   error(1, _("write error"));
+                   fatal(_("write error"));
 
            /* Add missing newline at EOF if needed. */
            if (nread > 0 && buf[nread - 1] != '\n') {
                buf[0] = '\n';
                if (write(tfd, buf, 1) != 1)
-                   error(1, _("write error"));
+                   fatal(_("write error"));
            }
        }
        (void) close(tfd);
     }
     (void) touch(-1, sp->tpath, &orig_mtim);
 
+    /* Does the editor support +lineno? */
+    if (lineno > 0)
+    {
+       char *editor_base = strrchr(editor, '/');
+       if (editor_base != NULL)
+           editor_base++;
+       else
+           editor_base = editor;
+       if (*editor_base == 'r')
+           editor_base++;
+
+       for (av = lineno_editors; (cp = *av) != NULL; av++) {
+           /* We only handle a leading '*' wildcard. */
+           if (*cp == '*') {
+               size_t blen = strlen(editor_base);
+               size_t clen = strlen(++cp);
+               if (blen >= clen) {
+                   if (strcmp(cp, editor_base + blen - clen) == 0)
+                       break;
+               }
+           } else if (strcmp(cp, editor_base) == 0)
+               break;
+       }
+       /* Disable +lineno if editor doesn't support it. */
+       if (cp == NULL)
+           lineno = -1;
+    }
+
     /* Find the length of the argument vector */
     ac = 3 + (lineno > 0);
     if (args) {
-        int wasblank;
+        bool wasblank;
 
         ac++;
-        for (wasblank = FALSE, cp = args; *cp; cp++) {
+        for (wasblank = false, cp = args; *cp; cp++) {
             if (isblank((unsigned char) *cp))
-                wasblank = TRUE;
+                wasblank = true;
             else if (wasblank) {
-                wasblank = FALSE;
+                wasblank = false;
                 ac++;
             }
         }
@@ -359,21 +415,21 @@ edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
        if (stat(sp->tpath, &sb) < 0) {
            warningx(_("unable to stat temporary file (%s), %s unchanged"),
                sp->tpath, sp->path);
-           return FALSE;
+           goto done;
        }
        if (sb.st_size == 0 && orig_size != 0) {
            warningx(_("zero length temporary file (%s), %s unchanged"),
                sp->tpath, sp->path);
-           sp->modified = TRUE;
-           return FALSE;
+           sp->modified = true;
+           goto done;
        }
     } else {
        warningx(_("editor (%s) failed, %s unchanged"), editor, sp->path);
-       return FALSE;
+       goto done;
     }
 
     /* Set modified bit if use changed the file. */
-    modified = TRUE;
+    modified = true;
     mtim_get(&sb, &tv);
     if (orig_size == sb.st_size && timevalcmp(&orig_mtim, &tv, ==)) {
        /*
@@ -382,7 +438,7 @@ edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
         */
        timevalsub(&tv1, &tv2);
        if (timevalisset(&tv2))
-           modified = FALSE;
+           modified = false;
     }
 
     /*
@@ -393,75 +449,79 @@ edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
     else
        warningx(_("%s unchanged"), sp->tpath);
 
-    return TRUE;
+    rval = true;
+done:
+    debug_return_bool(rval);
 }
 
 /*
  * Parse sudoers after editing and re-edit any ones that caused a parse error.
- * Returns TRUE on success, else FALSE.
  */
-static int
-reparse_sudoers(char *editor, char *args, int strict, int quiet)
+static bool
+reparse_sudoers(char *editor, char *args, bool strict, bool quiet)
 {
     struct sudoersfile *sp, *last;
     FILE *fp;
     int ch;
+    debug_decl(reparse_sudoers, SUDO_DEBUG_UTIL)
 
     /*
      * Parse the edited sudoers files and do sanity checking
      */
-    do {
-       sp = tq_first(&sudoerslist);
+    while ((sp = tq_first(&sudoerslist)) != NULL) {
        last = tq_last(&sudoerslist);
        fp = fopen(sp->tpath, "r+");
        if (fp == NULL)
-           errorx(1, _("unable to re-open temporary file (%s), %s unchanged."),
+           fatalx(_("unable to re-open temporary file (%s), %s unchanged."),
                sp->tpath, sp->path);
 
        /* Clean slate for each parse */
        init_defaults();
        init_parser(sp->path, quiet);
 
-       /* Parse the sudoers temp file */
-       yyrestart(fp);
-       if (yyparse() && !parse_error) {
+       /* Parse the sudoers temp file(s) */
+       sudoersrestart(fp);
+       if (sudoersparse() && !parse_error) {
            warningx(_("unabled to parse temporary file (%s), unknown error"),
                sp->tpath);
-           parse_error = TRUE;
+           parse_error = true;
            errorfile = sp->path;
        }
-       fclose(yyin);
+       fclose(sudoersin);
        if (!parse_error) {
-           if (!update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER) ||
+           if (!check_defaults(SETDEF_ALL, quiet) ||
                check_aliases(strict, quiet) != 0) {
-               parse_error = TRUE;
-               errorfile = sp->path;
+               parse_error = true;
+               errorfile = NULL;
            }
        }
 
        /*
-        * Got an error, prompt the user for what to do now
+        * Got an error, prompt the user for what to do now.
         */
        if (parse_error) {
            switch (whatnow()) {
-               case 'Q' :      parse_error = FALSE;    /* ignore parse error */
-                               break;
-               case 'x' :      cleanup(0);
-                               exit(0);
-                               break;
-           }
-       }
-       if (parse_error) {
-           /* Edit file with the parse error */
-           tq_foreach_fwd(&sudoerslist, sp) {
-               if (errorfile == NULL || strcmp(sp->path, errorfile) == 0) {
-                   edit_sudoers(sp, editor, args, errorlineno);
-                   break;
+           case 'Q':
+               parse_error = false;    /* ignore parse error */
+               break;
+           case 'x':
+               visudo_cleanup();       /* discard changes */
+               debug_return_bool(false);
+           case 'e':
+           default:
+               /* Edit file with the parse error */
+               tq_foreach_fwd(&sudoerslist, sp) {
+                   if (errorfile == NULL || strcmp(sp->path, errorfile) == 0) {
+                       edit_sudoers(sp, editor, args, errorlineno);
+                       if (errorfile != NULL)
+                           break;
+                   }
                }
-           }
-           if (sp == NULL) {
-               errorx(1, _("internal error, unable to find %s in list!"),
-                   sudoers);
+               if (errorfile != NULL && sp == NULL) {
+                   fatalx(_("internal error, unable to find %s in list!"),
+                       sudoers);
+               }
+               break;
            }
        }
 
@@ -472,19 +532,40 @@ reparse_sudoers(char *editor, char *args, int strict, int quiet)
                    continue;
            edit_sudoers(sp, editor, args, errorlineno);
        }
-    } while (parse_error);
 
-    return TRUE;
+       /* If all sudoers files parsed OK we are done. */
+       if (!parse_error)
+           break;
+    }
+
+    debug_return_bool(true);
 }
 
 /*
  * Set the owner and mode on a sudoers temp file and
- * move it into place.  Returns TRUE on success, else FALSE.
+ * move it into place.  Returns true on success, else false.
  */
-static int
-install_sudoers(struct sudoersfile *sp, int oldperms)
+static bool
+install_sudoers(struct sudoersfile *sp, bool oldperms)
 {
     struct stat sb;
+    bool rval = false;
+    debug_decl(install_sudoers, SUDO_DEBUG_UTIL)
+
+    if (!sp->modified) {
+       /*
+        * No changes but fix owner/mode if needed.
+        */
+       (void) unlink(sp->tpath);
+       if (!oldperms && fstat(sp->fd, &sb) != -1) {
+           if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID)
+               ignore_result(chown(sp->path, SUDOERS_UID, SUDOERS_GID));
+           if ((sb.st_mode & 0777) != SUDOERS_MODE)
+               ignore_result(chmod(sp->path, SUDOERS_MODE));
+       }
+       rval = true;
+       goto done;
+    }
 
     /*
      * Change mode and ownership of temp file so when
@@ -492,30 +573,26 @@ install_sudoers(struct sudoersfile *sp, int oldperms)
      */
     if (oldperms) {
        /* Use perms of the existing file.  */
-#ifdef HAVE_FSTAT
        if (fstat(sp->fd, &sb) == -1)
-#else
-       if (stat(sp->path, &sb) == -1)
-#endif
-           error(1, _("unable to stat %s"), sp->path);
+           fatal(_("unable to stat %s"), sp->path);
        if (chown(sp->tpath, sb.st_uid, sb.st_gid) != 0) {
-           warning(_("unable to set (uid, gid) of %s to (%d, %d)"),
-               sp->tpath, sb.st_uid, sb.st_gid);
+           warning(_("unable to set (uid, gid) of %s to (%u, %u)"),
+               sp->tpath, (unsigned int)sb.st_uid, (unsigned int)sb.st_gid);
        }
        if (chmod(sp->tpath, sb.st_mode & 0777) != 0) {
            warning(_("unable to change mode of %s to 0%o"), sp->tpath,
-               (sb.st_mode & 0777));
+               (unsigned int)(sb.st_mode & 0777));
        }
     } else {
        if (chown(sp->tpath, SUDOERS_UID, SUDOERS_GID) != 0) {
-           warning(_("unable to set (uid, gid) of %s to (%d, %d)"),
+           warning(_("unable to set (uid, gid) of %s to (%u, %u)"),
                sp->tpath, SUDOERS_UID, SUDOERS_GID);
-           return FALSE;
+           goto done;
        }
        if (chmod(sp->tpath, SUDOERS_MODE) != 0) {
            warning(_("unable to change mode of %s to 0%o"), sp->tpath,
                SUDOERS_MODE);
-           return FALSE;
+           goto done;
        }
     }
 
@@ -549,24 +626,19 @@ install_sudoers(struct sudoersfile *sp, int oldperms)
                (void) unlink(sp->tpath);
                efree(sp->tpath);
                sp->tpath = NULL;
-               return FALSE;
+               goto done;
            }
            efree(sp->tpath);
            sp->tpath = NULL;
        } else {
            warning(_("error renaming %s, %s unchanged"), sp->tpath, sp->path);
            (void) unlink(sp->tpath);
-           return FALSE;
+           goto done;
        }
     }
-    return TRUE;
-}
-
-/* STUB */
-void
-set_fqdn(void)
-{
-    return;
+    rval = true;
+done:
+    debug_return_bool(rval);
 }
 
 /* STUB */
@@ -577,10 +649,10 @@ init_envtables(void)
 }
 
 /* STUB */
-int
+bool
 user_is_exempt(void)
 {
-    return FALSE;
+    return false;
 }
 
 /* STUB */
@@ -601,17 +673,24 @@ sudo_endspent(void)
 int
 group_plugin_query(const char *user, const char *group, const struct passwd *pw)
 {
-    return FALSE;
+    return false;
+}
+
+/* STUB */
+struct interface *get_interfaces(void)
+{
+    return NULL;
 }
 
 /*
  * Assuming a parse error occurred, prompt the user for what they want
  * to do now.  Returns the first letter of their choice.
  */
-static char
+static int
 whatnow(void)
 {
     int choice, c;
+    debug_decl(whatnow, SUDO_DEBUG_UTIL)
 
     for (;;) {
        (void) fputs(_("What now? "), stdout);
@@ -626,7 +705,7 @@ whatnow(void)
            case 'e':
            case 'x':
            case 'Q':
-               return choice;
+               debug_return_int(choice);
            default:
                (void) puts(_("Options are:\n"
                    "  (e)dit sudoers file again\n"
@@ -642,19 +721,22 @@ whatnow(void)
 static void
 setup_signals(void)
 {
-       sigaction_t sa;
+    sigaction_t sa;
+    debug_decl(setup_signals, SUDO_DEBUG_UTIL)
 
-       /*
-        * Setup signal handlers to cleanup nicely.
-        */
-       zero_bytes(&sa, sizeof(sa));
-       sigemptyset(&sa.sa_mask);
-       sa.sa_flags = SA_RESTART;
-       sa.sa_handler = quit;
-       (void) sigaction(SIGTERM, &sa, NULL);
-       (void) sigaction(SIGHUP, &sa, NULL);
-       (void) sigaction(SIGINT, &sa, NULL);
-       (void) sigaction(SIGQUIT, &sa, NULL);
+    /*
+     * Setup signal handlers to cleanup nicely.
+     */
+    zero_bytes(&sa, sizeof(sa));
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = quit;
+    (void) sigaction(SIGTERM, &sa, NULL);
+    (void) sigaction(SIGHUP, &sa, NULL);
+    (void) sigaction(SIGINT, &sa, NULL);
+    (void) sigaction(SIGQUIT, &sa, NULL);
+
+    debug_return;
 }
 
 static int
@@ -662,10 +744,11 @@ run_command(char *path, char **argv)
 {
     int status;
     pid_t pid, rv;
+    debug_decl(run_command, SUDO_DEBUG_UTIL)
 
-    switch (pid = fork()) {
+    switch (pid = sudo_debug_fork()) {
        case -1:
-           error(1, _("unable to execute %s"), path);
+           fatal(_("unable to execute %s"), path);
            break;      /* NOTREACHED */
        case 0:
            sudo_endpwent();
@@ -681,73 +764,94 @@ run_command(char *path, char **argv)
        rv = waitpid(pid, &status, 0);
     } while (rv == -1 && errno == EINTR);
 
-    if (rv == -1 || !WIFEXITED(status))
-       return -1;
-    return WEXITSTATUS(status);
+    if (rv != -1)
+       rv = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+    debug_return_int(rv);
 }
 
-static int
-check_syntax(char *sudoers_path, int quiet, int strict)
+static bool
+check_owner(const char *path, bool quiet)
 {
     struct stat sb;
-    int error;
+    bool ok = true;
+    debug_decl(check_owner, SUDO_DEBUG_UTIL)
+
+    if (stat(path, &sb) == 0) {
+       if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID) {
+           ok = false;
+           if (!quiet) {
+               fprintf(stderr,
+                   _("%s: wrong owner (uid, gid) should be (%u, %u)\n"),
+                   path, SUDOERS_UID, SUDOERS_GID);
+               }
+       }
+       if ((sb.st_mode & 07777) != SUDOERS_MODE) {
+           ok = false;
+           if (!quiet) {
+               fprintf(stderr, _("%s: bad permissions, should be mode 0%o\n"),
+                   path, SUDOERS_MODE);
+           }
+       }
+    }
+    debug_return_bool(ok);
+}
+
+static bool
+check_syntax(char *sudoers_path, bool quiet, bool strict, bool oldperms)
+{
+    bool ok = false;
+    debug_decl(check_syntax, SUDO_DEBUG_UTIL)
 
     if (strcmp(sudoers_path, "-") == 0) {
-       yyin = stdin;
+       sudoersin = stdin;
        sudoers_path = "stdin";
-    } else if ((yyin = fopen(sudoers_path, "r")) == NULL) {
+    } else if ((sudoersin = fopen(sudoers_path, "r")) == NULL) {
        if (!quiet)
            warning(_("unable to open %s"), sudoers_path);
-       exit(1);
+       goto done;
     }
     init_parser(sudoers_path, quiet);
-    if (yyparse() && !parse_error) {
+    if (sudoersparse() && !parse_error) {
        if (!quiet)
            warningx(_("failed to parse %s file, unknown error"), sudoers_path);
-       parse_error = TRUE;
+       parse_error = true;
        errorfile = sudoers_path;
     }
-    if (!parse_error && check_aliases(strict, quiet) != 0) {
-       parse_error = TRUE;
-       errorfile = sudoers_path;
+    if (!parse_error) {
+       if (!check_defaults(SETDEF_ALL, quiet) ||
+           check_aliases(strict, quiet) != 0) {
+           parse_error = true;
+           errorfile = NULL;
+       }
     }
-    error = parse_error;
-    if (!quiet) {
-       if (parse_error) {
+    ok = !parse_error;
+
+    if (parse_error) {
+       if (!quiet) {
            if (errorlineno != -1)
                (void) printf(_("parse error in %s near line %d\n"),
                    errorfile, errorlineno);
-           else
+           else if (errorfile != NULL)
                (void) printf(_("parse error in %s\n"), errorfile);
-       } else {
-           (void) printf(_("%s: parsed OK\n"), sudoers_path);
-       }
-    }
-    /* Check mode and owner in strict mode. */
-#ifdef HAVE_FSTAT
-    if (strict && yyin != stdin && fstat(fileno(yyin), &sb) == 0)
-#else
-    if (strict && yyin != stdin && stat(sudoers_path, &sb) == 0)
-#endif
-    {
-       if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID) {
-           error = TRUE;
-           if (!quiet) {
-               fprintf(stderr,
-                   _("%s: wrong owner (uid, gid) should be (%d, %d)\n"),
-                   sudoers_path, SUDOERS_UID, SUDOERS_GID);
-               }
        }
-       if ((sb.st_mode & 07777) != SUDOERS_MODE) {
-           error = TRUE;
-           if (!quiet) {
-               fprintf(stderr, _("%s: bad permissions, should be mode 0%o\n"),
-                   sudoers_path, SUDOERS_MODE);
-           }
+    } else {
+       struct sudoersfile *sp;
+
+       /* Parsed OK, check mode and owner. */
+       if (oldperms || check_owner(sudoers_path, quiet))
+           (void) printf(_("%s: parsed OK\n"), sudoers_path);
+       else
+           ok = false;
+       tq_foreach_fwd(&sudoerslist, sp) {
+           if (oldperms || check_owner(sp->path, quiet))
+               (void) printf(_("%s: parsed OK\n"), sp->path);
+           else
+               ok = false;
        }
     }
 
-    return error;
+done:
+    debug_return_bool(ok);
 }
 
 /*
@@ -755,10 +859,17 @@ check_syntax(char *sudoers_path, int quiet, int strict)
  * any subsequent files #included via a callback from the parser.
  */
 FILE *
-open_sudoers(const char *path, int doedit, int *keepopen)
+open_sudoers(const char *path, bool doedit, bool *keepopen)
 {
     struct sudoersfile *entry;
     FILE *fp;
+    int open_flags;
+    debug_decl(open_sudoers, SUDO_DEBUG_UTIL)
+
+    if (checkonly)
+       open_flags = O_RDONLY;
+    else
+       open_flags = O_RDWR | O_CREAT;
 
     /* Check for existing entry */
     tq_foreach_fwd(&sudoerslist, entry) {
@@ -766,44 +877,45 @@ open_sudoers(const char *path, int doedit, int *keepopen)
            break;
     }
     if (entry == NULL) {
-       entry = emalloc(sizeof(*entry));
+       entry = ecalloc(1, sizeof(*entry));
        entry->path = estrdup(path);
-       entry->modified = 0;
+       /* entry->modified = 0; */
        entry->prev = entry;
-       entry->next = NULL;
-       entry->fd = open(entry->path, O_RDWR | O_CREAT, SUDOERS_MODE);
-       entry->tpath = NULL;
+       /* entry->next = NULL; */
+       entry->fd = open(entry->path, open_flags, SUDOERS_MODE);
+       /* entry->tpath = NULL; */
        entry->doedit = doedit;
        if (entry->fd == -1) {
            warning("%s", entry->path);
            efree(entry);
-           return NULL;
+           debug_return_ptr(NULL);
        }
-       if (!lock_file(entry->fd, SUDO_TLOCK))
-           errorx(1, _("%s busy, try again later"), entry->path);
+       if (!checkonly && !lock_file(entry->fd, SUDO_TLOCK))
+           fatalx(_("%s busy, try again later"), entry->path);
        if ((fp = fdopen(entry->fd, "r")) == NULL)
-           error(1, "%s", entry->path);
+           fatal("%s", entry->path);
        tq_append(&sudoerslist, entry);
     } else {
        /* Already exists, open .tmp version if there is one. */
        if (entry->tpath != NULL) {
            if ((fp = fopen(entry->tpath, "r")) == NULL)
-               error(1, "%s", entry->tpath);
+               fatal("%s", entry->tpath);
        } else {
            if ((fp = fdopen(entry->fd, "r")) == NULL)
-               error(1, "%s", entry->path);
+               fatal("%s", entry->path);
            rewind(fp);
        }
     }
     if (keepopen != NULL)
-       *keepopen = TRUE;
-    return fp;
+       *keepopen = true;
+    debug_return_ptr(fp);
 }
 
 static char *
 get_editor(char **args)
 {
     char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
+    debug_decl(get_editor, SUDO_DEBUG_UTIL)
 
     /*
      * Check VISUAL and EDITOR environment variables to see which editor
@@ -823,7 +935,7 @@ get_editor(char **args)
        } else {
            if (def_env_editor) {
                /* If we are honoring $EDITOR this is a fatal error. */
-               errorx(1, _("specified editor (%s) doesn't exist"), UserEditor);
+               fatalx(_("specified editor (%s) doesn't exist"), UserEditor);
            } else {
                /* Otherwise, just ignore $EDITOR. */
                UserEditor = NULL;
@@ -846,7 +958,7 @@ get_editor(char **args)
 
        if (stat(UserEditor, &user_editor_sb) != 0) {
            /* Should never happen since we already checked above. */
-           error(1, _("unable to stat editor (%s)"), UserEditor);
+           fatal(_("unable to stat editor (%s)"), UserEditor);
        }
        EditorPath = estrdup(def_editor);
        Editor = strtok(EditorPath, ":");
@@ -894,10 +1006,10 @@ get_editor(char **args)
 
        /* Bleah, none of the editors existed! */
        if (Editor == NULL || *Editor == '\0')
-           errorx(1, _("no editor found (editor path = %s)"), def_editor);
+           fatalx(_("no editor found (editor path = %s)"), def_editor);
     }
     *args = EditorArgs;
-    return Editor;
+    debug_return_str(Editor);
 }
 
 /*
@@ -907,6 +1019,7 @@ static char *
 get_args(char *cmnd)
 {
     char *args;
+    debug_decl(get_args, SUDO_DEBUG_UTIL)
 
     args = cmnd;
     while (*args && !isblank((unsigned char) *args))
@@ -916,7 +1029,7 @@ get_args(char *cmnd)
        while (*args && isblank((unsigned char) *args))
            args++;
     }
-    return *args ? args : NULL;
+    debug_return_str(*args ? args : NULL);
 }
 
 /*
@@ -925,44 +1038,44 @@ get_args(char *cmnd)
 static void
 get_hostname(void)
 {
-    char *p, thost[MAXHOSTNAMELEN + 1];
+    char *p, thost[HOST_NAME_MAX + 1];
+    debug_decl(get_hostname, SUDO_DEBUG_UTIL)
 
-    if (gethostname(thost, sizeof(thost)) != 0) {
-       user_host = user_shost = "localhost";
-       return;
-    }
-    thost[sizeof(thost) - 1] = '\0';
-    user_host = estrdup(thost);
+    if (gethostname(thost, sizeof(thost)) != -1) {
+       thost[sizeof(thost) - 1] = '\0';
+       user_host = estrdup(thost);
 
-    if ((p = strchr(user_host, '.'))) {
-       *p = '\0';
-       user_shost = estrdup(user_host);
-       *p = '.';
+       if ((p = strchr(user_host, '.'))) {
+           *p = '\0';
+           user_shost = estrdup(user_host);
+           *p = '.';
+       } else {
+           user_shost = user_host;
+       }
     } else {
-       user_shost = user_host;
+       user_host = user_shost = "localhost";
     }
+    debug_return;
 }
 
-static int
-alias_remove_recursive(char *name, int type, int strict, int quiet)
+static bool
+alias_remove_recursive(char *name, int type)
 {
     struct member *m;
     struct alias *a;
-    int error = 0;
+    bool rval = true;
+    debug_decl(alias_remove_recursive, SUDO_DEBUG_ALIAS)
 
-    if ((a = alias_find(name, type)) != NULL) {
+    if ((a = alias_remove(name, type)) != NULL) {
        tq_foreach_fwd(&a->members, m) {
            if (m->type == ALIAS) {
-               if (!alias_remove_recursive(m->name, type, strict, quiet))
-                   error = 1;
+               if (!alias_remove_recursive(m->name, type))
+                   rval = false;
            }
        }
-    }
-    alias_seqno++;
-    a = alias_remove(name, type);
-    if (a)
        rbinsert(alias_freelist, a);
-    return error;
+    }
+    debug_return_bool(rval);
 }
 
 static int
@@ -970,14 +1083,16 @@ check_alias(char *name, int type, int strict, int quiet)
 {
     struct member *m;
     struct alias *a;
-    int error = 0;
+    int errors = 0;
+    debug_decl(check_alias, SUDO_DEBUG_ALIAS)
 
-    if ((a = alias_find(name, type)) != NULL) {
+    if ((a = alias_get(name, type)) != NULL) {
        /* check alias contents */
        tq_foreach_fwd(&a->members, m) {
            if (m->type == ALIAS)
-               error += check_alias(m->name, type, strict, quiet);
+               errors += check_alias(m->name, type, strict, quiet);
        }
+       alias_put(a);
     } else {
        if (!quiet) {
            char *fmt;
@@ -995,10 +1110,10 @@ check_alias(char *name, int type, int strict, int quiet)
                type == USERALIAS ? "User" : type == RUNASALIAS ? "Runas" :
                "Unknown", name);
        }
-       error++;
+       errors++;
     }
 
-    return error;
+    debug_return_int(errors);
 }
 
 /*
@@ -1006,14 +1121,15 @@ check_alias(char *name, int type, int strict, int quiet)
  * aliases or unused aliases.
  */
 static int
-check_aliases(int strict, int quiet)
+check_aliases(bool strict, bool quiet)
 {
     struct cmndspec *cs;
     struct member *m, *binding;
     struct privilege *priv;
     struct userspec *us;
     struct defaults *d;
-    int atype, error = 0;
+    int atype, errors = 0;
+    debug_decl(check_aliases, SUDO_DEBUG_ALIAS)
 
     alias_freelist = rbcreate(alias_compare);
 
@@ -1021,27 +1137,23 @@ check_aliases(int strict, int quiet)
     tq_foreach_fwd(&userspecs, us) {
        tq_foreach_fwd(&us->users, m) {
            if (m->type == ALIAS) {
-               alias_seqno++;
-               error += check_alias(m->name, USERALIAS, strict, quiet);
+               errors += check_alias(m->name, USERALIAS, strict, quiet);
            }
        }
        tq_foreach_fwd(&us->privileges, priv) {
            tq_foreach_fwd(&priv->hostlist, m) {
                if (m->type == ALIAS) {
-                   alias_seqno++;
-                   error += check_alias(m->name, HOSTALIAS, strict, quiet);
+                   errors += check_alias(m->name, HOSTALIAS, strict, quiet);
                }
            }
            tq_foreach_fwd(&priv->cmndlist, cs) {
                tq_foreach_fwd(&cs->runasuserlist, m) {
                    if (m->type == ALIAS) {
-                       alias_seqno++;
-                       error += check_alias(m->name, RUNASALIAS, strict, quiet);
+                       errors += check_alias(m->name, RUNASALIAS, strict, quiet);
                    }
                }
                if ((m = cs->cmnd)->type == ALIAS) {
-                   alias_seqno++;
-                   error += check_alias(m->name, CMNDALIAS, strict, quiet);
+                   errors += check_alias(m->name, CMNDALIAS, strict, quiet);
                }
            }
        }
@@ -1051,34 +1163,27 @@ check_aliases(int strict, int quiet)
     tq_foreach_fwd(&userspecs, us) {
        tq_foreach_fwd(&us->users, m) {
            if (m->type == ALIAS) {
-               alias_seqno++;
-               if (!alias_remove_recursive(m->name, USERALIAS, strict, quiet))
-                   error++;
+               if (!alias_remove_recursive(m->name, USERALIAS))
+                   errors++;
            }
        }
        tq_foreach_fwd(&us->privileges, priv) {
            tq_foreach_fwd(&priv->hostlist, m) {
                if (m->type == ALIAS) {
-                   alias_seqno++;
-                   if (!alias_remove_recursive(m->name, HOSTALIAS, strict,
-                       quiet))
-                       error++;
+                   if (!alias_remove_recursive(m->name, HOSTALIAS))
+                       errors++;
                }
            }
            tq_foreach_fwd(&priv->cmndlist, cs) {
                tq_foreach_fwd(&cs->runasuserlist, m) {
                    if (m->type == ALIAS) {
-                       alias_seqno++;
-                       if (!alias_remove_recursive(m->name, RUNASALIAS,
-                           strict, quiet))
-                           error++;
+                       if (!alias_remove_recursive(m->name, RUNASALIAS))
+                           errors++;
                    }
                }
                if ((m = cs->cmnd)->type == ALIAS) {
-                   alias_seqno++;
-                   if (!alias_remove_recursive(m->name, CMNDALIAS, strict,
-                       quiet))
-                       error++;
+                   if (!alias_remove_recursive(m->name, CMNDALIAS))
+                       errors++;
                }
            }
        }
@@ -1103,9 +1208,8 @@ check_aliases(int strict, int quiet)
        tq_foreach_fwd(&d->binding, binding) {
            for (m = binding; m != NULL; m = m->next) {
                if (m->type == ALIAS) {
-                   alias_seqno++;
-                   if (!alias_remove_recursive(m->name, atype, strict, quiet))
-                       error++;
+                   if (!alias_remove_recursive(m->name, atype))
+                       errors++;
                }
            }
        }
@@ -1116,7 +1220,7 @@ check_aliases(int strict, int quiet)
     if (!no_aliases() && !quiet)
        alias_apply(print_unused, strict ? "Error" : "Warning");
 
-    return strict ? error : 0;
+    debug_return_int(strict ? errors : 0);
 }
 
 static int
@@ -1125,7 +1229,7 @@ print_unused(void *v1, void *v2)
     struct alias *a = (struct alias *)v1;
     char *prefix = (char *)v2;
 
-    warningx(_("%s: unused %s_Alias %s"), prefix,
+    warningx_nodebug(_("%s: unused %s_Alias %s"), prefix,
        a->type == HOSTALIAS ? "Host" : a->type == CMNDALIAS ? "Cmnd" :
        a->type == USERALIAS ? "User" : a->type == RUNASALIAS ? "Runas" :
        "Unknown", a->name);
@@ -1135,8 +1239,8 @@ print_unused(void *v1, void *v2)
 /*
  * Unlink any sudoers temp files that remain.
  */
-void
-cleanup(int gotsignal)
+static void
+visudo_cleanup(void)
 {
     struct sudoersfile *sp;
 
@@ -1144,10 +1248,8 @@ cleanup(int gotsignal)
        if (sp->tpath != NULL)
            (void) unlink(sp->tpath);
     }
-    if (!gotsignal) {
-       sudo_endpwent();
-       sudo_endgrent();
-    }
+    sudo_endpwent();
+    sudo_endgrent();
 }
 
 /*
@@ -1156,17 +1258,24 @@ cleanup(int gotsignal)
 static void
 quit(int signo)
 {
-    const char *signame, *myname;
+    struct sudoersfile *sp;
+    struct iovec iov[4];
+
+    tq_foreach_fwd(&sudoerslist, sp) {
+       if (sp->tpath != NULL)
+           (void) unlink(sp->tpath);
+    }
 
-    cleanup(signo);
 #define        emsg     " exiting due to signal: "
-    myname = getprogname();
-    signame = strsignal(signo);
-    if (write(STDERR_FILENO, myname, strlen(myname)) == -1 ||
-       write(STDERR_FILENO, emsg, sizeof(emsg) - 1) == -1 ||
-       write(STDERR_FILENO, signame, strlen(signame)) == -1 ||
-       write(STDERR_FILENO, "\n", 1) == -1)
-       /* shut up glibc */;
+    iov[0].iov_base = (char *)getprogname();
+    iov[0].iov_len = strlen(iov[0].iov_base);
+    iov[1].iov_base = emsg;
+    iov[1].iov_len = sizeof(emsg) - 1;
+    iov[2].iov_base = strsignal(signo);
+    iov[2].iov_len = strlen(iov[2].iov_base);
+    iov[3].iov_base = "\n";
+    iov[3].iov_len = 1;
+    ignore_result(writev(STDERR_FILENO, iov, 4));
     _exit(signo);
 }
 
@@ -1193,28 +1302,3 @@ help(void)
        "  -V          display version information and exit"));
     exit(0);
 }
-
-static int
-visudo_printf(int msg_type, const char *fmt, ...)
-{
-    va_list ap;
-    FILE *fp;
-            
-    switch (msg_type) {
-    case SUDO_CONV_INFO_MSG:
-       fp = stdout;
-       break;
-    case SUDO_CONV_ERROR_MSG:
-       fp = stderr;
-       break;
-    default:
-       errno = EINVAL;
-       return -1;
-    }
-   
-    va_start(ap, fmt);
-    vfprintf(fp, fmt, ap);
-    va_end(ap);
-   
-    return 0;
-}