From 8d1708434f750dd6b24708e77e9bcc142db7a69f Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Tue, 26 May 2015 15:46:41 -0600 Subject: [PATCH] Add sudo_strsplit(), similar to strtok_r() but non-destructive and operates on non-C strings (requires a length parameter). --- MANIFEST | 1 + include/sudo_util.h | 4 ++ lib/util/Makefile.in | 33 +++++++++------- lib/util/strsplit.c | 80 +++++++++++++++++++++++++++++++++++++++ lib/util/util.exp.in | 1 + plugins/sudoers/sudoers.c | 76 ++++++++++++++++++++++--------------- 6 files changed, 151 insertions(+), 44 deletions(-) create mode 100644 lib/util/strsplit.c diff --git a/MANIFEST b/MANIFEST index 5360972ab..6cfcaf351 100644 --- a/MANIFEST +++ b/MANIFEST @@ -162,6 +162,7 @@ lib/util/strlcpy.c lib/util/strndup.c lib/util/strnlen.c lib/util/strsignal.c +lib/util/strsplit.c lib/util/strtobool.c lib/util/strtoid.c lib/util/strtomode.c diff --git a/include/sudo_util.h b/include/sudo_util.h index 57f1ee437..f49e4d69b 100644 --- a/include/sudo_util.h +++ b/include/sudo_util.h @@ -201,6 +201,10 @@ __dso_public int sudo_secure_file_v1(const char *path, uid_t uid, gid_t gid, str __dso_public int sudo_setgroups_v1(int ngids, const GETGROUPS_T *gids); #define sudo_setgroups(_a, _b) sudo_setgroups_v1((_a), (_b)) +/* strsplit.c */ +__dso_public const char *sudo_strsplit_v1(const char *str, const char *endstr, const char *sep, const char **last); +#define sudo_strsplit(_a, _b, _c, _d) sudo_strsplit_v1(_a, _b, _c, _d) + /* strtobool.c */ __dso_public int sudo_strtobool_v1(const char *str); #define sudo_strtobool(_a) sudo_strtobool_v1((_a)) diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in index 46610c75b..6c52be61d 100644 --- a/lib/util/Makefile.in +++ b/lib/util/Makefile.in @@ -101,8 +101,9 @@ SHELL = @SHELL@ LTOBJS = alloc.lo event.lo fatal.lo key_val.lo gethostname.lo gettime.lo \ gidlist.lo lbuf.lo locking.lo parseln.lo progname.lo secure_path.lo \ - setgroups.lo strtobool.lo strtoid.lo strtomode.lo sudo_conf.lo \ - sudo_debug.lo sudo_dso.lo term.lo ttysize.lo @COMMON_OBJS@ @LTLIBOBJS@ + setgroups.lo strsplit.lo strtobool.lo strtoid.lo strtomode.lo \ + sudo_conf.lo sudo_debug.lo sudo_dso.lo term.lo ttysize.lo \ + @COMMON_OBJS@ @LTLIBOBJS@ ATOFOO_TEST_OBJS = atofoo_test.lo @@ -333,10 +334,9 @@ conf_test.lo: $(srcdir)/regress/sudo_conf/conf_test.c \ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ $(top_builddir)/config.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/regress/sudo_conf/conf_test.c -event.lo: $(srcdir)/event.c $(incdir)/compat/stdbool.h $(incdir)/sudo_alloc.h \ - $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ - $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h $(incdir)/sudo_queue.h \ - $(incdir)/sudo_util.h $(top_builddir)/config.h +event.lo: $(srcdir)/event.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/event.c event_poll.lo: $(srcdir)/event_poll.c $(incdir)/compat/stdbool.h \ $(incdir)/sudo_alloc.h $(incdir)/sudo_compat.h \ @@ -345,15 +345,15 @@ event_poll.lo: $(srcdir)/event_poll.c $(incdir)/compat/stdbool.h \ $(top_builddir)/config.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/event_poll.c event_select.lo: $(srcdir)/event_select.c $(incdir)/compat/stdbool.h \ - $(incdir)/sudo_alloc.h $(incdir)/sudo_compat.h \ - $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \ - $(incdir)/sudo_fatal.h $(incdir)/sudo_queue.h \ - $(incdir)/sudo_util.h $(top_builddir)/config.h + $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/event_select.c -fatal.lo: $(srcdir)/fatal.c $(incdir)/compat/stdbool.h $(incdir)/sudo_alloc.h \ - $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \ - $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \ - $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h +fatal.lo: $(srcdir)/fatal.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/fatal.c fnm_test.lo: $(srcdir)/regress/fnmatch/fnm_test.c $(incdir)/compat/fnmatch.h \ $(incdir)/sudo_compat.h $(top_builddir)/config.h @@ -492,6 +492,11 @@ strnlen.lo: $(srcdir)/strnlen.c $(incdir)/sudo_compat.h $(top_builddir)/config.h strsignal.lo: $(srcdir)/strsignal.c $(incdir)/sudo_compat.h \ $(incdir)/sudo_gettext.h $(top_builddir)/config.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/strsignal.c +strsplit.lo: $(srcdir)/strsplit.c $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/strsplit.c strtobool.lo: $(srcdir)/strtobool.c $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ diff --git a/lib/util/strsplit.c b/lib/util/strsplit.c new file mode 100644 index 000000000..15aaf17ef --- /dev/null +++ b/lib/util/strsplit.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 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 +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#include +#include + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +/* + * Like strtok_r but non-destructive and works w/o a NUL terminator. + * TODO: Optimize by storing current char in a variable ch + */ +const char * +sudo_strsplit_v1(const char *str, const char *endstr, const char *sep, const char **last) +{ + const char *cp, *s; + debug_decl(sudo_strsplit, SUDO_DEBUG_UTIL) + + /* If no str specified, use last ptr (if any). */ + if (str == NULL) + str = *last; + + /* Skip leading separator characters. */ + while (str < endstr) { + for (s = sep; *s != '\0'; s++) { + if (*str == *s) { + str++; + break; + } + } + if (*s == '\0') + break; + } + + /* Empty string? */ + if (str >= endstr) { + *last = endstr; + debug_return_ptr(NULL); + } + + /* Scan str until we hit a char from sep. */ + for (cp = str; cp < endstr; cp++) { + for (s = sep; *s != '\0'; s++) { + if (*cp == *s) + break; + } + if (*s != '\0') + break; + } + *last = cp; + debug_return_const_ptr(str); +} diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in index 127db1c41..5694567a6 100644 --- a/lib/util/util.exp.in +++ b/lib/util/util.exp.in @@ -80,6 +80,7 @@ sudo_parseln_v1 sudo_secure_dir_v1 sudo_secure_file_v1 sudo_setgroups_v1 +sudo_strsplit_v1 sudo_strtobool_v1 sudo_strtoid_v1 sudo_strtomode_v1 diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index 0c2e5aeae..e59075a93 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -1046,48 +1046,60 @@ sudoers_cleanup(void) } static char * -resolve_editor(const char *ed, size_t edlen, int nfiles, char **files, int *argc_out, char ***argv_out) +resolve_editor(const char *ed, size_t edlen, int nfiles, char **files, + int *argc_out, char ***argv_out) { - char *cp, **nargv, *editor, *editor_path = NULL; - int ac, i, nargc; - bool wasblank; + 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) - /* Note: editor becomes part of argv_out and is not freed. */ - editor = sudo_emalloc(edlen + 1); - memcpy(editor, ed, edlen); - editor[edlen] = '\0'; - /* - * Split editor into an argument vector; editor is reused (do not free). + * 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. */ - nargc = 1; - for (wasblank = false, cp = editor; *cp != '\0'; cp++) { - if (isblank((unsigned char) *cp)) - wasblank = true; - else if (wasblank) { - wasblank = false; - nargc++; - } + 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. */ - cp = strtok(editor, " \t"); - if (cp == NULL || - find_path(cp, &editor_path, NULL, getenv("PATH"), 0) != FOUND) { - sudo_efree(editor); + if (find_path(editor, &editor_path, NULL, getenv("PATH"), 0) != FOUND) { + free(editor); + errno = ENOENT; debug_return_str(NULL); } - nargv = (char **) sudo_emallocarray(nargc + 1 + nfiles + 1, sizeof(char *)); - for (ac = 0; cp != NULL && ac < nargc; ac++) { - nargv[ac] = cp; - cp = strtok(NULL, " \t"); + + /* 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[ac++] = "--"; - for (i = 0; i < nfiles; ) - nargv[ac++] = files[i++]; - nargv[ac] = NULL; + nargv[nargc++] = "--"; + while ((nargv[nargc++] = *files++) != NULL) + continue; *argc_out = nargc; *argv_out = nargv; @@ -1118,6 +1130,8 @@ find_editor(int nfiles, char **files, int *argc_out, char ***argv_out) if ((editor = getenv(*ev)) != NULL && *editor != '\0') { editor_path = resolve_editor(editor, strlen(editor), nfiles, files, argc_out, argv_out); + if (editor_path == NULL && errno != ENOENT) + debug_return_str(NULL); } } if (editor_path == NULL) { @@ -1129,6 +1143,8 @@ find_editor(int nfiles, char **files, int *argc_out, char ***argv_out) else len = strlen(cp); editor_path = resolve_editor(cp, len, nfiles, files, argc_out, argv_out); + if (editor_path == NULL && errno != ENOENT) + debug_return_str(NULL); cp = ep + 1; } while (ep != NULL && editor_path == NULL); } -- 2.40.0