From: Todd C. Miller Date: Thu, 13 May 2010 18:09:21 +0000 (-0400) Subject: Work in progress support for sudoedit. The actual interface used by the X-Git-Tag: SUDO_1_8_0~636 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=fdd28d411fb9ccc161936a85b910047a80242d65;p=sudo Work in progress support for sudoedit. The actual interface used by the plugin for sudoedit is likely to change. --- diff --git a/include/sudo_plugin.h b/include/sudo_plugin.h index 26289f901..0ad4e619f 100644 --- a/include/sudo_plugin.h +++ b/include/sudo_plugin.h @@ -68,6 +68,9 @@ struct policy_plugin { const char *list_user); int (*validate)(void); void (*invalidate)(int remove); + int (*check_sudoedit)(int argc, char * const argv[], + char *env_add[], char **command_info[], + char **argv_out[], char **user_env_out[]); }; /* I/O plugin type and defines */ diff --git a/plugins/sample/sample_plugin.c b/plugins/sample/sample_plugin.c index 04a724714..3c0d6079c 100644 --- a/plugins/sample/sample_plugin.c +++ b/plugins/sample/sample_plugin.c @@ -346,7 +346,8 @@ struct policy_plugin sample_policy = { policy_check, policy_list, NULL, /* validate */ - NULL /* invalidate */ + NULL, /* invalidate */ + NULL /* sudoedit */ }; /* diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 55d695c86..ada91c3b5 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -115,7 +115,7 @@ REPLAY_OBJS = getdate.o sudoreplay.o term.o error.o TEST_OBJS = interfaces.o testsudoers.o tsgetgrpw.o error.o -LINKS = alloc.c atobool.c error.c fileops.c fmt_string.c lbuf.c \ +LINKS = alloc.c atobool.c error.c gettime.c fileops.c fmt_string.c lbuf.c \ list.c term.c zero_bytes.c VERSION = @PACKAGE_VERSION@ @@ -192,7 +192,6 @@ defaults.lo: $(srcdir)/defaults.c $(SUDODEP) $(srcdir)/def_data.c $(authdir)/sud env.lo: $(srcdir)/env.c $(SUDODEP) find_path.lo: $(srcdir)/find_path.c $(SUDODEP) getspwuid.lo: $(srcdir)/getspwuid.c $(SUDODEP) -gettime.lo: $(srcdir)/gettime.c $(SUDODEP) goodpath.lo: $(srcdir)/goodpath.c $(SUDODEP) gram.lo: $(devdir)/gram.c $(SUDODEP) $(srcdir)/parse.h $(incdir)/list.h $(devdir)/gram.h interfaces.lo: $(srcdir)/interfaces.c $(SUDODEP) $(srcdir)/interfaces.h @@ -226,6 +225,9 @@ fileops.lo: fileops.c $(SUDODEP) fmt_string.c: $(top_srcdir)/src/fmt_string.c @$(LN_S) -f $(top_srcdir)/src/$@ $@ fmt_string.lo: fmt_string.c $(incdir)/compat.h $(top_builddir)/config.h +gettime.c: $(top_srcdir)/src/gettime.c + @$(LN_S) -f $(top_srcdir)/src/$@ $@ +gettime.lo: gettime.c $(incdir)/compat.h $(top_builddir)/config.h list.c: $(top_srcdir)/src/list.c @$(LN_S) -f $(top_srcdir)/src/$@ $@ list.lo: list.c $(SUDODEP) diff --git a/plugins/sudoers/set_perms.c b/plugins/sudoers/set_perms.c index 16233df81..a9e12344a 100644 --- a/plugins/sudoers/set_perms.c +++ b/plugins/sudoers/set_perms.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994-1996,1998-2009 Todd C. Miller + * Copyright (c) 1994-1996,1998-2010 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index 3d1ddf360..36106c235 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -97,6 +97,7 @@ #ifdef HAVE_MBR_CHECK_MEMBERSHIP # include #endif +#include #include #include "sudo_plugin.h" @@ -121,6 +122,7 @@ static void set_runaspw(char *); static int sudoers_policy_version(int verbose); static struct passwd *get_authpw(void); static int deserialize_info(char * const settings[], char * const user_info[]); +static char *find_editor(char ***argv_out); /* XXX */ extern int runas_ngroups; @@ -281,6 +283,7 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], char **command_infop[], char **argv_out[], char **user_env_out[]) { static char *command_info[32]; /* XXX */ + char **edit_argv = NULL; struct sudo_nss *nss; int cmnd_status = -1, validated; int info_len = 0; @@ -308,8 +311,7 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], /* * Make a local copy of argc/argv, with special handling - * for the '-e', '-i' or '-s' options. - * XXX - handle sudoedit + * for the '-i' option. */ NewArgv = emalloc2(argc + 1, sizeof(char *)); memcpy(NewArgv, argv, argc * sizeof(char *)); @@ -408,8 +410,12 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], (void) close(fd); } - /* User may have overridden environment resetting via the -E flag. */ - if (ISSET(sudo_mode, MODE_PRESERVE_ENV) && def_setenv) + /* + * 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. */ @@ -554,7 +560,14 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], sudo_endpwent(); sudo_endgrent(); - command_info[info_len++] = fmt_string("command", safe_cmnd); + if (ISSET(sudo_mode, MODE_EDIT)) { + char *editor = find_editor(&edit_argv); + if (!editor) + goto done; + command_info[info_len++] = fmt_string("command", editor); + } else { + command_info[info_len++] = fmt_string("command", safe_cmnd); + } if (def_stay_setuid) { easprintf(&command_info[info_len++], "runas_uid=%u", user_uid); easprintf(&command_info[info_len++], "runas_gid=%u", user_gid); @@ -589,7 +602,7 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], *command_infop = command_info; - *argv_out = NewArgv; + *argv_out = edit_argv ? edit_argv : NewArgv; *user_env_out = env_get(); /* our private copy */ rval = TRUE; @@ -610,6 +623,16 @@ sudoers_policy_check(int argc, char * const argv[], char *env_add[], argv_out, user_env_out); } +static int +sudoers_policy_sudoedit(int argc, char * const argv[], char *env_add[], + char **command_infop[], char **argv_out[], char **user_env_out[]) +{ + SET(sudo_mode, MODE_EDIT); + + return sudoers_policy_main(argc, argv, 0, env_add, command_infop, + argv_out, user_env_out); +} + static int sudoers_policy_validate(void) { @@ -658,7 +681,7 @@ init_vars(char * const envp[]) { char * const * ep; -#if 0 +#if 0 /* XXX */ /* Sanity check command from user. */ if (user_cmnd == NULL && strlen(NewArgv[0]) >= PATH_MAX) errorx(1, "%s: File name too long", NewArgv[0]); @@ -1300,6 +1323,80 @@ deserialize_info(char * const settings[], char * const user_info[]) return flags; } +static char * +resolve_editor(char *editor, char ***argv_out) +{ + char *cp, **nargv, *editor_path = NULL; + int ac, nargc, wasblank; + + /* + * 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) { + 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; + + *argv_out = nargv; + return 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. + */ +static char * +find_editor(char ***argv_out) +{ + 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') { + editor_path = resolve_editor(editor, argv_out); + if (editor_path != NULL) + break; + } + } + if (editor_path == NULL) { + editor = estrdup(def_editor); + if ((cp = strchr(editor, ':')) != NULL) + *cp = '\0'; /* def_editor could be a path */ + editor_path = resolve_editor(cp, argv_out); + } + if (!editor_path) { + audit_failure(NewArgv, "%s: command not found", editor); + warningx("%s: command not found", editor); + } + return editor_path; +} + struct policy_plugin sudoers_policy = { SUDO_POLICY_PLUGIN, SUDO_API_VERSION, @@ -1309,7 +1406,8 @@ struct policy_plugin sudoers_policy = { sudoers_policy_check, sudoers_policy_list, sudoers_policy_validate, - sudoers_policy_invalidate + sudoers_policy_invalidate, + sudoers_policy_sudoedit }; struct io_plugin sudoers_io = { diff --git a/src/Makefile.in b/src/Makefile.in index b2e881c3d..8d5bcf17f 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -72,11 +72,9 @@ SHELL = /bin/sh PROGS = @PROGS@ -# XXX - add back missing ones: -# sudo_edit.o -OBJS = sudo.o parse_args.o lbuf.o alloc.o error.o zero_bytes.o \ - load_plugins.o conversation.o list.o fmt_string.o tgetpass.o \ - fileops.o term.o ttysize.o atobool.o script.o pty.o @SUDO_OBJS@ +OBJS = alloc.o atobool.o conversation.o error.o fileops.o fmt_string.o \ + gettime.o lbuf.o list.o load_plugins.o parse_args.o pty.o script.o \ + sudo.o sudo_edit.o term.o tgetpass.o ttysize.o zero_bytes.o @SUDO_OBJS@ LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/ @@ -115,6 +113,7 @@ conversation.o: $(srcdir)/conversation.c $(SUDODEP) error.o: $(srcdir)/error.c $(incdir)/compat.h $(incdir)/error.h $(top_builddir)/config.h fileops.o: $(srcdir)/fileops.c $(SUDODEP) fmt_string.o: $(srcdir)/fmt_string.c $(incdir)/compat.h $(top_builddir)/config.h +gettime.o: $(srcdir)/gettime.c $(incdir)/compat.h $(top_builddir)/config.h lbuf.o: $(srcdir)/lbuf.c $(SUDODEP) list.o: $(srcdir)/list.c $(SUDODEP) load_plugins.o: $(srcdir)/load_plugins.c $(SUDODEP) diff --git a/plugins/sudoers/gettime.c b/src/gettime.c similarity index 100% rename from plugins/sudoers/gettime.c rename to src/gettime.c diff --git a/src/parse_args.c b/src/parse_args.c index ecf5a664f..801f41896 100644 --- a/src/parse_args.c +++ b/src/parse_args.c @@ -418,6 +418,13 @@ parse_args(int argc, char **argv, int *nargc, char ***nargv, char ***settingsp, } settings[j] = NULL; + /* Must have the command in argv[0]. */ + if (mode == MODE_EDIT) { + argc++; + argv--; + argv[0] = "sudoedit"; + } + *settingsp = settings; *env_addp = env_add; *nargc = argc; diff --git a/src/sudo.c b/src/sudo.c index 3046208f6..d7621f1ef 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -93,8 +93,6 @@ static void disable_coredumps(void); static char **get_user_info(struct user_details *); static void command_info_to_details(char * const info[], struct command_details *details); -static int run_command(struct command_details *details, char *argv[], - char *envp[]); /* XXX - header file */ extern const char *list_user, *runas_user, *runas_group; @@ -109,7 +107,7 @@ static struct rlimit corelimit; int main(int argc, char *argv[], char *envp[]) { - int nargc, sudo_mode; + int nargc, sudo_mode, exitcode = 0; char **nargv, **settings, **env_add; char **user_info, **command_info, **argv_out, **user_env_out; struct plugin_container *plugin, *next; @@ -200,9 +198,22 @@ main(int argc, char *argv[], char *envp[]) ISSET(sudo_mode, MODE_LONG_LIST), list_user); } exit(ok != TRUE); + case MODE_EDIT: + if (!policy_plugin.u.policy->check_sudoedit) + errorx(1, "policy plugin %s does not support sudoedit", + policy_plugin.name); + /* FALLTHROUGH */ case MODE_RUN: - ok = policy_plugin.u.policy->check_policy(nargc, nargv, env_add, - &command_info, &argv_out, &user_env_out); + if (sudo_mode & MODE_EDIT) { + /* XXX - must be able to tell which are the files in argv */ + /* as opposed to editor flags; could use original argv */ + /* and only use argv_out for the command path + args */ + ok = policy_plugin.u.policy->check_sudoedit(nargc, nargv, + env_add, &command_info, &argv_out, &user_env_out); + } else { + ok = policy_plugin.u.policy->check_policy(nargc, nargv, env_add, + &command_info, &argv_out, &user_env_out); + } sudo_debug(8, "policy plugin returns %d", ok); if (ok != TRUE) { if (ok == -2) @@ -234,15 +245,16 @@ main(int argc, char *argv[], char *envp[]) (void) setrlimit(RLIMIT_CORE, &corelimit); #endif /* RLIMIT_CORE && !SUDO_DEVEL */ /* run_command will call the close method for us */ - run_command(&command_details, argv_out, user_env_out); - break; - case MODE_EDIT: - /* XXX - fill in */ + if (sudo_mode & MODE_EDIT) { + exitcode = sudo_edit(&command_details, argv_out, nargv + 1, user_env_out); + } else { + exitcode = run_command(&command_details, argv_out, user_env_out); + } break; default: errorx(1, "unexpected sudo mode 0x%x", sudo_mode); } - exit(0); + exit(exitcode); } /* @@ -744,7 +756,7 @@ done: /* * Run the command and wait for it to complete. */ -static int +int run_command(struct command_details *details, char *argv[], char *envp[]) { struct plugin_container *plugin; @@ -790,7 +802,7 @@ run_command(struct command_details *details, char *argv[], char *envp[]) warningx("unexpected child termination condition: %d", cstat.type); break; } - exit(exitcode); + return exitcode; } /* diff --git a/src/sudo.h b/src/sudo.h index 0dbdf5013..fd913299b 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -74,19 +74,6 @@ #define MODE_NONINTERACTIVE 0x00800000 #define MODE_LONG_LIST 0x01000000 -/* - * Used with set_perms() - */ -#define PERM_ROOT 0x00 -#define PERM_USER 0x01 -#define PERM_FULL_USER 0x02 -#define PERM_SUDOERS 0x03 -#define PERM_RUNAS 0x04 -#define PERM_FULL_RUNAS 0x05 -#define PERM_TIMESTAMP 0x06 -#define PERM_NOEXIT 0x10 /* flag */ -#define PERM_MASK 0xf0 - /* * We used to use the system definition of PASS_MAX or _PASSWD_LEN, * but that caused problems with various alternate authentication @@ -199,12 +186,21 @@ void get_ttysize(int *linep, int *colp); /* sudo.c */ int exec_setup(struct command_details *details); +int run_command(struct command_details *details, char *argv[], + char *envp[]); extern int debug_level; extern struct plugin_container_list io_plugins; +/* sudo_edit.c */ +int sudo_edit(struct command_details *details, char *argv[], char *files[], + char *envp[]); + /* parse_args.c */ void usage(int) __attribute__((__noreturn__)); +/* gettime.c */ +int gettime(struct timeval *); + #ifndef errno extern int errno; #endif diff --git a/src/sudo_edit.c b/src/sudo_edit.c index f9c8751ba..0b7d3ee80 100644 --- a/src/sudo_edit.c +++ b/src/sudo_edit.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2008 Todd C. Miller + * Copyright (c) 2004-2008, 2010 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -42,6 +42,7 @@ # include #endif /* HAVE_UNISTD_H */ #include +#include #include #include #include @@ -49,37 +50,63 @@ #if TIME_WITH_SYS_TIME # include #endif -#ifndef HAVE_TIMESPEC -# include -#endif #include "sudo.h" -extern char **environ; +extern struct user_details user_details; + +static void +switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups) +{ + int serrno = errno; + + /* When restoring root, change euid first; otherwise change it last. */ + if (euid == ROOT_UID) { + if (seteuid(ROOT_UID) != 0) + error(1, "seteuid(ROOT_UID)"); + } + if (ngroups != -1) { + if (setgroups(ngroups, groups) != 0) + error(1, "setgroups"); + } + if (setegid(egid) != 0) + error(1, "setegid(%d)", (int)egid); + if (euid != ROOT_UID) { + if (seteuid(euid) != 0) + error(1, "seteuid(%d)", (int)euid); + } -static char *find_editor(); + errno = serrno; +} /* * Wrapper to allow users to edit privileged files with their own uid. */ int -sudo_edit(int argc, char **argv, char **envp) +sudo_edit(struct command_details *command_details, char *argv[], char *files[], + char *envp[]) { + struct command_details editor_details; ssize_t nread, nwritten; - pid_t kidpid, pid; const char *tmpdir; - char **nargv, **ap, *editor, *cp; + char **nargv, **ap, *cp; char buf[BUFSIZ]; - int error, i, ac, ofd, tfd, nargc, rval, tmplen, wasblank; + int retval, i, j, ac, ofd, tfd, nargc, nfiles, rval, tmplen; struct stat sb; - struct timespec ts1, ts2; + struct timeval tv, tv1, tv2; struct tempfile { char *tfile; char *ofile; - struct timespec omtim; + struct timeval omtim; off_t osize; } *tf; + /* + * Set real, effective and saved uids to root. + * We will change the euid as needed below. + */ + setuid(ROOT_UID); + /* * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp */ @@ -99,54 +126,57 @@ sudo_edit(int argc, char **argv, char **envp) * 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(); + endpwent(); + endgrent(); /* * 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++) { - error = -1; - set_perms(PERM_RUNAS); - if ((ofd = open(*ap, O_RDONLY, 0644)) != -1 || errno == ENOENT) { + for (nfiles = 0; files[nfiles] != NULL; nfiles++) + continue; + tf = emalloc2(nfiles, sizeof(*tf)); + zero_bytes(tf, nfiles * sizeof(*tf)); + for (i = 0, j = 0; i < nfiles; i++) { + retval = -1; + switch_user(command_details->euid, command_details->egid, + command_details->ngroups, command_details->groups); + if ((ofd = open(files[i], O_RDONLY, 0644)) != -1 || errno == ENOENT) { if (ofd == -1) { zero_bytes(&sb, sizeof(sb)); /* new file */ - error = 0; + retval = 0; } else { #ifdef HAVE_FSTAT - error = fstat(ofd, &sb); + retval = fstat(ofd, &sb); #else - error = stat(tf[i].ofile, &sb); + retval = stat(tf[j].ofile, &sb); #endif } } - set_perms(PERM_ROOT); - if (error || (ofd != -1 && !S_ISREG(sb.st_mode))) { - if (error) - warning("%s", *ap); + switch_user(ROOT_UID, user_details.egid, + user_details.ngroups, user_details.groups); + if (retval || (ofd != -1 && !S_ISREG(sb.st_mode))) { + if (retval) + 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; - tf[i].omtim.tv_sec = mtim_getsec(sb); - tf[i].omtim.tv_nsec = mtim_getnsec(sb); - 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); - set_perms(PERM_USER); - tfd = mkstemp(tf[i].tfile); - set_perms(PERM_ROOT); + cp = tf[j].ofile; + easprintf(&tf[j].tfile, "%.*s/%s.XXXXXXXX", tmplen, tmpdir, cp); + if (seteuid(user_details.uid) != 0) + error(1, "seteuid(%d)", (int)user_details.uid); + tfd = mkstemp(tf[j].tfile); + if (seteuid(ROOT_UID) != 0) + error(1, "seteuid(ROOT_UID)"); if (tfd == -1) { warning("mkstemp"); goto cleanup; @@ -155,9 +185,9 @@ sudo_edit(int argc, char **argv, char **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,104 +200,67 @@ sudo_edit(int argc, char **argv, char **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 - error = fstat(tfd, &sb); + retval = fstat(tfd, &sb); #else - error = stat(tf[i].tfile, &sb); + retval = stat(tf[j].tfile, &sb); #endif - if (!error) { - tf[i].omtim.tv_sec = mtim_getsec(sb); - tf[i].omtim.tv_nsec = mtim_getnsec(sb); - } + if (!retval) + mtim_get(&sb, &tf[j].omtim); close(tfd); + j++; } - if (argc == 1) + if (nfiles == 0) return(1); /* no files readable, you lose */ - environ = envp; - editor = find_editor(); - /* * 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 argv (the editor with its args) and the file list + * to create a new argv. */ - nargc = argc; - for (wasblank = FALSE, cp = editor; *cp != '\0'; cp++) { - if (isblank((unsigned char) *cp)) - wasblank = TRUE; - else if (wasblank) { - wasblank = FALSE; - nargc++; - } - } + for (ap = argv; *ap != NULL; ap++) + continue; + nargc = (int)(ap - argv) + nfiles; 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; ) + for (ap = argv; *ap != NULL; ap++) + nargv[ac++] = *ap; + for (i = 0; i < nfiles && ac < nargc; ) nargv[ac++] = tf[i++].tfile; nargv[ac] = 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(&ts1); - kidpid = fork(); - if (kidpid == -1) { - warning("fork"); - goto cleanup; - } else if (kidpid == 0) { - /* child */ - 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); - gettime(&ts2); - if (pid == -1 || !WIFEXITED(i)) - rval = 1; - else - rval = WEXITSTATUS(i); + gettime(&tv1); + memcpy(&editor_details, command_details, sizeof(editor_details)); + editor_details.uid = user_details.uid; + editor_details.euid = user_details.uid; + editor_details.gid = user_details.gid; + editor_details.egid = user_details.gid; + editor_details.ngroups = user_details.ngroups; + editor_details.groups = user_details.groups; + rval = run_command(&editor_details, nargv, envp); + gettime(&tv2); /* Copy contents of temp files to real ones */ - for (i = 0; i < argc - 1; i++) { - error = -1; - set_perms(PERM_USER); + for (i = 0; i < nfiles; i++) { + retval = -1; + if (seteuid(user_details.uid) != 0) + error(1, "seteuid(%d)", (int)user_details.uid); if ((tfd = open(tf[i].tfile, O_RDONLY, 0644)) != -1) { #ifdef HAVE_FSTAT - error = fstat(tfd, &sb); + retval = fstat(tfd, &sb); #else - error = stat(tf[i].tfile, &sb); + retval = stat(tf[i].tfile, &sb); #endif } - set_perms(PERM_ROOT); - if (error || !S_ISREG(sb.st_mode)) { - if (error) + if (seteuid(ROOT_UID) != 0) + error(1, "seteuid(ROOT_UID)"); + if (retval || !S_ISREG(sb.st_mode)) { + if (retval) warning("%s", tf[i].tfile); else warningx("%s: not a regular file", tf[i].tfile); @@ -276,27 +269,29 @@ sudo_edit(int argc, char **argv, char **envp) close(tfd); continue; } - if (tf[i].osize == sb.st_size && tf[i].omtim.tv_sec == mtim_getsec(sb) - && tf[i].omtim.tv_nsec == mtim_getnsec(sb)) { + mtim_get(&sb, &tv); + if (tf[i].osize == sb.st_size && timercmp(&tf[i].omtim, &tv, ==)) { /* * If mtime and size match but the user spent no measurable * time in the editor we can't tell if the file was changed. */ -#ifdef HAVE_TIMESPECSUB2 - timespecsub(&ts1, &ts2); +#ifdef HAVE_TIMERSUB2 + timersub(&tv1, &tv2); #else - timespecsub(&ts1, &ts2, &ts2); + timersub(&tv1, &tv2, &tv2); #endif - if (timespecisset(&ts2)) { + if (timerisset(&tv2)) { warningx("%s unchanged", tf[i].ofile); unlink(tf[i].tfile); close(tfd); continue; } } - set_perms(PERM_RUNAS); + switch_user(command_details->euid, command_details->egid, + command_details->ngroups, command_details->groups); ofd = open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT, 0644); - set_perms(PERM_ROOT); + switch_user(ROOT_UID, user_details.egid, + user_details.ngroups, user_details.groups); if (ofd == -1) { warning("unable to write to %s", tf[i].ofile); warningx("contents of edit session left in %s", tf[i].tfile); @@ -328,46 +323,9 @@ sudo_edit(int argc, char **argv, char **envp) 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); } - -/* - * 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. - */ -static char * -find_editor(void) -{ - char *cp, *editor = NULL, **ev, *ev0[4]; - - 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); - break; - } - } - editor = NULL; - } - if (editor == NULL) { - editor = estrdup(def_editor); - if ((cp = strchr(editor, ':')) != NULL) - *cp = '\0'; /* def_editor could be a path */ - } - return(editor); -}