diff options
Diffstat (limited to '')
-rw-r--r-- | src/sudo_edit.c | 796 |
1 files changed, 796 insertions, 0 deletions
diff --git a/src/sudo_edit.c b/src/sudo_edit.c new file mode 100644 index 0000000..b7f7ca5 --- /dev/null +++ b/src/sudo_edit.c @@ -0,0 +1,796 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2004-2008, 2010-2023 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <signal.h> +#include <fcntl.h> + +#include <sudo.h> +#include <sudo_edit.h> +#include <sudo_exec.h> + +#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID) + +/* + * Editor temporary file name along with original name, mtime and size. + */ +struct tempfile { + char *tfile; + char *ofile; + off_t osize; + struct timespec omtim; +}; + +static char edit_tmpdir[MAX(sizeof(_PATH_VARTMP), sizeof(_PATH_TMP))]; + +/* + * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp + * Returns true on success, else false; + */ +static bool +set_tmpdir(const struct sudo_cred *user_cred) +{ + const char *tdir = NULL; + const char *tmpdirs[] = { + _PATH_VARTMP, +#ifdef _PATH_USRTMP + _PATH_USRTMP, +#endif + _PATH_TMP + }; + struct sudo_cred saved_cred; + size_t i; + size_t len; + int dfd; + debug_decl(set_tmpdir, SUDO_DEBUG_EDIT); + + /* Stash old credentials. */ + saved_cred.uid = getuid(); + saved_cred.euid = geteuid(); + saved_cred.gid = getgid(); + saved_cred.egid = getegid(); + saved_cred.ngroups = getgroups(0, NULL); // -V575 + if (saved_cred.ngroups > 0) { + saved_cred.groups = + reallocarray(NULL, (size_t)saved_cred.ngroups, sizeof(GETGROUPS_T)); + if (saved_cred.groups == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_bool(false); + } + saved_cred.ngroups = getgroups(saved_cred.ngroups, saved_cred.groups); + if (saved_cred.ngroups < 0) { + sudo_warn("%s", U_("unable to get group list")); + free(saved_cred.groups); + debug_return_bool(false); + } + } else { + saved_cred.ngroups = 0; + saved_cred.groups = NULL; + } + + for (i = 0; tdir == NULL && i < nitems(tmpdirs); i++) { + if ((dfd = open(tmpdirs[i], O_RDONLY)) != -1) { + if (dir_is_writable(dfd, user_cred, &saved_cred) == true) + tdir = tmpdirs[i]; + close(dfd); + } + } + free(saved_cred.groups); + + if (tdir == NULL) { + sudo_warnx("%s", U_("no writable temporary directory found")); + debug_return_bool(false); + } + + len = strlcpy(edit_tmpdir, tdir, sizeof(edit_tmpdir)); + if (len >= sizeof(edit_tmpdir)) { + errno = ENAMETOOLONG; + sudo_warn("%s", tdir); + debug_return_bool(false); + } + while (len > 0 && edit_tmpdir[--len] == '/') + edit_tmpdir[len] = '\0'; + debug_return_bool(true); +} + +/* + * Construct a temporary file name for file and return an + * open file descriptor. The temporary file name is stored + * in tfile which the caller is responsible for freeing. + */ +static int +sudo_edit_mktemp(const char *ofile, char **tfile) +{ + const char *base, *suff; + int len, tfd; + debug_decl(sudo_edit_mktemp, SUDO_DEBUG_EDIT); + + base = sudo_basename(ofile); + suff = strrchr(base, '.'); + if (suff != NULL) { + len = asprintf(tfile, "%s/%.*sXXXXXXXX%s", edit_tmpdir, + (int)(size_t)(suff - base), base, suff); + } else { + len = asprintf(tfile, "%s/%s.XXXXXXXX", edit_tmpdir, base); + } + if (len == -1) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(-1); + } + tfd = mkstemps(*tfile, suff ? (int)strlen(suff) : 0); + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "%s -> %s, fd %d", ofile, *tfile, tfd); + debug_return_int(tfd); +} + +/* + * Create temporary copies of files[] and store the temporary path name + * along with the original name, size and mtime in tf. + * Returns the number of files copied (which may be less than nfiles) + * or -1 if a fatal error occurred. + */ +static int +sudo_edit_create_tfiles(const struct command_details *command_details, + const struct sudo_cred *user_cred, struct tempfile *tf, char *files[], + int nfiles) +{ + int i, j, tfd, ofd, rc; + struct timespec times[2]; + struct stat sb; + debug_decl(sudo_edit_create_tfiles, SUDO_DEBUG_EDIT); + + /* + * For each file specified by the user, make a temporary version + * and copy the contents of the original to it. + */ + for (i = 0, j = 0; i < nfiles; i++) { + rc = -1; + switch_user(command_details->cred.euid, command_details->cred.egid, + command_details->cred.ngroups, command_details->cred.groups); + ofd = sudo_edit_open(files[i], O_RDONLY, + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details->flags, + user_cred, &command_details->cred); + if (ofd != -1 || errno == ENOENT) { + if (ofd != -1) { + rc = fstat(ofd, &sb); + } else { + /* New file, verify parent dir exists and is not writable. */ + memset(&sb, 0, sizeof(sb)); + if (sudo_edit_parent_valid(files[i], command_details->flags, user_cred, &command_details->cred)) + rc = 0; + } + } + switch_user(ROOT_UID, user_cred->egid, user_cred->ngroups, user_cred->groups); + if (ofd != -1 && !S_ISREG(sb.st_mode)) { + sudo_warnx(U_("%s: not a regular file"), files[i]); + close(ofd); + continue; + } + if (rc == -1) { + /* open() or fstat() error. */ + if (ofd == -1 && errno == ELOOP) { + sudo_warnx(U_("%s: editing symbolic links is not permitted"), + files[i]); + } else if (ofd == -1 && errno == EISDIR) { + sudo_warnx(U_("%s: editing files in a writable directory is not permitted"), + files[i]); + } else { + sudo_warn("%s", files[i]); + } + if (ofd != -1) + close(ofd); + continue; + } + tf[j].ofile = files[i]; + tf[j].osize = sb.st_size; // -V614 + mtim_get(&sb, tf[j].omtim); + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "seteuid(%u)", (unsigned int)user_cred->uid); + if (seteuid(user_cred->uid) != 0) + sudo_fatal("seteuid(%u)", (unsigned int)user_cred->uid); + tfd = sudo_edit_mktemp(tf[j].ofile, &tf[j].tfile); + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "seteuid(%u)", ROOT_UID); + if (seteuid(ROOT_UID) != 0) + sudo_fatal("seteuid(ROOT_UID)"); + if (tfd == -1) { + sudo_warn("mkstemps"); + if (ofd != -1) + close(ofd); + debug_return_int(-1); + } + if (ofd != -1) { + if (sudo_copy_file(tf[j].ofile, ofd, tf[j].osize, tf[j].tfile, tfd, -1) == -1) { + close(ofd); + close(tfd); + debug_return_int(-1); + } + close(ofd); + } + /* + * We always update the stashed mtime because the time + * resolution of the filesystem the temporary file is on may + * not match that of the filesystem where the file to be edited + * resides. It is OK if futimens() fails since we only use the + * info to determine whether or not a file has been modified. + */ + times[0].tv_sec = times[1].tv_sec = tf[j].omtim.tv_sec; + times[0].tv_nsec = times[1].tv_nsec = tf[j].omtim.tv_nsec; + if (futimens(tfd, times) == -1) { + if (utimensat(AT_FDCWD, tf[j].tfile, times, 0) == -1) + sudo_warn("%s", tf[j].tfile); + } + rc = fstat(tfd, &sb); + if (!rc) + mtim_get(&sb, tf[j].omtim); + close(tfd); + j++; + } + debug_return_int(j); +} + +/* + * Copy the temporary files specified in tf to the originals. + * Returns the number of copy errors or 0 if completely successful. + */ +static int +sudo_edit_copy_tfiles(const struct command_details *command_details, + const struct sudo_cred *user_cred, struct tempfile *tf, + int nfiles, struct timespec *times) +{ + int i, tfd, ofd, errors = 0; + struct timespec ts; + struct stat sb; + mode_t oldmask; + debug_decl(sudo_edit_copy_tfiles, SUDO_DEBUG_EDIT); + + /* Copy contents of temp files to real ones. */ + for (i = 0; i < nfiles; i++) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "seteuid(%u)", (unsigned int)user_cred->uid); + if (seteuid(user_cred->uid) != 0) + sudo_fatal("seteuid(%u)", (unsigned int)user_cred->uid); + tfd = sudo_edit_open(tf[i].tfile, O_RDONLY, + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, 0, user_cred, NULL); + if (seteuid(ROOT_UID) != 0) + sudo_fatal("seteuid(ROOT_UID)"); + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "seteuid(%u)", ROOT_UID); + if (tfd == -1 || !sudo_check_temp_file(tfd, tf[i].tfile, user_cred->uid, &sb)) { + sudo_warnx(U_("%s left unmodified"), tf[i].ofile); + if (tfd != -1) + close(tfd); + errors++; + continue; + } + mtim_get(&sb, ts); + if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) { + /* + * If mtime and size match but the user spent no measurable + * time in the editor we can't tell if the file was changed. + */ + if (sudo_timespeccmp(×[0], ×[1], !=)) { + sudo_warnx(U_("%s unchanged"), tf[i].ofile); + unlink(tf[i].tfile); + close(tfd); + continue; + } + } + switch_user(command_details->cred.euid, command_details->cred.egid, + command_details->cred.ngroups, command_details->cred.groups); + oldmask = umask(command_details->umask); + ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_CREAT, + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details->flags, + user_cred, &command_details->cred); + umask(oldmask); + switch_user(ROOT_UID, user_cred->egid, user_cred->ngroups, user_cred->groups); + if (ofd == -1) { + sudo_warn(U_("unable to write to %s"), tf[i].ofile); + goto bad; + } + + /* Overwrite the old file with the new contents. */ + if (sudo_copy_file(tf[i].tfile, tfd, sb.st_size, tf[i].ofile, ofd, + tf[i].osize) == 0) { + /* success, remove temporary file. */ + unlink(tf[i].tfile); + } else { +bad: + sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile); + errors++; + } + + if (ofd != -1) + close(ofd); + close(tfd); + } + debug_return_int(errors); +} + +#ifdef HAVE_SELINUX +static int +selinux_run_helper(uid_t uid, gid_t gid, int ngroups, GETGROUPS_T *groups, + char *const argv[], char *const envp[]) +{ + int status, ret = SESH_ERR_FAILURE; + const char *sesh; + pid_t child, pid; + debug_decl(selinux_run_helper, SUDO_DEBUG_EDIT); + + sesh = sudo_conf_sesh_path(); + if (sesh == NULL) { + sudo_warnx("internal error: sesh path not set"); + debug_return_int(-1); + } + + child = sudo_debug_fork(); + switch (child) { + case -1: + sudo_warn("%s", U_("unable to fork")); + break; + case 0: + /* child runs sesh in new context */ + if (selinux_setexeccon() == 0) { + switch_user(uid, gid, ngroups, groups); + execve(sesh, argv, envp); + } + _exit(SESH_ERR_FAILURE); + default: + /* parent waits */ + do { + pid = waitpid(child, &status, 0); + } while (pid == -1 && errno == EINTR); + + ret = WIFSIGNALED(status) ? SESH_ERR_KILLED : WEXITSTATUS(status); + } + + debug_return_int(ret); +} + +static char * +selinux_fmt_sudo_user(const struct sudo_cred *user_cred) +{ + char *cp, *user_str; + size_t user_size; + int i, len; + debug_decl(selinux_fmt_sudo_user, SUDO_DEBUG_EDIT); + + user_size = (STRLEN_MAX_UNSIGNED(uid_t) + 1) * (2 + user_cred->ngroups); + if ((user_str = malloc(user_size)) == NULL) + debug_return_ptr(NULL); + + /* UID:GID: */ + len = snprintf(user_str, user_size, "%u:%u:", + (unsigned int)user_cred->uid, (unsigned int)user_cred->gid); + if (len < 0 || (size_t)len >= user_size) + sudo_fatalx(U_("internal error, %s overflow"), __func__); + + /* Supplementary GIDs */ + cp = user_str + len; + for (i = 0; i < user_cred->ngroups; i++) { + len = snprintf(cp, user_size - (cp - user_str), "%s%u", + i ? "," : "", (unsigned int)user_cred->groups[i]); + if (len < 0 || (size_t)len >= user_size - (cp - user_str)) + sudo_fatalx(U_("internal error, %s overflow"), __func__); + cp += len; + } + + debug_return_ptr(user_str); +} + +static int +selinux_edit_create_tfiles(const struct command_details *command_details, + const struct sudo_cred *user_cred, struct tempfile *tf, + char *files[], int nfiles) +{ + const char **sesh_args, **sesh_ap; + char *user_str = NULL; + int i, error, sesh_nargs, ret = -1; + struct stat sb; + debug_decl(selinux_edit_create_tfiles, SUDO_DEBUG_EDIT); + + if (nfiles < 1) + debug_return_int(0); + + sesh_nargs = 6 + (nfiles * 2) + 1; + sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *)); + if (sesh_args == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto done; + } + *sesh_ap++ = "sesh"; + *sesh_ap++ = "--edit-create"; + if (!ISSET(command_details->flags, CD_SUDOEDIT_FOLLOW)) + *sesh_ap++ = "--no-dereference"; + if (ISSET(command_details->flags, CD_SUDOEDIT_CHECKDIR)) { + if ((user_str = selinux_fmt_sudo_user(user_cred)) == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto done; + } + *sesh_ap++ = "--edit-checkdir"; + *sesh_ap++ = user_str; + } + *sesh_ap++ = "--"; + + for (i = 0; i < nfiles; i++) { + char *tfile, *ofile = files[i]; + int tfd; + *sesh_ap++ = ofile; + tf[i].ofile = ofile; + if (stat(ofile, &sb) == -1) + memset(&sb, 0, sizeof(sb)); /* new file */ + tf[i].osize = sb.st_size; + mtim_get(&sb, tf[i].omtim); + /* + * The temp file must be created by the sesh helper, + * which uses O_EXCL | O_NOFOLLOW to make this safe. + */ + tfd = sudo_edit_mktemp(ofile, &tfile); + if (tfd == -1) { + sudo_warn("mkstemps"); + free(tfile); + goto done; + } + /* Helper will re-create temp file with proper security context. */ + close(tfd); + unlink(tfile); + *sesh_ap++ = tfile; + tf[i].tfile = tfile; + } + *sesh_ap = NULL; + + /* Run sesh -c [-h] [-w userstr] <o1> <t1> ... <on> <tn> */ + error = selinux_run_helper(command_details->cred.uid, + command_details->cred.gid, command_details->cred.ngroups, + command_details->cred.groups, (char **)sesh_args, command_details->envp); + switch (error) { + case SESH_SUCCESS: + break; + case SESH_ERR_BAD_PATHS: + sudo_fatalx("%s", U_("sesh: internal error: odd number of paths")); + case SESH_ERR_NO_FILES: + sudo_fatalx("%s", U_("sesh: unable to create temporary files")); + case SESH_ERR_KILLED: + sudo_fatalx("%s", U_("sesh: killed by a signal")); + default: + sudo_warnx(U_("sesh: unknown error %d"), error); + goto done; + } + + for (i = 0; i < nfiles; i++) { + int tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW); + if (tfd == -1) { + sudo_warn(U_("unable to open %s"), tf[i].tfile); + goto done; + } + if (!sudo_check_temp_file(tfd, tf[i].tfile, command_details->cred.uid, NULL)) { + close(tfd); + goto done; + } + if (fchown(tfd, user_cred->uid, user_cred->gid) != 0) { + sudo_warn("unable to chown(%s) to %d:%d for editing", + tf[i].tfile, user_cred->uid, user_cred->gid); + close(tfd); + goto done; + } + close(tfd); + } + ret = nfiles; + +done: + /* Contents of tf will be freed by caller. */ + free(sesh_args); + free(user_str); + + debug_return_int(ret); +} + +static int +selinux_edit_copy_tfiles(const struct command_details *command_details, + const struct sudo_cred *user_cred, struct tempfile *tf, + int nfiles, struct timespec *times) +{ + const char **sesh_args, **sesh_ap; + char *user_str = NULL; + bool run_helper = false; + int i, error, sesh_nargs, ret = 1; + int tfd = -1; + struct timespec ts; + struct stat sb; + debug_decl(selinux_edit_copy_tfiles, SUDO_DEBUG_EDIT); + + if (nfiles < 1) + debug_return_int(0); + + sesh_nargs = 5 + (nfiles * 2) + 1; + sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *)); + if (sesh_args == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto done; + } + *sesh_ap++ = "sesh"; + *sesh_ap++ = "--edit-install"; + if (ISSET(command_details->flags, CD_SUDOEDIT_CHECKDIR)) { + if ((user_str = selinux_fmt_sudo_user(user_cred)) == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto done; + } + *sesh_ap++ = "--edit-checkdir"; + *sesh_ap++ = user_str; + } + *sesh_ap++ = "--"; + + for (i = 0; i < nfiles; i++) { + if (tfd != -1) + close(tfd); + if ((tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW)) == -1) { + sudo_warn(U_("unable to open %s"), tf[i].tfile); + continue; + } + if (!sudo_check_temp_file(tfd, tf[i].tfile, user_cred->uid, &sb)) + continue; + mtim_get(&sb, ts); + if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) { + /* + * If mtime and size match but the user spent no measurable + * time in the editor we can't tell if the file was changed. + */ + if (sudo_timespeccmp(×[0], ×[1], !=)) { + sudo_warnx(U_("%s unchanged"), tf[i].ofile); + unlink(tf[i].tfile); + continue; + } + } + run_helper = true; + *sesh_ap++ = tf[i].tfile; + *sesh_ap++ = tf[i].ofile; + if (fchown(tfd, command_details->cred.uid, command_details->cred.gid) != 0) { + sudo_warn("unable to chown(%s) back to %d:%d", tf[i].tfile, + command_details->cred.uid, command_details->cred.gid); + } + } + *sesh_ap = NULL; + + if (!run_helper) + goto done; + + /* Run sesh -i <t1> <o1> ... <tn> <on> */ + error = selinux_run_helper(command_details->cred.uid, + command_details->cred.gid, command_details->cred.ngroups, + command_details->cred.groups, (char **)sesh_args, command_details->envp); + switch (error) { + case SESH_SUCCESS: + ret = 0; + break; + case SESH_ERR_NO_FILES: + sudo_warnx("%s", + U_("unable to copy temporary files back to their original location")); + break; + case SESH_ERR_SOME_FILES: + sudo_warnx("%s", + U_("unable to copy some of the temporary files back to their original location")); + break; + case SESH_ERR_KILLED: + sudo_warnx("%s", U_("sesh: killed by a signal")); + break; + default: + sudo_warnx(U_("sesh: unknown error %d"), error); + break; + } + +done: + if (tfd != -1) + close(tfd); + /* Contents of tf will be freed by caller. */ + free(sesh_args); + free(user_str); + + debug_return_int(ret); +} +#endif /* HAVE_SELINUX */ + +/* + * Wrapper to allow users to edit privileged files with their own uid. + * Returns the wait status of the command on success and a wait status + * of 1 on failure. + */ +int +sudo_edit(struct command_details *command_details, + const struct user_details *user_details) +{ + struct command_details saved_command_details; + const struct sudo_cred *user_cred = &user_details->cred; + char **nargv = NULL, **files = NULL; + int nfiles = command_details->nfiles; + int errors, i, ac, nargc, ret; + int editor_argc = 0; + struct timespec times[2]; + struct tempfile *tf = NULL; + debug_decl(sudo_edit, SUDO_DEBUG_EDIT); + + /* + * Set real, effective and saved uids to root. + * We will change the euid as needed below. + */ + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "setuid(%u)", ROOT_UID); + if (setuid(ROOT_UID) != 0) { + sudo_warn(U_("unable to change uid to root (%u)"), ROOT_UID); + goto cleanup; + } + + /* Find a temporary directory writable by the user. */ + if (!set_tmpdir(user_cred)) + goto cleanup; + + if (nfiles > 0) { + /* + * The plugin specified the number of files to edit, use it. + */ + editor_argc = command_details->argc - nfiles; + if (editor_argc < 2 || strcmp(command_details->argv[editor_argc - 1], "--") != 0) { + sudo_warnx("%s", U_("plugin error: invalid file list for sudoedit")); + goto cleanup; + } + + /* We don't include the "--" when running the user's editor. */ + files = &command_details->argv[editor_argc--]; + } else { + /* + * Compute the number of files to edit by looking for the "--" + * option which separate the editor from the files. + */ + for (i = 0; command_details->argv[i] != NULL; i++) { + if (strcmp(command_details->argv[i], "--") == 0) { + editor_argc = i; + files = &command_details->argv[i + 1]; + nfiles = command_details->argc - (i + 1); + break; + } + } + } + if (nfiles == 0) { + sudo_warnx("%s", U_("plugin error: missing file list for sudoedit")); + goto cleanup; + } + + /* Copy editor files to temporaries. */ + tf = calloc((size_t)nfiles, sizeof(*tf)); + if (tf == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto cleanup; + } +#ifdef HAVE_SELINUX + if (ISSET(command_details->flags, CD_RBAC_ENABLED)) + nfiles = selinux_edit_create_tfiles(command_details, user_cred, tf, files, nfiles); + else +#endif + nfiles = sudo_edit_create_tfiles(command_details, user_cred, tf, files, nfiles); + if (nfiles <= 0) + goto cleanup; + + /* + * Allocate space for the new argument vector and fill it in. + * We concatenate the editor with its args and the file list + * to create a new argv. + */ + nargc = editor_argc + nfiles; + nargv = reallocarray(NULL, (size_t)nargc + 1, sizeof(char *)); + if (nargv == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto cleanup; + } + for (ac = 0; ac < editor_argc; ac++) + nargv[ac] = command_details->argv[ac]; + for (i = 0; i < nfiles && ac < nargc; ) + nargv[ac++] = tf[i++].tfile; + nargv[ac] = NULL; + + /* + * Run the editor with the invoking user's creds and drop setuid. + * Keep track of the time spent in the editor to distinguish between + * a user editing a file and a program doing it. + * XXX - should run editor with user's context + */ + if (sudo_gettime_real(×[0]) == -1) { + sudo_warn("%s", U_("unable to read the clock")); + goto cleanup; + } +#ifdef HAVE_SELINUX + if (ISSET(command_details->flags, CD_RBAC_ENABLED)) + selinux_audit_role_change(); +#endif + memcpy(&saved_command_details, command_details, sizeof(struct command_details)); + command_details->cred = *user_cred; + command_details->cred.euid = user_cred->uid; + command_details->cred.egid = user_cred->gid; + command_details->argc = nargc; + command_details->argv = nargv; + ret = run_command(command_details, user_details); + if (sudo_gettime_real(×[1]) == -1) { + sudo_warn("%s", U_("unable to read the clock")); + goto cleanup; + } + + /* Restore saved command_details. */ + command_details->cred = saved_command_details.cred; + command_details->argc = saved_command_details.argc; + command_details->argv = saved_command_details.argv; + + /* Copy contents of temp files to real ones. */ +#ifdef HAVE_SELINUX + if (ISSET(command_details->flags, CD_RBAC_ENABLED)) + errors = selinux_edit_copy_tfiles(command_details, user_cred, tf, nfiles, times); + else +#endif + errors = sudo_edit_copy_tfiles(command_details, user_cred, tf, nfiles, times); + if (errors) { + /* Preserve the edited temporary files. */ + ret = W_EXITCODE(1, 0); + } + + for (i = 0; i < nfiles; i++) + free(tf[i].tfile); + free(tf); + free(nargv); + debug_return_int(ret); + +cleanup: + /* Clean up temp files and return. */ + if (tf != NULL) { + for (i = 0; i < nfiles; i++) { + if (tf[i].tfile != NULL) + unlink(tf[i].tfile); + free(tf[i].tfile); + } + } + free(tf); + free(nargv); + debug_return_int(W_EXITCODE(1, 0)); +} + +#else /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */ + +/* + * Must have the ability to change the effective uid to use sudoedit. + */ +int +sudo_edit(const struct command_details *command_details, const struct sudo_cred *user_cred) +{ + debug_decl(sudo_edit, SUDO_DEBUG_EDIT); + debug_return_int(W_EXITCODE(1, 0)); +} + +#endif /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */ |