]> granicus.if.org Git - sudo/commitdiff
Use a common function for resolviong the user's editor in sudoedit
authorTodd C. Miller <Todd.Miller@courtesan.com>
Thu, 18 Jun 2015 15:51:36 +0000 (09:51 -0600)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Thu, 18 Jun 2015 15:51:36 +0000 (09:51 -0600)
and visudo.  The find_path() function now returns a dynamically
allocated path instead of using a static string.

MANIFEST
plugins/sudoers/Makefile.in
plugins/sudoers/editor.c [new file with mode: 0644]
plugins/sudoers/find_path.c
plugins/sudoers/goodpath.c
plugins/sudoers/sudoers.c
plugins/sudoers/sudoers.h
plugins/sudoers/visudo.c

index 1eeb85cc39cc48e1ee7c76080aa8ece98f7bf0e7..b6300c2c21ddffb163d921578d502784ae286931 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -256,6 +256,7 @@ plugins/sudoers/def_data.h
 plugins/sudoers/def_data.in
 plugins/sudoers/defaults.c
 plugins/sudoers/defaults.h
+plugins/sudoers/editor.c
 plugins/sudoers/env.c
 plugins/sudoers/find_path.c
 plugins/sudoers/getdate.c
index 6d8d3d7daaed2f7a9fa7be576322cc4ea4184755..a5b3b2efb2e4fc462c57cc6f8ac25cd2b7b388bd 100644 (file)
@@ -147,13 +147,13 @@ LIBPARSESUDOERS_OBJS = alias.lo audit.lo base64.lo defaults.lo hexchar.lo \
                       redblack.lo sudoers_debug.lo timestr.lo toke.lo \
                       toke_util.lo
 
-SUDOERS_OBJS = $(AUTH_OBJS) boottime.lo check.lo env.lo find_path.lo \
+SUDOERS_OBJS = $(AUTH_OBJS) boottime.lo check.lo editor.lo env.lo find_path.lo \
               goodpath.lo group_plugin.lo interfaces.lo iolog.lo \
               iolog_path.lo locale.lo logging.lo logwrap.lo parse.lo \
               policy.lo prompt.lo set_perms.lo sudo_nss.lo sudoers.lo \
               timestamp.lo @SUDOERS_OBJS@
 
-VISUDO_OBJS = find_path.o goodpath.o locale.o sudo_printf.o visudo.o \
+VISUDO_OBJS = editor.o find_path.o goodpath.o locale.o sudo_printf.o visudo.o \
              visudo_json.o
 
 REPLAY_OBJS = getdate.o locale.o sudoreplay.o
@@ -615,6 +615,16 @@ defaults.lo: $(srcdir)/defaults.c $(devdir)/def_data.c $(devdir)/def_data.h \
              $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \
              $(top_builddir)/config.h $(top_builddir)/pathnames.h
        $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/defaults.c
+editor.lo: $(srcdir)/editor.c $(devdir)/def_data.h $(incdir)/compat/stdbool.h \
+           $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+           $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+           $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+           $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/defaults.h \
+           $(srcdir)/logging.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \
+           $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \
+           $(top_builddir)/pathnames.h
+       $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/editor.c
+editor.o: editor.lo
 env.lo: $(srcdir)/env.c $(devdir)/def_data.h $(incdir)/compat/stdbool.h \
         $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
         $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
