]> granicus.if.org Git - sudo/commitdiff
Log sudoedit sessions as well; adapted from trunk
authorTodd C. Miller <Todd.Miller@courtesan.com>
Fri, 4 Jun 2010 18:23:59 +0000 (14:23 -0400)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Fri, 4 Jun 2010 18:23:59 +0000 (14:23 -0400)
--HG--
branch : 1.7

exec.c
pty.c
sudo.c
sudo.h
sudo_edit.c

diff --git a/exec.c b/exec.c
index 349c22488201d41e77c58ef4b26df936fe9217d9..a0448443838362cf36477a10ed20a2785c900d4e 100644 (file)
--- a/exec.c
+++ b/exec.c
@@ -131,12 +131,13 @@ static void deliver_signal(pid_t pid, int signo);
 static int safe_close(int fd);
 
 static void
-pty_setup()
+pty_setup(uid)
+    uid_t uid;
 {
     io_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
     if (io_fds[SFD_USERTTY] != -1) {
        if (!get_pty(&io_fds[SFD_MASTER], &io_fds[SFD_SLAVE],
-           slavename, sizeof(slavename)))
+           slavename, sizeof(slavename), uid))
            error(1, "Can't get pty");
     }
 }
@@ -352,7 +353,7 @@ perform_io(struct io_buffer *iobufs, fd_set *fdsr, fd_set *fdsw)
  *     controlling tty, belongs to child's session but has its own pgrp.
  */
 int
