diff --git a/MANIFEST b/MANIFEST index 0d6962293..8aca34e0c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -16,6 +16,13 @@ common/fileops.c common/fmt_string.c common/lbuf.c common/list.c +common/regress/sudo_conf/conf_test.c +common/regress/sudo_conf/test1.in +common/regress/sudo_conf/test1.out.ok +common/regress/sudo_conf/test2.in +common/regress/sudo_conf/test2.out.ok +common/regress/sudo_conf/test3.in +common/regress/sudo_conf/test3.out.ok common/regress/sudo_parseln/parseln_test.c common/regress/sudo_parseln/test1.in common/regress/sudo_parseln/test1.out.ok diff --git a/common/Makefile.in b/common/Makefile.in index 7a0eaa043..6f4989516 100644 --- a/common/Makefile.in +++ b/common/Makefile.in @@ -55,7 +55,7 @@ SSP_CFLAGS = @SSP_CFLAGS@ SSP_LDFLAGS = @SSP_LDFLAGS@ # Regression tests -TEST_PROGS = parseln_test +TEST_PROGS = conf_test parseln_test TEST_LIBS = @LIBS@ @LIBINTL@ TEST_LDFLAGS = @LDFLAGS@ @@ -72,6 +72,8 @@ LTOBJS = alloc.lo atobool.lo error.lo fileops.lo fmt_string.lo lbuf.lo list.lo \ PARSELN_TEST_OBJS = parseln_test.lo +CONF_TEST_OBJS = conf_test.lo + all: libcommon.la Makefile: $(srcdir)/Makefile.in @@ -85,6 +87,9 @@ Makefile: $(srcdir)/Makefile.in libcommon.la: $(LTOBJS) $(LIBTOOL) --mode=link $(CC) -o $@ $(LTOBJS) -no-install +conf_test: $(CONF_TEST_OBJS) libcommon.la + $(LIBTOOL) --mode=link $(CC) -o $@ $(CONF_TEST_OBJS) libcommon.la $(TEST_LDFLAGS) $(TEST_LIBS) + parseln_test: $(PARSELN_TEST_OBJS) libcommon.la $(LIBTOOL) --mode=link $(CC) -o $@ $(PARSELN_TEST_OBJS) libcommon.la $(TEST_LDFLAGS) $(TEST_LIBS) @@ -107,21 +112,26 @@ uninstall: check: $(TEST_PROGS) @if test X"$(cross_compiling)" != X"yes"; then \ passed=0; failed=0; total=0; \ - dir=sudo_parseln; \ - mkdir -p regress/$$dir; \ - for t in $(srcdir)/regress/$$dir/*.in; do \ - base=`basename $$t .in`; \ - out="regress/$$dir/$$base.out"; \ - ./parseln_test <$$t >$$out; \ - if cmp $$out $(srcdir)/$$out.ok >/dev/null; then \ - passed=`expr $$passed + 1`; \ - echo "$$dir/$$base: OK"; \ - else \ - failed=`expr $$failed + 1`; \ - echo "$$dir/$$base: FAIL"; \ - diff $$out $(srcdir)/$$out.ok; \ - fi; \ - total=`expr $$total + 1`; \ + for dir in sudo_conf sudo_parseln; do \ + mkdir -p regress/$$dir; \ + for t in $(srcdir)/regress/$$dir/*.in; do \ + base=`basename $$t .in`; \ + out="regress/$$dir/$$base.out"; \ + if test "$$dir" = "sudo_conf"; then \ + ./conf_test $$t >$$out; \ + else \ + ./parseln_test <$$t >$$out; \ + fi; \ + if cmp $$out $(srcdir)/$$out.ok >/dev/null; then \ + passed=`expr $$passed + 1`; \ + echo "$$dir/$$base: OK"; \ + else \ + failed=`expr $$failed + 1`; \ + echo "$$dir/$$base: FAIL"; \ + diff $$out $(srcdir)/$$out.ok; \ + fi; \ + total=`expr $$total + 1`; \ + done; \ done; \ echo "$$dir: $$passed/$$total tests passed; $$failed/$$total tests failed"; \ exit $$failed; \ @@ -171,6 +181,10 @@ lbuf.lo: $(srcdir)/lbuf.c $(top_builddir)/config.h $(incdir)/missing.h \ list.lo: $(srcdir)/list.c $(top_builddir)/config.h $(incdir)/missing.h \ $(incdir)/list.h $(incdir)/error.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/list.c +conf_test.lo: $(srcdir)/regress/sudo_conf/conf_test.c \ + $(top_builddir)/config.h $(top_srcdir)/compat/stdbool.h \ + $(incdir)/missing.h $(incdir)/fileops.h + $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/regress/sudo_conf/conf_test.c parseln_test.lo: $(srcdir)/regress/sudo_parseln/parseln_test.c \ $(top_builddir)/config.h $(top_srcdir)/compat/stdbool.h \ $(incdir)/missing.h $(incdir)/fileops.h diff --git a/common/regress/sudo_conf/conf_test.c b/common/regress/sudo_conf/conf_test.c new file mode 100644 index 000000000..f73ada14e --- /dev/null +++ b/common/regress/sudo_conf/conf_test.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2013 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 */ +#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_STDBOOL_H +# include +#else +# include "compat/stdbool.h" +#endif + +#include "missing.h" +#include "sudo_conf.h" + +static void sudo_conf_dump(void); + +/* + * Simple test driver for sudo_conf(). + * Parses the given configuration file and dumps the resulting + * sudo_conf_data struct to the standard output. + */ +int +main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "usage: conf_test conf_file\n"); + exit(1); + } + sudo_conf_read(argv[1]); + sudo_conf_dump(); + + exit(0); +} + +static void +sudo_conf_dump(void) +{ + struct plugin_info_list *plugins = sudo_conf_plugins(); + struct sudo_conf_paths *scp; + struct plugin_info *info; + + printf("Set disable_coredump %s\n", + sudo_conf_disable_coredump() ? "true" : "false"); + printf("Set group_source %s\n", + sudo_conf_group_source() == GROUP_SOURCE_ADAPTIVE ? "adaptive" : + sudo_conf_group_source() == GROUP_SOURCE_STATIC ? "static" : "dynamic"); + printf("Set max_groups %d\n", sudo_conf_max_groups()); + if (sudo_conf_debug_flags() != NULL) + printf("Debug %s %s\n", getprogname(), sudo_conf_debug_flags()); + if (sudo_conf_askpass_path() != NULL) + printf("Path askpass %s\n", sudo_conf_askpass_path()); +#ifdef _PATH_SUDO_NOEXEC + if (sudo_conf_noexec_path() != NULL) + printf("Path noexec %s\n", sudo_conf_noexec_path()); +#endif + tq_foreach_fwd(plugins, info) { + printf("Plugin %s %s", info->symbol_name, info->path); + if (info->options) { + char * const * op; + for (op = info->options; *op != NULL; op++) + printf(" %s", *op); + } + putchar('\n'); + } +} + +/* STUB */ +void +warning_set_locale(void) +{ + return; +} + +/* STUB */ +void +warning_restore_locale(void) +{ + return; +} diff --git a/common/regress/sudo_conf/test1.in b/common/regress/sudo_conf/test1.in new file mode 100644 index 000000000..d572cad99 --- /dev/null +++ b/common/regress/sudo_conf/test1.in @@ -0,0 +1,72 @@ +# +# Sample /etc/sudo.conf file +# +# Format: +# Plugin plugin_name plugin_path plugin_options ... +# Path askpass /path/to/askpass +# Path noexec /path/to/sudo_noexec.so +# Debug sudo /var/log/sudo_debug all@warn +# Set disable_coredump true +# +# Sudo plugins: +# +# The plugin_path is relative to ${prefix}/libexec unless fully qualified. +# The plugin_name corresponds to a global symbol in the plugin +# that contains the plugin interface structure. +# The plugin_options are optional. +# +# The sudoers plugin is used by default if no Plugin lines are present. +Plugin sudoers_policy sudoers.so +Plugin sudoers_io sudoers.so + +# +# Sudo askpass: +# +# An askpass helper program may be specified to provide a graphical +# password prompt for "sudo -A" support. Sudo does not ship with its +# own askpass program but can use the OpenSSH askpass. +# +# Use the OpenSSH askpass +Path askpass /usr/X11R6/bin/ssh-askpass +# +# Use the Gnome OpenSSH askpass +#Path askpass /usr/libexec/openssh/gnome-ssh-askpass + +# +# Sudo noexec: +# +# Path to a shared library containing dummy versions of the execv(), +# execve() and fexecve() library functions that just return an error. +# This is used to implement the "noexec" functionality on systems that +# support C or its equivalent. +# The compiled-in value is usually sufficient and should only be changed +# if you rename or move the sudo_noexec.so file. +# +Path noexec /usr/libexec/sudo_noexec.so + +# +# Core dumps: +# +# By default, sudo disables core dumps while it is executing (they +# are re-enabled for the command that is run). +# To aid in debugging sudo problems, you may wish to enable core +# dumps by setting "disable_coredump" to false. +# +Set disable_coredump false + +# +# User groups: +# +# Sudo passes the user's group list to the policy plugin. +# If the user is a member of the maximum number of groups (usually 16), +# sudo will query the group database directly to be sure to include +# the full list of groups. +# +# On some systems, this can be expensive so the behavior is configurable. +# The "group_source" setting has three possible values: +# static - use the user's list of groups returned by the kernel. +# dynamic - query the group database to find the list of groups. +# adaptive - if user is in less than the maximum number of groups. +# use the kernel list, else query the group database. +# +Set group_source static diff --git a/common/regress/sudo_conf/test1.out.ok b/common/regress/sudo_conf/test1.out.ok new file mode 100644 index 000000000..54e067180 --- /dev/null +++ b/common/regress/sudo_conf/test1.out.ok @@ -0,0 +1,6 @@ +Set disable_coredump false +Set group_source static +Set max_groups -1 +Path askpass /usr/X11R6/bin/ssh-askpass +Plugin sudoers_policy sudoers.so +Plugin sudoers_io sudoers.so diff --git a/common/regress/sudo_conf/test2.in b/common/regress/sudo_conf/test2.in new file mode 100644 index 000000000..e69de29bb diff --git a/common/regress/sudo_conf/test2.out.ok b/common/regress/sudo_conf/test2.out.ok new file mode 100644 index 000000000..af42145e2 --- /dev/null +++ b/common/regress/sudo_conf/test2.out.ok @@ -0,0 +1,3 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 diff --git a/common/regress/sudo_conf/test3.in b/common/regress/sudo_conf/test3.in new file mode 100644 index 000000000..b111a235d --- /dev/null +++ b/common/regress/sudo_conf/test3.in @@ -0,0 +1,2 @@ +Plugin sudoers_policy sudoers.so sudoers_file=/etc/sudoers sudoers_mode=0400 sudoers_gid=0 sudoers_uid=0 +Plugin sudoers_io sudoers.so diff --git a/common/regress/sudo_conf/test3.out.ok b/common/regress/sudo_conf/test3.out.ok new file mode 100644 index 000000000..819d638f8 --- /dev/null +++ b/common/regress/sudo_conf/test3.out.ok @@ -0,0 +1,5 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 +Plugin sudoers_policy sudoers.so sudoers_file=/etc/sudoers sudoers_mode=0400 sudoers_gid=0 sudoers_uid=0 +Plugin sudoers_io sudoers.so diff --git a/common/sudo_conf.c b/common/sudo_conf.c index 8bd5853bf..772b51edb 100644 --- a/common/sudo_conf.c +++ b/common/sudo_conf.c @@ -75,7 +75,7 @@ extern bool atobool(const char *str); /* atobool.c */ struct sudo_conf_table { const char *name; unsigned int namelen; - void (*setter)(const char *entry); + void (*setter)(const char *entry, const char *conf_file); }; struct sudo_conf_paths { @@ -84,15 +84,15 @@ struct sudo_conf_paths { const char *pval; }; -static void set_debug(const char *entry); -static void set_path(const char *entry); -static void set_plugin(const char *entry); -static void set_variable(const char *entry); -static void set_var_disable_coredump(const char *entry); -static void set_var_group_source(const char *entry); -static void set_var_max_groups(const char *entry); +static void set_debug(const char *entry, const char *conf_file); +static void set_path(const char *entry, const char *conf_file); +static void set_plugin(const char *entry, const char *conf_file); +static void set_variable(const char *entry, const char *conf_file); +static void set_var_disable_coredump(const char *entry, const char *conf_file); +static void set_var_group_source(const char *entry, const char *conf_file); +static void set_var_max_groups(const char *entry, const char *conf_file); -static unsigned int lineno; +static unsigned int conf_lineno; static struct sudo_conf_table sudo_conf_table[] = { { "Debug", sizeof("Debug") - 1, set_debug }, @@ -136,7 +136,7 @@ static struct sudo_conf_data { * "Set variable_name value" */ static void -set_variable(const char *entry) +set_variable(const char *entry, const char *conf_file) { struct sudo_conf_table *var; @@ -146,20 +146,20 @@ set_variable(const char *entry) entry += var->namelen + 1; while (isblank((unsigned char)*entry)) entry++; - var->setter(entry); + var->setter(entry, conf_file); break; } } } static void -set_var_disable_coredump(const char *entry) +set_var_disable_coredump(const char *entry, const char *conf_file) { sudo_conf_data.disable_coredump = atobool(entry); } static void -set_var_group_source(const char *entry) +set_var_group_source(const char *entry, const char *conf_file) { if (strcasecmp(entry, "adaptive") == 0) { sudo_conf_data.group_source = GROUP_SOURCE_ADAPTIVE; @@ -169,12 +169,12 @@ set_var_group_source(const char *entry) sudo_conf_data.group_source = GROUP_SOURCE_DYNAMIC; } else { warningx(_("unsupported group source `%s' in %s, line %d"), entry, - _PATH_SUDO_CONF, lineno); + conf_file, conf_lineno); } } static void -set_var_max_groups(const char *entry) +set_var_max_groups(const char *entry, const char *conf_file) { long lval; char *ep; @@ -183,7 +183,7 @@ set_var_max_groups(const char *entry) if (*entry == '\0' || *ep != '\0' || lval < 0 || lval > INT_MAX || (errno == ERANGE && lval == LONG_MAX)) { warningx(_("invalid max groups `%s' in %s, line %d"), entry, - _PATH_SUDO_CONF, lineno); + conf_file, conf_lineno); } else { sudo_conf_data.max_groups = (int)lval; } @@ -193,7 +193,7 @@ set_var_max_groups(const char *entry) * "Debug progname debug_file debug_flags" */ static void -set_debug(const char *entry) +set_debug(const char *entry, const char *conf_file) { size_t filelen, proglen; const char *progname; @@ -228,7 +228,7 @@ set_debug(const char *entry) } static void -set_path(const char *entry) +set_path(const char *entry, const char *conf_file) { const char *name, *path; struct sudo_conf_paths *cur; @@ -252,7 +252,7 @@ set_path(const char *entry) } static void -set_plugin(const char *entry) +set_plugin(const char *entry, const char *conf_file) { struct plugin_info *info; const char *name, *path, *cp, *ep; @@ -299,7 +299,7 @@ set_plugin(const char *entry) info->options = options; info->prev = info; /* info->next = NULL; */ - info->lineno = lineno; + info->lineno = conf_lineno; tq_append(&sudo_conf_data.plugins, info); } @@ -351,7 +351,7 @@ sudo_conf_disable_coredump(void) * Reads in /etc/sudo.conf and populates sudo_conf_data. */ void -sudo_conf_read(void) +sudo_conf_read(const char *conf_file) { struct sudo_conf_table *cur; struct stat sb; @@ -364,40 +364,43 @@ sudo_conf_read(void) if (prev_locale[0] != 'C' || prev_locale[1] != '\0') setlocale(LC_ALL, "C"); - switch (sudo_secure_file(_PATH_SUDO_CONF, ROOT_UID, -1, &sb)) { - case SUDO_PATH_SECURE: - break; - case SUDO_PATH_MISSING: - /* Root should always be able to read sudo.conf. */ - if (errno != ENOENT && geteuid() == ROOT_UID) - warning(_("unable to stat %s"), _PATH_SUDO_CONF); - goto done; - case SUDO_PATH_BAD_TYPE: - warningx(_("%s is not a regular file"), _PATH_SUDO_CONF); - goto done; - case SUDO_PATH_WRONG_OWNER: - warningx(_("%s is owned by uid %u, should be %u"), - _PATH_SUDO_CONF, (unsigned int) sb.st_uid, ROOT_UID); - goto done; - case SUDO_PATH_WORLD_WRITABLE: - warningx(_("%s is world writable"), _PATH_SUDO_CONF); - goto done; - case SUDO_PATH_GROUP_WRITABLE: - warningx(_("%s is group writable"), _PATH_SUDO_CONF); - goto done; - default: - /* NOTREACHED */ - goto done; + if (conf_file == NULL) { + conf_file = _PATH_SUDO_CONF; + switch (sudo_secure_file(conf_file, ROOT_UID, -1, &sb)) { + case SUDO_PATH_SECURE: + break; + case SUDO_PATH_MISSING: + /* Root should always be able to read sudo.conf. */ + if (errno != ENOENT && geteuid() == ROOT_UID) + warning(_("unable to stat %s"), conf_file); + goto done; + case SUDO_PATH_BAD_TYPE: + warningx(_("%s is not a regular file"), conf_file); + goto done; + case SUDO_PATH_WRONG_OWNER: + warningx(_("%s is owned by uid %u, should be %u"), + conf_file, (unsigned int) sb.st_uid, ROOT_UID); + goto done; + case SUDO_PATH_WORLD_WRITABLE: + warningx(_("%s is world writable"), conf_file); + goto done; + case SUDO_PATH_GROUP_WRITABLE: + warningx(_("%s is group writable"), conf_file); + goto done; + default: + /* NOTREACHED */ + goto done; + } } - if ((fp = fopen(_PATH_SUDO_CONF, "r")) == NULL) { + if ((fp = fopen(conf_file, "r")) == NULL) { if (errno != ENOENT && geteuid() == ROOT_UID) - warning(_("unable to open %s"), _PATH_SUDO_CONF); + warning(_("unable to open %s"), conf_file); goto done; } - lineno = 0; - while (sudo_parseln(&line, &linesize, &lineno, fp) != -1) { + conf_lineno = 0; + while (sudo_parseln(&line, &linesize, &conf_lineno, fp) != -1) { if (*(cp = line) == '\0') continue; /* empty line or comment */ @@ -407,7 +410,7 @@ sudo_conf_read(void) cp += cur->namelen; while (isblank((unsigned char)*cp)) cp++; - cur->setter(cp); + cur->setter(cp, conf_file); break; } } diff --git a/include/sudo_conf.h b/include/sudo_conf.h index a855e2091..778a4b3c4 100644 --- a/include/sudo_conf.h +++ b/include/sudo_conf.h @@ -34,7 +34,7 @@ struct plugin_info { TQ_DECLARE(plugin_info) /* Read main sudo.conf file. */ -void sudo_conf_read(void); +void sudo_conf_read(const char *); /* Accessor functions. */ const char *sudo_conf_askpass_path(void); diff --git a/plugins/sudoers/sudoreplay.c b/plugins/sudoers/sudoreplay.c index 2ed553856..b911cb1fe 100644 --- a/plugins/sudoers/sudoreplay.c +++ b/plugins/sudoers/sudoreplay.c @@ -271,7 +271,7 @@ main(int argc, char *argv[]) error_callback_register(sudoreplay_cleanup); /* Read sudo.conf. */ - sudo_conf_read(); + sudo_conf_read(NULL); while ((ch = getopt(argc, argv, "d:f:hlm:s:V")) != -1) { switch(ch) { diff --git a/plugins/sudoers/testsudoers.c b/plugins/sudoers/testsudoers.c index 8ac9130d1..cd1fd0b1b 100644 --- a/plugins/sudoers/testsudoers.c +++ b/plugins/sudoers/testsudoers.c @@ -147,7 +147,7 @@ main(int argc, char *argv[]) textdomain("sudoers"); /* Read sudo.conf. */ - sudo_conf_read(); + sudo_conf_read(NULL); dflag = 0; grfile = pwfile = NULL; diff --git a/plugins/sudoers/visudo.c b/plugins/sudoers/visudo.c index 2225aff0c..1e33d1781 100644 --- a/plugins/sudoers/visudo.c +++ b/plugins/sudoers/visudo.c @@ -166,7 +166,7 @@ main(int argc, char *argv[]) error_callback_register(visudo_cleanup); /* Read sudo.conf. */ - sudo_conf_read(); + sudo_conf_read(NULL); /* * Arg handling. diff --git a/src/sesh.c b/src/sesh.c index 9b376287f..c676f0fae 100644 --- a/src/sesh.c +++ b/src/sesh.c @@ -57,7 +57,7 @@ main(int argc, char *argv[], char *envp[]) errorx(EXIT_FAILURE, _("requires at least one argument")); /* Read sudo.conf. */ - sudo_conf_read(); + sudo_conf_read(NULL); /* If argv[0] ends in -noexec, pass the flag to sudo_execve() */ if ((cp = strrchr(argv[0], '-')) != NULL && cp != argv[0]) diff --git a/src/sudo.c b/src/sudo.c index 8b843b64c..d9088a444 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -178,7 +178,7 @@ main(int argc, char *argv[], char *envp[]) fix_fds(); /* Read sudo.conf. */ - sudo_conf_read(); + sudo_conf_read(NULL); /* Fill in user_info with user name, uid, cwd, etc. */ memset(&user_details, 0, sizeof(user_details));