diff --git a/plugins/sudoers/editor.c b/plugins/sudoers/editor.c
new file mode 100644 (file)
index 0000000..8eee62d
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2010-2015 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#include <errno.h>
+
+#include "sudoers.h"
+
+/*
+ * Search for the specified editor in the user's PATH, checking
+ * the result against whitelist if non-NULL.  An argument vector
+ * suitable for execve() is allocated and stored in argv_out.
+ * If nfiles is non-zero, files[] is added to the end of argv_out.
+ * Returns the path to be executed on success, else NULL.
+ * The caller is responsible for freeing the returned editor path
+ * as well as the argument vector.
+ */
+char *
+resolve_editor(const char *ed, size_t edlen, int nfiles, char **files,
+    int *argc_out, char ***argv_out, char * const *whitelist)
+{
+    char **nargv, *editor, *editor_path = NULL;
+    const char *cp, *ep, *tmp;
+    const char *edend = ed + edlen;
+    struct stat user_editor_sb;
+    int nargc;
+    debug_decl(resolve_editor, SUDOERS_DEBUG_UTIL)
+
+    /*
+     * Split editor into an argument vector, including files to edit.
+     * The EDITOR and VISUAL environment variables may contain command
+     * line args so look for those and alloc space for them too.
+     */
+    cp = sudo_strsplit(ed, edend, " \t", &ep);
+    if (cp == NULL)
+       debug_return_str(NULL);
+    editor = strndup(cp, (size_t)(ep - cp));
+    if (editor == NULL) {
+       sudo_warnx(U_("unable to allocate memory"));
+       debug_return_str(NULL);
+    }
+
+    /* If we can't find the editor in the user's PATH, give up. */
+    if (find_path(editor, &editor_path, &user_editor_sb, getenv("PATH"), 0, whitelist) != FOUND) {
+       free(editor);
+       errno = ENOENT;
+       debug_return_str(NULL);
+    }
+
+    /* Count rest of arguments and allocate editor argv. */
+    for (nargc = 1, tmp = ep; sudo_strsplit(NULL, edend, " \t", &tmp) != NULL; )
+       nargc++;
+    if (nfiles != 0)
+       nargc += nfiles + 1;
+    nargv = reallocarray(NULL, nargc + 1, sizeof(char *));
+    if (nargv == NULL) {
+       sudo_warnx(U_("unable to allocate memory"));
+       free(editor);
+       debug_return_str(NULL);
+    }
+
+    /* Fill in editor argv (assumes files[] is NULL-terminated). */
+    nargv[0] = editor;
+    for (nargc = 1; (cp = sudo_strsplit(NULL, edend, " \t", &ep)) != NULL; nargc++) {
+       nargv[nargc] = strndup(cp, (size_t)(ep - cp));
+       if (nargv[nargc] == NULL) {
+           sudo_warnx(U_("unable to allocate memory"));
+           while (nargc--)
+               free(nargv[nargc]);
+           debug_return_str(NULL);
+       }
+    }
+    if (nfiles != 0) {
+       nargv[nargc++] = "--";
+       while (nfiles--)
+           nargv[nargc++] = *files++;
+    }
+    nargv[nargc] = NULL;
+
+    *argc_out = nargc;
+    *argv_out = nargv;
+    debug_return_str(editor_path);
+}
index 4ca0f5ea012af1bd3d27c75b8e2eda6db41a8860..64e2e43df25db76c8843665daed48e7b5c807f5c 100644 (file)
 
 #include "sudoers.h"
 
+/*
+ * Check the given command against the specified whitelist (NULL-terminated).
+ * On success, rewrites cmnd based on the whitelist and returns true.
+ * On failure, returns false.
+ */
+static bool
+cmnd_allowed(char *cmnd, size_t cmnd_size, struct stat *cmnd_sbp,
+    char * const *whitelist)
+{
+    const char *cmnd_base;
+    char * const *wl;
+    debug_decl(cmnd_allowed, SUDOERS_DEBUG_UTIL)
+
+    if (!sudo_goodpath(cmnd, cmnd_sbp))
+       debug_return_bool(false);
+
+    if (whitelist == NULL)
+       debug_return_bool(true);        /* nothing to check */
+
+    /* We compare the base names to avoid excessive stat()ing. */
+    if ((cmnd_base = strrchr(cmnd, '/')) == NULL)
+       debug_return_bool(false);       /* can't happen */
+    cmnd_base++;
+
+    for (wl = whitelist; *wl != NULL; wl++) {
+       struct stat sb;
+       const char *base;
+
+       if ((base = strrchr(*wl, '/')) == NULL)
+           continue;           /* XXX - warn? */
+       base++;
+
+       if (strcmp(cmnd_base, base) != 0)
+           continue;
+
+       if (sudo_goodpath(*wl, &sb) &&
+           sb.st_dev == cmnd_sbp->st_dev && sb.st_ino == cmnd_sbp->st_ino) {
+           /* Overwrite cmnd with safe version from whitelist. */
+           if (strlcpy(cmnd, *wl, cmnd_size) < cmnd_size)
+               return true;
+               debug_return_bool(true);
+       }
+    }
+    debug_return_bool(false);
+}
+
 /*
  * This function finds the full pathname for a command and
  * stores it in a statically allocated array, filling in a pointer
  * to the array.  Returns FOUND if the command was found, NOT_FOUND
  * if it was not found, or NOT_FOUND_DOT if it would have been found
  * but it is in '.' and IGNORE_DOT is set.
+ * The caller is responsible for freeing the output file.
  */
 int
 find_path(const char *infile, char **outfile, struct stat *sbp,
-    const char *path, int ignore_dot)
+    const char *path, int ignore_dot, char * const *whitelist)
 {
-    static char command[PATH_MAX];
+    char command[PATH_MAX];
     const char *cp, *ep, *pathend;
     bool found = false;
     bool checkdot = false;
     int len;
     debug_decl(find_path, SUDOERS_DEBUG_UTIL)
 
-    if (strlen(infile) >= PATH_MAX) {
-       errno = ENAMETOOLONG;
-       debug_return_int(NOT_FOUND_ERROR);
-    }
-
     /*
      * If we were given a fully qualified or relative path
      * there is no need to look at $PATH.
      */
     if (strchr(infile, '/') != NULL) {
-       strlcpy(command, infile, sizeof(command));      /* paranoia */
-       if (sudo_goodpath(command, sbp)) {
-           *outfile = command;
-           debug_return_int(FOUND);
-       } else
-           debug_return_int(NOT_FOUND);
+       if (strlcpy(command, infile, sizeof(command)) >= sizeof(command)) {
+           errno = ENAMETOOLONG;
+           debug_return_int(NOT_FOUND_ERROR);
+       }
+       found = cmnd_allowed(command, sizeof(command), sbp, whitelist);
+       goto done;
     }
 
     if (path == NULL)
@@ -106,7 +148,8 @@ find_path(const char *infile, char **outfile, struct stat *sbp,
            errno = ENAMETOOLONG;
            debug_return_int(NOT_FOUND_ERROR);
        }
-       if ((found = sudo_goodpath(command, sbp)))
+       found = cmnd_allowed(command, sizeof(command), sbp, whitelist);
+       if (found)
            break;
     }
 
