Add simple regress tests for sudo.conf parsing.

This commit is contained in:
Todd C. Miller
2013-02-15 14:42:10 -05:00
parent 790304789e
commit 0c40e82c16
16 changed files with 295 additions and 72 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2013 Todd C. Miller <Todd.Miller@courtesan.com>
*
* 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 <config.h>
#include <sys/types.h>
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif /* STDC_HEADERS */
#ifdef HAVE_STRING_H
# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
# include <memory.h>
# endif
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#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;
}

View File

@@ -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<LD_PRELOAD> 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

View File

@@ -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

View File

View File

@@ -0,0 +1,3 @@
Set disable_coredump true
Set group_source adaptive
Set max_groups -1

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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.

View File

@@ -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])

View File

@@ -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));