Files
sudo/lib/util/sudo_conf.c
Todd C. Miller 3f022419ae Be consistent with the naming of the variable used to store the
function return value.  Previously, some code used "rval", some
used "ret".  This standardizes on "ret" and uses "rc" for temporary
return codes.
2016-09-08 16:38:08 -06:00

653 lines
18 KiB
C

/*
* Copyright (c) 2009-2016 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 <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include "compat/stdbool.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#define DEFAULT_TEXT_DOMAIN "sudo"
#include "sudo_gettext.h" /* must be included before sudo_compat.h */
#define SUDO_ERROR_WRAP 0
#include "sudo_compat.h"
#include "sudo_fatal.h"
#include "pathnames.h"
#include "sudo_plugin.h"
#include "sudo_conf.h"
#include "sudo_debug.h"
#include "sudo_util.h"
#ifdef __TANDEM
# define ROOT_UID 65535
#else
# define ROOT_UID 0
#endif
struct sudo_conf_table {
const char *name;
unsigned int namelen;
int (*parser)(const char *entry, const char *conf_file, unsigned int lineno);
};
struct sudo_conf_path_table {
const char *pname;
unsigned int pnamelen;
char **pval;
};
static int parse_debug(const char *entry, const char *conf_file, unsigned int lineno);
static int parse_path(const char *entry, const char *conf_file, unsigned int lineno);
static int parse_plugin(const char *entry, const char *conf_file, unsigned int lineno);
static int parse_variable(const char *entry, const char *conf_file, unsigned int lineno);
static struct sudo_conf_table sudo_conf_table[] = {
{ "Debug", sizeof("Debug") - 1, parse_debug },
{ "Path", sizeof("Path") - 1, parse_path },
{ "Plugin", sizeof("Plugin") - 1, parse_plugin },
{ "Set", sizeof("Set") - 1, parse_variable },
{ NULL }
};
static int set_var_disable_coredump(const char *entry, const char *conf_file, unsigned int);
static int set_var_group_source(const char *entry, const char *conf_file, unsigned int);
static int set_var_max_groups(const char *entry, const char *conf_file, unsigned int);
static int set_var_probe_interfaces(const char *entry, const char *conf_file, unsigned int);
static struct sudo_conf_table sudo_conf_var_table[] = {
{ "disable_coredump", sizeof("disable_coredump") - 1, set_var_disable_coredump },
{ "group_source", sizeof("group_source") - 1, set_var_group_source },
{ "max_groups", sizeof("max_groups") - 1, set_var_max_groups },
{ "probe_interfaces", sizeof("probe_interfaces") - 1, set_var_probe_interfaces },
{ NULL }
};
/*
* Using designated struct initializers would be clearer here but
* we want to avoid relying on C99 features for now.
*/
static struct sudo_conf_paths {
char *askpass;
char *sesh;
char *nodump;
char *noexec;
char *plugin_dir;
} sudo_conf_paths = {
_PATH_SUDO_ASKPASS,
_PATH_SUDO_SESH,
#ifdef _PATH_SUDO_NODUMP
_PATH_SUDO_NODUMP,
#else
NULL,
#endif
#ifdef _PATH_SUDO_NOEXEC
_PATH_SUDO_NOEXEC,
#else
NULL,
#endif
#ifdef _PATH_SUDO_PLUGIN_DIR
_PATH_SUDO_PLUGIN_DIR,
#else
NULL,
#endif
};
static struct sudo_conf_data {
bool disable_coredump;
bool probe_interfaces;
int group_source;
int max_groups;
struct sudo_conf_debug_list debugging;
struct plugin_info_list plugins;
struct sudo_conf_path_table path_table[5];
} sudo_conf_data = {
true,
true,
GROUP_SOURCE_ADAPTIVE,
-1,
TAILQ_HEAD_INITIALIZER(sudo_conf_data.debugging),
TAILQ_HEAD_INITIALIZER(sudo_conf_data.plugins),
{
{ "askpass", sizeof("askpass") - 1, &sudo_conf_paths.askpass },
{ "sesh", sizeof("sesh") - 1, &sudo_conf_paths.sesh },
{ "noexec", sizeof("noexec") - 1, &sudo_conf_paths.noexec },
{ "plugin_dir", sizeof("plugin_dir") - 1, &sudo_conf_paths.plugin_dir },
{ NULL }
}
};
/*
* "Set variable_name value"
*/
static int
parse_variable(const char *entry, const char *conf_file, unsigned int lineno)
{
struct sudo_conf_table *var;
int ret;
debug_decl(parse_variable, SUDO_DEBUG_UTIL)
for (var = sudo_conf_var_table; var->name != NULL; var++) {
if (strncmp(entry, var->name, var->namelen) == 0 &&
isblank((unsigned char)entry[var->namelen])) {
entry += var->namelen + 1;
while (isblank((unsigned char)*entry))
entry++;
ret = var->parser(entry, conf_file, lineno);
sudo_debug_printf(ret ? SUDO_DEBUG_INFO : SUDO_DEBUG_ERROR,
"%s: %s:%u: Set %s %s", __func__, conf_file,
lineno, var->name, entry);
debug_return_int(ret);
}
}
sudo_debug_printf(SUDO_DEBUG_WARN, "%s: %s:%u: unknown setting %s",
__func__, conf_file, lineno, entry);
debug_return_int(false);
}
/*
* "Path name /path/to/file"
* If path is missing it will be set to the NULL pointer.
*/
static int
parse_path(const char *entry, const char *conf_file, unsigned int lineno)
{
const char *entry_end = entry + strlen(entry);
const char *ep, *name, *path;
struct sudo_conf_path_table *cur;
size_t namelen;
debug_decl(parse_path, SUDO_DEBUG_UTIL)
/* Parse name. */
name = sudo_strsplit(entry, entry_end, " \t", &ep);
if (name == NULL)
goto bad;
namelen = (size_t)(ep - name);
/* Parse path (if present). */
path = sudo_strsplit(NULL, entry_end, " \t", &ep);
/* Match supported paths, ignoring unknown paths. */
for (cur = sudo_conf_data.path_table; cur->pname != NULL; cur++) {
if (namelen == cur->pnamelen &&
strncasecmp(name, cur->pname, cur->pnamelen) == 0) {
char *pval = NULL;
if (path != NULL) {
if ((pval = strdup(path)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
debug_return_int(-1);
}
}
*cur->pval = pval;
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %s:%u: Path %s %s",
__func__, conf_file, lineno, cur->pname,
pval ? pval : "(none)");
debug_return_int(true);
}
}
sudo_debug_printf(SUDO_DEBUG_WARN, "%s: %s:%u: unknown path %s",
__func__, conf_file, lineno, entry);
debug_return_int(false);
bad:
sudo_warnx(U_("invalid Path value `%s' in %s, line %u"),
entry, conf_file, lineno);
debug_return_int(false);
}
/*
* "Debug program /path/to/log flags,..."
*/
static int
parse_debug(const char *entry, const char *conf_file, unsigned int lineno)
{
struct sudo_conf_debug *debug_spec;
struct sudo_debug_file *debug_file = NULL;
const char *ep, *path, *progname, *flags;
const char *entry_end = entry + strlen(entry);
size_t pathlen, prognamelen;
debug_decl(parse_debug, SUDO_DEBUG_UTIL)
/* Parse progname. */
progname = sudo_strsplit(entry, entry_end, " \t", &ep);
if (progname == NULL)
debug_return_int(false); /* not enough fields */
prognamelen = (size_t)(ep - progname);
/* Parse path. */
path = sudo_strsplit(NULL, entry_end, " \t", &ep);
if (path == NULL)
debug_return_int(false); /* not enough fields */
pathlen = (size_t)(ep - path);
/* Remainder is flags (freeform). */
flags = sudo_strsplit(NULL, entry_end, " \t", &ep);
if (flags == NULL)
debug_return_int(false); /* not enough fields */
/* If progname already exists, use it, else alloc a new one. */
TAILQ_FOREACH(debug_spec, &sudo_conf_data.debugging, entries) {
if (strncmp(debug_spec->progname, progname, prognamelen) == 0 &&
debug_spec->progname[prognamelen] == '\0')
break;
}
if (debug_spec == NULL) {
debug_spec = malloc(sizeof(*debug_spec));
if (debug_spec == NULL)
goto oom;
debug_spec->progname = strndup(progname, prognamelen);
if (debug_spec->progname == NULL) {
free(debug_spec);
debug_spec = NULL;
goto oom;
}
TAILQ_INIT(&debug_spec->debug_files);
TAILQ_INSERT_TAIL(&sudo_conf_data.debugging, debug_spec, entries);
}
debug_file = calloc(1, sizeof(*debug_file));
if (debug_file == NULL)
goto oom;
debug_file->debug_file = strndup(path, pathlen);
if (debug_file->debug_file == NULL)
goto oom;
debug_file->debug_flags = strdup(flags);
if (debug_file->debug_flags == NULL)
goto oom;
TAILQ_INSERT_TAIL(&debug_spec->debug_files, debug_file, entries);
debug_return_int(true);
oom:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (debug_file != NULL) {
free(debug_file->debug_file);
free(debug_file->debug_flags);
free(debug_file);
}
debug_return_int(-1);
}
/*
* "Plugin symbol /path/to/log args..."
*/
static int
parse_plugin(const char *entry, const char *conf_file, unsigned int lineno)
{
struct plugin_info *info = NULL;
const char *ep, *path, *symbol;
const char *entry_end = entry + strlen(entry);
char **options = NULL;
size_t pathlen, symlen;
unsigned int nopts = 0;
debug_decl(parse_plugin, SUDO_DEBUG_UTIL)
/* Parse symbol. */
symbol = sudo_strsplit(entry, entry_end, " \t", &ep);
if (symbol == NULL)
debug_return_int(false); /* not enough fields */
symlen = (size_t)(ep - symbol);
/* Parse path. */
path = sudo_strsplit(NULL, entry_end, " \t", &ep);
if (path == NULL)
debug_return_int(false); /* not enough fields */
pathlen = (size_t)(ep - path);
/* Split options into an array if present. */
while (isblank((unsigned char)*ep))
ep++;
if (*ep != '\0') {
/* Count number of options and allocate array. */
const char *cp, *opt = ep;
/* Count and allocate options array. */
for (nopts = 0, cp = sudo_strsplit(opt, entry_end, " \t", &ep);
cp != NULL; cp = sudo_strsplit(NULL, entry_end, " \t", &ep)) {
nopts++;
}
options = reallocarray(NULL, nopts + 1, sizeof(*options));
if (options == NULL)
goto oom;
/* Fill in options array. */
for (nopts = 0, cp = sudo_strsplit(opt, entry_end, " \t", &ep);
cp != NULL; cp = sudo_strsplit(NULL, entry_end, " \t", &ep)) {
options[nopts] = strndup(cp, (size_t)(ep - cp));
if (options[nopts] == NULL)
goto oom;
nopts++;
}
options[nopts] = NULL;
}
info = calloc(sizeof(*info), 1);
if (info == NULL)
goto oom;
info->symbol_name = strndup(symbol, symlen);
if (info->symbol_name == NULL)
goto oom;
info->path = strndup(path, pathlen);
if (info->path == NULL)
goto oom;
info->options = options;
info->lineno = lineno;
TAILQ_INSERT_TAIL(&sudo_conf_data.plugins, info, entries);
debug_return_int(true);
oom:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (options != NULL) {
while (nopts--)
free(options[nopts]);
free(options);
}
if (info != NULL) {
free(info->symbol_name);
free(info->path);
free(info);
}
debug_return_int(-1);
}
static int
set_var_disable_coredump(const char *strval, const char *conf_file,
unsigned int lineno)
{
int val = sudo_strtobool(strval);
debug_decl(set_var_disable_coredump, SUDO_DEBUG_UTIL)
if (val == -1) {
sudo_warnx(U_("invalid value for %s `%s' in %s, line %u"),
"disable_coredump", strval, conf_file, lineno);
debug_return_bool(false);
}
sudo_conf_data.disable_coredump = val;
debug_return_bool(true);
}
static int
set_var_group_source(const char *strval, const char *conf_file,
unsigned int lineno)
{
debug_decl(set_var_group_source, SUDO_DEBUG_UTIL)
if (strcasecmp(strval, "adaptive") == 0) {
sudo_conf_data.group_source = GROUP_SOURCE_ADAPTIVE;
} else if (strcasecmp(strval, "static") == 0) {
sudo_conf_data.group_source = GROUP_SOURCE_STATIC;
} else if (strcasecmp(strval, "dynamic") == 0) {
sudo_conf_data.group_source = GROUP_SOURCE_DYNAMIC;
} else {
sudo_warnx(U_("unsupported group source `%s' in %s, line %u"), strval,
conf_file, lineno);
debug_return_bool(false);
}
debug_return_bool(true);
}
static int
set_var_max_groups(const char *strval, const char *conf_file,
unsigned int lineno)
{
int max_groups;
debug_decl(set_var_max_groups, SUDO_DEBUG_UTIL)
max_groups = strtonum(strval, 1, INT_MAX, NULL);
if (max_groups <= 0) {
sudo_warnx(U_("invalid max groups `%s' in %s, line %u"), strval,
conf_file, lineno);
debug_return_bool(false);
}
sudo_conf_data.max_groups = max_groups;
debug_return_bool(true);
}
static int
set_var_probe_interfaces(const char *strval, const char *conf_file,
unsigned int lineno)
{
int val = sudo_strtobool(strval);
debug_decl(set_var_probe_interfaces, SUDO_DEBUG_UTIL)
if (val == -1) {
sudo_warnx(U_("invalid value for %s `%s' in %s, line %u"),
"probe_interfaces", strval, conf_file, lineno);
debug_return_bool(false);
}
sudo_conf_data.probe_interfaces = val;
debug_return_bool(true);
}
const char *
sudo_conf_askpass_path_v1(void)
{
return sudo_conf_paths.askpass;
}
const char *
sudo_conf_sesh_path_v1(void)
{
return sudo_conf_paths.sesh;
}
#ifdef _PATH_SUDO_NOEXEC
const char *
sudo_conf_noexec_path_v1(void)
{
return sudo_conf_paths.noexec;
}
#endif
#ifdef _PATH_SUDO_PLUGIN_DIR
const char *
sudo_conf_plugin_dir_path_v1(void)
{
return sudo_conf_paths.plugin_dir;
}
#endif
int
sudo_conf_group_source_v1(void)
{
return sudo_conf_data.group_source;
}
int
sudo_conf_max_groups_v1(void)
{
return sudo_conf_data.max_groups;
}
struct plugin_info_list *
sudo_conf_plugins_v1(void)
{
return &sudo_conf_data.plugins;
}
struct sudo_conf_debug_list *
sudo_conf_debugging_v1(void)
{
return &sudo_conf_data.debugging;
}
/* Return the debug files list for a program, or NULL if none. */
struct sudo_conf_debug_file_list *
sudo_conf_debug_files_v1(const char *progname)
{
struct sudo_conf_debug *debug_spec;
size_t prognamelen, progbaselen;
const char *progbase = progname;
debug_decl(sudo_conf_debug_files, SUDO_DEBUG_UTIL)
/* Determine basename if program is fully qualified (like for plugins). */
prognamelen = progbaselen = strlen(progname);
if (*progname == '/') {
progbase = strrchr(progname, '/');
progbaselen = strlen(++progbase);
}
/* Convert sudoedit -> sudo. */
if (progbaselen > 4 && strcmp(progbase + 4, "edit") == 0) {
progbaselen -= 4;
}
TAILQ_FOREACH(debug_spec, &sudo_conf_data.debugging, entries) {
const char *prog = progbase;
size_t len = progbaselen;
if (debug_spec->progname[0] == '/') {
/* Match fully-qualified name, if possible. */
prog = progname;
len = prognamelen;
}
if (strncmp(debug_spec->progname, prog, len) == 0 &&
debug_spec->progname[len] == '\0') {
debug_return_ptr(&debug_spec->debug_files);
}
}
debug_return_ptr(NULL);
}
bool
sudo_conf_disable_coredump_v1(void)
{
return sudo_conf_data.disable_coredump;
}
bool
sudo_conf_probe_interfaces_v1(void)
{
return sudo_conf_data.probe_interfaces;
}
/*
* Reads in /etc/sudo.conf and populates sudo_conf_data.
*/
int
sudo_conf_read_v1(const char *conf_file, int conf_types)
{
struct stat sb;
FILE *fp = NULL;
int ret = false;
char *prev_locale, *line = NULL;
unsigned int conf_lineno = 0;
size_t linesize = 0;
debug_decl(sudo_conf_read, SUDO_DEBUG_UTIL)
if ((prev_locale = setlocale(LC_ALL, NULL)) == NULL) {
sudo_warn("setlocale(LC_ALL, NULL)");
debug_return_int(-1);
}
if ((prev_locale = strdup(prev_locale)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_int(-1);
}
/* Parse sudo.conf in the "C" locale. */
if (prev_locale[0] != 'C' || prev_locale[1] != '\0')
setlocale(LC_ALL, "C");
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)
sudo_warn(U_("unable to stat %s"), conf_file);
goto done;
case SUDO_PATH_BAD_TYPE:
sudo_warnx(U_("%s is not a regular file"), conf_file);
goto done;
case SUDO_PATH_WRONG_OWNER:
sudo_warnx(U_("%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:
sudo_warnx(U_("%s is world writable"), conf_file);
goto done;
case SUDO_PATH_GROUP_WRITABLE:
sudo_warnx(U_("%s is group writable"), conf_file);
goto done;
default:
/* NOTREACHED */
goto done;
}
}
if ((fp = fopen(conf_file, "r")) == NULL) {
if (errno != ENOENT && geteuid() == ROOT_UID)
sudo_warn(U_("unable to open %s"), conf_file);
goto done;
}
while (sudo_parseln(&line, &linesize, &conf_lineno, fp, 0) != -1) {
struct sudo_conf_table *cur;
unsigned int i;
char *cp;
if (*(cp = line) == '\0')
continue; /* empty line or comment */
for (i = 0, cur = sudo_conf_table; cur->name != NULL; i++, cur++) {
if (strncasecmp(cp, cur->name, cur->namelen) == 0 &&
isblank((unsigned char)cp[cur->namelen])) {
if (ISSET(conf_types, (1 << i))) {
cp += cur->namelen;
while (isblank((unsigned char)*cp))
cp++;
ret = cur->parser(cp, conf_file, conf_lineno);
if (ret == -1)
goto done;
}
break;
}
}
if (cur->name == NULL) {
sudo_debug_printf(SUDO_DEBUG_WARN,
"%s: %s:%u: unsupported entry: %s", __func__, conf_file,
conf_lineno, line);
}
}
ret = true;
done:
if (fp != NULL)
fclose(fp);
free(line);
/* Restore locale if needed. */
if (prev_locale[0] != 'C' || prev_locale[1] != '\0')
setlocale(LC_ALL, prev_locale);
free(prev_locale);
debug_return_int(ret);
}