@@ -119,14 +162,16 @@ find_path(const char *infile, char **outfile, struct stat *sbp,
            errno = ENAMETOOLONG;
            debug_return_int(NOT_FOUND_ERROR);
        }
-       found = sudo_goodpath(command, sbp);
+       found = cmnd_allowed(command, sizeof(command), sbp, whitelist);
        if (found && ignore_dot)
            debug_return_int(NOT_FOUND_DOT);
     }
 
+done:
     if (found) {
-       *outfile = command;
+       if ((*outfile = strdup(command)) == NULL)
+           debug_return_int(NOT_FOUND_ERROR);
        debug_return_int(FOUND);
-    } else
-       debug_return_int(NOT_FOUND);
+    }
+    debug_return_int(NOT_FOUND);
 }
index d67a096371e47e17c6ac0b1cc018010deec488b5..5a68391667881137b83b3c73e2776ec577863be3 100644 (file)
 bool
 sudo_goodpath(const char *path, struct stat *sbp)
 {
-    struct stat sb;
     bool rval = false;
     debug_decl(sudo_goodpath, SUDOERS_DEBUG_UTIL)
 
-    if (path != NULL && stat(path, &sb) == 0) {
-       /* Make sure path describes an executable regular file. */
-       if (S_ISREG(sb.st_mode) && ISSET(sb.st_mode, 0111))
-           rval = true;
-       else
-           errno = EACCES;
-       if (sbp)
-           (void) memcpy(sbp, &sb, sizeof(struct stat));
+    if (path != NULL) {
+       struct stat sb;
+
+       if (sbp == NULL)
+           sbp = &sb;
+
+       if (stat(path, sbp) == 0) {
+           /* Make sure path describes an executable regular file. */
+           if (S_ISREG(sbp->st_mode) && ISSET(sbp->st_mode, 0111))
+               rval = true;
+           else
+               errno = EACCES;
+       }
     }
 
     debug_return_bool(rval);
index 89ee2f7df741ee3302979534788e247ff99c2548..f6cd6df7c2c0ca97ce83630a93ca14f825461562 100644 (file)
@@ -604,7 +604,8 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
        int edit_argc;
 
        free(safe_cmnd);
-       safe_cmnd = find_editor(NewArgc - 1, NewArgv + 1, &edit_argc, &edit_argv);
+       safe_cmnd = find_editor(NewArgc - 1, NewArgv + 1, &edit_argc,
+           &edit_argv);
        if (safe_cmnd == NULL || audit_success(edit_argc, edit_argv) != 0)
            goto bad;
 
@@ -754,7 +755,7 @@ set_cmnd(void)
            if (!set_perms(PERM_RUNAS))
                debug_return_int(-1);
            rval = find_path(NewArgv[0], &user_cmnd, user_stat, path,
-               def_ignore_dot);
+               def_ignore_dot, NULL);
            if (!restore_perms())
                debug_return_int(-1);
            if (rval == NOT_FOUND) {
@@ -762,7 +763,7 @@ set_cmnd(void)
                if (!set_perms(PERM_USER))
                    debug_return_int(-1);
                rval = find_path(NewArgv[0], &user_cmnd, user_stat, path,
-                   def_ignore_dot);
+                   def_ignore_dot, NULL);
                if (!restore_perms())
                    debug_return_int(-1);
            }