-sudo_execve(const char *path, char *argv[], char *envp[],
+sudo_execve(const char *path, char *argv[], char *envp[], uid_t uid,
     struct command_status *cstat)
 {
     sigaction_t sa;
@@ -367,7 +368,7 @@ sudo_execve(const char *path, char *argv[], char *envp[],
 
     log_io = def_log_output || def_log_input;
     if (log_io) {
-       pty_setup();
+       pty_setup(uid);
        io_log_open();
     }
 
diff --git a/pty.c b/pty.c
index 65f1afd1245b70b2171f5d9a9949c8e7dc897276..19eedd937d9d3877662c091bb576e41917f13d7f 100644 (file)
--- a/pty.c
+++ b/pty.c
 
 #if defined(HAVE_OPENPTY)
 int
-get_pty(master, slave, name, namesz)
+get_pty(master, slave, name, namesz, ttyuid)
     int *master;
     int *slave;
     char *name;
     size_t namesz;
+    uid_t ttyuid;
 {
     struct group *gr;
     gid_t ttygid = -1;
@@ -72,17 +73,18 @@ get_pty(master, slave, name, namesz)
 
     if (openpty(master, slave, name, NULL, NULL) != 0)
        return(0);
-    (void) chown(name, runas_pw->pw_uid, ttygid);
+    (void) chown(name, ttyuid, ttygid);
     return(1);
 }
 
 #elif defined(HAVE__GETPTY)
 int
-get_pty(master, slave, name, namesz)
+get_pty(master, slave, name, namesz, ttyuid)
     int *master;
     int *slave;
     char *name;
     size_t namesz;
+    uid_t ttyuid;
 {
     char *line;
 
@@ -95,7 +97,7 @@ get_pty(master, slave, name, namesz)
        close(*master);
        return(0);
     }
-    (void) chown(line, runas_pw->pw_uid, -1);
+    (void) chown(line, ttyuid, -1);
     strlcpy(name, line, namesz);
     return(1);
 }
@@ -117,11 +119,12 @@ posix_openpt(oflag)
 # endif /* HAVE_POSIX_OPENPT */
 
 int
-get_pty(master, slave, name, namesz)
+get_pty(master, slave, name, namesz, ttyuid)
     int *master;
     int *slave;
     char *name;
     size_t namesz;
+    uid_t ttyuid;
 {
     char *line;
 
@@ -148,7 +151,7 @@ get_pty(master, slave, name, namesz)
     ioctl(*slave, I_PUSH, "ptem");     /* pseudo tty emulation module */
     ioctl(*slave, I_PUSH, "ldterm");   /* line discipline module */
 # endif
-    (void) chown(line, runas_pw->pw_uid, -1);
+    (void) chown(line, ttyuid, -1);
     strlcpy(name, line, namesz);
     return(1);
 }
@@ -158,11 +161,12 @@ get_pty(master, slave, name, namesz)
 static char line[] = "/dev/ptyXX";
 
 int
-get_pty(master, slave, name, namesz)
+get_pty(master, slave, name, namesz, ttyuid)
     int *master;
     int *slave;
     char *name;
     size_t namesz;
+    uid_t ttyuid;
 {
     char *bank, *cp;
     struct group *gr;
@@ -182,7 +186,7 @@ get_pty(master, slave, name, namesz)
                continue; /* already in use */
            }
            line[sizeof("/dev/p") - 2] = 't';
-           (void) chown(line, runas_pw->pw_uid, ttygid);
+           (void) chown(line, ttyuid, ttygid);
            (void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
 # ifdef HAVE_REVOKE
            (void) revoke(line);
diff --git a/sudo.c b/sudo.c
index 36f9a46a2bb8ccaf4555b1019d86954ecef2c884..4f04c4f1b6d6c63957d95a6726b9254a0a655e61 100644 (file)
--- a/sudo.c
+++ b/sudo.c
@@ -129,6 +129,7 @@ extern int sudo_edit                        __P((int, char **, char **));
 extern void rebuild_env                        __P((int, int));
 void validate_env_vars                 __P((struct list_member *));
 void insert_env_vars                   __P((struct list_member *));
+int run_command __P((const char *path, char *argv[], char *envp[], uid_t uid)); /* XXX should be in sudo.h */
 
 /*
  * Globals
@@ -417,9 +418,13 @@ main(argc, argv, envp)
     if (def_askpass && !user_askpass)
        user_askpass = def_askpass;
 
-    /* User may have overridden environment resetting via the -E flag. */
-    if (ISSET(sudo_mode, MODE_PRESERVE_ENV) && def_setenv)
-       def_env_reset = FALSE;
+    /*
+     * We don't reset the environment for sudoedit or if the user
+     * specified the -E command line flag and they have setenv privs.
+     */
+    if (ISSET(sudo_mode, MODE_EDIT) ||
+        (ISSET(sudo_mode, MODE_PRESERVE_ENV) && def_setenv))
+        def_env_reset = FALSE;
 
     /* Build a new environment that avoids any nasty bits. */
     rebuild_env(sudo_mode, def_noexec);
@@ -447,9 +452,6 @@ main(argc, argv, envp)
     }
 
     if (ISSET(validated, VALIDATE_OK)) {
-       struct command_status cstat;
-       int exitcode = 1;
-
        /* Finally tell the user if the command did not exist. */
        if (cmnd_status == NOT_FOUND_DOT) {
            audit_failure(NewArgv, "command in current directory");
@@ -490,8 +492,6 @@ main(argc, argv, envp)
        /* Must audit before uid change. */
        audit_success(NewArgv);
 
-       /* Become specified user or root if executing a command. */
-
        if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) {
            char *p;
 
@@ -507,15 +507,14 @@ main(argc, argv, envp)
 #endif
        }
 
-       if (ISSET(sudo_mode, MODE_EDIT))
-           exit(sudo_edit(NewArgc, NewArgv, envp));
-
-       /* Insert system-wide environment variables. */
-       if (def_env_file)
-           read_env_file(def_env_file, FALSE);
+       if (ISSET(sudo_mode, MODE_RUN)) {
+           /* Insert system-wide environment variables. */
+           if (def_env_file)
+               read_env_file(def_env_file, FALSE);
 
-       /* Insert user-specified environment variables. */
-       insert_env_vars(sudo_user.env_vars);
+           /* Insert user-specified environment variables. */
+           insert_env_vars(sudo_user.env_vars);
+       }
 
        /* Restore signal handlers before we exec. */
        (void) sigaction(SIGINT, &saved_sa_int, NULL);
@@ -523,34 +522,14 @@ main(argc, argv, envp)
        (void) sigaction(SIGTSTP, &saved_sa_tstp, NULL);
 
        /* Close the password and group files and free up memory. */
+       /* XXX - too early, pty code uses sudo_getgrnam */
        sudo_endpwent();
        sudo_endgrent();
 
-#ifdef PROFILING
-       exit(0);
-#endif
-       sudo_execve(safe_cmnd, NewArgv, environ, &cstat);
-       switch (cstat.type) {
-       case CMD_ERRNO:
-           /* exec_setup() or execve() returned an error. */
-           warningx("unable to execute %s: %s", safe_cmnd, strerror(cstat.val));
-           exitcode = 127;
-           break;
-       case CMD_WSTATUS:
-           /* Command ran, exited or was killed. */
-           if (WIFEXITED(cstat.val))
-               exitcode = WEXITSTATUS(cstat.val);
-           else if (WIFSIGNALED(cstat.val))
-               exitcode = WTERMSIG(cstat.val) | 128;
-           break;
-       default:
-           warningx("unexpected child termination condition: %d", cstat.type);
-           break;
-       }
-#ifdef _PATH_SUDO_IO_LOGDIR
-       io_log_close();
-#endif
-       exit(exitcode);
+       if (ISSET(sudo_mode, MODE_EDIT))
+           exit(sudo_edit(NewArgc, NewArgv, envp));
+       else
+           exit(run_command(safe_cmnd, NewArgv, environ, runas_pw->pw_uid));
     } else if (ISSET(validated, FLAG_NO_USER | FLAG_NO_HOST)) {
        audit_failure(NewArgv, "No user or host");
        log_denial(validated, 1);
@@ -846,6 +825,15 @@ exec_setup()
 {
     int rval = FALSE;
 
+    /*
+     * For sudoedit, the command runas a the user with no additional setup.
+     */
+    if (ISSET(sudo_mode, MODE_EDIT)) {
+       set_perms(PERM_FULL_USER);
+       rval = TRUE;
+       goto done;
+    }
+
     /*
      * Set umask based on sudoers.
      * If user's umask is more restrictive, OR in those bits too
@@ -897,6 +885,47 @@ done:
     return(rval);
 }
 
+/*
+ * Run the command and wait for it to complete.
+ */
+int
+run_command(const char *path, char *argv[], char *envp[], uid_t uid)
+{
+    struct command_status cstat;
+    int exitcode = 1;
+
+#ifdef PROFILING
+    exit(0);
+#endif
+
+    cstat.type = CMD_INVALID;
+    cstat.val = 0;
+
+    sudo_execve(path, argv, envp, uid, &cstat);
+
+    switch (cstat.type) {
+    case CMD_ERRNO:
+       /* exec_setup() or execve() returned an error. */
+       warningx("unable to execute %s: %s", path, strerror(cstat.val));
+       exitcode = 127;
+       break;
+    case CMD_WSTATUS:
+       /* Command ran, exited or was killed. */
+       if (WIFEXITED(cstat.val))
+           exitcode = WEXITSTATUS(cstat.val);
+       else if (WIFSIGNALED(cstat.val))
+           exitcode = WTERMSIG(cstat.val) | 128;
+       break;
+    default:
+       warningx("unexpected child termination condition: %d", cstat.type);
+       break;
+    }
+#ifdef _PATH_SUDO_IO_LOGDIR
+    io_log_close();
+#endif
+    return(exitcode);
+}
+
 /*
  * Command line argument parsing.
  * Sets NewArgc and NewArgv which corresponds to the argc/argv we'll use
diff --git a/sudo.h b/sudo.h
index 035a4f857b988dc6b4e97c75801a86ed1985d06e..3a615e72ed13419803cbb1c8da72d299d61b3323 100644 (file)
--- a/sudo.h
+++ b/sudo.h
@@ -296,7 +296,8 @@ int exec_setup __P((void));
 YY_DECL;
 
 /* exec.c */
-int sudo_execve __P((const char *, char *[], char *[], struct command_status *cstat));
+int sudo_execve __P((const char *path, char *argv[], char *envp[], uid_t uid,
+    struct command_status *cstat));
 
 /* iolog.c */
 int io_log_open __P((void));
@@ -309,7 +310,7 @@ void io_log_close __P((void));
 void io_nextid __P((void));
 
 /* pty.c */
-int get_pty __P((int *master, int *slave, char *name, size_t namesz));
+int get_pty __P((int *master, int *slave, char *name, size_t namesz, uid_t uid));
 
 /* Only provide extern declarations outside of sudo.c. */
 #ifndef _SUDO_MAIN
@@ -319,6 +320,8 @@ extern struct passwd *auth_pw, *list_pw;
 extern int tgetpass_flags;
 extern int long_list;
 extern uid_t timestamp_uid;
+/* XXX - conflicts with the one in visudo */
+int run_command __P((const char *path, char *argv[], char *envp[], uid_t uid));
 #endif
 #ifndef errno
 extern int errno;
index 44989d452125a2d10a8056cba5d25b5b63e8edbc..71efb2666b50953dffd58374ec7323bd28551de2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004-2008 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 2004-2008, 2010 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
@@ -16,6 +16,8 @@
 
 #include <config.h>
 
+#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
+
 #include <sys/types.h>
 #include <sys/param.h>
 #include <sys/stat.h>
@@ -42,6 +44,7 @@
 # include <unistd.h>
 #endif /* HAVE_UNISTD_H */
 #include <ctype.h>
+#include <grp.h>
 #include <pwd.h>
 #include <signal.h>
 #include <errno.h>
 
 #include "sudo.h"
 
-extern sigaction_t saved_sa_int, saved_sa_quit, saved_sa_tstp;
-extern char **environ;
+static char *find_editor __P((int *argc_out, char ***argv_out));
 
-static char *find_editor();
+extern char **NewArgv; /* XXX */
 
 /*
  * Wrapper to allow users to edit privileged files with their own uid.
@@ -63,15 +65,16 @@ static char *find_editor();
 int
 sudo_edit(argc, argv, envp)
     int argc;
-    char **argv;
-    char **envp;
+    char *argv[];
+    char *envp[];
 {
     ssize_t nread, nwritten;
-    pid_t kidpid, pid;
     const char *tmpdir;
-    char **nargv, **ap, *editor, *cp;
+    char *cp, **nargv, *editor, **files;
+    char **editor_argv = NULL;
     char buf[BUFSIZ];
-    int rc, i, ac, ofd, tfd, nargc, rval, tmplen, wasblank;
+    int rc, i, j, ac, ofd, tfd, nargc, rval, nfiles, tmplen;
+    int editor_argc = 0;
     struct stat sb;
     struct timeval tv, tv1, tv2;
     struct tempfile {
@@ -81,6 +84,11 @@ sudo_edit(argc, argv, envp)
        off_t osize;
     } *tf;
 
+    /* Determine user's editor. */
+    editor = find_editor(&editor_argc, &editor_argv);
+    if (editor == NULL)
+       return 1;
+
     /*
      * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp
      */
@@ -96,23 +104,27 @@ sudo_edit(argc, argv, envp)
     while (tmplen > 0 && tmpdir[tmplen - 1] == '/')
        tmplen--;
 
+#if 0 /* XXX - already done */
     /*
      * Close password, shadow, and group files before we try to open
      * user-specified files to prevent the opening of things like /dev/fd/4
      */
     sudo_endpwent();
     sudo_endgrent();
+#endif
 
     /*
      * For each file specified by the user, make a temporary version
      * and copy the contents of the original to it.
      */
-    tf = emalloc2(argc - 1, sizeof(*tf));
-    zero_bytes(tf, (argc - 1) * sizeof(*tf));
-    for (i = 0, ap = argv + 1; i < argc - 1 && *ap != NULL; i++, ap++) {
+    files = argv + 1;
+    nfiles = argc - 1;
+    tf = emalloc2(nfiles, sizeof(*tf));
+    zero_bytes(tf, nfiles * sizeof(*tf));
+    for (i = 0, j = 0; i < nfiles; i++) {
        rc = -1;
        set_perms(PERM_RUNAS);
-       if ((ofd = open(*ap, O_RDONLY, 0644)) != -1 || errno == ENOENT) {
+       if ((ofd = open(files[i], O_RDONLY, 0644)) != -1 || errno == ENOENT) {
            if (ofd == -1) {
                zero_bytes(&sb, sizeof(sb));            /* new file */
                rc = 0;
@@ -120,32 +132,30 @@ sudo_edit(argc, argv, envp)
 #ifdef HAVE_FSTAT
                rc = fstat(ofd, &sb);
 #else
-               rc = stat(tf[i].ofile, &sb);
+               rc = stat(tf[j].ofile, &sb);
 #endif
            }
        }
        set_perms(PERM_ROOT);
        if (rc || (ofd != -1 && !S_ISREG(sb.st_mode))) {
            if (rc)
-               warning("%s", *ap);
+               warning("%s", files[i]);
            else
-               warningx("%s: not a regular file", *ap);
+               warningx("%s: not a regular file", files[i]);
            if (ofd != -1)
                close(ofd);
-           argc--;
-           i--;
            continue;
        }
-       tf[i].ofile = *ap;
-       mtim_get(&sb, &tf[i].omtim);
-       tf[i].osize = sb.st_size;
-       if ((cp = strrchr(tf[i].ofile, '/')) != NULL)
+       tf[j].ofile = files[i];
+       tf[j].osize = sb.st_size;
+       mtim_get(&sb, &tf[j].omtim);
+       if ((cp = strrchr(tf[j].ofile, '/')) != NULL)
            cp++;
        else
-           cp = tf[i].ofile;
-       easprintf(&tf[i].tfile, "%.*s/%s.XXXXXXXX", tmplen, tmpdir, cp);
+           cp = tf[j].ofile;
+       easprintf(&tf[j].tfile, "%.*s/%s.XXXXXXXX", tmplen, tmpdir, cp);
        set_perms(PERM_USER);
-       tfd = mkstemp(tf[i].tfile);
+       tfd = mkstemp(tf[j].tfile);
        set_perms(PERM_ROOT);
        if (tfd == -1) {
            warning("mkstemp");
@@ -155,9 +165,9 @@ sudo_edit(argc, argv, envp)
            while ((nread = read(ofd, buf, sizeof(buf))) != 0) {
                if ((nwritten = write(tfd, buf, nread)) != nread) {
                    if (nwritten == -1)
-                       warning("%s", tf[i].tfile);
+                       warning("%s", tf[j].tfile);
                    else
-                       warningx("%s: short write", tf[i].tfile);
+                       warningx("%s: short write", tf[j].tfile);
                    goto cleanup;
                }
            }
@@ -170,95 +180,45 @@ sudo_edit(argc, argv, envp)
         * resides.  It is OK if touch() fails since we only use the info
         * to determine whether or not a file has been modified.
         */
-       (void) touch(tfd, NULL, &tf[i].omtim);
+       (void) touch(tfd, NULL, &tf[j].omtim);
 #ifdef HAVE_FSTAT
        rc = fstat(tfd, &sb);
 #else
-       rc = stat(tf[i].tfile, &sb);
+       rc = stat(tf[j].tfile, &sb);
 #endif
        if (!rc)
-           mtim_get(&sb, &tf[i].omtim);
+           mtim_get(&sb, &tf[j].omtim);
        close(tfd);
+       j++;
     }
-    if (argc == 1)
-       return(1);                      /* no files readable, you lose */
-
-    environ = envp;
-    editor = find_editor();
+    if ((nfiles = j) == 0)
+       return 1;                       /* no files readable, you lose */
 
     /*
      * Allocate space for the new argument vector and fill it in.
-     * The EDITOR and VISUAL environment variables may contain command
-     * line args so look for those and alloc space for them too.
+     * We concatenate the editor with its args and the file list
+     * to create a new argv.
+     * We allocate an extra slot to be used if execve() fails.
      */
-    nargc = argc;
-    for (wasblank = FALSE, cp = editor; *cp != '\0'; cp++) {
-       if (isblank((unsigned char) *cp))
-           wasblank = TRUE;
-       else if (wasblank) {
-           wasblank = FALSE;
-           nargc++;
-       }
-    }
-    nargv = (char **) emalloc2(nargc + 1, sizeof(char *));
-    ac = 0;
-    for ((cp = strtok(editor, " \t")); cp != NULL; (cp = strtok(NULL, " \t")))
-       nargv[ac++] = cp;
-    for (i = 0; i < argc - 1 && ac < nargc; )
+    nargc = editor_argc + nfiles;
+    nargv = (char **) emalloc2(1 + nargc + 1, sizeof(char *));
+    nargv++;
+    for (ac = 0; ac < editor_argc; ac++)
+       nargv[ac] = editor_argv[ac];
+    for (i = 0; i < nfiles && ac < nargc; )
        nargv[ac++] = tf[i++].tfile;
     nargv[ac] = NULL;
 
-    /* Allow the editor to be suspended. */
-    (void) sigaction(SIGTSTP, &saved_sa_tstp, NULL);
-
     /*
-     * Fork and exec the editor with the invoking user's creds,
+     * Run the editor with the invoking user's creds,
      * keeping track of the time spent in the editor.
      */
     gettime(&tv1);
-    kidpid = fork();
-    if (kidpid == -1) {
-       warning("fork");
-       goto cleanup;
-    } else if (kidpid == 0) {
-       /* child */
-       (void) sigaction(SIGINT, &saved_sa_int, NULL);
-       (void) sigaction(SIGQUIT, &saved_sa_quit, NULL);
-       set_perms(PERM_FULL_USER);
-       closefrom(def_closefrom);
-       execvp(nargv[0], nargv);
-       warning("unable to execute %s", nargv[0]);
-       _exit(127);
-    }
-
-    /*
-     * Wait for status from the child.  Most modern kernels
-     * will not let an unprivileged child process send a
-     * signal to its privileged parent so we have to request
-     * status when the child is stopped and then send the
-     * same signal to our own pid.
-     */
-    do {
-#ifdef sudo_waitpid
-        pid = sudo_waitpid(kidpid, &i, WUNTRACED);
-#else
-       pid = wait(&i);
-#endif
-       if (pid == kidpid) {
-           if (WIFSTOPPED(i))
-               kill(getpid(), WSTOPSIG(i));
-           else
-               break;
-       }
-    } while (pid != -1 || errno == EINTR);
+    rval = run_command(editor, nargv, envp, user_uid);
     gettime(&tv2);
-    if (pid == -1 || !WIFEXITED(i))
-       rval = 1;
-    else
-       rval = WEXITSTATUS(i);
 
     /* Copy contents of temp files to real ones */
-    for (i = 0; i < argc - 1; i++) {
+    for (i = 0; i < nfiles; i++) {
        rc = -1;
        set_perms(PERM_USER);
        if ((tfd = open(tf[i].tfile, O_RDONLY, 0644)) != -1) {
@@ -328,49 +288,110 @@ sudo_edit(argc, argv, envp)
        close(ofd);
     }
 
-    return(rval);
+    return rval;
 cleanup:
     /* Clean up temp files and return. */
-    for (i = 0; i < argc - 1; i++) {
+    for (i = 0; i < nfiles; i++) {
        if (tf[i].tfile != NULL)
            unlink(tf[i].tfile);
     }
-    return(1);
+    return 1;
+}
+
+static char *
+resolve_editor(char *editor, int *argc_out, char ***argv_out)
+{
+    char *cp, **nargv, *editor_path = NULL;
+    int ac, nargc, wasblank;
+
+    editor = estrdup(editor); /* becomes part of argv_out */
+
+    /*
+     * Split editor into an argument vector; editor is reused (do not free).
+     * The EDITOR and VISUAL environment variables may contain command
+     * line args so look for those and alloc space for them too.
+     */
+    nargc = 1;
+    for (wasblank = FALSE, cp = editor; *cp != '\0'; cp++) {
+       if (isblank((unsigned char) *cp))
+           wasblank = TRUE;
+       else if (wasblank) {
+           wasblank = FALSE;
+           nargc++;
+       }
+    }
+    /* If we can't find the editor in the user's PATH, give up. */
+    cp = strtok(editor, " \t");
+    if (cp == NULL ||
+       find_path(cp, &editor_path, NULL, getenv("PATH"), 0) != FOUND) {
+       efree(editor);
+       return NULL;
+    }
+    nargv = (char **) emalloc2(nargc + 1, sizeof(char *));
+    for (ac = 0; cp != NULL && ac < nargc; ac++) {
+       nargv[ac] = cp;
+       cp = strtok(NULL, " \t");
+    }
+    nargv[ac] = NULL;
+
+    *argc_out = nargc;
+    *argv_out = nargv;
+    return editor_path;
 }
 
 /*
- * Determine which editor to use.  We don't bother restricting this
- * based on def_env_editor or def_editor since the editor runs with
- * the uid of the invoking user, not the runas (privileged) user.
+ * 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.
+ * Fills in argv_out with an argument vector suitable for execve() that
+ * includes the editor with the specified files.
  */
 static char *
-find_editor()
+find_editor(int *argc_out, char ***argv_out)
 {
-    char *cp, *editor = NULL, **ev, *ev0[4];
+    char *cp, *editor, *editor_path = NULL, **ev, *ev0[4];
 
+    /*
+     * If any of SUDO_EDITOR, VISUAL or EDITOR are set, choose the first one.
+     */
     ev0[0] = "SUDO_EDITOR";
     ev0[1] = "VISUAL";
     ev0[2] = "EDITOR";
     ev0[3] = NULL;
     for (ev = ev0; *ev != NULL; ev++) {
        if ((editor = getenv(*ev)) != NULL && *editor != '\0') {
-           if ((cp = strrchr(editor, '/')) != NULL)
-               cp++;
-           else
-               cp = editor;
-           /* Ignore "sudoedit" and "sudo" to avoid an endless loop. */
-           if (strncmp(cp, "sudo", 4) != 0 ||
-               (cp[4] != ' ' && cp[4] != '\0' && strcmp(cp + 4, "edit") != 0)) {
-               editor = estrdup(editor);
+           editor_path = resolve_editor(editor, argc_out, argv_out);
+           if (editor_path != NULL)
                break;
-           }
        }
-       editor = NULL;
     }
-    if (editor == NULL) {
+    if (editor_path == NULL) {
+       /* def_editor could be a path, split it up */
        editor = estrdup(def_editor);
-       if ((cp = strchr(editor, ':')) != NULL)
-           *cp = '\0';                 /* def_editor could be a path */
+       cp = strtok(editor, ":");
+       while (cp != NULL && editor_path == NULL) {
+           editor_path = resolve_editor(cp, argc_out, argv_out);
+           cp = strtok(NULL, ":");
+       }
+       if (editor_path)
+           efree(editor);
     }
-    return(editor);
+    if (!editor_path) {
+       audit_failure(NewArgv, "%s: command not found", editor);
+       warningx("%s: command not found", editor);
+    }
+    return editor_path;
 }
+
+#else /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */
+
+/*
+ * Must have the ability to change the effective uid to use sudoedit.
+ */
+int
+sudo_edit(int argc, char *argv[], char *envp[])
+{
+    return 1;
+}
+
+#endif /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */