From: Todd C. Miller Date: Thu, 18 Jun 2015 15:51:36 +0000 (-0600) Subject: Use a common function for resolviong the user's editor in sudoedit X-Git-Tag: SUDO_1_8_14^2~77 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ac13264b447d50ad298735e7b544d74f944b4fc1;p=sudo Use a common function for resolviong the user's editor in sudoedit and visudo. The find_path() function now returns a dynamically allocated path instead of using a static string. --- diff --git a/MANIFEST b/MANIFEST index 1eeb85cc3..b6300c2c2 100644 --- 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 diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 6d8d3d7da..a5b3b2efb 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -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 index 000000000..8eee62d64 --- /dev/null +++ b/plugins/sudoers/editor.c @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010-2015 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 + * 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 + +#include +#include +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS) +# include +# endif +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#include + +#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); +} diff --git a/plugins/sudoers/find_path.c b/plugins/sudoers/find_path.c index 4ca0f5ea0..64e2e43df 100644 --- a/plugins/sudoers/find_path.c +++ b/plugins/sudoers/find_path.c @@ -45,40 +45,82 @@ #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); } diff --git a/plugins/sudoers/goodpath.c b/plugins/sudoers/goodpath.c index d67a09637..5a6839166 100644 --- a/plugins/sudoers/goodpath.c +++ b/plugins/sudoers/goodpath.c @@ -43,18 +43,22 @@ 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); diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index 89ee2f7df..f6cd6df7c 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -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); } diff --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h index 2e389fdac..315fac425 100644 --- a/plugins/sudoers/sudoers.h +++ b/plugins/sudoers/sudoers.h @@ -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 */ diff --git a/plugins/sudoers/visudo.c b/plugins/sudoers/visudo.c index 08c451df3..21bc30b81 100644 --- a/plugins/sudoers/visudo.c +++ b/plugins/sudoers/visudo.c @@ -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(×[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. */