@@ -1169,78 +1170,18 @@ sudoers_cleanup(void)
     debug_return;
 }
 
-static char *
-resolve_editor(const char *ed, size_t edlen, int nfiles, char **files,
-    int *argc_out, char ***argv_out)
-{
-    char **nargv, *editor, *editor_path = NULL;
-    const char *cp, *ep, *tmp;
-    const char *edend = ed + edlen;
-    int nargc;
-    debug_decl(resolve_editor, SUDOERS_DEBUG_PLUGIN)
-
-    /*
-     * Split editor into an argument vector, including files to edit.
-     * The EDITOR and VISUAL environment variables may contain command
-     * line args so look for those and alloc space for them too.
-     */
-    cp = sudo_strsplit(ed, edend, " \t", &ep);
-    if (cp == NULL)
-       debug_return_str(NULL);
-    editor = strndup(cp, (size_t)(ep - cp));
-    if (editor == NULL) {
-       sudo_warnx(U_("unable to allocate memory"));
-       debug_return_str(NULL);
-    }
-
-    /* If we can't find the editor in the user's PATH, give up. */
-    if (find_path(editor, &editor_path, NULL, getenv("PATH"), 0) != FOUND) {
-       free(editor);
-       errno = ENOENT;
-       debug_return_str(NULL);
-    }
-
-    /* Count rest of arguments and allocate editor argv. */
-    for (nargc = 1, tmp = ep; sudo_strsplit(NULL, edend, " \t", &tmp) != NULL; )
-       nargc++;
-    nargv = reallocarray(NULL, nargc + 1 + nfiles + 1, sizeof(char *));
-    if (nargv == NULL) {
-       sudo_warnx(U_("unable to allocate memory"));
-       free(editor);
-       debug_return_str(NULL);
-    }
-
-    /* Fill in editor argv (assumes files[] is NULL-terminated). */
-    nargv[0] = editor;
-    for (nargc = 1; (cp = sudo_strsplit(NULL, edend, " \t", &ep)) != NULL; nargc++) {
-       nargv[nargc] = strndup(cp, (size_t)(ep - cp));
-       if (nargv[nargc] == NULL) {
-           sudo_warnx(U_("unable to allocate memory"));
-           while (nargc--)
-               free(nargv[nargc]);
-           debug_return_str(NULL);
-       }
-    }
-    nargv[nargc++] = "--";
-    while ((nargv[nargc++] = *files++) != NULL)
-       continue;
-
-    *argc_out = nargc;
-    *argv_out = nargv;
-    debug_return_str(editor_path);
-}
-
 /*
  * Determine which editor to use.  We don't need to worry about restricting
  * this to a "safe" editor since it runs with the uid of the invoking user,
  * not the runas (privileged) user.
+ * Returns a fully-qualified path to the editor on success and fills
+ * in argc_out and argv_out accordingly.  Returns NULL on failure.
  */
 static char *
 find_editor(int nfiles, char **files, int *argc_out, char ***argv_out)
 {
-    const char *cp, *ep, *editor;
+    const char *cp, *ep, *editor = NULL;
     char *editor_path = NULL, **ev, *ev0[4];
-    size_t len;
     debug_decl(find_editor, SUDOERS_DEBUG_PLUGIN)
 
     /*
@@ -1252,29 +1193,27 @@ find_editor(int nfiles, char **files, int *argc_out, char ***argv_out)
     ev0[3] = NULL;
     for (ev = ev0; editor_path == NULL && *ev != NULL; ev++) {
        if ((editor = getenv(*ev)) != NULL && *editor != '\0') {
-           editor_path = resolve_editor(editor, strlen(editor), nfiles,
-               files, argc_out, argv_out);
+           editor_path = resolve_editor(editor, strlen(editor),
+               nfiles, files, argc_out, argv_out, NULL);
            if (editor_path == NULL && errno != ENOENT)
                debug_return_str(NULL);
        }
     }
     if (editor_path == NULL) {
        /* def_editor could be a path, split it up, avoiding strtok() */
-       cp = editor = def_editor;
-       do {
-           if ((ep = strchr(cp, ':')) != NULL)
-               len = ep - cp;
-           else
-               len = strlen(cp);
-           editor_path = resolve_editor(cp, len, nfiles, files, argc_out, argv_out);
+       const char *def_editor_end = def_editor + strlen(def_editor);
+       for (cp = sudo_strsplit(def_editor, def_editor_end, ":", &ep);
+           cp != NULL; cp = sudo_strsplit(NULL, def_editor_end, ":", &ep)) {
+           editor_path = resolve_editor(cp, (size_t)(ep - cp), nfiles,
+               files, argc_out, argv_out, NULL);
            if (editor_path == NULL && errno != ENOENT)
                debug_return_str(NULL);
-           cp = ep + 1;
        } while (ep != NULL && editor_path == NULL);
     }
     if (!editor_path) {
-       audit_failure(NewArgc, NewArgv, N_("%s: command not found"), editor);
-       sudo_warnx(U_("%s: command not found"), editor);
+       audit_failure(NewArgc, NewArgv, N_("%s: command not found"),
+           editor ? editor : def_editor);
+       sudo_warnx(U_("%s: command not found"), editor ? editor : def_editor);
     }
     debug_return_str(editor_path);
 }
index 2e389fdac98d58bf673913bb0a1d271d9c85169d..315fac425ccf4eb56e364e9b4ef556dc788815b3 100644 (file)
@@ -130,6 +130,7 @@ struct sudo_user {
 #define NOT_FOUND              1
 #define NOT_FOUND_DOT          2
 #define NOT_FOUND_ERROR                3
+#define NOT_FOUND_PATH         4
 
 /*
  * Various modes sudo can be in (based on arguments) in hex
@@ -223,10 +224,11 @@ struct timeval;
 #define YY_DECL int sudoerslex(void)
 
 /* goodpath.c */
-bool sudo_goodpath(const char *, struct stat *);
+bool sudo_goodpath(const char *path, struct stat *sbp);
 
 /* findpath.c */
-int find_path(const char *, char **, struct stat *, const char *, int);
+int find_path(const char *infile, char **outfile, struct stat *sbp,
+    const char *path, int ignore_dot, char * const *whitelist);
 
 /* check.c */
 int check_user(int validate, int mode);
@@ -369,4 +371,8 @@ void group_plugin_unload(void);
 int group_plugin_query(const char *user, const char *group,
     const struct passwd *pwd);
 
+/* editor.c */
+char *resolve_editor(const char *ed, size_t edlen, int nfiles, char **files,
+    int *argc_out, char ***argv_out, char * const *whitelist);
+
 #endif /* SUDOERS_SUDOERS_H */
index 08c451df35a4c7d6da69ce3c81b9b78005ed75a0..21bc30b814ba41a5e70f7932a554c62d96931f59 100644 (file)
@@ -99,16 +99,15 @@ TAILQ_HEAD(sudoersfile_list, sudoersfile);
  * Function prototypes
  */
 static void quit(int);
-static char *get_args(char *);
-static char *get_editor(char **);
 static void get_hostname(void);
 static int whatnow(void);
 static int check_aliases(bool, bool);
+static char *get_editor(int *editor_argc, char ***editor_argv);
 static bool check_syntax(const char *, bool, bool, bool);
-static bool edit_sudoers(struct sudoersfile *, char *, char *, int);
+static bool edit_sudoers(struct sudoersfile *, char *, int, char **, int);
 static bool install_sudoers(struct sudoersfile *, bool);
 static int print_unused(void *, void *);
-static bool reparse_sudoers(char *, char *, bool, bool);
+static bool reparse_sudoers(char *, int, char **, bool, bool);
 static int run_command(char *, char **);
 static void parse_sudoers_options(void);
 static void setup_signals(void);
@@ -147,8 +146,8 @@ int
 main(int argc, char *argv[])
 {
     struct sudoersfile *sp;
-    char *args, *editor;
-    int ch, exitcode = 0;
+    char *editor, **editor_argv;
+    int ch, editor_argc, exitcode = 0;
     bool quiet, strict, oldperms;
     const char *export_path;
     debug_decl(main, SUDOERS_DEBUG_MAIN)
@@ -253,7 +252,7 @@ main(int argc, char *argv[])
     sudoersparse();
     (void) update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER);
 
-    editor = get_editor(&args);
+    editor = get_editor(&editor_argc, &editor_argv);
 
     /* Install signal handlers to clean up temp files if we are killed. */
     setup_signals();
@@ -267,14 +266,14 @@ main(int argc, char *argv[])
            while ((ch = getchar()) != EOF && ch != '\n')
                    continue;
        }
-       edit_sudoers(sp, editor, args, -1);
+       edit_sudoers(sp, editor, editor_argc, editor_argv, -1);
     }
 
     /*
      * 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)) {
+    if (reparse_sudoers(editor, editor_argc, editor_argv, strict, quiet)) {
        TAILQ_FOREACH(sp, &sudoerslist, entries) {
            if (sp->doedit)
                (void) install_sudoers(sp, oldperms);
@@ -286,6 +285,75 @@ done:
     exit(exitcode);
 }
 
+static char *
+get_editor(int *editor_argc, char ***editor_argv)
+{
+    char *editor, *editor_path = NULL, **whitelist = NULL;
+    static char *files[] = { "+1", "sudoers" };
+    unsigned int whitelist_len = 0;
+    debug_decl(get_editor, SUDOERS_DEBUG_UTIL)
+
+    /* Build up editor whitelist from def_editor unless env_editor is set. */
+    if (!def_env_editor) {
+       const char *cp, *ep;
+       const char *def_editor_end = def_editor + strlen(def_editor);
+
+       /* Count number of entries in whitelist and split into a list. */
+       for (cp = sudo_strsplit(def_editor, def_editor_end, ":", &ep);
+           cp != NULL; cp = sudo_strsplit(NULL, def_editor_end, ":", &ep)) {
+           whitelist_len++;
+       }
+       whitelist = reallocarray(NULL, whitelist_len + 1, sizeof(char *));
+       if (whitelist == NULL)
+           sudo_fatalx(U_("unable to allocate memory"));
+       whitelist_len = 0;
+       for (cp = sudo_strsplit(def_editor, def_editor_end, ":", &ep);
+           cp != NULL; cp = sudo_strsplit(NULL, def_editor_end, ":", &ep)) {
+           whitelist[whitelist_len] = strndup(cp, (size_t)(ep - cp));
+           if (whitelist[whitelist_len] == NULL)
+               sudo_fatalx(U_("unable to allocate memory"));
+           whitelist_len++;
+       }
+       whitelist[whitelist_len] = NULL;
+    }
+
+    /* First try to use user's VISUAL or EDITOR environment vars. */
+    if ((editor = getenv("VISUAL")) == NULL || *editor == '\0')
+       editor = getenv("EDITOR");
+    if (editor && *editor == '\0')
+       editor = NULL;
+    if (editor != NULL) {
+       editor_path = resolve_editor(editor, strlen(editor), 2, files,
+           editor_argc, editor_argv, whitelist);
+       if (def_env_editor && editor_path == NULL) {
+           /* If we are honoring $EDITOR this is a fatal error. */
+           sudo_fatalx(U_("specified editor (%s) doesn't exist"), editor);
+       }
+    }
+    if (editor_path == NULL) {
+       /* def_editor could be a path, split it up, avoiding strtok() */
+       const char *def_editor_end = def_editor + strlen(def_editor);
+       const char *cp, *ep;
+       for (cp = sudo_strsplit(def_editor, def_editor_end, ":", &ep);
+           cp != NULL; cp = sudo_strsplit(NULL, def_editor_end, ":", &ep)) {
+           editor_path = resolve_editor(cp, (size_t)(ep - cp), 2, files,
+               editor_argc, editor_argv, whitelist);
+           if (editor_path == NULL && errno != ENOENT)
+               debug_return_str(NULL);
+       } while (ep != NULL && editor_path == NULL);
+    }
+    if (editor_path == NULL)
+       sudo_fatalx(U_("no editor found (editor path = %s)"), def_editor);
+
+    if (whitelist != NULL) {
+       while (whitelist_len--)
+           free(whitelist[whitelist_len]);
+       free(whitelist);
+    }
+
+    debug_return_str(editor_path);
+}
+
 /*
  * List of editors that support the "+lineno" command line syntax.
  * If an entry starts with '*' the tail end of the string is matched.
@@ -310,19 +378,52 @@ static char *lineno_editors[] = {
     NULL
 };
 
+/*
+ * Check whether or not the specified editor matched lineno_editors[].
+ * Returns true if yes, false if no.
+ */
+static bool
+editor_supports_plus(const char *editor)
+{
+    const char *editor_base = strrchr(editor, '/');
+    const char *cp;
+    char **av;
+    debug_decl(editor_supports_plus, SUDOERS_DEBUG_UTIL)
+
+    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;
+    }
+    debug_return_bool(cp != NULL);
+}
+
 /*
  * Edit each sudoers file.
  * Returns true on success, else false.
  */
 static bool
-edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
+edit_sudoers(struct sudoersfile *sp, char *editor, int editor_argc,
+    char **editor_argv, int lineno)
 {
     int tfd;                           /* sudoers temp file descriptor */
     bool modified;                     /* was the file modified? */
     int ac;                            /* argument count */
-    char **av;                         /* argument vector for run_command */
-    char *cp;                          /* scratch char pointer */
-    char buf[PATH_MAX*2];              /* buffer used for copying files */
+    char buf[4096];                    /* buffer used for copying files */
     char linestr[64];                  /* string version of lineno */
     struct timespec ts, times[2];      /* time before and after edit */
     struct timespec orig_mtim;         /* starting mtime of sudoers file */
@@ -365,69 +466,21 @@ edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
     times[0].tv_nsec = times[1].tv_nsec = orig_mtim.tv_nsec;
     (void) utimensat(AT_FDCWD, sp->tpath, times, 0);
 
-    /* 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)
+    /* Disable +lineno if editor doesn't support it. */
+    if (lineno > 0 && !editor_supports_plus(editor))
            lineno = -1;
-    }
-
-    /* Find the length of the argument vector */
-    ac = 3 + (lineno > 0);
-    if (args) {
-        bool wasblank;
-
-        ac++;
-        for (wasblank = false, cp = args; *cp; cp++) {
-            if (isblank((unsigned char) *cp))
-                wasblank = true;
-            else if (wasblank) {
-                wasblank = false;
-                ac++;
-            }
-        }
-    }
 
-    /* Build up argument vector for the command */
-    av = reallocarray(NULL, ac, sizeof(char *));
-    if (av == NULL)
-       sudo_fatalx(U_("unable to allocate memory"));
-    if ((av[0] = strrchr(editor, '/')) != NULL)
-       av[0]++;
-    else
-       av[0] = editor;
-    ac = 1;
+    /*
+     * We pre-allocated 2 extra spaces for "+n filename" in argv.
+     * Replace those placeholders with the real values.
+     */
+    ac = editor_argc - 2;
     if (lineno > 0) {
-       (void) snprintf(linestr, sizeof(linestr), "+%d", lineno);
-       av[ac++] = linestr;
-    }
-    if (args) {
-       for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t")))
-           av[ac++] = cp;
+       (void)snprintf(linestr, sizeof(linestr), "+%d", lineno);
+       editor_argv[ac++] = linestr;
     }
-    av[ac++] = sp->tpath;
-    av[ac++] = NULL;
+    editor_argv[ac++] = sp->tpath;
+    editor_argv[ac++] = NULL;
 
     /*
      * Do the edit:
@@ -440,7 +493,7 @@ edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
        goto done;
     }
 
-    if (run_command(editor, av) != -1) {
+    if (run_command(editor, editor_argv) != -1) {
        if (sudo_gettime_real(&times[1]) == -1) {
            sudo_warn(U_("unable to read the clock"));
            goto done;
@@ -495,7 +548,8 @@ done:
  * Parse sudoers after editing and re-edit any ones that caused a parse error.
  */
 static bool
-reparse_sudoers(char *editor, char *args, bool strict, bool quiet)
+reparse_sudoers(char *editor, int editor_argc, char **editor_argv,
+    bool strict, bool quiet)
 {
     struct sudoersfile *sp, *last;
     FILE *fp;
@@ -550,7 +604,8 @@ reparse_sudoers(char *editor, char *args, bool strict, bool quiet)
                /* Edit file with the parse error */
                TAILQ_FOREACH(sp, &sudoerslist, entries) {
                    if (errorfile == NULL || strcmp(sp->path, errorfile) == 0) {
-                       edit_sudoers(sp, editor, args, errorlineno);
+                       edit_sudoers(sp, editor, editor_argc, editor_argv,
+                           errorlineno);
                        if (errorfile != NULL)
                            break;
                    }
@@ -568,7 +623,8 @@ reparse_sudoers(char *editor, char *args, bool strict, bool quiet)
            printf(_("press return to edit %s: "), sp->path);
            while ((ch = getchar()) != EOF && ch != '\n')
                    continue;
-           edit_sudoers(sp, editor, args, errorlineno);
+           edit_sudoers(sp, editor, editor_argc, editor_argv,
+               errorlineno);
        }
 
        /* If all sudoers files parsed OK we are done. */
@@ -952,129 +1008,6 @@ open_sudoers(const char *path, bool doedit, bool *keepopen)
     debug_return_ptr(fp);
 }
 
-static char *
-get_editor(char **args)
-{
-    char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
-    debug_decl(get_editor, SUDOERS_DEBUG_UTIL)
-
-    /*
-     * Check VISUAL and EDITOR environment variables to see which editor
-     * the user wants to use (we may not end up using it though).
-     * If the path is not fully-qualified, make it so and check that
-     * the specified executable actually exists.
-     */
-    UserEditorArgs = NULL;
-    if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
-       UserEditor = getenv("EDITOR");
-    if (UserEditor && *UserEditor == '\0')
-       UserEditor = NULL;
-    else if (UserEditor) {
-       UserEditorArgs = get_args(UserEditor);
-       if (find_path(UserEditor, &Editor, NULL, getenv("PATH"), 0) == FOUND) {
-           UserEditor = Editor;
-       } else {
-           if (def_env_editor) {
-               /* If we are honoring $EDITOR this is a fatal error. */
-               sudo_fatalx(U_("specified editor (%s) doesn't exist"), UserEditor);
-           } else {
-               /* Otherwise, just ignore $EDITOR. */
-               UserEditor = NULL;
-           }
-       }
-    }
-
-    /*
-     * See if we can use the user's choice of editors either because
-     * we allow any $EDITOR or because $EDITOR is in the allowable list.
-     */
-    Editor = EditorArgs = EditorPath = NULL;
-    if (def_env_editor && UserEditor) {
-       Editor = UserEditor;
-       EditorArgs = UserEditorArgs;
-    } else if (UserEditor) {
-       struct stat editor_sb;
-       struct stat user_editor_sb;
-       char *base, *userbase;
-
-       if (stat(UserEditor, &user_editor_sb) != 0) {
-           /* Should never happen since we already checked above. */
-           sudo_fatal(U_("unable to stat editor (%s)"), UserEditor);
-       }
-       if ((EditorPath = strdup(def_editor)) == NULL)
-           sudo_fatalx(U_("unable to allocate memory"));
-       Editor = strtok(EditorPath, ":");
-       do {
-           EditorArgs = get_args(Editor);
-           /*
-            * Both Editor and UserEditor should be fully qualified but
-            * check anyway...
-            */
-           if ((base = strrchr(Editor, '/')) == NULL)
-               continue;
-           if ((userbase = strrchr(UserEditor, '/')) == NULL) {
-               Editor = NULL;
-               break;
-           }
-           base++, userbase++;
-
-           /*
-            * We compare the basenames first and then use stat to match
-            * for sure.
-            */
-           if (strcmp(base, userbase) == 0) {
-               if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
-                   && (editor_sb.st_mode & 0000111) &&
-                   editor_sb.st_dev == user_editor_sb.st_dev &&
-                   editor_sb.st_ino == user_editor_sb.st_ino)
-                   break;
-           }
-       } while ((Editor = strtok(NULL, ":")));
-    }
-
-    /*
-     * Can't use $EDITOR, try each element of def_editor until we
-     * find one that exists, is regular, and is executable.
-     */
-    if (Editor == NULL || *Editor == '\0') {
-       free(EditorPath);
-       if ((EditorPath = strdup(def_editor)) == NULL)
-           sudo_fatalx(U_("unable to allocate memory"));
-       Editor = strtok(EditorPath, ":");
-       do {
-           EditorArgs = get_args(Editor);
-           if (sudo_goodpath(Editor, NULL))
-               break;
-       } while ((Editor = strtok(NULL, ":")));
-
-       /* Bleah, none of the editors existed! */
-       if (Editor == NULL || *Editor == '\0')
-           sudo_fatalx(U_("no editor found (editor path = %s)"), def_editor);
-    }
-    *args = EditorArgs;
-    debug_return_str(Editor);
-}
-
-/*
- * Split out any command line arguments and return them.
- */
-static char *
-get_args(char *cmnd)
-{
-    char *args;
-    debug_decl(get_args, SUDOERS_DEBUG_UTIL)
-
-    args = cmnd;
-    while (*args && !isblank((unsigned char) *args))
-       args++;
-    if (*args) {
-       *args++ = '\0';
-       while (*args && isblank((unsigned char) *args))
-           args++;
-    }
-    debug_return_str(*args ? args : NULL);
-}
-
 /*
  * Look up the hostname and set user_host and user_shost.
  */